rimd/
midi.rs

1use std::error;
2use std::fmt;
3use std::convert::From;
4use std::io::{Error,Read};
5
6use num::FromPrimitive;
7
8use util::read_byte;
9
10/// An error that can occur trying to parse a midi message
11#[derive(Debug)]
12pub enum MidiError {
13    InvalidStatus(u8),
14    OtherErr(&'static str),
15    Error(Error),
16}
17
18impl From<Error> for MidiError {
19    fn from(err: Error) -> MidiError {
20        MidiError::Error(err)
21    }
22}
23
24impl error::Error for MidiError {
25    fn description(&self) -> &str {
26        match *self {
27            MidiError::InvalidStatus(_) => "Midi data has invalid status byte",
28            MidiError::OtherErr(_) => "A general midi error has occured",
29            MidiError::Error(ref e) => e.description(),
30        }
31    }
32
33    fn cause(&self) -> Option<&error::Error> {
34        match *self {
35            MidiError::Error(ref err) => Some(err as &error::Error),
36            _ => None,
37        }
38    }
39}
40
41impl fmt::Display for MidiError {
42    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43        match *self {
44            MidiError::InvalidStatus(ref s) => write!(f,"Invalid Midi status: {}",s),
45            MidiError::OtherErr(ref s) => write!(f,"Midi Error: {}",s),
46            MidiError::Error(ref e) => write!(f,"{}",e),
47        }
48    }
49}
50
51/// The status field of a midi message indicates what midi command it
52/// represents and what channel it is on
53enum_from_primitive! {
54#[derive(Debug,PartialEq,Clone,Copy)]
55pub enum Status {
56    // voice
57    NoteOff = 0x80,
58    NoteOn = 0x90,
59    PolyphonicAftertouch = 0xA0,
60    ControlChange = 0xB0,
61    ProgramChange = 0xC0,
62    ChannelAftertouch = 0xD0,
63    PitchBend = 0xE0,
64
65    // sysex
66    SysExStart = 0xF0,
67    MIDITimeCodeQtrFrame = 0xF1,
68    SongPositionPointer = 0xF2,
69    SongSelect = 0xF3,
70    TuneRequest = 0xF6, // F4 anf 5 are reserved and unused
71    SysExEnd = 0xF7,
72    TimingClock = 0xF8,
73    Start = 0xFA,
74    Continue = 0xFB,
75    Stop = 0xFC,
76    ActiveSensing = 0xFE, // FD also res/unused
77    SystemReset = 0xFF,
78}
79}
80
81/// Midi message building and parsing.  See
82/// http://www.midi.org/techspecs/midimessages.php for a description
83/// of the various Midi messages that exist.
84#[derive(Debug)]
85pub struct MidiMessage {
86    pub data: Vec<u8>,
87}
88
89impl Clone for MidiMessage {
90    fn clone(&self) -> MidiMessage {
91        MidiMessage {
92            data: self.data.clone()
93        }
94    }
95}
96
97static STATUS_MASK: u8 = 0xF0;
98static CHANNEL_MASK: u8 = 0x0F;
99
100impl MidiMessage {
101    /// Return the status (type) of this message
102    pub fn status(&self) -> Status {
103        Status::from_u8(self.data[0] & STATUS_MASK).unwrap()
104    }
105
106    /// Return the channel this message is on (TODO: return 0 for messages with no channel)
107    pub fn channel(&self) -> u8 {
108        (self.data[0] & CHANNEL_MASK) + 1
109    }
110
111    /// Get te data at index `index` from this message.  Status is at
112    /// index 0
113    pub fn data(&self, index: usize) -> u8 {
114        self.data[index]
115    }
116
117    // Or in the channel bits to a status
118    fn make_status(status: Status, channel: u8) -> u8 {
119        status as u8 | channel
120    }
121
122    /// Create a midi message from a vector of bytes
123    pub fn from_bytes(bytes: Vec<u8>) -> MidiMessage{
124        // TODO: Validate bytes
125        MidiMessage {
126            data: bytes,
127        }
128    }
129
130    // return the number of data bytes for a message with the given status
131    // -1 -> variable sized message, call get_variable_size
132    // -2 -> sysex, read until SysExEnd
133    // -3 -> invalid status
134    fn data_bytes(status: u8) -> isize {
135        match Status::from_u8(status & STATUS_MASK) {
136            Some(stat) => {
137                match stat {
138                    Status::NoteOff |
139                    Status::NoteOn |
140                    Status::PolyphonicAftertouch |
141                    Status::ControlChange |
142                    Status::PitchBend |
143                    Status::SongPositionPointer => { 2 }
144
145                    Status::SysExStart => { -2 }
146
147                    Status::ProgramChange |
148                    Status::ChannelAftertouch |
149                    Status::MIDITimeCodeQtrFrame |
150                    Status::SongSelect => { 1 }
151
152                    Status::TuneRequest |
153                    Status::SysExEnd |
154                    Status::TimingClock |
155                    Status::Start |
156                    Status::Continue |
157                    Status::Stop |
158                    Status::ActiveSensing |
159                    Status::SystemReset => { 0 }
160                }
161            }
162            None => -3
163        }
164    }
165
166    /// Get the next midi message from the reader given that the
167    /// status `stat` has just been read
168    pub fn next_message_given_status(stat: u8, reader: &mut Read) -> Result<MidiMessage, MidiError> {
169        let mut ret:Vec<u8> = Vec::with_capacity(3);
170        ret.push(stat);
171        match MidiMessage::data_bytes(stat) {
172            0 => {}
173            1 => { ret.push(try!(read_byte(reader))); }
174            2 => { ret.push(try!(read_byte(reader)));
175                   ret.push(try!(read_byte(reader))); }
176            -1 => { return Err(MidiError::OtherErr("Don't handle variable sized yet")); }
177            -2 => { return Err(MidiError::OtherErr("Don't handle sysex yet")); }
178            _ =>  { return Err(MidiError::InvalidStatus(stat)); }
179        }
180        Ok(MidiMessage{data: ret})
181    }
182
183    /// Get the next midi message from the reader given that there's a running
184    /// status of `stat` and that in place of a status was read `databyte`
185    pub fn next_message_running_status(stat: u8, databyte: u8, reader: &mut Read) -> Result<MidiMessage, MidiError> {
186        let mut ret:Vec<u8> = Vec::with_capacity(3);
187        ret.push(stat);
188        ret.push(databyte);
189        match MidiMessage::data_bytes(stat) {
190            0 => { panic!("Can't have zero length message with running status"); }
191            1 => { } // already read it
192            2 => { ret.push(try!(read_byte(reader))); } // only need one more byte
193            -1 => { return Err(MidiError::OtherErr("Don't handle variable sized yet")); }
194            -2 => { return Err(MidiError::OtherErr("Don't handle sysex yet")); }
195            _ =>  { return Err(MidiError::InvalidStatus(stat)); }
196        }
197        Ok(MidiMessage{data: ret})
198    }
199
200    /// Extract next midi message from a reader
201    pub fn next_message(reader: &mut Read) -> Result<MidiMessage,MidiError> {
202        let stat = try!(read_byte(reader));
203        MidiMessage::next_message_given_status(stat,reader)
204    }
205
206
207    // Functions to build midi messages
208
209    /// Create a note on message
210    pub fn note_on(note: u8, velocity: u8, channel: u8) -> MidiMessage {
211        MidiMessage {
212            data: vec![MidiMessage::make_status(Status::NoteOn,channel), note, velocity],
213        }
214    }
215
216    /// Create a note off message
217    pub fn note_off(note: u8, velocity: u8, channel: u8) -> MidiMessage {
218        MidiMessage {
219            data: vec![MidiMessage::make_status(Status::NoteOff,channel), note, velocity],
220        }
221    }
222
223    /// Create a polyphonic aftertouch message
224    /// This message is most often sent by pressing down on the key after it "bottoms out".
225    pub fn polyphonic_aftertouch(note: u8, pressure: u8, channel: u8) -> MidiMessage {
226        MidiMessage {
227            data: vec![MidiMessage::make_status(Status::PolyphonicAftertouch,channel), note, pressure],
228        }
229    }
230
231    /// Create a control change message
232    /// This message is sent when a controller value changes. Controllers include devices such as
233    /// pedals and levers. Controller numbers 120-127 are reserved as "Channel Mode Messages".
234    pub fn control_change(controler: u8, data: u8, channel: u8) -> MidiMessage {
235        MidiMessage {
236            data: vec![MidiMessage::make_status(Status::ControlChange,channel), controler, data],
237        }
238    }
239
240    /// Create a program change message
241    /// This message sent when the patch number changes. `program` is the new program number.
242    pub fn program_change(program: u8, channel: u8) -> MidiMessage {
243        MidiMessage {
244            data: vec![MidiMessage::make_status(Status::ProgramChange,channel), program],
245        }
246    }
247
248    /// Create a channel aftertouch
249    /// This message is most often sent by pressing down on the key after it "bottoms out". This message
250    /// is different from polyphonic after-touch. Use this message to send the single greatest pressure
251    /// value (of all the current depressed keys). `pressure` is the pressure value.
252    pub fn channel_aftertouch(pressure: u8, channel: u8) -> MidiMessage {
253        MidiMessage {
254            data: vec![MidiMessage::make_status(Status::ChannelAftertouch,channel), pressure],
255        }
256    }
257
258    /// Create a pitch bench message
259    /// This message is sent to indicate a change in the pitch bender (wheel or lever, typically).
260    /// The pitch bender is measured by a fourteen bit value. Center (no pitch change) is 2000H.
261    /// Sensitivity is a function of the transmitter. `lsb` are the least significant 7 bits.
262    /// `msb` are the most significant 7 bits.
263    pub fn pitch_bend(lsb: u8, msb: u8, channel: u8) -> MidiMessage {
264        MidiMessage {
265            data: vec![MidiMessage::make_status(Status::PitchBend,channel), lsb, msb],
266        }
267    }
268
269}
270
271impl fmt::Display for Status {
272    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
273        write!(f, "{}",
274               match *self {
275                   Status::NoteOff => "Note Off",
276                   Status::NoteOn => "Note On",
277                   Status::PolyphonicAftertouch => "Polyphonic Aftertouch",
278                   Status::ControlChange => "Control Change",
279                   Status::ProgramChange => "Program Change",
280                   Status::ChannelAftertouch => "Channel Aftertouch",
281                   Status::PitchBend => "Pitch Bend",
282                   Status::SysExStart => "SysEx Start",
283                   Status::MIDITimeCodeQtrFrame => "MIDI Time Code Qtr Frame",
284                   Status::SongPositionPointer => "Song Position Pointer",
285                   Status::SongSelect => "Song Select",
286                   Status::TuneRequest => "Tune Request",
287                   Status::SysExEnd => "SysEx End",
288                   Status::TimingClock => "Timing Clock",
289                   Status::Start => "Start",
290                   Status::Continue => "Continue",
291                   Status::Stop => "Stop",
292                   Status::ActiveSensing => "Active Sensing",
293                   Status::SystemReset => "System Reset",
294               })
295    }
296}
297
298impl fmt::Display for MidiMessage {
299    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
300        if self.data.len() == 2 {
301            write!(f, "{}: [{}]\tchannel: {}", self.status(),self.data[1],self.channel())
302        }
303        else if self.data.len() == 3 {
304            write!(f, "{}: [{},{}]\tchannel: {}", self.status(),self.data[1],self.data[2],self.channel())
305        }
306        else {
307            write!(f, "{}: [no data]\tchannel: {}", self.status(),self.channel())
308        }
309    }
310}