First step of the voice-UI: the menus can talk. You need a "voicefont" file in .rockbox to use this.


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@4381 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/bookmark.c b/apps/bookmark.c
index 51b720b..e832165 100644
--- a/apps/bookmark.c
+++ b/apps/bookmark.c
@@ -93,9 +93,9 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_BOOKMARK_MENU_CREATE), bookmark_create_menu},
-        { str(LANG_BOOKMARK_MENU_LIST), bookmark_load_menu},
-        { str(LANG_BOOKMARK_MENU_RECENT_BOOKMARKS), bookmark_mrb_load},
+        { STR(LANG_BOOKMARK_MENU_CREATE), bookmark_create_menu},
+        { STR(LANG_BOOKMARK_MENU_LIST), bookmark_load_menu},
+        { STR(LANG_BOOKMARK_MENU_RECENT_BOOKMARKS), bookmark_mrb_load},
     };
 
     m=menu_init( items, sizeof items / sizeof(struct menu_items), NULL );
diff --git a/apps/debug_menu.c b/apps/debug_menu.c
index b77014a..d0c575b 100644
--- a/apps/debug_menu.c
+++ b/apps/debug_menu.c
@@ -1476,36 +1476,36 @@
     bool result;
 
     struct menu_items items[] = {
-        { "Dump ROM contents", dbg_save_roms },
-        { "View I/O ports", dbg_ports },
+        { "Dump ROM contents", -1, dbg_save_roms },
+        { "View I/O ports", -1, dbg_ports },
 #ifdef HAVE_LCD_BITMAP
 #ifdef HAVE_RTC
-        { "View/clr RTC RAM", dbg_rtc },
+        { "View/clr RTC RAM", -1, dbg_rtc },
 #endif /* HAVE_RTC */
 #endif /* HAVE_LCD_BITMAP */
-        { "View OS stacks", dbg_os },
+        { "View OS stacks", -1, dbg_os },
 #ifdef HAVE_MAS3507D
-        { "View MAS info", dbg_mas_info },
+        { "View MAS info", -1, dbg_mas_info },
 #endif
-        { "View MAS regs", dbg_mas },
+        { "View MAS regs", -1, dbg_mas },
 #ifdef HAVE_MAS3587F
-        { "View MAS codec", dbg_mas_codec },
+        { "View MAS codec", -1, dbg_mas_codec },
 #endif
 #ifdef HAVE_LCD_BITMAP
-        { "View battery", view_battery },
+        { "View battery", -1, view_battery },
 #endif
-        { "View HW info", dbg_hw_info },
-        { "View partitions", dbg_partitions },
-        { "View disk info", dbg_disk_info },
+        { "View HW info", -1, dbg_hw_info },
+        { "View partitions", -1, dbg_partitions },
+        { "View disk info", -1, dbg_disk_info },
 #ifdef HAVE_LCD_BITMAP
-        { "View mpeg thread", dbg_mpeg_thread },
+        { "View mpeg thread", -1, dbg_mpeg_thread },
 #ifdef PM_DEBUG
-        { "pm histogram", peak_meter_histogram},
+        { "pm histogram", -1, peak_meter_histogram},
 #endif /* PM_DEBUG */
 #endif /* HAVE_LCD_BITMAP */
-        { "View runtime", view_runtime },
+        { "View runtime", -1, view_runtime },
 #ifdef HAVE_FMRADIO
-        { "FM Radio", dbg_fm_radio },
+        { "FM Radio", -1, dbg_fm_radio },
 #endif
     };
 
diff --git a/apps/main.c b/apps/main.c
index d6be594..2c2f9ac 100644
--- a/apps/main.c
+++ b/apps/main.c
@@ -54,6 +54,7 @@
 #include "rolo.h"
 #include "screens.h"
 #include "power.h"
+#include "talk.h"
 
 char appsversion[]=APPSVERSION;
 
@@ -208,6 +209,7 @@
               global_settings.avc,
               global_settings.channel_config );
     mpeg_init();
+    talk_init();
 
     /* no auto-rolo on startup any more, but I leave it here for reference */
 #if 0
diff --git a/apps/main_menu.c b/apps/main_menu.c
index f42655b..ade6e7b 100644
--- a/apps/main_menu.c
+++ b/apps/main_menu.c
@@ -262,8 +262,8 @@
 
     /* recording menu */
     struct menu_items items[] = {
-        { str(LANG_RECORDING_MENU),     recording_screen  },
-        { str(LANG_RECORDING_SETTINGS), recording_settings},
+        { STR(LANG_RECORDING_MENU),     recording_screen  },
+        { STR(LANG_RECORDING_SETTINGS), recording_settings},
     };
 
     m=menu_init( items, sizeof items / sizeof(struct menu_items), NULL );
