use crate::fixtures::graph_of;
use sva_engine::{RenderConfig, Tier, render};
const RATE: u32 = 8_000;
fn chain(base: &str, depth: usize, crop: &str, body: impl Fn(&str) -> String) -> u64 {
over(base, (depth, crop), body, &format!("@n{depth}(t)"))
}
fn over(base: &str, (depth, crop): (usize, &str), body: impl Fn(&str) -> String, top: &str) -> u64 {
let mut files = vec![("n0".to_string(), format!("crop({base}, 0s, {crop})\n"))];
for k in 1..=depth {
let read = format!("@n{}(t)", k - 1);
files.push((
format!("n{k}"),
format!("crop({}, 0s, {crop})\n", body(&read)),
));
}
files.push(("top".to_string(), format!("{top}\n")));
let held: Vec<(&str, &str)> = files
.iter()
.map(|(n, b)| (n.as_str(), b.as_str()))
.collect();
let g = graph_of("chain", &held);
let render = render(&g, "top", RenderConfig::at(RATE), &Tier::default())
.unwrap_or_else(|e| panic!("{top}: {e}"));
let id = render.id("top").expect("the root");
let out = render.output(id).expect("a buffer");
let mut digest = 0xcbf2_9ce4_8422_2325u64;
let mut word = |w: u64| {
for byte in w.to_le_bytes() {
digest = (digest ^ u64::from(byte)).wrapping_mul(0x100_0000_01b3);
}
};
let planes = out.slices();
word(planes.len() as u64);
for samples in planes {
assert!(samples.iter().any(|v| *v != 0.0), "silence tests nothing");
word(samples.len() as u64);
samples.iter().for_each(|v| word(v.to_bits()));
}
digest
}
const TONE: &str = "sin(2*pi*220*t)";
#[test]
fn a_sum_reading_its_source_thrice_at_each_of_twelve_levels() {
let digest = chain(TONE, 12, "1s", |p| format!("{p}*0.5 + {p}*0.3 + {p}*0.2"));
assert_eq!(digest, 0xec39_7011_2dae_c8b7);
}
#[test]
fn a_sum_with_a_modulated_term_at_each_of_twelve_levels() {
let digest = chain(TONE, 12, "1s", |p| {
format!("{p}*sin(2*pi*3*t) + {p}*0.3 + {p}*0.2")
});
assert_eq!(digest, 0x975d_1983_4c26_01b5);
}
#[test]
fn a_sum_with_a_max_at_each_of_twelve_levels() {
let digest = chain(TONE, 12, "1s", |p| {
format!("max({p}, 0)*0.5 + {p}*0.3 + {p}*0.2")
});
assert_eq!(digest, 0x2150_5fe6_e36e_9ea7);
}
#[test]
fn a_sum_with_a_square_at_each_of_eight_levels() {
let digest = chain("sin(2*pi*t) + 1.5", 8, "2s", |p| {
format!("0.5*{p} + 0.3*{p}*{p}")
});
assert_eq!(digest, 0xbf63_7e7d_2991_24b6);
}
#[test]
fn an_exponential_window_over_a_sum_twelve_levels_deep() {
let sum = |p: &str| format!("{p}*0.5 + {p}*0.3 + {p}*0.2");
let digest = over(TONE, (12, "1s"), sum, "crop(exp(-3*t)*@n12(t), 0s, 1s)");
assert_eq!(digest, 0xa515_a431_4935_0c71);
}
#[test]
fn a_loop_over_a_sum_with_a_modulated_term_twelve_levels_deep() {
let body = |p: &str| format!("{p}*sin(2*pi*3*t) + {p}*0.3 + {p}*0.2");
let digest = over(TONE, (12, "1s"), body, "@n12(t) + 0.5*self(t - 17ms)");
assert_eq!(digest, 0x1491_1a68_85d7_8c9e);
}
fn underflowing(depth: usize, read: impl Fn(&str) -> String, top: &str) -> usize {
let mut files = vec![("c0".to_string(), "sin(2*pi*220*t)\n".to_string())];
for k in 1..=depth {
files.push((
format!("c{k}"),
format!("{}\n", read(&format!("c{}", k - 1))),
));
}
files.push(("top".to_string(), format!("{top}\n")));
let held: Vec<(&str, &str)> = files
.iter()
.map(|(n, b)| (n.as_str(), b.as_str()))
.collect();
let g = graph_of("underflowing", &held);
let mut config = RenderConfig::at(RATE);
config.profile.prune_db = -1e4;
let render =
render(&g, "top", config, &Tier::default()).unwrap_or_else(|e| panic!("{top}: {e}"));
let id = render.id("top").expect("the root");
render.output(id).expect("a buffer").plane(0).len()
}
#[test]
fn an_exponential_over_a_tone_twenty_refs_down_ends_where_it_underflows() {
let read = |p: &str| format!("@{p}(t)");
let shallow = underflowing(20, read, "crop(exp(-20*t)*@c3(t)*exp(-20*t), 0s, inf)");
let deep = underflowing(20, read, "crop(exp(-20*t)*@c20(t)*exp(-20*t), 0s, inf)");
assert!(shallow > RATE as usize, "{shallow} samples");
assert!(shallow.abs_diff(deep) <= 1, "{shallow} against {deep}");
}
#[test]
fn an_exponential_over_a_tone_read_twice_a_level_forty_levels_down_ends() {
let read = |p: &str| format!("@{p}(t) + @{p}(t - 0.1s)");
let shallow = underflowing(1, read, "crop(exp(-20*t)*@c1(t)*exp(-20*t), 0s, inf)");
let deep = underflowing(40, read, "crop(exp(-20*t)*@c40(t)*exp(-20*t), 0s, inf)");
let one = underflowing(1, read, "crop(exp(-20*t)*@c1(t), 0s, inf)");
assert!(shallow < deep && deep < one, "{shallow}, {deep}, {one}");
}
#[test]
fn a_chain_a_thousand_refs_deep_renders_on_a_small_stack() {
let bodies = [
"0.999*@P(t)",
"max(@P(t), -1)",
"crop(lowpass(sample(@P(t)), 3000), 0s, 1s)",
"@P(t - 1ms)",
"tanh(@P(t))",
];
let mut files = vec![(
"c0".to_string(),
"crop(sin(2*pi*220*t), 0s, 0.1s)\n".to_string(),
)];
for k in 1..=1000 {
let body = bodies[k % bodies.len()].replace('P', &format!("c{}", k - 1));
files.push((format!("c{k}"), format!("{body}\n")));
}
files.push(("top".to_string(), "crop(@c1000(t), 0s, 1s)\n".to_string()));
let worker = std::thread::Builder::new()
.stack_size(1 << 20)
.spawn(move || {
let held: Vec<(&str, &str)> = files
.iter()
.map(|(n, b)| (n.as_str(), b.as_str()))
.collect();
let g = graph_of("thousand", &held);
let render = render(&g, "top", RenderConfig::at(RATE), &Tier::default())
.unwrap_or_else(|e| panic!("the chain renders: {e}"));
let id = render.id("top").expect("the root");
let out = render.output(id).expect("a buffer").plane(0).to_vec();
out.iter().any(|v| *v != 0.0)
});
let sounds = worker.expect("a worker").join().expect("no overflow");
assert!(sounds, "the chain sounds");
}