docs/audio_signal_conditioning.md
Phase 1 of the FastLED audio middleware adds professional-grade signal conditioning to the audio processing pipeline. This addresses critical gaps identified in the core FastLED audio library by providing:
These components work together to clean and normalize raw audio from I2S microphones before FFT analysis and beat detection.
#include <FastLED.h>
AudioReactive audio;
AudioReactiveConfig config;
// Enable signal conditioning (DC removal, spike filter, noise gate)
config.enableSignalConditioning = true;
// Enable adaptive gain control
config.enableAutoGain = true;
// Enable noise floor tracking
config.enableNoiseFloorTracking = true;
audio.begin(config);
// Process samples - signal conditioning applied automatically
AudioSample sample = ...; // From I2S microphone
audio.processSample(sample);
Each component can also be used independently:
#include "fl/audio/signal_conditioner.h"
#include "fl/audio/auto_gain.h"
#include "fl/audio/noise_floor_tracker.h"
// Signal conditioning
SignalConditioner conditioner;
AudioSample cleanSample = conditioner.processSample(rawSample);
// Auto gain
AutoGain agc;
AudioSample amplifiedSample = agc.process(cleanSample);
// Noise floor tracking
NoiseFloorTracker tracker;
tracker.update(sample.rms());
float normalized = tracker.normalize(sample.rms());
Cleans raw PCM audio by removing common artifacts from I2S microphones.
Features:
Configuration:
SignalConditionerConfig config;
config.enableDCRemoval = true;
config.enableSpikeFilter = true;
config.enableNoiseGate = true;
config.spikeThreshold = 10000; // Reject samples > ±10000
config.noiseGateOpenThreshold = 500; // Gate opens when signal > 500
config.noiseGateCloseThreshold = 300; // Gate closes when signal < 300
config.dcRemovalAlpha = 0.99f; // DC tracking smoothness
Example:
SignalConditioner conditioner;
conditioner.configure(config);
AudioSample rawSample = ...; // From I2S
AudioSample cleanSample = conditioner.processSample(rawSample);
// Check statistics
const auto& stats = conditioner.getStats();
Serial.printf("DC offset: %d, Spikes rejected: %u\n",
stats.dcOffset, stats.spikesRejected);
Adaptive gain control using a PI (proportional-integral) controller with slow peak envelope tracking, inspired by WLED Sound Reactive.
Features:
Configuration:
AutoGainConfig config;
config.enabled = true;
config.preset = AGCPreset_Normal; // Normal, Vivid, Lazy, or Custom
config.targetRMSLevel = 8000.0f; // Target RMS after gain
config.minGain = 1.0f / 64.0f; // Minimum gain multiplier
config.maxGain = 32.0f; // Maximum gain multiplier
// Custom preset (only used when preset == AGCPreset_Custom):
// config.peakDecayTau = 3.3f;
// config.kp = 0.6f;
// config.ki = 1.7f;
// config.gainFollowSlowTau = 12.3f;
// config.gainFollowFastTau = 0.38f;
Example:
AutoGain agc;
agc.configure(config);
// Process samples - AGC adapts over time
for (int i = 0; i < 100; ++i) {
AudioSample sample = ...;
AudioSample amplified = agc.process(sample);
// Monitor gain
if (i % 10 == 0) {
const auto& stats = agc.getStats();
Serial.printf("Gain: %.2f, Peak: %.1f, Input RMS: %.1f, Output RMS: %.1f\n",
stats.currentGain, stats.peakEnvelope, stats.inputRMS, stats.outputRMS);
}
}
Tracks adaptive noise floor with hysteresis to prevent "noise chasing."
Features:
Configuration:
NoiseFloorTrackerConfig config;
config.enabled = true;
config.decayRate = 0.99f; // How fast floor decreases (0-1)
config.attackRate = 0.001f; // How fast floor increases (0-1)
config.hysteresisMargin = 100.0f; // Prevents noise chasing
config.minFloor = 10.0f; // Minimum floor value
config.maxFloor = 5000.0f; // Maximum floor value
config.crossDomainWeight = 0.3f; // Blend time/freq domains (0-1)
Example:
NoiseFloorTracker tracker;
tracker.configure(config);
// Update with each sample
AudioSample sample = ...;
float rms = sample.rms();
tracker.update(rms);
// Use normalized values
float normalized = tracker.normalize(rms);
bool isSignal = tracker.isAboveFloor(rms);
// Check statistics
const auto& stats = tracker.getStats();
Serial.printf("Floor: %.1f, Min: %.1f, Max: %.1f\n",
stats.currentFloor, stats.minObserved, stats.maxObserved);
The signal conditioning pipeline is integrated into AudioReactive's processSample() method:
Raw AudioSample (from I2S)
↓
[SignalConditioner] → DC removal, spike filter, noise gate
↓
[AutoGain] → Adaptive gain normalization
↓
[NoiseFloorTracker] → Update noise floor estimate (passive)
↓
FFT Analysis → Beat Detection → Output
Access statistics from AudioReactive:
AudioReactive audio;
// ... configure and process samples ...
// Get signal conditioning stats
const auto* scStats = audio.getSignalConditionerStats();
if (scStats) {
Serial.printf("DC offset: %d\n", scStats->dcOffset);
}
const auto* agStats = audio.getAutoGainStats();
if (agStats) {
Serial.printf("Current gain: %.2f\n", agStats->currentGain);
}
const auto* nfStats = audio.getNoiseFloorStats();
if (nfStats) {
Serial.printf("Noise floor: %.1f\n", nfStats->currentFloor);
}
Comprehensive unit tests are provided:
# Test individual components
bash test signal_conditioner --cpp
bash test auto_gain --cpp
bash test noise_floor_tracker --cpp
# Test integration with AudioReactive
bash test audio_signal_conditioning_integration --cpp
# Run all audio tests
bash test audio --cpp
AutoGainConfig::maxGainAutoGainConfig::targetRMSLevelNoiseFloorTrackerConfig::hysteresisMarginNoiseFloorTrackerConfig::decayRate (slower decay)NoiseFloorTrackerConfig::attackRate (slower rise)SignalConditionerConfig::noiseGateCloseThresholdSignalConditionerConfig::dcRemovalAlpha for slower trackingSignal conditioning middleware is contributed to FastLED core. For issues or improvements, please open a GitHub issue.