#ifndef YMFM_FM_H
#define YMFM_FM_H
#pragma once
#define YMFM_DEBUG_LOG_WAVFILES (0)
namespace ymfm
{
enum keyon_type : uint32_t
{
KEYON_NORMAL = 0,
KEYON_RHYTHM = 1,
KEYON_CSM = 2
};
struct opdata_cache
{
static constexpr uint32_t PHASE_STEP_DYNAMIC = 1;
uint16_t const *waveform; uint32_t phase_step; uint32_t total_level; uint32_t block_freq; int32_t detune; uint32_t multiple; uint32_t eg_sustain; uint8_t eg_rate[EG_STATES]; uint8_t eg_shift = 0; };
class fm_registers_base
{
public:
static constexpr uint32_t RHYTHM_CHANNEL = 0xff;
static constexpr uint32_t WAVEFORM_LENGTH = 0x400;
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;
uint32_t status_mask() const { return 0; } uint32_t irq_reset() const { return 0; } uint32_t noise_enable() const { return 0; } uint32_t rhythm_enable() const { return 0; }
uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return 0; } uint32_t op_ssg_eg_mode(uint32_t opoffs) const { return 0; }
protected:
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);
}
static constexpr uint32_t effective_rate(uint32_t rawrate, uint32_t ksr)
{
return (rawrate == 0) ? 0 : std::min<uint32_t>(rawrate + ksr, 63);
}
};
template<class RegisterType> class fm_engine_base;
template<class RegisterType>
class fm_operator
{
static constexpr uint32_t EG_QUIET = 0x380;
public:
fm_operator(fm_engine_base<RegisterType> &owner, uint32_t opoffs);
void save_restore(ymfm_saved_state &state);
void reset();
uint32_t opoffs() const { return m_opoffs; }
uint32_t choffs() const { return m_choffs; }
void set_choffs(uint32_t choffs) { m_choffs = choffs; }
bool prepare();
void clock(uint32_t env_counter, int32_t lfo_raw_pm);
uint32_t phase() const { return m_phase >> 10; }
int32_t compute_volume(uint32_t phase, uint32_t am_offset) const;
int32_t compute_noise_volume(uint32_t am_offset) const;
void keyonoff(uint32_t on, keyon_type type);
RegisterType ®s() const { return m_regs; }
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:
void start_attack(bool is_restart = false);
void start_release();
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);
uint32_t envelope_attenuation(uint32_t am_offset) const;
uint32_t m_choffs; uint32_t m_opoffs; uint32_t m_phase; uint16_t m_env_attenuation; envelope_state m_env_state; uint8_t m_ssg_inverted; uint8_t m_key_state; uint8_t m_keyon_live; opdata_cache m_cache; RegisterType &m_regs; fm_engine_base<RegisterType> &m_owner; };
template<class RegisterType>
class fm_channel
{
using output_data = ymfm_output<RegisterType::OUTPUTS>;
public:
fm_channel(fm_engine_base<RegisterType> &owner, uint32_t choffs);
void save_restore(ymfm_saved_state &state);
void reset();
uint32_t choffs() const { return m_choffs; }
void assign(uint32_t index, fm_operator<RegisterType> *op)
{
assert(index < m_op.size());
m_op[index] = op;
if (op != nullptr)
op->set_choffs(m_choffs);
}
void keyonoff(uint32_t states, keyon_type type, uint32_t chnum);
bool prepare();
void clock(uint32_t env_counter, int32_t lfo_raw_pm);
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;
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;
bool is4op() const
{
if (RegisterType::DYNAMIC_OPS)
return (m_op[2] != nullptr);
return (RegisterType::OPERATORS / RegisterType::CHANNELS == 4);
}
RegisterType ®s() const { return m_regs; }
fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_op[index]; }
private:
void add_to_output(uint32_t choffs, output_data &output, int32_t value) const
{
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;
}
uint32_t m_choffs; int16_t m_feedback[2]; mutable int16_t m_feedback_in; std::array<fm_operator<RegisterType> *, 4> m_op; RegisterType &m_regs; fm_engine_base<RegisterType> &m_owner; };
template<class RegisterType>
class fm_engine_base : public ymfm_engine_callbacks
{
public:
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;
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;
using output_data = ymfm_output<OUTPUTS>;
fm_engine_base(ymfm_interface &intf);
void save_restore(ymfm_saved_state &state);
void reset();
uint32_t clock(uint32_t chanmask);
void output(output_data &output, uint32_t rshift, int32_t clipmax, uint32_t chanmask) const;
void write(uint16_t regnum, uint8_t data);
uint8_t status() const;
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();
}
void set_irq_mask(uint8_t mask) { m_irq_mask = mask; m_intf.ymfm_sync_check_interrupts(); }
uint32_t clock_prescale() const { return m_clock_prescale; }
void set_clock_prescale(uint32_t prescale) { m_clock_prescale = prescale; }
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);
}
ymfm_interface &intf() const { return m_intf; }
RegisterType ®s() { return m_regs; }
void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; }
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:
virtual void engine_timer_expired(uint32_t tnum) override;
virtual void engine_check_interrupts() override;
virtual void engine_mode_write(uint8_t data) override;
protected:
void assign_operators();
void update_timer(uint32_t which, uint32_t enable, int32_t delta_clocks);
ymfm_interface &m_intf; uint32_t m_env_counter; uint8_t m_status; uint8_t m_clock_prescale; uint8_t m_irq_mask; uint8_t m_irq_state; uint8_t m_timer_running[2]; uint8_t m_total_clocks; uint32_t m_active_channels; uint32_t m_modified_channels; uint32_t m_prepare_count; RegisterType m_regs; std::unique_ptr<fm_channel<RegisterType>> m_channel[CHANNELS]; std::unique_ptr<fm_operator<RegisterType>> m_operator[OPERATORS]; #if (YMFM_DEBUG_LOG_WAVFILES)
mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; #endif
};
}
#endif