mirror of
https://codeberg.org/armin/beamgrid.git
synced 2026-09-01 04:10:47 +02:00
add LICENSE, update README, update source
This commit is contained in:
parent
6ab142e450
commit
e0177ed26f
9 changed files with 395 additions and 53 deletions
|
|
@ -9,6 +9,8 @@ class AnalyserComponent : public juce::Component,
|
|||
public juce::Timer
|
||||
{
|
||||
public:
|
||||
enum { ModeBars = 1, ModeWaveform = 2, ModeLED = 3 };
|
||||
|
||||
AnalyserComponent (Analyser& analyserToUse,
|
||||
juce::AudioProcessorValueTreeState& paramsToUse,
|
||||
BeamgridLookAndFeel& lookAndFeelToUse)
|
||||
|
|
@ -29,6 +31,9 @@ public:
|
|||
const double barfall = *params.getRawParameterValue ("barfalloff");
|
||||
const int bars = juce::roundToInt (params.getRawParameterValue ("bars")->load());
|
||||
const double hue = *params.getRawParameterValue ("hue");
|
||||
const int mode = juce::roundToInt (params.getRawParameterValue ("mode")->load());
|
||||
const int leds = juce::roundToInt (params.getRawParameterValue ("leds")->load());
|
||||
const double smooth = *params.getRawParameterValue ("smooth");
|
||||
|
||||
analyser.setGraceSeconds (graceMs / 1000.0);
|
||||
analyser.setFalloffRate (0.1 + falloff * 5.0);
|
||||
|
|
@ -39,6 +44,10 @@ public:
|
|||
// HUE knob: 12 o'clock (0.5) = no rotation; left/right rotate +/- 180 deg.
|
||||
lf.setHueTurns (static_cast<float> (hue - 0.5));
|
||||
|
||||
displayMode = mode;
|
||||
ledCount = leds;
|
||||
smoothAmount = static_cast<float> (smooth);
|
||||
|
||||
analyser.update (1.0 / 60.0);
|
||||
repaint();
|
||||
}
|
||||
|
|
@ -57,12 +66,11 @@ 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 (0.32f);
|
||||
const juce::Colour gridColour = lf.transform (juce::Colours::grey.brighter (0.2f)).withAlpha (0.32f);
|
||||
const juce::Colour labelColour = lf.transform (juce::Colours::grey);
|
||||
|
||||
// Frequency gridlines + labels (log spaced).
|
||||
// --- Gridlines (drawn first; labels drawn last so they stay readable) ---
|
||||
const double freqTicks[] = { 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000 };
|
||||
g.setFont (juce::FontOptions (10.0f));
|
||||
for (double f : freqTicks)
|
||||
{
|
||||
if (f < analyser.getMinFreq() || f > analyser.getMaxFreq())
|
||||
|
|
@ -70,31 +78,61 @@ public:
|
|||
|
||||
const float frac = analyser.freqToFraction (f);
|
||||
const float x = plot.getX() + gap + frac * (plot.getWidth() - 2.0f * gap);
|
||||
|
||||
g.setColour (gridColour);
|
||||
g.drawVerticalLine (static_cast<int> (x), plot.getY(), plot.getBottom());
|
||||
|
||||
g.setColour (labelColour);
|
||||
g.drawText (formatFreq (f), x - 18.0f, plot.getBottom() + 3.0f, 36.0f, 14.0f,
|
||||
juce::Justification::centredTop, false);
|
||||
}
|
||||
|
||||
// Level gridlines + labels (dB).
|
||||
const double dbTicks[] = { 0.0, -6.0, -12.0, -24.0, -36.0, -48.0 };
|
||||
for (double db : dbTicks)
|
||||
{
|
||||
const float frac = analyser.dbToFraction (db);
|
||||
const float y = baseY - frac * plot.getHeight();
|
||||
|
||||
g.setColour (gridColour);
|
||||
g.drawHorizontalLine (static_cast<int> (y), plot.getX(), plot.getRight());
|
||||
}
|
||||
|
||||
g.setColour (labelColour);
|
||||
// --- Spectrum content (mode dependent) ---
|
||||
if (displayMode == ModeWaveform)
|
||||
drawWaveform (g, n, plot, gap, bandWidth, baseY, smoothAmount);
|
||||
else if (displayMode == ModeLED)
|
||||
drawLED (g, n, plot, gap, bandWidth, baseY);
|
||||
else
|
||||
drawBars (g, n, plot, gap, bandWidth, baseY);
|
||||
|
||||
// --- Axis labels (on top of everything) ---
|
||||
g.setFont (juce::FontOptions (10.0f));
|
||||
g.setColour (labelColour);
|
||||
for (double f : freqTicks)
|
||||
{
|
||||
if (f < analyser.getMinFreq() || f > analyser.getMaxFreq())
|
||||
continue;
|
||||
|
||||
const float frac = analyser.freqToFraction (f);
|
||||
const float x = plot.getX() + gap + frac * (plot.getWidth() - 2.0f * gap);
|
||||
g.drawText (formatFreq (f), x - 18.0f, plot.getBottom() + 3.0f, 36.0f, 14.0f,
|
||||
juce::Justification::centredTop, false);
|
||||
}
|
||||
|
||||
for (double db : dbTicks)
|
||||
{
|
||||
const float frac = analyser.dbToFraction (db);
|
||||
const float y = baseY - frac * plot.getHeight();
|
||||
g.drawText (juce::String (db, 0), area.getX(), y - 7.0f, 36.0f, 14.0f,
|
||||
juce::Justification::centredRight, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Analyzer bands (teal) and peak markers (peak color).
|
||||
static juce::String formatFreq (double f)
|
||||
{
|
||||
if (f >= 1000.0)
|
||||
return juce::String (f / 1000.0, 1, false) + "k";
|
||||
return juce::String (static_cast<int> (f));
|
||||
}
|
||||
|
||||
private:
|
||||
void drawBars (juce::Graphics& g, int n, const juce::Rectangle<float>& plot,
|
||||
float gap, float bandWidth, float baseY)
|
||||
{
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
const float level = analyser.getLevel (i);
|
||||
|
|
@ -113,17 +151,147 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
static juce::String formatFreq (double f)
|
||||
void drawWaveform (juce::Graphics& g, int n, const juce::Rectangle<float>& plot,
|
||||
float gap, float bandWidth, float baseY, float smooth)
|
||||
{
|
||||
if (f >= 1000.0)
|
||||
return juce::String (f / 1000.0, 1, false) + "k";
|
||||
return juce::String (static_cast<int> (f));
|
||||
// Gather the band levels and apply 1-2-1 smoothing passes. `smooth`
|
||||
// (0..1) blends each pass toward the averaged value, so 0 = untouched
|
||||
// (edgy/spiky) and 1 = heavily smoothed.
|
||||
const float t = juce::jlimit (0.0f, 1.0f, smooth);
|
||||
|
||||
juce::HeapBlock<float> levels (static_cast<size_t> (n));
|
||||
for (int i = 0; i < n; ++i)
|
||||
levels[i] = analyser.getLevel (i);
|
||||
|
||||
for (int pass = 0; pass < 4; ++pass)
|
||||
{
|
||||
juce::HeapBlock<float> tmp (static_cast<size_t> (n));
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
const float a = levels[juce::jmax (0, i - 1)];
|
||||
const float b = levels[i];
|
||||
const float c = levels[juce::jmin (n - 1, i + 1)];
|
||||
const float avg = a * 0.25f + b * 0.5f + c * 0.25f;
|
||||
tmp[i] = b * (1.0f - t) + avg * t;
|
||||
}
|
||||
for (int i = 0; i < n; ++i)
|
||||
levels[i] = tmp[i];
|
||||
}
|
||||
|
||||
// Trace the curve. `smooth` controls how much the band levels are
|
||||
// averaged (above); the path itself is straight segments between band
|
||||
// centres, so at smooth = 0 it stays raw/edgy and at 1 it is rounded
|
||||
// by the heavy level smoothing.
|
||||
juce::Path wave;
|
||||
wave.startNewSubPath (plot.getX() + gap, baseY);
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
const float x = plot.getX() + gap + i * (bandWidth + gap) + bandWidth * 0.5f;
|
||||
const float y = baseY - levels[i] * plot.getHeight();
|
||||
wave.lineTo (x, y);
|
||||
}
|
||||
wave.lineTo (plot.getRight() - gap, 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);
|
||||
|
||||
// Bright outline tracing the waveform.
|
||||
g.setColour (lf.getTeal().brighter (0.5f));
|
||||
g.strokePath (wave, juce::PathStrokeType (1.5f));
|
||||
|
||||
// Smooth peak-hold wave: per-band peaks, smoothed with the same
|
||||
// amount as the main curve and traced as a matching spline.
|
||||
juce::HeapBlock<float> peaks (static_cast<size_t> (n));
|
||||
for (int i = 0; i < n; ++i)
|
||||
peaks[i] = analyser.getPeak (i);
|
||||
|
||||
for (int pass = 0; pass < 4; ++pass)
|
||||
{
|
||||
juce::HeapBlock<float> tmp (static_cast<size_t> (n));
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
const float a = peaks[juce::jmax (0, i - 1)];
|
||||
const float b = peaks[i];
|
||||
const float c = peaks[juce::jmin (n - 1, i + 1)];
|
||||
const float avg = a * 0.25f + b * 0.5f + c * 0.25f;
|
||||
tmp[i] = b * (1.0f - t) + avg * t;
|
||||
}
|
||||
for (int i = 0; i < n; ++i)
|
||||
peaks[i] = tmp[i];
|
||||
}
|
||||
|
||||
juce::HeapBlock<juce::Point<float>> ppts (static_cast<size_t> (n));
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
const float x = plot.getX() + gap + i * (bandWidth + gap) + bandWidth * 0.5f;
|
||||
const float y = baseY - peaks[i] * plot.getHeight();
|
||||
ppts[i] = { x, y };
|
||||
}
|
||||
|
||||
juce::Path peakWave;
|
||||
peakWave.startNewSubPath (ppts[0]);
|
||||
for (int i = 0; i < n - 1; ++i)
|
||||
{
|
||||
const juce::Point<float> mid = { (ppts[i].x + ppts[i + 1].x) * 0.5f,
|
||||
(ppts[i].y + ppts[i + 1].y) * 0.5f };
|
||||
peakWave.quadraticTo (ppts[i], mid);
|
||||
}
|
||||
peakWave.quadraticTo (ppts[n - 1], ppts[n - 1]);
|
||||
g.setColour (lf.getPeak());
|
||||
g.strokePath (peakWave, juce::PathStrokeType (2.0f));
|
||||
}
|
||||
|
||||
void drawLED (juce::Graphics& g, int n, const juce::Rectangle<float>& plot,
|
||||
float gap, float bandWidth, float baseY)
|
||||
{
|
||||
const int leds = juce::jmax (1, ledCount);
|
||||
const float segH = plot.getHeight() / static_cast<float> (leds);
|
||||
const float blockLen = juce::jmax (1.0f, segH - 1.0f);
|
||||
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
const float level = analyser.getLevel (i);
|
||||
const float peak = analyser.getPeak (i);
|
||||
|
||||
const int litCount = juce::jlimit (0, leds, juce::roundToInt (level * leds));
|
||||
const int peakBlock = juce::jlimit (0, leds, juce::roundToInt (peak * leds)); // top lit block (1-based)
|
||||
|
||||
const float x = plot.getX() + gap + i * (bandWidth + gap);
|
||||
|
||||
for (int b = 0; b < leds; ++b)
|
||||
{
|
||||
const float blockBottom = baseY - b * segH;
|
||||
const float blockTop = blockBottom - blockLen;
|
||||
|
||||
if (b < litCount)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// 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.fillRect (x, blockTop, bandWidth, blockLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Analyser& analyser;
|
||||
juce::AudioProcessorValueTreeState& params;
|
||||
BeamgridLookAndFeel& lf;
|
||||
|
||||
int displayMode = ModeBars;
|
||||
int ledCount = 16;
|
||||
float smoothAmount = 0.35f;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyserComponent)
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue