Separated buffering stuff from pcm_playback to pcmbuf. Renamed some
function calls (audiobuffer -> pcmbuf etc.).


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7131 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/SOURCES b/apps/SOURCES
index 271c2ba..c1f3dfc 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -49,6 +49,7 @@
 recorder/recording.c
 #endif
 #if CONFIG_HWCODEC == MASNONE
+pcmbuf.c
 playback.c
 metadata.c
 codecs.c
diff --git a/apps/codecs.h b/apps/codecs.h
index a675245..b2cf9e5 100644
--- a/apps/codecs.h
+++ b/apps/codecs.h
@@ -137,8 +137,8 @@
     void* (*get_codec_memory)(long *size);
     /* Insert PCM data into audio buffer for playback. Playback will start
        automatically. */
-    bool (*audiobuffer_insert)(char *data, long length);
-    bool (*audiobuffer_insert_split)(void *ch1, void *ch2, long length);
+    bool (*pcmbuf_insert)(char *data, long length);
+    bool (*pcmbuf_insert_split)(void *ch1, void *ch2, long length);
     /* Set song position in WPS (value in ms). */
     void (*set_elapsed)(unsigned int value);
     
@@ -238,8 +238,7 @@
     void (*bitswap)(unsigned char *data, int length);
 #endif
 #if CONFIG_HWCODEC == MASNONE
-    void (*pcm_play_data)(const unsigned char *start, int size,
-    void (*get_more)(unsigned char** start, long*size));
+    void (*pcm_play_data)(void (*get_more)(unsigned char** start, long*size));
     void (*pcm_play_stop)(void);
     void (*pcm_set_frequency)(unsigned int frequency);
     bool (*pcm_is_playing)(void);
diff --git a/apps/codecs/a52.c b/apps/codecs/a52.c
index 663e794..3dacc52 100644
--- a/apps/codecs/a52.c
+++ b/apps/codecs/a52.c
@@ -59,7 +59,7 @@
   }
 
   rb->yield();
-  while(!ci->audiobuffer_insert((unsigned char*)int16_samples,256*2*2))
+  while(!ci->pcmbuf_insert((unsigned char*)int16_samples,256*2*2))
     rb->yield();
 }
 
diff --git a/apps/codecs/flac.c b/apps/codecs/flac.c
index d7ae037..259686e 100644
--- a/apps/codecs/flac.c
+++ b/apps/codecs/flac.c
@@ -83,7 +83,7 @@
    ci->set_elapsed(samplesdone/(ci->id3->frequency/1000));
    
    rb->yield();
-   while (!ci->audiobuffer_insert(pcmbuf, data_size))
+   while (!ci->pcmbuf_insert(pcmbuf, data_size))
      rb->yield();
 
    return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
diff --git a/apps/codecs/mpa.c b/apps/codecs/mpa.c
index da469f2..7756567 100644
--- a/apps/codecs/mpa.c
+++ b/apps/codecs/mpa.c
@@ -221,16 +221,16 @@
                 ci->configure(DSP_SET_STEREO_MODE, (int *)STEREO_NONINTERLEAVED);
                 current_stereo_mode = STEREO_NONINTERLEAVED;
             }
-            ci->audiobuffer_insert_split(&Synth.pcm.samples[0][start_skip],
-                                         &Synth.pcm.samples[1][start_skip],
-                                         (Synth.pcm.length - start_skip) * 4);
+            ci->pcmbuf_insert_split(&Synth.pcm.samples[0][start_skip],
+                                    &Synth.pcm.samples[1][start_skip],
+                                    (Synth.pcm.length - start_skip) * 4);
         } else {
             if (current_stereo_mode != STEREO_MONO) {
                 ci->configure(DSP_SET_STEREO_MODE, (int *)STEREO_MONO);
                 current_stereo_mode = STEREO_MONO;
             }
-            ci->audiobuffer_insert((char *)&Synth.pcm.samples[0][start_skip],
-                                   (Synth.pcm.length - start_skip) * 4);
+            ci->pcmbuf_insert((char *)&Synth.pcm.samples[0][start_skip],
+                              (Synth.pcm.length - start_skip) * 4);
         }
         start_skip = 0; /* not very elegant, and might want to keep this value */
         
