FastLED 3.9.15
Loading...
Searching...
No Matches
ElPanelReactive.ino
// Two EL panels on D2 and D1 driven at 50 Hz PWM.
// Auto-gain via Vibe detector's self-normalizing bass analysis:
// - Bass level centers around 1.0 (long-term EMA normalization)
// - Above 1.0 = louder than average → panels respond
// - Intense parts naturally attenuate, quiet parts are amplified
// - Fast attack from asymmetric smoothing preserves transients
// Per-panel thresholds keep the two panels at staggered levels:
// - Panel 1 (D2): higher threshold + squared signal → only strong hits
// - Panel 2 (D1): lower threshold + linear signal → more responsive
// @filter: (mem is large)
#include <FastLED.h>
#if defined(FL_IS_TEENSY)
// Keep fbuild's library scanner aware of PJRC Audio sources for Teensy.
#include <Audio.h>
#endif
#include "el_panel.h"
#include "fl/math/math.h"
#include "fl/ui/ui.h"
// ---------------------------------------------------------------------------
// UI
// ---------------------------------------------------------------------------
fl::UITitle title("ElPanel");
fl::UIDescription description("A visualizer for ElPanel, in wasm mode it shows as 2 sets of 4 dots representing the panels. In real device mode it drives EL panel PWM at 50Hz");
fl::UIAudio audio_ui("Audio Input");
fl::UISlider sensitivity("Sensitivity", 1.5f, 0.3f, 4.0f, 0.1f);
// Panel 1 controls
fl::UISlider threshold1("Threshold", 0.54f, 0.0f, 1.0f, 0.01f);
fl::UISlider attack1("Attack", 0.081f, 0.001f, 0.5f, 0.005f);
fl::UISlider decay1("Decay", 0.3f, 0.01f, 1.0f, 0.01f);
// Panel 2 controls
fl::UISlider threshold2("Threshold", 0.94f, 0.0f, 1.0f, 0.01f);
fl::UISlider attack2("Attack", 0.081f, 0.001f, 0.5f, 0.005f);
fl::UISlider decay2("Decay", 0.3f, 0.01f, 1.0f, 0.01f);
// ---------------------------------------------------------------------------
// Attack/Decay filters
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// State
// ---------------------------------------------------------------------------
static bool isSilent = false;
static uint32_t lastMillis = 0;
static uint32_t lastAudioMs = 0; // last time onVibeLevels fired
static const uint32_t kAudioTimeoutMs = 150; // decay if no audio for this long
void setup() {
Serial.begin(115200);
auto audio = FastLED.add(audio_ui);
if (audio) {
audio->onSilence([&](u8 silent) {
isSilent = (silent != 0);
if (isSilent) {
filterHigh.reset();
filterLow.reset();
}
});
audio->onVibeLevels(
[&](const fl::audio::detector::VibeLevels &levels) {
if (isSilent) return;
// Vibe's self-normalizing bass: ~1.0 = average level.
// Subtract 1.0 so silence/average → 0, beats → positive.
float signal = (levels.bass - 1.0f) * sensitivity.value();
signal = fl::clamp(signal, 0.0f, 1.0f);
uint32_t now = millis();
float dt = (now - lastMillis) / 1000.0f;
lastMillis = now;
// Apply UI-driven attack/decay settings
filterHigh.setAttackTau(attack1.value());
filterHigh.setDecayTau(decay1.value());
filterLow.setAttackTau(attack2.value());
filterLow.setDecayTau(decay2.value());
// Per-panel thresholds keep staggered levels.
// Remap [threshold..1] → [0..1], clamped.
float t1 = threshold1.value();
float sig1 = fl::map_range_clamped(signal, t1, 1.0f, 0.0f, 1.0f);
float t2 = threshold2.value();
float sig2 = fl::map_range_clamped(signal, t2, 1.0f, 0.0f, 1.0f);
// Panel 1: stronger signal (squared for contrast)
filterHigh.update(sig1 * sig1, dt);
// Panel 2: more responsive (linear)
filterLow.update(sig2, dt);
});
}
}
void loop() {
// Decay filters when audio is silent OR paused (no callbacks arriving).
uint32_t now = millis();
bool audioTimedOut = (now - lastAudioMs) > kAudioTimeoutMs;
if (isSilent || audioTimedOut) {
float dt = (now - lastMillis) / 1000.0f;
lastMillis = now;
filterHigh.update(0.0f, dt);
filterLow.update(0.0f, dt);
}
}
void setup()
void loop()
fl::UIAudio audio("Audio Input")
fl::UIDescription description("Demo of the Animatrix effects. @author of fx is StefanPetrick")
fl::UITitle title("Animartrix")
static uint32_t lastAudioMs
fl::UISlider threshold2("Threshold", 0.94f, 0.0f, 1.0f, 0.01f)
fl::UISlider attack2("Attack", 0.081f, 0.001f, 0.5f, 0.005f)
static fl::AttackDecayFilter< float > filterHigh(0.081f, 0.3f)
static fl::AttackDecayFilter< float > filterLow(0.081f, 0.3f)
fl::UIGroup group1("Panel 1", threshold1, attack1, decay1)
static bool isSilent
fl::UISlider decay1("Decay", 0.3f, 0.01f, 1.0f, 0.01f)
fl::UISlider sensitivity("Sensitivity", 1.5f, 0.3f, 4.0f, 0.1f)
static const uint32_t kAudioTimeoutMs
fl::UIAudio audio_ui("Audio Input")
fl::UISlider threshold1("Threshold", 0.54f, 0.0f, 1.0f, 0.01f)
fl::UISlider decay2("Decay", 0.3f, 0.01f, 1.0f, 0.01f)
static uint32_t lastMillis
fl::UISlider attack1("Attack", 0.081f, 0.001f, 0.5f, 0.005f)
fl::UIGroup group2("Panel 2", threshold2, attack2, decay2)
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
void setPanelHigh(float brightness)
Definition el_panel.cpp:53
void initPanels()
Definition el_panel.cpp:44
void showPanels()
Definition el_panel.cpp:61
void setPanelLow(float brightness)
Definition el_panel.cpp:57
unsigned char u8
Definition stdint.h:131
fl::u32 uint32_t
Definition s16x16x4.h:219
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
FASTLED_FORCE_INLINE U map_range_clamped(T value, T in_min, T in_max, U out_min, U out_max) FL_NOEXCEPT
Definition math.h:186
constexpr enable_if< is_fixed_point< T >::value, T >::type clamp(T x, T lo, T hi) FL_NOEXCEPT
#define Serial
Definition serial.h:304
Aggregator header for the fl/ui/ family of per-element UI types.