Index: apps/plugins/sncviewer.c =================================================================== --- apps/plugins/sncviewer.c (revision 0) +++ apps/plugins/sncviewer.c (revision 0) @@ -0,0 +1,2570 @@ +#include "plugin.h" +#include "pluginlib_actions.h" + +PLUGIN_HEADER + +#define IPOD_PAD (CONFIG_KEYPAD == IPOD_1G2G_PAD) ||\ + (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) + +#if IPOD_PAD || (CONFIG_KEYPAD == SANSA_E200_PAD) + #define INC - + #define DEC + +#else + #define INC + + #define DEC - +#endif + +#if IPOD_PAD + #define SNC_DOWN_HOLD PLA_UP_REPEAT + #define SNC_DOWN_REL PLA_UP + #define SNC_UP_HOLD PLA_DOWN_REPEAT + #define SNC_UP_REL PLA_DOWN +#else + #define SNC_DOWN_HOLD PLA_DOWN_REPEAT + #define SNC_DOWN_REL PLA_DOWN + #define SNC_UP_HOLD PLA_UP_REPEAT + #define SNC_UP_REL PLA_UP +#endif + +#define SNC_VISUAL_REW PLA_LEFT_REPEAT +#define SNC_LEFT PLA_LEFT +#define SNC_VISUAL_FF PLA_RIGHT_REPEAT +#define SNC_RIGHT PLA_RIGHT + +enum{SNC_SELECT_OR_MODE=LAST_PLUGINLIB_ACTION+1,SNC_OPEN_MENU, + SNC_PREV,SNC_AUDIO_REW, + SNC_NEXT,SNC_AUDIO_FF, + SNC_QUIT,SNC_PLAY_PAUSE, + SNC_SAVE,SNC_BACKLIGHT_OR_STAMPTIME, + SNC_AB,SNC_AB_MENU}; + +const struct button_mapping button_context_snc[] = { + /* common to all players */ + { SNC_AUDIO_REW, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT|BUTTON_REPEAT }, + { SNC_PREV, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT }, + { SNC_AUDIO_FF, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT|BUTTON_REPEAT }, + { SNC_NEXT, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT }, + +#if CONFIG_KEYPAD == IRIVER_H10_PAD + { SNC_OPEN_MENU, BUTTON_REW|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_SELECT_OR_MODE, BUTTON_REW|BUTTON_REL, BUTTON_REW }, + { SNC_SAVE, BUTTON_FF|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_BACKLIGHT_OR_STAMPTIME, BUTTON_FF|BUTTON_REL, BUTTON_FF }, + { SNC_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER }, + { SNC_PLAY_PAUSE, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY }, +#else + { SNC_OPEN_MENU, BUTTON_SELECT|BUTTON_REPEAT,BUTTON_NONE }, + { SNC_SELECT_OR_MODE,BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT }, + + #if IPOD_PAD + { SNC_SAVE, BUTTON_MENU|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_BACKLIGHT_OR_STAMPTIME, BUTTON_MENU|BUTTON_REL, BUTTON_MENU }, + { SNC_QUIT, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_QUIT, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_PLAY }, + { SNC_PLAY_PAUSE,BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY }, + #elif CONFIG_KEYPAD == GIGABEAT_PAD + { SNC_SAVE, BUTTON_MENU|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_BACKLIGHT_OR_STAMPTIME, BUTTON_MENU|BUTTON_REL, BUTTON_MENU }, + { SNC_QUIT, BUTTON_POWER|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_PLAY_PAUSE,BUTTON_POWER|BUTTON_REL, BUTTON_POWER }, + #define HAS_AB_BUTTON + { SNC_AB_MENU, BUTTON_A|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_AB, BUTTON_A|BUTTON_REL, BUTTON_A }, + #elif CONFIG_KEYPAD == SANSA_E200_PAD + { SNC_SAVE, BUTTON_REC|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_BACKLIGHT_OR_STAMPTIME, BUTTON_REC|BUTTON_REL, BUTTON_REC }, + { SNC_QUIT, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_QUIT, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP }, + { SNC_PLAY_PAUSE,BUTTON_UP|BUTTON_REL, BUTTON_UP }, + #define HAS_AB_BUTTON + { SNC_AB_MENU, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_AB, BUTTON_DOWN|BUTTON_REL, BUTTON_DOWN }, + #else /* iaudio x5, iriver h100/h300 */ + { SNC_SAVE, BUTTON_REC|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_BACKLIGHT_OR_STAMPTIME, BUTTON_REC|BUTTON_REL, BUTTON_REC }, + #if CONFIG_KEYPAD == IAUDIO_X5M5_PAD + { SNC_QUIT, BUTTON_PLAY|BUTTON_REPEAT,BUTTON_NONE }, + { SNC_PLAY_PAUSE,BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY }, + #else /* iriver h100/h300 */ + { SNC_QUIT, BUTTON_OFF|BUTTON_REL, BUTTON_NONE }, + { SNC_PLAY_PAUSE,BUTTON_ON|BUTTON_REL, BUTTON_ON }, + #define HAS_AB_BUTTON + { SNC_AB_MENU, BUTTON_MODE|BUTTON_REPEAT,BUTTON_NONE }, + { SNC_AB, BUTTON_MODE|BUTTON_REL, BUTTON_MODE }, + #endif + #endif /* else */ +#endif /* if h10 */ + {CONTEXT_CUSTOM, BUTTON_NONE, BUTTON_NONE} +}; +#ifdef HAVE_REMOTE_LCD +const struct button_mapping button_context_remote_snc[] = { + /* REMOTE */ + { SNC_DOWN_HOLD, BUTTON_RC_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_DOWN_REL, BUTTON_RC_VOL_DOWN, BUTTON_RC_VOL_DOWN }, + { SNC_UP_HOLD, BUTTON_RC_VOL_UP|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_UP_REL, BUTTON_RC_VOL_UP, BUTTON_RC_VOL_UP }, + { SNC_VISUAL_REW, BUTTON_RC_REW|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_AUDIO_REW, BUTTON_RC_REW|BUTTON_REL, BUTTON_RC_REW|BUTTON_REPEAT }, + { SNC_PREV, BUTTON_RC_REW|BUTTON_REL, BUTTON_RC_REW }, + { SNC_LEFT, BUTTON_RC_REW, BUTTON_NONE }, + { SNC_VISUAL_FF, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_AUDIO_FF, BUTTON_RC_FF|BUTTON_REL, BUTTON_RC_FF|BUTTON_REPEAT }, + { SNC_NEXT, BUTTON_RC_FF|BUTTON_REL, BUTTON_RC_FF }, + { SNC_RIGHT, BUTTON_RC_FF, BUTTON_NONE }, + { SNC_OPEN_MENU, BUTTON_RC_MENU|BUTTON_REPEAT, BUTTON_NONE }, + #if(CONFIG_KEYPAD == IRIVER_H100_PAD) || (CONFIG_KEYPAD == IRIVER_H300_PAD) + { SNC_QUIT, BUTTON_RC_STOP|BUTTON_REL, BUTTON_NONE }, + { SNC_PLAY_PAUSE, BUTTON_RC_ON|BUTTON_REL, BUTTON_RC_ON }, + #else + { SNC_QUIT, BUTTON_RC_PLAY|BUTTON_REPEAT, BUTTON_NONE }, + { SNC_PLAY_PAUSE, BUTTON_RC_PLAY|BUTTON_REL, BUTTON_RC_PLAY }, + #endif + {CONTEXT_CUSTOM,BUTTON_NONE,BUTTON_NONE} +}; +#endif /* remote */ + +#ifdef HAVE_REMOTE_LCD + #define NB_ACTION_CONTEXTS 3 +#else + #define NB_ACTION_CONTEXTS 2 +#endif + +const struct button_mapping *plugin_contexts[] = + {button_context_snc, generic_directions +#ifdef HAVE_REMOTE_LCD + ,button_context_remote_snc +#endif + }; + +#define BUFFERSIZE 512 +#define MAX_ROWS 8 /* max 7 lines [0]line1[1]line2[2]...line7[7] */ + +#define MAX_SECTIONS 300 /* section: time lyrics */ +#define NOT_INIT -1 +#define STATUS_ROW 0 +#define SET_TIME_LATENCY 100 +#define UPDATE_LATENCY 500 +#define FF_REW_TOLERANCE 500 +#define MODIFIED_TIMETAG 2 +#define MODIFIED_OFFSET_TIMETAG 4 +#define MODIFIED_LYRICS 8 +#define END_MARK '~' +#define COUNT_DOWN_MARK '*' +#define MARK '*' +#define TIME_FORMAT "%d:%02d" +#define RET_OK 1 +#define TIMEBAR_LYRICS_SPACE 3 +#define NL "\r\n" + +/* status: vol [!]/[vbr] [utf8] [II]/[bmp] [ab] track repeat_mode */ +#define STATUS_VOL_POS 0 +#define STATUS_MODIFIED_POS 20 /* modified: icon -> 8x8 */ +#define STATUS_UTF8_POS 31 /* utf8: icon -> 8x8 */ +#define STATUS_PAUSE_POS 41 +#define STATUS_AB_POS 49 /* time: (x:xx-x:xx) -> 66, char_witdh: 6 */ +#define STATUS_REPEAT_MODE_POS LCD_WIDTH-8 + +#ifdef HAVE_REMOTE_LCD + #define MAX_TYPE 3 + #define RC 2 +#else + #define MAX_TYPE 2 +#endif + +#define SCROLL 0 +#define EDIT 1 + +#define PREF_BMP 1 +#define PREF_BACKLIGHT 2 +#define PREF_TRANSLATION 4 +#define PREF_PEAKMETER 8 +#define PREF_SAVE 0xFFFF + +/* makros */ +#define SET_ROWS(snc,val) {int ii=0; for(;iix[ii].rows=val;} +#define SET_LYRICS(snc,addr,row) {int ii=0; for(;iix[ii].lyrics[row]=addr;} +#define SNC(snc, type) snc->x[type] +#define COPY_SNC(dest,src) {int ii=0; for(;iix[ii]), &(src->x[ii]));} + +#define CALC_MS(time) (time%1000)/10 +#define CALC_SS(time) (time/1000)%60 +#define CALC_MM(time) time/60000 +#define LCD_CLEAR_AREA(screen,x,y,w,h) \ + rb->screen_clear_area(rb->screens[screen],x,y,w,h); +#define AUDIO_ELAPSED() \ + (rb->audio_current_track()!=NULL ? rb->audio_current_track()->elapsed : 0) +#define IS_ID3_VALID(id3entry) (id3entry!=NULL && id3entry->path[0]=='/') + +#define SNCVIEWER_DATA_DIR PLUGIN_APPS_DIR +#define SETTINGS_FILE SNCVIEWER_DATA_DIR"/sncviewer.dat" + +/* structs */ +struct Preferences { + bool load_bmp; + bool backlight; + int save; + int load_translation; + bool peakmeter; +}prefs; + +struct SNCText{ + int rows; + unsigned char* lyrics[MAX_ROWS]; +}; + +struct SNCSection{ + unsigned long time_in_ms; + struct SNCText x[MAX_TYPE]; +}sncs[MAX_SECTIONS]; + +extern const fb_data sncviewer_artist[]; +extern const fb_data sncviewer_title[]; + +static unsigned char lyrics_buffer[MAX_SECTIONS*BUFFERSIZE]; +static int lyrics_buffer_used; +static struct plugin_api* rb; +MEM_FUNCTION_WRAPPERS(rb); +static struct mp3entry* id3; +static unsigned char buf[BUFFERSIZE]; /* temp. buffer */ +static unsigned char lyrics_filename[MAX_PATH]; +static bool auto_scroll; +static int num_snc; +static int current_snc; /* 0..num_snc-1 */ +static int current_snc_edit; /* 0..num_snc-1 */ +static int stop_scroll_snc_edit_id; + +static int start_snc; +static int stop_snc; +static int time_offset; +static int indentwidth, fontheight, sysfont_height; +static int lcd_max_rows; +static bool utf8; +static int modified; +static bool force_update_display; +static const unsigned char BOM[]={'\xef','\xbb','\xbf','\0'}; +static const unsigned char* FORMATS[]={"lrc","lrc8","snc","txt","ab", + "tr","mp3","cue"}; +enum e_supported_formats{EXT_LRC,EXT_LRC8,EXT_SNC,EXT_TXT, + EXT_AB,EXT_TR,EXT_MP3,EXT_CUE}; +static enum e_supported_formats g_ext; + +enum e_repeat_modes{RMODE_OFF, RMODE_ALL, RMODE_ONE, RMODE_SHUFFLE, RMODE_AB}; +enum e_load_translation{TR_FAILED=-1, TR_OFF=0, TR_OK=2}; +static int time_bar_row; +static int scroll_y0; +static int artist_title_row_height,remote_artist_title_row_height; +static int bmp_width; + +enum e_abfile_state{AB_NO,AB_YES,AB_UNKNOWN}; + +static enum e_abfile_state abfile_state; +#if defined(HAVE_LCD_COLOR) + bool change_fg_color; +#endif +unsigned char* (*utf_decode)(const unsigned char *, unsigned char *, int); +void update_display(void); +bool load_file(enum e_supported_formats); + +void default_backlight(void){ + rb->backlight_set_timeout(rb->global_settings->backlight_timeout); +#if CONFIG_CHARGING + rb->backlight_set_timeout_plugged( + rb->global_settings->backlight_timeout_plugged); +#endif /* CONFIG_CHARGING */ +} + +void force_backlight_on(void){ + rb->backlight_set_timeout(0); +#if CONFIG_CHARGING + rb->backlight_set_timeout_plugged(0); +#endif /* CONFIG_CHARGING */ +} + +unsigned long bytes2int(unsigned long b0, unsigned long b1, + unsigned long b2, unsigned long b3) +{ + return (((long)(b0 & 0xFF) << 24) | /* 3*8 */ + ((long)(b1 & 0xFF) << 16) | /* 2*8 */ + ((long)(b2 & 0xFF) << 8) | /* 1*8 */ + ((long)(b3 & 0xFF))); /* 0*8 */ +} + +inline int lrccmp(const char * str1, const char *str2){ + return (str1==str2)?0:rb->strcmp(str1,str2); +} + +inline void iso2utf8(const unsigned char *iso, unsigned char *utf8_buf, + unsigned int len){ + if(!utf8) rb->iso_decode(iso, utf8_buf, -1, len); + else rb->strncpy(utf8_buf,iso,len); +} +/* no time */ +void snc_copy_(struct SNCText* dest,const struct SNCText* src){ + dest->rows=src->rows; + int r; + for(r=0;rrows;r++) dest->lyrics[r]=src->lyrics[r]; +} +/* copy with time [offset] */ +void snc_copy(struct SNCSection* dest,const struct SNCSection* src,int offset){ + dest->time_in_ms=src->time_in_ms+offset; + COPY_SNC(dest,src); +} +char* cat_time(char* str,unsigned long time) +{ + return (time!=(unsigned long) NOT_INIT) ? + &str[rb->snprintf(str,6,TIME_FORMAT,(int)(CALC_MM(time)),(int)(CALC_SS(time)))]: + rb->strcpy(str,"?:??")+4; +} + +int get_center_pos(const unsigned char* const str, int min){ + int w=0; + if(str!=NULL) rb->lcd_getstringsize(str, &w, NULL); + w=(LCD_WIDTH-w)>>1; + if(wartist; + const unsigned char* title=entry->title; + int height=sysfont_height; + if(artist!=NULL && title!=NULL) + rb->snprintf(buf,BUFFERSIZE,"%s/%s",title,artist); + else if(entry->path[0]!=0)/* no artist and no title -> display filename */ + buffer=rb->strrchr(entry->path,'/')+1; + else buf[0]=0; + bool use_sysfont=true; + const unsigned char* str=buffer; + while(*str!=0){ + if(*str++ > 0xc3){ /* last char in rockbox_default.bdf: 255 -> c3 bf */ + use_sysfont=false; + break; + } + } + + struct screen *display = rb->screens[screen]; + if(use_sysfont){ + display->setfont(FONT_SYSFIXED); + } + else{ /* id3 and filename not ascii -> show filename and use ui_font */ + height=fontheight; + if(y+height>LCD_HEIGHT) y+=sysfont_height-fontheight; + } + display->putsxy(x,y,buffer); + display->setfont(FONT_UI); + return height; +} + +void exec_func(int delay, void (*func)(void)){ + #define MAX_FUNCS 2 + /* 2: update_status_display, print_next_playing */ + static struct { + int t; + void (*exec)(void); + } funcs[MAX_FUNCS]={{-1,NULL},{-1,NULL}}; + static int size=0; + static bool busy=false; + if(busy) return; /* a func is beeing executed, don't mess up */ + int i=0; + int free=-1; + if(func!=NULL){ /* add func to queue */ + if(size>0){ + for(i=0;i 0 */ + break; + } + } + if(i==MAX_FUNCS){ /* is a new func -> assign to a free place */ + i = free>=0 ? free : 0; /* overwrite first if queue is too small */ + } + } + funcs[i].t=delay; + funcs[i].exec=func; + size++; + DEBUGF("func %p added, num func in queue: %d\n",func,size); + } + if(size>0){ /* there are functions in the queue waiting */ + for(i=0;iaudio_next_track(); + int height=0; + if(IS_ID3_VALID(nextid3)){ + int y=LCD_HEIGHT-artist_title_row_height; + LCD_CLEAR_AREA(SCREEN_MAIN,0,y,LCD_WIDTH,artist_title_row_height); + rb->lcd_mono_bitmap(icon_next_7x8,0,y+((artist_title_row_height-8)>>1),7,8); + height=print_artist_title(SCREEN_MAIN,nextid3,7,y); + if(height==artist_title_row_height) + rb->lcd_update_rect(0,y,LCD_WIDTH,artist_title_row_height); + } + if(height!=artist_title_row_height){ /* height has changed */ + artist_title_row_height=height; + update_display(); + } +} + +void update_status_display(void) +{ + static unsigned char volume_icons[][7]={ + {0x00,0x1c,0x1c,0x3e,0x7f,0x00,0x00}, /* Speaker */ + {0x01,0x1e,0x1c,0x3e,0x7f,0x20,0x40}, /* Speaker mute */ + }; + #define UTF8_ICON bitmap_icons_8x8[0] + #define VBR_ICON bitmap_icons_8x8[1] + #define PAUSE_ICON bitmap_icons_7x8[5] + #define AA_ICON bitmap_icons_7x8[6] + #define TR_ICON bitmap_icons_7x8[7] + static const unsigned char bitmap_icons_8x8[][8]={ + {0x07,0x08,0x0e,0x21,0xd7,0xb1,0x4e,0x05}, /* utf8 */ + {0x0e,0xe4,0x53,0xa0,0x0f,0x15,0x0a,0x00}, /* vbr */ + }; + static const unsigned char bitmap_icons_7x8[][7] = + { + {0x00,0x60,0x7f,0x03,0x33,0x3f,0x00}, /* Musical note */ + {0x44,0x4e,0x5f,0x44,0x44,0x44,0x38}, /* Repeat playmode */ + {0x44,0x4e,0x5f,0x44,0x38,0x02,0x7f}, /* Repeat-one playmode */ + {0x3e,0x41,0x51,0x41,0x45,0x41,0x3e}, /* Shuffle playmode (dice) */ + {0x7f,0x04,0x4e,0x5f,0x44,0x38,0x7f}, /* Repeat-AB playmode */ + {0x00,0x7f,0x7f,0x00,0x7f,0x7f,0x00}, /* Pause */ + {0x18,0x24,0x3C,0x3C,0x24,0x18,0x00}, /* Album Art */ + /* {0x02,0x02,0x1f,0x01,0x3d,0x14,0x68}, Translation */ + {0x01,0x05,0x29,0x20,0xb8,0x80,0x80}, /* Translation */ + }; + static const int volume_values[]={0x00,0x08,0x14,0x2a,0x55,0x5f}; + int vol = rb->global_settings->volume; + unsigned char* volume_icon=volume_icons[1]; /* mute */ + if(vol>-60){ + volume_icon=volume_icons[0]; + if(vol>-15) volume_icons[0][6]=volume_values[5]; + else if(vol>-25) volume_icons[0][6]=volume_values[4]; + else if(vol>-30) volume_icons[0][6]=volume_values[3]; + else if(vol>-40) volume_icons[0][6]=volume_values[2]; + else if(vol>-50) volume_icons[0][6]=volume_values[1]; + else volume_icons[0][6]=volume_values[0]; /* >20 */ + } + + rb->lcd_setfont(FONT_SYSFIXED); + LCD_CLEAR_AREA(SCREEN_MAIN,0,0,LCD_WIDTH,sysfont_height); + if(prefs.load_translation) + rb->lcd_mono_bitmap(TR_ICON,10,0,7,8); + if(modified){ + static const unsigned char modified_icon[]={0x9f,0xdf}; /* modified*/ + rb->lcd_mono_bitmap(modified_icon,STATUS_MODIFIED_POS,0,2,8); + if(time_offset!=0){ + static unsigned char sign_icon[]={0x08,0x08,0x3e,0x08,0x08}; /* + */ + sign_icon[2]=time_offset<0 ? 0x08 : 0x3e; /* - */ + rb->lcd_mono_bitmap(sign_icon,STATUS_MODIFIED_POS+3,0,5,8); + } + } + else if(id3->vbr && id3->codectype==AFMT_MPA_L3){ /* sync problems with vbr mp3 */ + rb->lcd_mono_bitmap(VBR_ICON,STATUS_MODIFIED_POS,0,8,8); + } + + if(rb->audio_status() & AUDIO_STATUS_PAUSE) + rb->lcd_mono_bitmap(PAUSE_ICON,STATUS_PAUSE_POS,0,7,8); + else if(prefs.load_bmp) + rb->lcd_mono_bitmap(AA_ICON,STATUS_PAUSE_POS,0,7,8); + + if(utf8) rb->lcd_mono_bitmap(UTF8_ICON,STATUS_UTF8_POS,0,8,8); + + if(start_snc!=NOT_INIT) /* ab repeat */ + { + buf[0]='('; + char* end=cat_time(&buf[1],sncs[start_snc].time_in_ms); + if(stop_snc!=NOT_INIT) + { + *end='-'; + end=cat_time(++end,sncs[stop_snc].time_in_ms); + *end++=')'; + *end=0; + } + rb->lcd_putsxy(STATUS_AB_POS,0,buf); + } + rb->lcd_mono_bitmap(volume_icon,STATUS_VOL_POS,0,7,8); + +#ifdef CUSTOM_PLUGIN_PATCH + if(rb->playlist_amount()<100 || stop_snc==NOT_INIT){ /* not enough space */ + rb->snprintf(buf,BUFFERSIZE,"%d/%d",rb->playlist_get_display_index(), + rb->playlist_amount()); + rb->lcd_putsxy(LCD_WIDTH-rb->strlen(buf)*6-12,0,buf); + } +#endif + + rb->lcd_mono_bitmap(bitmap_icons_7x8[rb->global_settings->repeat_mode], + STATUS_REPEAT_MODE_POS,0,7,8); + + rb->lcd_setfont(FONT_UI); + rb->lcd_update_rect(0,0,LCD_WIDTH,sysfont_height); + + if(IS_ID3_VALID(rb->audio_next_track())) + print_next_playing(); /* show next playing immediately if available */ + exec_func(10,print_next_playing); /* update next playing */ +} + +int print_lyrics_line(struct SNCText* snc, int r, bool set_sysfont, + void (*lcd_puts)(int, int, const unsigned char*),int x, int y){ + int height=fontheight; + if(set_sysfont){ + rb->lcd_setfont(FONT_SYSFIXED); + fontheight=sysfont_height; + } + char c=0; + if(r+1rows){ + c=*(snc->lyrics[r+1]); /* backup */ + *(snc->lyrics[r+1])=0; + } + lcd_puts(x<0 ? get_center_pos(snc->lyrics[r],0) : x,y,snc->lyrics[r]); + /* restore */ + if(r+1rows) *(snc->lyrics[r+1])=c; + if(set_sysfont){ + rb->lcd_setfont(FONT_UI); + fontheight=height; + height=sysfont_height; + } + return height; +} + +void browse_snc(void) +{ + bool show_prev = current_snc_edit>0 && + (sncs[current_snc_edit].x[EDIT].rows + + sncs[current_snc_edit-1].x[EDIT].rows) <= lcd_max_rows; + int id=stop_scroll_snc_edit_id >= current_snc_edit ? + (show_prev ? current_snc_edit-1 : current_snc_edit):stop_scroll_snc_edit_id; + int r, mark_height=fontheight,mark_y=sysfont_height, y=sysfont_height; + LCD_CLEAR_AREA(SCREEN_MAIN,0,sysfont_height, + LCD_WIDTH,LCD_HEIGHT-(sysfont_height<<1)); + if(id==0){ + rb->lcd_putsxy(0, sysfont_height,""); + id=1; + y+=fontheight; + } + bool ok=true; + bool add_mark=true; + bool set_sysfont=prefs.load_translation==TR_OK && + sncs[id].time_in_ms==sncs[id-1].time_in_ms; + while(ok && idrows && ok) + { + add_mark=true; + if(r==0 && set_sysfont==false){ + char* end=cat_time(buf,sncs[id].time_in_ms); + if(id==start_snc){ + *end='>'; + add_mark=false; + } + else if(id==stop_snc) *end='<'; + else if(id==current_snc){ + *end=MARK; + add_mark=false; + } + *(end+1)=0; + rb->lcd_putsxy(0,y,buf); + } + if(id=start_snc && add_mark) + rb->lcd_putsxy(indentwidth-4,y,"|"); + y+=print_lyrics_line(snc,r,set_sysfont,rb->lcd_putsxy,indentwidth,y); + r++; + ok=LCD_HEIGHT>=y+fontheight+sysfont_height; + } + if(prefs.load_translation==TR_OK) + set_sysfont=sncs[id].time_in_ms==sncs[id+1].time_in_ms; + if(id==current_snc_edit) mark_height=y-mark_y; + id++; + } + if(id==num_snc && stop_scroll_snc_edit_id==MAX_SECTIONS && + LCD_HEIGHTlcd_set_drawmode(DRMODE_COMPLEMENT); + rb->lcd_fillrect(0, mark_y, LCD_WIDTH, mark_height); + rb->lcd_set_drawmode(DRMODE_SOLID); + rb->lcd_update(); +} + +void draw_ab(int x, int y, int snc){ + if(sncs[snc].time_in_ms==(unsigned) NOT_INIT) return; + const int sign=snc==start_snc?1:-1; + rb->lcd_hline(x+sign*2,x-sign,y); + rb->lcd_set_drawmode(DRMODE_COMPLEMENT); + rb->lcd_vline(x,y-2,y+2); /* | */ + rb->lcd_drawpixel(x+sign,y); /* . */ + rb->lcd_set_drawmode(DRMODE_SOLID); +} + +long update_time_display(long time_in_ms, bool force){ + if(time_in_ms < 0) return 0; + else if(time_in_ms > (long) id3->length) return id3->length; + + static unsigned int old; + unsigned int time=(time_in_ms/1000)&1; + if(old==time && !force) return time_in_ms; /* update every second */ + old=time; + int y=time_bar_row>=0 ? time_bar_row*fontheight+sysfont_height: + LCD_HEIGHT - sysfont_height; + LCD_CLEAR_AREA(SCREEN_MAIN,0,y,LCD_WIDTH,sysfont_height); + cat_time(buf,id3->length); /* getstringsize from length: can be >9 min */ + rb->lcd_setfont(FONT_SYSFIXED); + int x1,x2; + x1=rb->lcd_getstringsize(buf,&x1,&x2)+3; + x2=LCD_WIDTH-x1; + rb->lcd_putsxy(x2+3,y,buf); + cat_time(buf,time_in_ms); /* update elapsed time */ + rb->lcd_putsxy(0,y,buf); + rb->lcd_setfont(FONT_UI); + + /* timebar */ + y+=sysfont_height>>1; + rb->lcd_hline(x1,x2,y-1); + rb->lcd_hline(x1,x2,y+1); + rb->lcd_drawpixel(x2+1,y); + int width=x2-x1; + rb->lcd_hline(x1-1,x1+width*time_in_ms/id3->length,y); + if(!auto_scroll){ + rb->lcd_vline(x1+width*sncs[current_snc_edit].time_in_ms/id3->length, + y-3,y+3); /* | */ + } + if(start_snc!=NOT_INIT) + draw_ab(x1+width*sncs[start_snc].time_in_ms/id3->length,y,start_snc); + if(stop_snc!=NOT_INIT) + draw_ab(x1+width*sncs[stop_snc].time_in_ms/id3->length-1,y,stop_snc); + rb->lcd_update_rect(0,y-(sysfont_height>>1),LCD_WIDTH,sysfont_height); + return time_in_ms; +} + +void base_display(void) +{ + rb->lcd_clear_display(); + update_status_display(); + +#ifdef CUSTOM_PLUGIN_PATCH + #define BITMAP_SIZE 12 + int x=BITMAP_SIZE+2; +#else + int x=0; +#endif + const unsigned char* artist = id3->artist; + const unsigned char* title = id3->title; + if(artist==NULL && title==NULL) + title=rb->strrchr(id3->path,'/')+1; + int x_artist=get_center_pos(artist,x); + int x_title=get_center_pos(title,x); +#ifdef CUSTOM_PLUGIN_PATCH +/* if(fontheight>=BITMAP_SIZE){ */ + rb->lcd_bitmap(sncviewer_artist, + x_artist-x, sysfont_height, BITMAP_SIZE, BITMAP_SIZE); + rb->lcd_bitmap(sncviewer_title, + x_title-x, sysfont_height+fontheight, BITMAP_SIZE, BITMAP_SIZE); +/* } */ +#endif + if(artist!=NULL) rb->lcd_putsxy(x_artist,sysfont_height,artist); + if(title!=NULL) rb->lcd_putsxy(x_title,sysfont_height+fontheight,title); + +#ifdef HAVE_REMOTE_LCD + rb->lcd_remote_clear_display(); + remote_artist_title_row_height=print_artist_title(SCREEN_REMOTE,id3,0,0); + rb->lcd_remote_update(); +#endif + update_time_display(AUDIO_ELAPSED(),true); +} + +void load_bitmap(bool reload){ + #define BITMAP_BUFFERSIZE 65536 /* max 147 x 147 colour image */ + static unsigned char bitmap_buffer[BITMAP_BUFFERSIZE]; + static struct bitmap bm; + static int bmp_size; + bool load_bmp_mode=prefs.load_bmp && num_snc>1; + if(reload){ + bm.data=bitmap_buffer; + if(!rb->find_albumart(id3,buf,BUFFERSIZE) && !load_bmp_mode){ + /* only load sncviewer.bmp if there are no lyrics + (not in prefs.load_bmp mode) */ + rb->strcpy(buf,SNCVIEWER_DATA_DIR"/sncviewer.bmp"); + } + bmp_size=rb->read_bmp_file(buf, &bm, BITMAP_BUFFERSIZE, FORMAT_ANY); + } + + int y=scroll_y0-1; + if(bmp_size>0){ + bmp_width=bm.width+1; + int x=(LCD_WIDTH-bm.width)>>1; + if(load_bmp_mode && (bmp_width<(LCD_WIDTH>>1))) x=0; + const int max_y=LCD_HEIGHT-artist_title_row_height-1; + int height= (y+bm.height 1 + rb->lcd_bitmap((fb_data*) bm.data, x , y, bm.width, height); +#else + rb->lcd_mono_bitmap((fb_data*) bm.data, 0, y, bm.width, height); +#endif + } else if(load_bmp_mode) bmp_width=NOT_INIT; /* cannot load bitmap */ +} + +void peak_meter(void){ + int y=scroll_y0; + const int max_height=LCD_HEIGHT-y-artist_title_row_height-3; + const int width=((LCD_WIDTH-bmp_width)/5)<<1; + int left_peak,right_peak; +#if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F) + left_peak = rb->mas_codec_readreg(0xC); + right_peak = rb->mas_codec_readreg(0xD); +#elif (CONFIG_CODEC == SWCODEC) + rb->pcm_calculate_peaks(&left_peak, &right_peak); +#endif + /* clear */ + LCD_CLEAR_AREA(SCREEN_MAIN,0,y,width,max_height); + int xr=LCD_WIDTH-width; + LCD_CLEAR_AREA(SCREEN_MAIN,xr,y,width,max_height); + /* normalize */ + left_peak=(max_height*left_peak)>>14; /* scale: 4x */ + right_peak=(max_height*right_peak)>>14; + + /* draw bars */ + const int bar_height=max_height>>3; /* max: 8 bars */ + int num_bars_l=left_peak/bar_height; + int num_bars_r=right_peak/bar_height; + const int max_num_bars=num_bars_llcd_set_foreground(LCD_DARKGRAY); + if(ilcd_fillrect(i*step,y2,w2,bar_frame_height); + } + if(ilcd_fillrect(xr,y2,w2,bar_frame_height); + } +#if defined(HAVE_LCD_COLOR) + if(change_fg_color) +#endif + rb->lcd_set_foreground(LCD_DEFAULT_FG); + rb->lcd_drawrect(0,y2,width,bar_frame_height); + rb->lcd_drawrect(xr,y2,width,bar_frame_height); + + y2+=bar_height; + w2-=step; + i++; + } + rb->lcd_update(); +} +void scroll_snc(void) +{ + int id=current_snc; + bool set_sysfont=false; + int r; + struct SNCText* snc; + snc=&(sncs[id].x[SCROLL]); + bool restore=false; + if(snc->rows+2 don't display artist, title */ + rb->lcd_clear_display(); + time_bar_row=0; + update_status_display(); /* status, timebar, lyrics */ + update_time_display(AUDIO_ELAPSED(),true); + restore=true; + } + if(restore){ + scroll_y0=time_bar_row*fontheight+(sysfont_height<<1)+TIMEBAR_LYRICS_SPACE; + if(prefs.load_bmp) load_bitmap(false); + } + int y=scroll_y0; + LCD_CLEAR_AREA(SCREEN_MAIN, bmp_width!=NOT_INIT ? bmp_width:0, + y, LCD_WIDTH, LCD_HEIGHT-y-artist_title_row_height); + const int max_height=LCD_HEIGHT-artist_title_row_height-fontheight; + while(y<=max_height && idrows && y<=max_height;r++){ + y+=print_lyrics_line(snc,r,set_sysfont,rb->lcd_putsxy,bmp_width,y); + } + set_sysfont=prefs.load_translation==TR_OK && + sncs[id].time_in_ms==sncs[id+1].time_in_ms; + if(!set_sysfont){ +#if defined(HAVE_LCD_COLOR) + if(change_fg_color) +#endif + rb->lcd_set_foreground(LCD_DARKGRAY); + if(sncs[id].time_in_ms==sncs[current_snc].time_in_ms) + y+=((max_height-scroll_y0)%fontheight)>>1; + } + snc=&(sncs[++id].x[SCROLL]); + } +#if defined(HAVE_LCD_COLOR) + if(change_fg_color) +#endif + rb->lcd_set_foreground(LCD_DEFAULT_FG); + +#ifdef HAVE_REMOTE_LCD + snc=&(sncs[current_snc].x[RC]); + LCD_CLEAR_AREA(SCREEN_REMOTE,0,remote_artist_title_row_height, + LCD_REMOTE_WIDTH,LCD_REMOTE_HEIGHT-remote_artist_title_row_height); + const int rc_max_line=LCD_REMOTE_HEIGHT/fontheight; + for(r=0;rrows && rlcd_remote_putsxy,0, + r*fontheight+remote_artist_title_row_height); + } +#endif + FOR_NB_SCREENS(r) rb->screens[r]->update(); +} + +void update_display(void) +{ + if(!auto_scroll && num_snc>0){ + update_status_display(); + browse_snc(); + } + else{ + base_display(); + if(num_snc>1){ + if(prefs.load_bmp && bmp_width>0) load_bitmap(false); + scroll_snc(); + } + else{ +#ifdef HAVE_REMOTE_LCD + LCD_CLEAR_AREA(SCREEN_REMOTE,0,remote_artist_title_row_height, + LCD_REMOTE_WIDTH,LCD_REMOTE_HEIGHT-sysfont_height); + rb->lcd_remote_putsxy(0,remote_artist_title_row_height,"Lyrics?"); +#endif + load_bitmap(false); + int i; + FOR_NB_SCREENS(i) rb->screens[i]->update(); + } + } + force_update_display=false; +} + +void set_repeat_mode(int mode){ /* 0=off 1=repeat all 2=repeat one 3=shuffle 4=ab */ + if(rb->global_settings->repeat_mode==mode) return; + static int repeat_mode[]={NOT_INIT,0,0,0,0}; /* orig,repeat one, ab */ + int i,m=NOT_INIT; + if(mode==0){ /* clear */ + for(i=1;i<5;i++) repeat_mode[i]=0; + if(repeat_mode[0]!=-1){ /* restore */ + m=repeat_mode[0]; + repeat_mode[0]=NOT_INIT; + } + } + else if(mode<0){ /* restore */ + if(repeat_mode[0]>NOT_INIT){ + repeat_mode[-mode]=0; + for(i=4;i>0;i--){ /* i==mode */ +/* DEBUGF("repeat mode: %i val: %i\n",i,repeat_mode[i]); */ + if(repeat_mode[i]==1) break; + } + m=i==0?repeat_mode[i]:i; + if(i==0) repeat_mode[0]=NOT_INIT; /* no custom mode is set */ + } /* else call restore before backup */ + } + else { + if(repeat_mode[0]==NOT_INIT) + repeat_mode[0]=rb->global_settings->repeat_mode; /* backup */ + repeat_mode[mode]=1; + m=mode; /* set mode */ + } + if(m!=NOT_INIT){ + rb->global_settings->repeat_mode=m; + rb->audio_flush_and_reload_tracks(); + } + exec_func(5,update_status_display); +} +int open_file(enum e_supported_formats type,int flags) +{ + char* filename = lyrics_filename; + if(type==EXT_AB || type==EXT_TR) filename=buf; + rb->strcpy(filename,id3->path); + char* ext=rb->strrchr(filename,'.')+1; + rb->strcpy(ext,FORMATS[type]); + int fd=rb->open(filename, flags); + if(fd>=0){ + switch(type){ + case EXT_LRC8: utf8=true; break; + case EXT_LRC:{ + utf_decode=NULL; + const int idlen=2; + int ucs=0; + rb->read(fd,&ucs,idlen); + if (ucs == 0xFEFF) + utf_decode=rb->utf16LEdecode; + else if (ucs == 0xFFFE) + utf_decode=rb->utf16BEdecode; + if(utf_decode==NULL) rb->lseek(fd,-idlen,SEEK_CUR); + else{ + rb->splash(HZ,"Convert to UTF8..."); + int len=rb->read(fd,lyrics_buffer,rb->filesize(fd)-idlen); + rb->close(fd); + size_t size; + unsigned char* buffer=(unsigned char*) rb->plugin_get_buffer(&size); + unsigned char* end=utf_decode(lyrics_buffer,buffer,len>>1); + rb->remove(filename); + rb->strcat(filename,"8"); + fd=rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC); + rb->write(fd, buffer,end-buffer); + rb->close(fd); + fd=-1; /* loading order: lrc, lrc8 -> lrc fails, + try to open the newly converted lrc8 */ + } + break; + } + default: /* txt || snc */ + rb->read(fd,buf,3); + utf8=rb->strncmp(buf,BOM,3)==0; /* bom */ + if(!utf8) rb->lseek(fd,-3,SEEK_CUR); + } + } + return fd; +} + +int clear_ab(void){ + start_snc=NOT_INIT; + stop_snc=NOT_INIT; + set_repeat_mode(-RMODE_AB);/* reset repeat ab */ + return RET_OK; +} + +#ifdef HAS_AB_BUTTON +bool ab_file(bool load){ /* check: load=false */ + int fd = open_file(EXT_AB,O_RDONLY); + if(fd<0){ + abfile_state=AB_NO; + return false; + } + abfile_state=AB_YES; + if(load){ + int len = rb->read_line(fd, buf, BUFFERSIZE); + if(len > 0){ + start_snc=rb->atoi(buf); + rb->read_line(fd, buf, BUFFERSIZE); /* no test needed */ + stop_snc=rb->atoi(buf); + } + } + rb->close(fd); + return true; +} +void ab_menu(void){ + char* ab_items[4]; + int i=0; + int offset=0; + if(start_snc!=NOT_INIT){ + ab_items[i++] = "Clear"; + ab_items[i++] = "Save"; + } + else offset=2; + if(abfile_state==AB_UNKNOWN) abfile_state=ab_file(false); + if(abfile_state==AB_YES){ + ab_items[i++] = "Load"; + ab_items[i++] = "Remove"; + } + if(i>0){ + struct menu_callback_with_desc desc = {NULL,"A-B MENU", Icon_NOICON}; + struct menu_item_ex menu = { + MT_RETURN_ID|MENU_HAS_DESC|MENU_ITEM_COUNT(i), + {.strings = (const char **) ab_items}, + {.callback_and_desc = &desc} + }; + + switch(rb->do_menu(&menu, NULL,NULL,false)+offset){ + case 0: clear_ab(); break; + case 1: /* save */ + { + int fd = open_file(EXT_AB, O_WRONLY|O_CREAT|O_TRUNC); + if(fd<0){ + abfile_state=AB_NO; + break; + } + rb->fdprintf(fd,"%d\n%d\n",start_snc,stop_snc); + rb->close(fd); + abfile_state=AB_YES; + break; + } + case 2: /* load */ + ab_file(true); + break; + case 3: /* remove */ + rb->strcpy(buf,lyrics_filename); + char* ext=rb->strrchr(buf,'.')+1; + rb->strcpy(ext,FORMATS[EXT_AB]); + rb->remove(buf); + clear_ab(); + abfile_state=AB_NO; + break; + } + } +} +void set_ab_marker(void) +{ + if(num_snc<1) return; + int snc=auto_scroll?current_snc:current_snc_edit; + if(snc==start_snc) clear_ab(); + else if(snc==stop_snc) stop_snc=NOT_INIT; + else{ + if(start_snc==NOT_INIT) start_snc=snc; + else if(stop_snc==NOT_INIT) stop_snc=snc; + else if(sncsound_min(SOUND_VOLUME) || vol > rb->sound_max(SOUND_VOLUME)) + return; + rb->sound_set(SOUND_VOLUME, vol); + rb->global_settings->volume = vol; + rb->snprintf(buf, BUFFERSIZE, "%d", vol); + LCD_CLEAR_AREA(SCREEN_MAIN, + STATUS_VOL_POS,0,STATUS_MODIFIED_POS-STATUS_VOL_POS,8); + rb->lcd_setfont(FONT_SYSFIXED); + rb->lcd_putsxy(STATUS_VOL_POS,0,buf); + rb->lcd_setfont(FONT_UI); + rb->lcd_update_rect(STATUS_VOL_POS,0,STATUS_MODIFIED_POS-STATUS_VOL_POS,8); + exec_func(10,update_status_display); +} + +bool tt2int(unsigned char** buffer, int* number, char end_char){ + unsigned char* start=*buffer; + bool ok; + while((ok=**buffer!=0)==true && *(*buffer)++!=end_char); + size_t size=*buffer-start-1; + if(ok &= size<=3){ + char xx[size+1]; + xx[size]=0; + rb->memcpy(xx,start,size); + *number=rb->atoi(xx); + } + else *buffer=start; + return ok; +} + +bool lrc_timetag(unsigned char** buffer,unsigned long* time) +{ + /* format: [m:ss.xxx],[mm:ss.xxx],[mm:ss.xx],[mm:ss] */ + bool is_timetag=**buffer=='[' && + (*buffer)[1] >= '0' && (*buffer)[1] <= '9' && /* digit */ + ((*buffer)[3]==':' || (*buffer)[2]==':'); + if(is_timetag){ + int mm,ss,ms=0; + (*buffer)++; /* skip [ */ + tt2int(buffer,&mm,':'); + tt2int(buffer,&ss,'.') ? tt2int(buffer,&ms,']'): tt2int(buffer,&ss,']'); + /* +1 ensure that time is > 0 */ + *time=mm*60000+ss*1000+(ms<100 ? ms*10 : ms) +1; /* wrong if ms=0xx !!! */ + } + return is_timetag; +} + +inline bool is_wrap_char(const unsigned char* const buffer){ + return *buffer<'A' && *buffer!=0; +} + +unsigned char* get_utf_char_size_width( + const unsigned char* pos, int* size, int* width){ + if (*pos < 0x80) *size = 1; /* U-00000000 - U-0000007F, 1 *size */ + else if (*pos < 0xe0) *size = 2; /* U-00000080 - U-000007FF, 2 *sizes */ + else if (*pos < 0xf0) *size = 3; /* U-00000800 - U-0000FFFF, 3 *sizes */ + else if (*pos < 0xf5) *size = 4; /* U-00010000 - U-001FFFFF, 4 *sizes */ + /* else: Invalid size. */ + + unsigned char* end = (unsigned char*) pos + *size; + unsigned char c=*end; + *end=0; /* terminate string */ + rb->lcd_getstringsize(pos, width, NULL); /* get width */ + *end=c; /* restore string */ + return end; +} + +bool store_line_(struct SNCText* snc,const unsigned char* buffer, int max_width){ + bool ok; + int w; /* temp vars to hold for result passing */ + int byte=1; + const unsigned char* pos = buffer; /* current position */ + const unsigned char* sp = buffer; /* position of last space */ + int width = 0; /* total width */ + int sp_width = 0; /* width from start to space */ + while((ok=(snc->rows+1)1) + force_update_display=true; + width += w; /* add to total */ + } + if(width<=max_width){ + /* don't store empty lines (exception: 1st line) */ + if(pos>buffer+1 || snc->rows==0){ + /* set next row point to the end */ + snc->lyrics[++(snc->rows)]=(unsigned char*) pos; + } + break; + } + else {/* width exceeds max_width */ + const unsigned char* npos = sp+1; /* there was a space, we want to wrap after it */ + width -= sp_width; /* update width */ + if(sp==buffer){ /* no space */ + pos -= byte; /* restore the last position, which was in range */ + npos = pos; + width = 0; + } + while (is_wrap_char(npos)) npos++; /* new line shouldn't begin with one of these chars */ + snc->lyrics[++(snc->rows)]=(unsigned char*) npos; + buffer=npos; /* set beginning to the new position */ + sp = npos; + } + } + if(!ok) rb->splash(HZ,"Not Enough Rows!"); + /* look for end of string and set end mark AFTER \0 to prevent truncation of the line */ + while(*(snc->lyrics[snc->rows])++!=0); + return ok; +} +bool store_line( + struct SNCSection *snc, const unsigned char* buffer, bool reset_rows) +{ + if(reset_rows){ + SET_ROWS(snc,0); + } + bool ok=store_line_(&(SNC(snc,SCROLL)),buffer, + bmp_width>0 ? LCD_WIDTH-bmp_width : LCD_WIDTH); + ok &= store_line_(&(SNC(snc,EDIT)),buffer,LCD_WIDTH-indentwidth); +#ifdef HAVE_REMOTE_LCD + ok &= store_line_(&(SNC(snc,RC)),buffer,LCD_REMOTE_WIDTH); +#endif + return ok; +} + +void init_snc(struct SNCSection* snc, bool reset_time){ + if(snc==NULL) return; + if(reset_time) snc->time_in_ms=0; + SET_ROWS(snc,1); + unsigned char* lyrics=SNC(snc,SCROLL).lyrics[0]; + *lyrics=0; + /* [0]0[1] */ + SET_LYRICS(snc,lyrics,0); + SET_LYRICS(snc,lyrics+1,1); +} + +#ifndef NO_SNC_ID3 +/* snc: extract time from the buffer, if the buffer contains a timetag */ +bool snc_timetag(const char* buffer,unsigned long* time) +{ + /* format: \xA2\xE2hhmmss..\xA2\xD0 */ + if(rb->strlen(buffer)<10) return false; + const char* open_tag = "\xA2\xE2"; + unsigned char utf8_buf[10]; + rb->iso_decode(open_tag,utf8_buf,-1,3); /* depends on the codepage! */ + int open_tag_size = rb->strlen(utf8_buf); + bool is_timetag = rb->strncmp(buffer,utf8_buf,open_tag_size)==0; + + if(is_timetag) + { + char xx[3]; + xx[2]='\0'; + int mm,ss,ms; + const char* pos=&buffer[open_tag_size+2]; /* skip hh */ + + xx[0]=*pos++; + xx[1]=*pos++; + mm=rb->atoi(xx); + + xx[0]=*pos++; + xx[1]=*pos++; + ss=rb->atoi(xx); + + xx[0]=*pos++; + xx[1]=*pos++; + ms=rb->atoi(xx); + + *time=mm*60000+ss*1000+ms*10+1; /* ms*10, +1 ensure that time is > 0 */ + } + return is_timetag; +} + +int store_snc_section(struct SNCSection *snc, int fd) +{ + int len; + unsigned char* lyrics; + SET_ROWS(snc,0); + bool is_time_tag; + bool ok=true; + while((len = rb->read_line(fd, buf, BUFFERSIZE))>0 && + (is_time_tag = snc_timetag(buf,&(snc->time_in_ms)))==false){ + lyrics=SNC(snc,SCROLL).lyrics[SNC(snc,SCROLL).rows]; + SNC(snc,EDIT).lyrics[SNC(snc,EDIT).rows]=lyrics; +#ifdef HAVE_REMOTE_LCD + SNC(snc,RC).lyrics[SNC(snc,RC).rows]=lyrics; +#endif + iso2utf8(buf, lyrics, len); + if(ok) ok=store_line(snc,lyrics,false); + } + return len; +} + +bool read_snc(int fd) +{ + num_snc=0; + bool ok; + do{ + ok=store_snc_section(&sncs[num_snc],fd)>0; + struct SNCText* snc=&(sncs[num_snc].x[SCROLL]); + if(snc->rows==0) init_snc(&sncs[num_snc],false); + if(++num_snc < MAX_SECTIONS) + sncs[num_snc].x[SCROLL].lyrics[0]=snc->lyrics[snc->rows]; + else ok=false; + } while(ok); + /* update time: time[i]=time[i-1] */ + int i=num_snc-1; + for(;i>0;i--) sncs[i].time_in_ms=sncs[i-1].time_in_ms; + sncs[0].time_in_ms=0; + return true; +} + +bool read_id3(int fd){ + if(id3->codectype!=AFMT_MPA_L3 || id3->length>600000){ + /* not mp3 or > 10 min */ + return false; + } + const int HEADER_SIZE=10; + char header[HEADER_SIZE]; + rb->read(fd, header, 10); + /* DEBUGF("**ID3: %i,%i, %i\n",header[3], id3->id3version, id3->id3v2len); */ + unsigned int pos=HEADER_SIZE; + unsigned long framelen=0; + unsigned char* buffer=0; + int type=0; + int header_size=6; + enum {SYLT=1,USLT}; + while(posid3v2len){ + rb->read(fd, header, HEADER_SIZE); + framelen = bytes2int(header[4], header[5], header[6], header[7]); + if(rb->memcmp(header,"SYLT",4)==0) /* SYLT found */ + type=SYLT; + else if(rb->memcmp(header,"USLT",4)==0){ + type=USLT; + header_size=4; + /* check if there's a txt file */ + if(load_file(EXT_TXT)) return true; + /* handle like a txt file */ + rb->strcpy(rb->strrchr(lyrics_filename,'.')+1,FORMATS[EXT_TXT]); + g_ext=EXT_TXT; + auto_scroll=false; + current_snc_edit=1; + time_bar_row=-1; + } + if(type){ + size_t size; + buffer=(unsigned char*) rb->plugin_get_buffer(&size); + rb->read(fd, buffer, framelen); + break; + } + rb->lseek(fd, framelen, SEEK_CUR); /* skip content */ + pos+=HEADER_SIZE; + } + + if(type){ /* found */ + unsigned char* bpos=buffer+header_size; /* skip header */ + bool unicode=buffer[0]==1; + utf_decode=rb->utf16LEdecode; + if(!unicode) + while(*bpos++!=0); /* skip content descriptor */ + else{ + if(rb->strncmp(bpos,"\xFE\xFF",2)==0) utf_decode=rb->utf16BEdecode; + while(*bpos!=0 || *(bpos+1)!=0) bpos+=2; + bpos+=2; + while(*bpos==0xFF || *bpos==0xFE) bpos++; /* skip them (tagrename) */ + } + + init_snc(&sncs[0],true); + num_snc=1; /* start with snc = 1 */ + SET_LYRICS((&sncs[num_snc]),sncs[0].x[SCROLL].lyrics[0]+1,0); + unsigned char* endpos=buffer+framelen; + /* ensure termination (+1: unicode) */ + *(endpos)=0; *(endpos+1)=0; + while(endpos>bpos){ + struct SNCText* snc=&(sncs[num_snc].x[SCROLL]); + unsigned char* lyrics=snc->lyrics[0]; + if(type==USLT){ + /* replace USLT endchar 0x0A with 0x00 and 0x0D with a space */ + lyrics=bpos; + while((*lyrics)!=0x0A){ + if(*lyrics==0x0D) *lyrics=' '; + lyrics++; + } + *lyrics=0; /* now it's like SYLT and independent of endianess */ + lyrics=snc->lyrics[0]; + } + if(!unicode) + while((*lyrics++=*bpos++)!=0); + else{ + unsigned char* start=bpos; + while(*bpos!=0 || *(bpos+1)!=0) bpos+=2; /* not 00 */ + bpos+=2; /* include 00 */ + utf_decode(start,lyrics,bpos-start); + } + + SET_LYRICS((&sncs[num_snc]),snc->lyrics[0],0); + store_line(&sncs[num_snc],snc->lyrics[0],true); + if(type==SYLT){ + /* timestamp */ + sncs[num_snc].time_in_ms=bytes2int(bpos[0],bpos[1],bpos[2],bpos[3]); + bpos+=4; + } + else sncs[num_snc].time_in_ms=NOT_INIT; + sncs[++num_snc].x[SCROLL].lyrics[0]=snc->lyrics[snc->rows]; + } + } + return type; +} +#endif /* NO_SNC_ID3 */ + +void sort_sncs(int start){ + int i,j,min; + struct SNCSection tmp; + for(i=start;i sncs[j].time_in_ms) min=j; + } + if(min!=i){ /* swap */ + snc_copy(&tmp,&sncs[i],0); + snc_copy(&sncs[i],&sncs[min],0); + snc_copy(&sncs[min],&tmp,0); + } + } +} +bool read_lrc(int fd, bool store_notag, bool append){ + int sort_start=0; + if(append==false){ + init_snc(&sncs[0],true); + num_snc=1; /* start with snc = 1 */ + SET_LYRICS((&sncs[num_snc]),sncs[0].x[SCROLL].lyrics[0]+1,0); + } + else{ /* append */ + if(num_snc<2) return false; /* cannot append if there weren't any before */ + if(sncs[num_snc-1].x[SCROLL].lyrics[0][0]==END_MARK) + num_snc--; /* last: ~..~ -> append before */ + SET_LYRICS((&sncs[num_snc]),&lyrics_buffer[lyrics_buffer_used],0); + } + + int snc_id,src_id,len,old_len=100; + unsigned char* buffer; + while ((len = rb->read_line(fd, buf, BUFFERSIZE))>0 && num_snclyrics[0]; + iso2utf8(buffer, lyrics, len-(buffer-buf)); + SET_LYRICS((&sncs[num_snc]),lyrics,0); + store_line(&sncs[num_snc],lyrics,true); + while(++num_snc!=snc_id){ + COPY_SNC((&sncs[num_snc]),(&sncs[src_id])); + if(sort_start==0) sort_start=num_snc-1; + } + /* next */ + sncs[num_snc].x[SCROLL].lyrics[0]=snc->lyrics[snc->rows]; + old_len=len; + } + lyrics_buffer_used=sncs[num_snc].x[SCROLL].lyrics[0]-lyrics_buffer; + if(store_notag==false){ /* don't sort if it's txt */ + if(sort_start>0) sort_sncs(sort_start); /* lrc1 lrc2 .. [tr1 tr2 ..] */ + if(append) sort_sncs(1); /* lrc1 tr1 lrc2 tr2 ... */ + } + return true; +} + +bool read_cue(int fd){ + init_snc(&sncs[0],true); + num_snc=0; /* start with snc = 1 */ + SET_LYRICS((&sncs[num_snc]),sncs[0].x[SCROLL].lyrics[0]+1,0); + unsigned char *s; + struct SNCText* snc=NULL; + /* code copied from cuesheet.c (modified) */ + while (rb->read_line(fd, buf, BUFFERSIZE)>0 && num_snclyrics[0]; + + s = buf; + while(*s==' ' || *s=='\t') s++; /* skip space(s) */ + if (!rb->strncmp(s, "TRACK", 5)) + { + sncs[++num_snc].x[SCROLL].lyrics[0]=snc->lyrics[snc->rows]; + } + else if (!rb->strncmp(s, "INDEX 01", 8)) + { + s+=8; + while(*s==' ' || *s=='\t') s++; /* skip space(s) */ + sncs[num_snc].time_in_ms = 60000 * rb->atoi(s); + s = rb->strchr(s,':') + 1; + sncs[num_snc].time_in_ms += 1000 * rb->atoi(s); + s = rb->strchr(s,':') + 1; + sncs[num_snc].time_in_ms += 13 * rb->atoi(s); + if(sncs[num_snc].time_in_ms==0) sncs[num_snc].time_in_ms=1; + } + else if (!rb->strncmp(s, "TITLE", 5)) + { + char *string, *end; + string = rb->strchr(s, '"'); + if (!string) + { + string = rb->strchr(s, ' '); + if (!string) break; + } + end = rb->strchr(++string, '"'); + if (end) *end = '\0'; + + /* iso2utf8(string, lyrics, rb->strlen(string)+1); */ + rb->strncpy(lyrics,string,rb->strlen(string)+1); + SET_LYRICS((&sncs[num_snc]),lyrics,0); + store_line(&sncs[num_snc],lyrics,true); + } + } + sncs[0].x[SCROLL].lyrics[0][0]=0; + SET_ROWS((&sncs[0]),1); + if(snc!=NULL) sncs[++num_snc].x[SCROLL].lyrics[0]=snc->lyrics[snc->rows]; + return true; +} + +void append_end_section(void){ + /* add_last_section */ + if(num_snc+1time_in_ms=id3->length; + + static char lastBuf[LCD_WIDTH>>2]; + if(lastBuf[0]!=END_MARK){ /* initialized? */ + int w,tmp; + rb->lcd_getstringsize("~", &w, &tmp); + tmp=LCD_WIDTH/w; + rb->memset(lastBuf,END_MARK,tmp); /* fill with ~ */ + lastBuf[tmp]=0; + } + SET_LYRICS(last,lastBuf,0); + num_snc++; + } +} + +bool load_file(enum e_supported_formats type){ + bool ret=true; + int fd = open_file(type, O_RDONLY); + if (fd < 0){ + if(type==EXT_TR) prefs.load_translation=TR_FAILED; + return type==EXT_LRC ? load_file(EXT_LRC8):false; /* try to open lrc8 */ + } + if(type!=EXT_TR){ + g_ext=type; + force_update_display=false; + lyrics_buffer_used=0; + SET_LYRICS((&sncs[0]),lyrics_buffer,0); + } + switch(type){ + case EXT_LRC: + case EXT_LRC8: read_lrc(fd, false, false); break; + case EXT_TR: + prefs.load_translation=TR_OK; + rb->lcd_setfont(FONT_SYSFIXED); + read_lrc(fd, false, true); + rb->lcd_setfont(FONT_UI); + break; +#ifndef NO_SNC_ID3 + case EXT_SNC: read_snc(fd); break; + case EXT_MP3: ret=read_id3(fd); break; +#endif + case EXT_CUE: read_cue(fd); break; + default: /* read txt */ + bmp_width=NOT_INIT; + read_lrc(fd,true,false); + if(sncs[1].time_in_ms==(unsigned long)NOT_INIT) sncs[1].time_in_ms=1; + current_snc_edit=1; + } + rb->close(fd); + + /* if prefs.load_translation is set, try to load translation file */ + if(prefs.load_translation && + type!=EXT_TR && /* prevent loop */ + load_file(EXT_TR)){ /* try to load translation file */ + return true; /* continue if loading translation fails, + return otherwise (finishing is already done by EXT_TR)*/ + } + + /* lrc: after sorting, the last snc could be somewhere in the lyrics buffer */ + if(lyrics_buffer_used==0) + lyrics_buffer_used=sncs[num_snc].x[SCROLL].lyrics[0]-lyrics_buffer; + + append_end_section(); + + /* misuse force_update_display */ + if(force_update_display && utf8==false) utf8=true; + return ret; +} + +void ff_rew(unsigned long time_in_ms){ + bool playing=!(rb->audio_status()&AUDIO_STATUS_PAUSE); + if(playing) rb->audio_pause(); + rb->audio_ff_rewind(time_in_ms); + update_time_display(time_in_ms, true); + int diff; + while(((diff=AUDIO_ELAPSED()-time_in_ms)>FF_REW_TOLERANCE || + diff < -FF_REW_TOLERANCE) && !rb->action_userabort(TIMEOUT_NOBLOCK)) + rb->yield(); + + if(playing){ + rb->audio_resume(); + /* wait until status is updated */ + while((rb->audio_status()&AUDIO_STATUS_PAUSE) && + !rb->action_userabort(TIMEOUT_NOBLOCK)) + rb->yield(); + } +} +void ff_rew_snc(int snc){ + snc%=(num_snc-1); /* 0 .. num_snc-2 */ + if(sncs[snc].time_in_ms==(unsigned long) NOT_INIT) return; /* txt */ + ff_rew(sncs[snc].time_in_ms); +} + +void switch_mode(bool discard) +{ + if(num_sncaudio_current_track())) + rb->memcpy(id3,rb->audio_current_track(),sizeof(struct mp3entry)); + else return; + /*set_repeat_mode(RMODE_OFF);*/ /* ??? */ + auto_scroll=true; + num_snc=0; + clear_ab(); + time_offset=0; + utf8=false; + modified=0; + time_bar_row=2; + scroll_y0=time_bar_row*fontheight+(sysfont_height<<1)+TIMEBAR_LYRICS_SPACE; + force_update_display=false; + abfile_state=AB_UNKNOWN; + + char* end=cat_time(buf,id3->length); + *end='>'; + *++end=0; + rb->lcd_getstringsize(buf, &indentwidth, NULL); /* update indentwidth */ + } + current_snc=0; + current_snc_edit=0; + stop_scroll_snc_edit_id=MAX_SECTIONS; + bmp_width=NOT_INIT; + if(prefs.load_bmp){ + num_snc=MAX_SECTIONS; /* hack: to ensure that we are in prefs.load_bmp_mode */ + load_bitmap(true); + num_snc=0; + /* if bitmap width is wider than half lcd_width then show bitmap without lyrics */ + if(bmp_width>(LCD_WIDTH>>1)){ + force_update_display=true; + return; + } + } + if(!(load_file(EXT_LRC) || +#ifndef NO_SNC_ID3 + load_file(EXT_MP3) || + load_file(EXT_SNC) || +#endif + load_file(EXT_CUE))){ /* all supported formats failed */ + num_snc=0; + sncs[0].time_in_ms=0; /* to enable ff_rew */ + SET_ROWS((&sncs[0]),0); + if(prefs.load_translation) prefs.load_translation=TR_FAILED; + load_bitmap(true); + } + update_display(); +} + +void save2cue(void){ + int fd = rb->open(lyrics_filename, O_WRONLY|O_CREAT|O_TRUNC); + if(fd<0) return; + + rb->fdprintf(fd,"PERFORMER \"%s\"" NL "TITLE \"%s\"" NL "FILE \"%s\" MP3" NL, + id3->artist,id3->title,id3->path); + int i,last = num_snc-1; + + for(i=1;ifdprintf(fd, + "TRACK %02d AUDIO" NL " TITLE \"%s\"" NL " INDEX 01 %02d:%02d:%02d" NL, + i, sncs[i].x[SCROLL].lyrics[0], + (int) CALC_MM(sncs[i].time_in_ms), + (int) CALC_SS(sncs[i].time_in_ms), + (int) CALC_MS(sncs[i].time_in_ms)); + } + rb->close(fd); +} + +void save2file(void) +{ + if(modified==0) return; + rb->splash(HZ>>1,"Saving..."); + force_update_display=true; + + int r,i; + int last=num_snc-1; + int save_tr=prefs.load_translation==TR_OK? 2:0; + char filename_buf[MAX_PATH]; + char* filename=lyrics_filename; + if(save_tr==2){ + rb->strcpy(filename_buf,lyrics_filename); + filename=filename_buf; + } + char* ext=rb->strrchr(filename,'.')+1; + if(g_ext==EXT_CUE){ + rb->strcpy(ext,FORMATS[EXT_CUE]); /* ensure the extension is cue */ + save2cue(); + modified=0; + time_offset=0; + return; + } + bool is_txt=g_ext==EXT_TXT; + if( is_txt ||(g_ext==EXT_LRC && utf8)) /* lrc: -> rename to lrc8 */ + { + bool complete=false; + i=1; + while(iremove(lyrics_filename); /* remove old file */ + rb->strcpy(ext,FORMATS[utf8 ? EXT_LRC8 : EXT_LRC]); /* rename file to lrc/8 */ + g_ext=EXT_LRC; + } + } + else if(g_ext==EXT_MP3){ /* SYLT */ + g_ext=utf8 ? EXT_LRC8 : EXT_LRC; + rb->strcpy(ext,FORMATS[g_ext]); + } + int fd; + SAVE_TRANSLATION: + fd = rb->open(filename, O_WRONLY|O_CREAT|O_TRUNC); + if(fd<0) return; + + bool is_lrc=g_ext==EXT_LRC || g_ext==EXT_LRC8 || g_ext==EXT_TR; + if(is_lrc){ + rb->fdprintf(fd,"[ti:%s]" NL "[ar:%s]" NL,id3->title,id3->artist); + rb->strcpy(buf,"[%02d:%02d.%02d]"); + } + else if(is_txt){ + rb->strcpy(buf,"[%02d:%02d.%02d]"); + if(utf8) rb->fdprintf(fd,BOM); + } +#ifndef NO_SNC_ID3 + else{ /* snc */ + rb->strcpy(buf,"\xA2\xE2""00%02d%02d%02d\xA2\xD0" NL); + if(utf8){ + int len=rb->strlen(buf)+1; /* +\0 */ + unsigned char utf8_buf[len]; + rb->iso_decode(buf,utf8_buf,-1,len); + rb->strcpy(buf,utf8_buf); + rb->fdprintf(fd,BOM); + } + } +#endif + bool save[last]; + rb->memset(save,true,last); + int pos,j=last; + for(i=1;ifdprintf(fd,buf, + CALC_MM(sncs[pos].time_in_ms), + CALC_SS(sncs[pos].time_in_ms), + CALC_MS(sncs[pos].time_in_ms)); + } + if(is_lrc){ /* [timetag][timetag]..[timetag]lyrics */ + j=pos+1; + for(;jfdprintf(fd,"%s",snc->lyrics[0]); + + for(r=1; rrows;r++){ + unsigned char* pos=snc->lyrics[r]; + if(*(--pos)==0){ + if(*(--pos)!=' ') rb->fdprintf(fd," "); + rb->fdprintf(fd,"%s",snc->lyrics[r]); + } + } + rb->fdprintf(fd,NL); + } + rb->close(fd); + + if(save_tr==2){ + rb->strcpy(ext,FORMATS[EXT_TR]); + save_tr=1; + goto SAVE_TRANSLATION; + } + modified=0; + time_offset=0; + /* set_repeat_mode(-RMODE_ONE); */ +} + +bool yes_no(char* const what){ + const char* items[2] = {"Yes", "No"}; + struct menu_callback_with_desc desc = {NULL,what, Icon_NOICON}; + struct menu_item_ex menu = { + MT_RETURN_ID|MENU_HAS_DESC|MENU_ITEM_COUNT(2), + {.strings = items }, + {.callback_and_desc = &desc} + }; + return rb->do_menu(&menu,NULL,NULL,false)==0; +} + +void confirm_saving(void) +{ + if(modified==0) return; + if(yes_no("Save Changes?")) save2file(); + else modified=0; +} + +void update(void) +{ + if(rb->audio_has_changed_track() && /* new track is playing */ + rb->global_settings->repeat_mode!=RMODE_ONE && + rb->global_settings->repeat_mode!=RMODE_AB){ //no repeat + confirm_saving(); + reset(true); + } + if(num_snc>1){ + unsigned long elapsed = AUDIO_ELAPSED()+UPDATE_LATENCY; + int showSNC=current_snc; + int nextSNC=current_snc; + while(sncs[current_snc].time_in_ms==sncs[++nextSNC].time_in_ms); + if(auto_scroll){ + /* display countdown */ + struct SNCSection* snc=&sncs[current_snc]; + if((SNC(snc,SCROLL).lyrics[0][0]==0 || + SNC(snc,SCROLL).lyrics[0][0]==' ') && + (sncs[nextSNC].time_in_ms - snc->time_in_ms >= 3000)){ + int remain=1+(sncs[nextSNC].time_in_ms-elapsed)/1000; /* +1: 0.9 -> 0 */ + if(remain<6){ + rb->memset(buf,COUNT_DOWN_MARK,remain); + buf[remain]=0; + LCD_CLEAR_AREA(SCREEN_MAIN,bmp_width,scroll_y0,LCD_WIDTH,fontheight); + rb->lcd_putsxy(bmp_width==NOT_INIT ? + get_center_pos(buf,0) : bmp_width,scroll_y0,buf); + rb->lcd_update_rect(0,scroll_y0,LCD_WIDTH,fontheight); + } + } + } + /* update snc */ + while(sncs[nextSNC].time_in_ms <= elapsed || + sncs[current_snc].time_in_ms > elapsed){ /* -> ; <- */ + current_snc=nextSNC%num_snc; + nextSNC=current_snc; + while(sncs[current_snc].time_in_ms==sncs[++nextSNC].time_in_ms); /* update next snc */ + if(showSNC==current_snc) + sncs[num_snc].time_in_ms=NOT_INIT; /* ensure snc[0] < elapsed time < snc[max] */ + } + if(showSNC!=current_snc){ + if(auto_scroll) scroll_snc(); + else if((current_snc+1)>=current_snc_edit && + current_snc=stop_snc))) + ff_rew_snc(start_snc); + } + exec_func(0,NULL); +} + +int insert_sections(void){ + if(start_snc+1==num_snc || current_snc_edit+1==num_snc || + (start_snc current_snc_edit)) + return false; + if(start_snc==NOT_INIT) start_snc=0; /* insert blank */ + int offset=sncs[current_snc_edit].time_in_ms-sncs[start_snc].time_in_ms + 1; + int nr2Copy=(stop_snc==NOT_INIT)?1:stop_snc-start_snc; + num_snc+=nr2Copy; + if(num_snc>=MAX_SECTIONS){ + rb->splash(HZ,"Not Enough Sections!"); + num_snc=MAX_SECTIONS-1; + } + int i=num_snc; + for(;i>current_snc_edit+nr2Copy;i--)/* move */ + snc_copy(&sncs[i],&sncs[i-nr2Copy],0); + if(start_snc>current_snc_edit) start_snc+=nr2Copy; + + for(i=0;inum_snc-1) current_snc_edit=num_snc-1; + stop_scroll_snc_edit_id=MAX_SECTIONS; + return RET_OK; +} + +int join_sections(void){ + if(current_snc_edit>=num_snc-2 || current_snc_edit==0 || prefs.load_translation==TR_OK) + return false; + /* need to save reference pointer because current_snc_edit is not fixed */ + unsigned char* join_ref1=sncs[current_snc_edit].x[SCROLL].lyrics[0]; + unsigned char* join_ref2=sncs[current_snc_edit+1].x[SCROLL].lyrics[0]; + unsigned char* lyrics; + unsigned long time=sncs[current_snc_edit].time_in_ms; /* save time for restoring current_snc_edit */ + int i; + for(i=num_snc-3;i>0;i--){ /* look for other lines with the same constellation */ + if(lrccmp(join_ref1,sncs[i].x[SCROLL].lyrics[0])==0 + && lrccmp(join_ref2,sncs[i+1].x[SCROLL].lyrics[0])==0){ + + lyrics=sncs[i+1].x[SCROLL].lyrics[0]; + if(*(lyrics-1)==0){ + rb->strcpy(buf,sncs[i+1].x[SCROLL].lyrics[0]); + *(lyrics-1)=' '; /* replace \0 with ' ' */ + while(*(lyrics-2)==' ') --lyrics; /* remove spaces */ + rb->strcpy(lyrics,buf); + } + store_line(&sncs[i],sncs[i].x[SCROLL].lyrics[0],true); /* reformat */ + clear_ab(); /* make sure there ab is not set */ + current_snc_edit=i; /* make sure current_snc_edit fulfills the deletion criteria */ + start_snc=i+1; /* point to merged sync */ + delete_sections(); /* and get rid of it (does other housekeeping as well) */ + } + } + for(i=1;iglobal_settings->repeat_mode!=RMODE_ONE; + set_repeat_mode(RMODE_ONE); + char* lyrics=sncs[current_snc_edit].x[SCROLL].lyrics[0]; + rb->strcpy(buf,lyrics); + bool changed; + if((changed=!rb->kbd_input(buf,BUFFERSIZE))) + { + unsigned length=rb->strlen(buf); + /* append the edited lyrics at the end of the lyrics buffer */ + /* lyrics buffer: line1_line2_..lineN_new */ + struct SNCSection* snc=&sncs[current_snc_edit]; + SET_LYRICS(snc,&lyrics_buffer[lyrics_buffer_used],0); + lyrics=snc->x[SCROLL].lyrics[0]; + rb->strcpy(lyrics,buf); + store_line(snc,lyrics,true); /* format */ + /* check if there's enough space left? */ + lyrics_buffer_used+=length+1; /* +1: \0 */ + modified|=MODIFIED_LYRICS; + } + if(restore) set_repeat_mode(-RMODE_ONE); + update(); + return changed; +} +int edit_and_dup_lines(void){ + size_t size; + /* backup old line */ + unsigned char* old_lyrics=rb->plugin_get_buffer(&size); + rb->strcpy(old_lyrics,sncs[current_snc_edit].x[SCROLL].lyrics[0]); + if(edit_line()){ + int i; + for(i=1;ilcd_clear_display(); + rb->lcd_puts(0,0,title); + do{ + if(val!=time){ + val=time<0?-time:time; + rb->snprintf(buf,BUFFERSIZE,"%c" TIME_FORMAT ".%01d", + time<0?'-':'+', CALC_MM(val),CALC_SS(val),(val%1000)/100); + rb->lcd_puts(0,1,buf); + rb->lcd_update(); + val=time; + } + button = pluginlib_getaction(rb, HZ, plugin_contexts, NB_ACTION_CONTEXTS); + + if(button==SNC_UP_REL || button==SNC_DOWN_REL){ + accel_counter=0; + step_size=step; + } + else if(button==SNC_UP_HOLD || button==SNC_DOWN_HOLD) + if(++accel_counter>20) step_size=step*accel_counter>>1; + + switch(button){ + case SNC_NEXT: time+=step/5; break; + case SNC_PREV: time-=step/5; break; + case SNC_SELECT_OR_MODE: + *init_time=time; + case SNC_QUIT: + loop=false; + break; + case SNC_UP_REL: + case SNC_UP_HOLD: time = time INC step_size; break; + case SNC_DOWN_REL: + case SNC_DOWN_HOLD: time = time DEC step_size; break; + default:; + } + if(timeaudio_has_changed_track()){ /* track has changed -> exit */ + return 0; + } + rb->yield(); + } while(loop); + return button; +} +int set_time_offset(void) +{ + if(num_snc<1) return false; + int offset=time_offset; /* save old value */ + if(set_snc_time_screen("Time Offset:",500, + &time_offset,-id3->length)==SNC_QUIT){ /* cancelled */ + return RET_OK; + } + offset=time_offset-offset; /* calculate change */ + if(offset!=0){ + int start=1, stop=num_snc-1; + if(start_snc!=NOT_INIT){ + start=start_snc; + if(stop_snc!=NOT_INIT) stop=stop_snc; + } + int i=start; + for(;i id3->length){ + sncs[i].time_in_ms = NOT_INIT; + } + } + } + modified=(time_offset!=0)? + modified|MODIFIED_OFFSET_TIMETAG:modified&~MODIFIED_OFFSET_TIMETAG; + update_display(); + return RET_OK; +} + +int auto_cue(void){ + if(num_snc<=1) g_ext=EXT_CUE; /* no sncs => set extension to cue */ + else if(num_snc>1 && g_ext!=EXT_CUE) return 0; + static int interval=120000; + if(set_snc_time_screen("Cue Interval:",60000, + &interval,60000)==SNC_QUIT) /* cancelled */ + return RET_OK; + rb->splash(HZ,"Cueing ..."); + /* */ + lyrics_buffer_used=0; + SET_LYRICS((&sncs[0]),lyrics_buffer,0); + + init_snc(&sncs[0],true); + num_snc=1; /* start with snc = 1 */ + SET_LYRICS((&sncs[num_snc]),sncs[0].x[SCROLL].lyrics[0]+1,0); + struct SNCText* snc=NULL; + num_snc=id3->length/interval+1; /* 3 min */ + int i=1; + while(i<=num_snc){ + snc=&(sncs[i].x[SCROLL]); + sncs[i].time_in_ms=(i-1)*interval+1; + int bytes=rb->snprintf(snc->lyrics[0],9,"Track%d",i)+1; /* TrackXXX */ + SET_ROWS((&sncs[i]),1); + SET_LYRICS((&sncs[i]),snc->lyrics[0],0); + SET_LYRICS((&sncs[i]),snc->lyrics[0]+bytes,1); + sncs[++i].x[SCROLL].lyrics[0]=snc->lyrics[snc->rows]; + } + num_snc++; + lyrics_buffer_used=sncs[num_snc].x[SCROLL].lyrics[0]-lyrics_buffer; + append_end_section(); + + if(!prefs.load_bmp) bmp_width=NOT_INIT; + modified=MODIFIED_TIMETAG; +/* save2cue(); */ +/* reset(false); */ + return RET_OK; +} + +#ifdef CUSTOM_PLUGIN_PATCH +#define MAX_INDEX 100 +#define KEY_LENGTH 3 +struct dict_index_struct{ + char key[KEY_LENGTH]; + unsigned int pos; +}; +unsigned int get_dict_pos(const struct dict_index_struct* dict_index, + const char* word){ + int i=1; + int old=0,res=0; + int len=rb->strlen(word); + if(len>KEY_LENGTH) len=KEY_LENGTH; + for(;istrncasecmp(word,dict_index[i].key,len); + if(res>res+old) /* sign changed */ + break; + else + old=res; + } + i=(i-2)%MAX_INDEX; + DEBUGF("start key: %s\n",dict_index[i].key); + return dict_index[i].pos; +} + +bool create_dict_index(struct dict_index_struct* dict_index, + const char* dict, const char* idx){ + int fd=rb->open(dict, O_RDONLY); + if(fd<0) return -1; + rb->splash(HZ,"Creating Index ..."); + int len; + int section_size=rb->filesize(fd)/(MAX_INDEX-1); + int total_len=0; + rb->read(fd,&(dict_index[0].key),KEY_LENGTH); + dict_index[0].pos=0; + int i=1; + size_t size; + unsigned char* buffer=rb->plugin_get_buffer(&size); + while((len=rb->read_line(fd, buffer, size))>0){ + total_len+=len; + if(total_len>section_size*i){ + rb->strncpy(dict_index[i].key,buffer,KEY_LENGTH); + dict_index[i++].pos=total_len-len; + } + } + rb->close(fd); + fd = rb->creat(idx); + rb->write(fd, dict_index, sizeof(struct dict_index_struct)*MAX_INDEX); + rb->close(fd); + return 0; +} + +int lookup_word(void){ + const int max_words=30; + struct Dict_Entry{ + unsigned char* word; + int length; + } word_list[max_words]; + struct dict_index_struct dict_index[MAX_INDEX]; + unsigned char* new_pos; + unsigned char* pos=sncs[current_snc_edit].x[SCROLL].lyrics[0]; + int char_w, byte,i=0; + bool utf8_dict=false; + char* items[max_words]; + items[0]=buf; + while(*pos != 0 && i1) utf8_dict=true; + } while(byte<3 && !(is_wrap_char(new_pos) || *new_pos==0)); + rb->strncpy(items[i],pos,word_list[i].length); /* put word to item list */ + items[i][word_list[i].length]=0; /* terminate word */ + items[i+1]=items[i]+word_list[i].length+1; /* increase item */ + pos=new_pos; + i++; + } + if(i==0) return 0; + + int num_words=i; + struct menu_callback_with_desc desc = + {NULL,sncs[current_snc_edit].x[SCROLL].lyrics[0], Icon_NOICON}; + struct menu_item_ex words = { + MT_RETURN_ID|MENU_HAS_DESC|MENU_ITEM_COUNT(num_words), + {.strings = (const char **) items}, + {.callback_and_desc = &desc} + }; + char* idx=SNCVIEWER_DATA_DIR"/ed.idx"; + char* dict=SNCVIEWER_DATA_DIR"/ed.dict"; + if(utf8_dict){ + idx=SNCVIEWER_DATA_DIR"/ce.idx"; + dict=SNCVIEWER_DATA_DIR"/ce.dict"; + } + int fd=rb->open(idx, O_RDONLY); + if(fd >= 0){ + rb->read(fd,dict_index,sizeof(dict_index)); + rb->close(fd); + } + else if(create_dict_index(dict_index,dict,idx)<0){ + rb->splash(HZ, "Cannot Open Dictionary!"); + return 0; + } + fd=rb->open(dict, O_RDONLY); /* open dictionary */ + int selected=0; + while(rb->do_menu(&words, &selected,NULL,false)>=0){ + rb->splash(HZ, "Searching..."); + char* entries[max_words]; + size_t size; + entries[0]=rb->plugin_get_buffer(&size); + i=0; + int len; + unsigned int total=0; + rb->lseek(fd,get_dict_pos(dict_index,word_list[selected].word),SEEK_SET); + while((len=rb->read_line(fd, entries[i], BUFFERSIZE))>0 && istrncasecmp(word_list[selected].word, + entries[i],word_list[selected].length)==0){/* found */ + entries[i+1]=entries[i]+len+1; + i++; + } + else if(i!=0) break; + total+=len; + if(total>dict_index[1].pos<<1) break; /* don't have to search further */ + } + if(i>0){ + struct menu_callback_with_desc entry_desc = + {NULL,word_list[selected].word, Icon_NOICON}; + struct menu_item_ex entry_list = { + MT_RETURN_ID|MENU_HAS_DESC|MENU_ITEM_COUNT(i), + {.strings = (const char **) entries}, + {.callback_and_desc = &entry_desc} + }; + rb->do_menu(&entry_list, NULL,NULL,false); + } + else rb->splash(HZ, "Word '%s' not found!",items[selected]); + } + rb->close(fd); + rb->lcd_stop_scroll(); + return RET_OK; +} +#endif + +void delete_lyrics_file(void){ + if(num_snc<2) return; + char* ext=rb->strrchr(lyrics_filename,'/')+1; + rb->snprintf(buf,BUFFERSIZE,"Delete '%s'?",ext); + if(yes_no(buf)){ + rb->remove(lyrics_filename); /* remove lyrics file */ + if(prefs.load_translation==TR_OK){ + ext=rb->strrchr(lyrics_filename,'.')+1; + rb->strcpy(ext,FORMATS[EXT_TR]); + rb->remove(lyrics_filename); /* also remove translation file */ + } + reset(true); /* reset to ensure all is done */ + } +} + +void show_menu(void) +{ + static int edit_selected=0; + static int scroll_selected=0; + + #define SCROLL_ITEMS "Time Offset...","Repeat 1","Auto Cue...","Album Art",\ + "Translation", "Peakmeter", "Delete File..." + #define SCROLL_ITEM_OFFSET 10 +#ifdef CUSTOM_PLUGIN_PATCH + #define GENERAL_ITEMS SCROLL_ITEMS,"ID3 Info","Playlist","Files" + #define EDIT_INDEX_MAX 5 +#else + #define GENERAL_ITEMS SCROLL_ITEMS + #define EDIT_INDEX_MAX 4 /* must increase each time a new edit entry is added */ +#endif + + MENUITEM_STRINGLIST(edit_menu,"EDIT",NULL, + "Delete (A-B/current)","Insert (A-B/blank)","Join Sections","Edit+","Edit" +#ifdef CUSTOM_PLUGIN_PATCH + ,"Dictionary" +#endif + ,GENERAL_ITEMS + ); + MENUITEM_STRINGLIST(scroll_menu,"MENU",NULL,GENERAL_ITEMS); + + int ret=0; + if(auto_scroll){ + ret=rb->do_menu(&scroll_menu,&scroll_selected,NULL,false)+SCROLL_ITEM_OFFSET; + } + else{ + ret=rb->do_menu(&edit_menu,&edit_selected,NULL,false); + if(ret>EDIT_INDEX_MAX) ret+=SCROLL_ITEM_OFFSET-EDIT_INDEX_MAX-1; + } + switch(ret){ + /* only edit */ + case 0: delete_sections(); break; + case 1: insert_sections(); break; + case 2: join_sections(); break; + case 3: edit_and_dup_lines(); break; + case 4: edit_line(); break; +#ifdef CUSTOM_PLUGIN_PATCH + case 5: lookup_word(); break; +#endif + /* general */ + case 10: set_time_offset(); break; + case 11: /* repeat 1 */ + set_repeat_mode(rb->global_settings->repeat_mode!=RMODE_ONE?RMODE_ONE:-RMODE_ONE); + break; + case 12: auto_cue(); break; + case 13: /* switch album art */ + prefs.save^=PREF_BMP; + prefs.load_bmp=!prefs.load_bmp; + if(!prefs.load_bmp) bmp_width=NOT_INIT; + reset(false); + break; + case 14: /* load translation */ + confirm_saving(); + prefs.save^=PREF_TRANSLATION; + prefs.load_translation=!prefs.load_translation; + if(prefs.load_translation){ + load_file(EXT_TR); + stop_scroll_snc_edit_id=MAX_SECTIONS; + update_display(); + } + else reset(false); + break; + case 15: + prefs.save^=PREF_PEAKMETER; + prefs.peakmeter=!prefs.peakmeter; + break; + case 16: + delete_lyrics_file(); + break; +#ifdef CUSTOM_PLUGIN_PATCH + case 17: rb->browse_id3(); break; + case 18: + rb->playlist_viewer(); + force_update_display=true; + update(); + break; + case 19: + rb->rockbox_browse(id3->path,SHOW_MUSIC); + force_update_display=true; + update(); + break; +#endif + } + update_display(); +} + +void set_time_tag(void) +{ + if(auto_scroll || current_snc_edit==0 || current_snc_edit+1==num_snc) + return; + unsigned long t = AUDIO_ELAPSED()-SET_TIME_LATENCY; /* reaction time */ + const int delta = prefs.load_translation!=TR_OK ? 1:2; + if(prefs.load_translation != TR_OK){ + sncs[current_snc_edit].time_in_ms=t; + } + else{/* synchronze all sncs with the same time */ + unsigned long ref = sncs[current_snc_edit].time_in_ms; + int i; + for(i=0;ilcd_setfont(FONT_SYSFIXED); + rb->lcd_getstringsize("0", &indentwidth, &sysfont_height); + rb->lcd_setfont(FONT_UI); + /* init indentwidth, fontheight */ + rb->lcd_getstringsize("0:00>", &indentwidth, &fontheight); + /* statusrows at top and bottom -> 2*sysfont_height */ + lcd_max_rows=(LCD_HEIGHT-2*sysfont_height)/fontheight; + artist_title_row_height=sysfont_height; + remote_artist_title_row_height=sysfont_height; + bmp_width=NOT_INIT; + /* init settings (will be overwritten by loading the settings file) */ + prefs.load_bmp=true; + prefs.backlight=true; + prefs.save=PREF_SAVE; + prefs.load_translation=TR_OFF; + prefs.peakmeter=true; + /* load settings */ + int settings_fd=rb->open(SETTINGS_FILE, O_RDONLY); + if (settings_fd >= 0) + { + if((unsigned) rb->filesize(settings_fd)<=sizeof(struct Preferences)) + rb->read(settings_fd, &prefs, sizeof(struct Preferences)); + rb->close(settings_fd); + } + if(prefs.backlight) force_backlight_on(); + reset(true); +} + +int handle_button_scroll(int button){ + switch (button) { + case SNC_BACKLIGHT_OR_STAMPTIME: + prefs.save^=PREF_BACKLIGHT; + prefs.backlight=!prefs.backlight; + prefs.backlight? force_backlight_on():default_backlight(); + break; + case SNC_DOWN_REL: + case SNC_DOWN_HOLD: + set_volume(rb->global_settings->volume DEC 1); + break; + case SNC_UP_REL: + case SNC_UP_HOLD: + set_volume(rb->global_settings->volume INC 1); + break; + case SNC_PREV: + check_next_action(SNC_LEFT,SNC_PREV)? + rb->audio_prev():ff_rew_snc(start_snc!=NOT_INIT?start_snc:0); + break; + case SNC_NEXT: + (num_snc > 1 && check_next_action(SNC_RIGHT,SNC_NEXT)) ? + ff_rew_snc(current_snc+(prefs.load_translation!=TR_OK ? 1:2)): + rb->audio_next(); + break; + case SNC_SELECT_OR_MODE: + switch_mode(false); + break; + default:button=ACTION_NONE; + } + return button; +} +int handle_button_edit(int button){ + switch (button) { + case SNC_BACKLIGHT_OR_STAMPTIME: + set_time_tag(); + break; + case SNC_DOWN_HOLD: + if(current_snc_edit=num_snc) current_snc_edit = 1; + browse_snc(); + break; + case SNC_UP_HOLD: + if(current_snc_edit>2) current_snc_edit--; + else break; + case SNC_UP_REL: + if(--current_snc_edit<0) current_snc_edit = num_snc-2; + browse_snc(); + break; + case SNC_SELECT_OR_MODE: + ff_rew_snc(current_snc_edit); + break; + case SNC_PREV: + switch_mode(true); + break; + case SNC_NEXT: + switch_mode(false); + break; + default: + button=ACTION_NONE; + } + return button; +} + +/* this is the plugin entry point */ +enum plugin_status plugin_start(struct plugin_api* api, void* parameter) +{ + /* if you are using a global api pointer, don't forget to copy it! + otherwise you will get lovely "I04: IllInstr" errors... :-) */ + rb = api; + if((rb->audio_status() & AUDIO_STATUS_PLAY)==false){ + rb->splash(HZ<<1,"No Audio Playing!"); + return PLUGIN_OK; + } + /* if you don't use the parameter, you can do like + this to avoid the compiler warning about it */ + (void)parameter; +#if defined(HAVE_LCD_COLOR) + change_fg_color=rb->lcd_get_background()==LCD_DEFAULT_BG && + rb->lcd_get_foreground()==LCD_DEFAULT_FG; +#endif +#if LCD_DEPTH > 1 + rb->lcd_set_backdrop(NULL); +#endif + init(); + int button; + long newPos=0; + int timeout = HZ>>1; + #define PLUGIN_STAY 333 + int status=PLUGIN_STAY; /* see enum plugin_status */ + do{ + button=pluginlib_getaction(rb, timeout, plugin_contexts, NB_ACTION_CONTEXTS); + switch (button) { /* valid for both modes */ +#ifdef HAS_AB_BUTTON + case SNC_AB: + set_ab_marker(); + update_display(); + break; + case SNC_AB_MENU: + ab_menu(); + update_display(); + break; +#endif + case SNC_SAVE: + save2file(); + break; + case SNC_PLAY_PAUSE: + (rb->audio_status() & AUDIO_STATUS_PAUSE) ? + rb->audio_resume():rb->audio_pause(); + exec_func(1,update_status_display); + break; + case SNC_AUDIO_REW: + case SNC_AUDIO_FF: + ff_rew(newPos); + break; + case SNC_LEFT: + case SNC_RIGHT: + newPos=AUDIO_ELAPSED(); break; + case SNC_VISUAL_REW: + newPos-=4000; + case SNC_VISUAL_FF: + newPos+=2000; + newPos=update_time_display(newPos,true); + break; + case SNC_QUIT: + status=PLUGIN_OK; + break; + case SNC_OPEN_MENU: show_menu(); break; + default: + if(rb->default_event_handler(button)==SYS_USB_CONNECTED) + status=PLUGIN_USB_CONNECTED; + if((rb->audio_status() & AUDIO_STATUS_PLAY)==false) + status=PLUGIN_OK; + if((auto_scroll ? handle_button_scroll(button): + handle_button_edit(button))==ACTION_NONE) + update(); /* only update if no button was pressed */ + if(prefs.peakmeter && num_snc<2) peak_meter(); + (!force_update_display)? + update_time_display(AUDIO_ELAPSED(),!auto_scroll) : update_display(); + } +#ifndef SIMULATOR + if(rb->ata_disk_is_active()) rb->yield(); +#endif + } while(status==PLUGIN_STAY); + /* clean up before quit */ + confirm_saving(); + default_backlight(); /* restore backlight */ + set_repeat_mode(RMODE_OFF); + if(prefs.save){ + prefs.save=0; + int settings_fd = rb->creat(SETTINGS_FILE); /* create the settings file */ + rb->write(settings_fd, &prefs, sizeof(struct Preferences)); + rb->close(settings_fd); + } + return status; +} Index: apps/plugins/SOURCES =================================================================== --- apps/plugins/SOURCES (revision 16821) +++ apps/plugins/SOURCES (working copy) @@ -19,6 +19,7 @@ stopwatch.c vbrfix.c viewer.c +sncviewer.c #ifdef OLYMPUS_MROBE_500 /* remove these once the plugins before it are compileable */ Index: apps/plugins/CATEGORIES =================================================================== --- apps/plugins/CATEGORIES (revision 16821) +++ apps/plugins/CATEGORIES (working copy) @@ -95,3 +95,4 @@ wormlet,games xobox,games zxbox,viewers +sncviewer,apps