furnace/extern/vgsound_emu-modified/vgsound_emu/src/vrcvi/vrcvi.hpp
2023-02-11 22:49:07 -05:00

409 lines
8.1 KiB
C++

/*
License: Zlib
see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details
Copyright holder(s): cam900
Konami VRC VI sound emulation core
*/
#ifndef _VGSOUND_EMU_SRC_VRCVI_HPP
#define _VGSOUND_EMU_SRC_VRCVI_HPP
#pragma once
#include "../core/core.hpp"
using namespace vgsound_emu;
class vrcvi_intf : public vgsound_emu_core
{
public:
vrcvi_intf()
: vgsound_emu_core("vrc_vi_intf")
{
}
virtual void irq_w(bool irq) {}
};
class vrcvi_core : public vgsound_emu_core
{
friend class vrcvi_intf;
private:
// Common ALU for sound channels
class alu_t : public vgsound_emu_core
{
private:
class divider_t : public vgsound_emu_core
{
public:
divider_t()
: vgsound_emu_core("vrc_vi_frequency_divider")
, m_divider(0)
, m_enable(0)
{
}
void reset()
{
m_divider = 0;
m_enable = 0;
}
void write(bool msb, u8 data);
// getters
inline u16 divider() { return m_divider; }
inline bool enable() { return m_enable; }
public:
u16 m_divider : 12; // divider (pitch)
u16 m_enable : 1; // channel disable flag
};
public:
alu_t(std::string tag, vrcvi_core &host)
: vgsound_emu_core(tag)
, m_host(host)
, m_divider(divider_t())
, m_counter(0)
, m_cycle(0)
, m_out(0)
{
}
virtual void reset();
virtual bool tick();
virtual s8 get_output()
{
m_out = 0;
return 0;
}
// accessors
inline void clear_cycle() { m_cycle = 0; }
// getters
divider_t &divider() { return m_divider; }
inline u16 counter() { return m_counter; }
inline u8 cycle() { return m_cycle; }
// for previwe/debug only
inline s8 out() { return m_out; }
protected:
vrcvi_core &m_host;
divider_t m_divider;
u16 m_counter = 0; // clock counter
u8 m_cycle = 0; // clock cycle
s8 m_out = 0; // output per channel
};
// 2 Pulse channels
class pulse_t : public alu_t
{
private:
// Control bits
class pulse_control_t
{
public:
pulse_control_t()
: m_mode(0)
, m_duty(0)
, m_volume(0)
{
}
void reset()
{
m_mode = 0;
m_duty = 0;
m_volume = 0;
}
// accessors
inline void write(u8 data)
{
m_mode = (data >> 7) & 0x1;
m_duty = (data >> 4) & 0x7;
m_volume = (data >> 0) & 0xf;
}
// getters
inline bool mode() { return m_mode; }
inline u8 duty() { return m_duty; }
inline u8 volume() { return m_volume; }
public:
u8 m_mode : 1; // duty toggle flag
u8 m_duty : 3; // 3 bit duty cycle
u8 m_volume : 4; // 4 bit volume
};
public:
pulse_t(vrcvi_core &host)
: alu_t("vrc_vi_pulse", host)
, m_control(pulse_control_t())
{
}
virtual void reset() override;
virtual bool tick() override;
virtual s8 get_output() override;
// getters
pulse_control_t &control() { return m_control; }
private:
pulse_control_t m_control;
};
// 1 Sawtooth channel
class sawtooth_t : public alu_t
{
public:
sawtooth_t(vrcvi_core &host)
: alu_t("vrc_vi_sawtooth", host)
, m_rate(0)
, m_accum(0)
{
}
virtual void reset() override;
virtual bool tick() override;
virtual s8 get_output() override;
// accessors
inline void clear_accum() { m_accum = 0; }
// setters
inline void set_rate(u8 rate) { m_rate = rate; }
// getters
inline u8 rate() { return m_rate; }
inline u8 accum() { return m_accum; }
private:
u8 m_rate = 0; // sawtooth accumulate rate
u8 m_accum = 0; // sawtooth accumulator, high 5 bit is accumulated to output
};
// Internal timer
class timer_t : public vgsound_emu_core
{
private:
// Control bits
class timer_control_t : public vgsound_emu_core
{
public:
timer_control_t()
: vgsound_emu_core("vrc_vi_timer_control")
, m_irq_trigger(0)
, m_enable_ack(0)
, m_enable(0)
, m_sync(0)
{
}
void reset()
{
m_irq_trigger = 0;
m_enable_ack = 0;
m_enable = 0;
m_sync = 0;
}
// accessors
inline void irq_set(bool irq) { m_irq_trigger = irq ? 1 : 0; }
// setters
inline void set_enable_ack(bool enable_ack)
{
m_enable_ack = enable_ack ? 1 : 0;
}
inline void set_enable(bool enable) { m_enable = enable ? 1 : 0; }
inline void set_sync(bool sync) { m_sync = sync ? 1 : 0; }
// getters
inline bool irq_trigger() { return m_irq_trigger; }
inline bool enable_ack() { return m_enable_ack; }
inline bool enable() { return m_enable; }
inline bool sync() { return m_sync; }
private:
u8 m_irq_trigger : 1;
u8 m_enable_ack : 1;
u8 m_enable : 1;
u8 m_sync : 1;
};
public:
timer_t(vrcvi_core &host)
: vgsound_emu_core("vrc_vi_timer")
, m_host(host)
, m_timer_control(timer_control_t())
, m_prescaler(341)
, m_counter(0)
, m_counter_latch(0)
{
}
void reset();
bool tick();
void counter_tick();
// IRQ update
void update() { m_host.m_intf.irq_w(m_timer_control.irq_trigger()); }
void irq_set()
{
if (!m_timer_control.irq_trigger())
{
m_timer_control.irq_set(true);
update();
}
}
void irq_clear()
{
if (m_timer_control.irq_trigger())
{
m_timer_control.irq_set(false);
update();
}
}
// accessors
void reset_counter()
{
m_counter = m_counter_latch;
m_prescaler = 341;
}
void timer_control_w(u8 data)
{
m_timer_control.set_enable_ack((data >> 0) & 1);
m_timer_control.set_enable((data >> 1) & 1);
m_timer_control.set_sync((data >> 2) & 1);
if (m_timer_control.enable())
{
reset_counter();
}
irq_clear();
}
void irq_ack()
{
irq_clear();
m_timer_control.set_enable(m_timer_control.enable_ack());
}
// setters
inline void set_counter_latch(u8 counter_latch) { m_counter_latch = counter_latch; }
// getters
timer_control_t &timer_control() { return m_timer_control; }
inline s16 prescaler() { return m_prescaler; }
inline u8 counter() { return m_counter; }
inline u8 counter_latch() { return m_counter_latch; }
private:
vrcvi_core &m_host; // host core
timer_control_t m_timer_control; // timer control bits
s16 m_prescaler = 341; // prescaler
u8 m_counter = 0; // clock counter
u8 m_counter_latch = 0; // clock counter latch
};
class global_control_t : public vgsound_emu_core
{
public:
global_control_t()
: vgsound_emu_core("vrc_vi_global_control")
, m_halt(0)
, m_shift(0)
{
}
void reset()
{
m_halt = 0;
m_shift = 0;
}
// accessors
inline void write(u8 data)
{
m_halt = (data >> 0) & 1;
m_shift = (data >> 1) & 3;
}
// getters
inline bool halt() { return m_halt; }
inline u8 shift() { return m_shift; }
public:
u8 m_halt; // halt sound
u8 m_shift; // 4/8 bit right shift
};
public:
// constructor
vrcvi_core(vrcvi_intf &intf)
: vgsound_emu_core("vrc_vi")
, m_intf(intf)
, m_pulse{*this, *this}
, m_sawtooth(*this)
, m_timer(*this)
, m_control(global_control_t())
, m_out(0)
{
}
// accessors, getters, setters
void pulse_w(u8 voice, u8 address, u8 data);
void saw_w(u8 address, u8 data);
void timer_w(u8 address, u8 data);
void control_w(u8 data);
// internal state
void reset();
void tick();
// 6 bit output
inline s8 out() { return m_out; }
// for debug/preview only
inline s8 pulse_out(u8 pulse) { return (pulse < 2) ? m_pulse[pulse].out() : 0; }
inline s8 sawtooth_out() { return m_sawtooth.out(); }
private:
vrcvi_intf &m_intf;
pulse_t m_pulse[2]; // 2 pulse channels
sawtooth_t m_sawtooth; // sawtooth channel
timer_t m_timer; // internal timer
global_control_t m_control; // control
s8 m_out = 0; // 6 bit output
};
#endif