463 lines
17 KiB
C++
463 lines
17 KiB
C++
// 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 ®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<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 ®s() 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 ®s() { 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
|