home/ modules/ strudel-hydra-av-feature-mapping

Strudel → Hydra AV feature mapping: wiring the 4-bin FFT bridge

  • learner can explain the single 4-bin FFT path from Strudel to Hydra and correctly tag voices with `.analyze('hydra')` so they contribute to the bridge
  • learner can write reactive Hydra thunks, clamp out-of-range FFT values, avoid out-of-bounds index bugs, and tune the shim's four knobs to achieve fluid reactivity
  • learner can assign FFT bands to visual parameters using affine and squared-exponential transfer functions, and choose the correct form for continuous motion versus onset-ish pop
  • learner can construct a well-specced mapping bank where each band owns one dominant target, element size matches band register, and the silence floor produces an intentional frame

Starting from the rig's seed Hydra sketch (bass→brightness, high-mid→rotation, low-mid→warp), add a fourth coupling for the highs band (e.g. kaleidoscope symmetry) and remap at least one existing band using a squared-exponential transfer function to produce an onset-ish pop, then wire an exp-curve bass-amplitude pump as a sidechain-style visual duck. Tag exactly the Strudel voices that should drive the visuals, clamp the FFT output where needed, guard against out-of-bounds band reads, tune setScale and setSmooth so no parameter pins at min or max, and write per-value smoothing in one thunk. Deliver a runnable sketch and a one-paragraph design rationale that justifies every band assignment against register/element-size and the silence-floor base term, and states which couplings are true onset versus supported amplitude proxies.

This module builds the hands-on fluency that precedes any coherent AV performance in the Strudel+Hydra rig: the ability to wire a band to a parameter, tune the response, and trust that what you hear is actually driving what you see. In a live set this fluency is pre-performance prep — you don’t debug thunks on stage — so the capstone is a complete, runnable reactive sketch built and stress-tested before the crowd arrives.

The arc starts with the bridge itself. The AV link is a single, narrow channel: Strudel’s Web-Audio analyser fills a 4-element array a.fft[0..3] on a shim that replaces Hydra’s native audio object. No MIDI, no OSC, no per-instrument bus. The first exercise confirms the channel by adding .analyze('hydra') to one Strudel voice, checking that a.fft[0] moves, then removing the tag and watching the visuals go dead — the tag requirement is easy to forget and responsible for the most common “why isn’t it reacting?” failure.

With signal flowing, the next layer is the thunk misconception: a.fft[0] * 2.5 is frozen at eval time; only () => a.fft[0] * 2.5 updates every frame. This is introduced early because it blocks everything downstream — a frozen parameter looks like a working sketch and is almost always the first bug a new learner hits. Pair it immediately with the out-of-bounds guard: a.fft[4] returns undefined, silently turning every arithmetic result into NaN and freezing or blacking out the frame.

From there the learner works through the shim’s four tuning knobs. setScale is the gain before the sketch; setCutoff subtracts noise floor; setSmooth sets one-pole smoothing globally; setBins re-slices bands (stay at 4). The canonical failure modes are the sanity-check atoms: if a parameter spends most of its time pinned at min or max, scale/gain/base are miscalibrated. The calibration drill — raise scale until the visuals breathe, lower smooth until motion is legible but not jittery — is the part-task drill that runs on every new patch.

Transfer functions are the creative core. Affine linear base + gain · a.fft[i] suits parameters that should always be moving (rotation, scroll). Squared-exponential base + gain · a.fft[i]² suits parameters that should stay near their floor when quiet and pop on loud hits — a proxy for onset detection that the display-rate FFT (~60 Hz, transients averaged away) cannot provide directly. The distinction between these two forms, and the understanding that the base term must produce an intentional-looking frame at silence, is tested explicitly in the capstone’s design rationale.

The mapping bank exercises build on the seed: bass → large/slow elements, highs → fine/fast detail (kaleidoscope symmetry being the documented example). The one-band-one-target discipline and the element-size-to-register match are the two rules that separate a mud patch (everything pumping together) from a legible spectral-band-split. The four-band-layout insight ties everything together: a well-budgeted Strudel mix organizes the spectrum so that bass carries the kick and highs carry the hats, which means the mapping assignments are not arbitrary — they key off the same spectral convention that makes the mix itself legible.

The amplitude-proxy and amplitude-pump-proxy atoms give the learner the two supported workarounds for onset-ish coupling: squaring a band value for soft punch, and an exp-curve bass envelope for sidechain-style ducking. These sit at the edge of what the bridge can do and are gated here rather than later because they require the transfer-function and sanity-check fluency from earlier in the arc.