@@ -281,13 +281,13 @@
 
     /* info menu */
     struct menu_items items[] = {
-        { str(LANG_MENU_SHOW_ID3_INFO), browse_id3        },
-        { str(LANG_INFO_MENU),          show_info         },
-        { str(LANG_VERSION),            show_credits      },
+        { STR(LANG_MENU_SHOW_ID3_INFO), browse_id3        },
+        { STR(LANG_INFO_MENU),          show_info         },
+        { STR(LANG_VERSION),            show_credits      },
 #ifndef SIMULATOR
-        { str(LANG_DEBUG),              debug_menu        },
+        { STR(LANG_DEBUG),              debug_menu        },
 #else
-        { str(LANG_USB),                simulate_usb      },
+        { STR(LANG_USB),                simulate_usb      },
 #endif
     };
 
@@ -308,33 +308,41 @@
     struct menu_items items[8];
 
     items[i].desc = str(LANG_BOOKMARK_MENU);
+    items[i].voice_id = LANG_BOOKMARK_MENU;
     items[i++].function = bookmark_menu;
 
     items[i].desc = str(LANG_SOUND_SETTINGS);
+    items[i].voice_id = LANG_SOUND_SETTINGS;
     items[i++].function = sound_menu;
 
     items[i].desc = str(LANG_GENERAL_SETTINGS);
+    items[i].voice_id = LANG_GENERAL_SETTINGS;
     items[i++].function = settings_menu;
 
 #ifdef HAVE_FMRADIO
     if(radio_hardware_present()) {
         items[i].desc = str(LANG_FM_RADIO);
+        items[i].voice_id = LANG_FM_RADIO;
         items[i++].function = radio_screen;
     }
 #endif
 
 #ifdef HAVE_MAS3587F
     items[i].desc = str(LANG_RECORDING);
+    items[i].voice_id = LANG_RECORDING;
     items[i++].function = rec_menu;
 #endif
 
     items[i].desc = str(LANG_PLAYLIST_MENU);
+    items[i].voice_id = LANG_PLAYLIST_MENU;
     items[i++].function = playlist_menu;
 
     items[i].desc = str(LANG_PLUGINS);
+    items[i].voice_id = LANG_PLUGINS;
     items[i++].function = plugin_browse;
 
     items[i].desc = str(LANG_INFO);
+    items[i].voice_id = LANG_INFO;
     items[i++].function = info_menu;
 
     m=menu_init( items, i, NULL );
diff --git a/apps/menu.c b/apps/menu.c
index f187b5d..f944354 100644
--- a/apps/menu.c
+++ b/apps/menu.c
@@ -32,6 +32,7 @@
 #include "settings.h"
 #include "status.h"
 #include "screens.h"
+#include "talk.h"
 
 #ifdef HAVE_LCD_BITMAP
 #include "icons.h"
@@ -216,6 +217,13 @@
         lcd_update();
     }
 
+    if (do_update)
+    {   /* "say" the entry under the cursor */
+        int voice_id = menus[m].items[menus[m].cursor].voice_id;
+        if (voice_id >= 0) /* valid ID given? */
+            talk_id(voice_id, false); /* say it */
+    }
+
 }
 
 int menu_init(struct menu_items* mitems, int count, int (*callback)(int, int))
