mirror of
https://codeberg.org/armin/beamgrid.git
synced 2026-09-01 04:10:47 +02:00
Add GRID visibility knob and fix spectrum/grid rendering
- Add GRID rotary knob (0-1) controlling grid + axis-label brightness/visibility - Raise analyser ceiling to 0 dBFS; remove overlapping 0/-6 dB lines - Set plugin minimum width to 800px (and clamp restored width) - Extend spectrum signal/peak waves to full plot width - Render signal wave as quadratic spline to match peak smoothness - Restore 1px gaps in LED mode
This commit is contained in:
parent
e0177ed26f
commit
b6bdc73488
6 changed files with 52 additions and 14 deletions
|
|
@ -29,6 +29,7 @@ and a configurable display.
|
||||||
| MODE | 1 / 2 / 3 | BARS / SPECTRUM / LED |
|
| MODE | 1 / 2 / 3 | BARS / SPECTRUM / LED |
|
||||||
| LEDS | 4 – 48 | LEDs per bar in LED mode |
|
| LEDS | 4 – 48 | LEDs per bar in LED mode |
|
||||||
| SMOOTH | 0 – 1 | Waveform smoothing amount in SPECTRUM mode |
|
| SMOOTH | 0 – 1 | Waveform smoothing amount in SPECTRUM mode |
|
||||||
|
| GRID | 0 – 1 | Grid (frequency/level) visibility / brightness|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,5 +70,5 @@ private:
|
||||||
double falloffRate = 0.8; // normalized units per second while falling
|
double falloffRate = 0.8; // normalized units per second while falling
|
||||||
double barReleaseTau = 0.2; // seconds for the live bar to fall (release)
|
double barReleaseTau = 0.2; // seconds for the live bar to fall (release)
|
||||||
double minDb = -48.0; // dBFS-equivalent floor (bands below this -> 0)
|
double minDb = -48.0; // dBFS-equivalent floor (bands below this -> 0)
|
||||||
double maxDb = -6.0; // dBFS-equivalent ceiling (bands at/above -> 1)
|
double maxDb = 0.0; // dBFS-equivalent ceiling (bands at/above -> 1)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ public:
|
||||||
const int mode = juce::roundToInt (params.getRawParameterValue ("mode")->load());
|
const int mode = juce::roundToInt (params.getRawParameterValue ("mode")->load());
|
||||||
const int leds = juce::roundToInt (params.getRawParameterValue ("leds")->load());
|
const int leds = juce::roundToInt (params.getRawParameterValue ("leds")->load());
|
||||||
const double smooth = *params.getRawParameterValue ("smooth");
|
const double smooth = *params.getRawParameterValue ("smooth");
|
||||||
|
const double gridVis = *params.getRawParameterValue ("grid");
|
||||||
|
|
||||||
analyser.setGraceSeconds (graceMs / 1000.0);
|
analyser.setGraceSeconds (graceMs / 1000.0);
|
||||||
analyser.setFalloffRate (0.1 + falloff * 5.0);
|
analyser.setFalloffRate (0.1 + falloff * 5.0);
|
||||||
|
|
@ -47,6 +48,7 @@ public:
|
||||||
displayMode = mode;
|
displayMode = mode;
|
||||||
ledCount = leds;
|
ledCount = leds;
|
||||||
smoothAmount = static_cast<float> (smooth);
|
smoothAmount = static_cast<float> (smooth);
|
||||||
|
gridAmount = static_cast<float> (gridVis);
|
||||||
|
|
||||||
analyser.update (1.0 / 60.0);
|
analyser.update (1.0 / 60.0);
|
||||||
repaint();
|
repaint();
|
||||||
|
|
@ -66,8 +68,8 @@ public:
|
||||||
const float bandWidth = (plot.getWidth() - gap * (n + 1)) / static_cast<float> (n);
|
const float bandWidth = (plot.getWidth() - gap * (n + 1)) / static_cast<float> (n);
|
||||||
const float baseY = plot.getBottom();
|
const float baseY = plot.getBottom();
|
||||||
|
|
||||||
const juce::Colour gridColour = lf.transform (juce::Colours::grey.brighter (0.2f)).withAlpha (0.32f);
|
const juce::Colour gridColour = lf.transform (juce::Colours::grey.brighter (0.2f)).withAlpha (gridAmount);
|
||||||
const juce::Colour labelColour = lf.transform (juce::Colours::grey);
|
const juce::Colour labelColour = gridColour;
|
||||||
|
|
||||||
// --- Gridlines (drawn first; labels drawn last so they stay readable) ---
|
// --- Gridlines (drawn first; labels drawn last so they stay readable) ---
|
||||||
const double freqTicks[] = { 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000 };
|
const double freqTicks[] = { 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000 };
|
||||||
|
|
@ -179,18 +181,33 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trace the curve. `smooth` controls how much the band levels are
|
// Trace the curve. `smooth` controls how much the band levels are
|
||||||
// averaged (above); the path itself is straight segments between band
|
// averaged (above); the path itself is a quadratic spline through the
|
||||||
// centres, so at smooth = 0 it stays raw/edgy and at 1 it is rounded
|
// band centres (same technique as the peak wave), so at smooth = 0 it
|
||||||
// by the heavy level smoothing.
|
// stays raw/edgy and at 1 it is rounded by the heavy level smoothing.
|
||||||
juce::Path wave;
|
// The curve is anchored to the first/last band value right at the plot
|
||||||
wave.startNewSubPath (plot.getX() + gap, baseY);
|
// edges so it reaches the full width instead of ramping down to the
|
||||||
|
// baseline at the sides.
|
||||||
|
juce::HeapBlock<juce::Point<float>> wpts (static_cast<size_t> (n));
|
||||||
for (int i = 0; i < n; ++i)
|
for (int i = 0; i < n; ++i)
|
||||||
{
|
{
|
||||||
const float x = plot.getX() + gap + i * (bandWidth + gap) + bandWidth * 0.5f;
|
const float x = plot.getX() + gap + i * (bandWidth + gap) + bandWidth * 0.5f;
|
||||||
const float y = baseY - levels[i] * plot.getHeight();
|
const float y = baseY - levels[i] * plot.getHeight();
|
||||||
wave.lineTo (x, y);
|
wpts[i] = { x, y };
|
||||||
}
|
}
|
||||||
wave.lineTo (plot.getRight() - gap, baseY);
|
|
||||||
|
juce::Path wave;
|
||||||
|
wave.startNewSubPath (plot.getX(), wpts[0].y);
|
||||||
|
wave.lineTo (wpts[0]);
|
||||||
|
for (int i = 0; i < n - 1; ++i)
|
||||||
|
{
|
||||||
|
const juce::Point<float> mid = { (wpts[i].x + wpts[i + 1].x) * 0.5f,
|
||||||
|
(wpts[i].y + wpts[i + 1].y) * 0.5f };
|
||||||
|
wave.quadraticTo (wpts[i], mid);
|
||||||
|
}
|
||||||
|
wave.quadraticTo (wpts[n - 1], wpts[n - 1]);
|
||||||
|
wave.lineTo (plot.getRight(), wpts[n - 1].y);
|
||||||
|
wave.lineTo (plot.getRight(), baseY);
|
||||||
|
wave.lineTo (plot.getX(), baseY);
|
||||||
wave.closeSubPath();
|
wave.closeSubPath();
|
||||||
|
|
||||||
// Brighter at the top, darker toward the bottom.
|
// Brighter at the top, darker toward the bottom.
|
||||||
|
|
@ -233,7 +250,8 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
juce::Path peakWave;
|
juce::Path peakWave;
|
||||||
peakWave.startNewSubPath (ppts[0]);
|
peakWave.startNewSubPath (plot.getX(), baseY - peaks[0] * plot.getHeight());
|
||||||
|
peakWave.lineTo (ppts[0]);
|
||||||
for (int i = 0; i < n - 1; ++i)
|
for (int i = 0; i < n - 1; ++i)
|
||||||
{
|
{
|
||||||
const juce::Point<float> mid = { (ppts[i].x + ppts[i + 1].x) * 0.5f,
|
const juce::Point<float> mid = { (ppts[i].x + ppts[i + 1].x) * 0.5f,
|
||||||
|
|
@ -241,6 +259,7 @@ private:
|
||||||
peakWave.quadraticTo (ppts[i], mid);
|
peakWave.quadraticTo (ppts[i], mid);
|
||||||
}
|
}
|
||||||
peakWave.quadraticTo (ppts[n - 1], ppts[n - 1]);
|
peakWave.quadraticTo (ppts[n - 1], ppts[n - 1]);
|
||||||
|
peakWave.lineTo (plot.getRight(), baseY - peaks[n - 1] * plot.getHeight());
|
||||||
g.setColour (lf.getPeak());
|
g.setColour (lf.getPeak());
|
||||||
g.strokePath (peakWave, juce::PathStrokeType (2.0f));
|
g.strokePath (peakWave, juce::PathStrokeType (2.0f));
|
||||||
}
|
}
|
||||||
|
|
@ -292,6 +311,7 @@ private:
|
||||||
int displayMode = ModeBars;
|
int displayMode = ModeBars;
|
||||||
int ledCount = 16;
|
int ledCount = 16;
|
||||||
float smoothAmount = 0.35f;
|
float smoothAmount = 0.35f;
|
||||||
|
float gridAmount = 0.32f;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyserComponent)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyserComponent)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
||||||
// reports IPlugView::canResize() == true to the host, which is what FL
|
// reports IPlugView::canResize() == true to the host, which is what FL
|
||||||
// Studio (and other hosts) use to enable their native maximize button.
|
// Studio (and other hosts) use to enable their native maximize button.
|
||||||
setResizable (true, true);
|
setResizable (true, true);
|
||||||
setResizeLimits (420, 260, 4000, 4000);
|
setResizeLimits (800, 260, 4000, 4000);
|
||||||
|
|
||||||
analyserComponent.setLookAndFeel (&lookAndFeel);
|
analyserComponent.setLookAndFeel (&lookAndFeel);
|
||||||
addAndMakeVisible (analyserComponent);
|
addAndMakeVisible (analyserComponent);
|
||||||
|
|
@ -36,6 +36,7 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
||||||
setupKnob (modeSlider, "");
|
setupKnob (modeSlider, "");
|
||||||
setupKnob (ledsSlider, "");
|
setupKnob (ledsSlider, "");
|
||||||
setupKnob (smoothSlider, "");
|
setupKnob (smoothSlider, "");
|
||||||
|
setupKnob (gridSlider, "");
|
||||||
barsSlider.setNumDecimalPlacesToDisplay (0);
|
barsSlider.setNumDecimalPlacesToDisplay (0);
|
||||||
modeSlider.setNumDecimalPlacesToDisplay (0);
|
modeSlider.setNumDecimalPlacesToDisplay (0);
|
||||||
ledsSlider.setNumDecimalPlacesToDisplay (0);
|
ledsSlider.setNumDecimalPlacesToDisplay (0);
|
||||||
|
|
@ -80,6 +81,11 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
||||||
smoothLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
smoothLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
||||||
addAndMakeVisible (smoothLabel);
|
addAndMakeVisible (smoothLabel);
|
||||||
|
|
||||||
|
gridLabel.setText ("GRID", juce::dontSendNotification);
|
||||||
|
gridLabel.setJustificationType (juce::Justification::centred);
|
||||||
|
gridLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
||||||
|
addAndMakeVisible (gridLabel);
|
||||||
|
|
||||||
scaleBox.addItemList (juce::StringArray ("75%", "100%", "150%", "200%", "250%", "300%"), 1);
|
scaleBox.addItemList (juce::StringArray ("75%", "100%", "150%", "200%", "250%", "300%"), 1);
|
||||||
scaleBox.setSelectedItemIndex (1); // 100%
|
scaleBox.setSelectedItemIndex (1); // 100%
|
||||||
scaleBox.setColour (juce::ComboBox::backgroundColourId, juce::Colours::black.brighter (0.15f));
|
scaleBox.setColour (juce::ComboBox::backgroundColourId, juce::Colours::black.brighter (0.15f));
|
||||||
|
|
@ -127,6 +133,8 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
||||||
processorRef.getParametersState(), "leds", ledsSlider);
|
processorRef.getParametersState(), "leds", ledsSlider);
|
||||||
smoothAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
smoothAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||||
processorRef.getParametersState(), "smooth", smoothSlider);
|
processorRef.getParametersState(), "smooth", smoothSlider);
|
||||||
|
gridAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||||
|
processorRef.getParametersState(), "grid", gridSlider);
|
||||||
|
|
||||||
// Set these AFTER the attachments: SliderAttachment installs its own
|
// Set these AFTER the attachments: SliderAttachment installs its own
|
||||||
// textFromValueFunction, so assigning afterwards makes ours take effect.
|
// textFromValueFunction, so assigning afterwards makes ours take effect.
|
||||||
|
|
@ -162,7 +170,7 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
||||||
scaleIdx = i;
|
scaleIdx = i;
|
||||||
scaleBox.setSelectedItemIndex (scaleIdx); // triggers onChange -> setScaleFactor
|
scaleBox.setSelectedItemIndex (scaleIdx); // triggers onChange -> setScaleFactor
|
||||||
|
|
||||||
const int storedW = uiState.getProperty ("uiWidth", getWidth());
|
const int storedW = juce::jmax (800, static_cast<int> (uiState.getProperty ("uiWidth", getWidth())));
|
||||||
const int storedH = uiState.getProperty ("uiHeight", getHeight());
|
const int storedH = uiState.getProperty ("uiHeight", getHeight());
|
||||||
setSize (storedW, storedH);
|
setSize (storedW, storedH);
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +215,7 @@ void BeamgridAudioProcessorEditor::resized()
|
||||||
|
|
||||||
const int margin = 24;
|
const int margin = 24;
|
||||||
const int gap = 14;
|
const int gap = 14;
|
||||||
const int count = 8;
|
const int count = 9;
|
||||||
const int titleW = 220;
|
const int titleW = 220;
|
||||||
|
|
||||||
// Available width for the knob row (leave room on the right for the title).
|
// Available width for the knob row (leave room on the right for the title).
|
||||||
|
|
@ -227,6 +235,7 @@ void BeamgridAudioProcessorEditor::resized()
|
||||||
{ modeSlider, modeLabel },
|
{ modeSlider, modeLabel },
|
||||||
{ ledsSlider, ledsLabel },
|
{ ledsSlider, ledsLabel },
|
||||||
{ smoothSlider, smoothLabel },
|
{ smoothSlider, smoothLabel },
|
||||||
|
{ gridSlider, gridLabel },
|
||||||
};
|
};
|
||||||
|
|
||||||
const juce::Font labelFont (juce::FontOptions (11.0f));
|
const juce::Font labelFont (juce::FontOptions (11.0f));
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ private:
|
||||||
juce::Slider modeSlider;
|
juce::Slider modeSlider;
|
||||||
juce::Slider ledsSlider;
|
juce::Slider ledsSlider;
|
||||||
juce::Slider smoothSlider;
|
juce::Slider smoothSlider;
|
||||||
|
juce::Slider gridSlider;
|
||||||
|
|
||||||
juce::Label graceLabel;
|
juce::Label graceLabel;
|
||||||
juce::Label falloffLabel;
|
juce::Label falloffLabel;
|
||||||
|
|
@ -39,6 +40,7 @@ private:
|
||||||
juce::Label modeLabel;
|
juce::Label modeLabel;
|
||||||
juce::Label ledsLabel;
|
juce::Label ledsLabel;
|
||||||
juce::Label smoothLabel;
|
juce::Label smoothLabel;
|
||||||
|
juce::Label gridLabel;
|
||||||
juce::Label titleLabel;
|
juce::Label titleLabel;
|
||||||
juce::Label statusLabel;
|
juce::Label statusLabel;
|
||||||
|
|
||||||
|
|
@ -52,6 +54,7 @@ private:
|
||||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> modeAttachment;
|
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> modeAttachment;
|
||||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> ledsAttachment;
|
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> ledsAttachment;
|
||||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> smoothAttachment;
|
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> smoothAttachment;
|
||||||
|
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gridAttachment;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BeamgridAudioProcessorEditor)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BeamgridAudioProcessorEditor)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,11 @@ BeamgridAudioProcessor::createParameterLayout()
|
||||||
juce::NormalisableRange<float> (0.0f, 1.0f, 0.001f), 0.35f,
|
juce::NormalisableRange<float> (0.0f, 1.0f, 0.001f), 0.35f,
|
||||||
juce::AudioParameterFloatAttributes().withLabel ("")));
|
juce::AudioParameterFloatAttributes().withLabel ("")));
|
||||||
|
|
||||||
|
params.push_back (std::make_unique<juce::AudioParameterFloat>(
|
||||||
|
"grid", "Grid Visibility",
|
||||||
|
juce::NormalisableRange<float> (0.0f, 1.0f, 0.001f), 0.32f,
|
||||||
|
juce::AudioParameterFloatAttributes().withLabel ("")));
|
||||||
|
|
||||||
juce::NormalisableRange<float> barsRange (8.0f, 128.0f,
|
juce::NormalisableRange<float> barsRange (8.0f, 128.0f,
|
||||||
[] (float start, float end, float v) { return start + v * (end - start); }, // convertFrom0To1
|
[] (float start, float end, float v) { return start + v * (end - start); }, // convertFrom0To1
|
||||||
[] (float start, float end, float v) { return (v - start) / (end - start); }, // convertTo0To1
|
[] (float start, float end, float v) { return (v - start) / (end - start); }, // convertTo0To1
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue