Index: apps/playlist.c
===================================================================
--- apps/playlist.c	(revision 12956)
+++ apps/playlist.c	(working copy)
@@ -288,6 +288,7 @@
     {
         if (check_rockboxdir())
         {
+            cond_talk_ids_fq(LANG_PLAYLIST_CONTROL_ACCESS_ERROR);
             gui_syncsplash(HZ*2, (unsigned char *)"%s (%d)",
                            str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR),
                            playlist->control_fd);
@@ -484,6 +485,7 @@
         lcd_setmargins(0, 0);
 #endif
 
+    cond_talk_ids(LANG_WAIT);
     gui_syncsplash(0, str(LANG_PLAYLIST_LOAD));
 
     if (!buffer)
@@ -770,9 +772,9 @@
         unsigned char* count_str;
 
         if (c->queue)
-            count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
+            count_str = ID2P(LANG_PLAYLIST_QUEUE_COUNT);
         else
-            count_str = str(LANG_PLAYLIST_INSERT_COUNT);
+            count_str = ID2P(LANG_PLAYLIST_INSERT_COUNT);
 
         display_playlist_count(c->count, count_str);
         
@@ -1348,9 +1350,9 @@
         if (max < 0)
         {
             if (control_file)
-                gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
+                gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
             else
-                gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
+                gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
 
             return max;
         }
@@ -1440,7 +1442,7 @@
 
         if (ft_load(tc, (dir[0]=='\0')?"/":dir) < 0)
         {
-            gui_syncsplash(HZ*2, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
+            gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
             exit = true;
             result = -1;
             break;
@@ -1525,7 +1527,7 @@
     
     if (ft_load(tc, dir) < 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
         return -2;
     }
     
@@ -1579,7 +1581,7 @@
         /* we now need to reload our current directory */
         if(ft_load(tc, dir) < 0)
             gui_syncsplash(HZ*2,
-                str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));        
+                ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));        
     }
 
     return result;
@@ -1654,6 +1656,20 @@
  */
 static void display_playlist_count(int count, const unsigned char *fmt)
 {
+    static long talked_tick = 0;
+    if(count && (talked_tick == 0
+                 || TIME_AFTER(current_tick, talked_tick+5*HZ)))
+    {
+        talked_tick = current_tick;
+        long id = P2ID(fmt);
+        if(id>=0)
+        {
+            talk_number(count, false);
+            talk_id(id, true);
+        }
+    }
+    fmt = P2STR(fmt);
+
     lcd_clear_display();
 
 #ifdef HAVE_LCD_BITMAP
@@ -1677,7 +1693,7 @@
  */
 static void display_buffer_full(void)
 {
-    gui_syncsplash(HZ*2, str(LANG_PLAYLIST_BUFFER_FULL));
+    gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_BUFFER_FULL));
 }
 
 /*
@@ -1756,7 +1772,7 @@
     else
     {
         result = -1;
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_UPDATE_ERROR));
     }
 
     return result;
@@ -1943,11 +1959,11 @@
 
     empty_playlist(playlist, true);
 
-    gui_syncsplash(0, str(LANG_WAIT));
+    gui_syncsplash(0, ID2P(LANG_WAIT));
     playlist->control_fd = open(playlist->control_filename, O_RDWR);
     if (playlist->control_fd < 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
         return -1;
     }
     playlist->control_created = true;
@@ -1955,7 +1971,7 @@
     control_file_size = filesize(playlist->control_fd);
     if (control_file_size <= 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
         return -1;
     }
 
@@ -1964,7 +1980,7 @@
         PLAYLIST_COMMAND_SIZE<buflen?PLAYLIST_COMMAND_SIZE:buflen);
     if(nread <= 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
         return -1;
     }
 
@@ -2264,7 +2280,7 @@
 
         if (result < 0)
         {
-            gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_INVALID));
+            gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_INVALID));
             return result;
         }
 
@@ -2273,7 +2289,7 @@
             if ((total_read + count) >= control_file_size)
             {
                 /* no newline at end of control file */
-                gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_INVALID));
+                gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_INVALID));
                 return -1;
             }
 
@@ -2367,6 +2383,7 @@
         start_current = true;
     }
 
+    cond_talk_ids(LANG_WAIT);
     gui_syncsplash(0, str(LANG_PLAYLIST_SHUFFLE));
     
     randomise_playlist(playlist, random_seed, start_current, true);
@@ -2859,7 +2876,7 @@
 
     if (check_control(playlist) < 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
         return -1;
     }
 
@@ -2890,7 +2907,7 @@
 
     if (check_control(playlist) < 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
         return -1;
     }
 
@@ -2903,9 +2920,9 @@
     }
 
     if (queue)
-        count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
+        count_str = ID2P(LANG_PLAYLIST_QUEUE_COUNT);
     else
-        count_str = str(LANG_PLAYLIST_INSERT_COUNT);
+        count_str = ID2P(LANG_PLAYLIST_INSERT_COUNT);
 
     display_playlist_count(0, count_str);
 
@@ -2956,14 +2973,14 @@
 
     if (check_control(playlist) < 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
         return -1;
     }
 
     fd = open(filename, O_RDONLY);
     if (fd < 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
         return -1;
     }
 
@@ -2977,9 +2994,9 @@
         dir = "/";
 
     if (queue)
-        count_str = str(LANG_PLAYLIST_QUEUE_COUNT);
+        count_str = ID2P(LANG_PLAYLIST_QUEUE_COUNT);
     else
-        count_str = str(LANG_PLAYLIST_INSERT_COUNT);
+        count_str = ID2P(LANG_PLAYLIST_INSERT_COUNT);
 
     display_playlist_count(count, count_str);
 
@@ -3076,7 +3093,7 @@
 
     if (check_control(playlist) < 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
         return -1;
     }
 
@@ -3111,7 +3128,7 @@
 
     if (check_control(playlist) < 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR));
         return -1;
     }
 
@@ -3374,7 +3391,7 @@
         if (playlist->buffer_size < (int)(playlist->amount * sizeof(int)))
         {
             /* not enough buffer space to store updated indices */
-            gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
+            gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
             return -1;
         }
 
@@ -3390,11 +3407,11 @@
     fd = open(path, O_CREAT|O_WRONLY|O_TRUNC);
     if (fd < 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
         return -1;
     }
 
-    display_playlist_count(count, str(LANG_PLAYLIST_SAVE_COUNT));
+    display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT));
 
     cpu_boost(true);
 
@@ -3431,7 +3448,7 @@
 
             if (fdprintf(fd, "%s\n", tmp_buf) < 0)
             {
-                gui_syncsplash(HZ*2, str(LANG_PLAYLIST_ACCESS_ERROR));
+                gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
                 result = -1;
                 break;
             }
@@ -3518,7 +3535,7 @@
 
     if (ft_load(tc, dirname) < 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
+        gui_syncsplash(HZ*2, ID2P(LANG_PLAYLIST_DIRECTORY_ACCESS_ERROR));
         *(tc->dirfilter) = old_dirfilter;
         return -1;
     }
