Add ymfm library

This commit is contained in:
Adrien Moulin
2022-07-24 10:09:12 +02:00
parent 99f3cf8781
commit b10cd69dca
23 changed files with 15120 additions and 0 deletions

View File

@@ -0,0 +1 @@
add_library(ymfm STATIC ymfm_opl.cpp)

570
src/sound/ymfm/ymfm.h Normal file
View File

@@ -0,0 +1,570 @@
// 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 <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])
{
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:
// 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

View File

@@ -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);
}
}

411
src/sound/ymfm/ymfm_adpcm.h Normal file
View File

@@ -0,0 +1,411 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_ADPCM_H
#define YMFM_ADPCM_H
#pragma once
#include "ymfm.h"
namespace ymfm
{
//*********************************************************
// INTERFACE CLASSES
//*********************************************************
// forward declarations
class adpcm_a_engine;
class adpcm_b_engine;
// ======================> adpcm_a_registers
//
// ADPCM-A register map:
//
// System-wide registers:
// 00 x------- Dump (disable=1) or keyon (0) control
// --xxxxxx Mask of channels to dump or keyon
// 01 --xxxxxx Total level
// 02 xxxxxxxx Test register
// 08-0D x------- Pan left
// -x------ Pan right
// ---xxxxx Instrument level
// 10-15 xxxxxxxx Start address (low)
// 18-1D xxxxxxxx Start address (high)
// 20-25 xxxxxxxx End address (low)
// 28-2D xxxxxxxx End address (high)
//
class adpcm_a_registers
{
public:
// constants
static constexpr uint32_t OUTPUTS = 2;
static constexpr uint32_t CHANNELS = 6;
static constexpr uint32_t REGISTERS = 0x30;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
// constructor
adpcm_a_registers() { }
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// map channel number to register offset
static constexpr uint32_t channel_offset(uint32_t chnum)
{
assert(chnum < CHANNELS);
return chnum;
}
// direct read/write access
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
// system-wide registers
uint32_t dump() const { return bitfield(m_regdata[0x00], 7); }
uint32_t dump_mask() const { return bitfield(m_regdata[0x00], 0, 6); }
uint32_t total_level() const { return bitfield(m_regdata[0x01], 0, 6); }
uint32_t test() const { return m_regdata[0x02]; }
// per-channel registers
uint32_t ch_pan_left(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 7); }
uint32_t ch_pan_right(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 6); }
uint32_t ch_instrument_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 0, 5); }
uint32_t ch_start(uint32_t choffs) const { return m_regdata[choffs + 0x10] | (m_regdata[choffs + 0x18] << 8); }
uint32_t ch_end(uint32_t choffs) const { return m_regdata[choffs + 0x20] | (m_regdata[choffs + 0x28] << 8); }
// per-channel writes
void write_start(uint32_t choffs, uint32_t address)
{
write(choffs + 0x10, address);
write(choffs + 0x18, address >> 8);
}
void write_end(uint32_t choffs, uint32_t address)
{
write(choffs + 0x20, address);
write(choffs + 0x28, address >> 8);
}
private:
// internal state
uint8_t m_regdata[REGISTERS]; // register data
};
// ======================> adpcm_a_channel
class adpcm_a_channel
{
public:
// constructor
adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift);
// reset the channel state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// signal key on/off
void keyonoff(bool on);
// master clockingfunction
bool clock();
// return the computed output value, with panning applied
template<int NumOutputs>
void output(ymfm_output<NumOutputs> &output) const;
private:
// internal state
uint32_t const m_choffs; // channel offset
uint32_t const m_address_shift; // address bits shift-left
uint32_t m_playing; // currently playing?
uint32_t m_curnibble; // index of the current nibble
uint32_t m_curbyte; // current byte of data
uint32_t m_curaddress; // current address
int32_t m_accumulator; // accumulator
int32_t m_step_index; // index in the stepping table
adpcm_a_registers &m_regs; // reference to registers
adpcm_a_engine &m_owner; // reference to our owner
};
// ======================> adpcm_a_engine
class adpcm_a_engine
{
public:
static constexpr int CHANNELS = adpcm_a_registers::CHANNELS;
// constructor
adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift);
// reset our status
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// master clocking function
uint32_t clock(uint32_t chanmask);
// compute sum of channel outputs
template<int NumOutputs>
void output(ymfm_output<NumOutputs> &output, uint32_t chanmask);
// write to the ADPCM-A registers
void write(uint32_t regnum, uint8_t data);
// set the start/end address for a channel (for hardcoded YM2608 percussion)
void set_start_end(uint8_t chnum, uint16_t start, uint16_t end)
{
uint32_t choffs = adpcm_a_registers::channel_offset(chnum);
m_regs.write_start(choffs, start);
m_regs.write_end(choffs, end);
}
// return a reference to our interface
ymfm_interface &intf() { return m_intf; }
// return a reference to our registers
adpcm_a_registers &regs() { return m_regs; }
private:
// internal state
ymfm_interface &m_intf; // reference to the interface
std::unique_ptr<adpcm_a_channel> m_channel[CHANNELS]; // array of channels
adpcm_a_registers m_regs; // registers
};
// ======================> adpcm_b_registers
//
// ADPCM-B register map:
//
// System-wide registers:
// 00 x------- Start of synthesis/analysis
// -x------ Record
// --x----- External/manual driving
// ---x---- Repeat playback
// ----x--- Speaker off
// -------x Reset
// 01 x------- Pan left
// -x------ Pan right
// ----x--- Start conversion
// -----x-- DAC enable
// ------x- DRAM access (1=8-bit granularity; 0=1-bit)
// -------x RAM/ROM (1=ROM, 0=RAM)
// 02 xxxxxxxx Start address (low)
// 03 xxxxxxxx Start address (high)
// 04 xxxxxxxx End address (low)
// 05 xxxxxxxx End address (high)
// 06 xxxxxxxx Prescale value (low)
// 07 -----xxx Prescale value (high)
// 08 xxxxxxxx CPU data/buffer
// 09 xxxxxxxx Delta-N frequency scale (low)
// 0a xxxxxxxx Delta-N frequency scale (high)
// 0b xxxxxxxx Level control
// 0c xxxxxxxx Limit address (low)
// 0d xxxxxxxx Limit address (high)
// 0e xxxxxxxx DAC data [YM2608/10]
// 0f xxxxxxxx PCM data [YM2608/10]
// 0e xxxxxxxx DAC data high [Y8950]
// 0f xx------ DAC data low [Y8950]
// 10 -----xxx DAC data exponent [Y8950]
//
class adpcm_b_registers
{
public:
// constants
static constexpr uint32_t REGISTERS = 0x11;
// constructor
adpcm_b_registers() { }
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// direct read/write access
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
// system-wide registers
uint32_t execute() const { return bitfield(m_regdata[0x00], 7); }
uint32_t record() const { return bitfield(m_regdata[0x00], 6); }
uint32_t external() const { return bitfield(m_regdata[0x00], 5); }
uint32_t repeat() const { return bitfield(m_regdata[0x00], 4); }
uint32_t speaker() const { return bitfield(m_regdata[0x00], 3); }
uint32_t resetflag() const { return bitfield(m_regdata[0x00], 0); }
uint32_t pan_left() const { return bitfield(m_regdata[0x01], 7); }
uint32_t pan_right() const { return bitfield(m_regdata[0x01], 6); }
uint32_t start_conversion() const { return bitfield(m_regdata[0x01], 3); }
uint32_t dac_enable() const { return bitfield(m_regdata[0x01], 2); }
uint32_t dram_8bit() const { return bitfield(m_regdata[0x01], 1); }
uint32_t rom_ram() const { return bitfield(m_regdata[0x01], 0); }
uint32_t start() const { return m_regdata[0x02] | (m_regdata[0x03] << 8); }
uint32_t end() const { return m_regdata[0x04] | (m_regdata[0x05] << 8); }
uint32_t prescale() const { return m_regdata[0x06] | (bitfield(m_regdata[0x07], 0, 3) << 8); }
uint32_t cpudata() const { return m_regdata[0x08]; }
uint32_t delta_n() const { return m_regdata[0x09] | (m_regdata[0x0a] << 8); }
uint32_t level() const { return m_regdata[0x0b]; }
uint32_t limit() const { return m_regdata[0x0c] | (m_regdata[0x0d] << 8); }
uint32_t dac() const { return m_regdata[0x0e]; }
uint32_t pcm() const { return m_regdata[0x0f]; }
private:
// internal state
uint8_t m_regdata[REGISTERS]; // register data
};
// ======================> adpcm_b_channel
class adpcm_b_channel
{
static constexpr int32_t STEP_MIN = 127;
static constexpr int32_t STEP_MAX = 24576;
public:
static constexpr uint8_t STATUS_EOS = 0x01;
static constexpr uint8_t STATUS_BRDY = 0x02;
static constexpr uint8_t STATUS_PLAYING = 0x04;
// constructor
adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift);
// reset the channel state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// signal key on/off
void keyonoff(bool on);
// master clocking function
void clock();
// return the computed output value, with panning applied
template<int NumOutputs>
void output(ymfm_output<NumOutputs> &output, uint32_t rshift) const;
// return the status register
uint8_t status() const { return m_status; }
// handle special register reads
uint8_t read(uint32_t regnum);
// handle special register writes
void write(uint32_t regnum, uint8_t value);
private:
// helper - return the current address shift
uint32_t address_shift() const;
// load the start address
void load_start();
// limit checker; stops at the last byte of the chunk described by address_shift()
bool at_limit() const { return (m_curaddress == (((m_regs.limit() + 1) << address_shift()) - 1)); }
// end checker; stops at the last byte of the chunk described by address_shift()
bool at_end() const { return (m_curaddress == (((m_regs.end() + 1) << address_shift()) - 1)); }
// internal state
uint32_t const m_address_shift; // address bits shift-left
uint32_t m_status; // currently playing?
uint32_t m_curnibble; // index of the current nibble
uint32_t m_curbyte; // current byte of data
uint32_t m_dummy_read; // dummy read tracker
uint32_t m_position; // current fractional position
uint32_t m_curaddress; // current address
int32_t m_accumulator; // accumulator
int32_t m_prev_accum; // previous accumulator (for linear interp)
int32_t m_adpcm_step; // next forecast
adpcm_b_registers &m_regs; // reference to registers
adpcm_b_engine &m_owner; // reference to our owner
};
// ======================> adpcm_b_engine
class adpcm_b_engine
{
public:
// constructor
adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift = 0);
// reset our status
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// master clocking function
void clock();
// compute sum of channel outputs
template<int NumOutputs>
void output(ymfm_output<NumOutputs> &output, uint32_t rshift);
// read from the ADPCM-B registers
uint32_t read(uint32_t regnum) { return m_channel->read(regnum); }
// write to the ADPCM-B registers
void write(uint32_t regnum, uint8_t data);
// status
uint8_t status() const { return m_channel->status(); }
// return a reference to our interface
ymfm_interface &intf() { return m_intf; }
// return a reference to our registers
adpcm_b_registers &regs() { return m_regs; }
private:
// internal state
ymfm_interface &m_intf; // reference to our interface
std::unique_ptr<adpcm_b_channel> m_channel; // channel pointer
adpcm_b_registers m_regs; // registers
};
}
#endif // YMFM_ADPCM_H

