get things ready for neo geo

This commit is contained in:
tildearrow 2021-12-09 03:37:31 -05:00
parent 248cc6d37a
commit 6cc0d58624
15 changed files with 7886 additions and 3 deletions

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.0)
project(furnace)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)
set(BUILD_TESTING OFF)
add_subdirectory(extern/libsndfile)
@ -59,6 +59,10 @@ src/engine/platform/sound/c64/wave8580_PST.cc
src/engine/platform/sound/c64/wave8580_P_T.cc
src/engine/platform/sound/c64/wave8580__ST.cc
src/engine/platform/sound/ym2610/ymfm_adpcm.cpp
src/engine/platform/sound/ym2610/ymfm_opn.cpp
src/engine/platform/sound/ym2610/ymfm_ssg.cpp
src/engine/blip_buf.c
src/engine/safeReader.cpp
src/engine/engine.cpp

View File

@ -4,10 +4,10 @@ this is a work-in-progress chip music player (currently) for the .dmf format.
## features
- supports Sega Genesis, Master System, Game Boy, PC Engine, NES, C64 and YM2151/PCM (Neo Geo coming soon)
- supports Sega Genesis, Master System, Game Boy, PC Engine, NES, C64, YM2151/PCM and Neo Geo!
- clean-room design (zero reverse-engineered code and zero decompilation; using official DMF specs, guesswork and ABX tests only)
- bug/quirk implementation for increased playback accuracy
- accurate emulation cores (Nuked, MAME, SameBoy, Mednafen PCE, puNES and reSID (hahaha!))
- accurate emulation cores (Nuked, MAME, SameBoy, Mednafen PCE, puNES, reSID and ymfm)
- open-source. GPLv2.
## dependencies

View File

@ -0,0 +1,29 @@
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.

View File

