use thiserror::Error;
use crate::reader::Yieldable;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MidiEvent {
NoteOff(u8, NoteMeta),
NoteOn(u8, NoteMeta),
PolyphonicKeyPressure(u8, NoteMeta),
ControlChange(u8, ControlChange),
ProgramChange(u8, u8),
ChannelPressure(u8, u8),
PitchWheelChange(u8, u16),
}
#[derive(Error, Debug, Clone, Copy, PartialEq)]
#[error("Unsupported Status Code {0}")]
pub struct UnsupportedStatusCode(u8);
pub struct IteratorWrapper<T>(pub T);
impl<ITER> TryFrom<IteratorWrapper<&mut ITER>> for MidiEvent
where
ITER: Iterator<Item = u8>,
{
type Error = UnsupportedStatusCode;
fn try_from(value: IteratorWrapper<&mut ITER>) -> Result<Self, Self::Error> {
let value = value.0;
let status = value.get(1)[0];
let channel = status & 0x0F;
let status = status >> 4;
match status {
0b1000 => {
let reads = value.get(2);
Ok(Self::NoteOff(
channel,
NoteMeta {
key: reads[0],
velocity: reads[1],
},
))
}
0b1001 => {
let reads = value.get(2);
Ok(Self::NoteOn(
channel,
NoteMeta {
key: reads[0],
velocity: reads[1],
},
))
}
0b1011 => {
let reads = value.get(2);
Ok(Self::ControlChange(
channel,
ControlChange {
controller_number: reads[0],
new_value: reads[1],
},
))
}
0b1100 => {
let reads = value.get(1);
Ok(Self::ProgramChange(channel, reads[0]))
}
0b1101 => {
let reads = value.get(1);
Ok(Self::ChannelPressure(channel, reads[0]))
}
0b1110 => {
let reads = value.get(2);
const MASK: u8 = 0x7;
let mut result: u16 = 0;
for byte in reads.iter().rev() {
result <<= 7;
result |= (byte & MASK) as u16;
}
Ok(Self::PitchWheelChange(channel, result))
}
code => Err(UnsupportedStatusCode(code)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct NoteMeta {
key: u8,
velocity: u8,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ControlChange {
controller_number: u8,
new_value: u8,
}
#[cfg(test)]
mod tests {
use crate::chunk::track::event::UnsupportedStatusCode;
use super::{IteratorWrapper, MidiEvent, NoteMeta};
#[test]
fn midi_event_status_parsing() {
let status_channel = 0b10001111;
let key = 0b01010101;
let velocity = 0b11111111;
let mut stream = [status_channel, key, velocity].into_iter();
let status =
MidiEvent::try_from(IteratorWrapper(&mut stream)).expect("Parse off note signal");
let expected = MidiEvent::NoteOff(0x0F, NoteMeta { key, velocity });
assert_eq!(status, expected)
}
#[test]
fn midi_event_status_parsing_fails_on_invalid_status() {
let status_channel = 0b00101111;
let key = 0b01010101;
let velocity = 0b11111111;
let mut stream = [status_channel, key, velocity].into_iter();
let status = MidiEvent::try_from(IteratorWrapper(&mut stream));
assert_eq!(status, Err(UnsupportedStatusCode(0b0010)));
}
}