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
- LED strip and pixel planner Electronicsplan the strips, supply, wire and fuses there, then load the saved layout here to run effects on it
- Peripheral protocols, simulated IoT and embeddeddrive WS2812 and APA102 from an MCU peripheral (SPI, RMT, PIO) and check the waveform against the datasheet
- Bus protocols, simulated IoT and embeddedthe WS2812, SK6812 and APA102 bit timing behind the frame rates on the Hardware tab
- ESPHome config builder IoT and embeddedthe rest of the ESPHome device around the addressable_lambda light exported here
- Logic levels and level shifting Electronicswhy 3.3 V logic is marginal for a 5 V WS2812 input, and how to choose a level shifter
- Virtual microcontroller IoT and embeddedrun the exported NeoPixel sketch, compiled for the Uno, on an emulated Arduino with a decoded LED strip
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
-
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.
-
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. -
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.
-
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_PALETTEdoes. It also gives you the C++ for it. -
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. -
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 apresets.json. -
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.
-
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)orCHSV(h, s, v);CRGB.Red,CRGB(0xFF8800). JavaScript has no operator overloading, soleds[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,HEIGHTandXY(x, y)for grids.XY()returnsNUM_LEDSfor 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]andradii[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, …).paletteis the one chosen on the page. -
audio.level,.bass,.mid,.treble,.beatand.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_striplight with anaddressable_lambdaeffect whose C++ lives inled_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/stateJSON, an HTTP API link, and apresets.jsonof all of them. WLED runs its own code for each, so the result is similar, not identical. A custom palette exports aspalette0.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
.hexfiles, 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.hexthere.
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.
Feedback is a gift. Something to improve, a feature to add, or a bug? Tell me.
Runs entirely in your browser; nothing you type leaves this page.