@@ -250,6 +258,7 @@
 {
     bool exit = false;
     int key;
+    int voice_id;
 #ifdef HAVE_LCD_BITMAP
     int fw, fh;
     int menu_lines;
@@ -263,9 +272,16 @@
 
     menu_draw(m);
 
+    /* say current entry */
+    voice_id = menus[m].items[menus[m].cursor].voice_id;
+    if (voice_id >= 0) /* valid ID given? */
+        talk_id(voice_id, false); /* say it */
+
     while (!exit) {
         key = button_get_w_tmo(HZ/2);
+
         
+
         /*  
          *   "short-circuit" the default keypresses by running the callback function
          */
@@ -274,6 +290,7 @@
             key = menus[m].callback(key, m);            /* make sure there's no match in the switch */
 
         switch( key ) {
+
 #ifdef HAVE_RECORDER_KEYPAD
             case BUTTON_UP:
             case BUTTON_UP | BUTTON_REPEAT:
diff --git a/apps/menu.h b/apps/menu.h
index 827de1d..dbe5151 100644
--- a/apps/menu.h
+++ b/apps/menu.h
@@ -23,10 +23,14 @@
 #include <stdbool.h>
 
 struct menu_items {
-    unsigned char *desc;
+    unsigned char *desc; /* string */
+    int voice_id; /* the associated voice clip, -1 if none */
     bool (*function) (void); /* return true if USB was connected */
 };
 
+/* convenience macro to have both string and ID as arguments */
+#define STR(id) str(id), id
+
 int menu_init(struct menu_items* items, int count, int (*callback) (int keycode, int menu));
 void menu_exit(int menu);
 
diff --git a/apps/onplay.c b/apps/onplay.c
index e4733fb..9dab34c 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -41,6 +41,7 @@
 #include "settings.h"
 #include "status.h"
 #include "playlist_viewer.h"
+#include "talk.h"
 #include "onplay.h"
 
 static char* selected_file = NULL;
@@ -147,6 +148,7 @@
     if ((selected_file_attr & TREE_ATTR_MASK) == TREE_ATTR_M3U)
     {
         menu[i].desc = str(LANG_VIEW);
+        menu[i].voice_id = LANG_VIEW;
         menu[i].function = view_playlist;
         i++;
         pstart++;
@@ -155,31 +157,37 @@
     if (mpeg_status() & MPEG_STATUS_PLAY)
     {
         menu[i].desc = str(LANG_INSERT);
+        menu[i].voice_id = LANG_INSERT;
         args[i].position = PLAYLIST_INSERT;
         args[i].queue = false;
         i++;
         
         menu[i].desc = str(LANG_INSERT_FIRST);
+        menu[i].voice_id = LANG_INSERT_FIRST;
         args[i].position = PLAYLIST_INSERT_FIRST;
         args[i].queue = false;
         i++;
         
         menu[i].desc = str(LANG_INSERT_LAST);
+        menu[i].voice_id = LANG_INSERT_LAST;
         args[i].position = PLAYLIST_INSERT_LAST;
         args[i].queue = false;
         i++;
         
         menu[i].desc = str(LANG_QUEUE);
+        menu[i].voice_id = LANG_QUEUE;
         args[i].position = PLAYLIST_INSERT;
         args[i].queue = true;
         i++;
         
         menu[i].desc = str(LANG_QUEUE_FIRST);
+        menu[i].voice_id = LANG_QUEUE_FIRST;
         args[i].position = PLAYLIST_INSERT_FIRST;
         args[i].queue = true;
         i++;
         
         menu[i].desc = str(LANG_QUEUE_LAST);
+        menu[i].voice_id = LANG_QUEUE_LAST;
         args[i].position = PLAYLIST_INSERT_LAST;
         args[i].queue = true;
         i++;
@@ -188,6 +196,7 @@
              (selected_file_attr & ATTR_DIRECTORY))
     {
         menu[i].desc = str(LANG_INSERT);
+        menu[i].voice_id = LANG_INSERT;
         args[i].position = PLAYLIST_INSERT;
         args[i].queue = false;
         i++;
@@ -283,6 +292,8 @@
     int orig_fd, fd;
     char tmpname[MAX_PATH];
     
+    talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
+
     snprintf(tmpname, MAX_PATH, "%s.tmp", fname);
 
     orig_fd = open(fname, O_RDONLY);
@@ -384,6 +395,8 @@
         return onplay_result;
     }
     
+    talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
+
     lcd_clear_display();
     lcd_puts_scroll(0, 0, selected_file);
     lcd_update();
@@ -552,17 +565,20 @@
             ((attr & TREE_ATTR_MASK) == TREE_ATTR_M3U))
         {
             menu[i].desc = str(LANG_PLAYINDICES_PLAYLIST);
+            menu[i].voice_id = LANG_PLAYINDICES_PLAYLIST;
             menu[i].function = playlist_options;
             i++;
         }
         
         menu[i].desc = str(LANG_RENAME);
+        menu[i].voice_id = LANG_RENAME;
         menu[i].function = rename_file;
         i++;
         
         if (!(attr & ATTR_DIRECTORY))
         {
             menu[i].desc = str(LANG_DELETE);
+            menu[i].voice_id = LANG_DELETE;
             menu[i].function = delete_file;
             i++;
         }
@@ -570,12 +586,14 @@
         if ((attr & TREE_ATTR_MASK) == TREE_ATTR_MPA)
         {
             menu[i].desc = str(LANG_VBRFIX);
+            menu[i].voice_id = LANG_VBRFIX;
             menu[i].function = vbr_fix;
             i++;
         }
     }
 
     menu[i].desc = str(LANG_CREATE_DIR);
+    menu[i].voice_id = LANG_CREATE_DIR;
     menu[i].function = create_dir;
     i++;
 
diff --git a/apps/playlist.c b/apps/playlist.c
index 572eb26..53a18c6 100644
--- a/apps/playlist.c
+++ b/apps/playlist.c
@@ -91,6 +91,7 @@
 #endif
 
 #include "lang.h"
+#include "talk.h"
 
 #define PLAYLIST_CONTROL_FILE ROCKBOX_DIR "/.playlist_control"
 #define PLAYLIST_CONTROL_FILE_VERSION 2
@@ -337,6 +338,7 @@
     {
         /* use mp3 buffer for maximum load speed */
         mpeg_stop();
+        talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
 
         buffer = mp3buf;
         buflen = (mp3end - mp3buf);        
@@ -1192,6 +1194,7 @@
     };
 
     /* use mp3 buffer for maximum load speed */
+    talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
     buflen = (mp3end - mp3buf);
     buffer = mp3buf;
 
diff --git a/apps/playlist_menu.c b/apps/playlist_menu.c
index 87862ab..6eafe0c 100644
--- a/apps/playlist_menu.c
+++ b/apps/playlist_menu.c
@@ -65,10 +65,10 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_CREATE_PLAYLIST),       create_playlist   },
-        { str(LANG_VIEW_DYNAMIC_PLAYLIST), playlist_viewer   },
-        { str(LANG_SAVE_DYNAMIC_PLAYLIST), save_playlist     },
-        { str(LANG_RECURSE_DIRECTORY),     recurse_directory },
+        { STR(LANG_CREATE_PLAYLIST),       create_playlist   },
+        { STR(LANG_VIEW_DYNAMIC_PLAYLIST), playlist_viewer   },
+        { STR(LANG_SAVE_DYNAMIC_PLAYLIST), save_playlist     },
+        { STR(LANG_RECURSE_DIRECTORY),     recurse_directory },
     };
     
     m = menu_init( items, sizeof items / sizeof(struct menu_items), NULL );
