Index: apps/pcmbuf.c
===================================================================
--- apps/pcmbuf.c	(revision 29842)
+++ apps/pcmbuf.c	(working copy)
@@ -29,7 +29,7 @@
 #include "codec_thread.h"
 
 /* Define LOGF_ENABLE to enable logf output in this file */
-/*#define LOGF_ENABLE*/
+#define LOGF_ENABLE
 #include "logf.h"
 #if (CONFIG_PLATFORM & PLATFORM_NATIVE)
 #include "cpu.h"
@@ -52,6 +52,11 @@
 #define AUX_BUFSIZE           512 /* Size of the aux buffer; can be 512 if no
                                      resampling or timestretching is allowed in
                                      the aux channel, must be 2048 otherwise */
+#ifdef SIMULATOR                  /* Size of just-in-time mixing chunks: */
+#define MIX_BUFSIZE         88200 /* SDL requires large premixed chunks */
+#else
+#define MIX_BUFSIZE          4096 /* Small chunks on targets for low latency */
+#endif
 
 /* number of bytes played per second (sample rate * 2 channels * 2 bytes/sample) */
 #define BYTERATE            (NATIVE_FREQUENCY * 4)
@@ -121,11 +126,46 @@
 static size_t pcmbuf_unplayed_bytes IDATA_ATTR;
 static size_t pcmbuf_watermark IDATA_ATTR;
 
-/* Voice */
-static char *voicebuf IDATA_ATTR;
-static struct chunkdesc *mix_chunk IDATA_ATTR;
-static size_t pcmbuf_mix_sample IDATA_ATTR;
+/* Aux playback */
+static char *auxbuf IDATA_ATTR;
+static bool auxbuf_in_use;
 
+/* Mixer */
+struct mixdesc
+{
+    unsigned char *addr;
+    size_t used;
+};
+
+static enum {
+    MIXER_OFF = 0,
+    MIXER_FADE,
+    MIXER_ON,
+} mixer_state;
+
+#define FILL_BUF mixchunk[mixbuf]       /* currently filling buffer */
+#define PLAY_BUF mixchunk[1 - mixbuf]   /* currently playing buffer */
+
+static struct mixdesc *mixchunk[2] IDATA_ATTR;
+static struct chunkdesc *pcmchunk;
+static size_t pcmoffset;
+static int  mixbuf;
+static bool pcmbuf_playing;
+static bool pcmbuf_paused;
+static bool aux_playing;
+
+/* mixer pcm fade info */
+static int    fade_step;
+static size_t fade_pos;
+static size_t fade_count;
+static bool   fade_out;
+#define FADECHUNK     4096  /* Size of mixer fade chunk (can't be larger
+                               than MIX_BUFSIZE ) */
+#define FADE_PARTIAL    64  /* -6 dB */
+#define FADE_NOFADE    256  /* full volume */
+#define FADEC           24  /* change between mixing packets */
+
+
 static bool low_latency_mode = false;
 static bool flush_pcmbuf = false;
 
@@ -145,6 +185,7 @@
 static void write_to_crossfade(size_t length);
 static void pcmbuf_finish_crossfade_enable(void);
 #endif
+static bool mixer_interrupt_handler(unsigned char** start, size_t* size) ICODE_ATTR;
 
 /* Callbacks into playback.c */
 extern void audio_pcmbuf_position_callback(unsigned int time);
