frametable
frametable · effectsFRAMETABLE — a video WAVETABLE oscillator. It continuously records the last 60 rendered input frames into a GPU frame ring (a TEXTURE_2D_ARRAY, one layer per frame) and treats that 60-frame history like the single-cycle waves of a wavetable synth: MORPH scans a centre point through the table and SPREAD sets how wide a window around that centre each output pixel may draw from. For EVERY output pixel the shader draws exactly ONE source frame — a whole-pixel dither/mosaic, NEVER an alpha-average across frames — chosen probabilistically from a bell distribution over the window (centre frame most likely, periphery least). The frame index is picked in O(1) by an analytic inverse-CDF (one sqrt, one branch, one texture-array fetch — never a 60-frame loop). The per-pixel choice is fixed in SCREEN space by a static per-pixel threshold with NO time term, so a STILL input yields a stable image (no TV-static shimmer) even while the ring keeps refreshing, while MOVING content becomes a coherent, morph-scannable time-smear mosaic that scanning MORPH slides through like a soft crossfade. SPREAD 1 collapses to a single frame (a delta — crisp playback of one moment); wider SPREAD blends more of the history into the bell, centre frame still most likely. SHAPE morphs the bell from triangular (default, compact) to gaussian (smooth). SHIMMER (default 0 = fully static) animates the threshold along a golden-ratio sequence for a gentle living grain on moving content while the time-averaged distribution stays exactly the target bell. FREEZE (a toggle button, plus a freeze gate that holds the ring frozen while the gate is held high) stops the ring from advancing so you can scrub a held 60-frame window with MORPH/SPREAD; SAVE (a momentary button, also a rising edge on the SAVE trigger) snapshots the current 60-frame ring into an in-GPU slot for later recall (and to feed a future video Cube — the snapshot + live ring are readable through the handle). Beyond that in-GPU slot, FRAMETABLE saves + loads REAL FILES, mirroring the wavetable file workflow: SAVE FILE writes the whole 60-frame ring to a lossless .frametable.png sprite-sheet atlas (a fixed 10×6 = 60-tile contact sheet, no video codec) via the save-file picker (with a download fallback); LOAD reads a .frametable.png back into the ring, freezing it so the loaded table can be morphed/scanned. The multi-megabyte frame bytes persist PER-BROWSER in IndexedDB (never the Y.Doc — only a tiny file descriptor syncs), so a loaded/saved table survives a reload on the same machine. Rendered at half engine resolution (SwiftShader/CI budget); an unpatched input renders black. The selection math (triangular + gaussian inverse-CDF, ring wrap, head→layer mapping, freeze/save reducers) is a 1:1 CPU mirror unit-tested in $lib/video/frametable-core. All ports live on the yellow drill-down PATCH PANEL (no raw side jacks). Look-affecting new WebGL shader — held for owner visual preview.
the faceplate
inputs
| id | cable | what it does |
|---|---|---|
video_in | video | The source video recorded, frame by frame, into the 60-frame ring. Unpatched, the output is black. RGB video stream |
morph_cv | cv | CV that modulates MORPH (the centre point scanned through the 60-frame history), swept linearly over 0..1 (wraps at the ring seam). control voltage (CV); modulates morph (additive offset — ±1 CV sweeps the full range, centered on the knob) |
spread_cv | cv | CV that modulates SPREAD (the window width in frames — temporal-average width / dissolve width / bell window depending on mode), swept linearly over 1..60. control voltage (CV); modulates spread (additive offset — ±1 CV sweeps the full range, centered on the knob) |
shimmer_cv | cv | CV that modulates SHIMMER (flow speed in SMOOTH / auto-scan drift in MORPH / threshold dither in CHAOS), swept linearly over 0..1. control voltage (CV); modulates shimmer (additive offset — ±1 CV sweeps the full range, centered on the knob) |
weightShape_cv | cv | CV that modulates SHAPE (the CHAOS selection bell, triangular↔gaussian), swept linearly over 0..1. control voltage (CV); modulates weightShape (additive offset — ±1 CV sweeps the full range, centered on the knob) |
waveFreqX_cv | cv | CV that modulates X FREQ (cycles of the SMOOTH X-axis waveform across the screen), swept linearly over 0..8. control voltage (CV); modulates waveFreqX (additive offset — ±1 CV sweeps the full range, centered on the knob) |
waveAmtX_cv | cv | CV that modulates X AMT (the SMOOTH X-axis temporal-displacement amount), swept linearly over 0..1. control voltage (CV); modulates waveAmtX (additive offset — ±1 CV sweeps the full range, centered on the knob) |
waveShapeX_cv | cv | CV that modulates X SHAPE (the SMOOTH X-axis waveform morph, sine→tri→saw→square), swept linearly over 0..1. control voltage (CV); modulates waveShapeX (additive offset — ±1 CV sweeps the full range, centered on the knob) |
waveFreqY_cv | cv | CV that modulates Y FREQ (cycles of the SMOOTH Y-axis waveform), swept linearly over 0..8. control voltage (CV); modulates waveFreqY (additive offset — ±1 CV sweeps the full range, centered on the knob) |
waveAmtY_cv | cv | CV that modulates Y AMT (the SMOOTH Y-axis temporal-displacement amount), swept linearly over 0..1. control voltage (CV); modulates waveAmtY (additive offset — ±1 CV sweeps the full range, centered on the knob) |
waveShapeY_cv | cv | CV that modulates Y SHAPE (the SMOOTH Y-axis waveform morph), swept linearly over 0..1. control voltage (CV); modulates waveShapeY (additive offset — ±1 CV sweeps the full range, centered on the knob) |
freeze_gate | gate | FREEZE gate. WHILE the gate is HELD HIGH (level >= 0.5) the ring is frozen — a momentary hold, with the output staying live over the held 60 frames — and it resumes the instant the gate drops low. OR-combined with the FREEZE toggle button, so either can freeze the ring independently. gate / trigger; modulates freezeGate (summed directly (the destination DSP scales it)); gate — acts while the level is high (reacts to both edges) |
save_trig | gate | SAVE trigger. A RISING edge fires ONCE, snapshotting the current 60-frame ring into an in-GPU slot (idempotent per edge — held high does not re-snapshot). gate / trigger; modulates saveTrig (summed directly (the destination DSP scales it)); trigger — fires once per rising edge |
chaos_gate | gate | CHAOS gate. WHILE held HIGH (level >= 0.5) it momentarily forces the real-time per-pixel CHAOS render, overriding the MODE selector; it drops back to the selected mode the instant the gate goes low. OR-combined with the momentary CHAOS switch. gate / trigger; modulates chaosGate (summed directly (the destination DSP scales it)); gate — acts while the level is high (reacts to both edges) |
live_gate | gate | LIVE gate. WHILE held HIGH (level >= 0.5) it forces the real-time / no-lag read in ANY mode (SMOOTH and MORPH stop lagging and track the live input); it re-engages the ~2-second lag the instant the gate goes low. OR-combined with the LIVE switch. gate / trigger; modulates liveGate (summed directly (the destination DSP scales it)); gate — acts while the level is high (reacts to both edges) |
outputs
| id | cable | what it does |
|---|---|---|
video_out | video | The rendered frame: the SMOOTH weighted-average field, the MORPH cross-dissolve, or the CHAOS selected-frame mosaic, depending on the active mode. The card preview shows this output. RGB video stream |
params
| id | label | range | default | curve |
|---|---|---|---|---|
mode | mode | 0..2 | — | discrete |
live | live | 0..1 | — | discrete |
chaos | chaos | 0..1 | — | discrete |
morph | morph | 0..1 | — | linear |
spread | spread | 1..60 | — | linear |
shimmer | shimmer | 0..1 | — | linear |
weightShape | shape | 0..1 | — | linear |
freeze | freeze | 0..1 | — | discrete |
waveFreqX | x freq | 0..8 | — | linear |
waveAmtX | x amt | 0..1 | — | linear |
waveShapeX | x shape | 0..1 | — | linear |
waveFreqY | y freq | 0..8 | — | linear |
waveAmtY | y amt | 0..1 | — | linear |
waveShapeY | y shape | 0..1 | — | linear |
liveGate | live gate | 0..1 | — | linear |
chaosGate | chaos gate | 0..1 | — | linear |
freezeGate | frz gate | 0..1 | — | linear |
saveTrig | save | 0..1 | — | discrete |
controls
| control | what it does |
|---|---|
| chaos | CHAOS (0/1, default 0): momentarily overrides the MODE selector to the real-time CHAOS render while engaged, then reverts to the selected mode. Latched by the switch; the chaos gate additionally forces it while that gate is high. |
| chaos gate | Hidden synthetic param the chaos-gate CV bridge writes each frame with the chaos gate LEVEL; while it is HIGH (>= 0.5) the CHAOS render is forced, overriding the MODE selector (OR-combined with the momentary CHAOS switch). Exposed only as the chaos gate jack, not as a knob. |
| Frametable file input {n} | Load table - imports a `.frametable.png` atlas file (a lossless 10x6 = 60-tile contact sheet, no video codec) from disk and detiles it straight into the 60-frame ring, FREEZING the ring so the loaded table can be scanned with MORPH and SPREAD without live frames washing it away. A status line reports the frame count and tile size, or the reason a file was rejected (a PNG whose dimensions are not a 10x6 grid is not a frametable atlas). The multi-megabyte frame bytes are kept in THIS browser's IndexedDB and only a ~120-byte descriptor is saved with the patch, so a loaded table survives a reload for you but a peer without the local copy sees the loader rather than your table. |
| Frametable save file {n} | Save table - reads all 60 ring layers back from the GPU, tiles them into a lossless `.frametable.png` atlas and writes it to disk through the file picker (or a plain download where no picker exists). FREEZE first if you want a specific 60 frames: the ring keeps advancing while you click otherwise. Unlike the SAVE pad above — which snapshots into GPU memory and dies on reload — this produces a real file you can keep, re-load into any FRAMETABLE, and pass to someone else. It also stores a copy in this browser and stamps the descriptor onto the node, so the saved table is what a reload restores. |
| freeze | FREEZE (0/1, default 0): stops the ring from advancing so the held 60-frame window can be scrubbed with MORPH/SPREAD (the render pass keeps running, so the controls stay live over the frozen frames). The toggle button latches it; the freeze gate additionally holds it frozen while that gate is high. |
| frz gate | Hidden synthetic param the freeze-gate CV bridge writes each frame with the freeze gate LEVEL; while it is HIGH (>= 0.5) the ring is held frozen (OR-combined with the FREEZE toggle, so the per-frame level never stomps the button's latched state). Exposed only as the freeze gate jack, not as a knob. |
| live | LIVE (0/1, default 0): forces the REAL-TIME / no-lag read in any mode. Off (default) leaves SMOOTH and MORPH auto-lagged by ~2 seconds so they read a full trailing buffer; on makes every mode track the live input. Latched by the switch; the live gate additionally forces it while that gate is high. |
| live gate | Hidden synthetic param the live-gate CV bridge writes each frame with the live gate LEVEL; while it is HIGH (>= 0.5) the real-time / no-lag read is forced in any mode (OR-combined with the LIVE switch, so the per-frame level never stomps the button's latched state). Exposed only as the live gate jack, not as a knob. |
| mode | MODE (0..2, default 0 = SMOOTH): the render engine. 0 = SMOOTH (flowing 2D temporal-average field from two morphable waveforms, auto-lagged), 1 = MORPH (smoothest cross-dissolve scan of the table, auto-lagged), 2 = CHAOS (the original per-pixel inverse-CDF single-frame pick, always real-time). A faceplate selector; no CV (discrete). |
| morph | MORPH (0..1, default 0): scans the centre point through the 60-frame ring (wraps at the seam). SMOOTH → the field DC / scan depth; MORPH → the temporal position being dissolved to; CHAOS → the centre lag of the bell. |
| save | SAVE (0/1, default 0): a MOMENTARY press-pad. Its RISING edge snapshots the current 60-frame ring into an in-GPU slot (idempotent per edge — held down does not re-snapshot), which is the slot a VideoCube reads and the one the save trigger jack also fires. The snapshot lives in GPU memory and dies on reload; the FILE controls below are the ones that survive it. Releasing returns the pad to rest, so nothing is left latched in the patch. |
| shimmer | SHIMMER (0..1, default 0): SMOOTH → flow SPEED (the field drifts, incommensurate X/Y rates so it never loops = liquid); MORPH → a gentle auto-scan drift of the dissolve centre; CHAOS → temporal dither of the static per-pixel threshold. 0 = fully static. |
| spread | SPREAD (1..60, default 12): the window width in frames. SMOOTH → the temporal-average window; MORPH → the cross-dissolve blend width (1 ≈ a crisp single moment, wider = a longer buttery dissolve); CHAOS → the bell window (1 = a single-frame delta). |
| x amt | X AMT (0..1, default 0.35): how far the SMOOTH X-axis waveform displaces the per-pixel temporal centre (mapped to frames = amt·N/2). 0 = flat; higher = more of the history spread across the X axis. SMOOTH mode only. |
| y amt | Y AMT (0..1, default 0.35): the SMOOTH Y-axis temporal-displacement amount (mapped to frames = amt·N/2). SMOOTH mode only. |
| x freq | X FREQ (0..8, default 1): cycles of the SMOOTH X-axis morphable waveform across the screen. More cycles = finer horizontal temporal ripples in the field. SMOOTH mode only. |
| y freq | Y FREQ (0..8, default 1): cycles of the SMOOTH Y-axis morphable waveform across the screen. SMOOTH mode only. |
| x shape | X SHAPE (0..1, default 0): morphs the SMOOTH X-axis waveform sine→triangle→saw→square (zero-crossing-aligned so blending never phase-cancels). SMOOTH mode only. |
| y shape | Y SHAPE (0..1, default 0): morphs the SMOOTH Y-axis waveform sine→triangle→saw→square. SMOOTH mode only. |
| shape | SHAPE (0..1, default 0): morphs the CHAOS selection bell from triangular (0, compact) to gaussian (1, smooth). Idle in SMOOTH/MORPH (which use a fixed gaussian / Hann kernel). |
source
frametable.ts on GitHub.