summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/ymfm/src
diff options
context:
space:
mode:
Diffstat (limited to '3rdparty/ymfm/src')
-rw-r--r--3rdparty/ymfm/src/ymfm.h566
-rw-r--r--3rdparty/ymfm/src/ymfm_adpcm.cpp807
-rw-r--r--3rdparty/ymfm/src/ymfm_adpcm.h411
-rw-r--r--3rdparty/ymfm/src/ymfm_fm.h463
-rw-r--r--3rdparty/ymfm/src/ymfm_fm.ipp1592
-rw-r--r--3rdparty/ymfm/src/ymfm_misc.cpp175
-rw-r--r--3rdparty/ymfm/src/ymfm_misc.h93
-rw-r--r--3rdparty/ymfm/src/ymfm_opl.cpp2220
-rw-r--r--3rdparty/ymfm/src/ymfm_opl.h902
-rw-r--r--3rdparty/ymfm/src/ymfm_opm.cpp539
-rw-r--r--3rdparty/ymfm/src/ymfm_opm.h322
-rw-r--r--3rdparty/ymfm/src/ymfm_opn.cpp2485
-rw-r--r--3rdparty/ymfm/src/ymfm_opn.h802
-rw-r--r--3rdparty/ymfm/src/ymfm_opq.cpp480
-rw-r--r--3rdparty/ymfm/src/ymfm_opq.h293
-rw-r--r--3rdparty/ymfm/src/ymfm_opx.h290
-rw-r--r--3rdparty/ymfm/src/ymfm_opz.cpp808
-rw-r--r--3rdparty/ymfm/src/ymfm_opz.h332
-rw-r--r--3rdparty/ymfm/src/ymfm_pcm.cpp714
-rw-r--r--3rdparty/ymfm/src/ymfm_pcm.h347
-rw-r--r--3rdparty/ymfm/src/ymfm_ssg.cpp279
-rw-r--r--3rdparty/ymfm/src/ymfm_ssg.h207
22 files changed, 15127 insertions, 0 deletions
diff --git a/3rdparty/ymfm/src/ymfm.h b/3rdparty/ymfm/src/ymfm.h
new file mode 100644
index 00000000000..6427e34cda2
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm.h
@@ -0,0 +1,566 @@
+// 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_H
+#define YMFM_H
+
+#pragma once
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
+ #define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include <cassert>
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <algorithm>
+#include <array>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace ymfm
+{
+
+//*********************************************************
+// DEBUGGING
+//*********************************************************
+
+class debug
+{
+public:
+ // masks to help isolate specific channels
+ static constexpr uint32_t GLOBAL_FM_CHANNEL_MASK = 0xffffffff;
+ static constexpr uint32_t GLOBAL_ADPCM_A_CHANNEL_MASK = 0xffffffff;
+ static constexpr uint32_t GLOBAL_ADPCM_B_CHANNEL_MASK = 0xffffffff;
+ static constexpr uint32_t GLOBAL_PCM_CHANNEL_MASK = 0xffffffff;
+
+ // types of logging
+ static constexpr bool LOG_FM_WRITES = false;
+ static constexpr bool LOG_KEYON_EVENTS = false;
+ static constexpr bool LOG_UNEXPECTED_READ_WRITES = false;
+
+ // helpers to write based on the log type
+ template<typename... Params> static void log_fm_write(Params &&... args) { if (LOG_FM_WRITES) log(args...); }
+ template<typename... Params> static void log_keyon(Params &&... args) { if (LOG_KEYON_EVENTS) log(args...); }
+ template<typename... Params> static void log_unexpected_read_write(Params &&... args) { if (LOG_UNEXPECTED_READ_WRITES) log(args...); }
+
+ // downstream helper to output log data; defaults to printf
+ template<typename... Params> static void log(Params &&... args) { printf(args...); }
+};
+
+
+
+//*********************************************************
+// GLOBAL HELPERS
+//*********************************************************
+
+//-------------------------------------------------
+// bitfield - extract a bitfield from the given
+// value, starting at bit 'start' for a length of
+// 'length' bits
+//-------------------------------------------------
+
+inline uint32_t bitfield(uint32_t value, int start, int length = 1)
+{
+ return (value >> start) & ((1 << length) - 1);
+}
+
+
+//-------------------------------------------------
+// clamp - clamp between the minimum and maximum
+// values provided
+//-------------------------------------------------
+
+inline int32_t clamp(int32_t value, int32_t minval, int32_t maxval)
+{
+ if (value < minval)
+ return minval;
+ if (value > maxval)
+ return maxval;
+ return value;
+}
+
+
+//-------------------------------------------------
+// count_leading_zeros - return the number of
+// leading zeros in a 32-bit value; CPU-optimized
+// versions for various architectures are included
+// below
+//-------------------------------------------------
+
+#if defined(__GNUC__)
+
+inline uint8_t count_leading_zeros(uint32_t value)
+{
+ if (value == 0)
+ return 32;
+ return __builtin_clz(value);
+}
+
+#elif defined(_MSC_VER)
+
+inline uint8_t count_leading_zeros(uint32_t value)
+{
+ unsigned long index;
+ return _BitScanReverse(&index, value) ? uint8_t(31U - index) : 32U;
+}
+
+#else
+
+inline uint8_t count_leading_zeros(uint32_t value)
+{
+ if (value == 0)
+ return 32;
+ uint8_t count;
+ for (count = 0; int32_t(value) >= 0; count++)
+ value <<= 1;
+ return count;
+}
+
+#endif
+
+
+// Many of the Yamaha FM chips emit a floating-point value, which is sent to
+// a DAC for processing. The exact format of this floating-point value is
+// documented below. This description only makes sense if the "internal"
+// format treats sign as 1=positive and 0=negative, so the helpers below
+// presume that.
+//
+// Internal OPx data 16-bit signed data Exp Sign Mantissa
+// ================= ================= === ==== ========
+// 1 1xxxxxxxx------ -> 0 1xxxxxxxx------ -> 111 1 1xxxxxxx
+// 1 01xxxxxxxx----- -> 0 01xxxxxxxx----- -> 110 1 1xxxxxxx
+// 1 001xxxxxxxx---- -> 0 001xxxxxxxx---- -> 101 1 1xxxxxxx
+// 1 0001xxxxxxxx--- -> 0 0001xxxxxxxx--- -> 100 1 1xxxxxxx
+// 1 00001xxxxxxxx-- -> 0 00001xxxxxxxx-- -> 011 1 1xxxxxxx
+// 1 000001xxxxxxxx- -> 0 000001xxxxxxxx- -> 010 1 1xxxxxxx
+// 1 000000xxxxxxxxx -> 0 000000xxxxxxxxx -> 001 1 xxxxxxxx
+// 0 111111xxxxxxxxx -> 1 111111xxxxxxxxx -> 001 0 xxxxxxxx
+// 0 111110xxxxxxxx- -> 1 111110xxxxxxxx- -> 010 0 0xxxxxxx
+// 0 11110xxxxxxxx-- -> 1 11110xxxxxxxx-- -> 011 0 0xxxxxxx
+// 0 1110xxxxxxxx--- -> 1 1110xxxxxxxx--- -> 100 0 0xxxxxxx
+// 0 110xxxxxxxx---- -> 1 110xxxxxxxx---- -> 101 0 0xxxxxxx
+// 0 10xxxxxxxx----- -> 1 10xxxxxxxx----- -> 110 0 0xxxxxxx
+// 0 0xxxxxxxx------ -> 1 0xxxxxxxx------ -> 111 0 0xxxxxxx
+
+//-------------------------------------------------
+// encode_fp - given a 32-bit signed input value
+// convert it to a signed 3.10 floating-point
+// value
+//-------------------------------------------------
+
+inline int16_t encode_fp(int32_t value)
+{
+ // handle overflows first
+ if (value < -32768)
+ return (7 << 10) | 0x000;
+ if (value > 32767)
+ return (7 << 10) | 0x3ff;
+
+ // we need to count the number of leading sign bits after the sign
+ // we can use count_leading_zeros if we invert negative values
+ int32_t scanvalue = value ^ (int32_t(value) >> 31);
+
+ // exponent is related to the number of leading bits starting from bit 14
+ int exponent = 7 - count_leading_zeros(scanvalue << 17);
+
+ // smallest exponent value allowed is 1
+ exponent = std::max(exponent, 1);
+
+ // mantissa
+ int32_t mantissa = value >> (exponent - 1);
+
+ // assemble into final form, inverting the sign
+ return ((exponent << 10) | (mantissa & 0x3ff)) ^ 0x200;
+}
+
+
+//-------------------------------------------------
+// decode_fp - given a 3.10 floating-point value,
+// convert it to a signed 16-bit value
+//-------------------------------------------------
+
+inline int16_t decode_fp(int16_t value)
+{
+ // invert the sign and the exponent
+ value ^= 0x1e00;
+
+ // shift mantissa up to 16 bits then apply inverted exponent
+ return int16_t(value << 6) >> bitfield(value, 10, 3);
+}
+
+
+//-------------------------------------------------
+// roundtrip_fp - compute the result of a round
+// trip through the encode/decode process above
+//-------------------------------------------------
+
+inline int16_t roundtrip_fp(int32_t value)
+{
+ // handle overflows first
+ if (value < -32768)
+ return -32768;
+ if (value > 32767)
+ return 32767;
+
+ // we need to count the number of leading sign bits after the sign
+ // we can use count_leading_zeros if we invert negative values
+ int32_t scanvalue = value ^ (int32_t(value) >> 31);
+
+ // exponent is related to the number of leading bits starting from bit 14
+ int exponent = 7 - count_leading_zeros(scanvalue << 17);
+
+ // smallest exponent value allowed is 1
+ exponent = std::max(exponent, 1);
+
+ // apply the shift back and forth to zero out bits that are lost
+ exponent -= 1;
+ int32_t mask = (1 << exponent) - 1;
+ return value & ~mask;
+}
+
+
+
+//*********************************************************
+// HELPER CLASSES
+//*********************************************************
+
+// various envelope states
+enum envelope_state : uint32_t
+{
+ EG_DEPRESS = 0, // OPLL only; set EG_HAS_DEPRESS to enable
+ EG_ATTACK = 1,
+ EG_DECAY = 2,
+ EG_SUSTAIN = 3,
+ EG_RELEASE = 4,
+ EG_REVERB = 5, // OPQ/OPZ only; set EG_HAS_REVERB to enable
+ EG_STATES = 6
+};
+
+// external I/O access classes
+enum access_class : uint32_t
+{
+ ACCESS_IO = 0,
+ ACCESS_ADPCM_A,
+ ACCESS_ADPCM_B,
+ ACCESS_PCM,
+ ACCESS_CLASSES
+};
+
+
+
+//*********************************************************
+// HELPER CLASSES
+//*********************************************************
+
+// ======================> ymfm_output
+
+// struct containing an array of output values
+template<int NumOutputs>
+struct ymfm_output
+{
+ // clear all outputs to 0
+ ymfm_output &clear()
+ {
+ for (uint32_t index = 0; index < NumOutputs; index++)
+ data[index] = 0;
+ return *this;
+ }
+
+ // clamp all outputs to a 16-bit signed value
+ ymfm_output &clamp16()
+ {
+ for (uint32_t index = 0; index < NumOutputs; index++)
+ data[index] = clamp(data[index], -32768, 32767);
+ return *this;
+ }
+
+ // run each output value through the floating-point processor
+ ymfm_output &roundtrip_fp()
+ {
+ for (uint32_t index = 0; index < NumOutputs; index++)
+ data[index] = ymfm::roundtrip_fp(data[index]);
+ return *this;
+ }
+
+ // internal state
+ int32_t data[NumOutputs];
+};
+
+
+// ======================> ymfm_wavfile
+
+// this class is a debugging helper that accumulates data and writes it to wav files
+template<int Channels>
+class ymfm_wavfile
+{
+public:
+ // construction
+ ymfm_wavfile(uint32_t samplerate = 44100) :
+ m_samplerate(samplerate)
+ {
+ }
+
+ // configuration
+ ymfm_wavfile &set_index(uint32_t index) { m_index = index; return *this; }
+ ymfm_wavfile &set_samplerate(uint32_t samplerate) { m_samplerate = samplerate; return *this; }
+
+ // destruction
+ ~ymfm_wavfile()
+ {
+ if (!m_buffer.empty())
+ {
+ // create file
+ char name[20];
+ snprintf(&name[0], sizeof(name), "wavlog-%02d.wav", m_index);
+ FILE *out = fopen(name, "wb");
+
+ // make the wav file header
+ uint8_t header[44];
+ memcpy(&header[0], "RIFF", 4);
+ *(uint32_t *)&header[4] = m_buffer.size() * 2 + 44 - 8;
+ memcpy(&header[8], "WAVE", 4);
+ memcpy(&header[12], "fmt ", 4);
+ *(uint32_t *)&header[16] = 16;
+ *(uint16_t *)&header[20] = 1;
+ *(uint16_t *)&header[22] = Channels;
+ *(uint32_t *)&header[24] = m_samplerate;
+ *(uint32_t *)&header[28] = m_samplerate * 2 * Channels;
+ *(uint16_t *)&header[32] = 2 * Channels;
+ *(uint16_t *)&header[34] = 16;
+ memcpy(&header[36], "data", 4);
+ *(uint32_t *)&header[40] = m_buffer.size() * 2 + 44 - 44;
+
+ // write header then data
+ fwrite(&header[0], 1, sizeof(header), out);
+ fwrite(&m_buffer[0], 2, m_buffer.size(), out);
+ fclose(out);
+ }
+ }
+
+ // add data to the file
+ template<int Outputs>
+ void add(ymfm_output<Outputs> output)
+ {
+ int16_t sum[Channels] = { 0 };
+ for (int index = 0; index < Outputs; index++)
+ sum[index % Channels] += output.data[index];
+ for (int index = 0; index < Channels; index++)
+ m_buffer.push_back(sum[index]);
+ }
+
+ // add data to the file, using a reference
+ template<int Outputs>
+ void add(ymfm_output<Outputs> output, ymfm_output<Outputs> const &ref)
+ {
+ int16_t sum[Channels] = { 0 };
+ for (int index = 0; index < Outputs; index++)
+ sum[index % Channels] += output.data[index] - ref.data[index];
+ for (int index = 0; index < Channels; index++)
+ m_buffer.push_back(sum[index]);
+ }
+
+private:
+ // internal state
+ uint32_t m_index;
+ uint32_t m_samplerate;
+ std::vector<int16_t> m_buffer;
+};
+
+
+// ======================> ymfm_saved_state
+
+// this class contains a managed vector of bytes that is used to save and
+// restore state
+class ymfm_saved_state
+{
+public:
+ // construction
+ ymfm_saved_state(std::vector<uint8_t> &buffer, bool saving) :
+ m_buffer(buffer),
+ m_offset(saving ? -1 : 0)
+ {
+ if (saving)
+ buffer.resize(0);
+ }
+
+ // are we saving or restoring?
+ bool saving() const { return (m_offset < 0); }
+
+ // generic save/restore
+ template<typename DataType>
+ void save_restore(DataType &data)
+ {
+ if (saving())
+ save(data);
+ else
+ restore(data);
+ }
+
+public:
+ // save data to the buffer
+ void save(bool &data) { write(data ? 1 : 0); }
+ void save(int8_t &data) { write(data); }
+ void save(uint8_t &data) { write(data); }
+ void save(int16_t &data) { write(uint8_t(data)).write(data >> 8); }
+ void save(uint16_t &data) { write(uint8_t(data)).write(data >> 8); }
+ void save(int32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); }
+ void save(uint32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); }
+ void save(envelope_state &data) { write(uint8_t(data)); }
+ template<typename DataType, int Count>
+ void save(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) save(data[index]); }
+
+ // restore data from the buffer
+ void restore(bool &data) { data = read() ? true : false; }
+ void restore(int8_t &data) { data = read(); }
+ void restore(uint8_t &data) { data = read(); }
+ void restore(int16_t &data) { data = read(); data |= read() << 8; }
+ void restore(uint16_t &data) { data = read(); data |= read() << 8; }
+ void restore(int32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; }
+ void restore(uint32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; }
+ void restore(envelope_state &data) { data = envelope_state(read()); }
+ template<typename DataType, int Count>
+ void restore(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) restore(data[index]); }
+
+ // internal helper
+ ymfm_saved_state &write(uint8_t data) { m_buffer.push_back(data); return *this; }
+ uint8_t read() { return (m_offset < int32_t(m_buffer.size())) ? m_buffer[m_offset++] : 0; }
+
+ // internal state
+ std::vector<uint8_t> &m_buffer;
+ int32_t m_offset;
+};
+
+
+
+//*********************************************************
+// INTERFACE CLASSES
+//*********************************************************
+
+// ======================> ymfm_engine_callbacks
+
+// this class represents functions in the engine that the ymfm_interface
+// needs to be able to call; it is represented here as a separate interface
+// that is independent of the actual engine implementation
+class ymfm_engine_callbacks
+{
+public:
+ virtual ~ymfm_engine_callbacks() = default;
+
+ // timer callback; called by the interface when a timer fires
+ virtual void engine_timer_expired(uint32_t tnum) = 0;
+
+ // check interrupts; called by the interface after synchronization
+ virtual void engine_check_interrupts() = 0;
+
+ // mode register write; called by the interface after synchronization
+ virtual void engine_mode_write(uint8_t data) = 0;
+};
+
+
+// ======================> ymfm_interface
+
+// this class represents the interface between the fm_engine and the outside
+// world; it provides hooks for timers, synchronization, and I/O
+class ymfm_interface
+{
+ // the engine is our friend
+ template<typename RegisterType> friend class fm_engine_base;
+
+public:
+ virtual ~ymfm_interface() = default;
+
+ // the following functions must be implemented by any derived classes; the
+ // default implementations are sufficient for some minimal operation, but will
+ // likely need to be overridden to integrate with the outside world; they are
+ // all prefixed with ymfm_ to reduce the likelihood of namespace collisions
+
+ //
+ // timing and synchronizaton
+ //
+
+ // the chip implementation calls this when a write happens to the mode
+ // register, which could affect timers and interrupts; our responsibility
+ // is to ensure the system is up to date before calling the engine's
+ // engine_mode_write() method
+ virtual void ymfm_sync_mode_write(uint8_t data) { m_engine->engine_mode_write(data); }
+
+ // the chip implementation calls this when the chip's status has changed,
+ // which may affect the interrupt state; our responsibility is to ensure
+ // the system is up to date before calling the engine's
+ // engine_check_interrupts() method
+ virtual void ymfm_sync_check_interrupts() { m_engine->engine_check_interrupts(); }
+
+ // the chip implementation calls this when one of the two internal timers
+ // has changed state; our responsibility is to arrange to call the engine's
+ // engine_timer_expired() method after the provided number of clocks; if
+ // duration_in_clocks is negative, we should cancel any outstanding timers
+ virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) { }
+
+ // the chip implementation calls this to indicate that the chip should be
+ // considered in a busy state until the given number of clocks has passed;
+ // our responsibility is to compute and remember the ending time based on
+ // the chip's clock for later checking
+ virtual void ymfm_set_busy_end(uint32_t clocks) { }
+
+ // the chip implementation calls this to see if the chip is still currently
+ // is a busy state, as specified by a previous call to ymfm_set_busy_end();
+ // our responsibility is to compare the current time against the previously
+ // noted busy end time and return true if we haven't yet passed it
+ virtual bool ymfm_is_busy() { return false; }
+
+ //
+ // I/O functions
+ //
+
+ // the chip implementation calls this when the state of the IRQ signal has
+ // changed due to a status change; our responsibility is to respond as
+ // needed to the change in IRQ state, signaling any consumers
+ virtual void ymfm_update_irq(bool asserted) { }
+
+ // the chip implementation calls this whenever data is read from outside
+ // of the chip; our responsibility is to provide the data requested
+ virtual uint8_t ymfm_external_read(access_class type, uint32_t address) { return 0; }
+
+ // the chip implementation calls this whenever data is written outside
+ // of the chip; our responsibility is to pass the written data on to any consumers
+ virtual void ymfm_external_write(access_class type, uint32_t address, uint8_t data) { }
+
+protected:
+ // pointer to engine callbacks -- this is set directly by the engine at
+ // construction time
+ ymfm_engine_callbacks *m_engine;
+};
+
+}
+
+#endif // YMFM_H
diff --git a/3rdparty/ymfm/src/ymfm_adpcm.cpp b/3rdparty/ymfm/src/ymfm_adpcm.cpp
new file mode 100644
index 00000000000..4bc22beb2b0
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_adpcm.cpp
@@ -0,0 +1,807 @@
+// 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.
+
+#include "ymfm_adpcm.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// ADPCM "A" REGISTERS
+//*********************************************************
+
+//-------------------------------------------------
+// reset - reset the register state
+//-------------------------------------------------
+
+void adpcm_a_registers::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+
+ // initialize the pans to on by default, and max instrument volume;
+ // some neogeo homebrews (for example ffeast) rely on this
+ m_regdata[0x08] = m_regdata[0x09] = m_regdata[0x0a] =
+ m_regdata[0x0b] = m_regdata[0x0c] = m_regdata[0x0d] = 0xdf;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_a_registers::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_regdata);
+}
+
+
+//*********************************************************
+// ADPCM "A" CHANNEL
+//*********************************************************
+
+//-------------------------------------------------
+// adpcm_a_channel - constructor
+//-------------------------------------------------
+
+adpcm_a_channel::adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift) :
+ m_choffs(choffs),
+ m_address_shift(addrshift),
+ m_playing(0),
+ m_curnibble(0),
+ m_curbyte(0),
+ m_curaddress(0),
+ m_accumulator(0),
+ m_step_index(0),
+ m_regs(owner.regs()),
+ m_owner(owner)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the channel state
+//-------------------------------------------------
+
+void adpcm_a_channel::reset()
+{
+ m_playing = 0;
+ m_curnibble = 0;
+ m_curbyte = 0;
+ m_curaddress = 0;
+ m_accumulator = 0;
+ m_step_index = 0;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_a_channel::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_playing);
+ state.save_restore(m_curnibble);
+ state.save_restore(m_curbyte);
+ state.save_restore(m_curaddress);
+ state.save_restore(m_accumulator);
+ state.save_restore(m_step_index);
+}
+
+
+//-------------------------------------------------
+// keyonoff - signal key on/off
+//-------------------------------------------------
+
+void adpcm_a_channel::keyonoff(bool on)
+{
+ // QUESTION: repeated key ons restart the sample?
+ m_playing = on;
+ if (m_playing)
+ {
+ m_curaddress = m_regs.ch_start(m_choffs) << m_address_shift;
+ m_curnibble = 0;
+ m_curbyte = 0;
+ m_accumulator = 0;
+ m_step_index = 0;
+
+ // don't log masked channels
+ if (((debug::GLOBAL_ADPCM_A_CHANNEL_MASK >> m_choffs) & 1) != 0)
+ debug::log_keyon("KeyOn ADPCM-A%d: pan=%d%d start=%04X end=%04X level=%02X\n",
+ m_choffs,
+ m_regs.ch_pan_left(m_choffs),
+ m_regs.ch_pan_right(m_choffs),
+ m_regs.ch_start(m_choffs),
+ m_regs.ch_end(m_choffs),
+ m_regs.ch_instrument_level(m_choffs));
+ }
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+bool adpcm_a_channel::clock()
+{
+ // if not playing, just output 0
+ if (m_playing == 0)
+ {
+ m_accumulator = 0;
+ return false;
+ }
+
+ // if we're about to read nibble 0, fetch the data
+ uint8_t data;
+ if (m_curnibble == 0)
+ {
+ // stop when we hit the end address; apparently only low 20 bits are used for
+ // comparison on the YM2610: this affects sample playback in some games, for
+ // example twinspri character select screen music will skip some samples if
+ // this is not correct
+ //
+ // note also: end address is inclusive, so wait until we are about to fetch
+ // the sample just after the end before stopping; this is needed for nitd's
+ // jump sound, for example
+ uint32_t end = (m_regs.ch_end(m_choffs) + 1) << m_address_shift;
+ if (((m_curaddress ^ end) & 0xfffff) == 0)
+ {
+ m_playing = m_accumulator = 0;
+ return true;
+ }
+
+ m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_A, m_curaddress++);
+ data = m_curbyte >> 4;
+ m_curnibble = 1;
+ }
+
+ // otherwise just extract from the previosuly-fetched byte
+ else
+ {
+ data = m_curbyte & 0xf;
+ m_curnibble = 0;
+ }
+
+ // compute the ADPCM delta
+ static uint16_t const s_steps[49] =
+ {
+ 16, 17, 19, 21, 23, 25, 28,
+ 31, 34, 37, 41, 45, 50, 55,
+ 60, 66, 73, 80, 88, 97, 107,
+ 118, 130, 143, 157, 173, 190, 209,
+ 230, 253, 279, 307, 337, 371, 408,
+ 449, 494, 544, 598, 658, 724, 796,
+ 876, 963, 1060, 1166, 1282, 1411, 1552
+ };
+ int32_t delta = (2 * bitfield(data, 0, 3) + 1) * s_steps[m_step_index] / 8;
+ if (bitfield(data, 3))
+ delta = -delta;
+
+ // the 12-bit accumulator wraps on the ym2610 and ym2608 (like the msm5205)
+ m_accumulator = (m_accumulator + delta) & 0xfff;
+
+ // adjust ADPCM step
+ static int8_t const s_step_inc[8] = { -1, -1, -1, -1, 2, 5, 7, 9 };
+ m_step_index = clamp(m_step_index + s_step_inc[bitfield(data, 0, 3)], 0, 48);
+
+ return false;
+}
+
+
+//-------------------------------------------------
+// output - return the computed output value, with
+// panning applied
+//-------------------------------------------------
+
+template<int NumOutputs>
+void adpcm_a_channel::output(ymfm_output<NumOutputs> &output) const
+{
+ // volume combines instrument and total levels
+ int vol = (m_regs.ch_instrument_level(m_choffs) ^ 0x1f) + (m_regs.total_level() ^ 0x3f);
+
+ // if combined is maximum, don't add to outputs
+ if (vol >= 63)
+ return;
+
+ // convert into a shift and a multiplier
+ // QUESTION: verify this from other sources
+ int8_t mul = 15 - (vol & 7);
+ uint8_t shift = 4 + 1 + (vol >> 3);
+
+ // m_accumulator is a 12-bit value; shift up to sign-extend;
+ // the downshift is incorporated into 'shift'
+ int16_t value = ((int16_t(m_accumulator << 4) * mul) >> shift) & ~3;
+
+ // apply to left/right as appropriate
+ if (NumOutputs == 1 || m_regs.ch_pan_left(m_choffs))
+ output.data[0] += value;
+ if (NumOutputs > 1 && m_regs.ch_pan_right(m_choffs))
+ output.data[1] += value;
+}
+
+
+
+//*********************************************************
+// ADPCM "A" ENGINE
+//*********************************************************
+
+//-------------------------------------------------
+// adpcm_a_engine - constructor
+//-------------------------------------------------
+
+adpcm_a_engine::adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift) :
+ m_intf(intf)
+{
+ // create the channels
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ m_channel[chnum] = std::make_unique<adpcm_a_channel>(*this, chnum, addrshift);
+}
+
+
+//-------------------------------------------------
+// reset - reset the engine state
+//-------------------------------------------------
+
+void adpcm_a_engine::reset()
+{
+ // reset register state
+ m_regs.reset();
+
+ // reset each channel
+ for (auto &chan : m_channel)
+ chan->reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_a_engine::save_restore(ymfm_saved_state &state)
+{
+ // save register state
+ m_regs.save_restore(state);
+
+ // save channel state
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ m_channel[chnum]->save_restore(state);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+uint32_t adpcm_a_engine::clock(uint32_t chanmask)
+{
+ // clock each channel, setting a bit in result if it finished
+ uint32_t result = 0;
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ if (m_channel[chnum]->clock())
+ result |= 1 << chnum;
+
+ // return the bitmask of completed samples
+ return result;
+}
+
+
+//-------------------------------------------------
+// update - master update function
+//-------------------------------------------------
+
+template<int NumOutputs>
+void adpcm_a_engine::output(ymfm_output<NumOutputs> &output, uint32_t chanmask)
+{
+ // mask out some channels for debug purposes
+ chanmask &= debug::GLOBAL_ADPCM_A_CHANNEL_MASK;
+
+ // compute the output of each channel
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ m_channel[chnum]->output(output);
+}
+
+template void adpcm_a_engine::output<1>(ymfm_output<1> &output, uint32_t chanmask);
+template void adpcm_a_engine::output<2>(ymfm_output<2> &output, uint32_t chanmask);
+
+
+//-------------------------------------------------
+// write - handle writes to the ADPCM-A registers
+//-------------------------------------------------
+
+void adpcm_a_engine::write(uint32_t regnum, uint8_t data)
+{
+ // store the raw value to the register array;
+ // most writes are passive, consumed only when needed
+ m_regs.write(regnum, data);
+
+ // actively handle writes to the control register
+ if (regnum == 0x00)
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(data, chnum))
+ m_channel[chnum]->keyonoff(bitfield(~data, 7));
+}
+
+
+
+//*********************************************************
+// ADPCM "B" REGISTERS
+//*********************************************************
+
+//-------------------------------------------------
+// reset - reset the register state
+//-------------------------------------------------
+
+void adpcm_b_registers::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+
+ // default limit to wide open
+ m_regdata[0x0c] = m_regdata[0x0d] = 0xff;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_b_registers::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_regdata);
+}
+
+
+
+//*********************************************************
+// ADPCM "B" CHANNEL
+//*********************************************************
+
+//-------------------------------------------------
+// adpcm_b_channel - constructor
+//-------------------------------------------------
+
+adpcm_b_channel::adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift) :
+ m_address_shift(addrshift),
+ m_status(STATUS_BRDY),
+ m_curnibble(0),
+ m_curbyte(0),
+ m_dummy_read(0),
+ m_position(0),
+ m_curaddress(0),
+ m_accumulator(0),
+ m_prev_accum(0),
+ m_adpcm_step(STEP_MIN),
+ m_regs(owner.regs()),
+ m_owner(owner)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the channel state
+//-------------------------------------------------
+
+void adpcm_b_channel::reset()
+{
+ m_status = STATUS_BRDY;
+ m_curnibble = 0;
+ m_curbyte = 0;
+ m_dummy_read = 0;
+ m_position = 0;
+ m_curaddress = 0;
+ m_accumulator = 0;
+ m_prev_accum = 0;
+ m_adpcm_step = STEP_MIN;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_b_channel::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_status);
+ state.save_restore(m_curnibble);
+ state.save_restore(m_curbyte);
+ state.save_restore(m_dummy_read);
+ state.save_restore(m_position);
+ state.save_restore(m_curaddress);
+ state.save_restore(m_accumulator);
+ state.save_restore(m_prev_accum);
+ state.save_restore(m_adpcm_step);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+void adpcm_b_channel::clock()
+{
+ // only process if active and not recording (which we don't support)
+ if (!m_regs.execute() || m_regs.record() || (m_status & STATUS_PLAYING) == 0)
+ {
+ m_status &= ~STATUS_PLAYING;
+ return;
+ }
+
+ // otherwise, advance the step
+ uint32_t position = m_position + m_regs.delta_n();
+ m_position = uint16_t(position);
+ if (position < 0x10000)
+ return;
+
+ // if we're about to process nibble 0, fetch sample
+ if (m_curnibble == 0)
+ {
+ // playing from RAM/ROM
+ if (m_regs.external())
+ m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress);
+ }
+
+ // extract the nibble from our current byte
+ uint8_t data = uint8_t(m_curbyte << (4 * m_curnibble)) >> 4;
+ m_curnibble ^= 1;
+
+ // we just processed the last nibble
+ if (m_curnibble == 0)
+ {
+ // if playing from RAM/ROM, check the end/limit address or advance
+ if (m_regs.external())
+ {
+ // handle the sample end, either repeating or stopping
+ if (at_end())
+ {
+ // if repeating, go back to the start
+ if (m_regs.repeat())
+ load_start();
+
+ // otherwise, done; set the EOS bit
+ else
+ {
+ m_accumulator = 0;
+ m_prev_accum = 0;
+ m_status = (m_status & ~STATUS_PLAYING) | STATUS_EOS;
+ debug::log_keyon("%s\n", "ADPCM EOS");
+ return;
+ }
+ }
+
+ // wrap at the limit address
+ else if (at_limit())
+ m_curaddress = 0;
+
+ // otherwise, advance the current address
+ else
+ {
+ m_curaddress++;
+ m_curaddress &= 0xffffff;
+ }
+ }
+
+ // if CPU-driven, copy the next byte and request more
+ else
+ {
+ m_curbyte = m_regs.cpudata();
+ m_status |= STATUS_BRDY;
+ }
+ }
+
+ // remember previous value for interpolation
+ m_prev_accum = m_accumulator;
+
+ // forecast to next forecast: 1/8, 3/8, 5/8, 7/8, 9/8, 11/8, 13/8, 15/8
+ int32_t delta = (2 * bitfield(data, 0, 3) + 1) * m_adpcm_step / 8;
+ if (bitfield(data, 3))
+ delta = -delta;
+
+ // add and clamp to 16 bits
+ m_accumulator = clamp(m_accumulator + delta, -32768, 32767);
+
+ // scale the ADPCM step: 0.9, 0.9, 0.9, 0.9, 1.2, 1.6, 2.0, 2.4
+ static uint8_t const s_step_scale[8] = { 57, 57, 57, 57, 77, 102, 128, 153 };
+ m_adpcm_step = clamp((m_adpcm_step * s_step_scale[bitfield(data, 0, 3)]) / 64, STEP_MIN, STEP_MAX);
+}
+
+
+//-------------------------------------------------
+// output - return the computed output value, with
+// panning applied
+//-------------------------------------------------
+
+template<int NumOutputs>
+void adpcm_b_channel::output(ymfm_output<NumOutputs> &output, uint32_t rshift) const
+{
+ // mask out some channels for debug purposes
+ if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) == 0)
+ return;
+
+ // do a linear interpolation between samples
+ int32_t result = (m_prev_accum * int32_t((m_position ^ 0xffff) + 1) + m_accumulator * int32_t(m_position)) >> 16;
+
+ // apply volume (level) in a linear fashion and reduce
+ result = (result * int32_t(m_regs.level())) >> (8 + rshift);
+
+ // apply to left/right
+ if (NumOutputs == 1 || m_regs.pan_left())
+ output.data[0] += result;
+ if (NumOutputs > 1 && m_regs.pan_right())
+ output.data[1] += result;
+}
+
+
+//-------------------------------------------------
+// read - handle special register reads
+//-------------------------------------------------
+
+uint8_t adpcm_b_channel::read(uint32_t regnum)
+{
+ uint8_t result = 0;
+
+ // register 8 reads over the bus under some conditions
+ if (regnum == 0x08 && !m_regs.execute() && !m_regs.record() && m_regs.external())
+ {
+ // two dummy reads are consumed first
+ if (m_dummy_read != 0)
+ {
+ load_start();
+ m_dummy_read--;
+ }
+
+ // read the data
+ else
+ {
+ // read from outside of the chip
+ result = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress++);
+
+ // did we hit the end? if so, signal EOS
+ if (at_end())
+ {
+ m_status = STATUS_EOS | STATUS_BRDY;
+ debug::log_keyon("%s\n", "ADPCM EOS");
+ }
+ else
+ {
+ // signal ready
+ m_status = STATUS_BRDY;
+ }
+
+ // wrap at the limit address
+ if (at_limit())
+ m_curaddress = 0;
+ }
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write - handle special register writes
+//-------------------------------------------------
+
+void adpcm_b_channel::write(uint32_t regnum, uint8_t value)
+{
+ // register 0 can do a reset; also use writes here to reset the
+ // dummy read counter
+ if (regnum == 0x00)
+ {
+ if (m_regs.execute())
+ {
+ load_start();
+
+ // don't log masked channels
+ if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) != 0)
+ debug::log_keyon("KeyOn ADPCM-B: rep=%d spk=%d pan=%d%d dac=%d 8b=%d rom=%d ext=%d rec=%d start=%04X end=%04X pre=%04X dn=%04X lvl=%02X lim=%04X\n",
+ m_regs.repeat(),
+ m_regs.speaker(),
+ m_regs.pan_left(),
+ m_regs.pan_right(),
+ m_regs.dac_enable(),
+ m_regs.dram_8bit(),
+ m_regs.rom_ram(),
+ m_regs.external(),
+ m_regs.record(),
+ m_regs.start(),
+ m_regs.end(),
+ m_regs.prescale(),
+ m_regs.delta_n(),
+ m_regs.level(),
+ m_regs.limit());
+ }
+ else
+ m_status &= ~STATUS_EOS;
+ if (m_regs.resetflag())
+ reset();
+ if (m_regs.external())
+ m_dummy_read = 2;
+ }
+
+ // register 8 writes over the bus under some conditions
+ else if (regnum == 0x08)
+ {
+ // if writing from the CPU during execute, clear the ready flag
+ if (m_regs.execute() && !m_regs.record() && !m_regs.external())
+ m_status &= ~STATUS_BRDY;
+
+ // if writing during "record", pass through as data
+ else if (!m_regs.execute() && m_regs.record() && m_regs.external())
+ {
+ // clear out dummy reads and set start address
+ if (m_dummy_read != 0)
+ {
+ load_start();
+ m_dummy_read = 0;
+ }
+
+ // did we hit the end? if so, signal EOS
+ if (at_end())
+ {
+ debug::log_keyon("%s\n", "ADPCM EOS");
+ m_status = STATUS_EOS | STATUS_BRDY;
+ }
+
+ // otherwise, write the data and signal ready
+ else
+ {
+ m_owner.intf().ymfm_external_write(ACCESS_ADPCM_B, m_curaddress++, value);
+ m_status = STATUS_BRDY;
+ }
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// address_shift - compute the current address
+// shift amount based on register settings
+//-------------------------------------------------
+
+uint32_t adpcm_b_channel::address_shift() const
+{
+ // if a constant address shift, just provide that
+ if (m_address_shift != 0)
+ return m_address_shift;
+
+ // if ROM or 8-bit DRAM, shift is 5 bits
+ if (m_regs.rom_ram())
+ return 5;
+ if (m_regs.dram_8bit())
+ return 5;
+
+ // otherwise, shift is 2 bits
+ return 2;
+}
+
+
+//-------------------------------------------------
+// load_start - load the start address and
+// initialize the state
+//-------------------------------------------------
+
+void adpcm_b_channel::load_start()
+{
+ m_status = (m_status & ~STATUS_EOS) | STATUS_PLAYING;
+ m_curaddress = m_regs.external() ? (m_regs.start() << address_shift()) : 0;
+ m_curnibble = 0;
+ m_curbyte = 0;
+ m_position = 0;
+ m_accumulator = 0;
+ m_prev_accum = 0;
+ m_adpcm_step = STEP_MIN;
+}
+
+
+
+//*********************************************************
+// ADPCM "B" ENGINE
+//*********************************************************
+
+//-------------------------------------------------
+// adpcm_b_engine - constructor
+//-------------------------------------------------
+
+adpcm_b_engine::adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift) :
+ m_intf(intf)
+{
+ // create the channel (only one supported for now, but leaving possibilities open)
+ m_channel = std::make_unique<adpcm_b_channel>(*this, addrshift);
+}
+
+
+//-------------------------------------------------
+// reset - reset the engine state
+//-------------------------------------------------
+
+void adpcm_b_engine::reset()
+{
+ // reset registers
+ m_regs.reset();
+
+ // reset each channel
+ m_channel->reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void adpcm_b_engine::save_restore(ymfm_saved_state &state)
+{
+ // save our state
+ m_regs.save_restore(state);
+
+ // save channel state
+ m_channel->save_restore(state);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+void adpcm_b_engine::clock()
+{
+ // clock each channel, setting a bit in result if it finished
+ m_channel->clock();
+}
+
+
+//-------------------------------------------------
+// output - master output function
+//-------------------------------------------------
+
+template<int NumOutputs>
+void adpcm_b_engine::output(ymfm_output<NumOutputs> &output, uint32_t rshift)
+{
+ // compute the output of each channel
+ m_channel->output(output, rshift);
+}
+
+template void adpcm_b_engine::output<1>(ymfm_output<1> &output, uint32_t rshift);
+template void adpcm_b_engine::output<2>(ymfm_output<2> &output, uint32_t rshift);
+
+
+//-------------------------------------------------
+// write - handle writes to the ADPCM-B registers
+//-------------------------------------------------
+
+void adpcm_b_engine::write(uint32_t regnum, uint8_t data)
+{
+ // store the raw value to the register array;
+ // most writes are passive, consumed only when needed
+ m_regs.write(regnum, data);
+
+ // let the channel handle any special writes
+ m_channel->write(regnum, data);
+}
+
+}
diff --git a/3rdparty/ymfm/src/ymfm_adpcm.h b/3rdparty/ymfm/src/ymfm_adpcm.h
new file mode 100644
index 00000000000..d74e24f2770
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_adpcm.h
@@ -0,0 +1,411 @@
+// 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_ADPCM_H
+#define YMFM_ADPCM_H
+
+#pragma once
+
+#include "ymfm.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// INTERFACE CLASSES
+//*********************************************************
+
+// forward declarations
+class adpcm_a_engine;
+class adpcm_b_engine;
+
+
+// ======================> adpcm_a_registers
+
+//
+// ADPCM-A register map:
+//
+// System-wide registers:
+// 00 x------- Dump (disable=1) or keyon (0) control
+// --xxxxxx Mask of channels to dump or keyon
+// 01 --xxxxxx Total level
+// 02 xxxxxxxx Test register
+// 08-0D x------- Pan left
+// -x------ Pan right
+// ---xxxxx Instrument level
+// 10-15 xxxxxxxx Start address (low)
+// 18-1D xxxxxxxx Start address (high)
+// 20-25 xxxxxxxx End address (low)
+// 28-2D xxxxxxxx End address (high)
+//
+class adpcm_a_registers
+{
+public:
+ // constants
+ static constexpr uint32_t OUTPUTS = 2;
+ static constexpr uint32_t CHANNELS = 6;
+ static constexpr uint32_t REGISTERS = 0x30;
+ static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
+
+ // constructor
+ adpcm_a_registers() { }
+
+ // 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);
+ return chnum;
+ }
+
+ // direct read/write access
+ void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
+
+ // system-wide registers
+ uint32_t dump() const { return bitfield(m_regdata[0x00], 7); }
+ uint32_t dump_mask() const { return bitfield(m_regdata[0x00], 0, 6); }
+ uint32_t total_level() const { return bitfield(m_regdata[0x01], 0, 6); }
+ uint32_t test() const { return m_regdata[0x02]; }
+
+ // per-channel registers
+ uint32_t ch_pan_left(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 7); }
+ uint32_t ch_pan_right(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 6); }
+ uint32_t ch_instrument_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 0, 5); }
+ uint32_t ch_start(uint32_t choffs) const { return m_regdata[choffs + 0x10] | (m_regdata[choffs + 0x18] << 8); }
+ uint32_t ch_end(uint32_t choffs) const { return m_regdata[choffs + 0x20] | (m_regdata[choffs + 0x28] << 8); }
+
+ // per-channel writes
+ void write_start(uint32_t choffs, uint32_t address)
+ {
+ write(choffs + 0x10, address);
+ write(choffs + 0x18, address >> 8);
+ }
+ void write_end(uint32_t choffs, uint32_t address)
+ {
+ write(choffs + 0x20, address);
+ write(choffs + 0x28, address >> 8);
+ }
+
+private:
+ // internal state
+ uint8_t m_regdata[REGISTERS]; // register data
+};
+
+
+// ======================> adpcm_a_channel
+
+class adpcm_a_channel
+{
+public:
+ // constructor
+ adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift);
+
+ // reset the channel state
+ void reset();
+
+ // save/restore
+ void save_restore(ymfm_saved_state &state);
+
+ // signal key on/off
+ void keyonoff(bool on);
+
+ // master clockingfunction
+ bool clock();
+
+ // return the computed output value, with panning applied
+ template<int NumOutputs>
+ void output(ymfm_output<NumOutputs> &output) const;
+
+private:
+ // internal state
+ uint32_t const m_choffs; // channel offset
+ uint32_t const m_address_shift; // address bits shift-left
+ uint32_t m_playing; // currently playing?
+ uint32_t m_curnibble; // index of the current nibble
+ uint32_t m_curbyte; // current byte of data
+ uint32_t m_curaddress; // current address
+ int32_t m_accumulator; // accumulator
+ int32_t m_step_index; // index in the stepping table
+ adpcm_a_registers &m_regs; // reference to registers
+ adpcm_a_engine &m_owner; // reference to our owner
+};
+
+
+// ======================> adpcm_a_engine
+
+class adpcm_a_engine
+{
+public:
+ static constexpr int CHANNELS = adpcm_a_registers::CHANNELS;
+
+ // constructor
+ adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift);
+
+ // reset our status
+ void reset();
+
+ // save/restore
+ void save_restore(ymfm_saved_state &state);
+
+ // master clocking function
+ uint32_t clock(uint32_t chanmask);
+
+ // compute sum of channel outputs
+ template<int NumOutputs>
+ void output(ymfm_output<NumOutputs> &output, uint32_t chanmask);
+
+ // write to the ADPCM-A registers
+ void write(uint32_t regnum, uint8_t data);
+
+ // set the start/end address for a channel (for hardcoded YM2608 percussion)
+ void set_start_end(uint8_t chnum, uint16_t start, uint16_t end)
+ {
+ uint32_t choffs = adpcm_a_registers::channel_offset(chnum);
+ m_regs.write_start(choffs, start);
+ m_regs.write_end(choffs, end);
+ }
+
+ // return a reference to our interface
+ ymfm_interface &intf() { return m_intf; }
+
+ // return a reference to our registers
+ adpcm_a_registers &regs() { return m_regs; }
+
+private:
+ // internal state
+ ymfm_interface &m_intf; // reference to the interface
+ std::unique_ptr<adpcm_a_channel> m_channel[CHANNELS]; // array of channels
+ adpcm_a_registers m_regs; // registers
+};
+
+
+// ======================> adpcm_b_registers
+
+//
+// ADPCM-B register map:
+//
+// System-wide registers:
+// 00 x------- Start of synthesis/analysis
+// -x------ Record
+// --x----- External/manual driving
+// ---x---- Repeat playback
+// ----x--- Speaker off
+// -------x Reset
+// 01 x------- Pan left
+// -x------ Pan right
+// ----x--- Start conversion
+// -----x-- DAC enable
+// ------x- DRAM access (1=8-bit granularity; 0=1-bit)
+// -------x RAM/ROM (1=ROM, 0=RAM)
+// 02 xxxxxxxx Start address (low)
+// 03 xxxxxxxx Start address (high)
+// 04 xxxxxxxx End address (low)
+// 05 xxxxxxxx End address (high)
+// 06 xxxxxxxx Prescale value (low)
+// 07 -----xxx Prescale value (high)
+// 08 xxxxxxxx CPU data/buffer
+// 09 xxxxxxxx Delta-N frequency scale (low)
+// 0a xxxxxxxx Delta-N frequency scale (high)
+// 0b xxxxxxxx Level control
+// 0c xxxxxxxx Limit address (low)
+// 0d xxxxxxxx Limit address (high)
+// 0e xxxxxxxx DAC data [YM2608/10]
+// 0f xxxxxxxx PCM data [YM2608/10]
+// 0e xxxxxxxx DAC data high [Y8950]
+// 0f xx------ DAC data low [Y8950]
+// 10 -----xxx DAC data exponent [Y8950]
+//
+class adpcm_b_registers
+{
+public:
+ // constants
+ static constexpr uint32_t REGISTERS = 0x11;
+
+ // constructor
+ adpcm_b_registers() { }
+
+ // reset to initial state
+ void reset();
+
+ // save/restore
+ void save_restore(ymfm_saved_state &state);
+
+ // direct read/write access
+ void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
+
+ // system-wide registers
+ uint32_t execute() const { return bitfield(m_regdata[0x00], 7); }
+ uint32_t record() const { return bitfield(m_regdata[0x00], 6); }
+ uint32_t external() const { return bitfield(m_regdata[0x00], 5); }
+ uint32_t repeat() const { return bitfield(m_regdata[0x00], 4); }
+ uint32_t speaker() const { return bitfield(m_regdata[0x00], 3); }
+ uint32_t resetflag() const { return bitfield(m_regdata[0x00], 0); }
+ uint32_t pan_left() const { return bitfield(m_regdata[0x01], 7); }
+ uint32_t pan_right() const { return bitfield(m_regdata[0x01], 6); }
+ uint32_t start_conversion() const { return bitfield(m_regdata[0x01], 3); }
+ uint32_t dac_enable() const { return bitfield(m_regdata[0x01], 2); }
+ uint32_t dram_8bit() const { return bitfield(m_regdata[0x01], 1); }
+ uint32_t rom_ram() const { return bitfield(m_regdata[0x01], 0); }
+ uint32_t start() const { return m_regdata[0x02] | (m_regdata[0x03] << 8); }
+ uint32_t end() const { return m_regdata[0x04] | (m_regdata[0x05] << 8); }
+ uint32_t prescale() const { return m_regdata[0x06] | (bitfield(m_regdata[0x07], 0, 3) << 8); }
+ uint32_t cpudata() const { return m_regdata[0x08]; }
+ uint32_t delta_n() const { return m_regdata[0x09] | (m_regdata[0x0a] << 8); }
+ uint32_t level() const { return m_regdata[0x0b]; }
+ uint32_t limit() const { return m_regdata[0x0c] | (m_regdata[0x0d] << 8); }
+ uint32_t dac() const { return m_regdata[0x0e]; }
+ uint32_t pcm() const { return m_regdata[0x0f]; }
+
+private:
+ // internal state
+ uint8_t m_regdata[REGISTERS]; // register data
+};
+
+
+// ======================> adpcm_b_channel
+
+class adpcm_b_channel
+{
+ static constexpr int32_t STEP_MIN = 127;
+ static constexpr int32_t STEP_MAX = 24576;
+
+public:
+ static constexpr uint8_t STATUS_EOS = 0x01;
+ static constexpr uint8_t STATUS_BRDY = 0x02;
+ static constexpr uint8_t STATUS_PLAYING = 0x04;
+
+ // constructor
+ adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift);
+
+ // reset the channel state
+ void reset();
+
+ // save/restore
+ void save_restore(ymfm_saved_state &state);
+
+ // signal key on/off
+ void keyonoff(bool on);
+
+ // master clocking function
+ void clock();
+
+ // return the computed output value, with panning applied
+ template<int NumOutputs>
+ void output(ymfm_output<NumOutputs> &output, uint32_t rshift) const;
+
+ // return the status register
+ uint8_t status() const { return m_status; }
+
+ // handle special register reads
+ uint8_t read(uint32_t regnum);
+
+ // handle special register writes
+ void write(uint32_t regnum, uint8_t value);
+
+private:
+ // helper - return the current address shift
+ uint32_t address_shift() const;
+
+ // load the start address
+ void load_start();
+
+ // limit checker; stops at the last byte of the chunk described by address_shift()
+ bool at_limit() const { return (m_curaddress == (((m_regs.limit() + 1) << address_shift()) - 1)); }
+
+ // end checker; stops at the last byte of the chunk described by address_shift()
+ bool at_end() const { return (m_curaddress == (((m_regs.end() + 1) << address_shift()) - 1)); }
+
+ // internal state
+ uint32_t const m_address_shift; // address bits shift-left
+ uint32_t m_status; // currently playing?
+ uint32_t m_curnibble; // index of the current nibble
+ uint32_t m_curbyte; // current byte of data
+ uint32_t m_dummy_read; // dummy read tracker
+ uint32_t m_position; // current fractional position
+ uint32_t m_curaddress; // current address
+ int32_t m_accumulator; // accumulator
+ int32_t m_prev_accum; // previous accumulator (for linear interp)
+ int32_t m_adpcm_step; // next forecast
+ adpcm_b_registers &m_regs; // reference to registers
+ adpcm_b_engine &m_owner; // reference to our owner
+};
+
+
+// ======================> adpcm_b_engine
+
+class adpcm_b_engine
+{
+public:
+ // constructor
+ adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift = 0);
+
+ // reset our status
+ void reset();
+
+ // save/restore
+ void save_restore(ymfm_saved_state &state);
+
+ // master clocking function
+ void clock();
+
+ // compute sum of channel outputs
+ template<int NumOutputs>
+ void output(ymfm_output<NumOutputs> &output, uint32_t rshift);
+
+ // read from the ADPCM-B registers
+ uint32_t read(uint32_t regnum) { return m_channel->read(regnum); }
+
+ // write to the ADPCM-B registers
+ void write(uint32_t regnum, uint8_t data);
+
+ // status
+ uint8_t status() const { return m_channel->status(); }
+
+ // return a reference to our interface
+ ymfm_interface &intf() { return m_intf; }
+
+ // return a reference to our registers
+ adpcm_b_registers &regs() { return m_regs; }
+
+private:
+ // internal state
+ ymfm_interface &m_intf; // reference to our interface
+ std::unique_ptr<adpcm_b_channel> m_channel; // channel pointer
+ adpcm_b_registers m_regs; // registers
+};
+
+}
+
+#endif // YMFM_ADPCM_H
diff --git a/3rdparty/ymfm/src/ymfm_fm.h b/3rdparty/ymfm/src/ymfm_fm.h
new file mode 100644
index 00000000000..d40409fddfd
--- /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 < m_op.size());
+ 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)
+ std::array<fm_operator<RegisterType> *, 4> m_op; // 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
diff --git a/3rdparty/ymfm/src/ymfm_fm.ipp b/3rdparty/ymfm/src/ymfm_fm.ipp
new file mode 100644
index 00000000000..675424839ce
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_fm.ipp
@@ -0,0 +1,1592 @@
+// 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.
+
+namespace ymfm
+{
+
+//*********************************************************
+// GLOBAL TABLE LOOKUPS
+//*********************************************************
+
+//-------------------------------------------------
+// abs_sin_attenuation - given a sin (phase) input
+// where the range 0-2*PI is mapped onto 10 bits,
+// return the absolute value of sin(input),
+// logarithmically-adjusted and treated as an
+// attenuation value, in 4.8 fixed point format
+//-------------------------------------------------
+
+inline uint32_t abs_sin_attenuation(uint32_t input)
+{
+ // the values here are stored as 4.8 logarithmic values for 1/4 phase
+ // this matches the internal format of the OPN chip, extracted from the die
+ static uint16_t const s_sin_table[256] =
+ {
+ 0x859,0x6c3,0x607,0x58b,0x52e,0x4e4,0x4a6,0x471,0x443,0x41a,0x3f5,0x3d3,0x3b5,0x398,0x37e,0x365,
+ 0x34e,0x339,0x324,0x311,0x2ff,0x2ed,0x2dc,0x2cd,0x2bd,0x2af,0x2a0,0x293,0x286,0x279,0x26d,0x261,
+ 0x256,0x24b,0x240,0x236,0x22c,0x222,0x218,0x20f,0x206,0x1fd,0x1f5,0x1ec,0x1e4,0x1dc,0x1d4,0x1cd,
+ 0x1c5,0x1be,0x1b7,0x1b0,0x1a9,0x1a2,0x19b,0x195,0x18f,0x188,0x182,0x17c,0x177,0x171,0x16b,0x166,
+ 0x160,0x15b,0x155,0x150,0x14b,0x146,0x141,0x13c,0x137,0x133,0x12e,0x129,0x125,0x121,0x11c,0x118,
+ 0x114,0x10f,0x10b,0x107,0x103,0x0ff,0x0fb,0x0f8,0x0f4,0x0f0,0x0ec,0x0e9,0x0e5,0x0e2,0x0de,0x0db,
+ 0x0d7,0x0d4,0x0d1,0x0cd,0x0ca,0x0c7,0x0c4,0x0c1,0x0be,0x0bb,0x0b8,0x0b5,0x0b2,0x0af,0x0ac,0x0a9,
+ 0x0a7,0x0a4,0x0a1,0x09f,0x09c,0x099,0x097,0x094,0x092,0x08f,0x08d,0x08a,0x088,0x086,0x083,0x081,
+ 0x07f,0x07d,0x07a,0x078,0x076,0x074,0x072,0x070,0x06e,0x06c,0x06a,0x068,0x066,0x064,0x062,0x060,
+ 0x05e,0x05c,0x05b,0x059,0x057,0x055,0x053,0x052,0x050,0x04e,0x04d,0x04b,0x04a,0x048,0x046,0x045,
+ 0x043,0x042,0x040,0x03f,0x03e,0x03c,0x03b,0x039,0x038,0x037,0x035,0x034,0x033,0x031,0x030,0x02f,
+ 0x02e,0x02d,0x02b,0x02a,0x029,0x028,0x027,0x026,0x025,0x024,0x023,0x022,0x021,0x020,0x01f,0x01e,
+ 0x01d,0x01c,0x01b,0x01a,0x019,0x018,0x017,0x017,0x016,0x015,0x014,0x014,0x013,0x012,0x011,0x011,
+ 0x010,0x00f,0x00f,0x00e,0x00d,0x00d,0x00c,0x00c,0x00b,0x00a,0x00a,0x009,0x009,0x008,0x008,0x007,
+ 0x007,0x007,0x006,0x006,0x005,0x005,0x005,0x004,0x004,0x004,0x003,0x003,0x003,0x002,0x002,0x002,
+ 0x002,0x001,0x001,0x001,0x001,0x001,0x001,0x001,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000
+ };
+
+ // if the top bit is set, we're in the second half of the curve
+ // which is a mirror image, so invert the index
+ if (bitfield(input, 8))
+ input = ~input;
+
+ // return the value from the table
+ return s_sin_table[input & 0xff];
+}
+
+
+//-------------------------------------------------
+// attenuation_to_volume - given a 5.8 fixed point
+// logarithmic attenuation value, return a 13-bit
+// linear volume
+//-------------------------------------------------
+
+inline uint32_t attenuation_to_volume(uint32_t input)
+{
+ // the values here are 10-bit mantissas with an implied leading bit
+ // this matches the internal format of the OPN chip, extracted from the die
+
+ // as a nod to performance, the implicit 0x400 bit is pre-incorporated, and
+ // the values are left-shifted by 2 so that a simple right shift is all that
+ // is needed; also the order is reversed to save a NOT on the input
+#define X(a) (((a) | 0x400) << 2)
+ static uint16_t const s_power_table[256] =
+ {
+ X(0x3fa),X(0x3f5),X(0x3ef),X(0x3ea),X(0x3e4),X(0x3df),X(0x3da),X(0x3d4),
+ X(0x3cf),X(0x3c9),X(0x3c4),X(0x3bf),X(0x3b9),X(0x3b4),X(0x3ae),X(0x3a9),
+ X(0x3a4),X(0x39f),X(0x399),X(0x394),X(0x38f),X(0x38a),X(0x384),X(0x37f),
+ X(0x37a),X(0x375),X(0x370),X(0x36a),X(0x365),X(0x360),X(0x35b),X(0x356),
+ X(0x351),X(0x34c),X(0x347),X(0x342),X(0x33d),X(0x338),X(0x333),X(0x32e),
+ X(0x329),X(0x324),X(0x31f),X(0x31a),X(0x315),X(0x310),X(0x30b),X(0x306),
+ X(0x302),X(0x2fd),X(0x2f8),X(0x2f3),X(0x2ee),X(0x2e9),X(0x2e5),X(0x2e0),
+ X(0x2db),X(0x2d6),X(0x2d2),X(0x2cd),X(0x2c8),X(0x2c4),X(0x2bf),X(0x2ba),
+ X(0x2b5),X(0x2b1),X(0x2ac),X(0x2a8),X(0x2a3),X(0x29e),X(0x29a),X(0x295),
+ X(0x291),X(0x28c),X(0x288),X(0x283),X(0x27f),X(0x27a),X(0x276),X(0x271),
+ X(0x26d),X(0x268),X(0x264),X(0x25f),X(0x25b),X(0x257),X(0x252),X(0x24e),
+ X(0x249),X(0x245),X(0x241),X(0x23c),X(0x238),X(0x234),X(0x230),X(0x22b),
+ X(0x227),X(0x223),X(0x21e),X(0x21a),X(0x216),X(0x212),X(0x20e),X(0x209),
+ X(0x205),X(0x201),X(0x1fd),X(0x1f9),X(0x1f5),X(0x1f0),X(0x1ec),X(0x1e8),
+ X(0x1e4),X(0x1e0),X(0x1dc),X(0x1d8),X(0x1d4),X(0x1d0),X(0x1cc),X(0x1c8),
+ X(0x1c4),X(0x1c0),X(0x1bc),X(0x1b8),X(0x1b4),X(0x1b0),X(0x1ac),X(0x1a8),
+ X(0x1a4),X(0x1a0),X(0x19c),X(0x199),X(0x195),X(0x191),X(0x18d),X(0x189),
+ X(0x185),X(0x181),X(0x17e),X(0x17a),X(0x176),X(0x172),X(0x16f),X(0x16b),
+ X(0x167),X(0x163),X(0x160),X(0x15c),X(0x158),X(0x154),X(0x151),X(0x14d),
+ X(0x149),X(0x146),X(0x142),X(0x13e),X(0x13b),X(0x137),X(0x134),X(0x130),
+ X(0x12c),X(0x129),X(0x125),X(0x122),X(0x11e),X(0x11b),X(0x117),X(0x114),
+ X(0x110),X(0x10c),X(0x109),X(0x106),X(0x102),X(0x0ff),X(0x0fb),X(0x0f8),
+ X(0x0f4),X(0x0f1),X(0x0ed),X(0x0ea),X(0x0e7),X(0x0e3),X(0x0e0),X(0x0dc),
+ X(0x0d9),X(0x0d6),X(0x0d2),X(0x0cf),X(0x0cc),X(0x0c8),X(0x0c5),X(0x0c2),
+ X(0x0be),X(0x0bb),X(0x0b8),X(0x0b5),X(0x0b1),X(0x0ae),X(0x0ab),X(0x0a8),
+ X(0x0a4),X(0x0a1),X(0x09e),X(0x09b),X(0x098),X(0x094),X(0x091),X(0x08e),
+ X(0x08b),X(0x088),X(0x085),X(0x082),X(0x07e),X(0x07b),X(0x078),X(0x075),
+ X(0x072),X(0x06f),X(0x06c),X(0x069),X(0x066),X(0x063),X(0x060),X(0x05d),
+ X(0x05a),X(0x057),X(0x054),X(0x051),X(0x04e),X(0x04b),X(0x048),X(0x045),
+ X(0x042),X(0x03f),X(0x03c),X(0x039),X(0x036),X(0x033),X(0x030),X(0x02d),
+ X(0x02a),X(0x028),X(0x025),X(0x022),X(0x01f),X(0x01c),X(0x019),X(0x016),
+ X(0x014),X(0x011),X(0x00e),X(0x00b),X(0x008),X(0x006),X(0x003),X(0x000)
+ };
+#undef X
+
+ // look up the fractional part, then shift by the whole
+ return s_power_table[input & 0xff] >> (input >> 8);
+}
+
+
+//-------------------------------------------------
+// attenuation_increment - given a 6-bit ADSR
+// rate value and a 3-bit stepping index,
+// return a 4-bit increment to the attenutaion
+// for this step (or for the attack case, the
+// fractional scale factor to decrease by)
+//-------------------------------------------------
+
+inline uint32_t attenuation_increment(uint32_t rate, uint32_t index)
+{
+ static uint32_t const s_increment_table[64] =
+ {
+ 0x00000000, 0x00000000, 0x10101010, 0x10101010, // 0-3 (0x00-0x03)
+ 0x10101010, 0x10101010, 0x11101110, 0x11101110, // 4-7 (0x04-0x07)
+ 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 8-11 (0x08-0x0B)
+ 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 12-15 (0x0C-0x0F)
+ 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 16-19 (0x10-0x13)
+ 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 20-23 (0x14-0x17)
+ 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 24-27 (0x18-0x1B)
+ 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 28-31 (0x1C-0x1F)
+ 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 32-35 (0x20-0x23)
+ 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 36-39 (0x24-0x27)
+ 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 40-43 (0x28-0x2B)
+ 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 44-47 (0x2C-0x2F)
+ 0x11111111, 0x21112111, 0x21212121, 0x22212221, // 48-51 (0x30-0x33)
+ 0x22222222, 0x42224222, 0x42424242, 0x44424442, // 52-55 (0x34-0x37)
+ 0x44444444, 0x84448444, 0x84848484, 0x88848884, // 56-59 (0x38-0x3B)
+ 0x88888888, 0x88888888, 0x88888888, 0x88888888 // 60-63 (0x3C-0x3F)
+ };
+ return bitfield(s_increment_table[rate], 4*index, 4);
+}
+
+
+//-------------------------------------------------
+// detune_adjustment - given a 5-bit key code
+// value and a 3-bit detune parameter, return a
+// 6-bit signed phase displacement; this table
+// has been verified against Nuked's equations,
+// but the equations are rather complicated, so
+// we'll keep the simplicity of the table
+//-------------------------------------------------
+
+inline int32_t detune_adjustment(uint32_t detune, uint32_t keycode)
+{
+ static uint8_t const s_detune_adjustment[32][4] =
+ {
+ { 0, 0, 1, 2 }, { 0, 0, 1, 2 }, { 0, 0, 1, 2 }, { 0, 0, 1, 2 },
+ { 0, 1, 2, 2 }, { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, { 0, 1, 2, 3 },
+ { 0, 1, 2, 4 }, { 0, 1, 3, 4 }, { 0, 1, 3, 4 }, { 0, 1, 3, 5 },
+ { 0, 2, 4, 5 }, { 0, 2, 4, 6 }, { 0, 2, 4, 6 }, { 0, 2, 5, 7 },
+ { 0, 2, 5, 8 }, { 0, 3, 6, 8 }, { 0, 3, 6, 9 }, { 0, 3, 7, 10 },
+ { 0, 4, 8, 11 }, { 0, 4, 8, 12 }, { 0, 4, 9, 13 }, { 0, 5, 10, 14 },
+ { 0, 5, 11, 16 }, { 0, 6, 12, 17 }, { 0, 6, 13, 19 }, { 0, 7, 14, 20 },
+ { 0, 8, 16, 22 }, { 0, 8, 16, 22 }, { 0, 8, 16, 22 }, { 0, 8, 16, 22 }
+ };
+ int32_t result = s_detune_adjustment[keycode][detune & 3];
+ return bitfield(detune, 2) ? -result : result;
+}
+
+
+//-------------------------------------------------
+// opm_key_code_to_phase_step - converts an
+// OPM concatenated block (3 bits), keycode
+// (4 bits) and key fraction (6 bits) to a 0.10
+// phase step, after applying the given delta;
+// this applies to OPM and OPZ, so it lives here
+// in a central location
+//-------------------------------------------------
+
+inline uint32_t opm_key_code_to_phase_step(uint32_t block_freq, int32_t delta)
+{
+ // The phase step is essentially the fnum in OPN-speak. To compute this table,
+ // we used the standard formula for computing the frequency of a note, and
+ // then converted that frequency to fnum using the formula documented in the
+ // YM2608 manual.
+ //
+ // However, the YM2608 manual describes everything in terms of a nominal 8MHz
+ // clock, which produces an FM clock of:
+ //
+ // 8000000 / 24(operators) / 6(prescale) = 55555Hz FM clock
+ //
+ // Whereas the descriptions for the YM2151 use a nominal 3.579545MHz clock:
+ //
+ // 3579545 / 32(operators) / 2(prescale) = 55930Hz FM clock
+ //
+ // To correct for this, the YM2608 formula was adjusted to use a clock of
+ // 8053920Hz, giving this equation for the fnum:
+ //
+ // fnum = (double(144) * freq * (1 << 20)) / double(8053920) / 4;
+ //
+ // Unfortunately, the computed table differs in a few spots from the data
+ // verified from an actual chip. The table below comes from David Viens'
+ // analysis, used with his permission.
+ static const uint32_t s_phase_step[12*64] =
+ {
+ 41568,41600,41632,41664,41696,41728,41760,41792,41856,41888,41920,41952,42016,42048,42080,42112,
+ 42176,42208,42240,42272,42304,42336,42368,42400,42464,42496,42528,42560,42624,42656,42688,42720,
+ 42784,42816,42848,42880,42912,42944,42976,43008,43072,43104,43136,43168,43232,43264,43296,43328,
+ 43392,43424,43456,43488,43552,43584,43616,43648,43712,43744,43776,43808,43872,43904,43936,43968,
+ 44032,44064,44096,44128,44192,44224,44256,44288,44352,44384,44416,44448,44512,44544,44576,44608,
+ 44672,44704,44736,44768,44832,44864,44896,44928,44992,45024,45056,45088,45152,45184,45216,45248,
+ 45312,45344,45376,45408,45472,45504,45536,45568,45632,45664,45728,45760,45792,45824,45888,45920,
+ 45984,46016,46048,46080,46144,46176,46208,46240,46304,46336,46368,46400,46464,46496,46528,46560,
+ 46656,46688,46720,46752,46816,46848,46880,46912,46976,47008,47072,47104,47136,47168,47232,47264,
+ 47328,47360,47392,47424,47488,47520,47552,47584,47648,47680,47744,47776,47808,47840,47904,47936,
+ 48032,48064,48096,48128,48192,48224,48288,48320,48384,48416,48448,48480,48544,48576,48640,48672,
+ 48736,48768,48800,48832,48896,48928,48992,49024,49088,49120,49152,49184,49248,49280,49344,49376,
+ 49440,49472,49504,49536,49600,49632,49696,49728,49792,49824,49856,49888,49952,49984,50048,50080,
+ 50144,50176,50208,50240,50304,50336,50400,50432,50496,50528,50560,50592,50656,50688,50752,50784,
+ 50880,50912,50944,50976,51040,51072,51136,51168,51232,51264,51328,51360,51424,51456,51488,51520,
+ 51616,51648,51680,51712,51776,51808,51872,51904,51968,52000,52064,52096,52160,52192,52224,52256,
+ 52384,52416,52448,52480,52544,52576,52640,52672,52736,52768,52832,52864,52928,52960,52992,53024,
+ 53120,53152,53216,53248,53312,53344,53408,53440,53504,53536,53600,53632,53696,53728,53792,53824,
+ 53920,53952,54016,54048,54112,54144,54208,54240,54304,54336,54400,54432,54496,54528,54592,54624,
+ 54688,54720,54784,54816,54880,54912,54976,55008,55072,55104,55168,55200,55264,55296,55360,55392,
+ 55488,55520,55584,55616,55680,55712,55776,55808,55872,55936,55968,56032,56064,56128,56160,56224,
+ 56288,56320,56384,56416,56480,56512,56576,56608,56672,56736,56768,56832,56864,56928,56960,57024,
+ 57120,57152,57216,57248,57312,57376,57408,57472,57536,57568,57632,57664,57728,57792,57824,57888,
+ 57952,57984,58048,58080,58144,58208,58240,58304,58368,58400,58464,58496,58560,58624,58656,58720,
+ 58784,58816,58880,58912,58976,59040,59072,59136,59200,59232,59296,59328,59392,59456,59488,59552,
+ 59648,59680,59744,59776,59840,59904,59936,60000,60064,60128,60160,60224,60288,60320,60384,60416,
+ 60512,60544,60608,60640,60704,60768,60800,60864,60928,60992,61024,61088,61152,61184,61248,61280,
+ 61376,61408,61472,61536,61600,61632,61696,61760,61824,61856,61920,61984,62048,62080,62144,62208,
+ 62272,62304,62368,62432,62496,62528,62592,62656,62720,62752,62816,62880,62944,62976,63040,63104,
+ 63200,63232,63296,63360,63424,63456,63520,63584,63648,63680,63744,63808,63872,63904,63968,64032,
+ 64096,64128,64192,64256,64320,64352,64416,64480,64544,64608,64672,64704,64768,64832,64896,64928,
+ 65024,65056,65120,65184,65248,65312,65376,65408,65504,65536,65600,65664,65728,65792,65856,65888,
+ 65984,66016,66080,66144,66208,66272,66336,66368,66464,66496,66560,66624,66688,66752,66816,66848,
+ 66944,66976,67040,67104,67168,67232,67296,67328,67424,67456,67520,67584,67648,67712,67776,67808,
+ 67904,67936,68000,68064,68128,68192,68256,68288,68384,68448,68512,68544,68640,68672,68736,68800,
+ 68896,68928,68992,69056,69120,69184,69248,69280,69376,69440,69504,69536,69632,69664,69728,69792,
+ 69920,69952,70016,70080,70144,70208,70272,70304,70400,70464,70528,70560,70656,70688,70752,70816,
+ 70912,70976,71040,71104,71136,71232,71264,71360,71424,71488,71552,71616,71648,71744,71776,71872,
+ 71968,72032,72096,72160,72192,72288,72320,72416,72480,72544,72608,72672,72704,72800,72832,72928,
+ 72992,73056,73120,73184,73216,73312,73344,73440,73504,73568,73632,73696,73728,73824,73856,73952,
+ 74080,74144,74208,74272,74304,74400,74432,74528,74592,74656,74720,74784,74816,74912,74944,75040,
+ 75136,75200,75264,75328,75360,75456,75488,75584,75648,75712,75776,75840,75872,75968,76000,76096,
+ 76224,76288,76352,76416,76448,76544,76576,76672,76736,76800,76864,76928,77024,77120,77152,77248,
+ 77344,77408,77472,77536,77568,77664,77696,77792,77856,77920,77984,78048,78144,78240,78272,78368,
+ 78464,78528,78592,78656,78688,78784,78816,78912,78976,79040,79104,79168,79264,79360,79392,79488,
+ 79616,79680,79744,79808,79840,79936,79968,80064,80128,80192,80256,80320,80416,80512,80544,80640,
+ 80768,80832,80896,80960,80992,81088,81120,81216,81280,81344,81408,81472,81568,81664,81696,81792,
+ 81952,82016,82080,82144,82176,82272,82304,82400,82464,82528,82592,82656,82752,82848,82880,82976
+ };
+
+ // extract the block (octave) first
+ uint32_t block = bitfield(block_freq, 10, 3);
+
+ // the keycode (bits 6-9) is "gappy", mapping 12 values over 16 in each
+ // octave; to correct for this, we multiply the 4-bit value by 3/4 (or
+ // rather subtract 1/4); note that a (invalid) value of 15 will bleed into
+ // the next octave -- this is confirmed
+ uint32_t adjusted_code = bitfield(block_freq, 6, 4) - bitfield(block_freq, 8, 2);
+
+ // now re-insert the 6-bit fraction
+ int32_t eff_freq = (adjusted_code << 6) | bitfield(block_freq, 0, 6);
+
+ // now that the gaps are removed, add the delta
+ eff_freq += delta;
+
+ // handle over/underflow by adjusting the block:
+ if (uint32_t(eff_freq) >= 768)
+ {
+ // minimum delta is -512 (PM), so we can only underflow by 1 octave
+ if (eff_freq < 0)
+ {
+ eff_freq += 768;
+ if (block-- == 0)
+ return s_phase_step[0] >> 7;
+ }
+
+ // maximum delta is +512+608 (PM+detune), so we can overflow by up to 2 octaves
+ else
+ {
+ eff_freq -= 768;
+ if (eff_freq >= 768)
+ block++, eff_freq -= 768;
+ if (block++ >= 7)
+ return s_phase_step[767];
+ }
+ }
+
+ // look up the phase shift for the key code, then shift by octave
+ return s_phase_step[eff_freq] >> (block ^ 7);
+}
+
+
+//-------------------------------------------------
+// opn_lfo_pm_phase_adjustment - given the 7 most
+// significant frequency number bits, plus a 3-bit
+// PM depth value and a signed 5-bit raw PM value,
+// return a signed PM adjustment to the frequency;
+// algorithm written to match Nuked behavior
+//-------------------------------------------------
+
+inline int32_t opn_lfo_pm_phase_adjustment(uint32_t fnum_bits, uint32_t pm_sensitivity, int32_t lfo_raw_pm)
+{
+ // this table encodes 2 shift values to apply to the top 7 bits
+ // of fnum; it is effectively a cheap multiply by a constant
+ // value containing 0-2 bits
+ static uint8_t const s_lfo_pm_shifts[8][8] =
+ {
+ { 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77 },
+ { 0x77, 0x77, 0x77, 0x77, 0x72, 0x72, 0x72, 0x72 },
+ { 0x77, 0x77, 0x77, 0x72, 0x72, 0x72, 0x17, 0x17 },
+ { 0x77, 0x77, 0x72, 0x72, 0x17, 0x17, 0x12, 0x12 },
+ { 0x77, 0x77, 0x72, 0x17, 0x17, 0x17, 0x12, 0x07 },
+ { 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 },
+ { 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 },
+ { 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 }
+ };
+
+ // look up the relevant shifts
+ int32_t abs_pm = (lfo_raw_pm < 0) ? -lfo_raw_pm : lfo_raw_pm;
+ uint32_t const shifts = s_lfo_pm_shifts[pm_sensitivity][bitfield(abs_pm, 0, 3)];
+
+ // compute the adjustment
+ int32_t adjust = (fnum_bits >> bitfield(shifts, 0, 4)) + (fnum_bits >> bitfield(shifts, 4, 4));
+ if (pm_sensitivity > 5)
+ adjust <<= pm_sensitivity - 5;
+ adjust >>= 2;
+
+ // every 16 cycles it inverts sign
+ return (lfo_raw_pm < 0) ? -adjust : adjust;
+}
+
+
+
+//*********************************************************
+// FM OPERATOR
+//*********************************************************
+
+//-------------------------------------------------
+// fm_operator - constructor
+//-------------------------------------------------
+
+template<class RegisterType>
+fm_operator<RegisterType>::fm_operator(fm_engine_base<RegisterType> &owner, uint32_t opoffs) :
+ m_choffs(0),
+ m_opoffs(opoffs),
+ m_phase(0),
+ m_env_attenuation(0x3ff),
+ m_env_state(EG_RELEASE),
+ m_ssg_inverted(false),
+ m_key_state(0),
+ m_keyon_live(0),
+ m_regs(owner.regs()),
+ m_owner(owner)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the channel state
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_operator<RegisterType>::reset()
+{
+ // reset our data
+ m_phase = 0;
+ m_env_attenuation = 0x3ff;
+ m_env_state = EG_RELEASE;
+ m_ssg_inverted = 0;
+ m_key_state = 0;
+ m_keyon_live = 0;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_operator<RegisterType>::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_phase);
+ state.save_restore(m_env_attenuation);
+ state.save_restore(m_env_state);
+ state.save_restore(m_ssg_inverted);
+ state.save_restore(m_key_state);
+ state.save_restore(m_keyon_live);
+}
+
+
+//-------------------------------------------------
+// prepare - prepare for clocking
+//-------------------------------------------------
+
+template<class RegisterType>
+bool fm_operator<RegisterType>::prepare()
+{
+ // cache the data
+ m_regs.cache_operator_data(m_choffs, m_opoffs, m_cache);
+
+ // clock the key state
+ clock_keystate(uint32_t(m_keyon_live != 0));
+ m_keyon_live &= ~(1 << KEYON_CSM);
+
+ // we're active until we're quiet after the release
+ return (m_env_state != (RegisterType::EG_HAS_REVERB ? EG_REVERB : EG_RELEASE) || m_env_attenuation < EG_QUIET);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_operator<RegisterType>::clock(uint32_t env_counter, int32_t lfo_raw_pm)
+{
+ // clock the SSG-EG state (OPN/OPNA)
+ if (m_regs.op_ssg_eg_enable(m_opoffs))
+ clock_ssg_eg_state();
+ else
+ m_ssg_inverted = false;
+
+ // clock the envelope if on an envelope cycle; env_counter is a x.2 value
+ if (bitfield(env_counter, 0, 2) == 0)
+ clock_envelope(env_counter >> 2);
+
+ // clock the phase
+ clock_phase(lfo_raw_pm);
+}
+
+
+//-------------------------------------------------
+// compute_volume - compute the 14-bit signed
+// volume of this operator, given a phase
+// modulation and an AM LFO offset
+//-------------------------------------------------
+
+template<class RegisterType>
+int32_t fm_operator<RegisterType>::compute_volume(uint32_t phase, uint32_t am_offset) const
+{
+ // the low 10 bits of phase represents a full 2*PI period over
+ // the full sin wave
+
+ // early out if the envelope is effectively off
+ if (m_env_attenuation > EG_QUIET)
+ return 0;
+
+ // get the absolute value of the sin, as attenuation, as a 4.8 fixed point value
+ uint32_t sin_attenuation = m_cache.waveform[phase & (RegisterType::WAVEFORM_LENGTH - 1)];
+
+ // get the attenuation from the evelope generator as a 4.6 value, shifted up to 4.8
+ uint32_t env_attenuation = envelope_attenuation(am_offset) << 2;
+
+ // combine into a 5.8 value, then convert from attenuation to 13-bit linear volume
+ int32_t result = attenuation_to_volume((sin_attenuation & 0x7fff) + env_attenuation);
+
+ // negate if in the negative part of the sin wave (sign bit gives 14 bits)
+ return bitfield(sin_attenuation, 15) ? -result : result;
+}
+
+
+//-------------------------------------------------
+// compute_noise_volume - compute the 14-bit
+// signed noise volume of this operator, given a
+// noise input value and an AM offset
+//-------------------------------------------------
+
+template<class RegisterType>
+int32_t fm_operator<RegisterType>::compute_noise_volume(uint32_t am_offset) const
+{
+ // application manual says the logarithmic transform is not applied here, so we
+ // just use the raw envelope attenuation, inverted (since 0 attenuation should be
+ // maximum), and shift it up from a 10-bit value to an 11-bit value
+ int32_t result = (envelope_attenuation(am_offset) ^ 0x3ff) << 1;
+
+ // QUESTION: is AM applied still?
+
+ // negate based on the noise state
+ return bitfield(m_regs.noise_state(), 0) ? -result : result;
+}
+
+
+//-------------------------------------------------
+// keyonoff - signal a key on/off event
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_operator<RegisterType>::keyonoff(uint32_t on, keyon_type type)
+{
+ m_keyon_live = (m_keyon_live & ~(1 << int(type))) | (bitfield(on, 0) << int(type));
+}
+
+
+//-------------------------------------------------
+// start_attack - start the attack phase; called
+// when a keyon happens or when an SSG-EG cycle
+// is complete and restarts
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_operator<RegisterType>::start_attack(bool is_restart)
+{
+ // don't change anything if already in attack state
+ if (m_env_state == EG_ATTACK)
+ return;
+ m_env_state = EG_ATTACK;
+
+ // generally not inverted at start, except if SSG-EG is enabled and
+ // one of the inverted modes is specified; leave this alone on a
+ // restart, as it is managed by the clock_ssg_eg_state() code
+ if (RegisterType::EG_HAS_SSG && !is_restart)
+ m_ssg_inverted = m_regs.op_ssg_eg_enable(m_opoffs) & bitfield(m_regs.op_ssg_eg_mode(m_opoffs), 2);
+
+ // reset the phase when we start an attack due to a key on
+ // (but not when due to an SSG-EG restart except in certain cases
+ // managed directly by the SSG-EG code)
+ if (!is_restart)
+ m_phase = 0;
+
+ // if the attack rate >= 62 then immediately go to max attenuation
+ if (m_cache.eg_rate[EG_ATTACK] >= 62)
+ m_env_attenuation = 0;
+}
+
+
+//-------------------------------------------------
+// start_release - start the release phase;
+// called when a keyoff happens
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_operator<RegisterType>::start_release()
+{
+ // don't change anything if already in release state
+ if (m_env_state >= EG_RELEASE)
+ return;
+ m_env_state = EG_RELEASE;
+
+ // if attenuation if inverted due to SSG-EG, snap the inverted attenuation
+ // as the starting point
+ if (RegisterType::EG_HAS_SSG && m_ssg_inverted)
+ {
+ m_env_attenuation = (0x200 - m_env_attenuation) & 0x3ff;
+ m_ssg_inverted = false;
+ }
+}
+
+
+//-------------------------------------------------
+// clock_keystate - clock the keystate to match
+// the incoming keystate
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_operator<RegisterType>::clock_keystate(uint32_t keystate)
+{
+ assert(keystate == 0 || keystate == 1);
+
+ // has the key changed?
+ if ((keystate ^ m_key_state) != 0)
+ {
+ m_key_state = keystate;
+
+ // if the key has turned on, start the attack
+ if (keystate != 0)
+ {
+ // OPLL has a DP ("depress"?) state to bring the volume
+ // down before starting the attack
+ if (RegisterType::EG_HAS_DEPRESS && m_env_attenuation < 0x200)
+ m_env_state = EG_DEPRESS;
+ else
+ start_attack();
+ }
+
+ // otherwise, start the release
+ else
+ start_release();
+ }
+}
+
+
+//-------------------------------------------------
+// clock_ssg_eg_state - clock the SSG-EG state;
+// should only be called if SSG-EG is enabled
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_operator<RegisterType>::clock_ssg_eg_state()
+{
+ // work only happens once the attenuation crosses above 0x200
+ if (!bitfield(m_env_attenuation, 9))
+ return;
+
+ // 8 SSG-EG modes:
+ // 000: repeat normally
+ // 001: run once, hold low
+ // 010: repeat, alternating between inverted/non-inverted
+ // 011: run once, hold high
+ // 100: inverted repeat normally
+ // 101: inverted run once, hold low
+ // 110: inverted repeat, alternating between inverted/non-inverted
+ // 111: inverted run once, hold high
+ uint32_t mode = m_regs.op_ssg_eg_mode(m_opoffs);
+
+ // hold modes (1/3/5/7)
+ if (bitfield(mode, 0))
+ {
+ // set the inverted flag to the end state (0 for modes 1/7, 1 for modes 3/5)
+ m_ssg_inverted = bitfield(mode, 2) ^ bitfield(mode, 1);
+
+ // if holding, force the attenuation to the expected value once we're
+ // past the attack phase
+ if (m_env_state != EG_ATTACK)
+ m_env_attenuation = m_ssg_inverted ? 0x200 : 0x3ff;
+ }
+
+ // continuous modes (0/2/4/6)
+ else
+ {
+ // toggle invert in alternating mode (even in attack state)
+ m_ssg_inverted ^= bitfield(mode, 1);
+
+ // restart attack if in decay/sustain states
+ if (m_env_state == EG_DECAY || m_env_state == EG_SUSTAIN)
+ start_attack(true);
+
+ // phase is reset to 0 in modes 0/4
+ if (bitfield(mode, 1) == 0)
+ m_phase = 0;
+ }
+
+ // in all modes, once we hit release state, attenuation is forced to maximum
+ if (m_env_state == EG_RELEASE)
+ m_env_attenuation = 0x3ff;
+}
+
+
+//-------------------------------------------------
+// clock_envelope - clock the envelope state
+// according to the given count
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_operator<RegisterType>::clock_envelope(uint32_t env_counter)
+{
+ // handle attack->decay transitions
+ if (m_env_state == EG_ATTACK && m_env_attenuation == 0)
+ m_env_state = EG_DECAY;
+
+ // handle decay->sustain transitions; it is important to do this immediately
+ // after the attack->decay transition above in the event that the sustain level
+ // is set to 0 (in which case we will skip right to sustain without doing any
+ // decay); as an example where this can be heard, check the cymbals sound
+ // in channel 0 of shinobi's test mode sound #5
+ if (m_env_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain)
+ m_env_state = EG_SUSTAIN;
+
+ // fetch the appropriate 6-bit rate value from the cache
+ uint32_t rate = m_cache.eg_rate[m_env_state];
+
+ // compute the rate shift value; this is the shift needed to
+ // apply to the env_counter such that it becomes a 5.11 fixed
+ // point number
+ uint32_t rate_shift = rate >> 2;
+ env_counter <<= rate_shift;
+
+ // see if the fractional part is 0; if not, it's not time to clock
+ if (bitfield(env_counter, 0, 11) != 0)
+ return;
+
+ // determine the increment based on the non-fractional part of env_counter
+ uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3);
+ uint32_t increment = attenuation_increment(rate, relevant_bits);
+
+ // attack is the only one that increases
+ if (m_env_state == EG_ATTACK)
+ {
+ // glitch means that attack rates of 62/63 don't increment if
+ // changed after the initial key on (where they are handled
+ // specially); nukeykt confirms this happens on OPM, OPN, OPL/OPLL
+ // at least so assuming it is true for everyone
+ if (rate < 62)
+ m_env_attenuation += (~m_env_attenuation * increment) >> 4;
+ }
+
+ // all other cases are similar
+ else
+ {
+ // non-SSG-EG cases just apply the increment
+ if (!m_regs.op_ssg_eg_enable(m_opoffs))
+ m_env_attenuation += increment;
+
+ // SSG-EG only applies if less than mid-point, and then at 4x
+ else if (m_env_attenuation < 0x200)
+ m_env_attenuation += 4 * increment;
+
+ // clamp the final attenuation
+ if (m_env_attenuation >= 0x400)
+ m_env_attenuation = 0x3ff;
+
+ // transition from depress to attack
+ if (RegisterType::EG_HAS_DEPRESS && m_env_state == EG_DEPRESS && m_env_attenuation >= 0x200)
+ start_attack();
+
+ // transition from release to reverb, should switch at -18dB
+ if (RegisterType::EG_HAS_REVERB && m_env_state == EG_RELEASE && m_env_attenuation >= 0xc0)
+ m_env_state = EG_REVERB;
+ }
+}
+
+
+//-------------------------------------------------
+// clock_phase - clock the 10.10 phase value; the
+// OPN version of the logic has been verified
+// against the Nuked phase generator
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_operator<RegisterType>::clock_phase(int32_t lfo_raw_pm)
+{
+ // read from the cache, or recalculate if PM active
+ uint32_t phase_step = m_cache.phase_step;
+ if (phase_step == opdata_cache::PHASE_STEP_DYNAMIC)
+ phase_step = m_regs.compute_phase_step(m_choffs, m_opoffs, m_cache, lfo_raw_pm);
+
+ // finally apply the step to the current phase value
+ m_phase += phase_step;
+}
+
+
+//-------------------------------------------------
+// envelope_attenuation - return the effective
+// attenuation of the envelope
+//-------------------------------------------------
+
+template<class RegisterType>
+uint32_t fm_operator<RegisterType>::envelope_attenuation(uint32_t am_offset) const
+{
+ uint32_t result = m_env_attenuation >> m_cache.eg_shift;
+
+ // invert if necessary due to SSG-EG
+ if (RegisterType::EG_HAS_SSG && m_ssg_inverted)
+ result = (0x200 - result) & 0x3ff;
+
+ // add in LFO AM modulation
+ if (m_regs.op_lfo_am_enable(m_opoffs))
+ result += am_offset;
+
+ // add in total level and KSL from the cache
+ result += m_cache.total_level;
+
+ // clamp to max, apply shift, and return
+ return std::min<uint32_t>(result, 0x3ff);
+}
+
+
+
+//*********************************************************
+// FM CHANNEL
+//*********************************************************
+
+//-------------------------------------------------
+// fm_channel - constructor
+//-------------------------------------------------
+
+template<class RegisterType>
+fm_channel<RegisterType>::fm_channel(fm_engine_base<RegisterType> &owner, uint32_t choffs) :
+ m_choffs(choffs),
+ m_feedback{ 0, 0 },
+ m_feedback_in(0),
+ m_op{ nullptr, nullptr, nullptr, nullptr },
+ m_regs(owner.regs()),
+ m_owner(owner)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the channel state
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_channel<RegisterType>::reset()
+{
+ // reset our data
+ m_feedback[0] = m_feedback[1] = 0;
+ m_feedback_in = 0;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_channel<RegisterType>::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_feedback[0]);
+ state.save_restore(m_feedback[1]);
+ state.save_restore(m_feedback_in);
+}
+
+
+//-------------------------------------------------
+// keyonoff - signal key on/off to our operators
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_channel<RegisterType>::keyonoff(uint32_t states, keyon_type type, uint32_t chnum)
+{
+ for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
+ if (m_op[opnum] != nullptr)
+ m_op[opnum]->keyonoff(bitfield(states, opnum), type);
+
+ if (debug::LOG_KEYON_EVENTS && ((debug::GLOBAL_FM_CHANNEL_MASK >> chnum) & 1) != 0)
+ for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
+ if (m_op[opnum] != nullptr)
+ debug::log_keyon("%c%s\n", bitfield(states, opnum) ? '+' : '-', m_regs.log_keyon(m_choffs, m_op[opnum]->opoffs()).c_str());
+}
+
+
+//-------------------------------------------------
+// prepare - prepare for clocking
+//-------------------------------------------------
+
+template<class RegisterType>
+bool fm_channel<RegisterType>::prepare()
+{
+ uint32_t active_mask = 0;
+
+ // prepare all operators and determine if they are active
+ for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
+ if (m_op[opnum] != nullptr)
+ if (m_op[opnum]->prepare())
+ active_mask |= 1 << opnum;
+
+ return (active_mask != 0);
+}
+
+
+//-------------------------------------------------
+// clock - master clock of all operators
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_channel<RegisterType>::clock(uint32_t env_counter, int32_t lfo_raw_pm)
+{
+ // clock the feedback through
+ m_feedback[0] = m_feedback[1];
+ m_feedback[1] = m_feedback_in;
+
+ for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
+ if (m_op[opnum] != nullptr)
+ m_op[opnum]->clock(env_counter, lfo_raw_pm);
+
+/*
+useful temporary code for envelope debugging
+if (m_choffs == 0x101)
+{
+ for (uint32_t opnum = 0; opnum < m_op.size(); opnum++)
+ {
+ auto &op = *m_op[((opnum & 1) << 1) | ((opnum >> 1) & 1)];
+ printf(" %c%03X%c%c ",
+ "PADSRV"[op.debug_eg_state()],
+ op.debug_eg_attenuation(),
+ op.debug_ssg_inverted() ? '-' : '+',
+ m_regs.op_ssg_eg_enable(op.opoffs()) ? '0' + m_regs.op_ssg_eg_mode(op.opoffs()) : ' ');
+ }
+printf(" -- ");
+}
+*/
+}
+
+
+//-------------------------------------------------
+// output_2op - combine 4 operators according to
+// the specified algorithm, returning a sum
+// according to the rshift and clipmax parameters,
+// which vary between different implementations
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_channel<RegisterType>::output_2op(output_data &output, uint32_t rshift, int32_t clipmax) const
+{
+ // The first 2 operators should be populated
+ assert(m_op[0] != nullptr);
+ assert(m_op[1] != nullptr);
+
+ // AM amount is the same across all operators; compute it once
+ uint32_t am_offset = m_regs.lfo_am_offset(m_choffs);
+
+ // operator 1 has optional self-feedback
+ int32_t opmod = 0;
+ uint32_t feedback = m_regs.ch_feedback(m_choffs);
+ if (feedback != 0)
+ opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback);
+
+ // compute the 14-bit volume/value of operator 1 and update the feedback
+ int32_t op1value = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset);
+
+ // now that the feedback has been computed, skip the rest if all volumes
+ // are clear; no need to do all this work for nothing
+ if (m_regs.ch_output_any(m_choffs) == 0)
+ return;
+
+ // Algorithms for two-operator case:
+ // 0: O1 -> O2 -> out
+ // 1: (O1 + O2) -> out
+ int32_t result;
+ if (bitfield(m_regs.ch_algorithm(m_choffs), 0) == 0)
+ {
+ // some OPL chips use the previous sample for modulation instead of
+ // the current sample
+ opmod = (RegisterType::MODULATOR_DELAY ? m_feedback[1] : op1value) >> 1;
+ result = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset) >> rshift;
+ }
+ else
+ {
+ result = (RegisterType::MODULATOR_DELAY ? m_feedback[1] : op1value) >> rshift;
+ result += m_op[1]->compute_volume(m_op[1]->phase(), am_offset) >> rshift;
+ int32_t clipmin = -clipmax - 1;
+ result = clamp(result, clipmin, clipmax);
+ }
+
+ // add to the output
+ add_to_output(m_choffs, output, result);
+}
+
+
+//-------------------------------------------------
+// output_4op - combine 4 operators according to
+// the specified algorithm, returning a sum
+// according to the rshift and clipmax parameters,
+// which vary between different implementations
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_channel<RegisterType>::output_4op(output_data &output, uint32_t rshift, int32_t clipmax) const
+{
+ // all 4 operators should be populated
+ assert(m_op[0] != nullptr);
+ assert(m_op[1] != nullptr);
+ assert(m_op[2] != nullptr);
+ assert(m_op[3] != nullptr);
+
+ // AM amount is the same across all operators; compute it once
+ uint32_t am_offset = m_regs.lfo_am_offset(m_choffs);
+
+ // operator 1 has optional self-feedback
+ int32_t opmod = 0;
+ uint32_t feedback = m_regs.ch_feedback(m_choffs);
+ if (feedback != 0)
+ opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback);
+
+ // compute the 14-bit volume/value of operator 1 and update the feedback
+ int32_t op1value = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset);
+
+ // now that the feedback has been computed, skip the rest if all volumes
+ // are clear; no need to do all this work for nothing
+ if (m_regs.ch_output_any(m_choffs) == 0)
+ return;
+
+ // OPM/OPN offer 8 different connection algorithms for 4 operators,
+ // and OPL3 offers 4 more, which we designate here as 8-11.
+ //
+ // The operators are computed in order, with the inputs pulled from
+ // an array of values (opout) that is populated as we go:
+ // 0 = 0
+ // 1 = O1
+ // 2 = O2
+ // 3 = O3
+ // 4 = (O4)
+ // 5 = O1+O2
+ // 6 = O1+O3
+ // 7 = O2+O3
+ //
+ // The s_algorithm_ops table describes the inputs and outputs of each
+ // algorithm as follows:
+ //
+ // ---------x use opout[x] as operator 2 input
+ // ------xxx- use opout[x] as operator 3 input
+ // ---xxx---- use opout[x] as operator 4 input
+ // --x------- include opout[1] in final sum
+ // -x-------- include opout[2] in final sum
+ // x--------- include opout[3] in final sum
+ #define ALGORITHM(op2in, op3in, op4in, op1out, op2out, op3out) \
+ ((op2in) | ((op3in) << 1) | ((op4in) << 4) | ((op1out) << 7) | ((op2out) << 8) | ((op3out) << 9))
+ static uint16_t const s_algorithm_ops[8+4] =
+ {
+ ALGORITHM(1,2,3, 0,0,0), // 0: O1 -> O2 -> O3 -> O4 -> out (O4)
+ ALGORITHM(0,5,3, 0,0,0), // 1: (O1 + O2) -> O3 -> O4 -> out (O4)
+ ALGORITHM(0,2,6, 0,0,0), // 2: (O1 + (O2 -> O3)) -> O4 -> out (O4)
+ ALGORITHM(1,0,7, 0,0,0), // 3: ((O1 -> O2) + O3) -> O4 -> out (O4)
+ ALGORITHM(1,0,3, 0,1,0), // 4: ((O1 -> O2) + (O3 -> O4)) -> out (O2+O4)
+ ALGORITHM(1,1,1, 0,1,1), // 5: ((O1 -> O2) + (O1 -> O3) + (O1 -> O4)) -> out (O2+O3+O4)
+ ALGORITHM(1,0,0, 0,1,1), // 6: ((O1 -> O2) + O3 + O4) -> out (O2+O3+O4)
+ ALGORITHM(0,0,0, 1,1,1), // 7: (O1 + O2 + O3 + O4) -> out (O1+O2+O3+O4)
+ ALGORITHM(1,2,3, 0,0,0), // 8: O1 -> O2 -> O3 -> O4 -> out (O4) [same as 0]
+ ALGORITHM(0,2,3, 1,0,0), // 9: (O1 + (O2 -> O3 -> O4)) -> out (O1+O4) [unique]
+ ALGORITHM(1,0,3, 0,1,0), // 10: ((O1 -> O2) + (O3 -> O4)) -> out (O2+O4) [same as 4]
+ ALGORITHM(0,2,0, 1,0,1) // 11: (O1 + (O2 -> O3) + O4) -> out (O1+O3+O4) [unique]
+ };
+ uint32_t algorithm_ops = s_algorithm_ops[m_regs.ch_algorithm(m_choffs)];
+
+ // populate the opout table
+ int16_t opout[8];
+ opout[0] = 0;
+ opout[1] = op1value;
+
+ // compute the 14-bit volume/value of operator 2
+ opmod = opout[bitfield(algorithm_ops, 0, 1)] >> 1;
+ opout[2] = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset);
+ opout[5] = opout[1] + opout[2];
+
+ // compute the 14-bit volume/value of operator 3
+ opmod = opout[bitfield(algorithm_ops, 1, 3)] >> 1;
+ opout[3] = m_op[2]->compute_volume(m_op[2]->phase() + opmod, am_offset);
+ opout[6] = opout[1] + opout[3];
+ opout[7] = opout[2] + opout[3];
+
+ // compute the 14-bit volume/value of operator 4; this could be a noise
+ // value on the OPM; all algorithms consume OP4 output at a minimum
+ int32_t result;
+ if (m_regs.noise_enable() && m_choffs == 7)
+ result = m_op[3]->compute_noise_volume(am_offset);
+ else
+ {
+ opmod = opout[bitfield(algorithm_ops, 4, 3)] >> 1;
+ result = m_op[3]->compute_volume(m_op[3]->phase() + opmod, am_offset);
+ }
+ result >>= rshift;
+
+ // optionally add OP1, OP2, OP3
+ int32_t clipmin = -clipmax - 1;
+ if (bitfield(algorithm_ops, 7) != 0)
+ result = clamp(result + (opout[1] >> rshift), clipmin, clipmax);
+ if (bitfield(algorithm_ops, 8) != 0)
+ result = clamp(result + (opout[2] >> rshift), clipmin, clipmax);
+ if (bitfield(algorithm_ops, 9) != 0)
+ result = clamp(result + (opout[3] >> rshift), clipmin, clipmax);
+
+ // add to the output
+ add_to_output(m_choffs, output, result);
+}
+
+
+//-------------------------------------------------
+// output_rhythm_ch6 - special case output
+// computation for OPL channel 6 in rhythm mode,
+// which outputs a Bass Drum instrument
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_channel<RegisterType>::output_rhythm_ch6(output_data &output, uint32_t rshift, int32_t clipmax) const
+{
+ // AM amount is the same across all operators; compute it once
+ uint32_t am_offset = m_regs.lfo_am_offset(m_choffs);
+
+ // Bass Drum: this uses operators 12 and 15 (i.e., channel 6)
+ // in an almost-normal way, except that if the algorithm is 1,
+ // the first operator is ignored instead of added in
+
+ // operator 1 has optional self-feedback
+ int32_t opmod = 0;
+ uint32_t feedback = m_regs.ch_feedback(m_choffs);
+ if (feedback != 0)
+ opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback);
+
+ // compute the 14-bit volume/value of operator 1 and update the feedback
+ int32_t opout1 = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset);
+
+ // compute the 14-bit volume/value of operator 2, which is the result
+ opmod = bitfield(m_regs.ch_algorithm(m_choffs), 0) ? 0 : (opout1 >> 1);
+ int32_t result = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset) >> rshift;
+
+ // add to the output
+ add_to_output(m_choffs, output, result * 2);
+}
+
+
+//-------------------------------------------------
+// output_rhythm_ch7 - special case output
+// computation for OPL channel 7 in rhythm mode,
+// which outputs High Hat and Snare Drum
+// instruments
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_channel<RegisterType>::output_rhythm_ch7(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const
+{
+ // AM amount is the same across all operators; compute it once
+ uint32_t am_offset = m_regs.lfo_am_offset(m_choffs);
+ uint32_t noise_state = bitfield(m_regs.noise_state(), 0);
+
+ // High Hat: this uses the envelope from operator 13 (channel 7),
+ // and a combination of noise and the operator 13/17 phase select
+ // to compute the phase
+ uint32_t phase = (phase_select << 9) | (0xd0 >> (2 * (noise_state ^ phase_select)));
+ int32_t result = m_op[0]->compute_volume(phase, am_offset) >> rshift;
+
+ // Snare Drum: this uses the envelope from operator 16 (channel 7),
+ // and a combination of noise and operator 13 phase to pick a phase
+ uint32_t op13phase = m_op[0]->phase();
+ phase = (0x100 << bitfield(op13phase, 8)) ^ (noise_state << 8);
+ result += m_op[1]->compute_volume(phase, am_offset) >> rshift;
+ result = clamp(result, -clipmax - 1, clipmax);
+
+ // add to the output
+ add_to_output(m_choffs, output, result * 2);
+}
+
+
+//-------------------------------------------------
+// output_rhythm_ch8 - special case output
+// computation for OPL channel 8 in rhythm mode,
+// which outputs Tom Tom and Top Cymbal instruments
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_channel<RegisterType>::output_rhythm_ch8(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const
+{
+ // AM amount is the same across all operators; compute it once
+ uint32_t am_offset = m_regs.lfo_am_offset(m_choffs);
+
+ // Tom Tom: this is just a single operator processed normally
+ int32_t result = m_op[0]->compute_volume(m_op[0]->phase(), am_offset) >> rshift;
+
+ // Top Cymbal: this uses the envelope from operator 17 (channel 8),
+ // and the operator 13/17 phase select to compute the phase
+ uint32_t phase = 0x100 | (phase_select << 9);
+ result += m_op[1]->compute_volume(phase, am_offset) >> rshift;
+ result = clamp(result, -clipmax - 1, clipmax);
+
+ // add to the output
+ add_to_output(m_choffs, output, result * 2);
+}
+
+
+
+//*********************************************************
+// FM ENGINE BASE
+//*********************************************************
+
+//-------------------------------------------------
+// fm_engine_base - constructor
+//-------------------------------------------------
+
+template<class RegisterType>
+fm_engine_base<RegisterType>::fm_engine_base(ymfm_interface &intf) :
+ m_intf(intf),
+ m_env_counter(0),
+ m_status(0),
+ m_clock_prescale(RegisterType::DEFAULT_PRESCALE),
+ m_irq_mask(STATUS_TIMERA | STATUS_TIMERB),
+ m_irq_state(0),
+ m_timer_running{0,0},
+ m_total_clocks(0),
+ m_active_channels(ALL_CHANNELS),
+ m_modified_channels(ALL_CHANNELS),
+ m_prepare_count(0)
+{
+ // inform the interface of their engine
+ m_intf.m_engine = this;
+
+ // create the channels
+ for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
+ m_channel[chnum] = std::make_unique<fm_channel<RegisterType>>(*this, RegisterType::channel_offset(chnum));
+
+ // create the operators
+ for (uint32_t opnum = 0; opnum < OPERATORS; opnum++)
+ m_operator[opnum] = std::make_unique<fm_operator<RegisterType>>(*this, RegisterType::operator_offset(opnum));
+
+#if (YMFM_DEBUG_LOG_WAVFILES)
+ for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
+ m_wavfile[chnum].set_index(chnum);
+#endif
+
+ // do the initial operator assignment
+ assign_operators();
+}
+
+
+//-------------------------------------------------
+// reset - reset the overall state
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_engine_base<RegisterType>::reset()
+{
+ // reset all status bits
+ set_reset_status(0, 0xff);
+
+ // register type-specific initialization
+ m_regs.reset();
+
+ // explicitly write to the mode register since it has side-effects
+ // QUESTION: old cores initialize this to 0x30 -- who is right?
+ write(RegisterType::REG_MODE, 0);
+
+ // reset the channels
+ for (auto &chan : m_channel)
+ chan->reset();
+
+ // reset the operators
+ for (auto &op : m_operator)
+ op->reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_engine_base<RegisterType>::save_restore(ymfm_saved_state &state)
+{
+ // save our data
+ state.save_restore(m_env_counter);
+ state.save_restore(m_status);
+ state.save_restore(m_clock_prescale);
+ state.save_restore(m_irq_mask);
+ state.save_restore(m_irq_state);
+ state.save_restore(m_timer_running[0]);
+ state.save_restore(m_timer_running[1]);
+ state.save_restore(m_total_clocks);
+
+ // save the register/family data
+ m_regs.save_restore(state);
+
+ // save channel data
+ for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
+ m_channel[chnum]->save_restore(state);
+
+ // save operator data
+ for (uint32_t opnum = 0; opnum < OPERATORS; opnum++)
+ m_operator[opnum]->save_restore(state);
+
+ // invalidate any caches
+ invalidate_caches();
+}
+
+
+//-------------------------------------------------
+// clock - iterate over all channels, clocking
+// them forward one step
+//-------------------------------------------------
+
+template<class RegisterType>
+uint32_t fm_engine_base<RegisterType>::clock(uint32_t chanmask)
+{
+ // update the clock counter
+ m_total_clocks++;
+
+ // if something was modified, prepare
+ // also prepare every 4k samples to catch ending notes
+ if (m_modified_channels != 0 || m_prepare_count++ >= 4096)
+ {
+ // reassign operators to channels if dynamic
+ if (RegisterType::DYNAMIC_OPS)
+ assign_operators();
+
+ // call each channel to prepare
+ m_active_channels = 0;
+ for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ if (m_channel[chnum]->prepare())
+ m_active_channels |= 1 << chnum;
+
+ // reset the modified channels and prepare count
+ m_modified_channels = m_prepare_count = 0;
+ }
+
+ // if the envelope clock divider is 1, just increment by 4;
+ // otherwise, increment by 1 and manually wrap when we reach the divide count
+ if (RegisterType::EG_CLOCK_DIVIDER == 1)
+ m_env_counter += 4;
+ else if (bitfield(++m_env_counter, 0, 2) == RegisterType::EG_CLOCK_DIVIDER)
+ m_env_counter += 4 - RegisterType::EG_CLOCK_DIVIDER;
+
+ // clock the noise generator
+ int32_t lfo_raw_pm = m_regs.clock_noise_and_lfo();
+
+ // now update the state of all the channels and operators
+ for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ m_channel[chnum]->clock(m_env_counter, lfo_raw_pm);
+
+ // return the envelope counter as it is used to clock ADPCM-A
+ return m_env_counter;
+}
+
+
+//-------------------------------------------------
+// output - compute a sum over the relevant
+// channels
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_engine_base<RegisterType>::output(output_data &output, uint32_t rshift, int32_t clipmax, uint32_t chanmask) const
+{
+ // mask out some channels for debug purposes
+ chanmask &= debug::GLOBAL_FM_CHANNEL_MASK;
+
+ // mask out inactive channels
+ if (!YMFM_DEBUG_LOG_WAVFILES)
+ chanmask &= m_active_channels;
+
+ // handle the rhythm case, where some of the operators are dedicated
+ // to percussion (this is an OPL-specific feature)
+ if (m_regs.rhythm_enable())
+ {
+ // we don't support the OPM noise channel here; ensure it is off
+ assert(m_regs.noise_enable() == 0);
+
+ // precompute the operator 13+17 phase selection value
+ uint32_t op13phase = m_operator[13]->phase();
+ uint32_t op17phase = m_operator[17]->phase();
+ uint32_t phase_select = (bitfield(op13phase, 2) ^ bitfield(op13phase, 7)) | bitfield(op13phase, 3) | (bitfield(op17phase, 5) ^ bitfield(op17phase, 3));
+
+ // sum over all the desired channels
+ for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ {
+#if (YMFM_DEBUG_LOG_WAVFILES)
+ auto reference = output;
+#endif
+ if (chnum == 6)
+ m_channel[chnum]->output_rhythm_ch6(output, rshift, clipmax);
+ else if (chnum == 7)
+ m_channel[chnum]->output_rhythm_ch7(phase_select, output, rshift, clipmax);
+ else if (chnum == 8)
+ m_channel[chnum]->output_rhythm_ch8(phase_select, output, rshift, clipmax);
+ else if (m_channel[chnum]->is4op())
+ m_channel[chnum]->output_4op(output, rshift, clipmax);
+ else
+ m_channel[chnum]->output_2op(output, rshift, clipmax);
+#if (YMFM_DEBUG_LOG_WAVFILES)
+ m_wavfile[chnum].add(output, reference);
+#endif
+ }
+ }
+ else
+ {
+ // sum over all the desired channels
+ for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ {
+#if (YMFM_DEBUG_LOG_WAVFILES)
+ auto reference = output;
+#endif
+ if (m_channel[chnum]->is4op())
+ m_channel[chnum]->output_4op(output, rshift, clipmax);
+ else
+ m_channel[chnum]->output_2op(output, rshift, clipmax);
+#if (YMFM_DEBUG_LOG_WAVFILES)
+ m_wavfile[chnum].add(output, reference);
+#endif
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// write - handle writes to the OPN registers
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_engine_base<RegisterType>::write(uint16_t regnum, uint8_t data)
+{
+ debug::log_fm_write("%03X = %02X\n", regnum, data);
+
+ // special case: writes to the mode register can impact IRQs;
+ // schedule these writes to ensure ordering with timers
+ if (regnum == RegisterType::REG_MODE)
+ {
+ m_intf.ymfm_sync_mode_write(data);
+ return;
+ }
+
+ // for now just mark all channels as modified
+ m_modified_channels = ALL_CHANNELS;
+
+ // most writes are passive, consumed only when needed
+ uint32_t keyon_channel;
+ uint32_t keyon_opmask;
+ if (m_regs.write(regnum, data, keyon_channel, keyon_opmask))
+ {
+ // handle writes to the keyon register(s)
+ if (keyon_channel < CHANNELS)
+ {
+ // normal channel on/off
+ m_channel[keyon_channel]->keyonoff(keyon_opmask, KEYON_NORMAL, keyon_channel);
+ }
+ else if (CHANNELS >= 9 && keyon_channel == RegisterType::RHYTHM_CHANNEL)
+ {
+ // special case for the OPL rhythm channels
+ m_channel[6]->keyonoff(bitfield(keyon_opmask, 4) ? 3 : 0, KEYON_RHYTHM, 6);
+ m_channel[7]->keyonoff(bitfield(keyon_opmask, 0) | (bitfield(keyon_opmask, 3) << 1), KEYON_RHYTHM, 7);
+ m_channel[8]->keyonoff(bitfield(keyon_opmask, 2) | (bitfield(keyon_opmask, 1) << 1), KEYON_RHYTHM, 8);
+ }
+ }
+}
+
+
+//-------------------------------------------------
+// status - return the current state of the
+// status flags
+//-------------------------------------------------
+
+template<class RegisterType>
+uint8_t fm_engine_base<RegisterType>::status() const
+{
+ return m_status & ~STATUS_BUSY & ~m_regs.status_mask();
+}
+
+
+//-------------------------------------------------
+// assign_operators - get the current mapping of
+// operators to channels and assign them all
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_engine_base<RegisterType>::assign_operators()
+{
+ typename RegisterType::operator_mapping map;
+ m_regs.operator_map(map);
+
+ for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
+ for (uint32_t index = 0; index < 4; index++)
+ {
+ uint32_t opnum = bitfield(map.chan[chnum], 8 * index, 8);
+ m_channel[chnum]->assign(index, (opnum == 0xff) ? nullptr : m_operator[opnum].get());
+ }
+}
+
+
+//-------------------------------------------------
+// update_timer - update the state of the given
+// timer
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_engine_base<RegisterType>::update_timer(uint32_t tnum, uint32_t enable, int32_t delta_clocks)
+{
+ // if the timer is live, but not currently enabled, set the timer
+ if (enable && !m_timer_running[tnum])
+ {
+ // period comes from the registers, and is different for each
+ uint32_t period = (tnum == 0) ? (1024 - m_regs.timer_a_value()) : 16 * (256 - m_regs.timer_b_value());
+
+ // caller can also specify a delta to account for other effects
+ period += delta_clocks;
+
+ // reset it
+ m_intf.ymfm_set_timer(tnum, period * OPERATORS * m_clock_prescale);
+ m_timer_running[tnum] = 1;
+ }
+
+ // if the timer is not live, ensure it is not enabled
+ else if (!enable)
+ {
+ m_intf.ymfm_set_timer(tnum, -1);
+ m_timer_running[tnum] = 0;
+ }
+}
+
+
+//-------------------------------------------------
+// engine_timer_expired - timer has expired - signal
+// status and possibly IRQs
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_engine_base<RegisterType>::engine_timer_expired(uint32_t tnum)
+{
+ assert(tnum == 0 || tnum == 1);
+
+ // update status
+ if (tnum == 0 && m_regs.enable_timer_a())
+ set_reset_status(STATUS_TIMERA, 0);
+ else if (tnum == 1 && m_regs.enable_timer_b())
+ set_reset_status(STATUS_TIMERB, 0);
+
+ // if timer A fired in CSM mode, trigger CSM on all relevant channels
+ if (tnum == 0 && m_regs.csm())
+ for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(RegisterType::CSM_TRIGGER_MASK, chnum))
+ {
+ m_channel[chnum]->keyonoff(0xf, KEYON_CSM, chnum);
+ m_modified_channels |= 1 << chnum;
+ }
+
+ // reset
+ m_timer_running[tnum] = false;
+ update_timer(tnum, 1, 0);
+}
+
+
+//-------------------------------------------------
+// check_interrupts - check the interrupt sources
+// for interrupts
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_engine_base<RegisterType>::engine_check_interrupts()
+{
+ // update the state
+ uint8_t old_state = m_irq_state;
+ m_irq_state = ((m_status & m_irq_mask & ~m_regs.status_mask()) != 0);
+
+ // set the IRQ status bit
+ if (m_irq_state)
+ m_status |= STATUS_IRQ;
+ else
+ m_status &= ~STATUS_IRQ;
+
+ // if changed, signal the new state
+ if (old_state != m_irq_state)
+ m_intf.ymfm_update_irq(m_irq_state ? true : false);
+}
+
+
+//-------------------------------------------------
+// engine_mode_write - handle a mode register write
+// via timer callback
+//-------------------------------------------------
+
+template<class RegisterType>
+void fm_engine_base<RegisterType>::engine_mode_write(uint8_t data)
+{
+ // mark all channels as modified
+ m_modified_channels = ALL_CHANNELS;
+
+ // actually write the mode register now
+ uint32_t dummy1, dummy2;
+ m_regs.write(RegisterType::REG_MODE, data, dummy1, dummy2);
+
+ // reset IRQ status -- when written, all other bits are ignored
+ // QUESTION: should this maybe just reset the IRQ bit and not all the bits?
+ // That is, check_interrupts would only set, this would only clear?
+ if (m_regs.irq_reset())
+ set_reset_status(0, 0x78);
+ else
+ {
+ // reset timer status
+ uint8_t reset_mask = 0;
+ if (m_regs.reset_timer_b())
+ reset_mask |= RegisterType::STATUS_TIMERB;
+ if (m_regs.reset_timer_a())
+ reset_mask |= RegisterType::STATUS_TIMERA;
+ set_reset_status(0, reset_mask);
+
+ // load timers; note that timer B gets a small negative adjustment because
+ // the *16 multiplier is free-running, so the first tick of the clock
+ // is a bit shorter
+ update_timer(1, m_regs.load_timer_b(), -(m_total_clocks & 15));
+ update_timer(0, m_regs.load_timer_a(), 0);
+ }
+}
+
+}
diff --git a/3rdparty/ymfm/src/ymfm_misc.cpp b/3rdparty/ymfm/src/ymfm_misc.cpp
new file mode 100644
index 00000000000..fd0575f55fb
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_misc.cpp
@@ -0,0 +1,175 @@
+// 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.
+
+#include "ymfm_misc.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// YM2149
+//*********************************************************
+
+//-------------------------------------------------
+// ym2149 - constructor
+//-------------------------------------------------
+
+ym2149::ym2149(ymfm_interface &intf) :
+ m_address(0),
+ m_ssg(intf)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ym2149::reset()
+{
+ // reset the engines
+ m_ssg.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ym2149::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ m_ssg.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_data - read the data register
+//-------------------------------------------------
+
+uint8_t ym2149::read_data()
+{
+ return m_ssg.read(m_address & 0x0f);
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ym2149::read(uint32_t offset)
+{
+ uint8_t result = 0xff;
+ switch (offset & 3) // BC2,BC1
+ {
+ case 0: // inactive
+ break;
+
+ case 1: // address
+ break;
+
+ case 2: // inactive
+ break;
+
+ case 3: // read
+ result = read_data();
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ym2149::write_address(uint8_t data)
+{
+ // just set the address
+ m_address = data;
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2149::write_data(uint8_t data)
+{
+ m_ssg.write(m_address & 0x0f, data);
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2149::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 3) // BC2,BC1
+ {
+ case 0: // address
+ write_address(data);
+ break;
+
+ case 1: // inactive
+ break;
+
+ case 2: // write
+ write_data(data);
+ break;
+
+ case 3: // address
+ write_address(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate samples of SSG sound
+//-------------------------------------------------
+
+void ym2149::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the SSG
+ m_ssg.clock();
+
+ // YM2149 keeps the three SSG outputs independent
+ m_ssg.output(*output);
+ }
+}
+
+}
diff --git a/3rdparty/ymfm/src/ymfm_misc.h b/3rdparty/ymfm/src/ymfm_misc.h
new file mode 100644
index 00000000000..628d128f699
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_misc.h
@@ -0,0 +1,93 @@
+// 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_MISC_H
+#define YMFM_MISC_H
+
+#pragma once
+
+#include "ymfm.h"
+#include "ymfm_adpcm.h"
+#include "ymfm_ssg.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// SSG IMPLEMENTATION CLASSES
+//*********************************************************
+
+// ======================> ym2149
+
+// ym2149 is just an SSG with no FM part, but we expose FM-like parts so that it
+// integrates smoothly with everything else; they just don't do anything
+class ym2149
+{
+public:
+ static constexpr uint32_t OUTPUTS = ssg_engine::OUTPUTS;
+ static constexpr uint32_t SSG_OUTPUTS = ssg_engine::OUTPUTS;
+ using output_data = ymfm_output<OUTPUTS>;
+
+ // constructor
+ ym2149(ymfm_interface &intf);
+
+ // configuration
+ void ssg_override(ssg_override &intf) { m_ssg.override(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 input_clock / ssg_engine::CLOCK_DIVIDER / 8; }
+
+ // read access
+ 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 state
+ uint8_t m_address; // address register
+ ssg_engine m_ssg; // SSG engine
+};
+
+}
+
+#endif // YMFM_MISC_H
diff --git a/3rdparty/ymfm/src/ymfm_opl.cpp b/3rdparty/ymfm/src/ymfm_opl.cpp
new file mode 100644
index 00000000000..8e8025fd9cc
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_opl.cpp
@@ -0,0 +1,2220 @@
+// 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.
+
+#include "ymfm_opl.h"
+#include "ymfm_fm.ipp"
+
+namespace ymfm
+{
+
+//-------------------------------------------------
+// opl_key_scale_atten - converts an
+// OPL concatenated block (3 bits) and fnum
+// (10 bits) into an attenuation offset; values
+// here are for 6dB/octave, in 0.75dB units
+// (matching total level LSB)
+//-------------------------------------------------
+
+inline uint32_t opl_key_scale_atten(uint32_t block, uint32_t fnum_4msb)
+{
+ // this table uses the top 4 bits of FNUM and are the maximal values
+ // (for when block == 7). Values for other blocks can be computed by
+ // subtracting 8 for each block below 7.
+ static uint8_t const fnum_to_atten[16] = { 0,24,32,37,40,43,45,47,48,50,51,52,53,54,55,56 };
+ int32_t result = fnum_to_atten[fnum_4msb] - 8 * (block ^ 7);
+ return std::max<int32_t>(0, result);
+}
+
+
+//*********************************************************
+// OPL REGISTERS
+//*********************************************************
+
+//-------------------------------------------------
+// opl_registers_base - constructor
+//-------------------------------------------------
+
+template<int Revision>
+opl_registers_base<Revision>::opl_registers_base() :
+ m_lfo_am_counter(0),
+ m_lfo_pm_counter(0),
+ m_noise_lfsr(1),
+ m_lfo_am(0)
+{
+ // create these pointers to appease overzealous compilers checking array
+ // bounds in unreachable code (looking at you, clang)
+ uint16_t *wf0 = &m_waveform[0][0];
+ uint16_t *wf1 = &m_waveform[1 % WAVEFORMS][0];
+ uint16_t *wf2 = &m_waveform[2 % WAVEFORMS][0];
+ uint16_t *wf3 = &m_waveform[3 % WAVEFORMS][0];
+ uint16_t *wf4 = &m_waveform[4 % WAVEFORMS][0];
+ uint16_t *wf5 = &m_waveform[5 % WAVEFORMS][0];
+ uint16_t *wf6 = &m_waveform[6 % WAVEFORMS][0];
+ uint16_t *wf7 = &m_waveform[7 % WAVEFORMS][0];
+
+ // create the waveforms
+ for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
+ wf0[index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
+
+ if (WAVEFORMS >= 4)
+ {
+ uint16_t zeroval = wf0[0];
+ for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
+ {
+ wf1[index] = bitfield(index, 9) ? zeroval : wf0[index];
+ wf2[index] = wf0[index] & 0x7fff;
+ wf3[index] = bitfield(index, 8) ? zeroval : (wf0[index] & 0x7fff);
+ if (WAVEFORMS >= 8)
+ {
+ wf4[index] = bitfield(index, 9) ? zeroval : wf0[index * 2];
+ wf5[index] = bitfield(index, 9) ? zeroval : wf0[(index * 2) & 0x1ff];
+ wf6[index] = bitfield(index, 9) << 15;
+ wf7[index] = (bitfield(index, 9) ? (index ^ 0x13ff) : index) << 3;
+ }
+ }
+ }
+
+ // OPL3/OPL4 have dynamic operators, so initialize the fourop_enable value here
+ // since operator_map() is called right away, prior to reset()
+ if (Revision > 2)
+ m_regdata[0x104 % REGISTERS] = 0;
+}
+
+
+//-------------------------------------------------
+// reset - reset to initial state
+//-------------------------------------------------
+
+template<int Revision>
+void opl_registers_base<Revision>::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+template<int Revision>
+void opl_registers_base<Revision>::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_lfo_am_counter);
+ state.save_restore(m_lfo_pm_counter);
+ state.save_restore(m_lfo_am);
+ state.save_restore(m_noise_lfsr);
+ state.save_restore(m_regdata);
+}
+
+
+//-------------------------------------------------
+// operator_map - return an array of operator
+// indices for each channel; for OPL this is fixed
+//-------------------------------------------------
+
+template<int Revision>
+void opl_registers_base<Revision>::operator_map(operator_mapping &dest) const
+{
+ if (Revision <= 2)
+ {
+ // OPL/OPL2 has a fixed map, all 2 operators
+ static const operator_mapping s_fixed_map =
+ { {
+ operator_list( 0, 3 ), // Channel 0 operators
+ operator_list( 1, 4 ), // Channel 1 operators
+ operator_list( 2, 5 ), // Channel 2 operators
+ operator_list( 6, 9 ), // Channel 3 operators
+ operator_list( 7, 10 ), // Channel 4 operators
+ operator_list( 8, 11 ), // Channel 5 operators
+ operator_list( 12, 15 ), // Channel 6 operators
+ operator_list( 13, 16 ), // Channel 7 operators
+ operator_list( 14, 17 ), // Channel 8 operators
+ } };
+ dest = s_fixed_map;
+ }
+ else
+ {
+ // OPL3/OPL4 can be configured for 2 or 4 operators
+ uint32_t fourop = fourop_enable();
+
+ dest.chan[ 0] = bitfield(fourop, 0) ? operator_list( 0, 3, 6, 9 ) : operator_list( 0, 3 );
+ dest.chan[ 1] = bitfield(fourop, 1) ? operator_list( 1, 4, 7, 10 ) : operator_list( 1, 4 );
+ dest.chan[ 2] = bitfield(fourop, 2) ? operator_list( 2, 5, 8, 11 ) : operator_list( 2, 5 );
+ dest.chan[ 3] = bitfield(fourop, 0) ? operator_list() : operator_list( 6, 9 );
+ dest.chan[ 4] = bitfield(fourop, 1) ? operator_list() : operator_list( 7, 10 );
+ dest.chan[ 5] = bitfield(fourop, 2) ? operator_list() : operator_list( 8, 11 );
+ dest.chan[ 6] = operator_list( 12, 15 );
+ dest.chan[ 7] = operator_list( 13, 16 );
+ dest.chan[ 8] = operator_list( 14, 17 );
+
+ dest.chan[ 9] = bitfield(fourop, 3) ? operator_list( 18, 21, 24, 27 ) : operator_list( 18, 21 );
+ dest.chan[10] = bitfield(fourop, 4) ? operator_list( 19, 22, 25, 28 ) : operator_list( 19, 22 );
+ dest.chan[11] = bitfield(fourop, 5) ? operator_list( 20, 23, 26, 29 ) : operator_list( 20, 23 );
+ dest.chan[12] = bitfield(fourop, 3) ? operator_list() : operator_list( 24, 27 );
+ dest.chan[13] = bitfield(fourop, 4) ? operator_list() : operator_list( 25, 28 );
+ dest.chan[14] = bitfield(fourop, 5) ? operator_list() : operator_list( 26, 29 );
+ dest.chan[15] = operator_list( 30, 33 );
+ dest.chan[16] = operator_list( 31, 34 );
+ dest.chan[17] = operator_list( 32, 35 );
+ }
+}
+
+
+//-------------------------------------------------
+// write - handle writes to the register array
+//-------------------------------------------------
+
+template<int Revision>
+bool opl_registers_base<Revision>::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
+{
+ assert(index < REGISTERS);
+
+ // writes to the mode register with high bit set ignore the low bits
+ if (index == REG_MODE && bitfield(data, 7) != 0)
+ m_regdata[index] |= 0x80;
+ else
+ m_regdata[index] = data;
+
+ // handle writes to the rhythm keyons
+ if (index == 0xbd)
+ {
+ channel = RHYTHM_CHANNEL;
+ opmask = bitfield(data, 5) ? bitfield(data, 0, 5) : 0;
+ return true;
+ }
+
+ // handle writes to the channel keyons
+ if ((index & 0xf0) == 0xb0)
+ {
+ channel = index & 0x0f;
+ if (channel < 9)
+ {
+ if (IsOpl3Plus)
+ channel += 9 * bitfield(index, 8);
+ opmask = bitfield(data, 5) ? 15 : 0;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+//-------------------------------------------------
+// clock_noise_and_lfo - clock the noise and LFO,
+// handling clock division, depth, and waveform
+// computations
+//-------------------------------------------------
+
+static int32_t opl_clock_noise_and_lfo(uint32_t &noise_lfsr, uint16_t &lfo_am_counter, uint16_t &lfo_pm_counter, uint8_t &lfo_am, uint32_t am_depth, uint32_t pm_depth)
+{
+ // OPL has a 23-bit noise generator for the rhythm section, running at
+ // a constant rate, used only for percussion input
+ noise_lfsr <<= 1;
+ noise_lfsr |= bitfield(noise_lfsr, 23) ^ bitfield(noise_lfsr, 9) ^ bitfield(noise_lfsr, 8) ^ bitfield(noise_lfsr, 1);
+
+ // OPL has two fixed-frequency LFOs, one for AM, one for PM
+
+ // the AM LFO has 210*64 steps; at a nominal 50kHz output,
+ // this equates to a period of 50000/(210*64) = 3.72Hz
+ uint32_t am_counter = lfo_am_counter++;
+ if (am_counter >= 210*64 - 1)
+ lfo_am_counter = 0;
+
+ // low 8 bits are fractional; depth 0 is divided by 2, while depth 1 is times 2
+ int shift = 9 - 2 * am_depth;
+
+ // AM value is the upper bits of the value, inverted across the midpoint
+ // to produce a triangle
+ lfo_am = ((am_counter < 105*64) ? am_counter : (210*64+63 - am_counter)) >> shift;
+
+ // the PM LFO has 8192 steps, or a nominal period of 6.1Hz
+ uint32_t pm_counter = lfo_pm_counter++;
+
+ // PM LFO is broken into 8 chunks, each lasting 1024 steps; the PM value
+ // depends on the upper bits of FNUM, so this value is a fraction and
+ // sign to apply to that value, as a 1.3 value
+ static int8_t const pm_scale[8] = { 8, 4, 0, -4, -8, -4, 0, 4 };
+ return pm_scale[bitfield(pm_counter, 10, 3)] >> (pm_depth ^ 1);
+}
+
+template<int Revision>
+int32_t opl_registers_base<Revision>::clock_noise_and_lfo()
+{
+ return opl_clock_noise_and_lfo(m_noise_lfsr, m_lfo_am_counter, m_lfo_pm_counter, m_lfo_am, lfo_am_depth(), lfo_pm_depth());
+}
+
+
+//-------------------------------------------------
+// cache_operator_data - fill the operator cache
+// with prefetched data; note that this code is
+// also used by ymopna_registers, so it must
+// handle upper channels cleanly
+//-------------------------------------------------
+
+template<int Revision>
+void opl_registers_base<Revision>::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
+{
+ // set up the easy stuff
+ cache.waveform = &m_waveform[op_waveform(opoffs) % WAVEFORMS][0];
+
+ // get frequency from the channel
+ uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
+
+ // compute the keycode: block_freq is:
+ //
+ // 111 |
+ // 21098|76543210
+ // BBBFF|FFFFFFFF
+ // ^^^??
+ //
+ // the 4-bit keycode uses the top 3 bits plus one of the next two bits
+ uint32_t keycode = bitfield(block_freq, 10, 3) << 1;
+
+ // lowest bit is determined by note_select(); note that it is
+ // actually reversed from what the manual says, however
+ keycode |= bitfield(block_freq, 9 - note_select(), 1);
+
+ // no detune adjustment on OPL
+ cache.detune = 0;
+
+ // multiple value, as an x.1 value (0 means 0.5)
+ // replace the low bit with a table lookup to give 0,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15
+ uint32_t multiple = op_multiple(opoffs);
+ cache.multiple = ((multiple & 0xe) | bitfield(0xc2aa, multiple)) * 2;
+ if (cache.multiple == 0)
+ cache.multiple = 1;
+
+ // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on block_freq, detune,
+ // and multiple, so compute it after we've done those
+ if (op_lfo_pm_enable(opoffs) == 0)
+ cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
+ else
+ cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
+
+ // total level, scaled by 8
+ cache.total_level = op_total_level(opoffs) << 3;
+
+ // pre-add key scale level
+ uint32_t ksl = op_ksl(opoffs);
+ if (ksl != 0)
+ cache.total_level += opl_key_scale_atten(bitfield(block_freq, 10, 3), bitfield(block_freq, 6, 4)) << ksl;
+
+ // 4-bit sustain level, but 15 means 31 so effectively 5 bits
+ cache.eg_sustain = op_sustain_level(opoffs);
+ cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
+ cache.eg_sustain <<= 5;
+
+ // determine KSR adjustment for enevlope rates
+ uint32_t ksrval = keycode >> (2 * (op_ksr(opoffs) ^ 1));
+ cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 4, ksrval);
+ cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 4, ksrval);
+ cache.eg_rate[EG_SUSTAIN] = op_eg_sustain(opoffs) ? 0 : effective_rate(op_release_rate(opoffs) * 4, ksrval);
+ cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4, ksrval);
+ cache.eg_rate[EG_DEPRESS] = 0x3f;
+}
+
+
+//-------------------------------------------------
+// compute_phase_step - compute the phase step
+//-------------------------------------------------
+
+static uint32_t opl_compute_phase_step(uint32_t block_freq, uint32_t multiple, int32_t lfo_raw_pm)
+{
+ // OPL phase calculation has no detuning, but uses FNUMs like
+ // the OPN version, and computes PM a bit differently
+
+ // extract frequency number as a 12-bit fraction
+ uint32_t fnum = bitfield(block_freq, 0, 10) << 2;
+
+ // apply the phase adjustment based on the upper 3 bits
+ // of FNUM and the PM depth parameters
+ fnum += (lfo_raw_pm * bitfield(block_freq, 7, 3)) >> 1;
+
+ // keep fnum to 12 bits
+ fnum &= 0xfff;
+
+ // apply block shift to compute phase step
+ uint32_t block = bitfield(block_freq, 10, 3);
+ uint32_t phase_step = (fnum << block) >> 2;
+
+ // apply frequency multiplier (which is cached as an x.1 value)
+ return (phase_step * multiple) >> 1;
+}
+
+template<int Revision>
+uint32_t opl_registers_base<Revision>::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
+{
+ return opl_compute_phase_step(cache.block_freq, cache.multiple, op_lfo_pm_enable(opoffs) ? lfo_raw_pm : 0);
+}
+
+
+//-------------------------------------------------
+// log_keyon - log a key-on event
+//-------------------------------------------------
+
+template<int Revision>
+std::string opl_registers_base<Revision>::log_keyon(uint32_t choffs, uint32_t opoffs)
+{
+ uint32_t chnum = (choffs & 15) + 9 * bitfield(choffs, 8);
+ uint32_t opnum = (opoffs & 31) - 2 * ((opoffs & 31) / 8) + 18 * bitfield(opoffs, 8);
+
+ char buffer[256];
+ int end = 0;
+
+ end += snprintf(&buffer[end], sizeof(buffer) - end, "%2u.%02u freq=%04X fb=%u alg=%X mul=%X tl=%02X ksr=%u ns=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u",
+ chnum, opnum,
+ ch_block_freq(choffs),
+ ch_feedback(choffs),
+ ch_algorithm(choffs),
+ op_multiple(opoffs),
+ op_total_level(opoffs),
+ op_ksr(opoffs),
+ note_select(),
+ op_ksl(opoffs),
+ op_attack_rate(opoffs),
+ op_decay_rate(opoffs),
+ op_release_rate(opoffs),
+ op_sustain_level(opoffs),
+ op_eg_sustain(opoffs));
+
+ if (OUTPUTS > 1)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " out=%c%c%c%c",
+ ch_output_0(choffs) ? 'L' : '-',
+ ch_output_1(choffs) ? 'R' : '-',
+ ch_output_2(choffs) ? '0' : '-',
+ ch_output_3(choffs) ? '1' : '-');
+ if (op_lfo_am_enable(opoffs) != 0)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u", lfo_am_depth());
+ if (op_lfo_pm_enable(opoffs) != 0)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u", lfo_pm_depth());
+ if (waveform_enable() && op_waveform(opoffs) != 0)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=%u", op_waveform(opoffs));
+ if (is_rhythm(choffs))
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " rhy=1");
+ if (DYNAMIC_OPS)
+ {
+ operator_mapping map;
+ operator_map(map);
+ if (bitfield(map.chan[chnum], 16, 8) != 0xff)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " 4op");
+ }
+
+ return buffer;
+}
+
+
+//*********************************************************
+// OPLL SPECIFICS
+//*********************************************************
+
+//-------------------------------------------------
+// opll_registers - constructor
+//-------------------------------------------------
+
+opll_registers::opll_registers() :
+ m_lfo_am_counter(0),
+ m_lfo_pm_counter(0),
+ m_noise_lfsr(1),
+ m_lfo_am(0)
+{
+ // create the waveforms
+ for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
+ m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
+
+ uint16_t zeroval = m_waveform[0][0];
+ for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
+ m_waveform[1][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index];
+
+ // initialize the instruments to something sane
+ for (uint32_t choffs = 0; choffs < CHANNELS; choffs++)
+ m_chinst[choffs] = &m_regdata[0];
+ for (uint32_t opoffs = 0; opoffs < OPERATORS; opoffs++)
+ m_opinst[opoffs] = &m_regdata[bitfield(opoffs, 0)];
+}
+
+
+//-------------------------------------------------
+// reset - reset to initial state
+//-------------------------------------------------
+
+void opll_registers::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void opll_registers::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_lfo_am_counter);
+ state.save_restore(m_lfo_pm_counter);
+ state.save_restore(m_lfo_am);
+ state.save_restore(m_noise_lfsr);
+ state.save_restore(m_regdata);
+}
+
+
+//-------------------------------------------------
+// operator_map - return an array of operator
+// indices for each channel; for OPLL this is fixed
+//-------------------------------------------------
+
+void opll_registers::operator_map(operator_mapping &dest) const
+{
+ static const operator_mapping s_fixed_map =
+ { {
+ operator_list( 0, 1 ), // Channel 0 operators
+ operator_list( 2, 3 ), // Channel 1 operators
+ operator_list( 4, 5 ), // Channel 2 operators
+ operator_list( 6, 7 ), // Channel 3 operators
+ operator_list( 8, 9 ), // Channel 4 operators
+ operator_list( 10, 11 ), // Channel 5 operators
+ operator_list( 12, 13 ), // Channel 6 operators
+ operator_list( 14, 15 ), // Channel 7 operators
+ operator_list( 16, 17 ), // Channel 8 operators
+ } };
+ dest = s_fixed_map;
+}
+
+
+//-------------------------------------------------
+// write - handle writes to the register array;
+// note that this code is also used by
+// ymopl3_registers, so it must handle upper
+// channels cleanly
+//-------------------------------------------------
+
+bool opll_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
+{
+ // unclear the address is masked down to 6 bits or if writes above
+ // the register top are ignored; assuming the latter for now
+ if (index >= REGISTERS)
+ return false;
+
+ // write the new data
+ m_regdata[index] = data;
+
+ // handle writes to the rhythm keyons
+ if (index == 0x0e)
+ {
+ channel = RHYTHM_CHANNEL;
+ opmask = bitfield(data, 5) ? bitfield(data, 0, 5) : 0;
+ return true;
+ }
+
+ // handle writes to the channel keyons
+ if ((index & 0xf0) == 0x20)
+ {
+ channel = index & 0x0f;
+ if (channel < CHANNELS)
+ {
+ opmask = bitfield(data, 4) ? 3 : 0;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+//-------------------------------------------------
+// clock_noise_and_lfo - clock the noise and LFO,
+// handling clock division, depth, and waveform
+// computations
+//-------------------------------------------------
+
+int32_t opll_registers::clock_noise_and_lfo()
+{
+ // implementation is the same as OPL with fixed depths
+ return opl_clock_noise_and_lfo(m_noise_lfsr, m_lfo_am_counter, m_lfo_pm_counter, m_lfo_am, 1, 1);
+}
+
+
+//-------------------------------------------------
+// cache_operator_data - fill the operator cache
+// with prefetched data; note that this code is
+// also used by ymopna_registers, so it must
+// handle upper channels cleanly
+//-------------------------------------------------
+
+void opll_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
+{
+ // first set up the instrument data
+ uint32_t instrument = ch_instrument(choffs);
+ if (rhythm_enable() && choffs >= 6)
+ m_chinst[choffs] = &m_instdata[8 * (15 + (choffs - 6))];
+ else
+ m_chinst[choffs] = (instrument == 0) ? &m_regdata[0] : &m_instdata[8 * (instrument - 1)];
+ m_opinst[opoffs] = m_chinst[choffs] + bitfield(opoffs, 0);
+
+ // set up the easy stuff
+ cache.waveform = &m_waveform[op_waveform(opoffs) % WAVEFORMS][0];
+
+ // get frequency from the channel
+ uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
+
+ // compute the keycode: block_freq is:
+ //
+ // 11 |
+ // 1098|76543210
+ // BBBF|FFFFFFFF
+ // ^^^^
+ //
+ // the 4-bit keycode uses the top 4 bits
+ uint32_t keycode = bitfield(block_freq, 8, 4);
+
+ // no detune adjustment on OPLL
+ cache.detune = 0;
+
+ // multiple value, as an x.1 value (0 means 0.5)
+ // replace the low bit with a table lookup to give 0,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15
+ uint32_t multiple = op_multiple(opoffs);
+ cache.multiple = ((multiple & 0xe) | bitfield(0xc2aa, multiple)) * 2;
+ if (cache.multiple == 0)
+ cache.multiple = 1;
+
+ // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
+ // block_freq, detune, and multiple, so compute it after we've done those
+ if (op_lfo_pm_enable(opoffs) == 0)
+ cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
+ else
+ cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
+
+ // total level, scaled by 8; for non-rhythm operator 0, this is the total
+ // level from the instrument data; for other operators it is 4*volume
+ if (bitfield(opoffs, 0) == 1 || (rhythm_enable() && choffs >= 7))
+ cache.total_level = op_volume(opoffs) * 4;
+ else
+ cache.total_level = ch_total_level(choffs);
+ cache.total_level <<= 3;
+
+ // pre-add key scale level
+ uint32_t ksl = op_ksl(opoffs);
+ if (ksl != 0)
+ cache.total_level += opl_key_scale_atten(bitfield(block_freq, 9, 3), bitfield(block_freq, 5, 4)) << ksl;
+
+ // 4-bit sustain level, but 15 means 31 so effectively 5 bits
+ cache.eg_sustain = op_sustain_level(opoffs);
+ cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
+ cache.eg_sustain <<= 5;
+
+ // The envelope diagram in the YM2413 datasheet gives values for these
+ // in ms from 0->48dB. The attack/decay tables give values in ms from
+ // 0->96dB, so to pick an equivalent decay rate, we want to find the
+ // closest match that is 2x the 0->48dB value:
+ //
+ // DP = 10ms (0->48db) -> 20ms (0->96db); decay of 12 gives 19.20ms
+ // RR = 310ms (0->48db) -> 620ms (0->96db); decay of 7 gives 613.76ms
+ // RS = 1200ms (0->48db) -> 2400ms (0->96db); decay of 5 gives 2455.04ms
+ //
+ // The envelope diagram for percussive sounds (eg_sustain() == 0) also uses
+ // "RR" to mean both the constant RR above and the Release Rate specified in
+ // the instrument data. In this case, Relief Pitcher's credit sound bears out
+ // that the Release Rate is used during sustain, and that the constant RR
+ // (or RS) is used during the release phase.
+ constexpr uint8_t DP = 12 * 4;
+ constexpr uint8_t RR = 7 * 4;
+ constexpr uint8_t RS = 5 * 4;
+
+ // determine KSR adjustment for envelope rates
+ uint32_t ksrval = keycode >> (2 * (op_ksr(opoffs) ^ 1));
+ cache.eg_rate[EG_DEPRESS] = DP;
+ cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 4, ksrval);
+ cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 4, ksrval);
+ if (op_eg_sustain(opoffs))
+ {
+ cache.eg_rate[EG_SUSTAIN] = 0;
+ cache.eg_rate[EG_RELEASE] = ch_sustain(choffs) ? RS : effective_rate(op_release_rate(opoffs) * 4, ksrval);
+ }
+ else
+ {
+ cache.eg_rate[EG_SUSTAIN] = effective_rate(op_release_rate(opoffs) * 4, ksrval);
+ cache.eg_rate[EG_RELEASE] = ch_sustain(choffs) ? RS : RR;
+ }
+}
+
+
+//-------------------------------------------------
+// compute_phase_step - compute the phase step
+//-------------------------------------------------
+
+uint32_t opll_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
+{
+ // phase step computation is the same as OPL but the block_freq has one
+ // more bit, which we shift in
+ return opl_compute_phase_step(cache.block_freq << 1, cache.multiple, op_lfo_pm_enable(opoffs) ? lfo_raw_pm : 0);
+}
+
+
+//-------------------------------------------------
+// log_keyon - log a key-on event
+//-------------------------------------------------
+
+std::string opll_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
+{
+ uint32_t chnum = choffs;
+ uint32_t opnum = opoffs;
+
+ char buffer[256];
+ int end = 0;
+
+ end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X inst=%X fb=%u mul=%X",
+ chnum, opnum,
+ ch_block_freq(choffs),
+ ch_instrument(choffs),
+ ch_feedback(choffs),
+ op_multiple(opoffs));
+
+ if (bitfield(opoffs, 0) == 1 || (is_rhythm(choffs) && choffs >= 6))
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " vol=%X", op_volume(opoffs));
+ else
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " tl=%02X", ch_total_level(choffs));
+
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " ksr=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u/%u",
+ op_ksr(opoffs),
+ op_ksl(opoffs),
+ op_attack_rate(opoffs),
+ op_decay_rate(opoffs),
+ op_release_rate(opoffs),
+ op_sustain_level(opoffs),
+ op_eg_sustain(opoffs),
+ ch_sustain(choffs));
+
+ if (op_lfo_am_enable(opoffs))
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " am=1");
+ if (op_lfo_pm_enable(opoffs))
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=1");
+ if (op_waveform(opoffs) != 0)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=1");
+ if (is_rhythm(choffs))
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " rhy=1");
+
+ return buffer;
+}
+
+
+
+//*********************************************************
+// YM3526
+//*********************************************************
+
+//-------------------------------------------------
+// ym3526 - constructor
+//-------------------------------------------------
+
+ym3526::ym3526(ymfm_interface &intf) :
+ m_address(0),
+ m_fm(intf)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ym3526::reset()
+{
+ // reset the engines
+ m_fm.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ym3526::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ m_fm.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ym3526::read_status()
+{
+ return m_fm.status() | 0x06;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ym3526::read(uint32_t offset)
+{
+ uint8_t result = 0xff;
+ switch (offset & 1)
+ {
+ case 0: // status port
+ result = read_status();
+ break;
+
+ case 1: // when A0=1 datasheet says "the data on the bus are not guaranteed"
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ym3526::write_address(uint8_t data)
+{
+ // YM3526 doesn't expose a busy signal, and the datasheets don't indicate
+ // delays, but all other OPL chips need 12 cycles for address writes
+ m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale());
+
+ // just set the address
+ m_address = data;
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym3526::write_data(uint8_t data)
+{
+ // YM3526 doesn't expose a busy signal, and the datasheets don't indicate
+ // delays, but all other OPL chips need 84 cycles for data writes
+ m_fm.intf().ymfm_set_busy_end(84 * m_fm.clock_prescale());
+
+ // write to FM
+ m_fm.write(m_address, data);
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym3526::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 1)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate samples of sound
+//-------------------------------------------------
+
+void ym3526::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // update the FM content; mixing details for YM3526 need verification
+ m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS);
+
+ // YM3526 uses an external DAC (YM3014) with mantissa/exponent format
+ // convert to 10.3 floating point value and back to simulate truncation
+ output->roundtrip_fp();
+ }
+}
+
+
+
+//*********************************************************
+// Y8950
+//*********************************************************
+
+//-------------------------------------------------
+// y8950 - constructor
+//-------------------------------------------------
+
+y8950::y8950(ymfm_interface &intf) :
+ m_address(0),
+ m_io_ddr(0),
+ m_fm(intf),
+ m_adpcm_b(intf)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void y8950::reset()
+{
+ // reset the engines
+ m_fm.reset();
+ m_adpcm_b.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void y8950::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ state.save_restore(m_io_ddr);
+ m_fm.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t y8950::read_status()
+{
+ // start with current FM status, masking out bits we might set
+ uint8_t status = m_fm.status() & ~(STATUS_ADPCM_B_EOS | STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_PLAYING);
+
+ // insert the live ADPCM status bits
+ uint8_t adpcm_status = m_adpcm_b.status();
+ if ((adpcm_status & adpcm_b_channel::STATUS_EOS) != 0)
+ status |= STATUS_ADPCM_B_EOS;
+ if ((adpcm_status & adpcm_b_channel::STATUS_BRDY) != 0)
+ status |= STATUS_ADPCM_B_BRDY;
+ if ((adpcm_status & adpcm_b_channel::STATUS_PLAYING) != 0)
+ status |= STATUS_ADPCM_B_PLAYING;
+
+ // run it through the FM engine to handle interrupts for us
+ return m_fm.set_reset_status(status, ~status);
+}
+
+
+//-------------------------------------------------
+// read_data - read the data port
+//-------------------------------------------------
+
+uint8_t y8950::read_data()
+{
+ uint8_t result = 0xff;
+ switch (m_address)
+ {
+ case 0x05: // keyboard in
+ result = m_fm.intf().ymfm_external_read(ACCESS_IO, 1);
+ break;
+
+ case 0x09: // ADPCM data
+ case 0x1a:
+ result = m_adpcm_b.read(m_address - 0x07);
+ break;
+
+ case 0x19: // I/O data
+ result = m_fm.intf().ymfm_external_read(ACCESS_IO, 0);
+ break;
+
+ default:
+ debug::log_unexpected_read_write("Unexpected read from Y8950 data port %02X\n", m_address);
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t y8950::read(uint32_t offset)
+{
+ uint8_t result = 0xff;
+ switch (offset & 1)
+ {
+ case 0: // status port
+ result = read_status();
+ break;
+
+ case 1: // when A0=1 datasheet says "the data on the bus are not guaranteed"
+ result = read_data();
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void y8950::write_address(uint8_t data)
+{
+ // Y8950 doesn't expose a busy signal, but it does indicate that
+ // address writes should be no faster than every 12 clocks
+ m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale());
+
+ // just set the address
+ m_address = data;
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void y8950::write_data(uint8_t data)
+{
+ // Y8950 doesn't expose a busy signal, but it does indicate that
+ // data writes should be no faster than every 12 clocks for
+ // registers 00-1A, or every 84 clocks for other registers
+ m_fm.intf().ymfm_set_busy_end(((m_address <= 0x1a) ? 12 : 84) * m_fm.clock_prescale());
+
+ // handle special addresses
+ switch (m_address)
+ {
+ case 0x04: // IRQ control
+ m_fm.write(m_address, data);
+ read_status();
+ break;
+
+ case 0x06: // keyboard out
+ m_fm.intf().ymfm_external_write(ACCESS_IO, 1, data);
+ break;
+
+ case 0x08: // split FM/ADPCM-B
+ m_adpcm_b.write(m_address - 0x07, (data & 0x0f) | 0x80);
+ m_fm.write(m_address, data & 0xc0);
+ break;
+
+ case 0x07: // ADPCM-B registers
+ case 0x09:
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ case 0x0e:
+ case 0x0f:
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ case 0x15:
+ case 0x16:
+ case 0x17:
+ m_adpcm_b.write(m_address - 0x07, data);
+ break;
+
+ case 0x18: // I/O direction
+ m_io_ddr = data & 0x0f;
+ break;
+
+ case 0x19: // I/O data
+ m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data & m_io_ddr);
+ break;
+
+ default: // everything else to FM
+ m_fm.write(m_address, data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void y8950::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 1)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate samples of sound
+//-------------------------------------------------
+
+void y8950::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+ m_adpcm_b.clock();
+
+ // update the FM content; clipping need verification
+ m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS);
+
+ // mix in the ADPCM; ADPCM-B is stereo, but only one channel
+ // not sure how it's wired up internally
+ m_adpcm_b.output(*output, 3);
+
+ // Y8950 uses an external DAC (YM3014) with mantissa/exponent format
+ // convert to 10.3 floating point value and back to simulate truncation
+ output->roundtrip_fp();
+ }
+}
+
+
+
+//*********************************************************
+// YM3812
+//*********************************************************
+
+//-------------------------------------------------
+// ym3812 - constructor
+//-------------------------------------------------
+
+ym3812::ym3812(ymfm_interface &intf) :
+ m_address(0),
+ m_fm(intf)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ym3812::reset()
+{
+ // reset the engines
+ m_fm.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ym3812::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ m_fm.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ym3812::read_status()
+{
+ return m_fm.status() | 0x06;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ym3812::read(uint32_t offset)
+{
+ uint8_t result = 0xff;
+ switch (offset & 1)
+ {
+ case 0: // status port
+ result = read_status();
+ break;
+
+ case 1: // "inhibit" according to datasheet
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ym3812::write_address(uint8_t data)
+{
+ // YM3812 doesn't expose a busy signal, but it does indicate that
+ // address writes should be no faster than every 12 clocks
+ m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale());
+
+ // just set the address
+ m_address = data;
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym3812::write_data(uint8_t data)
+{
+ // YM3812 doesn't expose a busy signal, but it does indicate that
+ // data writes should be no faster than every 84 clocks
+ m_fm.intf().ymfm_set_busy_end(84 * m_fm.clock_prescale());
+
+ // write to FM
+ m_fm.write(m_address, data);
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym3812::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 1)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate samples of sound
+//-------------------------------------------------
+
+void ym3812::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // update the FM content; mixing details for YM3812 need verification
+ m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS);
+
+ // YM3812 uses an external DAC (YM3014) with mantissa/exponent format
+ // convert to 10.3 floating point value and back to simulate truncation
+ output->roundtrip_fp();
+ }
+}
+
+
+
+//*********************************************************
+// YMF262
+//*********************************************************
+
+//-------------------------------------------------
+// ymf262 - constructor
+//-------------------------------------------------
+
+ymf262::ymf262(ymfm_interface &intf) :
+ m_address(0),
+ m_fm(intf)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ymf262::reset()
+{
+ // reset the engines
+ m_fm.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ymf262::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ m_fm.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ymf262::read_status()
+{
+ return m_fm.status();
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ymf262::read(uint32_t offset)
+{
+ uint8_t result = 0xff;
+ switch (offset & 3)
+ {
+ case 0: // status port
+ result = read_status();
+ break;
+
+ case 1:
+ case 2:
+ case 3:
+ debug::log_unexpected_read_write("Unexpected read from YMF262 offset %d\n", offset & 3);
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ymf262::write_address(uint8_t data)
+{
+ // YMF262 doesn't expose a busy signal, but it does indicate that
+ // address writes should be no faster than every 32 clocks
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+
+ // just set the address
+ m_address = data;
+}
+
+
+//-------------------------------------------------
+// write_data - handle a write to the data
+// register
+//-------------------------------------------------
+
+void ymf262::write_data(uint8_t data)
+{
+ // YMF262 doesn't expose a busy signal, but it does indicate that
+ // data writes should be no faster than every 32 clocks
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+
+ // write to FM
+ m_fm.write(m_address, data);
+}
+
+
+//-------------------------------------------------
+// write_address_hi - handle a write to the upper
+// address register
+//-------------------------------------------------
+
+void ymf262::write_address_hi(uint8_t data)
+{
+ // YMF262 doesn't expose a busy signal, but it does indicate that
+ // address writes should be no faster than every 32 clocks
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+
+ // just set the address
+ m_address = data | 0x100;
+
+ // tests reveal that in compatibility mode, upper bit is masked
+ // except for register 0x105
+ if (m_fm.regs().newflag() == 0 && m_address != 0x105)
+ m_address &= 0xff;
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ymf262::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 3)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+
+ case 2: // address port
+ write_address_hi(data);
+ break;
+
+ case 3: // data port
+ write_data(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate samples of sound
+//-------------------------------------------------
+
+void ymf262::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // update the FM content; mixing details for YMF262 need verification
+ m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
+
+ // YMF262 output is 16-bit offset serial via YAC512 DAC
+ output->clamp16();
+ }
+}
+
+
+
+//*********************************************************
+// YMF289B
+//*********************************************************
+
+// YMF289B is a YMF262 with the following changes:
+// * "Power down" mode added
+// * Bulk register clear added
+// * Busy flag added to the status register
+// * Shorter busy times
+// * All registers can be read
+// * Only 2 outputs exposed
+
+//-------------------------------------------------
+// ymf289b - constructor
+//-------------------------------------------------
+
+ymf289b::ymf289b(ymfm_interface &intf) :
+ m_address(0),
+ m_fm(intf)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ymf289b::reset()
+{
+ // reset the engines
+ m_fm.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ymf289b::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ m_fm.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ymf289b::read_status()
+{
+ uint8_t result = m_fm.status();
+
+ // YMF289B adds a busy flag
+ if (ymf289b_mode() && m_fm.intf().ymfm_is_busy())
+ result |= STATUS_BUSY_FLAGS;
+ return result;
+}
+
+
+//-------------------------------------------------
+// read_data - read the data register
+//-------------------------------------------------
+
+uint8_t ymf289b::read_data()
+{
+ uint8_t result = 0xff;
+
+ // YMF289B can read register data back
+ if (ymf289b_mode())
+ result = m_fm.regs().read(m_address);
+ return result;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ymf289b::read(uint32_t offset)
+{
+ uint8_t result = 0xff;
+ switch (offset & 3)
+ {
+ case 0: // status port
+ result = read_status();
+ break;
+
+ case 1: // data port
+ result = read_data();
+ break;
+
+ case 2:
+ case 3:
+ debug::log_unexpected_read_write("Unexpected read from YMF289B offset %d\n", offset & 3);
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ymf289b::write_address(uint8_t data)
+{
+ m_address = data;
+
+ // count busy time
+ m_fm.intf().ymfm_set_busy_end(56);
+}
+
+
+//-------------------------------------------------
+// write_data - handle a write to the data
+// register
+//-------------------------------------------------
+
+void ymf289b::write_data(uint8_t data)
+{
+ // write to FM
+ m_fm.write(m_address, data);
+
+ // writes to 0x108 with the CLR flag set clear the registers
+ if (m_address == 0x108 && bitfield(data, 2) != 0)
+ m_fm.regs().reset();
+
+ // count busy time
+ m_fm.intf().ymfm_set_busy_end(56);
+}
+
+
+//-------------------------------------------------
+// write_address_hi - handle a write to the upper
+// address register
+//-------------------------------------------------
+
+void ymf289b::write_address_hi(uint8_t data)
+{
+ // just set the address
+ m_address = data | 0x100;
+
+ // tests reveal that in compatibility mode, upper bit is masked
+ // except for register 0x105
+ if (m_fm.regs().newflag() == 0 && m_address != 0x105)
+ m_address &= 0xff;
+
+ // count busy time
+ m_fm.intf().ymfm_set_busy_end(56);
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ymf289b::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 3)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+
+ case 2: // address port
+ write_address_hi(data);
+ break;
+
+ case 3: // data port
+ write_data(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate samples of sound
+//-------------------------------------------------
+
+void ymf289b::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // update the FM content; mixing details for YMF262 need verification
+ fm_engine::output_data full;
+ m_fm.output(full.clear(), 0, 32767, fm_engine::ALL_CHANNELS);
+
+ // YMF278B output is 16-bit offset serial via YAC512 DAC, but
+ // only 2 of the 4 outputs are exposed
+ output->data[0] = full.data[0];
+ output->data[1] = full.data[1];
+ output->clamp16();
+ }
+}
+
+
+
+//*********************************************************
+// YMF278B
+//*********************************************************
+
+//-------------------------------------------------
+// ymf278b - constructor
+//-------------------------------------------------
+
+ymf278b::ymf278b(ymfm_interface &intf) :
+ m_address(0),
+ m_fm_pos(0),
+ m_load_remaining(0),
+ m_next_status_id(false),
+ m_fm(intf),
+ m_pcm(intf)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ymf278b::reset()
+{
+ // reset the engines
+ m_fm.reset();
+ m_pcm.reset();
+
+ // next status read will return ID
+ m_next_status_id = true;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ymf278b::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ state.save_restore(m_fm_pos);
+ state.save_restore(m_load_remaining);
+ state.save_restore(m_next_status_id);
+ m_fm.save_restore(state);
+ m_pcm.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ymf278b::read_status()
+{
+ uint8_t result;
+
+ // first status read after initialization returns a chip ID, which
+ // varies based on the "new" flags, indicating the mode
+ if (m_next_status_id)
+ {
+ if (m_fm.regs().new2flag())
+ result = 0x02;
+ else if (m_fm.regs().newflag())
+ result = 0x00;
+ else
+ result = 0x06;
+ m_next_status_id = false;
+ }
+ else
+ {
+ result = m_fm.status();
+ if (m_fm.intf().ymfm_is_busy())
+ result |= STATUS_BUSY;
+ if (m_load_remaining != 0)
+ result |= STATUS_LD;
+
+ // if new2 flag is not set, we're in OPL2 or OPL3 mode
+ if (!m_fm.regs().new2flag())
+ result &= ~(STATUS_BUSY | STATUS_LD);
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_data_pcm - handle a write to the PCM data
+// register
+//-------------------------------------------------
+
+uint8_t ymf278b::read_data_pcm()
+{
+ // read from PCM
+ if (bitfield(m_address, 9) != 0)
+ {
+ uint8_t result = m_pcm.read(m_address & 0xff);
+ if ((m_address & 0xff) == 0x02)
+ result |= 0x20;
+
+ return result;
+ }
+ return 0;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ymf278b::read(uint32_t offset)
+{
+ uint8_t result = 0xff;
+ switch (offset & 7)
+ {
+ case 0: // status port
+ result = read_status();
+ break;
+
+ case 5: // PCM data port
+ result = read_data_pcm();
+ break;
+
+ default:
+ debug::log_unexpected_read_write("Unexpected read from ymf278b offset %d\n", offset & 3);
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ymf278b::write_address(uint8_t data)
+{
+ // just set the address
+ m_address = data;
+}
+
+
+//-------------------------------------------------
+// write_data - handle a write to the data
+// register
+//-------------------------------------------------
+
+void ymf278b::write_data(uint8_t data)
+{
+ // write to FM
+ if (bitfield(m_address, 9) == 0)
+ {
+ uint8_t old = m_fm.regs().new2flag();
+ m_fm.write(m_address, data);
+
+ // changing NEW2 from 0->1 causes the next status read to
+ // return the chip ID
+ if (old == 0 && m_fm.regs().new2flag() != 0)
+ m_next_status_id = true;
+ }
+
+ // BUSY goes for 56 clocks on FM writes
+ m_fm.intf().ymfm_set_busy_end(56);
+}
+
+
+//-------------------------------------------------
+// write_address_hi - handle a write to the upper
+// address register
+//-------------------------------------------------
+
+void ymf278b::write_address_hi(uint8_t data)
+{
+ // just set the address
+ m_address = data | 0x100;
+
+ // YMF262, in compatibility mode, treats the upper bit as masked
+ // except for register 0x105; assuming YMF278B works the same way?
+ if (m_fm.regs().newflag() == 0 && m_address != 0x105)
+ m_address &= 0xff;
+}
+
+
+//-------------------------------------------------
+// write_address_pcm - handle a write to the upper
+// address register
+//-------------------------------------------------
+
+void ymf278b::write_address_pcm(uint8_t data)
+{
+ // just set the address
+ m_address = data | 0x200;
+}
+
+
+//-------------------------------------------------
+// write_data_pcm - handle a write to the PCM data
+// register
+//-------------------------------------------------
+
+void ymf278b::write_data_pcm(uint8_t data)
+{
+ // ignore data writes if new2 is not yet set
+ if (m_fm.regs().new2flag() == 0)
+ return;
+
+ // write to FM
+ if (bitfield(m_address, 9) != 0)
+ {
+ uint8_t addr = m_address & 0xff;
+ m_pcm.write(addr, data);
+
+ // writes to the waveform number cause loads to happen for "about 300usec"
+ // which is ~13 samples at the nominal output frequency of 44.1kHz
+ if (addr >= 0x08 && addr <= 0x1f)
+ m_load_remaining = 13;
+ }
+
+ // BUSY goes for 88 clocks on PCM writes
+ m_fm.intf().ymfm_set_busy_end(88);
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ymf278b::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 7)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+
+ case 2: // address port
+ write_address_hi(data);
+ break;
+
+ case 3: // data port
+ write_data(data);
+ break;
+
+ case 4: // PCM address port
+ write_address_pcm(data);
+ break;
+
+ case 5: // PCM address port
+ write_data_pcm(data);
+ break;
+
+ default:
+ debug::log_unexpected_read_write("Unexpected write to ymf278b offset %d\n", offset & 7);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void ymf278b::generate(output_data *output, uint32_t numsamples)
+{
+ static const int16_t s_mix_scale[8] = { 0x7fa, 0x5a4, 0x3fd, 0x2d2, 0x1fe, 0x169, 0xff, 0 };
+ int32_t const pcm_l = s_mix_scale[m_pcm.regs().mix_pcm_l()];
+ int32_t const pcm_r = s_mix_scale[m_pcm.regs().mix_pcm_r()];
+ int32_t const fm_l = s_mix_scale[m_pcm.regs().mix_fm_l()];
+ int32_t const fm_r = s_mix_scale[m_pcm.regs().mix_fm_r()];
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm_pos += FM_EXTRA_SAMPLE_STEP;
+ if (m_fm_pos >= FM_EXTRA_SAMPLE_THRESH)
+ {
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+ m_fm_pos -= FM_EXTRA_SAMPLE_THRESH;
+ }
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+ m_pcm.clock(pcm_engine::ALL_CHANNELS);
+
+ // update the FM content; mixing details for YMF278B need verification
+ fm_engine::output_data fmout;
+ m_fm.output(fmout.clear(), 0, 32767, fm_engine::ALL_CHANNELS);
+
+ // update the PCM content
+ pcm_engine::output_data pcmout;
+ m_pcm.output(pcmout.clear(), pcm_engine::ALL_CHANNELS);
+
+ // DO0 output: FM channels 2+3 only
+ output->data[0] = fmout.data[2];
+ output->data[1] = fmout.data[3];
+
+ // DO1 output: wavetable channels 2+3 only
+ output->data[2] = pcmout.data[2];
+ output->data[3] = pcmout.data[3];
+
+ // DO2 output: mixed FM channels 0+1 and wavetable channels 0+1
+ output->data[4] = (fmout.data[0] * fm_l + pcmout.data[0] * pcm_l) >> 11;
+ output->data[5] = (fmout.data[1] * fm_r + pcmout.data[1] * pcm_r) >> 11;
+
+ // YMF278B output is 16-bit 2s complement serial
+ output->clamp16();
+ }
+
+ // decrement the load waiting count
+ if (m_load_remaining > 0)
+ m_load_remaining -= std::min(m_load_remaining, numsamples);
+}
+
+
+
+//*********************************************************
+// OPLL BASE
+//*********************************************************
+
+//-------------------------------------------------
+// opll_base - constructor
+//-------------------------------------------------
+
+opll_base::opll_base(ymfm_interface &intf, uint8_t const *instrument_data) :
+ m_address(0),
+ m_fm(intf)
+{
+ m_fm.regs().set_instrument_data(instrument_data);
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void opll_base::reset()
+{
+ // reset the engines
+ m_fm.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void opll_base::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ m_fm.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void opll_base::write_address(uint8_t data)
+{
+ // OPLL doesn't expose a busy signal, but datasheets are pretty consistent
+ // in indicating that address writes should be no faster than every 12 clocks
+ m_fm.intf().ymfm_set_busy_end(12);
+
+ // just set the address
+ m_address = data;
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void opll_base::write_data(uint8_t data)
+{
+ // OPLL doesn't expose a busy signal, but datasheets are pretty consistent
+ // in indicating that address writes should be no faster than every 84 clocks
+ m_fm.intf().ymfm_set_busy_end(84);
+
+ // write to FM
+ m_fm.write(m_address, data);
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void opll_base::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 1)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void opll_base::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // update the FM content; OPLL has a built-in 9-bit DAC
+ m_fm.output(output->clear(), 5, 256, fm_engine::ALL_CHANNELS);
+
+ // final output is multiplexed; we don't simulate that here except
+ // to average over everything
+ output->data[0] = (output->data[0] * 128) / 9;
+ output->data[1] = (output->data[1] * 128) / 9;
+ }
+}
+
+
+
+//*********************************************************
+// YM2413
+//*********************************************************
+
+//-------------------------------------------------
+// ym2413 - constructor
+//-------------------------------------------------
+
+ym2413::ym2413(ymfm_interface &intf, uint8_t const *instrument_data) :
+ opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments)
+{
+};
+
+// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches
+uint8_t const ym2413::s_default_instruments[] =
+{
+ //April 2015 David Viens, tweaked May 19-21th 2015 Hubert Lamontagne
+ 0x71, 0x61, 0x1E, 0x17, 0xEF, 0x7F, 0x00, 0x17, //Violin
+ 0x13, 0x41, 0x1A, 0x0D, 0xF8, 0xF7, 0x23, 0x13, //Guitar
+ 0x13, 0x01, 0x99, 0x00, 0xF2, 0xC4, 0x11, 0x23, //Piano
+ 0x31, 0x61, 0x0E, 0x07, 0x98, 0x64, 0x70, 0x27, //Flute
+ 0x22, 0x21, 0x1E, 0x06, 0xBF, 0x76, 0x00, 0x28, //Clarinet
+ 0x31, 0x22, 0x16, 0x05, 0xE0, 0x71, 0x0F, 0x18, //Oboe
+ 0x21, 0x61, 0x1D, 0x07, 0x82, 0x8F, 0x10, 0x07, //Trumpet
+ 0x23, 0x21, 0x2D, 0x14, 0xFF, 0x7F, 0x00, 0x07, //Organ
+ 0x41, 0x61, 0x1B, 0x06, 0x64, 0x65, 0x10, 0x17, //Horn
+ 0x61, 0x61, 0x0B, 0x18, 0x85, 0xFF, 0x81, 0x07, //Synthesizer
+ 0x13, 0x01, 0x83, 0x11, 0xFA, 0xE4, 0x10, 0x04, //Harpsichord
+ 0x17, 0x81, 0x23, 0x07, 0xF8, 0xF8, 0x22, 0x12, //Vibraphone
+ 0x61, 0x50, 0x0C, 0x05, 0xF2, 0xF5, 0x29, 0x42, //Synthesizer Bass
+ 0x01, 0x01, 0x54, 0x03, 0xC3, 0x92, 0x03, 0x02, //Acoustic Bass
+ 0x41, 0x41, 0x89, 0x03, 0xF1, 0xE5, 0x11, 0x13, //Electric Guitar
+ 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1
+ 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2
+ 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3
+};
+
+
+
+//*********************************************************
+// YM2423
+//*********************************************************
+
+//-------------------------------------------------
+// ym2423 - constructor
+//-------------------------------------------------
+
+ym2423::ym2423(ymfm_interface &intf, uint8_t const *instrument_data) :
+ opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments)
+{
+};
+
+// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches
+uint8_t const ym2423::s_default_instruments[] =
+{
+ // May 4-6 2016 Hubert Lamontagne
+ // Doesn't seem to have any diff between opllx-x and opllx-y
+ // Drums seem identical to regular opll
+ 0x61, 0x61, 0x1B, 0x07, 0x94, 0x5F, 0x10, 0x06, //1 Strings Saw wave with vibrato Violin
+ 0x93, 0xB1, 0x51, 0x04, 0xF3, 0xF2, 0x70, 0xFB, //2 Guitar Jazz GuitarPiano
+ 0x41, 0x21, 0x11, 0x85, 0xF2, 0xF2, 0x70, 0x75, //3 Electric Guitar Same as OPLL No.15 Synth
+ 0x93, 0xB2, 0x28, 0x07, 0xF3, 0xF2, 0x70, 0xB4, //4 Electric Piano 2 Slow attack, tremoloDing-a-ling
+ 0x72, 0x31, 0x97, 0x05, 0x51, 0x6F, 0x60, 0x09, //5 Flute Same as OPLL No.4Clarinet
+ 0x13, 0x30, 0x18, 0x06, 0xF7, 0xF4, 0x50, 0x85, //6 Marimba Also be used as steel drumXyophone
+ 0x51, 0x31, 0x1C, 0x07, 0x51, 0x71, 0x20, 0x26, //7 Trumpet Same as OPLL No.7Trumpet
+ 0x41, 0xF4, 0x1B, 0x07, 0x74, 0x34, 0x00, 0x06, //8 Harmonica Harmonica synth
+ 0x50, 0x30, 0x4D, 0x03, 0x42, 0x65, 0x20, 0x06, //9 Tuba Tuba
+ 0x40, 0x20, 0x10, 0x85, 0xF3, 0xF5, 0x20, 0x04, //10 Synth Brass 2 Synth sweep
+ 0x61, 0x61, 0x1B, 0x07, 0xC5, 0x96, 0xF3, 0xF6, //11 Short Saw Saw wave with short envelopeSynth hit
+ 0xF9, 0xF1, 0xDC, 0x00, 0xF5, 0xF3, 0x77, 0xF2, //12 Vibraphone Bright vibraphoneVibes
+ 0x60, 0xA2, 0x91, 0x03, 0x94, 0xC1, 0xF7, 0xF7, //13 Electric Guitar 2 Clean guitar with feedbackHarmonic bass
+ 0x30, 0x30, 0x17, 0x06, 0xF3, 0xF1, 0xB7, 0xFC, //14 Synth Bass 2Snappy bass
+ 0x31, 0x36, 0x0D, 0x05, 0xF2, 0xF4, 0x27, 0x9C, //15 Sitar Also be used as ShamisenBanjo
+ 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1
+ 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2
+ 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3
+};
+
+
+
+//*********************************************************
+// YMF281
+//*********************************************************
+
+//-------------------------------------------------
+// ymf281 - constructor
+//-------------------------------------------------
+
+ymf281::ymf281(ymfm_interface &intf, uint8_t const *instrument_data) :
+ opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments)
+{
+};
+
+// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches
+uint8_t const ymf281::s_default_instruments[] =
+{
+ // May 14th 2015 Hubert Lamontagne
+ 0x72, 0x21, 0x1A, 0x07, 0xF6, 0x64, 0x01, 0x16, // Clarinet ~~ Electric String Square wave with vibrato
+ 0x00, 0x10, 0x45, 0x00, 0xF6, 0x83, 0x73, 0x63, // Synth Bass ~~ Bow wow Triangular wave
+ 0x13, 0x01, 0x96, 0x00, 0xF1, 0xF4, 0x31, 0x23, // Piano ~~ Electric Guitar Despite of its name, same as Piano of YM2413.
+ 0x71, 0x21, 0x0B, 0x0F, 0xF9, 0x64, 0x70, 0x17, // Flute ~~ Organ Sine wave
+ 0x02, 0x21, 0x1E, 0x06, 0xF9, 0x76, 0x00, 0x28, // Square Wave ~~ Clarinet Same as ones of YM2413.
+ 0x00, 0x61, 0x82, 0x0E, 0xF9, 0x61, 0x20, 0x27, // Space Oboe ~~ Saxophone Saw wave with vibrato
+ 0x21, 0x61, 0x1B, 0x07, 0x84, 0x8F, 0x10, 0x07, // Trumpet ~~ Trumpet Same as ones of YM2413.
+ 0x37, 0x32, 0xCA, 0x02, 0x66, 0x64, 0x47, 0x29, // Wow Bell ~~ Street Organ Calliope
+ 0x41, 0x41, 0x07, 0x03, 0xF5, 0x70, 0x51, 0xF5, // Electric Guitar ~~ Synth Brass Same as Synthesizer of YM2413.
+ 0x36, 0x01, 0x5E, 0x07, 0xF2, 0xF3, 0xF7, 0xF7, // Vibes ~~ Electric Piano Simulate of Rhodes Piano
+ 0x00, 0x00, 0x18, 0x06, 0xC5, 0xF3, 0x20, 0xF2, // Bass ~~ Bass Electric bass
+ 0x17, 0x81, 0x25, 0x07, 0xF7, 0xF3, 0x21, 0xF7, // Vibraphone ~~ Vibraphone Same as ones of YM2413.
+ 0x35, 0x64, 0x00, 0x00, 0xFF, 0xF3, 0x77, 0xF5, // Vibrato Bell ~~ Chime Bell
+ 0x11, 0x31, 0x00, 0x07, 0xDD, 0xF3, 0xFF, 0xFB, // Click Sine ~~ Tom Tom II Tom
+ 0x3A, 0x21, 0x00, 0x07, 0x95, 0x84, 0x0F, 0xF5, // Noise and Tone ~~ Noise for S.E.
+ 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1
+ 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2
+ 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3
+};
+
+
+
+//*********************************************************
+// DS1001
+//*********************************************************
+
+//-------------------------------------------------
+// ds1001 - constructor
+//-------------------------------------------------
+
+ds1001::ds1001(ymfm_interface &intf, uint8_t const *instrument_data) :
+ opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments)
+{
+};
+
+// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches
+uint8_t const ds1001::s_default_instruments[] =
+{
+ // May 15th 2015 Hubert Lamontagne & David Viens
+ 0x03, 0x21, 0x05, 0x06, 0xC8, 0x81, 0x42, 0x27, // Buzzy Bell
+ 0x13, 0x41, 0x14, 0x0D, 0xF8, 0xF7, 0x23, 0x12, // Guitar
+ 0x31, 0x11, 0x08, 0x08, 0xFA, 0xC2, 0x28, 0x22, // Wurly
+ 0x31, 0x61, 0x0C, 0x07, 0xF8, 0x64, 0x60, 0x27, // Flute
+ 0x22, 0x21, 0x1E, 0x06, 0xFF, 0x76, 0x00, 0x28, // Clarinet
+ 0x02, 0x01, 0x05, 0x00, 0xAC, 0xF2, 0x03, 0x02, // Synth
+ 0x21, 0x61, 0x1D, 0x07, 0x82, 0x8F, 0x10, 0x07, // Trumpet
+ 0x23, 0x21, 0x22, 0x17, 0xFF, 0x73, 0x00, 0x17, // Organ
+ 0x15, 0x11, 0x25, 0x00, 0x41, 0x71, 0x00, 0xF1, // Bells
+ 0x95, 0x01, 0x10, 0x0F, 0xB8, 0xAA, 0x50, 0x02, // Vibes
+ 0x17, 0xC1, 0x5E, 0x07, 0xFA, 0xF8, 0x22, 0x12, // Vibraphone
+ 0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x10, 0x16, // Tutti
+ 0x01, 0x02, 0xD3, 0x05, 0xF3, 0x92, 0x83, 0xF2, // Fretless
+ 0x61, 0x63, 0x0C, 0x00, 0xA4, 0xFF, 0x30, 0x06, // Synth Bass
+ 0x21, 0x62, 0x0D, 0x00, 0xA1, 0xFF, 0x50, 0x08, // Sweep
+ 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1
+ 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2
+ 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3
+};
+
+
+//*********************************************************
+// EXPLICIT INSTANTIATION
+//*********************************************************
+
+template class opl_registers_base<4>;
+template class fm_engine_base<opl_registers_base<4>>;
+
+}
diff --git a/3rdparty/ymfm/src/ymfm_opl.h b/3rdparty/ymfm/src/ymfm_opl.h
new file mode 100644
index 00000000000..71b098e974a
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_opl.h
@@ -0,0 +1,902 @@
+// 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_OPL_H
+#define YMFM_OPL_H
+
+#pragma once
+
+#include "ymfm.h"
+#include "ymfm_adpcm.h"
+#include "ymfm_fm.h"
+#include "ymfm_pcm.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// REGISTER CLASSES
+//*********************************************************
+
+// ======================> opl_registers_base
+
+//
+// OPL/OPL2/OPL3/OPL4 register map:
+//
+// System-wide registers:
+// 01 xxxxxxxx Test register
+// --x----- Enable OPL compatibility mode [OPL2 only] (0 = enable)
+// 02 xxxxxxxx Timer A value (4 * OPN)
+// 03 xxxxxxxx Timer B value
+// 04 x------- RST
+// -x------ Mask timer A
+// --x----- Mask timer B
+// ------x- Load timer B
+// -------x Load timer A
+// 08 x------- CSM mode [OPL/OPL2 only]
+// -x------ Note select
+// BD x------- AM depth
+// -x------ PM depth
+// --x----- Rhythm enable
+// ---x---- Bass drum key on
+// ----x--- Snare drum key on
+// -----x-- Tom key on
+// ------x- Top cymbal key on
+// -------x High hat key on
+// 101 --xxxxxx Test register 2 [OPL3 only]
+// 104 --x----- Channel 6 4-operator mode [OPL3 only]
+// ---x---- Channel 5 4-operator mode [OPL3 only]
+// ----x--- Channel 4 4-operator mode [OPL3 only]
+// -----x-- Channel 3 4-operator mode [OPL3 only]
+// ------x- Channel 2 4-operator mode [OPL3 only]
+// -------x Channel 1 4-operator mode [OPL3 only]
+// 105 -------x New [OPL3 only]
+// ------x- New2 [OPL4 only]
+//
+// Per-channel registers (channel in address bits 0-3)
+// Note that all these apply to address+100 as well on OPL3+
+// A0-A8 xxxxxxxx F-number (low 8 bits)
+// B0-B8 --x----- Key on
+// ---xxx-- Block (octvate, 0-7)
+// ------xx F-number (high two bits)
+// C0-C8 x------- CHD output (to DO0 pin) [OPL3+ only]
+// -x------ CHC output (to DO0 pin) [OPL3+ only]
+// --x----- CHB output (mixed right, to DO2 pin) [OPL3+ only]
+// ---x---- CHA output (mixed left, to DO2 pin) [OPL3+ only]
+// ----xxx- Feedback level for operator 1 (0-7)
+// -------x Operator connection algorithm
+//
+// Per-operator registers (operator in bits 0-5)
+// Note that all these apply to address+100 as well on OPL3+
+// 20-35 x------- AM enable
+// -x------ PM enable (VIB)
+// --x----- EG type
+// ---x---- Key scale rate
+// ----xxxx Multiple value (0-15)
+// 40-55 xx------ Key scale level (0-3)
+// --xxxxxx Total level (0-63)
+// 60-75 xxxx---- Attack rate (0-15)
+// ----xxxx Decay rate (0-15)
+// 80-95 xxxx---- Sustain level (0-15)
+// ----xxxx Release rate (0-15)
+// E0-F5 ------xx Wave select (0-3) [OPL2 only]
+// -----xxx Wave select (0-7) [OPL3+ only]
+//
+
+template<int Revision>
+class opl_registers_base : public fm_registers_base
+{
+ static constexpr bool IsOpl2 = (Revision == 2);
+ static constexpr bool IsOpl2Plus = (Revision >= 2);
+ static constexpr bool IsOpl3Plus = (Revision >= 3);
+ static constexpr bool IsOpl4Plus = (Revision >= 4);
+
+public:
+ // constants
+ static constexpr uint32_t OUTPUTS = IsOpl3Plus ? 4 : 1;
+ static constexpr uint32_t CHANNELS = IsOpl3Plus ? 18 : 9;
+ static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
+ static constexpr uint32_t OPERATORS = CHANNELS * 2;
+ static constexpr uint32_t WAVEFORMS = IsOpl3Plus ? 8 : (IsOpl2Plus ? 4 : 1);
+ static constexpr uint32_t REGISTERS = IsOpl3Plus ? 0x200 : 0x100;
+ static constexpr uint32_t REG_MODE = 0x04;
+ static constexpr uint32_t DEFAULT_PRESCALE = IsOpl4Plus ? 19 : (IsOpl3Plus ? 8 : 4);
+ static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
+ static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
+ static constexpr bool DYNAMIC_OPS = IsOpl3Plus;
+ static constexpr bool MODULATOR_DELAY = !IsOpl3Plus;
+ static constexpr uint8_t STATUS_TIMERA = 0x40;
+ static constexpr uint8_t STATUS_TIMERB = 0x20;
+ static constexpr uint8_t STATUS_BUSY = 0;
+ static constexpr uint8_t STATUS_IRQ = 0x80;
+
+ // constructor
+ opl_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 (!IsOpl3Plus)
+ return chnum;
+ else
+ return (chnum % 9) + 0x100 * (chnum / 9);
+ }
+
+ // map operator number to register offset
+ static constexpr uint32_t operator_offset(uint32_t opnum)
+ {
+ assert(opnum < OPERATORS);
+ if (!IsOpl3Plus)
+ return opnum + 2 * (opnum / 6);
+ else
+ return (opnum % 18) + 2 * ((opnum % 18) / 6) + 0x100 * (opnum / 18);
+ }
+
+ // return an array of operator indices for each channel
+ struct operator_mapping { uint32_t chan[CHANNELS]; };
+ void operator_map(operator_mapping &dest) const;
+
+ // OPL4 apparently can read back FM registers?
+ 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_am_counter = m_lfo_pm_counter = 0; }
+
+ // return the AM offset from LFO for the given channel
+ // on OPL this is just a fixed value
+ uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
+
+ // return LFO/noise states
+ uint32_t noise_state() const { return m_noise_lfsr >> 23; }
+
+ // 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(0x01, 0, 8); }
+ uint32_t waveform_enable() const { return IsOpl2 ? byte(0x01, 5, 1) : (IsOpl3Plus ? 1 : 0); }
+ uint32_t timer_a_value() const { return byte(0x02, 0, 8) * 4; } // 8->10 bits
+ uint32_t timer_b_value() const { return byte(0x03, 0, 8); }
+ uint32_t status_mask() const { return byte(0x04, 0, 8) & 0x78; }
+ uint32_t irq_reset() const { return byte(0x04, 7, 1); }
+ uint32_t reset_timer_b() const { return byte(0x04, 7, 1) | byte(0x04, 5, 1); }
+ uint32_t reset_timer_a() const { return byte(0x04, 7, 1) | byte(0x04, 6, 1); }
+ uint32_t enable_timer_b() const { return 1; }
+ uint32_t enable_timer_a() const { return 1; }
+ uint32_t load_timer_b() const { return byte(0x04, 1, 1); }
+ uint32_t load_timer_a() const { return byte(0x04, 0, 1); }
+ uint32_t csm() const { return IsOpl3Plus ? 0 : byte(0x08, 7, 1); }
+ uint32_t note_select() const { return byte(0x08, 6, 1); }
+ uint32_t lfo_am_depth() const { return byte(0xbd, 7, 1); }
+ uint32_t lfo_pm_depth() const { return byte(0xbd, 6, 1); }
+ uint32_t rhythm_enable() const { return byte(0xbd, 5, 1); }
+ uint32_t rhythm_keyon() const { return byte(0xbd, 4, 0); }
+ uint32_t newflag() const { return IsOpl3Plus ? byte(0x105, 0, 1) : 0; }
+ uint32_t new2flag() const { return IsOpl4Plus ? byte(0x105, 1, 1) : 0; }
+ uint32_t fourop_enable() const { return IsOpl3Plus ? byte(0x104, 0, 6) : 0; }
+
+ // per-channel registers
+ uint32_t ch_block_freq(uint32_t choffs) const { return word(0xb0, 0, 5, 0xa0, 0, 8, choffs); }
+ uint32_t ch_feedback(uint32_t choffs) const { return byte(0xc0, 1, 3, choffs); }
+ uint32_t ch_algorithm(uint32_t choffs) const { return byte(0xc0, 0, 1, choffs) | (IsOpl3Plus ? (8 | (byte(0xc3, 0, 1, choffs) << 1)) : 0); }
+ uint32_t ch_output_any(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 4) : 1; }
+ uint32_t ch_output_0(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 1) : 1; }
+ uint32_t ch_output_1(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 5, 1) : (IsOpl3Plus ? 1 : 0); }
+ uint32_t ch_output_2(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 6, 1) : 0; }
+ uint32_t ch_output_3(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 7, 1) : 0; }
+
+ // per-operator registers
+ uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0x20, 7, 1, opoffs); }
+ uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return byte(0x20, 6, 1, opoffs); }
+ uint32_t op_eg_sustain(uint32_t opoffs) const { return byte(0x20, 5, 1, opoffs); }
+ uint32_t op_ksr(uint32_t opoffs) const { return byte(0x20, 4, 1, opoffs); }
+ uint32_t op_multiple(uint32_t opoffs) const { return byte(0x20, 0, 4, opoffs); }
+ uint32_t op_ksl(uint32_t opoffs) const { uint32_t temp = byte(0x40, 6, 2, opoffs); return bitfield(temp, 1) | (bitfield(temp, 0) << 1); }
+ uint32_t op_total_level(uint32_t opoffs) const { return byte(0x40, 0, 6, opoffs); }
+ uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x60, 4, 4, opoffs); }
+ uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0x60, 0, 4, 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_waveform(uint32_t opoffs) const { return waveform_enable() ? byte(0xe0, 0, newflag() ? 3 : 2, opoffs) : 0; }
+
+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);
+ }
+
+ // helper to determine if the this channel is an active rhythm channel
+ bool is_rhythm(uint32_t choffs) const
+ {
+ return rhythm_enable() && (choffs >= 6 && choffs <= 8);
+ }
+
+ // internal state
+ uint16_t m_lfo_am_counter; // LFO AM counter
+ uint16_t m_lfo_pm_counter; // LFO PM counter
+ uint32_t m_noise_lfsr; // noise LFSR state
+ 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 opl_registers = opl_registers_base<1>;
+using opl2_registers = opl_registers_base<2>;
+using opl3_registers = opl_registers_base<3>;
+using opl4_registers = opl_registers_base<4>;
+
+
+
+// ======================> opll_registers
+
+//
+// OPLL register map:
+//
+// System-wide registers:
+// 0E --x----- Rhythm enable
+// ---x---- Bass drum key on
+// ----x--- Snare drum key on
+// -----x-- Tom key on
+// ------x- Top cymbal key on
+// -------x High hat key on
+// 0F xxxxxxxx Test register
+//
+// Per-channel registers (channel in address bits 0-3)
+// 10-18 xxxxxxxx F-number (low 8 bits)
+// 20-28 --x----- Sustain on
+// ---x---- Key on
+// --- xxx- Block (octvate, 0-7)
+// -------x F-number (high bit)
+// 30-38 xxxx---- Instrument selection
+// ----xxxx Volume
+//
+// User instrument registers (for carrier, modulator operators)
+// 00-01 x------- AM enable
+// -x------ PM enable (VIB)
+// --x----- EG type
+// ---x---- Key scale rate
+// ----xxxx Multiple value (0-15)
+// 02 xx------ Key scale level (carrier, 0-3)
+// --xxxxxx Total level (modulator, 0-63)
+// 03 xx------ Key scale level (modulator, 0-3)
+// ---x---- Rectified wave (carrier)
+// ----x--- Rectified wave (modulator)
+// -----xxx Feedback level for operator 1 (0-7)
+// 04-05 xxxx---- Attack rate (0-15)
+// ----xxxx Decay rate (0-15)
+// 06-07 xxxx---- Sustain level (0-15)
+// ----xxxx Release rate (0-15)
+//
+// Internal (fake) registers:
+// 40-48 xxxxxxxx Current instrument base address
+// 4E-5F xxxxxxxx Current instrument base address + operator slot (0/1)
+// 70-FF xxxxxxxx Data for instruments (1-16 plus 3 drums)
+//
+
+class opll_registers : public fm_registers_base
+{
+public:
+ static constexpr uint32_t OUTPUTS = 2;
+ static constexpr uint32_t CHANNELS = 9;
+ static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
+ static constexpr uint32_t OPERATORS = CHANNELS * 2;
+ static constexpr uint32_t WAVEFORMS = 2;
+ static constexpr uint32_t REGISTERS = 0x40;
+ static constexpr uint32_t REG_MODE = 0x3f;
+ static constexpr uint32_t DEFAULT_PRESCALE = 4;
+ static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
+ static constexpr uint32_t CSM_TRIGGER_MASK = 0;
+ static constexpr bool EG_HAS_DEPRESS = true;
+ static constexpr bool MODULATOR_DELAY = true;
+ static constexpr uint8_t STATUS_TIMERA = 0;
+ static constexpr uint8_t STATUS_TIMERB = 0;
+ static constexpr uint8_t STATUS_BUSY = 0;
+ static constexpr uint8_t STATUS_IRQ = 0;
+
+ // OPLL-specific constants
+ static constexpr uint32_t INSTDATA_SIZE = 0x90;
+
+ // constructor
+ opll_registers();
+
+ // 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);
+ return chnum;
+ }
+
+ // map operator number to register offset
+ static constexpr uint32_t operator_offset(uint32_t opnum)
+ {
+ assert(opnum < OPERATORS);
+ return opnum;
+ }
+
+ // 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_am_counter = m_lfo_pm_counter = 0; }
+
+ // return the AM offset from LFO for the given channel
+ // on OPL this is just a fixed value
+ uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
+
+ // return LFO/noise states
+ uint32_t noise_state() const { return m_noise_lfsr >> 23; }
+
+ // 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);
+
+ // set the instrument data
+ void set_instrument_data(uint8_t const *data)
+ {
+ std::copy_n(data, INSTDATA_SIZE, &m_instdata[0]);
+ }
+
+ // system-wide registers
+ uint32_t rhythm_enable() const { return byte(0x0e, 5, 1); }
+ uint32_t rhythm_keyon() const { return byte(0x0e, 4, 0); }
+ uint32_t test() const { return byte(0x0f, 0, 8); }
+ uint32_t waveform_enable() const { return 1; }
+ uint32_t timer_a_value() const { return 0; }
+ uint32_t timer_b_value() const { return 0; }
+ uint32_t status_mask() const { return 0; }
+ uint32_t irq_reset() const { return 0; }
+ uint32_t reset_timer_b() const { return 0; }
+ uint32_t reset_timer_a() const { return 0; }
+ uint32_t enable_timer_b() const { return 0; }
+ uint32_t enable_timer_a() const { return 0; }
+ uint32_t load_timer_b() const { return 0; }
+ uint32_t load_timer_a() const { return 0; }
+ uint32_t csm() const { return 0; }
+
+ // per-channel registers
+ uint32_t ch_block_freq(uint32_t choffs) const { return word(0x20, 0, 4, 0x10, 0, 8, choffs); }
+ uint32_t ch_sustain(uint32_t choffs) const { return byte(0x20, 5, 1, choffs); }
+ uint32_t ch_total_level(uint32_t choffs) const { return instchbyte(0x02, 0, 6, choffs); }
+ uint32_t ch_feedback(uint32_t choffs) const { return instchbyte(0x03, 0, 3, choffs); }
+ uint32_t ch_algorithm(uint32_t choffs) const { return 0; }
+ uint32_t ch_instrument(uint32_t choffs) const { return byte(0x30, 4, 4, choffs); }
+ uint32_t ch_output_any(uint32_t choffs) const { return 1; }
+ uint32_t ch_output_0(uint32_t choffs) const { return !is_rhythm(choffs); }
+ uint32_t ch_output_1(uint32_t choffs) const { return is_rhythm(choffs); }
+ uint32_t ch_output_2(uint32_t choffs) const { return 0; }
+ uint32_t ch_output_3(uint32_t choffs) const { return 0; }
+
+ // per-operator registers
+ uint32_t op_lfo_am_enable(uint32_t opoffs) const { return instopbyte(0x00, 7, 1, opoffs); }
+ uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return instopbyte(0x00, 6, 1, opoffs); }
+ uint32_t op_eg_sustain(uint32_t opoffs) const { return instopbyte(0x00, 5, 1, opoffs); }
+ uint32_t op_ksr(uint32_t opoffs) const { return instopbyte(0x00, 4, 1, opoffs); }
+ uint32_t op_multiple(uint32_t opoffs) const { return instopbyte(0x00, 0, 4, opoffs); }
+ uint32_t op_ksl(uint32_t opoffs) const { return instopbyte(0x02, 6, 2, opoffs); }
+ uint32_t op_waveform(uint32_t opoffs) const { return instchbyte(0x03, 3 + bitfield(opoffs, 0), 1, opoffs >> 1); }
+ uint32_t op_attack_rate(uint32_t opoffs) const { return instopbyte(0x04, 4, 4, opoffs); }
+ uint32_t op_decay_rate(uint32_t opoffs) const { return instopbyte(0x04, 0, 4, opoffs); }
+ uint32_t op_sustain_level(uint32_t opoffs) const { return instopbyte(0x06, 4, 4, opoffs); }
+ uint32_t op_release_rate(uint32_t opoffs) const { return instopbyte(0x06, 0, 4, opoffs); }
+ uint32_t op_volume(uint32_t opoffs) const { return byte(0x30, 4 * bitfield(~opoffs, 0), 4, opoffs >> 1); }
+
+private:
+ // 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);
+ }
+
+ // helpers to read from instrument channel/operator data
+ uint32_t instchbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t choffs) const { return bitfield(m_chinst[choffs][offset], start, count); }
+ uint32_t instopbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t opoffs) const { return bitfield(m_opinst[opoffs][offset], start, count); }
+
+ // helper to determine if the this channel is an active rhythm channel
+ bool is_rhythm(uint32_t choffs) const
+ {
+ return rhythm_enable() && choffs >= 6;
+ }
+
+ // internal state
+ uint16_t m_lfo_am_counter; // LFO AM counter
+ uint16_t m_lfo_pm_counter; // LFO PM counter
+ uint32_t m_noise_lfsr; // noise LFSR state
+ uint8_t m_lfo_am; // current LFO AM value
+ uint8_t const *m_chinst[CHANNELS]; // pointer to instrument data for each channel
+ uint8_t const *m_opinst[OPERATORS]; // pointer to instrument data for each operator
+ uint8_t m_regdata[REGISTERS]; // register data
+ uint8_t m_instdata[INSTDATA_SIZE]; // instrument data
+ uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
+};
+
+
+
+//*********************************************************
+// OPL IMPLEMENTATION CLASSES
+//*********************************************************
+
+// ======================> ym3526
+
+class ym3526
+{
+public:
+ using fm_engine = fm_engine_base<opl_registers>;
+ using output_data = fm_engine::output_data;
+ static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
+
+ // constructor
+ ym3526(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(uint32_t offset, uint8_t data);
+
+ // generate samples of sound
+ void generate(output_data *output, uint32_t numsamples = 1);
+protected:
+ // internal state
+ uint8_t m_address; // address register
+ fm_engine m_fm; // core FM engine
+};
+
+
+// ======================> y8950
+
+class y8950
+{
+public:
+ using fm_engine = fm_engine_base<opl_registers>;
+ using output_data = fm_engine::output_data;
+ static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
+
+ static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x01;
+ static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08;
+ static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x10;
+ static constexpr uint8_t ALL_IRQS = STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_EOS | fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB;
+
+ // constructor
+ y8950(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_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 samples of sound
+ void generate(output_data *output, uint32_t numsamples = 1);
+
+protected:
+ // internal state
+ uint8_t m_address; // address register
+ uint8_t m_io_ddr; // data direction register for I/O
+ fm_engine m_fm; // core FM engine
+ adpcm_b_engine m_adpcm_b; // ADPCM-B engine
+};
+
+
+
+//*********************************************************
+// OPL2 IMPLEMENTATION CLASSES
+//*********************************************************
+
+// ======================> ym3812
+
+class ym3812
+{
+public:
+ using fm_engine = fm_engine_base<opl2_registers>;
+ using output_data = fm_engine::output_data;
+ static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
+
+ // constructor
+ ym3812(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(uint32_t offset, uint8_t data);
+
+ // generate samples of sound
+ void generate(output_data *output, uint32_t numsamples = 1);
+
+protected:
+ // internal state
+ uint8_t m_address; // address register
+ fm_engine m_fm; // core FM engine
+};
+
+
+
+//*********************************************************
+// OPL3 IMPLEMENTATION CLASSES
+//*********************************************************
+
+// ======================> ymf262
+
+class ymf262
+{
+public:
+ using fm_engine = fm_engine_base<opl3_registers>;
+ using output_data = fm_engine::output_data;
+ static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
+
+ // constructor
+ ymf262(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(uint32_t offset, uint8_t data);
+
+ // generate samples of sound
+ void generate(output_data *output, uint32_t numsamples = 1);
+
+protected:
+ // internal state
+ uint16_t m_address; // address register
+ fm_engine m_fm; // core FM engine
+};
+
+
+// ======================> ymf289b
+
+class ymf289b
+{
+ static constexpr uint8_t STATUS_BUSY_FLAGS = 0x05;
+
+public:
+ using fm_engine = fm_engine_base<opl3_registers>;
+ using output_data = fm_engine::output_data;
+ static constexpr uint32_t OUTPUTS = 2;
+
+ // constructor
+ ymf289b(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_data();
+ 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(uint32_t offset, uint8_t data);
+
+ // generate samples of sound
+ void generate(output_data *output, uint32_t numsamples = 1);
+
+protected:
+ // internal helpers
+ bool ymf289b_mode() { return ((m_fm.regs().read(0x105) & 0x04) != 0); }
+
+ // internal state
+ uint16_t m_address; // address register
+ fm_engine m_fm; // core FM engine
+};
+
+
+
+//*********************************************************
+// OPL4 IMPLEMENTATION CLASSES
+//*********************************************************
+
+// ======================> ymf278b
+
+class ymf278b
+{
+ // Using the nominal datasheet frequency of 33.868MHz, the output of the
+ // chip will be clock/768 = 44.1kHz. However, the FM engine is clocked
+ // internally at clock/(19*36), or 49.515kHz, so the FM output needs to
+ // be downsampled. We treat this as needing to clock the FM engine an
+ // extra tick every few samples. The exact ratio is 768/(19*36) or
+ // 768/684 = 192/171. So if we always clock the FM once, we'll have
+ // 192/171 - 1 = 21/171 left. Thus we count 21 for each sample and when
+ // it gets above 171, we tick an extra time.
+ static constexpr uint32_t FM_EXTRA_SAMPLE_THRESH = 171;
+ static constexpr uint32_t FM_EXTRA_SAMPLE_STEP = 192 - FM_EXTRA_SAMPLE_THRESH;
+
+public:
+ using fm_engine = fm_engine_base<opl4_registers>;
+ static constexpr uint32_t OUTPUTS = 6;
+ using output_data = ymfm_output<OUTPUTS>;
+
+ static constexpr uint8_t STATUS_BUSY = 0x01;
+ static constexpr uint8_t STATUS_LD = 0x02;
+
+ // constructor
+ ymf278b(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 input_clock / 768; }
+ void invalidate_caches() { m_fm.invalidate_caches(); }
+
+ // read access
+ uint8_t read_status();
+ uint8_t read_data_pcm();
+ 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_address_pcm(uint8_t data);
+ void write_data_pcm(uint8_t data);
+ void write(uint32_t offset, uint8_t data);
+
+ // generate samples of sound
+ void generate(output_data *output, uint32_t numsamples = 1);
+
+protected:
+ // internal state
+ uint16_t m_address; // address register
+ uint32_t m_fm_pos; // FM resampling position
+ uint32_t m_load_remaining; // how many more samples until LD flag clears
+ bool m_next_status_id; // flag to track which status ID to return
+ fm_engine m_fm; // core FM engine
+ pcm_engine m_pcm; // core PCM engine
+};
+
+
+
+//*********************************************************
+// OPLL IMPLEMENTATION CLASSES
+//*********************************************************
+
+// ======================> opll_base
+
+class opll_base
+{
+public:
+ using fm_engine = fm_engine_base<opll_registers>;
+ using output_data = fm_engine::output_data;
+ static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
+
+ // constructor
+ opll_base(ymfm_interface &intf, uint8_t const *data);
+
+ // configuration
+ void set_instrument_data(uint8_t const *data) { m_fm.regs().set_instrument_data(data); }
+
+ // 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 -- doesn't really have any, but provide these for consistency
+ uint8_t read_status() { return 0x00; }
+ uint8_t read(uint32_t offset) { return 0x00; }
+
+ // write access
+ void write_address(uint8_t data);
+ void write_data(uint8_t data);
+ void write(uint32_t offset, uint8_t data);
+
+ // generate samples of sound
+ void generate(output_data *output, uint32_t numsamples = 1);
+
+protected:
+ // internal state
+ uint8_t m_address; // address register
+ fm_engine m_fm; // core FM engine
+};
+
+
+// ======================> ym2413
+
+class ym2413 : public opll_base
+{
+public:
+ // constructor
+ ym2413(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
+
+private:
+ // internal state
+ static uint8_t const s_default_instruments[];
+};
+
+
+// ======================> ym2413
+
+class ym2423 : public opll_base
+{
+public:
+ // constructor
+ ym2423(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
+
+private:
+ // internal state
+ static uint8_t const s_default_instruments[];
+};
+
+
+// ======================> ymf281
+
+class ymf281 : public opll_base
+{
+public:
+ // constructor
+ ymf281(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
+
+private:
+ // internal state
+ static uint8_t const s_default_instruments[];
+};
+
+
+// ======================> ds1001
+
+class ds1001 : public opll_base
+{
+public:
+ // constructor
+ ds1001(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
+
+private:
+ // internal state
+ static uint8_t const s_default_instruments[];
+};
+
+}
+
+#endif // YMFM_OPL_H
diff --git a/3rdparty/ymfm/src/ymfm_opm.cpp b/3rdparty/ymfm/src/ymfm_opm.cpp
new file mode 100644
index 00000000000..03f54fb9039
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_opm.cpp
@@ -0,0 +1,539 @@
+// 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.
+
+#include "ymfm_opm.h"
+#include "ymfm_fm.ipp"
+
+namespace ymfm
+{
+
+//*********************************************************
+// OPM REGISTERS
+//*********************************************************
+
+//-------------------------------------------------
+// opm_registers - constructor
+//-------------------------------------------------
+
+opm_registers::opm_registers() :
+ m_lfo_counter(0),
+ m_noise_lfsr(1),
+ m_noise_counter(0),
+ m_noise_state(0),
+ m_noise_lfo(0),
+ m_lfo_am(0)
+{
+ // create the waveforms
+ for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
+ m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
+
+ // create the LFO waveforms; AM in the low 8 bits, PM in the upper 8
+ // waveforms are adjusted to match the pictures in the application manual
+ for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++)
+ {
+ // waveform 0 is a sawtooth
+ uint8_t am = index ^ 0xff;
+ uint8_t pm = index;
+ m_lfo_waveform[0][index] = am | (pm << 8);
+
+ // waveform 1 is a square wave
+ am = bitfield(index, 7) ? 0 : 0xff;
+ pm = am ^ 0x80;
+ m_lfo_waveform[1][index] = am | (pm << 8);
+
+ // waveform 2 is a triangle wave
+ am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1);
+ pm = bitfield(index, 6) ? am : ~am;
+ m_lfo_waveform[2][index] = am | (pm << 8);
+
+ // waveform 3 is noise; it is filled in dynamically
+ m_lfo_waveform[3][index] = 0;
+ }
+}
+
+
+//-------------------------------------------------
+// reset - reset to initial state
+//-------------------------------------------------
+
+void opm_registers::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+
+ // enable output on both channels by default
+ m_regdata[0x20] = m_regdata[0x21] = m_regdata[0x22] = m_regdata[0x23] = 0xc0;
+ m_regdata[0x24] = m_regdata[0x25] = m_regdata[0x26] = m_regdata[0x27] = 0xc0;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void opm_registers::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_lfo_counter);
+ state.save_restore(m_lfo_am);
+ state.save_restore(m_noise_lfsr);
+ state.save_restore(m_noise_counter);
+ state.save_restore(m_noise_state);
+ state.save_restore(m_noise_lfo);
+ state.save_restore(m_regdata);
+}
+
+
+//-------------------------------------------------
+// operator_map - return an array of operator
+// indices for each channel; for OPM this is fixed
+//-------------------------------------------------
+
+void opm_registers::operator_map(operator_mapping &dest) const
+{
+ // Note that the channel index order is 0,2,1,3, so we bitswap the index.
+ //
+ // This is because the order in the map is:
+ // carrier 1, carrier 2, modulator 1, modulator 2
+ //
+ // But when wiring up the connections, the more natural order is:
+ // carrier 1, modulator 1, carrier 2, modulator 2
+ static const operator_mapping s_fixed_map =
+ { {
+ operator_list( 0, 16, 8, 24 ), // Channel 0 operators
+ operator_list( 1, 17, 9, 25 ), // Channel 1 operators
+ operator_list( 2, 18, 10, 26 ), // Channel 2 operators
+ operator_list( 3, 19, 11, 27 ), // Channel 3 operators
+ operator_list( 4, 20, 12, 28 ), // Channel 4 operators
+ operator_list( 5, 21, 13, 29 ), // Channel 5 operators
+ operator_list( 6, 22, 14, 30 ), // Channel 6 operators
+ operator_list( 7, 23, 15, 31 ), // Channel 7 operators
+ } };
+ dest = s_fixed_map;
+}
+
+
+//-------------------------------------------------
+// write - handle writes to the register array
+//-------------------------------------------------
+
+bool opm_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
+{
+ assert(index < REGISTERS);
+
+ // LFO AM/PM depth are written to the same register (0x19);
+ // redirect the PM depth to an unused neighbor (0x1a)
+ if (index == 0x19)
+ m_regdata[index + bitfield(data, 7)] = data;
+ else if (index != 0x1a)
+ m_regdata[index] = data;
+
+ // handle writes to the key on index
+ if (index == 0x08)
+ {
+ channel = bitfield(data, 0, 3);
+ opmask = bitfield(data, 3, 4);
+ return true;
+ }
+ return false;
+}
+
+
+//-------------------------------------------------
+// clock_noise_and_lfo - clock the noise and LFO,
+// handling clock division, depth, and waveform
+// computations
+//-------------------------------------------------
+
+int32_t opm_registers::clock_noise_and_lfo()
+{
+ // base noise frequency is measured at 2x 1/2 FM frequency; this
+ // means each tick counts as two steps against the noise counter
+ uint32_t freq = noise_frequency();
+ for (int rep = 0; rep < 2; rep++)
+ {
+ // evidence seems to suggest the LFSR is clocked continually and just
+ // sampled at the noise frequency for output purposes; note that the
+ // low 8 bits are the most recent 8 bits of history while bits 8-24
+ // contain the 17 bit LFSR state
+ m_noise_lfsr <<= 1;
+ m_noise_lfsr |= bitfield(m_noise_lfsr, 17) ^ bitfield(m_noise_lfsr, 14) ^ 1;
+
+ // compare against the frequency and latch when we exceed it
+ if (m_noise_counter++ >= freq)
+ {
+ m_noise_counter = 0;
+ m_noise_state = bitfield(m_noise_lfsr, 17);
+ }
+ }
+
+ // treat the rate as a 4.4 floating-point step value with implied
+ // leading 1; this matches exactly the frequencies in the application
+ // manual, though it might not be implemented exactly this way on chip
+ uint32_t rate = lfo_rate();
+ m_lfo_counter += (0x10 | bitfield(rate, 0, 4)) << bitfield(rate, 4, 4);
+
+ // bit 1 of the test register is officially undocumented but has been
+ // discovered to hold the LFO in reset while active
+ if (lfo_reset())
+ m_lfo_counter = 0;
+
+ // now pull out the non-fractional LFO value
+ uint32_t lfo = bitfield(m_lfo_counter, 22, 8);
+
+ // fill in the noise entry 1 ahead of our current position; this
+ // ensures the current value remains stable for a full LFO clock
+ // and effectively latches the running value when the LFO advances
+ uint32_t lfo_noise = bitfield(m_noise_lfsr, 17, 8);
+ m_lfo_waveform[3][(lfo + 1) & 0xff] = lfo_noise | (lfo_noise << 8);
+
+ // fetch the AM/PM values based on the waveform; AM is unsigned and
+ // encoded in the low 8 bits, while PM signed and encoded in the upper
+ // 8 bits
+ int32_t ampm = m_lfo_waveform[lfo_waveform()][lfo];
+
+ // apply depth to the AM value and store for later
+ m_lfo_am = ((ampm & 0xff) * lfo_am_depth()) >> 7;
+
+ // apply depth to the PM value and return it
+ return ((ampm >> 8) * int32_t(lfo_pm_depth())) >> 7;
+}
+
+
+//-------------------------------------------------
+// lfo_am_offset - return the AM offset from LFO
+// for the given channel
+//-------------------------------------------------
+
+uint32_t opm_registers::lfo_am_offset(uint32_t choffs) const
+{
+ // OPM maps AM quite differently from OPN
+
+ // shift value for AM sensitivity is [*, 0, 1, 2],
+ // mapping to values of [0, 23.9, 47.8, and 95.6dB]
+ uint32_t am_sensitivity = ch_lfo_am_sens(choffs);
+ if (am_sensitivity == 0)
+ return 0;
+
+ // QUESTION: see OPN note below for the dB range mapping; it applies
+ // here as well
+
+ // raw LFO AM value on OPM is 0-FF, which is already a factor of 2
+ // larger than the OPN below, putting our staring point at 2x theirs;
+ // this works out since our minimum is 2x their maximum
+ return m_lfo_am << (am_sensitivity - 1);
+}
+
+
+//-------------------------------------------------
+// cache_operator_data - fill the operator cache
+// with prefetched data
+//-------------------------------------------------
+
+void opm_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
+{
+ // set up the easy stuff
+ cache.waveform = &m_waveform[0][0];
+
+ // get frequency from the channel
+ uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
+
+ // compute the keycode: block_freq is:
+ //
+ // BBBCCCCFFFFFF
+ // ^^^^^
+ //
+ // the 5-bit keycode is just the top 5 bits (block + top 2 bits
+ // of the key code)
+ uint32_t keycode = bitfield(block_freq, 8, 5);
+
+ // detune adjustment
+ cache.detune = detune_adjustment(op_detune(opoffs), keycode);
+
+ // multiple value, as an x.1 value (0 means 0.5)
+ cache.multiple = op_multiple(opoffs) * 2;
+ if (cache.multiple == 0)
+ cache.multiple = 1;
+
+ // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
+ // block_freq, detune, and multiple, so compute it after we've done those
+ if (lfo_pm_depth() == 0 || ch_lfo_pm_sens(choffs) == 0)
+ cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
+ else
+ cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
+
+ // total level, scaled by 8
+ cache.total_level = op_total_level(opoffs) << 3;
+
+ // 4-bit sustain level, but 15 means 31 so effectively 5 bits
+ cache.eg_sustain = op_sustain_level(opoffs);
+ cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
+ cache.eg_sustain <<= 5;
+
+ // determine KSR adjustment for enevlope rates
+ uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3);
+ cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval);
+}
+
+
+//-------------------------------------------------
+// compute_phase_step - compute the phase step
+//-------------------------------------------------
+
+uint32_t opm_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
+{
+ // OPM logic is rather unique here, due to extra detune
+ // and the use of key codes (not to be confused with keycode)
+
+ // start with coarse detune delta; table uses cents value from
+ // manual, converted into 1/64ths
+ static const int16_t s_detune2_delta[4] = { 0, (600*64+50)/100, (781*64+50)/100, (950*64+50)/100 };
+ int32_t delta = s_detune2_delta[op_detune2(opoffs)];
+
+ // add in the PM delta
+ uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs);
+ if (pm_sensitivity != 0)
+ {
+ // raw PM value is -127..128 which is +/- 200 cents
+ // manual gives these magnitudes in cents:
+ // 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700
+ // this roughly corresponds to shifting the 200-cent value:
+ // 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2
+ if (pm_sensitivity < 6)
+ delta += lfo_raw_pm >> (6 - pm_sensitivity);
+ else
+ delta += uint32_t(lfo_raw_pm) << (pm_sensitivity - 5);
+ }
+
+ // apply delta and convert to a frequency number
+ uint32_t phase_step = opm_key_code_to_phase_step(cache.block_freq, delta);
+
+ // apply detune based on the keycode
+ phase_step += cache.detune;
+
+ // apply frequency multiplier (which is cached as an x.1 value)
+ return (phase_step * cache.multiple) >> 1;
+}
+
+
+//-------------------------------------------------
+// log_keyon - log a key-on event
+//-------------------------------------------------
+
+std::string opm_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
+{
+ uint32_t chnum = choffs;
+ uint32_t opnum = opoffs;
+
+ char buffer[256];
+ int end = 0;
+
+ end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt2=%u dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
+ chnum, opnum,
+ ch_block_freq(choffs),
+ op_detune2(opoffs),
+ op_detune(opoffs),
+ ch_feedback(choffs),
+ ch_algorithm(choffs),
+ op_multiple(opoffs),
+ op_total_level(opoffs),
+ op_ksr(opoffs),
+ op_attack_rate(opoffs),
+ op_decay_rate(opoffs),
+ op_sustain_rate(opoffs),
+ op_release_rate(opoffs),
+ op_sustain_level(opoffs),
+ ch_output_0(choffs) ? 'L' : '-',
+ ch_output_1(choffs) ? 'R' : '-');
+
+ bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
+ if (am)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth());
+ bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0);
+ if (pm)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
+ if (am || pm)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
+ if (noise_enable() && opoffs == 31)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " noise=1");
+
+ return buffer;
+}
+
+
+
+//*********************************************************
+// YM2151
+//*********************************************************
+
+//-------------------------------------------------
+// ym2151 - constructor
+//-------------------------------------------------
+
+ym2151::ym2151(ymfm_interface &intf, opm_variant variant) :
+ m_variant(variant),
+ m_address(0),
+ m_fm(intf)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ym2151::reset()
+{
+ // reset the engines
+ m_fm.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ym2151::save_restore(ymfm_saved_state &state)
+{
+ m_fm.save_restore(state);
+ state.save_restore(m_address);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ym2151::read_status()
+{
+ uint8_t result = m_fm.status();
+ if (m_fm.intf().ymfm_is_busy())
+ result |= fm_engine::STATUS_BUSY;
+ return result;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ym2151::read(uint32_t offset)
+{
+ uint8_t result = 0xff;
+ switch (offset & 1)
+ {
+ case 0: // data port (unused)
+ debug::log_unexpected_read_write("Unexpected read from YM2151 offset %d\n", offset & 3);
+ break;
+
+ case 1: // status port, YM2203 compatible
+ result = read_status();
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ym2151::write_address(uint8_t data)
+{
+ // just set the address
+ m_address = data;
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2151::write_data(uint8_t data)
+{
+ // write the FM register
+ m_fm.write(m_address, data);
+
+ // special cases
+ if (m_address == 0x1b)
+ {
+ // writes to register 0x1B send the upper 2 bits to the output lines
+ m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6);
+ }
+
+ // mark busy for a bit
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2151::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 1)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void ym2151::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // update the FM content; OPM is full 14-bit with no intermediate clipping
+ m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
+
+ // YM2151 uses an external DAC (YM3012) with mantissa/exponent format
+ // convert to 10.3 floating point value and back to simulate truncation
+ output->roundtrip_fp();
+ }
+}
+
+}
diff --git a/3rdparty/ymfm/src/ymfm_opm.h b/3rdparty/ymfm/src/ymfm_opm.h
new file mode 100644
index 00000000000..b126135d4ff
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_opm.h
@@ -0,0 +1,322 @@
+// 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_OPM_H
+#define YMFM_OPM_H
+
+#pragma once
+
+#include "ymfm.h"
+#include "ymfm_fm.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// REGISTER CLASSES
+//*********************************************************
+
+// ======================> opm_registers
+
+//
+// OPM register map:
+//
+// System-wide registers:
+// 01 xxxxxx-x Test register
+// ------x- LFO reset
+// 08 -x------ Key on/off operator 4
+// --x----- Key on/off operator 3
+// ---x---- Key on/off operator 2
+// ----x--- Key on/off operator 1
+// -----xxx Channel select
+// 0F x------- Noise enable
+// ---xxxxx Noise frequency
+// 10 xxxxxxxx Timer A value (upper 8 bits)
+// 11 ------xx Timer A value (lower 2 bits)
+// 12 xxxxxxxx Timer B value
+// 14 x------- CSM mode
+// --x----- Reset timer B
+// ---x---- Reset timer A
+// ----x--- Enable timer B
+// -----x-- Enable timer A
+// ------x- Load timer B
+// -------x Load timer A
+// 18 xxxxxxxx LFO frequency
+// 19 0xxxxxxx AM LFO depth
+// 1xxxxxxx PM LFO depth
+// 1B xx------ CT (2 output data lines)
+// ------xx LFO waveform
+//
+// Per-channel registers (channel in address bits 0-2)
+// 20-27 x------- Pan right
+// -x------ Pan left
+// --xxx--- Feedback level for operator 1 (0-7)
+// -----xxx Operator connection algorithm (0-7)
+// 28-2F -xxxxxxx Key code
+// 30-37 xxxxxx-- Key fraction
+// 38-3F -xxx---- LFO PM sensitivity
+// ------xx LFO AM shift
+//
+// Per-operator registers (channel in address bits 0-2, operator in bits 3-4)
+// 40-5F -xxx---- Detune value (0-7)
+// ----xxxx Multiple value (0-15)
+// 60-7F -xxxxxxx Total level (0-127)
+// 80-9F xx------ Key scale rate (0-3)
+// ---xxxxx Attack rate (0-31)
+// A0-BF x------- LFO AM enable
+// ---xxxxx Decay rate (0-31)
+// C0-DF xx------ Detune 2 value (0-3)
+// ---xxxxx Sustain rate (0-31)
+// E0-FF xxxx---- Sustain level (0-15)
+// ----xxxx Release rate (0-15)
+//
+// Internal (fake) registers:
+// 1A -xxxxxxx PM depth
+//
+
+class opm_registers : public fm_registers_base
+{
+ // LFO waveforms are 256 entries long
+ static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256;
+
+public:
+ // constants
+ static constexpr uint32_t OUTPUTS = 2;
+ static constexpr uint32_t CHANNELS = 8;
+ 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 = 0x100;
+ static constexpr uint32_t DEFAULT_PRESCALE = 2;
+ static constexpr uint32_t EG_CLOCK_DIVIDER = 3;
+ static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
+ static constexpr uint32_t REG_MODE = 0x14;
+ 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
+ opm_registers();
+
+ // 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);
+ return chnum;
+ }
+
+ // map operator number to register offset
+ static constexpr uint32_t operator_offset(uint32_t opnum)
+ {
+ assert(opnum < OPERATORS);
+ return opnum;
+ }
+
+ // return an array of operator indices for each channel
+ struct operator_mapping { uint32_t chan[CHANNELS]; };
+ void operator_map(operator_mapping &dest) const;
+
+ // 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();
+
+ // return the AM offset from LFO for the given channel
+ uint32_t lfo_am_offset(uint32_t choffs) const;
+
+ // return the current noise state, gated by the noise clock
+ uint32_t noise_state() const { return m_noise_state; }
+
+ // 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(0x01, 0, 8); }
+ uint32_t lfo_reset() const { return byte(0x01, 1, 1); }
+ uint32_t noise_frequency() const { return byte(0x0f, 0, 5) ^ 0x1f; }
+ uint32_t noise_enable() const { return byte(0x0f, 7, 1); }
+ uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); }
+ uint32_t timer_b_value() const { return byte(0x12, 0, 8); }
+ uint32_t csm() const { return byte(0x14, 7, 1); }
+ uint32_t reset_timer_b() const { return byte(0x14, 5, 1); }
+ uint32_t reset_timer_a() const { return byte(0x14, 4, 1); }
+ uint32_t enable_timer_b() const { return byte(0x14, 3, 1); }
+ uint32_t enable_timer_a() const { return byte(0x14, 2, 1); }
+ uint32_t load_timer_b() const { return byte(0x14, 1, 1); }
+ uint32_t load_timer_a() const { return byte(0x14, 0, 1); }
+ uint32_t lfo_rate() const { return byte(0x18, 0, 8); }
+ uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); }
+ uint32_t lfo_pm_depth() const { return byte(0x1a, 0, 7); }
+ uint32_t output_bits() const { return byte(0x1b, 6, 2); }
+ uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); }
+
+ // per-channel registers
+ uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 6, 2, choffs); }
+ uint32_t ch_output_0(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); }
+ uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs); }
+ 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_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); }
+ uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); }
+ uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); }
+ uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); }
+ uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); }
+
+ // per-operator registers
+ uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); }
+ uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); }
+ uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); }
+ uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); }
+ uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); }
+ uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); }
+ uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); }
+ uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); }
+ uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); }
+ uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); }
+ uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, 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
+ uint32_t m_noise_lfsr; // noise LFSR state
+ uint8_t m_noise_counter; // noise counter
+ uint8_t m_noise_state; // latched noise state
+ uint8_t m_noise_lfo; // latched LFO noise value
+ uint8_t m_lfo_am; // current LFO AM value
+ uint8_t m_regdata[REGISTERS]; // register data
+ int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8
+ uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
+};
+
+
+
+//*********************************************************
+// OPM IMPLEMENTATION CLASSES
+//*********************************************************
+
+// ======================> ym2151
+
+class ym2151
+{
+public:
+ using fm_engine = fm_engine_base<opm_registers>;
+ using output_data = fm_engine::output_data;
+ static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
+
+ // constructor
+ ym2151(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2151) { }
+
+ // 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(uint32_t offset, uint8_t data);
+
+ // generate one sample of sound
+ void generate(output_data *output, uint32_t numsamples = 1);
+
+protected:
+ // variants
+ enum opm_variant
+ {
+ VARIANT_YM2151,
+ VARIANT_YM2164
+ };
+
+ // internal constructor
+ ym2151(ymfm_interface &intf, opm_variant variant);
+
+ // internal state
+ opm_variant m_variant; // chip variant
+ uint8_t m_address; // address register
+ fm_engine m_fm; // core FM engine
+};
+
+
+
+//*********************************************************
+// OPP IMPLEMENTATION CLASSES
+//*********************************************************
+
+// ======================> ym2164
+
+// the YM2164 is almost 100% functionally identical to the YM2151, except
+// it apparently has some mystery registers in the 00-07 range, and timer
+// B's frequency is half that of the 2151
+class ym2164 : public ym2151
+{
+public:
+ // constructor
+ ym2164(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2164) { }
+};
+
+}
+
+
+#endif // YMFM_OPM_H
diff --git a/3rdparty/ymfm/src/ymfm_opn.cpp b/3rdparty/ymfm/src/ymfm_opn.cpp
new file mode 100644
index 00000000000..9a8f5e99feb
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_opn.cpp
@@ -0,0 +1,2485 @@
+// 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.
+
+#include "ymfm_opn.h"
+#include "ymfm_fm.ipp"
+
+namespace ymfm
+{
+
+//*********************************************************
+// OPN/OPNA REGISTERS
+//*********************************************************
+
+//-------------------------------------------------
+// opn_registers_base - constructor
+//-------------------------------------------------
+
+template<bool IsOpnA>
+opn_registers_base<IsOpnA>::opn_registers_base() :
+ m_lfo_counter(0),
+ m_lfo_am(0)
+{
+ // create the waveforms
+ for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
+ m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
+}
+
+
+//-------------------------------------------------
+// reset - reset to initial state
+//-------------------------------------------------
+
+template<bool IsOpnA>
+void opn_registers_base<IsOpnA>::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+ if (IsOpnA)
+ {
+ // enable output on both channels by default
+ m_regdata[0xb4] = m_regdata[0xb5] = m_regdata[0xb6] = 0xc0;
+ m_regdata[0x1b4] = m_regdata[0x1b5] = m_regdata[0x1b6] = 0xc0;
+ }
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+template<bool IsOpnA>
+void opn_registers_base<IsOpnA>::save_restore(ymfm_saved_state &state)
+{
+ if (IsOpnA)
+ {
+ state.save_restore(m_lfo_counter);
+ state.save_restore(m_lfo_am);
+ }
+ state.save_restore(m_regdata);
+}
+
+
+//-------------------------------------------------
+// operator_map - return an array of operator
+// indices for each channel; for OPN this is fixed
+//-------------------------------------------------
+
+template<>
+void opn_registers_base<false>::operator_map(operator_mapping &dest) const
+{
+ // Note that the channel index order is 0,2,1,3, so we bitswap the index.
+ //
+ // This is because the order in the map is:
+ // carrier 1, carrier 2, modulator 1, modulator 2
+ //
+ // But when wiring up the connections, the more natural order is:
+ // carrier 1, modulator 1, carrier 2, modulator 2
+ static const operator_mapping s_fixed_map =
+ { {
+ operator_list( 0, 6, 3, 9 ), // Channel 0 operators
+ operator_list( 1, 7, 4, 10 ), // Channel 1 operators
+ operator_list( 2, 8, 5, 11 ), // Channel 2 operators
+ } };
+ dest = s_fixed_map;
+}
+
+template<>
+void opn_registers_base<true>::operator_map(operator_mapping &dest) const
+{
+ // Note that the channel index order is 0,2,1,3, so we bitswap the index.
+ //
+ // This is because the order in the map is:
+ // carrier 1, carrier 2, modulator 1, modulator 2
+ //
+ // But when wiring up the connections, the more natural order is:
+ // carrier 1, modulator 1, carrier 2, modulator 2
+ static const operator_mapping s_fixed_map =
+ { {
+ operator_list( 0, 6, 3, 9 ), // Channel 0 operators
+ operator_list( 1, 7, 4, 10 ), // Channel 1 operators
+ operator_list( 2, 8, 5, 11 ), // Channel 2 operators
+ operator_list( 12, 18, 15, 21 ), // Channel 3 operators
+ operator_list( 13, 19, 16, 22 ), // Channel 4 operators
+ operator_list( 14, 20, 17, 23 ), // Channel 5 operators
+ } };
+ dest = s_fixed_map;
+}
+
+
+//-------------------------------------------------
+// write - handle writes to the register array
+//-------------------------------------------------
+
+template<bool IsOpnA>
+bool opn_registers_base<IsOpnA>::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
+{
+ assert(index < REGISTERS);
+
+ // writes in the 0xa0-af/0x1a0-af region are handled as latched pairs
+ // borrow unused registers 0xb8-bf as temporary holding locations
+ if ((index & 0xf0) == 0xa0)
+ {
+ if (bitfield(index, 0, 2) == 3)
+ return false;
+
+ uint32_t latchindex = 0xb8 | bitfield(index, 3);
+
+ // writes to the upper half just latch (only low 6 bits matter)
+ if (bitfield(index, 2))
+ m_regdata[latchindex] = data & 0x3f;
+
+ // writes to the lower half also apply said latch
+ else
+ {
+ m_regdata[index] = data;
+ m_regdata[index | 4] = m_regdata[latchindex];
+ }
+ return false;
+ }
+ else if ((index & 0xf8) == 0xb8)
+ {
+ // registers 0xb8-0xbf are used internally
+ return false;
+ }
+
+ // everything else is normal
+ m_regdata[index] = data;
+
+ // handle writes to the key on index
+ if (index == 0x28)
+ {
+ channel = bitfield(data, 0, 2);
+ if (channel == 3)
+ return false;
+ if (IsOpnA)
+ channel += bitfield(data, 2, 1) * 3;
+ opmask = bitfield(data, 4, 4);
+ return true;
+ }
+ return false;
+}
+
+
+//-------------------------------------------------
+// clock_noise_and_lfo - clock the noise and LFO,
+// handling clock division, depth, and waveform
+// computations
+//-------------------------------------------------
+
+template<bool IsOpnA>
+int32_t opn_registers_base<IsOpnA>::clock_noise_and_lfo()
+{
+ // OPN has no noise generation
+
+ // if LFO not enabled (not present on OPN), quick exit with 0s
+ if (!IsOpnA || !lfo_enable())
+ {
+ m_lfo_counter = 0;
+
+ // special case: if LFO is disabled on OPNA, it basically just keeps the counter
+ // at 0; since position 0 gives an AM value of 0x3f, it is important to reflect
+ // that here; for example, MegaDrive Venom plays some notes with LFO globally
+ // disabled but enabling LFO on the operators, and it expects this added attenutation
+ m_lfo_am = IsOpnA ? 0x3f : 0x00;
+ return 0;
+ }
+
+ // this table is based on converting the frequencies in the applications
+ // manual to clock dividers, based on the assumption of a 7-bit LFO value
+ static uint8_t const lfo_max_count[8] = { 109, 78, 72, 68, 63, 45, 9, 6 };
+ uint32_t subcount = uint8_t(m_lfo_counter++);
+
+ // when we cross the divider count, add enough to zero it and cause an
+ // increment at bit 8; the 7-bit value lives from bits 8-14
+ if (subcount >= lfo_max_count[lfo_rate()])
+ {
+ // note: to match the published values this should be 0x100 - subcount;
+ // however, tests on the hardware and nuked bear out an off-by-one
+ // error exists that causes the max LFO rate to be faster than published
+ m_lfo_counter += 0x101 - subcount;
+ }
+
+ // AM value is 7 bits, staring at bit 8; grab the low 6 directly
+ m_lfo_am = bitfield(m_lfo_counter, 8, 6);
+
+ // first half of the AM period (bit 6 == 0) is inverted
+ if (bitfield(m_lfo_counter, 8+6) == 0)
+ m_lfo_am ^= 0x3f;
+
+ // PM value is 5 bits, starting at bit 10; grab the low 3 directly
+ int32_t pm = bitfield(m_lfo_counter, 10, 3);
+
+ // PM is reflected based on bit 3
+ if (bitfield(m_lfo_counter, 10+3))
+ pm ^= 7;
+
+ // PM is negated based on bit 4
+ return bitfield(m_lfo_counter, 10+4) ? -pm : pm;
+}
+
+
+//-------------------------------------------------
+// lfo_am_offset - return the AM offset from LFO
+// for the given channel
+//-------------------------------------------------
+
+template<bool IsOpnA>
+uint32_t opn_registers_base<IsOpnA>::lfo_am_offset(uint32_t choffs) const
+{
+ // shift value for AM sensitivity is [7, 3, 1, 0],
+ // mapping to values of [0, 1.4, 5.9, and 11.8dB]
+ uint32_t am_shift = (1 << (ch_lfo_am_sens(choffs) ^ 3)) - 1;
+
+ // QUESTION: max sensitivity should give 11.8dB range, but this value
+ // is directly added to an x.8 attenuation value, which will only give
+ // 126/256 or ~4.9dB range -- what am I missing? The calculation below
+ // matches several other emulators, including the Nuked implemenation.
+
+ // raw LFO AM value on OPN is 0-3F, scale that up by a factor of 2
+ // (giving 7 bits) before applying the final shift
+ return (m_lfo_am << 1) >> am_shift;
+}
+
+
+//-------------------------------------------------
+// cache_operator_data - fill the operator cache
+// with prefetched data
+//-------------------------------------------------
+
+template<bool IsOpnA>
+void opn_registers_base<IsOpnA>::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
+{
+ // set up the easy stuff
+ cache.waveform = &m_waveform[0][0];
+
+ // get frequency from the channel
+ uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
+
+ // if multi-frequency mode is enabled and this is channel 2,
+ // fetch one of the special frequencies
+ if (multi_freq() && choffs == 2)
+ {
+ if (opoffs == 2)
+ block_freq = cache.block_freq = multi_block_freq(1);
+ else if (opoffs == 10)
+ block_freq = cache.block_freq = multi_block_freq(2);
+ else if (opoffs == 6)
+ block_freq = cache.block_freq = multi_block_freq(0);
+ }
+
+ // compute the keycode: block_freq is:
+ //
+ // BBBFFFFFFFFFFF
+ // ^^^^???
+ //
+ // the 5-bit keycode uses the top 4 bits plus a magic formula
+ // for the final bit
+ uint32_t keycode = bitfield(block_freq, 10, 4) << 1;
+
+ // lowest bit is determined by a mix of next lower FNUM bits
+ // according to this equation from the YM2608 manual:
+ //
+ // (F11 & (F10 | F9 | F8)) | (!F11 & F10 & F9 & F8)
+ //
+ // for speed, we just look it up in a 16-bit constant
+ keycode |= bitfield(0xfe80, bitfield(block_freq, 7, 4));
+
+ // detune adjustment
+ cache.detune = detune_adjustment(op_detune(opoffs), keycode);
+
+ // multiple value, as an x.1 value (0 means 0.5)
+ cache.multiple = op_multiple(opoffs) * 2;
+ if (cache.multiple == 0)
+ cache.multiple = 1;
+
+ // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
+ // block_freq, detune, and multiple, so compute it after we've done those
+ if (!IsOpnA || lfo_enable() == 0 || ch_lfo_pm_sens(choffs) == 0)
+ cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
+ else
+ cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
+
+ // total level, scaled by 8
+ cache.total_level = op_total_level(opoffs) << 3;
+
+ // 4-bit sustain level, but 15 means 31 so effectively 5 bits
+ cache.eg_sustain = op_sustain_level(opoffs);
+ cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
+ cache.eg_sustain <<= 5;
+
+ // determine KSR adjustment for enevlope rates
+ uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3);
+ cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval);
+}
+
+
+//-------------------------------------------------
+// compute_phase_step - compute the phase step
+//-------------------------------------------------
+
+template<bool IsOpnA>
+uint32_t opn_registers_base<IsOpnA>::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
+{
+ // OPN phase calculation has only a single detune parameter
+ // and uses FNUMs instead of keycodes
+
+ // extract frequency number (low 11 bits of block_freq)
+ uint32_t fnum = bitfield(cache.block_freq, 0, 11) << 1;
+
+ // if there's a non-zero PM sensitivity, compute the adjustment
+ uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs);
+ if (pm_sensitivity != 0)
+ {
+ // apply the phase adjustment based on the upper 7 bits
+ // of FNUM and the PM depth parameters
+ fnum += opn_lfo_pm_phase_adjustment(bitfield(cache.block_freq, 4, 7), pm_sensitivity, lfo_raw_pm);
+
+ // keep fnum to 12 bits
+ fnum &= 0xfff;
+ }
+
+ // apply block shift to compute phase step
+ uint32_t block = bitfield(cache.block_freq, 11, 3);
+ uint32_t phase_step = (fnum << block) >> 2;
+
+ // apply detune based on the keycode
+ phase_step += cache.detune;
+
+ // clamp to 17 bits in case detune overflows
+ // QUESTION: is this specific to the YM2612/3438?
+ phase_step &= 0x1ffff;
+
+ // apply frequency multiplier (which is cached as an x.1 value)
+ return (phase_step * cache.multiple) >> 1;
+}
+
+
+//-------------------------------------------------
+// log_keyon - log a key-on event
+//-------------------------------------------------
+
+template<bool IsOpnA>
+std::string opn_registers_base<IsOpnA>::log_keyon(uint32_t choffs, uint32_t opoffs)
+{
+ uint32_t chnum = (choffs & 3) + 3 * bitfield(choffs, 8);
+ uint32_t opnum = (opoffs & 15) - ((opoffs & 15) / 4) + 12 * bitfield(opoffs, 8);
+
+ uint32_t block_freq = ch_block_freq(choffs);
+ if (multi_freq() && choffs == 2)
+ {
+ if (opoffs == 2)
+ block_freq = multi_block_freq(1);
+ else if (opoffs == 10)
+ block_freq = multi_block_freq(2);
+ else if (opoffs == 6)
+ block_freq = multi_block_freq(0);
+ }
+
+ char buffer[256];
+ int end = 0;
+
+ end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X",
+ chnum, opnum,
+ block_freq,
+ op_detune(opoffs),
+ ch_feedback(choffs),
+ ch_algorithm(choffs),
+ op_multiple(opoffs),
+ op_total_level(opoffs),
+ op_ksr(opoffs),
+ op_attack_rate(opoffs),
+ op_decay_rate(opoffs),
+ op_sustain_rate(opoffs),
+ op_release_rate(opoffs),
+ op_sustain_level(opoffs));
+
+ if (OUTPUTS > 1)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " out=%c%c",
+ ch_output_0(choffs) ? 'L' : '-',
+ ch_output_1(choffs) ? 'R' : '-');
+ if (op_ssg_eg_enable(opoffs))
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " ssg=%X", op_ssg_eg_mode(opoffs));
+ bool am = (op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0);
+ if (am)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u", ch_lfo_am_sens(choffs));
+ bool pm = (ch_lfo_pm_sens(choffs) != 0);
+ if (pm)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u", ch_lfo_pm_sens(choffs));
+ if (am || pm)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X", lfo_rate());
+ if (multi_freq() && choffs == 2)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " multi=1");
+
+ return buffer;
+}
+
+
+
+//*********************************************************
+// SSG RESAMPLER
+//*********************************************************
+
+//-------------------------------------------------
+// add_last - helper to add the last computed
+// value to the sums, applying the given scale
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+void ssg_resampler<OutputType, FirstOutput, MixTo1>::add_last(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale)
+{
+ sum0 += m_last.data[0] * scale;
+ sum1 += m_last.data[1] * scale;
+ sum2 += m_last.data[2] * scale;
+}
+
+
+//-------------------------------------------------
+// clock_and_add - helper to clock a new value
+// and then add it to the sums, applying the
+// given scale
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+void ssg_resampler<OutputType, FirstOutput, MixTo1>::clock_and_add(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale)
+{
+ m_ssg.clock();
+ m_ssg.output(m_last);
+ add_last(sum0, sum1, sum2, scale);
+}
+
+
+//-------------------------------------------------
+// write_to_output - helper to write the sums to
+// the appropriate outputs, applying the given
+// divisor to the final result
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+void ssg_resampler<OutputType, FirstOutput, MixTo1>::write_to_output(OutputType *output, int32_t sum0, int32_t sum1, int32_t sum2, int32_t divisor)
+{
+ if (MixTo1)
+ {
+ // mixing to one, apply a 2/3 factor to prevent overflow
+ output->data[FirstOutput] = (sum0 + sum1 + sum2) * 2 / (3 * divisor);
+ }
+ else
+ {
+ // write three outputs in a row
+ output->data[FirstOutput + 0] = sum0 / divisor;
+ output->data[FirstOutput + 1] = sum1 / divisor;
+ output->data[FirstOutput + 2] = sum2 / divisor;
+ }
+
+ // track the sample index here
+ m_sampindex++;
+}
+
+
+//-------------------------------------------------
+// ssg_resampler - constructor
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+ssg_resampler<OutputType, FirstOutput, MixTo1>::ssg_resampler(ssg_engine &ssg) :
+ m_ssg(ssg),
+ m_sampindex(0),
+ m_resampler(&ssg_resampler::resample_nop)
+{
+ m_last.clear();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+void ssg_resampler<OutputType, FirstOutput, MixTo1>::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_sampindex);
+ state.save_restore(m_last.data);
+}
+
+
+//-------------------------------------------------
+// configure - configure a new ratio
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+void ssg_resampler<OutputType, FirstOutput, MixTo1>::configure(uint8_t outsamples, uint8_t srcsamples)
+{
+ switch (outsamples * 10 + srcsamples)
+ {
+ case 4*10 + 1: /* 4:1 */ m_resampler = &ssg_resampler::resample_n_1<4>; break;
+ case 2*10 + 1: /* 2:1 */ m_resampler = &ssg_resampler::resample_n_1<2>; break;
+ case 4*10 + 3: /* 4:3 */ m_resampler = &ssg_resampler::resample_4_3; break;
+ case 1*10 + 1: /* 1:1 */ m_resampler = &ssg_resampler::resample_n_1<1>; break;
+ case 2*10 + 3: /* 2:3 */ m_resampler = &ssg_resampler::resample_2_3; break;
+ case 1*10 + 3: /* 1:3 */ m_resampler = &ssg_resampler::resample_1_n<3>; break;
+ case 2*10 + 9: /* 2:9 */ m_resampler = &ssg_resampler::resample_2_9; break;
+ case 1*10 + 6: /* 1:6 */ m_resampler = &ssg_resampler::resample_1_n<6>; break;
+ case 0*10 + 0: /* 0:0 */ m_resampler = &ssg_resampler::resample_nop; break;
+ default: assert(false); break;
+ }
+}
+
+
+//-------------------------------------------------
+// resample_n_1 - resample SSG output to the
+// target at a rate of 1 SSG sample to every
+// n output sample
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+template<int Multiplier>
+void ssg_resampler<OutputType, FirstOutput, MixTo1>::resample_n_1(OutputType *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ if (m_sampindex % Multiplier == 0)
+ {
+ m_ssg.clock();
+ m_ssg.output(m_last);
+ }
+ write_to_output(output, m_last.data[0], m_last.data[1], m_last.data[2]);
+ }
+}
+
+
+//-------------------------------------------------
+// resample_1_n - resample SSG output to the
+// target at a rate of n SSG samples to every
+// 1 output sample
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+template<int Divisor>
+void ssg_resampler<OutputType, FirstOutput, MixTo1>::resample_1_n(OutputType *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ int32_t sum0 = 0, sum1 = 0, sum2 = 0;
+ for (int rep = 0; rep < Divisor; rep++)
+ clock_and_add(sum0, sum1, sum2);
+ write_to_output(output, sum0, sum1, sum2, Divisor);
+ }
+}
+
+
+//-------------------------------------------------
+// resample_2_9 - resample SSG output to the
+// target at a rate of 9 SSG samples to every
+// 2 output samples
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+void ssg_resampler<OutputType, FirstOutput, MixTo1>::resample_2_9(OutputType *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ int32_t sum0 = 0, sum1 = 0, sum2 = 0;
+ if (bitfield(m_sampindex, 0) != 0)
+ add_last(sum0, sum1, sum2, 1);
+ clock_and_add(sum0, sum1, sum2, 2);
+ clock_and_add(sum0, sum1, sum2, 2);
+ clock_and_add(sum0, sum1, sum2, 2);
+ clock_and_add(sum0, sum1, sum2, 2);
+ if (bitfield(m_sampindex, 0) == 0)
+ clock_and_add(sum0, sum1, sum2, 1);
+ write_to_output(output, sum0, sum1, sum2, 9);
+ }
+}
+
+
+//-------------------------------------------------
+// resample_2_3 - resample SSG output to the
+// target at a rate of 3 SSG samples to every
+// 2 output samples
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+void ssg_resampler<OutputType, FirstOutput, MixTo1>::resample_2_3(OutputType *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ int32_t sum0 = 0, sum1 = 0, sum2 = 0;
+ if (bitfield(m_sampindex, 0) == 0)
+ {
+ clock_and_add(sum0, sum1, sum2, 2);
+ clock_and_add(sum0, sum1, sum2, 1);
+ }
+ else
+ {
+ add_last(sum0, sum1, sum2, 1);
+ clock_and_add(sum0, sum1, sum2, 2);
+ }
+ write_to_output(output, sum0, sum1, sum2, 3);
+ }
+}
+
+
+//-------------------------------------------------
+// resample_4_3 - resample SSG output to the
+// target at a rate of 3 SSG samples to every
+// 4 output samples
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+void ssg_resampler<OutputType, FirstOutput, MixTo1>::resample_4_3(OutputType *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ int32_t sum0 = 0, sum1 = 0, sum2 = 0;
+ int32_t step = bitfield(m_sampindex, 0, 2);
+ add_last(sum0, sum1, sum2, step);
+ if (step != 3)
+ clock_and_add(sum0, sum1, sum2, 3 - step);
+ write_to_output(output, sum0, sum1, sum2, 3);
+ }
+}
+
+
+//-------------------------------------------------
+// resample_nop - no-op resampler
+//-------------------------------------------------
+
+template<typename OutputType, int FirstOutput, bool MixTo1>
+void ssg_resampler<OutputType, FirstOutput, MixTo1>::resample_nop(OutputType *output, uint32_t numsamples)
+{
+ // nothing to do except increment the sample index
+ m_sampindex += numsamples;
+}
+
+
+
+//*********************************************************
+// YM2203
+//*********************************************************
+
+//-------------------------------------------------
+// ym2203 - constructor
+//-------------------------------------------------
+
+ym2203::ym2203(ymfm_interface &intf) :
+ m_fidelity(OPN_FIDELITY_MAX),
+ m_address(0),
+ m_fm(intf),
+ m_ssg(intf),
+ m_ssg_resampler(m_ssg)
+{
+ m_last_fm.clear();
+ update_prescale(m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ym2203::reset()
+{
+ // reset the engines
+ m_fm.reset();
+ m_ssg.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ym2203::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ state.save_restore(m_last_fm.data);
+
+ m_fm.save_restore(state);
+ m_ssg.save_restore(state);
+ m_ssg_resampler.save_restore(state);
+
+ update_prescale(m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ym2203::read_status()
+{
+ uint8_t result = m_fm.status();
+ if (m_fm.intf().ymfm_is_busy())
+ result |= fm_engine::STATUS_BUSY;
+ return result;
+}
+
+
+//-------------------------------------------------
+// read_data - read the data register
+//-------------------------------------------------
+
+uint8_t ym2203::read_data()
+{
+ uint8_t result = 0;
+ if (m_address < 0x10)
+ {
+ // 00-0F: Read from SSG
+ result = m_ssg.read(m_address & 0x0f);
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ym2203::read(uint32_t offset)
+{
+ uint8_t result = 0xff;
+ switch (offset & 1)
+ {
+ case 0: // status port
+ result = read_status();
+ break;
+
+ case 1: // data port (only SSG)
+ result = read_data();
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ym2203::write_address(uint8_t data)
+{
+ // just set the address
+ m_address = data;
+
+ // special case: update the prescale
+ if (m_address >= 0x2d && m_address <= 0x2f)
+ {
+ // 2D-2F: prescaler select
+ if (m_address == 0x2d)
+ update_prescale(6);
+ else if (m_address == 0x2e && m_fm.clock_prescale() == 6)
+ update_prescale(3);
+ else if (m_address == 0x2f)
+ update_prescale(2);
+ }
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2203::write_data(uint8_t data)
+{
+ if (m_address < 0x10)
+ {
+ // 00-0F: write to SSG
+ m_ssg.write(m_address & 0x0f, data);
+ }
+ else
+ {
+ // 10-FF: write to FM
+ m_fm.write(m_address, data);
+ }
+
+ // mark busy for a bit
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2203::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 1)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void ym2203::generate(output_data *output, uint32_t numsamples)
+{
+ // FM output is just repeated the prescale number of times; note that
+ // 0 is a special 1.5 case
+ if (m_fm_samples_per_output != 0)
+ {
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0)
+ clock_fm();
+ output->data[0] = m_last_fm.data[0];
+ }
+ }
+ else
+ {
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ uint32_t step = (m_ssg_resampler.sampindex() + samp) % 3;
+ if (step == 0)
+ clock_fm();
+ output->data[0] = m_last_fm.data[0];
+ if (step == 1)
+ {
+ clock_fm();
+ output->data[0] = (output->data[0] + m_last_fm.data[0]) / 2;
+ }
+ }
+ }
+
+ // resample the SSG as configured
+ m_ssg_resampler.resample(output - numsamples, numsamples);
+}
+
+
+//-------------------------------------------------
+// update_prescale - update the prescale value,
+// recomputing derived values
+//-------------------------------------------------
+
+void ym2203::update_prescale(uint8_t prescale)
+{
+ // tell the FM engine
+ m_fm.set_clock_prescale(prescale);
+ m_ssg.prescale_changed();
+
+ // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum-----
+ // rate = clock/24 rate = clock/12 rate = clock/4
+ // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
+ // 6 3:1 2:3 6:1 4:3 18:1 4:1
+ // 3 1.5:1 1:3 3:1 2:3 9:1 2:1
+ // 2 1:1 1:6 2:1 1:3 6:1 1:1
+
+ // compute the number of FM samples per output sample, and select the
+ // resampler function
+ if (m_fidelity == OPN_FIDELITY_MIN)
+ {
+ switch (prescale)
+ {
+ default:
+ case 6: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break;
+ case 3: m_fm_samples_per_output = 0; m_ssg_resampler.configure(1, 3); break;
+ case 2: m_fm_samples_per_output = 1; m_ssg_resampler.configure(1, 6); break;
+ }
+ }
+ else if (m_fidelity == OPN_FIDELITY_MED)
+ {
+ switch (prescale)
+ {
+ default:
+ case 6: m_fm_samples_per_output = 6; m_ssg_resampler.configure(4, 3); break;
+ case 3: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break;
+ case 2: m_fm_samples_per_output = 2; m_ssg_resampler.configure(1, 3); break;
+ }
+ }
+ else
+ {
+ switch (prescale)
+ {
+ default:
+ case 6: m_fm_samples_per_output = 18; m_ssg_resampler.configure(4, 1); break;
+ case 3: m_fm_samples_per_output = 9; m_ssg_resampler.configure(2, 1); break;
+ case 2: m_fm_samples_per_output = 6; m_ssg_resampler.configure(1, 1); break;
+ }
+ }
+
+ // if overriding the SSG, override the configuration with the nop
+ // resampler to at least keep the sample index moving forward
+ if (m_ssg.overridden())
+ m_ssg_resampler.configure(0, 0);
+}
+
+
+//-------------------------------------------------
+// clock_fm - clock FM state
+//-------------------------------------------------
+
+void ym2203::clock_fm()
+{
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // update the FM content; OPN is full 14-bit with no intermediate clipping
+ m_fm.output(m_last_fm.clear(), 0, 32767, fm_engine::ALL_CHANNELS);
+
+ // convert to 10.3 floating point value for the DAC and back
+ m_last_fm.roundtrip_fp();
+}
+
+
+
+//*********************************************************
+// YM2608
+//*********************************************************
+
+//-------------------------------------------------
+// ym2608 - constructor
+//-------------------------------------------------
+
+ym2608::ym2608(ymfm_interface &intf) :
+ m_fidelity(OPN_FIDELITY_MAX),
+ m_address(0),
+ m_irq_enable(0x1f),
+ m_flag_control(0x1c),
+ m_fm(intf),
+ m_ssg(intf),
+ m_ssg_resampler(m_ssg),
+ m_adpcm_a(intf, 0),
+ m_adpcm_b(intf)
+{
+ m_last_fm.clear();
+ update_prescale(m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ym2608::reset()
+{
+ // reset the engines
+ m_fm.reset();
+ m_ssg.reset();
+ m_adpcm_a.reset();
+ m_adpcm_b.reset();
+
+ // configure ADPCM percussion sounds; these are present in an embedded ROM
+ m_adpcm_a.set_start_end(0, 0x0000, 0x01bf); // bass drum
+ m_adpcm_a.set_start_end(1, 0x01c0, 0x043f); // snare drum
+ m_adpcm_a.set_start_end(2, 0x0440, 0x1b7f); // top cymbal
+ m_adpcm_a.set_start_end(3, 0x1b80, 0x1cff); // high hat
+ m_adpcm_a.set_start_end(4, 0x1d00, 0x1f7f); // tom tom
+ m_adpcm_a.set_start_end(5, 0x1f80, 0x1fff); // rim shot
+
+ // initialize our special interrupt states, then read the upper status
+ // register, which updates the IRQs
+ m_irq_enable = 0x1f;
+ m_flag_control = 0x1c;
+ read_status_hi();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ym2608::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ state.save_restore(m_irq_enable);
+ state.save_restore(m_flag_control);
+ state.save_restore(m_last_fm.data);
+
+ m_fm.save_restore(state);
+ m_ssg.save_restore(state);
+ m_ssg_resampler.save_restore(state);
+ m_adpcm_a.save_restore(state);
+ m_adpcm_b.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ym2608::read_status()
+{
+ uint8_t result = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB);
+ if (m_fm.intf().ymfm_is_busy())
+ result |= fm_engine::STATUS_BUSY;
+ return result;
+}
+
+
+//-------------------------------------------------
+// read_data - read the data register
+//-------------------------------------------------
+
+uint8_t ym2608::read_data()
+{
+ uint8_t result = 0;
+ if (m_address < 0x10)
+ {
+ // 00-0F: Read from SSG
+ result = m_ssg.read(m_address & 0x0f);
+ }
+ else if (m_address == 0xff)
+ {
+ // FF: ID code
+ result = 1;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// read_status_hi - read the extended status
+// register
+//-------------------------------------------------
+
+uint8_t ym2608::read_status_hi()
+{
+ // fetch regular status
+ uint8_t status = m_fm.status() & ~(STATUS_ADPCM_B_EOS | STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_PLAYING);
+
+ // fetch ADPCM-B status, and merge in the bits
+ uint8_t adpcm_status = m_adpcm_b.status();
+ if ((adpcm_status & adpcm_b_channel::STATUS_EOS) != 0)
+ status |= STATUS_ADPCM_B_EOS;
+ if ((adpcm_status & adpcm_b_channel::STATUS_BRDY) != 0)
+ status |= STATUS_ADPCM_B_BRDY;
+ if ((adpcm_status & adpcm_b_channel::STATUS_PLAYING) != 0)
+ status |= STATUS_ADPCM_B_PLAYING;
+
+ // turn off any bits that have been requested to be masked
+ status &= ~(m_flag_control & 0x1f);
+
+ // update the status so that IRQs are propagated
+ m_fm.set_reset_status(status, ~status);
+
+ // merge in the busy flag
+ if (m_fm.intf().ymfm_is_busy())
+ status |= fm_engine::STATUS_BUSY;
+ return status;
+}
+
+
+//-------------------------------------------------
+// read_data_hi - read the upper data register
+//-------------------------------------------------
+
+uint8_t ym2608::read_data_hi()
+{
+ uint8_t result = 0;
+ if ((m_address & 0xff) < 0x10)
+ {
+ // 00-0F: Read from ADPCM-B
+ result = m_adpcm_b.read(m_address & 0x0f);
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ym2608::read(uint32_t offset)
+{
+ uint8_t result = 0;
+ switch (offset & 3)
+ {
+ case 0: // status port, YM2203 compatible
+ result = read_status();
+ break;
+
+ case 1: // data port (only SSG)
+ result = read_data();
+ break;
+
+ case 2: // status port, extended
+ result = read_status_hi();
+ break;
+
+ case 3: // ADPCM-B data
+ result = read_data_hi();
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ym2608::write_address(uint8_t data)
+{
+ // just set the address
+ m_address = data;
+
+ // special case: update the prescale
+ if (m_address >= 0x2d && m_address <= 0x2f)
+ {
+ // 2D-2F: prescaler select
+ if (m_address == 0x2d)
+ update_prescale(6);
+ else if (m_address == 0x2e && m_fm.clock_prescale() == 6)
+ update_prescale(3);
+ else if (m_address == 0x2f)
+ update_prescale(2);
+ }
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the data register
+//-------------------------------------------------
+
+void ym2608::write_data(uint8_t data)
+{
+ // ignore if paired with upper address
+ if (bitfield(m_address, 8))
+ return;
+
+ if (m_address < 0x10)
+ {
+ // 00-0F: write to SSG
+ m_ssg.write(m_address & 0x0f, data);
+ }
+ else if (m_address < 0x20)
+ {
+ // 10-1F: write to ADPCM-A
+ m_adpcm_a.write(m_address & 0x0f, data);
+ }
+ else if (m_address == 0x29)
+ {
+ // 29: special IRQ mask register
+ m_irq_enable = data;
+ m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x1f);
+ }
+ else
+ {
+ // 20-28, 2A-FF: write to FM
+ m_fm.write(m_address, data);
+ }
+
+ // mark busy for a bit
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// write_address_hi - handle a write to the upper
+// address register
+//-------------------------------------------------
+
+void ym2608::write_address_hi(uint8_t data)
+{
+ // just set the address
+ m_address = 0x100 | data;
+}
+
+
+//-------------------------------------------------
+// write_data_hi - handle a write to the upper
+// data register
+//-------------------------------------------------
+
+void ym2608::write_data_hi(uint8_t data)
+{
+ // ignore if paired with upper address
+ if (!bitfield(m_address, 8))
+ return;
+
+ if (m_address < 0x110)
+ {
+ // 100-10F: write to ADPCM-B
+ m_adpcm_b.write(m_address & 0x0f, data);
+ }
+ else if (m_address == 0x110)
+ {
+ // 110: IRQ flag control
+ if (bitfield(data, 7))
+ m_fm.set_reset_status(0, 0xff);
+ else
+ {
+ m_flag_control = data;
+ m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x1f);
+ }
+ }
+ else
+ {
+ // 111-1FF: write to FM
+ m_fm.write(m_address, data);
+ }
+
+ // mark busy for a bit
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2608::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 3)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+
+ case 2: // upper address port
+ write_address_hi(data);
+ break;
+
+ case 3: // upper data port
+ write_data_hi(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void ym2608::generate(output_data *output, uint32_t numsamples)
+{
+ // FM output is just repeated the prescale number of times; note that
+ // 0 is a special 1.5 case
+ if (m_fm_samples_per_output != 0)
+ {
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0)
+ clock_fm_and_adpcm();
+ output->data[0] = m_last_fm.data[0];
+ output->data[1] = m_last_fm.data[1];
+ }
+ }
+ else
+ {
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ uint32_t step = (m_ssg_resampler.sampindex() + samp) % 3;
+ if (step == 0)
+ clock_fm_and_adpcm();
+ output->data[0] = m_last_fm.data[0];
+ output->data[1] = m_last_fm.data[1];
+ if (step == 1)
+ {
+ clock_fm_and_adpcm();
+ output->data[0] = (output->data[0] + m_last_fm.data[0]) / 2;
+ output->data[1] = (output->data[1] + m_last_fm.data[1]) / 2;
+ }
+ }
+ }
+
+ // resample the SSG as configured
+ m_ssg_resampler.resample(output - numsamples, numsamples);
+}
+
+
+//-------------------------------------------------
+// update_prescale - update the prescale value,
+// recomputing derived values
+//-------------------------------------------------
+
+void ym2608::update_prescale(uint8_t prescale)
+{
+ // tell the FM engine
+ m_fm.set_clock_prescale(prescale);
+ m_ssg.prescale_changed();
+
+ // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum-----
+ // rate = clock/48 rate = clock/24 rate = clock/8
+ // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
+ // 6 3:1 2:3 6:1 4:3 18:1 4:1
+ // 3 1.5:1 1:3 3:1 2:3 9:1 2:1
+ // 2 1:1 1:6 2:1 1:3 6:1 1:1
+
+ // compute the number of FM samples per output sample, and select the
+ // resampler function
+ if (m_fidelity == OPN_FIDELITY_MIN)
+ {
+ switch (prescale)
+ {
+ default:
+ case 6: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break;
+ case 3: m_fm_samples_per_output = 0; m_ssg_resampler.configure(1, 3); break;
+ case 2: m_fm_samples_per_output = 1; m_ssg_resampler.configure(1, 6); break;
+ }
+ }
+ else if (m_fidelity == OPN_FIDELITY_MED)
+ {
+ switch (prescale)
+ {
+ default:
+ case 6: m_fm_samples_per_output = 6; m_ssg_resampler.configure(4, 3); break;
+ case 3: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break;
+ case 2: m_fm_samples_per_output = 2; m_ssg_resampler.configure(1, 3); break;
+ }
+ }
+ else
+ {
+ switch (prescale)
+ {
+ default:
+ case 6: m_fm_samples_per_output = 18; m_ssg_resampler.configure(4, 1); break;
+ case 3: m_fm_samples_per_output = 9; m_ssg_resampler.configure(2, 1); break;
+ case 2: m_fm_samples_per_output = 6; m_ssg_resampler.configure(1, 1); break;
+ }
+ }
+
+ // if overriding the SSG, override the configuration with the nop
+ // resampler to at least keep the sample index moving forward
+ if (m_ssg.overridden())
+ m_ssg_resampler.configure(0, 0);
+}
+
+
+//-------------------------------------------------
+// clock_fm_and_adpcm - clock FM and ADPCM state
+//-------------------------------------------------
+
+void ym2608::clock_fm_and_adpcm()
+{
+ // top bit of the IRQ enable flags controls 3-channel vs 6-channel mode
+ uint32_t fmmask = bitfield(m_irq_enable, 7) ? 0x3f : 0x07;
+
+ // clock the system
+ uint32_t env_counter = m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // clock the ADPCM-A engine on every envelope cycle
+ // (channels 4 and 5 clock every 2 envelope clocks)
+ if (bitfield(env_counter, 0, 2) == 0)
+ m_adpcm_a.clock(bitfield(env_counter, 2) ? 0x0f : 0x3f);
+
+ // clock the ADPCM-B engine every cycle
+ m_adpcm_b.clock();
+
+ // update the FM content; OPNA is 13-bit with no intermediate clipping
+ m_fm.output(m_last_fm.clear(), 1, 32767, fmmask);
+
+ // mix in the ADPCM and clamp
+ m_adpcm_a.output(m_last_fm, 0x3f);
+ m_adpcm_b.output(m_last_fm, 1);
+ m_last_fm.clamp16();
+}
+
+
+//*********************************************************
+// YMF288
+//*********************************************************
+
+// YMF288 is a YM2608 with the following changes:
+// * ADPCM-B part removed
+// * prescaler removed (fixed at 6)
+// * CSM removed
+// * Low power mode added
+// * SSG tone frequency is altered in some way? (explicitly DC for Tp 0-7, also double volume in some cases)
+// * I/O ports removed
+// * Shorter busy times
+// * All registers can be read
+
+//-------------------------------------------------
+// ymf288 - constructor
+//-------------------------------------------------
+
+ymf288::ymf288(ymfm_interface &intf) :
+ m_fidelity(OPN_FIDELITY_MAX),
+ m_address(0),
+ m_irq_enable(0x03),
+ m_flag_control(0x03),
+ m_fm(intf),
+ m_ssg(intf),
+ m_ssg_resampler(m_ssg),
+ m_adpcm_a(intf, 0)
+{
+ m_last_fm.clear();
+ update_prescale();
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ymf288::reset()
+{
+ // reset the engines
+ m_fm.reset();
+ m_ssg.reset();
+ m_adpcm_a.reset();
+
+ // configure ADPCM percussion sounds; these are present in an embedded ROM
+ m_adpcm_a.set_start_end(0, 0x0000, 0x01bf); // bass drum
+ m_adpcm_a.set_start_end(1, 0x01c0, 0x043f); // snare drum
+ m_adpcm_a.set_start_end(2, 0x0440, 0x1b7f); // top cymbal
+ m_adpcm_a.set_start_end(3, 0x1b80, 0x1cff); // high hat
+ m_adpcm_a.set_start_end(4, 0x1d00, 0x1f7f); // tom tom
+ m_adpcm_a.set_start_end(5, 0x1f80, 0x1fff); // rim shot
+
+ // initialize our special interrupt states, then read the upper status
+ // register, which updates the IRQs
+ m_irq_enable = 0x03;
+ m_flag_control = 0x00;
+ read_status_hi();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ymf288::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ state.save_restore(m_irq_enable);
+ state.save_restore(m_flag_control);
+ state.save_restore(m_last_fm.data);
+
+ m_fm.save_restore(state);
+ m_ssg.save_restore(state);
+ m_ssg_resampler.save_restore(state);
+ m_adpcm_a.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ymf288::read_status()
+{
+ uint8_t result = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB);
+ if (m_fm.intf().ymfm_is_busy())
+ result |= fm_engine::STATUS_BUSY;
+ return result;
+}
+
+
+//-------------------------------------------------
+// read_data - read the data register
+//-------------------------------------------------
+
+uint8_t ymf288::read_data()
+{
+ uint8_t result = 0;
+ if (m_address < 0x0e)
+ {
+ // 00-0D: Read from SSG
+ result = m_ssg.read(m_address & 0x0f);
+ }
+ else if (m_address < 0x10)
+ {
+ // 0E-0F: I/O ports not supported
+ result = 0xff;
+ }
+ else if (m_address == 0xff)
+ {
+ // FF: ID code
+ result = 2;
+ }
+ else if (ymf288_mode())
+ {
+ // registers are readable in YMF288 mode
+ result = m_fm.regs().read(m_address);
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// read_status_hi - read the extended status
+// register
+//-------------------------------------------------
+
+uint8_t ymf288::read_status_hi()
+{
+ // fetch regular status
+ uint8_t status = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB);
+
+ // turn off any bits that have been requested to be masked
+ status &= ~(m_flag_control & 0x03);
+
+ // update the status so that IRQs are propagated
+ m_fm.set_reset_status(status, ~status);
+
+ // merge in the busy flag
+ if (m_fm.intf().ymfm_is_busy())
+ status |= fm_engine::STATUS_BUSY;
+ return status;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ymf288::read(uint32_t offset)
+{
+ uint8_t result = 0;
+ switch (offset & 3)
+ {
+ case 0: // status port, YM2203 compatible
+ result = read_status();
+ break;
+
+ case 1: // data port
+ result = read_data();
+ break;
+
+ case 2: // status port, extended
+ result = read_status_hi();
+ break;
+
+ case 3: // unmapped
+ debug::log_unexpected_read_write("Unexpected read from YMF288 offset %d\n", offset & 3);
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ymf288::write_address(uint8_t data)
+{
+ // just set the address
+ m_address = data;
+
+ // in YMF288 mode, busy is signaled after address writes too
+ if (ymf288_mode())
+ m_fm.intf().ymfm_set_busy_end(16);
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the data register
+//-------------------------------------------------
+
+void ymf288::write_data(uint8_t data)
+{
+ // ignore if paired with upper address
+ if (bitfield(m_address, 8))
+ return;
+
+ // wait times are shorter in YMF288 mode
+ int busy_cycles = ymf288_mode() ? 16 : 32 * m_fm.clock_prescale();
+ if (m_address < 0x0e)
+ {
+ // 00-0D: write to SSG
+ m_ssg.write(m_address & 0x0f, data);
+ }
+ else if (m_address < 0x10)
+ {
+ // 0E-0F: I/O ports not supported
+ }
+ else if (m_address < 0x20)
+ {
+ // 10-1F: write to ADPCM-A
+ m_adpcm_a.write(m_address & 0x0f, data);
+ busy_cycles = 32 * m_fm.clock_prescale();
+ }
+ else if (m_address == 0x27)
+ {
+ // 27: mode register; CSM isn't supported so disable it
+ data &= 0x7f;
+ m_fm.write(m_address, data);
+ }
+ else if (m_address == 0x29)
+ {
+ // 29: special IRQ mask register
+ m_irq_enable = data;
+ m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x03);
+ }
+ else
+ {
+ // 20-27, 2A-FF: write to FM
+ m_fm.write(m_address, data);
+ }
+
+ // mark busy for a bit
+ m_fm.intf().ymfm_set_busy_end(busy_cycles);
+}
+
+
+//-------------------------------------------------
+// write_address_hi - handle a write to the upper
+// address register
+//-------------------------------------------------
+
+void ymf288::write_address_hi(uint8_t data)
+{
+ // just set the address
+ m_address = 0x100 | data;
+
+ // in YMF288 mode, busy is signaled after address writes too
+ if (ymf288_mode())
+ m_fm.intf().ymfm_set_busy_end(16);
+}
+
+
+//-------------------------------------------------
+// write_data_hi - handle a write to the upper
+// data register
+//-------------------------------------------------
+
+void ymf288::write_data_hi(uint8_t data)
+{
+ // ignore if paired with upper address
+ if (!bitfield(m_address, 8))
+ return;
+
+ // wait times are shorter in YMF288 mode
+ int busy_cycles = ymf288_mode() ? 16 : 32 * m_fm.clock_prescale();
+ if (m_address == 0x110)
+ {
+ // 110: IRQ flag control
+ if (bitfield(data, 7))
+ m_fm.set_reset_status(0, 0xff);
+ else
+ {
+ m_flag_control = data;
+ m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x03);
+ }
+ }
+ else
+ {
+ // 100-10F,111-1FF: write to FM
+ m_fm.write(m_address, data);
+ }
+
+ // mark busy for a bit
+ m_fm.intf().ymfm_set_busy_end(busy_cycles);
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ymf288::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 3)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+
+ case 2: // upper address port
+ write_address_hi(data);
+ break;
+
+ case 3: // upper data port
+ write_data_hi(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void ymf288::generate(output_data *output, uint32_t numsamples)
+{
+ // FM output is just repeated the prescale number of times; note that
+ // 0 is a special 1.5 case
+ if (m_fm_samples_per_output != 0)
+ {
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0)
+ clock_fm_and_adpcm();
+ output->data[0] = m_last_fm.data[0];
+ output->data[1] = m_last_fm.data[1];
+ }
+ }
+ else
+ {
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ uint32_t step = (m_ssg_resampler.sampindex() + samp) % 3;
+ if (step == 0)
+ clock_fm_and_adpcm();
+ output->data[0] = m_last_fm.data[0];
+ output->data[1] = m_last_fm.data[1];
+ if (step == 1)
+ {
+ clock_fm_and_adpcm();
+ output->data[0] = (output->data[0] + m_last_fm.data[0]) / 2;
+ output->data[1] = (output->data[1] + m_last_fm.data[1]) / 2;
+ }
+ }
+ }
+
+ // resample the SSG as configured
+ m_ssg_resampler.resample(output - numsamples, numsamples);
+}
+
+
+//-------------------------------------------------
+// update_prescale - update the prescale value,
+// recomputing derived values
+//-------------------------------------------------
+
+void ymf288::update_prescale()
+{
+ // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum-----
+ // rate = clock/144 rate = clock/144 rate = clock/16
+ // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
+ // 6 1:1 2:9 1:1 2:9 9:1 2:1
+
+ // compute the number of FM samples per output sample, and select the
+ // resampler function
+ if (m_fidelity == OPN_FIDELITY_MIN || m_fidelity == OPN_FIDELITY_MED)
+ {
+ m_fm_samples_per_output = 1;
+ m_ssg_resampler.configure(2, 9);
+ }
+ else
+ {
+ m_fm_samples_per_output = 9;
+ m_ssg_resampler.configure(2, 1);
+ }
+
+ // if overriding the SSG, override the configuration with the nop
+ // resampler to at least keep the sample index moving forward
+ if (m_ssg.overridden())
+ m_ssg_resampler.configure(0, 0);
+}
+
+
+//-------------------------------------------------
+// clock_fm_and_adpcm - clock FM and ADPCM state
+//-------------------------------------------------
+
+void ymf288::clock_fm_and_adpcm()
+{
+ // top bit of the IRQ enable flags controls 3-channel vs 6-channel mode
+ uint32_t fmmask = bitfield(m_irq_enable, 7) ? 0x3f : 0x07;
+
+ // clock the system
+ uint32_t env_counter = m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // clock the ADPCM-A engine on every envelope cycle
+ // (channels 4 and 5 clock every 2 envelope clocks)
+ if (bitfield(env_counter, 0, 2) == 0)
+ m_adpcm_a.clock(bitfield(env_counter, 2) ? 0x0f : 0x3f);
+
+ // update the FM content; OPNA is 13-bit with no intermediate clipping
+ m_fm.output(m_last_fm.clear(), 1, 32767, fmmask);
+
+ // mix in the ADPCM
+ m_adpcm_a.output(m_last_fm, 0x3f);
+}
+
+
+
+//*********************************************************
+// YM2610
+//*********************************************************
+
+//-------------------------------------------------
+// ym2610 - constructor
+//-------------------------------------------------
+
+ym2610::ym2610(ymfm_interface &intf, uint8_t channel_mask) :
+ m_fidelity(OPN_FIDELITY_MAX),
+ m_address(0),
+ m_fm_mask(channel_mask),
+ m_eos_status(0x00),
+ m_flag_mask(EOS_FLAGS_MASK),
+ m_fm(intf),
+ m_ssg(intf),
+ m_ssg_resampler(m_ssg),
+ m_adpcm_a(intf, 8),
+ m_adpcm_b(intf, 8)
+{
+ update_prescale();
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ym2610::reset()
+{
+ // reset the engines
+ m_fm.reset();
+ m_ssg.reset();
+ m_adpcm_a.reset();
+ m_adpcm_b.reset();
+
+ // initialize our special interrupt states
+ m_eos_status = 0x00;
+ m_flag_mask = EOS_FLAGS_MASK;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ym2610::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ state.save_restore(m_eos_status);
+ state.save_restore(m_flag_mask);
+
+ m_fm.save_restore(state);
+ m_ssg.save_restore(state);
+ m_ssg_resampler.save_restore(state);
+ m_adpcm_a.save_restore(state);
+ m_adpcm_b.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ym2610::read_status()
+{
+ uint8_t result = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB);
+ if (m_fm.intf().ymfm_is_busy())
+ result |= fm_engine::STATUS_BUSY;
+ return result;
+}
+
+
+//-------------------------------------------------
+// read_data - read the data register
+//-------------------------------------------------
+
+uint8_t ym2610::read_data()
+{
+ uint8_t result = 0;
+ if (m_address < 0x0e)
+ {
+ // 00-0D: Read from SSG
+ result = m_ssg.read(m_address & 0x0f);
+ }
+ else if (m_address < 0x10)
+ {
+ // 0E-0F: I/O ports not supported
+ result = 0xff;
+ }
+ else if (m_address == 0xff)
+ {
+ // FF: ID code
+ result = 1;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// read_status_hi - read the extended status
+// register
+//-------------------------------------------------
+
+uint8_t ym2610::read_status_hi()
+{
+ return m_eos_status & m_flag_mask;
+}
+
+
+//-------------------------------------------------
+// read_data_hi - read the upper data register
+//-------------------------------------------------
+
+uint8_t ym2610::read_data_hi()
+{
+ uint8_t result = 0;
+ return result;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ym2610::read(uint32_t offset)
+{
+ uint8_t result = 0;
+ switch (offset & 3)
+ {
+ case 0: // status port, YM2203 compatible
+ result = read_status();
+ break;
+
+ case 1: // data port (only SSG)
+ result = read_data();
+ break;
+
+ case 2: // status port, extended
+ result = read_status_hi();
+ break;
+
+ case 3: // ADPCM-B data
+ result = read_data_hi();
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ym2610::write_address(uint8_t data)
+{
+ // just set the address
+ m_address = data;
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the data register
+//-------------------------------------------------
+
+void ym2610::write_data(uint8_t data)
+{
+ // ignore if paired with upper address
+ if (bitfield(m_address, 8))
+ return;
+
+ if (m_address < 0x0e)
+ {
+ // 00-0D: write to SSG
+ m_ssg.write(m_address & 0x0f, data);
+ }
+ else if (m_address < 0x10)
+ {
+ // 0E-0F: I/O ports not supported
+ }
+ else if (m_address < 0x1c)
+ {
+ // 10-1B: write to ADPCM-B
+ // YM2610 effectively forces external mode on, and disables recording
+ if (m_address == 0x10)
+ data = (data | 0x20) & ~0x40;
+ m_adpcm_b.write(m_address & 0x0f, data);
+ }
+ else if (m_address == 0x1c)
+ {
+ // 1C: EOS flag reset
+ m_flag_mask = ~data & EOS_FLAGS_MASK;
+ m_eos_status &= ~(data & EOS_FLAGS_MASK);
+ }
+ else
+ {
+ // 1D-FF: write to FM
+ m_fm.write(m_address, data);
+ }
+
+ // mark busy for a bit
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// write_address_hi - handle a write to the upper
+// address register
+//-------------------------------------------------
+
+void ym2610::write_address_hi(uint8_t data)
+{
+ // just set the address
+ m_address = 0x100 | data;
+}
+
+
+//-------------------------------------------------
+// write_data_hi - handle a write to the upper
+// data register
+//-------------------------------------------------
+
+void ym2610::write_data_hi(uint8_t data)
+{
+ // ignore if paired with upper address
+ if (!bitfield(m_address, 8))
+ return;
+
+ if (m_address < 0x130)
+ {
+ // 100-12F: write to ADPCM-A
+ m_adpcm_a.write(m_address & 0x3f, data);
+ }
+ else
+ {
+ // 130-1FF: write to FM
+ m_fm.write(m_address, data);
+ }
+
+ // mark busy for a bit
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2610::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 3)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+
+ case 2: // upper address port
+ write_address_hi(data);
+ break;
+
+ case 3: // upper data port
+ write_data_hi(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void ym2610::generate(output_data *output, uint32_t numsamples)
+{
+ // FM output is just repeated the prescale number of times
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0)
+ clock_fm_and_adpcm();
+ output->data[0] = m_last_fm.data[0];
+ output->data[1] = m_last_fm.data[1];
+ }
+
+ // resample the SSG as configured
+ m_ssg_resampler.resample(output - numsamples, numsamples);
+}
+
+
+//-------------------------------------------------
+// update_prescale - update the prescale value,
+// recomputing derived values
+//-------------------------------------------------
+
+void ym2610::update_prescale()
+{
+ // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum-----
+ // rate = clock/144 rate = clock/144 rate = clock/16
+ // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
+ // 6 1:1 2:9 1:1 2:9 9:1 2:1
+
+ // compute the number of FM samples per output sample, and select the
+ // resampler function
+ if (m_fidelity == OPN_FIDELITY_MIN || m_fidelity == OPN_FIDELITY_MED)
+ {
+ m_fm_samples_per_output = 1;
+ m_ssg_resampler.configure(2, 9);
+ }
+ else
+ {
+ m_fm_samples_per_output = 9;
+ m_ssg_resampler.configure(2, 1);
+ }
+
+ // if overriding the SSG, override the configuration with the nop
+ // resampler to at least keep the sample index moving forward
+ if (m_ssg.overridden())
+ m_ssg_resampler.configure(0, 0);
+}
+
+
+//-------------------------------------------------
+// clock_fm_and_adpcm - clock FM and ADPCM state
+//-------------------------------------------------
+
+void ym2610::clock_fm_and_adpcm()
+{
+ // clock the system
+ uint32_t env_counter = m_fm.clock(m_fm_mask);
+
+ // clock the ADPCM-A engine on every envelope cycle
+ if (bitfield(env_counter, 0, 2) == 0)
+ m_eos_status |= m_adpcm_a.clock(0x3f);
+
+ // clock the ADPCM-B engine every cycle
+ m_adpcm_b.clock();
+
+ // we track the last ADPCM-B EOS value in bit 6 (which is hidden from callers);
+ // if it changed since the last sample, update the visible EOS state in bit 7
+ uint8_t live_eos = ((m_adpcm_b.status() & adpcm_b_channel::STATUS_EOS) != 0) ? 0x40 : 0x00;
+ if (((live_eos ^ m_eos_status) & 0x40) != 0)
+ m_eos_status = (m_eos_status & ~0xc0) | live_eos | (live_eos << 1);
+
+ // update the FM content; OPNB is 13-bit with no intermediate clipping
+ m_fm.output(m_last_fm.clear(), 1, 32767, m_fm_mask);
+
+ // mix in the ADPCM and clamp
+ m_adpcm_a.output(m_last_fm, 0x3f);
+ m_adpcm_b.output(m_last_fm, 1);
+ m_last_fm.clamp16();
+}
+
+
+
+//*********************************************************
+// YM2612
+//*********************************************************
+
+//-------------------------------------------------
+// ym2612 - constructor
+//-------------------------------------------------
+
+ym2612::ym2612(ymfm_interface &intf) :
+ m_address(0),
+ m_dac_data(0),
+ m_dac_enable(0),
+ m_fm(intf)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ym2612::reset()
+{
+ // reset the engines
+ m_fm.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ym2612::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_address);
+ state.save_restore(m_dac_data);
+ state.save_restore(m_dac_enable);
+ m_fm.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ym2612::read_status()
+{
+ uint8_t result = m_fm.status();
+ if (m_fm.intf().ymfm_is_busy())
+ result |= fm_engine::STATUS_BUSY;
+ return result;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ym2612::read(uint32_t offset)
+{
+ uint8_t result = 0;
+ switch (offset & 3)
+ {
+ case 0: // status port, YM2203 compatible
+ result = read_status();
+ break;
+
+ case 1: // data port (unused)
+ case 2: // status port, extended
+ case 3: // data port (unused)
+ debug::log_unexpected_read_write("Unexpected read from YM2612 offset %d\n", offset & 3);
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ym2612::write_address(uint8_t data)
+{
+ // just set the address
+ m_address = data;
+}
+
+
+//-------------------------------------------------
+// write_data - handle a write to the data
+// register
+//-------------------------------------------------
+
+void ym2612::write_data(uint8_t data)
+{
+ // ignore if paired with upper address
+ if (bitfield(m_address, 8))
+ return;
+
+ if (m_address == 0x2a)
+ {
+ // 2A: DAC data (most significant 8 bits)
+ m_dac_data = (m_dac_data & ~0x1fe) | ((data ^ 0x80) << 1);
+ }
+ else if (m_address == 0x2b)
+ {
+ // 2B: DAC enable (bit 7)
+ m_dac_enable = bitfield(data, 7);
+ }
+ else if (m_address == 0x2c)
+ {
+ // 2C: test/low DAC bit
+ m_dac_data = (m_dac_data & ~1) | bitfield(data, 3);
+ }
+ else
+ {
+ // 00-29, 2D-FF: write to FM
+ m_fm.write(m_address, data);
+ }
+
+ // mark busy for a bit
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// write_address_hi - handle a write to the upper
+// address register
+//-------------------------------------------------
+
+void ym2612::write_address_hi(uint8_t data)
+{
+ // just set the address
+ m_address = 0x100 | data;
+}
+
+
+//-------------------------------------------------
+// write_data_hi - handle a write to the upper
+// data register
+//-------------------------------------------------
+
+void ym2612::write_data_hi(uint8_t data)
+{
+ // ignore if paired with upper address
+ if (!bitfield(m_address, 8))
+ return;
+
+ // 100-1FF: write to FM
+ m_fm.write(m_address, data);
+
+ // mark busy for a bit
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2612::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 3)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+
+ case 2: // upper address port
+ write_address_hi(data);
+ break;
+
+ case 3: // upper data port
+ write_data_hi(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void ym2612::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // sum individual channels to apply DAC discontinuity on each
+ output->clear();
+ output_data temp;
+
+ // first do FM-only channels; OPN2 is 9-bit with intermediate clipping
+ int const last_fm_channel = m_dac_enable ? 5 : 6;
+ for (int chan = 0; chan < last_fm_channel; chan++)
+ {
+ m_fm.output(temp.clear(), 5, 256, 1 << chan);
+ output->data[0] += dac_discontinuity(temp.data[0]);
+ output->data[1] += dac_discontinuity(temp.data[1]);
+ }
+
+ // add in DAC
+ if (m_dac_enable)
+ {
+ // DAC enabled: start with DAC value then add the first 5 channels only
+ int32_t dacval = dac_discontinuity(int16_t(m_dac_data << 7) >> 7);
+ output->data[0] += m_fm.regs().ch_output_0(0x102) ? dacval : dac_discontinuity(0);
+ output->data[1] += m_fm.regs().ch_output_1(0x102) ? dacval : dac_discontinuity(0);
+ }
+
+ // output is technically multiplexed rather than mixed, but that requires
+ // a better sound mixer than we usually have, so just average over the six
+ // channels; also apply a 64/65 factor to account for the discontinuity
+ // adjustment above
+ output->data[0] = (output->data[0] * 128) * 64 / (6 * 65);
+ output->data[1] = (output->data[1] * 128) * 64 / (6 * 65);
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void ym3438::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // first do FM-only channels; OPN2C is 9-bit with intermediate clipping
+ if (!m_dac_enable)
+ {
+ // DAC disabled: all 6 channels sum together
+ m_fm.output(output->clear(), 5, 256, fm_engine::ALL_CHANNELS);
+ }
+ else
+ {
+ // DAC enabled: start with DAC value then add the first 5 channels only
+ int32_t dacval = int16_t(m_dac_data << 7) >> 7;
+ output->data[0] = m_fm.regs().ch_output_0(0x102) ? dacval : 0;
+ output->data[1] = m_fm.regs().ch_output_1(0x102) ? dacval : 0;
+ m_fm.output(*output, 5, 256, fm_engine::ALL_CHANNELS ^ (1 << 5));
+ }
+
+ // YM3438 doesn't have the same DAC discontinuity, though its output is
+ // multiplexed like the YM2612
+ output->data[0] = (output->data[0] * 128) / 6;
+ output->data[1] = (output->data[1] * 128) / 6;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void ymf276::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // first do FM-only channels; OPN2L is 14-bit with intermediate clipping
+ if (!m_dac_enable)
+ {
+ // DAC disabled: all 6 channels sum together
+ m_fm.output(output->clear(), 0, 8191, fm_engine::ALL_CHANNELS);
+ }
+ else
+ {
+ // DAC enabled: start with DAC value then add the first 5 channels only
+ int32_t dacval = int16_t(m_dac_data << 7) >> 7;
+ output->data[0] = m_fm.regs().ch_output_0(0x102) ? dacval : 0;
+ output->data[1] = m_fm.regs().ch_output_1(0x102) ? dacval : 0;
+ m_fm.output(*output, 0, 8191, fm_engine::ALL_CHANNELS ^ (1 << 5));
+ }
+
+ // YMF276 is properly mixed; it shifts down 1 bit before clamping
+ output->data[0] = clamp(output->data[0] >> 1, -32768, 32767);
+ output->data[1] = clamp(output->data[1] >> 1, -32768, 32767);
+ }
+}
+
+}
diff --git a/3rdparty/ymfm/src/ymfm_opn.h b/3rdparty/ymfm/src/ymfm_opn.h
new file mode 100644
index 00000000000..daed8b0bf24
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_opn.h
@@ -0,0 +1,802 @@
+// 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 = 1);
+};
+
+}
+
+
+#endif // YMFM_OPN_H
diff --git a/3rdparty/ymfm/src/ymfm_opq.cpp b/3rdparty/ymfm/src/ymfm_opq.cpp
new file mode 100644
index 00000000000..78ae1616409
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_opq.cpp
@@ -0,0 +1,480 @@
+// 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.
+
+#include "ymfm_opq.h"
+#include "ymfm_fm.ipp"
+
+#define TEMPORARY_DEBUG_PRINTS (0)
+
+//
+// OPQ (aka YM3806/YM3533)
+//
+// This chip is not officially documented as far as I know. What I have
+// comes from Jari Kangas' work on reverse engineering the PSR70:
+//
+// https://github.com/JKN0/PSR70-reverse
+//
+// OPQ appears be bsaically a mixture of OPM and OPN.
+//
+
+namespace ymfm
+{
+
+//*********************************************************
+// OPQ SPECIFICS
+//*********************************************************
+
+//-------------------------------------------------
+// opq_registers - constructor
+//-------------------------------------------------
+
+opq_registers::opq_registers() :
+ m_lfo_counter(0),
+ m_lfo_am(0)
+{
+ // create the waveforms
+ for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
+ m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
+
+ uint16_t zeroval = m_waveform[0][0];
+ for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
+ m_waveform[1][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index];
+}
+
+
+//-------------------------------------------------
+// reset - reset to initial state
+//-------------------------------------------------
+
+void opq_registers::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+
+ // enable output on both channels by default
+ m_regdata[0x10] = m_regdata[0x11] = m_regdata[0x12] = m_regdata[0x13] = 0xc0;
+ m_regdata[0x14] = m_regdata[0x15] = m_regdata[0x16] = m_regdata[0x17] = 0xc0;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void opq_registers::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_lfo_counter);
+ state.save_restore(m_lfo_am);
+ state.save_restore(m_regdata);
+}
+
+
+//-------------------------------------------------
+// operator_map - return an array of operator
+// indices for each channel; for OPM this is fixed
+//-------------------------------------------------
+
+void opq_registers::operator_map(operator_mapping &dest) const
+{
+ // seems like the operators are not swizzled like they are on OPM/OPN?
+ static const operator_mapping s_fixed_map =
+ { {
+ operator_list( 0, 8, 16, 24 ), // Channel 0 operators
+ operator_list( 1, 9, 17, 25 ), // Channel 1 operators
+ operator_list( 2, 10, 18, 26 ), // Channel 2 operators
+ operator_list( 3, 11, 19, 27 ), // Channel 3 operators
+ operator_list( 4, 12, 20, 28 ), // Channel 4 operators
+ operator_list( 5, 13, 21, 29 ), // Channel 5 operators
+ operator_list( 6, 14, 22, 30 ), // Channel 6 operators
+ operator_list( 7, 15, 23, 31 ), // Channel 7 operators
+ } };
+ dest = s_fixed_map;
+}
+
+
+//-------------------------------------------------
+// write - handle writes to the register array
+//-------------------------------------------------
+
+bool opq_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
+{
+ assert(index < REGISTERS);
+
+ // detune/multiple share a register based on the MSB of what is written
+ // remap the multiple values to 100-11F
+ if ((index & 0xe0) == 0x40 && bitfield(data, 7) != 0)
+ index += 0xc0;
+
+ m_regdata[index] = data;
+
+ // handle writes to the key on index
+ if (index == 0x05)
+ {
+ channel = bitfield(data, 0, 3);
+ opmask = bitfield(data, 3, 4);
+ return true;
+ }
+ return false;
+}
+
+
+//-------------------------------------------------
+// clock_noise_and_lfo - clock the noise and LFO,
+// handling clock division, depth, and waveform
+// computations
+//-------------------------------------------------
+
+int32_t opq_registers::clock_noise_and_lfo()
+{
+ // OPQ LFO is not well-understood, but the enable and rate values
+ // look a lot like OPN, so we'll crib from there as a starting point
+
+ // if LFO not enabled (not present on OPN), quick exit with 0s
+ if (!lfo_enable())
+ {
+ m_lfo_counter = 0;
+ m_lfo_am = 0;
+ return 0;
+ }
+
+ // this table is based on converting the frequencies in the applications
+ // manual to clock dividers, based on the assumption of a 7-bit LFO value
+ static uint8_t const lfo_max_count[8] = { 109, 78, 72, 68, 63, 45, 9, 6 };
+ uint32_t subcount = uint8_t(m_lfo_counter++);
+
+ // when we cross the divider count, add enough to zero it and cause an
+ // increment at bit 8; the 7-bit value lives from bits 8-14
+ if (subcount >= lfo_max_count[lfo_rate()])
+ m_lfo_counter += 0x101 - subcount;
+
+ // AM value is 7 bits, staring at bit 8; grab the low 6 directly
+ m_lfo_am = bitfield(m_lfo_counter, 8, 6);
+
+ // first half of the AM period (bit 6 == 0) is inverted
+ if (bitfield(m_lfo_counter, 8+6) == 0)
+ m_lfo_am ^= 0x3f;
+
+ // PM value is 5 bits, starting at bit 10; grab the low 3 directly
+ int32_t pm = bitfield(m_lfo_counter, 10, 3);
+
+ // PM is reflected based on bit 3
+ if (bitfield(m_lfo_counter, 10+3))
+ pm ^= 7;
+
+ // PM is negated based on bit 4
+ return bitfield(m_lfo_counter, 10+4) ? -pm : pm;
+}
+
+
+//-------------------------------------------------
+// lfo_am_offset - return the AM offset from LFO
+// for the given channel
+//-------------------------------------------------
+
+uint32_t opq_registers::lfo_am_offset(uint32_t choffs) const
+{
+ // OPM maps AM quite differently from OPN
+
+ // shift value for AM sensitivity is [*, 0, 1, 2],
+ // mapping to values of [0, 23.9, 47.8, and 95.6dB]
+ uint32_t am_sensitivity = ch_lfo_am_sens(choffs);
+ if (am_sensitivity == 0)
+ return 0;
+
+ // QUESTION: see OPN note below for the dB range mapping; it applies
+ // here as well
+
+ // raw LFO AM value on OPM is 0-FF, which is already a factor of 2
+ // larger than the OPN below, putting our staring point at 2x theirs;
+ // this works out since our minimum is 2x their maximum
+ return m_lfo_am << (am_sensitivity - 1);
+}
+
+
+//-------------------------------------------------
+// cache_operator_data - fill the operator cache
+// with prefetched data
+//-------------------------------------------------
+
+void opq_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
+{
+ // set up the easy stuff
+ cache.waveform = &m_waveform[op_waveform(opoffs)][0];
+
+ // get frequency from the appropriate registers
+ uint32_t block_freq = cache.block_freq = (opoffs & 8) ? ch_block_freq_24(choffs) : ch_block_freq_13(choffs);
+
+ // compute the keycode: block_freq is:
+ //
+ // BBBFFFFFFFFFFFF
+ // ^^^^???
+ //
+ // keycode is not understood, so just guessing it is like OPN:
+ // the 5-bit keycode uses the top 4 bits plus a magic formula
+ // for the final bit
+ uint32_t keycode = bitfield(block_freq, 11, 4) << 1;
+
+ // lowest bit is determined by a mix of next lower FNUM bits
+ // according to this equation from the YM2608 manual:
+ //
+ // (F11 & (F10 | F9 | F8)) | (!F11 & F10 & F9 & F8)
+ //
+ // for speed, we just look it up in a 16-bit constant
+ keycode |= bitfield(0xfe80, bitfield(block_freq, 8, 4));
+
+ // detune adjustment: the detune values supported by the OPQ are
+ // a much larger range (6 bits vs 3 bits) compared to any other
+ // known FM chip; based on experiments, it seems that the extra
+ // bits provide a bigger detune range rather than finer control,
+ // so until we get true measurements just assemble a net detune
+ // value by summing smaller detunes
+ int32_t detune = int32_t(op_detune(opoffs)) - 0x20;
+ int32_t abs_detune = std::abs(detune);
+ int32_t adjust = (abs_detune / 3) * detune_adjustment(3, keycode) + detune_adjustment(abs_detune % 3, keycode);
+ cache.detune = (detune >= 0) ? adjust : -adjust;
+
+ // multiple value, as an x.1 value (0 means 0.5)
+ static const uint8_t s_multiple_map[16] = { 1,2,4,6,8,10,12,14,16,18,20,24,30,32,34,36 };
+ cache.multiple = s_multiple_map[op_multiple(opoffs)];
+
+ // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
+ // block_freq, detune, and multiple, so compute it after we've done those
+ if (lfo_enable() == 0 || ch_lfo_pm_sens(choffs) == 0)
+ cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
+ else
+ cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
+
+ // total level, scaled by 8
+ cache.total_level = op_total_level(opoffs) << 3;
+
+ // 4-bit sustain level, but 15 means 31 so effectively 5 bits
+ cache.eg_sustain = op_sustain_level(opoffs);
+ cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
+ cache.eg_sustain <<= 5;
+
+ // determine KSR adjustment for enevlope rates
+ uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3);
+ cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval);
+ cache.eg_rate[EG_REVERB] = (ch_reverb(choffs) != 0) ? 5*4 : cache.eg_rate[EG_RELEASE];
+ cache.eg_shift = 0;
+}
+
+
+//-------------------------------------------------
+// compute_phase_step - compute the phase step
+//-------------------------------------------------
+
+uint32_t opq_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
+{
+ // OPN phase calculation has only a single detune parameter
+ // and uses FNUMs instead of keycodes
+
+ // extract frequency number (low 12 bits of block_freq)
+ uint32_t fnum = bitfield(cache.block_freq, 0, 12);
+
+ // if there's a non-zero PM sensitivity, compute the adjustment
+ uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs);
+ if (pm_sensitivity != 0)
+ {
+ // apply the phase adjustment based on the upper 7 bits
+ // of FNUM and the PM depth parameters
+ fnum += opn_lfo_pm_phase_adjustment(bitfield(cache.block_freq, 5, 7), pm_sensitivity, lfo_raw_pm);
+
+ // keep fnum to 12 bits
+ fnum &= 0xfff;
+ }
+
+ // apply block shift to compute phase step
+ uint32_t block = bitfield(cache.block_freq, 12, 3);
+ uint32_t phase_step = (fnum << block) >> 2;
+
+ // apply detune based on the keycode
+ phase_step += cache.detune;
+
+ // clamp to 17 bits in case detune overflows
+ // QUESTION: is this specific to the YM2612/3438?
+ phase_step &= 0x1ffff;
+
+ // apply frequency multiplier (which is cached as an x.1 value)
+ return (phase_step * cache.multiple) >> 1;
+}
+
+
+//-------------------------------------------------
+// log_keyon - log a key-on event
+//-------------------------------------------------
+
+std::string opq_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
+{
+ uint32_t chnum = choffs;
+ uint32_t opnum = opoffs;
+
+ char buffer[256];
+ int end = 0;
+
+ end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u freq=%04X dt=%+2d fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
+ chnum, opnum,
+ (opoffs & 1) ? ch_block_freq_24(choffs) : ch_block_freq_13(choffs),
+ int32_t(op_detune(opoffs)) - 0x20,
+ ch_feedback(choffs),
+ ch_algorithm(choffs),
+ op_multiple(opoffs),
+ op_total_level(opoffs),
+ op_ksr(opoffs),
+ op_attack_rate(opoffs),
+ op_decay_rate(opoffs),
+ op_sustain_rate(opoffs),
+ op_release_rate(opoffs),
+ op_sustain_level(opoffs),
+ ch_output_0(choffs) ? 'L' : '-',
+ ch_output_1(choffs) ? 'R' : '-');
+
+ bool am = (lfo_enable() && op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0);
+ if (am)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u", ch_lfo_am_sens(choffs));
+ bool pm = (lfo_enable() && ch_lfo_pm_sens(choffs) != 0);
+ if (pm)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u", ch_lfo_pm_sens(choffs));
+ if (am || pm)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X", lfo_rate());
+ if (ch_reverb(choffs))
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " reverb");
+
+ return buffer;
+}
+
+
+
+//*********************************************************
+// YM3806
+//*********************************************************
+
+//-------------------------------------------------
+// ym3806 - constructor
+//-------------------------------------------------
+
+ym3806::ym3806(ymfm_interface &intf) :
+ m_fm(intf)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ym3806::reset()
+{
+ // reset the engines
+ m_fm.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ym3806::save_restore(ymfm_saved_state &state)
+{
+ m_fm.save_restore(state);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ym3806::read_status()
+{
+ uint8_t result = m_fm.status();
+ if (m_fm.intf().ymfm_is_busy())
+ result |= fm_engine::STATUS_BUSY;
+ return result;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ym3806::read(uint32_t offset)
+{
+ uint8_t result = 0xff;
+ switch (offset)
+ {
+ case 0: // status port
+ result = read_status();
+ break;
+
+ default: // unknown
+ debug::log_unexpected_read_write("Unexpected read from YM3806 offset %02X\n", offset);
+ break;
+ }
+if (TEMPORARY_DEBUG_PRINTS && offset != 0) printf("Read %02X = %02X\n", offset, result);
+ return result;
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym3806::write(uint32_t offset, uint8_t data)
+{
+if (TEMPORARY_DEBUG_PRINTS && (offset != 3 || data != 0x71)) printf("Write %02X = %02X\n", offset, data);
+ // write the FM register
+ m_fm.write(offset, data);
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void ym3806::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // update the FM content; YM3806 is full 14-bit with no intermediate clipping
+ m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
+
+ // YM3608 appears to go through a YM3012 DAC, which means we want to apply
+ // the FP truncation logic to the outputs
+ output->roundtrip_fp();
+ }
+}
+
+}
diff --git a/3rdparty/ymfm/src/ymfm_opq.h b/3rdparty/ymfm/src/ymfm_opq.h
new file mode 100644
index 00000000000..f530ac070f1
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_opq.h
@@ -0,0 +1,293 @@
+// 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_OPQ_H
+#define YMFM_OPQ_H
+
+#pragma once
+
+#include "ymfm.h"
+#include "ymfm_fm.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// REGISTER CLASSES
+//*********************************************************
+
+// ======================> opq_registers
+
+//
+// OPQ register map:
+//
+// System-wide registers:
+// 03 xxxxxxxx Timer control (unknown; 0x71 causes interrupts at ~10ms)
+// 04 ----x--- LFO disable
+// -----xxx LFO frequency (0=~4Hz, 6=~10Hz, 7=~47Hz)
+// 05 -x------ Key on/off operator 4
+// --x----- Key on/off operator 3
+// ---x---- Key on/off operator 2
+// ----x--- Key on/off operator 1
+// -----xxx Channel select
+//
+// Per-channel registers (channel in address bits 0-2)
+// 10-17 x------- Pan right
+// -x------ Pan left
+// --xxx--- Feedback level for operator 1 (0-7)
+// -----xxx Operator connection algorithm (0-7)
+// 18-1F x------- Reverb
+// -xxx---- PM sensitivity
+// ------xx AM shift
+// 20-27 -xxx---- Block (0-7), Operator 2 & 4
+// ----xxxx Frequency number upper 4 bits, Operator 2 & 4
+// 28-2F -xxx---- Block (0-7), Operator 1 & 3
+// ----xxxx Frequency number upper 4 bits, Operator 1 & 3
+// 30-37 xxxxxxxx Frequency number lower 8 bits, Operator 2 & 4
+// 38-3F xxxxxxxx Frequency number lower 8 bits, Operator 1 & 3
+//
+// Per-operator registers (channel in address bits 0-2, operator in bits 3-4)
+// 40-5F 0-xxxxxx Detune value (0-63)
+// 1---xxxx Multiple value (0-15)
+// 60-7F -xxxxxxx Total level (0-127)
+// 80-9F xx------ Key scale rate (0-3)
+// ---xxxxx Attack rate (0-31)
+// A0-BF x------- LFO AM enable, retrigger disable
+// x------ Waveform select
+// ---xxxxx Decay rate (0-31)
+// C0-DF ---xxxxx Sustain rate (0-31)
+// E0-FF xxxx---- Sustain level (0-15)
+// ----xxxx Release rate (0-15)
+//
+// Diffs from OPM:
+// - 2 frequencies/channel
+// - retrigger disable
+// - 2 waveforms
+// - uses FNUM
+// - reverb behavior
+// - larger detune range
+//
+// Questions:
+// - timer information is pretty light
+// - how does echo work?
+// -
+
+class opq_registers : public fm_registers_base
+{
+public:
+ // constants
+ static constexpr uint32_t OUTPUTS = 2;
+ static constexpr uint32_t CHANNELS = 8;
+ static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
+ static constexpr uint32_t OPERATORS = CHANNELS * 4;
+ static constexpr uint32_t WAVEFORMS = 2;
+ static constexpr uint32_t REGISTERS = 0x120;
+ static constexpr uint32_t REG_MODE = 0x03;
+ static constexpr uint32_t DEFAULT_PRESCALE = 2;
+ static constexpr uint32_t EG_CLOCK_DIVIDER = 3;
+ static constexpr bool EG_HAS_REVERB = true;
+ static constexpr bool MODULATOR_DELAY = false;
+ static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
+ static constexpr uint8_t STATUS_TIMERA = 0;
+ static constexpr uint8_t STATUS_TIMERB = 0x04;
+ static constexpr uint8_t STATUS_BUSY = 0x80;
+ static constexpr uint8_t STATUS_IRQ = 0;
+
+ // constructor
+ opq_registers();
+
+ // 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);
+ return chnum;
+ }
+
+ // map operator number to register offset
+ static constexpr uint32_t operator_offset(uint32_t opnum)
+ {
+ assert(opnum < OPERATORS);
+ return opnum;
+ }
+
+ // return an array of operator indices for each channel
+ struct operator_mapping { uint32_t chan[CHANNELS]; };
+ void operator_map(operator_mapping &dest) const;
+
+ // 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 the current noise state, gated by the noise clock
+ 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 timer_a_value() const { return 0; }
+ uint32_t timer_b_value() const { return byte(0x03, 2, 6) | 0xc0; } // ???
+ uint32_t csm() const { return 0; }
+ uint32_t reset_timer_b() const { return byte(0x03, 0, 1); } // ???
+ uint32_t reset_timer_a() const { return 0; }
+ uint32_t enable_timer_b() const { return byte(0x03, 0, 1); } // ???
+ uint32_t enable_timer_a() const { return 0; }
+ uint32_t load_timer_b() const { return byte(0x03, 0, 1); } // ???
+ uint32_t load_timer_a() const { return 0; }
+ uint32_t lfo_enable() const { return byte(0x04, 3, 1) ^ 1; }
+ uint32_t lfo_rate() const { return byte(0x04, 0, 3); }
+
+ // per-channel registers
+ uint32_t ch_output_any(uint32_t choffs) const { return byte(0x10, 6, 2, choffs); }
+ uint32_t ch_output_0(uint32_t choffs) const { return byte(0x10, 6, 1, choffs); }
+ uint32_t ch_output_1(uint32_t choffs) const { return byte(0x10, 7, 1, choffs); }
+ 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_feedback(uint32_t choffs) const { return byte(0x10, 3, 3, choffs); }
+ uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x10, 0, 3, choffs); }
+ uint32_t ch_reverb(uint32_t choffs) const { return byte(0x18, 7, 1, choffs); }
+ uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x18, 4, 3, choffs); }
+ uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x18, 0, 2, choffs); }
+ uint32_t ch_block_freq_24(uint32_t choffs) const { return word(0x20, 0, 7, 0x30, 0, 8, choffs); }
+ uint32_t ch_block_freq_13(uint32_t choffs) const { return word(0x28, 0, 7, 0x38, 0, 8, choffs); }
+
+ // per-operator registers
+ uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 0, 6, opoffs); }
+ uint32_t op_multiple(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); }
+ uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); }
+ uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); }
+ uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); }
+ uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); }
+ uint32_t op_waveform(uint32_t opoffs) const { return byte(0xa0, 6, 1, opoffs); }
+ uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); }
+ uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); }
+ uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); }
+ uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, 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
+};
+
+
+
+//*********************************************************
+// IMPLEMENTATION CLASSES
+//*********************************************************
+
+// ======================> ym3806
+
+class ym3806
+{
+public:
+ using fm_engine = fm_engine_base<opq_registers>;
+ static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
+ using output_data = fm_engine::output_data;
+
+ // constructor
+ ym3806(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) { /* not supported; only direct writes */ }
+ void write_data(uint8_t data) { /* not supported; only direct writes */ }
+ void write(uint32_t offset, uint8_t data);
+
+ // generate one sample of sound
+ void generate(output_data *output, uint32_t numsamples = 1);
+
+protected:
+ // internal state
+ fm_engine m_fm; // core FM engine
+};
+
+
+// ======================> ym3533
+
+class ym3533 : public ym3806
+{
+public:
+ // constructor
+ ym3533(ymfm_interface &intf) :
+ ym3806(intf) { }
+};
+
+}
+
+
+#endif // YMFM_OPQ_H
diff --git a/3rdparty/ymfm/src/ymfm_opx.h b/3rdparty/ymfm/src/ymfm_opx.h
new file mode 100644
index 00000000000..f8ee1c34f18
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_opx.h
@@ -0,0 +1,290 @@
+// 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_OPX_H
+#define YMFM_OPX_H
+
+#pragma once
+
+#include "ymfm.h"
+#include "ymfm_fm.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// REGISTER CLASSES
+//*********************************************************
+
+// ======================> opx_registers
+
+//
+// OPX register map:
+//
+// System-wide registers:
+//
+// Per-channel registers (channel in address bits 0-2)
+//
+// Per-operator registers (4 banks):
+// 00-0F x------- Enable
+// -xxxx--- EXT out
+// -------x Key on
+// 10-1F xxxxxxxx LFO frequency
+// 20-2F xx------ AM sensitivity (0-3)
+// --xxx--- PM sensitivity (0-7)
+// ------xx LFO waveform (0=disable, 1=saw, 2=
+// 30-3F -xxx---- Detune (0-7)
+// ----xxxx Multiple (0-15)
+// 40-4F -xxxxxxx Total level (0-127)
+// 50-5F xxx----- Key scale (0-7)
+// ---xxxxx Attack rate (0-31)
+// 60-6F ---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 xxxxxxxx Frequency number (low 8 bits)
+// A0-AF xxxx---- Block (0-15)
+// ----xxxx Frequency number (high 4 bits)
+// B0-BF x------- Acc on
+// -xxx---- Feedback level (0-7)
+// -----xxx Waveform (0-7, 7=PCM)
+// C0-CF ----xxxx Algorithm (0-15)
+// D0-DF xxxx---- CH0 level (0-15)
+// ----xxxx CH1 level (0-15)
+// E0-EF xxxx---- CH2 level (0-15)
+// ----xxxx CH3 level (0-15)
+//
+
+class opx_registers : public fm_registers_base
+{
+ // LFO waveforms are 256 entries long
+ static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256;
+
+public:
+ // constants
+ static constexpr uint32_t OUTPUTS = 8;
+ static constexpr uint32_t CHANNELS = 24;
+ static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
+ static constexpr uint32_t OPERATORS = CHANNELS * 2;
+ static constexpr uint32_t WAVEFORMS = 8;
+ static constexpr uint32_t REGISTERS = 0x800;
+ static constexpr uint32_t DEFAULT_PRESCALE = 8;
+ static constexpr uint32_t EG_CLOCK_DIVIDER = 2;
+ static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
+ static constexpr uint32_t REG_MODE = 0x14;
+ 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
+ opx_registers();
+
+ // 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);
+ return chnum;
+ }
+
+ // map operator number to register offset
+ static constexpr uint32_t operator_offset(uint32_t opnum)
+ {
+ assert(opnum < OPERATORS);
+ return opnum;
+ }
+
+ // return an array of operator indices for each channel
+ struct operator_mapping { uint32_t chan[CHANNELS]; };
+ void operator_map(operator_mapping &dest) const;
+
+ // 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();
+
+ // return the AM offset from LFO for the given channel
+ uint32_t lfo_am_offset(uint32_t choffs) const;
+
+ // return the current noise state, gated by the noise clock
+ uint32_t noise_state() const { return m_noise_state; }
+
+ // 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 noise_frequency() const { return byte(0x0f, 0, 5); }
+ uint32_t noise_enable() const { return byte(0x0f, 7, 1); }
+ uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); }
+ uint32_t timer_b_value() const { return byte(0x12, 0, 8); }
+ uint32_t csm() const { return byte(0x14, 7, 1); }
+ uint32_t reset_timer_b() const { return byte(0x14, 5, 1); }
+ uint32_t reset_timer_a() const { return byte(0x14, 4, 1); }
+ uint32_t enable_timer_b() const { return byte(0x14, 3, 1); }
+ uint32_t enable_timer_a() const { return byte(0x14, 2, 1); }
+ uint32_t load_timer_b() const { return byte(0x14, 1, 1); }
+ uint32_t load_timer_a() const { return byte(0x14, 0, 1); }
+ uint32_t lfo2_pm_depth() const { return byte(0x148, 0, 7); } // fake
+ uint32_t lfo2_rate() const { return byte(0x16, 0, 8); }
+ uint32_t lfo2_am_depth() const { return byte(0x17, 0, 7); }
+ uint32_t lfo_rate() const { return byte(0x18, 0, 8); }
+ uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); }
+ uint32_t lfo_pm_depth() const { return byte(0x149, 0, 7); } // fake
+ uint32_t output_bits() const { return byte(0x1b, 6, 2); }
+ uint32_t lfo2_sync() const { return byte(0x1b, 5, 1); }
+ uint32_t lfo_sync() const { return byte(0x1b, 4, 1); }
+ uint32_t lfo2_waveform() const { return byte(0x1b, 2, 2); }
+ uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); }
+
+ // per-channel registers
+ uint32_t ch_volume(uint32_t choffs) const { return byte(0x00, 0, 8, choffs); }
+ uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); }
+ uint32_t ch_output_0(uint32_t choffs) const { return byte(0x30, 0, 1, choffs); }
+ uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); }
+ 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_key_on(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); }
+ uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); }
+ uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); }
+ uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); }
+ uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); }
+ uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); }
+ uint32_t ch_lfo2_pm_sens(uint32_t choffs) const { return byte(0x140, 4, 3, choffs); } // fake
+ uint32_t ch_lfo2_am_sens(uint32_t choffs) const { return byte(0x140, 0, 2, choffs); } // fake
+
+ // per-operator registers
+ uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); }
+ uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); }
+ uint32_t op_fix_range(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); }
+ uint32_t op_fix_frequency(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); }
+ uint32_t op_waveform(uint32_t opoffs) const { return byte(0x100, 4, 3, opoffs); } // fake
+ uint32_t op_fine(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); } // fake
+ uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); }
+ uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); }
+ uint32_t op_fix_mode(uint32_t opoffs) const { return byte(0x80, 5, 1, opoffs); }
+ uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); }
+ uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); }
+ uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); }
+ uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); }
+ uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); }
+ uint32_t op_eg_shift(uint32_t opoffs) const { return byte(0x120, 6, 2, opoffs); } // fake
+ uint32_t op_reverb_rate(uint32_t opoffs) const { return byte(0x120, 0, 3, opoffs); } // fake
+ uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); }
+ uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, 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[2]; // LFO counter
+ uint32_t m_noise_lfsr; // noise LFSR state
+ uint8_t m_noise_counter; // noise counter
+ uint8_t m_noise_state; // latched noise state
+ uint8_t m_noise_lfo; // latched LFO noise value
+ uint8_t m_lfo_am[2]; // current LFO AM value
+ uint8_t m_regdata[REGISTERS]; // register data
+ uint16_t m_phase_substep[OPERATORS]; // phase substep for fixed frequency
+ int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8
+ uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
+};
+
+
+
+//*********************************************************
+// IMPLEMENTATION CLASSES
+//*********************************************************
+
+// ======================> ymf271
+
+class ymf271
+{
+public:
+ using fm_engine = fm_engine_base<opx_registers>;
+ static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
+ using output_data = fm_engine::output_data;
+
+ // constructor
+ ymf271(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(uint32_t offset, uint8_t data);
+
+ // generate one sample of sound
+ void generate(output_data *output, uint32_t numsamples = 1);
+
+protected:
+ // internal state
+ uint8_t m_address; // address register
+ fm_engine m_fm; // core FM engine
+};
+
+}
+
+
+#endif // YMFM_OPX_H
diff --git a/3rdparty/ymfm/src/ymfm_opz.cpp b/3rdparty/ymfm/src/ymfm_opz.cpp
new file mode 100644
index 00000000000..1178417bb8a
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_opz.cpp
@@ -0,0 +1,808 @@
+// 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.
+
+#include "ymfm_opz.h"
+#include "ymfm_fm.ipp"
+
+#define TEMPORARY_DEBUG_PRINTS (0)
+
+//
+// OPZ (aka YM2414)
+//
+// This chip is not officially documented as far as I know. What I have
+// comes from this site:
+//
+// http://sr4.sakura.ne.jp/fmsound/opz.html
+//
+// and from reading the TX81Z operator manual, which describes how a number
+// of these new features work.
+//
+// OPZ appears be bsaically OPM with a bunch of extra features.
+//
+// For starters, there are two LFO generators. I have presumed that they
+// operate identically since identical parameters are offered for each. I
+// have also presumed the effects are additive between them. The LFOs on
+// the OPZ have an extra "sync" option which apparently causes the LFO to
+// reset whenever a key on is received.
+//
+// At the channel level, there is an additional 8-bit volume control. This
+// might work as an addition to total level, or some other way. Completely
+// unknown, and unimplemented.
+//
+// At the operator level, there are a number of extra features. First, there
+// are 8 different waveforms to choose from. These are different than the
+// waveforms introduced in the OPL2 and later chips.
+//
+// Second, there is an additional "reverb" stage added to the envelope
+// generator, which kicks in when the envelope reaches -18dB. It specifies
+// a slower decay rate to produce a sort of faux reverb effect.
+//
+// The envelope generator also supports a 2-bit shift value, which can be
+// used to reduce the effect of the envelope attenuation.
+//
+// OPZ supports a "fixed frequency" mode for each operator, with a 3-bit
+// range and 4-bit frequency value, plus a 1-bit enable. Not sure how that
+// works at all, so it's not implemented.
+//
+// There are also several mystery fields in the operators which I have no
+// clue about: "fine" (4 bits), "eg_shift" (2 bits), and "rev" (3 bits).
+// eg_shift is some kind of envelope generator effect, but how it works is
+// unknown.
+//
+// Also, according to the site above, the panning controls are changed from
+// OPM, with a "mono" bit and only one control bit for the right channel.
+// Current implementation is just a guess.
+//
+
+namespace ymfm
+{
+
+//*********************************************************
+// OPZ REGISTERS
+//*********************************************************
+
+//-------------------------------------------------
+// opz_registers - constructor
+//-------------------------------------------------
+
+opz_registers::opz_registers() :
+ m_lfo_counter{ 0, 0 },
+ m_noise_lfsr(1),
+ m_noise_counter(0),
+ m_noise_state(0),
+ m_noise_lfo(0),
+ m_lfo_am{ 0, 0 }
+{
+ // create the waveforms
+ for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
+ m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
+
+ // we only have the diagrams to judge from, but suspecting waveform 1 (and
+ // derived waveforms) are sin^2, based on OPX description of similar wave-
+ // forms; since our sin table is logarithmic, this ends up just being
+ // 2*existing value
+ uint16_t zeroval = m_waveform[0][0];
+ for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
+ m_waveform[1][index] = std::min<uint16_t>(2 * (m_waveform[0][index] & 0x7fff), zeroval) | (bitfield(index, 9) << 15);
+
+ // remaining waveforms are just derivations of the 2 main ones
+ for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
+ {
+ m_waveform[2][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index];
+ m_waveform[3][index] = bitfield(index, 9) ? zeroval : m_waveform[1][index];
+ m_waveform[4][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index * 2];
+ m_waveform[5][index] = bitfield(index, 9) ? zeroval : m_waveform[1][index * 2];
+ m_waveform[6][index] = bitfield(index, 9) ? zeroval : m_waveform[0][(index * 2) & 0x1ff];
+ m_waveform[7][index] = bitfield(index, 9) ? zeroval : m_waveform[1][(index * 2) & 0x1ff];
+ }
+
+ // create the LFO waveforms; AM in the low 8 bits, PM in the upper 8
+ // waveforms are adjusted to match the pictures in the application manual
+ for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++)
+ {
+ // waveform 0 is a sawtooth
+ uint8_t am = index ^ 0xff;
+ uint8_t pm = index;
+ m_lfo_waveform[0][index] = am | (pm << 8);
+
+ // waveform 1 is a square wave
+ am = bitfield(index, 7) ? 0 : 0xff;
+ pm = am ^ 0x80;
+ m_lfo_waveform[1][index] = am | (pm << 8);
+
+ // waveform 2 is a triangle wave
+ am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1);
+ pm = bitfield(index, 6) ? am : ~am;
+ m_lfo_waveform[2][index] = am | (pm << 8);
+
+ // waveform 3 is noise; it is filled in dynamically
+ }
+}
+
+
+//-------------------------------------------------
+// reset - reset to initial state
+//-------------------------------------------------
+
+void opz_registers::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+
+ // enable output on both channels by default
+ m_regdata[0x30] = m_regdata[0x31] = m_regdata[0x32] = m_regdata[0x33] = 0x01;
+ m_regdata[0x34] = m_regdata[0x35] = m_regdata[0x36] = m_regdata[0x37] = 0x01;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void opz_registers::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_lfo_counter);
+ state.save_restore(m_lfo_am);
+ state.save_restore(m_noise_lfsr);
+ state.save_restore(m_noise_counter);
+ state.save_restore(m_noise_state);
+ state.save_restore(m_noise_lfo);
+ state.save_restore(m_regdata);
+ state.save_restore(m_phase_substep);
+}
+
+
+//-------------------------------------------------
+// operator_map - return an array of operator
+// indices for each channel; for OPZ this is fixed
+//-------------------------------------------------
+
+void opz_registers::operator_map(operator_mapping &dest) const
+{
+ // Note that the channel index order is 0,2,1,3, so we bitswap the index.
+ //
+ // This is because the order in the map is:
+ // carrier 1, carrier 2, modulator 1, modulator 2
+ //
+ // But when wiring up the connections, the more natural order is:
+ // carrier 1, modulator 1, carrier 2, modulator 2
+ static const operator_mapping s_fixed_map =
+ { {
+ operator_list( 0, 16, 8, 24 ), // Channel 0 operators
+ operator_list( 1, 17, 9, 25 ), // Channel 1 operators
+ operator_list( 2, 18, 10, 26 ), // Channel 2 operators
+ operator_list( 3, 19, 11, 27 ), // Channel 3 operators
+ operator_list( 4, 20, 12, 28 ), // Channel 4 operators
+ operator_list( 5, 21, 13, 29 ), // Channel 5 operators
+ operator_list( 6, 22, 14, 30 ), // Channel 6 operators
+ operator_list( 7, 23, 15, 31 ), // Channel 7 operators
+ } };
+ dest = s_fixed_map;
+}
+
+
+//-------------------------------------------------
+// write - handle writes to the register array
+//-------------------------------------------------
+
+bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask)
+{
+ assert(index < REGISTERS);
+
+ // special mappings:
+ // 0x16 -> 0x188 if bit 7 is set
+ // 0x19 -> 0x189 if bit 7 is set
+ // 0x38..0x3F -> 0x180..0x187 if bit 7 is set
+ // 0x40..0x5F -> 0x100..0x11F if bit 7 is set
+ // 0xC0..0xDF -> 0x120..0x13F if bit 5 is set
+ if (index == 0x17 && bitfield(data, 7) != 0)
+ m_regdata[0x188] = data;
+ else if (index == 0x19 && bitfield(data, 7) != 0)
+ m_regdata[0x189] = data;
+ else if ((index & 0xf8) == 0x38 && bitfield(data, 7) != 0)
+ m_regdata[0x180 + (index & 7)] = data;
+ else if ((index & 0xe0) == 0x40 && bitfield(data, 7) != 0)
+ m_regdata[0x100 + (index & 0x1f)] = data;
+ else if ((index & 0xe0) == 0xc0 && bitfield(data, 5) != 0)
+ m_regdata[0x120 + (index & 0x1f)] = data;
+ else if (index < 0x100)
+ m_regdata[index] = data;
+
+ // preset writes restore some values from a preset memory; not sure
+ // how this really works but the TX81Z will overwrite the sustain level/
+ // release rate register and the envelope shift/reverb rate register to
+ // dampen sound, then write the preset number to register 8 to restore them
+ if (index == 0x08)
+ {
+ int chan = bitfield(data, 0, 3);
+ if (TEMPORARY_DEBUG_PRINTS)
+ printf("Loading preset %d\n", chan);
+ m_regdata[0xe0 + chan + 0] = m_regdata[0x140 + chan + 0];
+ m_regdata[0xe0 + chan + 8] = m_regdata[0x140 + chan + 8];
+ m_regdata[0xe0 + chan + 16] = m_regdata[0x140 + chan + 16];
+ m_regdata[0xe0 + chan + 24] = m_regdata[0x140 + chan + 24];
+ m_regdata[0x120 + chan + 0] = m_regdata[0x160 + chan + 0];
+ m_regdata[0x120 + chan + 8] = m_regdata[0x160 + chan + 8];
+ m_regdata[0x120 + chan + 16] = m_regdata[0x160 + chan + 16];
+ m_regdata[0x120 + chan + 24] = m_regdata[0x160 + chan + 24];
+ }
+
+ // store the presets under some unknown condition; the pattern of writes
+ // when setting a new preset is:
+ //
+ // 08 (0-7), 80-9F, A0-BF, C0-DF, C0-DF (alt), 20-27, 40-5F, 40-5F (alt),
+ // C0-DF (alt -- again?), 38-3F, 1B, 18, E0-FF
+ //
+ // So it writes 0-7 to 08 to either reset all presets or to indicate
+ // that we're going to be loading them. Immediately after all the writes
+ // above, the very next write will be temporary values to blow away the
+ // values loaded into E0-FF, so somehow it also knows that anything after
+ // that point is not part of the preset.
+ //
+ // For now, try using the 40-5F (alt) writes as flags that presets are
+ // being loaded until the E0-FF writes happen.
+ bool is_setting_preset = (bitfield(m_regdata[0x100 + (index & 0x1f)], 7) != 0);
+ if (is_setting_preset)
+ {
+ if ((index & 0xe0) == 0xe0)
+ {
+ m_regdata[0x140 + (index & 0x1f)] = data;
+ m_regdata[0x100 + (index & 0x1f)] &= 0x7f;
+ }
+ else if ((index & 0xe0) == 0xc0 && bitfield(data, 5) != 0)
+ m_regdata[0x160 + (index & 0x1f)] = data;
+ }
+
+ // handle writes to the key on index
+ if ((index & 0xf8) == 0x20 && bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3))
+ {
+ channel = bitfield(index, 0, 3);
+ opmask = ch_key_on(channel) ? 0xf : 0;
+
+ // according to the TX81Z manual, the sync option causes the LFOs
+ // to reset at each note on
+ if (opmask != 0)
+ {
+ if (lfo_sync())
+ m_lfo_counter[0] = 0;
+ if (lfo2_sync())
+ m_lfo_counter[1] = 0;
+ }
+ return true;
+ }
+ return false;
+}
+
+
+//-------------------------------------------------
+// clock_noise_and_lfo - clock the noise and LFO,
+// handling clock division, depth, and waveform
+// computations
+//-------------------------------------------------
+
+int32_t opz_registers::clock_noise_and_lfo()
+{
+ // base noise frequency is measured at 2x 1/2 FM frequency; this
+ // means each tick counts as two steps against the noise counter
+ uint32_t freq = noise_frequency();
+ for (int rep = 0; rep < 2; rep++)
+ {
+ // evidence seems to suggest the LFSR is clocked continually and just
+ // sampled at the noise frequency for output purposes; note that the
+ // low 8 bits are the most recent 8 bits of history while bits 8-24
+ // contain the 17 bit LFSR state
+ m_noise_lfsr <<= 1;
+ m_noise_lfsr |= bitfield(m_noise_lfsr, 17) ^ bitfield(m_noise_lfsr, 14) ^ 1;
+
+ // compare against the frequency and latch when we exceed it
+ if (m_noise_counter++ >= freq)
+ {
+ m_noise_counter = 0;
+ m_noise_state = bitfield(m_noise_lfsr, 17);
+ }
+ }
+
+ // treat the rate as a 4.4 floating-point step value with implied
+ // leading 1; this matches exactly the frequencies in the application
+ // manual, though it might not be implemented exactly this way on chip
+ uint32_t rate0 = lfo_rate();
+ uint32_t rate1 = lfo2_rate();
+ m_lfo_counter[0] += (0x10 | bitfield(rate0, 0, 4)) << bitfield(rate0, 4, 4);
+ m_lfo_counter[1] += (0x10 | bitfield(rate1, 0, 4)) << bitfield(rate1, 4, 4);
+ uint32_t lfo0 = bitfield(m_lfo_counter[0], 22, 8);
+ uint32_t lfo1 = bitfield(m_lfo_counter[1], 22, 8);
+
+ // fill in the noise entry 1 ahead of our current position; this
+ // ensures the current value remains stable for a full LFO clock
+ // and effectively latches the running value when the LFO advances
+ uint32_t lfo_noise = bitfield(m_noise_lfsr, 17, 8);
+ m_lfo_waveform[3][(lfo0 + 1) & 0xff] = lfo_noise | (lfo_noise << 8);
+ m_lfo_waveform[3][(lfo1 + 1) & 0xff] = lfo_noise | (lfo_noise << 8);
+
+ // fetch the AM/PM values based on the waveform; AM is unsigned and
+ // encoded in the low 8 bits, while PM signed and encoded in the upper
+ // 8 bits
+ int32_t ampm0 = m_lfo_waveform[lfo_waveform()][lfo0];
+ int32_t ampm1 = m_lfo_waveform[lfo2_waveform()][lfo1];
+
+ // apply depth to the AM values and store for later
+ m_lfo_am[0] = ((ampm0 & 0xff) * lfo_am_depth()) >> 7;
+ m_lfo_am[1] = ((ampm1 & 0xff) * lfo2_am_depth()) >> 7;
+
+ // apply depth to the PM values and return them combined into two
+ int32_t pm0 = ((ampm0 >> 8) * int32_t(lfo_pm_depth())) >> 7;
+ int32_t pm1 = ((ampm1 >> 8) * int32_t(lfo2_pm_depth())) >> 7;
+ return (pm0 & 0xff) | (pm1 << 8);
+}
+
+
+//-------------------------------------------------
+// lfo_am_offset - return the AM offset from LFO
+// for the given channel
+//-------------------------------------------------
+
+uint32_t opz_registers::lfo_am_offset(uint32_t choffs) const
+{
+ // not sure how this works for real, but just adding the two
+ // AM LFOs together
+ uint32_t result = 0;
+
+ // shift value for AM sensitivity is [*, 0, 1, 2],
+ // mapping to values of [0, 23.9, 47.8, and 95.6dB]
+ uint32_t am_sensitivity = ch_lfo_am_sens(choffs);
+ if (am_sensitivity != 0)
+ result = m_lfo_am[0] << (am_sensitivity - 1);
+
+ // QUESTION: see OPN note below for the dB range mapping; it applies
+ // here as well
+
+ // raw LFO AM value on OPZ is 0-FF, which is already a factor of 2
+ // larger than the OPN below, putting our staring point at 2x theirs;
+ // this works out since our minimum is 2x their maximum
+ uint32_t am_sensitivity2 = ch_lfo2_am_sens(choffs);
+ if (am_sensitivity2 != 0)
+ result += m_lfo_am[1] << (am_sensitivity2 - 1);
+
+ return result;
+}
+
+
+//-------------------------------------------------
+// cache_operator_data - fill the operator cache
+// with prefetched data
+//-------------------------------------------------
+
+void opz_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache)
+{
+ // TODO: how does fixed frequency mode work? appears to be enabled by
+ // op_fix_mode(), and controlled by op_fix_range(), op_fix_frequency()
+
+ // TODO: what is op_rev()?
+
+ // set up the easy stuff
+ cache.waveform = &m_waveform[op_waveform(opoffs)][0];
+
+ // get frequency from the channel
+ uint32_t block_freq = cache.block_freq = ch_block_freq(choffs);
+
+ // compute the keycode: block_freq is:
+ //
+ // BBBCCCCFFFFFF
+ // ^^^^^
+ //
+ // the 5-bit keycode is just the top 5 bits (block + top 2 bits
+ // of the key code)
+ uint32_t keycode = bitfield(block_freq, 8, 5);
+
+ // detune adjustment
+ cache.detune = detune_adjustment(op_detune(opoffs), keycode);
+
+ // multiple value, as an x.4 value (0 means 0.5)
+ // the "fine" control provides the fractional bits
+ cache.multiple = op_multiple(opoffs) << 4;
+ if (cache.multiple == 0)
+ cache.multiple = 0x08;
+ cache.multiple |= op_fine(opoffs);
+
+ // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on
+ // block_freq, detune, and multiple, so compute it after we've done those;
+ // note that fix frequency mode is also treated as dynamic
+ if (!op_fix_mode(opoffs) && (lfo_pm_depth() == 0 || ch_lfo_pm_sens(choffs) == 0) && (lfo2_pm_depth() == 0 || ch_lfo2_pm_sens(choffs) == 0))
+ cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0);
+ else
+ cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC;
+
+ // total level, scaled by 8
+ // TODO: how does ch_volume() fit into this?
+ cache.total_level = op_total_level(opoffs) << 3;
+
+ // 4-bit sustain level, but 15 means 31 so effectively 5 bits
+ cache.eg_sustain = op_sustain_level(opoffs);
+ cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
+ cache.eg_sustain <<= 5;
+
+ // determine KSR adjustment for enevlope rates
+ uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3);
+ cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval);
+ cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval);
+ cache.eg_rate[EG_REVERB] = cache.eg_rate[EG_RELEASE];
+ uint32_t reverb = op_reverb_rate(opoffs);
+ if (reverb != 0)
+ cache.eg_rate[EG_REVERB] = std::min<uint32_t>(effective_rate(reverb * 4 + 2, ksrval), cache.eg_rate[EG_REVERB]);
+
+ // set the envelope shift; TX81Z manual says operator 1 shift is fixed at "off"
+ cache.eg_shift = ((opoffs & 0x18) == 0) ? 0 : op_eg_shift(opoffs);
+}
+
+
+//-------------------------------------------------
+// compute_phase_step - compute the phase step
+//-------------------------------------------------
+
+uint32_t opz_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm)
+{
+ // OPZ has a fixed frequency mode; it is unclear whether the
+ // detune and multiple parameters affect things
+
+ uint32_t phase_step;
+ if (op_fix_mode(opoffs))
+ {
+ // the baseline frequency in hz comes from the fix frequency and fine
+ // registers, which can specify values 8-255Hz in 1Hz increments; that
+ // value is then shifted up by the 3-bit range
+ uint32_t freq = op_fix_frequency(opoffs) << 4;
+ if (freq == 0)
+ freq = 8;
+ freq |= op_fine(opoffs);
+ freq <<= op_fix_range(opoffs);
+
+ // there is not enough resolution in the plain phase step to track the
+ // full range of frequencies, so we keep a per-operator sub step with an
+ // additional 12 bits of resolution; this calculation gives us, for
+ // example, a frequency of 8.0009Hz when 8Hz is requested
+ uint32_t substep = m_phase_substep[opoffs];
+ substep += 75 * freq;
+ phase_step = substep >> 12;
+ m_phase_substep[opoffs] = substep & 0xfff;
+
+ // detune/multiple occupy the same space as fix_range/fix_frequency so
+ // don't apply them in addition
+ return phase_step;
+ }
+ else
+ {
+ // start with coarse detune delta; table uses cents value from
+ // manual, converted into 1/64ths
+ static const int16_t s_detune2_delta[4] = { 0, (600*64+50)/100, (781*64+50)/100, (950*64+50)/100 };
+ int32_t delta = s_detune2_delta[op_detune2(opoffs)];
+
+ // add in the PM deltas
+ uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs);
+ if (pm_sensitivity != 0)
+ {
+ // raw PM value is -127..128 which is +/- 200 cents
+ // manual gives these magnitudes in cents:
+ // 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700
+ // this roughly corresponds to shifting the 200-cent value:
+ // 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2
+ if (pm_sensitivity < 6)
+ delta += int8_t(lfo_raw_pm) >> (6 - pm_sensitivity);
+ else
+ delta += int8_t(lfo_raw_pm) << (pm_sensitivity - 5);
+ }
+ uint32_t pm_sensitivity2 = ch_lfo2_pm_sens(choffs);
+ if (pm_sensitivity2 != 0)
+ {
+ // raw PM value is -127..128 which is +/- 200 cents
+ // manual gives these magnitudes in cents:
+ // 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700
+ // this roughly corresponds to shifting the 200-cent value:
+ // 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2
+ if (pm_sensitivity2 < 6)
+ delta += int8_t(lfo_raw_pm >> 8) >> (6 - pm_sensitivity2);
+ else
+ delta += int8_t(lfo_raw_pm >> 8) << (pm_sensitivity2 - 5);
+ }
+
+ // apply delta and convert to a frequency number; this translation is
+ // the same as OPM so just re-use that helper
+ phase_step = opm_key_code_to_phase_step(cache.block_freq, delta);
+
+ // apply detune based on the keycode
+ phase_step += cache.detune;
+
+ // apply frequency multiplier (which is cached as an x.4 value)
+ return (phase_step * cache.multiple) >> 4;
+ }
+}
+
+
+//-------------------------------------------------
+// log_keyon - log a key-on event
+//-------------------------------------------------
+
+std::string opz_registers::log_keyon(uint32_t choffs, uint32_t opoffs)
+{
+ uint32_t chnum = choffs;
+ uint32_t opnum = opoffs;
+
+ char buffer[256];
+ int end = 0;
+
+ end += snprintf(&buffer[end], sizeof(buffer) - end, "%u.%02u", chnum, opnum);
+
+ if (op_fix_mode(opoffs))
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " fixfreq=%X fine=%X shift=%X", op_fix_frequency(opoffs), op_fine(opoffs), op_fix_range(opoffs));
+ else
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " freq=%04X dt2=%u fine=%X", ch_block_freq(choffs), op_detune2(opoffs), op_fine(opoffs));
+
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c",
+ op_detune(opoffs),
+ ch_feedback(choffs),
+ ch_algorithm(choffs),
+ op_multiple(opoffs),
+ op_total_level(opoffs),
+ op_ksr(opoffs),
+ op_attack_rate(opoffs),
+ op_decay_rate(opoffs),
+ op_sustain_rate(opoffs),
+ op_release_rate(opoffs),
+ op_sustain_level(opoffs),
+ ch_output_0(choffs) ? 'L' : '-',
+ ch_output_1(choffs) ? 'R' : '-');
+
+ if (op_eg_shift(opoffs) != 0)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " egshift=%u", op_eg_shift(opoffs));
+
+ bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
+ if (am)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth());
+ bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0);
+ if (pm)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
+ if (am || pm)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
+
+ bool am2 = (lfo2_am_depth() != 0 && ch_lfo2_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0);
+ if (am2)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " am2=%u/%02X", ch_lfo2_am_sens(choffs), lfo2_am_depth());
+ bool pm2 = (lfo2_pm_depth() != 0 && ch_lfo2_pm_sens(choffs) != 0);
+ if (pm2)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " pm2=%u/%02X", ch_lfo2_pm_sens(choffs), lfo2_pm_depth());
+ if (am2 || pm2)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " lfo2=%02X/%c", lfo2_rate(), "WQTN"[lfo2_waveform()]);
+
+ if (op_reverb_rate(opoffs) != 0)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " rev=%u", op_reverb_rate(opoffs));
+ if (op_waveform(opoffs) != 0)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " wf=%u", op_waveform(opoffs));
+ if (noise_enable() && opoffs == 31)
+ end += snprintf(&buffer[end], sizeof(buffer) - end, " noise=1");
+
+ return buffer;
+}
+
+
+
+//*********************************************************
+// YM2414
+//*********************************************************
+
+//-------------------------------------------------
+// ym2414 - constructor
+//-------------------------------------------------
+
+ym2414::ym2414(ymfm_interface &intf) :
+ m_address(0),
+ m_fm(intf)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the system
+//-------------------------------------------------
+
+void ym2414::reset()
+{
+ // reset the engines
+ m_fm.reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ym2414::save_restore(ymfm_saved_state &state)
+{
+ m_fm.save_restore(state);
+ state.save_restore(m_address);
+}
+
+
+//-------------------------------------------------
+// read_status - read the status register
+//-------------------------------------------------
+
+uint8_t ym2414::read_status()
+{
+ uint8_t result = m_fm.status();
+ if (m_fm.intf().ymfm_is_busy())
+ result |= fm_engine::STATUS_BUSY;
+ return result;
+}
+
+
+//-------------------------------------------------
+// read - handle a read from the device
+//-------------------------------------------------
+
+uint8_t ym2414::read(uint32_t offset)
+{
+ uint8_t result = 0xff;
+ switch (offset & 1)
+ {
+ case 0: // data port (unused)
+ debug::log_unexpected_read_write("Unexpected read from YM2414 offset %d\n", offset & 3);
+ break;
+
+ case 1: // status port, YM2203 compatible
+ result = read_status();
+ break;
+ }
+ return result;
+}
+
+
+//-------------------------------------------------
+// write_address - handle a write to the address
+// register
+//-------------------------------------------------
+
+void ym2414::write_address(uint8_t data)
+{
+ // just set the address
+ m_address = data;
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2414::write_data(uint8_t data)
+{
+ // write the FM register
+ m_fm.write(m_address, data);
+ if (TEMPORARY_DEBUG_PRINTS)
+ {
+ switch (m_address & 0xe0)
+ {
+ case 0x00:
+ printf("CTL %02X = %02X\n", m_address, data);
+ break;
+
+ case 0x20:
+ switch (m_address & 0xf8)
+ {
+ case 0x20: printf("R/FBL/ALG %d = %02X\n", m_address & 7, data); break;
+ case 0x28: printf("KC %d = %02X\n", m_address & 7, data); break;
+ case 0x30: printf("KF/M %d = %02X\n", m_address & 7, data); break;
+ case 0x38: printf("PMS/AMS %d = %02X\n", m_address & 7, data); break;
+ }
+ break;
+
+ case 0x40:
+ if (bitfield(data, 7) == 0)
+ printf("DT1/MUL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
+ else
+ printf("OW/FINE %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
+ break;
+
+ case 0x60:
+ printf("TL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
+ break;
+
+ case 0x80:
+ printf("KRS/FIX/AR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
+ break;
+
+ case 0xa0:
+ printf("A/D1R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
+ break;
+
+ case 0xc0:
+ if (bitfield(data, 5) == 0)
+ printf("DT2/D2R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
+ else
+ printf("EGS/REV %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
+ break;
+
+ case 0xe0:
+ printf("D1L/RR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data);
+ break;
+ }
+ }
+
+ // special cases
+ if (m_address == 0x1b)
+ {
+ // writes to register 0x1B send the upper 2 bits to the output lines
+ m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6);
+ }
+
+ // mark busy for a bit
+ m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
+}
+
+
+//-------------------------------------------------
+// write - handle a write to the register
+// interface
+//-------------------------------------------------
+
+void ym2414::write(uint32_t offset, uint8_t data)
+{
+ switch (offset & 1)
+ {
+ case 0: // address port
+ write_address(data);
+ break;
+
+ case 1: // data port
+ write_data(data);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// generate - generate one sample of sound
+//-------------------------------------------------
+
+void ym2414::generate(output_data *output, uint32_t numsamples)
+{
+ for (uint32_t samp = 0; samp < numsamples; samp++, output++)
+ {
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // update the FM content; YM2414 is full 14-bit with no intermediate clipping
+ m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
+
+ // unsure about YM2414 outputs; assume it is like YM2151
+ output->roundtrip_fp();
+ }
+}
+
+}
diff --git a/3rdparty/ymfm/src/ymfm_opz.h b/3rdparty/ymfm/src/ymfm_opz.h
new file mode 100644
index 00000000000..997ba32f9f2
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_opz.h
@@ -0,0 +1,332 @@
+// 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_OPZ_H
+#define YMFM_OPZ_H
+
+#pragma once
+
+#include "ymfm.h"
+#include "ymfm_fm.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// REGISTER CLASSES
+//*********************************************************
+
+// ======================> opz_registers
+
+//
+// OPZ register map:
+//
+// System-wide registers:
+// 08 -----xxx Load preset (not sure how it gets saved)
+// 0F x------- Noise enable
+// ---xxxxx Noise frequency
+// 10 xxxxxxxx Timer A value (upper 8 bits)
+// 11 ------xx Timer A value (lower 2 bits)
+// 12 xxxxxxxx Timer B value
+// 14 x------- CSM mode
+// --x----- Reset timer B
+// ---x---- Reset timer A
+// ----x--- Enable timer B
+// -----x-- Enable timer A
+// ------x- Load timer B
+// -------x Load timer A
+// 16 xxxxxxxx LFO #2 frequency
+// 17 0xxxxxxx AM LFO #2 depth
+// 1xxxxxxx PM LFO #2 depth
+// 18 xxxxxxxx LFO frequency
+// 19 0xxxxxxx AM LFO depth
+// 1xxxxxxx PM LFO depth
+// 1B xx------ CT (2 output data lines)
+// --x----- LFO #2 sync
+// ---x---- LFO sync
+// ----xx-- LFO #2 waveform
+// ------xx LFO waveform
+//
+// Per-channel registers (channel in address bits 0-2)
+// 00-07 xxxxxxxx Channel volume
+// 20-27 x------- Pan right
+// -x------ Key on (0)/off(1)
+// --xxx--- Feedback level for operator 1 (0-7)
+// -----xxx Operator connection algorithm (0-7)
+// 28-2F -xxxxxxx Key code
+// 30-37 xxxxxx-- Key fraction
+// -------x Mono? mode
+// 38-3F 0xxx---- LFO PM sensitivity
+// -----0xx LFO AM shift
+// 1xxx---- LFO #2 PM sensitivity
+// -----1xx LFO #2 AM shift
+//
+// Per-operator registers (channel in address bits 0-2, operator in bits 3-4)
+// 40-5F 0xxx---- Detune value (0-7)
+// 0---xxxx Multiple value (0-15)
+// 0xxx---- Fix range (0-15)
+// 0---xxxx Fix frequency (0-15)
+// 1xxx---- Oscillator waveform (0-7)
+// 1---xxxx Fine? (0-15)
+// 60-7F -xxxxxxx Total level (0-127)
+// 80-9F xx------ Key scale rate (0-3)
+// --x----- Fix frequency mode
+// ---xxxxx Attack rate (0-31)
+// A0-BF x------- LFO AM enable
+// ---xxxxx Decay rate (0-31)
+// C0-DF xx0----- Detune 2 value (0-3)
+// --0xxxxx Sustain rate (0-31)
+// xx1----- Envelope generator shift? (0-3)
+// --1--xxx Rev? (0-7)
+// E0-FF xxxx---- Sustain level (0-15)
+// ----xxxx Release rate (0-15)
+//
+// Internal (fake) registers:
+// 100-11F -xxx---- Oscillator waveform (0-7)
+// ----xxxx Fine? (0-15)
+// 120-13F xx------ Envelope generator shift (0-3)
+// -----xxx Reverb rate (0-7)
+// 140-15F xxxx---- Preset sustain level (0-15)
+// ----xxxx Preset release rate (0-15)
+// 160-17F xx------ Envelope generator shift (0-3)
+// -----xxx Reverb rate (0-7)
+// 180-187 -xxx---- LFO #2 PM sensitivity
+// ---- xxx LFO #2 AM shift
+// 188 -xxxxxxx LFO #2 PM depth
+// 189 -xxxxxxx LFO PM depth
+//
+
+class opz_registers : public fm_registers_base
+{
+ // LFO waveforms are 256 entries long
+ static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256;
+
+public:
+ // constants
+ static constexpr uint32_t OUTPUTS = 2;
+ static constexpr uint32_t CHANNELS = 8;
+ static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
+ static constexpr uint32_t OPERATORS = CHANNELS * 4;
+ static constexpr uint32_t WAVEFORMS = 8;
+ static constexpr uint32_t REGISTERS = 0x190;
+ static constexpr uint32_t DEFAULT_PRESCALE = 2;
+ static constexpr uint32_t EG_CLOCK_DIVIDER = 3;
+ static constexpr bool EG_HAS_REVERB = true;
+ static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
+ static constexpr uint32_t REG_MODE = 0x14;
+ 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
+ opz_registers();
+
+ // 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);
+ return chnum;
+ }
+
+ // map operator number to register offset
+ static constexpr uint32_t operator_offset(uint32_t opnum)
+ {
+ assert(opnum < OPERATORS);
+ return opnum;
+ }
+
+ // return an array of operator indices for each channel
+ struct operator_mapping { uint32_t chan[CHANNELS]; };
+ void operator_map(operator_mapping &dest) const;
+
+ // 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();
+
+ // return the AM offset from LFO for the given channel
+ uint32_t lfo_am_offset(uint32_t choffs) const;
+
+ // return the current noise state, gated by the noise clock
+ uint32_t noise_state() const { return m_noise_state; }
+
+ // 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 noise_frequency() const { return byte(0x0f, 0, 5); }
+ uint32_t noise_enable() const { return byte(0x0f, 7, 1); }
+ uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); }
+ uint32_t timer_b_value() const { return byte(0x12, 0, 8); }
+ uint32_t csm() const { return byte(0x14, 7, 1); }
+ uint32_t reset_timer_b() const { return byte(0x14, 5, 1); }
+ uint32_t reset_timer_a() const { return byte(0x14, 4, 1); }
+ uint32_t enable_timer_b() const { return byte(0x14, 3, 1); }
+ uint32_t enable_timer_a() const { return byte(0x14, 2, 1); }
+ uint32_t load_timer_b() const { return byte(0x14, 1, 1); }
+ uint32_t load_timer_a() const { return byte(0x14, 0, 1); }
+ uint32_t lfo2_pm_depth() const { return byte(0x188, 0, 7); } // fake
+ uint32_t lfo2_rate() const { return byte(0x16, 0, 8); }
+ uint32_t lfo2_am_depth() const { return byte(0x17, 0, 7); }
+ uint32_t lfo_rate() const { return byte(0x18, 0, 8); }
+ uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); }
+ uint32_t lfo_pm_depth() const { return byte(0x189, 0, 7); } // fake
+ uint32_t output_bits() const { return byte(0x1b, 6, 2); }
+ uint32_t lfo2_sync() const { return byte(0x1b, 5, 1); }
+ uint32_t lfo_sync() const { return byte(0x1b, 4, 1); }
+ uint32_t lfo2_waveform() const { return byte(0x1b, 2, 2); }
+ uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); }
+
+ // per-channel registers
+ uint32_t ch_volume(uint32_t choffs) const { return byte(0x00, 0, 8, choffs); }
+ uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); }
+ uint32_t ch_output_0(uint32_t choffs) const { return byte(0x30, 0, 1, choffs); }
+ uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); }
+ 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_key_on(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); }
+ uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); }
+ uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); }
+ uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); }
+ uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); }
+ uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); }
+ uint32_t ch_lfo2_pm_sens(uint32_t choffs) const { return byte(0x180, 4, 3, choffs); } // fake
+ uint32_t ch_lfo2_am_sens(uint32_t choffs) const { return byte(0x180, 0, 2, choffs); } // fake
+
+ // per-operator registers
+ uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); }
+ uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); }
+ uint32_t op_fix_range(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); }
+ uint32_t op_fix_frequency(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); }
+ uint32_t op_waveform(uint32_t opoffs) const { return byte(0x100, 4, 3, opoffs); } // fake
+ uint32_t op_fine(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); } // fake
+ uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); }
+ uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); }
+ uint32_t op_fix_mode(uint32_t opoffs) const { return byte(0x80, 5, 1, opoffs); }
+ uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); }
+ uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); }
+ uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); }
+ uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); }
+ uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); }
+ uint32_t op_eg_shift(uint32_t opoffs) const { return byte(0x120, 6, 2, opoffs); } // fake
+ uint32_t op_reverb_rate(uint32_t opoffs) const { return byte(0x120, 0, 3, opoffs); } // fake
+ uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); }
+ uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, 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[2]; // LFO counter
+ uint32_t m_noise_lfsr; // noise LFSR state
+ uint8_t m_noise_counter; // noise counter
+ uint8_t m_noise_state; // latched noise state
+ uint8_t m_noise_lfo; // latched LFO noise value
+ uint8_t m_lfo_am[2]; // current LFO AM value
+ uint8_t m_regdata[REGISTERS]; // register data
+ uint16_t m_phase_substep[OPERATORS]; // phase substep for fixed frequency
+ int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8
+ uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
+};
+
+
+
+//*********************************************************
+// IMPLEMENTATION CLASSES
+//*********************************************************
+
+// ======================> ym2414
+
+class ym2414
+{
+public:
+ using fm_engine = fm_engine_base<opz_registers>;
+ static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
+ using output_data = fm_engine::output_data;
+
+ // constructor
+ ym2414(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(uint32_t offset, uint8_t data);
+
+ // generate one sample of sound
+ void generate(output_data *output, uint32_t numsamples = 1);
+
+protected:
+ // internal state
+ uint8_t m_address; // address register
+ fm_engine m_fm; // core FM engine
+};
+
+}
+
+
+#endif // YMFM_OPZ_H
diff --git a/3rdparty/ymfm/src/ymfm_pcm.cpp b/3rdparty/ymfm/src/ymfm_pcm.cpp
new file mode 100644
index 00000000000..34417490ccd
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_pcm.cpp
@@ -0,0 +1,714 @@
+// 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.
+
+#include "ymfm_pcm.h"
+#include "ymfm_fm.h"
+#include "ymfm_fm.ipp"
+
+namespace ymfm
+{
+
+//*********************************************************
+// PCM REGISTERS
+//*********************************************************
+
+//-------------------------------------------------
+// reset - reset the register state
+//-------------------------------------------------
+
+void pcm_registers::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+ m_regdata[0xf8] = 0x1b;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void pcm_registers::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_regdata);
+}
+
+
+//-------------------------------------------------
+// cache_channel_data - update the cache with
+// data from the registers
+//-------------------------------------------------
+
+void pcm_registers::cache_channel_data(uint32_t choffs, pcm_cache &cache)
+{
+ // compute step from octave and fnumber; the math here implies
+ // a .18 fraction but .16 should be perfectly fine
+ int32_t octave = int8_t(ch_octave(choffs) << 4) >> 4;
+ uint32_t fnum = ch_fnumber(choffs);
+ cache.step = ((0x400 | fnum) << (octave + 7)) >> 2;
+
+ // total level is computed as a .10 value for interpolation
+ cache.total_level = ch_total_level(choffs) << 10;
+
+ // compute panning values in terms of envelope attenuation
+ int32_t panpot = int8_t(ch_panpot(choffs) << 4) >> 4;
+ if (panpot >= 0)
+ {
+ cache.pan_left = (panpot == 7) ? 0x3ff : 0x20 * panpot;
+ cache.pan_right = 0;
+ }
+ else if (panpot >= -7)
+ {
+ cache.pan_left = 0;
+ cache.pan_right = (panpot == -7) ? 0x3ff : -0x20 * panpot;
+ }
+ else
+ cache.pan_left = cache.pan_right = 0x3ff;
+
+ // determine the LFO stepping value; this how much to add to a running
+ // x.18 value for the LFO; steps were derived from frequencies in the
+ // manual and come out very close with these values
+ static const uint8_t s_lfo_steps[8] = { 1, 12, 19, 25, 31, 35, 37, 42 };
+ cache.lfo_step = s_lfo_steps[ch_lfo_speed(choffs)];
+
+ // AM LFO depth values, derived from the manual; note each has at most
+ // 2 bits to make the "multiply" easy in hardware
+ static const uint8_t s_am_depth[8] = { 0, 0x14, 0x20, 0x28, 0x30, 0x40, 0x50, 0x80 };
+ cache.am_depth = s_am_depth[ch_am_depth(choffs)];
+
+ // PM LFO depth values; these are converted from the manual's cents values
+ // into f-numbers; the computations come out quite cleanly so pretty sure
+ // these are correct
+ static const uint8_t s_pm_depth[8] = { 0, 2, 3, 4, 6, 12, 24, 48 };
+ cache.pm_depth = s_pm_depth[ch_vibrato(choffs)];
+
+ // 4-bit sustain level, but 15 means 31 so effectively 5 bits
+ cache.eg_sustain = ch_sustain_level(choffs);
+ cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
+ cache.eg_sustain <<= 5;
+
+ // compute the key scaling correction factor; 15 means don't do any correction
+ int32_t correction = ch_rate_correction(choffs);
+ if (correction == 15)
+ correction = 0;
+ else
+ correction = (octave + correction) * 2 + bitfield(fnum, 9);
+
+ // compute the envelope generator rates
+ cache.eg_rate[EG_ATTACK] = effective_rate(ch_attack_rate(choffs), correction);
+ cache.eg_rate[EG_DECAY] = effective_rate(ch_decay_rate(choffs), correction);
+ cache.eg_rate[EG_SUSTAIN] = effective_rate(ch_sustain_rate(choffs), correction);
+ cache.eg_rate[EG_RELEASE] = effective_rate(ch_release_rate(choffs), correction);
+ cache.eg_rate[EG_REVERB] = 5;
+
+ // if damping is on, override some things; essentially decay at a hardcoded
+ // rate of 48 until -12db (0x80), then at maximum rate for the rest
+ if (ch_damp(choffs) != 0)
+ {
+ cache.eg_rate[EG_DECAY] = 48;
+ cache.eg_rate[EG_SUSTAIN] = 63;
+ cache.eg_rate[EG_RELEASE] = 63;
+ cache.eg_sustain = 0x80;
+ }
+}
+
+
+//-------------------------------------------------
+// effective_rate - return the effective rate,
+// clamping and applying corrections as needed
+//-------------------------------------------------
+
+uint32_t pcm_registers::effective_rate(uint32_t raw, uint32_t correction)
+{
+ // raw rates of 0 and 15 just pin to min/max
+ if (raw == 0)
+ return 0;
+ if (raw == 15)
+ return 63;
+
+ // otherwise add the correction and clamp to range
+ return clamp(raw * 4 + correction, 0, 63);
+}
+
+
+
+//*********************************************************
+// PCM CHANNEL
+//*********************************************************
+
+//-------------------------------------------------
+// pcm_channel - constructor
+//-------------------------------------------------
+
+pcm_channel::pcm_channel(pcm_engine &owner, uint32_t choffs) :
+ m_choffs(choffs),
+ m_baseaddr(0),
+ m_endpos(0),
+ m_looppos(0),
+ m_curpos(0),
+ m_nextpos(0),
+ m_lfo_counter(0),
+ m_eg_state(EG_RELEASE),
+ m_env_attenuation(0x3ff),
+ m_total_level(0x7f << 10),
+ m_format(0),
+ m_key_state(0),
+ m_regs(owner.regs()),
+ m_owner(owner)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the channel state
+//-------------------------------------------------
+
+void pcm_channel::reset()
+{
+ m_baseaddr = 0;
+ m_endpos = 0;
+ m_looppos = 0;
+ m_curpos = 0;
+ m_nextpos = 0;
+ m_lfo_counter = 0;
+ m_eg_state = EG_RELEASE;
+ m_env_attenuation = 0x3ff;
+ m_total_level = 0x7f << 10;
+ m_format = 0;
+ m_key_state = 0;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void pcm_channel::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_baseaddr);
+ state.save_restore(m_endpos);
+ state.save_restore(m_looppos);
+ state.save_restore(m_curpos);
+ state.save_restore(m_nextpos);
+ state.save_restore(m_lfo_counter);
+ state.save_restore(m_eg_state);
+ state.save_restore(m_env_attenuation);
+ state.save_restore(m_total_level);
+ state.save_restore(m_format);
+ state.save_restore(m_key_state);
+}
+
+
+//-------------------------------------------------
+// prepare - prepare for clocking
+//-------------------------------------------------
+
+bool pcm_channel::prepare()
+{
+ // cache the data
+ m_regs.cache_channel_data(m_choffs, m_cache);
+
+ // clock the key state
+ if ((m_key_state & KEY_PENDING) != 0)
+ {
+ uint8_t oldstate = m_key_state;
+ m_key_state = (m_key_state >> 1) & KEY_ON;
+ if (((oldstate ^ m_key_state) & KEY_ON) != 0)
+ {
+ if ((m_key_state & KEY_ON) != 0)
+ start_attack();
+ else
+ start_release();
+ }
+ }
+
+ // set the total level directly if not interpolating
+ if (m_regs.ch_level_direct(m_choffs))
+ m_total_level = m_cache.total_level;
+
+ // we're active until we're quiet after the release
+ return (m_eg_state < EG_RELEASE || m_env_attenuation < EG_QUIET);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+void pcm_channel::clock(uint32_t env_counter)
+{
+ // clock the LFO, which is an x.18 value incremented based on the
+ // LFO speed value
+ m_lfo_counter += m_cache.lfo_step;
+
+ // clock the envelope
+ clock_envelope(env_counter);
+
+ // determine the step after applying vibrato
+ uint32_t step = m_cache.step;
+ if (m_cache.pm_depth != 0)
+ {
+ // shift the LFO by 1/4 cycle for PM so that it starts at 0
+ uint32_t lfo_shifted = m_lfo_counter + (1 << 16);
+ int32_t lfo_value = bitfield(lfo_shifted, 10, 7);
+ if (bitfield(lfo_shifted, 17) != 0)
+ lfo_value ^= 0x7f;
+ lfo_value -= 0x40;
+ step += (lfo_value * int32_t(m_cache.pm_depth)) >> 7;
+ }
+
+ // advance the sample step and loop as needed
+ m_curpos = m_nextpos;
+ m_nextpos = m_curpos + step;
+ if (m_nextpos >= m_endpos)
+ m_nextpos += m_looppos - m_endpos;
+
+ // interpolate total level if needed
+ if (m_total_level != m_cache.total_level)
+ {
+ // max->min volume takes 156.4ms, or pretty close to 19/1024 per 44.1kHz sample
+ // min->max volume is half that, so advance by 38/1024 per sample
+ if (m_total_level < m_cache.total_level)
+ m_total_level = std::min<int32_t>(m_total_level + 19, m_cache.total_level);
+ else
+ m_total_level = std::max<int32_t>(m_total_level - 38, m_cache.total_level);
+ }
+}
+
+
+//-------------------------------------------------
+// output - return the computed output value, with
+// panning applied
+//-------------------------------------------------
+
+void pcm_channel::output(output_data &output) const
+{
+ // early out if the envelope is effectively off
+ uint32_t envelope = m_env_attenuation;
+ if (envelope > EG_QUIET)
+ return;
+
+ // add in LFO AM modulation
+ if (m_cache.am_depth != 0)
+ {
+ uint32_t lfo_value = bitfield(m_lfo_counter, 10, 7);
+ if (bitfield(m_lfo_counter, 17) != 0)
+ lfo_value ^= 0x7f;
+ envelope += (lfo_value * m_cache.am_depth) >> 7;
+ }
+
+ // add in the current interpolated total level value, which is a .10
+ // value shifted left by 2
+ envelope += m_total_level >> 8;
+
+ // add in panning effect and clamp
+ uint32_t lenv = std::min<uint32_t>(envelope + m_cache.pan_left, 0x3ff);
+ uint32_t renv = std::min<uint32_t>(envelope + m_cache.pan_right, 0x3ff);
+
+ // convert to volume as a .11 fraction
+ int32_t lvol = attenuation_to_volume(lenv << 2);
+ int32_t rvol = attenuation_to_volume(renv << 2);
+
+ // fetch current sample and add
+ int16_t sample = fetch_sample();
+ uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2;
+ output.data[outnum + 0] += (lvol * sample) >> 15;
+ output.data[outnum + 1] += (rvol * sample) >> 15;
+}
+
+
+//-------------------------------------------------
+// keyonoff - signal key on/off
+//-------------------------------------------------
+
+void pcm_channel::keyonoff(bool on)
+{
+ // mark the key state as pending
+ m_key_state |= KEY_PENDING | (on ? KEY_PENDING_ON : 0);
+
+ // don't log masked channels
+ if ((m_key_state & (KEY_PENDING_ON | KEY_ON)) == KEY_PENDING_ON && ((debug::GLOBAL_PCM_CHANNEL_MASK >> m_choffs) & 1) != 0)
+ {
+ debug::log_keyon("KeyOn PCM-%02d: num=%3d oct=%2d fnum=%03X level=%02X%c ADSR=%X/%X/%X/%X SL=%X",
+ m_choffs,
+ m_regs.ch_wave_table_num(m_choffs),
+ int8_t(m_regs.ch_octave(m_choffs) << 4) >> 4,
+ m_regs.ch_fnumber(m_choffs),
+ m_regs.ch_total_level(m_choffs),
+ m_regs.ch_level_direct(m_choffs) ? '!' : '/',
+ m_regs.ch_attack_rate(m_choffs),
+ m_regs.ch_decay_rate(m_choffs),
+ m_regs.ch_sustain_rate(m_choffs),
+ m_regs.ch_release_rate(m_choffs),
+ m_regs.ch_sustain_level(m_choffs));
+
+ if (m_regs.ch_rate_correction(m_choffs) != 15)
+ debug::log_keyon(" RC=%X", m_regs.ch_rate_correction(m_choffs));
+
+ if (m_regs.ch_pseudo_reverb(m_choffs) != 0)
+ debug::log_keyon(" %s", "REV");
+ if (m_regs.ch_damp(m_choffs) != 0)
+ debug::log_keyon(" %s", "DAMP");
+
+ if (m_regs.ch_vibrato(m_choffs) != 0 || m_regs.ch_am_depth(m_choffs) != 0)
+ {
+ if (m_regs.ch_vibrato(m_choffs) != 0)
+ debug::log_keyon(" VIB=%d", m_regs.ch_vibrato(m_choffs));
+ if (m_regs.ch_am_depth(m_choffs) != 0)
+ debug::log_keyon(" AM=%d", m_regs.ch_am_depth(m_choffs));
+ debug::log_keyon(" LFO=%d", m_regs.ch_lfo_speed(m_choffs));
+ }
+ debug::log_keyon("%s", "\n");
+ }
+}
+
+
+//-------------------------------------------------
+// load_wavetable - load a wavetable by fetching
+// its data from external memory
+//-------------------------------------------------
+
+void pcm_channel::load_wavetable()
+{
+ // determine the address of the wave table header
+ uint32_t wavnum = m_regs.ch_wave_table_num(m_choffs);
+ uint32_t wavheader = 12 * wavnum;
+
+ // above 384 it may be in a different bank
+ if (wavnum >= 384)
+ {
+ uint32_t bank = m_regs.wave_table_header();
+ if (bank != 0)
+ wavheader = 512*1024 * bank + (wavnum - 384) * 12;
+ }
+
+ // fetch the 22-bit base address and 2-bit format
+ uint8_t byte = read_pcm(wavheader + 0);
+ m_format = bitfield(byte, 6, 2);
+ m_baseaddr = bitfield(byte, 0, 6) << 16;
+ m_baseaddr |= read_pcm(wavheader + 1) << 8;
+ m_baseaddr |= read_pcm(wavheader + 2) << 0;
+
+ // fetch the 16-bit loop position
+ m_looppos = read_pcm(wavheader + 3) << 8;
+ m_looppos |= read_pcm(wavheader + 4);
+ m_looppos <<= 16;
+
+ // fetch the 16-bit end position, which is stored as a negative value
+ // for some reason that is unclear
+ m_endpos = read_pcm(wavheader + 5) << 8;
+ m_endpos |= read_pcm(wavheader + 6);
+ m_endpos = -int32_t(m_endpos) << 16;
+
+ // remaining data values set registers
+ m_owner.write(0x80 + m_choffs, read_pcm(wavheader + 7));
+ m_owner.write(0x98 + m_choffs, read_pcm(wavheader + 8));
+ m_owner.write(0xb0 + m_choffs, read_pcm(wavheader + 9));
+ m_owner.write(0xc8 + m_choffs, read_pcm(wavheader + 10));
+ m_owner.write(0xe0 + m_choffs, read_pcm(wavheader + 11));
+
+ // reset the envelope so we don't continue playing mid-sample from previous key ons
+ m_env_attenuation = 0x3ff;
+}
+
+
+//-------------------------------------------------
+// read_pcm - read a byte from the external PCM
+// memory interface
+//-------------------------------------------------
+
+uint8_t pcm_channel::read_pcm(uint32_t address) const
+{
+ return m_owner.intf().ymfm_external_read(ACCESS_PCM, address);
+}
+
+
+//-------------------------------------------------
+// start_attack - start the attack phase
+//-------------------------------------------------
+
+void pcm_channel::start_attack()
+{
+ // don't change anything if already in attack state
+ if (m_eg_state == EG_ATTACK)
+ return;
+ m_eg_state = EG_ATTACK;
+
+ // reset the LFO if requested
+ if (m_regs.ch_lfo_reset(m_choffs))
+ m_lfo_counter = 0;
+
+ // if the attack rate == 63 then immediately go to max attenuation
+ if (m_cache.eg_rate[EG_ATTACK] == 63)
+ m_env_attenuation = 0;
+
+ // reset the positions
+ m_curpos = m_nextpos = 0;
+}
+
+
+//-------------------------------------------------
+// start_release - start the release phase
+//-------------------------------------------------
+
+void pcm_channel::start_release()
+{
+ // don't change anything if already in release or reverb state
+ if (m_eg_state >= EG_RELEASE)
+ return;
+ m_eg_state = EG_RELEASE;
+}
+
+
+//-------------------------------------------------
+// clock_envelope - clock the envelope generator
+//-------------------------------------------------
+
+void pcm_channel::clock_envelope(uint32_t env_counter)
+{
+ // handle attack->decay transitions
+ if (m_eg_state == EG_ATTACK && m_env_attenuation == 0)
+ m_eg_state = EG_DECAY;
+
+ // handle decay->sustain transitions
+ if (m_eg_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain)
+ m_eg_state = EG_SUSTAIN;
+
+ // fetch the appropriate 6-bit rate value from the cache
+ uint32_t rate = m_cache.eg_rate[m_eg_state];
+
+ // compute the rate shift value; this is the shift needed to
+ // apply to the env_counter such that it becomes a 5.11 fixed
+ // point number
+ uint32_t rate_shift = rate >> 2;
+ env_counter <<= rate_shift;
+
+ // see if the fractional part is 0; if not, it's not time to clock
+ if (bitfield(env_counter, 0, 11) != 0)
+ return;
+
+ // determine the increment based on the non-fractional part of env_counter
+ uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3);
+ uint32_t increment = attenuation_increment(rate, relevant_bits);
+
+ // attack is the only one that increases
+ if (m_eg_state == EG_ATTACK)
+ m_env_attenuation += (~m_env_attenuation * increment) >> 4;
+
+ // all other cases are similar
+ else
+ {
+ // apply the increment
+ m_env_attenuation += increment;
+
+ // clamp the final attenuation
+ if (m_env_attenuation >= 0x400)
+ m_env_attenuation = 0x3ff;
+
+ // transition to reverb at -18dB if enabled
+ if (m_env_attenuation >= 0xc0 && m_eg_state < EG_REVERB && m_regs.ch_pseudo_reverb(m_choffs))
+ m_eg_state = EG_REVERB;
+ }
+}
+
+
+//-------------------------------------------------
+// fetch_sample - fetch a sample at the current
+// position
+//-------------------------------------------------
+
+int16_t pcm_channel::fetch_sample() const
+{
+ uint32_t addr = m_baseaddr;
+ uint32_t pos = m_curpos >> 16;
+
+ // 8-bit PCM: shift up by 8
+ if (m_format == 0)
+ return read_pcm(addr + pos) << 8;
+
+ // 16-bit PCM: assemble from 2 halves
+ if (m_format == 2)
+ {
+ addr += pos * 2;
+ return (read_pcm(addr) << 8) | read_pcm(addr + 1);
+ }
+
+ // 12-bit PCM: assemble out of half of 3 bytes
+ addr += (pos / 2) * 3;
+ if ((pos & 1) == 0)
+ return (read_pcm(addr + 0) << 8) | ((read_pcm(addr + 1) << 4) & 0xf0);
+ else
+ return (read_pcm(addr + 2) << 8) | ((read_pcm(addr + 1) << 0) & 0xf0);
+}
+
+
+
+//*********************************************************
+// PCM ENGINE
+//*********************************************************
+
+//-------------------------------------------------
+// pcm_engine - constructor
+//-------------------------------------------------
+
+pcm_engine::pcm_engine(ymfm_interface &intf) :
+ m_intf(intf),
+ m_env_counter(0),
+ m_modified_channels(ALL_CHANNELS),
+ m_active_channels(ALL_CHANNELS)
+{
+ // create the channels
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ m_channel[chnum] = std::make_unique<pcm_channel>(*this, chnum);
+}
+
+
+//-------------------------------------------------
+// reset - reset the engine state
+//-------------------------------------------------
+
+void pcm_engine::reset()
+{
+ // reset register state
+ m_regs.reset();
+
+ // reset each channel
+ for (auto &chan : m_channel)
+ chan->reset();
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void pcm_engine::save_restore(ymfm_saved_state &state)
+{
+ // save our data
+ state.save_restore(m_env_counter);
+
+ // save channel state
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ m_channel[chnum]->save_restore(state);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+void pcm_engine::clock(uint32_t chanmask)
+{
+ // if something was modified, prepare
+ // also prepare every 4k samples to catch ending notes
+ if (m_modified_channels != 0 || m_prepare_count++ >= 4096)
+ {
+ // call each channel to prepare
+ m_active_channels = 0;
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ if (m_channel[chnum]->prepare())
+ m_active_channels |= 1 << chnum;
+
+ // reset the modified channels and prepare count
+ m_modified_channels = m_prepare_count = 0;
+ }
+
+ // increment the envelope counter; the envelope generator
+ // only clocks every other sample in order to make the PCM
+ // envelopes line up with the FM envelopes (after taking into
+ // account the different FM sampling rate)
+ m_env_counter++;
+
+ // now update the state of all the channels and operators
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ m_channel[chnum]->clock(m_env_counter >> 1);
+}
+
+
+//-------------------------------------------------
+// update - master update function
+//-------------------------------------------------
+
+void pcm_engine::output(output_data &output, uint32_t chanmask)
+{
+ // mask out some channels for debug purposes
+ chanmask &= debug::GLOBAL_PCM_CHANNEL_MASK;
+
+ // compute the output of each channel
+ for (int chnum = 0; chnum < CHANNELS; chnum++)
+ if (bitfield(chanmask, chnum))
+ m_channel[chnum]->output(output);
+}
+
+
+//-------------------------------------------------
+// read - handle reads from the PCM registers
+//-------------------------------------------------
+
+uint8_t pcm_engine::read(uint32_t regnum)
+{
+ // handle reads from the data register
+ if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
+ return m_intf.ymfm_external_read(ACCESS_PCM, m_regs.memory_address_autoinc());
+
+ return m_regs.read(regnum);
+}
+
+
+//-------------------------------------------------
+// write - handle writes to the PCM registers
+//-------------------------------------------------
+
+void pcm_engine::write(uint32_t regnum, uint8_t data)
+{
+ // handle reads to the data register
+ if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
+ {
+ m_intf.ymfm_external_write(ACCESS_PCM, m_regs.memory_address_autoinc(), data);
+ return;
+ }
+
+ // for now just mark all channels as modified
+ m_modified_channels = ALL_CHANNELS;
+
+ // most writes are passive, consumed only when needed
+ m_regs.write(regnum, data);
+
+ // however, process keyons immediately
+ if (regnum >= 0x68 && regnum <= 0x7f)
+ m_channel[regnum - 0x68]->keyonoff(bitfield(data, 7));
+
+ // and also wavetable writes
+ else if (regnum >= 0x08 && regnum <= 0x1f)
+ m_channel[regnum - 0x08]->load_wavetable();
+}
+
+}
diff --git a/3rdparty/ymfm/src/ymfm_pcm.h b/3rdparty/ymfm/src/ymfm_pcm.h
new file mode 100644
index 00000000000..b471fa611a6
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_pcm.h
@@ -0,0 +1,347 @@
+// 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_PCM_H
+#define YMFM_PCM_H
+
+#pragma once
+
+#include "ymfm.h"
+
+namespace ymfm
+{
+
+/*
+Note to self: Sega "Multi-PCM" is almost identical to this
+
+28 channels
+
+Writes:
+00 = data reg, causes write
+01 = target slot = data - (data / 8)
+02 = address (clamped to 7)
+
+Slot data (registers with ADSR/KSR seem to be inaccessible):
+0: xxxx---- panpot
+1: xxxxxxxx wavetable low
+2: xxxxxx-- pitch low
+ -------x wavetable high
+3: xxxx---- octave
+ ----xxxx pitch hi
+4: x------- key on
+5: xxxxxxx- total level
+ -------x level direct (0=interpolate)
+6: --xxx--- LFO frequency
+ -----xxx PM sensitivity
+7: -----xxx AM sensitivity
+
+Sample data:
++00: start hi
++01: start mid
++02: start low
++03: loop hi
++04: loop low
++05: -end hi
++06: -end low
++07: vibrato (reg 6)
++08: attack/decay
++09: sustain level/rate
++0A: ksr/release
++0B: LFO amplitude (reg 7)
+
+*/
+
+//*********************************************************
+// INTERFACE CLASSES
+//*********************************************************
+
+class pcm_engine;
+
+
+// ======================> pcm_cache
+
+// this class holds data that is computed once at the start of clocking
+// and remains static during subsequent sound generation
+struct pcm_cache
+{
+ uint32_t step; // sample position step, as a .16 value
+ uint32_t total_level; // target total level, as a .10 value
+ uint32_t pan_left; // left panning attenuation
+ uint32_t pan_right; // right panning attenuation
+ uint32_t eg_sustain; // sustain level, shifted up to envelope values
+ uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR
+ uint8_t lfo_step; // stepping value for LFO
+ uint8_t am_depth; // scale value for AM LFO
+ uint8_t pm_depth; // scale value for PM LFO
+};
+
+
+// ======================> pcm_registers
+
+//
+// PCM register map:
+//
+// System-wide registers:
+// 00-01 xxxxxxxx LSI Test
+// 02 -------x Memory access mode (0=sound gen, 1=read/write)
+// ------x- Memory type (0=ROM, 1=ROM+SRAM)
+// ---xxx-- Wave table header
+// xxx----- Device ID (=1 for YMF278B)
+// 03 --xxxxxx Memory address high
+// 04 xxxxxxxx Memory address mid
+// 05 xxxxxxxx Memory address low
+// 06 xxxxxxxx Memory data
+// F8 --xxx--- Mix control (FM_R)
+// -----xxx Mix control (FM_L)
+// F9 --xxx--- Mix control (PCM_R)
+// -----xxx Mix control (PCM_L)
+//
+// Channel-specific registers:
+// 08-1F xxxxxxxx Wave table number low
+// 20-37 -------x Wave table number high
+// xxxxxxx- F-number low
+// 38-4F -----xxx F-number high
+// ----x--- Pseudo-reverb
+// xxxx---- Octave
+// 50-67 xxxxxxx- Total level
+// -------x Level direct
+// 68-7F x------- Key on
+// -x------ Damp
+// --x----- LFO reset
+// ---x---- Output channel
+// ----xxxx Panpot
+// 80-97 --xxx--- LFO speed
+// -----xxx Vibrato
+// 98-AF xxxx---- Attack rate
+// ----xxxx Decay rate
+// B0-C7 xxxx---- Sustain level
+// ----xxxx Sustain rate
+// C8-DF xxxx---- Rate correction
+// ----xxxx Release rate
+// E0-F7 -----xxx AM depth
+
+class pcm_registers
+{
+public:
+ // constants
+ static constexpr uint32_t OUTPUTS = 4;
+ static constexpr uint32_t CHANNELS = 24;
+ static constexpr uint32_t REGISTERS = 0x100;
+ static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
+
+ // constructor
+ pcm_registers() { }
+
+ // save/restore
+ void save_restore(ymfm_saved_state &state);
+
+ // reset to initial state
+ void reset();
+
+ // update cache information
+ void cache_channel_data(uint32_t choffs, pcm_cache &cache);
+
+ // direct read/write access
+ uint8_t read(uint32_t index ) { return m_regdata[index]; }
+ void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
+
+ // system-wide registers
+ uint32_t memory_access_mode() const { return bitfield(m_regdata[0x02], 0); }
+ uint32_t memory_type() const { return bitfield(m_regdata[0x02], 1); }
+ uint32_t wave_table_header() const { return bitfield(m_regdata[0x02], 2, 3); }
+ uint32_t device_id() const { return bitfield(m_regdata[0x02], 5, 3); }
+ uint32_t memory_address() const { return (bitfield(m_regdata[0x03], 0, 6) << 16) | (m_regdata[0x04] << 8) | m_regdata[0x05]; }
+ uint32_t memory_data() const { return m_regdata[0x06]; }
+ uint32_t mix_fm_r() const { return bitfield(m_regdata[0xf8], 3, 3); }
+ uint32_t mix_fm_l() const { return bitfield(m_regdata[0xf8], 0, 3); }
+ uint32_t mix_pcm_r() const { return bitfield(m_regdata[0xf9], 3, 3); }
+ uint32_t mix_pcm_l() const { return bitfield(m_regdata[0xf9], 0, 3); }
+
+ // per-channel registers
+ uint32_t ch_wave_table_num(uint32_t choffs) const { return m_regdata[choffs + 0x08] | (bitfield(m_regdata[choffs + 0x20], 0) << 8); }
+ uint32_t ch_fnumber(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x20], 1, 7) | (bitfield(m_regdata[choffs + 0x38], 0, 3) << 7); }
+ uint32_t ch_pseudo_reverb(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 3); }
+ uint32_t ch_octave(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 4, 4); }
+ uint32_t ch_total_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 1, 7); }
+ uint32_t ch_level_direct(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 0); }
+ uint32_t ch_keyon(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 7); }
+ uint32_t ch_damp(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 6); }
+ uint32_t ch_lfo_reset(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 5); }
+ uint32_t ch_output_channel(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 4); }
+ uint32_t ch_panpot(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 0, 4); }
+ uint32_t ch_lfo_speed(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 3, 3); }
+ uint32_t ch_vibrato(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 0, 3); }
+ uint32_t ch_attack_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 4, 4); }
+ uint32_t ch_decay_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 0, 4); }
+ uint32_t ch_sustain_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 4, 4); }
+ uint32_t ch_sustain_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 0, 4); }
+ uint32_t ch_rate_correction(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 4, 4); }
+ uint32_t ch_release_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 0, 4); }
+ uint32_t ch_am_depth(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xe0], 0, 3); }
+
+ // return the memory address and increment it
+ uint32_t memory_address_autoinc()
+ {
+ uint32_t result = memory_address();
+ uint32_t newval = result + 1;
+ m_regdata[0x05] = newval >> 0;
+ m_regdata[0x04] = newval >> 8;
+ m_regdata[0x03] = (newval >> 16) & 0x3f;
+ return result;
+ }
+
+private:
+ // internal helpers
+ uint32_t effective_rate(uint32_t raw, uint32_t correction);
+
+ // internal state
+ uint8_t m_regdata[REGISTERS]; // register data
+};
+
+
+// ======================> pcm_channel
+
+class pcm_channel
+{
+ static constexpr uint8_t KEY_ON = 0x01;
+ static constexpr uint8_t KEY_PENDING_ON = 0x02;
+ static constexpr uint8_t KEY_PENDING = 0x04;
+
+ // "quiet" value, used to optimize when we can skip doing working
+ static constexpr uint32_t EG_QUIET = 0x200;
+
+public:
+ using output_data = ymfm_output<pcm_registers::OUTPUTS>;
+
+ // constructor
+ pcm_channel(pcm_engine &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; }
+
+ // prepare prior to clocking
+ bool prepare();
+
+ // master clocking function
+ void clock(uint32_t env_counter);
+
+ // return the computed output value, with panning applied
+ void output(output_data &output) const;
+
+ // signal key on/off
+ void keyonoff(bool on);
+
+ // load a new wavetable entry
+ void load_wavetable();
+
+private:
+ // internal helpers
+ void start_attack();
+ void start_release();
+ void clock_envelope(uint32_t env_counter);
+ int16_t fetch_sample() const;
+ uint8_t read_pcm(uint32_t address) const;
+
+ // internal state
+ uint32_t const m_choffs; // channel offset
+ uint32_t m_baseaddr; // base address
+ uint32_t m_endpos; // ending position
+ uint32_t m_looppos; // loop position
+ uint32_t m_curpos; // current position
+ uint32_t m_nextpos; // next position
+ uint32_t m_lfo_counter; // LFO counter
+ envelope_state m_eg_state; // envelope state
+ uint16_t m_env_attenuation; // computed envelope attenuation
+ uint32_t m_total_level; // total level with as 7.10 for interp
+ uint8_t m_format; // sample format
+ uint8_t m_key_state; // current key state
+ pcm_cache m_cache; // cached data
+ pcm_registers &m_regs; // reference to registers
+ pcm_engine &m_owner; // reference to our owner
+};
+
+
+// ======================> pcm_engine
+
+class pcm_engine
+{
+public:
+ static constexpr int OUTPUTS = pcm_registers::OUTPUTS;
+ static constexpr int CHANNELS = pcm_registers::CHANNELS;
+ static constexpr uint32_t ALL_CHANNELS = pcm_registers::ALL_CHANNELS;
+ using output_data = pcm_channel::output_data;
+
+ // constructor
+ pcm_engine(ymfm_interface &intf);
+
+ // reset our status
+ void reset();
+
+ // save/restore
+ void save_restore(ymfm_saved_state &state);
+
+ // master clocking function
+ void clock(uint32_t chanmask);
+
+ // compute sum of channel outputs
+ void output(output_data &output, uint32_t chanmask);
+
+ // read from the PCM registers
+ uint8_t read(uint32_t regnum);
+
+ // write to the PCM registers
+ void write(uint32_t regnum, uint8_t data);
+
+ // return a reference to our interface
+ ymfm_interface &intf() { return m_intf; }
+
+ // return a reference to our registers
+ pcm_registers &regs() { return m_regs; }
+
+private:
+ // internal state
+ ymfm_interface &m_intf; // reference to the interface
+ uint32_t m_env_counter; // envelope counter
+ uint32_t m_modified_channels; // bitmask of modified channels
+ uint32_t m_active_channels; // bitmask of active channels
+ uint32_t m_prepare_count; // counter to do periodic prepare sweeps
+ std::unique_ptr<pcm_channel> m_channel[CHANNELS]; // array of channels
+ pcm_registers m_regs; // registers
+};
+
+}
+
+#endif // YMFM_PCM_H
diff --git a/3rdparty/ymfm/src/ymfm_ssg.cpp b/3rdparty/ymfm/src/ymfm_ssg.cpp
new file mode 100644
index 00000000000..1c477d0de0c
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_ssg.cpp
@@ -0,0 +1,279 @@
+// 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.
+
+#include "ymfm_ssg.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// SSG REGISTERS
+//*********************************************************
+
+//-------------------------------------------------
+// reset - reset the register state
+//-------------------------------------------------
+
+void ssg_registers::reset()
+{
+ std::fill_n(&m_regdata[0], REGISTERS, 0);
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ssg_registers::save_restore(ymfm_saved_state &state)
+{
+ state.save_restore(m_regdata);
+}
+
+
+
+//*********************************************************
+// SSG ENGINE
+//*********************************************************
+
+//-------------------------------------------------
+// ssg_engine - constructor
+//-------------------------------------------------
+
+ssg_engine::ssg_engine(ymfm_interface &intf) :
+ m_intf(intf),
+ m_tone_count{ 0,0,0 },
+ m_tone_state{ 0,0,0 },
+ m_envelope_count(0),
+ m_envelope_state(0),
+ m_noise_count(0),
+ m_noise_state(1),
+ m_override(nullptr)
+{
+}
+
+
+//-------------------------------------------------
+// reset - reset the engine state
+//-------------------------------------------------
+
+void ssg_engine::reset()
+{
+ // defer to the override if present
+ if (m_override != nullptr)
+ return m_override->ssg_reset();
+
+ // reset register state
+ m_regs.reset();
+
+ // reset engine state
+ for (int chan = 0; chan < 3; chan++)
+ {
+ m_tone_count[chan] = 0;
+ m_tone_state[chan] = 0;
+ }
+ m_envelope_count = 0;
+ m_envelope_state = 0;
+ m_noise_count = 0;
+ m_noise_state = 1;
+}
+
+
+//-------------------------------------------------
+// save_restore - save or restore the data
+//-------------------------------------------------
+
+void ssg_engine::save_restore(ymfm_saved_state &state)
+{
+ // save register state
+ m_regs.save_restore(state);
+
+ // save engine state
+ state.save_restore(m_tone_count);
+ state.save_restore(m_tone_state);
+ state.save_restore(m_envelope_count);
+ state.save_restore(m_envelope_state);
+ state.save_restore(m_noise_count);
+ state.save_restore(m_noise_state);
+}
+
+
+//-------------------------------------------------
+// clock - master clocking function
+//-------------------------------------------------
+
+void ssg_engine::clock()
+{
+ // clock tones; tone period units are clock/16 but since we run at clock/8
+ // that works out for us to toggle the state (50% duty cycle) at twice the
+ // programmed period
+ for (int chan = 0; chan < 3; chan++)
+ {
+ m_tone_count[chan]++;
+ if (m_tone_count[chan] >= m_regs.ch_tone_period(chan))
+ {
+ m_tone_state[chan] ^= 1;
+ m_tone_count[chan] = 0;
+ }
+ }
+
+ // clock noise; noise period units are clock/16 but since we run at clock/8,
+ // our counter needs a right shift prior to compare; note that a period of 0
+ // should produce an indentical result to a period of 1, so add a special
+ // check against that case
+ m_noise_count++;
+ if ((m_noise_count >> 1) >= m_regs.noise_period() && m_noise_count != 1)
+ {
+ m_noise_state ^= (bitfield(m_noise_state, 0) ^ bitfield(m_noise_state, 3)) << 17;
+ m_noise_state >>= 1;
+ m_noise_count = 0;
+ }
+
+ // clock envelope; envelope period units are clock/8 (manual says clock/256
+ // but that's for all 32 steps)
+ m_envelope_count++;
+ if (m_envelope_count >= m_regs.envelope_period())
+ {
+ m_envelope_state++;
+ m_envelope_count = 0;
+ }
+}
+
+
+//-------------------------------------------------
+// output - output the current state
+//-------------------------------------------------
+
+void ssg_engine::output(output_data &output)
+{
+ // volume to amplitude table, taken from MAME's implementation but biased
+ // so that 0 == 0
+ static int16_t const s_amplitudes[32] =
+ {
+ 0, 32, 78, 141, 178, 222, 262, 306,
+ 369, 441, 509, 585, 701, 836, 965, 1112,
+ 1334, 1595, 1853, 2146, 2576, 3081, 3576, 4135,
+ 5000, 6006, 7023, 8155, 9963,11976,14132,16382
+ };
+
+ // compute the envelope volume
+ uint32_t envelope_volume;
+ if ((m_regs.envelope_hold() | (m_regs.envelope_continue() ^ 1)) && m_envelope_state >= 32)
+ {
+ m_envelope_state = 32;
+ envelope_volume = ((m_regs.envelope_attack() ^ m_regs.envelope_alternate()) & m_regs.envelope_continue()) ? 31 : 0;
+ }
+ else
+ {
+ uint32_t attack = m_regs.envelope_attack();
+ if (m_regs.envelope_alternate())
+ attack ^= bitfield(m_envelope_state, 5);
+ envelope_volume = (m_envelope_state & 31) ^ (attack ? 0 : 31);
+ }
+
+ // iterate over channels
+ for (int chan = 0; chan < 3; chan++)
+ {
+ // noise depends on the noise state, which is the LSB of m_noise_state
+ uint32_t noise_on = m_regs.ch_noise_enable_n(chan) | m_noise_state;
+
+ // tone depends on the current tone state
+ uint32_t tone_on = m_regs.ch_tone_enable_n(chan) | m_tone_state[chan];
+
+ // if neither tone nor noise enabled, return 0
+ uint32_t volume;
+ if ((noise_on & tone_on) == 0)
+ volume = 0;
+
+ // if the envelope is enabled, use its amplitude
+ else if (m_regs.ch_envelope_enable(chan))
+ volume = envelope_volume;
+
+ // otherwise, scale the tone amplitude up to match envelope values
+ // according to the datasheet, amplitude 15 maps to envelope 31
+ else
+ {
+ volume = m_regs.ch_amplitude(chan) * 2;
+ if (volume != 0)
+ volume |= 1;
+ }
+
+ // convert to amplitude
+ output.data[chan] = s_amplitudes[volume];
+ }
+}
+
+
+//-------------------------------------------------
+// read - handle reads from the SSG registers
+//-------------------------------------------------
+
+uint8_t ssg_engine::read(uint32_t regnum)
+{
+ // defer to the override if present
+ if (m_override != nullptr)
+ return m_override->ssg_read(regnum);
+
+ // read from the I/O ports call the handlers if they are configured for input
+ if (regnum == 0x0e && !m_regs.io_a_out())
+ return m_intf.ymfm_external_read(ACCESS_IO, 0);
+ else if (regnum == 0x0f && !m_regs.io_b_out())
+ return m_intf.ymfm_external_read(ACCESS_IO, 1);
+
+ // otherwise just return the register value
+ return m_regs.read(regnum);
+}
+
+
+//-------------------------------------------------
+// write - handle writes to the SSG registers
+//-------------------------------------------------
+
+void ssg_engine::write(uint32_t regnum, uint8_t data)
+{
+ // defer to the override if present
+ if (m_override != nullptr)
+ return m_override->ssg_write(regnum, data);
+
+ // store the raw value to the register array;
+ // most writes are passive, consumed only when needed
+ m_regs.write(regnum, data);
+
+ // writes to the envelope shape register reset the state
+ if (regnum == 0x0d)
+ m_envelope_state = 0;
+
+ // writes to the I/O ports call the handlers if they are configured for output
+ else if (regnum == 0x0e && m_regs.io_a_out())
+ m_intf.ymfm_external_write(ACCESS_IO, 0, data);
+ else if (regnum == 0x0f && m_regs.io_b_out())
+ m_intf.ymfm_external_write(ACCESS_IO, 1, data);
+}
+
+}
diff --git a/3rdparty/ymfm/src/ymfm_ssg.h b/3rdparty/ymfm/src/ymfm_ssg.h
new file mode 100644
index 00000000000..cb7ec9e7c41
--- /dev/null
+++ b/3rdparty/ymfm/src/ymfm_ssg.h
@@ -0,0 +1,207 @@
+// 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_SSG_H
+#define YMFM_SSG_H
+
+#pragma once
+
+#include "ymfm.h"
+
+namespace ymfm
+{
+
+//*********************************************************
+// OVERRIDE INTERFACE
+//*********************************************************
+
+// ======================> ssg_override
+
+// this class describes a simple interface to allow the internal SSG to be
+// overridden with another implementation
+class ssg_override
+{
+public:
+ virtual ~ssg_override() = default;
+
+ // reset our status
+ virtual void ssg_reset() = 0;
+
+ // read/write to the SSG registers
+ virtual uint8_t ssg_read(uint32_t regnum) = 0;
+ virtual void ssg_write(uint32_t regnum, uint8_t data) = 0;
+
+ // notification when the prescale has changed
+ virtual void ssg_prescale_changed() = 0;
+};
+
+
+//*********************************************************
+// REGISTER CLASS
+//*********************************************************
+
+// ======================> ssg_registers
+
+//
+// SSG register map:
+//
+// System-wide registers:
+// 06 ---xxxxx Noise period
+// 07 x------- I/O B in(0) or out(1)
+// -x------ I/O A in(0) or out(1)
+// --x----- Noise enable(0) or disable(1) for channel C
+// ---x---- Noise enable(0) or disable(1) for channel B
+// ----x--- Noise enable(0) or disable(1) for channel A
+// -----x-- Tone enable(0) or disable(1) for channel C
+// ------x- Tone enable(0) or disable(1) for channel B
+// -------x Tone enable(0) or disable(1) for channel A
+// 0B xxxxxxxx Envelope period fine
+// 0C xxxxxxxx Envelope period coarse
+// 0D ----x--- Envelope shape: continue
+// -----x-- Envelope shape: attack/decay
+// ------x- Envelope shape: alternate
+// -------x Envelope shape: hold
+// 0E xxxxxxxx 8-bit parallel I/O port A
+// 0F xxxxxxxx 8-bit parallel I/O port B
+//
+// Per-channel registers:
+// 00,02,04 xxxxxxxx Tone period (fine) for channel A,B,C
+// 01,03,05 ----xxxx Tone period (coarse) for channel A,B,C
+// 08,09,0A ---x---- Mode: fixed(0) or variable(1) for channel A,B,C
+// ----xxxx Amplitude for channel A,B,C
+//
+class ssg_registers
+{
+public:
+ // constants
+ static constexpr uint32_t OUTPUTS = 3;
+ static constexpr uint32_t CHANNELS = 3;
+ static constexpr uint32_t REGISTERS = 0x10;
+ static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
+
+ // constructor
+ ssg_registers() { }
+
+ // reset to initial state
+ void reset();
+
+ // save/restore
+ void save_restore(ymfm_saved_state &state);
+
+ // direct read/write access
+ uint8_t read(uint32_t index) { return m_regdata[index]; }
+ void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
+
+ // system-wide registers
+ uint32_t noise_period() const { return bitfield(m_regdata[0x06], 0, 5); }
+ uint32_t io_b_out() const { return bitfield(m_regdata[0x07], 7); }
+ uint32_t io_a_out() const { return bitfield(m_regdata[0x07], 6); }
+ uint32_t envelope_period() const { return m_regdata[0x0b] | (m_regdata[0x0c] << 8); }
+ uint32_t envelope_continue() const { return bitfield(m_regdata[0x0d], 3); }
+ uint32_t envelope_attack() const { return bitfield(m_regdata[0x0d], 2); }
+ uint32_t envelope_alternate() const { return bitfield(m_regdata[0x0d], 1); }
+ uint32_t envelope_hold() const { return bitfield(m_regdata[0x0d], 0); }
+ uint32_t io_a_data() const { return m_regdata[0x0e]; }
+ uint32_t io_b_data() const { return m_regdata[0x0f]; }
+
+ // per-channel registers
+ uint32_t ch_noise_enable_n(uint32_t choffs) const { return bitfield(m_regdata[0x07], 3 + choffs); }
+ uint32_t ch_tone_enable_n(uint32_t choffs) const { return bitfield(m_regdata[0x07], 0 + choffs); }
+ uint32_t ch_tone_period(uint32_t choffs) const { return m_regdata[0x00 + 2 * choffs] | (bitfield(m_regdata[0x01 + 2 * choffs], 0, 4) << 8); }
+ uint32_t ch_envelope_enable(uint32_t choffs) const { return bitfield(m_regdata[0x08 + choffs], 4); }
+ uint32_t ch_amplitude(uint32_t choffs) const { return bitfield(m_regdata[0x08 + choffs], 0, 4); }
+
+private:
+ // internal state
+ uint8_t m_regdata[REGISTERS]; // register data
+};
+
+
+// ======================> ssg_engine
+
+class ssg_engine
+{
+public:
+ static constexpr int OUTPUTS = ssg_registers::OUTPUTS;
+ static constexpr int CHANNELS = ssg_registers::CHANNELS;
+ static constexpr int CLOCK_DIVIDER = 8;
+
+ using output_data = ymfm_output<OUTPUTS>;
+
+ // constructor
+ ssg_engine(ymfm_interface &intf);
+
+ // configure an override
+ void override(ssg_override &override) { m_override = &override; }
+
+ // reset our status
+ void reset();
+
+ // save/restore
+ void save_restore(ymfm_saved_state &state);
+
+ // master clocking function
+ void clock();
+
+ // compute sum of channel outputs
+ void output(output_data &output);
+
+ // read/write to the SSG registers
+ uint8_t read(uint32_t regnum);
+ void write(uint32_t regnum, uint8_t data);
+
+ // return a reference to our interface
+ ymfm_interface &intf() { return m_intf; }
+
+ // return a reference to our registers
+ ssg_registers &regs() { return m_regs; }
+
+ // true if we are overridden
+ bool overridden() const { return (m_override != nullptr); }
+
+ // indicate the prescale has changed
+ void prescale_changed() { if (m_override != nullptr) m_override->ssg_prescale_changed(); }
+
+private:
+ // internal state
+ ymfm_interface &m_intf; // reference to the interface
+ uint32_t m_tone_count[3]; // current tone counter
+ uint32_t m_tone_state[3]; // current tone state
+ uint32_t m_envelope_count; // envelope counter
+ uint32_t m_envelope_state; // envelope state
+ uint32_t m_noise_count; // current noise counter
+ uint32_t m_noise_state; // current noise state
+ ssg_registers m_regs; // registers
+ ssg_override *m_override; // override interface
+};
+
+}
+
+#endif // YMFM_SSG_H