Index: trunk/apps/screens.c =================================================================== --- trunk.orig/apps/screens.c +++ trunk/apps/screens.c @@ -75,6 +75,8 @@ #if (LCD_DEPTH > 1) || (defined(HAVE_LCD_REMOTE) && (LCD_REMOTE_DEPTH > 1)) #include "backdrop.h" #endif +#include "ctype.h" +#include "atoi.h" #ifdef HAVE_LCD_BITMAP #define SCROLLBAR_WIDTH 6 @@ -1169,12 +1171,96 @@ static const int id3_headers[]= LANG_ID3_PATH, }; -static 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. */ +static 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 */ +static 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); +} + +static 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; if(!(selected_item%2)) {/* header */ + if(say_it) + talk_id(id3_headers[info_no], false); return( str(id3_headers[info_no])); } else @@ -1185,88 +1271,160 @@ static char * id3_get_info(int selected_ { 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_ALBUMARTIST*/ info=id3->albumartist; + if(say_it && info) + talk_spell(info, true); break; case 4:/*LANG_ID3_GROUPING*/ info=id3->grouping; + if(say_it && info) + talk_spell(info, true); break; case 5:/*LANG_ID3_DISCNUM*/ if (id3->disc_string) + { info = id3->disc_string; + if(say_it) + say_number_and_spell(info, false); + } else if (id3->discnum) - { + { snprintf(buffer, MAX_PATH, "%d", id3->discnum); info = buffer; + if(say_it) + talk_number(id3->discnum, true); } break; case 6:/*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 7:/*LANG_ID3_COMMENT*/ info=id3->comment; + if(say_it && info) + talk_spell(info, true); break; case 8:/*LANG_ID3_GENRE*/ info = id3->genre_string; + if(say_it && info) + talk_spell(info, true); break; case 9:/*LANG_ID3_YEAR*/ if (id3->year_string) + { info = id3->year_string; + if(say_it && info) + talk_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 10:/*LANG_ID3_LENGTH*/ format_time(buffer, MAX_PATH, id3->length); info=buffer; + if(say_it) + talk_id(TALK_ID(id3->length /1000, UNIT_TIME_XACT), true); break; case 11:/*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 12:/*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 13:/*LANG_ID3_FREQUENCY*/ 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 14:/*LANG_ID3_TRACK_GAIN*/ info=id3->track_gain_string; + if(say_it && info) + say_gain(info); break; case 15:/*LANG_ID3_ALBUM_GAIN*/ info=id3->album_gain_string; - break; + if(say_it && info) + say_gain(info); + break; case 16:/*LANG_ID3_PATH*/ #else case 14:/*LANG_ID3_PATH*/ #endif info=id3->path; + if(say_it && info) + talk_fullpath(info, true); break; } + if((!info || !*info) && say_it) + talk_id(LANG_ID3_NO_INFO, true); return info && *info ? info : (char*) str(LANG_ID3_NO_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); +} +int id3_speak_item(int selected_item, void* data) +{ + char buffer[MAX_PATH]; + selected_item &= ~1; /* Make sure it's even, to indicate the header */ + /* say field name */ + id3_get_or_speak_info(selected_item, data, buffer, true); + /* and field value */ + id3_get_or_speak_info(selected_item+1, data, buffer, true); + return 0; +} + bool browse_id3(void) { struct gui_synclist id3_lists; @@ -1274,15 +1432,18 @@ bool browse_id3(void) int key; gui_synclist_init(&id3_lists, &id3_get_info, id3, true, 2); + if(talk_menus_enabled()) + gui_synclist_set_voice_callback(&id3_lists, id3_speak_item); gui_synclist_set_nb_items(&id3_lists, sizeof(id3_headers)/sizeof(id3_headers[0])*2); gui_synclist_draw(&id3_lists); + gui_synclist_speak_item(&id3_lists); gui_syncstatusbar_draw(&statusbars, true); while (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(!list_do_action(CONTEXT_LIST,HZ/2, + &id3_lists, &key,LIST_WRAP_UNLESS_HELD) + && key!=ACTION_NONE && key!=ACTION_UNKNOWN) { return(default_event_handler(key) == SYS_USB_CONNECTED); } Index: trunk/apps/talk.h =================================================================== --- trunk.orig/apps/talk.h +++ trunk/apps/talk.h @@ -45,6 +45,7 @@ enum { UNIT_KBIT, /* kilobits per sec */ UNIT_TIME_XACT,/* time duration/interval in seconds, says hours,mins,secs*/ UNIT_TIME, /* as above but less verbose */ + UNIT_DATEYEAR,/* for 1999 say nineteen ninety nine */ UNIT_LAST /* END MARKER */ }; Index: trunk/apps/talk.c =================================================================== --- trunk.orig/apps/talk.c +++ trunk/apps/talk.c @@ -944,6 +944,27 @@ 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 xact, bool enqueue) @@ -1008,6 +1029,9 @@ 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 || unit == UNIT_TIME_XACT) return talk_time(n, unit == UNIT_TIME_XACT, enqueue); Index: trunk/apps/lang/english.lang =================================================================== --- trunk.orig/apps/lang/english.lang +++ trunk/apps/lang/english.lang @@ -7444,263 +7444,263 @@ id: LANG_ID3_TITLE desc: in tag viewer user: *: "[Title]" *: "[Title]" - *: "" + *: "Title" id: LANG_ID3_ARTIST desc: in tag viewer user: *: "[Artist]" *: "[Artist]" - *: "" + *: "Artist" id: LANG_ID3_ALBUM desc: in tag viewer user: *: "[Album]" *: "[Album]" - *: "" + *: "Album" id: LANG_ID3_TRACKNUM desc: in tag viewer user: *: "[Tracknum]" *: "[Tracknum]" - *: "" + *: "Track number" id: LANG_ID3_GENRE desc: in tag viewer user: *: "[Genre]" *: "[Genre]" - *: "" + *: "Genre" id: LANG_ID3_YEAR desc: in tag viewer user: *: "[Year]" *: "[Year]" - *: "" + *: "Year" id: LANG_ID3_LENGTH desc: in tag viewer user: *: "[Length]" *: "[Length]" - *: "" + *: "Length" id: LANG_ID3_PLAYLIST desc: in tag viewer user: *: "[Playlist]" *: "[Playlist]" - *: "" + *: "Playlist" id: LANG_ID3_BITRATE desc: in tag viewer user: *: "[Bitrate]" *: "[Bitrate]" - *: "" + *: "Bit rate" id: LANG_ID3_ALBUMARTIST desc: in tag viewer user: *: "[Album Artist]" *: "[Album Artist]" - *: "" + *: "Album Artist" id: LANG_ID3_DISCNUM desc: in tag viewer user: *: "[Discnum]" *: "[Discnum]" - *: "" + *: "Disc number" id: LANG_ID3_COMMENT desc: in tag viewer user: *: "[Comment]" *: "[Comment]" - *: "" + *: "Comment" id: LANG_ID3_VBR desc: in browse_id3 user: *: " (VBR)" *: " (VBR)" - *: "" + *: "V B R" id: LANG_ID3_FREQUENCY desc: in tag viewer user: *: "[Frequency]" *: "[Frequency]" - *: "" + *: "Frequency" id: LANG_ID3_TRACK_GAIN desc: in tag viewer user: *: "[Track Gain]" *: "[Track Gain]" - *: "" + *: "Track gain" id: LANG_ID3_ALBUM_GAIN desc: in tag viewer user: *: "[Album Gain]" *: "[Album Gain]" - *: "" + *: "Album gain" id: LANG_ID3_PATH desc: in tag viewer user: *: "[Path]" *: "[Path]" - *: "" + *: "Path" id: LANG_ID3_NO_INFO desc: in tag viewer user: *: "" *: "" - *: "" + *: "No info" id: LANG_RENAME desc: The verb/action Rename user: *: "Rename" *: "Rename" @@ -11001,25 +11001,25 @@ id: LANG_ID3_GROUPING desc: in tag viewer user: *: "[Work]" *: "[Work]" - *: "" + *: "Work" id: LANG_SHOW_FILENAME_EXT desc: in settings_menu user: *: "Show Filename Extensions" *: "Show Filename Extensions"