Index: apps/tree.c
===================================================================
--- apps/tree.c	(revision 12956)
+++ apps/tree.c	(working copy)
@@ -369,7 +369,7 @@
         (tc.dirfull ||
                       tc.filesindir == global_settings.max_files_in_dir) )
         {
-            gui_syncsplash(HZ, str(LANG_SHOWDIR_BUFFER_FULL));
+            gui_syncsplash(HZ, ID2P(LANG_SHOWDIR_BUFFER_FULL));
         }
     }
 #ifdef HAVE_TAGCACHE
Index: apps/settings.c
===================================================================
--- apps/settings.c	(revision 12956)
+++ apps/settings.c	(working copy)
@@ -578,6 +578,7 @@
             screens[i].update();
 #endif
         }
+        cond_talk_ids_fq(LANG_SETTINGS_SAVE_RECORDER);
         sleep(HZ*2);
         return -1;
     }
@@ -596,15 +597,15 @@
             break;
         }
         else {
-            gui_syncsplash(HZ, str(LANG_MENU_SETTING_CANCEL));
+            gui_syncsplash(HZ, ID2P(LANG_MENU_SETTING_CANCEL));
             return false;
         }
     }
 
     if (settings_write_config(filename, options))
-        gui_syncsplash(HZ, str(LANG_SETTINGS_SAVED));
+        gui_syncsplash(HZ, ID2P(LANG_SETTINGS_SAVED));
     else
-        gui_syncsplash(HZ, str(LANG_FAILED));
+        gui_syncsplash(HZ, ID2P(LANG_FAILED));
     return true;
 }
 
@@ -1142,7 +1143,7 @@
             {
                 if (*(int*)variable != oldvalue)
                 {
-                    gui_syncsplash(HZ/2, str(LANG_MENU_SETTING_CANCEL));
+                    gui_syncsplash(HZ/2, ID2P(LANG_MENU_SETTING_CANCEL));
                     *(int*)variable = oldvalue;
                 }
             }
@@ -1150,7 +1151,7 @@
             {
                 if (*(bool*)variable != (bool)oldvalue)
                 {
-                    gui_syncsplash(HZ/2, str(LANG_MENU_SETTING_CANCEL));
+                    gui_syncsplash(HZ/2, ID2P(LANG_MENU_SETTING_CANCEL));
                     *(bool*)variable = (bool)oldvalue;
                 }
             }
Index: apps/lang/english.lang
===================================================================
--- apps/lang/english.lang	(revision 12956)
+++ apps/lang/english.lang	(working copy)
@@ -152,7 +152,7 @@
     *: "Shutting down..."
   </dest>
   <voice>
-    *: ""
+    *: "Shutting down"
   </voice>
 </phrase>
 <phrase>
@@ -194,7 +194,7 @@
     *: "Cancelled"
   </dest>
   <voice>
-    *: ""
+    *: "Cancelled"
   </voice>
 </phrase>
 <phrase>
@@ -208,7 +208,7 @@
     *: "Failed"
   </dest>
   <voice>
-    *: ""
+    *: "Failed"
   </voice>
 </phrase>
 <phrase>
@@ -852,7 +852,7 @@
     *: "Are You Sure?"
   </dest>
   <voice>
-    *: ""
+    *: "Are You Sure?"
   </voice>
 </phrase>
 <phrase>
@@ -920,7 +920,7 @@
     *: "Cleared"
   </dest>
   <voice>
-    *: ""
+    *: "Settings Cleared"
   </voice>
 </phrase>
 <phrase>
@@ -934,7 +934,7 @@
     *: "Cancelled"
   </dest>
   <voice>
-    *: ""
+    *: "Canceled"
   </voice>
 </phrase>
 <phrase>
@@ -962,7 +962,7 @@
     *: "Save Failed"
   </dest>
   <voice>
-    *: ""
+    *: "Save Failed"
   </voice>
 </phrase>
 <phrase>
@@ -1176,17 +1176,17 @@
   </voice>
 </phrase>
 <phrase>
-  id: LANG_EQUALIZER_BAND_PEAK
+  id: LANG_EQUALIZER_BAND_PEAK1
   desc: in the equalizer settings menu
   user:
   <source>
-    *: "Peak Filter %d"
+    *: "Peak Filter 1"
   </source>
   <dest>
-    *: "Peak Filter %d"
+    *: "Peak Filter 1"
   </dest>
   <voice>
-    *: "Peak filter"
+    *: "Peak Filter 1"
   </voice>
 </phrase>
 <phrase>
@@ -1760,7 +1760,7 @@
     *: "Updating in background"
   </dest>
   <voice>
-    *: ""
+    *: "Updating in background"
   </voice>
 </phrase>
 <phrase>
@@ -4129,7 +4129,7 @@
     *: "File/directory exists. Overwrite?"
   </dest>
   <voice>
-    *: ""
+    *: "File or directory exists. Overwrite?"
   </voice>
 </phrase>
 <phrase>
@@ -4185,7 +4185,7 @@
     *: "Delete?"
   </dest>
   <voice>
-    *: ""
+    *: "Really delete?"
   </voice>
 </phrase>
 <phrase>
@@ -4199,7 +4199,7 @@
     *: "Deleted"
   </dest>
   <voice>
-    *: ""
+    *: "Deleted"
   </voice>
 </phrase>
 <phrase>
@@ -4849,7 +4849,7 @@
     *: "Load Last Bookmark?"
   </dest>
   <voice>
-    *: ""
+    *: "Load Last Bookmark?"
   </voice>
 </phrase>
 <phrase>
@@ -4863,7 +4863,7 @@
     *: "Create a Bookmark?"
   </dest>
   <voice>
-    *: ""
+    *: "Create a Bookmark?"
   </voice>
 </phrase>
 <phrase>
@@ -4877,7 +4877,7 @@
     *: "Bookmark Created"
   </dest>
   <voice>
-    *: ""
+    *: "Bookmark Created"
   </voice>
 </phrase>
 <phrase>
@@ -4891,7 +4891,7 @@
     *: "Bookmark Failed!"
   </dest>
   <voice>
-    *: ""
+    *: "Bookmark Failed!"
   </voice>
 </phrase>
 <phrase>
@@ -4905,7 +4905,7 @@
     *: "Bookmark Empty"
   </dest>
   <voice>
-    *: ""
+    *: "Bookmark Empty"
   </voice>
 </phrase>
 <phrase>
@@ -7281,7 +7281,7 @@
     *: "Playlist Buffer Full"
   </dest>
   <voice>
-    *: ""
+    *: "Playlist Buffer Full"
   </voice>
 </phrase>
 <phrase>
@@ -7309,7 +7309,7 @@
     *: "End of Song List"
   </dest>
   <voice>
-    *: ""
+    *: "End of Song List"
   </voice>
 </phrase>
 <phrase>
@@ -7337,7 +7337,7 @@
     *: "Inserted %d tracks (%s)"
   </dest>
   <voice>
-    *: ""
+    *: "Inserted tracks so far"
   </voice>
 </phrase>
 <phrase>
@@ -7351,7 +7351,7 @@
     *: "Queued %d tracks (%s)"
   </dest>
   <voice>
-    *: ""
+    *: "Queued tracks so far"
   </voice>
 </phrase>
 <phrase>
@@ -7365,7 +7365,7 @@
     *: "Saved %d tracks (%s)"
   </dest>
   <voice>
-    *: ""
+    *: "Tracks saved"
   </voice>
 </phrase>
 <phrase>
@@ -7379,7 +7379,7 @@
     *: "Recursively?"
   </dest>
   <voice>
