diff --git a/3rdparty/ymfm/.gitignore b/3rdparty/ymfm/.gitignore
index 69111d94505..c1ef5b25572 100644
--- a/3rdparty/ymfm/.gitignore
+++ b/3rdparty/ymfm/.gitignore
@@ -37,3 +37,4 @@
# VS Code stuff
.vs/
+reference/
diff --git a/3rdparty/ymfm/README.md b/3rdparty/ymfm/README.md
index c3cb61eb995..29f0d87dca9 100644
--- a/3rdparty/ymfm/README.md
+++ b/3rdparty/ymfm/README.md
@@ -1,5 +1,9 @@
# ymfm
+
+

+
+
[ymfm](https://github.com/aaronsgiles/ymfm) is a collection of BSD-licensed Yamaha FM sound cores (OPM, OPN, OPL, and others), written by [Aaron Giles](https://aarongiles.com)
## Supported environments
diff --git a/3rdparty/ymfm/examples/vgmrender/vgmrender.cpp b/3rdparty/ymfm/examples/vgmrender/vgmrender.cpp
index cc1f3a13bae..14b5bda29d6 100644
--- a/3rdparty/ymfm/examples/vgmrender/vgmrender.cpp
+++ b/3rdparty/ymfm/examples/vgmrender/vgmrender.cpp
@@ -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 active_chips;
+std::vector> 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(clockval, type, (numchips == 2) ? name : chipname));
+ active_chips.push_back(std::make_unique>(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 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 &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 &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;
}
diff --git a/3rdparty/ymfm/src/ymfm.h b/3rdparty/ymfm/src/ymfm.h
index 7b98f849907..47ff90072cc 100644
--- a/3rdparty/ymfm/src/ymfm.h
+++ b/3rdparty/ymfm/src/ymfm.h
@@ -40,6 +40,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -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
+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
+ void add(ymfm_output 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
+ void add(ymfm_output output, ymfm_output 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 m_buffer;
+};
+
+
// ======================> ymfm_saved_state
// this class contains a managed vector of bytes that is used to save and
diff --git a/3rdparty/ymfm/src/ymfm_fm.h b/3rdparty/ymfm/src/ymfm_fm.h
index 1b24cc74518..81795f8fe43 100644
--- a/3rdparty/ymfm/src/ymfm_fm.h
+++ b/3rdparty/ymfm/src/ymfm_fm.h
@@ -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> m_channel[CHANNELS]; // channel pointers
std::unique_ptr> m_operator[OPERATORS]; // operator pointers
+#if (YMFM_DEBUG_LOG_WAVFILES)
+ mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; // for debugging
+#endif
};
}
diff --git a/3rdparty/ymfm/src/ymfm_fm.ipp b/3rdparty/ymfm/src/ymfm_fm.ipp
index 848a1181db1..78e3a62d9c8 100644
--- a/3rdparty/ymfm/src/ymfm_fm.ipp
+++ b/3rdparty/ymfm/src/ymfm_fm.ipp
@@ -1185,6 +1185,7 @@ fm_engine_base::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::fm_engine_base(ymfm_interface &intf) :
for (uint32_t opnum = 0; opnum < OPERATORS; opnum++)
m_operator[opnum] = std::make_unique>(*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::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::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::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::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
}
}
}
diff --git a/3rdparty/ymfm/src/ymfm_opl.cpp b/3rdparty/ymfm/src/ymfm_opl.cpp
index 86215c5b27e..f3f62955ecb 100644
--- a/3rdparty/ymfm/src/ymfm_opl.cpp
+++ b/3rdparty/ymfm/src/ymfm_opl.cpp
@@ -100,6 +100,11 @@ opl_registers_base::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;
}
diff --git a/3rdparty/ymfm/src/ymfm_opn.cpp b/3rdparty/ymfm/src/ymfm_opn.cpp
index 6022dbb90c2..053ad97701c 100644
--- a/3rdparty/ymfm/src/ymfm_opn.cpp
+++ b/3rdparty/ymfm/src/ymfm_opn.cpp
@@ -205,7 +205,12 @@ int32_t opn_registers_base::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::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)
diff --git a/3rdparty/ymfm/src/ymfm_pcm.h b/3rdparty/ymfm/src/ymfm_pcm.h
index 2022a69b981..b471fa611a6 100644
--- a/3rdparty/ymfm/src/ymfm_pcm.h
+++ b/3rdparty/ymfm/src/ymfm_pcm.h
@@ -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;
}