2022-09-16 15:05:54 +00:00
|
|
|
/*
|
|
|
|
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
|
|
|
|
|
2022-10-22 01:10:02 +00:00
|
|
|
#include "../core/core.hpp"
|
|
|
|
|
|
|
|
using namespace vgsound_emu;
|
2022-09-16 15:05:54 +00:00
|
|
|
|
|
|
|
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; }
|
|
|
|
|
2023-02-12 03:45:37 +00:00
|
|
|
public:
|
2022-09-16 15:05:54 +00:00
|
|
|
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 ÷r() { 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; }
|
|
|
|
|
2023-02-12 03:49:07 +00:00
|
|
|
public:
|
2022-09-16 15:05:54 +00:00
|
|
|
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; }
|
|
|
|
|
2023-02-12 03:40:29 +00:00
|
|
|
public:
|
|
|
|
u8 m_halt; // halt sound
|
|
|
|
u8 m_shift; // 4/8 bit right shift
|
2022-09-16 15:05:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2023-02-12 03:33:54 +00:00
|
|
|
pulse_t m_pulse[2]; // 2 pulse channels
|
2022-09-16 15:05:54 +00:00
|
|
|
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
|