FastLED 3.9.15
Loading...
Searching...
No Matches
ElPanelReactive.ino
Go to the documentation of this file.
1
4
5// Two EL panels on D2 and D1 driven at 50 Hz PWM.
6// Auto-gain via Vibe detector's self-normalizing bass analysis:
7// - Bass level centers around 1.0 (long-term EMA normalization)
8// - Above 1.0 = louder than average → panels respond
9// - Intense parts naturally attenuate, quiet parts are amplified
10// - Fast attack from asymmetric smoothing preserves transients
11// Per-panel thresholds keep the two panels at staggered levels:
12// - Panel 1 (D2): higher threshold + squared signal → only strong hits
13// - Panel 2 (D1): lower threshold + linear signal → more responsive
14
15// @filter: (mem is large)
16
17#include <FastLED.h>
18#if defined(FL_IS_TEENSY)
19// Keep fbuild's library scanner aware of PJRC Audio sources for Teensy.
20#include <Audio.h>
21#endif
22
23#include "el_panel.h"
25#include "fl/math/math.h"
26#include "fl/ui/ui.h"
27
28// ---------------------------------------------------------------------------
29// UI
30// ---------------------------------------------------------------------------
31fl::UITitle title("ElPanel");
32fl::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");
33fl::UIAudio audio_ui("Audio Input");
34fl::UISlider sensitivity("Sensitivity", 1.5f, 0.3f, 4.0f, 0.1f);
35
36// Panel 1 controls
37fl::UISlider threshold1("Threshold", 0.54f, 0.0f, 1.0f, 0.01f);
38fl::UISlider attack1("Attack", 0.081f, 0.001f, 0.5f, 0.005f);
39fl::UISlider decay1("Decay", 0.3f, 0.01f, 1.0f, 0.01f);
41
42// Panel 2 controls
43fl::UISlider threshold2("Threshold", 0.94f, 0.0f, 1.0f, 0.01f);
44fl::UISlider attack2("Attack", 0.081f, 0.001f, 0.5f, 0.005f);
45fl::UISlider decay2("Decay", 0.3f, 0.01f, 1.0f, 0.01f);
47
48// ---------------------------------------------------------------------------
49// Attack/Decay filters
50// ---------------------------------------------------------------------------
53
54// ---------------------------------------------------------------------------
55// State
56// ---------------------------------------------------------------------------
57static bool isSilent = false;
58static uint32_t lastMillis = 0;
59static uint32_t lastAudioMs = 0; // last time onVibeLevels fired
60static const uint32_t kAudioTimeoutMs = 150; // decay if no audio for this long
61
62void setup() {
63 Serial.begin(115200);
64 initPanels();
65
66 auto audio = FastLED.add(audio_ui);
67 if (audio) {
68 audio->onSilence([&](u8 silent) {
69 isSilent = (silent != 0);
70 if (isSilent) {
71 filterHigh.reset();
72 filterLow.reset();
73 }
74 });
75
76 audio->onVibeLevels(
77 [&](const fl::audio::detector::VibeLevels &levels) {
78 if (isSilent) return;
79 lastAudioMs = millis();
80
81 // Vibe's self-normalizing bass: ~1.0 = average level.
82 // Subtract 1.0 so silence/average → 0, beats → positive.
83 float signal = (levels.bass - 1.0f) * sensitivity.value();
84 signal = fl::clamp(signal, 0.0f, 1.0f);
85
86 uint32_t now = millis();
87 float dt = (now - lastMillis) / 1000.0f;
88 lastMillis = now;
89
90 // Apply UI-driven attack/decay settings
91 filterHigh.setAttackTau(attack1.value());
92 filterHigh.setDecayTau(decay1.value());
93 filterLow.setAttackTau(attack2.value());
94 filterLow.setDecayTau(decay2.value());
95
96 // Per-panel thresholds keep staggered levels.
97 // Remap [threshold..1] → [0..1], clamped.
98 float t1 = threshold1.value();
99 float sig1 = fl::map_range_clamped(signal, t1, 1.0f, 0.0f, 1.0f);
100
101 float t2 = threshold2.value();
102 float sig2 = fl::map_range_clamped(signal, t2, 1.0f, 0.0f, 1.0f);
103
104 // Panel 1: stronger signal (squared for contrast)
105 filterHigh.update(sig1 * sig1, dt);
106 // Panel 2: more responsive (linear)
107 filterLow.update(sig2, dt);
108 });
109 }
110}
111
112void loop() {
113 // Decay filters when audio is silent OR paused (no callbacks arriving).
114 uint32_t now = millis();
115 bool audioTimedOut = (now - lastAudioMs) > kAudioTimeoutMs;
116 if (isSilent || audioTimedOut) {
117 float dt = (now - lastMillis) / 1000.0f;
118 lastMillis = now;
119 filterHigh.update(0.0f, dt);
120 filterLow.update(0.0f, dt);
121 }
122 setPanelHigh(filterHigh.value());
123 setPanelLow(filterLow.value());
124 showPanels();
125}
fl::UIAudio audio("Audio Input")
fl::UIDescription description("Demo of the Animatrix effects. @author of fx is StefanPetrick")
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
void setup()
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::UITitle title("ElPanel")
fl::UIGroup group2("Panel 2", threshold2, attack2, decay2)
void loop()
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
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.