checkwps: Do not error out on rwps for non-remote targets.
[maemo-rb.git] / apps / plugins / lrcplayer.c
blob97385ff0479a7f751b9a7f5408f98c4c279c88e6
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 uifont = -1;
128 static int font_ui_height = 1;
129 static struct viewport vp_info[NB_SCREENS];
130 #endif
131 static struct viewport vp_lyrics[NB_SCREENS];
133 #define AUDIO_PAUSE (current.audio_status & AUDIO_STATUS_PAUSE)
134 #define AUDIO_PLAY (current.audio_status & AUDIO_STATUS_PLAY)
135 #define AUDIO_STOP (!(current.audio_status & AUDIO_STATUS_PLAY))
137 /*******************************
138 * lrc_set_time
139 *******************************/
140 #define LST_SET_MSEC 0x00010000
141 #define LST_SET_SEC 0x00020000
142 #define LST_SET_MIN 0x00040000
143 #define LST_SET_HOUR 0x00080000
145 #include "lib/pluginlib_actions.h"
146 #define LST_SET_TIME (LST_SET_MSEC|LST_SET_SEC|LST_SET_MIN|LST_SET_HOUR)
147 #ifdef HAVE_LCD_CHARCELLS
148 #define LST_OFF_Y 0
149 #else /* HAVE_LCD_BITMAP */
150 #define LST_OFF_Y 1
151 #endif
152 static int lrc_set_time(const char *title, const char *unit, long *pval,
153 int step, int min, int max, int flags)
155 const struct button_mapping *lst_contexts[] = {
156 pla_main_ctx,
157 #ifdef HAVE_REMOTE_LCD
158 pla_remote_ctx,
159 #endif
161 /* how many */
162 const unsigned char formats[4][8] = {"%03ld.", "%02ld.", "%02ld:", "%02ld:"};
163 const unsigned int maxs[4] = {1000, 60, 60, 24};
164 const unsigned int scls[4] = {1, 1000, 60*1000, 60*60*1000};
165 char buffer[32];
166 long value = *pval, scl_step = step, i = 0;
167 int pos = 0, last_pos = 0, pos_min = 3, pos_max = 0;
168 int x = 0, y = 0, p_start = 0, p_end = 0;
169 int ret = 10;
171 if (!(flags&LST_SET_TIME))
172 return -1;
174 for (i = 0; i < 4; i++)
176 if (flags&(LST_SET_MSEC<<i))
178 if (pos_min > i) pos_min = i;
179 if (pos_max < i) pos_max = i;
182 pos = pos_min;
184 rb->button_clear_queue();
185 rb->lcd_clear_display();
186 rb->lcd_puts_scroll(0, LST_OFF_Y, title);
187 while (ret == 10)
189 int len = 0;
190 long abs_val = value;
191 long segvals[4] = {-1, -1, -1, -1};
192 /* show negative value like -00:01 but 00:-1 */
193 if (value < 0)
195 buffer[len++] = '-';
196 abs_val = -value;
198 buffer[len] = 0;
199 /* calc value of each segments */
200 for (i = pos_min; i <= pos_max; i++)
202 segvals[i] = abs_val % maxs[i];
203 abs_val /= maxs[i];
205 segvals[i-1] += abs_val * maxs[i-1];
206 for (i = pos_max; i >= pos_min; i--)
208 if (pos == i)
210 rb->lcd_getstringsize(buffer, &x, &y);
211 p_start = len;
213 rb->snprintf(&buffer[len], 32-len, formats[i], segvals[i]);
214 len += rb->strlen(&buffer[len]);
215 if (pos == i)
216 p_end = len;
218 buffer[len-1] = 0; /* remove last separater */
219 if (unit != NULL)
221 rb->snprintf(&buffer[len], 32-len, " (%s)", unit);
223 rb->lcd_puts(0, LST_OFF_Y+1, buffer);
224 if (pos_min != pos_max)
226 /* draw cursor */
227 buffer[p_end-1] = 0;
228 #ifdef HAVE_LCD_BITMAP
229 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
230 rb->lcd_putsxy(x, y*(1+LST_OFF_Y), &buffer[p_start]);
231 rb->lcd_set_drawmode(DRMODE_SOLID);
232 #else
233 rb->lcd_put_cursor(x+rb->utf8length(&buffer[p_start])-1, y, 0x7F);
234 #endif
236 rb->lcd_update();
237 int button = pluginlib_getaction(TIMEOUT_BLOCK, lst_contexts, ARRAYLEN(lst_contexts));
238 int mult = 1;
239 #ifdef HAVE_LCD_CHARCELLS
240 if (pos_min != pos_max)
241 rb->lcd_remove_cursor();
242 #endif
243 switch (button)
245 case PLA_UP_REPEAT:
246 case PLA_DOWN_REPEAT:
247 mult *= 10;
248 case PLA_DOWN:
249 case PLA_UP:
250 if (button == PLA_DOWN_REPEAT || button == PLA_DOWN)
251 mult *= -1;
252 if (pos != last_pos)
254 scl_step = ((scls[pos]/scls[pos_min]+step-1)/step) * step;
255 last_pos = pos;
257 value += scl_step * mult;
258 if (value > max)
259 value = max;
260 if (value < min)
261 value = min;
262 break;
263 case PLA_LEFT:
264 case PLA_LEFT_REPEAT:
265 if (++pos > pos_max)
266 pos = pos_min;
267 break;
268 case PLA_RIGHT:
269 case PLA_RIGHT_REPEAT:
270 if (--pos < pos_min)
271 pos = pos_max;
272 break;
273 case PLA_SELECT:
274 *pval = value;
275 ret = 0;
276 break;
277 case PLA_CANCEL:
278 case PLA_EXIT:
279 rb->splash(HZ, "Cancelled");
280 ret = -1;
281 break;
282 default:
283 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
284 ret = 1;
285 break;
288 rb->lcd_clear_display();
289 rb->lcd_update();
290 return ret;
293 /*******************************
294 * misc stuff
295 *******************************/
296 static void reset_current_data(void)
298 current.title = NULL;
299 current.artist = NULL;
300 current.offset = 0;
301 current.offset_file_offset = -1;
302 current.nlrcbrpos = 0;
303 current.nlrcline = 0;
304 current.ll_head = NULL;
305 current.ll_tail = &current.ll_head;
306 current.loaded_lrc = false;
307 current.changed_lrc = false;
308 current.too_many_lines = false;
309 lrc_buffer_used = 0;
310 lrc_buffer_end = lrc_buffer_size;
313 /* check space and add str to lrc_buffer.
314 * return NULL if there is not enough buffer. */
315 static char *lrcbufadd(const char*str, bool join)
317 if (join) lrc_buffer_used--;
318 size_t siz = rb->strlen(str)+1;
319 char *pos = &lrc_buffer[lrc_buffer_used];
320 if (lrc_buffer_used + siz > lrc_buffer_end)
321 return NULL;
322 rb->strcpy(pos, str);
323 lrc_buffer_used += siz;
324 return pos;
326 static void *alloc_buf(size_t siz)
328 siz = (siz+3) & ~3;
329 if (lrc_buffer_used + siz > lrc_buffer_end)
330 return NULL;
331 lrc_buffer_end -= siz;
332 return &lrc_buffer[lrc_buffer_end];
334 static void *new_lrc_word(long time_start, char *word, bool join)
336 struct lrc_word *lrc_word;
337 if ((lrc_word = alloc_buf(sizeof(struct lrc_word))) == NULL)
338 return NULL;
339 if ((lrc_word->word = lrcbufadd(word, join)) == NULL)
340 return NULL;
341 lrc_word->time_start = time_start;
342 return lrc_word;
344 static bool add_lrc_line(struct lrc_line *lrc_line, char *word)
346 lrc_line->nword = 0;
347 lrc_line->next = NULL;
348 lrc_line->words = NULL;
349 if (word)
351 if ((lrc_line->words = new_lrc_word(-1, word, false)) == NULL)
352 return false;
353 lrc_line->nword++;
355 *current.ll_tail = lrc_line;
356 current.ll_tail = &(lrc_line->next);
357 current.nlrcline++;
358 return true;
360 static struct lrc_line *get_lrc_line(int idx)
362 static struct lrc_line *lrc_line = NULL;
363 static int n = 0;
364 if (idx < n)
366 lrc_line = current.ll_head;
367 n = 0;
369 while (n < idx && lrc_line)
371 lrc_line = lrc_line->next;
372 n++;
374 return lrc_line;
376 static char *get_lrc_str(struct lrc_line *lrc_line)
378 return lrc_line->words[lrc_line->nword-1].word;
380 static long get_time_start(struct lrc_line *lrc_line)
382 if (!lrc_line) return current.length+20;
383 long time = lrc_line->time_start + current.offset;
384 return time < 0? 0: time;
386 static void set_time_start(struct lrc_line *lrc_line, long time_start)
388 time_start -= current.offset;
389 time_start -= time_start%10;
390 if (lrc_line->time_start != time_start)
392 lrc_line->time_start = time_start;
393 current.changed_lrc = true;
396 #define get_word_time_start(x) get_time_start((struct lrc_line *)(x))
397 #define set_word_time_start(x, t) set_time_start((struct lrc_line *)(x), (t))
399 static int format_time_tag(char *buf, long t)
401 return rb->snprintf(buf, 16, "%02ld:%02ld.%02ld",
402 t/60000, (t/1000)%60, (t/10)%100);
404 /* find start of next line */
405 static const char *lrc_skip_space(const char *str)
407 #ifdef HAVE_LCD_BITMAP
408 if (prefs.wrap)
410 while (*str && *str != '\n' && isspace(*str))
411 str++;
413 #endif
414 if (*str == '\n')
415 str++;
416 return str;
419 #ifdef HAVE_LCD_BITMAP
420 static bool isbrchr(const unsigned char *str, int len)
422 const unsigned char *p = "!,-.:;? 、。!,.:;?―";
423 if (isspace(*str))
424 return true;
426 while(*p)
428 int n = rb->utf8seek(p, 1);
429 if (len == n && !rb->strncmp(p, str, len))
430 return true;
431 p += n;
433 return false;
435 #endif
437 /* calculate how many lines is needed to display and store it.
438 * create cache if there is enough space in lrc_buffer. */
439 static struct lrc_brpos *calc_brpos(struct lrc_line *lrc_line, int i)
441 struct lrc_brpos *lrc_brpos;
442 struct lrc_word *lrc_word;
443 int nlrcbrpos = 0, max_lrcbrpos;
444 #ifdef HAVE_LCD_BITMAP
445 uifont = rb->screens[0]->getuifont();
446 struct font* pf = rb->font_get(uifont);
447 unsigned short ch;
448 #endif
449 struct snap {
450 int count, width;
451 int nword;
452 int word_count, word_width;
453 const unsigned char *str;
455 #ifndef HAVE_LCD_CHARCELLS
456 sp,
457 #endif
460 lrc_buffer_used = (lrc_buffer_used+3)&~3; /* 4 bytes aligned */
461 lrc_brpos = (struct lrc_brpos *) &lrc_buffer[lrc_buffer_used];
462 max_lrcbrpos = (lrc_buffer_end-lrc_buffer_used) / sizeof(struct lrc_brpos);
464 if (!lrc_line)
466 /* calc info for all lrcs and store them if possible */
467 size_t buffer_used = lrc_buffer_used;
468 bool too_many_lines = false;
469 current.too_many_lines = true;
470 for (lrc_line = current.ll_head; lrc_line; lrc_line = lrc_line->next)
472 FOR_NB_SCREENS(i)
474 lrc_brpos = calc_brpos(lrc_line, i);
475 if (!too_many_lines)
477 lrc_buffer_used += lrc_line->nline[i]*sizeof(struct lrc_brpos);
478 if (nlrcbrpos + lrc_line->nline[i] >= max_lrcbrpos)
480 too_many_lines = true;
481 lrc_buffer_used = buffer_used;
482 calc_brpos(lrc_line, i);
485 nlrcbrpos += lrc_line->nline[i];
488 current.too_many_lines = too_many_lines;
489 lrc_buffer_used = buffer_used;
490 current.nlrcbrpos = nlrcbrpos;
491 return NULL;
494 if (!current.too_many_lines)
496 /* use stored infos. */
497 struct lrc_line *temp_lrc = current.ll_head;
498 for (; temp_lrc != lrc_line; temp_lrc = temp_lrc->next)
500 lrc_brpos += temp_lrc->nline[SCREEN_MAIN];
501 #ifdef HAVE_REMOTE_LCD
502 lrc_brpos += temp_lrc->nline[SCREEN_REMOTE];
503 #endif
505 #if NB_SCREENS >= 2
506 while (i)
507 lrc_brpos += lrc_line->nline[--i];
508 #endif
509 return lrc_brpos;
512 /* calculate number of lines, line width and char count for each line. */
513 lrc_line->width = 0;
514 cr.nword = lrc_line->nword;
515 lrc_word = lrc_line->words+cr.nword;
516 cr.str = (lrc_word-1)->word;
517 #ifndef HAVE_LCD_CHARCELLS
518 sp.word_count = 0;
519 sp.word_width = 0;
520 sp.nword = 0;
521 sp.count = 0;
522 sp.width = 0;
523 #endif
524 do {
525 cr.count = 0;
526 cr.width = 0;
527 #ifndef HAVE_LCD_CHARCELLS
528 sp.str = NULL;
529 #endif
531 while (1)
533 while(cr.nword > 0 && cr.str >= (lrc_word-1)->word)
535 cr.nword--;
536 lrc_word--;
537 lrc_word->count = 0;
538 lrc_word->width = 0;
540 if (*cr.str == 0 || *cr.str == '\n')
541 break;
543 int c, w;
544 #ifdef HAVE_LCD_CHARCELLS
545 c = rb->utf8seek(cr.str, 1);
546 w = 1;
547 #else
548 c = ((long)rb->utf8decode(cr.str, &ch) - (long)cr.str);
549 if (rb->is_diacritic(ch, NULL))
550 w = 0;
551 else
552 w = rb->font_get_width(pf, ch);
553 if (cr.count && prefs.wrap && isbrchr(cr.str, c))
555 /* remember position of last space */
556 rb->memcpy(&sp, &cr, sizeof(struct snap));
557 sp.word_count = lrc_word->count;
558 sp.word_width = lrc_word->width;
559 if (!isspace(*cr.str) && cr.width+w <= vp_lyrics[i].width)
561 sp.count += c;
562 sp.width += w;
563 sp.word_count += c;
564 sp.word_width += w;
565 sp.str += c;
568 if (cr.count && cr.width+w > vp_lyrics[i].width)
570 if (sp.str != NULL) /* wrap */
572 rb->memcpy(&cr, &sp, sizeof(struct snap));
573 lrc_word = lrc_line->words+cr.nword;
574 lrc_word->count = sp.word_count;
575 lrc_word->width = sp.word_width;
577 break;
579 #endif
580 cr.count += c;
581 cr.width += w;
582 lrc_word->count += c;
583 lrc_word->width += w;
584 cr.str += c;
586 lrc_line->width += cr.width;
587 lrc_brpos->count = cr.count;
588 lrc_brpos->width = cr.width;
589 nlrcbrpos++;
590 lrc_brpos++;
591 cr.str = lrc_skip_space(cr.str);
592 } while (*cr.str && nlrcbrpos < max_lrcbrpos);
593 lrc_line->nline[i] = nlrcbrpos;
595 while (cr.nword > 0)
597 cr.nword--;
598 lrc_word--;
599 lrc_word->count = 0;
600 lrc_word->width = 0;
602 return lrc_brpos-nlrcbrpos;
605 /* sort lyrics by time using stable sort. */
606 static void sort_lrcs(void)
608 struct lrc_line *p = current.ll_head, **q = NULL, *t;
609 long time_max = 0;
611 current.ll_head = NULL;
612 current.ll_tail = &current.ll_head;
613 while (p != NULL)
615 t = p->next;
616 /* remove problematic lrc_lines.
617 * it would cause problem in display_lrc_line() if nword is 0. */
618 if (p->nword)
620 q = p->time_start >= time_max? current.ll_tail: &current.ll_head;
621 while ((*q) && (*q)->time_start <= p->time_start)
622 q = &((*q)->next);
623 p->next = *q;
624 *q = p;
625 if (!p->next)
627 time_max = p->time_start;
628 current.ll_tail = &p->next;
631 p = t;
633 if (!current.too_many_lines)
634 calc_brpos(NULL, 0); /* stored data depends on order of lrcs if exist */
636 static void init_time_tag(void)
638 struct lrc_line *lrc_line = current.ll_head;
639 int nline = 0;
640 if (current.type == TXT || current.type == ID3_USLT)
642 /* set time tag according to length of audio and total line count
643 * for not synched lyrics, so that scroll speed is almost constant. */
644 for (; lrc_line; lrc_line = lrc_line->next)
646 lrc_line->time_start = nline * current.length / current.nlrcbrpos;
647 lrc_line->time_start -= lrc_line->time_start%10;
648 lrc_line->old_time_start = -1;
649 nline += lrc_line->nline[SCREEN_MAIN];
650 #ifdef HAVE_REMOTE_LCD
651 nline += lrc_line->nline[SCREEN_REMOTE];
652 #endif
655 else
657 /* reset timetags to the value read from file */
658 for (; lrc_line; lrc_line = lrc_line->next)
660 lrc_line->time_start = lrc_line->old_time_start;
662 sort_lrcs();
664 current.changed_lrc = false;
667 /*******************************
668 * Serch lrc file.
669 *******************************/
671 /* search in same or parent directries of playing file.
672 * assume playing file is /aaa/bbb/ccc/ddd.mp3,
673 * this function searchs lrc file following order.
674 * /aaa/bbb/ccc/ddd.lrc
675 * /aaa/bbb/ddd.lrc
676 * /aaa/ddd.lrc
677 * /ddd.lrc
680 /* taken from apps/recorder/albumart.c */
681 static void fix_filename(char* name)
683 static const char invalid_chars[] = "*/:<>?\\|";
685 while (1)
687 if (*name == 0)
688 return;
689 if (*name == '"')
690 *name = '\'';
691 else if (rb->strchr(invalid_chars, *name))
692 *name = '_';
693 name++;
696 static bool find_lrc_file_helper(const char *base_dir)
698 char fname[MAX_PATH];
699 char *names[3] = {NULL, NULL, NULL};
700 char *p, *dir;
701 int i, len;
702 /* /aaa/bbb/ccc/ddd.mp3
703 * dir <--q names[0]
706 /* assuming file name starts with '/' */
707 rb->strcpy(temp_buf, current.mp3_file);
708 /* get file name and remove extension */
709 names[0] = rb->strrchr(temp_buf, '/')+1;
710 if ((p = rb->strrchr(names[0], '.')) != NULL)
711 *p = 0;
712 if (current.id3->title && rb->strcmp(names[0], current.id3->title))
714 rb->strlcpy(fname, current.id3->title, sizeof(fname));
715 fix_filename(fname);
716 names[1] = fname;
719 dir = temp_buf;
720 p = names[0]-1;
721 do {
722 int n;
723 *p = 0;
724 for (n = 0; ; n++)
726 if (n == 0)
728 len = rb->snprintf(current.lrc_file, MAX_PATH, "%s%s/",
729 base_dir, dir);
731 else if (n == 1)
733 /* check file in subfolder named prefs.lrc_directory
734 * in the directory of mp3 file. */
735 if (prefs.lrc_directory[0] == '/')
737 len = rb->snprintf(current.lrc_file, MAX_PATH, "%s%s/",
738 dir, prefs.lrc_directory);
740 else
741 continue;
743 else
744 break;
745 DEBUGF("check file in %s\n", current.lrc_file);
746 if (!rb->dir_exists(current.lrc_file))
747 continue;
748 for (current.type = 0; current.type < NUM_TYPES; current.type++)
750 for (i = 0; names[i] != NULL; i++)
752 rb->snprintf(&current.lrc_file[len], MAX_PATH-len,
753 "%s%s", names[i], extentions[current.type]);
754 if (rb->file_exists(current.lrc_file))
756 DEBUGF("found: `%s'\n", current.lrc_file);
757 return true;
762 } while ((p = rb->strrchr(dir, '/')) != NULL);
763 return false;
766 /* return true if a lrc file is found */
767 static bool find_lrc_file(void)
769 reset_current_data();
771 DEBUGF("find lrc file for `%s'\n", current.mp3_file);
772 /* find .lrc file */
773 if (find_lrc_file_helper(""))
774 return true;
775 if (prefs.lrc_directory[0] == '/' && rb->dir_exists(prefs.lrc_directory))
777 if (find_lrc_file_helper(prefs.lrc_directory))
778 return true;
781 current.lrc_file[0] = 0;
782 return false;
785 /*******************************
786 * Load file.
787 *******************************/
789 /* check tag format and calculate value of the tag.
790 * supported tag: ti, ar, offset
791 * supported format of time tag: [mm:ss], [mm:ss.xx], [mm:ss.xxx]
792 * returns value of timega if tag is time tag, -1 if tag is supported tag,
793 * -10 otherwise.
795 static char *parse_int(char *ptr, int *val)
797 *val = rb->atoi(ptr);
798 while (isdigit(*ptr)) ptr++;
799 return ptr;
801 static long get_time_value(char *tag, bool read_id_tags, off_t file_offset)
803 long time;
804 char *ptr;
805 int val;
807 if (read_id_tags)
809 if (!rb->strncmp(tag, "ti:", 3))
811 if (!current.id3->title || rb->strcmp(&tag[3], current.id3->title))
812 current.title = lrcbufadd(&tag[3], false);
813 return -1;
815 if (!rb->strncmp(tag, "ar:", 3))
817 if (!current.id3->artist || rb->strcmp(&tag[3], current.id3->artist))
818 current.artist = lrcbufadd(&tag[3], false);
819 return -1;
821 if (!rb->strncmp(tag, "offset:", 7))
823 current.offset = rb->atoi(&tag[7]);
824 current.offset_file_offset = file_offset;
825 return -1;
829 /* minute */
830 ptr = parse_int(tag, &val);
831 if (ptr-tag < 1 || ptr-tag > 2 || *ptr != ':')
832 return -10;
833 time = val * 60000;
834 /* second */
835 tag = ptr+1;
836 ptr = parse_int(tag, &val);
837 if (ptr-tag != 2 || (*ptr != '.' && *ptr != ':' && *ptr != '\0'))
838 return -10;
839 time += val * 1000;
841 if (*ptr != '\0')
843 /* milliseccond */
844 tag = ptr+1;
845 ptr = parse_int(tag, &val);
846 if (ptr-tag < 2 || ptr-tag > 3 || *ptr != '\0')
847 return -10;
848 time += ((ptr-tag)==3 ?val: val*10);
851 return time;
854 /* format:
855 * [time tag]line
856 * [time tag]...[time tag]line
857 * [time tag]<word time tag>word<word time tag>...<word time tag>
859 static bool parse_lrc_line(char *line, off_t file_offset)
861 struct lrc_line *lrc_line = NULL, *first_lrc_line = NULL;
862 long time, time_start;
863 char *str, *tagstart, *tagend;
864 struct lrc_word *lrc_word;
865 int nword = 0;
867 /* parse [time tag]...[time tag] type tags */
868 str = line;
869 while (1)
871 if (*str != '[') break;
872 tagend = rb->strchr(str, ']');
873 if (tagend == NULL) break;
874 *tagend = 0;
875 time = get_time_value(str+1, !lrc_line, file_offset);
876 *tagend++ = ']';
877 if (time < 0)
878 break;
879 lrc_line = alloc_buf(sizeof(struct lrc_line));
880 if (lrc_line == NULL)
881 return false;
882 if (!first_lrc_line)
883 first_lrc_line = lrc_line;
884 lrc_line->file_offset = file_offset;
885 lrc_line->time_start = (time/10)*10;
886 lrc_line->old_time_start = lrc_line->time_start;
887 add_lrc_line(lrc_line, NULL);
888 file_offset += (long)tagend - (long)str;
889 str = tagend;
891 if (!first_lrc_line)
892 return true; /* no time tag in line */
894 lrc_line = first_lrc_line;
895 if (lrcbufadd("", false) == NULL)
896 return false;
898 /* parse <word time tag>...<word time tag> type tags */
899 /* [time tag]...[time tag]line type tags share lrc_line->words and can't
900 * use lrc_line->words->timestart. use lrc_line->time_start instead. */
901 time_start = -1;
902 tagstart = str;
903 while (*tagstart)
905 tagstart = rb->strchr(tagstart, '<');
906 if (!tagstart) break;
907 tagend = rb->strchr(tagstart, '>');
908 if (!tagend) break;
909 *tagend = 0;
910 time = get_time_value(tagstart+1, false,
911 file_offset + ((long)tagstart - (long)str));
912 *tagend++ = '>';
913 if (time < 0)
915 tagstart++;
916 continue;
918 *tagstart = 0;
919 /* found word time tag. */
920 if (*str || time_start != -1)
922 if ((lrc_word = new_lrc_word(time_start, str, true)) == NULL)
923 return false;
924 nword++;
926 file_offset += (long)tagend - (long)str;
927 tagstart = str = tagend;
928 time_start = time;
930 if ((lrc_word = new_lrc_word(time_start, str, true)) == NULL)
931 return false;
932 nword++;
934 /* duplicate lrc_lines */
935 while (lrc_line)
937 lrc_line->nword = nword;
938 lrc_line->words = lrc_word;
939 lrc_line = lrc_line->next;
942 return true;
945 /* format:
946 * \xa2\xe2hhmmssxx\xa2\xd0
947 * line 1
948 * line 2
949 * \xa2\xe2hhmmssxx\xa2\xd0
950 * line 3
951 * ...
953 static bool parse_snc_line(char *line, off_t file_offset)
955 #define SNC_TAG_START "\xa2\xe2"
956 #define SNC_TAG_END "\xa2\xd0"
958 /* SNC_TAG can be dencoded, so use
959 * temp_buf which contains native data */
960 if (!rb->memcmp(temp_buf, SNC_TAG_START, 2)
961 && !rb->memcmp(temp_buf+10, SNC_TAG_END, 2)) /* time tag */
963 const char *pos = temp_buf+2; /* skip SNC_TAG_START */
964 int hh, mm, ss, xx;
966 hh = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
967 mm = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
968 ss = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
969 xx = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
970 pos += 2; /* skip SNC_TAG_END */
972 /* initialize */
973 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line));
974 if (lrc_line == NULL)
975 return false;
976 lrc_line->file_offset = file_offset+2;
977 lrc_line->time_start = hh*3600000+mm*60000+ss*1000+xx*10;
978 lrc_line->old_time_start = lrc_line->time_start;
979 if (!add_lrc_line(lrc_line, ""))
980 return false;
981 if (pos[0]==0)
982 return true;
984 /* encode rest of line and add to buffer */
985 rb->iso_decode(pos, line, prefs.encoding, rb->strlen(pos)+1);
987 if (current.ll_head)
989 rb->strcat(line, "\n");
990 if (lrcbufadd(line, true) == NULL)
991 return false;
993 return true;
996 static bool parse_txt_line(char *line, off_t file_offset)
998 /* initialize */
999 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line));
1000 if (lrc_line == NULL)
1001 return false;
1002 lrc_line->file_offset = file_offset;
1003 lrc_line->time_start = 0;
1004 lrc_line->old_time_start = -1;
1005 if (!add_lrc_line(lrc_line, line))
1006 return false;
1007 return true;
1010 static void load_lrc_file(void)
1012 char utf8line[MAX_LINE_LEN*3];
1013 int fd;
1014 int encoding = prefs.encoding;
1015 bool (*line_parser)(char *line, off_t) = NULL;
1016 off_t file_offset, readsize;
1018 switch(current.type)
1020 case LRC8:
1021 encoding = UTF_8; /* .lrc8 is utf8 */
1022 /* fall through */
1023 case LRC:
1024 line_parser = parse_lrc_line;
1025 break;
1026 case SNC:
1027 line_parser = parse_snc_line;
1028 break;
1029 case TXT:
1030 line_parser = parse_txt_line;
1031 break;
1032 default:
1033 return;
1036 fd = rb->open(current.lrc_file, O_RDONLY);
1037 if (fd < 0) return;
1040 /* check encoding */
1041 #define BOM "\xef\xbb\xbf"
1042 #define BOM_SIZE 3
1043 unsigned char header[BOM_SIZE];
1044 unsigned char* (*utf_decode)(const unsigned char *,
1045 unsigned char *, int) = NULL;
1046 rb->read(fd, header, BOM_SIZE);
1047 if (!rb->memcmp(header, BOM, BOM_SIZE)) /* UTF-8 */
1049 encoding = UTF_8;
1051 else if (!rb->memcmp(header, "\xff\xfe", 2)) /* UTF-16LE */
1053 utf_decode = rb->utf16LEdecode;
1055 else if (!rb->memcmp(header, "\xfe\xff", 2)) /* UTF-16BE */
1057 utf_decode = rb->utf16BEdecode;
1059 else
1061 rb->lseek(fd, 0, SEEK_SET);
1064 if (utf_decode)
1066 /* convert encoding of file from UTF-16 to UTF-8 */
1067 char temp_file[MAX_PATH];
1068 int fe;
1069 rb->lseek(fd, 2, SEEK_SET);
1070 rb->snprintf(temp_file, MAX_PATH, "%s~", current.lrc_file);
1071 fe = rb->creat(temp_file, 0666);
1072 if (fe < 0)
1074 rb->close(fd);
1075 return;
1077 rb->write(fe, BOM, BOM_SIZE);
1078 while ((readsize = rb->read(fd, temp_buf, MAX_LINE_LEN)) > 0)
1080 char *end = utf_decode(temp_buf, utf8line, readsize/2);
1081 rb->write(fe, utf8line, end-utf8line);
1083 rb->close(fe);
1084 rb->close(fd);
1085 rb->remove(current.lrc_file);
1086 rb->rename(temp_file, current.lrc_file);
1087 fd = rb->open(current.lrc_file, O_RDONLY);
1088 if (fd < 0) return;
1089 rb->lseek(fd, BOM_SIZE, SEEK_SET); /* skip bom */
1090 encoding = UTF_8;
1094 file_offset = rb->lseek(fd, 0, SEEK_CUR); /* used in line_parser */
1095 while ((readsize = rb->read_line(fd, temp_buf, MAX_LINE_LEN)) > 0)
1097 /* note: parse_snc_line() reads temp_buf for native data. */
1098 rb->iso_decode(temp_buf, utf8line, encoding, readsize+1);
1099 if (!line_parser(utf8line, file_offset))
1100 break;
1101 file_offset += readsize;
1103 rb->close(fd);
1105 current.loaded_lrc = true;
1106 calc_brpos(NULL, 0);
1107 init_time_tag();
1109 return;
1112 #ifdef LRC_SUPPORT_ID3
1113 /*******************************
1114 * read lyrics from id3
1115 *******************************/
1116 static unsigned long unsync(unsigned long b0, unsigned long b1,
1117 unsigned long b2, unsigned long b3)
1119 return (((long)(b0 & 0x7F) << (3*7)) |
1120 ((long)(b1 & 0x7F) << (2*7)) |
1121 ((long)(b2 & 0x7F) << (1*7)) |
1122 ((long)(b3 & 0x7F) << (0*7)));
1125 static unsigned long bytes2int(unsigned long b0, unsigned long b1,
1126 unsigned long b2, unsigned long b3)
1128 return (((long)(b0 & 0xFF) << (3*8)) |
1129 ((long)(b1 & 0xFF) << (2*8)) |
1130 ((long)(b2 & 0xFF) << (1*8)) |
1131 ((long)(b3 & 0xFF) << (0*8)));
1134 static int unsynchronize(char* tag, int len, bool *ff_found)
1136 int i;
1137 unsigned char c;
1138 unsigned char *rp, *wp;
1139 bool _ff_found = false;
1140 if(ff_found) _ff_found = *ff_found;
1142 wp = rp = (unsigned char *)tag;
1144 rp = (unsigned char *)tag;
1145 for(i = 0; i<len; i++) {
1146 /* Read the next byte and write it back, but don't increment the
1147 write pointer */
1148 c = *rp++;
1149 *wp = c;
1150 if(_ff_found) {
1151 /* Increment the write pointer if it isn't an unsynch pattern */
1152 if(c != 0)
1153 wp++;
1154 _ff_found = false;
1155 } else {
1156 if(c == 0xff)
1157 _ff_found = true;
1158 wp++;
1161 if(ff_found) *ff_found = _ff_found;
1162 return (long)wp - (long)tag;
1165 static int read_unsynched(int fd, void *buf, int len, bool *ff_found)
1167 int i;
1168 int rc;
1169 int remaining = len;
1170 char *wp;
1172 wp = buf;
1174 while(remaining) {
1175 rc = rb->read(fd, wp, remaining);
1176 if(rc <= 0)
1177 return rc;
1179 i = unsynchronize(wp, remaining, ff_found);
1180 remaining -= i;
1181 wp += i;
1184 return len;
1187 static unsigned char* utf8cpy(const unsigned char *src,
1188 unsigned char *dst, int count)
1190 rb->strlcpy(dst, src, count+1);
1191 return dst+rb->strlen(dst);
1194 static void parse_id3v2(int fd)
1196 int minframesize;
1197 int size;
1198 long framelen;
1199 char header[10];
1200 char tmp[8];
1201 unsigned char version;
1202 int bytesread = 0;
1203 unsigned char global_flags;
1204 int flags;
1205 bool global_unsynch = false;
1206 bool global_ff_found = false;
1207 bool unsynch = false;
1208 int rc;
1209 enum {NOLT, SYLT, USLT} type = NOLT;
1211 /* Bail out if the tag is shorter than 10 bytes */
1212 if(current.id3->id3v2len < 10)
1213 return;
1215 /* Read the ID3 tag version from the header */
1216 if(10 != rb->read(fd, header, 10))
1217 return;
1219 /* Get the total ID3 tag size */
1220 size = current.id3->id3v2len - 10;
1222 version = current.id3->id3version;
1223 switch ( version )
1225 case ID3_VER_2_2:
1226 minframesize = 8;
1227 break;
1229 case ID3_VER_2_3:
1230 minframesize = 12;
1231 break;
1233 case ID3_VER_2_4:
1234 minframesize = 12;
1235 break;
1237 default:
1238 /* unsupported id3 version */
1239 return;
1242 global_flags = header[5];
1244 /* Skip the extended header if it is present */
1245 if(global_flags & 0x40) {
1247 if(version == ID3_VER_2_3) {
1248 if(10 != rb->read(fd, header, 10))
1249 return;
1250 /* The 2.3 extended header size doesn't include the header size
1251 field itself. Also, it is not unsynched. */
1252 framelen =
1253 bytes2int(header[0], header[1], header[2], header[3]) + 4;
1255 /* Skip the rest of the header */
1256 rb->lseek(fd, framelen - 10, SEEK_CUR);
1259 if(version >= ID3_VER_2_4) {
1260 if(4 != rb->read(fd, header, 4))
1261 return;
1263 /* The 2.4 extended header size does include the entire header,
1264 so here we can just skip it. This header is unsynched. */
1265 framelen = unsync(header[0], header[1],
1266 header[2], header[3]);
1268 rb->lseek(fd, framelen - 4, SEEK_CUR);
1272 /* Is unsynchronization applied? */
1273 if(global_flags & 0x80) {
1274 global_unsynch = true;
1277 /* We must have at least minframesize bytes left for the
1278 * remaining frames to be interesting */
1279 while (size >= minframesize) {
1280 flags = 0;
1282 /* Read frame header and check length */
1283 if(version >= ID3_VER_2_3) {
1284 if(global_unsynch && version <= ID3_VER_2_3)
1285 rc = read_unsynched(fd, header, 10, &global_ff_found);
1286 else
1287 rc = rb->read(fd, header, 10);
1288 if(rc != 10)
1289 return;
1290 /* Adjust for the 10 bytes we read */
1291 size -= 10;
1293 flags = bytes2int(0, 0, header[8], header[9]);
1295 if (version >= ID3_VER_2_4) {
1296 framelen = unsync(header[4], header[5],
1297 header[6], header[7]);
1298 } else {
1299 /* version .3 files don't use synchsafe ints for
1300 * size */
1301 framelen = bytes2int(header[4], header[5],
1302 header[6], header[7]);
1304 } else {
1305 if(6 != rb->read(fd, header, 6))
1306 return;
1307 /* Adjust for the 6 bytes we read */
1308 size -= 6;
1310 framelen = bytes2int(0, header[3], header[4], header[5]);
1313 if(framelen == 0){
1314 if (header[0] == 0 && header[1] == 0 && header[2] == 0)
1315 return;
1316 else
1317 continue;
1320 unsynch = false;
1322 if(flags)
1324 if (version >= ID3_VER_2_4) {
1325 if(flags & 0x0040) { /* Grouping identity */
1326 rb->lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */
1327 framelen--;
1329 } else {
1330 if(flags & 0x0020) { /* Grouping identity */
1331 rb->lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */
1332 framelen--;
1336 if(flags & 0x000c) /* Compression or encryption */
1338 /* Skip it */
1339 size -= framelen;
1340 rb->lseek(fd, framelen, SEEK_CUR);
1341 continue;
1344 if(flags & 0x0002) /* Unsynchronization */
1345 unsynch = true;
1347 if (version >= ID3_VER_2_4) {
1348 if(flags & 0x0001) { /* Data length indicator */
1349 if(4 != rb->read(fd, tmp, 4))
1350 return;
1352 /* We don't need the data length */
1353 framelen -= 4;
1358 if (framelen == 0)
1359 continue;
1361 if (framelen < 0)
1362 return;
1364 if(!rb->memcmp( header, "SLT", 3 ) ||
1365 !rb->memcmp( header, "SYLT", 4 ))
1367 /* found a supported tag */
1368 type = SYLT;
1369 break;
1371 else if(!rb->memcmp( header, "ULT", 3 ) ||
1372 !rb->memcmp( header, "USLT", 4 ))
1374 /* found a supported tag */
1375 type = USLT;
1376 break;
1378 else
1380 /* not a supported tag*/
1381 if(global_unsynch && version <= ID3_VER_2_3) {
1382 size -= read_unsynched(fd, lrc_buffer, framelen, &global_ff_found);
1383 } else {
1384 size -= framelen;
1385 if( rb->lseek(fd, framelen, SEEK_CUR) == -1 )
1386 return;
1390 if(type == NOLT)
1391 return;
1393 int encoding = 0, chsiz;
1394 char *tag, *p, utf8line[MAX_LINE_LEN*3];
1395 unsigned char* (*utf_decode)(const unsigned char *,
1396 unsigned char *, int) = NULL;
1397 /* use middle of lrc_buffer to store tag data. */
1398 if(framelen >= LRC_BUFFER_SIZE/3)
1399 framelen = LRC_BUFFER_SIZE/3-1;
1400 tag = lrc_buffer+LRC_BUFFER_SIZE*2/3-framelen-1;
1401 if(global_unsynch && version <= ID3_VER_2_3)
1402 bytesread = read_unsynched(fd, tag, framelen, &global_ff_found);
1403 else
1404 bytesread = rb->read(fd, tag, framelen);
1406 if( bytesread != framelen )
1407 return;
1409 if(unsynch || (global_unsynch && version >= ID3_VER_2_4))
1410 bytesread = unsynchronize(tag, bytesread, NULL);
1412 tag[bytesread] = 0;
1413 encoding = tag[0];
1414 p = tag;
1415 /* skip some data */
1416 if(type == SYLT) {
1417 p += 6;
1418 } else {
1419 p += 4;
1422 /* check encoding and skip content descriptor */
1423 switch (encoding) {
1424 case 0x01: /* Unicode with or without BOM */
1425 case 0x02:
1427 /* Now check if there is a BOM
1428 (zero-width non-breaking space, 0xfeff)
1429 and if it is in little or big endian format */
1430 if(!rb->memcmp(p, "\xff\xfe", 2)) { /* Little endian? */
1431 utf_decode = rb->utf16LEdecode;
1432 } else if(!rb->memcmp(p, "\xfe\xff", 2)) { /* Big endian? */
1433 utf_decode = rb->utf16BEdecode;
1434 } else
1435 utf_decode = NULL;
1437 encoding = NUM_CODEPAGES;
1438 do {
1439 size = p[0] | p[1];
1440 p += 2;
1441 } while(size);
1442 chsiz = 2;
1443 break;
1445 default:
1446 utf_decode = utf8cpy;
1447 if(encoding == 0x03) /* UTF-8 encoded string */
1448 encoding = UTF_8;
1449 else
1450 encoding = prefs.encoding;
1451 p += rb->strlen(p)+1;
1452 chsiz = 1;
1453 break;
1455 if(encoding == NUM_CODEPAGES)
1457 /* check if there is a BOM */
1458 if(!rb->memcmp(p, "\xff\xfe", 2)) { /* Little endian? */
1459 utf_decode = rb->utf16LEdecode;
1460 p += 2;
1461 } else if(!rb->memcmp(p, "\xfe\xff", 2)) { /* Big endian? */
1462 utf_decode = rb->utf16BEdecode;
1463 p += 2;
1464 } else if(!utf_decode) {
1465 /* If there is no BOM (which is a specification violation),
1466 let's try to guess it. If one of the bytes is 0x00, it is
1467 probably the most significant one. */
1468 if(p[1] == 0)
1469 utf_decode = rb->utf16LEdecode;
1470 else
1471 utf_decode = rb->utf16BEdecode;
1474 bytesread -= (long)p - (long)tag;
1475 tag = p;
1477 while ( bytesread > 0
1478 && lrc_buffer_used+bytesread < LRC_BUFFER_SIZE*2/3
1479 && LRC_BUFFER_SIZE*2/3 < lrc_buffer_end)
1481 bool is_crlf = false;
1482 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line));
1483 if(!lrc_line)
1484 break;
1485 lrc_line->file_offset = -1;
1486 if(type == USLT)
1488 /* replace 0x0a and 0x0d with 0x00 */
1489 p = tag;
1490 while(1) {
1491 utf_decode(p, tmp, 2);
1492 if(!tmp[0]) break;
1493 if(tmp[0] == 0x0d || tmp[0] == 0x0a)
1495 if(tmp[0] == 0x0d && tmp[1] == 0x0a)
1496 is_crlf = true;
1497 p[0] = 0;
1498 p[chsiz-1] = 0;
1499 break;
1501 p += chsiz;
1504 if(encoding == NUM_CODEPAGES)
1506 unsigned char* utf8 = utf8line;
1507 p = tag;
1508 do {
1509 utf8 = utf_decode(p, utf8, 1);
1510 p += 2;
1511 } while(*(utf8-1));
1513 else
1515 size = rb->strlen(tag)+1;
1516 rb->iso_decode(tag, utf8line, encoding, size);
1517 p = tag+size;
1520 if(type == SYLT) { /* timestamp */
1521 lrc_line->time_start = bytes2int(p[0], p[1], p[2], p[3]);
1522 lrc_line->old_time_start = lrc_line->time_start;
1523 p += 4;
1524 utf_decode(p, tmp, 1);
1525 if(tmp[0] == 0x0a)
1526 p += chsiz;
1527 } else { /* USLT */
1528 lrc_line->time_start = 0;
1529 lrc_line->old_time_start = -1;
1530 if(is_crlf) p += chsiz;
1532 bytesread -= (long)p - (long)tag;
1533 tag = p;
1534 if(!add_lrc_line(lrc_line, utf8line))
1535 break;
1538 current.type = ID3_SYLT-SYLT+type;
1539 rb->strcpy(current.lrc_file, current.mp3_file);
1541 current.loaded_lrc = true;
1542 calc_brpos(NULL, 0);
1543 init_time_tag();
1545 return;
1548 static bool read_id3(void)
1550 int fd;
1552 if(current.id3->codectype != AFMT_MPA_L1
1553 && current.id3->codectype != AFMT_MPA_L2
1554 && current.id3->codectype != AFMT_MPA_L3)
1555 return false;
1557 fd = rb->open(current.mp3_file, O_RDONLY);
1558 if(fd < 0) return false;
1559 current.loaded_lrc = false;
1560 parse_id3v2(fd);
1561 rb->close(fd);
1562 return current.loaded_lrc;
1564 #endif /* LRC_SUPPORT_ID3 */
1566 /*******************************
1567 * Display information
1568 *******************************/
1569 static void display_state(void)
1571 const char *str = NULL;
1573 if (AUDIO_STOP)
1574 str = "Audio Stopped";
1575 else if (current.found_lrc)
1577 if (!current.loaded_lrc)
1578 str = "Loading lrc";
1579 else if (!current.ll_head)
1580 str = "No lyrics";
1583 #ifdef HAVE_LCD_BITMAP
1584 const char *info = NULL;
1586 if (AUDIO_PLAY && prefs.display_title)
1588 char *title = (current.title? current.title: current.id3->title);
1589 char *artist = (current.artist? current.artist: current.id3->artist);
1591 if (artist != NULL && title != NULL)
1593 rb->snprintf(temp_buf, MAX_LINE_LEN, "%s/%s", title, artist);
1594 info = temp_buf;
1596 else if (title != NULL)
1597 info = title;
1598 else if (current.mp3_file[0] == '/')
1599 info = rb->strrchr(current.mp3_file, '/')+1;
1600 else
1601 info = "(no info)";
1604 int w, h;
1605 struct screen* display;
1606 FOR_NB_SCREENS(i)
1608 display = rb->screens[i];
1609 display->set_viewport(&vp_info[i]);
1610 display->clear_viewport();
1611 if (info)
1612 display->puts_scroll(0, 0, info);
1613 if (str)
1615 display->set_viewport(&vp_lyrics[i]);
1616 display->clear_viewport();
1617 display->getstringsize(str, &w, &h);
1618 if (vp_lyrics[i].width - w < 0)
1619 display->puts_scroll(0, vp_lyrics[i].height/font_ui_height/2,
1620 str);
1621 else
1622 display->putsxy((vp_lyrics[i].width - w)*prefs.align/2,
1623 (vp_lyrics[i].height-font_ui_height)/2, str);
1624 display->set_viewport(&vp_info[i]);
1626 display->update_viewport();
1627 display->set_viewport(NULL);
1629 #else
1630 /* there is no place to display title or artist. */
1631 rb->lcd_clear_display();
1632 if (str)
1633 rb->lcd_puts_scroll(0, 0, str);
1634 rb->lcd_update();
1635 #endif /* HAVE_LCD_BITMAP */
1638 static void display_time(void)
1640 rb->snprintf(temp_buf, MAX_LINE_LEN, "%ld:%02ld/%ld:%02ld",
1641 current.elapsed/60000, (current.elapsed/1000)%60,
1642 current.length/60000, (current.length)/1000%60);
1643 #ifdef HAVE_LCD_BITMAP
1644 int y = (prefs.display_title? font_ui_height:0);
1645 FOR_NB_SCREENS(i)
1647 struct screen* display = rb->screens[i];
1648 display->set_viewport(&vp_info[i]);
1649 display->setfont(FONT_SYSFIXED);
1650 display->putsxy(0, y, temp_buf);
1651 rb->gui_scrollbar_draw(display, 0, y+SYSFONT_HEIGHT+1,
1652 vp_info[i].width, SYSFONT_HEIGHT-2,
1653 current.length, 0, current.elapsed, HORIZONTAL);
1654 display->update_viewport_rect(0, y, vp_info[i].width, SYSFONT_HEIGHT*2);
1655 display->setfont(uifont);
1656 display->set_viewport(NULL);
1658 #else
1659 rb->lcd_puts(0, 0, temp_buf);
1660 rb->lcd_update();
1661 #endif /* HAVE_LCD_BITMAP */
1664 /*******************************
1665 * Display lyrics
1666 *******************************/
1667 #ifdef HAVE_LCD_BITMAP
1668 static inline void set_to_default(struct screen *display)
1670 #if (LCD_DEPTH > 1)
1671 #ifdef HAVE_REMOTE_LCD
1672 if (display->screen_type != SCREEN_REMOTE)
1673 #endif
1674 display->set_foreground(prefs.active_color);
1675 #endif
1676 display->set_drawmode(DRMODE_SOLID);
1678 static inline void set_to_active(struct screen *display)
1680 #if (LCD_DEPTH > 1)
1681 #ifdef HAVE_REMOTE_LCD
1682 if (display->screen_type == SCREEN_REMOTE)
1683 display->set_drawmode(DRMODE_INVERSEVID);
1684 else
1685 #endif
1687 display->set_foreground(prefs.active_color);
1688 display->set_drawmode(DRMODE_SOLID);
1690 #else /* LCD_DEPTH == 1 */
1691 display->set_drawmode(DRMODE_INVERSEVID);
1692 #endif
1694 static inline void set_to_inactive(struct screen *display)
1696 #if (LCD_DEPTH > 1)
1697 #ifdef HAVE_REMOTE_LCD
1698 if (display->screen_type != SCREEN_REMOTE)
1699 #endif
1700 display->set_foreground(prefs.inactive_color);
1701 #endif
1702 display->set_drawmode(DRMODE_SOLID);
1705 static int display_lrc_line(struct lrc_line *lrc_line, int ypos, int i)
1707 struct screen *display = rb->screens[i];
1708 struct lrc_word *lrc_word;
1709 struct lrc_brpos *lrc_brpos;
1710 long time_start, time_end, elapsed;
1711 int count, width, nword;
1712 int xpos;
1713 const char *str;
1714 bool active_line;
1716 time_start = get_time_start(lrc_line);
1717 time_end = get_time_start(lrc_line->next);
1718 active_line = (time_start <= current.elapsed
1719 && time_end > current.elapsed);
1721 if (!lrc_line->width)
1723 /* empty line. draw bar during interval. */
1724 long rin = current.elapsed - time_start;
1725 long len = time_end - time_start;
1726 if (current.wipe && active_line && len >= 3500)
1728 elapsed = rin * vp_lyrics[i].width / len;
1729 set_to_inactive(display);
1730 display->fillrect(elapsed, ypos+font_ui_height/4+1,
1731 vp_lyrics[i].width-elapsed-1, font_ui_height/2-2);
1732 set_to_active(display);
1733 display->drawrect(0, ypos+font_ui_height/4,
1734 vp_lyrics[i].width, font_ui_height/2);
1735 display->fillrect(1, ypos+font_ui_height/4+1,
1736 elapsed-1, font_ui_height/2-2);
1737 set_to_default(display);
1739 return ypos + font_ui_height;
1742 lrc_brpos = calc_brpos(lrc_line, i);
1743 /* initialize line */
1744 xpos = (vp_lyrics[i].width - lrc_brpos->width)*prefs.align/2;
1745 count = 0;
1746 width = 0;
1748 active_line = active_line || !prefs.active_one_line;
1749 nword = lrc_line->nword-1;
1750 lrc_word = lrc_line->words + nword;
1751 str = lrc_word->word;
1752 /* only time_start of first word could be -1 */
1753 if (lrc_word->time_start != -1)
1754 time_end = get_word_time_start(lrc_word);
1755 else
1756 time_end = time_start;
1757 do {
1758 time_start = time_end;
1759 if (nword > 0)
1760 time_end = get_word_time_start(lrc_word-1);
1761 else
1762 time_end = get_time_start(lrc_line->next);
1764 if (time_start > current.elapsed || !active_line)
1766 /* inactive */
1767 elapsed = 0;
1769 else if (!current.wipe || time_end <= current.elapsed)
1771 /* active whole word */
1772 elapsed = lrc_word->width;
1774 else
1776 /* wipe word */
1777 long rin = current.elapsed - time_start;
1778 long len = time_end - time_start;
1779 elapsed = rin * lrc_word->width / len;
1782 int word_count = lrc_word->count;
1783 int word_width = lrc_word->width;
1784 set_to_active(display);
1785 while (word_count > 0 && word_width > 0)
1787 int c = lrc_brpos->count - count;
1788 int w = lrc_brpos->width - width;
1789 if (c > word_count || w > word_width)
1791 c = word_count;
1792 w = word_width;
1794 if (elapsed <= 0)
1796 set_to_inactive(display);
1798 else if (elapsed < w)
1800 /* wipe text */
1801 display->fillrect(xpos, ypos, elapsed, font_ui_height);
1802 set_to_inactive(display);
1803 display->fillrect(xpos+elapsed, ypos,
1804 w-elapsed, font_ui_height);
1805 #if (LCD_DEPTH > 1)
1806 #ifdef HAVE_REMOTE_LCD
1807 if (display->screen_type == SCREEN_REMOTE)
1808 display->set_drawmode(DRMODE_INVERSEVID);
1809 else
1810 #endif
1811 display->set_drawmode(DRMODE_BG);
1812 #else
1813 display->set_drawmode(DRMODE_INVERSEVID);
1814 #endif
1816 rb->strlcpy(temp_buf, str, c+1);
1817 display->putsxy(xpos, ypos, temp_buf);
1818 str += c;
1819 xpos += w;
1820 count += c;
1821 width += w;
1822 word_count -= c;
1823 word_width -= w;
1824 elapsed -= w;
1825 if (count >= lrc_brpos->count || width >= lrc_brpos->width)
1827 /* prepare for next line */
1828 lrc_brpos++;
1829 str = lrc_skip_space(str);
1830 xpos = (vp_lyrics[i].width - lrc_brpos->width)*prefs.align/2;
1831 ypos += font_ui_height;
1832 count = 0;
1833 width = 0;
1836 lrc_word--;
1837 } while (nword--);
1838 set_to_default(display);
1839 return ypos;
1841 #endif /* HAVE_LCD_BITMAP */
1843 static void display_lrcs(void)
1845 long time_start, time_end, rin, len;
1846 int nline[NB_SCREENS] = {0};
1847 struct lrc_line *lrc_center = current.ll_head;
1849 if (!lrc_center) return;
1851 while (get_time_start(lrc_center->next) <= current.elapsed)
1853 nline[SCREEN_MAIN] += lrc_center->nline[SCREEN_MAIN];
1854 #ifdef HAVE_REMOTE_LCD
1855 nline[SCREEN_REMOTE] += lrc_center->nline[SCREEN_REMOTE];
1856 #endif
1857 lrc_center = lrc_center->next;
1860 time_start = get_time_start(lrc_center);
1861 time_end = get_time_start(lrc_center->next);
1862 rin = current.elapsed - time_start;
1863 len = time_end - time_start;
1865 struct screen *display;
1866 FOR_NB_SCREENS(i)
1868 display = rb->screens[i];
1869 /* display current line at the center of the viewport */
1870 display->set_viewport(&vp_lyrics[i]);
1871 display->clear_viewport();
1872 #ifdef HAVE_LCD_BITMAP
1873 struct lrc_line *lrc_line;
1874 int y, ypos = 0, nblines = vp_lyrics[i].height/font_ui_height;
1875 y = (nblines-1)/2;
1876 if (rin < 0)
1878 /* current.elapsed < time of first lrc */
1879 if (!current.wipe)
1880 ypos = (time_start - current.elapsed)
1881 * font_ui_height / time_start;
1882 else
1883 y++;
1885 else if (len > 0)
1887 if (!current.wipe)
1888 ypos = - rin * lrc_center->nline[i] * font_ui_height / len;
1889 else
1891 long elapsed = rin * lrc_center->width / len;
1892 struct lrc_brpos *lrc_brpos = calc_brpos(lrc_center, i);
1893 while (elapsed > lrc_brpos->width)
1895 elapsed -= lrc_brpos->width;
1896 y--;
1897 lrc_brpos++;
1902 /* find first line to display */
1903 y -= nline[i];
1904 lrc_line = current.ll_head;
1905 while (y < -lrc_line->nline[i])
1907 y += lrc_line->nline[i];
1908 lrc_line = lrc_line->next;
1911 ypos += y*font_ui_height;
1912 while (lrc_line && ypos < vp_lyrics[i].height)
1914 ypos = display_lrc_line(lrc_line, ypos, i);
1915 lrc_line = lrc_line->next;
1917 if (!lrc_line && ypos < vp_lyrics[i].height)
1918 display->putsxy(0, ypos, "[end]");
1919 #else /* HAVE_LCD_CHARCELLS */
1920 struct lrc_line *lrc_line = lrc_center;
1921 struct lrc_brpos *lrc_brpos = calc_brpos(lrc_line, i);
1922 long elapsed = 0;
1923 const char *str = get_lrc_str(lrc_line);
1924 int x = vp_lyrics[i].width/2, y = 0;
1926 if (rin >= 0 && len > 0)
1928 elapsed = rin * lrc_center->width / len;
1929 while (elapsed > lrc_brpos->width)
1931 elapsed -= lrc_brpos->width;
1932 str = lrc_skip_space(str+lrc_brpos->count);
1933 lrc_brpos++;
1936 rb->strlcpy(temp_buf, str, lrc_brpos->count+1);
1938 x -= elapsed;
1939 if (x < 0)
1940 display->puts(0, y, temp_buf + rb->utf8seek(temp_buf, -x));
1941 else
1942 display->puts(x, y, temp_buf);
1943 x += rb->utf8length(temp_buf)+1;
1944 lrc_line = lrc_line->next;
1945 if (!lrc_line && x < vp_lyrics[i].width)
1947 if (x < vp_lyrics[i].width/2)
1948 x = vp_lyrics[i].width/2;
1949 display->puts(x, y, "[end]");
1951 #endif /* HAVE_LCD_BITMAP */
1952 display->update_viewport();
1953 display->set_viewport(NULL);
1957 /*******************************
1958 * Browse lyrics and edit time.
1959 *******************************/
1960 /* point playing line in lyrics */
1961 static enum themable_icons get_icon(int selected, void * data)
1963 (void) data;
1964 struct lrc_line *lrc_line = get_lrc_line(selected);
1965 if (lrc_line)
1967 long time_start = get_time_start(lrc_line);
1968 long time_end = get_time_start(lrc_line->next);
1969 long elapsed = current.id3->elapsed;
1970 if (time_start <= elapsed && time_end > elapsed)
1971 return Icon_Moving;
1973 return Icon_NOICON;
1975 static const char *get_lrc_timeline(int selected, void *data,
1976 char *buffer, size_t buffer_len)
1978 (void) data;
1979 struct lrc_line *lrc_line = get_lrc_line(selected);
1980 if (lrc_line)
1982 format_time_tag(temp_buf, get_time_start(lrc_line));
1983 rb->snprintf(buffer, buffer_len, "[%s]%s",
1984 temp_buf, get_lrc_str(lrc_line));
1985 return buffer;
1987 return NULL;
1990 static void save_changes(void)
1992 char new_file[MAX_PATH], *p;
1993 bool success = false;
1994 int fd, fe;
1995 if (!current.changed_lrc)
1996 return;
1997 rb->splash(HZ/2, "Saving changes...");
1998 if (current.type == TXT || current.type > NUM_TYPES)
2000 /* save changes to new .lrc file */
2001 rb->strcpy(new_file, current.lrc_file);
2002 p = rb->strrchr(new_file, '.');
2003 rb->strcpy(p, extentions[LRC]);
2005 else
2007 /* file already exists. use temp file. */
2008 rb->snprintf(new_file, MAX_PATH, "%s~", current.lrc_file);
2010 fd = rb->creat(new_file, 0666);
2011 fe = rb->open(current.lrc_file, O_RDONLY);
2012 if (fd >= 0 && fe >= 0)
2014 struct lrc_line *lrc_line, *temp_lrc;
2015 off_t curr = 0, next = 0, size = 0, offset = 0;
2016 for (lrc_line = current.ll_head; lrc_line;
2017 lrc_line = lrc_line->next)
2019 /* apply offset and set old_time_start -1 to indicate
2020 that time tag is not saved yet. */
2021 lrc_line->time_start = get_time_start(lrc_line);
2022 lrc_line->old_time_start = -1;
2024 current.offset = 0;
2025 if (current.type > NUM_TYPES)
2027 curr = -1;
2028 rb->write(fd, BOM, BOM_SIZE);
2030 else
2031 size = rb->filesize(fe);
2032 while (curr < size)
2034 /* find offset of next tag */
2035 lrc_line = NULL;
2036 for (temp_lrc = current.ll_head, next = size;
2037 temp_lrc; temp_lrc = temp_lrc->next)
2039 offset = temp_lrc->file_offset;
2040 if (offset < next && temp_lrc->old_time_start == -1)
2042 lrc_line = temp_lrc;
2043 next = offset;
2044 if (offset <= curr) break;
2047 offset = current.offset_file_offset;
2048 if (offset >= 0 && offset < next)
2050 lrc_line = NULL;
2051 next = offset;
2052 current.offset_file_offset = -1;
2054 if (next > curr)
2056 if (curr == -1) curr = 0;
2057 /* copy before the next tag */
2058 while (curr < next)
2060 ssize_t count = next-curr;
2061 if (count > MAX_LINE_LEN)
2062 count = MAX_LINE_LEN;
2063 if (rb->read(fe, temp_buf, count)!=count)
2064 break;
2065 rb->write(fd, temp_buf, count);
2066 curr += count;
2068 if (curr < next || curr >= size) break;
2070 /* write tag to new file and skip tag in backup */
2071 if (lrc_line != NULL)
2073 lrc_line->file_offset = rb->lseek(fd, 0, SEEK_CUR);
2074 lrc_line->old_time_start = lrc_line->time_start;
2075 long t = lrc_line->time_start;
2076 if (current.type == SNC)
2078 rb->fdprintf(fd, "%02ld%02ld%02ld%02ld", (t/3600000)%100,
2079 (t/60000)%60, (t/1000)%60, (t/10)%100);
2080 /* skip time tag */
2081 curr += rb->read(fe, temp_buf, 8);
2083 else /* LRC || LRC8 */
2085 format_time_tag(temp_buf, t);
2086 rb->fdprintf(fd, "[%s]", temp_buf);
2088 if (next == -1)
2090 rb->fdprintf(fd, "%s\n", get_lrc_str(lrc_line));
2093 if (current.type == LRC || current.type == LRC8)
2095 /* skip both time tag and offset tag */
2096 while (curr++<size && rb->read(fe, temp_buf, 1)==1)
2097 if (temp_buf[0]==']') break;
2100 success = (curr>=size);
2102 if (fe >= 0) rb->close(fe);
2103 if (fd >= 0) rb->close(fd);
2105 if (success)
2107 if (current.type == TXT || current.type > NUM_TYPES)
2109 current.type = LRC;
2110 rb->strcpy(current.lrc_file, new_file);
2112 else
2114 rb->remove(current.lrc_file);
2115 rb->rename(new_file, current.lrc_file);
2118 else
2120 rb->remove(new_file);
2121 rb->splash(HZ, "Could not save changes.");
2123 rb->reload_directory();
2124 current.changed_lrc = false;
2126 static int timetag_editor(void)
2128 struct gui_synclist gui_editor;
2129 struct lrc_line *lrc_line;
2130 bool exit = false;
2131 int button, idx, selected = 0;
2133 if (current.id3 == NULL || !current.ll_head)
2135 rb->splash(HZ, "No lyrics");
2136 return LRC_GOTO_MAIN;
2139 get_lrc_line(-1); /* initialize static variables */
2141 for (lrc_line = current.ll_head, idx = 0;
2142 lrc_line; lrc_line = lrc_line->next, idx++)
2144 long time_start = get_time_start(lrc_line);
2145 long time_end = get_time_start(lrc_line->next);
2146 long elapsed = current.id3->elapsed;
2147 if (time_start <= elapsed && time_end > elapsed)
2148 selected = idx;
2151 rb->gui_synclist_init(&gui_editor, &get_lrc_timeline, NULL, false, 1, NULL);
2152 rb->gui_synclist_set_nb_items(&gui_editor, current.nlrcline);
2153 rb->gui_synclist_set_icon_callback(&gui_editor, get_icon);
2154 rb->gui_synclist_set_title(&gui_editor, "Timetag Editor",
2155 Icon_Menu_functioncall);
2156 rb->gui_synclist_select_item(&gui_editor, selected);
2157 rb->gui_synclist_draw(&gui_editor);
2159 while (!exit)
2161 button = rb->get_action(CONTEXT_TREE, TIMEOUT_BLOCK);
2162 if (rb->gui_synclist_do_button(&gui_editor, &button,
2163 LIST_WRAP_UNLESS_HELD))
2164 continue;
2166 switch (button)
2168 case ACTION_STD_OK:
2169 idx = rb->gui_synclist_get_sel_pos(&gui_editor);
2170 lrc_line = get_lrc_line(idx);
2171 if (lrc_line)
2173 set_time_start(lrc_line, current.id3->elapsed-500);
2174 rb->gui_synclist_draw(&gui_editor);
2176 break;
2177 case ACTION_STD_CONTEXT:
2178 idx = rb->gui_synclist_get_sel_pos(&gui_editor);
2179 lrc_line = get_lrc_line(idx);
2180 if (lrc_line)
2182 long temp_time = get_time_start(lrc_line);
2183 if (lrc_set_time(get_lrc_str(lrc_line), NULL,
2184 &temp_time, 10, 0, current.length,
2185 LST_SET_MSEC|LST_SET_SEC|LST_SET_MIN) == 1)
2186 return PLUGIN_USB_CONNECTED;
2187 set_time_start(lrc_line, temp_time);
2188 rb->gui_synclist_draw(&gui_editor);
2190 break;
2191 case ACTION_TREE_STOP:
2192 case ACTION_STD_CANCEL:
2193 exit = true;
2194 break;
2195 default:
2196 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
2197 return PLUGIN_USB_CONNECTED;
2198 break;
2202 FOR_NB_SCREENS(idx)
2203 rb->screens[idx]->stop_scroll();
2205 if (current.changed_lrc)
2207 MENUITEM_STRINGLIST(save_menu, "Save Changes?", NULL,
2208 "Yes", "No (save later)", "Discard All Changes")
2209 button = 0;
2210 exit = false;
2211 while (!exit)
2213 switch (rb->do_menu(&save_menu, &button, NULL, false))
2215 case 0:
2216 sort_lrcs();
2217 save_changes();
2218 exit = true;
2219 break;
2220 case 1:
2221 sort_lrcs();
2222 exit = true;
2223 break;
2224 case 2:
2225 init_time_tag();
2226 exit = true;
2227 break;
2228 case MENU_ATTACHED_USB:
2229 return PLUGIN_USB_CONNECTED;
2233 return LRC_GOTO_MAIN;
2236 /*******************************
2237 * Settings.
2238 *******************************/
2239 static void load_or_save_settings(bool save)
2241 static const char config_file[] = "lrcplayer.cfg";
2242 static struct configdata config[] = {
2243 #ifdef HAVE_LCD_COLOR
2244 { TYPE_INT, 0, 0xffffff, { .int_p = &prefs.inactive_color },
2245 "inactive color", NULL },
2246 #endif
2247 #ifdef HAVE_LCD_BITMAP
2248 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.wrap }, "wrap", NULL },
2249 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.wipe }, "wipe", NULL },
2250 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.active_one_line },
2251 "active one line", NULL },
2252 { TYPE_INT, 0, 2, { .int_p = &prefs.align }, "align", NULL },
2253 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.statusbar_on },
2254 "statusbar on", NULL },
2255 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.display_title },
2256 "display title", NULL },
2257 #endif
2258 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.display_time },
2259 "display time", NULL },
2260 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.backlight_on },
2261 "backlight on", NULL },
2263 { TYPE_STRING, 0, sizeof(prefs.lrc_directory),
2264 { .string = prefs.lrc_directory }, "lrc directory", NULL },
2265 { TYPE_INT, -1, NUM_CODEPAGES-1, { .int_p = &prefs.encoding },
2266 "encoding", NULL },
2267 #ifdef LRC_SUPPORT_ID3
2268 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.read_id3 }, "read id3", NULL },
2269 #endif
2272 if (!save)
2274 /* initialize setting */
2275 #if LCD_DEPTH > 1
2276 prefs.active_color = rb->lcd_get_foreground();
2277 prefs.inactive_color = LCD_LIGHTGRAY;
2278 #endif
2279 #ifdef HAVE_LCD_BITMAP
2280 prefs.wrap = true;
2281 prefs.wipe = true;
2282 prefs.active_one_line = false;
2283 prefs.align = 1; /* center */
2284 prefs.statusbar_on = false;
2285 prefs.display_title = true;
2286 #endif
2287 prefs.display_time = true;
2288 prefs.backlight_on = false;
2289 #ifdef LRC_SUPPORT_ID3
2290 prefs.read_id3 = true;
2291 #endif
2292 rb->strcpy(prefs.lrc_directory, "/Lyrics");
2293 prefs.encoding = -1; /* default codepage */
2295 configfile_load(config_file, config, ARRAYLEN(config), 0);
2297 else if (rb->memcmp(&old_prefs, &prefs, sizeof(prefs)))
2299 rb->splash(0, "Saving Settings");
2300 configfile_save(config_file, config, ARRAYLEN(config), 0);
2302 rb->memcpy(&old_prefs, &prefs, sizeof(prefs));
2305 static bool lrc_theme_menu(void)
2307 enum {
2308 #ifdef HAVE_LCD_BITMAP
2309 LRC_MENU_STATUSBAR,
2310 LRC_MENU_DISP_TITLE,
2311 #endif
2312 LRC_MENU_DISP_TIME,
2313 #ifdef HAVE_LCD_COLOR
2314 LRC_MENU_INACTIVE_COLOR,
2315 #endif
2316 LRC_MENU_BACKLIGHT,
2319 int selected = 0;
2320 bool exit = false, usb = false;
2322 MENUITEM_STRINGLIST(menu, "Theme Settings", NULL,
2323 #ifdef HAVE_LCD_BITMAP
2324 "Show Statusbar", "Display Title",
2325 #endif
2326 "Display Time",
2327 #ifdef HAVE_LCD_COLOR
2328 "Inactive Colour",
2329 #endif
2330 "Backlight Always On");
2332 while (!exit && !usb)
2334 switch (rb->do_menu(&menu, &selected, NULL, false))
2336 #ifdef HAVE_LCD_BITMAP
2337 case LRC_MENU_STATUSBAR:
2338 usb = rb->set_bool("Show Statusbar", &prefs.statusbar_on);
2339 break;
2340 case LRC_MENU_DISP_TITLE:
2341 usb = rb->set_bool("Display Title", &prefs.display_title);
2342 break;
2343 #endif
2344 case LRC_MENU_DISP_TIME:
2345 usb = rb->set_bool("Display Time", &prefs.display_time);
2346 break;
2347 #ifdef HAVE_LCD_COLOR
2348 case LRC_MENU_INACTIVE_COLOR:
2349 usb = rb->set_color(NULL, "Inactive Colour",
2350 &prefs.inactive_color, -1);
2351 break;
2352 #endif
2353 case LRC_MENU_BACKLIGHT:
2354 usb = rb->set_bool("Backlight Always On", &prefs.backlight_on);
2355 break;
2356 case MENU_ATTACHED_USB:
2357 usb = true;
2358 break;
2359 default:
2360 exit = true;
2361 break;
2365 return usb;
2368 #ifdef HAVE_LCD_BITMAP
2369 static bool lrc_display_menu(void)
2371 enum {
2372 LRC_MENU_WRAP,
2373 LRC_MENU_WIPE,
2374 LRC_MENU_ALIGN,
2375 LRC_MENU_LINE_MODE,
2378 int selected = 0;
2379 bool exit = false, usb = false;
2381 MENUITEM_STRINGLIST(menu, "Display Settings", NULL,
2382 "Wrap", "Wipe", "Alignment",
2383 "Activate Only Current Line");
2385 struct opt_items align_names[] = {
2386 {"Left", -1}, {"Centre", -1}, {"Right", -1},
2389 while (!exit && !usb)
2391 switch (rb->do_menu(&menu, &selected, NULL, false))
2393 case LRC_MENU_WRAP:
2394 usb = rb->set_bool("Wrap", &prefs.wrap);
2395 break;
2396 case LRC_MENU_WIPE:
2397 usb = rb->set_bool("Wipe", &prefs.wipe);
2398 break;
2399 case LRC_MENU_ALIGN:
2400 usb = rb->set_option("Alignment", &prefs.align, INT,
2401 align_names, 3, NULL);
2402 break;
2403 case LRC_MENU_LINE_MODE:
2404 usb = rb->set_bool("Activate Only Current Line",
2405 &prefs.active_one_line);
2406 break;
2407 case MENU_ATTACHED_USB:
2408 usb = true;
2409 break;
2410 default:
2411 exit = true;
2412 break;
2416 return usb;
2418 #endif /* HAVE_LCD_BITMAP */
2420 static bool lrc_lyrics_menu(void)
2422 enum {
2423 LRC_MENU_ENCODING,
2424 #ifdef LRC_SUPPORT_ID3
2425 LRC_MENU_READ_ID3,
2426 #endif
2427 LRC_MENU_LRC_DIR,
2430 int selected = 0;
2431 bool exit = false, usb = false;
2433 struct opt_items cp_names[NUM_CODEPAGES+1];
2434 int old_val;
2436 MENUITEM_STRINGLIST(menu, "Lyrics Settings", NULL,
2437 "Encoding",
2438 #ifdef LRC_SUPPORT_ID3
2439 "Read ID3 tag",
2440 #endif
2441 "Lrc Directry");
2443 cp_names[0].string = "Use default codepage";
2444 cp_names[0].voice_id = -1;
2445 for (old_val = 1; old_val < NUM_CODEPAGES+1; old_val++)
2447 cp_names[old_val].string = rb->get_codepage_name(old_val-1);
2448 cp_names[old_val].voice_id = -1;
2451 while (!exit && !usb)
2453 switch (rb->do_menu(&menu, &selected, NULL, false))
2455 case LRC_MENU_ENCODING:
2456 prefs.encoding++;
2457 old_val = prefs.encoding;
2458 usb = rb->set_option("Encoding", &prefs.encoding, INT,
2459 cp_names, NUM_CODEPAGES+1, NULL);
2460 if (prefs.encoding != old_val)
2462 save_changes();
2463 if (current.type < NUM_TYPES)
2465 /* let reload lrc file to apply encoding setting */
2466 reset_current_data();
2469 prefs.encoding--;
2470 break;
2471 #ifdef LRC_SUPPORT_ID3
2472 case LRC_MENU_READ_ID3:
2473 usb = rb->set_bool("Read ID3 tag", &prefs.read_id3);
2474 break;
2475 #endif
2476 case LRC_MENU_LRC_DIR:
2477 rb->strcpy(temp_buf, prefs.lrc_directory);
2478 if (!rb->kbd_input(temp_buf, sizeof(prefs.lrc_directory)))
2479 rb->strcpy(prefs.lrc_directory, temp_buf);
2480 break;
2481 case MENU_ATTACHED_USB:
2482 usb = true;
2483 break;
2484 default:
2485 exit = true;
2486 break;
2490 return usb;
2493 #ifdef LRC_DEBUG
2494 static const char* lrc_debug_data(int selected, void * data,
2495 char * buffer, size_t buffer_len)
2497 (void)data;
2498 switch (selected)
2500 case 0:
2501 rb->strlcpy(buffer, current.mp3_file, buffer_len);
2502 break;
2503 case 1:
2504 rb->strlcpy(buffer, current.lrc_file, buffer_len);
2505 break;
2506 case 2:
2507 rb->snprintf(buffer, buffer_len, "buf usage: %d,%d/%d",
2508 (int)lrc_buffer_used, (int)lrc_buffer_end,
2509 (int)lrc_buffer_size);
2510 break;
2511 case 3:
2512 rb->snprintf(buffer, buffer_len, "line count: %d,%d",
2513 current.nlrcline, current.nlrcbrpos);
2514 break;
2515 case 4:
2516 rb->snprintf(buffer, buffer_len, "loaded lrc? %s",
2517 current.loaded_lrc?"yes":"no");
2518 break;
2519 case 5:
2520 rb->snprintf(buffer, buffer_len, "too many lines? %s",
2521 current.too_many_lines?"yes":"no");
2522 break;
2523 default:
2524 return NULL;
2526 return buffer;
2529 static bool lrc_debug_menu(void)
2531 struct simplelist_info info;
2532 rb->simplelist_info_init(&info, "Debug Menu", 6, NULL);
2533 info.hide_selection = true;
2534 info.scroll_all = true;
2535 info.get_name = lrc_debug_data;
2536 return rb->simplelist_show_list(&info);
2538 #endif
2540 /* returns one of enum lrc_screen or enum plugin_status */
2541 static int lrc_menu(void)
2543 enum {
2544 LRC_MENU_THEME,
2545 #ifdef HAVE_LCD_BITMAP
2546 LRC_MENU_DISPLAY,
2547 #endif
2548 LRC_MENU_LYRICS,
2549 LRC_MENU_PLAYBACK,
2550 #ifdef LRC_DEBUG
2551 LRC_MENU_DEBUG,
2552 #endif
2553 LRC_MENU_OFFSET,
2554 LRC_MENU_TIMETAG_EDITOR,
2555 LRC_MENU_QUIT,
2558 MENUITEM_STRINGLIST(menu, "Lrcplayer Menu", NULL,
2559 "Theme Settings",
2560 #ifdef HAVE_LCD_BITMAP
2561 "Display Settings",
2562 #endif
2563 "Lyrics Settings",
2564 "Playback Control",
2565 #ifdef LRC_DEBUG
2566 "Debug Menu",
2567 #endif
2568 "Time Offset", "Timetag Editor",
2569 "Quit");
2570 int selected = 0, ret = LRC_GOTO_MENU;
2571 bool usb = false;
2573 while (ret == LRC_GOTO_MENU)
2575 switch (rb->do_menu(&menu, &selected, NULL, false))
2577 case LRC_MENU_THEME:
2578 usb = lrc_theme_menu();
2579 break;
2580 #ifdef HAVE_LCD_BITMAP
2581 case LRC_MENU_DISPLAY:
2582 usb = lrc_display_menu();
2583 break;
2584 #endif
2585 case LRC_MENU_LYRICS:
2586 usb = lrc_lyrics_menu();
2587 break;
2588 case LRC_MENU_PLAYBACK:
2589 usb = playback_control(NULL);
2590 ret = LRC_GOTO_MAIN;
2591 break;
2592 #ifdef LRC_DEBUG
2593 case LRC_MENU_DEBUG:
2594 usb = lrc_debug_menu();
2595 ret = LRC_GOTO_MAIN;
2596 break;
2597 #endif
2598 case LRC_MENU_OFFSET:
2599 usb = (lrc_set_time("Time Offset", "sec", &current.offset,
2600 10, -60*1000, 60*1000,
2601 LST_SET_MSEC|LST_SET_SEC) == 1);
2602 ret = LRC_GOTO_MAIN;
2603 break;
2604 case LRC_MENU_TIMETAG_EDITOR:
2605 ret = LRC_GOTO_EDITOR;
2606 break;
2607 case LRC_MENU_QUIT:
2608 ret = PLUGIN_OK;
2609 break;
2610 case MENU_ATTACHED_USB:
2611 usb = true;
2612 break;
2613 default:
2614 ret = LRC_GOTO_MAIN;
2615 break;
2617 if (usb)
2618 ret = PLUGIN_USB_CONNECTED;
2620 return ret;
2623 /*******************************
2624 * Main.
2625 *******************************/
2626 /* returns true if song has changed to know when to load new lyrics. */
2627 static bool check_audio_status(void)
2629 static int last_audio_status = 0;
2630 if (current.ff_rewind == -1)
2631 current.audio_status = rb->audio_status();
2632 current.id3 = rb->audio_current_track();
2633 if ((last_audio_status^current.audio_status)&AUDIO_STATUS_PLAY)
2635 last_audio_status = current.audio_status;
2636 return true;
2638 if (AUDIO_STOP || current.id3 == NULL)
2639 return false;
2640 if (rb->strcmp(current.mp3_file, current.id3->path))
2642 return true;
2644 return false;
2646 static void ff_rewind(long time, bool resume)
2648 if (AUDIO_PLAY)
2650 if (!AUDIO_PAUSE)
2652 resume = true;
2653 rb->audio_pause();
2655 rb->audio_ff_rewind(time);
2656 rb->sleep(HZ/10); /* take affect seeking */
2657 if (resume)
2658 rb->audio_resume();
2662 static int handle_button(void)
2664 int ret = LRC_GOTO_MAIN;
2665 static int step = 0;
2666 int limit, button = rb->get_action(CONTEXT_WPS, HZ/10);
2667 switch (button)
2669 case ACTION_WPS_BROWSE:
2670 #if CONFIG_KEYPAD == ONDIO_PAD
2671 /* ondio doesn't have ACTION_WPS_MENU,
2672 so use ACTION_WPS_BROWSE for menu */
2673 ret = LRC_GOTO_MENU;
2674 break;
2675 #endif
2676 case ACTION_WPS_STOP:
2677 save_changes();
2678 ret = PLUGIN_OK;
2679 break;
2680 case ACTION_WPS_PLAY:
2681 if (AUDIO_STOP && rb->global_status->resume_index != -1)
2683 if (rb->playlist_resume() != -1)
2685 rb->playlist_start(rb->global_status->resume_index,
2686 rb->global_status->resume_offset);
2689 else if (AUDIO_PAUSE)
2690 rb->audio_resume();
2691 else
2692 rb->audio_pause();
2693 break;
2694 case ACTION_WPS_SEEKFWD:
2695 case ACTION_WPS_SEEKBACK:
2696 if (AUDIO_STOP)
2697 break;
2698 if (current.ff_rewind > -1)
2700 if (button == ACTION_WPS_SEEKFWD)
2701 /* fast forwarding, calc max step relative to end */
2702 limit = (current.length - current.ff_rewind) * 3 / 100;
2703 else
2704 /* rewinding, calc max step relative to start */
2705 limit = (current.ff_rewind) * 3 / 100;
2706 limit = MAX(limit, 500);
2708 if (step > limit)
2709 step = limit;
2711 if (button == ACTION_WPS_SEEKFWD)
2712 current.ff_rewind += step;
2713 else
2714 current.ff_rewind -= step;
2716 if (current.ff_rewind > current.length-100)
2717 current.ff_rewind = current.length-100;
2718 if (current.ff_rewind < 0)
2719 current.ff_rewind = 0;
2721 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
2722 step += step >> (rb->global_settings->ff_rewind_accel + 3);
2724 else
2726 current.ff_rewind = current.elapsed;
2727 if (!AUDIO_PAUSE)
2728 rb->audio_pause();
2729 step = 1000 * rb->global_settings->ff_rewind_min_step;
2731 break;
2732 case ACTION_WPS_STOPSEEK:
2733 if (current.ff_rewind == -1)
2734 break;
2735 ff_rewind(current.ff_rewind, !AUDIO_PAUSE);
2736 current.elapsed = current.ff_rewind;
2737 current.ff_rewind = -1;
2738 break;
2739 case ACTION_WPS_SKIPNEXT:
2740 rb->audio_next();
2741 break;
2742 case ACTION_WPS_SKIPPREV:
2743 if (current.elapsed < 3000)
2744 rb->audio_prev();
2745 else
2746 ff_rewind(0, false);
2747 break;
2748 case ACTION_WPS_VOLDOWN:
2749 limit = rb->sound_min(SOUND_VOLUME);
2750 if (--rb->global_settings->volume < limit)
2751 rb->global_settings->volume = limit;
2752 rb->sound_set(SOUND_VOLUME, rb->global_settings->volume);
2753 break;
2754 case ACTION_WPS_VOLUP:
2755 limit = rb->sound_max(SOUND_VOLUME);
2756 if (++rb->global_settings->volume > limit)
2757 rb->global_settings->volume = limit;
2758 rb->sound_set(SOUND_VOLUME, rb->global_settings->volume);
2759 break;
2760 case ACTION_WPS_CONTEXT:
2761 ret = LRC_GOTO_EDITOR;
2762 break;
2763 case ACTION_WPS_MENU:
2764 ret = LRC_GOTO_MENU;
2765 break;
2766 default:
2767 if(rb->default_event_handler(button) == SYS_USB_CONNECTED)
2768 ret = PLUGIN_USB_CONNECTED;
2769 break;
2771 return ret;
2774 static int lrc_main(void)
2776 int ret = LRC_GOTO_MAIN;
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 uifont = rb->screens[0]->getuifont();
2919 font_ui_height = rb->font_get(uifont)->height;
2920 #endif
2922 lrc_buffer = rb->plugin_get_buffer(&lrc_buffer_size);
2923 lrc_buffer = (void *)(((long)lrc_buffer+3)&~3); /* 4 bytes aligned */
2924 lrc_buffer_size = (lrc_buffer_size - 4)&~3;
2926 reset_current_data();
2927 current.id3 = NULL;
2928 current.mp3_file[0] = 0;
2929 current.lrc_file[0] = 0;
2930 current.ff_rewind = -1;
2931 current.found_lrc = false;
2932 if (parameter && check_audio_status())
2934 const char *ext;
2935 rb->strcpy(current.mp3_file, current.id3->path);
2936 /* use passed parameter as lrc file. */
2937 rb->strcpy(current.lrc_file, parameter);
2938 if (!rb->file_exists(current.lrc_file))
2940 rb->splash(HZ, "Specified file dose not exist.");
2941 return PLUGIN_ERROR;
2943 ext = rb->strrchr(current.lrc_file, '.');
2944 if (!ext) ext = current.lrc_file;
2945 for (current.type = 0; current.type < NUM_TYPES; current.type++)
2947 if (!rb->strcasecmp(ext, extentions[current.type]))
2948 break;
2950 if (current.type == NUM_TYPES)
2952 rb->splashf(HZ, "%s is not supported", ext);
2953 return PLUGIN_ERROR;
2955 current.found_lrc = true;
2958 while (ret >= PLUGIN_OTHER)
2960 switch (ret)
2962 case LRC_GOTO_MAIN:
2963 ret = lrc_main();
2964 break;
2965 case LRC_GOTO_MENU:
2966 ret = lrc_menu();
2967 break;
2968 case LRC_GOTO_EDITOR:
2969 ret = timetag_editor();
2970 break;
2971 default:
2972 ret = PLUGIN_ERROR;
2973 break;
2977 load_or_save_settings(true);
2978 return ret;