mirror of
https://github.com/ZDoom/gzdoom-gles.git
synced 2024-12-11 04:51:13 +00:00
283 lines
7.4 KiB
C++
283 lines
7.4 KiB
C++
// SNES SPC-700 APU emulator
|
|
|
|
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
|
|
#ifndef SNES_SPC_H
|
|
#define SNES_SPC_H
|
|
|
|
#include "Spc_Dsp.h"
|
|
#include "blargg_endian.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
struct Snes_Spc {
|
|
public:
|
|
// Must be called once before using
|
|
blargg_err_t 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( uint8_t const 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
|
|
|
|
// 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:
|
|
// TODO: document
|
|
struct regs_t
|
|
{
|
|
uint16_t pc;
|
|
uint8_t a;
|
|
uint8_t x;
|
|
uint8_t y;
|
|
uint8_t psw;
|
|
uint8_t sp;
|
|
};
|
|
regs_t& smp_regs() { return m.cpu_regs; }
|
|
|
|
uint8_t* smp_ram() { return m.ram.ram; }
|
|
|
|
void run_until( time_t t ) { run_until_( t ); }
|
|
public:
|
|
BLARGG_DISABLE_NOTHROW
|
|
|
|
// 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;
|
|
int enabled;
|
|
int counter;
|
|
};
|
|
enum { reg_count = 0x10 };
|
|
enum { timer_count = 3 };
|
|
enum { extra_size = Spc_Dsp::extra_size };
|
|
|
|
enum { signature_size = 35 };
|
|
|
|
private:
|
|
Spc_Dsp dsp;
|
|
|
|
#if SPC_LESS_ACCURATE
|
|
static signed char const reg_times_ [256];
|
|
signed char reg_times [256];
|
|
#endif
|
|
|
|
struct state_t
|
|
{
|
|
Timer timers [timer_count];
|
|
|
|
uint8_t smp_regs [2] [reg_count];
|
|
|
|
regs_t 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 -- but this is
|
|
// still undefined behavior! TODO: remove and instead properly
|
|
// guard usage of emulated memory
|
|
uint8_t padding1 [0x100];
|
|
alignas(uint16_t) uint8_t ram [0x10000 + 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( 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 );
|
|
|
|
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, uint16_t addr );
|
|
void cpu_write_smp_reg ( int data, rel_time_t, uint16_t addr );
|
|
void cpu_write_high ( int data, uint8_t i );
|
|
void cpu_write ( int data, uint16_t addr, rel_time_t );
|
|
int cpu_read_smp_reg ( int i, rel_time_t );
|
|
int cpu_read ( uint16_t addr, rel_time_t );
|
|
unsigned CPU_mem_bit ( uint16_t 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 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] );
|
|
};
|
|
|
|
#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::mute_voices( int mask ) { dsp.mute_voices( mask ); }
|
|
|
|
inline void Snes_Spc::disable_surround( bool disable ) { dsp.disable_surround( disable ); }
|
|
|
|
#if !SPC_NO_COPY_STATE_FUNCS
|
|
inline bool Snes_Spc::check_kon() { return dsp.check_kon(); }
|
|
#endif
|
|
|
|
#endif
|