The supporting atom punctual-av-bridge-collapse is enrichment: Punctual removes the bridge entirely because audio and visuals share one program. It is not a required stop on this arc but provides the correct conceptual contrast — the 4-bin shim is a design constraint specific to the Strudel+Hydra combination, not a universal fact about live-coded AV.

Walkthrough

The Strudel→Hydra bridge is narrow: a 4-bin FFT (a.fft[0..3] = bass, low-mid, high-mid, highs) is all the audio Hydra sees. You’ll start from the rig’s seed sketch and build it into a robust, expressive bridge — new couplings, shaped transfer curves, and the safety rails that keep it from pinning or crashing mid-set. Open the Hydra editor, run with Ctrl-Shift-Enter, and remember: the value in each () => … thunk is re-read every frame ([[hydra-reactive-thunk]]).

1 — the seed. The rig ships three couplings: bass (fft[0]) → brightness, high-mid (fft[2]) → rotation, low-mid (fft[1]) → warp. setBins(4) fixes the band count so the indices are stable. This is your known-good starting point ([[seed-mapping-formalized]], [[one-band-one-target]]).

a.setBins(4)
osc(20, 0.1, 0.6)
  .brightness(() => a.fft[0] * 0.4)          // bass → brightness
  .rotate(() => a.fft[2] * 3)                // high-mid → rotation
  .modulate(noise(2), () => a.fft[1] * 0.3)  // low-mid → warp
  .out(o0)

2 — a fourth coupling: highs → symmetry. The highs band (fft[3]) is unused. Wire it to kaleidoscope symmetry so hats and cymbals fracture the frame — matching a fast, bright band to a fast, structural parameter ([[highs-to-kaleid]], [[band-element-size-match]]).

a.setBins(4)
osc(20, 0.1, 0.6)
  .brightness(() => a.fft[0] * 0.4)
  .rotate(() => a.fft[2] * 3)
  .modulate(noise(2), () => a.fft[1] * 0.3)
  .kaleid(() => 2 + a.fft[3] * 6)            // highs → kaleidoscope symmetry
  .out(o0)

3 — shape the response (transfer functions). A linear map reacts to everything equally, so quiet detail muddies the visual. Squaring the signal () suppresses the floor and pops only on peaks — an onset-ish response. The transfer curve is where a mapping gets its feel ([[av-transfer-function-forms]], [[amplitude-proxy-onset]]).

a.setBins(4)
osc(20, 0.1, 0.6)
  .kaleid(() => 2 + Math.pow(a.fft[3], 2) * 10)   // x² → quiet stays still, peaks jump
  .rotate(() => a.fft[2] * 3)
  .out(o0)

4 — a bass pump (sidechain-style duck). Borrow the sidechain move from music: let bass amplitude pull brightness down with an exponential curve, so the whole image ducks on the kick and breathes back — a visual pump locked to the low end ([[amplitude-pump-proxy-sidechain]]).

a.setBins(4)
osc(20, 0.1, 0.6)
  .brightness(() => -Math.pow(a.fft[0], 0.5) * 0.5)   // exp bass → pumping duck
  .out(o0)

5 — the safety rails. Live, the bridge must not pin or throw. FFT values can exceed 1, an out-of-range index reads undefined, and a pinned parameter looks broken. Guard every read (a.fft[i] || 0), clamp to 1, gate the floor with setCutoff, normalise peaks with setScale, and smooth so nothing strobes ([[fft-value-range-exceeds-one]], [[fft-index-out-of-bounds]], [[reactive-param-pinned-fails-sanity]], [[base-term-silence-floor]]).

a.setBins(4)
a.setSmooth(0.8)                             // no strobe
a.setScale(6)                                // normalise peaks toward 1
a.setCutoff(3)                               // gate the rumble floor
const bin = (i) => Math.min(a.fft[i] || 0, 1)  // guard out-of-bounds + clamp
osc(20, 0.1, 0.6)
  .brightness(() => 0.2 + bin(0) * 0.4)      // base term keeps it visible at silence
  .rotate(() => bin(2) * 3)
  .out(o0)

6 — the full bridge (the capstone). Everything together: guarded/clamped bins, all four bands coupled, a squared onset-pop on the highs, an exp bass-pump duck, tuned scale/smooth/cutoff, and a base term so the image lives even in silence ([[four-band-layout-shared-surface]], [[smoothing-global-per-value-workaround]]):

