FastLED 3.9.15
Loading...
Searching...
No Matches
buildup.cpp.hpp
Go to the documentation of this file.
1// BuildupDetector.cpp - Implementation of EDM buildup detection
2
5#include "fl/math/math.h"
6#include "fl/log/log.h"
7#include "fl/log/log.h"
8#include "fl/stl/noexcept.h"
9
10namespace fl {
11namespace audio {
12namespace detector {
13
15 : mBuildupActive(false)
16 , mPeakFired(false)
21 , mPrevEnergy(0.0f)
22 , mPrevTreble(0.0f)
24 , mMinDuration(2000) // 2 seconds minimum
25 , mMaxDuration(16000) // 16 seconds maximum
26 , mIntensityThreshold(0.6f) // 60% intensity to start
27 , mEnergyRiseThreshold(0.3f) // 30% rise rate
28{
29 // Initialize history buffers
30 for (int i = 0; i < 32; i++) {
31 mEnergyHistory[i] = 0.0f;
32 }
33 for (int i = 0; i < 16; i++) {
34 mTrebleHistory[i] = 0.0f;
35 }
36}
37
39
41 if (!context) {
42 FL_WARN("BuildupDetector::update: null context");
43 return;
44 }
45
46 mRetainedFFT = context->getFFT(32);
47 const fft::Bins& fft = *mRetainedFFT;
48 float rms = context->getRMS();
49 float treble = getTrebleEnergy(fft);
50 u32 timestamp = context->getTimestamp();
51
52 // Update history and SG filters
54 updateTrebleHistory(treble);
55 mEnergySG.update(rms);
56 mTrebleSG.update(treble);
57
58 // Calculate trends
59 float energyTrend = calculateEnergyTrend();
60 float trebleTrend = calculateTrebleTrend();
61
62 // Calculate buildup intensity
63 float intensity = calculateBuildupIntensity(energyTrend, trebleTrend, rms);
64
65 if (!mBuildupActive) {
66 // Check if we should start a buildup
67 if (shouldStartBuildup(intensity)) {
68 mBuildupActive = true;
69 mPeakFired = false;
70 mCurrentBuildup.timestamp = timestamp;
71 mCurrentBuildup.intensity = intensity;
72 mCurrentBuildup.progress = 0.0f;
73 mCurrentBuildup.duration = 0;
74 mCurrentBuildup.active = true;
75
76 FL_DBG("BuildupDetector: Buildup started (intensity=" << intensity << ")");
77
78 mFireBuildupStart = true;
79 }
80 } else {
81 // Update existing buildup
82 mCurrentBuildup.duration = timestamp - mCurrentBuildup.timestamp;
83 mCurrentBuildup.intensity = intensity;
84
85 // Calculate progress (0.0 to 1.0)
86 float normalizedDuration = static_cast<float>(mCurrentBuildup.duration) / static_cast<float>(mMaxDuration);
87 mCurrentBuildup.progress = fl::min(1.0f, normalizedDuration);
88
89 // Check if we've reached peak (just before drop)
90 if (!mPeakFired && shouldPeak()) {
91 mPeakFired = true;
92 FL_DBG("BuildupDetector: Peak reached");
93
94 mFireBuildupPeak = true;
95 }
96
97 // Set progress callback flag
99
100 // Set general buildup callback flag
101 mFireBuildup = true;
102
103 // Check if buildup should end
104 if (shouldEndBuildup()) {
105 FL_DBG("BuildupDetector: Buildup ended (duration=" << mCurrentBuildup.duration << "ms)");
106
107 mBuildupActive = false;
108 mCurrentBuildup.active = false;
109
110 mFireBuildupEnd = true;
111 }
112 }
113
115 mPrevTreble = treble;
116 mLastUpdateTime = timestamp;
117}
118
120 if (mFireBuildupStart) {
122 mFireBuildupStart = false;
123 }
124 if (mFireBuildupPeak) {
126 mFireBuildupPeak = false;
127 }
130 mFireBuildupProgress = false;
131 }
132 if (mFireBuildup) {
134 mFireBuildup = false;
135 }
136 if (mFireBuildupEnd) {
138 mFireBuildupEnd = false;
139 }
140}
141
143 mBuildupActive = false;
144 mPeakFired = false;
149 mPrevEnergy = 0.0f;
150 mPrevTreble = 0.0f;
151 mLastUpdateTime = 0;
152
153 for (int i = 0; i < 32; i++) {
154 mEnergyHistory[i] = 0.0f;
155 }
156 for (int i = 0; i < 16; i++) {
157 mTrebleHistory[i] = 0.0f;
158 }
159 mEnergySG.reset();
160 mTrebleSG.reset();
161
163}
164
166 if (mEnergyHistorySize < 8 || !mEnergySG.full()) {
167 return 0.0f; // Not enough data
168 }
169
170 // Use SG-smoothed current value vs oldest history entry for trend
171 float currentSmoothed = mEnergySG.value();
172 // Get the oldest entry in the circular buffer
173 int oldestIdx = (mEnergyHistorySize < 32) ? 0 : mEnergyHistoryIndex;
174 float oldestValue = mEnergyHistory[oldestIdx];
175
176 if (oldestValue < 1e-6f) {
177 return 0.0f;
178 }
179
180 float riseRate = (currentSmoothed - oldestValue) / oldestValue;
181 return fl::max(0.0f, fl::min(2.0f, riseRate)); // Clamp to [0, 2]
182}
183
185 if (mTrebleHistorySize < 4 || !mTrebleSG.full()) {
186 return 0.0f; // Not enough data
187 }
188
189 // Use SG-smoothed current value vs oldest history entry for trend
190 float currentSmoothed = mTrebleSG.value();
191 int oldestIdx = (mTrebleHistorySize < 16) ? 0 : mTrebleHistoryIndex;
192 float oldestValue = mTrebleHistory[oldestIdx];
193
194 if (oldestValue < 1e-6f) {
195 return 0.0f;
196 }
197
198 float riseRate = (currentSmoothed - oldestValue) / oldestValue;
199 return fl::max(0.0f, fl::min(2.0f, riseRate)); // Clamp to [0, 2]
200}
201
202float BuildupDetector::calculateBuildupIntensity(float energyTrend, float trebleTrend, float rms) const {
203 // Buildup intensity is a combination of:
204 // - Energy rise trend (50%)
205 // - Treble rise trend (30%)
206 // - Overall energy level (20%)
207
208 float normalizedEnergy = fl::min(1.0f, rms);
209 float normalizedEnergyTrend = energyTrend / 2.0f; // Normalize from [0, 2] to [0, 1]
210 float normalizedTrebleTrend = trebleTrend / 2.0f;
211
212 float intensity = normalizedEnergyTrend * 0.5f +
213 normalizedTrebleTrend * 0.3f +
214 normalizedEnergy * 0.2f;
215
216 return fl::max(0.0f, fl::min(1.0f, intensity));
217}
218
219bool BuildupDetector::shouldStartBuildup(float intensity) const {
220 // Start buildup if:
221 // 1. Intensity exceeds threshold
222 // 2. Energy is rising (positive trend)
223 // 3. We have enough history
224
225 if (mEnergyHistorySize < 8) {
226 return false; // Not enough data
227 }
228
229 float energyTrend = calculateEnergyTrend();
230
231 return intensity >= mIntensityThreshold &&
232 energyTrend >= mEnergyRiseThreshold;
233}
234
236 if (!mBuildupActive) {
237 return false;
238 }
239
240 // End buildup if:
241 // 1. Duration exceeded maximum
242 // 2. Energy drops significantly (sudden energy loss = cancelled buildup)
243 // 3. Intensity drops below threshold for sustained period
244
245 if (mCurrentBuildup.duration > mMaxDuration) {
246 return true; // Too long
247 }
248
249 // Check for sudden energy drop (drop cancelled)
250 float energyTrend = calculateEnergyTrend();
251 if (energyTrend < -0.5f) { // Significant energy drop
252 return true;
253 }
254
255 // Check if intensity dropped below threshold
256 if (mCurrentBuildup.intensity < mIntensityThreshold * 0.5f) {
257 return true; // Lost momentum
258 }
259
260 return false;
261}
262
264 if (!mBuildupActive || mPeakFired) {
265 return false;
266 }
267
268 // Peak when:
269 // 1. Duration is at least minimum
270 // 2. Progress is near 1.0 (85%+)
271 // 3. Intensity is very high (90%+)
272 // OR
273 // 4. We've reached maximum duration
274
275 bool durationOk = mCurrentBuildup.duration >= mMinDuration;
276 bool nearEnd = mCurrentBuildup.progress >= 0.85f;
277 bool highIntensity = mCurrentBuildup.intensity >= 0.9f;
278 bool atMax = mCurrentBuildup.duration >= mMaxDuration * 0.95f;
279
280 return durationOk && (nearEnd || highIntensity || atMax);
281}
282
286
287 if (mEnergyHistorySize < 32) {
289 }
290}
291
295
296 if (mTrebleHistorySize < 16) {
298 }
299}
300
302 // Calculate high-frequency energy (top 25% of bins)
303 int startBin = static_cast<int>(fft.raw().size() * 0.75f);
304 float energy = 0.0f;
305 int count = 0;
306
307 for (fl::size i = startBin; i < fft.raw().size(); i++) {
308 energy += fft.raw()[i];
309 count++;
310 }
311
312 return (count > 0) ? energy / static_cast<float>(count) : 0.0f;
313}
314
315} // namespace detector
316} // namespace audio
317} // namespace fl
float rms(fl::span< const int16_t > data)
Definition simple.h:104
SavitzkyGolayFilter< float, 7 > mEnergySG
Definition buildup.h:99
SavitzkyGolayFilter< float, 7 > mTrebleSG
Definition buildup.h:100
function_list< void()> onBuildupEnd
Definition buildup.h:67
function_list< void(float progress)> onBuildupProgress
Definition buildup.h:65
function_list< void(const Buildup &)> onBuildup
Definition buildup.h:68
void update(shared_ptr< Context > context) override
~BuildupDetector() FL_NOEXCEPT override
shared_ptr< const fft::Bins > mRetainedFFT
Definition buildup.h:113
function_list< void()> onBuildupStart
Definition buildup.h:64
float getTrebleEnergy(const fft::Bins &fft) const
float calculateBuildupIntensity(float energyTrend, float trebleTrend, float rms) const
function_list< void()> onBuildupPeak
Definition buildup.h:66
bool shouldStartBuildup(float intensity) const
#define FL_WARN(X)
Definition log.h:276
#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
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT