shizen_buffers/midi/
midi_message.rs

1use std::ops::{Add, AddAssign, Sub, SubAssign};
2
3#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
4#[non_exhaustive]
5#[repr(u8)]
6pub enum MidiMessage {
7    NoteOn { note_number: u8, velocity: u8 } = Self::NOTE_ON,
8    NoteOff { note_number: u8, velocity: u8 } = Self::NOTE_OFF,
9    ControlChange { controller_number: u8, velocity: u8 } = Self::CONTROL_CHANGE,
10}
11
12impl MidiMessage {
13    pub const NOTE_ON: u8 = 0x80; // 0x80..0x8F
14    pub const NOTE_OFF: u8 = 0x90; // 0x90..0x9F
15    pub const CONTROL_CHANGE: u8 = 0xB0; // 0xB0..0xBF
16
17    pub const fn from_bytes(bytes: [u8; 3]) -> Self {
18        match bytes[0] & 0xF0 {
19            0x80 => Self::NoteOff {
20                note_number: bytes[1],
21                velocity: 0, // disregard bytes[2] because velocity always = 0 for NoteOff
22            },
23            0x90 => Self::NoteOn {
24                note_number: bytes[1],
25                velocity: bytes[2],
26            },
27            0xB0 => Self::ControlChange {
28                controller_number: bytes[1],
29                velocity: bytes[2],
30            },
31            _ => unimplemented!(),
32        }
33    }
34
35    /// Mutes a MIDI note by setting its velocity to 0
36    pub fn mute(&mut self) {
37        match self {
38            MidiMessage::NoteOn { velocity, .. } => *velocity = 0,
39            MidiMessage::ControlChange { velocity, .. } => *velocity = 0,
40            MidiMessage::NoteOff { .. } => (), // already muted, 0 velocity
41        }
42    }
43
44    /// Transposes a MIDI note by a given interval.
45    ///
46    /// The [`+`](`Add::add`), [`-`](`Sub::sub`), [`+=`](`AddAssign::add_assign`), and [`-=`](`SubAssign::sub_assign`) operators can also be used to transpose a note.
47    ///
48    /// # Example
49    /// ```
50    /// # use shizen_buffers::midi::MidiMessage;
51    /// let mut note_on = MidiMessage::NoteOn { note_number: 60, velocity: 100 };
52    /// note_on.transpose(-5);
53    /// note_on += 10;
54    ///
55    /// assert_eq!(note_on, MidiMessage::NoteOn { note_number: 65, velocity: 100 });
56    /// ```
57    pub fn transpose(&mut self, interval: i8) {
58        if let MidiMessage::NoteOn { note_number, .. } = self {
59            let original_note = *note_number as i8;
60            let new_note = (original_note + interval).clamp(0, 127) as u8;
61            *note_number = new_note;
62        }
63    }
64
65    #[inline]
66    #[must_use]
67    pub const fn is_note_on(&self) -> bool {
68        matches!(self, MidiMessage::NoteOn { .. })
69    }
70
71    #[inline]
72    #[must_use]
73    pub const fn is_note_off(&self) -> bool {
74        matches!(self, MidiMessage::NoteOff { .. })
75    }
76
77    #[inline]
78    #[must_use]
79    pub const fn is_control_change(&self) -> bool {
80        matches!(self, MidiMessage::ControlChange { .. })
81    }
82}
83
84impl From<[u8; 3]> for MidiMessage {
85    fn from(bytes: [u8; 3]) -> Self {
86        Self::from_bytes(bytes)
87    }
88}
89
90impl Add<i8> for MidiMessage {
91    type Output = Self;
92
93    fn add(mut self, rhs: i8) -> Self::Output {
94        self.transpose(rhs);
95        self
96    }
97}
98
99impl Sub<i8> for MidiMessage {
100    type Output = Self;
101
102    fn sub(mut self, rhs: i8) -> Self::Output {
103        self.transpose(-rhs);
104        self
105    }
106}
107
108impl AddAssign<i8> for MidiMessage {
109    fn add_assign(&mut self, rhs: i8) {
110        self.transpose(rhs);
111    }
112}
113
114impl SubAssign<i8> for MidiMessage {
115    fn sub_assign(&mut self, rhs: i8) {
116        self.transpose(-rhs);
117    }
118}