FastLED 3.10.6
Loading...
Searching...
No Matches
hydropack.ino
Go to the documentation of this file.
1// FL_AGENT_ALLOW_NEW_EXAMPLE
5
6// @filter: (mem is large) and (platform is esp32*)
7
8// This is deliberately an LED prototype, not an EL renderer. It draws two
9// triangular clusters and a center bar using ordinary addressable LEDs. Two
10// independent threshold analyzers consume the same self-normalized bass input:
11// - center: sensitive - fires on a transient just above the song average
12// - triangles: loud - fire only on a substantially stronger transient
13//
14// Vibe's normalized bass-rise confidence arms music mode and drives the
15// actual real-time response. It is codec-level independent, unlike a fixed
16// raw spectral-flux threshold. Music mode warms up over two seconds of
17// recurring confident beats, with no fixed BPM or tempo-consistency lock.
18// Once music is established, FastLED's Vibe detector tracks its running bass
19// average; relative bass energy is about 1.0 at the current average. A loud
20// hit lights the center, then launches into the triangles.
21
22#include <Arduino.h>
23#include <FastLED.h>
24
25#include "fl/math/math.h"
27#include "fl/ui/ui.h"
28
29// ---------------------------------------------------------------------------
30// Configuration
31// ---------------------------------------------------------------------------
32
33// INMP441 wiring (matches examples/AudioInput):
34// SCK (BCLK) -> GPIO 4, WS (LRCLK) -> GPIO 7, SD (DATA) -> GPIO 8,
35// L/R -> 3.3V (right channel), VDD -> 3.3V, GND -> GND
36#define I2S_WS 7
37#define I2S_SD 8
38#define I2S_CLK 4
39
40#define PIN_PREVIEW 3
41
43constexpr uint8_t kCenterStart = 6;
48constexpr float kDefaultStageThresholdDbSpl = 72.0f;
49constexpr uint32_t kMusicWarmupMs = 2000;
51constexpr uint32_t kMusicHoldMs = 2000;
52constexpr float kMusicModeFadeSeconds = 0.5f;
53constexpr float kMaximumMusicZcf = 0.40f;
54constexpr float kDefaultMinimumBeatConfidence = 0.05f;
55// Locked HydroPack response: the center detects ordinary bass hits; the
56// triangles require a substantially louder hit. Two cascaded envelopes keep
57// the attack sharp while giving the center a slightly longer tail.
58constexpr float kCenterBassThreshold = 1.05f;
59constexpr float kTriangleBassThreshold = 1.65f;
60constexpr float kCenterEnvelopeAttackSeconds = 0.015f;
61constexpr float kCenterEnvelopeFadeSeconds = 0.060f;
62constexpr float kTriangleEnvelopeAttackSeconds = 0.015f;
63constexpr float kTriangleEnvelopeFadeSeconds = 0.040f;
64
66
67// Six LEDs form each triangle and six form the center bar. The layout reads
68// < | > in the WASM screenmap renderer without EL panel/wire primitives.
70 // Left triangle (<)
71 {12.0f, 50.0f}, {22.0f, 42.0f}, {22.0f, 58.0f},
72 {32.0f, 34.0f}, {32.0f, 50.0f}, {32.0f, 66.0f},
73 // Center bar (|)
74 {50.0f, 30.0f}, {50.0f, 38.0f}, {50.0f, 46.0f},
75 {50.0f, 54.0f}, {50.0f, 62.0f}, {50.0f, 70.0f},
76 // Right triangle (>)
77 {88.0f, 50.0f}, {78.0f, 42.0f}, {78.0f, 58.0f},
78 {68.0f, 34.0f}, {68.0f, 50.0f}, {68.0f, 66.0f},
79};
80
83 "HydroPack screenmap must contain every preview LED");
84
88
90
94
95fl::UITitle title("HydroPack Stage Music Launch");
97 "The INMP441 sound-pressure gate warms up for two seconds of confident "
98 "beats before arming music mode. Conversation and one-off noise stay "
99 "dark; musical bass "
100 "fires the center, with stronger hits launching to both triangles.");
101
102fl::UISlider stageThresholdDbSpl("Minimum Music Level (dB SPL)",
103 kDefaultStageThresholdDbSpl, 60.0f, 105.0f,
104 1.0f);
105fl::UISlider minimumBeatConfidence("Minimum Vibe Beat Confidence",
107 0.05f);
108#if defined(FL_IS_WASM)
109fl::UICheckbox streamDetectorFrames("Stream Detector Frames", true);
110#endif
111
112// The two analyzers have independent envelopes but share one bass input.
113// The loud analyzer is released 5 ms after the center, preserving an outward
114// launch without adding a perceptible artificial delay.
115float gCenterLevel = 0.0f;
116float gTriangleLevel = 0.0f;
119float gCenterTrigger = 0.0f;
121float gTriangleTrigger = 0.0f;
126float gBeatConfidence = 0.0f;
130float gMusicModeLevel = 0.0f;
131
133 if (!gAudio) {
134 return;
135 }
136 const fl::audio::Sample &sample = gAudio->getSample();
137 if (!sample.isValid() || sample.pcm().empty()) {
138 return;
139 }
140 gSoundLevelMeter.processBlock(sample.pcm());
141 gSoundPressureDbSpl = static_cast<float>(gSoundLevelMeter.getSPL());
142}
143
145 const fl::audio::Sample &sample = gAudio->getSample();
146 if (!sample.isValid() || sample.zcf() >= kMaximumMusicZcf) {
147 return false;
148 }
149
150 const uint32_t now = millis();
151 if (static_cast<int32_t>(now - gMusicActiveUntilMs) < 0) {
152 // Each qualifying bass beat extends the short music-present hold.
154 return true;
155 }
156
157 if (gMusicWarmupStartedMs == 0 ||
160 }
162
164 return false;
165 }
166
168 return true;
169}
170
172 return static_cast<int32_t>(millis() - gMusicActiveUntilMs) < 0;
173}
174
176 const fl::audio::detector::VibeLevels &levels) {
177 const float bassAverage = gAudio->getVibeBassAtt();
178 if (!levels.bassSpike || bassAverage <= 0.0f) {
179 return 0.0f;
180 }
181 return fl::clamp((levels.bass - bassAverage) / bassAverage, 0.0f, 1.0f);
182}
183
184void fireAnalyzer(float bass, float threshold, float minIntensity,
185 float &trigger) {
186 if (bass <= threshold) {
187 return;
188 }
189 const float intensity = fl::clamp((bass - threshold) / threshold,
190 minIntensity, 1.0f);
191 trigger = fl::max(trigger, intensity);
192}
193
194float applyAttackDecay(float value, float target, float attackSeconds,
195 float decaySeconds, float deltaSeconds) {
196 const float seconds = target > value ? attackSeconds : decaySeconds;
197 if (seconds <= 0.0f) {
198 return target;
199 }
200 const float amount = fl::clamp(deltaSeconds / seconds, 0.0f, 1.0f);
201 return value + (target - value) * amount;
202}
203
204void advanceDoubleEnvelope(float &trigger, float &stage, float &level,
205 float attackSeconds, float decaySeconds,
206 float deltaSeconds) {
207 // Two cascaded attack/decay filters soften the event into a natural
208 // envelope. Both stages share an intentionally short attack; their decay
209 // is independently adjustable for the center and triangle elements.
210 stage = applyAttackDecay(stage, trigger, attackSeconds, decaySeconds,
211 deltaSeconds);
212 level = applyAttackDecay(level, stage, attackSeconds, decaySeconds,
213 deltaSeconds);
214 trigger = 0.0f;
215}
216
217float advanceLinearFade(float value, float target, float durationSeconds,
218 float deltaSeconds) {
219 if (durationSeconds <= 0.0f) {
220 return target;
221 }
222 const float step = deltaSeconds / durationSeconds;
223 if (target > value) {
224 return fl::min(target, value + step);
225 }
226 return fl::max(target, value - step);
227}
228
229uint8_t toByte(float value) {
230 return static_cast<uint8_t>(fl::clamp(value, 0.0f, 255.0f));
231}
232
234 // Quiet is fully dark. The sensitive center appears first; a loud hit
235 // subsequently drives the two triangle clusters outward from it.
236 const uint8_t centerBrightness =
238 const uint8_t triangleBrightness =
240 const CRGB centerColor(0, centerBrightness / 3, centerBrightness);
241 const CRGB triangleColor(0, triangleBrightness / 3, triangleBrightness);
242
243 for (uint8_t i = 0; i < kTriangleLedCount; ++i) {
244 previewLeds[i] = triangleColor;
245 previewLeds[kRightTriangleStart + i] = triangleColor;
246 }
247 for (uint8_t i = 0; i < kTriangleLedCount; ++i) {
248 previewLeds[kCenterStart + i] = centerColor;
249 }
250}
251
252void setup() {
253 Serial.begin(115200);
254
256 .setScreenMap(previewMap);
257 FastLED.setBrightness(255);
258
259 stageThresholdDbSpl.setGroup("Stage Music Gate");
260 minimumBeatConfidence.setGroup("Stage Music Gate");
261#if defined(FL_IS_WASM)
262 streamDetectorFrames.setGroup("Debug");
263#endif
264
265 // The unified input is browser audio on WASM and INMP441 I2S on ESP32.
266 gAudio = FastLED.add(audioUi);
267 if (!gAudio) {
268 FL_WARN("HydroPack: no audio processor available");
269 return;
270 }
271
272 gAudio->setNoiseFloorTrackingEnabled(true);
273 gAudio->onVibeLevels([](const fl::audio::detector::VibeLevels &levels) {
274 // Feed every block so the calibrated meter observes the actual quiet
275 // floor, rather than only the relatively loud blocks that trigger a
276 // beat callback.
279#if defined(FL_IS_WASM)
280 // Keep this as one compact line per analysis frame. It deliberately
281 // polls both independent rhythm detectors: raw Beat is an onset
282 // detector; Tempo is the recurring-rhythm estimator. Calling these
283 // getters also enables their lazy detector instances for the next
284 // audio frame, so the trace diagnoses the real WASM signal path.
285 if (streamDetectorFrames.value()) {
286 const fl::audio::Sample &sample = gAudio->getSample();
288 "HYDRO frame=%lu spl=%.1f zcf=%.3f silent=%u "
289 "beat=%.3f bpm=%.1f tempo=%.3f tempo_bpm=%.1f "
290 "vibe_bass=%.3f vibe_att=%.3f vibe_conf=%.3f spike=%u "
291 "bass_raw=%.3f gate=%u\\n",
292 static_cast<unsigned long>(millis()), gSoundPressureDbSpl,
293 sample.zcf(), gAudio->isSilent() ? 1U : 0U,
294 gAudio->getBeatConfidence(), gAudio->getBPM(),
295 gAudio->getTempoConfidence(), gAudio->getTempoBPM(),
296 levels.bass, gAudio->getVibeBassAtt(), gBeatConfidence,
297 levels.bassSpike ? 1U : 0U, levels.bassRaw,
298 isMusicModeActive() ? 1U : 0U);
299 }
300#endif
301 if (gBeatConfidence >= minimumBeatConfidence.value() &&
304 }
305 if (!levels.bassSpike ||
308 return;
309 }
312 if (levels.bass > kTriangleBassThreshold) {
316 }
317 });
318}
319
320void loop() {
321 EVERY_N_MILLIS(10) {
322 static uint32_t lastMs = 0;
323 static bool firstFrame = true;
324 const uint32_t now = millis();
325 const uint32_t deltaMs = firstFrame ? 0 : (now - lastMs);
326 firstFrame = false;
327 lastMs = now;
328
329 if (gPendingTriangleLevel > 0.0f &&
330 static_cast<int32_t>(now - gTriangleFireAtMs) >= 0) {
334 }
335
336 if (deltaMs > 0) {
337 const float deltaSeconds = float(deltaMs) * 0.001f;
339 gMusicModeLevel, isMusicModeActive() ? 1.0f : 0.0f,
340 kMusicModeFadeSeconds, deltaSeconds);
344 kCenterEnvelopeFadeSeconds, deltaSeconds);
349 deltaSeconds);
350 }
352 FastLED.show(); // Auto-pumps browser or I2S microphone audio.
353 }
354 delay(1);
355}
fl::UIDescription description("Demo of the Animatrix effects. @author of fx is StefanPetrick")
#define I2S_CLK
#define I2S_SD
#define I2S_WS
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
static Config CreateInmp441(int pin_ws, int pin_sd, int pin_clk, AudioChannel channel, u16 sample_rate=44100ul, int i2s_num=0) FL_NO_EXCEPT
Definition input.h:153
constexpr EOrder GRB
Definition eorder.h:19
fl::UIButton trigger("Trigger")
fl::CRGB CRGB
Definition crgb.h:25
#define EVERY_N_MILLIS(N)
Checks whether to execute a block of code every N milliseconds.
Definition lib8tion.h:1000
float gBeatConfidence
#define PIN_PREVIEW
Definition hydropack.ino:40
constexpr float kDefaultMinimumBeatConfidence
Definition hydropack.ino:54
constexpr uint32_t kMaximumMusicBeatGapMs
Definition hydropack.ino:50
fl::UISlider stageThresholdDbSpl("Minimum Music Level (dB SPL)", kDefaultStageThresholdDbSpl, 60.0f, 105.0f, 1.0f)
CRGB previewLeds[kPreviewLedCount]
Definition hydropack.ino:65
float gCenterEnvelopeStage
void updateSoundLevel()
float gTriangleEnvelopeStage
uint8_t toByte(float value)
constexpr uint8_t kHydroPackLedCount
Definition hydropack.ino:45
constexpr float kTriangleEnvelopeFadeSeconds
Definition hydropack.ino:63
uint32_t gTriangleFireAtMs
constexpr uint8_t kPreviewLedCount
Definition hydropack.ino:46
fl::audio::SoundLevelMeter gSoundLevelMeter
constexpr uint8_t kTriangleLedCount
Definition hydropack.ino:42
float gSoundPressureDbSpl
constexpr float kTriangleBassThreshold
Definition hydropack.ino:59
constexpr uint8_t kRightTriangleStart
Definition hydropack.ino:44
void fireAnalyzer(float bass, float threshold, float minIntensity, float &trigger)
fl::audio::Config audioConfig
Definition hydropack.ino:91
void setup()
uint32_t gLastQualifyingBeatMs
fl::UISlider minimumBeatConfidence("Minimum Vibe Beat Confidence", kDefaultMinimumBeatConfidence, 0.0f, 1.0f, 0.05f)
float gCenterTrigger
fl::ScreenMap makePreviewMap()
Definition hydropack.ino:85
float gCenterLevel
float applyAttackDecay(float value, float target, float attackSeconds, float decaySeconds, float deltaSeconds)
fl::UITitle title("HydroPack Stage Music Launch")
uint32_t gMusicWarmupStartedMs
float getVibeBeatConfidence(const fl::audio::detector::VibeLevels &levels)
float gMusicModeLevel
float gTriangleLevel
constexpr uint8_t kCenterStart
Definition hydropack.ino:43
bool isMusicModeActive()
constexpr float kDefaultStageThresholdDbSpl
Definition hydropack.ino:48
void renderPreview()
fl::ScreenMap previewMap
Definition hydropack.ino:89
constexpr float kMaximumMusicZcf
Definition hydropack.ino:53
constexpr uint32_t kMusicWarmupMs
Definition hydropack.ino:49
constexpr float kCenterEnvelopeFadeSeconds
Definition hydropack.ino:61
float gTriangleTrigger
constexpr uint32_t kTriangleLaunchDelayMs
Definition hydropack.ino:47
const fl::vec2f kHydroPackLedLayout[]
Definition hydropack.ino:69
uint32_t gMusicActiveUntilMs
fl::shared_ptr< fl::audio::Processor > gAudio
constexpr uint32_t kMusicHoldMs
Definition hydropack.ino:51
fl::UIAudio audioUi("Audio Input", audioConfig)
constexpr float kMusicModeFadeSeconds
Definition hydropack.ino:52
float gPendingTriangleLevel
void advanceDoubleEnvelope(float &trigger, float &stage, float &level, float attackSeconds, float decaySeconds, float deltaSeconds)
constexpr float kCenterEnvelopeAttackSeconds
Definition hydropack.ino:60
constexpr float kCenterBassThreshold
Definition hydropack.ino:58
constexpr float kTriangleEnvelopeAttackSeconds
Definition hydropack.ino:62
bool isMusicPresent()
float advanceLinearFade(float value, float target, float durationSeconds, float deltaSeconds)
void loop()
#define FL_WARN(...)
Definition log.h:440
constexpr common_type_t< T, U > max(T a, U b) FL_NO_EXCEPT
Definition math.h:75
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NO_EXCEPT
Definition math.h:71
constexpr enable_if< is_fixed_point< T >::value, T >::type clamp(T x, T lo, T hi) FL_NO_EXCEPT
vec2< float > vec2f
Definition geometry.h:333
void printf(const char *format) FL_NO_EXCEPT
#define FL_STATIC_ASSERT(...)
Portable compile-time assertion wrapper.
unsigned char uint8_t
Definition stdint.h:208
fl::u32 uint32_t
Definition stdint.h:218
fl::i32 int32_t
Definition stdint.h:219
#define Serial
Definition serial.h:304
Aggregator header for the fl/ui/ family of per-element UI types.