Files
Cagire/tests/forth/temporal.rs

157 lines
4.3 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_sounds(outputs: &[String]) -> Vec<String> {
outputs
.iter()
.map(|o| {
let parts: Vec<&str> = o.trim_start_matches('/').split('/').collect();
if parts.len() >= 2 && parts[0] == "sound" {
parts[1].to_string()
} else {
String::new()
}
})
.collect()
}
const EPSILON: f64 = 1e-9;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < EPSILON
}
#[test]
fn stepdur_baseline() {
let f = run("stepdur");
assert!(approx_eq(stack_float(&f), 0.125));
}
#[test]
fn single_emit() {
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 multiple_emits_all_at_zero() {
let outputs = expect_outputs(r#""kick" s . . . ."#, 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);
}
}
#[test]
fn sound_persists() {
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");
assert_eq!(sounds[2], "hat");
assert_eq!(sounds[3], "hat");
}
#[test]
fn alternating_sounds() {
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_is_step_duration() {
let outputs = expect_outputs(r#""kick" s ."#, 1);
let durs = get_durs(&outputs);
assert!(approx_eq(durs[0], 0.125), "dur should be step_duration (0.125), got {}", durs[0]);
}
#[test]
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();
if runs % 2 == 0 {
assert_eq!(outputs.len(), 1, "runs={}: . should be picked", runs);
} else {
assert_eq!(outputs.len(), 0, "runs={}: _ should be picked", runs);
}
}
}
#[test]
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();
if iter % 2 == 0 {
assert_eq!(outputs.len(), 1, "iter={}: . should be picked", iter);
} else {
assert_eq!(outputs.len(), 0, "iter={}: _ should be picked", iter);
}
}
}
#[test]
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();
assert_eq!(outputs.len(), 1, "runs={}: expected 1 output", runs);
let sounds = get_sounds(&outputs);
let expected = ["kick", "hat", "snare"][runs % 3];
assert_eq!(sounds[0], expected, "runs={}: expected {}", runs, expected);
}
}
#[test]
fn emit_n_basic() {
let outputs = expect_outputs(r#""kick" s 4 .!"#, 4);
let sounds = get_sounds(&outputs);
assert_eq!(sounds, vec!["kick", "kick", "kick", "kick"]);
}
#[test]
fn emit_n_zero() {
let outputs = expect_outputs(r#""kick" s 0 .!"#, 0);
assert!(outputs.is_empty());
}
#[test]
fn emit_n_negative_error() {
let f = forth();
let result = f.evaluate(r#""kick" s -1 .!"#, &default_ctx());
assert!(result.is_err());
}