FastLED 3.9.15
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
14 : mPrevRMS(0.0f)
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
70 << ", Bass=" << bassEnergy
71 << ", Energy flux=" << energyFlux);
72 }
73
74 // Update baselines (exponential moving average)
75 updateBaselines(rms, bassEnergy);
76
77 // Store current values for next frame
78 mPrevRMS = rms;
79 mPrevBassEnergy = bassEnergy;
80 mPrevMidEnergy = midEnergy;
81 mPrevTrebleEnergy = trebleEnergy;
82}
83
86 if (onDrop) {
87 onDrop();
88 }
89 if (onDropEvent) {
91 }
92 if (onDropImpact) {
93 onDropImpact(mLastDrop.impact);
94 }
96 }
97}
98
100 mLastDrop = Drop();
101 mPrevRMS = 0.0f;
102 mPrevBassEnergy = 0.0f;
103 mPrevMidEnergy = 0.0f;
104 mPrevTrebleEnergy = 0.0f;
105 mEnergyBaseline = 0.0f;
106 mBassBaseline = 0.0f;
107}
108
110 // Bass = first 25% of bins (sub-bass and bass)
111 int endBin = fl::max(1, static_cast<int>(fft.raw().size() / 4));
112 float energy = 0.0f;
113
114 for (int i = 0; i < endBin; i++) {
115 energy += fft.raw()[i];
116 }
117
118 return energy / static_cast<float>(endBin);
119}
120
122 // Mid = middle 50% of bins (midrange frequencies)
123 int startBin = static_cast<int>(fft.raw().size() / 4);
124 int endBin = static_cast<int>(fft.raw().size() * 3 / 4);
125 float energy = 0.0f;
126 int count = 0;
127
128 for (int i = startBin; i < endBin; i++) {
129 energy += fft.raw()[i];
130 count++;
131 }
132
133 return (count > 0) ? energy / static_cast<float>(count) : 0.0f;
134}
135
137 // Treble = top 25% of bins (high frequencies)
138 int startBin = static_cast<int>(fft.raw().size() * 3 / 4);
139 float energy = 0.0f;
140 int count = 0;
141
142 for (fl::size i = startBin; i < fft.raw().size(); i++) {
143 energy += fft.raw()[i];
144 count++;
145 }
146
147 return (count > 0) ? energy / static_cast<float>(count) : 0.0f;
148}
149
150float DropDetector::calculateSpectralNovelty(float bass, float mid, float treble) const {
151 // Calculate how much the spectrum changed from previous frame
152 float bassChange = fl::abs(bass - mPrevBassEnergy);
153 float midChange = fl::abs(mid - mPrevMidEnergy);
154 float trebleChange = fl::abs(treble - mPrevTrebleEnergy);
155
156 // Weight bass changes more heavily (drops often emphasize bass)
157 float novelty = bassChange * 0.5f + midChange * 0.3f + trebleChange * 0.2f;
158
159 // Normalize (typical max change is ~2.0)
160 return fl::min(1.0f, novelty / 2.0f);
161}
162
163float DropDetector::calculateEnergyFlux(float currentRMS) const {
164 // Calculate how much energy increased from baseline
165 if (mEnergyBaseline < 1e-6f) {
166 return 0.0f; // No baseline yet
167 }
168
169 float increase = currentRMS - mEnergyBaseline;
170 float ratio = increase / mEnergyBaseline;
171
172 // Normalize to [0, 1] (2x increase = 1.0)
173 return fl::max(0.0f, fl::min(1.0f, ratio / 2.0f));
174}
175
176float DropDetector::calculateBassFlux(float currentBass) const {
177 // Calculate how much bass increased from baseline
178 if (mBassBaseline < 1e-6f) {
179 return 0.0f; // No baseline yet
180 }
181
182 float increase = currentBass - mBassBaseline;
183 float ratio = increase / mBassBaseline;
184
185 // Normalize to [0, 1] (2x increase = 1.0)
186 return fl::max(0.0f, fl::min(1.0f, ratio / 2.0f));
187}
188
189float DropDetector::calculateDropImpact(float energyFlux, float bassFlux, float spectralNovelty, float rms) const {
190 // Drop impact is a weighted combination of:
191 // - Energy flux (40%) - sudden energy burst
192 // - Bass flux (35%) - bass impact
193 // - Spectral novelty (15%) - dramatic change
194 // - Overall energy (10%) - absolute energy level
195
196 float normalizedRMS = fl::min(1.0f, rms);
197
198 float impact = energyFlux * 0.4f +
199 bassFlux * 0.35f +
200 spectralNovelty * 0.15f +
201 normalizedRMS * 0.1f;
202
203 return fl::max(0.0f, fl::min(1.0f, impact));
204}
205
206bool DropDetector::shouldTriggerDrop(float impact, u32 timestamp) const {
207 // Don't trigger if:
208 // 1. Impact below threshold
209 if (impact < mImpactThreshold) {
210 return false;
211 }
212
213 // 2. Too soon after last drop (cooldown)
214 u32 timeSinceLast = timestamp - mLastDrop.timestamp;
215 if (timeSinceLast < mMinTimeBetweenDrops) {
216 return false;
217 }
218
219 // 3. Not enough energy flux (not a real burst)
220 float energyFlux = calculateEnergyFlux(mPrevRMS);
221 if (energyFlux < mEnergyFluxThreshold) {
222 return false;
223 }
224
225 // 4. Not enough bass (drops should have strong bass)
226 float bassFlux = calculateBassFlux(mPrevBassEnergy);
227 if (bassFlux < mBassThreshold * 0.5f) { // At least 50% of bass threshold
228 return false;
229 }
230
231 return true;
232}
233
234void DropDetector::updateBaselines(float rms, float bass) {
235 // Exponential moving average with alpha = 0.9 (slow adaptation)
236 // This creates a "rolling baseline" of recent energy levels
237 const float alpha = 0.9f;
238
239 if (mEnergyBaseline < 1e-6f) {
240 // Initialize baseline
242 mBassBaseline = bass;
243 } else {
244 // Update baseline
245 mEnergyBaseline = alpha * mEnergyBaseline + (1.0f - alpha) * rms;
246 mBassBaseline = alpha * mBassBaseline + (1.0f - alpha) * bass;
247 }
248}
249
250} // namespace detector
251} // namespace audio
252} // namespace fl
float rms(fl::span< const int16_t > data)
Definition simple.h:104
void update(shared_ptr< Context > context) override
Definition drop.cpp.hpp:30
float getTrebleEnergy(const fft::Bins &fft) const
Definition drop.cpp.hpp:136
float calculateEnergyFlux(float currentRMS) const
Definition drop.cpp.hpp:163
function_list< void(const Drop &)> onDropEvent
Definition drop.h:66
bool shouldTriggerDrop(float impact, u32 timestamp) const
Definition drop.cpp.hpp:206
function_list< void()> onDrop
Definition drop.h:65
float calculateBassFlux(float currentBass) const
Definition drop.cpp.hpp:176
~DropDetector() FL_NOEXCEPT override
float getBassEnergy(const fft::Bins &fft) const
Definition drop.cpp.hpp:109
shared_ptr< const fft::Bins > mRetainedFFT
Definition drop.h:102
float calculateDropImpact(float energyFlux, float bassFlux, float spectralNovelty, float rms) const
Definition drop.cpp.hpp:189
function_list< void(float impact)> onDropImpact
Definition drop.h:67
float getMidEnergy(const fft::Bins &fft) const
Definition drop.cpp.hpp:121
void updateBaselines(float rms, float bass)
Definition drop.cpp.hpp:234
float calculateSpectralNovelty(float bass, float mid, float treble) const
Definition drop.cpp.hpp:150
#define FL_DBG
Definition log.h:388
Centralized logging categories for FastLED hardware interfaces and subsystems.
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 abs(T x) FL_NOEXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT
Compile-time rational arithmetic.
Definition ratio.h:21