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