|
FastLED 3.9.15
|
Directory dependency graph for audio:Directories | |
| detector | |
| fft | |
Files | |
| _build.cpp.hpp | |
| Unity build header for fl/audio/ directory. | |
| audio.cpp.hpp | |
| audio.h | |
| audio_batch.cpp.hpp | |
| audio_batch.h | |
| audio_context.cpp.hpp | |
| audio_context.h | |
| audio_detector.h | |
| audio_frame.h | |
| audio_input.cpp.hpp | |
| audio_input.h | |
| audio_manager.cpp.hpp | |
| audio_manager.h | |
| audio_processor.cpp.hpp | |
| audio_processor.h | |
| audio_reactive.cpp.hpp | |
| audio_reactive.h | |
| auto_gain.cpp.hpp | |
| auto_gain.h | |
| frequency_bin_mapper.cpp.hpp | |
| Implementation of FrequencyBinMapper for fft::FFT bin to frequency channel mapping. | |
| frequency_bin_mapper.h | |
| input.h | |
| mic_profiles.h | |
| mic_response_data.h | |
| High-resolution microphone frequency response data and utilities. | |
| noise_floor_tracker.cpp.hpp | |
| noise_floor_tracker.h | |
| signal_conditioner.cpp.hpp | |
| signal_conditioner.h | |
| silence_envelope.cpp.hpp | |
| silence_envelope.h | |
| spectral_equalizer.cpp.hpp | |
| Implementation of SpectralEqualizer for frequency-dependent gain correction. | |
| spectral_equalizer.h | |
| synth.cpp.hpp | |
| synth.h | |
| Bandlimited audio synthesizer - waveform oscillator module. | |
FastLED.add(AudioConfig) (Recommended)The simplest way to get audio-reactive LEDs. FastLED.add() creates the microphone, wires up a scheduler task that auto-reads samples, and returns an AudioProcessor ready for callbacks. No manual update() loop needed — audio is pumped automatically during FastLED.show().
How it works: FastLED.add(config) internally calls IAudioInput::create(), starts the mic, stores the AudioProcessor in an internal list, and creates a fl::task::every_ms(1) that drains all buffered samples and feeds them to the AudioProcessor. The task runs during FastLED.show() via end-frame → async_run() → Scheduler::update(). Use FastLED.remove(audio) to tear down a specific processor, or let it live for the lifetime of the program.
Platform behavior:
FASTLED_HAS_AUDIO_INPUT == 1): Real I2S mic is created and pumped.FASTLED_HAS_AUDIO_INPUT == 0): Returns a valid but inert AudioProcessor — callbacks never fire, polling getters return zero. Code compiles everywhere without #ifdef.Test injection: On any platform, pass a custom IAudioInput directly:
If you need more control over when samples are read (e.g., reading from a buffer at a specific rate), you can create the AudioProcessor yourself and call update() manually.
Use onKick(), onSnare(), and onHiHat() callbacks to trigger different colors for each drum hit.
If you prefer polling over callbacks, AudioProcessor also provides getter methods that return uint8_t-scaled values (0-255), perfect for direct LED control.
A dead-simple WLED-compatible equalizer: 16 frequency bins normalized to 0.0-1.0, plus bass/mid/treble/volume/zcf convenience getters. All values are pre-normalized — just multiply by 255 if you want bytes.
Or use polling — no callbacks needed:
Bin layout (WLED-compatible):
| Bins | Range | Getter |
|---|---|---|
| 0-3 | ~60-320 Hz (bass) | getEqBass() |
| 4-10 | ~320-2560 Hz (mid) | getEqMid() |
| 11-15 | ~2560-5120 Hz (treble) | getEqTreble() |
VibeDetector provides self-normalizing, FPS-independent bass/mid/treb levels with asymmetric attack/decay smoothing. The algorithm is a direct port of Ryan Geiss's DoCustomSoundAnalysis() from MilkDrop v2.25c — the legendary Winamp visualizer. See MILK_DROP_AUDIO_REACTIVE.md for a detailed technical analysis of the original algorithm.
Key properties:
bass > bass_att means a beat is happening right nowVibe levels are not 0.0-1.0 like the Equalizer. They are ratios against the long-term average energy of the current song:
| Value | Meaning |
|---|---|
1.0 | Average level for this song/environment |
> 1.0 | Louder than recent average (spike/beat) |
< 1.0 | Quieter than recent average |
~0.7 | Quiet passage |
~1.3 | Loud passage / beat hit |
The self-normalization means the same preset code works on quiet acoustic songs and loud electronic music without any gain or threshold calibration.
Each band has two relative levels:
bass / mid / treb — Immediate relative level. Reacts instantly to audio changes.bassAtt / midAtt / trebAtt — Smoothed ("attenuated") relative level. Follows the signal with asymmetric attack/decay: fast attack (80% new signal on beats), slow decay (graceful fadeout).The relationship between these two is the core of MilkDrop's beat detection:
bass > bassAtt, energy is rising — a beat is happeningbass < bassAtt, energy is falling — fading out between beatsThe canonical MilkDrop idiom for beat-reactive effects:
| API | Range | Use when... |
|---|---|---|
getVibeBass() | ~1.0 (unbounded) | You want self-normalizing, beat-reactive effects that adapt to any song |
getBassLevel() | 0.0-1.0 | You want simple normalized levels for brightness/color mapping |
getEqBass() | 0.0-1.0 | You want WLED-compatible spectrum analysis |
getBassRaw() | 0+ (absolute) | You need raw FFT energy for custom algorithms |
The onVibeLevels callback provides everything in one struct per frame:
Spike callbacks fire on the rising edge only (transition from no-spike to spike), so you get one event per beat rather than continuous firing:
Create your own detector by subclassing AudioDetector. This gives you direct access to AudioContext for FFT data while integrating into the update loop.
Create an AudioContext manually to share FFT data across multiple detectors. This demonstrates the two-phase update/fireCallbacks loop.
For full control, use AudioSample and FFT directly. This is useful when you want to build your own visualizer or analysis pipeline.
For the simplest audio reactivity without FFT, use AudioSample properties directly.
The Synth module generates bandlimited waveforms for audio output. Useful for creating tones, alerts, or musical output from your microcontroller.
| Level | Use when... | Key classes |
|---|---|---|
| Easiest (FastLED.add) | You want audio-reactive LEDs with zero boilerplate | FastLED.add(AudioConfig) |
| High (AudioProcessor) | You want manual control over when samples are processed | AudioProcessor |
| Mid (AudioContext) | You're writing a custom detector or need shared FFT caching | AudioContext, AudioDetector |
| Low (AudioSample/FFT) | You want raw spectrum data or PCM-level control | AudioSample, FFT, FFTBins |
| Output (Synth) | You need to generate audio waveforms | ISynthEngine, ISynthOscillator |
When you call audio.update(sample), three stages run in sequence:
update(context) is called with a shared AudioContext. Detectors read FFT/PCM data and compute their internal state, but do not fire callbacks yet. The FFT is computed lazily on first access and cached — if three detectors all call context->getFFT(16), the FFT runs only once.fireCallbacks() is called. This two-phase design prevents callback code from interfering with other detectors' analysis within the same frame.Detectors are created only when you register a callback or call a polling getter. If you only use onBeat() and getBassLevel(), only BeatDetector and FrequencyBands are instantiated. The rest consume zero memory.
AudioContext caches FFT results per frame. Multiple detectors requesting the same FFT parameters share a single computation. This is why the mid-level API passes a shared_ptr<AudioContext> — it's the shared cache.
| Category | Callbacks |
|---|---|
| Beat | onBeat(void()), onBeatPhase(float), onOnset(float), onTempoChange(float, float) |
| Tempo | onTempo(float), onTempoWithConfidence(float, float), onTempoStable(), onTempoUnstable() |
| Frequency | onBass(float), onMid(float), onTreble(float), onFrequencyBands(float, float, float) |
| Energy | onEnergy(float), onNormalizedEnergy(float), onPeak(float), onAverageEnergy(float) |
| Transient | onTransient(), onTransientWithStrength(float), onAttack(float) |
| Silence | onSilence(u8), onSilenceStart(), onSilenceEnd(), onSilenceDuration(u32) |
| Pitch | onPitch(float), onPitchWithConfidence(float, float), onPitchChange(float), onVoiced(u8) |
| Note | onNoteOn(u8, u8), onNoteOff(u8), onNoteChange(u8, u8) |
| Percussion | onPercussion(PercussionType), onKick(), onSnare(), onHiHat(), onTom() |
| Vocal | onVocal(u8), onVocalStart(), onVocalEnd(), onVocalConfidence(float) |
| Dynamics | onCrescendo(), onDiminuendo(), onDynamicTrend(float), onCompressionRatio(float) |
| Downbeat | onDownbeat(), onMeasureBeat(u8), onMeterChange(u8), onMeasurePhase(float) |
| Backbeat | onBackbeat(u8, float, float) |
| Chord | onChord(Chord), onChordChange(Chord), onChordEnd() |
| Key | onKey(Key), onKeyChange(Key), onKeyEnd() |
| Mood | onMood(Mood), onMoodChange(Mood), onValenceArousal(float, float) |
| Buildup | onBuildupStart(), onBuildupProgress(float), onBuildupPeak(), onBuildupEnd(), onBuildup(Buildup) |
| Drop | onDrop(), onDropEvent(Drop), onDropImpact(float) |
| Equalizer | onEqualizer(const Equalizer&) |
To create your own detector that integrates with AudioProcessor:
AudioDetectorWire your detector into a manual update loop (see the Multi-Detector example above), or use it standalone:
AudioProcessor includes a three-stage signal conditioning pipeline. Each stage can be enabled/disabled independently.
SignalConditionerConfig — Cleans raw I2S/PCM data:
| Field | Default | Description |
|---|---|---|
enableDCRemoval | true | Remove DC offset via running-average high-pass filter |
enableSpikeFilter | true | Reject I2S glitch samples beyond threshold |
enableNoiseGate | true | Hysteresis-based noise gate |
spikeThreshold | 10000 | Absolute sample value beyond which samples are rejected |
noiseGateOpenThreshold | 500 | Signal must exceed this to open the gate |
noiseGateCloseThreshold | 300 | Signal must fall below this to close the gate |
dcRemovalAlpha | 0.99f | Time constant (higher = slower DC adaptation) |
AutoGainConfig — Adaptive gain using PI controller with peak envelope tracking (WLED-style):
| Field | Default | Description |
|---|---|---|
preset | AGCPreset_Normal | Behavior preset: Normal, Vivid, Lazy, or Custom |
minGain | 1/64 | Minimum gain multiplier |
maxGain | 32.0f | Maximum gain multiplier |
targetRMSLevel | 8000.0f | Target RMS level after gain (0-32767) |
peakDecayTau | 3.3f | Peak envelope decay (seconds, Custom only) |
kp | 0.6f | PI proportional gain (Custom only) |
ki | 1.7f | PI integral gain (Custom only) |
gainFollowSlowTau | 12.3f | Slow gain-follow tau (seconds, Custom only) |
gainFollowFastTau | 0.38f | Fast gain-follow tau (seconds, Custom only) |
AGC Presets:
| Parameter | Normal | Vivid | Lazy |
|---|---|---|---|
| peakDecayTau | 3.3s | 1.3s | 6.7s |
| kp | 0.6 | 1.5 | 0.65 |
| ki | 1.7 | 1.85 | 1.2 |
| gainFollowSlowTau | 12.3s | 8.2s | 16.4s |
| gainFollowFastTau | 0.38s | 0.26s | 0.51s |
NoiseFloorTrackerConfig — Adaptive noise floor with hysteresis:
| Field | Default | Description |
|---|---|---|
decayRate | 0.99f | How slowly the floor decays (higher = more stable) |
attackRate | 0.001f | How quickly the floor rises when signal is low |
hysteresisMargin | 100.0f | Floor must drop by this before it can rise again |
minFloor | 10.0f | Prevents floor from reaching zero |
maxFloor | 5000.0f | Prevents floor from growing unbounded |
crossDomainWeight | 0.3f | Blend of time-domain (0.0) vs frequency-domain (1.0) |
| Detector | FFT? | History? | Key Callbacks | Key Polling |
|---|---|---|---|---|
| BeatDetector | Yes | No | onBeat(), onOnset(float) | isBeat(), getBeatConfidence() |
| TempoAnalyzer | No | No | onTempo(float), onTempoStable() | getTempoBPM(), isTempoStable() |
| EqualizerDetector | Yes | No | onEqualizer(const Equalizer&) | getEqBass(), getEqMid(), getEqTreble(), getEqVolume(), getEqZcf(), getEqBin(int) |
| FrequencyBands | Yes | No | onBass(float), onMid(float), onTreble(float) | getBassLevel(), getMidLevel(), getTrebleLevel() |
| EnergyAnalyzer | No | No | onEnergy(float), onPeak(float) | getEnergy(), getPeakLevel() |
| TransientDetector | Yes | No | onTransient(), onAttack(float) | isTransient(), getTransientStrength() |
| SilenceDetector | No | No | onSilenceStart(), onSilenceEnd() | isSilent(), getSilenceDuration() |
| DynamicsAnalyzer | No | No | onCrescendo(), onDiminuendo() | isCrescendo(), isDiminuendo() |
| PitchDetector | Yes | No | onPitch(float), onVoiced(u8) | getPitch(), isVoiced() |
| NoteDetector | Yes | No | onNoteOn(u8, u8), onNoteOff(u8) | getCurrentNote(), isNoteActive() |
| DownbeatDetector | No | No | onDownbeat(), onMeasureBeat(u8) | isDownbeat(), getCurrentBeatNumber() |
| BackbeatDetector | No | No | onBackbeat(u8, float, float) | getBackbeatConfidence() |
| VocalDetector | Yes | No | onVocalStart(), onVocalEnd() | isVocalActive(), getVocalConfidence() |
| PercussionDetector | Yes | No | onKick(), onSnare(), onHiHat(), onTom() | isKick(), isSnare(), isHiHat(), isTom() |
| ChordDetector | Yes | Yes | onChord(Chord), onChordChange(Chord) | hasChord(), getChordConfidence() |
| KeyDetector | Yes | Yes | onKey(Key), onKeyChange(Key) | hasKey(), getKeyConfidence() |
| MoodAnalyzer | Yes | Yes | onMood(Mood), onValenceArousal(float, float) | getMoodValence(), getMoodArousal() |
| BuildupDetector | Yes | No | onBuildupStart(), onBuildupPeak() | isBuilding(), getBuildupProgress() |
| DropDetector | Yes | No | onDrop(), onDropImpact(float) | getDropImpact() |
| VibeDetector | Yes | No | onVibeLevels(const VibeLevels&), onVibeBassSpike(), onVibeMidSpike(), onVibeTrebSpike() | getVibeBass(), getVibeMid(), getVibeTreb(), getVibeVol(), isVibeBassSpike() |
| Platform | Microphone | Configuration |
|---|---|---|
| ESP32 | INMP441 (I2S) | AudioConfig::CreateInmp441(ws, sd, clk, channel) |
| ESP32 | PDM mic | AudioConfig(AudioConfigPdm(din, clk, i2s_num)) |
| Teensy | I2S mic | AudioConfig::CreateTeensyI2S(port, channel) |