cookbook/advanced/audio.md
⚠️ EXPERIMENTAL: This documentation covers experimental audio features currently in development. The FastLED beat detection APIs were introduced in October 2025 and are still evolving. Code examples shown here demonstrate general audio-reactive concepts but may not reflect the final API.
Create LED visualizations that respond to audio input. This guide covers reading audio signals and creating reactive effects.
To read audio, you'll need an analog input connected to a microphone or audio line.
#define MIC_PIN A0
#define SAMPLE_WINDOW 50 // Sample window width in ms
uint16_t readAudioLevel() {
unsigned long startMillis = millis();
uint16_t peakToPeak = 0;
uint16_t signalMax = 0;
uint16_t signalMin = 1024;
// Collect data for sample window
while (millis() - startMillis < SAMPLE_WINDOW) {
uint16_t sample = analogRead(MIC_PIN);
if (sample > signalMax) signalMax = sample;
if (sample < signalMin) signalMin = sample;
}
peakToPeak = signalMax - signalMin;
return peakToPeak;
}
This function measures the peak-to-peak amplitude of the audio signal over a short time window, giving you a reading of the current volume level.
A classic visualization that displays audio level as a growing/shrinking bar.
void vuMeter() {
uint16_t audioLevel = readAudioLevel();
// Map to LED count (0 to NUM_LEDS)
uint8_t numLEDsToLight = map(audioLevel, 0, 1023, 0, NUM_LEDS);
// Clear all
fill_solid(leds, NUM_LEDS, CRGB::Black);
// Light up LEDs based on level
for (int i = 0; i < numLEDsToLight; i++) {
// Color gradient: green -> yellow -> red
uint8_t hue = map(i, 0, NUM_LEDS, 96, 0); // Green to red
leds[i] = CHSV(hue, 255, 255);
}
}
With Peak Hold:
void vuMeterWithPeak() {
static uint8_t peak = 0;
static unsigned long lastPeakTime = 0;
uint16_t audioLevel = readAudioLevel();
uint8_t numLEDsToLight = map(audioLevel, 0, 1023, 0, NUM_LEDS);
// Update peak
if (numLEDsToLight > peak) {
peak = numLEDsToLight;
lastPeakTime = millis();
}
// Peak decay
if (millis() - lastPeakTime > 1000) {
if (peak > 0) peak--;
lastPeakTime = millis();
}
// Draw VU meter
fill_solid(leds, NUM_LEDS, CRGB::Black);
for (int i = 0; i < numLEDsToLight; i++) {
uint8_t hue = map(i, 0, NUM_LEDS, 96, 0);
leds[i] = CHSV(hue, 255, 255);
}
// Draw peak indicator
if (peak < NUM_LEDS) {
leds[peak] = CRGB::White;
}
}
Detect beats/kicks in music for reactive effects.
class BeatDetector {
private:
uint16_t samples[32];
uint8_t sampleIndex = 0;
uint32_t lastBeatTime = 0;
public:
bool detectBeat(uint16_t audioLevel) {
// Store sample
samples[sampleIndex] = audioLevel;
sampleIndex = (sampleIndex + 1) % 32;
// Calculate average
uint32_t sum = 0;
for (int i = 0; i < 32; i++) sum += samples[i];
uint16_t average = sum / 32;
// Detect beat (current level significantly above average)
bool isBeat = (audioLevel > average * 1.5) &&
(millis() - lastBeatTime > 300); // Minimum 300ms between beats
if (isBeat) lastBeatTime = millis();
return isBeat;
}
};
BeatDetector beatDetector;
void beatEffect() {
uint16_t audioLevel = readAudioLevel();
if (beatDetector.detectBeat(audioLevel)) {
// Flash on beat
fill_solid(leds, NUM_LEDS, CRGB::White);
} else {
// Fade out
fadeToBlackBy(leds, NUM_LEDS, 10);
}
}
Color Change on Beat:
void beatColorChange() {
static uint8_t currentHue = 0;
uint16_t audioLevel = readAudioLevel();
if (beatDetector.detectBeat(audioLevel)) {
currentHue += 32; // Change color on beat
}
// Fade to current color
CRGB targetColor = CHSV(currentHue, 255, 255);
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = blend(leds[i], targetColor, 50);
}
}
Pulse on Beat:
void beatPulse() {
static uint8_t pulseIntensity = 0;
uint16_t audioLevel = readAudioLevel();
if (beatDetector.detectBeat(audioLevel)) {
pulseIntensity = 255;
}
// Draw pulsing effect
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CHSV(160, 255, pulseIntensity);
}
// Fade out
if (pulseIntensity > 0) pulseIntensity -= 5;
}
For more sophisticated audio reactivity, use FFT (Fast Fourier Transform) to analyze frequency bands. This requires additional libraries like:
Example concept (requires FFT library):
// Pseudo-code - requires FFT library
void frequencyBands() {
// Perform FFT on audio samples
// Extract bass, mid, treble
uint8_t bass = getBassLevel(); // 0-255
uint8_t mid = getMidLevel(); // 0-255
uint8_t treble = getTrebleLevel(); // 0-255
// Map to different parts of strip
int bassLEDs = NUM_LEDS / 3;
int midLEDs = NUM_LEDS / 3;
int trebleLEDs = NUM_LEDS / 3;
// Bass: red
fill_solid(&leds[0], bassLEDs, CRGB::Red);
fadeToBlackBy(&leds[0], bassLEDs, 255 - bass);
// Mid: green
fill_solid(&leds[bassLEDs], midLEDs, CRGB::Green);
fadeToBlackBy(&leds[bassLEDs], midLEDs, 255 - mid);
// Treble: blue
fill_solid(&leds[bassLEDs + midLEDs], trebleLEDs, CRGB::Blue);
fadeToBlackBy(&leds[bassLEDs + midLEDs], trebleLEDs, 255 - treble);
}
Adjust sensitivity based on your audio source:
// For quiet environments
uint16_t calibratedLevel = map(audioLevel, 0, 200, 0, 1023);
// For loud environments
uint16_t calibratedLevel = map(audioLevel, 100, 800, 0, 1023);
Smooth audio readings to avoid jitter:
uint16_t smoothAudio(uint16_t newValue) {
static uint16_t lastValue = 0;
lastValue = (lastValue * 7 + newValue) / 8; // Exponential smoothing
return lastValue;
}
Audio processing can be CPU-intensive:
No audio response:
Too sensitive/not sensitive enough:
Jerky motion:
False beat detection:
Next Steps: