FastLED 3.10.6
Loading...
Searching...
No Matches
MoodRing.ino
Go to the documentation of this file.
1// @filter: (memory is large)
2
32
33// Use SPI-based WS2812 driver instead of RMT on ESP32
34#define FASTLED_ESP32_USE_CLOCKLESS_SPI
35
36// FastLED.h must be included first to trigger precompiled headers for FastLED's
37// build system
38#include "FastLED.h"
39
40#if defined(FL_IS_TEENSY)
41// Keep fbuild's library scanner aware of PJRC Audio sources for Teensy.
42#include <Audio.h>
43#endif
44
46#include "fl/stl/stdio.h"
47#include "fl/ui/ui.h"
48
49#include "auto_brightness.h"
50#include "mood_painter.h"
51#include "ring_screenmap.h"
52#include "room_sense.h"
53
54FASTLED_TITLE("MoodRing");
55
56#define NUM_LEDS 244
57
58#ifndef PIN_DATA
59#define PIN_DATA 3 // ESP32C6 has this random pin available on the break out.
60#endif // PIN_DATA
61
62#define BRIGHTNESS 8
63
64// The ScreenMap only positions LEDs for the web preview; the painter draws
65// directly on the 1D ring. 16x16 is the preview grid, not a render target.
66#define GRID_WIDTH 16
67#define GRID_HEIGHT 16
68
69// 0.15 cm or 1.5mm -- appropriate for a dense LED rope.
70#define LED_DIAMETER 0.15f
71
73
76
77// Title comes from FASTLED_TITLE above -- do not declare a second fl::UITitle.
79 "A ring that listens to the room and lights the room back. Grant mic "
80 "access, or drag in a .wav.");
81
82// Brightness.
83fl::UISlider brightness("Brightness", BRIGHTNESS, 0, 255, 1);
84fl::UICheckbox autoBrightness("Auto Brightness", true);
85fl::UISlider autoBrightnessMax("Auto Brightness Max", 84, 0, 255, 1);
86fl::UISlider autoBrightnessLowThreshold("Auto Brightness Low Threshold", 8, 0,
87 100, 1);
88fl::UISlider autoBrightnessHighThreshold("Auto Brightness High Threshold", 22,
89 0, 100, 1);
90
91// Listening. On by default: a room-sensing toy that boots deaf is not the
92// product.
93fl::UIAudio audio("Audio Input");
94fl::UICheckbox listen("Listen", true);
95fl::UISlider presenceReleaseMs("Settle Into Calm (ms)", 900, 200, 4000, 50);
96fl::UISlider grooveAttackMs("Trust A Beat After (ms)", 800, 100, 4000, 50);
97fl::UISlider grooveEnter("Groove Enter Confidence", 0.60f, 0.1f, 1.0f, 0.01f);
98fl::UISlider grooveExit("Groove Exit Confidence", 0.35f, 0.0f, 0.9f, 0.01f);
99fl::UISlider punchGain("Punch Gain", 1.5f, 0.0f, 3.0f, 0.05f);
100
101// Painting. Zeroing a layer gain is how you check the other two in
102// isolation; turning trails and pulses off leaves the raw mix.
103fl::UISlider breathGain("Layer: Breath", 1.0f, 0.0f, 1.0f, 0.05f);
104fl::UISlider flowGain("Layer: Flow", 1.0f, 0.0f, 1.0f, 0.05f);
105fl::UISlider grooveGain("Layer: Groove", 1.0f, 0.0f, 1.0f, 0.05f);
106fl::UICheckbox trails("Trails", true);
107fl::UISlider trailOverride("Trail Keep Override", -1.0f, -1.0f, 0.98f, 0.02f);
108fl::UICheckbox pulses("Beat Pulses", true);
109fl::UISlider pulseOrigin("Pulse Origin (turns)", 0.0f, 0.0f, 1.0f, 0.01f);
110fl::UICheckbox debugPrint("Debug: Print Sense", false);
111
112// Wiring (initialized in setup).
116bool gAutoPump = false;
117
118void setup() {
119 Serial.begin(115200);
120
121 FastLED.addLeds<WS2812, PIN_DATA>(leds, NUM_LEDS)
122 .setCorrection(TypicalLEDStrip)
123 .setScreenMap(screenmap);
124 FastLED.setBrightness(brightness.value());
125
126 // Route audio through FastLED.add() for auto-pump when available.
127 // gAutoPump may only be true when FastLED.add() returned a live
128 // processor; otherwise loop() would skip the manual pump and the
129 // listener would never see a sample.
130 auto input = audio.audioInput();
131 if (input) {
132 gProcessor = FastLED.add(input);
133 if (gProcessor) {
134 gAutoPump = true;
135 fl::printf("MoodRing: audio routed via FastLED.add() (auto-pump)\n");
136 }
137 }
138 if (!gProcessor) {
140 gAutoPump = false;
141 fl::printf("MoodRing: audio using manual pump (fallback)\n");
142 }
143
145 gListener->begin();
146
147 Serial.println("MoodRing setup complete");
148}
149
150static void applyUiTuning() {
153 lt.grooveAttackMs = grooveAttackMs.value();
154 lt.grooveEnter = grooveEnter.value();
155 lt.grooveExit = grooveExit.value();
156 lt.punchGain = punchGain.value();
157
159 pt.breathGain = breathGain.value();
160 pt.flowGain = flowGain.value();
161 pt.grooveGain = grooveGain.value();
162 pt.trails = trails.value();
163 pt.trailOverride = trailOverride.value();
164 pt.pulses = pulses.value();
165 pt.pulseOrigin = pulseOrigin.value();
166}
167
168static void printSense(const mood_ring::RoomSense &s) {
169 fl::printf("MoodRing %s c/f/g=%.2f/%.2f/%.2f pres=%.2f en=%.2f tr=%+.2f "
170 "punch=%.2f shim=%.2f bands=%.2f/%.2f/%.2f bpm=%.0f conf=%.2f "
171 "warm=%.2f ar=%.2f pulses=%d\n",
172 gListener->regimeName(), s.calm, s.flow, s.groove, s.presence,
173 s.energy, s.trend, s.punch, s.shimmer, s.low, s.mid, s.high, s.bpm,
175 gPainter.activePulseCount());
176}
177
178void loop() {
179 const fl::u32 now = millis();
180
181 // Manual audio pump fallback (e.g. WASM / when FastLED.add() didn't take).
182 if (!gAutoPump) {
183 fl::audio::Sample sample = audio.next();
184 if (sample.isValid() && listen.value()) {
185 gProcessor->update(sample);
186 }
187 }
188
190
191 // Listen, then paint. With listening off every signal eases to calm at
192 // its normal release rate, so the sketch fades into a slow ambient lamp
193 // rather than cutting or going dark.
194 if (listen.value()) {
195 gListener->update(now);
196 } else {
197 gListener->rest(now);
198 }
199 const mood_ring::RoomSense &sense = gListener->sense();
200 gPainter.draw(sense, now, leds);
201
202 // Log regime changes so the listener is observable in the field.
203 static const char *sLastRegime = "";
204 const char *regime = gListener->regimeName();
205 if (regime != sLastRegime) {
206 sLastRegime = regime;
207 fl::printf("MoodRing: regime -> %s\n", regime);
208 }
209 if (debugPrint.value()) {
210 static fl::u32 sFrame = 0;
211 if ((sFrame++ % 30) == 0) printSense(sense);
212 }
213
214 // Brightness.
215 uint8_t finalBrightness;
216 if (autoBrightness.value()) {
217 const float avg = getAverageBrightness(leds);
218 finalBrightness = applyBrightnessCompression(
219 avg, static_cast<uint8_t>(autoBrightnessMax.value()),
222 } else {
223 finalBrightness = static_cast<uint8_t>(brightness.value());
224 }
225 FastLED.setBrightness(finalBrightness);
226 FastLED.show();
227}
CRGB leds[1]
fl::UIAudio audio("Audio Input")
#define NUM_LEDS
fl::UIDescription description("Demo of the Animatrix effects. @author of fx is StefanPetrick")
#define PIN_DATA
#define LED_DIAMETER
fl::UISlider brightness("Brightness", BRIGHTNESS, 0, 255)
#define BRIGHTNESS
uint8_t applyBrightnessCompression(float inputBrightnessPercent, uint8_t maxBrightness, float lowThreshold, float highThreshold)
float getAverageBrightness(fl::span< const CRGB > leds)
fl::ScreenMap makeRingScreenMap(int numLeds, int gridWidth, int gridHeight, float diameter)
#define GRID_WIDTH
fl::ScreenMap screenmap
#define GRID_HEIGHT
fl::UISlider autoBrightnessLowThreshold("Auto Brightness Low Threshold", 8, 0, 100, 1)
fl::UICheckbox autoBrightness("Auto Brightness", true)
fl::UISlider autoBrightnessHighThreshold("Auto Brightness High Threshold", 22, 0, 100, 1)
fl::UISlider autoBrightnessMax("Auto Brightness Max", 84, 0, 255, 1)
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
fl::UIAudio audio("Audio Input")
fl::UICheckbox pulses("Beat Pulses", true)
fl::UISlider pulseOrigin("Pulse Origin (turns)", 0.0f, 0.0f, 1.0f, 0.01f)
fl::UISlider presenceReleaseMs("Settle Into Calm (ms)", 900, 200, 4000, 50)
fl::shared_ptr< mood_ring::RoomListener > gListener
Definition MoodRing.ino:114
mood_ring::MoodPainter gPainter
Definition MoodRing.ino:115
fl::UICheckbox listen("Listen", true)
fl::UISlider grooveGain("Layer: Groove", 1.0f, 0.0f, 1.0f, 0.05f)
void setup()
Definition MoodRing.ino:118
fl::UISlider brightness("Brightness", BRIGHTNESS, 0, 255, 1)
fl::UISlider grooveEnter("Groove Enter Confidence", 0.60f, 0.1f, 1.0f, 0.01f)
fl::UISlider trailOverride("Trail Keep Override", -1.0f, -1.0f, 0.98f, 0.02f)
bool gAutoPump
Definition MoodRing.ino:116
fl::UISlider breathGain("Layer: Breath", 1.0f, 0.0f, 1.0f, 0.05f)
fl::UISlider autoBrightnessLowThreshold("Auto Brightness Low Threshold", 8, 0, 100, 1)
fl::UICheckbox autoBrightness("Auto Brightness", true)
static void applyUiTuning()
Definition MoodRing.ino:150
fl::shared_ptr< fl::audio::Processor > gProcessor
Definition MoodRing.ino:113
fl::UICheckbox trails("Trails", true)
fl::UICheckbox debugPrint("Debug: Print Sense", false)
fl::UISlider grooveExit("Groove Exit Confidence", 0.35f, 0.0f, 0.9f, 0.01f)
fl::UISlider autoBrightnessHighThreshold("Auto Brightness High Threshold", 22, 0, 100, 1)
fl::UISlider punchGain("Punch Gain", 1.5f, 0.0f, 3.0f, 0.05f)
static void printSense(const mood_ring::RoomSense &s)
Definition MoodRing.ino:168
FASTLED_TITLE("MoodRing")
fl::UISlider autoBrightnessMax("Auto Brightness Max", 84, 0, 255, 1)
fl::UISlider flowGain("Layer: Flow", 1.0f, 0.0f, 1.0f, 0.05f)
fl::UISlider grooveAttackMs("Trust A Beat After (ms)", 800, 100, 4000, 50)
void loop()
Definition MoodRing.ino:178
@ TypicalLEDStrip
Typical values for SMD5050 LEDs.
Definition color.h:15
fl::CRGB CRGB
Definition crgb.h:25
void printf(const char *format) FL_NO_EXCEPT
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
Definition shared_ptr.h:414
float shimmer
treble transient
Definition room_sense.h:42
float punchGain
scales the bass transient
Definition room_sense.h:68
float arousal
0 calm .. 1 energetic
Definition room_sense.h:55
float punch
bass transient (a hit, not loudness)
Definition room_sense.h:41
float grooveAttackMs
how slowly we start trusting a beat
Definition room_sense.h:64
float presenceReleaseMs
how slowly silence settles in
Definition room_sense.h:63
float energy
smoothed loudness
Definition room_sense.h:39
float trend
[-1, 1] falling .. rising
Definition room_sense.h:40
float grooveExit
confidence where groove is fully off
Definition room_sense.h:67
float presence
sound is present (silence -> 0)
Definition room_sense.h:33
float warmth
0 cool .. 1 warm, from valence
Definition room_sense.h:54
float grooveEnter
confidence where groove is fully on
Definition room_sense.h:66
float trailOverride
< 0 follows the regime blend
Everything a visual is allowed to react to.
Definition room_sense.h:31
Everything the UI is allowed to tune on the listening side.
Definition room_sense.h:61
unsigned char uint8_t
Definition stdint.h:208
#define Serial
Definition serial.h:304
Aggregator header for the fl/ui/ family of per-element UI types.