Initial commit
This commit is contained in:
307
src/core.rs
Normal file
307
src/core.rs
Normal file
@ -0,0 +1,307 @@
|
||||
//! 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::scales::ScaleType;
|
||||
|
||||
/// 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
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Track {
|
||||
pub name: String,
|
||||
pub octave: u8,
|
||||
pub notes: Vec<Note>,
|
||||
pub volume: f32,
|
||||
pub instrument_type: InstrumentType,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
};
|
||||
|
||||
composition.tracks.push(track);
|
||||
|
||||
let stats = composition.get_stats();
|
||||
assert_eq!(stats.total_notes, 3);
|
||||
assert_eq!(stats.track_count, 1);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user