Small fixes and additions
All checks were successful
Deploy Website / deploy (push) Has been skipped

This commit is contained in:
2026-03-05 19:20:52 +01:00
parent 0097777449
commit 5a72e4cef4
6 changed files with 76 additions and 66 deletions

View File

@@ -56,14 +56,14 @@ fn stepdur_baseline() {
#[test]
fn single_emit() {
let outputs = expect_outputs(r#""kick" s ."#, 1);
let outputs = expect_outputs(r#""kick" snd ."#, 1);
let deltas = get_deltas(&outputs);
assert!(approx_eq(deltas[0], 0.0), "single emit at start should have delta 0");
}
#[test]
fn multiple_emits_all_at_zero() {
let outputs = expect_outputs(r#""kick" s . . . ."#, 4);
let outputs = expect_outputs(r#""kick" snd . . . ."#, 4);
let deltas = get_deltas(&outputs);
for (i, delta) in deltas.iter().enumerate() {
assert!(approx_eq(*delta, 0.0), "emit {}: expected delta 0, got {}", i, delta);
@@ -72,7 +72,7 @@ fn multiple_emits_all_at_zero() {
#[test]
fn sound_persists() {
let outputs = expect_outputs(r#""kick" s . . "hat" s . ."#, 4);
let outputs = expect_outputs(r#""kick" snd . . "hat" snd . ."#, 4);
let sounds = get_sounds(&outputs);
assert_eq!(sounds[0], "kick");
assert_eq!(sounds[1], "kick");
@@ -82,14 +82,14 @@ fn sound_persists() {
#[test]
fn alternating_sounds() {
let outputs = expect_outputs(r#""kick" s . "snare" s . "kick" s . "snare" s ."#, 4);
let outputs = expect_outputs(r#""kick" snd . "snare" snd . "kick" snd . "snare" snd ."#, 4);
let sounds = get_sounds(&outputs);
assert_eq!(sounds, vec!["kick", "snare", "kick", "snare"]);
}
#[test]
fn dur_is_step_duration() {
let outputs = expect_outputs(r#""kick" s ."#, 1);
let outputs = expect_outputs(r#""kick" snd ."#, 1);
let durs = get_durs(&outputs);
assert!(approx_eq(durs[0], 0.5), "dur should be 4 * step_duration (0.5), got {}", durs[0]);
}
@@ -99,7 +99,7 @@ fn cycle_picks_by_runs() {
for runs in 0..4 {
let ctx = ctx_with(|c| c.runs = runs);
let f = forth();
let outputs = f.evaluate(r#""kick" s ( . ) ( ) 2 cycle"#, &ctx).unwrap();
let outputs = f.evaluate(r#""kick" snd ( . ) ( ) 2 cycle"#, &ctx).unwrap();
if runs % 2 == 0 {
assert_eq!(outputs.len(), 1, "runs={}: emit should be picked", runs);
} else {
@@ -113,7 +113,7 @@ fn pcycle_picks_by_iter() {
for iter in 0..4 {
let ctx = ctx_with(|c| c.iter = iter);
let f = forth();
let outputs = f.evaluate(r#""kick" s ( . ) ( ) 2 pcycle"#, &ctx).unwrap();
let outputs = f.evaluate(r#""kick" snd ( . ) ( ) 2 pcycle"#, &ctx).unwrap();
if iter % 2 == 0 {
assert_eq!(outputs.len(), 1, "iter={}: emit should be picked", iter);
} else {
@@ -128,7 +128,7 @@ fn cycle_with_sounds() {
let ctx = ctx_with(|c| c.runs = runs);
let f = forth();
let outputs = f.evaluate(
r#"( "kick" s . ) ( "hat" s . ) ( "snare" s . ) 3 cycle"#,
r#"( "kick" snd . ) ( "hat" snd . ) ( "snare" snd . ) 3 cycle"#,
&ctx
).unwrap();
assert_eq!(outputs.len(), 1, "runs={}: expected 1 output", runs);
@@ -141,7 +141,7 @@ fn cycle_with_sounds() {
#[test]
fn at_single_delta() {
let outputs = expect_outputs(r#"0.5 at "kick" s ."#, 1);
let outputs = expect_outputs(r#"0.5 at "kick" snd ."#, 1);
let deltas = get_deltas(&outputs);
let step_dur = 0.125;
assert!(approx_eq(deltas[0], 0.5 * step_dur), "expected delta at 0.5 of step, got {}", deltas[0]);
@@ -149,7 +149,7 @@ fn at_single_delta() {
#[test]
fn at_list_deltas() {
let outputs = expect_outputs(r#"0 0.5 at "kick" s ."#, 2);
let outputs = expect_outputs(r#"0 0.5 at "kick" snd ."#, 2);
let deltas = get_deltas(&outputs);
let step_dur = 0.125;
assert!(approx_eq(deltas[0], 0.0), "expected delta 0, got {}", deltas[0]);
@@ -158,7 +158,7 @@ fn at_list_deltas() {
#[test]
fn at_three_deltas() {
let outputs = expect_outputs(r#"0 0.33 0.67 at "kick" s ."#, 3);
let outputs = expect_outputs(r#"0 0.33 0.67 at "kick" snd ."#, 3);
let deltas = get_deltas(&outputs);
let step_dur = 0.125;
assert!(approx_eq(deltas[0], 0.0), "expected delta 0");
@@ -168,7 +168,7 @@ fn at_three_deltas() {
#[test]
fn at_persists_across_emits() {
let outputs = expect_outputs(r#"0 0.5 at "kick" s . "hat" s ."#, 4);
let outputs = expect_outputs(r#"0 0.5 at "kick" snd . "hat" snd ."#, 4);
let sounds = get_sounds(&outputs);
assert_eq!(sounds, vec!["kick", "kick", "hat", "hat"]);
}
@@ -176,14 +176,14 @@ fn at_persists_across_emits() {
#[test]
fn at_reset_with_zero() {
let outputs = expect_outputs(r#"0 0.5 at "kick" s . 0.0 at "hat" s ."#, 3);
let outputs = expect_outputs(r#"0 0.5 at "kick" snd . 0.0 at "hat" snd ."#, 3);
let sounds = get_sounds(&outputs);
assert_eq!(sounds, vec!["kick", "kick", "hat"]);
}
#[test]
fn clear_resets_at_deltas() {
let outputs = expect_outputs(r#"0 0.5 at "kick" s . clear "hat" s ."#, 3);
let outputs = expect_outputs(r#"0 0.5 at "kick" snd . clear "hat" snd ."#, 3);
let sounds = get_sounds(&outputs);
assert_eq!(sounds, vec!["kick", "kick", "hat"]);
let deltas = get_deltas(&outputs);
@@ -196,7 +196,7 @@ fn at_records_selected_spans() {
let f = forth();
let mut trace = ExecutionTrace::default();
let script = r#"0 0.5 0.75 at "kick" s ."#;
let script = r#"0 0.5 0.75 at "kick" snd ."#;
f.evaluate_with_trace(script, &default_ctx(), &mut trace).unwrap();
// Should have 6 selected spans: 3 for at deltas + 3 for sound (one per emit)
@@ -226,7 +226,7 @@ fn get_gains(outputs: &[String]) -> Vec<f64> {
#[test]
fn arp_auto_subdivide() {
let outputs = expect_outputs(r#"sine s c4 e4 g4 b4 arp note ."#, 4);
let outputs = expect_outputs(r#"sine snd c4 e4 g4 b4 arp note ."#, 4);
let notes = get_notes(&outputs);
assert!(approx_eq(notes[0], 60.0));
assert!(approx_eq(notes[1], 64.0));
@@ -242,7 +242,7 @@ fn arp_auto_subdivide() {
#[test]
fn arp_with_explicit_at() {
let outputs = expect_outputs(r#"0 0.25 0.5 0.75 at sine s c4 e4 g4 b4 arp note ."#, 4);
let outputs = expect_outputs(r#"0 0.25 0.5 0.75 at sine snd c4 e4 g4 b4 arp note ."#, 4);
let notes = get_notes(&outputs);
assert!(approx_eq(notes[0], 60.0));
assert!(approx_eq(notes[1], 64.0));
@@ -258,14 +258,14 @@ fn arp_with_explicit_at() {
#[test]
fn arp_single_note() {
let outputs = expect_outputs(r#"sine s c4 arp note ."#, 1);
let outputs = expect_outputs(r#"sine snd c4 arp note ."#, 1);
let notes = get_notes(&outputs);
assert!(approx_eq(notes[0], 60.0));
}
#[test]
fn arp_fewer_deltas_than_notes() {
let outputs = expect_outputs(r#"0 0.5 at sine s c4 e4 g4 b4 arp note ."#, 4);
let outputs = expect_outputs(r#"0 0.5 at sine snd c4 e4 g4 b4 arp note ."#, 4);
let notes = get_notes(&outputs);
assert!(approx_eq(notes[0], 60.0));
assert!(approx_eq(notes[1], 64.0));
@@ -281,7 +281,7 @@ fn arp_fewer_deltas_than_notes() {
#[test]
fn arp_fewer_notes_than_deltas() {
let outputs = expect_outputs(r#"0 0.25 0.5 0.75 at sine s c4 e4 arp note ."#, 4);
let outputs = expect_outputs(r#"0 0.25 0.5 0.75 at sine snd c4 e4 arp note ."#, 4);
let notes = get_notes(&outputs);
assert!(approx_eq(notes[0], 60.0));
assert!(approx_eq(notes[1], 64.0));
@@ -291,7 +291,7 @@ fn arp_fewer_notes_than_deltas() {
#[test]
fn arp_multiple_params() {
let outputs = expect_outputs(r#"sine s c4 e4 g4 arp note 0.5 0.7 0.9 arp gain ."#, 3);
let outputs = expect_outputs(r#"sine snd c4 e4 g4 arp note 0.5 0.7 0.9 arp gain ."#, 3);
let notes = get_notes(&outputs);
assert!(approx_eq(notes[0], 60.0));
assert!(approx_eq(notes[1], 64.0));
@@ -305,7 +305,7 @@ fn arp_multiple_params() {
#[test]
fn arp_no_arp_unchanged() {
// Standard CycleList without arp → cross-product (backward compat)
let outputs = expect_outputs(r#"0 0.5 at sine s c4 e4 note ."#, 4);
let outputs = expect_outputs(r#"0 0.5 at sine snd c4 e4 note ."#, 4);
let notes = get_notes(&outputs);
// Cross-product: each note at each delta
assert!(approx_eq(notes[0], 60.0));
@@ -318,7 +318,7 @@ fn arp_no_arp_unchanged() {
fn arp_mixed_cycle_and_arp() {
// CycleList sound (2) + ArpList note (3) → 3 arp × 2 poly = 6 voices
// Each arp step plays both sine and saw simultaneously (poly stacking)
let outputs = expect_outputs(r#"sine saw s c4 e4 g4 arp note ."#, 6);
let outputs = expect_outputs(r#"sine saw snd c4 e4 g4 arp note ."#, 6);
let sounds = get_sounds(&outputs);
// Arp step 0: poly 0=sine, poly 1=saw
assert_eq!(sounds[0], "sine");
@@ -346,7 +346,7 @@ fn every_offset_fires_at_offset() {
for iter in 0..8 {
let ctx = ctx_with(|c| c.iter = iter);
let f = forth();
let outputs = f.evaluate(r#""kick" s ( . ) 4 2 every+"#, &ctx).unwrap();
let outputs = f.evaluate(r#""kick" snd ( . ) 4 2 every+"#, &ctx).unwrap();
if iter % 4 == 2 {
assert_eq!(outputs.len(), 1, "iter={}: should fire", iter);
} else {
@@ -361,7 +361,7 @@ fn every_offset_wraps_large_offset() {
for iter in 0..8 {
let ctx = ctx_with(|c| c.iter = iter);
let f = forth();
let outputs = f.evaluate(r#""kick" s ( . ) 4 6 every+"#, &ctx).unwrap();
let outputs = f.evaluate(r#""kick" snd ( . ) 4 6 every+"#, &ctx).unwrap();
if iter % 4 == 2 {
assert_eq!(outputs.len(), 1, "iter={}: should fire (wrapped offset)", iter);
} else {
@@ -375,7 +375,7 @@ fn except_offset_inverse() {
for iter in 0..8 {
let ctx = ctx_with(|c| c.iter = iter);
let f = forth();
let outputs = f.evaluate(r#""kick" s ( . ) 4 2 except+"#, &ctx).unwrap();
let outputs = f.evaluate(r#""kick" snd ( . ) 4 2 except+"#, &ctx).unwrap();
if iter % 4 != 2 {
assert_eq!(outputs.len(), 1, "iter={}: should fire", iter);
} else {
@@ -389,8 +389,8 @@ fn every_offset_zero_is_same_as_every() {
for iter in 0..8 {
let ctx = ctx_with(|c| c.iter = iter);
let f = forth();
let a = f.evaluate(r#""kick" s ( . ) 3 every"#, &ctx).unwrap();
let b = f.evaluate(r#""kick" s ( . ) 3 0 every+"#, &ctx).unwrap();
let a = f.evaluate(r#""kick" snd ( . ) 3 every"#, &ctx).unwrap();
let b = f.evaluate(r#""kick" snd ( . ) 3 0 every+"#, &ctx).unwrap();
assert_eq!(a.len(), b.len(), "iter={}: every and every+ 0 should match", iter);
}
}