ymfm: Sync with latest, add complete YMF278B support (#8090)

* Sync with upstream. I/O callbacks are now consolidated into a single read callback and a single write callback, with an access type specifier.
* Initial working implementation of YM278B. Most features implemented, except vibrato.
* Implement vibrato and status register flags. Fix envelope rate computation.
* Rename ymfm_interface::external_type to access_class and clean up the fallout.
* Formally replace the old YMF278B engine with the one from ymfm
* Rotated YMF278B outputs into a more logical order.
* Re-evaluted envelope calculations and 2x works better than the weird 15/8 I came up with before. Also changed the way FM resampling is computed to be more precise (and simpler). Turned off extraneous debugging.
* Start of/reset to a null state with no loaded waveforms.
* Fix YM2608 I/O ports.
This commit is contained in:
Aaron Giles 2021-05-22 09:33:21 -07:00 committed by GitHub
parent 52c226c28e
commit d9db7d77c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 2330 additions and 1503 deletions

View File

@ -19,7 +19,7 @@ The Yamaha FM chips can be broadly categoried into families:
* OPL (YM3526)
* OPL2 (YM3812)
* OPLL (YM2413, YM2423, YMF281, DS1001, and others)
* OPL3 (YMF262)
* OPL3 (YMF262, YMF289B)
* OPL4 (YMF278)
Additionally, several lesser-documented variants exist exclusively in the employ of Yamaha synthesizers:
@ -242,9 +242,9 @@ some details on the OPN family:
chip ID: | YM2203 | YM2608 | YMF288 | YM2610 | YM2610B | YM2612 | YM3438 | YMF276 |
---------:|:------:|:------:|:------:|:------:|:-------:|:------:|:------:|:------:|
aka: | OPN | OPNA | OPN3 | OPNB | OPNB2 | OPN2 | OPN2C | OPN2L |
aka: | OPN | OPNA | OPN3L | OPNB | OPNB2 | OPN2 | OPN2C | OPN2L |
FM: | 3 | 6 | 6 | 4 | 6 | 6 | 6 | 6 |
AY-8910: | 3 | 3 | 3 | 3 | 3 | - | - | - |
AY-8910: | 3 | 1 | 1 | 1 | 1 | - | - | - |
ADPCM-A: | - | 6 int | 6 int | 6 ext | 6 ext | - | - | - |
ADPCM-B: | - | 1 ext | - | 1 ext | 1 ext | - | - | - |
DAC: | no | no | no | no | no | yes | yes | yes |
@ -252,7 +252,7 @@ output: | 10.3fp | 16-bit | 16-bit | 16-bit | 16-bit | 9-bit | 9-bit | 16-b
summing: | adder | adder | adder | adder | adder | muxer | muxer | adder |
* FM represents the number of FM channels available.
* AY-8910 represents the number of AY-8910-compatible channels that are built in.
* AY-8910 represents the number of AY-8910-compatible outputs.
* ADPCM-A represents the number of internal/external ADPCM-A channels present.
* ADPCM-B represents the number of internal/external ADPCM-B channels present.
* DAC indicates if a directly-accessible DAC output exists, replacing one channel.
@ -261,15 +261,15 @@ summing: | adder | adder | adder | adder | adder | muxer | muxer | add
OPL has a similar trove of chip variants:
chip ID: | YM3526 | Y8950 | YM3812 | YM2413 | YMF262 | YMF278B |
------------:|:------:|:-------:|:------:|:------:|:------:|:-------:|
aka: | OPL |MSX-AUDIO| OPL2 | OPLL | OPL3 | OPL4 |
FM: | 9 | 9 | 9 | 9 | 18 | 18 |
ADPCM-B: | - | 1 ext | - | - | - | - |
wavetable: | - | - | - | - | - | 24 |
instruments: | no | no | no | yes | no | no |
output: | 10.3fp | 10.3fp | 10.3fp | 9-bit | 16-bit | 16-bit |
summing: | adder | adder | adder | muxer | adder | adder |
chip ID: | YM3526 | Y8950 | YM3812 | YM2413 | YMF262 | YMF289B | YMF278B |
------------:|:------:|:-------:|:------:|:------:|:------:|:-------:|:-------:|
aka: | OPL |MSX-AUDIO| OPL2 | OPLL | OPL3 | OPL3L | OPL4 |
FM: | 9 | 9 | 9 | 9 | 18 | 18 | 18 |
ADPCM-B: | - | 1 ext | - | - | - | - | - |
wavetable: | - | - | - | - | - | - | 24 |
instruments: | no | no | no | yes | no | no | no |
output: | 10.3fp | 10.3fp | 10.3fp | 9-bit | 16-bit | 16-bit | 16-bit |
summing: | adder | adder | adder | muxer | adder | adder | adder |
* FM represents the number of FM channels available.
* ADPCM-B represents the number of external ADPCM-B channels present.

View File

@ -23,12 +23,14 @@ Currently, support is present for the following chips (organized by header file)
* YM2612 (OPN2)
* YM3438 (OPN2C)
* YMF276 (OPN2L)
* YMF288 (OPN3L)
* ymfm_opl.h:
* YM3526 (OPL)
* Y8950 (MSX-Audio)
* YM3812 (OPL2)
* YMF262 (OPL3)
* YMF278B (OPL4) -- partial (only the FM side)
* YMF289B (OPL3L)
* YMF278B (OPL4)
* YM2413 (OPLL)
* YM2423 (OPLL-X)
* YMF281 (OPLLP)
@ -38,6 +40,11 @@ Currently, support is present for the following chips (organized by header file)
* ymfm_opz.h:
* YM2414 (OPZ) -- preliminary
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)
## History
These cores were originally written during the summer and fall of 2020 as part of the [MAME](https://mamedev.org/) project.

View File

@ -5,15 +5,15 @@
//
// Compile with:
//
// g++ --std=c++17 -I../../src vgmrender.cpp em_inflate.cpp ../../src/ymfm_opl.cpp ../../src/ymfm_opm.cpp ../../src/ymfm_opn.cpp ../../src/ymfm_adpcm.cpp ../../src/ymfm_ssg.cpp -o vgmrender.exe
// g++ --std=c++14 -I../../src vgmrender.cpp em_inflate.cpp ../../src/ymfm_opl.cpp ../../src/ymfm_opm.cpp ../../src/ymfm_opn.cpp ../../src/ymfm_adpcm.cpp ../../src/ymfm_pcm.cpp ../../src/ymfm_ssg.cpp -o vgmrender.exe
//
// or:
//
// clang --std=c++17 -I../../src vgmrender.cpp em_inflate.cpp ../../src/ymfm_opl.cpp ../../src/ymfm_opm.cpp ../../src/ymfm_opn.cpp ../../src/ymfm_adpcm.cpp ../../src/ymfm_ssg.cpp -o vgmrender.exe
// clang --std=c++14 -I../../src vgmrender.cpp em_inflate.cpp ../../src/ymfm_opl.cpp ../../src/ymfm_opm.cpp ../../src/ymfm_opn.cpp ../../src/ymfm_adpcm.cpp ../../src/ymfm_pcm.cpp ../../src/ymfm_ssg.cpp -o vgmrender.exe
//
// or:
//
// cl -I..\..\src vgmrender.cpp em_inflate.cpp ..\..\src\ymfm_opl.cpp ..\..\src\ymfm_opm.cpp ..\..\src\ymfm_opn.cpp ..\..\src\ymfm_adpcm.cpp ..\..\src\ymfm_ssg.cpp /Od /Zi /std:c++17 /EHsc
// cl -I..\..\src vgmrender.cpp em_inflate.cpp ..\..\src\ymfm_opl.cpp ..\..\src\ymfm_opm.cpp ..\..\src\ymfm_opn.cpp ..\..\src\ymfm_adpcm.cpp ..\..\src\ymfm_pcm.cpp ..\..\src\ymfm_ssg.cpp /Od /Zi /std:c++14 /EHsc
//
#define _CRT_SECURE_NO_WARNINGS
@ -23,12 +23,15 @@
#include <cstdint>
#include <cstring>
#include <list>
#include <string>
#include "em_inflate.h"
#include "ymfm_opl.h"
#include "ymfm_opm.h"
#include "ymfm_opn.h"
#define LOG_WRITES (0)
// enable this to run the nuked OPN2 core in parallel; output is not captured,
// but logging can be added to observe behaviors
#define RUN_NUKED_OPN2 (0)
@ -64,6 +67,7 @@ enum chip_type
CHIP_Y8950,
CHIP_YM3812,
CHIP_YMF262,
CHIP_YMF278B,
CHIP_TYPES
};
@ -81,8 +85,9 @@ class vgm_chip_base
{
public:
// construction
vgm_chip_base(uint32_t clock, chip_type type) :
m_type(type)
vgm_chip_base(uint32_t clock, chip_type type, char const *name) :
m_type(type),
m_name(name)
{
}
@ -95,42 +100,23 @@ public:
virtual void generate(emulated_time output_start, emulated_time output_step, int32_t *buffer) = 0;
// write data to the ADPCM-A buffer
void write_adpcm_a(uint32_t base, uint32_t length, uint8_t const *src)
void write_data(ymfm::access_class type, uint32_t base, uint32_t length, uint8_t const *src)
{
uint32_t end = base + length;
if (end > m_adpcm_a_data.size())
m_adpcm_a_data.resize(end);
memcpy(&m_adpcm_a_data[base], src, length);
}
// write data to the ADPCM-B buffer
void write_adpcm_b(uint32_t base, uint32_t length, uint8_t const *src)
{
uint32_t end = base + length;
if (end > m_adpcm_b_data.size())
m_adpcm_b_data.resize(end);
memcpy(&m_adpcm_b_data[base], src, length);
}
// write data to the PCM buffer
void write_pcm(uint32_t base, uint32_t length, uint8_t const *src)
{
uint32_t end = base + length;
if (end > m_pcm_data.size())
m_pcm_data.resize(end);
memcpy(&m_pcm_data[base], src, length);
if (end > m_data[type].size())
m_data[type].resize(end);
memcpy(&m_data[type][base], src, length);
}
// seek within the PCM stream
void seek_pcm(uint32_t pos) { m_pcm_offset = pos; }
uint8_t read_pcm() { return (m_pcm_offset < m_pcm_data.size()) ? m_pcm_data[m_pcm_offset++] : 0; }
uint8_t read_pcm() { auto &pcm = m_data[ymfm::ACCESS_PCM]; return (m_pcm_offset < pcm.size()) ? pcm[m_pcm_offset++] : 0; }
protected:
// internal state
chip_type m_type;
std::vector<uint8_t> m_adpcm_a_data;
std::vector<uint8_t> m_adpcm_b_data;
std::vector<uint8_t> m_pcm_data;
std::string m_name;
std::vector<uint8_t> m_data[ymfm::ACCESS_CLASSES];
uint32_t m_pcm_offset;
#if (CAPTURE_NATIVE)
public:
@ -153,10 +139,11 @@ class vgm_chip : public vgm_chip_base, public ymfm::ymfm_interface
{
public:
// construction
vgm_chip(uint32_t clock, chip_type type) :
vgm_chip_base(clock, type),
vgm_chip(uint32_t clock, chip_type type, char const *name) :
vgm_chip_base(clock, type, name),
m_chip(*this),
m_clock(clock),
m_clocks(0),
m_step(0x100000000ull / m_chip.sample_rate(clock)),
m_pos(0)
{
@ -192,9 +179,9 @@ public:
if (!m_queue.empty())
{
auto front = m_queue.front();
addr1 = 0 + 2 * ((front.first >> 8) & 1);
addr1 = 0 + 2 * ((front.first >> 8) & 3);
data1 = front.first & 0xff;
addr2 = (m_type == CHIP_YM2149) ? 2 : (1 + 2 * ((front.first >> 8) & 1));
addr2 = addr1 + ((m_type == CHIP_YM2149) ? 2 : 1);
data2 = front.second;
m_queue.erase(m_queue.begin());
}
@ -202,6 +189,8 @@ public:
// write to the chip
if (addr1 != 0xffff)
{
if (LOG_WRITES)
printf("%10.5f: %s %03X=%02X\n", double(m_clocks) / double(m_chip.sample_rate(m_clock)), m_name.c_str(), data1, data2);
m_chip.write(addr1, data1);
m_chip.write(addr2, data2);
}
@ -264,6 +253,11 @@ public:
*buffer++ += out0 + out2;
*buffer++ += out1 + out2;
}
else if (m_type == CHIP_YMF278B)
{
*buffer++ += m_output.data[4];
*buffer++ += m_output.data[5];
}
else if (ChipType::OUTPUTS == 1)
{
*buffer++ += m_output.data[0];
@ -274,24 +268,21 @@ public:
*buffer++ += m_output.data[0];
*buffer++ += m_output.data[1 % ChipType::OUTPUTS];
}
m_clocks++;
}
protected:
// handle a read from the ADPCM-A buffer
virtual uint8_t ymfm_adpcm_a_read(uint32_t offset) override
// handle a read from the buffer
virtual uint8_t ymfm_external_read(ymfm::access_class type, uint32_t offset) override
{
return (offset < m_adpcm_a_data.size()) ? m_adpcm_a_data[offset] : 0;
}
// handle a read from the ADPCM-B buffer
virtual uint8_t ymfm_adpcm_b_read(uint32_t offset) override
{
return (offset < m_adpcm_b_data.size()) ? m_adpcm_b_data[offset] : 0;
auto &data = m_data[type];
return (offset < data.size()) ? data[offset] : 0;
}
// internal state
ChipType m_chip;
uint32_t m_clock;
uint64_t m_clocks;
typename ChipType::output_data m_output;
emulated_time m_step;
emulated_time m_pos;
@ -334,7 +325,11 @@ void add_chips(uint32_t clock, chip_type type, char const *chipname)
int numchips = (clock & 0x40000000) ? 2 : 1;
printf("Adding %s%s @ %dHz\n", (numchips == 2) ? "2 x " : "", chipname, clockval);
for (int index = 0; index < numchips; index++)
active_chips.push_back(new vgm_chip<ChipType>(clockval, type));
{
char name[100];
sprintf(name, "%s #%d", chipname, index);
active_chips.push_back(new vgm_chip<ChipType>(clockval, type, chipname));
}
if (type == CHIP_YM2608)
{
@ -351,7 +346,7 @@ void add_chips(uint32_t clock, chip_type type, char const *chipname)
fclose(rom);
for (auto chip : active_chips)
if (chip->type() == type)
chip->write_adpcm_a(0, size, &temp[0]);
chip->write_data(ymfm::ACCESS_ADPCM_A, 0, size, &temp[0]);
}
}
}
@ -500,7 +495,7 @@ uint32_t parse_header(std::vector<uint8_t> &buffer)
return data_start;
clock = parse_uint32(buffer, offset);
if (version >= 0x151 && clock != 0)
fprintf(stderr, "Warning: clock for YMF278B specified, but not supported\n");
add_chips<ymfm::ymf278b>(clock, CHIP_YMF278B, "YMF278B");
// +64: YMF271 clock
if (offset + 4 > data_start)
@ -752,15 +747,20 @@ void write_chip(chip_type type, uint8_t index, uint32_t reg, uint8_t data)
//-------------------------------------------------
// write_chip_hi - handle an upper-address write
// to the given chip and index
// add_rom_data - add data to the given chip
// type in the given access class
//-------------------------------------------------
void write_chip_hi(chip_type type, uint8_t index, uint32_t reg, uint8_t data)
void add_rom_data(chip_type type, ymfm::access_class access, std::vector<uint8_t> &buffer, uint32_t &localoffset, uint32_t size)
{
vgm_chip_base *chip = find_chip(type, index);
if (chip != nullptr)
chip->write(reg | 0x100, data);
uint32_t length = parse_uint32(buffer, localoffset);
uint32_t start = parse_uint32(buffer, localoffset);
for (int index = 0; index < 2; index++)
{
vgm_chip_base *chip = find_chip(type, index);
if (chip != nullptr)
chip->write_data(access, start, size, &buffer[localoffset]);
}
}
@ -799,7 +799,7 @@ void generate_all(std::vector<uint8_t> &buffer, uint32_t data_start, uint32_t ou
// YM2612 port 1, write value dd to register aa
case 0x53:
case 0xa3:
write_chip_hi(CHIP_YM2612, cmd >> 7, buffer[offset], buffer[offset + 1]);
write_chip(CHIP_YM2612, cmd >> 7, buffer[offset] | 0x100, buffer[offset + 1]);
offset += 2;
break;
@ -827,7 +827,7 @@ void generate_all(std::vector<uint8_t> &buffer, uint32_t data_start, uint32_t ou
// YM2608 port 1, write value dd to register aa
case 0x57:
case 0xa7:
write_chip_hi(CHIP_YM2608, cmd >> 7, buffer[offset], buffer[offset + 1]);
write_chip(CHIP_YM2608, cmd >> 7, buffer[offset] | 0x100, buffer[offset + 1]);
offset += 2;
break;
@ -841,7 +841,7 @@ void generate_all(std::vector<uint8_t> &buffer, uint32_t data_start, uint32_t ou
// YM2610 port 1, write value dd to register aa
case 0x59:
case 0xa9:
write_chip_hi(CHIP_YM2610, cmd >> 7, buffer[offset], buffer[offset + 1]);
write_chip(CHIP_YM2610, cmd >> 7, buffer[offset] | 0x100, buffer[offset + 1]);
offset += 2;
break;
@ -876,7 +876,7 @@ void generate_all(std::vector<uint8_t> &buffer, uint32_t data_start, uint32_t ou
// YMF262 port 1, write value dd to register aa
case 0x5f:
case 0xaf:
write_chip_hi(CHIP_YMF262, cmd >> 7, buffer[offset], buffer[offset + 1]);
write_chip(CHIP_YMF262, cmd >> 7, buffer[offset] | 0x100, buffer[offset + 1]);
offset += 2;
break;
@ -927,59 +927,34 @@ void generate_all(std::vector<uint8_t> &buffer, uint32_t data_start, uint32_t ou
{
vgm_chip_base *chip = find_chip(CHIP_YM2612, 0);
if (chip != nullptr)
chip->write_pcm(0, size - 8, &buffer[localoffset]);
chip->write_data(ymfm::ACCESS_PCM, 0, size - 8, &buffer[localoffset]);
break;
}
case 0x82: // YM2610 ADPCM ROM data
length = parse_uint32(buffer, localoffset);
start = parse_uint32(buffer, localoffset);
for (int index = 0; index < 2; index++)
{
vgm_chip_base *chip = find_chip(CHIP_YM2610, index);
if (chip != nullptr)
chip->write_adpcm_a(start, size - 8, &buffer[localoffset]);
}
add_rom_data(CHIP_YM2610, ymfm::ACCESS_ADPCM_A, buffer, localoffset, size - 8);
break;
case 0x81: // YM2608 DELTA-T ROM data
length = parse_uint32(buffer, localoffset);
start = parse_uint32(buffer, localoffset);
for (int index = 0; index < 2; index++)
{
vgm_chip_base *chip = find_chip(CHIP_YM2608, index);
if (chip != nullptr)
chip->write_adpcm_b(start, size - 8, &buffer[localoffset]);
}
add_rom_data(CHIP_YM2608, ymfm::ACCESS_ADPCM_B, buffer, localoffset, size - 8);
break;
case 0x83: // YM2610 DELTA-T ROM data
length = parse_uint32(buffer, localoffset);
start = parse_uint32(buffer, localoffset);
for (int index = 0; index < 2; index++)
{
vgm_chip_base *chip = find_chip(CHIP_YM2610, index);
if (chip != nullptr)
chip->write_adpcm_b(start, size - 8, &buffer[localoffset]);
}
add_rom_data(CHIP_YM2610, ymfm::ACCESS_ADPCM_B, buffer, localoffset, size - 8);
break;
case 0x84: // YMF278B ROM data
case 0x87: // YMF278B RAM data
add_rom_data(CHIP_YMF278B, ymfm::ACCESS_PCM, buffer, localoffset, size - 8);
break;
case 0x88: // Y8950 DELTA-T ROM data
length = parse_uint32(buffer, localoffset);
start = parse_uint32(buffer, localoffset);
for (int index = 0; index < 2; index++)
{
vgm_chip_base *chip = find_chip(CHIP_Y8950, index);
if (chip != nullptr)
chip->write_adpcm_b(start, size - 8, &buffer[localoffset]);
}
add_rom_data(CHIP_Y8950, ymfm::ACCESS_ADPCM_B, buffer, localoffset, size - 8);
break;
case 0x80: // Sega PCM ROM data
case 0x84: // YMF278B ROM data
case 0x85: // YMF271 ROM data
case 0x86: // YMZ280B ROM data
case 0x87: // YMF278B RAM data
case 0x89: // MultiPCM ROM data
case 0x8A: // uPD7759 ROM data
case 0x8B: // OKIM6295 ROM data
@ -1022,6 +997,12 @@ void generate_all(std::vector<uint8_t> &buffer, uint32_t data_start, uint32_t ou
offset += 2;
break;
// pp aa dd: YMF278B, port pp, write value dd to register aa
case 0xd0:
write_chip(CHIP_YMF278B, buffer[offset] >> 7, ((buffer[offset] & 0x7f) << 8) | buffer[offset + 1], buffer[offset + 2]);
offset += 3;
break;
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f:
delay = (cmd & 15) + 1;
@ -1080,7 +1061,6 @@ void generate_all(std::vector<uint8_t> &buffer, uint32_t data_start, uint32_t ou
case 0xc6: // mmll dd: WonderSwan, write value dd to memory offset mmll (mm - offset MSB, ll - offset LSB)
case 0xc7: // mmll dd: VSU, write value dd to memory offset mmll (mm - offset MSB, ll - offset LSB)
case 0xc8: // mmll dd: X1-010, write value dd to memory offset mmll (mm - offset MSB, ll - offset LSB)
case 0xd0: // pp aa dd: YMF278B, port pp, write value dd to register aa
case 0xd1: // pp aa dd: YMF271, port pp, write value dd to register aa
case 0xd2: // pp aa dd: SCC1, port pp, write value dd to register aa
case 0xd3: // pp aa dd: K054539, write value dd to register ppaa

View File

@ -55,6 +55,7 @@ public:
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;
@ -108,7 +109,7 @@ inline int32_t clamp(int32_t value, int32_t minval, int32_t maxval)
//-------------------------------------------------
template<typename ArrayType, int ArraySize>
constexpr int32_t array_size(ArrayType (&array)[ArraySize])
constexpr uint32_t array_size(ArrayType (&array)[ArraySize])
{
return ArraySize;
}
@ -257,9 +258,33 @@ inline int16_t roundtrip_fp(int32_t value)
// HELPER CLASSES
//*********************************************************
// forward declarations
enum envelope_state : uint32_t;
// 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, // 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
@ -270,7 +295,7 @@ struct ymfm_output
// clear all outputs to 0
ymfm_output &clear()
{
for (int index = 0; index < NumOutputs; index++)
for (uint32_t index = 0; index < NumOutputs; index++)
data[index] = 0;
return *this;
}
@ -278,7 +303,7 @@ struct ymfm_output
// clamp all outputs to a 16-bit signed value
ymfm_output &clamp16()
{
for (int index = 0; index < NumOutputs; index++)
for (uint32_t index = 0; index < NumOutputs; index++)
data[index] = clamp(data[index], -32768, 32767);
return *this;
}
@ -286,7 +311,7 @@ struct ymfm_output
// run each output value through the floating-point processor
ymfm_output &roundtrip_fp()
{
for (int index = 0; index < NumOutputs; index++)
for (uint32_t index = 0; index < NumOutputs; index++)
data[index] = ymfm::roundtrip_fp(data[index]);
return *this;
}
@ -336,7 +361,7 @@ public:
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 (int index = 0; index < Count; index++) save(data[index]); }
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; }
@ -348,11 +373,11 @@ public:
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 (int index = 0; index < Count; index++) restore(data[index]); }
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 < m_buffer.size()) ? m_buffer[m_offset++] : 0; }
uint8_t read() { return (m_offset < int32_t(m_buffer.size())) ? m_buffer[m_offset++] : 0; }
// internal state
std::vector<uint8_t> &m_buffer;
@ -442,31 +467,13 @@ public:
// needed to the change in IRQ state, signaling any consumers
virtual void ymfm_update_irq(bool asserted) { }
// the chip implementation calls this whenever a new value is written to
// one of the chip's output ports (only applies to some chip types); our
// responsibility is to pass the written data on to any consumers
virtual void ymfm_io_write(uint8_t port, uint8_t data) { }
// 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 an on-chip register is read
// which returns data from one of the chip's input ports; our responsibility
// is to produce the current input value so that it can be reflected by the
// read operation
virtual uint8_t ymfm_io_read(uint8_t port) { return 0; }
// the chip implementation calls this whenever the ADPCM-A engine needs to
// fetch data for sound generation; our responsibility is to read the data
// from the appropriate ROM/RAM at the given offset and return it
virtual uint8_t ymfm_adpcm_a_read(uint32_t offset) { return 0; }
// the chip implementation calls this whenever the ADPCM-B engine needs to
// fetch data for sound generation; our responsibility is to read the data
// from the appropriate ROM/RAM at the given offset and return it
virtual uint8_t ymfm_adpcm_b_read(uint32_t offset) { return 0; }
// the chip implementation calls this whenever the ADPCM-B engine requests
// a write to the sound data; our responsibility is to write the data to
// the appropriate RAM at the given offset
virtual void ymfm_adpcm_b_write(uint32_t offset, uint8_t data) { }
// 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

View File

@ -171,7 +171,7 @@ bool adpcm_a_channel::clock()
uint8_t data;
if (m_curnibble == 0)
{
m_curbyte = m_owner.intf().ymfm_adpcm_a_read(m_curaddress++);
m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_A, m_curaddress++);
data = m_curbyte >> 4;
m_curnibble = 1;
}
@ -482,7 +482,7 @@ void adpcm_b_channel::clock()
// if we're about to process nibble 0, fetch and increment
if (m_curnibble == 0)
{
m_curbyte = m_owner.intf().ymfm_adpcm_b_read(m_curaddress++);
m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress++);
m_curaddress &= 0xffffff;
}
}
@ -569,7 +569,7 @@ uint8_t adpcm_b_channel::read(uint32_t regnum)
// otherwise, write the data and signal ready
else
{
result = m_owner.intf().ymfm_adpcm_b_read(m_curaddress++);
result = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress++);
m_status = STATUS_BRDY;
}
}
@ -645,7 +645,7 @@ void adpcm_b_channel::write(uint32_t regnum, uint8_t value)
// otherwise, write the data and signal ready
else
{
m_owner.intf().ymfm_adpcm_b_write(m_curaddress++, value);
m_owner.intf().ymfm_external_write(ACCESS_ADPCM_B, m_curaddress++, value);
m_status = STATUS_BRDY;
}
}

