use midi_types::{status::*, MidiMessage};
pub trait MidiTransport {
type Error;
fn write(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
}
#[derive(Debug)]
pub struct MidiRenderer<T, const RUNNING_STATUS: bool = true> {
transport: T,
running_status: Option<u8>,
}
impl<T: MidiTransport, const RUNNING_STATUS: bool> MidiRenderer<T, RUNNING_STATUS> {
pub fn new(transport: T) -> Self {
Self {
transport,
running_status: None,
}
}
pub fn release(self) -> T {
self.transport
}
pub fn render(&mut self, message: &MidiMessage) -> Result<(), T::Error> {
match *message {
MidiMessage::NoteOn(channel, note, velocity) => {
self.write_channel_msg(&[
NOTE_ON + Into::<u8>::into(channel),
note.into(),
velocity.into(),
])?;
}
MidiMessage::NoteOff(channel, note, velocity) => {
self.write_channel_msg(&[
NOTE_OFF + Into::<u8>::into(channel),
note.into(),
velocity.into(),
])?;
}
MidiMessage::KeyPressure(channel, note, value) => {
self.write_channel_msg(&[
KEY_PRESSURE + Into::<u8>::into(channel),
note.into(),
value.into(),
])?;
}
MidiMessage::ControlChange(channel, control, value) => {
self.write_channel_msg(&[
CONTROL_CHANGE + Into::<u8>::into(channel),
control.into(),
value.into(),
])?;
}
MidiMessage::ProgramChange(channel, program) => {
self.write_channel_msg(&[
PROGRAM_CHANGE + Into::<u8>::into(channel),
program.into(),
])?;
}
MidiMessage::ChannelPressure(channel, value) => {
self.write_channel_msg(&[
CHANNEL_PRESSURE + Into::<u8>::into(channel),
value.into(),
])?;
}
MidiMessage::PitchBendChange(channel, value) => {
let (msb, lsb) = value.into();
self.write_channel_msg(&[PITCH_BEND_CHANGE + Into::<u8>::into(channel), lsb, msb])?;
}
MidiMessage::QuarterFrame(value) => {
self.write_sys_common_msg(&[QUARTER_FRAME, value.into()])?;
}
MidiMessage::SongPositionPointer(value) => {
let (msb, lsb) = value.into();
self.write_sys_common_msg(&[SONG_POSITION_POINTER, lsb, msb])?;
}
MidiMessage::SongSelect(value) => {
self.write_sys_common_msg(&[SONG_SELECT, value.into()])?;
}
MidiMessage::TuneRequest => {
self.write_sys_common_msg(&[TUNE_REQUEST])?;
}
MidiMessage::TimingClock => self.transport.write(&[TIMING_CLOCK])?,
MidiMessage::Start => self.transport.write(&[START])?,
MidiMessage::Continue => self.transport.write(&[CONTINUE])?,
MidiMessage::Stop => self.transport.write(&[STOP])?,
MidiMessage::ActiveSensing => self.transport.write(&[ACTIVE_SENSING])?,
MidiMessage::Reset => self.transport.write(&[RESET])?,
}
Ok(())
}
fn write_channel_msg(&mut self, data: &[u8]) -> Result<(), T::Error> {
let status = data[0];
if RUNNING_STATUS && self.running_status == Some(status) {
self.transport.write(&data[1..])?;
} else {
self.transport.write(data)?;
if RUNNING_STATUS {
self.running_status = Some(status);
}
}
Ok(())
}
fn write_sys_common_msg(&mut self, data: &[u8]) -> Result<(), T::Error> {
self.transport.write(data)?;
if RUNNING_STATUS {
self.running_status = None;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use midi_types::Note;
extern crate std;
use std::{string::String, vec::Vec};
#[test]
fn should_render_note_on() {
assert_eq!(
render::<true>(&[MidiMessage::NoteOn(2.into(), Note::C3, 0x34.into())]),
&[0x92, 0x3c, 0x34]
);
}
#[test]
fn should_render_note_off() {
assert_eq!(
render::<true>(&[MidiMessage::NoteOff(2.into(), Note::C3, 0x34.into())]),
&[0x82, 0x3c, 0x34],
);
}
#[test]
fn should_render_keypressure() {
assert_eq!(
render::<true>(&[MidiMessage::KeyPressure(2.into(), 0x76.into(), 0x34.into())]),
&[0xA2, 0x76, 0x34],
);
}
#[test]
fn should_render_control_change() {
assert_eq!(
render::<true>(&[MidiMessage::ControlChange(
2.into(),
0x76.into(),
0x34.into(),
)]),
&[0xB2, 0x76, 0x34],
);
}
#[test]
fn should_render_program_change() {
assert_eq!(
render::<true>(&[MidiMessage::ProgramChange(2.into(), 0x76.into())]),
&[0xC2, 0x76],
);
}
#[test]
fn should_render_channel_pressure() {
assert_eq!(
render::<true>(&[MidiMessage::ChannelPressure(2.into(), 0x76.into())]),
&[0xD2, 0x76],
);
}
#[test]
fn should_render_pitchbend() {
assert_eq!(
render::<true>(&[MidiMessage::PitchBendChange(8.into(), (0x56, 0x14).into(),)]),
&[0xE8, 0x14, 0x56],
);
}
#[test]
fn should_render_quarter_frame() {
assert_eq!(
render::<true>(&[MidiMessage::QuarterFrame(0x76.into())]),
&[0xF1, 0x76]
);
}
#[test]
fn should_render_song_position_pointer() {
assert_eq!(
render::<true>(&[MidiMessage::SongPositionPointer((0x68, 0x7f).into())]),
&[0xf2, 0x7f, 0x68],
);
}
#[test]
fn should_render_song_select() {
assert_eq!(
render::<true>(&[MidiMessage::SongSelect(0x76.into())]),
&[0xF3, 0x76]
);
}
#[test]
fn should_render_tune_request() {
assert_eq!(render::<true>(&[MidiMessage::TuneRequest]), &[0xF6]);
}
#[test]
fn should_render_timing_clock() {
assert_eq!(render::<true>(&[MidiMessage::TimingClock]), &[0xF8]);
}
#[test]
fn should_render_start() {
assert_eq!(render::<true>(&[MidiMessage::Start]), &[0xFA]);
}
#[test]
fn should_render_continue() {
assert_eq!(render::<true>(&[MidiMessage::Continue]), &[0xFB]);
}
#[test]
fn should_render_stop() {
assert_eq!(render::<true>(&[MidiMessage::Stop]), &[0xFC]);
}
#[test]
fn should_render_active_sensing() {
assert_eq!(render::<true>(&[MidiMessage::ActiveSensing]), &[0xFE]);
}
#[test]
fn should_render_reset() {
assert_eq!(render::<true>(&[MidiMessage::Reset]), &[0xFF]);
}
#[test]
fn should_skip_repeated_status_with_running_status_on() {
assert_eq!(
render::<true>(&[
MidiMessage::NoteOn(2.into(), Note::D4, 0x34.into()),
MidiMessage::NoteOn(2.into(), Note::G6, 0x65.into()),
]),
&[0x92, 0x4a, 0x34, 0x67, 0x65],
);
}
#[test]
fn should_not_skip_repeated_status_with_running_status_off() {
assert_eq!(
render::<false>(&[
MidiMessage::NoteOn(2.into(), Note::D4, 0x34.into()),
MidiMessage::NoteOff(2.into(), Note::G6, 0x65.into()),
]),
&[0x92, 0x4a, 0x34, 0x82, 0x67, 0x65],
);
}
#[test]
fn should_not_skip_status_when_channel_changes() {
assert_eq!(
render::<true>(&[
MidiMessage::NoteOn(2.into(), Note::D4, 0x34.into()),
MidiMessage::NoteOn(3.into(), Note::G6, 0x65.into()),
]),
&[0x92, 0x4a, 0x34, 0x93, 0x67, 0x65],
);
}
#[test]
fn should_not_skip_status_for_different_message() {
assert_eq!(
render::<true>(&[
MidiMessage::NoteOn(2.into(), Note::D4, 0x34.into()),
MidiMessage::NoteOff(2.into(), Note::G6, 0x65.into()),
]),
&[0x92, 0x4a, 0x34, 0x82, 0x67, 0x65],
);
}
#[derive(Debug, Default, Clone)]
struct MockTransport {
buffer: Vec<u8>,
}
impl MidiTransport for MockTransport {
type Error = String;
fn write(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
assert!(bytes.len() <= 3, "Too many bytes in one message");
bytes.iter().for_each(|value| self.buffer.push(*value));
Ok(())
}
}
fn render<const RUNNING_STATUS: bool>(messages: &[MidiMessage]) -> Vec<u8> {
let mut renderer: MidiRenderer<MockTransport, RUNNING_STATUS> =
MidiRenderer::new(MockTransport::default());
for message in messages {
renderer.render(message).expect("Error rendering message");
}
renderer.transport.buffer
}
}