mirror of
https://codeberg.org/armin/beamgrid.git
synced 2026-09-01 04:10:47 +02:00
Add TILT spectral-tilt knob plus grid zoom/fade and curving controls
- TILT knob (-1..1, 0=flat) boosts highs / cuts lows about the log-frequency centre so high frequencies become more visible in the display - Add GRID ZOOM, GRID FADE, and CURVING knobs; refine BARS/LEDS snapping - Fix README MODE knob placement description
This commit is contained in:
parent
b6bdc73488
commit
ca9555af13
7 changed files with 176 additions and 15 deletions
|
|
@ -5,7 +5,7 @@ and a configurable display.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Three display modes** (top-right `MODE` knob):
|
- **Three display modes** (`MODE` knob):
|
||||||
- **BARS** — classic log-spaced spectrum bars with peak-hold caps.
|
- **BARS** — classic log-spaced spectrum bars with peak-hold caps.
|
||||||
- **SPECTRUM** — a smooth (quadratic-spline) waveform trace of the spectrum,
|
- **SPECTRUM** — a smooth (quadratic-spline) waveform trace of the spectrum,
|
||||||
with a matching smooth peak-hold wave. Use the `SMOOTH` knob to go from
|
with a matching smooth peak-hold wave. Use the `SMOOTH` knob to go from
|
||||||
|
|
@ -30,6 +30,10 @@ and a configurable display.
|
||||||
| 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|
|
| GRID | 0 – 1 | Grid (frequency/level) visibility / brightness|
|
||||||
|
| ZOOM | 98.0% – 102.0% | Grid zoom about the plot centre |
|
||||||
|
| GRID FADE | 0 – 1 | Fades inner grid lines; outer border stays |
|
||||||
|
| CURVING | 0 – 1 | LED corner radius (0 = square, 1 = rounded) |
|
||||||
|
| TILT | -1 – 1 | Spectral tilt: + boosts highs / cuts lows, - does the reverse (0 = flat) |
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,14 @@ void Analyser::computeFFT()
|
||||||
// to ~full scale, otherwise the raw magnitudes sit ~66 dB too hot.
|
// to ~full scale, otherwise the raw magnitudes sit ~66 dB too hot.
|
||||||
const double normalized = avg / static_cast<double> (fftSize);
|
const double normalized = avg / static_cast<double> (fftSize);
|
||||||
const double db = 20.0 * std::log10 (normalized + 1e-9);
|
const double db = 20.0 * std::log10 (normalized + 1e-9);
|
||||||
double norm = (db - minDb) / (maxDb - minDb);
|
|
||||||
|
// Spectral tilt: boost highs / cut lows (or the reverse) about the
|
||||||
|
// logarithmic centre of the spectrum. The per-band fraction spans
|
||||||
|
// 0 (lowest band) to 1 (highest band), so (frac - 0.5) * 2 is -1..+1.
|
||||||
|
const double frac = (static_cast<double> (b) + 0.5) / numBands;
|
||||||
|
const double tiltDb = tilt * (frac - 0.5) * 2.0 * maxTiltDb;
|
||||||
|
|
||||||
|
double norm = (db + tiltDb - minDb) / (maxDb - minDb);
|
||||||
norm = juce::jlimit (0.0, 1.0, norm);
|
norm = juce::jlimit (0.0, 1.0, norm);
|
||||||
|
|
||||||
targets[b] = static_cast<float> (norm);
|
targets[b] = static_cast<float> (norm);
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ public:
|
||||||
void setFalloffRate (double rate) noexcept { falloffRate = rate; }
|
void setFalloffRate (double rate) noexcept { falloffRate = rate; }
|
||||||
void setBarReleaseTau (double seconds) noexcept { barReleaseTau = seconds; }
|
void setBarReleaseTau (double seconds) noexcept { barReleaseTau = seconds; }
|
||||||
void setNumBands (int n) noexcept;
|
void setNumBands (int n) noexcept;
|
||||||
|
void setTilt (double amount) noexcept { tilt = amount; }
|
||||||
|
double getTilt() const noexcept { return tilt; }
|
||||||
double getGraceSeconds() const noexcept { return grace; }
|
double getGraceSeconds() const noexcept { return grace; }
|
||||||
double getFalloffRate() const noexcept { return falloffRate; }
|
double getFalloffRate() const noexcept { return falloffRate; }
|
||||||
|
|
||||||
|
|
@ -71,4 +73,10 @@ private:
|
||||||
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 = 0.0; // dBFS-equivalent ceiling (bands at/above -> 1)
|
double maxDb = 0.0; // dBFS-equivalent ceiling (bands at/above -> 1)
|
||||||
|
|
||||||
|
// Spectral tilt: +1 fully boosts the highs and cuts the lows, -1 does the
|
||||||
|
// opposite, 0 is flat. The gain is applied symmetrically about the band at
|
||||||
|
// the centre of the log-frequency range.
|
||||||
|
static constexpr double maxTiltDb = 18.0;
|
||||||
|
double tilt = 0.0;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ public:
|
||||||
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");
|
const double gridVis = *params.getRawParameterValue ("grid");
|
||||||
|
const double gridZoomV = *params.getRawParameterValue ("gridzoom");
|
||||||
|
const double gridFadeV = *params.getRawParameterValue ("gridfade");
|
||||||
|
const double curveV = *params.getRawParameterValue ("curving");
|
||||||
|
|
||||||
analyser.setGraceSeconds (graceMs / 1000.0);
|
analyser.setGraceSeconds (graceMs / 1000.0);
|
||||||
analyser.setFalloffRate (0.1 + falloff * 5.0);
|
analyser.setFalloffRate (0.1 + falloff * 5.0);
|
||||||
|
|
@ -49,6 +52,9 @@ public:
|
||||||
ledCount = leds;
|
ledCount = leds;
|
||||||
smoothAmount = static_cast<float> (smooth);
|
smoothAmount = static_cast<float> (smooth);
|
||||||
gridAmount = static_cast<float> (gridVis);
|
gridAmount = static_cast<float> (gridVis);
|
||||||
|
gridZoom = static_cast<float> (gridZoomV);
|
||||||
|
gridFade = static_cast<float> (gridFadeV);
|
||||||
|
curveAmount = static_cast<float> (curveV);
|
||||||
|
|
||||||
analyser.update (1.0 / 60.0);
|
analyser.update (1.0 / 60.0);
|
||||||
repaint();
|
repaint();
|
||||||
|
|
@ -68,10 +74,21 @@ 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 (gridAmount);
|
const juce::Colour gridBase = lf.transform (juce::Colours::grey.brighter (0.2f));
|
||||||
const juce::Colour labelColour = gridColour;
|
const juce::Colour labelColour = gridBase.withAlpha (gridAmount);
|
||||||
|
|
||||||
// --- Gridlines (drawn first; labels drawn last so they stay readable) ---
|
// --- Gridlines (drawn first; labels drawn last so they stay readable) ---
|
||||||
|
// The grid is zoomed between 90% and 100% about the plot centre via the
|
||||||
|
// ZOOM knob; the spectrum content itself is not affected.
|
||||||
|
const juce::Point<float> gridCentre = plot.getCentre();
|
||||||
|
const juce::AffineTransform gridXform =
|
||||||
|
juce::AffineTransform::translation (-gridCentre.x, -gridCentre.y)
|
||||||
|
.followedBy (juce::AffineTransform::scale (gridZoom))
|
||||||
|
.followedBy (juce::AffineTransform::translation (gridCentre.x, gridCentre.y));
|
||||||
|
|
||||||
|
g.saveState();
|
||||||
|
g.addTransform (gridXform);
|
||||||
|
|
||||||
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 };
|
||||||
for (double f : freqTicks)
|
for (double f : freqTicks)
|
||||||
{
|
{
|
||||||
|
|
@ -80,7 +97,10 @@ public:
|
||||||
|
|
||||||
const float frac = analyser.freqToFraction (f);
|
const float frac = analyser.freqToFraction (f);
|
||||||
const float x = plot.getX() + gap + frac * (plot.getWidth() - 2.0f * gap);
|
const float x = plot.getX() + gap + frac * (plot.getWidth() - 2.0f * gap);
|
||||||
g.setColour (gridColour);
|
// The outermost (border) lines stay; inner lines fade with GRID FADE.
|
||||||
|
const bool isBorder = (frac <= 0.0f || frac >= 1.0f);
|
||||||
|
const float a = isBorder ? gridAmount : gridAmount * (1.0f - gridFade);
|
||||||
|
g.setColour (gridBase.withAlpha (a));
|
||||||
g.drawVerticalLine (static_cast<int> (x), plot.getY(), plot.getBottom());
|
g.drawVerticalLine (static_cast<int> (x), plot.getY(), plot.getBottom());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,10 +109,14 @@ public:
|
||||||
{
|
{
|
||||||
const float frac = analyser.dbToFraction (db);
|
const float frac = analyser.dbToFraction (db);
|
||||||
const float y = baseY - frac * plot.getHeight();
|
const float y = baseY - frac * plot.getHeight();
|
||||||
g.setColour (gridColour);
|
const bool isBorder = (frac <= 0.0f || frac >= 1.0f);
|
||||||
|
const float a = isBorder ? gridAmount : gridAmount * (1.0f - gridFade);
|
||||||
|
g.setColour (gridBase.withAlpha (a));
|
||||||
g.drawHorizontalLine (static_cast<int> (y), plot.getX(), plot.getRight());
|
g.drawHorizontalLine (static_cast<int> (y), plot.getX(), plot.getRight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g.restoreState();
|
||||||
|
|
||||||
// --- Spectrum content (mode dependent) ---
|
// --- Spectrum content (mode dependent) ---
|
||||||
if (displayMode == ModeWaveform)
|
if (displayMode == ModeWaveform)
|
||||||
drawWaveform (g, n, plot, gap, bandWidth, baseY, smoothAmount);
|
drawWaveform (g, n, plot, gap, bandWidth, baseY, smoothAmount);
|
||||||
|
|
@ -101,7 +125,9 @@ public:
|
||||||
else
|
else
|
||||||
drawBars (g, n, plot, gap, bandWidth, baseY);
|
drawBars (g, n, plot, gap, bandWidth, baseY);
|
||||||
|
|
||||||
// --- Axis labels (on top of everything) ---
|
// --- Axis labels (on top of everything; share the grid zoom) ---
|
||||||
|
g.saveState();
|
||||||
|
g.addTransform (gridXform);
|
||||||
g.setFont (juce::FontOptions (10.0f));
|
g.setFont (juce::FontOptions (10.0f));
|
||||||
g.setColour (labelColour);
|
g.setColour (labelColour);
|
||||||
for (double f : freqTicks)
|
for (double f : freqTicks)
|
||||||
|
|
@ -122,6 +148,7 @@ public:
|
||||||
g.drawText (juce::String (db, 0), area.getX(), y - 7.0f, 36.0f, 14.0f,
|
g.drawText (juce::String (db, 0), area.getX(), y - 7.0f, 36.0f, 14.0f,
|
||||||
juce::Justification::centredRight, false);
|
juce::Justification::centredRight, false);
|
||||||
}
|
}
|
||||||
|
g.restoreState();
|
||||||
}
|
}
|
||||||
|
|
||||||
static juce::String formatFreq (double f)
|
static juce::String formatFreq (double f)
|
||||||
|
|
@ -271,6 +298,9 @@ private:
|
||||||
const float segH = plot.getHeight() / static_cast<float> (leds);
|
const float segH = plot.getHeight() / static_cast<float> (leds);
|
||||||
const float blockLen = juce::jmax (1.0f, segH - 1.0f);
|
const float blockLen = juce::jmax (1.0f, segH - 1.0f);
|
||||||
|
|
||||||
|
// CURVING knob rounds the LED corners; 1.0 = fully rounded (pill).
|
||||||
|
const float radius = curveAmount * juce::jmin (bandWidth, blockLen) * 0.5f;
|
||||||
|
|
||||||
for (int i = 0; i < n; ++i)
|
for (int i = 0; i < n; ++i)
|
||||||
{
|
{
|
||||||
const float level = analyser.getLevel (i);
|
const float level = analyser.getLevel (i);
|
||||||
|
|
@ -290,7 +320,7 @@ private:
|
||||||
{
|
{
|
||||||
const float frac = (b + 0.5f) / static_cast<float> (leds);
|
const float frac = (b + 0.5f) / static_cast<float> (leds);
|
||||||
g.setColour (lf.getTeal().withMultipliedBrightness (0.35f + 0.65f * frac));
|
g.setColour (lf.getTeal().withMultipliedBrightness (0.35f + 0.65f * frac));
|
||||||
g.fillRect (x, blockTop, bandWidth, blockLen);
|
g.fillRoundedRectangle (juce::Rectangle<float> (x, blockTop, bandWidth, blockLen), radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peak honours the same discrete blocks: draw the peak block in the
|
// Peak honours the same discrete blocks: draw the peak block in the
|
||||||
|
|
@ -298,7 +328,7 @@ private:
|
||||||
if (b == peakBlock - 1)
|
if (b == peakBlock - 1)
|
||||||
{
|
{
|
||||||
g.setColour (lf.getPeak());
|
g.setColour (lf.getPeak());
|
||||||
g.fillRect (x, blockTop, bandWidth, blockLen);
|
g.fillRoundedRectangle (juce::Rectangle<float> (x, blockTop, bandWidth, blockLen), radius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -312,6 +342,9 @@ private:
|
||||||
int ledCount = 16;
|
int ledCount = 16;
|
||||||
float smoothAmount = 0.35f;
|
float smoothAmount = 0.35f;
|
||||||
float gridAmount = 0.32f;
|
float gridAmount = 0.32f;
|
||||||
|
float gridZoom = 1.0f;
|
||||||
|
float gridFade = 0.0f;
|
||||||
|
float curveAmount = 0.0f;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyserComponent)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyserComponent)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,10 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
||||||
setupKnob (ledsSlider, "");
|
setupKnob (ledsSlider, "");
|
||||||
setupKnob (smoothSlider, "");
|
setupKnob (smoothSlider, "");
|
||||||
setupKnob (gridSlider, "");
|
setupKnob (gridSlider, "");
|
||||||
|
setupKnob (gridzoomSlider, "");
|
||||||
|
setupKnob (gridfadeSlider, "");
|
||||||
|
setupKnob (curvingSlider, "");
|
||||||
|
setupKnob (tiltSlider, "");
|
||||||
barsSlider.setNumDecimalPlacesToDisplay (0);
|
barsSlider.setNumDecimalPlacesToDisplay (0);
|
||||||
modeSlider.setNumDecimalPlacesToDisplay (0);
|
modeSlider.setNumDecimalPlacesToDisplay (0);
|
||||||
ledsSlider.setNumDecimalPlacesToDisplay (0);
|
ledsSlider.setNumDecimalPlacesToDisplay (0);
|
||||||
|
|
@ -86,6 +90,26 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
||||||
gridLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
gridLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
||||||
addAndMakeVisible (gridLabel);
|
addAndMakeVisible (gridLabel);
|
||||||
|
|
||||||
|
gridzoomLabel.setText ("GRID ZOOM", juce::dontSendNotification);
|
||||||
|
gridzoomLabel.setJustificationType (juce::Justification::centred);
|
||||||
|
gridzoomLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
||||||
|
addAndMakeVisible (gridzoomLabel);
|
||||||
|
|
||||||
|
gridfadeLabel.setText ("GRID FADE", juce::dontSendNotification);
|
||||||
|
gridfadeLabel.setJustificationType (juce::Justification::centred);
|
||||||
|
gridfadeLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
||||||
|
addAndMakeVisible (gridfadeLabel);
|
||||||
|
|
||||||
|
curvingLabel.setText ("CURVING", juce::dontSendNotification);
|
||||||
|
curvingLabel.setJustificationType (juce::Justification::centred);
|
||||||
|
curvingLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
||||||
|
addAndMakeVisible (curvingLabel);
|
||||||
|
|
||||||
|
tiltLabel.setText ("TILT", juce::dontSendNotification);
|
||||||
|
tiltLabel.setJustificationType (juce::Justification::centred);
|
||||||
|
tiltLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
||||||
|
addAndMakeVisible (tiltLabel);
|
||||||
|
|
||||||
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));
|
||||||
|
|
@ -135,6 +159,14 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
||||||
processorRef.getParametersState(), "smooth", smoothSlider);
|
processorRef.getParametersState(), "smooth", smoothSlider);
|
||||||
gridAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
gridAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||||
processorRef.getParametersState(), "grid", gridSlider);
|
processorRef.getParametersState(), "grid", gridSlider);
|
||||||
|
gridzoomAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||||
|
processorRef.getParametersState(), "gridzoom", gridzoomSlider);
|
||||||
|
gridfadeAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||||
|
processorRef.getParametersState(), "gridfade", gridfadeSlider);
|
||||||
|
curvingAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||||
|
processorRef.getParametersState(), "curving", curvingSlider);
|
||||||
|
tiltAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||||
|
processorRef.getParametersState(), "tilt", tiltSlider);
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
@ -151,10 +183,23 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
||||||
: juce::String ("BARS");
|
: juce::String ("BARS");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ledsSlider.textFromValueFunction = [] (double v)
|
||||||
|
{
|
||||||
|
return juce::String (juce::roundToInt (v));
|
||||||
|
};
|
||||||
|
|
||||||
|
tiltSlider.textFromValueFunction = [] (double v)
|
||||||
|
{
|
||||||
|
return (v >= 0.0 ? juce::String ("+") : juce::String (""))
|
||||||
|
+ juce::String (v, 2);
|
||||||
|
};
|
||||||
|
|
||||||
// Force an immediate repaint of the text boxes now that our formatters
|
// Force an immediate repaint of the text boxes now that our formatters
|
||||||
// are installed (otherwise they keep the attachment's initial text).
|
// are installed (otherwise they keep the attachment's initial text).
|
||||||
barsSlider.updateText();
|
barsSlider.updateText();
|
||||||
modeSlider.updateText();
|
modeSlider.updateText();
|
||||||
|
ledsSlider.updateText();
|
||||||
|
tiltSlider.updateText();
|
||||||
|
|
||||||
processorRef.getParametersState().addParameterListener ("hue", this);
|
processorRef.getParametersState().addParameterListener ("hue", this);
|
||||||
|
|
||||||
|
|
@ -203,7 +248,6 @@ void BeamgridAudioProcessorEditor::paint (juce::Graphics& g)
|
||||||
void BeamgridAudioProcessorEditor::resized()
|
void BeamgridAudioProcessorEditor::resized()
|
||||||
{
|
{
|
||||||
const int panelH = 114;
|
const int panelH = 114;
|
||||||
const int knob = 64;
|
|
||||||
const int y = getHeight() - panelH + 14;
|
const int y = getHeight() - panelH + 14;
|
||||||
|
|
||||||
// 32px blank bar across the very top of the plugin (the dropdown sits
|
// 32px blank bar across the very top of the plugin (the dropdown sits
|
||||||
|
|
@ -215,11 +259,16 @@ void BeamgridAudioProcessorEditor::resized()
|
||||||
|
|
||||||
const int margin = 24;
|
const int margin = 24;
|
||||||
const int gap = 14;
|
const int gap = 14;
|
||||||
const int count = 9;
|
const int count = 13;
|
||||||
const int titleW = 220;
|
const int titleW = 180;
|
||||||
|
|
||||||
// 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).
|
||||||
const int avail = getWidth() - margin * 2 - titleW;
|
const int avail = getWidth() - margin * 2 - titleW;
|
||||||
|
|
||||||
|
// Shrink the knobs only if necessary so all `count` of them fit within the
|
||||||
|
// available width down to the 800px minimum; otherwise keep them at 64.
|
||||||
|
const int knob = juce::jlimit (40, 64, (avail - (count - 1) * 4) / count);
|
||||||
|
|
||||||
int spacing = knob + gap;
|
int spacing = knob + gap;
|
||||||
const int minSpacing = knob + 4;
|
const int minSpacing = knob + 4;
|
||||||
if ((count - 1) * spacing + knob > avail)
|
if ((count - 1) * spacing + knob > avail)
|
||||||
|
|
@ -236,6 +285,10 @@ void BeamgridAudioProcessorEditor::resized()
|
||||||
{ ledsSlider, ledsLabel },
|
{ ledsSlider, ledsLabel },
|
||||||
{ smoothSlider, smoothLabel },
|
{ smoothSlider, smoothLabel },
|
||||||
{ gridSlider, gridLabel },
|
{ gridSlider, gridLabel },
|
||||||
|
{ gridzoomSlider, gridzoomLabel },
|
||||||
|
{ gridfadeSlider, gridfadeLabel },
|
||||||
|
{ curvingSlider, curvingLabel },
|
||||||
|
{ tiltSlider, tiltLabel },
|
||||||
};
|
};
|
||||||
|
|
||||||
const juce::Font labelFont (juce::FontOptions (11.0f));
|
const juce::Font labelFont (juce::FontOptions (11.0f));
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,10 @@ private:
|
||||||
juce::Slider ledsSlider;
|
juce::Slider ledsSlider;
|
||||||
juce::Slider smoothSlider;
|
juce::Slider smoothSlider;
|
||||||
juce::Slider gridSlider;
|
juce::Slider gridSlider;
|
||||||
|
juce::Slider gridzoomSlider;
|
||||||
|
juce::Slider gridfadeSlider;
|
||||||
|
juce::Slider curvingSlider;
|
||||||
|
juce::Slider tiltSlider;
|
||||||
|
|
||||||
juce::Label graceLabel;
|
juce::Label graceLabel;
|
||||||
juce::Label falloffLabel;
|
juce::Label falloffLabel;
|
||||||
|
|
@ -41,6 +45,10 @@ private:
|
||||||
juce::Label ledsLabel;
|
juce::Label ledsLabel;
|
||||||
juce::Label smoothLabel;
|
juce::Label smoothLabel;
|
||||||
juce::Label gridLabel;
|
juce::Label gridLabel;
|
||||||
|
juce::Label gridzoomLabel;
|
||||||
|
juce::Label gridfadeLabel;
|
||||||
|
juce::Label curvingLabel;
|
||||||
|
juce::Label tiltLabel;
|
||||||
juce::Label titleLabel;
|
juce::Label titleLabel;
|
||||||
juce::Label statusLabel;
|
juce::Label statusLabel;
|
||||||
|
|
||||||
|
|
@ -55,6 +63,10 @@ private:
|
||||||
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;
|
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gridAttachment;
|
||||||
|
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gridzoomAttachment;
|
||||||
|
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gridfadeAttachment;
|
||||||
|
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> curvingAttachment;
|
||||||
|
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> tiltAttachment;
|
||||||
|
|
||||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BeamgridAudioProcessorEditor)
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BeamgridAudioProcessorEditor)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -51,12 +51,33 @@ BeamgridAudioProcessor::createParameterLayout()
|
||||||
juce::NormalisableRange<float> (0.0f, 1.0f, 0.001f), 0.32f,
|
juce::NormalisableRange<float> (0.0f, 1.0f, 0.001f), 0.32f,
|
||||||
juce::AudioParameterFloatAttributes().withLabel ("")));
|
juce::AudioParameterFloatAttributes().withLabel ("")));
|
||||||
|
|
||||||
|
params.push_back (std::make_unique<juce::AudioParameterFloat>(
|
||||||
|
"gridzoom", "Grid Zoom",
|
||||||
|
juce::NormalisableRange<float> (0.98f, 1.02f, 0.001f), 1.0f,
|
||||||
|
juce::AudioParameterFloatAttributes().withLabel ("")));
|
||||||
|
|
||||||
|
params.push_back (std::make_unique<juce::AudioParameterFloat>(
|
||||||
|
"gridfade", "Grid Fade",
|
||||||
|
juce::NormalisableRange<float> (0.0f, 1.0f, 0.001f), 0.0f,
|
||||||
|
juce::AudioParameterFloatAttributes().withLabel ("")));
|
||||||
|
|
||||||
|
params.push_back (std::make_unique<juce::AudioParameterFloat>(
|
||||||
|
"curving", "Curving",
|
||||||
|
juce::NormalisableRange<float> (0.0f, 1.0f, 0.001f), 0.0f,
|
||||||
|
juce::AudioParameterFloatAttributes().withLabel ("")));
|
||||||
|
|
||||||
|
params.push_back (std::make_unique<juce::AudioParameterFloat>(
|
||||||
|
"tilt", "Tilt",
|
||||||
|
juce::NormalisableRange<float> (-1.0f, 1.0f, 0.001f), 0.0f,
|
||||||
|
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
|
||||||
[] (float, float, float v) // snapToLegalValue
|
[] (float, float, float v) // snapToLegalValue
|
||||||
{
|
{
|
||||||
const float steps[] = { 8.0f, 16.0f, 32.0f, 64.0f, 128.0f };
|
const float steps[] = { 8.0f, 16.0f, 24.0f, 32.0f, 40.0f, 48.0f, 56.0f, 64.0f,
|
||||||
|
72.0f, 80.0f, 88.0f, 96.0f, 104.0f, 112.0f, 120.0f, 128.0f };
|
||||||
float best = steps[0];
|
float best = steps[0];
|
||||||
float bestDist = std::abs (v - best);
|
float bestDist = std::abs (v - best);
|
||||||
for (float s : steps)
|
for (float s : steps)
|
||||||
|
|
@ -77,8 +98,29 @@ BeamgridAudioProcessor::createParameterLayout()
|
||||||
params.push_back (std::make_unique<juce::AudioParameterInt>(
|
params.push_back (std::make_unique<juce::AudioParameterInt>(
|
||||||
"mode", "Mode", 1, 3, 1));
|
"mode", "Mode", 1, 3, 1));
|
||||||
|
|
||||||
params.push_back (std::make_unique<juce::AudioParameterInt>(
|
juce::NormalisableRange<float> ledsRange (4.0f, 48.0f,
|
||||||
"leds", "LEDs/Bar", 4, 48, 16));
|
[] (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, float, float v) // snapToLegalValue
|
||||||
|
{
|
||||||
|
const float steps[] = { 4.0f, 8.0f, 12.0f, 16.0f, 20.0f, 24.0f,
|
||||||
|
28.0f, 32.0f, 36.0f, 40.0f, 44.0f, 48.0f };
|
||||||
|
float best = steps[0];
|
||||||
|
float bestDist = std::abs (v - best);
|
||||||
|
for (float s : steps)
|
||||||
|
{
|
||||||
|
const float dist = std::abs (v - s);
|
||||||
|
if (dist < bestDist)
|
||||||
|
{
|
||||||
|
bestDist = dist;
|
||||||
|
best = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
});
|
||||||
|
|
||||||
|
params.push_back (std::make_unique<juce::AudioParameterFloat>(
|
||||||
|
"leds", "LEDs/Bar", ledsRange, 16.0f));
|
||||||
|
|
||||||
return { params.begin(), params.end() };
|
return { params.begin(), params.end() };
|
||||||
}
|
}
|
||||||
|
|
@ -107,6 +149,8 @@ void BeamgridAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer,
|
||||||
{
|
{
|
||||||
juce::ScopedNoDenormals noDenormals;
|
juce::ScopedNoDenormals noDenormals;
|
||||||
|
|
||||||
|
analyser.setTilt (*params.getRawParameterValue ("tilt"));
|
||||||
|
|
||||||
// Analyse the first channel; pass the audio through unchanged.
|
// Analyse the first channel; pass the audio through unchanged.
|
||||||
if (buffer.getNumChannels() > 0)
|
if (buffer.getNumChannels() > 0)
|
||||||
analyser.push (buffer.getReadPointer (0), buffer.getNumSamples());
|
analyser.push (buffer.getReadPointer (0), buffer.getNumSamples());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue