? apps/recorder/.recording.c.swp ? firmware/common/strcspn.c Index: apps/main.c =================================================================== RCS file: /cvsroot/rockbox/apps/main.c,v retrieving revision 1.67 diff -u -b -r1.67 main.c --- apps/main.c 14 Feb 2003 09:44:33 -0000 1.67 +++ apps/main.c 7 Mar 2003 04:30:58 -0000 @@ -164,6 +164,10 @@ settings_load(); +#ifdef HAVE_RECORDING + load_rec_settings(); +#endif + mpeg_init( global_settings.volume, global_settings.bass, global_settings.treble, Index: apps/settings.c =================================================================== RCS file: /cvsroot/rockbox/apps/settings.c,v retrieving revision 1.126 diff -u -b -r1.126 settings.c --- apps/settings.c 27 Feb 2003 15:02:17 -0000 1.126 +++ apps/settings.c 7 Mar 2003 04:30:59 -0000 @@ -54,6 +54,10 @@ #include "wps-display.h" #include "powermgmt.h" #include "sprintf.h" +#include "timefuncs.h" +#ifdef HAVE_RECORDING +#include "recording.h" +#endif struct user_settings global_settings; char rockboxdir[] = ROCKBOX_DIR; /* config/font/data file directory */ @@ -68,6 +72,13 @@ #define MAX_LINES 2 #endif +#ifdef HAVE_RECORDING +#define RECSET_FILE ROCKBOX_DIR "/.recSet" +#define RECSET_TAG_ID3 "recID3" +#define RECSET_TAG_FMT "fileFmt" +extern struct recSet recSet; +#endif + /******************************************** Config block as saved on the battery-packed RTC user RAM memory block @@ -1600,4 +1611,272 @@ return false; } +#endif + + +bool set_genre(char* string, unsigned char* variable) +{ + bool done = false; + int button; + unsigned char org_value = *variable; + + if (NULL == id3_get_genre(*variable)) + *variable = 0; + +#ifdef HAVE_LCD_BITMAP + if(global_settings.statusbar) + lcd_setmargins(0, STATUSBAR_HEIGHT); + else + lcd_setmargins(0, 0); +#endif + + lcd_clear_display(); + lcd_puts_scroll(0, 0, string); + + while (!done) { + char str[32]; + snprintf(str,sizeof str,"%03d) %s", *variable, + id3_get_genre(*variable)); + lcd_puts(0, 1, str); +#ifdef HAVE_LCD_BITMAP + status_draw(); +#endif + lcd_update(); + + button = button_get_w_tmo(HZ/2); + switch(button) { +#ifdef HAVE_RECORDER_KEYPAD + case BUTTON_UP: + case BUTTON_UP | BUTTON_REPEAT: +#else + case BUTTON_RIGHT: + case BUTTON_RIGHT | BUTTON_REPEAT: +#endif + *variable += 1; + if (NULL == id3_get_genre(*variable)) + *variable -= 1; + break; + +#ifdef HAVE_RECORDER_KEYPAD + case BUTTON_DOWN: + case BUTTON_DOWN | BUTTON_REPEAT: +#else + case BUTTON_LEFT: + case BUTTON_LEFT | BUTTON_REPEAT: +#endif + if (*variable > 0) + *variable -= 1; + break; + +#ifdef HAVE_RECORDER_KEYPAD + case BUTTON_LEFT: + case BUTTON_PLAY: +#else + case BUTTON_PLAY: +#endif + done = true; + break; + +#ifdef HAVE_RECORDER_KEYPAD + case BUTTON_OFF: +#else + case BUTTON_STOP: + case BUTTON_MENU: +#endif + if (*variable != org_value) { + *variable=org_value; + lcd_stop_scroll(); + lcd_puts(0, 0, str(LANG_MENU_SETTING_CANCEL)); + lcd_update(); + sleep(HZ/2); + } + done = true; + break; + + case SYS_USB_CONNECTED: + usb_screen(); + return true; + + } + + } + lcd_stop_scroll(); + + return false; +} + +#ifdef HAVE_RECORDING + +bool load_rec_settings(void) +{ + int fd; + char buf[128], *tokEnd; + char *readStart; + char tag[16], sep; + int filelen, nRead=0; + struct mp3entry *id3 = &(recSet.id3); + unsigned int id3BufLen, id3BufUsed; + int copyLen; + int tokIdx=-1; + bool done=false; + + /* set default values (if no file or entry not found) */ + /*----------------------------------------------------*/ + + /* default recording ID3 tags */ + { + struct tm *tm; + + memset(id3->id3v2buf, 0, sizeof(id3->id3v2buf)); + tm = get_time(); + + id3BufUsed = 0; id3BufLen = sizeof(id3->id3v2buf); + id3->title = id3->id3v2buf; + strncpy(id3->title, "recording", id3BufLen-id3BufUsed); + id3BufUsed += strlen(id3->title) + 1; + id3->artist = id3->id3v2buf + id3BufUsed; + strncpy(id3->artist, "archos", id3BufLen-id3BufUsed); + id3BufUsed += strlen(id3->artist) + 1; + id3->album = id3->id3v2buf + id3BufUsed; + id3->tracknum = 0; + id3->genre = 12; /* OTHER */ + id3->year = tm->tm_year%100 + 2000; + } + + /* default rec filename fmt */ + { + strncpy(recSet.filenameFmt, "/R%ty%tm%td%th%tn%ts.mp3", + sizeof(recSet.filenameFmt)); + } + + fd = open(RECSET_FILE,O_RDONLY); + if (fd < 0) + return true; + + filelen = lseek(fd, 0, SEEK_END); + tag[0] = 0; + readStart = buf; + tokEnd=NULL; + lseek(fd, 0, SEEK_SET); + id3BufUsed = 0; id3BufLen = sizeof(id3->id3v2buf)-1; + id3->id3v2buf[id3BufLen] = 0; + + /* read lines to parse individual settings */ + while (!done) + { + nRead = 0; + if (lseek(fd, 0, SEEK_CUR) < filelen) + nRead = read(fd, readStart, sizeof(buf)-(readStart-buf)); + memset(readStart+nRead, 0, sizeof(buf)-(readStart-buf)-nRead); + + tokEnd = buf + strcspn(buf, "*\n"); + sep = *tokEnd; *tokEnd = 0; + + if (strlen(tag) < 1) { + strncpy(tag, buf, sizeof(tag)); + tokIdx = -1; + } + else if (!strcmp(tag, RECSET_TAG_ID3)) { + switch (tokIdx) { + case 0: + id3->title = id3->id3v2buf; + copyLen = (strlen(buf)+1 < id3BufLen-id3BufUsed) ? + strlen(buf)+1 : id3BufLen-id3BufUsed; + strncpy(id3->title, buf, copyLen); + id3BufUsed += copyLen; + break; + case 1: + id3->artist = id3->id3v2buf+id3BufUsed; + copyLen = (strlen(buf)+1 < id3BufLen-id3BufUsed) ? + strlen(buf)+1 : id3BufLen-id3BufUsed; + strncpy(id3->artist, buf, copyLen); + id3BufUsed += copyLen; + break; + case 2: + id3->album = id3->id3v2buf+id3BufUsed; + copyLen = (strlen(buf)+1 < id3BufLen-id3BufUsed) ? + strlen(buf)+1 : id3BufLen-id3BufUsed; + strncpy(id3->album, buf, copyLen); + id3BufUsed += copyLen; + break; + case 3: + id3->year = atoi(buf); + break; + case 4: + id3->tracknum = atoi(buf); + break; + case 5: + id3->genre = atoi(buf); + break; + } + } + else if (!strcmp(tag, RECSET_TAG_FMT)) { + *tokEnd = sep; + tokEnd = buf + strcspn(buf, "\n"); + sep = *tokEnd; *tokEnd = 0; + + strncpy(recSet.filenameFmt, buf, + (strlen(buf)+1 < sizeof(recSet.filenameFmt)-1) ? + strlen(buf)+1 : sizeof(recSet.filenameFmt)-1); + recSet.filenameFmt[sizeof(recSet.filenameFmt)-1] = 0; + } + else /* unknown setting (abort) */ + return true; + + if (sep=='*') tokIdx++; /* switch to next token in same tag */ + else if (sep=='\n') tag[0]=0; /* switch to new tag */ + else done=true; /* EOF found. quit */ + + copyLen = sizeof(buf)-(tokEnd-buf)-1; + memcpy(buf, tokEnd+1, copyLen); + readStart += nRead-(tokEnd-buf+1); + } + + close(fd); + + return false; +} + + +bool save_rec_settings(void) +{ + char buf[128]; + int fd; + + fd = creat(RECSET_FILE, 0); + if (fd < 0) + return true; + + /* write default ID3 settings */ + { + struct mp3entry *id3 = &(recSet.id3); + snprintf(buf, sizeof(buf), "%s*", RECSET_TAG_ID3); + write(fd, buf, 7); + snprintf(buf, sizeof(buf), "%s*", id3->title); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "%s*", id3->artist); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "%s*", id3->album); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "%d*", id3->year); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "%d*", id3->tracknum); + write(fd, buf, strlen(buf)); + snprintf(buf, sizeof(buf), "%d\n", id3->genre); + write(fd, buf, strlen(buf)); + } + + /* write custom rec filename format */ + { + snprintf(buf, sizeof(buf), "%s*", RECSET_TAG_FMT); + write(fd, buf, strlen(RECSET_TAG_FMT)+1); + snprintf(buf, sizeof(buf), "%s\n", recSet.filenameFmt); + write(fd, buf, strlen(buf)); + } + + close(fd); + + return false; +} + #endif Index: apps/settings.h =================================================================== RCS file: /cvsroot/rockbox/apps/settings.h,v retrieving revision 1.73 diff -u -b -r1.73 settings.h --- apps/settings.h 27 Feb 2003 14:22:29 -0000 1.73 +++ apps/settings.h 7 Mar 2003 04:30:59 -0000 @@ -162,6 +162,9 @@ void settings_apply_pm_range(void); void settings_display(void); +bool save_rec_settings(void); +bool load_rec_settings(void); + bool settings_load_config(char* file); bool set_bool_options(char* string, bool* variable, char* yes_str, char* no_str ); @@ -173,6 +176,7 @@ void (*function)(int), int step, int min, int max ); bool set_time(char* string, int timedate[]); void set_file(char* filename, char* setting, int maxlen); +bool set_genre(char* string, unsigned char *variable); /* global settings */ extern struct user_settings global_settings; Index: apps/sound_menu.c =================================================================== RCS file: /cvsroot/rockbox/apps/sound_menu.c,v retrieving revision 1.33 diff -u -b -r1.33 sound_menu.c --- apps/sound_menu.c 27 Feb 2003 15:02:18 -0000 1.33 +++ apps/sound_menu.c 7 Mar 2003 04:30:59 -0000 @@ -32,6 +32,9 @@ #endif #include "lang.h" #include "sprintf.h" +#ifdef HAVE_RECORDING +#include "recording.h" +#endif static char *fmt[] = { @@ -219,6 +222,7 @@ &global_settings.rec_quality, NULL, 1, 0, 7 ); } + #endif /* HAVE_MAS3587F */ static void set_chanconf(int val) @@ -275,6 +279,8 @@ { str(LANG_RECORDING_FREQUENCY), recfrequency }, { str(LANG_RECORDING_SOURCE), recsource }, { str(LANG_RECORDING_CHANNELS), recchannels }, + { str(LANG_EDIT_ID3), rec_edit_id3 }, + { str(LANG_EDIT_REC_FILENAME_FMT), rec_edit_filenameFmt } }; m=menu_init( items, sizeof items / sizeof(struct menu_items) ); @@ -284,3 +290,4 @@ return result; } #endif + Index: apps/tree.c =================================================================== RCS file: /cvsroot/rockbox/apps/tree.c,v retrieving revision 1.175 diff -u -b -r1.175 tree.c --- apps/tree.c 23 Feb 2003 18:52:53 -0000 1.175 +++ apps/tree.c 7 Mar 2003 04:31:00 -0000 @@ -653,6 +653,7 @@ { bool exit = false; bool used = false; + bool allowEditID3 = false; bool playing = mpeg_status() & MPEG_STATUS_PLAY; char buf[MAX_PATH]; struct entry* f = &dircache[dirstart + dircursor]; @@ -693,6 +694,19 @@ lcd_putsxy(0, LCD_HEIGHT/2 - h/2, ptr); lcd_bitmap(bitmap_icons_7x8[Icon_FastBackward], LCD_WIDTH/2 - 16, LCD_HEIGHT/2 - 4, 7, 8, true); + + /* don't edit anything other than mp3 files */ + if (!strcmp(file+strlen(file)-4, ".mp3") || + !strcmp(file+strlen(file)-4, ".MP3")) { + allowEditID3 = true; + ptr = str(LANG_EDIT_ID3); + lcd_getstringsize(ptr, &w,&h); + lcd_putsxy((LCD_WIDTH-w)/2, LCD_HEIGHT-h, ptr); + lcd_bitmap(bitmap_icons_7x8[Icon_DownArrow], + LCD_WIDTH/2 - 3, LCD_HEIGHT/2+10, 7, 8, true); + } + else + allowEditID3 = false; } lcd_update(); @@ -764,6 +778,14 @@ break; } + case BUTTON_DOWN: + case BUTTON_ON | BUTTON_DOWN: + if (allowEditID3) { + edit_id3_infile(); + exit = true; + } + break; + case BUTTON_ON | BUTTON_REL: used = true; break; @@ -789,12 +811,13 @@ static int onplay_screen(char* dir, char* file) { bool exit = false; + bool allowEditID3 = false; bool playing = mpeg_status() & MPEG_STATUS_PLAY; char buf[MAX_PATH]; struct entry* f = &dircache[dirstart + dircursor]; bool isdir = f->attr & ATTR_DIRECTORY; - struct menu_items items[3]; - int ids[3]; + struct menu_items items[4]; + int ids[4]; int lastitem=0; int m_handle; int selected; @@ -820,6 +843,17 @@ ids[lastitem]=3; lastitem++; } + + /* only editID3 on mp3 files */ + if (!strcmp(file+strlen(file)-4, ".mp3") || + !strcmp(file+strlen(file)-4, ".MP3")) { + allowEditID3 = true; + items[lastitem].desc=str(LANG_EDIT_ID3); + ids[lastitem]=4; + lastitem++; + } + else + allowEditID3 = false; m_handle=menu_init(items, lastitem); selected=menu_show(m_handle); @@ -877,6 +911,16 @@ } } break; + + case 4: + DEBUGF("onPlay - Edit ID3 Selected"); + if (allowEditID3) { + DEBUGF("allowEditID3 = true"); + edit_id3_infile(); + DEBUGF("edit_id3_infile is done"); + exit = true; + } + break; } } menu_exit(m_handle); @@ -1510,3 +1554,35 @@ return false; } +/* gets currently selected file for external (non-tree.c) code to access */ +/* appends "/" to directories, so can distinguish dirs from file */ +char * get_dirBrowse_currFile(void) +{ + static char fullName[MAX_PATH]; + char* file = dircache[dircursor+dirstart].name; + struct entry* f = &dircache[dirstart + dircursor]; + bool isdir = f->attr & ATTR_DIRECTORY; + + if ((currdir[0]=='/') && (currdir[1]==0)) + snprintf(fullName, sizeof fullName, "%s%s", currdir, file); + else + snprintf(fullName, sizeof fullName, "%s/%s", currdir, file); + + if (isdir) + strcat(fullName, "/"); + + return fullName; +} + +/* gets current directory for external (non-tree.c) code to access */ +/* directory returned without trailing "/" */ +char * get_dirBrowse_currDir(void) +{ + static char dir[MAX_PATH]; + + strncpy(dir, currdir, sizeof(dir)); + if (dir[strlen(dir)-1] != '/') + strcat(dir, "/"); + + return dir; +} Index: apps/tree.h =================================================================== RCS file: /cvsroot/rockbox/apps/tree.h,v retrieving revision 1.3 diff -u -b -r1.3 tree.h --- apps/tree.h 29 Jan 2003 13:20:21 -0000 1.3 +++ apps/tree.h 7 Mar 2003 04:31:00 -0000 @@ -25,5 +25,7 @@ void set_current_file(char *path); bool dirbrowse(char *root); bool create_playlist(void); +char * get_dirBrowse_currFile(void); +char * get_dirBrowse_currDir(void); #endif Index: apps/wps-display.c =================================================================== RCS file: /cvsroot/rockbox/apps/wps-display.c,v retrieving revision 1.63 diff -u -b -r1.63 wps-display.c --- apps/wps-display.c 5 Mar 2003 23:01:55 -0000 1.63 +++ apps/wps-display.c 7 Mar 2003 04:31:01 -0000 @@ -74,39 +74,6 @@ bool wps_time_countup = true; static bool wps_loaded = false; -static const char* const genres[] = { - "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", - "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", - "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", - "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", - "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", - "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", - "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", - "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", - "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", - "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", - "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", - "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", - "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", - - /* winamp extensions */ - "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", - "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", - "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", - "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", - "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", - "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", - "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", - "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall" -}; - -char* wps_get_genre(unsigned int genre) -{ - if (genre < sizeof(genres)/sizeof(char*)) - return (char*)genres[genre]; - return NULL; -} - /* Set format string to use for WPS, splitting it into lines */ static void wps_format(char* fmt) { @@ -306,10 +273,7 @@ break; case 'g': /* genre */ - if (id3->genre < sizeof(genres)/sizeof(char*)) - return (char*)genres[id3->genre]; - else - return NULL; + return id3_get_genre(id3->genre); break; case 'v': /* id3 version */ Index: apps/wps-display.h =================================================================== RCS file: /cvsroot/rockbox/apps/wps-display.h,v retrieving revision 1.10 diff -u -b -r1.10 wps-display.h --- apps/wps-display.h 11 Feb 2003 15:03:26 -0000 1.10 +++ apps/wps-display.h 7 Mar 2003 04:31:01 -0000 @@ -37,6 +37,5 @@ bool wps_display(struct mp3entry* id3); bool wps_load(char* file, bool display); void wps_reset(void); -char* wps_get_genre(unsigned int genre); #endif Index: apps/wps.c =================================================================== RCS file: /cvsroot/rockbox/apps/wps.c,v retrieving revision 1.181 diff -u -b -r1.181 wps.c --- apps/wps.c 25 Feb 2003 16:22:10 -0000 1.181 +++ apps/wps.c 7 Mar 2003 04:31:02 -0000 @@ -25,7 +25,9 @@ #include "font.h" #include "backlight.h" #include "button.h" +#include "id3.h" #include "kernel.h" +#include "keyboard.h" #include "tree.h" #include "debug.h" #include "sprintf.h" @@ -39,9 +41,11 @@ #include "ata.h" #include "screens.h" #include "playlist.h" +#include "timefuncs.h" #ifdef HAVE_LCD_BITMAP #include "icons.h" #include "peakmeter.h" +#include "widgets.h" #endif #include "lang.h" #define FF_REWIND_MAX_PERCENT 3 /* cap ff/rewind step size at max % of file */ @@ -148,155 +152,406 @@ sleep(HZ); } -bool browse_id3(void) +enum known_id3_tags { + ID3_TITLE = 0, + ID3_ARTIST, + ID3_ALBUM, + ID3_TRACKNUM, + ID3_GENRE, + ID3_YEAR, + ID3_LENGTH, + ID3_BITRATE, + ID3_FREQUENCY, + NUM_KNOWN_ID3_TAGS +}; + +#define MAX_TAG_STRLEN 256 + +#ifdef HAVE_LCD_BITMAP +#define MARGIN_X (global_settings.scrollbar && \ + NUM_KNOWN_ID3_TAGS > max_on_screen ? \ + SCROLLBAR_WIDTH : 0) + CURSOR_WIDTH +#define MARGIN_Y (global_settings.statusbar ? STATUSBAR_HEIGHT : 0) +#define LINE_X 0 +#define LINE_Y (global_settings.statusbar ? 1 : 0) +#define CURSOR_X (global_settings.scrollbar &&\ + NUM_KNOWN_ID3_TAGS > max_on_screen ? 1 : 0) +#define CURSOR_Y 0 +#define CURSOR_WIDTH 4 +#define SCROLLBAR_X 0 +#define SCROLLBAR_Y lcd_getymargin() +#define SCROLLBAR_WIDTH 6 +#else +#define LINE_X 2 +#define LINE_Y 0 +#define CURSOR_X 0 +#define CURSOR_Y 0 +#define MAX_ON_SCREEN 2 +#endif + +#ifdef HAVE_RECORDER_KEYPAD +#define TAG_PREV BUTTON_UP +#define TAG_NEXT BUTTON_DOWN +#define TAG_EDIT BUTTON_RIGHT +#define TAG_REVERT BUTTON_OFF +#define TAG_ACCEPT BUTTON_ON +#else +#define TAG_PREV BUTTON_LEFT +#define TAG_NEXT BUTTON_RIGHT +#define TAG_EDIT BUTTON_PLAY +#define TAG_REVERT BUTTON_STOP +#define TAG_ACCEPT BUTTON_ON +#endif + +/* bool edit determines whether ID3-tag editing is allowed, or just viewing */ +bool edit_id3_tags(struct mp3entry *id3, bool edit) { + unsigned short i; + int max_on_screen; + bool done = false; + unsigned short cursorScnIdx=0; + unsigned short scnStart=0; + unsigned short lastCursorScnIdx=0; + unsigned short cursorTagIdx=ID3_TITLE; + bool refreshAll = true; + char lineStr[MAX_PATH]; + char lclTitleBuf[MAX_TAG_STRLEN]; + char lclArtistBuf[MAX_TAG_STRLEN]; + char lclAlbumBuf[MAX_TAG_STRLEN]; + char tmpTagBuf[MAX_TAG_STRLEN]; int button; - int menu_pos = 0; - int menu_max = 8; - bool exit = false; - char scroll_text[MAX_PATH]; + unsigned short bufLen; - if (!(mpeg_status() & MPEG_STATUS_PLAY)) - return false; +#ifdef HAVE_LCD_BITMAP + int line_height; + int fw, fh; + lcd_setfont(FONT_UI); + lcd_getstringsize("A", &fw, &fh); + max_on_screen = (LCD_HEIGHT - MARGIN_Y) / fh; + line_height = fh; +#else + max_on_screen = MAX_ON_SCREEN; +#endif - while (!exit) + memset(lclTitleBuf, 0, MAX_TAG_STRLEN); + memset(lclArtistBuf, 0, MAX_TAG_STRLEN); + memset(lclAlbumBuf, 0, MAX_TAG_STRLEN); + + if (id3->title) strncpy(lclTitleBuf, id3->title, MAX_TAG_STRLEN); + if (id3->artist) strncpy(lclArtistBuf, id3->artist, MAX_TAG_STRLEN); + if (id3->album) strncpy(lclAlbumBuf, id3->album, MAX_TAG_STRLEN); + + while (!done) { + if (refreshAll) { +#ifdef HAVE_LCD_CHARCELLS + lcd_stop_scroll(); + lcd_double_height(false); +#endif lcd_clear_display(); +#ifdef HAVE_LCD_BITMAP + lcd_setmargins(MARGIN_X, MARGIN_Y); + lcd_setfont(FONT_UI); +#endif + } - switch (menu_pos) + scnStart = cursorTagIdx-cursorScnIdx; + + /* display ID3 tags */ + for (i=0; ititle ? id3->title : - (char*)str(LANG_ID3_NO_TITLE)); + unsigned short dispTagIdx = scnStart+i; + + if (dispTagIdx >= NUM_KNOWN_ID3_TAGS) break; - case 1: - lcd_puts(0, 0, str(LANG_ID3_ARTIST)); - lcd_puts_scroll(0, 1, - id3->artist ? id3->artist : + /* skip lines that do not need updating */ + if (!refreshAll && + !( (cursorScnIdx != lastCursorScnIdx) && + ( (i == cursorScnIdx) || + (i == lastCursorScnIdx) ) ) ) + continue; + + switch (dispTagIdx) + { + case ID3_TITLE: + snprintf(lineStr, sizeof(lineStr), "%s: %s", + (char*)str(LANG_ID3_TITLE), + lclTitleBuf ? lclTitleBuf : + (char*)str(LANG_ID3_NO_TITLE)); + break; + case ID3_ARTIST: + snprintf(lineStr, sizeof(lineStr), "%s: %s", + (char*)str(LANG_ID3_ARTIST), + lclArtistBuf ? lclArtistBuf : (char*)str(LANG_ID3_NO_ARTIST)); break; - case 2: - lcd_puts(0, 0, str(LANG_ID3_ALBUM)); - lcd_puts_scroll(0, 1, id3->album ? id3->album : + case ID3_ALBUM: + snprintf(lineStr, sizeof(lineStr), "%s: %s", + (char*)str(LANG_ID3_ALBUM), + lclAlbumBuf ? lclAlbumBuf : (char*)str(LANG_ID3_NO_ALBUM)); break; - case 3: - lcd_puts(0, 0, str(LANG_ID3_TRACKNUM)); - - if (id3->tracknum) { - snprintf(scroll_text,sizeof(scroll_text), "%d", + case ID3_TRACKNUM: + if (id3->tracknum) + snprintf(lineStr, sizeof(lineStr), "%s: %d", + (char*)str(LANG_ID3_TRACKNUM), id3->tracknum); - lcd_puts_scroll(0, 1, scroll_text); - } else - lcd_puts_scroll(0, 1, str(LANG_ID3_NO_TRACKNUM)); + snprintf(lineStr, sizeof(lineStr), "%s: %s", + (char*)str(LANG_ID3_TRACKNUM), + str(LANG_ID3_NO_TRACKNUM)); break; - case 4: - lcd_puts(0, 0, str(LANG_ID3_GENRE)); - lcd_puts_scroll(0, 1, - wps_get_genre(id3->genre) ? - wps_get_genre(id3->genre) : + case ID3_GENRE: + snprintf(lineStr, sizeof(lineStr), "%s: %s", + (char*)str(LANG_ID3_GENRE), + id3_get_genre(id3->genre) ? + id3_get_genre(id3->genre) : (char*)str(LANG_ID3_NO_INFO)); break; - case 5: - lcd_puts(0, 0, str(LANG_ID3_YEAR)); - if (id3->year) { - snprintf(scroll_text,sizeof(scroll_text), "%d", - id3->year); - lcd_puts_scroll(0, 1, scroll_text); - } + case ID3_YEAR: + if (id3->year) + snprintf(lineStr, sizeof(lineStr), "%s: %d", + (char*)str(LANG_ID3_YEAR), id3->year); else - lcd_puts_scroll(0, 1, str(LANG_ID3_NO_INFO)); + snprintf(lineStr, sizeof(lineStr), "%s: %s", + (char*)str(LANG_ID3_YEAR), + (char*)str(LANG_ID3_NO_INFO)); break; - case 6: - lcd_puts(0, 0, str(LANG_ID3_LENGHT)); - snprintf(scroll_text,sizeof(scroll_text), "%d:%02d", + case ID3_LENGTH: + snprintf(lineStr, sizeof(lineStr), "%s: %d:%02d", + (char*)str(LANG_ID3_LENGHT), id3->length / 60000, id3->length % 60000 / 1000 ); - lcd_puts(0, 1, scroll_text); - break; - - case 7: - lcd_puts(0, 0, str(LANG_ID3_PLAYLIST)); - snprintf(scroll_text,sizeof(scroll_text), "%d/%d", - id3->index + 1, playlist_amount()); - lcd_puts_scroll(0, 1, scroll_text); break; - - case 8: - lcd_puts(0, 0, str(LANG_ID3_BITRATE)); - snprintf(scroll_text,sizeof(scroll_text), "%d kbps", + case ID3_BITRATE: + snprintf(lineStr, sizeof(lineStr), "%s: %d kbps", + (char*)str(LANG_ID3_BITRATE), id3->bitrate); - lcd_puts(0, 1, scroll_text); break; - case 9: - lcd_puts(0, 0, str(LANG_ID3_FRECUENCY)); - snprintf(scroll_text,sizeof(scroll_text), "%d Hz", + case ID3_FREQUENCY: + snprintf(lineStr, sizeof(lineStr), "%s: %d Hz", + (char*)str(LANG_ID3_FRECUENCY), id3->frequency); - lcd_puts(0, 1, scroll_text); break; + } - case 10: - lcd_puts(0, 0, str(LANG_ID3_PATH)); - lcd_puts_scroll(0, 1, id3->path); - break; + if (i == cursorScnIdx) + { + lcd_puts_scroll(LINE_X, i, lineStr); + put_cursorxy(CURSOR_X, CURSOR_Y + i, true); } + else + lcd_puts(LINE_X, i, lineStr); + } + if (refreshAll) refreshAll = false; + +#ifdef HAVE_LCD_BITMAP + if (global_settings.scrollbar && + (NUM_KNOWN_ID3_TAGS > max_on_screen) ) + scrollbar(SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH - 1, + LCD_HEIGHT - SCROLLBAR_Y, NUM_KNOWN_ID3_TAGS, + scnStart, scnStart + max_on_screen, VERTICAL); + status_draw(); +#endif lcd_update(); button = button_get(true); - switch(button) - { - case BUTTON_LEFT: -#ifdef HAVE_RECORDER_KEYPAD - case BUTTON_UP: -#endif - if (menu_pos > 0) - menu_pos--; - else - menu_pos = menu_max; + switch (button) { + case TAG_PREV: + lcd_stop_scroll(); + put_cursorxy(CURSOR_X, CURSOR_Y + cursorScnIdx, false); + lastCursorScnIdx = cursorScnIdx; + + if (cursorScnIdx) { + cursorScnIdx--; + cursorTagIdx--; + } + else { + if (scnStart) { + cursorTagIdx--; + refreshAll=true; + } + else { + if (NUM_KNOWN_ID3_TAGS < max_on_screen) { + cursorScnIdx = NUM_KNOWN_ID3_TAGS-1; + cursorTagIdx = NUM_KNOWN_ID3_TAGS-1; + } + else { + cursorScnIdx = max_on_screen-1; + cursorTagIdx = NUM_KNOWN_ID3_TAGS-1; + refreshAll = true; + } + } + } break; - case BUTTON_RIGHT: -#ifdef HAVE_RECORDER_KEYPAD - case BUTTON_DOWN: -#endif - if (menu_pos < menu_max) - menu_pos++; - else - menu_pos = 0; + case TAG_NEXT: + lcd_stop_scroll(); + lastCursorScnIdx = cursorScnIdx; + put_cursorxy(CURSOR_X, CURSOR_Y + cursorScnIdx, false); + + if (cursorScnIdx + scnStart + 1 < NUM_KNOWN_ID3_TAGS) { + if (cursorScnIdx+1 < max_on_screen) { + cursorScnIdx++; + cursorTagIdx++; + } + else { + cursorTagIdx++; + refreshAll = true; + } + } + else { + if (NUM_KNOWN_ID3_TAGS < max_on_screen) { + cursorScnIdx = 0; + cursorTagIdx = 0; + } + else { + cursorScnIdx = 0; + cursorTagIdx = 0; + refreshAll = true; + } + } + put_cursorxy(CURSOR_X, CURSOR_Y + cursorScnIdx, true); break; - case BUTTON_REPEAT: + case TAG_EDIT: + if (!edit) break; + + switch (cursorTagIdx) { + case ID3_TITLE: + strncpy(tmpTagBuf, lclTitleBuf, MAX_TAG_STRLEN); + if (kbd_input(lclTitleBuf, MAX_TAG_STRLEN)) + strncpy(lclTitleBuf, tmpTagBuf, MAX_TAG_STRLEN); break; -#ifdef HAVE_PLAYER_KEYPAD - case BUTTON_STOP: -#else - case BUTTON_OFF: -#endif - case BUTTON_PLAY: - lcd_stop_scroll(); - /* eat release event */ - button_get(true); - exit = true; + case ID3_ARTIST: + strncpy(tmpTagBuf, lclArtistBuf, MAX_TAG_STRLEN); + if (kbd_input(lclArtistBuf, MAX_TAG_STRLEN)) + strncpy(lclArtistBuf, tmpTagBuf, MAX_TAG_STRLEN); + break; + + case ID3_ALBUM: + strncpy(tmpTagBuf, lclAlbumBuf, MAX_TAG_STRLEN); + if (kbd_input(lclAlbumBuf, MAX_TAG_STRLEN)) + strncpy(lclAlbumBuf, tmpTagBuf, MAX_TAG_STRLEN); + break; + + case ID3_TRACKNUM: + snprintf(tmpTagBuf, MAX_TAG_STRLEN, "ID3 %s", + str(LANG_ID3_TRACKNUM)); + set_int(tmpTagBuf, "", &(id3->tracknum), NULL, + 1, 0, 100); + break; + + case ID3_GENRE: + snprintf(tmpTagBuf, MAX_TAG_STRLEN, "ID3 %s", + str(LANG_ID3_GENRE)); + set_genre(tmpTagBuf, &(id3->genre)); + break; + + case ID3_YEAR: + { + struct tm *tm; + tm = get_time(); + + /* default year to current, if not specified */ + if ( !(id3->year) ) id3->year = tm->tm_year%100 + 2000; + + snprintf(tmpTagBuf, MAX_TAG_STRLEN, "ID3 %s", + str(LANG_ID3_YEAR)); + set_int(tmpTagBuf, "", &(id3->year), NULL, + 1, 1900, 2100); + } + break; + } + refreshAll = true; + break; - case SYS_USB_CONNECTED: - status_set_playmode(STATUS_STOP); - usb_screen(); + case TAG_ACCEPT: + if (edit) { + /* readjust id3 buffers for new string */ + bufLen = sizeof(id3->id3v2buf); + id3->title = id3->id3v2buf; + strncpy(id3->title, lclTitleBuf, bufLen); + bufLen -= strlen(id3->title); + id3->artist = id3->title + strlen(id3->title)+1; + strncpy(id3->artist, lclArtistBuf, bufLen); + bufLen -= strlen(id3->artist); + id3->album = id3->artist+strlen(id3->artist)+1; + strncpy(id3->album, lclAlbumBuf, bufLen); + id3->id3v2buf[sizeof(id3->id3v2buf)] = '\0'; + done = true; + } + break; + + case TAG_REVERT: return true; break; + } /* switch (button) */ + } /* while (!done) */ + + return false; +} + +/* this wrapper is for editing ID3 tags in existing files. */ +/* see edit_id3_tags and write_id3_tags for the details */ +void edit_id3_infile(void) +{ + char *file = get_dirBrowse_currFile(); + struct mp3entry id3; + int fd; + + /* only edit tags in mp3 files */ + if (mp3info(&id3, file)) { + lcd_clear_display(); + lcd_puts(0,0,"Not an MP3 file"); + lcd_update(); + sleep(2*HZ); + return; + } + + /* since we use mp3buf for resize_id3_tags, only allow if not playing */ + if (mpeg_status() & MPEG_STATUS_PLAY) { + lcd_clear_display(); + lcd_puts(0,0,"Can't edit"); + lcd_puts(0,1,"while playing"); + lcd_update(); + sleep(2*HZ); + lcd_stop_scroll(); + return; + } + + if (edit_id3_tags(&id3, true)) + return; + + fd = open(file, O_RDWR); + if (fd < 0) + return; + + write_id3_tags(fd, &id3); + + close(fd); +} + +bool browse_id3(void) +{ + /* if PLAYing, *show* currently-playing ID3 info */ + if ((mpeg_status() & MPEG_STATUS_PLAY)) { + edit_id3_tags(id3, false); } + /* if STOPped, *edit* currently-selected ID3 info */ + else { + edit_id3_infile(); } return false; } Index: apps/wps.h =================================================================== RCS file: /cvsroot/rockbox/apps/wps.h,v retrieving revision 1.18 diff -u -b -r1.18 wps.h --- apps/wps.h 11 Feb 2003 15:03:28 -0000 1.18 +++ apps/wps.h 7 Mar 2003 04:31:02 -0000 @@ -28,6 +28,8 @@ bool refresh_wps(bool refresh_scroll); void handle_usb(void); bool browse_id3(void); +bool edit_id3_tags(struct mp3entry *id3, bool edit); +void edit_id3_infile(void); #ifdef HAVE_RECORDER_KEYPAD bool f2_screen(void); Index: apps/lang/english.lang =================================================================== RCS file: /cvsroot/rockbox/apps/lang/english.lang,v retrieving revision 1.60 diff -u -b -r1.60 english.lang --- apps/lang/english.lang 27 Feb 2003 15:02:18 -0000 1.60 +++ apps/lang/english.lang 7 Mar 2003 04:31:02 -0000 @@ -1429,3 +1429,13 @@ desc: in sound_settings eng: "Karaoke" new: + +id: LANG_EDIT_ID3 +decs: menu entry to edit (and show) id3 tags +eng: "Edit/Show ID3 Tags" +new: + +id: LANG_EDIT_REC_FILENAME_FMT +decs: menu entry to edit custom filename recording format +eng: "Edit Filename Fmt" +new: Index: apps/recorder/recording.c =================================================================== RCS file: /cvsroot/rockbox/apps/recorder/recording.c,v retrieving revision 1.21 diff -u -b -r1.21 recording.c --- apps/recorder/recording.c 14 Feb 2003 09:44:33 -0000 1.21 +++ apps/recorder/recording.c 7 Mar 2003 04:31:03 -0000 @@ -28,11 +28,13 @@ #include "mas.h" #include "button.h" #include "kernel.h" +#include "keyboard.h" #include "settings.h" #include "lang.h" #include "font.h" #include "icons.h" #include "screens.h" +#include "panic.h" #include "peakmeter.h" #include "status.h" #include "menu.h" @@ -40,6 +42,9 @@ #include "timefuncs.h" #include "debug.h" #include "string.h" +#include "tree.h" +#include "wps.h" +#include "recording.h" extern char *num2max5(unsigned int bytes, char *max5); @@ -70,6 +75,8 @@ "16kHz" }; +struct recSet recSet; + static void set_gain(void) { if(global_settings.rec_source == SOURCE_MIC) @@ -125,22 +132,234 @@ } } -static char *create_filename(void) +static char *create_filename(char *fmtStr) { - static char fname[32]; + static char fname[MAX_PATH]; + char tmpBuf[128]; struct tm * tm; + char *namePtr, *fmtPtr; + char *tagPtr; + char *idPtr = NULL; + struct mp3entry *id3 = &(recSet.id3); + int i; + + char *src_str[] = + { + str(LANG_RECORDING_SRC_MIC), + str(LANG_RECORDING_SRC_LINE), + str(LANG_RECORDING_SRC_DIGITAL) + }; tm = get_time(); - /* Create a filename: RYYMMDDHHMMSS.mp3 */ - snprintf(fname, 32, "/R%02d%02d%02d%02d%02d%02d.mp3", - tm->tm_year%100, tm->tm_mon+1, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec); + memset(fname, 0, sizeof(fname)); + namePtr = fname; + fmtPtr = fmtStr; + + /* check for empty fmtStr */ + if (!fmtStr || !strlen(fmtStr)) { + strncpy(fname, "", sizeof(fname)); + return fname; + } + + /* prepend current directory info to "relative path" naming */ + if (fmtPtr[0] != '/') { + int copyLen; + tagPtr = get_dirBrowse_currDir(); + copyLen = ( strlen(tagPtr) < sizeof(fname)-strlen(".mp3") - 1 ) ? + strlen(tagPtr) : sizeof(fname)-strlen(".mp3") - 1; + strncpy(namePtr, tagPtr, copyLen); + namePtr += copyLen; + } + + /* parse custom filename format string */ + while (*fmtPtr && (namePtr < fname+sizeof(fname)-2) ) { + if (*fmtPtr != '%') { + *namePtr++ = *fmtPtr++; + continue; + } + + fmtPtr++; /* skip past '%' */ + tmpBuf[0] = 0; /* init to empty-str */ + switch (*fmtPtr++) { + case 'i': /* ID3 info */ + switch (*fmtPtr++) { + case 't': + tagPtr = (id3->title) ? id3->title : tmpBuf; + break; + case 'a': + tagPtr = (id3->artist) ? id3->artist : tmpBuf; + break; + case 'd': + tagPtr = (id3->album) ? id3->album : tmpBuf; + break; + case 'n': + if (id3->tracknum) + snprintf(tmpBuf, sizeof(tmpBuf), + "%02d", id3->tracknum); + tagPtr = tmpBuf; + break; + case 'y': + if (id3->year) + snprintf(tmpBuf, sizeof(tmpBuf), + "%04d", id3->year); + tagPtr = tmpBuf; + break; + case 'g': + tagPtr = id3_get_genre(id3->genre); + break; + default: + snprintf(tmpBuf, sizeof(tmpBuf), + "%%i%c", *(fmtPtr-1)); + tagPtr = tmpBuf; + break; + } + break; + + case 'r': /* recording-settings */ + switch (*fmtPtr++) { + case 'f': + tagPtr = freq_str[global_settings.rec_frequency]; + break; + case 'q': + snprintf(tmpBuf, sizeof(tmpBuf), + "%d", global_settings.rec_quality); + tagPtr = tmpBuf; + break; + case 's': + tagPtr = src_str[global_settings.rec_source]; + break; + case 'c': + tagPtr = global_settings.rec_channels ? + str(LANG_CHANNEL_MONO) : + str(LANG_CHANNEL_STEREO); + break; + default: + snprintf(tmpBuf, sizeof(tmpBuf), + "%%r%c", *(fmtPtr-1)); + tagPtr = tmpBuf; + break; + } + break; + + case 't': /* timestamp info */ + switch (*fmtPtr++) { + case 'y': + snprintf(tmpBuf, sizeof(tmpBuf), + "%02d", tm->tm_year%100); + tagPtr = tmpBuf; + break; + case 'm': + snprintf(tmpBuf, sizeof(tmpBuf), + "%02d", tm->tm_mon+1); + tagPtr = tmpBuf; + break; + case 'd': + snprintf(tmpBuf, sizeof(tmpBuf), + "%02d", tm->tm_mday); + tagPtr = tmpBuf; + break; + case 'h': + snprintf(tmpBuf, sizeof(tmpBuf), + "%02d", tm->tm_hour); + tagPtr = tmpBuf; + break; + case 'n': + snprintf(tmpBuf, sizeof(tmpBuf), + "%02d", tm->tm_min); + tagPtr = tmpBuf; + break; + case 's': + snprintf(tmpBuf, sizeof(tmpBuf), + "%02d", tm->tm_sec); + tagPtr = tmpBuf; + break; + default: + snprintf(tmpBuf, sizeof(tmpBuf), + "%%t%c", *(fmtPtr-1)); + tagPtr = tmpBuf; + break; + } + break; + + case 'm': /* misc info */ + switch (*fmtPtr++) { + case 'i': + idPtr = namePtr; + snprintf(tmpBuf, sizeof(tmpBuf), + "%02d", 0); + tagPtr = tmpBuf; + break; + default: + snprintf(tmpBuf, sizeof(tmpBuf), + "%%m%c", *(fmtPtr-1)); + tagPtr = tmpBuf; + break; + } + break; + + default: + snprintf(tmpBuf, sizeof(tmpBuf), "%%%c%c", + *(fmtPtr-1), *fmtPtr++); + tagPtr = tmpBuf; + break; + } + + /* copy tag info into filename */ + unsigned int nameBufLeft = sizeof(fname) - (namePtr-fname)-1; + int copyLen = ( strlen(tagPtr) < nameBufLeft ) ? + strlen(tagPtr) : nameBufLeft; + strncpy(namePtr, tagPtr, copyLen); + namePtr += copyLen; + } + + /* suffix .mp3 to path if not there already */ + tagPtr = fname+strlen(fname)-4; + if (strcasecmp(tagPtr, ".mp3") && + strcasecmp(tagPtr, ".mp2") && + strcasecmp(tagPtr, ".mpa") ) { + if (strlen(fname) < sizeof(fname)-5) + strncpy(fname+strlen(fname), ".mp3", 5); + else + strncpy(fname+sizeof(fname)-5, ".mp3", 5); + } + + /* if unique-file tag (%mi) specified, search for unique filename */ + for (i=0; i<100; i++) { + int fd; + + snprintf(idPtr, strlen(fname)-(idPtr-fname)+1, "%02d%s", i, idPtr+2); + fd = open(fname, O_RDONLY); + if (fd >= 0) + close(fd); + else + break; + } - DEBUGF("Filename: %s\n", fname); return fname; } +bool rec_edit_id3(void) +{ + bool ret; + ret = edit_id3_tags(&recSet.id3, true); + if (!ret) save_rec_settings(); + return ret; +} + +bool rec_edit_filenameFmt(void) +{ + char lclFmt[MAX_PATH]; + strncpy(lclFmt, recSet.filenameFmt, sizeof(lclFmt)); + + if (kbd_input(recSet.filenameFmt, sizeof(recSet.filenameFmt))) + strncpy(recSet.filenameFmt, lclFmt, sizeof(recSet.filenameFmt)); + + save_rec_settings(); + + return false; +} + bool recording_screen(void) { int button; @@ -202,12 +421,30 @@ /* Only act if the mpeg is stopped */ if(!mpeg_status()) { + char *filename = create_filename(recSet.filenameFmt); have_recorded = true; - mpeg_record(create_filename()); + + /* reserve space for prepended id3v2 tags */ + { + int fd = creat(filename, O_WRONLY); + if (fd < 0) { + lcd_clear_display(); + lcd_puts(0,0,"Can't create file"); + lcd_puts(0,1,"Recording ABORTED"); + lcd_update(); + sleep(2*HZ); + } + else { + resize_id3v2(fd, ID3_DEF_PADDING); + close(fd); + + mpeg_record(filename); status_set_playmode(STATUS_RECORD); update_countdown = 1; /* Update immediately */ last_seconds = 0; } + } + } break; case BUTTON_UP: @@ -324,7 +561,7 @@ break; case BUTTON_F3: - if (f3_rec_screen()) + if (rec_edit_id3()) return SYS_USB_CONNECTED; update_countdown = 1; /* Update immediately */ break; @@ -444,6 +681,13 @@ int w, h; char buf[32]; + char *src_str[] = + { + str(LANG_RECORDING_SRC_MIC), + str(LANG_RECORDING_SRC_LINE), + str(LANG_RECORDING_SRC_DIGITAL) + }; + lcd_setfont(FONT_SYSFIXED); lcd_getstringsize("A",&w,&h); @@ -453,11 +697,11 @@ lcd_clear_display(); /* Recording quality */ - lcd_putsxy(0, LCD_HEIGHT/2 - h*2, str(LANG_RECORDING_QUALITY)); + lcd_putsxy(0, LCD_HEIGHT/2 - 3*h/2, str(LANG_RECORDING_QUALITY)); snprintf(buf, 32, "%d", global_settings.rec_quality); - lcd_putsxy(0, LCD_HEIGHT/2-h, buf); + lcd_putsxy(0, LCD_HEIGHT/2-h/2, buf); lcd_bitmap(bitmap_icons_7x8[Icon_FastBackward], - LCD_WIDTH/2 - 16, LCD_HEIGHT/2 - 4, 7, 8, true); + LCD_WIDTH/2 - 16, LCD_HEIGHT/2-h/2, 7, 8, true); /* Frequency */ snprintf(buf, sizeof buf, "%s:", str(LANG_RECORDING_FREQUENCY)); @@ -481,14 +725,24 @@ } lcd_getstringsize(str(LANG_RECORDING_CHANNELS), &w, &h); - lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2 - h*2, + lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2 - 3*h/2, str(LANG_RECORDING_CHANNELS)); lcd_getstringsize(str(LANG_F2_MODE), &w, &h); - lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2 - h, str(LANG_F2_MODE)); + lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2 - h/2, str(LANG_F2_MODE)); lcd_getstringsize(ptr, &w, &h); - lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2, ptr); + lcd_putsxy(LCD_WIDTH - w, LCD_HEIGHT/2 + h/2, ptr); lcd_bitmap(bitmap_icons_7x8[Icon_FastForward], - LCD_WIDTH/2 + 8, LCD_HEIGHT/2 - 4, 7, 8, true); + LCD_WIDTH/2 + 8, LCD_HEIGHT/2 - h/2, 7, 8, true); + + /* Recording source */ + lcd_getstringsize(str(LANG_RECORDING_SOURCE), &w, &h); + lcd_putsxy((LCD_WIDTH - w)/2, 0, + str(LANG_RECORDING_SOURCE)); + ptr = src_str[global_settings.rec_source]; + lcd_getstringsize(ptr, &w, &h); + lcd_putsxy((LCD_WIDTH-w)/2, h, ptr); + lcd_bitmap(bitmap_icons_7x8[Icon_UpArrow], + LCD_WIDTH/2 - 3, h*2, 7, 8, true); lcd_update(); @@ -517,97 +771,21 @@ used = true; break; - case BUTTON_F2 | BUTTON_REL: - if ( used ) - exit = true; - used = true; - break; - - case BUTTON_F2 | BUTTON_REPEAT: - used = true; - break; - - case SYS_USB_CONNECTED: - usb_screen(); - return true; - } - } - - mpeg_set_recording_options(global_settings.rec_frequency, - global_settings.rec_quality, - global_settings.rec_source, - global_settings.rec_channels); - - set_gain(); - - settings_save(); - lcd_setfont(FONT_UI); - - return false; -} - -bool f3_rec_screen(void) -{ - bool exit = false; - bool used = false; - int w, h; - char *src_str[] = - { - str(LANG_RECORDING_SRC_MIC), - str(LANG_RECORDING_SRC_LINE), - str(LANG_RECORDING_SRC_DIGITAL) - }; - - lcd_setfont(FONT_SYSFIXED); - lcd_getstringsize("A",&w,&h); - - while (!exit) { - char* ptr=NULL; - - lcd_clear_display(); - - /* Recording source */ - lcd_putsxy(0, LCD_HEIGHT/2 - h*2, str(LANG_RECORDING_SOURCE)); - ptr = src_str[global_settings.rec_source]; - lcd_getstringsize(ptr, &w, &h); - lcd_putsxy(0, LCD_HEIGHT/2-h, ptr); - lcd_bitmap(bitmap_icons_7x8[Icon_FastBackward], - LCD_WIDTH/2 - 16, LCD_HEIGHT/2 - 4, 7, 8, true); - - lcd_update(); - - switch (button_get(true)) { - case BUTTON_LEFT: - case BUTTON_F3 | BUTTON_LEFT: + case BUTTON_UP: + case BUTTON_F2 | BUTTON_UP: global_settings.rec_source++; if(global_settings.rec_source > 2) global_settings.rec_source = 0; used = true; break; - case BUTTON_DOWN: - case BUTTON_F3 | BUTTON_DOWN: - global_settings.rec_frequency++; - if(global_settings.rec_frequency > 5) - global_settings.rec_frequency = 0; - used = true; - break; - - case BUTTON_RIGHT: - case BUTTON_F3 | BUTTON_RIGHT: - global_settings.rec_channels++; - if(global_settings.rec_channels > 1) - global_settings.rec_channels = 0; - used = true; - break; - - case BUTTON_F3 | BUTTON_REL: + case BUTTON_F2 | BUTTON_REL: if ( used ) exit = true; used = true; break; - case BUTTON_F3 | BUTTON_REPEAT: + case BUTTON_F2 | BUTTON_REPEAT: used = true; break; @@ -628,4 +806,9 @@ lcd_setfont(FONT_UI); return false; +} + +struct mp3entry * get_recSet_id3(void) +{ + return &(recSet.id3); } Index: apps/recorder/recording.h =================================================================== RCS file: /cvsroot/rockbox/apps/recorder/recording.h,v retrieving revision 1.1 diff -u -b -r1.1 recording.h --- apps/recorder/recording.h 10 Nov 2002 16:42:31 -0000 1.1 +++ apps/recorder/recording.h 7 Mar 2003 04:31:03 -0000 @@ -19,6 +19,17 @@ #ifndef RECORDING_H #define RECORDING_H +#include "id3.h" +#include "file.h" + +struct recSet { + struct mp3entry id3; + char filenameFmt[MAX_PATH]; +}; + bool recording_screen(void); +bool rec_edit_id3(void); +bool rec_edit_filenameFmt(void); +struct mp3entry *get_recSet_id3(void); /* for mpeg.c */ #endif Index: firmware/Makefile =================================================================== RCS file: /cvsroot/rockbox/firmware/Makefile,v retrieving revision 1.40 diff -u -b -r1.40 Makefile Index: firmware/id3.c =================================================================== RCS file: /cvsroot/rockbox/firmware/id3.c,v retrieving revision 1.65 diff -u -b -r1.65 id3.c --- firmware/id3.c 11 Feb 2003 15:00:56 -0000 1.65 +++ firmware/id3.c 7 Mar 2003 04:31:04 -0000 @@ -35,19 +35,33 @@ #include "file.h" #include "debug.h" #include "atoi.h" +#include "lcd.h" /* for copy byte-countdown during id3 resize */ +#include "mpeg.h" /* for use of mp3buf during id3 resize */ #include "id3.h" +extern unsigned char mp3buf[], mp3end[]; /* use mp3buf for resize_id3 */ + #define UNSYNC(b0,b1,b2,b3) (((b0 & 0x7F) << (3*7)) | \ ((b1 & 0x7F) << (2*7)) | \ ((b2 & 0x7F) << (1*7)) | \ ((b3 & 0x7F) << (0*7))) +#define SYNC(num, b0,b1,b2,b3) ( (b0=((num) & (0x7F<<(3*7)) ) >> (3*7) ) | \ + (b1=((num) & (0x7F<<(2*7)) ) >> (2*7) ) | \ + (b2=((num) & (0x7F<<(1*7)) ) >> (1*7) ) | \ + (b3=((num) & (0x7F<<(0*7)) ) >> (0*7) ) ) + #define BYTES2INT(b0,b1,b2,b3) (((b0 & 0xFF) << (3*8)) | \ ((b1 & 0xFF) << (2*8)) | \ ((b2 & 0xFF) << (1*8)) | \ ((b3 & 0xFF) << (0*8))) +#define INT2BYTES(num,b0,b1,b2,b3) ( (b0=((num) & (0xFF<<(3*8)) ) >> (3*7) ) | \ + (b1=((num) & (0xFF<<(2*8)) ) >> (2*7) ) | \ + (b2=((num) & (0xFF<<(1*8)) ) >> (1*7) ) | \ + (b3=((num) & (0xFF<<(0*8)) ) >> (0*7) ) ) + /* Table of bitrates for MP3 files, all values in kilo. * Indexed by version, layer and value of bit 15-12 in header. */ @@ -80,6 +94,42 @@ {22050, 24000, 16000, 0}, /* MPEG version 2 */ }; +static const char* const genres[] = { + "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", + "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", + "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska", + "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", + "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", + "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", + "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", + "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", + "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", + "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", + "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", + "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", + "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", + + /* winamp extensions */ + "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", + "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", + "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", + "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", + "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", + "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", + "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", + "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall" +}; + +char* id3_get_genre(unsigned int genre) +{ + if (genre < sizeof(genres)/sizeof(char*)) + return (char*)genres[genre]; + return NULL; +} + +static bool write_id3v1_tags(int fd, struct mp3entry *id3); +static bool write_id3v2_tags(int fd, struct mp3entry *id3); + /* Checks to see if the passed in string is a 16-bit wide Unicode v2 string. If it is, we attempt to convert it to a 8-bit ASCII string (for valid 8-bit ASCII characters). If it's not unicode, we leave @@ -741,6 +791,8 @@ strncpy(entry->path, filename, sizeof(entry->path)); entry->title = NULL; + entry->artist= NULL; + entry->album = NULL; entry->filesize = getfilesize(fd); entry->id3v2len = getid3v2len(fd); entry->tracknum = 0; @@ -761,6 +813,306 @@ if(!entry->length || (entry->filesize < 8 )) /* no song length or less than 8 bytes is hereby considered to be an invalid mp3 and won't be played by us! */ + return true; + + return false; +} + +bool write_id3v2_header(int fd, int size) +{ + char hdr[10]; + + hdr[0]='I'; + hdr[1]='D'; + hdr[2]='3'; + hdr[3]=3; + hdr[4]=0; + hdr[5]=0; + SYNC(size, hdr[6], hdr[7], hdr[8], hdr[9]); + + lseek(fd, 0, SEEK_SET); + write(fd, hdr, 10); + + return false; +} + +/* resizes prepended id3v2 tags while maintaining (hopefully) data integrity */ +/* - if size larger than existing tag, 0-padding appended to tag */ +/* - if size smaller than existing tag, tags truncated to fit within size */ +/* - input "size" is id3v2 tag size, INCLUDING 10-byte header */ +/* - uses mp3buf for copying, so *requires* not to be playing anything */ +/* - may not be a byte-perfect shift. Especially last byte of id3v2 tag */ +/* and last byte of mp3 data */ +/* - return filesize (<0 is errCode) */ +int resize_id3v2(int fd, int size) +{ + int tagLen, dataLen; /* length of id3v2 tag & mp3 data */ + int fileSize; /* total filesize */ + int srcStart, dstStart; /* position to start copy from/to */ + int copyLen; /* # of bytes left to copy */ + int copyBufLen; /* size available in copyBuf */ + unsigned char *copyBuf; /* buffer to use for data shuffling */ + bool dispCntdown =false; /* display copy countdown? */ + char dispStr[32]; /* string for status display */ + + tagLen = getid3v2len(fd); + fileSize = getfilesize(fd); + dataLen = fileSize - tagLen; + + /* do we even need to resize? */ + if (size==tagLen) return size; + + /* can't have a tag shorter than header len, unless no tag at all */ + if ( (size < 10) && (size > 0) ) + return -1; + + /* archos must be not playing, or else is error! */ + if ( (mpeg_status() & MPEG_STATUS_PLAY) || + (mpeg_status() & MPEG_STATUS_RECORD) ) + return -2; + + mpeg_stop(); + + /* write in chunks of 512 for speed */ + copyBuf = mp3buf; + copyBufLen = (mp3end - copyBuf); copyBufLen -= copyBufLen%512; + + + if (dataLen > copyBufLen) { + dispCntdown = true; + lcd_clear_display(); + lcd_puts(0,0,"Resizing for id3 tag ..."); + snprintf(dispStr, sizeof(dispStr), "%d remaining", dataLen); + lcd_puts(0,1,dispStr); + lcd_update(); + } + + /* shift existing file data to account for new tagLen */ + /*----------------------------------------------------*/ + if (size > tagLen) { + /* write end-padding to expand file to desired size */ + srcStart = lseek(fd, 0, SEEK_END); + if (srcStart%2 !=0) srcStart--; /* must write from even offset */ + copyLen = size-tagLen; + memset(copyBuf, 0, (copyBufLen < copyLen) ? copyBufLen : copyLen); + while (copyLen > 0) { + copyLen -= write(fd, copyBuf, + (copyBufLen < copyLen) ? copyBufLen : copyLen); + } + + /* shift mp3 data to add id3v2 padding up to size */ + dstStart = getfilesize(fd); + copyLen = dataLen; + while (copyLen > 0) { + if (dispCntdown) { + snprintf(dispStr, sizeof(dispStr), "%d remaining", copyLen); + lcd_puts(0,1,dispStr); + lcd_update(); + } + + int bufLen = (copyBufLen < copyLen) ? copyBufLen : copyLen; + if ( (dstStart-bufLen)%2 != 0) bufLen--; /* wr strt on even off*/ + if ( bufLen==0 ) bufLen=2; + lseek(fd, srcStart-bufLen, SEEK_SET); + srcStart -= read(fd, copyBuf, bufLen); + lseek(fd, dstStart-bufLen, SEEK_SET); + dstStart -= write(fd, copyBuf, bufLen); + copyLen-=bufLen; + } + + /* write zero over newly-expanded id3v2 length */ + if (tagLen%2 !=0) tagLen++; /* must start write on even offset */ + copyLen = size-tagLen; + memset(copyBuf, 0, (copyBufLen < copyLen) ? copyBufLen : copyLen ); + lseek(fd, tagLen, SEEK_SET); + while (copyLen > 0) + copyLen -= write(fd, copyBuf, + (copyBufLen < copyLen) ? copyBufLen : copyLen); + } + else { /* (tagLen > size) */ + + /* check where to truncate frames */ + dstStart = 10; + while (1) { + lseek(fd, dstStart+4, SEEK_SET); + read(fd, dispStr, 4); + copyLen = UNSYNC(dispStr[0], dispStr[1], dispStr[2], dispStr[3])+10; + if ( (dstStart+copyLen) < size) + dstStart += copyLen; + else + break; + } + + /* add zero-padding to id3v2 tag */ + if (dstStart%2 != 0) dstStart++; /* must start write on even offset */ + copyLen = size-dstStart; + if (copyLen > 0) { + memset(copyBuf, 0, (copyBufLen < copyLen) ? copyBufLen : copyLen); + lseek(fd, dstStart, SEEK_SET); + while (copyLen > 0) + copyLen -= write(fd, copyBuf, + (copyBufLen < copyLen) ? copyBufLen : copyLen); + } + + /* shift mp3 data to reduce id3v2 to size */ + dstStart = size; + srcStart = tagLen; + copyLen = dataLen; + if (dstStart%2 != 0) { /* must start write on even offset */ + dstStart++; + srcStart++; + copyLen--; + } + while (copyLen > 0) { + if (dispCntdown) { + snprintf(dispStr, sizeof(dispStr), "%d remaining", copyLen); + lcd_puts(0,1,dispStr); + lcd_update(); + } + + int bufLen = (copyBufLen < copyLen) ? copyBufLen : copyLen; + lseek(fd, srcStart, SEEK_SET); + srcStart += read(fd, copyBuf, bufLen); + lseek(fd, dstStart, SEEK_SET); + dstStart += write(fd, copyBuf, bufLen); + copyLen-=bufLen; + } + + /* truncate file to new length */ + ftruncate(fd, size+dataLen); + } + + if (size > 0) + write_id3v2_header(fd, size); + + return size; +} + +/* writes a text-type id3v2 tag at CURRENT file location */ +static int write_id3v2_textFm(int fd, char *frmID, char *str, int *tagLen) +{ + int tagUsed; + char fmHdr[11]; + int written = 0; + + tagUsed = lseek(fd, 0, SEEK_CUR); + + if ( (int)(strlen(str) + 11 + tagUsed) > *tagLen ) + *tagLen = resize_id3v2(fd, *tagLen + strlen(str) + ID3_DEF_PADDING); + + lseek(fd, tagUsed, SEEK_SET); + strncpy(fmHdr, frmID, 4); + INT2BYTES(strlen(str) + 1, fmHdr[4], fmHdr[5], fmHdr[6], fmHdr[7]); + fmHdr[8] = fmHdr[9] = 0; + fmHdr[10] = 0; + written += write(fd, fmHdr, 11); + written += write(fd, str, strlen(str)); + + return written; +} + +/* known issue: write_id3v2_tags will overwrite ALL existing id3 tag info */ +/* - should enhance at some point so we keep all unsupported tags and */ +/* only overwrite supported tags. */ +static bool write_id3v2_tags(int fd, struct mp3entry *id3) +{ + int tagLen, tagUsed; + int copyStart, copyLen; + char copyBuf[500]; + int copyBufLen; + + tagLen = getid3v2len(fd); + copyBufLen = sizeof(copyBuf); + + /* clear out all existing id3v2 info */ + copyStart = 10; + copyLen = tagLen-10; + + memset(copyBuf, 0, copyBufLen); + lseek(fd, copyStart, SEEK_SET); + while (copyLen > 0) + copyLen -= write(fd, copyBuf, + (copyBufLen < copyLen) ? copyBufLen : copyLen); + + tagUsed = tagLen; + + /* write TITLE/ARTIST/ALBUM frames */ + lseek(fd, 10, SEEK_SET); + write_id3v2_textFm(fd, "TIT2", id3->title, &tagLen); + write_id3v2_textFm(fd, "TPE1", id3->artist, &tagLen); + write_id3v2_textFm(fd, "TALB", id3->album, &tagLen); + + /* write YEAR frame */ + snprintf(copyBuf, copyBufLen, "%4d", id3->year); + write_id3v2_textFm(fd, "TYER", copyBuf, &tagLen); + + /* write GENRE frame */ + snprintf(copyBuf, copyBufLen, "(%d)%s", + id3->genre, id3_get_genre(id3->genre)); + write_id3v2_textFm(fd, "TCON", copyBuf, &tagLen); + + /* write TRACKNUM frame */ + snprintf(copyBuf, copyBufLen, "%d", id3->tracknum); + write_id3v2_textFm(fd, "TRCK", copyBuf, &tagLen); + + return false; +} + +static bool write_id3v1_tags(int fd, struct mp3entry *id3) +{ + unsigned char buffer[128]; + int i, cnt=0; + + if (-1 == lseek(fd, -128, SEEK_END)) + return false; + + if (read(fd, buffer, 3) != 3) + return false; + + if (strncmp(buffer, "TAG", 3)) { + lseek(fd, 0, SEEK_END); + write(fd, "TAG", 3); + } + + for (i=0; i<7; i++) { + memset(buffer, 0, sizeof(buffer)); + + switch (i) { + case 0: + cnt=30; + snprintf(buffer, cnt+1, "%s", id3->title); break; + case 1: + cnt=30; + snprintf(buffer, cnt+1, "%s", id3->artist); break; + case 2: + cnt=30; + snprintf(buffer, cnt+1, "%s", id3->album); break; + case 3: + cnt=4; + snprintf(buffer, cnt+1, "%d", id3->year); break; + case 4: + cnt=28; + break; + case 5: + cnt=2; + snprintf(buffer, cnt+1, "%c%c", 0, (char)id3->tracknum); break; + case 6: + cnt=1; + snprintf(buffer, cnt+1, "%c", id3->genre); break; + } + + write(fd, buffer, cnt); + } + + return true; +} + +bool write_id3_tags(int fd, struct mp3entry *id3) +{ + if (write_id3v2_tags(fd, id3)) + return true; + + if (write_id3v1_tags(fd, id3)) return true; return false; Index: firmware/mpeg.c =================================================================== RCS file: /cvsroot/rockbox/firmware/mpeg.c,v retrieving revision 1.207 diff -u -b -r1.207 mpeg.c --- firmware/mpeg.c 3 Mar 2003 14:05:47 -0000 1.207 +++ firmware/mpeg.c 7 Mar 2003 04:31:06 -0000 @@ -38,6 +38,10 @@ extern void bitswap(unsigned char *data, int length); +#ifdef HAVE_RECORDING +extern struct mp3entry* get_recSet_id3(void); /* ID3 tag for recorded file */ +#endif + #ifdef HAVE_MAS3587F static void init_recording(void); static void init_playback(void); @@ -1828,16 +1832,11 @@ { case MPEG_RECORD: DEBUGF("Recording...\n"); + reset_mp3_buffer(); start_recording(); demand_irq_enable(true); - mpeg_file = creat(recording_filename, O_WRONLY); - if(mpeg_file < 0) - panicf("recfile: %d", mpeg_file); - - close(mpeg_file); - mpeg_file = -1; break; case MPEG_STOP: @@ -1852,11 +1851,13 @@ case MPEG_STOP_DONE: DEBUGF("MPEG_STOP_DONE\n"); - - if(mpeg_file >= 0) + mpeg_file = open(recording_filename, + O_RDWR); + if(mpeg_file >= 0) { + write_id3_tags(mpeg_file, get_recSet_id3()); close(mpeg_file); + } mpeg_file = -1; - #ifdef DEBUG1 { int i; Index: firmware/export/id3.h =================================================================== RCS file: /cvsroot/rockbox/firmware/export/id3.h,v retrieving revision 1.1 diff -u -b -r1.1 id3.h --- firmware/export/id3.h 7 Feb 2003 09:41:57 -0000 1.1 +++ firmware/export/id3.h 7 Mar 2003 04:31:06 -0000 @@ -21,6 +21,8 @@ #include "file.h" +#define ID3_DEF_PADDING 500 /* default id3v2 padding (bytes) */ + struct mp3entry { char path[MAX_PATH]; char *title; @@ -72,5 +74,8 @@ }; bool mp3info(struct mp3entry *entry, char *filename); +int resize_id3v2(int fd, int size); +bool write_id3_tags(int fd, struct mp3entry *entry); +char* id3_get_genre(unsigned int genre); #endif