Index: trunk/apps/talk.c =================================================================== --- trunk.orig/apps/talk.c +++ trunk/apps/talk.c @@ -41,6 +41,7 @@ #include "playback.h" #endif #include "debug.h" +#include "kernel.h" /* Memory layout varies between targets because the @@ -108,6 +109,7 @@ struct queue_entry /* one entry of the i /***************** Globals *****************/ static unsigned char* p_thumbnail = NULL; /* buffer for thumbnail */ +static volatile int thumbnail_buf_used; static long size_for_thumbnail; /* leftover buffer size for it */ static struct voicefile* p_voicefile; /* loaded voicefile */ static bool has_voicefile; /* a voicefile file is present */ @@ -115,6 +117,15 @@ static struct queue_entry queue[QUEUE_SI static bool force_enqueue_next; /* enqueue next utterance even if enqueue is false */ static int queue_write; /* write index of queue, by application */ static int queue_read; /* read index of queue, by ISR context */ +#if CONFIG_CODEC == SWCODEC +struct mutex queue_mutex; /* protects queue_read, queue_write + and thumbnail_buf_used */ +#define queue_lock() do { mutex_lock(&queue_mutex); } while(0) +#define queue_unlock() do { mutex_unlock(&queue_mutex); } while(0) +#else +#define queue_lock() do { } while(0) +#define queue_unlock() do { } while(0) +#endif /* CONFIG_CODEC */ static int sent; /* how many bytes handed over to playback, owned by ISR */ static unsigned char curr_hd[3]; /* current frame header, for re-sync */ static int filehandle = -1; /* global, so the MMC variant can keep the file open */ @@ -296,7 +307,11 @@ static void mp3_callback(unsigned char** *size = sent; return; } - else if (sent > 0) /* go to next entry */ + queue_lock(); + if(p_thumbnail + && queue[queue_read].buf == p_thumbnail +thumbnail_buf_used) + thumbnail_buf_used = 0; + if (sent > 0) /* go to next entry */ { queue_read = (queue_read + 1) & QUEUE_MASK; } @@ -318,7 +333,8 @@ re_check: } else if (p_silence != NULL /* silence clip available */ && p_lastclip != p_silence /* previous clip wasn't silence */ - && p_lastclip != p_thumbnail) /* ..or thumbnail */ + && !(p_lastclip >= p_thumbnail /* ..or thumbnail */ + && p_lastclip < p_thumbnail +size_for_thumbnail)) { /* add silence clip when queue runs empty playing a voice clip */ queue[queue_write].buf = p_silence; queue[queue_write].len = silence_len; @@ -330,6 +346,7 @@ re_check: { *size = 0; /* end of data */ } + queue_unlock(); } /***************** Public routines *****************/ @@ -385,6 +402,7 @@ void talk_force_shutup(void) DTCR3 = sent; /* let the DMA finish this frame */ CHCR3 |= 0x0001; /* re-enable DMA */ #endif /* CONFIG_CPU == SH7034 */ + thumbnail_buf_used = 0; return; } } @@ -392,8 +410,10 @@ void talk_force_shutup(void) /* Either SWCODEC, or MAS had nothing to do (was frame boundary or not our clip) */ mp3_play_stop(); + queue_lock(); queue_write = queue_read = 0; /* reset the queue */ - return; + thumbnail_buf_used = 0; + queue_unlock(); } /* Shutup the voice, except if force_enqueue_next is set. */ @@ -420,6 +440,7 @@ static void queue_clip(unsigned char* bu /* disable the DMA temporarily, to be safe of race condition */ CHCR3 &= ~0x0001; #endif + queue_lock(); queue_level = QUEUE_LEVEL; /* check old level */ if (queue_level < QUEUE_SIZE - 1) /* space left? */ @@ -428,7 +449,8 @@ static void queue_clip(unsigned char* bu queue[queue_write].len = size; queue_write = (queue_write + 1) & QUEUE_MASK; } - + queue_unlock(); + if (queue_level == 0) { /* queue was empty, we have to do the initial start */ p_lastclip = buf; @@ -458,6 +480,9 @@ static void queue_clip(unsigned char* bu static void reset_state(void) { queue_write = queue_read = 0; /* reset the queue */ +#if CONFIG_CODEC == SWCODEC + mutex_init(&queue_mutex); +#endif /* CONFIG_CODEC == SWCODEC */ p_voicefile = NULL; /* indicate no voicefile (trashed) */ #if CONFIG_CODEC == SWCODEC /* Allocate a dedicated thumbnail buffer - once */ @@ -473,6 +498,7 @@ static void reset_state(void) p_thumbnail = audiobuf; size_for_thumbnail = audiobufend - audiobuf; #endif + thumbnail_buf_used = 0; p_silence = NULL; /* pause clip not accessible */ } @@ -615,10 +641,13 @@ void talk_force_enqueue_next(void) } /* play a thumbnail from file */ -int talk_file(const char* filename, bool enqueue) +/* Returns size of spoken thumbnail, so >0 means something is spoken, + <=0 means something went wrong. */ +int talk_file(const char* filename, long *prefix_ids, bool enqueue) { int fd; int size; + int thumb_used; #if CONFIG_CODEC != SWCODEC struct mp3entry info; #endif @@ -640,32 +669,157 @@ int talk_file(const char* filename, bool } #endif + if (!enqueue) + /* We shutup() instead of relying on queue_clip() in case the + thumbnail buffer is currently in use: shutup now so it's + freed and we don't have to wait for it too long. */ + talk_shutup(); + fd = open(filename, O_RDONLY); if (fd < 0) /* failed to open */ { return 0; } + if(filesize(fd) > size_for_thumbnail -thumbnail_buf_used) + { /* Don't play truncated clips */ + close(fd); + return 0; + } + #if CONFIG_CODEC != SWCODEC lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */ #endif - size = read(fd, p_thumbnail, size_for_thumbnail); + thumb_used = thumbnail_buf_used; + size = read(fd, p_thumbnail +thumb_used, + size_for_thumbnail -thumb_used); close(fd); /* ToDo: find audio, skip ID headers and trailers */ - if (size > 0 && size != size_for_thumbnail) /* Don't play missing or truncated clips */ + if (size > 0) /* Don't play missing clips */ { #if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR) bitswap(p_thumbnail, size); #endif - queue_clip(p_thumbnail, size, enqueue); + if(prefix_ids) + /* prefix thumbnail by speaking these ids, but only now + that we know there's actually a thumbnail to be + spoken. */ + talk_idarray(prefix_ids, true); + queue_lock(); + thumbnail_buf_used = thumb_used +size; + queue_unlock(); + queue_clip(p_thumbnail +thumb_used, size, true); } return size; } +/* Play a file's .talk thumbnail, fallback to spelling the filename, or + go straight to spelling depending on settings. + If dirname is non-NULL, it is prepended to the filename, otherwise + the filename should be a full path name. */ +int talk_file_or_spell(const char *dirname, const char* filename, + long *prefix_ids, bool enqueue) +{ + if (global_settings.talk_file_clip) + { /* .talk clips enabled */ + char buf[MAX_PATH]; + /* Does dirname end with a slash */ + char *slash = (dirname && strlen(dirname) >1 + && dirname[strlen(dirname)-1] != '/') ? "/" : ""; + snprintf(buf, MAX_PATH, "%s%s%s%s", + dirname ? dirname : "", + slash, + filename, + file_thumbnail_ext); + if(talk_file(buf, prefix_ids, enqueue) > 0) + return 0; + } + if (global_settings.talk_file == 2) + { /* Either .talk clips are disabled, or as a fallback */ + if(prefix_ids) + { + talk_idarray(prefix_ids, enqueue); + enqueue = true; + } + /* Spelling is slow, so speak only the basename (after the + last slash ) */ + const char *ptr = strrchr(filename, '/'); + if(ptr) + ++ptr; + else ptr = filename; + return talk_spell(ptr, enqueue); + } + return 0; +} + +/* Play a directory's .talk thumbnail, fallback to spelling the filename, or + go straight to spelling depending on settings. */ +int talk_dir_or_spell(const char* dirname, + long *prefix_ids, bool enqueue) +{ + if (global_settings.talk_dir_clip) + { /* .talk clips enabled */ + char buf[MAX_PATH]; + /* Does directory name end in slash? */ + char *slash = (strlen(dirname) >1 + && dirname[strlen(dirname)-1] != '/') ? "/" : ""; + snprintf(buf, MAX_PATH, "%s%s%s", + dirname, slash, dir_thumbnail_name); + if(talk_file(buf, prefix_ids, enqueue) > 0) + return 0; + } + if (global_settings.talk_file == 2) + { /* Either .talk clips disabled or as a fallback */ + if(prefix_ids) + { + talk_idarray(prefix_ids, enqueue); + enqueue = true; + } + char buf[MAX_PATH]; + /* Spell only the path component after the last slash */ + strncpy(buf, dirname, MAX_PATH); + if(strlen(buf) >1 && buf[strlen(buf)-1] == '/') + /* strip trailing slash */ + buf[strlen(buf)-1] = '\0'; + char *ptr = strrchr(buf, '/'); + if(ptr && strlen(buf) >1) + ++ptr; + else ptr = buf; + return talk_spell(ptr, enqueue); + } + return 0; +} + +/* Speak thumbnail for each component of a full path, again falling + back or going straight to spelling depending on settings. */ +int talk_fullpath(const char* path, bool enqueue) +{ + if (!enqueue) + talk_shutup(); + if(path[0] != '/') + /* path ought to start with /... */ + return talk_spell(path, true); + talk_id(VOICE_CHAR_SLASH, true); + char buf[MAX_PATH]; + strncpy(buf, path, MAX_PATH); + char *start = buf+1; /* start of current component */ + char *ptr = strchr(start, '/'); /* end of current component */ + while(ptr) { /* There are more slashes ahead */ + /* temporarily poke a NULL at end of component to truncate string */ + *ptr = '\0'; + talk_dir_or_spell(buf, NULL, true); + *ptr = '/'; /* restore string */ + start = ptr+1; /* setup for next component */ + ptr = strchr(start, '/'); + talk_id(VOICE_CHAR_SLASH, true); + } + /* no more slashes, final component is a filename */ + return talk_file_or_spell(NULL, buf, NULL, true); +} /* say a numeric value, this word ordering works for english, but not necessarily for other languages (e.g. german) */ @@ -863,6 +1017,8 @@ int talk_spell(const char* spell, bool e talk_id(VOICE_DOT, true); else if (c == ' ') talk_id(VOICE_PAUSE, true); + else if (c == '/') + talk_id(VOICE_CHAR_SLASH, true); } return 0; Index: trunk/apps/tree.c =================================================================== --- trunk.orig/apps/tree.c +++ trunk/apps/tree.c @@ -1097,6 +1097,9 @@ void bookmark_play(char *resume_file, in int i; char* suffix = strrchr(resume_file, '.'); + /* Nicely shuts up the voice while acknowledging the key press */ + gui_syncsplash(0, ID2P(LANG_WAIT)); + if (suffix != NULL && (!strcasecmp(suffix, ".m3u") || !strcasecmp(suffix, ".m3u8"))) { @@ -1209,7 +1212,7 @@ static int ft_play_dirname(char* name) DEBUGF("Found: %s\n", dirname_mp3_filename); - talk_file(dirname_mp3_filename, false); + talk_file(dirname_mp3_filename, NULL, false); if(global_settings.talk_filetype) talk_id(VOICE_DIR, true); return 1; @@ -1230,14 +1233,15 @@ static void ft_play_filename(char *dir, snprintf(name_mp3_filename, sizeof(name_mp3_filename), "%s/%s%s", dir, file, file_thumbnail_ext); - talk_file(name_mp3_filename, false); + talk_file(name_mp3_filename, NULL, false); } else { /* it already is a .talk file, play this directly */ snprintf(name_mp3_filename, sizeof(name_mp3_filename), "%s/%s", dir, file); talk_id(LANG_VOICE_DIR_HOVER, false); /* prefix it */ - talk_file(name_mp3_filename, true); + talk_file(name_mp3_filename, TALK_IDARRAY(LANG_VOICE_DIR_HOVER), + false); } } Index: trunk/apps/talk.h =================================================================== --- trunk.orig/apps/talk.h +++ trunk/apps/talk.h @@ -74,7 +74,16 @@ int talk_get_bufsize(void); /* get the l void talk_buffer_steal(void); /* claim the mp3 buffer e.g. for play/record */ bool is_voice_queued(void); /* Are there more voice clips to be spoken? */ int talk_id(int32_t id, bool enqueue); /* play a voice ID from voicefont */ -int talk_file(const char* filename, bool enqueue); /* play a thumbnail from file */ +/* play a thumbnail from file */ +int talk_file(const char* filename, long *prefix_ids, bool enqueue); +/* play file's thumbnail or spell name */ +int talk_file_or_spell(const char *dirname, const char* filename, + long *prefix_ids, bool enqueue); +/* play dir's thumbnail or spell name */ +int talk_dir_or_spell(const char* filename, + long *prefix_ids, bool enqueue); +/* play thumbnails for each components of full path, or spell */ +int talk_fullpath(const char* path, bool enqueue); int talk_number(long n, bool enqueue); /* say a number */ int talk_value(long n, int unit, bool enqueue); /* say a numeric value */ int talk_spell(const char* spell, bool enqueue); /* spell a string */ Index: trunk/apps/lang/english.lang =================================================================== --- trunk.orig/apps/lang/english.lang +++ trunk/apps/lang/english.lang @@ -7329,7 +7329,7 @@ *: "%s doesn't exist" - *: "" + *: "Playlist directory doesn't exist" @@ -7343,7 +7343,7 @@ *: "No Playlists" - *: "" + *: "No Playlists" @@ -9856,6 +9856,20 @@ + id: VOICE_CHAR_SLASH + desc: spoken only, for spelling + user: + + *: "" + + + *: "" + + + *: "slash" + + + id: VOICE_PAUSE desc: spoken only, for spelling, a split second of silence (difficult to author) user: Index: trunk/apps/playlist_catalog.c =================================================================== --- trunk.orig/apps/playlist_catalog.c +++ trunk/apps/playlist_catalog.c @@ -39,6 +39,7 @@ #include "yesno.h" #include "filetypes.h" #include "debug.h" +#include "talk.h" #define PLAYLIST_CATALOG_CFG ROCKBOX_DIR "/playlist_catalog.config" #define PLAYLIST_CATALOG_DEFAULT_DIR "/Playlists" @@ -114,7 +115,12 @@ static int initialize_catalog(void) if (!playlist_dir_exists) { if (mkdir(playlist_dir) < 0) { - gui_syncsplash(HZ*2, str(LANG_CATALOG_NO_DIRECTORY), + if (global_settings.talk_menu) { + talk_id(LANG_CATALOG_NO_DIRECTORY, false); + talk_spell(playlist_dir, true); + talk_force_enqueue_next(); + } + gui_syncsplash(HZ*2, ID2P(LANG_CATALOG_NO_DIRECTORY), playlist_dir); return -1; } @@ -148,7 +154,7 @@ static int create_playlist_list(char** p if (ft_load(tc, playlist_dir) < 0) { - gui_syncsplash(HZ*2, str(LANG_CATALOG_NO_DIRECTORY), + gui_syncsplash(HZ*2, ID2P(LANG_CATALOG_NO_DIRECTORY), playlist_dir); goto exit; } @@ -228,6 +234,13 @@ static char* playlist_callback_name(int return buffer; } +static int playlist_callback_voice(int selected_item, void* data) +{ + char** playlists = (char**) data; + talk_file_or_spell(playlist_dir, playlists[selected_item], NULL, false); + return 0; +} + /* Display all playlists in catalog. Selected "playlist" is returned. If "view" mode is set then we're not adding anything into playlist. */ static int display_playlists(char* playlist, bool view) @@ -245,7 +258,7 @@ static int display_playlists(char* playl if (num_playlists <= 0) { - gui_syncsplash(HZ*2, str(LANG_CATALOG_NO_PLAYLISTS)); + gui_syncsplash(HZ*2, ID2P(LANG_CATALOG_NO_PLAYLISTS)); return -1; } @@ -254,16 +267,19 @@ static int display_playlists(char* playl gui_synclist_init(&playlist_lists, playlist_callback_name, playlists, false, 1); + if(global_settings.talk_menu) + gui_synclist_set_voice_callback(&playlist_lists, + playlist_callback_voice); gui_synclist_set_nb_items(&playlist_lists, num_playlists); gui_synclist_draw(&playlist_lists); + gui_synclist_speak_item(&playlist_lists); while (!exit) { - int button = get_action(CONTEXT_LIST,HZ/2); + int button; char* sel_file; - - gui_synclist_do_button(&playlist_lists, &button,LIST_WRAP_UNLESS_HELD); - + list_do_action(CONTEXT_LIST,HZ/2, + &playlist_lists, &button,LIST_WRAP_UNLESS_HELD); sel_file = playlists[gui_synclist_get_sel_pos(&playlist_lists)]; switch (button) @@ -299,7 +315,10 @@ static int display_playlists(char* playl exit = true; } else + { gui_synclist_draw(&playlist_lists); + gui_synclist_speak_item(&playlist_lists); + } } break; @@ -323,6 +342,15 @@ static int display_playlists(char* playl insert */ static void display_insert_count(int count) { + static long talked_tick = 0; + if(count && (talked_tick == 0 + || TIME_AFTER(current_tick, talked_tick+5*HZ))) + { + talked_tick = current_tick; + talk_number(count, false); + talk_id(LANG_PLAYLIST_INSERT_COUNT, true); + } + gui_syncsplash(0, str(LANG_PLAYLIST_INSERT_COUNT), count, str(LANG_OFF_ABORT)); } @@ -402,7 +430,7 @@ static int add_to_playlist(const char* p /* search directory for tracks and append to playlist */ bool recurse = false; char *lines[] = { - (char *)str(LANG_RECURSE_DIRECTORY_QUESTION), + ID2P(LANG_RECURSE_DIRECTORY_QUESTION), sel }; struct text_message message={lines, 2}; Index: trunk/apps/bookmark.c =================================================================== --- trunk.orig/apps/bookmark.c +++ trunk/apps/bookmark.c @@ -80,7 +80,7 @@ static bool check_bookmark(const char* static char* create_bookmark(void); static bool delete_bookmark(const char* bookmark_file_name, int bookmark_id); static void say_bookmark(const char* bookmark, - int bookmark_id); + int bookmark_id, bool show_playlist_name); static bool play_bookmark(const char* bookmark); static bool generate_bookmark_file_name(const char *in); static const char* skip_token(const char* s); @@ -624,6 +624,22 @@ static char* get_bookmark_info(int list_ } } +static int bookmark_list_voice_cb(int list_index, void* data) +{ + struct bookmark_list* bookmarks = (struct bookmark_list*) data; + int index = list_index / 2; + + if (bookmarks->show_dont_resume) + { + if (index == 0) + return talk_id(LANG_BOOKMARK_DONT_RESUME, false); + index--; + } + say_bookmark(bookmarks->items[index - bookmarks->start], index, + bookmarks->show_playlist_name); + return 0; +} + /* ----------------------------------------------------------------------- */ /* This displays a the bookmarks in a file and allows the user to */ /* select one to play. */ @@ -632,7 +648,6 @@ static char* select_bookmark(const char* { struct bookmark_list* bookmarks; struct gui_synclist list; - int last_item = -2; int item = 0; int action; size_t size; @@ -647,6 +662,8 @@ static char* select_bookmark(const char* bookmarks->show_playlist_name = strcmp(bookmark_file_name, RECENT_BOOKMARK_FILE) == 0; gui_synclist_init(&list, &get_bookmark_info, (void*) bookmarks, false, 2); + if(global_settings.talk_menu) + gui_synclist_set_voice_callback(&list, bookmark_list_voice_cb); gui_synclist_set_title(&list, str(LANG_BOOKMARK_SELECT_BOOKMARK), Icon_Bookmark); gui_syncstatusbar_draw(&statusbars, true); @@ -685,32 +702,20 @@ static char* select_bookmark(const char* buffer_bookmarks(bookmarks, bookmarks->start); gui_synclist_draw(&list); + cond_talk_ids_fq(VOICE_EXT_BMARK); + gui_synclist_speak_item(&list); refresh = false; } - action = get_action(CONTEXT_BOOKMARKSCREEN, HZ / 2); - gui_synclist_do_button(&list, &action, LIST_WRAP_UNLESS_HELD); + list_do_action(CONTEXT_BOOKMARKSCREEN, HZ / 2, + &list, &action, LIST_WRAP_UNLESS_HELD); item = gui_synclist_get_sel_pos(&list) / 2; if (bookmarks->show_dont_resume) { item--; } - - if (item != last_item && global_settings.talk_menu) - { - last_item = item; - - if (item == -1) - { - talk_id(LANG_BOOKMARK_DONT_RESUME, true); - } - else - { - say_bookmark(bookmarks->items[item - bookmarks->start], item); - } - } - + if (action == ACTION_STD_CONTEXT) { MENUITEM_STRINGLIST(menu_items, ID2P(LANG_BOOKMARK_CONTEXT_MENU), @@ -752,7 +757,6 @@ static char* select_bookmark(const char* delete_bookmark(bookmark_file_name, item); bookmarks->reload = true; refresh = true; - last_item = -2; } break; @@ -817,41 +821,53 @@ static bool delete_bookmark(const char* /* This function parses a bookmark, says the voice UI part of it. */ /* ------------------------------------------------------------------------*/ static void say_bookmark(const char* bookmark, - int bookmark_id) + int bookmark_id, bool show_playlist_name) { int resume_index; long ms; - bool enqueue = false; /* only the first voice is not queued */ + bool playlist_shuffle = false; + bool is_dir; - if (!parse_bookmark(bookmark, &resume_index, NULL, NULL, NULL, - global_temp_buffer, sizeof(global_temp_buffer), &ms, NULL, NULL, NULL)) + if (!parse_bookmark(bookmark, &resume_index, NULL, NULL, NULL, + global_temp_buffer,sizeof(global_temp_buffer), + &ms, NULL, &playlist_shuffle, + global_filename)) { - talk_id(LANG_BOOKMARK_INVALID, true); + talk_id(LANG_BOOKMARK_INVALID, false); return; } -/* disabled, because transition between talkbox and voice UI clip is not nice */ -#if 0 - if (global_settings.talk_dir >= 3) - { /* "talkbox" enabled */ - char* last = strrchr(global_temp_buffer, '/'); - if (last) - { /* compose filename for talkbox */ - strncpy(last + 1, dir_thumbnail_name, - sizeof(global_temp_buffer) - (last - global_temp_buffer) - 1); - talk_file(global_temp_buffer, enqueue); - enqueue = true; - } + talk_number(bookmark_id + 1, false); + + is_dir = (global_temp_buffer[strlen(global_temp_buffer)-1] == '/'); + if(show_playlist_name) + { /* It's useful to know which playlist this is */ + if(is_dir) + talk_dir_or_spell(global_temp_buffer, + TALK_IDARRAY(VOICE_DIR), true); + else talk_file_or_spell(NULL, global_temp_buffer, + TALK_IDARRAY(LANG_PLAYLIST), true); } -#endif - talk_id(VOICE_EXT_BMARK, enqueue); - talk_number(bookmark_id + 1, true); + + if(playlist_shuffle) + talk_id(LANG_SHUFFLE, true); + talk_id(VOICE_BOOKMARK_SELECT_INDEX_TEXT, true); talk_number(resume_index + 1, true); talk_id(LANG_TIME, true); - if (ms / 60000) - talk_value(ms / 60000, UNIT_MIN, true); - talk_value((ms % 60000) / 1000, UNIT_SEC, true); + talk_value(ms / 1000, UNIT_TIME, true); + + /* Track filename */ + if(is_dir) + talk_file_or_spell(global_temp_buffer, global_filename, + TALK_IDARRAY(VOICE_FILE), true); + else + { /* Unfortunately if this is a playlist, we do not know in which + directory the file is and therefore cannot find the track's + .talk file. */ + talk_id(VOICE_FILE, true); + talk_spell(global_filename, true); + } } /* ----------------------------------------------------------------------- */