use crate::helpers::part;
use std::f64::consts::TAU;
use sva_formula::{Body, C64, ClosedForm, Edge, Origin, Unary, Var};
use sva_samples::{CollapseError, Grid, PSYCHOACOUSTIC_V1, Rows};
const RATE: u32 = 8_000;
const LEN: usize = 4_000;
fn form(var: Var, body: Body) -> ClosedForm {
ClosedForm {
var,
body,
origin: Origin::new(0),
}
}
fn cosine(hz: f64, amp: f64) -> Body {
Body::Mul(vec![
part(Body::Const(C64::real(amp))),
part(Body::Apply(
Unary::Cos,
part(Body::Mul(vec![
part(Body::Const(C64::real(TAU * hz))),
part(Body::Line),
])),
)),
])
}
fn blocks(form: &ClosedForm, start: i64, block: i64) -> Vec<f64> {
let rows = Rows::of(form, Grid::of(RATE), &PSYCHOACOUSTIC_V1).expect("rows");
let mut out = Vec::new();
let mut from = start;
while from < LEN as i64 {
let to = (from + block).min(LEN as i64);
out.extend(rows.planes(from, to).expect("a span").swap_remove(0));
from = to;
}
out
}
fn whole(form: &ClosedForm, start: i64) -> Vec<f64> {
let rows = Rows::of(form, Grid::of(RATE), &PSYCHOACOUSTIC_V1).expect("rows");
let mut planes = rows.planes(start, LEN as i64).expect("a whole read");
planes.swap_remove(0)
}
#[test]
fn a_form_read_one_instant_at_a_time_is_the_same_in_any_span() {
let chord = form(
Var::T,
Body::Add(vec![
part(cosine(220.0, 0.5)),
part(cosine(330.0, 0.25)),
part(cosine(0.0, 0.125)),
]),
);
let shaped = form(Var::T, Body::Apply(Unary::Tanh, part(cosine(110.0, 3.0))));
for written in [chord, shaped] {
for start in [0, 777] {
let want = whole(&written, start);
assert!(want.iter().any(|v| *v != 0.0), "silence tests nothing");
for block in [1, 128, 999, LEN as i64] {
assert_eq!(
blocks(&written, start, block),
want,
"blocks of {block} from {start}"
);
}
}
}
}
#[test]
fn a_form_in_f_has_no_row_a_span_reads_alone() {
let spectrum = form(Var::F, Body::Const(C64::real(1.0)));
assert_eq!(
Rows::of(&spectrum, Grid::of(RATE), &PSYCHOACOUSTIC_V1).err(),
Some(CollapseError::NoBlockRow)
);
}
fn notes(terms: usize, gap: f64, width: f64) -> Body {
let note = |k: usize| Body::Shift {
by: k as f64 * gap,
of: part(Body::Crop {
of: part(Body::Apply(Unary::Tanh, part(cosine(440.0, 3.0)))),
l: Edge::at(0.0),
r: Edge::at(width),
rise: 0.0,
fall: 0.0,
}),
};
(1..terms).fold(note(0), |sum, k| Body::Add(vec![part(sum), part(note(k))]))
}
#[test]
fn a_point_sampled_sum_reads_each_term_only_inside_its_crop() {
let (gap, width) = (0.05, 0.01);
let len = |terms: usize| (terms as f64 * gap * f64::from(RATE)) as i64;
let rows = |terms: usize| {
Rows::of(
&form(Var::T, notes(terms, gap, width)),
Grid::of(RATE),
&PSYCHOACOUSTIC_V1,
)
.expect("rows")
};
let written = sva_samples::Evaluator::of(¬es(8, gap, width), 0);
let planes = rows(8).planes(0, len(8)).expect("samples");
for (n, v) in planes[0].iter().enumerate() {
let want = written
.at(sva_samples::At::Sample(
sva_samples::Grid::of(RATE),
n as i64,
))
.expect("a value");
assert_eq!(v.to_bits(), want.re.to_bits(), "sample {n}");
}
}