new plugin: FS#10559 - lrcplayer: a plugin to view .lrc file.
[kugel-rb.git] / apps / plugins / lrcplayer.c
blob2bc8197150d880b5bc39d05ef012bb53a7886e13
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id: lrcplayer.c 85 2010-06-03 10:01:19Z teru $
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>
27 PLUGIN_HEADER
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 = 10,
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 enum {
79 ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT,
80 } align;
81 bool statusbar_on;
82 bool display_title;
83 #endif
84 bool display_time;
85 bool backlight_on;
87 /* file settings */
88 char lrc_directory[64];
89 int encoding;
90 #ifdef LRC_SUPPORT_ID3
91 bool read_id3;
92 #endif
95 static struct preferences prefs, old_prefs;
96 static unsigned char *lrc_buffer;
97 static size_t lrc_buffer_size;
98 static size_t lrc_buffer_used, lrc_buffer_end;
99 enum extention_types {LRC, LRC8, SNC, TXT, NUM_TYPES, ID3_SYLT, ID3_USLT};
100 static const char *extentions[NUM_TYPES] = {
101 ".lrc", ".lrc8", ".snc", ".txt",
103 static struct lrc_info {
104 struct mp3entry *id3;
105 long elapsed;
106 long length;
107 long ff_rewind;
108 int audio_status;
109 char mp3_file[MAX_PATH];
110 char lrc_file[MAX_PATH];
111 char *title; /* use lrc_buffer */
112 char *artist; /* use lrc_buffer */
113 enum extention_types type;
114 long offset; /* msec */
115 off_t offset_file_offset; /* offset of offset tag in file */
116 int nlrcbrpos;
117 int nlrcline;
118 struct lrc_line *ll_head, **ll_tail;
119 bool found_lrc;
120 bool loaded_lrc;
121 bool changed_lrc;
122 bool too_many_lines; /* true if nlrcline >= max_lrclines after calc pos */
123 #ifdef HAVE_LCD_BITMAP
124 bool wipe; /* false if lyrics is unsynched */
125 #endif
126 } current;
127 static char temp_buf[MAX(MAX_LINE_LEN,MAX_PATH)];
128 #ifdef HAVE_LCD_BITMAP
129 static int font_ui_height = 1;
130 static struct viewport vp_info[NB_SCREENS];
131 #endif
132 static struct viewport vp_lyrics[NB_SCREENS];
134 #define AUDIO_PAUSE (current.audio_status & AUDIO_STATUS_PAUSE)
135 #define AUDIO_PLAY (current.audio_status & AUDIO_STATUS_PLAY)
136 #define AUDIO_STOP (!(current.audio_status & AUDIO_STATUS_PLAY))
138 /*******************************
139 * lrc_set_time
140 *******************************/
141 #define LST_SET_MSEC 0x00010000
142 #define LST_SET_SEC 0x00020000
143 #define LST_SET_MIN 0x00040000
144 #define LST_SET_HOUR 0x00080000
146 #include "lib/pluginlib_actions.h"
147 #define LST_SET_TIME (LST_SET_MSEC|LST_SET_SEC|LST_SET_MIN|LST_SET_HOUR)
148 #ifdef HAVE_LCD_CHARCELLS
149 #define LST_OFF_Y 0
150 #else /* HAVE_LCD_BITMAP */
151 #define LST_OFF_Y 1
152 #endif
153 int lrc_set_time(const char *title, const char *unit, long *pval,
154 int step, int min, int max, int flags)
156 const struct button_mapping *lst_contexts[] = {
157 pla_main_ctx,
158 #ifdef HAVE_REMOTE_LCD
159 pla_remote_ctx,
160 #endif
162 /* how many */
163 const unsigned char formats[4][8] = {"%03ld.", "%02ld.", "%02ld:", "%02ld:"};
164 const unsigned int maxs[4] = {1000, 60, 60, 24};
165 const unsigned int scls[4] = {1, 1000, 60*1000, 60*60*1000};
166 char buffer[32];
167 long value = *pval, scl_step = step, i = 0;
168 int pos = 0, last_pos = 0, pos_min = 3, pos_max = 0;
169 int x = 0, y = 0, p_start = 0, p_end = 0;
170 int ret = 10;
172 if (!(flags&LST_SET_TIME))
173 return -1;
175 for (i = 0; i < 4; i++)
177 if (flags&(LST_SET_MSEC<<i))
179 if (pos_min > i) pos_min = i;
180 if (pos_max < i) pos_max = i;
183 pos = pos_min;
185 rb->button_clear_queue();
186 rb->lcd_clear_display();
187 rb->lcd_puts_scroll(0, LST_OFF_Y, title);
188 while (ret == 10)
190 int len = 0;
191 long abs_val = value;
192 long segvals[4] = {-1, -1, -1, -1};
193 /* show negative value like -00:01 but 00:-1 */
194 if (value < 0)
196 buffer[len++] = '-';
197 abs_val = -value;
199 buffer[len] = 0;
200 /* calc value of each segments */
201 for (i = pos_min; i <= pos_max; i++)
203 segvals[i] = abs_val % maxs[i];
204 abs_val /= maxs[i];
206 segvals[i-1] += abs_val * maxs[i-1];
207 for (i = pos_max; i >= pos_min; i--)
209 if (pos == i)
211 rb->lcd_getstringsize(buffer, &x, &y);
212 p_start = len;
214 rb->snprintf(&buffer[len], 32-len, formats[i], segvals[i]);
215 len += rb->strlen(&buffer[len]);
216 if (pos == i)
217 p_end = len;
219 buffer[len-1] = 0; /* remove last separater */
220 if (unit != NULL)
222 rb->snprintf(&buffer[len], 32-len, " (%s)", unit);
224 rb->lcd_puts(0, LST_OFF_Y+1, buffer);
225 if (pos_min != pos_max)
227 /* draw cursor */
228 buffer[p_end-1] = 0;
229 #ifdef HAVE_LCD_BITMAP
230 rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
231 rb->lcd_putsxy(x, y*(1+LST_OFF_Y), &buffer[p_start]);
232 rb->lcd_set_drawmode(DRMODE_SOLID);
233 #else
234 rb->lcd_put_cursor(x+rb->utf8length(&buffer[p_start])-1, y, 0x7F);
235 #endif
237 rb->lcd_update();
238 int button = pluginlib_getaction(TIMEOUT_BLOCK, lst_contexts, ARRAYLEN(lst_contexts));
239 int mult = 1;
240 #ifdef HAVE_LCD_CHARCELLS
241 if (pos_min != pos_max)
242 rb->lcd_remove_cursor();
243 #endif
244 switch (button)
246 case PLA_UP_REPEAT:
247 case PLA_DOWN_REPEAT:
248 mult *= 10;
249 case PLA_DOWN:
250 case PLA_UP:
251 if (button == PLA_DOWN_REPEAT || button == PLA_DOWN)
252 mult *= -1;
253 if (pos != last_pos)
255 scl_step = ((scls[pos]/scls[pos_min]+step-1)/step) * step;
256 last_pos = pos;
258 value += scl_step * mult;
259 if (value > max)
260 value = max;
261 if (value < min)
262 value = min;
263 break;
264 case PLA_LEFT:
265 case PLA_LEFT_REPEAT:
266 if (++pos > pos_max)
267 pos = pos_min;
268 break;
269 case PLA_RIGHT:
270 case PLA_RIGHT_REPEAT:
271 if (--pos < pos_min)
272 pos = pos_max;
273 break;
274 case PLA_SELECT:
275 *pval = value;
276 ret = 0;
277 break;
278 case PLA_CANCEL:
279 case PLA_EXIT:
280 rb->splash(HZ, "Cancelled");
281 ret = -1;
282 break;
283 default:
284 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
285 ret = 1;
286 break;
289 rb->lcd_clear_display();
290 rb->lcd_update();
291 return ret;
294 /*******************************
295 * misc stuff
296 *******************************/
297 static void reset_current_data(void)
299 current.title = NULL;
300 current.artist = NULL;
301 current.offset = 0;
302 current.offset_file_offset = -1;
303 current.nlrcbrpos = 0;
304 current.nlrcline = 0;
305 current.ll_head = NULL;
306 current.ll_tail = &current.ll_head;
307 current.loaded_lrc = false;
308 current.changed_lrc = false;
309 current.too_many_lines = false;
310 lrc_buffer_used = 0;
311 lrc_buffer_end = lrc_buffer_size;
314 /* check space and add str to lrc_buffer.
315 * return NULL if there is not enough buffer. */
316 static char *lrcbufadd(const char*str, bool join)
318 if (join) lrc_buffer_used--;
319 size_t siz = rb->strlen(str)+1;
320 char *pos = &lrc_buffer[lrc_buffer_used];
321 if (lrc_buffer_used + siz > lrc_buffer_end)
322 return NULL;
323 rb->strcpy(pos, str);
324 lrc_buffer_used += siz;
325 return pos;
327 static void *alloc_buf(size_t siz)
329 siz = (siz+3) & ~3;
330 if (lrc_buffer_used + siz > lrc_buffer_end)
331 return NULL;
332 lrc_buffer_end -= siz;
333 return &lrc_buffer[lrc_buffer_end];
335 static void *new_lrc_word(long time_start, char *word, bool join)
337 struct lrc_word *lrc_word;
338 if ((lrc_word = alloc_buf(sizeof(struct lrc_word))) == NULL)
339 return NULL;
340 if ((lrc_word->word = lrcbufadd(word, join)) == NULL)
341 return NULL;
342 lrc_word->time_start = time_start;
343 return lrc_word;
345 static bool add_lrc_line(struct lrc_line *lrc_line, char *word)
347 lrc_line->nword = 0;
348 lrc_line->next = NULL;
349 lrc_line->words = NULL;
350 if (word)
352 if ((lrc_line->words = new_lrc_word(-1, word, false)) == NULL)
353 return false;
354 lrc_line->nword++;
356 *current.ll_tail = lrc_line;
357 current.ll_tail = &(lrc_line->next);
358 current.nlrcline++;
359 return true;
361 static struct lrc_line *get_lrc_line(int idx)
363 static struct lrc_line *lrc_line = NULL;
364 static int n = 0;
365 if (idx < n)
367 lrc_line = current.ll_head;
368 n = 0;
370 while (n < idx && lrc_line)
372 lrc_line = lrc_line->next;
373 n++;
375 return lrc_line;
377 static char *get_lrc_str(struct lrc_line *lrc_line)
379 return lrc_line->words[lrc_line->nword-1].word;
381 static long get_time_start(struct lrc_line *lrc_line)
383 if (!lrc_line) return current.length+20;
384 long time = lrc_line->time_start + current.offset;
385 return time < 0? 0: time;
387 static void set_time_start(struct lrc_line *lrc_line, long time_start)
389 time_start -= current.offset;
390 time_start -= time_start%10;
391 if (lrc_line->time_start != time_start)
393 lrc_line->time_start = time_start;
394 current.changed_lrc = true;
397 #define get_word_time_start(x) get_time_start((struct lrc_line *)(x))
398 #define set_word_time_start(x, t) set_time_start((struct lrc_line *)(x), (t))
400 static int format_time_tag(char *buf, long t)
402 return rb->snprintf(buf, 16, "%02ld:%02ld.%02ld",
403 t/60000, (t/1000)%60, (t/10)%100);
405 /* find start of next line */
406 static const char *lrc_skip_space(const char *str)
408 #ifdef HAVE_LCD_BITMAP
409 if (prefs.wrap)
411 while (*str && *str != '\n' && isspace(*str))
412 str++;
414 #endif
415 if (*str == '\n')
416 str++;
417 return str;
420 /* calculate how many lines is needed to display and store it.
421 * create cache if there is enough space in lrc_buffer. */
422 static struct lrc_brpos *calc_brpos(struct lrc_line *lrc_line, int i)
424 struct lrc_brpos *lrc_brpos;
425 struct lrc_word *lrc_word;
426 int nlrcbrpos = 0, max_lrcbrpos;
427 #ifdef HAVE_LCD_BITMAP
428 struct font* pf = rb->font_get(FONT_UI);
429 unsigned short ch;
430 #endif
431 struct snap {
432 int count, width;
433 int nword;
434 int word_count, word_width;
435 const unsigned char *str;
436 } sp, cr;
438 lrc_buffer_used = (lrc_buffer_used+3)&~3; /* 4 bytes aligned */
439 lrc_brpos = (struct lrc_brpos *) &lrc_buffer[lrc_buffer_used];
440 max_lrcbrpos = (lrc_buffer_end-lrc_buffer_used) / sizeof(struct lrc_brpos);
442 if (!lrc_line)
444 /* calc info for all lrcs and store them if possible */
445 size_t buffer_used = lrc_buffer_used;
446 bool too_many_lines = false;
447 current.too_many_lines = true;
448 for (lrc_line = current.ll_head; lrc_line; lrc_line = lrc_line->next)
450 FOR_NB_SCREENS(i)
452 lrc_brpos = calc_brpos(lrc_line, i);
453 if (!too_many_lines)
455 lrc_buffer_used += lrc_line->nline[i]*sizeof(struct lrc_brpos);
456 if (nlrcbrpos + lrc_line->nline[i] >= max_lrcbrpos)
458 too_many_lines = true;
459 lrc_buffer_used = buffer_used;
460 calc_brpos(lrc_line, i);
463 nlrcbrpos += lrc_line->nline[i];
466 current.too_many_lines = too_many_lines;
467 lrc_buffer_used = buffer_used;
468 current.nlrcbrpos = nlrcbrpos;
469 return NULL;
472 if (!current.too_many_lines)
474 /* use stored infos. */
475 struct lrc_line *temp_lrc = current.ll_head;
476 for (; temp_lrc != lrc_line; temp_lrc = temp_lrc->next)
478 lrc_brpos += temp_lrc->nline[SCREEN_MAIN];
479 #ifdef HAVE_REMOTE_LCD
480 lrc_brpos += temp_lrc->nline[SCREEN_REMOTE];
481 #endif
483 #if NB_SCREENS >= 2
484 while (i)
485 lrc_brpos += lrc_line->nline[--i];
486 #endif
487 return lrc_brpos;
490 /* calculate number of lines, line width and char count for each line. */
491 lrc_line->width = 0;
492 cr.nword = lrc_line->nword;
493 lrc_word = lrc_line->words+cr.nword;
494 cr.str = (lrc_word-1)->word;
495 sp.word_count = 0;
496 sp.word_width = 0;
497 sp.nword = 0;
498 sp.count = 0;
499 sp.width = 0;
500 do {
501 cr.count = 0;
502 cr.width = 0;
503 sp.str = NULL;
505 while (1)
507 while(cr.nword > 0 && cr.str >= (lrc_word-1)->word)
509 cr.nword--;
510 lrc_word--;
511 lrc_word->count = 0;
512 lrc_word->width = 0;
514 if (*cr.str == 0 || *cr.str == '\n')
515 break;
517 int c, w;
518 #ifdef HAVE_LCD_CHARCELLS
519 c = rb->utf8seek(cr.str, 1);
520 w = 1;
521 #else
522 c = ((long)rb->utf8decode(cr.str, &ch) - (long)cr.str);
523 w = rb->font_get_width(pf, ch);
524 if (cr.count && isspace(*cr.str))
526 /* remember position of last space */
527 rb->memcpy(&sp, &cr, sizeof(struct snap));
528 sp.word_count = lrc_word->count;
529 sp.word_width = lrc_word->width;
531 if (cr.count && cr.width+w > vp_lyrics[i].width)
533 if (prefs.wrap && sp.str != NULL) /* wrap */
535 rb->memcpy(&cr, &sp, sizeof(struct snap));
536 lrc_word = lrc_line->words+cr.nword;
537 lrc_word->count = sp.word_count;
538 lrc_word->width = sp.word_width;
540 break;
542 #endif
543 cr.count += c;
544 cr.width += w;
545 lrc_word->count += c;
546 lrc_word->width += w;
547 cr.str += c;
549 lrc_line->width += cr.width;
550 lrc_brpos->count = cr.count;
551 lrc_brpos->width = cr.width;
552 nlrcbrpos++;
553 lrc_brpos++;
554 cr.str = lrc_skip_space(cr.str);
555 } while (*cr.str && nlrcbrpos < max_lrcbrpos);
556 lrc_line->nline[i] = nlrcbrpos;
558 while (cr.nword > 0)
560 cr.nword--;
561 lrc_word--;
562 lrc_word->count = 0;
563 lrc_word->width = 0;
565 return lrc_brpos-nlrcbrpos;
568 /* sort lyrics by time using stable sort. */
569 static void sort_lrcs(void)
571 struct lrc_line *p = current.ll_head, **q = NULL, *t;
572 long time_max = 0;
574 current.ll_head = NULL;
575 current.ll_tail = &current.ll_head;
576 while (p != NULL)
578 t = p->next;
579 /* remove problematic lrc_lines.
580 * it would cause problem in display_lrc_line() if nword is 0. */
581 if (p->nword)
583 q = p->time_start >= time_max? current.ll_tail: &current.ll_head;
584 while ((*q) && (*q)->time_start <= p->time_start)
585 q = &((*q)->next);
586 p->next = *q;
587 *q = p;
588 if (!p->next)
590 time_max = p->time_start;
591 current.ll_tail = &p->next;
594 p = t;
596 if (!current.too_many_lines)
597 calc_brpos(NULL, 0); /* stored data depends on order of lrcs if exist */
599 static void init_time_tag(void)
601 struct lrc_line *lrc_line = current.ll_head;
602 int nline = 0;
603 if (current.type == TXT || current.type == ID3_USLT)
605 /* set time tag according to length of audio and total line count
606 * for not synched lyrics, so that scroll speed is almost constant. */
607 for (; lrc_line; lrc_line = lrc_line->next)
609 lrc_line->time_start = nline * current.length / current.nlrcbrpos;
610 lrc_line->time_start -= lrc_line->time_start%10;
611 lrc_line->old_time_start = -1;
612 nline += lrc_line->nline[SCREEN_MAIN];
613 #ifdef HAVE_REMOTE_LCD
614 nline += lrc_line->nline[SCREEN_REMOTE];
615 #endif
618 else
620 /* reset timetags to the value read from file */
621 for (; lrc_line; lrc_line = lrc_line->next)
623 lrc_line->time_start = lrc_line->old_time_start;
625 sort_lrcs();
627 current.changed_lrc = false;
630 /*******************************
631 * Serch lrc file.
632 *******************************/
634 /* search in same or parent directries of playing file.
635 * assume playing file is /aaa/bbb/ccc/ddd.mp3,
636 * this function searchs lrc file following order.
637 * /aaa/bbb/ccc/ddd.lrc
638 * /aaa/bbb/ddd.lrc
639 * /aaa/ddd.lrc
640 * /ddd.lrc
643 /* taken from apps/recorder/albumart.c */
644 static void fix_filename(char* name)
646 static const char invalid_chars[] = "*/:<>?\\|";
648 while (1)
650 if (*name == 0)
651 return;
652 if (*name == '"')
653 *name = '\'';
654 else if (rb->strchr(invalid_chars, *name))
655 *name = '_';
656 name++;
659 static bool find_lrc_file_helper(const char *base_dir)
661 char fname[MAX_PATH];
662 char *names[3] = {NULL, NULL, NULL};
663 char *p, *dir;
664 int i, len;
665 /* /aaa/bbb/ccc/ddd.mp3
666 * dir <--q names[0]
669 /* assuming file name starts with '/' */
670 rb->strcpy(temp_buf, current.mp3_file);
671 /* get file name and remove extension */
672 names[0] = rb->strrchr(temp_buf, '/')+1;
673 if ((p = rb->strrchr(names[0], '.')) != NULL)
674 *p = 0;
675 if (current.id3->title && rb->strcmp(names[0], current.id3->title))
677 rb->strlcpy(fname, current.id3->title, sizeof(fname));
678 fix_filename(fname);
679 names[1] = fname;
682 dir = temp_buf;
683 p = names[0]-1;
684 do {
685 int n;
686 *p = 0;
687 for (n = 0; ; n++)
689 if (n == 0)
691 len = rb->snprintf(current.lrc_file, MAX_PATH, "%s%s/",
692 base_dir, dir);
694 else if (n == 1)
696 /* check file in subfolder named prefs.lrc_directory
697 * in the directory of mp3 file. */
698 if (prefs.lrc_directory[0] == '/')
700 len = rb->snprintf(current.lrc_file, MAX_PATH, "%s%s/",
701 dir, prefs.lrc_directory);
703 else
704 continue;
706 else
707 break;
708 DEBUGF("check file in %s\n", current.lrc_file);
709 if (!rb->dir_exists(current.lrc_file))
710 continue;
711 for (current.type = 0; current.type < NUM_TYPES; current.type++)
713 for (i = 0; names[i] != NULL; i++)
715 rb->snprintf(&current.lrc_file[len], MAX_PATH-len,
716 "%s%s", names[i], extentions[current.type]);
717 if (rb->file_exists(current.lrc_file))
719 DEBUGF("found: `%s'\n", current.lrc_file);
720 return true;
725 } while ((p = rb->strrchr(dir, '/')) != NULL);
726 return false;
729 /* return true if a lrc file is found */
730 static bool find_lrc_file(void)
732 reset_current_data();
734 DEBUGF("find lrc file for `%s'\n", current.mp3_file);
735 /* find .lrc file */
736 if (find_lrc_file_helper(""))
737 return true;
738 if (prefs.lrc_directory[0] == '/' && rb->dir_exists(prefs.lrc_directory))
740 if (find_lrc_file_helper(prefs.lrc_directory))
741 return true;
744 current.lrc_file[0] = 0;
745 return false;
748 /*******************************
749 * Load file.
750 *******************************/
752 /* check tag format and calculate value of the tag.
753 * supported tag: ti, ar, offset
754 * supported format of time tag: [mm:ss], [mm:ss.xx], [mm:ss.xxx]
755 * returns value of timega if tag is time tag, -1 if tag is supported tag,
756 * -10 otherwise.
758 static char *parse_int(char *ptr, int *val)
760 *val = rb->atoi(ptr);
761 while (isdigit(*ptr)) ptr++;
762 return ptr;
764 static long get_time_value(char *tag, bool read_id_tags, off_t file_offset)
766 long time;
767 char *ptr;
768 int val;
770 if (read_id_tags)
772 if (!rb->strncmp(tag, "ti:", 3))
774 if (!current.id3->title || rb->strcmp(&tag[3], current.id3->title))
775 current.title = lrcbufadd(&tag[3], false);
776 return -1;
778 if (!rb->strncmp(tag, "ar:", 3))
780 if (!current.id3->artist || rb->strcmp(&tag[3], current.id3->artist))
781 current.artist = lrcbufadd(&tag[3], false);
782 return -1;
784 if (!rb->strncmp(tag, "offset:", 7))
786 current.offset = rb->atoi(&tag[7]);
787 current.offset_file_offset = file_offset;
788 return -1;
792 /* minute */
793 ptr = parse_int(tag, &val);
794 if (ptr-tag < 1 || ptr-tag > 2 || *ptr != ':')
795 return -10;
796 time = val * 60000;
797 /* second */
798 tag = ptr+1;
799 ptr = parse_int(tag, &val);
800 if (ptr-tag != 2 || (*ptr != '.' && *ptr != ':' && *ptr != '\0'))
801 return -10;
802 time += val * 1000;
804 if (*ptr != '\0')
806 /* milliseccond */
807 tag = ptr+1;
808 ptr = parse_int(tag, &val);
809 if (ptr-tag < 2 || ptr-tag > 3 || *ptr != '\0')
810 return -10;
811 time += ((ptr-tag)==3 ?val: val*10);
814 return time;
817 /* format:
818 * [time tag]line
819 * [time tag]...[time tag]line
820 * [time tag]<word time tag>word<word time tag>...<word time tag>
822 static bool parse_lrc_line(char *line, off_t file_offset)
824 struct lrc_line *lrc_line = NULL, *first_lrc_line = NULL;
825 long time, time_start;
826 char *str, *tagstart, *tagend;
827 struct lrc_word *lrc_word;
828 int nword = 0;
830 /* parse [time tag]...[time tag] type tags */
831 str = line;
832 while (1)
834 if (*str != '[') break;
835 tagend = rb->strchr(str, ']');
836 if (tagend == NULL) break;
837 *tagend = 0;
838 time = get_time_value(str+1, !lrc_line, file_offset);
839 *tagend++ = ']';
840 if (time < 0)
841 break;
842 lrc_line = alloc_buf(sizeof(struct lrc_line));
843 if (lrc_line == NULL)
844 return false;
845 if (!first_lrc_line)
846 first_lrc_line = lrc_line;
847 lrc_line->file_offset = file_offset;
848 lrc_line->time_start = (time/10)*10;
849 lrc_line->old_time_start = lrc_line->time_start;
850 add_lrc_line(lrc_line, NULL);
851 file_offset += (long)tagend - (long)str;
852 str = tagend;
854 if (!first_lrc_line)
855 return true; /* no time tag in line */
857 lrc_line = first_lrc_line;
858 if (lrcbufadd("", false) == NULL)
859 return false;
861 /* parse <word time tag>...<word time tag> type tags */
862 /* [time tag]...[time tag]line type tags share lrc_line->words and can't
863 * use lrc_line->words->timestart. use lrc_line->time_start instead. */
864 time_start = -1;
865 tagstart = str;
866 while (*tagstart)
868 tagstart = rb->strchr(tagstart, '<');
869 if (!tagstart) break;
870 tagend = rb->strchr(tagstart, '>');
871 if (!tagend) break;
872 *tagend = 0;
873 time = get_time_value(tagstart+1, false,
874 file_offset + ((long)tagstart - (long)str));
875 *tagend++ = '>';
876 if (time < 0)
878 tagstart++;
879 continue;
881 *tagstart = 0;
882 /* found word time tag. */
883 if (*str || time_start != -1)
885 if ((lrc_word = new_lrc_word(time_start, str, true)) == NULL)
886 return false;
887 nword++;
889 file_offset += (long)tagend - (long)str;
890 tagstart = str = tagend;
891 time_start = time;
893 if ((lrc_word = new_lrc_word(time_start, str, true)) == NULL)
894 return false;
895 nword++;
897 /* duplicate lrc_lines */
898 while (lrc_line)
900 lrc_line->nword = nword;
901 lrc_line->words = lrc_word;
902 lrc_line = lrc_line->next;
905 return true;
908 /* format:
909 * \xa2\xe2hhmmssxx\xa2\xd0
910 * line 1
911 * line 2
912 * \xa2\xe2hhmmssxx\xa2\xd0
913 * line 3
914 * ...
916 static bool parse_snc_line(char *line, off_t file_offset)
918 #define SNC_TAG_START "\xa2\xe2"
919 #define SNC_TAG_END "\xa2\xd0"
921 /* SNC_TAG can be dencoded, so use
922 * temp_buf which contains native data */
923 if (!rb->memcmp(temp_buf, SNC_TAG_START, 2)
924 && !rb->memcmp(temp_buf+10, SNC_TAG_END, 2)) /* time tag */
926 const char *pos = temp_buf+2; /* skip SNC_TAG_START */
927 int hh, mm, ss, xx;
929 hh = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
930 mm = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
931 ss = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
932 xx = (pos[0]-'0')*10+(pos[1]-'0'); pos += 2;
933 pos += 2; /* skip SNC_TAG_END */
935 /* initialize */
936 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line));
937 if (lrc_line == NULL)
938 return false;
939 lrc_line->file_offset = file_offset+2;
940 lrc_line->time_start = hh*3600000+mm*60000+ss*1000+xx*10;
941 lrc_line->old_time_start = lrc_line->time_start;
942 if (!add_lrc_line(lrc_line, ""))
943 return false;
944 if (pos[0]==0)
945 return true;
947 /* encode rest of line and add to buffer */
948 rb->iso_decode(pos, line, prefs.encoding, rb->strlen(pos)+1);
950 if (current.ll_head)
952 rb->strcat(line, "\n");
953 if (lrcbufadd(line, true) == NULL)
954 return false;
956 return true;
959 static bool parse_txt_line(char *line, off_t file_offset)
961 /* initialize */
962 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line));
963 if (lrc_line == NULL)
964 return false;
965 lrc_line->file_offset = file_offset;
966 lrc_line->time_start = 0;
967 lrc_line->old_time_start = -1;
968 if (!add_lrc_line(lrc_line, line))
969 return false;
970 return true;
973 static void load_lrc_file(void)
975 char utf8line[MAX_LINE_LEN*3];
976 int fd;
977 int encoding = prefs.encoding;
978 bool (*line_parser)(char *line, off_t) = NULL;
979 off_t file_offset, readsize;
981 switch(current.type)
983 case LRC8:
984 encoding = UTF_8; /* .lrc8 is utf8 */
985 /* fall through */
986 case LRC:
987 line_parser = parse_lrc_line;
988 break;
989 case SNC:
990 line_parser = parse_snc_line;
991 break;
992 case TXT:
993 line_parser = parse_txt_line;
994 break;
995 default:
996 return;
999 fd = rb->open(current.lrc_file, O_RDONLY);
1000 if (fd < 0) return;
1003 /* check encoding */
1004 #define BOM "\xef\xbb\xbf"
1005 #define BOM_SIZE 3
1006 unsigned char header[BOM_SIZE];
1007 unsigned char* (*utf_decode)(const unsigned char *,
1008 unsigned char *, int) = NULL;
1009 rb->read(fd, header, BOM_SIZE);
1010 if (!rb->memcmp(header, BOM, BOM_SIZE)) /* UTF-8 */
1012 encoding = UTF_8;
1014 else if (!rb->memcmp(header, "\xff\xfe", 2)) /* UTF-16LE */
1016 utf_decode = rb->utf16LEdecode;
1018 else if (!rb->memcmp(header, "\xfe\xff", 2)) /* UTF-16BE */
1020 utf_decode = rb->utf16BEdecode;
1022 else
1024 rb->lseek(fd, 0, SEEK_SET);
1027 if (utf_decode)
1029 /* convert encoding of file from UTF-16 to UTF-8 */
1030 char temp_file[MAX_PATH];
1031 int fe;
1032 rb->lseek(fd, 2, SEEK_SET);
1033 rb->snprintf(temp_file, MAX_PATH, "%s~", current.lrc_file);
1034 fe = rb->creat(temp_file, 0666);
1035 if (fe < 0)
1037 rb->close(fd);
1038 return;
1040 rb->write(fe, BOM, BOM_SIZE);
1041 while ((readsize = rb->read(fd, temp_buf, MAX_LINE_LEN)) > 0)
1043 char *end = utf_decode(temp_buf, utf8line, readsize/2);
1044 rb->write(fe, utf8line, end-utf8line);
1046 rb->close(fe);
1047 rb->close(fd);
1048 rb->remove(current.lrc_file);
1049 rb->rename(temp_file, current.lrc_file);
1050 fd = rb->open(current.lrc_file, O_RDONLY);
1051 if (fd < 0) return;
1052 rb->lseek(fd, BOM_SIZE, SEEK_SET); /* skip bom */
1053 encoding = UTF_8;
1057 file_offset = rb->lseek(fd, 0, SEEK_CUR); /* used in line_parser */
1058 while ((readsize = rb->read_line(fd, temp_buf, MAX_LINE_LEN)) > 0)
1060 /* note: parse_snc_line() reads temp_buf for native data. */
1061 rb->iso_decode(temp_buf, utf8line, encoding, readsize+1);
1062 if (!line_parser(utf8line, file_offset))
1063 break;
1064 file_offset += readsize;
1066 rb->close(fd);
1068 current.loaded_lrc = true;
1069 calc_brpos(NULL, 0);
1070 init_time_tag();
1072 return;
1075 #ifdef LRC_SUPPORT_ID3
1076 /*******************************
1077 * read lyrics from id3
1078 *******************************/
1079 /* taken from apps/metadata/mp3.c */
1080 static unsigned long unsync(unsigned long b0, unsigned long b1,
1081 unsigned long b2, unsigned long b3)
1083 return (((long)(b0 & 0x7F) << (3*7)) |
1084 ((long)(b1 & 0x7F) << (2*7)) |
1085 ((long)(b2 & 0x7F) << (1*7)) |
1086 ((long)(b3 & 0x7F) << (0*7)));
1089 static unsigned long bytes2int(unsigned long b0, unsigned long b1,
1090 unsigned long b2, unsigned long b3)
1092 return (((long)(b0 & 0xFF) << (3*8)) |
1093 ((long)(b1 & 0xFF) << (2*8)) |
1094 ((long)(b2 & 0xFF) << (1*8)) |
1095 ((long)(b3 & 0xFF) << (0*8)));
1098 static int unsynchronize(char* tag, int len, bool *ff_found)
1100 int i;
1101 unsigned char c;
1102 unsigned char *rp, *wp;
1103 bool _ff_found = false;
1104 if(ff_found) _ff_found = *ff_found;
1106 wp = rp = (unsigned char *)tag;
1108 rp = (unsigned char *)tag;
1109 for(i = 0; i<len; i++) {
1110 /* Read the next byte and write it back, but don't increment the
1111 write pointer */
1112 c = *rp++;
1113 *wp = c;
1114 if(_ff_found) {
1115 /* Increment the write pointer if it isn't an unsynch pattern */
1116 if(c != 0)
1117 wp++;
1118 _ff_found = false;
1119 } else {
1120 if(c == 0xff)
1121 _ff_found = true;
1122 wp++;
1125 if(ff_found) *ff_found = _ff_found;
1126 return (long)wp - (long)tag;
1129 static int read_unsynched(int fd, void *buf, int len, bool *ff_found)
1131 int i;
1132 int rc;
1133 int remaining = len;
1134 char *wp;
1136 wp = buf;
1138 while(remaining) {
1139 rc = rb->read(fd, wp, remaining);
1140 if(rc <= 0)
1141 return rc;
1143 i = unsynchronize(wp, remaining, ff_found);
1144 remaining -= i;
1145 wp += i;
1148 return len;
1151 static unsigned char* utf8cpy(const unsigned char *src,
1152 unsigned char *dst, int count)
1154 rb->strlcpy(dst, src, count+1);
1155 return dst+rb->strlen(dst);
1158 static void parse_id3v2(int fd)
1160 int minframesize;
1161 int size;
1162 long framelen;
1163 char header[10];
1164 char tmp[8];
1165 unsigned char version;
1166 int bytesread = 0;
1167 unsigned char global_flags;
1168 int flags;
1169 int skip;
1170 bool global_unsynch = false;
1171 bool global_ff_found = false;
1172 bool unsynch = false;
1173 int rc;
1174 enum {NOLT, SYLT, USLT} type = NOLT;
1176 /* Bail out if the tag is shorter than 10 bytes */
1177 if(current.id3->id3v2len < 10)
1178 return;
1180 /* Read the ID3 tag version from the header */
1181 if(10 != rb->read(fd, header, 10))
1182 return;
1184 /* Get the total ID3 tag size */
1185 size = current.id3->id3v2len - 10;
1187 version = current.id3->id3version;
1188 switch ( version )
1190 case ID3_VER_2_2:
1191 minframesize = 8;
1192 break;
1194 case ID3_VER_2_3:
1195 minframesize = 12;
1196 break;
1198 case ID3_VER_2_4:
1199 minframesize = 12;
1200 break;
1202 default:
1203 /* unsupported id3 version */
1204 return;
1207 global_flags = header[5];
1209 /* Skip the extended header if it is present */
1210 if(global_flags & 0x40) {
1211 skip = 0;
1213 if(version == ID3_VER_2_3) {
1214 if(10 != rb->read(fd, header, 10))
1215 return;
1216 /* The 2.3 extended header size doesn't include the header size
1217 field itself. Also, it is not unsynched. */
1218 framelen =
1219 bytes2int(header[0], header[1], header[2], header[3]) + 4;
1221 /* Skip the rest of the header */
1222 rb->lseek(fd, framelen - 10, SEEK_CUR);
1225 if(version >= ID3_VER_2_4) {
1226 if(4 != rb->read(fd, header, 4))
1227 return;
1229 /* The 2.4 extended header size does include the entire header,
1230 so here we can just skip it. This header is unsynched. */
1231 framelen = unsync(header[0], header[1],
1232 header[2], header[3]);
1234 rb->lseek(fd, framelen - 4, SEEK_CUR);
1238 /* Is unsynchronization applied? */
1239 if(global_flags & 0x80) {
1240 global_unsynch = true;
1243 /* We must have at least minframesize bytes left for the
1244 * remaining frames to be interesting */
1245 while (size >= minframesize) {
1246 flags = 0;
1248 /* Read frame header and check length */
1249 if(version >= ID3_VER_2_3) {
1250 if(global_unsynch && version <= ID3_VER_2_3)
1251 rc = read_unsynched(fd, header, 10, &global_ff_found);
1252 else
1253 rc = rb->read(fd, header, 10);
1254 if(rc != 10)
1255 return;
1256 /* Adjust for the 10 bytes we read */
1257 size -= 10;
1259 flags = bytes2int(0, 0, header[8], header[9]);
1261 if (version >= ID3_VER_2_4) {
1262 framelen = unsync(header[4], header[5],
1263 header[6], header[7]);
1264 } else {
1265 /* version .3 files don't use synchsafe ints for
1266 * size */
1267 framelen = bytes2int(header[4], header[5],
1268 header[6], header[7]);
1270 } else {
1271 if(6 != rb->read(fd, header, 6))
1272 return;
1273 /* Adjust for the 6 bytes we read */
1274 size -= 6;
1276 framelen = bytes2int(0, header[3], header[4], header[5]);
1279 if(framelen == 0){
1280 if (header[0] == 0 && header[1] == 0 && header[2] == 0)
1281 return;
1282 else
1283 continue;
1286 unsynch = false;
1288 if(flags)
1290 if (version >= ID3_VER_2_4) {
1291 if(flags & 0x0040) { /* Grouping identity */
1292 rb->lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */
1293 framelen--;
1295 } else {
1296 if(flags & 0x0020) { /* Grouping identity */
1297 rb->lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */
1298 framelen--;
1302 if(flags & 0x000c) /* Compression or encryption */
1304 /* Skip it */
1305 size -= framelen;
1306 rb->lseek(fd, framelen, SEEK_CUR);
1307 continue;
1310 if(flags & 0x0002) /* Unsynchronization */
1311 unsynch = true;
1313 if (version >= ID3_VER_2_4) {
1314 if(flags & 0x0001) { /* Data length indicator */
1315 if(4 != rb->read(fd, tmp, 4))
1316 return;
1318 /* We don't need the data length */
1319 framelen -= 4;
1324 if (framelen == 0)
1325 continue;
1327 if (framelen < 0)
1328 return;
1330 if(!rb->memcmp( header, "SLT", 3 ) ||
1331 !rb->memcmp( header, "SYLT", 4 ))
1333 /* found a supported tag */
1334 type = SYLT;
1335 break;
1337 else if(!rb->memcmp( header, "ULT", 3 ) ||
1338 !rb->memcmp( header, "USLT", 4 ))
1340 /* found a supported tag */
1341 type = USLT;
1342 break;
1344 else
1346 /* not a supported tag*/
1347 if(global_unsynch && version <= ID3_VER_2_3) {
1348 size -= read_unsynched(fd, lrc_buffer, framelen, &global_ff_found);
1349 } else {
1350 size -= framelen;
1351 if( rb->lseek(fd, framelen, SEEK_CUR) == -1 )
1352 return;
1356 if(type == NOLT)
1357 return;
1359 int encoding = 0, chsiz;
1360 char *tag, *p, utf8line[MAX_LINE_LEN*3];
1361 unsigned char* (*utf_decode)(const unsigned char *,
1362 unsigned char *, int) = NULL;
1363 /* use middle of lrc_buffer to store tag data. */
1364 if(framelen >= LRC_BUFFER_SIZE/3)
1365 framelen = LRC_BUFFER_SIZE/3-1;
1366 tag = lrc_buffer+LRC_BUFFER_SIZE*2/3-framelen-1;
1367 if(global_unsynch && version <= ID3_VER_2_3)
1368 bytesread = read_unsynched(fd, tag, framelen, &global_ff_found);
1369 else
1370 bytesread = rb->read(fd, tag, framelen);
1372 if( bytesread != framelen )
1373 return;
1375 if(unsynch || (global_unsynch && version >= ID3_VER_2_4))
1376 bytesread = unsynchronize(tag, bytesread, NULL);
1378 tag[bytesread] = 0;
1379 encoding = tag[0];
1380 p = tag;
1381 /* skip some data */
1382 if(type == SYLT) {
1383 p += 6;
1384 } else {
1385 p += 4;
1388 /* check encoding and skip content descriptor */
1389 switch (encoding) {
1390 case 0x01: /* Unicode with or without BOM */
1391 case 0x02:
1393 /* Now check if there is a BOM
1394 (zero-width non-breaking space, 0xfeff)
1395 and if it is in little or big endian format */
1396 if(!rb->memcmp(p, "\xff\xfe", 2)) { /* Little endian? */
1397 utf_decode = rb->utf16LEdecode;
1398 } else if(!rb->memcmp(p, "\xfe\xff", 2)) { /* Big endian? */
1399 utf_decode = rb->utf16BEdecode;
1400 } else
1401 utf_decode = NULL;
1403 encoding = NUM_CODEPAGES;
1404 do {
1405 size = p[0] | p[1];
1406 p += 2;
1407 } while(size);
1408 chsiz = 2;
1409 break;
1411 default:
1412 utf_decode = utf8cpy;
1413 if(encoding == 0x03) /* UTF-8 encoded string */
1414 encoding = UTF_8;
1415 else
1416 encoding = prefs.encoding;
1417 p += rb->strlen(p)+1;
1418 chsiz = 1;
1419 break;
1421 if(encoding == NUM_CODEPAGES)
1423 /* check if there is a BOM */
1424 if(!rb->memcmp(p, "\xff\xfe", 2)) { /* Little endian? */
1425 utf_decode = rb->utf16LEdecode;
1426 p += 2;
1427 } else if(!rb->memcmp(p, "\xfe\xff", 2)) { /* Big endian? */
1428 utf_decode = rb->utf16BEdecode;
1429 p += 2;
1430 } else if(!utf_decode) {
1431 /* If there is no BOM (which is a specification violation),
1432 let's try to guess it. If one of the bytes is 0x00, it is
1433 probably the most significant one. */
1434 if(p[1] == 0)
1435 utf_decode = rb->utf16LEdecode;
1436 else
1437 utf_decode = rb->utf16BEdecode;
1440 bytesread -= (long)p - (long)tag;
1441 tag = p;
1443 while ( bytesread > 0
1444 && lrc_buffer_used+bytesread < LRC_BUFFER_SIZE*2/3
1445 && LRC_BUFFER_SIZE*2/3 < lrc_buffer_end)
1447 bool is_crlf = false;
1448 struct lrc_line *lrc_line = alloc_buf(sizeof(struct lrc_line));
1449 if(!lrc_line)
1450 break;
1451 lrc_line->file_offset = -1;
1452 if(type == USLT)
1454 /* replace 0x0a and 0x0d with 0x00 */
1455 p = tag;
1456 while(1) {
1457 utf_decode(p, tmp, 2);
1458 if(!tmp[0]) break;
1459 if(tmp[0] == 0x0d || tmp[0] == 0x0a)
1461 if(tmp[0] == 0x0d && tmp[1] == 0x0a)
1462 is_crlf = true;
1463 p[0] = 0;
1464 p[chsiz-1] = 0;
1465 break;
1467 p += chsiz;
1470 if(encoding == NUM_CODEPAGES)
1472 unsigned char* utf8 = utf8line;
1473 p = tag;
1474 do {
1475 utf8 = utf_decode(p, utf8, 1);
1476 p += 2;
1477 } while(*(utf8-1));
1479 else
1481 size = rb->strlen(tag)+1;
1482 rb->iso_decode(tag, utf8line, encoding, size);
1483 p = tag+size;
1486 if(type == SYLT) { /* timestamp */
1487 lrc_line->time_start = bytes2int(p[0], p[1], p[2], p[3]);
1488 lrc_line->old_time_start = lrc_line->time_start;
1489 p += 4;
1490 utf_decode(p, tmp, 1);
1491 if(tmp[0] == 0x0a)
1492 p += chsiz;
1493 } else { /* USLT */
1494 lrc_line->time_start = 0;
1495 lrc_line->old_time_start = -1;
1496 if(is_crlf) p += chsiz;
1498 bytesread -= (long)p - (long)tag;
1499 tag = p;
1500 if(!add_lrc_line(lrc_line, utf8line))
1501 break;
1504 current.type = ID3_SYLT-SYLT+type;
1505 rb->strcpy(current.lrc_file, current.mp3_file);
1507 current.loaded_lrc = true;
1508 calc_brpos(NULL, 0);
1509 init_time_tag();
1511 return;
1514 static bool read_id3(void)
1516 int fd;
1518 if(current.id3->codectype != AFMT_MPA_L1
1519 && current.id3->codectype != AFMT_MPA_L2
1520 && current.id3->codectype != AFMT_MPA_L3)
1521 return false;
1523 fd = rb->open(current.mp3_file, O_RDONLY);
1524 if(fd < 0) return false;
1525 current.loaded_lrc = false;
1526 parse_id3v2(fd);
1527 rb->close(fd);
1528 return current.loaded_lrc;
1530 #endif /* LRC_SUPPORT_ID3 */
1532 /*******************************
1533 * Display information
1534 *******************************/
1535 static void display_state(void)
1537 const char *str = NULL;
1539 if (AUDIO_STOP)
1540 str = "Audio Stopped";
1541 else if (current.found_lrc)
1543 if (!current.loaded_lrc)
1544 str = "Loading lrc";
1545 else if (!current.ll_head)
1546 str = "No lyrics";
1549 #ifdef HAVE_LCD_BITMAP
1550 const char *info = NULL;
1552 if (AUDIO_PLAY && prefs.display_title)
1554 char *title = (current.title? current.title: current.id3->title);
1555 char *artist = (current.artist? current.artist: current.id3->artist);
1557 if (artist != NULL && title != NULL)
1559 rb->snprintf(temp_buf, MAX_LINE_LEN, "%s/%s", title, artist);
1560 info = temp_buf;
1562 else if (title != NULL)
1563 info = title;
1564 else if (current.mp3_file[0] == '/')
1565 info = rb->strrchr(current.mp3_file, '/')+1;
1566 else
1567 info = "(no info)";
1570 int i, w, h;
1571 struct screen* display;
1572 FOR_NB_SCREENS(i)
1574 display = rb->screens[i];
1575 display->set_viewport(&vp_info[i]);
1576 display->clear_viewport();
1577 if (info)
1578 display->puts_scroll(0, 0, info);
1579 if (str)
1581 display->set_viewport(&vp_lyrics[i]);
1582 display->clear_viewport();
1583 display->getstringsize(str, &w, &h);
1584 if (vp_lyrics[i].width - w < 0)
1585 display->puts_scroll(0, vp_lyrics[i].height/font_ui_height/2,
1586 str);
1587 else
1588 display->putsxy((vp_lyrics[i].width - w)*prefs.align/2,
1589 (vp_lyrics[i].height-font_ui_height)/2, str);
1590 display->set_viewport(&vp_info[i]);
1592 display->update_viewport();
1593 display->set_viewport(NULL);
1595 #else
1596 /* there is no place to display title or artist. */
1597 rb->lcd_clear_display();
1598 if (str)
1599 rb->lcd_puts_scroll(0, 0, str);
1600 rb->lcd_update();
1601 #endif /* HAVE_LCD_BITMAP */
1604 static void display_time(void)
1606 rb->snprintf(temp_buf, MAX_LINE_LEN, "%ld:%02ld/%ld:%02ld",
1607 current.elapsed/60000, (current.elapsed/1000)%60,
1608 current.length/60000, (current.length)/1000%60);
1609 #ifdef HAVE_LCD_BITMAP
1610 int y = (prefs.display_title? font_ui_height:0), i;
1611 FOR_NB_SCREENS(i)
1613 struct screen* display = rb->screens[i];
1614 display->set_viewport(&vp_info[i]);
1615 display->setfont(FONT_SYSFIXED);
1616 display->putsxy(0, y, temp_buf);
1617 rb->gui_scrollbar_draw(display, 0, y+SYSFONT_HEIGHT+1,
1618 vp_info[i].width, SYSFONT_HEIGHT-2,
1619 current.length, 0, current.elapsed, HORIZONTAL);
1620 display->update_viewport_rect(0, y, vp_info[i].width, SYSFONT_HEIGHT*2);
1621 display->setfont(FONT_UI);
1622 display->set_viewport(NULL);
1624 #else
1625 rb->lcd_puts(0, 0, temp_buf);
1626 rb->lcd_update();
1627 #endif /* HAVE_LCD_BITMAP */
1630 /*******************************
1631 * Display lyrics
1632 *******************************/
1633 #ifdef HAVE_LCD_BITMAP
1634 static inline void set_to_default(struct screen *display)
1636 #if (LCD_DEPTH > 1)
1637 #ifdef HAVE_LCD_REMOTE
1638 if (display->screen_type != SCREEN_REMOTE)
1639 #endif
1640 display->set_foreground(prefs.active_color);
1641 #endif
1642 display->set_drawmode(DRMODE_SOLID);
1644 static inline void set_to_active(struct screen *display)
1646 #if (LCD_DEPTH > 1)
1647 #ifdef HAVE_LCD_REMOTE
1648 if (display->screen_type == SCREEN_REMOTE)
1649 display->set_drawmode(DRMODE_INVERSEVID);
1650 else
1651 #endif
1653 display->set_foreground(prefs.active_color);
1654 display->set_drawmode(DRMODE_SOLID);
1656 #else /* LCD_DEPTH == 1 */
1657 display->set_drawmode(DRMODE_INVERSEVID);
1658 #endif
1660 static inline void set_to_inactive(struct screen *display)
1662 #if (LCD_DEPTH > 1)
1663 #ifdef HAVE_LCD_REMOTE
1664 if (display->screen_type != SCREEN_REMOTE)
1665 #endif
1666 display->set_foreground(prefs.inactive_color);
1667 #endif
1668 display->set_drawmode(DRMODE_SOLID);
1671 static int display_lrc_line(struct lrc_line *lrc_line, int ypos, int i)
1673 struct screen *display = rb->screens[i];
1674 struct lrc_word *lrc_word;
1675 struct lrc_brpos *lrc_brpos;
1676 long time_start, time_end, elapsed;
1677 int count, width, nword;
1678 int xpos;
1679 const char *str;
1680 bool active_line;
1682 time_start = get_time_start(lrc_line);
1683 time_end = get_time_start(lrc_line->next);
1684 active_line = (time_start <= current.elapsed
1685 && time_end > current.elapsed);
1687 if (!lrc_line->width)
1689 /* empty line. draw bar during interval. */
1690 long rin = current.elapsed - time_start;
1691 long len = time_end - time_start;
1692 if (current.wipe && active_line && len >= 3500)
1694 elapsed = rin * vp_lyrics[i].width / len;
1695 set_to_active(display);
1696 display->drawrect(0, ypos+font_ui_height/4,
1697 vp_lyrics[i].width, font_ui_height/2);
1698 display->fillrect(1, ypos+font_ui_height/4+1,
1699 elapsed-1, font_ui_height/2-2);
1700 set_to_inactive(display);
1701 display->fillrect(elapsed, ypos+font_ui_height/4+1,
1702 vp_lyrics[i].width-elapsed-1, font_ui_height/2-2);
1703 set_to_default(display);
1705 return ypos + font_ui_height;
1708 lrc_brpos = calc_brpos(lrc_line, i);
1709 /* initialize line */
1710 xpos = (vp_lyrics[i].width - lrc_brpos->width)*prefs.align/2;
1711 count = 0;
1712 width = 0;
1714 active_line = active_line || !prefs.active_one_line;
1715 nword = lrc_line->nword-1;
1716 lrc_word = lrc_line->words + nword;
1717 str = lrc_word->word;
1718 /* only time_start of first word could be -1 */
1719 if (lrc_word->time_start != -1)
1720 time_end = get_word_time_start(lrc_word);
1721 else
1722 time_end = time_start;
1723 do {
1724 time_start = time_end;
1725 if (nword > 0)
1726 time_end = get_word_time_start(lrc_word-1);
1727 else
1728 time_end = get_time_start(lrc_line->next);
1730 if (time_start > current.elapsed || !active_line)
1732 /* inactive */
1733 elapsed = 0;
1735 else if (!current.wipe || time_end <= current.elapsed)
1737 /* active whole word */
1738 elapsed = lrc_word->width;
1740 else
1742 /* wipe word */
1743 long rin = current.elapsed - time_start;
1744 long len = time_end - time_start;
1745 elapsed = rin * lrc_word->width / len;
1748 int word_count = lrc_word->count;
1749 int word_width = lrc_word->width;
1750 set_to_active(display);
1751 while (word_count > 0 && word_width > 0)
1753 int c = lrc_brpos->count - count;
1754 int w = lrc_brpos->width - width;
1755 if (c > word_count || w > word_width)
1757 c = word_count;
1758 w = word_width;
1760 if (elapsed <= 0)
1762 set_to_inactive(display);
1764 else if (elapsed < w)
1766 /* wipe text */
1767 display->fillrect(xpos, ypos, elapsed, font_ui_height);
1768 set_to_inactive(display);
1769 display->fillrect(xpos+elapsed, ypos,
1770 w-elapsed, font_ui_height);
1771 #if (LCD_DEPTH > 1)
1772 #ifdef HAVE_LCD_REMOTE
1773 if (display->screen_type == SCREEN_REMOTE)
1774 display->set_drawmode(DRMODE_INVERSEVID);
1775 else
1776 #endif
1777 display->set_drawmode(DRMODE_BG);
1778 #else
1779 display->set_drawmode(DRMODE_INVERSEVID);
1780 #endif
1782 rb->strlcpy(temp_buf, str, c+1);
1783 display->putsxy(xpos, ypos, temp_buf);
1784 str += c;
1785 xpos += w;
1786 count += c;
1787 width += w;
1788 word_count -= c;
1789 word_width -= w;
1790 elapsed -= w;
1791 if (count >= lrc_brpos->count || width >= lrc_brpos->width)
1793 /* prepare for next line */
1794 lrc_brpos++;
1795 str = lrc_skip_space(str);
1796 xpos = (vp_lyrics[i].width - lrc_brpos->width)*prefs.align/2;
1797 ypos += font_ui_height;
1798 count = 0;
1799 width = 0;
1802 lrc_word--;
1803 } while (nword--);
1804 set_to_default(display);
1805 return ypos;
1807 #endif /* HAVE_LCD_BITMAP */
1809 static void display_lrcs(void)
1811 long time_start, time_end, rin, len;
1812 int i, nline[NB_SCREENS] = {0};
1813 struct lrc_line *lrc_center = current.ll_head;
1815 if (!lrc_center) return;
1817 while (get_time_start(lrc_center->next) <= current.elapsed)
1819 nline[SCREEN_MAIN] += lrc_center->nline[SCREEN_MAIN];
1820 #ifdef HAVE_REMOTE_LCD
1821 nline[SCREEN_REMOTE] += lrc_center->nline[SCREEN_REMOTE];
1822 #endif
1823 lrc_center = lrc_center->next;
1826 time_start = get_time_start(lrc_center);
1827 time_end = get_time_start(lrc_center->next);
1828 rin = current.elapsed - time_start;
1829 len = time_end - time_start;
1831 struct screen *display;
1832 FOR_NB_SCREENS(i)
1834 display = rb->screens[i];
1835 /* display current line at the center of the viewport */
1836 display->set_viewport(&vp_lyrics[i]);
1837 display->clear_viewport();
1838 #ifdef HAVE_LCD_BITMAP
1839 struct lrc_line *lrc_line;
1840 int y, ypos = 0, nblines = vp_lyrics[i].height/font_ui_height;
1841 y = (nblines-1)/2;
1842 if (rin < 0)
1844 /* current.elapsed < time of first lrc */
1845 if (!current.wipe)
1846 ypos = (time_start - current.elapsed)
1847 * font_ui_height / time_start;
1848 else
1849 y++;
1851 else if (len > 0)
1853 if (!current.wipe)
1854 ypos = - rin * lrc_center->nline[i] * font_ui_height / len;
1855 else
1857 long elapsed = rin * lrc_center->width / len;
1858 struct lrc_brpos *lrc_brpos = calc_brpos(lrc_center, i);
1859 while (elapsed > lrc_brpos->width)
1861 elapsed -= lrc_brpos->width;
1862 y--;
1863 lrc_brpos++;
1868 /* find first line to display */
1869 y -= nline[i];
1870 lrc_line = current.ll_head;
1871 while (y < -lrc_line->nline[i])
1873 y += lrc_line->nline[i];
1874 lrc_line = lrc_line->next;
1877 ypos += y*font_ui_height;
1878 while (lrc_line && ypos < vp_lyrics[i].height)
1880 ypos = display_lrc_line(lrc_line, ypos, i);
1881 lrc_line = lrc_line->next;
1883 if (!lrc_line && ypos < vp_lyrics[i].height)
1884 display->putsxy(0, ypos, "[end]");
1885 #else /* HAVE_LCD_CHARCELLS */
1886 struct lrc_line *lrc_line = lrc_center;
1887 struct lrc_brpos *lrc_brpos = calc_brpos(lrc_line, i);
1888 long elapsed = 0;
1889 const char *str = get_lrc_str(lrc_line);
1890 int x = vp_lyrics[i].width/2, y = 0;
1892 if (rin >= 0 && len > 0)
1894 elapsed = rin * lrc_center->width / len;
1895 while (elapsed > lrc_brpos->width)
1897 elapsed -= lrc_brpos->width;
1898 str = lrc_skip_space(str+lrc_brpos->count);
1899 lrc_brpos++;
1902 rb->strlcpy(temp_buf, str, lrc_brpos->count+1);
1904 x -= elapsed;
1905 if (x < 0)
1906 display->puts(0, y, temp_buf + rb->utf8seek(temp_buf, -x));
1907 else
1908 display->puts(x, y, temp_buf);
1909 x += rb->utf8length(temp_buf)+1;
1910 lrc_line = lrc_line->next;
1911 if (!lrc_line && x < vp_lyrics[i].width)
1913 if (x < vp_lyrics[i].width/2)
1914 x = vp_lyrics[i].width/2;
1915 display->puts(x, y, "[end]");
1917 #endif /* HAVE_LCD_BITMAP */
1918 display->update_viewport();
1919 display->set_viewport(NULL);
1923 /*******************************
1924 * Browse lyrics and edit time.
1925 *******************************/
1926 /* point playing line in lyrics */
1927 static enum themable_icons get_icon(int selected, void * data)
1929 (void) data;
1930 struct lrc_line *lrc_line = get_lrc_line(selected);
1931 if (lrc_line)
1933 long time_start = get_time_start(lrc_line);
1934 long time_end = get_time_start(lrc_line->next);
1935 long elapsed = current.id3->elapsed;
1936 if (time_start <= elapsed && time_end > elapsed)
1937 return Icon_Moving;
1939 return Icon_NOICON;
1941 static const char *get_lrc_timeline(int selected, void *data,
1942 char *buffer, size_t buffer_len)
1944 (void) data;
1945 struct lrc_line *lrc_line = get_lrc_line(selected);
1946 if (lrc_line)
1948 format_time_tag(temp_buf, get_time_start(lrc_line));
1949 rb->snprintf(buffer, buffer_len, "[%s]%s",
1950 temp_buf, get_lrc_str(lrc_line));
1951 return buffer;
1953 return NULL;
1956 static void save_changes(void)
1958 char new_file[MAX_PATH], *p;
1959 bool success = false;
1960 int fd, fe;
1961 if (!current.changed_lrc)
1962 return;
1963 rb->splash(HZ/2, "Saving changes...");
1964 if (current.type > NUM_TYPES)
1966 /* save changes to new .lrc8 file */
1967 rb->strcpy(new_file, current.lrc_file);
1968 p = rb->strrchr(new_file, '.');
1969 rb->strcpy(p, extentions[LRC8]);
1971 else if (current.type == TXT)
1973 /* save changes to new .lrc file */
1974 rb->strcpy(new_file, current.lrc_file);
1975 p = rb->strrchr(new_file, '.');
1976 rb->strcpy(p, extentions[LRC]);
1978 else
1980 /* file already exists. use temp file. */
1981 rb->snprintf(new_file, MAX_PATH, "%s~", current.lrc_file);
1983 fd = rb->creat(new_file, 0666);
1984 fe = rb->open(current.lrc_file, O_RDONLY);
1985 if (fd >= 0 && fe >= 0)
1987 struct lrc_line *lrc_line, *temp_lrc;
1988 off_t curr = 0, next = 0, size = 0, offset = 0;
1989 for (lrc_line = current.ll_head; lrc_line;
1990 lrc_line = lrc_line->next)
1992 /* apply offset and set old_time_start -1 to indicate
1993 that time tag is not saved yet. */
1994 lrc_line->time_start = get_time_start(lrc_line);
1995 lrc_line->old_time_start = -1;
1997 current.offset = 0;
1998 if (current.type > NUM_TYPES)
1999 curr = -1;
2000 else
2001 size = rb->filesize(fe);
2002 while (curr < size)
2004 /* find offset of next tag */
2005 lrc_line = NULL;
2006 for (temp_lrc = current.ll_head, next = size;
2007 temp_lrc; temp_lrc = temp_lrc->next)
2009 offset = temp_lrc->file_offset;
2010 if (offset == -1 ||
2011 (offset < next && temp_lrc->old_time_start == -1))
2013 lrc_line = temp_lrc;
2014 next = offset;
2015 if (offset <= curr) break;
2018 offset = current.offset_file_offset;
2019 if (offset >= curr && offset < next)
2021 lrc_line = NULL;
2022 next = offset;
2023 current.offset_file_offset = -1;
2025 if (next > curr)
2027 if (curr == -1) curr = 0;
2028 /* copy before the next tag */
2029 while (curr < next)
2031 ssize_t count = next-curr;
2032 if (count > MAX_LINE_LEN)
2033 count = MAX_LINE_LEN;
2034 if (rb->read(fe, temp_buf, count)!=count)
2035 break;
2036 rb->write(fd, temp_buf, count);
2037 curr += count;
2039 if (curr < next || curr >= size) break;
2041 /* write tag to new file and skip tag in backup */
2042 if (lrc_line != NULL)
2044 lrc_line->file_offset = rb->lseek(fd, 0, SEEK_CUR);
2045 lrc_line->old_time_start = lrc_line->time_start;
2046 long t = lrc_line->time_start;
2047 if (current.type == SNC)
2049 rb->fdprintf(fd, "%02ld%02ld%02ld%02ld", (t/3600000)%100,
2050 (t/60000)%60, (t/1000)%60, (t/10)%100);
2051 /* skip time tag */
2052 curr += rb->read(fe, temp_buf, 8);
2054 else /* LRC || LRC8 */
2056 format_time_tag(temp_buf, t);
2057 rb->fdprintf(fd, "[%s]", temp_buf);
2059 if (next == -1)
2061 rb->fdprintf(fd, "%s\n", get_lrc_str(lrc_line));
2064 if (current.type == LRC || current.type == LRC8)
2066 /* skip both time tag and offset tag */
2067 while (curr++<size && rb->read(fe, temp_buf, 1)==1)
2068 if (temp_buf[0]==']') break;
2071 success = (curr>=size);
2073 if (fe >= 0) rb->close(fe);
2074 if (fd >= 0) rb->close(fd);
2076 if (success)
2078 if (current.type == TXT)
2080 current.type = LRC;
2081 rb->strcpy(current.lrc_file, new_file);
2083 else if (current.type > NUM_TYPES)
2085 current.type = LRC8;
2086 rb->strcpy(current.lrc_file, new_file);
2088 else
2090 rb->remove(current.lrc_file);
2091 rb->rename(new_file, current.lrc_file);
2094 else
2096 rb->remove(new_file);
2097 rb->splash(HZ, "Could not save changes.");
2099 rb->reload_directory();
2100 current.changed_lrc = false;
2102 static int timetag_editor(void)
2104 struct gui_synclist gui_editor;
2105 struct lrc_line *lrc_line;
2106 bool exit = false;
2107 int button, idx, selected = 0;
2109 if (current.id3 == NULL || !current.ll_head)
2111 rb->splash(HZ, "No lyrics");
2112 return LRC_GOTO_MAIN;
2115 get_lrc_line(-1); /* initialize static variables */
2117 for (lrc_line = current.ll_head, idx = 0;
2118 lrc_line; lrc_line = lrc_line->next, idx++)
2120 long time_start = get_time_start(lrc_line);
2121 long time_end = get_time_start(lrc_line->next);
2122 long elapsed = current.id3->elapsed;
2123 if (time_start <= elapsed && time_end > elapsed)
2124 selected = idx;
2127 rb->gui_synclist_init(&gui_editor, &get_lrc_timeline, NULL, false, 1, NULL);
2128 rb->gui_synclist_set_nb_items(&gui_editor, current.nlrcline);
2129 rb->gui_synclist_set_icon_callback(&gui_editor, get_icon);
2130 rb->gui_synclist_set_title(&gui_editor, "Timetag Editor",
2131 Icon_Menu_functioncall);
2132 rb->gui_synclist_select_item(&gui_editor, selected);
2133 rb->gui_synclist_draw(&gui_editor);
2135 while (!exit)
2137 button = rb->get_action(CONTEXT_TREE, TIMEOUT_BLOCK);
2138 if (rb->gui_synclist_do_button(&gui_editor, &button,
2139 LIST_WRAP_UNLESS_HELD))
2140 continue;
2142 switch (button)
2144 case ACTION_STD_OK:
2145 idx = rb->gui_synclist_get_sel_pos(&gui_editor);
2146 lrc_line = get_lrc_line(idx);
2147 if (lrc_line)
2149 set_time_start(lrc_line, current.id3->elapsed-500);
2150 rb->gui_synclist_draw(&gui_editor);
2152 break;
2153 case ACTION_STD_CONTEXT:
2154 idx = rb->gui_synclist_get_sel_pos(&gui_editor);
2155 lrc_line = get_lrc_line(idx);
2156 if (lrc_line)
2158 long temp_time = get_time_start(lrc_line);
2159 if (lrc_set_time(get_lrc_str(lrc_line), NULL,
2160 &temp_time, 10, 0, current.length,
2161 LST_SET_MSEC|LST_SET_SEC|LST_SET_MIN) == 1)
2162 return PLUGIN_USB_CONNECTED;
2163 set_time_start(lrc_line, temp_time);
2164 rb->gui_synclist_draw(&gui_editor);
2166 break;
2167 case ACTION_TREE_STOP:
2168 case ACTION_STD_CANCEL:
2169 exit = true;
2170 break;
2171 default:
2172 if (rb->default_event_handler(button) == SYS_USB_CONNECTED)
2173 return PLUGIN_USB_CONNECTED;
2174 break;
2178 FOR_NB_SCREENS(idx)
2179 rb->screens[idx]->stop_scroll();
2181 if (current.changed_lrc)
2183 MENUITEM_STRINGLIST(save_menu, "Save Changes?", NULL,
2184 "Yes", "No (save later)", "Discard All Changes")
2185 button = 0;
2186 exit = false;
2187 while (!exit)
2189 switch (rb->do_menu(&save_menu, &button, NULL, false))
2191 case 0:
2192 sort_lrcs();
2193 save_changes();
2194 exit = true;
2195 break;
2196 case 1:
2197 sort_lrcs();
2198 exit = true;
2199 break;
2200 case 2:
2201 init_time_tag();
2202 exit = true;
2203 break;
2204 case MENU_ATTACHED_USB:
2205 return PLUGIN_USB_CONNECTED;
2209 return LRC_GOTO_MAIN;
2212 /*******************************
2213 * Settings.
2214 *******************************/
2215 static void load_or_save_settings(bool save)
2217 static const char config_file[] = "lrcplayer.cfg";
2218 static struct configdata config[] = {
2219 #ifdef HAVE_LCD_COLOR
2220 { TYPE_INT, 0, 0xffffff, { .int_p = &prefs.inactive_color },
2221 "inactive color", NULL },
2222 #endif
2223 #ifdef HAVE_LCD_BITMAP
2224 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.wrap }, "wrap", NULL },
2225 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.wipe }, "wipe", NULL },
2226 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.active_one_line },
2227 "active one line", NULL },
2228 { TYPE_INT, 0, 2, { .int_p = (int *) &prefs.align }, "align", NULL },
2229 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.statusbar_on },
2230 "statusbar on", NULL },
2231 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.display_title },
2232 "display title", NULL },
2233 #endif
2234 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.display_time },
2235 "display time", NULL },
2236 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.backlight_on },
2237 "backlight on", NULL },
2239 { TYPE_STRING, 0, sizeof(prefs.lrc_directory),
2240 { .string = prefs.lrc_directory }, "lrc directory", NULL },
2241 { TYPE_INT, -1, NUM_CODEPAGES-1, { .int_p = &prefs.encoding },
2242 "encoding", NULL },
2243 #ifdef LRC_SUPPORT_ID3
2244 { TYPE_BOOL, 0, 1, { .bool_p = &prefs.read_id3 }, "read id3", NULL },
2245 #endif
2248 if (!save)
2250 /* initialize setting */
2251 #if LCD_DEPTH > 1
2252 prefs.active_color = rb->lcd_get_foreground();
2253 prefs.inactive_color = LCD_LIGHTGRAY;
2254 #endif
2255 #ifdef HAVE_LCD_BITMAP
2256 prefs.wrap = true;
2257 prefs.wipe = true;
2258 prefs.active_one_line = false;
2259 prefs.align = ALIGN_CENTER;
2260 prefs.statusbar_on = false;
2261 prefs.display_title = true;
2262 #endif
2263 prefs.display_time = true;
2264 prefs.backlight_on = false;
2265 #ifdef LRC_SUPPORT_ID3
2266 prefs.read_id3 = true;
2267 #endif
2268 rb->strcpy(prefs.lrc_directory, "/Lyrics");
2269 prefs.encoding = -1; /* default codepage */
2271 configfile_load(config_file, config, ARRAYLEN(config), 0);
2273 else if (rb->memcmp(&old_prefs, &prefs, sizeof(prefs)))
2275 rb->splash(0, "Saving Settings");
2276 configfile_save(config_file, config, ARRAYLEN(config), 0);
2278 rb->memcpy(&old_prefs, &prefs, sizeof(prefs));
2281 static bool lrc_theme_menu(void)
2283 enum {
2284 #ifdef HAVE_LCD_BITMAP
2285 LRC_MENU_STATUSBAR,
2286 LRC_MENU_DISP_TITLE,
2287 #endif
2288 LRC_MENU_DISP_TIME,
2289 #ifdef HAVE_LCD_COLOR
2290 LRC_MENU_INACTIVE_COLOR,
2291 #endif
2292 LRC_MENU_BACKLIGHT,
2295 int selected = 0;
2296 bool exit = false, usb = false;
2298 MENUITEM_STRINGLIST(menu, "Theme Settings", NULL,
2299 #ifdef HAVE_LCD_BITMAP
2300 "Show Statusbar", "Display Title",
2301 #endif
2302 "Display Time",
2303 #ifdef HAVE_LCD_COLOR
2304 "Inactive Color",
2305 #endif
2306 "Backlight Force On");
2308 while (!exit && !usb)
2310 switch (rb->do_menu(&menu, &selected, NULL, false))
2312 #ifdef HAVE_LCD_BITMAP
2313 case LRC_MENU_STATUSBAR:
2314 usb = rb->set_bool("Show Statusbar", &prefs.statusbar_on);
2315 break;
2316 case LRC_MENU_DISP_TITLE:
2317 usb = rb->set_bool("Display Title", &prefs.display_title);
2318 break;
2319 #endif
2320 case LRC_MENU_DISP_TIME:
2321 usb = rb->set_bool("Display Time", &prefs.display_time);
2322 break;
2323 #ifdef HAVE_LCD_COLOR
2324 case LRC_MENU_INACTIVE_COLOR:
2325 usb = rb->set_color(NULL, "Inactive Color",
2326 &prefs.inactive_color, -1);
2327 break;
2328 #endif
2329 case LRC_MENU_BACKLIGHT:
2330 usb = rb->set_bool("Backlight Force On", &prefs.backlight_on);
2331 break;
2332 case MENU_ATTACHED_USB:
2333 usb = true;
2334 break;
2335 default:
2336 exit = true;
2337 break;
2341 return usb;
2344 #ifdef HAVE_LCD_BITMAP
2345 static bool lrc_display_menu(void)
2347 enum {
2348 LRC_MENU_WRAP,
2349 LRC_MENU_WIPE,
2350 LRC_MENU_ALIGN,
2351 LRC_MENU_LINE_MODE,
2354 int selected = 0;
2355 bool exit = false, usb = false;
2357 MENUITEM_STRINGLIST(menu, "Display Settings", NULL,
2358 "Wrap", "Wipe", "Align",
2359 "Activate Only Current Line");
2361 struct opt_items align_names[] = {
2362 {"Left", -1}, {"Center", -1}, {"Right", -1},
2365 while (!exit && !usb)
2367 switch (rb->do_menu(&menu, &selected, NULL, false))
2369 case LRC_MENU_WRAP:
2370 usb = rb->set_bool("Wrap", &prefs.wrap);
2371 break;
2372 case LRC_MENU_WIPE:
2373 usb = rb->set_bool("Wipe", &prefs.wipe);
2374 break;
2375 case LRC_MENU_ALIGN:
2376 usb = rb->set_option("Align", &prefs.align, INT,
2377 align_names, 3, NULL);
2378 break;
2379 case LRC_MENU_LINE_MODE:
2380 usb = rb->set_bool("Activate Only Current Line",
2381 &prefs.active_one_line);
2382 break;
2383 case MENU_ATTACHED_USB:
2384 usb = true;
2385 break;
2386 default:
2387 exit = true;
2388 break;
2392 return usb;
2394 #endif /* HAVE_LCD_BITMAP */
2396 static bool lrc_lyrics_menu(void)
2398 enum {
2399 LRC_MENU_ENCODING,
2400 #ifdef LRC_SUPPORT_ID3
2401 LRC_MENU_READ_ID3,
2402 #endif
2403 LRC_MENU_LRC_DIR,
2406 int selected = 0;
2407 bool exit = false, usb = false;
2409 struct opt_items cp_names[NUM_CODEPAGES+1];
2410 int old_val;
2412 MENUITEM_STRINGLIST(menu, "Lyrics Settings", NULL,
2413 "Encoding",
2414 #ifdef LRC_SUPPORT_ID3
2415 "Read ID3 tag",
2416 #endif
2417 "Lrc Directry");
2419 cp_names[0].string = "Use default codepage";
2420 cp_names[0].voice_id = -1;
2421 for (old_val = 1; old_val < NUM_CODEPAGES+1; old_val++)
2423 cp_names[old_val].string = rb->get_codepage_name(old_val-1);
2424 cp_names[old_val].voice_id = -1;
2427 while (!exit && !usb)
2429 switch (rb->do_menu(&menu, &selected, NULL, false))
2431 case LRC_MENU_ENCODING:
2432 prefs.encoding++;
2433 old_val = prefs.encoding;
2434 usb = rb->set_option("Encoding", &prefs.encoding, INT,
2435 cp_names, NUM_CODEPAGES+1, NULL);
2436 if (prefs.encoding != old_val)
2438 save_changes();
2439 if (current.type < NUM_TYPES)
2441 /* let reload lrc file to apply encoding setting */
2442 reset_current_data();
2445 prefs.encoding--;
2446 break;
2447 #ifdef LRC_SUPPORT_ID3
2448 case LRC_MENU_READ_ID3:
2449 usb = rb->set_bool("Read ID3 tag", &prefs.read_id3);
2450 break;
2451 #endif
2452 case LRC_MENU_LRC_DIR:
2453 rb->strcpy(temp_buf, prefs.lrc_directory);
2454 if (!rb->kbd_input(temp_buf, sizeof(prefs.lrc_directory)))
2455 rb->strcpy(prefs.lrc_directory, temp_buf);
2456 break;
2457 case MENU_ATTACHED_USB:
2458 usb = true;
2459 break;
2460 default:
2461 exit = true;
2462 break;
2466 return usb;
2469 #ifdef LRC_DEBUG
2470 static const char* lrc_debug_data(int selected, void * data,
2471 char * buffer, size_t buffer_len)
2473 (void)data;
2474 switch (selected)
2476 case 0:
2477 rb->strlcpy(buffer, current.mp3_file, buffer_len);
2478 break;
2479 case 1:
2480 rb->strlcpy(buffer, current.lrc_file, buffer_len);
2481 break;
2482 case 2:
2483 rb->snprintf(buffer, buffer_len, "buf usage: %d,%d/%d",
2484 (int)lrc_buffer_used, (int)lrc_buffer_end,
2485 (int)lrc_buffer_size);
2486 break;
2487 case 3:
2488 rb->snprintf(buffer, buffer_len, "line count: %d,%d",
2489 current.nlrcline, current.nlrcbrpos);
2490 break;
2491 case 4:
2492 rb->snprintf(buffer, buffer_len, "loaded lrc? %s",
2493 current.loaded_lrc?"yes":"no");
2494 break;
2495 case 5:
2496 rb->snprintf(buffer, buffer_len, "too many lines? %s",
2497 current.too_many_lines?"yes":"no");
2498 break;
2499 default:
2500 return NULL;
2502 return buffer;
2505 static bool lrc_debug_menu(void)
2507 struct simplelist_info info;
2508 rb->simplelist_info_init(&info, "Debug Menu", 6, NULL);
2509 info.hide_selection = true;
2510 info.scroll_all = true;
2511 info.get_name = lrc_debug_data;
2512 return rb->simplelist_show_list(&info);
2514 #endif
2516 /* returns one of enum lrc_screen or enum plugin_status */
2517 static int lrc_menu(void)
2519 enum {
2520 LRC_MENU_THEME,
2521 #ifdef HAVE_LCD_BITMAP
2522 LRC_MENU_DISPLAY,
2523 #endif
2524 LRC_MENU_LYRICS,
2525 LRC_MENU_PLAYBACK,
2526 #ifdef LRC_DEBUG
2527 LRC_MENU_DEBUG,
2528 #endif
2529 LRC_MENU_OFFSET,
2530 LRC_MENU_TIMETAG_EDITOR,
2531 LRC_MENU_QUIT,
2534 MENUITEM_STRINGLIST(menu, "Lrcplayer Menu", NULL,
2535 "Theme Settings",
2536 #ifdef HAVE_LCD_BITMAP
2537 "Display Settings",
2538 #endif
2539 "Lyrics Settings",
2540 "Playback Control",
2541 #ifdef LRC_DEBUG
2542 "Debug Menu",
2543 #endif
2544 "Time Offset", "Timetag Editor",
2545 "Quit");
2546 int selected = 0, ret = LRC_GOTO_MENU;
2547 bool usb = false;
2549 while (ret == LRC_GOTO_MENU)
2551 switch (rb->do_menu(&menu, &selected, NULL, false))
2553 case LRC_MENU_THEME:
2554 usb = lrc_theme_menu();
2555 break;
2556 #ifdef HAVE_LCD_BITMAP
2557 case LRC_MENU_DISPLAY:
2558 usb = lrc_display_menu();
2559 break;
2560 #endif
2561 case LRC_MENU_LYRICS:
2562 usb = lrc_lyrics_menu();
2563 break;
2564 case LRC_MENU_PLAYBACK:
2565 usb = playback_control(NULL);
2566 ret = LRC_GOTO_MAIN;
2567 break;
2568 #ifdef LRC_DEBUG
2569 case LRC_MENU_DEBUG:
2570 usb = lrc_debug_menu();
2571 ret = LRC_GOTO_MAIN;
2572 break;
2573 #endif
2574 case LRC_MENU_OFFSET:
2575 usb = (lrc_set_time("Time Offset", "sec", &current.offset,
2576 10, -60*1000, 60*1000,
2577 LST_SET_MSEC|LST_SET_SEC) == 1);
2578 ret = LRC_GOTO_MAIN;
2579 break;
2580 case LRC_MENU_TIMETAG_EDITOR:
2581 ret = LRC_GOTO_EDITOR;
2582 break;
2583 case LRC_MENU_QUIT:
2584 ret = PLUGIN_OK;
2585 break;
2586 case MENU_ATTACHED_USB:
2587 usb = true;
2588 break;
2589 default:
2590 ret = LRC_GOTO_MAIN;
2591 break;
2593 if (usb)
2594 ret = PLUGIN_USB_CONNECTED;
2596 return ret;
2599 /*******************************
2600 * Main.
2601 *******************************/
2602 /* returns true if song has changed to know when to load new lyrics. */
2603 static bool check_audio_status(void)
2605 static int last_audio_status = 0;
2606 if (current.ff_rewind == -1)
2607 current.audio_status = rb->audio_status();
2608 current.id3 = rb->audio_current_track();
2609 if ((last_audio_status^current.audio_status)&AUDIO_STATUS_PLAY)
2611 last_audio_status = current.audio_status;
2612 return true;
2614 if (AUDIO_STOP || current.id3 == NULL)
2615 return false;
2616 if (rb->strcmp(current.mp3_file, current.id3->path))
2618 return true;
2620 return false;
2622 static void ff_rewind(long time, bool resume)
2624 if (AUDIO_PLAY)
2626 if (!AUDIO_PAUSE)
2628 resume = true;
2629 rb->audio_pause();
2631 rb->audio_ff_rewind(time);
2632 rb->sleep(HZ/10); /* take affect seeking */
2633 if (resume)
2634 rb->audio_resume();
2638 static int handle_button(void)
2640 int ret = LRC_GOTO_MAIN;
2641 static int step = 0;
2642 int limit, button = rb->get_action(CONTEXT_WPS, HZ/10);
2643 switch (button)
2645 case ACTION_WPS_BROWSE:
2646 #if CONFIG_KEYPAD == ONDIO_PAD
2647 /* ondio doesn't have ACTION_WPS_MENU,
2648 so use ACTION_WPS_BROWSE for menu */
2649 ret = LRC_GOTO_MENU;
2650 break;
2651 #endif
2652 case ACTION_WPS_STOP:
2653 save_changes();
2654 ret = PLUGIN_OK;
2655 break;
2656 case ACTION_WPS_PLAY:
2657 if (AUDIO_STOP && rb->global_status->resume_index != -1)
2659 if (rb->playlist_resume() != -1)
2661 rb->playlist_start(rb->global_status->resume_index,
2662 rb->global_status->resume_offset);
2665 else if (AUDIO_PAUSE)
2666 rb->audio_resume();
2667 else
2668 rb->audio_pause();
2669 break;
2670 case ACTION_WPS_SEEKFWD:
2671 case ACTION_WPS_SEEKBACK:
2672 if (AUDIO_STOP)
2673 break;
2674 if (current.ff_rewind > -1)
2676 if (button == ACTION_WPS_SEEKFWD)
2677 /* fast forwarding, calc max step relative to end */
2678 limit = (current.length - current.ff_rewind) * 3 / 100;
2679 else
2680 /* rewinding, calc max step relative to start */
2681 limit = (current.ff_rewind) * 3 / 100;
2682 limit = MAX(limit, 500);
2684 if (step > limit)
2685 step = limit;
2687 if (button == ACTION_WPS_SEEKFWD)
2688 current.ff_rewind += step;
2689 else
2690 current.ff_rewind -= step;
2692 if (current.ff_rewind > current.length-100)
2693 current.ff_rewind = current.length-100;
2694 if (current.ff_rewind < 0)
2695 current.ff_rewind = 0;
2697 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
2698 step += step >> (rb->global_settings->ff_rewind_accel + 3);
2700 else
2702 current.ff_rewind = current.elapsed;
2703 if (!AUDIO_PAUSE)
2704 rb->audio_pause();
2705 step = 1000 * rb->global_settings->ff_rewind_min_step;
2707 break;
2708 case ACTION_WPS_STOPSEEK:
2709 if (current.ff_rewind == -1)
2710 break;
2711 ff_rewind(current.ff_rewind, !AUDIO_PAUSE);
2712 current.elapsed = current.ff_rewind;
2713 current.ff_rewind = -1;
2714 break;
2715 case ACTION_WPS_SKIPNEXT:
2716 rb->audio_next();
2717 break;
2718 case ACTION_WPS_SKIPPREV:
2719 if (current.elapsed < 3000)
2720 rb->audio_prev();
2721 else
2722 ff_rewind(0, false);
2723 break;
2724 case ACTION_WPS_VOLDOWN:
2725 limit = rb->sound_min(SOUND_VOLUME);
2726 if (--rb->global_settings->volume < limit)
2727 rb->global_settings->volume = limit;
2728 rb->sound_set(SOUND_VOLUME, rb->global_settings->volume);
2729 break;
2730 case ACTION_WPS_VOLUP:
2731 limit = rb->sound_max(SOUND_VOLUME);
2732 if (++rb->global_settings->volume > limit)
2733 rb->global_settings->volume = limit;
2734 rb->sound_set(SOUND_VOLUME, rb->global_settings->volume);
2735 break;
2736 case ACTION_WPS_CONTEXT:
2737 ret = LRC_GOTO_EDITOR;
2738 break;
2739 case ACTION_WPS_MENU:
2740 ret = LRC_GOTO_MENU;
2741 break;
2742 default:
2743 if(rb->default_event_handler(button) == SYS_USB_CONNECTED)
2744 ret = PLUGIN_USB_CONNECTED;
2745 break;
2747 return ret;
2750 static int lrc_main(void)
2752 int ret = LRC_GOTO_MAIN;
2753 int i;
2754 long id3_timeout = 0;
2755 bool update_display_state = true;
2757 #ifdef HAVE_LCD_BITMAP
2758 /* y offset of vp_lyrics */
2759 int h = (prefs.display_title?font_ui_height:0)+
2760 (prefs.display_time?SYSFONT_HEIGHT*2:0);
2762 #endif
2764 FOR_NB_SCREENS(i)
2766 #ifdef HAVE_LCD_BITMAP
2767 rb->viewportmanager_theme_enable(i, prefs.statusbar_on, &vp_info[i]);
2768 vp_lyrics[i] = vp_info[i];
2769 vp_lyrics[i].flags &= ~VP_FLAG_ALIGNMENT_MASK;
2770 vp_lyrics[i].y += h;
2771 vp_lyrics[i].height -= h;
2772 #else
2773 rb->viewport_set_defaults(&vp_lyrics[i], i);
2774 if (prefs.display_time)
2776 vp_lyrics[i].y += 1; /* time */
2777 vp_lyrics[i].height -= 1;
2779 #endif
2782 if (prefs.backlight_on)
2783 backlight_force_on();
2785 #ifdef HAVE_LCD_BITMAP
2786 /* in case settings that may affect break position
2787 * are changed (statusbar_on and wrap). */
2788 if (!current.too_many_lines)
2789 calc_brpos(NULL, 0);
2790 #endif
2792 while (ret == LRC_GOTO_MAIN)
2794 if (check_audio_status())
2796 update_display_state = true;
2797 if (AUDIO_STOP)
2799 current.id3 = NULL;
2800 id3_timeout = 0;
2802 else if (rb->strcmp(current.mp3_file, current.id3->path))
2804 save_changes();
2805 reset_current_data();
2806 rb->strcpy(current.mp3_file, current.id3->path);
2807 id3_timeout = *rb->current_tick+HZ*3;
2808 current.found_lrc = false;
2811 if (current.id3 && current.id3->length)
2813 if (current.ff_rewind == -1)
2815 long di = current.id3->elapsed - current.elapsed;
2816 if (di < -250 || di > 0)
2817 current.elapsed = current.id3->elapsed;
2819 else
2820 current.elapsed = current.ff_rewind;
2821 current.length = current.id3->length;
2822 if (current.elapsed > current.length)
2823 current.elapsed = current.length;
2825 else
2827 current.elapsed = 0;
2828 current.length = 1;
2831 if (current.id3 && id3_timeout &&
2832 (TIME_AFTER(*rb->current_tick, id3_timeout) ||
2833 current.id3->artist))
2835 update_display_state = true;
2836 id3_timeout = 0;
2838 current.found_lrc = find_lrc_file();
2839 #ifdef LRC_SUPPORT_ID3
2840 if (!current.found_lrc && prefs.read_id3)
2842 /* no lyrics file found. try to read from id3 tag. */
2843 current.found_lrc = read_id3();
2845 #endif
2847 else if (current.found_lrc && !current.loaded_lrc)
2849 /* current.loaded_lrc is false after changing encode setting */
2850 update_display_state = true;
2851 load_lrc_file();
2853 if (update_display_state)
2855 #ifdef HAVE_LCD_BITMAP
2856 if (current.type == TXT || current.type == ID3_USLT)
2857 current.wipe = false;
2858 else
2859 current.wipe = prefs.wipe;
2860 #endif
2861 display_state();
2862 update_display_state = false;
2864 if (AUDIO_PLAY)
2866 if (prefs.display_time)
2867 display_time();
2868 if (!id3_timeout)
2869 display_lrcs();
2872 ret = handle_button();
2875 #ifdef HAVE_LCD_BITMAP
2876 FOR_NB_SCREENS(i)
2877 rb->viewportmanager_theme_undo(i, false);
2878 #endif
2879 if (prefs.backlight_on)
2880 backlight_use_settings();
2882 return ret;
2885 /* this is the plugin entry point */
2886 enum plugin_status plugin_start(const void* parameter)
2888 int ret = LRC_GOTO_MAIN;
2890 /* initialize settings. */
2891 load_or_save_settings(false);
2893 #ifdef HAVE_LCD_BITMAP
2894 rb->lcd_getstringsize("O", NULL, &font_ui_height);
2895 #endif
2897 lrc_buffer = rb->plugin_get_buffer(&lrc_buffer_size);
2898 lrc_buffer = (void *)(((long)lrc_buffer+3)&~3); /* 4 bytes aligned */
2899 lrc_buffer_size = (lrc_buffer_size - 4)&~3;
2901 reset_current_data();
2902 current.id3 = NULL;
2903 current.mp3_file[0] = 0;
2904 current.lrc_file[0] = 0;
2905 current.ff_rewind = -1;
2906 current.found_lrc = false;
2907 if (parameter && check_audio_status())
2909 const char *ext;
2910 rb->strcpy(current.mp3_file, current.id3->path);
2911 /* use passed parameter as lrc file. */
2912 rb->strcpy(current.lrc_file, parameter);
2913 if (!rb->file_exists(current.lrc_file))
2915 rb->splash(HZ, "Specified file dose not exist.");
2916 return PLUGIN_ERROR;
2918 ext = rb->strrchr(current.lrc_file, '.');
2919 for (current.type = 0; ext && current.type < NUM_TYPES; current.type++)
2921 if (!rb->strcmp(ext, extentions[current.type]))
2922 break;
2924 if (!ext || current.type == NUM_TYPES)
2926 rb->splashf(HZ, "%s is not supported", ext? ext: current.lrc_file);
2927 return PLUGIN_ERROR;
2929 current.found_lrc = true;
2932 while (ret >= PLUGIN_OTHER)
2934 switch (ret)
2936 case LRC_GOTO_MAIN:
2937 ret = lrc_main();
2938 break;
2939 case LRC_GOTO_MENU:
2940 ret = lrc_menu();
2941 break;
2942 case LRC_GOTO_EDITOR:
2943 ret = timetag_editor();
2944 break;
2945 default:
2946 ret = PLUGIN_ERROR;
2947 break;
2951 load_or_save_settings(true);
2952 return ret;