mirror of
https://github.com/holub/mame
synced 2025-06-02 02:49:44 +03:00
1417 lines
54 KiB
C++
1417 lines
54 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Aaron Giles
|
|
|
|
#ifndef MAME_SOUND_YMFM_H
|
|
#define MAME_SOUND_YMFM_H
|
|
|
|
#pragma once
|
|
|
|
|
|
//*********************************************************
|
|
// MACROS
|
|
//*********************************************************
|
|
|
|
// special naming helper to keep our namespace isolated from other
|
|
// same-named objects in the device's namespace (mostly necessary
|
|
// for chips which derive from AY-8910 classes and may have clashing
|
|
// names)
|
|
#define YMFM_NAME(x) x, "ymfm." #x
|
|
|
|
|
|
//*********************************************************
|
|
// GLOBAL ENUMERATORS
|
|
//*********************************************************
|
|
|
|
enum ymfm_envelope_state : u32
|
|
{
|
|
YMFM_ENV_DEPRESS = 0,
|
|
YMFM_ENV_ATTACK = 1,
|
|
YMFM_ENV_DECAY = 2,
|
|
YMFM_ENV_SUSTAIN = 3,
|
|
YMFM_ENV_RELEASE = 4,
|
|
YMFM_ENV_STATES = 5
|
|
};
|
|
|
|
|
|
//*********************************************************
|
|
// GLOBAL HELPERS
|
|
//*********************************************************
|
|
|
|
// Many of the Yamaha FM chips emit a floating-point value, which is sent to
|
|
// a DAC for processing. The exact format of this floating-point value is
|
|
// documented below. This description only makes sense if the "internal"
|
|
// format treats sign as 1=positive and 0=negative, so the helpers below
|
|
// presume that.
|
|
//
|
|
// Internal OPx data 16-bit signed data Exp Sign Mantissa
|
|
// ================= ================= === ==== ========
|
|
// 1 1xxxxxxxx------ -> 0 1xxxxxxxx------ -> 111 1 1xxxxxxx
|
|
// 1 01xxxxxxxx----- -> 0 01xxxxxxxx----- -> 110 1 1xxxxxxx
|
|
// 1 001xxxxxxxx---- -> 0 001xxxxxxxx---- -> 101 1 1xxxxxxx
|
|
// 1 0001xxxxxxxx--- -> 0 0001xxxxxxxx--- -> 100 1 1xxxxxxx
|
|
// 1 00001xxxxxxxx-- -> 0 00001xxxxxxxx-- -> 011 1 1xxxxxxx
|
|
// 1 000001xxxxxxxx- -> 0 000001xxxxxxxx- -> 010 1 1xxxxxxx
|
|
// 1 000000xxxxxxxxx -> 0 000000xxxxxxxxx -> 001 1 xxxxxxxx
|
|
// 0 111111xxxxxxxxx -> 1 111111xxxxxxxxx -> 001 0 xxxxxxxx
|
|
// 0 111110xxxxxxxx- -> 1 111110xxxxxxxx- -> 010 0 0xxxxxxx
|
|
// 0 11110xxxxxxxx-- -> 1 11110xxxxxxxx-- -> 011 0 0xxxxxxx
|
|
// 0 1110xxxxxxxx--- -> 1 1110xxxxxxxx--- -> 100 0 0xxxxxxx
|
|
// 0 110xxxxxxxx---- -> 1 110xxxxxxxx---- -> 101 0 0xxxxxxx
|
|
// 0 10xxxxxxxx----- -> 1 10xxxxxxxx----- -> 110 0 0xxxxxxx
|
|
// 0 0xxxxxxxx------ -> 1 0xxxxxxxx------ -> 111 0 0xxxxxxx
|
|
|
|
//-------------------------------------------------
|
|
// ymfm_encode_fp - given a 32-bit signed input
|
|
// value, convert it to a signed 3.10 floating-
|
|
// point value
|
|
//-------------------------------------------------
|
|
|
|
inline s16 ymfm_encode_fp(s32 value)
|
|
{
|
|
// handle overflows first
|
|
if (value < -32768)
|
|
return (7 << 10) | 0x000;
|
|
if (value > 32767)
|
|
return (7 << 10) | 0x3ff;
|
|
|
|
// we need to count the number of leading sign bits after the sign
|
|
// we can use count_leading_zeros if we invert negative values
|
|
s32 scanvalue = value ^ (s32(value) >> 31);
|
|
|
|
// exponent is related to the number of leading bits starting from bit 14
|
|
int exponent = 7 - count_leading_zeros(scanvalue << 17);
|
|
|
|
// smallest exponent value allowed is 1
|
|
exponent = std::max(exponent, 1);
|
|
|
|
// mantissa
|
|
s32 mantissa = value >> (exponent - 1);
|
|
|
|
// assemble into final form, inverting the sign
|
|
return ((exponent << 10) | (mantissa & 0x3ff)) ^ 0x200;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// ymfm_decode_fp - given a 3.10 floating-point
|
|
// value, convert it to a signed 16-bit value
|
|
//-------------------------------------------------
|
|
|
|
inline s16 ymfm_decode_fp(s16 value)
|
|
{
|
|
// invert the sign and the exponent
|
|
value ^= 0x1e00;
|
|
|
|
// shift mantissa up to 16 bits then apply inverted exponent
|
|
return s16(value << 6) >> BIT(value, 10, 3);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// ymfm_roundtrip_fp - compute the result of a
|
|
// round trip through the encode/decode process
|
|
// above
|
|
//-------------------------------------------------
|
|
|
|
inline s16 ymfm_roundtrip_fp(s32 value)
|
|
{
|
|
// handle overflows first
|
|
if (value < -32768)
|
|
return -32768;
|
|
if (value > 32767)
|
|
return 32767;
|
|
|
|
// we need to count the number of leading sign bits after the sign
|
|
// we can use count_leading_zeros if we invert negative values
|
|
s32 scanvalue = value ^ (s32(value) >> 31);
|
|
|
|
// exponent is related to the number of leading bits starting from bit 14
|
|
int exponent = 7 - count_leading_zeros(scanvalue << 17);
|
|
|
|
// smallest exponent value allowed is 1
|
|
exponent = std::max(exponent, 1);
|
|
|
|
// apply the shift back and forth to zero out bits that are lost
|
|
exponent -= 1;
|
|
return (value >> exponent) << exponent;
|
|
}
|
|
|
|
|
|
//*********************************************************
|
|
// REGISTER CLASSES
|
|
//*********************************************************
|
|
|
|
// ======================> ymfm_opdata_cache
|
|
|
|
// this class holds data that is computed once at the start of clocking
|
|
// and remains static during subsequent sound generation
|
|
struct ymfm_opdata_cache
|
|
{
|
|
// set phase_step to this value to recalculate it each sample; needed
|
|
// in the case of PM LFO changes
|
|
static constexpr u32 PHASE_STEP_DYNAMIC = 1;
|
|
|
|
u16 const *waveform; // base of sine table
|
|
u32 phase_step; // phase step, or PHASE_STEP_DYNAMIC if PM is active
|
|
u32 total_level; // total level * 8 + KSL
|
|
u32 block_freq; // raw block frequency value (used to compute phase_step)
|
|
s32 detune; // detuning value (used to compute phase_step)
|
|
u32 multiple; // multiple value (x.1, used to compute phase_step)
|
|
u32 eg_sustain; // sustain level, shifted up to envelope values
|
|
u8 eg_rate[YMFM_ENV_STATES]; // envelope rate, including KSR
|
|
};
|
|
|
|
|
|
// ======================> ymfm_registers_base
|
|
|
|
// base class for family-specific register classes; this provides a few
|
|
// constants, common defaults, and helpers, but mostly each derived
|
|
// class is responsible for defining all commonly-called methods
|
|
class ymfm_registers_base
|
|
{
|
|
public:
|
|
// this value is returned from the write() function for rhythm channels
|
|
static constexpr u32 YMFM_RHYTHM_CHANNEL = 0xff;
|
|
|
|
// this is the size of a full sin waveform
|
|
static constexpr u32 WAVEFORM_LENGTH = 0x400;
|
|
|
|
//
|
|
// the following constants need to be defined per family:
|
|
// u32 OUTPUTS: The number of outputs exposed (1-4)
|
|
// u32 CHANNELS: The number of channels on the chip
|
|
// u32 ALL_CHANNELS: A bitmask of all channels
|
|
// u32 OPERATORS: The number of operators on the chip
|
|
// bool DYNAMIC_OPS: True if ops/channel can be changed at runtime
|
|
// u32 WAVEFORMS: The number of waveforms offered
|
|
// u32 REGISTERS: The number of 8-bit registers allocated
|
|
// u32 REG_MODE: The address of the "mode" register controlling timers
|
|
// u32 DEFAULT_PRESCALE: The starting clock prescale
|
|
// u32 EG_CLOCK_DIVIDER: The clock divider of the envelope generator
|
|
// bool EG_HAS_DEPRESS: True if the chip has a DP ("depress"?) envelope stage
|
|
// bool EG_HAS_SSG: True if the chip has SSG envelope support
|
|
// bool MODULATOR_DELAY: True if the modulator is delayed by 1 sample (OPL pre-OPL3)
|
|
// u32 CSM_TRIGGER_MASK: Mask of channels to trigger in CSM mode
|
|
// u8 STATUS_TIMERA: Status bit to set when timer A fires
|
|
// u8 STATUS_TIMERB: Status bit to set when tiemr B fires
|
|
// u8 STATUS_BUSY: Status bit to set when the chip is busy
|
|
// u8 STATUS_IRQ: Status bit to set when an IRQ is signalled
|
|
//
|
|
|
|
// system-wide register defaults
|
|
u32 status_mask() const { return 0; } // OPL only
|
|
u32 irq_reset() const { return 0; } // OPL only
|
|
u32 noise_enable() const { return 0; } // OPM only
|
|
u32 rhythm_enable() const { return 0; } // OPL only
|
|
|
|
// per-operator register defaults
|
|
u32 op_ssg_eg_enable(u32 opoffs) const { return 0; } // OPN(A) only
|
|
u32 op_ssg_eg_mode(u32 opoffs) const { return 0; } // OPN(A) only
|
|
|
|
protected:
|
|
// helper to encode four operator numbers into a 32-bit value in the
|
|
// operator maps for each register class
|
|
static constexpr u32 operator_list(u8 o1 = 0xff, u8 o2 = 0xff, u8 o3 = 0xff, u8 o4 = 0xff)
|
|
{
|
|
return o1 | (o2 << 8) | (o3 << 16) | (o4 << 24);
|
|
}
|
|
|
|
// helper to apply KSR to the raw ADSR rate, ignoring ksr if the
|
|
// raw value is 0, and clamping to 63
|
|
static constexpr u32 effective_rate(u32 rawrate, u32 ksr)
|
|
{
|
|
return (rawrate == 0) ? 0 : std::min<u32>(rawrate + ksr, 63);
|
|
}
|
|
};
|
|
|
|
|
|
// ======================> ymopm_registers
|
|
|
|
//
|
|
// OPM register map:
|
|
//
|
|
// System-wide registers:
|
|
// 01 xxxxxxxx Test register
|
|
// 08 x------- Key on/off operator 4
|
|
// -x------ Key on/off operator 3
|
|
// --x----- Key on/off operator 2
|
|
// ---x---- Key on/off operator 1
|
|
// -----xxx Channel select
|
|
// 0F x------- NE
|
|
// ---xxxxx NFRQ
|
|
// 10 xxxxxxxx Timer A value (upper 8 bits)
|
|
// 11 ------xx Timer A value (lower 2 bits)
|
|
// 12 xxxxxxxx Timer B value
|
|
// 14 x------- CSM mode
|
|
// --x----- Reset timer B
|
|
// ---x---- Reset timer A
|
|
// ----x--- Enable timer B
|
|
// -----x-- Enable timer A
|
|
// ------x- Load timer B
|
|
// -------x Load timer A
|
|
// 18 xxxxxxxx LFO frequency
|
|
// 19 xxxxxxxx PM/AM LFO depth
|
|
// 1B xx------ CT
|
|
// ------xx W
|
|
//
|
|
// Per-channel registers (channel in address bits 0-2)
|
|
// 20-27 x------- Pan right
|
|
// -x------ Pan left
|
|
// --xxx--- Feedback level for operator 1 (0-7)
|
|
// -----xxx Operator connection algorithm (0-7)
|
|
// 28-2F -xxxxxxx Key code
|
|
// 30-37 xxxxxx-- KF
|
|
// 38-3F -xxx---- PM sensitivity
|
|
// ------xx AM shift
|
|
//
|
|
// Per-operator registers (channel in address bits 0-2, operator in bits 3-4)
|
|
// 40-5F -xxx---- Detune value (0-7)
|
|
// ----xxxx Multiple value (0-15)
|
|
// 60-7F -xxxxxxx Total level (0-127)
|
|
// 80-9F xx------ Key scale rate (0-3)
|
|
// ---xxxxx Attack rate (0-31)
|
|
// A0-BF x------- LFO AM enable
|
|
// ---xxxxx Decay rate (0-31)
|
|
// C0-DF xx------ Detune 2 value (0-3)
|
|
// ---xxxxx Sustain rate (0-31)
|
|
// E0-FF xxxx---- Sustain level (0-15)
|
|
// ----xxxx Release rate (0-15)
|
|
//
|
|
// Internal (fake) registers:
|
|
// 19 -xxxxxxx AM depth
|
|
// 1A -xxxxxxx PM depth
|
|
//
|
|
|
|
class ymopm_registers : public ymfm_registers_base
|
|
{
|
|
// LFO waveforms are 256 entries long
|
|
static constexpr u32 LFO_WAVEFORM_LENGTH = 256;
|
|
|
|
public:
|
|
// constants
|
|
static constexpr u32 OUTPUTS = 2;
|
|
static constexpr u32 CHANNELS = 8;
|
|
static constexpr u32 ALL_CHANNELS = (1 << CHANNELS) - 1;
|
|
static constexpr u32 OPERATORS = CHANNELS * 4;
|
|
static constexpr bool DYNAMIC_OPS = false;
|
|
static constexpr u32 WAVEFORMS = 1;
|
|
static constexpr u32 REGISTERS = 0x100;
|
|
static constexpr u32 REG_MODE = 0x14;
|
|
static constexpr u32 DEFAULT_PRESCALE = 2;
|
|
static constexpr u32 EG_CLOCK_DIVIDER = 3;
|
|
static constexpr bool EG_HAS_DEPRESS = false;
|
|
static constexpr bool EG_HAS_SSG = false;
|
|
static constexpr bool MODULATOR_DELAY = false;
|
|
static constexpr u32 CSM_TRIGGER_MASK = ALL_CHANNELS;
|
|
static constexpr u8 STATUS_TIMERA = 0x01;
|
|
static constexpr u8 STATUS_TIMERB = 0x02;
|
|
static constexpr u8 STATUS_BUSY = 0x80;
|
|
static constexpr u8 STATUS_IRQ = 0;
|
|
|
|
// constructor
|
|
ymopm_registers();
|
|
|
|
// register for save states
|
|
void save(device_t &device);
|
|
|
|
// reset to initial state
|
|
void reset();
|
|
|
|
// map channel number to register offset
|
|
static constexpr u32 channel_offset(u32 chnum)
|
|
{
|
|
assert(chnum < CHANNELS);
|
|
return chnum;
|
|
}
|
|
|
|
// map operator number to register offset
|
|
static constexpr u32 operator_offset(u32 opnum)
|
|
{
|
|
assert(opnum < OPERATORS);
|
|
return opnum;
|
|
}
|
|
|
|
// return an array of operator indices for each channel
|
|
struct operator_mapping { u32 chan[CHANNELS]; };
|
|
void operator_map(operator_mapping &dest) const;
|
|
|
|
// handle writes to the register array
|
|
bool write(u16 index, u8 data, u32 &chan, u32 &opmask);
|
|
|
|
// clock the noise and LFO, if present, returning LFO PM value
|
|
s32 clock_noise_and_lfo();
|
|
|
|
// reset the LFO
|
|
void reset_lfo() { m_lfo_counter = 0; }
|
|
|
|
// return the AM offset from LFO for the given channel
|
|
u32 lfo_am_offset(u32 choffs) const;
|
|
|
|
// return the current noise state, gated by the noise clock
|
|
u32 noise_state() const { return m_noise_state; }
|
|
|
|
// caching helpers
|
|
void cache_operator_data(u32 choffs, u32 opoffs, ymfm_opdata_cache &cache);
|
|
|
|
// compute the phase step, given a PM value
|
|
u32 compute_phase_step(u32 choffs, u32 opoffs, ymfm_opdata_cache const &cache, s32 lfo_raw_pm);
|
|
|
|
// log a key-on event
|
|
void log_keyon(u32 choffs, u32 opoffs);
|
|
|
|
// system-wide registers
|
|
u32 test() const { return byte(0x01, 0, 8); }
|
|
u32 noise_frequency() const { return byte(0x0f, 0, 5); }
|
|
u32 noise_enable() const { return byte(0x0f, 7, 1); }
|
|
u32 timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); }
|
|
u32 timer_b_value() const { return byte(0x12, 0, 8); }
|
|
u32 csm() const { return byte(0x14, 7, 1); }
|
|
u32 reset_timer_b() const { return byte(0x14, 5, 1); }
|
|
u32 reset_timer_a() const { return byte(0x14, 4, 1); }
|
|
u32 enable_timer_b() const { return byte(0x14, 3, 1); }
|
|
u32 enable_timer_a() const { return byte(0x14, 2, 1); }
|
|
u32 load_timer_b() const { return byte(0x14, 1, 1); }
|
|
u32 load_timer_a() const { return byte(0x14, 0, 1); }
|
|
u32 lfo_rate() const { return byte(0x18, 0, 8); }
|
|
u32 lfo_am_depth() const { return byte(0x19, 0, 7); }
|
|
u32 lfo_pm_depth() const { return byte(0x1a, 0, 7); }
|
|
u32 lfo_waveform() const { return byte(0x1b, 0, 2); }
|
|
|
|
// per-channel registers
|
|
u32 ch_output_any(u32 choffs) const { return byte(0x20, 6, 2, choffs); }
|
|
u32 ch_output_0(u32 choffs) const { return byte(0x20, 6, 1, choffs); }
|
|
u32 ch_output_1(u32 choffs) const { return byte(0x20, 7, 1, choffs); }
|
|
u32 ch_output_2(u32 choffs) const { return 0; }
|
|
u32 ch_output_3(u32 choffs) const { return 0; }
|
|
u32 ch_feedback(u32 choffs) const { return byte(0x20, 3, 3, choffs); }
|
|
u32 ch_algorithm(u32 choffs) const { return byte(0x20, 0, 3, choffs); }
|
|
u32 ch_block_freq(u32 choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); }
|
|
u32 ch_lfo_pm_sens(u32 choffs) const { return byte(0x38, 4, 3, choffs); }
|
|
u32 ch_lfo_am_sens(u32 choffs) const { return byte(0x38, 0, 2, choffs); }
|
|
|
|
// per-operator registers
|
|
u32 op_detune(u32 opoffs) const { return byte(0x40, 4, 3, opoffs); }
|
|
u32 op_multiple(u32 opoffs) const { return byte(0x40, 0, 4, opoffs); }
|
|
u32 op_total_level(u32 opoffs) const { return byte(0x60, 0, 7, opoffs); }
|
|
u32 op_ksr(u32 opoffs) const { return byte(0x80, 6, 2, opoffs); }
|
|
u32 op_attack_rate(u32 opoffs) const { return byte(0x80, 0, 5, opoffs); }
|
|
u32 op_lfo_am_enable(u32 opoffs) const { return byte(0xa0, 7, 1, opoffs); }
|
|
u32 op_decay_rate(u32 opoffs) const { return byte(0xa0, 0, 5, opoffs); }
|
|
u32 op_detune2(u32 opoffs) const { return byte(0xc0, 6, 2, opoffs); }
|
|
u32 op_sustain_rate(u32 opoffs) const { return byte(0xc0, 0, 5, opoffs); }
|
|
u32 op_sustain_level(u32 opoffs) const { return byte(0xe0, 4, 4, opoffs); }
|
|
u32 op_release_rate(u32 opoffs) const { return byte(0xe0, 0, 4, opoffs); }
|
|
|
|
protected:
|
|
// return a bitfield extracted from a byte
|
|
u32 byte(u32 offset, u32 start, u32 count, u32 extra_offset = 0) const
|
|
{
|
|
return BIT(m_regdata[offset + extra_offset], start, count);
|
|
}
|
|
|
|
// return a bitfield extracted from a pair of bytes, MSBs listed first
|
|
u32 word(u32 offset1, u32 start1, u32 count1, u32 offset2, u32 start2, u32 count2, u32 extra_offset = 0) const
|
|
{
|
|
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
|
|
}
|
|
|
|
// internal state
|
|
u32 m_lfo_counter; // LFO counter
|
|
u32 m_noise_lfsr; // noise LFSR state
|
|
u8 m_noise_counter; // noise counter
|
|
u8 m_noise_state; // latched noise state
|
|
u8 m_noise_lfo; // latched LFO noise value
|
|
u8 m_lfo_am; // current LFO AM value
|
|
u8 m_regdata[REGISTERS]; // register data
|
|
s16 m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8
|
|
u16 m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
|
|
};
|
|
|
|
|
|
// ======================> ymopn_registers_base
|
|
|
|
//
|
|
// OPN register map:
|
|
//
|
|
// System-wide registers:
|
|
// 21 xxxxxxxx Test register
|
|
// 22 ----x--- LFO enable [OPNA+ only]
|
|
// -----xxx LFO rate [OPNA+ only]
|
|
// 24 xxxxxxxx Timer A value (upper 8 bits)
|
|
// 25 ------xx Timer A value (lower 2 bits)
|
|
// 26 xxxxxxxx Timer B value
|
|
// 27 xx------ CSM/Multi-frequency mode for channel #2
|
|
// --x----- Reset timer B
|
|
// ---x---- Reset timer A
|
|
// ----x--- Enable timer B
|
|
// -----x-- Enable timer A
|
|
// ------x- Load timer B
|
|
// -------x Load timer A
|
|
// 28 x------- Key on/off operator 4
|
|
// -x------ Key on/off operator 3
|
|
// --x----- Key on/off operator 2
|
|
// ---x---- Key on/off operator 1
|
|
// ------xx Channel select
|
|
//
|
|
// Per-channel registers (channel in address bits 0-1)
|
|
// Note that all these apply to address+100 as well on OPNA+
|
|
// A0-A3 xxxxxxxx Frequency number lower 8 bits
|
|
// A4-A7 --xxx--- Block (0-7)
|
|
// -----xxx Frequency number upper 3 bits
|
|
// B0-B3 --xxx--- Feedback level for operator 1 (0-7)
|
|
// -----xxx Operator connection algorithm (0-7)
|
|
// B4-B7 x------- Pan left [OPNA]
|
|
// -x------ Pan right [OPNA]
|
|
// --xx---- LFO AM shift (0-3) [OPNA+ only]
|
|
// -----xxx LFO PM depth (0-7) [OPNA+ only]
|
|
//
|
|
// Per-operator registers (channel in address bits 0-1, operator in bits 2-3)
|
|
// Note that all these apply to address+100 as well on OPNA+
|
|
// 30-3F -xxx---- Detune value (0-7)
|
|
// ----xxxx Multiple value (0-15)
|
|
// 40-4F -xxxxxxx Total level (0-127)
|
|
// 50-5F xx------ Key scale rate (0-3)
|
|
// ---xxxxx Attack rate (0-31)
|
|
// 60-6F x------- LFO AM enable [OPNA]
|
|
// ---xxxxx Decay rate (0-31)
|
|
// 70-7F ---xxxxx Sustain rate (0-31)
|
|
// 80-8F xxxx---- Sustain level (0-15)
|
|
// ----xxxx Release rate (0-15)
|
|
// 90-9F ----x--- SSG-EG enable
|
|
// -----xxx SSG-EG envelope (0-7)
|
|
//
|
|
// Special multi-frequency registers (channel implicitly #2; operator in address bits 0-1)
|
|
// A8-AB xxxxxxxx Frequency number lower 8 bits
|
|
// AC-AF --xxx--- Block (0-7)
|
|
// -----xxx Frequency number upper 3 bits
|
|
//
|
|
// Internal (fake) registers:
|
|
// B8-BB --xxxxxx Latched frequency number upper bits (from A4-A7)
|
|
// BC-BF --xxxxxx Latched frequency number upper bits (from AC-AF)
|
|
//
|
|
|
|
template<bool IsOpnA>
|
|
class ymopn_registers_base : public ymfm_registers_base
|
|
{
|
|
public:
|
|
// constants
|
|
static constexpr u32 OUTPUTS = IsOpnA ? 2 : 1;
|
|
static constexpr u32 CHANNELS = IsOpnA ? 6 : 3;
|
|
static constexpr u32 ALL_CHANNELS = (1 << CHANNELS) - 1;
|
|
static constexpr u32 OPERATORS = CHANNELS * 4;
|
|
static constexpr bool DYNAMIC_OPS = false;
|
|
static constexpr u32 WAVEFORMS = 1;
|
|
static constexpr u32 REGISTERS = IsOpnA ? 0x200 : 0x100;
|
|
static constexpr u32 REG_MODE = 0x27;
|
|
static constexpr u32 DEFAULT_PRESCALE = 6;
|
|
static constexpr u32 EG_CLOCK_DIVIDER = 3;
|
|
static constexpr bool EG_HAS_DEPRESS = false;
|
|
static constexpr bool EG_HAS_SSG = true;
|
|
static constexpr bool MODULATOR_DELAY = false;
|
|
static constexpr u32 CSM_TRIGGER_MASK = 1 << 2;
|
|
static constexpr u8 STATUS_TIMERA = 0x01;
|
|
static constexpr u8 STATUS_TIMERB = 0x02;
|
|
static constexpr u8 STATUS_BUSY = 0x80;
|
|
static constexpr u8 STATUS_IRQ = 0;
|
|
|
|
// constructor
|
|
ymopn_registers_base();
|
|
|
|
// register for save states
|
|
void save(device_t &device);
|
|
|
|
// reset to initial state
|
|
void reset();
|
|
|
|
// map channel number to register offset
|
|
static constexpr u32 channel_offset(u32 chnum)
|
|
{
|
|
assert(chnum < CHANNELS);
|
|
if (!IsOpnA)
|
|
return chnum;
|
|
else
|
|
return (chnum % 3) + 0x100 * (chnum / 3);
|
|
}
|
|
|
|
// map operator number to register offset
|
|
static constexpr u32 operator_offset(u32 opnum)
|
|
{
|
|
assert(opnum < OPERATORS);
|
|
if (!IsOpnA)
|
|
return opnum + opnum / 3;
|
|
else
|
|
return (opnum % 12) + ((opnum % 12) / 3) + 0x100 * (opnum / 12);
|
|
}
|
|
|
|
// return an array of operator indices for each channel
|
|
struct operator_mapping { u32 chan[CHANNELS]; };
|
|
void operator_map(operator_mapping &dest) const;
|
|
|
|
// handle writes to the register array
|
|
bool write(u16 index, u8 data, u32 &chan, u32 &opmask);
|
|
|
|
// clock the noise and LFO, if present, returning LFO PM value
|
|
s32 clock_noise_and_lfo();
|
|
|
|
// reset the LFO
|
|
void reset_lfo() { m_lfo_counter = 0; }
|
|
|
|
// return the AM offset from LFO for the given channel
|
|
u32 lfo_am_offset(u32 choffs) const;
|
|
|
|
// return LFO/noise states
|
|
u32 noise_state() const { return 0; }
|
|
|
|
// caching helpers
|
|
void cache_operator_data(u32 choffs, u32 opoffs, ymfm_opdata_cache &cache);
|
|
|
|
// compute the phase step, given a PM value
|
|
u32 compute_phase_step(u32 choffs, u32 opoffs, ymfm_opdata_cache const &cache, s32 lfo_raw_pm);
|
|
|
|
// log a key-on event
|
|
void log_keyon(u32 choffs, u32 opoffs);
|
|
|
|
// system-wide registers
|
|
u32 test() const { return byte(0x21, 0, 8); }
|
|
u32 lfo_enable() const { return IsOpnA ? byte(0x22, 3, 1) : 0; }
|
|
u32 lfo_rate() const { return IsOpnA ? byte(0x22, 0, 3) : 0; }
|
|
u32 timer_a_value() const { return word(0x24, 0, 8, 0x25, 0, 2); }
|
|
u32 timer_b_value() const { return byte(0x26, 0, 8); }
|
|
u32 csm() const { return byte(0x27, 7, 1); }
|
|
u32 multi_freq() const { return (byte(0x27, 6, 2) != 0); }
|
|
u32 reset_timer_b() const { return byte(0x27, 5, 1); }
|
|
u32 reset_timer_a() const { return byte(0x27, 4, 1); }
|
|
u32 enable_timer_b() const { return byte(0x27, 3, 1); }
|
|
u32 enable_timer_a() const { return byte(0x27, 2, 1); }
|
|
u32 load_timer_b() const { return byte(0x27, 1, 1); }
|
|
u32 load_timer_a() const { return byte(0x27, 0, 1); }
|
|
u32 multi_block_freq(u32 num) const { return word(0xac, 0, 6, 0xa8, 0, 8, num); }
|
|
|
|
// per-channel registers
|
|
u32 ch_block_freq(u32 choffs) const { return word(0xa4, 0, 6, 0xa0, 0, 8, choffs); }
|
|
u32 ch_feedback(u32 choffs) const { return byte(0xb0, 3, 3, choffs); }
|
|
u32 ch_algorithm(u32 choffs) const { return byte(0xb0, 0, 3, choffs); }
|
|
u32 ch_output_any(u32 choffs) const { return IsOpnA ? byte(0xb4, 6, 2, choffs) : 1; }
|
|
u32 ch_output_0(u32 choffs) const { return IsOpnA ? byte(0xb4, 7, 1, choffs) : 1; }
|
|
u32 ch_output_1(u32 choffs) const { return IsOpnA ? byte(0xb4, 6, 1, choffs) : 0; }
|
|
u32 ch_output_2(u32 choffs) const { return 0; }
|
|
u32 ch_output_3(u32 choffs) const { return 0; }
|
|
u32 ch_lfo_am_sens(u32 choffs) const { return IsOpnA ? byte(0xb4, 4, 2, choffs) : 0; }
|
|
u32 ch_lfo_pm_sens(u32 choffs) const { return IsOpnA ? byte(0xb4, 0, 3, choffs) : 0; }
|
|
|
|
// per-operator registers
|
|
u32 op_detune(u32 opoffs) const { return byte(0x30, 4, 3, opoffs); }
|
|
u32 op_multiple(u32 opoffs) const { return byte(0x30, 0, 4, opoffs); }
|
|
u32 op_total_level(u32 opoffs) const { return byte(0x40, 0, 7, opoffs); }
|
|
u32 op_ksr(u32 opoffs) const { return byte(0x50, 6, 2, opoffs); }
|
|
u32 op_attack_rate(u32 opoffs) const { return byte(0x50, 0, 5, opoffs); }
|
|
u32 op_decay_rate(u32 opoffs) const { return byte(0x60, 0, 5, opoffs); }
|
|
u32 op_lfo_am_enable(u32 opoffs) const { return IsOpnA ? byte(0x60, 7, 1, opoffs) : 0; }
|
|
u32 op_sustain_rate(u32 opoffs) const { return byte(0x70, 0, 5, opoffs); }
|
|
u32 op_sustain_level(u32 opoffs) const { return byte(0x80, 4, 4, opoffs); }
|
|
u32 op_release_rate(u32 opoffs) const { return byte(0x80, 0, 4, opoffs); }
|
|
u32 op_ssg_eg_enable(u32 opoffs) const { return byte(0x90, 3, 1, opoffs); }
|
|
u32 op_ssg_eg_mode(u32 opoffs) const { return byte(0x90, 0, 3, opoffs); }
|
|
|
|
protected:
|
|
// return a bitfield extracted from a byte
|
|
u32 byte(u32 offset, u32 start, u32 count, u32 extra_offset = 0) const
|
|
{
|
|
return BIT(m_regdata[offset + extra_offset], start, count);
|
|
}
|
|
|
|
// return a bitfield extracted from a pair of bytes, MSBs listed first
|
|
u32 word(u32 offset1, u32 start1, u32 count1, u32 offset2, u32 start2, u32 count2, u32 extra_offset = 0) const
|
|
{
|
|
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
|
|
}
|
|
|
|
// internal state
|
|
u32 m_lfo_counter; // LFO counter
|
|
u8 m_lfo_am; // current LFO AM value
|
|
u8 m_multi_freq; // channel 2 is currently multi-frequency
|
|
u8 m_regdata[REGISTERS]; // register data
|
|
u16 m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
|
|
};
|
|
|
|
using ymopn_registers = ymopn_registers_base<false>;
|
|
using ymopna_registers = ymopn_registers_base<true>;
|
|
|
|
|
|
// ======================> ymopl_registers_base
|
|
|
|
//
|
|
// OPL/OPL2/OPL3/OPL4 register map:
|
|
//
|
|
// System-wide registers:
|
|
// 01 xxxxxxxx Test register
|
|
// --x----- Enable OPL compatibility mode [OPL2 only] (1 = enable)
|
|
// 02 xxxxxxxx Timer A value (4 * OPN)
|
|
// 03 xxxxxxxx Timer B value
|
|
// 04 x------- RST
|
|
// -x------ Mask timer A
|
|
// --x----- Mask timer B
|
|
// ------x- Load timer B
|
|
// -------x Load timer A
|
|
// 08 x------- CSM mode [OPL/OPL2 only]
|
|
// -x------ Note select
|
|
// BD x------- AM depth
|
|
// -x------ PM depth
|
|
// --x----- Rhythm enable
|
|
// ---x---- Bass drum key on
|
|
// ----x--- Snare drum key on
|
|
// -----x-- Tom key on
|
|
// ------x- Top cymbal key on
|
|
// -------x High hat key on
|
|
// 101 --xxxxxx Test register 2 [OPL3 only]
|
|
// 104 --x----- Channel 6 4-operator mode [OPL3 only]
|
|
// ---x---- Channel 5 4-operator mode [OPL3 only]
|
|
// ----x--- Channel 4 4-operator mode [OPL3 only]
|
|
// -----x-- Channel 3 4-operator mode [OPL3 only]
|
|
// ------x- Channel 2 4-operator mode [OPL3 only]
|
|
// -------x Channel 1 4-operator mode [OPL3 only]
|
|
// 105 -------x New [OPL3 only]
|
|
// ------x- New2 [OPL4 only]
|
|
//
|
|
// Per-channel registers (channel in address bits 0-3)
|
|
// Note that all these apply to address+100 as well on OPL3+
|
|
// A0-A8 xxxxxxxx F-number (low 8 bits)
|
|
// B0-B8 --x----- Key on
|
|
// ---xxx-- Block (octvate, 0-7)
|
|
// ------xx F-number (high two bits)
|
|
// C0-C8 x------- CHD output (to DO0 pin) [OPL3+ only]
|
|
// -x------ CHC output (to DO0 pin) [OPL3+ only]
|
|
// --x----- CHB output (mixed right, to DO2 pin) [OPL3+ only]
|
|
// ---x---- CHA output (mixed left, to DO2 pin) [OPL3+ only]
|
|
// ----xxx- Feedback level for operator 1 (0-7)
|
|
// -------x Operator connection algorithm
|
|
//
|
|
// Per-operator registers (operator in bits 0-5)
|
|
// Note that all these apply to address+100 as well on OPL3+
|
|
// 20-35 x------- AM enable
|
|
// -x------ PM enable (VIB)
|
|
// --x----- EG type
|
|
// ---x---- Key scale rate
|
|
// ----xxxx Multiple value (0-15)
|
|
// 40-55 xx------ Key scale level (0-3)
|
|
// --xxxxxx Total level (0-63)
|
|
// 60-75 xxxx---- Attack rate (0-15)
|
|
// ----xxxx Decay rate (0-15)
|
|
// 80-95 xxxx---- Sustain level (0-15)
|
|
// ----xxxx Release rate (0-15)
|
|
// E0-F5 ------xx Wave select (0-3) [OPL2 only]
|
|
// -----xxx Wave select (0-7) [OPL3+ only]
|
|
//
|
|
|
|
template<int Revision>
|
|
class ymopl_registers_base : public ymfm_registers_base
|
|
{
|
|
static constexpr bool IsOpl2 = (Revision == 2);
|
|
static constexpr bool IsOpl2Plus = (Revision >= 2);
|
|
static constexpr bool IsOpl3Plus = (Revision >= 3);
|
|
static constexpr bool IsOpl4Plus = (Revision >= 4);
|
|
|
|
public:
|
|
// constants
|
|
static constexpr u32 OUTPUTS = IsOpl3Plus ? 4 : 1;
|
|
static constexpr u32 CHANNELS = IsOpl3Plus ? 18 : 9;
|
|
static constexpr u32 ALL_CHANNELS = (1 << CHANNELS) - 1;
|
|
static constexpr u32 OPERATORS = CHANNELS * 2;
|
|
static constexpr bool DYNAMIC_OPS = IsOpl3Plus;
|
|
static constexpr u32 WAVEFORMS = IsOpl3Plus ? 8 : (IsOpl2Plus ? 4 : 1);
|
|
static constexpr u32 REGISTERS = IsOpl3Plus ? 0x200 : 0x100;
|
|
static constexpr u32 REG_MODE = 0x04;
|
|
static constexpr u32 DEFAULT_PRESCALE = IsOpl4Plus ? 19 : (IsOpl3Plus ? 8 : 4);
|
|
static constexpr u32 EG_CLOCK_DIVIDER = 1;
|
|
static constexpr bool EG_HAS_DEPRESS = false;
|
|
static constexpr bool EG_HAS_SSG = false;
|
|
static constexpr bool MODULATOR_DELAY = !IsOpl3Plus;
|
|
static constexpr u32 CSM_TRIGGER_MASK = ALL_CHANNELS;
|
|
static constexpr u8 STATUS_TIMERA = 0x40;
|
|
static constexpr u8 STATUS_TIMERB = 0x20;
|
|
static constexpr u8 STATUS_BUSY = 0;
|
|
static constexpr u8 STATUS_IRQ = 0x80;
|
|
|
|
// constructor
|
|
ymopl_registers_base();
|
|
|
|
// register for save states
|
|
void save(device_t &device);
|
|
|
|
// reset to initial state
|
|
void reset();
|
|
|
|
// map channel number to register offset
|
|
static constexpr u32 channel_offset(u32 chnum)
|
|
{
|
|
assert(chnum < CHANNELS);
|
|
if (!IsOpl3Plus)
|
|
return chnum;
|
|
else
|
|
return (chnum % 9) + 0x100 * (chnum / 9);
|
|
}
|
|
|
|
// map operator number to register offset
|
|
static constexpr u32 operator_offset(u32 opnum)
|
|
{
|
|
assert(opnum < OPERATORS);
|
|
if (!IsOpl3Plus)
|
|
return opnum + 2 * (opnum / 6);
|
|
else
|
|
return (opnum % 18) + 2 * ((opnum % 18) / 6) + 0x100 * (opnum / 18);
|
|
}
|
|
|
|
// return an array of operator indices for each channel
|
|
struct operator_mapping { u32 chan[CHANNELS]; };
|
|
void operator_map(operator_mapping &dest) const;
|
|
|
|
// OPL4 apparently can read back FM registers?
|
|
u8 read(u16 index) { return m_regdata[index]; }
|
|
|
|
// handle writes to the register array
|
|
bool write(u16 index, u8 data, u32 &chan, u32 &opmask);
|
|
|
|
// clock the noise and LFO, if present, returning LFO PM value
|
|
s32 clock_noise_and_lfo();
|
|
|
|
// reset the LFO
|
|
void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
|
|
|
|
// return the AM offset from LFO for the given channel
|
|
// on OPL this is just a fixed value
|
|
u32 lfo_am_offset(u32 choffs) const { return m_lfo_am; }
|
|
|
|
// return LFO/noise states
|
|
u32 noise_state() const { return m_noise_lfsr >> 23; }
|
|
|
|
// caching helpers
|
|
void cache_operator_data(u32 choffs, u32 opoffs, ymfm_opdata_cache &cache);
|
|
|
|
// compute the phase step, given a PM value
|
|
u32 compute_phase_step(u32 choffs, u32 opoffs, ymfm_opdata_cache const &cache, s32 lfo_raw_pm);
|
|
|
|
// log a key-on event
|
|
void log_keyon(u32 choffs, u32 opoffs);
|
|
|
|
// system-wide registers
|
|
u32 test() const { return byte(0x01, 0, 8); }
|
|
u32 waveform_enable() const { return IsOpl2 ? byte(0x01, 5, 1) : (IsOpl3Plus ? 1 : 0); }
|
|
u32 timer_a_value() const { return byte(0x02, 0, 8) * 4; } // 8->10 bits
|
|
u32 timer_b_value() const { return byte(0x03, 0, 8); }
|
|
u32 status_mask() const { return byte(0x04, 0, 8) & 0x78; }
|
|
u32 irq_reset() const { return byte(0x04, 7, 1); }
|
|
u32 reset_timer_b() const { return byte(0x04, 7, 1) | byte(0x04, 5, 1); }
|
|
u32 reset_timer_a() const { return byte(0x04, 7, 1) | byte(0x04, 6, 1); }
|
|
u32 enable_timer_b() const { return 1; }
|
|
u32 enable_timer_a() const { return 1; }
|
|
u32 load_timer_b() const { return byte(0x04, 1, 1); }
|
|
u32 load_timer_a() const { return byte(0x04, 0, 1); }
|
|
u32 csm() const { return IsOpl3Plus ? 0 : byte(0x08, 7, 1); }
|
|
u32 note_select() const { return byte(0x08, 6, 1); }
|
|
u32 lfo_am_depth() const { return byte(0xbd, 7, 1); }
|
|
u32 lfo_pm_depth() const { return byte(0xbd, 6, 1); }
|
|
u32 rhythm_enable() const { return byte(0xbd, 5, 1); }
|
|
u32 rhythm_keyon() const { return byte(0xbd, 4, 0); }
|
|
u32 newflag() const { return IsOpl3Plus ? byte(0x105, 0, 1) : 0; }
|
|
u32 new2flag() const { return IsOpl4Plus ? byte(0x105, 1, 1) : 0; }
|
|
u32 fourop_enable() const { return IsOpl3Plus ? byte(0x104, 0, 6) : 0; }
|
|
|
|
// per-channel registers
|
|
u32 ch_block_freq(u32 choffs) const { return word(0xb0, 0, 5, 0xa0, 0, 8, choffs); }
|
|
u32 ch_feedback(u32 choffs) const { return byte(0xc0, 1, 3, choffs); }
|
|
u32 ch_algorithm(u32 choffs) const { return byte(0xc0, 0, 1, choffs) | (IsOpl3Plus ? (8 | (byte(0xc3, 0, 1, choffs) << 1)) : 0); }
|
|
u32 ch_output_any(u32 choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 4) : 1; }
|
|
u32 ch_output_0(u32 choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 1) : 1; }
|
|
u32 ch_output_1(u32 choffs) const { return newflag() ? byte(0xc0 + choffs, 5, 1) : (IsOpl3Plus ? 1 : 0); }
|
|
u32 ch_output_2(u32 choffs) const { return newflag() ? byte(0xc0 + choffs, 6, 1) : 0; }
|
|
u32 ch_output_3(u32 choffs) const { return newflag() ? byte(0xc0 + choffs, 7, 1) : 0; }
|
|
|
|
// per-operator registers
|
|
u32 op_lfo_am_enable(u32 opoffs) const { return byte(0x20, 7, 1, opoffs); }
|
|
u32 op_lfo_pm_enable(u32 opoffs) const { return byte(0x20, 6, 1, opoffs); }
|
|
u32 op_eg_sustain(u32 opoffs) const { return byte(0x20, 5, 1, opoffs); }
|
|
u32 op_ksr(u32 opoffs) const { return byte(0x20, 4, 1, opoffs); }
|
|
u32 op_multiple(u32 opoffs) const { return byte(0x20, 0, 4, opoffs); }
|
|
u32 op_ksl(u32 opoffs) const { return bitswap<2>(byte(0x40, 6, 2, opoffs), 0, 1); }
|
|
u32 op_total_level(u32 opoffs) const { return byte(0x40, 0, 6, opoffs); }
|
|
u32 op_attack_rate(u32 opoffs) const { return byte(0x60, 4, 4, opoffs); }
|
|
u32 op_decay_rate(u32 opoffs) const { return byte(0x60, 0, 4, opoffs); }
|
|
u32 op_sustain_level(u32 opoffs) const { return byte(0x80, 4, 4, opoffs); }
|
|
u32 op_release_rate(u32 opoffs) const { return byte(0x80, 0, 4, opoffs); }
|
|
u32 op_waveform(u32 opoffs) const { return IsOpl2Plus ? byte(0xe0, 0, newflag() ? 3 : 2, opoffs) : 0; }
|
|
|
|
protected:
|
|
// return a bitfield extracted from a byte
|
|
u32 byte(u32 offset, u32 start, u32 count, u32 extra_offset = 0) const
|
|
{
|
|
return BIT(m_regdata[offset + extra_offset], start, count);
|
|
}
|
|
|
|
// return a bitfield extracted from a pair of bytes, MSBs listed first
|
|
u32 word(u32 offset1, u32 start1, u32 count1, u32 offset2, u32 start2, u32 count2, u32 extra_offset = 0) const
|
|
{
|
|
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
|
|
}
|
|
|
|
// helper to determine if the this channel is an active rhythm channel
|
|
bool is_rhythm(u32 choffs) const
|
|
{
|
|
return rhythm_enable() && (choffs >= 6 && choffs <= 8);
|
|
}
|
|
|
|
// internal state
|
|
u16 m_lfo_am_counter; // LFO AM counter
|
|
u16 m_lfo_pm_counter; // LFO PM counter
|
|
u32 m_noise_lfsr; // noise LFSR state
|
|
u8 m_lfo_am; // current LFO AM value
|
|
u8 m_regdata[REGISTERS]; // register data
|
|
u16 m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
|
|
};
|
|
|
|
using ymopl_registers = ymopl_registers_base<1>;
|
|
using ymopl2_registers = ymopl_registers_base<2>;
|
|
using ymopl3_registers = ymopl_registers_base<3>;
|
|
using ymopl4_registers = ymopl_registers_base<4>;
|
|
|
|
|
|
// ======================> ymopll_registers
|
|
|
|
//
|
|
// OPLL register map:
|
|
//
|
|
// System-wide registers:
|
|
// 0E --x----- Rhythm enable
|
|
// ---x---- Bass drum key on
|
|
// ----x--- Snare drum key on
|
|
// -----x-- Tom key on
|
|
// ------x- Top cymbal key on
|
|
// -------x High hat key on
|
|
// 0F xxxxxxxx Test register
|
|
//
|
|
// Per-channel registers (channel in address bits 0-3)
|
|
// 10-18 xxxxxxxx F-number (low 8 bits)
|
|
// 20-28 --x----- Sustain on
|
|
// ---x---- Key on
|
|
// --- xxx- Block (octvate, 0-7)
|
|
// -------x F-number (high bit)
|
|
// 30-38 xxxx---- Instrument selection
|
|
// ----xxxx Volume
|
|
//
|
|
// User instrument registers (for carrier, modulator operators)
|
|
// 00-01 x------- AM enable
|
|
// -x------ PM enable (VIB)
|
|
// --x----- EG type
|
|
// ---x---- Key scale rate
|
|
// ----xxxx Multiple value (0-15)
|
|
// 02 xx------ Key scale level (carrier, 0-3)
|
|
// --xxxxxx Total level (modulator, 0-63)
|
|
// 03 xx------ Key scale level (modulator, 0-3)
|
|
// ---x---- Rectified wave (carrier)
|
|
// ----x--- Rectified wave (modulator)
|
|
// -----xxx Feedback level for operator 1 (0-7)
|
|
// 04-05 xxxx---- Attack rate (0-15)
|
|
// ----xxxx Decay rate (0-15)
|
|
// 06-07 xxxx---- Sustain level (0-15)
|
|
// ----xxxx Release rate (0-15)
|
|
//
|
|
// Internal (fake) registers:
|
|
// 40-48 xxxxxxxx Current instrument base address
|
|
// 4E-5F xxxxxxxx Current instrument base address + operator slot (0/1)
|
|
// 70-FF xxxxxxxx Data for instruments (1-16 plus 3 drums)
|
|
//
|
|
|
|
class ymopll_registers : public ymfm_registers_base
|
|
{
|
|
public:
|
|
static constexpr u32 OUTPUTS = 2;
|
|
static constexpr u32 CHANNELS = 9;
|
|
static constexpr u32 ALL_CHANNELS = (1 << CHANNELS) - 1;
|
|
static constexpr u32 OPERATORS = CHANNELS * 2;
|
|
static constexpr bool DYNAMIC_OPS = false;
|
|
static constexpr u32 WAVEFORMS = 2;
|
|
static constexpr u32 REGISTERS = 0x40;
|
|
static constexpr u32 REG_MODE = 0x3f;
|
|
static constexpr u32 DEFAULT_PRESCALE = 4;
|
|
static constexpr u32 EG_CLOCK_DIVIDER = 1;
|
|
static constexpr bool EG_HAS_DEPRESS = true;
|
|
static constexpr bool EG_HAS_SSG = false;
|
|
static constexpr bool MODULATOR_DELAY = true;
|
|
static constexpr u32 CSM_TRIGGER_MASK = 0;
|
|
static constexpr u8 STATUS_TIMERA = 0;
|
|
static constexpr u8 STATUS_TIMERB = 0;
|
|
static constexpr u8 STATUS_BUSY = 0;
|
|
static constexpr u8 STATUS_IRQ = 0;
|
|
|
|
// OPLL-specific constants
|
|
static constexpr u32 INSTDATA_SIZE = 0x90;
|
|
|
|
// constructor
|
|
ymopll_registers();
|
|
|
|
// register for save states
|
|
void save(device_t &device);
|
|
|
|
// reset to initial state
|
|
void reset();
|
|
|
|
// map channel number to register offset
|
|
static constexpr u32 channel_offset(u32 chnum)
|
|
{
|
|
assert(chnum < CHANNELS);
|
|
return chnum;
|
|
}
|
|
|
|
// map operator number to register offset
|
|
static constexpr u32 operator_offset(u32 opnum)
|
|
{
|
|
assert(opnum < OPERATORS);
|
|
return opnum;
|
|
}
|
|
|
|
// return an array of operator indices for each channel
|
|
struct operator_mapping { u32 chan[CHANNELS]; };
|
|
void operator_map(operator_mapping &dest) const;
|
|
|
|
// handle writes to the register array
|
|
bool write(u16 index, u8 data, u32 &chan, u32 &opmask);
|
|
|
|
// clock the noise and LFO, if present, returning LFO PM value
|
|
s32 clock_noise_and_lfo();
|
|
|
|
// reset the LFO
|
|
void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
|
|
|
|
// return the AM offset from LFO for the given channel
|
|
// on OPL this is just a fixed value
|
|
u32 lfo_am_offset(u32 choffs) const { return m_lfo_am; }
|
|
|
|
// return LFO/noise states
|
|
u32 noise_state() const { return m_noise_lfsr >> 23; }
|
|
|
|
// caching helpers
|
|
void cache_operator_data(u32 choffs, u32 opoffs, ymfm_opdata_cache &cache);
|
|
|
|
// compute the phase step, given a PM value
|
|
u32 compute_phase_step(u32 choffs, u32 opoffs, ymfm_opdata_cache const &cache, s32 lfo_raw_pm);
|
|
|
|
// log a key-on event
|
|
void log_keyon(u32 choffs, u32 opoffs);
|
|
|
|
// set the instrument data
|
|
void set_instrument_data(u8 const *data)
|
|
{
|
|
memcpy(&m_instdata[0], data, INSTDATA_SIZE);
|
|
}
|
|
|
|
// system-wide registers
|
|
u32 rhythm_enable() const { return byte(0x0e, 5, 1); }
|
|
u32 rhythm_keyon() const { return byte(0x0e, 4, 0); }
|
|
u32 test() const { return byte(0x0f, 0, 8); }
|
|
u32 waveform_enable() const { return 1; }
|
|
u32 timer_a_value() const { return 0; }
|
|
u32 timer_b_value() const { return 0; }
|
|
u32 status_mask() const { return 0; }
|
|
u32 irq_reset() const { return 0; }
|
|
u32 reset_timer_b() const { return 0; }
|
|
u32 reset_timer_a() const { return 0; }
|
|
u32 enable_timer_b() const { return 0; }
|
|
u32 enable_timer_a() const { return 0; }
|
|
u32 load_timer_b() const { return 0; }
|
|
u32 load_timer_a() const { return 0; }
|
|
u32 csm() const { return 0; }
|
|
|
|
// per-channel registers
|
|
u32 ch_block_freq(u32 choffs) const { return word(0x20, 0, 4, 0x10, 0, 8, choffs); }
|
|
u32 ch_sustain(u32 choffs) const { return byte(0x20, 5, 1, choffs); }
|
|
u32 ch_total_level(u32 choffs) const { return instchbyte(0x02, 0, 6, choffs); }
|
|
u32 ch_feedback(u32 choffs) const { return instchbyte(0x03, 0, 3, choffs); }
|
|
u32 ch_algorithm(u32 choffs) const { return 0; }
|
|
u32 ch_instrument(u32 choffs) const { return byte(0x30, 4, 4, choffs); }
|
|
u32 ch_output_any(u32 choffs) const { return 1; }
|
|
u32 ch_output_0(u32 choffs) const { return !is_rhythm(choffs); }
|
|
u32 ch_output_1(u32 choffs) const { return is_rhythm(choffs); }
|
|
u32 ch_output_2(u32 choffs) const { return 0; }
|
|
u32 ch_output_3(u32 choffs) const { return 0; }
|
|
|
|
// per-operator registers
|
|
u32 op_lfo_am_enable(u32 opoffs) const { return instopbyte(0x00, 7, 1, opoffs); }
|
|
u32 op_lfo_pm_enable(u32 opoffs) const { return instopbyte(0x00, 6, 1, opoffs); }
|
|
u32 op_eg_sustain(u32 opoffs) const { return instopbyte(0x00, 5, 1, opoffs); }
|
|
u32 op_ksr(u32 opoffs) const { return instopbyte(0x00, 4, 1, opoffs); }
|
|
u32 op_multiple(u32 opoffs) const { return instopbyte(0x00, 0, 4, opoffs); }
|
|
u32 op_ksl(u32 opoffs) const { return instopbyte(0x02, 6, 2, opoffs); }
|
|
u32 op_waveform(u32 opoffs) const { return instchbyte(0x03, 3 + BIT(opoffs, 0), 1, opoffs >> 1); }
|
|
u32 op_attack_rate(u32 opoffs) const { return instopbyte(0x04, 4, 4, opoffs); }
|
|
u32 op_decay_rate(u32 opoffs) const { return instopbyte(0x04, 0, 4, opoffs); }
|
|
u32 op_sustain_level(u32 opoffs) const { return instopbyte(0x06, 4, 4, opoffs); }
|
|
u32 op_release_rate(u32 opoffs) const { return instopbyte(0x06, 0, 4, opoffs); }
|
|
u32 op_volume(u32 opoffs) const { return byte(0x30, 4 * BIT(~opoffs, 0), 4, opoffs >> 1); }
|
|
|
|
private:
|
|
// return a bitfield extracted from a byte
|
|
u32 byte(u32 offset, u32 start, u32 count, u32 extra_offset = 0) const
|
|
{
|
|
return BIT(m_regdata[offset + extra_offset], start, count);
|
|
}
|
|
|
|
// return a bitfield extracted from a pair of bytes, MSBs listed first
|
|
u32 word(u32 offset1, u32 start1, u32 count1, u32 offset2, u32 start2, u32 count2, u32 extra_offset = 0) const
|
|
{
|
|
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
|
|
}
|
|
|
|
// helpers to read from instrument channel/operator data
|
|
u32 instchbyte(u32 offset, u32 start, u32 count, u32 choffs) const { return BIT(m_chinst[choffs][offset], start, count); }
|
|
u32 instopbyte(u32 offset, u32 start, u32 count, u32 opoffs) const { return BIT(m_opinst[opoffs][offset], start, count); }
|
|
|
|
// helper to determine if the this channel is an active rhythm channel
|
|
bool is_rhythm(u32 choffs) const
|
|
{
|
|
return rhythm_enable() && choffs >= 6;
|
|
}
|
|
|
|
// internal state
|
|
u16 m_lfo_am_counter; // LFO AM counter
|
|
u16 m_lfo_pm_counter; // LFO PM counter
|
|
u32 m_noise_lfsr; // noise LFSR state
|
|
u8 m_lfo_am; // current LFO AM value
|
|
u8 const *m_chinst[CHANNELS]; // pointer to instrument data for each channel
|
|
u8 const *m_opinst[OPERATORS]; // pointer to instrument data for each operator
|
|
u8 m_regdata[REGISTERS]; // register data
|
|
u8 m_instdata[INSTDATA_SIZE]; // instrument data
|
|
u16 m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
|
|
};
|
|
|
|
|
|
//*********************************************************
|
|
// CORE ENGINE CLASSES
|
|
//*********************************************************
|
|
|
|
// forward declarations
|
|
template<class RegisterType> class ymfm_engine_base;
|
|
|
|
// three different keyon sources; actual keyon is an OR over all of these
|
|
enum ymfm_keyon_type : u32
|
|
{
|
|
YMFM_KEYON_NORMAL = 0,
|
|
YMFM_KEYON_RHYTHM = 1,
|
|
YMFM_KEYON_CSM = 2
|
|
};
|
|
|
|
|
|
// ======================> ymfm_operator
|
|
|
|
// ymfm_operator represents an FM operator (or "slot" in FM parlance), which
|
|
// produces an output sine wave modulated by an envelope
|
|
template<class RegisterType>
|
|
class ymfm_operator
|
|
{
|
|
// "quiet" value, used to optimize when we can skip doing working
|
|
static constexpr u32 ENV_QUIET = 0x200;
|
|
|
|
public:
|
|
// constructor
|
|
ymfm_operator(ymfm_engine_base<RegisterType> &owner, u32 opoffs);
|
|
|
|
// register for save states
|
|
void save(device_t &device, u32 index);
|
|
|
|
// reset the operator state
|
|
void reset();
|
|
|
|
// set the current channel
|
|
void set_choffs(u32 choffs) { m_choffs = choffs; }
|
|
|
|
// prepare prior to clocking
|
|
bool prepare();
|
|
|
|
// master clocking function
|
|
void clock(u32 env_counter, s32 lfo_raw_pm);
|
|
|
|
// return the current phase value
|
|
u32 phase() const { return m_phase >> 10; }
|
|
|
|
// compute operator volume
|
|
s32 compute_volume(u32 phase, u32 am_offset) const;
|
|
|
|
// compute volume for the OPM noise channel
|
|
s32 compute_noise_volume(u32 am_offset) const;
|
|
|
|
// key state control
|
|
void keyonoff(u32 on, ymfm_keyon_type type);
|
|
|
|
// return a reference to our registers
|
|
RegisterType ®s() { return m_regs; }
|
|
|
|
private:
|
|
// start the attack phase
|
|
void start_attack();
|
|
|
|
// start the release phase
|
|
void start_release();
|
|
|
|
// clock phases
|
|
void clock_keystate(u32 keystate);
|
|
void clock_ssg_eg_state();
|
|
void clock_envelope(u32 env_counter);
|
|
void clock_phase(s32 lfo_raw_pm);
|
|
|
|
// return effective attenuation of the envelope
|
|
u32 envelope_attenuation(u32 am_offset) const;
|
|
|
|
// internal state
|
|
u32 m_choffs; // channel offset in registers
|
|
u32 m_opoffs; // operator offset in registers
|
|
u32 m_phase; // current phase value (10.10 format)
|
|
u16 m_env_attenuation; // computed envelope attenuation (4.6 format)
|
|
ymfm_envelope_state m_env_state; // current envelope state
|
|
u8 m_ssg_inverted; // non-zero if the output should be inverted (bit 0)
|
|
u8 m_key_state; // current key state: on or off (bit 0)
|
|
u8 m_keyon_live; // live key on state (bit 0 = direct, bit 1 = rhythm, bit 2 = CSM)
|
|
ymfm_opdata_cache m_cache; // cached values for performance
|
|
RegisterType &m_regs; // direct reference to registers
|
|
ymfm_engine_base<RegisterType> &m_owner; // reference to the owning engine
|
|
};
|
|
|
|
|
|
// ======================> ymfm_channel
|
|
|
|
// ymfm_channel represents an FM channel which combines the output of 2 or 4
|
|
// operators into a final result
|
|
template<class RegisterType>
|
|
class ymfm_channel
|
|
{
|
|
public:
|
|
// constructor
|
|
ymfm_channel(ymfm_engine_base<RegisterType> &owner, u32 choffs);
|
|
|
|
// register for save states
|
|
void save(device_t &device, u32 index);
|
|
|
|
// reset the channel state
|
|
void reset();
|
|
|
|
// assign operators
|
|
void assign(int index, ymfm_operator<RegisterType> *op)
|
|
{
|
|
assert(index < std::size(m_op));
|
|
m_op[index] = op;
|
|
if (op != nullptr)
|
|
op->set_choffs(m_choffs);
|
|
}
|
|
|
|
// signal key on/off to our operators
|
|
void keyonoff(u32 states, ymfm_keyon_type type);
|
|
|
|
// prepare prior to clocking
|
|
bool prepare();
|
|
|
|
// master clocking function
|
|
void clock(u32 env_counter, s32 lfo_raw_pm);
|
|
|
|
// specific 2-operator and 4-operator output handlers
|
|
void output_2op(s32 outputs[RegisterType::OUTPUTS], u32 rshift, s32 clipmax) const;
|
|
void output_4op(s32 outputs[RegisterType::OUTPUTS], u32 rshift, s32 clipmax) const;
|
|
|
|
// compute the special OPL rhythm channel outputs
|
|
void output_rhythm_ch6(s32 outputs[RegisterType::OUTPUTS], u32 rshift, s32 clipmax) const;
|
|
void output_rhythm_ch7(u32 phase_select, s32 outputs[RegisterType::OUTPUTS], u32 rshift, s32 clipmax) const;
|
|
void output_rhythm_ch8(u32 phase_select, s32 outputs[RegisterType::OUTPUTS], u32 rshift, s32 clipmax) const;
|
|
|
|
// are we a 4-operator channel or a 2-operator one?
|
|
bool is4op() const
|
|
{
|
|
if (RegisterType::DYNAMIC_OPS)
|
|
return (m_op[2] != nullptr);
|
|
return (RegisterType::OPERATORS / RegisterType::CHANNELS == 4);
|
|
}
|
|
|
|
// return a reference to our registers
|
|
RegisterType ®s() { return m_regs; }
|
|
|
|
private:
|
|
// helper to add values to the outputs based on channel enables
|
|
void add_to_output(u32 choffs, s32 *outputs, s32 value) const
|
|
{
|
|
if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs))
|
|
outputs[0] += value;
|
|
if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs))
|
|
outputs[1] += value;
|
|
if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs))
|
|
outputs[2] += value;
|
|
if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs))
|
|
outputs[3] += value;
|
|
}
|
|
|
|
// internal state
|
|
u32 m_choffs; // channel offset in registers
|
|
s16 m_feedback[2]; // feedback memory for operator 1
|
|
mutable s16 m_feedback_in; // next input value for op 1 feedback (set in output)
|
|
ymfm_operator<RegisterType> *m_op[4]; // up to 4 operators
|
|
RegisterType &m_regs; // direct reference to registers
|
|
ymfm_engine_base<RegisterType> &m_owner; // reference to the owning engine
|
|
};
|
|
|
|
|
|
// ======================> ymfm_engine_base
|
|
|
|
// ymfm_engine_base represents a set of operators and channels which together
|
|
// form a Yamaha FM core; chips that implement other engines (ADPCM, wavetable,
|
|
// etc) take this output and combine it with the others externally
|
|
template<class RegisterType>
|
|
class ymfm_engine_base
|
|
{
|
|
public:
|
|
// expose some constants from the registers
|
|
static constexpr u32 OUTPUTS = RegisterType::OUTPUTS;
|
|
static constexpr u32 CHANNELS = RegisterType::CHANNELS;
|
|
static constexpr u32 ALL_CHANNELS = RegisterType::ALL_CHANNELS;
|
|
static constexpr u32 OPERATORS = RegisterType::OPERATORS;
|
|
|
|
// also expose status flags for consumers that inject additional bits
|
|
static constexpr u8 STATUS_TIMERA = RegisterType::STATUS_TIMERA;
|
|
static constexpr u8 STATUS_TIMERB = RegisterType::STATUS_TIMERB;
|
|
static constexpr u8 STATUS_BUSY = RegisterType::STATUS_BUSY;
|
|
static constexpr u8 STATUS_IRQ = RegisterType::STATUS_IRQ;
|
|
|
|
// constructor
|
|
ymfm_engine_base(device_t &device);
|
|
|
|
// configuration helpers
|
|
auto irq_handler() { return m_irq_handler.bind(); }
|
|
|
|
// register for save states
|
|
void save(device_t &device);
|
|
|
|
// reset the overall state
|
|
void reset();
|
|
|
|
// master clocking function
|
|
u32 clock(u32 chanmask);
|
|
|
|
// compute sum of channel outputs
|
|
void output(s32 outputs[RegisterType::OUTPUTS], u32 rshift, s32 clipmax, u32 chanmask) const;
|
|
|
|
// write to the OPN registers
|
|
void write(u16 regnum, u8 data);
|
|
|
|
// return the current status
|
|
u8 status() const;
|
|
|
|
// set/reset bits in the status register, updating the IRQ status
|
|
u8 set_reset_status(u8 set, u8 reset)
|
|
{
|
|
m_status = (m_status | set) & ~reset;
|
|
schedule_check_interrupts();
|
|
return m_status;
|
|
}
|
|
|
|
// set the IRQ mask
|
|
void set_irq_mask(u8 mask) { m_irq_mask = mask; schedule_check_interrupts(); }
|
|
|
|
// helper to compute the busy duration
|
|
attotime compute_busy_duration(u32 cycles = 32)
|
|
{
|
|
return attotime::from_hz(m_device.clock()) * (cycles * m_clock_prescale);
|
|
}
|
|
|
|
// set the time when the busy flag in the status register should be cleared
|
|
void set_busy_end(attotime end) { m_busy_end = end; }
|
|
|
|
// return the current clock prescale
|
|
u32 clock_prescale() const { return m_clock_prescale; }
|
|
|
|
// set prescale factor (2/3/6)
|
|
void set_clock_prescale(u32 prescale) { m_clock_prescale = prescale; }
|
|
|
|
// compute sample rate
|
|
u32 sample_rate(u32 baseclock) const { return baseclock / (m_clock_prescale * OPERATORS); }
|
|
|
|
// reset the LFO state
|
|
void reset_lfo() { m_regs.reset_lfo(); }
|
|
|
|
// return the owning device
|
|
device_t &device() const { return m_device; }
|
|
|
|
// return a reference to our registers
|
|
RegisterType ®s() { return m_regs; }
|
|
|
|
protected:
|
|
// assign the current set of operators to channels
|
|
void assign_operators();
|
|
|
|
// update the state of the given timer
|
|
void update_timer(u32 which, u32 enable);
|
|
|
|
// timer callback
|
|
TIMER_CALLBACK_MEMBER(timer_handler);
|
|
|
|
// schedule an interrupt check
|
|
void schedule_check_interrupts();
|
|
|
|
// check interrupts
|
|
TIMER_CALLBACK_MEMBER(check_interrupts);
|
|
|
|
// handle a mode register write
|
|
TIMER_CALLBACK_MEMBER(synced_mode_w);
|
|
|
|
// internal state
|
|
device_t &m_device; // reference to the owning device
|
|
u32 m_env_counter; // envelope counter; low 2 bits are sub-counter
|
|
u8 m_status; // current status register
|
|
u8 m_clock_prescale; // prescale factor (2/3/6)
|
|
u8 m_irq_mask; // mask of which bits signal IRQs
|
|
u8 m_irq_state; // current IRQ state
|
|
u32 m_active_channels; // mask of active channels (computed by prepare)
|
|
u32 m_modified_channels; // mask of channels that have been modified
|
|
u32 m_prepare_count; // counter to do periodic prepare sweeps
|
|
attotime m_busy_end; // end of the busy time
|
|
emu_timer *m_timer[2]; // our two timers
|
|
devcb_write_line m_irq_handler; // IRQ callback
|
|
RegisterType m_regs; // register accessor
|
|
std::unique_ptr<ymfm_channel<RegisterType>> m_channel[CHANNELS]; // channel pointers
|
|
std::unique_ptr<ymfm_operator<RegisterType>> m_operator[OPERATORS]; // operator pointers
|
|
};
|
|
|
|
|
|
// ======================> template instantiations
|
|
|
|
extern template class ymfm_engine_base<ymopm_registers>;
|
|
extern template class ymfm_engine_base<ymopn_registers>;
|
|
extern template class ymfm_engine_base<ymopna_registers>;
|
|
extern template class ymfm_engine_base<ymopl_registers>;
|
|
extern template class ymfm_engine_base<ymopl2_registers>;
|
|
extern template class ymfm_engine_base<ymopl3_registers>;
|
|
|
|
using ymopm_engine = ymfm_engine_base<ymopm_registers>;
|
|
using ymopn_engine = ymfm_engine_base<ymopn_registers>;
|
|
using ymopna_engine = ymfm_engine_base<ymopna_registers>;
|
|
using ymopl_engine = ymfm_engine_base<ymopl_registers>;
|
|
using ymopl2_engine = ymfm_engine_base<ymopl2_registers>;
|
|
using ymopl3_engine = ymfm_engine_base<ymopl3_registers>;
|
|
using ymopl4_engine = ymfm_engine_base<ymopl4_registers>;
|
|
|
|
|
|
// ======================> ymopll_engine
|
|
|
|
// ymopll_engine is a special case because instrument data needs to be
|
|
// provided from an external source
|
|
class ymopll_engine : public ymfm_engine_base<ymopll_registers>
|
|
{
|
|
public:
|
|
// constructor
|
|
ymopll_engine(device_t &device) :
|
|
ymfm_engine_base(device)
|
|
{
|
|
}
|
|
|
|
// set the instrument data
|
|
void set_instrument_data(u8 const *data)
|
|
{
|
|
m_regs.set_instrument_data(data);
|
|
}
|
|
};
|
|
|
|
|
|
#endif // MAME_SOUND_YMFM_H
|