Break down forth implementation properly
This commit is contained in:
@@ -181,14 +181,14 @@ fn empty_local_cycle() {
|
||||
|
||||
#[test]
|
||||
fn echo_creates_decaying_subdivisions() {
|
||||
// stepdur = 0.125, echo 3
|
||||
// d1 + d1/2 + d1/4 = d1 * 1.75 = 0.125
|
||||
// d1 = 0.125 / 1.75 = 0.0714285714...
|
||||
// default dur = 0.5, echo 3
|
||||
// d1 + d1/2 + d1/4 = d1 * 1.75 = 0.5
|
||||
// d1 = 0.5 / 1.75
|
||||
let outputs = expect_outputs(r#""kick" s 3 echo each"#, 3);
|
||||
let durs = get_durs(&outputs);
|
||||
let deltas = get_deltas(&outputs);
|
||||
|
||||
let d1 = 0.125 / 1.75;
|
||||
let d1 = 0.5 / 1.75;
|
||||
let d2 = d1 / 2.0;
|
||||
let d3 = d1 / 4.0;
|
||||
|
||||
@@ -220,14 +220,14 @@ fn echo_error_zero_count() {
|
||||
|
||||
#[test]
|
||||
fn necho_creates_growing_subdivisions() {
|
||||
// stepdur = 0.125, necho 3
|
||||
// d1 + 2*d1 + 4*d1 = d1 * 7 = 0.125
|
||||
// d1 = 0.125 / 7
|
||||
// default dur = 0.5, necho 3
|
||||
// d1 + 2*d1 + 4*d1 = d1 * 7 = 0.5
|
||||
// d1 = 0.5 / 7
|
||||
let outputs = expect_outputs(r#""kick" s 3 necho each"#, 3);
|
||||
let durs = get_durs(&outputs);
|
||||
let deltas = get_deltas(&outputs);
|
||||
|
||||
let d1 = 0.125 / 7.0;
|
||||
let d1 = 0.5 / 7.0;
|
||||
let d2 = d1 * 2.0;
|
||||
let d3 = d1 * 4.0;
|
||||
|
||||
|
||||
134
tests/forth/list_words.rs
Normal file
134
tests/forth/list_words.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use super::harness::*;
|
||||
|
||||
#[test]
|
||||
fn choose_word_from_list() {
|
||||
// 1 2 [ + - ] choose: picks + or -, applies to 1 2
|
||||
// With seed 42, choose picks one deterministically
|
||||
let f = forth();
|
||||
let ctx = default_ctx();
|
||||
f.evaluate("1 2 [ + - ] choose", &ctx).unwrap();
|
||||
let val = stack_int(&f);
|
||||
assert!(val == 3 || val == -1, "expected 3 or -1, got {}", val);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cycle_word_from_list() {
|
||||
// At runs=0, picks first word (dup)
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let f = run_ctx("5 < dup nip >", &ctx);
|
||||
assert_eq!(stack_int(&f), 5); // dup leaves 5 5, but stack check takes top
|
||||
|
||||
// At runs=1, picks second word (2 *)
|
||||
let f = forth();
|
||||
let ctx = ctx_with(|c| c.runs = 1);
|
||||
f.evaluate(": double 2 * ; 5 < dup double >", &ctx).unwrap();
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_word_in_list() {
|
||||
let f = forth();
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
f.evaluate(": add3 3 + ; : add5 5 + ; 10 < add3 add5 >", &ctx).unwrap();
|
||||
assert_eq!(stack_int(&f), 13); // runs=0 picks add3
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_word_in_list_second() {
|
||||
let f = forth();
|
||||
let ctx = ctx_with(|c| c.runs = 1);
|
||||
f.evaluate(": add3 3 + ; : add5 5 + ; 10 < add3 add5 >", &ctx).unwrap();
|
||||
assert_eq!(stack_int(&f), 15); // runs=1 picks add5
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn values_in_list_still_work() {
|
||||
// Numbers inside lists should still push as values (not quotations)
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let f = run_ctx("< 10 20 30 >", &ctx);
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn values_in_list_cycle() {
|
||||
let ctx = ctx_with(|c| c.runs = 2);
|
||||
let f = run_ctx("< 10 20 30 >", &ctx);
|
||||
assert_eq!(stack_int(&f), 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_values_and_words() {
|
||||
// Values stay as values, words become quotations
|
||||
// [ 10 20 ] choose just picks a number
|
||||
let f = forth();
|
||||
let ctx = default_ctx();
|
||||
f.evaluate("[ 10 20 ] choose", &ctx).unwrap();
|
||||
let val = stack_int(&f);
|
||||
assert!(val == 10 || val == 20, "expected 10 or 20, got {}", val);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_with_sound_params() {
|
||||
let f = forth();
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let outputs = f.evaluate(
|
||||
": myverb 0.5 verb ; \"sine\" s 440 freq < myverb > emit",
|
||||
&ctx
|
||||
).unwrap();
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert!(outputs[0].contains("verb/0.5"), "expected verb/0.5 in {}", outputs[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arithmetic_word_in_list() {
|
||||
// 3 4 [ + ] choose -> picks + (only option), applies to 3 4 = 7
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let f = run_ctx("3 4 < + >", &ctx);
|
||||
assert_eq!(stack_int(&f), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pcycle_word_from_list() {
|
||||
let ctx = ctx_with(|c| c.iter = 0);
|
||||
let f = run_ctx("10 << dup 2 * >>", &ctx);
|
||||
// iter=0 picks dup: 10 10
|
||||
let stack = f.stack();
|
||||
assert_eq!(stack.len(), 2);
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pcycle_word_second() {
|
||||
let ctx = ctx_with(|c| c.iter = 1);
|
||||
let f = run_ctx("10 << dup 2 * >>", &ctx);
|
||||
// iter=1 picks "2 *" — but wait, each token is its own element
|
||||
// so << dup 2 * >> has 3 elements: {dup}, 2, {*}
|
||||
// iter=1 picks element index 1 which is value 2
|
||||
assert_eq!(stack_int(&f), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_op_quotation_in_list() {
|
||||
// Use { } for multi-op quotations inside lists
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let f = run_ctx("10 < { 2 * } { 3 + } >", &ctx);
|
||||
assert_eq!(stack_int(&f), 20); // runs=0 picks {2 *}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_op_quotation_second() {
|
||||
let ctx = ctx_with(|c| c.runs = 1);
|
||||
let f = run_ctx("10 < { 2 * } { 3 + } >", &ctx);
|
||||
assert_eq!(stack_int(&f), 13); // runs=1 picks {3 +}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pipe_syntax_with_words() {
|
||||
// | word1 word2 | uses LocalCycleEnd which should auto-apply quotations
|
||||
// LocalCycleEnd uses time_ctx.iteration_index, which defaults to 0 outside for loops
|
||||
let f = forth();
|
||||
let ctx = default_ctx();
|
||||
f.evaluate(": add3 3 + ; : add5 5 + ; 10 | add3 add5 |", &ctx).unwrap();
|
||||
// iteration_index defaults to 0, picks first word (add3)
|
||||
assert_eq!(stack_int(&f), 13);
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user