463
src/sound/ymfm/ymfm_fm.h Normal file
View File

@@ -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 DEBUG_LOG_WAVFILES (0)
namespace ymfm
{
//*********************************************************
// GLOBAL ENUMERATORS
//*********************************************************
// three different keyon sources; actual keyon is an OR over all of these
enum keyon_type : uint32_t
{
KEYON_NORMAL = 0,
KEYON_RHYTHM = 1,
KEYON_CSM = 2
};
//*********************************************************
// CORE IMPLEMENTATION
//*********************************************************
// ======================> opdata_cache
// this class holds data that is computed once at the start of clocking
// and remains static during subsequent sound generation
struct opdata_cache
{
// set phase_step to this value to recalculate it each sample; needed
// in the case of PM LFO changes
static constexpr uint32_t PHASE_STEP_DYNAMIC = 1;
uint16_t const *waveform; // base of sine table
uint32_t phase_step; // phase step, or PHASE_STEP_DYNAMIC if PM is active
uint32_t total_level; // total level * 8 + KSL
uint32_t block_freq; // raw block frequency value (used to compute phase_step)
int32_t detune; // detuning value (used to compute phase_step)
uint32_t multiple; // multiple value (x.1, used to compute phase_step)
uint32_t eg_sustain; // sustain level, shifted up to envelope values
uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR
uint8_t eg_shift = 0; // envelope shift amount
};
// ======================> fm_registers_base
// base class for family-specific register classes; this provides a few
// constants, common defaults, and helpers, but mostly each derived class is
// responsible for defining all commonly-called methods
class fm_registers_base
{
public:
// this value is returned from the write() function for rhythm channels
static constexpr uint32_t RHYTHM_CHANNEL = 0xff;
// this is the size of a full sin waveform
static constexpr uint32_t WAVEFORM_LENGTH = 0x400;
//
// the following constants need to be defined per family:
// uint32_t OUTPUTS: The number of outputs exposed (1-4)
// uint32_t CHANNELS: The number of channels on the chip
// uint32_t ALL_CHANNELS: A bitmask of all channels
// uint32_t OPERATORS: The number of operators on the chip
// uint32_t WAVEFORMS: The number of waveforms offered
// uint32_t REGISTERS: The number of 8-bit registers allocated
// uint32_t DEFAULT_PRESCALE: The starting clock prescale
// uint32_t EG_CLOCK_DIVIDER: The clock divider of the envelope generator
// uint32_t CSM_TRIGGER_MASK: Mask of channels to trigger in CSM mode
// uint32_t REG_MODE: The address of the "mode" register controlling timers
// uint8_t STATUS_TIMERA: Status bit to set when timer A fires
// uint8_t STATUS_TIMERB: Status bit to set when tiemr B fires
// uint8_t STATUS_BUSY: Status bit to set when the chip is busy
// uint8_t STATUS_IRQ: Status bit to set when an IRQ is signalled
//
// the following constants are uncommon:
// bool DYNAMIC_OPS: True if ops/channel can be changed at runtime (OPL3+)
// bool EG_HAS_DEPRESS: True if the chip has a DP ("depress"?) envelope stage (OPLL)
// bool EG_HAS_REVERB: True if the chip has a faux reverb envelope stage (OPQ/OPZ)
// bool EG_HAS_SSG: True if the chip has SSG envelope support (OPN)
// bool MODULATOR_DELAY: True if the modulator is delayed by 1 sample (OPL pre-OPL3)
//
static constexpr bool DYNAMIC_OPS = false;
static constexpr bool EG_HAS_DEPRESS = false;
static constexpr bool EG_HAS_REVERB = false;
static constexpr bool EG_HAS_SSG = false;
static constexpr bool MODULATOR_DELAY = false;
// system-wide register defaults
uint32_t status_mask() const { return 0; } // OPL only
uint32_t irq_reset() const { return 0; } // OPL only
uint32_t noise_enable() const { return 0; } // OPM only
uint32_t rhythm_enable() const { return 0; } // OPL only
// per-operator register defaults
uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return 0; } // OPN(A) only
uint32_t op_ssg_eg_mode(uint32_t opoffs) const { return 0; } // OPN(A) only
protected:
// helper to encode four operator numbers into a 32-bit value in the
// operator maps for each register class
static constexpr uint32_t operator_list(uint8_t o1 = 0xff, uint8_t o2 = 0xff, uint8_t o3 = 0xff, uint8_t o4 = 0xff)
{
return o1 | (o2 << 8) | (o3 << 16) | (o4 << 24);
}
// helper to apply KSR to the raw ADSR rate, ignoring ksr if the
// raw value is 0, and clamping to 63
static constexpr uint32_t effective_rate(uint32_t rawrate, uint32_t ksr)
{
return (rawrate == 0) ? 0 : std::min<uint32_t>(rawrate + ksr, 63);
}
};
//*********************************************************
// CORE ENGINE CLASSES
//*********************************************************
// forward declarations
template<class RegisterType> class fm_engine_base;
// ======================> fm_operator
// fm_operator represents an FM operator (or "slot" in FM parlance), which
// produces an output sine wave modulated by an envelope
template<class RegisterType>
class fm_operator
{
// "quiet" value, used to optimize when we can skip doing work
static constexpr uint32_t EG_QUIET = 0x380;
public:
// constructor
fm_operator(fm_engine_base<RegisterType> &owner, uint32_t opoffs);
// save/restore
void save_restore(ymfm_saved_state &state);
// reset the operator state
void reset();
// return the operator/channel offset
uint32_t opoffs() const { return m_opoffs; }
uint32_t choffs() const { return m_choffs; }
// set the current channel
void set_choffs(uint32_t choffs) { m_choffs = choffs; }
// prepare prior to clocking
bool prepare();
// master clocking function
void clock(uint32_t env_counter, int32_t lfo_raw_pm);
// return the current phase value
uint32_t phase() const { return m_phase >> 10; }
// compute operator volume
int32_t compute_volume(uint32_t phase, uint32_t am_offset) const;
// compute volume for the OPM noise channel
int32_t compute_noise_volume(uint32_t am_offset) const;
// key state control
void keyonoff(uint32_t on, keyon_type type);
// return a reference to our registers
RegisterType &regs() const { return m_regs; }
// simple getters for debugging
envelope_state debug_eg_state() const { return m_env_state; }
uint16_t debug_eg_attenuation() const { return m_env_attenuation; }
uint8_t debug_ssg_inverted() const { return m_ssg_inverted; }
opdata_cache &debug_cache() { return m_cache; }
private:
// start the attack phase
void start_attack(bool is_restart = false);
// start the release phase
void start_release();
// clock phases
void clock_keystate(uint32_t keystate);
void clock_ssg_eg_state();
void clock_envelope(uint32_t env_counter);
void clock_phase(int32_t lfo_raw_pm);
// return effective attenuation of the envelope
uint32_t envelope_attenuation(uint32_t am_offset) const;
// internal state
uint32_t m_choffs; // channel offset in registers
uint32_t m_opoffs; // operator offset in registers
uint32_t m_phase; // current phase value (10.10 format)
uint16_t m_env_attenuation; // computed envelope attenuation (4.6 format)
envelope_state m_env_state; // current envelope state
uint8_t m_ssg_inverted; // non-zero if the output should be inverted (bit 0)
uint8_t m_key_state; // current key state: on or off (bit 0)
uint8_t m_keyon_live; // live key on state (bit 0 = direct, bit 1 = rhythm, bit 2 = CSM)
opdata_cache m_cache; // cached values for performance
RegisterType &m_regs; // direct reference to registers
fm_engine_base<RegisterType> &m_owner; // reference to the owning engine
};
// ======================> fm_channel
// fm_channel represents an FM channel which combines the output of 2 or 4
// operators into a final result
template<class RegisterType>
class fm_channel
{
using output_data = ymfm_output<RegisterType::OUTPUTS>;
public:
// constructor
fm_channel(fm_engine_base<RegisterType> &owner, uint32_t choffs);
// save/restore
void save_restore(ymfm_saved_state &state);
// reset the channel state
void reset();
// return the channel offset
uint32_t choffs() const { return m_choffs; }
// assign operators
void assign(uint32_t index, fm_operator<RegisterType> *op)
{
assert(index < array_size(m_op));
m_op[index] = op;
if (op != nullptr)
op->set_choffs(m_choffs);
}
// signal key on/off to our operators
void keyonoff(uint32_t states, keyon_type type, uint32_t chnum);
// prepare prior to clocking
bool prepare();
// master clocking function
void clock(uint32_t env_counter, int32_t lfo_raw_pm);
// specific 2-operator and 4-operator output handlers
void output_2op(output_data &output, uint32_t rshift, int32_t clipmax) const;
void output_4op(output_data &output, uint32_t rshift, int32_t clipmax) const;
// compute the special OPL rhythm channel outputs
void output_rhythm_ch6(output_data &output, uint32_t rshift, int32_t clipmax) const;
void output_rhythm_ch7(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const;
void output_rhythm_ch8(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const;
// are we a 4-operator channel or a 2-operator one?
bool is4op() const
{
if (RegisterType::DYNAMIC_OPS)
return (m_op[2] != nullptr);
return (RegisterType::OPERATORS / RegisterType::CHANNELS == 4);
}
// return a reference to our registers
RegisterType &regs() const { return m_regs; }
// simple getters for debugging
fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_op[index]; }
private:
// helper to add values to the outputs based on channel enables
void add_to_output(uint32_t choffs, output_data &output, int32_t value) const
{
// create these constants to appease overzealous compilers checking array
// bounds in unreachable code (looking at you, clang)
constexpr int out0_index = 0;
constexpr int out1_index = 1 % RegisterType::OUTPUTS;
constexpr int out2_index = 2 % RegisterType::OUTPUTS;
constexpr int out3_index = 3 % RegisterType::OUTPUTS;
if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs))
output.data[out0_index] += value;
if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs))
output.data[out1_index] += value;
if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs))
output.data[out2_index] += value;
if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs))
output.data[out3_index] += value;
}
// internal state
uint32_t m_choffs; // channel offset in registers
int16_t m_feedback[2]; // feedback memory for operator 1
mutable int16_t m_feedback_in; // next input value for op 1 feedback (set in output)
fm_operator<RegisterType> *m_op[4]; // 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 (DEBUG_LOG_WAVFILES)
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
m_wavfile[chnum].set_samplerate(baseclock / (m_clock_prescale * OPERATORS));
#endif
return baseclock / (m_clock_prescale * OPERATORS);
}
// return the owning device
ymfm_interface &intf() const { return m_intf; }
// return a reference to our registers
RegisterType &regs() { return m_regs; }
// invalidate any caches
void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; }
// simple getters for debugging
fm_channel<RegisterType> *debug_channel(uint32_t index) const { return m_channel[index].get(); }
fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_operator[index].get(); }
public:
// timer callback; called by the interface when a timer fires
virtual void engine_timer_expired(uint32_t tnum) override;
// check interrupts; called by the interface after synchronization
virtual void engine_check_interrupts() override;
// mode register write; called by the interface after synchronization
virtual void engine_mode_write(uint8_t data) override;
protected:
// assign the current set of operators to channels
void assign_operators();
// update the state of the given timer
void update_timer(uint32_t which, uint32_t enable, int32_t delta_clocks);
// internal state
ymfm_interface &m_intf; // reference to the system interface
uint32_t m_env_counter; // envelope counter; low 2 bits are sub-counter
uint8_t m_status; // current status register
uint8_t m_clock_prescale; // prescale factor (2/3/6)
uint8_t m_irq_mask; // mask of which bits signal IRQs
uint8_t m_irq_state; // current IRQ state
uint8_t m_timer_running[2]; // current timer running state
uint8_t m_total_clocks; // low 8 bits of the total number of clocks processed
uint32_t m_active_channels; // mask of active channels (computed by prepare)
uint32_t m_modified_channels; // mask of channels that have been modified
uint32_t m_prepare_count; // counter to do periodic prepare sweeps
RegisterType m_regs; // register accessor
std::unique_ptr<fm_channel<RegisterType>> m_channel[CHANNELS]; // channel pointers
std::unique_ptr<fm_operator<RegisterType>> m_operator[OPERATORS]; // operator pointers
#if (DEBUG_LOG_WAVFILES)
mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; // for debugging
#endif
};
}
#endif // YMFM_FM_H

1589
src/sound/ymfm/ymfm_fm.ipp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}
}

View File

@@ -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

2209
src/sound/ymfm/ymfm_opl.cpp Normal file

File diff suppressed because it is too large Load Diff

902
src/sound/ymfm/ymfm_opl.h Normal file
View File

@@ -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] (1 = 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 IsOpl2Plus ? 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

539
src/sound/ymfm/ymfm_opm.cpp Normal file
View File

@@ -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;
int8_t pm = int8_t(index);
m_lfo_waveform[0][index] = am | (pm << 8);
// waveform 1 is a square wave
am = bitfield(index, 7) ? 0 : 0xff;
pm = int8_t(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 = int8_t(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 += 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];
char *end = &buffer[0];
end += sprintf(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 += sprintf(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 += sprintf(end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
if (am || pm)
end += sprintf(end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]);
if (noise_enable() && opoffs == 31)
end += sprintf(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();
}
}
}

322
src/sound/ymfm/ymfm_opm.h Normal file
View File

@@ -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

2488
src/sound/ymfm/ymfm_opn.cpp Normal file

File diff suppressed because it is too large Load Diff

802
src/sound/ymfm/ymfm_opn.h Normal file
View File

@@ -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);
};
}
#endif // YMFM_OPN_H

480
src/sound/ymfm/ymfm_opq.cpp Normal file
View File

