diff --git a/src/common/SurgeSynthesizer.cpp b/src/common/SurgeSynthesizer.cpp index d7660a77bf2..9ae5771ed28 100644 --- a/src/common/SurgeSynthesizer.cpp +++ b/src/common/SurgeSynthesizer.cpp @@ -1755,6 +1755,39 @@ void SurgeSynthesizer::releaseNotePostHoldCheck(int scene, char channel, char ke } } +void SurgeSynthesizer::releaseNoteByHostID(int32_t host_noteid, char velocity) +{ + std::array done; + std::fill(done.begin(), done.end(), 0); + + for (int s = 0; s < n_scenes; ++s) + { + for (auto v : voices[s]) + { + if (v->host_note_id == host_noteid) + { + done[v->state.key] |= 1 << v->state.channel; + } + } + } + + int nidx{0}; + for (auto d : done) + { + if (d) + { + for (auto ch = 0; ch < 16; ++ch) + { + if (d & 1 << ch) + { + releaseNote(ch, nidx, velocity, host_noteid); + } + } + } + nidx++; + } +} + void SurgeSynthesizer::setNoteExpression(SurgeVoice::NoteExpressionType net, int32_t note_id, int16_t key, int16_t channel, float value) { diff --git a/src/common/SurgeSynthesizer.h b/src/common/SurgeSynthesizer.h index 3ce085dfd91..e9563afe6ec 100644 --- a/src/common/SurgeSynthesizer.h +++ b/src/common/SurgeSynthesizer.h @@ -90,6 +90,9 @@ class alignas(16) SurgeSynthesizer void playNoteByFrequency(float freq, char velocity, int32_t id); void releaseNote(char channel, char key, char velocity, int32_t host_noteid = -1); void chokeNote(int16_t channel, int16_t key, char velocity, int32_t host_noteid = -1); + // Release all notes matching just this host noteid. Mostly used for OpenSoundCtrl right now + void releaseNoteByHostID(int32_t host_noteid, char velocity); + void releaseNotePostHoldCheck(int scene, char channel, char key, char velocity, int32_t host_noteid = -1); void resetPitchBend(int8_t channel); diff --git a/src/surge-testrunner/UnitTestsMIDI.cpp b/src/surge-testrunner/UnitTestsMIDI.cpp index 4e66ae9fe96..aa8de9d50cf 100644 --- a/src/surge-testrunner/UnitTestsMIDI.cpp +++ b/src/surge-testrunner/UnitTestsMIDI.cpp @@ -1722,4 +1722,94 @@ 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->releaseNoteByHostID(173, 0); + proc(); + REQUIRE(voicecount() == 1); + + s->releaseNoteByHostID(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->releaseNoteByHostID(173, 0); + proc(); + REQUIRE(voicecount() == 2); + + s->releaseNoteByHostID(177, 0); + proc(); + REQUIRE(voicecount() == 0); + } } \ No newline at end of file