From a4aecba65dbe4a1fe574ea9ad251e62e6c2a6a0f Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Tue, 12 Nov 2024 10:47:04 -0500 Subject: [PATCH] Mono Legato Mode Mono Legato mode differes from Mono Retrigger mode in taht nodes get moved and rather than terminated and restarted. This implements that mode by adding two new api points to the voice manager and managing the internal state accordingly --- .../voicemanager/voicemanager_constraints.h | 4 + include/sst/voicemanager/voicemanager_impl.h | 107 ++++- tests/legato_playback.cpp | 430 +++++++++++++++++- tests/test_player.h | 109 ++++- 4 files changed, 638 insertions(+), 12 deletions(-) diff --git a/include/sst/voicemanager/voicemanager_constraints.h b/include/sst/voicemanager/voicemanager_constraints.h index b2ce200..be3456b 100644 --- a/include/sst/voicemanager/voicemanager_constraints.h +++ b/include/sst/voicemanager/voicemanager_constraints.h @@ -51,6 +51,10 @@ template struct Const void (Responder::*)(std::function)) HASMEM(retriggerVoiceWithNewNoteID, Responder, void (Responder::*)(typename Cfg::voice_t *, int32_t, float)) + HASMEM(moveVoice, Responder, + void (Responder::*)(typename Cfg::voice_t *, uint16_t, uint16_t, uint16_t, float)) + HASMEM(moveAndRetriggerVoice, Responder, + void (Responder::*)(typename Cfg::voice_t *, uint16_t, uint16_t, uint16_t, float)) HASMEM(beginVoiceCreationTransaction, Responder, int32_t (Responder::*)(typename VoiceBeginBufferEntry::buffer_t &, uint16_t, diff --git a/include/sst/voicemanager/voicemanager_impl.h b/include/sst/voicemanager/voicemanager_impl.h index 701c50f..6ab6597 100644 --- a/include/sst/voicemanager/voicemanager_impl.h +++ b/include/sst/voicemanager/voicemanager_impl.h @@ -55,6 +55,8 @@ struct VoiceManager::Details int16_t port{0}, channel{0}, key{0}; int32_t noteId{-1}; + int16_t originalPort{0}, originalChannel{0}, originalKey{0}; + int64_t voiceCounter{0}, transactionId{0}; bool gated{false}; @@ -73,6 +75,13 @@ struct VoiceManager::Details res = res && (nid == -1 || noteId == -1 || nid == noteId); return res; } + + void snapOriginalToCurrent() + { + originalPort = port; + originalChannel = channel; + originalKey = key; + } }; std::array voiceInfo; std::unordered_map polyLimits; @@ -278,7 +287,7 @@ struct VoiceManager::Details void doMonoRetrigger(int16_t port, uint64_t polyGroup) { - VML("=== MONO mode voice retrigger for " << polyGroup); + VML("=== MONO mode voice retrigger or move for " << polyGroup); auto &ks = keyStateByPort[port]; auto ft = playModeFeatures.at(polyGroup); int dch{-1}, dk{-1}; @@ -286,7 +295,6 @@ struct VoiceManager::Details auto findBestKey = [&](bool ignoreSustain) { - VML("- find best key " << ignoreSustain); if (ft & (uint64_t)MonoPlayModeFeatures::ON_RELEASE_TO_LATEST) { int64_t mtx = 0; @@ -368,7 +376,7 @@ struct VoiceManager::Details if (dch < 0) findBestKey(true); - if (dch >= 0 && dk >= 0) + if (ft & (uint64_t)MonoPlayModeFeatures::MONO_RETRIGGER && dch >= 0 && dk >= 0) { // Need to know the velocity and the port VML("- retrigger Note " << dch << " " << dk << " " << dvel); @@ -406,6 +414,7 @@ struct VoiceManager::Details vi.channel = dch; vi.key = dk; vi.noteId = dnid; + vi.snapOriginalToCurrent(); vi.gated = true; vi.gatedDueToSustain = false; @@ -435,6 +444,31 @@ struct VoiceManager::Details vm.responder.endVoiceCreationTransaction(port, dch, dk, dnid, dvel); } + + else if (ft & (uint64_t)MonoPlayModeFeatures::MONO_LEGATO && dch >= 0 && dk >= 0) + { + VML("- Move notes in group " << polyGroup << " to " << dch << "/" << dk); + for (auto &v : voiceInfo) + { + if (v.activeVoiceCookie && v.polyGroup == polyGroup) + { + if (v.gated || v.gatedDueToSustain) + { + VML("- Move gated voice"); + vm.responder.moveVoice(v.activeVoiceCookie, port, dch, dk, dvel); + } + else + { + VML("- Move and retrigger non gated voice"); + vm.responder.moveAndRetriggerVoice(v.activeVoiceCookie, port, dch, dk, + dvel); + } + v.port = port; + v.channel = dch; + v.key = dk; + } + } + } } bool anyKeyHeldFor(int16_t port, uint64_t polyGroup, int exceptChannel, int exceptKey, @@ -625,13 +659,57 @@ bool VoiceManager::processNoteOnEvent(int16_t por VML("Mono Stealing:"); for (const auto &mpg : monoGroups) { - VML("- Would steal all voices in " << mpg << " (TODO: This is *WRONG* for Legato)"); - for (const auto &v : details.voiceInfo) + VML("- Would steal all voices in " << mpg); + auto isLegato = + details.playModeFeatures.at(mpg) & (uint64_t)MonoPlayModeFeatures::MONO_LEGATO; + VML("- IsLegato : " << isLegato); + if (isLegato) + { + bool foundOne{false}; + for (auto &v : details.voiceInfo) + { + if (v.activeVoiceCookie && v.polyGroup == mpg) + { + VML(" - Moving existing voice " << v.activeVoiceCookie << " to " << key); + if (v.gated) + { + responder.moveVoice(v.activeVoiceCookie, port, channel, key, velocity); + } + else + { + responder.moveAndRetriggerVoice(v.activeVoiceCookie, port, channel, key, + velocity); + } + v.port = port; + v.channel = channel; + v.key = key; + v.gated = true; + + foundOne = true; + } + } + if (foundOne) + { + for (int i = 0; i < voicesToBeLaunched; ++i) + { + if (details.voiceBeginWorkingBuffer[i].polyphonyGroup == mpg) + { + VML(" - Setting instruction " << i << " to skip"); + details.voiceInitInstructionsBuffer[i].instruction = + VoiceInitInstructionsEntry::Instruction::SKIP; + } + } + } + } + else { - if (v.activeVoiceCookie && v.polyGroup == mpg) + for (const auto &v : details.voiceInfo) { - VML("- Stealing voice " << v.key); - responder.terminateVoice(v.activeVoiceCookie); + if (v.activeVoiceCookie && v.polyGroup == mpg) + { + VML("- Stealing voice " << v.key); + responder.terminateVoice(v.activeVoiceCookie); + } } } } @@ -682,6 +760,7 @@ bool VoiceManager::processNoteOnEvent(int16_t por vi.channel = channel; vi.key = key; vi.noteId = noteid; + vi.snapOriginalToCurrent(); vi.gated = true; vi.gatedDueToSustain = false; @@ -732,6 +811,17 @@ void VoiceManager::processNoteOffEvent(int16_t po VML("- Found matching release note at " << vi.polyGroup << " " << vi.key); if (details.playMode[vi.polyGroup] == PlayMode::MONO_NOTES) { + if (details.playModeFeatures[vi.polyGroup] & + (uint64_t)MonoPlayModeFeatures::MONO_LEGATO) + { + bool anyOtherOption = details.anyKeyHeldFor(port, vi.polyGroup, channel, key); + if (anyOtherOption) + { + retriggerGroups.insert(vi.polyGroup); + VML("- A key is down in same group. Initiating mono legato move"); + continue; + } + } if (details.sustainOn) { VML("- Release with sustain on. Checking to see if there are gated voices " @@ -776,6 +866,7 @@ void VoiceManager::processNoteOffEvent(int16_t po } else { + // Poly branch ere if (details.sustainOn) { vi.gatedDueToSustain = true; diff --git a/tests/legato_playback.cpp b/tests/legato_playback.cpp index 4b66500..593ed7c 100644 --- a/tests/legato_playback.cpp +++ b/tests/legato_playback.cpp @@ -18,6 +18,432 @@ #include "sst/voicemanager/voicemanager.h" #include "test_player.h" -TEST_CASE("Legato Mono Mode - Voices") { REQUIRE_INCOMPLETE_TEST; } +TEST_CASE("Legato Mono Mode - Single Key Releases") +{ + auto tp = TestPlayer<32, false>(); + typedef TestPlayer<32, false>::voiceManager_t vm_t; + auto &vm = tp.voiceManager; -TEST_CASE("Legato Mono Mode - Notes") { REQUIRE_INCOMPLETE_TEST; } \ No newline at end of file + vm.setPlaymode(0, vm_t::PlayMode::MONO_NOTES, + (uint64_t)vm_t::MonoPlayModeFeatures::NATURAL_LEGATO); + + REQUIRE_NO_VOICES; + + vm.processNoteOnEvent(0, 0, 60, -1, 0.8, 0); + REQUIRE_VOICE_COUNTS(1, 1); + vm.processNoteOffEvent(0, 0, 60, -1, 0.8); + REQUIRE_VOICE_COUNTS(1, 0); + tp.processFor(10); + REQUIRE_VOICE_COUNTS(0, 0); +} + +TEST_CASE("Legato Mono Mode - Simplest Move") +{ + auto tp = TestPlayer<32, false>(); + typedef TestPlayer<32, false>::voiceManager_t vm_t; + auto &vm = tp.voiceManager; + + vm.setPlaymode(0, vm_t::PlayMode::MONO_NOTES, + (uint64_t)vm_t::MonoPlayModeFeatures::NATURAL_LEGATO); + + REQUIRE_NO_VOICES; + + vm.processNoteOnEvent(0, 0, 60, -1, 0.8, 0); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + tp.processFor(2); + vm.processNoteOnEvent(0, 0, 62, -1, 0.8, 0); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); +} + +TEST_CASE("Legato Mono Mode - Release while Gated") +{ + auto tp = TestPlayer<32, false>(); + typedef TestPlayer<32, false>::voiceManager_t vm_t; + auto &vm = tp.voiceManager; + + vm.setPlaymode(0, vm_t::PlayMode::MONO_NOTES, + (uint64_t)vm_t::MonoPlayModeFeatures::NATURAL_LEGATO); + + REQUIRE_NO_VOICES; + + vm.processNoteOnEvent(0, 0, 60, -1, 0.8, 0); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + tp.processFor(2); + vm.processNoteOnEvent(0, 0, 62, -1, 0.8, 0); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.runtime >= 2); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + vm.processNoteOffEvent(0, 0, 60, -1, 0.8); + tp.processFor(2); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.runtime >= 4); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + tp.processFor(20); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.runtime >= 24); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + vm.processNoteOffEvent(0, 0, 62, -1, 0.8); + tp.processFor(2); + REQUIRE_VOICE_COUNTS(1, 0); + REQUIRE_VOICE_MATCH(1, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.runtime >= 26); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + tp.processFor(20); + REQUIRE_NO_VOICES; +} + +TEST_CASE("Legato Mono Mode - Multi-voice simple") +{ + auto tp = TestPlayer<32, false>(); + typedef TestPlayer<32, false>::voiceManager_t vm_t; + auto &vm = tp.voiceManager; + + vm.setPlaymode(0, vm_t::PlayMode::MONO_NOTES, + (uint64_t)vm_t::MonoPlayModeFeatures::NATURAL_LEGATO); + + REQUIRE_NO_VOICES; + vm.processNoteOnEvent(0, 0, 90, -1, 0.9, 0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(3, 3); + REQUIRE_VOICE_MATCH(3, v.key() == 90); + REQUIRE_VOICE_MATCH(3, v.originalKey() == 90); + + vm.processNoteOnEvent(0, 0, 92, -1, 0.9, 0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(3, 3); + REQUIRE_VOICE_MATCH(3, v.key() == 92); + REQUIRE_VOICE_MATCH(3, v.originalKey() == 90); + + vm.processNoteOffEvent(0, 0, 90, -1, 0.9); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(3, 3); + REQUIRE_VOICE_MATCH(3, v.key() == 92); + REQUIRE_VOICE_MATCH(3, v.originalKey() == 90); + + vm.processNoteOffEvent(0, 0, 92, -1, 0.9); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(3, 0); + REQUIRE_VOICE_MATCH(3, v.key() == 92); + REQUIRE_VOICE_MATCH(3, v.originalKey() == 90); + + tp.processFor(20); + REQUIRE_NO_VOICES; +} + +TEST_CASE("Legato Mono Mode - Simple Release Moves Back") +{ + auto tp = TestPlayer<32, false>(); + typedef TestPlayer<32, false>::voiceManager_t vm_t; + auto &vm = tp.voiceManager; + + vm.setPlaymode(0, vm_t::PlayMode::MONO_NOTES, + (uint64_t)vm_t::MonoPlayModeFeatures::NATURAL_LEGATO); + + REQUIRE_NO_VOICES; + + vm.processNoteOnEvent(0, 0, 60, -1, 0.8, 0); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + tp.processFor(2); + vm.processNoteOnEvent(0, 0, 62, -1, 0.8, 0); + tp.processFor(2); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.runtime > 2); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + vm.processNoteOffEvent(0, 0, 62, -1, 0.8); + tp.processFor(2); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + INFO("Do not create a new voice when releasing retriggering"); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(1, v.runtime > 4); // It should be the same voice + + vm.processNoteOffEvent(0, 0, 60, -1, 0.8); + tp.processFor(2); + REQUIRE_VOICE_COUNTS(1, 0); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.runtime > 6); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + tp.processFor(20); + REQUIRE_NO_VOICES; +} + +TEST_CASE("Legato Mono Mode - Low Release Pri") +{ + auto tp = TestPlayer<32, false>(); + typedef TestPlayer<32, false>::voiceManager_t vm_t; + auto &vm = tp.voiceManager; + + REQUIRE_NO_VOICES; + vm.setPlaymode(0, vm_t::PlayMode::MONO_NOTES, + (uint64_t)vm_t::MonoPlayModeFeatures::MONO_LEGATO | + (uint64_t)vm_t::MonoPlayModeFeatures::ON_RELEASE_TO_LOWEST); + + INFO("Play notes in order, 58, 60, 62"); + + vm.processNoteOnEvent(0, 0, 58, -1, 0.8, 0.0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 58); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(0, v.key() != 58); + + vm.processNoteOnEvent(0, 0, 60, -1, 0.8, 0.0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 58); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(0, v.key() != 60); + + vm.processNoteOnEvent(0, 0, 62, -1, 0.8, 0.0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 58); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(0, v.key() != 62); + + INFO("With 62 sounding, releasing it returns to lowest, which is 58"); + vm.processNoteOffEvent(0, 0, 62, -1, 0.0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 58); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 58); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(0, v.key() != 58); + + INFO("And releasing 58 returns to 60"); + vm.processNoteOffEvent(0, 0, 58, -1, 0.0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 58); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(0, v.key() != 60); + + INFO("The release of which returns to empty"); + vm.processNoteOffEvent(0, 0, 60, -1, 0.0); + REQUIRE_VOICE_COUNTS(1, 0); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 58); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + tp.processFor(10); + REQUIRE_NO_VOICES; +} + +TEST_CASE("Legato Mono Mode - High Release Pri") +{ + auto tp = TestPlayer<32, false>(); + typedef TestPlayer<32, false>::voiceManager_t vm_t; + auto &vm = tp.voiceManager; + + REQUIRE_NO_VOICES; + vm.setPlaymode(0, vm_t::PlayMode::MONO_NOTES, + (uint64_t)vm_t::MonoPlayModeFeatures::MONO_LEGATO | + (uint64_t)vm_t::MonoPlayModeFeatures::ON_RELEASE_TO_HIGHEST); + + INFO("Play notes in order, 62, 60, 58"); + + vm.processNoteOnEvent(0, 0, 62, -1, 0.8, 0.0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(0, v.key() != 62); + + vm.processNoteOnEvent(0, 0, 60, -1, 0.8, 0.0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 62); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(0, v.key() != 60); + + vm.processNoteOnEvent(0, 0, 58, -1, 0.8, 0.0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 58); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 62); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(0, v.key() != 58); + + INFO("With 58 sounding, releasing it returns to highest, which is 62"); + vm.processNoteOffEvent(0, 0, 58, -1, 0.0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 62); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(0, v.key() != 62); + + INFO("And releasing 62 returns to 60"); + vm.processNoteOffEvent(0, 0, 62, -1, 0.0); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 62); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(0, v.key() != 60); + + INFO("The release of which returns to empty"); + vm.processNoteOffEvent(0, 0, 60, -1, 0.0); + REQUIRE_VOICE_COUNTS(1, 0); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 62); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + tp.processFor(10); + REQUIRE_NO_VOICES; +} + +TEST_CASE("Legato Mono Mode - Retrigger during Release") +{ + auto tp = TestPlayer<32, false>(); + typedef TestPlayer<32, false>::voiceManager_t vm_t; + auto &vm = tp.voiceManager; + + vm.setPlaymode(0, vm_t::PlayMode::MONO_NOTES, + (uint64_t)vm_t::MonoPlayModeFeatures::NATURAL_LEGATO); + + REQUIRE_NO_VOICES; + + vm.processNoteOnEvent(0, 0, 60, -1, 0.8, 0); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + tp.processFor(2); + vm.processNoteOnEvent(0, 0, 62, -1, 0.8, 0); + tp.processFor(2); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.runtime > 2); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + vm.processNoteOffEvent(0, 0, 62, -1, 0.8); + tp.processFor(2); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + INFO("Do not create a new voice when releasing retriggering"); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + REQUIRE_VOICE_MATCH(1, v.runtime > 4); // It should be the same voice + + vm.processNoteOffEvent(0, 0, 60, -1, 0.8); + tp.processFor(2); + REQUIRE_VOICE_COUNTS(1, 0); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.runtime > 6); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + vm.processNoteOnEvent(0, 0, 64, -1, 0.9, 0); + tp.processFor(2); + REQUIRE_VOICE_COUNTS(1, 1); + REQUIRE_VOICE_MATCH(1, v.key() == 64); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.runtime > 8); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + vm.processNoteOffEvent(0, 0, 64, -1, 0.9); + tp.processFor(2); + REQUIRE_VOICE_COUNTS(1, 0); + REQUIRE_VOICE_MATCH(1, v.key() == 64); + REQUIRE_VOICE_MATCH(1, v.originalKey() == 60); + REQUIRE_VOICE_MATCH(1, v.runtime > 10); + REQUIRE_VOICE_MATCH(1, v.creationCount == 1); + + tp.processFor(20); + REQUIRE_NO_VOICES; +} +TEST_CASE("Legato Mono Mode - Mixed Group Poly/Mono/Legato") +{ + auto tp = ThreeGroupsEveryKey<32, true>(); + typedef ThreeGroupsEveryKey<32, true>::voiceManager_t vm_t; + auto &vm = tp.voiceManager; + + vm.setPlaymode(2112, vm_t::PlayMode::POLY_VOICES); + vm.setPlaymode(90125, vm_t::PlayMode::MONO_NOTES, + (uint64_t)vm_t::MonoPlayModeFeatures::NATURAL_MONO); + vm.setPlaymode(8675309, vm_t::PlayMode::MONO_NOTES, + (uint64_t)vm_t::MonoPlayModeFeatures::NATURAL_LEGATO); + + REQUIRE_NO_VOICES; + + vm.processNoteOnEvent(0, 0, 60, -1, 0.9, 0.0); + tp.processFor(1); + + for (auto &v : tp.voiceStorage) + { + if (v.state == TestPlayer<32, true>::Voice::ACTIVE) + { + std::cout << v.key() << " " << v.originalKey() << " " << v.creationCount << std::endl; + } + } + REQUIRE_VOICE_COUNTS(3, 3); + REQUIRE_VOICE_MATCH(3, v.key() == 60); + REQUIRE_VOICE_MATCH(3, v.creationCount <= 3); + + vm.processNoteOnEvent(0, 0, 62, -1, 0.9, 0.0); + tp.processFor(1); + for (auto &v : tp.voiceStorage) + { + if (v.state == TestPlayer<32, true>::Voice::ACTIVE) + { + std::cout << v.key() << " " << v.originalKey() << " " << v.creationCount << std::endl; + } + } + REQUIRE_VOICE_COUNTS(4, 4); + REQUIRE_VOICE_MATCH(3, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.key() == 60); + REQUIRE_VOICE_MATCH(2, v.creationCount <= 3); + + vm.processNoteOffEvent(0, 0, 60, -1, 0.9); + tp.processFor(1); + REQUIRE_VOICE_COUNTS(4, 3); + REQUIRE_VOICE_MATCH(3, v.key() == 62); + REQUIRE_VOICE_MATCH(1, v.key() == 60 && !v.isGated); + REQUIRE_VOICE_MATCH(2, v.creationCount <= 3); + + tp.processFor(10); + REQUIRE_VOICE_COUNTS(3, 3); + REQUIRE_VOICE_MATCH(3, v.key() == 62); + REQUIRE_VOICE_MATCH(0, v.key() == 60); + REQUIRE_VOICE_MATCH(1, v.creationCount <= 3); +} + +/* +TEST_CASE("Legato Mono Mode - Multi-voice complex") { REQUIRE_INCOMPLETE_TEST; } +TEST_CASE("Legato Mono Mode - Mixed Group Poly/Legato") { REQUIRE_INCOMPLETE_TEST; } +TEST_CASE("Legato Mono Mode - Mixed Group Mono/Legato") { REQUIRE_INCOMPLETE_TEST; } +*/ diff --git a/tests/test_player.h b/tests/test_player.h index 3ed19df..6948988 100644 --- a/tests/test_player.h +++ b/tests/test_player.h @@ -49,6 +49,8 @@ template struct TestPlayer { using pckn_t = std::tuple; + int32_t lastCreationCount{1}; + struct Voice { enum State @@ -57,12 +59,14 @@ template struct TestPlayer ACTIVE } state{UNUSED}; int32_t runtime{0}; + int32_t creationCount{1}; bool isGated{false}; int32_t releaseCountdown{0}; float velocity, releaseVelocity; pckn_t pckn{-1, -1, -1, -1}; + pckn_t original_pckn{-1, -1, -1, -1}; int8_t polyATValue{0}; @@ -71,6 +75,8 @@ template struct TestPlayer auto key() const { return std::get<2>(pckn); } auto noteid() const { return std::get<3>(pckn); } + auto originalKey() const { return std::get<2>(original_pckn); } + // Note these make the voices non-fixed size so in a real synth you wouldn't want this std::map noteExpressionCache; std::map paramModulationCache; @@ -112,6 +118,24 @@ template struct TestPlayer port, channel, key, noteId, velocity, retune); } + void moveVoice(typename Config::voice_t *voice, uint16_t port, uint16_t channel, + uint16_t key, float velocity) + { + TPF; + voice->pckn = {port, channel, key, std::get<3>(voice->original_pckn)}; + } + + void moveAndRetriggerVoice(typename Config::voice_t *voice, uint16_t port, uint16_t channel, + uint16_t key, float velocity) + { + TPF; + assert(!voice->isGated); + voice->pckn = {port, channel, key, std::get<3>(voice->original_pckn)}; + voice->isGated = true; + voice->releaseCountdown = 0; + voice->velocity = velocity; + } + void terminateVoice(Voice *v) { TPT("Terminate voice at " << TPD(testPlayer.pcknToString(v->pckn))); @@ -237,11 +261,20 @@ template struct TestPlayer auto v = nv[i]; if (!v) continue; + if (voiceInitInstructionBuffer[i].instruction == + sst::voicemanager::VoiceInitInstructionsEntry::Instruction::SKIP) + { + voiceInitWorkingBuffer[i].voice = nullptr; + continue; + } v->state = Voice::ACTIVE; v->runtime = 0; v->isGated = true; v->pckn = {port, channel, key, noteId}; + v->original_pckn = v->pckn; v->velocity = velocity; + v->creationCount = lastCreationCount++; + TPT("Last Creation Count is now " << lastCreationCount); voiceInitWorkingBuffer[i].voice = v; TPT(" Set voice at " << i << " voices " << pcknToString(v->pckn)); @@ -474,7 +507,79 @@ struct TwoGroupsEveryKey : TestPlayer v->runtime = 0; v->isGated = true; v->pckn = {port, channel, key, noteId}; + v->original_pckn = v->pckn; + v->velocity = velocity; + v->creationCount = this->lastCreationCount++; + + voiceInitWorkingBuffer[i].voice = v; + TPT(" Set voice at " << i << " voices " << this->pcknToString(v->pckn)); + } + + return idx; + } +}; + +template +struct ThreeGroupsEveryKey : TestPlayer +{ + ThreeGroupsEveryKey() + { + this->voiceManager.guaranteeGroup(2112); + this->voiceManager.guaranteeGroup(90125); + this->voiceManager.guaranteeGroup(8675309); + } + int beginFn(typename sst::voicemanager::VoiceBeginBufferEntry< + typename TestPlayer::Config>::buffer_t &buf, + uint16_t port, uint16_t channel, uint16_t key, int32_t noteid, + float velocity) override + { + buf[0].polyphonyGroup = 2112; + buf[1].polyphonyGroup = 90125; + buf[2].polyphonyGroup = 8675309; + return 3; + } + int32_t initFn( + int voices, + const typename sst::voicemanager::VoiceInitInstructionsEntry< + typename TestPlayer::Config>::buffer_t &voiceInitInstructionBuffer, + typename sst::voicemanager::VoiceInitBufferEntry< + typename TestPlayer::Config>::buffer_t &voiceInitWorkingBuffer, + uint16_t port, uint16_t channel, uint16_t key, int32_t noteId, float velocity, + float retune) override + { + assert(voices == 3); + typename TestPlayer::Voice *nv[3]{nullptr, nullptr, nullptr}; + + int idx = 0; + for (auto &v : this->voiceStorage) + { + if (v.state != TestPlayer::Voice::ACTIVE) + { + nv[idx] = &v; + TPT("Assigning voice at " << idx << " from " << &v); + ++idx; + if (idx == 3) + break; + } + } + + idx = 0; + for (int i = 0; i < voices; ++i) + { + if (voiceInitInstructionBuffer[i].instruction == + TestPlayer::voiceManager_t::initInstruction_t::Instruction::SKIP) + continue; + auto v = nv[idx]; + idx++; + if (!v) + continue; + v->state = TestPlayer::Voice::ACTIVE; + v->runtime = 0; + v->isGated = true; + v->pckn = {port, channel, key, noteId}; + v->original_pckn = v->pckn; v->velocity = velocity; + v->creationCount = this->lastCreationCount++; voiceInitWorkingBuffer[i].voice = v; TPT(" Set voice at " << i << " voices " << this->pcknToString(v->pckn)); @@ -509,7 +614,7 @@ struct TwoGroupsEveryKey : TestPlayer #define REQUIRE_KEY_COUNT(ct, keyNumber) \ REQUIRE(tp.activeVoicesMatching([](const auto &v) { return v.key() == keyNumber; }) == ct) -// #define REQUIRE_INCOMPLETE_TEST REQUIRE(false) -#define REQUIRE_INCOMPLETE_TEST INFO("This test is currently incomplete"); +#define REQUIRE_INCOMPLETE_TEST REQUIRE(false) +// #define REQUIRE_INCOMPLETE_TEST INFO("This test is currently incomplete"); #endif // TEST_RESPONDERS_H