status — a research build, and the question behind it is a toolchain one: compose a visual somewhere you can see it, and have it arrive intact on physical hardware. both modes run.
it 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 to an Adafruit RGB Matrix Bonnet at line rate.
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.
→ open the specimen sheet — full build documentation: bill of materials, signal flow, effect swatches and the complete dev log.
two modes
the interesting constraint was that every visualiser is built twice.
- mode A — TouchDesigner hosted on a laptop. audio-reactive, 32-band spectrum, GLSL effects.
- mode B — the Pi standalone and headless, no computer required. five effects reimplemented in Python.
the second mode is the one that could be left running on its own, with nothing attached to it. it also forces every effect to survive being rewritten against a 512 MB single-board computer.
from node to 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. a single Python process
reassembles the 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.
seven visualisers
- fx_noise — organic noise with animated hue cycling.
- fx_metaball — five-ball metaball liquid spheres in GLSL.
- fx_audio — 32-band spectrum analyzer, log-mapped.
- fx_text — scrolling marquee with rainbow coloration.
- fx_audio_metaball — beat-reactive metaballs; blobs jump on each bass kick.
- fx_ice_metaball — same shape, fixed ice-blue palette.
- fx_camera — webcam feed with beat-reactive scatter and hue rotation.
it almost didn't work. twice.
a condensed selection from the dev log — the kind of behavior you only find by staring at a black panel at two in the morning.
- the display showed two half-height repeats. HUB75E panels need the fifth address line. bridging the Bonnet's
16/E/8jumper — middle pad to 8 — snapped the second half into place. - the LEDs flickered during animation. a BCM2835 PWM conflict with
rpi-rgb-led-matrix's hardware pulse train. disabled in/boot/firmware/config.txtwithdtoverlay=vc4-kms-v3d,noaudio. steady from then on. - TouchDesigner froze the scriptTOP output. with no wired TOP input, TD assumes nothing changed and skips re-cooking. feeding a
noiseTOPwithtz = absTime.secondsinto input 0 forces a cook every frame. - the FFT was reading the wrong bins. with
frequencylog = 1.0the samples are log-distributed, so what was labelled 60 Hz actually queried around 20 Hz. switching to linear makesidx = int(freq_hz)work directly. - the spectrum bars grew downward.
copyNumpyArrayfollows the OpenGL convention wherearr[0]is the bottom row. fillingarr[0:bar_px]instead ofarr[H-bar_px:H]fixed the direction. - the bars pinned at 100%.
min(1.0, raw * gain)was clipping. replaced with arctan soft-compression, so loud signals approach the ceiling without collapsing onto it.
full documentation, effect swatches and the complete dev log live on the specimen sheet.