dirtydata_runtime/
offline.rs1use crate::nodes::ProcessContext;
2use crate::DspRunner;
3use dirtydata_core::ir::Graph;
4use dirtydata_observer::divergence::{DivergenceMap, DivergencePoint};
5
6pub struct OfflineRenderer {
8 runner: DspRunner,
9 sample_rate: f32,
10}
11
12impl OfflineRenderer {
13 pub fn new(graph: Graph, sample_rate: f32) -> Self {
14 let runner = DspRunner::new(graph, None, sample_rate);
16 Self { runner, sample_rate }
17 }
18
19 pub fn render(&mut self, duration_secs: f32) -> Vec<f32> {
22 let num_samples = (duration_secs * self.sample_rate) as usize;
23 let mut output = Vec::with_capacity(num_samples * 2);
24
25 for i in 0..num_samples {
26 let ctx = ProcessContext {
27 sample_rate: self.sample_rate,
28 global_sample_index: i as u64,
29 crash_flag: None,
30 osc_tx: None,
31 convergence_info: None,
32 node_diagnostics: None,
33 node_id: None,
34 };
35 let sample = self.runner.process_sample(&ctx);
36 output.push(sample[0]);
37 output.push(sample[1]);
38 }
39 output
40 }
41
42 pub fn verify_determinism(graph: Graph, duration_secs: f32, sample_rate: f32) -> Result<bool, String> {
44 let mut r1 = Self::new(graph.clone(), sample_rate);
45 let mut r2 = Self::new(graph, sample_rate);
46
47 let out1 = r1.render(duration_secs);
48 let out2 = r2.render(duration_secs);
49
50 if out1.len() != out2.len() {
51 return Err("Output length mismatch between identical runs".into());
52 }
53
54 for (s1, s2) in out1.iter().zip(out2.iter()) {
55 if (*s1 - *s2).abs() > 0.0 {
56 return Ok(false);
57 }
58 }
59 Ok(true)
60 }
61
62 pub fn compare(
65 graph_a: Graph,
66 graph_b: Graph,
67 duration_secs: f32,
68 sample_rate: f32,
69 ) -> DivergenceMap {
70 let mut r_a = DspRunner::new(graph_a, None, sample_rate);
71 let mut r_b = DspRunner::new(graph_b, None, sample_rate);
72
73 let num_samples = (duration_secs * sample_rate) as usize;
74 let mut map = DivergenceMap::new();
75
76 for i in 0..num_samples {
77 let ctx = ProcessContext {
78 sample_rate,
79 global_sample_index: i as u64,
80 crash_flag: None,
81 osc_tx: None,
82 convergence_info: None,
83 node_diagnostics: None,
84 node_id: None,
85 };
86
87 r_a.process_sample(&ctx);
88 r_b.process_sample(&ctx);
89
90 let ids: Vec<_> = r_a.get_graph().nodes.keys().cloned().collect();
92 for id in ids {
93 if let (Some(out_a), Some(out_b)) = (r_a.get_node_outputs(&id), r_b.get_node_outputs(&id)) {
94 for (p_idx, (v_a, v_b)) in out_a.iter().zip(out_b.iter()).enumerate() {
95 let diff_l = (v_a[0] - v_b[0]).abs();
96 let diff_r = (v_a[1] - v_b[1]).abs();
97 let mag = diff_l.max(diff_r);
98
99 if mag > 1e-7 { map.add_point(DivergencePoint {
101 sample_index: i as u64,
102 node_id: id,
103 node_name: "Unknown".into(), port_idx: p_idx,
105 expected_value: *v_a,
106 actual_value: *v_b,
107 diff_magnitude: mag,
108 });
109
110 if map.points.len() > 100 {
111 return map; }
113 }
114 }
115 }
116 }
117 }
118
119 map
120 }
121}