use std::sync::Arc;
use proptest::prelude::*;
use tono_core::dsl::{Adsr, SeqWave};
use tono_core::program::Program;
use tono_core::runtime::performance::{At, Command, Performance};
use tono_core::runtime::{AudioSource, Transport};
use tono_core::song::{CompileOptions, Song, note};
fn config() -> ProptestConfig {
ProptestConfig {
cases: 64,
failure_persistence: None,
..ProptestConfig::default()
}
}
fn amp() -> Adsr {
Adsr {
a: 0.005,
d: 0.1,
s: 0.8,
r: 0.2,
punch: 0.0,
}
}
const PITCHES: &[&str] = &["C2", "E2", "G2", "A2", "C3", "midi:36", "midi:38"];
fn arb_program() -> BoxedStrategy<Arc<Program>> {
(
proptest::sample::select(vec![100.0f32, 120.0, 140.0]),
1..=2usize,
1..=2u32,
proptest::collection::vec(
(0..16u32, 1..=8u32, proptest::sample::select(PITCHES)),
2..=5,
),
proptest::collection::vec(
(0..16u32, 1..=8u32, proptest::sample::select(PITCHES)),
1..=4,
),
)
.prop_map(|(bpm, n_tracks, bars, riff, stab)| {
let mut song = Song::new("fuzz-cmd", bpm);
song.add_track("t0", SeqWave::Square, amp());
if n_tracks == 2 {
song.add_track("t1", SeqWave::Bass, amp());
}
song.add_pattern(
"riff",
1,
riff.iter()
.map(|(s, l, p)| note(*s, *l, p))
.collect::<Vec<_>>(),
);
song.arrange_repeat("t0", "riff", 0, bars);
if n_tracks == 2 {
song.add_pattern(
"stab",
1,
stab.iter()
.map(|(s, l, p)| note(*s, *l, p))
.collect::<Vec<_>>(),
);
song.arrange_repeat("t1", "stab", 0, bars);
}
let options = CompileOptions {
sample_rate: Some(8_000),
..CompileOptions::default()
};
Arc::new(song.compile(&options).expect("the fuzz song compiles"))
})
.boxed()
}
fn arb_at() -> BoxedStrategy<At> {
prop_oneof![
2 => Just(At::Immediate),
3 => (0..=48_000u64).prop_map(At::Frame),
2 => (0.0f64..=24.0).prop_map(At::Beat),
2 => (0..=6u32).prop_map(At::Bar),
]
.boxed()
}
fn arb_command() -> BoxedStrategy<Command> {
prop_oneof![
2 => Just(Command::Play),
1 => Just(Command::Pause),
1 => Just(Command::Stop),
1 => (0..=6u32).prop_map(Command::SeekBar),
1 => (0.0f64..=24.0).prop_map(Command::SeekBeat),
1 => (0..=4u32, 0..=6u32).prop_map(|(a, b)| Command::SetLoopBars(a, b)),
1 => Just(Command::ClearLoop),
2 => (0.0f32..=2.0).prop_map(Command::SetGain),
]
.boxed()
}
fn arb_command_no_gain() -> BoxedStrategy<Command> {
prop_oneof![
2 => Just(Command::Play),
1 => Just(Command::Pause),
1 => Just(Command::Stop),
1 => (0..=6u32).prop_map(Command::SeekBar),
1 => (0.0f64..=24.0).prop_map(Command::SeekBeat),
1 => (0..=4u32, 0..=6u32).prop_map(|(a, b)| Command::SetLoopBars(a, b)),
1 => Just(Command::ClearLoop),
]
.boxed()
}
#[derive(Debug)]
struct Snapshot {
executed: u64,
depth: u64,
dropped: u64,
accepted: u64,
rejected: u64,
}
struct Run {
samples: Vec<f32>,
snapshots: Vec<Snapshot>,
}
fn run_script(
program: &Arc<Program>,
script: &[(Command, At)],
total_frames: usize,
block: usize,
schedule_up_front: bool,
) -> Run {
let mut p = Performance::new(program.clone());
let mut samples = Vec::with_capacity(total_frames * 2);
let mut snapshots = Vec::new();
let (mut accepted, mut rejected) = (0u64, 0u64);
let mut next = 0usize;
let batch = if schedule_up_front { script.len() } else { 3 };
while samples.len() < total_frames * 2 {
for _ in 0..batch {
if next >= script.len() {
break;
}
let (command, at) = &script[next];
next += 1;
match p.schedule(command.clone(), at.clone()) {
Ok(_) => accepted += 1,
Err(_) => rejected += 1,
}
}
let take = block.min(total_frames - samples.len() / 2);
let mut buf = vec![0.0f32; take * 2];
let filled = p.fill(&mut buf);
assert_eq!(filled, take, "fill must produce the requested frame count");
samples.extend_from_slice(&buf);
let m = p.metrics();
snapshots.push(Snapshot {
executed: m.commands_executed,
depth: p.queue_depth() as u64,
dropped: m.commands_dropped,
accepted,
rejected,
});
}
Run { samples, snapshots }
}
fn assert_metrics_contract(run: &Run) -> Result<(), TestCaseError> {
let mut prev: Option<&Snapshot> = None;
for snap in &run.snapshots {
prop_assert_eq!(
snap.executed + snap.depth,
snap.accepted,
"executed + queue depth must equal the accepted commands"
);
prop_assert_eq!(
snap.dropped,
snap.rejected,
"dropped must equal the rejected commands"
);
if let Some(prev) = prev {
prop_assert!(
snap.executed >= prev.executed && snap.dropped >= prev.dropped,
"counters went backwards: {prev:?} -> {snap:?}"
);
}
prev = Some(snap);
}
Ok(())
}
fn bits(samples: &[f32]) -> Vec<u32> {
samples.iter().map(|x| x.to_bits()).collect()
}
fn assert_finite(samples: &[f32]) -> Result<(), TestCaseError> {
prop_assert!(
samples.iter().all(|s| s.is_finite()),
"non-finite sample in the performance output"
);
Ok(())
}
const BLOCKS: &[usize] = &[1, 37, 256, 512, 1000, 2048];
proptest! {
#![proptest_config(config())]
#[test]
fn scripted_performance_is_deterministic(
program in arb_program(),
script in proptest::collection::vec((arb_command(), arb_at()), 1..=24),
total_frames in 6_000usize..=12_000,
block in proptest::sample::select(BLOCKS.to_vec()),
) {
let a = run_script(&program, &script, total_frames, block, false);
let b = run_script(&program, &script, total_frames, block, false);
assert_finite(&a.samples)?;
assert_eq!(
bits(&a.samples),
bits(&b.samples),
"the same script rendered twice diverged"
);
assert_metrics_contract(&a)?;
assert_metrics_contract(&b)?;
}
#[test]
fn gainless_scripts_are_block_size_independent(
program in arb_program(),
script in proptest::collection::vec((arb_command_no_gain(), arb_at()), 1..=16),
total_frames in 6_000usize..=10_000,
block_a in proptest::sample::select(BLOCKS.to_vec()),
block_b in proptest::sample::select(BLOCKS.to_vec()),
) {
let transport = Transport::for_program(&program.meta);
prop_assert!(transport.frame_at_beat(0.0) == 0);
let a = run_script(&program, &script, total_frames, block_a, true);
let b = run_script(&program, &script, total_frames, block_b, true);
assert_finite(&a.samples)?;
assert_finite(&b.samples)?;
assert_eq!(
bits(&a.samples),
bits(&b.samples),
"block sizes {block_a}/{block_b} diverged on a gainless script"
);
assert_metrics_contract(&a)?;
assert_metrics_contract(&b)?;
}
}