add LICENSE, update README, update source

This commit is contained in:
Armin 2026-08-14 01:33:20 +02:00
commit e0177ed26f
9 changed files with 395 additions and 53 deletions

View file

@ -7,7 +7,7 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
analyserComponent (p.getAnalyser(), p.getParametersState(), lookAndFeel)
{
setLookAndFeel (&lookAndFeel);
setSize (820, 460);
setSize (980, 480);
// Make the editor freely resizable (with a corner resizer). The plugin
// reports IPlugView::canResize() == true to the host, which is what FL
@ -33,7 +33,12 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
setupKnob (barfalloffSlider, "");
setupKnob (barsSlider, "");
setupKnob (hueSlider, "");
setupKnob (modeSlider, "");
setupKnob (ledsSlider, "");
setupKnob (smoothSlider, "");
barsSlider.setNumDecimalPlacesToDisplay (0);
modeSlider.setNumDecimalPlacesToDisplay (0);
ledsSlider.setNumDecimalPlacesToDisplay (0);
graceLabel.setText ("PEAK GRACE", juce::dontSendNotification);
graceLabel.setJustificationType (juce::Justification::centred);
@ -60,6 +65,35 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
hueLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
addAndMakeVisible (hueLabel);
modeLabel.setText ("MODE", juce::dontSendNotification);
modeLabel.setJustificationType (juce::Justification::centred);
modeLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
addAndMakeVisible (modeLabel);
ledsLabel.setText ("LEDS", juce::dontSendNotification);
ledsLabel.setJustificationType (juce::Justification::centred);
ledsLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
addAndMakeVisible (ledsLabel);
smoothLabel.setText ("SMOOTH", juce::dontSendNotification);
smoothLabel.setJustificationType (juce::Justification::centred);
smoothLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
addAndMakeVisible (smoothLabel);
scaleBox.addItemList (juce::StringArray ("75%", "100%", "150%", "200%", "250%", "300%"), 1);
scaleBox.setSelectedItemIndex (1); // 100%
scaleBox.setColour (juce::ComboBox::backgroundColourId, juce::Colours::black.brighter (0.15f));
scaleBox.setColour (juce::ComboBox::textColourId, juce::Colours::lightgrey);
scaleBox.setColour (juce::ComboBox::arrowColourId, juce::Colours::lightgrey);
scaleBox.onChange = [this]
{
const double pct = scaleBox.getText().retainCharacters ("0123456789.").getDoubleValue();
const double scale = juce::jlimit (0.5, 3.0, pct / 100.0);
setScaleFactor (scale);
processorRef.getParametersState().state.setProperty ("uiScale", scale, nullptr);
};
addAndMakeVisible (scaleBox);
titleLabel.setText ("BEAMGRID", juce::dontSendNotification);
titleLabel.setJustificationType (juce::Justification::centredRight);
titleLabel.setFont (juce::FontOptions (20.0f, juce::Font::bold));
@ -68,7 +102,7 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
statusLabel.setFont (juce::FontOptions (11.0f));
statusLabel.setColour (juce::Label::textColourId, juce::Colours::grey);
statusLabel.setJustificationType (juce::Justification::centredLeft);
statusLabel.setJustificationType (juce::Justification::centredRight);
juce::String status;
status << "git " << juce::String (BEAMGRID_GIT_COMMIT).substring (0, 7);
@ -87,13 +121,69 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
processorRef.getParametersState(), "bars", barsSlider);
hueAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
processorRef.getParametersState(), "hue", hueSlider);
modeAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
processorRef.getParametersState(), "mode", modeSlider);
ledsAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
processorRef.getParametersState(), "leds", ledsSlider);
smoothAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
processorRef.getParametersState(), "smooth", smoothSlider);
// Set these AFTER the attachments: SliderAttachment installs its own
// textFromValueFunction, so assigning afterwards makes ours take effect.
barsSlider.textFromValueFunction = [] (double v)
{
return juce::String (juce::roundToInt (v));
};
modeSlider.textFromValueFunction = [] (double v)
{
const int m = juce::roundToInt (v);
return m == 2 ? juce::String ("SPECTRUM")
: m == 3 ? juce::String ("LED")
: juce::String ("BARS");
};
// Force an immediate repaint of the text boxes now that our formatters
// are installed (otherwise they keep the attachment's initial text).
barsSlider.updateText();
modeSlider.updateText();
processorRef.getParametersState().addParameterListener ("hue", this);
// Restore the saved UI size + scale from the plugin state (the host saves
// this state with the project), so the window reopens the same way.
auto& uiState = processorRef.getParametersState().state;
const double storedScale = uiState.getProperty ("uiScale", 1.0);
const double scales[] = { 0.75, 1.0, 1.5, 2.0, 2.5, 3.0 };
int scaleIdx = 1;
for (int i = 0; i < 6; ++i)
if (std::abs (scales[i] - storedScale) < 0.001)
scaleIdx = i;
scaleBox.setSelectedItemIndex (scaleIdx); // triggers onChange -> setScaleFactor
const int storedW = uiState.getProperty ("uiWidth", getWidth());
const int storedH = uiState.getProperty ("uiHeight", getHeight());
setSize (storedW, storedH);
}
BeamgridAudioProcessorEditor::~BeamgridAudioProcessorEditor()
{
processorRef.getParametersState().removeParameterListener ("hue", this);
setLookAndFeel (nullptr);
}
void BeamgridAudioProcessorEditor::parameterChanged (const juce::String& parameterID, float newValue)
{
if (parameterID == "hue")
{
// Update the shared LookAndFeel immediately and repaint the whole
// editor so every rotary knob's arc (and the title) recolours.
lookAndFeel.setHueTurns (newValue - 0.5f);
repaint();
}
}
void BeamgridAudioProcessorEditor::paint (juce::Graphics& g)
{
g.fillAll (juce::Colours::black);
@ -108,27 +198,63 @@ void BeamgridAudioProcessorEditor::resized()
const int knob = 64;
const int y = getHeight() - panelH + 14;
analyserComponent.setBounds (0, 0, getWidth(), getHeight() - panelH);
// 32px blank bar across the very top of the plugin (the dropdown sits
// 16px below the top edge so it isn't glued to it). 30px blank strip on
// the right so the spectrum doesn't touch the right border.
const int topBarH = 32;
const int rightPad = 30;
analyserComponent.setBounds (0, topBarH, getWidth() - rightPad, getHeight() - panelH - topBarH);
const int margin = 32;
const int spacing = knob + 24;
const int margin = 24;
const int gap = 14;
const int count = 8;
const int titleW = 220;
barsSlider.setBounds (margin, y, knob, knob);
barsLabel.setBounds (margin, y + knob + 2, knob, 18);
// Available width for the knob row (leave room on the right for the title).
const int avail = getWidth() - margin * 2 - titleW;
int spacing = knob + gap;
const int minSpacing = knob + 4;
if ((count - 1) * spacing + knob > avail)
spacing = juce::jmax (minSpacing, (avail - knob) / (count - 1));
graceSlider.setBounds (margin + spacing, y, knob, knob);
graceLabel.setBounds (margin + spacing, y + knob + 2, knob, 18);
struct KnobRow { juce::Slider& s; juce::Label& l; };
const KnobRow knobs[] = {
{ graceSlider, graceLabel },
{ falloffSlider, falloffLabel },
{ barfalloffSlider, barfalloffLabel },
{ barsSlider, barsLabel },
{ hueSlider, hueLabel },
{ modeSlider, modeLabel },
{ ledsSlider, ledsLabel },
{ smoothSlider, smoothLabel },
};
barfalloffSlider.setBounds (margin + spacing * 2, y, knob, knob);
barfalloffLabel.setBounds (margin + spacing * 2, y + knob + 2, knob, 18);
const juce::Font labelFont (juce::FontOptions (11.0f));
falloffSlider.setBounds (margin + spacing * 3, y, knob, knob);
falloffLabel.setBounds (margin + spacing * 3, y + knob + 2, knob, 18);
// Knobs are drawn 16px higher than the panel baseline; the title and
// status bar below stay anchored to the original baseline.
const int knobY = y - 16;
hueSlider.setBounds (margin + spacing * 4, y, knob, knob);
hueLabel.setBounds (margin + spacing * 4, y + knob + 2, knob, 18);
for (int i = 0; i < count; ++i)
{
const int x = margin + i * spacing;
knobs[i].s.setBounds (x, knobY, knob, knob);
titleLabel.setBounds (getWidth() - 262, y - 6, 210, knob + 24);
// Let the label span the full spacing (wider than the knob) so longer
// names like "BAR FALLOFF" aren't clipped, centred under the knob.
const int labelW = spacing;
const int labelX = x + (knob - labelW) / 2;
knobs[i].l.setFont (labelFont);
knobs[i].l.setBounds (labelX, knobY + knob + 2, labelW, 18);
}
statusLabel.setBounds (8, getHeight() - 16, getWidth() - 16, 14);
titleLabel.setBounds (getWidth() - titleW - margin, y - 6, titleW, knob + 24);
statusLabel.setBounds (16, getHeight() - 16, getWidth() - 42, 14);
// UI scaling dropdown, top-right (16px below the top edge).
scaleBox.setBounds (getWidth() - 96 - margin, 16, 96, 16);
// Remember the current window size so it can be restored with the project.
processorRef.getParametersState().state.setProperty ("uiWidth", getWidth(), nullptr);
processorRef.getParametersState().state.setProperty ("uiHeight", getHeight(), nullptr);
}