-    *: ""
+    *: "Recursively?"
   </voice>
 </phrase>
 <phrase>
@@ -7393,7 +7393,7 @@
     *: "Erase dynamic playlist?"
   </dest>
   <voice>
-    *: ""
+    *: "Erase dynamic playlist?"
   </voice>
 </phrase>
 <phrase>
@@ -7407,7 +7407,7 @@
     *: "Nothing to resume"
   </dest>
   <voice>
-    *: ""
+    *: "Nothing to resume"
   </voice>
 </phrase>
 <phrase>
@@ -7421,7 +7421,7 @@
     *: "Error updating playlist control file"
   </dest>
   <voice>
-    *: ""
+    *: "Error updating playlist control file"
   </voice>
 </phrase>
 <phrase>
@@ -7435,7 +7435,7 @@
     *: "Error accessing playlist file"
   </dest>
   <voice>
-    *: ""
+    *: "Error accessing palylist file"
   </voice>
 </phrase>
 <phrase>
@@ -7449,7 +7449,7 @@
     *: "Error accessing playlist control file"
   </dest>
   <voice>
-    *: ""
+    *: "Error accessing playlist control file"
   </voice>
 </phrase>
 <phrase>
@@ -7463,7 +7463,7 @@
     *: "Error accessing directory"
   </dest>
   <voice>
-    *: ""
+    *: "Error accessing directory"
   </voice>
 </phrase>
 <phrase>
@@ -7477,7 +7477,7 @@
     *: "Playlist control file is invalid"
   </dest>
   <voice>
-    *: ""
+    *: "Playlist control file is invalid"
   </voice>
 </phrase>
 <phrase>
@@ -7841,7 +7841,7 @@
     *: "Dir Buffer is Full!"
   </dest>
   <voice>
-    *: ""
+    *: "Directory Buffer is Full!"
   </voice>
 </phrase>
 <phrase>
@@ -7855,7 +7855,7 @@
     *: "New Language"
   </dest>
   <voice>
-    *: ""
+    *: "New Language"
   </voice>
 </phrase>
 <phrase>
@@ -7869,7 +7869,7 @@
     *: "Settings Loaded"
   </dest>
   <voice>
-    *: ""
+    *: "Settings Loaded"
   </voice>
 </phrase>
 <phrase>
@@ -7883,7 +7883,7 @@
     *: "Settings Saved"
   </dest>
   <voice>
-    *: ""
+    *: "Settings Saved"
   </voice>
 </phrase>
 <phrase>
@@ -7961,7 +7961,7 @@
     *: "No files"
   </dest>
   <voice>
-    *: ""
+    *: "No files"
   </voice>
 </phrase>
 <phrase>
@@ -8003,7 +8003,7 @@
     *: "New Keyboard"
   </dest>
   <voice>
-    *: ""
+    *: "New Keyboard"
   </voice>
 </phrase>
 <phrase>
@@ -8171,7 +8171,7 @@
     *: "Move Failed"
   </dest>
   <voice>
-    *: ""
+    *: "Move Failed"
   </voice>
 </phrase>
 <phrase>
@@ -8339,7 +8339,7 @@
     *: "Extension array full"
   </dest>
   <voice>
-    *: ""
+    *: "Extension array full"
   </voice>
 </phrase>
 <phrase>
@@ -8353,7 +8353,7 @@
     *: "Filetype array full"
   </dest>
   <voice>
-    *: ""
+    *: "Filetype array full"
   </voice>
 </phrase>
 <phrase>
@@ -8367,7 +8367,7 @@
     *: "Plugin name too long"
   </dest>
   <voice>
-    *: ""
+    *: "Plugin name too long"
   </voice>
 </phrase>
 <phrase>
@@ -8381,7 +8381,7 @@
     *: "Filetype string buffer empty"
   </dest>
   <voice>
-    *: ""
+    *: "Filetype string buffer empty"
   </voice>
 </phrase>
 <phrase>
@@ -8726,7 +8726,7 @@
     *: "%s doesn't exist"
   </dest>
   <voice>
-    *: ""
+    *: "Playlist directory doesn't exist"
   </voice>
 </phrase>
 <phrase>
@@ -8740,7 +8740,7 @@
     *: "No Playlists"
   </dest>
   <voice>
-    *: ""
+    *: "No Playlists"
   </voice>
 </phrase>
 <phrase>
@@ -10052,7 +10052,7 @@
     *: "Please reboot to enable"
   </dest>
   <voice>
-    *: ""
+    *: "Please Reboot to enable the cache"
   </voice>
 </phrase>
 <phrase>
@@ -10272,7 +10272,7 @@
     *: "WARNING! Low Battery!"
   </dest>
   <voice>
-    *: ""
+    *: "WARNING! Low Battery!"
   </voice>
 </phrase>
 <phrase>
@@ -10286,7 +10286,7 @@
     *: "Battery empty! RECHARGE!"
   </dest>
   <voice>
-    *: ""
+    *: "Battery empty! RECHARGE!"
   </voice>
 </phrase>
 <phrase>
@@ -10725,3 +10725,31 @@
     *: ""
   </voice>
 </phrase>
