FastLED 3.10.8
Loading...
Searching...
No Matches
frequency_bands.cpp.hpp
Go to the documentation of this file.
3#include "fl/audio/fft/fft.h"
4#include "fl/math/math.h"
5#include "fl/stl/noexcept.h"
6
7namespace fl {
8namespace audio {
9namespace detector {
10
11// Diagnostic FFT counter. Deliberately a plain constant-initialized static,
12// NOT a fl::Singleton -- see the rationale on `sVibeFFTCount` in vibe.cpp.hpp
13// and FastLED#3486.
14// FL_LINT_ALLOW_GLOBAL(constant-initialized POD scalar -- already elidable under -fdata-sections; Singleton wrapping is a net size regression and loses constant-initialization. See FastLED#3486.)
18
19namespace {
20// Number of fft::FFT bins — higher count gives better frequency resolution
21// and cleaner band separation. With 64 bins, the CQ kernel provides
22// good frequency isolation for all three bands (bass, mid, treble).
23constexpr int kNumBands = 64;
24
25// fft::FFT frequency range covering bass through treble.
26// Range [100, 10000] covers the bass (20-250), mid (250-4000), and treble
27// (4000-20000) ranges with good CQ kernel resolution. The ratio of 100x
28// keeps the highest-frequency kernel window large enough for the 512-sample
29// fft::FFT (N_window = 512 / (10000/100) = 5 samples minimum).
30constexpr float kFFTMinFreq = 100.0f;
31constexpr float kFFTMaxFreq = 10000.0f;
32
33} // namespace
34
36 FL_NO_EXCEPT : mBass(0.0f)
37 , mMid(0.0f)
38 , mTreble(0.0f)
39 , mBassMin(20.0f)
40 , mBassMax(250.0f)
41 , mMidMin(250.0f)
42 , mMidMax(4000.0f)
43 , mTrebleMin(4000.0f)
44 , mTrebleMax(20000.0f)
45{}
46
48
50 // Use sample rate from context if available
51 mSampleRate = context->getSampleRate();
52
53 // Use shared master fft::FFT via context (downsampled to our config)
54 mRetainedFFT = context->getFFT(kNumBands, kFFTMinFreq, kFFTMaxFreq);
55 const fft::Bins& fftBins = *mRetainedFFT;
56 sFrequencyBandsFFTCount++; // Diagnostic counter (no private fft::FFT anymore)
57
58 span<const i16> pcm = context->getPCM();
59
60 // Calculate energy for each band using fractional bin overlap
61 float bassEnergy = calculateBandEnergy(fftBins, mBassMin, mBassMax, kFFTMinFreq, kFFTMaxFreq);
62 float midEnergy = calculateBandEnergy(fftBins, mMidMin, mMidMax, kFFTMinFreq, kFFTMaxFreq);
63 float trebleEnergy = calculateBandEnergy(fftBins, mTrebleMin, mTrebleMax, kFFTMinFreq, kFFTMaxFreq);
64
65 // Compute dt from actual audio buffer duration: pcmSize / sampleRate
66 const float dt = computeAudioDt(pcm.size(), mSampleRate);
67 mBass = mBassSmoother.update(bassEnergy, dt);
68 mMid = mMidSmoother.update(midEnergy, dt);
69 mTreble = mTrebleSmoother.update(trebleEnergy, dt);
70
71 // Per-band normalization — mirrors EnergyAnalyzer pattern
72 auto normalizeBand = [](float val, AttackDecayFilter<float>& filter,
73 float frameDt) -> float {
74 float runningMax = filter.update(val, frameDt);
75 if (runningMax < 0.001f) runningMax = 0.001f;
76 return fl::min(1.0f, val / runningMax);
77 };
78 mBassNorm = normalizeBand(mBass, mBassMaxFilter, dt);
79 mMidNorm = normalizeBand(mMid, mMidMaxFilter, dt);
80 mTrebleNorm = normalizeBand(mTreble, mTrebleMaxFilter, dt);
81}
82
97
99 mBass = 0.0f;
100 mMid = 0.0f;
101 mTreble = 0.0f;
102 mBassSmoother.reset();
103 mMidSmoother.reset();
104 mTrebleSmoother.reset();
105 mBassMaxFilter.reset(0.0f);
106 mMidMaxFilter.reset(0.0f);
107 mTrebleMaxFilter.reset(0.0f);
108 mBassNorm = 0.0f;
109 mMidNorm = 0.0f;
110 mTrebleNorm = 0.0f;
111}
112
113float FrequencyBands::calculateBandEnergy(const fft::Bins& fft, float minFreq, float maxFreq,
114 float fftMinFreq, float fftMaxFreq) FL_NO_EXCEPT {
115 const int numBins = static_cast<int>(fft.raw().size());
116 if (numBins <= 1) {
117 return 0.0f;
118 }
119
120 float totalEnergy = 0.0f;
121 float totalWeight = 0.0f;
122
123 for (int i = 0; i < numBins; i++) {
124 // Compute the frequency range for this CQ bin using log-spaced
125 // boundaries that match the CQ kernel's actual frequency layout.
126 float binLow;
127 float binHigh;
128 if (i == 0) {
129 binLow = fftMinFreq;
130 } else {
131 binLow = fft.binBoundary(i - 1);
132 }
133 if (i == numBins - 1) {
134 binHigh = fftMaxFreq;
135 } else {
136 binHigh = fft.binBoundary(i);
137 }
138
139 // Calculate fractional overlap between this bin and the target band
140 float overlapMin = fl::max(binLow, minFreq);
141 float overlapMax = fl::min(binHigh, maxFreq);
142
143 if (overlapMax <= overlapMin) {
144 continue; // No overlap
145 }
146
147 float binWidth = binHigh - binLow;
148 float overlapFraction = (overlapMax - overlapMin) / binWidth;
149
150 totalEnergy += fft.raw()[i] * overlapFraction;
151 totalWeight += overlapFraction;
152 }
153
154 // Normalize by total fractional weight to make bands comparable
155 return (totalWeight > 0.0f) ? totalEnergy / totalWeight : 0.0f;
156}
157
158} // namespace detector
159} // namespace audio
160} // namespace fl
~FrequencyBands() FL_NO_EXCEPT override
float calculateBandEnergy(const fft::Bins &fft, float minFreq, float maxFreq, float fftMinFreq, float fftMaxFreq) FL_NO_EXCEPT
function_list< void(float level)> onBassLevel
static void resetPrivateFFTCount() FL_NO_EXCEPT
void fireCallbacks() FL_NO_EXCEPT override
shared_ptr< const fft::Bins > mRetainedFFT
void update(shared_ptr< Context > context) FL_NO_EXCEPT override
AttackDecayFilter< float > mBassMaxFilter
AttackDecayFilter< float > mTrebleMaxFilter
static int getPrivateFFTCount() FL_NO_EXCEPT
function_list< void(float level)> onTrebleLevel
function_list< void(float level)> onMidLevel
ExponentialSmoother< float > mBassSmoother
ExponentialSmoother< float > mTrebleSmoother
function_list< void(float bass, float mid, float treble)> onLevelsUpdate
AttackDecayFilter< float > mMidMaxFilter
ExponentialSmoother< float > mMidSmoother
constexpr fl::size size() const FL_NO_EXCEPT
Definition span.h:483
float computeAudioDt(fl::size pcmSize, int sampleRate) FL_NO_EXCEPT
Compute the time delta (in seconds) for an audio buffer.
constexpr common_type_t< T, U > max(T a, U b) FL_NO_EXCEPT
Definition math.h:75
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NO_EXCEPT
Definition math.h:71
Base definition for an LED controller.
Definition crgb.hpp:179