diff --git a/apps/codecs/mpc.c b/apps/codecs/mpc.c
index e3a13c5..abcb30b 100644
--- a/apps/codecs/mpc.c
+++ b/apps/codecs/mpc.c
@@ -166,7 +166,7 @@
                 /* Flush the buffer if it is full. */
                 if (OutputPtr == OutputBufferEnd) {
                     ci->yield();
-                    while (!ci->audiobuffer_insert(OutputBuffer, OUTPUT_BUFFER_SIZE))
+                    while (!ci->pcmbuf_insert(OutputBuffer, OUTPUT_BUFFER_SIZE))
                         ci->yield();
                     ci->set_elapsed(samplesdone/(frequency/1000));
                     OutputPtr = OutputBuffer;
@@ -178,7 +178,7 @@
     /* Flush the remaining data in the output buffer */
     if (OutputPtr > OutputBuffer) {
         ci->yield();
-        while (!ci->audiobuffer_insert(OutputBuffer, OutputPtr - OutputBuffer))
+        while (!ci->pcmbuf_insert(OutputBuffer, OutputPtr - OutputBuffer))
             ci->yield();
     }
 
diff --git a/apps/codecs/vorbis.c b/apps/codecs/vorbis.c
index 03f1ae9..20fb79a 100644
--- a/apps/codecs/vorbis.c
+++ b/apps/codecs/vorbis.c
@@ -262,7 +262,7 @@
         } else if (n < 0) {
             DEBUGF("Error decoding frame\n");
         } else {
-            while (!rb->audiobuffer_insert(pcmbuf, n)) {
+            while (!rb->pcmbuf_insert(pcmbuf, n)) {
                 rb->sleep(1);
                 if ( rb->seek_time ) {
                    /* Hmmm, a seek was requested. Throw out the
diff --git a/apps/codecs/wav.c b/apps/codecs/wav.c
index 49bd12d..527d33d 100644
--- a/apps/codecs/wav.c
+++ b/apps/codecs/wav.c
@@ -138,7 +138,7 @@
     ci->set_elapsed(samplesdone/(ci->id3->frequency/1000));
    
     rb->yield();
-    while (!ci->audiobuffer_insert((unsigned char*)wavbuf, n))
+    while (!ci->pcmbuf_insert((unsigned char*)wavbuf, n))
       rb->yield();
 
     ci->advance_buffer(n);
diff --git a/apps/codecs/wavpack.c b/apps/codecs/wavpack.c
index d363cbe..f76ac5a 100644
--- a/apps/codecs/wavpack.c
+++ b/apps/codecs/wavpack.c
@@ -189,7 +189,7 @@
         if (ci->stop_codec || ci->reload_codec)
             break;
 
-        while (!ci->audiobuffer_insert ((char *) temp_buffer, nsamples * 4))
+        while (!ci->pcmbuf_insert ((char *) temp_buffer, nsamples * 4))
             rb->sleep (1);
 
         ci->set_elapsed (WavpackGetSampleIndex (wpc) / sr_100 * 10);
diff --git a/apps/main.c b/apps/main.c
index 4dee444..17f1540 100644
--- a/apps/main.c
+++ b/apps/main.c
@@ -62,7 +62,7 @@
 #include "database.h"
 
 #if (CONFIG_HWCODEC == MASNONE)
-#include "pcm_playback.h"
+#include "pcmbuf.h"
 #endif
 #if defined(IRIVER_H100_SERIES) && !defined(SIMULATOR)
 #include "pcm_record.h"
@@ -283,7 +283,7 @@
               global_settings.superbass);
     audio_init();
 #if (CONFIG_HWCODEC == MASNONE)
-    pcm_init();
+    pcmbuf_init();
     sound_settings_apply();
 #endif
 #if defined(IRIVER_H100_SERIES) && !defined(SIMULATOR)
diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c
new file mode 100644
index 0000000..1f08274
--- /dev/null
+++ b/apps/pcmbuf.c
@@ -0,0 +1,550 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2005 by Miika Pekkarinen
+ *
+ * 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 <stdbool.h>
+#include <stdio.h>
+#include "config.h"
+#include "debug.h"
+#include "panic.h"
+#include <kernel.h>
+#include "pcmbuf.h"
+#include "pcm_playback.h"
+#include "logf.h"
+#ifndef SIMULATOR
+#include "cpu.h"
+#endif
+#include "system.h"
+#include <string.h>
+#include "buffer.h"
+
+#define CHUNK_SIZE           32768
+/* Must be a power of 2 */
+#define NUM_PCM_BUFFERS      (PCMBUF_SIZE / CHUNK_SIZE)
+#define NUM_PCM_BUFFERS_MASK (NUM_PCM_BUFFERS - 1)
+#define PCMBUF_WATERMARK     (CHUNK_SIZE * 10)
+#define PCMBUF_CF_WATERMARK  (PCMBUF_SIZE - CHUNK_SIZE*8)
+
+/* Audio buffer related settings. */
+static char *audiobuffer;
+static long audiobuffer_pos;      /* Current audio buffer write index. */
+long audiobuffer_free;            /* Amount of bytes left in the buffer. */
+static long audiobuffer_fillpos;  /* Amount audiobuffer_pos will be increased. */
+static char *guardbuf;
+
+static void (*pcmbuf_event_handler)(void);
+
+/* Crossfade related. */
+static int crossfade_mode;
+static bool crossfade_enabled;
+static bool crossfade_active;
+static bool crossfade_init;
+static int crossfade_pos;
+static int crossfade_amount;
+static int crossfade_rem;
+
+
+static bool boost_mode;
+
+/* Crossfade modes. If CFM_CROSSFADE is selected, normal
+ * crossfader will activate. Selecting CFM_FLUSH is a special
+ * operation that only overwrites the pcm buffer without crossfading.
+ */
+enum {
+    CFM_CROSSFADE,
+    CFM_FLUSH
+};
+
+/* Structure we can use to queue pcm chunks in memory to be played
+ * by the driver code. */
+struct pcmbufdesc
+{
+    void *addr;
+    int size;
+    /* Call this when the buffer has been played */
+    void (*callback)(void);
+} pcmbuffers[NUM_PCM_BUFFERS];
+
+volatile int pcmbuf_read_index;
+volatile int pcmbuf_write_index;
+int pcmbuf_unplayed_bytes;
+int pcmbuf_watermark;
+void (*pcmbuf_watermark_event)(int bytes_left);
+static int last_chunksize;
+
+static void pcmbuf_boost(bool state)
+{
+    static bool boost_state = false;
+    
+    if (crossfade_init || crossfade_active || boost_mode)
+        return ;
+        
+    if (state != boost_state) {
+#ifdef HAVE_ADJUSTABLE_CPU_FREQ
+        cpu_boost(state);
+#endif    
+        boost_state = state;
+    }
+}
+
+int pcmbuf_num_used_buffers(void)
+{
+    return (pcmbuf_write_index - pcmbuf_read_index) & NUM_PCM_BUFFERS_MASK;
+}
+
+static void pcmbuf_callback(unsigned char** start, long* size)
+{
+    struct pcmbufdesc *desc = &pcmbuffers[pcmbuf_read_index];
+    int sz;
+
+    pcmbuf_unplayed_bytes -= last_chunksize;
+    audiobuffer_free += last_chunksize;
+    
+    
+    if(desc->size == 0)
+    {
+        /* The buffer is finished, call the callback function */
+        if(desc->callback)
+            desc->callback();
+
+        /* Advance to the next buffer */
+        pcmbuf_read_index = (pcmbuf_read_index + 1) & NUM_PCM_BUFFERS_MASK;
+        desc = &pcmbuffers[pcmbuf_read_index];
+    }
+    
+    if(pcmbuf_num_used_buffers())
+    {
+        /* Play max 64K at a time */
+        //sz = MIN(desc->size, 32768);
+        sz = desc->size;
+        *start = desc->addr;
+        *size = sz;
+
+        /* Update the buffer descriptor */
+        desc->size -= sz;
+        desc->addr += sz;
+
+        last_chunksize = sz;
+    }
+    else
+    {
+        /* No more buffers */
+        *size = 0;
+        if (pcmbuf_event_handler)
+            pcmbuf_event_handler();
+    }
+
+    logf("cbfm:%d", *size);
+    if(pcmbuf_unplayed_bytes <= pcmbuf_watermark)
+    {
+        if(pcmbuf_watermark_event)
+        {
+            pcmbuf_watermark_event(pcmbuf_unplayed_bytes);
+        }
+    }
+}
+
+void pcmbuf_set_watermark(int numbytes, void (*callback)(int bytes_left))
+{
+    pcmbuf_watermark = numbytes;
+    pcmbuf_watermark_event = callback;
+}
+
+bool pcmbuf_add_chunk(void *addr, int size, void (*callback)(void))
+{
+    /* We don't use the last buffer, since we can't see the difference
+       between the full and empty condition */
+    if(pcmbuf_num_used_buffers() < (NUM_PCM_BUFFERS - 2))
+    {
+        pcmbuffers[pcmbuf_write_index].addr = addr;
+        pcmbuffers[pcmbuf_write_index].size = size;
+        pcmbuffers[pcmbuf_write_index].callback = callback;
+        pcmbuf_write_index = (pcmbuf_write_index+1) & NUM_PCM_BUFFERS_MASK;
+        pcmbuf_unplayed_bytes += size;
+        return true;
+    }
+    else
+        return false;
+}
+
+void pcmbuf_watermark_callback(int bytes_left)
+{
+    /* Fill audio buffer by boosting cpu */
+    pcmbuf_boost(true);
+    if (bytes_left <= CHUNK_SIZE * 2)
+        crossfade_active = false;
+}
+
+void pcmbuf_set_boost_mode(bool state)
+{
+    if (state)
+        pcmbuf_boost(true);
+    boost_mode = state;
+}
+
+void pcmbuf_add_event(void (*event_handler)(void))
+{
+    pcmbuf_event_handler = event_handler;
+}
+
+unsigned int pcmbuf_get_latency(void)
+{
+    int latency;
+    
+    /* This has to be done better. */
+    latency = (PCMBUF_SIZE - audiobuffer_free - CHUNK_SIZE)/4 / (44100/1000);
+    if (latency < 0)
+        latency = 0;
+    
+    return latency;
+}
+
+bool pcmbuf_is_lowdata(void)
+{
+    if (!pcm_is_playing() || pcm_is_paused() || crossfade_init || crossfade_active)
+        return false;
+    
+    if (pcmbuf_unplayed_bytes < PCMBUF_WATERMARK)
+        return true;
+        
+    return false;
+}
+
+bool pcmbuf_crossfade_init(void)
+{
+    if (PCMBUF_SIZE - audiobuffer_free < CHUNK_SIZE * 8 || !crossfade_enabled
+        || crossfade_active || crossfade_init) {
+        return false;
+    }
+    logf("pcmbuf_crossfade_init");
+    pcmbuf_boost(true);
+    crossfade_mode = CFM_CROSSFADE;
+    crossfade_init = true;
+    
+    return true;
+    
+}
+
+void pcmbuf_play_stop(void)
+{
+    pcm_play_stop();
+    last_chunksize = 0;
+    pcmbuf_unplayed_bytes = 0;
+    pcmbuf_read_index = 0;
+    pcmbuf_write_index = 0;
+    audiobuffer_pos = 0;
+    audiobuffer_fillpos = 0;
+    audiobuffer_free = PCMBUF_SIZE;
+    crossfade_init = false;
+    crossfade_active = false;
+    
+    pcmbuf_set_boost_mode(false);
+    pcmbuf_boost(false);
+    
+}
+
+void pcmbuf_init(void)
+{
+    audiobuffer = &audiobuf[(audiobufend - audiobuf) - 
+                            PCMBUF_SIZE - PCMBUF_GUARD];
+    guardbuf = &audiobuffer[PCMBUF_SIZE];
+    pcmbuf_event_handler = NULL;
+    pcm_init();
+    pcmbuf_play_stop();
+}
+
+/** Initialize a track switch so that audio playback will not stop but
+ *  the switch to next track would happen as soon as possible.
+ */
+void pcmbuf_flush_audio(void)
+{
+    if (crossfade_init || crossfade_active || !pcm_is_playing()) {
+        pcmbuf_play_stop();
+        return ;
+    }
+    
+    crossfade_mode = CFM_FLUSH;
+    crossfade_init = true;
+}
+
+void pcmbuf_flush_fillpos(void)
+{
+    int copy_n;
+
+    copy_n = MIN(audiobuffer_fillpos, CHUNK_SIZE);
+    
+    if (copy_n) {
+        while (!pcmbuf_add_chunk(&audiobuffer[audiobuffer_pos],
+                                   copy_n, pcmbuf_event_handler)) {
+            pcmbuf_boost(false);
+            sleep(1);
+            /* This is a fatal error situation that should never happen. */
+            if (!pcm_is_playing()) {
+                logf("pcm_flush_fillpos error");
+                break ;
+            }
+        }
+        pcmbuf_event_handler = NULL;
+        audiobuffer_pos += copy_n;
+        if (audiobuffer_pos >= PCMBUF_SIZE)
+            audiobuffer_pos -= PCMBUF_SIZE;
+        audiobuffer_free -= copy_n;
+        audiobuffer_fillpos -= copy_n;
+    }
+}
+
+static void crossfade_start(void)
+{
+    int bytesleft = pcmbuf_unplayed_bytes;
+    
+    crossfade_init = 0;
+    if (bytesleft < CHUNK_SIZE * 3) {
+        logf("crossfade rejected");
+        pcmbuf_play_stop();
+        return ;
+    }
+
+    logf("crossfade_start");
+    pcmbuf_flush_fillpos();
+    pcmbuf_boost(true);
+    crossfade_active = true;
+    crossfade_pos = audiobuffer_pos;
+
+    switch (crossfade_mode) {
+        case CFM_CROSSFADE:
+            crossfade_amount = (bytesleft - (CHUNK_SIZE * 2))/2;
+            crossfade_rem = crossfade_amount;
+            break ;
+
+        case CFM_FLUSH:
+            crossfade_amount = (bytesleft - (CHUNK_SIZE * 2))/2;
+            crossfade_rem = crossfade_amount;
+            break ;
+    }
+    
+    crossfade_pos -= crossfade_amount*2;
+    if (crossfade_pos < 0)
+        crossfade_pos += PCMBUF_SIZE;
+}
+
+static __inline
+int crossfade(short *buf, const short *buf2, int length)
+{
+    int size, i;
+    int val1, val2;
+    
+    size = MIN(length, crossfade_rem);
+    switch (crossfade_mode) {
+        case CFM_CROSSFADE:
+            val1 = (crossfade_rem<<10)/crossfade_amount;
+            val2 = ((crossfade_amount-crossfade_rem)<<10)/crossfade_amount;
+            
+            for (i = 0; i < size; i++) {
+                buf[i] = ((buf[i] * val1) + (buf2[i] * val2)) >> 10;
+            }
+            break ;
+
+        case CFM_FLUSH:
+            for (i = 0; i < size; i++) {
+                buf[i] = buf2[i];
+            }
+            //memcpy((char *)buf, (char *)buf2, size*2);
+            break ;
+    }
+    
+    crossfade_rem -= size;
+    if (crossfade_rem <= 0)
+        crossfade_active = false;
+    
+    return size;
+}
+
+static bool prepare_insert(long length)
+{
+    if (crossfade_init)
+        crossfade_start();
+    
+    if (audiobuffer_free < length + audiobuffer_fillpos
+           + CHUNK_SIZE && !crossfade_active) {
+        pcmbuf_boost(false);
+        return false;
+    }
+    
+    if (!pcm_is_playing()) {
+        pcmbuf_boost(true);
+        crossfade_active = false;
+        if (audiobuffer_free < PCMBUF_SIZE - CHUNK_SIZE*4) {
+            logf("pcm starting");
+            pcm_play_data(pcmbuf_callback);
+        }
+    }
+    
+    return true;
+}
+
+void* pcmbuf_request_buffer(long length, long *realsize)
+{
+    void *ptr = NULL;
+    
+    while (audiobuffer_free < length + audiobuffer_fillpos
+           + CHUNK_SIZE && !crossfade_active) {
+        pcmbuf_boost(false);
+        sleep(1);
+    }
+    
+    if (crossfade_active) {
+        *realsize = MIN(length, PCMBUF_GUARD);
+        ptr = &guardbuf[0];
+    } else {
+        *realsize = MIN(length, PCMBUF_SIZE - audiobuffer_pos
+                            - audiobuffer_fillpos);
+        if (*realsize < length) {
+            *realsize += MIN((long)(length - *realsize), PCMBUF_GUARD);
+        }
+        ptr = &audiobuffer[audiobuffer_pos + audiobuffer_fillpos];
+    }
+    
+    return ptr;
+}
+
+bool pcmbuf_is_crossfade_active(void)
+{
+    return crossfade_active || crossfade_init;
+}
+
+void pcmbuf_flush_buffer(long length)
+{
+    int copy_n;
+    char *buf;
+
+    prepare_insert(length);
+    
+    if (crossfade_active) {
+        buf = &guardbuf[0];
+        length = MIN(length, PCMBUF_GUARD);
+        while (length > 0 && crossfade_active) {
+            copy_n = MIN(length, PCMBUF_SIZE - crossfade_pos);
+            copy_n = 2 * crossfade((short *)&audiobuffer[crossfade_pos], 
+                    (const short *)buf, copy_n/2);
+            buf += copy_n;
+            length -= copy_n;
+            crossfade_pos += copy_n;
+            if (crossfade_pos >= PCMBUF_SIZE)
+                crossfade_pos -= PCMBUF_SIZE;
+        }
+        
+        while (length > 0) {
+            copy_n = MIN(length, PCMBUF_SIZE - audiobuffer_pos);
+            memcpy(&audiobuffer[audiobuffer_pos], buf, copy_n);
+            audiobuffer_fillpos = copy_n;
+            buf += copy_n;
+            length -= copy_n;
+            if (length > 0)
+                pcmbuf_flush_fillpos();
+        }
+    }
+
+    audiobuffer_fillpos += length;
+
+    try_flush:
+    if (audiobuffer_fillpos < CHUNK_SIZE && PCMBUF_SIZE
+        - audiobuffer_pos - audiobuffer_fillpos > 0)
+        return ;
+
+    copy_n = audiobuffer_fillpos - (PCMBUF_SIZE - audiobuffer_pos);
+    if (copy_n > 0) {
+        audiobuffer_fillpos -= copy_n;
+        pcmbuf_flush_fillpos();
+        copy_n = MIN(copy_n, PCMBUF_GUARD);
+        memcpy(&audiobuffer[0], &guardbuf[0], copy_n);
+        audiobuffer_fillpos = copy_n;
+        goto try_flush;
+    }
+    pcmbuf_flush_fillpos();
+}
+
+bool pcmbuf_insert_buffer(char *buf, long length)
+{
+    long copy_n = 0;
+    
+    if (!prepare_insert(length))
+        return false;
+
+    
+    if (crossfade_active) {
+        while (length > 0 && crossfade_active) {
+            copy_n = MIN(length, PCMBUF_SIZE - crossfade_pos);
+                
+            copy_n = 2 * crossfade((short *)&audiobuffer[crossfade_pos],
+                                    (const short *)buf, copy_n/2);
+            buf += copy_n;
+            length -= copy_n;
+            crossfade_pos += copy_n;
+            if (crossfade_pos >= PCMBUF_SIZE)
+                crossfade_pos -= PCMBUF_SIZE;
+        }
+        
+        while (length > 0) {
+            copy_n = MIN(length, PCMBUF_SIZE - audiobuffer_pos);
+            memcpy(&audiobuffer[audiobuffer_pos], buf, copy_n);
+            audiobuffer_fillpos = copy_n;
+            buf += copy_n;
+            length -= copy_n;
+            if (length > 0)
+                pcmbuf_flush_fillpos();
+        }
+    }
+        
+    while (length > 0) {
+        copy_n = MIN(length, PCMBUF_SIZE - audiobuffer_pos -
+                    audiobuffer_fillpos);
+        copy_n = MIN(CHUNK_SIZE - audiobuffer_fillpos, copy_n);
+
+        memcpy(&audiobuffer[audiobuffer_pos+audiobuffer_fillpos],
+                buf, copy_n);
+        buf += copy_n;
+        audiobuffer_fillpos += copy_n;
+        length -= copy_n;
+        
+        /* Pre-buffer to meet CHUNK_SIZE requirement */
+        if (audiobuffer_fillpos < CHUNK_SIZE && length == 0) {
+            return true;
+        }
+
+        pcmbuf_flush_fillpos();
+    }
+
+    return true;
+}
+
+void pcmbuf_crossfade_enable(bool on_off)
+{
+    crossfade_enabled = on_off;
+    
+    if (crossfade_enabled) {
+        pcmbuf_set_watermark(PCMBUF_CF_WATERMARK, pcmbuf_watermark_callback);
+    } else {
+        pcmbuf_set_watermark(PCMBUF_WATERMARK, pcmbuf_watermark_callback);
+    }    
+}
+
+bool pcmbuf_is_crossfade_enabled(void)
+{
+    return crossfade_enabled;
+}
+
diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h
new file mode 100644
index 0000000..2b4023f
--- /dev/null
+++ b/apps/pcmbuf.h
@@ -0,0 +1,50 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ * $Id$
+ *
+ * Copyright (C) 2005 by Miika Pekkarinen
+ *
+ * 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 PCMBUF_H
+#define PCMBUF_H
+
+/* Guard buffer for crossfader when dsp is enabled. */
+#define PCMBUF_GUARD  32768
+
+/* PCM audio buffer. */
+#define PCMBUF_SIZE   (1*1024*1024)
+
+void pcmbuf_init(void);
+
+void pcmbuf_play_stop(void);
+bool pcmbuf_is_crossfade_active(void);
+
+/* These functions are for playing chained buffers of PCM data */
+bool pcmbuf_add_chunk(void *addr, int size, void (*callback)(void));
+int pcmbuf_num_used_buffers(void);
+void pcmbuf_set_watermark(int numbytes, void (*callback)(int bytes_left));
+
+void pcmbuf_set_boost_mode(bool state);
+bool pcmbuf_is_lowdata(void);
+void pcmbuf_flush_audio(void);
+bool pcmbuf_crossfade_init(void);
+void pcmbuf_add_event(void (*event_handler)(void));
+unsigned int pcmbuf_get_latency(void);
+bool pcmbuf_insert_buffer(char *buf, long length);
+void pcmbuf_flush_buffer(long length);
+void* pcmbuf_request_buffer(long length, long *realsize);
+bool pcmbuf_is_crossfade_enabled(void);
+void pcmbuf_crossfade_enable(bool on_off);
+
+#endif
diff --git a/apps/playback.c b/apps/playback.c
index 6713125..0e20d97 100644
--- a/apps/playback.c
+++ b/apps/playback.c
@@ -46,6 +46,7 @@
 #include "screens.h"
 #include "playlist.h"
 #include "playback.h"
+#include "pcmbuf.h"
 #include "pcm_playback.h"
 #include "buffer.h"
 #include "dsp.h"
@@ -173,91 +174,7 @@
 static void mp3_set_elapsed(struct mp3entry* id3);
 int mp3_get_file_pos(void);
 
-/* Simulator stubs. */
-#ifdef SIMULATOR
-bool pcm_insert_buffer(char *buf, long length)
-{
-    (void)buf;
-    (void)length;
-    
-    return true;
-}
-
-void pcm_flush_buffer(long length)
-{
-    (void)length;
-}
-
-
-void* pcm_request_buffer(long length, long *realsize)
-{
-    static char temp_audiobuffer[32768];
-
-    *realsize = MIN((int)sizeof(temp_audiobuffer), length);
-    
-    return temp_audiobuffer;
-}
-
-void audiobuffer_add_event(void (*event_handler)(void))
-{
-    event_handler();
-}
-
-unsigned int audiobuffer_get_latency()
-{
-    return 0;
-}
-
-void pcm_play_stop(void)
-{
-}
-
-bool pcm_is_playing(void)
-{
-    return false;
-}
-
-bool pcm_is_crossfade_active(void)
-{
-    return false;
-}
-
-bool pcm_is_lowdata(void)
-{
-    return false;
-}
-
-void pcm_flush_audio(void)
-{
-}
-
-bool pcm_crossfade_init(void)
-{
-    return false;
-}
-
-void pcm_set_boost_mode(bool state)
-{
-    (void)state;
-}
-
-bool pcm_is_crossfade_enabled(void)
-{
-    return false;
-}
-
-void pcm_play_pause(bool state)
-{
-    (void)state;
-}
-
-int ata_sleep(void)
-{
-    return 0;
-}
-#endif
-
-bool codec_audiobuffer_insert_callback(char *buf, long length)
+bool codec_pcmbuf_insert_callback(char *buf, long length)
 {
     char *dest;
     long realsize;
@@ -284,11 +201,11 @@
     while (length > 0) {
         /* Request a few extra bytes for resampling. */
         /* FIXME: Required extra bytes SHOULD be calculated. */
-        while ((dest = pcm_request_buffer(length+16384, &realsize)) == NULL)
+        while ((dest = pcmbuf_request_buffer(length+16384, &realsize)) == NULL)
             yield();
         
         if (realsize < 16384) {
-            pcm_flush_buffer(0);
+            pcmbuf_flush_buffer(0);
             continue ;
         }
         
@@ -300,7 +217,7 @@
         } else {
             processed_length = dsp_process(dest, buf, realsize >> (mono + 1));
         }
-        pcm_flush_buffer(processed_length);
+        pcmbuf_flush_buffer(processed_length);
         length -= realsize;
         buf += realsize << (factor + mono);
     }
@@ -308,7 +225,7 @@
     return true;
 }
 
-bool codec_audiobuffer_insert_split_callback(void *ch1, void *ch2, 
+bool codec_pcmbuf_insert_split_callback(void *ch1, void *ch2,
                                              long length)
 {
     char *dest;
@@ -326,11 +243,11 @@
     
     while (length > 0) {
         /* Request a few extra bytes for resampling. */
-        while ((dest = pcm_request_buffer(length+4096, &realsize)) == NULL)
+        while ((dest = pcmbuf_request_buffer(length+4096, &realsize)) == NULL)
             yield();
         
         if (realsize < 4096) {
-            pcm_flush_buffer(0);
+            pcmbuf_flush_buffer(0);
             continue ;
         }
         
@@ -338,7 +255,7 @@
         
         processed_length = dsp_process(dest, ch1, realsize / 4) * 2;
         dsp_process(dest, ch2, realsize / 4);
-        pcm_flush_buffer(processed_length);
+        pcmbuf_flush_buffer(processed_length);
         length -= realsize;
         ch1 += realsize >> factor;
         ch2 += realsize >> factor;
@@ -360,7 +277,7 @@
     if (ci.stop_codec)
         return ;
         
-    latency = audiobuffer_get_latency();
+    latency = pcmbuf_get_latency();
     
     if (value < latency) {
         cur_ti->id3.elapsed = 0;
@@ -377,10 +294,10 @@
     if (ci.stop_codec)
         return ;
         
-    /* The 1000 here is a hack.  audiobuffer_get_latency() should
+    /* The 1000 here is a hack.  pcmbuf_get_latency() should
      * be more accurate
      */
-    latency = (audiobuffer_get_latency() + 1000) * cur_ti->id3.bitrate / 8;
+    latency = (pcmbuf_get_latency() + 1000) * cur_ti->id3.bitrate / 8;
     
     if (value < latency) {
         cur_ti->id3.offset = 0;
@@ -487,7 +404,7 @@
     ci.curpos = newpos;
     cur_ti->available = 0;
     lseek(current_fd, newpos, SEEK_SET);
-    pcm_flush_audio();
+    pcmbuf_flush_audio();
         
     mutex_unlock(&mutex_bufferfill);
 
@@ -553,8 +470,8 @@
     if (difference >= 0) {
         logf("seek: +%d", difference);
         codec_advance_buffer_callback(difference);
-        if (!pcm_is_crossfade_active())
-            pcm_play_stop();
+        if (!pcmbuf_is_crossfade_active())
+            pcmbuf_play_stop();
         return true;
     }
 
@@ -575,8 +492,8 @@
     if (buf_ridx < 0)
         buf_ridx = codecbuflen + buf_ridx;
     ci.curpos -= difference;
-    if (!pcm_is_crossfade_active())
-        pcm_play_stop();
+    if (!pcmbuf_is_crossfade_active())
+        pcmbuf_play_stop();
     
     return true;
 }
@@ -598,9 +515,9 @@
         
     case CODEC_DSP_ENABLE:
         if ((bool)value)
-            ci.audiobuffer_insert = codec_audiobuffer_insert_callback;
+            ci.pcmbuf_insert = codec_pcmbuf_insert_callback;
         else
-            ci.audiobuffer_insert = pcm_insert_buffer;
+            ci.pcmbuf_insert = pcmbuf_insert_buffer;
         break ;
 
     default:
@@ -640,7 +557,7 @@
     yield();
     if (!pcm_is_playing())
         sleep(5);
-    while ((pcm_is_crossfade_active() || pcm_is_lowdata())
+    while ((pcmbuf_is_crossfade_active() || pcmbuf_is_lowdata())
             && !ci.stop_codec && playing && queue_empty(&audio_queue)
             && codecbufused > (128*1024))
         yield();
@@ -1067,7 +984,7 @@
     buf_ridx = 0;
     buf_widx = 0;
     codecbufused = 0;
-    pcm_set_boost_mode(true);
+    pcmbuf_set_boost_mode(true);
     
     fill_bytesleft = codecbuflen;
     filling = true;
@@ -1082,7 +999,7 @@
         logf("Failure");
     }
 
-    pcm_set_boost_mode(false);
+    pcmbuf_set_boost_mode(false);
 }
 
 void audio_clear_track_entries(void)
@@ -1162,7 +1079,7 @@
     fill_bytesleft = codecbuflen - codecbufused;
     cur_ti->start_pos = ci.curpos;
 
-    pcm_set_boost_mode(true);
+    pcmbuf_set_boost_mode(true);
     
     if (filling)
         return ;
@@ -1222,10 +1139,12 @@
         generate_postbuffer_events();
         filling = false;
         conf_bufferlimit = 0;
-        pcm_set_boost_mode(false);
-            
+        pcmbuf_set_boost_mode(false);
+
+#ifndef SIMULATOR                    
         if (playing)
             ata_sleep();
+#endif
     }
 }
 
@@ -1242,7 +1161,7 @@
             buf_ridx -= codecbuflen;
             
         if (!filling)
-            pcm_set_boost_mode(false);
+            pcmbuf_set_boost_mode(false);
     } else {
         buf_ridx -= ci.curpos + cur_ti->codecsize;
         codecbufused += ci.curpos + cur_ti->codecsize;
@@ -1263,8 +1182,8 @@
     ci.curpos = 0;
     cur_ti->start_pos = 0;
     ci.taginfo_ready = (bool *)&cur_ti->taginfo_ready;
-    if (!pcm_crossfade_init())
-        audiobuffer_add_event(codec_track_changed);
+    if (!pcmbuf_crossfade_init())
+        pcmbuf_add_event(codec_track_changed);
     else
         codec_track_changed();
 }
@@ -1278,7 +1197,7 @@
         close(current_fd);
         current_fd = -1;
     }
-    pcm_play_stop();
+    pcmbuf_play_stop();
     pcm_play_pause(true);
     track_count = 0;
     audio_clear_track_entries();
@@ -1448,8 +1367,8 @@
         queue_post(&audio_queue, AUDIO_PLAY, 0);
     } 
     
-    else if (!pcm_crossfade_init())
-        pcm_flush_audio();
+    else if (!pcmbuf_crossfade_init())
+        pcmbuf_flush_audio();
         
     codec_track_changed();
 }
