Add BOOST, 3D depth, and gradient-angle knobs

- BOOST: global display level multiplier (0.75 - 2.5, 1.0 default)
- 3D: pseudo-3D bevel/shading on bars, LEDs, peaks and curve (0 = flat)
- G-ANG: 3D gradient angle (-180 - 180, 0 = top-lit)
- Apply CURVING rounded corners to bars and peaks in BAR mode
This commit is contained in:
Armin 2026-08-14 03:38:09 +02:00
commit aeb4b83281
7 changed files with 142 additions and 18 deletions

View file

@ -38,6 +38,8 @@ public:
const double gridZoomV = *params.getRawParameterValue ("gridzoom");
const double gridFadeV = *params.getRawParameterValue ("gridfade");
const double curveV = *params.getRawParameterValue ("curving");
const double depthV = *params.getRawParameterValue ("depth3d");
const double gradAngV = *params.getRawParameterValue ("gradangle");
analyser.setGraceSeconds (graceMs / 1000.0);
analyser.setFalloffRate (0.1 + falloff * 5.0);
@ -55,6 +57,8 @@ public:
gridZoom = static_cast<float> (gridZoomV);
gridFade = static_cast<float> (gridFadeV);
curveAmount = static_cast<float> (curveV);
depthAmount = static_cast<float> (depthV);
gradAngle = static_cast<float> (gradAngV);
analyser.update (1.0 / 60.0);
repaint();
@ -159,9 +163,49 @@ public:
}
private:
// Build a top-lit 3D gradient across `bounds`. `angleDeg` rotates the
// gradient direction (0 = vertical/bright-top, +/-90 = horizontal), `bright`
// and `dark` scale the highlight/shadow strength (typically 0..1).
juce::ColourGradient make3DGradient (juce::Colour base, const juce::Rectangle<float>& bounds,
float angleDeg, float bright, float dark)
{
const float rad = juce::degreesToRadians (angleDeg);
const float dx = std::sin (rad);
const float dy = std::cos (rad);
const juce::Point<float> c = bounds.getCentre();
const float half = 0.5f * std::sqrt (bounds.getWidth() * bounds.getWidth()
+ bounds.getHeight() * bounds.getHeight());
const juce::Point<float> p1 = c - juce::Point<float> (dx, dy) * half;
const juce::Point<float> p2 = c + juce::Point<float> (dx, dy) * half;
juce::ColourGradient grad (base.brighter (bright), p1.x, p1.y,
base.darker (dark), p2.x, p2.y, false);
grad.addColour (0.5, base);
return grad;
}
// Fill a rounded rectangle with a top-lit 3D gradient. `depth` (0..1) blends
// between a flat solid fill and a fully raised/beveled look, angled by the
// G-ANG knob, so the two knobs control the 3D appearance.
void fill3D (juce::Graphics& g, juce::Colour base, const juce::Rectangle<float>& r,
float radius, float depth)
{
if (depth <= 0.001f)
{
g.setColour (base);
g.fillRoundedRectangle (r, radius);
return;
}
g.setGradientFill (make3DGradient (base, r, gradAngle, depth, depth));
g.fillRoundedRectangle (r, radius);
}
void drawBars (juce::Graphics& g, int n, const juce::Rectangle<float>& plot,
float gap, float bandWidth, float baseY)
{
const float peakH = 3.0f;
const float peakRadius = curveAmount * juce::jmin (bandWidth, peakH) * 0.5f;
for (int i = 0; i < n; ++i)
{
const float level = analyser.getLevel (i);
@ -171,12 +215,12 @@ private:
const float h = level * plot.getHeight();
const float y = baseY - h;
g.setColour (lf.getTeal());
g.fillRect (x, y, bandWidth, h);
const float barRadius = curveAmount * juce::jmin (bandWidth, h, 8.0f) * 0.5f;
fill3D (g, lf.getTeal(), juce::Rectangle<float> (x, y, bandWidth, h), barRadius, depthAmount);
const float peakY = baseY - peak * plot.getHeight();
g.setColour (lf.getPeak());
g.fillRect (x, peakY - 2.0f, bandWidth, 3.0f);
fill3D (g, lf.getPeak(), juce::Rectangle<float> (x, peakY - 2.0f, bandWidth, peakH), peakRadius, depthAmount);
}
}
@ -237,14 +281,23 @@ private:
wave.lineTo (plot.getX(), baseY);
wave.closeSubPath();
// Brighter at the top, darker toward the bottom.
juce::ColourGradient grad (lf.getTeal().brighter (0.35f), 0.0f, plot.getY(),
lf.getTeal().darker (0.85f), 0.0f, plot.getBottom(), false);
g.setGradientFill (grad);
g.fillPath (wave);
// 3D: a top-lit gradient (bright edge fading to a darker underside),
// angled by the G-ANG knob. `depthAmount` blends from a flat fill (0)
// to a fully raised look (1).
const juce::Colour base = lf.getTeal();
if (depthAmount <= 0.001f)
{
g.setColour (base);
g.fillPath (wave);
}
else
{
g.setGradientFill (make3DGradient (base, plot, gradAngle, 0.7f * depthAmount, depthAmount));
g.fillPath (wave);
}
// Bright outline tracing the waveform.
g.setColour (lf.getTeal().brighter (0.5f));
// Bright top-edge outline (the "lit" highlight), stronger with depth.
g.setColour (base.brighter (0.3f + 0.5f * depthAmount));
g.strokePath (wave, juce::PathStrokeType (1.5f));
// Smooth peak-hold wave: per-band peaks, smoothed with the same
@ -287,7 +340,16 @@ private:
}
peakWave.quadraticTo (ppts[n - 1], ppts[n - 1]);
peakWave.lineTo (plot.getRight(), baseY - peaks[n - 1] * plot.getHeight());
g.setColour (lf.getPeak());
// Peak wave gets the same top-lit 3D shading.
const juce::Colour pbase = lf.getPeak();
if (depthAmount <= 0.001f)
{
g.setColour (pbase);
}
else
{
g.setGradientFill (make3DGradient (pbase, plot, gradAngle, 0.7f * depthAmount, depthAmount));
}
g.strokePath (peakWave, juce::PathStrokeType (2.0f));
}
@ -319,16 +381,15 @@ private:
if (b < litCount)
{
const float frac = (b + 0.5f) / static_cast<float> (leds);
g.setColour (lf.getTeal().withMultipliedBrightness (0.35f + 0.65f * frac));
g.fillRoundedRectangle (juce::Rectangle<float> (x, blockTop, bandWidth, blockLen), radius);
const juce::Colour base = lf.getTeal().withMultipliedBrightness (0.35f + 0.65f * frac);
fill3D (g, base, juce::Rectangle<float> (x, blockTop, bandWidth, blockLen), radius, depthAmount);
}
// Peak honours the same discrete blocks: draw the peak block in the
// peak colour (a single, fully-filled block).
if (b == peakBlock - 1)
{
g.setColour (lf.getPeak());
g.fillRoundedRectangle (juce::Rectangle<float> (x, blockTop, bandWidth, blockLen), radius);
fill3D (g, lf.getPeak(), juce::Rectangle<float> (x, blockTop, bandWidth, blockLen), radius, depthAmount);
}
}
}
@ -345,6 +406,8 @@ private:
float gridZoom = 1.0f;
float gridFade = 0.0f;
float curveAmount = 0.0f;
float depthAmount = 0.0f;
float gradAngle = 0.0f;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyserComponent)
};