From 7e042a1c67cf8b487022a9d8f92a6bafeaeaa8bc Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Wed, 24 Nov 2021 17:13:46 -0500 Subject: [PATCH] Fix another octave-per-channel mode thing In the case where e RETUNE_AT_MIDI with a long scale the voice stealing compairson used different indices than the voice souding algorithm, so we compared in untuned space accidentally. Closes #5469 --- src/common/dsp/SurgeVoice.cpp | 15 +++- src/common/dsp/SurgeVoice.h | 3 +- src/surge-testrunner/UnitTestsTUN.cpp | 99 +++++++++++++++------------ 3 files changed, 71 insertions(+), 46 deletions(-) diff --git a/src/common/dsp/SurgeVoice.cpp b/src/common/dsp/SurgeVoice.cpp index 1bd408ecf73..3bbcf9b5f45 100644 --- a/src/common/dsp/SurgeVoice.cpp +++ b/src/common/dsp/SurgeVoice.cpp @@ -71,16 +71,27 @@ float SurgeVoiceState::getPitch(SurgeStorage *storage) res = (1.f - frac) * b0 + frac * b1; } - res = SurgeVoice::channelKeyEquvialent(res, channel, storage); + res = SurgeVoice::channelKeyEquvialent(res, channel, storage, false); return res; } -float SurgeVoice::channelKeyEquvialent(float key, int channel, SurgeStorage *storage) +float SurgeVoice::channelKeyEquvialent(float key, int channel, SurgeStorage *storage, + bool remapKeyForTuning) { float res = key; if (storage->mapChannelToOctave) { + if (remapKeyForTuning && !storage->isStandardTuning && + storage->tuningApplicationMode == SurgeStorage::RETUNE_MIDI_ONLY) + { + auto idx = (int)floor(res); + float frac = res - idx; // frac is 0 means use idx; frac is 1 means use idx+1 + float b0 = storage->currentTuning.logScaledFrequencyForMidiNote(idx) * 12; + float b1 = storage->currentTuning.logScaledFrequencyForMidiNote(idx + 1) * 12; + res = (1.f - frac) * b0 + frac * b1; + } + float shift; if (channel > 7) { diff --git a/src/common/dsp/SurgeVoice.h b/src/common/dsp/SurgeVoice.h index 1a6458da42d..a0c1fd90ea7 100644 --- a/src/common/dsp/SurgeVoice.h +++ b/src/common/dsp/SurgeVoice.h @@ -133,7 +133,8 @@ class alignas(16) SurgeVoice } } - static float channelKeyEquvialent(float key, int channel, SurgeStorage *storage); + static float channelKeyEquvialent(float key, int channel, SurgeStorage *storage, + bool remapKeyForTuning = true); private: template void calc_ctrldata(QuadFilterChainState *, int); diff --git a/src/surge-testrunner/UnitTestsTUN.cpp b/src/surge-testrunner/UnitTestsTUN.cpp index c977f13d6af..47e02073c21 100644 --- a/src/surge-testrunner/UnitTestsTUN.cpp +++ b/src/surge-testrunner/UnitTestsTUN.cpp @@ -1422,64 +1422,77 @@ TEST_CASE("Octave Per Channel and Porta", "[tun]") {{1, 3, 1}}, 31}, {"Note on off 1 ED2-31", ALWAYS_HIGHEST, 559.863, true, {{1, 3, 1}, {10, 3, 1}, {10, 3, 0 }}, 31}, + + { "Then in conjunction ED2-31", ALWAYS_HIGHEST, 611.6505, true, + {{26, 2, 1}, {5, 3, 1}}, 31}, + { "ED2-31 10/3 to 23/2", ALWAYS_HIGHEST, 684.203, true, + {{10, 3, 1}, {23, 2, 1}}, 31}, + { "ED2-31 10/3 to 18/2", ALWAYS_HIGHEST, 684.203, true, + {{10, 3, 1}, {18, 2, 1}}, 31}, + }; // clang-format on - for (auto mode : {pm_mono, pm_mono_fp, pm_mono_st, pm_mono_st_fp}) + for (auto tuningstyle : {SurgeStorage::RETUNE_ALL, SurgeStorage::RETUNE_MIDI_ONLY}) { - for (const auto &c : cases) + for (auto mode : {pm_mono, pm_mono_fp, pm_mono_st, pm_mono_st_fp}) { - DYNAMIC_SECTION("Mode " << mode << " : Pri : " << c.pri << " : Case : " << c.name - << " : Scale : ED2-" << c.scaleLen - << (c.should ? "" : " - SKIPPING")) + for (const auto &c : cases) { - auto surge = surgeOnSine(); - surge->storage.getPatch().scene[0].monoVoicePriorityMode = c.pri; - surge->storage.getPatch().scene[0].polymode.val.i = mode; - surge->storage.mapChannelToOctave = true; - surge->storage.setTuningApplicationMode(SurgeStorage::RETUNE_MIDI_ONLY); - surge->storage.retuneToScale(Tunings::evenDivisionOfSpanByM(2, c.scaleLen)); - - int len = BLOCK_SIZE * 20; - int idx = 0; - auto events = hs::playerEvents_t(); - for (const auto nt : c.notes) + DYNAMIC_SECTION("Mode " << mode << " : Pri : " << c.pri << " : Case : " << c.name + << " : Scale : ED2-" << c.scaleLen + << " TuningApplication : " << tuningstyle + << (c.should ? "" : " - SKIPPING")) { + auto surge = surgeOnSine(); + surge->storage.getPatch().scene[0].monoVoicePriorityMode = c.pri; + surge->storage.getPatch().scene[0].polymode.val.i = mode; + surge->storage.mapChannelToOctave = true; + surge->storage.tuningApplicationMode = tuningstyle; + surge->storage.setTuningApplicationMode(SurgeStorage::RETUNE_MIDI_ONLY); + surge->storage.retuneToScale(Tunings::evenDivisionOfSpanByM(2, c.scaleLen)); + + int len = BLOCK_SIZE * 20; + int idx = 0; + auto events = hs::playerEvents_t(); + for (const auto nt : c.notes) + { + auto on = hs::Event(); + on.type = nt.on ? hs::Event::NOTE_ON : hs::Event::NOTE_OFF; + on.channel = nt.c; + on.data1 = nt.n; + on.data2 = 100; + on.atSample = len * idx; + events.push_back(on); + idx++; + } + auto on = hs::Event(); - on.type = nt.on ? hs::Event::NOTE_ON : hs::Event::NOTE_OFF; - on.channel = nt.c; - on.data1 = nt.n; - on.data2 = 100; + on.type = hs::Event::NO_EVENT; on.atSample = len * idx; events.push_back(on); - idx++; - } - auto on = hs::Event(); - on.type = hs::Event::NO_EVENT; - on.atSample = len * idx; - events.push_back(on); + float *buffer; + int nS, nC; + hs::playAsConfigured(surge, events, &buffer, &nS, &nC); + delete[] buffer; - float *buffer; - int nS, nC; - hs::playAsConfigured(surge, events, &buffer, &nS, &nC); - delete[] buffer; - - events.clear(); - on.atSample = len * 3; - events.push_back(on); - hs::playAsConfigured(surge, events, &buffer, &nS, &nC); + events.clear(); + on.atSample = len * 3; + events.push_back(on); + hs::playAsConfigured(surge, events, &buffer, &nS, &nC); - int nSTrim = (int)(nS / 2 * 0.8); - int start = (int)(nS / 2 * 0.05); - auto freq = frequencyFromData(buffer, nS, nC, 0, start, nSTrim); + int nSTrim = (int)(nS / 2 * 0.8); + int start = (int)(nS / 2 * 0.05); + auto freq = frequencyFromData(buffer, nS, nC, 0, start, nSTrim); - delete[] buffer; + delete[] buffer; - if (c.should) - REQUIRE(freq == Approx(c.res).margin(1)); - else - REQUIRE_FALSE(freq == Approx(c.res).margin(1)); + if (c.should) + REQUIRE(freq == Approx(c.res).margin(1)); + else + REQUIRE_FALSE(freq == Approx(c.res).margin(1)); + } } } }