From ebc9e721f6f4374ac23ac419b2ebfd0c96c694c3 Mon Sep 17 00:00:00 2001 From: Brandon Wright Date: Sun, 26 Jun 2011 05:33:14 -0500 Subject: [PATCH] Add SPC dumping support. --- apu/apu.cpp | 29 ++++++- apu/apu.h | 4 +- apu/bapu/dsp/SPC_DSP.cpp | 161 +++++++++++++++++++------------------ apu/bapu/smp/smp.hpp | 1 + apu/bapu/smp/smp_state.cpp | 68 +++++++++++++++- snapshot.cpp | 26 ------ snapshot.h | 1 - 7 files changed, 179 insertions(+), 111 deletions(-) diff --git a/apu/apu.cpp b/apu/apu.cpp index 80f1ed4c..11bb5c7d 100644 --- a/apu/apu.cpp +++ b/apu/apu.cpp @@ -476,8 +476,8 @@ void S9xSetSoundMute (bool8 mute) void S9xDumpSPCSnapshot (void) { -/* TODO: SPC dumping */ -/* spc_core->dsp_dump_spc_snapshot(); */ + SNES::dsp.spc_dsp.dump_spc_snapshot(); + } static void SPCSnapshotCallback (void) @@ -492,6 +492,8 @@ bool8 S9xInitAPU (void) spc::shrink_buffer = NULL; spc::resampler = NULL; + SNES::dsp.spc_dsp.set_spc_snapshot_callback(SPCSnapshotCallback); + return (TRUE); } @@ -632,3 +634,26 @@ void S9xAPULoadState (uint8 *block) ptr += sizeof(int32); memcpy (SNES::cpu.registers, ptr, 4); } + +bool8 S9xSPCDump (const char *filename) +{ + FILE *fs; + uint8 buf[SPC_FILE_SIZE]; + size_t ignore; + + fs = fopen(filename, "wb"); + if (!fs) + return (FALSE); + + S9xSetSoundMute(TRUE); + + SNES::smp.save_spc (buf); + + ignore = fwrite(buf, SPC_FILE_SIZE, 1, fs); + + fclose(fs); + + S9xSetSoundMute(FALSE); + + return (TRUE); +} diff --git a/apu/apu.h b/apu/apu.h index 77cc4bfd..44b183a8 100644 --- a/apu/apu.h +++ b/apu/apu.h @@ -183,7 +183,8 @@ typedef void (*apu_callback) (void *); -#define SPC_SAVE_STATE_BLOCK_SIZE (1024 * 65) +#define SPC_SAVE_STATE_BLOCK_SIZE (1024 * 65) +#define SPC_FILE_SIZE (66048) bool8 S9xInitAPU (void); void S9xDeinitAPU (void); @@ -199,6 +200,7 @@ void S9xAPUAllowTimeOverflow (bool); void S9xAPULoadState (uint8 *); void S9xAPUSaveState (uint8 *); void S9xDumpSPCSnapshot (void); +bool8 S9xSPCDump (const char *); bool8 S9xInitSound (int, int); bool8 S9xOpenSoundDevice (void); diff --git a/apu/bapu/dsp/SPC_DSP.cpp b/apu/bapu/dsp/SPC_DSP.cpp index e6ba49ed..e3719203 100644 --- a/apu/bapu/dsp/SPC_DSP.cpp +++ b/apu/bapu/dsp/SPC_DSP.cpp @@ -132,7 +132,7 @@ inline int SPC_DSP::interpolate( voice_t const* v ) int offset = v->interp_pos >> 4 & 0xFF; short const* fwd = gauss + 255 - offset; short const* rev = gauss + offset; // mirror left half of gaussian - + int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos]; int out; out = (fwd [ 0] * in [0]) >> 11; @@ -140,7 +140,7 @@ inline int SPC_DSP::interpolate( voice_t const* v ) out += (rev [256] * in [2]) >> 11; out = (int16_t) out; out += (rev [ 0] * in [3]) >> 11; - + CLAMP16( out ); out &= ~1; return out; @@ -262,13 +262,13 @@ inline void SPC_DSP::run_envelope( voice_t* const v ) } } } - + // Sustain level if ( (env >> 8) == (env_data >> 5) && v->env_mode == env_decay ) v->env_mode = env_sustain; - + v->hidden_env = env; - + // unsigned cast because linear decrease going negative also triggers this if ( (unsigned) env > 0x7FF ) { @@ -276,7 +276,7 @@ inline void SPC_DSP::run_envelope( voice_t* const v ) if ( v->env_mode == env_attack ) v->env_mode = env_decay; } - + if ( !read_counter( rate ) ) v->env = env; // nothing else is controlled by the counter } @@ -289,27 +289,27 @@ inline void SPC_DSP::decode_brr( voice_t* v ) { // Arrange the four input nybbles in 0xABCD order for easy decoding int nybbles = m.t_brr_byte * 0x100 + m.ram [(v->brr_addr + v->brr_offset + 1) & 0xFFFF]; - + int const header = m.t_brr_header; - + // Write to next four samples in circular buffer int* pos = &v->buf [v->buf_pos]; int* end; if ( (v->buf_pos += 4) >= brr_buf_size ) v->buf_pos = 0; - + // Decode four samples for ( end = pos + 4; pos < end; pos++, nybbles <<= 4 ) { // Extract nybble and sign-extend int s = (int16_t) nybbles >> 12; - + // Shift sample based on header int const shift = header >> 4; s = (s << shift) >> 1; if ( shift >= 0xD ) // handle invalid range s = (s >> 25) << 11; // same as: s = (s < 0 ? -0x800 : 0) - + // Apply IIR filter (8 is the most commonly used) int const filter = header & 0x0C; int const p1 = pos [brr_buf_size - 1]; @@ -334,7 +334,7 @@ inline void SPC_DSP::decode_brr( voice_t* v ) s += p1 >> 1; s += (-p1) >> 5; } - + // Adjust and write sample CLAMP16( s ); s = (int16_t) (s * 2); @@ -367,11 +367,11 @@ MISC_CLOCK( 30 ) if ( m.every_other_sample ) { m.kon = m.new_kon; - m.t_koff = REG(koff) | m.mute_mask; + m.t_koff = REG(koff) | m.mute_mask; } - + run_counters(); - + // Noise if ( !read_counter( REG(flg) & 0x1F ) ) { @@ -397,9 +397,9 @@ inline VOICE_CLOCK( V2 ) if ( !v->kon_delay ) entry += 2; m.t_brr_next_addr = GET_LE16A( entry ); - + m.t_adsr0 = VREG(v->regs,adsr0); - + // Read pitch, spread over two clocks m.t_pitch = VREG(v->regs,pitchl); } @@ -418,7 +418,7 @@ VOICE_CLOCK( V3c ) // Pitch modulation using previous voice's output if ( m.t_pmon & v->vbit ) m.t_pitch += ((m.t_output >> 5) * m.t_pitch) >> 10; - + if ( v->kon_delay ) { // Get ready to start BRR decoding on next sample @@ -437,46 +437,46 @@ VOICE_CLOCK( V3c ) spc_snapshot_callback(); } } - + // Envelope is never run during KON v->env = 0; v->hidden_env = 0; - + // Disable BRR decoding until last three samples v->interp_pos = 0; if ( --v->kon_delay & 3 ) v->interp_pos = 0x4000; - + // Pitch is never added during KON m.t_pitch = 0; } - + // Gaussian interpolation { int output = interpolate( v ); - + // Noise if ( m.t_non & v->vbit ) output = (int16_t) (m.noise * 2); - + // Apply envelope m.t_output = (output * v->env) >> 11 & ~1; v->t_envx_out = (uint8_t) (v->env >> 4); } - + // Immediate silence due to end of sample or soft reset if ( REG(flg) & 0x80 || (m.t_brr_header & 3) == 1 ) { v->env_mode = env_release; v->env = 0; } - + if ( m.every_other_sample ) { // KOFF if ( m.t_koff & v->vbit ) v->env_mode = env_release; - + // KON if ( m.kon & v->vbit ) { @@ -484,7 +484,7 @@ VOICE_CLOCK( V3c ) v->env_mode = env_attack; } } - + // Run envelope for next sample if ( !v->kon_delay ) run_envelope( v ); @@ -499,7 +499,7 @@ inline void SPC_DSP::voice_output( voice_t const* v, int ch ) // Add to output total m.t_main_out [ch] += amp; CLAMP16( m.t_main_out [ch] ); - + // Optionally add to echo total if ( m.t_eon & v->vbit ) { @@ -514,7 +514,7 @@ VOICE_CLOCK( V4 ) if ( v->interp_pos >= 0x4000 ) { decode_brr( v ); - + if ( (v->brr_offset += 2) >= brr_block_size ) { // Start decoding next BRR block @@ -528,14 +528,14 @@ VOICE_CLOCK( V4 ) v->brr_offset = 1; } } - + // Apply pitch v->interp_pos = (v->interp_pos & 0x3FFF) + m.t_pitch; - + // Keep from getting too far ahead (when using pitch modulation) if ( v->interp_pos > 0x7FFF ) v->interp_pos = 0x7FFF; - + // Output left voice_output( v, 0 ); } @@ -543,10 +543,10 @@ inline VOICE_CLOCK( V5 ) { // Output right voice_output( v, 1 ); - + // ENDX, OUTX, and ENVX won't update if you wrote to them 1-2 clocks earlier int endx_buf = REG(endx) | m.t_looped; - + // Clear bit in ENDX if KON just began if ( v->kon_delay == 5 ) endx_buf &= ~v->vbit; @@ -561,7 +561,7 @@ inline VOICE_CLOCK( V7 ) { // Update ENDX REG(endx) = m.endx_buf; - + m.envx_buf = v->t_envx_out; } inline VOICE_CLOCK( V8 ) @@ -619,14 +619,14 @@ ECHO_CLOCK( 22 ) // History if ( ++m.echo_hist_pos >= &m.echo_hist [echo_hist_size] ) m.echo_hist_pos = m.echo_hist; - + m.t_echo_ptr = (m.t_esa * 0x100 + m.echo_offset) & 0xFFFF; echo_read( 0 ); - + // FIR (using l and r temporaries below helps compiler optimize) int l = CALC_FIR( 0, 0 ); int r = CALC_FIR( 0, 1 ); - + m.t_echo_in [0] = l; m.t_echo_in [1] = r; } @@ -634,17 +634,17 @@ ECHO_CLOCK( 23 ) { int l = CALC_FIR( 1, 0 ) + CALC_FIR( 2, 0 ); int r = CALC_FIR( 1, 1 ) + CALC_FIR( 2, 1 ); - + m.t_echo_in [0] += l; m.t_echo_in [1] += r; - + echo_read( 1 ); } ECHO_CLOCK( 24 ) { int l = CALC_FIR( 3, 0 ) + CALC_FIR( 4, 0 ) + CALC_FIR( 5, 0 ); int r = CALC_FIR( 3, 1 ) + CALC_FIR( 4, 1 ) + CALC_FIR( 5, 1 ); - + m.t_echo_in [0] += l; m.t_echo_in [1] += r; } @@ -652,16 +652,16 @@ ECHO_CLOCK( 25 ) { int l = m.t_echo_in [0] + CALC_FIR( 6, 0 ); int r = m.t_echo_in [1] + CALC_FIR( 6, 1 ); - + l = (int16_t) l; r = (int16_t) r; - + l += (int16_t) CALC_FIR( 7, 0 ); r += (int16_t) CALC_FIR( 7, 1 ); - + CLAMP16( l ); CLAMP16( r ); - + m.t_echo_in [0] = l & ~1; m.t_echo_in [1] = r & ~1; } @@ -677,14 +677,14 @@ ECHO_CLOCK( 26 ) // Left output volumes // (save sample for next clock so we can output both together) m.t_main_out [0] = echo_output( 0 ); - + // Echo feedback int l = m.t_echo_out [0] + (int16_t) ((m.t_echo_in [0] * (int8_t) REG(efb)) >> 7); int r = m.t_echo_out [1] + (int16_t) ((m.t_echo_in [1] * (int8_t) REG(efb)) >> 7); - + CLAMP16( l ); CLAMP16( r ); - + m.t_echo_out [0] = l & ~1; m.t_echo_out [1] = r & ~1; } @@ -695,7 +695,7 @@ ECHO_CLOCK( 27 ) int r = echo_output( 1 ); m.t_main_out [0] = 0; m.t_main_out [1] = 0; - + // TODO: global muting isn't this simple (turns DAC on and off // or something, causing small ~37-sample pulse when first muted) if ( REG(flg) & 0x40 ) @@ -703,7 +703,7 @@ ECHO_CLOCK( 27 ) l = 0; r = 0; } - + // Output sample to DAC #ifdef SPC_DSP_OUT_HOOK SPC_DSP_OUT_HOOK( l, r ); @@ -732,17 +732,17 @@ inline void SPC_DSP::echo_write( int ch ) ECHO_CLOCK( 29 ) { m.t_esa = REG(esa); - + if ( !m.echo_offset ) m.echo_length = (REG(edl) & 0x0F) * 0x800; - + m.echo_offset += 4; if ( m.echo_offset >= m.echo_length ) m.echo_offset = 0; - + // Write left echo echo_write( 0 ); - + m.t_echo_enabled = REG(flg); } ECHO_CLOCK( 30 ) @@ -805,17 +805,17 @@ PHASE(31) V(V4,0) V(V1,2)\ void SPC_DSP::run( int clocks_remain ) { require( clocks_remain > 0 ); - + int const phase = m.phase; m.phase = (phase + clocks_remain) & 31; switch ( phase ) { loop: - + #define PHASE( n ) if ( n && !--clocks_remain ) break; case n: GEN_DSP_TIMING #undef PHASE - + if ( --clocks_remain ) goto loop; } @@ -837,19 +837,20 @@ void SPC_DSP::init( void* ram_64k ) stereo_switch = 0xffff; take_spc_snapshot = 0; spc_snapshot_callback = 0; + rom_enabled = 0; #ifndef NDEBUG // be sure this sign-extends assert( (int16_t) 0x8000 == -0x8000 ); - + // be sure right shift preserves sign assert( (-1 >> 1) == -1 ); - + // check clamp macro int i; i = +0x8000; CLAMP16( i ); assert( i == +0x7FFF ); i = -0x8001; CLAMP16( i ); assert( i == -0x8000 ); - + blargg_verify_byte_order(); #endif } @@ -857,13 +858,13 @@ void SPC_DSP::init( void* ram_64k ) void SPC_DSP::soft_reset_common() { require( m.ram ); // init() must have been called already - + m.noise = 0x4000; m.echo_hist_pos = m.echo_hist; m.every_other_sample = 1; m.echo_offset = 0; m.phase = 0; - + init_counter(); for (int i = 0; i < voice_count; i++) @@ -880,7 +881,7 @@ void SPC_DSP::load( uint8_t const regs [register_count] ) { memcpy( m.regs, regs, sizeof m.regs ); memset( &m.regs [register_count], 0, offsetof (state_t,ram) - register_count ); - + // Internal state for ( int i = voice_count; --i >= 0; ) { @@ -892,7 +893,7 @@ void SPC_DSP::load( uint8_t const regs [register_count] ) m.new_kon = REG(kon); m.t_dir = REG(dir); m.t_esa = REG(esa); - + soft_reset_common(); } @@ -945,18 +946,18 @@ void SPC_State_Copier::extra() void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) { SPC_State_Copier copier( io, copy ); - + // DSP registers copier.copy( m.regs, register_count ); - + // Internal state - + // Voices int i; for ( i = 0; i < voice_count; i++ ) { voice_t* v = &m.voices [i]; - + // BRR buffer int i; for ( i = 0; i < brr_buf_size; i++ ) @@ -965,7 +966,7 @@ void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) SPC_COPY( int16_t, s ); v->buf [i] = v->buf [i + brr_buf_size] = s; } - + SPC_COPY( uint16_t, v->interp_pos ); SPC_COPY( uint16_t, v->brr_addr ); SPC_COPY( uint16_t, v->env ); @@ -979,10 +980,10 @@ void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) v->env_mode = (enum env_mode_t) m; } SPC_COPY( uint8_t, v->t_envx_out ); - + copier.extra(); } - + // Echo history for ( i = 0; i < echo_hist_size; i++ ) { @@ -996,28 +997,28 @@ void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) } m.echo_hist_pos = m.echo_hist; memcpy( &m.echo_hist [echo_hist_size], m.echo_hist, echo_hist_size * sizeof m.echo_hist [0] ); - + // Misc SPC_COPY( uint8_t, m.every_other_sample ); SPC_COPY( uint8_t, m.kon ); - + SPC_COPY( uint16_t, m.noise ); SPC_COPY( uint16_t, m.counter ); SPC_COPY( uint16_t, m.echo_offset ); SPC_COPY( uint16_t, m.echo_length ); SPC_COPY( uint8_t, m.phase ); - + SPC_COPY( uint8_t, m.new_kon ); SPC_COPY( uint8_t, m.endx_buf ); SPC_COPY( uint8_t, m.envx_buf ); SPC_COPY( uint8_t, m.outx_buf ); - + SPC_COPY( uint8_t, m.t_pmon ); SPC_COPY( uint8_t, m.t_non ); SPC_COPY( uint8_t, m.t_eon ); SPC_COPY( uint8_t, m.t_dir ); SPC_COPY( uint8_t, m.t_koff ); - + SPC_COPY( uint16_t, m.t_brr_next_addr ); SPC_COPY( uint8_t, m.t_adsr0 ); SPC_COPY( uint8_t, m.t_brr_header ); @@ -1025,20 +1026,20 @@ void SPC_DSP::copy_state( unsigned char** io, copy_func_t copy ) SPC_COPY( uint8_t, m.t_srcn ); SPC_COPY( uint8_t, m.t_esa ); SPC_COPY( uint8_t, m.t_echo_enabled ); - + SPC_COPY( int16_t, m.t_main_out [0] ); SPC_COPY( int16_t, m.t_main_out [1] ); SPC_COPY( int16_t, m.t_echo_out [0] ); SPC_COPY( int16_t, m.t_echo_out [1] ); SPC_COPY( int16_t, m.t_echo_in [0] ); SPC_COPY( int16_t, m.t_echo_in [1] ); - + SPC_COPY( uint16_t, m.t_dir_addr ); SPC_COPY( uint16_t, m.t_pitch ); SPC_COPY( int16_t, m.t_output ); SPC_COPY( uint16_t, m.t_echo_ptr ); SPC_COPY( uint8_t, m.t_looped ); - + copier.extra(); } #endif diff --git a/apu/bapu/smp/smp.hpp b/apu/bapu/smp/smp.hpp index b50bb631..638c400d 100644 --- a/apu/bapu/smp/smp.hpp +++ b/apu/bapu/smp/smp.hpp @@ -22,6 +22,7 @@ public: #else void load_state(uint8 **); void save_state(uint8 **); + void save_spc (uint8 *); #endif SMP(); ~SMP(); diff --git a/apu/bapu/smp/smp_state.cpp b/apu/bapu/smp/smp_state.cpp index 2e590e69..7c5179c4 100644 --- a/apu/bapu/smp/smp_state.cpp +++ b/apu/bapu/smp/smp_state.cpp @@ -1,9 +1,75 @@ #include "snes/snes.hpp" +#include + +typedef struct spc_file { + uint8 header[33]; + uint8 idtag[3]; + uint8 version_minor; + + uint8 pc_low; + uint8 pc_high; + uint8 a; + uint8 x; + uint8 y; + uint8 psw; + uint8 sp; + uint8 unused_a[2]; + + uint8 id666[210]; + + uint8 apuram[65536]; + uint8 dsp_registers[128]; + uint8 unused_b[64]; + uint8 iplrom[64]; +} spc_file; namespace SNES { #include "dsp/blargg_endian.h" +void SMP::save_spc (uint8 *block) { + spc_file out; + + const char *header = "SNES-SPC700 Sound File Data v0.30"; + memcpy (out.header, header, 33); + out.idtag[0] = out.idtag[1] = 26; + out.idtag[2] = 27; + out.version_minor = 30; + + out.pc_low = regs.pc & 0xff; + out.pc_high = (regs.pc >> 8) & 0xff; + out.a = regs.a; + out.x = regs.x; + out.y = regs.y; + out.psw = (uint8) ((unsigned) regs.p); + out.sp = regs.sp; + out.unused_a[0] = out.unused_a[1] = 0; + + memset (out.id666, 0, 210); + memcpy (out.apuram, apuram, 65536); + + for (int i = 0xf2; i <= 0xf7; i++) + { + out.apuram[i] = mmio_read (i); + } + + for (int i = 0xfd; i <= 0xff; i++) + { + out.apuram[i] = mmio_read (i); + } + + for (int i = 0; i < 128; i++) + { + out.dsp_registers[i] = dsp.read (i); + } + + memset (out.unused_b, 0, 64); + memcpy (out.iplrom, iplrom, 64); + + memcpy (block, &out, 66048); +} + + void SMP::save_state(uint8 **block) { uint8 *ptr = *block; memcpy(ptr, apuram, 64 * 1024); @@ -116,4 +182,4 @@ void SMP::load_state(uint8 **block) { *block = ptr; } -} +} /* namespace SNES */ diff --git a/snapshot.cpp b/snapshot.cpp index d07c430b..e6cd879a 100644 --- a/snapshot.cpp +++ b/snapshot.cpp @@ -2275,29 +2275,3 @@ static void UnfreezeStructFromCopy (void *sbase, FreezeData *fields, int num_fie } } } - -bool8 S9xSPCDump (const char *filename) -{ - /* TODO: No SPC dumping in byuu SMP */ - /* - FILE *fs; - uint8 buf[SNES_SPC::spc_file_size]; - size_t ignore; - - fs = fopen(filename, "wb"); - if (!fs) - return (FALSE); - - S9xSetSoundMute(TRUE); - - spc_core->init_header(buf); - spc_core->save_spc(buf); - - ignore = fwrite(buf, SNES_SPC::spc_file_size, 1, fs); - - fclose(fs); - - S9xSetSoundMute(FALSE); - */ - return (TRUE); -} diff --git a/snapshot.h b/snapshot.h index d55b8b7e..1cb4bd8c 100644 --- a/snapshot.h +++ b/snapshot.h @@ -195,6 +195,5 @@ bool8 S9xFreezeGame (const char *); bool8 S9xUnfreezeGame (const char *); void S9xFreezeToStream (STREAM); int S9xUnfreezeFromStream (STREAM); -bool8 S9xSPCDump (const char *); #endif