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;
 }