1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
22 #include "lib/playback_control.h"
23 #include "lib/configfile.h"
24 #include "lib/helper.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
35 /* define this to show debug info in menu */
36 /* #define LRC_DEBUG */
60 off_t file_offset
; /* offset of time tag in file */
63 short nline
[NB_SCREENS
];
64 struct lrc_line
*next
;
65 struct lrc_word
*words
;
69 /* display settings */
71 unsigned active_color
;
72 unsigned inactive_color
;
74 #ifdef HAVE_LCD_BITMAP
78 int align
; /* 0: left, 1: center, 2: right */
86 char lrc_directory
[64];
88 #ifdef LRC_SUPPORT_ID3
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
;
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 */
116 struct lrc_line
*ll_head
, **ll_tail
;
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 */
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
];
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 /*******************************
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
149 #else /* HAVE_LCD_BITMAP */
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
[] = {
157 #ifdef HAVE_REMOTE_LCD
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};
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;
171 if (!(flags
&LST_SET_TIME
))
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
;
184 rb
->button_clear_queue();
185 rb
->lcd_clear_display();
186 rb
->lcd_puts_scroll(0, LST_OFF_Y
, title
);
190 long abs_val
= value
;
191 long segvals
[4] = {-1, -1, -1, -1};
192 /* show negative value like -00:01 but 00:-1 */
199 /* calc value of each segments */
200 for (i
= pos_min
; i
<= pos_max
; i
++)
202 segvals
[i
] = abs_val
% maxs
[i
];
205 segvals
[i
-1] += abs_val
* maxs
[i
-1];
206 for (i
= pos_max
; i
>= pos_min
; i
--)
210 rb
->lcd_getstringsize(buffer
, &x
, &y
);
213 rb
->snprintf(&buffer
[len
], 32-len
, formats
[i
], segvals
[i
]);
214 len
+= rb
->strlen(&buffer
[len
]);
218 buffer
[len
-1] = 0; /* remove last separater */
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
)
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
);
233 rb
->lcd_put_cursor(x
+rb
->utf8length(&buffer
[p_start
])-1, y
, 0x7F);
237 int button
= pluginlib_getaction(TIMEOUT_BLOCK
, lst_contexts
, ARRAYLEN(lst_contexts
));
239 #ifdef HAVE_LCD_CHARCELLS
240 if (pos_min
!= pos_max
)
241 rb
->lcd_remove_cursor();
246 case PLA_DOWN_REPEAT
:
250 if (button
== PLA_DOWN_REPEAT
|| button
== PLA_DOWN
)
254 scl_step
= ((scls
[pos
]/scls
[pos_min
]+step
-1)/step
) * step
;
257 value
+= scl_step
* mult
;
264 case PLA_LEFT_REPEAT
:
269 case PLA_RIGHT_REPEAT
:
279 rb
->splash(HZ
, "Cancelled");
283 if (rb
->default_event_handler(button
) == SYS_USB_CONNECTED
)
288 rb
->lcd_clear_display();
293 /*******************************
295 *******************************/
296 static void reset_current_data(void)
298 current
.title
= NULL
;
299 current
.artist
= NULL
;
301 current
.offset_file_offset
= -1;
302 current
.nlrcbrpos
= 0;
303 current
.nlrcline
= 0;
304 current
.ll_head
= NULL
;
305 current
.ll_tail
= ¤t
.ll_head
;
306 current
.loaded_lrc
= false;
307 current
.changed_lrc
= false;
308 current
.too_many_lines
= false;
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
)
322 rb
->strcpy(pos
, str
);
323 lrc_buffer_used
+= siz
;
326 static void *alloc_buf(size_t siz
)
329 if (lrc_buffer_used
+ siz
> lrc_buffer_end
)
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
)
339 if ((lrc_word
->word
= lrcbufadd(word
, join
)) == NULL
)
341 lrc_word
->time_start
= time_start
;
344 static bool add_lrc_line(struct lrc_line
*lrc_line
, char *word
)
347 lrc_line
->next
= NULL
;
348 lrc_line
->words
= NULL
;
351 if ((lrc_line
->words
= new_lrc_word(-1, word
, false)) == NULL
)
355 *current
.ll_tail
= lrc_line
;
356 current
.ll_tail
= &(lrc_line
->next
);
360 static struct lrc_line
*get_lrc_line(int idx
)
362 static struct lrc_line
*lrc_line
= NULL
;
366 lrc_line
= current
.ll_head
;
369 while (n
< idx
&& lrc_line
)
371 lrc_line
= lrc_line
->next
;
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
410 while (*str
&& *str
!= '\n' && isspace(*str
))
419 #ifdef HAVE_LCD_BITMAP
420 static bool isbrchr(const unsigned char *str
, int len
)
422 const unsigned char *p
= "!,-.:;? 、。!,.:;?―";
428 int n
= rb
->utf8seek(p
, 1);
429 if (len
== n
&& !rb
->strncmp(p
, str
, len
))
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
);
452 int word_count
, word_width
;
453 const unsigned char *str
;
455 #ifndef HAVE_LCD_CHARCELLS
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
);
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
)
474 lrc_brpos
= calc_brpos(lrc_line
, i
);
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
;
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
];
507 lrc_brpos
+= lrc_line
->nline
[--i
];
512 /* calculate number of lines, line width and char count for each line. */
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
527 #ifndef HAVE_LCD_CHARCELLS
533 while(cr
.nword
> 0 && cr
.str
>= (lrc_word
-1)->word
)
540 if (*cr
.str
== 0 || *cr
.str
== '\n')
544 #ifdef HAVE_LCD_CHARCELLS
545 c
= rb
->utf8seek(cr
.str
, 1);
548 c
= ((long)rb
->utf8decode(cr
.str
, &ch
) - (long)cr
.str
);
549 if (rb
->is_diacritic(ch
, NULL
))
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
)
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
;
582 lrc_word
->count
+= c
;
583 lrc_word
->width
+= w
;
586 lrc_line
->width
+= cr
.width
;
587 lrc_brpos
->count
= cr
.count
;
588 lrc_brpos
->width
= cr
.width
;
591 cr
.str
= lrc_skip_space(cr
.str
);
592 } while (*cr
.str
&& nlrcbrpos
< max_lrcbrpos
);
593 lrc_line
->nline
[i
] = nlrcbrpos
;
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
;
611 current
.ll_head
= NULL
;
612 current
.ll_tail
= ¤t
.ll_head
;
616 /* remove problematic lrc_lines.
617 * it would cause problem in display_lrc_line() if nword is 0. */
620 q
= p
->time_start
>= time_max
? current
.ll_tail
: ¤t
.ll_head
;
621 while ((*q
) && (*q
)->time_start
<= p
->time_start
)
627 time_max
= p
->time_start
;
628 current
.ll_tail
= &p
->next
;
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
;
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
];
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
;
664 current
.changed_lrc
= false;
667 /*******************************
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
680 /* taken from apps/recorder/albumart.c */
681 static void fix_filename(char* name
)
683 static const char invalid_chars
[] = "*/:<>?\\|";
691 else if (rb
->strchr(invalid_chars
, *name
))
696 static bool find_lrc_file_helper(const char *base_dir
)
698 char fname
[MAX_PATH
];
699 char *names
[3] = {NULL
, NULL
, NULL
};
702 /* /aaa/bbb/ccc/ddd.mp3
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
)
712 if (current
.id3
->title
&& rb
->strcmp(names
[0], current
.id3
->title
))
714 rb
->strlcpy(fname
, current
.id3
->title
, sizeof(fname
));
728 len
= rb
->snprintf(current
.lrc_file
, MAX_PATH
, "%s%s/",
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
);
745 DEBUGF("check file in %s\n", current
.lrc_file
);
746 if (!rb
->dir_exists(current
.lrc_file
))
748 for (current
.type
= 0; current
.type
< NUM_TYPES
; current
.type
++)
750 for (i
= 0; names
[i
] != NULL
; i
++)
752 rb
->snprintf(¤t
.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
);
762 } while ((p
= rb
->strrchr(dir
, '/')) != NULL
);
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
);
773 if (find_lrc_file_helper(""))
775 if (prefs
.lrc_directory
[0] == '/' && rb
->dir_exists(prefs
.lrc_directory
))
777 if (find_lrc_file_helper(prefs
.lrc_directory
))
781 current
.lrc_file
[0] = 0;
785 /*******************************
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,
795 static char *parse_int(char *ptr
, int *val
)
797 *val
= rb
->atoi(ptr
);
798 while (isdigit(*ptr
)) ptr
++;
801 static long get_time_value(char *tag
, bool read_id_tags
, off_t file_offset
)
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);
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);
821 if (!rb
->strncmp(tag
, "offset:", 7))
823 current
.offset
= rb
->atoi(&tag
[7]);
824 current
.offset_file_offset
= file_offset
;
830 ptr
= parse_int(tag
, &val
);
831 if (ptr
-tag
< 1 || ptr
-tag
> 2 || *ptr
!= ':')
836 ptr
= parse_int(tag
, &val
);
837 if (ptr
-tag
!= 2 || (*ptr
!= '.' && *ptr
!= ':' && *ptr
!= '\0'))
845 ptr
= parse_int(tag
, &val
);
846 if (ptr
-tag
< 2 || ptr
-tag
> 3 || *ptr
!= '\0')
848 time
+= ((ptr
-tag
)==3 ?val
: val
*10);
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
;
867 /* parse [time tag]...[time tag] type tags */
871 if (*str
!= '[') break;
872 tagend
= rb
->strchr(str
, ']');
873 if (tagend
== NULL
) break;
875 time
= get_time_value(str
+1, !lrc_line
, file_offset
);
879 lrc_line
= alloc_buf(sizeof(struct lrc_line
));
880 if (lrc_line
== NULL
)
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
;
892 return true; /* no time tag in line */
894 lrc_line
= first_lrc_line
;
895 if (lrcbufadd("", false) == NULL
)
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. */
905 tagstart
= rb
->strchr(tagstart
, '<');
906 if (!tagstart
) break;
907 tagend
= rb
->strchr(tagstart
, '>');
910 time
= get_time_value(tagstart
+1, false,
911 file_offset
+ ((long)tagstart
- (long)str
));
919 /* found word time tag. */
920 if (*str
|| time_start
!= -1)
922 if ((lrc_word
= new_lrc_word(time_start
, str
, true)) == NULL
)
926 file_offset
+= (long)tagend
- (long)str
;
927 tagstart
= str
= tagend
;
930 if ((lrc_word
= new_lrc_word(time_start
, str
, true)) == NULL
)
934 /* duplicate lrc_lines */
937 lrc_line
->nword
= nword
;
938 lrc_line
->words
= lrc_word
;
939 lrc_line
= lrc_line
->next
;
946 * \xa2\xe2hhmmssxx\xa2\xd0
949 * \xa2\xe2hhmmssxx\xa2\xd0
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 */
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 */
973 struct lrc_line
*lrc_line
= alloc_buf(sizeof(struct lrc_line
));
974 if (lrc_line
== NULL
)
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
, ""))
984 /* encode rest of line and add to buffer */
985 rb
->iso_decode(pos
, line
, prefs
.encoding
, rb
->strlen(pos
)+1);
989 rb
->strcat(line
, "\n");
990 if (lrcbufadd(line
, true) == NULL
)
996 static bool parse_txt_line(char *line
, off_t file_offset
)
999 struct lrc_line
*lrc_line
= alloc_buf(sizeof(struct lrc_line
));
1000 if (lrc_line
== NULL
)
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
))
1010 static void load_lrc_file(void)
1012 char utf8line
[MAX_LINE_LEN
*3];
1014 int encoding
= prefs
.encoding
;
1015 bool (*line_parser
)(char *line
, off_t
) = NULL
;
1016 off_t file_offset
, readsize
;
1018 switch(current
.type
)
1021 encoding
= UTF_8
; /* .lrc8 is utf8 */
1024 line_parser
= parse_lrc_line
;
1027 line_parser
= parse_snc_line
;
1030 line_parser
= parse_txt_line
;
1036 fd
= rb
->open(current
.lrc_file
, O_RDONLY
);
1040 /* check encoding */
1041 #define BOM "\xef\xbb\xbf"
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 */
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
;
1061 rb
->lseek(fd
, 0, SEEK_SET
);
1066 /* convert encoding of file from UTF-16 to UTF-8 */
1067 char temp_file
[MAX_PATH
];
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);
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
);
1085 rb
->remove(current
.lrc_file
);
1086 rb
->rename(temp_file
, current
.lrc_file
);
1087 fd
= rb
->open(current
.lrc_file
, O_RDONLY
);
1089 rb
->lseek(fd
, BOM_SIZE
, SEEK_SET
); /* skip bom */
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
))
1101 file_offset
+= readsize
;
1105 current
.loaded_lrc
= true;
1106 calc_brpos(NULL
, 0);
1112 #ifdef LRC_SUPPORT_ID3
1113 /*******************************
1114 * read lyrics from id3
1115 *******************************/
1116 /* taken from apps/metadata/mp3.c */
1117 static unsigned long unsync(unsigned long b0
, unsigned long b1
,
1118 unsigned long b2
, unsigned long b3
)
1120 return (((long)(b0
& 0x7F) << (3*7)) |
1121 ((long)(b1
& 0x7F) << (2*7)) |
1122 ((long)(b2
& 0x7F) << (1*7)) |
1123 ((long)(b3
& 0x7F) << (0*7)));
1126 static unsigned long bytes2int(unsigned long b0
, unsigned long b1
,
1127 unsigned long b2
, unsigned long b3
)
1129 return (((long)(b0
& 0xFF) << (3*8)) |
1130 ((long)(b1
& 0xFF) << (2*8)) |
1131 ((long)(b2
& 0xFF) << (1*8)) |
1132 ((long)(b3
& 0xFF) << (0*8)));
1135 static int unsynchronize(char* tag
, int len
, bool *ff_found
)
1139 unsigned char *rp
, *wp
;
1140 bool _ff_found
= false;
1141 if(ff_found
) _ff_found
= *ff_found
;
1143 wp
= rp
= (unsigned char *)tag
;
1145 rp
= (unsigned char *)tag
;
1146 for(i
= 0; i
<len
; i
++) {
1147 /* Read the next byte and write it back, but don't increment the
1152 /* Increment the write pointer if it isn't an unsynch pattern */
1162 if(ff_found
) *ff_found
= _ff_found
;
1163 return (long)wp
- (long)tag
;
1166 static int read_unsynched(int fd
, void *buf
, int len
, bool *ff_found
)
1170 int remaining
= len
;
1176 rc
= rb
->read(fd
, wp
, remaining
);
1180 i
= unsynchronize(wp
, remaining
, ff_found
);
1188 static unsigned char* utf8cpy(const unsigned char *src
,
1189 unsigned char *dst
, int count
)
1191 rb
->strlcpy(dst
, src
, count
+1);
1192 return dst
+rb
->strlen(dst
);
1195 static void parse_id3v2(int fd
)
1202 unsigned char version
;
1204 unsigned char global_flags
;
1206 bool global_unsynch
= false;
1207 bool global_ff_found
= false;
1208 bool unsynch
= false;
1210 enum {NOLT
, SYLT
, USLT
} type
= NOLT
;
1212 /* Bail out if the tag is shorter than 10 bytes */
1213 if(current
.id3
->id3v2len
< 10)
1216 /* Read the ID3 tag version from the header */
1217 if(10 != rb
->read(fd
, header
, 10))
1220 /* Get the total ID3 tag size */
1221 size
= current
.id3
->id3v2len
- 10;
1223 version
= current
.id3
->id3version
;
1239 /* unsupported id3 version */
1243 global_flags
= header
[5];
1245 /* Skip the extended header if it is present */
1246 if(global_flags
& 0x40) {
1248 if(version
== ID3_VER_2_3
) {
1249 if(10 != rb
->read(fd
, header
, 10))
1251 /* The 2.3 extended header size doesn't include the header size
1252 field itself. Also, it is not unsynched. */
1254 bytes2int(header
[0], header
[1], header
[2], header
[3]) + 4;
1256 /* Skip the rest of the header */
1257 rb
->lseek(fd
, framelen
- 10, SEEK_CUR
);
1260 if(version
>= ID3_VER_2_4
) {
1261 if(4 != rb
->read(fd
, header
, 4))
1264 /* The 2.4 extended header size does include the entire header,
1265 so here we can just skip it. This header is unsynched. */
1266 framelen
= unsync(header
[0], header
[1],
1267 header
[2], header
[3]);
1269 rb
->lseek(fd
, framelen
- 4, SEEK_CUR
);
1273 /* Is unsynchronization applied? */
1274 if(global_flags
& 0x80) {
1275 global_unsynch
= true;
1278 /* We must have at least minframesize bytes left for the
1279 * remaining frames to be interesting */
1280 while (size
>= minframesize
) {
1283 /* Read frame header and check length */
1284 if(version
>= ID3_VER_2_3
) {
1285 if(global_unsynch
&& version
<= ID3_VER_2_3
)
1286 rc
= read_unsynched(fd
, header
, 10, &global_ff_found
);
1288 rc
= rb
->read(fd
, header
, 10);
1291 /* Adjust for the 10 bytes we read */
1294 flags
= bytes2int(0, 0, header
[8], header
[9]);
1296 if (version
>= ID3_VER_2_4
) {
1297 framelen
= unsync(header
[4], header
[5],
1298 header
[6], header
[7]);
1300 /* version .3 files don't use synchsafe ints for
1302 framelen
= bytes2int(header
[4], header
[5],
1303 header
[6], header
[7]);
1306 if(6 != rb
->read(fd
, header
, 6))
1308 /* Adjust for the 6 bytes we read */
1311 framelen
= bytes2int(0, header
[3], header
[4], header
[5]);
1315 if (header
[0] == 0 && header
[1] == 0 && header
[2] == 0)
1325 if (version
>= ID3_VER_2_4
) {
1326 if(flags
& 0x0040) { /* Grouping identity */
1327 rb
->lseek(fd
, 1, SEEK_CUR
); /* Skip 1 byte */
1331 if(flags
& 0x0020) { /* Grouping identity */
1332 rb
->lseek(fd
, 1, SEEK_CUR
); /* Skip 1 byte */
1337 if(flags
& 0x000c) /* Compression or encryption */
1341 rb
->lseek(fd
, framelen
, SEEK_CUR
);
1345 if(flags
& 0x0002) /* Unsynchronization */
1348 if (version
>= ID3_VER_2_4
) {
1349 if(flags
& 0x0001) { /* Data length indicator */
1350 if(4 != rb
->read(fd
, tmp
, 4))
1353 /* We don't need the data length */
1365 if(!rb
->memcmp( header
, "SLT", 3 ) ||
1366 !rb
->memcmp( header
, "SYLT", 4 ))
1368 /* found a supported tag */
1372 else if(!rb
->memcmp( header
, "ULT", 3 ) ||
1373 !rb
->memcmp( header
, "USLT", 4 ))
1375 /* found a supported tag */
1381 /* not a supported tag*/
1382 if(global_unsynch
&& version
<= ID3_VER_2_3
) {
1383 size
-= read_unsynched(fd
, lrc_buffer
, framelen
, &global_ff_found
);
1386 if( rb
->lseek(fd
, framelen
, SEEK_CUR
) == -1 )
1394 int encoding
= 0, chsiz
;
1395 char *tag
, *p
, utf8line
[MAX_LINE_LEN
*3];
1396 unsigned char* (*utf_decode
)(const unsigned char *,
1397 unsigned char *, int) = NULL
;
1398 /* use middle of lrc_buffer to store tag data. */
1399 if(framelen
>= LRC_BUFFER_SIZE
/3)
1400 framelen
= LRC_BUFFER_SIZE
/3-1;
1401 tag
= lrc_buffer
+LRC_BUFFER_SIZE
*2/3-framelen
-1;
1402 if(global_unsynch
&& version
<= ID3_VER_2_3
)
1403 bytesread
= read_unsynched(fd
, tag
, framelen
, &global_ff_found
);
1405 bytesread
= rb
->read(fd
, tag
, framelen
);
1407 if( bytesread
!= framelen
)
1410 if(unsynch
|| (global_unsynch
&& version
>= ID3_VER_2_4
))
1411 bytesread
= unsynchronize(tag
, bytesread
, NULL
);
1416 /* skip some data */
1423 /* check encoding and skip content descriptor */
1425 case 0x01: /* Unicode with or without BOM */
1428 /* Now check if there is a BOM
1429 (zero-width non-breaking space, 0xfeff)
1430 and if it is in little or big endian format */
1431 if(!rb
->memcmp(p
, "\xff\xfe", 2)) { /* Little endian? */
1432 utf_decode
= rb
->utf16LEdecode
;
1433 } else if(!rb
->memcmp(p
, "\xfe\xff", 2)) { /* Big endian? */
1434 utf_decode
= rb
->utf16BEdecode
;
1438 encoding
= NUM_CODEPAGES
;
1447 utf_decode
= utf8cpy
;
1448 if(encoding
== 0x03) /* UTF-8 encoded string */
1451 encoding
= prefs
.encoding
;
1452 p
+= rb
->strlen(p
)+1;
1456 if(encoding
== NUM_CODEPAGES
)
1458 /* check if there is a BOM */
1459 if(!rb
->memcmp(p
, "\xff\xfe", 2)) { /* Little endian? */
1460 utf_decode
= rb
->utf16LEdecode
;
1462 } else if(!rb
->memcmp(p
, "\xfe\xff", 2)) { /* Big endian? */
1463 utf_decode
= rb
->utf16BEdecode
;
1465 } else if(!utf_decode
) {
1466 /* If there is no BOM (which is a specification violation),
1467 let's try to guess it. If one of the bytes is 0x00, it is
1468 probably the most significant one. */
1470 utf_decode
= rb
->utf16LEdecode
;
1472 utf_decode
= rb
->utf16BEdecode
;
1475 bytesread
-= (long)p
- (long)tag
;
1478 while ( bytesread
> 0
1479 && lrc_buffer_used
+bytesread
< LRC_BUFFER_SIZE
*2/3
1480 && LRC_BUFFER_SIZE
*2/3 < lrc_buffer_end
)
1482 bool is_crlf
= false;
1483 struct lrc_line
*lrc_line
= alloc_buf(sizeof(struct lrc_line
));
1486 lrc_line
->file_offset
= -1;
1489 /* replace 0x0a and 0x0d with 0x00 */
1492 utf_decode(p
, tmp
, 2);
1494 if(tmp
[0] == 0x0d || tmp
[0] == 0x0a)
1496 if(tmp
[0] == 0x0d && tmp
[1] == 0x0a)
1505 if(encoding
== NUM_CODEPAGES
)
1507 unsigned char* utf8
= utf8line
;
1510 utf8
= utf_decode(p
, utf8
, 1);
1516 size
= rb
->strlen(tag
)+1;
1517 rb
->iso_decode(tag
, utf8line
, encoding
, size
);
1521 if(type
== SYLT
) { /* timestamp */
1522 lrc_line
->time_start
= bytes2int(p
[0], p
[1], p
[2], p
[3]);
1523 lrc_line
->old_time_start
= lrc_line
->time_start
;
1525 utf_decode(p
, tmp
, 1);
1529 lrc_line
->time_start
= 0;
1530 lrc_line
->old_time_start
= -1;
1531 if(is_crlf
) p
+= chsiz
;
1533 bytesread
-= (long)p
- (long)tag
;
1535 if(!add_lrc_line(lrc_line
, utf8line
))
1539 current
.type
= ID3_SYLT
-SYLT
+type
;
1540 rb
->strcpy(current
.lrc_file
, current
.mp3_file
);
1542 current
.loaded_lrc
= true;
1543 calc_brpos(NULL
, 0);
1549 static bool read_id3(void)
1553 if(current
.id3
->codectype
!= AFMT_MPA_L1
1554 && current
.id3
->codectype
!= AFMT_MPA_L2
1555 && current
.id3
->codectype
!= AFMT_MPA_L3
)
1558 fd
= rb
->open(current
.mp3_file
, O_RDONLY
);
1559 if(fd
< 0) return false;
1560 current
.loaded_lrc
= false;
1563 return current
.loaded_lrc
;
1565 #endif /* LRC_SUPPORT_ID3 */
1567 /*******************************
1568 * Display information
1569 *******************************/
1570 static void display_state(void)
1572 const char *str
= NULL
;
1575 str
= "Audio Stopped";
1576 else if (current
.found_lrc
)
1578 if (!current
.loaded_lrc
)
1579 str
= "Loading lrc";
1580 else if (!current
.ll_head
)
1584 #ifdef HAVE_LCD_BITMAP
1585 const char *info
= NULL
;
1587 if (AUDIO_PLAY
&& prefs
.display_title
)
1589 char *title
= (current
.title
? current
.title
: current
.id3
->title
);
1590 char *artist
= (current
.artist
? current
.artist
: current
.id3
->artist
);
1592 if (artist
!= NULL
&& title
!= NULL
)
1594 rb
->snprintf(temp_buf
, MAX_LINE_LEN
, "%s/%s", title
, artist
);
1597 else if (title
!= NULL
)
1599 else if (current
.mp3_file
[0] == '/')
1600 info
= rb
->strrchr(current
.mp3_file
, '/')+1;
1606 struct screen
* display
;
1609 display
= rb
->screens
[i
];
1610 display
->set_viewport(&vp_info
[i
]);
1611 display
->clear_viewport();
1613 display
->puts_scroll(0, 0, info
);
1616 display
->set_viewport(&vp_lyrics
[i
]);
1617 display
->clear_viewport();
1618 display
->getstringsize(str
, &w
, &h
);
1619 if (vp_lyrics
[i
].width
- w
< 0)
1620 display
->puts_scroll(0, vp_lyrics
[i
].height
/font_ui_height
/2,
1623 display
->putsxy((vp_lyrics
[i
].width
- w
)*prefs
.align
/2,
1624 (vp_lyrics
[i
].height
-font_ui_height
)/2, str
);
1625 display
->set_viewport(&vp_info
[i
]);
1627 display
->update_viewport();
1628 display
->set_viewport(NULL
);
1631 /* there is no place to display title or artist. */
1632 rb
->lcd_clear_display();
1634 rb
->lcd_puts_scroll(0, 0, str
);
1636 #endif /* HAVE_LCD_BITMAP */
1639 static void display_time(void)
1641 rb
->snprintf(temp_buf
, MAX_LINE_LEN
, "%ld:%02ld/%ld:%02ld",
1642 current
.elapsed
/60000, (current
.elapsed
/1000)%60,
1643 current
.length
/60000, (current
.length
)/1000%60);
1644 #ifdef HAVE_LCD_BITMAP
1645 int y
= (prefs
.display_title
? font_ui_height
:0);
1648 struct screen
* display
= rb
->screens
[i
];
1649 display
->set_viewport(&vp_info
[i
]);
1650 display
->setfont(FONT_SYSFIXED
);
1651 display
->putsxy(0, y
, temp_buf
);
1652 rb
->gui_scrollbar_draw(display
, 0, y
+SYSFONT_HEIGHT
+1,
1653 vp_info
[i
].width
, SYSFONT_HEIGHT
-2,
1654 current
.length
, 0, current
.elapsed
, HORIZONTAL
);
1655 display
->update_viewport_rect(0, y
, vp_info
[i
].width
, SYSFONT_HEIGHT
*2);
1656 display
->setfont(uifont
);
1657 display
->set_viewport(NULL
);
1660 rb
->lcd_puts(0, 0, temp_buf
);
1662 #endif /* HAVE_LCD_BITMAP */
1665 /*******************************
1667 *******************************/
1668 #ifdef HAVE_LCD_BITMAP
1669 static inline void set_to_default(struct screen
*display
)
1672 #ifdef HAVE_REMOTE_LCD
1673 if (display
->screen_type
!= SCREEN_REMOTE
)
1675 display
->set_foreground(prefs
.active_color
);
1677 display
->set_drawmode(DRMODE_SOLID
);
1679 static inline void set_to_active(struct screen
*display
)
1682 #ifdef HAVE_REMOTE_LCD
1683 if (display
->screen_type
== SCREEN_REMOTE
)
1684 display
->set_drawmode(DRMODE_INVERSEVID
);
1688 display
->set_foreground(prefs
.active_color
);
1689 display
->set_drawmode(DRMODE_SOLID
);
1691 #else /* LCD_DEPTH == 1 */
1692 display
->set_drawmode(DRMODE_INVERSEVID
);
1695 static inline void set_to_inactive(struct screen
*display
)
1698 #ifdef HAVE_REMOTE_LCD
1699 if (display
->screen_type
!= SCREEN_REMOTE
)
1701 display
->set_foreground(prefs
.inactive_color
);
1703 display
->set_drawmode(DRMODE_SOLID
);
1706 static int display_lrc_line(struct lrc_line
*lrc_line
, int ypos
, int i
)
1708 struct screen
*display
= rb
->screens
[i
];
1709 struct lrc_word
*lrc_word
;
1710 struct lrc_brpos
*lrc_brpos
;
1711 long time_start
, time_end
, elapsed
;
1712 int count
, width
, nword
;
1717 time_start
= get_time_start(lrc_line
);
1718 time_end
= get_time_start(lrc_line
->next
);
1719 active_line
= (time_start
<= current
.elapsed
1720 && time_end
> current
.elapsed
);
1722 if (!lrc_line
->width
)
1724 /* empty line. draw bar during interval. */
1725 long rin
= current
.elapsed
- time_start
;
1726 long len
= time_end
- time_start
;
1727 if (current
.wipe
&& active_line
&& len
>= 3500)
1729 elapsed
= rin
* vp_lyrics
[i
].width
/ len
;
1730 set_to_inactive(display
);
1731 display
->fillrect(elapsed
, ypos
+font_ui_height
/4+1,
1732 vp_lyrics
[i
].width
-elapsed
-1, font_ui_height
/2-2);
1733 set_to_active(display
);
1734 display
->drawrect(0, ypos
+font_ui_height
/4,
1735 vp_lyrics
[i
].width
, font_ui_height
/2);
1736 display
->fillrect(1, ypos
+font_ui_height
/4+1,
1737 elapsed
-1, font_ui_height
/2-2);
1738 set_to_default(display
);
1740 return ypos
+ font_ui_height
;
1743 lrc_brpos
= calc_brpos(lrc_line
, i
);
1744 /* initialize line */
1745 xpos
= (vp_lyrics
[i
].width
- lrc_brpos
->width
)*prefs
.align
/2;
1749 active_line
= active_line
|| !prefs
.active_one_line
;
1750 nword
= lrc_line
->nword
-1;
1751 lrc_word
= lrc_line
->words
+ nword
;
1752 str
= lrc_word
->word
;
1753 /* only time_start of first word could be -1 */
1754 if (lrc_word
->time_start
!= -1)
1755 time_end
= get_word_time_start(lrc_word
);
1757 time_end
= time_start
;
1759 time_start
= time_end
;
1761 time_end
= get_word_time_start(lrc_word
-1);
1763 time_end
= get_time_start(lrc_line
->next
);
1765 if (time_start
> current
.elapsed
|| !active_line
)
1770 else if (!current
.wipe
|| time_end
<= current
.elapsed
)
1772 /* active whole word */
1773 elapsed
= lrc_word
->width
;
1778 long rin
= current
.elapsed
- time_start
;
1779 long len
= time_end
- time_start
;
1780 elapsed
= rin
* lrc_word
->width
/ len
;
1783 int word_count
= lrc_word
->count
;
1784 int word_width
= lrc_word
->width
;
1785 set_to_active(display
);
1786 while (word_count
> 0 && word_width
> 0)
1788 int c
= lrc_brpos
->count
- count
;
1789 int w
= lrc_brpos
->width
- width
;
1790 if (c
> word_count
|| w
> word_width
)
1797 set_to_inactive(display
);
1799 else if (elapsed
< w
)
1802 display
->fillrect(xpos
, ypos
, elapsed
, font_ui_height
);
1803 set_to_inactive(display
);
1804 display
->fillrect(xpos
+elapsed
, ypos
,
1805 w
-elapsed
, font_ui_height
);
1807 #ifdef HAVE_REMOTE_LCD
1808 if (display
->screen_type
== SCREEN_REMOTE
)
1809 display
->set_drawmode(DRMODE_INVERSEVID
);
1812 display
->set_drawmode(DRMODE_BG
);
1814 display
->set_drawmode(DRMODE_INVERSEVID
);
1817 rb
->strlcpy(temp_buf
, str
, c
+1);
1818 display
->putsxy(xpos
, ypos
, temp_buf
);
1826 if (count
>= lrc_brpos
->count
|| width
>= lrc_brpos
->width
)
1828 /* prepare for next line */
1830 str
= lrc_skip_space(str
);
1831 xpos
= (vp_lyrics
[i
].width
- lrc_brpos
->width
)*prefs
.align
/2;
1832 ypos
+= font_ui_height
;
1839 set_to_default(display
);
1842 #endif /* HAVE_LCD_BITMAP */
1844 static void display_lrcs(void)
1846 long time_start
, time_end
, rin
, len
;
1847 int nline
[NB_SCREENS
] = {0};
1848 struct lrc_line
*lrc_center
= current
.ll_head
;
1850 if (!lrc_center
) return;
1852 while (get_time_start(lrc_center
->next
) <= current
.elapsed
)
1854 nline
[SCREEN_MAIN
] += lrc_center
->nline
[SCREEN_MAIN
];
1855 #ifdef HAVE_REMOTE_LCD
1856 nline
[SCREEN_REMOTE
] += lrc_center
->nline
[SCREEN_REMOTE
];
1858 lrc_center
= lrc_center
->next
;
1861 time_start
= get_time_start(lrc_center
);
1862 time_end
= get_time_start(lrc_center
->next
);
1863 rin
= current
.elapsed
- time_start
;
1864 len
= time_end
- time_start
;
1866 struct screen
*display
;
1869 display
= rb
->screens
[i
];
1870 /* display current line at the center of the viewport */
1871 display
->set_viewport(&vp_lyrics
[i
]);
1872 display
->clear_viewport();
1873 #ifdef HAVE_LCD_BITMAP
1874 struct lrc_line
*lrc_line
;
1875 int y
, ypos
= 0, nblines
= vp_lyrics
[i
].height
/font_ui_height
;
1879 /* current.elapsed < time of first lrc */
1881 ypos
= (time_start
- current
.elapsed
)
1882 * font_ui_height
/ time_start
;
1889 ypos
= - rin
* lrc_center
->nline
[i
] * font_ui_height
/ len
;
1892 long elapsed
= rin
* lrc_center
->width
/ len
;
1893 struct lrc_brpos
*lrc_brpos
= calc_brpos(lrc_center
, i
);
1894 while (elapsed
> lrc_brpos
->width
)
1896 elapsed
-= lrc_brpos
->width
;
1903 /* find first line to display */
1905 lrc_line
= current
.ll_head
;
1906 while (y
< -lrc_line
->nline
[i
])
1908 y
+= lrc_line
->nline
[i
];
1909 lrc_line
= lrc_line
->next
;
1912 ypos
+= y
*font_ui_height
;
1913 while (lrc_line
&& ypos
< vp_lyrics
[i
].height
)
1915 ypos
= display_lrc_line(lrc_line
, ypos
, i
);
1916 lrc_line
= lrc_line
->next
;
1918 if (!lrc_line
&& ypos
< vp_lyrics
[i
].height
)
1919 display
->putsxy(0, ypos
, "[end]");
1920 #else /* HAVE_LCD_CHARCELLS */
1921 struct lrc_line
*lrc_line
= lrc_center
;
1922 struct lrc_brpos
*lrc_brpos
= calc_brpos(lrc_line
, i
);
1924 const char *str
= get_lrc_str(lrc_line
);
1925 int x
= vp_lyrics
[i
].width
/2, y
= 0;
1927 if (rin
>= 0 && len
> 0)
1929 elapsed
= rin
* lrc_center
->width
/ len
;
1930 while (elapsed
> lrc_brpos
->width
)
1932 elapsed
-= lrc_brpos
->width
;
1933 str
= lrc_skip_space(str
+lrc_brpos
->count
);
1937 rb
->strlcpy(temp_buf
, str
, lrc_brpos
->count
+1);
1941 display
->puts(0, y
, temp_buf
+ rb
->utf8seek(temp_buf
, -x
));
1943 display
->puts(x
, y
, temp_buf
);
1944 x
+= rb
->utf8length(temp_buf
)+1;
1945 lrc_line
= lrc_line
->next
;
1946 if (!lrc_line
&& x
< vp_lyrics
[i
].width
)
1948 if (x
< vp_lyrics
[i
].width
/2)
1949 x
= vp_lyrics
[i
].width
/2;
1950 display
->puts(x
, y
, "[end]");
1952 #endif /* HAVE_LCD_BITMAP */
1953 display
->update_viewport();
1954 display
->set_viewport(NULL
);
1958 /*******************************
1959 * Browse lyrics and edit time.
1960 *******************************/
1961 /* point playing line in lyrics */
1962 static enum themable_icons
get_icon(int selected
, void * data
)
1965 struct lrc_line
*lrc_line
= get_lrc_line(selected
);
1968 long time_start
= get_time_start(lrc_line
);
1969 long time_end
= get_time_start(lrc_line
->next
);
1970 long elapsed
= current
.id3
->elapsed
;
1971 if (time_start
<= elapsed
&& time_end
> elapsed
)
1976 static const char *get_lrc_timeline(int selected
, void *data
,
1977 char *buffer
, size_t buffer_len
)
1980 struct lrc_line
*lrc_line
= get_lrc_line(selected
);
1983 format_time_tag(temp_buf
, get_time_start(lrc_line
));
1984 rb
->snprintf(buffer
, buffer_len
, "[%s]%s",
1985 temp_buf
, get_lrc_str(lrc_line
));
1991 static void save_changes(void)
1993 char new_file
[MAX_PATH
], *p
;
1994 bool success
= false;
1996 if (!current
.changed_lrc
)
1998 rb
->splash(HZ
/2, "Saving changes...");
1999 if (current
.type
== TXT
|| current
.type
> NUM_TYPES
)
2001 /* save changes to new .lrc file */
2002 rb
->strcpy(new_file
, current
.lrc_file
);
2003 p
= rb
->strrchr(new_file
, '.');
2004 rb
->strcpy(p
, extentions
[LRC
]);
2008 /* file already exists. use temp file. */
2009 rb
->snprintf(new_file
, MAX_PATH
, "%s~", current
.lrc_file
);
2011 fd
= rb
->creat(new_file
, 0666);
2012 fe
= rb
->open(current
.lrc_file
, O_RDONLY
);
2013 if (fd
>= 0 && fe
>= 0)
2015 struct lrc_line
*lrc_line
, *temp_lrc
;
2016 off_t curr
= 0, next
= 0, size
= 0, offset
= 0;
2017 for (lrc_line
= current
.ll_head
; lrc_line
;
2018 lrc_line
= lrc_line
->next
)
2020 /* apply offset and set old_time_start -1 to indicate
2021 that time tag is not saved yet. */
2022 lrc_line
->time_start
= get_time_start(lrc_line
);
2023 lrc_line
->old_time_start
= -1;
2026 if (current
.type
> NUM_TYPES
)
2029 rb
->write(fd
, BOM
, BOM_SIZE
);
2032 size
= rb
->filesize(fe
);
2035 /* find offset of next tag */
2037 for (temp_lrc
= current
.ll_head
, next
= size
;
2038 temp_lrc
; temp_lrc
= temp_lrc
->next
)
2040 offset
= temp_lrc
->file_offset
;
2041 if (offset
< next
&& temp_lrc
->old_time_start
== -1)
2043 lrc_line
= temp_lrc
;
2045 if (offset
<= curr
) break;
2048 offset
= current
.offset_file_offset
;
2049 if (offset
>= 0 && offset
< next
)
2053 current
.offset_file_offset
= -1;
2057 if (curr
== -1) curr
= 0;
2058 /* copy before the next tag */
2061 ssize_t count
= next
-curr
;
2062 if (count
> MAX_LINE_LEN
)
2063 count
= MAX_LINE_LEN
;
2064 if (rb
->read(fe
, temp_buf
, count
)!=count
)
2066 rb
->write(fd
, temp_buf
, count
);
2069 if (curr
< next
|| curr
>= size
) break;
2071 /* write tag to new file and skip tag in backup */
2072 if (lrc_line
!= NULL
)
2074 lrc_line
->file_offset
= rb
->lseek(fd
, 0, SEEK_CUR
);
2075 lrc_line
->old_time_start
= lrc_line
->time_start
;
2076 long t
= lrc_line
->time_start
;
2077 if (current
.type
== SNC
)
2079 rb
->fdprintf(fd
, "%02ld%02ld%02ld%02ld", (t
/3600000)%100,
2080 (t
/60000)%60, (t
/1000)%60, (t
/10)%100);
2082 curr
+= rb
->read(fe
, temp_buf
, 8);
2084 else /* LRC || LRC8 */
2086 format_time_tag(temp_buf
, t
);
2087 rb
->fdprintf(fd
, "[%s]", temp_buf
);
2091 rb
->fdprintf(fd
, "%s\n", get_lrc_str(lrc_line
));
2094 if (current
.type
== LRC
|| current
.type
== LRC8
)
2096 /* skip both time tag and offset tag */
2097 while (curr
++<size
&& rb
->read(fe
, temp_buf
, 1)==1)
2098 if (temp_buf
[0]==']') break;
2101 success
= (curr
>=size
);
2103 if (fe
>= 0) rb
->close(fe
);
2104 if (fd
>= 0) rb
->close(fd
);
2108 if (current
.type
== TXT
|| current
.type
> NUM_TYPES
)
2111 rb
->strcpy(current
.lrc_file
, new_file
);
2115 rb
->remove(current
.lrc_file
);
2116 rb
->rename(new_file
, current
.lrc_file
);
2121 rb
->remove(new_file
);
2122 rb
->splash(HZ
, "Could not save changes.");
2124 rb
->reload_directory();
2125 current
.changed_lrc
= false;
2127 static int timetag_editor(void)
2129 struct gui_synclist gui_editor
;
2130 struct lrc_line
*lrc_line
;
2132 int button
, idx
, selected
= 0;
2134 if (current
.id3
== NULL
|| !current
.ll_head
)
2136 rb
->splash(HZ
, "No lyrics");
2137 return LRC_GOTO_MAIN
;
2140 get_lrc_line(-1); /* initialize static variables */
2142 for (lrc_line
= current
.ll_head
, idx
= 0;
2143 lrc_line
; lrc_line
= lrc_line
->next
, idx
++)
2145 long time_start
= get_time_start(lrc_line
);
2146 long time_end
= get_time_start(lrc_line
->next
);
2147 long elapsed
= current
.id3
->elapsed
;
2148 if (time_start
<= elapsed
&& time_end
> elapsed
)
2152 rb
->gui_synclist_init(&gui_editor
, &get_lrc_timeline
, NULL
, false, 1, NULL
);
2153 rb
->gui_synclist_set_nb_items(&gui_editor
, current
.nlrcline
);
2154 rb
->gui_synclist_set_icon_callback(&gui_editor
, get_icon
);
2155 rb
->gui_synclist_set_title(&gui_editor
, "Timetag Editor",
2156 Icon_Menu_functioncall
);
2157 rb
->gui_synclist_select_item(&gui_editor
, selected
);
2158 rb
->gui_synclist_draw(&gui_editor
);
2162 button
= rb
->get_action(CONTEXT_TREE
, TIMEOUT_BLOCK
);
2163 if (rb
->gui_synclist_do_button(&gui_editor
, &button
,
2164 LIST_WRAP_UNLESS_HELD
))
2170 idx
= rb
->gui_synclist_get_sel_pos(&gui_editor
);
2171 lrc_line
= get_lrc_line(idx
);
2174 set_time_start(lrc_line
, current
.id3
->elapsed
-500);
2175 rb
->gui_synclist_draw(&gui_editor
);
2178 case ACTION_STD_CONTEXT
:
2179 idx
= rb
->gui_synclist_get_sel_pos(&gui_editor
);
2180 lrc_line
= get_lrc_line(idx
);
2183 long temp_time
= get_time_start(lrc_line
);
2184 if (lrc_set_time(get_lrc_str(lrc_line
), NULL
,
2185 &temp_time
, 10, 0, current
.length
,
2186 LST_SET_MSEC
|LST_SET_SEC
|LST_SET_MIN
) == 1)
2187 return PLUGIN_USB_CONNECTED
;
2188 set_time_start(lrc_line
, temp_time
);
2189 rb
->gui_synclist_draw(&gui_editor
);
2192 case ACTION_TREE_STOP
:
2193 case ACTION_STD_CANCEL
:
2197 if (rb
->default_event_handler(button
) == SYS_USB_CONNECTED
)
2198 return PLUGIN_USB_CONNECTED
;
2204 rb
->screens
[idx
]->stop_scroll();
2206 if (current
.changed_lrc
)
2208 MENUITEM_STRINGLIST(save_menu
, "Save Changes?", NULL
,
2209 "Yes", "No (save later)", "Discard All Changes")
2214 switch (rb
->do_menu(&save_menu
, &button
, NULL
, false))
2229 case MENU_ATTACHED_USB
:
2230 return PLUGIN_USB_CONNECTED
;
2234 return LRC_GOTO_MAIN
;
2237 /*******************************
2239 *******************************/
2240 static void load_or_save_settings(bool save
)
2242 static const char config_file
[] = "lrcplayer.cfg";
2243 static struct configdata config
[] = {
2244 #ifdef HAVE_LCD_COLOR
2245 { TYPE_INT
, 0, 0xffffff, { .int_p
= &prefs
.inactive_color
},
2246 "inactive color", NULL
},
2248 #ifdef HAVE_LCD_BITMAP
2249 { TYPE_BOOL
, 0, 1, { .bool_p
= &prefs
.wrap
}, "wrap", NULL
},
2250 { TYPE_BOOL
, 0, 1, { .bool_p
= &prefs
.wipe
}, "wipe", NULL
},
2251 { TYPE_BOOL
, 0, 1, { .bool_p
= &prefs
.active_one_line
},
2252 "active one line", NULL
},
2253 { TYPE_INT
, 0, 2, { .int_p
= &prefs
.align
}, "align", NULL
},
2254 { TYPE_BOOL
, 0, 1, { .bool_p
= &prefs
.statusbar_on
},
2255 "statusbar on", NULL
},
2256 { TYPE_BOOL
, 0, 1, { .bool_p
= &prefs
.display_title
},
2257 "display title", NULL
},
2259 { TYPE_BOOL
, 0, 1, { .bool_p
= &prefs
.display_time
},
2260 "display time", NULL
},
2261 { TYPE_BOOL
, 0, 1, { .bool_p
= &prefs
.backlight_on
},
2262 "backlight on", NULL
},
2264 { TYPE_STRING
, 0, sizeof(prefs
.lrc_directory
),
2265 { .string
= prefs
.lrc_directory
}, "lrc directory", NULL
},
2266 { TYPE_INT
, -1, NUM_CODEPAGES
-1, { .int_p
= &prefs
.encoding
},
2268 #ifdef LRC_SUPPORT_ID3
2269 { TYPE_BOOL
, 0, 1, { .bool_p
= &prefs
.read_id3
}, "read id3", NULL
},
2275 /* initialize setting */
2277 prefs
.active_color
= rb
->lcd_get_foreground();
2278 prefs
.inactive_color
= LCD_LIGHTGRAY
;
2280 #ifdef HAVE_LCD_BITMAP
2283 prefs
.active_one_line
= false;
2284 prefs
.align
= 1; /* center */
2285 prefs
.statusbar_on
= false;
2286 prefs
.display_title
= true;
2288 prefs
.display_time
= true;
2289 prefs
.backlight_on
= false;
2290 #ifdef LRC_SUPPORT_ID3
2291 prefs
.read_id3
= true;
2293 rb
->strcpy(prefs
.lrc_directory
, "/Lyrics");
2294 prefs
.encoding
= -1; /* default codepage */
2296 configfile_load(config_file
, config
, ARRAYLEN(config
), 0);
2298 else if (rb
->memcmp(&old_prefs
, &prefs
, sizeof(prefs
)))
2300 rb
->splash(0, "Saving Settings");
2301 configfile_save(config_file
, config
, ARRAYLEN(config
), 0);
2303 rb
->memcpy(&old_prefs
, &prefs
, sizeof(prefs
));
2306 static bool lrc_theme_menu(void)
2309 #ifdef HAVE_LCD_BITMAP
2311 LRC_MENU_DISP_TITLE
,
2314 #ifdef HAVE_LCD_COLOR
2315 LRC_MENU_INACTIVE_COLOR
,
2321 bool exit
= false, usb
= false;
2323 MENUITEM_STRINGLIST(menu
, "Theme Settings", NULL
,
2324 #ifdef HAVE_LCD_BITMAP
2325 "Show Statusbar", "Display Title",
2328 #ifdef HAVE_LCD_COLOR
2331 "Backlight Always On");
2333 while (!exit
&& !usb
)
2335 switch (rb
->do_menu(&menu
, &selected
, NULL
, false))
2337 #ifdef HAVE_LCD_BITMAP
2338 case LRC_MENU_STATUSBAR
:
2339 usb
= rb
->set_bool("Show Statusbar", &prefs
.statusbar_on
);
2341 case LRC_MENU_DISP_TITLE
:
2342 usb
= rb
->set_bool("Display Title", &prefs
.display_title
);
2345 case LRC_MENU_DISP_TIME
:
2346 usb
= rb
->set_bool("Display Time", &prefs
.display_time
);
2348 #ifdef HAVE_LCD_COLOR
2349 case LRC_MENU_INACTIVE_COLOR
:
2350 usb
= rb
->set_color(NULL
, "Inactive Colour",
2351 &prefs
.inactive_color
, -1);
2354 case LRC_MENU_BACKLIGHT
:
2355 usb
= rb
->set_bool("Backlight Always On", &prefs
.backlight_on
);
2357 case MENU_ATTACHED_USB
:
2369 #ifdef HAVE_LCD_BITMAP
2370 static bool lrc_display_menu(void)
2380 bool exit
= false, usb
= false;
2382 MENUITEM_STRINGLIST(menu
, "Display Settings", NULL
,
2383 "Wrap", "Wipe", "Alignment",
2384 "Activate Only Current Line");
2386 struct opt_items align_names
[] = {
2387 {"Left", -1}, {"Centre", -1}, {"Right", -1},
2390 while (!exit
&& !usb
)
2392 switch (rb
->do_menu(&menu
, &selected
, NULL
, false))
2395 usb
= rb
->set_bool("Wrap", &prefs
.wrap
);
2398 usb
= rb
->set_bool("Wipe", &prefs
.wipe
);
2400 case LRC_MENU_ALIGN
:
2401 usb
= rb
->set_option("Alignment", &prefs
.align
, INT
,
2402 align_names
, 3, NULL
);
2404 case LRC_MENU_LINE_MODE
:
2405 usb
= rb
->set_bool("Activate Only Current Line",
2406 &prefs
.active_one_line
);
2408 case MENU_ATTACHED_USB
:
2419 #endif /* HAVE_LCD_BITMAP */
2421 static bool lrc_lyrics_menu(void)
2425 #ifdef LRC_SUPPORT_ID3
2432 bool exit
= false, usb
= false;
2434 struct opt_items cp_names
[NUM_CODEPAGES
+1];
2437 MENUITEM_STRINGLIST(menu
, "Lyrics Settings", NULL
,
2439 #ifdef LRC_SUPPORT_ID3
2444 cp_names
[0].string
= "Use default codepage";
2445 cp_names
[0].voice_id
= -1;
2446 for (old_val
= 1; old_val
< NUM_CODEPAGES
+1; old_val
++)
2448 cp_names
[old_val
].string
= rb
->get_codepage_name(old_val
-1);
2449 cp_names
[old_val
].voice_id
= -1;
2452 while (!exit
&& !usb
)
2454 switch (rb
->do_menu(&menu
, &selected
, NULL
, false))
2456 case LRC_MENU_ENCODING
:
2458 old_val
= prefs
.encoding
;
2459 usb
= rb
->set_option("Encoding", &prefs
.encoding
, INT
,
2460 cp_names
, NUM_CODEPAGES
+1, NULL
);
2461 if (prefs
.encoding
!= old_val
)
2464 if (current
.type
< NUM_TYPES
)
2466 /* let reload lrc file to apply encoding setting */
2467 reset_current_data();
2472 #ifdef LRC_SUPPORT_ID3
2473 case LRC_MENU_READ_ID3
:
2474 usb
= rb
->set_bool("Read ID3 tag", &prefs
.read_id3
);
2477 case LRC_MENU_LRC_DIR
:
2478 rb
->strcpy(temp_buf
, prefs
.lrc_directory
);
2479 if (!rb
->kbd_input(temp_buf
, sizeof(prefs
.lrc_directory
)))
2480 rb
->strcpy(prefs
.lrc_directory
, temp_buf
);
2482 case MENU_ATTACHED_USB
:
2495 static const char* lrc_debug_data(int selected
, void * data
,
2496 char * buffer
, size_t buffer_len
)
2502 rb
->strlcpy(buffer
, current
.mp3_file
, buffer_len
);
2505 rb
->strlcpy(buffer
, current
.lrc_file
, buffer_len
);
2508 rb
->snprintf(buffer
, buffer_len
, "buf usage: %d,%d/%d",
2509 (int)lrc_buffer_used
, (int)lrc_buffer_end
,
2510 (int)lrc_buffer_size
);
2513 rb
->snprintf(buffer
, buffer_len
, "line count: %d,%d",
2514 current
.nlrcline
, current
.nlrcbrpos
);
2517 rb
->snprintf(buffer
, buffer_len
, "loaded lrc? %s",
2518 current
.loaded_lrc
?"yes":"no");
2521 rb
->snprintf(buffer
, buffer_len
, "too many lines? %s",
2522 current
.too_many_lines
?"yes":"no");
2530 static bool lrc_debug_menu(void)
2532 struct simplelist_info info
;
2533 rb
->simplelist_info_init(&info
, "Debug Menu", 6, NULL
);
2534 info
.hide_selection
= true;
2535 info
.scroll_all
= true;
2536 info
.get_name
= lrc_debug_data
;
2537 return rb
->simplelist_show_list(&info
);
2541 /* returns one of enum lrc_screen or enum plugin_status */
2542 static int lrc_menu(void)
2546 #ifdef HAVE_LCD_BITMAP
2555 LRC_MENU_TIMETAG_EDITOR
,
2559 MENUITEM_STRINGLIST(menu
, "Lrcplayer Menu", NULL
,
2561 #ifdef HAVE_LCD_BITMAP
2569 "Time Offset", "Timetag Editor",
2571 int selected
= 0, ret
= LRC_GOTO_MENU
;
2574 while (ret
== LRC_GOTO_MENU
)
2576 switch (rb
->do_menu(&menu
, &selected
, NULL
, false))
2578 case LRC_MENU_THEME
:
2579 usb
= lrc_theme_menu();
2581 #ifdef HAVE_LCD_BITMAP
2582 case LRC_MENU_DISPLAY
:
2583 usb
= lrc_display_menu();
2586 case LRC_MENU_LYRICS
:
2587 usb
= lrc_lyrics_menu();
2589 case LRC_MENU_PLAYBACK
:
2590 usb
= playback_control(NULL
);
2591 ret
= LRC_GOTO_MAIN
;
2594 case LRC_MENU_DEBUG
:
2595 usb
= lrc_debug_menu();
2596 ret
= LRC_GOTO_MAIN
;
2599 case LRC_MENU_OFFSET
:
2600 usb
= (lrc_set_time("Time Offset", "sec", ¤t
.offset
,
2601 10, -60*1000, 60*1000,
2602 LST_SET_MSEC
|LST_SET_SEC
) == 1);
2603 ret
= LRC_GOTO_MAIN
;
2605 case LRC_MENU_TIMETAG_EDITOR
:
2606 ret
= LRC_GOTO_EDITOR
;
2611 case MENU_ATTACHED_USB
:
2615 ret
= LRC_GOTO_MAIN
;
2619 ret
= PLUGIN_USB_CONNECTED
;
2624 /*******************************
2626 *******************************/
2627 /* returns true if song has changed to know when to load new lyrics. */
2628 static bool check_audio_status(void)
2630 static int last_audio_status
= 0;
2631 if (current
.ff_rewind
== -1)
2632 current
.audio_status
= rb
->audio_status();
2633 current
.id3
= rb
->audio_current_track();
2634 if ((last_audio_status
^current
.audio_status
)&AUDIO_STATUS_PLAY
)
2636 last_audio_status
= current
.audio_status
;
2639 if (AUDIO_STOP
|| current
.id3
== NULL
)
2641 if (rb
->strcmp(current
.mp3_file
, current
.id3
->path
))
2647 static void ff_rewind(long time
, bool resume
)
2656 rb
->audio_ff_rewind(time
);
2657 rb
->sleep(HZ
/10); /* take affect seeking */
2663 static int handle_button(void)
2665 int ret
= LRC_GOTO_MAIN
;
2666 static int step
= 0;
2667 int limit
, button
= rb
->get_action(CONTEXT_WPS
, HZ
/10);
2670 case ACTION_WPS_BROWSE
:
2671 #if CONFIG_KEYPAD == ONDIO_PAD
2672 /* ondio doesn't have ACTION_WPS_MENU,
2673 so use ACTION_WPS_BROWSE for menu */
2674 ret
= LRC_GOTO_MENU
;
2677 case ACTION_WPS_STOP
:
2681 case ACTION_WPS_PLAY
:
2682 if (AUDIO_STOP
&& rb
->global_status
->resume_index
!= -1)
2684 if (rb
->playlist_resume() != -1)
2686 rb
->playlist_start(rb
->global_status
->resume_index
,
2687 rb
->global_status
->resume_offset
);
2690 else if (AUDIO_PAUSE
)
2695 case ACTION_WPS_SEEKFWD
:
2696 case ACTION_WPS_SEEKBACK
:
2699 if (current
.ff_rewind
> -1)
2701 if (button
== ACTION_WPS_SEEKFWD
)
2702 /* fast forwarding, calc max step relative to end */
2703 limit
= (current
.length
- current
.ff_rewind
) * 3 / 100;
2705 /* rewinding, calc max step relative to start */
2706 limit
= (current
.ff_rewind
) * 3 / 100;
2707 limit
= MAX(limit
, 500);
2712 if (button
== ACTION_WPS_SEEKFWD
)
2713 current
.ff_rewind
+= step
;
2715 current
.ff_rewind
-= step
;
2717 if (current
.ff_rewind
> current
.length
-100)
2718 current
.ff_rewind
= current
.length
-100;
2719 if (current
.ff_rewind
< 0)
2720 current
.ff_rewind
= 0;
2722 /* smooth seeking by multiplying step by: 1 + (2 ^ -accel) */
2723 step
+= step
>> (rb
->global_settings
->ff_rewind_accel
+ 3);
2727 current
.ff_rewind
= current
.elapsed
;
2730 step
= 1000 * rb
->global_settings
->ff_rewind_min_step
;
2733 case ACTION_WPS_STOPSEEK
:
2734 if (current
.ff_rewind
== -1)
2736 ff_rewind(current
.ff_rewind
, !AUDIO_PAUSE
);
2737 current
.elapsed
= current
.ff_rewind
;
2738 current
.ff_rewind
= -1;
2740 case ACTION_WPS_SKIPNEXT
:
2743 case ACTION_WPS_SKIPPREV
:
2744 if (current
.elapsed
< 3000)
2747 ff_rewind(0, false);
2749 case ACTION_WPS_VOLDOWN
:
2750 limit
= rb
->sound_min(SOUND_VOLUME
);
2751 if (--rb
->global_settings
->volume
< limit
)
2752 rb
->global_settings
->volume
= limit
;
2753 rb
->sound_set(SOUND_VOLUME
, rb
->global_settings
->volume
);
2755 case ACTION_WPS_VOLUP
:
2756 limit
= rb
->sound_max(SOUND_VOLUME
);
2757 if (++rb
->global_settings
->volume
> limit
)
2758 rb
->global_settings
->volume
= limit
;
2759 rb
->sound_set(SOUND_VOLUME
, rb
->global_settings
->volume
);
2761 case ACTION_WPS_CONTEXT
:
2762 ret
= LRC_GOTO_EDITOR
;
2764 case ACTION_WPS_MENU
:
2765 ret
= LRC_GOTO_MENU
;
2768 if(rb
->default_event_handler(button
) == SYS_USB_CONNECTED
)
2769 ret
= PLUGIN_USB_CONNECTED
;
2775 static int lrc_main(void)
2777 int ret
= LRC_GOTO_MAIN
;
2778 long id3_timeout
= 0;
2779 bool update_display_state
= true;
2781 #ifdef HAVE_LCD_BITMAP
2782 /* y offset of vp_lyrics */
2783 int h
= (prefs
.display_title
?font_ui_height
:0)+
2784 (prefs
.display_time
?SYSFONT_HEIGHT
*2:0);
2790 #ifdef HAVE_LCD_BITMAP
2791 rb
->viewportmanager_theme_enable(i
, prefs
.statusbar_on
, &vp_info
[i
]);
2792 vp_lyrics
[i
] = vp_info
[i
];
2793 vp_lyrics
[i
].flags
&= ~VP_FLAG_ALIGNMENT_MASK
;
2794 vp_lyrics
[i
].y
+= h
;
2795 vp_lyrics
[i
].height
-= h
;
2797 rb
->viewport_set_defaults(&vp_lyrics
[i
], i
);
2798 if (prefs
.display_time
)
2800 vp_lyrics
[i
].y
+= 1; /* time */
2801 vp_lyrics
[i
].height
-= 1;
2806 if (prefs
.backlight_on
)
2807 backlight_ignore_timeout();
2809 #ifdef HAVE_LCD_BITMAP
2810 /* in case settings that may affect break position
2811 * are changed (statusbar_on and wrap). */
2812 if (!current
.too_many_lines
)
2813 calc_brpos(NULL
, 0);
2816 while (ret
== LRC_GOTO_MAIN
)
2818 if (check_audio_status())
2820 update_display_state
= true;
2826 else if (rb
->strcmp(current
.mp3_file
, current
.id3
->path
))
2829 reset_current_data();
2830 rb
->strcpy(current
.mp3_file
, current
.id3
->path
);
2831 id3_timeout
= *rb
->current_tick
+HZ
*3;
2832 current
.found_lrc
= false;
2835 if (current
.id3
&& current
.id3
->length
)
2837 if (current
.ff_rewind
== -1)
2839 long di
= current
.id3
->elapsed
- current
.elapsed
;
2840 if (di
< -250 || di
> 0)
2841 current
.elapsed
= current
.id3
->elapsed
;
2844 current
.elapsed
= current
.ff_rewind
;
2845 current
.length
= current
.id3
->length
;
2846 if (current
.elapsed
> current
.length
)
2847 current
.elapsed
= current
.length
;
2851 current
.elapsed
= 0;
2855 if (current
.id3
&& id3_timeout
&&
2856 (TIME_AFTER(*rb
->current_tick
, id3_timeout
) ||
2857 current
.id3
->artist
))
2859 update_display_state
= true;
2862 current
.found_lrc
= find_lrc_file();
2863 #ifdef LRC_SUPPORT_ID3
2864 if (!current
.found_lrc
&& prefs
.read_id3
)
2866 /* no lyrics file found. try to read from id3 tag. */
2867 current
.found_lrc
= read_id3();
2871 else if (current
.found_lrc
&& !current
.loaded_lrc
)
2873 /* current.loaded_lrc is false after changing encode setting */
2874 update_display_state
= true;
2878 if (update_display_state
)
2880 #ifdef HAVE_LCD_BITMAP
2881 if (current
.type
== TXT
|| current
.type
== ID3_USLT
)
2882 current
.wipe
= false;
2884 current
.wipe
= prefs
.wipe
;
2887 update_display_state
= false;
2891 if (prefs
.display_time
)
2897 ret
= handle_button();
2900 #ifdef HAVE_LCD_BITMAP
2902 rb
->viewportmanager_theme_undo(i
, false);
2904 if (prefs
.backlight_on
)
2905 backlight_use_settings();
2910 /* this is the plugin entry point */
2911 enum plugin_status
plugin_start(const void* parameter
)
2913 int ret
= LRC_GOTO_MAIN
;
2915 /* initialize settings. */
2916 load_or_save_settings(false);
2918 #ifdef HAVE_LCD_BITMAP
2919 uifont
= rb
->screens
[0]->getuifont();
2920 font_ui_height
= rb
->font_get(uifont
)->height
;
2923 lrc_buffer
= rb
->plugin_get_buffer(&lrc_buffer_size
);
2924 lrc_buffer
= (void *)(((long)lrc_buffer
+3)&~3); /* 4 bytes aligned */
2925 lrc_buffer_size
= (lrc_buffer_size
- 4)&~3;
2927 reset_current_data();
2929 current
.mp3_file
[0] = 0;
2930 current
.lrc_file
[0] = 0;
2931 current
.ff_rewind
= -1;
2932 current
.found_lrc
= false;
2933 if (parameter
&& check_audio_status())
2936 rb
->strcpy(current
.mp3_file
, current
.id3
->path
);
2937 /* use passed parameter as lrc file. */
2938 rb
->strcpy(current
.lrc_file
, parameter
);
2939 if (!rb
->file_exists(current
.lrc_file
))
2941 rb
->splash(HZ
, "Specified file dose not exist.");
2942 return PLUGIN_ERROR
;
2944 ext
= rb
->strrchr(current
.lrc_file
, '.');
2945 if (!ext
) ext
= current
.lrc_file
;
2946 for (current
.type
= 0; current
.type
< NUM_TYPES
; current
.type
++)
2948 if (!rb
->strcasecmp(ext
, extentions
[current
.type
]))
2951 if (current
.type
== NUM_TYPES
)
2953 rb
->splashf(HZ
, "%s is not supported", ext
);
2954 return PLUGIN_ERROR
;
2956 current
.found_lrc
= true;
2959 while (ret
>= PLUGIN_OTHER
)
2969 case LRC_GOTO_EDITOR
:
2970 ret
= timetag_editor();
2978 load_or_save_settings(true);