230 lines
6.5 KiB
Rust
230 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
|
|
|
|
#[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 window (0..0.125) => delta = 0.5 * 0.125 = 0.0625
|
|
let outputs = expect_outputs(r#""kick" s 0.5 at"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(
|
|
approx_eq(deltas[0], 0.0625),
|
|
"at 0.5 should be delta 0.0625, got {}",
|
|
deltas[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn at_quarter() {
|
|
let outputs = expect_outputs(r#""kick" s 0.25 at"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(
|
|
approx_eq(deltas[0], 0.03125),
|
|
"at 0.25 should be delta 0.03125, got {}",
|
|
deltas[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn at_zero() {
|
|
let outputs = expect_outputs(r#""kick" s 0.0 at"#, 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.0625 (half of 0.125)
|
|
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.0625),
|
|
"second subdivision at 0.0625, got {}",
|
|
deltas[1]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn div_4_each() {
|
|
// 4 subdivisions: 0, 0.03125, 0.0625, 0.09375
|
|
let outputs = expect_outputs(r#""kick" s 4 div each"#, 4);
|
|
let deltas = get_deltas(&outputs);
|
|
let expected = [0.0, 0.03125, 0.0625, 0.09375];
|
|
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.125/3, 2*0.125/3
|
|
let outputs = expect_outputs(r#""kick" s 3 div each"#, 3);
|
|
let deltas = get_deltas(&outputs);
|
|
let step = 0.125 / 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 window_full() {
|
|
// window 0.0 1.0 is the full step, same as root
|
|
let outputs = expect_outputs(r#"0.0 1.0 window "kick" s 0.5 at"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.0625), "full window at 0.5 = 0.0625");
|
|
}
|
|
|
|
#[test]
|
|
fn window_first_half() {
|
|
// window 0.0 0.5 restricts to first half (0..0.0625)
|
|
// at 0.5 within that = 0.25 of full step = 0.03125
|
|
let outputs = expect_outputs(r#"0.0 0.5 window "kick" s 0.5 at"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(
|
|
approx_eq(deltas[0], 0.03125),
|
|
"first-half window at 0.5 = 0.03125, got {}",
|
|
deltas[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn window_second_half() {
|
|
// window 0.5 1.0 restricts to second half (0.0625..0.125)
|
|
// at 0.0 within that = start of second half = 0.0625
|
|
let outputs = expect_outputs(r#"0.5 1.0 window "kick" s 0.0 at"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(
|
|
approx_eq(deltas[0], 0.0625),
|
|
"second-half window at 0.0 = 0.0625, got {}",
|
|
deltas[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn window_second_half_middle() {
|
|
// window 0.5 1.0, at 0.5 within that = 0.75 of full step = 0.09375
|
|
let outputs = expect_outputs(r#"0.5 1.0 window "kick" s 0.5 at"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.09375), "got {}", deltas[0]);
|
|
}
|
|
|
|
#[test]
|
|
fn nested_windows() {
|
|
// window 0.0 0.5, then window 0.5 1.0 within that
|
|
// outer: 0..0.0625, inner: 0.5..1.0 of that = 0.03125..0.0625
|
|
// at 0.0 in inner = 0.03125
|
|
let outputs = expect_outputs(r#"0.0 0.5 window 0.5 1.0 window "kick" s 0.0 at"#, 1);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(
|
|
approx_eq(deltas[0], 0.03125),
|
|
"nested window at 0.0 = 0.03125, got {}",
|
|
deltas[0]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn window_pop_sequence() {
|
|
// First in window 0.0 0.5 at 0.0 -> delta 0
|
|
// Pop, then in window 0.5 1.0 at 0.0 -> delta 0.0625
|
|
let outputs = expect_outputs(
|
|
r#"0.0 0.5 window "kick" s 0.0 at pop 0.5 1.0 window "snare" s 0.0 at"#,
|
|
2,
|
|
);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.0), "first window start");
|
|
assert!(
|
|
approx_eq(deltas[1], 0.0625),
|
|
"second window start, got {}",
|
|
deltas[1]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn div_in_window() {
|
|
// window 0.0 0.5 (duration 0.0625), then div 2 each
|
|
// subdivisions at 0 and 0.03125
|
|
let outputs = expect_outputs(r#"0.0 0.5 window "kick" s 2 div each"#, 2);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.0));
|
|
assert!(approx_eq(deltas[1], 0.03125), "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, so div 2 each => 0, 0.125
|
|
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.125), "got {}", deltas[1]);
|
|
}
|