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