Index: apps/tree.c
===================================================================
--- apps/tree.c	(revision 12293)
+++ apps/tree.c	(working copy)
@@ -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 },
Index: apps/metadata.c
===================================================================
--- apps/metadata.c	(revision 12293)
+++ apps/metadata.c	(working copy)
@@ -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)))
         {
Index: apps/codecs/spc/Spc_Dsp.h
===================================================================
--- apps/codecs/spc/Spc_Dsp.h	(revision 0)
+++ apps/codecs/spc/Spc_Dsp.h	(revision 0)
@@ -0,0 +1,969 @@
+enum { voice_count = 8 };
+enum { register_count = 128 };
+
+typedef 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];
+} raw_voice_t;
+	
+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
+};
+
+typedef struct cache_entry_t
+{
+	int16_t const* samples;
+	unsigned end; // past-the-end position
+	unsigned loop; // number of samples in loop
+	unsigned start_addr;
+} cache_entry_t;
+
+enum { brr_block_size = 16 };
+
+typedef 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;
+} voice_t;
+
+#if SPC_BRRCACHE
+static int16_t BRRcache [0x20000 + 32]; // a little extra for samples that go past end
+#endif
+
+enum { fir_buf_half = 8 };
+
+typedef struct Spc_Dsp
+{
+	union
+	{
+		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];
+	int fir_coeff [voice_count]; // copy of echo FIR constants as int, for faster access
+	
+	voice_t voice_state [voice_count];
+	
+#if SPC_BRRCACHE
+	uint8_t oldsize;
+	cache_entry_t wave_entry     [256];
+	cache_entry_t wave_entry_old [256];
+#endif
+} Spc_Dsp;
+
+typedef struct src_dir
+{
+	char start [2];
+	char loop  [2];
+} src_dir;
+
+static void DSP_reset( 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; )
+	{
+		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( Spc_Dsp* this, int i, int data ) ICODE_ATTR;
+static void DSP_write( 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];
+		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( 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( Spc_Dsp* this, unsigned start_addr, voice_t* voice, raw_voice_t const* const raw_voice ) ICODE_ATTR;
+static void decode_brr( Spc_Dsp* this, unsigned start_addr, voice_t* voice, raw_voice_t const* const raw_voice )
+{
+	// setup same variables as where decode_brr() is called from
+	#undef RAM
+	#define RAM ram.ram
+	src_dir const* const sd = (src_dir*) &RAM [this->r.g.wave_page * 0x100];
+	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++ )
+		{
+			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(Spc_Dsp* const this, voice_t* const voice, src_dir const* const sd, 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;
+    		voice->samples [brr_block_size + 1] = 0; // BRR filter uses previous samples
+    		voice->samples [brr_block_size + 2] = 0;
+    		voice->position     = (brr_block_size + 3) * 0x1000 - 1; // decode three samples immediately
+    		voice->block_header = 0; // "previous" BRR header
+    	}
+    	#else
+    	{
+    		voice->position = 3 * 0x1000 - 1;
+    		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_( Spc_Dsp* this, long count, sample_t* out_buf ) ICODE_ATTR;
+static void DSP_run_( Spc_Dsp* this, long count, sample_t* out_buf )
+{
+	#undef RAM
+#ifdef CPU_ARM
+	uint8_t* const ram_ = ram.ram;
+	#define RAM ram_
+#else
+	#define RAM ram.ram
+#endif
+	//EXIT_TIMER(cpu);
+	//ENTER_TIMER(dsp);
+	
+	// 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;
+		this->r.g.wave_ended &= ~key_ons;	// keying on a voice resets that bit in ENDX
+		this->r.g.key_ons = key_ons & key_offs; // key_off bits prevent key_on from being acknowledged
+		
+		// process key events outside loop, since they won't re-occur
+		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 );
+	}
+	
+	src_dir const* const sd = (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
+	int const global_muting = ((this->r.g.flags & 0x40) >> 2) + 14; // (g.flags & 0x40) ? 30 : 14
+	
+	int const global_vol_0 = this->r.g.volume_0;
+	int const global_vol_1 = this->r.g.volume_1;
+	
+	// each rate divides exactly into 0x7800 without remainder
+	int const env_rate_init = 0x7800;
+	static unsigned 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
+	};
+	
+	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;
+		raw_voice_t * raw_voice = this->r.voice; // TODO: put raw_voice pointer in voice_t?
+		voice_t* voice = this->voice_state;
+		int vbit = 1;
+		for ( ; vbit < 0x100; vbit <<= 1, ++voice, ++raw_voice )
+		{
+			/* pregen involves checking keyon, etc */
+			//ENTER_TIMER(dsp_pregen);
+			
+			// 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;
+							raw_voice->envx = envx >> 4; // TODO: should this be 8?
+							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;
+								raw_voice->envx = envx >> 4; // TODO: should this be 8?
+								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;
+							raw_voice->envx = envx >> 4; // TODO: should this be 8?
+							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;
+							raw_voice->envx = envx >> 4; // TODO: should this be 8?
+							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
+					{
+						this->keys_down ^= vbit; // bit was set, so this clears it
+						voice->envx = 0;
+						goto silent_chan;
+					}
+				}
+			init_env_timer:
+				env_timer = env_rate_init;
+			write_env_timer:
+				voice->env_timer = env_timer;
+			env_end:;
+			}
+			
+			//EXIT_TIMER(dsp_pregen);
+			
+			//ENTER_TIMER(dsp_gen);
+
+			#if !SPC_BRRCACHE
+			if ( voice->position >= brr_block_size * 0x1000 ) // Decode BRR block
+			{
+				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)
+						this->keys_down ^= vbit; // bit was set, so this clears it
+						
+						// 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;
+					voice->block_header = 1; // force sample to end on next decode
+				}
+				
+				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
+			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;
+			//int output = pos [0]; // no interpolation (hardly faster, and crappy sounding)
+			if ( this->r.g.noise_enables & vbit )
+				output = *(int16_t*) &this->noise;
+			
+			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
+			
+			//EXIT_TIMER(dsp_gen);
+			
+			//ENTER_TIMER(dsp_mix);
+			
+			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
+			
+			//EXIT_TIMER(dsp_mix);
+		}
+		// 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;
+		fir_ptr [fir_buf_half] [0] = fb_0; // duplicate at +8 eliminates wrap checking below
+		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 );
+	
+	//EXIT_TIMER(dsp);
+	//ENTER_TIMER(cpu);
+}
+
+static inline void DSP_run( Spc_Dsp* this, long count, sample_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 );
+}
Index: apps/codecs/spc/Spc_Cpu.h
===================================================================
--- apps/codecs/spc/Spc_Cpu.h	(revision 0)
+++ apps/codecs/spc/Spc_Cpu.h	(revision 0)
@@ -0,0 +1,1006 @@
+#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, spc_time_t const spc_time_ ) ICODE_ATTR;
+	
+static unsigned CPU_mem_bit( THIS, uint8_t const* pc, spc_time_t 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 spc_time_t CPU_run( THIS, spc_time_t start_time ) ICODE_ATTR;
+
+static spc_time_t CPU_run( THIS, spc_time_t start_time )
+{
+	//ENTER_TIMER(cpu);
+	
+	register spc_time_t 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:
+	spc_time_ -= this->cycle_table [*--pc]; // undo partial execution of opcode
+	{
+		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;
+	
+	//EXIT_TIMER(cpu);
+	
+	return spc_time_;
+}
Index: apps/codecs/spc/spc_profiler.h
===================================================================
--- apps/codecs/spc/spc_profiler.h	(revision 0)
+++ apps/codecs/spc/spc_profiler.h	(revision 0)
@@ -0,0 +1,63 @@
+#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);
+//CREATE_TIMER(cpu);
+//CREATE_TIMER(dsp);
+//CREATE_TIMER(dsp_pregen);
+//CREATE_TIMER(dsp_gen);
+//CREATE_TIMER(dsp_mix);
+
+static void reset_profile_timers(void) {
+	RESET_TIMER(total);
+	RESET_TIMER(render);
+	//RESET_TIMER(cpu);
+	//RESET_TIMER(dsp);
+	//RESET_TIMER(dsp_pregen);
+	//RESET_TIMER(dsp_gen);
+	//RESET_TIMER(dsp_mix);
+}
+
+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");
+	//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");
+	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
Index: apps/codecs/spc.c
===================================================================
--- apps/codecs/spc.c	(revision 0)
+++ apps/codecs/spc.c	(revision 0)
@@ -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 */
+#define WAV_CHUNK_SIZE 2048
+
+/* simple profiling with USEC_TIMER */
+//#define SPC_PROFILE
+
+#include "spc/spc_profiler.h"
+
+#define THIS struct Spc_Emu* const this
+
+typedef int32_t sample_t;
+typedef long spc_time_t;
+
+/**************** 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 };
+
+typedef struct Timer
+{
+	spc_time_t next_tick;
+	int period;
+	int count;
+	int shift;
+	int enabled;
+	int counter;
+} Timer;
+
+static void Timer_run_( Timer* t, spc_time_t time ) ICODE_ATTR;
+static void Timer_run_( Timer* t, spc_time_t time )
+{
+	assert( t->enabled ); // when disabled, next_tick should always be in the future
+	
+	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( Timer* t, spc_time_t time )
+{
+	if ( time >= t->next_tick )
+		Timer_run_( t, time );
+}
+
+/**************** SPC emulator ****************/
+
+enum { clocks_per_sample = 32 }; // 1.024 MHz clock / 32000 samples per second
+
+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;
+	
+	sample_t* sample_buf;
+	spc_time_t next_dsp;
+	int rom_enabled;
+	int extra_cycles;
+	
+	Timer timer [timer_count];
+	
+	// large objects at end
+	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
+	this->extra_cycles = 32; // some SPCs rely on DSP immediately generating one sample
+	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++ )
+	{
+		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, spc_time_t time ) ICODE_ATTR;
+static void SPC_run_dsp_( THIS, spc_time_t time )
+{
+	int count = ((time - this->next_dsp) >> 5) + 1; // divide by clocks_per_sample
+	sample_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, spc_time_t time )
+{
+	if ( time >= this->next_dsp )
+		SPC_run_dsp_( this, time );
+}
+
+static int SPC_read( THIS, unsigned addr, spc_time_t const time ) ICODE_ATTR;
+static int SPC_read( THIS, unsigned addr, spc_time_t const time )
+{
+	int result = RAM [addr];
+	
+	if ( ((unsigned) (addr - 0xF0)) < 0x10 )
+	{
+		assert( 0xF0 <= addr && addr <= 0xFF );
+		
+		// counters
+		int i = addr - 0xFD;
+		if ( i >= 0 )
+		{
+			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, spc_time_t const time ) ICODE_ATTR;
+static void SPC_write( THIS, unsigned addr, int data, spc_time_t 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++ )
+			{
+				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;
+		
+		//case 0xF8: // verified on SNES that these are read/write (RAM)
+		//case 0xF9:
+		
+		// Timers
+		case 0xFA:
+		case 0xFB:
+		case 0xFC: {
+			int i = addr - 0xFA;
+			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, sample_t* out ) ICODE_ATTR;
+static int SPC_play( THIS, long count, sample_t* out )
+{
+	int i;
+	assert( count % 2 == 0 ); // output is always in pairs of samples
+	
+	spc_time_t 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++ )
+	{
+		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
+	//ENTER_TIMER(cpu);
+	SPC_run_dsp( this, -extra_clocks );
+	//EXIT_TIMER(cpu);
+	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;
+	
+  //printf("%02x\n",ib[0xD2 - 0x2E - 112]);
+  
+  //printf("%d\n", isbinary);
+  
+	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;
+		//ID666.fade;
+		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[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 * 1000;
+		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;
+		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;
+}
+
+/**************** 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)
+{
+	/* Generic codec initialisation */
+	/* we only render 16 bits */
+	/*ci->configure(CODEC_SET_FILEBUF_CHUNKSIZE, (int *)(1024*256));*/
+
+	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;
+}
Index: apps/codecs/Makefile
===================================================================
--- apps/codecs/Makefile	(revision 12293)
+++ apps/codecs/Makefile	(working copy)
@@ -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
Index: apps/codecs/SOURCES
===================================================================
--- apps/codecs/SOURCES	(revision 12293)
+++ apps/codecs/SOURCES	(working copy)
@@ -17,6 +17,7 @@
 sid.c
 adx.c
 nsf.c
+spc.c
 #if defined(HAVE_RECORDING) && !defined(SIMULATOR)
 /* encoders */
 aiff_enc.c
Index: firmware/export/id3.h
===================================================================
--- firmware/export/id3.h	(revision 12293)
+++ firmware/export/id3.h	(working copy)
@@ -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 -
Index: firmware/id3.c
===================================================================
--- firmware/id3.c	(revision 12293)
+++ firmware/id3.c	(working copy)
@@ -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
 };
 
