Files
InstaShadow/Source/PluginEditor.cpp
hariel1985 d750716608 v1.1 — Improved metering, transformer, and optical cell tuning
- Needle VU meters with spring-mass-damper physics (analog inertia)
- Swappable meter modes: GR needles + input bars, or input needles + GR bars
- GR bar meters fill right-to-left (0dB=empty, -30dB=full)
- Input bar meters fill left-to-right with green color
- Optical cell: normalized parameters (eta=50) for proper audio-level response
- Transformer: removed 3-band crossover artifacts, simplified waveshaping with dry/wet mix
- Nickel/Iron/Steel with distinct but subtle harmonic character
- Layout: optical left, discrete right, meters center, transformer+output bottom center
2026-03-27 17:46:25 +01:00

254 sor
10 KiB
C++

#include "PluginEditor.h"
InstaShadowEditor::InstaShadowEditor (InstaShadowProcessor& p)
: AudioProcessorEditor (&p), processor (p)
{
setLookAndFeel (&lookAndFeel);
setSize (1000, 600);
setResizable (true, true);
setResizeLimits (800, 500, 1400, 900);
// Title
titleLabel.setText ("INSTASHADOW", juce::dontSendNotification);
titleLabel.setJustificationType (juce::Justification::centredLeft);
titleLabel.setColour (juce::Label::textColourId, InstaShadowLookAndFeel::accent);
addAndMakeVisible (titleLabel);
versionLabel.setText (kInstaShadowVersion, juce::dontSendNotification);
versionLabel.setJustificationType (juce::Justification::centredLeft);
versionLabel.setColour (juce::Label::textColourId, InstaShadowLookAndFeel::textSecondary);
addAndMakeVisible (versionLabel);
// Link
linkToggle.setToggleState (true, juce::dontSendNotification);
addAndMakeVisible (linkToggle);
linkLabel.setText ("LINK", juce::dontSendNotification);
linkLabel.setJustificationType (juce::Justification::centredRight);
linkLabel.setColour (juce::Label::textColourId, InstaShadowLookAndFeel::textSecondary);
addAndMakeVisible (linkLabel);
// Bypass
bypassToggle.setToggleState (false, juce::dontSendNotification);
addAndMakeVisible (bypassToggle);
bypassLabel.setText ("BYPASS", juce::dontSendNotification);
bypassLabel.setJustificationType (juce::Justification::centredRight);
bypassLabel.setColour (juce::Label::textColourId, InstaShadowLookAndFeel::textSecondary);
addAndMakeVisible (bypassLabel);
// Panels
addAndMakeVisible (opticalPanel);
addAndMakeVisible (discretePanel);
addAndMakeVisible (transformerPanel);
addAndMakeVisible (outputPanel);
// Needle meters (default: GR)
needleMeterL.setLabel ("OPTICAL GR");
needleMeterL.setMode (NeedleVuMeter::GR);
addAndMakeVisible (needleMeterL);
needleMeterR.setLabel ("DISCRETE GR");
needleMeterR.setMode (NeedleVuMeter::GR);
addAndMakeVisible (needleMeterR);
// Bar meters (default: input level)
barMeterL.setLabel ("INPUT L");
barMeterL.setBarColour (juce::Colour (0xff00cc44));
addAndMakeVisible (barMeterL);
barMeterR.setLabel ("INPUT R");
barMeterR.setBarColour (juce::Colour (0xff00cc44));
addAndMakeVisible (barMeterR);
// Meter swap button
meterSwapButton.onClick = [this]
{
metersSwapped = ! metersSwapped;
if (metersSwapped)
{
needleMeterL.setLabel ("INPUT L");
needleMeterL.setMode (NeedleVuMeter::VU);
needleMeterR.setLabel ("INPUT R");
needleMeterR.setMode (NeedleVuMeter::VU);
barMeterL.setLabel ("OPTICAL GR");
barMeterL.setBarColour (juce::Colour (0xffff8833));
barMeterR.setLabel ("DISCRETE GR");
barMeterR.setBarColour (juce::Colour (0xff4488ff));
}
else
{
needleMeterL.setLabel ("OPTICAL GR");
needleMeterL.setMode (NeedleVuMeter::GR);
needleMeterR.setLabel ("DISCRETE GR");
needleMeterR.setMode (NeedleVuMeter::GR);
barMeterL.setLabel ("INPUT L");
barMeterL.setBarColour (juce::Colour (0xff00cc44));
barMeterR.setLabel ("INPUT R");
barMeterR.setBarColour (juce::Colour (0xff00cc44));
}
};
addAndMakeVisible (meterSwapButton);
syncKnobsToEngine();
startTimerHz (30);
}
InstaShadowEditor::~InstaShadowEditor()
{
setLookAndFeel (nullptr);
stopTimer();
}
void InstaShadowEditor::syncKnobsToEngine()
{
auto& eng = processor.getEngine();
opticalPanel.thresholdKnob.setValue (eng.optoThresholdDb.load(), juce::dontSendNotification);
opticalPanel.gainKnob.setValue (eng.optoGainDb.load(), juce::dontSendNotification);
opticalPanel.scHpfKnob.setValue (eng.optoScHpfHz.load(), juce::dontSendNotification);
opticalPanel.bypassToggle.setToggleState (eng.optoBypass.load(), juce::dontSendNotification);
discretePanel.thresholdKnob.setValue (eng.vcaThresholdDb.load(), juce::dontSendNotification);
discretePanel.gainKnob.setValue (eng.vcaGainDb.load(), juce::dontSendNotification);
discretePanel.ratioBox.setSelectedId (eng.vcaRatioIndex.load() + 1, juce::dontSendNotification);
discretePanel.attackBox.setSelectedId (eng.vcaAttackIndex.load() + 1, juce::dontSendNotification);
discretePanel.releaseBox.setSelectedId (eng.vcaReleaseIndex.load() + 1, juce::dontSendNotification);
discretePanel.bypassToggle.setToggleState (eng.vcaBypass.load(), juce::dontSendNotification);
transformerPanel.setSelectedType (eng.transformerType.load());
outputPanel.outputGainKnob.setValue (eng.outputGainDb.load(), juce::dontSendNotification);
linkToggle.setToggleState (eng.stereoLink.load(), juce::dontSendNotification);
bypassToggle.setToggleState (eng.globalBypass.load(), juce::dontSendNotification);
}
void InstaShadowEditor::syncEngineFromKnobs()
{
auto& eng = processor.getEngine();
eng.optoThresholdDb.store ((float) opticalPanel.thresholdKnob.getValue());
eng.optoGainDb.store ((float) opticalPanel.gainKnob.getValue());
eng.optoScHpfHz.store ((float) opticalPanel.scHpfKnob.getValue());
eng.optoBypass.store (opticalPanel.bypassToggle.getToggleState());
eng.vcaThresholdDb.store ((float) discretePanel.thresholdKnob.getValue());
eng.vcaGainDb.store ((float) discretePanel.gainKnob.getValue());
eng.vcaRatioIndex.store (discretePanel.ratioBox.getSelectedId() - 1);
eng.vcaAttackIndex.store (discretePanel.attackBox.getSelectedId() - 1);
eng.vcaReleaseIndex.store (discretePanel.releaseBox.getSelectedId() - 1);
eng.vcaBypass.store (discretePanel.bypassToggle.getToggleState());
eng.transformerType.store (transformerPanel.getSelectedType());
eng.outputGainDb.store ((float) outputPanel.outputGainKnob.getValue());
eng.stereoLink.store (linkToggle.getToggleState());
eng.globalBypass.store (bypassToggle.getToggleState());
}
void InstaShadowEditor::timerCallback()
{
syncEngineFromKnobs();
auto& eng = processor.getEngine();
if (! metersSwapped)
{
// Default: needles = GR, bars = input
needleMeterL.setGainReduction (eng.optoGrDb.load());
needleMeterR.setGainReduction (eng.vcaGrDb.load());
barMeterL.setInputLevel (eng.inputLevelL.load());
barMeterR.setInputLevel (eng.inputLevelR.load());
}
else
{
// Swapped: needles = input, bars = GR
needleMeterL.setLevel (eng.inputLevelL.load());
needleMeterR.setLevel (eng.inputLevelR.load());
barMeterL.setGainReduction (eng.optoGrDb.load());
barMeterR.setGainReduction (eng.vcaGrDb.load());
}
// Output panel VU
outputPanel.vuMeter.setLevel (eng.outputLevelL.load(), eng.outputLevelR.load());
}
void InstaShadowEditor::paint (juce::Graphics& g)
{
g.fillAll (InstaShadowLookAndFeel::bgDark);
lookAndFeel.drawBackgroundTexture (g, getLocalBounds());
float scale = (float) getHeight() / 600.0f;
int topBarH = (int) (36.0f * scale);
g.setColour (InstaShadowLookAndFeel::bgMedium.withAlpha (0.7f));
g.fillRect (0, 0, getWidth(), topBarH);
g.setColour (InstaShadowLookAndFeel::bgLight.withAlpha (0.3f));
g.drawHorizontalLine (topBarH, 0, (float) getWidth());
}
void InstaShadowEditor::resized()
{
auto bounds = getLocalBounds();
float scale = (float) getHeight() / 600.0f;
int topBarH = (int) (36.0f * scale);
int pad = (int) (6.0f * scale);
// ===== TOP BAR =====
auto topBar = bounds.removeFromTop (topBarH).reduced (pad, 0);
titleLabel.setFont (lookAndFeel.getBoldFont (20.0f * scale));
titleLabel.setBounds (topBar.removeFromLeft ((int) (140 * scale)));
versionLabel.setFont (lookAndFeel.getRegularFont (13.0f * scale));
versionLabel.setBounds (topBar.removeFromLeft ((int) (50 * scale)));
auto bypassArea = topBar.removeFromRight ((int) (30 * scale));
bypassToggle.setBounds (bypassArea);
bypassLabel.setFont (lookAndFeel.getRegularFont (11.0f * scale));
bypassLabel.setBounds (topBar.removeFromRight ((int) (55 * scale)));
topBar.removeFromRight (pad);
auto linkArea = topBar.removeFromRight ((int) (30 * scale));
linkToggle.setBounds (linkArea);
linkLabel.setFont (lookAndFeel.getRegularFont (11.0f * scale));
linkLabel.setBounds (topBar.removeFromRight ((int) (40 * scale)));
auto content = bounds.reduced (pad);
// ===== MAIN: 3 columns =====
int sideW = (int) (content.getWidth() * 0.22f);
auto mainRow = content;
// Left: Optical
opticalPanel.setBounds (mainRow.removeFromLeft (sideW));
mainRow.removeFromLeft (pad);
// Right: Discrete
auto rightCol = mainRow.removeFromRight (sideW);
discretePanel.setBounds (rightCol);
mainRow.removeFromRight (pad);
// Center column: VU meters, GR bars, Transformer, Output — all stacked
auto centerArea = mainRow;
// Two needle meters side by side (~30%)
int vuH = (int) (centerArea.getHeight() * 0.30f);
auto needleRow = centerArea.removeFromTop (vuH);
int needleW = (needleRow.getWidth() - pad) / 2;
needleMeterL.setBounds (needleRow.removeFromLeft (needleW));
needleRow.removeFromLeft (pad);
needleMeterR.setBounds (needleRow);
centerArea.removeFromTop (pad);
// Swap button (compact, between needles and bars)
meterSwapButton.setBounds (centerArea.removeFromTop (20).reduced (centerArea.getWidth() / 4, 0));
centerArea.removeFromTop (pad);
// Two bar meters (~10%)
int barH = (int) (centerArea.getHeight() * 0.10f);
barMeterL.setBounds (centerArea.removeFromTop (barH));
centerArea.removeFromTop (pad);
barMeterR.setBounds (centerArea.removeFromTop (barH));
centerArea.removeFromTop (pad);
// Transformer + Output side by side in remaining center space
auto botCenter = centerArea;
int botHalf = botCenter.getWidth() / 2;
transformerPanel.setBounds (botCenter.removeFromLeft (botHalf - pad / 2));
botCenter.removeFromLeft (pad);
outputPanel.setBounds (botCenter);
}