FastLED 3.9.15
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
11MusicalBeat::MusicalBeat() = default;
12
16
18
20 mConfig = config;
21 mIBIHistory.clear();
22 reset();
23}
24
25void MusicalBeat::processSample(bool onsetDetected, float onsetStrength) {
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
78bool MusicalBeat::isBeat() const {
79 return mBeatDetected && (mLastBeatConfidence >= mConfig.minBeatConfidence);
80}
81
82float MusicalBeat::getBPM() const {
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) {
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
207bool MusicalBeat::isValidIBI(float ibi) const {
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)
void processSample(bool onsetDetected, float onsetStrength)
Process one audio frame.
bool isValidIBI(float ibi) const
Check if IBI is within valid BPM range.
float calculateBeatConfidence(float currentIBI)
Calculate beat confidence based on rhythmic consistency.
float getBeatConfidence() const
Get beat confidence for the last detected beat.
bool mBeatDetected
Last beat was detected.
float mCurrentBPM
Current BPM estimate (smoothed)
bool isBeat() const
Check if a musical beat was detected in the last frame.
float mLastBeatConfidence
Last beat confidence score.
void reset()
Reset internal state (clear history, reset BPM)
u32 mCurrentFrame
Current frame counter.
void configure(const MusicalBeatDetectorConfig &config)
Configure the beat detector.
float getAverageIBI() const
Get inter-beat interval (IBI) statistics.
u32 mLastBeatFrame
Last beat timestamp (in frames)
bool validateBeat(float onsetStrength)
Validate if an onset is a true musical beat.
float getBPM() const
Get current BPM estimate.
void updateBPMEstimate()
Update BPM estimate from inter-beat intervals.
float calculateIBIStdDev() const
Calculate standard deviation of IBI history.
Configuration for musical beat detection.
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NOEXCEPT
Definition math.h:71
constexpr common_type_t< T, U > max(T a, U b) FL_NOEXCEPT
Definition math.h:75
constexpr enable_if< is_fixed_point< T >::value, T >::type sqrt(T x) FL_NOEXCEPT
constexpr enable_if< is_fixed_point< T >::value, T >::type abs(T x) FL_NOEXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT