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:
Aaron Giles 2022-11-24 07:56:33 -08:00 committed by GitHub
parent 09e5a49fd8
commit c778f5406b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 167 additions and 19 deletions

View file

@ -37,3 +37,4 @@
# VS Code stuff
.vs/
reference/

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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
};
}

View file

@ -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
}
}
}

View file

@ -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;
}

View file

@ -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)

View file

@ -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;
}