mirror of
https://github.com/holub/mame
synced 2025-04-16 05:24:54 +03:00
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:
parent
52c226c28e
commit
d9db7d77c4
26
3rdparty/ymfm/GeneralInfo.md
vendored
26
3rdparty/ymfm/GeneralInfo.md
vendored
@ -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.
|
||||
|
9
3rdparty/ymfm/README.md
vendored
9
3rdparty/ymfm/README.md
vendored
@ -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.
|
||||
|
166
3rdparty/ymfm/examples/vgmrender/vgmrender.cpp
vendored
166
3rdparty/ymfm/examples/vgmrender/vgmrender.cpp
vendored
@ -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
|
||||
|
73
3rdparty/ymfm/src/ymfm.h
vendored
73
3rdparty/ymfm/src/ymfm.h
vendored
@ -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
|
||||
|
8
3rdparty/ymfm/src/ymfm_adpcm.cpp
vendored
8
3rdparty/ymfm/src/ymfm_adpcm.cpp
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
|
24
3rdparty/ymfm/src/ymfm_fm.h
vendored
24
3rdparty/ymfm/src/ymfm_fm.h
vendored
@ -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 ®s() { return m_regs; }
|
||||
RegisterType ®s() 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 ®s() { return m_regs; }
|
||||
RegisterType ®s() 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
|
||||
|
47
3rdparty/ymfm/src/ymfm_fm.ipp
vendored
47
3rdparty/ymfm/src/ymfm_fm.ipp
vendored
@ -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);
|
||||
|
||||
|
402
3rdparty/ymfm/src/ymfm_opl.cpp
vendored
402
3rdparty/ymfm/src/ymfm_opl.cpp
vendored
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
95
3rdparty/ymfm/src/ymfm_opl.h
vendored
95
3rdparty/ymfm/src/ymfm_opl.h
vendored
@ -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
|
||||
//*********************************************************
|
||||
|
6
3rdparty/ymfm/src/ymfm_opm.cpp
vendored
6
3rdparty/ymfm/src/ymfm_opm.cpp
vendored
@ -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
|
||||
|
436
3rdparty/ymfm/src/ymfm_opn.cpp
vendored
436
3rdparty/ymfm/src/ymfm_opn.cpp
vendored
@ -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
|
||||
|
77
3rdparty/ymfm/src/ymfm_opn.h
vendored
77
3rdparty/ymfm/src/ymfm_opn.h
vendored
@ -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
|
||||
|
4
3rdparty/ymfm/src/ymfm_opq.cpp
vendored
4
3rdparty/ymfm/src/ymfm_opq.cpp
vendored
@ -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];
|
||||
}
|
||||
|
||||
|
10
3rdparty/ymfm/src/ymfm_opz.cpp
vendored
10
3rdparty/ymfm/src/ymfm_opz.cpp
vendored
@ -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
712
3rdparty/ymfm/src/ymfm_pcm.cpp
vendored
Normal 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
307
3rdparty/ymfm/src/ymfm_pcm.h
vendored
Normal 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 ®s() { 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
|
14
3rdparty/ymfm/src/ymfm_ssg.cpp
vendored
14
3rdparty/ymfm/src/ymfm_ssg.cpp
vendored
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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",
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
---------------------------------------------------
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
//*********************************************************
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user