Index: rockbox-devel/apps/lang/english.lang =================================================================== --- rockbox-devel.orig/apps/lang/english.lang +++ rockbox-devel/apps/lang/english.lang @@ -5388,7 +5388,7 @@ *: "[Title]" - *: "" + *: "Title" @@ -5402,7 +5402,7 @@ *: "[Artist]" - *: "" + *: "Artist" @@ -5416,7 +5416,7 @@ *: "[Album]" - *: "" + *: "Album" @@ -5430,7 +5430,7 @@ *: "[Tracknum]" - *: "" + *: "Track number" @@ -5444,7 +5444,7 @@ *: "[Genre]" - *: "" + *: "Genre" @@ -5458,7 +5458,7 @@ *: "[Year]" - *: "" + *: "Year" @@ -5472,7 +5472,7 @@ *: "[Length]" - *: "" + *: "Length" @@ -5486,7 +5486,7 @@ *: "[Playlist]" - *: "" + *: "Playlist" @@ -5500,7 +5500,7 @@ *: "[Bitrate]" - *: "" + *: "Bit rate" @@ -5528,7 +5528,7 @@ *: " (VBR)" - *: "" + *: "V B R" @@ -5542,7 +5542,7 @@ *: "[Frequency]" - *: "" + *: "Frequency" @@ -5556,7 +5556,7 @@ *: "[Track Gain]" - *: "" + *: "Track gain" @@ -5570,7 +5570,7 @@ *: "[Album Gain]" - *: "" + *: "Album gain" @@ -5584,7 +5584,7 @@ *: "[Path]" - *: "" + *: "Path" @@ -5598,7 +5598,7 @@ *: "" - *: "" + *: "No info" @@ -10434,3 +10434,17 @@ *: "If bookmark file already exists" + + id: VOICE_OF + desc: in id3 info, as in "two of ten" "2/10" + user: + + *: "" + + + *: "" + + + *: "of" + + Index: rockbox-devel/apps/screens.c =================================================================== --- rockbox-devel.orig/apps/screens.c +++ rockbox-devel/apps/screens.c @@ -78,6 +78,8 @@ #ifdef HAVE_LCD_COLOR #include "backdrop.h" #endif +#include "ctype.h" +#include "atoi.h" #ifdef HAVE_LCD_BITMAP #define SCROLLBAR_WIDTH 6 @@ -1126,7 +1128,88 @@ bool shutdown_screen(void) #define ID3_ITEMS 11 #endif -char * id3_get_info(int selected_item, void* data, char *buffer) +/* Spell out a buffer, but when successive digits are encountered, say + the whole number. Useful for some ID3 tags that usually contain a + number but are in fact free-form. */ +void say_number_and_spell(char *buf, bool year_style) +{ + char *ptr = buf; + while(*ptr) { + if(isdigit(*ptr)) { + /* parse the number */ + int n = atoi(ptr); + /* skip over digits to rest of string */ + while(isdigit(*++ptr)); + /* say the number */ + if(year_style) + talk_id(TALK_ID(n, UNIT_DATEYEAR), true); + else talk_number(n, true); + }else{ + /* Spell a sequence of non-digits */ + char tmp, *start = ptr; + while(*++ptr && !isdigit(*ptr)); + /* temporarily truncate the string here */ + tmp = *ptr; + *ptr = '\0'; + talk_spell(start, true); + *ptr = tmp; /* restore string */ + } + } +} + +/* Say a replaygain ID3 value from its text form */ +void say_gain(char *buf) +{ + /* Expected form is "-5.74 dB". We'll try to parse out the number + until the dot, say it (forcing the + sign), then say dot and + spell the following numbers, and then say the decibel unit. */ + char *ptr = buf; + if(*ptr == '-' || *ptr == '+') + /* skip sign */ + ++ptr; + /* See if we can parse out a number. */ + if(isdigit(*ptr)) { + int n; + char tmp; + /* skip successive digits */ + while(isdigit(*++ptr)); + /* temporarily truncate the string here */ + tmp = *ptr; + *ptr = '\0'; + /* parse out the number we just skipped */ + n = atoi(buf); + *ptr = tmp; /* restore the string */ + talk_id(TALK_ID(n, UNIT_SIGNED), true); /* say the number with sign */ + if(*ptr == '.') { + /* found the dot, say it */ + ++ptr; + talk_id(LANG_POINT, true); + } + buf = ptr; + if(strlen(buf) >2 && !strcmp(buf+strlen(buf)-2, "dB")) { + /* String does end with "dB" */ + char tmp; + /* point to that "dB" */ + ptr = buf+strlen(buf)-2; + /* backup any spaces */ + while(ptr >buf && ptr[-1] == ' ') + --ptr; + /* temporarily truncate string here */ + tmp = *ptr; + *ptr = '\0'; + /* Spell everything between the number or dot until the + "dB". Normaly that's the numbers of the fraction part. */ + talk_spell(buf, true); + *ptr = tmp; /* restore string */ + talk_id(VOICE_DB, true); /* say the dB unit */ + }else /* doesn't end with dB, just spell everything after the + number of dot. */ + talk_spell(buf, true); + }else /* we didn't find a number, just spell everything */ + talk_spell(buf, true); +} + +char * id3_get_or_speak_info(int selected_item, void* data, char *buffer, bool say_it) { struct mp3entry* id3 =(struct mp3entry*)data; int info_no=selected_item/2; @@ -1150,6 +1233,8 @@ char * id3_get_info(int selected_item, v #endif LANG_ID3_PATH, }; + if(say_it) + talk_id(headers[info_no], false); return( str(headers[info_no])); } else @@ -1160,77 +1245,131 @@ char * id3_get_info(int selected_item, v { case 0:/*LANG_ID3_TITLE*/ info=id3->title; + if(say_it && info) + talk_spell(info, true); break; case 1:/*LANG_ID3_ARTIST*/ info=id3->artist; + if(say_it && info) + talk_spell(info, true); break; case 2:/*LANG_ID3_ALBUM*/ info=id3->album; + if(say_it && info) + talk_spell(info, true); break; case 3:/*LANG_ID3_TRACKNUM*/ if (id3->track_string) + { info = id3->track_string; + if(say_it) + say_number_and_spell(info, false); + } else if (id3->tracknum) { snprintf(buffer, MAX_PATH, "%d", id3->tracknum); info = buffer; + if(say_it) + talk_number(id3->tracknum, true); } break; case 4:/*LANG_ID3_GENRE*/ info = id3_get_genre(id3); + if(say_it && info) + talk_spell(info, true); break; case 5:/*LANG_ID3_YEAR*/ if (id3->year_string) + { info = id3->year_string; + if(say_it) + say_number_and_spell(info, true); + } else if (id3->year) { snprintf(buffer, MAX_PATH, "%d", id3->year); info = buffer; + if(say_it) + talk_id(TALK_ID(id3->year, UNIT_DATEYEAR), false); } break; case 6:/*LANG_ID3_LENGTH*/ gui_wps_format_time(buffer, MAX_PATH, id3->length); info=buffer; + if(say_it) + talk_id(TALK_ID(id3->length /1000, UNIT_TIME), true); break; case 7:/*LANG_ID3_PLAYLIST*/ snprintf(buffer, MAX_PATH, "%d/%d", playlist_get_display_index(), playlist_amount()); info=buffer; + if(say_it) + { + talk_number(playlist_get_display_index(), true); + talk_id(VOICE_OF, true); + talk_number(playlist_amount(), true); + } break; case 8:/*LANG_ID3_BITRATE*/ snprintf(buffer, MAX_PATH, "%d kbps%s", id3->bitrate, id3->vbr ? str(LANG_ID3_VBR) : (const unsigned char*) ""); info=buffer; + if(say_it) + { + talk_value(id3->bitrate, UNIT_KBIT, true); + if(id3->vbr) + talk_id(LANG_ID3_VBR, true); + } break; case 9:/*LANG_ID3_FRECUENCY*/ snprintf(buffer, MAX_PATH, "%ld Hz", id3->frequency); info=buffer; + if(say_it) + talk_value(id3->frequency, UNIT_HERTZ, true); break; #if CONFIG_CODEC == SWCODEC case 10:/*LANG_ID3_TRACK_GAIN*/ info=id3->track_gain_string; + if(say_it && info) + say_gain(info); break; case 11:/*LANG_ID3_ALBUM_GAIN*/ info=id3->album_gain_string; + if(say_it && info) + say_gain(info); break; case 12:/*LANG_ID3_PATH*/ #else case 10:/*LANG_ID3_PATH*/ #endif info=id3->path; + if(say_it && info) + talk_fullpath(info, true); break; } if(info==NULL) + { + if(say_it) + talk_id(LANG_ID3_NO_INFO, true); return(str(LANG_ID3_NO_INFO)); + } return(info); } } +/* gui_synclist callback */ +char * id3_get_info(int selected_item, void* data, char *buffer) +{ + return id3_get_or_speak_info(selected_item, data, buffer, false); +} + bool browse_id3(void) { struct gui_synclist id3_lists; struct mp3entry* id3 = audio_current_track(); int key; + bool say_fresh_line = true; + unsigned returned_button; gui_synclist_init(&id3_lists, &id3_get_info, id3, true, 2); gui_synclist_set_nb_items(&id3_lists, ID3_ITEMS*2); @@ -1238,14 +1377,29 @@ bool browse_id3(void) gui_syncstatusbar_draw(&statusbars, true); action_signalscreenchange(); while (true) { + if (global_settings.talk_menu && say_fresh_line) + { + say_fresh_line = false; + char buffer[MAX_PATH]; + int item = gui_synclist_get_sel_pos(&id3_lists); + /* say field name */ + id3_get_or_speak_info(item, id3, buffer, true); + /* and field value */ + id3_get_or_speak_info(item+1, id3, buffer, true); + } gui_syncstatusbar_draw(&statusbars, false); key = get_action(CONTEXT_LIST,HZ/2); - if(key!=ACTION_NONE && key!=ACTION_UNKNOWN - && !gui_synclist_do_button(&id3_lists, key,LIST_WRAP_UNLESS_HELD)) + if(key==ACTION_NONE || key==ACTION_UNKNOWN) + continue; + if(!(returned_button = gui_synclist_do_button(&id3_lists, key, + LIST_WRAP_UNLESS_HELD))) { action_signalscreenchange(); return(default_event_handler(key) == SYS_USB_CONNECTED); } + if(returned_button == ACTION_STD_NEXT + || returned_button == ACTION_STD_PREV) + say_fresh_line = true; } } Index: rockbox-devel/apps/talk.c =================================================================== --- rockbox-devel.orig/apps/talk.c +++ rockbox-devel/apps/talk.c @@ -912,6 +912,45 @@ int talk_number(long n, bool enqueue) return 0; } +/* Say year like "nineteen ninety nine" instead of "one thousand 9 + hundred ninety nine". */ +static int talk_year(long year, bool enqueue) +{ + int rem; + if(year < 1100 || year >=2000) + /* just say it as a regular number */ + return talk_number(year, enqueue); + /* Say century */ + talk_number(year/100, enqueue); + rem = year%100; + if(rem == 0) + /* as in 1900 */ + return talk_id(VOICE_HUNDRED, true); + if(rem <10) + /* as in 1905 */ + talk_id(VOICE_ZERO, true); + /* sub-century year */ + return talk_number(rem, true); +} + +/* Say time duration/interval. Input is time in seconds, + say hours,minutes,seconds. */ +int talk_time(long secs, bool enqueue) +{ + int hours, mins; + if (!enqueue) + shutup(); + if((hours = secs/3600)) { + secs %= 3600; + talk_value(hours, UNIT_HOUR, true); + } + if((mins = secs/60)) { + secs %= 60; + talk_value(mins, UNIT_MIN, true); + } + return talk_value(secs, UNIT_SEC, true); +} + /* singular/plural aware saying of a value */ int talk_value(long n, int unit, bool enqueue) { @@ -952,6 +991,13 @@ int talk_value(long n, int unit, bool en return -1; #endif + /* special pronounciation for year number */ + if (unit == UNIT_DATEYEAR) + return talk_year(n, enqueue); + /* special case for time duration */ + if (unit == UNIT_TIME) + return talk_time(n, enqueue); + if (unit < 0 || unit >= UNIT_LAST) unit_id = -1; else Index: rockbox-devel/apps/talk.h =================================================================== --- rockbox-devel.orig/apps/talk.h +++ rockbox-devel/apps/talk.h @@ -30,6 +30,7 @@ enum { /* See array "unit_voiced" in talk.c function "talk_value" */ UNIT_INT = 1, /* plain number */ UNIT_SIGNED, /* number with mandatory sign (even if positive) */ + UNIT_DATEYEAR,/* for 1999 say nineteen ninety nine */ UNIT_MS, /* milliseconds */ UNIT_SEC, /* seconds */ UNIT_MIN, /* minutes */ @@ -43,6 +44,7 @@ enum { UNIT_HERTZ, /* hertz */ UNIT_MB, /* Megabytes */ UNIT_KBIT, /* kilobits per sec */ + UNIT_TIME, /* time duration/interval in seconds, says hours,mins,secs */ UNIT_LAST /* END MARKER */ };