use crate::numeric::fixed::{Q12, Q6};
use crate::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(
all(feature = "wasm", target_arch = "wasm32"),
derive(serde::Serialize, serde::Deserialize, tsify::Tsify),
serde(rename_all = "camelCase", tag = "type")
)]
pub enum MidiEvent {
NoteOn {
channel: u8,
note: u8,
velocity: u8,
},
NoteOff {
channel: u8,
note: u8,
},
AllNotesOff,
}
impl MidiEvent {
pub fn from_bytes(bytes: [u8; 3]) -> Result<Option<Self>, Error> {
if bytes[0] < 0x80 || bytes[1] > 127 || bytes[2] > 127 {
return Err(Error::InvalidMidi);
}
let channel = bytes[0] & 15;
Ok(match bytes[0] & 0xf0 {
0x90 if bytes[2] != 0 => Some(Self::NoteOn {
channel,
note: bytes[1],
velocity: bytes[2],
}),
0x80 | 0x90 => Some(Self::NoteOff {
channel,
note: bytes[1],
}),
0xb0 if bytes[1] == 123 => Some(Self::AllNotesOff),
_ => None,
})
}
pub fn validate(&self) -> Result<(), Error> {
match *self {
Self::NoteOn {
channel,
note,
velocity,
} if channel > 15 || note > 127 || velocity > 127 => Err(Error::InvalidMidi),
Self::NoteOff { channel, note } if channel > 15 || note > 127 => {
Err(Error::InvalidMidi)
}
_ => Ok(()),
}
}
}
pub(crate) fn note_period(rate: f32, note: i32) -> Q6 {
let period = ((f64::from(rate) / 440.0)
/ ((f64::from(note as f32 - 69.0) * std::f64::consts::LN_2) / 12.0).exp())
as f32;
Q6::from_f64(f64::from(period))
}
pub(crate) fn velocity_gain(velocity: u8) -> Q12 {
if velocity == 0 {
return Q12::ZERO;
}
let db = (36.0 / 127.0) * f64::from(velocity) - 24.0;
let gain = 10.0_f64.powf(db / 20.0) as f32;
Q12::from_f64(f64::from(gain))
}