@@ -1473,8 +1392,8 @@
                 ci.stop_codec = true;
                 ci.reload_codec = false;
                 ci.seek_time = 0;
-                if (!pcm_crossfade_init() && !pcm_is_crossfade_active())
-                    pcm_flush_audio();
+                if (!pcmbuf_crossfade_init() && !pcmbuf_is_crossfade_active())
+                    pcmbuf_flush_audio();
                 audio_play_start((int)ev.data);
                 playlist_update_resume_info(audio_current_track());
                 break ;
@@ -1650,8 +1569,8 @@
 {
     logf("audio_play");
     ci.stop_codec = true;
-    if (!pcm_crossfade_init())
-        pcm_flush_audio();
+    if (!pcmbuf_crossfade_init())
+        pcmbuf_flush_audio();
     codec_track_changed();
         
     pcm_play_pause(true);
@@ -1696,7 +1615,7 @@
     logf("rewind: %d", newpos);
     if (playing) {
         ci.seek_time = newpos+1;
-        pcm_play_stop();
+        pcmbuf_play_stop();
         paused = false;
     }
 }
@@ -1905,8 +1824,8 @@
     
     /* Initialize codec api. */    
     ci.read_filebuf = codec_filebuf_callback;
-    ci.audiobuffer_insert = pcm_insert_buffer;
-    ci.audiobuffer_insert_split = codec_audiobuffer_insert_split_callback;
+    ci.pcmbuf_insert = pcmbuf_insert_buffer;
+    ci.pcmbuf_insert_split = codec_pcmbuf_insert_split_callback;
     ci.get_codec_memory = get_codec_memory_callback;
     ci.request_buffer = codec_request_buffer_callback;
     ci.advance_buffer = codec_advance_buffer_callback;
diff --git a/apps/plugin.h b/apps/plugin.h
index 7dff074..b081887 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -303,8 +303,7 @@
     void (*bitswap)(unsigned char *data, int length);
 #endif
 #if CONFIG_HWCODEC == MASNONE
-    void (*pcm_play_data)(const unsigned char *start, int size,
-    void (*get_more)(unsigned char** start, long*size));
+    void (*pcm_play_data)(void (*get_more)(unsigned char** start, long*size));
     void (*pcm_play_stop)(void);
     void (*pcm_set_frequency)(unsigned int frequency);
     bool (*pcm_is_playing)(void);
diff --git a/apps/settings.c b/apps/settings.c
index 50a309d..940c0d8 100644
--- a/apps/settings.c
+++ b/apps/settings.c
@@ -73,6 +73,7 @@
 const char rec_base_directory[] = REC_BASE_DIR;
 #endif
 #if CONFIG_HWCODEC == MASNONE
+#include "pcmbuf.h"
 #include "pcm_playback.h"
 #endif
 
@@ -845,7 +846,7 @@
     }
 
 #if CONFIG_HWCODEC == MASNONE && !defined(SIMULATOR)
-    pcm_crossfade_enable(global_settings.crossfade);
+    pcmbuf_crossfade_enable(global_settings.crossfade);
 #endif
 
 #ifdef HAVE_SPDIF_POWER
diff --git a/apps/settings_menu.c b/apps/settings_menu.c
index 5927635..90ad191 100644
--- a/apps/settings_menu.c
+++ b/apps/settings_menu.c
@@ -64,6 +64,7 @@
 #endif
 
 #if CONFIG_HWCODEC == MASNONE
+#include "pcmbuf.h"
 #include "pcm_playback.h"
 #endif
 
@@ -1107,7 +1108,7 @@
 {
     bool rc = set_bool( str(LANG_CROSSFADE), &global_settings.crossfade );
 #ifndef SIMULATOR
-    pcm_crossfade_enable(global_settings.crossfade);
+    pcmbuf_crossfade_enable(global_settings.crossfade);
 #endif
     return rc;
 }
diff --git a/firmware/export/pcm_playback.h b/firmware/export/pcm_playback.h
index c29cd2a..3f0b5ee 100644
--- a/firmware/export/pcm_playback.h
+++ b/firmware/export/pcm_playback.h
@@ -19,41 +19,15 @@
 #ifndef PCM_PLAYBACK_H
 #define PCM_PLAYBACK_H
 
-/* Guard buffer for crossfader when dsp is enabled. */
-#define PCMBUF_GUARD  32768
-
-/* PCM audio buffer. */
-#define PCMBUF_SIZE   (1*1024*1024)
-
 void pcm_init(void);
 void pcm_set_frequency(unsigned int frequency);
 
 /* This is for playing "raw" PCM data */
