Break down forth implementation properly

This commit is contained in:
2026-01-23 19:36:40 +01:00
parent 74f178f271
commit 1433e07066
11 changed files with 3246 additions and 2987 deletions

View File

@@ -28,6 +28,7 @@ fn approx_eq(a: f64, b: f64) -> bool {
}
// At 120 BPM, speed 1.0: stepdur = 60/120/4/1 = 0.125s
// Default duration = 4 * stepdur = 0.5s
#[test]
fn stepdur_baseline() {
@@ -47,23 +48,24 @@ fn emit_no_delta() {
#[test]
fn at_half() {
// at 0.5 in root zoom (0..0.125) => delta = 0.5 * 0.125 = 0.0625
// 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.0625),
"at 0.5 should be delta 0.0625, got {}",
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.03125),
"at 0.25 should be delta 0.03125, got {}",
approx_eq(deltas[0], 0.125),
"at 0.25 should be delta 0.125, got {}",
deltas[0]
);
}
@@ -77,23 +79,23 @@ fn at_zero() {
#[test]
fn div_2_each() {
// 2 subdivisions: deltas at 0 and 0.0625 (half of 0.125)
// 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.0625),
"second subdivision at 0.0625, got {}",
approx_eq(deltas[1], 0.25),
"second subdivision at 0.25, got {}",
deltas[1]
);
}
#[test]
fn div_4_each() {
// 4 subdivisions: 0, 0.03125, 0.0625, 0.09375
// 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.03125, 0.0625, 0.09375];
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),
@@ -107,10 +109,10 @@ fn div_4_each() {
#[test]
fn div_3_each() {
// 3 subdivisions: 0, 0.125/3, 2*0.125/3
// 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.125 / 3.0;
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]);
@@ -118,56 +120,56 @@ fn div_3_each() {
#[test]
fn zoom_full() {
// zoom 0.0 1.0 is the full step, same as root
// 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.0625), "full zoom at 0.5 = 0.0625");
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.0625)
// at 0.5 within that = 0.25 of full step = 0.03125
// 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.03125),
"first-half zoom at 0.5 = 0.03125, got {}",
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.0625..0.125)
// at 0.0 within that = start of second half = 0.0625
// 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.0625),
"second-half zoom at 0.0 = 0.0625, got {}",
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 step = 0.09375
// 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.09375), "got {}", deltas[0]);
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.0625, inner: 0.5..1.0 of that = 0.03125..0.0625
// at 0.0 in inner = 0.03125
// 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.03125),
"nested zoom at 0.0 = 0.03125, got {}",
approx_eq(deltas[0], 0.125),
"nested zoom at 0.0 = 0.125, got {}",
deltas[0]
);
}
@@ -175,7 +177,7 @@ fn nested_zooms() {
#[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.0625
// 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,
@@ -183,7 +185,7 @@ fn zoom_pop_sequence() {
let deltas = get_deltas(&outputs);
assert!(approx_eq(deltas[0], 0.0), "first zoom start");
assert!(
approx_eq(deltas[1], 0.0625),
approx_eq(deltas[1], 0.25),
"second zoom start, got {}",
deltas[1]
);
@@ -191,12 +193,12 @@ fn zoom_pop_sequence() {
#[test]
fn div_in_zoom() {
// zoom 0.0 0.5 (duration 0.0625), then div 2 each
// subdivisions at 0 and 0.03125
// 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.03125), "got {}", deltas[1]);
assert!(approx_eq(deltas[1], 0.125), "got {}", deltas[1]);
}
#[test]
@@ -219,11 +221,11 @@ fn speed_affects_stepdur() {
#[test]
fn div_each_at_different_tempo() {
// At 60 BPM: stepdur = 0.25, so div 2 each => 0, 0.125
// 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.125), "got {}", deltas[1]);
assert!(approx_eq(deltas[1], 0.5), "got {}", deltas[1]);
}