a.setBins(4)
a.setSmooth(0.82)
a.setScale(6)
a.setCutoff(3)
const b = (i) => Math.min(a.fft[i] || 0, 1)             // guarded, clamped bins
osc(20, 0.1, () => 0.5 + b(1) * 0.4)                    // low-mid → detail
  .brightness(() => 0.15 - Math.pow(b(0), 0.5) * 0.35)   // bass → exp pump duck (with a floor)
  .rotate(() => b(2) * 3)                                // high-mid → rotation
  .modulate(noise(2), () => b(1) * 0.3)                  // low-mid → warp
  .kaleid(() => 2 + Math.pow(b(3), 2) * 8)               // highs → squared onset-pop symmetry
  .out(o0)

What good looks like. A trustworthy bridge never pins (no parameter stuck at min/max — that reads as frozen or blown-out) and every band earns its coupling: you can tag exactly which Strudel voice drives which visual move. The tell of a fragile bridge is a parameter that clips to the rail the moment the drop hits, or a NaN that blacks the screen from an unguarded read. Tune setScale/setSmooth until each parameter swings through its middle range on a real track. (Skill map: live-visualist Domain C — audio-reactivity that feels musical, made robust enough to perform.)

Now make it yours. Tag your kick, hats, and pad Strudel voices and confirm each drives its intended band. Try a 1 - x² transfer for an inverse pop. Add per-value smoothing inside one thunk (a running lerp) where the global setSmooth is too blunt. Route the highs to .pixelate instead of .kaleid and compare. Add a stable, un-reactive layer via .layer(...) so the reactive bands have contrast.

Atoms in this module

Required — these gate the capstone

Sound reaches Hydra through exactly one path: a 4-element FFT array filled from Strudel's Web-Audio analyser — no MIDI, OSC, or per-instrument bus exists
Fact L2 First instrument JHF
A Strudel voice must be tagged `.analyze('hydra')` to contribute to the FFT — an untagged voice is inaudible to the visuals even if loud in the speakers
Fact L2 First instrument JFH
a.fft values are not clamped to 0–1 and can exceed 1 under normal use, so bounded visual parameters must be clamped in the sketch
Fact L2 First instrument JH
A reactive Hydra parameter must be written as a () => … thunk — a bare expression is evaluated once at eval time and frozen
Misconception L2 First instrument HJ
a.fft is updated once per rendered frame at ~60 Hz — sub-frame transients are averaged away, making onset detection impossible
Fact L2 First instrument JH
Reading a.fft[4] or higher returns undefined, causing NaN arithmetic and a frozen or black Hydra frame
Fact L2 First instrument HJ
The AV shim exposes four per-band tuning controls — setScale, setCutoff, setSmooth, setBins — that adjust how a.fft responds without changing the sketch
Procedure L2 First instrument JH
AV mapping transfer functions are almost always affine (linear) or squared-exponential — linear for continuous motion, exp for onset-ish pop
Concept L2 First instrument JH
The base term in a mapping transfer function is the visual floor at silence — it must produce an intentional frame when a.fft is zero
Principle L2 First instrument JH
A reactive visual parameter that spends most of its time pinned at its min or max fails the sanity check and needs its base, gain, or scale retuned
Misconception L2 First instrument JH
The seed mapping uses bass for brightness, high-mid for rotation speed, and low-mid for warp depth — three of four bands are assigned, highs are unused
Fact L2 First instrument JHF
Routing the highs band to kaleidoscope symmetry count produces a coherent coupling — fast bright content increases visual complexity
Procedure L2 First instrument JH
Bass should drive large, slow visual elements and highs should drive fine, fast detail for maximum AV coherence
Principle L2 First instrument JH
Driving several unrelated visual parameters from the same FFT band reads as everything-pumping-together mud — one band should map to one dominant target
Principle L2 First instrument JH
Hydra's a.setSmooth sets smoothing for all bands globally — to smooth one target differently, write a per-value lerp in the thunk
Fact L2 First instrument HJ
Squaring a band value produces a soft onset-ish punch that pops on loud hits without requiring true onset detection
Procedure L2 First instrument JH
An exp-curve bass amplitude pump is the supported proxy for a kick-keyed visual sidechain, and it is not the same thing
Misconception L2 First instrument JH
The 4-bin layout is the shared surface between music frequency-budgeting and visual spectral-band-split: a well-budgeted mix yields a legible band-split visual for free
Principle L2 First instrument JHF

Supporting — enrichment, not gating

Punctual runs audio and visuals in one program, eliminating the need for an external AV bridge — audio reacts to its own sound natively
Fact L2 First instrument JH