use crate::composition::TrackBuilder;
impl<'a> TrackBuilder<'a> {
pub fn swing(mut self, swing: f32) -> Self {
self.swing = swing.clamp(0.5, 0.9);
self
}
pub fn at(mut self, time: f32) -> Self {
self.cursor = time;
self
}
pub fn wait(mut self, duration: f32) -> Self {
self.cursor += duration;
self
}
pub fn seek(mut self, offset: f32) -> Self {
self.cursor += offset;
self
}
pub fn mark(self, name: &str) -> Self {
self.composition
.markers
.insert(name.to_string(), self.cursor);
self
}
pub fn at_mark(mut self, name: &str) -> Self {
let marker_time = match self.composition.markers.get(name) {
Some(time) => *time,
None => {
eprintln!(
"Warning: Marker '{}' not found. Use .mark(\"{}\") to create it first. Cursor position unchanged.",
name, name
);
return self;
}
};
self.cursor = marker_time;
self
}
pub fn at_marker(self, name: &str) -> Self {
self.at_mark(name)
}
pub fn peek_cursor(&self) -> f32 {
self.cursor
}
pub fn tempo(mut self, bpm: f32) -> Self {
let cursor = self.cursor;
self.get_track_mut()
.events
.push(crate::track::AudioEvent::TempoChange(
crate::track::TempoChangeEvent {
start_time: cursor,
bpm,
},
));
self.get_track_mut().invalidate_time_cache();
self
}
pub fn time_signature(mut self, numerator: u8, denominator: u8) -> Self {
let cursor = self.cursor;
self.get_track_mut()
.events
.push(crate::track::AudioEvent::TimeSignature(
crate::track::TimeSignatureEvent {
start_time: cursor,
numerator,
denominator,
},
));
self.get_track_mut().invalidate_time_cache();
self
}
pub fn key_signature(mut self, key_signature: crate::theory::key_signature::KeySignature) -> Self {
let cursor = self.cursor;
self.get_track_mut()
.events
.push(crate::track::AudioEvent::KeySignature(
crate::track::KeySignatureEvent {
start_time: cursor,
key_signature,
},
));
self.get_track_mut().invalidate_time_cache();
self
}
}
#[cfg(test)]
mod tests {
use crate::composition::Composition;
use crate::consts::notes::{C3, C4, E3, E4, G3, G4};
use crate::composition::timing::Tempo;
#[test]
fn test_swing_sets_value() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").swing(0.67);
assert_eq!(builder.swing, 0.67);
}
#[test]
fn test_swing_clamps_low_values() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").swing(0.2);
assert_eq!(builder.swing, 0.5);
}
#[test]
fn test_swing_clamps_high_values() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").swing(0.95);
assert_eq!(builder.swing, 0.9);
}
#[test]
fn test_swing_allows_boundary_values() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder_min = comp.track("test1").swing(0.5);
assert_eq!(builder_min.swing, 0.5);
let builder_max = comp.track("test2").swing(0.9);
assert_eq!(builder_max.swing, 0.9);
}
#[test]
fn test_swing_common_values() {
let mut comp = Composition::new(Tempo::new(120.0));
let straight = comp.track("straight").swing(0.5);
assert_eq!(straight.swing, 0.5);
let triplet = comp.track("triplet").swing(0.67);
assert!((triplet.swing - 0.67).abs() < 0.01);
let heavy = comp.track("heavy").swing(0.75);
assert_eq!(heavy.swing, 0.75);
}
#[test]
fn test_at_sets_absolute_cursor_position() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").at(5.0);
assert_eq!(builder.cursor, 5.0);
}
#[test]
fn test_at_can_move_backward() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").wait(10.0).at(3.0);
assert_eq!(builder.cursor, 3.0);
}
#[test]
fn test_at_zero() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").wait(5.0).at(0.0);
assert_eq!(builder.cursor, 0.0);
}
#[test]
fn test_wait_advances_cursor() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").wait(2.5);
assert_eq!(builder.cursor, 2.5);
}
#[test]
fn test_wait_is_additive() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").wait(1.0).wait(2.0).wait(0.5);
assert_eq!(builder.cursor, 3.5);
}
#[test]
fn test_wait_with_zero() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").wait(5.0).wait(0.0);
assert_eq!(builder.cursor, 5.0);
}
#[test]
fn test_seek_advances_cursor() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").seek(3.0);
assert_eq!(builder.cursor, 3.0);
}
#[test]
fn test_seek_is_additive() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").seek(1.5).seek(2.0);
assert_eq!(builder.cursor, 3.5);
}
#[test]
fn test_seek_backward_with_negative() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").seek(5.0).seek(-2.0);
assert_eq!(builder.cursor, 3.0);
}
#[test]
fn test_timing_methods_chain_together() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").at(0.0).wait(2.0).seek(1.0).swing(0.67);
assert_eq!(builder.cursor, 3.0);
assert!((builder.swing - 0.67).abs() < 0.01);
}
#[test]
fn test_complex_timing_sequence() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp
.track("test")
.at(10.0) .wait(5.0) .seek(-3.0) .at(0.0) .wait(1.0);
assert_eq!(builder.cursor, 1.0);
}
#[test]
fn test_wait_and_seek_are_equivalent() {
let mut comp1 = Composition::new(Tempo::new(120.0));
let mut comp2 = Composition::new(Tempo::new(120.0));
let with_wait = comp1.track("wait").wait(2.5);
let with_seek = comp2.track("seek").seek(2.5);
assert_eq!(with_wait.cursor, with_seek.cursor);
}
#[test]
fn test_timing_with_note_placement() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.at(0.0)
.note(&[440.0], 0.5) .wait(1.0) .note(&[550.0], 0.5);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 2);
if let crate::track::AudioEvent::Note(note1) = &track.events[0] {
assert_eq!(note1.start_time, 0.0);
}
if let crate::track::AudioEvent::Note(note2) = &track.events[1] {
assert_eq!(note2.start_time, 1.5); }
}
#[test]
fn test_tempo_adds_tempo_change_event() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").tempo(90.0);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 1);
if let crate::track::AudioEvent::TempoChange(tempo) = &track.events[0] {
assert_eq!(tempo.bpm, 90.0);
assert_eq!(tempo.start_time, 0.0);
} else {
panic!("Expected TempoChange event");
}
}
#[test]
fn test_tempo_at_specific_time() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").at(5.0).tempo(140.0);
let track = &comp.into_mixer().tracks()[0];
if let crate::track::AudioEvent::TempoChange(tempo) = &track.events[0] {
assert_eq!(tempo.bpm, 140.0);
assert_eq!(tempo.start_time, 5.0);
}
}
#[test]
fn test_multiple_tempo_changes() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.tempo(100.0)
.wait(2.0)
.tempo(120.0)
.wait(2.0)
.tempo(140.0);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 3);
if let crate::track::AudioEvent::TempoChange(tempo1) = &track.events[0] {
assert_eq!(tempo1.bpm, 100.0);
assert_eq!(tempo1.start_time, 0.0);
}
if let crate::track::AudioEvent::TempoChange(tempo2) = &track.events[1] {
assert_eq!(tempo2.bpm, 120.0);
assert_eq!(tempo2.start_time, 2.0);
}
if let crate::track::AudioEvent::TempoChange(tempo3) = &track.events[2] {
assert_eq!(tempo3.bpm, 140.0);
assert_eq!(tempo3.start_time, 4.0);
}
}
#[test]
fn test_tempo_with_notes() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.note(&[C4], 0.5)
.tempo(90.0)
.note(&[E4], 0.5);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 3);
if let crate::track::AudioEvent::Note(note) = &track.events[0] {
assert_eq!(note.start_time, 0.0);
}
if let crate::track::AudioEvent::TempoChange(tempo) = &track.events[1] {
assert_eq!(tempo.bpm, 90.0);
assert_eq!(tempo.start_time, 0.5);
}
if let crate::track::AudioEvent::Note(note) = &track.events[2] {
assert_eq!(note.start_time, 0.5);
}
}
#[test]
fn test_time_signature_adds_event() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").time_signature(3, 4);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 1);
if let crate::track::AudioEvent::TimeSignature(time_sig) = &track.events[0] {
assert_eq!(time_sig.numerator, 3);
assert_eq!(time_sig.denominator, 4);
assert_eq!(time_sig.start_time, 0.0);
} else {
panic!("Expected TimeSignature event");
}
}
#[test]
fn test_time_signature_at_specific_time() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").at(8.0).time_signature(6, 8);
let track = &comp.into_mixer().tracks()[0];
if let crate::track::AudioEvent::TimeSignature(time_sig) = &track.events[0] {
assert_eq!(time_sig.numerator, 6);
assert_eq!(time_sig.denominator, 8);
assert_eq!(time_sig.start_time, 8.0);
}
}
#[test]
fn test_multiple_time_signature_changes() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.time_signature(4, 4)
.wait(4.0)
.time_signature(3, 4)
.wait(3.0)
.time_signature(7, 8);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 3);
if let crate::track::AudioEvent::TimeSignature(time_sig1) = &track.events[0] {
assert_eq!(time_sig1.numerator, 4);
assert_eq!(time_sig1.denominator, 4);
assert_eq!(time_sig1.start_time, 0.0);
}
if let crate::track::AudioEvent::TimeSignature(time_sig2) = &track.events[1] {
assert_eq!(time_sig2.numerator, 3);
assert_eq!(time_sig2.denominator, 4);
assert_eq!(time_sig2.start_time, 4.0);
}
if let crate::track::AudioEvent::TimeSignature(time_sig3) = &track.events[2] {
assert_eq!(time_sig3.numerator, 7);
assert_eq!(time_sig3.denominator, 8);
assert_eq!(time_sig3.start_time, 7.0);
}
}
#[test]
fn test_time_signature_with_notes() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody")
.time_signature(4, 4)
.note(&[C4], 0.5)
.note(&[E4], 0.5)
.time_signature(3, 4)
.note(&[G4], 0.5);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 5);
if let crate::track::AudioEvent::TimeSignature(time_sig) = &track.events[0] {
assert_eq!(time_sig.numerator, 4);
assert_eq!(time_sig.denominator, 4);
assert_eq!(time_sig.start_time, 0.0);
}
if let crate::track::AudioEvent::Note(note) = &track.events[1] {
assert_eq!(note.start_time, 0.0);
}
if let crate::track::AudioEvent::Note(note) = &track.events[2] {
assert_eq!(note.start_time, 0.5);
}
if let crate::track::AudioEvent::TimeSignature(time_sig) = &track.events[3] {
assert_eq!(time_sig.numerator, 3);
assert_eq!(time_sig.denominator, 4);
assert_eq!(time_sig.start_time, 1.0);
}
if let crate::track::AudioEvent::Note(note) = &track.events[4] {
assert_eq!(note.start_time, 1.0);
}
}
#[test]
fn test_time_signature_common_values() {
let mut comp1 = Composition::new(Tempo::new(120.0));
comp1.track("test").time_signature(4, 4);
let mixer1 = comp1.into_mixer();
if let crate::track::AudioEvent::TimeSignature(time_sig) = &mixer1.tracks()[0].events[0] {
assert_eq!(time_sig.numerator, 4);
assert_eq!(time_sig.denominator, 4);
}
let mut comp2 = Composition::new(Tempo::new(120.0));
comp2.track("test").time_signature(3, 4);
let mixer2 = comp2.into_mixer();
if let crate::track::AudioEvent::TimeSignature(time_sig) = &mixer2.tracks()[0].events[0] {
assert_eq!(time_sig.numerator, 3);
assert_eq!(time_sig.denominator, 4);
}
let mut comp3 = Composition::new(Tempo::new(120.0));
comp3.track("test").time_signature(6, 8);
let mixer3 = comp3.into_mixer();
if let crate::track::AudioEvent::TimeSignature(time_sig) = &mixer3.tracks()[0].events[0] {
assert_eq!(time_sig.numerator, 6);
assert_eq!(time_sig.denominator, 8);
}
let mut comp4 = Composition::new(Tempo::new(120.0));
comp4.track("test").time_signature(5, 4);
let mixer4 = comp4.into_mixer();
if let crate::track::AudioEvent::TimeSignature(time_sig) = &mixer4.tracks()[0].events[0] {
assert_eq!(time_sig.numerator, 5);
assert_eq!(time_sig.denominator, 4);
}
let mut comp5 = Composition::new(Tempo::new(120.0));
comp5.track("test").time_signature(7, 8);
let mixer5 = comp5.into_mixer();
if let crate::track::AudioEvent::TimeSignature(time_sig) = &mixer5.tracks()[0].events[0] {
assert_eq!(time_sig.numerator, 7);
assert_eq!(time_sig.denominator, 8);
}
let mut comp6 = Composition::new(Tempo::new(120.0));
comp6.track("test").time_signature(12, 8);
let mixer6 = comp6.into_mixer();
if let crate::track::AudioEvent::TimeSignature(time_sig) = &mixer6.tracks()[0].events[0] {
assert_eq!(time_sig.numerator, 12);
assert_eq!(time_sig.denominator, 8);
}
}
#[test]
fn test_mark_saves_cursor_position() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("test").wait(5.0).mark("test_marker");
assert_eq!(*comp.markers.get("test_marker").unwrap(), 5.0);
}
#[test]
fn test_at_mark_moves_to_saved_position() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("track1").wait(10.0).mark("drop");
let builder = comp.track("track2").at_mark("drop");
assert_eq!(builder.cursor, 10.0);
}
#[test]
fn test_at_mark_handles_missing_marker() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("test")
.at(1.0) .at_mark("nonexistent") .note(&[C4], 0.5);
let mixer = comp.into_mixer();
let track = &mixer.tracks()[0];
if let crate::track::AudioEvent::Note(note) = &track.events[0] {
assert_eq!(note.start_time, 1.0);
}
}
#[test]
fn test_multiple_markers() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("main")
.wait(2.0)
.mark("verse")
.wait(4.0)
.mark("chorus")
.wait(3.0)
.mark("bridge");
assert_eq!(*comp.markers.get("verse").unwrap(), 2.0);
assert_eq!(*comp.markers.get("chorus").unwrap(), 6.0);
assert_eq!(*comp.markers.get("bridge").unwrap(), 9.0);
}
#[test]
fn test_markers_across_tracks() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("drums")
.note(&[C4], 0.5)
.mark("intro_end")
.wait(2.0)
.mark("verse_start");
let bass_builder = comp.track("bass").at_mark("verse_start");
assert_eq!(bass_builder.cursor, 2.5); }
#[test]
fn test_marker_can_be_overwritten() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("test1").wait(5.0).mark("position");
comp.track("test2").wait(10.0).mark("position");
assert_eq!(*comp.markers.get("position").unwrap(), 10.0);
}
#[test]
fn test_peek_cursor_returns_current_position() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").wait(3.5);
assert_eq!(builder.peek_cursor(), 3.5);
}
#[test]
fn test_peek_cursor_doesnt_advance() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").wait(2.0);
let pos1 = builder.peek_cursor();
let pos2 = builder.peek_cursor();
assert_eq!(pos1, 2.0);
assert_eq!(pos2, 2.0);
}
#[test]
fn test_mark_and_at_mark_workflow() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("structure")
.mark("start")
.wait(8.0)
.mark("verse")
.wait(16.0)
.mark("chorus")
.wait(8.0)
.mark("bridge")
.wait(16.0)
.mark("outro");
comp.track("lead").at_mark("verse").note(&[E4], 0.5);
comp.track("pad").at_mark("chorus").note(&[C3, E3, G3], 2.0);
let mixer = comp.into_mixer();
let mut note_times = vec![];
for track in &mixer.tracks() {
if let Some(crate::track::AudioEvent::Note(note)) = track.events.first() {
note_times.push(note.start_time);
}
}
note_times.sort_by(|a, b| a.partial_cmp(b).unwrap());
assert_eq!(note_times.len(), 2);
assert_eq!(note_times[0], 8.0); assert_eq!(note_times[1], 24.0); }
#[test]
fn test_markers_with_complex_timing() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("timing")
.at(0.0)
.wait(2.0)
.mark("a")
.seek(-1.0) .mark("b")
.at(10.0)
.mark("c")
.wait(5.0)
.seek(-3.0) .mark("d");
assert_eq!(*comp.markers.get("a").unwrap(), 2.0);
assert_eq!(*comp.markers.get("b").unwrap(), 1.0);
assert_eq!(*comp.markers.get("c").unwrap(), 10.0);
assert_eq!(*comp.markers.get("d").unwrap(), 12.0);
}
}