1use crate::{ControlFunction, Error, Note, ToSliceError, U14, U7};
2use core::convert::TryFrom;
3
4#[cfg(feature = "std")]
5use std::{io, vec::Vec};
6
7#[derive(Clone, Debug, PartialEq, Eq)]
9pub enum MidiMessage<'a> {
10 NoteOff(Channel, Note, Velocity),
12
13 NoteOn(Channel, Note, Velocity),
15
16 PolyphonicKeyPressure(Channel, Note, Velocity),
18
19 ControlChange(Channel, ControlFunction, ControlValue),
23
24 ProgramChange(Channel, ProgramNumber),
26
27 ChannelPressure(Channel, Velocity),
31
32 PitchBendChange(Channel, PitchBend),
35
36 SysEx(&'a [U7]),
44
45 #[cfg(feature = "std")]
53 OwnedSysEx(Vec<U7>),
54
55 MidiTimeCode(U7),
61
62 SongPositionPointer(SongPosition),
65
66 SongSelect(Song),
68
69 Reserved(u8),
71
72 TuneRequest,
74
75 TimingClock,
77
78 Start,
80
81 Continue,
83
84 Stop,
86
87 ActiveSensing,
92
93 Reset,
96}
97
98impl<'a> TryFrom<&'a [u8]> for MidiMessage<'a> {
99 type Error = Error;
100 fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
102 if bytes.is_empty() {
103 return Err(Error::NoBytes);
104 }
105 if !is_status_byte(bytes[0]) {
106 return Err(Error::UnexpectedDataByte);
107 }
108 let chan = Channel::from_index(bytes[0] & 0x0F)?;
109 let data_a = bytes
110 .get(1)
111 .ok_or(Error::NotEnoughBytes)
112 .and_then(|b| valid_data_byte(*b));
113 let data_b = bytes
114 .get(2)
115 .ok_or(Error::NotEnoughBytes)
116 .and_then(|b| valid_data_byte(*b));
117 match bytes[0] & 0xF0 {
118 0x80 => Ok(MidiMessage::NoteOff(chan, Note::from(data_a?), data_b?)),
119 0x90 => match data_b? {
120 U7::MIN => Ok(MidiMessage::NoteOff(chan, Note::from(data_a?), U7::MIN)),
121 _ => Ok(MidiMessage::NoteOn(chan, Note::from(data_a?), data_b?)),
122 },
123 0xA0 => Ok(MidiMessage::PolyphonicKeyPressure(
124 chan,
125 Note::from(data_a?),
126 data_b?,
127 )),
128 0xB0 => Ok(MidiMessage::ControlChange(chan, data_a?.into(), data_b?)),
129 0xC0 => Ok(MidiMessage::ProgramChange(chan, data_a?)),
130 0xD0 => Ok(MidiMessage::ChannelPressure(chan, data_a?)),
131 0xE0 => Ok(MidiMessage::PitchBendChange(
132 chan,
133 combine_data(data_a?, data_b?),
134 )),
135 0xF0 => match bytes[0] {
136 0xF0 => MidiMessage::new_sysex(bytes),
137 0xF1 => Ok(MidiMessage::MidiTimeCode(data_a?)),
138 0xF2 => Ok(MidiMessage::SongPositionPointer(combine_data(
139 data_a?, data_b?,
140 ))),
141 0xF3 => Ok(MidiMessage::SongSelect(data_a?)),
142 0xF4 | 0xF5 => Ok(MidiMessage::Reserved(bytes[0])),
143 0xF6 => Ok(MidiMessage::TuneRequest),
144 0xF7 => Err(Error::UnexpectedEndSysExByte),
145 0xF8 => Ok(MidiMessage::TimingClock),
146 0xF9 => Ok(MidiMessage::Reserved(bytes[0])),
147 0xFA => Ok(MidiMessage::Start),
148 0xFB => Ok(MidiMessage::Continue),
149 0xFC => Ok(MidiMessage::Stop),
150 0xFD => Ok(MidiMessage::Reserved(bytes[0])),
151 0xFE => Ok(MidiMessage::ActiveSensing),
152 0xFF => Ok(MidiMessage::Reset),
153 _ => unreachable!(),
154 },
155 _ => unreachable!(),
156 }
157 }
158}
159
160impl<'a> MidiMessage<'a> {
161 pub fn from_bytes(bytes: &'a [u8]) -> Result<Self, Error> {
163 MidiMessage::try_from(bytes)
164 }
165
166 #[allow(clippy::range_plus_one)]
170 pub fn copy_to_slice(&self, slice: &mut [u8]) -> Result<usize, ToSliceError> {
171 if slice.len() < self.bytes_size() {
172 Err(ToSliceError::BufferTooSmall)
173 } else {
174 let slice = &mut slice[..self.bytes_size()];
175 match self {
176 MidiMessage::NoteOff(a, b, c) => {
177 slice.copy_from_slice(&[0x80 | a.index(), u8::from(*b), u8::from(*c)]);
178 }
179 MidiMessage::NoteOn(a, b, c) => {
180 slice.copy_from_slice(&[0x90 | a.index(), u8::from(*b), u8::from(*c)]);
181 }
182 MidiMessage::PolyphonicKeyPressure(a, b, c) => {
183 slice.copy_from_slice(&[0xA0 | a.index(), *b as u8, u8::from(*c)]);
184 }
185 MidiMessage::ControlChange(a, b, c) => {
186 slice.copy_from_slice(&[0xB0 | a.index(), u8::from(*b), u8::from(*c)]);
187 }
188 MidiMessage::ProgramChange(a, b) => {
189 slice.copy_from_slice(&[0xC0 | a.index(), u8::from(*b)]);
190 }
191 MidiMessage::ChannelPressure(a, b) => {
192 slice.copy_from_slice(&[0xD0 | a.index(), u8::from(*b)]);
193 }
194 MidiMessage::PitchBendChange(a, b) => {
195 let (b1, b2) = split_data(*b);
196 slice.copy_from_slice(&[0xE0 | a.index(), b1, b2]);
197 }
198 MidiMessage::SysEx(b) => {
199 slice[0] = 0xF0;
200 slice[1..1 + b.len()].copy_from_slice(U7::data_to_bytes(b));
201 slice[1 + b.len()] = 0xF7;
202 }
203 #[cfg(feature = "std")]
204 MidiMessage::OwnedSysEx(ref b) => {
205 slice[0] = 0xF0;
206 slice[1..1 + b.len()].copy_from_slice(U7::data_to_bytes(b));
207 slice[1 + b.len()] = 0xF7;
208 }
209 MidiMessage::MidiTimeCode(a) => slice.copy_from_slice(&[0xF1, u8::from(*a)]),
210 MidiMessage::SongPositionPointer(a) => {
211 let (a1, a2) = split_data(*a);
212 slice.copy_from_slice(&[0xF2, a1, a2]);
213 }
214 MidiMessage::SongSelect(a) => slice.copy_from_slice(&[0xF3, u8::from(*a)]),
215 MidiMessage::Reserved(a) => slice.copy_from_slice(&[*a]),
216 MidiMessage::TuneRequest => slice.copy_from_slice(&[0xF6]),
217 MidiMessage::TimingClock => slice.copy_from_slice(&[0xF8]),
218 MidiMessage::Start => slice.copy_from_slice(&[0xFA]),
219 MidiMessage::Continue => slice.copy_from_slice(&[0xFB]),
220 MidiMessage::Stop => slice.copy_from_slice(&[0xFC]),
221 MidiMessage::ActiveSensing => slice.copy_from_slice(&[0xFE]),
222 MidiMessage::Reset => slice.copy_from_slice(&[0xFF]),
223 };
224 Ok(self.bytes_size())
225 }
226 }
227
228 pub fn drop_unowned_sysex(self) -> Option<MidiMessage<'static>> {
231 match self {
232 MidiMessage::NoteOff(a, b, c) => Some(MidiMessage::NoteOff(a, b, c)),
233 MidiMessage::NoteOn(a, b, c) => Some(MidiMessage::NoteOn(a, b, c)),
234 MidiMessage::PolyphonicKeyPressure(a, b, c) => {
235 Some(MidiMessage::PolyphonicKeyPressure(a, b, c))
236 }
237 MidiMessage::ControlChange(a, b, c) => Some(MidiMessage::ControlChange(a, b, c)),
238 MidiMessage::ProgramChange(a, b) => Some(MidiMessage::ProgramChange(a, b)),
239 MidiMessage::ChannelPressure(a, b) => Some(MidiMessage::ChannelPressure(a, b)),
240 MidiMessage::PitchBendChange(a, b) => Some(MidiMessage::PitchBendChange(a, b)),
241 MidiMessage::SysEx(_) => None,
242 #[cfg(feature = "std")]
243 MidiMessage::OwnedSysEx(bytes) => Some(MidiMessage::OwnedSysEx(bytes)),
244 MidiMessage::MidiTimeCode(a) => Some(MidiMessage::MidiTimeCode(a)),
245 MidiMessage::SongPositionPointer(a) => Some(MidiMessage::SongPositionPointer(a)),
246 MidiMessage::SongSelect(a) => Some(MidiMessage::SongSelect(a)),
247 MidiMessage::Reserved(a) => Some(MidiMessage::Reserved(a)),
248 MidiMessage::TuneRequest => Some(MidiMessage::TuneRequest),
249 MidiMessage::TimingClock => Some(MidiMessage::TimingClock),
250 MidiMessage::Start => Some(MidiMessage::Start),
251 MidiMessage::Continue => Some(MidiMessage::Continue),
252 MidiMessage::Stop => Some(MidiMessage::Stop),
253 MidiMessage::ActiveSensing => Some(MidiMessage::ActiveSensing),
254 MidiMessage::Reset => Some(MidiMessage::Reset),
255 }
256 }
257
258 #[inline(always)]
261 pub fn to_owned(&self) -> MidiMessage<'static> {
262 match self.clone() {
263 MidiMessage::NoteOff(a, b, c) => MidiMessage::NoteOff(a, b, c),
264 MidiMessage::NoteOn(a, b, c) => MidiMessage::NoteOn(a, b, c),
265 MidiMessage::PolyphonicKeyPressure(a, b, c) => {
266 MidiMessage::PolyphonicKeyPressure(a, b, c)
267 }
268 MidiMessage::ControlChange(a, b, c) => MidiMessage::ControlChange(a, b, c),
269 MidiMessage::ProgramChange(a, b) => MidiMessage::ProgramChange(a, b),
270 MidiMessage::ChannelPressure(a, b) => MidiMessage::ChannelPressure(a, b),
271 MidiMessage::PitchBendChange(a, b) => MidiMessage::PitchBendChange(a, b),
272 #[cfg(feature = "std")]
273 MidiMessage::SysEx(bytes) => MidiMessage::OwnedSysEx(bytes.to_vec()),
274 #[cfg(not(feature = "std"))]
275 MidiMessage::SysEx(_) => MidiMessage::SysEx(&[]), #[cfg(feature = "std")]
277 MidiMessage::OwnedSysEx(bytes) => MidiMessage::OwnedSysEx(bytes),
278 MidiMessage::MidiTimeCode(a) => MidiMessage::MidiTimeCode(a),
279 MidiMessage::SongPositionPointer(a) => MidiMessage::SongPositionPointer(a),
280 MidiMessage::SongSelect(a) => MidiMessage::SongSelect(a),
281 MidiMessage::Reserved(a) => MidiMessage::Reserved(a),
282 MidiMessage::TuneRequest => MidiMessage::TuneRequest,
283 MidiMessage::TimingClock => MidiMessage::TimingClock,
284 MidiMessage::Start => MidiMessage::Start,
285 MidiMessage::Continue => MidiMessage::Continue,
286 MidiMessage::Stop => MidiMessage::Stop,
287 MidiMessage::ActiveSensing => MidiMessage::ActiveSensing,
288 MidiMessage::Reset => MidiMessage::Reset,
289 }
290 }
291
292 pub fn bytes_size(&self) -> usize {
294 match self {
295 MidiMessage::NoteOff(..) => 3,
296 MidiMessage::NoteOn(..) => 3,
297 MidiMessage::PolyphonicKeyPressure(..) => 3,
298 MidiMessage::ControlChange(..) => 3,
299 MidiMessage::ProgramChange(..) => 2,
300 MidiMessage::ChannelPressure(..) => 2,
301 MidiMessage::PitchBendChange(..) => 3,
302 MidiMessage::SysEx(b) => 2 + b.len(),
303 #[cfg(feature = "std")]
304 MidiMessage::OwnedSysEx(b) => 2 + b.len(),
305 MidiMessage::MidiTimeCode(_) => 2,
306 MidiMessage::SongPositionPointer(_) => 3,
307 MidiMessage::SongSelect(_) => 2,
308 MidiMessage::Reserved(_) => 1,
309 MidiMessage::TuneRequest => 1,
310 MidiMessage::TimingClock => 1,
311 MidiMessage::Start => 1,
312 MidiMessage::Continue => 1,
313 MidiMessage::Stop => 1,
314 MidiMessage::ActiveSensing => 1,
315 MidiMessage::Reset => 1,
316 }
317 }
318
319 #[deprecated(
321 since = "3.1.0",
322 note = "Function has been renamed to MidiMessage::bytes_size()."
323 )]
324 pub fn wire_size(&self) -> usize {
325 self.bytes_size()
326 }
327
328 pub fn channel(&self) -> Option<Channel> {
330 match self {
331 MidiMessage::NoteOff(c, ..) => Some(*c),
332 MidiMessage::NoteOn(c, ..) => Some(*c),
333 MidiMessage::PolyphonicKeyPressure(c, ..) => Some(*c),
334 MidiMessage::ControlChange(c, ..) => Some(*c),
335 MidiMessage::ProgramChange(c, ..) => Some(*c),
336 MidiMessage::ChannelPressure(c, ..) => Some(*c),
337 MidiMessage::PitchBendChange(c, ..) => Some(*c),
338 _ => None,
339 }
340 }
341
342 #[inline(always)]
343 fn new_sysex(bytes: &'a [u8]) -> Result<Self, Error> {
344 debug_assert!(bytes[0] == 0xF0);
345 let end_i = 1 + bytes[1..]
346 .iter()
347 .copied()
348 .position(is_status_byte)
349 .ok_or(Error::NoSysExEndByte)?;
350 if bytes[end_i] != 0xF7 {
351 return Err(Error::UnexpectedNonSysExEndByte(bytes[end_i]));
352 }
353 let data_bytes = unsafe { U7::from_bytes_unchecked(&bytes[1..end_i]) };
356 Ok(MidiMessage::SysEx(data_bytes))
357 }
358
359 #[cfg(feature = "std")]
362 pub fn to_vec(&self) -> Vec<u8> {
363 let mut data = vec![0; self.bytes_size()];
364 self.copy_to_slice(&mut data).unwrap();
366 data
367 }
368}
369
370#[cfg(feature = "std")]
371impl<'a> io::Read for MidiMessage<'a> {
372 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
374 match self.copy_to_slice(buf) {
375 Ok(n) => Ok(n),
376 Err(ToSliceError::BufferTooSmall) => Ok(0),
377 }
378 }
379}
380
381pub type Velocity = U7;
383
384pub type ControlValue = U7;
386
387pub type ProgramNumber = U7;
389
390pub type PitchBend = U14;
392
393pub type SongPosition = U14;
395
396pub type Song = U7;
398
399#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
402pub enum Channel {
403 Ch1,
404 Ch2,
405 Ch3,
406 Ch4,
407 Ch5,
408 Ch6,
409 Ch7,
410 Ch8,
411 Ch9,
412 Ch10,
413 Ch11,
414 Ch12,
415 Ch13,
416 Ch14,
417 Ch15,
418 Ch16,
419}
420
421impl Channel {
422 pub fn from_index(i: u8) -> Result<Channel, Error> {
424 match i {
425 0 => Ok(Channel::Ch1),
426 1 => Ok(Channel::Ch2),
427 2 => Ok(Channel::Ch3),
428 3 => Ok(Channel::Ch4),
429 4 => Ok(Channel::Ch5),
430 5 => Ok(Channel::Ch6),
431 6 => Ok(Channel::Ch7),
432 7 => Ok(Channel::Ch8),
433 8 => Ok(Channel::Ch9),
434 9 => Ok(Channel::Ch10),
435 10 => Ok(Channel::Ch11),
436 11 => Ok(Channel::Ch12),
437 12 => Ok(Channel::Ch13),
438 13 => Ok(Channel::Ch14),
439 14 => Ok(Channel::Ch15),
440 15 => Ok(Channel::Ch16),
441 _ => Err(Error::ChannelOutOfRange),
442 }
443 }
444
445 pub fn index(self) -> u8 {
448 match self {
449 Channel::Ch1 => 0,
450 Channel::Ch2 => 1,
451 Channel::Ch3 => 2,
452 Channel::Ch4 => 3,
453 Channel::Ch5 => 4,
454 Channel::Ch6 => 5,
455 Channel::Ch7 => 6,
456 Channel::Ch8 => 7,
457 Channel::Ch9 => 8,
458 Channel::Ch10 => 9,
459 Channel::Ch11 => 10,
460 Channel::Ch12 => 11,
461 Channel::Ch13 => 12,
462 Channel::Ch14 => 13,
463 Channel::Ch15 => 14,
464 Channel::Ch16 => 15,
465 }
466 }
467
468 pub fn number(self) -> u8 {
471 self.index() + 1
472 }
473}
474
475#[inline(always)]
476fn combine_data(lower: U7, higher: U7) -> U14 {
477 let raw = u16::from(u8::from(lower)) + 128 * u16::from(u8::from(higher));
478 unsafe { U14::from_unchecked(raw) }
479}
480
481#[inline(always)]
482fn split_data(data: U14) -> (u8, u8) {
483 ((u16::from(data) % 128) as u8, (u16::from(data) / 128) as u8)
484}
485
486#[inline(always)]
487fn is_status_byte(b: u8) -> bool {
488 b & 0x80 == 0x80
489}
490
491#[inline(always)]
492fn valid_data_byte(b: u8) -> Result<U7, Error> {
493 U7::try_from(b).map_err(|_| Error::UnexpectedStatusByte)
494}
495
496#[cfg(test)]
497mod test {
498 use super::*;
499 use crate::{ControlFunction, Error, Note};
500
501 #[test]
502 fn try_from() {
503 assert_eq!(
504 MidiMessage::try_from([].as_ref()),
505 Err(Error::NoBytes),
506 "no bytes produces an error",
507 );
508 assert_eq!(
509 MidiMessage::try_from([0x00].as_ref()),
510 Err(Error::UnexpectedDataByte),
511 "no status byte produces an error",
512 );
513 assert_eq!(
514 MidiMessage::try_from([0x84].as_ref()),
515 Err(Error::NotEnoughBytes),
516 "NoteOff event produces errors with only 1 byte",
517 );
518 assert_eq!(
519 MidiMessage::try_from([0x84, 64].as_ref()),
520 Err(Error::NotEnoughBytes),
521 "NoteOff event produces errors with only 2 bytes",
522 );
523 assert_eq!(
524 MidiMessage::try_from([0x84, 64, 100].as_ref()),
525 Ok(MidiMessage::NoteOff(
526 Channel::Ch5,
527 Note::E4,
528 U7::try_from(100).unwrap()
529 )),
530 "NoteOff event is decoded.",
531 );
532
533 assert_eq!(
534 MidiMessage::try_from([0x94].as_ref()),
535 Err(Error::NotEnoughBytes),
536 "NoteOn event produces errors with only 1 byte",
537 );
538 assert_eq!(
539 MidiMessage::try_from([0x94, 64].as_ref()),
540 Err(Error::NotEnoughBytes),
541 "NoteOn event produces errors with only 2 bytes",
542 );
543 assert_eq!(
544 MidiMessage::try_from([0x94, 64, 100].as_ref()),
545 Ok(MidiMessage::NoteOn(
546 Channel::Ch5,
547 Note::E4,
548 U7::try_from(100).unwrap()
549 )),
550 "NoteOn event is decoded.",
551 );
552 assert_eq!(
553 MidiMessage::try_from([0x94, 64, 0].as_ref()),
554 Ok(MidiMessage::NoteOff(
555 Channel::Ch5,
556 Note::E4,
557 U7::try_from(0).unwrap()
558 )),
559 "NoteOn message with 0 veloctiy decodes as NoteOff",
560 );
561
562 assert_eq!(
563 MidiMessage::try_from([0xF0, 4, 8, 12, 16, 0xF7].as_ref()),
564 Ok(MidiMessage::SysEx(
565 U7::try_from_bytes(&[4, 8, 12, 16]).unwrap()
566 )),
567 "SysEx message is decoded with borrowed data.",
568 );
569 assert_eq!(
570 MidiMessage::try_from([0xF0, 3, 6, 9, 12, 15, 0xF7, 125].as_ref()),
571 Ok(MidiMessage::SysEx(
572 U7::try_from_bytes(&[3, 6, 9, 12, 15]).unwrap()
573 )),
574 "SysEx message does not include bytes after the end byte.",
575 );
576 assert_eq!(
577 MidiMessage::try_from([0xF0, 1, 2, 3, 4, 5, 6, 7, 8, 9].as_ref()),
578 Err(Error::NoSysExEndByte),
579 "SysEx message without end status produces error.",
580 );
581
582 assert_eq!(
583 MidiMessage::try_from([0xE4].as_ref()),
584 Err(Error::NotEnoughBytes),
585 "PitchBend with single byte produces error.",
586 );
587 assert_eq!(
588 MidiMessage::try_from([0xE4, 64].as_ref()),
589 Err(Error::NotEnoughBytes),
590 "PitchBend with only 2 bytes produces error.",
591 );
592 assert_eq!(
593 MidiMessage::try_from([0xE4, 64, 100].as_ref()),
594 Ok(MidiMessage::PitchBendChange(
595 Channel::Ch5,
596 U14::try_from(12864).unwrap()
597 )),
598 "PitchBendChange is decoded.",
599 );
600 }
601
602 #[test]
603 fn copy_to_slice() {
604 let b = {
605 let mut b = [0u8; 6];
606 let bytes_copied = MidiMessage::PolyphonicKeyPressure(
607 Channel::Ch10,
608 Note::A6,
609 U7::try_from(43).unwrap(),
610 )
611 .copy_to_slice(&mut b)
612 .unwrap();
613 assert_eq!(bytes_copied, 3);
614 b
615 };
616 assert_eq!(b, [0xA9, 93, 43, 0, 0, 0]);
617 }
618
619 #[test]
620 fn copy_to_slice_sysex() {
621 let b = {
622 let mut b = [0u8; 8];
623 let bytes_copied =
624 MidiMessage::SysEx(U7::try_from_bytes(&[10, 20, 30, 40, 50]).unwrap())
625 .copy_to_slice(&mut b)
626 .unwrap();
627 assert_eq!(bytes_copied, 7);
628 b
629 };
630 assert_eq!(b, [0xF0, 10, 20, 30, 40, 50, 0xF7, 0]);
631 }
632
633 #[cfg(feature = "std")]
634 #[test]
635 fn drop_unowned_sysex_with_std() {
636 assert_eq!(
637 MidiMessage::SysEx(U7::try_from_bytes(&[1, 2, 3]).unwrap()).drop_unowned_sysex(),
638 None
639 );
640 assert_eq!(
641 MidiMessage::OwnedSysEx(vec![
642 U7::try_from(1).unwrap(),
643 U7::try_from(2).unwrap(),
644 U7::try_from(3).unwrap()
645 ])
646 .drop_unowned_sysex(),
647 Some(MidiMessage::OwnedSysEx(vec![
648 U7::try_from(1).unwrap(),
649 U7::try_from(2).unwrap(),
650 U7::try_from(3).unwrap()
651 ]))
652 );
653 assert_eq!(
654 MidiMessage::TuneRequest.drop_unowned_sysex(),
655 Some(MidiMessage::TuneRequest)
656 );
657 }
658
659 #[test]
660 fn drop_unowned_sysex_with_nostd() {
661 assert_eq!(
662 MidiMessage::SysEx(U7::try_from_bytes(&[1, 2, 3]).unwrap()).drop_unowned_sysex(),
663 None
664 );
665 assert_eq!(
666 MidiMessage::TuneRequest.drop_unowned_sysex(),
667 Some(MidiMessage::TuneRequest)
668 );
669 }
670
671 #[cfg(feature = "std")]
672 #[test]
673 fn to_owned() {
674 assert_eq!(
675 MidiMessage::SysEx(U7::try_from_bytes(&[1, 2, 3]).unwrap()).to_owned(),
676 MidiMessage::OwnedSysEx(vec![
677 U7::try_from(1).unwrap(),
678 U7::try_from(2).unwrap(),
679 U7::try_from(3).unwrap()
680 ])
681 );
682 assert_ne!(
683 MidiMessage::SysEx(U7::try_from_bytes(&[1, 2, 3]).unwrap()).to_owned(),
684 MidiMessage::SysEx(U7::try_from_bytes(&[1, 2, 3]).unwrap())
685 );
686 }
687
688 #[test]
689 fn channel() {
690 assert_eq!(
691 MidiMessage::ControlChange(
692 Channel::Ch8,
693 ControlFunction::DAMPER_PEDAL,
694 U7::try_from(55).unwrap()
695 )
696 .channel(),
697 Some(Channel::Ch8)
698 );
699 assert_eq!(MidiMessage::Start.channel(), None);
700 }
701}