Adding SPC codec (FS #6542)


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12298 a1c6a512-1295-4272-9138-f99709370657
diff --git a/apps/codecs/Makefile b/apps/codecs/Makefile
index e555d94..65c319b 100644
--- a/apps/codecs/Makefile
+++ b/apps/codecs/Makefile
@@ -52,6 +52,7 @@
 $(OBJDIR)/sid.elf : $(OBJDIR)/sid.o
 $(OBJDIR)/adx.elf : $(OBJDIR)/adx.o
 $(OBJDIR)/nsf.elf : $(OBJDIR)/nsf.o
+$(OBJDIR)/spc.elf : $(OBJDIR)/spc.o
 $(OBJDIR)/aiff.elf : $(OBJDIR)/aiff.o
 $(OBJDIR)/mpa.elf : $(OBJDIR)/mpa.o $(BUILDDIR)/libmad.a
 $(OBJDIR)/a52.elf : $(OBJDIR)/a52.o $(BUILDDIR)/liba52.a
diff --git a/apps/codecs/SOURCES b/apps/codecs/SOURCES
index b912dd9..123a9c0 100644
--- a/apps/codecs/SOURCES
+++ b/apps/codecs/SOURCES
@@ -17,6 +17,7 @@
 sid.c
 adx.c
 nsf.c
+spc.c
 #if defined(HAVE_RECORDING) && !defined(SIMULATOR)
 /* encoders */
 aiff_enc.c
diff --git a/apps/codecs/spc.c b/apps/codecs/spc.c
new file mode 100644
index 0000000..2dac1d4
--- /dev/null
+++ b/apps/codecs/spc.c
@@ -0,0 +1,845 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ *
+ * Copyright (C) 2006-2007 Adam Gashlin (hcs)
+ * Copyright (C) 2004-2007 Shay Green (blargg)
+ * Copyright (C) 2002 Brad Martin
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/* lovingly ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */
+/* DSP Based on Brad Martin's OpenSPC DSP emulator */
+/* tag reading from sexyspc by John Brawn (John_Brawn@yahoo.com) and others */
+
+#include "codeclib.h"
+#include "inttypes.h"
+#include "system.h"
+
+/* rather than comment out asserts, just define NDEBUG */
+#define NDEBUG
+#include <assert.h>
+#undef check
+#define check assert
+
+CODEC_HEADER
+
+#ifdef CPU_ARM
+    #undef  ICODE_ATTR
+    #define ICODE_ATTR
+
+    #undef  IDATA_ATTR
+    #define IDATA_ATTR
+#endif
+
+/* TGB is the only target fast enough for gaussian and realtime BRR decode */
+/* echo is almost fast enough but not quite */
+#ifndef TOSHIBA_GIGABEAT_F
+    /* Cache BRR waves */
+    #define SPC_BRRCACHE 1
+
+    /* Disable gaussian interpolation */
+    #define SPC_NOINTERP 1
+
+    /* Disable echo processing */
+    #define SPC_NOECHO 1
+#endif
+
+/* Samples per channel per iteration */
+#ifdef CPU_COLDFIRE
+#define WAV_CHUNK_SIZE 1024
+#else
+#define WAV_CHUNK_SIZE 2048
+#endif
+
+/* simple profiling with USEC_TIMER */
+/*#define SPC_PROFILE*/
+
+#include "spc/spc_profiler.h"
+
+#define THIS struct Spc_Emu* const this
+
+/**************** Little-endian handling ****************/
+
+static inline unsigned get_le16( void const* p )
+{
+    return  ((unsigned char const*) p) [1] * 0x100u +
+            ((unsigned char const*) p) [0];
+}
+
+static inline int get_le16s( void const* p )
+{
+    return  ((signed char const*) p) [1] * 0x100 +
+            ((unsigned char const*) p) [0];
+}
+
+static inline void set_le16( void* p, unsigned n )
+{
+    ((unsigned char*) p) [1] = (unsigned char) (n >> 8);
+    ((unsigned char*) p) [0] = (unsigned char) n;
+}
+
+#define GET_LE16( addr )        get_le16( addr )
+#define SET_LE16( addr, data )  set_le16( addr, data )
+
+#if ROCKBOX_LITTLE_ENDIAN
+    #define GET_LE16A( addr )       (*(uint16_t*) (addr))
+    #define GET_LE16SA( addr )      (*( int16_t*) (addr))
+    #define SET_LE16A( addr, data ) (void) (*(uint16_t*) (addr) = (data))
+#else
+    #define GET_LE16A( addr )       get_le16 ( addr )
+    #define GET_LE16SA( addr )      get_le16s( addr )
+    #define SET_LE16A( addr, data ) set_le16 ( addr, data )
+#endif
+
+static struct
+{
+    union {
+        uint8_t padding1 [0x100];
+        uint16_t align;
+    } padding1 [1];
+    uint8_t ram      [0x10000];
+    uint8_t padding2 [0x100];
+} ram;
+
+#include "spc/Spc_Dsp.h"
+
+#undef RAM
+#define RAM ram.ram
+
+/**************** Timers ****************/
+
+enum { timer_count = 3 };
+
+struct Timer
+{
+    long next_tick;
+    int period;
+    int count;
+    int shift;
+    int enabled;
+    int counter;
+};
+
+static void Timer_run_( struct Timer* t, long time ) ICODE_ATTR;
+static void Timer_run_( struct Timer* t, long time )
+{
+    /* when disabled, next_tick should always be in the future */
+    assert( t->enabled ); 
+    
+    int elapsed = ((time - t->next_tick) >> t->shift) + 1;
+    t->next_tick += elapsed << t->shift;
+    
+    elapsed += t->count;
+    if ( elapsed >= t->period ) /* avoid unnecessary division */
+    {
+        int n = elapsed / t->period;
+        elapsed -= n * t->period;
+        t->counter = (t->counter + n) & 15;
+    }
+    t->count = elapsed;
+}
+
+static inline void Timer_run( struct Timer* t, long time )
+{
+    if ( time >= t->next_tick )
+        Timer_run_( t, time );
+}
+
+/**************** SPC emulator ****************/
+/* 1.024 MHz clock / 32000 samples per second */
+enum { clocks_per_sample = 32 }; 
+
+enum { extra_clocks = clocks_per_sample / 2 };
+
+/* using this disables timer (since this will always be in the future) */
+enum { timer_disabled_time = 127 };
+
+enum { rom_size = 64 };
+enum { rom_addr = 0xFFC0 };
+
+struct cpu_regs_t
+{
+    long pc; /* more than 16 bits to allow overflow detection */
+    uint8_t a;
+    uint8_t x;
+    uint8_t y;
+    uint8_t status;
+    uint8_t sp;
+};
+
+
+struct Spc_Emu
+{
+    uint8_t cycle_table [0x100];
+    struct cpu_regs_t r;
+    
+    int32_t* sample_buf;
+    long next_dsp;
+    int rom_enabled;
+    int extra_cycles;
+    
+    struct Timer timer [timer_count];
+    
+    /* large objects at end */
+    struct Spc_Dsp dsp;
+    uint8_t extra_ram [rom_size];
+    uint8_t boot_rom  [rom_size];
+};
+
+static void SPC_enable_rom( THIS, int enable )
+{
+    if ( this->rom_enabled != enable )
+    {
+        this->rom_enabled = enable;
+        memcpy( RAM + rom_addr, (enable ? this->boot_rom : this->extra_ram), rom_size );
+        /* TODO: ROM can still get overwritten when DSP writes to echo buffer */
+    }
+}
+
+static void SPC_Init( THIS )
+{
+    this->timer [0].shift = 4 + 3; /* 8 kHz */
+    this->timer [1].shift = 4 + 3; /* 8 kHz */
+    this->timer [2].shift = 4; /* 8 kHz */
+    
+    /* Put STOP instruction around memory to catch PC underflow/overflow. */
+    memset( ram.padding1, 0xFF, sizeof ram.padding1 );
+    memset( ram.padding2, 0xFF, sizeof ram.padding2 );
+    
+    /* A few tracks read from the last four bytes of IPL ROM */
+    this->boot_rom [sizeof this->boot_rom - 2] = 0xC0;
+    this->boot_rom [sizeof this->boot_rom - 1] = 0xFF;
+    memset( this->boot_rom, 0, sizeof this->boot_rom - 2 );
+}
+
+static void SPC_load_state( THIS, struct cpu_regs_t const* cpu_state,
+        const void* new_ram, const void* dsp_state )
+{
+    memcpy(&(this->r),cpu_state,sizeof this->r);
+        
+    /* ram */
+    memcpy( RAM, new_ram, sizeof RAM );
+    memcpy( this->extra_ram, RAM + rom_addr, sizeof this->extra_ram );
+    
+    /* boot rom (have to force enable_rom() to update it) */
+    this->rom_enabled = !(RAM [0xF1] & 0x80);
+    SPC_enable_rom( this, !this->rom_enabled );
+    
+    /* dsp */
+    /* some SPCs rely on DSP immediately generating one sample */
+    this->extra_cycles = 32; 
+    DSP_reset( &this->dsp );
+    int i;
+    for ( i = 0; i < register_count; i++ )
+        DSP_write( &this->dsp, i, ((uint8_t const*) dsp_state) [i] );
+    
+    /* timers */
+    for ( i = 0; i < timer_count; i++ )
+    {
+        struct Timer* t = &this->timer [i];
+        
+        t->next_tick = -extra_clocks;
+        t->enabled = (RAM [0xF1] >> i) & 1;
+        if ( !t->enabled )
+            t->next_tick = timer_disabled_time;
+        t->count = 0;
+        t->counter = RAM [0xFD + i] & 15;
+        
+        int p = RAM [0xFA + i];
+        if ( !p )
+            p = 0x100;
+        t->period = p;
+    }
+    
+    /* Handle registers which already give 0 when read by
+       setting RAM and not changing it.
+       Put STOP instruction in registers which can be read,
+       to catch attempted execution. */
+    RAM [0xF0] = 0;
+    RAM [0xF1] = 0;
+    RAM [0xF3] = 0xFF;
+    RAM [0xFA] = 0;
+    RAM [0xFB] = 0;
+    RAM [0xFC] = 0;
+    RAM [0xFD] = 0xFF;
+    RAM [0xFE] = 0xFF;
+    RAM [0xFF] = 0xFF;
+}
+
+static void clear_echo( THIS )
+{
+    if ( !(DSP_read( &this->dsp, 0x6C ) & 0x20) )
+    {
+        unsigned addr = 0x100 * DSP_read( &this->dsp, 0x6D );
+        size_t   size = 0x800 * DSP_read( &this->dsp, 0x7D );
+        size_t max_size = sizeof RAM - addr;
+        if ( size > max_size )
+            size = sizeof RAM - addr;
+        memset( RAM + addr, 0xFF, size );
+    }
+}
+
+enum { spc_file_size = 0x10180 };
+
+struct spc_file_t
+{
+    char    signature [27];
+    char    unused [10];
+    uint8_t pc [2];
+    uint8_t a;
+    uint8_t x;
+    uint8_t y;
+    uint8_t status;
+    uint8_t sp;
+    char    unused2 [212];
+    uint8_t ram [0x10000];
+    uint8_t dsp [128];
+    uint8_t ipl_rom [128];
+};
+
+static int SPC_load_spc( THIS, const void* data, long size )
+{
+    struct spc_file_t const* spc = (struct spc_file_t const*) data;
+    struct cpu_regs_t regs;
+    
+    if ( size < spc_file_size )
+        return -1;
+    
+    if ( memcmp( spc->signature, "SNES-SPC700 Sound File Data", 27 ) != 0 )
+        return -1;
+    
+    regs.pc     = spc->pc [1] * 0x100 + spc->pc [0];
+    regs.a      = spc->a;
+    regs.x      = spc->x;
+    regs.y      = spc->y;
+    regs.status = spc->status;
+    regs.sp     = spc->sp;
+    
+    if ( (unsigned long) size >= sizeof *spc )
+        memcpy( this->boot_rom, spc->ipl_rom, sizeof this->boot_rom );
+    
+    SPC_load_state( this, &regs, spc->ram, spc->dsp );
+    
+    clear_echo(this);
+    
+    return 0;
+}
+
+/**************** DSP interaction ****************/
+
+static void SPC_run_dsp_( THIS, long time ) ICODE_ATTR;
+static void SPC_run_dsp_( THIS, long time )
+{
+    /* divide by clocks_per_sample */
+    int count = ((time - this->next_dsp) >> 5) + 1; 
+    int32_t* buf = this->sample_buf;
+    this->sample_buf = buf + count;
+    this->next_dsp += count * clocks_per_sample;
+    DSP_run( &this->dsp, count, buf );
+}
+
+static inline void SPC_run_dsp( THIS, long time )
+{
+    if ( time >= this->next_dsp )
+        SPC_run_dsp_( this, time );
+}
+
+static int SPC_read( THIS, unsigned addr, long const time ) ICODE_ATTR;
+static int SPC_read( THIS, unsigned addr, long const time )
+{
+    int result = RAM [addr];
+    
+    if ( ((unsigned) (addr - 0xF0)) < 0x10 )
+    {
+        assert( 0xF0 <= addr && addr <= 0xFF );
+        
+        /* counters */
+        int i = addr - 0xFD;
+        if ( i >= 0 )
+        {
+            struct Timer* t = &this->timer [i];
+            Timer_run( t, time );
+            result = t->counter;
+            t->counter = 0;
+        }
+        /* dsp */
+        else if ( addr == 0xF3 )
+        {
+            SPC_run_dsp( this, time );
+            result = DSP_read( &this->dsp, RAM [0xF2] & 0x7F );
+        }
+    }
+    return result;
+}
+
+static void SPC_write( THIS, unsigned addr, int data, long const time )
+    ICODE_ATTR;
+static void SPC_write( THIS, unsigned addr, int data, long const time )
+{
+    /* first page is very common */
+    if ( addr < 0xF0 )
+    {
+        RAM [addr] = (uint8_t) data;
+    }
+    else switch ( addr )
+    {
+        /* RAM */
+        default:
+            if ( addr < rom_addr )
+            {
+                RAM [addr] = (uint8_t) data;
+            }
+            else
+            {
+                this->extra_ram [addr - rom_addr] = (uint8_t) data;
+                if ( !this->rom_enabled )
+                    RAM [addr] = (uint8_t) data;
+            }
+            break;
+        
+        /* DSP */
+        /*case 0xF2:*/ /* mapped to RAM */
+        case 0xF3: {
+            SPC_run_dsp( this, time );
+            int reg = RAM [0xF2];
+            if ( reg < register_count ) {
+                DSP_write( &this->dsp, reg, data );
+            }
+            else {
+                /*dprintf( "DSP write to $%02X\n", (int) reg ); */
+            }
+            break;
+        }
+        
+        case 0xF0: /* Test register */
+            /*dprintf( "Wrote $%02X to $F0\n", (int) data ); */
+            break;
+        
+        /* Config */
+        case 0xF1:
+        {
+            int i;
+            /* timers */
+            for ( i = 0; i < timer_count; i++ )
+            {
+                struct Timer * t = this->timer+i;
+                if ( !(data & (1 << i)) )
+                {
+                    t->enabled = 0;
+                    t->next_tick = timer_disabled_time;
+                }
+                else if ( !t->enabled )
+                {
+                    /* just enabled */
+                    t->enabled = 1;
+                    t->counter = 0;
+                    t->count = 0;
+                    t->next_tick = time;
+                }
+            }
+            
+            /* port clears */
+            if ( data & 0x10 )
+            {
+                RAM [0xF4] = 0;
+                RAM [0xF5] = 0;
+            }
+            if ( data & 0x20 )
+            {
+                RAM [0xF6] = 0;
+                RAM [0xF7] = 0;
+            }
+            
+            SPC_enable_rom( this, (data & 0x80) != 0 );
+            break;
+        }
+        
+        /* Ports */
+        case 0xF4:
+        case 0xF5:
+        case 0xF6:
+        case 0xF7:
+            /* to do: handle output ports */
+            break;
+
+        /* verified on SNES that these are read/write (RAM) */
+        /*case 0xF8: */
+        /*case 0xF9: */
+        
+        /* Timers */
+        case 0xFA:
+        case 0xFB:
+        case 0xFC: {
+            int i = addr - 0xFA;
+            struct Timer* t = &this->timer [i];
+            if ( (t->period & 0xFF) != data )
+            {
+                Timer_run( t, time );
+                this->timer[i].period = data ? data : 0x100;
+            }
+            break;
+        }
+        
+        /* Counters (cleared on write) */
+        case 0xFD:
+        case 0xFE:
+        case 0xFF:
+            /*dprintf( "Wrote to counter $%02X\n", (int) addr ); */
+            this->timer [addr - 0xFD].counter = 0;
+            break;
+    }
+}
+
+#include "spc/Spc_Cpu.h"
+
+/**************** Sample generation ****************/
+
+static int SPC_play( THIS, long count, int32_t* out ) ICODE_ATTR;
+static int SPC_play( THIS, long count, int32_t* out )
+{
+    int i;
+    assert( count % 2 == 0 ); /* output is always in pairs of samples */
+    
+    long start_time = -(count >> 1) * clocks_per_sample - extra_clocks;
+    
+    /* DSP output is made on-the-fly when DSP registers are read or written */
+    this->sample_buf = out;
+    this->next_dsp = start_time + clocks_per_sample;
+    
+    /* Localize timer next_tick times and run them to the present to prevent
+       a running but ignored timer's next_tick from getting too far behind
+       and overflowing. */
+    for ( i = 0; i < timer_count; i++ )
+    {
+        struct Timer* t = &this->timer [i];
+        if ( t->enabled )
+        {
+            t->next_tick += start_time + extra_clocks;
+            Timer_run( t, start_time );
+        }
+    }
+    
+    /* Run from start_time to 0, pre-advancing by extra cycles from last run */
+    this->extra_cycles = CPU_run( this, start_time + this->extra_cycles ) +
+                         extra_clocks;
+    if ( this->extra_cycles < 0 )
+    {
+        /*dprintf( "Unhandled instruction $%02X, pc = $%04X\n",
+                (int) CPU_read( r.pc ), (unsigned) r.pc ); */
+        
+        return -1;
+    }
+    
+    /* Catch DSP up to present */
+#if 0
+    ENTER_TIMER(cpu);
+#endif
+    SPC_run_dsp( this, -extra_clocks );
+#if 0
+    EXIT_TIMER(cpu);
+#endif
+    assert( this->next_dsp == clocks_per_sample - extra_clocks );
+    assert( this->sample_buf - out == count );
+    
+    return 0;
+}
+
+/**************** ID666 parsing ****************/
+
+struct {
+    unsigned char isBinary;
+    char song[32];
+    char game[32];
+    char dumper[16];
+    char comments[32];
+    int day,month,year;
+    unsigned long length;
+    unsigned long fade;
+    char artist[32];
+    unsigned char muted;
+    unsigned char emulator;
+} ID666;
+
+static int LoadID666(unsigned char *buf) {
+    unsigned char *ib=buf;
+    int isbinary = 1;
+    int i;
+  
+    memcpy(ID666.song,ib,32);
+    ID666.song[31]=0;
+    ib+=32;
+
+    memcpy(ID666.game,ib,32);
+    ID666.game[31]=0;
+    ib+=32;
+
+    memcpy(ID666.dumper,ib,16);
+    ID666.dumper[15]=0;
+    ib+=16;
+
+    memcpy(ID666.comments,ib,32);
+    ID666.comments[31]=0;
+    ib+=32;
+
+    /* Ok, now comes the fun part. */
+   
+    /* Date check */
+    if(ib[2] == '/'  && ib[5] == '/' )
+        isbinary = 0;
+  
+    /* Reserved bytes check */
+    if(ib[0xD2 - 0x2E - 112] >= '0' &&
+       ib[0xD2 - 0x2E - 112] <= '9' &&
+       ib[0xD3 - 0x2E - 112] == 0x00)
+        isbinary = 0;
+    
+    /* is length & fade only digits? */
+    for (i=0;i<8 && ( 
+        (ib[0xA9 - 0x2E - 112+i]>='0'&&ib[0xA9 - 0x2E - 112+i]<='9') ||
+        ib[0xA9 - 0x2E - 112+i]=='\0');
+        i++);
+    if (i==8) isbinary=0;
+    
+    ID666.isBinary = isbinary;
+  
+    if(isbinary) {
+        DEBUGF("binary tag detected\n");
+        ID666.year=*ib;
+        ib++;
+        ID666.year|=*ib<<8;
+        ib++;
+        ID666.month=*ib;
+        ib++;    
+        ID666.day=*ib;
+        ib++;
+
+        ib+=7;
+
+        ID666.length=*ib;
+        ib++;
+    
+        ID666.length|=*ib<<8;
+        ib++;
+    
+        ID666.length|=*ib<<16;
+        ID666.length*=1000;
+        ib++;
+    
+        ID666.fade=*ib;
+        ib++;
+        ID666.fade|=*ib<<8; 
+        ib++;
+        ID666.fade|=*ib<<16;
+        ib++;
+        ID666.fade|=*ib<<24;
+        ib++;
+
+        memcpy(ID666.artist,ib,32);
+        ID666.artist[31]=0;
+        ib+=32;
+
+        ID666.muted=*ib;
+        ib++;
+
+        ID666.emulator=*ib;
+        ib++;    
+    } else {
+        int year, month, day;
+        unsigned long tmp;
+        char buf[64];
+        
+        DEBUGF("text tag detected\n");
+    
+        year=month=day=0;
+        ib+=11;
+    
+        memcpy(buf, ib, 3);
+        buf[3] = 0; 
+        tmp = 0;
+        for (i=0;i<3 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0';
+        ID666.length = tmp * 1000;
+        ib+=3;
+
+        memcpy(buf, ib, 5);
+        buf[5] = 0;
+        tmp = 0;
+        for (i=0;i<5 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0';
+        ID666.fade = tmp;
+        ib+=5;
+
+        memcpy(ID666.artist,ib,32);
+        ID666.artist[31]=0;
+        ib+=32;
+
+        /*I have no idea if this is right or not.*/
+        ID666.muted=*ib;
+        ib++;
+
+        memcpy(buf, ib, 1);
+        buf[1] = 0;
+        tmp = 0;
+        ib++;
+    }
+    return 1;
+}
+
+/**************** Codec ****************/
+
+static int32_t samples[WAV_CHUNK_SIZE*2] IBSS_ATTR;
+
+static struct Spc_Emu spc_emu IDATA_ATTR;
+
+enum {sample_rate = 32000};
+
+/* The main decoder loop */
+static int play_track( void )
+{
+    int sampleswritten=0;
+    
+    unsigned long fadestartsample = ID666.length*(long long) sample_rate/1000;
+    unsigned long fadeendsample = (ID666.length+ID666.fade)*(long long) sample_rate/1000;
+    int fadedec = 0;
+    int fadevol = 0x7fffffffl;
+    
+    if (fadeendsample>fadestartsample)
+        fadedec=0x7fffffffl/(fadeendsample-fadestartsample)+1;
+        
+    ENTER_TIMER(total);
+    
+    while ( 1 )
+    {
+        ci->yield();
+        if (ci->stop_codec || ci->new_track) {
+            break;
+        }
+
+        if (ci->seek_time) {
+            int curtime = sampleswritten*1000LL/sample_rate;
+            DEBUGF("seek to %d\ncurrently at %d\n",ci->seek_time,curtime);
+            if (ci->seek_time < curtime) {
+                DEBUGF("seek backwards = reset\n");
+                ci->seek_complete();
+                return 1;
+            }
+            ci->seek_complete();
+        }
+        
+        ENTER_TIMER(render);
+        /* fill samples buffer */
+        if ( SPC_play(&spc_emu,WAV_CHUNK_SIZE*2,samples) )
+            assert( false );
+        EXIT_TIMER(render);
+        
+        sampleswritten+=WAV_CHUNK_SIZE;
+
+        /* is track timed? */
+        if (ci->global_settings->repeat_mode!=REPEAT_ONE && ci->id3->length) {
+            unsigned long curtime = sampleswritten*1000LL/sample_rate;
+            unsigned long lasttimesample = (sampleswritten-WAV_CHUNK_SIZE);
+
+            /* fade? */
+            if (curtime>ID666.length)
+            {
+                int i;
+                for (i=0;i<WAV_CHUNK_SIZE;i++) {
+                    if (lasttimesample+i>fadestartsample) {
+                        if (fadevol>0) {
+                            samples[i] = (samples[i]*(fadevol>>24))>>7;
+                            samples[i+WAV_CHUNK_SIZE] = (samples[i+WAV_CHUNK_SIZE]*(fadevol>>24))>>7;
+                        } else samples[i]=samples[i+WAV_CHUNK_SIZE]=0;
+                        fadevol-=fadedec;
+                    }
+                }
+            }
+            /* end? */
+            if (lasttimesample>=fadeendsample)
+                break;
+        }
+
+        ci->pcmbuf_insert(samples, samples+WAV_CHUNK_SIZE, WAV_CHUNK_SIZE);
+
+        if (ci->global_settings->repeat_mode!=REPEAT_ONE)
+        ci->set_elapsed(sampleswritten*1000LL/sample_rate);
+        else
+            ci->set_elapsed(0);
+    }
+    
+    EXIT_TIMER(total);
+    
+    return 0;
+}
+
+/* this is the codec entry point */
+enum codec_status codec_main(void)
+{
+    memcpy( spc_emu.cycle_table, cycle_table, sizeof cycle_table );
+
+    do
+    {
+        DEBUGF("SPC: next_track\n");
+        if (codec_init()) {
+            return CODEC_ERROR;
+        }
+        DEBUGF("SPC: after init\n");
+
+        ci->configure(DSP_SET_SAMPLE_DEPTH, 24);
+        ci->configure(DSP_SET_FREQUENCY, sample_rate);
+        ci->configure(DSP_SET_STEREO_MODE, STEREO_NONINTERLEAVED);
+
+        /* wait for track info to load */
+        while (!*ci->taginfo_ready && !ci->stop_codec)
+            ci->sleep(1);
+
+        /* Read the entire file */
+        DEBUGF("SPC: request initial buffer\n");
+        ci->configure(CODEC_SET_FILEBUF_WATERMARK, ci->filesize);
+
+        ci->seek_buffer(0);
+        size_t buffersize;
+        uint8_t* buffer = ci->request_buffer(&buffersize, ci->filesize);
+        if (!buffer) {
+            return CODEC_ERROR;
+        }
+
+        DEBUGF("SPC: read size = 0x%x\n",buffersize);
+        do
+        {
+            SPC_Init(&spc_emu);
+            if (SPC_load_spc(&spc_emu,buffer,buffersize)) {
+                DEBUGF("SPC load failure\n");
+                return CODEC_ERROR;
+            }
+
+            LoadID666(buffer+0x2e);
+            
+            if (ci->global_settings->repeat_mode!=REPEAT_ONE && ID666.length==0) {
+                ID666.length=3*60*1000; /* 3 minutes */
+                ID666.fade=5*1000; /* 5 seconds */
+            }
+            ci->id3->length = ID666.length+ID666.fade;
+            ci->id3->title = ID666.song;
+            ci->id3->album = ID666.game;
+            ci->id3->artist = ID666.artist;
+
+            reset_profile_timers();
+        }
+        
+        while ( play_track() );
+
+        print_timers(ci->id3->path);
+    }
+    while ( ci->request_next_track() );
+    
+    return CODEC_OK;
+}
diff --git a/apps/codecs/spc/Spc_Cpu.h b/apps/codecs/spc/Spc_Cpu.h
new file mode 100644
index 0000000..b931ca2
--- /dev/null
+++ b/apps/codecs/spc/Spc_Cpu.h
@@ -0,0 +1,1037 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ *
+ * Copyright (C) 2006-2007 Adam Gashlin (hcs)
+ * Copyright (C) 2004-2007 Shay Green (blargg)
+ * Copyright (C) 2002 Brad Martin
+ *
+ * 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.
+ *
+ ****************************************************************************/
+ 
+ 
+/* The CPU portion (shock!) */
+
+#define READ( addr )            (SPC_read( this, addr, spc_time_ ))
+#define WRITE( addr, value )    (SPC_write( this, addr, value, spc_time_ ))
+
+#define READ_DP( addr )         READ( (addr) + dp )
+#define WRITE_DP( addr, value ) WRITE( (addr) + dp, value )
+
+#define READ_PROG16( addr )     GET_LE16( RAM + (addr) )
+
+#define READ_PC( pc )           (*(pc))
+#define READ_PC16( pc )         GET_LE16( pc )
+
+#define SET_PC( n )             (pc = RAM + (n))
+#define GET_PC()                (pc - RAM)
+
+static unsigned char const cycle_table [0x100] = {
+    2,8,4,5,3,4,3,6,2,6,5,4,5,4,6,8, /* 0 */
+    2,8,4,5,4,5,5,6,5,5,6,5,2,2,4,6, /* 1 */
+    2,8,4,5,3,4,3,6,2,6,5,4,5,4,5,4, /* 2 */
+    2,8,4,5,4,5,5,6,5,5,6,5,2,2,3,8, /* 3 */
+    2,8,4,5,3,4,3,6,2,6,4,4,5,4,6,6, /* 4 */
+    2,8,4,5,4,5,5,6,5,5,4,5,2,2,4,3, /* 5 */
+    2,8,4,5,3,4,3,6,2,6,4,4,5,4,5,5, /* 6 */
+    2,8,4,5,4,5,5,6,5,5,5,5,2,2,3,6, /* 7 */
+    2,8,4,5,3,4,3,6,2,6,5,4,5,2,4,5, /* 8 */
+    2,8,4,5,4,5,5,6,5,5,5,5,2,2,12,5,/* 9 */
+    3,8,4,5,3,4,3,6,2,6,4,4,5,2,4,4, /* A */
+    2,8,4,5,4,5,5,6,5,5,5,5,2,2,3,4, /* B */
+    3,8,4,5,4,5,4,7,2,5,6,4,5,2,4,9, /* C */
+    2,8,4,5,5,6,6,7,4,5,4,5,2,2,6,3, /* D */
+    2,8,4,5,3,4,3,6,2,4,5,3,4,3,4,3, /* E */
+    2,8,4,5,4,5,5,6,3,4,5,4,2,2,4,3  /* F */
+};
+
+#define MEM_BIT() CPU_mem_bit( this, pc, spc_time_ )
+    
+static unsigned CPU_mem_bit( THIS, uint8_t const* pc, long const spc_time_ )
+    ICODE_ATTR;
+    
+static unsigned CPU_mem_bit( THIS, uint8_t const* pc, long const spc_time_ )
+{
+    unsigned addr = READ_PC16( pc );
+    unsigned t = READ( addr & 0x1FFF ) >> (addr >> 13);
+    return (t << 8) & 0x100;
+}
+
+/* status flags */
+enum { st_n = 0x80 };
+enum { st_v = 0x40 };
+enum { st_p = 0x20 };
+enum { st_b = 0x10 };
+enum { st_h = 0x08 };
+enum { st_i = 0x04 };
+enum { st_z = 0x02 };
+enum { st_c = 0x01 };
+    
+#define IS_NEG (nz & 0x880)
+
+#define CALC_STATUS( out )\
+{\
+    out = status & ~(st_n | st_z | st_c);\
+    out |= (c >> 8) & st_c;\
+    out |= (dp >> 3) & st_p;\
+    if ( IS_NEG ) out |= st_n;\
+    if ( !(nz & 0xFF) ) out |= st_z;\
+}
+
+#define SET_STATUS( in )\
+{\
+    status = in & ~(st_n | st_z | st_c | st_p);\
+    c = in << 8;\
+    nz = ((in << 4) & 0x800) | (~in & st_z);\
+    dp = (in << 3) & 0x100;\
+}
+
+
+/* stack */
+#define PUSH( v )       (*--sp = (uint8_t) (v))
+#define PUSH16( v )     (sp -= 2, SET_LE16( sp, v ))
+#define POP()           (*sp++)
+#define SET_SP( v )     (sp = RAM + 0x101 + (v))
+#define GET_SP()        (sp - 0x101 - RAM)
+
+static long CPU_run( THIS, long start_time ) ICODE_ATTR;
+
+static long CPU_run( THIS, long start_time )
+{
+#if 0
+    ENTER_TIMER(cpu);
+#endif
+    
+    register long spc_time_ = start_time;
+    
+#ifdef CPU_ARM
+    uint8_t* const ram_ = ram.ram;
+    #undef RAM
+    #define RAM ram_
+#endif
+    
+    int a = this->r.a;
+    int x = this->r.x;
+    int y = this->r.y;
+    
+    uint8_t const* pc;
+    SET_PC( this->r.pc );
+    
+    uint8_t* sp;
+    SET_SP( this->r.sp );
+    
+    int status;
+    int c;
+    int nz;
+    unsigned dp;
+    {
+        int temp = this->r.status;
+        SET_STATUS( temp );
+    }
+
+    goto loop;
+    
+    /* main loop */
+cbranch_taken_loop:
+    pc += *(int8_t const*) pc;
+    spc_time_ += 2;
+inc_pc_loop:
+    pc++;
+loop:
+    check( (unsigned) GET_PC() < 0x10000 );
+    check( (unsigned) GET_SP() < 0x100 );
+    check( (unsigned) a < 0x100 );
+    check( (unsigned) x < 0x100 );
+    check( (unsigned) y < 0x100 );
+    
+    unsigned opcode = *pc;
+    int cycles = this->cycle_table [opcode];
+    unsigned data = *++pc;
+    if ( (spc_time_ += cycles) > 0 )
+        goto out_of_time;
+    switch ( opcode )
+    {
+    
+/* Common instructions */
+
+#define BRANCH( cond )\
+{\
+    pc++;\
+    int offset = (int8_t) data;\
+    if ( cond ) {\
+        pc += offset;\
+        spc_time_ += 2;\
+    }\
+    goto loop;\
+}
+    
+    case 0xF0: /* BEQ (most common) */
+        BRANCH( !(uint8_t) nz )
+    
+    case 0xD0: /* BNE */
+        BRANCH( (uint8_t) nz )
+    
+    case 0x3F: /* CALL */
+        PUSH16( GET_PC() + 2 );
+        SET_PC( READ_PC16( pc ) );
+        goto loop;
+    
+    case 0x6F: /* RET */
+        SET_PC( POP() );
+        pc += POP() * 0x100;
+        goto loop;
+
+#define CASE( n )   case n:
+
+/* Define common address modes based on opcode for immediate mode. Execution
+   ends with data set to the address of the operand. */
+#define ADDR_MODES( op )\
+    CASE( op - 0x02 ) /* (X) */\
+        data = x + dp;\
+        pc--;\
+        goto end_##op;\
+    CASE( op + 0x0F ) /* (dp)+Y */\
+        data = READ_PROG16( data + dp ) + y;\
+        goto end_##op;\
+    CASE( op - 0x01 ) /* (dp+X) */\
+        data = READ_PROG16( ((uint8_t) (data + x)) + dp );\
+        goto end_##op;\
+    CASE( op + 0x0E ) /* abs+Y */\
+        data += y;\
+        goto abs_##op;\
+    CASE( op + 0x0D ) /* abs+X */\
+        data += x;\
+    CASE( op - 0x03 ) /* abs */\
+    abs_##op:\
+        data += 0x100 * READ_PC( ++pc );\
+        goto end_##op;\
+    CASE( op + 0x0C ) /* dp+X */\
+        data = (uint8_t) (data + x);\
+    CASE( op - 0x04 ) /* dp */\
+        data += dp;\
+    end_##op:
+
+/* 1. 8-bit Data Transmission Commands. Group I */
+
+    ADDR_MODES( 0xE8 ) /* MOV A,addr */
+    /*case 0xE4:*/ /* MOV a,dp (most common) */
+    mov_a_addr:
+        a = nz = READ( data );
+        goto inc_pc_loop;
+    case 0xBF: /* MOV A,(X)+ */
+        data = x + dp;
+        x = (uint8_t) (x + 1);
+        pc--;
+        goto mov_a_addr;
+    
+    case 0xE8: /* MOV A,imm */
+        a = data;
+        nz = data;
+        goto inc_pc_loop;
+    
+    case 0xF9: /* MOV X,dp+Y */
+        data = (uint8_t) (data + y);
+    case 0xF8: /* MOV X,dp */
+        data += dp;
+        goto mov_x_addr;
+    case 0xE9: /* MOV X,abs */
+        data = READ_PC16( pc );
+        pc++;
+    mov_x_addr:
+        data = READ( data );
+    case 0xCD: /* MOV X,imm */
+        x = data;
+        nz = data;
+        goto inc_pc_loop;
+    
+    case 0xFB: /* MOV Y,dp+X */
+        data = (uint8_t) (data + x);
+    case 0xEB: /* MOV Y,dp */
+        data += dp;
+        goto mov_y_addr;
+    case 0xEC: /* MOV Y,abs */
+        data = READ_PC16( pc );
+        pc++;
+    mov_y_addr:
+        data = READ( data );
+    case 0x8D: /* MOV Y,imm */
+        y = data;
+        nz = data;
+        goto inc_pc_loop;
+
+/* 2. 8-BIT DATA TRANSMISSION COMMANDS, GROUP 2 */
+
+    ADDR_MODES( 0xC8 ) /* MOV addr,A */
+        WRITE( data, a );
+        goto inc_pc_loop;
+    
+    {
+        int temp;
+    case 0xCC: /* MOV abs,Y */
+        temp = y;
+        goto mov_abs_temp;
+    case 0xC9: /* MOV abs,X */
+        temp = x;
+    mov_abs_temp:
+        WRITE( READ_PC16( pc ), temp );
+        pc += 2;
+        goto loop;
+    }
+    
+    case 0xD9: /* MOV dp+Y,X */
+        data = (uint8_t) (data + y);
+    case 0xD8: /* MOV dp,X */
+        WRITE( data + dp, x );
+        goto inc_pc_loop;
+    
+    case 0xDB: /* MOV dp+X,Y */
+        data = (uint8_t) (data + x);
+    case 0xCB: /* MOV dp,Y */
+        WRITE( data + dp, y );
+        goto inc_pc_loop;
+
+    case 0xFA: /* MOV dp,dp */
+        data = READ( data + dp );
+    case 0x8F: /* MOV dp,#imm */
+        WRITE_DP( READ_PC( ++pc ), data );
+        goto inc_pc_loop;
+    
+/* 3. 8-BIT DATA TRANSMISSIN COMMANDS, GROUP 3. */
+    
+    case 0x7D: /* MOV A,X */
+        a = x;
+        nz = x;
+        goto loop;
+    
+    case 0xDD: /* MOV A,Y */
+        a = y;
+        nz = y;
+        goto loop;
+    
+    case 0x5D: /* MOV X,A */
+        x = a;
+        nz = a;
+        goto loop;
+    
+    case 0xFD: /* MOV Y,A */
+        y = a;
+        nz = a;
+        goto loop;
+    
+    case 0x9D: /* MOV X,SP */
+        x = nz = GET_SP();
+        goto loop;
+    
+    case 0xBD: /* MOV SP,X */
+        SET_SP( x );
+        goto loop;
+    
+    /*case 0xC6:*/ /* MOV (X),A (handled by MOV addr,A in group 2) */
+    
+    case 0xAF: /* MOV (X)+,A */
+        WRITE_DP( x, a );
+        x++;
+        goto loop;
+    
+/* 5. 8-BIT LOGIC OPERATION COMMANDS */
+    
+#define LOGICAL_OP( op, func )\
+    ADDR_MODES( op ) /* addr */\
+        data = READ( data );\
+    case op: /* imm */\
+        nz = a func##= data;\
+        goto inc_pc_loop;\
+    {   unsigned addr;\
+    case op + 0x11: /* X,Y */\
+        data = READ_DP( y );\
+        addr = x + dp;\
+        pc--;\
+        goto addr_##op;\
+    case op + 0x01: /* dp,dp */\
+        data = READ_DP( data );\
+    case op + 0x10: /*dp,imm*/\
+        addr = READ_PC( ++pc ) + dp;\
+    addr_##op:\
+        nz = data func READ( addr );\
+        WRITE( addr, nz );\
+        goto inc_pc_loop;\
+    }
+    
+    LOGICAL_OP( 0x28, & ); /* AND */
+    
+    LOGICAL_OP( 0x08, | ); /* OR */
+    
+    LOGICAL_OP( 0x48, ^ ); /* EOR */
+    
+/* 4. 8-BIT ARITHMETIC OPERATION COMMANDS */
+
+    ADDR_MODES( 0x68 ) /* CMP addr */
+        data = READ( data );
+    case 0x68: /* CMP imm */
+        nz = a - data;
+        c = ~nz;
+        nz &= 0xFF;
+        goto inc_pc_loop;
+    
+    case 0x79: /* CMP (X),(Y) */
+        data = READ_DP( x );
+        nz = data - READ_DP( y );
+        c = ~nz;
+        nz &= 0xFF;
+        goto loop;
+    
+    case 0x69: /* CMP (dp),(dp) */
+        data = READ_DP( data );
+    case 0x78: /* CMP dp,imm */
+        nz = READ_DP( READ_PC( ++pc ) ) - data;
+        c = ~nz;
+        nz &= 0xFF;
+        goto inc_pc_loop;
+    
+    case 0x3E: /* CMP X,dp */
+        data += dp;
+        goto cmp_x_addr;
+    case 0x1E: /* CMP X,abs */
+        data = READ_PC16( pc );
+        pc++;
+    cmp_x_addr:
+        data = READ( data );
+    case 0xC8: /* CMP X,imm */
+        nz = x - data;
+        c = ~nz;
+        nz &= 0xFF;
+        goto inc_pc_loop;
+    
+    case 0x7E: /* CMP Y,dp */
+        data += dp;
+        goto cmp_y_addr;
+    case 0x5E: /* CMP Y,abs */
+        data = READ_PC16( pc );
+        pc++;
+    cmp_y_addr:
+        data = READ( data );
+    case 0xAD: /* CMP Y,imm */
+        nz = y - data;
+        c = ~nz;
+        nz &= 0xFF;
+        goto inc_pc_loop;
+    
+    {
+        int addr;
+    case 0xB9: /* SBC (x),(y) */
+    case 0x99: /* ADC (x),(y) */
+        pc--; /* compensate for inc later */
+        data = READ_DP( x );
+        addr = y + dp;
+        goto adc_addr;
+    case 0xA9: /* SBC dp,dp */
+    case 0x89: /* ADC dp,dp */
+        data = READ_DP( data );
+    case 0xB8: /* SBC dp,imm */
+    case 0x98: /* ADC dp,imm */
+        addr = READ_PC( ++pc ) + dp;
+    adc_addr:
+        nz = READ( addr );
+        goto adc_data;
+        
+/* catch ADC and SBC together, then decode later based on operand */
+#undef CASE
+#define CASE( n ) case n: case (n) + 0x20:
+    ADDR_MODES( 0x88 ) /* ADC/SBC addr */
+        data = READ( data );
+    case 0xA8: /* SBC imm */
+    case 0x88: /* ADC imm */
+        addr = -1; /* A */
+        nz = a;
+    adc_data: {
+        if ( opcode & 0x20 )
+            data ^= 0xFF; /* SBC */
+        int carry = (c >> 8) & 1;
+        int ov = (nz ^ 0x80) + carry + (int8_t) data; /* sign-extend */
+        int hc = (nz & 15) + carry;
+        c = nz += data + carry;
+        hc = (nz & 15) - hc;
+        status = (status & ~(st_v | st_h)) | ((ov >> 2) & st_v) |
+            ((hc >> 1) & st_h);
+        if ( addr < 0 ) {
+            a = (uint8_t) nz;
+            goto inc_pc_loop;
+        }
+        WRITE( addr, (uint8_t) nz );
+        goto inc_pc_loop;
+    }
+    
+    }
+    
+/* 6. ADDITION & SUBTRACTION COMMANDS */
+
+#define INC_DEC_REG( reg, n )\
+        nz = reg + n;\
+        reg = (uint8_t) nz;\
+        goto loop;
+
+    case 0xBC: INC_DEC_REG( a, 1 )  /* INC A */
+    case 0x3D: INC_DEC_REG( x, 1 )  /* INC X */
+    case 0xFC: INC_DEC_REG( y, 1 )  /* INC Y */
+    
+    case 0x9C: INC_DEC_REG( a, -1 ) /* DEC A */
+    case 0x1D: INC_DEC_REG( x, -1 ) /* DEC X */
+    case 0xDC: INC_DEC_REG( y, -1 ) /* DEC Y */
+
+    case 0x9B: /* DEC dp+X */
+    case 0xBB: /* INC dp+X */
+        data = (uint8_t) (data + x);
+    case 0x8B: /* DEC dp */
+    case 0xAB: /* INC dp */
+        data += dp;
+        goto inc_abs;
+    case 0x8C: /* DEC abs */
+    case 0xAC: /* INC abs */
+        data = READ_PC16( pc );
+        pc++;
+    inc_abs:
+        nz = ((opcode >> 4) & 2) - 1;
+        nz += READ( data );
+        WRITE( data, (uint8_t) nz );
+        goto inc_pc_loop;
+    
+/* 7. SHIFT, ROTATION COMMANDS */
+
+    case 0x5C: /* LSR A */
+        c = 0;
+    case 0x7C:{/* ROR A */
+        nz = ((c >> 1) & 0x80) | (a >> 1);
+        c = a << 8;
+        a = nz;
+        goto loop;
+    }
+    
+    case 0x1C: /* ASL A */
+        c = 0;
+    case 0x3C:{/* ROL A */
+        int temp = (c >> 8) & 1;
+        c = a << 1;
+        nz = c | temp;
+        a = (uint8_t) nz;
+        goto loop;
+    }
+    
+    case 0x0B: /* ASL dp */
+        c = 0;
+        data += dp;
+        goto rol_mem;
+    case 0x1B: /* ASL dp+X */
+        c = 0;
+    case 0x3B: /* ROL dp+X */
+        data = (uint8_t) (data + x);
+    case 0x2B: /* ROL dp */
+        data += dp;
+        goto rol_mem;
+    case 0x0C: /* ASL abs */
+        c = 0;
+    case 0x2C: /* ROL abs */
+        data = READ_PC16( pc );
+        pc++;
+    rol_mem:
+        nz = (c >> 8) & 1;
+        nz |= (c = READ( data ) << 1);
+        WRITE( data, (uint8_t) nz );
+        goto inc_pc_loop;
+    
+    case 0x4B: /* LSR dp */
+        c = 0;
+        data += dp;
+        goto ror_mem;
+    case 0x5B: /* LSR dp+X */
+        c = 0;
+    case 0x7B: /* ROR dp+X */
+        data = (uint8_t) (data + x);
+    case 0x6B: /* ROR dp */
+        data += dp;
+        goto ror_mem;
+    case 0x4C: /* LSR abs */
+        c = 0;
+    case 0x6C: /* ROR abs */
+        data = READ_PC16( pc );
+        pc++;
+    ror_mem: {
+        int temp = READ( data );
+        nz = ((c >> 1) & 0x80) | (temp >> 1);
+        c = temp << 8;
+        WRITE( data, nz );
+        goto inc_pc_loop;
+    }
+
+    case 0x9F: /* XCN */
+        nz = a = (a >> 4) | (uint8_t) (a << 4);
+        goto loop;
+
+/* 8. 16-BIT TRANSMISION COMMANDS */
+
+    case 0xBA: /* MOVW YA,dp */
+        a = READ_DP( data );
+        nz = (a & 0x7F) | (a >> 1);
+        y = READ_DP( (uint8_t) (data + 1) );
+        nz |= y;
+        goto inc_pc_loop;
+    
+    case 0xDA: /* MOVW dp,YA */
+        WRITE_DP( data, a );
+        WRITE_DP( (uint8_t) (data + 1), y );
+        goto inc_pc_loop;
+    
+/* 9. 16-BIT OPERATION COMMANDS */
+
+    case 0x3A: /* INCW dp */
+    case 0x1A:{/* DECW dp */
+        data += dp;
+        
+        /* low byte */
+        int temp = READ( data );
+        temp += ((opcode >> 4) & 2) - 1; /* +1 for INCW, -1 for DECW */
+        nz = ((temp >> 1) | temp) & 0x7F;
+        WRITE( data, (uint8_t) temp );
+        
+        /* high byte */
+        data = ((uint8_t) (data + 1)) + dp;
+        temp >>= 8;
+        temp = (uint8_t) (temp + READ( data ));
+        nz |= temp;
+        WRITE( data, temp );
+        
+        goto inc_pc_loop;
+    }
+        
+    case 0x9A: /* SUBW YA,dp */
+    case 0x7A: /* ADDW YA,dp */
+    {
+        /* read 16-bit addend */
+        int temp = READ_DP( data );
+        int sign = READ_DP( (uint8_t) (data + 1) );
+        temp += 0x100 * sign;
+        status &= ~(st_v | st_h);
+        
+        /* to do: fix half-carry for SUBW (it's probably wrong) */
+        
+        /* for SUBW, negate and truncate to 16 bits */
+        if ( opcode & 0x80 ) {
+            temp = (temp ^ 0xFFFF) + 1;
+            sign = temp >> 8;
+        }
+        
+        /* add low byte (A) */
+        temp += a;
+        a = (uint8_t) temp;
+        nz = (temp | (temp >> 1)) & 0x7F;
+        
+        /* add high byte (Y) */
+        temp >>= 8;
+        c = y + temp;
+        nz = (nz | c) & 0xFF;
+        
+        /* half-carry (temporary avoids CodeWarrior optimizer bug) */
+        unsigned hc = (c & 15) - (y & 15);
+        status |= (hc >> 4) & st_h;
+        
+        /* overflow if sign of YA changed when previous sign
+           and addend sign were same */
+        status |= (((c ^ y) & ~(y ^ sign)) >> 1) & st_v;
+        
+        y = (uint8_t) c;
+        
+        goto inc_pc_loop;
+    }
+    
+    case 0x5A: { /* CMPW YA,dp */
+        int temp = a - READ_DP( data );
+        nz = ((temp >> 1) | temp) & 0x7F;
+        temp = y + (temp >> 8);
+        temp -= READ_DP( (uint8_t) (data + 1) );
+        nz |= temp;
+        c = ~temp;
+        nz &= 0xFF;
+        goto inc_pc_loop;
+    }
+    
+/* 10. MULTIPLICATION & DIVISON COMMANDS */
+
+    case 0xCF: { /* MUL YA */
+        unsigned temp = y * a;
+        a = (uint8_t) temp;
+        nz = ((temp >> 1) | temp) & 0x7F;
+        y = temp >> 8;
+        nz |= y;
+        goto loop;
+    }
+    
+    case 0x9E: /* DIV YA,X */
+    {
+        /* behavior based on SPC CPU tests */
+        
+        status &= ~(st_h | st_v);
+        
+        if ( (y & 15) >= (x & 15) )
+            status |= st_h;
+        
+        if ( y >= x )
+            status |= st_v;
+        
+        unsigned ya = y * 0x100 + a;
+        if ( y < x * 2 )
+        {
+            a = ya / x;
+            y = ya - a * x;
+        }
+        else
+        {
+            a = 255 - (ya - x * 0x200) / (256 - x);
+            y = x   + (ya - x * 0x200) % (256 - x);
+        }
+        
+        nz = (uint8_t) a;
+        a = (uint8_t) a;
+        
+        goto loop;
+    }
+    
+/* 11. DECIMAL COMPENSATION COMMANDS */
+    
+    /* seem unused */
+    /* case 0xDF: */ /* DAA */
+    /* case 0xBE: */ /* DAS */
+    
+/* 12. BRANCHING COMMANDS */
+
+    case 0x2F: /* BRA rel */
+        pc += (int8_t) data;
+        goto inc_pc_loop;
+    
+    case 0x30: /* BMI */
+        BRANCH( IS_NEG )
+    
+    case 0x10: /* BPL */
+        BRANCH( !IS_NEG )
+    
+    case 0xB0: /* BCS */
+        BRANCH( c & 0x100 )
+    
+    case 0x90: /* BCC */
+        BRANCH( !(c & 0x100) )
+    
+    case 0x70: /* BVS */
+        BRANCH( status & st_v )
+    
+    case 0x50: /* BVC */
+        BRANCH( !(status & st_v) )
+    
+    case 0x03: /* BBS dp.bit,rel */
+    case 0x23:
+    case 0x43:
+    case 0x63:
+    case 0x83:
+    case 0xA3:
+    case 0xC3:
+    case 0xE3:
+        pc++;
+        if ( (READ_DP( data ) >> (opcode >> 5)) & 1 )
+            goto cbranch_taken_loop;
+        goto inc_pc_loop;
+    
+    case 0x13: /* BBC dp.bit,rel */
+    case 0x33:
+    case 0x53:
+    case 0x73:
+    case 0x93:
+    case 0xB3:
+    case 0xD3:
+    case 0xF3:
+        pc++;
+        if ( !((READ_DP( data ) >> (opcode >> 5)) & 1) )
+            goto cbranch_taken_loop;
+        goto inc_pc_loop;
+    
+    case 0xDE: /* CBNE dp+X,rel */
+        data = (uint8_t) (data + x);
+        /* fall through */
+    case 0x2E: /* CBNE dp,rel */
+        pc++;
+        if ( READ_DP( data ) != a )
+            goto cbranch_taken_loop;
+        goto inc_pc_loop;
+    
+    case 0xFE: /* DBNZ Y,rel */
+        y = (uint8_t) (y - 1);
+        BRANCH( y )
+    
+    case 0x6E: { /* DBNZ dp,rel */
+        pc++;
+        unsigned temp = READ_DP( data ) - 1;
+        WRITE_DP( (uint8_t) data, (uint8_t) temp );
+        if ( temp )
+            goto cbranch_taken_loop;
+        goto inc_pc_loop;
+    }
+    
+    case 0x1F: /* JMP (abs+X) */
+        SET_PC( READ_PC16( pc ) + x );
+        /* fall through */
+    case 0x5F: /* JMP abs */
+        SET_PC( READ_PC16( pc ) );
+        goto loop;
+    
+/* 13. SUB-ROUTINE CALL RETURN COMMANDS */
+    
+    case 0x0F:{/* BRK */
+        check( 0 ); /* untested */
+        PUSH16( GET_PC() + 1 );
+        SET_PC( READ_PROG16( 0xFFDE ) ); /* vector address verified */
+        int temp;
+        CALC_STATUS( temp );
+        PUSH( temp );
+        status = (status | st_b) & ~st_i;
+        goto loop;
+    }
+    
+    case 0x4F: /* PCALL offset */
+        PUSH16( GET_PC() + 1 );
+        SET_PC( 0xFF00 + data );
+        goto loop;
+    
+    case 0x01: /* TCALL n */
+    case 0x11:
+    case 0x21:
+    case 0x31:
+    case 0x41:
+    case 0x51:
+    case 0x61:
+    case 0x71:
+    case 0x81:
+    case 0x91:
+    case 0xA1:
+    case 0xB1:
+    case 0xC1:
+    case 0xD1:
+    case 0xE1:
+    case 0xF1:
+        PUSH16( GET_PC() );
+        SET_PC( READ_PROG16( 0xFFDE - (opcode >> 3) ) );
+        goto loop;
+    
+/* 14. STACK OPERATION COMMANDS */
+
+    {
+        int temp;
+    case 0x7F: /* RET1 */
+        temp = POP();
+        SET_PC( POP() );
+        pc += POP() << 8;
+        goto set_status;
+    case 0x8E: /* POP PSW */
+        temp = POP();
+    set_status:
+        SET_STATUS( temp );
+        goto loop;
+    }
+    
+    case 0x0D: { /* PUSH PSW */
+        int temp;
+        CALC_STATUS( temp );
+        PUSH( temp );
+        goto loop;
+    }
+
+    case 0x2D: /* PUSH A */
+        PUSH( a );
+        goto loop;
+    
+    case 0x4D: /* PUSH X */
+        PUSH( x );
+        goto loop;
+    
+    case 0x6D: /* PUSH Y */
+        PUSH( y );
+        goto loop;
+    
+    case 0xAE: /* POP A */
+        a = POP();
+        goto loop;
+    
+    case 0xCE: /* POP X */
+        x = POP();
+        goto loop;
+    
+    case 0xEE: /* POP Y */
+        y = POP();
+        goto loop;
+    
+/* 15. BIT OPERATION COMMANDS */
+
+    case 0x02: /* SET1 */
+    case 0x22:
+    case 0x42:
+    case 0x62:
+    case 0x82:
+    case 0xA2:
+    case 0xC2:
+    case 0xE2:
+    case 0x12: /* CLR1 */
+    case 0x32:
+    case 0x52:
+    case 0x72:
+    case 0x92:
+    case 0xB2:
+    case 0xD2:
+    case 0xF2: {
+        data += dp;
+        int bit = 1 << (opcode >> 5);
+        int mask = ~bit;
+        if ( opcode & 0x10 )
+            bit = 0;
+        WRITE( data, (READ( data ) & mask) | bit );
+        goto inc_pc_loop;
+    }
+        
+    case 0x0E: /* TSET1 abs */
+    case 0x4E:{/* TCLR1 abs */
+        data = READ_PC16( pc );
+        pc += 2;
+        unsigned temp = READ( data );
+        nz = temp & a;
+        temp &= ~a;
+        if ( !(opcode & 0x40) )
+            temp |= a;
+        WRITE( data, temp );
+        goto loop;
+    }
+    
+    case 0x4A: /* AND1 C,mem.bit */
+        c &= MEM_BIT();
+        pc += 2;
+        goto loop;
+    
+    case 0x6A: /* AND1 C,/mem.bit */
+        check( 0 ); /* untested */
+        c &= ~MEM_BIT();
+        pc += 2;
+        goto loop;
+    
+    case 0x0A: /* OR1 C,mem.bit */
+        check( 0 ); /* untested */
+        c |= MEM_BIT();
+        pc += 2;
+        goto loop;
+    
+    case 0x2A: /* OR1 C,/mem.bit */
+        check( 0 ); /* untested */
+        c |= ~MEM_BIT();
+        pc += 2;
+        goto loop;
+    
+    case 0x8A: /* EOR1 C,mem.bit */
+        c ^= MEM_BIT();
+        pc += 2;
+        goto loop;
+    
+    case 0xEA: { /* NOT1 mem.bit */
+        data = READ_PC16( pc );
+        pc += 2;
+        unsigned temp = READ( data & 0x1FFF );
+        temp ^= 1 << (data >> 13);
+        WRITE( data & 0x1FFF, temp );
+        goto loop;
+    }
+    
+    case 0xCA: { /* MOV1 mem.bit,C */
+        data = READ_PC16( pc );
+        pc += 2;
+        unsigned temp = READ( data & 0x1FFF );
+        unsigned bit = data >> 13;
+        temp = (temp & ~(1 << bit)) | (((c >> 8) & 1) << bit);
+        WRITE( data & 0x1FFF, temp );
+        goto loop;
+    }
+    
+    case 0xAA: /* MOV1 C,mem.bit */
+        c = MEM_BIT();
+        pc += 2;
+        goto loop;
+    
+/* 16. PROGRAM STATUS FLAG OPERATION COMMANDS */
+
+    case 0x60: /* CLRC */
+        c = 0;
+        goto loop;
+        
+    case 0x80: /* SETC */
+        c = ~0;
+        goto loop;
+    
+    case 0xED: /* NOTC */
+        c ^= 0x100;
+        goto loop;
+        
+    case 0xE0: /* CLRV */
+        status &= ~(st_v | st_h);
+        goto loop;
+    
+    case 0x20: /* CLRP */
+        dp = 0;
+        goto loop;
+    
+    case 0x40: /* SETP */
+        dp = 0x100;
+        goto loop;
+    
+    case 0xA0: /* EI */
+        check( 0 ); /* untested */
+        status |= st_i;
+        goto loop;
+    
+    case 0xC0: /* DI */
+        check( 0 ); /* untested */
+        status &= ~st_i;
+        goto loop;
+    
+/* 17. OTHER COMMANDS */
+
+    case 0x00: /* NOP */
+        goto loop;
+    
+    /*case 0xEF:*/ /* SLEEP */
+    /*case 0xFF:*/ /* STOP */
+    case 0xFF:
+        c |= 1; /* force switch table to have 256 entries,
+                   hopefully helping optimizer */
+    } /* switch */
+    
+    /* unhandled instructions fall out of switch so emulator can catch them */
+    
+out_of_time:
+    /* undo partial execution of opcode */
+    spc_time_ -= this->cycle_table [*--pc]; 
+    {
+        int temp;
+        CALC_STATUS( temp );
+        this->r.status = (uint8_t) temp;
+    }
+    
+    this->r.pc = GET_PC();
+    this->r.sp = (uint8_t) GET_SP();
+    this->r.a  = (uint8_t) a;
+    this->r.x  = (uint8_t) x;
+    this->r.y  = (uint8_t) y;
+ 
+#if 0
+    EXIT_TIMER(cpu);
+#endif
+    return spc_time_;
+}
diff --git a/apps/codecs/spc/Spc_Dsp.h b/apps/codecs/spc/Spc_Dsp.h
new file mode 100644
index 0000000..b297630
--- /dev/null
+++ b/apps/codecs/spc/Spc_Dsp.h
@@ -0,0 +1,1107 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ *
+ * Copyright (C) 2006-2007 Adam Gashlin (hcs)
+ * Copyright (C) 2004-2007 Shay Green (blargg)
+ * Copyright (C) 2002 Brad Martin
+ *
+ * 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.
+ *
+ ****************************************************************************/
+ 
+/* The DSP portion (awe!) */
+
+enum { voice_count = 8 };
+enum { register_count = 128 };
+
+struct raw_voice_t
+{
+    int8_t  volume [2];
+    uint8_t rate [2];
+    uint8_t waveform;
+    uint8_t adsr [2];   /* envelope rates for attack, decay, and sustain */
+    uint8_t gain;       /* envelope gain (if not using ADSR) */
+    int8_t  envx;       /* current envelope level */
+    int8_t  outx;       /* current sample */
+    int8_t  unused [6];
+};
+    
+struct globals_t
+{
+    int8_t  unused1 [12];
+    int8_t  volume_0;         /* 0C   Main Volume Left (-.7) */
+    int8_t  echo_feedback;    /* 0D   Echo Feedback (-.7) */
+    int8_t  unused2 [14];
+    int8_t  volume_1;         /* 1C   Main Volume Right (-.7) */
+    int8_t  unused3 [15];
+    int8_t  echo_volume_0;    /* 2C   Echo Volume Left (-.7) */
+    uint8_t pitch_mods;       /* 2D   Pitch Modulation on/off for each voice */
+    int8_t  unused4 [14];
+    int8_t  echo_volume_1;    /* 3C   Echo Volume Right (-.7) */
+    uint8_t noise_enables;    /* 3D   Noise output on/off for each voice */
+    int8_t  unused5 [14];
+    uint8_t key_ons;          /* 4C   Key On for each voice */
+    uint8_t echo_ons;         /* 4D   Echo on/off for each voice */
+    int8_t  unused6 [14];
+    uint8_t key_offs;         /* 5C   key off for each voice
+                                      (instantiates release mode) */
+    uint8_t wave_page;        /* 5D   source directory (wave table offsets) */
+    int8_t  unused7 [14];
+    uint8_t flags;            /* 6C   flags and noise freq */
+    uint8_t echo_page;        /* 6D */
+    int8_t  unused8 [14];
+    uint8_t wave_ended;       /* 7C */
+    uint8_t echo_delay;       /* 7D   ms >> 4 */
+    char    unused9 [2];
+};
+
+enum state_t {  /* -1, 0, +1 allows more efficient if statements */
+    state_decay   = -1,
+    state_sustain = 0,
+    state_attack  = +1,
+    state_release = 2
+};
+
+struct cache_entry_t
+{
+    int16_t const* samples;
+    unsigned end; /* past-the-end position */
+    unsigned loop; /* number of samples in loop */
+    unsigned start_addr;
+};
+
+enum { brr_block_size = 16 };
+
+struct voice_t
+{
+#if SPC_BRRCACHE
+    int16_t const* samples;
+    long wave_end;
+    int wave_loop;
+#else
+    int16_t samples [3 + brr_block_size + 1];
+    int block_header; /* header byte from current block */
+#endif
+    uint8_t const* addr;
+    short volume [2];
+    long position;/* position in samples buffer, with 12-bit fraction */
+    short envx;
+    short env_mode;
+    short env_timer;
+    short key_on_delay;
+};
+
+#if SPC_BRRCACHE
+/* a little extra for samples that go past end */
+static int16_t BRRcache [0x20000 + 32];
+#endif
+
+enum { fir_buf_half = 8 };
+
+struct Spc_Dsp
+{
+    union
+    {
+        struct raw_voice_t voice [voice_count];
+        uint8_t reg [register_count];
+        struct globals_t g;
+        int16_t align;
+    } r;
+    
+    unsigned echo_pos;
+    int keys_down;
+    int noise_count;
+    uint16_t noise; /* also read as int16_t */
+    
+    /* fir_buf [i + 8] == fir_buf [i], to avoid wrap checking in FIR code */
+    int fir_pos; /* (0 to 7) */
+    int fir_buf [fir_buf_half * 2] [2];
+    /* copy of echo FIR constants as int, for faster access */
+    int fir_coeff [voice_count]; 
+    
+    struct voice_t voice_state [voice_count];
+    
+#if SPC_BRRCACHE
+    uint8_t oldsize;
+    struct cache_entry_t wave_entry     [256];
+    struct cache_entry_t wave_entry_old [256];
+#endif
+};
+
+struct src_dir
+{
+    char start [2];
+    char loop  [2];
+};
+
+static void DSP_reset( struct Spc_Dsp* this )
+{
+    this->keys_down   = 0;
+    this->echo_pos    = 0;
+    this->noise_count = 0;
+    this->noise       = 2;
+    this->fir_pos     = 0;
+    
+    this->r.g.flags   = 0xE0; /* reset, mute, echo off */
+    this->r.g.key_ons = 0;
+    
+    memset( this->voice_state, 0, sizeof this->voice_state );
+    
+    int i;
+    for ( i = voice_count; --i >= 0; )
+    {
+        struct voice_t* v = this->voice_state + i;
+        v->env_mode = state_release;
+        v->addr     = ram.ram;
+    }
+    
+    #if SPC_BRRCACHE
+        this->oldsize = 0;
+        for ( i = 0; i < 256; i++ )
+            this->wave_entry [i].start_addr = -1;
+    #endif
+    
+    memset( this->fir_buf, 0, sizeof this->fir_buf );
+    assert( offsetof (struct globals_t,unused9 [2]) == register_count );
+    assert( sizeof (this->r.voice) == register_count );
+}
+
+static void DSP_write( struct Spc_Dsp* this, int i, int data ) ICODE_ATTR;
+static void DSP_write( struct Spc_Dsp* this, int i, int data )
+{
+    assert( (unsigned) i < register_count );
+    
+    this->r.reg [i] = data;
+    int high = i >> 4;
+    int low  = i & 0x0F;
+    if ( low < 2 ) /* voice volumes */
+    {
+        int left  = *(int8_t const*) &this->r.reg [i & ~1];
+        int right = *(int8_t const*) &this->r.reg [i |  1];
+        struct voice_t* v = this->voice_state + high;
+        v->volume [0] = left;
+        v->volume [1] = right;
+    }
+    else if ( low == 0x0F ) /* fir coefficients */
+    {
+        this->fir_coeff [7 - high] = (int8_t) data; /* sign-extend */
+    }
+}
+
+static inline int DSP_read( struct Spc_Dsp* this, int i )
+{
+    assert( (unsigned) i < register_count );
+    return this->r.reg [i];
+}
+    
+/* if ( n < -32768 ) out = -32768; */
+/* if ( n >  32767 ) out =  32767; */
+#define CLAMP16( n, out )\
+{\
+    if ( (int16_t) n != n )\
+        out = 0x7FFF ^ (n >> 31);\
+}
+
+#if SPC_BRRCACHE
+static void decode_brr( struct Spc_Dsp* this, unsigned start_addr,
+                        struct voice_t* voice,
+                        struct raw_voice_t const* const raw_voice ) ICODE_ATTR;
+static void decode_brr( struct Spc_Dsp* this, unsigned start_addr,
+                        struct voice_t* voice,
+                        struct raw_voice_t const* const raw_voice )
+{
+    /* setup same variables as where decode_brr() is called from */
+    #undef RAM
+    #define RAM ram.ram
+    struct src_dir const* const sd =
+        (struct src_dir*) &RAM [this->r.g.wave_page * 0x100];
+    struct cache_entry_t* const wave_entry =
+        &this->wave_entry [raw_voice->waveform];
+    
+    /* the following block can be put in place of the call to
+       decode_brr() below
+    */
+    {
+        DEBUGF( "decode at %08x (wave #%d)\n",
+                start_addr, raw_voice->waveform );
+        
+        /* see if in cache */
+        int i;
+        for ( i = 0; i < this->oldsize; i++ )
+        {
+            struct cache_entry_t* e = &this->wave_entry_old [i];
+            if ( e->start_addr == start_addr )
+            {
+                DEBUGF( "found in wave_entry_old (oldsize=%d)\n",
+                    this->oldsize );
+                *wave_entry = *e;
+                goto wave_in_cache;
+            }
+        }
+        
+        wave_entry->start_addr = start_addr;
+        
+        uint8_t const* const loop_ptr =
+            RAM + GET_LE16A( sd [raw_voice->waveform].loop );
+        short* loop_start = 0;
+        
+        short* out = BRRcache + start_addr * 2;
+        wave_entry->samples = out;
+        *out++ = 0;
+        int smp1 = 0;
+        int smp2 = 0;
+        
+        uint8_t const* addr = RAM + start_addr;
+        int block_header;
+        do
+        {
+            if ( addr == loop_ptr )
+            {
+                loop_start = out;
+                DEBUGF( "loop at %08x (wave #%d)\n", addr - RAM, raw_voice->waveform );
+            }
+            
+            /* header */
+            block_header = *addr;
+            addr += 9;
+            voice->addr = addr;
+            int const filter = (block_header & 0x0C) - 0x08;
+            
+            /* scaling
+               (invalid scaling gives -4096 for neg nybble, 0 for pos) */
+            static unsigned char const right_shifts [16] = {
+                5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,  4,  4, 29, 29, 29,
+            };
+            static unsigned char const left_shifts  [16] = {
+                0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 11, 11
+            };
+            int const scale = block_header >> 4;
+            int const right_shift = right_shifts [scale];
+            int const left_shift  = left_shifts  [scale];
+            
+            /* output position */
+            out += brr_block_size;
+            int offset = -brr_block_size << 2;
+            
+            do /* decode and filter 16 samples */
+            {
+                /* Get nybble, sign-extend, then scale
+                   get byte, select which nybble, sign-extend, then shift based
+                   on scaling. also handles invalid scaling values. */
+                int delta = (int) (int8_t) (addr [offset >> 3] << (offset & 4))
+                        >> right_shift << left_shift;
+                
+                out [offset >> 2] = smp2;
+                
+                if ( filter == 0 ) /* mode 0x08 (30-90% of the time) */
+                {
+                    delta -= smp2 >> 1;
+                    delta += smp2 >> 5;
+                    smp2 = smp1;
+                    delta += smp1;
+                    delta += (-smp1 - (smp1 >> 1)) >> 5;
+                }
+                else
+                {
+                    if ( filter == -4 ) /* mode 0x04 */
+                    {
+                        delta += smp1 >> 1;
+                        delta += (-smp1) >> 5;
+                    }
+                    else if ( filter > -4 ) /* mode 0x0C */
+                    {
+                        delta -= smp2 >> 1;
+                        delta += (smp2 + (smp2 >> 1)) >> 4;
+                        delta += smp1;
+                        delta += (-smp1 * 13) >> 7;
+                    }
+                    smp2 = smp1;
+                }
+                
+                CLAMP16( delta, delta );
+                smp1 = (int16_t) (delta * 2); /* sign-extend */
+            }
+            while ( (offset += 4) != 0 );
+            
+            /* if next block has end flag set, this block ends early */
+            /* (verified) */
+            if ( (block_header & 3) != 3 && (*addr & 3) == 1 )
+            {
+                /* skip last 9 samples */
+                out -= 9;
+                goto early_end;
+            }
+        }
+        while ( !(block_header & 1) && addr < RAM + 0x10000 );
+        
+        out [0] = smp2;
+        out [1] = smp1;
+        
+    early_end:
+        wave_entry->end = (out - 1 - wave_entry->samples) << 12;
+        
+        wave_entry->loop = 0;
+        if ( (block_header & 2) )
+        {
+            if ( loop_start )
+            {
+                int loop = out - loop_start;
+                wave_entry->loop = loop;
+                wave_entry->end += 0x3000;
+                out [2] = loop_start [2];
+                out [3] = loop_start [3];
+                out [4] = loop_start [4];
+            }
+            else
+            {
+                DEBUGF( "loop point outside initial wave\n" );
+            }
+        }
+        
+        DEBUGF( "end at %08x (wave #%d)\n", addr - RAM, raw_voice->waveform );
+        
+        /* add to cache */
+        this->wave_entry_old [this->oldsize++] = *wave_entry;
+wave_in_cache:;
+    }
+}
+#endif
+
+static void key_on(struct Spc_Dsp* const this, struct voice_t* const voice,
+                   struct src_dir const* const sd,
+                   struct raw_voice_t const* const raw_voice,
+                   const int key_on_delay, const int vbit) ICODE_ATTR;
+static void key_on(struct Spc_Dsp* const this, struct voice_t* const voice,
+                   struct src_dir const* const sd,
+                   struct raw_voice_t const* const raw_voice,
+                   const int key_on_delay, const int vbit) {
+    #undef RAM
+    #define RAM ram.ram
+    int const env_rate_init = 0x7800;
+    voice->key_on_delay = key_on_delay;
+    if ( key_on_delay == 0 )
+    {
+        this->keys_down |= vbit;
+        voice->envx         = 0;
+        voice->env_mode     = state_attack;
+        voice->env_timer    = env_rate_init; /* TODO: inaccurate? */
+        unsigned start_addr = GET_LE16A( sd [raw_voice->waveform].start );
+        #if !SPC_BRRCACHE
+        {
+            voice->addr = RAM + start_addr;
+            /* BRR filter uses previous samples */
+            voice->samples [brr_block_size + 1] = 0;
+            voice->samples [brr_block_size + 2] = 0;
+            /* decode three samples immediately */
+            voice->position     = (brr_block_size + 3) * 0x1000 - 1;
+            voice->block_header = 0; /* "previous" BRR header */
+        }
+        #else
+        {
+            voice->position = 3 * 0x1000 - 1;
+            struct cache_entry_t* const wave_entry =
+                &this->wave_entry [raw_voice->waveform];
+            
+            /* predecode BRR if not already */
+            if ( wave_entry->start_addr != start_addr )
+            {
+                /* the following line can be replaced by the indicated block
+                   in decode_brr() */
+                decode_brr( this, start_addr, voice, raw_voice );
+            }
+            
+            voice->samples   = wave_entry->samples;
+            voice->wave_end  = wave_entry->end;
+                    voice->wave_loop = wave_entry->loop;
+        }
+        #endif
+    }
+}
+
+static void DSP_run_( struct Spc_Dsp* this, long count, int32_t* out_buf )
+    ICODE_ATTR;
+static void DSP_run_( struct Spc_Dsp* this, long count, int32_t* out_buf )
+{
+    #undef RAM
+#ifdef CPU_ARM
+    uint8_t* const ram_ = ram.ram;
+    #define RAM ram_
+#else
+    #define RAM ram.ram
+#endif
+#if 0
+    EXIT_TIMER(cpu);
+    ENTER_TIMER(dsp);
+#endif
+    
+    /* Here we check for keys on/off.  Docs say that successive writes
+       to KON/KOF must be separated by at least 2 Ts periods or risk
+       being neglected.  Therefore DSP only looks at these during an
+       update, and not at the time of the write.  Only need to do this
+       once however, since the regs haven't changed over the whole
+       period we need to catch up with. */
+    
+    {
+        int key_ons  = this->r.g.key_ons;
+        int key_offs = this->r.g.key_offs;
+        /* keying on a voice resets that bit in ENDX */
+        this->r.g.wave_ended &= ~key_ons;
+        /* key_off bits prevent key_on from being acknowledged */
+        this->r.g.key_ons = key_ons & key_offs;
+        
+        /* process key events outside loop, since they won't re-occur */
+        struct voice_t* voice = this->voice_state + 8;
+        int vbit = 0x80;
+        do
+        {
+            --voice;
+            if ( key_offs & vbit )
+            {
+                voice->env_mode     = state_release;
+                voice->key_on_delay = 0;
+            }
+            else if ( key_ons & vbit )
+            {
+                voice->key_on_delay = 8;
+            }
+        }
+        while ( (vbit >>= 1) != 0 );
+    }
+    
+    struct src_dir const* const sd =
+        (struct src_dir*) &RAM [this->r.g.wave_page * 0x100];
+    
+#if !SPC_NOINTERP
+    int const slow_gaussian = (this->r.g.pitch_mods >> 1) |
+        this->r.g.noise_enables;
+#endif
+    /* (g.flags & 0x40) ? 30 : 14 */
+    int const global_muting = ((this->r.g.flags & 0x40) >> 2) + 14; 
+    
+    /* scaling to offset quietage */
+    int const global_vol_0 = this->r.g.volume_0 * 3;
+    int const global_vol_1 = this->r.g.volume_1 * 3;
+    
+    /* each rate divides exactly into 0x7800 without remainder */
+    int const env_rate_init = 0x7800;
+    static unsigned short const env_rates [0x20] ICONST_ATTR =
+    {
+        0x0000, 0x000F, 0x0014, 0x0018, 0x001E, 0x0028, 0x0030, 0x003C,
+        0x0050, 0x0060, 0x0078, 0x00A0, 0x00C0, 0x00F0, 0x0140, 0x0180,
+        0x01E0, 0x0280, 0x0300, 0x03C0, 0x0500, 0x0600, 0x0780, 0x0A00,
+        0x0C00, 0x0F00, 0x1400, 0x1800, 0x1E00, 0x2800, 0x3C00, 0x7800
+    };
+    
+    do /* one pair of output samples per iteration */
+    {
+        /* Noise */
+        if ( this->r.g.noise_enables )
+        {
+            if ( (this->noise_count -=
+                 env_rates [this->r.g.flags & 0x1F]) <= 0 )
+            {
+                this->noise_count = env_rate_init;
+                int feedback = (this->noise << 13) ^ (this->noise << 14);
+                this->noise = (feedback & 0x8000) ^ (this->noise >> 1 & ~1);
+            }
+        }
+        
+#if !SPC_NOECHO
+        int echo_0 = 0;
+        int echo_1 = 0;
+#endif
+        long prev_outx = 0; /* TODO: correct value for first channel? */
+        int chans_0 = 0;
+        int chans_1 = 0;
+        /* TODO: put raw_voice pointer in voice_t? */
+        struct raw_voice_t * raw_voice = this->r.voice;
+        struct voice_t* voice = this->voice_state;
+        int vbit = 1;
+        for ( ; vbit < 0x100; vbit <<= 1, ++voice, ++raw_voice )
+        {
+            /* pregen involves checking keyon, etc */
+#if 0
+            ENTER_TIMER(dsp_pregen);
+#endif
+            
+            /* Key on events are delayed */
+            int key_on_delay = voice->key_on_delay;
+
+            if ( --key_on_delay >= 0 ) /* <1% of the time */
+            {
+                key_on(this,voice,sd,raw_voice,key_on_delay,vbit);
+            }
+            
+            if ( !(this->keys_down & vbit) ) /* Silent channel */
+            {
+        silent_chan:
+                raw_voice->envx = 0;
+                raw_voice->outx = 0;
+                prev_outx = 0;
+                continue;
+            }
+            
+            /* Envelope */
+            {
+                int const env_range = 0x800;
+                int env_mode = voice->env_mode;
+                int adsr0 = raw_voice->adsr [0];
+                int env_timer;
+                if ( env_mode != state_release ) /* 99% of the time */
+                {
+                    env_timer = voice->env_timer;
+                    if ( adsr0 & 0x80 ) /* 79% of the time */
+                    {
+                        int adsr1 = raw_voice->adsr [1];
+                        if ( env_mode == state_sustain ) /* 74% of the time */
+                        {
+                            if ( (env_timer -= env_rates [adsr1 & 0x1F]) > 0 )
+                                goto write_env_timer;
+                            
+                            int envx = voice->envx;
+                            envx--; /* envx *= 255 / 256 */
+                            envx -= envx >> 8;
+                            voice->envx = envx;
+                            /* TODO: should this be 8? */
+                            raw_voice->envx = envx >> 4;
+                            goto init_env_timer;
+                        }
+                        else if ( env_mode < 0 ) /* 25% state_decay */
+                        {
+                            int envx = voice->envx;
+                            if ( (env_timer -=
+                                env_rates [(adsr0 >> 3 & 0x0E) + 0x10]) <= 0 )
+                            {
+                                envx--; /* envx *= 255 / 256 */
+                                envx -= envx >> 8;
+                                voice->envx = envx;
+                                /* TODO: should this be 8? */
+                                raw_voice->envx = envx >> 4;
+                                env_timer = env_rate_init;
+                            }
+                            
+                            int sustain_level = adsr1 >> 5;
+                            if ( envx <= (sustain_level + 1) * 0x100 )
+                                voice->env_mode = state_sustain;
+                            
+                            goto write_env_timer;
+                        }
+                        else /* state_attack */
+                        {
+                            int t = adsr0 & 0x0F;
+                            if ( (env_timer -= env_rates [t * 2 + 1]) > 0 )
+                                goto write_env_timer;
+                            
+                            int envx = voice->envx;
+                            
+                            int const step = env_range / 64;
+                            envx += step;
+                            if ( t == 15 )
+                                envx += env_range / 2 - step;
+                            
+                            if ( envx >= env_range )
+                            {
+                                envx = env_range - 1;
+                                voice->env_mode = state_decay;
+                            }
+                            voice->envx = envx;
+                            /* TODO: should this be 8? */
+                            raw_voice->envx = envx >> 4;
+                            goto init_env_timer;
+                        }
+                    }
+                    else /* gain mode */
+                    {
+                        int t = raw_voice->gain;
+                        if ( t < 0x80 )
+                        {
+                            raw_voice->envx = t;
+                            voice->envx = t << 4;
+                            goto env_end;
+                        }
+                        else
+                        {
+                            if ( (env_timer -= env_rates [t & 0x1F]) > 0 )
+                                goto write_env_timer;
+                            
+                            int envx = voice->envx;
+                            int mode = t >> 5;
+                            if ( mode <= 5 ) /* decay */
+                            {
+                                int step = env_range / 64;
+                                if ( mode == 5 ) /* exponential */
+                                {
+                                    envx--; /* envx *= 255 / 256 */
+                                    step = envx >> 8;
+                                }
+                                if ( (envx -= step) < 0 )
+                                {
+                                    envx = 0;
+                                    if ( voice->env_mode == state_attack )
+                                        voice->env_mode = state_decay;
+                                }
+                            }
+                            else /* attack */
+                            {
+                                int const step = env_range / 64;
+                                envx += step;
+                                if ( mode == 7 &&
+                                     envx >= env_range * 3 / 4 + step )
+                                    envx += env_range / 256 - step;
+                                
+                                if ( envx >= env_range )
+                                    envx = env_range - 1;
+                            }
+                            voice->envx = envx;
+                            /* TODO: should this be 8? */
+                            raw_voice->envx = envx >> 4;
+                            goto init_env_timer;
+                        }
+                    }
+                }
+                else /* state_release */
+                {
+                    int envx = voice->envx;
+                    if ( (envx -= env_range / 256) > 0 )
+                    {
+                        voice->envx = envx;
+                        raw_voice->envx = envx >> 8;
+                        goto env_end;
+                    }
+                    else
+                    {
+                        /* bit was set, so this clears it */
+                        this->keys_down ^= vbit;
+                        voice->envx = 0;
+                        goto silent_chan;
+                    }
+                }
+            init_env_timer:
+                env_timer = env_rate_init;
+            write_env_timer:
+                voice->env_timer = env_timer;
+            env_end:;
+            }
+#if 0            
+            EXIT_TIMER(dsp_pregen);
+            
+            ENTER_TIMER(dsp_gen);
+#endif
+            #if !SPC_BRRCACHE
+            /* Decode BRR block */
+            if ( voice->position >= brr_block_size * 0x1000 )
+            {
+                voice->position -= brr_block_size * 0x1000;
+                
+                uint8_t const* addr = voice->addr;
+                if ( addr >= RAM + 0x10000 )
+                    addr -= 0x10000;
+                
+                /* action based on previous block's header */
+                if ( voice->block_header & 1 )
+                {
+                    addr = RAM + GET_LE16A( sd [raw_voice->waveform].loop );
+                    this->r.g.wave_ended |= vbit;
+                    if ( !(voice->block_header & 2) ) /* 1% of the time */
+                    {
+                        /* first block was end block;
+                           don't play anything (verified) */
+                        /* bit was set, so this clears it */
+                        this->keys_down ^= vbit; 
+                        
+                        /* since voice->envx is 0,
+                           samples and position don't matter */
+                        raw_voice->envx = 0;
+                        voice->envx = 0;
+                        goto skip_decode;
+                    }
+                }
+                
+                /* header */
+                int const block_header = *addr;
+                addr += 9;
+                voice->addr = addr;
+                voice->block_header = block_header;
+                int const filter = (block_header & 0x0C) - 0x08;
+                
+                /* scaling (invalid scaling gives -4096 for neg nybble,
+                   0 for pos) */
+                static unsigned char const right_shifts [16] = {
+                    5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,  4,  4, 29, 29, 29,
+                };
+                static unsigned char const left_shifts  [16] = {
+                    0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 11, 11, 11
+                };
+                int const scale = block_header >> 4;
+                int const right_shift = right_shifts [scale];
+                int const left_shift  = left_shifts  [scale];
+                
+                /* previous samples */
+                int smp2 = voice->samples [brr_block_size + 1];
+                int smp1 = voice->samples [brr_block_size + 2];
+                voice->samples [0] = voice->samples [brr_block_size];
+                
+                /* output position */
+                short* out = voice->samples + (1 + brr_block_size);
+                int offset = -brr_block_size << 2;
+                
+                /* if next block has end flag set,
+                   this block ends early (verified) */
+                if ( (block_header & 3) != 3 && (*addr & 3) == 1 )
+                {
+                    /* arrange for last 9 samples to be skipped */
+                    int const skip = 9;
+                    out += (skip & 1);
+                    voice->samples [skip] = voice->samples [brr_block_size];
+                    voice->position += skip * 0x1000;
+                    offset = (-brr_block_size + (skip & ~1)) << 2;
+                    addr -= skip / 2;
+                    /* force sample to end on next decode */
+                    voice->block_header = 1;
+                }
+                
+                do /* decode and filter 16 samples */
+                {
+                    /* Get nybble, sign-extend, then scale
+                       get byte, select which nybble, sign-extend, then shift
+                       based on scaling. also handles invalid scaling values.*/
+                    int delta = (int) (int8_t) (addr [offset >> 3] <<
+                            (offset & 4)) >> right_shift << left_shift;
+                    
+                    out [offset >> 2] = smp2;
+                    
+                    if ( filter == 0 ) /* mode 0x08 (30-90% of the time) */
+                    {
+                        delta -= smp2 >> 1;
+                        delta += smp2 >> 5;
+                        smp2 = smp1;
+                        delta += smp1;
+                        delta += (-smp1 - (smp1 >> 1)) >> 5;
+                    }
+                    else
+                    {
+                        if ( filter == -4 ) /* mode 0x04 */
+                        {
+                            delta += smp1 >> 1;
+                            delta += (-smp1) >> 5;
+                        }
+                        else if ( filter > -4 ) /* mode 0x0C */
+                        {
+                            delta -= smp2 >> 1;
+                            delta += (smp2 + (smp2 >> 1)) >> 4;
+                            delta += smp1;
+                            delta += (-smp1 * 13) >> 7;
+                        }
+                        smp2 = smp1;
+                    }
+                    
+                    CLAMP16( delta, delta );
+                    smp1 = (int16_t) (delta * 2); /* sign-extend */
+                }
+                while ( (offset += 4) != 0 );
+                
+                out [0] = smp2;
+                out [1] = smp1;
+                
+            skip_decode:;
+            }
+            #endif
+
+            /* Get rate (with possible modulation) */
+            int rate = GET_LE16A( raw_voice->rate ) & 0x3FFF;
+            if ( this->r.g.pitch_mods & vbit )
+                rate = (rate * (prev_outx + 32768)) >> 15;
+
+        #if !SPC_NOINTERP
+            /* Interleved gauss table (to improve cache coherency). */
+            /* gauss [i * 2 + j] = normal_gauss [(1 - j) * 256 + i] */
+            static short const gauss [512] =
+            {
+370,1305, 366,1305, 362,1304, 358,1304, 354,1304, 351,1304, 347,1304, 343,1303,
+339,1303, 336,1303, 332,1302, 328,1302, 325,1301, 321,1300, 318,1300, 314,1299,
+311,1298, 307,1297, 304,1297, 300,1296, 297,1295, 293,1294, 290,1293, 286,1292,
+283,1291, 280,1290, 276,1288, 273,1287, 270,1286, 267,1284, 263,1283, 260,1282,
+257,1280, 254,1279, 251,1277, 248,1275, 245,1274, 242,1272, 239,1270, 236,1269,
+233,1267, 230,1265, 227,1263, 224,1261, 221,1259, 218,1257, 215,1255, 212,1253,
+210,1251, 207,1248, 204,1246, 201,1244, 199,1241, 196,1239, 193,1237, 191,1234,
+188,1232, 186,1229, 183,1227, 180,1224, 178,1221, 175,1219, 173,1216, 171,1213,
+168,1210, 166,1207, 163,1205, 161,1202, 159,1199, 156,1196, 154,1193, 152,1190,
+150,1186, 147,1183, 145,1180, 143,1177, 141,1174, 139,1170, 137,1167, 134,1164,
+132,1160, 130,1157, 128,1153, 126,1150, 124,1146, 122,1143, 120,1139, 118,1136,
+117,1132, 115,1128, 113,1125, 111,1121, 109,1117, 107,1113, 106,1109, 104,1106,
+102,1102, 100,1098,  99,1094,  97,1090,  95,1086,  94,1082,  92,1078,  90,1074,
+ 89,1070,  87,1066,  86,1061,  84,1057,  83,1053,  81,1049,  80,1045,  78,1040,
+ 77,1036,  76,1032,  74,1027,  73,1023,  71,1019,  70,1014,  69,1010,  67,1005,
+ 66,1001,  65, 997,  64, 992,  62, 988,  61, 983,  60, 978,  59, 974,  58, 969,
+ 56, 965,  55, 960,  54, 955,  53, 951,  52, 946,  51, 941,  50, 937,  49, 932,
+ 48, 927,  47, 923,  46, 918,  45, 913,  44, 908,  43, 904,  42, 899,  41, 894,
+ 40, 889,  39, 884,  38, 880,  37, 875,  36, 870,  36, 865,  35, 860,  34, 855,
+ 33, 851,  32, 846,  32, 841,  31, 836,  30, 831,  29, 826,  29, 821,  28, 816,
+ 27, 811,  27, 806,  26, 802,  25, 797,  24, 792,  24, 787,  23, 782,  23, 777,
+ 22, 772,  21, 767,  21, 762,  20, 757,  20, 752,  19, 747,  19, 742,  18, 737,
+ 17, 732,  17, 728,  16, 723,  16, 718,  15, 713,  15, 708,  15, 703,  14, 698,
+ 14, 693,  13, 688,  13, 683,  12, 678,  12, 674,  11, 669,  11, 664,  11, 659,
+ 10, 654,  10, 649,  10, 644,   9, 640,   9, 635,   9, 630,   8, 625,   8, 620,
+  8, 615,   7, 611,   7, 606,   7, 601,   6, 596,   6, 592,   6, 587,   6, 582,
+  5, 577,   5, 573,   5, 568,   5, 563,   4, 559,   4, 554,   4, 550,   4, 545,
+  4, 540,   3, 536,   3, 531,   3, 527,   3, 522,   3, 517,   2, 513,   2, 508,
+  2, 504,   2, 499,   2, 495,   2, 491,   2, 486,   1, 482,   1, 477,   1, 473,
+  1, 469,   1, 464,   1, 460,   1, 456,   1, 451,   1, 447,   1, 443,   1, 439,
+  0, 434,   0, 430,   0, 426,   0, 422,   0, 418,   0, 414,   0, 410,   0, 405,
+  0, 401,   0, 397,   0, 393,   0, 389,   0, 385,   0, 381,   0, 378,   0, 374,
+            };
+            
+            /* Gaussian interpolation using most recent 4 samples */
+            long position = voice->position;
+            voice->position += rate;
+            short const* interp = voice->samples + (position >> 12);
+            int offset = position >> 4 & 0xFF;
+            
+            /* Only left half of gaussian kernel is in table, so we must mirror
+               for right half */
+            short const* fwd = gauss       + offset * 2;
+            short const* rev = gauss + 510 - offset * 2;
+            
+            /* Use faster gaussian interpolation when exact result isn't needed
+               by pitch modulator of next channel */
+            int amp_0, amp_1;
+            if ( !(slow_gaussian & vbit) ) /* 99% of the time */
+            {
+                /* Main optimization is lack of clamping. Not a problem since
+                   output never goes more than +/- 16 outside 16-bit range and
+                   things are clamped later anyway. Other optimization is to
+                   preserve fractional accuracy, eliminating several masks. */
+                int output = (((fwd [0] * interp [0] +
+                         fwd [1] * interp [1] +
+                         rev [1] * interp [2] +
+                         rev [0] * interp [3]    ) >> 11) * voice->envx) >> 11;
+                
+                /* duplicated here to give compiler more to run in parallel */
+                amp_0 = voice->volume [0] * output;
+                amp_1 = voice->volume [1] * output;
+                raw_voice->outx = output >> 8;
+            }
+            else
+            {
+                int output = *(int16_t*) &this->noise;
+                if ( !(this->r.g.noise_enables & vbit) )
+                {
+                    output = (fwd [0] * interp [0]) & ~0xFFF;
+                    output = (output + fwd [1] * interp [1]) & ~0xFFF;
+                    output = (output + rev [1] * interp [2]) >> 12;
+                    output = (int16_t) (output * 2);
+                    output += ((rev [0] * interp [3]) >> 12) * 2;
+                    CLAMP16( output, output );
+                }
+                output = (output * voice->envx) >> 11 & ~1;
+                
+                /* duplicated here to give compiler more to run in parallel */
+                amp_0 = voice->volume [0] * output;
+                amp_1 = voice->volume [1] * output;
+                prev_outx = output;
+                raw_voice->outx = (int8_t) (output >> 8);
+            }
+        #else
+        /* two-point linear interpolation */
+        #ifdef CPU_COLDFIRE
+            int32_t  output = (int16_t)this->noise;
+
+            if ( (this->r.g.noise_enables & vbit) == 0 )
+            {
+                uint32_t f = voice->position;
+                int32_t y1;
+                asm (
+              "move.l     %[f], %[y0]               \n" /* separate fraction */
+              "and.l      #0xfff, %[f]              \n" /* and whole parts   */
+              "lsr.l      %[sh], %[y0]              \n"
+              "move.l     2(%[s], %[y0].l*2), %[y1] \n" /* load two samples  */
+              "move.l     %[y1], %[y0]              \n" /* separate samples  */
+              "ext.l      %[y1]                     \n" /* y0=s[1], y1=s[2]  */
+              "swap       %[y0]                     \n"
+              "ext.l      %[y0]                     \n"
+              "sub.l      %[y0], %[y1]              \n" /* diff = y1 - y0    */
+              "muls.l     %[f], %[y1]               \n" /* y0 += f*diff      */
+              "asr.l      %[sh], %[y1]              \n"
+              "add.l      %[y1], %[y0]              \n"
+              : [f]"+&d"(f), [y0]"=&d"(output), [y1]"=&d"(y1)
+              : [s]"a"(voice->samples), [sh]"r"(12)
+                    );
+            }
+
+            voice->position += rate;
+        #else
+
+            /* Try this one out on ARM and see - similar to above but the asm
+               on coldfire removes a redundant register load worth 1 or 2%;
+               switching to loading two samples at once may help too. That's
+               done above and while 6 to 7% faster on cf over two 16 bit loads
+               it makes it endian dependant.
+               
+               measured small improvement (~1.5%) - hcs
+            */
+
+            int output;
+            
+            if ( (this->r.g.noise_enables & vbit) == 0 )
+            {
+                int const fraction = voice->position & 0xfff;
+                short const* const pos = (voice->samples + (voice->position >> 12)) + 1;
+                output = pos[0] + ((fraction * (pos[1] - pos[0])) >> 12);
+            } else {
+                output = *(int16_t *)&this->noise;
+            }
+
+            voice->position += rate;
+            
+            /* old version */
+#if 0
+            int fraction = voice->position & 0xFFF;
+            short const* const pos = voice->samples + (voice->position >> 12);
+            voice->position += rate;
+            int output =
+                (pos [2] * fraction + pos [1] * (0x1000 - fraction)) >> 12;
+            /* no interpolation (hardly faster, and crappy sounding) */
+            /*int output = pos [0];*/
+            if ( this->r.g.noise_enables & vbit )
+                output = *(int16_t*) &this->noise;
+#endif
+        #endif /* CPU_COLDFIRE */
+
+            output = (output * voice->envx) >> 11;
+
+            /* duplicated here to give compiler more to run in parallel */
+            int amp_0 = voice->volume [0] * output;
+            int amp_1 = voice->volume [1] * output;
+
+            prev_outx = output;
+            raw_voice->outx = (int8_t) (output >> 8);
+        #endif
+        
+        #if SPC_BRRCACHE
+            if ( voice->position >= voice->wave_end )
+            {
+                long loop_len = voice->wave_loop << 12;
+                voice->position -= loop_len;
+                this->r.g.wave_ended |= vbit;
+                if ( !loop_len )
+                {
+                    this->keys_down ^= vbit;
+                    raw_voice->envx = 0;
+                    voice->envx = 0;
+                }
+            }
+        #endif
+#if 0
+            EXIT_TIMER(dsp_gen);
+            
+            ENTER_TIMER(dsp_mix);
+#endif            
+            chans_0 += amp_0;
+            chans_1 += amp_1;
+            #if !SPC_NOECHO
+                if ( this->r.g.echo_ons & vbit )
+                {
+                    echo_0 += amp_0;
+                    echo_1 += amp_1;
+                }
+            #endif
+#if 0            
+            EXIT_TIMER(dsp_mix);
+#endif
+        }
+        /* end of voice loop */
+        
+    #if !SPC_NOECHO
+        /* Read feedback from echo buffer */
+        int echo_pos = this->echo_pos;
+        uint8_t* const echo_ptr = RAM +
+                ((this->r.g.echo_page * 0x100 + echo_pos) & 0xFFFF);
+        echo_pos += 4;
+        if ( echo_pos >= (this->r.g.echo_delay & 15) * 0x800 )
+            echo_pos = 0;
+        this->echo_pos = echo_pos;
+        int fb_0 = GET_LE16SA( echo_ptr     );
+        int fb_1 = GET_LE16SA( echo_ptr + 2 );
+        
+        /* Keep last 8 samples */
+        int (* const fir_ptr) [2] = this->fir_buf + this->fir_pos;
+        this->fir_pos = (this->fir_pos + 1) & (fir_buf_half - 1);
+        fir_ptr [           0] [0] = fb_0;
+        fir_ptr [           0] [1] = fb_1;
+        /* duplicate at +8 eliminates wrap checking below */
+        fir_ptr [fir_buf_half] [0] = fb_0;
+        fir_ptr [fir_buf_half] [1] = fb_1;
+        
+        /* Apply FIR */
+        fb_0 *= this->fir_coeff [0];
+        fb_1 *= this->fir_coeff [0];
+
+        #define DO_PT( i )\
+            fb_0 += fir_ptr [i] [0] * this->fir_coeff [i];\
+            fb_1 += fir_ptr [i] [1] * this->fir_coeff [i];
+        
+        DO_PT( 1 )
+        DO_PT( 2 )
+        DO_PT( 3 )
+        DO_PT( 4 )
+        DO_PT( 5 )
+        DO_PT( 6 )
+        DO_PT( 7 )
+        
+        /* Generate output */
+        int amp_0 = (chans_0 * global_vol_0 + fb_0 * this->r.g.echo_volume_0)
+                    >> global_muting;
+        int amp_1 = (chans_1 * global_vol_1 + fb_1 * this->r.g.echo_volume_1)
+                    >> global_muting;
+        CLAMP16( amp_0, amp_0 );
+        out_buf [0] = amp_0 * (1 << 8);
+        CLAMP16( amp_1, amp_1 );
+        out_buf [WAV_CHUNK_SIZE] = amp_1 * (1 << 8);
+        out_buf ++;
+        
+        /* Feedback into echo buffer */
+        int e0 = (echo_0 >> 7) + ((fb_0 * this->r.g.echo_feedback) >> 14);
+        int e1 = (echo_1 >> 7) + ((fb_1 * this->r.g.echo_feedback) >> 14);
+        if ( !(this->r.g.flags & 0x20) )
+        {
+            CLAMP16( e0, e0 );
+            SET_LE16A( echo_ptr    , e0 );
+            CLAMP16( e1, e1 );
+            SET_LE16A( echo_ptr + 2, e1 );
+        }
+    #else
+        /* Generate output */
+        int amp_0 = (chans_0 * global_vol_0) >> global_muting;
+        int amp_1 = (chans_1 * global_vol_1) >> global_muting;
+        CLAMP16( amp_0, amp_0 );
+        out_buf [0] = amp_0 * (1 << 8);
+        CLAMP16( amp_1, amp_1 );
+        out_buf [WAV_CHUNK_SIZE] = amp_1 * (1 << 8);
+        out_buf ++;
+    #endif
+    }
+    while ( --count );
+#if 0
+    EXIT_TIMER(dsp);
+    ENTER_TIMER(cpu);
+#endif
+}
+
+static inline void DSP_run( struct Spc_Dsp* this, long count, int32_t* out )
+{
+    /* Should we just fill the buffer with silence? Flags won't be cleared */
+    /* during this run so it seems it should keep resetting every sample. */
+    if ( this->r.g.flags & 0x80 )
+        DSP_reset( this );
+    
+    DSP_run_( this, count, out );
+}
diff --git a/apps/codecs/spc/spc_profiler.h b/apps/codecs/spc/spc_profiler.h
new file mode 100644
index 0000000..99d3fdf
--- /dev/null
+++ b/apps/codecs/spc/spc_profiler.h
@@ -0,0 +1,89 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
+ *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
+ *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
+ *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
+ *                     \/            \/     \/    \/            \/
+ *
+ * Copyright (C) 2006-2007 Adam Gashlin (hcs)
+ *
+ * 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.
+ *
+ ****************************************************************************/
+ 
+/* a fun simple elapsed time profiler */
+
+#if defined(SPC_PROFILE) && defined(USEC_TIMER)
+
+#define CREATE_TIMER(name) static uint32_t spc_timer_##name##_start,\
+    spc_timer_##name##_total
+#define ENTER_TIMER(name) spc_timer_##name##_start=USEC_TIMER
+#define EXIT_TIMER(name) spc_timer_##name##_total+=\
+    (USEC_TIMER-spc_timer_##name##_start)
+#define READ_TIMER(name) (spc_timer_##name##_total)
+#define RESET_TIMER(name) spc_timer_##name##_total=0
+
+#define PRINT_TIMER_PCT(bname,tname,nstr) ci->fdprintf( \
+    logfd,"%10ld ",READ_TIMER(bname));\
+    ci->fdprintf(logfd,"(%3d%%) " nstr "\t",\
+    ((uint64_t)READ_TIMER(bname))*100/READ_TIMER(tname))
+
+CREATE_TIMER(total);
+CREATE_TIMER(render);
+#if 0
+CREATE_TIMER(cpu);
+CREATE_TIMER(dsp);
+CREATE_TIMER(dsp_pregen);
+CREATE_TIMER(dsp_gen);
+CREATE_TIMER(dsp_mix);
+#endif
+
+static void reset_profile_timers(void) {
+    RESET_TIMER(total);
+    RESET_TIMER(render);
+#if 0
+    RESET_TIMER(cpu);
+    RESET_TIMER(dsp);
+    RESET_TIMER(dsp_pregen);
+    RESET_TIMER(dsp_gen);
+    RESET_TIMER(dsp_mix);
+#endif
+}
+
+static int logfd=-1;
+
+static void print_timers(char * path) {
+    logfd = ci->open("/spclog.txt",O_WRONLY|O_CREAT|O_APPEND);
+    ci->fdprintf(logfd,"%s:\t",path);
+    ci->fdprintf(logfd,"%10ld total\t",READ_TIMER(total));
+    PRINT_TIMER_PCT(render,total,"render");
+#if 0
+    PRINT_TIMER_PCT(cpu,total,"CPU");
+    PRINT_TIMER_PCT(dsp,total,"DSP");
+    ci->fdprintf(logfd,"(");
+    PRINT_TIMER_PCT(dsp_pregen,dsp,"pregen");
+    PRINT_TIMER_PCT(dsp_gen,dsp,"gen");
+    PRINT_TIMER_PCT(dsp_mix,dsp,"mix");
+#endif
+    ci->fdprintf(logfd,"\n");
+    
+    ci->close(logfd);
+    logfd=-1;
+}
+
+#else
+
+#define CREATE_TIMER(name)
+#define ENTER_TIMER(name)
+#define EXIT_TIMER(name)
+#define READ_TIMER(name)
+#define RESET_TIMER(name)
+#define print_timers(path)
+#define reset_profile_timers()
+
+#endif
diff --git a/apps/metadata.c b/apps/metadata.c
index 81b2a3f..33b8287 100644
--- a/apps/metadata.c
+++ b/apps/metadata.c
@@ -2229,7 +2229,10 @@
             return false;
         }
         break;
-
+    case AFMT_SPC:
+       track->id3.filesize = filesize(fd);
+       track->id3.genre = 36;
+       break;
     case AFMT_ADX:
         if (!get_adx_metadata(fd, &(track->id3)))
         {
diff --git a/apps/tree.c b/apps/tree.c
index 0e40206..f4bf54b 100644
--- a/apps/tree.c
+++ b/apps/tree.c
@@ -111,6 +111,7 @@
     { "adx", TREE_ATTR_MPA, Icon_Audio, VOICE_EXT_MPA },
     { "nsf", TREE_ATTR_MPA, Icon_Audio, VOICE_EXT_MPA },
     { "nsfe", TREE_ATTR_MPA, Icon_Audio, VOICE_EXT_MPA },
+	 { "spc", TREE_ATTR_MPA, Icon_Audio, VOICE_EXT_MPA },
 #endif
     { "m3u", TREE_ATTR_M3U, Icon_Playlist, LANG_PLAYLIST },
     { "m3u8", TREE_ATTR_M3U, Icon_Playlist, LANG_PLAYLIST },
diff --git a/firmware/export/id3.h b/firmware/export/id3.h
index c016bd5..980d3b4 100644
--- a/firmware/export/id3.h
+++ b/firmware/export/id3.h
@@ -52,6 +52,7 @@
     AFMT_ADX,          /* ADX File Format */
     AFMT_NSF,          /* NESM (NES Sound Format) */
     AFMT_SPEEX,        /* Ogg Speex speech */
+    AFMT_SPC,          /* SPC700 save state */
 #endif
 
     /* add new formats at any index above this line to have a sensible order -
diff --git a/firmware/id3.c b/firmware/id3.c
index bdcf8e5..0547bed 100644
--- a/firmware/id3.c
+++ b/firmware/id3.c
@@ -104,6 +104,9 @@
     /* Speex File Format */
     [AFMT_SPEEX] =
         AFMT_ENTRY("Speex","speex",   NULL,          "spx\0"      ),
+    /* SPC700 Save State */
+    [AFMT_SPC] =
+        AFMT_ENTRY("SPC",  "spc",     NULL,          "spc\0"      ),
 #endif
 };