View File

@ -40,18 +40,6 @@ namespace ymfm
// GLOBAL ENUMERATORS
//*********************************************************
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, // OPZ only; set EG_HAS_REVERB to enable
EG_STATES = 6
};
// three different keyon sources; actual keyon is an OR over all of these
enum keyon_type : uint32_t
{
@ -213,7 +201,7 @@ public:
void keyonoff(uint32_t on, keyon_type type);
// return a reference to our registers
RegisterType &regs() { return m_regs; }
RegisterType &regs() const { return m_regs; }
// simple getters for debugging
envelope_state debug_eg_state() const { return m_env_state; }
@ -274,7 +262,7 @@ public:
uint32_t choffs() const { return m_choffs; }
// assign operators
void assign(int index, fm_operator<RegisterType> *op)
void assign(uint32_t index, fm_operator<RegisterType> *op)
{
assert(index < array_size(m_op));
m_op[index] = op;
@ -309,10 +297,10 @@ public:
}
// return a reference to our registers
RegisterType &regs() { return m_regs; }
RegisterType &regs() const { return m_regs; }
// simple getters for debugging
fm_operator<RegisterType> *debug_operator(int index) const { return m_op[index]; }
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
@ -420,8 +408,8 @@ public:
void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; }
// simple getters for debugging
fm_channel<RegisterType> *debug_channel(int index) const { return m_channel[index].get(); }
fm_operator<RegisterType> *debug_operator(int index) const { return m_operator[index].get(); }
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

View File

@ -540,8 +540,11 @@ void fm_operator<RegisterType>::start_attack(bool is_restart)
if (RegisterType::EG_HAS_SSG && !is_restart)
m_ssg_inverted = m_regs.op_ssg_eg_enable(m_opoffs) & bitfield(m_regs.op_ssg_eg_mode(m_opoffs), 2);
// reset the phase when we start an attack
m_phase = 0;
// reset the phase when we start an attack due to a key on
// (but not when due to an SSG-EG restart except in certain cases
// managed directly by the SSG-EG code)
if (!is_restart)
m_phase = 0;
// if the attack rate >= 62 then immediately go to max attenuation
if (m_cache.eg_rate[EG_ATTACK] >= 62)
@ -634,10 +637,10 @@ void fm_operator<RegisterType>::clock_ssg_eg_state()
// set the inverted flag to the end state (0 for modes 1/7, 1 for modes 3/5)
m_ssg_inverted = bitfield(mode, 2) ^ bitfield(mode, 1);
// if holding low (modes 1/5), force the attenuation to maximum
// once we're past the attack phase
if (m_env_state != EG_ATTACK && bitfield(mode, 1) == 0)
m_env_attenuation = 0x3ff;
// if holding, force the attenuation to the expected value once we're
// past the attack phase
if (m_env_state != EG_ATTACK)
m_env_attenuation = m_ssg_inverted ? 0x200 : 0x3ff;
}
// continuous modes (0/2/4/6)
@ -650,7 +653,7 @@ void fm_operator<RegisterType>::clock_ssg_eg_state()
if (m_env_state == EG_DECAY || m_env_state == EG_SUSTAIN)
start_attack(true);
// phase is reset to 0 regardless in modes 0/4
// phase is reset to 0 in modes 0/4
if (bitfield(mode, 1) == 0)
m_phase = 0;
}
@ -837,12 +840,12 @@ void fm_channel<RegisterType>::save_restore(ymfm_saved_state &state)
template<class RegisterType>
void fm_channel<RegisterType>::keyonoff(uint32_t states, keyon_type type, uint32_t chnum)
{
for (int opnum = 0; opnum < array_size(m_op); opnum++)
for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++)
if (m_op[opnum] != nullptr)
m_op[opnum]->keyonoff(bitfield(states, opnum), type);
if (debug::LOG_KEYON_EVENTS && ((debug::GLOBAL_FM_CHANNEL_MASK >> chnum) & 1) != 0)
for (int opnum = 0; opnum < array_size(m_op); opnum++)
for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++)
if (m_op[opnum] != nullptr)
debug::log_keyon("%c%s\n", bitfield(states, opnum) ? '+' : '-', m_regs.log_keyon(m_choffs, m_op[opnum]->opoffs()).c_str());
}
@ -858,7 +861,7 @@ bool fm_channel<RegisterType>::prepare()
uint32_t active_mask = 0;
// prepare all operators and determine if they are active
for (int opnum = 0; opnum < array_size(m_op); opnum++)
for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++)
if (m_op[opnum] != nullptr)
if (m_op[opnum]->prepare())
active_mask |= 1 << opnum;
@ -878,7 +881,7 @@ void fm_channel<RegisterType>::clock(uint32_t env_counter, int32_t lfo_raw_pm)
m_feedback[0] = m_feedback[1];
m_feedback[1] = m_feedback_in;
for (int opnum = 0; opnum < array_size(m_op); opnum++)
for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++)
if (m_op[opnum] != nullptr)
m_op[opnum]->clock(env_counter, lfo_raw_pm);
}
@ -1173,11 +1176,11 @@ fm_engine_base<RegisterType>::fm_engine_base(ymfm_interface &intf) :
m_intf.m_engine = this;
// create the channels
for (int chnum = 0; chnum < CHANNELS; chnum++)
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
m_channel[chnum] = std::make_unique<fm_channel<RegisterType>>(*this, RegisterType::channel_offset(chnum));
// create the operators
for (int opnum = 0; opnum < OPERATORS; opnum++)
for (uint32_t opnum = 0; opnum < OPERATORS; opnum++)
m_operator[opnum] = std::make_unique<fm_operator<RegisterType>>(*this, RegisterType::operator_offset(opnum));
// do the initial operator assignment
@ -1232,11 +1235,11 @@ void fm_engine_base<RegisterType>::save_restore(ymfm_saved_state &state)
m_regs.save_restore(state);
// save channel data
for (int chnum = 0; chnum < CHANNELS; chnum++)
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
m_channel[chnum]->save_restore(state);
// save operator data
for (int opnum = 0; opnum < OPERATORS; opnum++)
for (uint32_t opnum = 0; opnum < OPERATORS; opnum++)
m_operator[opnum]->save_restore(state);
// invalidate any caches
@ -1262,7 +1265,7 @@ uint32_t fm_engine_base<RegisterType>::clock(uint32_t chanmask)
// call each channel to prepare
m_active_channels = 0;
for (int chnum = 0; chnum < CHANNELS; chnum++)
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
if (m_channel[chnum]->prepare())
m_active_channels |= 1 << chnum;
@ -1282,7 +1285,7 @@ uint32_t fm_engine_base<RegisterType>::clock(uint32_t chanmask)
int32_t lfo_raw_pm = m_regs.clock_noise_and_lfo();
// now update the state of all the channels and operators
for (int chnum = 0; chnum < CHANNELS; chnum++)
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
m_channel[chnum]->clock(m_env_counter, lfo_raw_pm);
@ -1318,7 +1321,7 @@ void fm_engine_base<RegisterType>::output(output_data &output, uint32_t rshift,
uint32_t phase_select = (bitfield(op13phase, 2) ^ bitfield(op13phase, 7)) | bitfield(op13phase, 3) | (bitfield(op17phase, 5) ^ bitfield(op17phase, 3));
// sum over all the desired channels
for (int chnum = 0; chnum < CHANNELS; chnum++)
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
{
if (chnum == 6)
@ -1336,7 +1339,7 @@ void fm_engine_base<RegisterType>::output(output_data &output, uint32_t rshift,
else
{
// sum over all the desired channels
for (int chnum = 0; chnum < CHANNELS; chnum++)
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
{
if (m_channel[chnum]->is4op())
@ -1413,8 +1416,8 @@ void fm_engine_base<RegisterType>::assign_operators()
typename RegisterType::operator_mapping map;
m_regs.operator_map(map);
for (int chnum = 0; chnum < CHANNELS; chnum++)
for (int index = 0; index < 4; index++)
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
for (uint32_t index = 0; index < 4; index++)
{
uint32_t opnum = bitfield(map.chan[chnum], 8 * index, 8);
m_channel[chnum]->assign(index, (opnum == 0xff) ? nullptr : m_operator[opnum].get());
@ -1466,7 +1469,7 @@ void fm_engine_base<RegisterType>::engine_timer_expired(uint32_t tnum)
// if timer A fired in CSM mode, trigger CSM on all relevant channels
if (tnum == 0 && m_regs.csm())
for (int chnum = 0; chnum < CHANNELS; chnum++)
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(RegisterType::CSM_TRIGGER_MASK, chnum))
m_channel[chnum]->keyonoff(1, KEYON_CSM, chnum);

View File

@ -80,13 +80,13 @@ opl_registers_base<Revision>::opl_registers_base() :
uint16_t *wf7 = &m_waveform[7 % WAVEFORMS][0];
// create the waveforms
for (int index = 0; index < WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
wf0[index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
if (WAVEFORMS >= 4)
{
uint16_t zeroval = wf0[0];
for (int index = 0; index < WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
{
wf1[index] = bitfield(index, 9) ? zeroval : wf0[index];
wf2[index] = wf0[index] & 0x7fff;
@ -440,17 +440,17 @@ opll_registers::opll_registers() :
m_lfo_am(0)
{
// create the waveforms
for (int index = 0; index < WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
uint16_t zeroval = m_waveform[0][0];
for (int index = 0; index < WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[1][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index];
// initialize the instruments to something sane
for (int choffs = 0; choffs < CHANNELS; choffs++)
for (uint32_t choffs = 0; choffs < CHANNELS; choffs++)
m_chinst[choffs] = &m_regdata[0];
for (int opoffs = 0; opoffs < OPERATORS; opoffs++)
for (uint32_t opoffs = 0; opoffs < OPERATORS; opoffs++)
m_opinst[opoffs] = &m_regdata[bitfield(opoffs, 0)];
}
@ -794,7 +794,7 @@ void ym3526::write_address(uint8_t data)
{
// YM3526 doesn't expose a busy signal, and the datasheets don't indicate
// delays, but all other OPL chips need 12 cycles for address writes
m_fm.intf().ymfm_set_busy_end(12);
m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale());
// just set the address
m_address = data;
@ -810,7 +810,7 @@ void ym3526::write_data(uint8_t data)
{
// YM3526 doesn't expose a busy signal, and the datasheets don't indicate
// delays, but all other OPL chips need 84 cycles for data writes
m_fm.intf().ymfm_set_busy_end(84);
m_fm.intf().ymfm_set_busy_end(84 * m_fm.clock_prescale());
// write to FM
m_fm.write(m_address, data);
@ -933,7 +933,7 @@ uint8_t y8950::read_data()
switch (m_address)
{
case 0x05: // keyboard in
result = m_fm.intf().ymfm_io_read(1);
result = m_fm.intf().ymfm_external_read(ACCESS_IO, 1);
break;
case 0x09: // ADPCM data
@ -942,7 +942,7 @@ uint8_t y8950::read_data()
break;
case 0x19: // I/O data
result = m_fm.intf().ymfm_io_read(0);
result = m_fm.intf().ymfm_external_read(ACCESS_IO, 0);
break;
default:
@ -983,7 +983,7 @@ void y8950::write_address(uint8_t data)
{
// Y8950 doesn't expose a busy signal, but it does indicate that
// address writes should be no faster than every 12 clocks
m_fm.intf().ymfm_set_busy_end(12);
m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale());
// just set the address
m_address = data;
@ -1000,7 +1000,7 @@ void y8950::write_data(uint8_t data)
// Y8950 doesn't expose a busy signal, but it does indicate that
// data writes should be no faster than every 12 clocks for
// registers 00-1A, or every 84 clocks for other registers
m_fm.intf().ymfm_set_busy_end((m_address <= 0x1a) ? 12 : 84);
m_fm.intf().ymfm_set_busy_end(((m_address <= 0x1a) ? 12 : 84) * m_fm.clock_prescale());
// handle special addresses
switch (m_address)
@ -1011,7 +1011,7 @@ void y8950::write_data(uint8_t data)
break;
case 0x06: // keyboard out
m_fm.intf().ymfm_io_write(1, data);
m_fm.intf().ymfm_external_write(ACCESS_IO, 1, data);
break;
case 0x08: // split FM/ADPCM-B
@ -1041,7 +1041,7 @@ void y8950::write_data(uint8_t data)
break;
case 0x19: // I/O data
m_fm.intf().ymfm_io_write(0, data & m_io_ddr);
m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data & m_io_ddr);
break;
default: // everything else to FM
@ -1174,7 +1174,7 @@ void ym3812::write_address(uint8_t data)
{
// YM3812 doesn't expose a busy signal, but it does indicate that
// address writes should be no faster than every 12 clocks
m_fm.intf().ymfm_set_busy_end(12);
m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale());
// just set the address
m_address = data;
@ -1190,7 +1190,7 @@ void ym3812::write_data(uint8_t data)
{
// YM3812 doesn't expose a busy signal, but it does indicate that
// data writes should be no faster than every 84 clocks
m_fm.intf().ymfm_set_busy_end(84);
m_fm.intf().ymfm_set_busy_end(84 * m_fm.clock_prescale());
// write to FM
m_fm.write(m_address, data);
@ -1318,7 +1318,7 @@ void ymf262::write_address(uint8_t data)
{
// YMF262 doesn't expose a busy signal, but it does indicate that
// address writes should be no faster than every 32 clocks
m_fm.intf().ymfm_set_busy_end(32);
m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
// just set the address
m_address = data;
@ -1334,7 +1334,7 @@ void ymf262::write_data(uint8_t data)
{
// YMF262 doesn't expose a busy signal, but it does indicate that
// data writes should be no faster than every 32 clocks
m_fm.intf().ymfm_set_busy_end(32);
m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
// write to FM
m_fm.write(m_address, data);
@ -1350,7 +1350,7 @@ void ymf262::write_address_hi(uint8_t data)
{
// YMF262 doesn't expose a busy signal, but it does indicate that
// address writes should be no faster than every 32 clocks
m_fm.intf().ymfm_set_busy_end(32);
m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale());
// just set the address
m_address = data | 0x100;
@ -1411,6 +1411,213 @@ void ymf262::generate(output_data *output, uint32_t numsamples)
//*********************************************************
// YMF289B
//*********************************************************
// YMF289B is a YMF262 with the following changes:
// * "Power down" mode added
// * Bulk register clear added
// * Busy flag added to the status register
// * Shorter busy times
// * All registers can be read
// * Only 2 outputs exposed
//-------------------------------------------------
// ymf289b - constructor
//-------------------------------------------------
ymf289b::ymf289b(ymfm_interface &intf) :
m_address(0),
m_fm(intf)
{
}
//-------------------------------------------------
// reset - reset the system
//-------------------------------------------------
void ymf289b::reset()
{
// reset the engines
m_fm.reset();
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void ymf289b::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_address);
m_fm.save_restore(state);
}
//-------------------------------------------------
// read_status - read the status register
//-------------------------------------------------
uint8_t ymf289b::read_status()
{
uint8_t result = m_fm.status();
// YMF289B adds a busy flag
if (ymf289b_mode() && m_fm.intf().ymfm_is_busy())
result |= STATUS_BUSY_FLAGS;
return result;
}
//-------------------------------------------------
// read_data - read the data register
//-------------------------------------------------
uint8_t ymf289b::read_data()
{
uint8_t result = 0xff;
// YMF289B can read register data back
if (ymf289b_mode())
result = m_fm.regs().read(m_address);
return result;
}
//-------------------------------------------------
// read - handle a read from the device
//-------------------------------------------------
uint8_t ymf289b::read(uint32_t offset)
{
uint8_t result = 0xff;
switch (offset & 3)
{
case 0: // status port
result = read_status();
break;
case 1: // data port
result = read_data();
break;
case 2:
case 3:
debug::log_unexpected_read_write("Unexpected read from YMF289B offset %d\n", offset & 3);
break;
}
return result;
}
//-------------------------------------------------
// write_address - handle a write to the address
// register
//-------------------------------------------------
void ymf289b::write_address(uint8_t data)
{
m_address = data;
// count busy time
m_fm.intf().ymfm_set_busy_end(56);
}
//-------------------------------------------------
// write_data - handle a write to the data
// register
//-------------------------------------------------
void ymf289b::write_data(uint8_t data)
{
// write to FM
m_fm.write(m_address, data);
// writes to 0x108 with the CLR flag set clear the registers
if (m_address == 0x108 && bitfield(data, 2) != 0)
m_fm.regs().reset();
// count busy time
m_fm.intf().ymfm_set_busy_end(56);
}
//-------------------------------------------------
// write_address_hi - handle a write to the upper
// address register
//-------------------------------------------------
void ymf289b::write_address_hi(uint8_t data)
{
// just set the address
m_address = data | 0x100;
// tests reveal that in compatibility mode, upper bit is masked
// except for register 0x105
if (m_fm.regs().newflag() == 0 && m_address != 0x105)
m_address &= 0xff;
// count busy time
m_fm.intf().ymfm_set_busy_end(56);
}
//-------------------------------------------------
// write - handle a write to the register
// interface
//-------------------------------------------------
void ymf289b::write(uint32_t offset, uint8_t data)
{
switch (offset & 3)
{
case 0: // address port
write_address(data);
break;
case 1: // data port
write_data(data);
break;
case 2: // address port
write_address_hi(data);
break;
case 3: // data port
write_data(data);
break;
}
}
//-------------------------------------------------
// generate - generate samples of sound
//-------------------------------------------------
void ymf289b::generate(output_data *output, uint32_t numsamples)
{
for (uint32_t samp = 0; samp < numsamples; samp++, output++)
{
// clock the system
m_fm.clock(fm_engine::ALL_CHANNELS);
// update the FM content; mixing details for YMF262 need verification
fm_engine::output_data full;
m_fm.output(full.clear(), 0, 32767, fm_engine::ALL_CHANNELS);
// YMF278B output is 16-bit offset serial via YAC512 DAC, but
// only 2 of the 4 outputs are exposed
output->data[0] = full.data[0];
output->data[1] = full.data[1];
output->clamp16();
}
}
//*********************************************************
// YMF278B
//*********************************************************
@ -1421,7 +1628,11 @@ void ymf262::generate(output_data *output, uint32_t numsamples)
ymf278b::ymf278b(ymfm_interface &intf) :
m_address(0),
m_fm(intf)
m_fm_pos(0),
m_load_remaining(0),
m_next_status_id(false),
m_fm(intf),
m_pcm(intf)
{
}
@ -1434,6 +1645,10 @@ void ymf278b::reset()
{
// reset the engines
m_fm.reset();
m_pcm.reset();
// next status read will return ID
m_next_status_id = true;
}
@ -1444,7 +1659,11 @@ void ymf278b::reset()
void ymf278b::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_address);
state.save_restore(m_fm_pos);
state.save_restore(m_load_remaining);
state.save_restore(m_next_status_id);
m_fm.save_restore(state);
m_pcm.save_restore(state);
}
@ -1454,7 +1673,47 @@ void ymf278b::save_restore(ymfm_saved_state &state)
uint8_t ymf278b::read_status()
{
return m_fm.status();
uint8_t result;
// first status read after initialization returns a chip ID, which
// varies based on the "new" flags, indicating the mode
if (m_next_status_id)
{
if (m_fm.regs().new2flag())
result = 0x02;
else if (m_fm.regs().newflag())
result = 0x00;
else
result = 0x06;
m_next_status_id = false;
}
else
{
result = m_fm.status();
if (m_fm.intf().ymfm_is_busy())
result |= STATUS_BUSY;
if (m_load_remaining != 0)
result |= STATUS_LD;
// if new2 flag is not set, we're in OPL2 or OPL3 mode
if (!m_fm.regs().new2flag())
result &= ~(STATUS_BUSY | STATUS_LD);
}
return result;
}
//-------------------------------------------------
// write_data_pcm - handle a write to the PCM data
// register
//-------------------------------------------------
uint8_t ymf278b::read_data_pcm()
{
// write to FM
if (bitfield(m_address, 9) != 0)
return m_pcm.read(m_address & 0xff);
return 0;
}
@ -1471,8 +1730,8 @@ uint8_t ymf278b::read(uint32_t offset)
result = read_status();
break;
case 5: // PCM data port (not supported for now)
//result = read_data_pcm();
case 5: // PCM data port
result = read_data_pcm();
break;
default:
@ -1503,7 +1762,19 @@ void ymf278b::write_address(uint8_t data)
void ymf278b::write_data(uint8_t data)
{
// write to FM
m_fm.write(m_address, data);
if (bitfield(m_address, 9) == 0)
{
uint8_t old = m_fm.regs().new2flag();
m_fm.write(m_address, data);
// changing NEW2 from 0->1 causes the next status read to
// return the chip ID
if (old == 0 && m_fm.regs().new2flag() != 0)
m_next_status_id = true;
}
// BUSY goes for 56 clocks on FM writes
m_fm.intf().ymfm_set_busy_end(56);
}
@ -1524,6 +1795,44 @@ void ymf278b::write_address_hi(uint8_t data)
}
//-------------------------------------------------
// write_address_pcm - handle a write to the upper
// address register
//-------------------------------------------------
void ymf278b::write_address_pcm(uint8_t data)
{
// just set the address
m_address = data | 0x200;
// YMF262, in compatibility mode, treats the upper bit as masked
// except for register 0x105; assuming YMF278B works the same way?
if (m_fm.regs().newflag() == 0 && m_address != 0x105)
m_address &= 0xff;
}
//-------------------------------------------------
// write_data_pcm - handle a write to the PCM data
// register
//-------------------------------------------------
void ymf278b::write_data_pcm(uint8_t data)
{
// write to FM
if (bitfield(m_address, 9) != 0)
m_pcm.write(m_address & 0xff, data);
// writes to the waveform number cause loads to happen for "about 300usec"
// which is ~13 samples at the nominal output frequency of 44.1kHz
if (m_address >= 0x08 && m_address <= 0x1f)
m_load_remaining = 13;
// BUSY goes for 88 clocks on PCM writes
m_fm.intf().ymfm_set_busy_end(88);
}
//-------------------------------------------------
// write - handle a write to the register
// interface
@ -1549,12 +1858,12 @@ void ymf278b::write(uint32_t offset, uint8_t data)
write_data(data);
break;
case 4: // PCM address port (not supported for now)
//write_address_pcm(data);
case 4: // PCM address port
write_address_pcm(data);
break;
case 5: // PCM address port (not supported for now)
//write_data_pcm(data);
case 5: // PCM address port
write_data_pcm(data);
break;
default:
@ -1570,17 +1879,50 @@ void ymf278b::write(uint32_t offset, uint8_t data)
void ymf278b::generate(output_data *output, uint32_t numsamples)
{
for (uint32_t samp = 0; samp < numsamples; samp++)
static const int16_t s_mix_scale[8] = { 0x7fa, 0x5a4, 0x3fd, 0x2d2, 0x1fe, 0x169, 0xff, 0 };
int32_t const pcm_l = s_mix_scale[m_pcm.regs().mix_pcm_l()];
int32_t const pcm_r = s_mix_scale[m_pcm.regs().mix_pcm_r()];
int32_t const fm_l = s_mix_scale[m_pcm.regs().mix_fm_l()];
int32_t const fm_r = s_mix_scale[m_pcm.regs().mix_fm_r()];
for (uint32_t samp = 0; samp < numsamples; samp++, output++)
{
// clock the system
m_fm_pos += FM_EXTRA_SAMPLE_STEP;
if (m_fm_pos >= FM_EXTRA_SAMPLE_THRESH)
{
m_fm.clock(fm_engine::ALL_CHANNELS);
m_fm_pos -= FM_EXTRA_SAMPLE_THRESH;
}
m_fm.clock(fm_engine::ALL_CHANNELS);
m_pcm.clock(pcm_engine::ALL_CHANNELS);
// update the FM content; mixing details for YMF278B need verification
m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS);
fm_engine::output_data fmout;
m_fm.output(fmout.clear(), 0, 32767, fm_engine::ALL_CHANNELS);
// update the PCM content
pcm_engine::output_data pcmout;
m_pcm.output(pcmout.clear(), pcm_engine::ALL_CHANNELS);
// DO0 output: FM channels 2+3 only
output->data[0] = fmout.data[2];
output->data[1] = fmout.data[3];
// DO1 output: wavetable channels 2+3 only
output->data[2] = pcmout.data[2];
output->data[3] = pcmout.data[3];
// DO2 output: mixed FM channels 0+1 and wavetable channels 0+1
output->data[4] = (fmout.data[0] * fm_l + pcmout.data[0] * pcm_l) >> 11;
output->data[5] = (fmout.data[1] * fm_r + pcmout.data[1] * pcm_r) >> 11;
// YMF278B output is 16-bit 2s complement serial
output->clamp16();
}
// decrement the load waiting count
if (m_load_remaining > 0)
m_load_remaining -= std::min(m_load_remaining, numsamples);
}

View File

@ -36,6 +36,7 @@
#include "ymfm.h"
#include "ymfm_adpcm.h"
#include "ymfm_fm.h"
#include "ymfm_pcm.h"
namespace ymfm
{
@ -170,7 +171,7 @@ public:
void operator_map(operator_mapping &dest) const;
// OPL4 apparently can read back FM registers?
uint8_t read(uint16_t index) { return m_regdata[index]; }
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);
@ -375,6 +376,9 @@ public:
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);
@ -673,22 +677,19 @@ protected:
};
// ======================> ymf289b
//*********************************************************
// OPL4 IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ymf278b
class ymf278b
class ymf289b
{
static constexpr uint8_t STATUS_BUSY_FLAGS = 0x05;
public:
using fm_engine = fm_engine_base<opl4_registers>;
using fm_engine = fm_engine_base<opl3_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
static constexpr uint32_t OUTPUTS = 2;
// constructor
ymf278b(ymfm_interface &intf);
ymf289b(ymfm_interface &intf);
// reset
void reset();
@ -702,6 +703,7 @@ public:
// read access
uint8_t read_status();
uint8_t read_data();
uint8_t read(uint32_t offset);
// write access
@ -714,6 +716,9 @@ public:
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal helpers
bool ymf289b_mode() { return ((m_fm.regs().read(0x105) & 0x04) != 0); }
// internal state
uint16_t m_address; // address register
fm_engine m_fm; // core FM engine
@ -721,6 +726,74 @@ protected:
//*********************************************************
// OPL4 IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ymf278b
class ymf278b
{
// Using the nominal datasheet frequency of 33.868MHz, the output of the
// chip will be clock/768 = 44.1kHz. However, the FM engine is clocked
// internally at clock/(19*36), or 49.515kHz, so the FM output needs to
// be downsampled. We treat this as needing to clock the FM engine an
// extra tick every few samples. The exact ratio is 768/(19*36) or
// 768/684 = 192/171. So if we always clock the FM once, we'll have
// 192/171 - 1 = 21/171 left. Thus we count 21 for each sample and when
// it gets above 171, we tick an extra time.
static constexpr uint32_t FM_EXTRA_SAMPLE_THRESH = 171;
static constexpr uint32_t FM_EXTRA_SAMPLE_STEP = 192 - FM_EXTRA_SAMPLE_THRESH;
public:
using fm_engine = fm_engine_base<opl4_registers>;
static constexpr uint32_t OUTPUTS = 6;
using output_data = ymfm_output<OUTPUTS>;
static constexpr uint8_t STATUS_BUSY = 0x01;
static constexpr uint8_t STATUS_LD = 0x02;
// constructor
ymf278b(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return input_clock / 768; }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read_data_pcm();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write_address_hi(uint8_t data);
void write_address_pcm(uint8_t data);
void write_data_pcm(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint16_t m_address; // address register
uint32_t m_fm_pos; // FM resampling position
uint32_t m_load_remaining; // how many more samples until LD flag clears
bool m_next_status_id; // flag to track which status ID to return
fm_engine m_fm; // core FM engine
pcm_engine m_pcm; // core PCM engine
};
//*********************************************************
// OPLL IMPLEMENTATION CLASSES
//*********************************************************

View File

@ -51,12 +51,12 @@ opm_registers::opm_registers() :
m_lfo_am(0)
{
// create the waveforms
for (int index = 0; index < WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
// create the LFO waveforms; AM in the low 8 bits, PM in the upper 8
// waveforms are adjusted to match the pictures in the application manual
for (int index = 0; index < LFO_WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++)
{
// waveform 0 is a sawtooth
uint8_t am = index ^ 0xff;
@ -484,7 +484,7 @@ void ym2151::write_data(uint8_t data)
if (m_address == 0x1b)
{
// writes to register 0x1B send the upper 2 bits to the output lines
m_fm.intf().ymfm_io_write(0, data >> 6);
m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6);
}
// mark busy for a bit

View File

@ -48,7 +48,7 @@ opn_registers_base<IsOpnA>::opn_registers_base() :
m_lfo_am(0)
{
// create the waveforms
for (int index = 0; index < WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
}
@ -1548,6 +1548,423 @@ void ym2608::clock_fm_and_adpcm()
}
//*********************************************************
// YMF288
//*********************************************************
// YMF288 is a YM2608 with the following changes:
// * ADPCM-B part removed
// * prescaler removed (fixed at 6)
// * CSM removed
// * Low power mode added
// * SSG tone frequency is altered in some way? (explicitly DC for Tp 0-7, also double volume in some cases)
// * I/O ports removed
// * Shorter busy times
// * All registers can be read
//-------------------------------------------------
// ymf288 - constructor
//-------------------------------------------------
ymf288::ymf288(ymfm_interface &intf) :
m_fidelity(OPN_FIDELITY_MAX),
m_address(0),
m_irq_enable(0x03),
m_flag_control(0x03),
m_fm(intf),
m_ssg(intf),
m_ssg_resampler(m_ssg),
m_adpcm_a(intf, 0)
{
m_last_fm.clear();
update_prescale();
}
//-------------------------------------------------
// reset - reset the system
//-------------------------------------------------
void ymf288::reset()
{
// reset the engines
m_fm.reset();
m_ssg.reset();
m_adpcm_a.reset();
// configure ADPCM percussion sounds; these are present in an embedded ROM
m_adpcm_a.set_start_end(0, 0x0000, 0x01bf); // bass drum
m_adpcm_a.set_start_end(1, 0x01c0, 0x043f); // snare drum
m_adpcm_a.set_start_end(2, 0x0440, 0x1b7f); // top cymbal
m_adpcm_a.set_start_end(3, 0x1b80, 0x1cff); // high hat
m_adpcm_a.set_start_end(4, 0x1d00, 0x1f7f); // tom tom
m_adpcm_a.set_start_end(5, 0x1f80, 0x1fff); // rim shot
// initialize our special interrupt states, then read the upper status
// register, which updates the IRQs
m_irq_enable = 0x03;
m_flag_control = 0x00;
read_status_hi();
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void ymf288::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_address);
state.save_restore(m_irq_enable);
state.save_restore(m_flag_control);
state.save_restore(m_last_fm.data);
m_fm.save_restore(state);
m_ssg.save_restore(state);
m_ssg_resampler.save_restore(state);
m_adpcm_a.save_restore(state);
}
//-------------------------------------------------
// read_status - read the status register
//-------------------------------------------------
uint8_t ymf288::read_status()
{
uint8_t result = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB);
if (m_fm.intf().ymfm_is_busy())
result |= fm_engine::STATUS_BUSY;
return result;
}
//-------------------------------------------------
// read_data - read the data register
//-------------------------------------------------
uint8_t ymf288::read_data()
{
uint8_t result = 0;
if (m_address < 0x0e)
{
// 00-0D: Read from SSG
result = m_ssg.read(m_address & 0x0f);
}
else if (m_address < 0x10)
{
// 0E-0F: I/O ports not supported
result = 0xff;
}
else if (m_address == 0xff)
{
// FF: ID code
result = 2;
}
else if (ymf288_mode())
{
// registers are readable in YMF288 mode
result = m_fm.regs().read(m_address);
}
return result;
}
//-------------------------------------------------
// read_status_hi - read the extended status
// register
//-------------------------------------------------
uint8_t ymf288::read_status_hi()
{
// fetch regular status
uint8_t status = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB);
// turn off any bits that have been requested to be masked
status &= ~(m_flag_control & 0x03);
// update the status so that IRQs are propagated
m_fm.set_reset_status(status, ~status);
// merge in the busy flag
if (m_fm.intf().ymfm_is_busy())
status |= fm_engine::STATUS_BUSY;
return status;
}
//-------------------------------------------------
// read - handle a read from the device
//-------------------------------------------------
uint8_t ymf288::read(uint32_t offset)
{
uint8_t result = 0;
switch (offset & 3)
{
case 0: // status port, YM2203 compatible
result = read_status();
break;
case 1: // data port
result = read_data();
break;
case 2: // status port, extended
result = read_status_hi();
break;
case 3: // unmapped
debug::log_unexpected_read_write("Unexpected read from YMF288 offset %d\n", offset & 3);
break;
}
return result;
}
//-------------------------------------------------
// write_address - handle a write to the address
// register
//-------------------------------------------------
void ymf288::write_address(uint8_t data)
{
// just set the address
m_address = data;
// in YMF288 mode, busy is signaled after address writes too
if (ymf288_mode())
m_fm.intf().ymfm_set_busy_end(16);
}
//-------------------------------------------------
// write - handle a write to the data register
//-------------------------------------------------
void ymf288::write_data(uint8_t data)
{
// ignore if paired with upper address
if (bitfield(m_address, 8))
return;
// wait times are shorter in YMF288 mode
int busy_cycles = ymf288_mode() ? 16 : 32 * m_fm.clock_prescale();
if (m_address < 0x0e)
{
// 00-0D: write to SSG
m_ssg.write(m_address & 0x0f, data);
}
else if (m_address < 0x10)
{
// 0E-0F: I/O ports not supported
}
else if (m_address < 0x20)
{
// 10-1F: write to ADPCM-A
m_adpcm_a.write(m_address & 0x0f, data);
busy_cycles = 32 * m_fm.clock_prescale();
}
else if (m_address == 0x27)
{
// 27: mode register; CSM isn't supported so disable it
data &= 0x7f;
m_fm.write(m_address, data);
}
else if (m_address == 0x29)
{
// 29: special IRQ mask register
m_irq_enable = data;
m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x03);
}
else
{
// 20-27, 2A-FF: write to FM
m_fm.write(m_address, data);
}
// mark busy for a bit
m_fm.intf().ymfm_set_busy_end(busy_cycles);
}
//-------------------------------------------------
// write_address_hi - handle a write to the upper
// address register
//-------------------------------------------------
void ymf288::write_address_hi(uint8_t data)
{
// just set the address
m_address = 0x100 | data;
// in YMF288 mode, busy is signaled after address writes too
if (ymf288_mode())
m_fm.intf().ymfm_set_busy_end(16);
}
//-------------------------------------------------
// write_data_hi - handle a write to the upper
// data register
//-------------------------------------------------
void ymf288::write_data_hi(uint8_t data)
{
// ignore if paired with upper address
if (!bitfield(m_address, 8))
return;
// wait times are shorter in YMF288 mode
int busy_cycles = ymf288_mode() ? 16 : 32 * m_fm.clock_prescale();
if (m_address == 0x110)
{
// 110: IRQ flag control
if (bitfield(data, 7))
m_fm.set_reset_status(0, 0xff);
else
{
m_flag_control = data;
m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x03);
}
}
else
{
// 100-10F,111-1FF: write to FM
m_fm.write(m_address, data);
}
// mark busy for a bit
m_fm.intf().ymfm_set_busy_end(busy_cycles);
}
//-------------------------------------------------
// write - handle a write to the register
// interface
//-------------------------------------------------
void ymf288::write(uint32_t offset, uint8_t data)
{
switch (offset & 3)
{
case 0: // address port
write_address(data);
break;
case 1: // data port
write_data(data);
break;
case 2: // upper address port
write_address_hi(data);
break;
case 3: // upper data port
write_data_hi(data);
break;
}
}
//-------------------------------------------------
// generate - generate one sample of sound
//-------------------------------------------------
void ymf288::generate(output_data *output, uint32_t numsamples)
{
// FM output is just repeated the prescale number of times; note that
// 0 is a special 1.5 case
if (m_fm_samples_per_output != 0)
{
for (uint32_t samp = 0; samp < numsamples; samp++, output++)
{
if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0)
clock_fm_and_adpcm();
output->data[0] = m_last_fm.data[0];
output->data[1] = m_last_fm.data[1];
}
}
else
{
for (uint32_t samp = 0; samp < numsamples; samp++, output++)
{
uint32_t step = (m_ssg_resampler.sampindex() + samp) % 3;
if (step == 0)
clock_fm_and_adpcm();
output->data[0] = m_last_fm.data[0];
output->data[1] = m_last_fm.data[1];
if (step == 1)
{
clock_fm_and_adpcm();
output->data[0] = (output->data[0] + m_last_fm.data[0]) / 2;
output->data[1] = (output->data[1] + m_last_fm.data[1]) / 2;
}
}
}
// resample the SSG as configured
m_ssg_resampler.resample(output - numsamples, numsamples);
}
//-------------------------------------------------
// update_prescale - update the prescale value,
// recomputing derived values
//-------------------------------------------------
void ymf288::update_prescale()
{
// Fidelity: ---- minimum ---- ---- medium ----- ---- maximum-----
// rate = clock/144 rate = clock/144 rate = clock/16
// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate
// 6 1:1 2:9 1:1 2:9 9:1 2:1
// compute the number of FM samples per output sample, and select the
// resampler function
if (m_fidelity == OPN_FIDELITY_MIN || m_fidelity == OPN_FIDELITY_MED)
{
m_fm_samples_per_output = 1;
m_ssg_resampler.configure(2, 9);
}
else
{
m_fm_samples_per_output = 9;
m_ssg_resampler.configure(2, 1);
}
// if overriding the SSG, override the configuration with the nop
// resampler to at least keep the sample index moving forward
if (m_ssg.overridden())
m_ssg_resampler.configure(0, 0);
}
//-------------------------------------------------
// clock_fm_and_adpcm - clock FM and ADPCM state
//-------------------------------------------------
void ymf288::clock_fm_and_adpcm()
{
// top bit of the IRQ enable flags controls 3-channel vs 6-channel mode
uint32_t fmmask = bitfield(m_irq_enable, 7) ? 0x3f : 0x07;
// clock the system
uint32_t env_counter = m_fm.clock(fm_engine::ALL_CHANNELS);
// clock the ADPCM-A engine on every envelope cycle
// (channels 4 and 5 clock every 2 envelope clocks)
if (bitfield(env_counter, 0, 2) == 0)
m_adpcm_a.clock(bitfield(env_counter, 2) ? 0x0f : 0x3f);
// update the FM content; OPNA is 13-bit with no intermediate clipping
m_fm.output(m_last_fm.clear(), 1, 32767, fmmask);
// mix in the ADPCM
m_adpcm_a.output(m_last_fm, 0x3f);
}
//*********************************************************
// YM2610
@ -1629,11 +2046,16 @@ uint8_t ym2610::read_status()
uint8_t ym2610::read_data()
{
uint8_t result = 0;
if (m_address < 0x10)
if (m_address < 0x0e)
{
// 00-0F: Read from SSG
// 00-0D: Read from SSG
result = m_ssg.read(m_address & 0x0f);
}
else if (m_address < 0x10)
{
// 0E-0F: I/O ports not supported
result = 0xff;
}
else if (m_address == 0xff)
{
// FF: ID code
@ -1716,11 +2138,15 @@ void ym2610::write_data(uint8_t data)
if (bitfield(m_address, 8))
return;
if (m_address < 0x10)
if (m_address < 0x0e)
{
// 00-0F: write to SSG
// 00-0D: write to SSG
m_ssg.write(m_address & 0x0f, data);
}
else if (m_address < 0x10)
{
// 0E-0F: I/O ports not supported
}
else if (m_address < 0x1c)
{
// 10-1B: write to ADPCM-B

View File

@ -164,6 +164,9 @@ public:
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);
@ -618,6 +621,80 @@ protected:
};
// ======================> 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

View File

@ -58,11 +58,11 @@ opq_registers::opq_registers() :
m_lfo_am(0)
{
// create the waveforms
for (int index = 0; index < WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
uint16_t zeroval = m_waveform[0][0];
for (int index = 0; index < WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[1][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index];
}

View File

@ -99,14 +99,14 @@ opz_registers::opz_registers() :
m_lfo_am{ 0, 0 }
{
// create the waveforms
for (int index = 0; index < WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15);
uint16_t zeroval = m_waveform[0][0];
for (int index = 0; index < WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
m_waveform[1][index] = (zeroval - m_waveform[0][(index & 0x1ff) ^ 0x100]) | (bitfield(index, 9) << 15);
for (int index = 0; index < WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++)
{
m_waveform[2][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index];
m_waveform[3][index] = bitfield(index, 9) ? zeroval : m_waveform[1][index];
@ -118,7 +118,7 @@ opz_registers::opz_registers() :
// create the LFO waveforms; AM in the low 8 bits, PM in the upper 8
// waveforms are adjusted to match the pictures in the application manual
for (int index = 0; index < LFO_WAVEFORM_LENGTH; index++)
for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++)
{
// waveform 0 is a sawtooth
uint8_t am = index ^ 0xff;
@ -701,7 +701,7 @@ void ym2414::write_data(uint8_t data)
if (m_address == 0x1b)
{
// writes to register 0x1B send the upper 2 bits to the output lines
m_fm.intf().ymfm_io_write(0, data >> 6);
m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6);
}
// mark busy for a bit

712
3rdparty/ymfm/src/ymfm_pcm.cpp vendored Normal file
View File

@ -0,0 +1,712 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "ymfm_pcm.h"
#include "ymfm_fm.h"
#include "ymfm_fm.ipp"
namespace ymfm
{
//*********************************************************
// PCM REGISTERS
//*********************************************************
//-------------------------------------------------
// reset - reset the register state
//-------------------------------------------------
void pcm_registers::reset()
{
std::fill_n(&m_regdata[0], REGISTERS, 0);
m_regdata[0x02] = 0x20;
m_regdata[0xf8] = 0x1b;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void pcm_registers::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_regdata);
}
//-------------------------------------------------
// cache_channel_data - update the cache with
// data from the registers
//-------------------------------------------------
void pcm_registers::cache_channel_data(uint32_t choffs, pcm_cache &cache)
{
// compute step from octave and fnumber; the math here implies
// a .18 fraction but .16 should be perfectly fine
int32_t octave = int8_t(ch_octave(choffs) << 4) >> 4;
uint32_t fnum = ch_fnumber(choffs);
cache.step = ((0x400 | fnum) << (octave + 7)) >> 2;
// total level is computed as a .10 value for interpolation
cache.total_level = ch_total_level(choffs) << 10;
// compute panning values in terms of envelope attenuation
int32_t panpot = int8_t(ch_panpot(choffs) << 4) >> 4;
if (panpot >= 0)
{
cache.pan_left = (panpot == 7) ? 96 : 3 * panpot;
cache.pan_right = 0;
}
else if (panpot >= -7)
{
cache.pan_left = 0;
cache.pan_right = (panpot == -7) ? 96 : -3 * panpot;
}
else
cache.pan_left = cache.pan_right = 96;
// determine the LFO stepping value; this how much to add to a running
// x.18 value for the LFO; steps were derived from frequencies in the
// manual and come out very close with these values
static const uint8_t s_lfo_steps[8] = { 1, 12, 19, 25, 31, 35, 37, 42 };
cache.lfo_step = s_lfo_steps[ch_lfo_speed(choffs)];
// AM LFO depth values, derived from the manual; note each has at most
// 2 bits to make the "multiply" easy in hardware
static const uint8_t s_am_depth[8] = { 0, 0x14, 0x20, 0x28, 0x30, 0x40, 0x50, 0x80 };
cache.am_depth = s_am_depth[ch_am_depth(choffs)];
// PM LFO depth values; these are converted from the manual's cents values
// into f-numbers; the computations come out quite cleanly so pretty sure
// these are correct
static const uint8_t s_pm_depth[8] = { 0, 2, 3, 4, 6, 12, 24, 48 };
cache.pm_depth = s_pm_depth[ch_vibrato(choffs)];
// 4-bit sustain level, but 15 means 31 so effectively 5 bits
cache.eg_sustain = ch_sustain_level(choffs);
cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
cache.eg_sustain <<= 5;
// compute the key scaling correction factor; 15 means don't do any correction
int32_t correction = ch_rate_correction(choffs);
if (correction == 15)
correction = 0;
else
correction = (octave + correction) * 2 + bitfield(fnum, 9);
// compute the envelope generator rates
cache.eg_rate[EG_ATTACK] = effective_rate(ch_attack_rate(choffs), correction);
cache.eg_rate[EG_DECAY] = effective_rate(ch_decay_rate(choffs), correction);
cache.eg_rate[EG_SUSTAIN] = effective_rate(ch_sustain_rate(choffs), correction);
cache.eg_rate[EG_RELEASE] = effective_rate(ch_release_rate(choffs), correction);
cache.eg_rate[EG_REVERB] = 5;
// if damping is on, override some things; essentially decay at a hardcoded
// rate of 48 until -12db (0x80), then at maximum rate for the rest
if (ch_damp(choffs) != 0)
{
cache.eg_rate[EG_DECAY] = 48;
cache.eg_rate[EG_SUSTAIN] = 63;
cache.eg_rate[EG_RELEASE] = 63;
cache.eg_sustain = 0x80;
}
}
//-------------------------------------------------
// effective_rate - return the effective rate,
// clamping and applying corrections as needed
//-------------------------------------------------
uint32_t pcm_registers::effective_rate(uint32_t raw, uint32_t correction)
{
// raw rates of 0 and 15 just pin to min/max
if (raw == 0)
return 0;
if (raw == 15)
return 63;
// otherwise add the correction and clamp to range
return clamp(raw * 4 + correction, 0, 63);
}
//*********************************************************
// PCM CHANNEL
//*********************************************************
//-------------------------------------------------
// pcm_channel - constructor
//-------------------------------------------------
pcm_channel::pcm_channel(pcm_engine &owner, uint32_t choffs) :
m_choffs(choffs),
m_baseaddr(0),
m_endpos(0),
m_looppos(0),
m_curpos(0),
m_nextpos(0),
m_lfo_counter(0),
m_eg_state(EG_RELEASE),
m_env_attenuation(0x3ff),
m_total_level(0x7f << 10),
m_format(0),
m_key_state(0),
m_regs(owner.regs()),
m_owner(owner)
{
}
//-------------------------------------------------
// reset - reset the channel state
//-------------------------------------------------
void pcm_channel::reset()
{
m_baseaddr = 0;
m_endpos = 0;
m_looppos = 0;
m_curpos = 0;
m_nextpos = 0;
m_lfo_counter = 0;
m_eg_state = EG_RELEASE;
m_env_attenuation = 0x3ff;
m_total_level = 0x7f << 10;
m_format = 0;
m_key_state = 0;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void pcm_channel::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_baseaddr);
state.save_restore(m_endpos);
state.save_restore(m_looppos);
state.save_restore(m_curpos);
state.save_restore(m_nextpos);
state.save_restore(m_lfo_counter);
state.save_restore(m_eg_state);
state.save_restore(m_env_attenuation);
state.save_restore(m_total_level);
state.save_restore(m_format);
state.save_restore(m_key_state);
}
//-------------------------------------------------
// prepare - prepare for clocking
//-------------------------------------------------
bool pcm_channel::prepare()
{
// cache the data
m_regs.cache_channel_data(m_choffs, m_cache);
// clock the key state
if ((m_key_state & KEY_PENDING) != 0)
{
uint8_t oldstate = m_key_state;
m_key_state = (m_key_state >> 1) & KEY_ON;
if (((oldstate ^ m_key_state) & KEY_ON) != 0)
{
if ((m_key_state & KEY_ON) != 0)
start_attack();
else
start_release();
}
}
// set the total level directly if not interpolating
if (m_regs.ch_level_direct(m_choffs))
m_total_level = m_cache.total_level;
// we're active until we're quiet after the release
return (m_eg_state < EG_RELEASE || m_env_attenuation < EG_QUIET);
}
//-------------------------------------------------
// clock - master clocking function
//-------------------------------------------------
void pcm_channel::clock(uint32_t env_counter)
{
// clock the LFO, which is an x.18 value incremented based on the
// LFO speed value
m_lfo_counter += m_cache.lfo_step;
// clock the envelope
clock_envelope(env_counter);
// determine the step after applying vibrato
uint32_t step = m_cache.step;
if (m_cache.pm_depth != 0)
{
// shift the LFO by 1/4 cycle for PM so that it starts at 0
uint32_t lfo_shifted = m_lfo_counter + (1 << 16);
int32_t lfo_value = bitfield(lfo_shifted, 10, 7);
if (bitfield(lfo_shifted, 17) != 0)
lfo_value ^= 0x7f;
lfo_value -= 0x40;
step += (lfo_value * int32_t(m_cache.pm_depth)) >> 7;
}
// advance the sample step and loop as needed
m_curpos = m_nextpos;
m_nextpos = m_curpos + step;
if (m_nextpos >= m_endpos)
m_nextpos += m_looppos - m_endpos;
// interpolate total level if needed
if (m_total_level != m_cache.total_level)
{
// max->min volume takes 156.4ms, or pretty close to 19/1024 per 44.1kHz sample
// min->max volume is half that, so advance by 38/1024 per sample
if (m_total_level < m_cache.total_level)
m_total_level = std::min<int32_t>(m_total_level + 19, m_cache.total_level);
else
m_total_level = std::max<int32_t>(m_total_level - 38, m_cache.total_level);
}
}
//-------------------------------------------------
// output - return the computed output value, with
// panning applied
//-------------------------------------------------
void pcm_channel::output(output_data &output) const
{
// early out if the envelope is effectively off
uint32_t envelope = m_env_attenuation;
if (envelope > EG_QUIET)
return;
// add in LFO AM modulation
if (m_cache.am_depth != 0)
{
uint32_t lfo_value = bitfield(m_lfo_counter, 10, 7);
if (bitfield(m_lfo_counter, 17) != 0)
lfo_value ^= 0x7f;
envelope += (lfo_value * m_cache.am_depth) >> 7;
}
// add in the current interpolated total level value, which is a .10
// value shifted left by 2
envelope += m_total_level >> 8;
// add in panning effect and clamp
uint32_t lenv = std::min<uint32_t>(envelope + m_cache.pan_left, 0x3ff);
uint32_t renv = std::min<uint32_t>(envelope + m_cache.pan_right, 0x3ff);
// convert to volume as a .11 fraction
int32_t lvol = attenuation_to_volume(lenv << 2);
int32_t rvol = attenuation_to_volume(renv << 2);
// fetch current sample and add
int16_t sample = fetch_sample();
uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2;
output.data[outnum + 0] += (lvol * sample) >> 15;
output.data[outnum + 1] += (rvol * sample) >> 15;
}
//-------------------------------------------------
// keyonoff - signal key on/off
//-------------------------------------------------
void pcm_channel::keyonoff(bool on)
{
// mark the key state as pending
m_key_state |= KEY_PENDING | (on ? KEY_PENDING_ON : 0);
// don't log masked channels
if ((m_key_state & (KEY_PENDING_ON | KEY_ON)) == KEY_PENDING_ON && ((debug::GLOBAL_PCM_CHANNEL_MASK >> m_choffs) & 1) != 0)
{
debug::log_keyon("KeyOn PCM-%02d: num=%3d oct=%2d fnum=%03X level=%02X%c ADSR=%X/%X/%X/%X SL=%X",
m_choffs,
m_regs.ch_wave_table_num(m_choffs),
int8_t(m_regs.ch_octave(m_choffs) << 4) >> 4,
m_regs.ch_fnumber(m_choffs),
m_regs.ch_total_level(m_choffs),
m_regs.ch_level_direct(m_choffs) ? '!' : '/',
m_regs.ch_attack_rate(m_choffs),
m_regs.ch_decay_rate(m_choffs),
m_regs.ch_sustain_rate(m_choffs),
m_regs.ch_release_rate(m_choffs),
m_regs.ch_sustain_level(m_choffs));
if (m_regs.ch_rate_correction(m_choffs) != 15)
debug::log_keyon(" RC=%X", m_regs.ch_rate_correction(m_choffs));
if (m_regs.ch_pseudo_reverb(m_choffs) != 0)
debug::log_keyon(" %s", "REV");
if (m_regs.ch_damp(m_choffs) != 0)
debug::log_keyon(" %s", "DAMP");
if (m_regs.ch_vibrato(m_choffs) != 0 || m_regs.ch_am_depth(m_choffs) != 0)
{
if (m_regs.ch_vibrato(m_choffs) != 0)
debug::log_keyon(" VIB=%d", m_regs.ch_vibrato(m_choffs));
if (m_regs.ch_am_depth(m_choffs) != 0)
debug::log_keyon(" AM=%d", m_regs.ch_am_depth(m_choffs));
debug::log_keyon(" LFO=%d", m_regs.ch_lfo_speed(m_choffs));
}
debug::log_keyon("%s", "\n");
}
}
//-------------------------------------------------
// load_wavetable - load a wavetable by fetching
// its data from external memory
//-------------------------------------------------
void pcm_channel::load_wavetable()
{
// determine the address of the wave table header
uint32_t wavnum = m_regs.ch_wave_table_num(m_choffs);
uint32_t wavheader = 12 * wavnum;
// above 384 it may be in a different bank
if (wavnum >= 384)
{
uint32_t bank = m_regs.wave_table_header();
if (bank != 0)
wavheader = 512*1024 * bank + (wavnum - 384) * 12;
}
// fetch the 22-bit base address and 2-bit format
uint8_t byte = read_pcm(wavheader + 0);
m_format = bitfield(byte, 6, 2);
m_baseaddr = bitfield(byte, 0, 6) << 16;
m_baseaddr |= read_pcm(wavheader + 1) << 8;
m_baseaddr |= read_pcm(wavheader + 2) << 0;
// fetch the 16-bit loop position
m_looppos = read_pcm(wavheader + 3) << 8;
m_looppos |= read_pcm(wavheader + 4);
m_looppos <<= 16;
// fetch the 16-bit end position, which is stored as a negative value
// for some reason that is unclear
m_endpos = read_pcm(wavheader + 5) << 8;
m_endpos |= read_pcm(wavheader + 6);
m_endpos = -m_endpos << 16;
// remaining data values set registers
m_owner.write(0x80 + m_choffs, read_pcm(wavheader + 7));
m_owner.write(0x98 + m_choffs, read_pcm(wavheader + 8));
m_owner.write(0xb0 + m_choffs, read_pcm(wavheader + 9));
m_owner.write(0xc8 + m_choffs, read_pcm(wavheader + 10));
m_owner.write(0xe0 + m_choffs, read_pcm(wavheader + 11));
}
//-------------------------------------------------
// read_pcm - read a byte from the external PCM
// memory interface
//-------------------------------------------------
uint8_t pcm_channel::read_pcm(uint32_t address) const
{
return m_owner.intf().ymfm_external_read(ACCESS_PCM, address);
}
//-------------------------------------------------
// start_attack - start the attack phase
//-------------------------------------------------
void pcm_channel::start_attack()
{
// don't change anything if already in attack state
if (m_eg_state == EG_ATTACK)
return;
m_eg_state = EG_ATTACK;
// reset the LFO if requested
if (m_regs.ch_lfo_reset(m_choffs))
m_lfo_counter = 0;
// if the attack rate == 63 then immediately go to max attenuation
if (m_cache.eg_rate[EG_ATTACK] == 63)
m_env_attenuation = 0;
// reset the positions
m_curpos = m_nextpos = 0;
}
//-------------------------------------------------
// start_release - start the release phase
//-------------------------------------------------
void pcm_channel::start_release()
{
// don't change anything if already in release or reverb state
if (m_eg_state >= EG_RELEASE)
return;
m_eg_state = EG_RELEASE;
}
//-------------------------------------------------
// clock_envelope - clock the envelope generator
//-------------------------------------------------
void pcm_channel::clock_envelope(uint32_t env_counter)
{
// handle attack->decay transitions
if (m_eg_state == EG_ATTACK && m_env_attenuation == 0)
m_eg_state = EG_DECAY;
// handle decay->sustain transitions
if (m_eg_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain)
m_eg_state = EG_SUSTAIN;
// fetch the appropriate 6-bit rate value from the cache
uint32_t rate = m_cache.eg_rate[m_eg_state];
// compute the rate shift value; this is the shift needed to
// apply to the env_counter such that it becomes a 5.11 fixed
// point number
uint32_t rate_shift = rate >> 2;
env_counter <<= rate_shift;
// see if the fractional part is 0; if not, it's not time to clock
if (bitfield(env_counter, 0, 11) != 0)
return;
// determine the increment based on the non-fractional part of env_counter
uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3);
uint32_t increment = attenuation_increment(rate, relevant_bits);
// attack is the only one that increases
if (m_eg_state == EG_ATTACK)
m_env_attenuation += (~m_env_attenuation * increment) >> 4;
// all other cases are similar
else
{
// apply the increment
m_env_attenuation += increment;
// clamp the final attenuation
if (m_env_attenuation >= 0x400)
m_env_attenuation = 0x3ff;
// transition to reverb at -18dB if enabled
if (m_env_attenuation >= 0xc0 && m_eg_state < EG_REVERB && m_regs.ch_pseudo_reverb(m_choffs))
m_eg_state = EG_REVERB;
}
}
//-------------------------------------------------
// fetch_sample - fetch a sample at the current
// position
//-------------------------------------------------
int16_t pcm_channel::fetch_sample() const
{
uint32_t addr = m_baseaddr;
uint32_t pos = m_curpos >> 16;
// 8-bit PCM: shift up by 8
if (m_format == 0)
return read_pcm(addr + pos) << 8;
// 16-bit PCM: assemble from 2 halves
if (m_format == 2)
{
addr += pos * 2;
return (read_pcm(addr) << 8) | read_pcm(addr + 1);
}
// 12-bit PCM: assemble out of half of 3 bytes
addr += (pos / 2) * 3;
if ((pos & 1) == 0)
return (read_pcm(addr + 0) << 8) | ((read_pcm(addr + 1) << 0) & 0xf0);
else
return (read_pcm(addr + 2) << 8) | ((read_pcm(addr + 1) << 4) & 0xf0);
}
//*********************************************************
// PCM ENGINE
//*********************************************************
//-------------------------------------------------
// pcm_engine - constructor
//-------------------------------------------------
pcm_engine::pcm_engine(ymfm_interface &intf) :
m_intf(intf),
m_env_counter(0),
m_modified_channels(ALL_CHANNELS),
m_active_channels(ALL_CHANNELS)
{
// create the channels
for (int chnum = 0; chnum < CHANNELS; chnum++)
m_channel[chnum] = std::make_unique<pcm_channel>(*this, chnum);
}
//-------------------------------------------------
// reset - reset the engine state
//-------------------------------------------------
void pcm_engine::reset()
{
// reset register state
m_regs.reset();
// reset each channel
for (auto &chan : m_channel)
chan->reset();
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void pcm_engine::save_restore(ymfm_saved_state &state)
{
// save our data
state.save_restore(m_env_counter);
// save channel state
for (int chnum = 0; chnum < CHANNELS; chnum++)
m_channel[chnum]->save_restore(state);
}
//-------------------------------------------------
// clock - master clocking function
//-------------------------------------------------
void pcm_engine::clock(uint32_t chanmask)
{
// if something was modified, prepare
// also prepare every 4k samples to catch ending notes
if (m_modified_channels != 0 || m_prepare_count++ >= 4096)
{
// call each channel to prepare
m_active_channels = 0;
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
if (m_channel[chnum]->prepare())
m_active_channels |= 1 << chnum;
// reset the modified channels and prepare count
m_modified_channels = m_prepare_count = 0;
}
// increment the envelope counter; the envelope generator
// only clocks every other sample in order to make the PCM
// envelopes line up with the FM envelopes (after taking into
// account the different FM sampling rate)
m_env_counter++;
// now update the state of all the channels and operators
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
m_channel[chnum]->clock(m_env_counter >> 1);
}
//-------------------------------------------------
// update - master update function
//-------------------------------------------------
void pcm_engine::output(output_data &output, uint32_t chanmask)
{
// mask out some channels for debug purposes
chanmask &= debug::GLOBAL_PCM_CHANNEL_MASK;
// compute the output of each channel
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
m_channel[chnum]->output(output);
}
//-------------------------------------------------
// read - handle reads from the PCM registers
//-------------------------------------------------
uint8_t pcm_engine::read(uint32_t regnum)
{
// handle reads from the data register
if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
return m_intf.ymfm_external_read(ACCESS_PCM, m_regs.memory_address_autoinc());
return m_regs.read(regnum);
}
//-------------------------------------------------
// write - handle writes to the PCM registers
//-------------------------------------------------
void pcm_engine::write(uint32_t regnum, uint8_t data)
{
// handle reads to the data register
if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
{
m_intf.ymfm_external_write(ACCESS_PCM, m_regs.memory_address_autoinc(), data);
return;
}
// for now just mark all channels as modified
m_modified_channels = ALL_CHANNELS;
// most writes are passive, consumed only when needed
m_regs.write(regnum, data);
// however, process keyons immediately
if (regnum >= 0x68 && regnum <= 0x7f)
m_channel[regnum - 0x68]->keyonoff(bitfield(data, 7));
// and also wavetable writes
else if (regnum >= 0x08 && regnum <= 0x1f)
m_channel[regnum - 0x08]->load_wavetable();
}
}

307
3rdparty/ymfm/src/ymfm_pcm.h vendored Normal file
View File

@ -0,0 +1,307 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_PCM_H
#define YMFM_PCM_H
#pragma once
#include "ymfm.h"
namespace ymfm
{
//*********************************************************
// INTERFACE CLASSES
//*********************************************************
class pcm_engine;
// ======================> pcm_cache
// this class holds data that is computed once at the start of clocking
// and remains static during subsequent sound generation
struct pcm_cache
{
uint32_t step; // sample position step, as a .16 value
uint32_t total_level; // target total level, as a .10 value
uint32_t pan_left; // left panning attenuation
uint32_t pan_right; // right panning attenuation
uint32_t eg_sustain; // sustain level, shifted up to envelope values
uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR
uint8_t lfo_step; // stepping value for LFO
uint8_t am_depth; // scale value for AM LFO
uint8_t pm_depth; // scale value for PM LFO
};
// ======================> pcm_registers
//
// PCM register map:
//
// System-wide registers:
// 00-01 xxxxxxxx LSI Test
// 02 -------x Memory access mode (0=sound gen, 1=read/write)
// ------x- Memory type (0=ROM, 1=ROM+SRAM)
// ---xxx-- Wave table header
// xxx----- Device ID (=1 for YMF278B)
// 03 --xxxxxx Memory address high
// 04 xxxxxxxx Memory address mid
// 05 xxxxxxxx Memory address low
// 06 xxxxxxxx Memory data
// F8 --xxx--- Mix control (FM_R)
// -----xxx Mix control (FM_L)
// F9 --xxx--- Mix control (PCM_R)
// -----xxx Mix control (PCM_L)
//
// Channel-specific registers:
// 08-1F xxxxxxxx Wave table number low
// 20-37 -------x Wave table number high
// xxxxxxx- F-number low
// 38-4F -----xxx F-number high
// ----x--- Pseudo-reverb
// xxxx---- Octave
// 50-67 xxxxxxx- Total level
// -------x Level direct
// 68-7F x------- Key on
// -x------ Damp
// --x----- LFO reset
// ---x---- Output channel
// ----xxxx Panpot
// 80-97 --xxx--- LFO speed
// -----xxx Vibrato
// 98-AF xxxx---- Attack rate
// ----xxxx Decay rate
// B0-C7 xxxx---- Sustain level
// ----xxxx Sustain rate
// C8-DF xxxx---- Rate correction
// ----xxxx Release rate
// E0-F7 -----xxx AM depth
class pcm_registers
{
public:
// constants
static constexpr uint32_t OUTPUTS = 4;
static constexpr uint32_t CHANNELS = 24;
static constexpr uint32_t REGISTERS = 0x100;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
// constructor
pcm_registers() { }
// save/restore
void save_restore(ymfm_saved_state &state);
// reset to initial state
void reset();
// update cache information
void cache_channel_data(uint32_t choffs, pcm_cache &cache);
// direct read/write access
uint8_t read(uint32_t index ) { return m_regdata[index]; }
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
// system-wide registers
uint32_t memory_access_mode() const { return bitfield(m_regdata[0x02], 0); }
uint32_t memory_type() const { return bitfield(m_regdata[0x02], 1); }
uint32_t wave_table_header() const { return bitfield(m_regdata[0x02], 2, 3); }
uint32_t device_id() const { return bitfield(m_regdata[0x02], 5, 3); }
uint32_t memory_address() const { return (bitfield(m_regdata[0x03], 0, 6) << 16) | (m_regdata[0x04] << 8) | m_regdata[0x05]; }
uint32_t memory_data() const { return m_regdata[0x06]; }
uint32_t mix_fm_r() const { return bitfield(m_regdata[0xf8], 3, 3); }
uint32_t mix_fm_l() const { return bitfield(m_regdata[0xf8], 0, 3); }
uint32_t mix_pcm_r() const { return bitfield(m_regdata[0xf9], 3, 3); }
uint32_t mix_pcm_l() const { return bitfield(m_regdata[0xf9], 0, 3); }
// per-channel registers
uint32_t ch_wave_table_num(uint32_t choffs) const { return m_regdata[choffs + 0x08] | (bitfield(m_regdata[choffs + 0x20], 0) << 8); }
uint32_t ch_fnumber(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x20], 1, 7) | (bitfield(m_regdata[choffs + 0x38], 0, 3) << 7); }
uint32_t ch_pseudo_reverb(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 3); }
uint32_t ch_octave(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 4, 4); }
uint32_t ch_total_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 1, 7); }
uint32_t ch_level_direct(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 0); }
uint32_t ch_keyon(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 7); }
uint32_t ch_damp(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 6); }
uint32_t ch_lfo_reset(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 5); }
uint32_t ch_output_channel(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 4); }
uint32_t ch_panpot(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 0, 4); }
uint32_t ch_lfo_speed(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 3, 3); }
uint32_t ch_vibrato(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 0, 3); }
uint32_t ch_attack_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 4, 4); }
uint32_t ch_decay_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 0, 4); }
uint32_t ch_sustain_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 4, 4); }
uint32_t ch_sustain_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 0, 4); }
uint32_t ch_rate_correction(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 4, 4); }
uint32_t ch_release_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 0, 4); }
uint32_t ch_am_depth(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xe0], 0, 3); }
// return the memory address and increment it
uint32_t memory_address_autoinc()
{
uint32_t result = memory_address();
uint32_t newval = result + 1;
m_regdata[0x05] = newval >> 0;
m_regdata[0x06] = newval >> 8;
m_regdata[0x07] = (newval >> 16) & 0x3f;
return result;
}
private:
// internal helpers
uint32_t effective_rate(uint32_t raw, uint32_t correction);
// internal state
uint8_t m_regdata[REGISTERS]; // register data
};
// ======================> pcm_channel
class pcm_channel
{
static constexpr uint8_t KEY_ON = 0x01;
static constexpr uint8_t KEY_PENDING_ON = 0x02;
static constexpr uint8_t KEY_PENDING = 0x04;
// "quiet" value, used to optimize when we can skip doing working
static constexpr uint32_t EG_QUIET = 0x200;
public:
using output_data = ymfm_output<pcm_registers::OUTPUTS>;
// constructor
pcm_channel(pcm_engine &owner, uint32_t choffs);
// save/restore
void save_restore(ymfm_saved_state &state);
// reset the channel state
void reset();
// return the channel offset
uint32_t choffs() const { return m_choffs; }
// prepare prior to clocking
bool prepare();
// master clocking function
void clock(uint32_t env_counter);
// return the computed output value, with panning applied
void output(output_data &output) const;
// signal key on/off
void keyonoff(bool on);
// load a new wavetable entry
void load_wavetable();
private:
// internal helpers
void start_attack();
void start_release();
void clock_envelope(uint32_t env_counter);
int16_t fetch_sample() const;
uint8_t read_pcm(uint32_t address) const;
// internal state
uint32_t const m_choffs; // channel offset
uint32_t m_baseaddr; // base address
uint32_t m_endpos; // ending position
uint32_t m_looppos; // loop position
uint32_t m_curpos; // current position
uint32_t m_nextpos; // next position
uint32_t m_lfo_counter; // LFO counter
envelope_state m_eg_state; // envelope state
uint16_t m_env_attenuation; // computed envelope attenuation
uint32_t m_total_level; // total level with as 7.10 for interp
uint8_t m_format; // sample format
uint8_t m_key_state; // current key state
pcm_cache m_cache; // cached data
pcm_registers &m_regs; // reference to registers
pcm_engine &m_owner; // reference to our owner
};
// ======================> pcm_engine
class pcm_engine
{
public:
static constexpr int OUTPUTS = pcm_registers::OUTPUTS;
static constexpr int CHANNELS = pcm_registers::CHANNELS;
static constexpr uint32_t ALL_CHANNELS = pcm_registers::ALL_CHANNELS;
using output_data = pcm_channel::output_data;
// constructor
pcm_engine(ymfm_interface &intf);
// reset our status
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// master clocking function
void clock(uint32_t chanmask);
// compute sum of channel outputs
void output(output_data &output, uint32_t chanmask);
// read from the PCM registers
uint8_t read(uint32_t regnum);
// write to the PCM registers
void write(uint32_t regnum, uint8_t data);
// return a reference to our interface
ymfm_interface &intf() { return m_intf; }
// return a reference to our registers
pcm_registers &regs() { return m_regs; }
private:
// internal state
ymfm_interface &m_intf; // reference to the interface
uint32_t m_env_counter; // envelope counter
uint32_t m_modified_channels; // bitmask of modified channels
uint32_t m_active_channels; // bitmask of active channels
uint32_t m_prepare_count; // counter to do periodic prepare sweeps
std::unique_ptr<pcm_channel> m_channel[CHANNELS]; // array of channels
pcm_registers m_regs; // registers
};
}
#endif // YMFM_PCM_H

