use std::convert::TryFrom;
use std::ops::{Add, Sub};
use sim_kernel::{CodecId, Origin, SourceId, Span};
use crate::MidiError;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TickTime {
pub ticks: i64,
pub tpq: u32,
}
impl TickTime {
pub const ZERO: Self = Self { ticks: 0, tpq: 1 };
pub fn new(ticks: i64, tpq: u32) -> Result<Self, MidiError> {
if tpq == 0 {
Err(MidiError::ZeroTpq)
} else {
Ok(Self { ticks, tpq })
}
}
pub fn from_quarters(quarters: i64) -> Self {
Self {
ticks: quarters,
tpq: 1,
}
}
pub fn mul_int(self, factor: i64) -> Self {
Self {
ticks: self.ticks * factor,
tpq: self.tpq,
}
}
pub fn mul_ratio(self, numerator: i64, denominator: i64) -> Result<Self, MidiError> {
if denominator == 0 {
return Err(MidiError::InvalidRatio(numerator, denominator));
}
Ok(Self {
ticks: self.ticks * numerator / denominator,
tpq: self.tpq,
})
}
pub fn div_ratio(self, numerator: i64, denominator: i64) -> Result<Self, MidiError> {
if numerator == 0 {
return Err(MidiError::InvalidRatio(numerator, denominator));
}
Ok(Self {
ticks: self.ticks * denominator / numerator,
tpq: self.tpq,
})
}
pub fn rebase(self, new_tpq: u32) -> Result<Self, MidiError> {
if new_tpq == 0 {
return Err(MidiError::ZeroTpq);
}
let scaled = self.ticks * i64::from(new_tpq);
if scaled % i64::from(self.tpq) != 0 {
return Err(MidiError::InexactRebase);
}
Ok(Self {
ticks: scaled / i64::from(self.tpq),
tpq: new_tpq,
})
}
pub fn quantize(self, new_tpq: u32) -> Self {
let scaled = self.ticks as f64 * f64::from(new_tpq) / f64::from(self.tpq);
Self {
ticks: scaled.round() as i64,
tpq: new_tpq.max(1),
}
}
pub fn as_rational(self) -> (i64, i64) {
(self.ticks, i64::from(self.tpq))
}
pub fn as_f64_quarters(self) -> f64 {
self.ticks as f64 / self.tpq as f64
}
}
impl Add for TickTime {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
let other = other.quantize(self.tpq);
Self {
ticks: self.ticks + other.ticks,
tpq: self.tpq,
}
}
}
impl Sub for TickTime {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
let other = other.quantize(self.tpq);
Self {
ticks: self.ticks - other.ticks,
tpq: self.tpq,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct U7(pub u8);
impl TryFrom<u16> for U7 {
type Error = MidiError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
if value <= 127 {
Ok(Self(value as u8))
} else {
Err(MidiError::InvalidU7(value))
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct U14(pub u16);
impl TryFrom<u16> for U14 {
type Error = MidiError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
if value <= 16_383 {
Ok(Self(value))
} else {
Err(MidiError::InvalidU14(value))
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Channel(pub u8);
impl Channel {
pub fn new(value: u8) -> Result<Self, MidiError> {
if value <= 15 {
Ok(Self(value))
} else {
Err(MidiError::InvalidChannel(value))
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ChannelMessage {
NoteOff {
ch: Channel,
key: U7,
vel: U7,
},
NoteOn {
ch: Channel,
key: U7,
vel: U7,
},
PolyAftertouch {
ch: Channel,
key: U7,
pressure: U7,
},
ControlChange {
ch: Channel,
cc: U7,
value: U7,
},
ProgramChange {
ch: Channel,
program: U7,
},
ChanAftertouch {
ch: Channel,
pressure: U7,
},
PitchBend {
ch: Channel,
value: U14,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum MetaEvent {
EndOfTrack,
Tempo {
us_per_quarter: u32,
},
TimeSig {
num: u8,
den_pow2: u8,
clocks_per_click: u8,
thirty_seconds_per_quarter: u8,
},
KeySig {
sharps_flats: i8,
minor: bool,
},
Other(MetaBucket),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MetaBucket {
pub type_byte: u8,
pub data: Vec<u8>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct SmpteOffset {
pub hours: u8,
pub minutes: u8,
pub seconds: u8,
pub frames: u8,
pub subframes: u8,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SysExEvent {
F0 {
data: Vec<u8>,
},
F7 {
data: Vec<u8>,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RawBytes {
pub status: u8,
pub data: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum MidiPayload {
Channel(ChannelMessage),
Meta(MetaEvent),
SysEx(SysExEvent),
Raw(RawBytes),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MidiEvent {
pub time: TickTime,
pub origin: Origin,
pub payload: MidiPayload,
}
pub fn synthetic_origin() -> Origin {
Origin {
codec: CodecId(0),
source: SourceId("music-stack".to_owned()),
span: Span { start: 0, end: 0 },
trivia: Vec::new(),
}
}