■ huff Home How It Works Install Interface MIDI OSC Output
--:--:--
Architecture
_

Project Structure

huff/ ├── src/ │ ├── index.html # Controls window — UI, p5 draw loop, effect chain │ ├── canvas.html # Output mirror window │ ├── canvas.js # p5 setup, draw loop, video handling, WS sender │ ├── effects.js # applyGlitch · applyTrails · applyScanlines │ │ # applyFlowWarp · applySolarize · applySymmetry │ ├── p5.js # p5.js library (bundled locally) │ ├── midi/ # Bundled MIDI map files │ ├── osc/ # Bundled OSC map files │ └── presets/ # Factory preset JSON files │ ├── src-tauri/ │ ├── src/ │ │ ├── main.rs # WS relay · MIDI commands · OSC listener │ │ │ # Syphon handlers · Spout handlers │ │ ├── syphon.rs # macOS Syphon via ObjC FFI (Metal texture) │ │ └── spout.rs # Windows Spout2 via SpoutDX C-ABI bridge │ ├── native/ │ │ └── spout_bridge/ # C++ SpoutDX bridge (CMake) │ ├── frameworks/ │ │ └── Syphon.framework │ └── tauri.conf.json │ └── scripts/ ├── create_universal_dmg.sh └── tauri-build.cjs

WebSocket Relay

The relay runs inside the Tauri Rust process — not in Node.js or the browser.

  • Binds on 127.0.0.1:8787 (IPv4) and [::1]:8787 (IPv6) via tokio + tokio-tungstenite
  • Clients register via a JSON hello: {"type":"hello","role":"index"} or "canvas"
  • Text messages → broadcast to all other clients
  • Binary messages → inspected for magic prefix: HUFFSYPH → Syphon, HUFFSPOUT → Spout, all other → canvas clients

Rust Crates

CrateVersionPurpose
tauri1.xApp shell, two-window management, Tauri IPC
tokio1.xAsync runtime (relay + OSC listener)
tokio-tungstenite0.21WebSocket server
midir0.9Native MIDI input (USB + virtual)
rosc0.10OSC UDP packet decoder
once_cell1.xLazy static initialisation for global client map
serde / serde_json1.xJSON IPC serialisation
objc / objc-foundation0.2 / 0.1macOS Syphon ObjC FFI (macOS only)
cmake0.1Spout bridge CMake build (Windows only)

Frontend Architecture

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.


macOS Universal Binary Build

scripts/create_universal_dmg.sh ↓ tauri build --target aarch64-apple-darwin → huff_arm64.app tauri build --target x86_64-apple-darwin → huff_x64.app ↓ lipo -create huff_arm64.app/.../huff huff_x64.app/.../huff -output huff_universal.app/.../huff ↓ hdiutil create → huff-universal.dmg