φ_struct: structural descriptors
Twenty-six dimensions, free to compute.
These cost nothing: no compile, no render, only a walk of the term. That is what makes a screening cascade possible: a structure-only surrogate could prune candidates before the expensive render path. No such cascade is built yet (Directions §12). They also capture taste axes audio features cannot fully separate (“likes supersaws”, “likes deep modulated chains”).
The coordinates
Nineteen family counts:
n_vco n_supersaw n_noise n_wavetable n_pluck n_formant n_silence
n_filter n_drive n_time n_mod_fx n_reverb n_dynamics
n_lfo n_env n_rand n_follow n_mod_shape n_mod_logic
Seven term-level numbers:
mod_density mod_depth_mean amp_attack amp_sustain amp_release
chain_balance frac_sidechained
Twenty-six in total, appended after the eighteen audio coordinates to give .
Families, not one column per module
StructFeatures keeps a raw counter per module kind internally (the Styles tab
and the auto-namer both want “two filters”, not “two subtractive stages”), but
NAMES and to_vec collapse forty-six productions into nineteen family
counts, and three of them, AudioIn, Track and Capture, into none (see below).
Two reasons.
Nothing meaningful distinguishes them. n_fold, n_distortion, and
n_bitcrush all answer “how much nonlinear color”. n_chorus, n_phaser,
n_flanger, n_tremolo, and n_vibrato all answer “how much periodic
movement”. A user who likes drive does not first decide which drive.
Per-kind columns arrive as near-indicator variables. The prior draws bitcrush at 2.5%, ring mod at 2%, and granular at 1.5%, so those columns are zero in ~19 of every 20 pool members. A coefficient fitted on a column that is almost always zero is estimated from a handful of rows, and the Styles tab would render it beside coefficients fitted on hundreds, at the same visual weight.
Measured over 1,200 draws, the extreme case: each of the four CV processors appears in under 4% of patches and each of the six combiners in under 1%. A column that is zero in 99 rows of every 100 is not a coefficient, it is a rounding error with a name in the UI.
Sixteen sparse columns would also cost sixteen dimensions of posterior variance for the cold start to pay down before the model says anything at all.
The step sequencer shows the rule applied to a newcomer. The prior draws it
into about 3% of slots, so a column of its own would be exactly the
near-indicator above. It joins n_rand, which has become the stepped CV
family (StructFeatures::n_stepped): to a listener an S&H and a step pattern
are the same gesture, a value that holds and jumps (or glides) on a clock, and
they differ only in whether the values were drawn once into the genome or
anew at every tick. It does not join the euclid in n_mod_logic, because a
euclid emits a gate and a step sequence emits a value. The column keeps the
name n_rand because stored observations carry φ names, and every row already
on disk predates the step sequencer, so its n_rand already is its
stepped-CV count. The panel labels the column “stepped mods”.
What is deliberately not in φ
size, an exact identity
Every audio node increments exactly one raw counter, so
Including it makes the design matrix rank-deficient. The Gaussian prior
keeps the posterior proper, so nothing crashes and no test fails, but there is
an unidentified ridge along which the MH chain random-walks forever. That
wrecks mixing, splits each coefficient arbitrarily between size and the
counts (so the per-feature weights the Styles tab renders mean nothing
individually), and poisons the
taste→grammar proposal tilt, which reads exactly those
coefficients.
size − depth would be no better: still an exact linear combination of
coordinates already present.
The field is kept for display and naming. It just never reaches the model.
A second, subtler identity
Dropping size alone was not enough, and a VIF sweep caught it: VIF
on every column involved, which is what an exact dependency
looks like numerically.
A tree is a forest of source leaves joined by productions that each take some number of audio children, so the leaf count exceeds the total branch count by exactly one:
Silence joins that sum as a source leaf, because that is what it is: it has
no children, so it ends a branch exactly as a Vco does. Joining keeps this
one equation with one dropped column, and stays the
column dropped. Leaving it outside instead would make the identity exact for a
tree with no holes and slack for one with them: near-exact almost always,
which is a worse thing to carry than an exact dependency: an exact one is
unmistakable in a VIF sweep, and a near-exact one is a large number that looks
like a judgment call.
Audio in: counted, and not a column
AudioIn is a source leaf too, and it has its raw counter, n_audio_in, so
size is still the sum of the counts and the identity above, written over the
raw counts, still holds. But it is not a column of : RFC-008 keeps
’s shape, and at its prior weight (0 until live capture works, 0.5%
after) the column would be zero in nearly every row. Leaving it out brings no dependency back, because the identity was
never reachable from the retained columns: it needs n_mix, which is dropped,
and each binary count separately, which the families hide. The shape
coordinates count it with no help (chain_balance and frac_sidechained read
arity from the tree, where it is a leaf), and what an input patch does to a
signal reaches the model through , measured on the
audition clip.
Track and Capture are counted the same way, with n_track and n_capture
and no column, and one more reason: the prior never draws either (they are
player kinds), so a
column would be zero in every drawn row. The shape coordinates read them from
the tree: a TRACK is a binary node, a CAPTURE a unary one over what it records.
The identity holds exactly, for every tree. It became a general statement only when the four sidechained productions (comp, duck, gate, and vocoder) arrived, each taking two audio subterms exactly as mix and ring mod do.
That is one equation, so exactly one column has to go, and dropping more would remove real dimensions rather than redundant ones. With both binary counts gone, could not tell a crossfade from a ring modulator at all, which are about as different as two nodes in this grammar get.
So n_mix leaves: it is the one determined by the others, and its proposal
tilt is recovered from the source coefficients in the engine’s biased_prior.
The other five stay, but never as columns of their own: ring mod lives
inside n_drive, the vocoder inside n_filter, and comp/duck/gate inside
n_dynamics.
Why n_dynamics is still safe
Worth checking rather than assuming, because n_dynamics is exactly
and it is a retained
column, the only family whose members are all on the wrong side of the
identity.
It is safe because the identity needs each binary count separately.
n_ringmod is only ever visible summed with folds, distortions, and
bitcrushers; n_vocoder only summed with filters and EQs. No linear
combination of the retained columns isolates either, so the equation cannot be
reconstructed. n_dynamics supplies three of the six binary terms and nothing
supplies the other three.
Confirmed empirically: on the 1,200-draw sweep every structural coordinate comes
back well under 10, with n_dynamics at 1.7 (1.9 when this was first
measured).
depth, a weaker but real argument
VIF . Not exact, so the posterior stays proper, but a coefficient that unstable is not individually meaningful, and the Styles tab renders these per-feature weights as though they were. Dropped.
Health of the retained set
Every family count comes back at 3.0 or under on the 1,200-draw sweep, the
highest being n_noise. That is the reason the families exist; forty separate
module columns would not have managed it. The term-level numbers run higher,
and still well under 10: amp_attack is the highest of all twenty-six at 6.3,
then mod_density 4.6, amp_release 4.3 and mod_depth_mean 4.1.
When the three most recent additions arrived they measured n_mod_shape 1.6,
n_mod_logic 1.3 and mod_depth_mean 3.8, with mod_density rising from 2.7
to 4.1 as the one visible cost of adding a second modulation-shape coordinate
beside it. The same sweep today reads 1.8, 1.2, 4.1 and 4.6.
Reproduce with:
cargo run -p auracle-features --example pipeline_stats --release -- 1200
The unit coordinates
Six of the twenty-six are UNIT_NAMES, a subset of NAMES rather than a
reordering. Each is either a normalized genome site read straight through
(amp_attack, amp_sustain, and amp_release) or a ratio of two counts,
already in (mod_density, chain_balance, and frac_sidechained).
mod_depth_mean is not one of them. It is a mean of modulation depths (1
for a bare modulator, 2 for one shaped once), so it is 2 or more for exactly
the shaped chains it exists to measure.
The distinction matters for display: these can be rendered as percentages honestly, whereas a family count cannot.
amp_sustain is also the coordinate the 1e30 sentinel killed; see
the sentinel.