rlm@0: // SNES SPC-700 APU emulator rlm@0: rlm@0: // snes_spc 0.9.0 rlm@0: #ifndef SNES_SPC_H rlm@0: #define SNES_SPC_H rlm@0: rlm@0: #include "SPC_DSP.h" rlm@0: #include "blargg_endian.h" rlm@0: rlm@0: struct SNES_SPC { rlm@0: public: rlm@0: typedef BOOST::uint8_t uint8_t; rlm@0: rlm@0: // Must be called once before using rlm@0: blargg_err_t init(); rlm@0: rlm@0: // Sample pairs generated per second rlm@0: enum { sample_rate = 32000 }; rlm@0: rlm@0: // Emulator use rlm@0: rlm@0: // Sets IPL ROM data. Library does not include ROM data. Most SPC music files rlm@0: // don't need ROM, but a full emulator must provide this. rlm@0: enum { rom_size = 0x40 }; rlm@0: void init_rom( uint8_t const rom [rom_size] ); rlm@0: rlm@0: // Sets destination for output samples rlm@0: typedef short sample_t; rlm@0: void set_output( sample_t* out, int out_size ); rlm@0: rlm@0: // Number of samples written to output since last set rlm@0: int sample_count() const; rlm@0: rlm@0: // Resets SPC to power-on state. This resets your output buffer, so you must rlm@0: // call set_output() after this. rlm@0: void reset(); rlm@0: rlm@0: // Emulates pressing reset switch on SNES. This resets your output buffer, so rlm@0: // you must call set_output() after this. rlm@0: void soft_reset(); rlm@0: rlm@0: // 1024000 SPC clocks per second, sample pair every 32 clocks rlm@0: typedef int time_t; rlm@0: enum { clock_rate = 1024000 }; rlm@0: enum { clocks_per_sample = 32 }; rlm@0: rlm@0: // Emulated port read/write at specified time rlm@0: enum { port_count = 4 }; rlm@0: int read_port ( time_t, int port ); rlm@0: void write_port( time_t, int port, int data ); rlm@0: rlm@0: // Runs SPC to end_time and starts a new time frame at 0 rlm@0: void end_frame( time_t end_time ); rlm@0: rlm@0: // Sound control rlm@0: rlm@0: // Mutes voices corresponding to non-zero bits in mask (issues repeated KOFF events). rlm@0: // Reduces emulation accuracy. rlm@0: enum { voice_count = 8 }; rlm@0: void mute_voices( int mask ); rlm@0: rlm@0: // If true, prevents channels and global volumes from being phase-negated. rlm@0: // Only supported by fast DSP. rlm@0: void disable_surround( bool disable = true ); rlm@0: rlm@0: // Sets tempo, where tempo_unit = normal, tempo_unit / 2 = half speed, etc. rlm@0: enum { tempo_unit = 0x100 }; rlm@0: void set_tempo( int ); rlm@0: rlm@0: // SPC music files rlm@0: rlm@0: // Loads SPC data into emulator rlm@0: enum { spc_min_file_size = 0x10180 }; rlm@0: enum { spc_file_size = 0x10200 }; rlm@0: blargg_err_t load_spc( void const* in, long size ); rlm@0: rlm@0: // Clears echo region. Useful after loading an SPC as many have garbage in echo. rlm@0: void clear_echo(); rlm@0: rlm@0: // Plays for count samples and write samples to out. Discards samples if out rlm@0: // is NULL. Count must be a multiple of 2 since output is stereo. rlm@0: blargg_err_t play( int count, sample_t* out ); rlm@0: rlm@0: // Skips count samples. Several times faster than play() when using fast DSP. rlm@0: blargg_err_t skip( int count ); rlm@0: rlm@0: // State save/load (only available with accurate DSP) rlm@0: rlm@0: #if !SPC_NO_COPY_STATE_FUNCS rlm@0: // Saves/loads state rlm@0: enum { state_size = 67 * 1024L }; // maximum space needed when saving rlm@0: typedef SPC_DSP::copy_func_t copy_func_t; rlm@0: void copy_state( unsigned char** io, copy_func_t ); rlm@0: rlm@0: // Writes minimal header to spc_out rlm@0: static void init_header( void* spc_out ); rlm@0: rlm@0: // Saves emulator state as SPC file data. Writes spc_file_size bytes to spc_out. rlm@0: // Does not set up SPC header; use init_header() for that. rlm@0: void save_spc( void* spc_out ); rlm@0: rlm@0: // Returns true if new key-on events occurred since last check. Useful for rlm@0: // trimming silence while saving an SPC. rlm@0: bool check_kon(); rlm@0: #endif rlm@0: rlm@0: public: rlm@0: BLARGG_DISABLE_NOTHROW rlm@0: rlm@0: typedef BOOST::uint16_t uint16_t; rlm@0: rlm@0: // Time relative to m_spc_time. Speeds up code a bit by eliminating need to rlm@0: // constantly add m_spc_time to time from CPU. CPU uses time that ends at rlm@0: // 0 to eliminate reloading end time every instruction. It pays off. rlm@0: typedef int rel_time_t; rlm@0: rlm@0: struct Timer rlm@0: { rlm@0: rel_time_t next_time; // time of next event rlm@0: int prescaler; rlm@0: int period; rlm@0: int divider; rlm@0: int enabled; rlm@0: int counter; rlm@0: }; rlm@0: enum { reg_count = 0x10 }; rlm@0: enum { timer_count = 3 }; rlm@0: enum { extra_size = SPC_DSP::extra_size }; rlm@0: rlm@0: enum { signature_size = 35 }; rlm@0: rlm@0: private: rlm@0: SPC_DSP dsp; rlm@0: rlm@0: #if SPC_LESS_ACCURATE rlm@0: static signed char const reg_times_ [256]; rlm@0: signed char reg_times [256]; rlm@0: #endif rlm@0: rlm@0: struct state_t rlm@0: { rlm@0: Timer timers [timer_count]; rlm@0: rlm@0: uint8_t smp_regs [2] [reg_count]; rlm@0: rlm@0: struct rlm@0: { rlm@0: int pc; rlm@0: int a; rlm@0: int x; rlm@0: int y; rlm@0: int psw; rlm@0: int sp; rlm@0: } cpu_regs; rlm@0: rlm@0: rel_time_t dsp_time; rlm@0: time_t spc_time; rlm@0: bool echo_accessed; rlm@0: rlm@0: int tempo; rlm@0: int skipped_kon; rlm@0: int skipped_koff; rlm@0: const char* cpu_error; rlm@0: rlm@0: int extra_clocks; rlm@0: sample_t* buf_begin; rlm@0: sample_t const* buf_end; rlm@0: sample_t* extra_pos; rlm@0: sample_t extra_buf [extra_size]; rlm@0: rlm@0: int rom_enabled; rlm@0: uint8_t rom [rom_size]; rlm@0: uint8_t hi_ram [rom_size]; rlm@0: rlm@0: unsigned char cycle_table [256]; rlm@0: rlm@0: struct rlm@0: { rlm@0: // padding to neutralize address overflow rlm@0: union { rlm@0: uint8_t padding1 [0x100]; rlm@0: uint16_t align; // makes compiler align data for 16-bit access rlm@0: } padding1 [1]; rlm@0: uint8_t ram [0x10000]; rlm@0: uint8_t padding2 [0x100]; rlm@0: } ram; rlm@0: }; rlm@0: state_t m; rlm@0: rlm@0: enum { rom_addr = 0xFFC0 }; rlm@0: rlm@0: enum { skipping_time = 127 }; rlm@0: rlm@0: // Value that padding should be filled with rlm@0: enum { cpu_pad_fill = 0xFF }; rlm@0: rlm@0: enum { rlm@0: r_test = 0x0, r_control = 0x1, rlm@0: r_dspaddr = 0x2, r_dspdata = 0x3, rlm@0: r_cpuio0 = 0x4, r_cpuio1 = 0x5, rlm@0: r_cpuio2 = 0x6, r_cpuio3 = 0x7, rlm@0: r_f8 = 0x8, r_f9 = 0x9, rlm@0: r_t0target = 0xA, r_t1target = 0xB, r_t2target = 0xC, rlm@0: r_t0out = 0xD, r_t1out = 0xE, r_t2out = 0xF rlm@0: }; rlm@0: rlm@0: void timers_loaded(); rlm@0: void enable_rom( int enable ); rlm@0: void reset_buf(); rlm@0: void save_extra(); rlm@0: void load_regs( uint8_t const in [reg_count] ); rlm@0: void ram_loaded(); rlm@0: void regs_loaded(); rlm@0: void reset_time_regs(); rlm@0: void reset_common( int timer_counter_init ); rlm@0: rlm@0: Timer* run_timer_ ( Timer* t, rel_time_t ); rlm@0: Timer* run_timer ( Timer* t, rel_time_t ); rlm@0: int dsp_read ( rel_time_t ); rlm@0: void dsp_write ( int data, rel_time_t ); rlm@0: void cpu_write_smp_reg_( int data, rel_time_t, int addr ); rlm@0: void cpu_write_smp_reg ( int data, rel_time_t, int addr ); rlm@0: void cpu_write_high ( int data, int i, rel_time_t ); rlm@0: void cpu_write ( int data, int addr, rel_time_t ); rlm@0: int cpu_read_smp_reg ( int i, rel_time_t ); rlm@0: int cpu_read ( int addr, rel_time_t ); rlm@0: unsigned CPU_mem_bit ( uint8_t const* pc, rel_time_t ); rlm@0: rlm@0: bool check_echo_access ( int addr ); rlm@0: uint8_t* run_until_( time_t end_time ); rlm@0: rlm@0: struct spc_file_t rlm@0: { rlm@0: char signature [signature_size]; rlm@0: uint8_t has_id666; rlm@0: uint8_t version; rlm@0: uint8_t pcl, pch; rlm@0: uint8_t a; rlm@0: uint8_t x; rlm@0: uint8_t y; rlm@0: uint8_t psw; rlm@0: uint8_t sp; rlm@0: char text [212]; rlm@0: uint8_t ram [0x10000]; rlm@0: uint8_t dsp [128]; rlm@0: uint8_t unused [0x40]; rlm@0: uint8_t ipl_rom [0x40]; rlm@0: }; rlm@0: rlm@0: static char const signature [signature_size + 1]; rlm@0: rlm@0: void save_regs( uint8_t out [reg_count] ); rlm@0: }; rlm@0: rlm@0: #include rlm@0: rlm@0: inline int SNES_SPC::sample_count() const { return (m.extra_clocks >> 5) * 2; } rlm@0: rlm@0: inline int SNES_SPC::read_port( time_t t, int port ) rlm@0: { rlm@0: assert( (unsigned) port < port_count ); rlm@0: return run_until_( t ) [port]; rlm@0: } rlm@0: rlm@0: inline void SNES_SPC::write_port( time_t t, int port, int data ) rlm@0: { rlm@0: assert( (unsigned) port < port_count ); rlm@0: run_until_( t ) [0x10 + port] = data; rlm@0: } rlm@0: rlm@0: inline void SNES_SPC::mute_voices( int mask ) { dsp.mute_voices( mask ); } rlm@0: rlm@0: inline void SNES_SPC::disable_surround( bool disable ) { dsp.disable_surround( disable ); } rlm@0: rlm@0: #if !SPC_NO_COPY_STATE_FUNCS rlm@0: inline bool SNES_SPC::check_kon() { return dsp.check_kon(); } rlm@0: #endif rlm@0: rlm@0: #endif