furnace/src/engine/platform/sound/es550x/es550x.hpp

392 lines
9.0 KiB
C++

/*
License: BSD-3-Clause
see https://github.com/cam900/vgsound_emu/LICENSE for more details
Copyright holder(s): cam900
Ensoniq ES5504/ES5505/ES5506 emulation core
See es550x.cpp for more info
*/
#include <algorithm>
#include <memory>
#ifndef _VGSOUND_EMU_ES550X_HPP
#define _VGSOUND_EMU_ES550X_HPP
#pragma once
namespace es550x
{
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef signed char s8;
typedef signed short s16;
typedef signed int s32;
// get bitfield, bitfield(input, position, len)
template<typename T> T bitfield(T in, u8 pos, u8 len = 1)
{
return (in >> pos) & (len ? (T(1 << len) - 1) : 1);
}
// get sign extended value, sign_ext<type>(input, len)
template<typename T> T sign_ext(T in, u8 len)
{
len = std::max<u8>(0, (8 * sizeof(T)) - len);
return T(T(in) << len) >> len;
}
// std::clamp is only for C++17 or later; I use my own code
template<typename T> T clamp(T in, T min, T max)
{
return (in > max) ? max : ((in < min) ? min : in);
}
template<typename T, T InitWidth, u8 InitEdge = 0>
struct clock_pulse_t
{
void reset(T init = InitWidth)
{
m_edge.reset();
m_width = m_width_latch = m_counter = init;
m_cycle = 0;
}
bool tick(T width = 0)
{
bool carry = ((--m_counter) <= 0);
if (carry)
{
if (!width)
m_width = m_width_latch;
else
m_width = width; // reset width
m_counter = m_width;
m_cycle = 0;
}
else
m_cycle++;
m_edge.tick(carry);
return carry;
}
void set_width(T width) { m_width = width; }
void set_width_latch(T width) { m_width_latch = width; }
// Accessors
bool current_edge() { return m_edge.m_current; }
bool rising_edge() { return m_edge.m_rising; }
bool falling_edge() { return m_edge.m_rising; }
T cycle() { return m_cycle; }
struct edge_t
{
edge_t()
: m_current(InitEdge ^ 1)
, m_previous(InitEdge)
, m_rising(0)
, m_falling(0)
, m_changed(0)
{
set(InitEdge);
}
void tick(bool toggle)
{
u8 current = m_current;
if (toggle)
current ^= 1;
set(current);
}
void set(u8 edge)
{
edge &= 1;
m_rising = m_falling = m_changed = 0;
if (m_current != edge)
{
m_changed = 1;
if (m_current && (!edge))
m_falling = 1;
else if ((!m_current) && edge)
m_rising = 1;
m_current = edge;
}
m_previous = m_current;
}
void reset()
{
m_previous = InitEdge;
m_current = InitEdge ^ 1;
set(InitEdge);
}
u8 m_current : 1; // current edge
u8 m_previous : 1; // previous edge
u8 m_rising : 1; // rising edge
u8 m_falling : 1; // falling edge
u8 m_changed : 1; // changed flag
};
edge_t m_edge;
T m_width = InitWidth; // clock pulse width
T m_width_latch = InitWidth; // clock pulse width latch
T m_counter = InitWidth; // clock counter
T m_cycle = 0; // clock cycle
};
};
// ES5504/ES5505/ES5506 interface
using namespace es550x;
class es550x_intf
{
public:
virtual void e(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
using namespace es550x;
class es550x_shared_core
{
friend class es550x_intf; // es550x specific memory interface
public:
// constructor
es550x_shared_core(es550x_intf &intf)
: m_intf(intf)
{ };
// internal state
virtual void reset();
virtual void tick() {}
// clock outputs
bool _cas() { return m_cas.current_edge(); }
bool _cas_rising_edge() { return m_cas.rising_edge(); }
bool _cas_falling_edge() { return m_cas.falling_edge(); }
bool e() { return m_e.current_edge(); }
bool e_rising_edge() { return m_e.rising_edge(); }
bool e_falling_edge() { return m_e.falling_edge(); }
protected:
// Constants
virtual inline u8 max_voices() { return 32; }
// Shared registers, functions
virtual void voice_tick() {} // voice tick
// Interrupt bits
struct es550x_irq_t
{
es550x_irq_t()
: voice(0)
, irqb(1)
{ };
void reset()
{
voice = 0;
irqb = 1;
}
void set(u8 index)
{
irqb = 0;
voice = index;
}
void clear()
{
irqb = 1;
voice = 0;
}
u8 voice : 5;
u8 irqb : 1;
};
// Common control bits
struct es550x_control_t
{
es550x_control_t()
: ca(0)
, adc(0)
, bs(0)
, cmpd(0)
{ };
void reset()
{
ca = 0;
adc = 0;
bs = 0;
cmpd = 0;
}
u8 ca : 4; // Channel assign (4 bit (16 channel or Bank) for ES5504, 2 bit (4 stereo channels) for ES5505, 3 bit (6 stereo channels) for ES5506)
// ES5504 Specific
u8 adc : 1; // Start ADC
// ES5505/ES5506 Specific
u8 bs : 2; // Bank bit (1 bit for ES5505, 2 bit for ES5506)
u8 cmpd : 1; // Use compressed sample format
};
// Accumulator
struct es550x_alu_t
{
es550x_alu_t(u8 integer, u8 fraction, bool transwave)
: m_integer(integer)
, m_fraction(fraction)
, m_total_bits(integer + fraction)
, m_transwave(transwave)
{}
const u8 m_integer;
const u8 m_fraction;
const u8 m_total_bits;
const bool m_transwave;
void reset();
bool busy();
bool tick();
void loop_exec();
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); }
struct es550x_alu_cr_t
{
es550x_alu_cr_t()
: stop0(0)
, stop1(0)
, lpe(0)
, ble(0)
, irqe(0)
, dir(0)
, irq(0)
, lei(0)
{ };
void reset()
{
stop0 = 0;
stop1 = 0;
lpe = 0;
ble = 0;
irqe = 0;
dir = 0;
irq = 0;
lei = 0;
}
u8 stop0 : 1; // Stop with ALU
u8 stop1 : 1; // Stop with processor
u8 lpe : 1; // Loop enable
u8 ble : 1; // Bidirectional loop enable
u8 irqe : 1; // IRQ enable
u8 dir : 1; // Playback direction
u8 irq : 1; // IRQ bit
u8 lei : 1; // Loop end ignore (ES5506 specific)
};
es550x_alu_cr_t m_cr;
u32 m_fc = 0; // Frequency - 6 integer, 9 fraction for ES5506/ES5505, 6 integer, 11 fraction for ES5506
u32 m_start = 0; // Start register
u32 m_end = 0; // End register
u32 m_accum = 0; // Accumulator - 20 integer, 9 fraction for ES5506/ES5505, 21 integer, 11 fraction for ES5506
s32 m_sample[2] = {0}; // Samples
};
// Filter
struct es550x_filter_t
{
void reset();
void tick(s32 in);
s32 lp_exec(s32 coeff, s32 in, s32 prev_out);
s32 hp_exec(s32 coeff, s32 in, s32 prev_out, s32 prev_in);
// Registers
u8 m_lp = 0; // Filter mode
// Filter coefficient registers
s32 m_k2 = 0; // Filter coefficient 2 - 12 bit for filter calculation, 4 LSBs are used for fine control of ramp increment for hardware envelope (ES5506)
s32 m_k1 = 0; // Filter coefficient 1
// Filter storage registers
s32 m_o1_1 = 0; // First stage
s32 m_o2_1 = 0; // Second stage
s32 m_o2_2 = 0; // Second stage HP
s32 m_o3_1 = 0; // Third stage
s32 m_o3_2 = 0; // Third stage HP
s32 m_o4_1 = 0; // Final stage
};
// Common voice struct
struct es550x_voice_t
{
es550x_voice_t(u8 integer, u8 fraction, bool transwave)
: m_alu(integer, fraction, transwave)
{}
// internal state
virtual void reset();
virtual void fetch(u8 voice, u8 cycle) = 0;
virtual void tick(u8 voice) = 0;
es550x_control_t m_cr;
es550x_alu_t m_alu;
es550x_filter_t m_filter;
};
// Host interfaces
struct host_interface_flag_t
{
host_interface_flag_t()
: m_host_access(0)
, m_host_access_strobe(0)
, m_rw(0)
, m_rw_strobe(0)
{}
void reset()
{
m_host_access = 0;
m_host_access_strobe = 0;
m_rw = 0;
m_rw_strobe = 0;
}
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
};
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 = max_voices() - 1; // 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
es550x_intf &m_intf; // es550x specific memory interface
clock_pulse_t<s8, 1, 0> m_clkin; // CLKIN clock
clock_pulse_t<s8, 2, 1> m_cas; // /CAS clock (CLKIN / 4), falling edge of CLKIN trigger this clock
clock_pulse_t<s8, 4, 0> m_e; // E clock (CLKIN / 8), falling edge of CLKIN trigger this clock
};
#endif