Skip to main content

midi_io/midi/
raw_message.rs

1use std::ops::Deref;
2
3use crate::midi::decode::decode;
4use crate::midi::decode::Decoded;
5use crate::midi::message::MidiMessage;
6use crate::CodecError;
7
8/// An unparsed MIDI message.
9#[derive(Copy, Clone)]
10pub struct RawMidiMessage {
11    bytes: [u8; 3],
12    len: u8,
13}
14
15impl PartialEq for RawMidiMessage {
16    fn eq(&self, other: &Self) -> bool {
17        **self == **other
18    }
19}
20
21impl Eq for RawMidiMessage {}
22
23impl RawMidiMessage {
24    fn new(bytes: [u8; 3], len: u8) -> Self {
25        debug_assert!(len <= 3);
26        Self { bytes, len }
27    }
28
29    #[cfg(test)]
30    pub(crate) fn from_slice(bytes: &[u8]) -> Self {
31        let len = bytes.len().min(3);
32        let mut buf = [0u8; 3];
33        buf[..len].copy_from_slice(&bytes[..len]);
34        Self::new(buf, len as u8)
35    }
36}
37
38impl Deref for RawMidiMessage {
39    type Target = [u8];
40
41    fn deref(&self) -> &[u8] {
42        &self.bytes[..self.len as usize]
43    }
44}
45
46impl std::fmt::Debug for RawMidiMessage {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        write!(f, "{:02x?}", &**self)
49    }
50}
51
52impl From<&MidiMessage> for RawMidiMessage {
53    fn from(msg: &MidiMessage) -> Self {
54        match msg {
55            MidiMessage::NoteOff {
56                channel,
57                key,
58                velocity,
59            } => Self::new([0x80 | channel.index(), key.get(), velocity.get()], 3),
60            MidiMessage::NoteOn {
61                channel,
62                key,
63                velocity,
64            } => Self::new([0x90 | channel.index(), key.get(), velocity.get()], 3),
65            MidiMessage::PolyKeyPressure {
66                channel,
67                key,
68                pressure,
69            } => Self::new([0xA0 | channel.index(), key.get(), pressure.get()], 3),
70            MidiMessage::ControlChange {
71                channel,
72                controller,
73                value,
74            } => Self::new([0xB0 | channel.index(), controller.get(), value.get()], 3),
75            MidiMessage::ProgramChange { channel, program } => {
76                Self::new([0xC0 | channel.index(), program.get(), 0], 2)
77            }
78            MidiMessage::ChannelPressure { channel, pressure } => {
79                Self::new([0xD0 | channel.index(), pressure.get(), 0], 2)
80            }
81            MidiMessage::PitchBend { channel, value } => {
82                Self::new([0xE0 | channel.index(), value.lsb(), value.msb()], 3)
83            }
84            MidiMessage::MtcQuarterFrame(data) => Self::new([0xF1, data.get(), 0], 2),
85            MidiMessage::SongPositionPointer(pos) => Self::new([0xF2, pos.lsb(), pos.msb()], 3),
86            MidiMessage::SongSelect(song) => Self::new([0xF3, song.get(), 0], 2),
87            MidiMessage::TuneRequest => Self::new([0xF6, 0, 0], 1),
88            MidiMessage::TimingClock => Self::new([0xF8, 0, 0], 1),
89            MidiMessage::Start => Self::new([0xFA, 0, 0], 1),
90            MidiMessage::Continue => Self::new([0xFB, 0, 0], 1),
91            MidiMessage::Stop => Self::new([0xFC, 0, 0], 1),
92            MidiMessage::ActiveSensing => Self::new([0xFE, 0, 0], 1),
93            MidiMessage::Reset => Self::new([0xFF, 0, 0], 1),
94        }
95    }
96}
97
98impl From<MidiMessage> for RawMidiMessage {
99    fn from(msg: MidiMessage) -> Self {
100        Self::from(&msg)
101    }
102}
103
104impl TryFrom<RawMidiMessage> for MidiMessage {
105    type Error = CodecError;
106
107    fn try_from(raw: RawMidiMessage) -> Result<Self, CodecError> {
108        match decode(&raw) {
109            Ok(Decoded::Message(msg)) => Ok(msg),
110            _ => Err(CodecError::Unparseable(raw)),
111        }
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use crate::midi::data_byte::DataByte;
119    use crate::midi::pitch_bend::PitchBend;
120    use crate::midi::pitch_bend::PITCH_BEND_CENTER;
121    use crate::midi::song_position::SongPosition;
122    use crate::Channel;
123
124    fn roundtrip(msg: &MidiMessage) {
125        let raw = RawMidiMessage::from(msg);
126        let decoded = MidiMessage::try_from(raw).unwrap();
127        assert_eq!(decoded, *msg, "roundtrip failed for {msg:?}");
128    }
129
130    #[test]
131    fn channel_voice_roundtrip() {
132        for ch in (0..16u8).map(|i| Channel::from_index(i).unwrap()) {
133            for (key, vel) in [(0u8, 1u8), (63, 100), (127, 127)] {
134                roundtrip(&MidiMessage::NoteOn {
135                    channel: ch,
136                    key: DataByte::try_from(key).unwrap(),
137                    velocity: DataByte::try_from(vel).unwrap(),
138                });
139            }
140            for (key, vel) in [(0u8, 0u8), (63, 100), (127, 127)] {
141                roundtrip(&MidiMessage::NoteOff {
142                    channel: ch,
143                    key: DataByte::try_from(key).unwrap(),
144                    velocity: DataByte::try_from(vel).unwrap(),
145                });
146            }
147
148            for (ctl, val) in [(0u8, 0u8), (64, 64), (127, 127)] {
149                roundtrip(&MidiMessage::ControlChange {
150                    channel: ch,
151                    controller: DataByte::try_from(ctl).unwrap(),
152                    value: DataByte::try_from(val).unwrap(),
153                });
154            }
155
156            for val in [0u8, 64, 127] {
157                roundtrip(&MidiMessage::ProgramChange {
158                    channel: ch,
159                    program: DataByte::try_from(val).unwrap(),
160                });
161                roundtrip(&MidiMessage::ChannelPressure {
162                    channel: ch,
163                    pressure: DataByte::try_from(val).unwrap(),
164                });
165            }
166
167            for (key, pres) in [(0u8, 0u8), (63, 64), (127, 127)] {
168                roundtrip(&MidiMessage::PolyKeyPressure {
169                    channel: ch,
170                    key: DataByte::try_from(key).unwrap(),
171                    pressure: DataByte::try_from(pres).unwrap(),
172                });
173            }
174        }
175    }
176
177    #[test]
178    fn note_on_velocity_zero_decodes_as_note_off() {
179        let raw = RawMidiMessage::from(&MidiMessage::NoteOn {
180            channel: Channel::Ch1,
181            key: DataByte::try_from(60).unwrap(),
182            velocity: DataByte::try_from(0).unwrap(),
183        });
184        assert_eq!(&*raw, &[0x90, 60, 0]);
185        assert_eq!(
186            MidiMessage::try_from(raw).unwrap(),
187            MidiMessage::NoteOff {
188                channel: Channel::Ch1,
189                key: DataByte::try_from(60).unwrap(),
190                velocity: DataByte::try_from(0).unwrap(),
191            }
192        );
193    }
194
195    #[test]
196    fn pitch_bend_roundtrip_boundaries() {
197        for value in [-PITCH_BEND_CENTER, -4096, -1, 0, 1, PITCH_BEND_CENTER - 1] {
198            roundtrip(&MidiMessage::PitchBend {
199                channel: Channel::Ch1,
200                value: PitchBend::from_signed(value).unwrap(),
201            });
202        }
203    }
204
205    #[test]
206    fn system_common_one_byte() {
207        for msg in [
208            MidiMessage::TimingClock,
209            MidiMessage::Start,
210            MidiMessage::Continue,
211            MidiMessage::Stop,
212            MidiMessage::ActiveSensing,
213            MidiMessage::Reset,
214            MidiMessage::TuneRequest,
215        ] {
216            roundtrip(&msg);
217        }
218    }
219
220    #[test]
221    fn system_common_two_byte() {
222        for val in [0u8, 3, 7] {
223            roundtrip(&MidiMessage::MtcQuarterFrame(
224                DataByte::try_from(val).unwrap(),
225            ));
226        }
227
228        for val in [0u8, 42, 127] {
229            roundtrip(&MidiMessage::SongSelect(DataByte::try_from(val).unwrap()));
230        }
231    }
232
233    #[test]
234    fn system_common_three_byte() {
235        for pos in [0u16, 8191, 16383] {
236            roundtrip(&MidiMessage::SongPositionPointer(
237                SongPosition::try_from(pos).unwrap(),
238            ));
239        }
240    }
241
242    #[test]
243    fn encoding_produces_legal_data_bytes() {
244        let msg = MidiMessage::NoteOn {
245            channel: Channel::Ch1,
246            key: DataByte::try_from(127).unwrap(),
247            velocity: DataByte::try_from(127).unwrap(),
248        };
249        let raw = RawMidiMessage::from(&msg);
250        assert_eq!(&*raw, &[0x90, 0x7F, 0x7F]);
251        assert!(raw[1..].iter().all(|&b| b <= 0x7F));
252    }
253
254    #[test]
255    fn try_from_raw_unparseable_returns_bytes() {
256        let raw = RawMidiMessage::from_slice(&[0xF4, 0x05]);
257        let err = MidiMessage::try_from(raw).unwrap_err();
258        let CodecError::Unparseable(returned) = err else {
259            panic!("expected Unparseable, got {err:?}");
260        };
261        assert_eq!(&*returned, &[0xF4, 0x05]);
262    }
263
264    #[test]
265    fn from_slice_accepts_arbitrary_bytes() {
266        let raw = RawMidiMessage::from_slice(&[0xF4, 0x90, 0xFF]);
267        assert_eq!(&*raw, &[0xF4, 0x90, 0xFF]);
268    }
269
270    #[test]
271    fn from_slice_truncates_to_three() {
272        let raw = RawMidiMessage::from_slice(&[0x90, 60, 100, 200]);
273        assert_eq!(&*raw, &[0x90, 60, 100]);
274    }
275
276    #[test]
277    fn zero_value_deref_empty() {
278        let raw = RawMidiMessage::from_slice(&[]);
279        assert_eq!(raw.deref().len(), 0);
280    }
281}