2. Compiler & VM
Why the old approach was slow
WebFastLed parsed your pattern expression character-by-character on every pixel of every
frame. At 60 LEDs and 30 FPS, that's roughly 1,800 parse-eval cycles per second. Each one
dragged in heap allocations for temporary token strings and std::map lookups. On a
microcontroller, that adds up: memory fragments, the allocator stalls, frames drop.
How Ember fixes it
Ember ships a single-pass Pratt-style compiler baked directly into the firmware.
When you save a pattern it compiles once into a flat bytecode array, stashes numeric
literals in a de-duplicated float pool, and resolves variable names to
uint8_t register indices. After that the render loop just executes
opcodes against pre-allocated stack buffers, no malloc, no free, no surprises.
3. Concurrency on a Single Core
The ESP32-C3 is a single-core chip, so we have to share nicely. Ember uses a small FreeRTOS task schedule to make sure everything stays responsive:
RenderTask(Priority 2) — This is the heart of the show. It stays locked to 60Hz and doesn't get interrupted by the networking layer.AsyncTCP Worker(Priority 3) — This runs slightly above the renderer. It handles web requests, meaning the dashboard feels snappy, and it only steals a few microseconds to do its job.- Atomic pointer swap — When you update a pattern, it doesn't break the current
frame. It just atomically replaces a
std::atomic<Program*>pointer that the renderer grabs at the top of the next cycle.
4. Audio & FFT
Ember has two mic options depending on your build: a digital INMP441 MEMS mic over I2S (cleaner signal, better for music), or an analog MAX4466 electret mic through ADC1 (cheaper, easier to wire up). Either way, the processing pipeline is the same:
- A 256-point Hamming-windowed FFT runs via
arduinoFFTin a background task. - Raw frequency bins get compressed into 8 logarithmic bands that map well to human hearing: bass, mids, and treble don't all fight for the same bucket.
- The results feed into pattern builtins you can call directly:
vu(),beat(),bass(),mid(),treble(), andband(n)for per-band access. - The simulator above supports both a sequencer simulation and your actual browser microphone, so you can prototype audio-reactive patterns without touching hardware.