1use midi_types::{status::*, MidiMessage};
2
3pub trait MidiTransport {
5 type Error;
6
7 fn write(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
11}
12
13#[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 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 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 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 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 self.transport.write(&data[1..])?;
114 } else {
115 self.transport.write(data)?;
116
117 if RUNNING_STATUS {
118 self.running_status = Some(status);
120 }
121 }
122
123 Ok(())
124 }
125
126 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]
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]
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 #[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 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}