diff --git a/Source/GUI/Display.h b/Source/GUI/Display.h index a850432..5ff9410 100644 --- a/Source/GUI/Display.h +++ b/Source/GUI/Display.h @@ -12,8 +12,8 @@ namespace serum class Display : public juce::Component { public: - void setTitle (const juce::String& t) { title = t; repaint(); } - void setValue (const juce::String& v) { value = v; repaint(); } + void setTitle (const juce::String& t) { if (title != t) { title = t; repaint(); } } + void setValue (const juce::String& v) { if (value != v) { value = v; repaint(); } } void paint (juce::Graphics& g) override; diff --git a/Source/GUI/EnvelopeDisplay.cpp b/Source/GUI/EnvelopeDisplay.cpp index 7755be2..d2831ab 100644 --- a/Source/GUI/EnvelopeDisplay.cpp +++ b/Source/GUI/EnvelopeDisplay.cpp @@ -5,7 +5,10 @@ namespace serum void EnvelopeDisplay::setParams (float a, float d, float s, float r, float c) { + if (attack == a && decay == d && sustain == s && release == r && curve == c) + return; attack = a; decay = d; sustain = s; release = r; curve = c; + repaint(); } void EnvelopeDisplay::paint (juce::Graphics& g) @@ -19,7 +22,7 @@ void EnvelopeDisplay::paint (juce::Graphics& g) const float atkShape = 0.3f + curve * 2.7f; const float decShape = 3.0f - curve * 2.7f; - // Normalise durations for display (attack 0..1, decay 0..0.6, release 0..0.6). + // Normalise durations for display, allowing a short sustain plateau. const float aSec = maps::toSeconds (attack); const float dSec = maps::toSeconds (decay); const float rSec = maps::toSeconds (release); @@ -36,7 +39,6 @@ void EnvelopeDisplay::paint (juce::Graphics& g) juce::Path path; path.startNewSubPath (left, bottom); - path.lineTo (left, top); // Attack (curve-shaped). const int steps = 48; @@ -44,7 +46,7 @@ void EnvelopeDisplay::paint (juce::Graphics& g) for (int i = 0; i <= steps; ++i) { const float p = (float) i / steps; - const float y = top + (bottom - top) * std::pow (p, atkShape); + const float y = bottom - (bottom - top) * std::pow (p, atkShape); path.lineTo (left + p * (peakX - left), y); } @@ -53,7 +55,7 @@ void EnvelopeDisplay::paint (juce::Graphics& g) for (int i = 0; i <= steps; ++i) { const float p = (float) i / steps; - const float y = sustainY + (bottom - sustainY) * std::pow (1.0f - p, decShape); + const float y = sustainY + (top - sustainY) * std::pow (1.0f - p, decShape); path.lineTo (peakX + p * (decX - peakX), y); } path.lineTo (decX, sustainY); @@ -64,7 +66,7 @@ void EnvelopeDisplay::paint (juce::Graphics& g) for (int i = 0; i <= steps; ++i) { const float p = (float) i / steps; - const float y = sustainY + (bottom - sustainY) * std::pow (1.0f - p, decShape); + const float y = bottom - (bottom - sustainY) * std::pow (1.0f - p, decShape); path.lineTo (relStartX + p * (right - relStartX), y); } diff --git a/Source/GUI/FilterDisplay.cpp b/Source/GUI/FilterDisplay.cpp index bbf87ba..fc92730 100644 --- a/Source/GUI/FilterDisplay.cpp +++ b/Source/GUI/FilterDisplay.cpp @@ -37,7 +37,10 @@ float FilterDisplay::magnitude (float freqHz, float cutoffHz, float res, int typ void FilterDisplay::setParams (int t, float c, float r, float d, int s) { + if (type == t && cutoff == c && res == r && drive == d && slope == s) + return; type = t; cutoff = c; res = r; drive = d; slope = s; + repaint(); } void FilterDisplay::paint (juce::Graphics& g) diff --git a/Source/GUI/FilterDisplay.h b/Source/GUI/FilterDisplay.h index c4f2ee3..a214d23 100644 --- a/Source/GUI/FilterDisplay.h +++ b/Source/GUI/FilterDisplay.h @@ -8,13 +8,15 @@ namespace serum { // =========================================================================== -// Filter frequency-response view (magnitude vs log frequency). +// Approximate filter frequency-response view (magnitude vs log frequency). // =========================================================================== -class FilterDisplay : public juce::Component +class FilterDisplay : public juce::Component, + public juce::SettableTooltipClient { public: + FilterDisplay() { setTooltip ("Approximate response preview; slope, drive and modulation are not modelled."); } void setParams (int type, float cutoff, float res, float drive, int slope); - void setEnabled (bool e) { enabled = e; } + void setEnabled (bool e) { if (enabled != e) { enabled = e; repaint(); } } void paint (juce::Graphics& g) override; diff --git a/Source/GUI/LFODisplay.cpp b/Source/GUI/LFODisplay.cpp index a3f9e4e..dbbae37 100644 --- a/Source/GUI/LFODisplay.cpp +++ b/Source/GUI/LFODisplay.cpp @@ -5,10 +5,16 @@ namespace serum void LFODisplay::setShapeData (const std::vector& data, int s) { - steps = juce::jlimit (2, 64, s); - shapeData = data; - if ((int) shapeData.size() < 64) - shapeData.resize (64, 0.0f); + const int newSteps = juce::jlimit (2, 64, s); + const size_t dataSize = std::min (data.size(), size_t (64)); + if (steps == newSteps && shapeData.size() == 64 + && std::equal (data.begin(), data.begin() + dataSize, shapeData.begin()) + && std::all_of (shapeData.begin() + dataSize, shapeData.end(), [] (float v) { return v == 0.0f; })) + return; + + steps = newSteps; + shapeData.assign (data.begin(), data.begin() + dataSize); + shapeData.resize (64, 0.0f); repaint(); } diff --git a/Source/GUI/LFODisplay.h b/Source/GUI/LFODisplay.h index bc8f137..8d6eec2 100644 --- a/Source/GUI/LFODisplay.h +++ b/Source/GUI/LFODisplay.h @@ -13,7 +13,7 @@ namespace serum class LFODisplay : public juce::Component { public: - void setShape (int s) { shape = s; repaint(); } + void setShape (int s) { if (shape != s) { shape = s; repaint(); } } void setShapeData (const std::vector& data, int steps); void setOnShapeEdited (std::function&, int)> cb) { onEdited = std::move (cb); } diff --git a/Source/GUI/ToggleButton.cpp b/Source/GUI/ToggleButton.cpp index 614e63e..d9c6f42 100644 --- a/Source/GUI/ToggleButton.cpp +++ b/Source/GUI/ToggleButton.cpp @@ -10,12 +10,12 @@ ToggleButton::ToggleButton (const juce::String& lbl) : label (lbl) void ToggleButton::attach (juce::AudioProcessorValueTreeState& apvts, const juce::String& paramId) { - param = apvts.getParameter (paramId); - if (param != nullptr) + attachment.reset(); + if (auto* param = apvts.getParameter (paramId)) { - state = param->getValue() > 0.5f; attachment = std::make_unique (*param, [this] (float newValue) { setToggleState (newValue > 0.5f); }); + attachment->sendInitialUpdate(); } } @@ -60,9 +60,9 @@ void ToggleButton::mouseDown (const juce::MouseEvent&) { onClick (newState); } - else if (param != nullptr) + else if (attachment != nullptr) { - param->setValueNotifyingHost (newState ? 1.0f : 0.0f); + attachment->setValueAsCompleteGesture (newState ? 1.0f : 0.0f); } setToggleState (newState); diff --git a/Source/GUI/ToggleButton.h b/Source/GUI/ToggleButton.h index 05ea149..53752de 100644 --- a/Source/GUI/ToggleButton.h +++ b/Source/GUI/ToggleButton.h @@ -35,7 +35,6 @@ public: private: bool state = false; juce::String label; - juce::RangedAudioParameter* param = nullptr; std::unique_ptr attachment; std::function onClick; juce::Colour onColour = theme::accent; diff --git a/Source/GUI/WaveformDisplay.h b/Source/GUI/WaveformDisplay.h index d5faa77..6bc58ef 100644 --- a/Source/GUI/WaveformDisplay.h +++ b/Source/GUI/WaveformDisplay.h @@ -13,10 +13,10 @@ namespace serum class WaveformDisplay : public juce::Component { public: - void setWavetables (const WavetableLibrary* lib) { wtLib = lib; } - void setWaveIndex (int index) { wave = index; } - void setFramePosition (float pos) { wtPos = pos; } - void setEnabled (bool e) { enabled = e; } + void setWavetables (const WavetableLibrary* lib) { if (wtLib != lib) { wtLib = lib; repaint(); } } + void setWaveIndex (int index) { if (wave != index) { wave = index; repaint(); } } + void setFramePosition (float pos) { if (wtPos != pos) { wtPos = pos; repaint(); } } + void setEnabled (bool e) { if (enabled != e) { enabled = e; repaint(); } } void paint (juce::Graphics& g) override; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 3327f39..131cf8e 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -94,18 +94,25 @@ namespace knobs[(size_t) i]->setBounds (x + i * (w + gap), y, w, h); } - const char* kFxType[8] = { ids::fx1Type, ids::fx2Type, ids::fx3Type, ids::fx4Type, - ids::fx5Type, ids::fx6Type, ids::fx7Type, ids::fx8Type }; - const char* kFxMix[8] = { ids::fx1Mix, ids::fx2Mix, ids::fx3Mix, ids::fx4Mix, - ids::fx5Mix, ids::fx6Mix, ids::fx7Mix, ids::fx8Mix }; - const char* kFxP1[8] = { ids::fx1P1, ids::fx2P1, ids::fx3P1, ids::fx4P1, - ids::fx5P1, ids::fx6P1, ids::fx7P1, ids::fx8P1 }; - const char* kFxP2[8] = { ids::fx1P2, ids::fx2P2, ids::fx3P2, ids::fx4P2, - ids::fx5P2, ids::fx6P2, ids::fx7P2, ids::fx8P2 }; - const char* kFxP3[8] = { ids::fx1P3, ids::fx2P3, ids::fx3P3, ids::fx4P3, - ids::fx5P3, ids::fx6P3, ids::fx7P3, ids::fx8P3 }; - const char* kFxP4[8] = { ids::fx1P4, ids::fx2P4, ids::fx3P4, ids::fx4P4, - ids::fx5P4, ids::fx6P4, ids::fx7P4, ids::fx8P4 }; + constexpr auto& kFxType = paramIds::fxType; + constexpr auto& kFxMix = paramIds::fxMix; + constexpr auto& kFxP1 = paramIds::fxP1; + constexpr auto& kFxP2 = paramIds::fxP2; + constexpr auto& kFxP3 = paramIds::fxP3; + constexpr auto& kFxP4 = paramIds::fxP4; + + constexpr const char* kFxParamNames[(int) FxType::Count][4] = { + { "P1", "P2", "P3", "P4" }, + { "Intensity", "Low Amount", "High Amount", "Output" }, + { "Rate", "Depth", "Width", "Unused" }, + { "Rate", "Depth", "Feedback", "Unused" }, + { "Rate", "Depth", "Feedback", "Stages" }, + { "Drive", "Shape", "Tone", "Output" }, + { "Low Gain", "Mid Gain", "High Gain", "Mid Freq" }, + { "Threshold", "Ratio", "Attack", "Release" }, + { "Time", "Feedback", "Damping", "Ping-Pong" }, + { "Size", "Damping", "Width", "Predelay" } + }; } // --------------------------------------------------------------------------- @@ -131,7 +138,6 @@ PluginEditor::PluginEditor (SerumAltAudioProcessor& p) buildMacroTab(); setTab (0); - applyUiScale(); startTimerHz (30); } @@ -163,9 +169,11 @@ Knob* PluginEditor::makeKnob (juce::Component* parent, const juce::String& name, const juce::String& paramId, std::function fmt, const juce::String& tooltip) { - auto* k = new Knob (name, std::move (fmt)); + auto control = std::make_unique (name, std::move (fmt)); + auto* k = control.get(); + ownedControls.push_back (std::move (control)); parent->addAndMakeVisible (k); - if (paramId.isNotEmpty()) + if (paramId.isNotEmpty() && processor.parameters.getParameter (paramId) != nullptr) sliderAttachments.push_back (std::make_unique ( processor.parameters, paramId, *k)); k->setTooltip (tooltip.isNotEmpty() ? tooltip : name); @@ -175,11 +183,13 @@ Knob* PluginEditor::makeKnob (juce::Component* parent, const juce::String& name, juce::ComboBox* PluginEditor::makeCombo (juce::Component* parent, const juce::String& paramId, const juce::StringArray& items, const juce::String& tooltip) { - auto* c = new juce::ComboBox(); + auto control = std::make_unique(); + auto* c = control.get(); + ownedControls.push_back (std::move (control)); c->addItemList (items, 1); c->setSelectedItemIndex (0, juce::dontSendNotification); parent->addAndMakeVisible (c); - if (paramId.isNotEmpty()) + if (paramId.isNotEmpty() && processor.parameters.getParameter (paramId) != nullptr) comboAttachments.push_back (std::make_unique ( processor.parameters, paramId, *c)); if (tooltip.isNotEmpty()) @@ -190,7 +200,9 @@ juce::ComboBox* PluginEditor::makeCombo (juce::Component* parent, const juce::St ToggleButton* PluginEditor::makeToggle (juce::Component* parent, const juce::String& label, const juce::String& paramId, const juce::String& tooltip) { - auto* t = new ToggleButton (label); + auto control = std::make_unique (label); + auto* t = control.get(); + ownedControls.push_back (std::move (control)); parent->addAndMakeVisible (t); if (paramId.isNotEmpty()) t->attach (processor.parameters, paramId); @@ -430,11 +442,11 @@ void PluginEditor::buildModTab() envDisplays[(size_t) i].setBounds (layout::padding, layout::titleHeight, colW - 2 * layout::padding, layout::envDisplayHeight); - const char* idsA[4] = { ids::env1A, ids::env2A, ids::env3A, ids::env4A }; - const char* idsD[4] = { ids::env1D, ids::env2D, ids::env3D, ids::env4D }; - const char* idsS[4] = { ids::env1S, ids::env2S, ids::env3S, ids::env4S }; - const char* idsR[4] = { ids::env1R, ids::env2R, ids::env3R, ids::env4R }; - const char* idsC[4] = { ids::env1Curve, ids::env2Curve, ids::env3Curve, ids::env4Curve }; + const auto& idsA = paramIds::envAttack; + const auto& idsD = paramIds::envDecay; + const auto& idsS = paramIds::envSustain; + const auto& idsR = paramIds::envRelease; + const auto& idsC = paramIds::envCurve; std::vector knobs = { makeKnob (&envPanels[(size_t) i], "Attack", idsA[i], formatSeconds, "Attack time (0.5 ms - 12 s)"), @@ -455,13 +467,13 @@ void PluginEditor::buildModTab() lfoPanels[(size_t) i].setBounds (gridX (i, colW, colGap), lfoTop, colW, lfoH); modView.addAndMakeVisible (lfoPanels[(size_t) i]); - const char* rate[4] = { ids::lfo1Rate, ids::lfo2Rate, ids::lfo3Rate, ids::lfo4Rate }; - const char* sync[4] = { ids::lfo1Sync, ids::lfo2Sync, ids::lfo3Sync, ids::lfo4Sync }; - const char* beat[4] = { ids::lfo1Beat, ids::lfo2Beat, ids::lfo3Beat, ids::lfo4Beat }; - const char* shape[4] = { ids::lfo1Shape, ids::lfo2Shape, ids::lfo3Shape, ids::lfo4Shape }; - const char* phase[4] = { ids::lfo1Phase, ids::lfo2Phase, ids::lfo3Phase, ids::lfo4Phase }; - const char* fade[4] = { ids::lfo1Fade, ids::lfo2Fade, ids::lfo3Fade, ids::lfo4Fade }; - const char* delay[4] = { ids::lfo1Delay, ids::lfo2Delay, ids::lfo3Delay, ids::lfo4Delay }; + const auto& rate = paramIds::lfoRate; + const auto& sync = paramIds::lfoSync; + const auto& beat = paramIds::lfoBeat; + const auto& shape = paramIds::lfoShape; + const auto& phase = paramIds::lfoPhase; + const auto& fade = paramIds::lfoFade; + const auto& delay = paramIds::lfoDelay; makeCombo (&lfoPanels[(size_t) i], shape[i], lfoShapes, "LFO shape")->setBounds (layout::padding, comboRowY(), 110, layout::comboHeight); makeToggle (&lfoPanels[(size_t) i], "Sync", sync[i], "Tempo-sync LFO")->setBounds (layout::padding + 110 + layout::gap, layout::titleHeight, layout::toggleWidth, layout::toggleHeight); @@ -533,9 +545,12 @@ void PluginEditor::buildModTab() const int tid = modTargetCombo.getSelectedItemIndex(); if (tid >= 0 && tid < (int) targetEnums.size()) { - processor.engine.getMatrix().addConnection (src, targetEnums[(size_t) tid], - (float) modDepthKnob.getValue(), - modBipolarToggle.getToggleState()); + { + const juce::ScopedLock lock (processor.engine.getControlLock()); + processor.engine.getMatrix().addConnection (src, targetEnums[(size_t) tid], + (float) modDepthKnob.getValue(), + modBipolarToggle.getToggleState()); + } updateModList(); } }; @@ -546,7 +561,12 @@ void PluginEditor::buildModTab() modRemoveButton.setTooltip ("Remove last modulation connection"); modRemoveButton.onClick = [this] { - processor.engine.getMatrix().removeConnection (processor.engine.getMatrix().size() - 1); + { + const juce::ScopedLock lock (processor.engine.getControlLock()); + auto& matrix = processor.engine.getMatrix(); + if (matrix.size() > 0) + matrix.removeConnection (matrix.size() - 1); + } updateModList(); }; matrixPanel.addAndMakeVisible (modRemoveButton); @@ -556,7 +576,10 @@ void PluginEditor::buildModTab() modClearButton.setTooltip ("Clear all modulation connections"); modClearButton.onClick = [this] { - processor.engine.getMatrix().clear(); + { + const juce::ScopedLock lock (processor.engine.getControlLock()); + processor.engine.getMatrix().clear(); + } updateModList(); }; matrixPanel.addAndMakeVisible (modClearButton); @@ -599,11 +622,15 @@ void PluginEditor::buildFxTab() fxTypeCombos[(size_t) i]->setBounds (layout::padding, comboRowY(), 160, layout::comboHeight); const int upX = layout::padding + 160 + layout::gap; - fxUp[(size_t) i] = new juce::TextButton ("\xe2\x86\x91"); + auto up = std::make_unique ("\xe2\x86\x91"); + fxUp[(size_t) i] = up.get(); + ownedControls.push_back (std::move (up)); fxUp[(size_t) i]->setBounds (upX, comboRowY(), layout::arrowWidth, layout::comboHeight); fxUp[(size_t) i]->setTooltip ("Move effect up"); fxPanels[(size_t) i].addAndMakeVisible (fxUp[(size_t) i]); - fxDown[(size_t) i] = new juce::TextButton ("\xe2\x86\x93"); + auto down = std::make_unique ("\xe2\x86\x93"); + fxDown[(size_t) i] = down.get(); + ownedControls.push_back (std::move (down)); fxDown[(size_t) i]->setBounds (upX + layout::arrowWidth + layout::gap, comboRowY(), layout::arrowWidth, layout::comboHeight); fxDown[(size_t) i]->setTooltip ("Move effect down"); fxPanels[(size_t) i].addAndMakeVisible (fxDown[(size_t) i]); @@ -637,8 +664,8 @@ void PluginEditor::buildMacroTab() macroPanels[(size_t) i].setBounds (gridX (i, colW, colGap), layout::margin, colW, macroH); macroView.addAndMakeVisible (macroPanels[(size_t) i]); - const char* ids[4] = { ids::macro1, ids::macro2, ids::macro3, ids::macro4 }; - macroKnobs[(size_t) i] = makeKnob (¯oPanels[(size_t) i], MacroControls::macroName (i), ids[i], formatPercent, + const auto& macroIds = paramIds::macros; + macroKnobs[(size_t) i] = makeKnob (¯oPanels[(size_t) i], MacroControls::macroName (i), macroIds[i], formatPercent, MacroControls::macroName (i) + " macro (0-100%)"); macroKnobs[(size_t) i]->setBounds ((colW - layout::macroKnobSize) / 2, layout::titleHeight + layout::padding, @@ -684,8 +711,11 @@ void PluginEditor::buildMacroTab() const int tid = macroAssignTarget.getSelectedItemIndex(); if (tid >= 0 && tid < (int) targetEnums.size()) { - processor.engine.getMacros().addAssignment (macro, targetEnums[(size_t) tid], - (float) macroDepthKnob.getValue()); + { + const juce::ScopedLock lock (processor.engine.getControlLock()); + processor.engine.getMacros().addAssignment (macro, targetEnums[(size_t) tid], + (float) macroDepthKnob.getValue()); + } updateMacroList(); } }; @@ -696,7 +726,10 @@ void PluginEditor::buildMacroTab() macroClearButton.setTooltip ("Clear all macro assignments"); macroClearButton.onClick = [this] { - processor.engine.getMacros().clear(); + { + const juce::ScopedLock lock (processor.engine.getControlLock()); + processor.engine.getMacros().clear(); + } updateMacroList(); }; macroAssignPanel.addAndMakeVisible (macroClearButton); @@ -715,10 +748,15 @@ void PluginEditor::buildMacroTab() void PluginEditor::swapFxSlots (int a, int b) { + if (a < 0 || b < 0 || a >= kNumFxSlots || b >= kNumFxSlots || a == b) + return; + auto swapParam = [this] (const char* pa, const char* pb) { auto* p1 = processor.parameters.getParameter (pa); auto* p2 = processor.parameters.getParameter (pb); + if (p1 == nullptr || p2 == nullptr) + return; const float v1 = p1->getValue(); const float v2 = p2->getValue(); p1->setValueNotifyingHost (v2); @@ -748,6 +786,7 @@ void PluginEditor::setTab (int index) tabMod.setToggleState (currentTab == 2, juce::dontSendNotification); tabFx.setToggleState (currentTab == 3, juce::dontSendNotification); tabMacro.setToggleState (currentTab == 4, juce::dontSendNotification); + timerCallback(); } void PluginEditor::cycleTab (int delta) @@ -791,92 +830,139 @@ void PluginEditor::applyUiScale() void PluginEditor::updateVisuals() { const auto& apvts = processor.parameters; - auto gv = [&] (const char* id) { return apvts.getRawParameterValue (id)->load(); }; + auto gv = [&] (const char* id) + { + if (auto* value = apvts.getRawParameterValue (id)) + return value->load(); + return 0.0f; + }; masterDisplay.setValue (formatPercent (gv (ids::master))); // Waveforms. - const int waveA = juce::jlimit (0, kNumWavetables - 1, (int) std::llround (gv (ids::oscAWave) * (kNumWavetables - 1))); - const int waveB = juce::jlimit (0, kNumWavetables - 1, (int) std::llround (gv (ids::oscBWave) * (kNumWavetables - 1))); - oscAWave.setWaveIndex (waveA); - oscAWave.setFramePosition (gv (ids::oscAWtPos)); - oscAWave.setEnabled (gv (ids::oscAOn) > 0.5f); - oscBWave.setWaveIndex (waveB); - oscBWave.setFramePosition (gv (ids::oscBWtPos)); - oscBWave.setEnabled (gv (ids::oscBOn) > 0.5f); - oscAWave.repaint(); - oscBWave.repaint(); + if (currentTab == 0) + { + const int waveA = juce::jlimit (0, kNumWavetables - 1, (int) std::llround (gv (ids::oscAWave) * (kNumWavetables - 1))); + const int waveB = juce::jlimit (0, kNumWavetables - 1, (int) std::llround (gv (ids::oscBWave) * (kNumWavetables - 1))); + oscAWave.setWaveIndex (waveA); + oscAWave.setFramePosition (gv (ids::oscAWtPos)); + oscAWave.setEnabled (gv (ids::oscAOn) > 0.5f); + oscBWave.setWaveIndex (waveB); + oscBWave.setFramePosition (gv (ids::oscBWtPos)); + oscBWave.setEnabled (gv (ids::oscBOn) > 0.5f); + } // Filters. - filter1Display.setParams ((int) std::llround (gv (ids::f1Type) * 6.0f), gv (ids::f1Cutoff), gv (ids::f1Res), - gv (ids::f1Drive), (int) std::llround (gv (ids::f1Slope) * 2.0f)); - filter1Display.setEnabled (gv (ids::f1On) > 0.5f); - filter2Display.setParams ((int) std::llround (gv (ids::f2Type) * 6.0f), gv (ids::f2Cutoff), gv (ids::f2Res), - gv (ids::f2Drive), (int) std::llround (gv (ids::f2Slope) * 2.0f)); - filter2Display.setEnabled (gv (ids::f2On) > 0.5f); - filter1Display.repaint(); - filter2Display.repaint(); + if (currentTab == 1) + { + filter1Display.setParams ((int) std::llround (gv (ids::f1Type) * 6.0f), gv (ids::f1Cutoff), gv (ids::f1Res), + gv (ids::f1Drive), (int) std::llround (gv (ids::f1Slope) * 2.0f)); + filter1Display.setEnabled (gv (ids::f1On) > 0.5f); + filter2Display.setParams ((int) std::llround (gv (ids::f2Type) * 6.0f), gv (ids::f2Cutoff), gv (ids::f2Res), + gv (ids::f2Drive), (int) std::llround (gv (ids::f2Slope) * 2.0f)); + filter2Display.setEnabled (gv (ids::f2On) > 0.5f); + } // Envelopes. - const char* a[4] = { ids::env1A, ids::env2A, ids::env3A, ids::env4A }; - const char* d[4] = { ids::env1D, ids::env2D, ids::env3D, ids::env4D }; - const char* s[4] = { ids::env1S, ids::env2S, ids::env3S, ids::env4S }; - const char* r[4] = { ids::env1R, ids::env2R, ids::env3R, ids::env4R }; - const char* c[4] = { ids::env1Curve, ids::env2Curve, ids::env3Curve, ids::env4Curve }; - for (int i = 0; i < kNumEnvelopes; ++i) + if (currentTab == 2) { - envDisplays[(size_t) i].setParams (gv (a[i]), gv (d[i]), gv (s[i]), gv (r[i]), gv (c[i])); - envDisplays[(size_t) i].repaint(); + const auto& a = paramIds::envAttack; + const auto& d = paramIds::envDecay; + const auto& s = paramIds::envSustain; + const auto& r = paramIds::envRelease; + const auto& c = paramIds::envCurve; + for (int i = 0; i < kNumEnvelopes; ++i) + envDisplays[(size_t) i].setParams (gv (a[i]), gv (d[i]), gv (s[i]), gv (r[i]), gv (c[i])); } // LFOs. - const char* lshape[4] = { ids::lfo1Shape, ids::lfo2Shape, ids::lfo3Shape, ids::lfo4Shape }; - for (int i = 0; i < kNumLfos; ++i) + if (currentTab == 2) { - lfoDisplays[(size_t) i].setShape ((int) std::llround (gv (lshape[i]) * 6.0f)); - lfoDisplays[(size_t) i].setShapeData (processor.engine.getLfos()[(size_t) i].getShapeData(), - processor.engine.getLfos()[(size_t) i].getShapeSteps()); - lfoDisplays[(size_t) i].repaint(); + const auto& lshape = paramIds::lfoShape; + const juce::ScopedLock lock (processor.engine.getControlLock()); + for (int i = 0; i < kNumLfos; ++i) + { + const auto& lfo = processor.engine.getLfos()[(size_t) i]; + lfoDisplays[(size_t) i].setShape ((int) std::llround (gv (lshape[i]) * 6.0f)); + lfoDisplays[(size_t) i].setShapeData (lfo.getShapeData(), lfo.getShapeSteps()); + } + } + + if (currentTab == 3) + { + for (int i = 0; i < kNumFxSlots; ++i) + { + const int type = juce::jlimit (0, (int) FxType::Count - 1, + (int) std::llround (gv (kFxType[i]) * ((int) FxType::Count - 1))); + for (int p = 0; p < 4; ++p) + { + auto* knob = fxKnobs[(size_t) i][(size_t) p + 1]; + const juce::String name = kFxParamNames[type][p]; + if (knob->getName() != name) + { + knob->setName (name); + knob->setTooltip (name == "Unused" ? "Not used by this effect" + : name + " (normalised 0-100%)"); + knob->repaint(); + } + } + } } // RAVE state. raveButton.setToggleState (processor.isRaveEnabled()); + const int program = processor.getCurrentProgram(); + if (presetCombo.getSelectedItemIndex() != program) + presetCombo.setSelectedItemIndex (program, juce::dontSendNotification); + // Scale change. if (processor.getUiScaleIndex() != currentScaleIndex) applyUiScale(); + if (scaleCombo.getSelectedItemIndex() != currentScaleIndex) + scaleCombo.setSelectedItemIndex (currentScaleIndex, juce::dontSendNotification); } void PluginEditor::updateModList() { juce::String text; - const auto& cons = processor.engine.getMatrix().connections; - for (const auto& c : cons) - text += modSourceName (c.source) + " -> " + modTargetName (c.target) - + " [" + juce::String (c.depth, 2) + (c.bipolar ? ", bipolar]" : "]") + "\n"; - modList.setText (text, false); + { + const juce::ScopedLock lock (processor.engine.getControlLock()); + const auto& cons = processor.engine.getMatrix().connections; + for (const auto& c : cons) + text += modSourceName (c.source) + " -> " + modTargetName (c.target) + + " [" + juce::String (c.depth, 2) + (c.bipolar ? ", bipolar]" : "]") + "\n"; + } + if (modList.getText() != text) + modList.setText (text, false); } void PluginEditor::updateMacroList() { juce::String text; - for (int m = 0; m < kNumMacros; ++m) { - text += MacroControls::macroName (m) + ":\n"; - const auto& assigns = processor.engine.getMacros().assignments[(size_t) m]; - if (assigns.empty()) - text += " (none)\n"; - for (const auto& a : assigns) - text += " " + modTargetName (a.target) + " [" + juce::String (a.depth, 2) + "]\n"; + const juce::ScopedLock lock (processor.engine.getControlLock()); + for (int m = 0; m < kNumMacros; ++m) + { + text += MacroControls::macroName (m) + ":\n"; + const auto& assigns = processor.engine.getMacros().assignments[(size_t) m]; + if (assigns.empty()) + text += " (none)\n"; + for (const auto& a : assigns) + text += " " + modTargetName (a.target) + " [" + juce::String (a.depth, 2) + "]\n"; + } } - macroList.setText (text, false); + if (macroList.getText() != text) + macroList.setText (text, false); } void PluginEditor::timerCallback() { updateVisuals(); - updateModList(); - updateMacroList(); + if (currentTab == 2) + updateModList(); + else if (currentTab == 4) + updateMacroList(); } } // namespace serum diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 1045089..18aea76 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -99,6 +99,8 @@ private: juce::TextButton macroAssignButton, macroClearButton; juce::TextEditor macroList; + std::vector> ownedControls; + // --- attachments --- std::vector> sliderAttachments; std::vector> comboAttachments;