View File

@ -144,9 +144,11 @@ void ssg_engine::clock()
}
// clock noise; noise period units are clock/16 but since we run at clock/8,
// our counter needs a right shift prior to compare
// 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())
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;
@ -240,9 +242,9 @@ uint8_t ssg_engine::read(uint32_t 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_io_read(0);
return m_intf.ymfm_external_read(ACCESS_IO, 0);
else if (regnum == 0x0f && !m_regs.io_b_out())
return m_intf.ymfm_io_read(1);
return m_intf.ymfm_external_read(ACCESS_IO, 1);
// otherwise just return the register value
return m_regs.read(regnum);
@ -269,9 +271,9 @@ void ssg_engine::write(uint32_t regnum, uint8_t data)
// 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_io_write(0, data);
m_intf.ymfm_external_write(ACCESS_IO, 0, data);
else if (regnum == 0x0f && m_regs.io_b_out())
m_intf.ymfm_io_write(1, data);
m_intf.ymfm_external_write(ACCESS_IO, 1, data);
}
}

View File

@ -2283,6 +2283,8 @@ project "ymfm"
MAME_DIR .. "3rdparty/ymfm/src/ymfm_opq.h",
MAME_DIR .. "3rdparty/ymfm/src/ymfm_opz.cpp",
MAME_DIR .. "3rdparty/ymfm/src/ymfm_opz.h",
MAME_DIR .. "3rdparty/ymfm/src/ymfm_pcm.cpp",
MAME_DIR .. "3rdparty/ymfm/src/ymfm_pcm.h",
MAME_DIR .. "3rdparty/ymfm/src/ymfm_ssg.cpp",
MAME_DIR .. "3rdparty/ymfm/src/ymfm_ssg.h",
}