+<phrase>
+  id: LANG_EQUALIZER_BAND_PEAK2
+  desc: in the equalizer settings menu
+  user:
+  <source>
+    *: "Peak Filter 2"
+  </source>
+  <dest>
+    *: "Peak Filter 2"
+  </dest>
+  <voice>
+    *: "Peak Filter 2"
+  </voice>
+</phrase>
+<phrase>
+  id: LANG_EQUALIZER_BAND_PEAK3
+  desc: in the equalizer settings menu
+  user:
+  <source>
+    *: "Peak Filter 3"
+  </source>
+  <dest>
+    *: "Peak Filter 3"
+  </dest>
+  <voice>
+    *: "Peak Filter 3"
+  </voice>
+</phrase>
Index: apps/onplay.c
===================================================================
--- apps/onplay.c	(revision 12956)
+++ apps/onplay.c	(working copy)
@@ -168,12 +168,12 @@
 {
     bool new_playlist = !(audio_status() & AUDIO_STATUS_PLAY);
     char *lines[] = {
-        (char *)str(LANG_RECURSE_DIRECTORY_QUESTION),
+        ID2P(LANG_RECURSE_DIRECTORY_QUESTION),
         selected_file
     };
     struct text_message message={lines, 2};
 
-    gui_syncsplash(0, str(LANG_WAIT));
+    gui_syncsplash(0, ID2P(LANG_WAIT));
     
     if (new_playlist)
         playlist_create(NULL, NULL);
@@ -476,11 +476,11 @@
 static bool delete_handler(bool is_dir)
 {
     char *lines[]={
-        (char *)str(LANG_REALLY_DELETE),
+        ID2P(LANG_REALLY_DELETE),
         selected_file
     };
     char *yes_lines[]={
-        (char *)str(LANG_DELETED),
+        ID2P(LANG_DELETED),
         selected_file
     };
 
@@ -546,6 +546,7 @@
             lcd_puts(0,0,str(LANG_RENAME));
             lcd_puts(0,1,str(LANG_FAILED));
             lcd_update();
+            cond_talk_ids_fq(LANG_RENAME, LANG_FAILED);
             sleep(HZ*2);
         }
         else
@@ -575,6 +576,7 @@
 
     rc = mkdir(dirname);
     if (rc < 0) {
+        cond_talk_ids_fq(LANG_CREATE_DIR, LANG_FAILED);
         gui_syncsplash(HZ, (unsigned char *)"%s %s",
                        str(LANG_CREATE_DIR), str(LANG_FAILED));
     } else {
@@ -794,7 +796,7 @@
     bool success;
     int target_fd;
 
-    unsigned char *lines[]={str(LANG_REALLY_OVERWRITE)};
+    unsigned char *lines[]={ID2P(LANG_REALLY_OVERWRITE)};
     struct text_message message={(char **)lines, 1};
 
     /* Get the name of the current directory */
@@ -849,6 +851,7 @@
         /* Force reload of the current directory */
         onplay_result = ONPLAY_RELOAD_DIR;
     } else {
+        cond_talk_ids_fq(LANG_PASTE, LANG_FAILED);
         gui_syncsplash(HZ, (unsigned char *)"%s %s",
                str(LANG_PASTE), str(LANG_FAILED));
     }
Index: apps/gui/splash.c
===================================================================
--- apps/gui/splash.c	(revision 12956)
+++ apps/gui/splash.c	(working copy)
@@ -22,6 +22,9 @@
 #include "stdio.h"
 #include "kernel.h"
 #include "screen_access.h"
+#include "lang.h"
+#include "settings.h"
+#include "talk.h"
 
 #ifndef MAX
 #define MAX(a, b) (((a)>(b))?(a):(b))
@@ -199,6 +202,15 @@
 {
     va_list ap;
     int i;
+    long id;
+    /* fmt may be a so called virtual pointer. See settings.h. */
+    if((id = P2ID(fmt)) >= 0)
+        /* If fmt specifies a voicefont ID, and voice menus are
+           enabled, then speak it. */
+        cond_talk_ids_fq(id);
+    /* If fmt is a lang ID then get the corresponding string (which
+       still might contain % place holders). */
+    fmt = P2STR(fmt);
     va_start( ap, fmt );
     FOR_NB_SCREENS(i)
         splash(&(screens[i]), fmt, ap);
Index: apps/gui/yesno.c
===================================================================
--- apps/gui/yesno.c	(revision 12956)
+++ apps/gui/yesno.c	(working copy)
@@ -23,6 +23,7 @@
 #include "misc.h"
 #include "lang.h"
 #include "action.h"
+#include "talk.h"
 
 /*
  * Initializes the yesno asker
@@ -95,7 +96,28 @@
     gui_textarea_put_message(yn->display, message, 0);
     return(true);
 }
+
 #include "debug.h"
+
+/* Processes a text_message whose lines may be virtual pointers
+   representing language / voicefont IDs (see settings.h). Copies out
+   the IDs to the ids array, which is of length maxlen, and replaces
+   the pointers in the text_message with the actual language strings.
+   The ids array is terminated with the TALK_FINAL_ID sentinel
+   element. */
+static void extract_talk_ids(struct text_message *m, long *ids, int maxlen)
+{
+    int line, i=0;
+    if(m)
+        for(line=0; line<m->nb_lines; line++) {
+            long id = P2ID((unsigned char *)m->message_lines[line]);
+            if(id>=0 && i<maxlen-1)
+                ids[i++] = id;
+            m->message_lines[line] = (char *)P2STR((unsigned char *)m->message_lines[line]);
+        }
+    ids[i] = TALK_FINAL_ID;
+}
+
 enum yesno_res gui_syncyesno_run(struct text_message * main_message,
                                  struct text_message * yes_message,
                                  struct text_message * no_message)
@@ -105,6 +127,13 @@
     int result=-1;
     bool result_displayed;
     struct gui_yesno yn[NB_SCREENS];
+    long voice_ids[5];
+    long talked_tick = 0;
+    /* The text messages may contain virtual pointers to IDs (see
+       settings.h) instead of plain strings. Copy the IDs out so we
+       can speak them, and unwrap the actual language strings. */
+    extract_talk_ids(main_message, voice_ids,
+                     sizeof(voice_ids)/sizeof(voice_ids[0]));
     FOR_NB_SCREENS(i)
     {
         gui_yesno_init(&(yn[i]), main_message, yes_message, no_message);
@@ -114,7 +143,14 @@
     action_signalscreenchange();
     while (result==-1)
     {
-        button = get_action(CONTEXT_YESNOSCREEN,TIMEOUT_BLOCK);
+        /* Repeat the question every 5secs (more or less) */
+        if (global_settings.talk_menu
+            && (talked_tick==0 || TIME_AFTER(current_tick, talked_tick+HZ*5)))
+        {
+            talked_tick = current_tick;
+            talk_idarray(voice_ids, false);
+        }
+        button = get_action(CONTEXT_YESNOSCREEN, HZ*5);
         switch (button)
         {
             case ACTION_YESNO_ACCEPT:
@@ -131,6 +167,13 @@
     action_signalscreenchange();
     FOR_NB_SCREENS(i)
         result_displayed=gui_yesno_draw_result(&(yn[i]), result);
+    extract_talk_ids((result == YESNO_YES) ? yes_message : no_message,
+                     voice_ids, sizeof(voice_ids)/sizeof(voice_ids[0]));
+    if (global_settings.talk_menu)
+    {
+        talk_idarray(voice_ids, false);
+        talk_force_enqueue_next();
+    }
     if(result_displayed)
         sleep(HZ);
     return(result);
Index: apps/menus/recording_menu.c
===================================================================
--- apps/menus/recording_menu.c	(revision 12956)
+++ apps/menus/recording_menu.c	(working copy)
@@ -639,7 +639,7 @@
 
         switch (button) {
             case ACTION_STD_CANCEL:
-                gui_syncsplash(50, str(LANG_MENU_SETTING_CANCEL));
+                gui_syncsplash(50, ID2P(LANG_MENU_SETTING_CANCEL));
                 global_settings.rec_start_thres = old_start_thres;
                 global_settings.rec_start_duration = old_start_duration;
                 global_settings.rec_prerecord_time = old_prerecord_time;
Index: apps/menus/settings_menu.c
===================================================================
--- apps/menus/settings_menu.c	(revision 12956)
+++ apps/menus/settings_menu.c	(working copy)
@@ -49,13 +49,13 @@
 static void tagcache_rebuild_with_splash(void)
 {
     tagcache_rebuild();
-    gui_syncsplash(HZ*2, str(LANG_TAGCACHE_FORCE_UPDATE_SPLASH));   
+    gui_syncsplash(HZ*2, ID2P(LANG_TAGCACHE_FORCE_UPDATE_SPLASH));   
 }
 
 static void tagcache_update_with_splash(void)
 {
     tagcache_update();
-    gui_syncsplash(HZ*2, str(LANG_TAGCACHE_FORCE_UPDATE_SPLASH));
+    gui_syncsplash(HZ*2, ID2P(LANG_TAGCACHE_FORCE_UPDATE_SPLASH));
 }
 
 #ifdef HAVE_TC_RAMCACHE
@@ -167,7 +167,7 @@
             {
                 case true:
                     if (!dircache_is_enabled())
-                        gui_syncsplash(HZ*2, str(LANG_PLEASE_REBOOT));
+                        gui_syncsplash(HZ*2, ID2P(LANG_PLEASE_REBOOT));
                     break;
                 case false:
                     if (dircache_is_enabled())
Index: apps/menus/eq_menu.c
===================================================================
--- apps/menus/eq_menu.c	(revision 12956)
+++ apps/menus/eq_menu.c	(working copy)
@@ -161,46 +161,14 @@
 MAKE_MENU(gain_menu, ID2P(LANG_EQUALIZER_GAIN), NULL, Icon_NOICON, &gain_item_0, 
             &gain_item_1, &gain_item_2, &gain_item_3, &gain_item_4);
 
-static const struct menu_item_ex *band_items[3][3] = {
-    { &cutoff_1, &q_1, &gain_1 },
-    { &cutoff_2, &q_2, &gain_2 },
-    { &cutoff_3, &q_3, &gain_3 }
-};
-char* centerband_get_name(int selected_item, void * data, char *buffer)
-{
-    (void)selected_item;
-    int band = (intptr_t)data;
-    snprintf(buffer, MAX_PATH, str(LANG_EQUALIZER_BAND_PEAK), band);
-    return buffer;
-}
-int do_center_band_menu(void* param)
-{
-    int band = (intptr_t)param;
-    struct menu_item_ex menu;
-    struct menu_callback_with_desc cb_and_desc;
-    char desc[MAX_PATH];
-    
-    cb_and_desc.menu_callback = NULL;
-    snprintf(desc, MAX_PATH, str(LANG_EQUALIZER_BAND_PEAK), band);
-    cb_and_desc.desc = desc;
-    cb_and_desc.icon_id = Icon_EQ;
-    menu.flags = MT_MENU|(3<<MENU_COUNT_SHIFT)|MENU_HAS_DESC;
-    menu.submenus = band_items[band-1];
-    menu.callback_and_desc = &cb_and_desc;
-    do_menu(&menu, NULL);
-    return 0;
-}
 MAKE_MENU(band_0_menu, ID2P(LANG_EQUALIZER_BAND_LOW_SHELF), NULL, 
             Icon_EQ, &cutoff_0, &q_0, &gain_0);
-MENUITEM_FUNCTION_DYNTEXT(band_1_menu, MENU_FUNC_USEPARAM, 
-                            do_center_band_menu, (void*)1, 
-                            centerband_get_name, (void*)1, NULL, Icon_EQ);
-MENUITEM_FUNCTION_DYNTEXT(band_2_menu, MENU_FUNC_USEPARAM, 
-                            do_center_band_menu, (void*)2, 
-                            centerband_get_name, (void*)2, NULL, Icon_EQ);
-MENUITEM_FUNCTION_DYNTEXT(band_3_menu, MENU_FUNC_USEPARAM, 
-                            do_center_band_menu, (void*)3, 
-                            centerband_get_name, (void*)3, NULL, Icon_EQ);
+MAKE_MENU(band_1_menu, ID2P(LANG_EQUALIZER_BAND_PEAK1), NULL, 
+            Icon_EQ, &cutoff_1, &q_1, &gain_1);
+MAKE_MENU(band_2_menu, ID2P(LANG_EQUALIZER_BAND_PEAK2), NULL, 
+            Icon_EQ, &cutoff_2, &q_2, &gain_2);
+MAKE_MENU(band_3_menu, ID2P(LANG_EQUALIZER_BAND_PEAK3), NULL, 
+            Icon_EQ, &cutoff_3, &q_3, &gain_3);
 MAKE_MENU(band_4_menu, ID2P(LANG_EQUALIZER_BAND_HIGH_SHELF), NULL, 
             Icon_EQ, &cutoff_4, &q_4, &gain_4);
 
Index: apps/playlist_viewer.c
===================================================================
--- apps/playlist_viewer.c	(revision 12956)
+++ apps/playlist_viewer.c	(working copy)
@@ -635,6 +635,7 @@
         if (!viewer.playlist && !(audio_status() & AUDIO_STATUS_PLAY))
         {
             /* Play has stopped */
+            cond_talk_ids_fq(LANG_END_PLAYLIST_RECORDER);
 #ifdef HAVE_LCD_CHARCELLS
             gui_syncsplash(HZ, str(LANG_END_PLAYLIST_PLAYER));
 #else
@@ -696,7 +697,7 @@
                     ret = playlist_move(viewer.playlist, viewer.move_track,
                         current_track->index);
                     if (ret < 0)
-                        gui_syncsplash(HZ, str(LANG_MOVE_FAILED));
+                        gui_syncsplash(HZ, ID2P(LANG_MOVE_FAILED));
 
                     update_playlist(true);
                     viewer.move_track = -1;
Index: apps/talk.c
===================================================================
--- apps/talk.c	(revision 12956)
+++ apps/talk.c	(working copy)
@@ -116,6 +116,8 @@
 static struct voicefile* p_voicefile; /* loaded voicefile */
 static bool has_voicefile; /* a voicefile file is present */
 static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */
+/* enqueue next utterance even if enqueue is false. */
+static bool force_enqueue_next;
 static int queue_write; /* write index of queue, by application */
 static int queue_read; /* read index of queue, by ISR context */
 static int sent; /* how many bytes handed over to playback, owned by ISR */
@@ -132,7 +134,6 @@
 
 static void load_voicefile(void);
 static void mp3_callback(unsigned char** start, int* size);
-static int shutup(void);
 static int queue_clip(unsigned char* buf, long size, bool enqueue);
 static int open_voicefile(void);
 static unsigned char* get_clip(long id, long* p_size);
@@ -260,6 +261,13 @@
 }
 
 
+/* Are more voice clips queued and waiting? */
+bool is_voice_queued()
+{
+    return !!QUEUE_LEVEL;
+}
+
+
 /* called in ISR context if mp3 data got consumed */
 static void mp3_callback(unsigned char** start, int* size)
 {
@@ -314,7 +322,7 @@
 }
 
 /* stop the playback and the pending clips */
-static int shutup(void)
+int do_shutup(void)
 {
 #if CONFIG_CODEC != SWCODEC
     unsigned char* pos;
@@ -380,6 +388,13 @@
     return 0;
 }
 
+/* Shutup the voice, except if force_enqueue_next is set. */
+int shutup(void)
+{
+    if (!force_enqueue_next)
+        return do_shutup();
+    return 0;
+}
 
 /* schedule a clip, at the end or discard the existing queue */
 static int queue_clip(unsigned char* buf, long size, bool enqueue)
@@ -388,6 +403,9 @@
 
     if (!enqueue)
         shutup(); /* cut off all the pending stuff */
+    /* Something is being enqueued, force_enqueue_next override is no
+       longer in effect. */
+    force_enqueue_next = false;
     
     if (!size)
         return 0; /* safety check */
@@ -609,7 +627,27 @@
     return 0;
 }
 
+/* Speaks zero or more IDs (from an array). */
+int talk_idarray(long *ids, bool enqueue)
+{
+    int r;
+    if(!ids)
+        return 0;
+    while(*ids != TALK_FINAL_ID)
+    {
+        if((r = talk_id(*ids++, enqueue)) <0)
+            return r;
+        enqueue = true;
+    }
+    return 0;
+}
 
+/* Make sure the current utterance is not interrupted by the next one. */
+void talk_force_enqueue_next(void)
+{
+    force_enqueue_next = true;
+}
+
 /* play a thumbnail from file */
 int talk_file(const char* filename, bool enqueue)
 {
Index: apps/filetree.c
===================================================================
--- apps/filetree.c	(revision 12956)
+++ apps/filetree.c	(working copy)
@@ -363,21 +363,21 @@
         switch ( file->attr & TREE_ATTR_MASK ) {
             case TREE_ATTR_M3U:
                 if (global_settings.party_mode) {
-                    gui_syncsplash(HZ, str(LANG_PARTY_MODE));
+                    gui_syncsplash(HZ, ID2P(LANG_PARTY_MODE));
                     break;
                 }
 
                 if (bookmark_autoload(buf))
                     break;
 
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
 
                 /* about to create a new current playlist...
                    allow user to cancel the operation */
                 if (global_settings.warnon_erase_dynplaylist &&
                     playlist_modified(NULL))
                 {
-                    char *lines[]={str(LANG_WARN_ERASEDYNPLAYLIST_PROMPT)};
+                    char *lines[]={ID2P(LANG_WARN_ERASEDYNPLAYLIST_PROMPT)};
                     struct text_message message={lines, 1};
 
                     if(gui_syncyesno_run(&message, NULL, NULL) != YESNO_YES)
@@ -398,7 +398,7 @@
                 if (bookmark_autoload(c->currdir))
                     break;
 
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
 
                 /* about to create a new current playlist...
                    allow user to cancel the operation */
@@ -406,7 +406,7 @@
                     !global_settings.party_mode &&
                     playlist_modified(NULL))
                 {
-                    char *lines[]={str(LANG_WARN_ERASEDYNPLAYLIST_PROMPT)};
+                    char *lines[]={ID2P(LANG_WARN_ERASEDYNPLAYLIST_PROMPT)};
                     struct text_message message={lines, 1};
 
                     if(gui_syncyesno_run(&message, NULL, NULL) != YESNO_YES)
@@ -417,7 +417,7 @@
                 {
                     playlist_insert_track(NULL, buf,
                                           PLAYLIST_INSERT_LAST, true, true);
-                    gui_syncsplash(HZ, str(LANG_QUEUE_LAST));
+                    gui_syncsplash(HZ, ID2P(LANG_QUEUE_LAST));
                 }
                 else if (playlist_create(c->currdir, NULL) != -1)
                 {
@@ -442,7 +442,7 @@
                 /* fmr preset file */
             case TREE_ATTR_FMR:
 
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
 
                 /* Preset inside the default folder. */
                 if(!strncasecmp(FMPRESET_PATH, buf, strlen(FMPRESET_PATH)))
@@ -469,7 +469,7 @@
 
                 /* wps config file */
             case TREE_ATTR_WPS:
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
 #if LCD_DEPTH > 1
                 unload_wps_backdrop();
 #endif
@@ -481,7 +481,7 @@
 #if defined(HAVE_REMOTE_LCD) && (NB_SCREENS > 1)
                 /* remote-wps config file */
             case TREE_ATTR_RWPS:
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
                 wps_data_load(gui_wps[1].data, buf, true);
                 set_file(buf, (char *)global_settings.rwps_file,
                          MAX_FILENAME);
@@ -489,39 +489,39 @@
 #endif
 
             case TREE_ATTR_CFG:
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
                 if (!settings_load_config(buf,true))
                     break;
-                gui_syncsplash(HZ, str(LANG_SETTINGS_LOADED));
+                gui_syncsplash(HZ, ID2P(LANG_SETTINGS_LOADED));
                 break;
 
             case TREE_ATTR_BMARK:
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
                 bookmark_load(buf, false);
                 reload_dir = true;
                 break;
 
             case TREE_ATTR_LNG:
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
                 if(!lang_load(buf)) {
                     set_file(buf, (char *)global_settings.lang_file,
                              MAX_FILENAME);
                     talk_init(); /* use voice of same language */
-                    gui_syncsplash(HZ, str(LANG_LANGUAGE_LOADED));
+                    gui_syncsplash(HZ, ID2P(LANG_LANGUAGE_LOADED));
                 }
                 break;
 
 #ifdef HAVE_LCD_BITMAP
             case TREE_ATTR_FONT:
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
                 font_load(buf);
                 set_file(buf, (char *)global_settings.font_file, MAX_FILENAME);
                 break;
 
             case TREE_ATTR_KBD:
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
                 if (!load_kbd(buf))
-                    gui_syncsplash(HZ, str(LANG_KEYBOARD_LOADED));
+                    gui_syncsplash(HZ, ID2P(LANG_KEYBOARD_LOADED));
                 set_file(buf, (char *)global_settings.kbd_file, MAX_FILENAME);
                 break;
 #endif
@@ -529,7 +529,7 @@
 #ifndef SIMULATOR
                 /* firmware file */
             case TREE_ATTR_MOD:
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
                 rolo_load(buf);
                 break;
 #endif
@@ -537,11 +537,11 @@
                 /* plugin file */
             case TREE_ATTR_ROCK:
                 if (global_settings.party_mode) {
-                    gui_syncsplash(HZ, str(LANG_PARTY_MODE));
+                    gui_syncsplash(HZ, ID2P(LANG_PARTY_MODE));
                     break;
                 }
 
-                gui_syncsplash(0, str(LANG_WAIT));
+                gui_syncsplash(0, ID2P(LANG_WAIT));
 
                 if (plugin_load(buf,NULL) == PLUGIN_USB_CONNECTED)
                 {
@@ -563,7 +563,7 @@
                 char* plugin;
 
                 if (global_settings.party_mode) {
-                    gui_syncsplash(HZ, str(LANG_PARTY_MODE));
+                    gui_syncsplash(HZ, ID2P(LANG_PARTY_MODE));
                     break;
                 }
 
Index: apps/talk.h
===================================================================
--- apps/talk.h	(revision 12956)
+++ apps/talk.h	(working copy)
@@ -66,10 +66,46 @@
 int talk_get_bufsize(void); /* get the loaded voice file size */
 /* talk_buffer_steal - on SWCODEC, for use by buffer functions only */
 int talk_buffer_steal(void); /* claim the mp3 buffer e.g. for play/record */
+int shutup(void); /* Interrupt voice, as when enqueue is false */
+int do_shutup(void); /* kill voice unconditionally */
+bool is_voice_queued(void); /* Are there more voice clips to be spoken? */
 int talk_id(long id, bool enqueue); /* play a voice ID from voicefont */
 int talk_file(const char* filename, bool enqueue); /* play a thumbnail from file */
 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 */
 
+/* Enqueue next utterance even if enqueue parameter is false: don't
+   interrupt the current utterance. */
+void talk_force_enqueue_next(void);
+
+/* speaks one or more IDs (from an array)). */
+int talk_idarray(long *idarray, bool enqueue);
+/* This (otherwise invalid) ID signals the end of the array. */
+#define TALK_FINAL_ID LANG_LAST_INDEX_IN_ARRAY
+/* This makes an initializer for the array of IDs and takes care to
+   put the final sentinel element at the end. */
+#define TALK_IDARRAY(ids...) ((long[]){ids,TALK_FINAL_ID})
+/* And this handy macro makes it look like a variadic function. */
+#define talk_ids(enqueue, ids...) talk_idarray(TALK_IDARRAY(ids), enqueue)
+/* This version talks only if talking menus are enabled, and does not
+   enqueue the initial id. */
+#define cond_talk_ids(ids...) do { \
+        if (global_settings.talk_menu) \
+            talk_ids(false, ids); \
+    } while(0)
+/* And a version that takes the array parameter... */
+#define cond_talk_idarray(idarray) do { \
+        if (global_settings.talk_menu) \
+            talk_idarray(idarray, false); \
+    } while(0)
+/* Convenience macro to conditionally speak something and not have
+   it interrupted. */
+#define cond_talk_ids_fq(ids...) do { \
+        if (global_settings.talk_menu) { \
+            talk_ids(false, ids);                 \
+            talk_force_enqueue_next(); \
+        } \
+    }while(0)
+
 #endif /* __TALK_H__ */
Index: apps/playback.c
===================================================================
--- apps/playback.c	(revision 12956)
+++ apps/playback.c	(working copy)
@@ -971,6 +971,24 @@
         pcmbuf_play_stop();
 #endif
 } /* voice_stop */
+
+/* Is voice still speaking */
+/* Unfortunately only reliable when music is not also playing. */
+bool is_voice_speaking(void)
+{
+    return is_voice_queued()
+        || voice_is_playing
+        || (!playing && pcm_is_playing());
+}
+
+/* Wait for voice to finish speaking. */
+/* Also only reliable when music is not also playing. */
+void voice_wait(void)
+{
+    while (is_voice_speaking())
+        sleep(1);
+}
+
 #endif /* PLAYBACK_VOICE */
 
 static void set_filebuf_watermark(int seconds)
Index: apps/playback.h
===================================================================
--- apps/playback.h	(revision 12956)
+++ apps/playback.h	(working copy)
@@ -64,6 +64,8 @@
                                                   bool last_track));
 void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3,
                                                    bool last_track));
+bool is_voice_speaking(void);
+void voice_wait(void);
 
 #if CONFIG_CODEC == SWCODEC /* This #ifdef is better here than gui/gwps.c */
 extern void audio_next_dir(void);
Index: apps/plugin.c
===================================================================
--- apps/plugin.c	(revision 12956)
+++ apps/plugin.c	(working copy)
@@ -530,7 +530,7 @@
         plugin_loaded = false;
     }
     
-    gui_syncsplash(0, str(LANG_WAIT));
+    gui_syncsplash(0, ID2P(LANG_WAIT));
     strcpy(current_plugin,p);
 
 #ifdef SIMULATOR
Index: apps/filetypes.c
===================================================================
--- apps/filetypes.c	(revision 12956)
+++ apps/filetypes.c	(working copy)
@@ -340,14 +340,14 @@
         /* exttypes[] full, bail out */
         if (cnt_exttypes >= MAX_EXTTYPES)
         {
-            gui_syncsplash(HZ, str(LANG_FILETYPES_EXTENSION_FULL));
+            gui_syncsplash(HZ, ID2P(LANG_FILETYPES_EXTENSION_FULL));
             break;
         }
 
         /* filetypes[] full, bail out */
         if (cnt_filetypes >= MAX_FILETYPES)
         {
-            gui_syncsplash(HZ, str(LANG_FILETYPES_FULL));
+            gui_syncsplash(HZ, ID2P(LANG_FILETYPES_FULL));
             break;
         }
 
@@ -380,7 +380,7 @@
         /* filter out to long filenames */
         if (strlen((char *)entry->d_name) > MAX_PLUGIN_LENGTH + 5)
         {
-            gui_syncsplash(HZ, str(LANG_FILETYPES_PLUGIN_NAME_LONG));
+            gui_syncsplash(HZ, ID2P(LANG_FILETYPES_PLUGIN_NAME_LONG));
             continue;
         }
 
@@ -561,13 +561,13 @@
     {
         if (cnt_exttypes >= MAX_EXTTYPES)
         {
-            gui_syncsplash(HZ, str(LANG_FILETYPES_EXTENSION_FULL));
+            gui_syncsplash(HZ, ID2P(LANG_FILETYPES_EXTENSION_FULL));
             break;
         }
 
         if (cnt_filetypes >= MAX_FILETYPES)
         {
-            gui_syncsplash(HZ, str(LANG_FILETYPES_FULL));
+            gui_syncsplash(HZ, ID2P(LANG_FILETYPES_FULL));
             break;
         }
 
@@ -636,7 +636,7 @@
         {
             if (strlen(str[plugin]) > MAX_PLUGIN_LENGTH)
             {
-                gui_syncsplash(HZ, str(LANG_FILETYPES_PLUGIN_NAME_LONG));
+                gui_syncsplash(HZ, ID2P(LANG_FILETYPES_PLUGIN_NAME_LONG));
                 str[plugin] = NULL;
                 continue;
             }
@@ -736,7 +736,7 @@
          (unsigned long) string_buffer -
          (unsigned long) next_free_string) < ICON_LENGTH)
     {
-        gui_syncsplash(HZ, str(LANG_FILETYPES_STRING_BUFFER_EMPTY));
+        gui_syncsplash(HZ, ID2P(LANG_FILETYPES_STRING_BUFFER_EMPTY));
         return NULL;
     }
 
@@ -792,7 +792,7 @@
     }
     else
     {
-        gui_syncsplash(HZ, str(LANG_FILETYPES_STRING_BUFFER_EMPTY));
+        gui_syncsplash(HZ, ID2P(LANG_FILETYPES_STRING_BUFFER_EMPTY));
         return NULL;
     }
 }
