Files
biggy 8364737d1c perf(gui): skip unchanged repaints and gate updates by tab
Add change detection to Display, WaveformDisplay, FilterDisplay,
EnvelopeDisplay, and LFODisplay so repaint() is only called when
values actually change. Gate updateVisuals() by currentTab so only
the visible tab's displays are updated each timer tick. Similarly
gate updateModList/updateMacroList to their respective tabs.

Manage dynamically created controls via ownedControls
unique_ptr vector instead of raw new leaks. Null-check parameter
lookups before creating attachments. Hold engine control lock
when reading matrix/macros/LFO shape data.

Fix envelope display curve directions (attack rising from bottom,
decay falling toward sustain, release falling toward bottom).
Add tooltip to FilterDisplay noting the approximation. Replace
ToggleButton raw param pointer with ParameterAttachment for proper
gesture handling. Sync preset and scale combos to processor state.
2026-09-09 14:33:41 +02:00

135 lines
4.7 KiB
C++

#pragma once
#include <JuceHeader.h>
#include "PluginProcessor.h"
#include "GUI/SerumLookAndFeel.h"
#include "GUI/Knob.h"
#include "GUI/Slider.h"
#include "GUI/ToggleButton.h"
#include "GUI/Display.h"
#include "GUI/Panel.h"
#include "GUI/WaveformDisplay.h"
#include "GUI/FilterDisplay.h"
#include "GUI/EnvelopeDisplay.h"
#include "GUI/LFODisplay.h"
namespace serum
{
// ===========================================================================
// SerumAlt editor: dark vector GUI with a tabbed layout, real-time
// visualisations, RAVE, macro controls and preset-scale (75%..200%) scaling.
// ===========================================================================
class PluginEditor : public juce::AudioProcessorEditor,
public juce::Timer
{
public:
explicit PluginEditor (SerumAltAudioProcessor& p);
~PluginEditor() override;
void paint (juce::Graphics&) override;
void resized() override;
void timerCallback() override;
bool keyPressed (const juce::KeyPress& key) override;
void setTab (int index);
private:
SerumAltAudioProcessor& processor;
SerumLookAndFeel laf;
static constexpr int kBaseW = 1120;
static constexpr int kBaseH = 740;
juce::Component root;
// --- tooltips ---
juce::TooltipWindow tooltipWindow;
// --- top bar ---
std::unique_ptr<juce::Drawable> logo;
juce::ComboBox presetCombo;
juce::ComboBox scaleCombo;
ToggleButton raveButton;
Knob masterKnob;
Display masterDisplay;
// --- tabs ---
juce::TextButton tabOsc, tabFilter, tabMod, tabFx, tabMacro;
juce::Component oscView, filterView, modView, fxView, macroView;
int currentTab = 0;
// --- OSC ---
Panel oscAPanel { "Oscillator A" }, oscBPanel { "Oscillator B" };
Panel subPanel { "Sub" }, noisePanel { "Noise" };
WaveformDisplay oscAWave, oscBWave;
std::vector<juce::Component*> oscAComponents, oscBComponents;
// --- FILTER ---
Panel filter1Panel { "Filter 1" }, filter2Panel { "Filter 2" }, routingPanel { "Routing" };
FilterDisplay filter1Display, filter2Display;
// --- MOD ---
std::array<Panel, kNumEnvelopes> envPanels { { Panel ("Env 1 (Amp)"), Panel ("Env 2 (Filter)"),
Panel ("Env 3"), Panel ("Env 4") } };
std::array<EnvelopeDisplay, kNumEnvelopes> envDisplays;
std::array<Panel, kNumLfos> lfoPanels { { Panel ("LFO 1"), Panel ("LFO 2"), Panel ("LFO 3"), Panel ("LFO 4") } };
std::array<LFODisplay, kNumLfos> lfoDisplays;
Panel matrixPanel { "Modulation Matrix" };
juce::ComboBox modSourceCombo, modTargetCombo;
Knob modDepthKnob { "Depth" };
ToggleButton modBipolarToggle;
juce::TextButton modAddButton, modRemoveButton, modClearButton;
juce::TextEditor modList;
// --- FX ---
std::array<Panel, kNumFxSlots> fxPanels;
std::array<juce::ComboBox*, kNumFxSlots> fxTypeCombos;
std::array<std::vector<Knob*>, kNumFxSlots> fxKnobs;
std::array<juce::TextButton*, kNumFxSlots> fxUp, fxDown;
// --- MACRO ---
std::array<Panel, kNumMacros> macroPanels;
std::array<Knob*, kNumMacros> macroKnobs;
Panel macroAssignPanel { "Macro Assignments" };
juce::ComboBox macroAssignTarget;
juce::ComboBox macroAssignIndex;
Knob macroDepthKnob { "Depth" };
juce::TextButton macroAssignButton, macroClearButton;
juce::TextEditor macroList;
std::vector<std::unique_ptr<juce::Component>> ownedControls;
// --- attachments ---
std::vector<std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment>> sliderAttachments;
std::vector<std::unique_ptr<juce::AudioProcessorValueTreeState::ComboBoxAttachment>> comboAttachments;
int currentScaleIndex = -1;
// --- helpers ---
Knob* makeKnob (juce::Component* parent, const juce::String& name, const juce::String& paramId,
std::function<juce::String (float)> fmt = {}, const juce::String& tooltip = {});
juce::ComboBox* makeCombo (juce::Component* parent, const juce::String& paramId, const juce::StringArray& items,
const juce::String& tooltip = {});
ToggleButton* makeToggle (juce::Component* parent, const juce::String& label, const juce::String& paramId,
const juce::String& tooltip = {});
void cycleTab (int delta);
void buildTopBar();
void buildOscTab();
void buildFilterTab();
void buildModTab();
void buildFxTab();
void buildMacroTab();
void swapFxSlots (int a, int b);
void applyUiScale();
void updateVisuals();
void updateModList();
void updateMacroList();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginEditor)
};
} // namespace serum