Chris Collins

Tools · embedded

LED effect studio

Write and preview addressable-LED effects on strips, rings, matrices and LED planner layouts with a FastLED-style JavaScript API and 28 built-ins (Fire2012, plasma, noise, meteor, bouncing balls, matrix rain, audio-reactive), then export a FastLED or NeoPixel sketch, ESPHome YAML or WLED presets, stream live to WLED or over Web Serial, and check chip timing, level shifting and power.

Added

Works with

How to use it

Every LED project I build ends with the same loop: tweak an effect, compile, upload, squint at the strip, repeat. This page shortens it. Effects are written in JavaScript against the FastLED API — CHSV, beatsin8, fadeToBlackBy, palettes, inoise8 — and run on a drawing of your actual layout, with the current it would draw. When it looks right, export it as an Arduino sketch, an ESPHome light or a WLED preset, or send it straight to the LEDs.

How to use it

  1. Pick an Effect on the right. The built-ins cover the classics (rainbow, theatre chase, comet, Larson scanner, twinkle, Fire2012, meteor rain, bouncing balls, colour wipe, breathe, Pride 2015), FastLED’s DemoReel100 (sinelon, juggle, BPM, confetti), 2-D ones that work on any layout (plasma, noise flow, ripples, matrix rain, radar, pinwheel, rainbow sweep), three audio-reactive ones, and a wiring check. SPEED and INTENSITY are the two sliders every effect reads, as in WLED.

  2. Set the layout on the Layout tab: a strip, a ring (or a stack of rings such as 24, 16, 12, 8, 1), a matrix (serpentine or progressive, the corner of the first LED, rows or columns, rotation), several segments chained in data order (one per line: strip 30, ring 24 at 34 -4, matrix 8x8 at 44 0), or a layout from the LED planner. Tick indices and wiring under the preview, and run Wiring check, to see the data order.

  3. Change the code on the Effect code tab and press Run (Ctrl/⌘-Enter). Errors show with their line. Your version of a built-in is kept in this browser; Revert brings the original back.

  4. Choose a palette, or build one on the Palette tab: stops of position and colour, squeezed into 16 entries exactly as FastLED’s DEFINE_GRADIENT_PALETTE does. It also gives you the C++ for it.

  5. Set the Brightness, Gamma and the power limit. The box under them shows the current the frame draws by FastLED’s own model, the five-second peak, what full white would draw, and how far the limit has pulled the brightness down — the same sums FastLED.setMaxPowerInVoltsAndMilliamps() does on the board.

  6. Export: a FastLED sketch, an Adafruit NeoPixel sketch (for the simple effects), ESPHome YAML with the effect as an addressable_lambda, or WLED JSON, an HTTP API link and a presets.json.

  7. Live output: stream frames to WLED over Wi-Fi, to any board over USB with Web Serial (an Adalight receiver sketch is included), or open the virtual Arduino with a matching NeoPixel strip.

  8. The Hardware tab compares the chips (frame time and the most LEDs per data line at your frame rate), checks the logic level, and plans the power: injection points, supply, capacitor, resistor and fuses.

The API