View File

@ -1180,8 +1180,8 @@ end
--@src/devices/sound/ymopl.h,SOUNDS["YM3526"] = true
--@src/devices/sound/ymopl.h,SOUNDS["YM3812"] = true
--@src/devices/sound/ymopl.h,SOUNDS["YMF262"] = true
--@src/devices/sound/ymopl.h,SOUNDS["YMF278B"] = true
--@src/devices/sound/ymf271.h,SOUNDS["YMF271"] = true
--@src/devices/sound/ymf278b.h,SOUNDS["YMF278B"] = true
--@src/devices/sound/ymopl.h,SOUNDS["Y8950"] = true
---------------------------------------------------
@ -1215,7 +1215,7 @@ if (SOUNDS["YM2203"]~=null or SOUNDS["YM2608"]~=null or SOUNDS["YM2610"]~=null o
}
end
if (SOUNDS["YM3526"]~=null or SOUNDS["Y8950"]~=null or SOUNDS["YM3812"]~=null or SOUNDS["YMF262"]~=null or SOUNDS["YM2413"]~=null or SOUNDS["YM2423"]~=null or SOUNDS["YMF281"]~=null or SOUNDS["DS1001"]~=null) then
if (SOUNDS["YM3526"]~=null or SOUNDS["Y8950"]~=null or SOUNDS["YM3812"]~=null or SOUNDS["YMF262"]~=null or SOUNDS["YMF278B"]~=null or SOUNDS["YM2413"]~=null or SOUNDS["YM2423"]~=null or SOUNDS["YMF281"]~=null or SOUNDS["DS1001"]~=null) then
files {
MAME_DIR .. "src/devices/sound/ymopl.cpp",
MAME_DIR .. "src/devices/sound/ymopl.h",
@ -1229,13 +1229,6 @@ if (SOUNDS["YMF271"]~=null) then
}
end
if (SOUNDS["YMF278B"]~=null) then
files {
MAME_DIR .. "src/devices/sound/ymf278b.cpp",
MAME_DIR .. "src/devices/sound/ymf278b.h",
}
end
---------------------------------------------------

