mirror of
https://github.com/holub/mame
synced 2025-04-16 13:34:55 +03:00
Update 3rdparty/ymfm to latest. (#10583)
* Fixes uninitialized member causing slight jitter in timing (GitHub #10414). * Fixes OPNA behavior when LFO is disabled. * Fixes a PCM playback wraparound bug due to incorrect auto-incrementing.
This commit is contained in:
parent
09e5a49fd8
commit
c778f5406b
1
3rdparty/ymfm/.gitignore
vendored
1
3rdparty/ymfm/.gitignore
vendored
@ -37,3 +37,4 @@
|
||||
|
||||
# VS Code stuff
|
||||
.vs/
|
||||
reference/
|
||||
|
4
3rdparty/ymfm/README.md
vendored
4
3rdparty/ymfm/README.md
vendored
@ -1,5 +1,9 @@
|
||||
# ymfm
|
||||
|
||||
<div style='text-align:center;margin:auto'>
|
||||
<img src='https://aarongiles.com/img/icon-ymfm.png' width='128px'>
|
||||
</div>
|
||||
|
||||
[ymfm](https://github.com/aaronsgiles/ymfm) is a collection of BSD-licensed Yamaha FM sound cores (OPM, OPN, OPL, and others), written by [Aaron Giles](https://aarongiles.com)
|
||||
|
||||
## Supported environments
|
||||
|
45
3rdparty/ymfm/examples/vgmrender/vgmrender.cpp
vendored
45
3rdparty/ymfm/examples/vgmrender/vgmrender.cpp
vendored
@ -9,7 +9,7 @@
|
||||
//
|
||||
// or:
|
||||
//
|
||||
// clang --std=c++14 -I../../src vgmrender.cpp em_inflate.cpp ../../src/ymfm_misc.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
|
||||
// clang++ --std=c++14 -I../../src vgmrender.cpp em_inflate.cpp ../../src/ymfm_misc.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:
|
||||
//
|
||||
@ -33,11 +33,17 @@
|
||||
|
||||
#define LOG_WRITES (0)
|
||||
|
||||
// run this many dummy clocks of each chip before generating
|
||||
#define EXTRA_CLOCKS (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)
|
||||
#if (RUN_NUKED_OPN2)
|
||||
namespace nuked {
|
||||
bool s_log_envelopes = false;
|
||||
const int s_log_envelopes_channel = 5;
|
||||
#include "test/ym3438.h"
|
||||
}
|
||||
#endif
|
||||
@ -92,6 +98,11 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
// destruction
|
||||
virtual ~vgm_chip_base()
|
||||
{
|
||||
}
|
||||
|
||||
// simple getters
|
||||
chip_type type() const { return m_type; }
|
||||
virtual uint32_t sample_rate() const = 0;
|
||||
@ -149,12 +160,19 @@ public:
|
||||
m_pos(0)
|
||||
{
|
||||
m_chip.reset();
|
||||
|
||||
for (int clock = 0; clock < EXTRA_CLOCKS; clock++)
|
||||
m_chip.generate(&m_output);
|
||||
|
||||
#if (RUN_NUKED_OPN2)
|
||||
if (type == CHIP_YM2612)
|
||||
{
|
||||
m_external = new nuked::ym3438_t;
|
||||
nuked::OPN2_SetChipType(nuked::ym3438_mode_ym2612);
|
||||
nuked::OPN2_Reset(m_external);
|
||||
nuked::Bit16s buffer[2];
|
||||
for (int clocks = 0; clocks < 24 * EXTRA_CLOCKS; clocks++)
|
||||
nuked::OPN2_Clock(m_external, buffer);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -191,12 +209,13 @@ public:
|
||||
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);
|
||||
printf("%10.5f: %s %03X=%02X\n", double(output_start) / double(1LL << 32), m_name.c_str(), data1 + 0x100 * (addr1/2), data2);
|
||||
m_chip.write(addr1, data1);
|
||||
m_chip.write(addr2, data2);
|
||||
}
|
||||
|
||||
// generate at the appropriate sample rate
|
||||
// nuked::s_log_envelopes = (output_start >= (22ll << 32) && output_start < (24ll << 32));
|
||||
for ( ; m_pos <= output_start; m_pos += m_step)
|
||||
{
|
||||
m_chip.generate(&m_output);
|
||||
@ -256,8 +275,8 @@ public:
|
||||
}
|
||||
else if (m_type == CHIP_YMF278B)
|
||||
{
|
||||
*buffer++ += m_output.data[4];
|
||||
*buffer++ += m_output.data[5];
|
||||
*buffer++ += m_output.data[4 % ChipType::OUTPUTS];
|
||||
*buffer++ += m_output.data[5 % ChipType::OUTPUTS];
|
||||
}
|
||||
else if (ChipType::OUTPUTS == 1)
|
||||
{
|
||||
@ -297,7 +316,7 @@ protected:
|
||||
//*********************************************************
|
||||
|
||||
// global list of active chips
|
||||
std::list<vgm_chip_base *> active_chips;
|
||||
std::vector<std::unique_ptr<vgm_chip_base>> active_chips;
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
@ -329,7 +348,7 @@ void add_chips(uint32_t clock, chip_type type, char const *chipname)
|
||||
{
|
||||
char name[100];
|
||||
sprintf(name, "%s #%d", chipname, index);
|
||||
active_chips.push_back(new vgm_chip<ChipType>(clockval, type, (numchips == 2) ? name : chipname));
|
||||
active_chips.push_back(std::make_unique<vgm_chip<ChipType>>(clockval, type, (numchips == 2) ? name : chipname));
|
||||
}
|
||||
|
||||
if (type == CHIP_YM2608)
|
||||
@ -345,7 +364,7 @@ void add_chips(uint32_t clock, chip_type type, char const *chipname)
|
||||
std::vector<uint8_t> temp(size);
|
||||
fread(&temp[0], 1, size, rom);
|
||||
fclose(rom);
|
||||
for (auto chip : active_chips)
|
||||
for (auto &chip : active_chips)
|
||||
if (chip->type() == type)
|
||||
chip->write_data(ymfm::ACCESS_ADPCM_A, 0, size, &temp[0]);
|
||||
}
|
||||
@ -727,9 +746,9 @@ uint32_t parse_header(std::vector<uint8_t> &buffer)
|
||||
|
||||
vgm_chip_base *find_chip(chip_type type, uint8_t index)
|
||||
{
|
||||
for (auto chip : active_chips)
|
||||
for (auto &chip : active_chips)
|
||||
if (chip->type() == type && index-- == 0)
|
||||
return chip;
|
||||
return chip.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -1094,7 +1113,7 @@ void generate_all(std::vector<uint8_t> &buffer, uint32_t data_start, uint32_t ou
|
||||
{
|
||||
bool more_remaining = false;
|
||||
int32_t outputs[2] = { 0 };
|
||||
for (auto chip : active_chips)
|
||||
for (auto &chip : active_chips)
|
||||
chip->generate(output_pos, output_step, outputs);
|
||||
output_pos += output_step;
|
||||
wav_buffer.push_back(outputs[0]);
|
||||
@ -1386,7 +1405,7 @@ int main(int argc, char *argv[])
|
||||
#if (CAPTURE_NATIVE)
|
||||
{
|
||||
int chipnum = 0;
|
||||
for (auto chip : active_chips)
|
||||
for (auto &chip : active_chips)
|
||||
if (err == 0 && chip->m_native_data.size() > 0)
|
||||
{
|
||||
char filename[20];
|
||||
@ -1398,7 +1417,7 @@ int main(int argc, char *argv[])
|
||||
#if (RUN_NUKED_OPN2)
|
||||
{
|
||||
int chipnum = 0;
|
||||
for (auto chip : active_chips)
|
||||
for (auto &chip : active_chips)
|
||||
if (err == 0 && chip->m_nuked_data.size() > 0)
|
||||
{
|
||||
char filename[20];
|
||||
@ -1408,6 +1427,8 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
#endif
|
||||
|
||||
active_chips.clear();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
81
3rdparty/ymfm/src/ymfm.h
vendored
81
3rdparty/ymfm/src/ymfm.h
vendored
@ -40,6 +40,7 @@
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@ -325,6 +326,86 @@ struct ymfm_output
|
||||
};
|
||||
|
||||
|
||||
// ======================> ymfm_wavfile
|
||||
|
||||
// this class is a debugging helper that accumulates data and writes it to wav files
|
||||
template<int Channels>
|
||||
class ymfm_wavfile
|
||||
{
|
||||
public:
|
||||
// construction
|
||||
ymfm_wavfile(uint32_t samplerate = 44100) :
|
||||
m_samplerate(samplerate)
|
||||
{
|
||||
}
|
||||
|
||||
// configuration
|
||||
ymfm_wavfile &set_index(uint32_t index) { m_index = index; return *this; }
|
||||
ymfm_wavfile &set_samplerate(uint32_t samplerate) { m_samplerate = samplerate; return *this; }
|
||||
|
||||
// destruction
|
||||
~ymfm_wavfile()
|
||||
{
|
||||
if (!m_buffer.empty())
|
||||
{
|
||||
// create file
|
||||
char name[20];
|
||||
sprintf(name, "wavlog-%02d.wav", m_index);
|
||||
FILE *out = fopen(name, "wb");
|
||||
|
||||
// make the wav file header
|
||||
uint8_t header[44];
|
||||
memcpy(&header[0], "RIFF", 4);
|
||||
*(uint32_t *)&header[4] = m_buffer.size() * 2 + 44 - 8;
|
||||
memcpy(&header[8], "WAVE", 4);
|
||||
memcpy(&header[12], "fmt ", 4);
|
||||
*(uint32_t *)&header[16] = 16;
|
||||
*(uint16_t *)&header[20] = 1;
|
||||
*(uint16_t *)&header[22] = Channels;
|
||||
*(uint32_t *)&header[24] = m_samplerate;
|
||||
*(uint32_t *)&header[28] = m_samplerate * 2 * Channels;
|
||||
*(uint16_t *)&header[32] = 2 * Channels;
|
||||
*(uint16_t *)&header[34] = 16;
|
||||
memcpy(&header[36], "data", 4);
|
||||
*(uint32_t *)&header[40] = m_buffer.size() * 2 + 44 - 44;
|
||||
|
||||
// write header then data
|
||||
fwrite(&header[0], 1, sizeof(header), out);
|
||||
fwrite(&m_buffer[0], 2, m_buffer.size(), out);
|
||||
fclose(out);
|
||||
}
|
||||
}
|
||||
|
||||
// add data to the file
|
||||
template<int Outputs>
|
||||
void add(ymfm_output<Outputs> output)
|
||||
{
|
||||
int16_t sum[Channels] = { 0 };
|
||||
for (int index = 0; index < Outputs; index++)
|
||||
sum[index % Channels] += output.data[index];
|
||||
for (int index = 0; index < Channels; index++)
|
||||
m_buffer.push_back(sum[index]);
|
||||
}
|
||||
|
||||
// add data to the file, using a reference
|
||||
template<int Outputs>
|
||||
void add(ymfm_output<Outputs> output, ymfm_output<Outputs> const &ref)
|
||||
{
|
||||
int16_t sum[Channels] = { 0 };
|
||||
for (int index = 0; index < Outputs; index++)
|
||||
sum[index % Channels] += output.data[index] - ref.data[index];
|
||||
for (int index = 0; index < Channels; index++)
|
||||
m_buffer.push_back(sum[index]);
|
||||
}
|
||||
|
||||
private:
|
||||
// internal state
|
||||
uint32_t m_index;
|
||||
uint32_t m_samplerate;
|
||||
std::vector<int16_t> m_buffer;
|
||||
};
|
||||
|
||||
|
||||
// ======================> ymfm_saved_state
|
||||
|
||||
// this class contains a managed vector of bytes that is used to save and
|
||||
|
14
3rdparty/ymfm/src/ymfm_fm.h
vendored
14
3rdparty/ymfm/src/ymfm_fm.h
vendored
@ -33,6 +33,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define YMFM_DEBUG_LOG_WAVFILES (0)
|
||||
|
||||
namespace ymfm
|
||||
{
|
||||
|
||||
@ -397,7 +399,14 @@ public:
|
||||
void set_clock_prescale(uint32_t prescale) { m_clock_prescale = prescale; }
|
||||
|
||||
// compute sample rate
|
||||
uint32_t sample_rate(uint32_t baseclock) const { return baseclock / (m_clock_prescale * OPERATORS); }
|
||||
uint32_t sample_rate(uint32_t baseclock) const
|
||||
{
|
||||
#if (YMFM_DEBUG_LOG_WAVFILES)
|
||||
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
|
||||
m_wavfile[chnum].set_samplerate(baseclock / (m_clock_prescale * OPERATORS));
|
||||
#endif
|
||||
return baseclock / (m_clock_prescale * OPERATORS);
|
||||
}
|
||||
|
||||
// return the owning device
|
||||
ymfm_interface &intf() const { return m_intf; }
|
||||
@ -444,6 +453,9 @@ protected:
|
||||
RegisterType m_regs; // register accessor
|
||||
std::unique_ptr<fm_channel<RegisterType>> m_channel[CHANNELS]; // channel pointers
|
||||
std::unique_ptr<fm_operator<RegisterType>> m_operator[OPERATORS]; // operator pointers
|
||||
#if (YMFM_DEBUG_LOG_WAVFILES)
|
||||
mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; // for debugging
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
||||
|
21
3rdparty/ymfm/src/ymfm_fm.ipp
vendored
21
3rdparty/ymfm/src/ymfm_fm.ipp
vendored
@ -1185,6 +1185,7 @@ fm_engine_base<RegisterType>::fm_engine_base(ymfm_interface &intf) :
|
||||
m_irq_mask(STATUS_TIMERA | STATUS_TIMERB),
|
||||
m_irq_state(0),
|
||||
m_timer_running{0,0},
|
||||
m_total_clocks(0),
|
||||
m_active_channels(ALL_CHANNELS),
|
||||
m_modified_channels(ALL_CHANNELS),
|
||||
m_prepare_count(0)
|
||||
@ -1200,6 +1201,11 @@ fm_engine_base<RegisterType>::fm_engine_base(ymfm_interface &intf) :
|
||||
for (uint32_t opnum = 0; opnum < OPERATORS; opnum++)
|
||||
m_operator[opnum] = std::make_unique<fm_operator<RegisterType>>(*this, RegisterType::operator_offset(opnum));
|
||||
|
||||
#if (YMFM_DEBUG_LOG_WAVFILES)
|
||||
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
|
||||
m_wavfile[chnum].set_index(chnum);
|
||||
#endif
|
||||
|
||||
// do the initial operator assignment
|
||||
assign_operators();
|
||||
}
|
||||
@ -1327,7 +1333,8 @@ void fm_engine_base<RegisterType>::output(output_data &output, uint32_t rshift,
|
||||
chanmask &= debug::GLOBAL_FM_CHANNEL_MASK;
|
||||
|
||||
// mask out inactive channels
|
||||
chanmask &= m_active_channels;
|
||||
if (!YMFM_DEBUG_LOG_WAVFILES)
|
||||
chanmask &= m_active_channels;
|
||||
|
||||
// handle the rhythm case, where some of the operators are dedicated
|
||||
// to percussion (this is an OPL-specific feature)
|
||||
@ -1345,6 +1352,9 @@ void fm_engine_base<RegisterType>::output(output_data &output, uint32_t rshift,
|
||||
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
|
||||
if (bitfield(chanmask, chnum))
|
||||
{
|
||||
#if (YMFM_DEBUG_LOG_WAVFILES)
|
||||
auto reference = output;
|
||||
#endif
|
||||
if (chnum == 6)
|
||||
m_channel[chnum]->output_rhythm_ch6(output, rshift, clipmax);
|
||||
else if (chnum == 7)
|
||||
@ -1355,6 +1365,9 @@ void fm_engine_base<RegisterType>::output(output_data &output, uint32_t rshift,
|
||||
m_channel[chnum]->output_4op(output, rshift, clipmax);
|
||||
else
|
||||
m_channel[chnum]->output_2op(output, rshift, clipmax);
|
||||
#if (YMFM_DEBUG_LOG_WAVFILES)
|
||||
m_wavfile[chnum].add(output, reference);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -1363,10 +1376,16 @@ void fm_engine_base<RegisterType>::output(output_data &output, uint32_t rshift,
|
||||
for (uint32_t chnum = 0; chnum < CHANNELS; chnum++)
|
||||
if (bitfield(chanmask, chnum))
|
||||
{
|
||||
#if (YMFM_DEBUG_LOG_WAVFILES)
|
||||
auto reference = output;
|
||||
#endif
|
||||
if (m_channel[chnum]->is4op())
|
||||
m_channel[chnum]->output_4op(output, rshift, clipmax);
|
||||
else
|
||||
m_channel[chnum]->output_2op(output, rshift, clipmax);
|
||||
#if (YMFM_DEBUG_LOG_WAVFILES)
|
||||
m_wavfile[chnum].add(output, reference);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
3rdparty/ymfm/src/ymfm_opl.cpp
vendored
5
3rdparty/ymfm/src/ymfm_opl.cpp
vendored
@ -100,6 +100,11 @@ opl_registers_base<Revision>::opl_registers_base() :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OPL3/OPL4 have dynamic operators, so initialize the fourop_enable value here
|
||||
// since operator_map() is called right away, prior to reset()
|
||||
if (Revision > 2)
|
||||
m_regdata[0x104 % REGISTERS] = 0;
|
||||
}
|
||||
|
||||
|
||||
|
11
3rdparty/ymfm/src/ymfm_opn.cpp
vendored
11
3rdparty/ymfm/src/ymfm_opn.cpp
vendored
@ -205,7 +205,12 @@ int32_t opn_registers_base<IsOpnA>::clock_noise_and_lfo()
|
||||
if (!IsOpnA || !lfo_enable())
|
||||
{
|
||||
m_lfo_counter = 0;
|
||||
m_lfo_am = 0;
|
||||
|
||||
// special case: if LFO is disabled on OPNA, it basically just keeps the counter
|
||||
// at 0; since position 0 gives an AM value of 0x3f, it is important to reflect
|
||||
// that here; for example, MegaDrive Venom plays some notes with LFO globally
|
||||
// disabled but enabling LFO on the operators, and it expects this added attenutation
|
||||
m_lfo_am = IsOpnA ? 0x3f : 0x00;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -427,10 +432,10 @@ std::string opn_registers_base<IsOpnA>::log_keyon(uint32_t choffs, uint32_t opof
|
||||
ch_output_1(choffs) ? 'R' : '-');
|
||||
if (op_ssg_eg_enable(opoffs))
|
||||
end += sprintf(end, " ssg=%X", op_ssg_eg_mode(opoffs));
|
||||
bool am = (lfo_enable() && op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0);
|
||||
bool am = (op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0);
|
||||
if (am)
|
||||
end += sprintf(end, " am=%u", ch_lfo_am_sens(choffs));
|
||||
bool pm = (lfo_enable() && ch_lfo_pm_sens(choffs) != 0);
|
||||
bool pm = (ch_lfo_pm_sens(choffs) != 0);
|
||||
if (pm)
|
||||
end += sprintf(end, " pm=%u", ch_lfo_pm_sens(choffs));
|
||||
if (am || pm)
|
||||
|
4
3rdparty/ymfm/src/ymfm_pcm.h
vendored
4
3rdparty/ymfm/src/ymfm_pcm.h
vendored
@ -212,8 +212,8 @@ public:
|
||||
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;
|
||||
m_regdata[0x04] = newval >> 8;
|
||||
m_regdata[0x03] = (newval >> 16) & 0x3f;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user