1use crate::{LfeChoice, SAMPLE_RATE};
4use secs_types::chain::{MimoPaths, SignalChain};
5use serde::Serialize;
6use sha2::{Digest, Sha256};
7
8#[derive(Serialize)]
9struct CanonicalDesign<'a> {
10 schema: u32,
11 sample_rate: u32,
12 chain: &'a SignalChain,
13 mimo: Option<&'a MimoPaths>,
14 lfe: Option<CanonicalLfe>,
15}
16
17#[derive(Serialize)]
18struct CanonicalLfe {
19 gain_db: f64,
20 lp_hz: f64,
21}
22
23pub fn design_id(
28 chain: &SignalChain,
29 mimo: Option<&MimoPaths>,
30 lfe: LfeChoice,
31) -> Result<String, serde_json::Error> {
32 let lfe = match lfe {
33 LfeChoice::Omit => None,
34 LfeChoice::Include { gain_db, lp_hz } => Some(CanonicalLfe { gain_db, lp_hz }),
35 };
36 let canonical = CanonicalDesign {
37 schema: 1,
38 sample_rate: SAMPLE_RATE,
39 chain,
40 mimo,
41 lfe,
42 };
43 let digest = Sha256::digest(serde_json::to_vec(&canonical)?);
44 Ok(digest.iter().map(|byte| format!("{byte:02x}")).collect())
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50 use secs_types::chain::{MainChannel, MimoFilter, MimoPath};
51
52 fn chain() -> SignalChain {
53 SignalChain {
54 input_hp_hz: None,
55 sample_rate: SAMPLE_RATE,
56 mains: vec![MainChannel {
57 role: "L".into(),
58 input_peq: vec![(80.0, 1.0, -2.0)],
59 fir: vec![1.0, 0.25],
60 gain_db: 0.0,
61 delay_ms: 1.0,
62 xo_hz: Some(80.0),
63 top_peq: Vec::new(),
64 align_delay_ms: 0.0,
65 }],
66 subs: Vec::new(),
67 sub_bus_peq: Vec::new(),
68 main_extra_ms: 0.0,
69 lift_ms: 0.0,
70 latency_ms: 1.0,
71 }
72 }
73
74 #[test]
75 fn design_id_is_stable_and_changes_with_taps_delays_and_mimo() {
76 let chain = chain();
77 let first = design_id(&chain, None, LfeChoice::DOLBY).unwrap();
78 assert_eq!(first, design_id(&chain, None, LfeChoice::DOLBY).unwrap());
79 assert_eq!(first.len(), 64);
80 assert!(
81 first
82 .bytes()
83 .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase())
84 );
85
86 let mut tap = chain.clone();
87 tap.mains[0].fir[1] += 1e-12;
88 assert_ne!(first, design_id(&tap, None, LfeChoice::DOLBY).unwrap());
89
90 let mut delay = chain.clone();
91 delay.mains[0].delay_ms += 1.0 / 48.0;
92 assert_ne!(first, design_id(&delay, None, LfeChoice::DOLBY).unwrap());
93
94 let mimo = MimoPaths {
95 fsiso: 120.0,
96 paths: vec![MimoPath {
97 input: "L".into(),
98 support: "L".into(),
99 filter: MimoFilter::Iir {
100 gain_db: -6.0,
101 delay_ms: 0.0,
102 polarity: 1,
103 peq: Vec::new(),
104 allpass: Vec::new(),
105 },
106 low_hz: 40.0,
107 delay_ms: 0.0,
108 }],
109 };
110 assert_ne!(
111 first,
112 design_id(&chain, Some(&mimo), LfeChoice::DOLBY).unwrap()
113 );
114 }
115}