diff --git a/src/common/SurgeSynthesizer.cpp b/src/common/SurgeSynthesizer.cpp index 9cb6bbd6714..ba7207debb5 100644 --- a/src/common/SurgeSynthesizer.cpp +++ b/src/common/SurgeSynthesizer.cpp @@ -409,7 +409,7 @@ int SurgeSynthesizer::calculateChannelMask(int channel, int key) } void SurgeSynthesizer::playNote(char channel, char key, char velocity, char detune, - int32_t host_noteid) + int32_t host_noteid, int32_t forceScene) { if (halt_engine) { @@ -464,6 +464,10 @@ void SurgeSynthesizer::playNote(char channel, char key, char velocity, char detu // MIDI Channel 3 plays B int channelmask = calculateChannelMask(channel, key); + if (forceScene == 0) + channelmask = 1; + if (forceScene == 1) + channelmask = 2; // TODO: FIX SCENE ASSUMPTION if (channelmask & 1) @@ -4092,9 +4096,9 @@ void SurgeSynthesizer::processControl() // introduce int mods, we need to make sure the scenedata and so on is set up before // we latch if (playA && (storage.getPatch().scene[0].polymode.val.i == pm_latch) && voices[0].empty()) - playNote(1, 60, 100, 0); + playNote(1, 60, 100, 0, -1, 0); if (playB && (storage.getPatch().scene[1].polymode.val.i == pm_latch) && voices[1].empty()) - playNote(2, 60, 100, 0); + playNote(2, 60, 100, 0, -1, 1); for (int s = 0; s < n_scenes; s++) { diff --git a/src/common/SurgeSynthesizer.h b/src/common/SurgeSynthesizer.h index 9b78ca1f1c1..3ce085dfd91 100644 --- a/src/common/SurgeSynthesizer.h +++ b/src/common/SurgeSynthesizer.h @@ -82,8 +82,11 @@ class alignas(16) SurgeSynthesizer virtual ~SurgeSynthesizer(); // Also see setNoteExpression() which allows you to control all note parameters polyphonically - // with the user-provided host_noteid parameter. - void playNote(char channel, char key, char velocity, char detune, int32_t host_noteid = -1); + // with the user-provided host_noteid parameter. forceScene means to ignore any channel + // related shenanigans and force you onto scene 0, 1, 2 etc... which is useful mostly + // for latch mode working in MPE and ignore 23 mode + void playNote(char channel, char key, char velocity, char detune, int32_t host_noteid = -1, + int32_t forceScene = -1); 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); diff --git a/src/surge-testrunner/UnitTestsMIDI.cpp b/src/surge-testrunner/UnitTestsMIDI.cpp index 4fc66a3c674..4e66ae9fe96 100644 --- a/src/surge-testrunner/UnitTestsMIDI.cpp +++ b/src/surge-testrunner/UnitTestsMIDI.cpp @@ -1676,4 +1676,50 @@ TEST_CASE("Mono Modes Across Channels", "[midi]") } } } +} + +TEST_CASE("Latch in Dual MPE", "[midi]") +{ + for (auto u23 : {false, true}) + { + for (auto me : {false, true}) + { + DYNAMIC_SECTION("Latch " << (me ? "With" : "Without") + << " MPE use23=" << (u23 ? "true" : "false")) + { + auto surge = surgeOnSine(); + surge->storage.userDefaultsProvider->addOverride( + Surge::Storage::UseCh2Ch3ToPlayScenesIndividually, u23); + surge->mpeEnabled = me; + surge->storage.getPatch().scenemode.val.i = sm_dual; + + REQUIRE(surge->voices[0].empty()); + REQUIRE(surge->voices[1].empty()); + + for (int i = 0; i < 10; ++i) + surge->process(); + + REQUIRE(surge->voices[0].empty()); + REQUIRE(surge->voices[1].empty()); + + auto &p = surge->storage.getPatch().scene[0].polymode; + auto id = surge->idForParameter(&p); + + surge->setParameter01( + id, Parameter::intScaledToFloat(pm_latch, p.val_max.i, p.val_min.i)); + surge->process(); + + REQUIRE(surge->voices[0].size() == 1); + REQUIRE(surge->voices[1].size() == 0); + + surge->setParameter01( + id, Parameter::intScaledToFloat(pm_poly, p.val_max.i, p.val_min.i)); + for (int i = 0; i < 100; ++i) + surge->process(); + + REQUIRE(surge->voices[0].empty()); + REQUIRE(surge->voices[1].empty()); + } + } + } } \ No newline at end of file