diff options
Diffstat (limited to 'apps/cuesheet.c')
-rw-r--r-- | apps/cuesheet.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/apps/cuesheet.c b/apps/cuesheet.c new file mode 100644 index 0000000000..6db3528cad --- /dev/null +++ b/apps/cuesheet.c | |||
@@ -0,0 +1,360 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $$ | ||
9 | * | ||
10 | * Copyright (C) 2007 Nicolas Pennequin, Jonathan Gordon | ||
11 | * | ||
12 | * All files in this archive are subject to the GNU General Public License. | ||
13 | * See the file COPYING in the source tree root for full license agreement. | ||
14 | * | ||
15 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
16 | * KIND, either express or implied. | ||
17 | * | ||
18 | ****************************************************************************/ | ||
19 | |||
20 | #include <stdio.h> | ||
21 | #include <stdlib.h> | ||
22 | #include <stdbool.h> | ||
23 | #include <atoi.h> | ||
24 | #include <string.h> | ||
25 | #include "system.h" | ||
26 | #include "audio.h" | ||
27 | #include "kernel.h" | ||
28 | #include "logf.h" | ||
29 | #include "sprintf.h" | ||
30 | #include "misc.h" | ||
31 | #include "screens.h" | ||
32 | #include "splash.h" | ||
33 | #include "list.h" | ||
34 | #include "action.h" | ||
35 | #include "lang.h" | ||
36 | #include "debug.h" | ||
37 | #include "settings.h" | ||
38 | #include "buffer.h" | ||
39 | #include "plugin.h" | ||
40 | #include "playback.h" | ||
41 | #include "cuesheet.h" | ||
42 | |||
43 | |||
44 | void cuesheet_init(void) | ||
45 | { | ||
46 | if (global_settings.cuesheet) { | ||
47 | curr_cue = (struct cuesheet *)buffer_alloc(MAX_TRACKS * sizeof(struct cuesheet)); | ||
48 | temp_cue = (struct cuesheet *)buffer_alloc(MAX_TRACKS * sizeof(struct cuesheet)); | ||
49 | } else { | ||
50 | curr_cue = NULL; | ||
51 | temp_cue = NULL; | ||
52 | } | ||
53 | } | ||
54 | |||
55 | bool cuesheet_is_enabled(void) | ||
56 | { | ||
57 | return (curr_cue != NULL); | ||
58 | } | ||
59 | |||
60 | bool look_for_cuesheet_file(const char *trackpath) | ||
61 | { | ||
62 | char cuepath[MAX_PATH]; | ||
63 | strncpy(cuepath, trackpath, MAX_PATH); | ||
64 | char *dot = strrchr(cuepath, '.'); | ||
65 | strcpy(dot, ".cue"); | ||
66 | |||
67 | int fd = open(cuepath,O_RDONLY); | ||
68 | if (fd < 0) | ||
69 | { | ||
70 | return false; | ||
71 | } | ||
72 | else | ||
73 | { | ||
74 | close(fd); | ||
75 | return true; | ||
76 | } | ||
77 | } | ||
78 | |||
79 | char *skip_whitespace(char* buf) | ||
80 | { | ||
81 | char *r = buf; | ||
82 | while (*r && (*r < 33)) | ||
83 | r++; | ||
84 | return r; | ||
85 | } | ||
86 | |||
87 | /* parse cuesheet "file" and store the information in "cue" */ | ||
88 | bool parse_cuesheet(char *file, struct cuesheet *cue) | ||
89 | { | ||
90 | char line[MAX_PATH]; | ||
91 | char *s, *start, *end; | ||
92 | int fd = open(file,O_RDONLY); | ||
93 | if (fd < 0) | ||
94 | { | ||
95 | /* couln't open the file */ | ||
96 | return false; | ||
97 | } | ||
98 | |||
99 | memset(cue, 0, sizeof(struct cuesheet)); | ||
100 | |||
101 | strcpy(cue->path, file); | ||
102 | |||
103 | cue->curr_track_idx = 0; | ||
104 | cue->curr_track = cue->tracks; | ||
105 | |||
106 | cue->track_count = 0; | ||
107 | while (read_line(fd,line,MAX_PATH)) | ||
108 | { | ||
109 | s = skip_whitespace(line); | ||
110 | if (!strncmp(s, "TITLE", 5)) | ||
111 | { | ||
112 | start = strchr(s,'"'); | ||
113 | if (!start) | ||
114 | break; | ||
115 | end = strchr(++start,'"'); | ||
116 | if (!end) | ||
117 | break; | ||
118 | *end = '\0'; | ||
119 | if (cue->track_count <= 0) | ||
120 | strncpy(cue->title,start,MAX_NAME); | ||
121 | else strncpy(cue->tracks[cue->track_count-1].title, | ||
122 | start,MAX_NAME); | ||
123 | } | ||
124 | else if (!strncmp(s, "PERFORMER", 9)) | ||
125 | { | ||
126 | start = strchr(s,'"'); | ||
127 | if (!start) | ||
128 | break; | ||
129 | end = strchr(++start,'"'); | ||
130 | if (!end) | ||
131 | break; | ||
132 | *end = '\0'; | ||
133 | if (cue->track_count <= 0) | ||
134 | strncpy(cue->performer,start,MAX_NAME); | ||
135 | else strncpy(cue->tracks[cue->track_count-1].performer, | ||
136 | start,MAX_NAME); | ||
137 | } | ||
138 | else if (!strncmp(s, "TRACK", 5)) | ||
139 | { | ||
140 | if (cue->track_count >= MAX_TRACKS) | ||
141 | break; /* out of memeory! stop parsing */ | ||
142 | cue->track_count++; | ||
143 | } | ||
144 | else if (!strncmp(s, "INDEX", 5)) | ||
145 | { | ||
146 | s = strchr(s,' '); | ||
147 | s = skip_whitespace(s); | ||
148 | s = strchr(s,' '); | ||
149 | s = skip_whitespace(s); | ||
150 | cue->tracks[cue->track_count-1].offset = 60*1000 * atoi(s); | ||
151 | s = strchr(s,':') + 1; | ||
152 | cue->tracks[cue->track_count-1].offset += 1000 * atoi(s); | ||
153 | s = strchr(s,':') + 1; | ||
154 | cue->tracks[cue->track_count-1].offset += 13 * atoi(s); | ||
155 | } | ||
156 | } | ||
157 | close(fd); | ||
158 | |||
159 | /* If some songs don't have performer info, we copy the cuesheet performer */ | ||
160 | int i; | ||
161 | for (i = 0; i < cue->track_count; i++) | ||
162 | { | ||
163 | if (*(cue->tracks[i].performer) == '\0') | ||
164 | { | ||
165 | strncpy(cue->tracks[i].performer, cue->performer, MAX_NAME); | ||
166 | } | ||
167 | } | ||
168 | |||
169 | return true; | ||
170 | } | ||
171 | |||
172 | /* takes care of seeking to a track in a playlist | ||
173 | * returns false if audio isn't playing */ | ||
174 | bool seek(unsigned long pos) | ||
175 | { | ||
176 | if (!(audio_status() & AUDIO_STATUS_PLAY)) | ||
177 | { | ||
178 | return false; | ||
179 | } | ||
180 | else | ||
181 | { | ||
182 | #if (CONFIG_CODEC == SWCODEC) | ||
183 | audio_pre_ff_rewind(); | ||
184 | #else | ||
185 | audio_pause(); | ||
186 | #endif | ||
187 | audio_ff_rewind(pos); | ||
188 | return true; | ||
189 | } | ||
190 | } | ||
191 | |||
192 | /* returns the index of the track currently being played | ||
193 | and updates the information about the current track. */ | ||
194 | int cue_find_current_track(struct cuesheet *cue, unsigned long curpos) | ||
195 | { | ||
196 | int i=0; | ||
197 | while (i < cue->track_count-1 && cue->tracks[i+1].offset < curpos) | ||
198 | { | ||
199 | i++; | ||
200 | } | ||
201 | cue->curr_track_idx = i; | ||
202 | cue->curr_track = cue->tracks + i; | ||
203 | return i; | ||
204 | } | ||
205 | |||
206 | /* callback that gives list item titles for the cuesheet browser */ | ||
207 | char *list_get_name_cb(int selected_item, | ||
208 | void *data, | ||
209 | char *buffer) | ||
210 | { | ||
211 | struct cuesheet *cue = (struct cuesheet *)data; | ||
212 | |||
213 | if (selected_item & 1) | ||
214 | { | ||
215 | snprintf(buffer, MAX_PATH, | ||
216 | (selected_item+1)/2 > 9 ? " %s" : " %s", | ||
217 | cue->tracks[selected_item/2].title); | ||
218 | } | ||
219 | else | ||
220 | { | ||
221 | snprintf(buffer, MAX_PATH, "%d %s", selected_item/2+1, | ||
222 | cue->tracks[selected_item/2].performer); | ||
223 | } | ||
224 | return buffer; | ||
225 | } | ||
226 | |||
227 | void browse_cuesheet(struct cuesheet *cue) | ||
228 | { | ||
229 | struct gui_synclist lists; | ||
230 | int action; | ||
231 | bool done = false; | ||
232 | int sel; | ||
233 | char title[MAX_PATH]; | ||
234 | char cuepath[MAX_PATH]; | ||
235 | char *dot; | ||
236 | struct mp3entry *id3 = audio_current_track(); | ||
237 | |||
238 | snprintf(title, MAX_PATH, "%s: %s", cue->performer, cue->title); | ||
239 | gui_synclist_init(&lists, list_get_name_cb, cue, false, 2); | ||
240 | gui_synclist_set_nb_items(&lists, 2*cue->track_count); | ||
241 | gui_synclist_set_title(&lists, title, 0); | ||
242 | |||
243 | if (strcmp(id3->path, "No file!")) | ||
244 | { | ||
245 | strncpy(cuepath, id3->path, MAX_PATH); | ||
246 | dot = strrchr(cuepath, '.'); | ||
247 | strcpy(dot, ".cue"); | ||
248 | } | ||
249 | |||
250 | if (id3->cuesheet_type && !strcmp(cue->path, cuepath)) | ||
251 | { | ||
252 | gui_synclist_select_item(&lists, | ||
253 | 2*cue_find_current_track(cue, id3->elapsed)); | ||
254 | } | ||
255 | |||
256 | while (!done) | ||
257 | { | ||
258 | gui_synclist_draw(&lists); | ||
259 | action = get_action(CONTEXT_LIST,TIMEOUT_BLOCK); | ||
260 | if (gui_synclist_do_button(&lists,action,LIST_WRAP_UNLESS_HELD)) | ||
261 | continue; | ||
262 | switch (action) | ||
263 | { | ||
264 | case ACTION_STD_OK: | ||
265 | id3 = audio_current_track(); | ||
266 | if (strcmp(id3->path, "No file!")) | ||
267 | { | ||
268 | strncpy(cuepath, id3->path, MAX_PATH); | ||
269 | dot = strrchr(cuepath, '.'); | ||
270 | strcpy(dot, ".cue"); | ||
271 | if (id3->cuesheet_type && !strcmp(cue->path, cuepath)) | ||
272 | { | ||
273 | sel = gui_synclist_get_sel_pos(&lists); | ||
274 | seek(cue->tracks[sel/2].offset); | ||
275 | } | ||
276 | } | ||
277 | break; | ||
278 | case ACTION_STD_CANCEL: | ||
279 | done = true; | ||
280 | } | ||
281 | } | ||
282 | } | ||
283 | |||
284 | bool display_cuesheet_content(char* filename) | ||
285 | { | ||
286 | int bufsize = 0; | ||
287 | struct cuesheet *cue = (struct cuesheet *)plugin_get_buffer(&bufsize); | ||
288 | if (!cue) | ||
289 | return false; | ||
290 | |||
291 | if (!parse_cuesheet(filename, cue)) | ||
292 | return false; | ||
293 | |||
294 | browse_cuesheet(cue); | ||
295 | return true; | ||
296 | } | ||
297 | |||
298 | /* skips backwards or forward in the current cuesheet | ||
299 | * the return value indicates whether we're still in a cusheet after skipping | ||
300 | * it also returns false if we weren't in a cuesheet. | ||
301 | * direction should be 1 or -1. | ||
302 | */ | ||
303 | bool curr_cuesheet_skip(int direction, unsigned long curr_pos) | ||
304 | { | ||
305 | int track = cue_find_current_track(curr_cue, curr_pos); | ||
306 | |||
307 | if (direction >= 0 && track == curr_cue->track_count - 1) | ||
308 | { | ||
309 | /* we want to get out of the cuesheet */ | ||
310 | return false; | ||
311 | } | ||
312 | else | ||
313 | { | ||
314 | if (!(direction <= 0 && track == 0)) | ||
315 | track += direction; | ||
316 | |||
317 | seek(curr_cue->tracks[track].offset); | ||
318 | return true; | ||
319 | } | ||
320 | |||
321 | } | ||
322 | |||
323 | void cue_spoof_id3(struct cuesheet *cue, struct mp3entry *id3) | ||
324 | { | ||
325 | if (!cue) | ||
326 | return; | ||
327 | |||
328 | int i = cue->curr_track_idx; | ||
329 | |||
330 | id3->title = cue->tracks[i].title; | ||
331 | id3->artist = cue->tracks[i].performer; | ||
332 | id3->tracknum = i+1; | ||
333 | id3->album = cue->title; | ||
334 | id3->composer = cue->performer; | ||
335 | if (id3->track_string) | ||
336 | snprintf(id3->track_string, 10, "%d/%d", i+1, cue->track_count); | ||
337 | } | ||
338 | |||
339 | #ifdef HAVE_LCD_BITMAP | ||
340 | static inline void draw_veritcal_line_mark(struct screen * screen, | ||
341 | int x, int y, int h) | ||
342 | { | ||
343 | screen->set_drawmode(DRMODE_COMPLEMENT); | ||
344 | screen->vline(x, y, y+h-1); | ||
345 | } | ||
346 | |||
347 | /* draw the cuesheet markers for a track of length "tracklen", | ||
348 | between (x1,y) and (x2,y) */ | ||
349 | void cue_draw_markers(struct screen *screen, unsigned long tracklen, | ||
350 | int x1, int x2, int y, int h) | ||
351 | { | ||
352 | int i,xi; | ||
353 | int w = x2 - x1; | ||
354 | for (i=1; i < curr_cue->track_count; i++) | ||
355 | { | ||
356 | xi = x1 + (w * curr_cue->tracks[i].offset)/tracklen; | ||
357 | draw_veritcal_line_mark(screen, xi, y, h); | ||
358 | } | ||
359 | } | ||
360 | #endif | ||