core_alloc: Provide a tiny test allocation, which can be freed for debug purposes.
[maemo-rb.git] / apps / plugins / lrcplayer.c
blobf0b3e47728b1be7871d3bccc0cc6b813065fe056
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2008-2009 Teruaki Kawashima
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
21 #include "plugin.h"
22 #include "lib/playback_control.h"
23 #include "lib/configfile.h"
24 #include "lib/helper.h"
25 #include <ctype.h>
29 #define MAX_LINE_LEN 256
30 #define LRC_BUFFER_SIZE 0x3000 /* 12 kiB */
31 #if PLUGIN_BUFFER_SIZE >= 0x10000 /* no id3 support for low mem targets */
32 /* define this to read lyrics in id3 tag */
33 #define LRC_SUPPORT_ID3
34 #endif
35 /* define this to show debug info in menu */
36 /* #define LRC_DEBUG */
38 enum lrc_screen {
39 PLUGIN_OTHER = 0x200,
40 LRC_GOTO_MAIN,
41 LRC_GOTO_MENU,
42 LRC_GOTO_EDITOR,
45 struct lrc_word {
46 long time_start;
47 short count;
48 short width;
49 unsigned char *word;
52 struct lrc_brpos {
53 short count;
54 short width;
57 struct lrc_line {
58 long time_start;
59 long old_time_start;
60 off_t file_offset; /* offset of time tag in file */
61 short nword;
62 short width;
63 short nline[NB_SCREENS];
64 struct lrc_line *next;
65 struct lrc_word *words;
68 struct preferences {
69 /* display settings */
70 #if LCD_DEPTH > 1
71 unsigned active_color;
72 unsigned inactive_color;
73 #endif
74 #ifdef HAVE_LCD_BITMAP
75 bool wrap;
76 bool wipe;
77 bool active_one_line;
78 int align; /* 0: left, 1: center, 2: right */
79 bool statusbar_on;
80 bool display_title;
81 #endif
82 bool display_time;
83 bool backlight_on;
85 /* file settings */
86 char lrc_directory[64];
87 int encoding;
88 #ifdef LRC_SUPPORT_ID3
89 bool read_id3;
90 #endif
93 static struct preferences prefs, old_prefs;
94 static unsigned char *lrc_buffer;
95 static size_t lrc_buffer_size;
96 static size_t lrc_buffer_used, lrc_buffer_end;
97 enum extention_types {LRC, LRC8, SNC, TXT, NUM_TYPES, ID3_SYLT, ID3_USLT};
98 static const char *extentions[NUM_TYPES] = {
99 ".lrc", ".lrc8", ".snc", ".txt",
101 static struct lrc_info {
102 struct mp3entry *id3;
103 long elapsed;
104 long length;
105 long ff_rewind;
106 int audio_status;
107 char mp3_file[MAX_PATH];
108 char lrc_file[MAX_PATH];
109 char *title; /* use lrc_buffer */
110 char *artist; /* use lrc_buffer */
111 enum extention_types type;
112 long offset; /* msec */
113 off_t offset_file_offset; /* offset of offset tag in file */
114 int nlrcbrpos;
115 int nlrcline;
116 struct lrc_line *ll_head, **ll_tail;
117 bool found_lrc;
118 bool loaded_lrc;
119 bool changed_lrc;
120 bool too_many_lines; /* true if nlrcline >= max_lrclines after calc pos */
121 #ifdef HAVE_LCD_BITMAP
122 bool wipe; /* false if lyrics is unsynched */
123 #endif
124 } current;
125 static char temp_buf[MAX(MAX_LINE_LEN,MAX_PATH)];
126 #ifdef HAVE_LCD_BITMAP
127 static int font_ui_height = 1;
128 static struct viewport vp_info[NB_SCREENS];
129 #endif
130 static struct viewport vp_lyrics[NB_SCREENS];
132 #define AUDIO_PAUSE (current.audio_status & AUDIO_STATUS_PAUSE)
133 #define AUDIO_PLAY (current.audio_status & AUDIO_STATUS_PLAY)
134 #define AUDIO_STOP (!(current.audio_status & AUDIO_STATUS_PLAY))
136 /*******************************
137 * lrc_set_time
138 *******************************/
139 #define LST_SET_MSEC 0x00010000
140 #define LST_SET_SEC 0x00020000
141 #define LST_SET_MIN 0x00040000
142 #define LST_SET_HOUR 0x00080000
144 #include "lib/pluginlib_actions.h"
145 #define LST_SET_TIME (LST_SET_MSEC|LST_SET_SEC|LST_SET_MIN|LST_SET_HOUR)
146 #ifdef HAVE_LCD_CHARCELLS
147 #define LST_OFF_Y 0
148 #else /* HAVE_LCD_BITMAP */
149 #define LST_OFF_Y 1
150 #endif
151 static int lrc_set_time(const char *title, const char *unit, long *pval,
152 int step, int min, int max, int flags)
154 const struct button_mapping *lst_contexts[] = {
155 pla_main_ctx,
156 #ifdef HAVE_REMOTE_LCD
157 pla_remote_ctx,
158 #endif
160 /* how many */
161 const unsigned char formats[4][8] = {"%03ld.", "%02ld.", "%02ld:", "%02ld:"};
162 const unsigned int maxs[4] = {1000, 60, 60, 24};
163 const unsigned int scls[4] = {1, 1000, 60*1000, 60*60*1000};
164 char buffer[32];
165 long value = *pval, scl_step = step, i = 0;
166 int pos = 0, last_pos = 0, pos_min = 3, pos_max = 0;
167 int x = 0, y = 0, p_start = 0, p_end = 0;
168 int ret = 10;
170 if (!(flags&LST_SET_TIME))
171 return -1;
173 for (i = 0; i < 4; i++)
175 if (flags&(LST_SET_MSEC<<i))
177 if (pos_min > i) pos_min = i;
178 if (pos_max < i) pos_max = i;
181 pos = pos_min;
183 rb->button_clear_queue();
184 rb->lcd_clear_display();
185 rb->lcd_puts_scroll(0, LST_OFF_Y, title);
186 while (ret == 10)
188 int len = 0;
189 long abs_val = value;
190 long segvals[4] = {-1, -1, -1, -1};
191 /* show negative value like -00:01 but 00:-1 */
192 if (value < 0)
194 buffer[len++] = '-';
195 abs_val = -value;
197 buffer[len] = 0;
198 /* calc value of each segments */
199 for (i = pos_min; i <= pos_max; i++)
201 segvals[i] = abs_val % maxs[i];
202 abs_val /= maxs[i];
204 segvals[i-1] += abs_val * maxs[i-1];
205 for (i = pos_max; i >= pos_min; i--)
207 if (pos == i)
209 rb->lcd_getstringsize(buffer, &x, &y);
210 p_start = len;
212 rb->snprintf(&buffer[len], 32-len, formats[i], segvals[i]);
213 len += rb->strlen(&buffer[len]);
214 if (pos == i)
215 p_end = len;
217 buffer[len-1] = 0; /* remove last separater */
218 if (unit != NULL)
220 rb->snprintf(&buffer[len], 32-len, " (%s)", unit);
222 rb->lcd_puts(0, LST_OFF_Y+1, buffer);
223 if (pos_min != pos_max)
225 /* draw cursor */
226 buffer[p_end-1] = 0;
227 #ifdef HAVE_LCD_BITMAP
228 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
229 rb->lcd_putsxy(x, y*(1+LST_OFF_Y), &buffer[p_start]);
230 rb->lcd_set_drawmode(DRMODE_SOLID);
231 #else
232 rb->lcd_put_cursor(x+rb->utf8length(&buffer[p_start])-1, y, 0x7F);
233 #endif
235 rb->lcd_update();
236 int button = pluginlib_getaction(TIMEOUT_BLOCK, lst_contexts, ARRAYLEN(lst_contexts));
237 int mult = 1;
238 #ifdef HAVE_LCD_CHARCELLS
239 if (pos_min != pos_max)
240 rb->lcd_remove_cursor();
241 #endif
242 switch (button)
244 case PLA_UP_REPEAT:
245 case PLA_DOWN_REPEAT:
246 mult *= 10;
247 case PLA_DOWN:
248 case PLA_UP:
249 if (button == PLA_DOWN_REPEAT || button == PLA_DOWN)
250 mult *= -1;
251 if (pos != last_pos)
253 scl_step = ((scls[pos]/scls[pos_min]+step-1)/step) * step;
254 last_pos = pos;
256 value += scl_step * mult;
257 if (value > max)
258 value = max;
259 if (value < min)
260 value = min;
261 break;
262 case PLA_LEFT:
263 case PLA_LEFT_REPEAT:
264 if (++pos > pos_max)
265 pos = pos_min;
266 break;
267 case PLA_RIGHT:
268 case PLA_RIGHT_REPEAT:
269 if (--pos < pos_min)
270 pos = pos_max;
271 break;
272 case PLA_SELECT:
273 *pval = value;
274 ret = 0;
275 break;
276 case PLA_CANCEL:
277 case PLA_EXIT:
278 rb->splash(HZ, "Cancelled");
279 ret = -1;
280 break;
281 default:
282 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
283 ret = 1;
284 break;
287 rb->lcd_clear_display();
288 rb->lcd_update();
289 return ret;
292 /*******************************
293 * misc stuff
294 *******************************/
295 static void reset_current_data(void)
297 current.title = NULL;
298 current.artist = NULL;
299 current.offset = 0;
300 current.offset_file_offset = -1;
301 current.nlrcbrpos = 0;
302 current.nlrcline = 0;
303 current.ll_head = NULL;
304 current.ll_tail = &current.ll_head;
305 current.loaded_lrc = false;
306 current.changed_lrc = false;
307 current.too_many_lines = false;
308 lrc_buffer_used = 0;
309 lrc_buffer_end = lrc_buffer_size;
312 /* check space and add str to lrc_buffer.
313 * return NULL if there is not enough buffer. */
314 static char *lrcbufadd(const char*str, bool join)
316 if (join) lrc_buffer_used--;
317 size_t siz = rb->strlen(str)+1;
318 char *pos = &lrc_buffer[lrc_buffer_used];
319 if (lrc_buffer_used + siz > lrc_buffer_end)
320 return NULL;
321 rb->strcpy(pos, str);
322 lrc_buffer_used += siz;
323 return pos;
325 static void *alloc_buf(size_t siz)
327 siz = (siz+3) & ~3;
328 if (lrc_buffer_used + siz > lrc_buffer_end)
329 return NULL;
330 lrc_buffer_end -= siz;
331 return &lrc_buffer[lrc_buffer_end];
333 static void *new_lrc_word(long time_start, char *word, bool join)
335 struct lrc_word *lrc_word;
336 if ((lrc_word = alloc_buf(sizeof(struct lrc_word))) == NULL)
337 return NULL;
338 if ((lrc_word->word = lrcbufadd(word, join)) == NULL)
339 return NULL;
340 lrc_word->time_start = time_start;
341 return lrc_word;
343 static bool add_lrc_line(struct lrc_line *lrc_line, char *word)
345 lrc_line->nword = 0;
346 lrc_line->next = NULL;
347 lrc_line->words = NULL;
348 if (word)
350 if ((lrc_line->words = new_lrc_word(-1, word, false)) == NULL)
351 return false;
352 lrc_line->nword++;
354 *current.ll_tail = lrc_line;
355 current.ll_tail = &(lrc_line->next);
356 current.nlrcline++;
357 return true;
359 static struct lrc_line *get_lrc_line(int idx)
361 static struct lrc_line *lrc_line = NULL;
362 static int n = 0;
363 if (idx < n)
365 lrc_line = current.ll_head;
366 n = 0;
368 while (n < idx && lrc_line)
370 lrc_line = lrc_line->next;
371 n++;
373 return lrc_line;
375 static char *get_lrc_str(struct lrc_line *lrc_line)
377 return lrc_line->words[lrc_line->nword-1].word;
379 static long get_time_start(struct lrc_line *lrc_line)
381 if (!lrc_line) return current.length+20;
382 long time = lrc_line->time_start + current.offset;
383 return time < 0? 0: time;
385 static void set_time_start(struct lrc_line *lrc_line, long time_start)
387 time_start -= current.offset;
388 time_start -= time_start%10;
389 if (lrc_line->time_start != time_start)
391 lrc_line->time_start = time_start;
392 current.changed_lrc = true;
395 #define get_word_time_start(x) get_time_start((struct lrc_line *)(x))
396 #define set_word_time_start(x, t) set_time_start((struct lrc_line *)(x), (t))
398 static int format_time_tag(char *buf, long t)
400 return rb->snprintf(buf, 16, "%02ld:%02ld.%02ld",
401 t/60000, (t/1000)%60, (t/10)%100);
403 /* find start of next line */
404 static const char *lrc_skip_space(const char *str)
406 #ifdef HAVE_LCD_BITMAP
407 if (prefs.wrap)
409 while (*str && *str != '\n' && isspace(*str))
410 str++;
412 #endif
413 if (*str == '\n')
414 str++;
415 return str;
418 #ifdef HAVE_LCD_BITMAP
419 static bool isbrchr(const unsigned char *str, int len)
421 const unsigned char *p = "!,-.:;? 、。!,.:;?―";
422 if (isspace(*str))
423 return true;
425 while(*p)
427 int n = rb->utf8seek(p, 1);
428 if (len == n && !rb->strncmp(p, str, len))
429 return true;
430 p += n;
432 return false;
434 #endif
436 /* calculate how many lines is needed to display and store it.
437 * create cache if there is enough space in lrc_buffer. */
438 static struct lrc_brpos *calc_brpos(struct lrc_line *lrc_line, int i)
440 struct lrc_brpos *lrc_brpos;
441 struct lrc_word *lrc_word;
442 int nlrcbrpos = 0, max_lrcbrpos;
443 #ifdef HAVE_LCD_BITMAP
444 struct font* pf = rb->font_get(FONT_UI);
445 unsigned short ch;
446 #endif
447 struct snap {
448 int count, width;
449 int nword;
450 int word_count, word_width;
451 const unsigned char *str;
453 #ifndef HAVE_LCD_CHARCELLS
454 sp,
455 #endif
458 lrc_buffer_used = (lrc_buffer_used+3)&~3; /* 4 bytes aligned */
459 lrc_brpos = (struct lrc_brpos *) &lrc_buffer[lrc_buffer_used];
460 max_lrcbrpos = (lrc_buffer_end-lrc_buffer_used) / sizeof(struct lrc_brpos);
462 if (!lrc_line)
464 /* calc info for all lrcs and store them if possible */
465 size_t buffer_used = lrc_buffer_used;
466 bool too_many_lines = false;
467 current.too_many_lines = true;
468 for (lrc_line = current.ll_head; lrc_line; lrc_line = lrc_line->next)
470 FOR_NB_SCREENS(i)
472 lrc_brpos = calc_brpos(lrc_line, i);
473 if (!too_many_lines)
475 lrc_buffer_used += lrc_line->nline[i]*sizeof(struct lrc_brpos);
476 if (nlrcbrpos + lrc_line->nline[i] >= max_lrcbrpos)
478 too_many_lines = true;
479 lrc_buffer_used = buffer_used;
480 calc_brpos(lrc_line, i);
483 nlrcbrpos += lrc_line->nline[i];
486 current.too_many_lines = too_many_lines;
487 lrc_buffer_used = buffer_used;
488 current.nlrcbrpos = nlrcbrpos;
489 return NULL;
492 if (!current.too_many_lines)
494 /* use stored infos. */
495 struct lrc_line *temp_lrc = current.ll_head;
496 for (; temp_lrc != lrc_line; temp_lrc = temp_lrc->next)
498 lrc_brpos += temp_lrc->nline[SCREEN_MAIN];
499 #ifdef HAVE_REMOTE_LCD
500 lrc_brpos += temp_lrc->nline[SCREEN_REMOTE];
501 #endif
503 #if NB_SCREENS >= 2
504 while (i)
505 lrc_brpos += lrc_line->nline[--i];
506 #endif
507 return lrc_brpos;
510 /* calculate number of lines, line width and char count for each line. */
511 lrc_line->width = 0;
512 cr.nword = lrc_line->nword;
513 lrc_word = lrc_line->words+cr.nword;
514 cr.str = (lrc_word-1)->word;
515 #ifndef HAVE_LCD_CHARCELLS
516 sp.word_count = 0;
517 sp.word_width = 0;
518 sp.nword = 0;
519 sp.count = 0;
520 sp.width = 0;
521 #endif
522 do {
523 cr.count = 0;
524 cr.width = 0;
525 #ifndef HAVE_LCD_CHARCELLS
526 sp.str = NULL;
527 #endif
529 while (1)
531 while(cr.nword > 0 && cr.str >= (lrc_word-1)->word)
533 cr.nword--;
534 lrc_word--;
535 lrc_word->count = 0;
536 lrc_word->width = 0;
538 if (*cr.str == 0 || *cr.str == '\n')
539 break;
541 int c, w;
542 #ifdef HAVE_LCD_CHARCELLS
543 c = rb->utf8seek(cr.str, 1);
544 w = 1;
545 #else
546 c = ((long)rb->utf8decode(cr.str, &ch) - (long)cr.str);
547 if (rb->is_diacritic(ch, NULL))
548 w = 0;
549 else
550 w = rb->font_get_width(pf, ch);
551 if (cr.count && prefs.wrap && isbrchr(cr.str, c))
553 /* remember position of last space */
554 rb->memcpy(&sp, &cr, sizeof(struct snap));
555 sp.word_count = lrc_word->count;
556 sp.word_width = lrc_word->width;
557 if (!isspace(*cr.str) && cr.width+w <= vp_lyrics[i].width)
559 sp.count += c;
560 sp.width += w;
561 sp.word_count += c;
562 sp.word_width += w;
563 sp.str += c;
566 if (cr.count && cr.width+w > vp_lyrics[i].width)
568 if (sp.str != NULL) /* wrap */
570 rb->memcpy(&cr, &sp, sizeof(struct snap));
571 lrc_word = lrc_line->words+cr.nword;
572 lrc_word->count = sp.word_count;
573 lrc_word->width = sp.word_width;
575 break;
577 #endif
578 cr.count += c;
579 cr.width += w;
580 lrc_word->count += c;
581 lrc_word->width += w;
582 cr.str += c;
584 lrc_line->width += cr.width;
585 lrc_brpos->count = cr.count;
586 lrc_brpos->width = cr.width;
587 nlrcbrpos++;
588 lrc_brpos++;
589 cr.str = lrc_skip_space(cr.str);
590 } while (*cr.str && nlrcbrpos < max_lrcbrpos);
591 lrc_line->nline[i] = nlrcbrpos;
593 while (cr.nword > 0)
595 cr.nword--;
596 lrc_word--;
597 lrc_word->count = 0;
598 lrc_word->width = 0;
600 return lrc_brpos-nlrcbrpos;
603 /* sort lyrics by time using stable sort. */
604 static void sort_lrcs(void)
606 struct lrc_line *p = current.ll_head, **q = NULL, *t;
607 long time_max = 0;
609 current.ll_head = NULL;
610 current.ll_tail = &current.ll_head;
611 while (p != NULL)
613 t = p->next;
614 /* remove problematic lrc_lines.
615 * it would cause problem in display_lrc_line() if nword is 0. */
616 if (p->nword)
618 q = p->time_start >= time_max? current.ll_tail: &current.ll_head;
619 while ((*q) && (*q)->time_start <= p->time_start)
620 q = &((*q)->next);
621 p->next = *q;
622 *q = p;
623 if (!p->next)
625 time_max = p->time_start;
626 current.ll_tail = &p->next;
629 p = t;
631 if (!current.too_many_lines)
632 calc_brpos(NULL, 0); /* stored data depends on order of lrcs if exist */
634 static void init_time_tag(void)
636 struct lrc_line *lrc_line = current.ll_head;
637 int nline = 0;
638 if (current.type == TXT || current.type == ID3_USLT)
640 /* set time tag according to length of audio and total line count
641 * for not synched lyrics, so that scroll speed is almost constant. */
642 for (; lrc_line; lrc_line = lrc_line->next)
644 lrc_line->time_start = nline * current.length / current.nlrcbrpos;
645 lrc_line->time_start -= lrc_line->time_start%10;
646 lrc_line->old_time_start = -1;
647 nline += lrc_line->nline[SCREEN_MAIN];
648 #ifdef HAVE_REMOTE_LCD
649 nline += lrc_line->nline[SCREEN_REMOTE];
650 #endif
653 else
655 /* reset timetags to the value read from file */
656 for (; lrc_line; lrc_line = lrc_line->next)
658 lrc_line->time_start = lrc_line->old_time_start;
660 sort_lrcs();
662 current.changed_lrc = false;
665 /*******************************
666 * Serch lrc file.
667 *******************************/
669 /* search in same or parent directries of playing file.
670 * assume playing file is /aaa/bbb/ccc/ddd.mp3,
671 * this function searchs lrc file following order.
672 * /aaa/bbb/ccc/ddd.lrc
673 * /aaa/bbb/ddd.lrc
674 * /aaa/ddd.lrc
675 * /ddd.lrc
678 /* taken from apps/recorder/albumart.c */
679 static void fix_filename(char* name)
681 static const char invalid_chars[] = "*/:<>?\\|";
683 while (1)
685 if (*name == 0)
686 return;
687 if (*name == '"')
688 *name = '\'';
689 else if (rb->strchr(invalid_chars, *name))
690 *name = '_';
691 name++;
694 static bool find_lrc_file_helper(const char *base_dir)
696 char fname[MAX_PATH];
697 char *names[3] = {NULL, NULL, NULL};
698 char *p, *dir;
699 int i, len;
700 /* /aaa/bbb/ccc/ddd.mp3
701 * dir <--q names[0]
704 /* assuming file name starts with '/' */
705 rb->strcpy(temp_buf, current.mp3_file);
706 /* get file name and remove extension */
707 names[0] = rb->strrchr(temp_buf, '/')+1;
708 if ((p = rb->strrchr(names[0], '.')) != NULL)
709 *p = 0;
710 if (current.id3->title && rb->strcmp(names[0], current.id3->title))
712 rb->strlcpy(fname, current.id3->title, sizeof(fname));
713 fix_filename(fname);
714 names[1] = fname;
717 dir = temp_buf;
718 p = names[0]-1;
719 do {
720 int n;
721 *p = 0;
722 for (n = 0; ; n++)
724 if (n == 0)
726 len = rb->snprintf(current.lrc_file, MAX_PATH, "%s%s/",
727 base_dir, dir);
729 else if (n == 1)
731 /* check file in subfolder named prefs.lrc_directory
732 * in the directory of mp3 file. */
733 if (prefs.lrc_directory[0] == '/')
735 len = rb->snprintf(current.lrc_file, MAX_PATH, "%s%s/",
736 dir, prefs.lrc_directory);
738 else
739 continue;
741 else
742 break;
743 DEBUGF("check file in %s\n", current.lrc_file);
744 if (!rb->dir_exists(current.lrc_file))
745 continue;
746 for (current.type = 0; current.type < NUM_TYPES; current.type++)
748 for (i = 0; names[i] != NULL; i++)
750 rb->snprintf(&current.lrc_file[len], MAX_PATH-len,
751 "%s%s", names[i], extentions[current.type]);
752 if (rb->file_exists(current.lrc_file))
754 DEBUGF("found: `%s'\n", current.lrc_file);
755 return true;
760 } while ((p = rb->strrchr(dir, '/')) != NULL);
761 return false;
764 /* return true if a lrc file is found */
765 static bool find_lrc_file(void)
767 reset_current_data();
769 DEBUGF("find lrc file for `%s'\n", current.mp3_file);
770 /* find .lrc file */
771 if (find_lrc_file_helper(""))
772 return true;
773 if (prefs.lrc_directory[0] == '/' && rb->dir_exists(prefs.lrc_directory))
775 if (find_lrc_file_helper(prefs.lrc_directory))
776 return true;
779 current.lrc_file[0] = 0;
780 return false;
783 /*******************************
784 * Load file.
785 *******************************/
787 /* check tag format and calculate value of the tag.
788 * supported tag: ti, ar, offset
789 * supported format of time tag: [mm:ss], [mm:ss.xx], [mm:ss.xxx]
790 * returns value of timega if tag is time tag, -1 if tag is supported tag,
791 * -10 otherwise.
793 static char *parse_int(char *ptr, int *val)
795 *val = rb->atoi(ptr);
796 while (isdigit(*ptr)) ptr++;
797 return ptr;
799 static long get_time_value(char *tag, bool read_id_tags, off_t file_offset)
801 long time;
802 char *ptr;
803 int val;
805 if (read_id_tags)
807 if (!rb->strncmp(tag, "ti:", 3))
809 if (!current.id3->title || rb->strcmp(&tag[3], current.id3->title))
810 current.title = lrcbufadd(&tag[3], false);
811 return -1;
813 if (!rb->strncmp(tag, "ar:", 3))
815 if (!current.id3->artist || rb->strcmp(&tag[3], current.id3->artist))
816 current.artist = lrcbufadd(&tag[3], false);
817 return -1;
819 if (!rb->strncmp(tag, "offset:", 7))
821 current.offset = rb->atoi(&tag[7]);
822 current.offset_file_offset = file_offset;
823 return -1;
827 /* minute */
828 ptr = parse_int(tag, &val);
829 if (ptr-tag < 1 || ptr-tag > 2 || *ptr != ':')
830 return -10;
831 time = val * 60000;
832 /* second */
833 tag = ptr+1;
834 ptr = parse_int(tag, &val);
835 if (ptr-tag != 2 || (*ptr != '.' && *ptr != ':' && *ptr != '\0'))
836 return -10;
837 time += val * 1000;
839 if (*ptr != '\0')
841 /* milliseccond */
842 tag = ptr+1;
843 ptr = parse_int(tag, &val);
844 if (ptr-tag < 2 || ptr-tag > 3 || *ptr != '\0')
845 return -10;
846 time += ((ptr-tag)==3 ?val: val*10);
849 return time;
852 /* format:
853 * [time tag]line
854 * [time tag]...[time tag]line
855 * [time tag]<word time tag>word<word time tag>...<word time tag>
857 static bool parse_lrc_line(char *line, off_t file_offset)
859 struct lrc_line *lrc_line = NULL, *first_lrc_line = NULL;
860 long time, time_start;
861 char *str, *tagstart, *tagend;
862 struct lrc_word *lrc_word;
863 int nword = 0;
865 /* parse [time tag]...[time tag] type tags */
866 str = line;
867 while (1)
869 if (*str != '[') break;
870 tagend = rb->strchr(str, ']');
871 if (tagend == NULL) break;
872 *tagend = 0;
873 time = get_time_value(str+1, !lrc_line, file_offset);
874 *tagend++ = ']';
875 if (time < 0)
876 break;
877 lrc_line = alloc_buf(sizeof(struct lrc_line));
878 if (lrc_line == NULL)
879 return false;
880 if (!first_lrc_line)
881 first_lrc_line = lrc_line;
882 lrc_line->file_offset = file_offset;
883 lrc_line->time_start = (time/10)*10;
884 lrc_line->old_time_start = lrc_line->time_start;
885 add_lrc_line(lrc_line, NULL);
886 file_offset += (long)tagend - (long)str;
887 str = tagend;
889 if (!first_lrc_line)
890 return true; /* no time tag in line */
892 lrc_line = first_lrc_line;
893 if (lrcbufadd("", false) == NULL)
894 return false;
896 /* parse <word time tag>...<word time tag> type tags */
897 /* [time tag]...[time tag]line type tags share lrc_line->words and can't
898 * use lrc_line->words->timestart. use lrc_line->time_start instead. */
899 time_start = -1;
900 tagstart = str;
901 while (*tagstart)
903 tagstart = rb->strchr(tagstart, '<');
904 if (!tagstart) break;
905 tagend = rb->strchr(tagstart, '>');
906 if (!tagend) break;
907 *tagend = 0;
908 time = get_time_value(tagstart+1, false,
909 file_offset + ((long)tagstart - (long)str));
910 *tagend++ = '>';
911 if (time < 0)
913 tagstart++;
914 continue;
916 *tagstart = 0;
917 /* found word time tag. */
918 if (*str || time_start != -1)
920 if ((lrc_word = new_lrc_word(time_start, str, true)) == NULL)
921 return false;
922 nword++;
924 file_offset += (long)tagend - (long)str;
925 tagstart = str = tagend;
926 time_start = time;
928 if ((lrc_word = new_lrc_word(time_start, str, true)) == NULL)
929 return false;
930 nword++;
932 /* duplicate lrc_lines */
933 while (lrc_line)
935 lrc_line->nword = nword;
936 lrc_line->words = lrc_word;
937 lrc_line = lrc_line->next;
940 return true;
943 /* format:
944 * \xa2\xe2hhmmssxx\xa2\xd0
945 * line 1
946 * line 2
947 * \xa2\xe2hhmmssxx\xa2\xd0
948 * line 3
949 * ...
951 static bool parse_snc_line(char *line, off_t file_offset)
953 #define SNC_TAG_START "\xa2\xe2"
954 #define SNC_TAG_END "\xa2\xd0"
956 /* SNC_TAG can be dencoded, so use
957 * temp_buf which contains native data */
958 if (!rb->memcmp(temp_buf, SNC_TAG_START, 2)
959 && !rb->memcmp(temp_buf+10, SNC_TAG_END, 2)) /* time tag */
961 const char *pos = temp_buf+2; /* skip SNC_TAG_START */
962 int hh, mm, ss, xx;
964 hh = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
965 mm = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
966 ss = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
967 xx = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
968 pos += 2; /* skip SNC_TAG_END */
970 /* initialize */
971 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line));
972 if (lrc_line == NULL)
973 return false;
974 lrc_line->file_offset = file_offset+2;
975 lrc_line->time_start = hh*3600000+mm*60000+ss*1000+xx*10;
976 lrc_line->old_time_start = lrc_line->time_start;
977 if (!add_lrc_line(lrc_line, ""))
978 return false;
979 if (pos[0]==0)
980 return true;
982 /* encode rest of line and add to buffer */
983 rb->iso_decode(pos, line, prefs.encoding, rb->strlen(pos)+1);
985 if (current.ll_head)
987 rb->strcat(line, "\n");
988 if (lrcbufadd(line, true) == NULL)
989 return false;
991 return true;
994 static bool parse_txt_line(char *line, off_t file_offset)
996 /* initialize */
997 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line));
998 if (lrc_line == NULL)
999 return false;
1000 lrc_line->file_offset = file_offset;
1001 lrc_line->time_start = 0;
1002 lrc_line->old_time_start = -1;
1003 if (!add_lrc_line(lrc_line, line))
1004 return false;
1005 return true;
1008 static void load_lrc_file(void)
1010 char utf8line[MAX_LINE_LEN*3];
1011 int fd;
1012 int encoding = prefs.encoding;
1013 bool (*line_parser)(char *line, off_t) = NULL;
1014 off_t file_offset, readsize;
1016 switch(current.type)
1018 case LRC8:
1019 encoding = UTF_8; /* .lrc8 is utf8 */
1020 /* fall through */
1021 case LRC:
1022 line_parser = parse_lrc_line;
1023 break;
1024 case SNC:
1025 line_parser = parse_snc_line;
1026 break;
1027 case TXT:
1028 line_parser = parse_txt_line;
1029 break;
1030 default:
1031 return;
1034 fd = rb->open(current.lrc_file, O_RDONLY);
1035 if (fd < 0) return;
1038 /* check encoding */
1039 #define BOM "\xef\xbb\xbf"
1040 #define BOM_SIZE 3
1041 unsigned char header[BOM_SIZE];
1042 unsigned char* (*utf_decode)(const unsigned char *,
1043 unsigned char *, int) = NULL;
1044 rb->read(fd, header, BOM_SIZE);
1045 if (!rb->memcmp(header, BOM, BOM_SIZE)) /* UTF-8 */
1047 encoding = UTF_8;
1049 else if (!rb->memcmp(header, "\xff\xfe", 2)) /* UTF-16LE */
1051 utf_decode = rb->utf16LEdecode;
1053 else if (!rb->memcmp(header, "\xfe\xff", 2)) /* UTF-16BE */
1055 utf_decode = rb->utf16BEdecode;
1057 else
1059 rb->lseek(fd, 0, SEEK_SET);
1062 if (utf_decode)
1064 /* convert encoding of file from UTF-16 to UTF-8 */
1065 char temp_file[MAX_PATH];
1066 int fe;
1067 rb->lseek(fd, 2, SEEK_SET);
1068 rb->snprintf(temp_file, MAX_PATH, "%s~", current.lrc_file);
1069 fe = rb->creat(temp_file, 0666);
1070 if (fe < 0)
1072 rb->close(fd);
1073 return;
1075 rb->write(fe, BOM, BOM_SIZE);
1076 while ((readsize = rb->read(fd, temp_buf, MAX_LINE_LEN)) > 0)
1078 char *end = utf_decode(temp_buf, utf8line, readsize/2);
1079 rb->write(fe, utf8line, end-utf8line);
1081 rb->close(fe);
1082 rb->close(fd);
1083 rb->remove(current.lrc_file);
1084 rb->rename(temp_file, current.lrc_file);
1085 fd = rb->open(current.lrc_file, O_RDONLY);
1086 if (fd < 0) return;
1087 rb->lseek(fd, BOM_SIZE, SEEK_SET); /* skip bom */
1088 encoding = UTF_8;
1092 file_offset = rb->lseek(fd, 0, SEEK_CUR); /* used in line_parser */
1093 while ((readsize = rb->read_line(fd, temp_buf, MAX_LINE_LEN)) > 0)
1095 /* note: parse_snc_line() reads temp_buf for native data. */
1096 rb->iso_decode(temp_buf, utf8line, encoding, readsize+1);
1097 if (!line_parser(utf8line, file_offset))
1098 break;
1099 file_offset += readsize;
1101 rb->close(fd);
1103 current.loaded_lrc = true;
1104 calc_brpos(NULL, 0);
1105 init_time_tag();
1107 return;
1110 #ifdef LRC_SUPPORT_ID3
1111 /*******************************
1112 * read lyrics from id3
1113 *******************************/
1114 /* taken from apps/metadata/mp3.c */
1115 static unsigned long unsync(unsigned long b0, unsigned long b1,
1116 unsigned long b2, unsigned long b3)
1118 return (((long)(b0 & 0x7F) << (3*7)) |
1119 ((long)(b1 & 0x7F) << (2*7)) |
1120 ((long)(b2 & 0x7F) << (1*7)) |
1121 ((long)(b3 & 0x7F) << (0*7)));
1124 static unsigned long bytes2int(unsigned long b0, unsigned long b1,
1125 unsigned long b2, unsigned long b3)
1127 return (((long)(b0 & 0xFF) << (3*8)) |
1128 ((long)(b1 & 0xFF) << (2*8)) |
1129 ((long)(b2 & 0xFF) << (1*8)) |
1130 ((long)(b3 & 0xFF) << (0*8)));
1133 static int unsynchronize(char* tag, int len, bool *ff_found)
1135 int i;
1136 unsigned char c;
1137 unsigned char *rp, *wp;
1138 bool _ff_found = false;
1139 if(ff_found) _ff_found = *ff_found;
1141 wp = rp = (unsigned char *)tag;
1143 rp = (unsigned char *)tag;
1144 for(i = 0; i<len; i++) {
1145 /* Read the next byte and write it back, but don't increment the
1146 write pointer */
1147 c = *rp++;
1148 *wp = c;
1149 if(_ff_found) {
1150 /* Increment the write pointer if it isn't an unsynch pattern */
1151 if(c != 0)
1152 wp++;
1153 _ff_found = false;
1154 } else {
1155 if(c == 0xff)
1156 _ff_found = true;
1157 wp++;
1160 if(ff_found) *ff_found = _ff_found;
1161 return (long)wp - (long)tag;
1164 static int read_unsynched(int fd, void *buf, int len, bool *ff_found)
1166 int i;
1167 int rc;
1168 int remaining = len;
1169 char *wp;
1171 wp = buf;
1173 while(remaining) {
1174 rc = rb->read(fd, wp, remaining);
1175 if(rc <= 0)
1176 return rc;
1178 i = unsynchronize(wp, remaining, ff_found);
1179 remaining -= i;
1180 wp += i;
1183 return len;
1186 static unsigned char* utf8cpy(const unsigned char *src,
1187 unsigned char *dst, int count)
1189 rb->strlcpy(dst, src, count+1);
1190 return dst+rb->strlen(dst);
1193 static void parse_id3v2(int fd)
1195 int minframesize;
1196 int size;
1197 long framelen;
1198 char header[10];
1199 char tmp[8];
1200 unsigned char version;
1201 int bytesread = 0;
1202 unsigned char global_flags;
1203 int flags;
1204 bool global_unsynch = false;
1205 bool global_ff_found = false;
1206 bool unsynch = false;
1207 int rc;
1208 enum {NOLT, SYLT, USLT} type = NOLT;
1210 /* Bail out if the tag is shorter than 10 bytes */
1211 if(current.id3->id3v2len < 10)
1212 return;
1214 /* Read the ID3 tag version from the header */
1215 if(10 != rb->read(fd, header, 10))
1216 return;
1218 /* Get the total ID3 tag size */
1219 size = current.id3->id3v2len - 10;
1221 version = current.id3->id3version;
1222 switch ( version )
1224 case ID3_VER_2_2:
1225 minframesize = 8;
1226 break;
1228 case ID3_VER_2_3:
1229 minframesize = 12;
1230 break;
1232 case ID3_VER_2_4:
1233 minframesize = 12;
1234 break;
1236 default:
1237 /* unsupported id3 version */
1238 return;
1241 global_flags = header[5];
1243 /* Skip the extended header if it is present */
1244 if(global_flags & 0x40) {
1246 if(version == ID3_VER_2_3) {
1247 if(10 != rb->read(fd, header, 10))
1248 return;
1249 /* The 2.3 extended header size doesn't include the header size
1250 field itself. Also, it is not unsynched. */
1251 framelen =
1252 bytes2int(header[0], header[1], header[2], header[3]) + 4;
1254 /* Skip the rest of the header */
1255 rb->lseek(fd, framelen - 10, SEEK_CUR);
1258 if(version >= ID3_VER_2_4) {
1259 if(4 != rb->read(fd, header, 4))
1260 return;
1262 /* The 2.4 extended header size does include the entire header,
1263 so here we can just skip it. This header is unsynched. */
1264 framelen = unsync(header[0], header[1],
1265 header[2], header[3]);
1267 rb->lseek(fd, framelen - 4, SEEK_CUR);
1271 /* Is unsynchronization applied? */
1272 if(global_flags & 0x80) {
1273 global_unsynch = true;
1276 /* We must have at least minframesize bytes left for the
1277 * remaining frames to be interesting */
1278 while (size >= minframesize) {
1279 flags = 0;
1281 /* Read frame header and check length */
1282 if(version >= ID3_VER_2_3) {
1283 if(global_unsynch && version <= ID3_VER_2_3)
1284 rc = read_unsynched(fd, header, 10, &global_ff_found);
1285 else
1286 rc = rb->read(fd, header, 10);
1287 if(rc != 10)
1288 return;
1289 /* Adjust for the 10 bytes we read */
1290 size -= 10;
1292 flags = bytes2int(0, 0, header[8], header[9]);
1294 if (version >= ID3_VER_2_4) {
1295 framelen = unsync(header[4], header[5],
1296 header[6], header[7]);
1297 } else {
1298 /* version .3 files don't use synchsafe ints for
1299 * size */
1300 framelen = bytes2int(header[4], header[5],
1301 header[6], header[7]);
1303 } else {
1304 if(6 != rb->read(fd, header, 6))
1305 return;
1306 /* Adjust for the 6 bytes we read */
1307 size -= 6;
1309 framelen = bytes2int(0, header[3], header[4], header[5]);
1312 if(framelen == 0){
1313 if (header[0] == 0 && header[1] == 0 && header[2] == 0)
1314 return;
1315 else
1316 continue;
1319 unsynch = false;
1321 if(flags)
1323 if (version >= ID3_VER_2_4) {
1324 if(flags & 0x0040) { /* Grouping identity */
1325 rb->lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */
1326 framelen--;
1328 } else {
1329 if(flags & 0x0020) { /* Grouping identity */
1330 rb->lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */
1331 framelen--;
1335 if(flags & 0x000c) /* Compression or encryption */
1337 /* Skip it */
1338 size -= framelen;
1339 rb->lseek(fd, framelen, SEEK_CUR);
1340 continue;
1343 if(flags & 0x0002) /* Unsynchronization */
1344 unsynch = true;
1346 if (version >= ID3_VER_2_4) {
1347 if(flags & 0x0001) { /* Data length indicator */
1348 if(4 != rb->read(fd, tmp, 4))
1349 return;
1351 /* We don't need the data length */
1352 framelen -= 4;
1357 if (framelen == 0)
1358 continue;
1360 if (framelen < 0)
1361 return;
1363 if(!rb->memcmp( header, "SLT", 3 ) ||
1364 !rb->memcmp( header, "SYLT", 4 ))
1366 /* found a supported tag */
1367 type = SYLT;
1368 break;
1370 else if(!rb->memcmp( header, "ULT", 3 ) ||
1371 !rb->memcmp( header, "USLT", 4 ))
1373 /* found a supported tag */
1374 type = USLT;
1375 break;
1377 else
1379 /* not a supported tag*/
1380 if(global_unsynch && version <= ID3_VER_2_3) {
1381 size -= read_unsynched(fd, lrc_buffer, framelen, &global_ff_found);
1382 } else {
1383 size -= framelen;
1384 if( rb->lseek(fd, framelen, SEEK_CUR) == -1 )
1385 return;
1389 if(type == NOLT)
1390 return;
1392 int encoding = 0, chsiz;
1393 char *tag, *p, utf8line[MAX_LINE_LEN*3];
1394 unsigned char* (*utf_decode)(const unsigned char *,
1395 unsigned char *, int) = NULL;
1396 /* use middle of lrc_buffer to store tag data. */
1397 if(framelen >= LRC_BUFFER_SIZE/3)
1398 framelen = LRC_BUFFER_SIZE/3-1;
1399 tag = lrc_buffer+LRC_BUFFER_SIZE*2/3-framelen-1;
1400 if(global_unsynch && version <= ID3_VER_2_3)
1401 bytesread = read_unsynched(fd, tag, framelen, &global_ff_found);
1402 else
1403 bytesread = rb->read(fd, tag, framelen);
1405 if( bytesread != framelen )
1406 return;
1408 if(unsynch || (global_unsynch && version >= ID3_VER_2_4))
1409 bytesread = unsynchronize(tag, bytesread, NULL);
1411 tag[bytesread] = 0;
1412 encoding = tag[0];
1413 p = tag;
1414 /* skip some data */
1415 if(type == SYLT) {
1416 p += 6;
1417 } else {
1418 p += 4;
1421 /* check encoding and skip content descriptor */
1422 switch (encoding) {
1423 case 0x01: /* Unicode with or without BOM */
1424 case 0x02:
1426 /* Now check if there is a BOM
1427 (zero-width non-breaking space, 0xfeff)
1428 and if it is in little or big endian format */
1429 if(!rb->memcmp(p, "\xff\xfe", 2)) { /* Little endian? */
1430 utf_decode = rb->utf16LEdecode;
1431 } else if(!rb->memcmp(p, "\xfe\xff", 2)) { /* Big endian? */
1432 utf_decode = rb->utf16BEdecode;
1433 } else
1434 utf_decode = NULL;
1436 encoding = NUM_CODEPAGES;
1437 do {
1438 size = p[0] | p[1];
1439 p += 2;
1440 } while(size);
1441 chsiz = 2;
1442 break;
1444 default:
1445 utf_decode = utf8cpy;
1446 if(encoding == 0x03) /* UTF-8 encoded string */
1447 encoding = UTF_8;
1448 else
1449 encoding = prefs.encoding;
1450 p += rb->strlen(p)+1;
1451 chsiz = 1;
1452 break;
1454 if(encoding == NUM_CODEPAGES)
1456 /* check if there is a BOM */
1457 if(!rb->memcmp(p, "\xff\xfe", 2)) { /* Little endian? */
1458 utf_decode = rb->utf16LEdecode;
1459 p += 2;
1460 } else if(!rb->memcmp(p, "\xfe\xff", 2)) { /* Big endian? */
1461 utf_decode = rb->utf16BEdecode;
1462 p += 2;
1463 } else if(!utf_decode) {
1464 /* If there is no BOM (which is a specification violation),
1465 let's try to guess it. If one of the bytes is 0x00, it is
1466 probably the most significant one. */
1467 if(p[1] == 0)
1468 utf_decode = rb->utf16LEdecode;
1469 else
1470 utf_decode = rb->utf16BEdecode;
1473 bytesread -= (long)p - (long)tag;
1474 tag = p;
1476 while ( bytesread > 0
1477 && lrc_buffer_used+bytesread < LRC_BUFFER_SIZE*2/3
1478 && LRC_BUFFER_SIZE*2/3 < lrc_buffer_end)
1480 bool is_crlf = false;
1481 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line));
1482 if(!lrc_line)
1483 break;
1484 lrc_line->file_offset = -1;
1485 if(type == USLT)
1487 /* replace 0x0a and 0x0d with 0x00 */
1488 p = tag;
1489 while(1) {
1490 utf_decode(p, tmp, 2);
1491 if(!tmp[0]) break;
1492 if(tmp[0] == 0x0d || tmp[0] == 0x0a)
1494 if(tmp[0] == 0x0d && tmp[1] == 0x0a)
1495 is_crlf = true;
1496 p[0] = 0;
1497 p[chsiz-1] = 0;
1498 break;
1500 p += chsiz;
1503 if(encoding == NUM_CODEPAGES)
1505 unsigned char* utf8 = utf8line;
1506 p = tag;
1507 do {
1508 utf8 = utf_decode(p, utf8, 1);
1509 p += 2;
1510 } while(*(utf8-1));
1512 else
1514 size = rb->strlen(tag)+1;
1515 rb->iso_decode(tag, utf8line, encoding, size);
1516 p = tag+size;
1519 if(type == SYLT) { /* timestamp */
1520 lrc_line->time_start = bytes2int(p[0], p[1], p[2], p[3]);
1521 lrc_line->old_time_start = lrc_line->time_start;
1522 p += 4;
1523 utf_decode(p, tmp, 1);
1524 if(tmp[0] == 0x0a)
1525 p += chsiz;
1526 } else { /* USLT */
1527 lrc_line->time_start = 0;
1528 lrc_line->old_time_start = -1;
1529 if(is_crlf) p += chsiz;
1531 bytesread -= (long)p - (long)tag;
1532 tag = p;
1533 if(!add_lrc_line(lrc_line, utf8line))
1534 break;
1537 current.type = ID3_SYLT-SYLT+type;
1538 rb->strcpy(current.lrc_file, current.mp3_file);
1540 current.loaded_lrc = true;
1541 calc_brpos(NULL, 0);
1542 init_time_tag();
1544 return;
1547 static bool read_id3(void)
1549 int fd;
1551 if(current.id3->codectype != AFMT_MPA_L1
1552 && current.id3->codectype != AFMT_MPA_L2
1553 && current.id3->codectype != AFMT_MPA_L3)
1554 return false;
1556 fd = rb->open(current.mp3_file, O_RDONLY);
1557 if(fd < 0) return false;
1558 current.loaded_lrc = false;
1559 parse_id3v2(fd);
1560 rb->close(fd);
1561 return current.loaded_lrc;
1563 #endif /* LRC_SUPPORT_ID3 */
1565 /*******************************
1566 * Display information
1567 *******************************/
1568 static void display_state(void)
1570 const char *str = NULL;
1572 if (AUDIO_STOP)
1573 str = "Audio Stopped";
1574 else if (current.found_lrc)
1576 if (!current.loaded_lrc)
1577 str = "Loading lrc";
1578 else if (!current.ll_head)
1579 str = "No lyrics";
1582 #ifdef HAVE_LCD_BITMAP
1583 const char *info = NULL;
1585 if (AUDIO_PLAY && prefs.display_title)
1587 char *title = (current.title? current.title: current.id3->title);
1588 char *artist = (current.artist? current.artist: current.id3->artist);
1590 if (artist != NULL && title != NULL)
1592 rb->snprintf(temp_buf, MAX_LINE_LEN, "%s/%s", title, artist);
1593 info = temp_buf;
1595 else if (title != NULL)
1596 info = title;
1597 else if (current.mp3_file[0] == '/')
1598 info = rb->strrchr(current.mp3_file, '/')+1;
1599 else
1600 info = "(no info)";
1603 int i, w, h;
1604 struct screen* display;
1605 FOR_NB_SCREENS(i)
1607 display = rb->screens[i];
1608 display->set_viewport(&vp_info[i]);
1609 display->clear_viewport();
1610 if (info)
1611 display->puts_scroll(0, 0, info);
1612 if (str)
1614 display->set_viewport(&vp_lyrics[i]);
1615 display->clear_viewport();
1616 display->getstringsize(str, &w, &h);
1617 if (vp_lyrics[i].width - w < 0)
1618 display->puts_scroll(0, vp_lyrics[i].height/font_ui_height/2,
1619 str);
1620 else
1621 display->putsxy((vp_lyrics[i].width - w)*prefs.align/2,
1622 (vp_lyrics[i].height-font_ui_height)/2, str);
1623 display->set_viewport(&vp_info[i]);
1625 display->update_viewport();
1626 display->set_viewport(NULL);
1628 #else
1629 /* there is no place to display title or artist. */
1630 rb->lcd_clear_display();
1631 if (str)
1632 rb->lcd_puts_scroll(0, 0, str);
1633 rb->lcd_update();
1634 #endif /* HAVE_LCD_BITMAP */
1637 static void display_time(void)
1639 rb->snprintf(temp_buf, MAX_LINE_LEN, "%ld:%02ld/%ld:%02ld",
1640 current.elapsed/60000, (current.elapsed/1000)%60,
1641 current.length/60000, (current.length)/1000%60);
1642 #ifdef HAVE_LCD_BITMAP
1643 int y = (prefs.display_title? font_ui_height:0), i;
1644 FOR_NB_SCREENS(i)
1646 struct screen* display = rb->screens[i];
1647 display->set_viewport(&vp_info[i]);
1648 display->setfont(FONT_SYSFIXED);
1649 display->putsxy(0, y, temp_buf);
1650 rb->gui_scrollbar_draw(display, 0, y+SYSFONT_HEIGHT+1,
1651 vp_info[i].width, SYSFONT_HEIGHT-2,
1652 current.length, 0, current.elapsed, HORIZONTAL);
1653 display->update_viewport_rect(0, y, vp_info[i].width, SYSFONT_HEIGHT*2);
1654 display->setfont(FONT_UI);
1655 display->set_viewport(NULL);
1657 #else
1658 rb->lcd_puts(0, 0, temp_buf);
1659 rb->lcd_update();
1660 #endif /* HAVE_LCD_BITMAP */
1663 /*******************************
1664 * Display lyrics
1665 *******************************/
1666 #ifdef HAVE_LCD_BITMAP
1667 static inline void set_to_default(struct screen *display)
1669 #if (LCD_DEPTH > 1)
1670 #ifdef HAVE_REMOTE_LCD
1671 if (display->screen_type != SCREEN_REMOTE)
1672 #endif
1673 display->set_foreground(prefs.active_color);
1674 #endif
1675 display->set_drawmode(DRMODE_SOLID);
1677 static inline void set_to_active(struct screen *display)
1679 #if (LCD_DEPTH > 1)
1680 #ifdef HAVE_REMOTE_LCD
1681 if (display->screen_type == SCREEN_REMOTE)
1682 display->set_drawmode(DRMODE_INVERSEVID);
1683 else
1684 #endif
1686 display->set_foreground(prefs.active_color);
1687 display->set_drawmode(DRMODE_SOLID);
1689 #else /* LCD_DEPTH == 1 */
1690 display->set_drawmode(DRMODE_INVERSEVID);
1691 #endif
1693 static inline void set_to_inactive(struct screen *display)
1695 #if (LCD_DEPTH > 1)
1696 #ifdef HAVE_REMOTE_LCD
1697 if (display->screen_type != SCREEN_REMOTE)
1698 #endif
1699 display->set_foreground(prefs.inactive_color);
1700 #endif
1701 display->set_drawmode(DRMODE_SOLID);
1704 static int display_lrc_line(struct lrc_line *lrc_line, int ypos, int i)
1706 struct screen *display = rb->screens[i];
1707 struct lrc_word *lrc_word;
1708 struct lrc_brpos *lrc_brpos;
1709 long time_start, time_end, elapsed;
1710 int count, width, nword;
1711 int xpos;
1712 const char *str;
1713 bool active_line;
1715 time_start = get_time_start(lrc_line);
1716 time_end = get_time_start(lrc_line->next);
1717 active_line = (time_start <= current.elapsed
1718 && time_end > current.elapsed);
1720 if (!lrc_line->width)
1722 /* empty line. draw bar during interval. */
1723 long rin = current.elapsed - time_start;
1724 long len = time_end - time_start;
1725 if (current.wipe && active_line && len >= 3500)
1727 elapsed = rin * vp_lyrics[i].width / len;
1728 set_to_inactive(display);
1729 display->fillrect(elapsed, ypos+font_ui_height/4+1,
1730 vp_lyrics[i].width-elapsed-1, font_ui_height/2-2);
1731 set_to_active(display);
1732 display->drawrect(0, ypos+font_ui_height/4,
1733 vp_lyrics[i].width, font_ui_height/2);
1734 display->fillrect(1, ypos+font_ui_height/4+1,
1735 elapsed-1, font_ui_height/2-2);
1736 set_to_default(display);
1738 return ypos + font_ui_height;
1741 lrc_brpos = calc_brpos(lrc_line, i);
1742 /* initialize line */
1743 xpos = (vp_lyrics[i].width - lrc_brpos->width)*prefs.align/2;
1744 count = 0;
1745 width = 0;
1747 active_line = active_line || !prefs.active_one_line;
1748 nword = lrc_line->nword-1;
1749 lrc_word = lrc_line->words + nword;
1750 str = lrc_word->word;
1751 /* only time_start of first word could be -1 */
1752 if (lrc_word->time_start != -1)
1753 time_end = get_word_time_start(lrc_word);
1754 else
1755 time_end = time_start;
1756 do {
1757 time_start = time_end;
1758 if (nword > 0)
1759 time_end = get_word_time_start(lrc_word-1);
1760 else
1761 time_end = get_time_start(lrc_line->next);
1763 if (time_start > current.elapsed || !active_line)
1765 /* inactive */
1766 elapsed = 0;
1768 else if (!current.wipe || time_end <= current.elapsed)
1770 /* active whole word */
1771 elapsed = lrc_word->width;
1773 else
1775 /* wipe word */
1776 long rin = current.elapsed - time_start;
1777 long len = time_end - time_start;
1778 elapsed = rin * lrc_word->width / len;
1781 int word_count = lrc_word->count;
1782 int word_width = lrc_word->width;
1783 set_to_active(display);
1784 while (word_count > 0 && word_width > 0)
1786 int c = lrc_brpos->count - count;
1787 int w = lrc_brpos->width - width;
1788 if (c > word_count || w > word_width)
1790 c = word_count;
1791 w = word_width;
1793 if (elapsed <= 0)
1795 set_to_inactive(display);
1797 else if (elapsed < w)
1799 /* wipe text */
1800 display->fillrect(xpos, ypos, elapsed, font_ui_height);
1801 set_to_inactive(display);
1802 display->fillrect(xpos+elapsed, ypos,
1803 w-elapsed, font_ui_height);
1804 #if (LCD_DEPTH > 1)
1805 #ifdef HAVE_REMOTE_LCD
1806 if (display->screen_type == SCREEN_REMOTE)
1807 display->set_drawmode(DRMODE_INVERSEVID);
1808 else
1809 #endif
1810 display->set_drawmode(DRMODE_BG);
1811 #else
1812 display->set_drawmode(DRMODE_INVERSEVID);
1813 #endif
1815 rb->strlcpy(temp_buf, str, c+1);
1816 display->putsxy(xpos, ypos, temp_buf);
1817 str += c;
1818 xpos += w;
1819 count += c;
1820 width += w;
1821 word_count -= c;
1822 word_width -= w;
1823 elapsed -= w;
1824 if (count >= lrc_brpos->count || width >= lrc_brpos->width)
1826 /* prepare for next line */
1827 lrc_brpos++;
1828 str = lrc_skip_space(str);
1829 xpos = (vp_lyrics[i].width - lrc_brpos->width)*prefs.align/2;
1830 ypos += font_ui_height;
1831 count = 0;
1832 width = 0;
1835 lrc_word--;
1836 } while (nword--);
1837 set_to_default(display);
1838 return ypos;
1840 #endif /* HAVE_LCD_BITMAP */
1842 static void display_lrcs(void)
1844 long time_start, time_end, rin, len;
1845 int i, nline[NB_SCREENS] = {0};
1846 struct lrc_line *lrc_center = current.ll_head;
1848 if (!lrc_center) return;
1850 while (get_time_start(lrc_center->next) <= current.elapsed)
1852 nline[SCREEN_MAIN] += lrc_center->nline[SCREEN_MAIN];
1853 #ifdef HAVE_REMOTE_LCD
1854 nline[SCREEN_REMOTE] += lrc_center->nline[SCREEN_REMOTE];
1855 #endif
1856 lrc_center = lrc_center->next;
1859 time_start = get_time_start(lrc_center);
1860 time_end = get_time_start(lrc_center->next);
1861 rin = current.elapsed - time_start;
1862 len = time_end - time_start;
1864 struct screen *display;
1865 FOR_NB_SCREENS(i)
1867 display = rb->screens[i];
1868 /* display current line at the center of the viewport */
1869 display->set_viewport(&vp_lyrics[i]);
1870 display->clear_viewport();
1871 #ifdef HAVE_LCD_BITMAP
1872 struct lrc_line *lrc_line;
1873 int y, ypos = 0, nblines = vp_lyrics[i].height/font_ui_height;
1874 y = (nblines-1)/2;
1875 if (rin < 0)
1877 /* current.elapsed < time of first lrc */
1878 if (!current.wipe)
1879 ypos = (time_start - current.elapsed)
1880 * font_ui_height / time_start;
1881 else
1882 y++;
1884 else if (len > 0)
1886 if (!current.wipe)
1887 ypos = - rin * lrc_center->nline[i] * font_ui_height / len;
1888 else
1890 long elapsed = rin * lrc_center->width / len;
1891 struct lrc_brpos *lrc_brpos = calc_brpos(lrc_center, i);
1892 while (elapsed > lrc_brpos->width)
1894 elapsed -= lrc_brpos->width;
1895 y--;
1896 lrc_brpos++;
1901 /* find first line to display */
1902 y -= nline[i];
1903 lrc_line = current.ll_head;
1904 while (y < -lrc_line->nline[i])
1906 y += lrc_line->nline[i];
1907 lrc_line = lrc_line->next;
1910 ypos += y*font_ui_height;
1911 while (lrc_line && ypos < vp_lyrics[i].height)
1913 ypos = display_lrc_line(lrc_line, ypos, i);
1914 lrc_line = lrc_line->next;
1916 if (!lrc_line && ypos < vp_lyrics[i].height)
1917 display->putsxy(0, ypos, "[end]");
1918 #else /* HAVE_LCD_CHARCELLS */
1919 struct lrc_line *lrc_line = lrc_center;
1920 struct lrc_brpos *lrc_brpos = calc_brpos(lrc_line, i);
1921 long elapsed = 0;
1922 const char *str = get_lrc_str(lrc_line);
1923 int x = vp_lyrics[i].width/2, y = 0;
1925 if (rin >= 0 && len > 0)
1927 elapsed = rin * lrc_center->width / len;
1928 while (elapsed > lrc_brpos->width)
1930 elapsed -= lrc_brpos->width;
1931 str = lrc_skip_space(str+lrc_brpos->count);
1932 lrc_brpos++;
1935 rb->strlcpy(temp_buf, str, lrc_brpos->count+1);
1937 x -= elapsed;
1938 if (x < 0)
1939 display->puts(0, y, temp_buf + rb->utf8seek(temp_buf, -x));
1940 else
1941 display->puts(x, y, temp_buf);
1942 x += rb->utf8length(temp_buf)+1;
1943 lrc_line = lrc_line->next;
1944 if (!lrc_line && x < vp_lyrics[i].width)
1946 if (x < vp_lyrics[i].width/2)
1947 x = vp_lyrics[i].width/2;
1948 display->puts(x, y, "[end]");
1950 #endif /* HAVE_LCD_BITMAP */
1951 display->update_viewport();
1952 display->set_viewport(NULL);
1956 /*******************************
1957 * Browse lyrics and edit time.
1958 *******************************/
1959 /* point playing line in lyrics */
1960 static enum themable_icons get_icon(int selected, void * data)
1962 (void) data;
1963 struct lrc_line *lrc_line = get_lrc_line(selected);
1964 if (lrc_line)
1966 long time_start = get_time_start(lrc_line);
1967 long time_end = get_time_start(lrc_line->next);
1968 long elapsed = current.id3->elapsed;
1969 if (time_start <= elapsed && time_end > elapsed)
1970 return Icon_Moving;
1972 return Icon_NOICON;
1974 static const char *get_lrc_timeline(int selected, void *data,
1975 char *buffer, size_t buffer_len)
1977 (void) data;
1978 struct lrc_line *lrc_line = get_lrc_line(selected);
1979 if (lrc_line)
1981 format_time_tag(temp_buf, get_time_start(lrc_line));
1982 rb->snprintf(buffer, buffer_len, "[%s]%s",
1983 temp_buf, get_lrc_str(lrc_line));
1984 return buffer;
1986 return NULL;
1989 static void save_changes(void)
1991 char new_file[MAX_PATH], *p;
1992 bool success = false;
1993 int fd, fe;
1994 if (!current.changed_lrc)
1995 return;
1996 rb->splash(HZ/2, "Saving changes...");
1997 if (current.type == TXT || current.type > NUM_TYPES)
1999 /* save changes to new .lrc file */
2000 rb->strcpy(new_file, current.lrc_file);
2001 p = rb->strrchr(new_file, '.');
2002 rb->strcpy(p, extentions[LRC]);
2004 else
2006 /* file already exists. use temp file. */
2007 rb->snprintf(new_file, MAX_PATH, "%s~", current.lrc_file);
2009 fd = rb->creat(new_file, 0666);
2010 fe = rb->open(current.lrc_file, O_RDONLY);
2011 if (fd >= 0 && fe >= 0)
2013 struct lrc_line *lrc_line, *temp_lrc;
2014 off_t curr = 0, next = 0, size = 0, offset = 0;
2015 for (lrc_line = current.ll_head; lrc_line;
2016 lrc_line = lrc_line->next)
2018 /* apply offset and set old_time_start -1 to indicate
2019 that time tag is not saved yet. */
2020 lrc_line->time_start = get_time_start(lrc_line);
2021 lrc_line->old_time_start = -1;
2023 current.offset = 0;
2024 if (current.type > NUM_TYPES)
2026 curr = -1;
2027 rb->write(fd, BOM, BOM_SIZE);
2029 else
2030 size = rb->filesize(fe);
2031 while (curr < size)
2033 /* find offset of next tag */
2034 lrc_line = NULL;
2035 for (temp_lrc = current.ll_head, next = size;
2036 temp_lrc; temp_lrc = temp_lrc->next)
2038 offset = temp_lrc->file_offset;
2039 if (offset < next && temp_lrc->old_time_start == -1)
2041 lrc_line = temp_lrc;
2042 next = offset;
2043 if (offset <= curr) break;
2046 offset = current.offset_file_offset;
2047 if (offset >= 0 && offset < next)
2049 lrc_line = NULL;
2050 next = offset;
2051 current.offset_file_offset = -1;
2053 if (next > curr)
2055 if (curr == -1) curr = 0;
2056 /* copy before the next tag */
2057 while (curr < next)
2059 ssize_t count = next-curr;
2060 if (count > MAX_LINE_LEN)
2061 count = MAX_LINE_LEN;
2062 if (rb->read(fe, temp_buf, count)!=count)
2063 break;
2064 rb->write(fd, temp_buf, count);
2065 curr += count;
2067 if (curr < next || curr >= size) break;
2069 /* write tag to new file and skip tag in backup */
2070 if (lrc_line != NULL)
2072 lrc_line->file_offset = rb->lseek(fd, 0, SEEK_CUR);
2073 lrc_line->old_time_start = lrc_line->time_start;
2074 long t = lrc_line->time_start;
2075 if (current.type == SNC)
2077 rb->fdprintf(fd, "%02ld%02ld%02ld%02ld", (t/3600000)%100,
2078 (t/60000)%60, (t/1000)%60, (t/10)%100);
2079 /* skip time tag */
2080 curr += rb->read(fe, temp_buf, 8);
2082 else /* LRC || LRC8 */
2084 format_time_tag(temp_buf, t);
2085 rb->fdprintf(fd, "[%s]", temp_buf);
2087 if (next == -1)
2089 rb->fdprintf(fd, "%s\n", get_lrc_str(lrc_line));
2092 if (current.type == LRC || current.type == LRC8)
2094 /* skip both time tag and offset tag */
2095 while (curr++<size && rb->read(fe, temp_buf, 1)==1)
2096 if (temp_buf[0]==']') break;
2099 success = (curr>=size);
2101 if (fe >= 0) rb->close(fe);
2102 if (fd >= 0) rb->close(fd);
2104 if (success)
2106 if (current.type == TXT || current.type > NUM_TYPES)
2108 current.type = LRC;
2109 rb->strcpy(current.lrc_file, new_file);
2111 else
2113 rb->remove(current.lrc_file);
2114 rb->rename(new_file, current.lrc_file);
2117 else
2119 rb->remove(new_file);
2120 rb->splash(HZ, "Could not save changes.");
2122 rb->reload_directory();
2123 current.changed_lrc = false;
2125 static int timetag_editor(void)
2127 struct gui_synclist gui_editor;
2128 struct lrc_line *lrc_line;
2129 bool exit = false;
2130 int button, idx, selected = 0;
2132 if (current.id3 == NULL || !current.ll_head)
2134 rb->splash(HZ, "No lyrics");
2135 return LRC_GOTO_MAIN;
2138 get_lrc_line(-1); /* initialize static variables */
2140 for (lrc_line = current.ll_head, idx = 0;
2141 lrc_line; lrc_line = lrc_line->next, idx++)
2143 long time_start = get_time_start(lrc_line);
2144 long time_end = get_time_start(lrc_line->next);
2145 long elapsed = current.id3->elapsed;
2146 if (time_start <= elapsed && time_end > elapsed)
2147 selected = idx;
2150 rb->gui_synclist_init(&gui_editor, &get_lrc_timeline, NULL, false, 1, NULL);
2151 rb->gui_synclist_set_nb_items(&gui_editor, current.nlrcline);
2152 rb->gui_synclist_set_icon_callback(&gui_editor, get_icon);
2153 rb->gui_synclist_set_title(&gui_editor, "Timetag Editor",
2154 Icon_Menu_functioncall);
2155 rb->gui_synclist_select_item(&gui_editor, selected);
2156 rb->gui_synclist_draw(&gui_editor);
2158 while (!exit)
2160 button = rb->get_action(CONTEXT_TREE, TIMEOUT_BLOCK);
2161 if (rb->gui_synclist_do_button(&gui_editor, &button,
2162 LIST_WRAP_UNLESS_HELD))
2163 continue;
2165 switch (button)
2167 case ACTION_STD_OK:
2168 idx = rb->gui_synclist_get_sel_pos(&gui_editor);
2169 lrc_line = get_lrc_line(idx);
2170 if (lrc_line)
2172 set_time_start(lrc_line, current.id3->elapsed-500);
2173 rb->gui_synclist_draw(&gui_editor);
2175 break;
2176 case ACTION_STD_CONTEXT:
2177 idx = rb->gui_synclist_get_sel_pos(&gui_editor);
2178 lrc_line = get_lrc_line(idx);
2179 if (lrc_line)
2181 long temp_time = get_time_start(lrc_line);
2182 if (lrc_set_time(get_lrc_str(lrc_line), NULL,
2183 &temp_time, 10, 0, current.length,
2184 LST_SET_MSEC|LST_SET_SEC|LST_SET_MIN) == 1)
2185 return PLUGIN_USB_CONNECTED;
2186 set_time_start(lrc_line, temp_time);
2187 rb->gui_synclist_draw(&gui_editor);
2189 break;
2190 case ACTION_TREE_STOP:
2191 case ACTION_STD_CANCEL:
2192 exit = true;
2193 break;
2194 default:
2195 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
2196 return PLUGIN_USB_CONNECTED;
2197 break;
2201 FOR_NB_SCREENS(idx)
2202 rb->screens[idx]->stop_scroll();
2204 if (current.changed_lrc)
2206 MENUITEM_STRINGLIST(save_menu, "Save Changes?", NULL,
2207 "Yes", "No (save later)", "Discard All Changes")
2208 button = 0;
2209 exit = false;
2210 while (!exit)
2212 switch (rb->do_menu(&save_menu, &button, NULL, false))
2214 case 0:
2215 sort_lrcs();
2216 save_changes();
2217 exit = true;
2218 break;
2219 case 1:
2220 sort_lrcs();
2221 exit = true;
2222 break;
2223 case 2:
2224 init_time_tag();
2225 exit = true;
2226 break;
2227 case MENU_ATTACHED_USB:
2228 return PLUGIN_USB_CONNECTED;
2232 return LRC_GOTO_MAIN;
2235 /*******************************
2236 * Settings.
2237 *******************************/
2238 static void load_or_save_settings(bool save)
2240 static const char config_file[] = "lrcplayer.cfg";
2241 static struct configdata config[] = {
2242 #ifdef HAVE_LCD_COLOR
2243 { TYPE_INT, 0, 0xffffff, { .int_p = &prefs.inactive_color },
2244 "inactive color", NULL },
2245 #endif
2246 #ifdef HAVE_LCD_BITMAP
2247 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.wrap }, "wrap", NULL },
2248 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.wipe }, "wipe", NULL },
2249 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.active_one_line },
2250 "active one line", NULL },
2251 { TYPE_INT, 0, 2, { .int_p = &prefs.align }, "align", NULL },
2252 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.statusbar_on },
2253 "statusbar on", NULL },
2254 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.display_title },
2255 "display title", NULL },
2256 #endif
2257 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.display_time },
2258 "display time", NULL },
2259 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.backlight_on },
2260 "backlight on", NULL },
2262 { TYPE_STRING, 0, sizeof(prefs.lrc_directory),
2263 { .string = prefs.lrc_directory }, "lrc directory", NULL },
2264 { TYPE_INT, -1, NUM_CODEPAGES-1, { .int_p = &prefs.encoding },
2265 "encoding", NULL },
2266 #ifdef LRC_SUPPORT_ID3
2267 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.read_id3 }, "read id3", NULL },
2268 #endif
2271 if (!save)
2273 /* initialize setting */
2274 #if LCD_DEPTH > 1
2275 prefs.active_color = rb->lcd_get_foreground();
2276 prefs.inactive_color = LCD_LIGHTGRAY;
2277 #endif
2278 #ifdef HAVE_LCD_BITMAP
2279 prefs.wrap = true;
2280 prefs.wipe = true;
2281 prefs.active_one_line = false;
2282 prefs.align = 1; /* center */
2283 prefs.statusbar_on = false;
2284 prefs.display_title = true;
2285 #endif
2286 prefs.display_time = true;
2287 prefs.backlight_on = false;
2288 #ifdef LRC_SUPPORT_ID3
2289 prefs.read_id3 = true;
2290 #endif
2291 rb->strcpy(prefs.lrc_directory, "/Lyrics");
2292 prefs.encoding = -1; /* default codepage */
2294 configfile_load(config_file, config, ARRAYLEN(config), 0);
2296 else if (rb->memcmp(&old_prefs, &prefs, sizeof(prefs)))
2298 rb->splash(0, "Saving Settings");
2299 configfile_save(config_file, config, ARRAYLEN(config), 0);
2301 rb->memcpy(&old_prefs, &prefs, sizeof(prefs));
2304 static bool lrc_theme_menu(void)
2306 enum {
2307 #ifdef HAVE_LCD_BITMAP
2308 LRC_MENU_STATUSBAR,
2309 LRC_MENU_DISP_TITLE,
2310 #endif
2311 LRC_MENU_DISP_TIME,
2312 #ifdef HAVE_LCD_COLOR
2313 LRC_MENU_INACTIVE_COLOR,
2314 #endif
2315 LRC_MENU_BACKLIGHT,
2318 int selected = 0;
2319 bool exit = false, usb = false;
2321 MENUITEM_STRINGLIST(menu, "Theme Settings", NULL,
2322 #ifdef HAVE_LCD_BITMAP
2323 "Show Statusbar", "Display Title",
2324 #endif
2325 "Display Time",
2326 #ifdef HAVE_LCD_COLOR
2327 "Inactive Colour",
2328 #endif
2329 "Backlight Always On");
2331 while (!exit && !usb)
2333 switch (rb->do_menu(&menu, &selected, NULL, false))
2335 #ifdef HAVE_LCD_BITMAP
2336 case LRC_MENU_STATUSBAR:
2337 usb = rb->set_bool("Show Statusbar", &prefs.statusbar_on);
2338 break;
2339 case LRC_MENU_DISP_TITLE:
2340 usb = rb->set_bool("Display Title", &prefs.display_title);
2341 break;
2342 #endif
2343 case LRC_MENU_DISP_TIME:
2344 usb = rb->set_bool("Display Time", &prefs.display_time);
2345 break;
2346 #ifdef HAVE_LCD_COLOR
2347 case LRC_MENU_INACTIVE_COLOR:
2348 usb = rb->set_color(NULL, "Inactive Colour",
2349 &prefs.inactive_color, -1);
2350 break;
2351 #endif
2352 case LRC_MENU_BACKLIGHT:
2353 usb = rb->set_bool("Backlight Always On", &prefs.backlight_on);
2354 break;
2355 case MENU_ATTACHED_USB:
2356 usb = true;
2357 break;
2358 default:
2359 exit = true;
2360 break;
2364 return usb;
2367 #ifdef HAVE_LCD_BITMAP
2368 static bool lrc_display_menu(void)
2370 enum {
2371 LRC_MENU_WRAP,
2372 LRC_MENU_WIPE,
2373 LRC_MENU_ALIGN,
2374 LRC_MENU_LINE_MODE,
2377 int selected = 0;
2378 bool exit = false, usb = false;
2380 MENUITEM_STRINGLIST(menu, "Display Settings", NULL,
2381 "Wrap", "Wipe", "Alignment",
2382 "Activate Only Current Line");
2384 struct opt_items align_names[] = {
2385 {"Left", -1}, {"Centre", -1}, {"Right", -1},
2388 while (!exit && !usb)
2390 switch (rb->do_menu(&menu, &selected, NULL, false))
2392 case LRC_MENU_WRAP:
2393 usb = rb->set_bool("Wrap", &prefs.wrap);
2394 break;
2395 case LRC_MENU_WIPE:
2396 usb = rb->set_bool("Wipe", &prefs.wipe);
2397 break;
2398 case LRC_MENU_ALIGN:
2399 usb = rb->set_option("Alignment", &prefs.align, INT,
2400 align_names, 3, NULL);
2401 break;
2402 case LRC_MENU_LINE_MODE:
2403 usb = rb->set_bool("Activate Only Current Line",
2404 &prefs.active_one_line);
2405 break;
2406 case MENU_ATTACHED_USB:
2407 usb = true;
2408 break;
2409 default:
2410 exit = true;
2411 break;
2415 return usb;
2417 #endif /* HAVE_LCD_BITMAP */
2419 static bool lrc_lyrics_menu(void)
2421 enum {
2422 LRC_MENU_ENCODING,
2423 #ifdef LRC_SUPPORT_ID3
2424 LRC_MENU_READ_ID3,
2425 #endif
2426 LRC_MENU_LRC_DIR,
2429 int selected = 0;
2430 bool exit = false, usb = false;
2432 struct opt_items cp_names[NUM_CODEPAGES+1];
2433 int old_val;
2435 MENUITEM_STRINGLIST(menu, "Lyrics Settings", NULL,
2436 "Encoding",
2437 #ifdef LRC_SUPPORT_ID3
2438 "Read ID3 tag",
2439 #endif
2440 "Lrc Directry");
2442 cp_names[0].string = "Use default codepage";
2443 cp_names[0].voice_id = -1;
2444 for (old_val = 1; old_val < NUM_CODEPAGES+1; old_val++)
2446 cp_names[old_val].string = rb->get_codepage_name(old_val-1);
2447 cp_names[old_val].voice_id = -1;
2450 while (!exit && !usb)
2452 switch (rb->do_menu(&menu, &selected, NULL, false))
2454 case LRC_MENU_ENCODING:
2455 prefs.encoding++;
2456 old_val = prefs.encoding;
2457 usb = rb->set_option("Encoding", &prefs.encoding, INT,
2458 cp_names, NUM_CODEPAGES+1, NULL);
2459 if (prefs.encoding != old_val)
2461 save_changes();
2462 if (current.type < NUM_TYPES)
2464 /* let reload lrc file to apply encoding setting */
2465 reset_current_data();
2468 prefs.encoding--;
2469 break;
2470 #ifdef LRC_SUPPORT_ID3
2471 case LRC_MENU_READ_ID3:
2472 usb = rb->set_bool("Read ID3 tag", &prefs.read_id3);
2473 break;
2474 #endif
2475 case LRC_MENU_LRC_DIR:
2476 rb->strcpy(temp_buf, prefs.lrc_directory);
2477 if (!rb->kbd_input(temp_buf, sizeof(prefs.lrc_directory)))
2478 rb->strcpy(prefs.lrc_directory, temp_buf);
2479 break;
2480 case MENU_ATTACHED_USB:
2481 usb = true;
2482 break;
2483 default:
2484 exit = true;
2485 break;
2489 return usb;
2492 #ifdef LRC_DEBUG
2493 static const char* lrc_debug_data(int selected, void * data,
2494 char * buffer, size_t buffer_len)
2496 (void)data;
2497 switch (selected)
2499 case 0:
2500 rb->strlcpy(buffer, current.mp3_file, buffer_len);
2501 break;
2502 case 1:
2503 rb->strlcpy(buffer, current.lrc_file, buffer_len);
2504 break;
2505 case 2:
2506 rb->snprintf(buffer, buffer_len, "buf usage: %d,%d/%d",
2507 (int)lrc_buffer_used, (int)lrc_buffer_end,
2508 (int)lrc_buffer_size);
2509 break;
2510 case 3:
2511 rb->snprintf(buffer, buffer_len, "line count: %d,%d",
2512 current.nlrcline, current.nlrcbrpos);
2513 break;
2514 case 4:
2515 rb->snprintf(buffer, buffer_len, "loaded lrc? %s",
2516 current.loaded_lrc?"yes":"no");
2517 break;
2518 case 5:
2519 rb->snprintf(buffer, buffer_len, "too many lines? %s",
2520 current.too_many_lines?"yes":"no");
2521 break;
2522 default:
2523 return NULL;
2525 return buffer;
2528 static bool lrc_debug_menu(void)
2530 struct simplelist_info info;
2531 rb->simplelist_info_init(&info, "Debug Menu", 6, NULL);
2532 info.hide_selection = true;
2533 info.scroll_all = true;
2534 info.get_name = lrc_debug_data;
2535 return rb->simplelist_show_list(&info);
2537 #endif
2539 /* returns one of enum lrc_screen or enum plugin_status */
2540 static int lrc_menu(void)
2542 enum {
2543 LRC_MENU_THEME,
2544 #ifdef HAVE_LCD_BITMAP
2545 LRC_MENU_DISPLAY,
2546 #endif
2547 LRC_MENU_LYRICS,
2548 LRC_MENU_PLAYBACK,
2549 #ifdef LRC_DEBUG
2550 LRC_MENU_DEBUG,
2551 #endif
2552 LRC_MENU_OFFSET,
2553 LRC_MENU_TIMETAG_EDITOR,
2554 LRC_MENU_QUIT,
2557 MENUITEM_STRINGLIST(menu, "Lrcplayer Menu", NULL,
2558 "Theme Settings",
2559 #ifdef HAVE_LCD_BITMAP
2560 "Display Settings",
2561 #endif
2562 "Lyrics Settings",
2563 "Playback Control",
2564 #ifdef LRC_DEBUG
2565 "Debug Menu",
2566 #endif
2567 "Time Offset", "Timetag Editor",
2568 "Quit");
2569 int selected = 0, ret = LRC_GOTO_MENU;
2570 bool usb = false;
2572 while (ret == LRC_GOTO_MENU)
2574 switch (rb->do_menu(&menu, &selected, NULL, false))
2576 case LRC_MENU_THEME:
2577 usb = lrc_theme_menu();
2578 break;
2579 #ifdef HAVE_LCD_BITMAP
2580 case LRC_MENU_DISPLAY:
2581 usb = lrc_display_menu();
2582 break;
2583 #endif
2584 case LRC_MENU_LYRICS:
2585 usb = lrc_lyrics_menu();
2586 break;
2587 case LRC_MENU_PLAYBACK:
2588 usb = playback_control(NULL);
2589 ret = LRC_GOTO_MAIN;
2590 break;
2591 #ifdef LRC_DEBUG
2592 case LRC_MENU_DEBUG:
2593 usb = lrc_debug_menu();
2594 ret = LRC_GOTO_MAIN;
2595 break;
2596 #endif
2597 case LRC_MENU_OFFSET:
2598 usb = (lrc_set_time("Time Offset", "sec", &current.offset,
2599 10, -60*1000, 60*1000,
2600 LST_SET_MSEC|LST_SET_SEC) == 1);
2601 ret = LRC_GOTO_MAIN;
2602 break;
2603 case LRC_MENU_TIMETAG_EDITOR:
2604 ret = LRC_GOTO_EDITOR;
2605 break;
2606 case LRC_MENU_QUIT:
2607 ret = PLUGIN_OK;
2608 break;
2609 case MENU_ATTACHED_USB:
2610 usb = true;
2611 break;
2612 default:
2613 ret = LRC_GOTO_MAIN;
2614 break;
2616 if (usb)
2617 ret = PLUGIN_USB_CONNECTED;
2619 return ret;
2622 /*******************************
2623 * Main.
2624 *******************************/
2625 /* returns true if song has changed to know when to load new lyrics. */
2626 static bool check_audio_status(void)
2628 static int last_audio_status = 0;
2629 if (current.ff_rewind == -1)
2630 current.audio_status = rb->audio_status();
2631 current.id3 = rb->audio_current_track();
2632 if ((last_audio_status^current.audio_status)&AUDIO_STATUS_PLAY)
2634 last_audio_status = current.audio_status;
2635 return true;
2637 if (AUDIO_STOP || current.id3 == NULL)
2638 return false;
2639 if (rb->strcmp(current.mp3_file, current.id3->path))
2641 return true;
2643 return false;
2645 static void ff_rewind(long time, bool resume)
2647 if (AUDIO_PLAY)
2649 if (!AUDIO_PAUSE)
2651 resume = true;
2652 rb->audio_pause();
2654 rb->audio_ff_rewind(time);
2655 rb->sleep(HZ/10); /* take affect seeking */
2656 if (resume)
2657 rb->audio_resume();
2661 static int handle_button(void)
2663 int ret = LRC_GOTO_MAIN;
2664 static int step = 0;
2665 int limit, button = rb->get_action(CONTEXT_WPS, HZ/10);
2666 switch (button)
2668 case ACTION_WPS_BROWSE:
2669 #if CONFIG_KEYPAD == ONDIO_PAD
2670 /* ondio doesn't have ACTION_WPS_MENU,
2671 so use ACTION_WPS_BROWSE for menu */
2672 ret = LRC_GOTO_MENU;
2673 break;
2674 #endif
2675 case ACTION_WPS_STOP:
2676 save_changes();
2677 ret = PLUGIN_OK;
2678 break;
2679 case ACTION_WPS_PLAY:
2680 if (AUDIO_STOP && rb->global_status->resume_index != -1)
2682 if (rb->playlist_resume() != -1)
2684 rb->playlist_start(rb->global_status->resume_index,
2685 rb->global_status->resume_offset);
2688 else if (AUDIO_PAUSE)
2689 rb->audio_resume();
2690 else
2691 rb->audio_pause();
2692 break;
2693 case ACTION_WPS_SEEKFWD:
2694 case ACTION_WPS_SEEKBACK:
2695 if (AUDIO_STOP)
2696 break;
2697 if (current.ff_rewind > -1)
2699 if (button == ACTION_WPS_SEEKFWD)
2700 /* fast forwarding, calc max step relative to end */
2701 limit = (current.length - current.ff_rewind) * 3 / 100;
2702 else
2703 /* rewinding, calc max step relative to start */
2704 limit = (current.ff_rewind) * 3 / 100;
2705 limit = MAX(limit, 500);
2707 if (step > limit)
2708 step = limit;
2710 if (button == ACTION_WPS_SEEKFWD)
2711 current.ff_rewind += step;
2712 else
2713 current.ff_rewind -= step;
2715 if (current.ff_rewind > current.length-100)
2716 current.ff_rewind = current.length-100;
2717 if (current.ff_rewind < 0)
2718 current.ff_rewind = 0;
2720 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
2721 step += step >> (rb->global_settings->ff_rewind_accel + 3);
2723 else
2725 current.ff_rewind = current.elapsed;
2726 if (!AUDIO_PAUSE)
2727 rb->audio_pause();
2728 step = 1000 * rb->global_settings->ff_rewind_min_step;
2730 break;
2731 case ACTION_WPS_STOPSEEK:
2732 if (current.ff_rewind == -1)
2733 break;
2734 ff_rewind(current.ff_rewind, !AUDIO_PAUSE);
2735 current.elapsed = current.ff_rewind;
2736 current.ff_rewind = -1;
2737 break;
2738 case ACTION_WPS_SKIPNEXT:
2739 rb->audio_next();
2740 break;
2741 case ACTION_WPS_SKIPPREV:
2742 if (current.elapsed < 3000)
2743 rb->audio_prev();
2744 else
2745 ff_rewind(0, false);
2746 break;
2747 case ACTION_WPS_VOLDOWN:
2748 limit = rb->sound_min(SOUND_VOLUME);
2749 if (--rb->global_settings->volume < limit)
2750 rb->global_settings->volume = limit;
2751 rb->sound_set(SOUND_VOLUME, rb->global_settings->volume);
2752 break;
2753 case ACTION_WPS_VOLUP:
2754 limit = rb->sound_max(SOUND_VOLUME);
2755 if (++rb->global_settings->volume > limit)
2756 rb->global_settings->volume = limit;
2757 rb->sound_set(SOUND_VOLUME, rb->global_settings->volume);
2758 break;
2759 case ACTION_WPS_CONTEXT:
2760 ret = LRC_GOTO_EDITOR;
2761 break;
2762 case ACTION_WPS_MENU:
2763 ret = LRC_GOTO_MENU;
2764 break;
2765 default:
2766 if(rb->default_event_handler(button) == SYS_USB_CONNECTED)
2767 ret = PLUGIN_USB_CONNECTED;
2768 break;
2770 return ret;
2773 static int lrc_main(void)
2775 int ret = LRC_GOTO_MAIN;
2776 int i;
2777 long id3_timeout = 0;
2778 bool update_display_state = true;
2780 #ifdef HAVE_LCD_BITMAP
2781 /* y offset of vp_lyrics */
2782 int h = (prefs.display_title?font_ui_height:0)+
2783 (prefs.display_time?SYSFONT_HEIGHT*2:0);
2785 #endif
2787 FOR_NB_SCREENS(i)
2789 #ifdef HAVE_LCD_BITMAP
2790 rb->viewportmanager_theme_enable(i, prefs.statusbar_on, &vp_info[i]);
2791 vp_lyrics[i] = vp_info[i];
2792 vp_lyrics[i].flags &= ~VP_FLAG_ALIGNMENT_MASK;
2793 vp_lyrics[i].y += h;
2794 vp_lyrics[i].height -= h;
2795 #else
2796 rb->viewport_set_defaults(&vp_lyrics[i], i);
2797 if (prefs.display_time)
2799 vp_lyrics[i].y += 1; /* time */
2800 vp_lyrics[i].height -= 1;
2802 #endif
2805 if (prefs.backlight_on)
2806 backlight_ignore_timeout();
2808 #ifdef HAVE_LCD_BITMAP
2809 /* in case settings that may affect break position
2810 * are changed (statusbar_on and wrap). */
2811 if (!current.too_many_lines)
2812 calc_brpos(NULL, 0);
2813 #endif
2815 while (ret == LRC_GOTO_MAIN)
2817 if (check_audio_status())
2819 update_display_state = true;
2820 if (AUDIO_STOP)
2822 current.id3 = NULL;
2823 id3_timeout = 0;
2825 else if (rb->strcmp(current.mp3_file, current.id3->path))
2827 save_changes();
2828 reset_current_data();
2829 rb->strcpy(current.mp3_file, current.id3->path);
2830 id3_timeout = *rb->current_tick+HZ*3;
2831 current.found_lrc = false;
2834 if (current.id3 && current.id3->length)
2836 if (current.ff_rewind == -1)
2838 long di = current.id3->elapsed - current.elapsed;
2839 if (di < -250 || di > 0)
2840 current.elapsed = current.id3->elapsed;
2842 else
2843 current.elapsed = current.ff_rewind;
2844 current.length = current.id3->length;
2845 if (current.elapsed > current.length)
2846 current.elapsed = current.length;
2848 else
2850 current.elapsed = 0;
2851 current.length = 1;
2854 if (current.id3 && id3_timeout &&
2855 (TIME_AFTER(*rb->current_tick, id3_timeout) ||
2856 current.id3->artist))
2858 update_display_state = true;
2859 id3_timeout = 0;
2861 current.found_lrc = find_lrc_file();
2862 #ifdef LRC_SUPPORT_ID3
2863 if (!current.found_lrc && prefs.read_id3)
2865 /* no lyrics file found. try to read from id3 tag. */
2866 current.found_lrc = read_id3();
2868 #endif
2870 else if (current.found_lrc && !current.loaded_lrc)
2872 /* current.loaded_lrc is false after changing encode setting */
2873 update_display_state = true;
2874 display_state();
2875 load_lrc_file();
2877 if (update_display_state)
2879 #ifdef HAVE_LCD_BITMAP
2880 if (current.type == TXT || current.type == ID3_USLT)
2881 current.wipe = false;
2882 else
2883 current.wipe = prefs.wipe;
2884 #endif
2885 display_state();
2886 update_display_state = false;
2888 if (AUDIO_PLAY)
2890 if (prefs.display_time)
2891 display_time();
2892 if (!id3_timeout)
2893 display_lrcs();
2896 ret = handle_button();
2899 #ifdef HAVE_LCD_BITMAP
2900 FOR_NB_SCREENS(i)
2901 rb->viewportmanager_theme_undo(i, false);
2902 #endif
2903 if (prefs.backlight_on)
2904 backlight_use_settings();
2906 return ret;
2909 /* this is the plugin entry point */
2910 enum plugin_status plugin_start(const void* parameter)
2912 int ret = LRC_GOTO_MAIN;
2914 /* initialize settings. */
2915 load_or_save_settings(false);
2917 #ifdef HAVE_LCD_BITMAP
2918 rb->lcd_getstringsize("O", NULL, &font_ui_height);
2919 #endif
2921 lrc_buffer = rb->plugin_get_buffer(&lrc_buffer_size);
2922 lrc_buffer = (void *)(((long)lrc_buffer+3)&~3); /* 4 bytes aligned */
2923 lrc_buffer_size = (lrc_buffer_size - 4)&~3;
2925 reset_current_data();
2926 current.id3 = NULL;
2927 current.mp3_file[0] = 0;
2928 current.lrc_file[0] = 0;
2929 current.ff_rewind = -1;
2930 current.found_lrc = false;
2931 if (parameter && check_audio_status())
2933 const char *ext;
2934 rb->strcpy(current.mp3_file, current.id3->path);
2935 /* use passed parameter as lrc file. */
2936 rb->strcpy(current.lrc_file, parameter);
2937 if (!rb->file_exists(current.lrc_file))
2939 rb->splash(HZ, "Specified file dose not exist.");
2940 return PLUGIN_ERROR;
2942 ext = rb->strrchr(current.lrc_file, '.');
2943 if (!ext) ext = current.lrc_file;
2944 for (current.type = 0; current.type < NUM_TYPES; current.type++)
2946 if (!rb->strcasecmp(ext, extentions[current.type]))
2947 break;
2949 if (current.type == NUM_TYPES)
2951 rb->splashf(HZ, "%s is not supported", ext);
2952 return PLUGIN_ERROR;
2954 current.found_lrc = true;
2957 while (ret >= PLUGIN_OTHER)
2959 switch (ret)
2961 case LRC_GOTO_MAIN:
2962 ret = lrc_main();
2963 break;
2964 case LRC_GOTO_MENU:
2965 ret = lrc_menu();
2966 break;
2967 case LRC_GOTO_EDITOR:
2968 ret = timetag_editor();
2969 break;
2970 default:
2971 ret = PLUGIN_ERROR;
2972 break;
2976 load_or_save_settings(true);
2977 return ret;