View File

@ -6,7 +6,7 @@
#pragma once
#include "bus/msx_cart/cartridge.h"
#include "sound/ymf278b.h"
#include "sound/ymopl.h"
DECLARE_DEVICE_TYPE(MSX_CART_MOONSOUND, msx_cart_moonsound_device)

View File

@ -1,983 +0,0 @@
// license:BSD-3-Clause
// copyright-holders:R. Belmont, Olivier Galibert, hap
/*
YMF278B FM + Wave table Synthesizer (OPL4)
Timer and PCM YMF278B. The FM will be shared with the ymf262, eventually.
This chip roughly splits the difference between the Sega 315-5560 MultiPCM
(Multi32, Model 1/2) and YMF 292-F SCSP (later Model 2, STV, Saturn, Model 3).
Features as listed in LSI-4MF2782 data sheet:
FM Synthesis (same as YMF262)
1. Sound generation mode
Two-operater mode
Generates eighteen voices or fifteen voices plus five rhythm sounds simultaneously
Four-operator mode
Generates six voices in four-operator mode plus six voices in two-operator mode simultaneously,
or generates six voices in four-operator mode plus three voices in two-operator mode plus five
rhythm sounds simultaneously
2. Eight selectable waveforms
3. Stereo output
Wave Table Synthesis
1. Generates twenty-four voices simultaneously
2. 44.1kHz sampling rate for output sound data
3. Selectable from 8-bit, 12-bit and 16-bit word lengths for wave data
4. Stereo output (16-stage panpot for each voice)
Wave Data
1. Accepts 32M bit external memory at maximum
2. Up to 512 wave tables
3. External ROM or SRAM can be connected. With SRAM connected, the CPU can download wave data
4. Outputs chip select signals for 1Mbit, 4Mbit, 8Mbit or 16Mbit memory
5. Can be directly connected to the Yamaha YRW801 (Wave data ROM)
Features of YRW801 as listed in LSI 4RW801A2
Built-in wave data of tones which comply with GM system Level 1
Melody tone ....... 128 tones
Percussion tone ... 47 tones
16Mbit capacity (2,097,152word x 8)
By R. Belmont and O. Galibert.
TODO:
- accurate timing of envelopes
- LFO (vibrato, tremolo)
- integrate YMF262 mixing (used by Fuuki games, not used by Psikyo and Metro games)
- Envelope and LFO function is similar algorithm as multipcm.cpp (except Damp, Pseudo Reverb)
Can it be merged with/ported to this?
*/
#include "emu.h"
#include "ymf278b.h"
#include <algorithm>
#define VERBOSE 0
#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
// Using the nominal datasheet frequency of 33.868MHz, the output of
// the chip will be clock/768 = 44.1kHz. However, the FM engine is
// clocked internally at clock/(19*36), or 49.515kHz, so the FM output
// needs to be downsampled. The calculations below produce the fractional
// number of extra FM samples we need to consume for each output sample,
// as a 0.24 fixed point fraction.
static constexpr double NOMINAL_CLOCK = 33868800;
static constexpr double NOMINAL_FM_RATE = NOMINAL_CLOCK / double(ymfm::opl4_registers::DEFAULT_PRESCALE * ymfm::opl4_registers::OPERATORS);
static constexpr double NOMINAL_OUTPUT_RATE = NOMINAL_CLOCK / 768.0;
static constexpr uint32_t FM_STEP = uint32_t((NOMINAL_FM_RATE / NOMINAL_OUTPUT_RATE - 1.0) * double(1 << 24));
/**************************************************************************/
int ymf278b_device::compute_rate(YMF278BSlot *slot, int val)
{
int res, oct;
if(val == 0)
return 0;
if(val == 15)
return 63;
if(slot->RC != 15)
{
oct = slot->octave;
if (oct & 8)
oct |= -8;
res = (oct+slot->RC)*2 + (slot->F_NUMBER & 0x200 ? 1 : 0) + val*4;
}
else
res = val * 4;
if(res < 0)
res = 0;
else if(res > 63)
res = 63;
return res;
}
uint32_t ymf278b_device::compute_decay_env_vol_step(YMF278BSlot *slot, int val)
{
int rate;
uint32_t res;
// rate override with damping/pseudo reverb
if (slot->DAMP)
rate = 56; // approximate, datasheet says it's slightly curved though
else if (slot->preverb && slot->env_vol > ((6*8)<<23))
{
// pseudo reverb starts at -18dB (6 in voltab)
slot->env_preverb = 1;
rate = 5;
}
else
rate = compute_rate(slot, val);
if (rate < 4)
res = 0;
else
res = (256U<<23) / m_lut_dr[rate];
return res;
}
void ymf278b_device::compute_freq_step(YMF278BSlot *slot)
{
uint32_t step;
int oct;
oct = slot->octave;
if(oct & 8)
oct |= -8;
step = (slot->F_NUMBER | 1024) << (oct + 8);
slot->step = step >> 3;
}
void ymf278b_device::compute_envelope(YMF278BSlot *slot)
{
switch (slot->env_step)
{
// Attack
case 0:
{
// Attack
int rate = compute_rate(slot, slot->AR);
slot->env_vol = 256U<<23;
slot->env_vol_lim = (256U<<23) - 1;
if (rate==63)
{
// immediate
LOG(("YMF278B: Attack skipped - "));
slot->env_vol = 0;
slot->env_step++;
compute_envelope(slot);
}
else if (rate<4)
{
slot->env_vol_step = 0;
}
else
{
// NOTE: attack rate is linear here, but datasheet shows a smooth curve
LOG(("YMF278B: Attack, val = %d, rate = %d, delay = %g\n", slot->AR, rate, m_lut_ar[rate]*1000.0));
slot->env_vol_step = ~((256U<<23) / m_lut_ar[rate]);
}
break;
}
// Decay 1
case 1:
if(slot->DL)
{
LOG(("YMF278B: Decay step 1, dl=%d, val = %d rate = %d, delay = %g, PRVB = %d, DAMP = %d\n", slot->DL, slot->D1R, compute_rate(slot, slot->D1R), m_lut_dr[compute_rate(slot, slot->D1R)]*1000.0, slot->preverb, slot->DAMP));
slot->env_vol_step = compute_decay_env_vol_step(slot, slot->D1R);
slot->env_vol_lim = (slot->DL*8)<<23;
}
else
{
LOG(("YMF278B: Decay 1 skipped - "));
slot->env_step++;
compute_envelope(slot);
}
break;
// Decay 2
case 2:
LOG(("YMF278B: Decay step 2, val = %d, rate = %d, delay = %g, , PRVB = %d, DAMP = %d, current vol = %d\n", slot->D2R, compute_rate(slot, slot->D2R), m_lut_dr[compute_rate(slot, slot->D2R)]*1000.0, slot->preverb, slot->DAMP, slot->env_vol >> 23));
slot->env_vol_step = compute_decay_env_vol_step(slot, slot->D2R);
slot->env_vol_lim = 256U<<23;
break;
// Decay 2 reached -96dB
case 3:
LOG(("YMF278B: Voice cleared because of decay 2\n"));
slot->env_vol = 256U<<23;
slot->env_vol_step = 0;
slot->env_vol_lim = 0;
slot->active = 0;
break;
// Release
case 4:
LOG(("YMF278B: Release, val = %d, rate = %d, delay = %g, PRVB = %d, DAMP = %d\n", slot->RR, compute_rate(slot, slot->RR), m_lut_dr[compute_rate(slot, slot->RR)]*1000.0, slot->preverb, slot->DAMP));
slot->env_vol_step = compute_decay_env_vol_step(slot, slot->RR);
slot->env_vol_lim = 256U<<23;
break;
// Release reached -96dB
case 5:
LOG(("YMF278B: Release ends\n"));
slot->env_vol = 256U<<23;
slot->env_vol_step = 0;
slot->env_vol_lim = 0;
slot->active = 0;
break;
default: break;
}
}
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
void ymf278b_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
{
int i, j;
YMF278BSlot *slot;
int16_t sample = 0;
int32_t *mixp;
std::fill(m_mix_buffer.begin(), m_mix_buffer.end(), 0);
for (i = 0; i < 24; i++)
{
slot = &m_slots[i];
if (slot->active)
{
mixp = &m_mix_buffer[0];
for (j = 0; j < outputs[0].samples(); j++)
{
if (slot->stepptr >= slot->endaddr)
{
slot->stepptr = slot->stepptr - slot->endaddr + slot->loopaddr;
// NOTE: loop overflow is still possible here if (slot->stepptr >= slot->endaddr)
// This glitch may be (ab)used to your advantage to create pseudorandom noise.
}
switch (slot->bits)
{
// 8 bit
case 0:
sample = read_byte(slot->startaddr + (slot->stepptr>>16))<<8;
break;
// 12 bit
case 1:
if (slot->stepptr & 0x10000)
sample = read_byte(slot->startaddr + (slot->stepptr>>17)*3+2)<<8 |
(read_byte(slot->startaddr + (slot->stepptr>>17)*3+1) & 0xf0);
else
sample = read_byte(slot->startaddr + (slot->stepptr>>17)*3)<<8 |
((read_byte(slot->startaddr + (slot->stepptr>>17)*3+1) << 4) & 0xf0);
break;
// 16 bit
case 2:
sample = read_byte(slot->startaddr + ((slot->stepptr>>16)*2))<<8 |
read_byte(slot->startaddr + ((slot->stepptr>>16)*2)+1);
break;
// ?? bit, effect is unknown, datasheet says it's prohibited
case 3:
sample = 0;
break;
}
if (slot->CH) // DO1 out
{
mixp++;
mixp++;
*mixp++ += (sample * m_volume[slot->TL+m_pan_left [slot->pan]+(slot->env_vol>>23)])>>17;
*mixp++ += (sample * m_volume[slot->TL+m_pan_right[slot->pan]+(slot->env_vol>>23)])>>17;
}
else // DO2 out
{
*mixp++ += (sample * m_volume[slot->TL+m_pan_left [slot->pan]+(slot->env_vol>>23)])>>17;
*mixp++ += (sample * m_volume[slot->TL+m_pan_right[slot->pan]+(slot->env_vol>>23)])>>17;
mixp++;
mixp++;
}
// update frequency
slot->stepptr += slot->step;
// update envelope
slot->env_vol += slot->env_vol_step;
if (((int32_t)(slot->env_vol - slot->env_vol_lim)) >= 0)
{
slot->env_step++;
compute_envelope(slot);
}
else if (slot->preverb && !slot->env_preverb && slot->env_step && slot->env_vol > ((6*8)<<23))
compute_envelope(slot);
}
}
}
mixp = &m_mix_buffer[0];
stream_buffer::sample_t wtl = stream_buffer::sample_t(m_mix_level[m_pcm_l]) / (65536.0f * 32768.0f);
stream_buffer::sample_t wtr = stream_buffer::sample_t(m_mix_level[m_pcm_r]) / (65536.0f * 32768.0f);
stream_buffer::sample_t fml = stream_buffer::sample_t(m_mix_level[m_fm_l]) / (65536.0f * 32768.0f);
stream_buffer::sample_t fmr = stream_buffer::sample_t(m_mix_level[m_fm_r]) / (65536.0f * 32768.0f);
for (i = 0; i < outputs[0].samples(); i++)
{
// the FM_STEP value is the fractional number of extra samples consumed per
// output sample; when this overflows, we need to clock the FM engine an
// extra time; since the PCM side of the chip doesn't do interpolation, I'm
// assuming this resampling stage doesn't either
m_fm_pos += FM_STEP;
if (BIT(m_fm_pos, 24))
{
m_fm.clock(fm_engine::ALL_CHANNELS);
m_fm_pos &= 0xffffff;
}
// clock the system
m_fm.clock(fm_engine::ALL_CHANNELS);
// update the FM content; clipping is unknown
fm_engine::output_data sums;
m_fm.output(sums.clear(), 1, 32767, fm_engine::ALL_CHANNELS);
// DO2 output: mixed FM channels 0+1 and wavetable channels 0+1
outputs[0].put(i, stream_buffer::sample_t(*mixp++) * wtl + stream_buffer::sample_t(sums.data[0]) * fml);
outputs[1].put(i, stream_buffer::sample_t(*mixp++) * wtr + stream_buffer::sample_t(sums.data[1]) * fmr);
// DO0 output: FM channels 2+3 only
outputs[2].put_int(i, sums.data[2], 32768);
outputs[3].put_int(i, sums.data[3], 32768);
// DO1 output: wavetable channels 2+3 only
outputs[4].put_int(i, *mixp++, 32768);
outputs[5].put_int(i, *mixp++, 32768);
}
}
enum
{
TIMER_BUSY_CLEAR,
TIMER_LD_CLEAR
};
void ymf278b_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch(id)
{
case TIMER_BUSY_CLEAR:
m_fm.set_reset_status(0, STATUS_BUSY);
break;
case TIMER_LD_CLEAR:
m_fm.set_reset_status(0, STATUS_LD);
break;
}
}
/**************************************************************************/
void ymf278b_device::retrigger_sample(YMF278BSlot *slot)
{
// activate channel
if (slot->octave != 8)
slot->active = 1;
// reset sample pos and go to attack stage
slot->stepptr = 0;
slot->env_step = 0;
slot->env_preverb = 0;
compute_freq_step(slot);
compute_envelope(slot);
}
void ymf278b_device::C_w(uint8_t reg, uint8_t data)
{
// Handle slot registers specifically
if (reg >= 0x08 && reg <= 0xf7)
{
YMF278BSlot *slot;
int snum;
snum = (reg-8) % 24;
slot = &m_slots[snum];
switch((reg-8) / 24)
{
case 0:
{
attotime period;
uint32_t offset;
uint8_t p[12];
int i;
slot->wave &= 0x100;
slot->wave |= data;
// load wavetable header
if(slot->wave < 384 || !m_wavetblhdr)
offset = slot->wave * 12;
else
offset = m_wavetblhdr*0x80000 + (slot->wave - 384) * 12;
for (i = 0; i < 12; i++)
p[i] = read_byte(offset+i);
slot->bits = (p[0]&0xc0)>>6;
slot->startaddr = (p[2] | (p[1]<<8) | ((p[0]&0x3f)<<16));
slot->loopaddr = (p[4]<<16) | (p[3]<<24);
slot->endaddr = (p[6]<<16) | (p[5]<<24);
slot->endaddr -= 0x00010000U;
slot->endaddr ^= 0xffff0000U;
// copy internal registers data
for (i = 7; i < 12; i++)
C_w(8 + snum + (i-2) * 24, p[i]);
// status register LD bit is on for approx 300us
m_fm.set_reset_status(STATUS_LD, 0);
period = clocks_to_attotime(10);
m_timer_ld->adjust(period);
// retrigger if key is on
if (slot->KEY_ON)
retrigger_sample(slot);
else if (slot->active)
{
// deactivate channel
slot->env_step = 5;
compute_envelope(slot);
}
break;
}
case 1:
slot->wave &= 0xff;
slot->wave |= ((data&0x1)<<8);
slot->F_NUMBER &= 0x380;
slot->F_NUMBER |= (data>>1);
if (slot->active && (data ^ m_pcmregs[reg]) & 0xfe)
{
compute_freq_step(slot);
compute_envelope(slot);
}
break;
case 2:
slot->F_NUMBER &= 0x07f;
slot->F_NUMBER |= ((data&0x07)<<7);
slot->preverb = (data&0x8)>>3;
slot->octave = (data&0xf0)>>4;
if (data != m_pcmregs[reg])
{
// channel goes off if octave is set to -8 (datasheet says it's prohibited)
// (it is ok if this activates the channel while it was off: compute_envelope will reset it again if needed)
slot->active = (slot->octave != 8);
if (slot->active)
{
slot->env_preverb = 0;
compute_freq_step(slot);
compute_envelope(slot);
}
}
break;
case 3:
slot->TL = data>>1;
slot->LD = data&0x1;
break;
case 4:
slot->CH = (data&0x10)>>4;
// CH bit note: output to DO1 pin (1) or DO2 pin (0), this may
// silence the channel depending on how it's wired up on the PCB.
// For now, it's always enabled.
// (bit 5 (LFO reset) is also not hooked up yet)
slot->pan = data&0xf;
slot->DAMP = (data&0x40)>>6;
if (data & 0x80)
{
// don't retrigger if key was already on
if (slot->KEY_ON)
{
if ((data ^ m_pcmregs[reg]) & 0x40)
compute_envelope(slot);
break;
}
retrigger_sample(slot);
}
else if (slot->active)
{
// release
slot->env_step = 4;
compute_envelope(slot);
}
slot->KEY_ON = (data&0x80)>>7;
break;
case 5:
// LFO and vibrato level, not hooked up yet
slot->LFO = (data>>3)&0x7;
slot->VIB = data&0x7;
break;
case 6:
slot->AR = data>>4;
slot->D1R = data&0xf;
if (slot->active && data != m_pcmregs[reg])
compute_envelope(slot);
break;
case 7:
slot->DL = data>>4;
slot->D2R = data&0xf;
if (slot->active && data != m_pcmregs[reg])
compute_envelope(slot);
break;
case 8:
slot->RC = data>>4;
slot->RR = data&0xf;
if (slot->active && data != m_pcmregs[reg])
compute_envelope(slot);
break;
case 9:
// tremolo level, not hooked up yet
slot->AM = data & 0x7;
break;
}
}
else
{
// All non-slot registers
switch (reg)
{
// LSI TEST
case 0x00:
case 0x01:
break;
case 0x02:
m_wavetblhdr = (data>>2)&0x7;
m_memmode = data&3;
break;
case 0x03:
data &= 0x3f; // !
break;
case 0x04:
break;
case 0x05:
// set memory address
m_memadr = m_pcmregs[3] << 16 | m_pcmregs[4] << 8 | data;
break;
case 0x06:
// memory data
space(0).write_byte(m_memadr, data);
m_memadr = (m_memadr + 1) & 0x3fffff;
break;
case 0x07:
break; // unused
case 0xf8:
m_fm_l = data & 0x7;
m_fm_r = (data>>3)&0x7;
break;
case 0xf9:
m_pcm_l = data & 0x7;
m_pcm_r = (data>>3)&0x7;
break;
default:
logerror("YMF278B: Port C write %02x, %02x\n", reg, data);
break;
}
}
m_pcmregs[reg] = data;
}
void ymf278b_device::timer_busy_start(int is_pcm)
{
// status register BUSY bit is on for 56(FM) or 88(PCM) cycles
m_fm.set_reset_status(STATUS_BUSY, 0);
m_timer_busy->adjust(attotime::from_hz(m_clock / (is_pcm ? 88 : 56)));
}
void ymf278b_device::write(offs_t offset, u8 data)
{
uint32_t old;
switch (offset & 7)
{
case 0:
case 2:
timer_busy_start(0);
m_port_AB = data;
m_lastport = BIT(offset, 1);
break;
case 1:
case 3:
timer_busy_start(0);
old = m_fm.regs().new2flag();
m_fm.write(m_port_AB | (m_lastport << 8), data);
// if the new2 flag is turned on, the next status read will set bit 1
// but only for the first status read after new2 is set
if (old == 0 && m_fm.regs().new2flag() != 0)
m_next_status_id = true;
break;
case 4:
timer_busy_start(1);
m_port_C = data;
break;
case 5:
// PCM regs are only accessible if NEW2 is set
if (!m_fm.regs().new2flag())
break;
m_stream->update();
timer_busy_start(1);
C_w(m_port_C, data);
break;
default:
logerror("%s: unexpected write at offset %X to ymf278b = %02X\n", machine().describe_context(), offset, data);
break;
}
}
u8 ymf278b_device::read(offs_t offset)
{
uint8_t ret = 0;
switch (offset & 7)
{
// status register
case 0:
// first status read after initialization returns a chip ID, which
// varies based on the "new" flags, indicating the mode
if (m_next_status_id)
{
if (m_fm.regs().new2flag())
ret = 0x02;
else if (m_fm.regs().newflag())
ret = 0x00;
else
ret = 0x06;
m_next_status_id = false;
}
else
{
ret = m_fm.status();
// if new2 flag is not set, we're in OPL2 or OPL3 mode
if (!m_fm.regs().new2flag())
ret &= ~(STATUS_BUSY | STATUS_LD);
}
break;
// FM regs can be read too (on contrary to what the datasheet says)
case 1:
case 3:
// but they're not implemented here yet
// This may be incorrect, but it makes the mbwave moonsound detection in msx drivers pass.
ret = m_fm.regs().read(m_port_AB | (m_lastport << 8));
break;
// PCM regs
case 5:
// only accessible if NEW2 is set
if (!m_fm.regs().new2flag())
break;
switch (m_port_C)
{
// special cases
case 2:
ret = (m_pcmregs[m_port_C] & 0x1f) | 0x20; // device ID in upper bits
break;
case 6:
ret = read_byte(m_memadr);
m_memadr = (m_memadr + 1) & 0x3fffff;
break;
default:
ret = m_pcmregs[m_port_C];
break;
}
break;
default:
logerror("%s: unexpected read at offset %X from ymf278b\n", machine().describe_context(), offset);
break;
}
return ret;
}
/**************************************************************************/
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void ymf278b_device::device_reset()
{
int i;
// clear registers
for (i = 0; i < 8; i++)
C_w(i, 0);
for (i = 0xff; i >= 8; i--)
C_w(i, 0);
C_w(0xf8, 0x1b);
m_port_AB = m_port_C = 0;
m_lastport = 0;
m_next_status_id = true;
m_memadr = 0;
// init/silence channels
for (i = 0; i < 24 ; i++)
{
YMF278BSlot *slot = &m_slots[i];
slot->LFO = 0;
slot->VIB = 0;
slot->AR = 0;
slot->D1R = 0;
slot->DL = 0;
slot->D2R = 0;
slot->RC = 0;
slot->RR = 0;
slot->AM = 0;
slot->startaddr = 0;
slot->loopaddr = 0;
slot->endaddr = 0;
slot->env_step = 5;
compute_envelope(slot);
}
m_timer_busy->reset();
m_timer_ld->reset();
m_fm.reset();
}
void ymf278b_device::device_clock_changed()
{
int old_rate = m_rate;
m_clock = clock();
m_rate = m_clock/768;
m_fm_pos = 0;
if (m_rate > old_rate)
{
m_mix_buffer.resize(m_rate*4,0);
}
m_stream->set_sample_rate(m_rate);
}
void ymf278b_device::rom_bank_updated()
{
m_stream->update();
}
void ymf278b_device::precompute_rate_tables()
{
int i;
// decay rate
for (i = 0; i < 64; i++)
{
if (i <= 3)
m_lut_dr[i] = 0;
else if (i >= 60)
m_lut_dr[i] = 15 << 4;
else
m_lut_dr[i] = (15 << (21 - i / 4)) / (4 + i % 4);
}
// attack rate (manual shows curve instead of linear though, so this is not entirely accurate)
for (i = 0; i < 64; i++)
{
if (i <= 3 || i == 63)
m_lut_ar[i] = 0;
else if (i >= 60)
m_lut_ar[i] = 17;
else
m_lut_ar[i] = (67 << (15 - i / 4)) / (4 + i % 4);
}
}
void ymf278b_device::register_save_state()
{
int i;
save_item(NAME(m_pcmregs));
save_item(NAME(m_wavetblhdr));
save_item(NAME(m_memmode));
save_item(NAME(m_memadr));
save_item(NAME(m_fm_l));
save_item(NAME(m_fm_r));
save_item(NAME(m_fm_pos));
save_item(NAME(m_pcm_l));
save_item(NAME(m_pcm_r));
save_item(NAME(m_port_AB));
save_item(NAME(m_port_C));
save_item(NAME(m_lastport));
save_item(NAME(m_next_status_id));
for (i = 0; i < 24; ++i)
{
save_item(NAME(m_slots[i].wave), i);
save_item(NAME(m_slots[i].F_NUMBER), i);
save_item(NAME(m_slots[i].octave), i);
save_item(NAME(m_slots[i].preverb), i);
save_item(NAME(m_slots[i].DAMP), i);
save_item(NAME(m_slots[i].CH), i);
save_item(NAME(m_slots[i].LD), i);
save_item(NAME(m_slots[i].TL), i);
save_item(NAME(m_slots[i].pan), i);
save_item(NAME(m_slots[i].LFO), i);
save_item(NAME(m_slots[i].VIB), i);
save_item(NAME(m_slots[i].AM), i);
save_item(NAME(m_slots[i].AR), i);
save_item(NAME(m_slots[i].D1R), i);
save_item(NAME(m_slots[i].DL), i);
save_item(NAME(m_slots[i].D2R), i);
save_item(NAME(m_slots[i].RC), i);
save_item(NAME(m_slots[i].RR), i);
save_item(NAME(m_slots[i].step), i);
save_item(NAME(m_slots[i].stepptr), i);
save_item(NAME(m_slots[i].active), i);
save_item(NAME(m_slots[i].KEY_ON), i);
save_item(NAME(m_slots[i].bits), i);
save_item(NAME(m_slots[i].startaddr), i);
save_item(NAME(m_slots[i].loopaddr), i);
save_item(NAME(m_slots[i].endaddr), i);
save_item(NAME(m_slots[i].env_step), i);
save_item(NAME(m_slots[i].env_vol), i);
save_item(NAME(m_slots[i].env_vol_step), i);
save_item(NAME(m_slots[i].env_vol_lim), i);
save_item(NAME(m_slots[i].env_preverb), i);
}
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void ymf278b_device::device_start()
{
int i;
m_clock = clock();
m_rate = m_clock / 768;
m_fm_pos = 0;
m_timer_busy = timer_alloc(TIMER_BUSY_CLEAR);
m_timer_ld = timer_alloc(TIMER_LD_CLEAR);
for (i = 0; i < 24; i++)
{
m_slots[i].num = i;
}
m_stream = stream_alloc(0, 6, m_rate);
m_mix_buffer.resize(m_rate*4,0);
// rate tables
precompute_rate_tables();
// Volume table, 1 = -0.375dB, 8 = -3dB, 256 = -96dB
for(i = 0; i < 256; i++)
m_volume[i] = 65536*pow(2.0, (-0.375/6)*i);
for(i = 256; i < 256*4; i++)
m_volume[i] = 0;
// Pan values, units are -3dB, i.e. 8.
for(i = 0; i < 16; i++)
{
m_pan_left[i] = i < 7 ? i*8 : i < 9 ? 256 : 0;
m_pan_right[i] = i < 8 ? 0 : i < 10 ? 256 : (16-i)*8;
}
// Mixing levels, units are -3dB, and add some margin to avoid clipping
for(i=0; i<7; i++)
m_mix_level[i] = m_volume[8*i+13];
m_mix_level[7] = 0;
// Register state for saving
register_save_state();
// YMF262 related -- cribbed from ymfm_device_base_common
{
// allocate our timers
for (int tnum = 0; tnum < 2; tnum++)
m_timer[tnum] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(ymf278b_device::fm_timer_handler), this));
// resolve the handlers
m_update_irq.resolve();
// compute the size of the save buffer by doing an initial save
ymfm::ymfm_saved_state state(m_save_blob, true);
m_fm.save_restore(state);
// now register the blob for save, on the assumption the size won't change
save_item(NAME(m_save_blob));
}
}
DEFINE_DEVICE_TYPE(YMF278B, ymf278b_device, "ymf278b", "Yamaha YMF278B OPL4")
ymf278b_device::ymf278b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, YMF278B, tag, owner, clock)
, device_sound_interface(mconfig, *this)
, device_rom_interface(mconfig, *this)
, m_fm(*this)
, m_update_irq(*this)
{
}
// handle pre-saving by filling the blob
void ymf278b_device::device_pre_save()
{
// remember the original blob size
auto orig_size = m_save_blob.size();
// save the state
ymfm::ymfm_saved_state state(m_save_blob, true);
m_fm.save_restore(state);
// ensure that the size didn't change since we first allocated
if (m_save_blob.size() != orig_size)
throw emu_fatalerror("State size changed for ymfm chip");
}
// handle post-loading by restoring from the blob
void ymf278b_device::device_post_load()
{
// populate the state from the blob
ymfm::ymfm_saved_state state(m_save_blob, false);
m_fm.save_restore(state);
}

