Mercurial > audlegacy-plugins
diff src/console/Snes_Spc.h @ 1037:c31e94fefd2a trunk
[svn] - upstream updates regarding handling of SPC700 instructions and runtime
length issues.
| author | nenolod |
|---|---|
| date | Tue, 15 May 2007 13:18:35 -0700 |
| parents | 986f098da058 |
| children |
line wrap: on
line diff
--- a/src/console/Snes_Spc.h Sat May 12 17:06:13 2007 -0700 +++ b/src/console/Snes_Spc.h Tue May 15 13:18:35 2007 -0700 @@ -1,121 +1,284 @@ -// Super Nintendo (SNES) SPC-700 APU Emulator +// SNES SPC-700 APU emulator -// Game_Music_Emu 0.5.2 +// snes_spc 0.9.0 #ifndef SNES_SPC_H #define SNES_SPC_H -#include "blargg_common.h" -#include "Spc_Cpu.h" #include "Spc_Dsp.h" +#include "blargg_endian.h" -class Snes_Spc { +struct Snes_Spc { public: + typedef BOOST::uint8_t uint8_t; + + // Must be called once before using + blargg_err_t init(); - // Load copy of SPC data into emulator. Clear echo buffer if 'clear_echo' is true. - enum { spc_file_size = 0x10180 }; - blargg_err_t load_spc( const void* spc, long spc_size ); + // Sample pairs generated per second + enum { sample_rate = 32000 }; + +// Emulator use - // Generate 'count' samples and optionally write to 'buf'. Count must be even. - // Sample output is 16-bit 32kHz, signed stereo pairs with the left channel first. + // Sets IPL ROM data. Library does not include ROM data. Most SPC music files + // don't need ROM, but a full emulator must provide this. + enum { rom_size = 0x40 }; + void init_rom( uint8_t const rom [rom_size] ); + + // Sets destination for output samples typedef short sample_t; - blargg_err_t play( long count, sample_t* buf = NULL ); - -// Optional functionality + void set_output( sample_t* out, int out_size ); + + // Number of samples written to output since last set + int sample_count() const; + + // Resets SPC to power-on state. This resets your output buffer, so you must + // call set_output() after this. + void reset(); + + // Emulates pressing reset switch on SNES. This resets your output buffer, so + // you must call set_output() after this. + void soft_reset(); + + // 1024000 SPC clocks per second, sample pair every 32 clocks + typedef int time_t; + enum { clock_rate = 1024000 }; + enum { clocks_per_sample = 32 }; - // Load copy of state into emulator. - typedef Spc_Cpu::registers_t registers_t; - blargg_err_t load_state( const registers_t& cpu_state, const void* ram_64k, - const void* dsp_regs_128 ); + // Emulated port read/write at specified time + enum { port_count = 4 }; + int read_port ( time_t, int port ); + void write_port( time_t, int port, int data ); + + // Runs SPC to end_time and starts a new time frame at 0 + void end_frame( time_t end_time ); - // Clear echo buffer, useful because many tracks have junk in the buffer. - void clear_echo(); +// Sound control - // Mute voice n if bit n (1 << n) of mask is set - enum { voice_count = Spc_Dsp::voice_count }; + // Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events). + // Reduces emulation accuracy. + enum { voice_count = 8 }; void mute_voices( int mask ); - // Skip forward by the specified number of samples (64000 samples = 1 second) - blargg_err_t skip( long count ); - - // Set gain, where 1.0 is normal. When greater than 1.0, output is clamped the - // 16-bit sample range. - void set_gain( double ); - - // If true, prevent channels and global volumes from being phase-negated + // If true, prevents channels and global volumes from being phase-negated. + // Only supported by fast DSP. void disable_surround( bool disable = true ); - // Set 128 bytes to use for IPL boot ROM. Makes copy. Default is zero filled, - // to avoid including copyrighted code from the SPC-700. - void set_ipl_rom( const void* ); + // Sets tempo, where tempo_unit = normal, tempo_unit / 2 = half speed, etc. + enum { tempo_unit = 0x100 }; + void set_tempo( int ); + + enum { gain_unit = Spc_Dsp::gain_unit }; + void set_gain( int gain ); - void set_tempo( double ); +// SPC music files + + // Loads SPC data into emulator + enum { spc_min_file_size = 0x10180 }; + enum { spc_file_size = 0x10200 }; + blargg_err_t load_spc( void const* in, long size ); + + // Clears echo region. Useful after loading an SPC as many have garbage in echo. + void clear_echo(); + + // Plays for count samples and write samples to out. Discards samples if out + // is NULL. Count must be a multiple of 2 since output is stereo. + blargg_err_t play( int count, sample_t* out ); + + // Skips count samples. Several times faster than play() when using fast DSP. + blargg_err_t skip( int count ); +// State save/load (only available with accurate DSP) + +#if !SPC_NO_COPY_STATE_FUNCS + // Saves/loads state + enum { state_size = 67 * 1024L }; // maximum space needed when saving + typedef Spc_Dsp::copy_func_t copy_func_t; + void copy_state( unsigned char** io, copy_func_t ); + + // Writes minimal header to spc_out + static void init_header( void* spc_out ); + + // Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out. + // Does not set up SPC header; use init_header() for that. + void save_spc( void* spc_out ); + + // Returns true if new key-on events occurred since last check. Useful for + // trimming silence while saving an SPC. + bool check_kon(); +#endif + public: - Snes_Spc(); - typedef BOOST::uint8_t uint8_t; -private: - // timers + BLARGG_DISABLE_NOTHROW + + typedef BOOST::uint16_t uint16_t; + + // Time relative to m_spc_time. Speeds up code a bit by eliminating need to + // constantly add m_spc_time to time from CPU. CPU uses time that ends at + // 0 to eliminate reloading end time every instruction. It pays off. + typedef int rel_time_t; + struct Timer { - spc_time_t next_tick; + rel_time_t next_time; // time of next event + int prescaler; int period; - int count; - int divisor; + int divider; int enabled; int counter; - - void run_until_( spc_time_t ); - void run_until( spc_time_t time ) - { - if ( time >= next_tick ) - run_until_( time ); - } }; + enum { reg_count = 0x10 }; enum { timer_count = 3 }; - Timer timer [timer_count]; - - // hardware - int extra_cycles; - spc_time_t time() const; - int read( spc_addr_t ); - void write( spc_addr_t, int ); - friend class Spc_Cpu; + enum { extra_size = Spc_Dsp::extra_size }; - // dsp - sample_t* sample_buf; - sample_t* buf_end; // to do: remove this once possible bug resolved - spc_time_t next_dsp; + enum { signature_size = 35 }; + +private: Spc_Dsp dsp; - int keys_pressed; - int keys_released; - sample_t skip_sentinel [1]; // special value for play() passed by skip() - void run_dsp( spc_time_t ); - void run_dsp_( spc_time_t ); - bool echo_accessed; - void check_for_echo_access( spc_addr_t ); + + #if SPC_LESS_ACCURATE + static signed char const reg_times_ [256]; + signed char reg_times [256]; + #endif - // boot rom - enum { rom_size = 64 }; + struct state_t + { + Timer timers [timer_count]; + + uint8_t smp_regs [2] [reg_count]; + + struct + { + int pc; + int a; + int x; + int y; + int psw; + int sp; + } cpu_regs; + + rel_time_t dsp_time; + time_t spc_time; + bool echo_accessed; + + int tempo; + int skipped_kon; + int skipped_koff; + const char* cpu_error; + + int extra_clocks; + sample_t* buf_begin; + sample_t const* buf_end; + sample_t* extra_pos; + sample_t extra_buf [extra_size]; + + int rom_enabled; + uint8_t rom [rom_size]; + uint8_t hi_ram [rom_size]; + + unsigned char cycle_table [256]; + + struct + { + // padding to neutralize address overflow + union { + uint8_t padding1 [0x100]; + uint16_t align; // makes compiler align data for 16-bit access + } padding1 [1]; + uint8_t ram [0x10000]; + uint8_t padding2 [0x100]; + } ram; + }; + state_t m; + enum { rom_addr = 0xFFC0 }; - bool rom_enabled; - void enable_rom( bool ); + + enum { skipping_time = 127 }; + + // Value that padding should be filled with + enum { cpu_pad_fill = 0xFF }; + + enum { + r_test = 0x0, r_control = 0x1, + r_dspaddr = 0x2, r_dspdata = 0x3, + r_cpuio0 = 0x4, r_cpuio1 = 0x5, + r_cpuio2 = 0x6, r_cpuio3 = 0x7, + r_f8 = 0x8, r_f9 = 0x9, + r_t0target = 0xA, r_t1target = 0xB, r_t2target = 0xC, + r_t0out = 0xD, r_t1out = 0xE, r_t2out = 0xF + }; + + void timers_loaded(); + void enable_rom( int enable ); + void reset_buf(); + void save_extra(); + void load_regs( uint8_t const in [reg_count] ); + void ram_loaded(); + void regs_loaded(); + void reset_time_regs(); + void reset_common( int timer_counter_init ); - // CPU and RAM (at end because it's large) - Spc_Cpu cpu; - uint8_t extra_ram [rom_size]; - struct { - // padding to catch jumps before beginning or past end - uint8_t padding1 [0x100]; + Timer* run_timer_ ( Timer* t, rel_time_t ); + Timer* run_timer ( Timer* t, rel_time_t ); + int dsp_read ( rel_time_t ); + void dsp_write ( int data, rel_time_t ); + void cpu_write_smp_reg_( int data, rel_time_t, int addr ); + void cpu_write_smp_reg ( int data, rel_time_t, int addr ); + void cpu_write_high ( int data, int i, rel_time_t ); + void cpu_write ( int data, int addr, rel_time_t ); + int cpu_read_smp_reg ( int i, rel_time_t ); + int cpu_read ( int addr, rel_time_t ); + unsigned CPU_mem_bit ( uint8_t const* pc, rel_time_t ); + + bool check_echo_access ( int addr ); + uint8_t* run_until_( time_t end_time ); + + struct spc_file_t + { + char signature [signature_size]; + uint8_t has_id666; + uint8_t version; + uint8_t pcl, pch; + uint8_t a; + uint8_t x; + uint8_t y; + uint8_t psw; + uint8_t sp; + char text [212]; uint8_t ram [0x10000]; - uint8_t padding2 [0x100]; - } mem; - uint8_t boot_rom [rom_size]; + uint8_t dsp [128]; + uint8_t unused [0x40]; + uint8_t ipl_rom [0x40]; + }; + + static char const signature [signature_size + 1]; + + void save_regs( uint8_t out [reg_count] ); }; -inline void Snes_Spc::disable_surround( bool disable ) { dsp.disable_surround( disable ); } +#include <assert.h> + +inline int Snes_Spc::sample_count() const { return (m.extra_clocks >> 5) * 2; } + +inline int Snes_Spc::read_port( time_t t, int port ) +{ + assert( (unsigned) port < port_count ); + return run_until_( t ) [port]; +} + +inline void Snes_Spc::write_port( time_t t, int port, int data ) +{ + assert( (unsigned) port < port_count ); + run_until_( t ) [0x10 + port] = data; +} + +inline void Snes_Spc::set_gain( int gain ) { dsp.set_gain( gain ); } inline void Snes_Spc::mute_voices( int mask ) { dsp.mute_voices( mask ); } + +inline void Snes_Spc::disable_surround( bool disable ) { dsp.disable_surround( disable ); } -inline void Snes_Spc::set_gain( double v ) { dsp.set_gain( v ); } +#if !SPC_NO_COPY_STATE_FUNCS +inline bool Snes_Spc::check_kon() { return dsp.check_kon(); } +#endif #endif
