Initial commit

This commit is contained in:
2025-06-20 18:52:55 -06:00
commit 9e3bf41d61
18 changed files with 8429 additions and 0 deletions

653
src/sequencer.rs Normal file
View File

@ -0,0 +1,653 @@
//! 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::core::{Composition, InstrumentType};
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<ScheduledEvent>,
// Playback state
tracks: Vec<TrackState>,
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<u8>, // Currently playing MIDI notes
}
impl TrackState {
fn new(waveform: Waveform, max_tracks: usize) -> Self {
Self {
synth: PolySynth::new(max_tracks, waveform),
volume: 1.0,
is_muted: false,
current_notes: Vec::new(),
}
}
}
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 = match track.instrument_type {
InstrumentType::Lead => Waveform::Sawtooth,
InstrumentType::Bass => Waveform::Square,
InstrumentType::Pad => Waveform::Sine,
InstrumentType::Arp => Waveform::Triangle,
InstrumentType::Percussion => Waveform::Noise,
InstrumentType::Drone => Waveform::Sine,
};
let mut track_state = TrackState::new(waveform, 8);
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 &note 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 &note 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 &note 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();
sample += track_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<MelodicPattern>,
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<MelodicPattern> {
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);
}
}
}