From fa97f161abc45bfd5db86bceb8803d2661e65447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Hohensohn?= Date: Fri, 19 Mar 2004 22:15:53 +0000 Subject: [PATCH] Third step of the voice-UI: numerical settings are spoken (composed at runtime), needs new voicefont at the new location ".rockbox/langs/english.voice" git-svn-id: svn://svn.rockbox.org/rockbox/trunk@4414 a1c6a512-1295-4272-9138-f99709370657 --- apps/bookmark.c | 1 + apps/main_menu.c | 1 + apps/playlist.c | 1 + apps/playlist_menu.c | 2 +- apps/playlist_viewer.c | 2 + apps/recorder/radio.c | 8 + apps/recorder/recording.c | 14 ++ apps/settings.h | 3 - apps/settings_menu.c | 209 ++++++++-------- apps/sound_menu.c | 111 +++++---- apps/talk.c | 398 +++++++++++++++++++++++++++++++ {firmware/export => apps}/talk.h | 31 ++- firmware/mpeg.c | 4 - firmware/talk.c | 209 ---------------- 14 files changed, 619 insertions(+), 375 deletions(-) create mode 100644 apps/talk.c rename {firmware/export => apps}/talk.h (53%) delete mode 100644 firmware/talk.c diff --git a/apps/bookmark.c b/apps/bookmark.c index 32521a3868..b376cdfa20 100644 --- a/apps/bookmark.c +++ b/apps/bookmark.c @@ -44,6 +44,7 @@ #include "debug.h" #include "kernel.h" #include "sprintf.h" +#include "talk.h" #define MAX_BOOKMARKS 10 #define MAX_BOOKMARK_SIZE 350 diff --git a/apps/main_menu.c b/apps/main_menu.c index 6fec696240..dc6e84d626 100644 --- a/apps/main_menu.c +++ b/apps/main_menu.c @@ -44,6 +44,7 @@ #include "buffer.h" #include "screens.h" #include "playlist_menu.h" +#include "talk.h" #ifdef HAVE_FMRADIO #include "radio.h" #endif diff --git a/apps/playlist.c b/apps/playlist.c index 53a18c606e..f38f215361 100644 --- a/apps/playlist.c +++ b/apps/playlist.c @@ -1596,6 +1596,7 @@ int playlist_start(int start_index, int offset) struct playlist_info* playlist = ¤t_playlist; playlist->index = start_index; + talk_buffer_steal(); /* will use the mp3 buffer */ mpeg_play(offset); return 0; diff --git a/apps/playlist_menu.c b/apps/playlist_menu.c index da1f9f3734..d1c69bfdae 100644 --- a/apps/playlist_menu.c +++ b/apps/playlist_menu.c @@ -26,7 +26,7 @@ #include "tree.h" #include "settings.h" #include "playlist_viewer.h" - +#include "talk.h" #include "lang.h" #define DEFAULT_PLAYLIST_NAME "/dynamic.m3u" diff --git a/apps/playlist_viewer.c b/apps/playlist_viewer.c index e41b942538..d7c34e4300 100644 --- a/apps/playlist_viewer.c +++ b/apps/playlist_viewer.c @@ -31,6 +31,7 @@ #include "keyboard.h" #include "tree.h" #include "onplay.h" +#include "talk.h" #ifdef HAVE_LCD_BITMAP #include "widgets.h" @@ -721,6 +722,7 @@ static int onplay_menu(int index) if (tracks[index].display_index != viewer.num_tracks || global_settings.repeat_mode == REPEAT_ALL) { + talk_buffer_steal(); /* will use the mp3 buffer */ mpeg_play(0); viewer.current_playing_track = -1; } diff --git a/apps/recorder/radio.c b/apps/recorder/radio.c index 5430279f85..f8da238c6f 100644 --- a/apps/recorder/radio.c +++ b/apps/recorder/radio.c @@ -46,6 +46,7 @@ #include "font.h" #include "sound_menu.h" #include "recording.h" +#include "talk.h" #ifdef HAVE_FMRADIO @@ -174,6 +175,9 @@ bool radio_screen(void) peak_meter_enabled = true; + if (global_settings.rec_prerecord_time) + talk_buffer_steal(); /* will use the mp3 buffer */ + mpeg_set_recording_options(global_settings.rec_frequency, global_settings.rec_quality, 1, /* Line In */ @@ -257,6 +261,7 @@ bool radio_screen(void) else { have_recorded = true; + talk_buffer_steal(); /* we use the mp3 buffer */ mpeg_record(rec_create_filename(buf)); status_set_playmode(STATUS_RECORD); update_screen = true; @@ -704,6 +709,9 @@ static bool fm_recording_settings(void) ret = recording_menu(true); if(!ret) { + if (global_settings.rec_prerecord_time) + talk_buffer_steal(); /* will use the mp3 buffer */ + mpeg_set_recording_options(global_settings.rec_frequency, global_settings.rec_quality, 1, /* Line In */ diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c index c7d4b803d2..c2f2462a5c 100644 --- a/apps/recorder/recording.c +++ b/apps/recorder/recording.c @@ -46,6 +46,7 @@ #include "string.h" #include "dir.h" #include "errno.h" +#include "talk.h" bool f2_rec_screen(void); bool f3_rec_screen(void); @@ -166,6 +167,9 @@ bool recording_screen(void) peak_meter_enabled = true; + if (global_settings.rec_prerecord_time) + talk_buffer_steal(); /* will use the mp3 buffer */ + mpeg_set_recording_options(global_settings.rec_frequency, global_settings.rec_quality, global_settings.rec_source, @@ -223,6 +227,7 @@ bool recording_screen(void) if(!(mpeg_status() & MPEG_STATUS_RECORD)) { have_recorded = true; + talk_buffer_steal(); /* we use the mp3 buffer */ mpeg_record(rec_create_filename(path_buffer)); status_set_playmode(STATUS_RECORD); update_countdown = 1; /* Update immediately */ @@ -336,6 +341,9 @@ bool recording_screen(void) return SYS_USB_CONNECTED; settings_save(); + if (global_settings.rec_prerecord_time) + talk_buffer_steal(); /* will use the mp3 buffer */ + mpeg_set_recording_options(global_settings.rec_frequency, global_settings.rec_quality, global_settings.rec_source, @@ -660,6 +668,9 @@ bool f2_rec_screen(void) } } + if (global_settings.rec_prerecord_time) + talk_buffer_steal(); /* will use the mp3 buffer */ + mpeg_set_recording_options(global_settings.rec_frequency, global_settings.rec_quality, global_settings.rec_source, @@ -730,6 +741,9 @@ bool f3_rec_screen(void) } } + if (global_settings.rec_prerecord_time) + talk_buffer_steal(); /* will use the mp3 buffer */ + mpeg_set_recording_options(global_settings.rec_frequency, global_settings.rec_quality, global_settings.rec_source, diff --git a/apps/settings.h b/apps/settings.h index d5992ccd79..b51c4d8e47 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -60,9 +60,6 @@ #define FF_REWIND_45000 12 #define FF_REWIND_60000 13 -/* convenience macro to have both string and ID as arguments */ -#define STR(id) str(id), id - struct user_settings { diff --git a/apps/settings_menu.c b/apps/settings_menu.c index 64df007afe..e1dcf6582c 100644 --- a/apps/settings_menu.c +++ b/apps/settings_menu.c @@ -40,6 +40,7 @@ #include "ata.h" #include "tree.h" #include "screens.h" +#include "talk.h" #ifdef HAVE_LCD_BITMAP #include "peakmeter.h" #endif @@ -167,23 +168,23 @@ static bool peak_meter_hold(void) { bool retval = false; struct opt_items names[] = { { STR(LANG_OFF) }, - { "200 ms " , -1 }, - { "300 ms " , -1 }, - { "500 ms " , -1 }, - { "1 s " , -1 }, - { "2 s " , -1 }, - { "3 s " , -1 }, - { "4 s " , -1 }, - { "5 s " , -1 }, - { "6 s " , -1 }, - { "7 s" , -1 }, - { "8 s" , -1 }, - { "9 s" , -1 }, - { "10 s" , -1 }, - { "15 s" , -1 }, - { "20 s" , -1 }, - { "30 s" , -1 }, - { "1 min" , -1 } + { "200 ms " , TALK_ID(200, UNIT_MS) }, + { "300 ms " , TALK_ID(300, UNIT_MS) }, + { "500 ms " , TALK_ID(500, UNIT_MS) }, + { "1 s" , TALK_ID(1, UNIT_SEC) }, + { "2 s" , TALK_ID(2, UNIT_SEC) }, + { "3 s" , TALK_ID(3, UNIT_SEC) }, + { "4 s" , TALK_ID(4, UNIT_SEC) }, + { "5 s" , TALK_ID(5, UNIT_SEC) }, + { "6 s" , TALK_ID(6, UNIT_SEC) }, + { "7 s" , TALK_ID(7, UNIT_SEC) }, + { "8 s" , TALK_ID(8, UNIT_SEC) }, + { "9 s" , TALK_ID(9, UNIT_SEC) }, + { "10 s" , TALK_ID(10, UNIT_SEC) }, + { "15 s" , TALK_ID(15, UNIT_SEC) }, + { "20 s" , TALK_ID(20, UNIT_SEC) }, + { "30 s" , TALK_ID(30, UNIT_SEC) }, + { "1 min" , TALK_ID(1, UNIT_MIN) } }; retval = set_option( str(LANG_PM_PEAK_HOLD), &global_settings.peak_meter_hold, INT, names, @@ -204,30 +205,30 @@ static bool peak_meter_clip_hold(void) { struct opt_items names[] = { { STR(LANG_PM_ETERNAL) }, - { "1s " , -1 }, - { "2s " , -1 }, - { "3s " , -1 }, - { "4s " , -1 }, - { "5s " , -1 }, - { "6s " , -1 }, - { "7s " , -1 }, - { "8s " , -1 }, - { "9s " , -1 }, - { "10s" , -1 }, - { "15s" , -1 }, - { "20s" , -1 }, - { "25s" , -1 }, - { "30s" , -1 }, - { "45s" , -1 }, - { "60s" , -1 }, - { "90s" , -1 }, - { "2min" , -1 }, - { "3min" , -1 }, - { "5min" , -1 }, - { "10min" , -1 }, - { "20min" , -1 }, - { "45min" , -1 }, - { "90min" , -1 } + { "1s " , TALK_ID(1, UNIT_SEC) }, + { "2s " , TALK_ID(2, UNIT_SEC) }, + { "3s " , TALK_ID(3, UNIT_SEC) }, + { "4s " , TALK_ID(4, UNIT_SEC) }, + { "5s " , TALK_ID(5, UNIT_SEC) }, + { "6s " , TALK_ID(6, UNIT_SEC) }, + { "7s " , TALK_ID(7, UNIT_SEC) }, + { "8s " , TALK_ID(8, UNIT_SEC) }, + { "9s " , TALK_ID(9, UNIT_SEC) }, + { "10s" , TALK_ID(10, UNIT_SEC) }, + { "15s" , TALK_ID(15, UNIT_SEC) }, + { "20s" , TALK_ID(20, UNIT_SEC) }, + { "25s" , TALK_ID(25, UNIT_SEC) }, + { "30s" , TALK_ID(30, UNIT_SEC) }, + { "45s" , TALK_ID(45, UNIT_SEC) }, + { "60s" , TALK_ID(60, UNIT_SEC) }, + { "90s" , TALK_ID(90, UNIT_SEC) }, + { "2min" , TALK_ID(2, UNIT_MIN) }, + { "3min" , TALK_ID(3, UNIT_MIN) }, + { "5min" , TALK_ID(5, UNIT_MIN) }, + { "10min" , TALK_ID(10, UNIT_MIN) }, + { "20min" , TALK_ID(20, UNIT_MIN) }, + { "45min" , TALK_ID(45, UNIT_MIN) }, + { "90min" , TALK_ID(90, UNIT_MIN) } }; retval = set_option( str(LANG_PM_CLIP_HOLD), &global_settings.peak_meter_clip_hold, INT, names, @@ -531,23 +532,23 @@ static bool backlight_timer(void) struct opt_items names[] = { { STR(LANG_OFF) }, { STR(LANG_ON) }, - { "1s ", -1 }, - { "2s ", -1 }, - { "3s ", -1 }, - { "4s ", -1 }, - { "5s ", -1 }, - { "6s ", -1 }, - { "7s ", -1 }, - { "8s ", -1 }, - { "9s ", -1 }, - { "10s", -1 }, - { "15s", -1 }, - { "20s", -1 }, - { "25s", -1 }, - { "30s", -1 }, - { "45s", -1 }, - { "60s", -1 }, - { "90s", -1 } + { "1s ", TALK_ID(1, UNIT_SEC) }, + { "2s ", TALK_ID(2, UNIT_SEC) }, + { "3s ", TALK_ID(3, UNIT_SEC) }, + { "4s ", TALK_ID(4, UNIT_SEC) }, + { "5s ", TALK_ID(5, UNIT_SEC) }, + { "6s ", TALK_ID(6, UNIT_SEC) }, + { "7s ", TALK_ID(7, UNIT_SEC) }, + { "8s ", TALK_ID(8, UNIT_SEC) }, + { "9s ", TALK_ID(9, UNIT_SEC) }, + { "10s", TALK_ID(10, UNIT_SEC) }, + { "15s", TALK_ID(15, UNIT_SEC) }, + { "20s", TALK_ID(20, UNIT_SEC) }, + { "25s", TALK_ID(25, UNIT_SEC) }, + { "30s", TALK_ID(30, UNIT_SEC) }, + { "45s", TALK_ID(45, UNIT_SEC) }, + { "60s", TALK_ID(60, UNIT_SEC) }, + { "90s", TALK_ID(90, UNIT_SEC) } }; return set_option(str(LANG_BACKLIGHT), &global_settings.backlight_timeout, INT, names, 19, backlight_set_timeout ); @@ -557,20 +558,20 @@ static bool poweroff_idle_timer(void) { struct opt_items names[] = { { STR(LANG_OFF) }, - { "1m ", -1 }, - { "2m ", -1 }, - { "3m ", -1 }, - { "4m ", -1 }, - { "5m ", -1 }, - { "6m ", -1 }, - { "7m ", -1 }, - { "8m ", -1 }, - { "9m ", -1 }, - { "10m", -1 }, - { "15m", -1 }, - { "30m", -1 }, - { "45m", -1 }, - { "60m", -1 } + { "1m ", TALK_ID(1, UNIT_MIN) }, + { "2m ", TALK_ID(2, UNIT_MIN) }, + { "3m ", TALK_ID(3, UNIT_MIN) }, + { "4m ", TALK_ID(4, UNIT_MIN) }, + { "5m ", TALK_ID(5, UNIT_MIN) }, + { "6m ", TALK_ID(6, UNIT_MIN) }, + { "7m ", TALK_ID(7, UNIT_MIN) }, + { "8m ", TALK_ID(8, UNIT_MIN) }, + { "9m ", TALK_ID(9, UNIT_MIN) }, + { "10m", TALK_ID(10, UNIT_MIN) }, + { "15m", TALK_ID(15, UNIT_MIN) }, + { "30m", TALK_ID(30, UNIT_MIN) }, + { "45m", TALK_ID(45, UNIT_MIN) }, + { "60m", TALK_ID(60, UNIT_MIN) } }; return set_option(str(LANG_POWEROFF_IDLE), &global_settings.poweroff, INT, names, 15, set_poweroff_timeout); @@ -613,9 +614,9 @@ static bool jump_scroll(void) struct opt_items names[] = { { STR(LANG_OFF) }, { STR(LANG_ONE_TIME) }, - { "2", -1 }, - { "3", -1 }, - { "4", -1 }, + { "2", TALK_ID(2, UNIT_INT) }, + { "3", TALK_ID(3, UNIT_INT) }, + { "4", TALK_ID(4, UNIT_INT) }, { STR(LANG_ALWAYS) } }; bool ret; @@ -799,20 +800,20 @@ static bool buffer_margin(void) static bool ff_rewind_min_step(void) { struct opt_items names[] = { - { "1s", -1 }, - { "2s", -1 }, - { "3s", -1 }, - { "4s", -1 }, - { "5s", -1 }, - { "6s", -1 }, - { "8s", -1 }, - { "10s", -1 }, - { "15s", -1 }, - { "20s", -1 }, - { "25s", -1 }, - { "30s", -1 }, - { "45s", -1 }, - { "60s", -1 } + { "1s", TALK_ID(1, UNIT_SEC) }, + { "2s", TALK_ID(2, UNIT_SEC) }, + { "3s", TALK_ID(3, UNIT_SEC) }, + { "4s", TALK_ID(4, UNIT_SEC) }, + { "5s", TALK_ID(5, UNIT_SEC) }, + { "6s", TALK_ID(6, UNIT_SEC) }, + { "8s", TALK_ID(8, UNIT_SEC) }, + { "10s", TALK_ID(10, UNIT_SEC) }, + { "15s", TALK_ID(15, UNIT_SEC) }, + { "20s", TALK_ID(20, UNIT_SEC) }, + { "25s", TALK_ID(25, UNIT_SEC) }, + { "30s", TALK_ID(30, UNIT_SEC) }, + { "45s", TALK_ID(45, UNIT_SEC) }, + { "60s", TALK_ID(60, UNIT_SEC) } }; return set_option(str(LANG_FFRW_STEP), &global_settings.ff_rewind_min_step, INT, names, 14, NULL ); @@ -828,21 +829,21 @@ static bool ff_rewind_accel(void) { struct opt_items names[] = { { STR(LANG_OFF) }, - { "2x/1s", -1 }, - { "2x/2s", -1 }, - { "2x/3s", -1 }, - { "2x/4s", -1 }, - { "2x/5s", -1 }, - { "2x/6s", -1 }, - { "2x/7s", -1 }, - { "2x/8s", -1 }, - { "2x/9s", -1 }, - { "2x/10s", -1 }, - { "2x/11s", -1 }, - { "2x/12s", -1 }, - { "2x/13s", -1 }, - { "2x/14s", -1 }, - { "2x/15s", -1 } + { "2x/1s", TALK_ID(1, UNIT_SEC) }, + { "2x/2s", TALK_ID(2, UNIT_SEC) }, + { "2x/3s", TALK_ID(3, UNIT_SEC) }, + { "2x/4s", TALK_ID(4, UNIT_SEC) }, + { "2x/5s", TALK_ID(5, UNIT_SEC) }, + { "2x/6s", TALK_ID(6, UNIT_SEC) }, + { "2x/7s", TALK_ID(7, UNIT_SEC) }, + { "2x/8s", TALK_ID(8, UNIT_SEC) }, + { "2x/9s", TALK_ID(9, UNIT_SEC) }, + { "2x/10s", TALK_ID(10, UNIT_SEC) }, + { "2x/11s", TALK_ID(11, UNIT_SEC) }, + { "2x/12s", TALK_ID(12, UNIT_SEC) }, + { "2x/13s", TALK_ID(13, UNIT_SEC) }, + { "2x/14s", TALK_ID(14, UNIT_SEC) }, + { "2x/15s", TALK_ID(15, UNIT_SEC) } }; return set_option(str(LANG_FFRW_ACCEL), &global_settings.ff_rewind_accel, INT, names, 16, NULL ); diff --git a/apps/sound_menu.c b/apps/sound_menu.c index 17f6eb5072..0001d8b2fe 100644 --- a/apps/sound_menu.c +++ b/apps/sound_menu.c @@ -32,6 +32,7 @@ #endif #include "lang.h" #include "sprintf.h" +#include "talk.h" static char *fmt[] = { @@ -53,11 +54,16 @@ bool set_sound(char* string, int dec; char* unit; char str[32]; + int talkunit = UNIT_INT; unit = mpeg_sound_unit(setting); numdec = mpeg_sound_numdecimals(setting); min = mpeg_sound_min(setting); max = mpeg_sound_max(setting); + if (*unit == 'd') /* crude reconstruction */ + talkunit = UNIT_DB; + else if (*unit == '%') + talkunit = UNIT_PERCENT; #ifdef HAVE_LCD_BITMAP if(global_settings.statusbar) @@ -81,6 +87,7 @@ bool set_sound(char* string, { snprintf(str,sizeof str,"%d %s ", val, unit); } + talk_value(val, talkunit, false); /* speak it */ } lcd_puts(0,1,str); status_draw(true); @@ -183,9 +190,9 @@ static bool avc(void) { struct opt_items names[] = { { STR(LANG_OFF) }, - { "2s", -1 }, - { "4s", -1 }, - { "8s", -1 } + { "2s", TALK_ID(2, UNIT_SEC) }, + { "4s", TALK_ID(4, UNIT_SEC) }, + { "8s", TALK_ID(8, UNIT_SEC) } }; return set_option(str(LANG_DECAY), &global_settings.avc, INT, names, 4, set_avc); @@ -206,12 +213,12 @@ static bool recsource(void) static bool recfrequency(void) { struct opt_items names[] = { - { "44.1kHz", -1 }, - { "48kHz", -1 }, - { "32kHz", -1 }, - { "22.05kHz", -1 }, - { "24kHz", -1 }, - { "16kHz", -1 } + { "44.1kHz", TALK_ID(44, UNIT_KHZ) }, + { "48kHz", TALK_ID(48, UNIT_KHZ) }, + { "32kHz", TALK_ID(32, UNIT_KHZ) }, + { "22.05kHz", TALK_ID(22, UNIT_KHZ) }, + { "24kHz", TALK_ID(24, UNIT_KHZ) }, + { "16kHz", TALK_ID(16, UNIT_KHZ) } }; return set_option(str(LANG_RECORDING_FREQUENCY), &global_settings.rec_frequency, INT, @@ -246,19 +253,19 @@ static bool rectimesplit(void) { struct opt_items names[] = { { STR(LANG_OFF) }, - { "00:05" , -1 }, - { "00:10" , -1 }, - { "00:15" , -1 }, - { "00:30" , -1 }, - { "01:00" , -1 }, - { "02:00" , -1 }, - { "04:00" , -1 }, - { "06:00" , -1 }, - { "08:00" , -1 }, - { "10:00" , -1 }, - { "12:00" , -1 }, - { "18:00" , -1 }, - { "24:00" , -1 } + { "00:05" , TALK_ID(5, UNIT_MIN) }, + { "00:10" , TALK_ID(10, UNIT_MIN) }, + { "00:15" , TALK_ID(15, UNIT_MIN) }, + { "00:30" , TALK_ID(30, UNIT_MIN) }, + { "01:00" , TALK_ID(1, UNIT_HOUR) }, + { "02:00" , TALK_ID(2, UNIT_HOUR) }, + { "04:00" , TALK_ID(4, UNIT_HOUR) }, + { "06:00" , TALK_ID(6, UNIT_HOUR) }, + { "08:00" , TALK_ID(8, UNIT_HOUR) }, + { "10:00" , TALK_ID(10, UNIT_HOUR) }, + { "12:00" , TALK_ID(12, UNIT_HOUR) }, + { "18:00" , TALK_ID(18, UNIT_HOUR) }, + { "24:00" , TALK_ID(24, UNIT_HOUR) } }; return set_option(str(LANG_RECORD_TIMESPLIT), &global_settings.rec_timesplit, INT, @@ -269,36 +276,36 @@ static bool recprerecord(void) { struct opt_items names[] = { { STR(LANG_OFF) }, - { "1s", -1 }, - { "2s", -1 }, - { "3s", -1 }, - { "4s", -1 }, - { "5s", -1 }, - { "6s", -1 }, - { "7s", -1 }, - { "8s", -1 }, - { "9s", -1 }, - { "10s", -1 }, - { "11s", -1 }, - { "12s", -1 }, - { "13s", -1 }, - { "14s", -1 }, - { "15s", -1 }, - { "16s", -1 }, - { "17s", -1 }, - { "18s", -1 }, - { "19s", -1 }, - { "10s", -1 }, - { "21s", -1 }, - { "22s", -1 }, - { "23s", -1 }, - { "24s", -1 }, - { "25s", -1 }, - { "26s", -1 }, - { "27s", -1 }, - { "28s", -1 }, - { "29s", -1 }, - { "30s", -1 } + { "1s", TALK_ID(1, UNIT_SEC) }, + { "2s", TALK_ID(2, UNIT_SEC) }, + { "3s", TALK_ID(3, UNIT_SEC) }, + { "4s", TALK_ID(4, UNIT_SEC) }, + { "5s", TALK_ID(5, UNIT_SEC) }, + { "6s", TALK_ID(6, UNIT_SEC) }, + { "7s", TALK_ID(7, UNIT_SEC) }, + { "8s", TALK_ID(8, UNIT_SEC) }, + { "9s", TALK_ID(9, UNIT_SEC) }, + { "10s", TALK_ID(10, UNIT_SEC) }, + { "11s", TALK_ID(11, UNIT_SEC) }, + { "12s", TALK_ID(12, UNIT_SEC) }, + { "13s", TALK_ID(13, UNIT_SEC) }, + { "14s", TALK_ID(14, UNIT_SEC) }, + { "15s", TALK_ID(15, UNIT_SEC) }, + { "16s", TALK_ID(16, UNIT_SEC) }, + { "17s", TALK_ID(17, UNIT_SEC) }, + { "18s", TALK_ID(18, UNIT_SEC) }, + { "19s", TALK_ID(19, UNIT_SEC) }, + { "20s", TALK_ID(20, UNIT_SEC) }, + { "21s", TALK_ID(21, UNIT_SEC) }, + { "22s", TALK_ID(22, UNIT_SEC) }, + { "23s", TALK_ID(23, UNIT_SEC) }, + { "24s", TALK_ID(24, UNIT_SEC) }, + { "25s", TALK_ID(25, UNIT_SEC) }, + { "26s", TALK_ID(26, UNIT_SEC) }, + { "27s", TALK_ID(27, UNIT_SEC) }, + { "28s", TALK_ID(28, UNIT_SEC) }, + { "29s", TALK_ID(29, UNIT_SEC) }, + { "30s", TALK_ID(30, UNIT_SEC) } }; return set_option(str(LANG_RECORD_PRERECORD_TIME), &global_settings.rec_prerecord_time, INT, diff --git a/apps/talk.c b/apps/talk.c new file mode 100644 index 0000000000..d208c7296c --- /dev/null +++ b/apps/talk.c @@ -0,0 +1,398 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2004 Jörg Hohensohn + * + * This module collects the Talkbox and voice UI functions. + * (Talkbox reads directory names from mp3 clips called thumbnails, + * the voice UI lets menus and screens "talk" from a voicefont in memory. + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ + +#include +#include +#include "file.h" +#include "buffer.h" +#include "system.h" +#include "mp3_playback.h" +#include "mpeg.h" +#include "lang.h" +#include "talk.h" +#include "screens.h" /* test hack */ +extern void bitswap(unsigned char *data, int length); /* no header for this */ + +/***************** Constants *****************/ + +#define VOICEFONT_FILENAME "/.rockbox/langs/english.voice" +#define QUEUE_SIZE 32 + + +/***************** Data types *****************/ + +struct clip_entry /* one entry of the index table */ +{ + int offset; /* offset from start of voicefont file */ + int size; /* size of the clip */ +}; + +struct voicefont /* file format of our "voicefont" */ +{ + int version; /* version of the voicefont */ + int headersize; /* size of the header, =offset to index */ + int id_max; /* number of clips contained */ + struct clip_entry index[]; /* followed by the index table */ + /* and finally the bitswapped mp3 clips, not visible here */ +}; + + +struct queue_entry /* one entry of the internal queue */ +{ + unsigned char* buf; + int len; +}; + + + +/***************** Globals *****************/ + +static unsigned char* p_thumbnail; /* buffer for thumbnail */ +static long size_for_thumbnail; /* leftover buffer size for it */ +static struct voicefont* p_voicefont; /* loaded voicefont */ +static bool has_voicefont; /* a voicefont file is present */ +static bool is_playing; /* we're currently playing */ +static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */ +static int queue_write; /* write index of queue, by application */ +static int queue_read; /* read index of queue, by ISR context */ + + + +/***************** Private implementation *****************/ + +static int load_voicefont(void) +{ + int fd; + int size; + + p_voicefont = NULL; /* indicate no voicefont if we fail below */ + + fd = open(VOICEFONT_FILENAME, O_RDONLY); + if (fd < 0) /* failed to open */ + { + p_voicefont = NULL; /* indicate no voicefont */ + has_voicefont = false; /* don't try again */ + return 0; + } + + size = read(fd, mp3buf, mp3end - mp3buf); + if (size > 1000 + && ((struct voicefont*)mp3buf)->headersize + == offsetof(struct voicefont, index)) + { + p_voicefont = (struct voicefont*)mp3buf; + + /* thumbnail buffer is the remaining space behind */ + p_thumbnail = mp3buf + size; + p_thumbnail += (int)p_thumbnail % 2; /* 16-bit align */ + size_for_thumbnail = mp3end - p_thumbnail; + } + else + { + has_voicefont = false; /* don't try again */ + } + close(fd); + + return size; +} + + +/* called in ISR context if mp3 data got consumed */ +static void mp3_callback(unsigned char** start, int* size) +{ + int play_now; + + if (queue[queue_read].len > 0) /* current clip not finished? */ + { /* feed the next 64K-1 chunk */ + play_now = MIN(queue[queue_read].len, 0xFFFF); + *start = queue[queue_read].buf; + *size = play_now; + queue[queue_read].buf += play_now; + queue[queue_read].len -= play_now; + return; + } + else /* go to next entry */ + { + queue_read++; + if (queue_read >= QUEUE_SIZE) + queue_read = 0; + } + + if (queue_read != queue_write) /* queue is not empty? */ + { /* start next clip */ + play_now = MIN(queue[queue_read].len, 0xFFFF); + *start = queue[queue_read].buf; + *size = play_now; + queue[queue_read].buf += play_now; + queue[queue_read].len -= play_now; + } + else + { + *size = 0; /* end of data */ + is_playing = false; + mp3_play_stop(); /* fixme: should be done by caller */ + } +} + + +/* stop the playback and the pending clips, but at frame boundary */ +static int shutup(void) +{ + mp3_play_pause(false); /* pause */ + + /* ToDo: search next frame boundary and continue up to there */ + + queue_write = queue_read; + is_playing = false; + mp3_play_stop(); + + return 0; +} + + +/* schedule a clip, at the end or discard the existing queue */ +static int queue_clip(unsigned char* buf, int size, bool enqueue) +{ + if (!enqueue) + shutup(); /* cut off all the pending stuff */ + + queue[queue_write].buf = buf; + queue[queue_write].len = size; + + /* FixMe: make this IRQ-safe */ + + if (!is_playing) + { /* queue empty, we have to do the initial start */ + int size_now = MIN(size, 0xFFFF); /* DMA can do no more */ + is_playing = true; + mp3_play_data(buf, size_now, mp3_callback); + mp3_play_pause(true); /* kickoff audio */ + queue[queue_write].buf += size_now; + queue[queue_write].len -= size_now; + } + + queue_write++; + if (queue_write >= QUEUE_SIZE) + queue_write = 0; + + return 0; +} + + +/***************** Public implementation *****************/ + +void talk_init(void) +{ + has_voicefont = true; /* unless we fail later, assume we have one */ + talk_buffer_steal(); + queue_write = queue_read = 0; +} + + +/* somebody else claims the mp3 buffer, e.g. for regular play/record */ +int talk_buffer_steal(void) +{ + p_voicefont = NULL; /* indicate no voicefont (trashed) */ + p_thumbnail = mp3buf; /* whole space for thumbnail */ + size_for_thumbnail = mp3end - mp3buf; + return 0; +} + + +/* play a voice ID from voicefont */ +int talk_id(int id, bool enqueue) +{ + int clipsize; + unsigned char* clipbuf; + int unit; + + if (mpeg_status()) /* busy, buffer in use */ + return -1; + + if (p_voicefont == NULL && has_voicefont) + load_voicefont(); /* reload needed */ + + if (p_voicefont == NULL) /* still no voices? */ + return -1; + + /* check if this is a special ID, with a value */ + unit = ((unsigned)id) >> UNIT_SHIFT; + if (id != -1 && unit) + { /* sign-extend the value */ + //splash(200, true,"unit=%d", unit); + id = (unsigned)id << (32-UNIT_SHIFT); + id >>= (32-UNIT_SHIFT); + talk_value(id, unit, enqueue); /* speak it */ + return 0; /* and stop, end of special case */ + } + + if (id < 0 || id >= p_voicefont->id_max) + return -1; + + clipsize = p_voicefont->index[id].size; + if (clipsize == 0) /* clip not included in voicefont */ + return -1; + + clipbuf = mp3buf + p_voicefont->index[id].offset; + + queue_clip(clipbuf, clipsize, enqueue); + + return 0; +} + + +/* play a thumbnail from file */ +int talk_file(char* filename, bool enqueue) +{ + int fd; + int size; + + if (mpeg_status()) /* busy, buffer in use */ + return -1; + + if (p_thumbnail == NULL || size_for_thumbnail <= 0) + return -1; + + fd = open(filename, O_RDONLY); + if (fd < 0) /* failed to open */ + { + return 0; + } + + size = read(fd, p_thumbnail, size_for_thumbnail); + close(fd); + + /* ToDo: find audio, skip ID headers and trailers */ + + if (size) + { + bitswap(p_thumbnail, size); + queue_clip(p_thumbnail, size, enqueue); + } + + return size; +} + + +/* say a numeric value, this works for english, + but not necessarily for other languages */ +int talk_number(int n, bool enqueue) +{ + int level = 0; // mille count + int mil = 1000000000; // highest possible "-illion" + + if (!enqueue) + shutup(); /* cut off all the pending stuff */ + + if (n==0) + { // special case + talk_id(VOICE_ZERO, true); + return 0; + } + + if (n<0) + { + talk_id(VOICE_MINUS, true); + n = -n; + } + + while (n) + { + int segment = n / mil; // extract in groups of 3 digits + n -= segment * mil; // remove the used digits from number + mil /= 1000; // digit place for next round + + if (segment) + { + int hundreds = segment / 100; + int ones = segment % 100; + + if (hundreds) + { + talk_id(VOICE_ZERO + hundreds, true); + talk_id(VOICE_HUNDRED, true); + } + + // combination indexing + if (ones > 20) + { + int tens = ones/10 + 18; + talk_id(VOICE_ZERO + tens, true); + ones %= 10; + } + + // direct indexing + if (ones) + talk_id(VOICE_ZERO + ones, true); + + // add billion, million, thousand + if (mil) + talk_id(VOICE_BILLION + level, true); + } + level++; + } + + return 0; +} + +int talk_value(int n, int unit, bool enqueue) +{ + int unit_id; + const int unit_voiced[] = + { /* lookup table for the voice ID of the units */ + -1, -1, -1, /* regular ID, int, signed */ + VOICE_MILLISECONDS, /* here come the "real" units */ + VOICE_SECONDS, + VOICE_MINUTES, + VOICE_HOURS, + VOICE_KHZ, + VOICE_DB, + VOICE_PERCENT, + VOICE_MEGABYTE, + VOICE_GIGABYTE + }; + + if (unit < 0 || unit >= UNIT_LAST) + unit_id = -1; + else + unit_id = unit_voiced[unit]; + + if ((n==1 || n==-1) // singular? + && unit_id >= VOICE_SECONDS && unit_id <= VOICE_HOURS) + { + unit_id--; /* use the singular for those units which have */ + } + + /* special case with a "plus" before */ + if (n > 0 && (unit == UNIT_SIGNED || unit == UNIT_DB)) + { + talk_id(VOICE_PLUS, enqueue); + enqueue = true; + } + + talk_number(n, enqueue); /* say the number */ + talk_id(unit_id, true); /* say the unit, if any */ + + return 0; +} + diff --git a/firmware/export/talk.h b/apps/talk.h similarity index 53% rename from firmware/export/talk.h rename to apps/talk.h index b2af46f15e..4851e7b6fb 100644 --- a/firmware/export/talk.h +++ b/apps/talk.h @@ -26,9 +26,36 @@ #include +enum { + UNIT_INT = 1, /* plain number */ + UNIT_SIGNED, /* number with mandatory sign (even if positive) */ + UNIT_MS, /* milliseconds */ + UNIT_SEC, /* seconds */ + UNIT_MIN, /* minutes */ + UNIT_HOUR, /* hours */ + UNIT_KHZ, /* kHz */ + UNIT_DB, /* dB, mandatory sign */ + UNIT_PERCENT, /* % */ + UNIT_MB, /* megabyte */ + UNIT_GB, /* gigabyte */ + UNIT_LAST /* END MARKER */ +}; + +#define UNIT_SHIFT (32-4) /* this many bits left from UNIT_xx enum */ + +/* make a "talkable" ID from number + unit + unit is upper 4 bits, number the remaining (in regular 2's complement) */ +#define TALK_ID(n,u) ((u)< ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2004 Jörg Hohensohn - * - * This module collects the Talkbox and voice UI functions. - * (Talkbox reads directory names from mp3 clips called thumbnails, - * the voice UI lets menus and screens "talk" from a voicefont in memory. - * - * All files in this archive are subject to the GNU General Public License. - * See the file COPYING in the source tree root for full license agreement. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ - -#include -#include -#include "file.h" -#include "buffer.h" -#include "kernel.h" -#include "mp3_playback.h" -#include "mpeg.h" -#include "talk.h" -extern void bitswap(unsigned char *data, int length); /* no header for this */ - -/***************** Constants *****************/ - -#define VOICEFONT_FILENAME "/.rockbox/voicefont" - - -/***************** Data types *****************/ - -struct clip_entry /* one entry of the index table */ -{ - int offset; /* offset from start of voicefont file */ - int size; /* size of the clip */ -}; - -struct voicefont /* file format of our "voicefont" */ -{ - int version; /* version of the voicefont */ - int headersize; /* size of the header, =offset to index */ - int id_max; /* number of clips contained */ - struct clip_entry index[]; /* followed by the index table */ - /* and finally the bitswapped mp3 clips, not visible here */ -}; - - -/***************** Globals *****************/ - -static unsigned char* p_thumbnail; /* buffer for thumbnail */ -static long size_for_thumbnail; /* leftover buffer size for it */ -static struct voicefont* p_voicefont; /* loaded voicefont */ -static bool has_voicefont; /* a voicefont file is present */ -static bool is_playing; /* we're currently playing */ - - - -/***************** Private implementation *****************/ - -static int load_voicefont(void) -{ - int fd; - int size; - - p_voicefont = NULL; /* indicate no voicefont if we fail below */ - - fd = open(VOICEFONT_FILENAME, O_RDONLY); - if (fd < 0) /* failed to open */ - { - p_voicefont = NULL; /* indicate no voicefont */ - has_voicefont = false; /* don't try again */ - return 0; - } - - size = read(fd, mp3buf, mp3end - mp3buf); - if (size > 1000 - && ((struct voicefont*)mp3buf)->headersize - == offsetof(struct voicefont, index)) - { - p_voicefont = (struct voicefont*)mp3buf; - - /* thumbnail buffer is the remaining space behind */ - p_thumbnail = mp3buf + size; - p_thumbnail += (int)p_thumbnail % 2; /* 16-bit align */ - size_for_thumbnail = mp3end - p_thumbnail; - - /* max. DMA size, fixme */ - if (size_for_thumbnail > 0xFFFF) - size_for_thumbnail = 0xFFFF; - } - else - { - has_voicefont = false; /* don't try again */ - } - close(fd); - - return size; -} - - -/* called in ISR context if mp3 data got consumed */ -void mp3_callback(unsigned char** start, int* size) -{ - (void)start; /* unused parameter, avoid warning */ - *size = 0; /* end of data */ - is_playing = false; - mp3_play_stop(); /* fixme: should be done by caller */ -} - -/***************** Public implementation *****************/ - -void talk_init(void) -{ - has_voicefont = true; /* unless we fail later, assume we have one */ - talk_buffer_steal(); -} - - -/* somebody else claims the mp3 buffer, e.g. for regular play/record */ -int talk_buffer_steal(void) -{ - p_voicefont = NULL; /* indicate no voicefont (trashed) */ - p_thumbnail = mp3buf; /* whole space for thumbnail */ - size_for_thumbnail = mp3end - mp3buf; - /* max. DMA size, fixme */ - if (size_for_thumbnail > 0xFFFF) - size_for_thumbnail = 0xFFFF; - return 0; -} - - -/* play a voice ID from voicefont */ -int talk_id(int id, bool block) -{ - int clipsize; - - if (mpeg_status()) /* busy, buffer in use */ - return -1; - - if (p_voicefont == NULL && has_voicefont) - load_voicefont(); /* reload needed */ - - if (p_voicefont == NULL) /* still no voices? */ - return -1; - - if (id >= p_voicefont->id_max) - return -1; - - clipsize = p_voicefont->index[id].size; - if (clipsize == 0) /* clip not included in voicefont */ - return -1; - - is_playing = true; - mp3_play_data(mp3buf + p_voicefont->index[id].offset, - clipsize, mp3_callback); - mp3_play_pause(true); /* kickoff audio */ - - while(block && is_playing) - sleep(1); - - return 0; -} - - -/* play a thumbnail from file */ -int talk_file(char* filename, bool block) -{ - int fd; - int size; - - if (mpeg_status()) /* busy, buffer in use */ - return -1; - - if (p_thumbnail == NULL || size_for_thumbnail <= 0) - return -1; - - fd = open(filename, O_RDONLY); - if (fd < 0) /* failed to open */ - { - return 0; - } - - size = read(fd, p_thumbnail, size_for_thumbnail); - close(fd); - - /* ToDo: find audio, skip ID headers and trailers */ - - if (size) - { - bitswap(p_thumbnail, size); - is_playing = true; - mp3_play_data(p_thumbnail, size, mp3_callback); - mp3_play_pause(true); /* kickoff audio */ - - while(block && is_playing) - sleep(1); - } - - return size; -}