auracle_grammar/term.rs
1//! The patch term: a typed tree over the v1 module palette.
2//!
3//! The Audio/Mod sort distinction is enforced by the Rust type system itself —
4//! an ill-sorted term (an LFO where audio is expected, a filter in a
5//! modulation slot) is unrepresentable. The grammar prior samples these types
6//! directly and the compiler interprets them into a quiver [`Patch`](quiver);
7//! there is no unvalidated intermediate representation.
8//!
9//! All continuous parameters are **normalized to `[0, 1]`** (uniform prior
10//! support); musical mappings (log cutoff scales, bounded resonance/feedback)
11//! live in [`crate::compile`]. Discrete parameters are small enums.
12
13use std::hash::{Hash, Hasher};
14use std::sync::atomic::{AtomicU64, Ordering};
15
16use serde::{Deserialize, Serialize};
17
18/// A stable identity for one node, independent of where it sits.
19///
20/// # Why the tree needs one
21///
22/// Every other handle on a node in this system is its **position**: the trace
23/// key (`node/0/1`), and therefore the trace address of every knob on it, and
24/// therefore every lock, every remembered hand position, every selection. That
25/// is fine until the structure moves. Insert one filter at the top of a chain
26/// and every key below it shifts by one segment; delete a mixer branch and the
27/// survivor is re-rooted; run one generation of refinement and the tree is
28/// rebuilt from a trace with no memory of the object graph at all. Under
29/// positional identity the only honest response to a structural edit is to
30/// throw the UI's state away — which is exactly what the workbench used to do,
31/// with the comment "structure changed — locks cleared" — and that destroys the
32/// loop the graph editor exists to serve: build a routing by hand, pin it, and
33/// breed around it.
34///
35/// A `Uid` is minted once, travels with the node through clones, splices and
36/// refinement, and is dropped only when the node itself is.
37///
38/// # It is deliberately invisible to the model
39///
40/// `uid` is UI identity, nothing else. It must never reach the generative
41/// model, the featurizer, the compiler, or anything content-addressed, because
42/// two patches that differ only in uids are the *same patch* and every one of
43/// those systems is entitled to say so:
44///
45/// - **Equality ignores it.** [`PartialEq`] here is `true` unconditionally, so
46/// the derived `PartialEq` on [`AudioNode`]/[`PatchTree`] keeps comparing
47/// patches by content. The engine's pool dedup (`self.pool.iter().any(|c|
48/// c.tree == end)`) and refinement's own "did this walk move at all"
49/// (`current != *seed`) are both that comparison, and both would break the
50/// moment a fresh uid could make two identical trees differ.
51/// - **Hashing ignores it**, for the same reason and so the two stay coherent.
52/// - **It is skipped in JSON when unset**, and
53/// `auracle_features::cache::canonical_tree_json` clears uids before hashing,
54/// so the render memo's content address is byte-identical to what it was
55/// before uids existed. A cache key that moved with the UI's bookkeeping
56/// would miss on every refinement step *and* invalidate every persisted row.
57/// - **It is not a choice site.** [`crate::genome`] neither encodes nor decodes
58/// it, so `to_trace`/`from_trace` round-trips carry no uid — which is why
59/// [`PatchTree::inherit_uids`] exists.
60///
61/// # `Uid::NEW` and settling
62///
63/// Construction sites write [`Uid::NEW`] (zero, "unset") rather than minting
64/// eagerly. The prior samples thousands of trees per refinement walk and all
65/// but one are thrown away; minting there would burn identities for nothing and
66/// make the counter's value depend on how much search ran. Instead a tree is
67/// *settled* — [`PatchTree::ensure_uids`] — at the few points where it becomes
68/// something a person can point at: admitted to the pool, adopted onto the
69/// bench, restored from a save. Everything before that is anonymous.
70#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
71#[serde(transparent)]
72pub struct Uid(pub u64);
73
74/// The mint. One process-global counter, monotonic from 1.
75///
76/// Not seeded, not per-engine, and deliberately not derived from the tree:
77/// uniqueness *within a session* is the whole contract, and a content-derived
78/// id would give two identical sibling modules the same identity — which is
79/// precisely the confusion uids exist to end.
80static UID_SOURCE: AtomicU64 = AtomicU64::new(1);
81
82impl Uid {
83 /// The unset identity: a node that has not been settled yet.
84 pub const NEW: Uid = Uid(0);
85
86 /// Mint a fresh identity.
87 pub fn mint() -> Uid {
88 Uid(UID_SOURCE.fetch_add(1, Ordering::Relaxed))
89 }
90
91 /// Note that this identity already exists, so the mint never reissues it.
92 ///
93 /// The counter is per *process*, and a restored session is the case that
94 /// makes that a problem rather than a detail: a save carries the uids it
95 /// was written with (that is the entire point — a hand-placed layout has to
96 /// survive a reload), but a fresh page load starts the counter at 1. Insert
97 /// one module into a restored patch and the mint would hand out an id that
98 /// tree is already using, and two nodes would answer to one lock. So every
99 /// identity the engine *sees* pushes the counter past itself, and a restore
100 /// — which settles the whole bank on the way in — leaves it above every id
101 /// in the save.
102 pub fn observe(id: Uid) {
103 UID_SOURCE.fetch_max(id.0.saturating_add(1), Ordering::Relaxed);
104 }
105
106 /// Whether this node still needs an identity.
107 pub fn is_new(&self) -> bool {
108 self.0 == 0
109 }
110}
111
112// Two patches that differ only in uids are the same patch. See the type docs:
113// pool dedup, refinement's fixed-point test and every golden-tree assertion in
114// the suite are this comparison, and all of them mean "same content".
115impl PartialEq for Uid {
116 fn eq(&self, _: &Self) -> bool {
117 true
118 }
119}
120impl Eq for Uid {}
121impl Hash for Uid {
122 fn hash<H: Hasher>(&self, _: &mut H) {}
123}
124
125/// Every [`AudioNode`] variant, in declaration order — the one list the uid
126/// accessors expand over, so a new module cannot be added without one.
127macro_rules! audio_variants {
128 ($mac:ident) => {
129 $mac!(
130 Vco, Supersaw, Noise, Wavetable, Pluck, Formant, Silence, Mix, Filter, Fold, Delay,
131 Chorus, Reverb, Distortion, Bitcrush, Phaser, Flanger, Tremolo, Vibrato, Eq, Granular,
132 RingMod, Shift, Comp, Duck, Gate, Vocoder
133 )
134 };
135}
136
137/// Every [`AudioNode`] variant that carries a modulation slot — all of them
138/// but `Noise`, `Silence`, `Mix` and `RingMod`.
139macro_rules! modulated_variants {
140 ($mac:ident) => {
141 $mac!(
142 Vco, Supersaw, Wavetable, Pluck, Formant, Filter, Fold, Delay, Chorus, Reverb,
143 Distortion, Bitcrush, Phaser, Flanger, Tremolo, Vibrato, Eq, Granular, Shift, Comp,
144 Duck, Gate, Vocoder
145 )
146 };
147}
148
149/// Every non-empty [`ModNode`] variant. `None` is excluded on purpose: an
150/// empty slot is not a module and carries no identity.
151macro_rules! mod_variants {
152 ($mac:ident) => {
153 $mac!(Lfo, Env, Rand, Follow, Euclid, Op, Pair)
154 };
155}
156
157/// Oscillator / LFO waveform. Index order matches the trace-site categorical
158/// and the quiver output-port table.
159#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
160pub enum Waveform {
161 /// Sine
162 Sine,
163 /// Triangle
164 Triangle,
165 /// Sawtooth
166 Saw,
167 /// Square
168 Square,
169}
170
171impl Waveform {
172 /// All waveforms, in categorical-site index order.
173 pub const ALL: [Waveform; 4] = [
174 Waveform::Sine,
175 Waveform::Triangle,
176 Waveform::Saw,
177 Waveform::Square,
178 ];
179
180 /// Categorical-site index of this waveform.
181 pub fn index(self) -> usize {
182 Self::ALL.iter().position(|w| *w == self).expect("in table")
183 }
184
185 /// Waveform from a categorical-site index.
186 pub fn from_index(i: usize) -> Self {
187 Self::ALL[i % Self::ALL.len()]
188 }
189
190 /// The quiver output-port name on `Vco` / `Lfo` for this waveform.
191 pub fn port_name(self) -> &'static str {
192 match self {
193 Waveform::Sine => "sin",
194 Waveform::Triangle => "tri",
195 Waveform::Saw => "saw",
196 Waveform::Square => "sqr",
197 }
198 }
199}
200
201/// Noise color; maps to `NoiseGenerator` output ports.
202#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
203pub enum NoiseColor {
204 /// White noise (flat spectrum).
205 White,
206 /// Pink noise (1/f spectrum).
207 Pink,
208}
209
210impl NoiseColor {
211 /// All colors, in categorical-site index order.
212 pub const ALL: [NoiseColor; 2] = [NoiseColor::White, NoiseColor::Pink];
213
214 /// Categorical-site index.
215 pub fn index(self) -> usize {
216 Self::ALL.iter().position(|c| *c == self).expect("in table")
217 }
218
219 /// From a categorical-site index.
220 pub fn from_index(i: usize) -> Self {
221 Self::ALL[i % Self::ALL.len()]
222 }
223
224 /// The quiver output-port name on `NoiseGenerator`.
225 pub fn port_name(self) -> &'static str {
226 match self {
227 NoiseColor::White => "white",
228 NoiseColor::Pink => "pink",
229 }
230 }
231}
232
233/// Filter kind: three SVF responses plus the diode ladder.
234#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
235pub enum FilterKind {
236 /// State-variable lowpass.
237 SvfLp,
238 /// State-variable bandpass.
239 SvfBp,
240 /// State-variable highpass.
241 SvfHp,
242 /// Diode ladder lowpass (TB-303 flavor).
243 Ladder,
244}
245
246impl FilterKind {
247 /// All kinds, in categorical-site index order.
248 pub const ALL: [FilterKind; 4] = [
249 FilterKind::SvfLp,
250 FilterKind::SvfBp,
251 FilterKind::SvfHp,
252 FilterKind::Ladder,
253 ];
254
255 /// Categorical-site index.
256 pub fn index(self) -> usize {
257 Self::ALL.iter().position(|k| *k == self).expect("in table")
258 }
259
260 /// From a categorical-site index.
261 pub fn from_index(i: usize) -> Self {
262 Self::ALL[i % Self::ALL.len()]
263 }
264}
265
266/// Wavetable shape. Index order matches the trace-site categorical and
267/// quiver's own `WavetableType` table, which the `table` CV selects into.
268#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
269pub enum TableShape {
270 /// Pure sine.
271 Sine,
272 /// Triangle.
273 Tri,
274 /// Sawtooth.
275 Saw,
276 /// Square.
277 Square,
278 /// 25% pulse.
279 Pulse25,
280 /// 12.5% pulse.
281 Pulse12,
282 /// Vowel "ah" formant stack.
283 FormantA,
284 /// Vowel "oh" formant stack.
285 FormantO,
286}
287
288impl TableShape {
289 /// All shapes, in categorical-site index order.
290 pub const ALL: [TableShape; 8] = [
291 TableShape::Sine,
292 TableShape::Tri,
293 TableShape::Saw,
294 TableShape::Square,
295 TableShape::Pulse25,
296 TableShape::Pulse12,
297 TableShape::FormantA,
298 TableShape::FormantO,
299 ];
300
301 /// Categorical-site index.
302 pub fn index(self) -> usize {
303 Self::ALL.iter().position(|s| *s == self).expect("in table")
304 }
305
306 /// From a categorical-site index.
307 pub fn from_index(i: usize) -> Self {
308 Self::ALL[i % Self::ALL.len()]
309 }
310
311 /// Silkscreen label; also the s-expression tag.
312 pub fn label(self) -> &'static str {
313 match self {
314 TableShape::Sine => "sine",
315 TableShape::Tri => "tri",
316 TableShape::Saw => "saw",
317 TableShape::Square => "square",
318 TableShape::Pulse25 => "pulse 25",
319 TableShape::Pulse12 => "pulse 12",
320 TableShape::FormantA => "formant a",
321 TableShape::FormantO => "formant o",
322 }
323 }
324}
325
326/// Distortion shaping curve.
327///
328/// quiver's `Distortion` also offers a foldback mode; it is deliberately
329/// absent here because [`AudioNode::Fold`] already *is* that module, and a
330/// second production for one timbre only splits the prior mass that teaches
331/// the model what "folded" means.
332#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
333pub enum DriveMode {
334 /// Bounded `tanh` — symmetric, warm.
335 Soft,
336 /// Hard clip — symmetric, brittle.
337 Hard,
338 /// Asymmetric tube curve. The only member that rectifies, so it is also
339 /// the only one that costs a DC blocker (see `compile::makes_dc`).
340 Tube,
341}
342
343impl DriveMode {
344 /// All modes, in categorical-site index order.
345 pub const ALL: [DriveMode; 3] = [DriveMode::Soft, DriveMode::Hard, DriveMode::Tube];
346
347 /// Categorical-site index.
348 pub fn index(self) -> usize {
349 Self::ALL.iter().position(|m| *m == self).expect("in table")
350 }
351
352 /// From a categorical-site index.
353 pub fn from_index(i: usize) -> Self {
354 Self::ALL[i % Self::ALL.len()]
355 }
356
357 /// Silkscreen label; also the s-expression tag.
358 pub fn label(self) -> &'static str {
359 match self {
360 DriveMode::Soft => "soft",
361 DriveMode::Hard => "hard",
362 DriveMode::Tube => "tube",
363 }
364 }
365}
366
367/// A unary CV processor: something that sits *between* a modulator and its
368/// destination. Index order matches the `#modop` categorical.
369///
370/// The four are quiver's CV utilities that survive contact with this grammar.
371/// The ones that did not, and why, are recorded on [`ModNode`].
372#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
373#[serde(rename_all = "snake_case")]
374pub enum ModOp {
375 /// Snap the incoming CV to a musical scale (`ScaleQuantizer`).
376 Quantize,
377 /// Rate-limit it, with separate up and down times (`SlewLimiter`).
378 Slew,
379 /// Fold it into one polarity (`Rectifier`).
380 Rectify,
381 /// Sample it on an internal clock (`Clock` → `SampleAndHold`).
382 Hold,
383}
384
385impl ModOp {
386 /// All kinds, in categorical-site index order.
387 pub const ALL: [ModOp; 4] = [ModOp::Quantize, ModOp::Slew, ModOp::Rectify, ModOp::Hold];
388
389 /// Categorical-site index.
390 pub fn index(self) -> usize {
391 Self::ALL.iter().position(|k| *k == self).expect("in table")
392 }
393
394 /// From a categorical-site index.
395 pub fn from_index(i: usize) -> Self {
396 Self::ALL[i % Self::ALL.len()]
397 }
398
399 /// Silkscreen title, and the `RackModule::kind` tag.
400 pub fn label(self) -> &'static str {
401 match self {
402 ModOp::Quantize => "quantize",
403 ModOp::Slew => "slew",
404 ModOp::Rectify => "rectify",
405 ModOp::Hold => "hold",
406 }
407 }
408
409 /// The continuous trace sites this op owns, in `(p0, p1)` order.
410 ///
411 /// Two ops carry one parameter rather than two, and the unused `p1` is
412 /// **not** a trace site: sampling a choice nothing reads would cost prior
413 /// mass and hand MH a proposal that can never change the sound. It is
414 /// pinned to 0 by [`ModNode::normalized`] so the term still round-trips
415 /// through its own encoding.
416 pub fn param_sites(self) -> &'static [&'static str] {
417 match self {
418 ModOp::Quantize => &["qroot", "qscale"],
419 ModOp::Slew => &["rise", "fall"],
420 ModOp::Rectify => &["rmode"],
421 ModOp::Hold => &["hrate"],
422 }
423 }
424}
425
426/// A binary CV combiner over two modulation terms. Index order matches the
427/// `#pairop` categorical.
428///
429/// None of the six takes a continuous parameter, which is what keeps
430/// `#pairop` cheap: a `Pair` costs its two subterms and one categorical.
431#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
432#[serde(rename_all = "snake_case")]
433pub enum PairOp {
434 /// Lower of the two (`Min`).
435 Min,
436 /// Higher of the two (`Max`).
437 Max,
438 /// Gate AND (`LogicAnd`).
439 And,
440 /// Gate OR (`LogicOr`).
441 Or,
442 /// Gate XOR (`LogicXor`).
443 Xor,
444 /// `b` while `b` is above the gate threshold, `a` otherwise (`VcSwitch`).
445 Switch,
446}
447
448impl PairOp {
449 /// All kinds, in categorical-site index order.
450 pub const ALL: [PairOp; 6] = [
451 PairOp::Min,
452 PairOp::Max,
453 PairOp::And,
454 PairOp::Or,
455 PairOp::Xor,
456 PairOp::Switch,
457 ];
458
459 /// Categorical-site index.
460 pub fn index(self) -> usize {
461 Self::ALL.iter().position(|k| *k == self).expect("in table")
462 }
463
464 /// From a categorical-site index.
465 pub fn from_index(i: usize) -> Self {
466 Self::ALL[i % Self::ALL.len()]
467 }
468
469 /// Silkscreen title, and the `RackModule::kind` tag.
470 pub fn label(self) -> &'static str {
471 match self {
472 PairOp::Min => "min",
473 PairOp::Max => "max",
474 PairOp::And => "and",
475 PairOp::Or => "or",
476 PairOp::Xor => "xor",
477 PairOp::Switch => "switch",
478 }
479 }
480
481 /// Whether this op emits a 0–5 V **gate** rather than passing its inputs'
482 /// own voltage range through. The three logic gates do; min, max and the
483 /// switch hand back one of their inputs.
484 pub fn is_gate(self) -> bool {
485 matches!(self, PairOp::And | PairOp::Or | PairOp::Xor)
486 }
487}
488
489/// The scales quiver's `ScaleQuantizer` selects between, in **its own** index
490/// order — which is not `quiver::modules::Scale`'s.
491///
492/// `ScaleQuantizer` does not use that enum at all: it matches on
493/// `(scale_cv · 6.99) as u8` against its own inline table of **seven** scales
494/// (`utilities.rs`), and Mixolydian — index 6 of the eight-member `Scale` enum
495/// — is simply absent from it. Taking `Scale`'s order would have selected
496/// blues whenever the plate said mixolydian, and nothing at all for blues.
497pub const QUANT_SCALES: [&str; 7] = [
498 "chromatic",
499 "major",
500 "minor",
501 "penta major",
502 "penta minor",
503 "dorian",
504 "blues",
505];
506
507/// Which of [`QUANT_SCALES`] a normalized `qscale` knob selects — quiver's own
508/// `(cv · 6.99) as u8`, so the plate and the module cannot disagree.
509pub fn quant_scale_index(x: f64) -> usize {
510 ((x.clamp(0.0, 1.0) * 6.99) as usize).min(QUANT_SCALES.len() - 1)
511}
512
513/// Root-note names for the quantizer's `root` port, which quiver reads as
514/// `(cv · 11.99) as i32` semitones above C.
515pub const QUANT_ROOTS: [&str; 12] = [
516 "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
517];
518
519/// Which of [`QUANT_ROOTS`] a normalized `qroot` knob selects.
520pub fn quant_root_index(x: f64) -> usize {
521 ((x.clamp(0.0, 1.0) * 11.99) as usize).min(QUANT_ROOTS.len() - 1)
522}
523
524/// Rectifier polarities, in the order `rmode` selects them.
525///
526/// quiver's `Rectifier` has **no `mode` port**: it publishes all three at once
527/// as separate output ports (`full`, `half_pos`, `half_neg`), so this knob
528/// picks a cable at compile time rather than writing a CV.
529pub const RECT_MODES: [&str; 3] = ["full", "positive", "negative"];
530
531/// Which of [`RECT_MODES`] a normalized `rmode` knob selects — cell centres on
532/// a three-way split, the convention the other quantized knobs use.
533pub fn rect_mode_index(x: f64) -> usize {
534 ((x.clamp(0.0, 1.0) * 2.99) as usize).min(RECT_MODES.len() - 1)
535}
536
537/// A modulation-slot term: what drives a processor's mod input.
538///
539/// Modulation is a **recursive sort** with a depth bound, not a flat list of
540/// leaves: [`ModNode::Op`] wraps one modulation term in a CV processor and
541/// [`ModNode::Pair`] combines two, so `s&h rand → quantize → slew` is a term
542/// the grammar writes, the taste model reads and the rack draws.
543///
544/// The bound is [`crate::prior::PatchGrammarPrior::max_mod_depth`]. Without it
545/// the mod sort has no parsimony pressure at all — nothing in the audio tree's
546/// prior mass objects to a forty-node CV chain that moves one knob.
547///
548/// # What quiver ships that is deliberately *not* here
549///
550/// - **`StepSequencer`** — its eight step values are `steps: [f64; 8]`
551/// *internal state* with no ports (`utilities.rs`, and its own comment says
552/// so). It would need eight genome sites baked at compile time and could not
553/// be edited live, which breaks both the four-knob faceplate budget and the
554/// "every knob is a trace address you can turn" contract the instrument
555/// rests on.
556/// - **`Quantizer`** — subsumed by `ScaleQuantizer`, which is the same module
557/// with a scale rather than raw semitones.
558/// - **`Comparator`** — its useful output is a gate, and [`PairOp`]'s logic
559/// ops already make gates out of the sources that matter.
560/// - **`Multiple`, `PrecisionAdder`** — the tree already fans out, and
561/// quiver's gather already sums several cables into one port.
562/// - **`Attenuverter`** — this *is* `mod_depth`. It is already in every mod
563/// cable, one per slot.
564/// - **`EdgeDetector`** — a primitive the clocked modules use internally.
565/// - **`ChordMemory`, `Arpeggiator`** — polyphonic pitch generators; the
566/// instrument already has an arpeggiator in the keybed and a per-voice one
567/// would fight it.
568/// - **`MidSideEncode`/`Decode`** — needs a stereo sort the grammar has no
569/// way to name.
570/// - **`SamplePlayer`** — no asset pipeline, and it would make this something
571/// other than a synth.
572/// - **`Crosstalk`, `GroundLoop`, `Oversampler`, `UnitDelay`, `Mixer`** —
573/// analog imperfection, a wrapper, a one-sample primitive, and a module
574/// `Crossfader` already covers.
575///
576/// `Default` is [`ModNode::None`], which is what `#[serde(default)]` on the
577/// modulation slots that the v2 palette *added* to already-shipped variants
578/// resolves to — see [`AudioNode::Delay`].
579#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
580pub enum ModNode {
581 /// No modulation attached.
582 #[default]
583 None,
584 /// A low-frequency oscillator.
585 Lfo {
586 /// LFO waveform.
587 wave: Waveform,
588 /// Normalized rate (0-1).
589 rate: f64,
590 /// Stable identity for this node; see [`Uid`].
591 #[serde(default, skip_serializing_if = "Uid::is_new")]
592 uid: Uid,
593 },
594 /// An attack/decay envelope retriggered by the note gate.
595 Env {
596 /// Normalized attack time (0-1).
597 attack: f64,
598 /// Normalized decay time (0-1).
599 decay: f64,
600 /// Stable identity for this node; see [`Uid`].
601 #[serde(default, skip_serializing_if = "Uid::is_new")]
602 uid: Uid,
603 },
604 /// A random stepped source (noise sampled-and-held on an internal
605 /// clock) — the classic S&H burble.
606 Rand {
607 /// Normalized clock rate (0-1).
608 rate: f64,
609 /// Normalized slew between steps (0 = hard steps, 1 = a drift).
610 glide: f64,
611 /// Stable identity for this node; see [`Uid`].
612 #[serde(default, skip_serializing_if = "Uid::is_new")]
613 uid: Uid,
614 },
615 /// An envelope follower riding the *owning module's own input* — the
616 /// patch's dynamics fed back into its timbre (a filter that opens on the
617 /// transient, a fold that hardens when the note is loud).
618 ///
619 /// Deliberately a **leaf**: it takes no audio subterm of its own, so the
620 /// grammar gains a modulation source without gaining a second recursion
621 /// or a second audio-sort site. Its source is whatever the compiler has
622 /// already built for the module below it.
623 Follow {
624 /// Normalized sensitivity (detector output gain).
625 sens: f64,
626 /// Normalized release time.
627 release: f64,
628 /// Stable identity for this node; see [`Uid`].
629 #[serde(default, skip_serializing_if = "Uid::is_new")]
630 uid: Uid,
631 },
632 /// A clocked euclidean gate pattern — pulses spread as evenly as the step
633 /// count allows. A **leaf**, like the four above: it generates rather than
634 /// processes, and its clock is its own.
635 Euclid {
636 /// Normalized clock rate (0-1 → 20..300 BPM).
637 rate: f64,
638 /// Normalized step count (0-1 → 2..16 steps).
639 steps: f64,
640 /// Normalized pulse density (0-1 → 1..steps−1 pulses).
641 pulses: f64,
642 /// Stable identity for this node; see [`Uid`].
643 #[serde(default, skip_serializing_if = "Uid::is_new")]
644 uid: Uid,
645 },
646 /// A unary CV processor wrapping another modulation term.
647 ///
648 /// `p0`/`p1` are the op's two continuous knobs; which sites they occupy is
649 /// [`ModOp::param_sites`]. The ops that take one parameter pin `p1` to 0
650 /// and do not encode it.
651 Op {
652 /// Which processor.
653 kind: ModOp,
654 /// First continuous parameter (root / rise / mode / rate).
655 p0: f64,
656 /// Second continuous parameter (scale / fall); 0 and unused on the
657 /// one-parameter ops.
658 p1: f64,
659 /// The modulation term being processed. Never [`ModNode::None`] — a
660 /// processor with nothing under it is a dead cable, so the grammar
661 /// renormalizes it away rather than drawing it (see
662 /// [`ModNode::normalized`]).
663 input: Box<ModNode>,
664 /// Stable identity for this node; see [`Uid`].
665 #[serde(default, skip_serializing_if = "Uid::is_new")]
666 uid: Uid,
667 },
668 /// A binary CV combiner over two modulation terms.
669 Pair {
670 /// Which combiner.
671 kind: PairOp,
672 /// First input. Never [`ModNode::None`].
673 a: Box<ModNode>,
674 /// Second input. Never [`ModNode::None`]; on
675 /// [`PairOp::Switch`] it is also the control.
676 b: Box<ModNode>,
677 /// Stable identity for this node; see [`Uid`].
678 #[serde(default, skip_serializing_if = "Uid::is_new")]
679 uid: Uid,
680 },
681}
682
683/// An audio-sort term: sources at the leaves, processors and mixers above.
684#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
685pub enum AudioNode {
686 /// Band-limited analog-style oscillator.
687 ///
688 /// Its modulation slot lands on **pitch**, not on a timbre parameter — the
689 /// one destination no other module in the grammar can offer, and the
690 /// reason vibrato and pitch envelopes exist at all here. See
691 /// [`crate::compile`]'s `wire_pitch`.
692 ///
693 /// `mod_depth`/`modulation` are `#[serde(default)]` for the reason spelled
694 /// out on [`AudioNode::Delay`], and more urgently: a vco is in *every*
695 /// saved patch, so without the defaults no session on disk deserializes at
696 /// all.
697 Vco {
698 /// Output waveform.
699 wave: Waveform,
700 /// Octave offset, −2..=+2.
701 octave: i8,
702 /// Normalized detune (0-1 → ±50 cents).
703 detune: f64,
704 /// Normalized pitch-modulation depth (0-1 → ±0.5 octave). Absent in
705 /// pre-2A saves; defaults to 0, i.e. no pitch modulation.
706 #[serde(default)]
707 mod_depth: f64,
708 /// Pitch modulation source. Absent in pre-2A saves.
709 #[serde(default)]
710 modulation: ModNode,
711 /// Stable identity for this node; see [`Uid`].
712 #[serde(default, skip_serializing_if = "Uid::is_new")]
713 uid: Uid,
714 },
715 /// Seven-voice detuned saw stack with sub oscillator.
716 Supersaw {
717 /// Octave offset, −2..=+2.
718 octave: i8,
719 /// Normalized voice detune spread (0-1).
720 detune: f64,
721 /// Normalized center/stack blend (0-1).
722 mix: f64,
723 /// Normalized pitch-modulation depth (0-1 → ±0.5 octave). Absent in
724 /// pre-2A saves — see [`AudioNode::Vco`].
725 #[serde(default)]
726 mod_depth: f64,
727 /// Pitch modulation source. Absent in pre-2A saves.
728 #[serde(default)]
729 modulation: ModNode,
730 /// Stable identity for this node; see [`Uid`].
731 #[serde(default, skip_serializing_if = "Uid::is_new")]
732 uid: Uid,
733 },
734 /// Noise source.
735 Noise {
736 /// Noise color.
737 color: NoiseColor,
738 /// Stable identity for this node; see [`Uid`].
739 #[serde(default, skip_serializing_if = "Uid::is_new")]
740 uid: Uid,
741 },
742 /// Morphing wavetable oscillator: eight bandlimited tables, crossfaded.
743 ///
744 /// It has no `detune` — the faceplate budget is four knobs and `morph` is
745 /// the reason this module exists, so detune loses. The compiler still
746 /// routes pitch through the usual offset, pinned at "no detune".
747 Wavetable {
748 /// Base table.
749 table: TableShape,
750 /// Octave offset, −2..=+2.
751 octave: i8,
752 /// Normalized morph toward the next table (0-1).
753 morph: f64,
754 /// Normalized modulation depth (0-1, cable attenuation).
755 mod_depth: f64,
756 /// Morph modulation source.
757 modulation: ModNode,
758 /// Stable identity for this node; see [`Uid`].
759 #[serde(default, skip_serializing_if = "Uid::is_new")]
760 uid: Uid,
761 },
762 /// Karplus-Strong plucked string, retriggered by the note gate.
763 Pluck {
764 /// Octave offset, −2..=+2.
765 octave: i8,
766 /// Normalized loop damping (0-1; higher rings longer and brighter).
767 damping: f64,
768 /// Normalized excitation brightness (0-1, noise-vs-impulse blend).
769 brightness: f64,
770 /// Normalized modulation depth (0-1).
771 mod_depth: f64,
772 /// Damping modulation source.
773 modulation: ModNode,
774 /// Stable identity for this node; see [`Uid`].
775 #[serde(default, skip_serializing_if = "Uid::is_new")]
776 uid: Uid,
777 },
778 /// Formant (vocal-tract) oscillator: a glottal pulse through five
779 /// parallel resonators.
780 ///
781 /// `vowel` is a **continuous** position interpolated across A/E/I/O/U
782 /// rather than a five-way switch, which is what makes it worth a
783 /// modulation slot: sweeping it moves the formant peaks, and that is a
784 /// spectral movement `φ`'s centroid and rolloff coordinates measure
785 /// directly.
786 Formant {
787 /// Normalized vowel position (0-1 across A/E/I/O/U).
788 vowel: f64,
789 /// Normalized formant shift (0-1 → 0.5×..2× formant frequencies;
790 /// 0.5 is no shift).
791 shift: f64,
792 /// Octave offset, −2..=+2.
793 octave: i8,
794 /// Normalized modulation depth (0-1).
795 mod_depth: f64,
796 /// Vowel modulation source.
797 modulation: ModNode,
798 /// Stable identity for this node; see [`Uid`].
799 #[serde(default, skip_serializing_if = "Uid::is_new")]
800 uid: Uid,
801 },
802 /// A source that makes no sound.
803 ///
804 /// The seventh source leaf, and the only one a player can mean literally:
805 /// an unplugged socket. Before this existed the rack drew a dashed EMPTY
806 /// plate over a substitute `Vco`, so the plate was honest and the patch
807 /// underneath was not — the model was taught on a tree containing a source
808 /// the player believed was silent, and every φ coordinate measured a
809 /// render that had it in.
810 ///
811 /// It carries no parameters, which makes it the only source whose whole
812 /// genome is `#leaf` and `#src`. Its prior weight is deliberately tiny
813 /// (see [`PatchGrammarPrior::source_weights`](crate::PatchGrammarPrior))
814 /// but **not zero**: at zero the grammar gives `p = 0` to any tree
815 /// containing one, the Boltzmann target is −∞, and MH rejects every
816 /// proposal touching a hand-made hole — so unplugging a socket would
817 /// quietly make a patch un-evolvable. Small and nonzero instead means a
818 /// `Silence`-only tree renders silent, the vet gate quarantines it, and
819 /// evolution learns to avoid it. That is a path rather than a wall, and it
820 /// is the designed one.
821 ///
822 /// Appended **after** `Formant` on purpose: `#src` is a categorical whose
823 /// *index* is what a saved trace stores, so a new kind may only ever be
824 /// added at the end. Inserting one here would silently re-point every
825 /// persisted genome at a different oscillator.
826 Silence {
827 /// Stable identity for this node; see [`Uid`].
828 #[serde(default, skip_serializing_if = "Uid::is_new")]
829 uid: Uid,
830 },
831 /// Equal-power crossfade of two audio terms.
832 Mix {
833 /// Normalized balance (0 = all `a`, 1 = all `b`).
834 balance: f64,
835 /// First input.
836 a: Box<AudioNode>,
837 /// Second input.
838 b: Box<AudioNode>,
839 /// Stable identity for this node; see [`Uid`].
840 #[serde(default, skip_serializing_if = "Uid::is_new")]
841 uid: Uid,
842 },
843 /// A filter over an audio term, with an optional cutoff modulation.
844 Filter {
845 /// Which filter.
846 kind: FilterKind,
847 /// Normalized cutoff (0-1, exponential inside quiver).
848 cutoff: f64,
849 /// Normalized resonance (0-1, mapped to a bounded range).
850 resonance: f64,
851 /// Normalized modulation depth (0-1, cable attenuation).
852 mod_depth: f64,
853 /// Audio input.
854 input: Box<AudioNode>,
855 /// Cutoff modulation source.
856 modulation: ModNode,
857 /// Stable identity for this node; see [`Uid`].
858 #[serde(default, skip_serializing_if = "Uid::is_new")]
859 uid: Uid,
860 },
861 /// Wavefolder over an audio term, with optional threshold modulation.
862 Fold {
863 /// Normalized fold threshold (0-1; lower folds harder).
864 threshold: f64,
865 /// Normalized modulation depth (0-1).
866 mod_depth: f64,
867 /// Audio input.
868 input: Box<AudioNode>,
869 /// Threshold modulation source.
870 modulation: ModNode,
871 /// Stable identity for this node; see [`Uid`].
872 #[serde(default, skip_serializing_if = "Uid::is_new")]
873 uid: Uid,
874 },
875 /// Delay line over an audio term, with optional delay-time modulation.
876 ///
877 /// `mod_depth` and `modulation` are `#[serde(default)]` because this
878 /// variant **shipped without them**: the v2 palette added a modulation
879 /// slot to a module users already have saved patches of. Serde requires
880 /// every field of a struct variant unless told otherwise, so without the
881 /// defaults a single v1-era delay anywhere in a bank fails the
882 /// `SessionState` deserialize — and that failure is not local to the
883 /// patch, it takes the whole save down: bank, observation log, lineage.
884 /// A grammar that grows knobs on existing modules must default them, and
885 /// the defaults must be the *v1 behaviour* (depth 0, no source), so a
886 /// restored patch sounds like the one that was saved. Same for
887 /// [`AudioNode::Chorus`] and [`AudioNode::Reverb`]. The variants the v2
888 /// palette *introduced* (wavetable, pluck, distortion, bitcrush, phaser,
889 /// ring mod) need no such treatment: no old save can contain one.
890 Delay {
891 /// Normalized delay time (0-1).
892 time: f64,
893 /// Normalized feedback (0-1, mapped to a bounded range).
894 feedback: f64,
895 /// Normalized wet/dry mix (0-1).
896 mix: f64,
897 /// Normalized modulation depth (0-1). Absent in v1 saves; defaults
898 /// to 0, which is "no modulation reaches the delay time".
899 #[serde(default)]
900 mod_depth: f64,
901 /// Audio input.
902 input: Box<AudioNode>,
903 /// Delay-time modulation source. Absent in v1 saves; defaults to
904 /// [`ModNode::None`].
905 #[serde(default)]
906 modulation: ModNode,
907 /// Stable identity for this node; see [`Uid`].
908 #[serde(default, skip_serializing_if = "Uid::is_new")]
909 uid: Uid,
910 },
911 /// Chorus over an audio term, with optional depth modulation.
912 Chorus {
913 /// Normalized modulation rate (0-1).
914 rate: f64,
915 /// Normalized modulation depth (0-1).
916 depth: f64,
917 /// Normalized wet/dry mix (0-1).
918 mix: f64,
919 /// Normalized modulation depth of the *modulation slot* (0-1).
920 /// Absent in v1 saves — see [`AudioNode::Delay`].
921 #[serde(default)]
922 mod_depth: f64,
923 /// Audio input.
924 input: Box<AudioNode>,
925 /// Chorus-depth modulation source. Absent in v1 saves.
926 #[serde(default)]
927 modulation: ModNode,
928 /// Stable identity for this node; see [`Uid`].
929 #[serde(default, skip_serializing_if = "Uid::is_new")]
930 uid: Uid,
931 },
932 /// Algorithmic reverb (Freeverb) over an audio term, with optional size
933 /// modulation.
934 Reverb {
935 /// Normalized room size (0-1).
936 size: f64,
937 /// Normalized damping (0-1).
938 damp: f64,
939 /// Normalized wet/dry mix (0-1).
940 mix: f64,
941 /// Normalized modulation depth (0-1). Absent in v1 saves — see
942 /// [`AudioNode::Delay`].
943 #[serde(default)]
944 mod_depth: f64,
945 /// Audio input.
946 input: Box<AudioNode>,
947 /// Room-size modulation source. Absent in v1 saves.
948 #[serde(default)]
949 modulation: ModNode,
950 /// Stable identity for this node; see [`Uid`].
951 #[serde(default, skip_serializing_if = "Uid::is_new")]
952 uid: Uid,
953 },
954 /// Waveshaping distortion over an audio term.
955 Distortion {
956 /// Normalized drive (0-1).
957 drive: f64,
958 /// Normalized tone (0-1; a real one-pole lowpass, dark to open).
959 tone: f64,
960 /// Shaping curve.
961 mode: DriveMode,
962 /// Normalized modulation depth (0-1).
963 mod_depth: f64,
964 /// Audio input.
965 input: Box<AudioNode>,
966 /// Drive modulation source.
967 modulation: ModNode,
968 /// Stable identity for this node; see [`Uid`].
969 #[serde(default, skip_serializing_if = "Uid::is_new")]
970 uid: Uid,
971 },
972 /// Bit-depth and sample-rate reduction over an audio term.
973 Bitcrush {
974 /// Normalized bit depth (0-1 → 1..16 bits).
975 bits: f64,
976 /// Normalized sample-rate reduction (0-1).
977 downsample: f64,
978 /// Normalized modulation depth (0-1).
979 mod_depth: f64,
980 /// Audio input.
981 input: Box<AudioNode>,
982 /// Bit-depth modulation source.
983 modulation: ModNode,
984 /// Stable identity for this node; see [`Uid`].
985 #[serde(default, skip_serializing_if = "Uid::is_new")]
986 uid: Uid,
987 },
988 /// Swept allpass phaser over an audio term.
989 Phaser {
990 /// Normalized sweep rate (0-1).
991 rate: f64,
992 /// Normalized sweep depth (0-1).
993 depth: f64,
994 /// Normalized resonance feedback (0-1, mapped bipolar and bounded).
995 feedback: f64,
996 /// Normalized modulation depth (0-1).
997 mod_depth: f64,
998 /// Audio input.
999 input: Box<AudioNode>,
1000 /// Sweep-depth modulation source.
1001 modulation: ModNode,
1002 /// Stable identity for this node; see [`Uid`].
1003 #[serde(default, skip_serializing_if = "Uid::is_new")]
1004 uid: Uid,
1005 },
1006 /// Flanging comb over an audio term: a 1–10 ms swept delay against the
1007 /// dry signal, with signed feedback. Stereo — its `spread` decorrelates
1008 /// the two sweeps, exactly as the phaser's does.
1009 Flanger {
1010 /// Normalized sweep rate (0-1).
1011 rate: f64,
1012 /// Normalized sweep depth (0-1).
1013 depth: f64,
1014 /// Normalized feedback (0-1, mapped bipolar and bounded — the sign is
1015 /// a timbre, as on the phaser).
1016 feedback: f64,
1017 /// Normalized modulation depth (0-1).
1018 mod_depth: f64,
1019 /// Audio input.
1020 input: Box<AudioNode>,
1021 /// Sweep-depth modulation source.
1022 modulation: ModNode,
1023 /// Stable identity for this node; see [`Uid`].
1024 #[serde(default, skip_serializing_if = "Uid::is_new")]
1025 uid: Uid,
1026 },
1027 /// Amplitude modulation over an audio term, sine-to-triangle.
1028 Tremolo {
1029 /// Normalized LFO rate (0-1).
1030 rate: f64,
1031 /// Normalized depth (0-1).
1032 depth: f64,
1033 /// Normalized waveform blend (0 = sine, 1 = triangle).
1034 shape: f64,
1035 /// Normalized modulation depth (0-1).
1036 mod_depth: f64,
1037 /// Audio input.
1038 input: Box<AudioNode>,
1039 /// Depth modulation source.
1040 modulation: ModNode,
1041 /// Stable identity for this node; see [`Uid`].
1042 #[serde(default, skip_serializing_if = "Uid::is_new")]
1043 uid: Uid,
1044 },
1045 /// Pitch wobble over an audio term (a modulated delay read).
1046 ///
1047 /// The distinction from [`AudioNode::Chorus`] is `mix`: a half-wet
1048 /// vibrato *is* a chorus, because the dry and the pitch-shifted copies
1049 /// beat against each other. This module earns its place by running wet.
1050 Vibrato {
1051 /// Normalized LFO rate (0-1).
1052 rate: f64,
1053 /// Normalized depth (0-1).
1054 depth: f64,
1055 /// Normalized wet/dry mix (0-1; 1 is the module's own idiom).
1056 mix: f64,
1057 /// Normalized modulation depth (0-1).
1058 mod_depth: f64,
1059 /// Audio input.
1060 input: Box<AudioNode>,
1061 /// Depth modulation source.
1062 modulation: ModNode,
1063 /// Stable identity for this node; see [`Uid`].
1064 #[serde(default, skip_serializing_if = "Uid::is_new")]
1065 uid: Uid,
1066 },
1067 /// Three-band tone control over an audio term (low shelf, parametric mid,
1068 /// high shelf), each ±12 dB with unity at the knob's centre.
1069 Eq {
1070 /// Normalized low-shelf gain (0-1; 0.5 is 0 dB).
1071 low: f64,
1072 /// Normalized mid-bell gain (0-1; 0.5 is 0 dB).
1073 mid: f64,
1074 /// Normalized high-shelf gain (0-1; 0.5 is 0 dB).
1075 high: f64,
1076 /// Normalized modulation depth (0-1).
1077 mod_depth: f64,
1078 /// Audio input.
1079 input: Box<AudioNode>,
1080 /// Mid-gain modulation source.
1081 modulation: ModNode,
1082 /// Stable identity for this node; see [`Uid`].
1083 #[serde(default, skip_serializing_if = "Uid::is_new")]
1084 uid: Uid,
1085 },
1086 /// Granular re-reading of an audio term: overlapping Hann-windowed grains
1087 /// taken from a rolling buffer of what the input just played.
1088 Granular {
1089 /// Normalized playback position in the buffer (0-1).
1090 position: f64,
1091 /// Normalized grain size (0-1 → 10..500 ms).
1092 size: f64,
1093 /// Normalized grain density (0-1 → 1..20 grains/second).
1094 density: f64,
1095 /// Normalized modulation depth (0-1).
1096 mod_depth: f64,
1097 /// Audio input.
1098 input: Box<AudioNode>,
1099 /// Position modulation source.
1100 modulation: ModNode,
1101 /// Stable identity for this node; see [`Uid`].
1102 #[serde(default, skip_serializing_if = "Uid::is_new")]
1103 uid: Uid,
1104 },
1105 /// Ring modulation of two audio terms, crossfaded against the dry
1106 /// carrier. The grammar's **second** binary node.
1107 RingMod {
1108 /// Normalized dry/ring balance (0 = all carrier, 1 = all ring).
1109 mix: f64,
1110 /// Carrier (also the dry side of the crossfade).
1111 a: Box<AudioNode>,
1112 /// Modulator.
1113 b: Box<AudioNode>,
1114 /// Stable identity for this node; see [`Uid`].
1115 #[serde(default, skip_serializing_if = "Uid::is_new")]
1116 uid: Uid,
1117 },
1118 /// Grain-based pitch shifter over an audio term: two Hann-windowed grains
1119 /// read from a rolling buffer at a resampled rate.
1120 ///
1121 /// Unary, unlike the four modules below it. Wave one cut it alongside the
1122 /// compressor and the vocoder on the belief that it needed a second audio
1123 /// input; it does not — quiver's `PitchShifter` is `in`/`shift`/`window`/
1124 /// `mix`, one signal in and one out.
1125 Shift {
1126 /// Normalized transposition (0-1; 0.5 is unison, the ends are ∓12
1127 /// semitones).
1128 semis: f64,
1129 /// Normalized grain window (0-1 → 10..100 ms).
1130 window: f64,
1131 /// Normalized wet/dry mix (0-1).
1132 mix: f64,
1133 /// Normalized modulation depth (0-1).
1134 mod_depth: f64,
1135 /// Audio input.
1136 input: Box<AudioNode>,
1137 /// Transposition modulation source.
1138 modulation: ModNode,
1139 /// Stable identity for this node; see [`Uid`].
1140 #[serde(default, skip_serializing_if = "Uid::is_new")]
1141 uid: Uid,
1142 },
1143 /// Compressor whose detector runs on a **second** audio term: the
1144 /// grammar's third binary node, and the first whose `/1` branch is heard
1145 /// only as a control.
1146 ///
1147 /// The four binary nodes below all follow [`AudioNode::Mix`]'s child
1148 /// order: `/0` is the signal you hear, `/1` the signal that shapes it.
1149 Comp {
1150 /// Normalized threshold (0-1 → 0..5 V of detector level).
1151 threshold: f64,
1152 /// Normalized ratio (0-1 → 1:1..20:1).
1153 ratio: f64,
1154 /// Normalized makeup gain (0-1 → 1×..4×).
1155 makeup: f64,
1156 /// Normalized modulation depth (0-1).
1157 mod_depth: f64,
1158 /// The signal being compressed.
1159 input: Box<AudioNode>,
1160 /// The signal the detector listens to.
1161 sidechain: Box<AudioNode>,
1162 /// Threshold modulation source.
1163 modulation: ModNode,
1164 /// Stable identity for this node; see [`Uid`].
1165 #[serde(default, skip_serializing_if = "Uid::is_new")]
1166 uid: Uid,
1167 },
1168 /// Ducker: a key signal pulls the main signal down, in proportion to how
1169 /// far the key's envelope sits above the threshold.
1170 ///
1171 /// The difference from [`AudioNode::Comp`] is what the gain reduction is
1172 /// proportional to — a compressor reduces by the *excess over* the
1173 /// threshold in dB, a ducker by the key's level *relative to* it, up to a
1174 /// fixed depth. That is the sidechain-pump gesture rather than dynamics
1175 /// control, and it is the one people actually reach for.
1176 Duck {
1177 /// Normalized duck depth (0-1; 1 is full attenuation on a loud key).
1178 amount: f64,
1179 /// Normalized key level for full ducking (0-1 → 0..5 V).
1180 threshold: f64,
1181 /// Normalized recovery time (0-1 → 10..1000 ms).
1182 release: f64,
1183 /// Normalized modulation depth (0-1).
1184 mod_depth: f64,
1185 /// The signal being ducked.
1186 input: Box<AudioNode>,
1187 /// The key that ducks it.
1188 key: Box<AudioNode>,
1189 /// Duck-amount modulation source.
1190 modulation: ModNode,
1191 /// Stable identity for this node; see [`Uid`].
1192 #[serde(default, skip_serializing_if = "Uid::is_new")]
1193 uid: Uid,
1194 },
1195 /// Noise gate keyed by a second audio term.
1196 Gate {
1197 /// Normalized open threshold (0-1 → 0..5 V of detector level).
1198 threshold: f64,
1199 /// Normalized gate range (0-1; 1 closes to silence, 0 is a no-op).
1200 range: f64,
1201 /// Normalized release time (0-1 → 10..500 ms).
1202 release: f64,
1203 /// Normalized modulation depth (0-1).
1204 mod_depth: f64,
1205 /// The signal being gated.
1206 input: Box<AudioNode>,
1207 /// The signal that opens the gate.
1208 sidechain: Box<AudioNode>,
1209 /// Threshold modulation source.
1210 modulation: ModNode,
1211 /// Stable identity for this node; see [`Uid`].
1212 #[serde(default, skip_serializing_if = "Uid::is_new")]
1213 uid: Uid,
1214 },
1215 /// Vocoder: a bank of bandpass filters whose per-band envelopes are taken
1216 /// from the **modulator** and imposed on the **carrier**.
1217 ///
1218 /// Both branches are audible in the sense that both shape the output, but
1219 /// only the carrier's *waveform* reaches it — the modulator contributes
1220 /// its spectral envelope and nothing else, which is why `/1` is still the
1221 /// control side under this family's child-order convention.
1222 Vocoder {
1223 /// Normalized band count (0-1 → 4..16 bands).
1224 bands: f64,
1225 /// Normalized envelope attack (0-1 → 10..200 ms).
1226 attack: f64,
1227 /// Normalized envelope release (0-1 → 10..200 ms).
1228 release: f64,
1229 /// Normalized modulation depth (0-1).
1230 mod_depth: f64,
1231 /// The signal that is spectrally shaped (wants harmonic richness).
1232 carrier: Box<AudioNode>,
1233 /// The signal whose spectrum is measured (wants formants).
1234 modulator: Box<AudioNode>,
1235 /// Band-count modulation source.
1236 modulation: ModNode,
1237 /// Stable identity for this node; see [`Uid`].
1238 #[serde(default, skip_serializing_if = "Uid::is_new")]
1239 uid: Uid,
1240 },
1241}
1242
1243/// The mandatory amplitude envelope on every voice (ADSR, normalized 0-1).
1244#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1245pub struct AmpEnv {
1246 /// Normalized attack.
1247 pub attack: f64,
1248 /// Normalized decay.
1249 pub decay: f64,
1250 /// Normalized sustain level.
1251 pub sustain: f64,
1252 /// Normalized release.
1253 pub release: f64,
1254}
1255
1256/// A complete patch genome: an audio term wrapped in the mandatory voice
1257/// stage (amp ADSR → VCA → limiter → stereo out, added by the compiler).
1258#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1259pub struct PatchTree {
1260 /// Amplitude envelope parameters.
1261 pub amp: AmpEnv,
1262 /// The evolved audio-term tree.
1263 pub root: AudioNode,
1264}
1265
1266impl ModNode {
1267 /// This term's stable identity — `None` for [`ModNode::None`], which is an
1268 /// empty slot rather than a module.
1269 pub fn uid(&self) -> Option<Uid> {
1270 macro_rules! arms {
1271 ($($v:ident),*) => {
1272 match self { $(ModNode::$v { uid, .. } => Some(*uid),)* ModNode::None => None }
1273 };
1274 }
1275 mod_variants!(arms)
1276 }
1277
1278 /// Overwrite this term's identity (a no-op on [`ModNode::None`]).
1279 pub fn set_uid(&mut self, id: Uid) {
1280 macro_rules! arms {
1281 ($($v:ident),*) => {
1282 match self { $(ModNode::$v { uid, .. } => *uid = id,)* ModNode::None => {} }
1283 };
1284 }
1285 mod_variants!(arms)
1286 }
1287
1288 /// Sub-terms in key-index order (`Op`'s input is `/0`; a `Pair`'s two
1289 /// branches are `/0` and `/1`).
1290 pub fn children(&self) -> Vec<&ModNode> {
1291 match self {
1292 ModNode::Op { input, .. } => vec![input],
1293 ModNode::Pair { a, b, .. } => vec![a, b],
1294 _ => Vec::new(),
1295 }
1296 }
1297
1298 /// Mutable [`Self::children`].
1299 pub fn children_mut(&mut self) -> Vec<&mut ModNode> {
1300 match self {
1301 ModNode::Op { input, .. } => vec![input],
1302 ModNode::Pair { a, b, .. } => vec![a, b],
1303 _ => Vec::new(),
1304 }
1305 }
1306
1307 /// Number of probabilistic-choice sites this mod term occupies.
1308 pub fn site_count(&self) -> usize {
1309 match self {
1310 ModNode::None => 1, // #mod
1311 ModNode::Lfo { .. } => 3, // #mod #wave #rate
1312 ModNode::Env { .. } => 3, // #mod #att #dec
1313 ModNode::Rand { .. } => 3, // #mod #rate #glide
1314 ModNode::Follow { .. } => 3, // #mod #sens #rel
1315 // #mod #erate #esteps #epulses
1316 ModNode::Euclid { .. } => 4,
1317 // #mod #modop, the op's own knobs, then the subterm.
1318 ModNode::Op { kind, input, .. } => 2 + kind.param_sites().len() + input.site_count(),
1319 // #mod #pairop and two subterms — no continuous sites of its own.
1320 ModNode::Pair { a, b, .. } => 2 + a.site_count() + b.site_count(),
1321 }
1322 }
1323
1324 /// Nesting depth of this modulation term: an empty slot is 0, a leaf is 1,
1325 /// and every processor above one adds a level.
1326 ///
1327 /// This is the quantity [`crate::prior::PatchGrammarPrior::max_mod_depth`]
1328 /// bounds and that `φ`'s `mod_depth_mean` averages.
1329 pub fn depth(&self) -> usize {
1330 match self {
1331 ModNode::None => 0,
1332 ModNode::Lfo { .. }
1333 | ModNode::Env { .. }
1334 | ModNode::Rand { .. }
1335 | ModNode::Follow { .. }
1336 | ModNode::Euclid { .. } => 1,
1337 ModNode::Op { input, .. } => 1 + input.depth(),
1338 ModNode::Pair { a, b, .. } => 1 + a.depth().max(b.depth()),
1339 }
1340 }
1341
1342 /// Number of nodes in this modulation term (an empty slot is 0).
1343 pub fn size(&self) -> usize {
1344 match self {
1345 ModNode::None => 0,
1346 ModNode::Op { input, .. } => 1 + input.size(),
1347 ModNode::Pair { a, b, .. } => 1 + a.size() + b.size(),
1348 _ => 1,
1349 }
1350 }
1351
1352 /// The canonical form of a hand-built modulation term.
1353 ///
1354 /// Two normalizations, both of which the prior enforces by construction
1355 /// (it renormalizes the `#mod` categorical over the non-empty kinds below
1356 /// an `Op` or inside a `Pair`) but which an explicit term arriving from
1357 /// the panel through
1358 /// [`StructOp::SetModTree`](crate::mutate::StructOp::SetModTree) can
1359 /// violate:
1360 ///
1361 /// - **A processor over nothing is nothing.** `Op` with an empty input is
1362 /// a quantizer fed 0 V; `Pair` with two empty inputs is a logic gate
1363 /// fed two zeroes. Both compile to a cable carrying a constant, which is
1364 /// a module on the rack that does nothing. A `Pair` with *one* empty
1365 /// input collapses to the other side rather than to nothing — `And(x, 0)`
1366 /// is identically low and `Min(x, 0)` throws away the positive half, so
1367 /// there is no reading under which the empty branch is a musical choice.
1368 /// - **An unused parameter is pinned to 0.** [`ModOp::param_sites`] does
1369 /// not encode `p1` for the one-parameter ops, so a term carrying a
1370 /// non-zero one would not survive its own trace round-trip.
1371 ///
1372 /// Folding here rather than in the prior is deliberate: the generative
1373 /// model and [`crate::genome`]'s encoding are asserted site-for-site
1374 /// identical, so a prior that *sampled* a degenerate term and then folded
1375 /// it would emit choices the encoding does not.
1376 pub fn normalized(self) -> ModNode {
1377 match self {
1378 ModNode::Op {
1379 kind,
1380 p0,
1381 p1,
1382 input,
1383 ..
1384 } => match input.normalized() {
1385 ModNode::None => ModNode::None,
1386 input => ModNode::Op {
1387 uid: Uid::NEW,
1388 kind,
1389 p0,
1390 p1: if kind.param_sites().len() > 1 {
1391 p1
1392 } else {
1393 0.0
1394 },
1395 input: Box::new(input),
1396 },
1397 },
1398 ModNode::Pair { kind, a, b, .. } => match (a.normalized(), b.normalized()) {
1399 (ModNode::None, ModNode::None) => ModNode::None,
1400 (a, ModNode::None) => a,
1401 (ModNode::None, b) => b,
1402 (a, b) => ModNode::Pair {
1403 uid: Uid::NEW,
1404 kind,
1405 a: Box::new(a),
1406 b: Box::new(b),
1407 },
1408 },
1409 leaf => leaf,
1410 }
1411 }
1412}
1413
1414impl AudioNode {
1415 /// This node's stable identity ([`Uid::NEW`] until the tree is settled).
1416 pub fn uid(&self) -> Uid {
1417 macro_rules! arms {
1418 ($($v:ident),*) => { match self { $(AudioNode::$v { uid, .. } => *uid,)* } };
1419 }
1420 audio_variants!(arms)
1421 }
1422
1423 /// Overwrite this node's identity. Only the settling and inheritance
1424 /// passes have any business calling this.
1425 pub fn set_uid(&mut self, id: Uid) {
1426 macro_rules! arms {
1427 ($($v:ident),*) => { match self { $(AudioNode::$v { uid, .. } => *uid = id,)* } };
1428 }
1429 audio_variants!(arms)
1430 }
1431
1432 /// Audio children in **key-index order** — the same `/0`, `/1` order
1433 /// `mutate::child_mut`, `describe` and `genome` all use, so index `i` here
1434 /// and the key segment `i` there address the same node.
1435 pub fn children(&self) -> Vec<&AudioNode> {
1436 match self {
1437 AudioNode::Mix { a, b, .. } | AudioNode::RingMod { a, b, .. } => vec![a, b],
1438 AudioNode::Comp {
1439 input,
1440 sidechain: other,
1441 ..
1442 }
1443 | AudioNode::Gate {
1444 input,
1445 sidechain: other,
1446 ..
1447 }
1448 | AudioNode::Duck {
1449 input, key: other, ..
1450 }
1451 | AudioNode::Vocoder {
1452 carrier: input,
1453 modulator: other,
1454 ..
1455 } => vec![input, other],
1456 AudioNode::Shift { input, .. }
1457 | AudioNode::Filter { input, .. }
1458 | AudioNode::Fold { input, .. }
1459 | AudioNode::Delay { input, .. }
1460 | AudioNode::Chorus { input, .. }
1461 | AudioNode::Reverb { input, .. }
1462 | AudioNode::Distortion { input, .. }
1463 | AudioNode::Bitcrush { input, .. }
1464 | AudioNode::Phaser { input, .. }
1465 | AudioNode::Flanger { input, .. }
1466 | AudioNode::Tremolo { input, .. }
1467 | AudioNode::Vibrato { input, .. }
1468 | AudioNode::Eq { input, .. }
1469 | AudioNode::Granular { input, .. } => vec![input],
1470 // The sources. Spelled out rather than caught by a wildcard so
1471 // that a new production cannot quietly become childless — and so
1472 // become invisible to identity — by being forgotten here.
1473 AudioNode::Vco { .. }
1474 | AudioNode::Supersaw { .. }
1475 | AudioNode::Noise { .. }
1476 | AudioNode::Wavetable { .. }
1477 | AudioNode::Pluck { .. }
1478 | AudioNode::Formant { .. }
1479 | AudioNode::Silence { .. } => Vec::new(),
1480 }
1481 }
1482
1483 /// Mutable [`Self::children`].
1484 pub fn children_mut(&mut self) -> Vec<&mut AudioNode> {
1485 match self {
1486 AudioNode::Mix { a, b, .. } | AudioNode::RingMod { a, b, .. } => vec![a, b],
1487 AudioNode::Comp {
1488 input,
1489 sidechain: other,
1490 ..
1491 }
1492 | AudioNode::Gate {
1493 input,
1494 sidechain: other,
1495 ..
1496 }
1497 | AudioNode::Duck {
1498 input, key: other, ..
1499 }
1500 | AudioNode::Vocoder {
1501 carrier: input,
1502 modulator: other,
1503 ..
1504 } => vec![input, other],
1505 AudioNode::Shift { input, .. }
1506 | AudioNode::Filter { input, .. }
1507 | AudioNode::Fold { input, .. }
1508 | AudioNode::Delay { input, .. }
1509 | AudioNode::Chorus { input, .. }
1510 | AudioNode::Reverb { input, .. }
1511 | AudioNode::Distortion { input, .. }
1512 | AudioNode::Bitcrush { input, .. }
1513 | AudioNode::Phaser { input, .. }
1514 | AudioNode::Flanger { input, .. }
1515 | AudioNode::Tremolo { input, .. }
1516 | AudioNode::Vibrato { input, .. }
1517 | AudioNode::Eq { input, .. }
1518 | AudioNode::Granular { input, .. } => vec![input],
1519 // The sources. Spelled out rather than caught by a wildcard so
1520 // that a new production cannot quietly become childless — and so
1521 // become invisible to identity — by being forgotten here.
1522 AudioNode::Vco { .. }
1523 | AudioNode::Supersaw { .. }
1524 | AudioNode::Noise { .. }
1525 | AudioNode::Wavetable { .. }
1526 | AudioNode::Pluck { .. }
1527 | AudioNode::Formant { .. }
1528 | AudioNode::Silence { .. } => Vec::new(),
1529 }
1530 }
1531
1532 /// The modulation term hanging off this node's one mod slot, if the module
1533 /// has one (`Mix`, `RingMod`, `Noise` and `Shift` do not).
1534 pub fn modulation(&self) -> Option<&ModNode> {
1535 macro_rules! arms {
1536 ($($v:ident),*) => {
1537 match self {
1538 $(AudioNode::$v { modulation, .. } => Some(modulation),)*
1539 // The four productions with nothing worth modulating:
1540 // `Noise` has only a colour, `Silence` has nothing at all,
1541 // and `Mix`/`RingMod` have two audio inputs and a blend.
1542 // Named rather than wildcarded so a new module with a slot
1543 // cannot silently land here.
1544 AudioNode::Noise { .. }
1545 | AudioNode::Silence { .. }
1546 | AudioNode::Mix { .. }
1547 | AudioNode::RingMod { .. } => None,
1548 }
1549 };
1550 }
1551 modulated_variants!(arms)
1552 }
1553
1554 /// Mutable [`Self::modulation`].
1555 pub fn modulation_mut(&mut self) -> Option<&mut ModNode> {
1556 macro_rules! arms {
1557 ($($v:ident),*) => {
1558 match self {
1559 $(AudioNode::$v { modulation, .. } => Some(modulation),)*
1560 // The four productions with nothing worth modulating:
1561 // `Noise` has only a colour, `Silence` has nothing at all,
1562 // and `Mix`/`RingMod` have two audio inputs and a blend.
1563 // Named rather than wildcarded so a new module with a slot
1564 // cannot silently land here.
1565 AudioNode::Noise { .. }
1566 | AudioNode::Silence { .. }
1567 | AudioNode::Mix { .. }
1568 | AudioNode::RingMod { .. } => None,
1569 }
1570 };
1571 }
1572 modulated_variants!(arms)
1573 }
1574
1575 /// Tree depth (a source leaf is depth 1).
1576 pub fn depth(&self) -> usize {
1577 match self {
1578 AudioNode::Vco { .. }
1579 | AudioNode::Supersaw { .. }
1580 | AudioNode::Noise { .. }
1581 | AudioNode::Wavetable { .. }
1582 | AudioNode::Pluck { .. }
1583 | AudioNode::Formant { .. }
1584 | AudioNode::Silence { .. } => 1,
1585 AudioNode::Mix { a, b, .. } | AudioNode::RingMod { a, b, .. } => {
1586 1 + a.depth().max(b.depth())
1587 }
1588 // The control branch is a full audio subtree that has to be built
1589 // and rendered, so it counts toward depth exactly as a mixer's
1590 // second input does — the render budget cannot tell the two apart.
1591 AudioNode::Comp {
1592 input,
1593 sidechain: other,
1594 ..
1595 }
1596 | AudioNode::Gate {
1597 input,
1598 sidechain: other,
1599 ..
1600 }
1601 | AudioNode::Duck {
1602 input, key: other, ..
1603 }
1604 | AudioNode::Vocoder {
1605 carrier: input,
1606 modulator: other,
1607 ..
1608 } => 1 + input.depth().max(other.depth()),
1609 AudioNode::Shift { input, .. }
1610 | AudioNode::Filter { input, .. }
1611 | AudioNode::Fold { input, .. }
1612 | AudioNode::Delay { input, .. }
1613 | AudioNode::Chorus { input, .. }
1614 | AudioNode::Reverb { input, .. }
1615 | AudioNode::Distortion { input, .. }
1616 | AudioNode::Bitcrush { input, .. }
1617 | AudioNode::Phaser { input, .. }
1618 | AudioNode::Flanger { input, .. }
1619 | AudioNode::Tremolo { input, .. }
1620 | AudioNode::Vibrato { input, .. }
1621 | AudioNode::Eq { input, .. }
1622 | AudioNode::Granular { input, .. } => 1 + input.depth(),
1623 }
1624 }
1625
1626 /// Number of audio nodes in the tree.
1627 pub fn size(&self) -> usize {
1628 match self {
1629 AudioNode::Vco { .. }
1630 | AudioNode::Supersaw { .. }
1631 | AudioNode::Noise { .. }
1632 | AudioNode::Wavetable { .. }
1633 | AudioNode::Pluck { .. }
1634 | AudioNode::Formant { .. }
1635 | AudioNode::Silence { .. } => 1,
1636 AudioNode::Mix { a, b, .. } | AudioNode::RingMod { a, b, .. } => {
1637 1 + a.size() + b.size()
1638 }
1639 AudioNode::Comp {
1640 input,
1641 sidechain: other,
1642 ..
1643 }
1644 | AudioNode::Gate {
1645 input,
1646 sidechain: other,
1647 ..
1648 }
1649 | AudioNode::Duck {
1650 input, key: other, ..
1651 }
1652 | AudioNode::Vocoder {
1653 carrier: input,
1654 modulator: other,
1655 ..
1656 } => 1 + input.size() + other.size(),
1657 AudioNode::Shift { input, .. }
1658 | AudioNode::Filter { input, .. }
1659 | AudioNode::Fold { input, .. }
1660 | AudioNode::Delay { input, .. }
1661 | AudioNode::Chorus { input, .. }
1662 | AudioNode::Reverb { input, .. }
1663 | AudioNode::Distortion { input, .. }
1664 | AudioNode::Bitcrush { input, .. }
1665 | AudioNode::Phaser { input, .. }
1666 | AudioNode::Flanger { input, .. }
1667 | AudioNode::Tremolo { input, .. }
1668 | AudioNode::Vibrato { input, .. }
1669 | AudioNode::Eq { input, .. }
1670 | AudioNode::Granular { input, .. } => 1 + input.size(),
1671 }
1672 }
1673
1674 /// Number of probabilistic-choice sites in this subtree (including the
1675 /// structural `#leaf`/`#src`/`#op` sites and any modulation subterm).
1676 pub fn site_count(&self) -> usize {
1677 // Every node carries #leaf plus either #src or #op.
1678 match self {
1679 // #wave #oct #det #mdepth
1680 AudioNode::Vco { modulation, .. } => 2 + 4 + modulation.site_count(),
1681 // #oct #det #smix #mdepth
1682 AudioNode::Supersaw { modulation, .. } => 2 + 4 + modulation.site_count(),
1683 AudioNode::Noise { .. } => 2 + 1, // #color
1684 // #vowel #fshift #oct #mdepth
1685 AudioNode::Formant { modulation, .. } => 2 + 4 + modulation.site_count(),
1686 AudioNode::Wavetable { modulation, .. } => 2 + 4 + modulation.site_count(), // #table #oct #morph #mdepth
1687 AudioNode::Pluck { modulation, .. } => 2 + 4 + modulation.site_count(), // #oct #damp #bright #mdepth
1688 // The only production that is nothing but its two structural
1689 // sites: no parameters, no modulation slot, nothing to draw.
1690 AudioNode::Silence { .. } => 2,
1691 AudioNode::Mix { a, b, .. } => 2 + 1 + a.site_count() + b.site_count(),
1692 AudioNode::RingMod { a, b, .. } => 2 + 1 + a.site_count() + b.site_count(),
1693 AudioNode::Filter {
1694 input, modulation, ..
1695 } => 2 + 4 + input.site_count() + modulation.site_count(),
1696 AudioNode::Fold {
1697 input, modulation, ..
1698 } => 2 + 2 + input.site_count() + modulation.site_count(),
1699 AudioNode::Delay {
1700 input, modulation, ..
1701 } => 2 + 4 + input.site_count() + modulation.site_count(),
1702 AudioNode::Chorus {
1703 input, modulation, ..
1704 } => 2 + 4 + input.site_count() + modulation.site_count(),
1705 AudioNode::Reverb {
1706 input, modulation, ..
1707 } => 2 + 4 + input.site_count() + modulation.site_count(),
1708 AudioNode::Distortion {
1709 input, modulation, ..
1710 } => 2 + 4 + input.site_count() + modulation.site_count(),
1711 AudioNode::Bitcrush {
1712 input, modulation, ..
1713 } => 2 + 3 + input.site_count() + modulation.site_count(),
1714 AudioNode::Phaser {
1715 input, modulation, ..
1716 } => 2 + 4 + input.site_count() + modulation.site_count(),
1717 AudioNode::Flanger {
1718 input, modulation, ..
1719 } => 2 + 4 + input.site_count() + modulation.site_count(),
1720 AudioNode::Tremolo {
1721 input, modulation, ..
1722 } => 2 + 4 + input.site_count() + modulation.site_count(),
1723 AudioNode::Vibrato {
1724 input, modulation, ..
1725 } => 2 + 4 + input.site_count() + modulation.site_count(),
1726 AudioNode::Eq {
1727 input, modulation, ..
1728 } => 2 + 4 + input.site_count() + modulation.site_count(),
1729 AudioNode::Granular {
1730 input, modulation, ..
1731 } => 2 + 4 + input.site_count() + modulation.site_count(),
1732 // #semis #window #smix #mdepth
1733 AudioNode::Shift {
1734 input, modulation, ..
1735 } => 2 + 4 + input.site_count() + modulation.site_count(),
1736 // Four knobs plus a modulation subterm, as the unary processors
1737 // have — and *two* audio subterms, as the binary ones do.
1738 AudioNode::Comp {
1739 input,
1740 sidechain,
1741 modulation,
1742 ..
1743 } => 2 + 4 + input.site_count() + sidechain.site_count() + modulation.site_count(),
1744 AudioNode::Duck {
1745 input,
1746 key,
1747 modulation,
1748 ..
1749 } => 2 + 4 + input.site_count() + key.site_count() + modulation.site_count(),
1750 AudioNode::Gate {
1751 input,
1752 sidechain,
1753 modulation,
1754 ..
1755 } => 2 + 4 + input.site_count() + sidechain.site_count() + modulation.site_count(),
1756 AudioNode::Vocoder {
1757 carrier,
1758 modulator,
1759 modulation,
1760 ..
1761 } => 2 + 4 + carrier.site_count() + modulator.site_count() + modulation.site_count(),
1762 }
1763 }
1764
1765 /// Compact s-expression rendering for logs and tests.
1766 pub fn to_sexpr(&self) -> String {
1767 match self {
1768 AudioNode::Vco {
1769 wave,
1770 octave,
1771 detune,
1772 modulation,
1773 ..
1774 } => format!(
1775 "(vco {} {octave:+} {detune:.2} {})",
1776 wave.port_name(),
1777 mod_sexpr(modulation)
1778 ),
1779 AudioNode::Supersaw {
1780 octave,
1781 detune,
1782 mix,
1783 modulation,
1784 ..
1785 } => format!(
1786 "(supersaw {octave:+} {detune:.2} {mix:.2} {})",
1787 mod_sexpr(modulation)
1788 ),
1789 AudioNode::Noise { color, .. } => format!("(noise {})", color.port_name()),
1790 AudioNode::Silence { .. } => "(silence)".into(),
1791 AudioNode::Formant {
1792 vowel,
1793 shift,
1794 octave,
1795 modulation,
1796 ..
1797 } => format!(
1798 "(formant v={vowel:.2} s={shift:.2} {octave:+} {})",
1799 mod_sexpr(modulation)
1800 ),
1801 AudioNode::Wavetable {
1802 table,
1803 octave,
1804 morph,
1805 modulation,
1806 ..
1807 } => format!(
1808 "(wavetable {} {octave:+} m={morph:.2} {})",
1809 table.label(),
1810 mod_sexpr(modulation)
1811 ),
1812 AudioNode::Pluck {
1813 octave,
1814 damping,
1815 brightness,
1816 modulation,
1817 ..
1818 } => format!(
1819 "(pluck {octave:+} d={damping:.2} b={brightness:.2} {})",
1820 mod_sexpr(modulation)
1821 ),
1822 AudioNode::Mix { balance, a, b, .. } => {
1823 format!("(mix {balance:.2} {} {})", a.to_sexpr(), b.to_sexpr())
1824 }
1825 AudioNode::RingMod { mix, a, b, .. } => {
1826 format!("(ringmod {mix:.2} {} {})", a.to_sexpr(), b.to_sexpr())
1827 }
1828 AudioNode::Filter {
1829 kind,
1830 cutoff,
1831 resonance,
1832 input,
1833 modulation,
1834 ..
1835 } => format!(
1836 "(filter {kind:?} c={cutoff:.2} r={resonance:.2} {} {})",
1837 mod_sexpr(modulation),
1838 input.to_sexpr()
1839 ),
1840 AudioNode::Fold {
1841 threshold,
1842 input,
1843 modulation,
1844 ..
1845 } => format!(
1846 "(fold t={threshold:.2} {} {})",
1847 mod_sexpr(modulation),
1848 input.to_sexpr()
1849 ),
1850 AudioNode::Delay {
1851 time,
1852 feedback,
1853 mix,
1854 input,
1855 modulation,
1856 ..
1857 } => format!(
1858 "(delay t={time:.2} fb={feedback:.2} mix={mix:.2} {} {})",
1859 mod_sexpr(modulation),
1860 input.to_sexpr()
1861 ),
1862 AudioNode::Chorus {
1863 rate,
1864 depth,
1865 mix,
1866 input,
1867 modulation,
1868 ..
1869 } => format!(
1870 "(chorus r={rate:.2} d={depth:.2} mix={mix:.2} {} {})",
1871 mod_sexpr(modulation),
1872 input.to_sexpr()
1873 ),
1874 AudioNode::Reverb {
1875 size,
1876 damp,
1877 mix,
1878 input,
1879 modulation,
1880 ..
1881 } => format!(
1882 "(reverb s={size:.2} d={damp:.2} mix={mix:.2} {} {})",
1883 mod_sexpr(modulation),
1884 input.to_sexpr()
1885 ),
1886 AudioNode::Distortion {
1887 drive,
1888 tone,
1889 mode,
1890 input,
1891 modulation,
1892 ..
1893 } => format!(
1894 "(dist {} g={drive:.2} t={tone:.2} {} {})",
1895 mode.label(),
1896 mod_sexpr(modulation),
1897 input.to_sexpr()
1898 ),
1899 AudioNode::Bitcrush {
1900 bits,
1901 downsample,
1902 input,
1903 modulation,
1904 ..
1905 } => format!(
1906 "(bitcrush b={bits:.2} r={downsample:.2} {} {})",
1907 mod_sexpr(modulation),
1908 input.to_sexpr()
1909 ),
1910 AudioNode::Phaser {
1911 rate,
1912 depth,
1913 feedback,
1914 input,
1915 modulation,
1916 ..
1917 } => format!(
1918 "(phaser r={rate:.2} d={depth:.2} fb={feedback:.2} {} {})",
1919 mod_sexpr(modulation),
1920 input.to_sexpr()
1921 ),
1922 AudioNode::Flanger {
1923 rate,
1924 depth,
1925 feedback,
1926 input,
1927 modulation,
1928 ..
1929 } => format!(
1930 "(flanger r={rate:.2} d={depth:.2} fb={feedback:.2} {} {})",
1931 mod_sexpr(modulation),
1932 input.to_sexpr()
1933 ),
1934 AudioNode::Tremolo {
1935 rate,
1936 depth,
1937 shape,
1938 input,
1939 modulation,
1940 ..
1941 } => format!(
1942 "(tremolo r={rate:.2} d={depth:.2} s={shape:.2} {} {})",
1943 mod_sexpr(modulation),
1944 input.to_sexpr()
1945 ),
1946 AudioNode::Vibrato {
1947 rate,
1948 depth,
1949 mix,
1950 input,
1951 modulation,
1952 ..
1953 } => format!(
1954 "(vibrato r={rate:.2} d={depth:.2} mix={mix:.2} {} {})",
1955 mod_sexpr(modulation),
1956 input.to_sexpr()
1957 ),
1958 AudioNode::Eq {
1959 low,
1960 mid,
1961 high,
1962 input,
1963 modulation,
1964 ..
1965 } => format!(
1966 "(eq l={low:.2} m={mid:.2} h={high:.2} {} {})",
1967 mod_sexpr(modulation),
1968 input.to_sexpr()
1969 ),
1970 AudioNode::Granular {
1971 position,
1972 size,
1973 density,
1974 input,
1975 modulation,
1976 ..
1977 } => format!(
1978 "(granular p={position:.2} s={size:.2} d={density:.2} {} {})",
1979 mod_sexpr(modulation),
1980 input.to_sexpr()
1981 ),
1982 AudioNode::Shift {
1983 semis,
1984 window,
1985 mix,
1986 input,
1987 modulation,
1988 ..
1989 } => format!(
1990 "(shift s={semis:.2} w={window:.2} mix={mix:.2} {} {})",
1991 mod_sexpr(modulation),
1992 input.to_sexpr()
1993 ),
1994 AudioNode::Comp {
1995 threshold,
1996 ratio,
1997 makeup,
1998 input,
1999 sidechain,
2000 modulation,
2001 ..
2002 } => format!(
2003 "(comp t={threshold:.2} r={ratio:.2} m={makeup:.2} {} {} {})",
2004 mod_sexpr(modulation),
2005 input.to_sexpr(),
2006 sidechain.to_sexpr()
2007 ),
2008 AudioNode::Duck {
2009 amount,
2010 threshold,
2011 release,
2012 input,
2013 key,
2014 modulation,
2015 ..
2016 } => format!(
2017 "(duck a={amount:.2} t={threshold:.2} r={release:.2} {} {} {})",
2018 mod_sexpr(modulation),
2019 input.to_sexpr(),
2020 key.to_sexpr()
2021 ),
2022 AudioNode::Gate {
2023 threshold,
2024 range,
2025 release,
2026 input,
2027 sidechain,
2028 modulation,
2029 ..
2030 } => format!(
2031 "(gate t={threshold:.2} rg={range:.2} r={release:.2} {} {} {})",
2032 mod_sexpr(modulation),
2033 input.to_sexpr(),
2034 sidechain.to_sexpr()
2035 ),
2036 AudioNode::Vocoder {
2037 bands,
2038 attack,
2039 release,
2040 carrier,
2041 modulator,
2042 modulation,
2043 ..
2044 } => format!(
2045 "(vocoder b={bands:.2} a={attack:.2} r={release:.2} {} {} {})",
2046 mod_sexpr(modulation),
2047 carrier.to_sexpr(),
2048 modulator.to_sexpr()
2049 ),
2050 }
2051 }
2052}
2053
2054fn mod_sexpr(m: &ModNode) -> String {
2055 match m {
2056 ModNode::None => "nomod".to_string(),
2057 ModNode::Lfo { wave, rate, .. } => format!("(lfo {} {rate:.2})", wave.port_name()),
2058 ModNode::Env { attack, decay, .. } => format!("(env a={attack:.2} d={decay:.2})"),
2059 ModNode::Rand { rate, glide, .. } => format!("(rand r={rate:.2} g={glide:.2})"),
2060 ModNode::Follow { sens, release, .. } => format!("(follow s={sens:.2} r={release:.2})"),
2061 ModNode::Euclid {
2062 rate,
2063 steps,
2064 pulses,
2065 ..
2066 } => format!("(euclid r={rate:.2} s={steps:.2} p={pulses:.2})"),
2067 ModNode::Op {
2068 kind,
2069 p0,
2070 p1,
2071 input,
2072 ..
2073 } => match kind.param_sites().len() {
2074 1 => format!("({} {p0:.2} {})", kind.label(), mod_sexpr(input)),
2075 _ => format!("({} {p0:.2} {p1:.2} {})", kind.label(), mod_sexpr(input)),
2076 },
2077 ModNode::Pair { kind, a, b, .. } => {
2078 format!("({} {} {})", kind.label(), mod_sexpr(a), mod_sexpr(b))
2079 }
2080 }
2081}
2082
2083fn spine_tags(n: &AudioNode, out: &mut Vec<&'static str>) {
2084 match n {
2085 AudioNode::Vco { wave, .. } => out.push(wave.port_name()),
2086 AudioNode::Supersaw { .. } => out.push("ssaw"),
2087 AudioNode::Noise { .. } => out.push("noiz"),
2088 AudioNode::Wavetable { table, .. } => out.push(match table {
2089 TableShape::Sine => "wsin",
2090 TableShape::Tri => "wtri",
2091 TableShape::Saw => "wsaw",
2092 TableShape::Square => "wsqr",
2093 TableShape::Pulse25 | TableShape::Pulse12 => "wpul",
2094 TableShape::FormantA | TableShape::FormantO => "wfmt",
2095 }),
2096 AudioNode::Pluck { .. } => out.push("plk"),
2097 AudioNode::Formant { .. } => out.push("vox"),
2098 AudioNode::Silence { .. } => out.push("mute"),
2099 AudioNode::Mix { a, .. } => {
2100 spine_tags(a, out);
2101 out.push("mix");
2102 }
2103 // The carrier is the spine; the modulator is a side branch, exactly as
2104 // for `Mix`.
2105 AudioNode::RingMod { a, .. } => {
2106 spine_tags(a, out);
2107 out.push("ring");
2108 }
2109 AudioNode::Filter { kind, input, .. } => {
2110 spine_tags(input, out);
2111 out.push(match kind {
2112 FilterKind::Ladder => "ladr",
2113 FilterKind::SvfLp => "lp",
2114 FilterKind::SvfBp => "bp",
2115 FilterKind::SvfHp => "hp",
2116 });
2117 }
2118 AudioNode::Fold { input, .. } => {
2119 spine_tags(input, out);
2120 out.push("fold");
2121 }
2122 AudioNode::Delay { input, .. } => {
2123 spine_tags(input, out);
2124 out.push("dly");
2125 }
2126 AudioNode::Chorus { input, .. } => {
2127 spine_tags(input, out);
2128 out.push("cho");
2129 }
2130 AudioNode::Reverb { input, .. } => {
2131 spine_tags(input, out);
2132 out.push("rvb");
2133 }
2134 AudioNode::Distortion { input, mode, .. } => {
2135 spine_tags(input, out);
2136 out.push(match mode {
2137 DriveMode::Soft => "drv",
2138 DriveMode::Hard => "clip",
2139 DriveMode::Tube => "tube",
2140 });
2141 }
2142 AudioNode::Bitcrush { input, .. } => {
2143 spine_tags(input, out);
2144 out.push("crsh");
2145 }
2146 AudioNode::Phaser { input, .. } => {
2147 spine_tags(input, out);
2148 out.push("phsr");
2149 }
2150 AudioNode::Flanger { input, .. } => {
2151 spine_tags(input, out);
2152 out.push("flng");
2153 }
2154 AudioNode::Tremolo { input, .. } => {
2155 spine_tags(input, out);
2156 out.push("trem");
2157 }
2158 AudioNode::Vibrato { input, .. } => {
2159 spine_tags(input, out);
2160 out.push("vib");
2161 }
2162 AudioNode::Eq { input, .. } => {
2163 spine_tags(input, out);
2164 out.push("eq");
2165 }
2166 AudioNode::Granular { input, .. } => {
2167 spine_tags(input, out);
2168 out.push("gran");
2169 }
2170 AudioNode::Shift { input, .. } => {
2171 spine_tags(input, out);
2172 out.push("shft");
2173 }
2174 // `/0` is the spine and `/1` the side branch, exactly as for `Mix` and
2175 // `RingMod` — and here the convention is not just a convention: the
2176 // control branch is never heard on its own.
2177 AudioNode::Comp { input, .. } => {
2178 spine_tags(input, out);
2179 out.push("comp");
2180 }
2181 AudioNode::Duck { input, .. } => {
2182 spine_tags(input, out);
2183 out.push("duck");
2184 }
2185 AudioNode::Gate { input, .. } => {
2186 spine_tags(input, out);
2187 out.push("gate");
2188 }
2189 AudioNode::Vocoder { carrier, .. } => {
2190 spine_tags(carrier, out);
2191 out.push("voc");
2192 }
2193 }
2194}
2195
2196impl PatchTree {
2197 /// Total probabilistic-choice sites (amp envelope + tree).
2198 pub fn site_count(&self) -> usize {
2199 4 + self.root.site_count()
2200 }
2201
2202 /// Short human-readable signature along the main signal spine
2203 /// (`saw·ladr·dly`) — the default display name for unnamed patches.
2204 pub fn signature(&self) -> String {
2205 let mut tags = Vec::new();
2206 spine_tags(&self.root, &mut tags);
2207 if tags.len() > 4 {
2208 let skipped = tags.len() - 4;
2209 let tail: Vec<&str> = tags[skipped..].to_vec();
2210 format!("{}+·{}", skipped, tail.join("·"))
2211 } else {
2212 tags.join("·")
2213 }
2214 }
2215
2216 /// Compact s-expression rendering for logs and tests.
2217 pub fn to_sexpr(&self) -> String {
2218 format!(
2219 "(voice a={:.2} d={:.2} s={:.2} r={:.2} {})",
2220 self.amp.attack,
2221 self.amp.decay,
2222 self.amp.sustain,
2223 self.amp.release,
2224 self.root.to_sexpr()
2225 )
2226 }
2227
2228 /// Give every node that still lacks an identity a fresh one, and break any
2229 /// duplicates.
2230 ///
2231 /// This is the *settle* step: the point at which an anonymous tree becomes
2232 /// a thing a person can point at. Call it wherever a tree is adopted —
2233 /// admitted to the pool, put on the bench, restored from a save, arrived
2234 /// from the panel — and nowhere in the middle of search.
2235 ///
2236 /// De-duplication is not paranoia. A whole-tree replace from the panel is
2237 /// arbitrary client JSON, and the one gesture that most obviously produces
2238 /// a repeat is "duplicate this module": copying a subtree copies its uids,
2239 /// and two nodes claiming one identity is worse than no identity at all —
2240 /// a lock on either would light both. First occurrence in walk order keeps
2241 /// the id; the copies are minted fresh.
2242 pub fn ensure_uids(&mut self) {
2243 let mut seen = std::collections::HashSet::new();
2244 settle_audio(&mut self.root, &mut seen);
2245 }
2246
2247 /// Strip every identity back to [`Uid::NEW`].
2248 ///
2249 /// Used where a tree must hash or serialize as pure content — the render
2250 /// memo's key, above all, which would otherwise miss on every refinement
2251 /// step and invalidate every persisted row the moment uids started moving.
2252 pub fn clear_uids(&mut self) {
2253 walk_audio_mut(&mut self.root, &mut |n| n.set_uid(Uid::NEW), &mut |m| {
2254 m.set_uid(Uid::NEW)
2255 });
2256 }
2257
2258 /// Carry `parent`'s identities onto this tree wherever the structure is
2259 /// unchanged.
2260 ///
2261 /// **This is what makes locks and hand positions survive ⚡ evolve.**
2262 /// Refinement does not mutate a tree in place — `EvolutionChain` proposes
2263 /// over the *trace*, and every accepted step reconstructs the whole genome
2264 /// through [`crate::genome`]'s decoder, which knows nothing about uids and
2265 /// cannot: a trace is a map from address to value. So a refined child comes
2266 /// back structurally near-identical to its seed and completely anonymous,
2267 /// and without this pass every generation would look to the UI like a brand
2268 /// new patch — the exact failure (`R6`) that would make pinned routings and
2269 /// freeform layout read as broken.
2270 ///
2271 /// The match is positional and shallow-by-variant: walking both trees in
2272 /// lockstep, a node inherits its counterpart's identity **only if it is the
2273 /// same variant**, but the walk descends either way. That last part
2274 /// matters — a step that swaps one filter for a reverb should not orphan
2275 /// the entire chain beneath it, which was not touched.
2276 ///
2277 /// Anything left unmatched (a genuinely new module, a branch that grew)
2278 /// stays [`Uid::NEW`] and is minted by the following [`Self::ensure_uids`].
2279 pub fn inherit_uids(&mut self, parent: &PatchTree) {
2280 inherit_audio(&mut self.root, &parent.root);
2281 }
2282}
2283
2284/// Depth-first over an audio subtree and every modulation term hanging off it.
2285fn walk_audio_mut(
2286 n: &mut AudioNode,
2287 fa: &mut impl FnMut(&mut AudioNode),
2288 fm: &mut impl FnMut(&mut ModNode),
2289) {
2290 fa(n);
2291 if let Some(m) = n.modulation_mut() {
2292 walk_mod_mut(m, fm);
2293 }
2294 for c in n.children_mut() {
2295 walk_audio_mut(c, fa, fm);
2296 }
2297}
2298
2299fn walk_mod_mut(n: &mut ModNode, fm: &mut impl FnMut(&mut ModNode)) {
2300 fm(n);
2301 for c in n.children_mut() {
2302 walk_mod_mut(c, fm);
2303 }
2304}
2305
2306fn settle_audio(n: &mut AudioNode, seen: &mut std::collections::HashSet<u64>) {
2307 let uid = n.uid();
2308 if uid.is_new() || !seen.insert(uid.0) {
2309 let fresh = Uid::mint();
2310 seen.insert(fresh.0);
2311 n.set_uid(fresh);
2312 } else {
2313 Uid::observe(uid);
2314 }
2315 if let Some(m) = n.modulation_mut() {
2316 settle_mod(m, seen);
2317 }
2318 for c in n.children_mut() {
2319 settle_audio(c, seen);
2320 }
2321}
2322
2323fn settle_mod(n: &mut ModNode, seen: &mut std::collections::HashSet<u64>) {
2324 // `None` is an empty slot, not a module: it draws no plate, owns no knobs
2325 // and cannot be locked, so it has nothing to be identified by — and
2326 // `ModNode::uid` says so by returning `None` for it.
2327 if let Some(uid) = n.uid() {
2328 if uid.is_new() || !seen.insert(uid.0) {
2329 let fresh = Uid::mint();
2330 seen.insert(fresh.0);
2331 n.set_uid(fresh);
2332 } else {
2333 Uid::observe(uid);
2334 }
2335 }
2336 for c in n.children_mut() {
2337 settle_mod(c, seen);
2338 }
2339}
2340
2341fn inherit_audio(child: &mut AudioNode, parent: &AudioNode) {
2342 if std::mem::discriminant(&*child) == std::mem::discriminant(parent) {
2343 child.set_uid(parent.uid());
2344 }
2345 if let (Some(cm), Some(pm)) = (child.modulation_mut(), parent.modulation()) {
2346 inherit_mod(cm, pm);
2347 }
2348 let pk = parent.children();
2349 for (i, c) in child.children_mut().into_iter().enumerate() {
2350 if let Some(p) = pk.get(i) {
2351 inherit_audio(c, p);
2352 }
2353 }
2354}
2355
2356fn inherit_mod(child: &mut ModNode, parent: &ModNode) {
2357 if std::mem::discriminant(&*child) == std::mem::discriminant(parent) {
2358 if let Some(u) = parent.uid() {
2359 child.set_uid(u);
2360 }
2361 }
2362 let pk = parent.children();
2363 for (i, c) in child.children_mut().into_iter().enumerate() {
2364 if let Some(p) = pk.get(i) {
2365 inherit_mod(c, p);
2366 }
2367 }
2368}