FastLED 3.10.8
Loading...
Searching...
No Matches
musical_beat_detector.cpp.hpp
Go to the documentation of this file.
2#include "fl/stl/int.h"
3#include "fl/math/math.h"
4#include "fl/stl/vector.h"
5#include "fl/stl/noexcept.h"
6
7namespace fl {
8namespace audio {
9namespace detector {
10
12
16
18
20 mConfig = config;
21 mIBIHistory.clear();
22 reset();
23}
24
25void MusicalBeat::processSample(bool onsetDetected, float onsetStrength) FL_NO_EXCEPT {
26 mBeatDetected = false;
29
30 if (!onsetDetected) {
31 return;
32 }
33
34 mStats.totalOnsets++;
35
36 // Validate if this onset is a true musical beat
37 if (validateBeat(onsetStrength)) {
38 mBeatDetected = true;
39 mStats.validatedBeats++;
40
41 // Calculate inter-beat interval (IBI)
42 u32 ibiFrames = mCurrentFrame - mLastBeatFrame;
44
45 // Convert IBI from frames to seconds
46 float ibiSeconds = static_cast<float>(ibiFrames * mConfig.samplesPerFrame) /
47 static_cast<float>(mConfig.sampleRate);
48
49 // Validate IBI is within BPM range
50 if (isValidIBI(ibiSeconds)) {
51 // Add to history
52 if (mIBIHistory.size() >= mConfig.maxIBIHistory) {
53 // Remove oldest IBI
54 mIBIHistory.pop_front();
55 }
56 mIBIHistory.push_back(ibiFrames);
57
58 // Calculate beat confidence
60
61 // Update BPM estimate
63
64 // Update stats
65 mStats.currentBPM = mCurrentBPM;
66 mStats.averageIBI = getAverageIBI();
67 mStats.ibiCount = static_cast<u32>(mIBIHistory.size());
68 } else {
69 // IBI out of valid range - reject beat
70 mBeatDetected = false;
71 mStats.rejectedOnsets++;
72 }
73 } else {
74 mStats.rejectedOnsets++;
75 }
76}
77
79 return mBeatDetected && (mLastBeatConfidence >= mConfig.minBeatConfidence);
80}
81
83 return mCurrentBPM;
84}
85
89
91 if (mIBIHistory.empty()) {
92 return 0.0f;
93 }
94
95 u32 sum = 0;
96 for (size i = 0; i < mIBIHistory.size(); ++i) {
97 sum += mIBIHistory[i];
98 }
99
100 float avgFrames = static_cast<float>(sum) / static_cast<float>(mIBIHistory.size());
101 return (avgFrames * static_cast<float>(mConfig.samplesPerFrame)) /
102 static_cast<float>(mConfig.sampleRate);
103}
104
106 mBeatDetected = false;
107 mLastBeatConfidence = 0.0f;
108 mCurrentBPM = 120.0f; // Default to common tempo
109 mLastBeatFrame = 0;
110 mCurrentFrame = 0;
111 mIBIHistory.clear();
112 mAverageIBI = 0.0f;
113
114 mStats.totalOnsets = 0;
115 mStats.validatedBeats = 0;
116 mStats.rejectedOnsets = 0;
117 mStats.currentBPM = mCurrentBPM;
118 mStats.averageIBI = 0.0f;
119 mStats.ibiCount = 0;
120}
121
122bool MusicalBeat::validateBeat(float onsetStrength) FL_NO_EXCEPT {
123 // First beat always validates (no history to compare)
124 if (mLastBeatFrame == 0) {
125 return true;
126 }
127
128 // Calculate expected IBI based on current BPM
129 float expectedIBI = 60.0f / mCurrentBPM; // seconds per beat
130 float expectedFrames = (expectedIBI * static_cast<float>(mConfig.sampleRate)) /
131 static_cast<float>(mConfig.samplesPerFrame);
132
133 // Calculate actual IBI since last beat
134 float actualFrames = static_cast<float>(mCurrentFrame - mLastBeatFrame);
135
136 // Allow ±25% deviation from expected tempo
137 float tolerance = 0.25f;
138 float minExpected = expectedFrames * (1.0f - tolerance);
139 float maxExpected = expectedFrames * (1.0f + tolerance);
140
141 // If we have no IBI history yet, accept any beat within valid BPM range
142 if (mIBIHistory.empty()) {
143 float actualIBI = (actualFrames * static_cast<float>(mConfig.samplesPerFrame)) /
144 static_cast<float>(mConfig.sampleRate);
145 return isValidIBI(actualIBI);
146 }
147
148 // Validate timing matches expected tempo
149 return (actualFrames >= minExpected) && (actualFrames <= maxExpected);
150}
151
153 // Confidence based on temporal consistency
154 if (mIBIHistory.size() < 2) {
155 // Not enough history - return moderate confidence
156 return 0.6f;
157 }
158
159 // Calculate IBI standard deviation
160 float stdDev = calculateIBIStdDev();
161
162 // Average IBI in seconds
163 float avgIBI = getAverageIBI();
164
165 // Coefficient of variation (normalized std dev)
166 float cv = (avgIBI > 0.0f) ? (stdDev / avgIBI) : 1.0f;
167
168 // Confidence inversely proportional to variability
169 // cv = 0.0 (perfect consistency) → confidence = 1.0
170 // cv = 0.2 (20% variation) → confidence ≈ 0.5
171 // cv ≥ 0.5 (50%+ variation) → confidence → 0.0
172 float confidence = fl::max(0.0f, 1.0f - (cv * 2.0f));
173
174 // Boost confidence if current IBI matches average closely
175 float ibiDiff = fl::abs(currentIBI - avgIBI);
176 float ibiError = (avgIBI > 0.0f) ? (ibiDiff / avgIBI) : 1.0f;
177 float ibiBonus = fl::max(0.0f, 1.0f - (ibiError * 4.0f));
178
179 // Combined confidence (weighted average)
180 return (confidence * 0.7f) + (ibiBonus * 0.3f);
181}
182
184 if (mIBIHistory.empty()) {
185 return;
186 }
187
188 // Calculate BPM from average IBI
189 float avgIBI = getAverageIBI();
190 if (avgIBI <= 0.0f) {
191 return;
192 }
193
194 float instantaneousBPM = 60.0f / avgIBI;
195
196 // Clamp to valid range
197 instantaneousBPM = fl::max(mConfig.minBPM, fl::min(mConfig.maxBPM, instantaneousBPM));
198
199 // Smooth BPM estimate (exponential moving average)
200 float alpha = mConfig.bpmSmoothingAlpha;
201 mCurrentBPM = (alpha * mCurrentBPM) + ((1.0f - alpha) * instantaneousBPM);
202
203 // Ensure BPM stays in valid range after smoothing
205}
206
208 if (ibi <= 0.0f) {
209 return false;
210 }
211
212 // Convert IBI to BPM
213 float bpm = 60.0f / ibi;
214
215 // Check if BPM is within valid range
216 return (bpm >= mConfig.minBPM) && (bpm <= mConfig.maxBPM);
217}
218
220 if (mIBIHistory.size() < 2) {
221 return 0.0f;
222 }
223
224 // Calculate mean IBI in frames
225 u32 sum = 0;
226 for (size i = 0; i < mIBIHistory.size(); ++i) {
227 sum += mIBIHistory[i];
228 }
229 float mean = static_cast<float>(sum) / static_cast<float>(mIBIHistory.size());
230
231 // Calculate variance
232 float variance = 0.0f;
233 for (size i = 0; i < mIBIHistory.size(); ++i) {
234 float diff = static_cast<float>(mIBIHistory[i]) - mean;
235 variance += diff * diff;
236 }
237 variance /= static_cast<float>(mIBIHistory.size());
238
239 // Convert std dev from frames to seconds
240 float stdDevFrames = fl::sqrt(variance);
241 return (stdDevFrames * static_cast<float>(mConfig.samplesPerFrame)) /
242 static_cast<float>(mConfig.sampleRate);
243}
244
245} // namespace detector
246} // namespace audio
247} // namespace fl
void bpm()
deque< u32 > mIBIHistory
Inter-beat interval history (in frames)
float mAverageIBI
Average inter-beat interval (in frames)
bool validateBeat(float onsetStrength) FL_NO_EXCEPT
Validate if an onset is a true musical beat.
void configure(const MusicalBeatDetectorConfig &config) FL_NO_EXCEPT
Configure the beat detector.
float getBeatConfidence() const FL_NO_EXCEPT
Get beat confidence for the last detected beat.
bool mBeatDetected
Last beat was detected.
float mCurrentBPM
Current BPM estimate (smoothed)
void processSample(bool onsetDetected, float onsetStrength) FL_NO_EXCEPT
Process one audio frame.
void reset() FL_NO_EXCEPT
Reset internal state (clear history, reset BPM)
void updateBPMEstimate() FL_NO_EXCEPT
Update BPM estimate from inter-beat intervals.
float getBPM() const FL_NO_EXCEPT
Get current BPM estimate.
float mLastBeatConfidence
Last beat confidence score.
float calculateIBIStdDev() const FL_NO_EXCEPT
Calculate standard deviation of IBI history.
u32 mCurrentFrame
Current frame counter.
bool isBeat() const FL_NO_EXCEPT
Check if a musical beat was detected in the last frame.
u32 mLastBeatFrame
Last beat timestamp (in frames)
float getAverageIBI() const FL_NO_EXCEPT
Get inter-beat interval (IBI) statistics.
bool isValidIBI(float ibi) const FL_NO_EXCEPT
Check if IBI is within valid BPM range.
float calculateBeatConfidence(float currentIBI) FL_NO_EXCEPT
Calculate beat confidence based on rhythmic consistency.
Configuration for musical beat detection.
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
constexpr enable_if< is_fixed_point< T >::value, T >::type sqrt(T x) FL_NO_EXCEPT
constexpr enable_if< is_fixed_point< T >::value, T >::type abs(T x) FL_NO_EXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179