mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
802 lines
28 KiB
C++
802 lines
28 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_OPN_H
|
|
#define YMFM_OPN_H
|
|
|
|
#pragma once
|
|
|
|
#include "ymfm.h"
|
|
#include "ymfm_adpcm.h"
|
|
#include "ymfm_fm.h"
|
|
#include "ymfm_ssg.h"
|
|
|
|
namespace ymfm
|
|
{
|
|
|
|
//*********************************************************
|
|
// REGISTER CLASSES
|
|
//*********************************************************
|
|
|
|
// ======================> opn_registers_base
|
|
|
|
//
|
|
// OPN register map:
|
|
//
|
|
// System-wide registers:
|
|
// 21 xxxxxxxx Test register
|
|
// 22 ----x--- LFO enable [OPNA+ only]
|
|
// -----xxx LFO rate [OPNA+ only]
|
|
// 24 xxxxxxxx Timer A value (upper 8 bits)
|
|
// 25 ------xx Timer A value (lower 2 bits)
|
|
// 26 xxxxxxxx Timer B value
|
|
// 27 xx------ CSM/Multi-frequency mode for channel #2
|
|
// --x----- Reset timer B
|
|
// ---x---- Reset timer A
|
|
// ----x--- Enable timer B
|
|
// -----x-- Enable timer A
|
|
// ------x- Load timer B
|
|
// -------x Load timer A
|
|
// 28 x------- Key on/off operator 4
|
|
// -x------ Key on/off operator 3
|
|
// --x----- Key on/off operator 2
|
|
// ---x---- Key on/off operator 1
|
|
// ------xx Channel select
|
|
//
|
|
// Per-channel registers (channel in address bits 0-1)
|
|
// Note that all these apply to address+100 as well on OPNA+
|
|
// A0-A3 xxxxxxxx Frequency number lower 8 bits
|
|
// A4-A7 --xxx--- Block (0-7)
|
|
// -----xxx Frequency number upper 3 bits
|
|
// B0-B3 --xxx--- Feedback level for operator 1 (0-7)
|
|
// -----xxx Operator connection algorithm (0-7)
|
|
// B4-B7 x------- Pan left [OPNA]
|
|
// -x------ Pan right [OPNA]
|
|
// --xx---- LFO AM shift (0-3) [OPNA+ only]
|
|
// -----xxx LFO PM depth (0-7) [OPNA+ only]
|
|
//
|
|
// Per-operator registers (channel in address bits 0-1, operator in bits 2-3)
|
|
// Note that all these apply to address+100 as well on OPNA+
|
|
// 30-3F -xxx---- Detune value (0-7)
|
|
// ----xxxx Multiple value (0-15)
|
|
// 40-4F -xxxxxxx Total level (0-127)
|
|
// 50-5F xx------ Key scale rate (0-3)
|
|
// ---xxxxx Attack rate (0-31)
|
|
// 60-6F x------- LFO AM enable [OPNA]
|
|
// ---xxxxx Decay rate (0-31)
|
|
// 70-7F ---xxxxx Sustain rate (0-31)
|
|
// 80-8F xxxx---- Sustain level (0-15)
|
|
// ----xxxx Release rate (0-15)
|
|
// 90-9F ----x--- SSG-EG enable
|
|
// -----xxx SSG-EG envelope (0-7)
|
|
//
|
|
// Special multi-frequency registers (channel implicitly #2; operator in address bits 0-1)
|
|
// A8-AB xxxxxxxx Frequency number lower 8 bits
|
|
// AC-AF --xxx--- Block (0-7)
|
|
// -----xxx Frequency number upper 3 bits
|
|
//
|
|
// Internal (fake) registers:
|
|
// B8-BB --xxxxxx Latched frequency number upper bits (from A4-A7)
|
|
// BC-BF --xxxxxx Latched frequency number upper bits (from AC-AF)
|
|
//
|
|
|
|
template<bool IsOpnA>
|
|
class opn_registers_base : public fm_registers_base
|
|
{
|
|
public:
|
|
// constants
|
|
static constexpr uint32_t OUTPUTS = IsOpnA ? 2 : 1;
|
|
static constexpr uint32_t CHANNELS = IsOpnA ? 6 : 3;
|
|
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 = IsOpnA ? 0x200 : 0x100;
|
|
static constexpr uint32_t REG_MODE = 0x27;
|
|
static constexpr uint32_t DEFAULT_PRESCALE = 6;
|
|
static constexpr uint32_t EG_CLOCK_DIVIDER = 3;
|
|
static constexpr bool EG_HAS_SSG = true;
|
|
static constexpr bool MODULATOR_DELAY = false;
|
|
static constexpr uint32_t CSM_TRIGGER_MASK = 1 << 2;
|
|
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
|
|
opn_registers_base();
|
|
|
|
// 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);
|
|
if (!IsOpnA)
|
|
return chnum;
|
|
else
|
|
return (chnum % 3) + 0x100 * (chnum / 3);
|
|
}
|
|
|
|
// map operator number to register offset
|
|
static constexpr uint32_t operator_offset(uint32_t opnum)
|
|
{
|
|
assert(opnum < OPERATORS);
|
|
if (!IsOpnA)
|
|
return opnum + opnum / 3;
|
|
else
|
|
return (opnum % 12) + ((opnum % 12) / 3) + 0x100 * (opnum / 12);
|
|
}
|
|
|
|
// return an array of operator indices for each channel
|
|
struct operator_mapping { uint32_t chan[CHANNELS]; };
|
|
void operator_map(operator_mapping &dest) const;
|
|
|
|
// read a register value
|
|
uint8_t read(uint16_t index) const { return m_regdata[index]; }
|
|
|
|
// 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();
|
|
|
|
// reset the LFO
|
|
void reset_lfo() { m_lfo_counter = 0; }
|
|
|
|
// return the AM offset from LFO for the given channel
|
|
uint32_t lfo_am_offset(uint32_t choffs) const;
|
|
|
|
// return LFO/noise states
|
|
uint32_t noise_state() const { return 0; }
|
|
|
|
// 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(0x21, 0, 8); }
|
|
uint32_t lfo_enable() const { return IsOpnA ? byte(0x22, 3, 1) : 0; }
|
|
uint32_t lfo_rate() const { return IsOpnA ? byte(0x22, 0, 3) : 0; }
|
|
uint32_t timer_a_value() const { return word(0x24, 0, 8, 0x25, 0, 2); }
|
|
uint32_t timer_b_value() const { return byte(0x26, 0, 8); }
|
|
uint32_t csm() const { return (byte(0x27, 6, 2) == 2); }
|
|
uint32_t multi_freq() const { return (byte(0x27, 6, 2) != 0); }
|
|
uint32_t reset_timer_b() const { return byte(0x27, 5, 1); }
|
|
uint32_t reset_timer_a() const { return byte(0x27, 4, 1); }
|
|
uint32_t enable_timer_b() const { return byte(0x27, 3, 1); }
|
|
uint32_t enable_timer_a() const { return byte(0x27, 2, 1); }
|
|
uint32_t load_timer_b() const { return byte(0x27, 1, 1); }
|
|
uint32_t load_timer_a() const { return byte(0x27, 0, 1); }
|
|
uint32_t multi_block_freq(uint32_t num) const { return word(0xac, 0, 6, 0xa8, 0, 8, num); }
|
|
|
|
// per-channel registers
|
|
uint32_t ch_block_freq(uint32_t choffs) const { return word(0xa4, 0, 6, 0xa0, 0, 8, choffs); }
|
|
uint32_t ch_feedback(uint32_t choffs) const { return byte(0xb0, 3, 3, choffs); }
|
|
uint32_t ch_algorithm(uint32_t choffs) const { return byte(0xb0, 0, 3, choffs); }
|
|
uint32_t ch_output_any(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 6, 2, choffs) : 1; }
|
|
uint32_t ch_output_0(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 7, 1, choffs) : 1; }
|
|
uint32_t ch_output_1(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 6, 1, choffs) : 0; }
|
|
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_lfo_am_sens(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 4, 2, choffs) : 0; }
|
|
uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 0, 3, choffs) : 0; }
|
|
|
|
// per-operator registers
|
|
uint32_t op_detune(uint32_t opoffs) const { return byte(0x30, 4, 3, opoffs); }
|
|
uint32_t op_multiple(uint32_t opoffs) const { return byte(0x30, 0, 4, opoffs); }
|
|
uint32_t op_total_level(uint32_t opoffs) const { return byte(0x40, 0, 7, opoffs); }
|
|
uint32_t op_ksr(uint32_t opoffs) const { return byte(0x50, 6, 2, opoffs); }
|
|
uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x50, 0, 5, opoffs); }
|
|
uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0x60, 0, 5, opoffs); }
|
|
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return IsOpnA ? byte(0x60, 7, 1, opoffs) : 0; }
|
|
uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0x70, 0, 5, opoffs); }
|
|
uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0x80, 4, 4, opoffs); }
|
|
uint32_t op_release_rate(uint32_t opoffs) const { return byte(0x80, 0, 4, opoffs); }
|
|
uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return byte(0x90, 3, 1, opoffs); }
|
|
uint32_t op_ssg_eg_mode(uint32_t opoffs) const { return byte(0x90, 0, 3, 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
|
|
uint8_t m_lfo_am; // current LFO AM value
|
|
uint8_t m_regdata[REGISTERS]; // register data
|
|
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
|
|
};
|
|
|
|
using opn_registers = opn_registers_base<false>;
|
|
using opna_registers = opn_registers_base<true>;
|
|
|
|
|
|
|
|
//*********************************************************
|
|
// OPN IMPLEMENTATION CLASSES
|
|
//*********************************************************
|
|
|
|
// A note about prescaling and sample rates.
|
|
//
|
|
// YM2203, YM2608, and YM2610 contain an onboard SSG (basically, a YM2149).
|
|
// In order to properly generate sound at fully fidelity, the output sample
|
|
// rate of the YM2149 must be input_clock / 8. This is much higher than the
|
|
// FM needs, but in the interest of keeping things simple, the OPN generate
|
|
// functions will output at the higher rate and just replicate the last FM
|
|
// sample as many times as needed.
|
|
//
|
|
// To make things even more complicated, the YM2203 and YM2608 allow for
|
|
// software-controlled prescaling, which affects the FM and SSG clocks in
|
|
// different ways. There are three settings: divide by 6/4 (FM/SSG); divide
|
|
// by 3/2; and divide by 2/1.
|
|
//
|
|
// Thus, the minimum output sample rate needed by each part of the chip
|
|
// varies with the prescale as follows:
|
|
//
|
|
// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 -----
|
|
// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
|
|
// 6 /72 /16 /144 /32 /144 /32
|
|
// 3 /36 /8 /72 /16
|
|
// 2 /24 /4 /48 /8
|
|
//
|
|
// If we standardized on the fastest SSG rate, we'd end up with the following
|
|
// (ratios are output_samples:source_samples):
|
|
//
|
|
// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 -----
|
|
// rate = clock/4 rate = clock/8 rate = clock/16
|
|
// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
|
|
// 6 18:1 4:1 18:1 4:1 9:1 2:1
|
|
// 3 9:1 2:1 9:1 2:1
|
|
// 2 6:1 1:1 6:1 1:1
|
|
//
|
|
// However, that's a pretty big performance hit for minimal gain. Going to
|
|
// the other extreme, we could standardize on the fastest FM rate, but then
|
|
// at least one prescale case (3) requires the FM to be smeared across two
|
|
// output samples:
|
|
//
|
|
// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 -----
|
|
// rate = clock/24 rate = clock/48 rate = clock/144
|
|
// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
|
|
// 6 3:1 2:3 3:1 2:3 1:1 2:9
|
|
// 3 1.5:1 1:3 1.5:1 1:3
|
|
// 2 1:1 1:6 1:1 1:6
|
|
//
|
|
// Stepping back one factor of 2 addresses that issue:
|
|
//
|
|
// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 -----
|
|
// rate = clock/12 rate = clock/24 rate = clock/144
|
|
// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
|
|
// 6 6:1 4:3 6:1 4:3 1:1 2:9
|
|
// 3 3:1 2:3 3:1 2:3
|
|
// 2 2:1 1:3 2:1 1:3
|
|
//
|
|
// This gives us three levels of output fidelity:
|
|
// OPN_FIDELITY_MAX -- highest sample rate, using fastest SSG rate
|
|
// OPN_FIDELITY_MIN -- lowest sample rate, using fastest FM rate
|
|
// OPN_FIDELITY_MED -- medium sample rate such that FM is never smeared
|
|
//
|
|
// At the maximum clocks for YM2203/YM2608 (4Mhz/8MHz), these rates will
|
|
// end up as:
|
|
// OPN_FIDELITY_MAX = 1000kHz
|
|
// OPN_FIDELITY_MIN = 166kHz
|
|
// OPN_FIEDLITY_MED = 333kHz
|
|
|
|
|
|
// ======================> opn_fidelity
|
|
|
|
enum opn_fidelity : uint8_t
|
|
{
|
|
OPN_FIDELITY_MAX,
|
|
OPN_FIDELITY_MIN,
|
|
OPN_FIDELITY_MED,
|
|
|
|
OPN_FIDELITY_DEFAULT = OPN_FIDELITY_MAX
|
|
};
|
|
|
|
|
|
// ======================> ssg_resampler
|
|
|
|
template<typename OutputType, int FirstOutput, bool MixTo1>
|
|
class ssg_resampler
|
|
{
|
|
private:
|
|
// helper to add the last computed value to the sums, applying the given scale
|
|
void add_last(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale = 1);
|
|
|
|
// helper to clock a new value and then add it to the sums, applying the given scale
|
|
void clock_and_add(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale = 1);
|
|
|
|
// helper to write the sums to the appropriate outputs, applying the given
|
|
// divisor to the final result
|
|
void write_to_output(OutputType *output, int32_t sum0, int32_t sum1, int32_t sum2, int32_t divisor = 1);
|
|
|
|
public:
|
|
// constructor
|
|
ssg_resampler(ssg_engine &ssg);
|
|
|
|
// save/restore
|
|
void save_restore(ymfm_saved_state &state);
|
|
|
|
// get the current sample index
|
|
uint32_t sampindex() const { return m_sampindex; }
|
|
|
|
// configure the ratio
|
|
void configure(uint8_t outsamples, uint8_t srcsamples);
|
|
|
|
// resample
|
|
void resample(OutputType *output, uint32_t numsamples)
|
|
{
|
|
(this->*m_resampler)(output, numsamples);
|
|
}
|
|
|
|
private:
|
|
// resample SSG output to the target at a rate of 1 SSG sample
|
|
// to every n output samples
|
|
template<int Multiplier>
|
|
void resample_n_1(OutputType *output, uint32_t numsamples);
|
|
|
|
// resample SSG output to the target at a rate of n SSG samples
|
|
// to every 1 output sample
|
|
template<int Divisor>
|
|
void resample_1_n(OutputType *output, uint32_t numsamples);
|
|
|
|
// resample SSG output to the target at a rate of 9 SSG samples
|
|
// to every 2 output samples
|
|
void resample_2_9(OutputType *output, uint32_t numsamples);
|
|
|
|
// resample SSG output to the target at a rate of 3 SSG samples
|
|
// to every 1 output sample
|
|
void resample_1_3(OutputType *output, uint32_t numsamples);
|
|
|
|
// resample SSG output to the target at a rate of 3 SSG samples
|
|
// to every 2 output samples
|
|
void resample_2_3(OutputType *output, uint32_t numsamples);
|
|
|
|
// resample SSG output to the target at a rate of 3 SSG samples
|
|
// to every 4 output samples
|
|
void resample_4_3(OutputType *output, uint32_t numsamples);
|
|
|
|
// no-op resampler
|
|
void resample_nop(OutputType *output, uint32_t numsamples);
|
|
|
|
// define a pointer type
|
|
using resample_func = void (ssg_resampler::*)(OutputType *output, uint32_t numsamples);
|
|
|
|
// internal state
|
|
ssg_engine &m_ssg;
|
|
uint32_t m_sampindex;
|
|
resample_func m_resampler;
|
|
ssg_engine::output_data m_last;
|
|
};
|
|
|
|
|
|
// ======================> ym2203
|
|
|
|
class ym2203
|
|
{
|
|
public:
|
|
using fm_engine = fm_engine_base<opn_registers>;
|
|
static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS;
|
|
static constexpr uint32_t SSG_OUTPUTS = ssg_engine::OUTPUTS;
|
|
static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS;
|
|
using output_data = ymfm_output<OUTPUTS>;
|
|
|
|
// constructor
|
|
ym2203(ymfm_interface &intf);
|
|
|
|
// configuration
|
|
void ssg_override(ssg_override &intf) { m_ssg.override(intf); }
|
|
void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(m_fm.clock_prescale()); }
|
|
|
|
// reset
|
|
void reset();
|
|
|
|
// save/restore
|
|
void save_restore(ymfm_saved_state &state);
|
|
|
|
// pass-through helpers
|
|
uint32_t sample_rate(uint32_t input_clock) const
|
|
{
|
|
switch (m_fidelity)
|
|
{
|
|
case OPN_FIDELITY_MIN: return input_clock / 24;
|
|
case OPN_FIDELITY_MED: return input_clock / 12;
|
|
default:
|
|
case OPN_FIDELITY_MAX: return input_clock / 4;
|
|
}
|
|
}
|
|
uint32_t ssg_effective_clock(uint32_t input_clock) const { uint32_t scale = m_fm.clock_prescale() * 2 / 3; return input_clock * 2 / scale; }
|
|
void invalidate_caches() { m_fm.invalidate_caches(); }
|
|
|
|
// read access
|
|
uint8_t read_status();
|
|
uint8_t read_data();
|
|
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:
|
|
// internal helpers
|
|
void update_prescale(uint8_t prescale);
|
|
void clock_fm();
|
|
|
|
// internal state
|
|
opn_fidelity m_fidelity; // configured fidelity
|
|
uint8_t m_address; // address register
|
|
uint8_t m_fm_samples_per_output; // how many samples to repeat
|
|
fm_engine::output_data m_last_fm; // last FM output
|
|
fm_engine m_fm; // core FM engine
|
|
ssg_engine m_ssg; // SSG engine
|
|
ssg_resampler<output_data, 1, false> m_ssg_resampler; // SSG resampler helper
|
|
};
|
|
|
|
|
|
|
|
//*********************************************************
|
|
// OPNA IMPLEMENTATION CLASSES
|
|
//*********************************************************
|
|
|
|
// ======================> ym2608
|
|
|
|
class ym2608
|
|
{
|
|
static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x04;
|
|
static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08;
|
|
static constexpr uint8_t STATUS_ADPCM_B_ZERO = 0x10;
|
|
static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x20;
|
|
|
|
public:
|
|
using fm_engine = fm_engine_base<opna_registers>;
|
|
static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS;
|
|
static constexpr uint32_t SSG_OUTPUTS = 1;
|
|
static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS;
|
|
using output_data = ymfm_output<OUTPUTS>;
|
|
|
|
// constructor
|
|
ym2608(ymfm_interface &intf);
|
|
|
|
// configuration
|
|
void ssg_override(ssg_override &intf) { m_ssg.override(intf); }
|
|
void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(m_fm.clock_prescale()); }
|
|
|
|
// reset
|
|
void reset();
|
|
|
|
// save/restore
|
|
void save_restore(ymfm_saved_state &state);
|
|
|
|
// pass-through helpers
|
|
uint32_t sample_rate(uint32_t input_clock) const
|
|
{
|
|
switch (m_fidelity)
|
|
{
|
|
case OPN_FIDELITY_MIN: return input_clock / 48;
|
|
case OPN_FIDELITY_MED: return input_clock / 24;
|
|
default:
|
|
case OPN_FIDELITY_MAX: return input_clock / 8;
|
|
}
|
|
}
|
|
uint32_t ssg_effective_clock(uint32_t input_clock) const { uint32_t scale = m_fm.clock_prescale() * 2 / 3; return input_clock / scale; }
|
|
void invalidate_caches() { m_fm.invalidate_caches(); }
|
|
|
|
// read access
|
|
uint8_t read_status();
|
|
uint8_t read_data();
|
|
uint8_t read_status_hi();
|
|
uint8_t read_data_hi();
|
|
uint8_t read(uint32_t offset);
|
|
|
|
// write access
|
|
void write_address(uint8_t data);
|
|
void write_data(uint8_t data);
|
|
void write_address_hi(uint8_t data);
|
|
void write_data_hi(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:
|
|
// internal helpers
|
|
void update_prescale(uint8_t prescale);
|
|
void clock_fm_and_adpcm();
|
|
|
|
// internal state
|
|
opn_fidelity m_fidelity; // configured fidelity
|
|
uint16_t m_address; // address register
|
|
uint8_t m_fm_samples_per_output; // how many samples to repeat
|
|
uint8_t m_irq_enable; // IRQ enable register
|
|
uint8_t m_flag_control; // flag control register
|
|
fm_engine::output_data m_last_fm; // last FM output
|
|
fm_engine m_fm; // core FM engine
|
|
ssg_engine m_ssg; // SSG engine
|
|
ssg_resampler<output_data, 2, true> m_ssg_resampler; // SSG resampler helper
|
|
adpcm_a_engine m_adpcm_a; // ADPCM-A engine
|
|
adpcm_b_engine m_adpcm_b; // ADPCM-B engine
|
|
};
|
|
|
|
|
|
// ======================> ymf288
|
|
|
|
class ymf288
|
|
{
|
|
public:
|
|
using fm_engine = fm_engine_base<opna_registers>;
|
|
static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS;
|
|
static constexpr uint32_t SSG_OUTPUTS = 1;
|
|
static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS;
|
|
using output_data = ymfm_output<OUTPUTS>;
|
|
|
|
// constructor
|
|
ymf288(ymfm_interface &intf);
|
|
|
|
// configuration
|
|
void ssg_override(ssg_override &intf) { m_ssg.override(intf); }
|
|
void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(); }
|
|
|
|
// reset
|
|
void reset();
|
|
|
|
// save/restore
|
|
void save_restore(ymfm_saved_state &state);
|
|
|
|
// pass-through helpers
|
|
uint32_t sample_rate(uint32_t input_clock) const
|
|
{
|
|
switch (m_fidelity)
|
|
{
|
|
case OPN_FIDELITY_MIN: return input_clock / 144;
|
|
case OPN_FIDELITY_MED: return input_clock / 144;
|
|
default:
|
|
case OPN_FIDELITY_MAX: return input_clock / 16;
|
|
}
|
|
}
|
|
uint32_t ssg_effective_clock(uint32_t input_clock) const { return input_clock / 4; }
|
|
void invalidate_caches() { m_fm.invalidate_caches(); }
|
|
|
|
// read access
|
|
uint8_t read_status();
|
|
uint8_t read_data();
|
|
uint8_t read_status_hi();
|
|
uint8_t read(uint32_t offset);
|
|
|
|
// write access
|
|
void write_address(uint8_t data);
|
|
void write_data(uint8_t data);
|
|
void write_address_hi(uint8_t data);
|
|
void write_data_hi(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:
|
|
// internal helpers
|
|
bool ymf288_mode() { return ((m_fm.regs().read(0x20) & 0x02) != 0); }
|
|
void update_prescale();
|
|
void clock_fm_and_adpcm();
|
|
|
|
// internal state
|
|
opn_fidelity m_fidelity; // configured fidelity
|
|
uint16_t m_address; // address register
|
|
uint8_t m_fm_samples_per_output; // how many samples to repeat
|
|
uint8_t m_irq_enable; // IRQ enable register
|
|
uint8_t m_flag_control; // flag control register
|
|
fm_engine::output_data m_last_fm; // last FM output
|
|
fm_engine m_fm; // core FM engine
|
|
ssg_engine m_ssg; // SSG engine
|
|
ssg_resampler<output_data, 2, true> m_ssg_resampler; // SSG resampler helper
|
|
adpcm_a_engine m_adpcm_a; // ADPCM-A engine
|
|
};
|
|
|
|
|
|
// ======================> ym2610/ym2610b
|
|
|
|
class ym2610
|
|
{
|
|
static constexpr uint8_t EOS_FLAGS_MASK = 0xbf;
|
|
|
|
public:
|
|
using fm_engine = fm_engine_base<opna_registers>;
|
|
static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS;
|
|
static constexpr uint32_t SSG_OUTPUTS = 1;
|
|
static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS;
|
|
using output_data = ymfm_output<OUTPUTS>;
|
|
|
|
// constructor
|
|
ym2610(ymfm_interface &intf, uint8_t channel_mask = 0x36);
|
|
|
|
// configuration
|
|
void ssg_override(ssg_override &intf) { m_ssg.override(intf); }
|
|
void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(); }
|
|
|
|
// reset
|
|
void reset();
|
|
|
|
// save/restore
|
|
void save_restore(ymfm_saved_state &state);
|
|
|
|
// pass-through helpers
|
|
uint32_t sample_rate(uint32_t input_clock) const
|
|
{
|
|
switch (m_fidelity)
|
|
{
|
|
case OPN_FIDELITY_MIN: return input_clock / 144;
|
|
case OPN_FIDELITY_MED: return input_clock / 144;
|
|
default:
|
|
case OPN_FIDELITY_MAX: return input_clock / 16;
|
|
}
|
|
}
|
|
uint32_t ssg_effective_clock(uint32_t input_clock) const { return input_clock / 4; }
|
|
void invalidate_caches() { m_fm.invalidate_caches(); }
|
|
|
|
// read access
|
|
uint8_t read_status();
|
|
uint8_t read_data();
|
|
uint8_t read_status_hi();
|
|
uint8_t read_data_hi();
|
|
uint8_t read(uint32_t offset);
|
|
|
|
// write access
|
|
void write_address(uint8_t data);
|
|
void write_data(uint8_t data);
|
|
void write_address_hi(uint8_t data);
|
|
void write_data_hi(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:
|
|
// internal helpers
|
|
void update_prescale();
|
|
void clock_fm_and_adpcm();
|
|
|
|
// internal state
|
|
opn_fidelity m_fidelity; // configured fidelity
|
|
uint16_t m_address; // address register
|
|
uint8_t const m_fm_mask; // FM channel mask
|
|
uint8_t m_fm_samples_per_output; // how many samples to repeat
|
|
uint8_t m_eos_status; // end-of-sample signals
|
|
uint8_t m_flag_mask; // flag mask control
|
|
fm_engine::output_data m_last_fm; // last FM output
|
|
fm_engine m_fm; // core FM engine
|
|
ssg_engine m_ssg; // core FM engine
|
|
ssg_resampler<output_data, 2, true> m_ssg_resampler; // SSG resampler helper
|
|
adpcm_a_engine m_adpcm_a; // ADPCM-A engine
|
|
adpcm_b_engine m_adpcm_b; // ADPCM-B engine
|
|
};
|
|
|
|
class ym2610b : public ym2610
|
|
{
|
|
public:
|
|
// constructor
|
|
ym2610b(ymfm_interface &intf) : ym2610(intf, 0x3f) { }
|
|
};
|
|
|
|
|
|
// ======================> ym2612
|
|
|
|
class ym2612
|
|
{
|
|
public:
|
|
using fm_engine = fm_engine_base<opna_registers>;
|
|
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
|
|
using output_data = fm_engine::output_data;
|
|
|
|
// constructor
|
|
ym2612(ymfm_interface &intf);
|
|
|
|
// 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_address_hi(uint8_t data);
|
|
void write_data_hi(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:
|
|
// simulate the DAC discontinuity
|
|
constexpr int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 3) : (value + 4); }
|
|
|
|
// internal state
|
|
uint16_t m_address; // address register
|
|
uint16_t m_dac_data; // 9-bit DAC data
|
|
uint8_t m_dac_enable; // DAC enabled?
|
|
fm_engine m_fm; // core FM engine
|
|
};
|
|
|
|
|
|
// ======================> ym3438
|
|
|
|
class ym3438 : public ym2612
|
|
{
|
|
public:
|
|
ym3438(ymfm_interface &intf) : ym2612(intf) { }
|
|
|
|
// generate one sample of sound
|
|
void generate(output_data *output, uint32_t numsamples = 1);
|
|
};
|
|
|
|
|
|
// ======================> ymf276
|
|
|
|
class ymf276 : public ym2612
|
|
{
|
|
public:
|
|
ymf276(ymfm_interface &intf) : ym2612(intf) { }
|
|
|
|
// generate one sample of sound
|
|
void generate(output_data *output, uint32_t numsamples);
|
|
};
|
|
|
|
}
|
|
|
|
|
|
#endif // YMFM_OPN_H
|