Skip to main content

auracle_grammar/
presets.rs

1//! Hand-designed preset patches: a starting vocabulary for the bank, and a
2//! fast way to seed the taste model ("keep the ones you like").
3//!
4//! Every preset must compile and pass the vetting gate — pinned by a test in
5//! the features crate's dependents (see auracle-wasm tests).
6//!
7//! # Every parameter here is normalized; none of them are in units
8//!
9//! A `PatchTree` field is `0.0..=1.0` and the musical meaning lives in the
10//! compiler and in quiver. Writing these by feel rather than by the maps is
11//! how the first nine went wrong in a way nobody could see: **every modulated
12//! preset in the library ran between 0.033 Hz and 0.165 Hz** — six to thirty
13//! seconds per cycle — against an audition phrase whose longest held note is
14//! 1.8 s. The library demonstrated no audible modulation at all, and since the
15//! warm start draws its whole cold-start evidence from these patches, two of
16//! the fifteen features the taste model reasons over (`centroid_std`,
17//! `flux_mean`) were pinned to the floor for every observation it started
18//! from. `Dub Echo`'s `time: 0.45` was likewise 30 ms — a flanger.
19//!
20//! So: **use the maps.** They are the arithmetic, not a style guide.
21//!
22//! | field | map | worked examples |
23//! |---|---|---|
24//! | `Filter.cutoff` | `20·1000^x` Hz | .3→159 · .5→632 · .7→2.5k · .85→7.1k |
25//! | `AmpEnv.*`, `Env.*` | `1 ms·10000^x` **time constant** | .0→1 ms · .25→10 ms · .5→100 ms · .75→1 s |
26//! | `Lfo.rate`, `Rand.rate` | `0.01·3000^x` Hz | .42→0.29 · .49→0.51 · .58→1.0 · .75→4.05 |
27//! | `Delay.time` | `1 ms·2000^x` | .54→60 ms · .63→120 ms · .79→420 ms |
28//! | `Chorus.rate` | `0.1·50^x` Hz | .15→0.18 · .25→0.27 · .35→0.39 · .5→0.71 |
29//! | `Reverb.size` | Freeverb room `0.28+0.7x` | .9→0.91 |
30//! | `Filter.resonance` | `×0.85` | 1.0 is 0.85, never self-oscillation |
31//! | `Delay.feedback` | `×0.7` | .62→0.43 — the grammar cannot run away |
32//! | `Phaser.rate` | `0.05·100^x` Hz | .25→0.16 · .35→0.25 · .5→0.5 · .7→1.25 |
33//! | `Phaser.feedback` | `(2x−1)·0.7` | .5 is *no* feedback; below it the sign flips |
34//! | `Bitcrush.bits` | `1 + 15x` bits | .2→4 · .35→6.3 · .6→10 · 1.0→16 (clean) |
35//! | `Bitcrush.downsample` | `1 + 63x` × slower | .1→7.3× · .3→20× · .5→33× |
36//! | `Follow.release` | `1 + 999x` ms | .2→200 ms · .4→400 ms · .7→700 ms |
37//!
38//! `AmpEnv.sustain` and every `mix`/`balance`/`depth` are levels, not times.
39//!
40//! **The envelope map is a time constant, not a duration.** `compile.rs` runs
41//! the ADSR in exponential mode, so the mapped value is a one-pole τ: reaching
42//! 90% takes ≈2.3 τ and settling takes ≈6.9 τ. `Cathedral`'s attack of `0.75`
43//! maps to 1.0 s and *measures* 1.397 s to its knee. Read every envelope
44//! number here as "about 1.4× longer than the table says", and remember the
45//! phrase's longest note is 1.8 s — an attack much past `0.75` will still be
46//! climbing when the note ends.
47//!
48//! **`Supersaw` is ~14 dB quieter than `Vco`.** Quiver scales `Vco` and
49//! `Noise` outputs by 5.0 and leaves `Supersaw` at unity. That is invisible
50//! for anything downstream that only cares about relative level, and fatal for
51//! `Fold`, which folds `x / 5.0` and therefore never reaches its threshold on
52//! a supersaw. Build folded patches on `Vco`.
53//!
54//! # What the grammar cannot do
55//!
56//! Worth knowing before designing: there is no FM, no oscillator sync and no
57//! PWM. `Vco.detune` on a lone oscillator only transposes the whole patch a
58//! few cents sharp; it is only musical inside a `Mix` (or a `RingMod`) of two
59//! sources.
60//!
61//! Pitch modulation **does** exist as of wave 2A — `Vco` and `Supersaw` carry
62//! a slot that lands on their pitch offset (±0.5 octave at full depth), which
63//! is where vibrato and pitch envelopes come from.
64//!
65//! What a `ModNode` reaches is one parameter per module, and the choice is
66//! the compiler's, not the patch's: `Vco`/`Supersaw` pitch, `Filter.cutoff`,
67//! `Fold.threshold`, `Delay.time`, `Chorus.depth`, `Reverb.size`,
68//! `Distortion.drive`, `Bitcrush.bits`, `Phaser.depth`, `Flanger.depth`,
69//! `Tremolo.depth`, `Vibrato.depth`, `Eq.mid`, `Granular.position`,
70//! `Wavetable.morph`, `Pluck.damping`, `Formant.vowel`, `Shift.semis`,
71//! `Comp`/`Gate` threshold, `Duck.amount` and `Vocoder.bands`. Only `Mix` and
72//! `RingMod` have no slot — both of their inputs are audio and their one knob
73//! is the blend.
74//!
75//! `ModNode::Follow` is the odd one: it listens to the owning module's *own*
76//! input, so it only says something on a module whose input has dynamics to
77//! follow. On a source's slot it is silence, deliberately.
78//!
79//! # Levels are not comparable, and the dynamics modules care
80//!
81//! `Comp`, `Duck` and `Gate` are keyed by a second branch whose **absolute**
82//! level decides everything. Measured as mean `|x|` on a held note: a sine
83//! vco is 3.18 V, a supersaw ≈0.6 V, a plucked string **0.14 V**. The
84//! threshold knobs are geometric over 0.05–5 V (`compile::map::detector_volts`)
85//! so all three are reachable, but a key branch swapped from a pluck to a vco
86//! moves the useful threshold by 25 dB — about a third of the knob.
87//!
88//! # The gate is not the constraint
89//!
90//! Vetting rejects only non-finite output, RMS < 1e-4, peak > 3.5, and
91//! `|mean|/rms > 0.6`. It catches pathology, not taste. Design musically and
92//! let the gate do its narrow job.
93//!
94//! # This is also the model's vocabulary lesson
95//!
96//! The bank's auto-namer (`auracle-session::naming`) fits its adjectives to
97//! the pool's *own* spread and suppresses any axis whose range falls below a
98//! just-noticeable difference. A library clustered in one corner therefore
99//! produces a bank that honestly reports itself as `Soft Lead`, `Soft Lead 2`,
100//! `Soft Lead 3`. Coverage here is what gives the whole app its adjectives.
101
102use crate::term::{
103    AmpEnv, AudioNode, DriveMode, FilterKind, ModNode, ModOp, NoiseColor, PairOp, PatchTree,
104    TableShape, Uid, Waveform,
105};
106
107fn amp(attack: f64, decay: f64, sustain: f64, release: f64) -> AmpEnv {
108    AmpEnv {
109        attack,
110        decay,
111        sustain,
112        release,
113    }
114}
115
116/// A vco with no pitch modulation — the shape every preset written before
117/// wave 2A had. Presets that *want* the pitch slot spell out the full literal,
118/// which is also what makes them findable.
119fn vco(wave: Waveform, octave: i8, detune: f64) -> AudioNode {
120    AudioNode::Vco {
121        uid: Uid::NEW,
122        wave,
123        octave,
124        detune,
125        mod_depth: 0.0,
126        modulation: ModNode::None,
127    }
128}
129
130/// A supersaw with no pitch modulation.
131fn supersaw(octave: i8, detune: f64, mix: f64) -> AudioNode {
132    AudioNode::Supersaw {
133        uid: Uid::NEW,
134        octave,
135        detune,
136        mix,
137        mod_depth: 0.0,
138        modulation: ModNode::None,
139    }
140}
141
142/// What a preset is *for*, so the browser can group them and the warm start
143/// can sample across the space instead of down one corner of it.
144///
145/// A `&'static str` rather than an enum: it exists to be grouped by and shown,
146/// it crosses into JS as JSON, and nothing dispatches on it.
147pub type Category = &'static str;
148
149/// The categories, in display order.
150pub const CATEGORIES: [Category; 7] = ["bass", "lead", "keys", "pad", "texture", "perc", "weird"];
151
152/// One built-in patch, with the copy that makes it findable.
153pub struct Preset {
154    /// Display name.
155    pub name: &'static str,
156    /// Which family it belongs to; one of [`CATEGORIES`].
157    pub category: Category,
158    /// One line on what it sounds like. Shown in the browser row and its
159    /// tooltip — the handbook delivered where the user is already looking.
160    pub blurb: &'static str,
161    /// The patch.
162    pub tree: PatchTree,
163}
164
165/// The built-in presets, as `(name, tree)` pairs.
166///
167/// Retained as the compatibility shim over [`preset_bank`]; new callers that
168/// want categories or copy should use that instead.
169pub fn presets() -> Vec<(&'static str, PatchTree)> {
170    preset_bank()
171        .into_iter()
172        .map(|p| (p.name, p.tree))
173        .collect()
174}
175
176/// The built-in preset library, in category order.
177pub fn preset_bank() -> Vec<Preset> {
178    use AudioNode::*;
179    vec![
180        // ---------------------------------------------------------------
181        // BASS
182        // ---------------------------------------------------------------
183        Preset {
184            name: "First Bass",
185            category: "bass",
186            blurb: "the plain correct one — measure the others against it",
187            tree: PatchTree {
188                amp: amp(0.03, 0.35, 0.55, 0.2),
189                root: Filter {
190                    uid: Uid::NEW,
191                    kind: FilterKind::Ladder,
192                    cutoff: 0.45,
193                    resonance: 0.55,
194                    mod_depth: 0.6,
195                    modulation: ModNode::Env {
196                        uid: Uid::NEW,
197                        attack: 0.02,
198                        decay: 0.45,
199                    },
200                    input: Box::new(vco(Waveform::Saw, -1, 0.5)),
201                },
202            },
203        },
204        Preset {
205            name: "Sub & Sparkle",
206            category: "bass",
207            blurb: "weight underneath, air above, nothing in the middle",
208            tree: PatchTree {
209                amp: amp(0.05, 0.35, 0.65, 0.35),
210                root: Mix {
211                    uid: Uid::NEW,
212                    balance: 0.35,
213                    a: Box::new(vco(Waveform::Sine, -1, 0.5)),
214                    b: Box::new(Filter {
215                        uid: Uid::NEW,
216                        kind: FilterKind::SvfHp,
217                        cutoff: 0.7,
218                        resonance: 0.3,
219                        mod_depth: 0.0,
220                        modulation: ModNode::None,
221                        input: Box::new(vco(Waveform::Saw, 1, 0.6)),
222                    }),
223                },
224            },
225        },
226        Preset {
227            name: "Acid Line",
228            category: "bass",
229            // Ladder at high resonance with a fast env sweep is the 303; the
230            // decay (0.30 ≈ 16 ms) is what makes it spit rather than wobble.
231            blurb: "a 303 that got out of the cage",
232            tree: PatchTree {
233                amp: amp(0.0, 0.42, 0.3, 0.3),
234                root: Filter {
235                    uid: Uid::NEW,
236                    kind: FilterKind::Ladder,
237                    cutoff: 0.3,
238                    resonance: 0.8,
239                    mod_depth: 0.65,
240                    modulation: ModNode::Env {
241                        uid: Uid::NEW,
242                        attack: 0.0,
243                        decay: 0.3,
244                    },
245                    input: Box::new(vco(Waveform::Saw, -1, 0.52)),
246                },
247            },
248        },
249        Preset {
250            name: "Reese",
251            category: "bass",
252            // Two saws a hair apart is the only place `detune` is musical —
253            // on a lone oscillator it just transposes the patch sharp.
254            blurb: "two saws beating against each other, low and wide",
255            tree: PatchTree {
256                amp: amp(0.02, 0.5, 0.75, 0.35),
257                root: Filter {
258                    uid: Uid::NEW,
259                    kind: FilterKind::SvfLp,
260                    cutoff: 0.45,
261                    resonance: 0.35,
262                    mod_depth: 0.25,
263                    modulation: ModNode::Lfo {
264                        uid: Uid::NEW,
265                        wave: Waveform::Triangle,
266                        rate: 0.49, // 0.5 Hz — one full sweep per held note
267                    },
268                    input: Box::new(Mix {
269                        uid: Uid::NEW,
270                        balance: 0.5,
271                        a: Box::new(vco(Waveform::Saw, -1, 0.3)),
272                        b: Box::new(vco(Waveform::Saw, -1, 0.85)),
273                    }),
274                },
275            },
276        },
277        Preset {
278            name: "Anvil",
279            category: "bass",
280            blurb: "a square driven into the folder, then hammered flat",
281            tree: PatchTree {
282                amp: amp(0.0, 0.45, 0.45, 0.3),
283                root: Filter {
284                    uid: Uid::NEW,
285                    kind: FilterKind::Ladder,
286                    cutoff: 0.42,
287                    resonance: 0.35,
288                    mod_depth: 0.0,
289                    modulation: ModNode::None,
290                    input: Box::new(Fold {
291                        uid: Uid::NEW,
292                        threshold: 0.35,
293                        mod_depth: 0.4,
294                        modulation: ModNode::Env {
295                            uid: Uid::NEW,
296                            attack: 0.0,
297                            decay: 0.325,
298                        },
299                        input: Box::new(vco(Waveform::Square, -1, 0.5)),
300                    }),
301                },
302            },
303        },
304        Preset {
305            name: "Iron Bass",
306            category: "bass",
307            // Tube drive is the asymmetric shaper, so this patch is also the
308            // library's only user of the DC blocker on a non-ladder path.
309            // The follower makes the drive track the note's own envelope:
310            // hard at the attack, cleaner as it decays, which is what a real
311            // overdriven amp does and what a fixed drive knob cannot.
312            blurb: "an amp being pushed harder than it wants to go",
313            tree: PatchTree {
314                amp: amp(0.0, 0.45, 0.6, 0.3),
315                root: Filter {
316                    uid: Uid::NEW,
317                    kind: FilterKind::Ladder,
318                    cutoff: 0.48,
319                    resonance: 0.3,
320                    mod_depth: 0.0,
321                    modulation: ModNode::None,
322                    input: Box::new(Distortion {
323                        uid: Uid::NEW,
324                        drive: 0.55,
325                        tone: 0.45,
326                        mode: DriveMode::Tube,
327                        mod_depth: 0.6,
328                        modulation: ModNode::Follow {
329                            uid: Uid::NEW,
330                            sens: 0.55,
331                            release: 0.35,
332                        },
333                        input: Box::new(vco(Waveform::Saw, -1, 0.5)),
334                    }),
335                },
336            },
337        },
338        Preset {
339            name: "Held Under",
340            category: "bass",
341            // Sidechain compression, which needs two branches and therefore
342            // could not be written before wave 2B. The plucked string is
343            // never heard; it only tells the detector when to pull the sub
344            // down. Threshold 0.3 is ≈0.2 V on the geometric detector map —
345            // under the string's envelope peak, so every strike bites.
346            blurb: "a sub that gets pushed down every time the string speaks",
347            tree: PatchTree {
348                amp: amp(0.05, 0.4, 0.8, 0.35),
349                root: Comp {
350                    uid: Uid::NEW,
351                    threshold: 0.3,
352                    ratio: 0.7,   // ≈14:1 — limiting, so the pump is not subtle
353                    makeup: 0.25, // 1.75×, giving the level back between hits
354                    mod_depth: 0.0,
355                    modulation: ModNode::None,
356                    input: Box::new(Filter {
357                        uid: Uid::NEW,
358                        kind: FilterKind::SvfLp,
359                        cutoff: 0.4, // ≈316 Hz
360                        resonance: 0.3,
361                        mod_depth: 0.0,
362                        modulation: ModNode::None,
363                        input: Box::new(vco(Waveform::Saw, -1, 0.5)),
364                    }),
365                    sidechain: Box::new(Pluck {
366                        uid: Uid::NEW,
367                        octave: 0,
368                        damping: 0.3,
369                        brightness: 0.8,
370                        mod_depth: 0.0,
371                        modulation: ModNode::None,
372                    }),
373                },
374            },
375        },
376        // ---------------------------------------------------------------
377        // LEAD
378        // ---------------------------------------------------------------
379        Preset {
380            name: "Folded Lead",
381            category: "lead",
382            blurb: "buzzing, metallic, never quite still",
383            tree: PatchTree {
384                amp: amp(0.02, 0.3, 0.7, 0.3),
385                root: Fold {
386                    uid: Uid::NEW,
387                    threshold: 0.4,
388                    mod_depth: 0.5,
389                    modulation: ModNode::Lfo {
390                        uid: Uid::NEW,
391                        wave: Waveform::Triangle,
392                        rate: 0.6, // 1.2 Hz — was 0.35 (0.165 Hz), inaudible
393                    },
394                    input: Box::new(vco(Waveform::Saw, 0, 0.55)),
395                },
396            },
397        },
398        Preset {
399            name: "Hornet",
400            category: "lead",
401            blurb: "nasal and angry; cuts through anything",
402            tree: PatchTree {
403                amp: amp(0.15, 0.4, 0.65, 0.3),
404                root: Filter {
405                    uid: Uid::NEW,
406                    kind: FilterKind::SvfBp,
407                    cutoff: 0.62,
408                    resonance: 0.7,
409                    mod_depth: 0.25,
410                    modulation: ModNode::Rand {
411                        uid: Uid::NEW,
412                        rate: 0.62,
413                        glide: 0.0,
414                    }, // ~1.4 Hz stepping
415                    input: Box::new(vco(Waveform::Square, 0, 0.5)),
416                },
417            },
418        },
419        Preset {
420            name: "Solo Flight",
421            category: "lead",
422            blurb: "wide enough to sing, not so wide it stops being one voice",
423            tree: PatchTree {
424                amp: amp(0.3, 0.45, 0.78, 0.45),
425                root: Chorus {
426                    uid: Uid::NEW,
427                    rate: 0.3,
428                    depth: 0.35,
429                    mix: 0.3,
430                    mod_depth: 0.0,
431                    modulation: ModNode::None,
432                    input: Box::new(Filter {
433                        uid: Uid::NEW,
434                        kind: FilterKind::SvfLp,
435                        cutoff: 0.72,
436                        resonance: 0.3,
437                        mod_depth: 0.18,
438                        modulation: ModNode::Lfo {
439                            uid: Uid::NEW,
440                            wave: Waveform::Sine,
441                            rate: 0.55, // 0.8 Hz — a slow breath, still audible
442                        },
443                        input: Box::new(supersaw(0, 0.25, 0.45)),
444                    }),
445                },
446            },
447        },
448        Preset {
449            name: "Telegraph",
450            category: "lead",
451            blurb: "every note answers itself once and stops",
452            tree: PatchTree {
453                amp: amp(0.15, 0.45, 0.55, 0.35),
454                root: Delay {
455                    uid: Uid::NEW,
456                    time: 0.63, // 120 ms — a slapback, not a comb
457                    feedback: 0.4,
458                    mix: 0.32,
459                    mod_depth: 0.0,
460                    modulation: ModNode::None,
461                    input: Box::new(Filter {
462                        uid: Uid::NEW,
463                        kind: FilterKind::SvfLp,
464                        cutoff: 0.68,
465                        resonance: 0.4,
466                        mod_depth: 0.35,
467                        modulation: ModNode::Env {
468                            uid: Uid::NEW,
469                            attack: 0.0,
470                            decay: 0.42,
471                        },
472                        input: Box::new(vco(Waveform::Triangle, 1, 0.5)),
473                    }),
474                },
475            },
476        },
477        Preset {
478            name: "Wobble Board",
479            category: "lead",
480            // The headline of wave 2A: `mod_depth` 0.09 is ±54 cents, which is
481            // a singer's vibrato rather than a siren. The taper reaches ±0.5
482            // octave at 1.0, so the musical corner is the bottom tenth — small
483            // to dial, and the alternative was not having pitch modulation.
484            blurb: "a lead that sings — the first patch here that can vibrato",
485            tree: PatchTree {
486                amp: amp(0.04, 0.4, 0.7, 0.3),
487                root: Filter {
488                    uid: Uid::NEW,
489                    kind: FilterKind::Ladder,
490                    cutoff: 0.55,
491                    resonance: 0.4,
492                    mod_depth: 0.0,
493                    modulation: ModNode::None,
494                    input: Box::new(Vco {
495                        uid: Uid::NEW,
496                        wave: Waveform::Saw,
497                        octave: 0,
498                        detune: 0.5,
499                        mod_depth: 0.09,
500                        modulation: ModNode::Lfo {
501                            uid: Uid::NEW,
502                            wave: Waveform::Sine,
503                            rate: 0.78, // 5.2 Hz
504                        },
505                    }),
506                },
507            },
508        },
509        Preset {
510            name: "Falling Sign",
511            category: "lead",
512            // The other pitch-slot idiom, and the one that argues for the
513            // wide taper: a unipolar mod envelope on pitch starts the note
514            // 3.3 semitones sharp and lets it fall in over 63 ms. A ceiling
515            // tight enough to make vibrato comfortable would not reach here.
516            blurb: "every note drops into tune from a third above",
517            tree: PatchTree {
518                amp: amp(0.0, 0.45, 0.6, 0.25),
519                root: Filter {
520                    uid: Uid::NEW,
521                    kind: FilterKind::SvfLp,
522                    cutoff: 0.6,
523                    resonance: 0.35,
524                    mod_depth: 0.0,
525                    modulation: ModNode::None,
526                    input: Box::new(Vco {
527                        uid: Uid::NEW,
528                        wave: Waveform::Square,
529                        octave: 0,
530                        detune: 0.5,
531                        mod_depth: 0.55,
532                        modulation: ModNode::Env {
533                            uid: Uid::NEW,
534                            attack: 0.0,
535                            decay: 0.45, // 63 ms
536                        },
537                    }),
538                },
539            },
540        },
541        Preset {
542            name: "Loudhailer",
543            category: "lead",
544            // The eq's three bands are bipolar: 0.5 is 0 dB, so 0.15 is
545            // −8.4 dB and 0.75 is +6 dB. The mod slot reaches the mid gain at
546            // its own volt scale, so depth 0.45 is a ±5.4 dB pump — a wah
547            // built out of a tone control.
548            blurb: "a megaphone with a hand moving over the mouth",
549            tree: PatchTree {
550                amp: amp(0.02, 0.4, 0.7, 0.25),
551                root: Eq {
552                    uid: Uid::NEW,
553                    low: 0.15,
554                    mid: 0.75,
555                    high: 0.2,
556                    mod_depth: 0.45,
557                    modulation: ModNode::Lfo {
558                        uid: Uid::NEW,
559                        wave: Waveform::Triangle,
560                        rate: 0.52, // 0.64 Hz
561                    },
562                    input: Box::new(Filter {
563                        uid: Uid::NEW,
564                        kind: FilterKind::Ladder,
565                        cutoff: 0.55,
566                        resonance: 0.5,
567                        mod_depth: 0.0,
568                        modulation: ModNode::None,
569                        input: Box::new(vco(Waveform::Saw, 0, 0.5)),
570                    }),
571                },
572            },
573        },
574        Preset {
575            name: "Fifth Wheel",
576            category: "lead",
577            // The shifter is 0.79 on a `(2x−1)·2.5 V` knob into quiver's
578            // `cv/5·24` semitones: +1.45 V, i.e. +7.0 semitones — a fifth, not
579            // an "up a bit". At mix 0.5 both voices are present, which is what
580            // makes it a harmony rather than a transposition.
581            blurb: "plays a fifth above itself, in parallel, slightly grainy",
582            tree: PatchTree {
583                amp: amp(0.15, 0.4, 0.7, 0.3),
584                root: Shift {
585                    uid: Uid::NEW,
586                    semis: 0.79,
587                    // 41 ms grains: long enough to track the fundamental of a
588                    // lead register, short enough that the harmony arrives
589                    // with the note rather than behind it.
590                    window: 0.35,
591                    mix: 0.5,
592                    mod_depth: 0.0,
593                    modulation: ModNode::None,
594                    input: Box::new(Filter {
595                        uid: Uid::NEW,
596                        kind: FilterKind::Ladder,
597                        cutoff: 0.55, // ≈1.2 kHz
598                        resonance: 0.45,
599                        mod_depth: 0.5,
600                        modulation: ModNode::Env {
601                            uid: Uid::NEW,
602                            attack: 0.05,
603                            decay: 0.45, // ≈45 ms
604                        },
605                        input: Box::new(vco(Waveform::Saw, 0, 0.5)),
606                    }),
607                },
608            },
609        },
610        // ---------------------------------------------------------------
611        // KEYS
612        // ---------------------------------------------------------------
613        Preset {
614            name: "Pluck",
615            category: "keys",
616            blurb: "woody and dry; gone before you lift the key",
617            tree: PatchTree {
618                amp: amp(0.0, 0.25, 0.05, 0.25),
619                root: Filter {
620                    uid: Uid::NEW,
621                    kind: FilterKind::SvfBp,
622                    cutoff: 0.6,
623                    resonance: 0.45,
624                    mod_depth: 0.5,
625                    modulation: ModNode::Env {
626                        uid: Uid::NEW,
627                        attack: 0.0,
628                        decay: 0.25,
629                    },
630                    input: Box::new(vco(Waveform::Triangle, 0, 0.5)),
631                },
632            },
633        },
634        Preset {
635            name: "Bell Jar",
636            category: "keys",
637            // Folding a sine is the cheapest way to inharmonic partials in a
638            // grammar with no FM and no ring mod.
639            blurb: "struck glass, heard from across the room",
640            tree: PatchTree {
641                amp: amp(0.0, 0.55, 0.0, 0.7),
642                root: Reverb {
643                    uid: Uid::NEW,
644                    size: 0.7,
645                    damp: 0.3,
646                    mix: 0.4,
647                    mod_depth: 0.0,
648                    modulation: ModNode::None,
649                    input: Box::new(Fold {
650                        uid: Uid::NEW,
651                        threshold: 0.55,
652                        mod_depth: 0.35,
653                        modulation: ModNode::Env {
654                            uid: Uid::NEW,
655                            attack: 0.0,
656                            decay: 0.35,
657                        },
658                        input: Box::new(vco(Waveform::Sine, 1, 0.5)),
659                    }),
660                },
661            },
662        },
663        Preset {
664            name: "Tine",
665            category: "keys",
666            blurb: "electric-piano-shaped, without pretending to be one",
667            tree: PatchTree {
668                amp: amp(0.0, 0.6, 0.25, 0.5),
669                root: Chorus {
670                    uid: Uid::NEW,
671                    rate: 0.2,
672                    depth: 0.3,
673                    mix: 0.3,
674                    mod_depth: 0.0,
675                    modulation: ModNode::None,
676                    input: Box::new(Mix {
677                        uid: Uid::NEW,
678                        balance: 0.3,
679                        a: Box::new(vco(Waveform::Sine, 0, 0.5)),
680                        b: Box::new(Filter {
681                            uid: Uid::NEW,
682                            kind: FilterKind::SvfHp,
683                            cutoff: 0.78,
684                            resonance: 0.2,
685                            mod_depth: 0.0,
686                            modulation: ModNode::None,
687                            input: Box::new(vco(Waveform::Triangle, 2, 0.5)),
688                        }),
689                    }),
690                },
691            },
692        },
693        Preset {
694            name: "Coin Toss",
695            category: "keys",
696            blurb: "a resonant burble that never settles",
697            tree: PatchTree {
698                amp: amp(0.0, 0.42, 0.0, 0.35),
699                root: Filter {
700                    uid: Uid::NEW,
701                    kind: FilterKind::Ladder,
702                    cutoff: 0.55,
703                    resonance: 0.6,
704                    mod_depth: 0.3,
705                    modulation: ModNode::Rand {
706                        uid: Uid::NEW,
707                        rate: 0.75,
708                        glide: 0.0,
709                    }, // ~4 Hz
710                    input: Box::new(vco(Waveform::Saw, 0, 0.5)),
711                },
712            },
713        },
714        Preset {
715            name: "Gut String",
716            category: "keys",
717            // Karplus-Strong excites once per gate edge and then rings from
718            // its own loop, so the amp envelope is a window on the decay
719            // rather than the decay itself — hence the long sustain on a
720            // patch that plainly dies away.
721            blurb: "a string that was plucked, not switched on",
722            tree: PatchTree {
723                amp: amp(0.0, 0.5, 0.8, 0.4),
724                root: Pluck {
725                    uid: Uid::NEW,
726                    octave: 0,
727                    damping: 0.62,
728                    brightness: 0.55,
729                    mod_depth: 0.0,
730                    modulation: ModNode::None,
731                },
732            },
733        },
734        Preset {
735            name: "Ghost Bell",
736            category: "keys",
737            // The classic ring-mod bell: sum and difference tones of two
738            // sines a fifth-and-an-octave apart are inharmonic, which is what
739            // makes it read as struck metal rather than as a note. `mix`
740            // keeps some dry carrier so the patch still has a pitch.
741            blurb: "two sines multiplied into something that never had a pitch",
742            tree: PatchTree {
743                amp: amp(0.0, 0.5, 0.0, 0.65),
744                root: Reverb {
745                    uid: Uid::NEW,
746                    size: 0.72,
747                    damp: 0.35,
748                    mix: 0.4,
749                    mod_depth: 0.0,
750                    modulation: ModNode::None,
751                    input: Box::new(RingMod {
752                        uid: Uid::NEW,
753                        mix: 0.68,
754                        a: Box::new(vco(Waveform::Sine, 1, 0.5)),
755                        b: Box::new(vco(Waveform::Sine, 2, 0.62)),
756                    }),
757                },
758            },
759        },
760        Preset {
761            name: "Choirboy",
762            category: "keys",
763            // quiver's `vowel` port is a continuous position across A/E/I/O/U,
764            // not a five-way switch — so an LFO on it *slides* between vowels,
765            // which is a spectral movement the feature extractor's centroid
766            // and rolloff see directly.
767            blurb: "vowels, sung by something with no mouth",
768            tree: PatchTree {
769                amp: amp(0.12, 0.5, 0.75, 0.4),
770                root: Reverb {
771                    uid: Uid::NEW,
772                    size: 0.7,
773                    damp: 0.4,
774                    mix: 0.4,
775                    mod_depth: 0.0,
776                    modulation: ModNode::None,
777                    input: Box::new(Formant {
778                        uid: Uid::NEW,
779                        vowel: 0.25,
780                        shift: 0.55,
781                        octave: 0,
782                        mod_depth: 0.5,
783                        modulation: ModNode::Lfo {
784                            uid: Uid::NEW,
785                            wave: Waveform::Triangle,
786                            rate: 0.45, // 0.37 Hz
787                        },
788                    }),
789                },
790            },
791        },
792        Preset {
793            name: "Twelve String",
794            category: "keys",
795            // The shifter at 0.79 is +7.0 semitones (its knob is ±12 over the
796            // port's ±24, so the mod cable can reach the rest); at mix 0.5 the
797            // original is still there, so one key gives a string and its
798            // fifth. On a plucked source that is a twelve-string, not a
799            // transposition.
800            blurb: "one string, and its fifth, struck together",
801            tree: PatchTree {
802                amp: amp(0.0, 0.45, 0.25, 0.35),
803                root: Reverb {
804                    uid: Uid::NEW,
805                    size: 0.5,
806                    damp: 0.55,
807                    mix: 0.25,
808                    mod_depth: 0.0,
809                    modulation: ModNode::None,
810                    input: Box::new(AudioNode::Shift {
811                        uid: Uid::NEW,
812                        semis: 0.79,
813                        window: 0.3, // ≈35 ms grains — tracks a key register
814                        mix: 0.5,
815                        mod_depth: 0.0,
816                        modulation: ModNode::None,
817                        input: Box::new(AudioNode::Pluck {
818                            uid: Uid::NEW,
819                            octave: 0,
820                            damping: 0.55,
821                            brightness: 0.55,
822                            mod_depth: 0.0,
823                            modulation: ModNode::None,
824                        }),
825                    }),
826                },
827            },
828        },
829        // ---------------------------------------------------------------
830        // PAD
831        // ---------------------------------------------------------------
832        Preset {
833            name: "Cathedral",
834            category: "pad",
835            blurb: "slow, sacred and wide",
836            tree: PatchTree {
837                // Was 0.4 — 40 ms, an organ stab into a hall. 0.75 is ~1 s,
838                // which is what "slow" claimed all along.
839                amp: amp(0.75, 0.4, 0.75, 0.7),
840                root: Reverb {
841                    uid: Uid::NEW,
842                    size: 0.85,
843                    damp: 0.35,
844                    mix: 0.5,
845                    mod_depth: 0.0,
846                    modulation: ModNode::None,
847                    input: Box::new(Filter {
848                        uid: Uid::NEW,
849                        kind: FilterKind::SvfLp,
850                        cutoff: 0.55,
851                        resonance: 0.25,
852                        mod_depth: 0.3,
853                        modulation: ModNode::Rand {
854                            uid: Uid::NEW,
855                            rate: 0.45,
856                            glide: 0.0,
857                        }, // 0.37 Hz
858                        input: Box::new(vco(Waveform::Triangle, 0, 0.55)),
859                    }),
860                },
861            },
862        },
863        Preset {
864            name: "Glass Pad",
865            category: "pad",
866            blurb: "bright, transparent, faintly fragile",
867            tree: PatchTree {
868                amp: amp(0.55, 0.4, 0.8, 0.65),
869                root: Chorus {
870                    uid: Uid::NEW,
871                    rate: 0.25,
872                    depth: 0.5,
873                    mix: 0.45,
874                    mod_depth: 0.0,
875                    modulation: ModNode::None,
876                    input: Box::new(Filter {
877                        uid: Uid::NEW,
878                        kind: FilterKind::SvfLp,
879                        cutoff: 0.65,
880                        resonance: 0.2,
881                        mod_depth: 0.25,
882                        modulation: ModNode::Lfo {
883                            uid: Uid::NEW,
884                            wave: Waveform::Sine,
885                            rate: 0.49, // 0.5 Hz — was 0.2 (0.05 Hz, 20 s/cycle)
886                        },
887                        input: Box::new(supersaw(0, 0.4, 0.6)),
888                    }),
889                },
890            },
891        },
892        Preset {
893            name: "Detune Dream",
894            category: "pad",
895            blurb: "a wide smear that drifts off the end of the note",
896            tree: PatchTree {
897                amp: amp(0.35, 0.4, 0.75, 0.55),
898                root: Delay {
899                    uid: Uid::NEW,
900                    time: 0.72, // 250 ms — was 0.3 (8 ms), a metallic comb
901                    feedback: 0.45,
902                    mix: 0.35,
903                    mod_depth: 0.0,
904                    modulation: ModNode::None,
905                    input: Box::new(Chorus {
906                        uid: Uid::NEW,
907                        rate: 0.3,
908                        depth: 0.55,
909                        mix: 0.4,
910                        mod_depth: 0.0,
911                        modulation: ModNode::None,
912                        input: Box::new(supersaw(0, 0.55, 0.5)),
913                    }),
914                },
915            },
916        },
917        Preset {
918            name: "Ember",
919            category: "pad",
920            blurb: "a pad that glows rather than shines",
921            tree: PatchTree {
922                amp: amp(0.75, 0.55, 0.8, 0.85),
923                root: Reverb {
924                    uid: Uid::NEW,
925                    size: 0.75,
926                    damp: 0.55,
927                    mix: 0.45,
928                    mod_depth: 0.0,
929                    modulation: ModNode::None,
930                    input: Box::new(Filter {
931                        uid: Uid::NEW,
932                        kind: FilterKind::SvfLp,
933                        cutoff: 0.42,
934                        resonance: 0.25,
935                        mod_depth: 0.25,
936                        modulation: ModNode::Lfo {
937                            uid: Uid::NEW,
938                            wave: Waveform::Sine,
939                            rate: 0.45, // 0.37 Hz
940                        },
941                        input: Box::new(Mix {
942                            uid: Uid::NEW,
943                            balance: 0.4,
944                            a: Box::new(vco(Waveform::Sine, -1, 0.5)),
945                            b: Box::new(Noise {
946                                uid: Uid::NEW,
947                                color: NoiseColor::Pink,
948                            }),
949                        }),
950                    }),
951                },
952            },
953        },
954        Preset {
955            name: "Slow Weather",
956            category: "pad",
957            blurb: "never sits in the same place twice",
958            tree: PatchTree {
959                amp: amp(0.79, 0.5, 0.85, 0.8),
960                root: Reverb {
961                    uid: Uid::NEW,
962                    size: 0.65,
963                    damp: 0.4,
964                    mix: 0.35,
965                    mod_depth: 0.0,
966                    modulation: ModNode::None,
967                    input: Box::new(Filter {
968                        uid: Uid::NEW,
969                        kind: FilterKind::SvfBp,
970                        cutoff: 0.5,
971                        resonance: 0.45,
972                        mod_depth: 0.5,
973                        modulation: ModNode::Lfo {
974                            uid: Uid::NEW,
975                            wave: Waveform::Triangle,
976                            rate: 0.4, // 0.25 Hz — slow, but it does arrive
977                        },
978                        input: Box::new(supersaw(0, 0.6, 0.55)),
979                    }),
980                },
981            },
982        },
983        Preset {
984            name: "Morph Pad",
985            category: "pad",
986            // The whole point of the wavetable: the LFO walks `morph` across
987            // the crossfade between adjacent tables, so the spectrum breathes
988            // without the filter moving at all.
989            blurb: "one oscillator slowly turning into a different one",
990            tree: PatchTree {
991                amp: amp(0.6, 0.45, 0.8, 0.7),
992                root: Reverb {
993                    uid: Uid::NEW,
994                    size: 0.68,
995                    damp: 0.4,
996                    mix: 0.35,
997                    mod_depth: 0.0,
998                    modulation: ModNode::None,
999                    input: Box::new(Filter {
1000                        uid: Uid::NEW,
1001                        kind: FilterKind::SvfLp,
1002                        cutoff: 0.62,
1003                        resonance: 0.25,
1004                        mod_depth: 0.0,
1005                        modulation: ModNode::None,
1006                        input: Box::new(Wavetable {
1007                            uid: Uid::NEW,
1008                            table: TableShape::Saw,
1009                            octave: 0,
1010                            morph: 0.3,
1011                            mod_depth: 0.6,
1012                            modulation: ModNode::Lfo {
1013                                uid: Uid::NEW,
1014                                wave: Waveform::Sine,
1015                                rate: 0.44, // 0.34 Hz — one sweep per held note
1016                            },
1017                        }),
1018                    }),
1019                },
1020            },
1021        },
1022        Preset {
1023            name: "Sweep Machine",
1024            category: "pad",
1025            // Feedback above 0.5 is positive (the map is bipolar and centred),
1026            // which turns the notches into resonant peaks — the difference
1027            // between a phaser you notice and one you don't.
1028            blurb: "notches walking up and down a wall of saws",
1029            tree: PatchTree {
1030                amp: amp(0.45, 0.45, 0.85, 0.6),
1031                root: Phaser {
1032                    uid: Uid::NEW,
1033                    rate: 0.35, // 0.25 Hz
1034                    depth: 0.75,
1035                    feedback: 0.78,
1036                    mod_depth: 0.0,
1037                    modulation: ModNode::None,
1038                    input: Box::new(supersaw(0, 0.45, 0.55)),
1039                },
1040            },
1041        },
1042        Preset {
1043            name: "Sea Change",
1044            category: "pad",
1045            // Fully wet, which is the whole distinction from a chorus: at
1046            // `mix` 0.5 the dry copy beats against the shifted one and this
1047            // becomes `Detune Dream` with extra steps.
1048            blurb: "a pad that will not quite sit still in tune",
1049            tree: PatchTree {
1050                amp: amp(0.35, 0.5, 0.8, 0.5),
1051                root: Vibrato {
1052                    uid: Uid::NEW,
1053                    rate: 0.55, // 1.6 Hz on quiver's own 0.1·150^x map
1054                    depth: 0.3,
1055                    mix: 1.0,
1056                    mod_depth: 0.4,
1057                    modulation: ModNode::Lfo {
1058                        uid: Uid::NEW,
1059                        wave: Waveform::Sine,
1060                        rate: 0.4, // 0.25 Hz
1061                    },
1062                    input: Box::new(supersaw(0, 0.35, 0.5)),
1063                },
1064            },
1065        },
1066        Preset {
1067            name: "Pump Room",
1068            category: "pad",
1069            // The first patch in the library whose level is driven by a
1070            // *second* signal: the plucked string is never heard, it only
1071            // tells the ducker when to get out of the way. Threshold 0.4 is
1072            // ≈0.32 V on the geometric detector map, just under the string's
1073            // own envelope peak, so every note ducks and then recovers.
1074            blurb: "a wide pad that steps aside every time the string is struck",
1075            tree: PatchTree {
1076                amp: amp(0.4, 0.5, 0.85, 0.5),
1077                root: Duck {
1078                    uid: Uid::NEW,
1079                    amount: 0.85,
1080                    threshold: 0.4,
1081                    release: 0.6, // ≈600 ms — the recovery is the groove
1082                    mod_depth: 0.0,
1083                    modulation: ModNode::None,
1084                    input: Box::new(Chorus {
1085                        uid: Uid::NEW,
1086                        rate: 0.25, // 0.27 Hz
1087                        depth: 0.6,
1088                        mix: 0.5,
1089                        mod_depth: 0.0,
1090                        modulation: ModNode::None,
1091                        input: Box::new(Filter {
1092                            uid: Uid::NEW,
1093                            kind: FilterKind::SvfLp,
1094                            cutoff: 0.62, // ≈2.0 kHz
1095                            resonance: 0.25,
1096                            mod_depth: 0.0,
1097                            modulation: ModNode::None,
1098                            input: Box::new(supersaw(-1, 0.4, 0.55)),
1099                        }),
1100                    }),
1101                    key: Box::new(Pluck {
1102                        uid: Uid::NEW,
1103                        octave: -1,
1104                        damping: 0.35,
1105                        brightness: 0.75,
1106                        mod_depth: 0.0,
1107                        modulation: ModNode::None,
1108                    }),
1109                },
1110            },
1111        },
1112        Preset {
1113            name: "Tidal",
1114            category: "pad",
1115            // `Or` fires wherever *either* pattern does, which on 8-against-12
1116            // is a dense but uneven figure — the opposite bargain to Ticker's
1117            // `And`. On chorus depth rather than on a cutoff it reads as the
1118            // width breathing rather than as a rhythm, which is what keeps it
1119            // a pad.
1120            blurb: "width that swells on two rhythms at once",
1121            tree: PatchTree {
1122                amp: amp(0.5, 0.5, 0.9, 0.55),
1123                root: Chorus {
1124                    uid: Uid::NEW,
1125                    rate: 0.25, // 0.27 Hz
1126                    depth: 0.35,
1127                    mix: 0.6,
1128                    mod_depth: 0.8,
1129                    modulation: ModNode::Pair {
1130                        uid: Uid::NEW,
1131                        kind: PairOp::Or,
1132                        a: Box::new(ModNode::Euclid {
1133                            uid: Uid::NEW,
1134                            rate: 0.55, // ≈83 bpm
1135                            steps: 0.303,
1136                            pulses: 0.230,
1137                        }),
1138                        b: Box::new(ModNode::Euclid {
1139                            uid: Uid::NEW,
1140                            rate: 0.55,
1141                            steps: 0.613,
1142                            pulses: 0.190,
1143                        }),
1144                    },
1145                    input: Box::new(Filter {
1146                        uid: Uid::NEW,
1147                        kind: FilterKind::SvfLp,
1148                        cutoff: 0.62, // ≈1.6 kHz
1149                        resonance: 0.15,
1150                        mod_depth: 0.0,
1151                        modulation: ModNode::None,
1152                        input: Box::new(supersaw(-1, 0.45, 0.5)),
1153                    }),
1154                },
1155            },
1156        },
1157        // ---------------------------------------------------------------
1158        // TEXTURE
1159        // ---------------------------------------------------------------
1160        Preset {
1161            name: "Noise Wash",
1162            category: "texture",
1163            blurb: "coastal fog with a pitch somewhere inside it",
1164            tree: PatchTree {
1165                amp: amp(0.7, 0.5, 0.75, 0.8),
1166                root: Filter {
1167                    uid: Uid::NEW,
1168                    kind: FilterKind::SvfLp,
1169                    cutoff: 0.45,
1170                    resonance: 0.5,
1171                    mod_depth: 0.5,
1172                    modulation: ModNode::Lfo {
1173                        uid: Uid::NEW,
1174                        wave: Waveform::Sine,
1175                        rate: 0.44, // 0.34 Hz — was 0.15 (0.033 Hz, 30 s/cycle)
1176                    },
1177                    input: Box::new(Noise {
1178                        uid: Uid::NEW,
1179                        color: NoiseColor::Pink,
1180                    }),
1181                },
1182            },
1183        },
1184        Preset {
1185            name: "Dub Echo",
1186            category: "texture",
1187            blurb: "the mixing desk is the instrument",
1188            tree: PatchTree {
1189                amp: amp(0.02, 0.3, 0.5, 0.4),
1190                root: Delay {
1191                    uid: Uid::NEW,
1192                    // Was 0.45 — 31 ms, which is a flanger. 0.79 is ~420 ms,
1193                    // which is the sound the name has always promised.
1194                    time: 0.79,
1195                    feedback: 0.75,
1196                    mix: 0.5,
1197                    mod_depth: 0.0,
1198                    modulation: ModNode::None,
1199                    input: Box::new(Filter {
1200                        uid: Uid::NEW,
1201                        kind: FilterKind::SvfLp,
1202                        cutoff: 0.5,
1203                        resonance: 0.35,
1204                        mod_depth: 0.0,
1205                        modulation: ModNode::None,
1206                        input: Box::new(vco(Waveform::Square, 0, 0.5)),
1207                    }),
1208                },
1209            },
1210        },
1211        Preset {
1212            name: "Static Ocean",
1213            category: "texture",
1214            blurb: "surf heard from a headland",
1215            tree: PatchTree {
1216                amp: amp(0.85, 0.5, 0.85, 0.9),
1217                root: Reverb {
1218                    uid: Uid::NEW,
1219                    size: 0.9,
1220                    damp: 0.6,
1221                    mix: 0.55,
1222                    mod_depth: 0.0,
1223                    modulation: ModNode::None,
1224                    input: Box::new(Filter {
1225                        uid: Uid::NEW,
1226                        kind: FilterKind::SvfBp,
1227                        cutoff: 0.55,
1228                        resonance: 0.65,
1229                        mod_depth: 0.55,
1230                        modulation: ModNode::Lfo {
1231                            uid: Uid::NEW,
1232                            wave: Waveform::Sine,
1233                            rate: 0.42, // 0.29 Hz
1234                        },
1235                        input: Box::new(Noise {
1236                            uid: Uid::NEW,
1237                            color: NoiseColor::White,
1238                        }),
1239                    }),
1240                },
1241            },
1242        },
1243        Preset {
1244            name: "Rotor",
1245            category: "texture",
1246            blurb: "a machine idling in the next room",
1247            tree: PatchTree {
1248                amp: amp(0.65, 0.5, 0.9, 0.6),
1249                root: Chorus {
1250                    uid: Uid::NEW,
1251                    rate: 0.45,
1252                    depth: 0.6,
1253                    mix: 0.5,
1254                    mod_depth: 0.0,
1255                    modulation: ModNode::None,
1256                    input: Box::new(Filter {
1257                        uid: Uid::NEW,
1258                        kind: FilterKind::SvfLp,
1259                        cutoff: 0.42,
1260                        resonance: 0.4,
1261                        mod_depth: 0.4,
1262                        modulation: ModNode::Rand {
1263                            uid: Uid::NEW,
1264                            rate: 0.58,
1265                            glide: 0.0,
1266                        }, // ~1.0 Hz
1267                        input: Box::new(vco(Waveform::Square, -1, 0.5)),
1268                    }),
1269                },
1270            },
1271        },
1272        Preset {
1273            name: "Long Room",
1274            category: "texture",
1275            blurb: "a drone that builds itself while you hold the key",
1276            tree: PatchTree {
1277                amp: amp(0.7, 0.5, 0.85, 0.9),
1278                root: Reverb {
1279                    uid: Uid::NEW,
1280                    size: 0.8,
1281                    damp: 0.45,
1282                    mix: 0.4,
1283                    mod_depth: 0.0,
1284                    modulation: ModNode::None,
1285                    input: Box::new(Delay {
1286                        uid: Uid::NEW,
1287                        time: 0.82, // ~500 ms
1288                        feedback: 0.62,
1289                        mix: 0.45,
1290                        mod_depth: 0.0,
1291                        modulation: ModNode::None,
1292                        input: Box::new(Filter {
1293                            uid: Uid::NEW,
1294                            kind: FilterKind::SvfLp,
1295                            cutoff: 0.45,
1296                            resonance: 0.3,
1297                            mod_depth: 0.22,
1298                            modulation: ModNode::Lfo {
1299                                uid: Uid::NEW,
1300                                wave: Waveform::Triangle,
1301                                rate: 0.42, // 0.29 Hz — 0.38 was 0.21, under the floor
1302                            },
1303                            input: Box::new(vco(Waveform::Sine, -1, 0.5)),
1304                        }),
1305                    }),
1306                },
1307            },
1308        },
1309        Preset {
1310            name: "Handheld",
1311            category: "texture",
1312            // S&H on the bit depth, with the glide open a little so the
1313            // quantizer slides between resolutions instead of stepping —
1314            // the difference between a broken machine and a dying one.
1315            blurb: "a sound remembered by something with not enough memory",
1316            tree: PatchTree {
1317                amp: amp(0.02, 0.4, 0.6, 0.3),
1318                root: Bitcrush {
1319                    uid: Uid::NEW,
1320                    bits: 0.32,
1321                    downsample: 0.42,
1322                    mod_depth: 0.45,
1323                    modulation: ModNode::Rand {
1324                        uid: Uid::NEW,
1325                        rate: 0.55, // 0.82 Hz
1326                        glide: 0.35,
1327                    },
1328                    input: Box::new(vco(Waveform::Square, 0, 0.5)),
1329                },
1330            },
1331        },
1332        Preset {
1333            name: "Jet Wash",
1334            category: "texture",
1335            // `feedback` 0.78 is a *positive* 0.39 on the bipolar map — the
1336            // resonant side, where the comb peaks ring rather than the
1337            // notches deepening. Below 0.5 the same knob flips the sign.
1338            blurb: "white noise pushed through a jet engine",
1339            tree: PatchTree {
1340                amp: amp(0.25, 0.5, 0.8, 0.5),
1341                root: Flanger {
1342                    uid: Uid::NEW,
1343                    rate: 0.45, // 0.4 Hz on quiver's own 0.05·100^x map
1344                    depth: 0.75,
1345                    feedback: 0.78,
1346                    mod_depth: 0.35,
1347                    modulation: ModNode::Lfo {
1348                        uid: Uid::NEW,
1349                        wave: Waveform::Sine,
1350                        rate: 0.42, // 0.29 Hz
1351                    },
1352                    input: Box::new(AudioNode::Noise {
1353                        uid: Uid::NEW,
1354                        color: NoiseColor::White,
1355                    }),
1356                },
1357            },
1358        },
1359        Preset {
1360            name: "Cloud Chamber",
1361            category: "texture",
1362            // `position` is kept near the write head on purpose: quiver's
1363            // grain buffer is 96 000 samples and starts empty, so a granulator
1364            // parked at the far end of it reads silence for the first two
1365            // seconds of a five-second phrase.
1366            blurb: "a triangle taken apart and reassembled in the wrong order",
1367            tree: PatchTree {
1368                amp: amp(0.3, 0.5, 0.85, 0.6),
1369                root: Reverb {
1370                    uid: Uid::NEW,
1371                    size: 0.75,
1372                    damp: 0.5,
1373                    mix: 0.45,
1374                    mod_depth: 0.0,
1375                    modulation: ModNode::None,
1376                    input: Box::new(Granular {
1377                        uid: Uid::NEW,
1378                        position: 0.08,
1379                        size: 0.35,    // 181 ms grains
1380                        density: 0.75, // 15 grains/second, so ~2.8 overlapping
1381                        mod_depth: 0.5,
1382                        modulation: ModNode::Lfo {
1383                            uid: Uid::NEW,
1384                            wave: Waveform::Sine,
1385                            rate: 0.43, // 0.31 Hz
1386                        },
1387                        input: Box::new(vco(Waveform::Triangle, 0, 0.5)),
1388                    }),
1389                },
1390            },
1391        },
1392        Preset {
1393            name: "Vox Machina",
1394            category: "texture",
1395            // A vocoder needs both branches to be right or it says nothing: a
1396            // sine carrier has no energy in fifteen of the sixteen bands, and
1397            // a static modulator makes a fixed filter. So the carrier is a
1398            // supersaw and the modulator's vowel is swept a full cycle per
1399            // held note — the sweep *is* the patch.
1400            blurb: "a saw stack made to speak, one vowel at a time",
1401            tree: PatchTree {
1402                amp: amp(0.25, 0.45, 0.85, 0.4),
1403                root: Vocoder {
1404                    uid: Uid::NEW,
1405                    bands: 0.75,  // ≈13 bands
1406                    attack: 0.15, // ≈38 ms
1407                    release: 0.35,
1408                    mod_depth: 0.0,
1409                    modulation: ModNode::None,
1410                    carrier: Box::new(supersaw(0, 0.5, 0.65)),
1411                    modulator: Box::new(Formant {
1412                        uid: Uid::NEW,
1413                        vowel: 0.25,
1414                        shift: 0.5, // no formant shift; the sweep is the vowel
1415                        octave: 0,
1416                        mod_depth: 0.7,
1417                        modulation: ModNode::Lfo {
1418                            uid: Uid::NEW,
1419                            wave: Waveform::Triangle,
1420                            rate: 0.49, // 0.5 Hz — one sweep per held note
1421                        },
1422                    }),
1423                },
1424            },
1425        },
1426        Preset {
1427            name: "Glass Rain",
1428            category: "texture",
1429            // `Hold` samples another modulator on its own clock, which is what
1430            // separates it from `s&h rand`: the values are not noise, they are
1431            // a sine caught at moments. On the granulator's read position that
1432            // makes the grains jump between a handful of places in the buffer
1433            // instead of sweeping through it — the difference between rain and
1434            // a smear.
1435            blurb: "grains caught from a few places at once, never sweeping",
1436            tree: PatchTree {
1437                amp: amp(0.35, 0.5, 0.85, 0.55),
1438                root: AudioNode::Granular {
1439                    uid: Uid::NEW,
1440                    position: 0.5,
1441                    size: 0.25,    // ≈130 ms grains
1442                    density: 0.75, // ≈15 per second
1443                    mod_depth: 0.8,
1444                    modulation: ModNode::Op {
1445                        uid: Uid::NEW,
1446                        kind: ModOp::Hold,
1447                        p0: 0.55, // ≈83 bpm — about one jump per bar
1448                        p1: 0.0,
1449                        input: Box::new(ModNode::Lfo {
1450                            uid: Uid::NEW,
1451                            wave: Waveform::Sine,
1452                            rate: 0.52, // 0.64 Hz, so successive samples differ
1453                        }),
1454                    },
1455                    input: Box::new(vco(Waveform::Triangle, 0, 0.5)),
1456                },
1457            },
1458        },
1459        Preset {
1460            name: "One Way",
1461            category: "texture",
1462            // `Rectify` in `positive` mode throws away the bottom half of the
1463            // LFO, so the fold threshold is only ever pushed *down* from where
1464            // the knob set it and the patch has a resting state it returns to.
1465            // A bipolar LFO on the same cable would fold and unfold
1466            // symmetrically, which reads as a wobble rather than as a bloom.
1467            blurb: "folds harder in bursts and always settles back",
1468            tree: PatchTree {
1469                amp: amp(0.3, 0.45, 0.8, 0.4),
1470                root: Reverb {
1471                    uid: Uid::NEW,
1472                    size: 0.6,
1473                    damp: 0.5,
1474                    mix: 0.35,
1475                    mod_depth: 0.0,
1476                    modulation: ModNode::None,
1477                    input: Box::new(Fold {
1478                        uid: Uid::NEW,
1479                        threshold: 0.7,
1480                        mod_depth: 0.7,
1481                        modulation: ModNode::Op {
1482                            uid: Uid::NEW,
1483                            kind: ModOp::Rectify,
1484                            p0: 0.5, // cell centre of `positive`
1485                            p1: 0.0,
1486                            input: Box::new(ModNode::Lfo {
1487                                uid: Uid::NEW,
1488                                wave: Waveform::Triangle,
1489                                rate: 0.5, // 0.55 Hz
1490                            }),
1491                        },
1492                        input: Box::new(vco(Waveform::Triangle, 0, 0.5)),
1493                    }),
1494                },
1495            },
1496        },
1497        // ---------------------------------------------------------------
1498        // PERC
1499        // ---------------------------------------------------------------
1500        Preset {
1501            name: "Flint",
1502            category: "perc",
1503            blurb: "a struck spark",
1504            tree: PatchTree {
1505                amp: amp(0.0, 0.35, 0.0, 0.25),
1506                root: Filter {
1507                    uid: Uid::NEW,
1508                    kind: FilterKind::SvfHp,
1509                    cutoff: 0.72,
1510                    resonance: 0.55,
1511                    mod_depth: 0.3,
1512                    modulation: ModNode::Env {
1513                        uid: Uid::NEW,
1514                        attack: 0.0,
1515                        decay: 0.2,
1516                    },
1517                    input: Box::new(Noise {
1518                        uid: Uid::NEW,
1519                        color: NoiseColor::White,
1520                    }),
1521                },
1522            },
1523        },
1524        Preset {
1525            name: "Deadfall",
1526            category: "perc",
1527            blurb: "a tuned drum with the room taken out",
1528            tree: PatchTree {
1529                amp: amp(0.0, 0.45, 0.0, 0.3),
1530                root: Filter {
1531                    uid: Uid::NEW,
1532                    kind: FilterKind::Ladder,
1533                    cutoff: 0.5,
1534                    resonance: 0.3,
1535                    mod_depth: 0.55,
1536                    modulation: ModNode::Env {
1537                        uid: Uid::NEW,
1538                        attack: 0.0,
1539                        decay: 0.2,
1540                    },
1541                    input: Box::new(vco(Waveform::Sine, -2, 0.5)),
1542                },
1543            },
1544        },
1545        Preset {
1546            name: "Ricochet",
1547            category: "perc",
1548            blurb: "a metallic ping bouncing down a corridor",
1549            tree: PatchTree {
1550                amp: amp(0.0, 0.4, 0.0, 0.25),
1551                root: Delay {
1552                    uid: Uid::NEW,
1553                    time: 0.54, // 60 ms — tight enough to read as a bounce
1554                    feedback: 0.68,
1555                    mix: 0.55,
1556                    mod_depth: 0.0,
1557                    modulation: ModNode::None,
1558                    input: Box::new(Filter {
1559                        uid: Uid::NEW,
1560                        kind: FilterKind::SvfBp,
1561                        cutoff: 0.7,
1562                        resonance: 0.65,
1563                        mod_depth: 0.25,
1564                        modulation: ModNode::Env {
1565                            uid: Uid::NEW,
1566                            attack: 0.0,
1567                            decay: 0.15,
1568                        },
1569                        input: Box::new(vco(Waveform::Square, 1, 0.5)),
1570                    }),
1571                },
1572            },
1573        },
1574        Preset {
1575            name: "Heartbeat",
1576            category: "perc",
1577            // `shape` 0.8 is most of the way to triangle, which is what makes
1578            // the pulse arrive rather than swell — a sine tremolo at this
1579            // depth reads as a wobble, not a beat.
1580            blurb: "a square wave with a pulse, and it quickens",
1581            tree: PatchTree {
1582                amp: amp(0.02, 0.45, 0.7, 0.25),
1583                root: Tremolo {
1584                    uid: Uid::NEW,
1585                    rate: 0.62, // 2.7 Hz on quiver's own 0.1·200^x map
1586                    depth: 0.85,
1587                    shape: 0.8,
1588                    mod_depth: 0.4,
1589                    modulation: ModNode::Env {
1590                        uid: Uid::NEW,
1591                        attack: 0.3,
1592                        decay: 0.6,
1593                    },
1594                    input: Box::new(Filter {
1595                        uid: Uid::NEW,
1596                        kind: FilterKind::SvfLp,
1597                        cutoff: 0.45,
1598                        resonance: 0.4,
1599                        mod_depth: 0.0,
1600                        modulation: ModNode::None,
1601                        input: Box::new(vco(Waveform::Square, -1, 0.5)),
1602                    }),
1603                },
1604            },
1605        },
1606        Preset {
1607            name: "Morse",
1608            category: "perc",
1609            // The drone underneath is continuous; what you hear is the
1610            // string's own envelope cut into it. Range 0.95 means a shut gate
1611            // passes almost nothing, so the patch is percussive without any
1612            // percussive source in it.
1613            blurb: "a drone you only hear while the string is still ringing",
1614            tree: PatchTree {
1615                amp: amp(0.1, 0.4, 0.9, 0.4),
1616                root: Gate {
1617                    uid: Uid::NEW,
1618                    threshold: 0.45,
1619                    range: 0.95,
1620                    release: 0.25, // ≈133 ms — a tight close
1621                    mod_depth: 0.0,
1622                    modulation: ModNode::None,
1623                    input: Box::new(Filter {
1624                        uid: Uid::NEW,
1625                        kind: FilterKind::SvfBp,
1626                        cutoff: 0.55, // ≈1.2 kHz
1627                        resonance: 0.6,
1628                        mod_depth: 0.0,
1629                        modulation: ModNode::None,
1630                        input: Box::new(supersaw(0, 0.35, 0.5)),
1631                    }),
1632                    sidechain: Box::new(Pluck {
1633                        uid: Uid::NEW,
1634                        octave: -1,
1635                        damping: 0.35,
1636                        brightness: 0.75,
1637                        mod_depth: 0.0,
1638                        modulation: ModNode::None,
1639                    }),
1640                },
1641            },
1642        },
1643        Preset {
1644            name: "Ticker",
1645            category: "perc",
1646            // Two euclidean patterns ANDed: the filter only opens where both
1647            // agree, which on 8-against-9 is a sparse figure that takes 72
1648            // steps to repeat. `esteps` 0.303 is 8 and 0.380 is 9 (quiver
1649            // takes `2 + cv·14.99` and the compiler feeds it `0.14 + 0.86x`);
1650            // `epulses` 0.230 is 3 of 8 and 0.122 is 3 of 9.
1651            blurb: "a filter opening only where two rhythms agree",
1652            tree: PatchTree {
1653                amp: amp(0.0, 0.25, 0.9, 0.15),
1654                root: Filter {
1655                    uid: Uid::NEW,
1656                    kind: FilterKind::SvfBp,
1657                    cutoff: 0.66, // ≈1.9 kHz
1658                    resonance: 0.55,
1659                    mod_depth: 0.8,
1660                    modulation: ModNode::Pair {
1661                        uid: Uid::NEW,
1662                        kind: PairOp::And,
1663                        a: Box::new(ModNode::Euclid {
1664                            uid: Uid::NEW,
1665                            rate: 0.62, // ≈107 bpm
1666                            steps: 0.303,
1667                            pulses: 0.230,
1668                        }),
1669                        b: Box::new(ModNode::Euclid {
1670                            uid: Uid::NEW,
1671                            rate: 0.62,
1672                            steps: 0.380,
1673                            pulses: 0.122,
1674                        }),
1675                    },
1676                    input: Box::new(AudioNode::Noise {
1677                        uid: Uid::NEW,
1678                        color: NoiseColor::White,
1679                    }),
1680                },
1681            },
1682        },
1683        Preset {
1684            name: "Woodblock",
1685            category: "perc",
1686            // The pluck's own decay is the sound; the bandpass only decides
1687            // which block. Brightness high and decay low is a small hard
1688            // object — the string's loop filter opens as `damping` rises, so
1689            // 0.2 is a short, dry ring rather than a long one.
1690            blurb: "small, hard and hollow; struck once",
1691            tree: PatchTree {
1692                amp: amp(0.0, 0.3, 0.0, 0.2),
1693                root: Filter {
1694                    uid: Uid::NEW,
1695                    kind: FilterKind::SvfBp,
1696                    cutoff: 0.6, // ≈1.0 kHz
1697                    resonance: 0.6,
1698                    mod_depth: 0.0,
1699                    modulation: ModNode::None,
1700                    input: Box::new(AudioNode::Pluck {
1701                        uid: Uid::NEW,
1702                        octave: 1,
1703                        damping: 0.2,
1704                        brightness: 0.85,
1705                        mod_depth: 0.0,
1706                        modulation: ModNode::None,
1707                    }),
1708                },
1709            },
1710        },
1711        Preset {
1712            name: "Gated Snare",
1713            category: "perc",
1714            // The gate is keyed by the pluck, not by the noise it passes —
1715            // which is the whole point of a second audio branch. The key is a
1716            // plucked string at ≈0.14 V mean, so the threshold sits low on the
1717            // geometric detector map; 0.45 is where a struck string opens it
1718            // and lets it shut again inside one note.
1719            blurb: "noise, allowed through only while a string is ringing",
1720            tree: PatchTree {
1721                amp: amp(0.0, 0.4, 0.4, 0.2),
1722                root: AudioNode::Gate {
1723                    uid: Uid::NEW,
1724                    threshold: 0.45,
1725                    range: 0.85,
1726                    release: 0.2,
1727                    mod_depth: 0.0,
1728                    modulation: ModNode::None,
1729                    input: Box::new(Filter {
1730                        uid: Uid::NEW,
1731                        kind: FilterKind::SvfHp,
1732                        cutoff: 0.5, // ≈630 Hz
1733                        resonance: 0.2,
1734                        mod_depth: 0.0,
1735                        modulation: ModNode::None,
1736                        input: Box::new(AudioNode::Noise {
1737                            uid: Uid::NEW,
1738                            color: NoiseColor::White,
1739                        }),
1740                    }),
1741                    sidechain: Box::new(AudioNode::Pluck {
1742                        uid: Uid::NEW,
1743                        octave: 0,
1744                        damping: 0.3,
1745                        brightness: 0.6,
1746                        mod_depth: 0.0,
1747                        modulation: ModNode::None,
1748                    }),
1749                },
1750            },
1751        },
1752        // ---------------------------------------------------------------
1753        // WEIRD
1754        // ---------------------------------------------------------------
1755        Preset {
1756            name: "Two Minds",
1757            category: "weird",
1758            // `Switch` picks between its two branches on the level of `b`, so
1759            // the *modulator itself* changes shape mid-note: while the fast
1760            // LFO is high the cutoff follows the slow one, and while it is low
1761            // the fast one takes over. Neither rate divides the other, so the
1762            // handover never lands in the same place twice.
1763            blurb: "a filter modulated by two minds that keep interrupting each other",
1764            tree: PatchTree {
1765                amp: amp(0.3, 0.4, 0.8, 0.4),
1766                root: Filter {
1767                    uid: Uid::NEW,
1768                    kind: FilterKind::SvfLp,
1769                    cutoff: 0.5, // ≈630 Hz
1770                    resonance: 0.5,
1771                    mod_depth: 0.75,
1772                    modulation: ModNode::Pair {
1773                        uid: Uid::NEW,
1774                        kind: PairOp::Switch,
1775                        a: Box::new(ModNode::Lfo {
1776                            uid: Uid::NEW,
1777                            wave: Waveform::Triangle,
1778                            rate: 0.42, // 0.29 Hz
1779                        }),
1780                        b: Box::new(ModNode::Lfo {
1781                            uid: Uid::NEW,
1782                            wave: Waveform::Sine,
1783                            rate: 0.58, // 1.0 Hz
1784                        }),
1785                    },
1786                    input: Box::new(supersaw(0, 0.4, 0.5)),
1787                },
1788            },
1789        },
1790        Preset {
1791            name: "Ceiling",
1792            category: "weird",
1793            // `Max` is a floor, not a ceiling — the name is what it does to
1794            // the *sound*. The envelope closes the filter after each attack,
1795            // but the LFO underneath it never lets the cutoff fall past its
1796            // own peak, so the patch decays to a breathing plateau instead of
1797            // to silence.
1798            blurb: "decays toward a moving floor and never reaches it",
1799            tree: PatchTree {
1800                amp: amp(0.05, 0.4, 0.7, 0.35),
1801                root: Filter {
1802                    uid: Uid::NEW,
1803                    kind: FilterKind::Ladder,
1804                    cutoff: 0.35, // ≈220 Hz
1805                    resonance: 0.55,
1806                    mod_depth: 0.6,
1807                    modulation: ModNode::Pair {
1808                        uid: Uid::NEW,
1809                        kind: PairOp::Max,
1810                        a: Box::new(ModNode::Env {
1811                            uid: Uid::NEW,
1812                            attack: 0.05, // ≈2.5 ms
1813                            decay: 0.5,   // ≈100 ms
1814                        }),
1815                        b: Box::new(ModNode::Lfo {
1816                            uid: Uid::NEW,
1817                            wave: Waveform::Sine,
1818                            rate: 0.49, // 0.51 Hz
1819                        }),
1820                    },
1821                    input: Box::new(vco(Waveform::Saw, -1, 0.5)),
1822                },
1823            },
1824        },
1825        Preset {
1826            name: "Undertow",
1827            category: "weird",
1828            // `Min` is the opposite bargain: the room only opens where *both*
1829            // modulators agree to open it, so two slow sines a fifth apart in
1830            // rate produce a swell that mostly does not arrive. The patch is
1831            // built to be listened to across several notes rather than one.
1832            blurb: "a room that only opens when two slow tides agree",
1833            tree: PatchTree {
1834                amp: amp(0.55, 0.5, 0.85, 0.6),
1835                root: Reverb {
1836                    uid: Uid::NEW,
1837                    size: 0.55,
1838                    damp: 0.4,
1839                    mix: 0.7,
1840                    mod_depth: 0.9,
1841                    modulation: ModNode::Pair {
1842                        uid: Uid::NEW,
1843                        kind: PairOp::Min,
1844                        a: Box::new(ModNode::Lfo {
1845                            uid: Uid::NEW,
1846                            wave: Waveform::Sine,
1847                            rate: 0.4, // 0.25 Hz
1848                        }),
1849                        b: Box::new(ModNode::Lfo {
1850                            uid: Uid::NEW,
1851                            wave: Waveform::Sine,
1852                            rate: 0.46, // 0.38 Hz
1853                        }),
1854                    },
1855                    input: Box::new(Filter {
1856                        uid: Uid::NEW,
1857                        kind: FilterKind::SvfLp,
1858                        cutoff: 0.55, // ≈890 Hz
1859                        resonance: 0.2,
1860                        mod_depth: 0.0,
1861                        modulation: ModNode::None,
1862                        input: Box::new(supersaw(-1, 0.5, 0.6)),
1863                    }),
1864                },
1865            },
1866        },
1867        Preset {
1868            name: "Sour Mash",
1869            category: "weird",
1870            // A square LFO on a fold threshold is the only hard-switching
1871            // modulation the grammar can make. Nothing else uses it.
1872            //
1873            // The input is deliberately a pair of `Vco`s and **not** a
1874            // `Supersaw`. Quiver's wavefolder folds `x / 5.0`, and `Vco` and
1875            // `Noise` scale their outputs `* 5.0` while `Supersaw` does not
1876            // (`oscillators.rs`, `outputs.set(10, output)`) — measured peaks
1877            // 4.94 against 0.74. Fed a supersaw, the folder never reaches its
1878            // own threshold: sweeping it 0.25 → 1.0 gave bit-identical output,
1879            // and this patch, whose entire premise is a hard-switching folder,
1880            // measured as the 4th most static of the 29. Anything built on
1881            // `Fold` needs a source at rack level.
1882            blurb: "pure character, no manners, and no filter at all",
1883            tree: PatchTree {
1884                amp: amp(0.2, 0.45, 0.7, 0.35),
1885                root: Fold {
1886                    uid: Uid::NEW,
1887                    threshold: 0.3,
1888                    mod_depth: 0.6,
1889                    modulation: ModNode::Lfo {
1890                        uid: Uid::NEW,
1891                        wave: Waveform::Square,
1892                        rate: 0.6, // 1.2 Hz — audibly switching
1893                    },
1894                    input: Box::new(Mix {
1895                        uid: Uid::NEW,
1896                        balance: 0.45,
1897                        a: Box::new(vco(Waveform::Saw, 0, 0.5)),
1898                        b: Box::new(vco(Waveform::Square, -1, 0.5)),
1899                    }),
1900                },
1901            },
1902        },
1903        Preset {
1904            name: "Wrong Number",
1905            category: "weird",
1906            blurb: "a narrow resonant peak, hunting for a pitch",
1907            tree: PatchTree {
1908                amp: amp(0.15, 0.5, 0.6, 0.35),
1909                root: Filter {
1910                    uid: Uid::NEW,
1911                    kind: FilterKind::SvfBp,
1912                    cutoff: 0.45,
1913                    resonance: 0.82,
1914                    mod_depth: 0.7,
1915                    modulation: ModNode::Rand {
1916                        uid: Uid::NEW,
1917                        rate: 0.8,
1918                        glide: 0.0,
1919                    }, // ~6 Hz
1920                    input: Box::new(vco(Waveform::Triangle, 0, 0.5)),
1921                },
1922            },
1923        },
1924        Preset {
1925            name: "Inside Out",
1926            category: "weird",
1927            // The one place an Env *attack* is long: the highpass corner
1928            // climbs for most of a second after the note starts, so the body
1929            // thins out as the note sustains and the transient is the last
1930            // thing left.
1931            blurb: "the note arrives some time after the transient does",
1932            tree: PatchTree {
1933                amp: amp(0.6, 0.5, 0.7, 0.45),
1934                root: Chorus {
1935                    uid: Uid::NEW,
1936                    rate: 0.35, // 0.39 Hz — 0.15 was 0.18, slower than any LFO here
1937                    depth: 0.7,
1938                    mix: 0.6,
1939                    mod_depth: 0.0,
1940                    modulation: ModNode::None,
1941                    input: Box::new(Filter {
1942                        uid: Uid::NEW,
1943                        kind: FilterKind::SvfHp,
1944                        cutoff: 0.8,
1945                        resonance: 0.45,
1946                        mod_depth: 0.65,
1947                        modulation: ModNode::Env {
1948                            uid: Uid::NEW,
1949                            attack: 0.6,
1950                            decay: 0.65,
1951                        },
1952                        input: Box::new(Mix {
1953                            uid: Uid::NEW,
1954                            balance: 0.55,
1955                            a: Box::new(vco(Waveform::Saw, -1, 0.5)),
1956                            b: Box::new(vco(Waveform::Square, 1, 0.5)),
1957                        }),
1958                    }),
1959                },
1960            },
1961        },
1962        // ---------------------------------------------------------------
1963        // The three below could not have existed before wave 2C, when
1964        // modulation stopped being a flat list of leaves. Each is built on a
1965        // different one of the new productions.
1966        // ---------------------------------------------------------------
1967        Preset {
1968            name: "Ask The Dice",
1969            category: "lead",
1970            // The whole point of the quantizer, and the reason `mod_depth` is
1971            // exactly 1.0 here rather than a taste value: the cable's gain at
1972            // full depth is 0.1, which is precisely what turns the quantizer's
1973            // grid into whole semitones on the pitch offset. Anywhere else on
1974            // the knob the intervals are a stretched tuning — still a melody,
1975            // but not this one.
1976            blurb: "a random melody, quantized to A minor and glided into",
1977            tree: PatchTree {
1978                amp: amp(0.02, 0.3, 0.5, 0.25),
1979                root: Filter {
1980                    uid: Uid::NEW,
1981                    kind: FilterKind::SvfLp,
1982                    cutoff: 0.62,
1983                    resonance: 0.4,
1984                    mod_depth: 0.35,
1985                    modulation: ModNode::Env {
1986                        uid: Uid::NEW,
1987                        attack: 0.02,
1988                        decay: 0.3,
1989                    },
1990                    input: Box::new(Vco {
1991                        uid: Uid::NEW,
1992                        wave: Waveform::Saw,
1993                        octave: 0,
1994                        detune: 0.5,
1995                        mod_depth: 1.0,
1996                        // The wave's headline term, two processors deep:
1997                        // noise sampled and held, snapped to a scale, then
1998                        // glided between. The slew is last so it glides
1999                        // *between notes* — put it under the quantizer and it
2000                        // would only smooth the noise before it was snapped,
2001                        // which is inaudible.
2002                        modulation: ModNode::Op {
2003                            uid: Uid::NEW,
2004                            kind: ModOp::Slew,
2005                            p0: 0.18, // ≈35 ms up…
2006                            p1: 0.3,  // …and ≈90 ms down: a fall is a slur
2007                            input: Box::new(ModNode::Op {
2008                                uid: Uid::NEW,
2009                                kind: ModOp::Quantize,
2010                                p0: 9.5 / 12.0, // root A
2011                                p1: 2.5 / 7.0,  // minor
2012                                input: Box::new(ModNode::Rand {
2013                                    uid: Uid::NEW,
2014                                    rate: 0.62, // ≈1.6 Hz — a note every beat
2015                                    glide: 0.0, // the slew above owns the glide
2016                                }),
2017                            }),
2018                        },
2019                    }),
2020                },
2021            },
2022        },
2023        Preset {
2024            name: "Nine Against Four",
2025            category: "pad",
2026            // Two euclidean patterns of different lengths XORed: the combined
2027            // gate does not repeat until their step counts do, so a pad that
2028            // is nominally static never opens the same way twice inside a
2029            // phrase. This is the `Pair` production's reason for existing.
2030            blurb: "two rhythms disagreeing, opening the filter where they do",
2031            tree: PatchTree {
2032                amp: amp(0.25, 0.5, 0.85, 0.6),
2033                root: Reverb {
2034                    uid: Uid::NEW,
2035                    size: 0.7,
2036                    damp: 0.45,
2037                    mix: 0.45,
2038                    mod_depth: 0.0,
2039                    modulation: ModNode::None,
2040                    input: Box::new(Filter {
2041                        uid: Uid::NEW,
2042                        kind: FilterKind::SvfLp,
2043                        cutoff: 0.32,
2044                        resonance: 0.5,
2045                        mod_depth: 0.75,
2046                        modulation: ModNode::Pair {
2047                            uid: Uid::NEW,
2048                            kind: PairOp::Xor,
2049                            a: Box::new(ModNode::Euclid {
2050                                uid: Uid::NEW,
2051                                rate: 0.62,   // ≈120 BPM
2052                                steps: 0.36,  // 8 steps
2053                                pulses: 0.34, // 3 of 8
2054                            }),
2055                            b: Box::new(ModNode::Euclid {
2056                                uid: Uid::NEW,
2057                                rate: 0.62,   // the same clock…
2058                                steps: 0.44,  // …over 9 steps, so they drift
2059                                pulses: 0.28, // 3 of 9
2060                            }),
2061                        },
2062                        input: Box::new(supersaw(-1, 0.4, 0.55)),
2063                    }),
2064                },
2065            },
2066        },
2067        Preset {
2068            name: "Long Way Down",
2069            category: "texture",
2070            // The slew limiter on its own, at the top of its travel: a
2071            // stepped random source becomes a drift with no steps left in it
2072            // at all. `Rand`'s own `glide` cannot reach here — it shares one
2073            // knob between rise and fall and runs on quiver's raw map, where
2074            // everything under a second and a half lives in the bottom
2075            // quarter.
2076            blurb: "a random source with every step sanded off it",
2077            tree: PatchTree {
2078                amp: amp(0.4, 0.6, 0.9, 0.7),
2079                root: Filter {
2080                    uid: Uid::NEW,
2081                    kind: FilterKind::SvfBp,
2082                    cutoff: 0.5,
2083                    resonance: 0.6,
2084                    mod_depth: 0.85,
2085                    modulation: ModNode::Op {
2086                        uid: Uid::NEW,
2087                        kind: ModOp::Slew,
2088                        p0: 0.85, // ≈1.2 s up…
2089                        p1: 0.55, // …and ≈0.5 s down, so it falls faster
2090                        input: Box::new(ModNode::Rand {
2091                            uid: Uid::NEW,
2092                            rate: 0.5,  // ≈0.55 Hz
2093                            glide: 0.0, // hard steps in; the slew makes the ramp
2094                        }),
2095                    },
2096                    input: Box::new(Mix {
2097                        uid: Uid::NEW,
2098                        balance: 0.4,
2099                        a: Box::new(supersaw(0, 0.6, 0.4)),
2100                        b: Box::new(AudioNode::Noise {
2101                            uid: Uid::NEW,
2102                            color: NoiseColor::Pink,
2103                        }),
2104                    }),
2105                },
2106            },
2107        },
2108    ]
2109}
2110
2111#[cfg(test)]
2112mod tests {
2113    use super::*;
2114    use std::collections::HashSet;
2115
2116    /// `0.01·3000^x` Hz, the LFO/S&H rate map (quiver `oscillators.rs`).
2117    fn mod_hz(cv: f64) -> f64 {
2118        0.01 * 3000f64.powf(cv)
2119    }
2120
2121    /// `0.1·50^x` Hz — the chorus runs its own, much narrower map, which is
2122    /// why it needs its own conversion rather than sharing the LFO's. Missing
2123    /// this is how `Inside Out` ended up with a 0.18 Hz chorus: slower than
2124    /// any LFO in the library, and slower than the floor the library enforces
2125    /// on LFOs, while sitting outside the test that enforces it.
2126    fn chorus_hz(cv: f64) -> f64 {
2127        0.1 * 50f64.powf(cv)
2128    }
2129
2130    fn hz_of(kind: &str, cv: f64) -> f64 {
2131        if kind == "chorus" {
2132            chorus_hz(cv)
2133        } else {
2134            mod_hz(cv)
2135        }
2136    }
2137
2138    /// Walk every modulation source in the library.
2139    fn mod_sources() -> Vec<(&'static str, &'static str, f64)> {
2140        fn walk(
2141            n: &AudioNode,
2142            name: &'static str,
2143            out: &mut Vec<(&'static str, &'static str, f64)>,
2144        ) {
2145            // A free function, not a closure over `out`: the chorus arm has
2146            // to push its own rate *and* note its slot, and a closure holding
2147            // `out` mutably cannot coexist with that.
2148            fn note(
2149                m: &ModNode,
2150                name: &'static str,
2151                out: &mut Vec<(&'static str, &'static str, f64)>,
2152            ) {
2153                match m {
2154                    ModNode::Lfo { rate, .. } => out.push((name, "lfo", *rate)),
2155                    ModNode::Rand { rate, .. } => out.push((name, "s&h", *rate)),
2156                    ModNode::Env { .. } | ModNode::Follow { .. } | ModNode::None => {}
2157                    // A shaper's rate is its subterm's, so recurse rather
2158                    // than reporting the wrapper.
2159                    ModNode::Op { input, .. } => note(input, name, out),
2160                    ModNode::Pair { a, b, .. } => {
2161                        note(a, name, out);
2162                        note(b, name, out);
2163                    }
2164                    ModNode::Euclid { rate, .. } => out.push((name, "euclid", *rate)),
2165                }
2166            }
2167            match n {
2168                AudioNode::Chorus {
2169                    rate,
2170                    modulation,
2171                    input,
2172                    ..
2173                } => {
2174                    out.push((name, "chorus", *rate));
2175                    note(modulation, name, out);
2176                    walk(input, name, out);
2177                }
2178                AudioNode::Filter {
2179                    modulation, input, ..
2180                }
2181                | AudioNode::Fold {
2182                    modulation, input, ..
2183                }
2184                | AudioNode::Delay {
2185                    modulation, input, ..
2186                }
2187                | AudioNode::Reverb {
2188                    modulation, input, ..
2189                }
2190                | AudioNode::Distortion {
2191                    modulation, input, ..
2192                }
2193                | AudioNode::Bitcrush {
2194                    modulation, input, ..
2195                }
2196                | AudioNode::Phaser {
2197                    modulation, input, ..
2198                }
2199                | AudioNode::Flanger {
2200                    modulation, input, ..
2201                }
2202                | AudioNode::Tremolo {
2203                    modulation, input, ..
2204                }
2205                | AudioNode::Vibrato {
2206                    modulation, input, ..
2207                }
2208                | AudioNode::Eq {
2209                    modulation, input, ..
2210                }
2211                | AudioNode::Granular {
2212                    modulation, input, ..
2213                }
2214                | AudioNode::Shift {
2215                    modulation, input, ..
2216                } => {
2217                    note(modulation, name, out);
2218                    walk(input, name, out);
2219                }
2220                // The 2B binaries: a slot *and* two branches, and the control
2221                // branch's own LFOs are as audible as any other — it drives a
2222                // detector, and a detector hears everything the ear does.
2223                AudioNode::Comp {
2224                    modulation,
2225                    input,
2226                    sidechain: other,
2227                    ..
2228                }
2229                | AudioNode::Gate {
2230                    modulation,
2231                    input,
2232                    sidechain: other,
2233                    ..
2234                }
2235                | AudioNode::Duck {
2236                    modulation,
2237                    input,
2238                    key: other,
2239                    ..
2240                }
2241                | AudioNode::Vocoder {
2242                    modulation,
2243                    carrier: input,
2244                    modulator: other,
2245                    ..
2246                } => {
2247                    note(modulation, name, out);
2248                    walk(input, name, out);
2249                    walk(other, name, out);
2250                }
2251                // The two oscillators' slots reach *pitch* rather than a
2252                // timbre parameter, but a rate is a rate: an LFO too slow to
2253                // complete a cycle inside a note is as inaudible on pitch as
2254                // it is on a cutoff.
2255                AudioNode::Wavetable { modulation, .. }
2256                | AudioNode::Pluck { modulation, .. }
2257                | AudioNode::Formant { modulation, .. }
2258                | AudioNode::Vco { modulation, .. }
2259                | AudioNode::Supersaw { modulation, .. } => note(modulation, name, out),
2260                AudioNode::Mix { a, b, .. } | AudioNode::RingMod { a, b, .. } => {
2261                    walk(a, name, out);
2262                    walk(b, name, out);
2263                }
2264                // Neither has a modulation slot or a child to walk.
2265                AudioNode::Noise { .. } | AudioNode::Silence { .. } => {}
2266            }
2267        }
2268        let mut out = Vec::new();
2269        for p in preset_bank() {
2270            walk(&p.tree.root, p.name, &mut out);
2271        }
2272        out
2273    }
2274
2275    /// The regression this library shipped with for its whole life.
2276    ///
2277    /// Every LFO and S&H in the original nine sat between 0.033 Hz and
2278    /// 0.165 Hz — 6 to 30 seconds per cycle — while the audition phrase's
2279    /// longest held note is 1.8 s. Nothing modulated audibly, anywhere, and
2280    /// the warm start taught the model its first eighteen preferences from
2281    /// exactly those patches. The bug was invisible because `rate: 0.15`
2282    /// looks like a slow-ish number rather than a half-minute.
2283    ///
2284    /// **The floor is set by the bug it has to catch**, which is the only
2285    /// honest way to pick one. The four original offenders were 0.0332,
2286    /// 0.0496, 0.1648 and 0.1648 Hz; a floor of 0.15 Hz — the first number
2287    /// that "felt slow" — would have waved two of them straight through,
2288    /// while the module doc two hundred lines up calls 0.165 Hz inaudible. A
2289    /// gate that disagrees with its own file is not a gate. 0.2 Hz clears all
2290    /// four with margin and still leaves room for a genuinely slow pad drift.
2291    #[test]
2292    fn every_modulation_source_is_audible_within_one_note() {
2293        const FLOOR_HZ: f64 = 0.2;
2294        let slow: Vec<String> = mod_sources()
2295            .into_iter()
2296            .filter(|(_, kind, cv)| hz_of(kind, *cv) < FLOOR_HZ)
2297            .map(|(name, kind, cv)| {
2298                let f = hz_of(kind, cv);
2299                format!(
2300                    "{name} {kind} rate {cv} = {f:.3} Hz ({:.1} s per cycle)",
2301                    1.0 / f
2302                )
2303            })
2304            .collect();
2305        assert!(
2306            slow.is_empty(),
2307            "modulation too slow to hear in a 1.8 s note (floor {FLOOR_HZ} Hz):\n  {}",
2308            slow.join("\n  ")
2309        );
2310    }
2311
2312    /// Every one of the original four offenders must fail the gate. Without
2313    /// this, the floor is a number someone liked rather than one that catches
2314    /// the bug — which is exactly how the first version of it let half of them
2315    /// through.
2316    #[test]
2317    fn the_floor_catches_every_original_offender() {
2318        for cv in [0.15, 0.20, 0.35, 0.35] {
2319            let f = mod_hz(cv);
2320            assert!(
2321                f < 0.2,
2322                "rate {cv} = {f:.4} Hz would now pass the floor it exists to catch"
2323            );
2324        }
2325    }
2326
2327    /// What the library exercises, tallied in one place so the walker takes
2328    /// one argument instead of eight.
2329    #[derive(Default)]
2330    struct Coverage {
2331        waves: HashSet<String>,
2332        lfo_waves: HashSet<String>,
2333        kinds: HashSet<String>,
2334        colors: HashSet<String>,
2335        tables: HashSet<String>,
2336        drive_modes: HashSet<String>,
2337        octaves: HashSet<i8>,
2338        mods: HashSet<&'static str>,
2339        nodes: HashSet<&'static str>,
2340    }
2341
2342    impl Coverage {
2343        fn note_mod(&mut self, m: &ModNode) {
2344            match m {
2345                ModNode::None => {
2346                    self.mods.insert("none");
2347                }
2348                ModNode::Lfo { wave, .. } => {
2349                    self.mods.insert("lfo");
2350                    self.lfo_waves.insert(format!("{wave:?}"));
2351                }
2352                ModNode::Env { .. } => {
2353                    self.mods.insert("env");
2354                }
2355                ModNode::Rand { .. } => {
2356                    self.mods.insert("rand");
2357                }
2358                ModNode::Follow { .. } => {
2359                    self.mods.insert("follow");
2360                }
2361                ModNode::Euclid { .. } => {
2362                    self.mods.insert("euclid");
2363                }
2364                ModNode::Op { kind, input, .. } => {
2365                    self.mods.insert(kind.label());
2366                    self.note_mod(input);
2367                }
2368                ModNode::Pair { kind, a, b, .. } => {
2369                    self.mods.insert(kind.label());
2370                    self.note_mod(a);
2371                    self.note_mod(b);
2372                }
2373            };
2374        }
2375
2376        fn walk(&mut self, n: &AudioNode) {
2377            match n {
2378                AudioNode::Vco {
2379                    wave,
2380                    octave,
2381                    modulation,
2382                    ..
2383                } => {
2384                    self.nodes.insert("vco");
2385                    self.waves.insert(format!("{wave:?}"));
2386                    self.octaves.insert(*octave);
2387                    self.note_mod(modulation);
2388                }
2389                AudioNode::Supersaw {
2390                    octave, modulation, ..
2391                } => {
2392                    self.nodes.insert("supersaw");
2393                    self.octaves.insert(*octave);
2394                    self.note_mod(modulation);
2395                }
2396                AudioNode::Formant {
2397                    octave, modulation, ..
2398                } => {
2399                    self.nodes.insert("formant");
2400                    self.octaves.insert(*octave);
2401                    self.note_mod(modulation);
2402                }
2403                AudioNode::Noise { color, .. } => {
2404                    self.nodes.insert("noise");
2405                    self.colors.insert(format!("{color:?}"));
2406                }
2407                AudioNode::Silence { .. } => {
2408                    self.nodes.insert("silence");
2409                }
2410                AudioNode::Wavetable {
2411                    table,
2412                    octave,
2413                    modulation,
2414                    ..
2415                } => {
2416                    self.nodes.insert("wavetable");
2417                    self.tables.insert(format!("{table:?}"));
2418                    self.octaves.insert(*octave);
2419                    self.note_mod(modulation);
2420                }
2421                AudioNode::Pluck {
2422                    octave, modulation, ..
2423                } => {
2424                    self.nodes.insert("pluck");
2425                    self.octaves.insert(*octave);
2426                    self.note_mod(modulation);
2427                }
2428                AudioNode::Mix { a, b, .. } => {
2429                    self.nodes.insert("mix");
2430                    self.walk(a);
2431                    self.walk(b);
2432                }
2433                AudioNode::RingMod { a, b, .. } => {
2434                    self.nodes.insert("ringmod");
2435                    self.walk(a);
2436                    self.walk(b);
2437                }
2438                AudioNode::Filter {
2439                    kind,
2440                    modulation,
2441                    input,
2442                    ..
2443                } => {
2444                    self.nodes.insert("filter");
2445                    self.kinds.insert(format!("{kind:?}"));
2446                    self.note_mod(modulation);
2447                    self.walk(input);
2448                }
2449                AudioNode::Fold {
2450                    modulation, input, ..
2451                } => {
2452                    self.nodes.insert("fold");
2453                    self.note_mod(modulation);
2454                    self.walk(input);
2455                }
2456                AudioNode::Delay {
2457                    modulation, input, ..
2458                } => {
2459                    self.nodes.insert("delay");
2460                    self.note_mod(modulation);
2461                    self.walk(input);
2462                }
2463                AudioNode::Chorus {
2464                    modulation, input, ..
2465                } => {
2466                    self.nodes.insert("chorus");
2467                    self.note_mod(modulation);
2468                    self.walk(input);
2469                }
2470                AudioNode::Reverb {
2471                    modulation, input, ..
2472                } => {
2473                    self.nodes.insert("reverb");
2474                    self.note_mod(modulation);
2475                    self.walk(input);
2476                }
2477                AudioNode::Distortion {
2478                    mode,
2479                    modulation,
2480                    input,
2481                    ..
2482                } => {
2483                    self.nodes.insert("distortion");
2484                    self.drive_modes.insert(format!("{mode:?}"));
2485                    self.note_mod(modulation);
2486                    self.walk(input);
2487                }
2488                AudioNode::Bitcrush {
2489                    modulation, input, ..
2490                } => {
2491                    self.nodes.insert("bitcrush");
2492                    self.note_mod(modulation);
2493                    self.walk(input);
2494                }
2495                AudioNode::Phaser {
2496                    modulation, input, ..
2497                } => {
2498                    self.nodes.insert("phaser");
2499                    self.note_mod(modulation);
2500                    self.walk(input);
2501                }
2502                AudioNode::Flanger {
2503                    modulation, input, ..
2504                } => {
2505                    self.nodes.insert("flanger");
2506                    self.note_mod(modulation);
2507                    self.walk(input);
2508                }
2509                AudioNode::Tremolo {
2510                    modulation, input, ..
2511                } => {
2512                    self.nodes.insert("tremolo");
2513                    self.note_mod(modulation);
2514                    self.walk(input);
2515                }
2516                AudioNode::Vibrato {
2517                    modulation, input, ..
2518                } => {
2519                    self.nodes.insert("vibrato");
2520                    self.note_mod(modulation);
2521                    self.walk(input);
2522                }
2523                AudioNode::Eq {
2524                    modulation, input, ..
2525                } => {
2526                    self.nodes.insert("eq");
2527                    self.note_mod(modulation);
2528                    self.walk(input);
2529                }
2530                AudioNode::Granular {
2531                    modulation, input, ..
2532                } => {
2533                    self.nodes.insert("granular");
2534                    self.note_mod(modulation);
2535                    self.walk(input);
2536                }
2537                AudioNode::Shift {
2538                    modulation, input, ..
2539                } => {
2540                    self.nodes.insert("shift");
2541                    self.note_mod(modulation);
2542                    self.walk(input);
2543                }
2544                AudioNode::Comp {
2545                    modulation,
2546                    input,
2547                    sidechain,
2548                    ..
2549                } => {
2550                    self.nodes.insert("comp");
2551                    self.note_mod(modulation);
2552                    self.walk(input);
2553                    self.walk(sidechain);
2554                }
2555                AudioNode::Duck {
2556                    modulation,
2557                    input,
2558                    key,
2559                    ..
2560                } => {
2561                    self.nodes.insert("duck");
2562                    self.note_mod(modulation);
2563                    self.walk(input);
2564                    self.walk(key);
2565                }
2566                AudioNode::Gate {
2567                    modulation,
2568                    input,
2569                    sidechain,
2570                    ..
2571                } => {
2572                    self.nodes.insert("gate");
2573                    self.note_mod(modulation);
2574                    self.walk(input);
2575                    self.walk(sidechain);
2576                }
2577                AudioNode::Vocoder {
2578                    modulation,
2579                    carrier,
2580                    modulator,
2581                    ..
2582                } => {
2583                    self.nodes.insert("vocoder");
2584                    self.note_mod(modulation);
2585                    self.walk(carrier);
2586                    self.walk(modulator);
2587                }
2588            }
2589        }
2590    }
2591
2592    /// The preset library doubles as the instrument's documentation and as the
2593    /// taste model's first evidence, so it has to actually exercise the
2594    /// grammar. Before this it used white noise never, octave ±2 never,
2595    /// `Ladder`/`Rand`/`Fold`/`Mix` once each, and never nested a `Mix`.
2596    #[test]
2597    fn the_library_covers_the_grammar() {
2598        let mut cov = Coverage::default();
2599        for p in preset_bank() {
2600            cov.walk(&p.tree.root);
2601        }
2602        let Coverage {
2603            waves,
2604            lfo_waves,
2605            kinds,
2606            colors,
2607            tables,
2608            drive_modes,
2609            octaves,
2610            mods,
2611            nodes,
2612        } = cov;
2613
2614        assert_eq!(waves.len(), 4, "not every oscillator waveform is used");
2615        assert!(
2616            lfo_waves.len() >= 3,
2617            "the LFO is only ever run as {lfo_waves:?} — its waveform is a real timbral choice"
2618        );
2619        assert_eq!(kinds.len(), 4, "not every filter kind is used: {kinds:?}");
2620        assert_eq!(colors.len(), 2, "one of the noise colours is never heard");
2621        assert!(
2622            !tables.is_empty(),
2623            "the wavetable oscillator is never heard"
2624        );
2625        assert!(!drive_modes.is_empty(), "the distortion is never heard");
2626        // Every modulation **source** — the five leaf kinds plus the empty
2627        // slot — has to appear, on the same argument as the node list: a
2628        // source no preset demonstrates is a source nobody discovers.
2629        //
2630        // The eleven **shapers** are held to a weaker bar, and deliberately.
2631        // They are a combinatorial space rather than a list (any of them over
2632        // any source, two deep), so "one preset each" would be eleven near
2633        // duplicates that teach the same lesson; what the library owes is that
2634        // each of the three new *productions* — a leaf generator, a unary
2635        // processor, a binary combiner — is shown at least once, so the shape
2636        // of the sort is discoverable from the bank.
2637        for src in ["none", "lfo", "env", "rand", "follow", "euclid"] {
2638            assert!(mods.contains(src), "no preset uses the {src} modulator");
2639        }
2640        // The shapers *were* held to a weaker bar than the sources, on the
2641        // argument above that they are a combinatorial space rather than a
2642        // list. That bar turned out to be too weak to be worth having: it
2643        // passed with `rectify`, `hold` and five of the six combiners in zero
2644        // presets, which is exactly the "a source nobody demonstrates is a
2645        // source nobody discovers" failure the source rule exists to prevent —
2646        // and the bank's auto-namer has no adjectives for a region of the
2647        // grammar the library never visits.
2648        //
2649        // Each of the ten now needs one preset. They are not near-duplicates
2650        // in practice: `min` and `max` are opposite bargains on the same two
2651        // modulators, `and` and `or` are opposite densities on the same two
2652        // patterns, and `rectify` is the one that gives a modulator a resting
2653        // state. If a future op genuinely has nothing of its own to show, the
2654        // honest move is to cut it from the palette rather than to lower this.
2655        for op in [
2656            "quantize", "slew", "rectify", "hold", "min", "max", "and", "or", "xor", "switch",
2657        ] {
2658            assert!(
2659                mods.contains(op),
2660                "no preset uses the {op} modulation shaper: {mods:?}"
2661            );
2662        }
2663        assert_eq!(nodes.len(), 26, "not every audio node is used: {nodes:?}");
2664        for oct in [-2, -1, 0, 1, 2] {
2665            assert!(octaves.contains(&oct), "octave {oct} is never used");
2666        }
2667    }
2668
2669    /// A patch that contains a wavefolder must actually fold.
2670    ///
2671    /// `Sour Mash` shipped in the first draft of this library with a folder
2672    /// that did nothing at all: sweeping its threshold across the whole range
2673    /// produced *bit-identical* audio. Quiver's `Wavefolder` folds `x / 5.0`,
2674    /// and `Vco`/`Noise` scale their outputs `* 5.0` while `Supersaw` leaves
2675    /// them at unity — so on a supersaw the signal never reaches the fold
2676    /// threshold and the module is a wire. Nothing caught it: the patch
2677    /// compiled, vetted, rendered, and sounded like a plain supersaw with a
2678    /// confident blurb about hard-switching character.
2679    ///
2680    /// So: for every preset with a `Fold` in it, moving the threshold from
2681    /// hard to soft has to move the audio.
2682    #[test]
2683    fn every_wavefolder_actually_folds() {
2684        fn set_fold(n: &AudioNode, t: f64) -> AudioNode {
2685            match n {
2686                // The modulation is deliberately stripped. Sweeping the base
2687                // threshold while an LFO still rides on it makes the two
2688                // renders differ for reasons that have nothing to do with
2689                // folding — which is exactly how the first version of this
2690                // test passed on the very bug it was written to catch.
2691                AudioNode::Fold { input, .. } => AudioNode::Fold {
2692                    uid: Uid::NEW,
2693                    threshold: t,
2694                    mod_depth: 0.0,
2695                    modulation: ModNode::None,
2696                    input: Box::new(set_fold(input, t)),
2697                },
2698                AudioNode::Filter {
2699                    kind,
2700                    cutoff,
2701                    resonance,
2702                    mod_depth,
2703                    modulation,
2704                    input,
2705                    ..
2706                } => AudioNode::Filter {
2707                    uid: Uid::NEW,
2708                    kind: *kind,
2709                    cutoff: *cutoff,
2710                    resonance: *resonance,
2711                    mod_depth: *mod_depth,
2712                    modulation: modulation.clone(),
2713                    input: Box::new(set_fold(input, t)),
2714                },
2715                AudioNode::Delay {
2716                    time,
2717                    feedback,
2718                    mix,
2719                    mod_depth,
2720                    modulation,
2721                    input,
2722                    ..
2723                } => AudioNode::Delay {
2724                    uid: Uid::NEW,
2725                    time: *time,
2726                    feedback: *feedback,
2727                    mix: *mix,
2728                    mod_depth: *mod_depth,
2729                    modulation: modulation.clone(),
2730                    input: Box::new(set_fold(input, t)),
2731                },
2732                AudioNode::Chorus {
2733                    rate,
2734                    depth,
2735                    mix,
2736                    mod_depth,
2737                    modulation,
2738                    input,
2739                    ..
2740                } => AudioNode::Chorus {
2741                    uid: Uid::NEW,
2742                    rate: *rate,
2743                    depth: *depth,
2744                    mix: *mix,
2745                    mod_depth: *mod_depth,
2746                    modulation: modulation.clone(),
2747                    input: Box::new(set_fold(input, t)),
2748                },
2749                AudioNode::Reverb {
2750                    size,
2751                    damp,
2752                    mix,
2753                    mod_depth,
2754                    modulation,
2755                    input,
2756                    ..
2757                } => AudioNode::Reverb {
2758                    uid: Uid::NEW,
2759                    size: *size,
2760                    damp: *damp,
2761                    mix: *mix,
2762                    mod_depth: *mod_depth,
2763                    modulation: modulation.clone(),
2764                    input: Box::new(set_fold(input, t)),
2765                },
2766                AudioNode::Distortion {
2767                    drive,
2768                    tone,
2769                    mode,
2770                    mod_depth,
2771                    modulation,
2772                    input,
2773                    ..
2774                } => AudioNode::Distortion {
2775                    uid: Uid::NEW,
2776                    drive: *drive,
2777                    tone: *tone,
2778                    mode: *mode,
2779                    mod_depth: *mod_depth,
2780                    modulation: modulation.clone(),
2781                    input: Box::new(set_fold(input, t)),
2782                },
2783                AudioNode::Mix { balance, a, b, .. } => AudioNode::Mix {
2784                    uid: Uid::NEW,
2785                    balance: *balance,
2786                    a: Box::new(set_fold(a, t)),
2787                    b: Box::new(set_fold(b, t)),
2788                },
2789                other => other.clone(),
2790            }
2791        }
2792        fn has_fold(n: &AudioNode) -> bool {
2793            match n {
2794                AudioNode::Fold { .. } => true,
2795                AudioNode::Filter { input, .. }
2796                | AudioNode::Delay { input, .. }
2797                | AudioNode::Chorus { input, .. }
2798                | AudioNode::Reverb { input, .. }
2799                | AudioNode::Distortion { input, .. }
2800                | AudioNode::Bitcrush { input, .. }
2801                | AudioNode::Phaser { input, .. } => has_fold(input),
2802                AudioNode::Mix { a, b, .. } | AudioNode::RingMod { a, b, .. } => {
2803                    has_fold(a) || has_fold(b)
2804                }
2805                _ => false,
2806            }
2807        }
2808
2809        const SR: f64 = 44_100.0;
2810        let render = |tree: &PatchTree| -> f64 {
2811            quiver::rng::seed(0x0F01_D5EE);
2812            let mut v = crate::compile(tree, SR).expect("preset compiles");
2813            v.pitch.set(0.0);
2814            v.gate.set(5.0);
2815            let buf: Vec<(f64, f64)> = (0..44_100).map(|_| v.patch.tick()).collect();
2816            (buf.iter().map(|(l, _)| l * l).sum::<f64>() / buf.len() as f64).sqrt()
2817        };
2818
2819        let mut checked = 0;
2820        for p in preset_bank() {
2821            if !has_fold(&p.tree.root) {
2822                continue;
2823            }
2824            checked += 1;
2825            let hard = PatchTree {
2826                amp: p.tree.amp.clone(),
2827                root: set_fold(&p.tree.root, 0.2),
2828            };
2829            let soft = PatchTree {
2830                amp: p.tree.amp.clone(),
2831                root: set_fold(&p.tree.root, 0.95),
2832            };
2833            let (a, b) = (render(&hard), render(&soft));
2834            let rel = (a - b).abs() / a.max(b).max(1e-12);
2835            assert!(
2836                rel > 0.02,
2837                "{}: folding is inert — threshold 0.2 gives rms {a:.6}, 0.95 gives {b:.6} \
2838                 (relative difference {rel:.5}). A `Fold` fed a source that never reaches \
2839                 its threshold is a wire.",
2840                p.name
2841            );
2842        }
2843        assert!(checked >= 3, "only {checked} folded presets found");
2844    }
2845
2846    /// Categories are what the browser groups by and what the warm start
2847    /// samples across, so a typo'd one would silently create an eighth family
2848    /// of one — and quietly bias the cold start toward it.
2849    #[test]
2850    fn every_preset_declares_a_known_category_and_copy() {
2851        for p in preset_bank() {
2852            assert!(
2853                CATEGORIES.contains(&p.category),
2854                "{}: unknown category {:?}",
2855                p.name,
2856                p.category
2857            );
2858            assert!(!p.blurb.is_empty(), "{}: no blurb", p.name);
2859        }
2860        for c in CATEGORIES {
2861            let n = preset_bank().iter().filter(|p| p.category == c).count();
2862            assert!(
2863                n >= 3,
2864                "category {c} has only {n} presets — too thin to sample"
2865            );
2866        }
2867    }
2868
2869    /// Names are the handle the user carries back to the bank; two presets
2870    /// with one name is two patches nobody can tell apart.
2871    #[test]
2872    fn preset_names_are_unique() {
2873        let bank = preset_bank();
2874        let unique: HashSet<&str> = bank.iter().map(|p| p.name).collect();
2875        assert_eq!(unique.len(), bank.len(), "duplicate preset name");
2876    }
2877}