315 lines
8.1 KiB
Rust
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);
|
|
}
|
|
}
|