257 lines
7.4 KiB
Rust
257 lines
7.4 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()
|
|
}
|
|
|
|
fn get_durs(outputs: &[String]) -> Vec<f64> {
|
|
outputs
|
|
.iter()
|
|
.map(|o| parse_params(o).get("dur").copied().unwrap_or(0.0))
|
|
.collect()
|
|
}
|
|
|
|
fn get_notes(outputs: &[String]) -> Vec<f64> {
|
|
outputs
|
|
.iter()
|
|
.map(|o| parse_params(o).get("note").copied().unwrap_or(0.0))
|
|
.collect()
|
|
}
|
|
|
|
fn get_sounds(outputs: &[String]) -> Vec<String> {
|
|
outputs
|
|
.iter()
|
|
.map(|o| {
|
|
let parts: Vec<&str> = o.trim_start_matches('/').split('/').collect();
|
|
for i in (0..parts.len()).step_by(2) {
|
|
if parts[i] == "sound" && i + 1 < parts.len() {
|
|
return parts[i + 1].to_string();
|
|
}
|
|
}
|
|
String::new()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
const EPSILON: f64 = 1e-9;
|
|
|
|
fn approx_eq(a: f64, b: f64) -> bool {
|
|
(a - b).abs() < EPSILON
|
|
}
|
|
|
|
#[test]
|
|
fn stack_creates_subdivisions_at_same_time() {
|
|
let outputs = expect_outputs(r#""kick" s 3 stack each"#, 3);
|
|
let deltas = get_deltas(&outputs);
|
|
assert!(approx_eq(deltas[0], 0.0));
|
|
assert!(approx_eq(deltas[1], 0.0));
|
|
assert!(approx_eq(deltas[2], 0.0));
|
|
}
|
|
|
|
#[test]
|
|
fn stack_vs_div_timing() {
|
|
let stack_outputs = expect_outputs(r#""kick" s 3 stack each"#, 3);
|
|
let div_outputs = expect_outputs(r#""kick" s 3 div each"#, 3);
|
|
|
|
let stack_deltas = get_deltas(&stack_outputs);
|
|
let div_deltas = get_deltas(&div_outputs);
|
|
|
|
for d in stack_deltas {
|
|
assert!(approx_eq(d, 0.0), "stack should have all delta=0");
|
|
}
|
|
|
|
assert!(approx_eq(div_deltas[0], 0.0));
|
|
assert!(!approx_eq(div_deltas[1], 0.0), "div should spread in time");
|
|
assert!(!approx_eq(div_deltas[2], 0.0), "div should spread in time");
|
|
}
|
|
|
|
#[test]
|
|
fn for_with_div_arpeggio() {
|
|
let outputs = expect_outputs(r#"{ "kick" s emit } 3 div for"#, 3);
|
|
let deltas = get_deltas(&outputs);
|
|
|
|
assert!(approx_eq(deltas[0], 0.0));
|
|
assert!(!approx_eq(deltas[1], 0.0));
|
|
assert!(!approx_eq(deltas[2], 0.0));
|
|
}
|
|
|
|
#[test]
|
|
fn for_with_stack_chord() {
|
|
let outputs = expect_outputs(r#"{ "kick" s emit } 3 stack for"#, 3);
|
|
let deltas = get_deltas(&outputs);
|
|
|
|
for d in deltas {
|
|
assert!(approx_eq(d, 0.0), "stack for should have all delta=0");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn local_cycle_with_for() {
|
|
let outputs = expect_outputs(r#"{ | 60 62 64 | note "sine" s emit } 3 div for"#, 3);
|
|
let notes = get_notes(&outputs);
|
|
|
|
assert!(approx_eq(notes[0], 60.0));
|
|
assert!(approx_eq(notes[1], 62.0));
|
|
assert!(approx_eq(notes[2], 64.0));
|
|
}
|
|
|
|
#[test]
|
|
fn local_cycle_wraps_around() {
|
|
let outputs = expect_outputs(r#"{ | 60 62 | note "sine" s emit } 4 div for"#, 4);
|
|
let notes = get_notes(&outputs);
|
|
|
|
assert!(approx_eq(notes[0], 60.0));
|
|
assert!(approx_eq(notes[1], 62.0));
|
|
assert!(approx_eq(notes[2], 60.0));
|
|
assert!(approx_eq(notes[3], 62.0));
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_local_cycles() {
|
|
let outputs =
|
|
expect_outputs(r#"{ | "bd" "sn" | s | 60 64 | note emit } 2 stack for"#, 2);
|
|
let sounds = get_sounds(&outputs);
|
|
let notes = get_notes(&outputs);
|
|
|
|
assert_eq!(sounds[0], "bd");
|
|
assert_eq!(sounds[1], "sn");
|
|
assert!(approx_eq(notes[0], 60.0));
|
|
assert!(approx_eq(notes[1], 64.0));
|
|
}
|
|
|
|
#[test]
|
|
fn local_cycle_outside_for_defaults_to_first() {
|
|
expect_int("| 60 62 64 |", 60);
|
|
}
|
|
|
|
#[test]
|
|
fn polymetric_cycles() {
|
|
let outputs = expect_outputs(
|
|
r#"{ | 0 1 | n | "a" "b" "c" | s emit } 6 div for"#,
|
|
6,
|
|
);
|
|
let sounds = get_sounds(&outputs);
|
|
|
|
assert_eq!(sounds[0], "a");
|
|
assert_eq!(sounds[1], "b");
|
|
assert_eq!(sounds[2], "c");
|
|
assert_eq!(sounds[3], "a");
|
|
assert_eq!(sounds[4], "b");
|
|
assert_eq!(sounds[5], "c");
|
|
}
|
|
|
|
#[test]
|
|
fn stack_error_zero_count() {
|
|
expect_error(r#""kick" s 0 stack each"#, "stack count must be > 0");
|
|
}
|
|
|
|
#[test]
|
|
fn for_requires_subdivide() {
|
|
expect_error(r#"{ "kick" s emit } for"#, "for requires subdivide first");
|
|
}
|
|
|
|
#[test]
|
|
fn for_requires_quotation() {
|
|
expect_error(r#"42 3 div for"#, "expected quotation");
|
|
}
|
|
|
|
#[test]
|
|
fn empty_local_cycle() {
|
|
expect_error("| |", "empty local cycle list");
|
|
}
|
|
|
|
// Echo tests - stutter effect with halving durations
|
|
|
|
#[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...
|
|
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 d2 = d1 / 2.0;
|
|
let d3 = d1 / 4.0;
|
|
|
|
assert!(approx_eq(durs[0], d1), "first dur should be {}, got {}", d1, durs[0]);
|
|
assert!(approx_eq(durs[1], d2), "second dur should be {}, got {}", d2, durs[1]);
|
|
assert!(approx_eq(durs[2], d3), "third dur should be {}, got {}", d3, durs[2]);
|
|
|
|
assert!(approx_eq(deltas[0], 0.0), "first delta should be 0");
|
|
assert!(approx_eq(deltas[1], d1), "second delta should be {}, got {}", d1, deltas[1]);
|
|
assert!(approx_eq(deltas[2], d1 + d2), "third delta should be {}, got {}", d1 + d2, deltas[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn echo_with_for() {
|
|
let outputs = expect_outputs(r#"{ "kick" s emit } 3 echo for"#, 3);
|
|
let durs = get_durs(&outputs);
|
|
|
|
// Each subsequent duration should be half the previous
|
|
assert!(approx_eq(durs[1], durs[0] / 2.0), "second should be half of first");
|
|
assert!(approx_eq(durs[2], durs[1] / 2.0), "third should be half of second");
|
|
}
|
|
|
|
#[test]
|
|
fn echo_error_zero_count() {
|
|
expect_error(r#""kick" s 0 echo each"#, "echo count must be > 0");
|
|
}
|
|
|
|
// Necho tests - reverse echo (durations grow)
|
|
|
|
#[test]
|
|
fn necho_creates_growing_subdivisions() {
|
|
// stepdur = 0.125, necho 3
|
|
// d1 + 2*d1 + 4*d1 = d1 * 7 = 0.125
|
|
// d1 = 0.125 / 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 d2 = d1 * 2.0;
|
|
let d3 = d1 * 4.0;
|
|
|
|
assert!(approx_eq(durs[0], d1), "first dur should be {}, got {}", d1, durs[0]);
|
|
assert!(approx_eq(durs[1], d2), "second dur should be {}, got {}", d2, durs[1]);
|
|
assert!(approx_eq(durs[2], d3), "third dur should be {}, got {}", d3, durs[2]);
|
|
|
|
assert!(approx_eq(deltas[0], 0.0), "first delta should be 0");
|
|
assert!(approx_eq(deltas[1], d1), "second delta should be {}, got {}", d1, deltas[1]);
|
|
assert!(approx_eq(deltas[2], d1 + d2), "third delta should be {}, got {}", d1 + d2, deltas[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn necho_with_for() {
|
|
let outputs = expect_outputs(r#"{ "kick" s emit } 3 necho for"#, 3);
|
|
let durs = get_durs(&outputs);
|
|
|
|
// Each subsequent duration should be double the previous
|
|
assert!(approx_eq(durs[1], durs[0] * 2.0), "second should be double first");
|
|
assert!(approx_eq(durs[2], durs[1] * 2.0), "third should be double second");
|
|
}
|
|
|
|
#[test]
|
|
fn necho_error_zero_count() {
|
|
expect_error(r#""kick" s 0 necho each"#, "necho count must be > 0");
|
|
}
|