Skip to content

Commit

Permalink
Handle latch in dual MPE and no-23 mode
Browse files Browse the repository at this point in the history
In MPE or ignore-23-to-scene mode, turning one scene
on to latch would mistakenly trigger voices in the non
latched scene. Fix that but adding an intended scene optional
argument to playNote and calling it from the right place. Also
add a regtest.

Closes surge-synthesizer#6963
  • Loading branch information
baconpaul committed Jul 29, 2023
1 parent 8d791a1 commit 3165567
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 5 deletions.
10 changes: 7 additions & 3 deletions src/common/SurgeSynthesizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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++)
{
Expand Down
7 changes: 5 additions & 2 deletions src/common/SurgeSynthesizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
46 changes: 46 additions & 0 deletions src/surge-testrunner/UnitTestsMIDI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
}

0 comments on commit 3165567

Please sign in to comment.