#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 window { fftSize, juce::dsp::WindowingFunction::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 ring {}; std::array fftData {}; int writePos = 0; int samplesSinceFFT = 0; int totalSamples = 0; int numBands = 64; std::array levels {}; std::array targets {}; std::array peaks {}; std::array peakTimers {}; // First/last FFT bin index covered by each band (log spaced). std::array 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 = 0.0; // dBFS-equivalent ceiling (bands at/above -> 1) };