pub enum ModNode {
None,
Lfo {
wave: Waveform,
rate: f64,
uid: Uid,
},
Env {
attack: f64,
decay: f64,
uid: Uid,
},
Rand {
rate: f64,
glide: f64,
uid: Uid,
},
Follow {
sens: f64,
release: f64,
uid: Uid,
},
Euclid {
rate: f64,
steps: f64,
pulses: f64,
uid: Uid,
},
Op {
kind: ModOp,
p0: f64,
p1: f64,
input: Box<ModNode>,
uid: Uid,
},
Pair {
kind: PairOp,
a: Box<ModNode>,
b: Box<ModNode>,
uid: Uid,
},
}Expand description
A modulation-slot term: what drives a processor’s mod input.
Modulation is a recursive sort with a depth bound, not a flat list of
leaves: ModNode::Op wraps one modulation term in a CV processor and
ModNode::Pair combines two, so s&h rand → quantize → slew is a term
the grammar writes, the taste model reads and the rack draws.
The bound is crate::prior::PatchGrammarPrior::max_mod_depth. Without it
the mod sort has no parsimony pressure at all — nothing in the audio tree’s
prior mass objects to a forty-node CV chain that moves one knob.
§What quiver ships that is deliberately not here
StepSequencer— its eight step values aresteps: [f64; 8]internal state with no ports (utilities.rs, and its own comment says so). It would need eight genome sites baked at compile time and could not be edited live, which breaks both the four-knob faceplate budget and the “every knob is a trace address you can turn” contract the instrument rests on.Quantizer— subsumed byScaleQuantizer, which is the same module with a scale rather than raw semitones.Comparator— its useful output is a gate, andPairOp’s logic ops already make gates out of the sources that matter.Multiple,PrecisionAdder— the tree already fans out, and quiver’s gather already sums several cables into one port.Attenuverter— this ismod_depth. It is already in every mod cable, one per slot.EdgeDetector— a primitive the clocked modules use internally.ChordMemory,Arpeggiator— polyphonic pitch generators; the instrument already has an arpeggiator in the keybed and a per-voice one would fight it.MidSideEncode/Decode— needs a stereo sort the grammar has no way to name.SamplePlayer— no asset pipeline, and it would make this something other than a synth.Crosstalk,GroundLoop,Oversampler,UnitDelay,Mixer— analog imperfection, a wrapper, a one-sample primitive, and a moduleCrossfaderalready covers.
Default is ModNode::None, which is what #[serde(default)] on the
modulation slots that the v2 palette added to already-shipped variants
resolves to — see AudioNode::Delay.
Variants§
None
No modulation attached.
Lfo
A low-frequency oscillator.
Fields
Env
An attack/decay envelope retriggered by the note gate.
Fields
Rand
A random stepped source (noise sampled-and-held on an internal clock) — the classic S&H burble.
Fields
Follow
An envelope follower riding the owning module’s own input — the patch’s dynamics fed back into its timbre (a filter that opens on the transient, a fold that hardens when the note is loud).
Deliberately a leaf: it takes no audio subterm of its own, so the grammar gains a modulation source without gaining a second recursion or a second audio-sort site. Its source is whatever the compiler has already built for the module below it.
Fields
Euclid
A clocked euclidean gate pattern — pulses spread as evenly as the step count allows. A leaf, like the four above: it generates rather than processes, and its clock is its own.
Fields
Op
A unary CV processor wrapping another modulation term.
p0/p1 are the op’s two continuous knobs; which sites they occupy is
ModOp::param_sites. The ops that take one parameter pin p1 to 0
and do not encode it.
Fields
input: Box<ModNode>The modulation term being processed. Never ModNode::None — a
processor with nothing under it is a dead cable, so the grammar
renormalizes it away rather than drawing it (see
ModNode::normalized).
Pair
A binary CV combiner over two modulation terms.
Fields
a: Box<ModNode>First input. Never ModNode::None.
b: Box<ModNode>Second input. Never ModNode::None; on
PairOp::Switch it is also the control.
Implementations§
Source§impl ModNode
impl ModNode
Sourcepub fn uid(&self) -> Option<Uid>
pub fn uid(&self) -> Option<Uid>
This term’s stable identity — None for ModNode::None, which is an
empty slot rather than a module.
Sourcepub fn set_uid(&mut self, id: Uid)
pub fn set_uid(&mut self, id: Uid)
Overwrite this term’s identity (a no-op on ModNode::None).
Sourcepub fn children(&self) -> Vec<&ModNode>
pub fn children(&self) -> Vec<&ModNode>
Sub-terms in key-index order (Op’s input is /0; a Pair’s two
branches are /0 and /1).
Sourcepub fn children_mut(&mut self) -> Vec<&mut ModNode>
pub fn children_mut(&mut self) -> Vec<&mut ModNode>
Mutable Self::children.
Sourcepub fn site_count(&self) -> usize
pub fn site_count(&self) -> usize
Number of probabilistic-choice sites this mod term occupies.
Sourcepub fn depth(&self) -> usize
pub fn depth(&self) -> usize
Nesting depth of this modulation term: an empty slot is 0, a leaf is 1, and every processor above one adds a level.
This is the quantity crate::prior::PatchGrammarPrior::max_mod_depth
bounds and that φ’s mod_depth_mean averages.
Sourcepub fn normalized(self) -> ModNode
pub fn normalized(self) -> ModNode
The canonical form of a hand-built modulation term.
Two normalizations, both of which the prior enforces by construction
(it renormalizes the #mod categorical over the non-empty kinds below
an Op or inside a Pair) but which an explicit term arriving from
the panel through
StructOp::SetModTree can
violate:
- A processor over nothing is nothing.
Opwith an empty input is a quantizer fed 0 V;Pairwith two empty inputs is a logic gate fed two zeroes. Both compile to a cable carrying a constant, which is a module on the rack that does nothing. APairwith one empty input collapses to the other side rather than to nothing —And(x, 0)is identically low andMin(x, 0)throws away the positive half, so there is no reading under which the empty branch is a musical choice. - An unused parameter is pinned to 0.
ModOp::param_sitesdoes not encodep1for the one-parameter ops, so a term carrying a non-zero one would not survive its own trace round-trip.
Folding here rather than in the prior is deliberate: the generative
model and crate::genome’s encoding are asserted site-for-site
identical, so a prior that sampled a degenerate term and then folded
it would emit choices the encoding does not.