// 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 inline 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 inline 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      { (void)choffs; return 0; }
	uint32_t ch_output_3(uint32_t choffs) const      { (void)choffs; 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);

	void write_pan(uint16_t chan, 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);

	void write_pan(uint16_t chan, uint8_t data);

	// generate one sample of sound
	void generate(output_data *output, uint32_t numsamples = 1);

protected:
	// simulate the DAC discontinuity
	static inline int32_t dac_discontinuity(int32_t value) { return (value < 0) ? (value - 3) : (value + 4); }
	// 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