Index: apps/bookmark.c
===================================================================
--- apps/bookmark.c	(revision 12956)
+++ apps/bookmark.c	(working copy)
@@ -171,10 +171,10 @@
             return write_bookmark(false);
     }
 #ifdef HAVE_LCD_BITMAP
-    unsigned char *lines[]={str(LANG_AUTO_BOOKMARK_QUERY)};
+    unsigned char *lines[]={ID2P(LANG_AUTO_BOOKMARK_QUERY)};
     struct text_message message={(char **)lines, 1};
 #else
-    unsigned char *lines[]={str(LANG_AUTO_BOOKMARK_QUERY),
+    unsigned char *lines[]={ID2P(LANG_AUTO_BOOKMARK_QUERY),
                             str(LANG_RESUME_CONFIRM_PLAYER)};
     struct text_message message={(char **)lines, 2};
 #endif
@@ -226,8 +226,8 @@
         }
     }
 
-    gui_syncsplash(HZ, str(success ? LANG_BOOKMARK_CREATE_SUCCESS
-        : LANG_BOOKMARK_CREATE_FAILURE));
+    gui_syncsplash(HZ, success ? ID2P(LANG_BOOKMARK_CREATE_SUCCESS)
+        : ID2P(LANG_BOOKMARK_CREATE_FAILURE));
 
     return true;
 }
