Index: apps/plugins/sncviewer.c =================================================================== --- apps/plugins/sncviewer.c (revision 0) +++ apps/plugins/sncviewer.c (revision 0) @@ -0,0 +1,2265 @@ +#include "plugin.h" +PLUGIN_HEADER + +enum{ACTION_SELECT_REL=300,ACTION_SELECT_HOLD, + ACTION_DOWN_HOLD,ACTION_DOWN_REL,ACTION_DOWN, + ACTION_UP_HOLD,ACTION_UP_REL, + ACTION_LEFT_HOLD,ACTION_LEFT_REL,ACTION_LEFT,ACTION_LEFT_HOLD_REL, + ACTION_RIGHT_HOLD,ACTION_RIGHT_REL,ACTION_RIGHT,ACTION_RIGHT_HOLD_REL, + ACTION_STOP_REL,ACTION_REC_HOLD,ACTION_REC_REL, + ACTION_PLAY_PAUSE_REL,ACTION_PLAY_PAUSE_HOLD,ACTION_AB_REL,ACTION_AB_HOLD}; + +const struct button_mapping button_context_snc[] = { +#if CONFIG_KEYPAD == IRIVER_H10_PAD + { ACTION_SELECT_HOLD, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_SELECT_REL, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT }, + { ACTION_LEFT_HOLD, BUTTON_REW|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_LEFT_HOLD_REL, BUTTON_REW|BUTTON_REL, BUTTON_REW|BUTTON_REPEAT }, + { ACTION_LEFT_REL, BUTTON_REW|BUTTON_REL, BUTTON_REW }, + { ACTION_LEFT, BUTTON_REW, BUTTON_NONE }, + { ACTION_RIGHT_HOLD, BUTTON_FF|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_RIGHT_HOLD_REL, BUTTON_FF|BUTTON_REL, BUTTON_FF|BUTTON_REPEAT }, + { ACTION_RIGHT_REL, BUTTON_FF|BUTTON_REL, BUTTON_FF }, + { ACTION_RIGHT, BUTTON_FF, BUTTON_NONE }, + + { ACTION_DOWN_REL, BUTTON_SCROLL_DOWN, BUTTON_NONE }, + { ACTION_DOWN_HOLD, BUTTON_SCROLL_DOWN|BUTTON_REPEAT,BUTTON_NONE }, + { ACTION_UP_REL, BUTTON_SCROLL_UP, BUTTON_NONE }, + { ACTION_UP_HOLD, BUTTON_SCROLL_UP|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_STOP_REL, BUTTON_POWER|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_STOP_REL, BUTTON_POWER|BUTTON_REPEAT, BUTTON_POWER }, + { ACTION_REC_HOLD, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_REC_REL, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT }, + { ACTION_PLAY_PAUSE_REL, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY }, +#else + //common to all players + { ACTION_SELECT_HOLD, BUTTON_SELECT|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_SELECT_REL, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT }, + { ACTION_LEFT_HOLD, BUTTON_LEFT|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_LEFT_HOLD_REL, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT|BUTTON_REPEAT }, + { ACTION_LEFT_REL, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT }, + { ACTION_LEFT, BUTTON_LEFT, BUTTON_NONE }, + { ACTION_RIGHT_HOLD, BUTTON_RIGHT|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_RIGHT_HOLD_REL, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT|BUTTON_REPEAT }, + { ACTION_RIGHT_REL, BUTTON_RIGHT|BUTTON_REL, BUTTON_RIGHT }, + { ACTION_RIGHT, BUTTON_RIGHT, BUTTON_NONE }, + +#if (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) + { ACTION_DOWN_REL, BUTTON_SCROLL_FWD, BUTTON_NONE }, + { ACTION_DOWN_HOLD, BUTTON_SCROLL_FWD|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_UP_REL, BUTTON_SCROLL_BACK, BUTTON_NONE }, + { ACTION_UP_HOLD, BUTTON_SCROLL_BACK|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_STOP_REL, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_STOP_REL, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_PLAY }, + { ACTION_REC_HOLD, BUTTON_MENU|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_REC_REL, BUTTON_MENU|BUTTON_REL, BUTTON_MENU }, + { ACTION_PLAY_PAUSE_REL, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY }, +#elif CONFIG_KEYPAD == SANSA_E200_PAD + { ACTION_DOWN_REL, BUTTON_SCROLL_DOWN, BUTTON_NONE }, + { ACTION_DOWN_HOLD, BUTTON_SCROLL_DOWN|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_UP_REL, BUTTON_SCROLL_UP, BUTTON_NONE }, + { ACTION_UP_HOLD, BUTTON_SCROLL_UP|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_STOP_REL, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_STOP_REL, BUTTON_UP|BUTTON_REPEAT, BUTTON_UP }, + { ACTION_REC_HOLD, BUTTON_REC|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_REC_REL, BUTTON_REC|BUTTON_REL, BUTTON_REC }, + { ACTION_PLAY_PAUSE_REL, BUTTON_UP|BUTTON_REL, BUTTON_UP }, + #define HAS_AB_BUTTON + { ACTION_AB_HOLD, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_AB_REL, BUTTON_DOWN|BUTTON_REL, BUTTON_DOWN }, +#elif CONFIG_KEYPAD == GIGABEAT_PAD + { ACTION_DOWN_HOLD, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_DOWN_REL, BUTTON_DOWN|BUTTON_REL, BUTTON_NONE }, + { ACTION_UP_HOLD, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_UP_REL, BUTTON_UP|BUTTON_REL, BUTTON_NONE }, + + { ACTION_STOP_REL, BUTTON_POWER|BUTTON_REPEAT, BUTTON_NONE }, + + { ACTION_REC_HOLD, BUTTON_MENU|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_REC_REL, BUTTON_MENU|BUTTON_REL, BUTTON_MENU }, + { ACTION_PLAY_PAUSE_REL, BUTTON_POWER|BUTTON_REL, BUTTON_POWER }, + #define HAS_AB_BUTTON + { ACTION_AB_HOLD, BUTTON_A|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_AB_REL, BUTTON_A|BUTTON_REL, BUTTON_A }, +#else //iaudio x5, iriver h100/h300 + { ACTION_DOWN_HOLD, BUTTON_DOWN|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_DOWN_REL, BUTTON_DOWN|BUTTON_REL, BUTTON_DOWN }, + { ACTION_DOWN, BUTTON_DOWN, BUTTON_NONE }, + { ACTION_UP_HOLD, BUTTON_UP|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_UP_REL, BUTTON_UP|BUTTON_REL, BUTTON_UP }, + { ACTION_REC_HOLD, BUTTON_REC|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_REC_REL, BUTTON_REC|BUTTON_REL, BUTTON_REC }, + #if CONFIG_KEYPAD == IAUDIO_X5M5_PAD + { ACTION_STOP_REL, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_PLAY_PAUSE_REL, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY }, + #else //iriver h100/h300 + { ACTION_STOP_REL, BUTTON_OFF|BUTTON_REL, BUTTON_NONE }, + { ACTION_PLAY_PAUSE_REL, BUTTON_ON|BUTTON_REL, BUTTON_ON }, + { ACTION_PLAY_PAUSE_HOLD, BUTTON_ON|BUTTON_REPEAT, BUTTON_NONE }, + #define HAS_AB_BUTTON + { ACTION_AB_HOLD, BUTTON_MODE|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_AB_REL, BUTTON_MODE|BUTTON_REL, BUTTON_MODE }, + #endif + // REMOTE + { ACTION_DOWN_HOLD, BUTTON_RC_VOL_DOWN|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_DOWN_REL, BUTTON_RC_VOL_DOWN, BUTTON_RC_VOL_DOWN }, + { ACTION_UP_HOLD, BUTTON_RC_VOL_UP|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_UP_REL, BUTTON_RC_VOL_UP, BUTTON_RC_VOL_UP }, + + { ACTION_LEFT_HOLD, BUTTON_RC_REW|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_LEFT_HOLD_REL, BUTTON_RC_REW|BUTTON_REL, BUTTON_RC_REW|BUTTON_REPEAT }, + { ACTION_LEFT_REL, BUTTON_RC_REW|BUTTON_REL, BUTTON_RC_REW }, + { ACTION_LEFT, BUTTON_RC_REW, BUTTON_NONE }, + { ACTION_RIGHT_HOLD, BUTTON_RC_FF|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_RIGHT_HOLD_REL, BUTTON_RC_FF|BUTTON_REL, BUTTON_RC_FF|BUTTON_REPEAT }, + { ACTION_RIGHT_REL, BUTTON_RC_FF|BUTTON_REL, BUTTON_RC_FF }, + { ACTION_RIGHT, BUTTON_RC_FF, BUTTON_NONE }, + { ACTION_SELECT_HOLD, BUTTON_RC_MENU|BUTTON_REPEAT, BUTTON_NONE }, + #if CONFIG_KEYPAD == IAUDIO_X5M5_PAD + { ACTION_STOP_REL, BUTTON_RC_PLAY|BUTTON_REPEAT, BUTTON_NONE }, + { ACTION_PLAY_PAUSE_REL, BUTTON_RC_PLAY|BUTTON_REL, BUTTON_RC_PLAY }, + #else + { ACTION_STOP_REL, BUTTON_RC_STOP|BUTTON_REL, BUTTON_NONE }, + { ACTION_PLAY_PAUSE_REL, BUTTON_RC_ON|BUTTON_REL, BUTTON_RC_ON }, + { ACTION_PLAY_PAUSE_HOLD, BUTTON_RC_ON|BUTTON_REPEAT, BUTTON_NONE }, + #endif +#endif //other +#endif //if h10 + LAST_ITEM_IN_LIST +}; + +#define BITMAP_BUFFERSIZE 65536 +#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 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 +/* status: volume/ab [!]/[vbr] [x]/[u] [II] track repeat_mode*/ +#define STATUS_VOL_POS 0 //utf8: icon -> 8x8 +#define STATUS_MODIFIED_POS 20 //time: (x:xx-x:xx) -> 66, char_witdh: 6 +#define STATUS_UTF8_POS 31 //modified: icon -> 8x8 +#define STATUS_PAUSE_POS 41 +#define STATUS_AB_POS 49 +#define STATUS_REPEAT_MODE_POS LCD_WIDTH-8 + +#ifdef IRIVER_H300_SERIES + #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_SAVE 0xFFFF + +//makros +#define SET_ROWS(snc,val) for(ix=0;ixx[ix].rows=val; +#define SET_LYRICS(snc,addr,row) for(ix=0;ixx[ix].lyrics[row]=addr; +#define SNC(snc, type) snc->x[type] +#define SNC_COPY(dest,src) for(ix=0;ixx[ix]), &(src->x[ix])); + +#define CALC_MS(time) (time%1000)/10 +#define CALC_SS(time) (time/1000)%60 +#define CALC_MM(time) time/60000 +#define SETTINGS_FILE PLUGIN_DIR"/sncviewer.dat" + +//structs +struct Preferences { + bool load_bmp; + bool backlight; + int save; +}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 unsigned char bitmap_buffer[BITMAP_BUFFERSIZE]; +static int lyrics_buffer_used; +static const struct plugin_api* rb; +static const struct mp3entry* id3; +static unsigned char buf[BUFFERSIZE]; //temp. buffer +static unsigned char file[MAX_PATH]; +static bool auto_scroll; +static int nrSNC; +static int currentSNC; //0..nrSNC-1 +static int currentSNCedit; //0..nrSNC-1 + +static int startSNC; +static int stopSNC; +static int time_offset; +static int indentwidth, fontheight; +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"}; +enum e_supported_formats{e_lrc,e_lrc8,e_snc,e_txt,e_ab_,e_tr,e_mp3}; +enum e_repeat_modes{e_off, e_all, e_one, e_shuffle, e_ab}; +static int sysfont_height; +static int ix; //global index +static int time_bar_row; +static int scroll_y0; +static int artist_title_row_height,remote_artist_title_row_height; +static int load_translation; +static int bmp_width; + +enum e_abfile_state{e_no,e_yes,e_unknown}; +static enum e_abfile_state abfile_state; +#if defined(HAVE_LCD_COLOR) + bool change_fg_color; +#endif +// static int busy=0; +unsigned char* (*utf_decode)(const unsigned char *, unsigned char *, int); +int set_offset_screen(void); +void update_display(void); + +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; + SNC_COPY(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; +} + +void lcd_clear_rect(int x, int y, int w, int h){ + rb->lcd_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); + if(x<0) x=0; + rb->lcd_fillrect(x,y,w,h); + rb->lcd_set_drawmode(DRMODE_SOLID); +} + +#ifdef HAVE_REMOTE_LCD +void lcd_remote_clear_rect(int x, int y, int w, int h){ + rb->lcd_remote_set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); + rb->lcd_remote_fillrect(x,y,w,h); + rb->lcd_remote_set_drawmode(DRMODE_SOLID); +} +#endif + +int get_center_pos(const unsigned char* const str, int min){ + int w=0; + if(str!=NULL) rb->lcd_getstringsize(str, &w, &fontheight); + w=(LCD_WIDTH-w)>>1; + if(wartist; + const unsigned char* title=entry->title; + int height=sysfont_height; + if(artist!=NULL && title!=NULL && *artist < 0x80 && *title < 0x80) + rb->snprintf(buf,BUFFERSIZE,"%s/%s",title,artist); + else //id3 != ascii -> display filename + buffer=rb->strrchr(entry->path,'/')+1; + if(*buffer < 0x80){ + lcd_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; +// buffer=buf; +// rb->snprintf(buf,BUFFERSIZE,"%s/%s",title,artist); + } + lcd_putsxy(x,y,buffer); + lcd_setfont(FONT_UI); + return height; +} + +void exec_func(int delay, void (*func)(void)){ + #define MAX_FUNCS 2 + static int size=0; + struct FuncContainer{ + int t; + void (*exec)(void); + }; + static struct FuncContainer funcs[MAX_FUNCS]={{-1,NULL},{-1,NULL}}; + int i; + if(func!=NULL){ + for(i=0;iMAX_FUNCS) size=MAX_FUNCS; + } +// DEBUGF("**********exec insert funcs size: %d, pos:%d, delay: %d\n",size,i,delay); + } + if(size>0){ + for(i=0;i0) funcs[i].t--; + } + for(i=0;iget_custom_action(CONTEXT_CUSTOM,HZ,get_context_map)==button1 && + rb->get_custom_action(CONTEXT_CUSTOM,HZ,get_context_map)==button2; +} +// void invert_rect(int x, int y, int w, int h){ +// rb->lcd_set_drawmode(DRMODE_COMPLEMENT); +// rb->lcd_fillrect(x, y, w, h); +// rb->lcd_set_drawmode(DRMODE_SOLID); +// } +#ifdef CUSTOM_PLUGIN_PATCH +int snc_browse_dir(void){ + rb->rockbox_browse(id3->path,SHOW_MUSIC); + return 1; +} +#endif +void print_next_playing(void){ + if(!auto_scroll) return; + static const unsigned char icon_7x8[] = {0x22,0x14,0x2a,0x14,0x08,0x3e,0x00}; + const struct mp3entry* nextid3 = rb->audio_next_track(); + if(nextid3!=NULL){ + int y=LCD_HEIGHT-artist_title_row_height; + lcd_clear_rect(0,y,LCD_WIDTH,artist_title_row_height); + rb->lcd_mono_bitmap(icon_7x8,0,y+((artist_title_row_height-8)>>1),7,8); + int height=print_artist_title(nextid3,7,y,rb->lcd_putsxy,rb->lcd_setfont); +// invert_rect(0,y,LCD_WIDTH,sysfont_height); + if(height==artist_title_row_height) + rb->lcd_update_rect(0,y,LCD_WIDTH,artist_title_row_height); + else{ //height has changed + artist_title_row_height=height; + update_display(); + } + } +} + +void update_status_display(void) +{ + if(id3==NULL) return; + 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] + 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*/ + //{0x0e,0x04,0x7b,0xa8,0x50,0x0e,0x05,0x0a}, /* vbr*/ + }; + #define PAUSE_ICON bitmap_icons_7x8[5] +// #define BUSY_ICON bitmap_icons_7x8[6] + 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*/ +// {0x00,0xc3,0xa5,0xdd,0xf9,0xe5,0xc3}, /* Hourglass */ + // {0x00,0x60,0x30,0x3e,0x2c,0x26,0x03}, /* no lyrics */ + }; + static const int volume_values[]={0x00,0x08,0x14,0x2a,0x55,0x5f}; +// int vol = 100 * (rb->global_settings->volume - rb->sound_min(SOUND_VOLUME)) +// / (rb->sound_max(SOUND_VOLUME) - rb->sound_min(SOUND_VOLUME)); + 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_rect(0,0,LCD_WIDTH,sysfont_height); + if(load_translation) rb->lcd_putsxy(8,0,"T"); + 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(bitmap_icons_7x8[6],STATUS_PAUSE_POS,0,7,8); +// else if(busy>0) rb->lcd_mono_bitmap(BUSY_ICON,STATUS_PAUSE_POS,0,7,8); + + if(utf8) rb->lcd_mono_bitmap(UTF8_ICON,STATUS_UTF8_POS,0,8,8); + + if(startSNC!=NOT_INIT) //ab repeat + { + buf[0]='('; + char* end=cat_time(&buf[1],sncs[startSNC].time_in_ms); + if(stopSNC!=NOT_INIT) + { + *end='-'; + end=cat_time(++end,sncs[stopSNC].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 || stopSNC==NOT_INIT){ //not enough space to + 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); + +// if(auto_scroll) invert_rect(0,0,LCD_WIDTH,sysfont_height); + + rb->lcd_update_rect(0,0,LCD_WIDTH,sysfont_height); + (rb->audio_next_track()==id3 || rb->audio_next_track()==NULL)? + exec_func(6,print_next_playing):print_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= currentSNCedit>0 && + (sncs[currentSNCedit].x[EDIT].rows + + sncs[currentSNCedit-1].x[EDIT].rows) <= lcd_max_rows && + (!load_translation || sncs[currentSNCedit].time_in_ms!=sncs[currentSNCedit+1].time_in_ms); + + int id = show_prev?currentSNCedit-1:currentSNCedit; + int r, mark_height=fontheight,mark_y=sysfont_height, y=sysfont_height; + lcd_clear_rect(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 set_sysfont=false; + while(ok && idrows && ok) + { + if(r==0 && set_sysfont==false){ + char* end=cat_time(buf,sncs[id].time_in_ms); + if(id==startSNC) *end='>'; + else if(id==stopSNC) *end='<'; + else if(id==currentSNC) *end=MARK; + else *end=' '; + *++end=0; + rb->lcd_putsxy(0,y,buf); + } + y+=print_lyrics_line(snc,r,set_sysfont,rb->lcd_putsxy,indentwidth,y); + r++; + ok=LCD_HEIGHT>=y+fontheight+sysfont_height; + } + if(load_translation==1) set_sysfont=sncs[id].time_in_ms==sncs[id+1].time_in_ms; + if(id==currentSNCedit) mark_height=y-mark_y; + id++; + } + //invert currentSNCedit + rb->lcd_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; + if(snc==stopSNC) rb->lcd_hline(x-2,x+1,y); + rb->lcd_set_drawmode(DRMODE_COMPLEMENT); + rb->lcd_vline(x,y-2,y+2); // | + rb->lcd_drawpixel(x+(snc==startSNC?1:-1),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_rect(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(startSNC!=NOT_INIT) draw_ab(x1+width*sncs[startSNC].time_in_ms/id3->length,y,startSNC); + if(stopSNC!=NOT_INIT) draw_ab(x1+width*sncs[stopSNC].time_in_ms/id3->length-1,y,stopSNC); + if(!auto_scroll){ + rb->lcd_vline(x1+width*sncs[currentSNCedit].time_in_ms/id3->length,y-2,y+2); // | + } + rb->lcd_update_rect(0,y-(sysfont_height>>1),LCD_WIDTH,sysfont_height); + return time_in_ms; +} + +void base_display(void) +{ + if(id3==NULL) return; + 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(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(id3,0,0,rb->lcd_remote_putsxy,rb->lcd_remote_setfont); + rb->lcd_remote_update(); +#endif + update_time_display(id3->elapsed,true); +} + +void load_bitmap(bool reload){ + static struct bitmap bm; + static int bmp_size; + bool load_bmp_mode=prefs.load_bmp && nrSNC>1; + if(reload){ + char* const path = buf; + rb->strcpy(path,id3->path); + int size=BITMAP_BUFFERSIZE; + bm.data=bitmap_buffer; + char* foundc=rb->strrchr(path,'.'); + rb->strcpy(foundc,".bmp"); + + bool read_bmp_file(char* const file){//return true if the bmp cannot be found + return (bmp_size=rb->read_bmp_file(file, &bm, size, FORMAT_ANY))<0; + } + if(read_bmp_file(path)){ // path/.bmp + foundc=rb->strrchr(path,'/')+1; + if(id3->album!=NULL) rb->snprintf(foundc,BUFFERSIZE,"%s.bmp",id3->album); + else goto TRY_WITH_COVER; + if(read_bmp_file(path)){ // path/.bmp + TRY_WITH_COVER: rb->strcpy(foundc,"cover.bmp"); + if(read_bmp_file(path) && !load_bmp_mode){ // path/cover.bmp + //only load sncviewer.bmp if there are no lyrics (not in prefs.load_bmp mode) + read_bmp_file(PLUGIN_DIR"/sncviewer.bmp"); + } + } + } + } + + 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-sysfont_height-1; + int height= (y+bm.heightlcd_drawrect(x-1,y-1,bm.width+2, height+2); +#if LCD_DEPTH > 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 +// else rb->lcd_putsxy(0,y,"lyrics?"); +// rb->lcd_update(); +} + +void peak_meter(void){ + int y=scroll_y0; + const int max_height=LCD_HEIGHT-y-sysfont_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_rect(0,y,width,max_height); + int xr=LCD_WIDTH-width; + lcd_clear_rect(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=currentSNC; + bool set_sysfont=false; + int r; + struct SNCText* snc; +#ifdef HAVE_REMOTE_LCD + +#ifdef IRIVER_H300_SERIES + snc=&(sncs[id].x[RC]); +#else + snc=&(sncs[id].x[EDIT]); //display on rc (same as edit) +#endif + lcd_remote_clear_rect(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); + } + rb->lcd_remote_update(); +#endif + +// if(!auto_scroll) return; //edit mode: don't scroll display on main unit + 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(id3->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; + int height=LCD_HEIGHT-y-artist_title_row_height; + lcd_clear_rect(bmp_width,y,LCD_WIDTH,height); + const int max_height=height+y-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=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[currentSNC].time_in_ms)y+=3; + } + snc=&(sncs[++id].x[SCROLL]); + } +#if defined(HAVE_LCD_COLOR) + if(change_fg_color) +#endif + rb->lcd_set_foreground(LCD_DEFAULT_FG); + rb->lcd_update(); +} + +void update_display(void) +{ + if(!auto_scroll && nrSNC>0){ + update_status_display(); + browse_snc(); + } + else{ + base_display(); + if(nrSNC>1){ + if(prefs.load_bmp && bmp_width>0) load_bitmap(false); + scroll_snc(); + } + + else{ +#ifdef HAVE_REMOTE_LCD + lcd_remote_clear_rect(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?"); + rb->lcd_remote_update(); +#endif + load_bitmap(false); + rb->lcd_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 = file; + if(type==e_ab_ || type==e_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 e_lrc8: utf8=true; break; + case e_lrc:{ + utf_decode=NULL; + const int idlen=2; + rb->read(fd,buf,idlen); + if(rb->strncmp(buf,"\xFF\xFE",idlen)==0) utf_decode=rb->utf16LEdecode; + else if(rb->strncmp(buf,"\xFE\xFF",idlen)==0) utf_decode=rb->utf16BEdecode; + if(utf_decode==NULL) rb->lseek(fd,-idlen,SEEK_CUR); + else{ + rb->splash(HZ,"convert to utf8 ..."); + ssize_t len=rb->read(fd,lyrics_buffer,rb->filesize(fd)-idlen); + rb->close(fd); + size_t size=len; + unsigned char* buffer=(unsigned char*) rb->plugin_get_buffer(&size); + unsigned char* end=utf_decode(lyrics_buffer,buffer,len); + 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 save_ab(void){ + int fd = open_file(e_ab_, O_WRONLY|O_CREAT|O_TRUNC); + if(fd<0){ + abfile_state=e_no; + return 0; + } + rb->fdprintf(fd,"%d\n%d\n",startSNC,stopSNC); + rb->close(fd); + abfile_state=e_yes; + return RET_OK; +} + +bool ab_file(bool load){ //check: load=false + int fd = open_file(e_ab_,O_RDONLY); + if(fd<0){ + abfile_state=e_no; + return false; + } + abfile_state=e_yes; + if(load){ + int len = rb->read_line(fd, buf, BUFFERSIZE); + if(len > 0){ + startSNC=rb->atoi(buf); + rb->read_line(fd, buf, BUFFERSIZE); //no test needed + stopSNC=rb->atoi(buf); + } + } + rb->close(fd); + return true; +} +int load_ab(void){ + return ab_file(true); +} +int clear_ab(void){ + startSNC=NOT_INIT; + stopSNC=NOT_INIT; + set_repeat_mode(-e_ab);//reset repeat ab + return RET_OK; +} + +int remove_ab(void){ + rb->strcpy(buf,file); + char* ext=rb->strrchr(buf,'.')+1; + rb->strcpy(ext,FORMATS[e_ab_]); + rb->remove(buf); + clear_ab(); + abfile_state=e_no; + return RET_OK; +} +MENUITEM_FUNCTION(load_ab_item,MENU_FUNC_CHECK_RETVAL,"load", load_ab ,NULL, NULL, Icon_NOICON); +MENUITEM_FUNCTION(remove_ab_item,MENU_FUNC_CHECK_RETVAL,"remove", remove_ab ,NULL, NULL, Icon_NOICON); +MENUITEM_FUNCTION(clear_ab_item,MENU_FUNC_CHECK_RETVAL,"clear", clear_ab ,NULL, NULL, Icon_NOICON); +MENUITEM_FUNCTION(save_ab_item,MENU_FUNC_CHECK_RETVAL,"save", save_ab ,NULL, NULL, Icon_NOICON); +void ab_menu(void){ + const struct menu_item_ex *ab_items[4]; + static const struct menu_callback_with_desc desc_ab = {NULL,"A-B MENU",Icon_NOICON}; + int i=0; + if(startSNC!=NOT_INIT){ + ab_items[i++] = &clear_ab_item; + ab_items[i++] = &save_ab_item; + } + if(abfile_state==e_unknown) abfile_state=ab_file(false); + if(abfile_state==e_yes){ + ab_items[i++] = &load_ab_item; + ab_items[i++] = &remove_ab_item; + } + if(i>0){ + const struct menu_item_ex menu = {MT_MENU|MENU_HAS_DESC|MENU_ITEM_COUNT(i), + { (void*)ab_items},{.callback_and_desc = &desc_ab}}; + rb->do_menu(&menu, NULL); + } +} +void set_ab_marker(void) +{ + if(nrSNC<1) return; + int snc=auto_scroll?currentSNC:currentSNCedit; + if(snc==startSNC) clear_ab(); + else if(snc==stopSNC) stopSNC=NOT_INIT; + else{ + if(startSNC==NOT_INIT) startSNC=snc; + else if(stopSNC==NOT_INIT) stopSNC=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_rect(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(5,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); + int 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; +} +//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; + bool is_timetag; + int open_tag_size=2; + if(!utf8) is_timetag=(buffer[0]=='\xa2' && buffer[1]=='\xe2'); + else{ + if(buffer[0] < '\xe0') open_tag_size=4; //latin 2 byte -> 2*2 + else if (buffer[0] < '\xf0') open_tag_size=3; //chinese 3 byte + is_timetag=rb->strncmp(&buffer[open_tag_size],"00",2)==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; +} + +inline bool is_wrap_char(const unsigned char* const buffer){ + return (*buffer == ' ') || (*buffer == '-') || (*buffer == ',')|| (*buffer == '.'); +} + +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, &fontheight); // 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; // position of last space + int width = 0; // total width + while((ok=(snc->rows+1)1) force_update_display=true; + width += w; // add to total + } + if(width<=max_width){ + if(pos>buffer+1 || snc->rows==0){ // don't store empty lines (exception: 1st line) + snc->lyrics[++(snc->rows)]=(unsigned char*) pos; //set next row point to the end + } + break; + } + else {//width exceeds max_width + pos=(sp>buffer) ? sp+1 : pos-byte; // there was a space, we want to wrap after it + while (is_wrap_char(pos)) pos++; //new line shouldn't begin with one of these chars + snc->lyrics[++(snc->rows)]=(unsigned char*) pos; + buffer=pos; + } + } + if(!ok) rb->splash(HZ,"WRN: 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 IRIVER_H300_SERIES + ok &= store_line_(&(SNC(snc,RC)),buffer,LCD_REMOTE_WIDTH); +#endif + return ok; +} + +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 IRIVER_H300_SERIES + SNC(snc,RC).lyrics[SNC(snc,RC).rows]=lyrics; +#endif + iso2utf8(buf, lyrics, len); + if(ok) ok=store_line(snc,lyrics,false); + } + return len; +} + +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); +} + +bool read_snc(int fd) +{ + nrSNC=0; + bool ok; + do{ + ok=store_snc_section(&sncs[nrSNC],fd)>0; + struct SNCText* snc=&(sncs[nrSNC].x[SCROLL]); + if(snc->rows==0) init_snc(&sncs[nrSNC],false); + if(++nrSNC < MAX_SECTIONS) + sncs[nrSNC].x[SCROLL].lyrics[0]=snc->lyrics[snc->rows]; + else ok=false; + } while(ok); + //update time: time[i]=time[i-1] + int i=nrSNC-1; + for(;i>0;i--) sncs[i].time_in_ms=sncs[i-1].time_in_ms; + sncs[0].time_in_ms=0; + return true; +} + +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 append){ + int sort_start=0; + if(append==false){ + init_snc(&sncs[0],true); + nrSNC=1; //start with snc = 1 + SET_LYRICS((&sncs[nrSNC]),sncs[0].x[SCROLL].lyrics[0]+1,0); + } + else{ + if(sncs[nrSNC-1].x[SCROLL].lyrics[0][0]=='*') nrSNC--; //last: *...* -> append before + SET_LYRICS((&sncs[nrSNC]),&lyrics_buffer[lyrics_buffer_used],0); + } + + int len,snc_id,src_id; + unsigned char* buffer; + while ((len = rb->read_line(fd, buf, BUFFERSIZE))>0 && nrSNClyrics[0]; + iso2utf8(buffer, lyrics, len-(buffer-buf)); + SET_LYRICS((&sncs[nrSNC]),lyrics,0); + store_line(&sncs[nrSNC],lyrics,true); + while(++nrSNC!=snc_id){ + SNC_COPY((&sncs[nrSNC]),(&sncs[src_id])); + if(sort_start==0) sort_start=nrSNC-1; + } + //next + sncs[nrSNC].x[SCROLL].lyrics[0]=snc->lyrics[snc->rows]; + } + } + lyrics_buffer_used=sncs[nrSNC].x[SCROLL].lyrics[0]-lyrics_buffer; + 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_txt(int fd) +{ + init_snc(&sncs[0],true); + nrSNC=1; //start with snc = 1 + SET_LYRICS((&sncs[nrSNC]),sncs[0].x[SCROLL].lyrics[0]+1,0); + sncs[nrSNC].time_in_ms=0; + int len,old_len=100; + while ((len = rb->read_line(fd, buf, BUFFERSIZE))>0 && nrSNClyrics[0]; + iso2utf8(buf, lyrics, len); + SET_LYRICS((&sncs[nrSNC]),lyrics,0); + store_line(&sncs[nrSNC],lyrics,true); + + sncs[++nrSNC].x[SCROLL].lyrics[0]=snc->lyrics[snc->rows]; + sncs[nrSNC].time_in_ms=-1; + } + //else is a timetag + old_len=len; + } + currentSNCedit=1; +// set_repeat_mode(e_one); + return true; +} + +bool read_id3(int fd){ + if(id3->codectype!=AFMT_MPA_L3){ + 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; + bool found=false; + 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 +// DEBUGF("**********SYLT found: %i, %i, %s\n",pos,framelen,header); + size_t size=framelen; + buffer=(unsigned char*) rb->plugin_get_buffer(&size); + rb->read(fd, buffer, framelen); + found=true; + break; + } + rb->lseek(fd, framelen, SEEK_CUR); //skip content + pos+=HEADER_SIZE; + } + + if(found){ + unsigned char* bpos=buffer+6; //skip header + bool unicode=buffer[0]==1; + utf_decode=rb->utf16LEdecode; + //DEBUGF("**********Unicode: %d\n",unicode); + 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; + } + + init_snc(&sncs[0],true); + nrSNC=1; //start with snc = 1 + SET_LYRICS((&sncs[nrSNC]),sncs[0].x[SCROLL].lyrics[0]+1,0); + const unsigned char* endpos=buffer+framelen; + while(endpos>bpos){ +// DEBUGF("nrSNC %d\n",nrSNC); + struct SNCText* snc=&(sncs[nrSNC].x[SCROLL]); + unsigned char* lyrics=snc->lyrics[0]; + if(!unicode) + while((*lyrics++=*bpos++)!=0); + else{ + unsigned char* start=bpos; + while(*bpos!=0 || *(bpos+1)!=0) bpos+=2; + bpos+=2; + utf_decode(start,lyrics,bpos-start); + } + + SET_LYRICS((&sncs[nrSNC]),snc->lyrics[0],0); + store_line(&sncs[nrSNC],snc->lyrics[0],true); + //timestamp + sncs[nrSNC].time_in_ms=bytes2int(bpos[0],bpos[1],bpos[2],bpos[3]); + bpos+=4; + sncs[++nrSNC].x[SCROLL].lyrics[0]=snc->lyrics[snc->rows]; + } + } + return found; +} +bool load_file(enum e_supported_formats type){ + bool ret=true; + int fd = open_file(type, O_RDONLY); + if (fd < 0){ + if(type==e_tr) load_translation=-1; //load_translation={-1:fails, 0:don't load, 1:ok} + return type==e_lrc ? load_file(e_lrc8):false; //try to open lrc8 + } + if(type!=e_tr){ + force_update_display=false; + lyrics_buffer_used=0; + SET_LYRICS((&sncs[0]),lyrics_buffer,0); + } + else if(load_translation==-1) load_translation=1; + if(type==e_lrc || type==e_lrc8 || type==e_tr){ + if(type==e_tr) rb->lcd_setfont(FONT_SYSFIXED); + read_lrc(fd, type==e_tr); + if(type==e_tr) rb->lcd_setfont(FONT_UI); + } + else if(type==e_snc) read_snc(fd); + else if(type==e_mp3) ret=read_id3(fd); + else{ + bmp_width=NOT_INIT; + read_txt(fd); + } + rb->close(fd); + + //if load_translation is set, try to load translation file + //continue if loading fails + if(load_translation && type!=e_tr && load_file(e_tr)) return true; + + //lrc: after sorting, the last snc could be somewhere in the lyrics buffer + if(lyrics_buffer_used==0) lyrics_buffer_used=sncs[nrSNC].x[SCROLL].lyrics[0]-lyrics_buffer; + + //add_last_section + if(nrSNC+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); + nrSNC++; + } + //misuse force_update_display + if(force_update_display && utf8==false) utf8=true; + return ret; +} + +// void wait(bool (*condition)(void)){ +// //user has the opportunity to cancel the wait state -> break an endless loop +// while(condition() && rb->get_action(CONTEXT_STD,TIMEOUT_NOBLOCK)==ACTION_NONE) rb->yield(); +// } + +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); + int diff; + while(((diff=id3->elapsed-time_in_ms)>500 || diff < -500)&& + !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%=(nrSNC-1); //0 .. nrSNC-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(nrSNCaudio_current_track(); + if(all){ + set_repeat_mode(e_off); + auto_scroll=true; + currentSNC=0; + nrSNC=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=e_unknown; + } + bmp_width=NOT_INIT; + if(prefs.load_bmp){ + nrSNC=MAX_SECTIONS; //hack: to ensure that we are in prefs.load_bmp_mode + load_bitmap(true); + nrSNC=0; + //if bitmap width is wider than half lcd_width then show bitmap without lyrics + if(bmp_width>(LCD_WIDTH>>1)){ + update_display(); + return; + } + } + if(!load_file(e_lrc) ? (!load_file(e_mp3)? !load_file(e_snc):false) : false){ + nrSNC=0; + sncs[0].time_in_ms=0; //to enable ff_rew + SET_ROWS((&sncs[0]),0); + load_bitmap(true); + } + update_display(); +} + +void save2file(void) +{ + if(modified==0) return; + #define NL "\r\n" + rb->splash(HZ>>1,"saving..."); + force_update_display=true; + + int r,i; + int last=nrSNC-1; + char* ext=rb->strrchr(file,'.')+1; + if(rb->strncmp(ext,FORMATS[e_txt],3)==0 || + (rb->strcmp(ext,FORMATS[e_lrc])==0 && utf8)) //lrc: -> rename to lrc8 + { + bool complete=false; + i=1; + while(iremove(file); //remove old file + rb->strcpy(ext,FORMATS[utf8 ? e_lrc8 : e_lrc]); //rename file to lrc/8 +// set_repeat_mode(-e_one); //restore repeat mode + } + } + else if(rb->strcmp(ext,FORMATS[e_mp3])==0){ + rb->strcpy(ext,FORMATS[utf8 ? e_lrc8 : e_lrc]); + } + else if(load_translation==1){ + rb->strcpy(ext,FORMATS[e_tr]); + } + int fd = rb->open(file, O_WRONLY|O_CREAT|O_TRUNC); + if(fd<0) return; + + bool is_lrc=rb->strncmp(ext,FORMATS[e_lrc],3)==0 || load_translation==1; //lrc,lrc8 + if(is_lrc){ + rb->fdprintf(fd,"[ti:%s]" NL "[ar:%s]" NL,id3->title,id3->artist); + rb->strcpy(buf,"[%02d:%02d.%02d]"); + } + 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); + } + } + 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); + + modified=0; + time_offset=0; + //set_repeat_mode(-e_one); +} + +void confirm_saving(void) +{ + if(modified==0 || nrSNC<1) return; + rb->lcd_clear_display(); + rb->lcd_puts(0,0,"save changes?"); + rb->lcd_puts(0,2,"yes: SELECT"); + rb->lcd_puts(0,3,"no: STOP"); + rb->lcd_update(); + + int button=ACTION_NONE; + while(button!=ACTION_STD_CANCEL) + { + button = rb->get_action(CONTEXT_YESNOSCREEN,HZ); + if(button==ACTION_YESNO_ACCEPT){ + save2file(); + button=ACTION_STD_CANCEL; + } + rb->yield(); + } + rb->action_signalscreenchange(); +} + +void update(void) +{ + bool no_repeat = rb->global_settings->repeat_mode!=e_one && rb->global_settings->repeat_mode!=e_ab; + if(id3 != rb->audio_current_track()) { //new track is playing + if(no_repeat){ + confirm_saving(); + reset(true); + } + else id3 = rb->audio_current_track(); // repeat one, ab + } + unsigned long elapsed = id3->elapsed+UPDATE_LATENCY; + if(nrSNC>1){ + int showSNC=currentSNC; + int nextSNC=currentSNC; + while(sncs[currentSNC].time_in_ms==sncs[++nextSNC].time_in_ms); + if(auto_scroll){ + //display countdown + struct SNCSection* snc=&sncs[currentSNC]; + 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_rect(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[currentSNC].time_in_ms > elapsed){ //-> ; <- + currentSNC=nextSNC%nrSNC; + nextSNC=currentSNC; + while(sncs[currentSNC].time_in_ms==sncs[++nextSNC].time_in_ms); //update next snc + if(showSNC==currentSNC) + sncs[nrSNC].time_in_ms=NOT_INIT; //ensure snc[0] < elapsed time < snc[max] + } + if(showSNC!=currentSNC){ + if(auto_scroll) scroll_snc(); + else if((currentSNC+1)>=currentSNCedit && + currentSNC=sncs[stopSNC].time_in_ms) || + (startSNC!=NOT_INIT && currentSNC= id3->length && no_repeat) +// set_repeat_mode(e_one); //crashes sometime !!!! + exec_func(0,NULL); +} + +int insert_sections(void){ + if(startSNC+1==nrSNC || currentSNCedit+1==nrSNC || (startSNC currentSNCedit)) return false; + if(startSNC==NOT_INIT) startSNC=0; //insert blank + int offset=sncs[currentSNCedit].time_in_ms-sncs[startSNC].time_in_ms; + int nr2Copy=(stopSNC==NOT_INIT)?1:stopSNC-startSNC; + nrSNC+=nr2Copy; + if(nrSNC>=MAX_SECTIONS){ + rb->splash(HZ,"WRN: not enough sections!"); + nrSNC=MAX_SECTIONS-1; + } + int i=nrSNC; + for(;i>currentSNCedit+nr2Copy;i--)//move + snc_copy(&sncs[i],&sncs[i-nr2Copy],0); + if(startSNC>currentSNCedit) startSNC+=nr2Copy; + + for(i=0;inrSNC-1) currentSNCedit=nrSNC-1; + return RET_OK; +} + +int join_sections(void){ + if(currentSNCedit>=nrSNC-2 || currentSNCedit==0) return false; + //need to save reference pointer because currentSNCedit is not fixed + unsigned char* join_ref1=sncs[currentSNCedit].x[SCROLL].lyrics[0]; + unsigned char* join_ref2=sncs[currentSNCedit+1].x[SCROLL].lyrics[0]; + unsigned char* lyrics; + unsigned long time=sncs[currentSNCedit].time_in_ms; //save time for restoring currentSNCedit + int i; + for(i=nrSNC-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 + currentSNCedit=i; // make sure currentSNCedit fulfills the deletion criteria + startSNC=i+1; // point to merged sync + delete_sections(); // and get rid of it (does other housekeeping as well) + } + } + for(i=1;istrrchr(file,'.')+1; + const int max=sizeof(FORMATS)/sizeof(char *); + int i; + for(i=0; istrcmp(ext,FORMATS[i])==0){ + load_file(i); + break; + } + } + return RET_OK; +} +int load_translation_file(void){ + confirm_saving(); + time_offset=0; + modified=0; + load_translation=load_translation==0?1:0; + load_translation ? load_file(e_tr) : reload_file(); + return RET_OK; +} +int switch_repeat_one(void){ + set_repeat_mode(rb->global_settings->repeat_mode!=e_one?e_one:-e_one); + return RET_OK; +} + +int edit_line(void){ + bool restore=rb->global_settings->repeat_mode!=e_one; + set_repeat_mode(e_one); + char* lyrics=sncs[currentSNCedit].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[currentSNCedit]; + 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; + } + id3=rb->audio_current_track(); + if(restore) set_repeat_mode(-e_one); + force_update_display=true; + return changed; +} +int edit_and_dup_lines(void){ + size_t size=BUFFERSIZE; + //backup old line + unsigned char* old_lyrics=rb->plugin_get_buffer(&size); + rb->strcpy(old_lyrics,sncs[currentSNCedit].x[SCROLL].lyrics[0]); + if(edit_line()){ + int i; + for(i=1;ibrowse_id3(); + return RET_OK; +} + +int print_dict(int fd,const unsigned char* string){ + int row=0; + const int max=lcd_max_rows-1; + lcd_clear_rect(0,fontheight,LCD_WIDTH,LCD_HEIGHT-fontheight); + rb->lcd_puts_scroll(0,++row,string); + ++row; + rb->lcd_puts(0,++row,string); + int len; + int delta=0; + while(rowread_line(fd, buf, BUFFERSIZE))>0){ + delta+=len; + rb->lcd_puts(0,++row,buf); + } + rb->lcd_update(); + return delta; +} +void lookup_word_in_row(int fd, int row){ + unsigned char* lyrics=sncs[currentSNCedit].x[SCROLL].lyrics[row]; + int file_pos_next=0, file_pos_next_page=0; + const int max_words=30; + struct xy{ + unsigned char* pos; + int w; + int byte; + int x; + } pos_container[max_words]; + unsigned char* pos=lyrics; + int i=0, width=0; + while(*pos != 0 && ilcd_clear_display(); + rb->lcd_putsxy(0,0,lyrics); + rb->lcd_set_drawmode(DRMODE_COMPLEMENT); + rb->lcd_fillrect(pos_container[i].x, 0, pos_container[i].w, fontheight); + rb->lcd_update(); + bool next=false; + int button; + do{ + button = rb->get_custom_action(CONTEXT_CUSTOM,HZ,get_context_map); + switch(button){ + case ACTION_DOWN_REL:{ + if(check_next_action(ACTION_DOWN,ACTION_DOWN_REL)==false){ + //next line + rb->lseek(fd,file_pos_next,SEEK_SET); + file_pos_next+=rb->read_line(fd, buf, BUFFERSIZE); + } + else{ //next page + file_pos_next=file_pos_next_page; + } + file_pos_next_page=file_pos_next+print_dict(fd,buf); + break; + } + case ACTION_SELECT_REL:{ + rb->lcd_set_drawmode(DRMODE_SOLID); + file_pos_next=0; + rb->lseek(fd,file_pos_next,SEEK_SET); + int len; + while((len=rb->read_line(fd, buf, BUFFERSIZE))>0){ + file_pos_next+=len; + if(rb->strncmp(pos_container[i].pos,buf,pos_container[i].byte)==0){//found + file_pos_next_page=file_pos_next+print_dict(fd,buf); + break; + } + } + break; + } + case ACTION_RIGHT_HOLD: + case ACTION_LEFT_HOLD: + case ACTION_RIGHT_REL: + case ACTION_LEFT_REL:{ + rb->lcd_set_drawmode(DRMODE_COMPLEMENT); + rb->lcd_fillrect(pos_container[i].x, 0, pos_container[i].w, fontheight); + i=(button==ACTION_RIGHT_REL || button==ACTION_RIGHT_HOLD)? + i+1: i-1; + if(i>=0 && pos_container[i].x<=LCD_WIDTH && ilcd_fillrect(pos_container[i].x, 0, pos_container[i].w, fontheight); + rb->lcd_update(); + } + else next=true; + } + } + }while(button!=ACTION_STOP_REL && next==false); + if(next){ + if(i<0) lookup_word_in_row(fd,row>0?row-1:0); + else lookup_word_in_row(fd,(row+1)%sncs[currentSNCedit].x[SCROLL].rows); + } + else{ + rb->lcd_set_drawmode(DRMODE_SOLID); + rb->lcd_stop_scroll(); + } +} + +int lookup_word(void){ + int fd=rb->open(PLUGIN_DIR"/sncdict.txt", O_RDONLY); //open dictionary + if(fd < 0) return 0; + lookup_word_in_row(fd,0); + rb->close(fd); + return RET_OK; +} +#endif +//create menu items + MENUITEM_FUNCTION(delete_item,MENU_FUNC_CHECK_RETVAL,"delete (a-b/current)", delete_sections,NULL,NULL,Icon_NOICON); + MENUITEM_FUNCTION(insert_item,MENU_FUNC_CHECK_RETVAL,"insert (a-b/blank)", insert_sections,NULL, NULL, Icon_NOICON); + MENUITEM_FUNCTION(join_item,MENU_FUNC_CHECK_RETVAL,"join sections", join_sections,NULL, NULL, Icon_NOICON); + MENUITEM_FUNCTION(edit0_item,MENU_FUNC_CHECK_RETVAL,"edit+", edit_and_dup_lines,NULL, NULL, Icon_NOICON); + MENUITEM_FUNCTION(edit1_item,MENU_FUNC_CHECK_RETVAL,"edit", edit_line,NULL, NULL, Icon_NOICON); +#ifdef CUSTOM_PLUGIN_PATCH + MENUITEM_FUNCTION(dict_item,MENU_FUNC_CHECK_RETVAL," dict", lookup_word,NULL, NULL, NULL); +#endif + //show in auto_scroll mode + MENUITEM_FUNCTION(time_offset_item,MENU_FUNC_CHECK_RETVAL,"time offset", set_offset_screen,NULL, NULL, Icon_NOICON); + MENUITEM_FUNCTION(repeat1_item,MENU_FUNC_CHECK_RETVAL,"repeat 1", switch_repeat_one,NULL, NULL, Icon_NOICON); + MENUITEM_FUNCTION(reload_file_item,MENU_FUNC_CHECK_RETVAL,"reload file", reload_file,NULL, NULL, Icon_NOICON); + MENUITEM_FUNCTION(album_art_item,MENU_FUNC_CHECK_RETVAL,"album art", switch_album_art,NULL, NULL, Icon_NOICON); +#ifdef SIMULATOR + MENUITEM_FUNCTION(save_item,MENU_FUNC_CHECK_RETVAL,"save", force_save2file,NULL, NULL, NULL); +#endif +#ifdef CUSTOM_PLUGIN_PATCH + MENUITEM_FUNCTION(translation_item,MENU_FUNC_CHECK_RETVAL," translation on/off", load_translation_file,NULL, NULL, NULL); + MENUITEM_FUNCTION(id3_item,MENU_FUNC_CHECK_RETVAL," id3 info", browse_id3,NULL, NULL, NULL); + MENUITEM_FUNCTION(browse_dir_item,MENU_FUNC_CHECK_RETVAL," browse dir", snc_browse_dir,NULL, NULL, NULL); +#endif + +#ifdef CUSTOM_PLUGIN_PATCH +#define SCROLL_MENU_ITEMS \ + &time_offset_item,&repeat1_item,&reload_file_item,&album_art_item \ + ,&translation_item,&id3_item,&browse_dir_item +#else +#define SCROLL_MENU_ITEMS \ + &time_offset_item,&repeat1_item,&reload_file_item,&album_art_item +#endif + //MAKE_MENU + MAKE_MENU(scroll_menu, "MENU", NULL, Icon_NOICON, SCROLL_MENU_ITEMS +#ifdef SIMULATOR + ,&save_item +#endif + ); + MAKE_MENU(edit_menu, "EDIT MENU", NULL, Icon_NOICON, + &delete_item,&insert_item,&join_item,&edit0_item,&edit1_item, +#ifdef CUSTOM_PLUGIN_PATCH + &dict_item, +#endif + SCROLL_MENU_ITEMS); + +void show_menu(void) +{ + static int edit_selected=0; + static int scroll_selected=0; + auto_scroll ? + rb->do_menu(&scroll_menu,&scroll_selected): + rb->do_menu(&edit_menu,&edit_selected); + update_display(); +} + +void set_time_tag(void) +{ + if(auto_scroll || currentSNCedit==0 || currentSNCedit+1==nrSNC) return; + sncs[currentSNCedit].time_in_ms=id3->elapsed-SET_TIME_LATENCY; //reaction time + if(currentSNCedit!=nrSNC-2) currentSNCedit++; + modified|=MODIFIED_TIMETAG; + update_status_display(); + browse_snc(); +} + +void init(void){ + rb->lcd_setfont(FONT_SYSFIXED); + rb->lcd_getstringsize("0", &indentwidth, &sysfont_height); + rb->lcd_setfont(FONT_UI); + rb->lcd_getstringsize("0:00>", &indentwidth, &fontheight); //init 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; + load_translation=0; + 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; + //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); + } + rb->backlight_set_timeout(prefs.backlight); + reset(true); +} + +int set_offset_screen(void) +{ + if(nrSNC<1) return false; + int offset=time_offset; //save old value + int val=NOT_INIT, accel_counter=1; + int step_size=500; + int button; + rb->lcd_clear_display(); + rb->lcd_puts(0,0,"time offset:"); + do{ + if(val!=time_offset){ + val=time_offset<0?-time_offset:time_offset; + rb->snprintf(buf,BUFFERSIZE,"%c" TIME_FORMAT ".%01d", + time_offset<0?'-':'+', CALC_MM(val),CALC_SS(val),(val%1000)/100); + rb->lcd_puts(0,1,buf); + rb->lcd_update(); + val=time_offset; + } + button = rb->get_custom_action(CONTEXT_CUSTOM,HZ,get_context_map); + if(button==ACTION_UP_REL || button==ACTION_DOWN_REL){ + accel_counter=0; + step_size=500; + } + else if(button==ACTION_UP_HOLD || button==ACTION_DOWN_HOLD) + if(++accel_counter>10) step_size=100*accel_counter; + + switch(button){ + case ACTION_RIGHT_REL: time_offset+=100; break; + case ACTION_LEFT_REL: time_offset-=100; break; + case ACTION_STOP_REL: time_offset=0; break; + case ACTION_UP_REL: + case ACTION_UP_HOLD: time_offset+=step_size; break; + case ACTION_DOWN_REL: + case ACTION_DOWN_HOLD: time_offset-=step_size; break; + default:; + } + if(id3 != rb->audio_current_track()){ //track has changed -> exit + return true; + } + rb->yield(); + } while(button!=ACTION_SELECT_REL); + offset=time_offset-offset; //calculate change + if(offset!=0){ + int start=1, stop=nrSNC-1; + if(startSNC!=NOT_INIT){ + start=startSNC; + if(stopSNC!=NOT_INIT) stop=stopSNC; + } + int i=start; + for(;i id3->length){ + sncs[i].time_in_ms = offset < 0 ? 0 : id3->length; + } + } + } + modified=(time_offset!=0)? + modified|MODIFIED_OFFSET_TIMETAG:modified&~MODIFIED_OFFSET_TIMETAG; + update_display(); + return true; +} + +int handle_button_scroll(int button){ + switch (button) { + case ACTION_REC_REL: + prefs.save^=PREF_BACKLIGHT; + prefs.backlight=!prefs.backlight; + rb->backlight_set_timeout(prefs.backlight); + break; +#ifdef CUSTOM_PLUGIN_PATCH + case ACTION_PLAY_PAUSE_HOLD:{ + rb->playlist_viewer(); + force_update_display=true; + update(); + break; + } +#endif +#if (CONFIG_KEYPAD == IPOD_4G_PAD) || (CONFIG_KEYPAD == IPOD_3G_PAD) + case ACTION_DOWN_REL: //scroll forward + case ACTION_DOWN_HOLD: + set_volume(rb->global_settings->volume+1); + break; + case ACTION_UP_REL: //scroll backward + case ACTION_UP_HOLD: + set_volume(rb->global_settings->volume-1); + break; +#else + case ACTION_DOWN_REL: + case ACTION_DOWN_HOLD: + set_volume(rb->global_settings->volume-1); + break; + case ACTION_UP_REL: + case ACTION_UP_HOLD: + set_volume(rb->global_settings->volume+1); + break; +#endif + case ACTION_LEFT_REL: + check_next_action(ACTION_LEFT,ACTION_LEFT_REL)? + rb->audio_prev():ff_rew_snc(startSNC!=NOT_INIT?startSNC:0); + break; + case ACTION_RIGHT_REL: + (nrSNC > 1 && check_next_action(ACTION_RIGHT,ACTION_RIGHT_REL)) ? + ff_rew_snc(currentSNC+1):rb->audio_next(); + break; + case ACTION_SELECT_REL: + switch_mode(false); + break; + default:button=ACTION_NONE; + } + return button; +} +int handle_button_edit(int button){ + switch (button) { + case ACTION_REC_REL: + set_time_tag(); + break; + case ACTION_DOWN_HOLD: + if(currentSNCedit=nrSNC) currentSNCedit = 1; + browse_snc(); + break; + case ACTION_UP_HOLD: + if(currentSNCedit>2) currentSNCedit--; + else break; + case ACTION_UP_REL: + if(--currentSNCedit<0) currentSNCedit = nrSNC-2; + browse_snc(); + break; + case ACTION_SELECT_REL: + ff_rew_snc(currentSNCedit); + break; + case ACTION_LEFT_REL: + switch_mode(true); + break; + case ACTION_RIGHT_REL: + 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 + init(); + int button; + long newPos=0; + int timeout = HZ>>1; + while(true){ + button = rb->get_custom_action(CONTEXT_CUSTOM,timeout,get_context_map); + switch (button) { //valid for both modes +#ifdef HAS_AB_BUTTON + case ACTION_AB_REL: + set_ab_marker(); + update_display(); + break; + case ACTION_AB_HOLD: + ab_menu(); + update_display(); + break; +#endif + case ACTION_REC_HOLD: + save2file(); + break; + case ACTION_PLAY_PAUSE_REL: + (rb->audio_status() & AUDIO_STATUS_PAUSE) ? + rb->audio_resume():rb->audio_pause(); + exec_func(1,update_status_display); + break; + case ACTION_LEFT_HOLD_REL: + case ACTION_RIGHT_HOLD_REL: + ff_rew(newPos); + break; + case ACTION_LEFT: + case ACTION_RIGHT: + newPos=id3->elapsed; break; + case ACTION_LEFT_HOLD: + newPos-=4000; + case ACTION_RIGHT_HOLD: + newPos+=2000; + newPos=update_time_display(newPos,true); + break; + case ACTION_STOP_REL:{ + //restore backlight + rb->backlight_set_timeout(rb->global_settings->backlight_timeout); + set_repeat_mode(e_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); + } + confirm_saving(); + return PLUGIN_OK; + } + case ACTION_SELECT_HOLD: show_menu(); break; + default: + if(rb->default_event_handler(button)==SYS_USB_CONNECTED) return PLUGIN_USB_CONNECTED; + if((rb->audio_status() & AUDIO_STATUS_PLAY)==false) return PLUGIN_OK; + if((auto_scroll ? handle_button_scroll(button): + handle_button_edit(button))==ACTION_NONE) + update(); //only update if no button was pressed + if(nrSNC<2) peak_meter(); + (!force_update_display)?update_time_display(id3->elapsed,!auto_scroll):update_display(); + } + } + return PLUGIN_OK; +} Index: apps/plugins/SOURCES =================================================================== --- apps/plugins/SOURCES (revision 13673) +++ apps/plugins/SOURCES (working copy) @@ -18,6 +18,7 @@ stopwatch.c vbrfix.c viewer.c +sncviewer.c /* plugins built for all targets, but not for the simulator */ #if !defined(SIMULATOR)