Initial voice ui support for software codec platforms. Added also a
beep when changing tracks.


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7360 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/SOURCES b/apps/SOURCES
index c1f3dfc..e8fd2d2 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -22,7 +22,7 @@
 sleeptimer.c
 sound_menu.c
 status.c
-#ifndef SIMULATOR
+#if !defined(SIMULATOR) || CONFIG_HWCODEC == MASNONE
 talk.c
 #endif
 tree.c
diff --git a/apps/codecs.c b/apps/codecs.c
index b1d3086..004d468 100644
--- a/apps/codecs.c
+++ b/apps/codecs.c
@@ -54,7 +54,7 @@
 
 #ifdef SIMULATOR
 #if CONFIG_HWCODEC == MASNONE
-static unsigned char codecbuf[CODEC_SIZE];
+unsigned char codecbuf[CODEC_SIZE];
 #endif
 void *sim_codec_load_ram(char* codecptr, int size,
         void* ptr2, int bufwrap, int *pd);
@@ -68,6 +68,8 @@
 
 static int codec_test(int api_version, int model, int memsize);
 
+struct codec_api ci_voice;
+
 struct codec_api ci = {
     CODEC_API_VERSION,
     codec_test,
@@ -247,7 +249,8 @@
     NULL,
 };
 
-int codec_load_ram(char* codecptr, int size, void* ptr2, int bufwrap)
+int codec_load_ram(char* codecptr, int size, void* ptr2, int bufwrap,
+                   struct codec_api *api)
 {
     enum codec_status (*codec_start)(const struct codec_api* api);
     int status;
@@ -277,7 +280,7 @@
 #endif /* SIMULATOR */
 
     invalidate_icache();
-    status = codec_start(&ci);
+    status = codec_start(api);
 #ifdef SIMULATOR
     sim_codec_close(pd);
 #endif
@@ -285,7 +288,7 @@
     return status;
 }
 
-int codec_load_file(const char *plugin)
+int codec_load_file(const char *plugin, struct codec_api *api)
 {
     char msgbuf[80];
     int fd;
@@ -309,7 +312,7 @@
         return CODEC_ERROR;
     }
 
-    return codec_load_ram(codecbuf, (size_t)rc, NULL, 0);
+    return codec_load_ram(codecbuf, (size_t)rc, NULL, 0, api);
 }
 
 static int codec_test(int api_version, int model, int memsize)
diff --git a/apps/codecs.h b/apps/codecs.h
index b2cf9e5..3b8e1d8 100644
--- a/apps/codecs.h
+++ b/apps/codecs.h
@@ -331,8 +331,9 @@
 
 /* defined by the codec loader (codec.c) */
 #if CONFIG_HWCODEC == MASNONE
-int codec_load_ram(char* codecptr, int size, void* ptr2, int bufwrap);
-int codec_load_file(const char* codec);
+int codec_load_ram(char* codecptr, int size, void* ptr2, int bufwrap,
+                   struct codec_api *api);
+int codec_load_file(const char* codec, struct codec_api *api);
 #endif
 
 /* defined by the codec */
diff --git a/apps/codecs/wav.c b/apps/codecs/wav.c
index 527d33d..d2ae7bd 100644
--- a/apps/codecs/wav.c
+++ b/apps/codecs/wav.c
@@ -71,15 +71,12 @@
     return CODEC_ERROR;
   }
 
-  while (!rb->taginfo_ready)
+  while (!*rb->taginfo_ready)
       rb->yield();
-    
-  if (rb->id3->frequency != NATIVE_FREQUENCY) {
-      rb->configure(DSP_SET_FREQUENCY, (long *)(rb->id3->frequency));
-      rb->configure(CODEC_DSP_ENABLE, (bool *)true);
-  } else {
-      rb->configure(CODEC_DSP_ENABLE, (bool *)false);
-  }
+
+  /* Always enable DSP to support voice ui. */
+  rb->configure(CODEC_DSP_ENABLE, (bool *)true);
+  rb->configure(DSP_SET_FREQUENCY, (long *)(rb->id3->frequency));
     
   /* FIX: Correctly parse WAV header - we assume canonical 44-byte header */
 
diff --git a/apps/debug_menu.c b/apps/debug_menu.c
index 86e02a1..b4cd056 100644
--- a/apps/debug_menu.c
+++ b/apps/debug_menu.c
@@ -208,8 +208,8 @@
 }
 #else
 extern size_t audiobuffer_free;
-extern int codecbuflen;
-extern int codecbufused;
+extern int filebuflen;
+extern int filebufused;
 extern int track_count;
 
 static int ticks, boost_ticks;
@@ -260,12 +260,12 @@
                   bufsize-audiobuffer_free, HORIZONTAL);
         line++;
 
-        snprintf(buf, sizeof(buf), "codec: %d/%d", codecbufused, codecbuflen);
+        snprintf(buf, sizeof(buf), "codec: %d/%d", filebufused, filebuflen);
         lcd_puts(0, line++, buf);
 
         /* Playable space left */
-        scrollbar(0, line*8, LCD_WIDTH, 6, codecbuflen, 0,
-                  codecbufused, HORIZONTAL);
+        scrollbar(0, line*8, LCD_WIDTH, 6, filebuflen, 0,
+                  filebufused, HORIZONTAL);
         line++;
 
         snprintf(buf, sizeof(buf), "track count: %d", track_count);
diff --git a/apps/dsp.c b/apps/dsp.c
index 2ba7fb9..8064a88 100644
--- a/apps/dsp.c
+++ b/apps/dsp.c
@@ -132,9 +132,12 @@
     long random;
 };
 
-static struct dsp_config dsp IDATA_ATTR;
+static struct dsp_config dsp_conf[2] IDATA_ATTR;
 static struct dither_data dither_data[2] IDATA_ATTR;
-static struct resample_data resample_data[2] IDATA_ATTR;
+static struct resample_data resample_data[2][2] IDATA_ATTR;
+
+extern int current_codec;
+struct dsp_config *dsp;
 
 /* The internal format is 32-bit samples, non-interleaved, stereo. This
  * format is similar to the raw output from several codecs, so the amount
@@ -155,20 +158,20 @@
 {
     count = MIN(SAMPLE_BUF_SIZE / 2, count);
 
-    if ((dsp.sample_depth <= NATIVE_DEPTH)
-        || (dsp.stereo_mode == STEREO_INTERLEAVED))
+    if ((dsp->sample_depth <= NATIVE_DEPTH)
+        || (dsp->stereo_mode == STEREO_INTERLEAVED))
     {
         dst[0] = &sample_buf[0];
-        dst[1] = (dsp.stereo_mode == STEREO_MONO)
+        dst[1] = (dsp->stereo_mode == STEREO_MONO)
             ? dst[0] : &sample_buf[SAMPLE_BUF_SIZE / 2];
     }
     else
     {
         dst[0] = (long*) src[0];
-        dst[1] = (long*) ((dsp.stereo_mode == STEREO_MONO) ? src[0] : src[1]);
+        dst[1] = (long*) ((dsp->stereo_mode == STEREO_MONO) ? src[0] : src[1]);
     }
 
-    if (dsp.sample_depth <= NATIVE_DEPTH)
+    if (dsp->sample_depth <= NATIVE_DEPTH)
     {
         short* s0 = (short*) src[0];
         long* d0 = dst[0];
@@ -176,7 +179,7 @@
         int scale = WORD_SHIFT;
         int i;
 
-        if (dsp.stereo_mode == STEREO_INTERLEAVED)
+        if (dsp->stereo_mode == STEREO_INTERLEAVED)
         {
             for (i = 0; i < count; i++)
             {
@@ -184,7 +187,7 @@
                 *d1++ = *s0++ << scale;
             }
         }
-        else if (dsp.stereo_mode == STEREO_NONINTERLEAVED)
+        else if (dsp->stereo_mode == STEREO_NONINTERLEAVED)
         {
             short* s1 = (short*) src[1];
 
@@ -202,7 +205,7 @@
             }
         }
     }
-    else if (dsp.stereo_mode == STEREO_INTERLEAVED)
+    else if (dsp->stereo_mode == STEREO_INTERLEAVED)
     {
         long* s0 = (long*) src[0];
         long* d0 = dst[0];
@@ -216,18 +219,18 @@
         }
     }
 
-    if (dsp.stereo_mode == STEREO_NONINTERLEAVED)
+    if (dsp->stereo_mode == STEREO_NONINTERLEAVED)
     {
-        src[0] += count * dsp.sample_bytes;
-        src[1] += count * dsp.sample_bytes;
+        src[0] += count * dsp->sample_bytes;
+        src[1] += count * dsp->sample_bytes;
     }
-    else if (dsp.stereo_mode == STEREO_INTERLEAVED)
+    else if (dsp->stereo_mode == STEREO_INTERLEAVED)
     {
-        src[0] += count * dsp.sample_bytes * 2;
+        src[0] += count * dsp->sample_bytes * 2;
     }
     else
     {
-        src[0] += count * dsp.sample_bytes;
+        src[0] += count * dsp->sample_bytes;
     }
 
     return count;
@@ -310,29 +313,33 @@
 {
     long new_count;
 
-    if (dsp.frequency != NATIVE_FREQUENCY)
+    if (dsp->frequency != NATIVE_FREQUENCY)
     {
         long* d0 = &resample_buf[0];
         /* Only process the second channel if needed. */
         long* d1 = (src[0] == src[1]) ? d0
             : &resample_buf[RESAMPLE_BUF_SIZE / 2];
 
