mirror of
https://codeberg.org/armin/beamgrid.git
synced 2026-09-01 04:10:47 +02:00
Initial BEAMGRID VST3/AU spectrum analyzer
This commit is contained in:
commit
0a3fe6d853
15 changed files with 908 additions and 0 deletions
129
Source/AnalyserComponent.h
Normal file
129
Source/AnalyserComponent.h
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
#pragma once
|
||||
|
||||
#include "JuceHeader.h"
|
||||
#include "Analyser.h"
|
||||
#include "BeamgridLookAndFeel.h"
|
||||
|
||||
// Black spectrum visualizer: teal analyzer bands and peak-colored markers.
|
||||
class AnalyserComponent : public juce::Component,
|
||||
public juce::Timer
|
||||
{
|
||||
public:
|
||||
AnalyserComponent (Analyser& analyserToUse,
|
||||
juce::AudioProcessorValueTreeState& paramsToUse,
|
||||
BeamgridLookAndFeel& lookAndFeelToUse)
|
||||
: analyser (analyserToUse), params (paramsToUse), lf (lookAndFeelToUse)
|
||||
{
|
||||
startTimerHz (60);
|
||||
}
|
||||
|
||||
~AnalyserComponent() override
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
const double graceMs = *params.getRawParameterValue ("grace");
|
||||
const double falloff = *params.getRawParameterValue ("falloff");
|
||||
const double barfall = *params.getRawParameterValue ("barfalloff");
|
||||
const int bars = juce::roundToInt (params.getRawParameterValue ("bars")->load());
|
||||
const double hue = *params.getRawParameterValue ("hue");
|
||||
|
||||
analyser.setGraceSeconds (graceMs / 1000.0);
|
||||
analyser.setFalloffRate (0.1 + falloff * 5.0);
|
||||
// Bar falloff: higher knob -> faster bar descent (smaller release tau).
|
||||
analyser.setBarReleaseTau (2.0 - barfall * 1.98);
|
||||
analyser.setNumBands (bars);
|
||||
|
||||
// HUE knob: 12 o'clock (0.5) = no rotation; left/right rotate +/- 180 deg.
|
||||
lf.setHueTurns (static_cast<float> (hue - 0.5));
|
||||
|
||||
analyser.update (1.0 / 60.0);
|
||||
repaint();
|
||||
}
|
||||
|
||||
void paint (juce::Graphics& g) override
|
||||
{
|
||||
const auto area = getLocalBounds().toFloat().reduced (8.0f);
|
||||
const int n = analyser.getNumBands();
|
||||
|
||||
// Reserve margins for the axis legends.
|
||||
juce::Rectangle<float> plot = area;
|
||||
plot.setLeft (area.getX() + 40.0f);
|
||||
plot.setBottom (area.getBottom() - 20.0f);
|
||||
|
||||
const float gap = 2.0f;
|
||||
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 labelColour = lf.transform (juce::Colours::grey);
|
||||
|
||||
// Frequency gridlines + labels (log spaced).
|
||||
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())
|
||||
continue;
|
||||
|
||||
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);
|
||||
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).
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
const float level = analyser.getLevel (i);
|
||||
const float peak = analyser.getPeak (i);
|
||||
|
||||
const float x = plot.getX() + gap + i * (bandWidth + gap);
|
||||
const float h = level * plot.getHeight();
|
||||
const float y = baseY - h;
|
||||
|
||||
g.setColour (lf.getTeal());
|
||||
g.fillRect (x, y, bandWidth, h);
|
||||
|
||||
const float peakY = baseY - peak * plot.getHeight();
|
||||
g.setColour (lf.getPeak());
|
||||
g.fillRect (x, peakY - 2.0f, bandWidth, 3.0f);
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
Analyser& analyser;
|
||||
juce::AudioProcessorValueTreeState& params;
|
||||
BeamgridLookAndFeel& lf;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyserComponent)
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue