summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/ymfm.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/ymfm.h')
-rw-r--r--src/devices/sound/ymfm.h1495
1 files changed, 1033 insertions, 462 deletions
diff --git a/src/devices/sound/ymfm.h b/src/devices/sound/ymfm.h
index 06bde003c9b..b452e15b944 100644
--- a/src/devices/sound/ymfm.h
+++ b/src/devices/sound/ymfm.h
@@ -6,71 +6,6 @@
#pragma once
-//
-// Implementation notes:
-//
-//
-// REGISTER CLASSES
-//
-// OPM and OPN are very closely related, and thus share a common engine
-// and implementation. Differentiation is provided by the various registers
-// classes, which are specified as template parameters to the shared
-// implementation.
-//
-// There are currently three register classes:
-//
-// ymopm_registers: OPM (YM2151)
-// ymopn_registers: OPN (YM2203)
-// ymopna_registers: OPNA (YM2608) / OPNB (YM2610/B) / OPN2 (YM2612/YM3438)
-//
-//
-// FREQUENCIES
-//
-// One major difference between OPM and OPN is in how frequencies are
-// specified. OPM specifies frequency via a 3-bit 'block' (aka octave),
-// combined with a 4-bit 'key code' (note number) and a 6-bit 'key
-// fraction'. The key code and fraction are converted on the chip
-// into an x.11 fixed-point value and then shifted by the block to
-// produce the final step value for the phase.
-//
-// OPN, on the other hand, specifies frequencies via a 3-bit 'block'
-// just as on OPM, but combined with an 11-bit 'frequency number' or
-// 'fnum', which is directly shifted by the block to produce the step
-// value. So essentially, OPN makes the user do the conversion from
-// note value to phase increment, while OPM is programmed in a more
-// 'musical' way, specifying notes and cents.
-//
-// Interally, this is abstracted away into a 'block_freq' value,
-// which is a 16-bit value containing the block and frequency info
-// concatenated together as follows:
-//
-// OPM: [3-bit block]:[4-bit keycode]:[6-bit fraction] = 13 bits total
-//
-// OPN: [3-bit block]:[11-bit fnum] = 14 bits total
-//
-// Template specialization in functions that interpret the 'block_freq'
-// value is used to deconstruct it appropriately (specifically, see
-// clock_phase).
-//
-//
-// LOW FREQUENCY OSCILLATOR (LFO)
-//
-// The LFO engines are different in several key ways. The OPM LFO
-// engine is fairly intricate. It has a 4.4 floating-point rate which
-// allows for a huge range of frequencies, and can select between four
-// different waveforms (sawtooth, square, triangle, or noise). Separate
-// 7-bit depth controls for AM and PM control the amount of modulation
-// applied in each case. This global LFO value is then further controlled
-// at the channel level by a 2-bit AM sensitivity and a 3-bit PM
-// sensitivity, and each operator has a 1-bit AM on/off switch.
-//
-// For OPN the LFO engine was removed entirely, but a limited version
-// was put back in OPNA and later chips. This stripped-down version
-// offered only a 3-bit rate setting (versus the 4.4 floating-point rate
-// in OPN), and no depth control. It did bring back the channel-level
-// sensitivity controls and the operator-level on/off control.
-//
-
//*********************************************************
// MACROS
@@ -83,70 +18,210 @@
#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
{
-protected:
- // constructor
- ymfm_registers_base(std::vector<u8> &regdata, u16 chbase = 0, u16 opbase = 0) :
- m_chbase(chbase),
- m_opbase(opbase),
- m_regdata(regdata)
- {
- }
-
public:
- // system-wide registers that aren't universally supported
- u8 noise_frequency() const /* 5 bits */ { return 0; } // not on OPN,OPNA
- u8 noise_enabled() const /* 1 bit */ { return 0; } // not on OPN,OPNA
- u8 lfo_enabled() const /* 1 bit */ { return 0; } // not on OPM,OPN
- u8 lfo_rate() const /*3-8 bits */ { return 0; } // not on OPN
- u8 lfo_waveform() const /* 2 bits */ { return 0; } // not on OPN,OPNA
- u8 lfo_pm_depth() const /* 7 bits */ { return 0; } // not on OPN,OPNA
- u8 lfo_am_depth() const /* 7 bits */ { return 0; } // not on OPN,OPNA
- u8 multi_freq() const /* 1 bit */ { return 0; } // not on OPM
- u16 multi_block_freq0() const /* 14 bits */ { return 0; } // not on OPM
- u16 multi_block_freq1() const /* 14 bits */ { return 0; } // not on OPM
- u16 multi_block_freq2() const /* 14 bits */ { return 0; } // not on OPM
-
- // per-channel registers that aren't universally supported
- u8 pan_right() const /* 1 bit */ { return 1; } // not on OPN
- u8 pan_left() const /* 1 bit */ { return 1; } // not on OPN
- u8 lfo_pm_sensitivity() const /* 3 bits */ { return 0; } // not on OPN
- u8 lfo_am_sensitivity() const /* 2 bits */ { return 0; } // not on OPN
-
- // per-operator registers that aren't universally supported
- u8 lfo_am_enabled() const /* 1 bit */ { return 0; } // not on OPN
- u8 detune2() const /* 2 bits */ { return 0; } // not on OPN,OPN2
- u8 ssg_eg_enabled() const /* 1 bit */ { return 0; } // not on OPM
- u8 ssg_eg_mode() const /* 1 bit */ { return 0; } // not on OPM
+ // 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:
- // return a bitfield extracted from a byte
- u8 sysbyte(u16 offset, u8 start, u8 count) const
+ // 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 BIT(m_regdata[offset], start, count);
+ return o1 | (o2 << 8) | (o3 << 16) | (o4 << 24);
}
- u8 chbyte(u16 offset, u8 start, u8 count) const { return sysbyte(offset + m_chbase, start, count); }
- u8 opbyte(u16 offset, u8 start, u8 count) const { return sysbyte(offset + m_opbase, start, count); }
- // return a bitfield extracted from a pair of bytes, MSBs listed first
- u16 sysword(u16 offset1, u8 start1, u8 count1, u16 offset2, u8 start2, u8 count2) const
+ // 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 (sysbyte(offset1, start1, count1) << count2) | sysbyte(offset2, start2, count2);
+ return (rawrate == 0) ? 0 : std::min<u32>(rawrate + ksr, 63);
}
- u16 chword(u16 offset1, u8 start1, u8 count1, u16 offset2, u8 start2, u8 count2) const { return sysword(offset1 + m_chbase, start1, count1, offset2 + m_chbase, start2, count2); }
-
- // internal state
- u16 m_chbase; // base offset for channel-specific data
- u16 m_opbase; // base offset for operator-specific data
- std::vector<u8> &m_regdata; // reference to the raw data
};
@@ -180,7 +255,7 @@ protected:
// ------xx W
//
// Per-channel registers (channel in address bits 0-2)
-// 20-27 xx------ Pan right
+// 20-27 x------- Pan right
// -x------ Pan left
// --xxx--- Feedback level for operator 1 (0-7)
// -----xxx Operator connection algorithm (0-7)
@@ -202,124 +277,166 @@ protected:
// 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 u8 DEFAULT_PRESCALE = 2;
- static constexpr u8 CHANNELS = 8;
- static constexpr u8 CSM_TRIGGER_MASK = 0xff;
- static constexpr u16 REGISTERS = 0x100;
- static constexpr u16 REG_MODE = 0x14;
- static constexpr u16 REG_KEYON = 0x08;
+ 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(std::vector<u8> &regdata, u16 chbase = 0, u16 opbase = 0) :
- ymfm_registers_base(regdata, chbase, opbase)
- {
- }
+ ymopm_registers();
- // return channel/operator number
- u8 chnum() const { return BIT(m_chbase, 0, 3); }
- u8 opnum() const { return BIT(m_opbase, 4) | (BIT(m_opbase, 3) << 1); }
+ // register for save states
+ void save(device_t &device);
- // reset state to default values
- void reset()
+ // reset to initial state
+ void reset();
+
+ // map channel number to register offset
+ static constexpr u32 channel_offset(u32 chnum)
{
- // enable output on both channels by default
- m_regdata[0x20] = m_regdata[0x21] = m_regdata[0x22] = m_regdata[0x23] = 0xc0;
- m_regdata[0x24] = m_regdata[0x25] = m_regdata[0x26] = m_regdata[0x27] = 0xc0;
+ assert(chnum < CHANNELS);
+ return chnum;
}
- // write access
- void write(u16 index, u8 data)
+ // map operator number to register offset
+ static constexpr u32 operator_offset(u32 opnum)
{
- // LFO AM/PM depth are written to the same register (0x19);
- // redirect the PM depth to an unused neighbor (0x1a)
- if (index == 0x19)
- m_regdata[index + BIT(data, 7)] = data;
- else if (index != 0x1a)
- m_regdata[index] = data;
+ assert(opnum < OPERATORS);
+ return opnum;
}
- // create a new version of ourself with a different channel/operator base
- ymopm_registers channel_registers(u8 chnum) { return ymopm_registers(m_regdata, channel_offset(chnum)); }
- ymopm_registers operator_registers(u8 opnum) { return ymopm_registers(m_regdata, m_chbase, m_chbase + operator_offset(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
- u8 test() const /* 8 bits */ { return sysbyte(0x01, 0, 8); }
- u8 keyon_states() const /* 4 bits */ { return sysbyte(0x08, 3, 4); }
- u8 keyon_channel() const /* 3 bits */ { return sysbyte(0x08, 0, 3); }
- u8 noise_frequency() const /* 5 bits */ { return sysbyte(0x0f, 0, 5); }
- u8 noise_enabled() const /* 1 bit */ { return sysbyte(0x0f, 7, 1); }
- u16 timer_a_value() const /* 10 bits */ { return sysword(0x10, 0, 8, 0x11, 0, 2); }
- u8 timer_b_value() const /* 8 bits */ { return sysbyte(0x12, 0, 8); }
- u8 csm() const /* 1 bit */ { return sysbyte(0x14, 7, 1); }
- u8 reset_timer_b() const /* 1 bit */ { return sysbyte(0x14, 5, 1); }
- u8 reset_timer_a() const /* 1 bit */ { return sysbyte(0x14, 4, 1); }
- u8 enable_timer_b() const /* 1 bit */ { return sysbyte(0x14, 3, 1); }
- u8 enable_timer_a() const /* 1 bit */ { return sysbyte(0x14, 2, 1); }
- u8 load_timer_b() const /* 1 bit */ { return sysbyte(0x14, 1, 1); }
- u8 load_timer_a() const /* 1 bit */ { return sysbyte(0x14, 0, 1); }
- u8 lfo_rate() const /* 8 bits */ { return sysbyte(0x18, 0, 8); }
- u8 lfo_am_depth() const /* 7 bits */ { return sysbyte(0x19, 0, 7); }
- u8 lfo_pm_depth() const /* 7 bits */ { return sysbyte(0x1a, 0, 7); }
- u8 lfo_waveform() const /* 2 bits */ { return sysbyte(0x1b, 0, 2); }
+ 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
- u8 pan_right() const /* 1 bit */ { return chbyte(0x20, 7, 1); }
- u8 pan_left() const /* 1 bit */ { return chbyte(0x20, 6, 1); }
- u8 feedback() const /* 3 bits */ { return chbyte(0x20, 3, 3); }
- u8 algorithm() const /* 3 bits */ { return chbyte(0x20, 0, 3); }
- u16 block_freq() const /* 13 bits */ { return chword(0x28, 0, 7, 0x30, 2, 6); }
- u8 lfo_pm_sensitivity() const /* 3 bits */ { return chbyte(0x38, 4, 3); }
- u8 lfo_am_sensitivity() const /* 2 bits */ { return chbyte(0x38, 0, 2); }
+ 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
- u8 detune() const /* 3 bits */ { return opbyte(0x40, 4, 3); }
- u8 multiple() const /* 4 bits */ { return opbyte(0x40, 0, 4); }
- u8 total_level() const /* 7 bits */ { return opbyte(0x60, 0, 7); }
- u8 ksr() const /* 2 bits */ { return opbyte(0x80, 6, 2); }
- u8 attack_rate() const /* 5 bits */ { return opbyte(0x80, 0, 5); }
- u8 lfo_am_enabled() const /* 1 bit */ { return opbyte(0xa0, 7, 1); }
- u8 decay_rate() const /* 5 bits */ { return opbyte(0xa0, 0, 5); }
- u8 detune2() const /* 2 bits */ { return opbyte(0xc0, 6, 2); }
- u8 sustain_rate() const /* 5 bits */ { return opbyte(0xc0, 0, 5); }
- u8 sustain_level() const /* 4 bits */ { return opbyte(0xe0, 4, 4); }
- u8 release_rate() const /* 4 bits */ { return opbyte(0xe0, 0, 4); }
-
- // LFO is always enabled
- u8 lfo_enabled() const { return 1; }
-
- // special helper for generically getting the attack/decay/statain/release rates
- u8 adsr_rate(u8 state) const
- {
- // attack/decay/sustain are identical
- if (state < 3)
- return opbyte(0x80 + (state << 5), 0, 5);
+ 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); }
- // release encodes 4 bits and expands them
- else
- return opbyte(0xe0, 0, 4) * 2 + 1;
+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);
}
-protected:
- // convert a channel number into a register offset; channel goes into the low 3 bits
- static constexpr u8 channel_offset(u8 chnum) { return BIT(chnum, 0, 3); }
+ // 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);
+ }
- // convert an operator number into a register offset; operator goes into bits 3-4
- static constexpr u8 operator_offset(u8 opnum) { return (BIT(opnum, 0) << 4) | (BIT(opnum, 1) << 3); }
+ // 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
+// ======================> 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
@@ -336,403 +453,832 @@ protected:
// ---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 ---xxxxx Decay 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)
//
-// Per-channel registers (channel in address bits 0-1)
-// 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)
-//
// 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)
+//
-class ymopn_registers : public ymfm_registers_base
+template<bool IsOpnA>
+class ymopn_registers_base : public ymfm_registers_base
{
public:
// constants
- static constexpr u8 DEFAULT_PRESCALE = 6;
- static constexpr u8 CHANNELS = 3;
- static constexpr u8 CSM_TRIGGER_MASK = 1 << 2;
- static constexpr u16 REGISTERS = 0x100;
- static constexpr u16 REG_MODE = 0x27;
- static constexpr u16 REG_KEYON = 0x28;
+ 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(std::vector<u8> &regdata, u16 chbase = 0, u16 opbase = 0) :
- ymfm_registers_base(regdata, chbase, opbase)
- {
- }
+ ymopn_registers_base();
- // return channel/operator number
- u8 chnum() const { return BIT(m_chbase, 0, 2); }
- u8 opnum() const { return BIT(m_opbase, 3) | (BIT(m_opbase, 2) << 1); }
+ // register for save states
+ void save(device_t &device);
- // reset state to default values
- void reset()
+ // 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);
}
- // write access
- void write(u16 index, u8 data)
+ // map operator number to register offset
+ static constexpr u32 operator_offset(u32 opnum)
{
- // writes in the 0xa0-af/0x1a0-af region are handled as latched pairs
- // borrow unused registers 0xb8-bf/0x1b8-bf as temporary holding locations
- if ((index & 0xf0) == 0xa0)
- {
- u16 latchindex = (index & 0x100) | 0xb8 | (BIT(index, 3) << 2) | BIT(index, 0, 2);
-
- // writes to the upper half just latch (only low 6 bits matter)
- if (BIT(index, 2))
- m_regdata[latchindex] = data | 0x80;
-
- // writes to the lower half only commit if the latch is there
- else if (BIT(m_regdata[latchindex], 7))
- {
- m_regdata[index | 4] = m_regdata[latchindex] & 0x3f;
- m_regdata[latchindex] = 0;
- }
- }
-
- // everything else is normal
- m_regdata[index] = data;
+ assert(opnum < OPERATORS);
+ if (!IsOpnA)
+ return opnum + opnum / 3;
+ else
+ return (opnum % 12) + ((opnum % 12) / 3) + 0x100 * (opnum / 12);
}
- // create a new version of ourself with a different channel/operator base
- ymopn_registers channel_registers(u8 chnum) { return ymopn_registers(m_regdata, channel_offset(chnum)); }
- ymopn_registers operator_registers(u8 opnum) { return ymopn_registers(m_regdata, m_chbase, m_chbase + operator_offset(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 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
- u8 test() const /* 8 bits */ { return sysbyte(0x21, 0, 8); }
- u16 timer_a_value() const /* 10 bits */ { return sysword(0x24, 0, 8, 0x25, 0, 2); }
- u8 timer_b_value() const /* 8 bits */ { return sysbyte(0x26, 0, 8); }
- u8 csm() const /* 2 bits */ { return (sysbyte(0x27, 6, 2) == 2); }
- u8 multi_freq() const /* 2 bits */ { return (sysbyte(0x27, 6, 2) != 0); }
- u8 reset_timer_b() const /* 1 bit */ { return sysbyte(0x27, 5, 1); }
- u8 reset_timer_a() const /* 1 bit */ { return sysbyte(0x27, 4, 1); }
- u8 enable_timer_b() const /* 1 bit */ { return sysbyte(0x27, 3, 1); }
- u8 enable_timer_a() const /* 1 bit */ { return sysbyte(0x27, 2, 1); }
- u8 load_timer_b() const /* 1 bit */ { return sysbyte(0x27, 1, 1); }
- u8 load_timer_a() const /* 1 bit */ { return sysbyte(0x27, 0, 1); }
- u8 keyon_states() const /* 4 bits */ { return sysbyte(0x28, 4, 4); }
- u8 keyon_channel() const /* 2 bits */ { return sysbyte(0x28, 0, 2); }
- u16 multi_block_freq0() const /* 14 bits */ { return sysword(0xac, 0, 6, 0xa8, 0, 8); }
- u16 multi_block_freq1() const /* 14 bits */ { return sysword(0xad, 0, 6, 0xa9, 0, 8); }
- u16 multi_block_freq2() const /* 14 bits */ { return sysword(0xae, 0, 6, 0xaa, 0, 8); }
+ 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, 6, 2) == 2); }
+ 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
- u16 block_freq() const /* 14 bits */ { return chword(0xa4, 0, 6, 0xa0, 0, 8); }
- u8 feedback() const /* 3 bits */ { return chbyte(0xb0, 3, 3); }
- u8 algorithm() const /* 3 bits */ { return chbyte(0xb0, 0, 3); }
+ 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
- u8 detune() const /* 3 bits */ { return opbyte(0x30, 4, 3); }
- u8 multiple() const /* 4 bits */ { return opbyte(0x30, 0, 4); }
- u8 total_level() const /* 8 bits */ { return opbyte(0x40, 0, 7); }
- u8 ksr() const /* 2 bits */ { return opbyte(0x50, 6, 2); }
- u8 attack_rate() const /* 5 bits */ { return opbyte(0x50, 0, 5); }
- u8 decay_rate() const /* 5 bits */ { return opbyte(0x60, 0, 5); }
- u8 sustain_rate() const /* 5 bits */ { return opbyte(0x70, 0, 5); }
- u8 sustain_level() const /* 4 bits */ { return opbyte(0x80, 4, 4); }
- u8 release_rate() const /* 4 bits */ { return opbyte(0x80, 0, 4); }
- u8 ssg_eg_enabled() const /* 1 bit */ { return opbyte(0x90, 3, 1); }
- u8 ssg_eg_mode() const /* 3 bits */ { return opbyte(0x90, 0, 3); }
-
- // special helper for generically getting the attack/decay/statain/release rates
- u8 adsr_rate(u8 state) const
- {
- // attack/decay/sustain are identical
- if (state < 3)
- return opbyte(0x50 + (state << 4), 0, 5);
+ 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); }
- // release encodes 4 bits and expands them
- else
- return opbyte(0x80, 0, 4) * 2 + 1;
+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);
}
-protected:
- // convert a channel number into a register offset; channel goes in low 2 bits
- static constexpr u16 channel_offset(u8 chnum) { return BIT(chnum, 0, 2); }
+ // 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);
+ }
- // convert an operator number into a register offset; operator goes into bits 2-3
- static constexpr u8 operator_offset(u8 opnum) { return (BIT(opnum, 0) << 3) | (BIT(opnum, 1) << 2); }
+ // internal state
+ u32 m_lfo_counter; // LFO counter
+ u8 m_lfo_am; // current LFO AM value
+ 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>;
+
-// ======================> ymopna_registers
+// ======================> ymopl_registers_base
//
-// OPNA/OPNB/OPNB2/OPN2 register map:
+// OPL/OPL2/OPL3/OPL4 register map:
//
// System-wide registers:
-// 21 xxxxxxxx Test register
-// 22 ----x--- LFO enable (new for OPNA)
-// -----xxx LFO rate (new for OPNA)
-// 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
+// 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
-// 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
-// -----x-- Upper channel select (new for OPNA)
-// ------xx Channel select
+// 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-operator registers (channel in address bits 0-1, operator in bits 2-3)
-// 30-3F -xxx---- Detune value (0-7)
+// 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-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 (new for OPNA)
-// ---xxxxx Decay rate (0-31)
-// 70-7F ---xxxxx Sustain rate (0-31)
-// 80-8F xxxx---- Sustain level (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)
-// 90-9F ----x--- SSG-EG enable
-// -----xxx SSG-EG envelope (0-7)
+// E0-F5 ------xx Wave select (0-3) [OPL2 only]
+// -----xxx Wave select (0-7) [OPL3+ only]
//
-// Per-channel registers (channel in address bits 0-1)
-// 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 (new for OPNA)
-// -x------ Pan right (new for OPNA)
-// --xx---- LFO AM shift (0-3) (new for OPNA)
-// -----xxx LFO PM depth (0-7) (new for OPNA)
+
+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 byte(0x04, 5, 1) ^ 1; }
+ u32 enable_timer_a() const { return byte(0x04, 6, 1) ^ 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 IsOpl3Plus ? byte(0xc0 + choffs, 4, 4) : 1; }
+ u32 ch_output_0(u32 choffs) const { return IsOpl3Plus ? byte(0xc0 + choffs, 4, 1) : 1; }
+ u32 ch_output_1(u32 choffs) const { return IsOpl3Plus ? byte(0xc0 + choffs, 5, 1) : 0; }
+ u32 ch_output_2(u32 choffs) const { return IsOpl3Plus ? byte(0xc0 + choffs, 6, 1) : 0; }
+ u32 ch_output_3(u32 choffs) const { return IsOpl3Plus ? 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, IsOpl3Plus ? 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
+
//
-// 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
+// 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 ymopna_registers : public ymopn_registers
+class ymopll_registers : public ymfm_registers_base
{
public:
- // constants
- static constexpr u8 CHANNELS = 6;
- static constexpr u16 REGISTERS = 0x200;
+ 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
- ymopna_registers(std::vector<u8> &regdata, u16 chbase = 0, u16 opbase = 0) :
- ymopn_registers(regdata, chbase, opbase)
+ 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 channel/operator number
- u8 chnum() const { return BIT(m_chbase, 0, 2) + 3 * BIT(m_chbase, 8); }
+ // 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; }
- // reset state to default values
- void reset()
+ // 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)
{
- // enable output on both channels by default
- m_regdata[0xb4] = m_regdata[0xb5] = m_regdata[0xb6] = 0xc0;
- m_regdata[0x1b4] = m_regdata[0x1b5] = m_regdata[0x1b6] = 0xc0;
+ memcpy(&m_instdata[0], data, INSTDATA_SIZE);
}
- // create a new version of ourself with a different channel/operator base
- ymopna_registers channel_registers(u8 chnum) { return ymopna_registers(m_regdata, channel_offset(chnum)); }
- ymopna_registers operator_registers(u8 opnum) { return ymopna_registers(m_regdata, m_chbase, m_chbase + operator_offset(opnum)); }
+ // 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; }
- // OPNA-specific system-wide registers
- u8 lfo_enabled() const /* 3 bits */ { return sysbyte(0x22, 3, 1); }
- u8 lfo_rate() const /* 3 bits */ { return sysbyte(0x22, 0, 3); }
- u8 keyon_channel() const /* 3 bits */
+ // 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
{
- // ensure that both 3 and 7 return out-of-range values
- u8 temp = sysbyte(0x28, 0, 3);
- return (temp == 3) ? 6 : temp - BIT(temp, 2);
+ return BIT(m_regdata[offset + extra_offset], start, count);
}
- // OPNA-specific per-channel registers
- u8 pan_left() const /* 1 bit */ { return chbyte(0xb4, 7, 1); }
- u8 pan_right() const /* 1 bit */ { return chbyte(0xb4, 6, 1); }
- u8 lfo_am_sensitivity() const /* 2 bits */ { return chbyte(0xb4, 4, 2); }
- u8 lfo_pm_sensitivity() const /* 3 bits */ { return chbyte(0xb4, 0, 3); }
+ // 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);
+ }
- // OPNA-specific per-operator registers
- u8 lfo_am_enabled() const /* 1 bit */ { return opbyte(0x60, 7, 1); }
+ // 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); }
-protected:
- // convert a channel number into a register offset
- static constexpr u16 channel_offset(u8 chnum) { return chnum % 3 + ((chnum / 3) << 8); }
-};
+ // 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
{
- enum envelope_state : u8
- {
- ENV_ATTACK = 0,
- ENV_DECAY = 1,
- ENV_SUSTAIN = 2,
- ENV_RELEASE = 3
- };
- static constexpr u16 ENV_QUIET = 0x200;
+ // "quiet" value, used to optimize when we can skip doing working
+ static constexpr u32 ENV_QUIET = 0x200;
public:
// constructor
- ymfm_operator(RegisterType regs);
+ ymfm_operator(ymfm_engine_base<RegisterType> &owner, u32 opoffs);
// register for save states
- void save(device_t &device, u8 index);
+ 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, s8 lfo_raw_pm, u16 block_freq);
+ void clock(u32 env_counter, s32 lfo_raw_pm);
+
+ // return the current phase value
+ u32 phase() const { return m_phase >> 10; }
// compute operator volume
- s16 compute_volume(u16 modulation, u16 am_offset) const;
+ s32 compute_volume(u32 phase, u32 am_offset) const;
// compute volume for the OPM noise channel
- s16 compute_noise_volume(u8 noise_state, u16 am_offset) const;
+ s32 compute_noise_volume(u32 am_offset) const;
// key state control
- void keyonoff(u8 on) { m_keyon = on; }
- void keyon_csm() { m_csm_triggered = 1; }
+ void keyonoff(u32 on, ymfm_keyon_type type);
- // are we active?
- bool active() const { return (m_env_state != ENV_RELEASE || m_env_attenuation < ENV_QUIET); }
+ // return a reference to our registers
+ RegisterType &regs() { return m_regs; }
private:
- // convert the generic block_freq into a 5-bit keycode
- u8 block_freq_to_keycode(u16 block_freq);
-
- // return the effective 6-bit ADSR rate after adjustments
- u8 effective_rate(u8 rawrate, u8 keycode);
-
// start the attack phase
- void start_attack(u8 keycode);
+ void start_attack();
// start the release phase
void start_release();
// clock phases
- void clock_keystate(u8 keystate, u8 keycode);
- void clock_ssg_eg_state(u8 keycode);
- void clock_envelope(u16 env_counter, u8 keycode);
- void clock_phase(s8 lfo_raw_pm, u16 block_freq);
+ 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
- u16 envelope_attenuation(u8 am_offset) const;
+ 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)
- envelope_state m_env_state; // current envelope state
+ 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 key on state (bit 0)
- u8 m_csm_triggered; // true if a CSM key on has been triggered (bit 0)
- RegisterType m_regs; // operator-specific registers
+ 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
};
-template<>
-u8 ymfm_operator<ymopm_registers>::block_freq_to_keycode(u16 block_freq);
-
-template<>
-void ymfm_operator<ymopm_registers>::clock_phase(s8 lfo_raw_pm, u16 block_freq);
-
// ======================> 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(RegisterType regs);
+ ymfm_channel(ymfm_engine_base<RegisterType> &owner, u32 choffs);
// register for save states
- void save(device_t &device, u8 index);
+ 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(u8 states);
+ void keyonoff(u32 states, ymfm_keyon_type type);
- // signal CSM key on to our operators
- void keyon_csm();
+ // prepare prior to clocking
+ bool prepare();
// master clocking function
- void clock(u32 env_counter, s8 lfo_raw_pm, bool is_multi_freq);
+ 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 channel output and add to the left/right output sums
- void output(u8 lfo_raw_am, u8 noise_state, s32 &lsum, s32 &rsum, u8 rshift, s16 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);
+ }
- // is this channel active?
- bool active() const { return m_op1.active() || m_op2.active() || m_op3.active() || m_op4.active(); }
+ // return a reference to our registers
+ RegisterType &regs() { return m_regs; }
private:
- // convert a 6/8-bit raw AM value into an amplitude offset based on sensitivity
- u16 lfo_am_offset(u8 am_value) const;
+ // 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_op1; // operator 1
- ymfm_operator<RegisterType> m_op2; // operator 2
- ymfm_operator<RegisterType> m_op3; // operator 3
- ymfm_operator<RegisterType> m_op4; // operator 4
- RegisterType m_regs; // channel-specific registers
+ 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
};
-template<>
-u16 ymfm_channel<ymopm_registers>::lfo_am_offset(u8 lfo_raw_am) const;
-
// ======================> 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:
- enum : u8
- {
- STATUS_TIMERA = 0x01,
- STATUS_TIMERB = 0x02,
- STATUS_BUSY = 0x80
- };
+ // 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);
@@ -740,10 +1286,10 @@ public:
void reset();
// master clocking function
- u32 clock(u8 chanmask);
+ u32 clock(u32 chanmask);
// compute sum of channel outputs
- void output(s32 &lsum, s32 &rsum, u8 rshift, s16 clipmax, u8 chanmask) const;
+ void output(s32 outputs[RegisterType::OUTPUTS], u32 rshift, s32 clipmax, u32 chanmask) const;
// write to the OPN registers
void write(u16 regnum, u8 data);
@@ -752,7 +1298,12 @@ public:
u8 status() const;
// set/reset bits in the status register, updating the IRQ status
- void set_reset_status(u8 set, u8 reset) { m_status = (m_status | set) & ~reset; schedule_check_interrupts(); }
+ u8 set_reset_status(u8 set, u8 reset)
+ {
+ m_status = (m_status | set) & ~reset & ~m_regs.status_mask();
+ schedule_check_interrupts();
+ return m_status;
+ }
// set the IRQ mask
void set_irq_mask(u8 mask) { m_irq_mask = mask; schedule_check_interrupts(); }
@@ -767,26 +1318,29 @@ public:
void set_busy_end(attotime end) { m_busy_end = end; }
// return the current clock prescale
- u8 clock_prescale() const { return m_clock_prescale; }
+ u32 clock_prescale() const { return m_clock_prescale; }
// set prescale factor (2/3/6)
- void set_clock_prescale(u8 prescale) { m_clock_prescale = prescale; }
+ void set_clock_prescale(u32 prescale) { m_clock_prescale = prescale; }
- // configuration helpers
- auto irq_handler() { return m_irq_handler.bind(); }
+ // compute sample rate
+ u32 sample_rate(u32 baseclock) const { return baseclock / (m_clock_prescale * OPERATORS); }
// reset the LFO state
- void reset_lfo() { m_lfo_counter = 0; }
+ void reset_lfo() { m_regs.reset_lfo(); }
-private:
- // clock the LFO, updating m_lfo_am and return the signed PM value
- s8 clock_lfo();
+ // return the owning device
+ device_t &device() const { return m_device; }
- // clock the noise generator
- void clock_noise();
+ // return a reference to our registers
+ RegisterType &regs() { 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(u8 which, u8 enable);
+ void update_timer(u32 which, u32 enable);
// timer callback
TIMER_CALLBACK_MEMBER(timer_handler);
@@ -803,12 +1357,6 @@ private:
// internal state
device_t &m_device; // reference to the owning device
u32 m_env_counter; // envelope counter; low 2 bits are sub-counter
- 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_status; // current status register
u8 m_clock_prescale; // prescale factor (2/3/6)
u8 m_irq_mask; // mask of which bits signal IRQs
@@ -819,26 +1367,49 @@ private:
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
- std::unique_ptr<ymfm_channel<RegisterType>> m_channel[RegisterType::CHANNELS]; // channel pointers
- std::vector<u8> m_regdata; // raw register data
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<>
-s8 ymfm_engine_base<ymopm_registers>::clock_lfo();
-
-template<>
-void ymfm_engine_base<ymopm_registers>::clock_noise();
-
// ======================> 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