├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── other └── update-gh-pages.sh └── src ├── constants.rs ├── lib.rs ├── manufacturer.rs ├── message.rs ├── raw_message.rs ├── to_raw_messages.rs ├── types.rs └── utils.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "midi" 4 | version = "0.1.0" 5 | authors = ["Sam Doshi "] 6 | license = "MIT" 7 | description = "Common Midi types for Rust" 8 | homepage = "https://github.com/samdoshi/midi-rs" 9 | repository = "https://github.com/samdoshi/midi-rs" 10 | documentation = "http://samdoshi.github.io/midi-rs/midi/index.html" 11 | keywords = ["midi", "music", "audio"] 12 | 13 | [dependencies] 14 | num = "*" 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Sam Doshi (sam@metal-fish.co.uk) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # midi-rs 2 | 3 | [![Build Status](https://travis-ci.org/samdoshi/midi-rs.svg?branch=master)](https://travis-ci.org/samdoshi/midi-rs) 4 | 5 | [Documentation](http://samdoshi.github.io/midi-rs/midi/index.html) 6 | 7 | Common Midi types for Rust. 8 | 9 | ```toml 10 | # Cargo.toml 11 | [dependencies] 12 | midi = "*" 13 | ``` 14 | 15 | Very much a work in progress. 16 | -------------------------------------------------------------------------------- /other/update-gh-pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # force errors to quit the script 4 | set -e 5 | 6 | GIT_ROOT=$(git rev-parse --show-toplevel) 7 | DOC_DIR="$GIT_ROOT/target/doc" 8 | INDEX_PAGE="$DOC_DIR/index.html" 9 | 10 | echo "git root: $GIT_ROOT" 11 | echo "doc dir: $DOC_DIR" 12 | echo "index page: $INDEX_PAGE" 13 | 14 | cargo clean 15 | cargo doc 16 | echo '' > "$INDEX_PAGE" 17 | ghp-import -n "$DOC_DIR" 18 | echo 19 | echo "Now 'git push -qf origin gh-pages'" 20 | 21 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Sam Doshi (sam@metal-fish.co.uk) 2 | // 3 | // Licensed under the MIT License . 4 | // This file may not be copied, modified, or distributed except according to those terms. 5 | 6 | 7 | // source: http://www.midi.org/techspecs/midimessages.php 8 | 9 | pub const NOTE_OFF: u8 = 8; 10 | pub const NOTE_ON: u8 = 9; 11 | pub const POLYPHONIC_PRESSURE: u8 = 10; 12 | pub const CONTROL_CHANGE: u8 = 11; 13 | pub const PROGRAM_CHANGE: u8 = 12; 14 | pub const CHANNEL_PRESSURE: u8 = 13; 15 | pub const PITCH_BEND: u8 = 14; 16 | 17 | pub const SYSEX: u8 = 240; 18 | pub const MTC_QUARTER_FRAME: u8 = 241; 19 | pub const SONG_POSITION_POINTER: u8 = 242; 20 | pub const SONG_SELECT: u8 = 243; 21 | pub const TUNE_REQUEST: u8 = 246; 22 | pub const SYSEX_EOX: u8 = 247; 23 | pub const TIMING_CLOCK: u8 = 248; 24 | pub const START: u8 = 250; 25 | pub const CONTINUE: u8 = 251; 26 | pub const STOP: u8 = 252; 27 | pub const ACTIVE_SENSING: u8 = 254; 28 | pub const SYSTEM_RESET: u8 = 255; 29 | 30 | pub const CC_RPN_MSB: u8 = 101; 31 | pub const CC_RPN_LSB: u8 = 100; 32 | pub const CC_NRPN_MSB: u8 = 99; 33 | pub const CC_NRPN_LSB: u8 = 98; 34 | pub const CC_DATA_ENTRY_MSB: u8 = 6; 35 | pub const CC_DATA_ENTRY_LSB: u8 = 38; 36 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Sam Doshi (sam@metal-fish.co.uk) 2 | // 3 | // Licensed under the MIT License . 4 | // This file may not be copied, modified, or distributed except according to those terms. 5 | 6 | //! Midi types and traits for Rust 7 | 8 | extern crate num; 9 | 10 | pub use types::{Channel, U7, U14}; 11 | pub use Channel::{Ch1, Ch2, Ch3, Ch4, Ch5, Ch6, Ch7, Ch8, 12 | Ch9, Ch10, Ch11, Ch12, Ch13, Ch14, Ch15, Ch16}; 13 | pub use raw_message::RawMessage; 14 | pub use RawMessage::{Status, StatusData, StatusDataData, Raw}; 15 | pub use message::Message; 16 | pub use Message::{Start, TimingClock, Continue, Stop, ActiveSensing, SystemReset, 17 | AllSoundOff, ResetAllControllers, LocalControlOff, LocalControlOn, 18 | AllNotesOff, NoteOff, 19 | ProgramChange, ControlChange, RPN7, RPN14, NRPN7, NRPN14, 20 | SysEx, NoteOn, PitchBend, PolyphonicPressure, ChannelPressure}; 21 | pub use manufacturer::Manufacturer; 22 | pub use to_raw_messages::ToRawMessages; 23 | 24 | pub mod constants; 25 | pub mod utils; 26 | 27 | mod types; 28 | mod raw_message; 29 | mod message; 30 | mod manufacturer; 31 | mod to_raw_messages; 32 | 33 | -------------------------------------------------------------------------------- /src/manufacturer.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Sam Doshi (sam@metal-fish.co.uk) 2 | // 3 | // Licensed under the MIT License . 4 | // This file may not be copied, modified, or distributed except according to those terms. 5 | 6 | use types::U7; 7 | use utils::mask7; 8 | 9 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 10 | pub enum Manufacturer { 11 | OneByte(U7), 12 | ThreeByte(U7, U7, U7) 13 | } 14 | 15 | impl Manufacturer { 16 | pub fn to_u7s(&self) -> Vec { 17 | match self { 18 | &Manufacturer::OneByte(b) => vec!(mask7(b)), 19 | &Manufacturer::ThreeByte(b1, b2, b3) => vec!(mask7(b1), mask7(b2), mask7(b3)) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/message.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Sam Doshi (sam@metal-fish.co.uk) 2 | // 3 | // Licensed under the MIT License . 4 | // This file may not be copied, modified, or distributed except according to those terms. 5 | 6 | use types::{U7, U14, Channel}; 7 | use manufacturer::Manufacturer; 8 | 9 | /// Defines the various Midi messages that can be sent 10 | /// 11 | /// The variants are ordered such that they may be sorted and sent in a sensible order when they 12 | /// occur at the same time, thus `NoteOff` before `NoteOn`, `Start` before `TimingClock`, 13 | /// `ControlChange` and `ProgramChange` before `NoteOn`, etc, etc 14 | #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] 15 | pub enum Message { 16 | // System realtime 17 | // --------------- 18 | 19 | /// Start. Start the current sequence playing. 20 | /// (This message will be followed with Timing Clocks). 21 | Start, 22 | 23 | /// Timing Clock. Sent 24 times per quarter note when synchronization 24 | /// is required. 25 | TimingClock, 26 | 27 | /// Continue. Continue at the point the sequence was Stopped 28 | Continue, 29 | 30 | /// Stop. Stop the current sequence. 31 | Stop, 32 | 33 | /// Active Sensing. This message is intended to be sent repeatedly to tell the receiver that a 34 | /// connection is alive. Use of this message is optional. When initially received, the receiver 35 | /// will expect to receive another Active Sensing message each 300ms (max), and if it does not 36 | /// then it will assume that the connection has been terminated. At termination, the receiver 37 | /// will turn off all voices and return to normal (non- active sensing) operation. 38 | ActiveSensing, 39 | 40 | /// Reset. Reset all receivers in the system to power-up status. This should be used sparingly, 41 | /// preferably under manual control. In particular, it should not be sent on power-up. 42 | SystemReset, 43 | 44 | // Channel mode 45 | // ------------ 46 | 47 | /// All Sound Off. When All Sound Off is received all oscillators will turn off, and their 48 | /// volume envelopes are set to zero as soon as possible. 49 | AllSoundOff(Channel), 50 | 51 | /// Reset All Controllers. When Reset All Controllers is received, all controller values are 52 | /// reset to their default values. (See specific Recommended Practices for defaults). 53 | ResetAllControllers(Channel), 54 | 55 | /// When Local Control is Off, all devices on a given channel will respond only to data 56 | /// received over MIDI. Played data, etc. will be ignored. 57 | LocalControlOff(Channel), 58 | 59 | /// Local Control On restores the functions of the normal controllers. 60 | LocalControlOn(Channel), 61 | 62 | /// All Notes Off. When an All Notes Off is received, all oscillators will turn off. 63 | /// 64 | /// (if you need to use one of the more obsure all notes off modes, send the direct 65 | /// `ControlChange` message 66 | AllNotesOff(Channel), 67 | 68 | // Channel voice 69 | // ------------- 70 | 71 | /// Note Off event. This message is sent when a note is released (ended). 72 | /// The second argument is the key (note) number. 73 | /// The third argument is the velocity. 74 | NoteOff(Channel, U7, U7), 75 | 76 | /// Program Change. This message sent when the patch number changes. 77 | /// The second argument is the new program number. 78 | ProgramChange(Channel, U7), 79 | 80 | /// Control Change. This message is sent when a controller value changes. 81 | /// The second argument is the controller number (0-119, though 0-127 is allowed). 82 | /// The third argument is the controller value (0-127). 83 | ControlChange(Channel, U7, U7), 84 | 85 | /// 7-bit RPN. This message is sent when a 7-bit RPN changes. 86 | /// The second argument is the RPN. 87 | /// The third argument is the value. 88 | RPN7(Channel, U14, U7), 89 | 90 | /// 14-bit RPN. This message is sent when a 14-bit RPN changes. 91 | /// The second argument is the RPN. 92 | /// The third argument is the value. 93 | RPN14(Channel, U14, U14), 94 | 95 | /// 7-bit NRPN. This message is sent when a 7-bit NRPN changes. 96 | /// The second argument is the NRPN. 97 | /// The third argument is the value. 98 | NRPN7(Channel, U14, U7), 99 | 100 | /// 14-bit NRPN. This message is sent when a 14-bit NRPN changes. 101 | /// The second argument is the NRPN. 102 | /// The third argument is the value. 103 | NRPN14(Channel, U14, U14), 104 | 105 | /// System Exclusive. This message type allows manufacturers to create their own messages (such 106 | /// as bulk dumps, patch parameters, and other non-spec data) and provides a mechanism for 107 | /// creating additional MIDI Specification messages. 108 | /// The first argument indicates the manufacturer. 109 | /// The second argument contains the data (without the `F0` header, or `F7` terminator). 110 | SysEx(Manufacturer, Vec), 111 | 112 | /// Note On event. This message is sent when a note is depressed (start). 113 | /// The second argument is the key (note) number. 114 | /// The third is the velocity. 115 | NoteOn(Channel, U7, U7), 116 | 117 | /// Pitch Bend Change. This message is sent to indicate a change in the pitch bender 118 | /// (wheel or lever, typically). The pitch bender is measured by a fourteen bit value. Center 119 | /// (no pitch change) is 2000H. 120 | PitchBend(Channel, U14), 121 | 122 | /// Polyphonic Key Pressure (Aftertouch). This message is most often sent by pressing down 123 | /// on the key after it "bottoms out". 124 | /// The second argument is the key (note) number. 125 | /// The third argument is the pressure value. 126 | PolyphonicPressure(Channel, U7, U7), 127 | 128 | /// Channel Pressure (Aftertouch). This message is most often sent by pressing down on the key 129 | /// after it "bottoms out". This message is different from polyphonic after-touch. Use this 130 | /// message to send the single greatest pressure value (of all the current depressed keys). 131 | /// The second argument is the pressure value. 132 | ChannelPressure(Channel, U7) 133 | } 134 | 135 | -------------------------------------------------------------------------------- /src/raw_message.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Sam Doshi (sam@metal-fish.co.uk) 2 | // 3 | // Licensed under the MIT License . 4 | // This file may not be copied, modified, or distributed except according to those terms. 5 | 6 | use types::U7; 7 | 8 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 9 | pub enum RawMessage { 10 | Status(u8), 11 | StatusData(u8, U7), 12 | StatusDataData(u8, U7, U7), 13 | /// Raw 8-bit data, useful for SysEx 14 | Raw(u8) 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/to_raw_messages.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Sam Doshi (sam@metal-fish.co.uk) 2 | // 3 | // Licensed under the MIT License . 4 | // This file may not be copied, modified, or distributed except according to those terms. 5 | 6 | use constants::*; 7 | use types::{U7, Channel}; 8 | use raw_message::{RawMessage}; 9 | use RawMessage::*; 10 | use message::{Message}; 11 | use Message::*; 12 | use utils::{mask7, status_byte, u14_to_msb_lsb}; 13 | 14 | /// Convert `self` to `Vec` 15 | /// 16 | /// A `Vec` represents ordered Midi data that must be sent as a contigious 17 | /// block, this is useful for representing `Message::SysEx` and `Message::NRPN14`, 18 | /// note that midi clock messages are allowed to interrupt sysex messages as part of the spec. 19 | pub trait ToRawMessages { 20 | fn to_raw_messages(&self) -> Vec; 21 | } 22 | 23 | impl ToRawMessages for RawMessage { 24 | fn to_raw_messages(&self) -> Vec { 25 | vec!(*self) 26 | } 27 | } 28 | 29 | impl ToRawMessages for Message { 30 | fn to_raw_messages(&self) -> Vec { 31 | match self { 32 | // System realtime 33 | &Start => vec!(Status(START)), 34 | &TimingClock => vec!(Status(TIMING_CLOCK)), 35 | &Continue => vec!(Status(CONTINUE)), 36 | &Stop => vec!(Status(STOP)), 37 | &ActiveSensing => vec!(Status(ACTIVE_SENSING)), 38 | &SystemReset => vec!(Status(SYSTEM_RESET)), 39 | 40 | // Channel mode 41 | &AllSoundOff(ch) => ControlChange(ch, 120, 0).to_raw_messages(), 42 | &ResetAllControllers(ch) => ControlChange(ch, 121, 0).to_raw_messages(), 43 | &LocalControlOff(ch) => ControlChange(ch, 122, 0).to_raw_messages(), 44 | &LocalControlOn(ch) => ControlChange(ch, 122, 127).to_raw_messages(), 45 | &AllNotesOff(ch) => ControlChange(ch, 123, 0).to_raw_messages(), 46 | 47 | // Channel voice 48 | &ProgramChange(ch, no) => { 49 | let sb = status_byte(PROGRAM_CHANGE, ch); 50 | vec!(StatusData(sb, mask7(no))) 51 | }, 52 | &ControlChange(ch, no, val) => { 53 | vec!(cc(ch, mask7(no), mask7(val))) 54 | }, 55 | &RPN7(ch, rpn, val) => { 56 | let (rpn_msb, rpn_lsb) = u14_to_msb_lsb(rpn); 57 | vec!( 58 | cc(ch, CC_RPN_MSB, rpn_msb), 59 | cc(ch, CC_RPN_LSB, rpn_lsb), 60 | cc(ch, CC_DATA_ENTRY_MSB, mask7(val)) 61 | ) 62 | }, 63 | &RPN14(ch, rpn, val) => { 64 | let (rpn_msb, rpn_lsb) = u14_to_msb_lsb(rpn); 65 | let (val_msb, val_lsb) = u14_to_msb_lsb(val); 66 | vec!( 67 | cc(ch, CC_RPN_MSB, rpn_msb), 68 | cc(ch, CC_RPN_LSB, rpn_lsb), 69 | cc(ch, CC_DATA_ENTRY_MSB, val_msb), 70 | cc(ch, CC_DATA_ENTRY_LSB, val_lsb) 71 | ) 72 | }, 73 | &NRPN7(ch, nrpn, val) => { 74 | let (nrpn_msb, nrpn_lsb) = u14_to_msb_lsb(nrpn); 75 | vec!( 76 | cc(ch, CC_NRPN_MSB, nrpn_msb), 77 | cc(ch, CC_NRPN_LSB, nrpn_lsb), 78 | cc(ch, CC_DATA_ENTRY_MSB, mask7(val)) 79 | ) 80 | }, 81 | &NRPN14(ch, nrpn, val) => { 82 | let (nrpn_msb, nrpn_lsb) = u14_to_msb_lsb(nrpn); 83 | let (val_msb, val_lsb) = u14_to_msb_lsb(val); 84 | vec!( 85 | cc(ch, CC_NRPN_MSB, nrpn_msb), 86 | cc(ch, CC_NRPN_LSB, nrpn_lsb), 87 | cc(ch, CC_DATA_ENTRY_MSB, val_msb), 88 | cc(ch, CC_DATA_ENTRY_LSB, val_lsb) 89 | ) 90 | }, 91 | &SysEx(manufacturer, ref data) => { 92 | let mut output = Vec::new(); 93 | output.push(SYSEX); 94 | output.extend(manufacturer.to_u7s()); 95 | output.extend(data.iter().map(|d| mask7(*d))); 96 | output.push(SYSEX_EOX); 97 | output.into_iter().map(|d| Raw(d)).collect() 98 | }, 99 | &NoteOff(ch, no, vel) => { 100 | let sb = status_byte(NOTE_OFF, ch); 101 | vec!(StatusDataData(sb, mask7(no), mask7(vel))) 102 | }, 103 | &NoteOn(ch, no, vel) => { 104 | let sb = status_byte(NOTE_ON, ch); 105 | vec!(StatusDataData(sb, mask7(no), mask7(vel))) 106 | }, 107 | &PitchBend(ch, bend) => { 108 | let sb = status_byte(PITCH_BEND, ch); 109 | let (msb, lsb) = u14_to_msb_lsb(bend); 110 | vec!(StatusDataData(sb, lsb, msb)) 111 | } 112 | &PolyphonicPressure(ch, no, vel) => { 113 | let sb = status_byte(POLYPHONIC_PRESSURE, ch); 114 | vec!(StatusDataData(sb, mask7(no), mask7(vel))) 115 | }, 116 | &ChannelPressure(ch, vel) => { 117 | let sb = status_byte(CHANNEL_PRESSURE, ch); 118 | vec!(StatusData(sb, mask7(vel))) 119 | } 120 | } 121 | } 122 | } 123 | 124 | // we need to generate a lot of CC messages... 125 | fn cc(ch: Channel, cc_no: U7, val: U7) -> RawMessage { 126 | let sb = status_byte(CONTROL_CHANGE, ch); 127 | StatusDataData(sb, cc_no, val) 128 | } 129 | 130 | #[cfg(test)] 131 | mod test { 132 | use super::ToRawMessages; 133 | use message::Message::*; 134 | use raw_message::RawMessage::*; 135 | use manufacturer::Manufacturer::*; 136 | use types::Channel::*; 137 | 138 | #[test] 139 | fn test_message_to_raw_messages() { 140 | // Where possible these numbers have been pasted in from 141 | // http://www.midi.org/techspecs/midimessages.php 142 | 143 | // Start 144 | assert_eq!(Start.to_raw_messages(), vec![Status(0b11111010)]); 145 | 146 | // TimingClock 147 | assert_eq!(TimingClock.to_raw_messages(), vec![Status(0b11111000)]); 148 | 149 | // Continue 150 | assert_eq!(Continue.to_raw_messages(), vec![Status(0b11111011)]); 151 | 152 | // Stop 153 | assert_eq!(Stop.to_raw_messages(), vec![Status(0b11111100)]); 154 | 155 | // ActiveSensing 156 | assert_eq!(ActiveSensing.to_raw_messages(), vec![Status(0b11111110)]); 157 | 158 | // SystemReset 159 | assert_eq!(SystemReset.to_raw_messages(), vec![Status(0b11111111)]); 160 | 161 | // AllSoundOff 162 | assert_eq!(AllSoundOff(Ch1).to_raw_messages(), vec![StatusDataData(176, 120, 0)]); 163 | 164 | // ResetAllControllers 165 | assert_eq!(ResetAllControllers(Ch1).to_raw_messages(), vec![StatusDataData(176, 121, 0)]); 166 | 167 | // LocalControlOff 168 | assert_eq!(LocalControlOff(Ch1).to_raw_messages(), vec![StatusDataData(176, 122, 0)]); 169 | 170 | // LocalControlOn 171 | assert_eq!(LocalControlOn(Ch1).to_raw_messages(), vec![StatusDataData(176, 122, 127)]); 172 | 173 | // AllNotesOff 174 | assert_eq!(AllNotesOff(Ch1).to_raw_messages(), vec![StatusDataData(176, 123, 0)]); 175 | 176 | // ProgramChange 177 | assert_eq!(ProgramChange(Ch1, 0).to_raw_messages(), vec![StatusData(192, 0)]); 178 | assert_eq!(ProgramChange(Ch1, 127).to_raw_messages(), vec![StatusData(192, 127)]); 179 | assert_eq!(ProgramChange(Ch1, 128).to_raw_messages(), vec![StatusData(192, 0)]); 180 | 181 | // ControlChange 182 | assert_eq!(ControlChange(Ch1, 0, 0).to_raw_messages(), vec![StatusDataData(176, 0, 0)]); 183 | assert_eq!(ControlChange(Ch1, 0, 127).to_raw_messages(), vec![StatusDataData(176, 0, 127)]); 184 | assert_eq!(ControlChange(Ch1, 0, 128).to_raw_messages(), vec![StatusDataData(176, 0, 0)]); 185 | assert_eq!(ControlChange(Ch1, 127, 0).to_raw_messages(), vec![StatusDataData(176, 127, 0)]); 186 | assert_eq!(ControlChange(Ch1, 128, 0).to_raw_messages(), vec![StatusDataData(176, 0, 0)]); 187 | 188 | // RPN7 189 | assert_eq!(RPN7(Ch1, 1000, 0).to_raw_messages(), vec![StatusDataData(176, 101, 7), 190 | StatusDataData(176, 100, 104), 191 | StatusDataData(176, 6, 0)]); 192 | 193 | // RPN14 194 | assert_eq!(RPN14(Ch1, 1000, 1001).to_raw_messages(), vec![StatusDataData(176, 101, 7), 195 | StatusDataData(176, 100, 104), 196 | StatusDataData(176, 6, 7), 197 | StatusDataData(176, 38, 105)]); 198 | 199 | // NRPN7 200 | assert_eq!(NRPN7(Ch1, 1000, 0).to_raw_messages(), vec![StatusDataData(176, 99, 7), 201 | StatusDataData(176, 98, 104), 202 | StatusDataData(176, 6, 0)]); 203 | 204 | // NRPN14 205 | assert_eq!(NRPN14(Ch1, 1000, 1001).to_raw_messages(), vec![StatusDataData(176, 99, 7), 206 | StatusDataData(176, 98, 104), 207 | StatusDataData(176, 6, 7), 208 | StatusDataData(176, 38, 105)]); 209 | 210 | // SysEx 211 | assert_eq!(SysEx(OneByte(100), vec![1, 2, 3, 4]).to_raw_messages(), 212 | vec![Raw(0b11110000), 213 | Raw(100), 214 | Raw(1), Raw(2), Raw(3), Raw(4), 215 | Raw(0b11110111)]); 216 | 217 | assert_eq!(SysEx(OneByte(128), vec![1, 2, 3, 4, 128]).to_raw_messages(), 218 | vec![Raw(0b11110000), 219 | Raw(0), 220 | Raw(1), Raw(2), Raw(3), Raw(4), Raw(0), 221 | Raw(0b11110111)]); 222 | 223 | assert_eq!(SysEx(ThreeByte(100, 101, 128), vec![1, 2, 3, 4]).to_raw_messages(), 224 | vec![Raw(0b11110000), 225 | Raw(100), Raw(101), Raw(0), 226 | Raw(1), Raw(2), Raw(3), Raw(4), 227 | Raw(0b11110111)]); 228 | 229 | // NoteOff 230 | assert_eq!(NoteOff(Ch1, 0, 0).to_raw_messages(), vec![StatusDataData(128, 0, 0)]); 231 | assert_eq!(NoteOff(Ch2, 127, 127).to_raw_messages(), vec![StatusDataData(129, 127, 127)]); 232 | assert_eq!(NoteOff(Ch3, 128, 128).to_raw_messages(), vec![StatusDataData(130, 0, 0)]); 233 | 234 | // NoteOn 235 | assert_eq!(NoteOn(Ch4, 0, 0).to_raw_messages(), vec![StatusDataData(147, 0, 0)]); 236 | assert_eq!(NoteOn(Ch5, 127, 127).to_raw_messages(), vec![StatusDataData(148, 127, 127)]); 237 | assert_eq!(NoteOn(Ch6, 128, 128).to_raw_messages(), vec![StatusDataData(149, 0, 0)]); 238 | 239 | // PitchBend 240 | assert_eq!(PitchBend(Ch7, 0).to_raw_messages(), vec![StatusDataData(230, 0, 0)]); 241 | assert_eq!(PitchBend(Ch8, 1000).to_raw_messages(), vec![StatusDataData(231, 104, 7)]); 242 | assert_eq!(PitchBend(Ch9, 45000).to_raw_messages(), vec![StatusDataData(232, 72, 95)]); 243 | assert_eq!(PitchBend(Ch10, 12232).to_raw_messages(), vec![StatusDataData(233, 72, 95)]); 244 | 245 | // PolyphonicPressure 246 | assert_eq!(PolyphonicPressure(Ch11, 0, 0).to_raw_messages(), 247 | vec![StatusDataData(170, 0, 0)]); 248 | assert_eq!(PolyphonicPressure(Ch12, 127, 127).to_raw_messages(), 249 | vec![StatusDataData(171, 127, 127)]); 250 | assert_eq!(PolyphonicPressure(Ch13, 128, 128).to_raw_messages(), 251 | vec![StatusDataData(172, 0, 0)]); 252 | 253 | // ChannelPressure 254 | assert_eq!(ChannelPressure(Ch14, 0).to_raw_messages(), vec![StatusData(221, 0)]); 255 | assert_eq!(ChannelPressure(Ch15, 127).to_raw_messages(), vec![StatusData(222, 127)]); 256 | assert_eq!(ChannelPressure(Ch16, 128).to_raw_messages(), vec![StatusData(223, 0)]); 257 | } 258 | } 259 | 260 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Sam Doshi (sam@metal-fish.co.uk) 2 | // 3 | // Licensed under the MIT License . 4 | // This file may not be copied, modified, or distributed except according to those terms. 5 | 6 | use num::FromPrimitive; 7 | 8 | pub type U7 = u8; 9 | pub type U14 = u16; 10 | 11 | /// Represents a Midi channel 12 | /// 13 | /// Note than `Ch1 = 0`, `Ch2 = 1`, etc, as the actual protocol is 0-indexed. 14 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] 15 | pub enum Channel { 16 | Ch1 = 0 , Ch2 = 1 , Ch3 = 2 , Ch4 = 3 , 17 | Ch5 = 4 , Ch6 = 5 , Ch7 = 6 , Ch8 = 7 , 18 | Ch9 = 8 , Ch10 = 9 , Ch11 = 10, Ch12 = 11, 19 | Ch13 = 12, Ch14 = 13, Ch15 = 14, Ch16 = 15 20 | } 21 | 22 | impl FromPrimitive for Channel { 23 | fn from_u64(n: u64) -> Option { 24 | if n < 16 { 25 | FromPrimitive::from_i64(n as i64) 26 | } 27 | else { 28 | None 29 | } 30 | } 31 | 32 | fn from_i64(n: i64) -> Option { 33 | match n { 34 | 0 => Some(Channel::Ch1), 35 | 1 => Some(Channel::Ch2), 36 | 2 => Some(Channel::Ch3), 37 | 3 => Some(Channel::Ch4), 38 | 4 => Some(Channel::Ch5), 39 | 5 => Some(Channel::Ch6), 40 | 6 => Some(Channel::Ch7), 41 | 7 => Some(Channel::Ch8), 42 | 8 => Some(Channel::Ch9), 43 | 9 => Some(Channel::Ch10), 44 | 10 => Some(Channel::Ch11), 45 | 11 => Some(Channel::Ch12), 46 | 12 => Some(Channel::Ch13), 47 | 13 => Some(Channel::Ch14), 48 | 14 => Some(Channel::Ch15), 49 | 15 => Some(Channel::Ch16), 50 | _ => None 51 | } 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Sam Doshi (sam@metal-fish.co.uk) 2 | // 3 | // Licensed under the MIT License . 4 | // This file may not be copied, modified, or distributed except according to those terms. 5 | 6 | use super::types::{Channel, U7, U14}; 7 | 8 | /// 7 bit mask 9 | #[inline(always)] 10 | pub fn mask7(input: u8) -> U7 { 11 | input & 0b01111111 12 | } 13 | 14 | /// 14 bit mask 15 | #[inline(always)] 16 | pub fn mask14(input: u16) -> U14 { 17 | input & 0b0011111111111111 18 | } 19 | 20 | /// Extract the MSB and LSB from a `U14` 21 | #[inline] 22 | pub fn u14_to_msb_lsb(input: U14) -> (U7, U7) { 23 | let msb = mask7((input >> 7) as U7); 24 | let lsb = mask7(input as u8); 25 | (msb, lsb) 26 | } 27 | 28 | /// Convert an MSB and LSB to a `U14` 29 | #[inline] 30 | pub fn msb_lsb_to_u14(msb: U7, lsb: U7) -> U14 { 31 | ((mask7(msb) as U14) << 7) + mask7(lsb) as U14 32 | } 33 | 34 | /// Calculate the status byte for a given channel no. 35 | #[inline(always)] 36 | pub fn status_byte(status: u8, channel: Channel) -> u8 { 37 | (status & 0b00001111) * 16 + (channel as u8) 38 | } 39 | 40 | /// Seperate the status from the channel no. 41 | #[inline] 42 | pub fn from_status_byte(sb: u8) -> (u8, Channel) { 43 | use num::FromPrimitive; 44 | let status = (sb & 0b11110000) >> 4; 45 | let channel = FromPrimitive::from_u8(sb & 0b00001111).unwrap(); 46 | (status, channel) 47 | } 48 | 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | use constants::*; 54 | use types::Channel; 55 | 56 | #[test] 57 | fn test_mask7() { 58 | assert_eq!(127, mask7(255)); 59 | assert_eq!(126, mask7(254)); 60 | assert_eq!(127, mask7(127)); 61 | assert_eq!(126, mask7(126)); 62 | } 63 | 64 | #[test] 65 | fn test_all_mask7() { 66 | for i in 0..255 { // should be 0..256 67 | if i > 127 { 68 | assert_eq!(mask7(i), i - 128); 69 | } 70 | else { 71 | assert_eq!(mask7(i), i); 72 | } 73 | } 74 | } 75 | 76 | #[test] 77 | fn test_mask14() { 78 | assert_eq!(16383, mask14(65535)); 79 | assert_eq!(16382, mask14(65534)); 80 | assert_eq!(16383, mask14(16383)); 81 | assert_eq!(16382, mask14(16382)); 82 | } 83 | 84 | #[test] 85 | fn test_all_mask14() { 86 | for i in 0..65535 { // should be 0..65536 87 | if i > 16383 { 88 | assert!(mask14(i) != i); 89 | } 90 | else { 91 | assert_eq!(mask14(i), i); 92 | } 93 | } 94 | } 95 | 96 | #[test] 97 | fn test_msb_lsb() { 98 | // data from: http://mididesigner.com/help/midi-byte-calculator/ 99 | // 100 | assert_eq!(0, msb_lsb_to_u14(0, 0)); 101 | assert_eq!((0, 0), u14_to_msb_lsb(0)); 102 | 103 | assert_eq!(16383, msb_lsb_to_u14(127, 127)); 104 | assert_eq!((127, 127), u14_to_msb_lsb(16383)); 105 | 106 | assert_eq!(14442, msb_lsb_to_u14(112, 106)); 107 | assert_eq!((112, 106), u14_to_msb_lsb(14442)); 108 | 109 | assert_eq!(24, msb_lsb_to_u14(0, 24)); 110 | assert_eq!((0, 24), u14_to_msb_lsb(24)); 111 | } 112 | 113 | #[test] 114 | fn test_all_msb_lsb() { 115 | for i in 0..65535 { // should be 0..65536 116 | let (msb, lsb) = u14_to_msb_lsb(i); 117 | assert_eq!(msb_lsb_to_u14(msb, lsb), mask14(i)) 118 | } 119 | } 120 | 121 | #[test] 122 | fn test_status_byte() { 123 | // data from: http://www.midi.org/techspecs/midimessages.php 124 | 125 | assert_eq!(128, status_byte(NOTE_OFF, Channel::Ch1)); 126 | assert_eq!((NOTE_OFF, Channel::Ch1), from_status_byte(128)); 127 | 128 | assert_eq!(155, status_byte(NOTE_ON, Channel::Ch12)); 129 | assert_eq!((NOTE_ON, Channel::Ch12), from_status_byte(155)); 130 | } 131 | 132 | #[test] 133 | fn test_all_status_byte() { 134 | use num::FromPrimitive; 135 | for status in 0..16 { 136 | for ch in 0..16 { 137 | let channel = FromPrimitive::from_u8(ch).unwrap(); 138 | let converted = from_status_byte(status_byte(status, channel)); 139 | assert_eq!((status, channel), converted); 140 | } 141 | } 142 | } 143 | } 144 | 145 | --------------------------------------------------------------------------------