@@ -403,6 +403,7 @@
             screens[i].puts(0,1,str(LANG_RESUME_CONFIRM_PLAYER));
 #endif
         }
+        cond_talk_ids(LANG_BOOKMARK_AUTOLOAD_QUERY);
 
         /* Wait for a key to be pushed */
         key = get_action(CONTEXT_BOOKMARKSCREEN,TIMEOUT_BLOCK);
@@ -520,7 +521,7 @@
             /* if there were no bookmarks in the file, delete the file and exit. */
             if(bookmark_id <= 0)
             {
-                gui_syncsplash(HZ, str(LANG_BOOKMARK_LOAD_EMPTY));
+                gui_syncsplash(HZ, ID2P(LANG_BOOKMARK_LOAD_EMPTY));
                 remove(bookmark_file_name);
                 action_signalscreenchange();
                 return NULL;
Index: apps/root_menu.c
===================================================================
--- apps/root_menu.c	(revision 12956)
+++ apps/root_menu.c	(working copy)
@@ -231,7 +231,7 @@
     }
     else
     {
-        gui_syncsplash(HZ*2, str(LANG_NOTHING_TO_RESUME));
+        gui_syncsplash(HZ*2, ID2P(LANG_NOTHING_TO_RESUME));
         if (last_screen == GO_TO_WPS)
             ret_val = GO_TO_ROOT;
     }
