Index: apps/plugins/sncviewer.c =================================================================== --- apps/plugins/sncviewer.c (revision 0) +++ apps/plugins/sncviewer.c (revision 0) @@ -0,0 +1,2885 @@ +#include "plugin.h" + +PLUGIN_HEADER + +/******************************************/ +/* Keymapping */ +#define CONTEXT_SNC CONTEXT_WPS +#define SNC_SELECT_OR_MODE ACTION_WPS_BROWSE +#define SNC_PLAY_PAUSE ACTION_WPS_PLAY +#define SNC_VISUAL_REW ACTION_WPS_SEEKBACK +#define SNC_VISUAL_FF ACTION_WPS_SEEKFWD +#define SNC_STOP_SEEK ACTION_WPS_STOPSEEK +#define SNC_NEXT ACTION_WPS_SKIPNEXT +#define SNC_PREV ACTION_WPS_SKIPPREV +#define SNC_QUIT ACTION_WPS_STOP +#define SNC_DEC ACTION_WPS_VOLDOWN +#define SNC_INC ACTION_WPS_VOLUP +#define SNC_OPEN_MENU ACTION_WPS_CONTEXT +#define SNC_SAVE ACTION_WPS_REC +#define SNC_AB_MENU ACTION_WPS_QUICKSCREEN + +#ifdef CUSTOM_PATCH + #define SNC_TIMESTAMP_BL ACTION_WPS_CUSTOM /* button rec */ + #define SNC_AB ACTION_WPS_MENU +#else + #define SNC_TIMESTAMP_BL ACTION_WPS_MENU + #define SNC_AB ACTION_WPS_ABRESET +#endif + +#ifdef HAVE_SCROLLWHEEL /* iPods and Sansa e200 */ + #define SNC_LIST_UP ACTION_WPS_VOLDOWN + #define SNC_LIST_DOWN ACTION_WPS_VOLUP +#else + #define SNC_LIST_UP ACTION_WPS_VOLUP + #define SNC_LIST_DOWN ACTION_WPS_VOLDOWN +#endif +/******************************************/ +#define APP_NAME "sncviewer" +/* Color definitions */ +#define SNC_INACTIVE_COLOR LCD_LIGHTGRAY +#define SNC_FG_COLOR LCD_BLACK +#define SNC_BG_COLOR LCD_WHITE + +/******************************************/ +#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 1000 +#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 RET_NOK -1 +#define TIMEBAR_LYRICS_SPACE 3 +#define NL "\r\n" +#define BREAK_LENGTH 3000 + +/* 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_SCROLL_LIMIT 16 +#define PREF_INVERT_DISPLAY 32 +#define PREF_SAVE 0xFFFF + +#define SCROLL_LIMIT_MARKER_WIDTH 3 +#define SCROLL_LIMIT_POS_X LCD_WIDTH-SCROLL_LIMIT_MARKER_WIDTH +#define SCROLL_LIMIT_MARKER "<" +/* 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]=='/') +/* spaces at the beginning are skipped, so it's not necessary to check for a space */ +#define IS_BLANK(id) sncs[id].x[SCROLL].lyrics[0][0] == 0 +#define NO_LYRICS() (num_snc < 2) + +#define SNCVIEWER_DATA_DIR PLUGIN_APPS_DIR +#define SETTINGS_FILE SNCVIEWER_DATA_DIR"/"APP_NAME".dat" +#define TIME_BAR_ROW 2 +/* structs */ +struct Preferences { + int load_bmp; + bool backlight; + int save; + int load_translation; + bool peakmeter; + int scroll_limit_y; + bool invert_display; + int fontheight; +}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 const 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, 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 scroll_y0; +static int artist_title_row_height,remote_artist_title_row_height; +static int bmp_width; +static int bmp_height; +static const int DOUBLE_CLICK_TICKS = HZ>>2; +static char language[3]; + +enum e_abfile_state{AB_NO,AB_YES,AB_UNKNOWN}; + +static enum e_abfile_state abfile_state; +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 */ +} + +int rb_menu(const struct menu_item_ex *menu, int *start_selected){ + /* don't invert the colors */ +#ifdef HAVE_LCD_INVERT + if(prefs.invert_display) + rb->lcd_set_invert_display(false); +#endif + int ret = rb->do_menu(menu, start_selected, NULL, false); +#ifdef HAVE_LCD_INVERT + if(prefs.invert_display) + rb->lcd_set_invert_display(true); +#endif + return ret; +} + +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=prefs.fontheight; + if(y+height>LCD_HEIGHT) y+=sysfont_height-prefs.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){ + if(set_sysfont) rb->lcd_setfont(FONT_SYSFIXED); + 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); + return set_sysfont ? sysfont_height:prefs.fontheight; +} + +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=prefs.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+=prefs.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+prefs.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=auto_scroll ? TIME_BAR_ROW*prefs.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; + int x_artist=get_center_pos(artist,x); + int x_title=get_center_pos(title,x); +#ifdef CUSTOM_PLUGIN_PATCH +#if LCD_DEPTH == 16 + #define RB_LCD_BITMAP rb->lcd_bitmap_transparent +#else + #define RB_LCD_BITMAP rb->lcd_bitmap +#endif + RB_LCD_BITMAP(sncviewer_artist, + x_artist-x, sysfont_height, BITMAP_SIZE, BITMAP_SIZE); + RB_LCD_BITMAP(sncviewer_title, + x_title-x, sysfont_height+prefs.fontheight, BITMAP_SIZE, BITMAP_SIZE); +/* } */ +#endif + rb->lcd_putsxy(x_artist,sysfont_height,artist); + rb->lcd_putsxy(x_title,sysfont_height+prefs.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 && !NO_LYRICS(); + bool invert_image = true; + 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"/"APP_NAME".bmp"); + invert_image = false; + } + bmp_size=rb->read_bmp_file(buf, &bm, BITMAP_BUFFERSIZE, FORMAT_ANY); + if(bmp_size>0 && prefs.invert_display && invert_image){ + long i=0; + for(;i0){ + if(prefs.load_bmp==1 || NO_LYRICS()){ + bmp_width = bm.width+2; + bmp_height = NOT_INIT; + } + else{ + bmp_width = NOT_INIT; + bmp_height=bm.height+1; + } + int x=(LCD_WIDTH-bm.width)>>1; + if(load_bmp_mode && prefs.load_bmp==1 && (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 */ + bmp_height=NOT_INIT; + } +} + +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(SNC_INACTIVE_COLOR); + if(ilcd_fillrect(i*step,y2,w2,bar_frame_height); + } + if(ilcd_fillrect(xr,y2,w2,bar_frame_height); + } + rb->lcd_set_foreground(SNC_FG_COLOR); + 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) +{ + bool set_sysfont=false; + int r; + const int max_y = LCD_HEIGHT - artist_title_row_height - prefs.fontheight - + (bmp_height == NOT_INIT ? 0 : bmp_height); + LCD_CLEAR_AREA(SCREEN_MAIN, bmp_width!=NOT_INIT ? bmp_width:0, + scroll_y0, LCD_WIDTH, max_y + prefs.fontheight - scroll_y0); + /* show the vertical scroll limit */ + rb->lcd_putsxy(SCROLL_LIMIT_POS_X, prefs.scroll_limit_y,SCROLL_LIMIT_MARKER); + + static int current_y0 = LCD_HEIGHT; + int id=current_snc; + struct SNCText* snc; + snc=&(sncs[id].x[SCROLL]); + int tr_height = prefs.load_translation==TR_OK && + sncs[id].time_in_ms==sncs[id+1].time_in_ms ? + sncs[id+1].x[SCROLL].rows * sysfont_height : 0; + /* position the current lyrics line */ + if(current_y0 > prefs.scroll_limit_y) + current_y0 = prefs.scroll_limit_y; + if((max_y < current_y0 + (snc->rows * prefs.fontheight) + tr_height) /* height exceeded */ + || id == 0 /* the first */ + || (IS_BLANK(id) && + sncs[id+1].time_in_ms >= sncs[id].time_in_ms + BREAK_LENGTH) + || (id>0 && IS_BLANK(id-1) && (scroll_y0 + prefs.fontheight == current_y0))) + current_y0 = scroll_y0; + + /* printing the lines on screen */ + int y=current_y0; + int new_current_y0 = current_y0; + while(idrows;r++){ + if(y>max_y) goto END_PRINT_LINES; /* quit the nested loop */ + 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){ + rb->lcd_set_foreground(SNC_INACTIVE_COLOR); + if(sncs[id].time_in_ms==sncs[current_snc].time_in_ms){ + new_current_y0 = y; + y+=((max_y-scroll_y0)%prefs.fontheight)>>1; /* additional space */ + } + } + snc=&(sncs[++id].x[SCROLL]); + } + END_PRINT_LINES: + /* show previous lines */ + y = current_y0; + id = current_snc; + while(id>1){ + snc=&(sncs[--id].x[SCROLL]); + set_sysfont=prefs.load_translation==TR_OK && + sncs[id].time_in_ms==sncs[id-1].time_in_ms; + for(r=snc->rows-1; r>=0; r--){ + y-=set_sysfont ? sysfont_height:prefs.fontheight; + if(ylcd_putsxy,bmp_width,y); + } + } + END_PRINT_PREVIOUS_LINES: + current_y0 = new_current_y0; + rb->lcd_set_foreground(SNC_FG_COLOR); /* restore foreground color */ + +#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/prefs.fontheight; + for(r=0;rrows && rlcd_remote_putsxy,0, + r*prefs.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(!NO_LYRICS()){ + if(prefs.load_bmp && (bmp_width>0 || bmp_height>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; +} + +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 set_ab_marker(void) +{ + if(NO_LYRICS()) 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(snc0) i+=offset; + switch(i){ + case 0: set_ab_marker(); break; + case 1: clear_ab(); break; + case 2: /* 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 3: /* load */ + ab_file(true); + break; + case 4: /* 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_volume(int vol) +{ + if(vol < rb->sound_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; + if(width != NULL){ + /* get char width */ + 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); + } + /* remove trailing spaces */ + unsigned char* end = (unsigned char*) buffer + rb->strlen(buffer) - 1; + while(*end == ' ') *end-- = 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; + } + 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(NO_LYRICS()) 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_sncstrncmp(buffer,"[la:",4)==0){ /* language tag */ + if(buffer[4] != ']'){ + language[0]=buffer[4]; + language[1]=buffer[5]; + language[2]=0; + } + continue; + } + else continue; /* not handled lrc tag */ + struct SNCText* snc=&(sncs[num_snc].x[SCROLL]); + unsigned char* lyrics=snc->lyrics[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; + bmp_height=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)); + if(id3->title == NULL){ + id3->title = rb->strrchr(id3->path,'/')+1; + id3->artist = ""; + } + } + else return; + /*set_repeat_mode(RMODE_OFF);*/ /* ??? */ + auto_scroll=true; + num_snc=0; + clear_ab(); + time_offset=0; + utf8=false; + modified=0; + 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 */ + } + language[0] = 0; + 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){ + if(language[0]!=0){ + rb->fdprintf(fd,"[la:%s]" NL,language); + } + 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_Questionmark}; + struct menu_item_ex menu = { + MT_RETURN_ID|MENU_HAS_DESC|MENU_ITEM_COUNT(2), + {.strings = items }, + {.callback_and_desc = &desc} + }; + return rb_menu(&menu,NULL)==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); + } + exec_func(0,NULL); /* update func queue */ + if(NO_LYRICS()) return; + + unsigned long elapsed = AUDIO_ELAPSED()+UPDATE_LATENCY; + int show_snc=current_snc; + int next_snc=current_snc; + while(sncs[current_snc].time_in_ms==sncs[++next_snc].time_in_ms); + if(auto_scroll){ + /* display countdown */ + struct SNCSection* snc=&sncs[current_snc]; + if(IS_BLANK(current_snc) && + (sncs[next_snc].time_in_ms >= snc->time_in_ms + BREAK_LENGTH)){ + int remain=1+(sncs[next_snc].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,prefs.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,prefs.fontheight); + } + } + } + /* update snc */ + while(sncs[next_snc].time_in_ms <= elapsed || + sncs[current_snc].time_in_ms > elapsed){ /* -> ; <- */ + current_snc=next_snc%num_snc; + next_snc=current_snc; + while(sncs[current_snc].time_in_ms==sncs[++next_snc].time_in_ms); /* update next snc */ + if(show_snc==current_snc) + sncs[num_snc].time_in_ms=NOT_INIT; /* ensure snc[0] < elapsed time < snc[max] */ + } + if(show_snc!=current_snc){ + if(auto_scroll) scroll_snc(); + else if((current_snc+1)>=current_snc_edit && + current_snc=stop_snc))) + ff_rew_snc(start_snc); +} + +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 = rb->get_action(CONTEXT_SNC,HZ); + 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_INC: time += step_size; break; + case SNC_DEC: time -= 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(NO_LYRICS()) 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(NO_LYRICS()) g_ext=EXT_CUE; /* no sncs => set extension to cue */ + else if(NO_LYRICS() == false && g_ext!=EXT_CUE) return RET_NOK; + 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; + bmp_height=NOT_INIT; + } + modified=MODIFIED_TIMETAG; +/* save2cue(); */ +/* reset(false); */ + return RET_OK; +} + +/****************************************************************/ +/* dictionary definitions only */ +#define DICT_EXT "dic" +#define MAX_INDEX 100 +#define MAX_WORDS 30 +#define MAX_DICTS 10 +#define KEY_LENGTH 3 +#define LF 10 +struct dict_index_struct{ + unsigned long pos; + char key[KEY_LENGTH]; +}; + +unsigned long get_dict_pos(const struct dict_index_struct* dict_index, + const char* word){ + int i=1; + int sign=NOT_INIT,res=0; + int len=rb->strlen(word); + if(len>KEY_LENGTH) len=KEY_LENGTH; + for(;istrncasecmp(word,dict_index[i].key,len)) == 0) + break; + + if(sign != NOT_INIT){ + if(sign != (res >= 0)) /* sign has changed! */ + break; + } + else + sign = res >= 0; /* sign = 1 if res positiv */ + + } + if(--i < 0) /* return previous index */ + i = 0; + return dict_index[i].pos; +} + +int 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 RET_NOK; + if(rb->lcd_get_backdrop!=NULL){ + rb->lcd_set_backdrop(NULL); + rb->lcd_update(); + } + rb->splash(HZ,"Creating Index ..."); + ssize_t section_size=rb->filesize(fd)/MAX_INDEX; + size_t size; + char* buffer=rb->plugin_get_buffer(&size); + char* old_key = "???"; + ssize_t len; + ssize_t plen; + int i=0; + unsigned long offset=0; + char* pos; + char* end; + while((len=rb->read(fd,buffer,size))>0){ + pos=buffer; + pos[len-1]=LF; + end = buffer + len; + if(i!=0){ + while(pos < end && *pos++!=LF); + old_key = pos; /* to prevent the key not being the 1st entry */ + } + while((plen=pos-buffer)strncasecmp(old_key,pos,KEY_LENGTH)!=0){ + rb->strncpy(dict_index[i].key,pos,KEY_LENGTH); + dict_index[i++].pos=offset+plen; + if(pos + section_size >= end) + break; + pos+=section_size; /* move to next section */ + while(pos < end && *pos++!=LF); /* ensure the next read pos will start at ^*/ + } + old_key=pos; + while(pos < end && *pos++!=LF); /* next line */ + } + offset+=size; + } + rb->close(fd); + dict_index[i].pos=0; /* terminate */ + fd = rb->creat(idx); + rb->write(fd, dict_index, sizeof(struct dict_index_struct)*i); + rb->close(fd); + return RET_OK; +} + +int open_dict(struct dict_index_struct* dict_index){ + /* open default dictionary if the language is not set */ + const char* lang = language[0]==0 ? APP_NAME : language; + char idx[MAX_PATH]; + char dict[MAX_PATH]; + rb->snprintf(idx,MAX_PATH,SNCVIEWER_DATA_DIR"/%s.idx",lang); + rb->snprintf(dict,MAX_PATH,SNCVIEWER_DATA_DIR"/%s."DICT_EXT,lang); + + int fd=rb->open(idx, O_RDONLY); + if(fd >= 0){ + rb->read(fd,dict_index,sizeof(struct dict_index_struct)*MAX_INDEX); + rb->close(fd); + } + else if(create_dict_index(dict_index,dict,idx)!=RET_OK){ + rb->splash(HZ>>1, "Cannot Open %s!", dict); + return RET_NOK; + } + /* open dictionary */ + return rb->open(dict, O_RDONLY); +} + +int change_dict(int* fd, struct dict_index_struct* dict_index){ + /* list dictionaries */ + DIR* dir = rb->opendir(SNCVIEWER_DATA_DIR); + if (!dir) { + rb->splash(HZ,"Cannot open directory: '%s'!",SNCVIEWER_DATA_DIR); + return RET_NOK; + } + + char* dict_items[MAX_DICTS]; + size_t size; + dict_items[0]=rb->plugin_get_buffer(&size); + struct dirent *entry; + unsigned char* ext; + int i=0; + while((entry=rb->readdir(dir))!=0 && istrrchr(entry->d_name,'.'); + if(ext == NULL) continue; /* directory */ + ext++; + if(rb->strncasecmp(ext,DICT_EXT,3)==0){ /* check if it's a dictionary */ + rb->strcpy(dict_items[i], entry->d_name); + dict_items[i+1]=dict_items[i]+rb->strlen(entry->d_name)+1; + i++; + } + } + rb->closedir(dir); + + if(i==0) return RET_NOK; + struct menu_callback_with_desc entry_desc = {NULL,"Dictionaries", Icon_Language}; + struct menu_item_ex entry_list = { + MT_RETURN_ID|MENU_HAS_DESC|MENU_ITEM_COUNT(i), + {.strings = (const char **) dict_items}, + {.callback_and_desc = &entry_desc} + }; + i=rb->do_menu(&entry_list, NULL, NULL, false); + if(i>=0){ + language[0] = 0; + if(rb->strlen(dict_items[i])-rb->strlen(DICT_EXT)-1 == 2){ + language[0] = dict_items[i][0]; + language[1] = dict_items[i][1]; + language[2] = 0; + } + if(*fd>0) rb->close(*fd); + *fd = open_dict(dict_index); + modified = 1; + } + return *fd; +} + +int lookup_word(void){ + struct dict_index_struct dict_index[MAX_INDEX]; + + /* parse line (build menu) */ + struct Dict_Entry{ + const char* word; + int length; + } word_list[MAX_WORDS]; /* wordlist: items[0] items[1] ... */ + char* items[MAX_WORDS]; + + const char* new_pos; + const char* pos=sncs[current_snc_edit].x[SCROLL].lyrics[0]; + #define I1ST 2 + int byte,i=I1ST; + + items[0]=""; + items[1]=""; + items[I1ST]=buf; + while(*pos != 0 && istrncpy(items[i],pos,word_list[i].length); /* copy word into item list */ + items[i][word_list[i].length]=0; /* terminate word */ + items[i+1]=items[i]+word_list[i].length+1; /* next item */ + pos=new_pos; + i++; + } + if(i==I1ST) return RET_NOK; + + int selected=I1ST; + /* menu */ + struct menu_callback_with_desc desc = {NULL, language, Icon_Language}; + struct menu_item_ex words = { + MT_RETURN_ID|MENU_HAS_DESC|MENU_ITEM_COUNT(i), + {.strings = (const char **) items}, + {.callback_and_desc = &desc} + }; + + int cmp_len; + const char* search_word; + char* entries[MAX_WORDS]; + char* str; + size_t size; + ssize_t len, len_bak; + char* const plugin_buffer = rb->plugin_get_buffer(&size); + char kb_buffer[20]; + kb_buffer[0]=0; + int fd=-1; + void read_next_block(void){ + len=rb->read(fd,plugin_buffer, + dict_index[2].pos < size ? dict_index[2].pos : size); /* read two sections */ + len_bak = len; + plugin_buffer[len-1]=0; /* ensure termination */ + } + + while(rb->do_menu(&words, &selected, NULL, false)>=0){ + switch(selected){ + case 0: + change_dict(&fd, dict_index); + continue; + case 1: + { + if(rb->kbd_input(kb_buffer,MAX_PATH) || kb_buffer[0]==0) + continue; /* cancel input: return to menu */ + search_word = kb_buffer; + cmp_len = rb->strlen(search_word); + break; + } + default: + search_word = word_list[selected].word; + cmp_len = word_list[selected].length; + } + if(fd<0) fd = open_dict(dict_index); + if(fd<0) return RET_NOK; + rb->splash(HZ, "Searching..."); + rb->lseek(fd,get_dict_pos(dict_index,search_word),SEEK_SET); + read_next_block(); + + SEEKING_WORD: + entries[0] = plugin_buffer; + str = plugin_buffer; + + NEXT_PAGE: + i=0; + do{ + while(*str!=LF && *str!=0) str++; /* set str to end of line */ + *str++=0; + len-=(str-entries[i]); + if(rb->strncasecmp(search_word, entries[i], cmp_len)==0){ + /* found */ + i++; + if(len<=0) read_next_block(); + } + else if(i!=0) break; /* stop if it doesn't match */ + }while(len>0 && + i+1 */ + *(entries[i]=str)!=0); + if(i>0){ /* show found entries */ + if(i+1==MAX_WORDS) entries[i++] = ""; + struct menu_callback_with_desc entry_desc = + {NULL,(char*) search_word, Icon_Questionmark}; + 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} + }; + if(rb->do_menu(&entry_list, NULL, NULL, false)+1==MAX_WORDS){ + entries[0]=str; /* next entries */ + goto NEXT_PAGE; + } + } + else { + if(selected>=I1ST) rb->strcpy(kb_buffer,items[selected]); + if(cmp_len>KEY_LENGTH){ + /* no exact match found, try with parts of the word */ + cmp_len >>= 1; + if(cmp_lensplash(HZ, "Word '%s' not found!", + selected>=I1ST ? items[selected] : kb_buffer); + } + } + rb->close(fd); + return RET_OK; +} + +#ifdef CUSTOM_PLUGIN_PATCH +int show_playlist(void){ + rb->playlist_viewer(); + update(); + return RET_OK; +} + +int browse_dir(void){ + rb->rockbox_browse(id3->path,SHOW_MUSIC); + update(); + return RET_OK; +} +int show_id3(void){ + rb->browse_id3(); + return RET_OK; +} +#endif + +int delete_lyrics_file(void){ + if(NO_LYRICS()) return RET_NOK; + 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 */ + } + return RET_OK; +} + +/* + goto next gap: + a gap is >= 8s between 2 sncs or a blank snc +*/ +int goto_next_gap(void){ + if(NO_LYRICS()) return RET_NOK; + int current = auto_scroll ? current_snc:current_snc_edit; + int id = (current+1)%num_snc; + while( current != id && /* one cycle */ + !(IS_BLANK(id)) && /* not blank */ + sncs[id].time_in_ms + 8000 > sncs[(id=(id+1)%num_snc)].time_in_ms); /* not a gap */ + + if(current != id && /* only goto next gap if the song has one */ + id + 3 < num_snc){ /* and the gap is not at the end (problems with ff) */ + if(auto_scroll) + ff_rew_snc(id); + else{ + current_snc_edit = id; + browse_snc(); + } + } + return RET_OK; +} + +/* + clean_blanks: + delete the blank snc, if the blank length is shorter than the set time +*/ +int clean_blanks(void){ + if(NO_LYRICS()) return RET_NOK; + int id = 1; + int num_blanks=0; + int delta = 1000; + if(set_snc_time_screen("Min Blank Length:",500,&delta,500)==SNC_QUIT) + return RET_NOK; /* cancelled */ + while(id < num_snc){ + if(IS_BLANK(id) + && (sncs[id].time_in_ms == (unsigned long) NOT_INIT + || (sncs[id].time_in_ms + delta > sncs[id+1].time_in_ms))){ + if(sncs[id].time_in_ms != (unsigned long) NOT_INIT){ + /* copy time from blank snc and delete the blank */ + sncs[id+1].time_in_ms = sncs[id].time_in_ms + 1; + } + current_snc_edit = id; + start_snc = id; + stop_snc = id + 1; + delete_sections(); + num_blanks++; + } + id++; + } + /* report */ + if(num_blanks) + rb->splash(HZ,"Number of Blanks Deleted: %d",num_blanks); + return RET_OK; +} + +/* + set_scroll_limit: + configure the vertical scroll limit +*/ +int set_scroll_limit(void){ + if(NO_LYRICS()) return RET_NOK; /* only possible with lyrics */ + int button; + bool save = false; + int old_val = prefs.scroll_limit_y; + auto_scroll = true; + update_display(); + do{ + button=rb->get_action(CONTEXT_SNC,HZ>>1); + switch (button) { + case SNC_SELECT_OR_MODE: + save = true; + button = SNC_QUIT; + break; + case SNC_LIST_DOWN: + if(prefs.scroll_limit_y + prefs.fontheight < LCD_HEIGHT - prefs.fontheight) + prefs.scroll_limit_y += prefs.fontheight; + break; + case SNC_LIST_UP: + prefs.scroll_limit_y -= prefs.fontheight; + if(prefs.scroll_limit_y < scroll_y0) + prefs.scroll_limit_y = scroll_y0; + break; + } + update(); + (button==ACTION_NONE) ? + update_time_display(AUDIO_ELAPSED(),false): + update_display(); + /* show that we are in configuration mode */ + rb->lcd_set_drawmode(DRMODE_COMPLEMENT); + rb->lcd_fillrect(SCROLL_LIMIT_POS_X,prefs.scroll_limit_y, + SCROLL_LIMIT_MARKER_WIDTH,prefs.fontheight); + rb->lcd_set_drawmode(DRMODE_SOLID); + rb->lcd_update(); + } while (button!=SNC_QUIT); + + if(save) + prefs.save|=PREF_SCROLL_LIMIT; + else + prefs.scroll_limit_y = old_val; + return RET_OK; +} + +int switch_peakmeter(void){ + prefs.save^=PREF_PEAKMETER; + prefs.peakmeter=!prefs.peakmeter; + return RET_OK; +} + +#ifdef HAVE_LCD_INVERT +int invert_colors(void){ + prefs.save^=PREF_INVERT_DISPLAY; + prefs.invert_display=!prefs.invert_display; + rb->lcd_set_invert_display(prefs.invert_display); + if(prefs.load_bmp) load_bitmap(true); + return RET_OK; +} +#endif + +int switch_album_art(void){ + #if LCD_HEIGHT > LCD_WIDTH + #define AA_NUM_STATES 3 + #else + #define AA_NUM_STATES 2 + #endif + prefs.save^=PREF_BMP; + prefs.load_bmp=(prefs.load_bmp+1)%AA_NUM_STATES; + if(!prefs.load_bmp){ + bmp_width=NOT_INIT; + bmp_height=NOT_INIT; + } + reset(false); + return RET_OK; +} + +int switch_repeat1(void){ + set_repeat_mode(rb->global_settings->repeat_mode!=RMODE_ONE ? + RMODE_ONE:-RMODE_ONE); + return RET_OK; +} + +int load_translation(void){ + 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); + return RET_OK; +} + +/* Make menu items */ +#define SNC_MENU_ITEM(item,name,func,icon)\ + MENUITEM_FUNCTION(item,MENU_FUNC_CHECK_RETVAL|MENU_EXITAFTERTHISMENU,name, \ + func,NULL,NULL,icon) + +/* menus in edit mode */ +SNC_MENU_ITEM(delete_item,"Delete (A-B/current)",delete_sections, Icon_NOICON); +SNC_MENU_ITEM(insert_item,"Insert (A-B/blank)", insert_sections, Icon_NOICON); +SNC_MENU_ITEM(join_item,"Join Sections", join_sections, Icon_NOICON); +SNC_MENU_ITEM(edit_all_item,"Edit+", edit_and_dup_lines, Icon_NOICON); +SNC_MENU_ITEM(edit_item,"Edit", edit_line, Icon_NOICON); +SNC_MENU_ITEM(dict_item,"Dictionary", lookup_word, Icon_Language); + +/* general menu items */ +SNC_MENU_ITEM(time_offset_item,"Time Offset...", set_time_offset,Icon_NOICON); +SNC_MENU_ITEM(repeat1_item,"Repeat 1", switch_repeat1, Icon_Audio); +SNC_MENU_ITEM(translation_item,"Translation", load_translation, Icon_Language); +SNC_MENU_ITEM(delete_file_item,"Delete File...", delete_lyrics_file, Icon_NOICON); +SNC_MENU_ITEM(clean_blanks_item,"Clean Blanks...", clean_blanks, Icon_NOICON); +SNC_MENU_ITEM(next_gap_item,"Next Gap", goto_next_gap, Icon_Audio); + +/* scroll menu items */ +SNC_MENU_ITEM(auto_cue_item,"Auto Cue...", auto_cue, Icon_NOICON); +SNC_MENU_ITEM(album_art_item,"Album Art", switch_album_art, Icon_Config); +SNC_MENU_ITEM(peakmeter_item,"Peakmeter", switch_peakmeter, Icon_Config); +SNC_MENU_ITEM(scroll_limit_item,"Scroll-limit", set_scroll_limit, Icon_Cursor); +#ifdef HAVE_LCD_INVERT + SNC_MENU_ITEM(invert_colors_item,"Invert Colors", invert_colors, Icon_Config); +#endif + +#define GENERAL_MENU_ITEMS \ + &time_offset_item, &repeat1_item, &translation_item, &delete_file_item, \ + &clean_blanks_item, &next_gap_item + +#define SCROLL_MENU_ITEMS \ + &auto_cue_item, &album_art_item, &peakmeter_item, &scroll_limit_item + +#define EDIT_MENU_ITEMS \ + &delete_item, &insert_item, &join_item, &edit_all_item, &edit_item, &dict_item + +#ifdef CUSTOM_PLUGIN_PATCH + SNC_MENU_ITEM(id3_item,"ID3 Info", show_id3, Icon_Questionmark); + SNC_MENU_ITEM(playlist_item,"Playlist", show_playlist, Icon_Playlist); + SNC_MENU_ITEM(browse_dir_item,"Files", browse_dir, Icon_Folder); + + #define CUSTOM_MENU_ITEMS \ + &id3_item, &playlist_item, &browse_dir_item +#else + #define CUSTOM_MENU_ITEMS +#endif + +//MAKE_MENU +MAKE_MENU(scroll_menu, "MENU", NULL, Icon_Audio, + GENERAL_MENU_ITEMS, SCROLL_MENU_ITEMS +#ifdef HAVE_LCD_INVERT + , &invert_colors_item +#endif + , CUSTOM_MENU_ITEMS +); + +MAKE_MENU(edit_menu, "EDIT", NULL, Icon_NOICON, + EDIT_MENU_ITEMS, GENERAL_MENU_ITEMS , CUSTOM_MENU_ITEMS +); + +void show_menu(void) +{ + static int edit_selected=0; + static int scroll_selected=0; + auto_scroll ? + rb_menu(&scroll_menu,&scroll_selected): + rb_menu(&edit_menu,&edit_selected); + 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;iopen(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(); +#ifdef HAVE_LCD_INVERT + rb->lcd_set_invert_display(prefs.invert_display); +#endif + + rb->lcd_setfont(FONT_SYSFIXED); + rb->lcd_getstringsize("0", &indentwidth, &sysfont_height); + rb->lcd_setfont(FONT_UI); + /* init indentwidth, fontheight */ + int 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; + bmp_height=NOT_INIT; + scroll_y0=TIME_BAR_ROW*fontheight+(sysfont_height<<1)+TIMEBAR_LYRICS_SPACE; + if(fontheight != prefs.fontheight) { /* fontheight was changed! */ + prefs.fontheight = fontheight; + prefs.scroll_limit_y = scroll_y0; /* scroll_limit_y is dependent on fontheight */ + prefs.save = PREF_SAVE; /* save the new fontheight */ + } + reset(true); +} + +int handle_button_scroll(int button){ + switch (button) { + case SNC_TIMESTAMP_BL: + prefs.save^=PREF_BACKLIGHT; + prefs.backlight=!prefs.backlight; + prefs.backlight? force_backlight_on():default_backlight(); + break; + case SNC_DEC: + set_volume(rb->global_settings->volume - 1); + break; + case SNC_INC: + set_volume(rb->global_settings->volume + 1); + break; + case SNC_PREV: + rb->button_get_w_tmo(DOUBLE_CLICK_TICKS)!=BUTTON_NONE? + rb->audio_prev():ff_rew_snc(start_snc!=NOT_INIT?start_snc:0); + break; + case SNC_NEXT: + (num_snc > 1 && rb->button_get_w_tmo(DOUBLE_CLICK_TICKS)!=BUTTON_NONE) ? + 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){ + static const int TIMEOUT = HZ>>2; + switch (button) { + case SNC_TIMESTAMP_BL: + set_time_tag(); + break; + case SNC_LIST_DOWN: + if(++current_snc_edit>=num_snc){ + current_snc_edit = 1; + while(rb->button_get_w_tmo(TIMEOUT)&BUTTON_REPEAT){ + current_snc_edit=num_snc-1; + rb->yield(); + } + } + browse_snc(); + break; + case SNC_LIST_UP: + if(--current_snc_edit<0){ + current_snc_edit = num_snc-2; + while(rb->button_get_w_tmo(TIMEOUT)&BUTTON_REPEAT){ + current_snc_edit=0; + rb->yield(); + } + } + 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(const struct plugin_api* api, const 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; +#ifdef HAVE_LCD_COLOR + unsigned bg_color = rb->global_settings->bg_color; + unsigned fg_color = rb->global_settings->fg_color; + rb->global_settings->bg_color = SNC_BG_COLOR; + rb->global_settings->fg_color = SNC_FG_COLOR; + rb->lcd_set_background(SNC_BG_COLOR); + rb->lcd_set_foreground(SNC_FG_COLOR); +#endif +#if LCD_DEPTH > 1 + struct bitmap backdrop; + size_t buffer_size; + backdrop.data = rb->plugin_get_buffer(&buffer_size); + const int max_backdrop_size = LCD_HEIGHT * LCD_WIDTH * 2; + int size = 0; + if(buffer_size > (size_t) max_backdrop_size){ + /* move pointer to the end of the buffer */ + backdrop.data += buffer_size - max_backdrop_size; + size = rb->read_bmp_file(SNCVIEWER_DATA_DIR"/"APP_NAME"_bd.bmp", + &backdrop, max_backdrop_size, FORMAT_ANY); + } + rb->lcd_set_backdrop(size > 0 ? (fb_data*) backdrop.data : NULL); +#endif + init(); + int button; + long newPos=NOT_INIT; + int timeout = HZ>>1; + #define PLUGIN_STAY 333 + int status=PLUGIN_STAY; /* see enum plugin_status */ + + do{ + button=rb->get_action(CONTEXT_SNC,timeout); + switch (button) { /* valid for both modes */ + case SNC_AB: + set_ab_marker(); + update_display(); + break; + case SNC_AB_MENU: + ab_menu(); + update_display(); + break; + 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_STOP_SEEK: + ff_rew(newPos); + newPos=NOT_INIT; + break; + case SNC_VISUAL_REW: + case SNC_VISUAL_FF: + if(newPos==NOT_INIT) + newPos=AUDIO_ELAPSED(); + newPos+=(button==SNC_VISUAL_FF) ? 2000:-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 && NO_LYRICS()) 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); + } +#ifdef HAVE_LCD_COLOR + /* restore color settings */ + rb->global_settings->bg_color = bg_color; + rb->global_settings->fg_color = fg_color; + rb->lcd_set_background(rb->global_settings->bg_color); + rb->lcd_set_foreground(rb->global_settings->fg_color); +#endif +#ifdef HAVE_LCD_INVERT + if(prefs.invert_display) + rb->lcd_set_invert_display(rb->global_settings->invert); +#endif + return status; +} Index: apps/plugins/SOURCES =================================================================== --- apps/plugins/SOURCES (revision 17956) +++ apps/plugins/SOURCES (working copy) @@ -19,6 +19,7 @@ stopwatch.c vbrfix.c viewer.c +sncviewer.c #ifdef HAVE_BACKLIGHT lamp.c #endif /* HAVE_BACKLIGHT */ Index: apps/plugins/CATEGORIES =================================================================== --- apps/plugins/CATEGORIES (revision 17956) +++ apps/plugins/CATEGORIES (working copy) @@ -72,6 +72,7 @@ sliding_puzzle,games snake,games snake2,games +sncviewer,apps snow,demos sokoban,games solitaire,games