//! Sequencer module for playing and timing musical sequences //! //! This module provides a sequencer that can play back compositions, //! handle timing, and coordinate multiple tracks and patterns. use crate::bpm_to_samples_per_beat; use crate::config::EffectConfig; use crate::core::Composition; use crate::effects::{ AudioEffect, BitCrusher, Chorus, Delay, Distortion, EffectsChain, HighPassFilter, LowPassFilter, Reverb, TapeSaturation, VinylCrackle, }; use crate::patterns::MelodicPattern; use crate::synthesis::{PolySynth, Waveform}; use std::collections::VecDeque; /// Main sequencer for playing compositions #[derive(Debug)] pub struct Sequencer { pub tempo: f32, pub is_playing: bool, pub current_position: f32, // Current position in beats pub loop_enabled: bool, pub loop_start: f32, // Loop start in beats pub loop_end: f32, // Loop end in beats // Internal timing samples_per_beat: usize, current_sample: usize, // Event scheduling scheduled_events: VecDeque, // Playback state tracks: Vec, global_volume: f32, } /// A scheduled musical event #[derive(Debug, Clone)] struct ScheduledEvent { pub event_time: f32, // Time in beats pub event_type: EventType, pub track_index: usize, } /// Types of sequencer events #[derive(Debug, Clone)] #[allow(dead_code)] enum EventType { NoteOn { midi_note: u8, velocity: f32 }, NoteOff { midi_note: u8 }, VolumeChange { volume: f32 }, TempoChange { new_tempo: f32 }, } /// State for each track in the sequencer #[derive(Debug)] struct TrackState { pub synth: PolySynth, pub volume: f32, pub is_muted: bool, pub current_notes: Vec, // Currently playing MIDI notes pub effects: EffectsChain, } impl TrackState { fn new(waveform: Waveform, max_tracks: usize, effect_configs: &[EffectConfig]) -> Self { let effects = Self::create_effects_chain(effect_configs).unwrap_or_else(|_| EffectsChain::new()); Self { synth: PolySynth::new(max_tracks, waveform), volume: 1.0, is_muted: false, current_notes: Vec::new(), effects, } } /// Create an effects chain from effect configurations fn create_effects_chain(effect_configs: &[EffectConfig]) -> Result { let mut effects_chain = EffectsChain::new(); for effect_config in effect_configs { let effect: Box = match effect_config.effect_type.as_str() { "lowpass" => { let cutoff = effect_config .params .get("cutoff") .and_then(|v| v.as_float()) .unwrap_or(1000.0) as f32; let resonance = effect_config .params .get("resonance") .and_then(|v| v.as_float()) .unwrap_or(1.0) as f32; Box::new(LowPassFilter::new(cutoff, resonance)) } "highpass" => { let cutoff = effect_config .params .get("cutoff") .and_then(|v| v.as_float()) .unwrap_or(1000.0) as f32; let resonance = effect_config .params .get("resonance") .and_then(|v| v.as_float()) .unwrap_or(1.0) as f32; Box::new(HighPassFilter::new(cutoff, resonance)) } "delay" => { let time = effect_config .params .get("time") .and_then(|v| v.as_float()) .unwrap_or(0.3) as f32; let feedback = effect_config .params .get("feedback") .and_then(|v| v.as_float()) .unwrap_or(0.3) as f32; let mix = effect_config .params .get("mix") .and_then(|v| v.as_float()) .unwrap_or(0.3) as f32; Box::new(Delay::new(time, feedback, mix)) } "reverb" => { let room_size = effect_config .params .get("room_size") .and_then(|v| v.as_float()) .unwrap_or(0.5) as f32; let damping = effect_config .params .get("damping") .and_then(|v| v.as_float()) .unwrap_or(0.5) as f32; let mix = effect_config .params .get("mix") .and_then(|v| v.as_float()) .unwrap_or(0.3) as f32; Box::new(Reverb::new(room_size, damping, mix)) } "distortion" => { let drive = effect_config .params .get("drive") .and_then(|v| v.as_float()) .unwrap_or(2.0) as f32; let tone = effect_config .params .get("tone") .and_then(|v| v.as_float()) .unwrap_or(0.5) as f32; Box::new(Distortion::new(drive, tone)) } "chorus" => { let rate = effect_config .params .get("rate") .and_then(|v| v.as_float()) .unwrap_or(1.0) as f32; let depth = effect_config .params .get("depth") .and_then(|v| v.as_float()) .unwrap_or(0.5) as f32; let mix = effect_config .params .get("mix") .and_then(|v| v.as_float()) .unwrap_or(0.3) as f32; let layers = effect_config .params .get("layers") .and_then(|v| v.as_integer()) .unwrap_or(3) as usize; Box::new(Chorus::new(rate, depth, mix, layers)) } "vinyl" => { let intensity = effect_config .params .get("intensity") .and_then(|v| v.as_float()) .unwrap_or(0.5) as f32; let frequency = effect_config .params .get("frequency") .and_then(|v| v.as_float()) .unwrap_or(0.5) as f32; let mix = effect_config .params .get("mix") .and_then(|v| v.as_float()) .unwrap_or(0.3) as f32; Box::new(VinylCrackle::new(intensity, frequency, mix)) } "tape" => { let drive = effect_config .params .get("drive") .and_then(|v| v.as_float()) .unwrap_or(2.0) as f32; let warmth = effect_config .params .get("warmth") .and_then(|v| v.as_float()) .unwrap_or(0.7) as f32; let mix = effect_config .params .get("mix") .and_then(|v| v.as_float()) .unwrap_or(0.5) as f32; Box::new(TapeSaturation::new(drive, warmth, mix)) } "bitcrusher" => { let bit_depth = effect_config .params .get("bit_depth") .and_then(|v| v.as_float()) .unwrap_or(8.0) as f32; let sample_rate_reduction = effect_config .params .get("sample_rate_reduction") .and_then(|v| v.as_float()) .unwrap_or(2.0) as f32; let mix = effect_config .params .get("mix") .and_then(|v| v.as_float()) .unwrap_or(0.5) as f32; Box::new(BitCrusher::new(bit_depth, sample_rate_reduction, mix)) } _ => { return Err(format!( "Unknown effect type: {}", effect_config.effect_type )); } }; effects_chain.add_effect(effect); } Ok(effects_chain) } } impl Sequencer { /// Create a new sequencer /// /// # Arguments /// * `tempo` - Initial tempo in BPM pub fn new(tempo: f32) -> Self { let samples_per_beat = bpm_to_samples_per_beat(tempo); Self { tempo, is_playing: false, current_position: 0.0, loop_enabled: false, loop_start: 0.0, loop_end: 16.0, samples_per_beat, current_sample: 0, scheduled_events: VecDeque::new(), tracks: Vec::new(), global_volume: 1.0, } } /// Load a composition into the sequencer pub fn load_composition(&mut self, composition: &Composition) -> Result<(), String> { self.clear(); // Create track states for each track in the composition for track in &composition.tracks { let waveform = track.waveform; let mut track_state = TrackState::new(waveform, 8, &track.effect_configs); track_state.volume = track.volume; self.tracks.push(track_state); } // Schedule all note events self.schedule_composition_events(composition)?; // Set loop end to composition duration self.loop_end = composition.total_duration; Ok(()) } /// Schedule events from a composition fn schedule_composition_events(&mut self, composition: &Composition) -> Result<(), String> { for (track_index, track) in composition.tracks.iter().enumerate() { for note in &track.notes { // Schedule note on let note_on_event = ScheduledEvent { event_time: note.start_time, event_type: EventType::NoteOn { midi_note: note.midi_note, velocity: note.velocity, }, track_index, }; // Schedule note off let note_off_event = ScheduledEvent { event_time: note.start_time + note.duration, event_type: EventType::NoteOff { midi_note: note.midi_note, }, track_index, }; self.scheduled_events.push_back(note_on_event); self.scheduled_events.push_back(note_off_event); } } // Sort events by time let mut events: Vec<_> = self.scheduled_events.drain(..).collect(); events.sort_by(|a, b| a.event_time.partial_cmp(&b.event_time).unwrap()); self.scheduled_events.extend(events); Ok(()) } /// Start playback pub fn play(&mut self) { self.is_playing = true; } /// Stop playback pub fn stop(&mut self) { self.is_playing = false; self.current_position = 0.0; self.current_sample = 0; // Stop all playing notes for track in &mut self.tracks { for ¬e in track.current_notes.clone().iter() { track.synth.stop_note(crate::midi_to_frequency(note)); } track.current_notes.clear(); } } /// Pause playback pub fn pause(&mut self) { self.is_playing = false; } /// Set the current playback position pub fn set_position(&mut self, position: f32) { self.current_position = position; self.current_sample = (position * self.samples_per_beat as f32) as usize; // Stop all current notes when seeking for track in &mut self.tracks { for ¬e in track.current_notes.clone().iter() { track.synth.stop_note(crate::midi_to_frequency(note)); } track.current_notes.clear(); } } /// Set the tempo pub fn set_tempo(&mut self, tempo: f32) { self.tempo = tempo; self.samples_per_beat = bmp_to_samples_per_beat(tempo); } /// Enable/disable looping pub fn set_loop(&mut self, enabled: bool, start: f32, end: f32) { self.loop_enabled = enabled; self.loop_start = start; self.loop_end = end; } /// Set global volume pub fn set_global_volume(&mut self, volume: f32) { self.global_volume = volume.clamp(0.0, 1.0); } /// Mute/unmute a track pub fn set_track_mute(&mut self, track_index: usize, muted: bool) { if let Some(track) = self.tracks.get_mut(track_index) { track.is_muted = muted; if muted { // Stop all notes in this track for ¬e in track.current_notes.clone().iter() { track.synth.stop_note(crate::midi_to_frequency(note)); } track.current_notes.clear(); } } } /// Set track volume pub fn set_track_volume(&mut self, track_index: usize, volume: f32) { if let Some(track) = self.tracks.get_mut(track_index) { track.volume = volume.clamp(0.0, 1.0); } } /// Process audio for a given number of samples pub fn process_audio(&mut self, buffer: &mut [f32]) -> Result<(), String> { if !self.is_playing { // Fill with silence for sample in buffer.iter_mut() { *sample = 0.0; } return Ok(()); } for i in 0..buffer.len() { // Update position self.current_position = self.current_sample as f32 / self.samples_per_beat as f32; // Process scheduled events self.process_events_at_position(self.current_position); // Generate audio sample let mut sample = 0.0; for track in &mut self.tracks { if !track.is_muted { let track_sample = track.synth.next_sample(); let processed_sample = track.effects.process_sample(track_sample); sample += processed_sample * track.volume; } } // Apply global volume buffer[i] = sample * self.global_volume; // Advance sample counter self.current_sample += 1; // Handle looping if self.loop_enabled && self.current_position >= self.loop_end { self.set_position(self.loop_start); } } Ok(()) } /// Process events that should occur at the current position fn process_events_at_position(&mut self, position: f32) { while let Some(event) = self.scheduled_events.front() { if event.event_time <= position { let event = self.scheduled_events.pop_front().unwrap(); self.process_event(event); } else { break; } } } /// Process a single event fn process_event(&mut self, event: ScheduledEvent) { if event.track_index >= self.tracks.len() { return; } let track = &mut self.tracks[event.track_index]; if track.is_muted { return; } match event.event_type { EventType::NoteOn { midi_note, velocity: _, } => { let frequency = crate::midi_to_frequency(midi_note); track.synth.play_note(frequency); track.current_notes.push(midi_note); } EventType::NoteOff { midi_note } => { let frequency = crate::midi_to_frequency(midi_note); track.synth.stop_note(frequency); track.current_notes.retain(|&n| n != midi_note); } EventType::VolumeChange { volume } => { track.volume = volume; } EventType::TempoChange { new_tempo } => { self.set_tempo(new_tempo); } } } /// Clear all scheduled events and reset state pub fn clear(&mut self) { self.scheduled_events.clear(); self.tracks.clear(); self.current_position = 0.0; self.current_sample = 0; self.is_playing = false; } /// Get current playback information pub fn get_playback_info(&self) -> PlaybackInfo { PlaybackInfo { is_playing: self.is_playing, current_position: self.current_position, tempo: self.tempo, loop_enabled: self.loop_enabled, loop_start: self.loop_start, loop_end: self.loop_end, track_count: self.tracks.len(), active_notes: self.tracks.iter().map(|t| t.current_notes.len()).sum(), } } /// Add a real-time note event pub fn trigger_note(&mut self, track_index: usize, midi_note: u8, _velocity: f32) { if let Some(track) = self.tracks.get_mut(track_index) { if !track.is_muted { let frequency = crate::midi_to_frequency(midi_note); track.synth.play_note(frequency); track.current_notes.push(midi_note); } } } /// Release a real-time note pub fn release_note(&mut self, track_index: usize, midi_note: u8) { if let Some(track) = self.tracks.get_mut(track_index) { let frequency = crate::midi_to_frequency(midi_note); track.synth.stop_note(frequency); track.current_notes.retain(|&n| n != midi_note); } } /// Schedule a pattern to play pub fn schedule_pattern( &mut self, pattern: &MelodicPattern, track_index: usize, start_time: f32, ) { let midi_notes = pattern.get_midi_notes(4); // Use octave 4 as base for (midi_note, offset_time, duration, velocity) in midi_notes { let note_on_event = ScheduledEvent { event_time: start_time + offset_time, event_type: EventType::NoteOn { midi_note, velocity, }, track_index, }; let note_off_event = ScheduledEvent { event_time: start_time + offset_time + duration, event_type: EventType::NoteOff { midi_note }, track_index, }; // Insert events in the correct position to maintain time order self.insert_event_sorted(note_on_event); self.insert_event_sorted(note_off_event); } } /// Insert an event maintaining time order fn insert_event_sorted(&mut self, event: ScheduledEvent) { let mut insert_index = self.scheduled_events.len(); for (i, existing_event) in self.scheduled_events.iter().enumerate() { if event.event_time < existing_event.event_time { insert_index = i; break; } } self.scheduled_events.insert(insert_index, event); } } /// Information about current playback state #[derive(Debug, Clone)] pub struct PlaybackInfo { pub is_playing: bool, pub current_position: f32, pub tempo: f32, pub loop_enabled: bool, pub loop_start: f32, pub loop_end: f32, pub track_count: usize, pub active_notes: usize, } /// A real-time pattern player for live performance #[derive(Debug)] pub struct PatternPlayer { pub patterns: Vec, pub current_pattern: usize, pub is_recording: bool, recorded_pattern: MelodicPattern, quantization: f32, // Beat quantization (e.g., 0.25 for sixteenth notes) } impl PatternPlayer { /// Create a new pattern player pub fn new() -> Self { Self { patterns: Vec::new(), current_pattern: 0, is_recording: false, recorded_pattern: MelodicPattern::new( crate::scales::Scale::new(crate::scales::ScaleType::Major, 60), 2, ), quantization: 0.25, } } /// Add a pattern to the player pub fn add_pattern(&mut self, pattern: MelodicPattern) { self.patterns.push(pattern); } /// Select a pattern to play pub fn select_pattern(&mut self, index: usize) { if index < self.patterns.len() { self.current_pattern = index; } } /// Start recording a new pattern pub fn start_recording(&mut self) { self.is_recording = true; self.recorded_pattern.notes.clear(); } /// Stop recording and save the pattern pub fn stop_recording(&mut self) -> Option { if self.is_recording { self.is_recording = false; if !self.recorded_pattern.notes.is_empty() { return Some(self.recorded_pattern.clone()); } } None } /// Record a note during recording pub fn record_note(&mut self, scale_degree: usize, duration: f32, velocity: f32) { if self.is_recording { let note = crate::patterns::PatternNote::new(scale_degree, 0, duration, velocity); self.recorded_pattern.add_note(note); } } /// Get the current pattern pub fn get_current_pattern(&self) -> Option<&MelodicPattern> { self.patterns.get(self.current_pattern) } /// Set quantization amount pub fn set_quantization(&mut self, quantization: f32) { self.quantization = quantization.max(0.0625); // Minimum 64th note quantization } } impl Default for PatternPlayer { fn default() -> Self { Self::new() } } // Fix the typo in the helper function fn bmp_to_samples_per_beat(bpm: f32) -> usize { bpm_to_samples_per_beat(bpm) } #[cfg(test)] mod tests { use super::*; use crate::core::{CompositionBuilder, CompositionStyle}; use crate::scales::{Scale, ScaleType}; #[test] fn test_sequencer_creation() { let sequencer = Sequencer::new(120.0); assert_eq!(sequencer.tempo, 120.0); assert!(!sequencer.is_playing); assert_eq!(sequencer.current_position, 0.0); } #[test] fn test_playback_control() { let mut sequencer = Sequencer::new(120.0); sequencer.play(); assert!(sequencer.is_playing); sequencer.pause(); assert!(!sequencer.is_playing); sequencer.stop(); assert!(!sequencer.is_playing); assert_eq!(sequencer.current_position, 0.0); } #[test] fn test_position_setting() { let mut sequencer = Sequencer::new(120.0); sequencer.set_position(4.0); assert_eq!(sequencer.current_position, 4.0); } #[test] fn test_tempo_change() { let mut sequencer = Sequencer::new(120.0); sequencer.set_tempo(140.0); assert_eq!(sequencer.tempo, 140.0); } #[test] fn test_loop_settings() { let mut sequencer = Sequencer::new(120.0); sequencer.set_loop(true, 0.0, 8.0); assert!(sequencer.loop_enabled); assert_eq!(sequencer.loop_start, 0.0); assert_eq!(sequencer.loop_end, 8.0); } #[test] fn test_composition_loading() { let mut sequencer = Sequencer::new(120.0); let mut composition = CompositionBuilder::new() .style(CompositionStyle::Electronic) .measures(4) .build(); let _ = composition.generate(); let result = sequencer.load_composition(&composition); assert!(result.is_ok()); assert_eq!(sequencer.tracks.len(), composition.tracks.len()); } #[test] fn test_pattern_player() { let mut player = PatternPlayer::new(); let scale = Scale::new(ScaleType::Major, 60); let mut pattern = MelodicPattern::new(scale, 1); pattern.generate_ascending(1); player.add_pattern(pattern); assert_eq!(player.patterns.len(), 1); player.select_pattern(0); assert_eq!(player.current_pattern, 0); assert!(player.get_current_pattern().is_some()); } #[test] fn test_pattern_recording() { let mut player = PatternPlayer::new(); player.start_recording(); assert!(player.is_recording); player.record_note(1, 1.0, 0.8); player.record_note(3, 1.0, 0.7); let recorded = player.stop_recording(); assert!(recorded.is_some()); assert_eq!(recorded.unwrap().notes.len(), 2); } #[test] fn test_real_time_notes() { let mut sequencer = Sequencer::new(120.0); // Create a simple composition to have tracks let mut composition = CompositionBuilder::new().measures(1).build(); let _ = composition.generate(); let _ = sequencer.load_composition(&composition); // Only test if we have tracks if sequencer.tracks.len() > 0 { // Test real-time note triggering sequencer.trigger_note(0, 60, 0.8); assert!(sequencer.tracks[0].current_notes.contains(&60)); sequencer.release_note(0, 60); assert!(!sequencer.tracks[0].current_notes.contains(&60)); } else { // If no tracks, just verify the sequencer doesn't crash sequencer.trigger_note(0, 60, 0.8); sequencer.release_note(0, 60); } } #[test] fn test_track_control() { let mut sequencer = Sequencer::new(120.0); // Create a composition with multiple tracks let mut composition = CompositionBuilder::new().measures(2).build(); let _ = composition.generate(); let _ = sequencer.load_composition(&composition); if sequencer.tracks.len() > 0 { // Test muting sequencer.set_track_mute(0, true); assert!(sequencer.tracks[0].is_muted); // Test volume control sequencer.set_track_volume(0, 0.5); assert_eq!(sequencer.tracks[0].volume, 0.5); } } }