//! Core types for musical composition //! //! This module contains the fundamental data structures used throughout //! the musicgen library for representing notes, tracks, and compositions. use crate::config::EffectConfig; use crate::scales::ScaleType; use crate::synthesis::Waveform; /// Musical note representation #[derive(Debug, Clone, PartialEq)] pub struct Note { pub midi_note: u8, pub start_time: f32, pub duration: f32, pub velocity: f32, } impl Note { /// Create a new note pub fn new(midi_note: u8, start_time: f32, duration: f32, velocity: f32) -> Self { Self { midi_note, start_time, duration, velocity, } } /// Convert MIDI note to frequency in Hz pub fn frequency(&self) -> f32 { 440.0 * 2.0_f32.powf((self.midi_note as f32 - 69.0) / 12.0) } } /// Types of instruments for track categorization #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InstrumentType { Lead, Bass, Pad, Arp, Percussion, Drone, } /// A track represents a single instrument part /// Track represents a single instrument part in a composition #[derive(Debug, Clone)] pub struct Track { pub name: String, pub octave: u8, pub notes: Vec, pub volume: f32, pub instrument_type: InstrumentType, pub waveform: Waveform, pub effect_configs: Vec, } /// Composition styles (kept for compatibility) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CompositionStyle { Classical, Jazz, Electronic, Ambient, Minimalist, Generative, } /// Parameters for composition generation #[derive(Debug, Clone)] pub struct CompositionParams { pub style: CompositionStyle, pub key: u8, pub scale_type: ScaleType, pub tempo: f32, pub time_signature: (u8, u8), pub measures: usize, pub track_count: usize, pub complexity: f32, pub harmony_density: f32, pub rhythmic_density: f32, } impl Default for CompositionParams { fn default() -> Self { Self { style: CompositionStyle::Electronic, key: 60, // C4 scale_type: ScaleType::Minor, tempo: 120.0, time_signature: (4, 4), measures: 8, track_count: 4, complexity: 0.6, harmony_density: 0.7, rhythmic_density: 0.7, } } } /// A complete musical composition #[derive(Debug, Clone)] pub struct Composition { pub params: CompositionParams, pub tracks: Vec, pub chord_progression: Vec, pub total_duration: f32, } impl Composition { /// Create a new composition with the given parameters pub fn new(params: CompositionParams) -> Self { let total_duration = params.measures as f32 * params.time_signature.0 as f32 * (60.0 / params.tempo) * 4.0; Self { params, tracks: Vec::new(), chord_progression: Vec::new(), total_duration, } } /// Get all notes from all tracks sorted by start time pub fn get_all_notes(&self) -> Vec { let mut all_notes = Vec::new(); for track in &self.tracks { all_notes.extend(track.notes.clone()); } all_notes.sort_by(|a, b| a.start_time.partial_cmp(&b.start_time).unwrap()); all_notes } /// Generate the composition (placeholder for compatibility) /// /// This is a no-op method for compatibility with the old composition system. /// The composition system builds compositions directly from configuration. pub fn generate(&mut self) -> Result<(), String> { Ok(()) } /// Get composition statistics pub fn get_stats(&self) -> CompositionStats { let total_notes = self.tracks.iter().map(|t| t.notes.len()).sum(); CompositionStats { total_notes, total_duration: self.total_duration, track_count: self.tracks.len(), chord_count: self.chord_progression.len(), measures: self.params.measures, tempo: self.params.tempo, } } } /// Statistics about a composition #[derive(Debug, Clone)] pub struct CompositionStats { pub total_notes: usize, pub total_duration: f32, pub track_count: usize, pub chord_count: usize, pub measures: usize, pub tempo: f32, } /// Builder for creating compositions with a fluent API #[derive(Debug, Clone)] pub struct CompositionBuilder { params: CompositionParams, } impl CompositionBuilder { /// Create a new composition builder pub fn new() -> Self { Self { params: CompositionParams::default(), } } /// Set the composition style pub fn style(mut self, style: CompositionStyle) -> Self { self.params.style = style; self } /// Set the key pub fn key(mut self, key: u8) -> Self { self.params.key = key; self } /// Set the scale type pub fn scale(mut self, scale_type: ScaleType) -> Self { self.params.scale_type = scale_type; self } /// Set the tempo pub fn tempo(mut self, tempo: f32) -> Self { self.params.tempo = tempo; self } /// Set the number of measures pub fn measures(mut self, measures: usize) -> Self { self.params.measures = measures; self } /// Set the complexity level (0.0 to 1.0) pub fn complexity(mut self, complexity: f32) -> Self { self.params.complexity = complexity.clamp(0.0, 1.0); self } /// Set the harmony density (0.0 to 1.0) pub fn harmony_density(mut self, density: f32) -> Self { self.params.harmony_density = density.clamp(0.0, 1.0); self } /// Set the rhythmic density (0.0 to 1.0) pub fn rhythmic_density(mut self, density: f32) -> Self { self.params.rhythmic_density = density.clamp(0.0, 1.0); self } /// Build the composition pub fn build(self) -> Composition { Composition::new(self.params) } } impl Default for CompositionBuilder { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_note_creation() { let note = Note::new(60, 0.0, 1.0, 0.8); assert_eq!(note.midi_note, 60); assert_eq!(note.start_time, 0.0); assert_eq!(note.duration, 1.0); assert_eq!(note.velocity, 0.8); } #[test] fn test_note_frequency() { let note = Note::new(69, 0.0, 1.0, 0.8); // A4 assert!((note.frequency() - 440.0).abs() < 0.001); } #[test] fn test_composition_creation() { let composition = Composition::new(CompositionParams::default()); assert_eq!(composition.tracks.len(), 0); assert!(composition.total_duration > 0.0); } #[test] fn test_composition_builder() { let composition = CompositionBuilder::new() .style(CompositionStyle::Jazz) .key(67) // G .tempo(140.0) .measures(16) .complexity(0.8) .build(); assert_eq!(composition.params.style, CompositionStyle::Jazz); assert_eq!(composition.params.key, 67); assert_eq!(composition.params.tempo, 140.0); assert_eq!(composition.params.measures, 16); assert_eq!(composition.params.complexity, 0.8); } #[test] fn test_composition_stats() { let mut composition = Composition::new(CompositionParams::default()); // Add a test track with some notes let track = Track { name: "Test".to_string(), octave: 4, notes: vec![ Note::new(60, 0.0, 1.0, 0.8), Note::new(64, 1.0, 1.0, 0.8), Note::new(67, 2.0, 1.0, 0.8), ], volume: 0.8, instrument_type: InstrumentType::Lead, waveform: Waveform::Sawtooth, effect_configs: Vec::new(), }; composition.tracks.push(track); let stats = composition.get_stats(); assert_eq!(stats.total_notes, 3); assert_eq!(stats.track_count, 1); } }