midi_convert/
render.rs

1use midi_types::{status::*, MidiMessage};
2
3/// This trait abstracts the transport mechanism for the MidiRenderer. An instance of a type that implements this trait can be used by the MidiRenderer to write midi-messages
4pub trait MidiTransport {
5    type Error;
6
7    /// Write a message as series of bytes to the midi transport layer
8    ///
9    /// For compatibility this should always be used to write one whole midi-message with a maximum of 3 bytes
10    fn write(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
11}
12
13/// The MidiRenderer takes MIDI messages and writes them to the underlying transport, the boolean const generic RUNNING_STATUS enables or disables rendering running status for midi messages
14#[derive(Debug)]
15pub struct MidiRenderer<T, const RUNNING_STATUS: bool = true> {
16    transport: T,
17    running_status: Option<u8>,
18}
19
20impl<T: MidiTransport, const RUNNING_STATUS: bool> MidiRenderer<T, RUNNING_STATUS> {
21    pub fn new(transport: T) -> Self {
22        Self {
23            transport,
24            running_status: None,
25        }
26    }
27
28    pub fn release(self) -> T {
29        self.transport
30    }
31
32    pub fn render(&mut self, message: &MidiMessage) -> Result<(), T::Error> {
33        match *message {
34            // Channel voice messages
35            MidiMessage::NoteOn(channel, note, velocity) => {
36                self.write_channel_msg(&[
37                    NOTE_ON + Into::<u8>::into(channel),
38                    note.into(),
39                    velocity.into(),
40                ])?;
41            }
42            MidiMessage::NoteOff(channel, note, velocity) => {
43                self.write_channel_msg(&[
44                    NOTE_OFF + Into::<u8>::into(channel),
45                    note.into(),
46                    velocity.into(),
47                ])?;
48            }
49            MidiMessage::KeyPressure(channel, note, value) => {
50                self.write_channel_msg(&[
51                    KEY_PRESSURE + Into::<u8>::into(channel),
52                    note.into(),
53                    value.into(),
54                ])?;
55            }
56            MidiMessage::ControlChange(channel, control, value) => {
57                self.write_channel_msg(&[
58                    CONTROL_CHANGE + Into::<u8>::into(channel),
59                    control.into(),
60                    value.into(),
61                ])?;
62            }
63            MidiMessage::ProgramChange(channel, program) => {
64                self.write_channel_msg(&[
65                    PROGRAM_CHANGE + Into::<u8>::into(channel),
66                    program.into(),
67                ])?;
68            }
69            MidiMessage::ChannelPressure(channel, value) => {
70                self.write_channel_msg(&[
71                    CHANNEL_PRESSURE + Into::<u8>::into(channel),
72                    value.into(),
73                ])?;
74            }
75            MidiMessage::PitchBendChange(channel, value) => {
76                let (msb, lsb) = value.into();
77                self.write_channel_msg(&[PITCH_BEND_CHANGE + Into::<u8>::into(channel), lsb, msb])?;
78            }
79
80            // System common messages
81            MidiMessage::QuarterFrame(value) => {
82                self.write_sys_common_msg(&[QUARTER_FRAME, value.into()])?;
83            }
84            MidiMessage::SongPositionPointer(value) => {
85                let (msb, lsb) = value.into();
86                self.write_sys_common_msg(&[SONG_POSITION_POINTER, lsb, msb])?;
87            }
88            MidiMessage::SongSelect(value) => {
89                self.write_sys_common_msg(&[SONG_SELECT, value.into()])?;
90            }
91            MidiMessage::TuneRequest => {
92                self.write_sys_common_msg(&[TUNE_REQUEST])?;
93            }
94
95            // System real time messages
96            MidiMessage::TimingClock => self.transport.write(&[TIMING_CLOCK])?,
97            MidiMessage::Start => self.transport.write(&[START])?,
98            MidiMessage::Continue => self.transport.write(&[CONTINUE])?,
99            MidiMessage::Stop => self.transport.write(&[STOP])?,
100            MidiMessage::ActiveSensing => self.transport.write(&[ACTIVE_SENSING])?,
101            MidiMessage::Reset => self.transport.write(&[RESET])?,
102        }
103        Ok(())
104    }
105
106    /// Write a channel voice or channel mode messages, these messages optionally use running status to
107    /// skip sending the status byte
108    fn write_channel_msg(&mut self, data: &[u8]) -> Result<(), T::Error> {
109        let status = data[0];
110        if RUNNING_STATUS && self.running_status == Some(status) {
111            // If the last command written had the same status/channel, the MIDI protocol allows us to
112            // omit sending the status byte again.
113            self.transport.write(&data[1..])?;
114        } else {
115            self.transport.write(data)?;
116
117            if RUNNING_STATUS {
118                // Store running state so the next message can use it
119                self.running_status = Some(status);
120            }
121        }
122
123        Ok(())
124    }
125
126    /// Write a System common message, these messages do not use running status but do reset it
127    fn write_sys_common_msg(&mut self, data: &[u8]) -> Result<(), T::Error> {
128        self.transport.write(data)?;
129
130        if RUNNING_STATUS {
131            self.running_status = None;
132        }
133
134        Ok(())
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use midi_types::Note;
142
143    extern crate std;
144    use std::{string::String, vec::Vec};
145
146    // Test if all midi messages are rendered correctly
147
148    #[test]
149    fn should_render_note_on() {
150        assert_eq!(
151            render::<true>(&[MidiMessage::NoteOn(2.into(), Note::C3, 0x34.into())]),
152            &[0x92, 0x3c, 0x34]
153        );
154    }
155
156    #[test]
157    fn should_render_note_off() {
158        assert_eq!(
159            render::<true>(&[MidiMessage::NoteOff(2.into(), Note::C3, 0x34.into())]),
160            &[0x82, 0x3c, 0x34],
161        );
162    }
163
164    #[test]
165    fn should_render_keypressure() {
166        assert_eq!(
167            render::<true>(&[MidiMessage::KeyPressure(2.into(), 0x76.into(), 0x34.into())]),
168            &[0xA2, 0x76, 0x34],
169        );
170    }
171
172    #[test]
173    fn should_render_control_change() {
174        assert_eq!(
175            render::<true>(&[MidiMessage::ControlChange(
176                2.into(),
177                0x76.into(),
178                0x34.into(),
179            )]),
180            &[0xB2, 0x76, 0x34],
181        );
182    }
183
184    #[test]
185    fn should_render_program_change() {
186        assert_eq!(
187            render::<true>(&[MidiMessage::ProgramChange(2.into(), 0x76.into())]),
188            &[0xC2, 0x76],
189        );
190    }
191
192    #[test]
193    fn should_render_channel_pressure() {
194        assert_eq!(
195            render::<true>(&[MidiMessage::ChannelPressure(2.into(), 0x76.into())]),
196            &[0xD2, 0x76],
197        );
198    }
199
200    #[test]
201    fn should_render_pitchbend() {
202        assert_eq!(
203            render::<true>(&[MidiMessage::PitchBendChange(8.into(), (0x56, 0x14).into(),)]),
204            &[0xE8, 0x14, 0x56],
205        );
206    }
207
208    #[test]
209    fn should_render_quarter_frame() {
210        assert_eq!(
211            render::<true>(&[MidiMessage::QuarterFrame(0x76.into())]),
212            &[0xF1, 0x76]
213        );
214    }
215
216    #[test]
217    fn should_render_song_position_pointer() {
218        assert_eq!(
219            render::<true>(&[MidiMessage::SongPositionPointer((0x68, 0x7f).into())]),
220            &[0xf2, 0x7f, 0x68],
221        );
222    }
223
224    #[test]
225    fn should_render_song_select() {
226        assert_eq!(
227            render::<true>(&[MidiMessage::SongSelect(0x76.into())]),
228            &[0xF3, 0x76]
229        );
230    }
231
232    #[test]
233    fn should_render_tune_request() {
234        assert_eq!(render::<true>(&[MidiMessage::TuneRequest]), &[0xF6]);
235    }
236
237    #[test]
238    fn should_render_timing_clock() {
239        assert_eq!(render::<true>(&[MidiMessage::TimingClock]), &[0xF8]);
240    }
241
242    #[test]
243    fn should_render_start() {
244        assert_eq!(render::<true>(&[MidiMessage::Start]), &[0xFA]);
245    }
246
247    #[test]
248    fn should_render_continue() {
249        assert_eq!(render::<true>(&[MidiMessage::Continue]), &[0xFB]);
250    }
251
252    #[test]
253    fn should_render_stop() {
254        assert_eq!(render::<true>(&[MidiMessage::Stop]), &[0xFC]);
255    }
256
257    #[test]
258    fn should_render_active_sensing() {
259        assert_eq!(render::<true>(&[MidiMessage::ActiveSensing]), &[0xFE]);
260    }
261
262    #[test]
263    fn should_render_reset() {
264        assert_eq!(render::<true>(&[MidiMessage::Reset]), &[0xFF]);
265    }
266
267    // Test running status
268
269    #[test]
270    fn should_skip_repeated_status_with_running_status_on() {
271        assert_eq!(
272            render::<true>(&[
273                MidiMessage::NoteOn(2.into(), Note::D4, 0x34.into()),
274                MidiMessage::NoteOn(2.into(), Note::G6, 0x65.into()),
275            ]),
276            &[0x92, 0x4a, 0x34, 0x67, 0x65],
277        );
278    }
279
280    #[test]
281    fn should_not_skip_repeated_status_with_running_status_off() {
282        assert_eq!(
283            render::<false>(&[
284                MidiMessage::NoteOn(2.into(), Note::D4, 0x34.into()),
285                MidiMessage::NoteOff(2.into(), Note::G6, 0x65.into()),
286            ]),
287            &[0x92, 0x4a, 0x34, 0x82, 0x67, 0x65],
288        );
289    }
290
291    #[test]
292    fn should_not_skip_status_when_channel_changes() {
293        assert_eq!(
294            render::<true>(&[
295                MidiMessage::NoteOn(2.into(), Note::D4, 0x34.into()),
296                MidiMessage::NoteOn(3.into(), Note::G6, 0x65.into()),
297            ]),
298            &[0x92, 0x4a, 0x34, 0x93, 0x67, 0x65],
299        );
300    }
301
302    #[test]
303    fn should_not_skip_status_for_different_message() {
304        assert_eq!(
305            render::<true>(&[
306                MidiMessage::NoteOn(2.into(), Note::D4, 0x34.into()),
307                MidiMessage::NoteOff(2.into(), Note::G6, 0x65.into()),
308            ]),
309            &[0x92, 0x4a, 0x34, 0x82, 0x67, 0x65],
310        );
311    }
312
313    // Some test helpers
314
315    #[derive(Debug, Default, Clone)]
316    struct MockTransport {
317        buffer: Vec<u8>,
318    }
319
320    impl MidiTransport for MockTransport {
321        type Error = String;
322
323        fn write(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
324            // Make sure all messages fit in a USB midi packet
325            assert!(bytes.len() <= 3, "Too many bytes in one message");
326            bytes.iter().for_each(|value| self.buffer.push(*value));
327            Ok(())
328        }
329    }
330
331    fn render<const RUNNING_STATUS: bool>(messages: &[MidiMessage]) -> Vec<u8> {
332        let mut renderer: MidiRenderer<MockTransport, RUNNING_STATUS> =
333            MidiRenderer::new(MockTransport::default());
334        for message in messages {
335            renderer.render(message).expect("Error rendering message");
336        }
337
338        renderer.transport.buffer
339    }
340}