@@ -210,7 +251,7 @@
     /* Never use the last buffer descriptor */
     while (write_chunk == write_end_chunk) {
         /* If this happens, something is being stupid */
-        if (!pcm_is_playing()) {
+        if (!pcmbuf_is_playing()) {
             logf("commit_chunk error");
             pcmbuf_play_start();
         }
@@ -320,7 +361,7 @@
     if (low_latency_mode)
     {
         /* 1/4s latency. */
-        if (!LOW_DATA(1) && pcm_is_playing())
+        if (!LOW_DATA(1) && pcmbuf_is_playing())
             return false;
     }
 
@@ -329,7 +370,7 @@
         return false;
 
     /* Maintain the buffer level above the watermark */
-    if (pcm_is_playing())
+    if (pcmbuf_is_playing())
     {
         /* Only codec thread initiates boost - voice boosts the cpu when playing
            a clip */
@@ -351,7 +392,7 @@
         }
 #endif
     }
-    else    /* pcm_is_playing */
+    else    /* pcmbuf_is_playing */
     {
         /* Boost CPU for pre-buffer */
         trigger_cpu_boost();
@@ -466,19 +507,23 @@
 }
 
 /* Initialize the pcmbuffer the structure looks like this:
- * ...|---------PCMBUF---------|FADEBUF|VOICEBUF|DESCS|... */
+ * ...|---PCMBUF---|(FADEBUF)|MIXBUF1|MIXBUF0|AUXBUF|MIXDESCS|CHUNKDESCS|... */
 size_t pcmbuf_init(unsigned char *bufend)
 {
-    pcmbuf_bufend = bufend;
-    pcmbuf_size = get_next_required_pcmbuf_size();
-    write_chunk = (struct chunkdesc *)pcmbuf_bufend -
+    pcmbuf_bufend     = bufend;
+    pcmbuf_size       = get_next_required_pcmbuf_size();
+    write_chunk       = (struct chunkdesc *)pcmbuf_bufend -
         NUM_CHUNK_DESCS(pcmbuf_size);
-    voicebuf = (char *)write_chunk - AUX_BUFSIZE;
+    mixchunk[0]       = (struct mixdesc *)write_chunk - 1;
+    mixchunk[1]       = mixchunk[0] - 1;
+    auxbuf            = (char *)mixchunk[1] - AUX_BUFSIZE;
+    mixchunk[0]->addr = auxbuf              - MIX_BUFSIZE;
+    mixchunk[1]->addr = mixchunk[0]->addr   - MIX_BUFSIZE;
 #ifdef HAVE_CROSSFADE
-    fadebuf = voicebuf - CROSSFADE_BUFSIZE;
-    pcmbuffer = fadebuf - pcmbuf_size;
+    fadebuf           = mixchunk[1]->addr   - CROSSFADE_BUFSIZE;
+    pcmbuffer         = fadebuf             - pcmbuf_size;
 #else
-    pcmbuffer = voicebuf - pcmbuf_size;
+    pcmbuffer         = mixchunk[1]->addr   - pcmbuf_size;
 #endif
 
     init_pcmbuffers();
@@ -489,6 +534,11 @@
     pcmbuf_watermark = PCMBUF_WATERMARK;
 #endif
 
+    mixer_state = MIXER_OFF;
+    aux_playing = false;
+    auxbuf_in_use = false;
+    fade_step = FADE_NOFADE;
+    
     pcmbuf_play_stop();
 
     return pcmbuf_bufend - pcmbuffer;
@@ -572,7 +622,7 @@
 #ifdef HAVE_CROSSFADE
             pcmbuf_is_crossfade_active() ||
 #endif
-            !pcm_is_playing())
+            !pcmbuf_is_playing())
         {
             pcmbuf_play_stop();
             pcm_play_unlock();
@@ -621,7 +671,7 @@
 
 /** Playback */
 
-/* PCM driver callback
+/* This is the core of the PCM driver callback
  * This function has 3 major logical parts (separated by brackets both for
  * readability and variable scoping).  The first part performs the
  * operations related to finishing off the last chunk we fed to the DMA.
@@ -629,8 +679,8 @@
  * buffer is empty except for uncommitted samples.  Then they are committed
  * and sent to the PCM driver for playback.  The third part performs the
  * operations involved in sending a new chunk to the DMA. */
-static void pcmbuf_pcm_callback(unsigned char** start, size_t* size) ICODE_ATTR;
-static void pcmbuf_pcm_callback(unsigned char** start, size_t* size)
+static void get_next_chunk(void) ICODE_ATTR;
+static void get_next_chunk(void)
 {
     {
         struct chunkdesc *pcmbuf_current = read_chunk;
@@ -652,10 +702,6 @@
         write_end_chunk->link = pcmbuf_current;
         write_end_chunk = pcmbuf_current;
 
-        /* If we've read over the mix chunk while it's still mixing there */
-        if (pcmbuf_current == mix_chunk)
-            mix_chunk = NULL;
-
 #ifdef HAVE_CROSSFADE
         /* If we've read over the crossfade chunk while it's still fading */
         if (pcmbuf_current == crossfade_chunk)
@@ -673,46 +719,91 @@
     }
 
     {
-        /* Send the new chunk to the DMA */
         if(read_chunk)
         {
             last_chunksize = read_chunk->size;
             pcmbuf_unplayed_bytes -= last_chunksize;
-            *size = last_chunksize;
-            *start = read_chunk->addr;
         }
         else
         {
             /* No more chunks */
             logf("pcmbuf_pcm_callback: no more chunks");
             last_chunksize = 0;
-            *size = 0;
-            *start = NULL;
+            pcmbuf_playing = false;
         }
     }
     DISPLAY_DESC("callback");
 }
 
+/* PCM driver callback
+ * This function does all the processing necessary to feed PCM chunks
+ * to the DMA. */
+static void pcmbuf_pcm_callback(unsigned char** start, size_t* size) ICODE_ATTR;
+static void pcmbuf_pcm_callback(unsigned char** start, size_t* size)
+{
+    /* Service the mixer before handling PCM chunks */
+    if (mixer_state && mixer_interrupt_handler(start, size))
+        return;
+    
+    if (read_chunk)
+        get_next_chunk();
+    
+    if (read_chunk && (mixer_state == MIXER_OFF))
+    {
+        *size = last_chunksize;
+        *start = read_chunk->addr;
+        return;
+    }
+    
+    *size = 0;
+    *start = NULL;
+}
+
+bool pcmbuf_is_playing(void)
+{
+    return pcmbuf_playing && pcm_is_playing();
+}
+
+bool pcmbuf_is_paused(void)
+{
+    return pcmbuf_paused;
+}
+
+static inline bool pcmbuf_playing_now(void)
+{
+    return pcmbuf_playing && !pcmbuf_paused;
+}
+
 /* Force playback */
 void pcmbuf_play_start(void)
 {
-    if (!pcm_is_playing() && pcmbuf_unplayed_bytes && read_chunk != NULL)
+    if (!pcmbuf_is_playing() && pcmbuf_unplayed_bytes && read_chunk != NULL)
     {
         logf("pcmbuf_play_start");
         last_chunksize = read_chunk->size;
         pcmbuf_unplayed_bytes -= last_chunksize;
-        pcm_play_data(pcmbuf_pcm_callback,
-            read_chunk->addr, last_chunksize);
+        pcmbuf_playing = true;
+        pcmbuf_paused = false;
+        if (!mixer_state)
+        {
+            pcm_play_data(pcmbuf_pcm_callback,
+                read_chunk->addr, last_chunksize);
+        }
     }
 }
 
 void pcmbuf_play_stop(void)
 {
     logf("pcmbuf_play_stop");
-    pcm_play_stop();
+    pcmbuf_playing = false;
+    pcmbuf_paused = false;
+    if (!mixer_state)
+    {
+        /* stop all playback */
+        pcm_play_stop();
+    }
 
     pcmbuf_unplayed_bytes = 0;
-    mix_chunk = NULL;
     if (read_chunk) {
         write_end_chunk->link = read_chunk;
         write_end_chunk = read_end_chunk;
@@ -737,15 +828,28 @@
 void pcmbuf_pause(bool pause)
 {
     logf("pcmbuf_pause: %s", pause?"pause":"play");
-    if (pcm_is_playing())
-        pcm_play_pause(!pause);
+    if (pcmbuf_is_playing())
+    {
+        pcmbuf_paused = pause;
+        if (mixer_state)
+        {
+            if (pause)
+            {
+                /* TODO: save pcmbuf state and play only aux */
+            }
+            else
+            {
+                /* TODO: resume pcmbuf and mix with aux */
+            }
+        }
+        else
+            /* can use pcm function to perform pause */
+            pcm_play_pause(!pause);
+    }
     else if (!pause)
         pcmbuf_play_start();
 }
 
-
-/** Crossfade */
-
 /* Clip sample to signed 16 bit range */
 static inline int32_t clip_sample_16(int32_t sample)
 {
@@ -754,7 +858,6 @@
     return sample;
 }
 
-#ifdef HAVE_CROSSFADE
 /* Find the chunk that's (length) deep in the list. Return the position within
  * the chunk, and leave the chunkdesc pointer pointing to the chunk. */
 static size_t find_chunk(size_t length, struct chunkdesc **chunk)
@@ -767,6 +870,10 @@
     return length;
 }
 
+
+/** Crossfade */
+
+#ifdef HAVE_CROSSFADE
 /* Returns the number of bytes _NOT_ mixed/faded */
 static size_t crossfade_mix_fade(int factor, size_t length, const char *buf,
                                  size_t *out_sample, struct chunkdesc **out_chunk)
@@ -1031,99 +1138,308 @@
 #endif /* HAVE_CROSSFADE */
 
 
-/** Voice */
+/** Aux playback */
 
-/* Returns pcm buffer usage in percents (0 to 100). */
-static int pcmbuf_usage(void)
+void *pcmbuf_request_aux_buffer(int *count)
 {
-    return pcmbuf_unplayed_bytes * 100 / pcmbuf_size;
-}
-
-static int pcmbuf_mix_free(void)
-{
-    if (mix_chunk)
+    if (!auxbuf_in_use)
     {
-        size_t my_mix_end =
-            (size_t)&((int16_t *)mix_chunk->addr)[pcmbuf_mix_sample];
-        size_t my_write_pos = (size_t)&pcmbuffer[pcmbuffer_pos];
-        if (my_write_pos < my_mix_end)
-            my_write_pos += pcmbuf_size;
-        return (my_write_pos - my_mix_end) * 100 / pcmbuf_unplayed_bytes;
+        *count = MIN(*count, AUX_BUFSIZE/4);
+        return auxbuf;
     }
-    return 100;
+    
+    return NULL;
 }
 
-void *pcmbuf_request_voice_buffer(int *count)
+/* Commit aux buffer for playback */
+void pcmbuf_write_aux_complete(int count)
 {
-    /* A get-it-to-work-for-now hack (audio status could change by
-       completion) */
-    if (audio_status() & AUDIO_STATUS_PLAY)
+    size_t bytes = (unsigned int)count << 2;
+    auxbuf_in_use = true;
+    int16_t *auxin = (int16_t *)auxbuf;
+    
+    if (aux_playing)
     {
-        if (read_chunk == NULL)
+        /* if no room in mix buffer, wait for mixer to switch buffers */
+        while ((bytes + FILL_BUF->used) > MIX_BUFSIZE)
+            sleep(1);
+    }
+    else
+    {
+        /* new aux insert, start with mix buffer 0 */
+        logf("\npcmbuf: new aux insert");
+        mixbuf = 0;
+        mixchunk[0]->used = 0;
+        aux_playing = true;
+        
+        if (pcmbuf_playing)
         {
-            return NULL;
+            /* get current location within pcm chunk */
+            pcm_play_pause(false);
+            pcmchunk = read_chunk;
+            pcmoffset = read_chunk->size - pcm_get_bytes_waiting();
         }
-        else if (pcmbuf_usage() >= 10 && pcmbuf_mix_free() >= 30 &&
-                 (mix_chunk || read_chunk->link))
+        else
         {
-            *count = MIN(*count, AUX_BUFSIZE/4);
-            return voicebuf;
+            pcmchunk = NULL;
+            pcmoffset = 0;
         }
+        
+        if (pcmbuf_playing_now())
+        {
+            fade_pos = pcmoffset;
+            
+            /* calculate start point for mixing */
+            if (fade_step < (FADE_PARTIAL + FADEC))
+                fade_step = FADE_PARTIAL + FADEC;
+            size_t delay = (fade_step - (FADE_PARTIAL + FADEC)) / FADEC * FADECHUNK;
+            pcmoffset = find_chunk(pcmoffset + delay, &pcmchunk);
+            
+            /* we're already playing, so resume playback right away with a
+               fade out; we'll be adding to the aux buffer in the meantime */
+            logf("MIXER_FADE(out): %ld bytes", (long)delay);
+            mixer_state = MIXER_FADE;
+            fade_out = true;
+            fade_count = FADECHUNK;
+            pcm_play_data(pcmbuf_pcm_callback, NULL, 0);
+        }
         else
         {
-            return NULL;
+            /* we're going to add to the aux buffer before starting playback */
+            logf("MIXER_ON");
+            mixer_state = MIXER_ON;
         }
     }
-    else
+    
+    /* Start mixing */
+    pcm_play_lock();
+
+    if (pcmbuf_playing_now())
     {
-        return pcmbuf_request_buffer(count);
+        /* mix pcm + aux */
+        while (bytes && pcmchunk)
+        {
+            /* only mix until end of pcm chunk or end of buffer */
+            size_t this_size = MIN(pcmchunk->size - pcmoffset,
+                MIX_BUFSIZE - FILL_BUF->used);
+            if (this_size > bytes)
+                this_size = bytes;
+//            logf("pcm + aux write: %d to mixbuf[%d]", this_size, mixbuf);
+            size_t this_count = this_size >> 2;
+            
+            int16_t *pcmin  = (int16_t *)(pcmchunk->addr + pcmoffset);
+            int16_t *outbuf = (int16_t *)(FILL_BUF->addr + FILL_BUF->used);
+
+            /* aux always played at full volume, pcm is played at -6db */
+            while (this_count--)
+            {
+                *outbuf++ = clip_sample_16((*pcmin++ >> 2) + (*auxin++ * 0));
+                *outbuf++ = clip_sample_16((*pcmin++ >> 2) + (*auxin++ * 0));
+            }
+            FILL_BUF->used += this_size;
+            pcmoffset += this_size;
+            bytes -= this_size;
+            
+            /* test for end of chunk */
+            if (pcmoffset >= pcmchunk->size)
+            {
+                logf("   Mixed end of chunk");
+                pcmchunk = pcmchunk->link;
+                pcmoffset = 0;
+            }
+            
+            /* test for full buffer with bytes left, should never happen */
+            if (bytes && (FILL_BUF->used >= MIX_BUFSIZE))
+            {
+                logf("   Mix buffer full");
+                pcm_play_unlock();
+                while ((bytes + FILL_BUF->used) > MIX_BUFSIZE)
+                    sleep(1);
+                pcm_play_lock();
+            }
+        }
     }
+    
+    /* copy remaining bytes directly to the mix buffer */
+    if (bytes)
+    {
+//        logf("aux write: %d to mixbuf[%d]", bytes, mixbuf);
+        memcpy(FILL_BUF->addr + FILL_BUF->used, (char *)auxin, bytes);
+        FILL_BUF->used += bytes;
+    }
+    
+    /* start playing if buffer full */
+    if (!pcm_is_playing() && (FILL_BUF->used >= MIX_BUFSIZE))
+    {
+        logf("   Mix buffer full: start playback");
+        pcm_play_data(pcmbuf_pcm_callback, NULL, 0);
+    }
+    
+    auxbuf_in_use = false;
+    pcm_play_unlock();
 }
 
-void pcmbuf_write_voice_complete(int count)
+/* start aux playback */
+void pcmbuf_aux_play(void)
 {
-    /* A get-it-to-work-for-now hack (audio status could have changed) */
-    if (!(audio_status() & AUDIO_STATUS_PLAY))
+    /* start aux playback by forcing an immediate DMA ISR callback */
+    if (mixer_state && !pcm_is_playing())
     {
-        pcmbuf_write_complete(count);
-        return;
+        logf("pcmbuf_aux_play");
+        pcm_play_data(pcmbuf_pcm_callback, NULL, 0);
     }
+}
 
-    int16_t *ibuf = (int16_t *)voicebuf;
-    int16_t *obuf;
-    size_t chunk_samples;
+bool pcmbuf_aux_is_playing(void)
+{
+    return aux_playing;
+}
 
-    if (mix_chunk == NULL && read_chunk != NULL)
+/* Operate the mixer during DMA ISR callback
+   Returns true if pcm chunk processing isn't needed */
+static bool mixer_interrupt_handler(unsigned char** start, size_t* size)
+{
+    switch (mixer_state)
     {
-        mix_chunk = read_chunk->link;
-        /* Start 1/8s into the next chunk */
-        pcmbuf_mix_sample = BYTERATE / 16;
-    }
-
-    if (!mix_chunk)
-        return;
-
-    obuf = (int16_t *)mix_chunk->addr;
-    chunk_samples = mix_chunk->size / sizeof (int16_t);
-
-    count <<= 1;
-
-    while (count-- > 0)
+        case MIXER_OFF: /* should never happen, avoids compiler warning */
+            break;
+        case MIXER_FADE:
+        mixer_fade:
+            /* FIXME: make this behave if entering at end of chunk */
+            if (read_chunk)
+            {
+                size_t mixbuf_rem = MIX_BUFSIZE;
+                int16_t *inbuf  = (int16_t *)(read_chunk->addr + fade_pos);
+                int16_t *outbuf = (int16_t *)mixchunk[1]->addr;
+                mixchunk[1]->used = 0;
+                size_t fade_rem = 0;
+                
+                do
+                {
+                    if (!fade_count)
+                    {
+                        fade_step += (fade_out ? -FADEC : FADEC);
+                        fade_count = FADECHUNK;
+                    }
+            
+                    if (fade_step >= FADE_NOFADE)
+                    {
+                        /* done fading in, stop mixing */
+                        logf("FADE(in) -> OFF");
+                        fade_step = FADE_NOFADE;
+                        pcmoffset = fade_pos;
+                        goto mixer_off;
+                    }
+                    if (fade_step <= FADE_PARTIAL)
+                    {
+                        /* done fading out, start mixer */
+                        logf("FADE(out) -> ON");
+                        fade_step = FADE_PARTIAL;
+                        mixer_state = MIXER_ON;
+                        goto mixer_on;
+                    }
+                    
+                    /* fade a small chunk of pcmbuf and put into mix buffer 1 */
+                    fade_rem = MIN(read_chunk->size - fade_pos, mixbuf_rem);
+                    if (fade_rem)
+                    {
+                        size_t count = MIN(fade_rem, fade_count);
+                        size_t samples = count / 2;
+                        while (samples--)
+                        {
+                            *outbuf++ = (int16_t)(((int32_t)*inbuf++ * fade_step) >> 8);
+                        }
+                        logf("mixer[1]: %ld\tfade step: %d", (long)count, fade_step);
+                        
+                        mixchunk[1]->used += count;
+                        fade_pos += count;
+                        fade_left -= count;
+                        fade_count -= count;
+                    }
+                }
+                while (fade_rem);
+                
+                /* play mix buffer 1 */
+                *size = mixchunk[1]->used;
+                *start = mixchunk[1]->addr;
+                
+                /* process next pcm chunk if this one has played out */
+                if (fade_pos >= read_chunk->size)
+                    fade_pos = 0;
+                
+                return (fade_pos > 0);
+            }
+            /* if aux_playing, start mixer */
+            /* if not, stop playback */
+            
+        case MIXER_ON:
+mixer_on:
+        {
+            /* switch mix buffers & init next buffer */
+            mixbuf = 1 - mixbuf;
+            FILL_BUF->used = 0;
+            
+            /* play buffered mix */
+            size_t chunk_size = PLAY_BUF->used;
+            logf("mixer[%d]: %ld", 1 - mixbuf, (long)chunk_size);
+            if (chunk_size)
+            {
+                /* feed new buffer to DMA */
+                *size  = chunk_size;
+                *start = PLAY_BUF->addr;
+                if (pcmbuf_playing_now() && read_chunk)
+                {
+                    /* process next pcm chunk if this one has played out */
+                    if (pcmoffset >= read_chunk->size)
+                        pcmoffset = 0;
+                    return pcmoffset;
+                }
+                return true;
+            }
+            
+            /* nothing buffered */
+            break;
+        }
+    }   /* switch */
+    
+mixer_off:
+    /* done mixing */
+    aux_playing = false;
+    if (pcmbuf_playing && read_chunk)
     {
-        int32_t sample = *ibuf++;
-
-        if (pcmbuf_mix_sample >= chunk_samples)
+        if (pcmbuf_paused || (fade_step == FADE_NOFADE))
         {
-            mix_chunk = mix_chunk->link;
-            if (!mix_chunk)
-                return;
-            pcmbuf_mix_sample = 0;
-            obuf = (int16_t *)mix_chunk->addr;
-            chunk_samples = mix_chunk->size / 2;
+            /* resume normal pcm playback */
+            logf("Resume PCM only playback");
+            mixer_state = MIXER_OFF;
+            pcm_play_pause(!pcmbuf_paused);
+            size_t chunk_size = read_chunk->size - pcmoffset;
+            if (chunk_size)
+            {
+                /* play out the rest of the chunk */
+                *size = chunk_size;
+                *start = read_chunk->addr + pcmoffset;
+                return true;
+            }
+            
+            /* end of chunk, let the callback handle it */
+            return false;
         }
-        sample += obuf[pcmbuf_mix_sample] >> 2;
-        obuf[pcmbuf_mix_sample++] = clip_sample_16(sample);
+        
+        /* continue pcmbuf only playback with a fade in */
+        logf("ON -> FADE(in)");
+        mixer_state = MIXER_FADE;
+        fade_out = false;
+        fade_pos = pcmoffset;
+        fade_count = FADECHUNK;
+        goto mixer_fade;
     }
+    /* stop all playback */
+    logf("MIXER_OFF, stop playback");
+    mixer_state = MIXER_OFF;
+    *size = 0;
+    *start = NULL;
+    return true;
 }
 
 
@@ -1178,7 +1494,7 @@
 
 bool pcmbuf_is_lowdata(void)
 {
-    if (!pcm_is_playing() || pcm_is_paused()
+    if (!pcmbuf_is_playing() || pcmbuf_paused
 #ifdef HAVE_CROSSFADE
         || pcmbuf_is_crossfade_active()
 #endif
Index: apps/pcmbuf.h
===================================================================
--- apps/pcmbuf.h	(revision 29842)
+++ apps/pcmbuf.h	(working copy)
@@ -34,6 +34,8 @@
 void pcmbuf_pause(bool pause);
 void pcmbuf_monitor_track_change(bool monitor);
 bool pcmbuf_start_track_change(bool manual_skip);
+bool pcmbuf_is_playing(void);
+bool pcmbuf_is_paused(void);
 
 /* Crossfade */
 #ifdef HAVE_CROSSFADE
@@ -50,9 +52,11 @@
     { return true; }
 #endif
 
-/* Voice */
-void *pcmbuf_request_voice_buffer(int *count);
-void pcmbuf_write_voice_complete(int count);
+/* Aux playback */
+void *pcmbuf_request_aux_buffer(int *count);
+void pcmbuf_write_aux_complete(int count);
+void pcmbuf_aux_play(void);
+bool pcmbuf_aux_is_playing(void);
 
 /* Debug menu, other metrics */
 size_t pcmbuf_free(void);
Index: apps/talk.c
===================================================================
--- apps/talk.c	(revision 29842)
+++ apps/talk.c	(working copy)
@@ -37,7 +37,7 @@
 #include "lang.h"
 #include "talk.h"
 #include "metadata.h"
-/*#define LOGF_ENABLE*/
+#define LOGF_ENABLE
 #include "logf.h"
 #include "bitswap.h"
 #include "structec.h"
Index: apps/voice_thread.c
===================================================================
--- apps/voice_thread.c	(revision 29842)
+++ apps/voice_thread.c	(working copy)
@@ -109,12 +109,6 @@
     int count;              /* Count of samples remaining to send to PCM */
 };
 
-/* Audio playback is in a playing state? */
-static inline bool playback_is_playing(void)
-{
-    return (audio_status() & AUDIO_STATUS_PLAY) != 0;
-}
-
 /* Stop any current clip and start playing a new one */
 void mp3_play_data(const unsigned char* start, int size,
                    pcm_play_callback_type get_more)
@@ -194,8 +188,7 @@
      * new clip by the time we wait. This should be resolvable if conditions
      * ever require knowing the very clip you requested has finished. */
 
-    /* Wait for PCM buffer to be exhausted. Works only if not playing. */
-    while(!voice_done || (!playback_is_playing() && pcm_is_playing()))
+    while(!voice_done || pcmbuf_aux_is_playing())
         sleep(1);
 }
 
@@ -239,11 +232,6 @@
                 /* Boost CPU now */
                 trigger_cpu_boost();
             }
-            else if (!playback_is_playing())
-            {
-                /* Just voice, stop any clip still playing */
-                pcmbuf_play_stop();
-            }
 
             /* Clean-start the decoder */
             td->st = speex_decoder_init(&speex_wb_mode);
@@ -258,12 +246,6 @@
         case Q_VOICE_STOP:
             LOGFQUEUE("voice < Q_VOICE_STOP: %ld", (long)td->ev.data);
 
-            if (td->ev.data != 0 && !playback_is_playing())
-            {
-                /* If not playing, it's just voice so stop pcm playback */
-                pcmbuf_play_stop();
-            }
-
             /* Cancel boost */
             cancel_cpu_boost();
 
@@ -360,10 +342,6 @@
                     goto voice_decode;
             }
 
