1use crate::buffer::AudioBuffer;
5use crate::ctx::{Globals, NoProgress, OpCtx};
6use crate::op::{Args, OpSpec};
7use crate::registry::Registry;
8use crate::wav::SimpleIo;
9use std::f64::consts::PI;
10
11pub static SIMPLE_IO: SimpleIo = SimpleIo;
12pub static NO_PROGRESS: NoProgress = NoProgress;
13
14pub fn ctx() -> OpCtx<'static> {
16 OpCtx::new("test", vec![], Globals::default(), &SIMPLE_IO, &NO_PROGRESS)
17}
18
19pub fn ctx_with(registry: &'static Registry) -> OpCtx<'static> {
21 let mut c = ctx();
22 c.registry = Some(registry);
23 c
24}
25
26pub fn leak_registry(ops: Vec<OpSpec>) -> &'static Registry {
28 let mut r = Registry::new();
29 r.extend(ops);
30 Box::leak(Box::new(r))
31}
32
33pub fn args(spec: &OpSpec, pairs: &[(&str, &str)]) -> Args {
35 Args::build(spec, pairs.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(), &|_, _| None)
36 .expect("valid test args")
37}
38
39pub fn run(spec: &OpSpec, pairs: &[(&str, &str)], input: AudioBuffer) -> crate::error::Result<AudioBuffer> {
41 let a = args(spec, pairs);
42 let mut c = ctx();
43 crate::exec::apply_spec(spec, &a, input, &mut c)
44}
45
46pub fn run_ctx(
48 spec: &OpSpec,
49 pairs: &[(&str, &str)],
50 input: AudioBuffer,
51) -> crate::error::Result<(AudioBuffer, OpCtx<'static>)> {
52 let a = args(spec, pairs);
53 let mut c = ctx();
54 let out = crate::exec::apply_spec(spec, &a, input, &mut c)?;
55 Ok((out, c))
56}
57
58pub fn sine(sr: u32, freq: f64, secs: f64, amp: f32) -> AudioBuffer {
59 let n = (sr as f64 * secs).round() as usize;
60 AudioBuffer::from_mono(sr, (0..n).map(|i| amp * (2.0 * PI * freq * i as f64 / sr as f64).sin() as f32).collect())
61}
62
63pub fn stereo(b: AudioBuffer) -> AudioBuffer {
64 b.with_channels(2)
65}
66
67pub fn noise(sr: u32, secs: f64, amp: f32, seed: u64) -> AudioBuffer {
69 let n = (sr as f64 * secs).round() as usize;
70 let mut x = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407) | 1;
71 AudioBuffer::from_mono(
72 sr,
73 (0..n)
74 .map(|_| {
75 x ^= x << 13;
76 x ^= x >> 7;
77 x ^= x << 17;
78 amp * ((x >> 11) as f64 / (1u64 << 53) as f64 * 2.0 - 1.0) as f32
79 })
80 .collect(),
81 )
82}
83
84pub fn chirp(sr: u32, f0: f64, f1: f64, secs: f64, amp: f32) -> AudioBuffer {
86 let n = (sr as f64 * secs).round() as usize;
87 let k = (f1 - f0) / secs;
88 AudioBuffer::from_mono(
89 sr,
90 (0..n)
91 .map(|i| {
92 let t = i as f64 / sr as f64;
93 amp * (2.0 * PI * (f0 * t + 0.5 * k * t * t)).sin() as f32
94 })
95 .collect(),
96 )
97}
98
99pub fn clicks(sr: u32, period: f64, secs: f64, amp: f32) -> AudioBuffer {
101 let n = (sr as f64 * secs).round() as usize;
102 let step = (period * sr as f64).round() as usize;
103 let mut v = vec![0.0; n];
104 let mut i = 0;
105 while i < n {
106 v[i] = amp;
107 i += step.max(1);
108 }
109 AudioBuffer::from_mono(sr, v)
110}
111
112pub fn add(a: &AudioBuffer, b: &AudioBuffer) -> AudioBuffer {
114 let n = a.frames().max(b.frames());
115 let ch = a.channels().max(b.channels());
116 let a2 = a.with_channels(ch);
117 let b2 = b.with_channels(ch);
118 let data = (0..ch)
119 .map(|c| {
120 (0..n)
121 .map(|i| a2.data[c].get(i).copied().unwrap_or(0.0) + b2.data[c].get(i).copied().unwrap_or(0.0))
122 .collect()
123 })
124 .collect();
125 AudioBuffer { sample_rate: a.sample_rate, data }
126}
127
128pub fn rms_diff(a: &AudioBuffer, b: &AudioBuffer) -> f64 {
130 let n = a.frames().min(b.frames());
131 let ch = a.channels().min(b.channels());
132 let mut s = 0.0;
133 for c in 0..ch {
134 for i in 0..n {
135 let d = (a.data[c][i] - b.data[c][i]) as f64;
136 s += d * d;
137 }
138 }
139 (s / (n * ch).max(1) as f64).sqrt()
140}
141
142pub fn rms_db(x: &[f32]) -> f64 {
144 let s: f64 = x.iter().map(|v| (*v as f64).powi(2)).sum::<f64>() / x.len().max(1) as f64;
145 crate::buffer::lin_to_db(s.sqrt())
146}
147
148pub fn tone_level(x: &[f32], sr: u32, f: f64) -> f64 {
151 let n = x.len();
152 if n == 0 {
153 return 0.0;
154 }
155 let w = 2.0 * PI * f / sr as f64;
157 let coeff = 2.0 * w.cos();
158 let (mut s1, mut s2) = (0.0f64, 0.0f64);
159 let mut wsum = 0.0;
160 for (i, v) in x.iter().enumerate() {
161 let win = 0.5 - 0.5 * (2.0 * PI * i as f64 / (n - 1).max(1) as f64).cos();
162 wsum += win;
163 let s0 = *v as f64 * win + coeff * s1 - s2;
164 s2 = s1;
165 s1 = s0;
166 }
167 let power = s1 * s1 + s2 * s2 - coeff * s1 * s2;
168 2.0 * power.max(0.0).sqrt() / wsum
169}
170
171pub fn si_sdr(reference: &[f32], estimate: &[f32]) -> f64 {
173 let n = reference.len().min(estimate.len());
174 let r = &reference[..n];
175 let e = &estimate[..n];
176 let dot: f64 = r.iter().zip(e).map(|(a, b)| *a as f64 * *b as f64).sum();
177 let rr: f64 = r.iter().map(|a| (*a as f64).powi(2)).sum::<f64>().max(1e-20);
178 let alpha = dot / rr;
179 let mut st = 0.0;
180 let mut en = 0.0;
181 for i in 0..n {
182 let t = alpha * r[i] as f64;
183 st += t * t;
184 en += (e[i] as f64 - t).powi(2);
185 }
186 10.0 * (st.max(1e-20) / en.max(1e-20)).log10()
187}
188
189pub struct TempDir(pub std::path::PathBuf);
191
192impl TempDir {
193 pub fn new(name: &str) -> TempDir {
194 let p = std::env::temp_dir().join(format!(
195 "ferraus-test-{}-{}-{}",
196 name,
197 std::process::id(),
198 std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
199 ));
200 std::fs::create_dir_all(&p).unwrap();
201 TempDir(p)
202 }
203 pub fn path(&self, f: &str) -> std::path::PathBuf {
204 self.0.join(f)
205 }
206}
207
208impl Drop for TempDir {
209 fn drop(&mut self) {
210 let _ = std::fs::remove_dir_all(&self.0);
211 }
212}
213
214pub fn write_wav(path: &std::path::Path, b: &AudioBuffer) {
216 std::fs::write(path, crate::wav::encode(b, 32, true).unwrap()).unwrap();
217}
218
219pub fn read_wav(path: &std::path::Path) -> AudioBuffer {
220 crate::wav::decode(&std::fs::read(path).unwrap()).unwrap().0
221}