diff --git a/apps/playlist_viewer.c b/apps/playlist_viewer.c
index 58052af..8b9470c 100644
--- a/apps/playlist_viewer.c
+++ b/apps/playlist_viewer.c
@@ -685,12 +685,15 @@
     bool current = (tracks[index].index == viewer.current_playing_track);
 
     menu[i].desc = str(LANG_REMOVE);
+    menu[i].voice_id = LANG_REMOVE;
     i++;
 
     menu[i].desc = str(LANG_MOVE);
+    menu[i].voice_id = LANG_MOVE;
     i++;
 
     menu[i].desc = str(LANG_FILE_OPTIONS);
+    menu[i].voice_id = LANG_FILE_OPTIONS;
     i++;
 
     m = menu_init(menu, i, NULL);
@@ -757,10 +760,10 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_SHOW_ICONS),             show_icons },
-        { str(LANG_SHOW_INDICES),           show_indices },
-        { str(LANG_TRACK_DISPLAY),          track_display },
-        { str(LANG_SAVE_DYNAMIC_PLAYLIST),  save_playlist },
+        { STR(LANG_SHOW_ICONS),             show_icons },
+        { STR(LANG_SHOW_INDICES),           show_indices },
+        { STR(LANG_TRACK_DISPLAY),          track_display },
+        { STR(LANG_SAVE_DYNAMIC_PLAYLIST),  save_playlist },
     };
     
     m=menu_init( items, sizeof(items) / sizeof(*items), NULL );
diff --git a/apps/plugin.c b/apps/plugin.c
index 08c9801..cdb787a 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -38,6 +38,7 @@
 #include "mp3_playback.h"
 #include "backlight.h"
 #include "ata.h"
+#include "talk.h"
 
 #ifdef HAVE_LCD_BITMAP
 #include "widgets.h"
@@ -325,6 +326,7 @@
     return buf;
 #else
     mpeg_stop();
+    talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
     *buffer_size = mp3end - mp3buf;
     return mp3buf;
 #endif 