The effect is a script that defines loop() (and optionally setup()); variables at the top keep their values between frames, like globals in a sketch. The FastLED names are there as functions:

  • leds[i] = CRGB(r, g, b) or CHSV(h, s, v); CRGB.Red, CRGB(0xFF8800). JavaScript has no operator overloading, so leds[i] = c` is written `leds[i].add(c)` (and `.sub`, `.or`, `.and`, `.nscale8()`, `.fadeToBlackBy()`); the exporter turns them back into `= and so on.

  • NUM_LEDS, WIDTH, HEIGHT and XY(x, y) for grids. XY() returns NUM_LEDS for a gap or a point off the grid, which writes to a hidden "safety pixel", the trick FastLED’s XYMatrix example uses. coordsX[i], coordsY[i], angles[i] and radii[i] give every LED’s position scaled to 0–255, so a 2-D effect written with them works on a ring, a matrix or a planner layout alike.

  • lib8tion: scale8, qadd8, qsub8, lerp8by8, map8, sin8, cos8, triwave8, quadwave8, cubicwave8, ease8InOutCubic, beat8, beatsin8, beatsin16, beatsin88, random8, random16, inoise8, millis(), EVERY_N_MILLISECONDS(ms, () ⇒ { … }).

  • fill_solid, fill_rainbow, fill_palette, fill_gradient_RGB, fadeToBlackBy, nscale8, blur1d, ColorFromPalette, CRGBPalette16, HeatColor, blend, nblend, and FastLED’s eight palettes (RainbowColors_p, PartyColors_p, LavaColors_p, …). palette is the one chosen on the page.

  • audio.level, .bass, .mid, .treble, .beat and .bins[0..15] — from the microphone through a Web Audio FFT (16 bands, log-spaced from 60 Hz to 8 kHz), or a built-in 120 BPM demo beat so the audio effects show something without a microphone.

These are ports of FastLED 3.6’s integer code, not look-alikes: CHSV converts with hsv2rgb_rainbow (the "rainbow" hue map with its boosted yellow), scale8 is the FASTLED_SCALE8_FIXED version, sin8 uses the same piecewise-linear table, inoise8 the same Perlin permutation, and the tests check them against FastLED’s own outputs. A JavaScript number is not a byte, though: hue + 1 keeps growing past 255 unless you % 256 it (the FastLED functions and leds[] wrap their arguments, so most code doesn’t notice).

The effect runs in a Web Worker: if a frame takes over two seconds (a loop that never ends) it is stopped and the page carries on.

The preview, gamma and power

LEDs are linear: a WS2812 at 64 of 255 gives a quarter of the light, which looks far brighter than a quarter to the eye. Preview as light shows the frame that way (the values converted from linear light to the screen’s sRGB), so an effect without gamma correction looks washed out here as it does on the strip. Gamma 2.6 applies round(255 · (v/255)^2.6) before the brightness, as WLED and Adafruit’s gamma8 table do; the exported sketch carries the same table.

The current is FastLED’s model from power_mgt.cpp: 16, 11 and 15 mA for full red, green and blue per pixel and 1 mA idle, at 5 V, applied after gamma and brightness. The power limit computes the brightness exactly as calculate_max_brightness_for_power_mW() does. For the 12 V chips the per-pixel figure is scaled to the chip’s (≈15 mA for a WS2815). It is an estimate: measure a metre of your strip at full white before you buy the supply.

Exports and their limits

  • FastLED sketch. The built-ins come with hand-written C. Your own code (or an edited built-in) is *transpiled* line by line: `let`/`const` become `int`, `float` or `CRGB` by a guess at the value, `===` becomes `==`, `Math.floor` becomes `floor`, `CRGB.Red` becomes `CRGB::Red`, `.add(c)` becomes `+= c`, typed arrays become C arrays, and `EVERY_N_MILLISECONDS(ms, () => {…})` becomes the macro. Objects, strings, arrow functions, `for…of` and array methods are not translated, and the page lists every line it was unsure of. Integer division truncates in C and not in JavaScript. Read the result through. The sketch includes the layout’s XY() (a formula for a plain matrix, a table otherwise) and whichever coordinate tables the effect uses. On an AVR (Uno, Nano) mind the 2 KB of RAM.

  • Adafruit NeoPixel versions exist for the effects that don’t need FastLED’s maths (rainbow, chase, comet, scanner, wipe, …). NeoPixel has no power limiting, so the brightness is the only cap.

  • ESPHome: an esp32_rmt_led_strip light with an addressable_lambda effect whose C++ lives in led_effect.h, pulling FastLED in as a library for lib8tion and palettes but leaving the driving to ESPHome. Some built-ins also have a plain lambda with no header.

  • WLED: the effect id, speed, intensity and palette of the closest WLED effect (Fire 2012, Pride 2015, Juggle, BPM, Sinelon, Meteor, Bouncing Balls, Plasma, Twinkle, …) from WLED’s FX.h, as /json/state JSON, an HTTP API link, and a presets.json of all of them. WLED runs its own code for each, so the result is similar, not identical. A custom palette exports as palette0.json (WLED 0.14+). 2-D and audio effect ids move between WLED versions, so once you connect to a controller its own effect list is used to match them by name.

