From d6df8b3c5f346aac8713b96db6fe231046b869f1 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 27 Nov 2024 08:11:37 -0500 Subject: [PATCH] Complete Multi-Sel Consistency (LFO and Routing) (#1467) Closes #761 If LFO shapes or Routing shapes are different pop a consistent button like we do for processors or mod matrix rows --- src-ui/app/edit-screen/components/LFOPane.cpp | 59 +++++++++++++++++-- src-ui/app/edit-screen/components/LFOPane.h | 2 + .../app/edit-screen/components/OutputPane.cpp | 49 ++++++++++++--- .../app/edit-screen/components/OutputPane.h | 2 + src/engine/zone.h | 1 + src/json/engine_traits.h | 2 + src/json/modulation_traits.h | 3 + src/messaging/client/client_serial.h | 3 + src/messaging/client/group_or_zone_messages.h | 10 ++-- src/messaging/client/structure_messages.h | 13 ++++ src/messaging/client/zone_messages.h | 13 ++++ src/modulation/modulator_storage.h | 2 + src/selection/selection_manager.cpp | 45 +++++++++++++- 13 files changed, 180 insertions(+), 24 deletions(-) diff --git a/src-ui/app/edit-screen/components/LFOPane.cpp b/src-ui/app/edit-screen/components/LFOPane.cpp index e31cdb30..44b8135f 100644 --- a/src-ui/app/edit-screen/components/LFOPane.cpp +++ b/src-ui/app/edit-screen/components/LFOPane.cpp @@ -43,6 +43,7 @@ #include "modulation/modulators/steplfo.h" #include "app/edit-screen/EditScreen.h" #include "app/shared/MenuValueTypein.h" +#include "sst/jucegui/components/TextPushButton.h" namespace scxt::ui::app::edit_screen { @@ -744,7 +745,45 @@ struct MSEGLFOPane : juce::Component } }; -// TODO: A Million things of course +struct ConsistencyLFOPane : juce::Component +{ + LfoPane *parent{nullptr}; + std::unique_ptr conLabel; + std::unique_ptr conButton; + ConsistencyLFOPane(LfoPane *p) : parent(p) + { + conLabel = std::make_unique(); + conLabel->setText("LFO Type is Inconsistent across zone selection"); + addAndMakeVisible(*conLabel); + + conButton = std::make_unique(); + conButton->setLabel("Label This"); + conButton->setOnCallback([w = juce::Component::SafePointer(this)]() { + if (!w) + return; + w->parent->modulatorShapeA->setValueFromGUI(w->parent->modulatorShapeA->getValue()); + }); + addAndMakeVisible(*conButton); + } + + void resized() override + { + auto b = getLocalBounds().reduced(10).withTrimmedTop(20).withHeight(24); + conLabel->setBounds(b); + b = b.translated(0, 30); + conButton->setBounds(b); + } + + void visibilityChanged() override + { + auto &ms = parent->modulatorStorageData[parent->selectedTab]; + if (isVisible()) + { + conButton->setLabel("Set all LFO shapes to " + + parent->modulatorShapeA->getValueAsString()); + } + } +}; LfoPane::LfoPane(SCXTEditor *e, bool fz) : sst::jucegui::components::NamedPanel(""), HasEditor(e), forZone(fz) @@ -839,6 +878,9 @@ void LfoPane::rebuildPanelComponents() curveLfoPane = std::make_unique(this); getContentAreaComponent()->addChildComponent(*curveLfoPane); + consistencyLfoPane = std::make_unique(this); + getContentAreaComponent()->addChildComponent(*consistencyLfoPane); + sfac::attach(ms, ms.modulatorShape, this, modulatorShapeA, modulatorShape, forZone, selectedTab); connectors::addGuiStep(*modulatorShapeA, @@ -896,20 +938,25 @@ void LfoPane::repositionContentAreaComponents() envLfoPane->setBounds(paneArea); msegLfoPane->setBounds(paneArea); curveLfoPane->setBounds(paneArea); + consistencyLfoPane->setBounds(paneArea); modulatorShape->setBounds(mg, 0, paneArea.getWidth() / 4, ht); } void LfoPane::setSubPaneVisibility() { - if (!stepLfoPane || !msegLfoPane || !curveLfoPane) + if (!stepLfoPane || !msegLfoPane || !curveLfoPane || !consistencyLfoPane || !envLfoPane) return; auto &ms = modulatorStorageData[selectedTab]; - stepLfoPane->setVisible(ms.isStep()); - msegLfoPane->setVisible(ms.isMSEG()); - curveLfoPane->setVisible(ms.isCurve()); - envLfoPane->setVisible(ms.isEnv()); + auto con = ms.modulatorConsistent; + + stepLfoPane->setVisible(ms.isStep() && con); + msegLfoPane->setVisible(ms.isMSEG() && con); + curveLfoPane->setVisible(ms.isCurve() && con); + envLfoPane->setVisible(ms.isEnv() && con); + consistencyLfoPane->setVisible(!con); + modulatorShape->setVisible(con); } void LfoPane::pickPresets() diff --git a/src-ui/app/edit-screen/components/LFOPane.h b/src-ui/app/edit-screen/components/LFOPane.h index ca43b9e8..3a9b9e50 100644 --- a/src-ui/app/edit-screen/components/LFOPane.h +++ b/src-ui/app/edit-screen/components/LFOPane.h @@ -51,6 +51,7 @@ struct StepLFOPane; struct CurveLFOPane; struct ENVLFOPane; struct MSEGLFOPane; +struct ConsistencyLFOPane; struct LfoPane : sst::jucegui::components::NamedPanel, app::HasEditor { @@ -76,6 +77,7 @@ struct LfoPane : sst::jucegui::components::NamedPanel, app::HasEditor std::unique_ptr curveLfoPane; std::unique_ptr envLfoPane; std::unique_ptr msegLfoPane; + std::unique_ptr consistencyLfoPane; bool forZone{true}; diff --git a/src-ui/app/edit-screen/components/OutputPane.cpp b/src-ui/app/edit-screen/components/OutputPane.cpp index deac3396..b9e552f3 100644 --- a/src-ui/app/edit-screen/components/OutputPane.cpp +++ b/src-ui/app/edit-screen/components/OutputPane.cpp @@ -58,6 +58,8 @@ template struct ProcTab : juce::Component, HasEditor { OutputPane *outputPane{nullptr}; std::unique_ptr procRouting; + std::unique_ptr consistentButton; + std::unique_ptr consistentLabel; ProcTab(SCXTEditor *e, OutputPane *pane) : HasEditor(e), outputPane(pane), info(OTTraits::outputInfo(e)) @@ -70,18 +72,29 @@ template struct ProcTab : juce::Component, HasEditor w->selectNewProcRouting(); }); addAndMakeVisible(*procRouting); + + consistentLabel = std::make_unique(); + consistentLabel->setText("Routing inconsistent across selection"); + addChildComponent(*consistentLabel); + consistentButton = std::make_unique(); + consistentButton->setLabel("Make Consistent"); + consistentButton->setOnCallback([w = juce::Component::SafePointer(this)]() { + if (w) + { + w->template sendSingleToSerialization( + w->info, w->info.procRouting); + } + }); + addChildComponent(*consistentButton); } - void paint(juce::Graphics &g) + + void resized() { - auto ft = editor->style()->getFont(jcmp::Label::Styles::styleClass, - jcmp::Label::Styles::labelfont); - g.setFont(ft.withHeight(14)); - g.setColour(juce::Colours::white); - g.drawText("Proc Routing", getLocalBounds().withHeight(30), juce::Justification::centred); + procRouting->setBounds(getLocalBounds().reduced(3, 5).withHeight(24)); + consistentLabel->setBounds(procRouting->getBounds()); + consistentButton->setBounds(procRouting->getBounds().translated(0, 30)); } - void resized() { procRouting->setBounds(getLocalBounds().reduced(3, 5).withHeight(24)); } - std::string getRoutingName(typename OTTraits::route_t r) { auto zn = std::string(); @@ -91,6 +104,24 @@ template struct ProcTab : juce::Component, HasEditor zn = scxt::engine::Group::getProcRoutingPathDisplayName(r); return zn; } + void updateProcRoutingFromInfo() + { + updateProcRoutingLabel(); + if constexpr (OTTraits::forZone) + { + auto c = info.procRoutingConsistent; + procRouting->setVisible(c); + for (int i = 0; i < nOuts; ++i) + levelK[i]->setVisible(c); + consistentButton->setVisible(!c); + consistentLabel->setVisible(!c); + + if (!c) + { + consistentButton->setLabel("Set to " + getRoutingName(info.procRouting)); + } + } + } void updateProcRoutingLabel() { procRouting->setLabel(getRoutingName(info.procRouting)); @@ -350,7 +381,7 @@ template void OutputPane::updateFromOutputInfo() output->updateRoutingLabel(); output->repaint(); - proc->updateProcRoutingLabel(); + proc->updateProcRoutingFromInfo(); proc->repaint(); } diff --git a/src-ui/app/edit-screen/components/OutputPane.h b/src-ui/app/edit-screen/components/OutputPane.h index 1c03dad2..9ecfd64a 100644 --- a/src-ui/app/edit-screen/components/OutputPane.h +++ b/src-ui/app/edit-screen/components/OutputPane.h @@ -47,6 +47,7 @@ struct OutPaneZoneTraits using floatMsg_t = scxt::messaging::client::UpdateZoneOutputFloatValue; using int16Msg_t = scxt::messaging::client::UpdateZoneOutputInt16TValue; + using int16RefreshMsg_t = scxt::messaging::client::UpdateZoneOutputInt16TValueThenRefresh; static engine::Zone::ZoneOutputInfo &outputInfo(SCXTEditor *e); }; @@ -60,6 +61,7 @@ struct OutPaneGroupTraits using floatMsg_t = scxt::messaging::client::UpdateGroupOutputFloatValue; using int16Msg_t = scxt::messaging::client::UpdateGroupOutputInt16TValue; + using int16RefreshMsg_t = int16Msg_t; // for now. static engine::Group::GroupOutputInfo &outputInfo(SCXTEditor *e); }; diff --git a/src/engine/zone.h b/src/engine/zone.h index 0d425a14..0907c7bb 100644 --- a/src/engine/zone.h +++ b/src/engine/zone.h @@ -163,6 +163,7 @@ struct Zone : MoveableOnly, HasGroupZoneProcessors, SampleRateSuppor float amplitude{1.f}, pan{0.f}; bool muted{false}; ProcRoutingPath procRouting{procRoute_linear}; + bool procRoutingConsistent{true}; BusAddress routeTo{DEFAULT_BUS}; } outputInfo; static_assert(std::is_standard_layout::value); diff --git a/src/json/engine_traits.h b/src/json/engine_traits.h index 798adae4..79987fb6 100644 --- a/src/json/engine_traits.h +++ b/src/json/engine_traits.h @@ -397,6 +397,7 @@ SC_STREAMDEF(scxt::engine::Group, SC_FROM({ SC_STREAMDEF(scxt::engine::Zone::ZoneOutputInfo, SC_FROM({ v = {{"amp", t.amplitude}, {"pan", t.pan}, {"to", (int)t.routeTo}}; + addUnlessDefault(v, "prc", true, t.procRoutingConsistent); addUnlessDefault(v, "prt", engine::Zone::ProcRoutingPath::procRoute_linear, t.procRouting); addUnlessDefault(v, "muted", false, t.muted); @@ -406,6 +407,7 @@ SC_STREAMDEF(scxt::engine::Zone::ZoneOutputInfo, SC_FROM({ findIf(v, {"amp", "amplitude"}, zo.amplitude); findIf(v, "pan", zo.pan); findOrSet(v, "muted", false, zo.muted); + findOrSet(v, "prc", true, zo.procRoutingConsistent); findOrSet(v, {"prt", "procRouting"}, engine::Zone::ProcRoutingPath::procRoute_linear, zo.procRouting); int rt{engine::BusAddress::DEFAULT_BUS}; diff --git a/src/json/modulation_traits.h b/src/json/modulation_traits.h index cc14a142..f1b6a86f 100644 --- a/src/json/modulation_traits.h +++ b/src/json/modulation_traits.h @@ -136,6 +136,8 @@ SC_STREAMDEF(scxt::modulation::ModulatorStorage, SC_FROM({ {"curveLfoStorage", t.curveLfoStorage}, {"stepLfoStorage", t.stepLfoStorage}, {"envLfoStorage", t.envLfoStorage}}; + + addUnlessDefault(v, "cn", true, t.modulatorConsistent); }), SC_TO({ const auto &object = v.get_object(); @@ -147,6 +149,7 @@ SC_STREAMDEF(scxt::modulation::ModulatorStorage, SC_FROM({ findIf(v, "curveLfoStorage", result.curveLfoStorage); findIf(v, "stepLfoStorage", result.stepLfoStorage); findIf(v, "envLfoStorage", result.envLfoStorage); + findOrSet(v, "cn", true, result.modulatorConsistent); result.configureCalculatedState(); })) diff --git a/src/messaging/client/client_serial.h b/src/messaging/client/client_serial.h index 33b204b2..fc9d554c 100644 --- a/src/messaging/client/client_serial.h +++ b/src/messaging/client/client_serial.h @@ -97,6 +97,7 @@ enum ClientToSerializationMessagesIds c2s_update_zone_output_float_value, c2s_update_zone_output_int16_t_value, + c2s_update_zone_output_int16_t_value_then_refresh, c2s_update_zone_routing_row, @@ -108,6 +109,8 @@ enum ClientToSerializationMessagesIds c2s_update_group_trigger_conditions, + c2s_request_zone_data_refresh, + // #1141 done up until here. Below this point the name rubric above isn't confirmed in place c2s_request_pgz_structure, // ? diff --git a/src/messaging/client/group_or_zone_messages.h b/src/messaging/client/group_or_zone_messages.h index 9504da5b..ef5c9610 100644 --- a/src/messaging/client/group_or_zone_messages.h +++ b/src/messaging/client/group_or_zone_messages.h @@ -66,15 +66,13 @@ CLIENT_TO_SERIAL_CONSTRAINED( auto forZone = std::get<0>(payload); if (forZone) { - // ToDo: Have to do the group side of this later + // ToDo: This is a wee bit heavy handed. Refactor so we can just send lfo + // and mod matrix (look at the git history before this for matrix). auto lz = eng.getSelectionManager()->currentLeadZone(eng); if (lz.has_value()) { - auto &z = - eng.getPatch()->getPart(lz->part)->getGroup(lz->group)->getZone(lz->zone); - serializationSendToClient(messaging::client::s2c_update_zone_matrix_metadata, - voice::modulation::getVoiceMatrixMetadata(*z), - *(eng.getMessageController())); + eng.getSelectionManager()->sendDisplayDataForZonesBasedOnLead( + lz->part, lz->group, lz->zone); } } else diff --git a/src/messaging/client/structure_messages.h b/src/messaging/client/structure_messages.h index 60113766..7cc13d6d 100644 --- a/src/messaging/client/structure_messages.h +++ b/src/messaging/client/structure_messages.h @@ -415,6 +415,19 @@ inline void doDeactivatePart(int part, messaging::MessageController &cont) } CLIENT_TO_SERIAL(DeactivatePart, c2s_deactivate_part, int32_t, doDeactivatePart(payload, cont)); +inline void doRequestZoneDataRefresh(const engine::Engine &eng, messaging::MessageController &cont) +{ + auto lz = eng.getSelectionManager()->currentLeadZone(eng); + if (lz.has_value()) + { + eng.getSelectionManager()->sendDisplayDataForZonesBasedOnLead(lz->part, lz->group, + lz->zone); + } +} + +CLIENT_TO_SERIAL(RequestZoneDataRefresh, c2s_request_zone_data_refresh, bool, + doRequestZoneDataRefresh(engine, cont)); + } // namespace scxt::messaging::client #endif // SHORTCIRCUIT_STRUCTURE_MESSAGES_H diff --git a/src/messaging/client/zone_messages.h b/src/messaging/client/zone_messages.h index 6ffc782b..19c3b26e 100644 --- a/src/messaging/client/zone_messages.h +++ b/src/messaging/client/zone_messages.h @@ -172,6 +172,19 @@ CLIENT_TO_SERIAL_CONSTRAINED(UpdateZoneOutputInt16TValue, c2s_update_zone_output detail::updateZoneMemberValue(&engine::Zone::outputInfo, payload, engine, cont)); +CLIENT_TO_SERIAL_CONSTRAINED( + UpdateZoneOutputInt16TValueThenRefresh, c2s_update_zone_output_int16_t_value_then_refresh, + detail::diffMsg_t, engine::Zone::ZoneOutputInfo, + detail::updateZoneMemberValue( + &engine::Zone::outputInfo, payload, engine, cont, [](auto const &eng) { + auto lz = eng.getSelectionManager()->currentLeadZone(eng); + if (lz.has_value()) + { + eng.getSelectionManager()->sendDisplayDataForZonesBasedOnLead(lz->part, lz->group, + lz->zone); + } + })); + using addBlankZonePayload_t = std::array; inline void doAddBlankZone(const addBlankZonePayload_t &payload, engine::Engine &engine, MessageController &cont) diff --git a/src/modulation/modulator_storage.h b/src/modulation/modulator_storage.h index c832bb81..cab44e36 100644 --- a/src/modulation/modulator_storage.h +++ b/src/modulation/modulator_storage.h @@ -126,6 +126,8 @@ struct ModulatorStorage inline bool isMSEG() const { return modulatorShape == MSEG; } inline bool isEnv() const { return modulatorShape == LFO_ENV; } inline bool isCurve() const { return !isStep() && !isEnv() && !isMSEG(); } + + bool modulatorConsistent{true}; }; inline double secondsToNormalizedEnvTime(double s) diff --git a/src/selection/selection_manager.cpp b/src/selection/selection_manager.cpp index 10d5c85f..e39eb8dc 100644 --- a/src/selection/selection_manager.cpp +++ b/src/selection/selection_manager.cpp @@ -513,6 +513,27 @@ void SelectionManager::sendDisplayDataForZonesBasedOnLead(int p, int g, int z) for (int i = 0; i < engine::lfosPerZone; ++i) { + auto rsh = zp->modulatorStorage[i].modulatorShape; + auto con = true; + if (allSelectedZones[selectedPart].size() > 1) + { + for (const auto &sz : allSelectedZones[selectedPart]) + { + const auto &zsh = engine.getPatch() + ->getPart(sz.part) + ->getGroup(sz.group) + ->getZone(sz.zone) + ->modulatorStorage[i] + .modulatorShape; + if (zsh != rsh) + { + con = false; + break; + } + } + } + + zp->modulatorStorage[i].modulatorConsistent = con; serializationSendToClient( cms::s2c_update_group_or_zone_individual_modulator_storage, cms::indexedModulatorStorageUpdate_t{true, true, i, zp->modulatorStorage[i]}, @@ -549,9 +570,27 @@ void SelectionManager::sendDisplayDataForZonesBasedOnLead(int p, int g, int z) configureAndSendZoneModMatrixMetadata(p, g, z); - serializationSendToClient(cms::s2c_update_zone_output_info, - cms::zoneOutputInfoUpdate_t{true, zp->outputInfo}, - *(engine.getMessageController())); + // Update across selections here to see if the routing is consistent + auto rt = zp->outputInfo.procRouting; + auto con = true; + if (allSelectedZones[selectedPart].size() > 1) + { + for (const auto &sz : allSelectedZones[selectedPart]) + { + const auto &zpr = engine.getPatch() + ->getPart(sz.part) + ->getGroup(sz.group) + ->getZone(sz.zone) + ->outputInfo.procRouting; + + if (zpr != rt) + { + con = false; + break; + } + } + } + zp->outputInfo.procRoutingConsistent = con; serializationSendToClient(cms::s2c_update_zone_output_info, cms::zoneOutputInfoUpdate_t{true, zp->outputInfo},