1use fugue_evo::genome::trace_genome::{ChoiceValue, TraceGenome};
8use serde::{Deserialize, Serialize};
9
10use crate::edit::split_addr;
11use crate::term::PatchTree;
12
13#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
15pub struct DiffEntry {
16 pub addr: String,
18 pub before: Option<String>,
20 pub after: Option<String>,
22}
23
24fn display_value(site: &str, v: &ChoiceValue) -> String {
25 match v {
26 ChoiceValue::F64(x) => format!("{x:.2}"),
27 ChoiceValue::Bool(b) => if *b { "source" } else { "processor" }.into(),
28 ChoiceValue::Usize(i) => {
29 let name = |names: &[&str]| names.get(*i).map(|s| s.to_string());
30 match site {
31 "wave" => name(&["sin", "tri", "saw", "sqr"]),
32 "color" => name(&["white", "pink"]),
33 "fkind" => name(&["svf lp", "svf bp", "svf hp", "ladder"]),
34 "src" => name(&["vco", "supersaw", "noise", "wavetable", "pluck", "formant"]),
40 "op" => name(&[
41 "mix",
42 "filter",
43 "wavefolder",
44 "delay",
45 "chorus",
46 "reverb",
47 "distortion",
48 "bitcrush",
49 "phaser",
50 "ring mod",
51 "flanger",
52 "tremolo",
53 "vibrato",
54 "eq",
55 "granular",
56 ]),
57 "mod" => name(&["no mod", "lfo", "mod env", "s&h rand", "follower"]),
58 "table" => name(&[
59 "sine",
60 "tri",
61 "saw",
62 "square",
63 "pulse 25",
64 "pulse 12",
65 "formant a",
66 "formant o",
67 ]),
68 "dmode" => name(&["soft", "hard", "tube"]),
69 "oct" => Some(format!("{:+}", *i as i8 - 2)),
70 _ => None,
71 }
72 .unwrap_or_else(|| i.to_string())
73 }
74 other => format!("{other:?}"),
75 }
76}
77
78pub fn tree_diff(before: &PatchTree, after: &PatchTree) -> Vec<DiffEntry> {
83 let ta = before.to_trace();
84 let tb = after.to_trace();
85 let mut out = Vec::new();
86 for (addr, ca) in &ta.choices {
87 let site = split_addr(addr).1;
88 match tb.choices.get(addr) {
89 Some(cb) if cb.value == ca.value => {}
90 Some(cb) => out.push(DiffEntry {
91 addr: addr.to_string(),
92 before: Some(display_value(site, &ca.value)),
93 after: Some(display_value(site, &cb.value)),
94 }),
95 None => out.push(DiffEntry {
96 addr: addr.to_string(),
97 before: Some(display_value(site, &ca.value)),
98 after: None,
99 }),
100 }
101 }
102 for (addr, cb) in &tb.choices {
103 if !ta.choices.contains_key(addr) {
104 let site = split_addr(addr).1;
105 out.push(DiffEntry {
106 addr: addr.to_string(),
107 before: None,
108 after: Some(display_value(site, &cb.value)),
109 });
110 }
111 }
112 out.sort_by(|x, y| x.addr.cmp(&y.addr));
113 out
114}