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}