Skip to main content

auracle_grammar/
genome.rs

1//! [`PatchTree`] as a fugue-evo genome.
2//!
3//! The canonical [`TraceGenome`] encoding **is** the grammar's address scheme
4//! (see [`crate::prior`]): `to_trace` is a deterministic walk emitting the
5//! same `#leaf`/`#src`/`#op`/param choices the generative model samples, and
6//! `from_trace` inverts it. Because [`GenomePrior::trace_of`]'s default
7//! delegates to `to_trace`, scoring, warm-starting chains, and decode-replay
8//! all work without a second, divergent encoding.
9//!
10//! [`GenomePrior::trace_of`]: fugue_evo::inference::prior::GenomePrior::trace_of
11
12use fugue::{addr, Trace};
13use fugue_evo::error::GenomeError;
14use fugue_evo::genome::bounds::MultiBounds;
15use fugue_evo::genome::trace_genome::{ChoiceValue, TraceGenome};
16use fugue_evo::genome::traits::EvolutionaryGenome;
17use rand::Rng;
18
19use crate::prior::PatchGrammarPrior;
20use crate::term::{
21    AmpEnv, AudioNode, DriveMode, FilterKind, ModNode, ModOp, NoiseColor, PairOp, PatchTree,
22    TableShape, Uid, Waveform,
23};
24
25impl EvolutionaryGenome for PatchTree {
26    type Allele = f64;
27    type Phenotype = PatchTree;
28
29    fn decode(&self) -> Self::Phenotype {
30        self.clone()
31    }
32
33    fn dimension(&self) -> usize {
34        self.site_count()
35    }
36
37    /// Draws from the **default** grammar configuration; `bounds` is ignored
38    /// (this genome has no numeric-box structure). Prefer
39    /// [`PatchGrammarPrior::sample_with_rng`] to control the grammar.
40    fn generate<R: Rng>(rng: &mut R, _bounds: &MultiBounds) -> Self {
41        PatchGrammarPrior::default().sample_with_rng(rng)
42    }
43
44    /// Structural distance: parameter L1 where the trees agree, and a
45    /// subtree-size penalty where they diverge. Defined for any pair (never
46    /// panics); a heuristic for diversity mechanisms, not a metric with
47    /// audio-perceptual meaning.
48    fn distance(&self, other: &Self) -> f64 {
49        let amp = (self.amp.attack - other.amp.attack).abs()
50            + (self.amp.decay - other.amp.decay).abs()
51            + (self.amp.sustain - other.amp.sustain).abs()
52            + (self.amp.release - other.amp.release).abs();
53        amp + node_distance(&self.root, &other.root)
54    }
55
56    fn try_distance(&self, other: &Self) -> Result<f64, GenomeError> {
57        Ok(self.distance(other))
58    }
59}
60
61fn mod_distance(a: &ModNode, b: &ModNode) -> f64 {
62    match (a, b) {
63        (ModNode::None, ModNode::None) => 0.0,
64        (
65            ModNode::Lfo {
66                wave: wa, rate: ra, ..
67            },
68            ModNode::Lfo {
69                wave: wb, rate: rb, ..
70            },
71        ) => (if wa == wb { 0.0 } else { 1.0 }) + (ra - rb).abs(),
72        (
73            ModNode::Env {
74                attack: aa,
75                decay: da,
76                ..
77            },
78            ModNode::Env {
79                attack: ab,
80                decay: db,
81                ..
82            },
83        ) => (aa - ab).abs() + (da - db).abs(),
84        (
85            ModNode::Rand {
86                rate: ra,
87                glide: ga,
88                ..
89            },
90            ModNode::Rand {
91                rate: rb,
92                glide: gb,
93                ..
94            },
95        ) => (ra - rb).abs() + (ga - gb).abs(),
96        (
97            ModNode::Follow {
98                sens: sa,
99                release: ra,
100                ..
101            },
102            ModNode::Follow {
103                sens: sb,
104                release: rb,
105                ..
106            },
107        ) => (sa - sb).abs() + (ra - rb).abs(),
108        (
109            ModNode::Euclid {
110                rate: ra,
111                steps: sa,
112                pulses: pa,
113                ..
114            },
115            ModNode::Euclid {
116                rate: rb,
117                steps: sb,
118                pulses: pb,
119                ..
120            },
121        ) => (ra - rb).abs() + (sa - sb).abs() + (pa - pb).abs(),
122        // Recursive arms, on the same rule the audio tree uses: parameter L1
123        // where the terms agree, and a flat penalty where they diverge.
124        (
125            ModNode::Op {
126                kind: ka,
127                p0: p0a,
128                p1: p1a,
129                input: ia,
130                ..
131            },
132            ModNode::Op {
133                kind: kb,
134                p0: p0b,
135                p1: p1b,
136                input: ib,
137                ..
138            },
139        ) if ka == kb => (p0a - p0b).abs() + (p1a - p1b).abs() + mod_distance(ia, ib),
140        (
141            ModNode::Pair {
142                kind: ka,
143                a: aa,
144                b: ba,
145                ..
146            },
147            ModNode::Pair {
148                kind: kb,
149                a: ab,
150                b: bb,
151                ..
152            },
153        ) if ka == kb => mod_distance(aa, ab) + mod_distance(ba, bb),
154        // Diverging structures pay by size, so replacing a leaf with a
155        // two-deep chain reads as further away than swapping two leaves — the
156        // same shape as `node_distance`'s subtree penalty.
157        (a, b) => 2.0 + (a.size() as f64 - b.size() as f64).abs(),
158    }
159}
160
161fn node_distance(a: &AudioNode, b: &AudioNode) -> f64 {
162    use AudioNode::*;
163    match (a, b) {
164        (
165            Vco {
166                wave: wa,
167                octave: oa,
168                detune: da,
169                mod_depth: mda,
170                modulation: moda,
171                ..
172            },
173            Vco {
174                wave: wb,
175                octave: ob,
176                detune: db,
177                mod_depth: mdb,
178                modulation: modb,
179                ..
180            },
181        ) => {
182            (if wa == wb { 0.0 } else { 1.0 })
183                + (*oa as f64 - *ob as f64).abs() / 4.0
184                + (da - db).abs()
185                + (mda - mdb).abs()
186                + mod_distance(moda, modb)
187        }
188        (
189            Supersaw {
190                octave: oa,
191                detune: da,
192                mix: ma,
193                mod_depth: mda,
194                modulation: moda,
195                ..
196            },
197            Supersaw {
198                octave: ob,
199                detune: db,
200                mix: mb,
201                mod_depth: mdb,
202                modulation: modb,
203                ..
204            },
205        ) => {
206            (*oa as f64 - *ob as f64).abs() / 4.0
207                + (da - db).abs()
208                + (ma - mb).abs()
209                + (mda - mdb).abs()
210                + mod_distance(moda, modb)
211        }
212        (
213            Formant {
214                vowel: va,
215                shift: sa,
216                octave: oa,
217                mod_depth: mda,
218                modulation: moda,
219                ..
220            },
221            Formant {
222                vowel: vb,
223                shift: sb,
224                octave: ob,
225                mod_depth: mdb,
226                modulation: modb,
227                ..
228            },
229        ) => {
230            (va - vb).abs()
231                + (sa - sb).abs()
232                + (*oa as f64 - *ob as f64).abs() / 4.0
233                + (mda - mdb).abs()
234                + mod_distance(moda, modb)
235        }
236        (Noise { color: ca, .. }, Noise { color: cb, .. }) => {
237            if ca == cb {
238                0.0
239            } else {
240                1.0
241            }
242        }
243        (
244            Wavetable {
245                table: ta,
246                octave: oa,
247                morph: ma,
248                mod_depth: da,
249                modulation: moda,
250                ..
251            },
252            Wavetable {
253                table: tb,
254                octave: ob,
255                morph: mb,
256                mod_depth: db,
257                modulation: modb,
258                ..
259            },
260        ) => {
261            (if ta == tb { 0.0 } else { 1.0 })
262                + (*oa as f64 - *ob as f64).abs() / 4.0
263                + (ma - mb).abs()
264                + (da - db).abs()
265                + mod_distance(moda, modb)
266        }
267        (
268            Pluck {
269                octave: oa,
270                damping: da,
271                brightness: ba,
272                mod_depth: mda,
273                modulation: moda,
274                ..
275            },
276            Pluck {
277                octave: ob,
278                damping: db,
279                brightness: bb,
280                mod_depth: mdb,
281                modulation: modb,
282                ..
283            },
284        ) => {
285            (*oa as f64 - *ob as f64).abs() / 4.0
286                + (da - db).abs()
287                + (ba - bb).abs()
288                + (mda - mdb).abs()
289                + mod_distance(moda, modb)
290        }
291        (
292            Mix {
293                balance: la,
294                a: aa,
295                b: ba,
296                ..
297            },
298            Mix {
299                balance: lb,
300                a: ab,
301                b: bb,
302                ..
303            },
304        ) => (la - lb).abs() + node_distance(aa, ab) + node_distance(ba, bb),
305        (
306            RingMod {
307                mix: la,
308                a: aa,
309                b: ba,
310                ..
311            },
312            RingMod {
313                mix: lb,
314                a: ab,
315                b: bb,
316                ..
317            },
318        ) => (la - lb).abs() + node_distance(aa, ab) + node_distance(ba, bb),
319        (
320            Filter {
321                kind: ka,
322                cutoff: ca,
323                resonance: ra,
324                mod_depth: ma,
325                input: ia,
326                modulation: moda,
327                ..
328            },
329            Filter {
330                kind: kb,
331                cutoff: cb,
332                resonance: rb,
333                mod_depth: mb,
334                input: ib,
335                modulation: modb,
336                ..
337            },
338        ) => {
339            (if ka == kb { 0.0 } else { 1.0 })
340                + (ca - cb).abs()
341                + (ra - rb).abs()
342                + (ma - mb).abs()
343                + mod_distance(moda, modb)
344                + node_distance(ia, ib)
345        }
346        (
347            Fold {
348                threshold: ta,
349                mod_depth: ma,
350                input: ia,
351                modulation: moda,
352                ..
353            },
354            Fold {
355                threshold: tb,
356                mod_depth: mb,
357                input: ib,
358                modulation: modb,
359                ..
360            },
361        ) => (ta - tb).abs() + (ma - mb).abs() + mod_distance(moda, modb) + node_distance(ia, ib),
362        (
363            Delay {
364                time: ta,
365                feedback: fa,
366                mix: ma,
367                mod_depth: dpa,
368                input: ia,
369                modulation: moda,
370                ..
371            },
372            Delay {
373                time: tb,
374                feedback: fb,
375                mix: mb,
376                mod_depth: dpb,
377                input: ib,
378                modulation: modb,
379                ..
380            },
381        ) => {
382            (ta - tb).abs()
383                + (fa - fb).abs()
384                + (ma - mb).abs()
385                + (dpa - dpb).abs()
386                + mod_distance(moda, modb)
387                + node_distance(ia, ib)
388        }
389        (
390            Chorus {
391                rate: ra,
392                depth: da,
393                mix: ma,
394                mod_depth: dpa,
395                input: ia,
396                modulation: moda,
397                ..
398            },
399            Chorus {
400                rate: rb,
401                depth: db,
402                mix: mb,
403                mod_depth: dpb,
404                input: ib,
405                modulation: modb,
406                ..
407            },
408        ) => {
409            (ra - rb).abs()
410                + (da - db).abs()
411                + (ma - mb).abs()
412                + (dpa - dpb).abs()
413                + mod_distance(moda, modb)
414                + node_distance(ia, ib)
415        }
416        (
417            Reverb {
418                size: sa,
419                damp: da,
420                mix: ma,
421                mod_depth: dpa,
422                input: ia,
423                modulation: moda,
424                ..
425            },
426            Reverb {
427                size: sb,
428                damp: db,
429                mix: mb,
430                mod_depth: dpb,
431                input: ib,
432                modulation: modb,
433                ..
434            },
435        ) => {
436            (sa - sb).abs()
437                + (da - db).abs()
438                + (ma - mb).abs()
439                + (dpa - dpb).abs()
440                + mod_distance(moda, modb)
441                + node_distance(ia, ib)
442        }
443        (
444            Distortion {
445                drive: ga,
446                tone: ta,
447                mode: ka,
448                mod_depth: dpa,
449                input: ia,
450                modulation: moda,
451                ..
452            },
453            Distortion {
454                drive: gb,
455                tone: tb,
456                mode: kb,
457                mod_depth: dpb,
458                input: ib,
459                modulation: modb,
460                ..
461            },
462        ) => {
463            (ga - gb).abs()
464                + (ta - tb).abs()
465                + (if ka == kb { 0.0 } else { 1.0 })
466                + (dpa - dpb).abs()
467                + mod_distance(moda, modb)
468                + node_distance(ia, ib)
469        }
470        (
471            Bitcrush {
472                bits: ba,
473                downsample: sa,
474                mod_depth: dpa,
475                input: ia,
476                modulation: moda,
477                ..
478            },
479            Bitcrush {
480                bits: bb,
481                downsample: sb,
482                mod_depth: dpb,
483                input: ib,
484                modulation: modb,
485                ..
486            },
487        ) => {
488            (ba - bb).abs()
489                + (sa - sb).abs()
490                + (dpa - dpb).abs()
491                + mod_distance(moda, modb)
492                + node_distance(ia, ib)
493        }
494        (
495            Phaser {
496                rate: ra,
497                depth: da,
498                feedback: fa,
499                mod_depth: dpa,
500                input: ia,
501                modulation: moda,
502                ..
503            },
504            Phaser {
505                rate: rb,
506                depth: db,
507                feedback: fb,
508                mod_depth: dpb,
509                input: ib,
510                modulation: modb,
511                ..
512            },
513        ) => {
514            (ra - rb).abs()
515                + (da - db).abs()
516                + (fa - fb).abs()
517                + (dpa - dpb).abs()
518                + mod_distance(moda, modb)
519                + node_distance(ia, ib)
520        }
521        (
522            Flanger {
523                rate: xa,
524                depth: ya,
525                feedback: za,
526                mod_depth: dpa,
527                input: ia,
528                modulation: moda,
529                ..
530            },
531            Flanger {
532                rate: xb,
533                depth: yb,
534                feedback: zb,
535                mod_depth: dpb,
536                input: ib,
537                modulation: modb,
538                ..
539            },
540        ) => {
541            (xa - xb).abs()
542                + (ya - yb).abs()
543                + (za - zb).abs()
544                + (dpa - dpb).abs()
545                + mod_distance(moda, modb)
546                + node_distance(ia, ib)
547        }
548        (
549            Tremolo {
550                rate: xa,
551                depth: ya,
552                shape: za,
553                mod_depth: dpa,
554                input: ia,
555                modulation: moda,
556                ..
557            },
558            Tremolo {
559                rate: xb,
560                depth: yb,
561                shape: zb,
562                mod_depth: dpb,
563                input: ib,
564                modulation: modb,
565                ..
566            },
567        ) => {
568            (xa - xb).abs()
569                + (ya - yb).abs()
570                + (za - zb).abs()
571                + (dpa - dpb).abs()
572                + mod_distance(moda, modb)
573                + node_distance(ia, ib)
574        }
575        (
576            Vibrato {
577                rate: xa,
578                depth: ya,
579                mix: za,
580                mod_depth: dpa,
581                input: ia,
582                modulation: moda,
583                ..
584            },
585            Vibrato {
586                rate: xb,
587                depth: yb,
588                mix: zb,
589                mod_depth: dpb,
590                input: ib,
591                modulation: modb,
592                ..
593            },
594        ) => {
595            (xa - xb).abs()
596                + (ya - yb).abs()
597                + (za - zb).abs()
598                + (dpa - dpb).abs()
599                + mod_distance(moda, modb)
600                + node_distance(ia, ib)
601        }
602        (
603            Eq {
604                low: xa,
605                mid: ya,
606                high: za,
607                mod_depth: dpa,
608                input: ia,
609                modulation: moda,
610                ..
611            },
612            Eq {
613                low: xb,
614                mid: yb,
615                high: zb,
616                mod_depth: dpb,
617                input: ib,
618                modulation: modb,
619                ..
620            },
621        ) => {
622            (xa - xb).abs()
623                + (ya - yb).abs()
624                + (za - zb).abs()
625                + (dpa - dpb).abs()
626                + mod_distance(moda, modb)
627                + node_distance(ia, ib)
628        }
629        (
630            Granular {
631                position: xa,
632                size: ya,
633                density: za,
634                mod_depth: dpa,
635                input: ia,
636                modulation: moda,
637                ..
638            },
639            Granular {
640                position: xb,
641                size: yb,
642                density: zb,
643                mod_depth: dpb,
644                input: ib,
645                modulation: modb,
646                ..
647            },
648        ) => {
649            (xa - xb).abs()
650                + (ya - yb).abs()
651                + (za - zb).abs()
652                + (dpa - dpb).abs()
653                + mod_distance(moda, modb)
654                + node_distance(ia, ib)
655        }
656        (
657            Shift {
658                semis: xa,
659                window: ya,
660                mix: za,
661                mod_depth: dpa,
662                input: ia,
663                modulation: moda,
664                ..
665            },
666            Shift {
667                semis: xb,
668                window: yb,
669                mix: zb,
670                mod_depth: dpb,
671                input: ib,
672                modulation: modb,
673                ..
674            },
675        ) => {
676            (xa - xb).abs()
677                + (ya - yb).abs()
678                + (za - zb).abs()
679                + (dpa - dpb).abs()
680                + mod_distance(moda, modb)
681                + node_distance(ia, ib)
682        }
683        // The binary dynamics nodes: both branches are real audio subtrees, so
684        // both are walked, exactly as `Mix` and `RingMod` are.
685        (
686            Comp {
687                threshold: xa,
688                ratio: ya,
689                makeup: za,
690                mod_depth: dpa,
691                input: ia,
692                sidechain: sa,
693                modulation: moda,
694                ..
695            },
696            Comp {
697                threshold: xb,
698                ratio: yb,
699                makeup: zb,
700                mod_depth: dpb,
701                input: ib,
702                sidechain: sb,
703                modulation: modb,
704                ..
705            },
706        ) => {
707            (xa - xb).abs()
708                + (ya - yb).abs()
709                + (za - zb).abs()
710                + (dpa - dpb).abs()
711                + mod_distance(moda, modb)
712                + node_distance(ia, ib)
713                + node_distance(sa, sb)
714        }
715        (
716            Duck {
717                amount: xa,
718                threshold: ya,
719                release: za,
720                mod_depth: dpa,
721                input: ia,
722                key: ka,
723                modulation: moda,
724                ..
725            },
726            Duck {
727                amount: xb,
728                threshold: yb,
729                release: zb,
730                mod_depth: dpb,
731                input: ib,
732                key: kb,
733                modulation: modb,
734                ..
735            },
736        ) => {
737            (xa - xb).abs()
738                + (ya - yb).abs()
739                + (za - zb).abs()
740                + (dpa - dpb).abs()
741                + mod_distance(moda, modb)
742                + node_distance(ia, ib)
743                + node_distance(ka, kb)
744        }
745        (
746            Gate {
747                threshold: xa,
748                range: ya,
749                release: za,
750                mod_depth: dpa,
751                input: ia,
752                sidechain: sa,
753                modulation: moda,
754                ..
755            },
756            Gate {
757                threshold: xb,
758                range: yb,
759                release: zb,
760                mod_depth: dpb,
761                input: ib,
762                sidechain: sb,
763                modulation: modb,
764                ..
765            },
766        ) => {
767            (xa - xb).abs()
768                + (ya - yb).abs()
769                + (za - zb).abs()
770                + (dpa - dpb).abs()
771                + mod_distance(moda, modb)
772                + node_distance(ia, ib)
773                + node_distance(sa, sb)
774        }
775        (
776            Vocoder {
777                bands: xa,
778                attack: ya,
779                release: za,
780                mod_depth: dpa,
781                carrier: ca,
782                modulator: ma,
783                modulation: moda,
784                ..
785            },
786            Vocoder {
787                bands: xb,
788                attack: yb,
789                release: zb,
790                mod_depth: dpb,
791                carrier: cb,
792                modulator: mb,
793                modulation: modb,
794                ..
795            },
796        ) => {
797            (xa - xb).abs()
798                + (ya - yb).abs()
799                + (za - zb).abs()
800                + (dpa - dpb).abs()
801                + mod_distance(moda, modb)
802                + node_distance(ca, cb)
803                + node_distance(ma, mb)
804        }
805        // Different constructors: whole-subtree penalty.
806        _ => (a.size() + b.size()) as f64,
807    }
808}
809
810// ---------------------------------------------------------------------------
811// Trace encoding (canonical == grammar address scheme)
812// ---------------------------------------------------------------------------
813
814fn child_key(key: &str, i: usize) -> String {
815    format!("{key}/{i}")
816}
817
818fn mod_key(key: &str) -> String {
819    format!("{key}/m")
820}
821
822fn put_f64(t: &mut Trace, key: &str, site: &str, v: f64) {
823    t.insert_choice(addr!(key, site), ChoiceValue::F64(v), 0.0);
824}
825
826fn put_usize(t: &mut Trace, key: &str, site: &str, v: usize) {
827    t.insert_choice(addr!(key, site), ChoiceValue::Usize(v), 0.0);
828}
829
830fn put_bool(t: &mut Trace, key: &str, site: &str, v: bool) {
831    t.insert_choice(addr!(key, site), ChoiceValue::Bool(v), 0.0);
832}
833
834fn encode_mod(m: &ModNode, key: &str, t: &mut Trace) {
835    match m {
836        ModNode::None => put_usize(t, key, "mod", 0),
837        ModNode::Lfo { wave, rate, .. } => {
838            put_usize(t, key, "mod", 1);
839            put_usize(t, key, "wave", wave.index());
840            put_f64(t, key, "rate", *rate);
841        }
842        ModNode::Env { attack, decay, .. } => {
843            put_usize(t, key, "mod", 2);
844            put_f64(t, key, "att", *attack);
845            put_f64(t, key, "dec", *decay);
846        }
847        ModNode::Rand { rate, glide, .. } => {
848            put_usize(t, key, "mod", 3);
849            put_f64(t, key, "rate", *rate);
850            put_f64(t, key, "glide", *glide);
851        }
852        ModNode::Follow { sens, release, .. } => {
853            put_usize(t, key, "mod", 4);
854            put_f64(t, key, "sens", *sens);
855            put_f64(t, key, "rel", *release);
856        }
857        ModNode::Euclid {
858            rate,
859            steps,
860            pulses,
861            ..
862        } => {
863            put_usize(t, key, "mod", 5);
864            put_f64(t, key, "erate", *rate);
865            put_f64(t, key, "esteps", *steps);
866            put_f64(t, key, "epulses", *pulses);
867        }
868        // The recursive arms. Subterm keys are `<key>/0` and `<key>/1`, the
869        // same convention the audio tree uses — unambiguous because every
870        // modulation key already sits below a `/m`.
871        ModNode::Op {
872            kind,
873            p0,
874            p1,
875            input,
876            ..
877        } => {
878            put_usize(t, key, "mod", 6);
879            put_usize(t, key, "modop", kind.index());
880            let sites = kind.param_sites();
881            put_f64(t, key, sites[0], *p0);
882            if let Some(site) = sites.get(1) {
883                put_f64(t, key, site, *p1);
884            }
885            encode_mod(input, &child_key(key, 0), t);
886        }
887        ModNode::Pair { kind, a, b, .. } => {
888            put_usize(t, key, "mod", 7);
889            put_usize(t, key, "pairop", kind.index());
890            encode_mod(a, &child_key(key, 0), t);
891            encode_mod(b, &child_key(key, 1), t);
892        }
893    }
894}
895
896fn encode_node(n: &AudioNode, key: &str, t: &mut Trace) {
897    use AudioNode::*;
898    match n {
899        Vco {
900            wave,
901            octave,
902            detune,
903            mod_depth,
904            modulation,
905            ..
906        } => {
907            put_bool(t, key, "leaf", true);
908            put_usize(t, key, "src", 0);
909            put_usize(t, key, "wave", wave.index());
910            put_usize(t, key, "oct", (octave + 2) as usize);
911            put_f64(t, key, "det", *detune);
912            put_f64(t, key, "mdepth", *mod_depth);
913            encode_mod(modulation, &mod_key(key), t);
914        }
915        Supersaw {
916            octave,
917            detune,
918            mix,
919            mod_depth,
920            modulation,
921            ..
922        } => {
923            put_bool(t, key, "leaf", true);
924            put_usize(t, key, "src", 1);
925            put_usize(t, key, "oct", (octave + 2) as usize);
926            put_f64(t, key, "det", *detune);
927            put_f64(t, key, "smix", *mix);
928            put_f64(t, key, "mdepth", *mod_depth);
929            encode_mod(modulation, &mod_key(key), t);
930        }
931        Noise { color, .. } => {
932            put_bool(t, key, "leaf", true);
933            put_usize(t, key, "src", 2);
934            put_usize(t, key, "color", color.index());
935        }
936        // Index 6, appended after `Formant`: a source kind's index *is* what
937        // the trace stores, so a new one may only ever go on the end.
938        Silence { .. } => {
939            put_bool(t, key, "leaf", true);
940            put_usize(t, key, "src", 6);
941        }
942        Wavetable {
943            table,
944            octave,
945            morph,
946            mod_depth,
947            modulation,
948            ..
949        } => {
950            put_bool(t, key, "leaf", true);
951            put_usize(t, key, "src", 3);
952            put_usize(t, key, "table", table.index());
953            put_usize(t, key, "oct", (octave + 2) as usize);
954            put_f64(t, key, "morph", *morph);
955            put_f64(t, key, "mdepth", *mod_depth);
956            encode_mod(modulation, &mod_key(key), t);
957        }
958        Pluck {
959            octave,
960            damping,
961            brightness,
962            mod_depth,
963            modulation,
964            ..
965        } => {
966            put_bool(t, key, "leaf", true);
967            put_usize(t, key, "src", 4);
968            put_usize(t, key, "oct", (octave + 2) as usize);
969            put_f64(t, key, "damp", *damping);
970            put_f64(t, key, "bright", *brightness);
971            put_f64(t, key, "mdepth", *mod_depth);
972            encode_mod(modulation, &mod_key(key), t);
973        }
974        Formant {
975            vowel,
976            shift,
977            octave,
978            mod_depth,
979            modulation,
980            ..
981        } => {
982            put_bool(t, key, "leaf", true);
983            put_usize(t, key, "src", 5);
984            put_f64(t, key, "vowel", *vowel);
985            put_f64(t, key, "fshift", *shift);
986            put_usize(t, key, "oct", (octave + 2) as usize);
987            put_f64(t, key, "mdepth", *mod_depth);
988            encode_mod(modulation, &mod_key(key), t);
989        }
990        Mix { balance, a, b, .. } => {
991            put_bool(t, key, "leaf", false);
992            put_usize(t, key, "op", 0);
993            put_f64(t, key, "bal", *balance);
994            encode_node(a, &child_key(key, 0), t);
995            encode_node(b, &child_key(key, 1), t);
996        }
997        Filter {
998            kind,
999            cutoff,
1000            resonance,
1001            mod_depth,
1002            input,
1003            modulation,
1004            ..
1005        } => {
1006            put_bool(t, key, "leaf", false);
1007            put_usize(t, key, "op", 1);
1008            put_usize(t, key, "fkind", kind.index());
1009            put_f64(t, key, "cut", *cutoff);
1010            put_f64(t, key, "res", *resonance);
1011            put_f64(t, key, "mdepth", *mod_depth);
1012            encode_mod(modulation, &mod_key(key), t);
1013            encode_node(input, &child_key(key, 0), t);
1014        }
1015        Fold {
1016            threshold,
1017            mod_depth,
1018            input,
1019            modulation,
1020            ..
1021        } => {
1022            put_bool(t, key, "leaf", false);
1023            put_usize(t, key, "op", 2);
1024            put_f64(t, key, "thresh", *threshold);
1025            put_f64(t, key, "mdepth", *mod_depth);
1026            encode_mod(modulation, &mod_key(key), t);
1027            encode_node(input, &child_key(key, 0), t);
1028        }
1029        Delay {
1030            time,
1031            feedback,
1032            mix,
1033            mod_depth,
1034            input,
1035            modulation,
1036            ..
1037        } => {
1038            put_bool(t, key, "leaf", false);
1039            put_usize(t, key, "op", 3);
1040            put_f64(t, key, "time", *time);
1041            put_f64(t, key, "fb", *feedback);
1042            put_f64(t, key, "dmix", *mix);
1043            put_f64(t, key, "mdepth", *mod_depth);
1044            encode_mod(modulation, &mod_key(key), t);
1045            encode_node(input, &child_key(key, 0), t);
1046        }
1047        Chorus {
1048            rate,
1049            depth,
1050            mix,
1051            mod_depth,
1052            input,
1053            modulation,
1054            ..
1055        } => {
1056            put_bool(t, key, "leaf", false);
1057            put_usize(t, key, "op", 4);
1058            put_f64(t, key, "crate", *rate);
1059            put_f64(t, key, "cdepth", *depth);
1060            put_f64(t, key, "cmix", *mix);
1061            put_f64(t, key, "mdepth", *mod_depth);
1062            encode_mod(modulation, &mod_key(key), t);
1063            encode_node(input, &child_key(key, 0), t);
1064        }
1065        Reverb {
1066            size,
1067            damp,
1068            mix,
1069            mod_depth,
1070            input,
1071            modulation,
1072            ..
1073        } => {
1074            put_bool(t, key, "leaf", false);
1075            put_usize(t, key, "op", 5);
1076            put_f64(t, key, "rsize", *size);
1077            put_f64(t, key, "rdamp", *damp);
1078            put_f64(t, key, "rmix", *mix);
1079            put_f64(t, key, "mdepth", *mod_depth);
1080            encode_mod(modulation, &mod_key(key), t);
1081            encode_node(input, &child_key(key, 0), t);
1082        }
1083        Distortion {
1084            drive,
1085            tone,
1086            mode,
1087            mod_depth,
1088            input,
1089            modulation,
1090            ..
1091        } => {
1092            put_bool(t, key, "leaf", false);
1093            put_usize(t, key, "op", 6);
1094            put_f64(t, key, "drive", *drive);
1095            put_f64(t, key, "tone", *tone);
1096            put_usize(t, key, "dmode", mode.index());
1097            put_f64(t, key, "mdepth", *mod_depth);
1098            encode_mod(modulation, &mod_key(key), t);
1099            encode_node(input, &child_key(key, 0), t);
1100        }
1101        Bitcrush {
1102            bits,
1103            downsample,
1104            mod_depth,
1105            input,
1106            modulation,
1107            ..
1108        } => {
1109            put_bool(t, key, "leaf", false);
1110            put_usize(t, key, "op", 7);
1111            put_f64(t, key, "bits", *bits);
1112            put_f64(t, key, "dsamp", *downsample);
1113            put_f64(t, key, "mdepth", *mod_depth);
1114            encode_mod(modulation, &mod_key(key), t);
1115            encode_node(input, &child_key(key, 0), t);
1116        }
1117        Phaser {
1118            rate,
1119            depth,
1120            feedback,
1121            mod_depth,
1122            input,
1123            modulation,
1124            ..
1125        } => {
1126            put_bool(t, key, "leaf", false);
1127            put_usize(t, key, "op", 8);
1128            put_f64(t, key, "prate", *rate);
1129            put_f64(t, key, "pdepth", *depth);
1130            put_f64(t, key, "pfb", *feedback);
1131            put_f64(t, key, "mdepth", *mod_depth);
1132            encode_mod(modulation, &mod_key(key), t);
1133            encode_node(input, &child_key(key, 0), t);
1134        }
1135        Flanger {
1136            rate,
1137            depth,
1138            feedback,
1139            mod_depth,
1140            input,
1141            modulation,
1142            ..
1143        } => {
1144            put_bool(t, key, "leaf", false);
1145            put_usize(t, key, "op", 10);
1146            put_f64(t, key, "frate", *rate);
1147            put_f64(t, key, "fdepth", *depth);
1148            put_f64(t, key, "ffb", *feedback);
1149            put_f64(t, key, "mdepth", *mod_depth);
1150            encode_mod(modulation, &mod_key(key), t);
1151            encode_node(input, &child_key(key, 0), t);
1152        }
1153        Tremolo {
1154            rate,
1155            depth,
1156            shape,
1157            mod_depth,
1158            input,
1159            modulation,
1160            ..
1161        } => {
1162            put_bool(t, key, "leaf", false);
1163            put_usize(t, key, "op", 11);
1164            put_f64(t, key, "trate", *rate);
1165            put_f64(t, key, "tdepth", *depth);
1166            put_f64(t, key, "tshape", *shape);
1167            put_f64(t, key, "mdepth", *mod_depth);
1168            encode_mod(modulation, &mod_key(key), t);
1169            encode_node(input, &child_key(key, 0), t);
1170        }
1171        Vibrato {
1172            rate,
1173            depth,
1174            mix,
1175            mod_depth,
1176            input,
1177            modulation,
1178            ..
1179        } => {
1180            put_bool(t, key, "leaf", false);
1181            put_usize(t, key, "op", 12);
1182            put_f64(t, key, "vrate", *rate);
1183            put_f64(t, key, "vdepth", *depth);
1184            put_f64(t, key, "vmix", *mix);
1185            put_f64(t, key, "mdepth", *mod_depth);
1186            encode_mod(modulation, &mod_key(key), t);
1187            encode_node(input, &child_key(key, 0), t);
1188        }
1189        Eq {
1190            low,
1191            mid,
1192            high,
1193            mod_depth,
1194            input,
1195            modulation,
1196            ..
1197        } => {
1198            put_bool(t, key, "leaf", false);
1199            put_usize(t, key, "op", 13);
1200            put_f64(t, key, "low", *low);
1201            put_f64(t, key, "mid", *mid);
1202            put_f64(t, key, "high", *high);
1203            put_f64(t, key, "mdepth", *mod_depth);
1204            encode_mod(modulation, &mod_key(key), t);
1205            encode_node(input, &child_key(key, 0), t);
1206        }
1207        Granular {
1208            position,
1209            size,
1210            density,
1211            mod_depth,
1212            input,
1213            modulation,
1214            ..
1215        } => {
1216            put_bool(t, key, "leaf", false);
1217            put_usize(t, key, "op", 14);
1218            put_f64(t, key, "gpos", *position);
1219            put_f64(t, key, "gsize", *size);
1220            put_f64(t, key, "gdens", *density);
1221            put_f64(t, key, "mdepth", *mod_depth);
1222            encode_mod(modulation, &mod_key(key), t);
1223            encode_node(input, &child_key(key, 0), t);
1224        }
1225        RingMod { mix, a, b, .. } => {
1226            put_bool(t, key, "leaf", false);
1227            put_usize(t, key, "op", 9);
1228            put_f64(t, key, "rgmix", *mix);
1229            encode_node(a, &child_key(key, 0), t);
1230            encode_node(b, &child_key(key, 1), t);
1231        }
1232        Shift {
1233            semis,
1234            window,
1235            mix,
1236            mod_depth,
1237            input,
1238            modulation,
1239            ..
1240        } => {
1241            put_bool(t, key, "leaf", false);
1242            put_usize(t, key, "op", 15);
1243            put_f64(t, key, "semis", *semis);
1244            put_f64(t, key, "window", *window);
1245            put_f64(t, key, "smix", *mix);
1246            put_f64(t, key, "mdepth", *mod_depth);
1247            encode_mod(modulation, &mod_key(key), t);
1248            encode_node(input, &child_key(key, 0), t);
1249        }
1250        // The four binary nodes write their control branch at `/1`, exactly
1251        // where `Mix` and `RingMod` write theirs — the address scheme cannot
1252        // tell a second audio input from a second *audio* input.
1253        Comp {
1254            threshold,
1255            ratio,
1256            makeup,
1257            mod_depth,
1258            input,
1259            sidechain,
1260            modulation,
1261            ..
1262        } => {
1263            put_bool(t, key, "leaf", false);
1264            put_usize(t, key, "op", 16);
1265            put_f64(t, key, "thresh", *threshold);
1266            put_f64(t, key, "ratio", *ratio);
1267            put_f64(t, key, "makeup", *makeup);
1268            put_f64(t, key, "mdepth", *mod_depth);
1269            encode_mod(modulation, &mod_key(key), t);
1270            encode_node(input, &child_key(key, 0), t);
1271            encode_node(sidechain, &child_key(key, 1), t);
1272        }
1273        Duck {
1274            amount,
1275            threshold,
1276            release,
1277            mod_depth,
1278            input,
1279            key: key_input,
1280            modulation,
1281            ..
1282        } => {
1283            put_bool(t, key, "leaf", false);
1284            put_usize(t, key, "op", 17);
1285            put_f64(t, key, "amount", *amount);
1286            put_f64(t, key, "dthresh", *threshold);
1287            put_f64(t, key, "drel", *release);
1288            put_f64(t, key, "mdepth", *mod_depth);
1289            encode_mod(modulation, &mod_key(key), t);
1290            encode_node(input, &child_key(key, 0), t);
1291            encode_node(key_input, &child_key(key, 1), t);
1292        }
1293        Gate {
1294            threshold,
1295            range,
1296            release,
1297            mod_depth,
1298            input,
1299            sidechain,
1300            modulation,
1301            ..
1302        } => {
1303            put_bool(t, key, "leaf", false);
1304            put_usize(t, key, "op", 18);
1305            put_f64(t, key, "gthresh", *threshold);
1306            put_f64(t, key, "range", *range);
1307            put_f64(t, key, "grel", *release);
1308            put_f64(t, key, "mdepth", *mod_depth);
1309            encode_mod(modulation, &mod_key(key), t);
1310            encode_node(input, &child_key(key, 0), t);
1311            encode_node(sidechain, &child_key(key, 1), t);
1312        }
1313        Vocoder {
1314            bands,
1315            attack,
1316            release,
1317            mod_depth,
1318            carrier,
1319            modulator,
1320            modulation,
1321            ..
1322        } => {
1323            put_bool(t, key, "leaf", false);
1324            put_usize(t, key, "op", 19);
1325            put_f64(t, key, "bands", *bands);
1326            put_f64(t, key, "vatt", *attack);
1327            put_f64(t, key, "vrel", *release);
1328            put_f64(t, key, "mdepth", *mod_depth);
1329            encode_mod(modulation, &mod_key(key), t);
1330            encode_node(carrier, &child_key(key, 0), t);
1331            encode_node(modulator, &child_key(key, 1), t);
1332        }
1333    }
1334}
1335
1336fn get_f64(t: &Trace, key: &str, site: &str) -> Result<f64, GenomeError> {
1337    let a = addr!(key, site);
1338    t.get_f64(&a)
1339        .ok_or_else(|| GenomeError::MissingAddress(a.to_string()))
1340}
1341
1342fn get_usize(t: &Trace, key: &str, site: &str) -> Result<usize, GenomeError> {
1343    let a = addr!(key, site);
1344    t.get_usize(&a)
1345        .ok_or_else(|| GenomeError::MissingAddress(a.to_string()))
1346}
1347
1348fn get_bool(t: &Trace, key: &str, site: &str) -> Result<bool, GenomeError> {
1349    let a = addr!(key, site);
1350    t.get_bool(&a)
1351        .ok_or_else(|| GenomeError::MissingAddress(a.to_string()))
1352}
1353
1354/// A site added *after* traces were already being persisted: absent means the
1355/// value the palette-v1 engine behaved as if it had, not a corrupt genome.
1356///
1357/// Old traces are the user's taste history and the bank they saved; the only
1358/// two ways to treat a missing site are "default it" and "throw the session
1359/// away", so every v2 site on a v1 variant reads through here.
1360fn get_f64_or(t: &Trace, key: &str, site: &str, default: f64) -> f64 {
1361    t.get_f64(&addr!(key, site)).unwrap_or(default)
1362}
1363
1364fn decode_mod(t: &Trace, key: &str) -> Result<ModNode, GenomeError> {
1365    match get_usize(t, key, "mod")? {
1366        0 => Ok(ModNode::None),
1367        1 => Ok(ModNode::Lfo {
1368            uid: Uid::NEW,
1369            wave: Waveform::from_index(get_usize(t, key, "wave")?),
1370            rate: get_f64(t, key, "rate")?,
1371        }),
1372        2 => Ok(ModNode::Env {
1373            uid: Uid::NEW,
1374            attack: get_f64(t, key, "att")?,
1375            decay: get_f64(t, key, "dec")?,
1376        }),
1377        3 => Ok(ModNode::Rand {
1378            uid: Uid::NEW,
1379            rate: get_f64(t, key, "rate")?,
1380            // v1 S&H had no slew: hard steps.
1381            glide: get_f64_or(t, key, "glide", 0.0),
1382        }),
1383        4 => Ok(ModNode::Follow {
1384            uid: Uid::NEW,
1385            sens: get_f64(t, key, "sens")?,
1386            release: get_f64(t, key, "rel")?,
1387        }),
1388        5 => Ok(ModNode::Euclid {
1389            uid: Uid::NEW,
1390            rate: get_f64(t, key, "erate")?,
1391            steps: get_f64(t, key, "esteps")?,
1392            pulses: get_f64(t, key, "epulses")?,
1393        }),
1394        6 => {
1395            let kind = ModOp::from_index(get_usize(t, key, "modop")?);
1396            let sites = kind.param_sites();
1397            Ok(ModNode::Op {
1398                uid: Uid::NEW,
1399                kind,
1400                p0: get_f64(t, key, sites[0])?,
1401                // The one-parameter ops do not write `p1` at all, so there is
1402                // nothing to read back — see `ModOp::param_sites`.
1403                p1: match sites.get(1) {
1404                    Some(site) => get_f64(t, key, site)?,
1405                    None => 0.0,
1406                },
1407                input: Box::new(decode_mod(t, &child_key(key, 0))?),
1408            })
1409        }
1410        7 => Ok(ModNode::Pair {
1411            uid: Uid::NEW,
1412            kind: PairOp::from_index(get_usize(t, key, "pairop")?),
1413            a: Box::new(decode_mod(t, &child_key(key, 0))?),
1414            b: Box::new(decode_mod(t, &child_key(key, 1))?),
1415        }),
1416        k => Err(GenomeError::InvalidStructure(format!(
1417            "mod kind {k} out of range at {key}"
1418        ))),
1419    }
1420}
1421
1422/// Decode a modulation slot that did not exist when the trace was written
1423/// (`Delay`, `Chorus`, `Reverb` from the v2 palette; `Vco` and `Supersaw` from
1424/// wave 2A's pitch slot): a trace with no `#mod` site at all decodes to an
1425/// empty slot, which is exactly how those modules used to sound.
1426fn decode_new_mod(t: &Trace, key: &str) -> Result<ModNode, GenomeError> {
1427    if t.get_usize(&addr!(key, "mod")).is_none() {
1428        return Ok(ModNode::None);
1429    }
1430    decode_mod(t, key)
1431}
1432
1433/// The mod-depth a v2 module gets when its trace predates the slot. Matches
1434/// `mutate::default_node`, so a migrated patch and a hand-placed one start
1435/// from the same knob.
1436const DEFAULT_MOD_DEPTH: f64 = 0.3;
1437
1438fn decode_node(t: &Trace, key: &str) -> Result<AudioNode, GenomeError> {
1439    if get_bool(t, key, "leaf")? {
1440        match get_usize(t, key, "src")? {
1441            // The pitch-modulation sites postdate every trace written before
1442            // wave 2A, and a vco is in nearly all of them — so these two read
1443            // through the defaulting accessors, exactly as `Delay` does.
1444            0 => Ok(AudioNode::Vco {
1445                uid: Uid::NEW,
1446                wave: Waveform::from_index(get_usize(t, key, "wave")?),
1447                octave: get_usize(t, key, "oct")? as i8 - 2,
1448                detune: get_f64(t, key, "det")?,
1449                mod_depth: get_f64_or(t, key, "mdepth", DEFAULT_MOD_DEPTH),
1450                modulation: decode_new_mod(t, &mod_key(key))?,
1451            }),
1452            1 => Ok(AudioNode::Supersaw {
1453                uid: Uid::NEW,
1454                octave: get_usize(t, key, "oct")? as i8 - 2,
1455                detune: get_f64(t, key, "det")?,
1456                mix: get_f64(t, key, "smix")?,
1457                mod_depth: get_f64_or(t, key, "mdepth", DEFAULT_MOD_DEPTH),
1458                modulation: decode_new_mod(t, &mod_key(key))?,
1459            }),
1460            2 => Ok(AudioNode::Noise {
1461                uid: Uid::NEW,
1462                color: NoiseColor::from_index(get_usize(t, key, "color")?),
1463            }),
1464            3 => Ok(AudioNode::Wavetable {
1465                uid: Uid::NEW,
1466                table: TableShape::from_index(get_usize(t, key, "table")?),
1467                octave: get_usize(t, key, "oct")? as i8 - 2,
1468                morph: get_f64(t, key, "morph")?,
1469                mod_depth: get_f64(t, key, "mdepth")?,
1470                modulation: decode_new_mod(t, &mod_key(key))?,
1471            }),
1472            4 => Ok(AudioNode::Pluck {
1473                uid: Uid::NEW,
1474                octave: get_usize(t, key, "oct")? as i8 - 2,
1475                damping: get_f64(t, key, "damp")?,
1476                brightness: get_f64(t, key, "bright")?,
1477                mod_depth: get_f64(t, key, "mdepth")?,
1478                modulation: decode_new_mod(t, &mod_key(key))?,
1479            }),
1480            5 => Ok(AudioNode::Formant {
1481                uid: Uid::NEW,
1482                vowel: get_f64(t, key, "vowel")?,
1483                shift: get_f64(t, key, "fshift")?,
1484                octave: get_usize(t, key, "oct")? as i8 - 2,
1485                mod_depth: get_f64(t, key, "mdepth")?,
1486                modulation: decode_mod(t, &mod_key(key))?,
1487            }),
1488            6 => Ok(AudioNode::Silence { uid: Uid::NEW }),
1489            k => Err(GenomeError::InvalidStructure(format!(
1490                "source kind {k} out of range at {key}"
1491            ))),
1492        }
1493    } else {
1494        match get_usize(t, key, "op")? {
1495            0 => Ok(AudioNode::Mix {
1496                uid: Uid::NEW,
1497                balance: get_f64(t, key, "bal")?,
1498                a: Box::new(decode_node(t, &child_key(key, 0))?),
1499                b: Box::new(decode_node(t, &child_key(key, 1))?),
1500            }),
1501            1 => Ok(AudioNode::Filter {
1502                uid: Uid::NEW,
1503                kind: FilterKind::from_index(get_usize(t, key, "fkind")?),
1504                cutoff: get_f64(t, key, "cut")?,
1505                resonance: get_f64(t, key, "res")?,
1506                mod_depth: get_f64(t, key, "mdepth")?,
1507                modulation: decode_mod(t, &mod_key(key))?,
1508                input: Box::new(decode_node(t, &child_key(key, 0))?),
1509            }),
1510            2 => Ok(AudioNode::Fold {
1511                uid: Uid::NEW,
1512                threshold: get_f64(t, key, "thresh")?,
1513                mod_depth: get_f64(t, key, "mdepth")?,
1514                modulation: decode_mod(t, &mod_key(key))?,
1515                input: Box::new(decode_node(t, &child_key(key, 0))?),
1516            }),
1517            3 => Ok(AudioNode::Delay {
1518                uid: Uid::NEW,
1519                time: get_f64(t, key, "time")?,
1520                feedback: get_f64(t, key, "fb")?,
1521                mix: get_f64(t, key, "dmix")?,
1522                mod_depth: get_f64_or(t, key, "mdepth", DEFAULT_MOD_DEPTH),
1523                modulation: decode_new_mod(t, &mod_key(key))?,
1524                input: Box::new(decode_node(t, &child_key(key, 0))?),
1525            }),
1526            4 => Ok(AudioNode::Chorus {
1527                uid: Uid::NEW,
1528                rate: get_f64(t, key, "crate")?,
1529                depth: get_f64(t, key, "cdepth")?,
1530                mix: get_f64(t, key, "cmix")?,
1531                mod_depth: get_f64_or(t, key, "mdepth", DEFAULT_MOD_DEPTH),
1532                modulation: decode_new_mod(t, &mod_key(key))?,
1533                input: Box::new(decode_node(t, &child_key(key, 0))?),
1534            }),
1535            5 => Ok(AudioNode::Reverb {
1536                uid: Uid::NEW,
1537                size: get_f64(t, key, "rsize")?,
1538                damp: get_f64(t, key, "rdamp")?,
1539                mix: get_f64(t, key, "rmix")?,
1540                mod_depth: get_f64_or(t, key, "mdepth", DEFAULT_MOD_DEPTH),
1541                modulation: decode_new_mod(t, &mod_key(key))?,
1542                input: Box::new(decode_node(t, &child_key(key, 0))?),
1543            }),
1544            6 => Ok(AudioNode::Distortion {
1545                uid: Uid::NEW,
1546                drive: get_f64(t, key, "drive")?,
1547                tone: get_f64(t, key, "tone")?,
1548                mode: DriveMode::from_index(get_usize(t, key, "dmode")?),
1549                mod_depth: get_f64(t, key, "mdepth")?,
1550                modulation: decode_mod(t, &mod_key(key))?,
1551                input: Box::new(decode_node(t, &child_key(key, 0))?),
1552            }),
1553            7 => Ok(AudioNode::Bitcrush {
1554                uid: Uid::NEW,
1555                bits: get_f64(t, key, "bits")?,
1556                downsample: get_f64(t, key, "dsamp")?,
1557                mod_depth: get_f64(t, key, "mdepth")?,
1558                modulation: decode_mod(t, &mod_key(key))?,
1559                input: Box::new(decode_node(t, &child_key(key, 0))?),
1560            }),
1561            8 => Ok(AudioNode::Phaser {
1562                uid: Uid::NEW,
1563                rate: get_f64(t, key, "prate")?,
1564                depth: get_f64(t, key, "pdepth")?,
1565                feedback: get_f64(t, key, "pfb")?,
1566                mod_depth: get_f64(t, key, "mdepth")?,
1567                modulation: decode_mod(t, &mod_key(key))?,
1568                input: Box::new(decode_node(t, &child_key(key, 0))?),
1569            }),
1570            9 => Ok(AudioNode::RingMod {
1571                uid: Uid::NEW,
1572                mix: get_f64(t, key, "rgmix")?,
1573                a: Box::new(decode_node(t, &child_key(key, 0))?),
1574                b: Box::new(decode_node(t, &child_key(key, 1))?),
1575            }),
1576            10 => Ok(AudioNode::Flanger {
1577                uid: Uid::NEW,
1578                rate: get_f64(t, key, "frate")?,
1579                depth: get_f64(t, key, "fdepth")?,
1580                feedback: get_f64(t, key, "ffb")?,
1581                mod_depth: get_f64(t, key, "mdepth")?,
1582                modulation: decode_mod(t, &mod_key(key))?,
1583                input: Box::new(decode_node(t, &child_key(key, 0))?),
1584            }),
1585            11 => Ok(AudioNode::Tremolo {
1586                uid: Uid::NEW,
1587                rate: get_f64(t, key, "trate")?,
1588                depth: get_f64(t, key, "tdepth")?,
1589                shape: get_f64(t, key, "tshape")?,
1590                mod_depth: get_f64(t, key, "mdepth")?,
1591                modulation: decode_mod(t, &mod_key(key))?,
1592                input: Box::new(decode_node(t, &child_key(key, 0))?),
1593            }),
1594            12 => Ok(AudioNode::Vibrato {
1595                uid: Uid::NEW,
1596                rate: get_f64(t, key, "vrate")?,
1597                depth: get_f64(t, key, "vdepth")?,
1598                mix: get_f64(t, key, "vmix")?,
1599                mod_depth: get_f64(t, key, "mdepth")?,
1600                modulation: decode_mod(t, &mod_key(key))?,
1601                input: Box::new(decode_node(t, &child_key(key, 0))?),
1602            }),
1603            13 => Ok(AudioNode::Eq {
1604                uid: Uid::NEW,
1605                low: get_f64(t, key, "low")?,
1606                mid: get_f64(t, key, "mid")?,
1607                high: get_f64(t, key, "high")?,
1608                mod_depth: get_f64(t, key, "mdepth")?,
1609                modulation: decode_mod(t, &mod_key(key))?,
1610                input: Box::new(decode_node(t, &child_key(key, 0))?),
1611            }),
1612            14 => Ok(AudioNode::Granular {
1613                uid: Uid::NEW,
1614                position: get_f64(t, key, "gpos")?,
1615                size: get_f64(t, key, "gsize")?,
1616                density: get_f64(t, key, "gdens")?,
1617                mod_depth: get_f64(t, key, "mdepth")?,
1618                modulation: decode_mod(t, &mod_key(key))?,
1619                input: Box::new(decode_node(t, &child_key(key, 0))?),
1620            }),
1621            15 => Ok(AudioNode::Shift {
1622                uid: Uid::NEW,
1623                semis: get_f64(t, key, "semis")?,
1624                window: get_f64(t, key, "window")?,
1625                mix: get_f64(t, key, "smix")?,
1626                mod_depth: get_f64(t, key, "mdepth")?,
1627                modulation: decode_mod(t, &mod_key(key))?,
1628                input: Box::new(decode_node(t, &child_key(key, 0))?),
1629            }),
1630            16 => Ok(AudioNode::Comp {
1631                uid: Uid::NEW,
1632                threshold: get_f64(t, key, "thresh")?,
1633                ratio: get_f64(t, key, "ratio")?,
1634                makeup: get_f64(t, key, "makeup")?,
1635                mod_depth: get_f64(t, key, "mdepth")?,
1636                modulation: decode_mod(t, &mod_key(key))?,
1637                input: Box::new(decode_node(t, &child_key(key, 0))?),
1638                sidechain: Box::new(decode_node(t, &child_key(key, 1))?),
1639            }),
1640            17 => Ok(AudioNode::Duck {
1641                uid: Uid::NEW,
1642                amount: get_f64(t, key, "amount")?,
1643                threshold: get_f64(t, key, "dthresh")?,
1644                release: get_f64(t, key, "drel")?,
1645                mod_depth: get_f64(t, key, "mdepth")?,
1646                modulation: decode_mod(t, &mod_key(key))?,
1647                input: Box::new(decode_node(t, &child_key(key, 0))?),
1648                key: Box::new(decode_node(t, &child_key(key, 1))?),
1649            }),
1650            18 => Ok(AudioNode::Gate {
1651                uid: Uid::NEW,
1652                threshold: get_f64(t, key, "gthresh")?,
1653                range: get_f64(t, key, "range")?,
1654                release: get_f64(t, key, "grel")?,
1655                mod_depth: get_f64(t, key, "mdepth")?,
1656                modulation: decode_mod(t, &mod_key(key))?,
1657                input: Box::new(decode_node(t, &child_key(key, 0))?),
1658                sidechain: Box::new(decode_node(t, &child_key(key, 1))?),
1659            }),
1660            19 => Ok(AudioNode::Vocoder {
1661                uid: Uid::NEW,
1662                bands: get_f64(t, key, "bands")?,
1663                attack: get_f64(t, key, "vatt")?,
1664                release: get_f64(t, key, "vrel")?,
1665                mod_depth: get_f64(t, key, "mdepth")?,
1666                modulation: decode_mod(t, &mod_key(key))?,
1667                carrier: Box::new(decode_node(t, &child_key(key, 0))?),
1668                modulator: Box::new(decode_node(t, &child_key(key, 1))?),
1669            }),
1670            k => Err(GenomeError::InvalidStructure(format!(
1671                "op kind {k} out of range at {key}"
1672            ))),
1673        }
1674    }
1675}
1676
1677impl TraceGenome for PatchTree {
1678    fn to_trace(&self) -> Trace {
1679        let mut t = Trace::default();
1680        put_f64(&mut t, "amp", "attack", self.amp.attack);
1681        put_f64(&mut t, "amp", "decay", self.amp.decay);
1682        put_f64(&mut t, "amp", "sustain", self.amp.sustain);
1683        put_f64(&mut t, "amp", "release", self.amp.release);
1684        encode_node(&self.root, "node", &mut t);
1685        t
1686    }
1687
1688    fn from_trace(trace: &Trace) -> Result<Self, GenomeError> {
1689        Ok(PatchTree {
1690            amp: AmpEnv {
1691                attack: get_f64(trace, "amp", "attack")?,
1692                decay: get_f64(trace, "amp", "decay")?,
1693                sustain: get_f64(trace, "amp", "sustain")?,
1694                release: get_f64(trace, "amp", "release")?,
1695            },
1696            root: decode_node(trace, "node")?,
1697        })
1698    }
1699
1700    fn trace_prefix() -> &'static str {
1701        "node"
1702    }
1703}
1704
1705// ---------------------------------------------------------------------------
1706// Parameter domains
1707// ---------------------------------------------------------------------------
1708
1709/// The declared range of **every** continuous site in this grammar.
1710///
1711/// Not a convention: [`crate::prior`] samples every one of them from `u01()`,
1712/// so a value outside this interval has zero prior mass by construction and is
1713/// a corruption rather than an unusual patch. Stated once, here, so the check
1714/// and the repair below cannot drift from the generative model — and so that
1715/// the day a site wants a different range, this is the line that has to change.
1716pub const PARAM_DOMAIN: std::ops::RangeInclusive<f64> = 0.0..=1.0;
1717
1718/// Is `v` a legal value for a continuous site?
1719///
1720/// Non-finite fails: `NaN` compares false against every bound, and an infinity
1721/// is exactly the runaway this gate exists to stop.
1722pub fn in_domain(v: f64) -> bool {
1723    v.is_finite() && PARAM_DOMAIN.contains(&v)
1724}
1725
1726impl PatchTree {
1727    /// Every continuous site of this term that sits outside [`PARAM_DOMAIN`],
1728    /// as `(trace address, value)`, in address order.
1729    ///
1730    /// Reads the **trace**, not the term, and that is the whole point: the
1731    /// trace enumerates exactly the continuous sites, by construction, from the
1732    /// same walk the prior samples. A hand-written match over 26 productions
1733    /// would be a second table of "which fields are knobs" — and the first
1734    /// module somebody forgot to add to it would be the one the next sentinel
1735    /// escaped through.
1736    pub fn domain_violations(&self) -> Vec<(String, f64)> {
1737        let mut out: Vec<(String, f64)> = self
1738            .to_trace()
1739            .choices
1740            .iter()
1741            .filter_map(|(a, c)| match c.value {
1742                ChoiceValue::F64(v) if !in_domain(v) => Some((a.to_string(), v)),
1743                _ => None,
1744            })
1745            .collect();
1746        out.sort_by(|a, b| a.0.cmp(&b.0));
1747        out
1748    }
1749
1750    /// Pull every out-of-domain continuous site back into [`PARAM_DOMAIN`].
1751    /// Returns how many sites were repaired (0 = the term was already clean,
1752    /// and nothing was rebuilt).
1753    ///
1754    /// **Repair, not refusal, and the asymmetry is deliberate.** A term over
1755    /// the size/depth ceilings cannot be fixed without deciding which modules
1756    /// to delete, so those are refused. A knob outside its range *can* be
1757    /// fixed, exactly and locally, and the alternative — refusing — would mean
1758    /// a saved session that already contains one becomes an app the player
1759    /// cannot edit, load or evolve their way out of. Corruption must not be
1760    /// load-bearing.
1761    ///
1762    /// `NaN` clamps to the middle of the range rather than to an end: it
1763    /// carries no information about which way it went, and pinning it to a
1764    /// boundary would state one.
1765    ///
1766    /// Identities survive. The rebuild goes through the trace, which does not
1767    /// carry `uid`s, so the repaired term inherits them back from the term it
1768    /// replaced — same rule, and the same reason, as [`crate::set_param`].
1769    pub fn clamp_domains(&mut self) -> usize {
1770        let mut trace = self.to_trace();
1771        let mut fixed = 0usize;
1772        for c in trace.choices.values_mut() {
1773            if let ChoiceValue::F64(v) = c.value {
1774                if !in_domain(v) {
1775                    let repaired = if v.is_nan() {
1776                        (PARAM_DOMAIN.start() + PARAM_DOMAIN.end()) / 2.0
1777                    } else {
1778                        v.clamp(*PARAM_DOMAIN.start(), *PARAM_DOMAIN.end())
1779                    };
1780                    c.value = ChoiceValue::F64(repaired);
1781                    fixed += 1;
1782                }
1783            }
1784        }
1785        if fixed == 0 {
1786            return 0;
1787        }
1788        // Decoding can only fail on a *structurally* broken trace, and this one
1789        // came from `to_trace` on a live term with nothing but leaf values
1790        // touched. If it somehow does, keep the term we have: a patch with a
1791        // bad knob is worth more to the player than no patch at all, and every
1792        // consumer downstream of here has its own guard.
1793        if let Ok(mut repaired) = PatchTree::from_trace(&trace) {
1794            repaired.inherit_uids(self);
1795            *self = repaired;
1796        }
1797        fixed
1798    }
1799}
1800
1801#[cfg(test)]
1802mod tests {
1803    use super::*;
1804    use crate::term::FilterKind;
1805
1806    /// A trace written by the v1 palette still decodes.
1807    ///
1808    /// Saved sessions, bank entries and the whole observation log are stored
1809    /// as traces, so a site added to an *existing* variant is a wire-format
1810    /// change: `Delay` gained `#mdepth` and a `/m` slot, and `Rand` gained
1811    /// `#glide`, none of which appear in a trace written last week. The
1812    /// defaults are chosen so the decoded patch still *sounds* like the one
1813    /// that was saved — an empty slot and a mod depth that modulates nothing.
1814    #[test]
1815    fn a_v1_trace_still_decodes() {
1816        let mut t = Trace::default();
1817        for (site, v) in [
1818            ("attack", 0.1),
1819            ("decay", 0.3),
1820            ("sustain", 0.6),
1821            ("release", 0.2),
1822        ] {
1823            put_f64(&mut t, "amp", site, v);
1824        }
1825        // node = Delay { time, fb, dmix } — no #mdepth, no /m slot at all.
1826        put_bool(&mut t, "node", "leaf", false);
1827        put_usize(&mut t, "node", "op", 3);
1828        put_f64(&mut t, "node", "time", 0.6);
1829        put_f64(&mut t, "node", "fb", 0.4);
1830        put_f64(&mut t, "node", "dmix", 0.35);
1831        // node/0 = Filter modulated by a v1 Rand — rate but no glide.
1832        put_bool(&mut t, "node/0", "leaf", false);
1833        put_usize(&mut t, "node/0", "op", 1);
1834        put_usize(&mut t, "node/0", "fkind", 3);
1835        put_f64(&mut t, "node/0", "cut", 0.5);
1836        put_f64(&mut t, "node/0", "res", 0.4);
1837        put_f64(&mut t, "node/0", "mdepth", 0.5);
1838        put_usize(&mut t, "node/0/m", "mod", 3);
1839        put_f64(&mut t, "node/0/m", "rate", 0.62);
1840        // node/0/0 = Vco.
1841        put_bool(&mut t, "node/0/0", "leaf", true);
1842        put_usize(&mut t, "node/0/0", "src", 0);
1843        put_usize(&mut t, "node/0/0", "wave", 2);
1844        put_usize(&mut t, "node/0/0", "oct", 1);
1845        put_f64(&mut t, "node/0/0", "det", 0.5);
1846
1847        let tree = PatchTree::from_trace(&t).expect("a v1 trace must still load");
1848        let AudioNode::Delay {
1849            mod_depth,
1850            modulation,
1851            input,
1852            ..
1853        } = &tree.root
1854        else {
1855            panic!("decoded the wrong node: {}", tree.root.to_sexpr());
1856        };
1857        assert_eq!(*mod_depth, 0.3, "new mod depth did not default");
1858        assert_eq!(*modulation, ModNode::None, "absent slot must decode empty");
1859        let AudioNode::Filter {
1860            kind, modulation, ..
1861        } = &**input
1862        else {
1863            panic!("decoded the wrong child: {}", input.to_sexpr());
1864        };
1865        assert_eq!(*kind, FilterKind::Ladder);
1866        assert_eq!(
1867            *modulation,
1868            ModNode::Rand {
1869                uid: Uid::NEW,
1870                rate: 0.62,
1871                glide: 0.0
1872            },
1873            "a v1 S&H must come back as hard steps"
1874        );
1875        // The vco at the bottom is the one that matters most: wave 2A gave it
1876        // a pitch slot, and a vco is in nearly every trace ever written. An
1877        // absent `#mdepth`/`/m` must decode to "no pitch modulation", not to
1878        // a missing-address error that fails the whole genome.
1879        let AudioNode::Filter { input, .. } = &**input else {
1880            unreachable!("checked above")
1881        };
1882        assert_eq!(
1883            **input,
1884            AudioNode::Vco {
1885                uid: Uid::NEW,
1886                wave: Waveform::Saw,
1887                octave: -1,
1888                detune: 0.5,
1889                mod_depth: DEFAULT_MOD_DEPTH,
1890                modulation: ModNode::None,
1891            },
1892            "a v1 vco must decode with its pitch slot empty"
1893        );
1894
1895        // ...and once loaded it is a v2 genome like any other: re-encoding it
1896        // writes the new sites, and that trace round-trips.
1897        let back = PatchTree::from_trace(&tree.to_trace()).expect("re-encoded trace decodes");
1898        assert_eq!(back, tree);
1899    }
1900}
1901
1902#[cfg(test)]
1903mod domain_tests {
1904    use super::*;
1905    use crate::mutate::{apply_struct_op, validate_tree, StructOp};
1906    use crate::term::{FilterKind, Waveform};
1907    use rand::rngs::StdRng;
1908    use rand::SeedableRng;
1909
1910    fn filter_over_vco(cutoff: f64) -> PatchTree {
1911        PatchTree {
1912            amp: AmpEnv {
1913                attack: 0.1,
1914                decay: 0.3,
1915                sustain: 0.6,
1916                release: 0.2,
1917            },
1918            root: AudioNode::Filter {
1919                uid: Uid(7),
1920                kind: FilterKind::SvfBp,
1921                cutoff,
1922                resonance: 0.4,
1923                mod_depth: 0.5,
1924                input: Box::new(AudioNode::Vco {
1925                    uid: Uid(9),
1926                    wave: Waveform::Saw,
1927                    octave: 0,
1928                    detune: 0.5,
1929                    mod_depth: 0.3,
1930                    modulation: ModNode::None,
1931                }),
1932                modulation: ModNode::None,
1933            },
1934        }
1935    }
1936
1937    /// The generative model's own claim, checked rather than trusted: every
1938    /// continuous site the prior can draw lands inside [`PARAM_DOMAIN`]. If
1939    /// this ever fails, the domain constant is wrong and every gate built on
1940    /// it is refusing legitimate patches.
1941    #[test]
1942    fn every_prior_draw_is_in_domain() {
1943        let prior = PatchGrammarPrior::default();
1944        let mut rng = StdRng::seed_from_u64(20260802);
1945        for _ in 0..400 {
1946            let t = prior.sample_with_rng(&mut rng);
1947            assert!(
1948                t.domain_violations().is_empty(),
1949                "the prior drew an out-of-domain site: {:?}",
1950                t.domain_violations()
1951            );
1952        }
1953    }
1954
1955    /// The sentinel, exactly as it was found in the shipped session: four
1956    /// sites of one patch at `1e30`. Repair moves all four and nothing else,
1957    /// and every node keeps the identity it had — locks and hand-placed
1958    /// positions ride on `uid`, so a repair that reissued them would fix a
1959    /// number by destroying the player's arrangement.
1960    #[test]
1961    fn clamp_repairs_the_sentinel_and_keeps_identity() {
1962        let mut t = filter_over_vco(1e30);
1963        t.amp.sustain = 1e30;
1964        assert_eq!(t.domain_violations().len(), 2);
1965
1966        assert_eq!(t.clamp_domains(), 2);
1967        assert!(t.domain_violations().is_empty());
1968        assert_eq!(t.amp.sustain, 1.0);
1969        assert_eq!(t.amp.attack, 0.1, "a clean site must not move");
1970        let AudioNode::Filter {
1971            uid,
1972            cutoff,
1973            resonance,
1974            input,
1975            ..
1976        } = &t.root
1977        else {
1978            panic!("the repair changed the term's shape");
1979        };
1980        assert_eq!(*cutoff, 1.0);
1981        assert_eq!(*resonance, 0.4);
1982        assert_eq!(*uid, Uid(7), "the repair reissued an identity");
1983        let AudioNode::Vco { uid, .. } = &**input else {
1984            panic!("the repair changed the child");
1985        };
1986        assert_eq!(*uid, Uid(9));
1987
1988        // Idempotent, and free on a clean term.
1989        assert_eq!(t.clamp_domains(), 0);
1990    }
1991
1992    /// NaN carries no direction, so it lands in the middle rather than being
1993    /// pinned to an end that would state one.
1994    #[test]
1995    fn nan_lands_mid_range() {
1996        let mut t = filter_over_vco(f64::NAN);
1997        assert_eq!(t.clamp_domains(), 1);
1998        let AudioNode::Filter { cutoff, .. } = &t.root else {
1999            unreachable!()
2000        };
2001        assert_eq!(*cutoff, 0.5);
2002    }
2003
2004    /// `validate_tree` is the predicate and names the site — the WS-1 rider
2005    /// used to speak only about size and depth, which is why a value could
2006    /// walk through it.
2007    #[test]
2008    fn validate_tree_refuses_an_out_of_domain_site() {
2009        assert!(validate_tree(&filter_over_vco(0.6)).is_ok());
2010        let err = validate_tree(&filter_over_vco(1e30)).expect_err("must refuse");
2011        assert!(
2012            err.contains("node#cut"),
2013            "the reason must name the site: {err}"
2014        );
2015        assert!(err.contains("out of range"), "{err}");
2016    }
2017
2018    /// The route the corruption actually travelled: an explicit fragment
2019    /// handed to `apply_struct_op` (a HELD subtree, a bank drop) is adopted
2020    /// verbatim, so `finish()` has to be the funnel that cleans it.
2021    #[test]
2022    fn an_explicit_fragment_cannot_seat_a_bad_value() {
2023        let host = filter_over_vco(0.6);
2024        let bad = AudioNode::Fold {
2025            uid: Uid::NEW,
2026            threshold: 1e30,
2027            mod_depth: 0.3,
2028            input: Box::new(AudioNode::Noise {
2029                uid: Uid::NEW,
2030                color: crate::term::NoiseColor::White,
2031            }),
2032            modulation: ModNode::None,
2033        };
2034        let out = apply_struct_op(
2035            &host,
2036            &StructOp::InsertTree {
2037                key: "node/0".into(),
2038                node: bad,
2039            },
2040        )
2041        .expect("a repairable fragment must still land");
2042        assert!(
2043            out.domain_violations().is_empty(),
2044            "finish() seated {:?}",
2045            out.domain_violations()
2046        );
2047    }
2048
2049    /// `Silence` survives a codec round trip, and it is source index **6**.
2050    ///
2051    /// The index is asserted as a literal, not read back from the encoder,
2052    /// because it is a wire format: a saved trace stores the number, so if
2053    /// this ever changes every persisted genome silently re-points at a
2054    /// different oscillator. A test that asked the encoder what it wrote would
2055    /// agree with any renumbering and catch nothing.
2056    #[test]
2057    fn silence_round_trips_at_source_index_six() {
2058        let tree = PatchTree {
2059            amp: crate::term::AmpEnv {
2060                attack: 0.1,
2061                decay: 0.3,
2062                sustain: 0.6,
2063                release: 0.2,
2064            },
2065            root: AudioNode::Mix {
2066                uid: Uid::NEW,
2067                balance: 0.5,
2068                a: Box::new(AudioNode::Silence { uid: Uid::NEW }),
2069                b: Box::new(AudioNode::Noise {
2070                    uid: Uid::NEW,
2071                    color: crate::term::NoiseColor::White,
2072                }),
2073            },
2074        };
2075
2076        let t = tree.to_trace();
2077        assert_eq!(
2078            get_usize(&t, "node/0", "src").expect("a source index"),
2079            6,
2080            "Silence must stay source index 6 — the index is the wire format"
2081        );
2082        // Two sites and no more: a hole has nothing to set.
2083        assert_eq!(
2084            t.choices.keys().filter(|k| k.starts_with("node/0")).count(),
2085            2,
2086            "Silence should write only #leaf and #src"
2087        );
2088
2089        let back = PatchTree::from_trace(&t).expect("round trips");
2090        assert_eq!(back.to_sexpr(), tree.to_sexpr());
2091    }
2092}