1use num_rational::Ratio;
2use std::any::Any;
3use thiserror::Error;
4
5pub use sim_lib_midi_core::{Channel, ChannelMessage, MidiEvent, MidiPayload, TickTime};
6pub use sim_lib_midi_smf::SmfFile;
7pub use sim_lib_pitch_core::{Pitch, PitchClass, PitchError, parse_pitch};
8
9use crate::{arranger::Arranger, piano_roll::PianoRoll};
10
11pub type Time = Ratio<i64>;
16
17#[derive(Debug, Error, Clone, PartialEq, Eq)]
19pub enum MusicError {
20 #[error("duration cannot be negative")]
22 NegativeDuration,
23 #[error("onset cannot be negative")]
25 NegativeOnset,
26 #[error("tempo must be positive")]
28 InvalidTempo,
29 #[error("time signature denominator must be non-zero")]
31 InvalidTimeSignature,
32 #[error("melody items must be monophonic")]
34 NonMonophonicMelody,
35 #[error("play range end cannot precede start")]
37 InvalidTimeRange,
38 #[error("play PPQ must be greater than zero")]
40 InvalidPpq,
41 #[error("lane {lane} target {target} is invalid for its event kind")]
43 InvalidLaneTarget {
44 lane: String,
46 target: String,
48 },
49 #[error("piano-roll time grid must have positive TPQ and step")]
51 InvalidPianoRollGrid,
52 #[error("piano-roll lane {lane} of kind {lane_kind} cannot contain {cell_kind} cells")]
54 PianoRollLaneCellMismatch {
55 lane: String,
57 lane_kind: String,
59 cell_kind: String,
61 },
62}
63
64pub trait MusicObject: Send + Sync + Any {
69 fn kind(&self) -> &'static str;
71 fn duration(&self) -> Time;
73 fn voices<'a>(&'a self, offset: Time, out: &mut Vec<TimedAtom<'a>>);
75 fn clone_box(&self) -> Box<dyn MusicObject>;
77 fn as_any(&self) -> &dyn Any;
79}
80
81impl Clone for Box<dyn MusicObject> {
82 fn clone(&self) -> Self {
83 self.clone_box()
84 }
85}
86
87#[derive(Clone, Debug, PartialEq, Eq)]
89pub struct TimedAtom<'a> {
90 pub onset: Time,
92 pub atom: AtomRef<'a>,
94}
95
96#[derive(Clone, Debug, PartialEq, Eq)]
98pub enum AtomRef<'a> {
99 Note(Note),
101 Rest(Rest),
103 Phantom(std::marker::PhantomData<&'a ()>),
105}
106
107#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
109pub enum Articulation {
110 Normal,
112 Staccato,
114 Legato,
116 Tenuto,
118 Accent,
120 Marcato,
122}
123
124#[derive(Clone, Debug, PartialEq, Eq)]
126pub struct Note {
127 pub duration: Time,
129 pub pitch: Pitch,
131 pub velocity: u8,
133 pub channel: Channel,
135 pub articulation: Articulation,
137}
138
139impl Note {
140 pub fn new(
142 duration: Time,
143 pitch: Pitch,
144 velocity: u8,
145 channel: Channel,
146 articulation: Articulation,
147 ) -> Result<Self, MusicError> {
148 ensure_non_negative(duration)?;
149 Ok(Self {
150 duration,
151 pitch,
152 velocity,
153 channel,
154 articulation,
155 })
156 }
157}
158
159#[derive(Clone, Debug, PartialEq, Eq)]
161pub struct Rest {
162 pub duration: Time,
164}
165
166impl Rest {
167 pub fn new(duration: Time) -> Result<Self, MusicError> {
178 ensure_non_negative(duration)?;
179 Ok(Self { duration })
180 }
181}
182
183#[derive(Clone)]
185pub struct Par {
186 pub children: Vec<Box<dyn MusicObject>>,
188}
189
190impl std::fmt::Debug for Par {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 f.debug_struct("Par")
193 .field("children_len", &self.children.len())
194 .finish()
195 }
196}
197
198#[derive(Clone)]
200pub struct Seq {
201 pub children: Vec<Box<dyn MusicObject>>,
203}
204
205impl std::fmt::Debug for Seq {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 f.debug_struct("Seq")
208 .field("children_len", &self.children.len())
209 .finish()
210 }
211}
212
213#[derive(Clone, Debug, PartialEq, Eq)]
215pub struct Chord {
216 pub duration: Time,
218 pub symbol: String,
220 pub pitches: Vec<Pitch>,
222 pub velocity: u8,
224 pub channel: Channel,
226}
227
228impl Chord {
229 pub fn new(
231 duration: Time,
232 symbol: impl Into<String>,
233 pitches: Vec<Pitch>,
234 velocity: u8,
235 channel: Channel,
236 ) -> Result<Self, MusicError> {
237 ensure_non_negative(duration)?;
238 Ok(Self {
239 duration,
240 symbol: symbol.into(),
241 pitches,
242 velocity,
243 channel,
244 })
245 }
246}
247
248#[derive(Clone, Debug, PartialEq, Eq)]
250pub enum MelodyItem {
251 Note(Note),
253 Rest(Rest),
255}
256
257impl MelodyItem {
258 pub fn duration(&self) -> Time {
260 match self {
261 Self::Note(note) => note.duration,
262 Self::Rest(rest) => rest.duration,
263 }
264 }
265}
266
267#[derive(Clone, Debug, PartialEq, Eq)]
269pub struct Melody {
270 pub items: Vec<MelodyItem>,
272}
273
274impl Melody {
275 pub fn new(items: Vec<MelodyItem>) -> Result<Self, MusicError> {
277 for item in &items {
278 ensure_non_negative(item.duration())?;
279 }
280 Ok(Self { items })
281 }
282
283 pub fn total_duration(&self) -> Time {
298 self.items
299 .iter()
300 .fold(Time::from_integer(0), |sum, item| sum + item.duration())
301 }
302}
303
304#[derive(Clone, Debug, PartialEq, Eq)]
306pub struct Progression {
307 pub key: Option<String>,
309 pub chords: Vec<Chord>,
311}
312
313impl Progression {
314 pub fn new(key: Option<String>, chords: Vec<Chord>) -> Result<Self, MusicError> {
316 for chord in &chords {
317 ensure_non_negative(chord.duration)?;
318 }
319 Ok(Self { key, chords })
320 }
321}
322
323#[derive(Clone, Debug, PartialEq, Eq)]
325pub struct Counterpoint {
326 pub voices: Vec<Melody>,
328 pub voice_names: Vec<String>,
330}
331
332impl Counterpoint {
333 pub fn new(voices: Vec<Melody>, voice_names: Vec<String>) -> Result<Self, MusicError> {
335 let voice_names = normalize_voice_names(voices.len(), voice_names);
336 Ok(Self {
337 voices,
338 voice_names,
339 })
340 }
341
342 pub fn normalized_voice_names(&self) -> Vec<String> {
344 normalize_voice_names(self.voices.len(), self.voice_names.clone())
345 }
346}
347
348#[derive(Clone, Debug, PartialEq, Eq)]
350pub struct MidiTrackObj {
351 pub events: Vec<MidiEvent>,
353 pub channel_hint: Option<Channel>,
355}
356
357impl MidiTrackObj {
358 pub fn new(events: Vec<MidiEvent>, channel_hint: Option<Channel>) -> Self {
360 Self {
361 events,
362 channel_hint,
363 }
364 }
365}
366
367#[derive(Clone, Debug, PartialEq, Eq)]
369pub struct MidiFileObj {
370 pub file: SmfFile,
372}
373
374impl MidiFileObj {
375 pub fn new(file: SmfFile) -> Self {
377 Self { file }
378 }
379}
380
381#[derive(Clone, Debug)]
383pub struct Score {
384 pub tempo_bpm: u32,
386 pub time_signature: (u8, u8),
388 pub key: Option<String>,
390 pub body: Music,
392}
393
394impl Score {
395 pub fn new(
397 tempo_bpm: u32,
398 time_signature: (u8, u8),
399 key: Option<String>,
400 body: Music,
401 ) -> Result<Self, MusicError> {
402 if tempo_bpm == 0 {
403 return Err(MusicError::InvalidTempo);
404 }
405 if time_signature.1 == 0 {
406 return Err(MusicError::InvalidTimeSignature);
407 }
408 Ok(Self {
409 tempo_bpm,
410 time_signature,
411 key,
412 body,
413 })
414 }
415}
416
417#[derive(Clone)]
419pub enum Music {
420 Note(Note),
422 Rest(Rest),
424 Par(Par),
426 Seq(Seq),
428 Chord(Chord),
430 Melody(Melody),
432 Progression(Progression),
434 Counterpoint(Counterpoint),
436 PianoRoll(PianoRoll),
438 Arranger(Arranger),
440 MidiTrack(MidiTrackObj),
442 MidiFile(MidiFileObj),
444}
445
446impl std::fmt::Debug for Music {
447 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
448 match self {
449 Self::Note(_) => f.write_str("Music::Note(..)"),
450 Self::Rest(_) => f.write_str("Music::Rest(..)"),
451 Self::Par(par) => f.debug_tuple("Music::Par").field(par).finish(),
452 Self::Seq(seq) => f.debug_tuple("Music::Seq").field(seq).finish(),
453 Self::Chord(chord) => f.debug_tuple("Music::Chord").field(chord).finish(),
454 Self::Melody(melody) => f.debug_tuple("Music::Melody").field(melody).finish(),
455 Self::Progression(progression) => f
456 .debug_tuple("Music::Progression")
457 .field(progression)
458 .finish(),
459 Self::Counterpoint(counterpoint) => f
460 .debug_tuple("Music::Counterpoint")
461 .field(counterpoint)
462 .finish(),
463 Self::PianoRoll(roll) => f.debug_tuple("Music::PianoRoll").field(roll).finish(),
464 Self::Arranger(arranger) => f.debug_tuple("Music::Arranger").field(arranger).finish(),
465 Self::MidiTrack(track) => f.debug_tuple("Music::MidiTrack").field(track).finish(),
466 Self::MidiFile(file) => f.debug_tuple("Music::MidiFile").field(file).finish(),
467 }
468 }
469}
470
471pub(crate) fn ensure_non_negative(value: Time) -> Result<(), MusicError> {
472 if value < Time::from_integer(0) {
473 Err(MusicError::NegativeDuration)
474 } else {
475 Ok(())
476 }
477}
478
479fn normalize_voice_names(count: usize, voice_names: Vec<String>) -> Vec<String> {
480 if voice_names.len() == count {
481 voice_names
482 } else {
483 (0..count)
484 .map(|index| format!("Voice {}", index + 1))
485 .collect()
486 }
487}