big commit
This commit is contained in:
@@ -150,3 +150,53 @@ fn chain() {
|
||||
fn underflow() {
|
||||
expect_error("1 +", "stack underflow");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_int() {
|
||||
expect_int("2 3 pow", 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_float() {
|
||||
expect_float("2 0.5 pow", std::f64::consts::SQRT_2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqrt() {
|
||||
expect_int("16 sqrt", 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqrt_float() {
|
||||
expect_float("2 sqrt", std::f64::consts::SQRT_2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sin_zero() {
|
||||
expect_int("0 sin", 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sin_pi_half() {
|
||||
expect_float("3.14159265358979 2 / sin", 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cos_zero() {
|
||||
expect_int("0 cos", 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cos_pi() {
|
||||
expect_int("3.14159265358979 cos", -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn log_e() {
|
||||
expect_float("2.718281828459045 log", 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn log_one() {
|
||||
expect_int("1 log", 0);
|
||||
}
|
||||
|
||||
@@ -134,3 +134,83 @@ fn truthy_nonzero() {
|
||||
fn truthy_negative() {
|
||||
expect_int("-1 not", 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xor_tt() {
|
||||
expect_int("1 1 xor", 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xor_tf() {
|
||||
expect_int("1 0 xor", 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xor_ft() {
|
||||
expect_int("0 1 xor", 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xor_ff() {
|
||||
expect_int("0 0 xor", 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nand_tt() {
|
||||
expect_int("1 1 nand", 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nand_tf() {
|
||||
expect_int("1 0 nand", 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nand_ff() {
|
||||
expect_int("0 0 nand", 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nor_tt() {
|
||||
expect_int("1 1 nor", 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nor_tf() {
|
||||
expect_int("1 0 nor", 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nor_ff() {
|
||||
expect_int("0 0 nor", 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ifelse_true() {
|
||||
expect_int("{ 42 } { 99 } 1 ifelse", 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ifelse_false() {
|
||||
expect_int("{ 42 } { 99 } 0 ifelse", 99);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_first() {
|
||||
expect_int("{ 10 } { 20 } { 30 } 0 pick", 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_second() {
|
||||
expect_int("{ 10 } { 20 } { 30 } 1 pick", 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_third() {
|
||||
expect_int("{ 10 } { 20 } { 30 } 2 pick", 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pick_preserves_stack() {
|
||||
expect_int("5 { 10 } { 20 } 0 pick +", 15);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ fn redefine_word_overwrites() {
|
||||
|
||||
#[test]
|
||||
fn word_with_param() {
|
||||
let outputs = expect_outputs(": loud 0.9 gain ; \"kick\" s loud emit", 1);
|
||||
let outputs = expect_outputs(": loud 0.9 gain ; \"kick\" s loud .", 1);
|
||||
assert!(outputs[0].contains("gain/0.9"));
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ fn define_word_containing_quotation() {
|
||||
|
||||
#[test]
|
||||
fn define_word_with_sound() {
|
||||
let outputs = expect_outputs(": kick \"kick\" s emit ; kick", 1);
|
||||
let outputs = expect_outputs(": kick \"kick\" s . ; kick", 1);
|
||||
assert!(outputs[0].contains("sound/kick"));
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ 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",
|
||||
": myverb 0.5 verb ; \"sine\" s 440 freq < myverb > .",
|
||||
&ctx
|
||||
).unwrap();
|
||||
assert_eq!(outputs.len(), 1);
|
||||
|
||||
@@ -59,31 +59,31 @@ fn nested_quotations() {
|
||||
|
||||
#[test]
|
||||
fn quotation_with_param() {
|
||||
let outputs = expect_outputs(r#""kick" s { 2 distort } 1 ? emit"#, 1);
|
||||
let outputs = expect_outputs(r#""kick" s { 2 distort } 1 ? ."#, 1);
|
||||
assert!(outputs[0].contains("distort/2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quotation_skips_param() {
|
||||
let outputs = expect_outputs(r#""kick" s { 2 distort } 0 ? emit"#, 1);
|
||||
let outputs = expect_outputs(r#""kick" s { 2 distort } 0 ? ."#, 1);
|
||||
assert!(!outputs[0].contains("distort"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quotation_with_emit() {
|
||||
// When true, emit should fire
|
||||
let outputs = expect_outputs(r#""kick" s { emit } 1 ?"#, 1);
|
||||
// When true, . should fire
|
||||
let outputs = expect_outputs(r#""kick" s { . } 1 ?"#, 1);
|
||||
assert!(outputs[0].contains("kick"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quotation_skips_emit() {
|
||||
// When false, emit should not fire
|
||||
// When false, . should not fire
|
||||
let f = forth();
|
||||
let outputs = f
|
||||
.evaluate(r#""kick" s { emit } 0 ?"#, &default_ctx())
|
||||
.evaluate(r#""kick" s { . } 0 ?"#, &default_ctx())
|
||||
.unwrap();
|
||||
// No output since emit was skipped and no implicit emit
|
||||
// No output since . was skipped and no implicit emit
|
||||
assert_eq!(outputs.len(), 0);
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ fn every_with_quotation_integration() {
|
||||
let ctx = ctx_with(|c| c.iter = iter);
|
||||
let f = forth();
|
||||
let outputs = f
|
||||
.evaluate(r#""kick" s { 2 distort } 2 every ? emit"#, &ctx)
|
||||
.evaluate(r#""kick" s { 2 distort } 2 every ? ."#, &ctx)
|
||||
.unwrap();
|
||||
if iter % 2 == 0 {
|
||||
assert!(
|
||||
@@ -163,7 +163,7 @@ fn when_and_unless_complementary() {
|
||||
let f = forth();
|
||||
let outputs = f
|
||||
.evaluate(
|
||||
r#""kick" s { 2 distort } 2 every ? { 4 distort } 2 every !? emit"#,
|
||||
r#""kick" s { 2 distort } 2 every ? { 4 distort } 2 every !? ."#,
|
||||
&ctx,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -130,64 +130,64 @@ fn ramp_with_range() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_deterministic() {
|
||||
fn perlin_deterministic() {
|
||||
let ctx = ctx_with(|c| c.beat = 2.7);
|
||||
let f = forth();
|
||||
f.evaluate("1.0 noise", &ctx).unwrap();
|
||||
f.evaluate("1.0 perlin", &ctx).unwrap();
|
||||
let val1 = stack_float(&f);
|
||||
f.evaluate("1.0 noise", &ctx).unwrap();
|
||||
f.evaluate("1.0 perlin", &ctx).unwrap();
|
||||
let val2 = stack_float(&f);
|
||||
assert!((val1 - val2).abs() < 1e-9, "noise should be deterministic");
|
||||
assert!((val1 - val2).abs() < 1e-9, "perlin should be deterministic");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_in_range() {
|
||||
fn perlin_in_range() {
|
||||
for i in 0..100 {
|
||||
let ctx = ctx_with(|c| c.beat = i as f64 * 0.1);
|
||||
let f = forth();
|
||||
f.evaluate("1.0 noise", &ctx).unwrap();
|
||||
f.evaluate("1.0 perlin", &ctx).unwrap();
|
||||
let val = stack_float(&f);
|
||||
assert!(val >= 0.0 && val <= 1.0, "noise out of range: {}", val);
|
||||
assert!(val >= 0.0 && val <= 1.0, "perlin out of range: {}", val);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_varies() {
|
||||
fn perlin_varies() {
|
||||
let ctx1 = ctx_with(|c| c.beat = 0.5);
|
||||
let ctx2 = ctx_with(|c| c.beat = 1.5);
|
||||
let f = forth();
|
||||
f.evaluate("1.0 noise", &ctx1).unwrap();
|
||||
f.evaluate("1.0 perlin", &ctx1).unwrap();
|
||||
let val1 = stack_float(&f);
|
||||
f.evaluate("1.0 noise", &ctx2).unwrap();
|
||||
f.evaluate("1.0 perlin", &ctx2).unwrap();
|
||||
let val2 = stack_float(&f);
|
||||
assert!((val1 - val2).abs() > 1e-9, "noise should vary with beat");
|
||||
assert!((val1 - val2).abs() > 1e-9, "perlin should vary with beat");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_smooth() {
|
||||
fn perlin_smooth() {
|
||||
let f = forth();
|
||||
let mut prev = 0.0;
|
||||
for i in 0..100 {
|
||||
let ctx = ctx_with(|c| c.beat = i as f64 * 0.01);
|
||||
f.evaluate("1.0 noise", &ctx).unwrap();
|
||||
f.evaluate("1.0 perlin", &ctx).unwrap();
|
||||
let val = stack_float(&f);
|
||||
if i > 0 {
|
||||
assert!((val - prev).abs() < 0.2, "noise not smooth: jump {} at step {}", (val - prev).abs(), i);
|
||||
assert!((val - prev).abs() < 0.2, "perlin not smooth: jump {} at step {}", (val - prev).abs(), i);
|
||||
}
|
||||
prev = val;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_with_range() {
|
||||
fn perlin_with_range() {
|
||||
let ctx = ctx_with(|c| c.beat = 1.3);
|
||||
let f = forth();
|
||||
f.evaluate("1.0 noise 200.0 800.0 range", &ctx).unwrap();
|
||||
f.evaluate("1.0 perlin 200.0 800.0 range", &ctx).unwrap();
|
||||
let val = stack_float(&f);
|
||||
assert!(val >= 200.0 && val <= 800.0, "noise+range out of bounds: {}", val);
|
||||
assert!(val >= 200.0 && val <= 800.0, "perlin+range out of bounds: {}", val);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noise_underflow() {
|
||||
expect_error("noise", "stack underflow");
|
||||
fn perlin_underflow() {
|
||||
expect_error("perlin", "stack underflow");
|
||||
}
|
||||
|
||||
@@ -2,19 +2,19 @@ use super::harness::*;
|
||||
|
||||
#[test]
|
||||
fn basic_emit() {
|
||||
let outputs = expect_outputs(r#""kick" sound emit"#, 1);
|
||||
let outputs = expect_outputs(r#""kick" sound ."#, 1);
|
||||
assert!(outputs[0].contains("sound/kick"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_s() {
|
||||
let outputs = expect_outputs(r#""snare" s emit"#, 1);
|
||||
let outputs = expect_outputs(r#""snare" s ."#, 1);
|
||||
assert!(outputs[0].contains("sound/snare"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_params() {
|
||||
let outputs = expect_outputs(r#""kick" s 440 freq 0.5 gain emit"#, 1);
|
||||
let outputs = expect_outputs(r#""kick" s 440 freq 0.5 gain ."#, 1);
|
||||
assert!(outputs[0].contains("sound/kick"));
|
||||
assert!(outputs[0].contains("freq/440"));
|
||||
assert!(outputs[0].contains("gain/0.5"));
|
||||
@@ -22,24 +22,24 @@ fn with_params() {
|
||||
|
||||
#[test]
|
||||
fn auto_dur() {
|
||||
let outputs = expect_outputs(r#""kick" s emit"#, 1);
|
||||
let outputs = expect_outputs(r#""kick" s ."#, 1);
|
||||
assert!(outputs[0].contains("dur/"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_delaytime() {
|
||||
let outputs = expect_outputs(r#""kick" s emit"#, 1);
|
||||
let outputs = expect_outputs(r#""kick" s ."#, 1);
|
||||
assert!(outputs[0].contains("delaytime/"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emit_no_sound() {
|
||||
expect_error("emit", "no sound set");
|
||||
expect_error(".", "no sound set");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_emits() {
|
||||
let outputs = expect_outputs(r#""kick" s emit "snare" s emit"#, 2);
|
||||
let outputs = expect_outputs(r#""kick" s . "snare" s ."#, 2);
|
||||
assert!(outputs[0].contains("sound/kick"));
|
||||
assert!(outputs[1].contains("sound/snare"));
|
||||
}
|
||||
@@ -47,7 +47,7 @@ fn multiple_emits() {
|
||||
#[test]
|
||||
fn envelope_params() {
|
||||
let outputs = expect_outputs(
|
||||
r#""synth" s 0.01 attack 0.1 decay 0.7 sustain 0.3 release emit"#,
|
||||
r#""synth" s 0.01 attack 0.1 decay 0.7 sustain 0.3 release ."#,
|
||||
1,
|
||||
);
|
||||
assert!(outputs[0].contains("attack/0.01"));
|
||||
@@ -58,14 +58,14 @@ fn envelope_params() {
|
||||
|
||||
#[test]
|
||||
fn filter_params() {
|
||||
let outputs = expect_outputs(r#""synth" s 2000 lpf 0.5 lpq emit"#, 1);
|
||||
let outputs = expect_outputs(r#""synth" s 2000 lpf 0.5 lpq ."#, 1);
|
||||
assert!(outputs[0].contains("lpf/2000"));
|
||||
assert!(outputs[0].contains("lpq/0.5"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adsr_sets_all_envelope_params() {
|
||||
let outputs = expect_outputs(r#""synth" s 0.01 0.1 0.5 0.3 adsr emit"#, 1);
|
||||
let outputs = expect_outputs(r#""synth" s 0.01 0.1 0.5 0.3 adsr ."#, 1);
|
||||
assert!(outputs[0].contains("attack/0.01"));
|
||||
assert!(outputs[0].contains("decay/0.1"));
|
||||
assert!(outputs[0].contains("sustain/0.5"));
|
||||
@@ -74,7 +74,7 @@ fn adsr_sets_all_envelope_params() {
|
||||
|
||||
#[test]
|
||||
fn ad_sets_attack_decay_sustain_zero() {
|
||||
let outputs = expect_outputs(r#""synth" s 0.01 0.1 ad emit"#, 1);
|
||||
let outputs = expect_outputs(r#""synth" s 0.01 0.1 ad ."#, 1);
|
||||
assert!(outputs[0].contains("attack/0.01"));
|
||||
assert!(outputs[0].contains("decay/0.1"));
|
||||
assert!(outputs[0].contains("sustain/0"));
|
||||
@@ -82,7 +82,7 @@ fn ad_sets_attack_decay_sustain_zero() {
|
||||
|
||||
#[test]
|
||||
fn bank_param() {
|
||||
let outputs = expect_outputs(r#""loop" s "a" bank emit"#, 1);
|
||||
let outputs = expect_outputs(r#""loop" s "a" bank ."#, 1);
|
||||
assert!(outputs[0].contains("sound/loop"));
|
||||
assert!(outputs[0].contains("bank/a"));
|
||||
}
|
||||
|
||||
@@ -35,11 +35,6 @@ fn dupn_underflow() {
|
||||
expect_error("3 dupn", "stack underflow");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bang_alias() {
|
||||
expect_stack("c4 3 !", &[int(60), int(60), int(60)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop() {
|
||||
expect_stack("1 2 drop", &[int(1)]);
|
||||
|
||||
@@ -61,14 +61,14 @@ fn stepdur_baseline() {
|
||||
|
||||
#[test]
|
||||
fn single_emit() {
|
||||
let outputs = expect_outputs(r#""kick" s @"#, 1);
|
||||
let outputs = expect_outputs(r#""kick" s ."#, 1);
|
||||
let deltas = get_deltas(&outputs);
|
||||
assert!(approx_eq(deltas[0], 0.0), "single emit at start should have delta 0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn implicit_subdivision_2() {
|
||||
let outputs = expect_outputs(r#""kick" s @ @"#, 2);
|
||||
let outputs = expect_outputs(r#""kick" s . ."#, 2);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let step = 0.5 / 2.0;
|
||||
assert!(approx_eq(deltas[0], 0.0), "first slot at 0");
|
||||
@@ -77,7 +77,7 @@ fn implicit_subdivision_2() {
|
||||
|
||||
#[test]
|
||||
fn implicit_subdivision_4() {
|
||||
let outputs = expect_outputs(r#""kick" s @ @ @ @"#, 4);
|
||||
let outputs = expect_outputs(r#""kick" s . . . ."#, 4);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let step = 0.5 / 4.0;
|
||||
for (i, delta) in deltas.iter().enumerate() {
|
||||
@@ -92,7 +92,7 @@ fn implicit_subdivision_4() {
|
||||
|
||||
#[test]
|
||||
fn implicit_subdivision_3() {
|
||||
let outputs = expect_outputs(r#""kick" s @ @ @"#, 3);
|
||||
let outputs = expect_outputs(r#""kick" s . . ."#, 3);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let step = 0.5 / 3.0;
|
||||
assert!(approx_eq(deltas[0], 0.0));
|
||||
@@ -102,7 +102,7 @@ fn implicit_subdivision_3() {
|
||||
|
||||
#[test]
|
||||
fn silence_creates_gap() {
|
||||
let outputs = expect_outputs(r#""kick" s @ ~ @"#, 2);
|
||||
let outputs = expect_outputs(r#""kick" s . _ ."#, 2);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let step = 0.5 / 3.0;
|
||||
assert!(approx_eq(deltas[0], 0.0), "first at 0");
|
||||
@@ -116,7 +116,7 @@ fn silence_creates_gap() {
|
||||
|
||||
#[test]
|
||||
fn silence_at_start() {
|
||||
let outputs = expect_outputs(r#""kick" s ~ @"#, 1);
|
||||
let outputs = expect_outputs(r#""kick" s _ ."#, 1);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let step = 0.5 / 2.0;
|
||||
assert!(
|
||||
@@ -129,13 +129,13 @@ fn silence_at_start() {
|
||||
|
||||
#[test]
|
||||
fn silence_only() {
|
||||
let outputs = expect_outputs(r#""kick" s ~"#, 0);
|
||||
let outputs = expect_outputs(r#""kick" s _"#, 0);
|
||||
assert!(outputs.is_empty(), "silence only should produce no output");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sound_persists() {
|
||||
let outputs = expect_outputs(r#""kick" s @ @ "hat" s @ @"#, 4);
|
||||
let outputs = expect_outputs(r#""kick" s . . "hat" s . ."#, 4);
|
||||
let sounds = get_sounds(&outputs);
|
||||
assert_eq!(sounds[0], "kick");
|
||||
assert_eq!(sounds[1], "kick");
|
||||
@@ -145,14 +145,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" s . "snare" s . "kick" s . "snare" s ."#, 4);
|
||||
let sounds = get_sounds(&outputs);
|
||||
assert_eq!(sounds, vec!["kick", "snare", "kick", "snare"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dur_matches_slot_duration() {
|
||||
let outputs = expect_outputs(r#""kick" s @ @ @ @"#, 4);
|
||||
let outputs = expect_outputs(r#""kick" s . . . ."#, 4);
|
||||
let durs = get_durs(&outputs);
|
||||
let expected_dur = 0.5 / 4.0;
|
||||
for (i, dur) in durs.iter().enumerate() {
|
||||
@@ -168,7 +168,7 @@ fn dur_matches_slot_duration() {
|
||||
fn tempo_affects_subdivision() {
|
||||
let ctx = ctx_with(|c| c.tempo = 60.0);
|
||||
let f = forth();
|
||||
let outputs = f.evaluate(r#""kick" s @ @"#, &ctx).unwrap();
|
||||
let outputs = f.evaluate(r#""kick" s . ."#, &ctx).unwrap();
|
||||
let deltas = get_deltas(&outputs);
|
||||
// At 60 BPM: stepdur = 0.25, root dur = 1.0
|
||||
let step = 1.0 / 2.0;
|
||||
@@ -180,7 +180,7 @@ fn tempo_affects_subdivision() {
|
||||
fn speed_affects_subdivision() {
|
||||
let ctx = ctx_with(|c| c.speed = 2.0);
|
||||
let f = forth();
|
||||
let outputs = f.evaluate(r#""kick" s @ @"#, &ctx).unwrap();
|
||||
let outputs = f.evaluate(r#""kick" s . ."#, &ctx).unwrap();
|
||||
let deltas = get_deltas(&outputs);
|
||||
// At speed 2.0: stepdur = 0.0625, root dur = 0.25
|
||||
let step = 0.25 / 2.0;
|
||||
@@ -193,11 +193,11 @@ fn cycle_picks_by_step() {
|
||||
for runs in 0..4 {
|
||||
let ctx = ctx_with(|c| c.runs = runs);
|
||||
let f = forth();
|
||||
let outputs = f.evaluate(r#""kick" s < @ ~ >"#, &ctx).unwrap();
|
||||
let outputs = f.evaluate(r#""kick" s < . _ >"#, &ctx).unwrap();
|
||||
if runs % 2 == 0 {
|
||||
assert_eq!(outputs.len(), 1, "runs={}: @ should be picked", runs);
|
||||
assert_eq!(outputs.len(), 1, "runs={}: . should be picked", runs);
|
||||
} else {
|
||||
assert_eq!(outputs.len(), 0, "runs={}: ~ should be picked", runs);
|
||||
assert_eq!(outputs.len(), 0, "runs={}: _ should be picked", runs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,11 +207,11 @@ fn pcycle_picks_by_pattern() {
|
||||
for iter in 0..4 {
|
||||
let ctx = ctx_with(|c| c.iter = iter);
|
||||
let f = forth();
|
||||
let outputs = f.evaluate(r#""kick" s << @ ~ >>"#, &ctx).unwrap();
|
||||
let outputs = f.evaluate(r#""kick" s << . _ >>"#, &ctx).unwrap();
|
||||
if iter % 2 == 0 {
|
||||
assert_eq!(outputs.len(), 1, "iter={}: @ should be picked", iter);
|
||||
assert_eq!(outputs.len(), 1, "iter={}: . should be picked", iter);
|
||||
} else {
|
||||
assert_eq!(outputs.len(), 0, "iter={}: ~ should be picked", iter);
|
||||
assert_eq!(outputs.len(), 0, "iter={}: _ should be picked", iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +221,7 @@ fn cycle_with_sounds() {
|
||||
for runs in 0..3 {
|
||||
let ctx = ctx_with(|c| c.runs = runs);
|
||||
let f = forth();
|
||||
let outputs = f.evaluate(r#"< { "kick" s @ } { "hat" s @ } { "snare" s @ } >"#, &ctx).unwrap();
|
||||
let outputs = f.evaluate(r#"< { "kick" s . } { "hat" s . } { "snare" s . } >"#, &ctx).unwrap();
|
||||
assert_eq!(outputs.len(), 1, "runs={}: expected 1 output", runs);
|
||||
let sounds = get_sounds(&outputs);
|
||||
let expected = ["kick", "hat", "snare"][runs % 3];
|
||||
@@ -238,7 +238,7 @@ fn dot_alias_for_emit() {
|
||||
|
||||
#[test]
|
||||
fn dot_with_silence() {
|
||||
let outputs = expect_outputs(r#""kick" s . ~ . ~"#, 2);
|
||||
let outputs = expect_outputs(r#""kick" s . _ . _"#, 2);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let step = 0.5 / 4.0;
|
||||
assert!(approx_eq(deltas[0], 0.0));
|
||||
@@ -292,7 +292,7 @@ fn internal_alternation_empty_error() {
|
||||
|
||||
#[test]
|
||||
fn div_basic_subdivision() {
|
||||
let outputs = expect_outputs(r#"div "kick" s . "hat" s . end"#, 2);
|
||||
let outputs = expect_outputs(r#"div "kick" s . "hat" s . ~"#, 2);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let sounds = get_sounds(&outputs);
|
||||
assert_eq!(sounds, vec!["kick", "hat"]);
|
||||
@@ -301,59 +301,58 @@ fn div_basic_subdivision() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_superposition() {
|
||||
let outputs = expect_outputs(r#"div "kick" s . end div "hat" s . end"#, 2);
|
||||
fn div_sequential() {
|
||||
// Two consecutive divs each claim a slot in root, so they're sequential
|
||||
let outputs = expect_outputs(r#"div "kick" s . ~ div "hat" s . ~"#, 2);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let sounds = get_sounds(&outputs);
|
||||
assert_eq!(sounds.len(), 2);
|
||||
// Both at delta 0 (superposed)
|
||||
assert_eq!(sounds, vec!["kick", "hat"]);
|
||||
assert!(approx_eq(deltas[0], 0.0));
|
||||
assert!(approx_eq(deltas[1], 0.0));
|
||||
assert!(approx_eq(deltas[1], 0.25), "second div at slot 1, got {}", deltas[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_with_root_emit() {
|
||||
// kick at root level, hat in div - both should superpose at 0
|
||||
// Note: div resolves first (when end is hit), root resolves at script end
|
||||
let outputs = expect_outputs(r#""kick" s . div "hat" s . end"#, 2);
|
||||
// kick claims slot 0 at root, div claims slot 1 at root
|
||||
let outputs = expect_outputs(r#""kick" s . div "hat" s . ~"#, 2);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let sounds = get_sounds(&outputs);
|
||||
// Order is hat then kick because div resolves before root
|
||||
assert_eq!(sounds, vec!["hat", "kick"]);
|
||||
assert!(approx_eq(deltas[0], 0.0));
|
||||
assert!(approx_eq(deltas[1], 0.0));
|
||||
assert_eq!(sounds, vec!["kick", "hat"]);
|
||||
assert!(approx_eq(deltas[0], 0.0), "kick at slot 0");
|
||||
assert!(approx_eq(deltas[1], 0.25), "hat at slot 1, got {}", deltas[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_nested() {
|
||||
// kick takes first slot in outer div, inner div takes second slot
|
||||
// Inner div resolves first, then outer div resolves
|
||||
let outputs = expect_outputs(r#"div "kick" s . div "hat" s . . end end"#, 3);
|
||||
// kick claims slot 0 in outer div, inner div claims slot 1
|
||||
// Inner div's 2 hats subdivide its slot (0.25 duration) into 2 sub-slots
|
||||
let outputs = expect_outputs(r#"div "kick" s . div "hat" s . . ~ ~"#, 3);
|
||||
let sounds = get_sounds(&outputs);
|
||||
let deltas = get_deltas(&outputs);
|
||||
// Inner div resolves first (hat, hat), then outer div (kick)
|
||||
assert_eq!(sounds[0], "hat");
|
||||
// Output order: kick (slot 0), then hats (slot 1 subdivided)
|
||||
assert_eq!(sounds[0], "kick");
|
||||
assert_eq!(sounds[1], "hat");
|
||||
assert_eq!(sounds[2], "kick");
|
||||
// Inner div inherits parent's start (0) and duration (0.5), subdivides into 2
|
||||
assert!(approx_eq(deltas[0], 0.0), "first hat at 0, got {}", deltas[0]);
|
||||
assert!(approx_eq(deltas[1], 0.25), "second hat at 0.25, got {}", deltas[1]);
|
||||
// Outer div has 2 slots: kick at 0, inner div at slot 1 (but inner resolved independently)
|
||||
assert!(approx_eq(deltas[2], 0.0), "kick at 0, got {}", deltas[2]);
|
||||
assert_eq!(sounds[2], "hat");
|
||||
// Outer div has 2 slots of 0.25 each
|
||||
// kick at slot 0 -> delta 0
|
||||
// inner div at slot 1 -> starts at 0.25, subdivided into 2 -> hats at 0.25 and 0.375
|
||||
assert!(approx_eq(deltas[0], 0.0), "kick at 0, got {}", deltas[0]);
|
||||
assert!(approx_eq(deltas[1], 0.25), "first hat at 0.25, got {}", deltas[1]);
|
||||
assert!(approx_eq(deltas[2], 0.375), "second hat at 0.375, got {}", deltas[2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_with_silence() {
|
||||
let outputs = expect_outputs(r#"div "kick" s . ~ end"#, 1);
|
||||
let outputs = expect_outputs(r#"div "kick" s . _ ~"#, 1);
|
||||
let deltas = get_deltas(&outputs);
|
||||
assert!(approx_eq(deltas[0], 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_unmatched_end_error() {
|
||||
fn unmatched_scope_terminator_error() {
|
||||
let f = forth();
|
||||
let result = f.evaluate(r#""kick" s . end"#, &default_ctx());
|
||||
assert!(result.is_err(), "unmatched end should error");
|
||||
let result = f.evaluate(r#""kick" s . ~"#, &default_ctx());
|
||||
assert!(result.is_err(), "unmatched ~ should error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -392,7 +391,7 @@ fn alternator_with_arithmetic() {
|
||||
|
||||
#[test]
|
||||
fn stack_superposes_sounds() {
|
||||
let outputs = expect_outputs(r#"stack "kick" s . "hat" s . end"#, 2);
|
||||
let outputs = expect_outputs(r#"stack "kick" s . "hat" s . ~"#, 2);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let sounds = get_sounds(&outputs);
|
||||
assert_eq!(sounds.len(), 2);
|
||||
@@ -403,7 +402,7 @@ fn stack_superposes_sounds() {
|
||||
|
||||
#[test]
|
||||
fn stack_with_multiple_emits() {
|
||||
let outputs = expect_outputs(r#"stack "kick" s . . . . end"#, 4);
|
||||
let outputs = expect_outputs(r#"stack "kick" s . . . . ~"#, 4);
|
||||
let deltas = get_deltas(&outputs);
|
||||
// All 4 kicks at delta 0
|
||||
for (i, delta) in deltas.iter().enumerate() {
|
||||
@@ -415,7 +414,7 @@ fn stack_with_multiple_emits() {
|
||||
fn stack_inside_div() {
|
||||
// div subdivides, stack inside superposes
|
||||
// stack doesn't claim a slot in parent div, so snare is also at 0
|
||||
let outputs = expect_outputs(r#"div stack "kick" s . "hat" s . end "snare" s . end"#, 3);
|
||||
let outputs = expect_outputs(r#"div stack "kick" s . "hat" s . ~ "snare" s . ~"#, 3);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let sounds = get_sounds(&outputs);
|
||||
// stack resolves first (kick, hat at 0), then div resolves (snare at 0)
|
||||
@@ -429,20 +428,21 @@ fn stack_inside_div() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_then_stack_sequential() {
|
||||
// Nested div doesn't claim a slot in parent, only emit/silence do
|
||||
// So nested div and snare both resolve with parent's timing
|
||||
let outputs = expect_outputs(r#"div div "kick" s . "hat" s . end "snare" s . end"#, 3);
|
||||
fn div_nested_with_sibling() {
|
||||
// Inner div claims slot 0, snare claims slot 1
|
||||
// Inner div's kick/hat subdivide slot 0
|
||||
let outputs = expect_outputs(r#"div div "kick" s . "hat" s . ~ "snare" s . ~"#, 3);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let sounds = get_sounds(&outputs);
|
||||
// Inner div resolves first (kick at 0, hat at 0.25 of parent duration)
|
||||
// Outer div has 1 slot (snare's .), so snare at 0
|
||||
// Outer div has 2 slots of 0.25 each
|
||||
// Inner div at slot 0: kick at 0, hat at 0.125
|
||||
// snare at slot 1: delta 0.25
|
||||
assert_eq!(sounds[0], "kick");
|
||||
assert_eq!(sounds[1], "hat");
|
||||
assert_eq!(sounds[2], "snare");
|
||||
assert!(approx_eq(deltas[0], 0.0));
|
||||
assert!(approx_eq(deltas[1], 0.25), "hat at 0.25, got {}", deltas[1]);
|
||||
assert!(approx_eq(deltas[2], 0.0), "snare at 0, got {}", deltas[2]);
|
||||
assert!(approx_eq(deltas[0], 0.0), "kick at 0, got {}", deltas[0]);
|
||||
assert!(approx_eq(deltas[1], 0.125), "hat at 0.125, got {}", deltas[1]);
|
||||
assert!(approx_eq(deltas[2], 0.25), "snare at 0.25, got {}", deltas[2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user