diff options
Diffstat (limited to '3rdparty/ymfm/src')
-rw-r--r-- | 3rdparty/ymfm/src/ymfm.h | 566 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_adpcm.cpp | 807 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_adpcm.h | 411 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_fm.h | 463 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_fm.ipp | 1592 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_misc.cpp | 175 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_misc.h | 93 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opl.cpp | 2220 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opl.h | 902 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opm.cpp | 539 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opm.h | 322 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opn.cpp | 2485 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opn.h | 802 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opq.cpp | 480 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opq.h | 293 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opx.h | 290 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opz.cpp | 808 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_opz.h | 332 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_pcm.cpp | 714 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_pcm.h | 347 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_ssg.cpp | 279 | ||||
-rw-r--r-- | 3rdparty/ymfm/src/ymfm_ssg.h | 207 |
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 ®s() { 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 ®s() { 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 ®s() const { return m_regs; } + + // simple getters for debugging + envelope_state debug_eg_state() const { return m_env_state; } + uint16_t debug_eg_attenuation() const { return m_env_attenuation; } + uint8_t debug_ssg_inverted() const { return m_ssg_inverted; } + opdata_cache &debug_cache() { return m_cache; } + +private: + // start the attack phase + void start_attack(bool is_restart = false); + + // start the release phase + void start_release(); + + // clock phases + void clock_keystate(uint32_t keystate); + void clock_ssg_eg_state(); + void clock_envelope(uint32_t env_counter); + void clock_phase(int32_t lfo_raw_pm); + + // return effective attenuation of the envelope + uint32_t envelope_attenuation(uint32_t am_offset) const; + + // internal state + uint32_t m_choffs; // channel offset in registers + uint32_t m_opoffs; // operator offset in registers + uint32_t m_phase; // current phase value (10.10 format) + uint16_t m_env_attenuation; // computed envelope attenuation (4.6 format) + envelope_state m_env_state; // current envelope state + uint8_t m_ssg_inverted; // non-zero if the output should be inverted (bit 0) + uint8_t m_key_state; // current key state: on or off (bit 0) + uint8_t m_keyon_live; // live key on state (bit 0 = direct, bit 1 = rhythm, bit 2 = CSM) + opdata_cache m_cache; // cached values for performance + RegisterType &m_regs; // direct reference to registers + fm_engine_base<RegisterType> &m_owner; // reference to the owning engine +}; + + +// ======================> fm_channel + +// fm_channel represents an FM channel which combines the output of 2 or 4 +// operators into a final result +template<class RegisterType> +class fm_channel +{ + using output_data = ymfm_output<RegisterType::OUTPUTS>; + +public: + // constructor + fm_channel(fm_engine_base<RegisterType> &owner, uint32_t choffs); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // reset the channel state + void reset(); + + // return the channel offset + uint32_t choffs() const { return m_choffs; } + + // assign operators + void assign(uint32_t index, fm_operator<RegisterType> *op) + { + assert(index < 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 ®s() const { return m_regs; } + + // simple getters for debugging + fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_op[index]; } + +private: + // helper to add values to the outputs based on channel enables + void add_to_output(uint32_t choffs, output_data &output, int32_t value) const + { + // create these constants to appease overzealous compilers checking array + // bounds in unreachable code (looking at you, clang) + constexpr int out0_index = 0; + constexpr int out1_index = 1 % RegisterType::OUTPUTS; + constexpr int out2_index = 2 % RegisterType::OUTPUTS; + constexpr int out3_index = 3 % RegisterType::OUTPUTS; + + if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs)) + output.data[out0_index] += value; + if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs)) + output.data[out1_index] += value; + if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs)) + output.data[out2_index] += value; + if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs)) + output.data[out3_index] += value; + } + + // internal state + uint32_t m_choffs; // channel offset in registers + int16_t m_feedback[2]; // feedback memory for operator 1 + mutable int16_t m_feedback_in; // next input value for op 1 feedback (set in output) + 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 ®s() { return m_regs; } + + // invalidate any caches + void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; } + + // simple getters for debugging + fm_channel<RegisterType> *debug_channel(uint32_t index) const { return m_channel[index].get(); } + fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_operator[index].get(); } + +public: + // timer callback; called by the interface when a timer fires + virtual void engine_timer_expired(uint32_t tnum) override; + + // check interrupts; called by the interface after synchronization + virtual void engine_check_interrupts() override; + + // mode register write; called by the interface after synchronization + virtual void engine_mode_write(uint8_t data) override; + +protected: + // assign the current set of operators to channels + void assign_operators(); + + // update the state of the given timer + void update_timer(uint32_t which, uint32_t enable, int32_t delta_clocks); + + // internal state + ymfm_interface &m_intf; // reference to the system interface + uint32_t m_env_counter; // envelope counter; low 2 bits are sub-counter + uint8_t m_status; // current status register + uint8_t m_clock_prescale; // prescale factor (2/3/6) + uint8_t m_irq_mask; // mask of which bits signal IRQs + uint8_t m_irq_state; // current IRQ state + uint8_t m_timer_running[2]; // current timer running state + uint8_t m_total_clocks; // low 8 bits of the total number of clocks processed + uint32_t m_active_channels; // mask of active channels (computed by prepare) + uint32_t m_modified_channels; // mask of channels that have been modified + uint32_t m_prepare_count; // counter to do periodic prepare sweeps + RegisterType m_regs; // register accessor + std::unique_ptr<fm_channel<RegisterType>> m_channel[CHANNELS]; // channel pointers + std::unique_ptr<fm_operator<RegisterType>> m_operator[OPERATORS]; // operator pointers +#if (YMFM_DEBUG_LOG_WAVFILES) + mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; // for debugging +#endif +}; + +} + +#endif // YMFM_FM_H 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 ®s() { 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 ®s() { 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 |