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