-void pcm_play_data(const unsigned char* start, int size,
-                   void (*get_more)(unsigned char** start, long* size));
+void pcm_play_data(void (*get_more)(unsigned char** start, long* size));
 
 void pcm_play_stop(void);
 void pcm_play_pause(bool play);
+bool pcm_is_paused(void);
 bool pcm_is_playing(void);
-bool pcm_is_crossfade_active(void);
-
-/* These functions are for playing chained buffers of PCM data */
-void pcm_play_init(void);
-void pcm_play_start(void);
-bool pcm_play_add_chunk(void *addr, int size, void (*callback)(void));
-int pcm_play_num_used_buffers(void);
-void pcm_play_set_watermark(int numbytes, void (*callback)(int bytes_left));
-
-void pcm_set_boost_mode(bool state);
-bool pcm_is_lowdata(void);
-void pcm_flush_audio(void);
-bool pcm_crossfade_init(void);
-void audiobuffer_add_event(void (*event_handler)(void));
-unsigned int audiobuffer_get_latency(void);
-bool pcm_insert_buffer(char *buf, long length);
-void pcm_flush_buffer(long length);
-void* pcm_request_buffer(long length, long *realsize);
-bool pcm_is_crossfade_enabled(void);
-void pcm_crossfade_enable(bool on_off);
 
 #endif
diff --git a/firmware/pcm_playback.c b/firmware/pcm_playback.c
index af8ce18..7682ce0 100644
--- a/firmware/pcm_playback.c
+++ b/firmware/pcm_playback.c
@@ -43,61 +43,13 @@
 
 #ifdef HAVE_UDA1380
 
-#define CHUNK_SIZE           32768
-/* Must be a power of 2 */
-#define NUM_PCM_BUFFERS      (PCMBUF_SIZE / CHUNK_SIZE)
-#define NUM_PCM_BUFFERS_MASK (NUM_PCM_BUFFERS - 1)
-#define PCM_WATERMARK        (CHUNK_SIZE * 10)
-#define PCM_CF_WATERMARK     (PCMBUF_SIZE - CHUNK_SIZE*8)
-
 static bool pcm_playing;
 static bool pcm_paused;
 static int pcm_freq = 0x6; /* 44.1 is default */
 
-static char *audiobuffer;
-static long audiobuffer_pos;
-long audiobuffer_free;
-static long audiobuffer_fillpos;
-static bool boost_mode;
-
-/* Crossfade modes. If CFM_CROSSFADE is selected, normal
- * crossfader will activate. Selecting CFM_FLUSH is a special
- * operation that only overwrites the pcm buffer without crossfading.
- */
-enum {
-    CFM_CROSSFADE,
-    CFM_FLUSH
-};
-
-static int crossfade_mode;
-static bool crossfade_enabled;
-static bool crossfade_active;
-static bool crossfade_init;
-static int crossfade_pos;
-static int crossfade_amount;
-static int crossfade_rem;
-
-static char *guardbuf;
-static void (*pcm_event_handler)(void);
-
 static unsigned char *next_start;
 static long next_size;
 
-static int last_chunksize = 0;
-
-struct pcmbufdesc
-{
-    void *addr;
-    int size;
-    void (*callback)(void); /* Call this when the buffer has been played */
-} pcmbuffers[NUM_PCM_BUFFERS];
-
-volatile int pcmbuf_read_index;
-volatile int pcmbuf_write_index;
-int pcmbuf_unplayed_bytes;
-int pcmbuf_watermark;
-void (*pcmbuf_watermark_callback)(int bytes_left);
-
 /* Set up the DMA transfer that kicks in when the audio FIFO gets empty */
 static void dma_start(const void *addr, long size)
 {
@@ -122,21 +74,6 @@
     DCR0 = DMA_INT | DMA_EEXT | DMA_CS | DMA_SINC | DMA_START;
 }
 
-void pcm_boost(bool state)
-{
-    static bool boost_state = false;
-    
-    if (crossfade_init || crossfade_active || boost_mode)
-        return ;
-        
-    if (state != boost_state) {
-#ifdef HAVE_ADJUSTABLE_CPU_FREQ
-        cpu_boost(state);
-#endif    
-        boost_state = state;
-    }
-}
-
 /* Stops the DMA transfer and interrupt */
 static void dma_stop(void)
 {
@@ -147,17 +84,8 @@
     IIS2CONFIG = 0x800;
     EBU1CONFIG = 0x800;
 
-    pcmbuf_unplayed_bytes = 0;
-    last_chunksize = 0;
-    audiobuffer_pos = 0;
-    audiobuffer_fillpos = 0;
-    audiobuffer_free = PCMBUF_SIZE;
-    pcmbuf_read_index = 0;
-    pcmbuf_write_index = 0;
     next_start = NULL;
     next_size = 0;
-    crossfade_init = 0;
-    crossfade_active = 0;
     pcm_paused = false;
 }
 
@@ -185,67 +113,13 @@
 /* the registered callback function to ask for more mp3 data */
 static void (*callback_for_more)(unsigned char**, long*) = NULL;
 
