aboutsummaryrefslogtreecommitdiff
path: root/src/chips/ymfm/ymfm.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/chips/ymfm/ymfm.h')
-rw-r--r--src/chips/ymfm/ymfm.h573
1 files changed, 573 insertions, 0 deletions
diff --git a/src/chips/ymfm/ymfm.h b/src/chips/ymfm/ymfm.h
new file mode 100644
index 0000000..ccd0d6c
--- /dev/null
+++ b/src/chips/ymfm/ymfm.h
@@ -0,0 +1,573 @@
+// 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 <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;
+}
+
+
+//-------------------------------------------------
+// array_size - return the size of an array
+//-------------------------------------------------
+
+template<typename ArrayType, int ArraySize>
+constexpr uint32_t array_size(ArrayType (&array)[ArraySize])
+{
+ (void)array;
+ return ArraySize;
+}
+
+
+//-------------------------------------------------
+// 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;
+ return (value >> exponent) << exponent;
+}
+
+
+
+//*********************************************************
+// 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];
+ sprintf(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:
+ // 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() {}
+ // 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) { (void)tnum; (void)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) { (void)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) { (void)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) { (void)type; (void)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) { (void)type; (void)address; (void)data; }
+
+protected:
+ // pointer to engine callbacks -- this is set directly by the engine at
+ // construction time
+ ymfm_engine_callbacks *m_engine;
+};
+
+}
+
+#endif // YMFM_H