mirror of
https://codeberg.org/armin/beamgrid.git
synced 2026-09-01 04:10:47 +02:00
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:
parent
ca9555af13
commit
aeb4b83281
7 changed files with 142 additions and 18 deletions
|
|
@ -34,6 +34,9 @@ and a configurable display.
|
|||
| 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) |
|
||||
| BOOST | 0.75 – 2.5 | Global display level multiplier (1.0 = unity) |
|
||||
| 3D | 0 – 1 | Pseudo-3D bevel/shading on bars, LEDs, peaks and curve (0 = flat) |
|
||||
| G-ANG | -180 – 180 | Gradient angle for the 3D shading (0 = top-lit, +/-90 = side-lit) |
|
||||
|
||||
## Building
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ void Analyser::computeFFT()
|
|||
double norm = (db + tiltDb - minDb) / (maxDb - minDb);
|
||||
norm = juce::jlimit (0.0, 1.0, norm);
|
||||
|
||||
targets[b] = static_cast<float> (norm);
|
||||
targets[b] = static_cast<float> (norm * boost);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ public:
|
|||
void setNumBands (int n) noexcept;
|
||||
void setTilt (double amount) noexcept { tilt = amount; }
|
||||
double getTilt() const noexcept { return tilt; }
|
||||
void setBoost (double amount) noexcept { boost = amount; }
|
||||
double getBoost() const noexcept { return boost; }
|
||||
double getGraceSeconds() const noexcept { return grace; }
|
||||
double getFalloffRate() const noexcept { return falloffRate; }
|
||||
|
||||
|
|
@ -79,4 +81,8 @@ private:
|
|||
// the centre of the log-frequency range.
|
||||
static constexpr double maxTiltDb = 18.0;
|
||||
double tilt = 0.0;
|
||||
|
||||
// Display boost: a global multiplier on the level the analyser shows
|
||||
// (0.75 = quieter, 1.5 = louder, 1.0 = unity).
|
||||
double boost = 1.0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
// 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)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
|||
setupKnob (gridfadeSlider, "");
|
||||
setupKnob (curvingSlider, "");
|
||||
setupKnob (tiltSlider, "");
|
||||
setupKnob (boostSlider, "");
|
||||
setupKnob (depth3dSlider, "");
|
||||
setupKnob (gradangleSlider, "");
|
||||
barsSlider.setNumDecimalPlacesToDisplay (0);
|
||||
modeSlider.setNumDecimalPlacesToDisplay (0);
|
||||
ledsSlider.setNumDecimalPlacesToDisplay (0);
|
||||
|
|
@ -110,6 +113,21 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
|||
tiltLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
||||
addAndMakeVisible (tiltLabel);
|
||||
|
||||
boostLabel.setText ("BOOST", juce::dontSendNotification);
|
||||
boostLabel.setJustificationType (juce::Justification::centred);
|
||||
boostLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
||||
addAndMakeVisible (boostLabel);
|
||||
|
||||
depth3dLabel.setText ("3D", juce::dontSendNotification);
|
||||
depth3dLabel.setJustificationType (juce::Justification::centred);
|
||||
depth3dLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
||||
addAndMakeVisible (depth3dLabel);
|
||||
|
||||
gradangleLabel.setText ("G-ANG", juce::dontSendNotification);
|
||||
gradangleLabel.setJustificationType (juce::Justification::centred);
|
||||
gradangleLabel.setColour (juce::Label::textColourId, juce::Colours::lightgrey);
|
||||
addAndMakeVisible (gradangleLabel);
|
||||
|
||||
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));
|
||||
|
|
@ -167,6 +185,12 @@ BeamgridAudioProcessorEditor::BeamgridAudioProcessorEditor (BeamgridAudioProcess
|
|||
processorRef.getParametersState(), "curving", curvingSlider);
|
||||
tiltAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||
processorRef.getParametersState(), "tilt", tiltSlider);
|
||||
boostAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||
processorRef.getParametersState(), "boost", boostSlider);
|
||||
depth3dAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||
processorRef.getParametersState(), "depth3d", depth3dSlider);
|
||||
gradangleAttachment = std::make_unique<juce::AudioProcessorValueTreeState::SliderAttachment>(
|
||||
processorRef.getParametersState(), "gradangle", gradangleSlider);
|
||||
|
||||
// Set these AFTER the attachments: SliderAttachment installs its own
|
||||
// textFromValueFunction, so assigning afterwards makes ours take effect.
|
||||
|
|
@ -259,7 +283,7 @@ void BeamgridAudioProcessorEditor::resized()
|
|||
|
||||
const int margin = 24;
|
||||
const int gap = 14;
|
||||
const int count = 13;
|
||||
const int count = 16;
|
||||
const int titleW = 180;
|
||||
|
||||
// Available width for the knob row (leave room on the right for the title).
|
||||
|
|
@ -289,6 +313,9 @@ void BeamgridAudioProcessorEditor::resized()
|
|||
{ gridfadeSlider, gridfadeLabel },
|
||||
{ curvingSlider, curvingLabel },
|
||||
{ tiltSlider, tiltLabel },
|
||||
{ boostSlider, boostLabel },
|
||||
{ depth3dSlider, depth3dLabel },
|
||||
{ gradangleSlider, gradangleLabel },
|
||||
};
|
||||
|
||||
const juce::Font labelFont (juce::FontOptions (11.0f));
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ private:
|
|||
juce::Slider gridfadeSlider;
|
||||
juce::Slider curvingSlider;
|
||||
juce::Slider tiltSlider;
|
||||
juce::Slider boostSlider;
|
||||
juce::Slider depth3dSlider;
|
||||
juce::Slider gradangleSlider;
|
||||
|
||||
juce::Label graceLabel;
|
||||
juce::Label falloffLabel;
|
||||
|
|
@ -49,6 +52,9 @@ private:
|
|||
juce::Label gridfadeLabel;
|
||||
juce::Label curvingLabel;
|
||||
juce::Label tiltLabel;
|
||||
juce::Label boostLabel;
|
||||
juce::Label depth3dLabel;
|
||||
juce::Label gradangleLabel;
|
||||
juce::Label titleLabel;
|
||||
juce::Label statusLabel;
|
||||
|
||||
|
|
@ -67,6 +73,9 @@ private:
|
|||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gridfadeAttachment;
|
||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> curvingAttachment;
|
||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> tiltAttachment;
|
||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> boostAttachment;
|
||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> depth3dAttachment;
|
||||
std::unique_ptr<juce::AudioProcessorValueTreeState::SliderAttachment> gradangleAttachment;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BeamgridAudioProcessorEditor)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -71,6 +71,21 @@ BeamgridAudioProcessor::createParameterLayout()
|
|||
juce::NormalisableRange<float> (-1.0f, 1.0f, 0.001f), 0.0f,
|
||||
juce::AudioParameterFloatAttributes().withLabel ("")));
|
||||
|
||||
params.push_back (std::make_unique<juce::AudioParameterFloat>(
|
||||
"boost", "Boost",
|
||||
juce::NormalisableRange<float> (0.75f, 2.5f, 0.001f), 1.0f,
|
||||
juce::AudioParameterFloatAttributes().withLabel ("")));
|
||||
|
||||
params.push_back (std::make_unique<juce::AudioParameterFloat>(
|
||||
"depth3d", "3D",
|
||||
juce::NormalisableRange<float> (0.0f, 1.0f, 0.001f), 0.0f,
|
||||
juce::AudioParameterFloatAttributes().withLabel ("")));
|
||||
|
||||
params.push_back (std::make_unique<juce::AudioParameterFloat>(
|
||||
"gradangle", "Gradient Angle",
|
||||
juce::NormalisableRange<float> (-180.0f, 180.0f, 0.5f), 0.0f,
|
||||
juce::AudioParameterFloatAttributes().withLabel ("")));
|
||||
|
||||
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 (v - start) / (end - start); }, // convertTo0To1
|
||||
|
|
@ -150,6 +165,7 @@ void BeamgridAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer,
|
|||
juce::ScopedNoDenormals noDenormals;
|
||||
|
||||
analyser.setTilt (*params.getRawParameterValue ("tilt"));
|
||||
analyser.setBoost (*params.getRawParameterValue ("boost"));
|
||||
|
||||
// Analyse the first channel; pass the audio through unchanged.
|
||||
if (buffer.getNumChannels() > 0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue