patchtogether.live

Multiplayer browser-native modular synthesizer. Audio + video. Patches are CRDT-shared; rendering is local.

Performers patch a shared rack live in their browser. The patch graph is the canonical state — audio (Web Audio + AudioWorklet) and video (WebGL2 fragment shaders) are renderers of that graph. Authoring is collaborative; rendering is local.

184 modules in the registry today (audio + video). Catalog: /docs/modules. Right-click any module on the canvas to open its per-module docs page in a new tab.

Multi-user model

Authoring runs over a Yjs doc accessed through SyncedStore (the patch graph). A Hocuspocus server on Fly.io brokers updates between rackspace participants and persists snapshots to Neon Postgres. Auth is Clerk; an HMAC-derived invite code (/r/[id]?invite=...) lets anonymous users join without an account. Cap: 4 concurrent users per rackspace (1 owner + 3 others); the 5th visitor gets a /full page. Anonymous edits persist in the shared graph the same as any signed-in user's.

Domains

Two domains today, with the architecture from day 1 to drop in more:

  • Audio. Web Audio + AudioWorklet. Most DSP is Faust 2 → WASM (packages/dsp/src/*.dsp). A handful of modules — LFO, Wavetable VCO, TIMELORDE, CHARLOTTE'S ECHOS, DX7 — ship as hand-written AudioWorkletProcessors in TS (packages/dsp/src/*.ts) when they need clock arithmetic, lookahead schedulers, or in-JS state. CV cables carry a bipolar −1..+1 signal where ±1 sweeps the target param edge-to-edge; per-port cvScale hints (linear/log/ discrete/passthrough) drive the scaling.
  • Video. WebGL2 fragment shaders. Each video module ships its own GLSL + a VideoModuleDef factory under packages/web/src/lib/video/modules/. Cable types: image (still RGB), mono-video (1-channel animated), video (RGB animated), keys (1-channel still). Free upcasts handle the obvious widenings.

The graph is a Y.Doc with nodes, edges, and per-user layouts. A PatchEngine reconciler diffs the live graph against per-domain rendering engines (AudioEngine, VideoEngine) and issues add / remove / setParam calls. Audio-side cv cables can also terminate on a video module's CV input — the cross-domain bridge reads the audio CV at frame rate and pushes it into VideoEngine.setParam.

Persistence

Neon Postgres, one project, three branches (production / autotest / dev). The web tier on Cloudflare Workers can only reach Postgres through Neon's HTTP neon template tag (pg sockets and the WebSocket Pool both fail in Workers — see deploy notes). Hocuspocus on Fly runs Node and uses standard pg.Pool over TCP. Rackspace metadata, owner, member list, and Yjs snapshots all live in Postgres. PICTUREBOX images and DX7 user banks ride inside the Yjs snapshot — see rackspace persistence.

Deploy topology

Three tiers, fan-out from a single repo:

  • prodpatchtogether.live. Gated on a package.json:.version bump in a merge commit.
  • autotestautotest.patchtogether.live. Auto-deploys on every push to main. Beta-gated (basic-auth beta:robotsonly).
  • devdev.patchtogether.live. Same as autotest, separate Hocuspocus and Neon branch. Beta-gated (beta:2600hz).

Each tier maps to its own Cloudflare Pages project, Fly Hocuspocus app, and Neon Postgres branch. See deploy for the full topology.

What to read next

  • Module catalog — every module, its I/O, params, source link.
  • DOOM multiplayer — 4-player co-op: starting a game, joining, late-join, player colors, controls.
  • Clip player + monome grid — an 8-instrument-lane, TIMELORDE-locked clip launcher you build, launch, scene, and edit from a monome grid 128 (WebSerial, no helper).
  • Rackspace persistence — where patches + assets live, what Save/Load do, what auto-saves.
  • Testing — unit / ART / E2E layers + port-surface consistency gates.
  • Deploy + ops — the 3-tier flow, Workers↔Postgres caveats.
Generated from packages/web/src/lib/{audio,video}/module-registry.ts · repo