-int pcm_play_num_used_buffers(void)
+void pcm_play_data(void (*get_more)(unsigned char** start, long* size))
 {
-    return (pcmbuf_write_index - pcmbuf_read_index) & NUM_PCM_BUFFERS_MASK;
-}
-
-static void pcm_play_callback(unsigned char** start, long* size)
-{
-    struct pcmbufdesc *desc = &pcmbuffers[pcmbuf_read_index];
-    int sz;
-
-    pcmbuf_unplayed_bytes -= last_chunksize;
-    audiobuffer_free += last_chunksize;
+    unsigned char *start;
+    long size;
     
-    
-    if(desc->size == 0)
-    {
-        /* The buffer is finished, call the callback function */
-        if(desc->callback)
-            desc->callback();
-
-        /* Advance to the next buffer */
-        pcmbuf_read_index = (pcmbuf_read_index + 1) & NUM_PCM_BUFFERS_MASK;
-        desc = &pcmbuffers[pcmbuf_read_index];
-    }
-    
-    if(pcm_play_num_used_buffers())
-    {
-        /* Play max 64K at a time */
-        //sz = MIN(desc->size, 32768);
-        sz = desc->size;
-        *start = desc->addr;
-        *size = sz;
-
-        /* Update the buffer descriptor */
-        desc->size -= sz;
-        desc->addr += sz;
-
-        last_chunksize = sz;
-    }
-    else
-    {
-        /* No more buffers */
-        *size = 0;
-        if (pcm_event_handler)
-            pcm_event_handler();
-    }
-#if 1
-    if(pcmbuf_unplayed_bytes <= pcmbuf_watermark)
-    {
-        if(pcmbuf_watermark_callback)
-        {
-            pcmbuf_watermark_callback(pcmbuf_unplayed_bytes);
-        }
-    }
-#endif
-}
-
-void pcm_play_data(const unsigned char* start, int size,
-                   void (*get_more)(unsigned char** start, long* size))
-{
     callback_for_more = get_more;
+    
     /** FIXME: This is a temporary fix to prevent playback glitches when
       * playing the first file. We will just drop the first frame to prevent
       * that problem from occurring.
@@ -259,6 +133,7 @@
       *     find the explanation for this bug from this file.
       */
     get_more((unsigned char **)&start, (long *)&size); // REMOVE THIS TO TEST
+    get_more((unsigned char **)&start, (long *)&size);
     get_more(&next_start, &next_size);
     dma_start(start, size);
     uda1380_mute(false);
@@ -270,13 +145,11 @@
         uda1380_mute(true);
         dma_stop();
     }
