mod random_walk;
mod transforms;
mod generators;
mod builders;
pub use random_walk::{random_walk_sequence, biased_random_walk_sequence};
pub use builders::{TransformBuilder, GeneratorBuilder};
pub use transforms::EventMut;
#[cfg(test)]
mod tests {
use super::*;
use crate::composition::Composition;
use crate::composition::timing::Tempo;
use crate::consts::notes::*;
use crate::consts::scales::C4_MAJOR_SCALE;
use crate::track::AudioEvent;
#[test]
fn test_random_walk_sequence_generates_correct_length() {
let seq = random_walk_sequence(3, 16, 0, 7);
assert_eq!(seq.len(), 16);
}
#[test]
fn test_random_walk_sequence_stays_in_bounds() {
let seq = random_walk_sequence(5, 100, 0, 12);
for &val in &seq {
assert!(val < 12);
}
}
#[test]
fn test_random_walk_sequence_empty() {
let seq = random_walk_sequence(0, 0, 0, 10);
assert_eq!(seq.len(), 0);
}
#[test]
fn test_biased_random_walk_tends_upward() {
let seq = biased_random_walk_sequence(0, 50, 0, 20, 0.8);
let avg = seq.iter().sum::<u32>() as f32 / seq.len() as f32;
assert!(
avg > 5.0,
"Average {} should be > 5.0 with upward bias",
avg
);
}
#[test]
fn test_biased_random_walk_tends_downward() {
let seq = biased_random_walk_sequence(19, 50, 0, 20, 0.2);
let avg = seq.iter().sum::<u32>() as f32 / seq.len() as f32;
assert!(
avg < 15.0,
"Average {} should be < 15.0 with downward bias",
avg
);
}
#[test]
fn test_random_walk_sequence_with_sequence_from() {
let mut comp = Composition::new(Tempo::new(120.0));
let walk = random_walk_sequence(3, 16, 0, 7);
comp.track("walk")
.sequence_from(&walk, &C4_MAJOR_SCALE, 0.25);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks()[0].events.len(), 16);
}
#[test]
fn test_random_walk_generates_notes() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("walk")
.random_walk(C4, 16, 0.25, &C4_MAJOR_SCALE);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks()[0].events.len(), 16);
}
#[test]
fn test_random_walk_stays_in_scale() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("walk")
.random_walk(C4, 32, 0.25, &C4_MAJOR_SCALE);
let mixer = comp.into_mixer();
for event in &mixer.tracks()[0].events {
if let AudioEvent::Note(note) = event {
let freq = note.frequencies[0];
let in_scale = C4_MAJOR_SCALE
.iter()
.any(|&scale_note| (freq - scale_note).abs() < 0.1);
assert!(in_scale, "Generated note {} not in scale", freq);
}
}
}
#[test]
fn test_random_walk_empty_scale() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("walk").random_walk(C4, 16, 0.25, &[]);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_shift_transposes_up() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.5)
.shift(12);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C5).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[1] {
assert!((note.frequencies[0] - E5).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[2] {
assert!((note.frequencies[0] - G5).abs() < 0.1);
}
}
#[test]
fn test_shift_transposes_down() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.5)
.shift(-12);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C3).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[1] {
assert!((note.frequencies[0] - E3).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[2] {
assert!((note.frequencies[0] - G3).abs() < 0.1);
}
}
#[test]
fn test_shift_by_zero_no_change() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.5)
.shift(0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
}
#[test]
fn test_shift_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().shift(12);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_humanize_adds_variance() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, C4, C4, C4], 0.25)
.humanize(0.05, 0.2);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 4);
let mut has_variance = false;
for event in events {
if let AudioEvent::Note(note) = event {
let expected_times = [0.0, 0.25, 0.5, 0.75];
let time_exact = expected_times.iter().any(|&t| (note.start_time - t).abs() < 0.001);
if !time_exact || (note.velocity - 0.7).abs() > 0.01 {
has_variance = true;
break;
}
}
}
assert!(has_variance, "Humanize should add some variance");
}
#[test]
fn test_rotate_cycles_pitches() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4, C5], 0.25)
.rotate(1);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 4);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - E4).abs() < 0.1);
assert_eq!(note.start_time, 0.0); }
if let AudioEvent::Note(note) = &events[1] {
assert!((note.frequencies[0] - G4).abs() < 0.1);
assert_eq!(note.start_time, 0.25);
}
if let AudioEvent::Note(note) = &events[2] {
assert!((note.frequencies[0] - C5).abs() < 0.1);
assert_eq!(note.start_time, 0.5);
}
if let AudioEvent::Note(note) = &events[3] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
assert_eq!(note.start_time, 0.75);
}
}
#[test]
fn test_rotate_negative() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.rotate(-1);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - G4).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[1] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[2] {
assert!((note.frequencies[0] - E4).abs() < 0.1);
}
}
#[test]
fn test_rotate_by_zero() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.rotate(0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
}
#[test]
fn test_retrograde_reverses_pitches() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4, C5], 0.25)
.retrograde();
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 4);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C5).abs() < 0.1);
assert_eq!(note.start_time, 0.0); }
if let AudioEvent::Note(note) = &events[1] {
assert!((note.frequencies[0] - G4).abs() < 0.1);
assert_eq!(note.start_time, 0.25);
}
if let AudioEvent::Note(note) = &events[2] {
assert!((note.frequencies[0] - E4).abs() < 0.1);
assert_eq!(note.start_time, 0.5);
}
if let AudioEvent::Note(note) = &events[3] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
assert_eq!(note.start_time, 0.75);
}
}
#[test]
fn test_retrograde_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().retrograde();
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_mutate_changes_pitches() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4, C5], 0.25)
.mutate(2);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 4);
if let AudioEvent::Note(note) = &events[0] {
assert_eq!(note.start_time, 0.0);
}
if let AudioEvent::Note(note) = &events[1] {
assert_eq!(note.start_time, 0.25);
}
let original_freqs = [C4, E4, G4, C5];
let mut has_mutation = false;
for (i, event) in events.iter().enumerate() {
if let AudioEvent::Note(note) = event {
let diff = (note.frequencies[0] - original_freqs[i]).abs();
if diff > 0.1 {
has_mutation = true;
break;
}
}
}
assert!(has_mutation, "Mutate should change at least one note");
}
#[test]
fn test_mutate_by_zero() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.mutate(0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[1] {
assert!((note.frequencies[0] - E4).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[2] {
assert!((note.frequencies[0] - G4).abs() < 0.1);
}
}
#[test]
fn test_mutate_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().mutate(2);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_stack_octave_above() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4], 0.5)
.stack(12, 1);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 1);
if let AudioEvent::Note(note) = &events[0] {
assert_eq!(note.num_freqs, 2);
assert!((note.frequencies[0] - C4).abs() < 0.1);
assert!((note.frequencies[1] - C5).abs() < 0.1);
}
}
#[test]
fn test_stack_two_octaves() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4], 0.5)
.stack(12, 2);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 1);
if let AudioEvent::Note(note) = &events[0] {
assert_eq!(note.num_freqs, 3);
assert!((note.frequencies[0] - C4).abs() < 0.1);
assert!((note.frequencies[1] - C5).abs() < 0.1);
assert!((note.frequencies[2] - C6).abs() < 0.1);
}
}
#[test]
fn test_stack_octave_below() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4], 0.5)
.stack(-12, 1);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 1);
if let AudioEvent::Note(note) = &events[0] {
assert_eq!(note.num_freqs, 2);
assert!((note.frequencies[0] - C4).abs() < 0.1);
assert!((note.frequencies[1] - C3).abs() < 0.1);
}
}
#[test]
fn test_stack_perfect_fifth() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4], 0.5)
.stack(7, 2);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 1);
if let AudioEvent::Note(note) = &events[0] {
assert_eq!(note.num_freqs, 3);
assert!((note.frequencies[0] - C4).abs() < 0.1);
assert!((note.frequencies[1] - G4).abs() < 0.1);
let d5 = C4 * 2.0_f32.powf(14.0 / 12.0);
assert!((note.frequencies[2] - d5).abs() < 1.0);
}
}
#[test]
fn test_stack_chord() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25) .stack(12, 1);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
if let AudioEvent::Note(note) = &events[0] {
assert_eq!(note.num_freqs, 2);
assert!((note.frequencies[0] - C4).abs() < 0.1);
assert!((note.frequencies[1] - C5).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[1] {
assert_eq!(note.num_freqs, 2);
assert!((note.frequencies[0] - E4).abs() < 0.1);
assert!((note.frequencies[1] - E5).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[2] {
assert_eq!(note.num_freqs, 2);
assert!((note.frequencies[0] - G4).abs() < 0.1);
assert!((note.frequencies[1] - G5).abs() < 0.1);
}
}
#[test]
fn test_stack_count_zero() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4], 0.5)
.stack(12, 0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
if let AudioEvent::Note(note) = &events[0] {
assert_eq!(note.num_freqs, 1);
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
}
#[test]
fn test_stack_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().stack(12, 1);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_stretch_double_speed() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.5)
.stretch(2.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.start_time - 0.0).abs() < 0.01);
assert!((note.duration - 1.0).abs() < 0.01); }
if let AudioEvent::Note(note) = &events[1] {
assert!((note.start_time - 1.0).abs() < 0.01); assert!((note.duration - 1.0).abs() < 0.01);
}
if let AudioEvent::Note(note) = &events[2] {
assert!((note.start_time - 2.0).abs() < 0.01); assert!((note.duration - 1.0).abs() < 0.01);
}
}
#[test]
fn test_stretch_half_speed() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4], 1.0)
.stretch(0.5);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 2);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.start_time - 0.0).abs() < 0.01);
assert!((note.duration - 0.5).abs() < 0.01); }
if let AudioEvent::Note(note) = &events[1] {
assert!((note.start_time - 0.5).abs() < 0.01); assert!((note.duration - 0.5).abs() < 0.01);
}
}
#[test]
fn test_stretch_by_one() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4], 0.25)
.stretch(1.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
if let AudioEvent::Note(note) = &events[0] {
assert!((note.start_time - 0.0).abs() < 0.01);
assert!((note.duration - 0.25).abs() < 0.01);
}
if let AudioEvent::Note(note) = &events[1] {
assert!((note.start_time - 0.25).abs() < 0.01);
assert!((note.duration - 0.25).abs() < 0.01);
}
}
#[test]
fn test_stretch_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().stretch(2.0);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_compress_to_target_duration() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25) .compress(0.5);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
if let AudioEvent::Note(note) = &events[0] {
assert_eq!(note.start_time, 0.0);
assert!((note.duration - 0.167).abs() < 0.01);
}
if let AudioEvent::Note(note) = &events[1] {
assert!((note.start_time - 0.167).abs() < 0.01);
}
}
#[test]
fn test_compress_expand_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4], 0.5) .compress(2.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
if let AudioEvent::Note(note) = &events[0] {
assert_eq!(note.start_time, 0.0);
assert!((note.duration - 1.0).abs() < 0.01); }
if let AudioEvent::Note(note) = &events[1] {
assert!((note.start_time - 1.0).abs() < 0.01); }
}
#[test]
fn test_compress_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().compress(1.0);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_quantize_to_grid() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.wait(0.12)
.note(&[C4], 0.25)
.wait(0.11)
.note(&[E4], 0.25)
.wait(0.04)
.note(&[G4], 0.25)
.quantize(0.25);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.start_time - 0.0).abs() < 0.01); }
if let AudioEvent::Note(note) = &events[1] {
assert!((note.start_time - 0.5).abs() < 0.01); }
if let AudioEvent::Note(note) = &events[2] {
assert!((note.start_time - 0.75).abs() < 0.01); }
}
#[test]
fn test_quantize_eighth_notes() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.wait(0.13)
.note(&[C4], 0.25)
.wait(0.24)
.note(&[E4], 0.25)
.quantize(0.5);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
if let AudioEvent::Note(note) = &events[0] {
assert!((note.start_time - 0.0).abs() < 0.01); }
if let AudioEvent::Note(note) = &events[1] {
assert!((note.start_time - 0.5).abs() < 0.01); }
}
#[test]
fn test_quantize_preserves_pitches() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.wait(0.12)
.note(&[C4], 0.25)
.wait(0.01)
.note(&[E4], 0.25)
.quantize(0.25);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[1] {
assert!((note.frequencies[0] - E4).abs() < 0.1);
}
}
#[test]
fn test_quantize_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().quantize(0.25);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_palindrome_mirrors_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.palindrome();
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 6);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
assert_eq!(note.start_time, 0.0);
}
if let AudioEvent::Note(note) = &events[1] {
assert!((note.frequencies[0] - E4).abs() < 0.1);
assert_eq!(note.start_time, 0.25);
}
if let AudioEvent::Note(note) = &events[2] {
assert!((note.frequencies[0] - G4).abs() < 0.1);
assert_eq!(note.start_time, 0.5);
}
if let AudioEvent::Note(note) = &events[3] {
assert!((note.frequencies[0] - G4).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[4] {
assert!((note.frequencies[0] - E4).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[5] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
}
#[test]
fn test_palindrome_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().palindrome();
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_stutter_adds_repeats() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4], 0.25)
.stutter(1.0, 3);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 8);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
assert_eq!(note.start_time, 0.0);
}
}
#[test]
fn test_stutter_with_zero_probability() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.stutter(0.0, 4);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
}
#[test]
fn test_stutter_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().stutter(1.0, 4);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_stutter_every_nth_note() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4, C5], 0.25)
.stutter_every(2, 3);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 10);
}
#[test]
fn test_stutter_every_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().stutter_every(2, 4);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_granularize_splits_notes() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("texture")
.pattern_start()
.note(&[C4], 1.0)
.granularize(10);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 10);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
assert_eq!(note.start_time, 0.0);
assert!((note.duration - 0.09).abs() < 0.01); }
if let AudioEvent::Note(note) = &events[1] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
assert!((note.start_time - 0.1).abs() < 0.01); }
}
#[test]
fn test_granularize_multiple_notes() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("shimmer")
.pattern_start()
.notes(&[C4, E4], 0.5)
.granularize(5);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 10); }
#[test]
fn test_granularize_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("texture").pattern_start().granularize(10);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_shuffle_reorders_pitches() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4, C5], 0.25)
.shuffle();
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 4);
if let AudioEvent::Note(note) = &events[0] {
assert_eq!(note.start_time, 0.0);
}
if let AudioEvent::Note(note) = &events[1] {
assert_eq!(note.start_time, 0.25);
}
let original_freqs = vec![C4, E4, G4, C5];
let mut result_freqs = Vec::new();
for event in events {
if let AudioEvent::Note(note) = event {
result_freqs.push(note.frequencies[0]);
}
}
result_freqs.sort_by(|a, b| a.partial_cmp(b).unwrap());
let mut sorted_original = original_freqs.clone();
sorted_original.sort_by(|a, b| a.partial_cmp(b).unwrap());
for (a, b) in result_freqs.iter().zip(sorted_original.iter()) {
assert!((a - b).abs() < 0.1);
}
}
#[test]
fn test_shuffle_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().shuffle();
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_thin_removes_notes() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, C4, C4, C4, C4, C4, C4, C4], 0.125)
.thin(0.5);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert!(events.len() < 8, "Should remove some notes");
assert!(events.len() > 0, "Should keep some notes");
}
#[test]
fn test_thin_keep_all() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4, C5], 0.25)
.thin(1.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 4); }
#[test]
fn test_thin_remove_all() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4, C5], 0.25)
.thin(0.0);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 1);
assert_eq!(mixer.tracks()[0].events.len(), 0);
}
#[test]
fn test_thin_with_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().thin(0.5);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_invert_mirrors_pitches() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.5)
.invert(C4);
let mixer = comp.into_mixer();
let track = &mixer.tracks()[0];
assert_eq!(track.events.len(), 3);
if let AudioEvent::Note(note) = &track.events[0] {
assert!((note.frequencies[0] - C4).abs() < 1.0);
}
if let AudioEvent::Note(note) = &track.events[1] {
let expected = C4 * 2.0_f32.powf(-4.0 / 12.0); assert!((note.frequencies[0] - expected).abs() < 1.0);
}
}
#[test]
fn test_invert_empty_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").pattern_start().invert(C4);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0);
}
#[test]
fn test_invert_constrained_keeps_in_range() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4, C5], 0.5)
.invert_constrained(C4, C3, C5);
let mixer = comp.into_mixer();
let track = &mixer.tracks()[0];
for event in &track.events {
if let AudioEvent::Note(note) = event {
assert!(note.frequencies[0] >= C3 - 1.0);
assert!(note.frequencies[0] <= C5 + 1.0);
}
}
}
#[test]
fn test_magnetize_snaps_to_scale() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, CS4, D4, DS4, E4], 0.25)
.magnetize(&[C4, D4, E4, G4, A4]);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 5);
if let AudioEvent::Note(note) = &events[1] {
let snapped_to_c = (note.frequencies[0] - C4).abs() < 1.0;
let snapped_to_d = (note.frequencies[0] - D4).abs() < 1.0;
assert!(snapped_to_c || snapped_to_d);
}
if let AudioEvent::Note(note) = &events[3] {
let snapped_to_d = (note.frequencies[0] - D4).abs() < 1.0;
let snapped_to_e = (note.frequencies[0] - E4).abs() < 1.0;
assert!(snapped_to_d || snapped_to_e);
}
}
#[test]
fn test_magnetize_empty_scale() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.magnetize(&[]);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
}
#[test]
fn test_magnetize_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.notes(&[C4, E4, G4], 0.25)
.magnetize(&[C4, D4, E4]);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
}
#[test]
fn test_gravity_pulls_toward_center() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C3, C5], 0.5)
.gravity(C4, 0.5);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 2);
if let AudioEvent::Note(note) = &events[0] {
assert!(note.frequencies[0] > C3);
assert!(note.frequencies[0] < C4);
}
if let AudioEvent::Note(note) = &events[1] {
assert!(note.frequencies[0] < C5);
assert!(note.frequencies[0] > C4);
}
}
#[test]
fn test_gravity_repels_from_center() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4], 0.5)
.gravity(D4, -0.3);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 2);
if let AudioEvent::Note(note) = &events[0] {
assert!(note.frequencies[0] < C4);
}
if let AudioEvent::Note(note) = &events[1] {
assert!(note.frequencies[0] > E4);
}
}
#[test]
fn test_gravity_zero_strength() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.5)
.gravity(D4, 0.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
}
#[test]
fn test_gravity_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.notes(&[C4, E4, G4], 0.5)
.gravity(D4, 0.5);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
}
#[test]
fn test_ripple_affects_timing() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, C4, C4, C4], 0.25)
.ripple(0.02);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 4);
if let (AudioEvent::Note(note1), AudioEvent::Note(note2)) = (&events[0], &events[1]) {
let expected_interval = 0.25;
let actual_interval = note2.start_time - note1.start_time;
assert!(actual_interval > expected_interval);
}
if let (AudioEvent::Note(note2), AudioEvent::Note(note3)) = (&events[1], &events[2]) {
let interval = note3.start_time - note2.start_time;
assert!(interval > 0.25);
}
}
#[test]
fn test_ripple_affects_pitch() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, C4, C4], 0.25)
.ripple(0.05);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[1] {
assert!(note.frequencies[0] > C4);
}
if let AudioEvent::Note(note) = &events[2] {
assert!(note.frequencies[0] > C4);
}
}
#[test]
fn test_ripple_zero_intensity() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, C4, C4], 0.25)
.ripple(0.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
for event in events {
if let AudioEvent::Note(note) = event {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
}
}
#[test]
fn test_ripple_no_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.notes(&[C4, C4, C4], 0.25)
.ripple(0.05);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
}
#[test]
fn test_transform_closure_syntax() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.5)
.transform(|t| t
.shift(7) .humanize(0.01, 0.05)
.rotate(1) )
.wait(1.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
if let AudioEvent::Note(note) = &events[0] {
let expected = E4 * 2.0_f32.powf(7.0 / 12.0);
assert!((note.frequencies[0] - expected).abs() < 1.0);
}
}
#[test]
fn test_transform_chaining_multiple_calls() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, D4, E4], 0.25)
.transform(|t| t.shift(12)) .transform(|t| t.rotate(1)) .wait(1.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
for event in events {
if let AudioEvent::Note(note) = event {
assert!(note.frequencies[0] >= C5 - 1.0);
assert!(note.frequencies[0] <= E5 + 1.0);
}
}
}
#[test]
fn test_sieve_inclusive_keeps_only_range() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C3, E3, G3, C4, E4, G4], 0.25)
.sieve_inclusive(150.0, 300.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
for event in events {
if let AudioEvent::Note(note) = event {
assert!(note.frequencies[0] >= 150.0);
assert!(note.frequencies[0] <= 300.0);
}
}
}
#[test]
fn test_sieve_inclusive_with_namespace() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4, C5], 0.25)
.transform(|t| t.sieve_inclusive(250.0, 400.0));
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
}
#[test]
fn test_sieve_exclusive_removes_range() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C3, E3, G3, C4, E4, G4], 0.25)
.sieve_exclusive(150.0, 300.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
for event in events {
if let AudioEvent::Note(note) = event {
assert!(note.frequencies[0] < 150.0 || note.frequencies[0] > 300.0);
}
}
}
#[test]
fn test_sieve_exclusive_with_namespace() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C3, E3, G3, C4, E4, G4], 0.25)
.transform(|t| t.sieve_exclusive(150.0, 300.0));
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
}
#[test]
fn test_sieve_inclusive_empty_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("empty")
.pattern_start()
.sieve_inclusive(100.0, 500.0);
assert_eq!(builder.cursor, 0.0);
}
#[test]
fn test_sieve_exclusive_empty_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("empty")
.pattern_start()
.sieve_exclusive(100.0, 500.0);
assert_eq!(builder.cursor, 0.0);
}
#[test]
fn test_sieve_chaining() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C3, E3, G3, C4, E4, G4, C5], 0.25)
.transform(|t| t
.sieve_exclusive(100.0, 200.0) .sieve_exclusive(380.0, 600.0) );
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 2);
for event in events {
if let AudioEvent::Note(note) = event {
let freq = note.frequencies[0];
assert!(freq > 200.0 && freq < 380.0);
}
}
}
#[test]
fn test_group_collapses_to_chord() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4, C5], 0.25)
.group(2.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 1);
if let AudioEvent::Note(note) = &events[0] {
assert_eq!(note.num_freqs, 4);
assert_eq!(note.duration, 2.0);
}
}
#[test]
fn test_group_with_namespace() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.5)
.transform(|t| t.group(1.5));
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 1);
}
#[test]
fn test_group_updates_cursor() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.group(3.0);
assert_eq!(builder.cursor, 3.0);
}
#[test]
fn test_group_empty_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("empty")
.pattern_start()
.group(2.0);
assert_eq!(builder.cursor, 0.0);
}
#[test]
fn test_duplicate_doubles_notes() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.duplicate();
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 6);
}
#[test]
fn test_duplicate_with_transform() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("harmony")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.duplicate()
.transform(|t| t.shift(12));
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 6);
if let (AudioEvent::Note(original), AudioEvent::Note(shifted)) = (&events[0], &events[3]) {
let expected = original.frequencies[0] * 2.0; assert!((shifted.frequencies[0] - expected).abs() < 1.0);
}
}
#[test]
fn test_duplicate_preserves_timing() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4], 0.5)
.duplicate();
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
if let (AudioEvent::Note(note1), AudioEvent::Note(note3)) = (&events[0], &events[2]) {
assert_eq!(note1.start_time, 0.0);
assert_eq!(note3.start_time, 1.0); }
}
#[test]
fn test_duplicate_with_namespace() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.transform(|t| t.duplicate());
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 6);
}
#[test]
fn test_duplicate_updates_cursor() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.duplicate();
assert_eq!(builder.cursor, 1.5);
}
#[test]
fn test_duplicate_empty_pattern() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("empty")
.pattern_start()
.duplicate();
assert_eq!(builder.cursor, 0.0);
}
#[test]
fn test_group_then_duplicate() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("chords")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.group(1.0)
.duplicate();
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 2);
for event in events {
if let AudioEvent::Note(note) = event {
assert_eq!(note.num_freqs, 3);
}
}
}
#[test]
fn test_range_dilation_compress() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C3, C5], 0.5)
.range_dilation(0.5);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 2);
if let (AudioEvent::Note(note1), AudioEvent::Note(note2)) =
(&events[0], &events[1])
{
let center = C4;
let expected_low = center * 2.0_f32.powf(-6.0 / 12.0); let expected_high = center * 2.0_f32.powf(6.0 / 12.0);
assert!((note1.frequencies[0] - expected_low).abs() < 1.0);
assert!((note2.frequencies[0] - expected_high).abs() < 1.0);
}
}
#[test]
fn test_range_dilation_expand() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[B3, CS4], 0.5)
.range_dilation(2.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 2);
if let (AudioEvent::Note(note1), AudioEvent::Note(note2)) =
(&events[0], &events[1])
{
let interval_semitones = 12.0 * (note2.frequencies[0] / note1.frequencies[0]).log2();
assert!((interval_semitones - 4.0).abs() < 0.1);
}
}
#[test]
fn test_range_dilation_no_change() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4, G4], 0.25)
.range_dilation(1.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
if let AudioEvent::Note(note) = &events[1] {
assert!((note.frequencies[0] - E4).abs() < 0.1);
}
}
#[test]
fn test_shape_contour_smooth() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, C6], 0.5)
.shape_contour(0.5);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 2);
if let (AudioEvent::Note(note1), AudioEvent::Note(note2)) =
(&events[0], &events[1])
{
assert!((note1.frequencies[0] - C4).abs() < 0.1);
let expected = C4 * 2.0_f32.powf(12.0 / 12.0); assert!((note2.frequencies[0] - expected).abs() < 1.0);
}
}
#[test]
fn test_shape_contour_exaggerate() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, D4], 0.5)
.shape_contour(2.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 2);
if let (AudioEvent::Note(note1), AudioEvent::Note(note2)) =
(&events[0], &events[1])
{
assert!((note1.frequencies[0] - C4).abs() < 0.1);
let expected = C4 * 2.0_f32.powf(4.0 / 12.0); assert!((note2.frequencies[0] - expected).abs() < 0.5);
}
}
#[test]
fn test_shape_contour_single_note() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.note(&[C4], 0.5)
.shape_contour(2.0);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 1);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.frequencies[0] - C4).abs() < 0.1);
}
}
#[test]
fn test_echo_creates_repetitions() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.note(&[C4], 0.25)
.echo(0.5, 3, 0.7);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 4);
if let AudioEvent::Note(note) = &events[0] {
assert!((note.start_time - 0.0).abs() < 0.01);
}
if let AudioEvent::Note(note) = &events[1] {
assert!((note.start_time - 0.5).abs() < 0.01);
}
if let AudioEvent::Note(note) = &events[2] {
assert!((note.start_time - 1.0).abs() < 0.01);
}
if let AudioEvent::Note(note) = &events[3] {
assert!((note.start_time - 1.5).abs() < 0.01);
}
}
#[test]
fn test_echo_volume_decay() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.note(&[C4], 0.25)
.echo(0.3, 2, 0.5);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 3);
if let (
AudioEvent::Note(note1),
AudioEvent::Note(note2),
AudioEvent::Note(note3),
) = (&events[0], &events[1], &events[2])
{
assert!((note2.velocity - note1.velocity * 0.5).abs() < 0.01);
assert!((note3.velocity - note1.velocity * 0.25).abs() < 0.01);
}
}
#[test]
fn test_echo_with_multiple_notes() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.notes(&[C4, E4], 0.25)
.echo(0.5, 2, 0.6);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 6);
}
#[test]
fn test_echo_zero_repeats() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.pattern_start()
.note(&[C4], 0.25)
.echo(0.5, 0, 0.7);
let mixer = comp.into_mixer();
let events = &mixer.tracks()[0].events;
assert_eq!(events.len(), 1);
}
}