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:
Armin 2026-08-14 02:55:28 +02:00
commit ca9555af13
7 changed files with 176 additions and 15 deletions

View file

@ -35,6 +35,9 @@ public:
const int leds = juce::roundToInt (params.getRawParameterValue ("leds")->load());
const double smooth = *params.getRawParameterValue ("smooth");
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.setFalloffRate (0.1 + falloff * 5.0);
@ -49,6 +52,9 @@ public:
ledCount = leds;
smoothAmount = static_cast<float> (smooth);
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);
repaint();
@ -68,10 +74,21 @@ public:
const float bandWidth = (plot.getWidth() - gap * (n + 1)) / static_cast<float> (n);
const float baseY = plot.getBottom();
const juce::Colour gridColour = lf.transform (juce::Colours::grey.brighter (0.2f)).withAlpha (gridAmount);
const juce::Colour labelColour = gridColour;
const juce::Colour gridBase = lf.transform (juce::Colours::grey.brighter (0.2f));
const juce::Colour labelColour = gridBase.withAlpha (gridAmount);
// --- 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 };
for (double f : freqTicks)
{
@ -80,7 +97,10 @@ public:
const float frac = analyser.freqToFraction (f);
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());
}
@ -89,10 +109,14 @@ public:
{
const float frac = analyser.dbToFraction (db);
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.restoreState();
// --- Spectrum content (mode dependent) ---
if (displayMode == ModeWaveform)
drawWaveform (g, n, plot, gap, bandWidth, baseY, smoothAmount);
@ -101,7 +125,9 @@ public:
else
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.setColour (labelColour);
for (double f : freqTicks)
@ -122,6 +148,7 @@ public:
g.drawText (juce::String (db, 0), area.getX(), y - 7.0f, 36.0f, 14.0f,
juce::Justification::centredRight, false);
}
g.restoreState();
}
static juce::String formatFreq (double f)
@ -271,6 +298,9 @@ private:
const float segH = plot.getHeight() / static_cast<float> (leds);
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)
{
const float level = analyser.getLevel (i);
@ -290,7 +320,7 @@ private:
{
const float frac = (b + 0.5f) / static_cast<float> (leds);
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
@ -298,7 +328,7 @@ private:
if (b == peakBlock - 1)
{
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;
float smoothAmount = 0.35f;
float gridAmount = 0.32f;
float gridZoom = 1.0f;
float gridFade = 0.0f;
float curveAmount = 0.0f;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyserComponent)
};