diff --git a/apps/recorder/radio.c b/apps/recorder/radio.c
index a9937a3..86e11f9 100644
--- a/apps/recorder/radio.c
+++ b/apps/recorder/radio.c
@@ -595,6 +595,7 @@
             if(presets[i].frequency)
             {
                 menu[num_presets].desc = presets[i].name;
+                menu[num_presets].voice_id = -1;
                 /* We use the function pointer entry for the preset
                    entry index */
                 menu[num_presets++].function = (void *)i;
@@ -669,6 +670,7 @@
             if(presets[i].frequency)
             {
                 menu[num_presets].desc = presets[i].name;
+                menu[num_presets].voice_id = -1;
                 /* We use the function pointer entry for the preset
                    entry index */
                 menu[num_presets++].function = (void *)i;
@@ -715,10 +717,10 @@
 bool radio_menu(void)
 {
     struct menu_items radio_menu_items[] = {
-        { str(LANG_FM_SAVE_PRESET), radio_add_preset },
-        { str(LANG_FM_DELETE_PRESET), radio_delete_preset },
-        { str(LANG_SOUND_SETTINGS), sound_menu },
-        { str(LANG_RECORDING_SETTINGS), fm_recording_settings }
+        { STR(LANG_FM_SAVE_PRESET), radio_add_preset },
+        { STR(LANG_FM_DELETE_PRESET), radio_delete_preset },
+        { STR(LANG_SOUND_SETTINGS), sound_menu },
+        { STR(LANG_RECORDING_SETTINGS), fm_recording_settings }
     };
     int m;
     bool result;
diff --git a/apps/settings_menu.c b/apps/settings_menu.c
index 7c060c4..75c78a7 100644
--- a/apps/settings_menu.c
+++ b/apps/settings_menu.c
@@ -368,16 +368,16 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_PM_RELEASE)  , peak_meter_release   },  
-        { str(LANG_PM_PEAK_HOLD), peak_meter_hold      },  
-        { str(LANG_PM_CLIP_HOLD), peak_meter_clip_hold },
-        { str(LANG_PM_PERFORMANCE), peak_meter_performance },
+        { STR(LANG_PM_RELEASE)  , peak_meter_release   },  
+        { STR(LANG_PM_PEAK_HOLD), peak_meter_hold      },  
+        { STR(LANG_PM_CLIP_HOLD), peak_meter_clip_hold },
+        { STR(LANG_PM_PERFORMANCE), peak_meter_performance },
 #ifdef PM_DEBUG
-        { "Refresh rate"        , peak_meter_fps_menu  },
+        { "Refresh rate" , -1    , peak_meter_fps_menu  },
 #endif
-        { str(LANG_PM_SCALE)    , peak_meter_scale     },
-        { str(LANG_PM_MIN)      , peak_meter_min       },
-        { str(LANG_PM_MAX)      , peak_meter_max       },
+        { STR(LANG_PM_SCALE)    , peak_meter_scale     },
+        { STR(LANG_PM_MIN)      , peak_meter_min       },
+        { STR(LANG_PM_MAX)      , peak_meter_max       },
     };
     
     m=menu_init( items, sizeof(items) / sizeof(*items), NULL );
@@ -791,8 +791,8 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_FFRW_STEP), ff_rewind_min_step },
-        { str(LANG_FFRW_ACCEL), ff_rewind_accel },
+        { STR(LANG_FFRW_STEP), ff_rewind_min_step },
+        { STR(LANG_FFRW_ACCEL), ff_rewind_accel },
     };
 
     m=menu_init( items, sizeof(items) / sizeof(*items), NULL );
@@ -808,13 +808,13 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_SHUFFLE), shuffle },
-        { str(LANG_REPEAT), repeat_mode },
-        { str(LANG_PLAY_SELECTED), play_selected },
-        { str(LANG_RESUME), resume },
-        { str(LANG_WIND_MENU), ff_rewind_settings_menu },
-        { str(LANG_MP3BUFFER_MARGIN), buffer_margin },
-        { str(LANG_FADE_ON_STOP), set_fade_on_stop },
+        { STR(LANG_SHUFFLE), shuffle },
+        { STR(LANG_REPEAT), repeat_mode },
+        { STR(LANG_PLAY_SELECTED), play_selected },
+        { STR(LANG_RESUME), resume },
+        { STR(LANG_WIND_MENU), ff_rewind_settings_menu },
+        { STR(LANG_MP3BUFFER_MARGIN), buffer_margin },
+        { STR(LANG_FADE_ON_STOP), set_fade_on_stop },
     };
 
     bool old_shuffle = global_settings.playlist_shuffle;
@@ -843,9 +843,9 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_BOOKMARK_SETTINGS_AUTOCREATE), autocreatebookmark},
-        { str(LANG_BOOKMARK_SETTINGS_AUTOLOAD), autoloadbookmark},
-        { str(LANG_BOOKMARK_SETTINGS_MAINTAIN_RECENT_BOOKMARKS), useMRB},
+        { STR(LANG_BOOKMARK_SETTINGS_AUTOCREATE), autocreatebookmark},
+        { STR(LANG_BOOKMARK_SETTINGS_AUTOLOAD), autoloadbookmark},
+        { STR(LANG_BOOKMARK_SETTINGS_MAINTAIN_RECENT_BOOKMARKS), useMRB},
     };
 
     m=menu_init( items, sizeof items / sizeof(struct menu_items), NULL );
@@ -910,10 +910,10 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_CASE_MENU),    sort_case           },
-        { str(LANG_FILTER),       dir_filter          },
-        { str(LANG_FOLLOW),       browse_current      },
-        { str(LANG_SHOW_ICONS),   show_icons          },
+        { STR(LANG_CASE_MENU),    sort_case           },
+        { STR(LANG_FILTER),       dir_filter          },
+        { STR(LANG_FOLLOW),       browse_current      },
+        { STR(LANG_SHOW_ICONS),   show_icons          },
     };
 
     m=menu_init( items, sizeof(items) / sizeof(*items), NULL );
@@ -929,15 +929,15 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_SCROLL_SPEED),     scroll_speed    },
-        { str(LANG_SCROLL_DELAY),    scroll_delay    },  
+        { STR(LANG_SCROLL_SPEED),     scroll_speed    },
+        { STR(LANG_SCROLL_DELAY),    scroll_delay    },  
 #ifdef HAVE_LCD_BITMAP
-        { str(LANG_SCROLL_STEP),     scroll_step     },  
+        { STR(LANG_SCROLL_STEP),     scroll_step     },  
 #endif
-        { str(LANG_BIDIR_SCROLL),    bidir_limit    },
+        { STR(LANG_BIDIR_SCROLL),    bidir_limit    },
 #ifdef HAVE_LCD_CHARCELLS
-        { str(LANG_JUMP_SCROLL),    jump_scroll    },
-        { str(LANG_JUMP_SCROLL_DELAY),    jump_scroll_delay    },
+        { STR(LANG_JUMP_SCROLL),    jump_scroll    },
+        { STR(LANG_JUMP_SCROLL_DELAY),    jump_scroll_delay    },
 #endif
     };
 
@@ -953,14 +953,14 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_BACKLIGHT),       backlight_timer },
-        { str(LANG_BACKLIGHT_ON_WHEN_CHARGING), backlight_on_when_charging },
-        { str(LANG_CAPTION_BACKLIGHT), caption_backlight },
-        { str(LANG_CONTRAST),        contrast },
+        { STR(LANG_BACKLIGHT),       backlight_timer },
+        { STR(LANG_BACKLIGHT_ON_WHEN_CHARGING), backlight_on_when_charging },
+        { STR(LANG_CAPTION_BACKLIGHT), caption_backlight },
+        { STR(LANG_CONTRAST),        contrast },
 #ifdef HAVE_LCD_BITMAP
-        { str(LANG_INVERT),          invert },
-        { str(LANG_FLIP_DISPLAY),    flip_display },
-        { str(LANG_INVERT_CURSOR),   invert_cursor },
+        { STR(LANG_INVERT),          invert },
+        { STR(LANG_FLIP_DISPLAY),    flip_display },
+        { STR(LANG_INVERT_CURSOR),   invert_cursor },
 #endif
     };
 
@@ -977,10 +977,10 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_SCROLL_BAR),      scroll_bar },
-        { str(LANG_STATUS_BAR),      status_bar },
-        { str(LANG_VOLUME_DISPLAY),  volume_type },
-        { str(LANG_BATTERY_DISPLAY), battery_type },
+        { STR(LANG_SCROLL_BAR),      scroll_bar },
+        { STR(LANG_STATUS_BAR),      status_bar },
+        { STR(LANG_VOLUME_DISPLAY),  volume_type },
+        { STR(LANG_BATTERY_DISPLAY), battery_type },
     };
 
     m=menu_init( items, sizeof(items) / sizeof(*items), NULL );
@@ -998,14 +998,14 @@
 
     struct menu_items items[] = {
 #ifdef HAVE_LCD_BITMAP
-        { str(LANG_CUSTOM_FONT),     font_browse },
+        { STR(LANG_CUSTOM_FONT),     font_browse },
 #endif
-        { str(LANG_WHILE_PLAYING),   custom_wps_browse },
-        { str(LANG_LCD_MENU),        lcd_settings_menu },
-        { str(LANG_SCROLL_MENU),     scroll_settings_menu },
+        { STR(LANG_WHILE_PLAYING),   custom_wps_browse },
+        { STR(LANG_LCD_MENU),        lcd_settings_menu },
+        { STR(LANG_SCROLL_MENU),     scroll_settings_menu },
 #ifdef HAVE_LCD_BITMAP
-        { str(LANG_BARS_MENU),       bars_settings_menu },
-        { str(LANG_PM_MENU),         peak_meter_menu },
+        { STR(LANG_BARS_MENU),       bars_settings_menu },
+        { STR(LANG_PM_MENU),         peak_meter_menu },
 #endif
     };
 
@@ -1028,11 +1028,11 @@
 
     struct menu_items items[] = {
 #ifdef HAVE_CHARGE_CTRL
-        { str(LANG_DISCHARGE),        deep_discharge   },
-        { str(LANG_TRICKLE_CHARGE),   trickle_charge   },
+        { STR(LANG_DISCHARGE),        deep_discharge   },
+        { STR(LANG_TRICKLE_CHARGE),   trickle_charge   },
 #endif
 #ifndef SIMULATOR
-        { str(LANG_BATTERY_CAPACITY), battery_capacity },
+        { STR(LANG_BATTERY_CAPACITY), battery_capacity },
 #endif
     };
 
@@ -1048,9 +1048,9 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_SPINDOWN),    spindown        },
+        { STR(LANG_SPINDOWN),    spindown        },
 #ifdef HAVE_ATA_POWER_OFF
-        { str(LANG_POWEROFF),    poweroff        },
+        { STR(LANG_POWEROFF),    poweroff        },
 #endif
     };
 
@@ -1067,8 +1067,8 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_TIME),        timedate_set    },
-        { str(LANG_TIMEFORMAT),  timeformat_set  },
+        { STR(LANG_TIME),        timedate_set    },
+        { STR(LANG_TIMEFORMAT),  timeformat_set  },
     };
 
     m=menu_init( items, sizeof(items) / sizeof(*items), NULL );
@@ -1084,10 +1084,10 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_CUSTOM_CFG),      custom_cfg_browse },
-        { str(LANG_FIRMWARE),        firmware_browse },
-        { str(LANG_RESET),           reset_settings },
-        { str(LANG_SAVE_SETTINGS),   settings_save_config },
+        { STR(LANG_CUSTOM_CFG),      custom_cfg_browse },
+        { STR(LANG_FIRMWARE),        firmware_browse },
+        { STR(LANG_RESET),           reset_settings },
+        { STR(LANG_SAVE_SETTINGS),   settings_save_config },
     };
 
     m=menu_init( items, sizeof(items) / sizeof(*items), NULL );
@@ -1102,8 +1102,8 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_MAX_FILES_IN_DIR),    max_files_in_dir        },
-        { str(LANG_MAX_FILES_IN_PLAYLIST),    max_files_in_playlist        },
+        { STR(LANG_MAX_FILES_IN_DIR),    max_files_in_dir        },
+        { STR(LANG_MAX_FILES_IN_PLAYLIST),    max_files_in_playlist        },
     };
 
     m=menu_init( items, sizeof(items) / sizeof(*items), NULL );
@@ -1119,22 +1119,22 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_BATTERY_MENU),     battery_settings_menu },
-        { str(LANG_DISK_MENU),        disk_settings_menu     },
+        { STR(LANG_BATTERY_MENU),     battery_settings_menu },
+        { STR(LANG_DISK_MENU),        disk_settings_menu     },
 #ifdef HAVE_RTC
-        { str(LANG_TIME_MENU),        time_settings_menu     },
+        { STR(LANG_TIME_MENU),        time_settings_menu     },
 #endif
-        { str(LANG_POWEROFF_IDLE),    poweroff_idle_timer    },
-        { str(LANG_SLEEP_TIMER),      sleeptimer_screen      },
+        { STR(LANG_POWEROFF_IDLE),    poweroff_idle_timer    },
+        { STR(LANG_SLEEP_TIMER),      sleeptimer_screen      },
 #ifdef HAVE_ALARM_MOD
-        { str(LANG_ALARM_MOD_ALARM_MENU), alarm_screen       },
+        { STR(LANG_ALARM_MOD_ALARM_MENU), alarm_screen       },
 #endif
-        { str(LANG_LIMITS_MENU),      limits_settings_menu   },
+        { STR(LANG_LIMITS_MENU),      limits_settings_menu   },
 #ifdef HAVE_MAS3507D
-        { str(LANG_LINE_IN),          line_in                },
+        { STR(LANG_LINE_IN),          line_in                },
 #endif
-        { str(LANG_CAR_ADAPTER_MODE), car_adapter_mode       },
-        { str(LANG_MANAGE_MENU),      manage_settings_menu   },
+        { STR(LANG_CAR_ADAPTER_MODE), car_adapter_mode       },
+        { STR(LANG_MANAGE_MENU),      manage_settings_menu   },
     };
 
     m=menu_init( items, sizeof(items) / sizeof(*items), NULL );
@@ -1149,12 +1149,12 @@
     bool result;
 
     struct menu_items items[] = {
-        { str(LANG_PLAYBACK),         playback_settings_menu },
-        { str(LANG_FILE),             fileview_settings_menu },
-        { str(LANG_DISPLAY),          display_settings_menu  },
-        { str(LANG_SYSTEM),           system_settings_menu   },
-        { str(LANG_BOOKMARK_SETTINGS),bookmark_settings_menu },
-        { str(LANG_LANGUAGE),         language_browse        },
+        { STR(LANG_PLAYBACK),         playback_settings_menu },
+        { STR(LANG_FILE),             fileview_settings_menu },
+        { STR(LANG_DISPLAY),          display_settings_menu  },
+        { STR(LANG_SYSTEM),           system_settings_menu   },
+        { STR(LANG_BOOKMARK_SETTINGS),bookmark_settings_menu },
+        { STR(LANG_LANGUAGE),         language_browse        },
     };
     
     m=menu_init( items, sizeof(items) / sizeof(*items), NULL );