View File

@ -1,192 +0,0 @@
// license:BSD-3-Clause
// copyright-holders:R. Belmont, Olivier Galibert, hap
#ifndef MAME_SOUND_YMF278B_H
#define MAME_SOUND_YMF278B_H
#pragma once
#include "ymfm/src/ymfm_opl.h"
#include "dirom.h"
class ymf278b_device : public device_t, public device_sound_interface, public device_rom_interface<22>, public ymfm::ymfm_interface
{
public:
static constexpr u8 STATUS_BUSY = 0x01;
static constexpr u8 STATUS_LD = 0x02;
// YMF278B is OPL4
using fm_engine = ymfm::fm_engine_base<ymfm::opl4_registers>;
// constructor
ymf278b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
// configuration helpers
auto irq_handler() { return m_update_irq.bind(); }
// read/write access
u8 read(offs_t offset);
void write(offs_t offset, u8 data);
protected:
// device-level overrides
virtual void device_start() override;
virtual void device_reset() override;
virtual void device_clock_changed() override;
virtual void device_pre_save() override;
virtual void device_post_load() override;
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
// sound stream update overrides
virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override;
// device_rom_interface overrides
virtual void rom_bank_updated() override;
private:
// timer callbacks
void fm_timer_handler(void *ptr, int param) { m_engine->engine_timer_expired(param); }
void fm_mode_write(void *ptr, int param) { m_engine->engine_mode_write(param); }
void fm_check_interrupts(void *ptr, int param) { m_engine->engine_check_interrupts(); }
struct YMF278BSlot
{
int16_t wave; /* wavetable number */
int16_t F_NUMBER; /* frequency */
int8_t octave; /* octave */
int8_t preverb; /* pseudo-reverb */
int8_t DAMP; /* damping */
int8_t CH; /* output channel */
int8_t LD; /* level direct */
int8_t TL; /* total level */
int8_t pan; /* panpot */
int8_t LFO; /* LFO */
int8_t VIB; /* vibrato */
int8_t AM; /* tremolo */
int8_t AR; /* attack rate */
int8_t D1R; /* decay 1 rate */
int8_t DL; /* decay level */
int8_t D2R; /* decay 2 rate */
int8_t RC; /* rate correction */
int8_t RR; /* release rate */
uint32_t step; /* fixed-point frequency step */
uint64_t stepptr; /* fixed-point pointer into the sample */
int8_t active; /* channel is playing */
int8_t KEY_ON; /* slot keyed on */
int8_t bits; /* width of the samples */
uint32_t startaddr;
uint32_t loopaddr;
uint32_t endaddr;
int env_step;
uint32_t env_vol;
uint32_t env_vol_step;
uint32_t env_vol_lim;
int8_t env_preverb;
int num; /* slot number (for debug only) */
};
int compute_rate(YMF278BSlot *slot, int val);
uint32_t compute_decay_env_vol_step(YMF278BSlot *slot, int val);
void compute_freq_step(YMF278BSlot *slot);
void compute_envelope(YMF278BSlot *slot);
void irq_check();
void retrigger_sample(YMF278BSlot *slot);
void C_w(uint8_t reg, uint8_t data);
void timer_busy_start(int is_pcm);
void precompute_rate_tables();
void register_save_state();
// internal state
uint8_t m_pcmregs[256];
YMF278BSlot m_slots[24];
int8_t m_wavetblhdr;
int8_t m_memmode;
int32_t m_memadr;
emu_timer *m_timer_busy;
emu_timer *m_timer_ld;
int32_t m_fm_l, m_fm_r;
int32_t m_pcm_l, m_pcm_r;
uint32_t m_fm_pos;
uint8_t m_port_C, m_port_AB, m_lastport;
bool m_next_status_id;
// precomputed tables
uint32_t m_lut_ar[64]; // attack rate
uint32_t m_lut_dr[64]; // decay rate
int32_t m_volume[256*4]; // precalculated attenuation values with some margin for envelope and pan levels
int m_pan_left[16],m_pan_right[16]; // pan volume offsets
int32_t m_mix_level[8];
int m_clock;
int m_rate;
sound_stream * m_stream;
std::vector<int32_t> m_mix_buffer;
// ymfm OPL4 -- cribbed from ymfm_device_base_common until we figure out how to
// make a proper chip out of this hybrid
fm_engine m_fm;
attotime m_busy_end; // busy end time
emu_timer *m_timer[2]; // two timers
devcb_write_line m_update_irq; // IRQ update callback
std::vector<uint8_t> m_save_blob;// save state blob for FM
// perform a synchronized write
virtual void ymfm_sync_mode_write(uint8_t data) override
{
machine().scheduler().synchronize(timer_expired_delegate(FUNC(ymf278b_device::fm_mode_write), this), data);
}
// perform a synchronized interrupt check
virtual void ymfm_sync_check_interrupts() override
{
// if we're currently executing a CPU, schedule the interrupt check;
// otherwise, do it directly
auto &scheduler = machine().scheduler();
if (scheduler.currently_executing())
scheduler.synchronize(timer_expired_delegate(FUNC(ymf278b_device::fm_check_interrupts), this));
else
m_engine->engine_check_interrupts();
}
// set a timer
virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) override
{
if (duration_in_clocks >= 0)
m_timer[tnum]->adjust(attotime::from_ticks(duration_in_clocks, device_t::clock()), tnum);
else
m_timer[tnum]->enable(false);
}
// set the time when busy will be clear
virtual void ymfm_set_busy_end(uint32_t clocks) override
{
m_busy_end = machine().time() + attotime::from_ticks(clocks, device_t::clock());
}
// are we past the busy clear time?
virtual bool ymfm_is_busy() override
{
return (machine().time() < m_busy_end);
}
// handle IRQ signaling
virtual void ymfm_update_irq(bool asserted) override
{
if (!m_update_irq.isnull())
m_update_irq(asserted ? ASSERT_LINE : CLEAR_LINE);
}
};
DECLARE_DEVICE_TYPE(YMF278B, ymf278b_device)
#endif // MAME_SOUND_YMF278B_H

