From db4b71fc9a5054c816b8e6a4387d80a2039bc0f2 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sun, 5 Apr 2020 22:57:53 -0400 Subject: [PATCH] Adds correct LSFR, something of OPLL -> OPL2 logic. --- Components/OPL2/OPL2.cpp | 111 ++++++++++++++++++++++++++++++++++++--- Components/OPL2/OPL2.hpp | 12 +++++ 2 files changed, 117 insertions(+), 6 deletions(-) diff --git a/Components/OPL2/OPL2.cpp b/Components/OPL2/OPL2.cpp index 94bef2fa2..ff3ebc808 100644 --- a/Components/OPL2/OPL2.cpp +++ b/Components/OPL2/OPL2.cpp @@ -86,11 +86,9 @@ constexpr uint8_t percussion_patch_set[] = { using namespace Yamaha; -OPL2::OPL2(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue): task_queue_(task_queue) { - (void)opll_patch_set; - (void)vrc7_patch_set; - (void)percussion_patch_set; +// MARK: - Construction +OPL2::OPL2(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue): task_queue_(task_queue), personality_(personality) { // Populate the exponential and log-sine tables; formulas here taken from Matthew Gambrell // and Olli Niemitalo's decapping and reverse-engineering of the OPL2. for(int c = 0; c < 256; ++c) { @@ -103,8 +101,13 @@ OPL2::OPL2(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_q ) ); } + + // TODO: use this when in OPLL percussion mode. + (void)percussion_patch_set; } +// MARK: - Audio Generation + bool OPL2::is_zero_level() { return true; } @@ -127,7 +130,7 @@ void OPL2::get_samples(std::size_t number_of_samples, std::int16_t *target) { 7 13 16 8 14 17 - In percussion mode, only channels 0–5 are use as melodic, with 6 7 and 8 being + In percussion mode, only channels 0–5 are use as melodic, with 6, 7 and 8 being replaced by: Bass drum, using operators 12 and 15; @@ -142,9 +145,18 @@ void OPL2::set_sample_volume_range(std::int16_t range) { // TODO. } +// MARK: - Software Interface + void OPL2::write(uint16_t address, uint8_t value) { if(address & 1) { - set_opl2_register(selected_register_, value); + switch(personality_) { + case Personality::OPL2: + set_opl2_register(selected_register_, value); + break; + default: + set_opll_register(selected_register_, value); + break; + } } else { selected_register_ = value; } @@ -158,6 +170,92 @@ uint8_t OPL2::read(uint16_t address) { return 0xff; } +void OPL2::set_opll_register(uint8_t location, uint8_t value) { + if(location < 8) { + opll_custom_instrument_[location] = value; + // Repush this instrument for any channels it's presently selected on. + for(int c = 0; c < 9; ++c) { + if(!(instrument_selections_[c] >> 4)) { + set_opll_instrument(uint8_t(c), 0, instrument_selections_[c] & 0xf); + } + } + return; + } + + if(location >= 0x30 && location <= 0x38) { + instrument_selections_[location - 0x30] = value; + set_opll_instrument(location - 0x30, value >> 4, value & 0xf); + return; + } + + if(location == 0xe) { + set_opl2_register(0xbd, value & 0x3f); + return; + } + + if(location >= 0x10 && location <= 0x18) { + set_opl2_register(location - 0x10 + 0xa0, value); + return; + } + + if(location >= 0x20 && location <= 0x28) { + const auto index = location = 0x20; + operators_[index].hold_sustain_level = value & 0x20; + + // Only the bottom bit contributes to the frequency on an OPLL; on an OPL2 it's the two + // bottom bits (and hold-sustain isn't set in the same register). + set_opl2_register(index + 0xb0, uint8_t((value & 1) | ((value & 0xfe) << 1))); + return; + } +} + +void OPL2::set_opll_instrument(uint8_t target, uint8_t source_instrument, uint8_t volume) { + const uint8_t *source; + if(!source_instrument) { + source = opll_custom_instrument_; + } else { + --source_instrument; + source = + (source_instrument * 8) + + (personality_ == Personality::OPLL ? opll_patch_set : vrc7_patch_set); + } + + constexpr uint8_t offsets[9][2] = { + {0x00, 0x03}, + {0x01, 0x04}, + {0x02, 0x05}, + + {0x08, 0x0b}, + {0x09, 0x0c}, + {0x0a, 0x0d}, + + {0x10, 0x13}, + {0x11, 0x14}, + {0x12, 0x15}, + }; + const auto carrier = offsets[target][0]; + const auto modulator = offsets[target][1]; + + // Set waveforms — only sine and halfsine are available. + set_opl2_register(0xe0 + carrier, (source[3] & 0x10) ? 1 : 0); + set_opl2_register(0xe0 + modulator, (source[3] & 0x08) ? 1 : 0); + + // Volume on the OPLL is four bit; on the OPL2 it's six. Pair that with key scale level. + set_opl2_register(0xe0 + carrier, uint8_t((source[3] & 0xc0) | (volume << 2))); + + // Set feedback level, which is per channel. And always set frequency modulation. + set_opl2_register(0xc0 + target, uint8_t((source[3] & 0x7) << 1)); + + // The other values don't require any mapping. + set_opl2_register(0x20 + carrier, source[0]); + set_opl2_register(0x20 + modulator, source[1]); + set_opl2_register(0x40 + carrier, source[2]); + set_opl2_register(0x60 + carrier, source[4]); + set_opl2_register(0x60 + modulator, source[5]); + set_opl2_register(0x80 + carrier, source[6]); + set_opl2_register(0x80 + modulator, source[7]); +} + void OPL2::set_opl2_register(uint8_t location, uint8_t value) { printf("OPL2 write: %02x to %d\n", value, selected_register_); @@ -263,6 +361,7 @@ void OPL2::set_opl2_register(uint8_t location, uint8_t value) { // // Modal modifications. // + switch(location) { case 0x01: waveform_enable_ = value & 0x20; break; case 0x08: diff --git a/Components/OPL2/OPL2.hpp b/Components/OPL2/OPL2.hpp index 65647e258..386f3bd58 100644 --- a/Components/OPL2/OPL2.hpp +++ b/Components/OPL2/OPL2.hpp @@ -11,6 +11,7 @@ #include "../../Outputs/Speaker/Implementation/SampleSource.hpp" #include "../../Concurrency/AsyncTaskQueue.hpp" +#include "../../Numeric/LFSR.hpp" namespace Yamaha { @@ -43,12 +44,16 @@ class OPL2: public ::Outputs::Speaker::SampleSource { private: Concurrency::DeferringAsyncTaskQueue &task_queue_; + const Personality personality_; int exponential_[256]; int log_sin_[256]; uint8_t selected_register_ = 0; + // Register write routines. void set_opl2_register(uint8_t location, uint8_t value); + void set_opll_register(uint8_t location, uint8_t value); + void set_opll_instrument(uint8_t target, uint8_t source, uint8_t volume); // Asynchronous properties, valid only on the audio thread. struct Operator { @@ -78,9 +83,16 @@ class OPL2: public ::Outputs::Speaker::SampleSource { uint8_t csm_keyboard_split_; bool waveform_enable_; + // This is the correct LSFR per forums.submarine.org.uk. + Numeric::LFSR noise_source_; + // Synchronous properties, valid only on the emulation thread. uint8_t timers_[2]; uint8_t timer_control_; + + // OPLL-specific storage. + uint8_t opll_custom_instrument_[8]; + uint8_t instrument_selections_[9]; }; }