furnace/extern/vgsound_emu-modified/vgsound_emu/src/es550x/es550x.hpp

611 lines
15 KiB
C++

/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504/ES5505/ES5506 emulation core
*/
#ifndef _VGSOUND_EMU_SRC_ES550X_HPP
#define _VGSOUND_EMU_SRC_ES550X_HPP
#pragma once
#include "../core/util.hpp"
// ES5504/ES5505/ES5506 interface
class es550x_intf : public vgsound_emu_core
{
public:
es550x_intf()
: vgsound_emu_core("es550x_intf")
{
}
virtual void e_pin(bool state) {} // E output
virtual void bclk(bool state) {} // BCLK output (serial specific)
virtual void lrclk(bool state) {} // LRCLK output (serial specific)
virtual void wclk(bool state) {} // WCLK output (serial specific)
virtual void irqb(bool state) {} // irqb output
virtual u16 adc_r() { return 0; } // ADC input
virtual void adc_w(u16 data) {} // ADC output
virtual s16 read_sample(u8 voice, u8 bank, u32 address) { return 0; }
};
// Shared functions for ES5504/ES5505/ES5506
class es550x_shared_core : public vgsound_emu_core
{
friend class es550x_intf; // es550x specific memory interface
private:
const u8 m_max_voices = 32;
protected:
// Interrupt bits
class es550x_irq_t : public vgsound_emu_core
{
public:
es550x_irq_t()
: vgsound_emu_core("es550x_irq")
, m_voice(0)
, m_irqb(1)
{
}
void reset()
{
m_voice = 0;
m_irqb = 1;
}
// setter
void set(u8 index)
{
m_irqb = 0;
m_voice = index & 0x1f;
}
void clear()
{
m_irqb = 1;
m_voice = 0;
}
// getter
inline bool irqb() { return m_irqb; }
inline u8 voice() { return m_voice; }
inline u8 get() { return (m_irqb << 7) | (m_voice & 0x1f); }
u8 m_voice : 5;
u8 m_irqb : 1;
};
// Common voice class
class es550x_voice_t : public vgsound_emu_core
{
private:
// Common control bits
class es550x_control_t : public vgsound_emu_core
{
public:
es550x_control_t()
: vgsound_emu_core("es550x_voice_control")
, m_ca(0)
, m_adc(0)
, m_bs(0)
, m_cmpd(0)
{
}
void reset()
{
m_ca = 0;
m_adc = 0;
m_bs = 0;
m_cmpd = 0;
}
// setters
inline void set_ca(u8 ca) { m_ca = ca & 0xf; }
inline void set_adc(bool adc) { m_adc = adc ? 1 : 0; }
inline void set_bs(u8 bs) { m_bs = bs & 0x3; }
inline void set_cmpd(bool cmpd) { m_cmpd = cmpd ? 1 : 0; }
// getters
inline u8 ca() { return m_ca; }
inline bool adc() { return m_adc; }
inline u8 bs() { return m_bs; }
inline bool cmpd() { return m_cmpd; }
protected:
// Channel assign -
// 4 bit (16 channel or Bank) for ES5504
// 2 bit (4 stereo channels) for ES5505
// 3 bit (6 stereo channels) for ES5506
u8 m_ca : 4;
// ES5504 Specific
u8 m_adc : 1; // Start ADC
// ES5505/ES5506 Specific
u8 m_bs : 2; // Bank bit (1 bit for ES5505, 2 bit for ES5506)
u8 m_cmpd : 1; // Use compressed sample format (ES5506)
};
// Accumulator
class es550x_alu_t : public vgsound_emu_core
{
public:
es550x_alu_t(u8 integer, u8 fraction, bool transwave)
: vgsound_emu_core("es550x_voice_alu")
, m_integer(integer)
, m_fraction(fraction)
, m_total_bits(integer + fraction)
, m_accum_mask(
u32(std::min<u64>(~0, u64(u64(1) << u64(integer + fraction)) - 1)))
, m_transwave(transwave)
, m_fc(0)
, m_start(0)
, m_end(0)
, m_accum(0)
, m_sample({0})
{
}
// configurations
const u8 m_integer;
const u8 m_fraction;
const u8 m_total_bits;
const u32 m_accum_mask;
const bool m_transwave;
// internal states
void reset();
bool tick();
void loop_exec();
bool busy();
s32 interpolation();
u32 get_accum_integer();
void irq_exec(es550x_intf &intf, es550x_irq_t &irqv, u8 index);
void irq_update(es550x_intf &intf, es550x_irq_t &irqv)
{
intf.irqb(irqv.irqb() ? false : true);
}
// setters
inline void set_stop0(bool stop0) { m_cr.set_stop0(stop0); }
inline void set_stop1(bool stop1) { m_cr.set_stop1(stop1); }
inline void set_lpe(bool lpe) { m_cr.set_lpe(lpe); }
inline void set_ble(bool ble) { m_cr.set_ble(ble); }
inline void set_irqe(bool irqe) { m_cr.set_irqe(irqe); }
inline void set_dir(bool dir) { m_cr.set_dir(dir); }
inline void set_irq(bool irq) { m_cr.set_irq(irq); }
inline void set_lei(bool lei) { m_cr.set_lei(lei); }
inline void set_stop(u8 stop) { m_cr.set_stop(stop); }
inline void set_loop(u8 loop) { m_cr.set_loop(loop); }
inline void set_fc(u32 fc) { m_fc = fc; }
inline void set_start(u32 start, u32 mask = ~0)
{
m_start = (m_start & ~mask) | (start & mask);
}
inline void set_end(u32 end, u32 mask = ~0)
{
m_end = (m_end & ~mask) | (end & mask);
}
inline void set_accum(u32 accum, u32 mask = ~0)
{
m_accum = (m_accum & ~mask) | (accum & mask);
}
inline void set_sample(u8 slot, s32 sample) { m_sample[slot & 1] = sample; }
// getters
inline bool stop0() { return m_cr.stop0(); }
inline bool stop1() { return m_cr.stop1(); }
inline bool lpe() { return m_cr.lpe(); }
inline bool ble() { return m_cr.ble(); }
inline bool irqe() { return m_cr.irqe(); }
inline bool dir() { return m_cr.dir(); }
inline bool irq() { return m_cr.irq(); }
inline bool lei() { return m_cr.lei(); }
inline u8 stop() { return m_cr.stop(); }
inline u8 loop() { return m_cr.loop(); }
inline u32 fc() { return m_fc; }
inline u32 start() { return m_start; }
inline u32 end() { return m_end; }
inline u32 accum() { return m_accum; }
inline s32 sample(u8 slot) { return m_sample[slot & 1]; }
private:
class es550x_alu_cr_t : public vgsound_emu_core
{
public:
es550x_alu_cr_t()
: vgsound_emu_core("es550x_voice_alu_cr")
, m_stop0(0)
, m_stop1(0)
, m_lpe(0)
, m_ble(0)
, m_irqe(0)
, m_dir(0)
, m_irq(0)
, m_lei(0)
{
}
void reset()
{
m_stop0 = 0;
m_stop1 = 0;
m_lpe = 0;
m_ble = 0;
m_irqe = 0;
m_dir = 0;
m_irq = 0;
m_lei = 0;
}
// setters
inline void set_stop0(bool stop0) { m_stop0 = stop0 ? 1 : 0; }
inline void set_stop1(bool stop1) { m_stop1 = stop1 ? 1 : 0; }
inline void set_lpe(bool lpe) { m_lpe = lpe ? 1 : 0; }
inline void set_ble(bool ble) { m_ble = ble ? 1 : 0; }
inline void set_irqe(bool irqe) { m_irqe = irqe ? 1 : 0; }
inline void set_dir(bool dir) { m_dir = dir ? 1 : 0; }
inline void set_irq(bool irq) { m_irq = irq ? 1 : 0; }
inline void set_lei(bool lei) { m_lei = lei ? 1 : 0; }
inline void set_stop(u8 stop)
{
m_stop0 = (stop >> 0) & 1;
m_stop1 = (stop >> 1) & 1;
}
inline void set_loop(u8 loop)
{
m_lpe = (loop >> 0) & 1;
m_ble = (loop >> 1) & 1;
}
// getters
inline bool stop0() { return m_stop0; }
inline bool stop1() { return m_stop1; }
inline bool lpe() { return m_lpe; }
inline bool ble() { return m_ble; }
inline bool irqe() { return m_irqe; }
inline bool dir() { return m_dir; }
inline bool irq() { return m_irq; }
inline bool lei() { return m_lei; }
inline u8 stop() { return (m_stop0 << 0) | (m_stop1 << 1); }
inline u8 loop() { return (m_lpe << 0) | (m_ble << 1); }
private:
u8 m_stop0 : 1; // Stop with ALU
u8 m_stop1 : 1; // Stop with processor
u8 m_lpe : 1; // Loop enable
u8 m_ble : 1; // Bidirectional loop enable
u8 m_irqe : 1; // IRQ enable
u8 m_dir : 1; // Playback direction
u8 m_irq : 1; // IRQ bit
u8 m_lei : 1; // Loop end ignore (ES5506 specific)
};
es550x_alu_cr_t m_cr;
// Frequency -
// 6 integer, 9 fraction for ES5504/ES5505
// 6 integer, 11 fraction for ES5506
u32 m_fc = 0;
u32 m_start = 0; // Start register
u32 m_end = 0; // End register
// Accumulator -
// 20 integer, 9 fraction for ES5504/ES5505
// 21 integer, 11 fraction for ES5506
u32 m_accum = 0;
// Samples
std::array<s32, 2> m_sample = {0};
};
// Filter
class es550x_filter_t : public vgsound_emu_core
{
public:
es550x_filter_t()
: vgsound_emu_core("es550x_voice_filter")
, m_lp(0)
, m_k2(0)
, m_k1(0)
{
for (std::array<s32, 2> &elem : m_o)
{
std::fill(elem.begin(), elem.end(), 0);
}
}
void reset();
void tick(s32 in);
// setters
inline void set_lp(u8 lp) { m_lp = lp & 3; }
inline void set_k2(s32 k2) { m_k2 = k2; }
inline void set_k1(s32 k1) { m_k1 = k1; }
inline void set_o1_1(s32 o1_1) { m_o[1][0] = o1_1; }
inline void set_o2_1(s32 o2_1) { m_o[2][0] = o2_1; }
inline void set_o2_2(s32 o2_2) { m_o[2][1] = o2_2; }
inline void set_o3_1(s32 o3_1) { m_o[3][0] = o3_1; }
inline void set_o3_2(s32 o3_2) { m_o[3][1] = o3_2; }
inline void set_o4_1(s32 o4_1) { m_o[4][0] = o4_1; }
// getters
inline u8 lp() { return m_lp; }
inline s32 k2() { return m_k2; }
inline s32 k1() { return m_k1; }
inline s32 o1_1() { return m_o[1][0]; }
inline s32 o2_1() { return m_o[2][0]; }
inline s32 o2_2() { return m_o[2][1]; }
inline s32 o3_1() { return m_o[3][0]; }
inline s32 o3_2() { return m_o[3][1]; }
inline s32 o4_1() { return m_o[4][0]; }
private:
void lp_exec(s32 coeff, s32 in, s32 out);
void hp_exec(s32 coeff, s32 in, s32 out);
// Registers
u8 m_lp = 0; // Filter mode
// Filter coefficient registers
// 12 bit for filter calculation, 4
// LSBs are used for fine control of ramp increment for
// hardware envelope (ES5506)
s32 m_k2 = 0; // Filter coefficient 2
s32 m_k1 = 0; // Filter coefficient 1
// Filter storage registers
std::array<std::array<s32, 2>, 5> m_o;
};
public:
es550x_voice_t(std::string tag, u8 integer, u8 fraction, bool transwave)
: vgsound_emu_core(tag)
, m_cr(es550x_control_t())
, m_alu(integer, fraction, transwave)
, m_filter(es550x_filter_t())
{
}
// internal state
virtual void reset();
virtual void fetch(u8 voice, u8 cycle) = 0;
virtual void tick(u8 voice) = 0;
void irq_update(es550x_intf &intf, es550x_irq_t &irqv)
{
m_alu.irq_update(intf, irqv);
}
// Getters
es550x_control_t &cr() { return m_cr; }
es550x_alu_t &alu() { return m_alu; }
es550x_filter_t &filter() { return m_filter; }
protected:
es550x_control_t m_cr;
es550x_alu_t m_alu;
es550x_filter_t m_filter;
};
// Host interfaces
class host_interface_flag_t : public vgsound_emu_core
{
public:
host_interface_flag_t()
: vgsound_emu_core("es550x_host_interface_flag")
, m_host_access(0)
, m_host_access_strobe(0)
, m_rw(0)
, m_rw_strobe(0)
{
}
// Accessors
void reset()
{
m_host_access = 0;
m_host_access_strobe = 0;
m_rw = 0;
m_rw_strobe = 0;
}
// Setters
void set_strobe(bool rw)
{
m_rw_strobe = rw ? 1 : 0;
m_host_access_strobe = 1;
}
void clear_strobe() { m_host_access_strobe = 0; }
void clear_host_access() { m_host_access = 0; }
void update_strobe()
{
m_rw = m_rw_strobe;
m_host_access = m_host_access_strobe;
}
// Getters
bool host_access() { return m_host_access; }
bool rw() { return m_rw; }
private:
u8 m_host_access : 1; // Host access trigger
u8 m_host_access_strobe : 1; // Host access strobe
u8 m_rw : 1; // R/W state
u8 m_rw_strobe : 1; // R/W strobe
};
public:
// internal state
virtual void reset();
virtual void tick() {}
// clock outputs
inline bool _cas() { return m_cas.current_edge(); }
inline bool _cas_rising_edge() { return m_cas.rising_edge(); }
inline bool _cas_falling_edge() { return m_cas.falling_edge(); }
inline bool e() { return m_e.current_edge(); }
inline bool e_rising_edge() { return m_e.rising_edge(); }
inline bool e_falling_edge() { return m_e.falling_edge(); }
//-----------------------------------------------------------------
//
// for preview/debug purpose only, not for serious emulators
//
//-----------------------------------------------------------------
// voice cycle
inline u8 voice_cycle() { return m_voice_cycle; }
// voice update flag
inline bool voice_update() { return m_voice_update; }
inline bool voice_end() { return m_voice_end; }
protected:
// constructor
es550x_shared_core(std::string tag, const u8 voice, es550x_intf &intf)
: vgsound_emu_core(tag)
, m_max_voices(voice)
, m_intf(intf)
, m_host_intf(host_interface_flag_t())
, m_ha(0)
, m_hd(0)
, m_page(0)
, m_irqv(es550x_irq_t())
, m_active(m_max_voices - 1)
, m_voice_cycle(0)
, m_voice_fetch(0)
, m_voice_update(0)
, m_voice_end(0)
, m_clkin(clock_pulse_t<s8>(1, 0))
, m_cas(clock_pulse_t<s8>(2, 1))
, m_e(clock_pulse_t<s8>(4, 0))
{
}
// Constants
virtual inline u8 max_voices() { return m_max_voices; }
// Shared registers, functions
virtual void voice_tick() {} // voice tick
es550x_intf &m_intf; // es550x specific memory interface
host_interface_flag_t m_host_intf; // Host interface flag
u8 m_ha = 0; // Host address (4 bit)
u16 m_hd = 0; // Host data (16 bit for ES5504/ES5505, 8 bit for ES5506)
u8 m_page = 0; // Page
es550x_irq_t m_irqv; // Voice interrupt vector registers
// Internal states
u8 m_active = 31; // Activated voices
// -1, ~25 for ES5504,
// ~32 for ES5505/ES5506
u8 m_voice_cycle = 0; // Voice cycle
u8 m_voice_fetch = 0; // Voice fetch cycle
bool m_voice_update = false; // Voice update flag
bool m_voice_end = false; // End of one voice cycle flag
clock_pulse_t<s8> m_clkin; // CLKIN clock
clock_pulse_t<s8> m_cas; // /CAS clock (CLKIN / 4), falling edge of
// CLKIN trigger this clock
clock_pulse_t<s8> m_e; // E clock (CLKIN / 8),
// falling edge of CLKIN trigger this clock
};
#endif