Files
musicgen/src/core.rs

315 lines
8.1 KiB
Rust

//! 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<Note>,
pub volume: f32,
pub instrument_type: InstrumentType,
pub waveform: Waveform,
pub effect_configs: Vec<EffectConfig>,
}
/// 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<Track>,
pub chord_progression: Vec<u8>,
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<Note> {
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);
}
}