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

huff processes video through a sequential chain of effect passes each frame. Understanding the order helps predict how parameter changes interact.


Effect Pipeline

Each pass is independently toggleable. Disabled passes are completely skipped — no bleed-through. GLITCH and SCANLINES are also weighted against each other by the A/B Mix layer-priority slider: the recessive effect's opacity floors at 35% rather than reaching zero, so both remain visible at any position. The ring stores the clean source frame each cycle — passes that sample the ring (GLITCH, TRAILS, FLOW WARP with PULSE) always read previous frames, never the one being assembled.
huff effect pipeline diagram — full per-frame processing order from Source through Trails, Glitch, Luma Key, Scanlines, Global Mix, Feedback, Flow Warp, Symmetry, Solarize, to Output

UI → Effect Data Flow

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.

The DOM remains the public control surface; the typed cache is the render surface. MIDI and OSC updates dispatch the same events as pointer interaction, so the next draw frame receives the new value without repeated per-frame DOM lookup or numeric conversion.

Full-Resolution Buffer Topology

gCur clean decoded/camera frame gBuf active persistent/effect composite gScratch shared ping-pong target for feedback, Flow Warp, and Symmetry

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.


Frame Ring Buffer

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.

ParameterEffect on ring
QUALITYControls ring depth: 0 → 4 frames, 1 → 60 frames
DEPTHHow far back effects sample into the ring (0–50% of ring depth)
DEPTH SCATTERPer-tile randomisation of the back index
192 MB capRing depth also capped by pixel budget: at 1080p (~8 MB/frame) max ≈ 23 frames regardless of quality
ResolutionBytes/frameMax frames (quality=1)
720p (1280×720)~3.7 MB51 frames
1080p (1920×1080)~8.3 MB23 frames
1440p (2560×1440)~14.7 MB13 frames
4K (3840×2160)~33.2 MB5 frames
720p or 1080p is the practical sweet spot. At 4K the ring holds only 5 frames — DEPTH and temporal effects lose most of their range.

Frame Output Pipeline

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.

Canvas mirror:
final canvas → worker JPEG → index role socket
→ latest-frame Rust relay → canvas role socket → ImageBitmap display
→ mirror acknowledgement permits next submission
Syphon:
worker scale/readback → raw RGBA → syphon-sender role socket
→ syphon::push_pixels() → reusable MTLTexture → Syphon clients
Spout:
canvas scale/readback → raw RGBA → spout-sender role socket
→ spout::push_pixels() → D3D11 texture → Spout receivers