get things ready for neo geo
This commit is contained in:
parent
248cc6d37a
commit
6cc0d58624
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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()`.
|
||||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,411 @@
|
|||
// BSD 3-Clause License
|
||||
//
|
||||
// Copyright (c) 2021, Aaron Giles
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||
// list of conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#ifndef YMFM_ADPCM_H
|
||||
#define YMFM_ADPCM_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ymfm.h"
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
//*********************************************************
|
||||
// INTERFACE CLASSES
|
||||
//*********************************************************
|
||||
|
||||
// forward declarations
|
||||
class adpcm_a_engine;
|
||||
class adpcm_b_engine;
|
||||
|
||||
|
||||
// ======================> adpcm_a_registers
|
||||
|
||||
//
|
||||
// ADPCM-A register map:
|
||||
//
|
||||
// System-wide registers:
|
||||
// 00 x------- Dump (disable=1) or keyon (0) control
|
||||
// --xxxxxx Mask of channels to dump or keyon
|
||||
// 01 --xxxxxx Total level
|
||||
// 02 xxxxxxxx Test register
|
||||
// 08-0D x------- Pan left
|
||||
// -x------ Pan right
|
||||
// ---xxxxx Instrument level
|
||||
// 10-15 xxxxxxxx Start address (low)
|
||||
// 18-1D xxxxxxxx Start address (high)
|
||||
// 20-25 xxxxxxxx End address (low)
|
||||
// 28-2D xxxxxxxx End address (high)
|
||||
//
|
||||
class adpcm_a_registers
|
||||
{
|
||||
public:
|
||||
// constants
|
||||
static constexpr uint32_t OUTPUTS = 2;
|
||||
static constexpr uint32_t CHANNELS = 6;
|
||||
static constexpr uint32_t REGISTERS = 0x30;
|
||||
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
|
||||
|
||||
// constructor
|
||||
adpcm_a_registers() { }
|
||||
|
||||
// reset to initial state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// map channel number to register offset
|
||||
static constexpr uint32_t channel_offset(uint32_t chnum)
|
||||
{
|
||||
assert(chnum < CHANNELS);
|
||||
return chnum;
|
||||
}
|
||||
|
||||
// direct read/write access
|
||||
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
|
||||
|
||||
// system-wide registers
|
||||
uint32_t dump() const { return bitfield(m_regdata[0x00], 7); }
|
||||
uint32_t dump_mask() const { return bitfield(m_regdata[0x00], 0, 6); }
|
||||
uint32_t total_level() const { return bitfield(m_regdata[0x01], 0, 6); }
|
||||
uint32_t test() const { return m_regdata[0x02]; }
|
||||
|
||||
// per-channel registers
|
||||
uint32_t ch_pan_left(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 7); }
|
||||
uint32_t ch_pan_right(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 6); }
|
||||
uint32_t ch_instrument_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 0, 5); }
|
||||
uint32_t ch_start(uint32_t choffs) const { return m_regdata[choffs + 0x10] | (m_regdata[choffs + 0x18] << 8); }
|
||||
uint32_t ch_end(uint32_t choffs) const { return m_regdata[choffs + 0x20] | (m_regdata[choffs + 0x28] << 8); }
|
||||
|
||||
// per-channel writes
|
||||
void write_start(uint32_t choffs, uint32_t address)
|
||||
{
|
||||
write(choffs + 0x10, address);
|
||||
write(choffs + 0x18, address >> 8);
|
||||
}
|
||||
void write_end(uint32_t choffs, uint32_t address)
|
||||
{
|
||||
write(choffs + 0x20, address);
|
||||
write(choffs + 0x28, address >> 8);
|
||||
}
|
||||
|
||||
private:
|
||||
// internal state
|
||||
uint8_t m_regdata[REGISTERS]; // register data
|
||||
};
|
||||
|
||||
|
||||
// ======================> adpcm_a_channel
|
||||
|
||||
class adpcm_a_channel
|
||||
{
|
||||
public:
|
||||
// constructor
|
||||
adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift);
|
||||
|
||||
// reset the channel state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// signal key on/off
|
||||
void keyonoff(bool on);
|
||||
|
||||
// master clockingfunction
|
||||
bool clock();
|
||||
|
||||
// return the computed output value, with panning applied
|
||||
template<int NumOutputs>
|
||||
void output(ymfm_output<NumOutputs> &output) const;
|
||||
|
||||
private:
|
||||
// internal state
|
||||
uint32_t const m_choffs; // channel offset
|
||||
uint32_t const m_address_shift; // address bits shift-left
|
||||
uint32_t m_playing; // currently playing?
|
||||
uint32_t m_curnibble; // index of the current nibble
|
||||
uint32_t m_curbyte; // current byte of data
|
||||
uint32_t m_curaddress; // current address
|
||||
int32_t m_accumulator; // accumulator
|
||||
int32_t m_step_index; // index in the stepping table
|
||||
adpcm_a_registers &m_regs; // reference to registers
|
||||
adpcm_a_engine &m_owner; // reference to our owner
|
||||
};
|
||||
|
||||
|
||||
// ======================> adpcm_a_engine
|
||||
|
||||
class adpcm_a_engine
|
||||
{
|
||||
public:
|
||||
static constexpr int CHANNELS = adpcm_a_registers::CHANNELS;
|
||||
|
||||
// constructor
|
||||
adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift);
|
||||
|
||||
// reset our status
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// master clocking function
|
||||
uint32_t clock(uint32_t chanmask);
|
||||
|
||||
// compute sum of channel outputs
|
||||
template<int NumOutputs>
|
||||
void output(ymfm_output<NumOutputs> &output, uint32_t chanmask);
|
||||
|
||||
// write to the ADPCM-A registers
|
||||
void write(uint32_t regnum, uint8_t data);
|
||||
|
||||
// set the start/end address for a channel (for hardcoded YM2608 percussion)
|
||||
void set_start_end(uint8_t chnum, uint16_t start, uint16_t end)
|
||||
{
|
||||
uint32_t choffs = adpcm_a_registers::channel_offset(chnum);
|
||||
m_regs.write_start(choffs, start);
|
||||
m_regs.write_end(choffs, end);
|
||||
}
|
||||
|
||||
// return a reference to our interface
|
||||
ymfm_interface &intf() { return m_intf; }
|
||||
|
||||
// return a reference to our registers
|
||||
adpcm_a_registers ®s() { return m_regs; }
|
||||
|
||||
private:
|
||||
// internal state
|
||||
ymfm_interface &m_intf; // reference to the interface
|
||||
std::unique_ptr<adpcm_a_channel> m_channel[CHANNELS]; // array of channels
|
||||
adpcm_a_registers m_regs; // registers
|
||||
};
|
||||
|
||||
|
||||
// ======================> adpcm_b_registers
|
||||
|
||||
//
|
||||
// ADPCM-B register map:
|
||||
//
|
||||
// System-wide registers:
|
||||
// 00 x------- Start of synthesis/analysis
|
||||
// -x------ Record
|
||||
// --x----- External/manual driving
|
||||
// ---x---- Repeat playback
|
||||
// ----x--- Speaker off
|
||||
// -------x Reset
|
||||
// 01 x------- Pan left
|
||||
// -x------ Pan right
|
||||
// ----x--- Start conversion
|
||||
// -----x-- DAC enable
|
||||
// ------x- DRAM access (1=8-bit granularity; 0=1-bit)
|
||||
// -------x RAM/ROM (1=ROM, 0=RAM)
|
||||
// 02 xxxxxxxx Start address (low)
|
||||
// 03 xxxxxxxx Start address (high)
|
||||
// 04 xxxxxxxx End address (low)
|
||||
// 05 xxxxxxxx End address (high)
|
||||
// 06 xxxxxxxx Prescale value (low)
|
||||
// 07 -----xxx Prescale value (high)
|
||||
// 08 xxxxxxxx CPU data/buffer
|
||||
// 09 xxxxxxxx Delta-N frequency scale (low)
|
||||
// 0a xxxxxxxx Delta-N frequency scale (high)
|
||||
// 0b xxxxxxxx Level control
|
||||
// 0c xxxxxxxx Limit address (low)
|
||||
// 0d xxxxxxxx Limit address (high)
|
||||
// 0e xxxxxxxx DAC data [YM2608/10]
|
||||
// 0f xxxxxxxx PCM data [YM2608/10]
|
||||
// 0e xxxxxxxx DAC data high [Y8950]
|
||||
// 0f xx------ DAC data low [Y8950]
|
||||
// 10 -----xxx DAC data exponent [Y8950]
|
||||
//
|
||||
class adpcm_b_registers
|
||||
{
|
||||
public:
|
||||
// constants
|
||||
static constexpr uint32_t REGISTERS = 0x11;
|
||||
|
||||
// constructor
|
||||
adpcm_b_registers() { }
|
||||
|
||||
// reset to initial state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// direct read/write access
|
||||
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
|
||||
|
||||
// system-wide registers
|
||||
uint32_t execute() const { return bitfield(m_regdata[0x00], 7); }
|
||||
uint32_t record() const { return bitfield(m_regdata[0x00], 6); }
|
||||
uint32_t external() const { return bitfield(m_regdata[0x00], 5); }
|
||||
uint32_t repeat() const { return bitfield(m_regdata[0x00], 4); }
|
||||
uint32_t speaker() const { return bitfield(m_regdata[0x00], 3); }
|
||||
uint32_t resetflag() const { return bitfield(m_regdata[0x00], 0); }
|
||||
uint32_t pan_left() const { return bitfield(m_regdata[0x01], 7); }
|
||||
uint32_t pan_right() const { return bitfield(m_regdata[0x01], 6); }
|
||||
uint32_t start_conversion() const { return bitfield(m_regdata[0x01], 3); }
|
||||
uint32_t dac_enable() const { return bitfield(m_regdata[0x01], 2); }
|
||||
uint32_t dram_8bit() const { return bitfield(m_regdata[0x01], 1); }
|
||||
uint32_t rom_ram() const { return bitfield(m_regdata[0x01], 0); }
|
||||
uint32_t start() const { return m_regdata[0x02] | (m_regdata[0x03] << 8); }
|
||||
uint32_t end() const { return m_regdata[0x04] | (m_regdata[0x05] << 8); }
|
||||
uint32_t prescale() const { return m_regdata[0x06] | (bitfield(m_regdata[0x07], 0, 3) << 8); }
|
||||
uint32_t cpudata() const { return m_regdata[0x08]; }
|
||||
uint32_t delta_n() const { return m_regdata[0x09] | (m_regdata[0x0a] << 8); }
|
||||
uint32_t level() const { return m_regdata[0x0b]; }
|
||||
uint32_t limit() const { return m_regdata[0x0c] | (m_regdata[0x0d] << 8); }
|
||||
uint32_t dac() const { return m_regdata[0x0e]; }
|
||||
uint32_t pcm() const { return m_regdata[0x0f]; }
|
||||
|
||||
private:
|
||||
// internal state
|
||||
uint8_t m_regdata[REGISTERS]; // register data
|
||||
};
|
||||
|
||||
|
||||
// ======================> adpcm_b_channel
|
||||
|
||||
class adpcm_b_channel
|
||||
{
|
||||
static constexpr int32_t STEP_MIN = 127;
|
||||
static constexpr int32_t STEP_MAX = 24576;
|
||||
|
||||
public:
|
||||
static constexpr uint8_t STATUS_EOS = 0x01;
|
||||
static constexpr uint8_t STATUS_BRDY = 0x02;
|
||||
static constexpr uint8_t STATUS_PLAYING = 0x04;
|
||||
|
||||
// constructor
|
||||
adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift);
|
||||
|
||||
// reset the channel state
|
||||
void reset();
|
||||
|
||||
// save/restore
|
||||
void save_restore(ymfm_saved_state &state);
|
||||
|
||||
// signal key on/off
|
||||
void keyonoff(bool on);
|
||||
|
||||
// master clocking function
|
||||
void clock();
|
||||
|
||||
// return the computed output value, with panning applied
|
||||
template<int NumOutputs>
|
||||
void output(ymfm_output<NumOutputs> &output, uint32_t rshift) const;
|
||||
|
||||
// return the status register
|
||||
uint8_t status() const { return m_status; }
|
||||
|
||||
// handle special register reads
|
||||
uint8_t read(uint32_t regnum);
|
||||
|
||||
// handle special register writes
|
||||
void write(uint32_t regnum, uint8_t value);
|
||||
|
||||
private:
|
||||
// helper - return the current address shift
|
||||
uint32_t address_shift() const;
|
||||
|
||||
// load the start address
|
||||
void load_start();
|
||||
|
||||
// limit checker
|
||||
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 ®s() { return m_regs; }
|
||||
|
||||
private:
|
||||
// internal state
|
||||
ymfm_interface &m_intf; // reference to our interface
|
||||
std::unique_ptr<adpcm_b_channel> m_channel; // channel pointer
|
||||
adpcm_b_registers m_regs; // registers
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // YMFM_ADPCM_H
|
|
@ -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 ®s() const { return m_regs; }
|
||||
|
||||
// simple getters for debugging
|
||||
envelope_state debug_eg_state() const { return m_env_state; }
|
||||
uint16_t debug_eg_attenuation() const { return m_env_attenuation; }
|
||||
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 ®s() const { return m_regs; }
|
||||
|
||||
// simple getters for debugging
|
||||
fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_op[index]; }
|
||||
|
||||
private:
|
||||
// helper to add values to the outputs based on channel enables
|
||||
void add_to_output(uint32_t choffs, output_data &output, int32_t value) const
|
||||
{
|
||||
// create these constants to appease overzealous compilers checking array
|
||||
// bounds in unreachable code (looking at you, clang)
|
||||
constexpr int out0_index = 0;
|
||||
constexpr int out1_index = 1 % RegisterType::OUTPUTS;
|
||||
constexpr int out2_index = 2 % RegisterType::OUTPUTS;
|
||||
constexpr int out3_index = 3 % RegisterType::OUTPUTS;
|
||||
|
||||
if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs))
|
||||
output.data[out0_index] += value;
|
||||
if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs))
|
||||
output.data[out1_index] += value;
|
||||
if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs))
|
||||
output.data[out2_index] += value;
|
||||
if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs))
|
||||
output.data[out3_index] += value;
|
||||
}
|
||||
|
||||
// internal state
|
||||
uint32_t m_choffs; // channel offset in registers
|
||||
int16_t m_feedback[2]; // feedback memory for operator 1
|
||||
mutable int16_t m_feedback_in; // next input value for op 1 feedback (set in output)
|
||||
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 ®s() { return m_regs; }
|
||||
|
||||
// invalidate any caches
|
||||
void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; }
|
||||
|
||||
// simple getters for debugging
|
||||
fm_channel<RegisterType> *debug_channel(uint32_t index) const { return m_channel[index].get(); }
|
||||
fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_operator[index].get(); }
|
||||
|
||||
public:
|
||||
// timer callback; called by the interface when a timer fires
|
||||
virtual void engine_timer_expired(uint32_t tnum) override;
|
||||
|
||||
// check interrupts; called by the interface after synchronization
|
||||
virtual void engine_check_interrupts() override;
|
||||
|
||||
// mode register write; called by the interface after synchronization
|
||||
virtual void engine_mode_write(uint8_t data) override;
|
||||
|
||||
protected:
|
||||
// assign the current set of operators to channels
|
||||
void assign_operators();
|
||||
|
||||
// update the state of the given timer
|
||||
void update_timer(uint32_t which, uint32_t enable);
|
||||
|
||||
// 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
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 ®s() { return m_regs; }
|
||||
|
||||
// true if we are overridden
|
||||
bool overridden() const { return (m_override != nullptr); }
|
||||
|
||||
// indicate the prescale has changed
|
||||
void prescale_changed() { if (m_override != nullptr) m_override->ssg_prescale_changed(); }
|
||||
|
||||
private:
|
||||
// internal state
|
||||
ymfm_interface &m_intf; // reference to the interface
|
||||
uint32_t m_tone_count[3]; // current tone counter
|
||||
uint32_t m_tone_state[3]; // current tone state
|
||||
uint32_t m_envelope_count; // envelope counter
|
||||
uint32_t m_envelope_state; // envelope state
|
||||
uint32_t m_noise_count; // current noise counter
|
||||
uint32_t m_noise_state; // current noise state
|
||||
ssg_registers m_regs; // registers
|
||||
ssg_override *m_override; // override interface
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // YMFM_SSG_H
|
Loading…
Reference in New Issue