Chris Collins

Tools · embedded

Boing Lamp effect lab

Write effects for the Boing Ball lamp in JavaScript, the way the firmware's are written — render(ctx, out) over the lamp's 128 cells — watch them on a 3-D ball with the lamp's real cells and on a flat map, stream them to the lamp over Bluetooth, or run them as a full-screen screensaver that shuffles the effects and bounces the ball.

Added · updated 27 October 2026

How to use it

The Boing Lamp is an ESP32-C6 inside a printed Boing ball, lighting 128 cells — 8 rows of 16 — with WS2812 LEDs. Its effects are C functions that colour those cells once a frame. This page lets you write one in JavaScript instead, with the same model and the same helpers, see it straight away on a 3-D ball whose cells match the lamp’s, and send it to the real lamp frame by frame over Bluetooth. When an effect is right, Copy as firmware stub gives you the C boilerplate with your JavaScript in a comment, to port by hand.

How to use it

  1. Pick an example — most are ported from the firmware: the Boing checker tumbling, the shaded spinning ball, rainbow, plasma, fire, scanner, lighthouse, twinkle, an earth-ish planet, the eye, copper bars, candy stripes, a meteor and a beat pulse.

  2. Edit it. With Run as I type on, each change runs as soon as it compiles; otherwise press Run or Ctrl+Enter. Editing an example makes it My effect, which this browser keeps.

  3. Drag the ball to look round it. The marker on the stand is the front — R4-S0, the cell the firmware’s eye, clock and faces look out from; Face front turns the view to it, and Cell names labels the cells. The flat map below shows all 128 at once; point at a cell for its name, its index in cells and its direction.

  4. Speed and Tweak are the lamp’s two knobs, ctx.speed (0–4) and ctx.param (0–1).

An effect is a script that defines render(ctx, out):

let phase = 0;                       // kept between frames, like a static in C
function render(ctx, out) {
  phase += ctx.dt * ctx.speed;
  for (const c of cells) {           // c.n is the unit vector through the cell's centre, z up
    out[c.i] = hsv(c.n[2] * 0.5 + phase * 0.1, 1, 1);
  }
}

ctx has t, dt, frame, speed, param and beat (a 120 bpm pulse standing in for the lamp’s microphone). cells[i] has layer (0 is the bottom row, R8), seg (0 faces +X), n, lat, lon and checker. The helpers mirror the firmware’s fx_util.h: hsv, mix, scale, bell, smooth, arc, dir, rotate, checkerAt, plus noise, fbm and rand. The page lists them all under What an effect can use.

Mistakes show under the editor with the line they’re on, and the preview keeps going: a version that doesn’t compile leaves the previous one running, and an error while drawing keeps the last good frame up. The effect runs in a worker, so an endless loop can’t hang the page — a frame that takes more than a second stops it.

Screensaver

⛶ Screensaver turns the page into a screensaver: the ball fills the screen and a different effect comes up every so often (10 seconds to 5 minutes, or never), in a shuffled order so each one shows before any repeats, crossfading from one to the next with a random Tweak. Bounce sends the ball across a dark Amiga-style grid with its shadow behind it, the way the original Boing demo did; without it the ball sits still in the middle. Clock puts the time in the corner, and Include my effect adds yours to the mix.

It goes full screen where the browser allows it and asks the browser to keep the screen on while it runs (Chrome, Edge and Safari do; Firefox doesn’t, so the system may still dim it). Click, tap or press Esc to leave; → (or N, or space) skips to the next effect, B turns the bounce on and off, C the clock, F full screen. If you’re streaming to the lamp, the lamp follows the screensaver. A link to /tools/boing-effect-lab/#screensaver opens straight into it.

Streaming to the lamp

  1. Press Connect by Bluetooth and choose boing-lamp.

  2. Press Stream to the lamp. The page switches the lamp to its stream effect (it sends fx stream) and sends it the preview’s frames, at up to the rate you set, 5–30 frames a second. The rate drops to what the link manages and climbs back when it can. Brightness sets the lamp’s own brightness.

  3. Stop streaming puts back the effect the lamp was showing. If the page goes away instead, the lamp holds the last frame for 3 seconds and then fades to a dim blue checker.

Streaming needs firmware with the stream effect, newer than the lamp’s first releases. Older firmware still connects, and the page tells you it has no frames characteristic; brightness works either way.

How it works

The 3-D ball is drawn on a canvas, cell by cell, along the same latitude bands as the firmware: the two cap rows are 1.6 times the height of the others, so they are deep enough to hold an LED. The geometry is a port of the firmware’s sphere.c, and its tests check it against numbers printed by the C itself.

Frames go to a characteristic of their own, 6b1a0005-4253-89b4-a543-135c471e5d9e, written without response. Each packet is three bytes — frame number, first cell, flags — then as many cells as fit: 3 bytes each (RGB888), or 2 (RGB565) for a slow link. Because every packet says where its cells go, any packet size works and a lost packet only leaves a few cells a frame behind. A frame is two 195-byte packets where the Bluetooth link allows it, or three of 132 bytes on a Mac. The browser doesn’t say what the link allows, so Packets: automatic starts big, and after 40 frames asks the lamp (the stream console command) how many cells the last frame had; if some were missing it steps down.

Browser support

The editor and preview work in any current browser. Streaming uses Web Bluetooth, which works in Chrome and Edge on Mac, Windows, ChromeOS and Android; Safari, Firefox and every browser on iPhone and iPad can’t reach the lamp. The lamp takes one Bluetooth connection at a time, so close the Boing Lamp tool or the Mac bridge first.

Privacy

Nothing leaves your browser except the frames and commands sent to your lamp over Bluetooth. Your effect is kept in this browser’s local storage.

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