1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7pub mod prelude {
8 pub use crate::{
9 MidiChannel, MidiControllerNumber, MidiDeviceKind, MidiError, MidiEventKind,
10 MidiMessageKind, MidiNoteNumber, MidiPortName, MidiProfileKind, MidiProgramNumber,
11 MidiPropertyKind, MidiUmpMessageKind, MidiVelocity, MidiVersion,
12 };
13}
14#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
15pub struct MidiPortName(String);
16
17impl MidiPortName {
18 pub fn new(value: impl AsRef<str>) -> Result<Self, MidiError> {
19 non_empty_text(value).map(Self)
20 }
21
22 pub fn as_str(&self) -> &str {
23 &self.0
24 }
25
26 pub fn value(&self) -> &str {
27 self.as_str()
28 }
29
30 pub fn into_string(self) -> String {
31 self.0
32 }
33}
34
35impl AsRef<str> for MidiPortName {
36 fn as_ref(&self) -> &str {
37 self.as_str()
38 }
39}
40
41impl fmt::Display for MidiPortName {
42 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43 formatter.write_str(self.as_str())
44 }
45}
46
47impl FromStr for MidiPortName {
48 type Err = MidiError;
49
50 fn from_str(value: &str) -> Result<Self, Self::Err> {
51 Self::new(value)
52 }
53}
54
55impl TryFrom<&str> for MidiPortName {
56 type Error = MidiError;
57
58 fn try_from(value: &str) -> Result<Self, Self::Error> {
59 Self::new(value)
60 }
61}
62#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
63pub struct MidiChannel(u8);
64
65impl MidiChannel {
66 pub fn new(value: u8) -> Result<Self, MidiError> {
67 if !(1..=16).contains(&value) {
68 return Err(MidiError::OutOfRange);
69 }
70
71 Ok(Self(value))
72 }
73
74 pub const fn value(self) -> u8 {
75 self.0
76 }
77}
78
79impl fmt::Display for MidiChannel {
80 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
81 self.0.fmt(formatter)
82 }
83}
84
85impl FromStr for MidiChannel {
86 type Err = MidiError;
87
88 fn from_str(value: &str) -> Result<Self, Self::Err> {
89 let parsed = value
90 .trim()
91 .parse::<u8>()
92 .map_err(|_| MidiError::InvalidFormat)?;
93 Self::new(parsed)
94 }
95}
96
97impl TryFrom<u8> for MidiChannel {
98 type Error = MidiError;
99
100 fn try_from(value: u8) -> Result<Self, Self::Error> {
101 Self::new(value)
102 }
103}
104#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
105pub struct MidiNoteNumber(u8);
106
107impl MidiNoteNumber {
108 pub fn new(value: u8) -> Result<Self, MidiError> {
109 if !(0..=127).contains(&value) {
110 return Err(MidiError::OutOfRange);
111 }
112
113 Ok(Self(value))
114 }
115
116 pub const fn value(self) -> u8 {
117 self.0
118 }
119}
120
121impl fmt::Display for MidiNoteNumber {
122 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
123 self.0.fmt(formatter)
124 }
125}
126
127impl FromStr for MidiNoteNumber {
128 type Err = MidiError;
129
130 fn from_str(value: &str) -> Result<Self, Self::Err> {
131 let parsed = value
132 .trim()
133 .parse::<u8>()
134 .map_err(|_| MidiError::InvalidFormat)?;
135 Self::new(parsed)
136 }
137}
138
139impl TryFrom<u8> for MidiNoteNumber {
140 type Error = MidiError;
141
142 fn try_from(value: u8) -> Result<Self, Self::Error> {
143 Self::new(value)
144 }
145}
146#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
147pub struct MidiVelocity(u8);
148
149impl MidiVelocity {
150 pub fn new(value: u8) -> Result<Self, MidiError> {
151 if !(0..=127).contains(&value) {
152 return Err(MidiError::OutOfRange);
153 }
154
155 Ok(Self(value))
156 }
157
158 pub const fn value(self) -> u8 {
159 self.0
160 }
161}
162
163impl fmt::Display for MidiVelocity {
164 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
165 self.0.fmt(formatter)
166 }
167}
168
169impl FromStr for MidiVelocity {
170 type Err = MidiError;
171
172 fn from_str(value: &str) -> Result<Self, Self::Err> {
173 let parsed = value
174 .trim()
175 .parse::<u8>()
176 .map_err(|_| MidiError::InvalidFormat)?;
177 Self::new(parsed)
178 }
179}
180
181impl TryFrom<u8> for MidiVelocity {
182 type Error = MidiError;
183
184 fn try_from(value: u8) -> Result<Self, Self::Error> {
185 Self::new(value)
186 }
187}
188#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
189pub struct MidiControllerNumber(u8);
190
191impl MidiControllerNumber {
192 pub fn new(value: u8) -> Result<Self, MidiError> {
193 if !(0..=127).contains(&value) {
194 return Err(MidiError::OutOfRange);
195 }
196
197 Ok(Self(value))
198 }
199
200 pub const fn value(self) -> u8 {
201 self.0
202 }
203}
204
205impl fmt::Display for MidiControllerNumber {
206 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
207 self.0.fmt(formatter)
208 }
209}
210
211impl FromStr for MidiControllerNumber {
212 type Err = MidiError;
213
214 fn from_str(value: &str) -> Result<Self, Self::Err> {
215 let parsed = value
216 .trim()
217 .parse::<u8>()
218 .map_err(|_| MidiError::InvalidFormat)?;
219 Self::new(parsed)
220 }
221}
222
223impl TryFrom<u8> for MidiControllerNumber {
224 type Error = MidiError;
225
226 fn try_from(value: u8) -> Result<Self, Self::Error> {
227 Self::new(value)
228 }
229}
230#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
231pub struct MidiProgramNumber(u8);
232
233impl MidiProgramNumber {
234 pub fn new(value: u8) -> Result<Self, MidiError> {
235 if !(0..=127).contains(&value) {
236 return Err(MidiError::OutOfRange);
237 }
238
239 Ok(Self(value))
240 }
241
242 pub const fn value(self) -> u8 {
243 self.0
244 }
245}
246
247impl fmt::Display for MidiProgramNumber {
248 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
249 self.0.fmt(formatter)
250 }
251}
252
253impl FromStr for MidiProgramNumber {
254 type Err = MidiError;
255
256 fn from_str(value: &str) -> Result<Self, Self::Err> {
257 let parsed = value
258 .trim()
259 .parse::<u8>()
260 .map_err(|_| MidiError::InvalidFormat)?;
261 Self::new(parsed)
262 }
263}
264
265impl TryFrom<u8> for MidiProgramNumber {
266 type Error = MidiError;
267
268 fn try_from(value: u8) -> Result<Self, Self::Error> {
269 Self::new(value)
270 }
271}
272#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
273pub enum MidiVersion {
274 Midi1,
275 Midi2,
276}
277
278impl MidiVersion {
279 pub const ALL: &'static [Self] = &[Self::Midi1, Self::Midi2];
280
281 pub const fn as_str(self) -> &'static str {
282 match self {
283 Self::Midi1 => "midi-1",
284 Self::Midi2 => "midi-2",
285 }
286 }
287}
288
289impl fmt::Display for MidiVersion {
290 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
291 formatter.write_str(self.as_str())
292 }
293}
294
295impl FromStr for MidiVersion {
296 type Err = MidiError;
297
298 fn from_str(value: &str) -> Result<Self, Self::Err> {
299 match normalized_label(value)?.as_str() {
300 "midi-1" => Ok(Self::Midi1),
301 "midi-2" => Ok(Self::Midi2),
302 _ => Err(MidiError::UnknownLabel),
303 }
304 }
305}
306#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
307pub enum MidiMessageKind {
308 NoteOff,
309 NoteOn,
310 PolyphonicKeyPressure,
311 ControlChange,
312 ProgramChange,
313 ChannelPressure,
314 PitchBend,
315 SystemExclusive,
316 SystemCommon,
317 SystemRealTime,
318 UniversalMidiPacket,
319 Unknown,
320}
321
322impl MidiMessageKind {
323 pub const ALL: &'static [Self] = &[
324 Self::NoteOff,
325 Self::NoteOn,
326 Self::PolyphonicKeyPressure,
327 Self::ControlChange,
328 Self::ProgramChange,
329 Self::ChannelPressure,
330 Self::PitchBend,
331 Self::SystemExclusive,
332 Self::SystemCommon,
333 Self::SystemRealTime,
334 Self::UniversalMidiPacket,
335 Self::Unknown,
336 ];
337
338 pub const fn as_str(self) -> &'static str {
339 match self {
340 Self::NoteOff => "note-off",
341 Self::NoteOn => "note-on",
342 Self::PolyphonicKeyPressure => "polyphonic-key-pressure",
343 Self::ControlChange => "control-change",
344 Self::ProgramChange => "program-change",
345 Self::ChannelPressure => "channel-pressure",
346 Self::PitchBend => "pitch-bend",
347 Self::SystemExclusive => "system-exclusive",
348 Self::SystemCommon => "system-common",
349 Self::SystemRealTime => "system-real-time",
350 Self::UniversalMidiPacket => "universal-midi-packet",
351 Self::Unknown => "unknown",
352 }
353 }
354}
355
356impl fmt::Display for MidiMessageKind {
357 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
358 formatter.write_str(self.as_str())
359 }
360}
361
362impl FromStr for MidiMessageKind {
363 type Err = MidiError;
364
365 fn from_str(value: &str) -> Result<Self, Self::Err> {
366 match normalized_label(value)?.as_str() {
367 "note-off" => Ok(Self::NoteOff),
368 "note-on" => Ok(Self::NoteOn),
369 "polyphonic-key-pressure" => Ok(Self::PolyphonicKeyPressure),
370 "control-change" => Ok(Self::ControlChange),
371 "program-change" => Ok(Self::ProgramChange),
372 "channel-pressure" => Ok(Self::ChannelPressure),
373 "pitch-bend" => Ok(Self::PitchBend),
374 "system-exclusive" => Ok(Self::SystemExclusive),
375 "system-common" => Ok(Self::SystemCommon),
376 "system-real-time" => Ok(Self::SystemRealTime),
377 "universal-midi-packet" => Ok(Self::UniversalMidiPacket),
378 "unknown" => Ok(Self::Unknown),
379 _ => Err(MidiError::UnknownLabel),
380 }
381 }
382}
383#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
384pub enum MidiEventKind {
385 ChannelVoice,
386 System,
387 Meta,
388 Sysex,
389 Ump,
390 Unknown,
391}
392
393impl MidiEventKind {
394 pub const ALL: &'static [Self] = &[
395 Self::ChannelVoice,
396 Self::System,
397 Self::Meta,
398 Self::Sysex,
399 Self::Ump,
400 Self::Unknown,
401 ];
402
403 pub const fn as_str(self) -> &'static str {
404 match self {
405 Self::ChannelVoice => "channel-voice",
406 Self::System => "system",
407 Self::Meta => "meta",
408 Self::Sysex => "sysex",
409 Self::Ump => "ump",
410 Self::Unknown => "unknown",
411 }
412 }
413}
414
415impl fmt::Display for MidiEventKind {
416 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
417 formatter.write_str(self.as_str())
418 }
419}
420
421impl FromStr for MidiEventKind {
422 type Err = MidiError;
423
424 fn from_str(value: &str) -> Result<Self, Self::Err> {
425 match normalized_label(value)?.as_str() {
426 "channel-voice" => Ok(Self::ChannelVoice),
427 "system" => Ok(Self::System),
428 "meta" => Ok(Self::Meta),
429 "sysex" => Ok(Self::Sysex),
430 "ump" => Ok(Self::Ump),
431 "unknown" => Ok(Self::Unknown),
432 _ => Err(MidiError::UnknownLabel),
433 }
434 }
435}
436#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
437pub enum MidiDeviceKind {
438 Keyboard,
439 Controller,
440 Synthesizer,
441 DrumMachine,
442 Sequencer,
443 Daw,
444 Interface,
445 Unknown,
446}
447
448impl MidiDeviceKind {
449 pub const ALL: &'static [Self] = &[
450 Self::Keyboard,
451 Self::Controller,
452 Self::Synthesizer,
453 Self::DrumMachine,
454 Self::Sequencer,
455 Self::Daw,
456 Self::Interface,
457 Self::Unknown,
458 ];
459
460 pub const fn as_str(self) -> &'static str {
461 match self {
462 Self::Keyboard => "keyboard",
463 Self::Controller => "controller",
464 Self::Synthesizer => "synthesizer",
465 Self::DrumMachine => "drum-machine",
466 Self::Sequencer => "sequencer",
467 Self::Daw => "daw",
468 Self::Interface => "interface",
469 Self::Unknown => "unknown",
470 }
471 }
472}
473
474impl fmt::Display for MidiDeviceKind {
475 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
476 formatter.write_str(self.as_str())
477 }
478}
479
480impl FromStr for MidiDeviceKind {
481 type Err = MidiError;
482
483 fn from_str(value: &str) -> Result<Self, Self::Err> {
484 match normalized_label(value)?.as_str() {
485 "keyboard" => Ok(Self::Keyboard),
486 "controller" => Ok(Self::Controller),
487 "synthesizer" => Ok(Self::Synthesizer),
488 "drum-machine" => Ok(Self::DrumMachine),
489 "sequencer" => Ok(Self::Sequencer),
490 "daw" => Ok(Self::Daw),
491 "interface" => Ok(Self::Interface),
492 "unknown" => Ok(Self::Unknown),
493 _ => Err(MidiError::UnknownLabel),
494 }
495 }
496}
497#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
498pub enum MidiProfileKind {
499 DefaultControlChange,
500 Mpe,
501 DrawbarOrgan,
502 OrchestralArticulation,
503 Custom,
504}
505
506impl MidiProfileKind {
507 pub const ALL: &'static [Self] = &[
508 Self::DefaultControlChange,
509 Self::Mpe,
510 Self::DrawbarOrgan,
511 Self::OrchestralArticulation,
512 Self::Custom,
513 ];
514
515 pub const fn as_str(self) -> &'static str {
516 match self {
517 Self::DefaultControlChange => "default-control-change",
518 Self::Mpe => "mpe",
519 Self::DrawbarOrgan => "drawbar-organ",
520 Self::OrchestralArticulation => "orchestral-articulation",
521 Self::Custom => "custom",
522 }
523 }
524}
525
526impl fmt::Display for MidiProfileKind {
527 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
528 formatter.write_str(self.as_str())
529 }
530}
531
532impl FromStr for MidiProfileKind {
533 type Err = MidiError;
534
535 fn from_str(value: &str) -> Result<Self, Self::Err> {
536 match normalized_label(value)?.as_str() {
537 "default-control-change" => Ok(Self::DefaultControlChange),
538 "mpe" => Ok(Self::Mpe),
539 "drawbar-organ" => Ok(Self::DrawbarOrgan),
540 "orchestral-articulation" => Ok(Self::OrchestralArticulation),
541 "custom" => Ok(Self::Custom),
542 _ => Err(MidiError::UnknownLabel),
543 }
544 }
545}
546#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
547pub enum MidiPropertyKind {
548 PerNoteExpression,
549 PropertyExchange,
550 ProfileConfiguration,
551 EndpointInfo,
552 Custom,
553}
554
555impl MidiPropertyKind {
556 pub const ALL: &'static [Self] = &[
557 Self::PerNoteExpression,
558 Self::PropertyExchange,
559 Self::ProfileConfiguration,
560 Self::EndpointInfo,
561 Self::Custom,
562 ];
563
564 pub const fn as_str(self) -> &'static str {
565 match self {
566 Self::PerNoteExpression => "per-note-expression",
567 Self::PropertyExchange => "property-exchange",
568 Self::ProfileConfiguration => "profile-configuration",
569 Self::EndpointInfo => "endpoint-info",
570 Self::Custom => "custom",
571 }
572 }
573}
574
575impl fmt::Display for MidiPropertyKind {
576 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
577 formatter.write_str(self.as_str())
578 }
579}
580
581impl FromStr for MidiPropertyKind {
582 type Err = MidiError;
583
584 fn from_str(value: &str) -> Result<Self, Self::Err> {
585 match normalized_label(value)?.as_str() {
586 "per-note-expression" => Ok(Self::PerNoteExpression),
587 "property-exchange" => Ok(Self::PropertyExchange),
588 "profile-configuration" => Ok(Self::ProfileConfiguration),
589 "endpoint-info" => Ok(Self::EndpointInfo),
590 "custom" => Ok(Self::Custom),
591 _ => Err(MidiError::UnknownLabel),
592 }
593 }
594}
595#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
596pub enum MidiUmpMessageKind {
597 Utility,
598 System,
599 Midi1ChannelVoice,
600 Data64,
601 Midi2ChannelVoice,
602 Data128,
603 FlexData,
604 Unknown,
605}
606
607impl MidiUmpMessageKind {
608 pub const ALL: &'static [Self] = &[
609 Self::Utility,
610 Self::System,
611 Self::Midi1ChannelVoice,
612 Self::Data64,
613 Self::Midi2ChannelVoice,
614 Self::Data128,
615 Self::FlexData,
616 Self::Unknown,
617 ];
618
619 pub const fn as_str(self) -> &'static str {
620 match self {
621 Self::Utility => "utility",
622 Self::System => "system",
623 Self::Midi1ChannelVoice => "midi-1-channel-voice",
624 Self::Data64 => "data-64",
625 Self::Midi2ChannelVoice => "midi-2-channel-voice",
626 Self::Data128 => "data-128",
627 Self::FlexData => "flex-data",
628 Self::Unknown => "unknown",
629 }
630 }
631}
632
633impl fmt::Display for MidiUmpMessageKind {
634 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
635 formatter.write_str(self.as_str())
636 }
637}
638
639impl FromStr for MidiUmpMessageKind {
640 type Err = MidiError;
641
642 fn from_str(value: &str) -> Result<Self, Self::Err> {
643 match normalized_label(value)?.as_str() {
644 "utility" => Ok(Self::Utility),
645 "system" => Ok(Self::System),
646 "midi-1-channel-voice" => Ok(Self::Midi1ChannelVoice),
647 "data-64" => Ok(Self::Data64),
648 "midi-2-channel-voice" => Ok(Self::Midi2ChannelVoice),
649 "data-128" => Ok(Self::Data128),
650 "flex-data" => Ok(Self::FlexData),
651 "unknown" => Ok(Self::Unknown),
652 _ => Err(MidiError::UnknownLabel),
653 }
654 }
655}
656
657#[derive(Clone, Copy, Debug, Eq, PartialEq)]
658pub enum MidiError {
659 Empty,
660 InvalidFormat,
661 OutOfRange,
662 NonFinite,
663 NonPositive,
664 UnknownLabel,
665}
666
667impl fmt::Display for MidiError {
668 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
669 match self {
670 Self::Empty => formatter.write_str("MIDI metadata text cannot be empty"),
671 Self::InvalidFormat => formatter.write_str("MIDI metadata has an invalid format"),
672 Self::OutOfRange => formatter.write_str("MIDI metadata value is out of range"),
673 Self::NonFinite => formatter.write_str("MIDI metadata value must be finite"),
674 Self::NonPositive => formatter.write_str("MIDI metadata value must be positive"),
675 Self::UnknownLabel => formatter.write_str("unknown MIDI metadata label"),
676 }
677 }
678}
679
680impl Error for MidiError {}
681
682#[allow(dead_code)]
683fn non_empty_text(value: impl AsRef<str>) -> Result<String, MidiError> {
684 let trimmed = value.as_ref().trim();
685 if trimmed.is_empty() {
686 Err(MidiError::Empty)
687 } else {
688 Ok(trimmed.to_string())
689 }
690}
691
692fn normalized_label(value: &str) -> Result<String, MidiError> {
693 let trimmed = value.trim();
694 if trimmed.is_empty() {
695 Err(MidiError::Empty)
696 } else {
697 Ok(trimmed.to_ascii_lowercase().replace(['_', ' '], "-"))
698 }
699}
700#[cfg(test)]
701#[allow(
702 unused_imports,
703 clippy::unnecessary_wraps,
704 clippy::assertions_on_constants
705)]
706mod tests {
707 use super::{
708 MidiChannel, MidiControllerNumber, MidiDeviceKind, MidiError, MidiEventKind,
709 MidiMessageKind, MidiNoteNumber, MidiPortName, MidiProfileKind, MidiProgramNumber,
710 MidiPropertyKind, MidiUmpMessageKind, MidiVelocity, MidiVersion,
711 };
712 use core::{fmt, str::FromStr};
713
714 fn assert_enum_family<T>(variants: &[T]) -> Result<(), MidiError>
715 where
716 T: Copy + Eq + fmt::Debug + fmt::Display + FromStr<Err = MidiError>,
717 {
718 for variant in variants {
719 let label = variant.to_string();
720 assert_eq!(label.parse::<T>()?, *variant);
721 assert_eq!(label.replace('-', "_").parse::<T>()?, *variant);
722 assert_eq!(label.replace('-', " ").parse::<T>()?, *variant);
723 }
724 Ok(())
725 }
726
727 #[test]
728 fn validates_text_newtypes() -> Result<(), MidiError> {
729 let value = MidiPortName::new(" example-value ")?;
730 assert_eq!(value.as_str(), "example-value");
731 assert_eq!(value.value(), "example-value");
732 assert_eq!(value.to_string(), "example-value");
733 assert_eq!(
734 <MidiPortName as TryFrom<&str>>::try_from("example-value")?,
735 value
736 );
737 Ok(())
738 }
739
740 #[test]
741 fn validates_numeric_newtypes() -> Result<(), MidiError> {
742 let value = MidiChannel::new(1)?;
743 assert_eq!(value.value(), 1);
744 assert_eq!("1".parse::<MidiChannel>()?, value);
745 assert_eq!(MidiChannel::new(17), Err(MidiError::OutOfRange));
746 let value = MidiNoteNumber::new(0)?;
747 assert_eq!(value.value(), 0);
748 assert_eq!("0".parse::<MidiNoteNumber>()?, value);
749 assert_eq!(MidiNoteNumber::new(128), Err(MidiError::OutOfRange));
750 let value = MidiVelocity::new(0)?;
751 assert_eq!(value.value(), 0);
752 assert_eq!("0".parse::<MidiVelocity>()?, value);
753 assert_eq!(MidiVelocity::new(128), Err(MidiError::OutOfRange));
754 let value = MidiControllerNumber::new(0)?;
755 assert_eq!(value.value(), 0);
756 assert_eq!("0".parse::<MidiControllerNumber>()?, value);
757 assert_eq!(MidiControllerNumber::new(128), Err(MidiError::OutOfRange));
758 let value = MidiProgramNumber::new(0)?;
759 assert_eq!(value.value(), 0);
760 assert_eq!("0".parse::<MidiProgramNumber>()?, value);
761 assert_eq!(MidiProgramNumber::new(128), Err(MidiError::OutOfRange));
762 Ok(())
763 }
764
765 #[test]
766 fn displays_and_parses_enums() -> Result<(), MidiError> {
767 assert_enum_family(MidiVersion::ALL)?;
768 assert_enum_family(MidiMessageKind::ALL)?;
769 assert_enum_family(MidiEventKind::ALL)?;
770 assert_enum_family(MidiDeviceKind::ALL)?;
771 assert_enum_family(MidiProfileKind::ALL)?;
772 assert_enum_family(MidiPropertyKind::ALL)?;
773 assert_enum_family(MidiUmpMessageKind::ALL)?;
774 Ok(())
775 }
776}