1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
// midi_event.rs // // Copyright (c) 2020 All The Music, LLC // // This work is licensed under the Creative Commons Attribution 4.0 International License. // To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send // a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. /// MIDI message status /// /// Each MIDI event (message) has a status, which sets the message type and thus the meaning /// of the associated message data. Technically the status bits also include the channel number, /// but this library currently only supports single track, single channel MIDI files (and thus /// defaults to channel 0). For a detailed description of each status type, see Appendix 1.1 of the document here: /// <https://www.cs.cmu.edu/~music/cmsip/readings/Standard-MIDI-file-format-updated.pdf>. #[derive(Clone, Copy, Debug)] pub enum MIDIStatus { /// Assume status bytes of previous MIDI channel message RunningStatus = 0b0000, /// Note released NoteOff = 0b1000, /// Note pressed NoteOn = 0b1001, /// Pressure on key after pressed down PolyphonicAftertouch = 0b1010, /// Controller value change ControlChange = 0b1011, /// Change program (patch) number ProgramChange = 0b1100, /// Greatest pressure on key after pressed down Aftertouch = 0b1101, /// Chainge pitch wheel PitchWheelChange = 0b1110, } /// MIDI channel voice message /// /// MIDI supports two main types of messages: Channel and System. /// Channel messages are tied to a specific MIDI channel, whereas /// System messages are not (and thus don't contain a channel number). /// This library only supports channel messages, and more specifically /// the `NoteOn` and `NoteOff` channel _voice_ messages, /// which actually produce sounds. For a detailed explanation of /// MIDI messages, see appendix 1.1 of the document here: /// <https://www.cs.cmu.edu/~music/cmsip/readings/Standard-MIDI-file-format-updated.pdf>. #[derive(Clone, Copy, Debug, PartialEq)] pub struct MIDIChannelVoiceMessage { pub delta_time: u8, pub status: u8, pub note: u8, pub velocity: u8, } impl MIDIChannelVoiceMessage { /// Create new `MIDIChannelVoiceMessage` /// /// # Arguments /// /// * `delta_time`: time delta since last MIDI channel message /// * `note`: [MIDINote](../midi_note/struct.MIDINote.html) to play /// * `velocity`: velocity with which to play the note /// * `status`: [MIDIStatus](enum.MIDIStatus.html) bits of the message /// * `channel`: channel on which to play the message /// /// # Examples /// /// ```rust /// // Create Middle C note and two MIDI events, one to "press" the key and /// // one to "release" they key after 5 ticks. /// let note = libatm::MIDINote::new(libatm::MIDINoteType::C, 4); /// let note_on_event = libatm::MIDIChannelVoiceMessage::new(0, ¬e, 0x64, libatm::MIDIStatus::NoteOn, 0); /// let note_off_event = libatm::MIDIChannelVoiceMessage::new(5, ¬e, 0, libatm::MIDIStatus::RunningStatus, 0); /// ``` /// /// # Notes /// /// * The meaning of `delta_time` is determined by the `division` value present /// in the [MIDIHeader](../midi_file/struct.MIDIHeader.html). /// * A `NoteOn` event with a velocity of 0 is equivalent to a `NoteOff` event. This library /// heavily exploits this feature, as well as running status, to produce the smallest /// possible MIDI files. /// * If the note type is [MIDINoteType::Rest](../midi_note/enum.MIDINoteType.html#variant.Rest) /// then the velocity will automatically be set to 0 (equivalent to a `NoteOff` event). pub fn new( delta_time: u8, note: &crate::midi_note::MIDINote, velocity: u8, status: MIDIStatus, channel: u8, ) -> MIDIChannelVoiceMessage { // 0 <= channel < 0x10 (16) assert!(channel < 0x10); // 0 <= velocity < 0x80 (128) assert!(velocity < 0x80); // If note type is Rest, velocity must be 0 let velocity = match note.note_type { crate::midi_note::MIDINoteType::Rest => 0u8, _ => velocity, }; let event_status = match status { MIDIStatus::RunningStatus => 0, _ => (((status as u8) << 4) | channel), }; MIDIChannelVoiceMessage { delta_time, status: event_status, note: (note.convert() as u8), velocity, } } /// Write MIDI channel message to buffer /// /// # Arguments /// /// * `target`: buffer to write to /// /// # Examples /// /// ```rust /// use byteorder::WriteBytesExt; /// /// // Target buffer /// let mut buffer = std::io::BufWriter::new(Vec::new()); /// // Middle C /// let note = libatm::MIDINote::new(libatm::MIDINoteType::C, 4); /// // Play for 5 ticks /// let note_on_event = libatm::MIDIChannelVoiceMessage::new(0, ¬e, 0x64, libatm::MIDIStatus::NoteOn, 0); /// let note_off_event = libatm::MIDIChannelVoiceMessage::new(5, ¬e, 0, libatm::MIDIStatus::RunningStatus, 0); /// // Write notes to buffer /// note_on_event.write_buffer(&mut buffer).unwrap(); /// note_off_event.write_buffer(&mut buffer).unwrap(); /// ``` pub fn write_buffer<T>(&self, target: &mut T) -> std::io::Result<()> where T: byteorder::WriteBytesExt, { target.write_u8(self.delta_time)?; if self.status != 0 { target.write_u8(self.status)?; } target.write_u8(self.note)?; target.write_u8(self.velocity)?; Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn test_midi_channel_message_invalid_channel() { let note = crate::midi_note::MIDINote::new(crate::midi_note::MIDINoteType::C, 4); // Invalid channel 0x11 let _ = MIDIChannelVoiceMessage::new(0, ¬e, 0x64, MIDIStatus::NoteOn, 0x11); } #[test] #[should_panic] fn test_midi_channel_message_invalid_velocity() { let note = crate::midi_note::MIDINote::new(crate::midi_note::MIDINoteType::C, 4); // Invalid velocity 0x81 let _ = MIDIChannelVoiceMessage::new(0, ¬e, 0x81, MIDIStatus::NoteOn, 0); } #[test] fn test_midi_channel_message_rest_note() { let note = crate::midi_note::MIDINote::new(crate::midi_note::MIDINoteType::Rest, 4); let event = MIDIChannelVoiceMessage::new(0, ¬e, 0x64, MIDIStatus::NoteOn, 0); assert_eq!(0, event.velocity); } }