Index: apps/misc.c
===================================================================
--- apps/misc.c	(revision 12956)
+++ apps/misc.c	(working copy)
@@ -60,6 +60,7 @@
 #include "gui/gwps-common.h"
 
 #include "misc.h"
+#include "playback.h"
 
 /* Format a large-range value for output, using the appropriate unit so that
  * the displayed value is in the range 1 <= display < 1000 (1024 for "binary"
@@ -608,23 +609,27 @@
         x5_backlight_shutdown();
 #endif
         if (!battery_level_safe())
+        {
+            cond_talk_ids(LANG_WARNING_BATTERY_EMPTY, LANG_SHUTTINGDOWN);
             gui_syncsplash(3*HZ, "%s %s",
                            str(LANG_WARNING_BATTERY_EMPTY),
                            str(LANG_SHUTTINGDOWN));
-        else if (battery_level_critical())
+        } else if (battery_level_critical())
+        {
+            cond_talk_ids(LANG_WARNING_BATTERY_LOW, LANG_SHUTTINGDOWN);
             gui_syncsplash(3*HZ, "%s %s",
                            str(LANG_WARNING_BATTERY_LOW),
                            str(LANG_SHUTTINGDOWN));
-        else {
+        } else {
 #ifdef HAVE_TAGCACHE
             if (!tagcache_prepare_shutdown())
             {
                 cancel_shutdown();
-                gui_syncsplash(HZ, str(LANG_TAGCACHE_BUSY));
+                gui_syncsplash(HZ, ID2P(LANG_TAGCACHE_BUSY));
                 return false;
             }
 #endif
-            gui_syncsplash(0, str(LANG_SHUTTINGDOWN));
+            gui_syncsplash(0, ID2P(LANG_SHUTTINGDOWN));
         }
         
         if (global_settings.fade_on_stop 
@@ -648,7 +653,11 @@
         audio_stop();
         while (audio_status())
             sleep(1);
-        
+
+#if CONFIG_CODEC == SWCODEC
+        voice_wait();
+#endif
+
         if (callback != NULL)
             callback(parameter);
 
Index: apps/playlist_catalog.c
===================================================================
--- apps/playlist_catalog.c	(revision 12956)
+++ apps/playlist_catalog.c	(working copy)
@@ -37,6 +37,7 @@
 #include "sprintf.h"
 #include "tree.h"
 #include "yesno.h"
+#include "talk.h"
 
 #define PLAYLIST_CATALOG_CFG ROCKBOX_DIR "/playlist_catalog.config"
 #define PLAYLIST_CATALOG_DEFAULT_DIR "/Playlists"
@@ -112,7 +113,7 @@
     if (!playlist_dir_exists)
     {
         if (mkdir(playlist_dir) < 0) {
-            gui_syncsplash(HZ*2, str(LANG_CATALOG_NO_DIRECTORY),
+            gui_syncsplash(HZ*2, ID2P(LANG_CATALOG_NO_DIRECTORY),
                 playlist_dir);
             return -1;
         }
@@ -146,7 +147,7 @@
     
     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;
     }
@@ -231,7 +232,7 @@
 
     if (num_playlists <= 0)
     {
-        gui_syncsplash(HZ*2, str(LANG_CATALOG_NO_PLAYLISTS));
+        gui_syncsplash(HZ*2, ID2P(LANG_CATALOG_NO_PLAYLISTS));
         return -1;
     }
 
@@ -319,6 +320,15 @@
    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,
 #if CONFIG_KEYPAD == PLAYER_PAD
         str(LANG_STOP_ABORT)
@@ -403,7 +413,7 @@
         /* 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: apps/tagtree.c
===================================================================
--- apps/tagtree.c	(revision 12956)
+++ apps/tagtree.c	(working copy)
@@ -682,7 +682,7 @@
     gui_syncsplash(0, str(LANG_CREATING));
     if (!tagcache_create_changelog(&tcs))
     {
-        gui_syncsplash(HZ*2, str(LANG_FAILED));
+        gui_syncsplash(HZ*2, ID2P(LANG_FAILED));
     }
     
     return false;
@@ -690,10 +690,10 @@
 
 bool tagtree_import(void)
 {
-    gui_syncsplash(0, str(LANG_WAIT));
+    gui_syncsplash(0, ID2P(LANG_WAIT));
     if (!tagcache_import_changelog())
     {
-        gui_syncsplash(HZ*2, str(LANG_FAILED));
+        gui_syncsplash(HZ*2, ID2P(LANG_FAILED));
     }
     
     return false;
@@ -1206,7 +1206,7 @@
     
     if (!sort && (sort_inverse || sort_limit))
     {
-        gui_syncsplash(HZ*4, str(LANG_SHOWDIR_BUFFER_FULL), total_count);
+        gui_syncsplash(HZ*4, ID2P(LANG_SHOWDIR_BUFFER_FULL), total_count);
         logf("Too small dir buffer");
         return 0;
     }
@@ -1397,7 +1397,7 @@
                     !global_settings.party_mode &&
                     playlist_modified(NULL))
                 {
-                    char *lines[]={str(LANG_WARN_ERASEDYNPLAYLIST_PROMPT)};
+                    char *lines[]={ID2P(LANG_WARN_ERASEDYNPLAYLIST_PROMPT)};
                     struct text_message message={lines, 1};
                     
                     if (gui_syncyesno_run(&message, NULL, NULL) != YESNO_YES)
@@ -1474,7 +1474,7 @@
     cpu_boost(true);
     if (!tagcache_search(&tcs, tag_filename))
     {
-        gui_syncsplash(HZ, str(LANG_TAGCACHE_BUSY));
+        gui_syncsplash(HZ, ID2P(LANG_TAGCACHE_BUSY));
         cpu_boost(false);
         return false;
     }
@@ -1577,12 +1577,12 @@
     }
 
     if (tc->filesindir <= 0)
-        gui_syncsplash(HZ, str(LANG_END_PLAYLIST_PLAYER));
+        gui_syncsplash(HZ, ID2P(LANG_END_PLAYLIST_PLAYER));
     else
     {
         logf("insert_all_playlist");
         if (!insert_all_playlist(tc, position, queue))
-            gui_syncsplash(HZ*2, str(LANG_FAILED));
+            gui_syncsplash(HZ*2, ID2P(LANG_FAILED));
     }
     
     /* Finally return the dirlevel to its original value. */
