5fd4c15e2a
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12391 a1c6a512-1295-4272-9138-f99709370657
854 lines
22 KiB
C
854 lines
22 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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
|
|
#else
|
|
/* Don't cache BRR waves */
|
|
#define SPC_BRRCACHE 0
|
|
|
|
/* Allow gaussian interpolation */
|
|
#define SPC_NOINTERP 0
|
|
|
|
/* Allow echo processing */
|
|
#define SPC_NOECHO 0
|
|
#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 )
|
|
|
|
#ifdef 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;
|
|
}
|