From 32fd1897d083f58d5e4949ab6bc70690f8bdcdb1 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 20 Apr 2020 23:17:29 -0400 Subject: [PATCH] Via a unit test, confirms and fixes relative volumes of OPLL channels. Also rejigs responsibility for scaling to emulator-standard volume. --- Components/OPL2/Implementation/Channel.cpp | 4 +- Components/OPL2/Implementation/Operator.cpp | 2 +- Components/OPL2/OPL2.cpp | 4 +- .../xcschemes/Clock Signal.xcscheme | 2 +- OSBindings/Mac/Clock SignalTests/OPLTests.mm | 55 ++++++++++++++++++- 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/Components/OPL2/Implementation/Channel.cpp b/Components/OPL2/Implementation/Channel.cpp index 20b141f63..aafe43dfb 100644 --- a/Components/OPL2/Implementation/Channel.cpp +++ b/Components/OPL2/Implementation/Channel.cpp @@ -40,12 +40,12 @@ int Channel::update(Operator *modulator, Operator *carrier, OperatorOverrides *m const auto modulator_level = modulator_state_.level(); carrier->update(carrier_state_, key_on_, period_ << frequency_shift_, octave_, modulator_level, carrier_overrides); - return carrier_state_.level() << 2; + return carrier_state_.level(); } else { // Get modulator and carrier levels separately, return their sum. modulator->update(modulator_state_, key_on_, period_ << frequency_shift_, octave_, 0, modulator_overrides); carrier->update(carrier_state_, key_on_, period_ << frequency_shift_, octave_, 0, carrier_overrides); - return (modulator_state_.level() + carrier_state_.level()) << 1; + return (modulator_state_.level() + carrier_state_.level()); } } diff --git a/Components/OPL2/Implementation/Operator.cpp b/Components/OPL2/Implementation/Operator.cpp index 6dccc721b..6eb72a6a9 100644 --- a/Components/OPL2/Implementation/Operator.cpp +++ b/Components/OPL2/Implementation/Operator.cpp @@ -178,7 +178,7 @@ void Operator::update(OperatorState &state, bool key_on, int channel_period, int // Overrides here represent per-channel volume on an OPLL. The bits are defined to represent // attenuations of 24db to 3db; the main envelope generator is stated to have a resolution of // 0.325db (which I've assumed is supposed to say 0.375db). - state.attenuation.log += state.adsr_attenuation_ + (overrides->attenuation << 4); + state.attenuation.log += (state.adsr_attenuation_ << 3) + (overrides->attenuation << 7); } else { // Overrides here represent per-channel volume on an OPLL. The bits are defined to represent // attenuations of 24db to 0.75db. diff --git a/Components/OPL2/OPL2.cpp b/Components/OPL2/OPL2.cpp index 692a1e229..a5a92d2ce 100644 --- a/Components/OPL2/OPL2.cpp +++ b/Components/OPL2/OPL2.cpp @@ -182,7 +182,7 @@ void OPLL::setup_fixed_instrument(int number, const uint8_t *data) { void OPLL::update_all_chanels() { // Channels that are updated for melodic output regardless. for(int c = 0; c < 6; ++ c) { - channels_[c].level = (channels_[c].update() * total_volume_) >> 14; + channels_[c].level = (channels_[c].update() * total_volume_) >> 11; } if(depth_rhythm_control_ & 0x20) { @@ -190,7 +190,7 @@ void OPLL::update_all_chanels() { } else { // All melody, all the time. for(int c = 6; c < 9; ++ c) { - channels_[c].level = (channels_[c].update() * total_volume_) >> 14; + channels_[c].level = (channels_[c].update() * total_volume_) >> 11; } } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme index 63be2c037..a99eb0a5d 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal.xcscheme @@ -67,7 +67,7 @@ > 2; XCTAssertLessThanOrEqual(abs(generated - known), 10, "FM synthesis varies by more than 10 at sample %d of attenuation %d", c, attenuation); } } @@ -87,6 +87,59 @@ } } +// MARK: - Level tests + +- (int)maxLevelForOPLLAttenuation:(int)attenuation { + Yamaha::OPL::Operator modulator, carrier; + Yamaha::OPL::Channel channel; + Yamaha::OPL::OperatorOverrides overrides; + + // Reach maximum volume immediately, and hold it during sustain. + carrier.set_sustain_release(0x0f); + carrier.set_attack_decay(0xf0); + + // Use FM synthesis. + channel.set_feedback_mode(1); + + // Set hold sustain level. + carrier.set_am_vibrato_hold_sustain_ksr_multiple(0x20); + + // Disable the modulator. + modulator.set_scaling_output(0x3f); + + // Set a non-zero frequency, set key on. + channel.set_frequency_low(0x40); + channel.set_9bit_frequency_octave_key_on(0x10); + + // Get the maximum output for this volume level. + overrides.attenuation = attenuation; + overrides.use_sustain_level = true; + + int max = 0; + for(int c = 0; c < 16384; ++c) { + const int level = channel.update(&modulator, &carrier, nullptr, &overrides); + if(level > max) max = level; + } + + return max; +} + +- (void)testOPLLVolumeLevels { + // Get maximum output levels for all channels. + int maxLevels[16]; + for(int c = 0; c < 16; ++c) { + maxLevels[c] = [self maxLevelForOPLLAttenuation:c]; + } + + // Figure out a divider to turn that into the sampled range. + const double multiplier = 255.0 / double(maxLevels[0]); + const double expectedLevels[16] = {255.0, 181.0, 127.0, 90.0, 63.0, 45.0, 31.0, 22.0, 15.0, 11.0, 7.0, 5.0, 3.0, 2.0, 1.0, 1.0}; + for(int c = 0; c < 16; ++c) { + const double empiricalLevel = double(maxLevels[c]) * multiplier; + XCTAssertLessThanOrEqual(fabs(round(empiricalLevel) - expectedLevels[c]), 2.0, "Fixed attenuation %d was %0.0f; should have been %0.0f", c, empiricalLevel, expectedLevels[c]); + } +} + // MARK: - ADSR tests - (void)testADSR {