// 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_FM_H #define YMFM_FM_H #pragma once #define YMFM_DEBUG_LOG_WAVFILES (0) namespace ymfm { //********************************************************* // GLOBAL ENUMERATORS //********************************************************* // three different keyon sources; actual keyon is an OR over all of these enum keyon_type : uint32_t { KEYON_NORMAL = 0, KEYON_RHYTHM = 1, KEYON_CSM = 2 }; //********************************************************* // CORE IMPLEMENTATION //********************************************************* // ======================> opdata_cache // this class holds data that is computed once at the start of clocking // and remains static during subsequent sound generation struct opdata_cache { // set phase_step to this value to recalculate it each sample; needed // in the case of PM LFO changes static constexpr uint32_t PHASE_STEP_DYNAMIC = 1; uint16_t const *waveform; // base of sine table uint32_t phase_step; // phase step, or PHASE_STEP_DYNAMIC if PM is active uint32_t total_level; // total level * 8 + KSL uint32_t block_freq; // raw block frequency value (used to compute phase_step) int32_t detune; // detuning value (used to compute phase_step) uint32_t multiple; // multiple value (x.1, used to compute phase_step) uint32_t eg_sustain; // sustain level, shifted up to envelope values uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR uint8_t eg_shift = 0; // envelope shift amount }; // ======================> fm_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 fm_registers_base { public: // this value is returned from the write() function for rhythm channels static constexpr uint32_t RHYTHM_CHANNEL = 0xff; // this is the size of a full sin waveform static constexpr uint32_t WAVEFORM_LENGTH = 0x400; // // the following constants need to be defined per family: // uint32_t OUTPUTS: The number of outputs exposed (1-4) // uint32_t CHANNELS: The number of channels on the chip // uint32_t ALL_CHANNELS: A bitmask of all channels // uint32_t OPERATORS: The number of operators on the chip // uint32_t WAVEFORMS: The number of waveforms offered // uint32_t REGISTERS: The number of 8-bit registers allocated // uint32_t DEFAULT_PRESCALE: The starting clock prescale // uint32_t EG_CLOCK_DIVIDER: The clock divider of the envelope generator // uint32_t CSM_TRIGGER_MASK: Mask of channels to trigger in CSM mode // uint32_t REG_MODE: The address of the "mode" register controlling timers // uint8_t STATUS_TIMERA: Status bit to set when timer A fires // uint8_t STATUS_TIMERB: Status bit to set when tiemr B fires // uint8_t STATUS_BUSY: Status bit to set when the chip is busy // uint8_t STATUS_IRQ: Status bit to set when an IRQ is signalled // // the following constants are uncommon: // bool DYNAMIC_OPS: True if ops/channel can be changed at runtime (OPL3+) // bool EG_HAS_DEPRESS: True if the chip has a DP ("depress"?) envelope stage (OPLL) // bool EG_HAS_REVERB: True if the chip has a faux reverb envelope stage (OPQ/OPZ) // bool EG_HAS_SSG: True if the chip has SSG envelope support (OPN) // bool MODULATOR_DELAY: True if the modulator is delayed by 1 sample (OPL pre-OPL3) // static constexpr bool DYNAMIC_OPS = false; static constexpr bool EG_HAS_DEPRESS = false; static constexpr bool EG_HAS_REVERB = false; static constexpr bool EG_HAS_SSG = false; static constexpr bool MODULATOR_DELAY = false; // system-wide register defaults uint32_t status_mask() const { return 0; } // OPL only uint32_t irq_reset() const { return 0; } // OPL only uint32_t noise_enable() const { return 0; } // OPM only uint32_t rhythm_enable() const { return 0; } // OPL only // per-operator register defaults uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return 0; } // OPN(A) only uint32_t op_ssg_eg_mode(uint32_t opoffs) const { return 0; } // OPN(A) only protected: // helper to encode four operator numbers into a 32-bit value in the // operator maps for each register class static constexpr uint32_t operator_list(uint8_t o1 = 0xff, uint8_t o2 = 0xff, uint8_t o3 = 0xff, uint8_t o4 = 0xff) { return o1 | (o2 << 8) | (o3 << 16) | (o4 << 24); } // helper to apply KSR to the raw ADSR rate, ignoring ksr if the // raw value is 0, and clamping to 63 static constexpr uint32_t effective_rate(uint32_t rawrate, uint32_t ksr) { return (rawrate == 0) ? 0 : std::min(rawrate + ksr, 63); } }; //********************************************************* // CORE ENGINE CLASSES //********************************************************* // forward declarations template class fm_engine_base; // ======================> fm_operator // fm_operator represents an FM operator (or "slot" in FM parlance), which // produces an output sine wave modulated by an envelope template class fm_operator { // "quiet" value, used to optimize when we can skip doing work static constexpr uint32_t EG_QUIET = 0x380; public: // constructor fm_operator(fm_engine_base &owner, uint32_t opoffs); // save/restore void save_restore(ymfm_saved_state &state); // reset the operator state void reset(); // return the operator/channel offset uint32_t opoffs() const { return m_opoffs; } uint32_t choffs() const { return m_choffs; } // set the current channel void set_choffs(uint32_t choffs) { m_choffs = choffs; } // prepare prior to clocking bool prepare(); // master clocking function void clock(uint32_t env_counter, int32_t lfo_raw_pm); // return the current phase value uint32_t phase() const { return m_phase >> 10; } // compute operator volume int32_t compute_volume(uint32_t phase, uint32_t am_offset) const; // compute volume for the OPM noise channel int32_t compute_noise_volume(uint32_t am_offset) const; // key state control void keyonoff(uint32_t on, keyon_type type); // return a reference to our registers RegisterType ®s() const { return m_regs; } // simple getters for debugging envelope_state debug_eg_state() const { return m_env_state; } uint16_t debug_eg_attenuation() const { return m_env_attenuation; } uint8_t debug_ssg_inverted() const { return m_ssg_inverted; } opdata_cache &debug_cache() { return m_cache; } private: // start the attack phase void start_attack(bool is_restart = false); // start the release phase void start_release(); // clock phases void clock_keystate(uint32_t keystate); void clock_ssg_eg_state(); void clock_envelope(uint32_t env_counter); void clock_phase(int32_t lfo_raw_pm); // return effective attenuation of the envelope uint32_t envelope_attenuation(uint32_t am_offset) const; // internal state uint32_t m_choffs; // channel offset in registers uint32_t m_opoffs; // operator offset in registers uint32_t m_phase; // current phase value (10.10 format) uint16_t m_env_attenuation; // computed envelope attenuation (4.6 format) envelope_state m_env_state; // current envelope state uint8_t m_ssg_inverted; // non-zero if the output should be inverted (bit 0) uint8_t m_key_state; // current key state: on or off (bit 0) uint8_t m_keyon_live; // live key on state (bit 0 = direct, bit 1 = rhythm, bit 2 = CSM) opdata_cache m_cache; // cached values for performance RegisterType &m_regs; // direct reference to registers fm_engine_base &m_owner; // reference to the owning engine }; // ======================> fm_channel // fm_channel represents an FM channel which combines the output of 2 or 4 // operators into a final result template class fm_channel { using output_data = ymfm_output; public: // constructor fm_channel(fm_engine_base &owner, uint32_t choffs); // save/restore void save_restore(ymfm_saved_state &state); // reset the channel state void reset(); // return the channel offset uint32_t choffs() const { return m_choffs; } // assign operators void assign(uint32_t index, fm_operator *op) { assert(index < array_size(m_op)); m_op[index] = op; if (op != nullptr) op->set_choffs(m_choffs); } // signal key on/off to our operators void keyonoff(uint32_t states, keyon_type type, uint32_t chnum); // prepare prior to clocking bool prepare(); // master clocking function void clock(uint32_t env_counter, int32_t lfo_raw_pm); // specific 2-operator and 4-operator output handlers void output_2op(output_data &output, uint32_t rshift, int32_t clipmax) const; void output_4op(output_data &output, uint32_t rshift, int32_t clipmax) const; // compute the special OPL rhythm channel outputs void output_rhythm_ch6(output_data &output, uint32_t rshift, int32_t clipmax) const; void output_rhythm_ch7(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const; void output_rhythm_ch8(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const; // are we a 4-operator channel or a 2-operator one? bool is4op() const { if (RegisterType::DYNAMIC_OPS) return (m_op[2] != nullptr); return (RegisterType::OPERATORS / RegisterType::CHANNELS == 4); } // return a reference to our registers RegisterType ®s() const { return m_regs; } // simple getters for debugging fm_operator *debug_operator(uint32_t index) const { return m_op[index]; } private: // helper to add values to the outputs based on channel enables void add_to_output(uint32_t choffs, output_data &output, int32_t value) const { // create these constants to appease overzealous compilers checking array // bounds in unreachable code (looking at you, clang) constexpr int out0_index = 0; constexpr int out1_index = 1 % RegisterType::OUTPUTS; constexpr int out2_index = 2 % RegisterType::OUTPUTS; constexpr int out3_index = 3 % RegisterType::OUTPUTS; if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs)) output.data[out0_index] += value; if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs)) output.data[out1_index] += value; if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs)) output.data[out2_index] += value; if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs)) output.data[out3_index] += value; } // internal state uint32_t m_choffs; // channel offset in registers int16_t m_feedback[2]; // feedback memory for operator 1 mutable int16_t m_feedback_in; // next input value for op 1 feedback (set in output) fm_operator *m_op[4]; // up to 4 operators RegisterType &m_regs; // direct reference to registers fm_engine_base &m_owner; // reference to the owning engine }; // ======================> fm_engine_base // fm_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 fm_engine_base : public ymfm_engine_callbacks { public: // expose some constants from the registers static constexpr uint32_t OUTPUTS = RegisterType::OUTPUTS; static constexpr uint32_t CHANNELS = RegisterType::CHANNELS; static constexpr uint32_t ALL_CHANNELS = RegisterType::ALL_CHANNELS; static constexpr uint32_t OPERATORS = RegisterType::OPERATORS; // also expose status flags for consumers that inject additional bits static constexpr uint8_t STATUS_TIMERA = RegisterType::STATUS_TIMERA; static constexpr uint8_t STATUS_TIMERB = RegisterType::STATUS_TIMERB; static constexpr uint8_t STATUS_BUSY = RegisterType::STATUS_BUSY; static constexpr uint8_t STATUS_IRQ = RegisterType::STATUS_IRQ; // expose the correct output class using output_data = ymfm_output; // constructor fm_engine_base(ymfm_interface &intf); // save/restore void save_restore(ymfm_saved_state &state); // reset the overall state void reset(); // master clocking function uint32_t clock(uint32_t chanmask); // compute sum of channel outputs void output(output_data &output, uint32_t rshift, int32_t clipmax, uint32_t chanmask) const; // write to the OPN registers void write(uint16_t regnum, uint8_t data); // return the current status uint8_t status() const; // set/reset bits in the status register, updating the IRQ status uint8_t set_reset_status(uint8_t set, uint8_t reset) { m_status = (m_status | set) & ~(reset | STATUS_BUSY); m_intf.ymfm_sync_check_interrupts(); return m_status & ~m_regs.status_mask(); } // set the IRQ mask void set_irq_mask(uint8_t mask) { m_irq_mask = mask; m_intf.ymfm_sync_check_interrupts(); } // return the current clock prescale uint32_t clock_prescale() const { return m_clock_prescale; } // set prescale factor (2/3/6) void set_clock_prescale(uint32_t prescale) { m_clock_prescale = prescale; } // compute sample rate uint32_t sample_rate(uint32_t baseclock) const { #if (YMFM_DEBUG_LOG_WAVFILES) for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) m_wavfile[chnum].set_samplerate(baseclock / (m_clock_prescale * OPERATORS)); #endif return baseclock / (m_clock_prescale * OPERATORS); } // return the owning device ymfm_interface &intf() const { return m_intf; } // return a reference to our registers RegisterType ®s() { return m_regs; } // invalidate any caches void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; } // simple getters for debugging fm_channel *debug_channel(uint32_t index) const { return m_channel[index].get(); } fm_operator *debug_operator(uint32_t index) const { return m_operator[index].get(); } public: // timer callback; called by the interface when a timer fires virtual void engine_timer_expired(uint32_t tnum) override; // check interrupts; called by the interface after synchronization virtual void engine_check_interrupts() override; // mode register write; called by the interface after synchronization virtual void engine_mode_write(uint8_t data) override; protected: // assign the current set of operators to channels void assign_operators(); // update the state of the given timer void update_timer(uint32_t which, uint32_t enable, int32_t delta_clocks); // internal state ymfm_interface &m_intf; // reference to the system interface uint32_t m_env_counter; // envelope counter; low 2 bits are sub-counter uint8_t m_status; // current status register uint8_t m_clock_prescale; // prescale factor (2/3/6) uint8_t m_irq_mask; // mask of which bits signal IRQs uint8_t m_irq_state; // current IRQ state uint8_t m_timer_running[2]; // current timer running state uint8_t m_total_clocks; // low 8 bits of the total number of clocks processed uint32_t m_active_channels; // mask of active channels (computed by prepare) uint32_t m_modified_channels; // mask of channels that have been modified uint32_t m_prepare_count; // counter to do periodic prepare sweeps RegisterType m_regs; // register accessor std::unique_ptr> m_channel[CHANNELS]; // channel pointers std::unique_ptr> m_operator[OPERATORS]; // operator pointers #if (YMFM_DEBUG_LOG_WAVFILES) mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; // for debugging #endif }; } #endif // YMFM_FM_H