From ba498cea6ad3aa5b6ef579b45480564caf60f708 Mon Sep 17 00:00:00 2001 From: Paul Date: Sat, 30 Sep 2023 13:21:18 -0400 Subject: [PATCH] Adjust release-by-note-id for ST modes (#7228) In ST modes release-by-note-id didn't work properly with recycled note ids, and our tests didn't test this. Now it does work it seems and our tests are a bit more comprehensive but we really need to expand the coverage through that test set in UnitTestsVOICE --- src/common/SurgeSynthesizer.cpp | 9 + src/common/dsp/effects/AudioInputEffect.cpp | 21 ++ src/common/dsp/effects/AudioInputEffect.h | 38 ++- src/surge-testrunner/CMakeLists.txt | 1 + src/surge-testrunner/UnitTestsMIDI.cpp | 90 ------ src/surge-testrunner/UnitTestsVOICE.cpp | 295 ++++++++++++++++++++ src/surge-xt/osc/OpenSoundControl.h | 4 +- 7 files changed, 352 insertions(+), 106 deletions(-) create mode 100644 src/surge-testrunner/UnitTestsVOICE.cpp diff --git a/src/common/SurgeSynthesizer.cpp b/src/common/SurgeSynthesizer.cpp index dfed8b0089b..203c8c95b44 100644 --- a/src/common/SurgeSynthesizer.cpp +++ b/src/common/SurgeSynthesizer.cpp @@ -1762,11 +1762,20 @@ void SurgeSynthesizer::releaseNoteByHostNoteID(int32_t host_noteid, char velocit for (int s = 0; s < n_scenes; ++s) { + auto &ptS = storage.getPatch().scene[s]; + + // In the single trigger mode we can recycle ids + // so we need to portentialy kill the originating + // note also + bool recycleNoteID = + ptS.polymode.val.i == pm_mono_st_fp || ptS.polymode.val.i == pm_mono_st; for (auto v : voices[s]) { if (v->host_note_id == host_noteid) { done[v->state.key] |= 1 << v->state.channel; + if (recycleNoteID) + done[v->originating_host_key] |= 1 << v->state.channel; } } } diff --git a/src/common/dsp/effects/AudioInputEffect.cpp b/src/common/dsp/effects/AudioInputEffect.cpp index 2ccf79eafd4..5b36c20e152 100644 --- a/src/common/dsp/effects/AudioInputEffect.cpp +++ b/src/common/dsp/effects/AudioInputEffect.cpp @@ -1,3 +1,24 @@ +/* + * Surge XT - a free and open source hybrid synthesizer, + * built by Surge Synth Team + * + * Learn more at https://surge-synthesizer.github.io/ + * + * Copyright 2018-2023, various authors, as described in the GitHub + * transaction log. + * + * Surge XT is released under the GNU General Public Licence v3 + * or later (GPL-3.0-or-later). The license is found in the "LICENSE" + * file in the root of this repository, or at + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * Surge was a commercial product from 2004-2018, copyright and ownership + * held by Claes Johanson at Vember Audio during that period. + * Claes made Surge open source in September 2018. + * + * All source for Surge XT is available at + * https://github.com/surge-synthesizer/surge + */ #include "AudioInputEffect.h" diff --git a/src/common/dsp/effects/AudioInputEffect.h b/src/common/dsp/effects/AudioInputEffect.h index 6879f974fd0..7f30790dfb9 100644 --- a/src/common/dsp/effects/AudioInputEffect.h +++ b/src/common/dsp/effects/AudioInputEffect.h @@ -1,18 +1,26 @@ /* -** Surge Synthesizer is Free and Open Source Software -** -** Surge is made available under the Gnu General Public License, v3.0 -** https://www.gnu.org/licenses/gpl-3.0.en.html -** -** Copyright 2004-2023 by various individuals as described by the Git transaction log -** -** All source at: https://github.com/surge-synthesizer/surge.git -** -** Surge was a commercial product from 2004-2018, with Copyright and ownership -** in that period held by Claes Johanson at Vember Audio. Claes made Surge -** open source in September 2018. -*/ -#pragma once + * Surge XT - a free and open source hybrid synthesizer, + * built by Surge Synth Team + * + * Learn more at https://surge-synthesizer.github.io/ + * + * Copyright 2018-2023, various authors, as described in the GitHub + * transaction log. + * + * Surge XT is released under the GNU General Public Licence v3 + * or later (GPL-3.0-or-later). The license is found in the "LICENSE" + * file in the root of this repository, or at + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * Surge was a commercial product from 2004-2018, copyright and ownership + * held by Claes Johanson at Vember Audio during that period. + * Claes made Surge open source in September 2018. + * + * All source for Surge XT is available at + * https://github.com/surge-synthesizer/surge + */ +#ifndef SURGE_SRC_COMMON_DSP_EFFECTS_AUDIOINPUTEFFECT_H +#define SURGE_SRC_COMMON_DSP_EFFECTS_AUDIOINPUTEFFECT_H #include "Effect.h" class AudioInputEffect : public Effect { @@ -57,3 +65,5 @@ class AudioInputEffect : public Effect void applySlidersControls(float *buffer[], const float &channel, const float &pan, const float &levelDb); }; + +#endif // SURGE_SRC_COMMON_DSP_EFFECTS_AUDIOINPUTEFFECT_H diff --git a/src/surge-testrunner/CMakeLists.txt b/src/surge-testrunner/CMakeLists.txt index 439060f14ff..ddace810073 100644 --- a/src/surge-testrunner/CMakeLists.txt +++ b/src/surge-testrunner/CMakeLists.txt @@ -27,6 +27,7 @@ add_executable(${PROJECT_NAME} UnitTestsPARAM.cpp UnitTestsQUERY.cpp UnitTestsTUN.cpp + UnitTestsVOICE.cpp main.cpp ) diff --git a/src/surge-testrunner/UnitTestsMIDI.cpp b/src/surge-testrunner/UnitTestsMIDI.cpp index 3818bf76722..a877af0bf82 100644 --- a/src/surge-testrunner/UnitTestsMIDI.cpp +++ b/src/surge-testrunner/UnitTestsMIDI.cpp @@ -1723,93 +1723,3 @@ TEST_CASE("Latch in Dual MPE", "[midi]") } } } - -TEST_CASE("Release by Note ID", "[midi]") -{ - SECTION("Simple Sine Case") - { - auto s = surgeOnSine(); - - auto proc = [&s]() { - for (int i = 0; i < 5; ++i) - { - s->process(); - } - }; - - auto voicecount = [&s]() -> int { - int res{0}; - for (auto sc = 0; sc < n_scenes; ++sc) - { - for (const auto &v : s->voices[sc]) - { - if (v->state.gate) - res++; - } - } - return res; - }; - - proc(); - - s->playNote(0, 60, 127, 0, 173); - proc(); - REQUIRE(voicecount() == 1); - - s->playNote(0, 64, 127, 0, 177); - proc(); - REQUIRE(voicecount() == 2); - - s->releaseNoteByHostNoteID(173, 0); - proc(); - REQUIRE(voicecount() == 1); - - s->releaseNoteByHostNoteID(177, 0); - proc(); - REQUIRE(voicecount() == 0); - } - - SECTION("Playmode DUal Sine Case") - { - auto s = surgeOnSine(); - s->storage.getPatch().scenemode.val.i = sm_dual; - - auto proc = [&s]() { - for (int i = 0; i < 5; ++i) - { - s->process(); - } - }; - - auto voicecount = [&s]() -> int { - int res{0}; - for (auto sc = 0; sc < n_scenes; ++sc) - { - for (const auto &v : s->voices[sc]) - { - if (v->state.gate) - res++; - } - } - return res; - }; - - proc(); - - s->playNote(0, 60, 127, 0, 173); - proc(); - REQUIRE(voicecount() == 2); - - s->playNote(0, 64, 127, 0, 177); - proc(); - REQUIRE(voicecount() == 4); - - s->releaseNoteByHostNoteID(173, 0); - proc(); - REQUIRE(voicecount() == 2); - - s->releaseNoteByHostNoteID(177, 0); - proc(); - REQUIRE(voicecount() == 0); - } -} \ No newline at end of file diff --git a/src/surge-testrunner/UnitTestsVOICE.cpp b/src/surge-testrunner/UnitTestsVOICE.cpp new file mode 100644 index 00000000000..6ae1e1a9037 --- /dev/null +++ b/src/surge-testrunner/UnitTestsVOICE.cpp @@ -0,0 +1,295 @@ +/* + * Surge XT - a free and open source hybrid synthesizer, + * built by Surge Synth Team + * + * Learn more at https://surge-synthesizer.github.io/ + * + * Copyright 2018-2023, various authors, as described in the GitHub + * transaction log. + * + * Surge XT is released under the GNU General Public Licence v3 + * or later (GPL-3.0-or-later). The license is found in the "LICENSE" + * file in the root of this repository, or at + * https://www.gnu.org/licenses/gpl-3.0.en.html + * + * Surge was a commercial product from 2004-2018, copyright and ownership + * held by Claes Johanson at Vember Audio during that period. + * Claes made Surge open source in September 2018. + * + * All source for Surge XT is available at + * https://github.com/surge-synthesizer/surge + */ + +#include +#include +#include +#include + +#include "HeadlessUtils.h" +#include "Player.h" + +#include "catch2/catch_amalgamated.hpp" + +#include "UnitTestUtilities.h" + +using namespace Surge::Test; + +TEST_CASE("Release by Note ID", "[voice]") +{ + SECTION("Simple Sine Case") + { + auto s = surgeOnSine(); + + auto proc = [&s]() { + for (int i = 0; i < 5; ++i) + { + s->process(); + } + }; + + auto voicecount = [&s]() -> int { + int res{0}; + for (auto sc = 0; sc < n_scenes; ++sc) + { + for (const auto &v : s->voices[sc]) + { + if (v->state.gate) + res++; + } + } + return res; + }; + + proc(); + + s->playNote(0, 60, 127, 0, 173); + proc(); + REQUIRE(voicecount() == 1); + + s->playNote(0, 64, 127, 0, 177); + proc(); + REQUIRE(voicecount() == 2); + + s->releaseNoteByHostNoteID(173, 0); + proc(); + REQUIRE(voicecount() == 1); + + s->releaseNoteByHostNoteID(177, 0); + proc(); + REQUIRE(voicecount() == 0); + } + + SECTION("Playmode Dual Sine Case") + { + auto s = surgeOnSine(); + s->storage.getPatch().scenemode.val.i = sm_dual; + + auto proc = [&s]() { + for (int i = 0; i < 5; ++i) + { + s->process(); + } + }; + + auto voicecount = [&s]() -> int { + int res{0}; + for (auto sc = 0; sc < n_scenes; ++sc) + { + for (const auto &v : s->voices[sc]) + { + if (v->state.gate) + res++; + } + } + return res; + }; + + proc(); + + s->playNote(0, 60, 127, 0, 173); + proc(); + REQUIRE(voicecount() == 2); + + s->playNote(0, 64, 127, 0, 177); + proc(); + REQUIRE(voicecount() == 4); + + s->releaseNoteByHostNoteID(173, 0); + proc(); + REQUIRE(voicecount() == 2); + + s->releaseNoteByHostNoteID(177, 0); + proc(); + REQUIRE(voicecount() == 0); + } + + for (auto pm : {pm_poly, pm_mono, pm_mono_fp, pm_mono_st, pm_mono_st_fp}) + { + DYNAMIC_SECTION("PlayMode Sine Case " << pm << " " << play_mode_names[pm]) + { + bool recycleNoteId = (pm == pm_mono_st || pm == pm_mono_st_fp); + auto s = surgeOnSine(); + s->storage.getPatch().scene[0].polymode.val.i = pm; + s->process(); + + auto proc = [&s]() { + for (int i = 0; i < 5; ++i) + { + s->process(); + } + }; + + auto voicecount = [&s]() -> int { + int res{0}; + for (auto sc = 0; sc < n_scenes; ++sc) + { + for (const auto &v : s->voices[sc]) + { + if (v->state.gate) + res++; + } + } + return res; + }; + + proc(); + + s->playNote(0, 60, 127, 0, 173); + proc(); + REQUIRE(voicecount() == 1); + + std::cout << "A" << std::endl; + for (auto v : s->voices[0]) + { + std::cout << v->host_note_id << " " << v->state.key << " " + << v->originating_host_key << std::endl; + } + + s->playNote(0, 64, 127, 0, 177); + proc(); + REQUIRE(voicecount() == (pm == pm_poly ? 2 : 1)); + std::cout << "B" << std::endl; + for (auto v : s->voices[0]) + { + std::cout << v->host_note_id << " " << v->state.key << " " + << v->originating_host_key << std::endl; + } + + std::cout << "FIRST" << std::endl; + s->releaseNoteByHostNoteID(173, 0); + proc(); + REQUIRE(voicecount() == (recycleNoteId ? 0 : 1)); + + std::cout << "SECOND" << std::endl; + s->releaseNoteByHostNoteID(177, 0); + proc(); + REQUIRE(voicecount() == 0); + } + } +} + +TEST_CASE("Play and release Frequency and Note ID", "[voice]") +{ + using cmd = std::tuple; + using tcase = std::tuple>; + + // clang-format off + // This is a list of (mode) (byKeyOrFreq) and actions + // an action is play-or-release then key, channel, nid, exp + // exp is the expected voice count after the action. + auto data = std::vector{ + {pm_poly, true, + { + {true, 60, 0, 184, 1}, + {false, -1, -1, 184, 0} + } + }, + {pm_poly, true, + { + {true, 60, 0, 184, 1}, + {true, 65, 0, 187, 2}, + {false, -1, -1, 187, 1}, + {false, -1, -1, 184, 0} + } + }, + {pm_poly, true, + { + {true, 60, 0, 184, 1}, + {true, 65, 0, 187, 2}, + {false, -1, -1, 184, 1}, + {false, -1, -1, 187, 0} + } + }, + // This are the cases that led to this test. + // in mono_st you recycle voice ids + {pm_mono_st, true, + { + {true, 60, 0, 2421, 1}, + {true, 65, 0, 9324, 1}, // but 9324 is gone and we are mono + {false, -1, -1, 9324, 1}, // so killing it doesnt matter + {false, -1, -1, 2421, 0} + } + }, + {pm_mono_st, true, + { + {true, 60, 0, 2421, 1}, + {true, 65, 0, 9324, 1}, // but 9324 is gone and we are mono + {false, -1, -1, 2421, 0}, // so killing the lead voice silences us + {false, -1, -1, 9324, 0} // and this is a no-op + } + }, + }; + // clang-format on + + for (auto &d : data) + { + auto mode = std::get<0>(d); + auto byNote = std::get<1>(d); + + auto s = surgeOnSine(); + s->storage.getPatch().scene[0].polymode.val.i = mode; + + auto proc = [&s]() { + for (int i = 0; i < 5; ++i) + { + s->process(); + } + }; + + auto voicecount = [&s]() -> int { + int res{0}; + for (auto sc = 0; sc < n_scenes; ++sc) + { + for (const auto &v : s->voices[sc]) + { + if (v->state.gate) + res++; + } + } + return res; + }; + + auto cmd = std::get<2>(d); + for (auto &c : cmd) + { + auto [play, keyOrFreq, channel, nid, expected] = c; + if (play) + { + if (byNote) + { + s->playNote(channel, keyOrFreq, 90, 0, nid); + } + else + { + s->playNoteByFrequency(keyOrFreq, 90, nid); + } + } + else + { + s->releaseNoteByHostNoteID(nid, 0); + } + proc(); + REQUIRE(voicecount() == expected); + } + } +} diff --git a/src/surge-xt/osc/OpenSoundControl.h b/src/surge-xt/osc/OpenSoundControl.h index 4bb777fb8a3..c184011d879 100644 --- a/src/surge-xt/osc/OpenSoundControl.h +++ b/src/surge-xt/osc/OpenSoundControl.h @@ -19,8 +19,8 @@ * All source for Surge XT is available at * https://github.com/surge-synthesizer/surge */ -#ifndef SURGE_SRC_SURGE_XT_OSC_OSCLISTENER_H -#define SURGE_SRC_SURGE_XT_OSC_OSCLISTENER_H +#ifndef SURGE_SRC_SURGE_XT_OSC_OPENSOUNDCONTROL_H +#define SURGE_SRC_SURGE_XT_OSC_OPENSOUNDCONTROL_H /* ** Surge Synthesizer is Free and Open Source Software **