use crate::{event::MidiMessage, prelude::*};
#[cfg(feature = "alloc")]
use crate::{event::TrackEventKind, Arena};
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum LiveEvent<'a> {
Midi {
channel: u4,
message: MidiMessage,
},
Common(SystemCommon<'a>),
Realtime(SystemRealtime),
}
impl<'a> LiveEvent<'a> {
pub fn parse(mut raw: &'a [u8]) -> Result<LiveEvent<'a>> {
let status = raw
.split_checked(1)
.ok_or_else(|| err_invalid!("no status byte"))?[0];
let data = u7::slice_from_int(raw);
Self::read(status, data)
}
pub(crate) fn read(status: u8, data: &[u7]) -> Result<LiveEvent> {
match status {
0x80..=0xEF => {
let data = MidiMessage::get_data_u7(status, data)?;
let (channel, message) = MidiMessage::read(status, data);
Ok(LiveEvent::Midi { channel, message })
}
0xF8..=0xFF => {
let ev = SystemRealtime::new(status);
Ok(LiveEvent::Realtime(ev))
}
_ => {
let ev = SystemCommon::read(status, data)?;
Ok(LiveEvent::Common(ev))
}
}
}
#[inline]
pub fn write<W: Write>(&self, out: &mut W) -> WriteResult<W> {
self.write_with_running_status(&mut None, out)
}
pub fn write_with_running_status<W: Write>(
&self,
running_status: &mut Option<u8>,
out: &mut W,
) -> WriteResult<W> {
match self {
LiveEvent::Midi { channel, message } => {
let status = message.status_nibble() << 4 | channel.as_int();
if Some(status) != *running_status {
*running_status = Some(status);
out.write(&[status])?;
}
message.write(out)?;
}
LiveEvent::Common(common) => {
*running_status = None;
common.write(out)?;
}
LiveEvent::Realtime(realtime) => {
out.write(&[realtime.encode()])?;
}
}
Ok(())
}
#[cfg(feature = "std")]
#[inline]
pub fn write_std<W: io::Write>(&self, out: W) -> io::Result<()> {
self.write(&mut IoWrap(out))
}
#[cfg(feature = "std")]
#[inline]
pub fn write_std_with_running_status<W: io::Write>(
&self,
running_status: &mut Option<u8>,
out: W,
) -> io::Result<()> {
self.write_with_running_status(running_status, &mut IoWrap(out))
}
pub fn to_static(&self) -> LiveEvent<'static> {
use self::LiveEvent::*;
match *self {
Midi { channel, message } => Midi { channel, message },
Common(sysc) => Common(sysc.to_static()),
Realtime(realt) => Realtime(realt),
}
}
#[cfg(feature = "alloc")]
pub fn as_track_event<'b>(&self, arena: &'b Arena) -> TrackEventKind<'b> {
match self {
LiveEvent::Midi { channel, message } => TrackEventKind::Midi {
channel: *channel,
message: *message,
},
LiveEvent::Common(common) => match common {
SystemCommon::SysEx(data) => {
let mut sysex_bytes = Vec::with_capacity(data.len() + 1);
sysex_bytes.extend_from_slice(u7::slice_as_int(data));
sysex_bytes.push(0xF7);
TrackEventKind::SysEx(arena.add_vec(sysex_bytes))
}
SystemCommon::Undefined(status, data) => {
let mut ev_bytes = Vec::with_capacity(1 + data.len());
ev_bytes.push(*status);
ev_bytes.extend_from_slice(u7::slice_as_int(data));
TrackEventKind::Escape(arena.add_vec(ev_bytes))
}
syscommon => {
let mut buf = [0; 4];
let rem_bytes = {
let mut buf_ref = &mut buf[..];
syscommon
.write(&mut buf_ref)
.expect("failed to write system common message");
buf_ref.len()
};
let bytes = &buf[..buf.len() - rem_bytes];
TrackEventKind::Escape(arena.add(bytes))
}
},
LiveEvent::Realtime(realtime) => {
TrackEventKind::Escape(arena.add(&[realtime.encode()]))
}
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum SystemCommon<'a> {
SysEx(&'a [u7]),
MidiTimeCodeQuarterFrame(MtcQuarterFrameMessage, u4),
SongPosition(u14),
SongSelect(u7),
TuneRequest,
Undefined(u8, &'a [u7]),
}
impl<'a> SystemCommon<'a> {
#[allow(clippy::len_zero)]
fn read(status: u8, data: &'a [u7]) -> Result<SystemCommon<'a>> {
let ev = match status {
0xF0 => {
SystemCommon::SysEx(&data[..])
}
0xF1 if data.len() >= 1 => {
SystemCommon::MidiTimeCodeQuarterFrame(
MtcQuarterFrameMessage::from_code(data[0].as_int() >> 4).unwrap(),
u4::from(data[0].as_int()),
)
}
0xF2 if data.len() >= 2 => {
SystemCommon::SongPosition(u14::from(
(data[0].as_int() as u16) | ((data[1].as_int() as u16) << 7),
))
}
0xF3 if data.len() >= 1 => {
SystemCommon::SongSelect(data[0])
}
0xF6 => {
SystemCommon::TuneRequest
}
0xF1..=0xF5 => {
SystemCommon::Undefined(status, &data[..])
}
_ => {
bail!(err_invalid!("invalid status byte"))
}
};
Ok(ev)
}
fn write<W: Write>(&self, out: &mut W) -> WriteResult<W> {
match self {
SystemCommon::SysEx(data) => {
out.write(&[0xF0])?;
out.write(u7::slice_as_int(data))?;
out.write(&[0xF7])
}
SystemCommon::MidiTimeCodeQuarterFrame(msgtype, data) => {
out.write(&[0xF1, msgtype.as_code() << 4 | data.as_int()])
}
SystemCommon::SongPosition(pos) => {
out.write(&[0xF2, pos.as_int() as u8 & 0x7F, (pos.as_int() >> 7) as u8])
}
SystemCommon::SongSelect(song) => out.write(&[0xF3, song.as_int()]),
SystemCommon::TuneRequest => out.write(&[0xF6]),
SystemCommon::Undefined(status, data) => {
out.write(&[*status])?;
out.write(u7::slice_as_int(data))
}
}
}
pub fn to_static(&self) -> SystemCommon<'static> {
use self::SystemCommon::*;
match *self {
SysEx(_) => SysEx(u7::slice_from_int(b"")),
MidiTimeCodeQuarterFrame(v0, v1) => MidiTimeCodeQuarterFrame(v0, v1),
SongPosition(v) => SongPosition(v),
SongSelect(v) => SongSelect(v),
TuneRequest => TuneRequest,
Undefined(v, _) => Undefined(v, u7::slice_from_int(b"")),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum MtcQuarterFrameMessage {
FramesLow,
FramesHigh,
SecondsLow,
SecondsHigh,
MinutesLow,
MinutesHigh,
HoursLow,
HoursHigh,
}
impl MtcQuarterFrameMessage {
fn as_code(self) -> u8 {
use MtcQuarterFrameMessage::*;
match self {
FramesLow => 0,
FramesHigh => 1,
SecondsLow => 2,
SecondsHigh => 3,
MinutesLow => 4,
MinutesHigh => 5,
HoursLow => 6,
HoursHigh => 7,
}
}
fn from_code(code: u8) -> Option<MtcQuarterFrameMessage> {
use MtcQuarterFrameMessage::*;
Some(match code {
0 => FramesLow,
1 => FramesHigh,
2 => SecondsLow,
3 => SecondsHigh,
4 => MinutesLow,
5 => MinutesHigh,
6 => HoursLow,
7 => HoursHigh,
_ => return None,
})
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum SystemRealtime {
TimingClock,
Start,
Continue,
Stop,
ActiveSensing,
Reset,
Undefined(u8),
}
impl SystemRealtime {
#[inline]
pub fn new(status: u8) -> SystemRealtime {
use SystemRealtime::*;
match status {
0xF8 => TimingClock,
0xFA => Start,
0xFB => Continue,
0xFC => Stop,
0xFE => ActiveSensing,
0xFF => Reset,
_ => {
Undefined(status)
}
}
}
#[inline]
pub fn encode(self) -> u8 {
use SystemRealtime::*;
match self {
TimingClock => 0xF8,
Start => 0xFA,
Continue => 0xFB,
Stop => 0xFC,
ActiveSensing => 0xFE,
Reset => 0xFF,
Undefined(byte) => byte,
}
}
}