From 7a3842a4d79b53b4378fdf4a454230c67b4ff7b7 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Tue, 5 Nov 2024 12:03:07 -0500 Subject: [PATCH] Mono Note in addition to Poly Voice mode 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;