glowing cells · specimen sheet
sect. rgb vol. 64² yr 2026
projectphysical computing — rt visuals — audio reactive edition of 01

glowing cells64 × 64 led panel

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.

resolution
64 × 64 px / 4096 leds
refresh
60 hz / 12.288 kb/frame
effects
7 generators / glsl + python
transport
artnet udp / 24 universes
mode a
td-hosted, audio-reactive
mode b
pi-standalone, headless
live — metaball.glsl

Painted with photons,
not pixels.

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.

p2 / hub75e 2 mm pitch 1/32 scan 5v · 8a
1

about the build

overview

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.

panel
waveshare p2 · hub75e
scan
1/32 · 2 mm pitch
driver
adafruit rgb bonnet
sbc
rpi zero 2w · 512 mb
host
touchdesigner 2025
transport
artnet · 24 × 512 b
library
hzeller / rgb-led-matrix
audio
vb-audio loopback
bands
32 (td) · 16 (pi)
power
5 v · 8 a regulated
2

in motion

documentation / vimeo
vid — demo reel · captured from panel recorded as-is
3

seven visualisers

swatches / 64 × 64
4

signal flow

node → photon

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.

  1. fx_switch picks one of seven baseCOMP visuals
  2. level1 colour-grades; OUT caps at 64×64
  3. artnet_sender DAT packs 24 universes per frame
  4. Pi reassembles → rgbmatrix → vsync swap
  5. viz_led GLSL TOP renders a 640×640 preview
// 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
5

bill of materials

4 boards / 1 solder bridge
panel

the panel

waveshare
rgb-matrix-p2

64 × 64 · hub75e
2 mm pitch · 1/32 scan
~3 a peak draw

driver

driver board

adafruit
rgb bonnet

hub75 level shifter
jumper 16/e/8
mid → 8 (required)

compute

sbc

rpi zero 2w

4-core arm cortex-a53
512 mb ram
gpio slowdown = 4

power

supply

5 v · 8 a regulated

panel ≤ 3 a
pi ≤ 1 a
generous headroom

6

field notes

from the dev log

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.

touchdesigner

scripttop output is frozen.

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.

fft

audiospectrum indexing was wrong.

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.

numpy

y-axis is inverted on write.

copyNumpyArray uses OpenGL convention: arr[0] = bottom. Bars filling arr[H-bar_px:H] grow downward. Fill arr[0:bar_px] instead.

dsp

bars stuck at 100%.

Replaced min(1.0, raw*gain) with arctan soft-compression. Loud signals approach the ceiling without collapsing onto it.

hardware

display showed two half-height repeats.

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.

pi zero

led flicker during animation.

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.

photons, at sixty
hertz.

specimen n°01 · 2026
waveshare × adafruit
× derivative
printed in single-colour orange
on warm cream, 2026.