diff options
Diffstat (limited to 'src/devices/sound/ymfm.h')
-rw-r--r-- | src/devices/sound/ymfm.h | 1495 |
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> ®data, 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> ®data, 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> ®data, 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> ®data, 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 ®s() { 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 ®s() { 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 ®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(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 |