From c508b76c82e31558e1078835230e35e44dacd684 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 6 Nov 2024 09:12:48 -0500 Subject: [PATCH] Mono Note in addition to Poly Voice mode (#1444) Initrocuce mono note mode With layerd groups and stuff And initial support for priority in the menu Legato and cleanup still to do, but mergable Mono - checkpoint one. Test at a keyboard mono done --- libs/sst/sst-voicemanager | 2 +- .../components/GroupSettingsCard.cpp | 124 +++++++++++++++++- .../components/GroupSettingsCard.h | 3 +- src/configuration.h | 1 + src/engine/engine.h | 2 + src/engine/engine_voice_responder.cpp | 24 +++- src/engine/group.cpp | 23 +++- src/engine/group.h | 6 + src/engine/zone.cpp | 4 +- src/json/engine_traits.h | 76 ++++++++++- src/voice/voice.cpp | 4 + 11 files changed, 250 insertions(+), 19 deletions(-) diff --git a/libs/sst/sst-voicemanager b/libs/sst/sst-voicemanager index 4fa6af74..67942144 160000 --- a/libs/sst/sst-voicemanager +++ b/libs/sst/sst-voicemanager @@ -1 +1 @@ -Subproject commit 4fa6af74907b57db54da87dbab9c81df6b26e197 +Subproject commit 6794214460d3314c8993f25614749ce8fc147c67 diff --git a/src-ui/app/edit-screen/components/GroupSettingsCard.cpp b/src-ui/app/edit-screen/components/GroupSettingsCard.cpp index 22425d93..ca183852 100644 --- a/src-ui/app/edit-screen/components/GroupSettingsCard.cpp +++ b/src-ui/app/edit-screen/components/GroupSettingsCard.cpp @@ -70,6 +70,14 @@ GroupSettingsCard::GroupSettingsCard(SCXTEditor *e) }); addAndMakeVisible(*polyMenu); + polyModeMenu = std::make_unique(); + polyModeMenu->setLabel("POLY"); + polyModeMenu->setOnCallback([w = juce::Component::SafePointer(this)]() { + if (w) + w->showPolyModeMenu(); + }); + addAndMakeVisible(*polyModeMenu); + prioGlyph = mkg(jcmp::GlyphPainter::GlyphType::NOTE_PRIORITY); prioMenu = mkm("LAST", "Priority"); glideGlpyh = mkg(jcmp::GlyphPainter::GlyphType::CURVE); @@ -119,6 +127,7 @@ void GroupSettingsCard::resized() spair(outputGlyph, outputMenu); r = r.translated(0, rowHeight); spair(polyGlygh, polyMenu); + polyModeMenu->setBounds(polyMenu->getBounds().translated(polyMenu->getWidth() + 2, 0)); r = r.translated(0, rowHeight); spair(prioGlyph, prioMenu); r = r.translated(0, rowHeight); @@ -139,13 +148,25 @@ void GroupSettingsCard::resized() void GroupSettingsCard::rebuildFromInfo() { - if (info.hasIndependentPolyLimit) + if (info.vmPlayModeInt == (int32_t)engine::Engine::voiceManager_t::PlayMode::MONO_NOTES) { - polyMenu->setLabel(std::to_string(info.polyLimit)); + polyMenu->setEnabled(false); + polyModeMenu->setLabel("MONO"); + repaint(); } else { - polyMenu->setLabel("PART"); + polyMenu->setEnabled(true); + polyModeMenu->setLabel("POLY"); + if (info.hasIndependentPolyLimit) + { + polyMenu->setLabel(std::to_string(info.polyLimit)); + } + else + { + polyMenu->setLabel("PART"); + } + repaint(); } } @@ -181,4 +202,101 @@ void GroupSettingsCard::showPolyMenu() p.showMenuAsync(editor->defaultPopupMenuOptions()); } +void GroupSettingsCard::showPolyModeMenu() +{ + auto p = juce::PopupMenu(); + p.addSectionHeader("Group Voice/Note Mode"); + p.addSeparator(); + + p.addItem( + "Poly", true, + info.vmPlayModeInt == (uint32_t)engine::Engine::voiceManager_t::PlayMode::POLY_VOICES, + [w = juce::Component::SafePointer(this)]() { + if (!w) + return; + w->info.vmPlayModeInt = (uint32_t)engine::Engine::voiceManager_t::PlayMode::POLY_VOICES; + + w->rebuildFromInfo(); + w->sendToSerialization(messaging::client::UpdateGroupOutputInfoPolyphony{w->info}); + }); + p.addItem( + "Mono", true, + info.vmPlayModeInt == (uint32_t)engine::Engine::voiceManager_t::PlayMode::MONO_NOTES, + [w = juce::Component::SafePointer(this)]() { + if (!w) + return; + w->info.vmPlayModeInt = (uint32_t)engine::Engine::voiceManager_t::PlayMode::MONO_NOTES; + w->info.vmPlayModeFeaturesInt = + (uint64_t)engine::Engine::voiceManager_t::MonoPlayModeFeatures::NATURAL_MONO; + + w->rebuildFromInfo(); + w->sendToSerialization(messaging::client::UpdateGroupOutputInfoPolyphony{w->info}); + }); + p.addItem("Legato", false, false, [w = juce::Component::SafePointer(this)]() { + if (!w) + return; + }); + p.addSeparator(); + p.addSectionHeader("Mono Release Priority"); + // I could ovciously structure this better + p.addItem( + "Latest", false, + info.vmPlayModeFeaturesInt & + (uint32_t)engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_LATEST, + [w = juce::Component::SafePointer(this)]() { + if (!w) + return; + w->info.vmPlayModeInt = (uint32_t)engine::Engine::voiceManager_t::PlayMode::MONO_NOTES; + w->info.vmPlayModeFeaturesInt &= + ~(uint64_t) + engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_HIGHEST; + w->info.vmPlayModeFeaturesInt &= ~( + uint64_t)engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_LOWEST; + w->info.vmPlayModeFeaturesInt |= (uint64_t) + engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_LATEST; + + w->rebuildFromInfo(); + w->sendToSerialization(messaging::client::UpdateGroupOutputInfoPolyphony{w->info}); + }); + p.addItem( + "Highest", false, + info.vmPlayModeFeaturesInt & + (uint32_t)engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_HIGHEST, + [w = juce::Component::SafePointer(this)]() { + if (!w) + return; + w->info.vmPlayModeInt = (uint32_t)engine::Engine::voiceManager_t::PlayMode::MONO_NOTES; + w->info.vmPlayModeFeaturesInt |= (uint64_t) + engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_HIGHEST; + w->info.vmPlayModeFeaturesInt &= ~( + uint64_t)engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_LOWEST; + w->info.vmPlayModeFeaturesInt &= ~( + uint64_t)engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_LATEST; + + w->rebuildFromInfo(); + w->sendToSerialization(messaging::client::UpdateGroupOutputInfoPolyphony{w->info}); + }); + p.addItem( + "Lowest", false, + info.vmPlayModeFeaturesInt & + (uint32_t)engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_LOWEST, + [w = juce::Component::SafePointer(this)]() { + if (!w) + return; + w->info.vmPlayModeInt = (uint32_t)engine::Engine::voiceManager_t::PlayMode::MONO_NOTES; + w->info.vmPlayModeFeaturesInt |= (uint64_t) + engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_LOWEST; + w->info.vmPlayModeFeaturesInt &= ~( + uint64_t)engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_LOWEST; + w->info.vmPlayModeFeaturesInt &= + ~(uint64_t) + engine::Engine::voiceManager_t::MonoPlayModeFeatures::ON_RELEASE_TO_HIGHEST; + + w->rebuildFromInfo(); + w->sendToSerialization(messaging::client::UpdateGroupOutputInfoPolyphony{w->info}); + }); + + p.showMenuAsync(editor->defaultPopupMenuOptions()); +} + } // namespace scxt::ui::app::edit_screen \ No newline at end of file diff --git a/src-ui/app/edit-screen/components/GroupSettingsCard.h b/src-ui/app/edit-screen/components/GroupSettingsCard.h index 663a159a..c76ee2de 100644 --- a/src-ui/app/edit-screen/components/GroupSettingsCard.h +++ b/src-ui/app/edit-screen/components/GroupSettingsCard.h @@ -53,7 +53,7 @@ struct GroupSettingsCard : juce::Component, HasEditor std::unique_ptr midiGlyph, outputGlyph, polyGlygh, prioGlyph, glideGlpyh, volGlyph, panGlyph, tuneGlyph; std::unique_ptr pbLabel, SRCLabel; - std::unique_ptr polyMenu; + std::unique_ptr polyMenu, polyModeMenu; std::unique_ptr midiMenu, outputMenu, prioMenu, glideMenu, srcMenu; std::unique_ptr pbDnVal, pbUpDrag, @@ -67,6 +67,7 @@ struct GroupSettingsCard : juce::Component, HasEditor engine::Group::GroupOutputInfo &info; void showPolyMenu(); + void showPolyModeMenu(); }; } // namespace scxt::ui::app::edit_screen #endif // GROUPSETTINGSCARD_H diff --git a/src/configuration.h b/src/configuration.h index c5b4b49f..00622d1a 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -88,6 +88,7 @@ static constexpr bool uiStructure{false}; static constexpr bool groupZoneMutation{false}; static constexpr bool memoryPool{false}; static constexpr bool voiceResponder{false}; +static constexpr bool voiceLifecycle{false}; static constexpr bool generatorInitialization{false}; } // namespace log diff --git a/src/engine/engine.h b/src/engine/engine.h index 4e212296..1d07ba52 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -202,6 +202,8 @@ struct Engine : MoveableOnly, SampleRateSupport uint16_t channel, uint16_t key, int32_t noteId, float velocity); int32_t initializeMultipleVoices( + int32_t voiceCount, + const sst::voicemanager::VoiceInitInstructionsEntry::buffer_t &, sst::voicemanager::VoiceInitBufferEntry::buffer_t &voiceInitWorkingBuffer, uint16_t port, uint16_t channel, uint16_t key, int32_t noteId, float velocity, float retune); diff --git a/src/engine/engine_voice_responder.cpp b/src/engine/engine_voice_responder.cpp index 73c29dac..1f3de6fb 100644 --- a/src/engine/engine_voice_responder.cpp +++ b/src/engine/engine_voice_responder.cpp @@ -48,8 +48,11 @@ int32_t Engine::VoiceManagerResponder::beginVoiceCreationTransaction( auto &z = engine.zoneByPath(path); voiceCreationWorkingBuffer[voicesCreated] = {path, -1}; - if (z->parentGroup->outputInfo.hasIndependentPolyLimit) + if (z->parentGroup->outputInfo.hasIndependentPolyLimit || + z->parentGroup->outputInfo.vmPlayModeInt != + (uint32_t)Engine::voiceManager_t::PlayMode::POLY_VOICES) { + SCLOG_IF(voiceResponder, "-- Setting polyphony group to " << (uint64_t)z->parentGroup); buffer[idx].polyphonyGroup = (uint64_t)z->parentGroup; } else @@ -69,6 +72,8 @@ int32_t Engine::VoiceManagerResponder::beginVoiceCreationTransaction( } int32_t Engine::VoiceManagerResponder::initializeMultipleVoices( + int32_t voiceCount, + const sst::voicemanager::VoiceInitInstructionsEntry::buffer_t &voiceInstructionBuffer, sst::voicemanager::VoiceInitBufferEntry::buffer_t &voiceInitWorkingBuffer, uint16_t port, uint16_t channel, uint16_t key, int32_t noteId, float velocity, float retune) { @@ -76,10 +81,21 @@ int32_t Engine::VoiceManagerResponder::initializeMultipleVoices( assert(transactionVoiceCount > 0); auto useKey = engine.midikeyRetuner.remapKeyTo(channel, key); auto nts = transactionVoiceCount; + assert(nts == voiceCount); SCLOG_IF(voiceResponder, "voice initiation of " << nts << " voices"); int32_t actualCreated{0}; + int outIdx{0}; for (auto idx = 0; idx < nts; ++idx) { + if (voiceInstructionBuffer[idx].instruction == + sst::voicemanager::VoiceInitInstructionsEntry< + scxt::engine::Engine::VMConfig>::Instruction::SKIP) + { + SCLOG_IF(voiceResponder, "Skipping voice at index " << idx); + voiceInitWorkingBuffer[outIdx].voice = nullptr; + outIdx++; + continue; + } const auto &[path, variantIndex] = voiceCreationWorkingBuffer[idx]; auto &z = engine.zoneByPath(path); auto nbSampleLoadedInZone = z->getNumSampleLoaded(); @@ -95,7 +111,8 @@ int32_t Engine::VoiceManagerResponder::initializeMultipleVoices( v->attack(); actualCreated++; } - voiceInitWorkingBuffer[idx].voice = v; + voiceInitWorkingBuffer[outIdx].voice = v; + outIdx++; SCLOG_IF(voiceResponder, "-- Created single voice for single zone (" << std::hex << v << std::dec << ")"); } @@ -172,7 +189,8 @@ int32_t Engine::VoiceManagerResponder::initializeMultipleVoices( v->attack(); actualCreated++; } - voiceInitWorkingBuffer[idx].voice = v; + voiceInitWorkingBuffer[outIdx].voice = v; + outIdx++; } } } diff --git a/src/engine/group.cpp b/src/engine/group.cpp index dc3f92f5..5772a6d7 100644 --- a/src/engine/group.cpp +++ b/src/engine/group.cpp @@ -608,13 +608,30 @@ void Group::onRoutingChanged() { SCLOG_ONCE("Implement Group LFO modulator use o void Group::resetPolyAndPlaymode(engine::Engine &e) { - if (!outputInfo.hasIndependentPolyLimit) + if (outputInfo.vmPlayModeInt == (uint32_t)Engine::voiceManager_t::PlayMode::MONO_NOTES) { - // TODO we really want a 'clear' + if (outputInfo.vmPlayModeFeaturesInt == 0) + { + outputInfo.vmPlayModeFeaturesInt = + (uint64_t)Engine::voiceManager_t::MonoPlayModeFeatures::NATURAL_MONO; + } + auto pgrp = (uint64_t)this; + SCLOG_IF(voiceResponder, "Setting up mono group " << pgrp); + e.voiceManager.guaranteeGroup(pgrp); + e.voiceManager.setPlaymode(pgrp, Engine::voiceManager_t::PlayMode::MONO_NOTES, + outputInfo.vmPlayModeFeaturesInt); } else { - e.voiceManager.setPolyphonyGroupVoiceLimit((uint64_t)this, outputInfo.polyLimit); + assert(outputInfo.vmPlayModeInt == (uint32_t)Engine::voiceManager_t::PlayMode::POLY_VOICES); + if (!outputInfo.hasIndependentPolyLimit) + { + // TODO we really want a 'clear' + } + else + { + e.voiceManager.setPolyphonyGroupVoiceLimit((uint64_t)this, outputInfo.polyLimit); + } } } diff --git a/src/engine/group.h b/src/engine/group.h index d16fb07c..8553cbb3 100644 --- a/src/engine/group.h +++ b/src/engine/group.h @@ -86,6 +86,12 @@ struct Group : MoveableOnly, bool hasIndependentPolyLimit{false}; int32_t polyLimit{0}; + + // I wish I could define these as the enum + // but this includes before engine and template blah + // makes it not quite worth it + uint32_t vmPlayModeInt{0}; + uint64_t vmPlayModeFeaturesInt{0}; } outputInfo; GroupTriggerConditions triggerConditions; diff --git a/src/engine/zone.cpp b/src/engine/zone.cpp index 417afca0..4332a2da 100644 --- a/src/engine/zone.cpp +++ b/src/engine/zone.cpp @@ -104,9 +104,7 @@ template void Zone::processWithOS(scxt::engine::Engine &onto) for (int i = 0; i < cleanupIdx; ++i) { -#if DEBUG_VOICE_LIFECYCLE - SCLOG("Cleanup Voice at " << SCDBGV((int)toCleanUp[i]->key)); -#endif + SCLOG_IF(voiceLifecycle, "Cleanup Voice at " << SCD((int)toCleanUp[i]->key)); toCleanUp[i]->cleanupVoice(); } } diff --git a/src/json/engine_traits.h b/src/json/engine_traits.h index 8f1d19d5..798adae4 100644 --- a/src/json/engine_traits.h +++ b/src/json/engine_traits.h @@ -272,12 +272,72 @@ SC_STREAMDEF(scxt::engine::GroupTriggerConditions, SC_FROM({ findIf(v, "conj", to.conjunctions); })); +/* + * PlayMode uses engine vm typedefs so is a bit of a special case + */ +inline std::string groupInfoPlayModeTo(uint32_t p) +{ + if (p == (uint32_t)engine::Engine::voiceManager_t::PlayMode::POLY_VOICES) + return "p"; + if (p == (uint32_t)engine::Engine::voiceManager_t::PlayMode::MONO_NOTES) + return "m"; + return "p"; +} +inline uint32_t groupInfoPlayModeFrom(const std::string &s) +{ + if (s == "m") + return (uint32_t)engine::Engine::voiceManager_t::PlayMode::MONO_NOTES; + return (uint32_t)engine::Engine::voiceManager_t::PlayMode::POLY_VOICES; +} + +// this is kinda gross, but its nov 6 2024 and i just want something we can make stable +#define PMTS(pm, s) \ + if (x & (uint64_t)engine::Engine::voiceManager_t::MonoPlayModeFeatures::pm) \ + oss << "|" << s "|"; +inline std::string groupInfoPlayModeFeatureTo(uint64_t x) +{ + std::ostringstream oss; + PMTS(MONO_RETRIGGER, "mr"); + PMTS(MONO_LEGATO, "ml"); + PMTS(ON_RELEASE_TO_LATEST, "rl"); + PMTS(ON_RELEASE_TO_HIGHEST, "rh"); + PMTS(ON_RELEASE_TO_LOWEST, "rh"); + return oss.str(); +} +#undef PMTS +#define PMTS(pm, s) \ + { \ + std::string q = std::string("|") + s + "|"; \ + if (x.find(q) != std::string::npos) \ + { \ + res |= (uint64_t)engine::Engine::voiceManager_t::MonoPlayModeFeatures::pm; \ + } \ + } + +inline uint64_t groupInfoPlayModeFeatureFrom(const std::string &x) +{ + uint64_t res{0}; + PMTS(MONO_RETRIGGER, "mr"); + PMTS(MONO_LEGATO, "ml"); + PMTS(ON_RELEASE_TO_LATEST, "rl"); + PMTS(ON_RELEASE_TO_HIGHEST, "rh"); + PMTS(ON_RELEASE_TO_LOWEST, "rh"); + return res; +} +#undef PMTS + SC_STREAMDEF(scxt::engine::Group::GroupOutputInfo, SC_FROM({ - v = {{"amplitude", t.amplitude}, {"pan", t.pan}, - {"oversample", t.oversample}, {"velocitySensitivity", t.velocitySensitivity}, - {"muted", t.muted}, {"procRouting", t.procRouting}, - {"routeTo", (int)t.routeTo}, {"hip", t.hasIndependentPolyLimit}, - {"pl", t.polyLimit}}; + v = {{"amplitude", t.amplitude}, + {"pan", t.pan}, + {"oversample", t.oversample}, + {"velocitySensitivity", t.velocitySensitivity}, + {"muted", t.muted}, + {"procRouting", t.procRouting}, + {"routeTo", (int)t.routeTo}, + {"hip", t.hasIndependentPolyLimit}, + {"pl", t.polyLimit}, + {"vpm", groupInfoPlayModeTo(t.vmPlayModeInt)}, + {"vpf", groupInfoPlayModeFeatureTo(t.vmPlayModeFeaturesInt)}}; }), SC_TO({ findIf(v, "amplitude", result.amplitude); @@ -291,6 +351,12 @@ SC_STREAMDEF(scxt::engine::Group::GroupOutputInfo, SC_FROM({ findIf(v, "hip", result.hasIndependentPolyLimit); findIf(v, "pl", result.polyLimit); result.routeTo = (engine::BusAddress)(rt); + + std::string tmp; + findIf(v, "vpm", tmp); + result.vmPlayModeInt = groupInfoPlayModeFrom(tmp); + findIf(v, "vpf", tmp); + result.vmPlayModeFeaturesInt = groupInfoPlayModeFeatureFrom(tmp); })); SC_STREAMDEF(scxt::engine::Group, SC_FROM({ diff --git a/src/voice/voice.cpp b/src/voice/voice.cpp index eec4bd79..ff4451b1 100644 --- a/src/voice/voice.cpp +++ b/src/voice/voice.cpp @@ -615,11 +615,15 @@ template bool Voice::processWithOS() } else { + SCLOG_IF(voiceLifecycle, "Voice terminating due to AEG at " << SCD(key)); + isVoicePlaying = false; } if (terminationSequence == 0) { + SCLOG_IF(voiceLifecycle, "Voice terminating due to termiantion sequence " << SCD(key)); + isVoicePlaying = false; } return true;