-    pcm_set_boost_mode(false);
-    pcm_boost(false);
 }
 
 void pcm_play_pause(bool play)
 {
-    if(pcm_paused && play && pcmbuf_unplayed_bytes)
+    if(pcm_paused && play && next_size)
     {
         logf("unpause");
         /* Reset chunk size so dma has enough data to fill the fifo. */
@@ -303,6 +176,11 @@
     pcm_paused = !play;
 }
 
+bool pcm_is_paused(void)
+{
+    return pcm_paused;
+}
+
 bool pcm_is_playing(void)
 {
     return pcm_playing;
@@ -370,390 +248,8 @@
     sleep(HZ/4);
     uda1380_enable_output(true);
     
-    pcm_play_init();
-}
-
-void pcm_play_set_watermark(int numbytes, void (*callback)(int bytes_left))
-{
-    pcmbuf_watermark = numbytes;
-    pcmbuf_watermark_callback = callback;
-}
-
-bool pcm_play_add_chunk(void *addr, int size, void (*callback)(void))
-{
-    /* We don't use the last buffer, since we can't see the difference
-       between the full and empty condition */
-    if(pcm_play_num_used_buffers() < (NUM_PCM_BUFFERS - 2))
-    {
-        pcmbuffers[pcmbuf_write_index].addr = addr;
-        pcmbuffers[pcmbuf_write_index].size = size;
-        pcmbuffers[pcmbuf_write_index].callback = callback;
-        pcmbuf_write_index = (pcmbuf_write_index+1) & NUM_PCM_BUFFERS_MASK;
-        pcmbuf_unplayed_bytes += size;
-        return true;
-    }
-    else
-        return false;
-}
-
-void pcm_watermark_callback(int bytes_left)
-{
-    /* Fill audio buffer by boosting cpu */
-    pcm_boost(true);
-    if (bytes_left <= CHUNK_SIZE * 2)
-        crossfade_active = false;
-}
-
-void pcm_set_boost_mode(bool state)
-{
-    if (state)
-        pcm_boost(true);
-    boost_mode = state;
-}
-
-void audiobuffer_add_event(void (*event_handler)(void))
-{
-    pcm_event_handler = event_handler;
-}
-
-unsigned int audiobuffer_get_latency(void)
-{
-    int latency;
-    
-    /* This has to be done better. */
-    latency = (PCMBUF_SIZE - audiobuffer_free - CHUNK_SIZE)/4 / (44100/1000);
-    if (latency < 0)
-        latency = 0;
-    
-    return latency;
-}
-
-bool pcm_is_lowdata(void)
-{
-    if (!pcm_is_playing() || pcm_paused || crossfade_init || crossfade_active)
-        return false;
-    
-    if (pcmbuf_unplayed_bytes < PCM_WATERMARK)
-        return true;
-        
-    return false;
-}
-
-bool pcm_crossfade_init(void)
-{
-    if (PCMBUF_SIZE - audiobuffer_free < CHUNK_SIZE * 8 || !crossfade_enabled
-        || crossfade_active || crossfade_init) {
-        return false;
-    }
-    logf("pcm_crossfade_init");
-    pcm_boost(true);
-    crossfade_mode = CFM_CROSSFADE;
-    crossfade_init = true;
-    
-    return true;
-    
-}
-
-/** Initialize a track switch so that audio playback will not stop but
- *  the switch to next track would happen as soon as possible.
- */
-void pcm_flush_audio(void)
-{
-    if (crossfade_init || crossfade_active || !pcm_playing) {
-        pcm_play_stop();
-        return ;
-    }
-    
-    crossfade_mode = CFM_FLUSH;
-    crossfade_init = true;
-}
-
-void pcm_flush_fillpos(void)
-{
-    int copy_n;
-
-    copy_n = MIN(audiobuffer_fillpos, CHUNK_SIZE);
-    
-    if (copy_n) {
-        while (!pcm_play_add_chunk(&audiobuffer[audiobuffer_pos],
-                                   copy_n, pcm_event_handler)) {
-            pcm_boost(false);
-            sleep(1);
-            /* This is a fatal error situation that should never happen. */
-            if (!pcm_playing) {
-                logf("pcm_flush_fillpos error");
-                break ;
-            }
-        }
-        pcm_event_handler = NULL;
-        audiobuffer_pos += copy_n;
-        if (audiobuffer_pos >= PCMBUF_SIZE)
-            audiobuffer_pos -= PCMBUF_SIZE;
-        audiobuffer_free -= copy_n;
-        audiobuffer_fillpos -= copy_n;
-    }
-}
-
-static void crossfade_start(void)
-{
-    int bytesleft = pcmbuf_unplayed_bytes;
-    
-    crossfade_init = 0;
-    if (bytesleft < CHUNK_SIZE * 3) {
-        logf("crossfade rejected");
-        pcm_play_stop();
-        return ;
-    }
-
-    logf("crossfade_start");
-    pcm_flush_fillpos();
-    pcm_boost(true);
-    crossfade_active = true;
-    crossfade_pos = audiobuffer_pos;
-
-    switch (crossfade_mode) {
-        case CFM_CROSSFADE:
-            crossfade_amount = (bytesleft - (CHUNK_SIZE * 2))/2;
-            crossfade_rem = crossfade_amount;
-            break ;
-
-        case CFM_FLUSH:
-            crossfade_amount = (bytesleft - (CHUNK_SIZE * 2))/2;
-            crossfade_rem = crossfade_amount;
-            break ;
-    }
-    
-    crossfade_pos -= crossfade_amount*2;
-    if (crossfade_pos < 0)
-        crossfade_pos += PCMBUF_SIZE;
-}
-
-static __inline
-int crossfade(short *buf, const short *buf2, int length)
-{
-    int size, i;
-    int val1, val2;
-    
-    size = MIN(length, crossfade_rem);
-    switch (crossfade_mode) {
-        case CFM_CROSSFADE:
-            val1 = (crossfade_rem<<10)/crossfade_amount;
-            val2 = ((crossfade_amount-crossfade_rem)<<10)/crossfade_amount;
-            
-            for (i = 0; i < size; i++) {
-                buf[i] = ((buf[i] * val1) + (buf2[i] * val2)) >> 10;
-            }
-            break ;
-
-        case CFM_FLUSH:
-            for (i = 0; i < size; i++) {
-                buf[i] = buf2[i];
-            }
-            //memcpy((char *)buf, (char *)buf2, size*2);
-            break ;
-    }
-    
-    crossfade_rem -= size;
-    if (crossfade_rem <= 0)
-        crossfade_active = false;
-    
-    return size;
-}
-
-static bool prepare_insert(long length)
-{
-    if (crossfade_init)
-        crossfade_start();
-    
-    if (audiobuffer_free < length + audiobuffer_fillpos
-           + CHUNK_SIZE && !crossfade_active) {
-        pcm_boost(false);
-        return false;
-    }
-    
-    if (!pcm_is_playing()) {
-        pcm_boost(true);
-        crossfade_active = false;
-        if (audiobuffer_free < PCMBUF_SIZE - CHUNK_SIZE*4)
-            pcm_play_start();
-    }
-    
-    return true;
-}
-
-void* pcm_request_buffer(long length, long *realsize)
-{
-    void *ptr = NULL;
-    
-    while (audiobuffer_free < length + audiobuffer_fillpos
-           + CHUNK_SIZE && !crossfade_active) {
-        pcm_boost(false);
-        sleep(1);
-    }
-    
-    if (crossfade_active) {
-        *realsize = MIN(length, PCMBUF_GUARD);
-        ptr = &guardbuf[0];
-    } else {
-        *realsize = MIN(length, PCMBUF_SIZE - audiobuffer_pos
-                            - audiobuffer_fillpos);
-        if (*realsize < length) {
-            *realsize += MIN((long)(length - *realsize), PCMBUF_GUARD);
-        }
-        ptr = &audiobuffer[audiobuffer_pos + audiobuffer_fillpos];
-    }
-    
-    return ptr;
-}
-
-bool pcm_is_crossfade_active(void)
-{
-    return crossfade_active || crossfade_init;
-}
-
-void pcm_flush_buffer(long length)
-{
-    int copy_n;
-    char *buf;
-
-    prepare_insert(length);
-    
-    if (crossfade_active) {
-        buf = &guardbuf[0];
-        length = MIN(length, PCMBUF_GUARD);
-        while (length > 0 && crossfade_active) {
-            copy_n = MIN(length, PCMBUF_SIZE - crossfade_pos);
-            copy_n = 2 * crossfade((short *)&audiobuffer[crossfade_pos], 
-                    (const short *)buf, copy_n/2);
-            buf += copy_n;
-            length -= copy_n;
-            crossfade_pos += copy_n;
-            if (crossfade_pos >= PCMBUF_SIZE)
-                crossfade_pos -= PCMBUF_SIZE;
-        }
-        
-        while (length > 0) {
-            copy_n = MIN(length, PCMBUF_SIZE - audiobuffer_pos);
-            memcpy(&audiobuffer[audiobuffer_pos], buf, copy_n);
-            audiobuffer_fillpos = copy_n;
-            buf += copy_n;
-            length -= copy_n;
-            if (length > 0)
-                pcm_flush_fillpos();
-        }
-    }
-
-    audiobuffer_fillpos += length;
-
-    try_flush:
-    if (audiobuffer_fillpos < CHUNK_SIZE && PCMBUF_SIZE
-        - audiobuffer_pos - audiobuffer_fillpos > 0)
-        return ;
-
-    copy_n = audiobuffer_fillpos - (PCMBUF_SIZE - audiobuffer_pos);
-    if (copy_n > 0) {
-        audiobuffer_fillpos -= copy_n;
-        pcm_flush_fillpos();
-        copy_n = MIN(copy_n, PCMBUF_GUARD);
-        memcpy(&audiobuffer[0], &guardbuf[0], copy_n);
-        audiobuffer_fillpos = copy_n;
-        goto try_flush;
-    }
-    pcm_flush_fillpos();
-}
-
-bool pcm_insert_buffer(char *buf, long length)
-{
-    long copy_n = 0;
-    
-    if (!prepare_insert(length))
-        return false;
-
-    
-    if (crossfade_active) {
-        while (length > 0 && crossfade_active) {
-            copy_n = MIN(length, PCMBUF_SIZE - crossfade_pos);
-                
-            copy_n = 2 * crossfade((short *)&audiobuffer[crossfade_pos],
-                                    (const short *)buf, copy_n/2);
-            buf += copy_n;
-            length -= copy_n;
-            crossfade_pos += copy_n;
-            if (crossfade_pos >= PCMBUF_SIZE)
-                crossfade_pos -= PCMBUF_SIZE;
-        }
-        
-        while (length > 0) {
-            copy_n = MIN(length, PCMBUF_SIZE - audiobuffer_pos);
-            memcpy(&audiobuffer[audiobuffer_pos], buf, copy_n);
-            audiobuffer_fillpos = copy_n;
-            buf += copy_n;
-            length -= copy_n;
-            if (length > 0)
-                pcm_flush_fillpos();
-        }
-    }
-        
-    while (length > 0) {
-        copy_n = MIN(length, PCMBUF_SIZE - audiobuffer_pos -
-                    audiobuffer_fillpos);
-        copy_n = MIN(CHUNK_SIZE - audiobuffer_fillpos, copy_n);
-
-        memcpy(&audiobuffer[audiobuffer_pos+audiobuffer_fillpos],
-                buf, copy_n);
-        buf += copy_n;
-        audiobuffer_fillpos += copy_n;
-        length -= copy_n;
-        
-        /* Pre-buffer to meet CHUNK_SIZE requirement */
-        if (audiobuffer_fillpos < CHUNK_SIZE && length == 0) {
-            return true;
-        }
-
-        pcm_flush_fillpos();
-    }
-
-    return true;
-}
-
-void pcm_play_init(void)
-{
-    audiobuffer = &audiobuf[(audiobufend - audiobuf) - 
-                            PCMBUF_SIZE - PCMBUF_GUARD];
-    guardbuf = &audiobuffer[PCMBUF_SIZE];
-
     /* Call dma_stop to initialize everything. */
     dma_stop();
-    pcm_event_handler = NULL;
-}
-
-void pcm_crossfade_enable(bool on_off)
-{
-    crossfade_enabled = on_off;
-}
-
-bool pcm_is_crossfade_enabled(void)
-{
-    return crossfade_enabled;
-}
-
-void pcm_play_start(void)
-{
-    unsigned long size;
-    unsigned char *start;
-    
-    if (crossfade_enabled) {
-        pcm_play_set_watermark(PCM_CF_WATERMARK, pcm_watermark_callback);
-    } else {
-        pcm_play_set_watermark(PCM_WATERMARK, pcm_watermark_callback);
-    }
-    crossfade_active = false;
-    
-    if(!pcm_is_playing())
-    {
-        pcm_play_callback(&start, &size);
-        pcm_play_data(start, size, pcm_play_callback);
-    }
 }
 
 #endif /* HAVE_UDA1380 */
diff --git a/uisimulator/common/stubs.c b/uisimulator/common/stubs.c
index 3b61e27..5865b2c 100644
--- a/uisimulator/common/stubs.c
+++ b/uisimulator/common/stubs.c
@@ -33,7 +33,36 @@
 
 extern char having_new_lcd;
 
+/* Stubs for PCM audio playback. */
+bool pcm_is_playing(void)
+{
+    return false;
+}
 
+void pcm_play_pause(bool state)
+{
+    (void)state;
+}
+
+bool pcm_is_paused(void)
+{
+    return false;
+}
+
+void pcm_play_stop(void)
+{
+}
+
+void pcm_init(void)
+{
+}
+
+void pcm_play_data(void (*get_more)(unsigned char** start, long* size))
+{
+    (void)get_more;
+}
+
+/* Generic firmware stubs. */
 void backlight_on(void)
 {
   /* we could do something better here! */