skip to content
Jason Worden
Table of Contents

If I describe Supervision casually, I call it an iPad app for live lighting and laser.

That is true in the same way a browser tab is “the internet.” The iPad is the thing you touch. It is not the thing running the show.

The actual system is split across a few devices:

  • An iPad as the primary control surface
  • An iPhone as a narrower companion controller
  • A Raspberry Pi 5 running the authoritative engine and state
  • A GL.iNet travel router carrying the local network
  • A LumenRadio Aurora receiving Art-Net and bridging out to CRMX fixtures
  • A Radiator box driving the laser path when that part of the rig is connected

Radiator is optional at runtime, not optional in the architecture. The minimum system is still a lights-only rig: Pi on Ethernet, Aurora on Wi-Fi, controllers on the same LAN. When I want laser, I add Radiator and the projector. Same control model. Bigger output graph.

Supervision iPad Control view

iPad Control view — the main live performance surface for Supervision, with patterns, effects, one-shots, tempo, and master brightness in reach.

Here is the full topology:

flowchart LR
  iPad -->|Wi-Fi| Router
  iPhone -->|Wi-Fi| Router
  Router -->|Ethernet| Pi[Raspberry Pi 5 · engine]
  Router -->|Wi-Fi| Aurora
  Pi -->|Art-Net UDP| Router
  Pi -->|TCP| Radiator
  Aurora -->|CRMX wireless| Lights[Tube lights]
  Radiator -->|ILDA| Laser[Laser]
3D render of the rig from the audience view

3D render — audience view of the rig at Gray Area, San Francisco, built in three.js.

The render is useful for explaining the system. The live room is messier, brighter, and less forgiving.

Tube lights and laser crossing reflective panels over the audience

Live photo — tube lights, laser, and reflective panels carrying across the room during the set, 5/14/2026. Photo by Mariah Tiffany.

And here is the minimum path:

flowchart LR
  iPad -->|Wi-Fi| Router
  iPhone -->|Wi-Fi| Router
  Router -->|Ethernet| Pi[Raspberry Pi 5 · engine]
  Router -->|Wi-Fi| Aurora
  Pi -->|Art-Net UDP| Router
  Aurora -->|CRMX wireless| Lights[Tube lights]

The important split is not “mobile app versus server.” It is control path versus output path.

The iPad and iPhone are control surfaces. The Pi owns the engine loop, speaks Art-Net to the lighting side, speaks TCP to Radiator on the laser side, keeps the hot state, writes the performance checkpoint, and keeps running if a controller app backgrounds, crashes, or loses Wi-Fi for a few seconds.

That also means the Pi is the only place that gets to be right. It is the authoritative copy of scene state, output state, and reconnect state for every client and every output path. The iPad is the main surface. The iPhone companion joins the same session with a smaller, intentionally limited control set. Neither one gets to become a shadow server just because it happens to be nearby.

That architecture came from one practical question: what do I want to fail first?

For a live rig, the answer is easy. I would much rather lose a screen than lose the lights.

Laser reflections stretching over the crowd

Live photo — laser reflections stretching over the crowd during the set, 5/14/2026. Photo by Mariah Tiffany.

What the product is actually doing

Supervision is built for busking-style performance control: live scene changes, layered effects, tempo-driven movement, one-shot bursts, and scene-tied laser preset recall. There is no preprogrammed timeline waiting backstage to rescue you. The operator is making decisions in the moment, and the software has to behave more like an instrument than a cue stack.

That matters because it changes what “reliability” means.

In a lot of products, a restart is annoying but recoverable. Here, a restart can reset the room. If the controller locks up and takes the output with it, the audience sees the failure immediately. That pushed me away from an all-on-the-iPad architecture earlier than I expected.

The architecture I did not choose

The original plan was to keep everything on the iPad: UI, engine, lighting output, laser networking, and the server side of the companion connection. It is not a ridiculous plan. Professional iPad lighting products exist, and they prove you can keep serious output moving from iOS if you are willing to invest in the native plumbing.

But that path came with a tax I didn’t want to keep paying:

  • UDP output from React Native
  • TCP client support for Radiator
  • A WebSocket server hosted from iOS for multiple clients
  • Background execution strategy
  • More native-module surface area tied to mobile release churn

None of those problems is individually impossible. Together, they point at a system where the most fragile part of the stack is also the part responsible for keeping the room lit.

That is the inversion I wanted to avoid.

Why the Pi won

Moving the engine to a Pi made the core runtime much less interesting, which is exactly what I wanted.

On the Pi side, the networking stack is plain Node:

  • dgram for Art-Net
  • ws for the control protocol
  • better-sqlite3 for persistence on the main thread
  • worker_threads for isolating the engine loop from everything else

The iPad and iPhone stop being special infrastructure and go back to being clients. That is a better fit for their actual job. The iPad is the primary live surface. The iPhone companion is already shipped as a limited-feature MVP, and on purpose it is not a second full editor. It is the smaller remote you can trust for a constrained set of actions, not a peer trying to mirror the whole iPad experience.

That split matters even more once laser is in the picture. A scene activation can change light state and laser state at the same time. If Radiator is connected, the Pi recalls the scene’s laser preset. If it is not connected, the rest of the rig keeps working. If Radiator drops and comes back, the Pi persists lastLaserPreset and then re-sends it on reconnect so the laser side snaps back to the current world instead of inventing a new one.

That same authority model keeps multiple clients boring. The iPad can fire a scene. The iPhone companion can issue its narrower set of commands. A controller can disappear and rejoin. None of that should create output drift, dueling truth, or “which device is right?” bugs during a set. The Pi stays right. Everyone else catches up.

There are obvious tradeoffs:

  • One more box to pack
  • A boot sequence instead of instant-on
  • A hard dependency on the local network for control
  • Two runtimes instead of one

I took all of them. The payoff is that Linux is much better than iOS at being a headless process host, and Node on a Pi is much better than React Native at being boring network infrastructure for Art-Net, Radiator TCP, persistence, and client fan-out.

The result is a system whose failure boundaries make more sense for live use:

  • A controller failure is a control problem.
  • An engine failure is an output problem.

Those should not be the same failure.

What this bought me

The Pi split did more than make the output path safer. It clarified the rest of the design.

Once the engine was its own process on its own box:

  • State needed one authoritative home
  • Clients could reconnect by asking for the whole world again
  • Checkpointing became straightforward
  • The engine loop could be isolated from databases and sockets
  • Lights-only and full laser rigs could share one architecture
  • Radiator could stay optional at runtime without becoming a special case everywhere else

That is when the project stopped feeling like “an app with some networking” and started feeling like a control system.

The network is what made that control system real, which is where the next part goes.