Index: apps/tree.c
===================================================================
--- apps/tree.c	(revision 12014)
+++ apps/tree.c	(working copy)
@@ -108,6 +108,7 @@
     { "aiff",TREE_ATTR_MPA, Icon_Audio, VOICE_EXT_MPA },
     { "sid", TREE_ATTR_MPA, Icon_Audio, VOICE_EXT_MPA },
     { "adx", 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 },
Index: apps/metadata.c
===================================================================
--- apps/metadata.c	(revision 12014)
+++ apps/metadata.c	(working copy)
@@ -2012,6 +2012,9 @@
         }
 
         break;
+	 case AFMT_SPC:
+		 track->id3.filesize = filesize(fd);
+		 break;
 
     case AFMT_AIFF:
         if (!get_aiff_metadata(fd, &(track->id3)))
Index: apps/codecs/spc.c
===================================================================
--- apps/codecs/spc.c	(revision 0)
+++ apps/codecs/spc.c	(revision 0)
@@ -0,0 +1,3056 @@
+/* lovingly ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */
+/* Copyright (C) 2004-2006 Shay Green. */
+/* DSP Based on Brad Martin's OpenSPC DSP emulator */
+/* Copyright (C) 2002 Brad Martin */
+
+/* CPU here usually means the SPC700 */
+
+#include "codeclib.h"
+#include "inttypes.h"
+#include "system.h"
+
+CODEC_HEADER
+
+/* Maximum number of bytes to process in one iteration */
+#define WAV_CHUNK_SIZE (1024*2)
+
+/* Volume for ADX decoder */
+#define BASE_VOL 0x2000
+
+/* Number of times to loop looped tracks when repeat is disabled */
+#define LOOP_TIMES 2
+
+/* Length of fade-out for looped tracks (milliseconds) */
+#define FADE_LENGTH 10000L
+
+static int16_t samples[WAV_CHUNK_SIZE] IBSS_ATTR;
+
+uint8_t *buffer;
+size_t n, buffersize;
+
+//#include <string.h>
+//#include <stdlib.h>
+//#include <stdio.h>
+//#include <stdint.h>
+//#include <math.h>
+
+#define min(x,y) ((x)<(y)?(x):(y))
+#define max(x,y) ((x)>(y)?(x):(y))
+
+/* endian goodies */
+
+inline unsigned get_le16( void const* p ) {
+	return  ((unsigned char const*) p) [1] * 0x100u +
+			((unsigned char const*) p) [0];
+}
+inline unsigned get_be16( void const* p ) {
+	return  ((unsigned char const*) p) [0] * 0x100u +
+			((unsigned char const*) p) [1];
+}
+inline unsigned long get_le32( void const* p ) {
+	return  ((unsigned char const*) p) [3] * 0x01000000u +
+			((unsigned char const*) p) [2] * 0x00010000u +
+			((unsigned char const*) p) [1] * 0x00000100u +
+			((unsigned char const*) p) [0];
+}
+inline unsigned long get_be32( void const* p ) {
+	return  ((unsigned char const*) p) [0] * 0x01000000u +
+			((unsigned char const*) p) [1] * 0x00010000u +
+			((unsigned char const*) p) [2] * 0x00000100u +
+			((unsigned char const*) p) [3];
+}
+inline void set_le16( void* p, unsigned n ) {
+	((unsigned char*) p) [1] = (unsigned char) (n >> 8);
+	((unsigned char*) p) [0] = (unsigned char) n;
+}
+inline void set_be16( void* p, unsigned n ) {
+	((unsigned char*) p) [0] = (unsigned char) (n >> 8);
+	((unsigned char*) p) [1] = (unsigned char) n;
+}
+inline void set_le32( void* p, unsigned long n ) {
+	((unsigned char*) p) [3] = (unsigned char) (n >> 24);
+	((unsigned char*) p) [2] = (unsigned char) (n >> 16);
+	((unsigned char*) p) [1] = (unsigned char) (n >> 8);
+	((unsigned char*) p) [0] = (unsigned char) n;
+}
+inline void set_be32( void* p, unsigned long n ) {
+	((unsigned char*) p) [0] = (unsigned char) (n >> 24);
+	((unsigned char*) p) [1] = (unsigned char) (n >> 16);
+	((unsigned char*) p) [2] = (unsigned char) (n >> 8);
+	((unsigned char*) p) [3] = (unsigned char) n;
+}
+
+#if BLARGG_NONPORTABLE
+	// Optimized implementation if byte order is known
+#if BLARGG_LITTLE_ENDIAN
+#define GET_LE16( addr )        (*(uint16_t*) (addr))
+#define GET_LE32( addr )        (*(uint32_t*) (addr))
+#define SET_LE16( addr, data )  (void) (*(uint16_t*) (addr) = (data))
+#define SET_LE32( addr, data )  (void) (*(uint32_t*) (addr) = (data))
+#elif BLARGG_BIG_ENDIAN
+#define GET_BE16( addr )        (*(uint16_t*) (addr))
+#define GET_BE32( addr )        (*(uint32_t*) (addr))
+#define SET_BE16( addr, data )  (void) (*(uint16_t*) (addr) = (data))
+#define SET_BE32( addr, data )  (void) (*(uint32_t*) (addr) = (data))
+#endif
+	
+#if BLARGG_CPU_POWERPC && defined (__MWERKS__)
+		// PowerPC has special byte-reversed instructions
+		// to do: assumes that PowerPC is running in big-endian mode
+		// to do: implement for other compilers which don't support these macros
+#define GET_LE16( addr )        (__lhbrx( (addr), 0 ))
+#define GET_LE32( addr )        (__lwbrx( (addr), 0 ))
+#define SET_LE16( addr, data )  (__sthbrx( (data), (addr), 0 ))
+#define SET_LE32( addr, data )  (__stwbrx( (data), (addr), 0 ))
+#endif
+#endif
+
+#ifndef GET_LE16
+#define GET_LE16( addr )        get_le16( addr )
+#define GET_LE32( addr )        get_le32( addr )
+#define SET_LE16( addr, data )  set_le16( addr, data )
+#define SET_LE32( addr, data )  set_le32( addr, data )
+#endif
+
+#ifndef GET_BE16
+#define GET_BE16( addr )        get_be16( addr )
+#define GET_BE32( addr )        get_be32( addr )
+#define SET_BE16( addr, data )  set_be16( addr, data )
+#define SET_BE32( addr, data )  set_be32( addr, data )
+#endif
+
+/* Spc_Dsp.h */
+
+enum { voice_count = 8 };
+// Mute voice n if bit n (1 << n) of mask is clear.
+void DSP_mute_voices( int mask );
+	
+// Clear state and silence everything.
+void DSP_reset(void);
+	
+// Set gain, where 1.0 is normal. When greater than 1.0, output is clamped to
+// the 16-bit sample range.
+void DSP_set_gain( double );
+	
+// Read/write register 'n', where n ranges from 0 to register_count - 1.
+enum { register_count = 128 };
+int  DSP_read ( int n );
+void DSP_write( int n, int );
+	
+// Run DSP for 'count' samples. Write resulting samples to 'buf' if not NULL.
+void DSP_run( long count, short* buf );
+	
+struct raw_voice_t {
+	int8_t  left_vol;
+	int8_t  right_vol;
+	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  left_volume;        // 0C   Main Volume Left (-.7)
+	int8_t  echo_feedback;      // 0D   Echo Feedback (-.7)
+	int8_t  unused2 [14];
+	int8_t  right_volume;       // 1C   Main Volume Right (-.7)
+	int8_t  unused3 [15];
+	int8_t  left_echo_volume;   // 2C   Echo Volume Left (-.7)
+	uint8_t pitch_mods;         // 2D   Pitch Modulation on/off for each voice
+	int8_t  unused4 [14];
+	int8_t  right_echo_volume;  // 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];
+};
+	
+union {
+	struct raw_voice_t voice [voice_count];
+	uint8_t reg [register_count];
+	struct globals_t g;
+} dspregs;
+	
+// Cache of echo FIR values for faster access
+short fir_coeff [voice_count];
+	
+// fir_buf [i + 8] == fir_buf [i], to avoid wrap checking in FIR code
+short fir_buf [16] [2];
+int fir_offset; // (0 to 7)
+	
+enum { emu_gain_bits = 8 };
+int emu_gain;
+
+int keyed_on; // 8-bits for 8 voices
+int keys;
+
+int echo_ptr;
+int noise_amp;
+int noise;
+int noise_count;
+int surround_threshold;
+
+enum state_t {
+	state_attack,
+	state_decay,
+	state_sustain,
+	state_release
+};
+
+struct voice_t {
+	short volume [2];
+	short fraction;// 12-bit fractional position
+	short interp3; // most recent four decoded samples
+	short interp2;
+	short interp1;
+	short interp0;
+	short block_remain; // number of nybbles remaining in current block
+	unsigned short addr;
+	short block_header; // header byte from current block
+	short envcnt;
+	short envx;
+	short on_cnt;
+	short enabled; // 7 if enabled, 31 if disabled
+	short envstate;
+	short unused; // pad to power of 2
+};
+
+// If true, prevent channels and global volumes from being phase-negated
+void disable_surround( int disable );
+
+inline void disable_surround( int disable ) { surround_threshold = disable ? 0 : -0x7FFF; }
+
+struct voice_t voice_state [voice_count];
+
+int DSP_clock_envelope( int );
+
+inline void DSP_set_gain( double v ) { emu_gain = (int) (v * (1 << emu_gain_bits)); }
+
+inline int DSP_read( int i )
+{
+	//assert( (unsigned) i < register_count );
+	return dspregs.reg [i];
+}
+
+/* Spc_Cpu.h */
+typedef unsigned spc_addr_t;
+//typedef blargg_long spc_time_t;
+typedef long spc_time_t;
+
+// SPC-700 registers. *Not* kept updated during a call to run().
+struct registers_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;
+} r;
+	
+// Run CPU for at least 'count' cycles. Return the number of cycles remaining
+// when emulation stopped (negative if extra cycles were emulated). Emulation
+// stops when there are no more remaining cycles or an unhandled instruction
+// is encountered (STOP, SLEEP, and any others not yet implemented). In the
+// latter case, the return value is greater than zero.
+spc_time_t CPU_run( spc_time_t count );
+	
+// Number of clock cycles remaining for current run() call
+spc_time_t CPU_remain(void);
+	
+// Access memory as the emulated CPU does
+int  CPU_read ( spc_addr_t );
+void CPUwrite( spc_addr_t, int );
+	
+unsigned CPU_mem_bit( spc_addr_t );
+	
+spc_time_t CPU_remain_;
+
+inline spc_time_t CPU_remain(void) { return CPU_remain_; }
+
+/* Snes_Spc.h */
+
+// Load copy of SPC data into emulator. Clear echo buffer if 'clear_echo' is true.
+enum { spc_file_size = 0x10180 };
+int SPC_load_spc( const void* spc, long spc_size );
+	
+// Generate 'count' samples and optionally write to 'buf'. Count must be even.
+// Sample output is 16-bit 32kHz, signed stereo pairs with the left channel first.
+typedef short sample_t;
+int SPC_play( long count, sample_t* buf );
+	
+// Optional functionality
+	
+// Load copy of state into emulator.
+int SPC_load_state( const struct registers_t* const cpu_state, const void* const ram_64k,
+	const void* dsp_regs_128 );
+	
+// Clear echo buffer, useful because many tracks have junk in the buffer.
+void clear_echo(void);
+	
+// Mute voice n if bit n (1 << n) of mask is set
+void mute_voices( int mask );
+	
+// Set gain, where 1.0 is normal. When greater than 1.0, output is clamped the
+// 16-bit sample range.
+void set_gain( double );
+	
+// If true, prevent channels and global volumes from being phase-negated
+void disable_surround( int disable );
+	
+// Set 128 bytes to use for IPL boot ROM. Makes copy. Default is zero filled,
+// to avoid including copyrighted code from the SPC-700.
+void set_ipl_rom( const void* );
+	
+void SPC_set_tempo( double );
+	
+// timers
+struct Timer
+{
+	spc_time_t next_tick;
+	int period;
+	int count;
+	int divisor;
+	int enabled;
+	int counter;
+		
+};
+enum { timer_count = 3 };
+struct Timer timer [timer_count];
+
+void run_until_( struct Timer *, spc_time_t );
+inline void run_until( struct Timer *t, spc_time_t time )
+{
+	if ( time >= t->next_tick )
+		run_until_( t, time );
+}
+
+// hardware
+int extra_cycles;
+spc_time_t SPC_time(void);
+int  SPC_read( spc_addr_t );
+void SPC_write( spc_addr_t, int );
+	
+// dsp
+sample_t* sample_buf;
+sample_t* buf_end; // to do: remove this once possible bug resolved
+spc_time_t next_dsp;
+int keys_pressed;
+int keys_released;
+void SPC_run_dsp( spc_time_t );
+void SPC_run_dsp_( spc_time_t );
+	
+// boot rom
+enum { rom_size = 64 };
+enum { rom_addr = 0xFFC0 }; // mem;
+int rom_enabled;
+void SPC_enable_rom( int );
+	
+// CPU and RAM (at end because it's large)
+uint8_t extra_ram [rom_size];
+
+// mem;
+uint8_t padding1 [0x100];
+uint8_t ram [0x10000];
+uint8_t padding2 [0x100];
+
+uint8_t boot_rom [rom_size];
+
+/* Snes_Spc.cpp */
+
+// always in the future (CPU time can go over 0, but not by this much)
+int const timer_disabled_time = 127;
+
+void SPC_Init(void)
+{
+    /* CPU init */
+	CPU_remain_ = 0;
+
+    /* DSP init */
+	DSP_set_gain( 1.0 );
+	DSP_mute_voices( 0 );
+	disable_surround( 0 );
+	
+	//assert( offsetof (struct globals_t,unused9 [2]) == register_count );
+	//assert( sizeof (dspregs.voice) == register_count );
+	//blargg_verify_byte_order();
+
+    /* SPC init */
+	SPC_set_tempo( 1.0 );
+	
+	// Put STOP instruction around memory to catch PC underflow/overflow.
+	memset( padding1, 0xFF, sizeof padding1 );
+	memset( padding2, 0xFF, sizeof padding2 );
+	
+	// A few tracks read from the last four bytes of IPL ROM
+	boot_rom [sizeof boot_rom - 2] = 0xC0;
+	boot_rom [sizeof boot_rom - 1] = 0xFF;
+	memset( boot_rom, 0, sizeof boot_rom - 2 );
+}
+
+void SPC_set_tempo( double t )
+{
+	int unit = (int) (16.0 / t + 0.5);
+	
+	timer [0].divisor = unit * 8; // 8 kHz
+	timer [1].divisor = unit * 8; // 8 kHz
+	timer [2].divisor = unit;     // 64 kHz
+}
+
+// Load
+
+void set_ipl_rom( void const* in )
+{
+	memcpy( boot_rom, in, sizeof boot_rom );
+}
+
+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];
+};
+
+int SPC_load_spc( const void* data, long size )
+{
+	const struct spc_file_t* spc = (struct spc_file_t const*) data;
+	struct registers_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 )
+		set_ipl_rom( spc->ipl_rom );
+	
+	int error = SPC_load_state( &regs, spc->ram, spc->dsp );
+	
+	clear_echo();
+	
+	return error;
+}
+
+void clear_echo()
+{
+	if ( !(DSP_read( 0x6C ) & 0x20) )
+	{
+		unsigned addr = 0x100 * DSP_read( 0x6D );
+		size_t   size = 0x800 * DSP_read( 0x7D );
+		memset( ram + addr, 0xFF, min( size, sizeof ram - addr ) );
+	}
+}
+
+// Handle other file formats (emulator save states) in user code, not here.
+
+int SPC_load_state( const struct registers_t * const cpu_state, const void* const new_ram,
+		const void* const dsp_state )
+{
+	// cpu
+	memcpy(&r,cpu_state,sizeof r);
+		
+	// Allow DSP to generate one sample before code starts
+	// (Tengai Makyo Zero, Tenjin's Table Toss first notes are lost since it
+	// clears KON 31 cycles from starting execution. It works on the SNES
+	// since the SPC player adds a few extra cycles delay after restoring
+	// KON from the DSP registers at the end of an SPC file).
+	extra_cycles = 32; 
+	
+	// ram
+	memcpy( ram, new_ram, sizeof ram );
+	memcpy( extra_ram, ram + rom_addr, sizeof extra_ram );
+	
+	// boot rom (have to force enable_rom() to update it)
+	rom_enabled = !(ram [0xF1] & 0x80);
+	SPC_enable_rom( !rom_enabled );
+	
+	// dsp
+	DSP_reset();
+	int i;
+	for ( i = 0; i < register_count; i++ )
+		DSP_write( i, ((uint8_t const*) dsp_state) [i] );
+	
+	// timers
+	for ( i = 0; i < timer_count; i++ )
+	{
+		timer[i].next_tick = 0;
+		timer[i].enabled = ( ram [0xF1] >> i) & 1;
+		if ( !timer[i].enabled )
+			timer[i].next_tick = timer_disabled_time;
+		timer[i].count = 0;
+		timer[i].counter = ram [0xFD + i] & 15;
+		
+		int p = ram [0xFA + i];
+		timer[i].period = p ? p : 0x100;
+	}
+	
+	// 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 CPU 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;
+	
+	return 0; // success
+}
+
+// Hardware
+
+// Current time starts negative and ends at 0
+inline spc_time_t SPC_time()
+{
+	return -CPU_remain();
+}
+
+// Keep track of next time to run and avoid a function call if it hasn't been reached.
+
+// Timers
+
+void run_until_( struct Timer * t, spc_time_t time )
+{
+	//if ( !t->enabled )
+	//	dprintf( "next_tick: %ld, time: %ld", (long) t->next_tick, (long) time );
+	//assert( t->enabled ); // when disabled, next_tick should always be in the future
+	
+	int elapsed = ((time - t->next_tick) / t->divisor) + 1;
+	t->next_tick += elapsed * t->divisor;
+	
+	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;
+}
+
+// DSP
+
+const int clocks_per_sample = 32; // 1.024 MHz CPU clock / 32000 samples per second
+
+void SPC_run_dsp_( spc_time_t time )
+{
+	int count = ((time - next_dsp) >> 5) + 1; // divide by clocks_per_sample
+	sample_t* buf = sample_buf;
+	if ( buf ) {
+		sample_buf = buf + count * 2; // stereo
+		//assert( sample_buf <= buf_end );
+	}
+	next_dsp += count * clocks_per_sample;
+	DSP_run( count, buf );
+}
+
+inline void SPC_run_dsp( spc_time_t time )
+{
+	if ( time >= next_dsp )
+		SPC_run_dsp_( time );
+}
+
+// Read
+
+int SPC_read( spc_addr_t addr )
+{
+	int result = ram [addr];
+	
+	//if ( (rom_addr <= addr && addr < 0xFFFC || addr >= 0xFFFE) && rom_enabled )
+	//	dprintf( "Read from ROM: %04X -> %02X\n", addr, result );
+	
+	if ( ((unsigned) (addr - 0xF0)) < 0x10 )
+	{
+		//assert( 0xF0 <= addr && addr <= 0xFF );
+		
+		// counters
+		int i = addr - 0xFD;
+		if ( i >= 0 )
+		{
+			run_until(timer+i, SPC_time() );
+			int old = timer[i].counter;
+			timer[i].counter = 0;
+			return old;
+		}
+		
+		// dsp
+		if ( addr == 0xF3 )
+		{
+			SPC_run_dsp( SPC_time() );
+			//if ( ram [0xF2] >= register_count )
+				//dprintf( "DSP read from $%02X\n", (int) ram [0xF2] );
+			return DSP_read( ram [0xF2] & 0x7F );
+		}
+		
+		//if ( addr == 0xF0 || addr == 0xF1 || addr == 0xF8 ||
+		//		addr == 0xF9 || addr == 0xFA )
+			//dprintf( "Read from register $%02X\n", (int) addr );
+		
+		// Registers which always read as 0 are handled by setting mem.ram [reg] to 0
+		// at startup then never changing that value.
+		
+		//check(( check_for_echo_access( addr ), 1 ));
+	}
+	
+	return result;
+}
+
+
+// Write
+
+void SPC_enable_rom( int enable )
+{
+	if ( rom_enabled != enable )
+	{
+		rom_enabled = enable;
+		memcpy( ram + rom_addr, (enable ? boot_rom : extra_ram), rom_size );
+		// TODO: ROM can still get overwritten when DSP writes to echo buffer
+	}
+}
+
+void SPC_write( spc_addr_t addr, int data )
+{
+	// first page is very common
+	if ( addr < 0xF0 ) {
+		ram [addr] = (uint8_t) data;
+	}
+	else switch ( addr )
+	{
+		// RAM
+		default:
+			//check(( check_for_echo_access( addr ), 1 ));
+			if ( addr < rom_addr ) {
+				ram [addr] = (uint8_t) data;
+			}
+			else {
+				extra_ram [addr - rom_addr] = (uint8_t) data;
+				if ( !rom_enabled )
+					ram [addr] = (uint8_t) data;
+			}
+			break;
+		
+		// DSP
+		//case 0xF2: // mapped to RAM
+		case 0xF3: {
+			SPC_run_dsp( SPC_time() );
+			int reg = ram [0xF2];
+			if ( next_dsp > 0 ) {
+				// skip mode
+				
+				// key press
+				if ( reg == 0x4C )
+					keys_pressed |= data & ~DSP_read( 0x5C );
+				
+				// key release
+				if ( reg == 0x5C ) {
+					keys_released |= data;
+					keys_pressed &= ~data;
+				}
+			}
+			if ( reg < register_count ) {
+				DSP_write( 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 = 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 = SPC_time();
+				}
+			}
+			
+			// port clears
+			if ( data & 0x10 ) {
+				ram [0xF4] = 0;
+				ram [0xF5] = 0;
+			}
+			if ( data & 0x20 ) {
+				ram [0xF6] = 0;
+				ram [0xF7] = 0;
+			}
+			
+			SPC_enable_rom( (data & 0x80) != 0 );
+			
+			break;
+		}
+		
+		// Ports
+		case 0xF4:
+		case 0xF5:
+		case 0xF6:
+		case 0xF7:
+			// to do: handle output ports
+			break;
+		
+		//case 0xF8: // verified on SNES that these are read/write (RAM)
+		//case 0xF9:
+		
+		// Timers
+		case 0xFA:
+		case 0xFB:
+		case 0xFC: {
+			int i = addr - 0xFA;
+			if ( (timer[i].period & 0xFF) != data ) {
+				run_until(timer+i, SPC_time() );
+				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 );
+			timer [addr - 0xFD].counter = 0;
+			break;
+	}
+}
+
+// Play
+
+int SPC_play( long count, sample_t* out )
+{
+	int i;
+	//require( count % 2 == 0 ); // output is always in pairs of samples
+	
+	// CPU time() runs from -duration to 0
+	spc_time_t duration = (count / 2) * clocks_per_sample;
+	
+	// DSP output is made on-the-fly when the CPU reads/writes DSP registers
+	sample_buf = out;
+	buf_end = out + count;
+	next_dsp = -duration + 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++ )
+	{
+		if ( timer[i].enabled )
+		{
+			timer[i].next_tick -= duration;
+			run_until(timer+i, -duration );
+		}
+	}
+	
+	// Run CPU for duration, reduced by any extra cycles from previous run
+	int elapsed = CPU_run( duration - extra_cycles );
+	if ( elapsed > 0 )
+	{
+		//dprintf( "Unhandled instruction $%02X, pc = $%04X\n",
+		//		(int) CPU_read( r.pc ), (unsigned) r.pc );
+		return -1;
+	}
+	extra_cycles = -elapsed;
+	
+	// Catch DSP up to present.
+	SPC_run_dsp( 0 );
+	if ( out ) {
+		//assert( next_dsp == clocks_per_sample );
+		//assert( out == skip_sentinel || sample_buf - out == count );
+		//assert( sample_buf - out == count );
+	}
+	buf_end = 0;
+	
+	return 0;
+}
+
+/* Spc_Cpu.cpp */
+
+// Several instructions are commented out (or not even implemented). These aren't
+// used by the SPC files tested.
+
+// Optimize performance for the most common instructions, and size for the rest:
+//
+// 15%  0xF0    BEQ rel
+//  8%  0xE4    MOV A,dp
+//  4%  0xF5    MOV A,abs+X
+//  4%  0xD0    BNE rel
+//  4%  0x6F    RET
+//  4%  0x3F    CALL addr
+//  4%  0xF4    MOV A,dp+X
+//  3%  0xC4    MOV dp,A
+//  2%  0xEB    MOV Y,dp
+//  2%  0x3D    INC X
+//  2%  0xF6    MOV A,abs+Y
+// (1% and below not shown)
+
+#define READ( addr )            (SPC_read( addr ))
+#define WRITE( addr, value )    (SPC_write( addr, value ))
+
+#define READ_DP( addr )         READ( (addr) + dp )
+#define WRITE_DP( addr, value ) WRITE( (addr) + dp, value )
+
+#define READ_PROG( addr )       (ram [addr])
+#define READ_PROG16( addr )     GET_LE16( &READ_PROG( addr ) )
+
+int CPU_read( spc_addr_t addr )
+{
+	return READ( addr );
+}
+
+void CPU_write( spc_addr_t addr, int data )
+{
+	WRITE( addr, data );
+}
+
+// Cycle table derived from text copy of SPC-700 manual (using regular expressions)
+static unsigned char const cycle_table [0x100] = {
+//  0 1 2 3 4 5 6 7 8 9 A B C D E F
+	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
+};
+
+// The C,mem instructions are hardly used, so a non-inline function is used for
+// the common access code.
+unsigned CPU_mem_bit( spc_addr_t pc )
+{
+	unsigned addr = READ_PROG16( pc );
+	unsigned t = READ( addr & 0x1FFF ) >> (addr >> 13);
+	return (t << 8) & 0x100;
+}
+
+spc_time_t CPU_run( spc_time_t cycle_count )
+{
+	CPU_remain_ = cycle_count;
+	
+	//uint8_t* const ram = this->ram; // cache
+	
+	// Stack pointer is kept one greater than usual SPC stack pointer to allow
+	// common pre-decrement and post-increment memory instructions that some
+	// processors have. Address wrap-around isn't supported.
+	#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)
+
+	// registers
+	unsigned pc = (unsigned) r.pc;
+	int a = r.a;
+	int x = r.x;
+	int y = r.y;
+	
+	// status flags
+	
+	const int st_n = 0x80;
+	const int st_v = 0x40;
+	const int st_p = 0x20;
+	const int st_b = 0x10;
+	const int st_h = 0x08;
+	const int st_i = 0x04;
+	const int st_z = 0x02;
+	const int st_c = 0x01;
+	
+	unsigned opcode;
+	
+	uint8_t* sp;
+	SET_SP( r.sp );
+	
+	#define IS_NEG (nz & 0x880)
+	
+	#define CALC_STATUS( out ) do {\
+		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;\
+	} while ( 0 )       
+
+	#define SET_STATUS( in ) do {\
+		status = in & ~(st_n | st_z | st_c | st_p);\
+		c = in << 8;\
+		nz = (in << 4) & 0x800;\
+		nz |= ~in & st_z;\
+		dp = (in << 3) & 0x100;\
+	} while ( 0 )
+	
+	int status;
+	int c;  // store C as 'c' & 0x100.
+	int nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x880) != 0
+	unsigned dp; // direct page base
+	{
+		int temp = r.status;
+		SET_STATUS( temp );
+	}
+
+	goto loop;
+	
+	unsigned data; // first operand of instruction and temporary across function calls
+	
+	// Common endings for instructions
+cbranch_taken_loop: // compare and branch
+	pc += (int8_t) READ_PROG( pc );
+	CPU_remain_ -= 2;
+inc_pc_loop: // end of instruction with an operand
+	pc++;
+loop:
+	
+	//check( (unsigned) pc < 0x10000 );
+	//check( (unsigned) GET_SP() < 0x100 );
+	
+	//check( (unsigned) a < 0x100 );
+	//check( (unsigned) x < 0x100 );
+	//check( (unsigned) y < 0x100 );
+	
+	opcode = READ_PROG( pc );
+	pc++;
+	// to do: if pc is at end of memory, this will get wrong byte
+	data = READ_PROG( pc );
+	
+	if ( CPU_remain_ <= 0 )
+		goto stop;
+	
+	CPU_remain_ -= cycle_table [opcode];
+	
+	// Use 'data' for temporaries whose lifetime crosses read/write calls, otherwise
+	// use a local temporary.
+	switch ( opcode )
+	{
+	
+	#define BRANCH( cond ) {\
+		pc++;\
+		int offset = (int8_t) data;\
+		if ( cond ) {\
+			pc += offset;\
+			CPU_remain_ -= 2;\
+		}\
+		goto loop;\
+	}
+	
+// Most-Common
+
+	case 0xF0: // BEQ (most common)
+		BRANCH( !(uint8_t) nz )
+	
+	case 0xD0: // BNE
+		BRANCH( (uint8_t) nz )
+	
+	case 0x3F: // CALL
+		PUSH16( pc + 2 );
+		pc = READ_PROG16( pc );
+		goto loop;
+	
+	case 0x6F: // RET
+		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:\
+		pc++;\
+		data += 0x100 * READ_PROG( 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_PROG16( 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_PROG16( 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_PROG16( 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
+		pc++;
+		WRITE_DP( READ_PROG( 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*/\
+		pc++;\
+		addr = READ_PROG( 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
+		pc++;
+		nz = READ_DP( READ_PROG( 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_PROG16( 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_PROG16( 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
+		pc++;
+		addr = READ_PROG( 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_PROG16( 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_PROG16( 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_PROG16( 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)
+		pc = READ_PROG16( pc ) + x;
+		// fall through
+	case 0x5F: // JMP abs
+		pc = READ_PROG16( pc );
+		goto loop;
+	
+// 13. SUB-ROUTINE CALL RETURN COMMANDS
+	
+	case 0x0F:{// BRK
+		//check( 0 ); // untested
+		PUSH16( pc + 1 );
+		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
+		pc++;
+		PUSH16( pc );
+		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( pc );
+		pc = READ_PROG16( 0xFFDE - (opcode >> 3) );
+		goto loop;
+	
+// 14. STACK OPERATION COMMANDS
+
+	{
+		int temp;
+	case 0x7F: // RET1
+		temp = POP();
+		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_PROG16( 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 &= CPU_mem_bit( pc );
+		pc += 2;
+		goto loop;
+	
+	case 0x6A: // AND1 C,/mem.bit
+		//check( 0 ); // untested
+		c &= ~CPU_mem_bit( pc );
+		pc += 2;
+		goto loop;
+	
+	case 0x0A: // OR1 C,mem.bit
+		//check( 0 ); // untested
+		c |= CPU_mem_bit( pc );
+		pc += 2;
+		goto loop;
+	
+	case 0x2A: // OR1 C,/mem.bit
+		//check( 0 ); // untested
+		c |= ~CPU_mem_bit( pc );
+		pc += 2;
+		goto loop;
+	
+	case 0x8A: // EOR1 C,mem.bit
+		c ^= CPU_mem_bit( pc );
+		pc += 2;
+		goto loop;
+	
+	case 0xEA: { // NOT1 mem.bit
+		data = READ_PROG16( 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_PROG16( 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 = CPU_mem_bit( pc );
+		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
+	
+	} // switch
+	
+	// unhandled instructions fall out of switch so emulator can catch them
+	
+stop:
+	pc--;
+	
+	{
+		int temp;
+		CALC_STATUS( temp );
+		r.status = (uint8_t) temp;
+	}
+	
+	r.pc = pc;
+	r.sp = (uint8_t) GET_SP();
+	r.a  = (uint8_t) a;
+	r.x  = (uint8_t) x;
+	r.y  = (uint8_t) y;
+	
+	return CPU_remain_;
+}
+
+/* Spc_Dsp.cpp */
+
+void DSP_mute_voices( int mask )
+{
+	int i;
+	for ( i = 0; i < voice_count; i++ )
+		voice_state [i].enabled = (mask >> i & 1) ? 31 : 7;
+}
+
+void DSP_reset()
+{
+	int i;
+	keys = 0;
+	echo_ptr = 0;
+	noise_count = 0;
+	noise = 1;
+	fir_offset = 0;
+	
+	dspregs.g.flags = 0xE0; // reset, mute, echo off
+	dspregs.g.key_ons = 0;
+	
+	for ( i = 0; i < voice_count; i++ )
+	{
+	    struct voice_t * const v = voice_state + i;
+		v->on_cnt = 0;
+		v->volume [0] = 0;
+		v->volume [1] = 0;
+		v->envstate = state_release;
+	}
+	
+	memset( fir_buf, 0, sizeof fir_buf );
+}
+
+void DSP_write( int i, int data )
+{
+	//require( (unsigned) i < register_count );
+	
+	dspregs.reg [i] = data;
+	int high = i >> 4;
+	switch ( i & 0x0F )
+	{
+		// voice volume
+		case 0:
+		case 1: {
+			short* volume = voice_state [high].volume;
+			int left  = (int8_t) dspregs.reg [i & ~1];
+			int right = (int8_t) dspregs.reg [i |  1];
+			volume [0] = left;
+			volume [1] = right;
+			// kill surround only if enabled and signs of volumes differ
+			if ( left * right < surround_threshold )
+			{
+				if ( left < 0 )
+					volume [0] = -left;
+				else
+					volume [1] = -right;
+			}
+			break;
+		}
+		
+		// fir coefficients
+		case 0x0F:
+			fir_coeff [high] = (int8_t) data; // sign-extend
+			break;
+	}
+}
+
+// This table is for envelope timing.  It represents the number of counts
+// that should be subtracted from the counter each sample period (32kHz).
+// The counter starts at 30720 (0x7800). Each count divides exactly into
+// 0x7800 without remainder.
+const int env_rate_init = 0x7800;
+static short const env_rates [0x20] =
+{
+	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
+};
+
+const int env_range = 0x800;
+
+inline int DSP_clock_envelope( int v )
+{                               /* Return value is current 
+								 * ENVX */
+	struct raw_voice_t * const raw_voice = dspregs.voice + v;
+	struct voice_t * const voice = voice_state + v;
+	
+	int envx = voice->envx;
+	if ( voice->envstate == state_release )
+	{
+		/*
+		 * Docs: "When in the state of "key off". the "click" sound is 
+		 * prevented by the addition of the fixed value 1/256" WTF???
+		 * Alright, I'm going to choose to interpret that this way:
+		 * When a note is keyed off, start the RELEASE state, which
+		 * subtracts 1/256th each sample period (32kHz).  Note there's 
+		 * no need for a count because it always happens every update. 
+		 */
+		envx -= env_range / 256;
+		if ( envx <= 0 )
+		{
+			envx = 0;
+			keys &= ~(1 << v);
+			return -1;
+		}
+		voice->envx = envx;
+		raw_voice->envx = envx >> 8;
+		return envx;
+	}
+	
+	int cnt = voice->envcnt;
+	int adsr1 = raw_voice->adsr [0];
+	if ( adsr1 & 0x80 )
+	{
+		switch ( voice->envstate )
+		{
+			case state_attack: {
+				// increase envelope by 1/64 each step
+				int t = adsr1 & 15;
+				if ( t == 15 )
+				{
+					envx += env_range / 2;
+				}
+				else
+				{
+					cnt -= env_rates [t * 2 + 1];
+					if ( cnt > 0 )
+						break;
+					envx += env_range / 64;
+					cnt = env_rate_init;
+				}
+				if ( envx >= env_range )
+				{
+					envx = env_range - 1;
+					voice->envstate = state_decay;
+				}
+				voice->envx = envx;
+				break;
+			}
+			
+			case state_decay: {
+				// Docs: "DR... [is multiplied] by the fixed value
+				// 1-1/256." Well, at least that makes some sense.
+				// Multiplying ENVX by 255/256 every time DECAY is
+				// updated. 
+				cnt -= env_rates [((adsr1 >> 3) & 0xE) + 0x10];
+				if ( cnt <= 0 )
+				{
+					cnt = env_rate_init;
+					envx -= ((envx - 1) >> 8) + 1;
+					voice->envx = envx;
+				}
+				int sustain_level = raw_voice->adsr [1] >> 5;
+				
+				if ( envx <= (sustain_level + 1) * 0x100 )
+					voice->envstate = state_sustain;
+				break;
+			}
+			
+			case state_sustain:
+				// Docs: "SR [is multiplied] by the fixed value 1-1/256."
+				// Multiplying ENVX by 255/256 every time SUSTAIN is
+				// updated. 
+				cnt -= env_rates [raw_voice->adsr [1] & 0x1F];
+				if ( cnt <= 0 )
+				{
+					cnt = env_rate_init;
+					envx -= ((envx - 1) >> 8) + 1;
+					voice->envx = envx;
+				}
+				break;
+			
+			case state_release:
+				// handled above
+				break;
+		}
+	}
+	else
+	{                           /* GAIN mode is set */
+		/*
+		 * Note: if the game switches between ADSR and GAIN modes
+		 * partway through, should the count be reset, or should it
+		 * continue from where it was? Does the DSP actually watch for 
+		 * that bit to change, or does it just go along with whatever
+		 * it sees when it performs the update? I'm going to assume
+		 * the latter and not update the count, unless I see a game
+		 * that obviously wants the other behavior.  The effect would
+		 * be pretty subtle, in any case. 
+		 */
+		int t = raw_voice->gain;
+		if (t < 0x80)
+		{
+			envx = voice->envx = t << 4;
+		}
+		else switch (t >> 5)
+		{
+		case 4:         /* Docs: "Decrease (linear): Subtraction
+							 * of the fixed value 1/64." */
+			cnt -= env_rates [t & 0x1F];
+			if (cnt > 0)
+				break;
+			cnt = env_rate_init;
+			envx -= env_range / 64;
+			if ( envx < 0 )
+			{
+				envx = 0;
+				if ( voice->envstate == state_attack )
+					voice->envstate = state_decay;
+			}
+			voice->envx = envx;
+			break;
+		case 5:         /* Docs: "Drecrease <sic> (exponential):
+							 * Multiplication by the fixed value
+							 * 1-1/256." */
+			cnt -= env_rates [t & 0x1F];
+			if (cnt > 0)
+				break;
+			cnt = env_rate_init;
+			envx -= ((envx - 1) >> 8) + 1;
+			if ( envx < 0 )
+			{
+				envx = 0;
+				if ( voice->envstate == state_attack )
+					voice->envstate = state_decay;
+			}
+			voice->envx = envx;
+			break;
+		case 6:         /* Docs: "Increase (linear): Addition of
+							 * the fixed value 1/64." */
+			cnt -= env_rates [t & 0x1F];
+			if (cnt > 0)
+				break;
+			cnt = env_rate_init;
+			envx += env_range / 64;
+			if ( envx >= env_range )
+				envx = env_range - 1;
+			voice->envx = envx;
+			break;
+		case 7:         /* Docs: "Increase (bent line): Addition
+							 * of the constant 1/64 up to .75 of the
+							 * constaint <sic> 1/256 from .75 to 1." */
+			cnt -= env_rates [t & 0x1F];
+			if (cnt > 0)
+				break;
+			cnt = env_rate_init;
+			if ( envx < env_range * 3 / 4 )
+				envx += env_range / 64;
+			else
+				envx += env_range / 256;
+			if ( envx >= env_range )
+				envx = env_range - 1;
+			voice->envx = envx;
+			break;
+		}
+	}
+	voice->envcnt = cnt;
+	raw_voice->envx = envx >> 4;
+	return envx;
+}
+
+// Clamp n into range -32768 <= n <= 32767
+inline int clamp_16( int n )
+{
+	if ( (int16_t) n != n )
+		n = (int16_t) (0x7FFF - (n >> 31));
+	return n;
+}
+
+// Base normal_gauss table is almost exactly (with an error of 0 or -1 for each entry):
+// int normal_gauss [512];
+// normal_gauss [i] = exp((i-511)*(i-511)*-9.975e-6)*pow(sin(0.00307096*i),1.7358)*1304.45
+
+// Interleved gauss table (to improve cache coherency).
+// gauss [i * 2 + j] = normal_gauss [(1 - j) * 256 + i]
+const int16_t 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,
+};
+
+void DSP_run( long count, short* out_buf )
+{
+	// to do: make clock_envelope() inline so that this becomes a leaf function?
+	
+	// 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 ( dspregs.g.flags & 0x80 )
+		DSP_reset();
+	
+	struct src_dir {
+		char start [2];
+		char loop [2];
+	};
+	
+	const struct src_dir* const sd = (struct src_dir*) &ram [dspregs.g.wave_page * 0x100];
+	
+	int left_volume  = dspregs.g.left_volume;
+	int right_volume = dspregs.g.right_volume;
+	if ( left_volume * right_volume < surround_threshold )
+		right_volume = -right_volume; // kill global surround
+	left_volume  *= emu_gain;
+	right_volume *= emu_gain;
+	
+	while ( --count >= 0 )
+	{
+		// 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. 
+		
+		// What is the expected behavior when pitch modulation is enabled on
+		// voice 0? Jurassic Park 2 does this. Assume 0 for now.
+		//blargg_long prev_outx = 0;
+		long prev_outx = 0;
+		
+		int echol = 0;
+		int echor = 0;
+		int left = 0;
+		int right = 0;
+		int vidx;
+				
+		dspregs.g.wave_ended &= ~dspregs.g.key_ons; // Keying on a voice resets that bit in ENDX.
+		
+		if ( dspregs.g.noise_enables )
+		{
+			noise_count -= env_rates [dspregs.g.flags & 0x1F];
+			if ( noise_count <= 0 )
+			{
+				noise_count = env_rate_init;
+				
+				noise_amp = (int16_t) (noise * 2);
+				
+				// TODO: switch to Galios style
+				int feedback = (noise << 13) ^ (noise << 14);
+				noise = (feedback & 0x4000) | (noise >> 1);
+			}
+		}
+		
+		for ( vidx = 0; vidx < voice_count; vidx++ )
+		{
+			const int vbit = 1 << vidx;
+			struct raw_voice_t * const raw_voice = dspregs.voice + vidx;
+			struct voice_t * const voice = voice_state + vidx;
+			
+			int envx;
+			int n;
+
+			if ( voice->on_cnt && !--voice->on_cnt )
+			{
+				// key on
+				keys |= vbit;
+				voice->addr = GET_LE16( sd [raw_voice->waveform].start );
+				voice->block_remain = 1;
+				voice->envx = 0;
+				voice->block_header = 0;
+				voice->fraction = 0x3FFF; // decode three samples immediately
+				voice->interp0 = 0; // BRR decoder filter uses previous two samples
+				voice->interp1 = 0;
+				
+				// NOTE: Real SNES does *not* appear to initialize the
+				// envelope counter to anything in particular. The first
+				// cycle always seems to come at a random time sooner than 
+				// expected; as yet, I have been unable to find any
+				// pattern.  I doubt it will matter though, so we'll go
+				// ahead and do the full time for now. 
+				voice->envcnt = env_rate_init;
+				voice->envstate = state_attack;
+			}
+			
+			if ( dspregs.g.key_ons & vbit & ~dspregs.g.key_offs )
+			{
+				// voice doesn't come on if key off is set
+				dspregs.g.key_ons &= ~vbit;
+				voice->on_cnt = 8;
+			}
+			
+			if ( keys & dspregs.g.key_offs & vbit )
+			{
+				// key off
+				voice->envstate = state_release;
+				voice->on_cnt = 0;
+			}
+			
+			if ( !(keys & vbit) || (envx = DSP_clock_envelope( vidx )) < 0 )
+			{
+				raw_voice->envx = 0;
+				raw_voice->outx = 0;
+				prev_outx = 0;
+				continue;
+			}
+			
+			// Decode samples when fraction >= 1.0 (0x1000)
+			for ( n = voice->fraction >> 12; --n >= 0; )
+			{
+				if ( !--voice->block_remain )
+				{
+					if ( voice->block_header & 1 )
+					{
+						dspregs.g.wave_ended |= vbit;
+					
+						if ( voice->block_header & 2 )
+						{
+							// verified (played endless looping sample and ENDX was set)
+							voice->addr = GET_LE16( sd [raw_voice->waveform].loop );
+						}
+						else
+						{
+							// first block was end block; don't play anything (verified)
+							goto sample_ended; // to do: find alternative to goto
+						}
+					}
+					
+					voice->block_header = ram [(voice->addr)++];
+					voice->block_remain = 16; // nybbles
+				}
+				
+				// if next block has end flag set, *this* block ends *early* (verified)
+				if ( voice->block_remain == 9 && (ram [voice->addr + 5] & 3) == 1 &&
+						(voice->block_header & 3) != 3 )
+				{
+			sample_ended:
+					dspregs.g.wave_ended |= vbit;
+					keys &= ~vbit;
+					raw_voice->envx = 0;
+					voice->envx = 0;
+					// add silence samples to interpolation buffer
+					do
+					{
+						voice->interp3 = voice->interp2;
+						voice->interp2 = voice->interp1;
+						voice->interp1 = voice->interp0;
+						voice->interp0 = 0;
+					}
+					while ( --n >= 0 );
+					break;
+				}
+				
+				int delta = ram [voice->addr];
+				if ( voice->block_remain & 1 )
+				{
+					delta <<= 4; // use lower nybble
+					(voice->addr)++;
+				}
+				
+				// Use sign-extended upper nybble
+				delta = ((int8_t) delta) >> 4;
+				
+				// For invalid ranges (D,E,F): if the nybble is negative,
+				// the result is F000.  If positive, 0000. Nothing else
+				// like previous range, etc seems to have any effect.  If
+				// range is valid, do the shift normally.  Note these are
+				// both shifted right once to do the filters properly, but 
+				// the output will be shifted back again at the end.
+				int shift = (voice->block_header) >> 4;
+				delta = (delta << shift) >> 1;
+				if ( shift > 0x0C )
+					delta = (delta >> 14) & ~0x7FF;
+				
+				// One, two and three point IIR filters
+				int smp1 = voice->interp0;
+				int smp2 = voice->interp1;
+				if ( voice->block_header & 8 )
+				{
+					delta += smp1;
+					delta -= smp2 >> 1;
+					if ( !(voice->block_header & 4) )
+					{
+						delta += (-smp1 - (smp1 >> 1)) >> 5;
+						delta += smp2 >> 5;
+					}
+					else
+					{
+						delta += (-smp1 * 13) >> 7;
+						delta += (smp2 + (smp2 >> 1)) >> 4;
+					}
+				}
+				else if ( voice->block_header & 4 )
+				{
+					delta += smp1 >> 1;
+					delta += (-smp1) >> 5;
+				}
+				
+				voice->interp3 = voice->interp2;
+				voice->interp2 = smp2;
+				voice->interp1 = smp1;
+				voice->interp0 = (int16_t) (clamp_16( delta ) * 2); // sign-extend
+			}
+			
+			// rate (with possible modulation)
+			int rate = GET_LE16( raw_voice->rate ) & 0x3FFF;
+			if ( dspregs.g.pitch_mods & vbit )
+				rate = (rate * (prev_outx + 32768)) >> 15;
+			
+			// Gaussian interpolation using most recent 4 samples
+			int index = voice->fraction >> 2 & 0x3FC;
+			voice->fraction = (voice->fraction & 0x0FFF) + rate;
+			const int16_t* table  = (int16_t const*) ((char const*) gauss + index);
+			const int16_t* table2 = (int16_t const*) ((char const*) gauss + (255*4 - index));
+			int s = ((table  [0] * voice->interp3) >> 12) +
+					((table  [1] * voice->interp2) >> 12) +
+					((table2 [1] * voice->interp1) >> 12);
+			s = (int16_t) (s * 2);
+			s += (table2 [0] * voice->interp0) >> 11 & ~1;
+			int output = clamp_16( s );
+			if ( dspregs.g.noise_enables & vbit )
+				output = noise_amp;
+			
+			// scale output and set outx values
+			output = (output * envx) >> 11 & ~1;
+			
+			// output and apply muting (by setting voice.enabled to 31)
+			// if voice is externally disabled (not a SNES feature)
+			int l = (voice->volume [0] * output) >> voice->enabled;
+			int r = (voice->volume [1] * output) >> voice->enabled;
+			prev_outx = output;
+			raw_voice->outx = (int8_t) (output >> 8);
+			if ( dspregs.g.echo_ons & vbit )
+			{
+				echol += l;
+				echor += r;
+			}
+			left  += l;
+			right += r;
+		}
+		// end of channel loop
+		
+		// main volume control
+		left  = (left  * left_volume ) >> (7 + emu_gain_bits);
+		right = (right * right_volume) >> (7 + emu_gain_bits);
+		
+		// Echo FIR filter
+		
+		// read feedback from echo buffer
+		int temp_echo_ptr = echo_ptr;
+		uint8_t* echo_buf = &ram [(dspregs.g.echo_page * 0x100 + temp_echo_ptr) & 0xFFFF];
+		temp_echo_ptr += 4;
+		if ( temp_echo_ptr >= (dspregs.g.echo_delay & 15) * 0x800 )
+			temp_echo_ptr = 0;
+		int fb_left  = (int16_t) GET_LE16( echo_buf     ); // sign-extend
+		int fb_right = (int16_t) GET_LE16( echo_buf + 2 ); // sign-extend
+		echo_ptr = temp_echo_ptr;
+		
+		// put samples in history ring buffer
+		//const int fir_offset = this->fir_offset;
+		short (*fir_pos) [2] = &fir_buf [fir_offset];
+		//this->fir_offset = (fir_offset + 7) & 7; // move backwards one step
+		fir_offset = (fir_offset + 7) & 7; // move backwards one step
+		fir_pos [0] [0] = (short) fb_left;
+		fir_pos [0] [1] = (short) fb_right;
+		fir_pos [8] [0] = (short) fb_left; // duplicate at +8 eliminates wrap checking below
+		fir_pos [8] [1] = (short) fb_right;
+		
+		// FIR
+		fb_left =       fb_left * fir_coeff [7] +
+				fir_pos [1] [0] * fir_coeff [6] +
+				fir_pos [2] [0] * fir_coeff [5] +
+				fir_pos [3] [0] * fir_coeff [4] +
+				fir_pos [4] [0] * fir_coeff [3] +
+				fir_pos [5] [0] * fir_coeff [2] +
+				fir_pos [6] [0] * fir_coeff [1] +
+				fir_pos [7] [0] * fir_coeff [0];
+		
+		fb_right =     fb_right * fir_coeff [7] +
+				fir_pos [1] [1] * fir_coeff [6] +
+				fir_pos [2] [1] * fir_coeff [5] +
+				fir_pos [3] [1] * fir_coeff [4] +
+				fir_pos [4] [1] * fir_coeff [3] +
+				fir_pos [5] [1] * fir_coeff [2] +
+				fir_pos [6] [1] * fir_coeff [1] +
+				fir_pos [7] [1] * fir_coeff [0];
+		
+		left  += (fb_left  * dspregs.g.left_echo_volume ) >> 14;
+		right += (fb_right * dspregs.g.right_echo_volume) >> 14;
+		
+		// echo buffer feedback
+		if ( !(dspregs.g.flags & 0x20) )
+		{
+			echol += (fb_left  * dspregs.g.echo_feedback) >> 14;
+			echor += (fb_right * dspregs.g.echo_feedback) >> 14;
+			SET_LE16( echo_buf    , clamp_16( echol ) );
+			SET_LE16( echo_buf + 2, clamp_16( echor ) );
+		}
+		
+		if ( out_buf )
+		{
+			// write final samples
+			
+			left  = clamp_16( left  );
+			right = clamp_16( right );
+			
+			int mute = dspregs.g.flags & 0x40;
+			
+			out_buf [0] = (short) left;
+			out_buf [1] = (short) right;
+			out_buf += 2;
+			
+			// muting
+			if ( mute )
+			{
+				out_buf [-2] = 0;
+				out_buf [-1] = 0;
+			}
+		}
+	}
+}
+
+/* Track info */
+/* from sexyspc */
+
+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;
+
+int LoadID666(char *buf) {
+	unsigned char *ib=buf;
+	int isbinary = 1;
+  
+	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;
+  //printf("%02x\n",ib[0xD2 - 0x2E - 112]);
+  
+  //printf("%d\n", isbinary);
+  
+	ID666.isBinary = isbinary;
+  
+	if(isbinary) {
+		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*=64000;
+		ib++;
+    
+		ID666.fade=*ib;
+		ib++;
+		ID666.fade|=*ib<<8; 
+		ib++;
+		ID666.fade|=*ib<<16;
+		ib++;
+		ID666.fade|=*ib<<24;
+		ID666.fade*=64;
+		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;
+		int tmp;
+		int i;
+		char buf[64];
+    
+		year=month=day=0;
+		//ib[10]	= 0;
+		//sscanf(ib, "%02d/%02d/%04d", &month, &day, &year);
+		//ID666.year = year;
+		//ID666.day = day;
+		//ID666.month = month;
+		ib+=11;
+    
+		memcpy(buf, ib, 3);
+		buf[3] = 0;	
+		tmp = 0;
+		//sscanf(buf, "%d", &tmp);
+		for (i=0;i<3 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0';
+		ID666.length = tmp * 64000;
+		ib+=3;
+
+		memcpy(buf, ib, 5);
+		buf[5] = 0;
+		tmp = 0;
+		//sscanf(buf, "%d", &tmp);
+		for (i=0;i<5 && buf[i]>='0' && buf[i]<='9';i++) tmp=tmp*10+buf[i]-'0';
+		ID666.fade = tmp * 64;
+		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;
+		//sscanf(buf, "%d", &tmp);
+		//ID666.emulator=tmp;
+		ib++;
+	}
+	return 1;
+}
+
+
+/* resampling */
+
+#if 0
+#define FIR_WIDTH 24
+
+// Use Fir_Resampler<width> (below)
+	
+// Set input/output resampling ratio and optionally low-pass rolloff and gain.
+// Returns actual ratio used (rounded to internal precision).
+//double time_ratio( double factor, double rolloff = 0.999, double gain = 1.0 );
+double FIR_time_ratio( double factor, double rolloff, double gain );
+
+// Input
+
+enum { stereo = 2 };
+enum { max_res = 32 };
+int FIR_res;
+int FIR_imp_phase;
+unsigned long FIR_skip_bits;
+int FIR_step;
+int FIR_input_per_cycle;
+double FIR_ratio_;
+sample_t* FIR_impulses;
+int FIR_left;
+
+// Width is number of points in FIR. Must be even and 4 or more. More points give
+// better quality and rolloff effectiveness, and take longer to calculate.
+short FIR_impulsetab [max_res] [FIR_WIDTH];
+	
+// Read at most 'count' samples. Returns number of samples actually read.
+
+int FIR_read( sample_t* out_begin, long count ) {
+	sample_t* out = out_begin;
+	const sample_t* in = audiobuf;
+	sample_t* end_pos = audiobuf + RENDER_SIZE;
+	unsigned long skip = FIR_skip_bits >> FIR_imp_phase;
+	sample_t const* imp = FIR_impulsetab [FIR_imp_phase];
+	int remain = FIR_res - FIR_imp_phase;
+	int const step = FIR_step;
+	
+	count >>= 1;
+
+	if ( end_pos - in >= FIR_WIDTH * stereo )
+	{
+		end_pos -= FIR_WIDTH * stereo;
+		do
+		{
+			count--;
+			// accumulate in extended precision
+			long l = 0;
+			long r = 0;
+			int n;
+	
+			const sample_t* i = in;
+			if ( count < 0 )
+				break;
+	
+			for ( n = FIR_WIDTH / 2; n; --n )
+			{
+				int pt0 = imp [0];
+				l += pt0 * i [0];
+				r += pt0 * i [1];
+				int pt1 = imp [1];
+				imp += 2;
+				l += pt1 * i [2];
+				r += pt1 * i [3];
+				i += 4;
+			}
+	
+			remain--;
+	
+			l >>= 15;
+			r >>= 15;
+			in += (skip * stereo) & stereo;
+			skip >>= 1;
+			in += step;
+	
+			if ( !remain )
+			{
+				imp = FIR_impulsetab [0];
+				skip = FIR_skip_bits;
+				remain = FIR_res;
+			}
+	
+			out [0] = (sample_t) l;
+			out [1] = (sample_t) r;
+			out += 2;
+		}
+		while ( in <= end_pos );
+	}
+		
+	FIR_imp_phase = FIR_res - remain;
+	
+	FIR_left = (audiobuf + RENDER_SIZE) - in;
+	//printf("left=%d\n",FIR_left);
+	memmove( audiobuf, in, FIR_left * sizeof *in );
+
+	return out - out_begin;
+}
+		
+
+#undef PI
+#define PI 3.1415926535897932384626433832795029
+
+static void gen_sinc( double rolloff, int width, double offset, double spacing, double scale,
+							 int count, short* out )
+{
+	double const maxh = 256;
+	double const step = PI / maxh * spacing;
+	double const to_w = maxh * 2 / width;
+	double const pow_a_n = pow( rolloff, maxh );
+	scale /= maxh * 2;
+	
+	double angle = (count / 2 - 1 + offset) * -step;
+	while ( count-- )
+	{
+		*out++ = 0;
+		double w = angle * to_w;
+		if ( fabs( w ) < PI )
+		{
+			double rolloff_cos_a = rolloff * cos( angle );
+			double num = 1 - rolloff_cos_a -
+						pow_a_n * cos( maxh * angle ) +
+						pow_a_n * rolloff * cos( (maxh - 1) * angle );
+			double den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff;
+			double sinc = scale * num / den - scale;
+			
+			out [-1] = (short) (cos( w ) * sinc + sinc);
+		}
+		angle += step;
+	}
+}
+
+void FIR_Init( )
+{
+	FIR_res       = 1;
+	FIR_imp_phase = 0;
+	FIR_skip_bits = 0;
+	FIR_step      = stereo;
+	FIR_ratio_    = 1.0;
+	FIR_impulses=FIR_impulsetab[0];
+
+	FIR_left=0;
+}
+
+double FIR_time_ratio( double new_factor, double rolloff, double gain )
+{
+	FIR_ratio_ = new_factor;
+	
+	double fstep = 0.0;
+	double pos = 0;
+	int i;
+	double filter = (FIR_ratio_ < 1.0) ? 1.0 : 1.0 / FIR_ratio_;
+	{
+		double least_error = 2;
+		double pos = 0;
+		int r;
+	
+		FIR_res = -1;
+		for ( r = 1; r <= max_res; r++ )
+		{
+			pos += FIR_ratio_;
+			double nearest = floor( pos + 0.5 );
+			double error = fabs( pos - nearest );
+			if ( error < least_error )
+			{
+				FIR_res = r;
+				fstep = nearest / FIR_res;
+				least_error = error;
+			}
+		}
+	}
+	
+	FIR_skip_bits = 0;
+	
+	FIR_step = stereo * (int) floor( fstep );
+	
+	FIR_ratio_ = fstep;
+	fstep = fmod( fstep, 1.0 );
+	
+	pos = 0.0;
+	
+	FIR_input_per_cycle = 0;
+	for ( i = 0; i < FIR_res; i++ )
+	{
+		gen_sinc( rolloff, ((int) (FIR_WIDTH * filter + 1)) & ~1, pos, filter,
+					 (double) (0x7FFF * gain * filter),
+					 (int) FIR_WIDTH, FIR_impulses + i * FIR_WIDTH );
+		
+		pos += fstep;
+		FIR_input_per_cycle += FIR_step;
+		if ( pos >= 0.9999999 )
+		{
+			pos -= 1.0;
+			FIR_skip_bits |= 1 << i;
+			FIR_input_per_cycle++;
+		}
+	}
+	
+	printf("FIR_res=%d;\n",FIR_res);
+	printf("FIR_imp_phase=%d\n",FIR_imp_phase);
+	printf("FIR_skip_bits=%08x\n",FIR_skip_bits);
+	printf("FIR_step=%d\n",FIR_step);
+	printf("FIR_ratio_=%lf\n",FIR_ratio_);
+	
+	//clear();
+	
+	return FIR_ratio_;
+}
+
+#if 0
+int FIR_input_needed( blargg_long output_count )
+{
+	blargg_long input_count = 0;
+	
+	unsigned long skip = FIR_skip_bits >> FIR_imp_phase;
+	int remain = FIR_res - FIR_imp_phase;
+	while ( (output_count -= 2) > 0 )
+	{
+		FIR_input_count += step + (skip & 1) * stereo;
+		skip >>= 1;
+		if ( !--remain )
+		{
+			skip = skip_bits;
+			remain = res;
+		}
+		output_count -= 2;
+	}
+	
+	long input_extra = input_count - (write_pos - &buf [(width_ - 1) * stereo]);
+	if ( input_extra < 0 )
+		input_extra = 0;
+	return input_extra;
+}
+
+int Fir_Resampler_::avail_( blargg_long input_count ) const
+{
+	int cycle_count = input_count / input_per_cycle;
+	int output_count = cycle_count * res * stereo;
+	input_count -= cycle_count * input_per_cycle;
+	
+	blargg_ulong skip = skip_bits >> imp_phase;
+	int remain = res - imp_phase;
+	while ( input_count >= 0 )
+	{
+		input_count -= step + (skip & 1) * stereo;
+		skip >>= 1;
+		if ( !--remain )
+		{
+			skip = skip_bits;
+			remain = res;
+		}
+		output_count += 2;
+	}
+	return output_count;
+}
+
+int Fir_Resampler_::skip_input( long count )
+{
+	int remain = write_pos - buf.begin();
+	int max_count = remain - width_ * stereo;
+	if ( count > max_count )
+		count = max_count;
+	
+	remain -= count;
+	write_pos = &buf [remain];
+	memmove( buf.begin(), &buf [count], remain * sizeof buf [0] );
+	
+	return count;
+}
+#endif
+#endif
+
+/* main */
+#if 0
+int main(int argc, char ** argv) {
+    FILE * infile, * outfile;
+	 int len;
+    char buf[spc_file_size+128];
+    if (argc!=2) {
+        fprintf(stderr,"spc test\nusage: ./spc state.spc\n\n");
+        return 1;
+    }
+    
+    infile = fopen(argv[1],"rb");
+    if (!infile) {
+        fprintf(stderr,"failed to open %s\n");
+        return 1;
+    }
+    
+    SPC_Init();
+    if (SPC_load_spc(buf,fread(buf,1,sizeof buf,infile))) {
+        fprintf(stderr,"error loading SPC\n");
+        return 1;
+    }
+	 
+	 load_info(infile);
+    
+	 fclose(infile); infile=NULL;
+	 
+	 outfile=fopen("out.raw","wb");
+	 
+	 DSP_set_gain( 1.4 );
+	 FIR_Init();
+	 FIR_time_ratio( 32000.0 / 44100.0, 0.9965, 1.0 );
+    
+    /* play */
+	 for (len=len_usecs/500;len--;len>0) {
+	 	 SPC_play(RENDER_SIZE-FIR_left,audiobuf+FIR_left);
+		 fwrite(audioresampbuf,2,FIR_read(audioresampbuf,RESAMP_SIZE),outfile);
+		 //fwrite(audiobuf,2,RENDER_SIZE,outfile);
+	 }
+	 
+	 fclose(outfile);
+}
+#endif
+
+/* this is the codec entry point */
+enum codec_status codec_main(void)
+{
+	int sampleswritten, i;
+	int endofstream; /* end of stream flag */
+	unsigned long fadestartsample = ID666.length/64*32000LL*2/1000;
+	unsigned long fadeendsample = (ID666.length/64+ID666.fade/64)*32000LL*2/1000;
+	
+	/* Generic codec initialisation */
+	/* we only render 16 bits */
+	/*ci->configure(CODEC_SET_FILEBUF_CHUNKSIZE, (int *)(1024*256));*/
+  
+next_track:
+	 DEBUGF("SPC: next_track\n");
+    if (codec_init()) {
+		 return CODEC_ERROR;
+	 }
+	 DEBUGF("SPC: after init\n");
+	 
+	 ci->configure(DSP_SET_SAMPLE_DEPTH, (long *)16);
+	 ci->configure(DSP_SWITCH_FREQUENCY, (long *)32000);
+	 ci->configure(DSP_SET_STEREO_MODE, (long *)STEREO_INTERLEAVED);
+	    
+	 /* wait for track info to load */
+	 while (!*ci->taginfo_ready && !ci->stop_codec)
+		 ci->sleep(1);
+        
+	 /* Read the entire file (or as much as possible) */
+	 DEBUGF("SPC: request initial buffer\n");
+	 ci->configure(CODEC_SET_FILEBUF_WATERMARK, (int *)(ci->filesize));
+	 
+	 ci->seek_buffer(0);
+	 buffer = ci->request_buffer(&n, ci->filesize);
+	 if (!buffer) {
+		 return CODEC_ERROR;
+	 }
+	 
+	 buffersize = n;
+	 DEBUGF("SPC: read size = %x\n",buffersize);
+	 
+	 if (SPC_load_spc(buffer,buffersize)) {
+		 DEBUGF("SPC load failure\n");
+		 return CODEC_ERROR;
+	 }
+	 
+	 sampleswritten=0;
+	 
+	 SPC_Init();
+	 LoadID666(buffer+0x2e);
+	 
+	 fadestartsample = ID666.length/64*32000LL*2/1000;
+	 fadeendsample = (ID666.length/64+ID666.fade/64)*32000LL*2/1000;
+	 ci->id3->length = ID666.length/64+ID666.fade/64;
+	 ci->id3->title = ID666.song;
+	 ci->id3->album = ID666.game;
+	 ci->id3->artist = ID666.artist;
+	 
+	 DSP_set_gain( 1.4 );
+	 //FIR_Init();
+	 //FIR_time_ratio( 32000.0 / 44100.0, 0.9965, 1.0 );
+
+	 endofstream = 0;
+
+	 /* The main decoder loop */
+        
+	 while (!endofstream) {
+		 ci->yield();
+		 if (ci->stop_codec || ci->new_track) {
+			 break;
+		 }
+		 
+		SPC_play(WAV_CHUNK_SIZE,samples);
+		
+		sampleswritten+=WAV_CHUNK_SIZE;
+		
+		/* is track timed? */
+		if (ci->global_settings->repeat_mode!=REPEAT_ONE && ci->id3->length) {
+			unsigned long curtime = sampleswritten*1000LL/32000/2;
+			unsigned long lasttimesample = (sampleswritten-WAV_CHUNK_SIZE);
+
+			/* fade? */
+			if (curtime>ID666.length/64) {
+				for (i=0;i<WAV_CHUNK_SIZE;i+=2) {
+					if (lasttimesample+i>fadestartsample) {
+						if (lasttimesample+i<fadeendsample) {
+							samples[i] = (long long)samples[i]*(fadeendsample-lasttimesample-i)/(fadeendsample-fadestartsample);
+							samples[i+1] = (long long)samples[i+1]*(fadeendsample-lasttimesample-i)/(fadeendsample-fadestartsample);
+						} else samples[i]=samples[i+1]=0;
+					}
+				}
+			}
+			/* end? */
+			if (curtime>ci->id3->length)
+				  endofstream=1;
+		}
+		
+		while (!ci->pcmbuf_insert((char *)samples, WAV_CHUNK_SIZE*2))
+			ci->yield();
+		
+		ci->set_elapsed(sampleswritten*1000LL/32000/2);
+	 }
+
+	 if (ci->request_next_track())
+		 goto next_track;
+
+	 return CODEC_OK;
+}
Index: apps/codecs/Makefile
===================================================================
--- apps/codecs/Makefile	(revision 12014)
+++ apps/codecs/Makefile	(working copy)
@@ -51,6 +51,7 @@
 $(OBJDIR)/wav.elf : $(OBJDIR)/wav.o
 $(OBJDIR)/sid.elf : $(OBJDIR)/sid.o
 $(OBJDIR)/adx.elf : $(OBJDIR)/adx.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
Index: apps/codecs/SOURCES
===================================================================
--- apps/codecs/SOURCES	(revision 12014)
+++ apps/codecs/SOURCES	(working copy)
@@ -15,6 +15,7 @@
 aiff.c
 sid.c
 adx.c
+spc.c
 #if defined(HAVE_RECORDING) && !defined(SIMULATOR)
 /* encoders */
 aiff_enc.c
Index: firmware/export/id3.h
===================================================================
--- firmware/export/id3.h	(revision 12014)
+++ firmware/export/id3.h	(working copy)
@@ -47,6 +47,7 @@
     AFMT_SHN,          /* Shorten */
     AFMT_SID,          /* SID File Format */
     AFMT_ADX,          /* ADX File Format */
+	 AFMT_SPC,			  /* SPC700 save state */
 #endif
 
     /* add new formats at any index above this line to have a sensible order -
Index: firmware/id3.c
===================================================================
--- firmware/id3.c	(revision 12014)
+++ firmware/id3.c	(working copy)
@@ -18,7 +18,7 @@
  ****************************************************************************/
 /*
  * Parts of this code has been stolen from the Ample project and was written
- * by David Härdeman. It has since been extended and enhanced pretty much by
+ * by David Hï¿½deman. It has since been extended and enhanced pretty much by
  * all sorts of friendly Rockbox people.
  *
  */
@@ -98,6 +98,9 @@
     /* ADX File Format */
     [AFMT_ADX] =
         AFMT_ENTRY("ADX",  "adx",     NULL,          "adx\0"      ),
+	 /* SPC700 Save State */
+	 [AFMT_SPC] =
+	 	  AFMT_ENTRY("SPC",  "spc",     NULL,          "spc\0"      ),
 #endif
 };
 
