summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/ymfm/src/ymfm_fm.h
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/ymfm/src/ymfm_fm.h')
-rw-r--r--3rdparty/ymfm/src/ymfm_fm.h463
1 files changed, 463 insertions, 0 deletions
diff --git a/3rdparty/ymfm/src/ymfm_fm.h b/3rdparty/ymfm/src/ymfm_fm.h
new file mode 100644
index 00000000000..81795f8fe43
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_fm.h
@@ -0,0 +1,463 @@
+// 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<uint32_t>(rawrate + ksr, 63);
+ }
+};
+
+
+
+//*********************************************************
+// CORE ENGINE CLASSES
+//*********************************************************
+
+// forward declarations
+template<class RegisterType> 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 RegisterType>
+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<RegisterType> &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 &regs() 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<RegisterType> &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 RegisterType>
+class fm_channel
+{
+ using output_data = ymfm_output<RegisterType::OUTPUTS>;
+
+public:
+ // constructor
+ fm_channel(fm_engine_base<RegisterType> &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<RegisterType> *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 &regs() const { return m_regs; }
+
+ // simple getters for debugging
+ fm_operator<RegisterType> *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<RegisterType> *m_op[4]; // up to 4 operators
+ RegisterType &m_regs; // direct reference to registers
+ fm_engine_base<RegisterType> &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 RegisterType>
+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<OUTPUTS>;
+
+ // 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 &regs() { return m_regs; }
+
+ // invalidate any caches
+ void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; }
+
+ // simple getters for debugging
+ fm_channel<RegisterType> *debug_channel(uint32_t index) const { return m_channel[index].get(); }
+ fm_operator<RegisterType> *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<fm_channel<RegisterType>> m_channel[CHANNELS]; // channel pointers
+ std::unique_ptr<fm_operator<RegisterType>> m_operator[OPERATORS]; // operator pointers
+#if (YMFM_DEBUG_LOG_WAVFILES)
+ mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; // for debugging
+#endif
+};
+
+}
+
+#endif // YMFM_FM_H