The relay runs inside the Tauri Rust process — not in Node.js or the browser.
127.0.0.1:8787 (IPv4) and [::1]:8787 (IPv6) via tokio + tokio-tungstenite{"type":"hello","role":"index"} or "canvas"HUFFSYPH → Syphon, HUFFSPOUT → Spout, all other → canvas clients| Crate | Version | Purpose |
|---|---|---|
| tauri | 1.x | App shell, two-window management, Tauri IPC |
| tokio | 1.x | Async runtime (relay + OSC listener) |
| tokio-tungstenite | 0.21 | WebSocket server |
| midir | 0.9 | Native MIDI input (USB + virtual) |
| rosc | 0.10 | OSC UDP packet decoder |
| once_cell | 1.x | Lazy static initialisation for global client map |
| serde / serde_json | 1.x | JSON IPC serialisation |
| objc / objc-foundation | 0.2 / 0.1 | macOS Syphon ObjC FFI (macOS only) |
| cmake | 0.1 | Spout bridge CMake build (Windows only) |
The frontend intentionally has no build step and no bundler. Everything is plain ES6 loaded via script tags. This keeps the dev cycle fast — edit a file, reload the WebView, see the change immediately.
index.html is large (~2300 lines) because the entire controls UI, all MIDI/OSC/Syphon/Spout logic, and the p5 draw loop all live in one file. This is intentional — a single file is trivially auditable and has no dependency graph to maintain.
effects.js contains the Canvas2D effect functions called by canvas.js. The renderer shares a canvas-backed FrameRing, the clean-source surface gCur, the active persistent surface gBuf, and one reusable ping-pong surface gScratch. Flow, symmetry, and feedback reuse that same scratch surface rather than owning separate full-resolution buffers. Flow's static tile geometry is retained in a typed workspace and rebuilt only after render-size or SCALE changes. Scanline band preparation likewise uses a persistent typed workspace with cached rotated-span geometry and per-band noise constants.
canvas.js maintains one sealed render-activity record reused every frame. It distinguishes enabled controls from stages that can actually contribute pixels. Neutral stages never acquire gScratch, trigger CPU readback, or issue full-resolution buffer work. In the all-neutral path, the main canvas receives a direct clean-frame copy and gBuf is refreshed only when the decoded-frame serial changes.
The Rust relay tracks index and canvas roles. It reports canvas receiver count to the controls WebView and acknowledges accepted JPEG frames, allowing the browser to stop mirror capture entirely without an output client and to keep only one relay submission in flight.