View File

@ -123,22 +123,19 @@ protected:
return (machine().time() < m_busy_end);
}
// the chip implementation calls this whenever a new value is written to
// one of the chip's output ports (only applies to some chip types); our
// responsibility is to pass the written data on to any consumers
virtual void ymfm_io_write(uint8_t port, uint8_t data) override
// 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(ymfm::access_class type, uint32_t address) override
{
if (!m_io_write[port & 1].isnull())
m_io_write[port & 1](data);
return (type != ymfm::ACCESS_IO || m_io_read[address & 1].isnull()) ? 0 : m_io_read[address & 1]();
}
// the chip implementation calls this whenever an on-chip register is read
// which returns data from one of the chip's input ports; our responsibility
// is to produce the current input value so that it can be reflected by the
// read operation
virtual uint8_t ymfm_io_read(uint8_t port) override
// 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(ymfm::access_class type, uint32_t address, uint8_t data) override
{
return m_io_read[port & 1].isnull() ? 0 : m_io_read[port & 1]();
if (type == ymfm::ACCESS_IO && !m_io_write[address & 1].isnull())
m_io_write[address & 1](data);
}
// handle device start

View File

@ -51,26 +51,30 @@ void y8950_device::rom_bank_updated()
//-------------------------------------------------
// ymfm_adpcm_b_read - callback to read data for
// ymfm_external_read - callback to read data for
// the ADPCM-B engine; in this case, from our
// default address space
//-------------------------------------------------
uint8_t y8950_device::ymfm_adpcm_b_read(uint32_t offset)
uint8_t y8950_device::ymfm_external_read(ymfm::access_class type, uint32_t offset)
{
return read_byte(offset);
if (type == ymfm::ACCESS_ADPCM_B)
return read_byte(offset);
return parent::ymfm_external_read(type, offset);
}
//-------------------------------------------------
// ymfm_adpcm_b_write - callback to write data to
// ymfm_external_write - callback to write data to
// the ADPCM-B engine; in this case, to our
// default address space
//-------------------------------------------------
void y8950_device::ymfm_adpcm_b_write(uint32_t offset, uint8_t data)
void y8950_device::ymfm_external_write(ymfm::access_class type, uint32_t offset, uint8_t data)
{
space().write_byte(offset, data);
if (type == ymfm::ACCESS_ADPCM_B)
return space().write_byte(offset, data);
parent::ymfm_external_write(type, offset, data);
}
@ -109,6 +113,75 @@ ymf262_device::ymf262_device(const machine_config &mconfig, const char *tag, dev
//*********************************************************
// YMF278B DEVICE
//*********************************************************
DEFINE_DEVICE_TYPE(YMF278B, ymf278b_device, "ymf278b", "YMF278B OPL4")
//-------------------------------------------------
// ymf278b_device - constructor
//-------------------------------------------------
ymf278b_device::ymf278b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
ymfm_device_base<ymfm::ymf278b>(mconfig, tag, owner, clock, YMF278B),
device_rom_interface(mconfig, *this)
{
}
//-------------------------------------------------
// rom_bank_updated - refresh the stream if the
// ROM banking changes
//-------------------------------------------------
void ymf278b_device::rom_bank_updated()
{
m_stream->update();
}
//-------------------------------------------------
// ymfm_external_read - callback to read data for
// the ADPCM-B engine; in this case, from our
// default address space
//-------------------------------------------------
uint8_t ymf278b_device::ymfm_external_read(ymfm::access_class type, uint32_t offset)
{
if (type == ymfm::ACCESS_PCM)
return read_byte(offset);
return 0;
}
//-------------------------------------------------
// ymfm_external_write - callback to write data to
// the ADPCM-B engine; in this case, to our
// default address space
//-------------------------------------------------
void ymf278b_device::ymfm_external_write(ymfm::access_class type, uint32_t offset, uint8_t data)
{
if (type == ymfm::ACCESS_PCM)
return space().write_byte(offset, data);
}
//-------------------------------------------------
// ymfm_external_write - callback to write data to
// the ADPCM-B engine; in this case, to our
// default address space
//-------------------------------------------------
void ymf278b_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
{
// rotate the outputs so that the DO2 outputs are first
parent::update_internal(outputs, 2);
}
//*********************************************************
// YM2413 DEVICE
//*********************************************************

View File

@ -29,6 +29,8 @@ DECLARE_DEVICE_TYPE(Y8950, y8950_device);
class y8950_device : public ymfm_device_base<ymfm::y8950>, public device_rom_interface<21>
{
using parent = ymfm_device_base<ymfm::y8950>;
public:
// constructor
y8950_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
@ -48,8 +50,8 @@ protected:
private:
// ADPCM read/write callbacks
uint8_t ymfm_adpcm_b_read(offs_t address) override;
void ymfm_adpcm_b_write(offs_t address, uint8_t data) override;
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address) override;
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) override;
};
@ -81,6 +83,41 @@ public:
};
// ======================> ymf278b_device
DECLARE_DEVICE_TYPE(YMF278B, ymf278b_device);
class ymf278b_device : public ymfm_device_base<ymfm::ymf278b>, public device_rom_interface<22>
{
using parent = ymfm_device_base<ymfm::ymf278b>;
public:
// constructor
ymf278b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
// additional register reads
uint8_t data_pcm_r() { return update_streams().read_data_pcm(); }
// additional register writes
void address_hi_w(u8 data) { update_streams().write_address_hi(data); }
void data_hi_w(u8 data) { update_streams().write_data(data); }
void address_pcm_w(u8 data) { update_streams().write_address_pcm(data); }
void data_pcm_w(u8 data) { update_streams().write_data_pcm(data); }
protected:
// device_rom_interface overrides
virtual void rom_bank_updated() override;
// sound overrides
virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override;
private:
// ADPCM read/write callbacks
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address) override;
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) override;
};
// ======================> ym2413_device
DECLARE_DEVICE_TYPE(YM2413, ym2413_device);

View File

@ -109,38 +109,31 @@ void ym2608_device::rom_bank_updated()
//-------------------------------------------------
// ymfm_adpcm_a_read - callback to read data for
// the ADPCM-A engine; in this case, from the
// internal ROM containing drum samples
// ymfm_external_read - callback to read data for
// the ADPCM-A/B engines
//-------------------------------------------------
uint8_t ym2608_device::ymfm_adpcm_a_read(uint32_t offset)
uint8_t ym2608_device::ymfm_external_read(ymfm::access_class type, uint32_t offset)
{
return m_internal->as_u8(offset % m_internal->bytes());
if (type == ymfm::ACCESS_ADPCM_A)
return m_internal->as_u8(offset % m_internal->bytes());
else if (type == ymfm::ACCESS_ADPCM_B)
return space(0).read_byte(offset);
return parent::ymfm_external_read(type, offset);
}
//-------------------------------------------------
// ymfm_adpcm_b_read - callback to read data for
// the ADPCM-B engine; in this case, from our
// default address space
//-------------------------------------------------
uint8_t ym2608_device::ymfm_adpcm_b_read(uint32_t offset)
{
return space(0).read_byte(offset);
}
//-------------------------------------------------
// ymfm_adpcm_b_write - callback to write data to
// ymfm_external_write - callback to write data to
// the ADPCM-B engine; in this case, to our
// default address space
//-------------------------------------------------
void ym2608_device::ymfm_adpcm_b_write(uint32_t offset, uint8_t data)
void ym2608_device::ymfm_external_write(ymfm::access_class type, uint32_t offset, uint8_t data)
{
space(0).write_byte(offset, data);
if (type == ymfm::ACCESS_ADPCM_B)
return space(0).write_byte(offset, data);
parent::ymfm_external_write(type, offset, data);
}
@ -208,28 +201,18 @@ void ym2610_device_base<ChipClass>::device_start()
//-------------------------------------------------
// ymfm_adpcm_a_read - callback to read data for
// the ADPCM-A engine; in this case, from address
// space 0
// ymfm_external_read - callback to read data for
// the ADPCM-A/B engines
//-------------------------------------------------
template<typename ChipClass>
uint8_t ym2610_device_base<ChipClass>::ymfm_adpcm_a_read(uint32_t offset)
uint8_t ym2610_device_base<ChipClass>::ymfm_external_read(ymfm::access_class type, uint32_t offset)
{
return space(0).read_byte(offset);
}
//-------------------------------------------------
// ymfm_adpcm_b_read - callback to read data for
// the ADPCM-B engine; in this case, from address
// space 1
//-------------------------------------------------
template<typename ChipClass>
uint8_t ym2610_device_base<ChipClass>::ymfm_adpcm_b_read(uint32_t offset)
{
return space(1).read_byte(offset);
if (type == ymfm::ACCESS_ADPCM_A)
return space(0).read_byte(offset);
else if (type == ymfm::ACCESS_ADPCM_B)
return space(1).read_byte(offset);
return 0;
}

View File

@ -75,9 +75,8 @@ protected:
private:
// ADPCM read/write callbacks
virtual uint8_t ymfm_adpcm_a_read(uint32_t address) override;
virtual uint8_t ymfm_adpcm_b_read(uint32_t address) override;
virtual void ymfm_adpcm_b_write(uint32_t address, u8 data) override;
virtual uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address) override;
virtual void ymfm_external_write(ymfm::access_class type, uint32_t address, u8 data) override;
// internal state
required_memory_region m_internal; // internal memory region
@ -115,8 +114,7 @@ protected:
private:
// ADPCM read/write callbacks
virtual uint8_t ymfm_adpcm_a_read(offs_t address) override;
virtual uint8_t ymfm_adpcm_b_read(offs_t address) override;
virtual uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address) override;
// internal state
address_space_config const m_adpcm_a_config; // address space 0 config (ADPCM-A)

View File

@ -161,7 +161,7 @@ FG-3J ROM-J 507KA0301P04 Rev:1.3
#include "cpu/z80/z80.h"
#include "cpu/m68000/m68000.h"
#include "sound/ymf278b.h"
#include "sound/ymopl.h"
#include "speaker.h"

View File

@ -46,7 +46,6 @@ Notes:
#include "cpu/z80/z80.h"
#include "machine/i8255.h"
#include "sound/ymopl.h"
#include "sound/ymf278b.h"
#include "speaker.h"

View File

@ -102,7 +102,6 @@ driver modified by Hau
#include "sound/msm5205.h"
#include "sound/ymopl.h"
#include "sound/ymopn.h"
#include "sound/ymf278b.h"
#include "speaker.h"
#include <algorithm>

View File

@ -85,8 +85,8 @@ This was pointed out by Bart Puype
#include "cpu/m68000/m68000.h"
#include "cpu/pic16c5x/pic16c5x.h"
#include "sound/okim6295.h"
#include "sound/ymopl.h"
#include "sound/ymopn.h"
#include "sound/ymf278b.h"
#include "speaker.h"

View File

@ -279,7 +279,7 @@ Notes:
#include "cpu/sh/sh2.h"
#include "machine/eepromser.h"
#include "machine/watchdog.h"
#include "sound/ymf278b.h"
#include "sound/ymopl.h"
#include "speaker.h"

View File

@ -39,7 +39,6 @@
#include "sound/vgm_visualizer.h"
#include "sound/x1_010.h"
#include "sound/ymf271.h"
#include "sound/ymf278b.h"
#include "sound/ymopl.h"
#include "sound/ymopm.h"
#include "sound/ymopn.h"

View File

@ -15,8 +15,6 @@
#define CPU_CLOCK (XTAL(40'000'000) / 2) /* clock for 68020 */
#define SOUND_CPU_CLOCK (XTAL(12'000'000) / 2) /* clock for Z80 sound CPU */
/* NOTE: YMF278B_STD_CLOCK is defined in /src/emu/sound/ymf278b.h */
class fuuki32_state : public driver_device
{

View File

@ -7,7 +7,7 @@
*************************************************************************/
#include "cpu/sh/sh2.h"
#include "sound/ymf278b.h"
#include "sound/ymopl.h"
#include "machine/eepromser.h"
#include "emupal.h"
#include "screen.h"