pub struct PatchTree {
pub amp: AmpEnv,
pub root: AudioNode,
}Expand description
A complete patch genome: an audio term wrapped in the mandatory voice stage (amp ADSR → VCA → limiter → stereo out, added by the compiler).
Fields§
§amp: AmpEnvAmplitude envelope parameters.
root: AudioNodeThe evolved audio-term tree.
Implementations§
Source§impl PatchTree
impl PatchTree
Sourcepub fn domain_violations(&self) -> Vec<(String, f64)>
pub fn domain_violations(&self) -> Vec<(String, f64)>
Every continuous site of this term that sits outside PARAM_DOMAIN,
as (trace address, value), in address order.
Reads the trace, not the term, and that is the whole point: the trace enumerates exactly the continuous sites, by construction, from the same walk the prior samples. A hand-written match over 26 productions would be a second table of “which fields are knobs” — and the first module somebody forgot to add to it would be the one the next sentinel escaped through.
Sourcepub fn clamp_domains(&mut self) -> usize
pub fn clamp_domains(&mut self) -> usize
Pull every out-of-domain continuous site back into PARAM_DOMAIN.
Returns how many sites were repaired (0 = the term was already clean,
and nothing was rebuilt).
Repair, not refusal, and the asymmetry is deliberate. A term over the size/depth ceilings cannot be fixed without deciding which modules to delete, so those are refused. A knob outside its range can be fixed, exactly and locally, and the alternative — refusing — would mean a saved session that already contains one becomes an app the player cannot edit, load or evolve their way out of. Corruption must not be load-bearing.
NaN clamps to the middle of the range rather than to an end: it
carries no information about which way it went, and pinning it to a
boundary would state one.
Identities survive. The rebuild goes through the trace, which does not
carry uids, so the repaired term inherits them back from the term it
replaced — same rule, and the same reason, as crate::set_param.
Source§impl PatchTree
impl PatchTree
Sourcepub fn site_count(&self) -> usize
pub fn site_count(&self) -> usize
Total probabilistic-choice sites (amp envelope + tree).
Sourcepub fn signature(&self) -> String
pub fn signature(&self) -> String
Short human-readable signature along the main signal spine
(saw·ladr·dly) — the default display name for unnamed patches.
Sourcepub fn ensure_uids(&mut self)
pub fn ensure_uids(&mut self)
Give every node that still lacks an identity a fresh one, and break any duplicates.
This is the settle step: the point at which an anonymous tree becomes a thing a person can point at. Call it wherever a tree is adopted — admitted to the pool, put on the bench, restored from a save, arrived from the panel — and nowhere in the middle of search.
De-duplication is not paranoia. A whole-tree replace from the panel is arbitrary client JSON, and the one gesture that most obviously produces a repeat is “duplicate this module”: copying a subtree copies its uids, and two nodes claiming one identity is worse than no identity at all — a lock on either would light both. First occurrence in walk order keeps the id; the copies are minted fresh.
Sourcepub fn clear_uids(&mut self)
pub fn clear_uids(&mut self)
Strip every identity back to Uid::NEW.
Used where a tree must hash or serialize as pure content — the render memo’s key, above all, which would otherwise miss on every refinement step and invalidate every persisted row the moment uids started moving.
Sourcepub fn inherit_uids(&mut self, parent: &PatchTree)
pub fn inherit_uids(&mut self, parent: &PatchTree)
Carry parent’s identities onto this tree wherever the structure is
unchanged.
This is what makes locks and hand positions survive ⚡ evolve.
Refinement does not mutate a tree in place — EvolutionChain proposes
over the trace, and every accepted step reconstructs the whole genome
through crate::genome’s decoder, which knows nothing about uids and
cannot: a trace is a map from address to value. So a refined child comes
back structurally near-identical to its seed and completely anonymous,
and without this pass every generation would look to the UI like a brand
new patch — the exact failure (R6) that would make pinned routings and
freeform layout read as broken.
The match is positional and shallow-by-variant: walking both trees in lockstep, a node inherits its counterpart’s identity only if it is the same variant, but the walk descends either way. That last part matters — a step that swaps one filter for a reverb should not orphan the entire chain beneath it, which was not touched.
Anything left unmatched (a genuinely new module, a branch that grew)
stays Uid::NEW and is minted by the following Self::ensure_uids.
Trait Implementations§
Source§impl<'de> Deserialize<'de> for PatchTree
impl<'de> Deserialize<'de> for PatchTree
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl EvolutionaryGenome for PatchTree
impl EvolutionaryGenome for PatchTree
Source§fn generate<R: Rng>(rng: &mut R, _bounds: &MultiBounds) -> Self
fn generate<R: Rng>(rng: &mut R, _bounds: &MultiBounds) -> Self
Draws from the default grammar configuration; bounds is ignored
(this genome has no numeric-box structure). Prefer
PatchGrammarPrior::sample_with_rng to control the grammar.
Source§fn distance(&self, other: &Self) -> f64
fn distance(&self, other: &Self) -> f64
Structural distance: parameter L1 where the trees agree, and a subtree-size penalty where they diverge. Defined for any pair (never panics); a heuristic for diversity mechanisms, not a metric with audio-perceptual meaning.