From ac96627377b2eb960d02557dc956401ae157f280 Mon Sep 17 00:00:00 2001 From: Aaron Giles Date: Sun, 23 May 2021 13:24:36 -0700 Subject: [PATCH] ymfm: Some progress on OPZ. Some instruments in the TX81Z sound kind of ok now. --- 3rdparty/ymfm/src/ymfm_opz.cpp | 150 ++++++++++++++++++++++----------- 3rdparty/ymfm/src/ymfm_opz.h | 26 +++--- 2 files changed, 115 insertions(+), 61 deletions(-) diff --git a/3rdparty/ymfm/src/ymfm_opz.cpp b/3rdparty/ymfm/src/ymfm_opz.cpp index ae814e45b13..fc6a3ab44c4 100644 --- a/3rdparty/ymfm/src/ymfm_opz.cpp +++ b/3rdparty/ymfm/src/ymfm_opz.cpp @@ -31,6 +31,8 @@ #include "ymfm_opz.h" #include "ymfm_fm.ipp" +#define TEMPORARY_DEBUG_PRINTS (0) + // // OPZ (aka YM2414) // @@ -102,10 +104,15 @@ opz_registers::opz_registers() : for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); + // we only have the diagrams to judge from, but suspecting waveform 1 (and + // derived waveforms) are sin^2, based on OPX description of similar wave- + // forms; since our sin table is logarithmic, this ends up just being + // 2*existing value uint16_t zeroval = m_waveform[0][0]; for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) - m_waveform[1][index] = (zeroval - m_waveform[0][(index & 0x1ff) ^ 0x100]) | (bitfield(index, 9) << 15); + m_waveform[1][index] = std::min(2 * (m_waveform[0][index] & 0x7fff), zeroval) | (bitfield(index, 9) << 15); + // remaining waveforms are just derivations of the 2 main ones for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) { m_waveform[2][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index]; @@ -209,17 +216,17 @@ bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint3 assert(index < REGISTERS); // special mappings: - // 0x16 -> 0x148 if bit 7 is set - // 0x19 -> 0x149 if bit 7 is set - // 0x38..0x3F -> 0x140..0x147 if bit 7 is set + // 0x16 -> 0x188 if bit 7 is set + // 0x19 -> 0x189 if bit 7 is set + // 0x38..0x3F -> 0x180..0x187 if bit 7 is set // 0x40..0x5F -> 0x100..0x11F if bit 7 is set // 0xC0..0xDF -> 0x120..0x13F if bit 5 is set if (index == 0x17 && bitfield(data, 7) != 0) - m_regdata[0x148] = data; + m_regdata[0x188] = data; else if (index == 0x19 && bitfield(data, 7) != 0) - m_regdata[0x149] = data; + m_regdata[0x189] = data; else if ((index & 0xf8) == 0x38 && bitfield(data, 7) != 0) - m_regdata[0x140 + (index & 7)] = data; + m_regdata[0x180 + (index & 7)] = data; else if ((index & 0xe0) == 0x40 && bitfield(data, 7) != 0) m_regdata[0x100 + (index & 0x1f)] = data; else if ((index & 0xe0) == 0xc0 && bitfield(data, 5) != 0) @@ -227,16 +234,60 @@ bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint3 else if (index < 0x100) m_regdata[index] = data; + // preset writes restore some values from a preset memory; not sure + // how this really works but the TX81Z will overwrite the sustain level/ + // release rate register and the envelope shift/reverb rate register to + // dampen sound, then write the preset number to register 8 to restore them + if (index == 0x08) + { + int chan = bitfield(data, 0, 3); + if (TEMPORARY_DEBUG_PRINTS) + printf("Loading preset %d\n", chan); + m_regdata[0xe0 + chan + 0] = m_regdata[0x140 + chan + 0]; + m_regdata[0xe0 + chan + 8] = m_regdata[0x140 + chan + 8]; + m_regdata[0xe0 + chan + 16] = m_regdata[0x140 + chan + 16]; + m_regdata[0xe0 + chan + 24] = m_regdata[0x140 + chan + 24]; + m_regdata[0x120 + chan + 0] = m_regdata[0x160 + chan + 0]; + m_regdata[0x120 + chan + 8] = m_regdata[0x160 + chan + 8]; + m_regdata[0x120 + chan + 16] = m_regdata[0x160 + chan + 16]; + m_regdata[0x120 + chan + 24] = m_regdata[0x160 + chan + 24]; + } + + // store the presets under some unknown condition; the pattern of writes + // when setting a new preset is: + // + // 08 (0-7), 80-9F, A0-BF, C0-DF, C0-DF (alt), 20-27, 40-5F, 40-5F (alt), + // C0-DF (alt -- again?), 38-3F, 1B, 18, E0-FF + // + // So it writes 0-7 to 08 to either reset all presets or to indicate + // that we're going to be loading them. Immediately after all the writes + // above, the very next write will be temporary values to blow away the + // values loaded into E0-FF, so somehow it also knows that anything after + // that point is not part of the preset. + // + // For now, try using the 40-5F (alt) writes as flags that presets are + // being loaded until the E0-FF writes happen. + bool is_setting_preset = (bitfield(m_regdata[0x100 + (index & 0x1f)], 7) != 0); + if (is_setting_preset) + { + if ((index & 0xe0) == 0xe0) + { + m_regdata[0x140 + (index & 0x1f)] = data; + m_regdata[0x100 + (index & 0x1f)] &= 0x7f; + } + else if ((index & 0xe0) == 0xc0 && bitfield(data, 5) != 0) + m_regdata[0x160 + (index & 0x1f)] = data; + } + // handle writes to the key on index if ((index & 0xf8) == 0x20 && bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)) { channel = bitfield(index, 0, 3); - opmask = ch_key_on(channel) ? 0xf : 0; //bitfield(data, 3, 4); + opmask = ch_key_on(channel) ? 0xf : 0; // according to the TX81Z manual, the sync option causes the LFOs - // to reset at each note on; assume that's when operator 4 gets - // a note on signal - if (bitfield(opmask, 3) != 0) + // to reset at each note on + if (opmask != 0) { if (lfo_sync()) m_lfo_counter[0] = 0; @@ -650,51 +701,54 @@ void ym2414::write_data(uint8_t data) { // write the FM register m_fm.write(m_address, data); - switch (m_address & 0xe0) + if (TEMPORARY_DEBUG_PRINTS) { - case 0x00: - printf("CTL %02X = %02X\n", m_address, data); - break; + switch (m_address & 0xe0) + { + case 0x00: + printf("CTL %02X = %02X\n", m_address, data); + break; - case 0x20: - switch (m_address & 0xf8) - { - case 0x20: printf("R/FBL/ALG %d = %02X\n", m_address & 7, data); break; - case 0x28: printf("KC %d = %02X\n", m_address & 7, data); break; - case 0x30: printf("KF/M %d = %02X\n", m_address & 7, data); break; - case 0x38: printf("PMS/AMS %d = %02X\n", m_address & 7, data); break; - } - break; + case 0x20: + switch (m_address & 0xf8) + { + case 0x20: printf("R/FBL/ALG %d = %02X\n", m_address & 7, data); break; + case 0x28: printf("KC %d = %02X\n", m_address & 7, data); break; + case 0x30: printf("KF/M %d = %02X\n", m_address & 7, data); break; + case 0x38: printf("PMS/AMS %d = %02X\n", m_address & 7, data); break; + } + break; - case 0x40: - if (bitfield(data, 7) == 0) - printf("DT1/MUL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); - else - printf("OW/FINE %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); - break; + case 0x40: + if (bitfield(data, 7) == 0) + printf("DT1/MUL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + else + printf("OW/FINE %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; - case 0x60: - printf("TL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); - break; + case 0x60: + printf("TL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; - case 0x80: - printf("KRS/FIX/AR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); - break; + case 0x80: + printf("KRS/FIX/AR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; - case 0xa0: - printf("A/D1R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); - break; + case 0xa0: + printf("A/D1R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; - case 0xc0: - if (bitfield(data, 5) == 0) - printf("DT2/D2R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); - else - printf("EGS/REV %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); - break; + case 0xc0: + if (bitfield(data, 5) == 0) + printf("DT2/D2R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + else + printf("EGS/REV %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; - case 0xf0: - printf("D1L/RR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); - break; + case 0xe0: + printf("D1L/RR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + } } // special cases diff --git a/3rdparty/ymfm/src/ymfm_opz.h b/3rdparty/ymfm/src/ymfm_opz.h index 3a6bd5733cd..c38dd246ab9 100644 --- a/3rdparty/ymfm/src/ymfm_opz.h +++ b/3rdparty/ymfm/src/ymfm_opz.h @@ -49,11 +49,7 @@ namespace ymfm // OPZ register map: // // System-wide registers: -// 08 -x------ Key on/off operator 4 -// --x----- Key on/off operator 3 -// ---x---- Key on/off operator 2 -// ----x--- Key on/off operator 1 -// -----xxx Channel select +// 08 -----xxx Load preset (not sure how it gets saved) // 0F x------- Noise enable // ---xxxxx Noise frequency // 10 xxxxxxxx Timer A value (upper 8 bits) @@ -117,10 +113,14 @@ namespace ymfm // ----xxxx Fine? (0-15) // 120-13F xx------ Envelope generator shift (0-3) // -----xxx Reverb rate (0-7) -// 140-147 -xxx---- LFO #2 PM sensitivity +// 140-15F xxxx---- Preset sustain level (0-15) +// ----xxxx Preset release rate (0-15) +// 160-17F xx------ Envelope generator shift (0-3) +// -----xxx Reverb rate (0-7) +// 180-187 -xxx---- LFO #2 PM sensitivity // ---- xxx LFO #2 AM shift -// 148 -xxxxxxx LFO #2 PM depth -// 149 -xxxxxxx LFO PM depth +// 188 -xxxxxxx LFO #2 PM depth +// 189 -xxxxxxx LFO PM depth // class opz_registers : public fm_registers_base @@ -135,7 +135,7 @@ public: static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; static constexpr uint32_t OPERATORS = CHANNELS * 4; static constexpr uint32_t WAVEFORMS = 8; - static constexpr uint32_t REGISTERS = 0x150; + static constexpr uint32_t REGISTERS = 0x190; static constexpr uint32_t DEFAULT_PRESCALE = 2; static constexpr uint32_t EG_CLOCK_DIVIDER = 3; static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; @@ -205,12 +205,12 @@ public: uint32_t enable_timer_a() const { return byte(0x14, 2, 1); } uint32_t load_timer_b() const { return byte(0x14, 1, 1); } uint32_t load_timer_a() const { return byte(0x14, 0, 1); } - uint32_t lfo2_pm_depth() const { return byte(0x148, 0, 7); } // fake + uint32_t lfo2_pm_depth() const { return byte(0x188, 0, 7); } // fake uint32_t lfo2_rate() const { return byte(0x16, 0, 8); } uint32_t lfo2_am_depth() const { return byte(0x17, 0, 7); } uint32_t lfo_rate() const { return byte(0x18, 0, 8); } uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); } - uint32_t lfo_pm_depth() const { return byte(0x149, 0, 7); } // fake + uint32_t lfo_pm_depth() const { return byte(0x189, 0, 7); } // fake uint32_t output_bits() const { return byte(0x1b, 6, 2); } uint32_t lfo2_sync() const { return byte(0x1b, 5, 1); } uint32_t lfo_sync() const { return byte(0x1b, 4, 1); } @@ -230,8 +230,8 @@ public: uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); } uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); } uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); } - uint32_t ch_lfo2_pm_sens(uint32_t choffs) const { return byte(0x140, 4, 3, choffs); } // fake - uint32_t ch_lfo2_am_sens(uint32_t choffs) const { return byte(0x140, 0, 2, choffs); } // fake + uint32_t ch_lfo2_pm_sens(uint32_t choffs) const { return byte(0x180, 4, 3, choffs); } // fake + uint32_t ch_lfo2_am_sens(uint32_t choffs) const { return byte(0x180, 0, 2, choffs); } // fake // per-operator registers uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); }