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. Under
the model view the instrument also draws which way your taste leans along
each control (below), perform_lean.
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 σ.
The constants on this page live in auracle-session’s perform.rs unless the
text names another file.
A named control is a direction
Six controls on the panel, each a fixed weighting of named φ_audio coordinates, normalized to unit length. They are the first six of the palette's eighteen, which the engine defines and measures the same way:
| 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 of the eighteen directions to
have unit norm over φ’s real coordinate names, and every coordinate it weights
to be one of them, so a control that lost any coordinate to a rename fails the
build instead of quietly narrowing 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, canceling 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 order they
were wired (for the panel, Bright, Snap, Motion, Body, Grit, Space) becomes a
search control (separate, COLLINEAR).
A palette control is not one of the six axes, so the same idea is written for
any direction (purity_basis). Take an orthonormal basis of
the span of and the six axes, with and the rest from
Gram–Schmidt over the six in order, and, for the predicted movement as
above,
For one of the six this is the formula above, bit for bit: the other five axes
share no coordinate with it, so they enter the basis unchanged (a unit test
holds the two equal). A control that leans on some of the six (Thump on Body,
and on Snap through crest) takes up what it shares with them, and the
remainder of each, the part orthogonal to , counts against it in full:
a move of plus an equal part of that remainder has
(the unit test checks this for every such pair). One with a
coordinate of its own (Air’s zcr_mean) widens the span by that much. A move
along the control’s own direction has .
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’s ring solid on the side it can turn toward and dotted on the other, with a stop at the center, and says which way it turns: turns toward restless only, 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, stamped
with the standardizer's revision and the render namespace (cache_namespace: the
featurizer's RENDER_EPOCH and the quiver version), so returning to a patch
costs none. A stamp that no longer matches is played at once and re-measured
in the background. The stamp is the standardizer's revision (the status's
std_rev), not the vote count: the scale moves when a fit runs over sounds
the session had not met, and a pick alone left every kept wiring stale. Every preset’s measurement also
ships with the app (apps/web/perform-wirings.json, made natively through the
same WasmEngine calls by make perform-wirings), so a preset is playable the
moment it lands; the page re-measures it in the background, because the file
was measured under a standardizer fitted to a standard pool, not the session’s.
The view re-wires after every patch change, and after a glide or a Keep that
carries the knobs out of the neighborhood the wiring was measured in (see
below), so the claims are always about the neighborhood the sound is in. A
background re-measurement that wires each control to the same knobs is taken
in place, without re-centering the controls.
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. The palette’s twelve are held to the same check on the same four presets, each wired alone, and must show at least 16 open halves over at least 8 of the twelve.
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 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 never the last word.
It is the first one, though (#290). A first measurement is 8 to 71 renders, a
minute and more on a slow computer, and a sound nobody has measured plays its
controls at once on a wiring that costs none: one kept for a sound of the same
shape (the same modules and selectors, so the same knobs; predict::shape_of)
if one was measured, and otherwise one predicted from a table of how each
kind of module's knob moves φ (predict::KnobTable: the median measured
column per module kind, site, modulation target and third of the range,
backing off to coarser keys), run through the same solve. The measurement
then lands on it by a rebase, so nothing heard moves. Until it does, a control
playing on either says not measured yet: a borrowed wiring was measured, on
another sound, and is not gated; a predicted one is gated (below). Judged against each sound's own Jacobian,
held out (examples/wire_predict.rs, 240 pool sounds from six sessions):
| Table learned from | Wires | Turns the named way | Covers the measured reach |
|---|---|---|---|
| The presets | 767 | 70% | 53% |
| The presets and one session's pool | 688 | 76% | 56% |
| The presets and five sessions' pools | 579 | 79% | 50% |
The gate. It is right on some controls far more than others (Space 99%,
Bright and Snap about 78%, Motion 62%, Body 63%), so a control is wired from
the prediction only where the table has shown it can be. make perform-wirings
learns the shipped table from the presets' Jacobians and 240 pool sounds' (the
standard pool's and five more sessions'), and for each palette control counts,
on every pool sound held out of the table in turn, how often the prediction
wired it and how often that turned it the named way (KnobTable::agreement).
A control passes when the lower end of the Wilson interval at two standard
errors reaches 0.70 (PREDICT_GATE, GATE_Z): more than two turns in three
the named way, after paying for how few turns there were, so a perfect record
needs ten. One session's pool is too few to read it on: across six, one pool's
Bright read 65% and another's Motion 72%. The rest wait for the measurement and
read listening…, as do controls the prediction cannot reach (a guess does
not say a patch can't).
| Control | Wired | Named way | Bound | |
|---|---|---|---|---|
| Bright | 139 | 82% | 0.746 | passes |
| Snap | 231 | 83% | 0.776 | passes |
| Motion | 108 | 60% | 0.506 | |
| Body | 19 | 32% | 0.151 | |
| Grit | 5 | 100% | 0.556 | |
| Space | 73 | 99% | 0.924 | passes |
Of the palette's twelve, Warmth, Thump, Round, Distance, Haze and Bite pass;
Air, Heft, Punch, Throb, Sway and Lo-fi do not. shipped_wirings.rs pins the
list. By ear, rendering each gated control turned fully up and fully down on a
fresh session's 40 sounds (wire_predict --shipped), 81% moved the named way
turned up and 66% both ways (ungated, 66% and 55%), and the prediction wires
58% of the controls the measurement reaches. "The named way" is the sign of
the movement along the control's own direction, not how much else moves with
it, so a purity row like the one above would read lower.
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 offer_cost --release -- 12 3 20 | What an offer, an aimed offer and a drift cost, per preset and as a distribution, and how much of it is renders (offer_cost.mjs in auracle-wasm's examples is the wasm twin) |
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 φ |
cargo run -p auracle-wasm --example palette_census --release -- 3 --prototype | The palette: the eighteen directions, their cosines, how often each reaches the presets, and what measuring them costs natively |
node crates/auracle-wasm/examples/palette_cost.mjs 4 | The same measurement’s cost in wasm, the six against the eighteen (make wasm first) |
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.
The palette: eighteen directions
RFC-006
turns PERFORM’s controls into a palette of eighteen in six families, of which
the player places up to eight. The engine defines all eighteen (PALETTE in
perform.rs) and measures any of them exactly as it measures the six: one
Jacobian, the ridge solve onto at most four knobs, the purity and reach gate,
separation, and verification on real renders (Engine::wire_named; the
arithmetic alone, unverified and without a render, is wire_set). The first
six entries are CONTROLS, at the same indices, so an index names the same
control in either list. The app wires the controls the player has placed on
PERFORM’s panel, at most eight (PANEL_MAX in perform.js): for the six it
sends no controls, so the request is the one it always was, and for any
other panel it sends the panel’s set.
The set is asked for in palette order, whatever order the panel shows.
separate makes the later of two collinear controls the search control, so
the order asked is part of the answer; asking in palette order makes the
answer a function of the set alone, so reordering the panel costs no renders,
and the six keep the precedence the census above measured them with (“beside
the six”). The page keys its cache of wirings (wireKey) by the patch and the
set: the patch’s own key for the six, which is the key every kept and shipped
wiring already had, and that key with #controls= and the set for any other.
A set not measured on a patch yet borrows, control by control, from any set
that was (borrowWiring), and is measured behind it.
How a control is named across the boundary. perform_wire’s controls is
a JSON array of palette indices, read entry by entry: a non-negative whole
number is an index, and an index out of range, a repeat, or anything else in
the array (a negative, a fraction, a string) is dropped on its own. What is
not an array at all means the six. An array with nothing valid in it wires
nothing and renders only the patch itself. The reply’s wiring follows the
order asked, and each entry carries its palette index (Wiring::index)
beside its name. The page names a control back to the engine by that
index (perform_offer’s control, perform_graft’s k), never by the
entry’s position: asked for [16, 6], the first entry is Bite with index
16, and a position of 0 would aim along Bright. Only for the six asked in
order are position and index the same.
The directions
Each of the twelve is a direction of its own over φ’s coordinates, normalized like the six:
| Family | Control | Low · high | Weights before normalizing | What it means in φ |
|---|---|---|---|---|
| Tone | Bright | dark · bright | centroid_mean , rolloff_mean | The six, above |
| Tone | Warmth | cold · warm | bass_fraction , high_ratio | Weight low down and a soft upper register: the highest note comes through quieter than the held one, as under a low-pass that does not follow the keys |
| Tone | Air | closed · airy | zcr_mean , rolloff_mean , centroid_mean | The very top, above the notes: the highest partials and any breath, and the rolloff, measured against the centroid, so the top opens while the body of the sound stays |
| Weight | Body | thin · full | bass_fraction | The six |
| Weight | Thump | light · thumping | bass_fraction , crest | Weight low down that hits |
| Weight | Heft | slight · heavy | rms_mean , bass_fraction | Dense, held weight: frames full of level (which loudness normalization gives a sustained, low sound, since K-weighting counts the lows for less) and weight low down |
| Dynamics | Snap | bloom · snap | attack_s , crest | The six |
| Dynamics | Punch | gentle · punchy | crest , rms_std | The size of the hit against the rest: peaks, and a level that moves over the phrase |
| Dynamics | Round | hard · round | attack_s , rolloff_mean | A soft attack and few harmonics, the voice’s round |
| Movement | Motion | still · restless | held_centroid_std, motion_slow, motion_mid, motion_fast, each | The six |
| Movement | Throb | steady · throbbing | motion_mid | Pulsing and tremolo: the held note’s motion at 2–8 Hz alone |
| Movement | Sway | fixed · swaying | motion_slow | Sweeps and breathing: the held note’s motion at 0.5–2 Hz alone |
| Space | Space | close · far | tail_ratio | The six |
| Space | Distance | near · distant | tail_ratio , attack_s , crest , rolloff_mean | What distance does to a sound: a longer tail, a softer attack, smaller peaks and a duller top |
| Space | Haze | clear · hazy | tail_ratio , motion_fast , crest | A wash: a tail, a shimmer (the 8–30 Hz beating that detune and chorus give) and peaks smoothed away |
| Character | Grit | smooth · rough | flatness_mean | The six |
| Character | Bite | mild · biting | flux_mean , zcr_mean | An edge: the spectrum changing fast, and high partials (a filter that snaps open, a resonance that rings) |
| Character | Lo-fi | clean · worn | flatness_mean , rolloff_mean , motion_fast | Worn like tape: hiss, a dull top and flutter |
Three things φ lacks shaped these. It has no low-mid band, so Warmth is not
quite the voice’s warm, “weight in the low middle, a soft top”. It measures
weight low down (bass_fraction, below about 250 Hz) against a soft top of
the keyboard (−high_ratio: the highest note quieter than the held one).
That is close to the word, and not exactly it: a sound can gain low-middle
warmth that this does not see, and lose some that it does not count.
Defined with the spectrum’s top instead (rolloff_mean down with
bass_fraction up), it moved with Body across the sounds (), and
on 28 of the 40 presets where both it and Bright reached it was Bright’s
gesture, which is why it reads the keyboard’s top and not the spectrum’s.
φ has no bit-depth or bandwidth feature, so a crusher reaches Lo-fi only
through the flatness it adds. And its only space coordinate is tail_ratio,
which the amp release sets (a reverb’s tail is cut at note-off), so Distance
and Haze lean on what else distance and a wash do to a sound.
Two names for one control
The approved prototype (docs/notes/vision-2026-09/prototype/perform.js)
defined the twelve as blends of the six. Five pairs of those blends were one
direction, : Air and Bright (), Softness and
Snap (, Snap turned down), Wobble and Motion (), Distance and
Space (), and Warmth and Air (). Punch was to Snap. That is
why each of the twelve above is its own direction. Redundancy was then measured
three ways over the eighteen, on the presets and the shipped engine’s pool:
- The directions themselves, : no pair reaches . The largest are Space and Distance () and Body with each of Warmth, Thump and Heft ().
- The sounds’ positions along them, correlated over the 62 presets and the pool of 40: no pair reaches . The closest are Motion and Throb (), Snap and Distance (), Motion and Sway () and Throb and Sway (): a sound that moves tends to move in every band, and a distant one tends to have a slow attack.
- The wirings: over the presets where both reach alone, how often their
predicted movements are one gesture (, what
separatechecks). Only Space and Distance do on more than half (9 of 14): where a patch can reach Space at all, it is through the release, and Distance turns it too. A first Heft that also carriedcrestwas Thump turned down on two presets in three (both turned the amp envelope, opposite ways), and was changed to the definition above.
How often each reaches
Each control wired on each of the 62 presets, on the engine the shipped wirings are measured under. Alone is the control wired by itself: whether this patch has knobs that move it, purely and verifiably. Beside the six is the control wired after the six, so a control whose gesture on a patch is one of theirs () is a search control there: whether it adds a gesture the panel does not have. Both halves is both directions open after verification.
| Family | Control | Alone | Beside the six | Both halves | Median purity | Median reach (σ) |
|---|---|---|---|---|---|---|
| Tone | Bright | 79% | 79% | 74% | 0.81 | 1.64 |
| Tone | Warmth | 73% | 52% | 26% | 0.70 | 0.57 |
| Tone | Air | 66% | 48% | 35% | 0.60 | 0.47 |
| Weight | Body | 53% | 32% | 21% | 0.54 | 0.68 |
| Weight | Thump | 81% | 40% | 27% | 0.70 | 1.55 |
| Weight | Heft | 76% | 52% | 19% | 0.65 | 0.77 |
| Dynamics | Snap | 82% | 82% | 32% | 0.99 | 1.56 |
| Dynamics | Punch | 73% | 60% | 15% | 0.89 | 0.92 |
| Dynamics | Round | 82% | 44% | 34% | 0.74 | 1.18 |
| Movement | Motion | 77% | 74% | 26% | 0.98 | 1.91 |
| Movement | Throb | 47% | 29% | 6% | 0.75 | 1.80 |
| Movement | Sway | 55% | 34% | 5% | 0.94 | 2.67 |
| Space | Space | 23% | 23% | 3% | 0.98 | 2.18 |
| Space | Distance | 94% | 39% | 32% | 0.55 | 1.16 |
| Space | Haze | 90% | 63% | 35% | 0.69 | 1.49 |
| Character | Grit | 11% | 6% | 11% | 0.58 | 0.29 |
| Character | Bite | 81% | 58% | 37% | 0.73 | 0.72 |
| Character | Lo-fi | 56% | 34% | 23% | 0.56 | 0.91 |
Purity and reach are medians over the presets the control reaches alone. For the six, “beside the six” is the panel as it is today: Body and Grit lose patches to the gestures of the controls wired before them. Alone, a median preset is reached by 12 of the eighteen (5 at least, 16 at most), against 3 of the six.
Read the table with one caution. Purity measures cross-talk against the named axes, not whether a control moved every coordinate it names, so a direction is reached through whichever of its coordinates the knobs move. On a patch where nothing moves the flatness, Lo-fi is reached by a duller top and flutter, and Distance (94%) by a softer attack and a duller top, since few patches can move their tail (Space reaches 23%). Throb and Sway rarely open both halves: a sound that is still cannot be made stiller.
What it costs
A measurement is renders for the Jacobian, shared by every control, then four per reachable control and two more for each retry. Wiring eighteen instead of six multiplies only the arithmetic (the solve per control takes microseconds) and the verification renders.
| Per preset | Renders, median | Mean | Most | Native s, mean | wasm s, mean |
|---|---|---|---|---|---|
| The Jacobian alone | 12 | 12.1 | 23 | ||
| The six, today | 25 | 26.2 | 42 | 5.9 | 7.5 |
| All eighteen | 49 | 49.8 | 84 | 11.2 | 14.2 |
The render counts are over all 62 presets (palette_census). The native times
are from the same run, three presets at a time on an Apple M3 Max shared with
other work, at 224 ms a render (173 ms in a quieter run). The wasm times are
palette_cost.mjs under node, on one thread as the engine worker has, over
every fourth preset (16), at 265 ms a render. That run boots its own pool from
the same seed, and in wasm the pool comes out differently from the native one,
so its counts differ a little per preset (median 28 renders for the six, 60 for
the eighteen).
A control that reaches costs about five verification renders: 4.7 for the six, and 5.0 for the eighteen wired in palette order, where 7.5 controls reach a preset on average. So the eighteen double today’s measurement. The panel never needs all eighteen. To keep within today’s budget:
- Wire all eighteen by prediction from the one Jacobian the six already pay for. That is arithmetic, and enough for each palette entry to preview what it would do, drawn as a guess (ADR-012): verification closes some predicted reaches (Throb predicted on 71% of presets alone, verified on 47%).
- Verify only the placed controls, at most eight. A panel of the six and two of the twelve adds one or two controls that reach (the twelve reach a mean 46% of presets beside the six, 73% alone), so five to seven renders, a fifth to a quarter more than today’s measurement.
- Verify a control placed later from the memo: the Jacobian is already there, so it costs only its own four renders, and two more if it is retried at half travel.
The app does the second and the third: it measures the panel’s set and
nothing off the panel, on the sound in hand, when PERFORM is in sight
(measurePanel in perform.js), and the engine’s memo holds the Jacobian
from the six’s measurement. The first, a preview of every palette entry by
prediction, is not built (below).
The shipped preset wirings stay the six’s. With the twelve’s wirings beside
them, apps/web/perform-wirings.json would weigh 391,740 bytes instead of
173,171 (2.3 times), fetched by every visitor for controls the panel does not
show yet.
Names
Three of the prototype’s names are changed, and the maintainer approved them with Heft and the end words below on 2026-10-01 (voice):
- Round (hard · round) for Softness (hard · soft): the voice’s word table already defines round as “a soft attack, few harmonics”, which is this direction.
- Throb (steady · throbbing) for Wobble: Wobble Board is a preset, and a control of the same name reads as that sound. Throb is the tremolo module’s own tag. Pulse was the other candidate, and it is a wave shape.
- Sway (fixed · swaying) for Drift: drift is already WANDER’s zone and
the walk behind it (
Engine::drift). Breath would read as Air’s breathiness, and Sweep Machine is a preset.
Heft (slight · heavy), the twelfth, is new. The four have their rows in the voice’s word table.
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::drift). Refinement’s kernel, fugue’s adaptive single-site MH,
starts every fresh chain at a step of scale 1 on each site and adapts it only
after ten proposals there, so on a unit-interval knob each step of a short
walk is a Gaussian as wide as the whole range, reflected back into it: close to
a fresh draw. An offer of 8 steps with every site but the live knobs locked
moved its farthest knob 0.36–0.68 of its range over the table’s 12 presets
(its last row). A drift should wander, and how far is the Wander dial’s to say.
| Wander | Steps | Farthest knob moved (12 presets) | |
|---|---|---|---|
| drift, at its start | 8 | 0.05 | 0.06–0.14 |
| drift, at its top | 18 | 0.08 | 0.15–0.33 |
| roam, at its top | 40 | 0.15 | 0.25–0.61 |
| refinement’s kernel, the live knobs alone | 8 | 1, at first | 0.36–0.68 |
(cargo run -p auracle-session --example drift_distance --release.) Within
each zone the pace rises linearly with the dial (wanderPace, in
perform.js); roam starts at 24 steps and . 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 neighborhood of where they were measured (TRUST,
in perform.js). 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. The Offer pad and Wander ask for this
walk. A search control released more than
from its center (ASK_AT, in perform.js) asks for an aimed one
(below), and springs back without moving a knob.
A search control’s offer is aimed
Engine::offer_toward walks the same kernel on the target tilted along the
control’s direction , turned up () or down ():
where is the taste surrogate , or
VetOnlyFitness before any fit, and is AIM_GAMMA . At
, a proposal one σ further the asked way gains as much log-weight as
one unit more of expected utility. TiltedFitness reads from the memo
entry the inner fitness has just written, so the tilt costs no render, and a
proposal that does not vet keeps its quarantine score untilted.
Twenty single-site steps rarely propose the one module that makes a patch
rough (a noise source, a crusher), and the tilt cannot make a proposal the
kernel never draws. So a walk that has not moved at least REACH_FLOOR the
asked way,
keeps walking from the state it stopped in, up to AIM_WALKS walks of
the asked steps. It is one longer chain that stops as soon as it has arrived,
not a filter: nothing grown is thrown away, which is what the rejected
alternative (grow several undirected offers and keep the one that moved most)
would have done.
The reply carries moved
(Engine::moved_along), and B’s strip prints moved in amber:
grittier by 1.8σ. Below half of REACH_FLOOR (AIM_SAID, in perform.js:
the line a verified half must clear to be said to turn that way), it says the
walk did not get there, not grittier: this walk found no way there, and
invites another turn.
The census. and the walk count were chosen by
make offer-census (offer_census): 16 presets (every third in the bank),
every control PERFORM draws as a search control on each (verified, as the app
wires them: Grit on 14, Body on 14, Space on 12, and a few of the rest), turned
both ways, two seeded offers of 20 steps a walk per arm, the same seeds in
every arm so the arms are paired and is the Offer pad’s walk
itself. The session is a filled pool (its standardizer) with a taste taught by
the synthetic listener search_health climbs toward, which dislikes noise; the
prior rows are the same session before any fit. A move counts when it is at
least REACH_FLOOR the asked way; σ is the median verified
reach of a control that does reach, on the same presets.
| walks | ≥ floor, taught | ≥ , taught | median , taught | ≥ floor, prior | Grit up ≥ floor, taught / prior |
|---|---|---|---|---|---|
| (Offer pad) | 22% | 13% | +0.92 | 23% | 7% / 14% |
| 26% | 16% | +0.84 | 31% | 14% / 35% | |
| 34% | 23% | +0.74 | 35% | 17% / 35% | |
| 39% | 22% | +0.63 | 36% | 25% / 32% | |
| 40% | 23% | +0.62 | 38% | 32% / 32% | |
| 32% | 18% | +1.72 | 41% | 17% / 32% | |
| (shipped) | 46% | 28% | +1.33 | 53% | 46% / 64% |
| 48% | 28% | +1.18 | 54% | 50% / 71% |
(192 offers per arm. is the offer’s expected utility less the patch it grew from; the pool’s spread of is 0.72.)
Reading it: the tilt buys most of what it can by . Past that, more aim buys a few points of the floor, none of , and a lower , so 1 is the smallest at which the curve has flattened. The walks do more than the tilt for the control that needs them most. Grit turned up went from 7% (Offer pad) to 17% with the tilt alone and 46% with three walks, after a taste that dislikes noise, and from 14% to 64% before any fit. Continuing without the tilt () does less than continuing with it (32% and 41% of all asks, against 46% and 53%). The taste cost is bounded: every arm’s median offer still climbs .
Some asks have nowhere to go, and no changes that: Grit down and
Space down moved in none of 28 and 24 trials, because a patch with no
noise sits at the floor of flatness and one with no tail at the floor of
tail_ratio. B says not smoother then, which is the truth. Space up
reaches 29–45%; the instrument asks for it only when the release graft did
not reach.
What it costs in time. The tilt costs no render; the extra walks do.
Timed natively on a shared four-core machine (offer_census --timed, each
offer from a fresh memo holding only the patch it grew from, as in the app),
one tilted walk took 5.1 s on average against 5.1 s for the untilted one, and
the shipped offer, which walks again only when it has not arrived, 15.6 s
against 6.0 s for the Offer pad’s. The instrument counts the seconds in B
while it grows. Growing it on the render farm, and starting both ways at
pointer-down, would take the wait off the player’s hands; that belongs to
the farm’s walk path and is not built here.
Whether it holds the engine. It does not. A walk is a chain of steps, and
a step costs at most one phrase render (about 0.15 s natively, 0.3 s in wasm
under node on the machine this was timed on, offer_cost), so the engine
worker runs an offer or a drift one step at a time and answers the player’s
picks, Keeps and plays between steps. An offer you did not ask for (the spare
grown while you play) gives way to one you did. Cutting a walk up does not
change what it finds: for a seed, the offer is the same tree whatever the step
sizes (the walk is the same code, run in pieces), and what you do between
steps does not move it, because it keeps the target it began on and draws
from a generator of its own.
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 style, 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: the line under Wander
reads drift · gliding · no taste yet while a grammar-drawn drift glides, and
drift · gliding once there is a taste to walk toward. 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 one
loudness normalization computes for the offer,
TARGET_LUFS minus the phrase’s measured lufs_before, so the offer’s standard
phrase plays at −18 LUFS. The gain is clamped only to the range the voice
accepts (MAKEUP_MIN_DB to
MAKEUP_MAX_DB, −24 to +60 dB, in auracle-wasm’s live.rs). It is the same
makeup every live patch gets (live_makeup, in level.rs). It used to be
clamped to ±12 dB, which left 43% of a fresh pool outside the clamp; a leveler
on the summed polyphony now catches a held note that grows louder than the
phrase predicted. Without the match 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 afterward changes A’s level without
re-normalizing it.
Which way your taste leans
Under the model view (hold ⌥, or MODEL), each control carries its lean: how the model’s rating of the sound in hand changes as the sound moves along the control’s direction. With the sound’s standardized and the control’s unit direction, extended to all coordinates with zeros on the structural block (turning a control moves the sound, never the patch’s module counts), the lean of one posterior draw is the derivative of its utility along :
The utility is a maximum of experts, piecewise linear, so its slope at a sound is the slope of the style that claims that sound, and is chosen per draw: a draw in which another style rates the sound highest answers with that style’s . It is not the mean of one style projected on , which would describe a style that may not be the one rating this sound. Far enough along another style can take the sound over, so a lean says what happens near the sound, not along the whole control. Which style a draw names does not depend on how the styles are labeled, so no alignment is needed.
The instrument is sent the importance-weighted mean and standard deviation of
over the draws (TastePosterior::slope, through Engine::lean; in
utility per unit of moved toward the control’s high word), and draws it on
the control’s dial: an arc from 12 o’clock toward the end it leans to, its
SD interval behind it, every control of the panel on one scale. A lean
whose interval crosses zero () is
drawn as a guess, the rule LEARNING’s weights and PATCH’s worth chips use.
Before the first fit there is no posterior and perform_lean answers null:
nothing is drawn. Opening a taste file takes the posterior away until it is
fitted again from the file (a file with nothing taught in it asks for no
fit), and the leans go with it.
The lean is taken at the tree PERFORM measures, with no overrides. That tree's render is in the engine's memo once PERFORM has measured it (the measurement renders it first) or the bench has opened it, and then the lean is a few dot products per draw; a tree not in the memo costs one render, as ▶ does. A control turned since is not part of it, and it is asked again when the view comes up, the sound or the panel changes (for a sound with an AUDIO IN, a new audition clip of an input it reads too, since it is measured through it), and the posterior moves (a refit, a pick’s reweighting, a taste file opened), never per frame.
What is not done
- An aimed offer is aimed at a direction, not at a sound. A search control’s offer tilts the target along (above); it does not aim at a reference sound. Breeding a generation toward a recording is a sound of your own, which the engine has and PERFORM's offers do not use. Nor is the aim itself tuned per patch or per player: repeated turns the same way do not ask harder, and Wander’s offers are not aimed at the last control turned (RFC-002’s open questions).
- The palette doesn’t preview. A palette entry not on the panel is not
measured, so the palette shows nothing of what it would do to this sound.
The preview by prediction from the Jacobian (the first of the budget’s three
points, above) needs the unverified wiring (
wire_set) exposed to the page, and is not built. The twelve have no grafts yet, so a search control among them asks for an aimed offer only. The shipped preset wirings are the six’s, so a preset opened with a palette control on the panel plays the six at once and measures the rest. - The directions are fixed, not personal. The eighteen are the same for every player. A control along a fitted style is a different and more interesting object, and not this one.
- Most of what the player does here is logged, not fitted. Keep, Back,
Take, each offer asked for, and each control turn are recorded as
ImplicitEvents (perform_keep,perform_back,perform_take,perform_offer,perform_turn), and none of those events 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. The exception is an offer’s answer: once B has been heard for a second (HEARD_MS, inperform.js), Take, or asking for another offer, is recorded as a duel with thePerformOfferprovenance, and it counts as a pick. - 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.