-            /* If all clips are done and not playing, force pcm playback. */
-            if (!pcm_is_playing())
-                pcmbuf_play_start();
-
             /* Synthesize a stop request */
             /* NOTE: We have no way to know when the pcm data placed in the
              * buffer is actually consumed and playback has reached the end
@@ -400,7 +378,7 @@
                 if (!queue_empty(&voice_queue))
                     goto message_wait;
 
-                if ((dest = pcmbuf_request_voice_buffer(&out_count)) != NULL)
+                if ((dest = pcmbuf_request_aux_buffer(&out_count)) != NULL)
                     break;
 
                 yield();
@@ -423,10 +401,11 @@
             if (out_count <= 0)
                 break;
 
-            pcmbuf_write_voice_complete(out_count);
+            pcmbuf_write_aux_complete(out_count);
             td.count -= inp_count;
         }
 
+        pcmbuf_aux_play();
         yield();
     } /* end while */
 } /* voice_thread */
Index: firmware/target/hosted/sdl/pcm-sdl.c
===================================================================
--- firmware/target/hosted/sdl/pcm-sdl.c	(revision 29842)
+++ firmware/target/hosted/sdl/pcm-sdl.c	(working copy)
@@ -44,6 +44,8 @@
 /*#define LOGF_ENABLE*/
 #include "logf.h"
 
+#define DEBUG
+
 #ifdef DEBUG
 #include <stdio.h>
 extern bool debug_audio;
Index: firmware/target/hosted/sdl/system-sdl.c
===================================================================
--- firmware/target/hosted/sdl/system-sdl.c	(revision 29842)
+++ firmware/target/hosted/sdl/system-sdl.c	(working copy)
@@ -64,6 +64,8 @@
 
 static SDL_Thread *evt_thread = NULL;
 
+#define DEBUG
+
 #ifdef DEBUG
 bool debug_audio = false;
 #endif
