use num_rational::Ratio;
use std::any::Any;
use thiserror::Error;
pub use sim_lib_midi_core::{Channel, ChannelMessage, MidiEvent, MidiPayload, TickTime};
pub use sim_lib_midi_smf::SmfFile;
pub use sim_lib_pitch_core::{Pitch, PitchClass, PitchError, parse_pitch};
use crate::{arranger::Arranger, piano_roll::PianoRoll};
pub type Time = Ratio<i64>;
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum MusicError {
#[error("duration cannot be negative")]
NegativeDuration,
#[error("onset cannot be negative")]
NegativeOnset,
#[error("tempo must be positive")]
InvalidTempo,
#[error("time signature denominator must be non-zero")]
InvalidTimeSignature,
#[error("melody items must be monophonic")]
NonMonophonicMelody,
#[error("play range end cannot precede start")]
InvalidTimeRange,
#[error("play PPQ must be greater than zero")]
InvalidPpq,
#[error("lane {lane} target {target} is invalid for its event kind")]
InvalidLaneTarget {
lane: String,
target: String,
},
#[error("piano-roll time grid must have positive TPQ and step")]
InvalidPianoRollGrid,
#[error("piano-roll lane {lane} of kind {lane_kind} cannot contain {cell_kind} cells")]
PianoRollLaneCellMismatch {
lane: String,
lane_kind: String,
cell_kind: String,
},
}
pub trait MusicObject: Send + Sync + Any {
fn kind(&self) -> &'static str;
fn duration(&self) -> Time;
fn voices<'a>(&'a self, offset: Time, out: &mut Vec<TimedAtom<'a>>);
fn clone_box(&self) -> Box<dyn MusicObject>;
fn as_any(&self) -> &dyn Any;
}
impl Clone for Box<dyn MusicObject> {
fn clone(&self) -> Self {
self.clone_box()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TimedAtom<'a> {
pub onset: Time,
pub atom: AtomRef<'a>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AtomRef<'a> {
Note(Note),
Rest(Rest),
Phantom(std::marker::PhantomData<&'a ()>),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Articulation {
Normal,
Staccato,
Legato,
Tenuto,
Accent,
Marcato,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Note {
pub duration: Time,
pub pitch: Pitch,
pub velocity: u8,
pub channel: Channel,
pub articulation: Articulation,
}
impl Note {
pub fn new(
duration: Time,
pitch: Pitch,
velocity: u8,
channel: Channel,
articulation: Articulation,
) -> Result<Self, MusicError> {
ensure_non_negative(duration)?;
Ok(Self {
duration,
pitch,
velocity,
channel,
articulation,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Rest {
pub duration: Time,
}
impl Rest {
pub fn new(duration: Time) -> Result<Self, MusicError> {
ensure_non_negative(duration)?;
Ok(Self { duration })
}
}
#[derive(Clone)]
pub struct Par {
pub children: Vec<Box<dyn MusicObject>>,
}
impl std::fmt::Debug for Par {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Par")
.field("children_len", &self.children.len())
.finish()
}
}
#[derive(Clone)]
pub struct Seq {
pub children: Vec<Box<dyn MusicObject>>,
}
impl std::fmt::Debug for Seq {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Seq")
.field("children_len", &self.children.len())
.finish()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Chord {
pub duration: Time,
pub symbol: String,
pub pitches: Vec<Pitch>,
pub velocity: u8,
pub channel: Channel,
}
impl Chord {
pub fn new(
duration: Time,
symbol: impl Into<String>,
pitches: Vec<Pitch>,
velocity: u8,
channel: Channel,
) -> Result<Self, MusicError> {
ensure_non_negative(duration)?;
Ok(Self {
duration,
symbol: symbol.into(),
pitches,
velocity,
channel,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MelodyItem {
Note(Note),
Rest(Rest),
}
impl MelodyItem {
pub fn duration(&self) -> Time {
match self {
Self::Note(note) => note.duration,
Self::Rest(rest) => rest.duration,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Melody {
pub items: Vec<MelodyItem>,
}
impl Melody {
pub fn new(items: Vec<MelodyItem>) -> Result<Self, MusicError> {
for item in &items {
ensure_non_negative(item.duration())?;
}
Ok(Self { items })
}
pub fn total_duration(&self) -> Time {
self.items
.iter()
.fold(Time::from_integer(0), |sum, item| sum + item.duration())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Progression {
pub key: Option<String>,
pub chords: Vec<Chord>,
}
impl Progression {
pub fn new(key: Option<String>, chords: Vec<Chord>) -> Result<Self, MusicError> {
for chord in &chords {
ensure_non_negative(chord.duration)?;
}
Ok(Self { key, chords })
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Counterpoint {
pub voices: Vec<Melody>,
pub voice_names: Vec<String>,
}
impl Counterpoint {
pub fn new(voices: Vec<Melody>, voice_names: Vec<String>) -> Result<Self, MusicError> {
let voice_names = normalize_voice_names(voices.len(), voice_names);
Ok(Self {
voices,
voice_names,
})
}
pub fn normalized_voice_names(&self) -> Vec<String> {
normalize_voice_names(self.voices.len(), self.voice_names.clone())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MidiTrackObj {
pub events: Vec<MidiEvent>,
pub channel_hint: Option<Channel>,
}
impl MidiTrackObj {
pub fn new(events: Vec<MidiEvent>, channel_hint: Option<Channel>) -> Self {
Self {
events,
channel_hint,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MidiFileObj {
pub file: SmfFile,
}
impl MidiFileObj {
pub fn new(file: SmfFile) -> Self {
Self { file }
}
}
#[derive(Clone, Debug)]
pub struct Score {
pub tempo_bpm: u32,
pub time_signature: (u8, u8),
pub key: Option<String>,
pub body: Music,
}
impl Score {
pub fn new(
tempo_bpm: u32,
time_signature: (u8, u8),
key: Option<String>,
body: Music,
) -> Result<Self, MusicError> {
if tempo_bpm == 0 {
return Err(MusicError::InvalidTempo);
}
if time_signature.1 == 0 {
return Err(MusicError::InvalidTimeSignature);
}
Ok(Self {
tempo_bpm,
time_signature,
key,
body,
})
}
}
#[derive(Clone)]
pub enum Music {
Note(Note),
Rest(Rest),
Par(Par),
Seq(Seq),
Chord(Chord),
Melody(Melody),
Progression(Progression),
Counterpoint(Counterpoint),
PianoRoll(PianoRoll),
Arranger(Arranger),
MidiTrack(MidiTrackObj),
MidiFile(MidiFileObj),
}
impl std::fmt::Debug for Music {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Note(_) => f.write_str("Music::Note(..)"),
Self::Rest(_) => f.write_str("Music::Rest(..)"),
Self::Par(par) => f.debug_tuple("Music::Par").field(par).finish(),
Self::Seq(seq) => f.debug_tuple("Music::Seq").field(seq).finish(),
Self::Chord(chord) => f.debug_tuple("Music::Chord").field(chord).finish(),
Self::Melody(melody) => f.debug_tuple("Music::Melody").field(melody).finish(),
Self::Progression(progression) => f
.debug_tuple("Music::Progression")
.field(progression)
.finish(),
Self::Counterpoint(counterpoint) => f
.debug_tuple("Music::Counterpoint")
.field(counterpoint)
.finish(),
Self::PianoRoll(roll) => f.debug_tuple("Music::PianoRoll").field(roll).finish(),
Self::Arranger(arranger) => f.debug_tuple("Music::Arranger").field(arranger).finish(),
Self::MidiTrack(track) => f.debug_tuple("Music::MidiTrack").field(track).finish(),
Self::MidiFile(file) => f.debug_tuple("Music::MidiFile").field(file).finish(),
}
}
}
pub(crate) fn ensure_non_negative(value: Time) -> Result<(), MusicError> {
if value < Time::from_integer(0) {
Err(MusicError::NegativeDuration)
} else {
Ok(())
}
}
fn normalize_voice_names(count: usize, voice_names: Vec<String>) -> Vec<String> {
if voice_names.len() == count {
voice_names
} else {
(0..count)
.map(|index| format!("Voice {}", index + 1))
.collect()
}
}