Skip to main content

carmen_lang/core/
mod.rs

1//! Core music theory types and functionality.
2//!
3//! This module provides fundamental music theory concepts including:
4//! - Pitch classes and pitches with MIDI conversion
5//! - Pitch class sets with set theory operations (normal form, prime form, interval class vectors)
6//! - Musical durations with dotted notes and tuplet support
7//! - Musical events, voices, staves, parts, and complete scores
8//! - Key signatures, dynamics, and musical attributes
9//! - Transposition and inversion operations
10
11pub mod traits;
12
13use crate::common::fraction::{Fraction, TupletInfo};
14use crate::errors::{CoreResult, ErrorSource};
15use std::cmp::Ordering;
16use std::collections::HashSet;
17use std::hash::{Hash, Hasher};
18use std::ops::{Add, Mul};
19use std::str::FromStr;
20pub use traits::{Interval, Invert, Transpose};
21
22/// Represents a pitch class in the chromatic scale (0-11).
23///
24/// A pitch class is an equivalence class of pitches that differ by octaves.
25/// For example, all c notes (c4, c5, c6, etc.) belong to pitch class 0.
26/// The 12 pitch classes correspond to the 12 semitones in an octave:
27/// c=0, c#=1, d=2, d#=3, e=4, f=5, f#=6, g=7, g#=8, a=9, a#=10, b=11.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29pub struct PitchClass(pub u8);
30
31impl PitchClass {
32    /// Creates a new pitch class, automatically wrapping values to 0-11 range.
33    pub fn new(value: u8) -> Self {
34        Self(value % 12)
35    }
36
37    /// Creates a pitch class from a note name and accidentals.
38    ///
39    /// # Arguments
40    /// * `note` - Base note name (c, d, e, f, g, a, b), case insensitive
41    /// * `accidentals` - Number of semitones to adjust (positive for sharps, negative for flats)
42    pub fn from_note_name(note: &str, accidentals: i32) -> CoreResult<Self> {
43        let base_pitch_class = match note.to_lowercase().as_str() {
44            "c" => 0,
45            "d" => 2,
46            "e" => 4,
47            "f" => 5,
48            "g" => 7,
49            "a" => 9,
50            "b" => 11,
51            _ => return Err(ErrorSource::Music(format!("Invalid note name: {note}"))),
52        };
53
54        let pitch_class = ((base_pitch_class + accidentals) % 12 + 12) % 12;
55        Ok(Self::new(pitch_class as u8))
56    }
57
58    /// Converts the pitch class to its enharmonic note name using sharps.
59    ///
60    /// Returns the simplest sharp-based representation of the pitch class.
61    pub fn to_note_name(&self) -> String {
62        let note_names = [
63            "c", "c#", "d", "d#", "e", "f", "f#", "g", "g#", "a", "a#", "b",
64        ];
65        note_names[self.0 as usize].to_string()
66    }
67
68    /// Calculates the interval class (smallest distance) between two pitch classes.
69    ///
70    /// Interval classes range from 0-6, representing the shortest path between
71    /// pitch classes regardless of direction (up or down).
72    pub fn interval_class_to(&self, other: &PitchClass) -> u8 {
73        let interval = self.interval_to(other);
74        std::cmp::min(interval, 12 - interval)
75    }
76}
77
78impl Transpose for PitchClass {
79    fn transpose(&self, semitones: i32) -> Self {
80        let new_value = ((self.0 as i32 + semitones) % 12 + 12) % 12;
81        Self(new_value as u8)
82    }
83}
84
85impl Invert<PitchClass> for PitchClass {
86    fn invert(&self, axis: &PitchClass) -> Self {
87        let inverted = ((axis.0 as i32 - self.0 as i32) % 12 + 12) % 12;
88        Self(inverted as u8)
89    }
90}
91
92impl Interval for PitchClass {
93    type Output = u8;
94    fn interval_to(&self, other: &PitchClass) -> Self::Output {
95        (((other.0 as i32 - self.0 as i32) % 12 + 12) % 12) as u8
96    }
97}
98
99/// Represents a specific pitch with both pitch class and octave information.
100///
101/// A pitch combines a pitch class (0-11) with an octave number to specify
102/// an exact frequency.
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
104pub struct Pitch {
105    pub pitch_class: PitchClass,
106    pub octave: i8,
107}
108
109impl Pitch {
110    /// Creates a pitch from a MIDI note number (0-127).
111    ///
112    /// MIDI note 60 = c4 (middle C), 69 = a4 (440 Hz), etc.
113    pub fn from_midi(midi_note: u8) -> Self {
114        Self {
115            pitch_class: PitchClass::new(midi_note % 12),
116            octave: (midi_note / 12) as i8 - 1,
117        }
118    }
119
120    /// Converts the pitch to a MIDI note number (0-127).
121    ///
122    /// Values are clamped to the valid MIDI range.
123    pub fn to_midi(&self) -> u8 {
124        ((self.octave + 1) as u8 * 12 + self.pitch_class.0).clamp(0, 127)
125    }
126
127    /// Creates a pitch from note name, accidentals, and octave.
128    pub fn from_note_name(note: &str, accidentals: i32, octave: i8) -> CoreResult<Self> {
129        Ok(Self {
130            pitch_class: PitchClass::from_note_name(note, accidentals)?,
131            octave,
132        })
133    }
134
135    pub fn to_note_name(&self) -> String {
136        format!("{}{}", self.pitch_class.to_note_name(), self.octave)
137    }
138}
139
140impl Interval for Pitch {
141    type Output = i32;
142    fn interval_to(&self, other: &Pitch) -> Self::Output {
143        other.to_midi() as i32 - self.to_midi() as i32
144    }
145}
146
147impl Transpose for Pitch {
148    fn transpose(&self, semitones: i32) -> Self {
149        let new_midi = (self.to_midi() as i32 + semitones).clamp(0, 127) as u8;
150        Self::from_midi(new_midi)
151    }
152}
153
154impl Invert<PitchClass> for Pitch {
155    fn invert(&self, axis: &PitchClass) -> Self {
156        let inverted_pc = self.pitch_class.invert(axis);
157        Self {
158            pitch_class: inverted_pc,
159            octave: self.octave,
160        }
161    }
162}
163
164impl Invert<Pitch> for Pitch {
165    fn invert(&self, axis: &Pitch) -> Self {
166        let axis_midi = axis.to_midi() as i32;
167        let self_midi = self.to_midi() as i32;
168        let inverted_midi = (2 * axis_midi - self_midi).clamp(0, 127) as u8;
169        Self::from_midi(inverted_midi)
170    }
171}
172
173/// A collection of unique pitch classes, used in atonal music analysis.
174///
175/// Provides set theory operations like normal form, prime form, and interval class vectors.
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct PitchClassSet {
178    pub classes: HashSet<PitchClass>,
179}
180
181impl Hash for PitchClassSet {
182    fn hash<H: Hasher>(&self, state: &mut H) {
183        // Convert to sorted vector for consistent hashing
184        let mut classes: Vec<u8> = self.classes.iter().map(|pc| pc.0).collect();
185        classes.sort();
186        classes.hash(state);
187    }
188}
189
190impl Ord for PitchClassSet {
191    fn cmp(&self, other: &Self) -> Ordering {
192        // Compare based on normal form
193        let self_normal: Vec<u8> = self.classes.iter().map(|pc| pc.0).collect::<Vec<_>>();
194        let other_normal: Vec<u8> = other.classes.iter().map(|pc| pc.0).collect::<Vec<_>>();
195
196        let mut self_sorted = self_normal;
197        let mut other_sorted = other_normal;
198        self_sorted.sort();
199        other_sorted.sort();
200
201        self_sorted.cmp(&other_sorted)
202    }
203}
204
205impl PartialOrd for PitchClassSet {
206    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
207        Some(self.cmp(other))
208    }
209}
210
211impl PitchClassSet {
212    pub fn new(classes: Vec<PitchClass>) -> Self {
213        Self {
214            classes: classes.into_iter().collect(),
215        }
216    }
217
218    pub fn from_u8_values(values: Vec<u8>) -> Self {
219        let classes = values.into_iter().map(PitchClass::new).collect();
220        Self { classes }
221    }
222
223    pub fn from_pitches(pitches: &[Pitch]) -> Self {
224        let classes = pitches.iter().map(|p| p.pitch_class).collect();
225        Self { classes }
226    }
227
228    /// Computes the normal form of the pitch class set.
229    ///
230    /// Normal form is the most compact ordering of the set, starting from 0,
231    /// with the smallest possible span. This provides a canonical representation
232    /// for comparing sets regardless of transposition.
233    pub fn normal_form(&self) -> PitchClassSet {
234        if self.classes.is_empty() {
235            return PitchClassSet::new(Vec::new());
236        }
237
238        let mut classes: Vec<u8> = self.classes.iter().map(|pc| pc.0).collect();
239        classes.sort();
240
241        let mut best_rotation = Vec::new();
242        let mut best_span = 12; // Initialize to maximum possible span
243
244        for i in 0..classes.len() {
245            let mut rotation = classes[i..].to_vec();
246            rotation.extend_from_slice(&classes[..i]);
247
248            // Normalize to start from 0
249            let first = rotation[0];
250            let normalized: Vec<u8> = rotation
251                .iter()
252                .map(|&x| (((x as i32 - first as i32) % 12 + 12) % 12) as u8)
253                .collect();
254
255            let span = self.span(&normalized);
256            if span < best_span || (span == best_span && normalized < best_rotation) {
257                best_rotation = normalized;
258                best_span = span;
259            }
260        }
261
262        PitchClassSet::from_u8_values(best_rotation)
263    }
264
265    /// Computes the prime form of the pitch class set.
266    ///
267    /// Prime form is the most compact form among the set and its inversion,
268    /// providing a canonical representation that's invariant under both
269    /// transposition and inversion. This is used to identify set classes.
270    pub fn prime_form(&self) -> PitchClassSet {
271        if self.classes.is_empty() {
272            return PitchClassSet::new(Vec::new());
273        }
274
275        let normal = self.normal_form();
276        let inverted = self.invert(&PitchClass::new(0)).normal_form();
277
278        if inverted < normal {
279            inverted
280        } else {
281            normal
282        }
283    }
284
285    /// Generates all members of the set class (transpositions and inversions).
286    ///
287    /// Returns all 24 possible transformations (12 transpositions × 2 for inversion)
288    /// of the set, which together form the complete set class.
289    pub fn set_class(&self) -> Vec<PitchClassSet> {
290        let mut result = Vec::new();
291        let mut seen = std::collections::HashSet::new();
292
293        // Generate all transpositions and inversions
294        for t in 0..12 {
295            // Transposition
296            let transposed = self.transpose(t);
297            if seen.insert(transposed.clone()) {
298                result.push(transposed);
299            }
300
301            // Inversion then transposition
302            for axis in 0..12 {
303                let inverted = self.invert(&PitchClass::new(axis)).transpose(t);
304                if seen.insert(inverted.clone()) {
305                    result.push(inverted);
306                }
307            }
308        }
309
310        // Sort by the set itself (using the Ord implementation)
311        result.sort();
312        result
313    }
314
315    fn span(&self, classes: &[u8]) -> u8 {
316        if classes.is_empty() {
317            return 0;
318        }
319        (((classes[classes.len() - 1] as i32 - classes[0] as i32) % 12 + 12) % 12) as u8
320    }
321
322    /// Calculates the interval class vector of the set.
323    ///
324    /// Returns a 6-element array counting occurrences of each interval class (1-6)
325    /// between all pairs of pitch classes in the set. This provides a unique
326    /// "fingerprint" for many set classes, useful for analysis and comparison.
327    ///
328    /// # Returns
329    /// Array where index 0 = minor seconds/major sevenths (ic1),
330    /// index 1 = major seconds/minor sevenths (ic2), etc.
331    pub fn interval_class_vector(&self) -> [u8; 6] {
332        let mut icv = [0; 6];
333        let classes: Vec<PitchClass> = self.classes.iter().cloned().collect();
334
335        for i in 0..classes.len() {
336            for j in (i + 1)..classes.len() {
337                let pc1 = classes[i];
338                let pc2 = classes[j];
339                let ic = pc1.interval_class_to(&pc2);
340                if ic > 0 && ic <= 6 {
341                    icv[ic as usize - 1] += 1;
342                }
343            }
344        }
345
346        icv
347    }
348}
349
350impl Transpose for PitchClassSet {
351    fn transpose(&self, semitones: i32) -> Self {
352        let classes = self
353            .classes
354            .iter()
355            .map(|pc| pc.transpose(semitones))
356            .collect();
357        Self { classes }
358    }
359}
360
361impl Invert<PitchClass> for PitchClassSet {
362    fn invert(&self, axis: &PitchClass) -> Self {
363        let classes = self.classes.iter().map(|pc| pc.invert(axis)).collect();
364        Self { classes }
365    }
366}
367
368/// Represents a musical duration as a fraction of a whole note.
369///
370/// Supports standard note values (whole, half, quarter, etc.), dotted notes,
371/// and complex tuplets. All durations are stored as exact fractions to avoid
372/// floating-point precision issues.
373#[derive(Debug, Clone, Copy, PartialEq)]
374pub struct Duration {
375    pub fraction: Fraction,
376}
377
378impl Duration {
379    pub fn from_fraction(numerator: u32, denominator: u32) -> Self {
380        Self {
381            fraction: Fraction::new(numerator, denominator),
382        }
383    }
384
385    pub fn whole() -> Self {
386        Self::from_fraction(1, 1)
387    }
388
389    pub fn half() -> Self {
390        Self::from_fraction(1, 2)
391    }
392
393    pub fn quarter() -> Self {
394        Self::from_fraction(1, 4)
395    }
396
397    pub fn eighth() -> Self {
398        Self::from_fraction(1, 8)
399    }
400
401    pub fn sixteenth() -> Self {
402        Self::from_fraction(1, 16)
403    }
404
405    /// Creates a dotted version of this duration.
406    ///
407    /// Each dot adds half the value of the previous duration.
408    /// For example, a dotted quarter note = 1/4 + 1/8 = 3/8.
409    pub fn with_dots(&self, dots: u32) -> Self {
410        Self {
411            fraction: self.fraction.with_dots(dots),
412        }
413    }
414
415    pub fn fraction(&self) -> &Fraction {
416        &self.fraction
417    }
418
419    /// Returns tuplet information if this duration represents a tuplet.
420    pub fn to_tuplet_info(&self) -> Option<TupletInfo> {
421        self.fraction.to_tuplet_info()
422    }
423
424    /// Returns true if this duration cannot be represented as a standard binary division.
425    pub fn is_tuplet(&self) -> bool {
426        !self.fraction.is_standard_binary()
427    }
428
429    pub fn to_fractional_string(&self) -> String {
430        format!("{}", self.fraction)
431    }
432
433    pub fn to_f64(&self) -> f64 {
434        self.fraction.to_f64()
435    }
436}
437
438impl<'b> Add<&'b Duration> for &Duration {
439    type Output = Duration;
440
441    fn add(self, other: &'b Duration) -> Self::Output {
442        Duration {
443            fraction: self.fraction + other.fraction,
444        }
445    }
446}
447
448impl Mul<u32> for &Duration {
449    type Output = Duration;
450
451    fn mul(self, rhs: u32) -> Self::Output {
452        Duration {
453            fraction: self.fraction * rhs,
454        }
455    }
456}
457
458impl Mul<Fraction> for &Duration {
459    type Output = Duration;
460
461    fn mul(self, rhs: Fraction) -> Self::Output {
462        Duration {
463            fraction: self.fraction * rhs,
464        }
465    }
466}
467
468/// Musical dynamics (volume/intensity markings).
469///
470/// Represents standard dynamic markings from pppp (very very quiet) to ffff (very very loud),
471/// plus the ability to specify exact MIDI velocity values.
472#[derive(Debug, Clone, Copy, PartialEq)]
473pub enum Dynamic {
474    Pppp,
475    Ppp,
476    Pp,
477    P,
478    Mp,
479    Mf,
480    F,
481    Ff,
482    Fff,
483    Ffff,
484    Velocity(u8), // MIDI velocity 0-127
485}
486
487impl FromStr for Dynamic {
488    type Err = ErrorSource;
489
490    fn from_str(s: &str) -> Result<Self, Self::Err> {
491        match s {
492            "pppp" => Ok(Dynamic::Pppp),
493            "ppp" => Ok(Dynamic::Ppp),
494            "pp" => Ok(Dynamic::Pp),
495            "p" => Ok(Dynamic::P),
496            "mp" => Ok(Dynamic::Mp),
497            "mf" => Ok(Dynamic::Mf),
498            "f" => Ok(Dynamic::F),
499            "ff" => Ok(Dynamic::Ff),
500            "fff" => Ok(Dynamic::Fff),
501            "ffff" => Ok(Dynamic::Ffff),
502            _ => Err(ErrorSource::Argument(format!("Invalid dynamic: {s}"))),
503        }
504    }
505}
506
507impl Dynamic {
508    /// Converts the dynamic marking to a MIDI velocity value (0-127).
509    pub fn to_velocity(&self) -> u8 {
510        match self {
511            Dynamic::Pppp => 8,
512            Dynamic::Ppp => 16,
513            Dynamic::Pp => 32,
514            Dynamic::P => 48,
515            Dynamic::Mp => 64,
516            Dynamic::Mf => 80,
517            Dynamic::F => 96,
518            Dynamic::Ff => 112,
519            Dynamic::Fff => 120,
520            Dynamic::Ffff => 127,
521            Dynamic::Velocity(v) => *v,
522        }
523    }
524}
525
526/// Musical articulation and expression attributes.
527///
528/// These modify how notes are played or performed.
529#[derive(Debug, Clone, Copy, PartialEq)]
530pub enum Attribute {
531    Staccato,
532    Accent,
533    Tenuto,
534}
535
536impl FromStr for Attribute {
537    type Err = ErrorSource;
538
539    fn from_str(s: &str) -> Result<Self, Self::Err> {
540        match s {
541            "staccato" => Ok(Attribute::Staccato),
542            "accent" => Ok(Attribute::Accent),
543            "tenuto" => Ok(Attribute::Tenuto),
544            _ => Err(ErrorSource::Argument(format!("Invalid attribute: {s}"))),
545        }
546    }
547}
548
549/// A collection of pitches played simultaneously.
550#[derive(Debug, Clone, PartialEq)]
551pub struct Chord {
552    pub pitches: Vec<Pitch>,
553}
554
555impl Chord {
556    pub fn to_pitch_class_set(&self) -> PitchClassSet {
557        PitchClassSet::from_pitches(&self.pitches)
558    }
559}
560
561impl Transpose for Chord {
562    fn transpose(&self, semitones: i32) -> Self {
563        let pitches = self
564            .pitches
565            .iter()
566            .map(|p| p.transpose(semitones))
567            .collect();
568        Self { pitches }
569    }
570}
571
572impl Invert<PitchClass> for Chord {
573    fn invert(&self, axis: &PitchClass) -> Self {
574        let pitches = self.pitches.iter().map(|p| p.invert(axis)).collect();
575        Self { pitches }
576    }
577}
578
579impl Invert<Pitch> for Chord {
580    fn invert(&self, axis: &Pitch) -> Self {
581        let pitches = self.pitches.iter().map(|p| p.invert(axis)).collect();
582        Self { pitches }
583    }
584}
585
586/// The musical content of an event - what is actually played or heard.
587#[derive(Debug, Clone, PartialEq)]
588pub enum EventContent {
589    Note(Pitch),                 // A single note
590    Chord(Chord),                // Multiple notes played together
591    Rest,                        // Rest
592    Sequence(Vec<MusicalEvent>), // A sequence of sub-events
593}
594
595/// A complete musical event with timing, content, and expression information.
596///
597/// This represents any musical occurrence - notes, chords, rests, or sequences
598/// of other events. It includes all the information needed to render the event
599/// in a musical context.
600#[derive(Debug, Clone, PartialEq)]
601pub struct MusicalEvent {
602    pub duration: Duration,         // How long the event lasts
603    pub content: EventContent,      // What is played (note, chord, rest, sequence)
604    pub dynamic: Option<Dynamic>,   // Volume/intensity
605    pub attributes: Vec<Attribute>, // Aattributes
606    pub offset: Fraction,           // Timing offset from the expected position
607}
608
609impl MusicalEvent {
610    fn new(duration: Duration, content: EventContent) -> Self {
611        Self {
612            duration,
613            content,
614            dynamic: None,
615            attributes: Vec::new(),
616            offset: Fraction::new(0, 1),
617        }
618    }
619
620    pub fn note(pitch: Pitch, duration: Duration) -> Self {
621        Self::new(duration, EventContent::Note(pitch))
622    }
623
624    pub fn chord(chord: Chord, duration: Duration) -> Self {
625        Self::new(duration, EventContent::Chord(chord))
626    }
627
628    pub fn rest(duration: Duration) -> Self {
629        Self::new(duration, EventContent::Rest)
630    }
631
632    pub fn set_dynamic(&mut self, dynamic: Dynamic) {
633        self.dynamic = Some(dynamic);
634    }
635
636    pub fn with_dynamic(mut self, dynamic: Dynamic) -> Self {
637        self.set_dynamic(dynamic);
638        self
639    }
640
641    pub fn add_attribute(&mut self, attribute: Attribute) {
642        self.attributes.push(attribute);
643    }
644
645    pub fn with_attribute(mut self, attribute: Attribute) -> Self {
646        self.add_attribute(attribute);
647        self
648    }
649
650    pub fn set_attributes(&mut self, attributes: Vec<Attribute>) {
651        self.attributes = attributes;
652    }
653
654    pub fn with_attributes(mut self, attributes: Vec<Attribute>) -> Self {
655        self.set_attributes(attributes);
656        self
657    }
658
659    pub fn set_offset(&mut self, offset: Fraction) {
660        self.offset = offset;
661    }
662
663    pub fn with_offset(mut self, offset: Fraction) -> Self {
664        self.set_offset(offset);
665        self
666    }
667
668    /// Counts the number of atomic (non-sequence) events contained within this event.
669    pub fn atomic_event_count(&self) -> usize {
670        match &self.content {
671            EventContent::Sequence(events) => events.iter().map(|e| e.atomic_event_count()).sum(),
672            _ => 1,
673        }
674    }
675
676    /// Calculates the total duration including any nested sequences.
677    pub fn total_duration(&self) -> Fraction {
678        match &self.content {
679            EventContent::Sequence(events) => {
680                if let Some(last_event) = events.last() {
681                    last_event.offset + last_event.total_duration()
682                } else {
683                    Fraction::new(0, 1)
684                }
685            }
686            _ => self.duration.fraction,
687        }
688    }
689}
690
691impl Transpose for MusicalEvent {
692    fn transpose(&self, semitones: i32) -> Self {
693        let content = match &self.content {
694            EventContent::Note(pitch) => EventContent::Note(pitch.transpose(semitones)),
695            EventContent::Chord(chord) => EventContent::Chord(chord.transpose(semitones)),
696            EventContent::Rest => EventContent::Rest,
697            EventContent::Sequence(events) => {
698                EventContent::Sequence(events.iter().map(|e| e.transpose(semitones)).collect())
699            }
700        };
701
702        Self {
703            content,
704            duration: self.duration,
705            dynamic: self.dynamic,
706            attributes: self.attributes.clone(),
707            offset: self.offset,
708        }
709    }
710}
711
712impl Invert<PitchClass> for MusicalEvent {
713    fn invert(&self, axis: &PitchClass) -> Self {
714        let content = match &self.content {
715            EventContent::Note(pitch) => EventContent::Note(pitch.invert(axis)),
716            EventContent::Chord(chord) => EventContent::Chord(chord.invert(axis)),
717            EventContent::Rest => EventContent::Rest,
718            EventContent::Sequence(events) => {
719                EventContent::Sequence(events.iter().map(|e| e.invert(axis)).collect())
720            }
721        };
722
723        Self {
724            content,
725            duration: self.duration,
726            dynamic: self.dynamic,
727            attributes: self.attributes.clone(),
728            offset: self.offset,
729        }
730    }
731}
732
733impl Invert<Pitch> for MusicalEvent {
734    fn invert(&self, axis: &Pitch) -> Self {
735        let content = match &self.content {
736            EventContent::Note(pitch) => EventContent::Note(pitch.invert(axis)),
737            EventContent::Chord(chord) => EventContent::Chord(chord.invert(axis)),
738            EventContent::Rest => EventContent::Rest,
739            EventContent::Sequence(events) => {
740                EventContent::Sequence(events.iter().map(|e| e.invert(axis)).collect())
741            }
742        };
743
744        Self {
745            content,
746            duration: self.duration,
747            dynamic: self.dynamic,
748            attributes: self.attributes.clone(),
749            offset: self.offset,
750        }
751    }
752}
753
754/// Musical key signatures defining the tonal center and scale.
755#[derive(Debug, Clone, PartialEq)]
756pub enum KeySignature {
757    Major(u8),       // Major keys: 0=C, 1=G, 2=D, etc.
758    Minor(u8),       // Minor keys: 0=A, 1=E, 2=B, etc.
759    Custom(Vec<u8>), // Custom
760}
761
762impl std::fmt::Display for KeySignature {
763    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
764        match self {
765            KeySignature::Major(val) => {
766                let keys = [
767                    "C", "G", "D", "A", "E", "B", "F#", "C#", "F", "Bb", "Eb", "Ab", "Db", "Gb",
768                    "Cb",
769                ];
770                if (*val as usize) < keys.len() {
771                    write!(f, "{} Major", keys[*val as usize])
772                } else {
773                    write!(f, "Unknown Major Key({val})")
774                }
775            }
776            KeySignature::Minor(val) => {
777                let keys = [
778                    "A", "E", "B", "F#", "C#", "G#", "D#", "A#", "D", "G", "C", "F", "Bb", "Eb",
779                    "Ab",
780                ];
781                if (*val as usize) < keys.len() {
782                    write!(f, "{} Minor", keys[*val as usize])
783                } else {
784                    write!(f, "Unknown Minor Key({val})")
785                }
786            }
787            KeySignature::Custom(vals) => write!(f, "Custom Key Signature {vals:?}"),
788        }
789    }
790}
791
792/// Keys for musical metadata that can change during a piece.
793#[derive(Debug, Clone, PartialEq)]
794pub enum MetadataKey {
795    Title,
796    Composer,
797    Copyright,
798    Tempo,
799    TimeSignature,
800    KeySignature,
801    Clef,
802    Instrument,
803    Channel,
804    Offset,
805}
806
807impl std::fmt::Display for MetadataKey {
808    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
809        match self {
810            MetadataKey::Title => write!(f, "title"),
811            MetadataKey::Composer => write!(f, "composer"),
812            MetadataKey::Copyright => write!(f, "copyright"),
813            MetadataKey::Tempo => write!(f, "tempo"),
814            MetadataKey::TimeSignature => write!(f, "time_signature"),
815            MetadataKey::KeySignature => write!(f, "key_signature"),
816            MetadataKey::Clef => write!(f, "clef"),
817            MetadataKey::Instrument => write!(f, "instrument"),
818            MetadataKey::Channel => write!(f, "channel"),
819            MetadataKey::Offset => write!(f, "offset"),
820        }
821    }
822}
823
824/// Values for musical metadata, supporting various data types.
825#[derive(Debug, Clone, PartialEq)]
826pub enum MetadataValue {
827    String(String),
828    Number(f64),
829    TimeSignature(u32, u32),
830    KeySignature(KeySignature),
831    Clef(Clef),
832}
833
834impl std::fmt::Display for MetadataValue {
835    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
836        match self {
837            MetadataValue::String(s) => write!(f, "\"{s}\""),
838            MetadataValue::Number(n) => write!(f, "{n}"),
839            MetadataValue::TimeSignature(num, den) => write!(f, "{num}/{den}"),
840            MetadataValue::KeySignature(ks) => write!(f, "{ks}"),
841            MetadataValue::Clef(c) => write!(f, "{c}"),
842        }
843    }
844}
845
846/// Represents a change in musical context (tempo, key signature, etc.) at a specific time.
847#[derive(Debug, Clone, PartialEq)]
848pub struct ContextChange {
849    pub key: MetadataKey,      // What aspect of the context is changing
850    pub value: MetadataValue,  // The new value
851    pub time_offset: Fraction, // When the change occurs
852}
853
854/// Musical clefs that determine the pitch reference for staff notation.
855#[derive(Debug, Clone, Copy, PartialEq, Default)]
856pub enum Clef {
857    #[default]
858    Treble,
859    Bass,
860    Alto,
861    Tenor,
862}
863
864impl std::fmt::Display for Clef {
865    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
866        write!(f, "{self:?}")
867    }
868}
869
870impl FromStr for Clef {
871    type Err = ErrorSource;
872
873    fn from_str(s: &str) -> Result<Self, Self::Err> {
874        match s.to_lowercase().as_str() {
875            "treble" => Ok(Clef::Treble),
876            "bass" => Ok(Clef::Bass),
877            "alto" => Ok(Clef::Alto),
878            "tenor" => Ok(Clef::Tenor),
879            _ => Err(ErrorSource::Argument(format!("Invalid clef: {s}"))),
880        }
881    }
882}
883
884/// A single melodic line or voice within a musical staff.
885///
886/// In musical notation, multiple voices can share the same staff.
887#[derive(Debug, Clone, PartialEq, Default)]
888pub struct Voice {
889    pub events: Vec<MusicalEvent>,
890}
891
892impl Voice {
893    pub fn new() -> Self {
894        Self::default()
895    }
896
897    pub fn add_event(&mut self, event: MusicalEvent) {
898        self.events.push(event);
899    }
900
901    pub fn with_event(mut self, event: MusicalEvent) -> Self {
902        self.add_event(event);
903        self
904    }
905
906    pub fn total_duration(&self) -> Fraction {
907        self.events
908            .iter()
909            .map(|e| e.offset + e.total_duration())
910            .max()
911            .unwrap_or_else(|| Fraction::new(0, 1))
912    }
913}
914
915/// A musical staff containing one or more voices.
916#[derive(Debug, Clone, PartialEq)]
917pub struct Staff {
918    pub number: u32,                         // Staff number for ordering
919    pub voices: Vec<Voice>,                  // Independent voices on this staff
920    pub context_changes: Vec<ContextChange>, // Staff-specific context changes
921}
922
923impl Staff {
924    pub fn new(number: u32) -> Self {
925        Self {
926            number,
927            voices: Vec::new(),
928            context_changes: Vec::new(),
929        }
930    }
931
932    pub fn add_voice(mut self, voice: Voice) -> Self {
933        self.voices.push(voice);
934        self
935    }
936
937    pub fn add_context_change(&mut self, change: ContextChange) {
938        self.context_changes.push(change);
939    }
940
941    pub fn with_context_change(mut self, change: ContextChange) -> Self {
942        self.add_context_change(change);
943        self
944    }
945
946    pub fn total_duration(&self) -> Fraction {
947        self.voices
948            .iter()
949            .map(|v| v.total_duration())
950            .max()
951            .unwrap_or_else(|| Fraction::new(0, 1))
952    }
953}
954
955/// A musical part representing a single instrument or voice in an ensemble.
956///
957/// Parts can contain events directly or organize them into multiple staves
958/// (e.g., piano parts typically have two staves for left and right hands).
959#[derive(Debug, Clone, PartialEq)]
960pub struct Part {
961    pub name: Option<String>,                // Display name of the part
962    pub instrument: Option<String>,          // Instrument name
963    pub channel: Option<u8>,                 // MIDI channel assignment
964    pub offset: Fraction,                    // Timing offset for the entire part
965    pub events: Vec<MusicalEvent>,           // Events not assigned to specific staves
966    pub context_changes: Vec<ContextChange>, // Part-level context changes
967    pub staves: Vec<Staff>,                  // Multiple staves (e.g., piano grand staff)
968}
969
970impl Default for Part {
971    fn default() -> Self {
972        Self {
973            name: None,
974            instrument: None,
975            channel: None,
976            offset: Fraction::new(0, 1),
977            events: Vec::new(),
978            context_changes: Vec::new(),
979            staves: Vec::new(),
980        }
981    }
982}
983
984impl Part {
985    pub fn new(name: Option<String>) -> Self {
986        Self {
987            name,
988            instrument: None,
989            channel: None,
990            offset: Fraction::new(0, 1),
991            events: Vec::new(),
992            context_changes: Vec::new(),
993            staves: Vec::new(),
994        }
995    }
996
997    pub fn set_instrument(&mut self, instrument: String) {
998        self.instrument = Some(instrument);
999    }
1000
1001    pub fn with_instrument(mut self, instrument: String) -> Self {
1002        self.set_instrument(instrument);
1003        self
1004    }
1005
1006    pub fn set_channel(&mut self, channel: u8) {
1007        self.channel = Some(channel);
1008    }
1009
1010    pub fn with_channel(mut self, channel: u8) -> Self {
1011        self.set_channel(channel);
1012        self
1013    }
1014
1015    pub fn set_offset(&mut self, offset: Fraction) {
1016        self.offset = offset;
1017    }
1018
1019    pub fn with_offset(mut self, offset: Fraction) -> Self {
1020        self.set_offset(offset);
1021        self
1022    }
1023
1024    pub fn add_event(&mut self, event: MusicalEvent) {
1025        self.events.push(event);
1026    }
1027
1028    pub fn with_event(mut self, event: MusicalEvent) -> Self {
1029        self.add_event(event);
1030        self
1031    }
1032
1033    pub fn add_staff(&mut self, staff: Staff) {
1034        self.staves.push(staff);
1035    }
1036
1037    pub fn with_staff(mut self, staff: Staff) -> Self {
1038        self.add_staff(staff);
1039        self
1040    }
1041
1042    pub fn add_context_change(&mut self, change: ContextChange) {
1043        self.context_changes.push(change);
1044    }
1045
1046    pub fn with_context_change(mut self, change: ContextChange) -> Self {
1047        self.add_context_change(change);
1048        self
1049    }
1050
1051    /// Returns references to all events in this part, including those in staves.
1052    pub fn all_events(&self) -> Vec<&MusicalEvent> {
1053        let mut events = Vec::new();
1054
1055        events.extend(self.events.iter());
1056
1057        for staff in &self.staves {
1058            for voice in &staff.voices {
1059                events.extend(voice.events.iter());
1060            }
1061        }
1062
1063        events
1064    }
1065
1066    /// Counts all atomic events in this part (including nested sequences).
1067    pub fn total_event_count(&self) -> usize {
1068        let events_count: usize = self.events.iter().map(|e| e.atomic_event_count()).sum();
1069        let staves_count: usize = self
1070            .staves
1071            .iter()
1072            .map(|s| {
1073                s.voices
1074                    .iter()
1075                    .flat_map(|v| &v.events)
1076                    .map(|e| e.atomic_event_count())
1077                    .sum::<usize>()
1078            })
1079            .sum();
1080        events_count + staves_count
1081    }
1082
1083    pub fn total_duration(&self) -> Fraction {
1084        let main_duration = self
1085            .events
1086            .iter()
1087            .map(|e| e.offset + e.total_duration())
1088            .max()
1089            .unwrap_or_else(|| Fraction::new(0, 1));
1090
1091        let staff_duration = self
1092            .staves
1093            .iter()
1094            .map(|s| s.total_duration())
1095            .max()
1096            .unwrap_or_else(|| Fraction::new(0, 1));
1097
1098        self.offset + main_duration.max(staff_duration)
1099    }
1100
1101    /// Merges another part into this one, appending its content.
1102    ///
1103    /// The events, staves, and context changes from the `other` part are appended
1104    /// to this one, with their timing adjusted to occur after this part's content
1105    /// has finished.
1106    ///
1107    /// # Arguments
1108    ///
1109    /// * `other` - The part to merge into this one.
1110    ///
1111    /// # Returns
1112    ///
1113    /// A new `Part` containing the merged musical content.
1114    pub fn merge(&self, other: &Part) -> Part {
1115        let mut new_part = self.clone();
1116        let base_offset = self.total_duration();
1117
1118        // Merge top-level events from the other part, adjusting their offsets.
1119        new_part.events.extend(other.events.iter().map(|e| {
1120            let mut new_event = e.clone();
1121            new_event.offset = e.offset + base_offset;
1122            new_event
1123        }));
1124
1125        // Merge context changes, adjusting their time offsets.
1126        new_part
1127            .context_changes
1128            .extend(other.context_changes.iter().map(|c| ContextChange {
1129                time_offset: c.time_offset + base_offset,
1130                ..c.clone()
1131            }));
1132
1133        // Merge staves from the other part.
1134        for other_staff in &other.staves {
1135            if let Some(self_staff) = new_part
1136                .staves
1137                .iter_mut()
1138                .find(|s| s.number == other_staff.number)
1139            {
1140                // If a staff with the same number exists, merge the voices.
1141                let staff_offset = self_staff.total_duration();
1142                for (i, other_voice) in other_staff.voices.iter().enumerate() {
1143                    let new_events: Vec<MusicalEvent> = other_voice
1144                        .events
1145                        .iter()
1146                        .map(|e| {
1147                            let mut new_event = e.clone();
1148                            new_event.offset = e.offset + staff_offset;
1149                            new_event
1150                        })
1151                        .collect();
1152
1153                    if i < self_staff.voices.len() {
1154                        // Append events to the corresponding existing voice.
1155                        self_staff.voices[i].events.extend(new_events);
1156                    } else {
1157                        // If the other staff has more voices, add them as new voices.
1158                        let mut new_voice = Voice::new();
1159                        new_voice.events = new_events;
1160                        self_staff.voices.push(new_voice);
1161                    }
1162                }
1163            } else {
1164                // If the staff does not exist in the current part, add it as a new staff.
1165                let mut new_staff = other_staff.clone();
1166                for voice in &mut new_staff.voices {
1167                    for event in &mut voice.events {
1168                        event.offset += base_offset;
1169                    }
1170                }
1171                new_part.staves.push(new_staff);
1172            }
1173        }
1174
1175        new_part
1176    }
1177}
1178
1179/// A collection of musical parts that play simultaneously.
1180///
1181/// Represents the complete musical content at a given level,
1182/// whether for a full score or a movement within a larger work.
1183#[derive(Debug, Clone, PartialEq, Default)]
1184pub struct Timeline {
1185    pub parts: Vec<Part>,                    // All parts in this timeline
1186    pub context_changes: Vec<ContextChange>, // Global context changes
1187}
1188
1189impl Timeline {
1190    pub fn add_part(&mut self, part: Part) {
1191        self.parts.push(part);
1192    }
1193
1194    pub fn with_part(mut self, part: Part) -> Self {
1195        self.parts.push(part);
1196        self
1197    }
1198
1199    pub fn add_parts(&mut self, parts: Vec<Part>) {
1200        self.parts.extend(parts);
1201    }
1202
1203    pub fn with_parts(mut self, parts: Vec<Part>) -> Self {
1204        self.parts.extend(parts);
1205        self
1206    }
1207
1208    pub fn add_context_change(&mut self, change: ContextChange) {
1209        self.context_changes.push(change);
1210    }
1211
1212    pub fn with_context_change(mut self, change: ContextChange) -> Self {
1213        self.add_context_change(change);
1214        self
1215    }
1216
1217    pub fn total_duration(&self) -> Fraction {
1218        self.parts
1219            .iter()
1220            .map(|p| p.total_duration())
1221            .max()
1222            .unwrap_or_else(|| Fraction::new(0, 1))
1223    }
1224}
1225
1226/// A single movement within a larger musical work.
1227///
1228/// Classical works often consist of multiple movements with different
1229/// tempos, keys, and characters (e.g., a symphony's four movements).
1230#[derive(Debug, Clone, PartialEq)]
1231pub struct Movement {
1232    pub name: Option<String>,                // Movement name or number
1233    pub timeline: Timeline,                  // The musical content
1234    pub context_changes: Vec<ContextChange>, // Movement-level context changes
1235}
1236
1237impl Movement {
1238    pub fn new(name: Option<String>) -> Self {
1239        Self {
1240            name,
1241            timeline: Timeline::default(),
1242            context_changes: Vec::new(),
1243        }
1244    }
1245
1246    pub fn set_timeline(&mut self, timeline: Timeline) {
1247        self.timeline = timeline;
1248    }
1249
1250    pub fn with_timeline(mut self, timeline: Timeline) -> Self {
1251        self.set_timeline(timeline);
1252        self
1253    }
1254
1255    pub fn add_context_change(&mut self, change: ContextChange) {
1256        self.context_changes.push(change);
1257    }
1258
1259    pub fn with_context_change(mut self, change: ContextChange) -> Self {
1260        self.add_context_change(change);
1261        self
1262    }
1263
1264    pub fn total_duration(&self) -> Fraction {
1265        self.timeline.total_duration()
1266    }
1267}
1268
1269/// The top-level container for a complete musical work.
1270///
1271/// A score can either be:
1272/// - Multi-movement (symphony, sonata) with separate movements
1273/// - Single-timeline (song, short piece) with direct timeline
1274#[derive(Debug, Clone, Default, PartialEq)]
1275pub struct Score {
1276    pub name: Option<String>,                // Title of the work
1277    pub movements: Vec<Movement>,            // Multiple movements (if multi-movement)
1278    pub timeline: Option<Timeline>,          // Direct timeline (if single-movement)
1279    pub context_changes: Vec<ContextChange>, // Score-level context changes
1280}
1281
1282impl Score {
1283    pub fn set_name(&mut self, name: String) {
1284        self.name = Some(name);
1285    }
1286    pub fn with_name(mut self, name: String) -> Self {
1287        self.set_name(name);
1288        self
1289    }
1290
1291    pub fn add_movement(&mut self, movement: Movement) {
1292        self.movements.push(movement);
1293    }
1294
1295    pub fn with_movement(mut self, movement: Movement) -> Self {
1296        self.add_movement(movement);
1297        self
1298    }
1299
1300    pub fn set_timeline(&mut self, timeline: Timeline) {
1301        self.timeline = Some(timeline);
1302    }
1303
1304    pub fn with_timeline(mut self, timeline: Timeline) -> Self {
1305        self.set_timeline(timeline);
1306        self
1307    }
1308
1309    pub fn add_context_change(&mut self, change: ContextChange) {
1310        self.context_changes.push(change);
1311    }
1312
1313    pub fn with_context_change(mut self, change: ContextChange) -> Self {
1314        self.add_context_change(change);
1315        self
1316    }
1317
1318    pub fn total_duration(&self) -> Fraction {
1319        if let Some(timeline) = &self.timeline {
1320            timeline.total_duration()
1321        } else {
1322            self.movements.iter().map(|m| m.total_duration()).sum()
1323        }
1324    }
1325
1326    /// Returns true if this score contains multiple movements.
1327    pub fn is_multi_movement(&self) -> bool {
1328        !self.movements.is_empty()
1329    }
1330}
1331
1332/// Calculates the interval between two pitches in semitones.
1333///
1334/// # Arguments
1335/// * `ordered` - If true, returns signed interval (p2 - p1); if false, returns absolute value
1336pub fn pitch_interval(p1: &Pitch, p2: &Pitch, ordered: bool) -> i32 {
1337    let interval = p1.interval_to(p2);
1338    if ordered {
1339        interval
1340    } else {
1341        interval.abs()
1342    }
1343}
1344
1345/// Calculates the interval between two pitch classes.
1346///
1347/// # Arguments
1348/// * `ordered` - If true, returns directed interval (0-11); if false, returns interval class (0-6)
1349pub fn pitch_class_interval(pc1: &PitchClass, pc2: &PitchClass, ordered: bool) -> u8 {
1350    if ordered {
1351        pc1.interval_to(pc2)
1352    } else {
1353        pc1.interval_class_to(pc2)
1354    }
1355}