embedded_midi/
lib.rs

1//! *Midi driver on top of embedded hal serial communications*
2//!
3#![no_std]
4#[warn(missing_debug_implementations, missing_docs)]
5mod parser;
6
7use core::fmt::Debug;
8use embedded_hal::serial;
9pub use midi_types::{Channel, Control, MidiMessage, Note, Program};
10use nb::block;
11pub use parser::MidiParser;
12
13pub struct MidiIn<RX> {
14    rx: RX,
15    parser: MidiParser,
16}
17
18impl<RX, E> MidiIn<RX>
19where
20    RX: serial::Read<u8, Error = E>,
21    E: Debug,
22{
23    pub fn new(rx: RX) -> Self {
24        MidiIn {
25            rx,
26            parser: MidiParser::new(),
27        }
28    }
29
30    pub fn read(&mut self) -> nb::Result<MidiMessage, E> {
31        let byte = self.rx.read()?;
32
33        match self.parser.parse_byte(byte) {
34            Some(event) => Ok(event),
35            None => Err(nb::Error::WouldBlock),
36        }
37    }
38}
39
40pub struct MidiOut<TX> {
41    tx: TX,
42    last_status: Option<u8>,
43}
44
45impl<TX, E> MidiOut<TX>
46where
47    TX: serial::Write<u8, Error = E>,
48    E: Debug,
49{
50    pub fn new(tx: TX) -> Self {
51        MidiOut {
52            tx,
53            last_status: None,
54        }
55    }
56
57    pub fn release(self) -> TX {
58        self.tx
59    }
60
61    pub fn write(&mut self, message: &MidiMessage) -> Result<(), E> {
62        match message {
63            &MidiMessage::NoteOn(channel, note, velocity) => {
64                self.write_channel_message(0x90, channel.into(), &[note.into(), velocity.into()])?;
65            }
66            &MidiMessage::NoteOff(channel, note, velocity) => {
67                self.write_channel_message(0x80, channel.into(), &[note.into(), velocity.into()])?;
68            }
69            &MidiMessage::KeyPressure(channel, note, value) => {
70                self.write_channel_message(0xA0, channel.into(), &[note.into(), value.into()])?;
71            }
72            &MidiMessage::ControlChange(channel, control, value) => {
73                self.write_channel_message(0xB0, channel.into(), &[control.into(), value.into()])?;
74            }
75            &MidiMessage::ProgramChange(channel, program) => {
76                self.write_channel_message(0xC0, channel.into(), &[program.into()])?;
77            }
78            &MidiMessage::ChannelPressure(channel, value) => {
79                self.write_channel_message(0xD0, channel.into(), &[value.into()])?;
80            }
81            &MidiMessage::PitchBendChange(channel, value) => {
82                let (value_lsb, value_msb) = value.into();
83                self.write_channel_message(0xE0, channel.into(), &[value_lsb, value_msb])?;
84            }
85            &MidiMessage::QuarterFrame(value) => {
86                block!(self.tx.write(0xF1))?;
87                block!(self.tx.write(value.into()))?;
88                self.last_status = None;
89            }
90            &MidiMessage::SongPositionPointer(value) => {
91                let (value_lsb, value_msb) = value.into();
92                block!(self.tx.write(0xF2))?;
93                block!(self.tx.write(value_lsb))?;
94                block!(self.tx.write(value_msb))?;
95                self.last_status = None;
96            }
97            &MidiMessage::SongSelect(value) => {
98                block!(self.tx.write(0xF3))?;
99                block!(self.tx.write(value.into()))?;
100                self.last_status = None;
101            }
102            &MidiMessage::TuneRequest => {
103                block!(self.tx.write(0xF6))?;
104                self.last_status = None;
105            }
106            &MidiMessage::TimingClock => {
107                block!(self.tx.write(0xF8))?;
108            }
109            &MidiMessage::Start => {
110                block!(self.tx.write(0xFA))?;
111            }
112            &MidiMessage::Continue => {
113                block!(self.tx.write(0xFB))?;
114            }
115            &MidiMessage::Stop => {
116                block!(self.tx.write(0xFC))?;
117            }
118            &MidiMessage::ActiveSensing => {
119                block!(self.tx.write(0xFE))?;
120            }
121            &MidiMessage::Reset => {
122                block!(self.tx.write(0xFF))?;
123            }
124        }
125
126        Ok(())
127    }
128
129    fn write_channel_message(&mut self, status_msb: u8, channel: u8, data: &[u8]) -> Result<(), E> {
130        let status = status_msb + channel;
131        // If the last command written had the same status/channel, the MIDI protocol allows us to
132        // omit sending the status byte again.
133        if self.last_status != Some(status) {
134            block!(self.tx.write(status))?;
135        }
136        for byte in data {
137            block!(self.tx.write(*byte))?;
138        }
139        self.last_status = Some(status);
140
141        Ok(())
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    extern crate std;
148    use super::*;
149    use embedded_hal_mock::serial;
150    use std::vec::Vec;
151
152    fn verify_writes(messages: &[MidiMessage], bytes: &[u8]) {
153        let expectations: Vec<serial::Transaction<u8>> = bytes
154            .into_iter()
155            .map(|byte| serial::Transaction::write(*byte))
156            .collect();
157        let serial = serial::Mock::new(&expectations);
158        let mut midi_out = MidiOut::new(serial);
159        for message in messages {
160            midi_out.write(&message).unwrap();
161        }
162        let mut serial = midi_out.release();
163        serial.done();
164    }
165
166    #[test]
167    fn note_on_should_write_successfully() {
168        verify_writes(
169            &[MidiMessage::NoteOn(0x02.into(), 0x76.into(), 0x34.into())],
170            &[0x92, 0x76, 0x34],
171        );
172    }
173
174    #[test]
175    fn note_on_second_note_should_skip_status() {
176        verify_writes(
177            &[
178                MidiMessage::NoteOn(0x02.into(), 0x76.into(), 0x34.into()),
179                MidiMessage::NoteOn(0x02.into(), 0x33.into(), 0x65.into()),
180            ],
181            &[0x92, 0x76, 0x34, 0x33, 0x65],
182        );
183    }
184    #[test]
185    fn note_on_second_note_different_channel_should_not_skip_status() {
186        verify_writes(
187            &[
188                MidiMessage::NoteOn(0x02.into(), 0x76.into(), 0x34.into()),
189                MidiMessage::NoteOn(0x03.into(), 0x33.into(), 0x65.into()),
190            ],
191            &[0x92, 0x76, 0x34, 0x93, 0x33, 0x65],
192        );
193    }
194    #[test]
195    fn note_on_note_off_should_not_skip_status() {
196        verify_writes(
197            &[
198                MidiMessage::NoteOn(0x02.into(), 0x76.into(), 0x34.into()),
199                MidiMessage::NoteOff(0x02.into(), 0x33.into(), 0x65.into()),
200            ],
201            &[0x92, 0x76, 0x34, 0x82, 0x33, 0x65],
202        );
203    }
204    #[test]
205    fn note_off_should_write_successfully() {
206        verify_writes(
207            &[MidiMessage::NoteOff(0x02.into(), 0x76.into(), 0x34.into())],
208            &[0x82, 0x76, 0x34],
209        );
210    }
211    #[test]
212    fn key_pressure_should_write_successfully() {
213        verify_writes(
214            &[MidiMessage::KeyPressure(
215                0x02.into(),
216                0x76.into(),
217                0x34.into(),
218            )],
219            &[0xA2, 0x76, 0x34],
220        );
221    }
222    #[test]
223    fn control_change_should_write_successfully() {
224        verify_writes(
225            &[MidiMessage::ControlChange(
226                0x02.into(),
227                0x76.into(),
228                0x34.into(),
229            )],
230            &[0xB2, 0x76, 0x34],
231        );
232    }
233    #[test]
234    fn program_change_should_write_successfully() {
235        verify_writes(
236            &[MidiMessage::ProgramChange(0x02.into(), 0x76.into())],
237            &[0xC2, 0x76],
238        );
239    }
240    #[test]
241    fn channel_pressure_should_write_successfully() {
242        verify_writes(
243            &[MidiMessage::ChannelPressure(0x02.into(), 0x76.into())],
244            &[0xD2, 0x76],
245        );
246    }
247    #[test]
248    fn pitch_bend_should_write_successfully() {
249        verify_writes(
250            &[MidiMessage::PitchBendChange(
251                0x02.into(),
252                (0x76, 0x34).into(),
253            )],
254            &[0xE2, 0x76, 0x34],
255        );
256    }
257    #[test]
258    fn quarter_frame_should_write_successfully() {
259        verify_writes(&[MidiMessage::QuarterFrame(0x76.into())], &[0xF1, 0x76]);
260    }
261    #[test]
262    fn song_position_pointer_should_write_successfully() {
263        verify_writes(
264            &[MidiMessage::SongPositionPointer((0x76, 0x34).into())],
265            &[0xF2, 0x76, 0x34],
266        );
267    }
268    #[test]
269    fn song_select_should_write_successfully() {
270        verify_writes(&[MidiMessage::SongSelect(0x76.into())], &[0xF3, 0x76]);
271    }
272    #[test]
273    fn tune_request_should_write_successfully() {
274        verify_writes(&[MidiMessage::TuneRequest], &[0xF6]);
275    }
276    #[test]
277    fn timing_clock_should_write_successfully() {
278        verify_writes(&[MidiMessage::TimingClock], &[0xF8]);
279    }
280    #[test]
281    fn start_should_write_successfully() {
282        verify_writes(&[MidiMessage::Start], &[0xFA]);
283    }
284    #[test]
285    fn continue_should_write_successfully() {
286        verify_writes(&[MidiMessage::Continue], &[0xFB]);
287    }
288    #[test]
289    fn stop_should_write_successfully() {
290        verify_writes(&[MidiMessage::Stop], &[0xFC]);
291    }
292    #[test]
293    fn active_sensing_should_write_successfully() {
294        verify_writes(&[MidiMessage::ActiveSensing], &[0xFE]);
295    }
296    #[test]
297    fn reset_should_write_successfully() {
298        verify_writes(&[MidiMessage::Reset], &[0xFF]);
299    }
300}