use crate::buffer::AudioBuffer;
use crate::ctx::{Globals, NoProgress, OpCtx};
use crate::op::{Args, OpSpec};
use crate::registry::Registry;
use crate::wav::SimpleIo;
use std::f64::consts::PI;
pub static SIMPLE_IO: SimpleIo = SimpleIo;
pub static NO_PROGRESS: NoProgress = NoProgress;
pub fn ctx() -> OpCtx<'static> {
OpCtx::new("test", vec![], Globals::default(), &SIMPLE_IO, &NO_PROGRESS)
}
pub fn ctx_with(registry: &'static Registry) -> OpCtx<'static> {
let mut c = ctx();
c.registry = Some(registry);
c
}
pub fn leak_registry(ops: Vec<OpSpec>) -> &'static Registry {
let mut r = Registry::new();
r.extend(ops);
Box::leak(Box::new(r))
}
pub fn args(spec: &OpSpec, pairs: &[(&str, &str)]) -> Args {
Args::build(spec, pairs.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(), &|_, _| None)
.expect("valid test args")
}
pub fn run(spec: &OpSpec, pairs: &[(&str, &str)], input: AudioBuffer) -> crate::error::Result<AudioBuffer> {
let a = args(spec, pairs);
let mut c = ctx();
crate::exec::apply_spec(spec, &a, input, &mut c)
}
pub fn run_ctx(
spec: &OpSpec,
pairs: &[(&str, &str)],
input: AudioBuffer,
) -> crate::error::Result<(AudioBuffer, OpCtx<'static>)> {
let a = args(spec, pairs);
let mut c = ctx();
let out = crate::exec::apply_spec(spec, &a, input, &mut c)?;
Ok((out, c))
}
pub fn sine(sr: u32, freq: f64, secs: f64, amp: f32) -> AudioBuffer {
let n = (sr as f64 * secs).round() as usize;
AudioBuffer::from_mono(sr, (0..n).map(|i| amp * (2.0 * PI * freq * i as f64 / sr as f64).sin() as f32).collect())
}
pub fn stereo(b: AudioBuffer) -> AudioBuffer {
b.with_channels(2)
}
pub fn noise(sr: u32, secs: f64, amp: f32, seed: u64) -> AudioBuffer {
let n = (sr as f64 * secs).round() as usize;
let mut x = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407) | 1;
AudioBuffer::from_mono(
sr,
(0..n)
.map(|_| {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
amp * ((x >> 11) as f64 / (1u64 << 53) as f64 * 2.0 - 1.0) as f32
})
.collect(),
)
}
pub fn chirp(sr: u32, f0: f64, f1: f64, secs: f64, amp: f32) -> AudioBuffer {
let n = (sr as f64 * secs).round() as usize;
let k = (f1 - f0) / secs;
AudioBuffer::from_mono(
sr,
(0..n)
.map(|i| {
let t = i as f64 / sr as f64;
amp * (2.0 * PI * (f0 * t + 0.5 * k * t * t)).sin() as f32
})
.collect(),
)
}
pub fn clicks(sr: u32, period: f64, secs: f64, amp: f32) -> AudioBuffer {
let n = (sr as f64 * secs).round() as usize;
let step = (period * sr as f64).round() as usize;
let mut v = vec![0.0; n];
let mut i = 0;
while i < n {
v[i] = amp;
i += step.max(1);
}
AudioBuffer::from_mono(sr, v)
}
pub fn add(a: &AudioBuffer, b: &AudioBuffer) -> AudioBuffer {
let n = a.frames().max(b.frames());
let ch = a.channels().max(b.channels());
let a2 = a.with_channels(ch);
let b2 = b.with_channels(ch);
let data = (0..ch)
.map(|c| {
(0..n)
.map(|i| a2.data[c].get(i).copied().unwrap_or(0.0) + b2.data[c].get(i).copied().unwrap_or(0.0))
.collect()
})
.collect();
AudioBuffer { sample_rate: a.sample_rate, data }
}
pub fn rms_diff(a: &AudioBuffer, b: &AudioBuffer) -> f64 {
let n = a.frames().min(b.frames());
let ch = a.channels().min(b.channels());
let mut s = 0.0;
for c in 0..ch {
for i in 0..n {
let d = (a.data[c][i] - b.data[c][i]) as f64;
s += d * d;
}
}
(s / (n * ch).max(1) as f64).sqrt()
}
pub fn rms_db(x: &[f32]) -> f64 {
let s: f64 = x.iter().map(|v| (*v as f64).powi(2)).sum::<f64>() / x.len().max(1) as f64;
crate::buffer::lin_to_db(s.sqrt())
}
pub fn tone_level(x: &[f32], sr: u32, f: f64) -> f64 {
let n = x.len();
if n == 0 {
return 0.0;
}
let w = 2.0 * PI * f / sr as f64;
let coeff = 2.0 * w.cos();
let (mut s1, mut s2) = (0.0f64, 0.0f64);
let mut wsum = 0.0;
for (i, v) in x.iter().enumerate() {
let win = 0.5 - 0.5 * (2.0 * PI * i as f64 / (n - 1).max(1) as f64).cos();
wsum += win;
let s0 = *v as f64 * win + coeff * s1 - s2;
s2 = s1;
s1 = s0;
}
let power = s1 * s1 + s2 * s2 - coeff * s1 * s2;
2.0 * power.max(0.0).sqrt() / wsum
}
pub fn si_sdr(reference: &[f32], estimate: &[f32]) -> f64 {
let n = reference.len().min(estimate.len());
let r = &reference[..n];
let e = &estimate[..n];
let dot: f64 = r.iter().zip(e).map(|(a, b)| *a as f64 * *b as f64).sum();
let rr: f64 = r.iter().map(|a| (*a as f64).powi(2)).sum::<f64>().max(1e-20);
let alpha = dot / rr;
let mut st = 0.0;
let mut en = 0.0;
for i in 0..n {
let t = alpha * r[i] as f64;
st += t * t;
en += (e[i] as f64 - t).powi(2);
}
10.0 * (st.max(1e-20) / en.max(1e-20)).log10()
}
pub struct TempDir(pub std::path::PathBuf);
impl TempDir {
pub fn new(name: &str) -> TempDir {
let p = std::env::temp_dir().join(format!(
"ferraus-test-{}-{}-{}",
name,
std::process::id(),
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
));
std::fs::create_dir_all(&p).unwrap();
TempDir(p)
}
pub fn path(&self, f: &str) -> std::path::PathBuf {
self.0.join(f)
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
pub fn write_wav(path: &std::path::Path, b: &AudioBuffer) {
std::fs::write(path, crate::wav::encode(b, 32, true).unwrap()).unwrap();
}
pub fn read_wav(path: &std::path::Path) -> AudioBuffer {
crate::wav::decode(&std::fs::read(path).unwrap()).unwrap().0
}