@@ -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];
char *end = &buffer[0];
end += sprintf(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 += sprintf(end, " am=%u", ch_lfo_am_sens(choffs));
bool pm = (lfo_enable() && ch_lfo_pm_sens(choffs) != 0);
if (pm)
end += sprintf(end, " pm=%u", ch_lfo_pm_sens(choffs));
if (am || pm)
end += sprintf(end, " lfo=%02X", lfo_rate());
if (ch_reverb(choffs))
end += sprintf(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();
}
}
}

293
src/sound/ymfm/ymfm_opq.h Normal file
View File

@@ -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

290
src/sound/ymfm/ymfm_opx.h Normal file
View File

@@ -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
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(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
//*********************************************************
// ======================> 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

808
src/sound/ymfm/ymfm_opz.cpp Normal file
View File

@@ -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;
int8_t pm = int8_t(index);
m_lfo_waveform[0][index] = am | (pm << 8);
// waveform 1 is a square wave
am = bitfield(index, 7) ? 0 : 0xff;
pm = int8_t(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 = int8_t(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];
char *end = &buffer[0];
end += sprintf(end, "%u.%02u", chnum, opnum);
if (op_fix_mode(opoffs))
end += sprintf(end, " fixfreq=%X fine=%X shift=%X", op_fix_frequency(opoffs), op_fine(opoffs), op_fix_range(opoffs));
else
end += sprintf(end, " freq=%04X dt2=%u fine=%X", ch_block_freq(choffs), op_detune2(opoffs), op_fine(opoffs));
end += sprintf(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 += sprintf(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 += sprintf(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 += sprintf(end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth());
if (am || pm)
end += sprintf(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 += sprintf(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 += sprintf(end, " pm2=%u/%02X", ch_lfo2_pm_sens(choffs), lfo2_pm_depth());
if (am2 || pm2)
end += sprintf(end, " lfo2=%02X/%c", lfo2_rate(), "WQTN"[lfo2_waveform()]);
if (op_reverb_rate(opoffs) != 0)
end += sprintf(end, " rev=%u", op_reverb_rate(opoffs));
if (op_waveform(opoffs) != 0)
end += sprintf(end, " wf=%u", op_waveform(opoffs));
if (noise_enable() && opoffs == 31)
end += sprintf(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();
}
}
}

332
src/sound/ymfm/ymfm_opz.h Normal file
View File

@@ -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

715
src/sound/ymfm/ymfm_pcm.cpp Normal file
View File

@@ -0,0 +1,715 @@
// 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[0x02] = 0x20;
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();
}
}

347
src/sound/ymfm/ymfm_pcm.h Normal file
View File

@@ -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[0x06] = newval >> 8;
m_regdata[0x07] = (newval >> 16) & 0x3f;
return result;
}
private:
// internal helpers
uint32_t effective_rate(uint32_t raw, uint32_t correction);
// internal state
uint8_t m_regdata[REGISTERS]; // register data
};
// ======================> pcm_channel
class pcm_channel
{
static constexpr uint8_t KEY_ON = 0x01;
static constexpr uint8_t KEY_PENDING_ON = 0x02;
static constexpr uint8_t KEY_PENDING = 0x04;
// "quiet" value, used to optimize when we can skip doing working
static constexpr uint32_t EG_QUIET = 0x200;
public:
using output_data = ymfm_output<pcm_registers::OUTPUTS>;
// constructor
pcm_channel(pcm_engine &owner, uint32_t choffs);
// save/restore
void save_restore(ymfm_saved_state &state);
// reset the channel state
void reset();
// return the channel offset
uint32_t choffs() const { return m_choffs; }
// prepare prior to clocking
bool prepare();
// master clocking function
void clock(uint32_t env_counter);
// return the computed output value, with panning applied
void output(output_data &output) const;
// signal key on/off
void keyonoff(bool on);
// load a new wavetable entry
void load_wavetable();
private:
// internal helpers
void start_attack();
void start_release();
void clock_envelope(uint32_t env_counter);
int16_t fetch_sample() const;
uint8_t read_pcm(uint32_t address) const;
// internal state
uint32_t const m_choffs; // channel offset
uint32_t m_baseaddr; // base address
uint32_t m_endpos; // ending position
uint32_t m_looppos; // loop position
uint32_t m_curpos; // current position
uint32_t m_nextpos; // next position
uint32_t m_lfo_counter; // LFO counter
envelope_state m_eg_state; // envelope state
uint16_t m_env_attenuation; // computed envelope attenuation
uint32_t m_total_level; // total level with as 7.10 for interp
uint8_t m_format; // sample format
uint8_t m_key_state; // current key state
pcm_cache m_cache; // cached data
pcm_registers &m_regs; // reference to registers
pcm_engine &m_owner; // reference to our owner
};
// ======================> pcm_engine
class pcm_engine
{
public:
static constexpr int OUTPUTS = pcm_registers::OUTPUTS;
static constexpr int CHANNELS = pcm_registers::CHANNELS;
static constexpr uint32_t ALL_CHANNELS = pcm_registers::ALL_CHANNELS;
using output_data = pcm_channel::output_data;
// constructor
pcm_engine(ymfm_interface &intf);
// reset our status
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// master clocking function
void clock(uint32_t chanmask);
// compute sum of channel outputs
void output(output_data &output, uint32_t chanmask);
// read from the PCM registers
uint8_t read(uint32_t regnum);
// write to the PCM registers
void write(uint32_t regnum, uint8_t data);
// return a reference to our interface
ymfm_interface &intf() { return m_intf; }
// return a reference to our registers
pcm_registers &regs() { return m_regs; }
private:
// internal state
ymfm_interface &m_intf; // reference to the interface
uint32_t m_env_counter; // envelope counter
uint32_t m_modified_channels; // bitmask of modified channels
uint32_t m_active_channels; // bitmask of active channels
uint32_t m_prepare_count; // counter to do periodic prepare sweeps
std::unique_ptr<pcm_channel> m_channel[CHANNELS]; // array of channels
pcm_registers m_regs; // registers
};
}
#endif // YMFM_PCM_H

