A 64 × 64 rgb led panel, driven like an instrument — visuals authored in touchdesigner and streamed live over artnet to a raspberry pi zero 2w, painting four thousand and ninety-six photons in real time.
The matrix runs cool to the touch and disturbingly bright in person. Each cell is a discrete physical RGB LED, not a sub-pixel — which is why the visual grammar has to be coarse, legible, and built for distance.
glowing cells is a research build that turns a waveshare p2 64×64 panel into a playable visual instrument. touchdesigner generates frames on a laptop; a raspberry pi zero 2w receives them over artnet udp and pushes them straight to the adafruit rgb matrix bonnet at line rate.
The project explores how far a tiny single-board computer can be pushed when paired with a desktop visual engine — and how to compose effects whose grammar stays legible at sixty-four squared. Every visualiser is built twice: once as a glsl/python pipeline in td, and once as a standalone python runner so the pi can perform without a host computer at all.
Each frame is a 12,288-byte payload — 64 × 64 × 3 bytes of RGB — chopped into 24 sequentially-numbered ArtNet universes and unicast to the Pi. On the receiving end, a single Python process reassembles universes, writes into the rpi-rgb-led-matrix offscreen canvas, and swaps it on vsync.
Latency is one frame at 60 Hz, plus one Ethernet hop — fast enough to feel instant.
// TouchDesigner — Windows host fx_switch ─▶ level1 ─▶ OUT (64×64) │ ┌──────────┴───────────┐ ▼ ▼ viz_led (GLSL) artnet_sender 640×640 preview executeDAT │ ▼ UDP ┌────────────┐ │ 24 univ. │ │ 512 B each │ └─────┬──────┘ ▼ // Raspberry Pi Zero 2W artnet_matrix.py ─▶ rgbmatrix ─▶ LED Panel 64×64 physical
waveshare
rgb-matrix-p2
64 × 64 · hub75e
2 mm pitch · 1/32 scan
~3 a peak draw
adafruit
rgb bonnet
hub75 level shifter
jumper 16/e/8
mid → 8 (required)
rpi zero 2w
4-core arm cortex-a53
512 mb ram
gpio slowdown = 4
5 v · 8 a regulated
panel ≤ 3 a
pi ≤ 1 a
generous headroom
A condensed selection — the kind of behaviour you only learn about by staring at a black panel for an hour at two in the morning.
With no wired TOP input, TD assumes inputs are unchanged and skips re-cooking. Fix: feed a noiseTOP with tz = absTime.seconds into input 0 to force a cook every frame.
With frequencylog = 1.0, samples are log-distributed — what we labelled "60 Hz" actually queried ~20 Hz. Switch to linear; idx = int(freq_hz) works directly.
copyNumpyArray uses OpenGL convention: arr[0] = bottom. Bars filling arr[H-bar_px:H] grow downward. Fill arr[0:bar_px] instead.
Replaced min(1.0, raw*gain) with arctan soft-compression. Loud signals approach the ceiling without collapsing onto it.
HUB75E panels need the 5th address line. Bridged the Bonnet's 16/E/8 jumper — middle pad to 8 — and the second half snapped into place.
BCM2835 PWM conflict with rpi-rgb-led-matrix's hardware pulse train. Disabled in /boot/firmware/config.txt: dtoverlay=vc4-kms-v3d,noaudio. Steady from then on.