// SNES SPC-700 APU emulator
// snes_spc 0.9.0
#pragma once
#include "SPC_DSP.h"
#include "blargg_endian.h"
// (n ? n : 256)
template<typename T> inline T IF_0_THEN_256(const T &n) { return static_cast<uint8_t>(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<unsigned>(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<unsigned>(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; }