279
src/sound/ymfm/ymfm_ssg.cpp Normal file
View File

@@ -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);
}
}

205
src/sound/ymfm/ymfm_ssg.h Normal file
View File

@@ -0,0 +1,205 @@
// 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:
// reset our status
virtual void ssg_reset() = 0;
// read/write to the SSG registers
virtual uint8_t ssg_read(uint32_t regnum) = 0;
virtual void ssg_write(uint32_t regnum, uint8_t data) = 0;
// notification when the prescale has changed
virtual void ssg_prescale_changed() = 0;
};
//*********************************************************
// REGISTER CLASS
//*********************************************************
// ======================> ssg_registers
//
// SSG register map:
//
// System-wide registers:
// 06 ---xxxxx Noise period
// 07 x------- I/O B in(0) or out(1)
// -x------ I/O A in(0) or out(1)
// --x----- Noise enable(0) or disable(1) for channel C
// ---x---- Noise enable(0) or disable(1) for channel B
// ----x--- Noise enable(0) or disable(1) for channel A
// -----x-- Tone enable(0) or disable(1) for channel C
// ------x- Tone enable(0) or disable(1) for channel B
// -------x Tone enable(0) or disable(1) for channel A
// 0B xxxxxxxx Envelope period fine
// 0C xxxxxxxx Envelope period coarse
// 0D ----x--- Envelope shape: continue
// -----x-- Envelope shape: attack/decay
// ------x- Envelope shape: alternate
// -------x Envelope shape: hold
// 0E xxxxxxxx 8-bit parallel I/O port A
// 0F xxxxxxxx 8-bit parallel I/O port B
//
// Per-channel registers:
// 00,02,04 xxxxxxxx Tone period (fine) for channel A,B,C
// 01,03,05 ----xxxx Tone period (coarse) for channel A,B,C
// 08,09,0A ---x---- Mode: fixed(0) or variable(1) for channel A,B,C
// ----xxxx Amplitude for channel A,B,C
//
class ssg_registers
{
public:
// constants
static constexpr uint32_t OUTPUTS = 3;
static constexpr uint32_t CHANNELS = 3;
static constexpr uint32_t REGISTERS = 0x10;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
// constructor
ssg_registers() { }
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// direct read/write access
uint8_t read(uint32_t index) { return m_regdata[index]; }
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
// system-wide registers
uint32_t noise_period() const { return bitfield(m_regdata[0x06], 0, 5); }
uint32_t io_b_out() const { return bitfield(m_regdata[0x07], 7); }
uint32_t io_a_out() const { return bitfield(m_regdata[0x07], 6); }
uint32_t envelope_period() const { return m_regdata[0x0b] | (m_regdata[0x0c] << 8); }
uint32_t envelope_continue() const { return bitfield(m_regdata[0x0d], 3); }
uint32_t envelope_attack() const { return bitfield(m_regdata[0x0d], 2); }
uint32_t envelope_alternate() const { return bitfield(m_regdata[0x0d], 1); }
uint32_t envelope_hold() const { return bitfield(m_regdata[0x0d], 0); }
uint32_t io_a_data() const { return m_regdata[0x0e]; }
uint32_t io_b_data() const { return m_regdata[0x0f]; }
// per-channel registers
uint32_t ch_noise_enable_n(uint32_t choffs) const { return bitfield(m_regdata[0x07], 3 + choffs); }
uint32_t ch_tone_enable_n(uint32_t choffs) const { return bitfield(m_regdata[0x07], 0 + choffs); }
uint32_t ch_tone_period(uint32_t choffs) const { return m_regdata[0x00 + 2 * choffs] | (bitfield(m_regdata[0x01 + 2 * choffs], 0, 4) << 8); }
uint32_t ch_envelope_enable(uint32_t choffs) const { return bitfield(m_regdata[0x08 + choffs], 4); }
uint32_t ch_amplitude(uint32_t choffs) const { return bitfield(m_regdata[0x08 + choffs], 0, 4); }
private:
// internal state
uint8_t m_regdata[REGISTERS]; // register data
};
// ======================> ssg_engine
class ssg_engine
{
public:
static constexpr int OUTPUTS = ssg_registers::OUTPUTS;
static constexpr int CHANNELS = ssg_registers::CHANNELS;
static constexpr int CLOCK_DIVIDER = 8;
using output_data = ymfm_output<OUTPUTS>;
// constructor
ssg_engine(ymfm_interface &intf);
// configure an override
void override(ssg_override &override) { m_override = &override; }
// reset our status
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// master clocking function
void clock();
// compute sum of channel outputs
void output(output_data &output);
// read/write to the SSG registers
uint8_t read(uint32_t regnum);
void write(uint32_t regnum, uint8_t data);
// return a reference to our interface
ymfm_interface &intf() { return m_intf; }
// return a reference to our registers
ssg_registers &regs() { return m_regs; }
// true if we are overridden
bool overridden() const { return (m_override != nullptr); }
// indicate the prescale has changed
void prescale_changed() { if (m_override != nullptr) m_override->ssg_prescale_changed(); }
private:
// internal state
ymfm_interface &m_intf; // reference to the interface
uint32_t m_tone_count[3]; // current tone counter
uint32_t m_tone_state[3]; // current tone state
uint32_t m_envelope_count; // envelope counter
uint32_t m_envelope_state; // envelope state
uint32_t m_noise_count; // current noise counter
uint32_t m_noise_state; // current noise state
ssg_registers m_regs; // registers
ssg_override *m_override; // override interface
};
}
#endif // YMFM_SSG_H