Index: apps/tree.c
===================================================================
--- apps/tree.c	(revision 12099)
+++ 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 12099)
+++ 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_Dsp.h
===================================================================
--- apps/codecs/Spc_Dsp.h	(revision 0)
+++ apps/codecs/Spc_Dsp.h	(revision 0)
@@ -0,0 +1,904 @@
+enum { voice_count = 8 };
+enum { register_count = 128 };
+
+typedef 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];
+} raw_voice_t;
+	
+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];
+};
+
+enum state_t
+{
+	state_attack,
+	state_decay,
+	state_sustain,
+	state_release
+};
+
+typedef struct voice_t
+{
+	short volume [2];
+	short fraction;// 12-bit fractional position
+#ifndef SPC_NOINTERP
+	short interp3; // most recent four decoded samples
+	short interp2;
+#else
+	short cursample;
+#endif
+	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 envstate;
+	short unused; // pad to power of 2
+} voice_t;
+
+struct cache_entry_t
+{
+	short* samples;
+	int startaddr; /* in memory */
+	int loopstartsample;		/* in samples */
+	int endsample;		/* in samples */
+};
+
+typedef struct Spc_Dsp
+{
+	union
+	{
+		raw_voice_t voice [voice_count];
+		uint8_t reg [register_count];
+		struct globals_t g;
+	} dspregs;
+	
+	voice_t voice_state [voice_count];
+	
+	// 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)
+
+	int keys;
+
+	int echo_ptr;
+	int noise_amp;
+	int noise;
+	int noise_count;
+	
+#ifdef SPC_NOINTERP
+	struct cache_entry_t wave_entry[256];
+#endif
+
+	uint8_t padding1 [0x100];
+	uint8_t ram [0x10000];
+	uint8_t padding2 [0x100];
+	
+#ifdef SPC_NOINTERP
+	// large objects at end
+	sample_t BRRcache[0x20000];
+#endif
+} Spc_Dsp;
+
+static void DSP_reset( Spc_Dsp* this )
+{
+	int i;
+	this->keys = 0;
+	this->echo_ptr = 0;
+	this->noise_count = 0;
+	this->noise = 1;
+	this->fir_offset = 0;
+	
+	this->dspregs.g.flags = 0xE0; // reset, mute, echo off
+	this->dspregs.g.key_ons = 0;
+	
+	for ( i = 0; i < voice_count; i++ )
+	{
+		voice_t* v = this->voice_state + i;
+		v->on_cnt = 0;
+		v->volume [0] = 0;
+		v->volume [1] = 0;
+		v->envstate = state_release;
+	}
+	
+#ifdef SPC_NOINTERP
+	for (i=0;i<256;i++)
+		this->wave_entry[i].startaddr=-1;
+#endif
+	
+	memset( this->fir_buf, 0, sizeof this->fir_buf );
+	
+	assert( offsetof (struct globals_t,unused9 [2]) == register_count );
+	assert( sizeof (this->dspregs.voice) == register_count );
+}
+
+static void DSP_write( Spc_Dsp* this, int i, int data )
+{
+	//require( (unsigned) i < register_count );
+	
+	this->dspregs.reg [i] = data;
+	int high = i >> 4;
+	switch ( i & 0x0F )
+	{
+		// voice volume
+		case 0:
+		case 1: {
+			short* volume = this->voice_state [high].volume;
+			int left  = (int8_t) this->dspregs.reg [i & ~1];
+			int right = (int8_t) this->dspregs.reg [i |  1];
+			volume [0] = left;
+			volume [1] = right;
+			break;
+		}
+		
+		// fir coefficients
+		case 0x0F:
+			this->fir_coeff [high] = (int8_t) data; // sign-extend
+			break;
+	}
+}
+
+static inline int DSP_read( Spc_Dsp* this, int i )
+{
+	assert( (unsigned) i < register_count );
+	return this->dspregs.reg [i];
+}
+	
+typedef struct src_dir
+{
+	char start [2];
+	char loop [2];
+} src_dir;
+
+// 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.
+enum { 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
+};
+
+enum { env_range = 0x800 };
+
+static inline int DSP_clock_envelope( Spc_Dsp* this, int v )
+{                               /* Return value is current 
+								 * ENVX */
+	
+	raw_voice_t * const raw_voice = this->dspregs.voice + v;
+	voice_t * const voice = this->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;
+			this->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
+static inline int clamp_16( int n )
+{
+	if ( (int16_t) n != n )
+		n = (int16_t) (0x7FFF - (n >> 31));
+	return n;
+}
+
+// Interleved gauss table (to improve cache coherency).
+// gauss [i * 2 + j] = normal_gauss [(1 - j) * 256 + i]
+#ifndef SPC_NOINTERP
+static 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,
+};
+
+#else
+
+static void renderBRR( Spc_Dsp* this, voice_t* voice, const raw_voice_t* raw_voice,
+		struct cache_entry_t* wc, src_dir* sd )
+{
+	int samples=0;
+	wc->startaddr=voice->addr;
+	wc->samples = this->BRRcache + voice->addr * 2;
+	
+	DEBUGF("decode at %08x (wave #%d)\n",voice->addr,raw_voice->waveform);
+
+	while (1) {
+		if (voice->addr==GET_LE16( sd [raw_voice->waveform].loop )) {
+			DEBUGF("loop at %08x (wave #%d)\n",voice->addr,raw_voice->waveform);
+			wc->loopstartsample=samples;
+		}
+		if ( !--voice->block_remain )
+		{
+			if ( voice->block_header & 1 )
+			{
+				wc->endsample=samples;
+				if ( voice->block_header & 2 )
+				{
+					// verified (played endless looping sample and ENDX was set)
+					//voice->addr = GET_LE16( sd [raw_voice->waveform].loop );
+					//voice->loopstart=GET_LE16( sd [raw_voice->waveform].loop );
+				} else wc->loopstartsample=-1;
+				break;
+			}
+
+			voice->block_header = this->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 && (this->ram [voice->addr + 5] & 3) == 1 &&
+									(voice->block_header & 3) != 3 )
+		{
+			wc->endsample=samples;
+			wc->loopstartsample=-1;
+			//dspregs.g.wave_ended |= vbit;
+			//keys &= ~vbit;
+			//raw_voice->envx = 0;
+			//voice->envx = 0;
+			break;
+		}
+		
+		int delta = this->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->interp1 = smp1;
+		wc->samples [samples] = voice->interp0 = (int16_t) (clamp_16( delta ) * 2); // sign-extend
+		samples++;
+	} /* end of sample loop */
+}
+
+#endif
+
+static void DSP_run( Spc_Dsp* this, long count, short* out_buf )
+{
+	EXIT_TIMER(cpu);
+	ENTER_TIMER(dsp);
+	
+	// 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 ( this->dspregs.g.flags & 0x80 )
+		DSP_reset(this);
+	
+	src_dir* const sd = (src_dir*) &(this->ram) [this->dspregs.g.wave_page * 0x100];
+	
+	int const left_volume  = this->dspregs.g.left_volume  << 8;
+	int const right_volume = this->dspregs.g.right_volume << 8;
+	
+	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;
+#ifndef SPC_NOECHO
+		int echol = 0;
+		int echor = 0;
+#endif
+		int left = 0;
+		int right = 0;
+		int vidx;
+				
+		this->dspregs.g.wave_ended &= ~this->dspregs.g.key_ons; // Keying on a voice resets that bit in ENDX.
+		
+		if ( this->dspregs.g.noise_enables )
+		{
+			this->noise_count -= env_rates [this->dspregs.g.flags & 0x1F];
+			if ( this->noise_count <= 0 )
+			{
+				this->noise_count = env_rate_init;
+				
+				this->noise_amp = (int16_t) (this->noise * 2);
+				
+				// TODO: switch to Galios style
+				int feedback = (this->noise << 13) ^ (this->noise << 14);
+				this->noise = (feedback & 0x4000) | (this->noise >> 1);
+			}
+		}
+		
+		for ( vidx = 0; vidx < voice_count; vidx++ )
+		{
+			const int vbit = 1 << vidx;
+			raw_voice_t * const raw_voice = this->dspregs.voice + vidx;
+			voice_t * const voice = this->voice_state + vidx;
+#ifdef SPC_NOINTERP
+			struct cache_entry_t * const wc = this->wave_entry + raw_voice->waveform;
+#else
+			int n;
+#endif
+			int envx;
+			
+			/* pregen involves checking keyon, etc */
+			//ENTER_TIMER(dsp_pregen);
+
+			if ( voice->on_cnt && !--voice->on_cnt )
+			{
+				// key on
+				this->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;
+				
+#ifdef SPC_NOINTERP
+				/* predecode BRR if not already */
+				
+				voice->fraction = 0;
+
+				if (voice->addr != wc->startaddr) renderBRR(this,voice,raw_voice,wc,sd);
+				voice->cursample=0;
+				
+#endif
+				
+				// 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 ( this->dspregs.g.key_ons & vbit & ~this->dspregs.g.key_offs )
+			{
+				// voice doesn't come on if key off is set
+				this->dspregs.g.key_ons &= ~vbit;
+				voice->on_cnt = 8;
+			}
+			
+			if ( this->keys & this->dspregs.g.key_offs & vbit )
+			{
+				// key off
+				voice->envstate = state_release;
+				voice->on_cnt = 0;
+			}
+			
+			if ( !(this->keys & vbit) || (envx = DSP_clock_envelope( this, vidx )) < 0 )
+			{
+				raw_voice->envx = 0;
+				raw_voice->outx = 0;
+				prev_outx = 0;
+				//EXIT_TIMER(dsp_pregen);
+				continue;
+			}
+			
+			//EXIT_TIMER(dsp_pregen);
+			
+			//ENTER_TIMER(dsp_gen);
+
+#ifndef SPC_NOINTERP
+			// Decode samples when fraction >= 1.0 (0x1000)
+			for ( n = voice->fraction >> 12; --n >= 0; )
+			{
+				if ( !--voice->block_remain )
+				{
+					if ( voice->block_header & 1 )
+					{
+						this->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 = this->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 && (this->ram [voice->addr + 5] & 3) == 1 &&
+						(voice->block_header & 3) != 3 )
+				{
+			sample_ended:
+					this->dspregs.g.wave_ended |= vbit;
+					this->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 = this->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
+			} /* end decode samples loop */
+#else
+			// Read samples (from cache) when fraction >= 1.0 (0x1000)
+			voice->cursample+=voice->fraction >> 12;
+			if (voice->cursample>=wc->endsample) {
+				this->dspregs.g.wave_ended |= vbit;
+				if (wc->loopstartsample>=0) voice->cursample=wc->loopstartsample;
+				else {
+					this->keys &= ~vbit;
+					raw_voice->envx = 0;
+					voice->envx = 0;
+				}
+			}
+#endif
+
+			// rate (with possible modulation)
+			int rate = GET_LE16( raw_voice->rate ) & 0x3FFF;
+			if ( this->dspregs.g.pitch_mods & vbit )
+				rate = (rate * (prev_outx + 32768)) >> 15;
+
+#ifndef SPC_NOINTERP
+			// Gaussian interpolation using most recent 4 samples
+			int index = voice->fraction >> 2 & 0x3FC;
+			const int16_t* const table  = (int16_t const*) ((char const*) gauss + index);
+			const int16_t* const table2 = (int16_t const*) ((char const*) gauss + (255*4 - index));
+			//#define MASK & ~0xFFF /* SNES drops low bits, but they hardly matter */
+			#define MASK
+			int s = (table  [0] * voice->interp3) MASK;
+			s = (s + table  [1] * voice->interp2) MASK;
+			s = (s + table2 [1] * voice->interp1) MASK;
+			s = (s + table2 [0] * voice->interp0) >> 11;
+			voice->fraction = (voice->fraction & 0x0FFF) + rate;
+			int output = clamp_16( s );
+#else
+			// two-point linear interpolation
+			short const* pos = wc->samples + voice->cursample;
+			int fraction = voice->fraction & 0xFFF;
+			int output = (pos [0] * fraction + pos [-1] * (0x1000 - fraction)) >> 12;
+			//int output = pos [0]; // no interplation
+			voice->fraction = fraction + rate;
+#endif
+			
+			if ( this->dspregs.g.noise_enables & vbit )
+				output = this->noise_amp;
+			
+			//EXIT_TIMER(dsp_gen);
+			
+			//ENTER_TIMER(dsp_mix);
+			
+			// scale output and set outx values
+			output = (output * envx) >> 11 & ~1;
+			
+			// output
+			int l = (voice->volume [0] * output) >> 7;
+			int r = (voice->volume [1] * output) >> 7;
+			prev_outx = output;
+			raw_voice->outx = (int8_t) (output >> 8);
+#ifndef SPC_NOECHO
+			if ( this->dspregs.g.echo_ons & vbit )
+			{
+				echol += l;
+				echor += r;
+			}
+#endif
+			left  += l;
+			right += r;
+			
+			//EXIT_TIMER(dsp_mix);
+		}
+		// end of voice loop
+		
+		// main volume control
+		left  = (left  * left_volume ) >> (7 + 8);
+		right = (right * right_volume) >> (7 + 8);
+		
+		// Echo FIR filter
+#ifndef SPC_NOECHO
+		// read feedback from echo buffer
+		int temp_echo_ptr = this->echo_ptr;
+		uint8_t* const echo_buf = &(this->ram) [(this->dspregs.g.echo_page * 0x100 + temp_echo_ptr) & 0xFFFF];
+		temp_echo_ptr += 4;
+		if ( temp_echo_ptr >= (this->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
+		this->echo_ptr = temp_echo_ptr;
+		
+		// put samples in history ring buffer
+		short (* const fir_pos) [2] = &(this->fir_buf) [this->fir_offset];
+		this->fir_offset = (this->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 * this->fir_coeff [7] +
+				fir_pos [1] [0] * this->fir_coeff [6] +
+				fir_pos [2] [0] * this->fir_coeff [5] +
+				fir_pos [3] [0] * this->fir_coeff [4] +
+				fir_pos [4] [0] * this->fir_coeff [3] +
+				fir_pos [5] [0] * this->fir_coeff [2] +
+				fir_pos [6] [0] * this->fir_coeff [1] +
+				fir_pos [7] [0] * this->fir_coeff [0];
+		
+		fb_right =     fb_right * this->fir_coeff [7] +
+				fir_pos [1] [1] * this->fir_coeff [6] +
+				fir_pos [2] [1] * this->fir_coeff [5] +
+				fir_pos [3] [1] * this->fir_coeff [4] +
+				fir_pos [4] [1] * this->fir_coeff [3] +
+				fir_pos [5] [1] * this->fir_coeff [2] +
+				fir_pos [6] [1] * this->fir_coeff [1] +
+				fir_pos [7] [1] * this->fir_coeff [0];
+		
+		left  += (fb_left  * this->dspregs.g.left_echo_volume ) >> 14;
+		right += (fb_right * this->dspregs.g.right_echo_volume) >> 14;
+		
+		// echo buffer feedback
+		if ( !(this->dspregs.g.flags & 0x20) )
+		{
+			echol += (fb_left  * this->dspregs.g.echo_feedback) >> 14;
+			echor += (fb_right * this->dspregs.g.echo_feedback) >> 14;
+			SET_LE16( echo_buf    , clamp_16( echol ) );
+			SET_LE16( echo_buf + 2, clamp_16( echor ) );
+		}
+#endif
+		// write final samples
+		
+		left  = clamp_16( left  );
+		right = clamp_16( right );
+		
+		if ( this->dspregs.g.flags & 0x40 )
+		{
+			left  = 0;
+			right = 0;
+		}
+			
+		out_buf [0] = (short) left;
+		out_buf [1] = (short) right;
+		out_buf += 2;
+	}
+	
+	EXIT_TIMER(dsp);
+	ENTER_TIMER(cpu);
+}
Index: apps/codecs/spc.c
===================================================================
--- apps/codecs/spc.c	(revision 0)
+++ apps/codecs/spc.c	(revision 0)
@@ -0,0 +1,789 @@
+/***************************************************************************
+ *             __________               __   ___.
+ *   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
+
+/* disable gaussian interpolation */
+#define SPC_NOINTERP
+
+/* disable echo processing */
+#define SPC_NOECHO
+
+/* simple profiling with USEC_TIMER */
+//#define SPC_PROFILE
+
+#include "spc_profiler.h"
+
+#define THIS struct Spc_Emu* const this
+
+typedef short 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 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 && 0 /* TODO: enable when host can make unaligned accesses */
+	#define GET_LE16( addr )        (*(uint16_t*) (addr))
+	#define SET_LE16( addr, data )  (void) (*(uint16_t*) (addr) = (data))
+#else
+	#define GET_LE16( addr )        get_le16( addr )
+	#define SET_LE16( addr, data )  set_le16( addr, data )
+#endif
+
+#include "Spc_Dsp.h"
+
+/**************** 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 )
+{
+	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( this->dsp.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( this->dsp.padding1, 0xFF, sizeof this->dsp.padding1 );
+	memset( this->dsp.padding2, 0xFF, sizeof this->dsp.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( this->dsp.ram, new_ram, sizeof this->dsp.ram );
+	memcpy( this->extra_ram, this->dsp.ram + rom_addr, sizeof this->extra_ram );
+	
+	// boot rom (have to force enable_rom() to update it)
+	this->rom_enabled = !(this->dsp.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 = (this->dsp.ram [0xF1] >> i) & 1;
+		if ( !t->enabled )
+			t->next_tick = timer_disabled_time;
+		t->count = 0;
+		t->counter = this->dsp.ram [0xFD + i] & 15;
+		
+		int p = this->dsp.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.
+	this->dsp.ram [0xF0] = 0;
+	this->dsp.ram [0xF1] = 0;
+	this->dsp.ram [0xF3] = 0xFF;
+	this->dsp.ram [0xFA] = 0;
+	this->dsp.ram [0xFB] = 0;
+	this->dsp.ram [0xFC] = 0;
+	this->dsp.ram [0xFD] = 0xFF;
+	this->dsp.ram [0xFE] = 0xFF;
+	this->dsp.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 this->dsp.ram - addr;
+		if ( size > max_size )
+			size = sizeof this->dsp.ram - addr;
+		memset( this->dsp.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 )
+{
+	int count = ((time - this->next_dsp) >> 5) + 1; // divide by clocks_per_sample
+	sample_t* buf = this->sample_buf;
+	this->sample_buf = buf + count * 2; // stereo
+	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 )
+{
+	int result = this->dsp.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, this->dsp.ram [0xF2] & 0x7F );
+		}
+	}
+	return result;
+}
+
+static void SPC_write( THIS, unsigned addr, int data, spc_time_t const time )
+{
+	// first page is very common
+	if ( addr < 0xF0 )
+	{
+		this->dsp.ram [addr] = (uint8_t) data;
+	}
+	else switch ( addr )
+	{
+		// RAM
+		default:
+			if ( addr < rom_addr )
+			{
+				this->dsp.ram [addr] = (uint8_t) data;
+			}
+			else
+			{
+				this->extra_ram [addr - rom_addr] = (uint8_t) data;
+				if ( !this->rom_enabled )
+					this->dsp.ram [addr] = (uint8_t) data;
+			}
+			break;
+		
+		// DSP
+		//case 0xF2: // mapped to RAM
+		case 0xF3: {
+			SPC_run_dsp( this, time );
+			int reg = this->dsp.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 )
+			{
+				this->dsp.ram [0xF4] = 0;
+				this->dsp.ram [0xF5] = 0;
+			}
+			if ( data & 0x20 )
+			{
+				this->dsp.ram [0xF6] = 0;
+				this->dsp.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_Cpu.h"
+
+/**************** Sample generation ****************/
+
+static int SPC_play( THIS, long count, sample_t* out )
+{
+	int i;
+	assert( count % 2 == 0 ); // output is always in pairs of samples
+	
+	ENTER_TIMER(total);
+	
+	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 );
+		
+		EXIT_TIMER(total);
+		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 );
+	
+	EXIT_TIMER(total);
+	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 ****************/
+
+/* Maximum number of bytes to process in one iteration */
+#define WAV_CHUNK_SIZE (1024*2)
+
+static int16_t samples[WAV_CHUNK_SIZE] IBSS_ATTR;
+
+static struct Spc_Emu spc_emu;
+
+/* The main decoder loop */
+static int play_track( void )
+{
+	int sampleswritten=0;
+	
+	unsigned long fadestartsample = ID666.length*32000LL*2/1000;
+	unsigned long fadeendsample = (ID666.length+ID666.fade)*32000LL*2/1000;
+	
+	while ( 1 )
+	{
+		ci->yield();
+		if (ci->stop_codec || ci->new_track) {
+			break;
+		}
+
+		if (ci->seek_time) {
+			int curtime = sampleswritten*1000LL/32000/2;
+			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();
+		}
+
+		if ( SPC_play(&spc_emu,WAV_CHUNK_SIZE,samples) )
+			assert( false );
+
+		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)
+			{
+				int i;
+				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 (lasttimesample>=fadeendsample)
+				break;
+		}
+
+		while (!ci->pcmbuf_insert((char *)samples, WAV_CHUNK_SIZE*2))
+			ci->yield();
+
+		ci->set_elapsed(sampleswritten*1000LL/32000/2);
+	}
+	
+	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 );
+
+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);
+	size_t buffersize;
+	uint8_t* buffer = ci->request_buffer(&buffersize, ci->filesize);
+	if (!buffer) {
+		return CODEC_ERROR;
+	}
+
+	DEBUGF("SPC: read size = %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);
+
+	if (ci->request_next_track())
+		goto next_track;
+
+	return CODEC_OK;
+}
Index: apps/codecs/Makefile
===================================================================
--- apps/codecs/Makefile	(revision 12099)
+++ 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/Spc_Cpu.h
===================================================================
--- apps/codecs/Spc_Cpu.h	(revision 0)
+++ apps/codecs/Spc_Cpu.h	(revision 0)
@@ -0,0 +1,996 @@
+#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( this->dsp.ram + (addr) )
+
+#define READ_PC( pc )			(*(pc))
+#define READ_PC16( pc )			GET_LE16( pc )
+
+#define SET_PC( n )				(pc = this->dsp.ram + (n))
+#define GET_PC()				(pc - this->dsp.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_ )
+{
+	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 = this->dsp.ram + 0x101 + (v))
+#define GET_SP()        (sp - 0x101 - this->dsp.ram)
+
+static spc_time_t CPU_run( THIS, spc_time_t start_time )
+{
+	ENTER_TIMER(cpu);
+	
+	register spc_time_t spc_time_ = start_time;
+	
+	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/SOURCES
===================================================================
--- apps/codecs/SOURCES	(revision 12099)
+++ 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: apps/codecs/spc_profiler.h
===================================================================
--- apps/codecs/spc_profiler.h	(revision 0)
+++ apps/codecs/spc_profiler.h	(revision 0)
@@ -0,0 +1,60 @@
+#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(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(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(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: firmware/export/id3.h
===================================================================
--- firmware/export/id3.h	(revision 12099)
+++ 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 12099)
+++ firmware/id3.c	(working copy)
@@ -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
 };
 
