1use std::{
4 fmt::{Display, Formatter},
5 time::SystemTime,
6};
7
8const NOTES: [&str; 12] = ["C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"];
9
10#[repr(C)]
14#[derive(Debug, Clone, Copy)]
15pub struct MidiMessage {
16 channel: u8,
17 status: MidiMessageStatus,
18 data_1: u8,
19 data_2: u8,
20}
21
22#[repr(C)]
26#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
27pub enum MidiMessageStatus {
28 Unknown = 0,
30 NoteOff = 1,
32 NoteOn = 2,
34 PolyphonicAftertouch = 3,
36 ControlChange = 4,
38 ProgramChange = 5,
40 ChannelAftertouch = 6,
42 PitchWheel = 7,
44}
45
46#[repr(C)]
53#[derive(Debug, Clone, Copy)]
54pub struct MidiEvent {
55 pub message: MidiMessage,
57 pub sample_offset: u32,
59}
60
61impl MidiEvent {
62 pub fn new(message: MidiMessage, sample_offset: u32) -> Self {
64 Self { message, sample_offset }
65 }
66}
67
68impl From<u8> for MidiMessageStatus {
69 fn from(byte: u8) -> Self {
70 match byte {
71 0x80..0x90 => MidiMessageStatus::NoteOff,
73 0x90..0xA0 => MidiMessageStatus::NoteOn,
75 0xA0..0xB0 => MidiMessageStatus::PolyphonicAftertouch,
77 0xB0..0xC0 => MidiMessageStatus::ControlChange,
79 0xC0..0xD0 => MidiMessageStatus::ProgramChange,
81 0xD0..0xE0 => MidiMessageStatus::ChannelAftertouch,
83 0xE0..=0xFF => MidiMessageStatus::PitchWheel,
85 _ => MidiMessageStatus::Unknown,
86 }
87 }
88}
89
90impl MidiMessage {
91 pub fn new(bytes: [u8; 3]) -> Self {
93 MidiMessage {
94 channel: (bytes[0] & 0x0F) + 1,
95 status: MidiMessageStatus::from(bytes[0]),
96 data_1: bytes[1],
97 data_2: bytes[2],
98 }
99 }
100}
101
102impl MidiMessage {
103 pub fn get_status(&self) -> MidiMessageStatus {
105 self.status
106 }
107
108 pub fn get_channel(&self) -> u8 {
110 self.channel
111 }
112
113 fn get_data(&self, data_field: usize, statuses: &[MidiMessageStatus]) -> Option<u8> {
114 if statuses.contains(&self.status) {
115 match data_field {
116 1 => Some(self.data_1),
117 2 => Some(self.data_2),
118 _ => None,
119 }
120 } else {
121 None
122 }
123 }
124
125 pub fn get_note(&self) -> Option<String> {
127 let note_number = self.get_note_number()?;
128 let note_index = (note_number % 12) as usize;
130 let note_name = NOTES[note_index];
131
132 let octave = (note_number / 12) as i8 - 1;
134
135 Some(format!("{note_name}{octave}"))
137 }
138
139 pub fn get_note_frequency(&self) -> Option<f32> {
141 let note_number = self.get_note_number()?;
142 Some(440.0 * 2.0f32.powf((note_number as f32 - 69.0) / 12.0))
143 }
144
145 pub fn get_note_number(&self) -> Option<u8> {
147 self.get_data(1, &[MidiMessageStatus::NoteOn, MidiMessageStatus::NoteOff])
148 }
149
150 pub fn get_velocity(&self) -> Option<u8> {
152 self.get_data(2, &[MidiMessageStatus::NoteOn, MidiMessageStatus::NoteOff])
153 }
154
155 pub fn get_pressure(&self) -> Option<u8> {
157 self.get_data(2, &[MidiMessageStatus::PolyphonicAftertouch])
158 }
159
160 pub fn get_control_change_data(&self) -> Option<u8> {
162 self.get_data(2, &[MidiMessageStatus::ControlChange])
163 }
164
165 pub fn get_pitch_wheel_data(&self) -> Option<(u8, u8)> {
167 let least_significant_byte = self.get_data(1, &[MidiMessageStatus::PitchWheel])?;
168 let most_significant_byte = self.get_data(2, &[MidiMessageStatus::PitchWheel])?;
169 Some((least_significant_byte, most_significant_byte))
170 }
171}
172
173impl Display for MidiMessage {
174 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
175 let now = SystemTime::now()
176 .duration_since(SystemTime::UNIX_EPOCH)
177 .unwrap()
178 .as_secs();
179 match self.status {
180 MidiMessageStatus::NoteOff | MidiMessageStatus::NoteOn => {
181 write!(
182 f,
183 "[{}] Ch {} {:?}\t Note = {} ({}Hz)\t Velocity = {}",
184 now,
185 self.channel,
186 self.status,
187 self.get_note().unwrap(),
188 self.get_note_frequency().unwrap(),
189 self.get_velocity().unwrap()
190 )
191 }
192 _ => {
193 write!(
194 f,
195 "[{}] Ch {} {:?}\t Data 1 = {}\t Data 2 = {}",
196 now, self.channel, self.status, self.data_1, self.data_2
197 )
198 }
199 }
200 }
201}
202
203impl From<&[u8]> for MidiMessage {
204 fn from(bytes: &[u8]) -> Self {
205 match bytes.len() {
206 1 => MidiMessage {
207 channel: (bytes[0] & 0x0F) + 1,
208 status: MidiMessageStatus::from(bytes[0]),
209 data_1: 0,
210 data_2: 0,
211 },
212 2 => MidiMessage {
213 channel: (bytes[0] & 0x0F) + 1,
214 status: MidiMessageStatus::from(bytes[0]),
215 data_1: bytes[1],
216 data_2: 0,
217 },
218 3 => MidiMessage {
219 channel: (bytes[0] & 0x0F) + 1,
220 status: MidiMessageStatus::from(bytes[0]),
221 data_1: bytes[1],
222 data_2: bytes[2],
223 },
224 _ => MidiMessage {
225 channel: (bytes[0] & 0x0F) + 1,
226 status: MidiMessageStatus::Unknown,
227 data_1: 0,
228 data_2: 0,
229 },
230 }
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_note_on_parsing() {
240 let msg = MidiMessage::new([0x90, 60, 100]);
242 assert_eq!(msg.get_status(), MidiMessageStatus::NoteOn);
243 assert_eq!(msg.get_channel(), 1);
244 assert_eq!(msg.get_note_number(), Some(60));
245 assert_eq!(msg.get_velocity(), Some(100));
246 assert_eq!(msg.get_note(), Some("C4".to_string()));
247 }
248
249 #[test]
250 fn test_note_off_parsing() {
251 let msg = MidiMessage::new([0x82, 64, 64]);
253 assert_eq!(msg.get_status(), MidiMessageStatus::NoteOff);
254 assert_eq!(msg.get_channel(), 3);
255 assert_eq!(msg.get_note_number(), Some(64));
256 assert_eq!(msg.get_velocity(), Some(64));
257 assert_eq!(msg.get_note(), Some("E4".to_string()));
258 }
259
260 #[test]
261 fn test_control_change_parsing() {
262 let msg = MidiMessage::new([0xB0, 7, 127]);
264 assert_eq!(msg.get_status(), MidiMessageStatus::ControlChange);
265 assert_eq!(msg.get_channel(), 1);
266 assert_eq!(msg.get_control_change_data(), Some(127));
267 assert_eq!(msg.get_note_number(), None);
268 }
269
270 #[test]
271 fn test_channel_extraction() {
272 for ch in 0..16u8 {
274 let msg = MidiMessage::new([0x90 | ch, 60, 100]);
275 assert_eq!(msg.get_channel(), ch + 1);
276 }
277 }
278
279 #[test]
280 fn test_note_frequency() {
281 let msg = MidiMessage::new([0x90, 69, 100]);
283 let freq = msg.get_note_frequency().unwrap();
284 assert!((freq - 440.0).abs() < 0.01);
285 }
286
287 #[test]
288 fn test_from_slice() {
289 let bytes: &[u8] = &[0x90, 60, 100];
291 let msg = MidiMessage::from(bytes);
292 assert_eq!(msg.get_status(), MidiMessageStatus::NoteOn);
293 assert_eq!(msg.get_note_number(), Some(60));
294 }
295
296 #[test]
297 fn test_pitch_wheel() {
298 let msg = MidiMessage::new([0xE0, 0x00, 0x40]);
300 assert_eq!(msg.get_status(), MidiMessageStatus::PitchWheel);
301 assert_eq!(msg.get_pitch_wheel_data(), Some((0x00, 0x40)));
302 }
303}