312 lines
7.1 KiB
C++
312 lines
7.1 KiB
C++
/*
|
|
License: Zlib
|
|
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
|
|
|
|
Copyright holder(s): cam900
|
|
Ensoniq ES5504 emulation core
|
|
*/
|
|
|
|
#ifndef _VGSOUND_EMU_SRC_ES5505_HPP
|
|
#define _VGSOUND_EMU_SRC_ES5505_HPP
|
|
|
|
#pragma once
|
|
|
|
#include "es550x.hpp"
|
|
|
|
// ES5505 specific
|
|
class es5505_core : public es550x_shared_core
|
|
{
|
|
private:
|
|
class output_t : public vgsound_emu_core
|
|
{
|
|
public:
|
|
output_t(s32 left = 0, s32 right = 0)
|
|
: vgsound_emu_core("es5505_output")
|
|
, m_left(left)
|
|
, m_right(right)
|
|
{
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
m_left = 0;
|
|
m_right = 0;
|
|
}
|
|
|
|
inline void copy_output(output_t &src)
|
|
{
|
|
m_left = src.left();
|
|
m_right = src.right();
|
|
}
|
|
|
|
inline s32 clamp16(s32 in) { return clamp(in, -0x8000, 0x7fff); }
|
|
|
|
inline void clamp16(output_t &src)
|
|
{
|
|
m_left = clamp16(src.left());
|
|
m_right = clamp16(src.right());
|
|
}
|
|
|
|
inline void clamp16()
|
|
{
|
|
m_left = clamp16(m_left);
|
|
m_right = clamp16(m_right);
|
|
}
|
|
|
|
// setters
|
|
inline void set_left(s32 left) { m_left = left; }
|
|
|
|
inline void set_right(s32 right) { m_right = right; }
|
|
|
|
inline void serial_in(bool ch, u8 in)
|
|
{
|
|
if (ch) // Right output
|
|
{
|
|
m_right = (m_right << 1) | (in ? 1 : 0);
|
|
}
|
|
else // Left output
|
|
{
|
|
m_left = (m_left << 1) | (in ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
// getters
|
|
inline s32 left() { return m_left; }
|
|
|
|
inline s32 right() { return m_right; }
|
|
|
|
output_t &operator+=(output_t &src)
|
|
{
|
|
m_left = clamp16(m_left + src.left());
|
|
m_right = clamp16(m_right + src.right());
|
|
return *this;
|
|
}
|
|
|
|
output_t &operator>>(s32 shift)
|
|
{
|
|
m_left >>= shift;
|
|
m_right >>= shift;
|
|
return *this;
|
|
}
|
|
|
|
private:
|
|
s32 m_left = 0;
|
|
s32 m_right = 0;
|
|
};
|
|
|
|
// es5505 voice classes
|
|
class voice_t : public es550x_voice_t
|
|
{
|
|
public:
|
|
// constructor
|
|
voice_t(es5505_core &host)
|
|
: es550x_voice_t("es5505_voice", 20, 9, false)
|
|
, m_host(host)
|
|
, m_lvol(0)
|
|
, m_rvol(0)
|
|
, m_ch(output_t())
|
|
, m_mute(false)
|
|
, m_output{0}
|
|
{
|
|
}
|
|
|
|
// internal state
|
|
virtual void reset() override;
|
|
virtual void fetch(u8 voice, u8 cycle) override;
|
|
virtual void tick(u8 voice) override;
|
|
|
|
// setters
|
|
inline void set_lvol(u8 lvol) { m_lvol = lvol; }
|
|
|
|
inline void set_rvol(u8 rvol) { m_rvol = rvol; }
|
|
|
|
// getters
|
|
inline u8 lvol() { return m_lvol; }
|
|
|
|
inline u8 rvol() { return m_rvol; }
|
|
|
|
output_t &ch() { return m_ch; }
|
|
|
|
// for debug/preview only
|
|
inline void set_mute(bool mute) { m_mute = mute; }
|
|
|
|
inline s32 left_out() { return m_mute ? 0 : m_output[0]; }
|
|
|
|
inline s32 right_out() { return m_mute ? 0 : m_output[1]; }
|
|
|
|
private:
|
|
s32 volume_calc(u8 volume, s32 in);
|
|
|
|
// registers
|
|
es5505_core &m_host;
|
|
u8 m_lvol = 0; // Left volume
|
|
u8 m_rvol = 0; // Right volume
|
|
output_t m_ch; // channel output
|
|
bool m_mute = false; // mute flag (for debug purpose)
|
|
std::array<s32, 2> m_output; // output preview (for debug purpose)
|
|
};
|
|
|
|
class sermode_t : public vgsound_emu_core
|
|
{
|
|
public:
|
|
sermode_t()
|
|
: vgsound_emu_core("es5505_sermode")
|
|
, m_adc(0)
|
|
, m_test(0)
|
|
, m_sony_bb(0)
|
|
, m_msb(0)
|
|
{
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
m_adc = 0;
|
|
m_test = 0;
|
|
m_sony_bb = 0;
|
|
m_msb = 0;
|
|
}
|
|
|
|
// setters
|
|
void write(u16 data)
|
|
{
|
|
m_adc = (data >> 0) & 1;
|
|
m_test = (data >> 1) & 1;
|
|
m_sony_bb = (data >> 2) & 1;
|
|
m_msb = (data >> 11) & 0x1f;
|
|
}
|
|
|
|
void set_adc(bool adc) { m_adc = adc ? 1 : 0; }
|
|
|
|
void set_test(bool test) { m_test = test ? 1 : 0; }
|
|
|
|
void set_sony_bb(bool sony_bb) { m_sony_bb = sony_bb ? 1 : 0; }
|
|
|
|
void set_msb(u8 msb) { m_msb = msb & 0x1f; }
|
|
|
|
// getters
|
|
bool adc() { return m_adc; }
|
|
|
|
bool test() { return m_test; }
|
|
|
|
bool sony_bb() { return m_sony_bb; }
|
|
|
|
u8 msb() { return m_msb; }
|
|
|
|
private:
|
|
u8 m_adc : 1; // A/D
|
|
u8 m_test : 1; // Test
|
|
u8 m_sony_bb : 1; // Sony/BB format serial output
|
|
u8 m_msb : 5; // Serial output MSB
|
|
};
|
|
|
|
public:
|
|
// constructor
|
|
es5505_core(es550x_intf &intf)
|
|
: es550x_shared_core("es5505", 32, intf)
|
|
, m_voice{*this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this,
|
|
*this, *this, *this, *this, *this, *this, *this, *this, *this, *this, *this,
|
|
*this, *this, *this, *this, *this, *this, *this, *this, *this, *this}
|
|
, m_sermode(sermode_t())
|
|
, m_bclk(clock_pulse_t<s8>(4, 0))
|
|
, m_lrclk(clock_pulse_t<s8>(16, 1))
|
|
, m_wclk(0)
|
|
, m_wclk_lr(false)
|
|
, m_output_bit(0)
|
|
, m_ch{output_t()}
|
|
, m_output{output_t()}
|
|
, m_output_temp{output_t()}
|
|
, m_output_latch{output_t()}
|
|
{
|
|
}
|
|
|
|
// host interface
|
|
u16 host_r(u8 address);
|
|
void host_w(u8 address, u16 data);
|
|
|
|
// internal state
|
|
virtual void reset() override;
|
|
virtual void tick() override;
|
|
|
|
// less cycle accurate, but also less cpu heavy update routine
|
|
void tick_perf();
|
|
|
|
// clock outputs
|
|
inline bool bclk() { return m_bclk.current_edge(); }
|
|
|
|
inline bool bclk_rising_edge() { return m_bclk.rising_edge(); }
|
|
|
|
inline bool bclk_falling_edge() { return m_bclk.falling_edge(); }
|
|
|
|
// Input mode for Channel 3
|
|
inline void lin(s32 in)
|
|
{
|
|
if (m_sermode.adc())
|
|
{
|
|
m_ch[3].set_left(in);
|
|
}
|
|
}
|
|
|
|
inline void rin(s32 in)
|
|
{
|
|
if (m_sermode.adc())
|
|
{
|
|
m_ch[3].set_right(in);
|
|
}
|
|
}
|
|
|
|
// 4 stereo output channels
|
|
inline s32 lout(u8 ch) { return m_ch[ch & 0x3].left(); }
|
|
|
|
inline s32 rout(u8 ch) { return m_ch[ch & 0x3].right(); }
|
|
|
|
//-----------------------------------------------------------------
|
|
//
|
|
// for preview/debug purpose only, not for serious emulators
|
|
//
|
|
//-----------------------------------------------------------------
|
|
|
|
// bypass chips host interface for debug purpose only
|
|
u16 read(u8 address, bool cpu_access = false);
|
|
void write(u8 address, u16 data);
|
|
|
|
u16 regs_r(u8 page, u8 address, bool cpu_access = false);
|
|
void regs_w(u8 page, u8 address, u16 data);
|
|
|
|
u16 regs_r(u8 page, u8 address)
|
|
{
|
|
u8 prev = m_page;
|
|
m_page = page;
|
|
u16 ret = read(address, false);
|
|
m_page = prev;
|
|
return ret;
|
|
}
|
|
|
|
inline void set_mute(u8 ch, bool mute) { m_voice[ch & 0x1f].set_mute(mute); }
|
|
|
|
// per-voice outputs
|
|
inline s32 voice_lout(u8 voice) { return (voice < 32) ? m_voice[voice].left_out() : 0; }
|
|
|
|
inline s32 voice_rout(u8 voice) { return (voice < 32) ? m_voice[voice].right_out() : 0; }
|
|
|
|
protected:
|
|
virtual inline u8 max_voices() override { return 32; }
|
|
|
|
virtual void voice_tick() override;
|
|
|
|
private:
|
|
std::array<voice_t, 32> m_voice; // 32 voices
|
|
// Serial related stuffs
|
|
sermode_t m_sermode; // Serial mode register
|
|
clock_pulse_t<s8> m_bclk; // BCLK clock (CLKIN / 4), freely running clock
|
|
clock_pulse_t<s8> m_lrclk; // LRCLK
|
|
s16 m_wclk = 0; // WCLK
|
|
bool m_wclk_lr = false; // WCLK, L/R output select
|
|
s8 m_output_bit = 0; // Bit position in output
|
|
std::array<output_t, 4> m_ch; // 4 stereo output channels
|
|
std::array<output_t, 4> m_output; // Serial outputs
|
|
std::array<output_t, 4> m_output_temp; // temporary signal for serial output
|
|
std::array<output_t, 4> m_output_latch; // output latch
|
|
};
|
|
|
|
#endif
|