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,98 @@ 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 */
+#if CONFIG_CODEC == SWCODEC
+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);
+}
+#endif
+
+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 +1273,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_EXACT), 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 +1434,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_EXACT,/* 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 exact, 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_EXACT)
return talk_time(n, unit == UNIT_TIME_EXACT, 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"