Live output

  • WLED. WLED’s JSON API answers cross-origin requests, so a page served over plain http talks to it directly. This page is served over https, and browsers block https pages from fetching plain http addresses (mixed content); Chrome’s private-network checks can also refuse a public page talking to a 192.168.x.x address. The page detects that case and offers what still works: Open as a link (the older /win&FX=… HTTP API opened as a page, which is a navigation rather than a request, so it isn’t blocked), the JSON to paste into WLED’s preset editor, or running this page locally over http. Streaming sends each frame as {"seg":{"i":[…]}} in chunks of 256 LEDs; that manages 5–15 fps for a few hundred LEDs. For video rates use WLED’s UDP realtime protocols (DDP, E1.31), which a browser cannot send.

  • Web Serial (Adalight). In Chrome or Edge on a desktop, connect a board running the receiver sketch and every frame goes out as an Adalight packet: Ada, the LED count − 1 as two bytes, a checksum (high ^ low ^ 0x55), then R, G, B per LED, with brightness, gamma and the power limit already applied. At 115 200 baud that is 10 bits a byte, so 60 LEDs manage about 60 fps and 300 LEDs about 12. Boards with native USB ignore the baud rate and go much faster.

  • mcu-sim. The virtual Arduino runs compiled .hex files, not source, so this hands over a matching NeoPixel strip on pin D6; compile the NeoPixel or FastLED export for the Uno (pin 6) and open the .hex there.

Hardware notes

The frame rate on one data line is set by the protocol: WS2812B, WS2811, WS2813 and WS2815 send 24 bits at 800 kHz (30 µs a pixel) plus the latch gap (≥ 280 µs on current WS2812B, 80 µs on SK6812), so about 1 100 LEDs is the limit at 30 fps and 550 at 60. SK6812 RGBW sends 32 bits (40 µs). APA102 and SK9822 have a clock line and go as fast as the wiring allows. The level check uses the WS281x input threshold of 0.7 × VDD (3.5 V at 5 V), which 3.3 V logic misses; a 74AHCT125 (TTL inputs, 5 V outputs) or a sacrificial first pixel powered through a diode fixes it. The injection points use the LED planner’s model: a uniformly loaded strip fed at one end drops R′ · i · L² across both rails, and between two feeds s apart the middle drops R′ · i · s² / 4.

Sources: FastLED 3.6 (lib8tion, hsv2rgb.cpp, colorutils.cpp, colorpalettes.cpp, noise.cpp, power_mgt.cpp; MIT licence); Mark Kriegsman’s Fire2012, DemoReel100 and Pride2015; the tweaking4all meteor rain and bouncing balls; WLED’s FX.h and JSON API documentation; Worldsemi WS2812B (V5), WS2811, WS2813, WS2815, Opsco SK6812 and APA102C datasheets; Adafruit’s NeoPixel Überguide (capacitor, resistor and level shifting advice); the Adalight protocol as in Adafruit’s original Adalight sketch.

Sharing

Share link copies a URL that opens this tool with your layout, effect, palette, settings and any code you changed, packed into ?s=. The WLED address is not included.

Browser support

Any current browser runs the editor, the preview and the exports, on a phone too. The microphone needs permission; Web Serial needs Chrome, Edge or Opera on a desktop.

Privacy

Nothing leaves your browser unless you connect it to a WLED controller or a serial port of your own. The microphone is analysed on the page and never recorded or sent. Settings and your code are kept in this browser’s local storage.

Runs entirely in your browser; nothing you type leaves this page.