232 lines
6.5 KiB
Rust
232 lines
6.5 KiB
Rust
use super::harness::*;
|
|
use std::collections::HashMap;
|
|
|
|
fn parse_params(output: &str) -> HashMap<String, f64> {
|
|
let mut params = HashMap::new();
|
|
let parts: Vec<&str> = output.trim_start_matches('/').split('/').collect();
|
|
let mut i = 0;
|
|
while i + 1 < parts.len() {
|
|
if let Ok(v) = parts[i + 1].parse::<f64>() {
|
|
params.insert(parts[i].to_string(), v);
|
|
}
|
|
i += 2;
|
|
}
|
|
params
|
|
}
|
|
|
|
fn get_deltas(outputs: &[String]) -> Vec<f64> {
|
|
outputs
|
|
.iter()
|
|
.map(|o| parse_params(o).get("delta").copied().unwrap_or(0.0))
|
|
.collect()
|
|
}
|
|
|
|
const EPSILON: f64 = 1e-9;
|
|
|
|
fn approx_eq(a: f64, b: f64) -> bool {
|
|
(a - b).abs() < EPSILON
|
|
}
|
|
|
|
// At 120 BPM, speed 1.0: stepdur = 60/120/4/1 = 0.125s
|
|
// Default duration = 4 * stepdur = 0.5s
|
|
|
|
#[test]
|
|
fn stepdur_baseline() {
|
|
let f = run("stepdur");
|
|
assert!(approx_eq(stack_float(&f), 0.125));
|
|
}
|
|
|
|
#[test]
|
|
fn emit_no_delta() {
|
|
let outputs = expect_outputs(r#""kick" s emit"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(
|
|
approx_eq(deltas[0], 0.0),
|
|
"emit at start should have delta 0"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn at_half() {
|
|
// at 0.5 in root (0..0.5) => delta = 0.5 * 0.5 = 0.25
|
|
let outputs = expect_outputs(r#""kick" s 0.5 at emit pop"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(
|
|
approx_eq(deltas[0], 0.25),
|
|
"at 0.5 should be delta 0.25, got {}",
|
|
deltas[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn at_quarter() {
|
|
// at 0.25 in root (0..0.5) => delta = 0.25 * 0.5 = 0.125
|
|
let outputs = expect_outputs(r#""kick" s 0.25 at emit pop"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(
|
|
approx_eq(deltas[0], 0.125),
|
|
"at 0.25 should be delta 0.125, got {}",
|
|
deltas[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn at_zero() {
|
|
let outputs = expect_outputs(r#""kick" s 0.0 at emit pop"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.0), "at 0.0 should be delta 0");
|
|
}
|
|
|
|
#[test]
|
|
fn div_2_each() {
|
|
// 2 subdivisions: deltas at 0 and 0.25 (half of 0.5)
|
|
let outputs = expect_outputs(r#""kick" s 2 div each"#, 2);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.0), "first subdivision at 0");
|
|
assert!(
|
|
approx_eq(deltas[1], 0.25),
|
|
"second subdivision at 0.25, got {}",
|
|
deltas[1]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn div_4_each() {
|
|
// 4 subdivisions: 0, 0.125, 0.25, 0.375
|
|
let outputs = expect_outputs(r#""kick" s 4 div each"#, 4);
|
|
let deltas = get_deltas(&outputs);
|
|
let expected = [0.0, 0.125, 0.25, 0.375];
|
|
for (i, (got, exp)) in deltas.iter().zip(expected.iter()).enumerate() {
|
|
assert!(
|
|
approx_eq(*got, *exp),
|
|
"subdivision {}: expected {}, got {}",
|
|
i,
|
|
exp,
|
|
got
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn div_3_each() {
|
|
// 3 subdivisions: 0, 0.5/3, 2*0.5/3
|
|
let outputs = expect_outputs(r#""kick" s 3 div each"#, 3);
|
|
let deltas = get_deltas(&outputs);
|
|
let step = 0.5 / 3.0;
|
|
assert!(approx_eq(deltas[0], 0.0));
|
|
assert!(approx_eq(deltas[1], step), "got {}", deltas[1]);
|
|
assert!(approx_eq(deltas[2], 2.0 * step), "got {}", deltas[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn zoom_full() {
|
|
// zoom 0.0 1.0 is the full duration, same as root
|
|
let outputs = expect_outputs(r#"0.0 1.0 zoom "kick" s 0.5 at emit pop"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.25), "full zoom at 0.5 = 0.25");
|
|
}
|
|
|
|
#[test]
|
|
fn zoom_first_half() {
|
|
// zoom 0.0 0.5 restricts to first half (0..0.25)
|
|
// at 0.5 within that = 0.125
|
|
let outputs = expect_outputs(r#"0.0 0.5 zoom "kick" s 0.5 at emit pop"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(
|
|
approx_eq(deltas[0], 0.125),
|
|
"first-half zoom at 0.5 = 0.125, got {}",
|
|
deltas[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn zoom_second_half() {
|
|
// zoom 0.5 1.0 restricts to second half (0.25..0.5)
|
|
// at 0.0 within that = start of second half = 0.25
|
|
let outputs = expect_outputs(r#"0.5 1.0 zoom "kick" s 0.0 at emit pop"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(
|
|
approx_eq(deltas[0], 0.25),
|
|
"second-half zoom at 0.0 = 0.25, got {}",
|
|
deltas[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn zoom_second_half_middle() {
|
|
// zoom 0.5 1.0, at 0.5 within that = 0.75 of full duration = 0.375
|
|
let outputs = expect_outputs(r#"0.5 1.0 zoom "kick" s 0.5 at emit pop"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.375), "got {}", deltas[0]);
|
|
}
|
|
|
|
#[test]
|
|
fn nested_zooms() {
|
|
// zoom 0.0 0.5, then zoom 0.5 1.0 within that
|
|
// outer: 0..0.25, inner: 0.5..1.0 of that = 0.125..0.25
|
|
// at 0.0 in inner = 0.125
|
|
let outputs = expect_outputs(r#"0.0 0.5 zoom 0.5 1.0 zoom "kick" s 0.0 at emit pop"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(
|
|
approx_eq(deltas[0], 0.125),
|
|
"nested zoom at 0.0 = 0.125, got {}",
|
|
deltas[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn zoom_pop_sequence() {
|
|
// First in zoom 0.0 0.5 at 0.0 -> delta 0
|
|
// Pop at context and zoom, then in zoom 0.5 1.0 at 0.0 -> delta 0.25
|
|
let outputs = expect_outputs(
|
|
r#"0.0 0.5 zoom "kick" s 0.0 at emit pop pop 0.5 1.0 zoom "snare" s 0.0 at emit pop"#,
|
|
2,
|
|
);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.0), "first zoom start");
|
|
assert!(
|
|
approx_eq(deltas[1], 0.25),
|
|
"second zoom start, got {}",
|
|
deltas[1]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn div_in_zoom() {
|
|
// zoom 0.0 0.5 (duration 0.25), then div 2 each
|
|
// subdivisions at 0 and 0.125
|
|
let outputs = expect_outputs(r#"0.0 0.5 zoom "kick" s 2 div each"#, 2);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.0));
|
|
assert!(approx_eq(deltas[1], 0.125), "got {}", deltas[1]);
|
|
}
|
|
|
|
#[test]
|
|
fn tempo_affects_stepdur() {
|
|
// At 60 BPM: stepdur = 60/60/4/1 = 0.25
|
|
let ctx = ctx_with(|c| c.tempo = 60.0);
|
|
let f = forth();
|
|
f.evaluate("stepdur", &ctx).unwrap();
|
|
assert!(approx_eq(stack_float(&f), 0.25));
|
|
}
|
|
|
|
#[test]
|
|
fn speed_affects_stepdur() {
|
|
// At 120 BPM, speed 2.0: stepdur = 60/120/4/2 = 0.0625
|
|
let ctx = ctx_with(|c| c.speed = 2.0);
|
|
let f = forth();
|
|
f.evaluate("stepdur", &ctx).unwrap();
|
|
assert!(approx_eq(stack_float(&f), 0.0625));
|
|
}
|
|
|
|
#[test]
|
|
fn div_each_at_different_tempo() {
|
|
// At 60 BPM: stepdur = 0.25, default dur = 1.0, so div 2 each => 0, 0.5
|
|
let ctx = ctx_with(|c| c.tempo = 60.0);
|
|
let f = forth();
|
|
let outputs = f.evaluate(r#""kick" s 2 div each"#, &ctx).unwrap();
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.0));
|
|
assert!(approx_eq(deltas[1], 0.5), "got {}", deltas[1]);
|
|
}
|