diff options
Diffstat (limited to '3rdparty/ymfm/src/ymfm_opm.h')
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opm.h | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/3rdparty/ymfm/src/ymfm_opm.h b/3rdparty/ymfm/src/ymfm_opm.h new file mode 100644 index 00000000000..b126135d4ff --- /dev/null +++ b/3rdparty/ymfm/src/ymfm_opm.h @@ -0,0 +1,322 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPM_H +#define YMFM_OPM_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_fm.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opm_registers + +// +// OPM register map: +// +// System-wide registers: +// 01 xxxxxx-x Test register +// ------x- LFO reset +// 08 -x------ Key on/off operator 4 +// --x----- Key on/off operator 3 +// ---x---- Key on/off operator 2 +// ----x--- Key on/off operator 1 +// -----xxx Channel select +// 0F x------- Noise enable +// ---xxxxx Noise frequency +// 10 xxxxxxxx Timer A value (upper 8 bits) +// 11 ------xx Timer A value (lower 2 bits) +// 12 xxxxxxxx Timer B value +// 14 x------- CSM mode +// --x----- Reset timer B +// ---x---- Reset timer A +// ----x--- Enable timer B +// -----x-- Enable timer A +// ------x- Load timer B +// -------x Load timer A +// 18 xxxxxxxx LFO frequency +// 19 0xxxxxxx AM LFO depth +// 1xxxxxxx PM LFO depth +// 1B xx------ CT (2 output data lines) +// ------xx LFO waveform +// +// Per-channel registers (channel in address bits 0-2) +// 20-27 x------- Pan right +// -x------ Pan left +// --xxx--- Feedback level for operator 1 (0-7) +// -----xxx Operator connection algorithm (0-7) +// 28-2F -xxxxxxx Key code +// 30-37 xxxxxx-- Key fraction +// 38-3F -xxx---- LFO PM sensitivity +// ------xx LFO AM shift +// +// Per-operator registers (channel in address bits 0-2, operator in bits 3-4) +// 40-5F -xxx---- Detune value (0-7) +// ----xxxx Multiple value (0-15) +// 60-7F -xxxxxxx Total level (0-127) +// 80-9F xx------ Key scale rate (0-3) +// ---xxxxx Attack rate (0-31) +// A0-BF x------- LFO AM enable +// ---xxxxx Decay rate (0-31) +// C0-DF xx------ Detune 2 value (0-3) +// ---xxxxx Sustain rate (0-31) +// E0-FF xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// +// Internal (fake) registers: +// 1A -xxxxxxx PM depth +// + +class opm_registers : public fm_registers_base +{ + // LFO waveforms are 256 entries long + static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256; + +public: + // constants + static constexpr uint32_t OUTPUTS = 2; + static constexpr uint32_t CHANNELS = 8; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 4; + static constexpr uint32_t WAVEFORMS = 1; + static constexpr uint32_t REGISTERS = 0x100; + static constexpr uint32_t DEFAULT_PRESCALE = 2; + static constexpr uint32_t EG_CLOCK_DIVIDER = 3; + static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; + static constexpr uint32_t REG_MODE = 0x14; + static constexpr uint8_t STATUS_TIMERA = 0x01; + static constexpr uint8_t STATUS_TIMERB = 0x02; + static constexpr uint8_t STATUS_BUSY = 0x80; + static constexpr uint8_t STATUS_IRQ = 0; + + // constructor + opm_registers(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + return opnum; + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // return the AM offset from LFO for the given channel + uint32_t lfo_am_offset(uint32_t choffs) const; + + // return the current noise state, gated by the noise clock + uint32_t noise_state() const { return m_noise_state; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t test() const { return byte(0x01, 0, 8); } + uint32_t lfo_reset() const { return byte(0x01, 1, 1); } + uint32_t noise_frequency() const { return byte(0x0f, 0, 5) ^ 0x1f; } + uint32_t noise_enable() const { return byte(0x0f, 7, 1); } + uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); } + uint32_t timer_b_value() const { return byte(0x12, 0, 8); } + uint32_t csm() const { return byte(0x14, 7, 1); } + uint32_t reset_timer_b() const { return byte(0x14, 5, 1); } + uint32_t reset_timer_a() const { return byte(0x14, 4, 1); } + uint32_t enable_timer_b() const { return byte(0x14, 3, 1); } + uint32_t enable_timer_a() const { return byte(0x14, 2, 1); } + uint32_t load_timer_b() const { return byte(0x14, 1, 1); } + uint32_t load_timer_a() const { return byte(0x14, 0, 1); } + uint32_t lfo_rate() const { return byte(0x18, 0, 8); } + uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); } + uint32_t lfo_pm_depth() const { return byte(0x1a, 0, 7); } + uint32_t output_bits() const { return byte(0x1b, 6, 2); } + uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); } + + // per-channel registers + uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 6, 2, choffs); } + uint32_t ch_output_0(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); } + uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs); } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); } + uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); } + uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); } + uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); } + + // per-operator registers + uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); } + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); } + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); } + uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); } + uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); } + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // internal state + uint32_t m_lfo_counter; // LFO counter + uint32_t m_noise_lfsr; // noise LFSR state + uint8_t m_noise_counter; // noise counter + uint8_t m_noise_state; // latched noise state + uint8_t m_noise_lfo; // latched LFO noise value + uint8_t m_lfo_am; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8 + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + + + +//********************************************************* +// OPM IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2151 + +class ym2151 +{ +public: + using fm_engine = fm_engine_base<opm_registers>; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + // constructor + ym2151(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2151) { } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // variants + enum opm_variant + { + VARIANT_YM2151, + VARIANT_YM2164 + }; + + // internal constructor + ym2151(ymfm_interface &intf, opm_variant variant); + + // internal state + opm_variant m_variant; // chip variant + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + + +//********************************************************* +// OPP IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2164 + +// the YM2164 is almost 100% functionally identical to the YM2151, except +// it apparently has some mystery registers in the 00-07 range, and timer +// B's frequency is half that of the 2151 +class ym2164 : public ym2151 +{ +public: + // constructor + ym2164(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2164) { } +}; + +} + + +#endif // YMFM_OPM_H |