FastLED 3.10.8
Loading...
Searching...
No Matches
drop.cpp.hpp
Go to the documentation of this file.
1// DropDetector.cpp - Implementation of EDM drop detection
2
5#include "fl/math/math.h"
6#include "fl/log/log.h"
7#include "fl/stl/noexcept.h"
8
9namespace fl {
10namespace audio {
11namespace detector {
12
15 , mPrevBassEnergy(0.0f)
16 , mPrevMidEnergy(0.0f)
17 , mPrevTrebleEnergy(0.0f)
18 , mEnergyBaseline(0.0f)
19 , mBassBaseline(0.0f)
20 , mImpactThreshold(0.75f) // 75% impact to trigger
21 , mMinTimeBetweenDrops(2000) // 2 seconds between drops
22 , mBassThreshold(0.6f) // 60% bass energy required
23 , mEnergyFluxThreshold(0.5f) // 50% energy increase required
24{
25 mLastDrop.timestamp = 0; // Initialize to allow immediate first drop
26}
27
29
31 if (!context) {
32 // Null context - nothing to process
33 return;
34 }
35
36 mRetainedFFT = context->getFFT(32);
37 const fft::Bins& fft = *mRetainedFFT;
38 float rms = context->getRMS();
39 u32 timestamp = context->getTimestamp();
40
41 // Calculate frequency band energies
42 float bassEnergy = getBassEnergy(fft);
43 float midEnergy = getMidEnergy(fft);
44 float trebleEnergy = getTrebleEnergy(fft);
45
46 // Calculate flux values (rate of change)
47 float energyFlux = calculateEnergyFlux(rms);
48 float bassFlux = calculateBassFlux(bassEnergy);
49
50 // Calculate spectral novelty (how much spectrum changed)
51 float spectralNovelty = calculateSpectralNovelty(bassEnergy, midEnergy, trebleEnergy);
52
53 // Calculate drop impact
54 float impact = calculateDropImpact(energyFlux, bassFlux, spectralNovelty, rms);
55
56 // Check if we should trigger a drop
58 if (shouldTriggerDrop(impact, timestamp)) {
59 // Create drop event
60 Drop dropEvent;
61 dropEvent.impact = impact;
62 dropEvent.bassEnergy = bassEnergy;
63 dropEvent.energyIncrease = energyFlux;
64 dropEvent.timestamp = timestamp;
65
66 mLastDrop = dropEvent;
68
69 FL_DBG("DropDetector: Drop detected! Impact=" << impact << ", Bass=" << bassEnergy << ", Energy flux=" << energyFlux);
70 }
71
72 // Update baselines (exponential moving average)
73 updateBaselines(rms, bassEnergy);
74
75 // Store current values for next frame
76 mPrevRMS = rms;
77 mPrevBassEnergy = bassEnergy;
78 mPrevMidEnergy = midEnergy;
79 mPrevTrebleEnergy = trebleEnergy;
80}
81
84 if (onDrop) {
85 onDrop();
86 }
87 if (onDropEvent) {
89 }
90 if (onDropImpact) {
91 onDropImpact(mLastDrop.impact);
92 }
94 }
95}
96
98 mLastDrop = Drop();
99 mPrevRMS = 0.0f;
100 mPrevBassEnergy = 0.0f;
101 mPrevMidEnergy = 0.0f;
102 mPrevTrebleEnergy = 0.0f;
103 mEnergyBaseline = 0.0f;
104 mBassBaseline = 0.0f;
105}
106
108 // Bass = first 25% of bins (sub-bass and bass)
109 int endBin = fl::max(1, static_cast<int>(fft.raw().size() / 4));
110 float energy = 0.0f;
111
112 for (int i = 0; i < endBin; i++) {
113 energy += fft.raw()[i];
114 }
115
116 return energy / static_cast<float>(endBin);
117}
118
120 // Mid = middle 50% of bins (midrange frequencies)
121 int startBin = static_cast<int>(fft.raw().size() / 4);
122 int endBin = static_cast<int>(fft.raw().size() * 3 / 4);
123 float energy = 0.0f;
124 int count = 0;
125
126 for (int i = startBin; i < endBin; i++) {
127 energy += fft.raw()[i];
128 count++;
129 }
130
131 return (count > 0) ? energy / static_cast<float>(count) : 0.0f;
132}
133
135 // Treble = top 25% of bins (high frequencies)
136 int startBin = static_cast<int>(fft.raw().size() * 3 / 4);
137 float energy = 0.0f;
138 int count = 0;
139
140 for (fl::size i = startBin; i < fft.raw().size(); i++) {
141 energy += fft.raw()[i];
142 count++;
143 }
144
145 return (count > 0) ? energy / static_cast<float>(count) : 0.0f;
146}
147
148float DropDetector::calculateSpectralNovelty(float bass, float mid, float treble) const FL_NO_EXCEPT {
149 // Calculate how much the spectrum changed from previous frame
150 float bassChange = fl::abs(bass - mPrevBassEnergy);
151 float midChange = fl::abs(mid - mPrevMidEnergy);
152 float trebleChange = fl::abs(treble - mPrevTrebleEnergy);
153
154 // Weight bass changes more heavily (drops often emphasize bass)
155 float novelty = bassChange * 0.5f + midChange * 0.3f + trebleChange * 0.2f;
156
157 // Normalize (typical max change is ~2.0)
158 return fl::min(1.0f, novelty / 2.0f);
159}
160
161float DropDetector::calculateEnergyFlux(float currentRMS) const FL_NO_EXCEPT {
162 // Calculate how much energy increased from baseline
163 if (mEnergyBaseline < 1e-6f) {
164 return 0.0f; // No baseline yet
165 }
166
167 float increase = currentRMS - mEnergyBaseline;
168 float ratio = increase / mEnergyBaseline;
169
170 // Normalize to [0, 1] (2x increase = 1.0)
171 return fl::max(0.0f, fl::min(1.0f, ratio / 2.0f));
172}
173
174float DropDetector::calculateBassFlux(float currentBass) const FL_NO_EXCEPT {
175 // Calculate how much bass increased from baseline
176 if (mBassBaseline < 1e-6f) {
177 return 0.0f; // No baseline yet
178 }
179
180 float increase = currentBass - mBassBaseline;
181 float ratio = increase / mBassBaseline;
182
183 // Normalize to [0, 1] (2x increase = 1.0)
184 return fl::max(0.0f, fl::min(1.0f, ratio / 2.0f));
185}
186
187float DropDetector::calculateDropImpact(float energyFlux, float bassFlux, float spectralNovelty, float rms) const FL_NO_EXCEPT {
188 // Drop impact is a weighted combination of:
189 // - Energy flux (40%) - sudden energy burst
190 // - Bass flux (35%) - bass impact
191 // - Spectral novelty (15%) - dramatic change
192 // - Overall energy (10%) - absolute energy level
193
194 float normalizedRMS = fl::min(1.0f, rms);
195
196 float impact = energyFlux * 0.4f +
197 bassFlux * 0.35f +
198 spectralNovelty * 0.15f +
199 normalizedRMS * 0.1f;
200
201 return fl::max(0.0f, fl::min(1.0f, impact));
202}
203
204bool DropDetector::shouldTriggerDrop(float impact, u32 timestamp) const FL_NO_EXCEPT {
205 // Don't trigger if:
206 // 1. Impact below threshold
207 if (impact < mImpactThreshold) {
208 return false;
209 }
210
211 // 2. Too soon after last drop (cooldown)
212 u32 timeSinceLast = timestamp - mLastDrop.timestamp;
213 if (timeSinceLast < mMinTimeBetweenDrops) {
214 return false;
215 }
216
217 // 3. Not enough energy flux (not a real burst)
218 float energyFlux = calculateEnergyFlux(mPrevRMS);
219 if (energyFlux < mEnergyFluxThreshold) {
220 return false;
221 }
222
223 // 4. Not enough bass (drops should have strong bass)
224 float bassFlux = calculateBassFlux(mPrevBassEnergy);
225 if (bassFlux < mBassThreshold * 0.5f) { // At least 50% of bass threshold
226 return false;
227 }
228
229 return true;
230}
231
233 // Exponential moving average with alpha = 0.9 (slow adaptation)
234 // This creates a "rolling baseline" of recent energy levels
235 const float alpha = 0.9f;
236
237 if (mEnergyBaseline < 1e-6f) {
238 // Initialize baseline
240 mBassBaseline = bass;
241 } else {
242 // Update baseline
243 mEnergyBaseline = alpha * mEnergyBaseline + (1.0f - alpha) * rms;
244 mBassBaseline = alpha * mBassBaseline + (1.0f - alpha) * bass;
245 }
246}
247
248} // namespace detector
249} // namespace audio
250} // namespace fl
float rms(fl::span< const int16_t > data)
Definition simple.h:104
function_list< void(const Drop &)> onDropEvent
Definition drop.h:66
void fireCallbacks() FL_NO_EXCEPT override
Definition drop.cpp.hpp:82
function_list< void()> onDrop
Definition drop.h:65
~DropDetector() FL_NO_EXCEPT override
float getBassEnergy(const fft::Bins &fft) const FL_NO_EXCEPT
Definition drop.cpp.hpp:107
void update(shared_ptr< Context > context) FL_NO_EXCEPT override
Definition drop.cpp.hpp:30
float calculateSpectralNovelty(float bass, float mid, float treble) const FL_NO_EXCEPT
Definition drop.cpp.hpp:148
void updateBaselines(float rms, float bass) FL_NO_EXCEPT
Definition drop.cpp.hpp:232
shared_ptr< const fft::Bins > mRetainedFFT
Definition drop.h:102
float getMidEnergy(const fft::Bins &fft) const FL_NO_EXCEPT
Definition drop.cpp.hpp:119
float calculateBassFlux(float currentBass) const FL_NO_EXCEPT
Definition drop.cpp.hpp:174
function_list< void(float impact)> onDropImpact
Definition drop.h:67
float getTrebleEnergy(const fft::Bins &fft) const FL_NO_EXCEPT
Definition drop.cpp.hpp:134
float calculateEnergyFlux(float currentRMS) const FL_NO_EXCEPT
Definition drop.cpp.hpp:161
void reset() FL_NO_EXCEPT override
Definition drop.cpp.hpp:97
float calculateDropImpact(float energyFlux, float bassFlux, float spectralNovelty, float rms) const FL_NO_EXCEPT
Definition drop.cpp.hpp:187
bool shouldTriggerDrop(float impact, u32 timestamp) const FL_NO_EXCEPT
Definition drop.cpp.hpp:204
#define FL_DBG
Definition log.h:553
Centralized logging categories for FastLED hardware interfaces and subsystems.
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 abs(T x) FL_NO_EXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
Compile-time rational arithmetic.
Definition ratio.h:21