diff --git a/apps/sound_menu.c b/apps/sound_menu.c
index 256da0b..e0415cd 100644
--- a/apps/sound_menu.c
+++ b/apps/sound_menu.c
@@ -295,15 +295,15 @@
     int m;
     bool result;
     struct menu_items items[] = {
-        { str(LANG_VOLUME), volume },
-        { str(LANG_BASS), bass },
-        { str(LANG_TREBLE), treble },
-        { str(LANG_BALANCE), balance },
-        { str(LANG_CHANNEL_MENU), chanconf },
+        { STR(LANG_VOLUME), volume },
+        { STR(LANG_BASS), bass },
+        { STR(LANG_TREBLE), treble },
+        { STR(LANG_BALANCE), balance },
+        { STR(LANG_CHANNEL_MENU), chanconf },
 #ifdef HAVE_MAS3587F
-        { str(LANG_LOUDNESS), loudness },
-        { str(LANG_BBOOST), bass_boost },
-        { str(LANG_AUTOVOL), avc }
+        { STR(LANG_LOUDNESS), loudness },
+        { STR(LANG_BBOOST), bass_boost },
+        { STR(LANG_AUTOVOL), avc }
 #endif
     };
     
@@ -323,22 +323,30 @@
     bool result;
 
     menu[i].desc = str(LANG_RECORDING_QUALITY);
+    menu[i].voice_id = LANG_RECORDING_QUALITY;
     menu[i++].function = recquality;
     menu[i].desc = str(LANG_RECORDING_FREQUENCY);
+    menu[i].voice_id = LANG_RECORDING_FREQUENCY;
     menu[i++].function = recfrequency;
     if(!no_source) {
         menu[i].desc = str(LANG_RECORDING_SOURCE);
+        menu[i].voice_id = LANG_RECORDING_SOURCE;
         menu[i++].function = recsource;
     }
     menu[i].desc = str(LANG_RECORDING_CHANNELS);
+    menu[i].voice_id = LANG_RECORDING_CHANNELS;
     menu[i++].function = recchannels;
     menu[i].desc = str(LANG_RECORDING_EDITABLE);
+    menu[i].voice_id = LANG_RECORDING_EDITABLE;
     menu[i++].function = receditable;
     menu[i].desc = str(LANG_RECORD_TIMESPLIT);
+    menu[i].voice_id = LANG_RECORD_TIMESPLIT;
     menu[i++].function = rectimesplit;
     menu[i].desc = str(LANG_RECORD_PRERECORD_TIME);
+    menu[i].voice_id = LANG_RECORD_PRERECORD_TIME;
     menu[i++].function = recprerecord;
     menu[i].desc = str(LANG_RECORD_DIRECTORY);
+    menu[i].voice_id = LANG_RECORD_DIRECTORY;
     menu[i++].function = recdirectory;
         
     m=menu_init( menu, i, NULL );
diff --git a/firmware/export/talk.h b/firmware/export/talk.h
new file mode 100644
index 0000000..b2af46f
--- /dev/null
+++ b/firmware/export/talk.h
@@ -0,0 +1,34 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   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.
+ *
+ ****************************************************************************/
+
+#ifndef __TALK_H__
+#define __TALK_H__
+
+#include <stdbool.h>
+
+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 */
+
+#endif /* __TALK_H__ */
diff --git a/firmware/mpeg.c b/firmware/mpeg.c
index ff545f0..0f676f1 100644
--- a/firmware/mpeg.c
+++ b/firmware/mpeg.c
@@ -2134,6 +2134,7 @@
     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);
 }
 
@@ -2148,6 +2149,7 @@
     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;
 
@@ -2404,6 +2406,7 @@
 #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 */
 
diff --git a/firmware/talk.c b/firmware/talk.c
new file mode 100644
index 0000000..d02a4ef
--- /dev/null
+++ b/firmware/talk.c
@@ -0,0 +1,209 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   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;
+}
diff --git a/uisimulator/common/stubs.c b/uisimulator/common/stubs.c
index 23a6728..4098c0b 100644
--- a/uisimulator/common/stubs.c
+++ b/uisimulator/common/stubs.c
@@ -225,3 +225,22 @@
 {
     (void)yesno;
 }
+
+int talk_buffer_steal(void)
+{
+    return 0;
+}
+
+int talk_id(int id, bool block)
+{
+    (void)id;
+    (void)block;
+    return 0;
+}
+
+int talk_file(char* filename, bool block)
+{
+    (void)filename;
+    (void)block;
+    return 0;
+}