Add SPC dumping support.

This commit is contained in:
Brandon Wright 2011-06-26 05:33:14 -05:00
parent 2e94b98e90
commit ebc9e721f6
7 changed files with 179 additions and 111 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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

View File

@ -22,6 +22,7 @@ public:
#else
void load_state(uint8 **);
void save_state(uint8 **);
void save_spc (uint8 *);
#endif
SMP();
~SMP();

View File

@ -1,9 +1,75 @@
#include "snes/snes.hpp"
#include <stdio.h>
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 */

View File

@ -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);
}

View File

@ -195,6 +195,5 @@ bool8 S9xFreezeGame (const char *);
bool8 S9xUnfreezeGame (const char *);
void S9xFreezeToStream (STREAM);
int S9xUnfreezeFromStream (STREAM);
bool8 S9xSPCDump (const char *);
#endif