-        if (dsp.frequency < NATIVE_FREQUENCY)
+        if (dsp->frequency < NATIVE_FREQUENCY)
         {
-            new_count = upsample(d0, src[0], count, &resample_data[0]);
+            new_count = upsample(d0, src[0], count,
+                            &resample_data[current_codec][0]);
 
             if (d0 != d1)
             {
-                upsample(d1, src[1], count, &resample_data[1]);
+                upsample(d1, src[1], count,
+                    &resample_data[current_codec][1]);
             }
         }
         else
         {
-            new_count = downsample(d0, src[0], count, &resample_data[0]);
+            new_count = downsample(d0, src[0], count,
+                            &resample_data[current_codec][0]);
 
             if (d0 != d1)
             {
-                downsample(d1, src[1], count, &resample_data[1]);
+                downsample(d1, src[1], count,
+                    &resample_data[current_codec][1]);
             }
         }
 
@@ -389,8 +396,8 @@
 
     /* Clip and quantize */
 
-    min = dsp.clip_min;
-    max = dsp.clip_max;
+    min = dsp->clip_min;
+    max = dsp->clip_max;
     sample = clip_sample(sample, min, max);
     output = clip_sample(output, min, max) & ~mask;
 
@@ -407,13 +414,13 @@
  */
 static void apply_gain(long* src[], int count)
 {
-    if (dsp.replaygain)
+    if (dsp->replaygain)
     {
         long* s0 = src[0];
         long* s1 = src[1];
         long* d0 = &sample_buf[0];
         long* d1 = (s0 == s1) ? d0 : &sample_buf[SAMPLE_BUF_SIZE / 2];
-        long gain = dsp.replaygain;
+        long gain = dsp->replaygain;
         long s;
         long i;
 
@@ -442,11 +449,11 @@
 {
     long* s0 = src[0];
     long* s1 = src[1];
-    int scale = dsp.frac_bits + 1 - NATIVE_DEPTH;
+    int scale = dsp->frac_bits + 1 - NATIVE_DEPTH;
 
-    if (dsp.dither_enabled)
+    if (dsp->dither_enabled)
     {
-        long bias = (1L << (dsp.frac_bits - NATIVE_DEPTH));
+        long bias = (1L << (dsp->frac_bits - NATIVE_DEPTH));
         long mask = (1L << scale) - 1;
 
         while (count-- > 0)
@@ -459,8 +466,8 @@
     }
     else
     {
-        long min = dsp.clip_min;
-        long max = dsp.clip_max;
+        long min = dsp->clip_min;
+        long max = dsp->clip_max;
 
         while (count-- > 0)
         {
@@ -482,10 +489,13 @@
 {
     long* tmp[2];
     long written = 0;
-    long factor = (dsp.stereo_mode != STEREO_MONO) ? 2 : 1;
+    long factor;
     int samples;
 
-    size /= dsp.sample_bytes * factor;
+    dsp = &dsp_conf[current_codec];
+
+    factor = (dsp->stereo_mode != STEREO_MONO) ? 2 : 1;
+    size /= dsp->sample_bytes * factor;
     INIT();
     dsp_set_replaygain(false);
 
@@ -513,21 +523,23 @@
 /* dsp_input_size MUST be called afterwards */
 long dsp_output_size(long size)
 {
-    if (dsp.sample_depth > NATIVE_DEPTH)
+    dsp = &dsp_conf[current_codec];
+    
+    if (dsp->sample_depth > NATIVE_DEPTH)
     {
         size /= 2;
     }
 
-    if (dsp.frequency != NATIVE_FREQUENCY)
+    if (dsp->frequency != NATIVE_FREQUENCY)
     {
         size = (long) ((((unsigned long) size * NATIVE_FREQUENCY)
-            + (dsp.frequency - 1)) / dsp.frequency);
+            + (dsp->frequency - 1)) / dsp->frequency);
     }
 
     /* round to the next multiple of 2 (these are shorts) */
     size = (size + 1) & ~1;
 
-    if (dsp.stereo_mode == STEREO_MONO)
+    if (dsp->stereo_mode == STEREO_MONO)
     {
         size *= 2;
     }
@@ -547,25 +559,28 @@
  */
 long dsp_input_size(long size)
 {
+    dsp = &dsp_conf[current_codec];
+    
     /* convert to number of output stereo samples. */
     size /= 2;
 
     /* Mono means we need half input samples to fill the output buffer */
-    if (dsp.stereo_mode == STEREO_MONO)
+    if (dsp->stereo_mode == STEREO_MONO)
         size /= 2;
 
     /* size is now the number of resampled input samples. Convert to
        original input samples. */
-    if (dsp.frequency != NATIVE_FREQUENCY)
+    if (dsp->frequency != NATIVE_FREQUENCY)
     {
         /* Use the real resampling delta =
-         *  (unsigned long) dsp.frequency * 65536 / NATIVE_FREQUENCY, and
+         *  (unsigned long) dsp->frequency * 65536 / NATIVE_FREQUENCY, and
          * round towards zero to avoid buffer overflows. */
-        size = ((unsigned long)size * resample_data[0].delta) >> 16;
+        size = ((unsigned long)size *
+            resample_data[current_codec][0].delta) >> 16;
     }
 
     /* Convert back to bytes. */
-    if (dsp.sample_depth > NATIVE_DEPTH)
+    if (dsp->sample_depth > NATIVE_DEPTH)
         size *= 4;
     else
         size *= 2;
@@ -575,90 +590,96 @@
 
 int dsp_stereo_mode(void)
 {
-    return dsp.stereo_mode;
+    dsp = &dsp_conf[current_codec];
+    
+    return dsp->stereo_mode;
 }
 
 bool dsp_configure(int setting, void *value)
 {
+    dsp = &dsp_conf[current_codec];
+
     switch (setting)
     {
     case DSP_SET_FREQUENCY:
-        memset(resample_data, 0, sizeof(resample_data));
+        memset(&resample_data[current_codec][0], 0,
+            sizeof(struct resample_data) * 2);
         /* Fall through!!! */
     case DSP_SWITCH_FREQUENCY:
-        dsp.frequency = ((int) value == 0) ? NATIVE_FREQUENCY : (int) value;
-        resample_data[0].delta = resample_data[1].delta =
-            (unsigned long) dsp.frequency * 65536 / NATIVE_FREQUENCY;
+        dsp->frequency = ((int) value == 0) ? NATIVE_FREQUENCY : (int) value;
+        resample_data[current_codec][0].delta =
+            resample_data[current_codec][1].delta =
+            (unsigned long) dsp->frequency * 65536 / NATIVE_FREQUENCY;
         break;
 
     case DSP_SET_CLIP_MIN:
-        dsp.clip_min = (long) value;
+        dsp->clip_min = (long) value;
         break;
 
     case DSP_SET_CLIP_MAX:
-        dsp.clip_max = (long) value;
+        dsp->clip_max = (long) value;
         break;
 
     case DSP_SET_SAMPLE_DEPTH:
-        dsp.sample_depth = (long) value;
+        dsp->sample_depth = (long) value;
 
-        if (dsp.sample_depth <= NATIVE_DEPTH)
+        if (dsp->sample_depth <= NATIVE_DEPTH)
         {
-            dsp.frac_bits = WORD_FRACBITS;
-            dsp.sample_bytes = sizeof(short);
-            dsp.clip_max =  ((1 << WORD_FRACBITS) - 1);
-            dsp.clip_min = -((1 << WORD_FRACBITS));
+            dsp->frac_bits = WORD_FRACBITS;
+            dsp->sample_bytes = sizeof(short);
+            dsp->clip_max =  ((1 << WORD_FRACBITS) - 1);
+            dsp->clip_min = -((1 << WORD_FRACBITS));
         }
         else
         {
-            dsp.frac_bits = (long) value;
-            dsp.sample_bytes = sizeof(long);
+            dsp->frac_bits = (long) value;
+            dsp->sample_bytes = sizeof(long);
         }
 
         break;
 
     case DSP_SET_STEREO_MODE:
-        dsp.stereo_mode = (long) value;
+        dsp->stereo_mode = (long) value;
         break;
 
     case DSP_RESET:
-        dsp.dither_enabled = false;
-        dsp.stereo_mode = STEREO_NONINTERLEAVED;
-        dsp.clip_max =  ((1 << WORD_FRACBITS) - 1);
-        dsp.clip_min = -((1 << WORD_FRACBITS));
-        dsp.track_gain = 0;
-        dsp.album_gain = 0;
-        dsp.track_peak = 0;
-        dsp.album_peak = 0;
-        dsp.frequency = NATIVE_FREQUENCY;
-        dsp.sample_depth = NATIVE_DEPTH;
-        dsp.frac_bits = WORD_FRACBITS;
-        dsp.new_gain = true;
+        dsp->dither_enabled = false;
+        dsp->stereo_mode = STEREO_NONINTERLEAVED;
+        dsp->clip_max =  ((1 << WORD_FRACBITS) - 1);
+        dsp->clip_min = -((1 << WORD_FRACBITS));
+        dsp->track_gain = 0;
+        dsp->album_gain = 0;
+        dsp->track_peak = 0;
+        dsp->album_peak = 0;
+        dsp->frequency = NATIVE_FREQUENCY;
+        dsp->sample_depth = NATIVE_DEPTH;
+        dsp->frac_bits = WORD_FRACBITS;
+        dsp->new_gain = true;
         break;
 
     case DSP_DITHER:
         memset(dither_data, 0, sizeof(dither_data));
-        dsp.dither_enabled = (bool) value;
+        dsp->dither_enabled = (bool) value;
         break;
 
     case DSP_SET_TRACK_GAIN:
-        dsp.track_gain = (long) value;
-        dsp.new_gain = true;
+        dsp->track_gain = (long) value;
+        dsp->new_gain = true;
         break;
 
     case DSP_SET_ALBUM_GAIN:
-        dsp.album_gain = (long) value;
-        dsp.new_gain = true;
+        dsp->album_gain = (long) value;
+        dsp->new_gain = true;
         break;
 
     case DSP_SET_TRACK_PEAK:
-        dsp.track_peak = (long) value;
-        dsp.new_gain = true;
+        dsp->track_peak = (long) value;
+        dsp->new_gain = true;
         break;
 
     case DSP_SET_ALBUM_PEAK:
-        dsp.album_peak = (long) value;
-        dsp.new_gain = true;
+        dsp->album_peak = (long) value;
+        dsp->new_gain = true;
         break;
 
     default:
@@ -670,11 +691,13 @@
 
 void dsp_set_replaygain(bool always)
 {
-    if (always || dsp.new_gain)
+    dsp = &dsp_conf[current_codec];
+    
+    if (always || dsp->new_gain)
     {
         long gain = 0;
 
-        dsp.new_gain = false;
+        dsp->new_gain = false;
 
         if (global_settings.replaygain || global_settings.replaygain_noclip)
         {
@@ -682,8 +705,8 @@
 
             if (global_settings.replaygain)
             {
-                gain = (global_settings.replaygain_track || !dsp.album_gain)
-                    ? dsp.track_gain : dsp.album_gain;
+                gain = (global_settings.replaygain_track || !dsp->album_gain)
+                    ? dsp->track_gain : dsp->album_gain;
 
                 if (global_settings.replaygain_preamp)
                 {
@@ -694,8 +717,8 @@
                 }
             }
 
-            peak = (global_settings.replaygain_track || !dsp.album_peak)
-                ? dsp.track_peak : dsp.album_peak;
+            peak = (global_settings.replaygain_track || !dsp->album_peak)
+                ? dsp->track_peak : dsp->album_peak;
 
             if (gain == 0)
             {
@@ -718,6 +741,6 @@
         }
 
         /* Store in S8.23 format to simplify calculations. */
-        dsp.replaygain = gain >> 1;
+        dsp->replaygain = gain >> 1;
     }
 }
diff --git a/apps/main.c b/apps/main.c
index 90be703..55897de 100644
--- a/apps/main.c
+++ b/apps/main.c
@@ -129,6 +129,9 @@
               global_settings.mdb_enable,
               global_settings.superbass);
     button_clear_queue(); /* Empty the keyboard buffer */
+#if CONFIG_HWCODEC == MASNONE
+    talk_init();
+#endif
 }
 
 #else
diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c
index 691f8d5..5f78901 100644
--- a/apps/pcmbuf.c
+++ b/apps/pcmbuf.c
@@ -34,6 +34,7 @@
 #include "buffer.h"
 #include "settings.h"
 #include "audio.h"
+#include "dsp.h"
 
 #define CHUNK_SIZE           PCMBUF_GUARD
 /* Must be a power of 2 */
@@ -86,9 +87,11 @@
 volatile int pcmbuf_read_index;
 volatile int pcmbuf_write_index;
 int pcmbuf_unplayed_bytes;
+int pcmbuf_mix_used_bytes;
 int pcmbuf_watermark;
 void (*pcmbuf_watermark_event)(int bytes_left);
 static int last_chunksize;
+static long mixpos = 0;
 
 static void pcmbuf_boost(bool state)
 {
@@ -173,6 +176,7 @@
         pcmbuffers[pcmbuf_write_index].callback = callback;
         pcmbuf_write_index = (pcmbuf_write_index+1) & NUM_PCM_BUFFERS_MASK;
         pcmbuf_unplayed_bytes += size;
+        pcmbuf_mix_used_bytes = MAX(0, pcmbuf_mix_used_bytes - size);
         return true;
     }
     else
@@ -254,6 +258,7 @@
     pcm_play_stop();
     last_chunksize = 0;
     pcmbuf_unplayed_bytes = 0;
+    pcmbuf_mix_used_bytes = 0;
     pcmbuf_read_index = 0;
     pcmbuf_write_index = 0;
     audiobuffer_pos = 0;
@@ -297,6 +302,13 @@
     crossfade_init = true;
 }
 
+/* Force playback. */
+void pcmbuf_play_start(void)
+{
+    if (!pcm_is_playing() && pcmbuf_unplayed_bytes)
+        pcm_play_data(pcmbuf_callback);
+}
+
 void pcmbuf_flush_fillpos(void)
 {
     int copy_n;
@@ -562,6 +574,98 @@
     return true;
 }
 
+/* Generates a constant square wave sound with a given frequency
+   in Hertz for a duration in milliseconds. */
+void pcmbuf_beep(int frequency, int duration, int amplitude)
+{
+    int state = 0, count = 0;
+    int interval = NATIVE_FREQUENCY / frequency;
+    int pos;
+    short *buf = (short *)audiobuffer;
+    int bufsize = pcmbuf_size / 2;
+    
+    /* FIXME: Should start playback. */
+    //if (pcmbuf_unplayed_bytes * 1000 < 4 * NATIVE_FREQUENCY * duration)
+    //    return ;
+
+    pos = (audiobuffer_pos - pcmbuf_unplayed_bytes) / 2;
+    if (pos < 0)
+        pos += bufsize;
+
+    duration = NATIVE_FREQUENCY / 1000 * duration;
+    while (duration-- > 0)
+    {
+        if (state) {
+            buf[pos] = MIN(MAX(buf[pos] + amplitude, -32768), 32767);
+            if (++pos >= bufsize)
+                pos = 0;
+            buf[pos] = MIN(MAX(buf[pos] + amplitude, -32768), 32767);
+        } else {
+            buf[pos] = MIN(MAX(buf[pos] - amplitude, -32768), 32767);
+            if (++pos >= bufsize)
+                pos = 0;
+            buf[pos] = MIN(MAX(buf[pos] - amplitude, -32768), 32767);
+        }
+        
+        if (++count >= interval)
+        {
+            count = 0;
+            if (state)
+                state = 0;
+            else
+                state = 1;
+        }
+        pos++;
+        if (pos >= bufsize)
+            pos = 0;
+    }
+}
+
+/* Returns pcm buffer usage in percents (0 to 100). */
+int pcmbuf_usage(void)
+{
+    return pcmbuf_unplayed_bytes * 100 / pcmbuf_size;
+}
+
+int pcmbuf_mix_usage(void)
+{
+    return pcmbuf_mix_used_bytes * 100 / pcmbuf_unplayed_bytes;
+}
+
+void pcmbuf_reset_mixpos(void)
+{
+    int bufsize = pcmbuf_size / 2;
+
+    pcmbuf_mix_used_bytes = 0;
+    mixpos = (audiobuffer_pos - pcmbuf_unplayed_bytes) / 2;
+    if (mixpos < 0)
+        mixpos += bufsize;
+    if (mixpos >= bufsize)
+        mixpos -= bufsize;
+}
+
+void pcmbuf_mix(char *buf, long length)
+{
+    short *ibuf = (short *)buf;
+    short *obuf = (short *)audiobuffer;
+    int bufsize = pcmbuf_size / 2;
+
+    if (pcmbuf_mix_used_bytes == 0)
+        pcmbuf_reset_mixpos();
+    
+    pcmbuf_mix_used_bytes += length;
+    length /= 2;
+
+    while (length-- > 0) {
+        obuf[mixpos] = MIN(MAX(obuf[mixpos] + *ibuf*4, -32768), 32767);
+        
+        ibuf++;
+        mixpos++;
+        if (mixpos >= bufsize)
+            mixpos = 0;
+    }
+}
+
 void pcmbuf_crossfade_enable(bool on_off)
 {
     crossfade_enabled = on_off;
diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h
index 6381dbc..f2533de 100644
--- a/apps/pcmbuf.h
+++ b/apps/pcmbuf.h
@@ -36,6 +36,7 @@
 void pcmbuf_set_boost_mode(bool state);
 bool pcmbuf_is_lowdata(void);
 void pcmbuf_flush_audio(void);
+void pcmbuf_play_start(void);
 bool pcmbuf_crossfade_init(int type);
 void pcmbuf_add_event(void (*event_handler)(void));
 unsigned int pcmbuf_get_latency(void);
@@ -45,4 +46,10 @@
 bool pcmbuf_is_crossfade_enabled(void);
 void pcmbuf_crossfade_enable(bool on_off);
 
+int pcmbuf_usage(void);
+int pcmbuf_mix_usage(void);
+void pcmbuf_beep(int frequency, int duration, int amplitude);
+void pcmbuf_reset_mixpos(void);
+void pcmbuf_mix(char *buf, long length);
+
 #endif
diff --git a/apps/playback.c b/apps/playback.c
index e12b01e..8829757 100644
--- a/apps/playback.c
+++ b/apps/playback.c
@@ -60,18 +60,20 @@
 #include "misc.h"
 #include "sound.h"
 #include "metadata.h"
+#include "talk.h"
 
-static volatile bool codec_loaded;
+static volatile bool audio_codec_loaded;
+static volatile bool voice_codec_loaded;
 static volatile bool playing;
 static volatile bool paused;
 
-#define CODEC_VORBIS   "/.rockbox/codecs/vorbis.codec";
-#define CODEC_MPA_L3   "/.rockbox/codecs/mpa.codec";
-#define CODEC_FLAC     "/.rockbox/codecs/flac.codec";
-#define CODEC_WAV      "/.rockbox/codecs/wav.codec";
-#define CODEC_A52      "/.rockbox/codecs/a52.codec";
-#define CODEC_MPC      "/.rockbox/codecs/mpc.codec";
-#define CODEC_WAVPACK  "/.rockbox/codecs/wavpack.codec";
+#define CODEC_VORBIS   "/.rockbox/codecs/vorbis.codec"
+#define CODEC_MPA_L3   "/.rockbox/codecs/mpa.codec"
+#define CODEC_FLAC     "/.rockbox/codecs/flac.codec"
+#define CODEC_WAV      "/.rockbox/codecs/wav.codec"
+#define CODEC_A52      "/.rockbox/codecs/a52.codec"
+#define CODEC_MPC      "/.rockbox/codecs/mpc.codec"
+#define CODEC_WAVPACK  "/.rockbox/codecs/wavpack.codec"
 
 #define AUDIO_FILL_CYCLE             (1024*256)
 #define AUDIO_DEFAULT_WATERMARK      (1024*512)
@@ -96,6 +98,10 @@
 #define MALLOC_BUFSIZE (512*1024)
 #define GUARD_BUFSIZE  (8*1024)
 
+/* As defined in plugin.lds */
+#define CODEC_IRAM_ORIGIN   0x10010000
+#define CODEC_IRAM_SIZE     0x8000
+
 extern bool audio_is_initialized;
 
 /* Buffer control thread. */
@@ -108,19 +114,39 @@
 static long codec_stack[(DEFAULT_STACK_SIZE + 0x2500)/sizeof(long)] IDATA_ATTR;
 static const char codec_thread_name[] = "codec";
 
+/* Voice codec thread. */
+static struct event_queue voice_codec_queue;
+/* Not enough IRAM for this. */
+static long voice_codec_stack[(DEFAULT_STACK_SIZE + 0x2500)/sizeof(long)];
+static const char voice_codec_thread_name[] = "voice codec";
+
 static struct mutex mutex_bufferfill;
+static struct mutex mutex_codecthread;
+
+static struct mp3entry id3_voice;
+
+#define CODEC_IDX_AUDIO  0
+#define CODEC_IDX_VOICE  1
+
+static char *voicebuf;
+static int voice_remaining;
+static bool voice_is_playing;
+static void (*voice_getmore)(unsigned char** start, int* size);
 
 /* Is file buffer currently being refilled? */
 static volatile bool filling;
 
+volatile int current_codec;
+extern unsigned char codecbuf[];
+
 /* Ring buffer where tracks and codecs are loaded. */
-static char *codecbuf;
+static char *filebuf;
 
 /* Total size of the ring buffer. */
-int codecbuflen;
+int filebuflen;
 
 /* Bytes available in the buffer. */
-int codecbufused;
+int filebufused;
 
 /* Ring buffer read and write indexes. */
 static volatile int buf_ridx;
@@ -153,6 +179,7 @@
 
 /* Codec API including function callbacks. */
 extern struct codec_api ci;
+extern struct codec_api ci_voice;
 
 /* When we change a song and buffer is not in filling state, this
    variable keeps information about whether to go a next/previous track. */
@@ -174,6 +201,59 @@
 static void mp3_set_elapsed(struct mp3entry* id3);
 int mp3_get_file_pos(void);
 
+static void do_swap(int idx_old, int idx_new)
+{
+#ifndef SIMULATOR
+    unsigned char *iram_p = (unsigned char *)(CODEC_IRAM_ORIGIN);
+    unsigned char *iram_buf[2];
+#endif
+    unsigned char *dram_buf[2];
+
+
+#ifndef SIMULATOR
+    iram_buf[0] = &filebuf[filebuflen];
+    iram_buf[1] = &filebuf[filebuflen+CODEC_IRAM_SIZE];
+    memcpy(iram_buf[idx_old], iram_p, CODEC_IRAM_SIZE);
+    memcpy(iram_p, iram_buf[idx_new], CODEC_IRAM_SIZE);
+#endif
+
+    dram_buf[0] = &filebuf[filebuflen+CODEC_IRAM_SIZE*2];
+    dram_buf[1] = &filebuf[filebuflen+CODEC_IRAM_SIZE*2+CODEC_SIZE];
+    memcpy(dram_buf[idx_old], codecbuf, CODEC_SIZE);
+    memcpy(codecbuf, dram_buf[idx_new], CODEC_SIZE);
+}
+
+static void swap_codec(void)
+{
+    int last_codec;
+    
+    logf("swapping codec:%d", current_codec);
+    
+    /* We should swap codecs' IRAM contents and code space. */
+    do_swap(current_codec, !current_codec);
+    
+    last_codec = current_codec;
+    current_codec = !current_codec;
+
+    /* Release the semaphore and force a task switch. */
+    mutex_unlock(&mutex_codecthread);
+    sleep(1);
+
+    /* Waiting until we are ready to run again. */
+    mutex_lock(&mutex_codecthread);
+
+    /* Check if codec swap did not happen. */
+    if (current_codec != last_codec)
+    {
+        logf("no codec switch happened!");
+        do_swap(current_codec, !current_codec);
+        current_codec = !current_codec;
+    }
+    
+    invalidate_icache();
+    logf("codec resuming:%d", current_codec);
+}
+
 bool codec_pcmbuf_insert_split_callback(void *ch1, void *ch2,
                                         long length)
 {
@@ -209,12 +289,41 @@
             pcmbuf_flush_buffer(0);
             DEBUGF("Warning: dsp_input_size(%ld=dsp_output_size(%ld))=%ld <= 0\n",
                    output_size, length, input_size);
-            /* should we really continue, or should we break? */
+            /* should we really continue, or should we break?
+             * We should probably continue because calling pcmbuf_flush_buffer(0)
+             * will wrap the buffer if it was fully filled and so next call to
+             * pcmbuf_request_buffer should give the requested output_size. */
             continue;
         }
 
         output_size = dsp_process(dest, src, input_size);
-        pcmbuf_flush_buffer(output_size);
+
+        /* Hotswap between audio and voice codecs as necessary. */
+        switch (current_codec)
+        {
+            case CODEC_IDX_AUDIO:
+                pcmbuf_flush_buffer(output_size);
+                if (voice_is_playing && pcmbuf_usage() > 30
+                    && pcmbuf_mix_usage() < 20)
+                {
+                    cpu_boost(true);
+                    swap_codec();
+                    cpu_boost(false);
+                }
+                break ;
+
+            case CODEC_IDX_VOICE:
+                if (audio_codec_loaded) {
+                    pcmbuf_mix(dest, output_size);
+                    if ((pcmbuf_usage() < 10)
+                        || pcmbuf_mix_usage() > 70)
+                        swap_codec();
+                } else {
+                    pcmbuf_flush_buffer(output_size);
+                }
+                break ;
+        }
+            
         length -= input_size;
     }
 
@@ -241,6 +350,9 @@
 void* get_codec_memory_callback(long *size)
 {
     *size = MALLOC_BUFSIZE;
+    if (voice_codec_loaded)
+        return &audiobuf[talk_get_bufsize()];
+
     return &audiobuf[0];
 }
 
@@ -248,7 +360,7 @@
 {
     unsigned int latency;
 
-    if (ci.stop_codec)
+    if (ci.stop_codec || current_codec == CODEC_IDX_VOICE)
         return ;
         
     latency = pcmbuf_get_latency();
@@ -265,7 +377,7 @@
 {
     unsigned int latency;
 
-    if (ci.stop_codec)
+    if (ci.stop_codec || current_codec == CODEC_IDX_VOICE)
         return ;
         
     latency = pcmbuf_get_latency() * cur_ti->id3.bitrate / 8;
@@ -283,7 +395,7 @@
     int copy_n;
     int part_n;
     
-    if (ci.stop_codec || !playing)
+    if (ci.stop_codec || !playing || current_codec == CODEC_IDX_VOICE)
         return 0;
     
     copy_n = MIN((off_t)size, (off_t)cur_ti->available + cur_ti->filerem);
@@ -297,26 +409,78 @@
     if (copy_n == 0)
         return 0;
     
-    part_n = MIN(copy_n, codecbuflen - buf_ridx);
-    memcpy(buf, &codecbuf[buf_ridx], part_n);
+    part_n = MIN(copy_n, filebuflen - buf_ridx);
+    memcpy(buf, &filebuf[buf_ridx], part_n);
     if (part_n < copy_n) {
-        memcpy(&buf[part_n], &codecbuf[0], copy_n - part_n);
+        memcpy(&buf[part_n], &filebuf[0], copy_n - part_n);
     }
     
     buf_ridx += copy_n;
-    if (buf_ridx >= codecbuflen)
-        buf_ridx -= codecbuflen;
+    if (buf_ridx >= filebuflen)
+        buf_ridx -= filebuflen;
     ci.curpos += copy_n;
     cur_ti->available -= copy_n;
-    codecbufused -= copy_n;
+    filebufused -= copy_n;
     
     return copy_n;
 }
 
+void* voice_request_data(long *realsize, long reqsize)
+{
+    while (queue_empty(&voice_codec_queue) && (voice_remaining == 0
+            || voicebuf == NULL) && !ci_voice.stop_codec)
+    {
+        yield();
+        if (audio_codec_loaded && (pcmbuf_usage() < 30
+            || !voice_is_playing || voicebuf == NULL))
+        {
+            swap_codec();
+        }
+        if (!voice_is_playing)
+            sleep(HZ/16);
+            
+        if (voice_remaining)
+        {
+            voice_is_playing = true;
+            break ;
+        }
+            
+        if (voice_getmore != NULL)
+        {
+            voice_getmore((unsigned char **)&voicebuf, (int *)&voice_remaining);
+
+            if (!voice_remaining)
+            {
+                voice_is_playing = false;
+                /* Force pcm playback. */
+                pcmbuf_play_start();
+            }
+        }
+    }
+
+    if (reqsize < 0)
+        reqsize = 0;
+        
+    voice_is_playing = true;
+    *realsize = voice_remaining;
+    if (*realsize > reqsize)
+        *realsize = reqsize;
+
+    if (*realsize == 0)
+        return NULL;
+
+    return voicebuf;
+}
+
 void* codec_request_buffer_callback(long *realsize, long reqsize)
 {
     long part_n;
-    
+
+    /* Voice codec. */
+    if (current_codec == CODEC_IDX_VOICE) {
+        return voice_request_data(realsize, reqsize);
+    }
+   
     if (ci.stop_codec || !playing) {
         *realsize = 0;
         return NULL;
@@ -335,22 +499,22 @@
         }
     }
     
-    part_n = MIN((int)*realsize, codecbuflen - buf_ridx);
+    part_n = MIN((int)*realsize, filebuflen - buf_ridx);
     if (part_n < *realsize) {
         part_n += GUARD_BUFSIZE;
         if (part_n < *realsize)
             *realsize = part_n;
-        memcpy(&codecbuf[codecbuflen], &codecbuf[0], *realsize - 
-            (codecbuflen - buf_ridx));
+        memcpy(&filebuf[filebuflen], &filebuf[0], *realsize -
+            (filebuflen - buf_ridx));
     }
     
-    return (char *)&codecbuf[buf_ridx];
+    return (char *)&filebuf[buf_ridx];
 }
 
 static bool rebuffer_and_seek(int newpos)
 {
     int fd;
-    
+
     logf("Re-buffering song");
     mutex_lock(&mutex_bufferfill);
 
@@ -367,7 +531,7 @@
         
     /* Clear codec buffer. */
     audio_invalidate_tracks();
-    codecbufused = 0;
+    filebufused = 0;
     buf_ridx = buf_widx = 0;
     cur_ti->filerem = cur_ti->filesize - newpos;
     cur_ti->filepos = newpos;
@@ -390,6 +554,15 @@
 
 void codec_advance_buffer_callback(long amount)
 {
+    if (current_codec == CODEC_IDX_VOICE) {
+        //logf("voice ad.buf:%d", amount);
+        amount = MAX(0, MIN(amount, voice_remaining));
+        voicebuf += amount;
+        voice_remaining -= amount;
+        
+        return ;
+    }
+    
     if (amount > cur_ti->available + cur_ti->filerem)
         amount = cur_ti->available + cur_ti->filerem;
     
@@ -400,10 +573,10 @@
     }
     
     buf_ridx += amount;
-    if (buf_ridx >= codecbuflen)
-        buf_ridx -= codecbuflen;
+    if (buf_ridx >= filebuflen)
+        buf_ridx -= filebuflen;
     cur_ti->available -= amount;
-    codecbufused -= amount;
+    filebufused -= amount;
     ci.curpos += amount;
     codec_set_offset_callback(ci.curpos);
 }
@@ -411,15 +584,18 @@
 void codec_advance_buffer_loc_callback(void *ptr)
 {
     long amount;
-    
-    amount = (int)ptr - (int)&codecbuf[buf_ridx];
+
+    if (current_codec == CODEC_IDX_VOICE)
+        amount = (int)ptr - (int)voicebuf;
+    else
+        amount = (int)ptr - (int)&filebuf[buf_ridx];
     codec_advance_buffer_callback(amount);
 }
 
 off_t codec_mp3_get_filepos_callback(int newtime)
 {
     off_t newpos;
-    
+
     cur_ti->id3.elapsed = newtime;
     newpos = mp3_get_file_pos();
     
@@ -429,7 +605,10 @@
 bool codec_seek_buffer_callback(off_t newpos)
 {
     int difference;
-    
+
+    if (current_codec == CODEC_IDX_VOICE)
+        return false;
+        
     if (newpos < 0)
         newpos = 0;
     
@@ -457,11 +636,11 @@
 
     /* Seeking inside buffer space. */
     logf("seek: -%d", difference);
-    codecbufused += difference;
+    filebufused += difference;
     cur_ti->available += difference;
     buf_ridx -= difference;
     if (buf_ridx < 0)
-        buf_ridx = codecbuflen + buf_ridx;
+        buf_ridx = filebuflen + buf_ridx;
     ci.curpos -= difference;
     if (!pcmbuf_is_crossfade_active())
         pcmbuf_play_stop();
@@ -473,8 +652,11 @@
 {
     long bytes;
 
+    if (current_codec == CODEC_IDX_VOICE)
+        return ;
+        
     bytes = MAX((int)cur_ti->id3.bitrate * seconds * (1000/8), conf_watermark);
-    bytes = MIN(bytes, codecbuflen / 2);
+    bytes = MIN(bytes, filebuflen / 2);
     conf_watermark = bytes;
 }
 
@@ -540,7 +722,7 @@
         sleep(5);
     while ((pcmbuf_is_crossfade_active() || pcmbuf_is_lowdata())
             && !ci.stop_codec && playing && queue_empty(&audio_queue)
-            && codecbufused > (128*1024))
+            && filebufused > (128*1024))
         yield();
 }
 
@@ -552,18 +734,18 @@
     int tagptr;
     bool found = true;
 
-    if (codecbufused >= 128)
+    if (filebufused >= 128)
     {
         tagptr = buf_widx - 128;
         if (tagptr < 0)
-            tagptr += codecbuflen;
+            tagptr += filebuflen;
         
         for(i = 0;i < 3;i++)
         {
-            if(tagptr >= codecbuflen)
-                tagptr -= codecbuflen;
+            if(tagptr >= filebuflen)
+                tagptr -= filebuflen;
             
-            if(codecbuf[tagptr] != tag[i])
+            if(filebuf[tagptr] != tag[i])
             {
                 found = false;
                 break;
@@ -578,7 +760,7 @@
             logf("Skipping ID3v1 tag\n");
             buf_widx -= 128;
             tracks[track_widx].available -= 128;
-            codecbufused -= 128;
+            filebufused -= 128;
         }
     }
 }
@@ -603,9 +785,9 @@
             
         if (fill_bytesleft == 0)
             break ;
-        rc = MIN(conf_filechunk, codecbuflen - buf_widx);
+        rc = MIN(conf_filechunk, filebuflen - buf_widx);
         rc = MIN(rc, fill_bytesleft);
-        rc = read(current_fd, &codecbuf[buf_widx], rc);
+        rc = read(current_fd, &filebuf[buf_widx], rc);
         if (rc <= 0) {
             tracks[track_widx].filerem = 0;
             strip_id3v1_tag();
@@ -613,13 +795,13 @@
         }
         
         buf_widx += rc;
-        if (buf_widx >= codecbuflen)
-            buf_widx -= codecbuflen;
+        if (buf_widx >= filebuflen)
+            buf_widx -= filebuflen;
         i += rc;
         tracks[track_widx].available += rc;
         tracks[track_widx].filerem -= rc;
         tracks[track_widx].filepos += rc;
-        codecbufused += rc;
+        filebufused += rc;
         fill_bytesleft -= rc;
     }
     
@@ -725,15 +907,15 @@
     while (i < size) {
         yield_codecs();
         
-        copy_n = MIN(conf_filechunk, codecbuflen - buf_widx);
-        rc = read(fd, &codecbuf[buf_widx], copy_n);
+        copy_n = MIN(conf_filechunk, filebuflen - buf_widx);
+        rc = read(fd, &filebuf[buf_widx], copy_n);
         if (rc < 0)
             return false;
         buf_widx += rc;
-        codecbufused += rc;
+        filebufused += rc;
         fill_bytesleft -= rc;
-        if (buf_widx >= codecbuflen)
-            buf_widx -= codecbuflen;
+        if (buf_widx >= filebuflen)
+            buf_widx -= filebuflen;
         i += rc;
     }
     close(fd);
@@ -840,20 +1022,23 @@
     tracks[track_widx].playlist_offset = peek_offset;
     last_peek_offset = peek_offset;
     
-    if (buf_widx >= codecbuflen)
-        buf_widx -= codecbuflen;
+    if (buf_widx >= filebuflen)
+        buf_widx -= filebuflen;
     
     /* Set default values */
     if (start_play) {
+        int last_codec = current_codec;
+        current_codec = CODEC_IDX_AUDIO;
         conf_bufferlimit = 0;
         conf_watermark = AUDIO_DEFAULT_WATERMARK;
         conf_filechunk = AUDIO_DEFAULT_FILECHUNK;
         dsp_configure(DSP_RESET, 0);
         ci.configure(CODEC_DSP_ENABLE, false);
+        current_codec = last_codec;
     }
 
     /* Load the codec. */
-    tracks[track_widx].codecbuf = &codecbuf[buf_widx];
+    tracks[track_widx].codecbuf = &filebuf[buf_widx];
     if (!loadcodec(trackname, start_play)) {
         close(fd);
         /* Stop buffer filling if codec load failed. */
@@ -870,7 +1055,7 @@
         }
         return false;
     }
-    // tracks[track_widx].filebuf = &codecbuf[buf_widx];
+    // tracks[track_widx].filebuf = &filebuf[buf_widx];
     tracks[track_widx].start_pos = 0;
         
     /* Get track metadata if we don't already have it. */
@@ -933,10 +1118,10 @@
         if (fill_bytesleft == 0)
             break ;
         
-        copy_n = MIN(conf_filechunk, codecbuflen - buf_widx);
+        copy_n = MIN(conf_filechunk, filebuflen - buf_widx);
         copy_n = MIN(size - i, copy_n);
         copy_n = MIN((int)fill_bytesleft, copy_n);
-        rc = read(fd, &codecbuf[buf_widx], copy_n);
+        rc = read(fd, &filebuf[buf_widx], copy_n);
         if (rc < copy_n) {
             logf("File error!");
             tracks[track_widx].filesize = 0;
@@ -945,12 +1130,12 @@
             return false;
         }
         buf_widx += rc;
-        if (buf_widx >= codecbuflen)
-            buf_widx -= codecbuflen;
+        if (buf_widx >= filebuflen)
+            buf_widx -= filebuflen;
         i += rc;
         tracks[track_widx].available += rc;
         tracks[track_widx].filerem -= rc;
-        codecbufused += rc;
+        filebufused += rc;
         fill_bytesleft -= rc;
     }
     
@@ -997,10 +1182,10 @@
     track_ridx = 0;
     buf_ridx = 0;
     buf_widx = 0;
-    codecbufused = 0;
+    filebufused = 0;
     pcmbuf_set_boost_mode(true);
     
-    fill_bytesleft = codecbuflen;
+    fill_bytesleft = filebuflen;
     filling = true;
     last_peek_offset = -1;
     if (audio_load_track(offset, true, 0)) {
@@ -1089,7 +1274,7 @@
     int cur_idx, i;
     
     
-    fill_bytesleft = codecbuflen - codecbufused;
+    fill_bytesleft = filebuflen - filebufused;
     cur_ti->start_pos = ci.curpos;
 
     pcmbuf_set_boost_mode(true);
@@ -1124,7 +1309,7 @@
 void audio_check_buffer(void)
 {
     /* Start buffer filling as necessary. */
-    if ((codecbufused > conf_watermark || !queue_empty(&audio_queue) 
+    if ((filebufused > conf_watermark || !queue_empty(&audio_queue)
         || !playing || ci.stop_codec || ci.reload_codec) && !filling)
         return ;
     
@@ -1132,8 +1317,8 @@
     
     /* Limit buffering size at first run. */
     if (conf_bufferlimit && fill_bytesleft > conf_bufferlimit
-            - codecbufused) {
-        fill_bytesleft = MAX(0, conf_bufferlimit - codecbufused);
+            - filebufused) {
+        fill_bytesleft = MAX(0, conf_bufferlimit - filebufused);
     }
     
     /* Try to load remainings of the file. */
@@ -1169,27 +1354,27 @@
 {
     if (new_track >= 0) {
         buf_ridx += cur_ti->available;
-        codecbufused -= cur_ti->available;
+        filebufused -= cur_ti->available;
         
         cur_ti = &tracks[track_ridx];
         buf_ridx += cur_ti->codecsize;
-        codecbufused -= cur_ti->codecsize;
-        if (buf_ridx >= codecbuflen)
-            buf_ridx -= codecbuflen;
+        filebufused -= cur_ti->codecsize;
+        if (buf_ridx >= filebuflen)
+            buf_ridx -= filebuflen;
             
         if (!filling)
             pcmbuf_set_boost_mode(false);
     } else {
         buf_ridx -= ci.curpos + cur_ti->codecsize;
-        codecbufused += ci.curpos + cur_ti->codecsize;
+        filebufused += ci.curpos + cur_ti->codecsize;
         cur_ti->available = cur_ti->filesize;
         
         cur_ti = &tracks[track_ridx];
         buf_ridx -= cur_ti->filesize;
-        codecbufused += cur_ti->filesize;
+        filebufused += cur_ti->filesize;
         cur_ti->available = cur_ti->filesize;
         if (buf_ridx < 0)
-            buf_ridx = codecbuflen + buf_ridx;
+            buf_ridx = filebuflen + buf_ridx;
     }
     
     ci.filesize = cur_ti->filesize;
@@ -1220,7 +1405,7 @@
         current_fd = -1;
     }
     pcmbuf_play_stop();
-    while (codec_loaded)
+    while (audio_codec_loaded)
         yield();
     pcm_play_pause(true);
     track_count = 0;
@@ -1267,6 +1452,11 @@
 
 bool codec_request_next_track_callback(void)
 {
+    if (current_codec == CODEC_IDX_VOICE) {
+        voice_remaining = 0;
+        return !ci_voice.stop_codec;
+    }
+        
     if (ci.stop_codec || !playing)
         return false;
     
@@ -1309,8 +1499,8 @@
         if (--track_ridx < 0)
             track_ridx = MAX_TRACK-1;
         if (tracks[track_ridx].filesize == 0 || 
-            codecbufused+ci.curpos+tracks[track_ridx].filesize
-            /*+ (off_t)tracks[track_ridx].codecsize*/ > codecbuflen) {
+            filebufused+ci.curpos+tracks[track_ridx].filesize
+            /*+ (off_t)tracks[track_ridx].codecsize*/ > filebuflen) {
             logf("Loading from disk...");
             new_track = 0;
             last_index = -1;
@@ -1379,10 +1569,10 @@
     track_widx = track_ridx;
     /* Mark all other entries null (also buffered wrong metadata). */
     audio_clear_track_entries(false);
-    codecbufused = cur_ti->available;
+    filebufused = cur_ti->available;
     buf_widx = buf_ridx + cur_ti->available;
-    if (buf_widx >= codecbuflen)
-        buf_widx -= codecbuflen;
+    if (buf_widx >= filebuflen)
+        buf_widx -= filebuflen;
     read_next_metadata();
 }
 
@@ -1436,7 +1626,7 @@
                 ci.reload_codec = false;
                 ci.seek_time = 0;
                 pcmbuf_crossfade_init(CROSSFADE_MODE_CROSSFADE);
-                while (codec_loaded)
+                while (audio_codec_loaded)
                     yield();
                 audio_play_start((int)ev.data);
                 playlist_update_resume_info(audio_current_track());
@@ -1462,11 +1652,13 @@
             
             case AUDIO_NEXT:
                 logf("audio_next");
+                pcmbuf_beep(5000, 100, 5000);
                 initiate_track_change(1);
                 break ;
                 
             case AUDIO_PREV:
                 logf("audio_prev");
+                pcmbuf_beep(5000, 100, 5000);
                 initiate_track_change(-1);
                 break;
                 
@@ -1514,8 +1706,11 @@
         switch (ev.id) {
             case CODEC_LOAD_DISK:
                 ci.stop_codec = false;
-                codec_loaded = true;
-                status = codec_load_file((char *)ev.data);
+                audio_codec_loaded = true;
+                mutex_lock(&mutex_codecthread);
+                current_codec = CODEC_IDX_AUDIO;
+                status = codec_load_file((char *)ev.data, &ci);
+                mutex_unlock(&mutex_codecthread);
                 break ;
                 
             case CODEC_LOAD:
@@ -1531,10 +1726,13 @@
                 }
                 
                 ci.stop_codec = false;
-                wrap = (int)&codecbuf[codecbuflen] - (int)cur_ti->codecbuf;
-                codec_loaded = true;
-                status = codec_load_ram(cur_ti->codecbuf,  codecsize, 
-                                        &codecbuf[0], wrap);
+                wrap = (int)&filebuf[filebuflen] - (int)cur_ti->codecbuf;
+                audio_codec_loaded = true;
+                mutex_lock(&mutex_codecthread);
+                current_codec = CODEC_IDX_AUDIO;
+                status = codec_load_ram(cur_ti->codecbuf,  codecsize,
+                                        &filebuf[0], wrap, &ci);
+                mutex_unlock(&mutex_codecthread);
                 break ;
 
 #ifndef SIMULATOR                
@@ -1545,7 +1743,7 @@
 #endif
         }
 
-        codec_loaded = false;
+        audio_codec_loaded = false;
         
         switch (ev.id) {
         case CODEC_LOAD_DISK:
@@ -1569,6 +1767,83 @@
     }
 }
 
+static void reset_buffer(void)
+{
+    filebuf = &audiobuf[MALLOC_BUFSIZE];
+    filebuflen = audiobufend - audiobuf - pcmbuf_get_bufsize()
+                  - PCMBUF_GUARD - MALLOC_BUFSIZE - GUARD_BUFSIZE;
+                  
+    if (talk_get_bufsize() && voice_codec_loaded)
+    {
+        filebuf = &filebuf[talk_get_bufsize()];
+        filebuflen -= 2*CODEC_IRAM_SIZE + 2*CODEC_SIZE + talk_get_bufsize();
+    }
+}
+
+void voice_codec_thread(void)
+{
+    struct event ev;
+    int status;
+
+    current_codec = CODEC_IDX_AUDIO;
+    voice_codec_loaded = false;
+    while (1) {
+        status = 0;
+        queue_wait(&voice_codec_queue, &ev);
+        switch (ev.id) {
+            case CODEC_LOAD_DISK:
+                logf("Loading voice codec");
+                audio_stop_playback();
+                mutex_lock(&mutex_codecthread);
+                current_codec = CODEC_IDX_VOICE;
+                dsp_configure(DSP_RESET, 0);
+                ci.configure(CODEC_DSP_ENABLE, (bool *)true);
+                voice_remaining = 0;
+                voice_getmore = NULL;
+                voice_codec_loaded = true;
+                reset_buffer();
+                ci_voice.stop_codec = false;
+                
+                status = codec_load_file((char *)ev.data, &ci_voice);
+                
+                logf("Voice codec finished");
+                audio_stop_playback();
+                mutex_unlock(&mutex_codecthread);
+                current_codec = CODEC_IDX_AUDIO;
+                voice_codec_loaded = false;
+                reset_buffer();
+                break ;
+
+#ifndef SIMULATOR                
+            case SYS_USB_CONNECTED:
+                usb_acknowledge(SYS_USB_CONNECTED_ACK);
+                usb_wait_for_disconnect(&voice_codec_queue);
+                break ;
+#endif
+        }
+    }
+}
+
+void voice_init(void)
+{
+    while (voice_codec_loaded)
+    {
+        logf("Terminating voice codec");
+        ci_voice.stop_codec = true;
+        if (current_codec != CODEC_IDX_VOICE)
+            swap_codec();
+        sleep(1);
+    }
+
+    if (!talk_get_bufsize())
+        return ;
+        
+    logf("Starting voice codec");
+    queue_post(&voice_codec_queue, CODEC_LOAD_DISK, (void *)CODEC_MPA_L3);
+    while (!voice_codec_loaded)
+        sleep(1);
+}
+
 struct mp3entry* audio_current_track(void)
 {
     // logf("audio_current_track");
@@ -1620,7 +1895,7 @@
 {
     logf("audio_stop");
     queue_post(&audio_queue, AUDIO_STOP, 0);
-    while (playing || codec_loaded)
+    while (playing || audio_codec_loaded)
         yield();
 }
 
@@ -1805,6 +2080,16 @@
     return pos;    
 }
 
+void mp3_play_data(const unsigned char* start, int size,
+                   void (*get_more)(unsigned char** start, int* size))
+{
+    voice_getmore = get_more;
+    voicebuf = (unsigned char *)start;
+    voice_remaining = size;
+    voice_is_playing = true;
+    pcmbuf_reset_mixpos();
+}
+
 void audio_set_buffer_margin(int setting)
 {
     int lookup[] = {5, 15, 30, 60, 120, 180, 300, 600};
@@ -1827,7 +2112,7 @@
         offset = cur_ti->id3.offset;
 
     if (type == CROSSFADE_MODE_OFF)
-        seconds = 0;
+        seconds = 1;
         
     /* Buffer has to be at least 2s long. */
     seconds += 2;
@@ -1843,12 +2128,13 @@
     if (was_playing)
         splash(0, true, str(LANG_RESTARTING_PLAYBACK));
     pcmbuf_init(size);
-    pcmbuf_crossfade_enable(seconds > 2);
-    codecbuflen = audiobufend - audiobuf - pcmbuf_get_bufsize()
-                  - PCMBUF_GUARD - MALLOC_BUFSIZE - GUARD_BUFSIZE;
+    pcmbuf_crossfade_enable(type != CROSSFADE_MODE_OFF);
+    reset_buffer();
     logf("abuf:%dB", pcmbuf_get_bufsize());
-    logf("fbuf:%dB", codecbuflen);
+    logf("fbuf:%dB", filebuflen);
 
+    voice_init();
+    
     /* Restart playback. */
     if (was_playing) {
         audio_play(offset);
@@ -1856,7 +2142,7 @@
         /* Wait for the playback to start again (and display the splash
            screen during that period. */
         playing = true;
-        while (playing && !codec_loaded)
+        while (playing && !audio_codec_loaded)
             yield();
     }
 }
@@ -1884,13 +2170,17 @@
 
 void audio_init(void)
 {
+    static bool voicetagtrue = true;
+    
     logf("audio api init");
     pcm_init();
-    codecbufused = 0;
+    filebufused = 0;
     filling = false;
-    codecbuf = &audiobuf[MALLOC_BUFSIZE];
+    current_codec = CODEC_IDX_AUDIO;
+    filebuf = &audiobuf[MALLOC_BUFSIZE];
     playing = false;
-    codec_loaded = false;
+    audio_codec_loaded = false;
+    voice_is_playing = false;
     paused = false;
     track_changed = false;
     current_fd = -1;
@@ -1918,12 +2208,25 @@
     ci.set_offset = codec_set_offset_callback;
     ci.configure = codec_configure_callback;
 
+    memcpy(&ci_voice, &ci, sizeof(struct codec_api));
+    memset(&id3_voice, 0, sizeof(struct mp3entry));
+    ci_voice.taginfo_ready = &voicetagtrue;
+    ci_voice.id3 = &id3_voice;
+    ci_voice.pcmbuf_insert = codec_pcmbuf_insert_callback;
+    id3_voice.frequency = 11200;
+    id3_voice.length = 1000000L;
+    
     mutex_init(&mutex_bufferfill);
+    mutex_init(&mutex_codecthread);
+    
     queue_init(&audio_queue);
     queue_init(&codec_queue);
+    queue_init(&voice_codec_queue);
     
     create_thread(codec_thread, codec_stack, sizeof(codec_stack),
                   codec_thread_name);
+    create_thread(voice_codec_thread, voice_codec_stack,
+                  sizeof(voice_codec_stack), voice_codec_thread_name);
     create_thread(audio_thread, audio_stack, sizeof(audio_stack),
                   audio_thread_name);
 }
diff --git a/apps/playback.h b/apps/playback.h
index 7ed9a4b..a5b64ba 100644
--- a/apps/playback.h
+++ b/apps/playback.h
@@ -72,6 +72,7 @@
 void audio_set_track_unbuffer_event(void (*handler)(struct mp3entry *id3,
                                                    bool last_track));
 void audio_invalidate_tracks(void);
+void voice_init(void);
 
 #endif
 
diff --git a/apps/playlist.c b/apps/playlist.c
index 68fd8be..bd443e4 100644
--- a/apps/playlist.c
+++ b/apps/playlist.c
@@ -1413,9 +1413,14 @@
     };
 
     /* use mp3 buffer for maximum load speed */
+#if CONFIG_HWCODEC != MASNONE
     talk_buffer_steal(); /* we use the mp3 buffer, need to tell */
     buflen = (audiobufend - audiobuf);
     buffer = audiobuf;
+#else
+    buflen = (audiobufend - audiobuf - talk_get_bufsize());
+    buffer = &audiobuf[talk_get_bufsize()];
+#endif
 
     empty_playlist(playlist, true);
 
@@ -1827,7 +1832,9 @@
     struct playlist_info* playlist = &current_playlist;
 
     playlist->index = start_index;
+#if CONFIG_HWCODEC != MASNONE
     talk_buffer_steal(); /* will use the mp3 buffer */
+#endif
     audio_play(offset);
 
     return 0;
diff --git a/apps/talk.c b/apps/talk.c
index a896ca3..b417046 100644
--- a/apps/talk.c
+++ b/apps/talk.c
@@ -32,7 +32,11 @@
 #include "lang.h"
 #include "talk.h"
 #include "id3.h"
+#include "logf.h"
 #include "bitswap.h"
+#if CONFIG_HWCODEC == MASNONE
+#include "playback.h"
+#endif
 
 /***************** Constants *****************/
 
@@ -88,6 +92,7 @@
 static unsigned char* p_silence; /* VOICE_PAUSE clip, used for termination */
 static long silence_len; /* length of the VOICE_PAUSE clip */
 static unsigned char* p_lastclip; /* address of latest clip, for silence add */
+static unsigned long voicefile_size = 0; /* size of the loaded voice file */
 
 
 /***************** Private prototypes *****************/
@@ -114,10 +119,28 @@
     }
 
     snprintf(buf, sizeof(buf), ROCKBOX_DIR LANG_DIR "/%s.voice", p_lang);
-
+    
     return open(buf, O_RDONLY);
 }
 
+int talk_get_bufsize(void)
+{
+    return voicefile_size;
+}
+
+#ifdef SIMULATOR
+static unsigned short BSWAP16(unsigned short value)
+{
+    return (value >> 8) | (value << 8);
+}
+
+static unsigned long BSWAP32(unsigned long value)
+{
+    unsigned long hi = BSWAP16(value >> 16);
+    unsigned long lo = BSWAP16(value & 0xffff);
+    return (lo << 16) | hi;
+}
+#endif
 
 /* load the voice file into the mp3 buffer */
 static void load_voicefile(void)
@@ -125,6 +148,10 @@
     int load_size;
     int got_size;
     int file_size;
+#if CONFIG_HWCODEC == MASNONE    
+    int length, i;
+    unsigned char *buf, temp;
+#endif
 
     filehandle = open_voicefile();
     if (filehandle < 0) /* failed to open */
@@ -141,8 +168,20 @@
 #endif
 
     got_size = read(filehandle, audiobuf, load_size);
-    if (got_size == load_size /* success */
-        && ((struct voicefile*)audiobuf)->table /* format check */
+    if (got_size != load_size /* failure */)
+        goto load_err;
+            
+#ifdef SIMULATOR
+    logf("Byte swapping voice file");
+    p_voicefile = (struct voicefile*)audiobuf;
+    p_voicefile->version = BSWAP32(p_voicefile->version);
+    p_voicefile->table = BSWAP32(p_voicefile->table);
+    p_voicefile->id1_max = BSWAP32(p_voicefile->id1_max);
+    p_voicefile->id2_max = BSWAP32(p_voicefile->id2_max);
+    p_voicefile = NULL;
+#endif
+
+    if (((struct voicefile*)audiobuf)->table /* format check */
            == offsetof(struct voicefile, index))
     {
         p_voicefile = (struct voicefile*)audiobuf;
@@ -155,7 +194,42 @@
     else
        goto load_err;
 
-#ifdef HAVE_MMC 
+#ifdef SIMULATOR
+    for (i = 0; i < p_voicefile->id1_max + p_voicefile->id2_max; i++)
+    {
+        struct clip_entry *ce;
+        ce = &p_voicefile->index[i];
+        ce->offset = BSWAP32(ce->offset);
+        ce->size = BSWAP32(ce->size);
+    }
+#endif
+
+    /* Do a bitswap as necessary. */
+#if CONFIG_HWCODEC == MASNONE
+    logf("Bitswapping voice file.");
+    cpu_boost(true);
+    buf = (unsigned char *)(&p_voicefile->index) +
+        (p_voicefile->id1_max + p_voicefile->id2_max) * sizeof(struct clip_entry);
+    length = file_size - offsetof(struct voicefile, index) -
+               (p_voicefile->id1_max - p_voicefile->id2_max) * sizeof(struct clip_entry);
+               
+    for (i = 0; i < length; i++)
+    {
+        temp = buf[i];
+        buf[i] = ((temp >> 7) & 0x01)
+                | ((temp >> 5) & 0x02)
+                | ((temp >> 3) & 0x04)
+                | ((temp >> 1) & 0x08)
+                | ((temp << 1) & 0x10)
+                | ((temp << 3) & 0x20)
+                | ((temp << 5) & 0x40)
+                | ((temp << 7) & 0x80);
+    }
+    cpu_boost(false);
+    
+#endif
+
+#ifdef HAVE_MMC
     /* load the index table, now that we know its size from the header */
     load_size = (p_voicefile->id1_max + p_voicefile->id2_max)
                 * sizeof(struct clip_entry);
@@ -193,7 +267,11 @@
 
     if (queue[queue_read].len > 0) /* current clip not finished? */
     {   /* feed the next 64K-1 chunk */
+#if CONFIG_HWCODEC != MASNONE
         sent = MIN(queue[queue_read].len, 0xFFFF);
+#else
+        sent = queue[queue_read].len;
+#endif
         *start = queue[queue_read].buf;
         *size = sent;
         return;
@@ -207,7 +285,11 @@
 
     if (QUEUE_LEVEL) /* queue is not empty? */
     {   /* start next clip */
+#if CONFIG_HWCODEC != MASNONE
         sent = MIN(queue[queue_read].len, 0xFFFF);
+#else
+        sent = queue[queue_read].len;
+#endif
         *start = p_lastclip = queue[queue_read].buf;
         *size = sent;
         curr_hd[0] = p_lastclip[1];
@@ -286,7 +368,7 @@
     /* nothing to do, was frame boundary or not our clip */
     mp3_play_stop();
     queue_write = queue_read = 0; /* reset the queue */
-
+    
     return 0;
 }
 
@@ -317,7 +399,11 @@
     if (queue_level == 0)
     {   /* queue was empty, we have to do the initial start */
         p_lastclip = buf;
+#if CONFIG_HWCODEC != MASNONE
         sent = MIN(size, 0xFFFF); /* DMA can do no more */
+#else
+        sent = size;
+#endif
         mp3_play_data(buf, sent, mp3_callback);
         curr_hd[0] = buf[1];
         curr_hd[1] = buf[2];
@@ -400,12 +486,19 @@
 #else
     filehandle = open_voicefile();
     has_voicefile = (filehandle >= 0); /* test if we can open it */
+    voicefile_size = 0;
+    
     if (has_voicefile)
     {
+        voicefile_size = filesize(filehandle);
+#if CONFIG_HWCODEC == MASNONE
+        voice_init();
+#endif
         close(filehandle); /* close again, this was just to detect presence */
         filehandle = -1;
     }
 #endif
+
 }
 
 
@@ -432,8 +525,10 @@
     unsigned char* clipbuf;
     int unit;
 
+#if CONFIG_HWCODEC != MASNONE
     if (audio_status()) /* busy, buffer in use */
         return -1; 
+#endif
 
     if (p_voicefile == NULL && has_voicefile)
         load_voicefile(); /* reload needed */
@@ -514,8 +609,10 @@
     int level = 0; /* mille count */
     long mil = 1000000000; /* highest possible "-illion" */
 
+#if CONFIG_HWCODEC != MASNONE
     if (audio_status()) /* busy, buffer in use */
         return -1; 
+#endif
 
     if (!enqueue)
         shutup(); /* cut off all the pending stuff */
@@ -593,8 +690,10 @@
         VOICE_HERTZ,
     };
 
+#if CONFIG_HWCODEC != MASNONE
     if (audio_status()) /* busy, buffer in use */
         return -1; 
+#endif
 
     if (unit < 0 || unit >= UNIT_LAST)
         unit_id = -1;
@@ -625,8 +724,10 @@
 {
     char c; /* currently processed char */
     
+#if CONFIG_HWCODEC != MASNONE
     if (audio_status()) /* busy, buffer in use */
         return -1; 
+#endif
 
     if (!enqueue)
         shutup(); /* cut off all the pending stuff */
diff --git a/apps/talk.h b/apps/talk.h
index 213e180..18314e5 100644
--- a/apps/talk.h
+++ b/apps/talk.h
@@ -59,6 +59,7 @@
 extern const char* const file_thumbnail_ext; /* ".talk" for file voicing */
 
 void talk_init(void);
+int talk_get_bufsize(void); /* get the loaded voice file size */
 int talk_buffer_steal(void); /* claim the mp3 buffer e.g. for play/record */
 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 */