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#[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}