huff processes video through a sequential chain of effect passes each frame. Understanding the order helps predict how parameter changes interact.
All parameter control — sliders, MIDI CC, OSC messages, presets, and reset actions — still converges on the same DOM controls. Two delegated input/change listeners mirror those controls into the typed HUFF_RENDER_STATE cache, and the draw loop reads that cache instead of reparsing the DOM every frame.
Only three p5 Graphics surfaces remain at the render resolution. Each full-frame stage completely overwrites gScratch before the references swap, so the same allocation can serve multiple sequential stages without changing effect order.
Flow Warp also retains a typed geometry workspace keyed by render size and SCALE. Tile positions, edge dimensions, normalized noise coordinates, inward unit vectors, and radial angles are rebuilt only when that key changes; per-frame noise, displacement, and draw order remain unchanged.
The Scanline engine uses the same persistent-workspace discipline. Rotated-span geometry and per-band noise seed constants are retained, prepared band coordinates are reused when phase and controls are unchanged, and the Canvas2D alpha state is set once for the pass. Drift, focus, roll, gap, skew, shift, spin, clipping, and band draw order remain unchanged.
The frame dispatcher also resolves an allocation-free activity plan before entering the persistent pipeline. Exact no-op states—such as zero-strength Flow, invisible Scanlines, zero-mix Luma/Global Mix, identity Feedback, edge-position Symmetry, and exact-identity Solarize—are skipped. When every stage is neutral, the clean source is copied directly to the output and the persistent buffer is synchronized only once per decoded source frame. Temporal phase accumulators continue advancing so re-enabled motion resumes without a timing reset.
The ring buffer stores reusable canvas-backed frame snapshots at canvas resolution. New decoded frames are copied once into owned ring slots, and temporal effects sample those canvases directly with drawImage(); there is no per-frame ImageData reconstruction step. Each dedicated ring context remains in overwrite (copy) mode, and ring-capacity calculations are repeated only when render dimensions or QUALITY change.
| Parameter | Effect on ring |
|---|---|
| QUALITY | Controls ring depth: 0 → 4 frames, 1 → 60 frames |
| DEPTH | How far back effects sample into the ring (0–50% of ring depth) |
| DEPTH SCATTER | Per-tile randomisation of the back index |
| 192 MB cap | Ring depth also capped by pixel budget: at 1080p (~8 MB/frame) max ≈ 23 frames regardless of quality |
| Resolution | Bytes/frame | Max frames (quality=1) |
|---|---|---|
| 720p (1280×720) | ~3.7 MB | 51 frames |
| 1080p (1920×1080) | ~8.3 MB | 23 frames |
| 1440p (2560×1440) | ~14.7 MB | 13 frames |
| 4K (3840×2160) | ~33.2 MB | 5 frames |
Output routes use role-specific WebSocket connections. Dimensions for native output are declared once in each sender's hello message, so normal Syphon/Spout frames carry raw RGBA pixels without a per-frame header. The JPEG mirror route is receiver-aware and pauses capture/encoding when no canvas client is attached.