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
This commit is contained in:
parent
239b70fad3
commit
fa97f161ab
14 changed files with 619 additions and 375 deletions
|
@ -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
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
#include "buffer.h"
|
||||
#include "screens.h"
|
||||
#include "playlist_menu.h"
|
||||
#include "talk.h"
|
||||
#ifdef HAVE_FMRADIO
|
||||
#include "radio.h"
|
||||
#endif
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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,
|
||||
|
|
398
apps/talk.c
Normal file
398
apps/talk.c
Normal file
|
@ -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 <stdio.h>
|
||||
#include <stddef.h>
|
||||
#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;
|
||||
}
|
||||
|
|
@ -26,9 +26,36 @@
|
|||
|
||||
#include <stdbool.h>
|
||||
|
||||
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)<<UNIT_SHIFT | ((n) & ~(-1<<UNIT_SHIFT)))
|
||||
|
||||
/* convenience macro to have both string and ID as arguments */
|
||||
#define STR(id) str(id), id
|
||||
|
||||
|
||||
void talk_init(void);
|
||||
int talk_buffer_steal(void); /* claim the mp3 buffer e.g. for play/record */
|
||||
int talk_id(int id, bool block); /* play a voice ID from voicefont */
|
||||
int talk_file(char* filename, bool block); /* play a thumbnail from file */
|
||||
int talk_id(int id, bool enqueue); /* play a voice ID from voicefont */
|
||||
int talk_file(char* filename, bool enqueue); /* play a thumbnail from file */
|
||||
int talk_number(int n, bool enqueue); /* say a number */
|
||||
int talk_value(int n, int unit, bool enqueue); /* say a numeric value */
|
||||
|
||||
#endif /* __TALK_H__ */
|
|
@ -30,7 +30,6 @@
|
|||
#include "mp3data.h"
|
||||
#include "buffer.h"
|
||||
#include "mp3_playback.h"
|
||||
#include "talk.h"
|
||||
#ifndef SIMULATOR
|
||||
#include "i2c.h"
|
||||
#include "mas.h"
|
||||
|
@ -2135,7 +2134,6 @@ void mpeg_record(char *filename)
|
|||
recording_filename[MAX_PATH - 1] = 0;
|
||||
|
||||
disable_xing_header = false;
|
||||
talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
|
||||
queue_post(&mpeg_queue, MPEG_RECORD, NULL);
|
||||
}
|
||||
|
||||
|
@ -2150,7 +2148,6 @@ static void start_prerecording(void)
|
|||
prerecord_timeout = current_tick + HZ;
|
||||
memset(prerecord_buffer, 0, sizeof(prerecord_buffer));
|
||||
reset_mp3_buffer();
|
||||
talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
|
||||
|
||||
is_prerecording = true;
|
||||
|
||||
|
@ -2407,7 +2404,6 @@ void mpeg_play(int offset)
|
|||
#else
|
||||
is_playing = true;
|
||||
|
||||
talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
|
||||
queue_post(&mpeg_queue, MPEG_PLAY, (void*)offset);
|
||||
#endif /* #ifdef SIMULATOR */
|
||||
|
||||
|
|
209
firmware/talk.c
209
firmware/talk.c
|
@ -1,209 +0,0 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 <stdio.h>
|
||||
#include <stddef.h>
|
||||
#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;
|
||||
}
|
Loading…
Reference in a new issue