// SNES SPC-700 APU emulator // snes_spc 0.9.0 #pragma once #include "SPC_DSP.h" #include "blargg_endian.h" // (n ? n : 256) template inline T IF_0_THEN_256(const T &n) { return static_cast(n - 1) + 1; } struct SNES_SPC { public: // Must be called once before using void init(); // Sample pairs generated per second enum { sample_rate = 32000 }; // Emulator use // 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(const uint8_t rom[rom_size]); // Sets destination for output samples typedef short sample_t; 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 }; // 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); // Sound control // 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); // If true, prevents channels and global volumes from being phase-negated. // Only supported by fast DSP. void disable_surround(bool disable = true ); // Sets tempo, where tempo_unit = normal, tempo_unit / 2 = half speed, etc. enum { tempo_unit = 0x100 }; void set_tempo(int); // SPC music files // 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. void play(int count, sample_t *out); // Skips count samples. Several times faster than play() when using fast DSP. void skip(int count); //// Snes9x Accessor void spc_allow_time_overflow(bool); void dsp_set_stereo_switch(int); uint8_t dsp_reg_value(int, int); int dsp_envx_value(int); public: // 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 { rel_time_t next_time; // time of next event int prescaler; int period; int divider; bool enabled; int counter; }; enum { reg_count = 0x10 }; enum { timer_count = 3 }; enum { extra_size = SPC_DSP::extra_size }; private: SPC_DSP dsp; 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; int tempo; int extra_clocks; sample_t *buf_begin; const sample_t *buf_end; sample_t *extra_pos; sample_t extra_buf[extra_size]; bool 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 }; 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(bool enable); void reset_buf(); void save_extra(); void load_regs(const uint8_t in[reg_count]); void ram_loaded(); void regs_loaded(); void reset_time_regs(); void reset_common(int timer_counter_init); Timer *run_timer_(Timer *t, rel_time_t); Timer *run_timer(Timer *t, rel_time_t); void RUN_DSP(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(const uint8_t *pc, rel_time_t); bool check_echo_access(int addr); uint8_t *run_until_(time_t end_time); // Snes9x timing hack bool allow_time_overflow; }; inline int SNES_SPC::sample_count() const { return (this->m.extra_clocks >> 5) * 2; } inline int SNES_SPC::read_port(time_t t, int port) { assert(static_cast(port) < port_count); return this->run_until_(t)[port]; } inline void SNES_SPC::write_port(time_t t, int port, int data) { assert(static_cast(port) < port_count); this->run_until_(t)[0x10 + port] = data; this->m.ram.ram[0xF4 + port] = data; } inline void SNES_SPC::mute_voices(int mask) { this->dsp.mute_voices(mask); } inline void SNES_SPC::disable_surround(bool disable) { this->dsp.disable_surround(disable); } inline void SNES_SPC::spc_allow_time_overflow(bool allow) { this->allow_time_overflow = allow; }