386 lines
9.3 KiB
C++
386 lines
9.3 KiB
C++
/*
|
|
License: Zlib
|
|
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
|
|
|
|
Copyright holder(s): cam900
|
|
Ensoniq ES5506 emulation core
|
|
*/
|
|
|
|
#ifndef _VGSOUND_EMU_SRC_ES5506_HPP
|
|
#define _VGSOUND_EMU_SRC_ES5506_HPP
|
|
|
|
#pragma once
|
|
|
|
#include "es550x.hpp"
|
|
|
|
// ES5506 specific
|
|
class es5506_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("es5506_output")
|
|
, m_left(left)
|
|
, m_right(right)
|
|
{
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
m_left = 0;
|
|
m_right = 0;
|
|
};
|
|
|
|
inline s32 clamp20(s32 in) { return clamp(in, -0x80000, 0x7ffff); }
|
|
|
|
inline void clamp20(output_t src)
|
|
{
|
|
m_left = clamp20(src.left());
|
|
m_right = clamp20(src.right());
|
|
}
|
|
|
|
inline void clamp20()
|
|
{
|
|
m_left = clamp20(m_left);
|
|
m_right = clamp20(m_right);
|
|
}
|
|
|
|
// setters
|
|
inline void set_left(s32 left) { m_left = clamp20(left); }
|
|
|
|
inline void set_right(s32 right) { m_right = clamp20(right); }
|
|
|
|
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 u32 left() { return m_left; }
|
|
|
|
inline u32 right() { return m_right; }
|
|
|
|
output_t &operator+=(output_t &src)
|
|
{
|
|
m_left = clamp20(m_left + src.left());
|
|
m_right = clamp20(m_right + src.right());
|
|
return *this;
|
|
}
|
|
|
|
output_t &operator=(output_t src)
|
|
{
|
|
clamp20(src);
|
|
return *this;
|
|
}
|
|
|
|
output_t &operator=(s32 val)
|
|
{
|
|
m_left = m_right = clamp20(val);
|
|
return *this;
|
|
}
|
|
|
|
output_t &operator>>(s32 shift)
|
|
{
|
|
m_left >>= shift;
|
|
m_right >>= shift;
|
|
return *this;
|
|
}
|
|
|
|
private:
|
|
s32 m_left = 0;
|
|
s32 m_right = 0;
|
|
};
|
|
|
|
// es5506 voice classes
|
|
class voice_t : public es550x_voice_t
|
|
{
|
|
private:
|
|
// es5506 Filter ramp class
|
|
class filter_ramp_t : public vgsound_emu_core
|
|
{
|
|
public:
|
|
filter_ramp_t()
|
|
: vgsound_emu_core("es5506_filter_ramp")
|
|
, m_slow(0)
|
|
, m_ramp(0)
|
|
{
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
m_slow = 0;
|
|
m_ramp = 0;
|
|
};
|
|
|
|
// Setters
|
|
inline void write(u16 data)
|
|
{
|
|
m_slow = data & 1;
|
|
m_ramp = (data >> 8) & 0xff;
|
|
}
|
|
|
|
// Getters
|
|
inline bool slow() { return m_slow; }
|
|
|
|
inline u16 ramp() { return m_ramp; }
|
|
|
|
private:
|
|
u16 m_slow : 1; // Slow mode flag
|
|
u16 m_ramp = 8; // Ramp value
|
|
};
|
|
|
|
public:
|
|
// constructor
|
|
voice_t(es5506_core &host)
|
|
: es550x_voice_t("es5506_voice", 21, 11, true)
|
|
, m_host(host)
|
|
, m_lvol(0)
|
|
, m_rvol(0)
|
|
, m_lvramp(0)
|
|
, m_rvramp(0)
|
|
, m_ecount(0)
|
|
, m_k2ramp(filter_ramp_t())
|
|
, m_k1ramp(filter_ramp_t())
|
|
, m_filtcount(0)
|
|
, m_ch(output_t())
|
|
, m_mute(false)
|
|
{
|
|
}
|
|
|
|
// 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(s32 lvol) { m_lvol = lvol; }
|
|
|
|
inline void set_rvol(s32 rvol) { m_rvol = rvol; }
|
|
|
|
inline void set_lvramp(s32 lvramp) { m_lvramp = lvramp; }
|
|
|
|
inline void set_rvramp(s32 rvramp) { m_rvramp = rvramp; }
|
|
|
|
inline void set_ecount(s16 ecount) { m_ecount = ecount; }
|
|
|
|
// Getters
|
|
inline s32 lvol() { return m_lvol; }
|
|
|
|
inline s32 rvol() { return m_rvol; }
|
|
|
|
inline s32 lvramp() { return m_lvramp; }
|
|
|
|
inline s32 rvramp() { return m_rvramp; }
|
|
|
|
inline s16 ecount() { return m_ecount; }
|
|
|
|
inline filter_ramp_t &k2ramp() { return m_k2ramp; }
|
|
|
|
inline filter_ramp_t &k1ramp() { return m_k1ramp; }
|
|
|
|
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_ch.left(); }
|
|
|
|
inline s32 right_out() { return m_mute ? 0 : m_ch.right(); }
|
|
|
|
private:
|
|
// accessors, getters, setters
|
|
s16 decompress(u8 sample);
|
|
s32 volume_calc(u16 volume, s32 in);
|
|
|
|
// registers
|
|
es5506_core &m_host;
|
|
// Volume register: 4 bit exponent, 8 bit mantissa
|
|
// 4 LSBs are used for fine control of ramp increment for hardware envelope
|
|
s32 m_lvol = 0; // Left volume
|
|
s32 m_rvol = 0; // Right volume
|
|
// Envelope
|
|
s32 m_lvramp = 0; // Left volume ramp
|
|
s32 m_rvramp = 0; // Righr volume ramp
|
|
s16 m_ecount = 0; // Envelope counter
|
|
filter_ramp_t m_k2ramp; // Filter coefficient 2 Ramp
|
|
filter_ramp_t m_k1ramp; // Filter coefficient 1 Ramp
|
|
u8 m_filtcount = 0; // Internal counter for slow mode
|
|
output_t m_ch; // channel output
|
|
bool m_mute = false; // mute flag (for debug purpose)
|
|
};
|
|
|
|
// 5 bit mode
|
|
class mode_t : public vgsound_emu_core
|
|
{
|
|
public:
|
|
mode_t()
|
|
: vgsound_emu_core("es5506_mode")
|
|
, m_lrclk_en(1)
|
|
, m_wclk_en(1)
|
|
, m_bclk_en(1)
|
|
, m_master(0)
|
|
, m_dual(0)
|
|
{
|
|
}
|
|
|
|
// internal states
|
|
void reset()
|
|
{
|
|
m_lrclk_en = 1;
|
|
m_wclk_en = 1;
|
|
m_bclk_en = 1;
|
|
m_master = 0;
|
|
m_dual = 0;
|
|
}
|
|
|
|
// accessors
|
|
void write(u8 data)
|
|
{
|
|
m_lrclk_en = (data >> 0) & 1;
|
|
m_wclk_en = (data >> 1) & 1;
|
|
m_bclk_en = (data >> 2) & 1;
|
|
m_master = (data >> 3) & 1;
|
|
m_dual = (data >> 4) & 1;
|
|
}
|
|
|
|
// getters
|
|
bool lrclk_en() { return m_lrclk_en; }
|
|
|
|
bool wclk_en() { return m_wclk_en; }
|
|
|
|
bool bclk_en() { return m_bclk_en; }
|
|
|
|
bool master() { return m_master; }
|
|
|
|
bool dual() { return m_dual; }
|
|
|
|
private:
|
|
u8 m_lrclk_en : 1; // Set LRCLK to output
|
|
u8 m_wclk_en : 1; // Set WCLK to output
|
|
u8 m_bclk_en : 1; // Set BCLK to output
|
|
u8 m_master : 1; // Set memory mode to master
|
|
u8 m_dual : 1; // Set dual chip config
|
|
};
|
|
|
|
public:
|
|
// constructor
|
|
es5506_core(es550x_intf &intf)
|
|
: es550x_shared_core("es5506", 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_read_latch(0)
|
|
, m_write_latch(0)
|
|
, m_w_st(0)
|
|
, m_w_end(0)
|
|
, m_lr_end(0)
|
|
, m_mode(mode_t())
|
|
, m_w_st_curr(0)
|
|
, m_w_end_curr(0)
|
|
, m_bclk(clock_pulse_t<s8>(4, 0))
|
|
, m_lrclk(clock_pulse_t<s8>(32, 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
|
|
u8 host_r(u8 address);
|
|
void host_w(u8 address, u8 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(); }
|
|
|
|
// 6 stereo output channels
|
|
inline s32 lout(u8 ch) { return m_output[std::min<u8>(5, ch & 0x7)].left(); }
|
|
|
|
inline s32 rout(u8 ch) { return m_output[std::min<u8>(5, ch & 0x7)].right(); }
|
|
|
|
//-----------------------------------------------------------------
|
|
//
|
|
// for preview/debug purpose only, not for serious emulators
|
|
//
|
|
//-----------------------------------------------------------------
|
|
|
|
// bypass chips host interface for debug purpose only
|
|
u8 read(u8 address, bool cpu_access = false);
|
|
void write(u8 address, u8 data, bool cpu_access = false);
|
|
|
|
u32 regs_r(u8 page, u8 address, bool cpu_access = false);
|
|
void regs_w(u8 page, u8 address, u32 data, bool cpu_access = false);
|
|
|
|
u8 regs8_r(u8 page, u8 address)
|
|
{
|
|
u8 prev = m_page;
|
|
m_page = page;
|
|
u8 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
|
|
|
|
// Host interfaces
|
|
u32 m_read_latch = 0; // 32 bit register latch for host read
|
|
u32 m_write_latch = 0; // 32 bit register latch for host write
|
|
|
|
// Serial register
|
|
u8 m_w_st = 0; // Word clock start register
|
|
u8 m_w_end = 0; // Word clock end register
|
|
u8 m_lr_end = 0; // Left/Right clock end register
|
|
mode_t m_mode; // Global mode
|
|
|
|
// Serial related stuffs
|
|
u8 m_w_st_curr = 0; // Word clock start, current status
|
|
u8 m_w_end_curr = 0; // Word clock end 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, 6> m_ch; // 6 stereo output channels
|
|
std::array<output_t, 6> m_output; // Serial outputs
|
|
std::array<output_t, 6> m_output_temp; // temporary signal for serial output
|
|
std::array<output_t, 6> m_output_latch; // output latch
|
|
};
|
|
|
|
#endif
|