Performance: named controls and the drift walk
A control called Bright is a fixed direction in standardized φ. Which knobs it turns is measured per patch, checked on real renders, and refused when the knobs cannot honestly produce it.
PERFORM, the instrument's first view, needs two things from the machinery. It
needs controls whose names mean the same thing on every patch, which a knob
address cannot give: node/0#cut is a brightness control in one patch and a
wavefolder's drive in another. And it needs motion that stays inside the
sound: a patch that changes on its own without changing what it is made of.
The first is a least-squares problem on the patch's own Jacobian. The second is
the locked walk with every structural site locked. Both live in
auracle_session::perform, and reach the browser as perform_wire,
perform_apply, perform_drift and perform_offer on WasmEngine.
Symbols on this page
The notation fixes as the feature dimension and as a
term, so this page uses different letters for the two things the code calls
d and x.
| Symbol | Is | In the code |
|---|---|---|
| The patch's continuous knob values, normalized | Jacobian::values | |
| The audio block of standardized at those values | audio_z | |
| A named control's unit direction | direction(...), d | |
| at the current values | Jacobian::cols | |
| The knob move the wiring solves for | x | |
| The knobs a control is allowed to move, | support | |
| A control's setting; is the sound as it is | c |
Distances along are in units: one unit is one standard deviation of the session's own spread, from the standardizer. The instrument's tooltips and the code's comments write this unit as σ.
A named control is a direction
Six controls, each a fixed weighting of named φ_audio coordinates, normalized to unit length:
| Control | Low · high | Weights before normalizing |
|---|---|---|
| Bright | dark · bright | centroid_mean , rolloff_mean |
| Snap | bloom · snap | attack_s , crest |
| Motion | still · restless | held_centroid_std, motion_slow, motion_mid, motion_fast, each |
| Body | thin · full | bass_fraction |
| Grit | smooth · rough | flatness_mean |
| Space | close · far | tail_ratio |
So , and
Motion spreads over its four coordinates with weight each, the
motion bands plus the older
held_centroid_std. A unit test requires each direction to have unit norm over
φ's real coordinate names, so a control whose every coordinate was renamed away
fails the build. A control that lost one of several coordinates would still
pass, and would quietly narrow to the rest.
The direction is defined in , not raw , because only a standardized coordinate has a scale that means the same thing across axes. The patch's current position on a control is ; the instrument draws it as a dot on the control's ring, at of the way to the stop.
The standardizer is the session's, refit at every posterior fit. A control's
wiring and position are therefore relative to the patches this session has
seen, and can change after a refit. Before the pool has a standardizer at all,
perform_wire returns null and the view says there is nothing to measure
against yet.
The other two controls, Blend and Wander, are not directions in φ and live in the instrument, not in this module.
The Jacobian, by one-sided differences
For each continuous knob , one render nudged by :
JACOBIAN_STEP of the knob's normalized range: large enough to move φ
past the numerical floor of a five-second render, small enough to stay local.
Stepping toward the interior keeps every nudge inside the knob's domain,
and one-sided differences cost renders instead of the that central
differences would, all through the render memo. A knob whose nudge fails to vet
gets a zero column, and the wiring simply cannot use it.
The estimate is first order: its error is in the curvature of the response. That is not bounded here. It is what the verification below exists to catch.
Wiring: ridge, support, re-solve
The knob move that best produces is the ridge solution
Ridge rather than plain least squares because is badly conditioned by construction. Two knobs can do nearly the same thing to the sound, which makes columns nearly collinear, and many knobs are nearly inaudible, which makes columns nearly zero. Unregularized, the solve answers both with large, cancelling moves.
The support is the four knobs with the largest effect
(MAX_KNOBS), and
the move is re-solved on it:
Truncating would throw away the other knobs' contribution without letting the four that remain compensate; the re-solve is the best move on those four. Choosing the four by effect is a heuristic, not a best-subset search. It is effect and not because a knob that barely moves the sound needs a large coefficient to contribute anything. Ranked by coefficient, that knob headed the support, and the scale below then gave the knob doing the work a sliver of a turn. On Iron Bass, Bright wired to the drive () at , and the cutoff (, of centroid per unit) barely moved: of reach.
The control's own knobs first. The solve runs twice at most. First it is
restricted to the knobs a musician would name for the control
(NamedControl::sites: cutoff, tone, … for Bright; mod depth and rate for
Motion). If that wiring clears the gate below, it is used. Only if it does not
does the solve range over every live knob, with those sites as a soft prior
(SEMANTIC_RIDGE). A player who turns Bright and watches the cutoff move has
learned something true about the patch. One who watches the amp release move,
which is what the unrestricted solve chose on Acid Line, has learned nothing.
Four, because a control is heard as one gesture, and because four is where most of a patch's audible leverage already is: over the preset library, a patch's four most audible knobs carry a median 68% of its φ movement (below).
The movement this predicts is , and three numbers follow from it.
Purity is the cosine between the predicted movement and the direction asked for:
is a pure move along the label. At the off-axis part of the movement is times the on-axis part.
Scale. A full turn may move no knob by more than MAX_TRAVEL of its
range. The knob with the largest effect gets all of it, and the rest follow in
proportion, clamped at the same limit:
Purity and reach below are computed from the clamped travel , so they describe the move the control actually makes.
The upper clip is KNOB_MAX. Every continuous site is
on the half-open interval, so a knob at exactly has log-prior and
would make the patch un-evolvable. A performance gesture must never do that.
Reach is the predicted movement along at a full turn, in units:
A control is a search control on this patch when the knobs cannot honestly produce it:
(PURITY_FLOOR, REACH_FLOOR), or when there is no support or the predicted
movement points the wrong way. The reach floor was first set at against
the preset library's spread. The session pool is spread wider than a curated
library, which shrinks every distance, and hid controls that are
plainly audible on a live patch.
Several controls turned at once add: each contributes to the knobs it wires, and the sum is clipped. The composition is linear by assumption and is not verified.
Purity measures cross-talk, not correlates
Purity asks whether a move is this control. Measured against the whole of φ, it cannot tell a brightening that also raises the zero-crossing rate and the high band — which every real brightening does — from one that also slows the attack. Over a fresh session pool, Bright's median cosine with its own axis was 0.26 for that reason. What a player hears as "this control does something else" is movement along another control's axis, so purity is the cosine with inside the subspace the six named axes span:
with the other controls' axes. Reach and position stay on .
And two controls whose predicted movements are within
of each other are one gesture with two names: the later one in the fixed order
(Bright, Snap, Motion, Body, Grit, Space) becomes a search control
(separate, COLLINEAR).
Measured over the first 24 patches of a fresh session pool (reach_census,
seed 7), with verification on real renders:
| Reachable | Bright | Snap | Motion | Body | Grit | Space |
|---|---|---|---|---|---|---|
| Purity against the whole of φ | 25% | 58% | 58% | 17% | 4% | 38% |
| Purity against the named axes | 29% | 71% | 62% | 21% | 4% | 38% |
Patches on which no control reaches fell from 4 of 24 to 2. The gain is modest because purity was not Bright's real limit: its median predicted reach is about 0.1σ, since many pool patches have no filter for a named control to turn. That is the honest reading, and a search control's offer is the answer to it.
The same 24 patches, after the effect ranking, the control's-own-knobs pass
and a half-travel retry in verify (all three above and below). Both rows
are measured on the same build, since the pink-noise fix and the re-voiced
presets moved the pool standardizer:
| Reachable | Bright | Snap | Motion | Body | Grit | Space | mean per patch |
|---|---|---|---|---|---|---|---|
| Ranked by coefficient | 29% | 67% | 62% | 21% | 4% | 38% | 2.21 |
| Ranked by effect | 50% | 79% | 46% | 29% | 4% | 46% | 2.54 |
The median verified reach of a reachable control grew three- to four-fold (Bright 0.09σ → 0.33σ, Snap 0.54σ → 0.95σ). Motion lost: 62% → 46%. Motion leans on several small knobs together, and giving the strongest one its whole turn is where a Motion wiring most often turns back on itself. The half-travel retry did not win those patches back. Dropping the weak knobs was tried first and cost Motion more.
Tried and not shipped. Aiming the solve at each control's population pattern (the correlation-weighted direction, Haufe et al. 2014) first looked like a large improvement, but only because purity was then measured against the pattern instead of the axis. Measured against the axis, with the pool correlation shrunk toward the identity (Schäfer & Strimmer 2005) and collinear controls separated, it reached fewer patches than the bare axis (Bright 21%, Motion 42%), and on 2 of 12 patches it put Bright and Body onto the same knobs with opposite signs. It was removed.
Verification on real renders
The Jacobian is a local, linear claim, and it fails exactly where a player would notice: at a boundary. On First Bass, which already sits at the floor of motion, the linear prediction said turning Motion down would make it stiller. Rendered, it made it very slightly more restless.
So verify renders every non-search control alone at four settings and
measures the movement along its own axis:
For each half, with sign , the measured reach is
The half must move the asked way at half travel, and further at full travel. A half that moves at the end but reverses on the way is closed. A half is open when , half the reach floor. A control with neither half open becomes a search control. The instrument draws a half-closed control with half its ring and names the end it is stuck at: already as still as it gets, for Motion on First Bass.
A control that would close at full travel gets one retry at half: the same two-point test over (the renders are already in the memo). If a half opens, the wiring keeps half its travel rather than closing.
Total cost for a patch with knobs and reachable controls is renders, plus two for each retry, all through the memo. In the browser that is around 20–30 renders, and the page caches each measurement by tree and by vote count, so returning to a patch costs none. The view re-wires after every glide and every patch change, so the claims are always about the neighbourhood the sound is in.
The gate, and why it samples somewhere else
named_controls_move_the_sound_they_name wires and verifies four presets
(First Bass, Ceiling, Detune Dream, Long Way Down) and then checks every
open half at , a point verification never rendered:
It requires at least eight open halves, so a wiring change that closed most of them would fail the gate rather than pass it vacuously.
It samples somewhere else because no finite set of samples proves a response monotone. A smooth function can agree with any finite set of points and reverse between them. Checking an unsampled point is the measurable form of the promise, and a check at the points verification already used would be the same test twice.
The tolerance is stated rather than zero because a five-second render has numerical noise. units is two orders of magnitude below an audible difference and above that noise. A check with no tolerance failed on Detune Dream's Snap, which reversed by : real, measurable, and far below anything a listener would hear. That is the case the tolerance exists for. Verification itself has no tolerance: a half that reverses at a sampled point is closed however small the reversal.
The measurements, and what reproduces them
Per patch, not per knob kind. The obvious alternative to measuring a Jacobian per patch is a table: what a cutoff knob, or an attack knob, usually does. Both were measured over the 61 presets. The table was built leave-one-out, from per-site-kind averages over the other 60, so no preset was wired by a table that had seen it.
| Median purity | Bright | Snap | Motion |
|---|---|---|---|
| Wired from the patch's own Jacobian | 0.61 | 0.77 | 0.75 |
| Wired from the best leave-one-out per-site table | 0.23 | 0.16 | 0.09 |
The same knob does different things in different patches. That is the reason the grammar exists, and it is the reason the table is not used.
Grit and Space measure a median purity of about zero by knobs alone on most presets, because most patches contain no drive or reverb to turn. They are the controls most often drawn as search controls.
Grafts. Turning a search control first tries to give it something to turn
(graft_for). Bright and Body get a flat EQ, placed below any stereo module
that ends the chain, because above one it folds the patch to mono (Ghost Bell
moved 0.27σ that way). Space, turned up, gets its amp release raised to 0.6
(≈250 ms). A reverb was tried first, and every effect here sits before the amp
envelope: on First Bass with a reverb grafted, ∂Space/∂mix measured −0.04 and
∂Space/∂release +3.65. Grit gets nothing (see the
open question). perform_inserts measures each
graft. Over the preset bank, the EQ is transparent (median |Δz| 0.000; the
unit test bounds it at 0.05σ). It opens Bright on 7 of the 10 presets where
Bright was a search control, and Body on 15 of 48. The release graft opens
Space on 41 of 46, at 1–2σ of reach. It is not transparent, and is not meant
to be: it is what "farther" asked for.
Leverage. Nudging every knob of every preset by and measuring how
far moves, in units of the library's own
per-coordinate spread: over 61 presets and 821 knobs, a patch's four most
audible knobs carry a median 68% of its total movement and its top eight
94%. 172 of the 821 knobs barely move it at all. That is what makes
MAX_KNOBS a real operation rather than a random pick with a story
attached.
| Example | Produces |
|---|---|
cargo run -p auracle-features --example jacobian_probe --release > jac.csv | for every preset: the raw material for both purity rows |
cargo run -p auracle-features --example leverage_probe --release > leverage.csv | Per-knob leverage for every preset |
cargo run -p auracle-session --example perform_wiring --release -- "First Bass" | The shipped wiring on named presets: knobs, purity, reach, position, search |
cargo run -p auracle-session --example perform_inserts --release | For each preset's search controls, whether PERFORM's graft is transparent and whether it makes the control reachable |
cargo run -p auracle-session --example reach_census --release -- 24 7 | How many controls reach the patches of a fresh session pool, with verification, and how the gate would read with purity against the whole of φ |
The two probes print CSV and the medians are computed from it. jacobian_probe
uses central differences on raw φ at the same , so it measures the
same response as the shipped code without being bit-identical to it.
perform_wiring runs the shipped jacobian and the bare-axis wire under a
preset-library standardizer, and does not run verification; reach_census
runs the shipped path (Engine::wire_controls) with verification.
Drift: a local walk on the live knobs
Engine::drift samples the same target
restricted to this patch's shape: every structural and categorical address,
every continuous site without a live handle, and the player's own locks are
held fixed — the walk moves only live_knobs. Holding sites fixed in a Metropolis–Hastings walk is
exact conditioning (Locks as conditional refinement), so the walk
targets over the knobs the voices can take
live. Structure cannot change under the player's hands, and nothing the walk
moves needs a recompile to be heard.
The kernel is not refinement's. Each step picks one free knob uniformly and proposes
accepted with probability . The
reflected Gaussian is symmetric, so there is no Hastings correction
(Engine::local_walk). Refinement's kernel, fugue's adaptive single-site MH,
starts every fresh chain with a wide proposal on a unit-interval knob: measured
over 12 presets, an 8-step "drift" moved some knob by 0.3–0.85 of its range. A
drift should wander, and how far is the Wander dial's to say.
| Wander | Steps | Farthest knob moved (12 presets) | |
|---|---|---|---|
| gentle drift | 8 | 0.05 | 0.06–0.14 |
| mid drift | 18 | 0.08 | 0.15–0.33 |
| roam | 40 | 0.15 | 0.25–0.61 |
(cargo run -p auracle-session --example drift_distance --release.) The walk
returns its end state, or nothing if it ends where it started. Like
refinement, a short walk's end state is local movement on , not a
draw from it (What is not sampled).
Nothing enters the pool: a performance gesture is not a candidate until the
player keeps it.
The wiring survives a small drift. The named controls are a linear model measured with 0.08 knob steps, so the instrument re-measures them only when some knob has left a 0.12 neighbourhood of where they were measured. A gentle drift usually stays inside and costs nothing; before, every glide was followed by a full re-measure of about 46 renders, which in drift kept the worker busy much of the time and queued offers behind it.
The instrument then glides the knobs from where they are to the returned values along a smoothstep, , over 2 to 6 seconds. The intermediate states are interpolations, not states of the walk. They are all valid patches, since the structure is fixed and the knob box is convex. A touch stops the glide where it is.
Offers
Engine::offer is the same walk with only the player's locks, so structural
moves are allowed: it may add, remove or replace a module. It is also
non-inserting. The instrument asks for 20 steps (40 in roam) and plays the
result in the B slot, never as a jump. A search
control released more than from its
centre asks for one, and springs back without moving a knob.
Before any evidence: VetOnlyFitness
With no posterior there is no utility to climb, and the first version of PERFORM returned nothing for drift and offers until the player had made picks. But the posterior before any evidence is not undefined. It is the prior.
With a zero-mean prior on and a single lens, the prior expectation of
utility is for every patch, so the target is
itself. VetOnlyFitness writes that down, with the vetting
gate kept:
At an unvetted term is down-weighted by , so is restricted to listenable patches, to any precision that matters. The proposal distribution is the plain grammar prior too, since the taste tilt needs a posterior.
Every drift and offer reply carries whether it was taste-directed
(Engine::has_taste), and the instrument says which: drifting through the
grammar — no taste yet, or drifting toward your taste. Wiring the named
controls needs no taste at all, only a standardizer.
The B slot
An offer is heard through a second LivePoly in the same AudioWorklet. It
receives every note-on, note-off, bend, glide, unison and arpeggiator message
the first one does, and notes held when it loads are replayed into it, so it
joins a chord already sounding. It renders continuously while loaded, so its
envelopes and tails are in step with A when the mix moves.
The mix is equal-power, with the mix position smoothed per sample:
The one-pole has a time constant of 500 samples, about 10 ms at 48 kHz, so a Peek is a gesture rather than a click. The gains satisfy , which holds the summed power constant when A and B are uncorrelated. When they are nearly identical the sum rises by up to 3 dB at , since . An equal-gain law would have the opposite error, flat for identical sources and 3 dB down for unrelated ones. An offer is a different patch, so the equal-power error is the one taken.
B is loudness-matched. Its makeup gain is the offer's own loudness normalization to −18 LUFS on the standard phrase, with clamped to dB, the same makeup every live patch gets. Without it a crossfade would mostly compare levels, and the louder side reliably wins. The match is made on the standard phrase at each patch's own settings; a named control turned on A afterwards changes A's level without re-normalizing it.
What is not done
- Named controls are not directed search. A search control asks for an untargeted offer: the walk still targets , not tilted toward . Adding to the log-target would aim it, and is the same substitution as target-directed search. It is not built, so the offer may not move the way the control was turned.
- The directions are fixed, not personal. The six are the same for every player. A control along a fitted style lens is a different and more interesting object, and not this one.
- What the player does here is logged, not fitted. Keep, Back, Take, each
offer and each control turn are recorded as
ImplicitEvents (perform_keep,perform_back,perform_take,perform_offer,perform_turn). None enters the likelihood. A Keep can mean I love this or stop drifting for a moment, and implicit signals stay out of the model until a fit using them can be validated against the explicit ones. - Combinations are not verified. Each control is verified alone, at .
- Motion hears one note. Its axis is built from the held note's span, with the limits that implies.