FastLED 3.9.15
Loading...
Searching...
No Matches
audio_reactive.cpp
Go to the documentation of this file.
1#include "fl/audio_reactive.h"
2#include "fl/math.h"
3#include "fl/span.h"
4#include "fl/int.h"
5#include "fl/memory.h"
6#include <math.h>
7
8namespace fl {
9
11 : mConfig{}, mFFTBins(16) // Initialize with 16 frequency bins
12{
13 // Initialize enhanced beat detection components
16
17 // Initialize previous magnitudes array to zero
18 for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
19 mPreviousMagnitudes[i] = 0.0f;
20 }
21}
22
24
27
28 // Reset state
31 mLastBeatTime = 0;
32 mPreviousVolume = 0.0f;
33 mAGCMultiplier = 1.0f;
34 mMaxSample = 0.0f;
35 mAverageLevel = 0.0f;
36
37 // Reset enhanced beat detection components
39 mSpectralFluxDetector->reset();
40 mSpectralFluxDetector->setThreshold(config.spectralFluxThreshold);
41 }
42
43 // Reset previous magnitudes
44 for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
45 mPreviousMagnitudes[i] = 0.0f;
46 }
47}
48
52
54 if (!sample.isValid()) {
55 return; // Invalid sample, ignore
56 }
57
58 // Extract timestamp from the AudioSample
59 fl::u32 currentTimeMs = sample.timestamp();
60
61 // Process the AudioSample immediately - timing is gated by sample availability
62 processFFT(sample);
63 updateVolumeAndPeak(sample);
64
65 // Enhanced processing pipeline
68
69 // Enhanced beat detection (includes original)
70 detectBeat(currentTimeMs);
71 detectEnhancedBeats(currentTimeMs);
72
73 // Apply perceptual weighting if enabled
75
76 applyGain();
79
80 mCurrentData.timestamp = currentTimeMs;
81}
82
83void AudioReactive::update(fl::u32 currentTimeMs) {
84 // This method handles updates without new sample data
85 // Just apply smoothing and update timestamp
87 mCurrentData.timestamp = currentTimeMs;
88}
89
91 // Get PCM data from AudioSample
92 const auto& pcmData = sample.pcm();
93 if (pcmData.empty()) return;
94
95 // Use AudioSample's built-in FFT capability
96 sample.fft(&mFFTBins);
97
98 // Map FFT bins to frequency channels using WLED-compatible mapping
100}
101
103 // Copy FFT results to frequency bins array
104 for (int i = 0; i < 16; ++i) {
105 if (i < static_cast<int>(mFFTBins.bins_raw.size())) {
106 mCurrentData.frequencyBins[i] = mFFTBins.bins_raw[i];
107 } else {
108 mCurrentData.frequencyBins[i] = 0.0f;
109 }
110 }
111
112 // Apply pink noise compensation (from WLED)
113 for (int i = 0; i < 16; ++i) {
114 mCurrentData.frequencyBins[i] *= PINK_NOISE_COMPENSATION[i];
115 }
116
117 // Find dominant frequency
118 float maxMagnitude = 0.0f;
119 int maxBin = 0;
120 for (int i = 0; i < 16; ++i) {
121 if (mCurrentData.frequencyBins[i] > maxMagnitude) {
122 maxMagnitude = mCurrentData.frequencyBins[i];
123 maxBin = i;
124 }
125 }
126
127 // Convert bin index to approximate frequency
128 // Rough approximation based on WLED frequency mapping
129 const float binCenterFrequencies[16] = {
130 64.5f, // Bin 0: 43-86 Hz
131 107.5f, // Bin 1: 86-129 Hz
132 172.5f, // Bin 2: 129-216 Hz
133 258.5f, // Bin 3: 216-301 Hz
134 365.5f, // Bin 4: 301-430 Hz
135 495.0f, // Bin 5: 430-560 Hz
136 689.0f, // Bin 6: 560-818 Hz
137 969.0f, // Bin 7: 818-1120 Hz
138 1270.5f, // Bin 8: 1120-1421 Hz
139 1658.0f, // Bin 9: 1421-1895 Hz
140 2153.5f, // Bin 10: 1895-2412 Hz
141 2713.5f, // Bin 11: 2412-3015 Hz
142 3359.5f, // Bin 12: 3015-3704 Hz
143 4091.5f, // Bin 13: 3704-4479 Hz
144 5792.5f, // Bin 14: 4479-7106 Hz
145 8182.5f // Bin 15: 7106-9259 Hz
146 };
147
148 mCurrentData.dominantFrequency = binCenterFrequencies[maxBin];
149 mCurrentData.magnitude = maxMagnitude;
150}
151
153 // Get PCM data from AudioSample
154 const auto& pcmData = sample.pcm();
155 if (pcmData.empty()) {
156 mCurrentData.volume = 0.0f;
157 mCurrentData.volumeRaw = 0.0f;
158 mCurrentData.peak = 0.0f;
159 return;
160 }
161
162 // Use AudioSample's built-in RMS calculation
163 float rms = sample.rms();
164
165 // Calculate peak from PCM data
166 float maxSample = 0.0f;
167 for (fl::i16 pcmSample : pcmData) {
168 float absSample = (pcmSample < 0) ? -pcmSample : pcmSample;
169 maxSample = (maxSample > absSample) ? maxSample : absSample;
170 }
171
172 // Scale to 0-255 range (approximately)
173 mCurrentData.volumeRaw = rms / 128.0f; // Rough scaling
174 mCurrentData.volume = mCurrentData.volumeRaw;
175
176 // Peak detection
177 mCurrentData.peak = maxSample / 32768.0f * 255.0f;
178
179 // Update AGC tracking
180 if (mConfig.agcEnabled) {
181 // AGC with attack/decay behavior
182 float agcAttackRate = mConfig.attack / 255.0f * 0.2f + 0.01f; // 0.01 to 0.21
183 float agcDecayRate = mConfig.decay / 255.0f * 0.05f + 0.001f; // 0.001 to 0.051
184
185 // Track maximum level with attack/decay
186 if (maxSample > mMaxSample) {
187 // Rising - use attack rate (faster response)
188 mMaxSample = mMaxSample * (1.0f - agcAttackRate) + maxSample * agcAttackRate;
189 } else {
190 // Falling - use decay rate (slower response)
191 mMaxSample = mMaxSample * (1.0f - agcDecayRate) + maxSample * agcDecayRate;
192 }
193
194 // Update AGC multiplier with proper bounds
195 if (mMaxSample > 1000.0f) {
196 float targetLevel = 16384.0f; // Half of full scale
197 float newMultiplier = targetLevel / mMaxSample;
198
199 // Smooth AGC multiplier changes using attack/decay
200 if (newMultiplier > mAGCMultiplier) {
201 // Increasing gain - use attack rate
202 mAGCMultiplier = mAGCMultiplier * (1.0f - agcAttackRate) + newMultiplier * agcAttackRate;
203 } else {
204 // Decreasing gain - use decay rate
205 mAGCMultiplier = mAGCMultiplier * (1.0f - agcDecayRate) + newMultiplier * agcDecayRate;
206 }
207
208 // Clamp multiplier to reasonable bounds
209 mAGCMultiplier = (mAGCMultiplier < 0.1f) ? 0.1f : ((mAGCMultiplier > 10.0f) ? 10.0f : mAGCMultiplier);
210 }
211 }
212}
213
214void AudioReactive::detectBeat(fl::u32 currentTimeMs) {
215 // Need minimum time since last beat
216 if (currentTimeMs - mLastBeatTime < BEAT_COOLDOWN) {
217 mCurrentData.beatDetected = false;
218 return;
219 }
220
221 // Simple beat detection based on volume increase
222 float currentVolume = mCurrentData.volume;
223
224 // Beat detected if volume significantly increased
225 if (currentVolume > mPreviousVolume + mVolumeThreshold &&
226 currentVolume > 5.0f) { // Minimum volume threshold
227 mCurrentData.beatDetected = true;
228 mLastBeatTime = currentTimeMs;
229 } else {
230 mCurrentData.beatDetected = false;
231 }
232
233 // Update previous volume for next comparison using attack/decay
234 float beatAttackRate = mConfig.attack / 255.0f * 0.5f + 0.1f; // 0.1 to 0.6
235 float beatDecayRate = mConfig.decay / 255.0f * 0.3f + 0.05f; // 0.05 to 0.35
236
237 if (currentVolume > mPreviousVolume) {
238 // Rising volume - use attack rate (faster tracking)
239 mPreviousVolume = mPreviousVolume * (1.0f - beatAttackRate) + currentVolume * beatAttackRate;
240 } else {
241 // Falling volume - use decay rate (slower tracking)
242 mPreviousVolume = mPreviousVolume * (1.0f - beatDecayRate) + currentVolume * beatDecayRate;
243 }
244}
245
247 // Apply gain setting (0-255 maps to 0.0-2.0 multiplier)
248 float gainMultiplier = static_cast<float>(mConfig.gain) / 128.0f;
249
250 mCurrentData.volume *= gainMultiplier;
251 mCurrentData.volumeRaw *= gainMultiplier;
252 mCurrentData.peak *= gainMultiplier;
253
254 for (int i = 0; i < 16; ++i) {
255 mCurrentData.frequencyBins[i] *= gainMultiplier;
256 }
257
258 // Apply AGC if enabled
259 if (mConfig.agcEnabled) {
261 mCurrentData.volumeRaw *= mAGCMultiplier;
263
264 for (int i = 0; i < 16; ++i) {
265 mCurrentData.frequencyBins[i] *= mAGCMultiplier;
266 }
267 }
268}
269
271 // Apply scaling mode to frequency bins
272 for (int i = 0; i < 16; ++i) {
273 float value = mCurrentData.frequencyBins[i];
274
275 switch (mConfig.scalingMode) {
276 case 1: // Logarithmic scaling
277 if (value > 1.0f) {
278 value = logf(value) * 20.0f; // Scale factor
279 } else {
280 value = 0.0f;
281 }
282 break;
283
284 case 2: // Linear scaling (no change)
285 // value remains as-is
286 break;
287
288 case 3: // Square root scaling
289 if (value > 0.0f) {
290 value = sqrtf(value) * 8.0f; // Scale factor
291 } else {
292 value = 0.0f;
293 }
294 break;
295
296 case 0: // No scaling
297 default:
298 // value remains as-is
299 break;
300 }
301
302 mCurrentData.frequencyBins[i] = value;
303 }
304}
305
307 // Attack/decay smoothing - different rates for rising vs falling values
308 // Convert attack/decay times to smoothing factors
309 // Shorter times = less smoothing (faster response)
310 float attackFactor = 1.0f - (mConfig.attack / 255.0f * 0.9f); // Range: 0.1 to 1.0
311 float decayFactor = 1.0f - (mConfig.decay / 255.0f * 0.95f); // Range: 0.05 to 1.0
312
313 // Apply attack/decay smoothing to volume
314 if (mCurrentData.volume > mSmoothedData.volume) {
315 // Rising - use attack time (faster response)
316 mSmoothedData.volume = mSmoothedData.volume * (1.0f - attackFactor) +
317 mCurrentData.volume * attackFactor;
318 } else {
319 // Falling - use decay time (slower response)
320 mSmoothedData.volume = mSmoothedData.volume * (1.0f - decayFactor) +
321 mCurrentData.volume * decayFactor;
322 }
323
324 // Apply attack/decay smoothing to volumeRaw
325 if (mCurrentData.volumeRaw > mSmoothedData.volumeRaw) {
326 mSmoothedData.volumeRaw = mSmoothedData.volumeRaw * (1.0f - attackFactor) +
327 mCurrentData.volumeRaw * attackFactor;
328 } else {
329 mSmoothedData.volumeRaw = mSmoothedData.volumeRaw * (1.0f - decayFactor) +
330 mCurrentData.volumeRaw * decayFactor;
331 }
332
333 // Apply attack/decay smoothing to peak
334 if (mCurrentData.peak > mSmoothedData.peak) {
335 mSmoothedData.peak = mSmoothedData.peak * (1.0f - attackFactor) +
336 mCurrentData.peak * attackFactor;
337 } else {
338 mSmoothedData.peak = mSmoothedData.peak * (1.0f - decayFactor) +
339 mCurrentData.peak * decayFactor;
340 }
341
342 // Apply attack/decay smoothing to frequency bins
343 for (int i = 0; i < 16; ++i) {
344 if (mCurrentData.frequencyBins[i] > mSmoothedData.frequencyBins[i]) {
345 // Rising - use attack time
346 mSmoothedData.frequencyBins[i] = mSmoothedData.frequencyBins[i] * (1.0f - attackFactor) +
347 mCurrentData.frequencyBins[i] * attackFactor;
348 } else {
349 // Falling - use decay time
350 mSmoothedData.frequencyBins[i] = mSmoothedData.frequencyBins[i] * (1.0f - decayFactor) +
351 mCurrentData.frequencyBins[i] * decayFactor;
352 }
353 }
354
355 // Copy non-smoothed values
356 mSmoothedData.beatDetected = mCurrentData.beatDetected;
357 mSmoothedData.dominantFrequency = mCurrentData.dominantFrequency;
358 mSmoothedData.magnitude = mCurrentData.magnitude;
359 mSmoothedData.timestamp = mCurrentData.timestamp;
360}
361
363 return mCurrentData;
364}
365
369
371 return mCurrentData.volume;
372}
373
375 // Average of bins 0-1 (sub-bass and bass)
376 return (mCurrentData.frequencyBins[0] + mCurrentData.frequencyBins[1]) / 2.0f;
377}
378
380 // Average of bins 6-7 (midrange around 1kHz)
381 return (mCurrentData.frequencyBins[6] + mCurrentData.frequencyBins[7]) / 2.0f;
382}
383
385 // Average of bins 14-15 (high frequencies)
386 return (mCurrentData.frequencyBins[14] + mCurrentData.frequencyBins[15]) / 2.0f;
387}
388
390 return mCurrentData.beatDetected;
391}
392
394 return mCurrentData.bassBeatDetected;
395}
396
398 return mCurrentData.midBeatDetected;
399}
400
402 return mCurrentData.trebleBeatDetected;
403}
404
406 return mCurrentData.spectralFlux;
407}
408
410 return mCurrentData.bassEnergy;
411}
412
414 return mCurrentData.midEnergy;
415}
416
418 return mCurrentData.trebleEnergy;
419}
420
422 float vol = (mCurrentData.volume < 0.0f) ? 0.0f : ((mCurrentData.volume > 255.0f) ? 255.0f : mCurrentData.volume);
423 return static_cast<fl::u8>(vol);
424}
425
426CRGB AudioReactive::volumeToColor(const CRGBPalette16& /* palette */) const {
427 fl::u8 index = volumeToScale255();
428 // Simplified color palette lookup
429 return CRGB(index, index, index); // For now, return grayscale
430}
431
433 if (binIndex >= 16) return 0;
434
435 float value = (mCurrentData.frequencyBins[binIndex] < 0.0f) ? 0.0f :
436 ((mCurrentData.frequencyBins[binIndex] > 255.0f) ? 255.0f : mCurrentData.frequencyBins[binIndex]);
437 return static_cast<fl::u8>(value);
438}
439
440// Enhanced beat detection methods
442 // Calculate energy for bass frequencies (bins 0-1)
443 mCurrentData.bassEnergy = (mCurrentData.frequencyBins[0] + mCurrentData.frequencyBins[1]) / 2.0f;
444
445 // Calculate energy for mid frequencies (bins 6-7)
446 mCurrentData.midEnergy = (mCurrentData.frequencyBins[6] + mCurrentData.frequencyBins[7]) / 2.0f;
447
448 // Calculate energy for treble frequencies (bins 14-15)
449 mCurrentData.trebleEnergy = (mCurrentData.frequencyBins[14] + mCurrentData.frequencyBins[15]) / 2.0f;
450}
451
454 mCurrentData.spectralFlux = 0.0f;
455 return;
456 }
457
458 // Calculate spectral flux from current and previous frequency bins
459 mCurrentData.spectralFlux = mSpectralFluxDetector->calculateSpectralFlux(
460 mCurrentData.frequencyBins,
462 );
463
464 // Update previous magnitudes for next frame
465 for (int i = 0; i < 16; ++i) {
466 mPreviousMagnitudes[i] = mCurrentData.frequencyBins[i];
467 }
468}
469
470void AudioReactive::detectEnhancedBeats(fl::u32 currentTimeMs) {
471 // Reset beat flags
472 mCurrentData.bassBeatDetected = false;
473 mCurrentData.midBeatDetected = false;
474 mCurrentData.trebleBeatDetected = false;
475
476 // Skip if enhanced beat detection is disabled
477 if (!mConfig.enableSpectralFlux && !mConfig.enableMultiBand) {
478 return;
479 }
480
481 // Need minimum time since last beat for enhanced detection too
482 if (currentTimeMs - mLastBeatTime < BEAT_COOLDOWN) {
483 return;
484 }
485
486 // Spectral flux-based beat detection
487 if (mConfig.enableSpectralFlux && mSpectralFluxDetector) {
488 bool onsetDetected = mSpectralFluxDetector->detectOnset(
489 mCurrentData.frequencyBins,
491 );
492
493 if (onsetDetected) {
494 // Enhance the traditional beat detection when spectral flux confirms
495 mCurrentData.beatDetected = true;
496 mLastBeatTime = currentTimeMs;
497 }
498 }
499
500 // Multi-band beat detection
501 if (mConfig.enableMultiBand) {
502 // Bass beat detection (bins 0-1)
503 if (mCurrentData.bassEnergy > mConfig.bassThreshold) {
504 mCurrentData.bassBeatDetected = true;
505 }
506
507 // Mid beat detection (bins 6-7)
508 if (mCurrentData.midEnergy > mConfig.midThreshold) {
509 mCurrentData.midBeatDetected = true;
510 }
511
512 // Treble beat detection (bins 14-15)
513 if (mCurrentData.trebleEnergy > mConfig.trebleThreshold) {
514 mCurrentData.trebleBeatDetected = true;
515 }
516 }
517}
518
520 // Apply perceptual weighting if available
522 mPerceptualWeighting->applyAWeighting(mCurrentData);
523
524 // Apply loudness compensation with reference level of 50.0f
525 mPerceptualWeighting->applyLoudnessCompensation(mCurrentData, 50.0f);
526 }
527}
528
529// Helper methods
530float AudioReactive::mapFrequencyBin(int fromBin, int toBin) {
531 if (fromBin < 0 || toBin >= static_cast<int>(mFFTBins.size()) || fromBin > toBin) {
532 return 0.0f;
533 }
534
535 float sum = 0.0f;
536 for (int i = fromBin; i <= toBin; ++i) {
537 if (i < static_cast<int>(mFFTBins.bins_raw.size())) {
538 sum += mFFTBins.bins_raw[i];
539 }
540 }
541
542 return sum / static_cast<float>(toBin - fromBin + 1);
543}
544
546 if (samples.empty()) return 0.0f;
547
548 float sumSquares = 0.0f;
549 for (const auto& sample : samples) {
550 float f = static_cast<float>(sample);
551 sumSquares += f * f;
552 }
553
554 return sqrtf(sumSquares / samples.size());
555}
556
557// SpectralFluxDetector implementation
559 : mFluxThreshold(0.1f)
561 , mHistoryIndex(0)
562#endif
563{
564 // Initialize previous magnitudes to zero
565 for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
566 mPreviousMagnitudes[i] = 0.0f;
567 }
568
569#if SKETCH_HAS_LOTS_OF_MEMORY
570 // Initialize flux history to zero
571 for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
572 mFluxHistory[i] = 0.0f;
573 }
574#endif
575}
576
578
580 for (fl::size i = 0; i < mPreviousMagnitudes.size(); ++i) {
581 mPreviousMagnitudes[i] = 0.0f;
582 }
583
584#if SKETCH_HAS_LOTS_OF_MEMORY
585 for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
586 mFluxHistory[i] = 0.0f;
587 }
588 mHistoryIndex = 0;
589#endif
590}
591
592bool SpectralFluxDetector::detectOnset(const float* currentBins, const float* /* previousBins */) {
593 float flux = calculateSpectralFlux(currentBins, mPreviousMagnitudes.data());
594
595#if SKETCH_HAS_LOTS_OF_MEMORY
596 // Store flux in history for adaptive threshold calculation
597 mFluxHistory[mHistoryIndex] = flux;
598 mHistoryIndex = (mHistoryIndex + 1) % mFluxHistory.size();
599
600 float adaptiveThreshold = calculateAdaptiveThreshold();
601 return flux > adaptiveThreshold;
602#else
603 // Simple fixed threshold for memory-constrained platforms
604 return flux > mFluxThreshold;
605#endif
606}
607
608float SpectralFluxDetector::calculateSpectralFlux(const float* currentBins, const float* previousBins) {
609 float flux = 0.0f;
610
611 // Calculate spectral flux as sum of positive differences
612 for (int i = 0; i < 16; ++i) {
613 float diff = currentBins[i] - previousBins[i];
614 if (diff > 0.0f) {
615 flux += diff;
616 }
617 }
618
619 // Update previous magnitudes for next calculation
620 for (int i = 0; i < 16; ++i) {
621 mPreviousMagnitudes[i] = currentBins[i];
622 }
623
624 return flux;
625}
626
628 mFluxThreshold = threshold;
629}
630
632 return mFluxThreshold;
633}
634
635#if SKETCH_HAS_LOTS_OF_MEMORY
636float SpectralFluxDetector::calculateAdaptiveThreshold() {
637 // Calculate moving average of flux history
638 float sum = 0.0f;
639 for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
640 sum += mFluxHistory[i];
641 }
642 float average = sum / mFluxHistory.size();
643
644 // Adaptive threshold is base threshold plus some multiple of recent average
645 return mFluxThreshold + (average * 0.5f);
646}
647#endif
648
649// BeatDetectors implementation
655
657
659#if SKETCH_HAS_LOTS_OF_MEMORY
660 bass.reset();
661 mid.reset();
662 treble.reset();
663#else
664 combined.reset();
665#endif
666
667 mBassEnergy = 0.0f;
668 mMidEnergy = 0.0f;
669 mTrebleEnergy = 0.0f;
670 mPreviousBassEnergy = 0.0f;
671 mPreviousMidEnergy = 0.0f;
673}
674
675void BeatDetectors::detectBeats(const float* frequencyBins, AudioData& audioData) {
676 // Calculate current band energies
677 mBassEnergy = (frequencyBins[0] + frequencyBins[1]) / 2.0f;
678 mMidEnergy = (frequencyBins[6] + frequencyBins[7]) / 2.0f;
679 mTrebleEnergy = (frequencyBins[14] + frequencyBins[15]) / 2.0f;
680
681#if SKETCH_HAS_LOTS_OF_MEMORY
682 // Use separate detectors for each band
683 audioData.bassBeatDetected = bass.detectOnset(&mBassEnergy, &mPreviousBassEnergy);
684 audioData.midBeatDetected = mid.detectOnset(&mMidEnergy, &mPreviousMidEnergy);
685 audioData.trebleBeatDetected = treble.detectOnset(&mTrebleEnergy, &mPreviousTrebleEnergy);
686#else
687 // Use simple threshold detection for memory-constrained platforms
688 audioData.bassBeatDetected = (mBassEnergy > mPreviousBassEnergy * 1.3f) && (mBassEnergy > 0.1f);
689 audioData.midBeatDetected = (mMidEnergy > mPreviousMidEnergy * 1.25f) && (mMidEnergy > 0.08f);
690 audioData.trebleBeatDetected = (mTrebleEnergy > mPreviousTrebleEnergy * 1.2f) && (mTrebleEnergy > 0.05f);
691#endif
692
693 // Update previous energies
697}
698
699void BeatDetectors::setThresholds(float bassThresh, float midThresh, float trebleThresh) {
700#if SKETCH_HAS_LOTS_OF_MEMORY
701 bass.setThreshold(bassThresh);
702 mid.setThreshold(midThresh);
703 treble.setThreshold(trebleThresh);
704#else
705 combined.setThreshold((bassThresh + midThresh + trebleThresh) / 3.0f);
706#endif
707}
708
709// PerceptualWeighting implementation
711#if SKETCH_HAS_LOTS_OF_MEMORY
712 : mHistoryIndex(0)
713#endif
714{
715#if SKETCH_HAS_LOTS_OF_MEMORY
716 // Initialize loudness history to zero
717 for (fl::size i = 0; i < mLoudnessHistory.size(); ++i) {
718 mLoudnessHistory[i] = 0.0f;
719 }
720 // Suppress unused warning until mHistoryIndex is implemented
721 (void)mHistoryIndex;
722#endif
723}
724
726
728 // Apply A-weighting coefficients to frequency bins
729 for (int i = 0; i < 16; ++i) {
730 data.frequencyBins[i] *= A_WEIGHTING_COEFFS[i];
731 }
732}
733
734void PerceptualWeighting::applyLoudnessCompensation(AudioData& data, float referenceLevel) const {
735 // Calculate current loudness level
736 float currentLoudness = data.volume;
737
738 // Calculate compensation factor based on difference from reference
739 float compensationFactor = 1.0f;
740 if (currentLoudness < referenceLevel) {
741 // Boost quiet signals
742 compensationFactor = 1.0f + (referenceLevel - currentLoudness) / referenceLevel * 0.3f;
743 } else if (currentLoudness > referenceLevel * 1.5f) {
744 // Slightly reduce very loud signals
745 compensationFactor = 1.0f - (currentLoudness - referenceLevel * 1.5f) / (referenceLevel * 2.0f) * 0.2f;
746 }
747
748 // Apply compensation to frequency bins
749 for (int i = 0; i < 16; ++i) {
750 data.frequencyBins[i] *= compensationFactor;
751 }
752
753#if SKETCH_HAS_LOTS_OF_MEMORY
754 // Store in history for future adaptive compensation (not implemented yet)
755 // This would be used for more sophisticated dynamic range compensation
756#endif
757}
758
759} // namespace fl
float rms(Slice< const int16_t > data)
Definition simple.h:98
fl::AudioConfig config
Definition AudioInput.h:27
fl::array< float, 16 > mPreviousMagnitudes
bool isTrebleBeat() const
CRGB volumeToColor(const CRGBPalette16 &palette) const
const AudioData & getData() const
void update(fl::u32 currentTimeMs)
void processFFT(const AudioSample &sample)
fl::u8 volumeToScale255() const
void detectBeat(fl::u32 currentTimeMs)
AudioReactiveConfig mConfig
void mapFFTBinsToFrequencyChannels()
float getSpectralFlux() const
float getBass() const
float getMidEnergy() const
float computeRMS(const fl::vector< fl::i16 > &samples)
static constexpr float PINK_NOISE_COMPENSATION[16]
void updateVolumeAndPeak(const AudioSample &sample)
float getTrebleEnergy() const
fl::unique_ptr< SpectralFluxDetector > mSpectralFluxDetector
static constexpr fl::u32 BEAT_COOLDOWN
bool isBassBeat() const
float getVolume() const
float getTreble() const
float mapFrequencyBin(int fromBin, int toBin)
void detectEnhancedBeats(fl::u32 currentTimeMs)
const AudioData & getSmoothedData() const
void begin(const AudioReactiveConfig &config=AudioReactiveConfig{})
void setConfig(const AudioReactiveConfig &config)
fl::u8 frequencyToScale255(fl::u8 binIndex) const
float getBassEnergy() const
void processSample(const AudioSample &sample)
fl::unique_ptr< PerceptualWeighting > mPerceptualWeighting
const VectorPCM & pcm() const
Definition audio.cpp:62
bool isValid() const
Definition audio.h:36
void fft(FFTBins *out) const
Definition audio.cpp:173
float rms() const
Definition audio.cpp:128
fl::u32 timestamp() const
Definition audio.cpp:121
bool empty() const
Definition vector.h:547
fl::size size() const
Definition vector.h:545
void applyLoudnessCompensation(AudioData &data, float referenceLevel) const
static constexpr float A_WEIGHTING_COEFFS[16]
void applyAWeighting(AudioData &data) const
void setThreshold(float threshold)
float calculateSpectralFlux(const float *currentBins, const float *previousBins)
bool detectOnset(const float *currentBins, const float *previousBins)
fl::array< float, 16 > mPreviousMagnitudes
unsigned char u8
Definition int.h:17
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args)
Definition memory.h:42
HeapVector< T, Allocator > vector
Definition vector.h:1214
IMPORTANT!
Definition crgb.h:20
float frequencyBins[16]
#define SKETCH_HAS_LOTS_OF_MEMORY
Representation of an RGB pixel (Red, Green, Blue)
Definition crgb.h:86
void detectBeats(const float *frequencyBins, AudioData &audioData)
void setThresholds(float bassThresh, float midThresh, float trebleThresh)
SpectralFluxDetector combined