beamgrid/Source/Analyser.h

74 lines
2.9 KiB
C
Raw Normal View History

#pragma once
#include "JuceHeader.h"
// Beamgrid analyser: log-spaced spectrum bands with peak-hold markers.
// Each band tracks a current level and a peak that holds for a definable
// "grace" time before falling off at a definable rate.
class Analyser
{
public:
static constexpr int maxBands = 256;
enum { fftOrder = 11, fftSize = 1 << fftOrder, fftBins = fftSize / 2 };
Analyser();
void prepare (double sampleRate, int blockSize);
void push (const float* channelData, int numSamples);
void update (double dt);
int getNumBands() const noexcept { return numBands; }
float getLevel (int band) const noexcept { return band < numBands ? juce::jmin (1.0f, levels[band]) : 0.0f; }
float getPeak (int band) const noexcept { return band < numBands ? juce::jmin (1.0f, peaks[band]) : 0.0f; }
// Helpers for drawing axis legends.
double getMinFreq() const noexcept { return minFreq; }
double getMaxFreq() const noexcept { return maxFreq; }
float freqToFraction (double freq) const noexcept;
float dbToFraction (double db) const noexcept;
void setGraceSeconds (double seconds) noexcept { grace = seconds; }
void setFalloffRate (double rate) noexcept { falloffRate = rate; }
void setBarReleaseTau (double seconds) noexcept { barReleaseTau = seconds; }
void setNumBands (int n) noexcept;
double getGraceSeconds() const noexcept { return grace; }
double getFalloffRate() const noexcept { return falloffRate; }
private:
void pushSample (float sample);
void computeFFT();
void computeBandEdges();
juce::dsp::FFT fft { fftOrder };
juce::dsp::WindowingFunction<float> window { fftSize, juce::dsp::WindowingFunction<float>::hann, true };
// Sliding time-domain window so the FFT is recomputed every `hopSize`
// samples (overlapped) instead of only once per full window. This keeps
// the visualization temporally smooth.
static constexpr int hopSize = 256;
std::array<float, fftSize> ring {};
std::array<float, 2 * fftSize> fftData {};
int writePos = 0;
int samplesSinceFFT = 0;
int totalSamples = 0;
int numBands = 64;
std::array<float, maxBands> levels {};
std::array<float, maxBands> targets {};
std::array<float, maxBands> peaks {};
std::array<float, maxBands> peakTimers {};
// First/last FFT bin index covered by each band (log spaced).
std::array<int, maxBands + 1> bandBinEdges {};
double minFreq = 20.0;
double maxFreq = 22050.0;
double sampleRate = 44100.0;
double grace = 0.8; // seconds the peak is held before falling
double falloffRate = 0.8; // normalized units per second while falling
double barReleaseTau = 0.2; // seconds for the live bar to fall (release)
double minDb = -48.0; // dBFS-equivalent floor (bands below this -> 0)
double maxDb = -6.0; // dBFS-equivalent ceiling (bands at/above -> 1)
};