use super::TrackBuilder;
use crate::instruments::drums::DrumType;
impl<'a> TrackBuilder<'a> {
pub fn note(mut self, frequencies: &[f32], duration: f32) -> Self {
let cursor = self.cursor;
let waveform = self.waveform;
let envelope = self.envelope;
let filter_envelope = self.filter_envelope;
let fm_params = self.fm_params;
let pitch_bend = self.pitch_bend;
let custom_wavetable = self.custom_wavetable.clone();
let velocity = self.velocity;
let spatial_position = self.spatial_position;
self.get_track_mut().add_note_with_complete_params(
frequencies,
cursor,
duration,
waveform,
envelope,
filter_envelope,
fm_params,
pitch_bend,
custom_wavetable,
velocity,
spatial_position,
);
let swung_duration = self.apply_swing(duration);
self.cursor += swung_duration;
self.update_section_duration();
self
}
pub fn drum(mut self, drum_type: DrumType, duration: f32) -> Self {
let cursor = self.cursor;
let spatial_position = self.spatial_position;
self.get_track_mut().add_drum(drum_type, cursor, spatial_position);
let swung_duration = self.apply_swing(duration);
self.cursor += swung_duration;
self.update_section_duration();
self
}
pub fn sample(mut self, sample_name: &str) -> Self {
let cursor = self.cursor;
let sample = match self.composition.get_sample(sample_name) {
Some(s) => s.clone(),
None => {
eprintln!(
"Warning: Sample '{}' not found. Load it first with comp.load_sample(). Skipping sample event.",
sample_name
);
return self;
}
};
use crate::track::{AudioEvent, SampleEvent};
let sample_event = SampleEvent::new(sample.clone(), cursor);
let duration = sample.duration;
self.get_track_mut()
.events
.push(AudioEvent::Sample(sample_event));
self.get_track_mut().invalidate_time_cache();
let swung_duration = self.apply_swing(duration);
self.cursor += swung_duration;
self.update_section_duration();
self
}
pub fn play_sample(mut self, sample: &crate::synthesis::Sample, playback_rate: f32) -> Self {
let cursor = self.cursor;
use crate::track::{AudioEvent, SampleEvent};
let sample_event =
SampleEvent::new(sample.clone(), cursor).with_playback_rate(playback_rate);
let duration = sample.duration / playback_rate;
self.get_track_mut()
.events
.push(AudioEvent::Sample(sample_event));
self.get_track_mut().invalidate_time_cache();
let swung_duration = self.apply_swing(duration);
self.cursor += swung_duration;
self.update_section_duration();
self
}
pub fn play_slice(
self,
slice: &crate::synthesis::SampleSlice,
playback_rate: f32,
) -> Result<Self, crate::error::TunesError> {
let sample = slice.to_sample()?;
Ok(self.play_sample(&sample, playback_rate))
}
pub fn sample_with_rate(mut self, sample_name: &str, playback_rate: f32) -> Self {
let cursor = self.cursor;
let sample = match self.composition.get_sample(sample_name) {
Some(s) => s.clone(),
None => {
eprintln!(
"Warning: Sample '{}' not found. Load it first with comp.load_sample(). Skipping sample event.",
sample_name
);
return self;
}
};
use crate::track::{AudioEvent, SampleEvent};
let sample_event =
SampleEvent::new(sample.clone(), cursor).with_playback_rate(playback_rate);
let duration = sample.duration / playback_rate;
self.get_track_mut()
.events
.push(AudioEvent::Sample(sample_event));
self.get_track_mut().invalidate_time_cache();
let swung_duration = self.apply_swing(duration);
self.cursor += swung_duration;
self.update_section_duration();
self
}
pub fn interpolated(
mut self,
start_freq: f32,
end_freq: f32,
segments: usize,
note_duration: f32,
) -> Self {
if segments == 0 {
return self; }
let waveform = self.waveform;
let envelope = self.envelope;
if segments == 1 {
let cursor = self.cursor;
self.get_track_mut().add_note_with_waveform_and_envelope(
&[start_freq],
cursor,
note_duration,
waveform,
envelope,
);
self.cursor += note_duration;
self.update_section_duration();
return self;
}
for i in 0..segments {
let t = i as f32 / (segments - 1) as f32;
let freq = start_freq + (end_freq - start_freq) * t;
let cursor = self.cursor;
self.get_track_mut().add_note_with_waveform_and_envelope(
&[freq],
cursor,
note_duration,
waveform,
envelope,
);
self.cursor += note_duration;
}
self.update_section_duration();
self
}
pub fn notes(mut self, frequencies: &[f32], note_duration: f32) -> Self {
let waveform = self.waveform;
let envelope = self.envelope;
let filter_envelope = self.filter_envelope;
let fm_params = self.fm_params;
let pitch_bend = self.pitch_bend;
let custom_wavetable = self.custom_wavetable.clone();
let velocity = self.velocity;
let spatial_position = self.spatial_position;
for &freq in frequencies {
let cursor = self.cursor;
self.get_track_mut().add_note_with_complete_params(
&[freq],
cursor,
note_duration,
waveform,
envelope,
filter_envelope,
fm_params,
pitch_bend,
custom_wavetable.clone(),
velocity,
spatial_position,
);
let swung_duration = self.apply_swing(note_duration);
self.cursor += swung_duration;
}
self.update_section_duration();
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::composition::Composition;
use crate::consts::notes::*;
use crate::composition::timing::Tempo;
use crate::track::AudioEvent;
#[test]
fn test_note_adds_single_note() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("test").note(&[440.0], 1.0);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 1);
if let AudioEvent::Note(note) = &track.events[0] {
assert_eq!(note.frequencies[0], 440.0);
assert_eq!(note.start_time, 0.0);
assert_eq!(note.duration, 1.0);
} else {
panic!("Expected NoteEvent");
}
}
#[test]
fn test_note_advances_cursor() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("test").note(&[440.0], 1.0);
assert_eq!(builder.cursor, 1.0);
}
#[test]
fn test_note_chaining() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("test")
.note(&[440.0], 0.5)
.note(&[550.0], 0.5)
.note(&[660.0], 0.5);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 3);
if let AudioEvent::Note(note) = &track.events[0] {
assert_eq!(note.start_time, 0.0);
}
if let AudioEvent::Note(note) = &track.events[1] {
assert_eq!(note.start_time, 0.5);
}
if let AudioEvent::Note(note) = &track.events[2] {
assert_eq!(note.start_time, 1.0);
}
}
#[test]
fn test_note_with_chord() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("test").note(&[440.0, 554.37, 659.25], 1.0);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 1);
if let AudioEvent::Note(note) = &track.events[0] {
assert_eq!(note.num_freqs, 3);
assert_eq!(note.frequencies[0], 440.0);
assert_eq!(note.frequencies[1], 554.37);
assert_eq!(note.frequencies[2], 659.25);
}
}
#[test]
fn test_drum_adds_drum_hit() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("drums").drum(DrumType::Kick, 0.0);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 1);
if let AudioEvent::Drum(drum) = &track.events[0] {
assert!(matches!(drum.drum_type, DrumType::Kick));
assert_eq!(drum.start_time, 0.0);
} else {
panic!("Expected DrumEvent");
}
}
#[test]
fn test_drum_advances_cursor_by_duration() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("drums").drum(DrumType::Kick, 0.5);
assert_eq!(builder.cursor, 0.5);
}
#[test]
fn test_drum_chaining() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("drums")
.drum(DrumType::Kick, 0.25)
.drum(DrumType::Snare, 0.25)
.drum(DrumType::HiHatClosed, 0.0);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 3);
if let AudioEvent::Drum(drum) = &track.events[0] {
assert!(matches!(drum.drum_type, DrumType::Kick));
}
if let AudioEvent::Drum(drum) = &track.events[1] {
assert!(matches!(drum.drum_type, DrumType::Snare));
}
if let AudioEvent::Drum(drum) = &track.events[2] {
assert!(matches!(drum.drum_type, DrumType::HiHatClosed));
}
}
#[test]
fn test_interpolated_creates_smooth_glide() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").interpolated(440.0, 880.0, 5, 0.1);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 5);
let expected_freqs = [440.0, 550.0, 660.0, 770.0, 880.0];
for (i, expected) in expected_freqs.iter().enumerate() {
if let AudioEvent::Note(note) = &track.events[i] {
assert_eq!(note.frequencies[0], *expected);
assert_eq!(note.start_time, i as f32 * 0.1);
}
}
}
#[test]
fn test_interpolated_with_zero_segments() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("melody").interpolated(440.0, 880.0, 0, 0.1);
assert_eq!(builder.cursor, 0.0, "Cursor should not advance");
let mixer = comp.into_mixer();
assert_eq!(
mixer.tracks().len(),
0,
"Zero segments should create no track"
);
}
#[test]
fn test_interpolated_with_one_segment() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("melody").interpolated(440.0, 880.0, 1, 0.5);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 1);
if let AudioEvent::Note(note) = &track.events[0] {
assert_eq!(note.frequencies[0], 440.0); assert_eq!(note.duration, 0.5);
}
}
#[test]
fn test_notes_creates_sequence() {
let mut comp = Composition::new(Tempo::new(120.0));
let freqs = [C4, E4, G4, C5]; comp.track("melody").notes(&freqs, 0.25);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 4);
for (i, &expected_freq) in freqs.iter().enumerate() {
if let AudioEvent::Note(note) = &track.events[i] {
assert_eq!(note.frequencies[0], expected_freq);
assert_eq!(note.start_time, i as f32 * 0.25);
assert_eq!(note.duration, 0.25);
}
}
}
#[test]
fn test_notes_with_empty_array() {
let mut comp = Composition::new(Tempo::new(120.0));
let builder = comp.track("melody").notes(&[], 0.5);
assert_eq!(
builder.cursor, 0.0,
"Cursor should not advance for empty array"
);
let mixer = comp.into_mixer();
assert_eq!(mixer.tracks().len(), 0, "Empty array should create no track");
}
#[test]
fn test_notes_advances_cursor_correctly() {
let mut comp = Composition::new(Tempo::new(120.0));
let freqs = [440.0, 550.0, 660.0];
let builder = comp.track("melody").notes(&freqs, 0.5);
assert_eq!(builder.cursor, 1.5); }
#[test]
fn test_mixed_notes_and_drums() {
let mut comp = Composition::new(Tempo::new(120.0));
comp.track("mixed")
.note(&[440.0], 0.5)
.drum(DrumType::Kick, 0.25)
.note(&[550.0], 0.5)
.drum(DrumType::Snare, 0.0);
let track = &comp.into_mixer().tracks()[0];
assert_eq!(track.events.len(), 4);
assert!(matches!(track.events[0], AudioEvent::Note(_)));
assert!(matches!(track.events[1], AudioEvent::Drum(_)));
assert!(matches!(track.events[2], AudioEvent::Note(_)));
assert!(matches!(track.events[3], AudioEvent::Drum(_)));
}
}