Skip to main content

tono_core/dsl/
node.rs

1//! The synthesis-graph [`Node`] enum — every source, combinator, and
2//! processor in the DSL — with its per-family serde defaults co-located.
3
4use super::{
5    Adsr, DriveShape, KitStyle, Mode, NoiseColor, SeqNote, SeqWave, SuperWave, Track, Value,
6    default_gain,
7};
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11// Serde `default = "..."` requires free functions. Values with non-obvious
12// origins: q 0.707 is Butterworth (maximally flat).
13fn default_duty() -> Value {
14    Value::Const(0.5)
15}
16fn default_q() -> f32 {
17    0.707
18}
19fn default_steps_per_beat() -> u32 {
20    4
21}
22// Shared by chorus / flanger / phaser ("mod fx").
23fn default_mod_depth() -> f32 {
24    0.5
25}
26fn default_mod_mix() -> f32 {
27    0.5
28}
29fn default_chorus_rate() -> f32 {
30    1.5
31}
32fn default_flanger_rate() -> f32 {
33    0.25
34}
35fn default_flanger_feedback() -> f32 {
36    0.5
37}
38fn default_phaser_rate() -> f32 {
39    0.4
40}
41fn default_phaser_feedback() -> f32 {
42    0.3
43}
44fn default_comp_attack() -> f32 {
45    0.005
46}
47fn default_comp_release() -> f32 {
48    0.08
49}
50fn default_voices() -> u32 {
51    7
52}
53fn default_detune() -> f32 {
54    15.0
55}
56// Seq instrument defaults: ratio 1 + decaying index ≈ an FM piano strike;
57// pluck decay 0.996 rings ~1 s in the mid register.
58fn default_seq_fm_ratio() -> f32 {
59    1.0
60}
61fn default_seq_fm_index() -> f32 {
62    5.0
63}
64fn default_seq_fm_strike() -> f32 {
65    0.2
66}
67fn default_pluck_decay() -> f32 {
68    0.996
69}
70// Guitar tone stages on the `pluck` voice. All default to identity (0.0), so
71// omitting them renders byte-identically and draws no extra RNG.
72fn default_pluck_body() -> f32 {
73    0.0
74}
75fn default_pluck_pick() -> f32 {
76    0.0
77}
78fn default_pluck_tone() -> f32 {
79    0.0
80}
81// Piano tone knobs (engine-3 additive `piano` voice). Every default reproduces
82// the concert-grand kernel bit-for-bit (x*1.0==x, x/1.0==x, 0.125==1.0/8.0 in
83// f32), so a doc that omits them renders byte-identically. Variants set others.
84fn default_piano_hammer() -> f32 {
85    1.0
86}
87fn default_piano_strike() -> f32 {
88    0.125
89}
90fn default_piano_inharm() -> f32 {
91    1.0
92}
93fn default_piano_detune() -> f32 {
94    1.0
95}
96fn default_piano_decay() -> f32 {
97    1.0
98}
99// Bass tone knobs (the `bass` voice). Every default is the current voice's
100// hard-coded constant, so omitting them renders byte-identically.
101fn default_bass_cutoff() -> f32 {
102    250.0
103}
104fn default_bass_env() -> f32 {
105    700.0
106}
107fn default_bass_env_vel() -> f32 {
108    1100.0
109}
110fn default_bass_decay() -> f32 {
111    0.15
112}
113fn default_bass_click() -> f32 {
114    0.0
115}
116fn default_bass_body() -> f32 {
117    0.7
118}
119fn default_bass_sub() -> f32 {
120    0.45
121}
122fn default_bass_sub_ratio() -> f32 {
123    1.0
124}
125fn default_bass_drive() -> f32 {
126    0.0
127}
128fn default_bass_body_decay() -> f32 {
129    2.0
130}
131fn default_duck_amount() -> f32 {
132    0.8
133}
134fn default_duck_attack() -> f32 {
135    0.005
136}
137fn default_duck_release() -> f32 {
138    0.25
139}
140fn default_modal_mix() -> f32 {
141    1.0
142}
143fn default_impact_hardness() -> f32 {
144    0.5
145}
146fn default_dust_decay() -> f32 {
147    0.02
148}
149
150/// A node in the synthesis graph. Every node evaluates to a mono signal.
151#[non_exhaustive]
152#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
153#[serde(tag = "type", rename_all = "lowercase")]
154pub enum Node {
155    // --- Sources (output audio in [-1, 1]) ---
156    /// Square/pulse wave with variable duty cycle. `duty` may be a modulator
157    /// (e.g. an `lfo`) for PWM — the classic moving chiptune lead.
158    Square {
159        /// Frequency in Hz.
160        freq: Value,
161        /// Fraction of the period spent "high", 0..1 (0.5 = symmetric square).
162        #[serde(default = "default_duty")]
163        duty: Value,
164    },
165    /// Triangle wave.
166    Triangle {
167        /// Frequency in Hz.
168        freq: Value,
169    },
170    /// Sawtooth wave.
171    Sawtooth {
172        /// Frequency in Hz.
173        freq: Value,
174    },
175    /// Sine wave.
176    Sine {
177        /// Frequency in Hz.
178        freq: Value,
179    },
180    /// Noise source (percussion / explosion / texture). White by default;
181    /// `pink` is warmer (−3 dB/oct, good for wind/rumble), `brown` darker still.
182    Noise {
183        /// Spectral colour of the noise.
184        #[serde(default)]
185        color: NoiseColor,
186    },
187    /// Two-operator FM: a carrier at `freq` is phase-modulated by an operator
188    /// at `freq * ratio` with modulation `index`. Bells, e-piano, metallic
189    /// basses. Slide `index` down for a struck/plucked attack.
190    Fm {
191        /// Carrier frequency in Hz.
192        freq: Value,
193        /// Modulator frequency as a multiple of the carrier (e.g. 2.0, 3.5).
194        ratio: f32,
195        /// Modulation index (depth). Higher ⇒ brighter / more sidebands.
196        index: Value,
197    },
198    /// Unison "super" oscillator: `voices` detuned copies of a band-limited
199    /// saw/square, summed for a fat, wide lead or pad (the supersaw). Detune is
200    /// spread symmetrically across `detune_cents`.
201    Super {
202        /// Oscillator shape for every voice.
203        #[serde(default)]
204        wave: SuperWave,
205        /// Centre frequency in Hz (modulatable).
206        freq: Value,
207        /// Number of detuned voices (1..=16).
208        #[serde(default = "default_voices")]
209        voices: u32,
210        /// Total detune spread in cents across all voices.
211        #[serde(default = "default_detune")]
212        detune_cents: f32,
213    },
214    /// A note sequencer: plays `notes` on a tempo grid, each with its own pitch,
215    /// length, and a shared per-note ADSR. This is how you write real melodies,
216    /// basslines, and drum patterns (rests = gaps between notes).
217    Seq {
218        /// Tempo in beats per minute.
219        bpm: f32,
220        /// Grid resolution: steps per beat (4 = sixteenth notes).
221        #[serde(default = "default_steps_per_beat")]
222        steps_per_beat: u32,
223        /// Instrument used for every note.
224        wave: SeqWave,
225        /// Duty cycle when `wave` is `square` (may be modulated for PWM).
226        #[serde(default = "default_duty")]
227        duty: Value,
228        /// FM voice knobs (`wave: "fm"`), flattened onto the node.
229        #[serde(flatten)]
230        fm: FmKnobs,
231        /// Plucked-string knobs (`wave: "pluck"`), flattened onto the node.
232        #[serde(flatten)]
233        pluck: PluckKnobs,
234        /// Piano tone knobs (`wave: "piano"`, engine ≥ 3), flattened onto the node.
235        #[serde(flatten)]
236        piano: PianoKnobs,
237        /// Drum-kit voicing when `wave` is `kit`. Omitted ⇒ `classic` (the
238        /// original kit, byte-identical); `acoustic`/`electronic`/`808` are
239        /// alternate synthesized kits.
240        #[serde(default)]
241        kit: KitStyle,
242        /// Bass tone knobs (`wave: "bass"`), flattened onto the node.
243        #[serde(flatten)]
244        bass: BassKnobs,
245        /// SoundFont sampler settings (`wave: "sampler"`), flattened onto the node.
246        #[serde(flatten)]
247        sf2: Sf2Knobs,
248        /// Swing, 0..1: every off-beat grid step is delayed by this fraction
249        /// of a step (0 = straight, ~0.55 = classic shuffle). Off-beats are
250        /// odd steps, so set `steps_per_beat` to the swung subdivision.
251        #[serde(default)]
252        swing: f32,
253        /// Humanize, 0..1: deterministic per-note timing and velocity jitter
254        /// (from the doc's seed) so repeats stop sounding machine-perfect.
255        /// 0.1–0.25 is a tasteful player; 1 is sloppy.
256        #[serde(default)]
257        humanize: f32,
258        /// Per-note amplitude envelope.
259        env: Adsr,
260        /// The notes to play.
261        notes: Vec<SeqNote>,
262    },
263
264    /// Impact exciter: a short excitation burst that models the contact force
265    /// of a strike — a single raised-cosine force pulse whose width is set by
266    /// `hardness` (hard = brief, bright click; soft = wider, duller thud),
267    /// scaled by `velocity`. On its own it is a faint tick; its job is to
268    /// *excite* a resonant body — put it before a `modal` bank (or any
269    /// resonant filter) in a `chain`: `chain[ impact, modal ]` is a struck
270    /// object. The harder the strike, the more high modes it lights up.
271    Impact {
272        /// Strike hardness, 0..1 (1 = hardest / brightest / shortest contact).
273        #[serde(default = "default_impact_hardness")]
274        hardness: f32,
275        /// Strike velocity / level, 0..1.
276        #[serde(default = "default_gain")]
277        velocity: f32,
278    },
279    /// Sparse stochastic impulses — a Poisson click train. `density` events per
280    /// second fire at random times with random ± amplitude, each decaying over
281    /// `decay` seconds (0 = bare single-sample impulses). The grain generator
282    /// behind crackle textures: fire, rain, geiger ticks, sparks, debris. Feed
283    /// it through a `bandpass`/`highpass` (for tone) or a `modal` (for pitched
284    /// debris). Its randomness draws from the layer's deterministic stream, so
285    /// like `noise` it is edit-stable within its own mixer layer.
286    Dust {
287        /// Mean events per second.
288        density: f32,
289        /// Per-grain decay time in seconds (0 = single-sample impulses).
290        #[serde(default = "default_dust_decay")]
291        decay: f32,
292    },
293
294    // --- Envelope (outputs a 0..1 control signal) ---
295    /// ADSR amplitude envelope with an sfxr-style `punch` transient.
296    Env {
297        /// Envelope shape.
298        #[serde(flatten)]
299        adsr: Adsr,
300    },
301
302    // --- Combinators ---
303    /// The mixing console — only valid as the document root. Each track is a
304    /// mono graph placed on the stereo stage with its own pan and gain
305    /// (sampler tracks keep their native stereo); `master` is a processor
306    /// chain applied to the stereo bus (compressor glue, reverb — the reverb
307    /// runs with decorrelated left/right tails). This is how multi-
308    /// instrument music gets a real stereo image instead of a mono sum.
309    Tracks {
310        /// The mixer channels.
311        tracks: Vec<Track>,
312        /// Processors applied to the stereo master bus, in order.
313        #[serde(default)]
314        master: Vec<Node>,
315    },
316    /// Sum (layer) all inputs.
317    Mix {
318        /// Branches to add together.
319        inputs: Vec<Node>,
320    },
321    /// Multiply all inputs (typically `source × envelope`).
322    Mul {
323        /// Branches to multiply together.
324        inputs: Vec<Node>,
325    },
326    /// Serial pipe: stage 0 is a source, each later stage processes the prior output.
327    Chain {
328        /// Ordered processing stages.
329        stages: Vec<Node>,
330    },
331
332    // --- Processors (transform the preceding signal in a chain) ---
333    /// Resonant low-pass filter.
334    Lowpass {
335        /// Cutoff frequency in Hz.
336        cutoff: Value,
337        /// Resonance / quality factor.
338        #[serde(default = "default_q")]
339        q: f32,
340    },
341    /// Resonant high-pass filter.
342    Highpass {
343        /// Cutoff frequency in Hz.
344        cutoff: Value,
345        /// Resonance / quality factor.
346        #[serde(default = "default_q")]
347        q: f32,
348    },
349    /// Band-pass filter.
350    Bandpass {
351        /// Center frequency in Hz.
352        cutoff: Value,
353        /// Resonance / quality factor.
354        #[serde(default = "default_q")]
355        q: f32,
356    },
357    /// Notch (band-reject) filter: removes a narrow band around `cutoff` (hum /
358    /// resonance removal).
359    Notch {
360        /// Center frequency in Hz.
361        cutoff: Value,
362        /// Quality factor (higher ⇒ narrower notch).
363        #[serde(default = "default_q")]
364        q: f32,
365    },
366    /// Peaking EQ: boost or cut a band around `cutoff` by `gain_db` (surgical
367    /// tone shaping — act on the brightness/centroid the analyzer reports).
368    Peak {
369        /// Center frequency in Hz.
370        cutoff: Value,
371        /// Quality factor (bandwidth).
372        #[serde(default = "default_q")]
373        q: f32,
374        /// Gain in dB (positive boosts, negative cuts).
375        #[serde(default)]
376        gain_db: f32,
377    },
378    /// Low shelf: boost/cut everything below `cutoff` by `gain_db` (add weight or
379    /// thin out lows).
380    Lowshelf {
381        /// Shelf corner frequency in Hz.
382        cutoff: Value,
383        /// Gain in dB.
384        #[serde(default)]
385        gain_db: f32,
386    },
387    /// High shelf: boost/cut everything above `cutoff` by `gain_db` (air / de-ess).
388    Highshelf {
389        /// Shelf corner frequency in Hz.
390        cutoff: Value,
391        /// Gain in dB.
392        #[serde(default)]
393        gain_db: f32,
394    },
395    /// Scale the signal by a (possibly modulated) factor.
396    Gain {
397        /// Multiplier.
398        amount: Value,
399    },
400    /// Quantize amplitude to `bits` of resolution for crunch.
401    Bitcrush {
402        /// Bit depth, 1..16.
403        bits: u8,
404    },
405    /// Sample-rate reduction by an integer `factor` for lo-fi grit.
406    Downsample {
407        /// Hold each sample for this many samples.
408        factor: u32,
409    },
410    /// Feedback delay (echo / comb).
411    Delay {
412        /// Delay time in seconds.
413        secs: f32,
414        /// Feedback amount, 0..1.
415        #[serde(default)]
416        feedback: f32,
417    },
418    /// Schroeder-style reverb.
419    Reverb {
420        /// Room size, 0..1 (larger ⇒ longer tail).
421        #[serde(default)]
422        room: f32,
423        /// Dry/wet mix, 0..1.
424        #[serde(default)]
425        mix: f32,
426    },
427    /// Modal resonator bank: a set of damped sinusoidal partials (`modes`)
428    /// excited by the incoming signal — a struck/resonant object's *body*.
429    /// Bells, glass, metal bars, wood, ceramic, coins, and the resonant ping
430    /// of UI/impact sounds, none of which the oscillators can voice cleanly.
431    /// Each mode is one 2-pole resonator (a constant-peak-gain bandpass), so a
432    /// bank is N parallel resonators — cheap, stable, deterministic. Use it as
433    /// a chain stage after an excitation: `chain[ impact, modal ]`. The
434    /// excitation's brightness lights the modes; the modes' frequencies and
435    /// decays define the timbre. Author modes explicitly — the cookbook lists
436    /// frequency/decay tables for common materials to copy and tune.
437    Modal {
438        /// The resonant partials (1..=64). Each is a damped sine.
439        modes: Vec<Mode>,
440        /// Wet/dry mix, 0..1 (1 = pure resonance; lower keeps some of the raw
441        /// excitation transient for extra attack click).
442        #[serde(default = "default_modal_mix")]
443        mix: f32,
444    },
445    /// Waveshaper for saturation / distortion. `amount` is pre-gain; `shape`
446    /// chooses the curve (warm `tanh`, aggressive `hard` clip, or `fold`back).
447    Drive {
448        /// Drive amount (pre-gain into the shaper).
449        amount: Value,
450        /// Distortion curve.
451        #[serde(default)]
452        shape: DriveShape,
453        /// Antiderivative anti-aliasing. The waveshaper's harmonics fold back
454        /// as inharmonic alias dirt at the base rate; ADAA suppresses that for
455        /// a clean, hi-fi distortion. Honoured only when the document's
456        /// `engine` is ≥ 1 (so legacy documents stay bit-exact); within an
457        /// engine-1 document it is on by default — set `false` to hear the raw
458        /// aliasing curve. Omitted ⇒ follow the engine.
459        #[serde(default, skip_serializing_if = "Option::is_none")]
460        aa: Option<bool>,
461    },
462    /// Ring modulation: multiply the signal by a sine carrier at `freq`. Metallic,
463    /// clangorous, robotic textures.
464    RingMod {
465        /// Carrier frequency in Hz.
466        freq: Value,
467    },
468    /// Flanger: a very short modulated delay with feedback — the classic jet
469    /// sweep / metallic whoosh. Stronger and more resonant than chorus.
470    Flanger {
471        /// LFO rate in Hz.
472        #[serde(default = "default_flanger_rate")]
473        rate: f32,
474        /// Modulation depth, 0..1.
475        #[serde(default = "default_mod_depth")]
476        depth: f32,
477        /// Feedback amount, 0..1 (more ⇒ more resonant).
478        #[serde(default = "default_flanger_feedback")]
479        feedback: f32,
480        /// Dry/wet mix, 0..1.
481        #[serde(default = "default_mod_mix")]
482        mix: f32,
483    },
484    /// Phaser: swept all-pass notches — a hollow, swooshing movement for pads,
485    /// lasers, and sci-fi textures.
486    Phaser {
487        /// LFO sweep rate in Hz.
488        #[serde(default = "default_phaser_rate")]
489        rate: f32,
490        /// Sweep depth, 0..1.
491        #[serde(default = "default_mod_depth")]
492        depth: f32,
493        /// Feedback amount, 0..1.
494        #[serde(default = "default_phaser_feedback")]
495        feedback: f32,
496        /// Dry/wet mix, 0..1.
497        #[serde(default = "default_mod_mix")]
498        mix: f32,
499    },
500    /// Chorus: a short modulated delay mixed with the dry signal for thickening
501    /// and width.
502    Chorus {
503        /// LFO rate in Hz.
504        #[serde(default = "default_chorus_rate")]
505        rate: f32,
506        /// Modulation depth, 0..1.
507        #[serde(default = "default_mod_depth")]
508        depth: f32,
509        /// Dry/wet mix, 0..1.
510        #[serde(default = "default_mod_mix")]
511        mix: f32,
512    },
513    /// Sidechain duck: gain-reduces the chained signal whenever `trigger` is
514    /// loud — the pumping that glues a bass or pad to the kick. The trigger
515    /// is rendered silently (it only steers the gain); chain the audible
516    /// kick separately in the mix.
517    Duck {
518        /// The signal whose loudness drives the ducking (e.g. the kick seq).
519        trigger: Box<Node>,
520        /// Duck depth, 0..1 (1 = fully silent at the trigger's peak).
521        #[serde(default = "default_duck_amount")]
522        amount: f32,
523        /// Gain-reduction attack in seconds.
524        #[serde(default = "default_duck_attack")]
525        attack: f32,
526        /// Recovery time in seconds (the "pump" length).
527        #[serde(default = "default_duck_release")]
528        release: f32,
529    },
530    /// Dynamic-range compressor: tames peaks above `threshold` (dBFS) by `ratio`,
531    /// with `attack`/`release` ballistics, then applies `makeup` gain (dB). The
532    /// glue behind loud, punchy game audio.
533    Compress {
534        /// Threshold in dBFS (e.g. -18).
535        threshold: f32,
536        /// Compression ratio (e.g. 4 = 4:1).
537        ratio: f32,
538        /// Attack time in seconds.
539        #[serde(default = "default_comp_attack")]
540        attack: f32,
541        /// Release time in seconds.
542        #[serde(default = "default_comp_release")]
543        release: f32,
544        /// Make-up gain in dB.
545        #[serde(default)]
546        makeup: f32,
547    },
548}
549
550impl Node {
551    /// True if this node only makes sense as a non-first stage of a `chain`
552    /// (it transforms an incoming signal rather than generating one).
553    pub fn is_processor(&self) -> bool {
554        matches!(
555            self,
556            Node::Lowpass { .. }
557                | Node::Highpass { .. }
558                | Node::Bandpass { .. }
559                | Node::Notch { .. }
560                | Node::Peak { .. }
561                | Node::Lowshelf { .. }
562                | Node::Highshelf { .. }
563                | Node::Gain { .. }
564                | Node::Bitcrush { .. }
565                | Node::Downsample { .. }
566                | Node::Delay { .. }
567                | Node::Reverb { .. }
568                | Node::Modal { .. }
569                | Node::Drive { .. }
570                | Node::RingMod { .. }
571                | Node::Chorus { .. }
572                | Node::Flanger { .. }
573                | Node::Phaser { .. }
574                | Node::Compress { .. }
575                | Node::Duck { .. }
576        )
577    }
578
579    /// Every direct child of this node — the nested graphs of the combinator
580    /// variants: `mix`/`mul` inputs, `chain` stages, a `tracks`' layers then
581    /// its `master` chain, and a `duck`'s trigger. The ONE traversal
582    /// definition every walker shares, so a new variant can never be silently
583    /// skipped the way the hand-written walkers were (the `duck`-trigger
584    /// omissions in the vary and MIDI walkers were exactly that class). The
585    /// match is exhaustive *on purpose*: adding a variant without a children
586    /// decision is a compile error, never a silent skip.
587    pub fn children(&self) -> Children<'_> {
588        let kind = match self {
589            Node::Mix { inputs } | Node::Mul { inputs } => ChildrenKind::Slice(inputs.iter()),
590            Node::Chain { stages } => ChildrenKind::Slice(stages.iter()),
591            Node::Tracks { tracks, master } => ChildrenKind::Tracks {
592                tracks: tracks.iter(),
593                master: master.iter(),
594            },
595            Node::Duck { trigger, .. } => ChildrenKind::One(Some(trigger)),
596            Node::Square { .. }
597            | Node::Triangle { .. }
598            | Node::Sawtooth { .. }
599            | Node::Sine { .. }
600            | Node::Noise { .. }
601            | Node::Fm { .. }
602            | Node::Super { .. }
603            | Node::Seq { .. }
604            | Node::Impact { .. }
605            | Node::Dust { .. }
606            | Node::Env { .. }
607            | Node::Lowpass { .. }
608            | Node::Highpass { .. }
609            | Node::Bandpass { .. }
610            | Node::Notch { .. }
611            | Node::Peak { .. }
612            | Node::Lowshelf { .. }
613            | Node::Highshelf { .. }
614            | Node::Gain { .. }
615            | Node::Bitcrush { .. }
616            | Node::Downsample { .. }
617            | Node::Delay { .. }
618            | Node::Reverb { .. }
619            | Node::Modal { .. }
620            | Node::Drive { .. }
621            | Node::RingMod { .. }
622            | Node::Chorus { .. }
623            | Node::Flanger { .. }
624            | Node::Phaser { .. }
625            | Node::Compress { .. } => ChildrenKind::None,
626        };
627        Children { kind }
628    }
629
630    /// Mutable form of [`children`](Self::children).
631    pub fn children_mut(&mut self) -> ChildrenMut<'_> {
632        let kind = match self {
633            Node::Mix { inputs } | Node::Mul { inputs } => {
634                ChildrenMutKind::Slice(inputs.iter_mut())
635            }
636            Node::Chain { stages } => ChildrenMutKind::Slice(stages.iter_mut()),
637            Node::Tracks { tracks, master } => ChildrenMutKind::Tracks {
638                tracks: tracks.iter_mut(),
639                master: master.iter_mut(),
640            },
641            Node::Duck { trigger, .. } => ChildrenMutKind::One(Some(trigger)),
642            Node::Square { .. }
643            | Node::Triangle { .. }
644            | Node::Sawtooth { .. }
645            | Node::Sine { .. }
646            | Node::Noise { .. }
647            | Node::Fm { .. }
648            | Node::Super { .. }
649            | Node::Seq { .. }
650            | Node::Impact { .. }
651            | Node::Dust { .. }
652            | Node::Env { .. }
653            | Node::Lowpass { .. }
654            | Node::Highpass { .. }
655            | Node::Bandpass { .. }
656            | Node::Notch { .. }
657            | Node::Peak { .. }
658            | Node::Lowshelf { .. }
659            | Node::Highshelf { .. }
660            | Node::Gain { .. }
661            | Node::Bitcrush { .. }
662            | Node::Downsample { .. }
663            | Node::Delay { .. }
664            | Node::Reverb { .. }
665            | Node::Modal { .. }
666            | Node::Drive { .. }
667            | Node::RingMod { .. }
668            | Node::Chorus { .. }
669            | Node::Flanger { .. }
670            | Node::Phaser { .. }
671            | Node::Compress { .. } => ChildrenMutKind::None,
672        };
673        ChildrenMut { kind }
674    }
675
676    /// Apply `f` to this node and every descendant, depth-first (parents
677    /// before children, in document order).
678    pub fn walk(&self, f: &mut impl FnMut(&Node)) {
679        f(self);
680        for c in self.children() {
681            c.walk(f);
682        }
683    }
684
685    /// Mutable form of [`walk`](Self::walk).
686    pub fn walk_mut(&mut self, f: &mut impl FnMut(&mut Node)) {
687        f(self);
688        for c in self.children_mut() {
689            c.walk_mut(f);
690        }
691    }
692}
693
694/// The iterator returned by [`Node::children`].
695pub struct Children<'a> {
696    kind: ChildrenKind<'a>,
697}
698
699enum ChildrenKind<'a> {
700    None,
701    One(Option<&'a Node>),
702    Slice(std::slice::Iter<'a, Node>),
703    Tracks {
704        tracks: std::slice::Iter<'a, Track>,
705        master: std::slice::Iter<'a, Node>,
706    },
707}
708
709impl<'a> Iterator for Children<'a> {
710    type Item = &'a Node;
711    fn next(&mut self) -> Option<&'a Node> {
712        match &mut self.kind {
713            ChildrenKind::None => None,
714            ChildrenKind::One(x) => x.take(),
715            ChildrenKind::Slice(it) => it.next(),
716            ChildrenKind::Tracks { tracks, master } => match tracks.next() {
717                Some(t) => Some(&t.node),
718                None => master.next(),
719            },
720        }
721    }
722}
723
724/// The iterator returned by [`Node::children_mut`].
725pub struct ChildrenMut<'a> {
726    kind: ChildrenMutKind<'a>,
727}
728
729enum ChildrenMutKind<'a> {
730    None,
731    One(Option<&'a mut Node>),
732    Slice(std::slice::IterMut<'a, Node>),
733    Tracks {
734        tracks: std::slice::IterMut<'a, Track>,
735        master: std::slice::IterMut<'a, Node>,
736    },
737}
738
739impl<'a> Iterator for ChildrenMut<'a> {
740    type Item = &'a mut Node;
741    fn next(&mut self) -> Option<&'a mut Node> {
742        match &mut self.kind {
743            ChildrenMutKind::None => None,
744            ChildrenMutKind::One(x) => x.take(),
745            ChildrenMutKind::Slice(it) => it.next(),
746            ChildrenMutKind::Tracks { tracks, master } => match tracks.next() {
747                Some(t) => Some(&mut t.node),
748                None => master.next(),
749            },
750        }
751    }
752}
753
754/// FM voice knobs of a `seq` node (`wave: "fm"`), flattened onto the node in
755/// JSON. Defaults ≈ an FM piano strike (ratio 1 + decaying index).
756#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
757pub struct FmKnobs {
758    /// Modulator frequency ratio (1 = e-piano/piano, 3.5 = bell, 14 = tine).
759    #[serde(default = "default_seq_fm_ratio")]
760    pub fm_ratio: f32,
761    /// Modulation index at the strike (brightness; also scaled by each note's
762    /// velocity, so louder notes ring brighter).
763    #[serde(default = "default_seq_fm_index")]
764    pub fm_index: f32,
765    /// Strike decay in seconds: how fast the index (brightness) fades after
766    /// each note's attack. Short = percussive e-piano, long = bell shimmer.
767    #[serde(default = "default_seq_fm_strike")]
768    pub fm_strike: f32,
769}
770
771/// Plucked-string knobs of a `seq` node (`wave: "pluck"`), flattened onto the
772/// node in JSON. The tone stages default to identity, so omitting them renders
773/// byte-identically and draws no extra RNG.
774#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
775pub struct PluckKnobs {
776    /// String feedback decay, 0.8..1 (higher rings longer; low notes
777    /// naturally ring longer than high ones).
778    #[serde(default = "default_pluck_decay")]
779    pub pluck_decay: f32,
780    /// Acoustic body-resonance depth, 0..1 — mixes in a fixed guitar-body
781    /// mode bank. 0 = solid-body (default).
782    #[serde(default = "default_pluck_body")]
783    pub pluck_body: f32,
784    /// Pick/attack transient level, 0..1. 0 = none.
785    #[serde(default = "default_pluck_pick")]
786    pub pluck_pick: f32,
787    /// String brightness/damping, −1..1. 0 = the current loop filter;
788    /// + brightens, − darkens.
789    #[serde(default = "default_pluck_tone")]
790    pub pluck_tone: f32,
791}
792
793/// Piano tone knobs of a `seq` node (`wave: "piano"`, engine ≥ 3), flattened
794/// onto the node in JSON. Every default reproduces the concert-grand kernel
795/// bit-for-bit, so a doc that omits them renders byte-identically.
796#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
797pub struct PianoKnobs {
798    /// Hammer hardness: spectral brightness. 1 = concert grand; > 1
799    /// harder/brighter, < 1 softer/darker.
800    #[serde(default = "default_piano_hammer")]
801    pub piano_hammer: f32,
802    /// Hammer strike position (fraction along the string): the comb-notch
803    /// that thins the spectrum. 0.125 = grand; toward the bridge is brighter.
804    #[serde(default = "default_piano_strike")]
805    pub piano_strike: f32,
806    /// String-stiffness scale: stretches the partials sharp. 1 = grand;
807    /// > 1 = short/stiff upright jangle.
808    #[serde(default = "default_piano_inharm")]
809    pub piano_inharm: f32,
810    /// Unison detune width: the two-string beating. 1 ≈ ±1 cent (grand
811    /// shimmer); ~12 = honky-tonk warble.
812    #[serde(default = "default_piano_detune")]
813    pub piano_detune: f32,
814    /// Ring-time scale. 1 = grand; < 1 = shorter, damped; > 1 = longer.
815    #[serde(default = "default_piano_decay")]
816    pub piano_decay: f32,
817}
818
819/// Bass tone knobs of a `seq` node (`wave: "bass"`), flattened onto the node
820/// in JSON. Every default is the original voice's hard-coded constant, so
821/// omitting them renders byte-identically.
822#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
823pub struct BassKnobs {
824    /// Filter resting floor in Hz. Low = dark/round.
825    #[serde(default = "default_bass_cutoff")]
826    pub bass_cutoff: f32,
827    /// Fixed cutoff-sweep depth above the floor, Hz.
828    #[serde(default = "default_bass_env")]
829    pub bass_env: f32,
830    /// Velocity-scaled sweep depth, Hz (× note velocity).
831    #[serde(default = "default_bass_env_vel")]
832    pub bass_env_vel: f32,
833    /// Filter-sweep time constant, seconds — how fast the cutoff closes.
834    #[serde(default = "default_bass_decay")]
835    pub bass_decay: f32,
836    /// Pick-tick: an extra attack cutoff bump over ~8 ms, Hz. 0 = none.
837    #[serde(default = "default_bass_click")]
838    pub bass_click: f32,
839    /// Filtered-saw body level.
840    #[serde(default = "default_bass_body")]
841    pub bass_body: f32,
842    /// Sine-sub level.
843    #[serde(default = "default_bass_sub")]
844    pub bass_sub: f32,
845    /// Sub frequency ratio to the note. 1 = reinforce; 0.5 = octave down.
846    #[serde(default = "default_bass_sub_ratio")]
847    pub bass_sub_ratio: f32,
848    /// tanh saturation, 0..1. 0 = clean; > 0 = synth-bass grit.
849    #[serde(default = "default_bass_drive")]
850    pub bass_drive: f32,
851    /// Note body decay, seconds (on top of the ADSR). Longer = sustained.
852    #[serde(default = "default_bass_body_decay")]
853    pub bass_body_decay: f32,
854}
855
856/// SoundFont sampler settings of a `seq` node (`wave: "sampler"`), flattened
857/// onto the node in JSON.
858#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
859pub struct Sf2Knobs {
860    /// Path to a SoundFont (.sf2) file.
861    #[serde(default)]
862    pub sf2: String,
863    /// General MIDI program number (0..=127).
864    #[serde(default)]
865    pub sf2_preset: u32,
866    /// SoundFont bank (0 = melodic, 128 = the percussion bank / GM drum map).
867    #[serde(default)]
868    pub sf2_bank: u32,
869}