@ -0,0 +1,121 @@
# ymfm
[ymfm](https://github.com/aaronsgiles/ymfm) is a collection of BSD-licensed Yamaha FM sound cores (OPM, OPN, OPL, and others), written by [Aaron Giles](https://aarongiles.com)
## Supported environments
This code should compile cleanly in any environment that has C++14 support.
It has been tested on gcc, clang, and Microsoft Visual C++ 2019.
## Supported chip families
Currently, support is present for the following chips (organized by header file):
* ymfm_misc.h:
* YM2149 (SSG) [1983: MSX; Atari ST]
* ymfm_opm.h:
* YM2151 (OPM) [1983: Sharp X1, X68000; MSX; synths: DX21, DX27, DX100]
* YM2164 (OPP) [1985: FB-01 MIDI Expander; IBM Music Feature Card; MSX; synths: Korg DS-8, 707]
* ymfm_opn.h:
* YM2203 (OPN) [1984: NEC PC-88, PC-98, NEC PC-6001mkII SR, PC-6601 SR]
* YM2608 (OPNA) [1985: NEC PC-88, PC-98]
* YM2610 (OPNB) [1987: Neo Geo]
* YM2610B (OPNB2)
* YM2612 (OPN2) [1988: Sega Mega Drive/Genesis; FM Towns]
* YM3438 (OPN2C)
* YMF276 (OPN2L)
* YMF288 (OPN3L) [1995: NEC PC-98]
* ymfm_opl.h:
* YM3526 (OPL) [1984: C64 SFX Sound Expander]
* Y8950 (MSX-Audio) [1984: MSX]
* YM3812 (OPL2) [1985: AdLib, Sound Blaster; synths: some Portasound keyboards]
* YMF262 (OPL3) [1988: Sound Blaster Pro 2.0, SB16]
* YMF289B (OPL3L)
* YMF278B (OPL4) [1993: MSX Moonsound cartridge]
* YM2413 (OPLL) [1986: Sega Master System, Mark III; MSX; synths: Portasound PSS-140, PSS-170, PSS-270]
* YM2423 (OPLL-X)
* YMF281 (OPLLP)
* DS1001 (Konami 053982/VRC7) [1991: Famicom cartridge Lagrange Point]
* ymfm_opq.h:
* YM3806 (OPQ) [synths: PSR-60/70]
* ymfm_opz.h:
* YM2414 (OPZ) [1987: synths: TX81Z, DX11, YS200; Korg Z3 guitar synth]
There are some obviously-related chips that also are on my horizon but have no implementation as yet:
* YMW-258-F 'GEW8' (aka Sega 315-5560 aka Sega Multi-PCM)
* YMF271 (OPX)
* YM21280 (OPS) / YM21290 (EGS) [synths: DX7, DX1, DX5, DX9, TX7, TX216, TX416, TX816]
* OPK?
## History
These cores were originally written during the summer and fall of 2020 as part of the [MAME](https://mamedev.org/) project.
As such, their design started off heavily based on how MAME works.
The OPM/OPN cores first appeared in MAME 0.230.
The OPL cores were added in MAME 0.231.
A further rewrite to abstract MAME dependencies is planned for MAME 0.232.
The goal was threefold:
1. provide BSD-licensed emulation cores that are more compatible with MAME's core licensing
1. modernize and unify the code around a common implementation of shared features
1. improve accuracy where possible based on discoveries made by others
## Accuracy
The goal of these cores is not 100% digital accuracy.
To achieve that would require full emulation of the pipelines, which would make the code extremely difficult to comprehend.
It would also make it much harder to share common implementations of features, or to add support for less well-known chip types.
If you want that level of accuracy, there are [several](https://github.com/nukeykt/Nuked-OPN2) [decap-based](https://github.com/nukeykt/Nuked-OPM) [emulation cores](https://github.com/nukeykt/Nuked-OPLL) out there.
Instead, the main goals are:
1. Extremely high (audibly indistinguishable) accuracy
1. Reasonable performance
1. Clean design with readable code
1. Clear documentation of the various chips
## General approach
Check out the [examples directory](https://github.com/aaronsgiles/ymfm/tree/main/examples) for some example usage patterns.
I'm not a big fan of makefiles for simple things, so instructions on how to compile each example are provided at the top.
# IMPORTANT
As of May 2021, the interface to these is still a bit in flux.
Be prepared when syncing with upstream to make some adjustments.
### Clocking
The general philosophy of the emulators provided here is that they are clock-independent.
Much like the actual chips, you (the consumer) control the clock; the chips themselves have no idea what time it is.
They just tick forward each time you ask them to.
The way you move things along is via the `generate()` function, which ticks the internal system forward one or more samples, and writes out an array out chip-specific `output_data`.
But what, exactly, is a "sample", and how long is it?
This is where the external clock comes in.
Most of the Yamaha chips are externally clocked in the MHz range.
They then divide that clock by a factor (sometimes dynamically controllable), and then the internal operators are pipelined to further divide the clock.
For example, the YM2151 internally divides the clock by 2, and has 32 operators to iterate through.
Thus, for a nominal input lock of 3.58MHz, you end up at around a 55.9kHz sample rate.
Fortunately, all the chip implementations can compute this for you; just pass the raw external clock value to the `sample_rate()` method and it will hand you back the output sample rate you want.
Then call `generate()` that many times per second to output the results.
But what if I want to output at a "normal" rate, like 44.1kHz?
Sorry, you'll have to rate convert as needed.
### Reading and Writing
To read or write to the chips, you can call the `read()` and `write()` methods.
The offset provided corresponds to the addressing input lines in a (hopefully) logical way.
For reads, almost all chips have a status register, which you can read via `read_status()`.
Some chips have a data port that can be read via `read_data()`.
And chips with extended addressing may also have `read_status_hi()` and `read_data_hi()`.
For writes, almost all chips have an address register and a data register, and so you can reliably count on there being a `write_address()` and `write_data()` method as well.
If the chip supports extended addressing, it may also have `write_address_hi()` and `write_data_hi()`.

View File

@ -0,0 +1,490 @@
// 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
#ifdef _MSC_VER
#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_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,786 @@
// 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 playing from RAM/ROM, check the end address and process
if (m_regs.external())
{
// wrap at the limit address
if (at_limit())
m_curaddress = 0;
// 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 and return
else
{
m_accumulator = 0;
m_prev_accum = 0;
m_status = (m_status & ~STATUS_PLAYING) | STATUS_EOS;
debug::log_keyon("%s\n", "ADPCM EOS");
return;
}
}
// if we're about to process nibble 0, fetch and increment
if (m_curnibble == 0)
{
m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress++);
m_curaddress &= 0xffffff;
}
}
// extract the nibble from our current byte
uint8_t data = uint8_t(m_curbyte << (4 * m_curnibble)) >> 4;
m_curnibble ^= 1;
// if CPU-driven and we just processed the last nibble, copy the next byte and request more
if (m_curnibble == 0 && !m_regs.external())
{
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--;
}
// 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");
}
// otherwise, write the data and signal ready
else
{
result = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress++);
m_status = STATUS_BRDY;
}
}
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);
}
}

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
bool at_limit() const { return (m_curaddress >> address_shift()) >= m_regs.limit(); }
// end checker
bool at_end() const { return (m_curaddress >> address_shift()) > m_regs.end(); }
// 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

View File

@ -0,0 +1,449 @@
// 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
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 working
static constexpr uint32_t EG_QUIET = 0x200;
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; }
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 { 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);
// 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
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
};
}
#endif // YMFM_FM_H

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

File diff suppressed because it is too large Load Diff

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 - 2) : (value + 3); }
// 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

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(chan) & m_noise_state;
// tone depends on the current tone state
uint32_t tone_on = m_regs.ch_tone_enable(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);
}
}

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(uint32_t choffs) const { return bitfield(~m_regdata[0x07], 3 + choffs); }
uint32_t ch_tone_enable(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