├── .gitignore ├── .travis.yml ├── Cargo.toml ├── src ├── bin │ ├── copy.rs │ └── test.rs ├── util.rs ├── reader.rs ├── writer.rs ├── lib.rs ├── midi.rs ├── builder.rs └── meta.rs ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /src/TAGS 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - stable -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rimd" 3 | description = "Library for handling Midi and reading and writing Standard Midi Files in Rust" 4 | version = "0.0.3" 5 | authors = ["Nick Lanham "] 6 | homepage = "https://github.com/RustAudio/rimd" 7 | repository = "https://github.com/RustAudio/rimd" 8 | documentation = "https://nicklan.github.io/rimd/target/doc/rimd/index.html" 9 | readme = "README.md" 10 | keywords = ["midi", "smf", "music", "audio", "mid"] 11 | license = "MIT" 12 | 13 | [dependencies] 14 | byteorder = "1.3.2" 15 | encoding = "0.2.*" 16 | num-traits = "0.2.14" 17 | num-derive = "0.3.3" 18 | -------------------------------------------------------------------------------- /src/bin/copy.rs: -------------------------------------------------------------------------------- 1 | extern crate rimd; 2 | 3 | use rimd::{SMF,SMFError,SMFWriter}; 4 | use std::env::{args,Args}; 5 | use std::path::Path; 6 | 7 | fn main() { 8 | let mut args: Args = args(); 9 | args.next(); 10 | let pathstr = match args.next() { 11 | Some(s) => s, 12 | None => { panic!("Need a source path"); } 13 | }; 14 | let deststr = match args.next() { 15 | Some(s) => s, 16 | None => { panic!("Need a destination path") }, 17 | }; 18 | match SMF::from_file(&Path::new(&pathstr[..])) { 19 | Ok(smf) => { 20 | let writer = SMFWriter::from_smf(smf); 21 | writer.write_to_file(&Path::new(&deststr[..])).unwrap(); 22 | } 23 | Err(e) => { 24 | match e { 25 | SMFError::InvalidSMFFile(s) => {println!("{}",s);} 26 | SMFError::Error(e) => {println!("io: {}",e);} 27 | SMFError::MidiError(_) => {println!("Midi Error");} 28 | SMFError::MetaError(_) => {println!("Meta Error");} 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nick Lanham 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rimd [![Build Status](https://travis-ci.org/nicklan/rimd.svg?branch=master)](https://travis-ci.org/nicklan/rimd) 2 | 3 | rimd is a set of utilities to deal with midi messages and standard 4 | midi files (SMF). It handles both standard midi messages and the meta 5 | messages that are found in SMFs. 6 | 7 | rimd is fairly low level, and messages are stored and accessed in 8 | their underlying format (i.e. a vector of `u8`s). There are some 9 | utility methods for accessing the various pieces of a message, and 10 | for constructing new messages. 11 | 12 | For a description of the underlying format of midi messages see [here](http://www.midi.org/techspecs/midimessages.php) 13 | For a description of the underlying format of meta messages see [here](https://web.archive.org/web/20150217154504/http://cs.fit.edu/~ryan/cse4051/projects/midi/midi.html#meta_event) 14 | 15 | ## Docs 16 | 17 | Most public functions have docs in the source. To build the docs do 18 | 19 | cargo doc 20 | 21 | and then point your browser at /path/to/rimd/target/doc/rimd/index.html 22 | 23 | ## Installation 24 | 25 | Use [Cargo](http://doc.crates.io/) and add the following to your Cargo.toml 26 | 27 | ``` 28 | [dependencies.rimd] 29 | git = "https://github.com/RustAudio/rimd.git" 30 | ``` 31 | 32 | ## Building 33 | 34 | To build simply do 35 | 36 | cargo build 37 | 38 | ## License 39 | 40 | MIT (see LICENSE file) 41 | -------------------------------------------------------------------------------- /src/bin/test.rs: -------------------------------------------------------------------------------- 1 | extern crate rimd; 2 | 3 | use rimd::{SMF,SMFError}; 4 | use std::env::{args,Args}; 5 | use std::path::Path; 6 | 7 | fn main() { 8 | let mut args: Args = args(); 9 | args.next(); 10 | let pathstr = match args.next() { 11 | Some(s) => s, 12 | None => { panic!("Please pass a path to an SMF to test") }, 13 | }; 14 | println!("Reading: {}",pathstr); 15 | match SMF::from_file(&Path::new(&pathstr[..])) { 16 | Ok(smf) => { 17 | println!("format: {}",smf.format); 18 | println!("tracks: {}",smf.tracks.len()); 19 | println!("division: {}",smf.division); 20 | let mut tnum = 1; 21 | for track in smf.tracks.iter() { 22 | let mut time: u64 = 0; 23 | println!("\n{}: {}\nevents:",tnum,track); 24 | tnum+=1; 25 | for event in track.events.iter() { 26 | println!(" {}",event.fmt_with_time_offset(time)); 27 | time += event.vtime; 28 | } 29 | } 30 | } 31 | Err(e) => { 32 | match e { 33 | SMFError::InvalidSMFFile(s) => {println!("{}",s);} 34 | SMFError::Error(e) => {println!("io: {}",e);} 35 | SMFError::MidiError(e) => {println!("Midi Error: {}",e);} 36 | SMFError::MetaError(_) => {println!("Meta Error");} 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | //! Some useful utility functions 2 | 3 | use std::iter; 4 | use std::io::{Read,Error,ErrorKind}; 5 | 6 | static NSTRS: &'static str = "C C#D D#E F F#G G#A A#B "; 7 | 8 | /// convert a midi note number to a name 9 | pub fn note_num_to_name(num: u32) -> String { 10 | let oct = (num as f32 /12 as f32).floor()-1.0; 11 | let nmt = ((num%12)*2) as usize; 12 | let slice = 13 | if NSTRS.as_bytes()[nmt+1] == ' ' as u8{ 14 | &NSTRS[nmt..(nmt+1)] 15 | } else { 16 | &NSTRS[nmt..(nmt+2)] 17 | }; 18 | format!("{}{}",slice,oct) 19 | } 20 | 21 | /// Read a single byte from a Reader 22 | pub fn read_byte(reader: &mut dyn Read) -> Result { 23 | let mut b = [0; 1]; 24 | reader.read(&mut b)?; 25 | Ok(b[0]) 26 | } 27 | 28 | /// Read from reader until buffer is full, or an error occurs 29 | pub fn fill_buf(reader: &mut dyn Read, buf: &mut [u8]) -> Result<(),Error> { 30 | let mut read = 0; 31 | while read < buf.len() { 32 | let bytes_read = reader.read(&mut buf[read..])?; 33 | if bytes_read == 0 { 34 | return Err(Error::new(ErrorKind::InvalidData, "file ends before it should")); 35 | } 36 | read += bytes_read; 37 | } 38 | Ok(()) 39 | } 40 | 41 | /// Read amt from reader and put result in dest. Errors in underlying 42 | /// reader will cause this function to return an error 43 | pub fn read_amount(reader: &mut dyn Read, dest: &mut Vec, amt: usize) -> Result<(),Error> { 44 | let start_len = dest.len(); 45 | let mut len = start_len; 46 | if dest.capacity() < start_len + amt { 47 | dest.extend(iter::repeat(0).take(amt)); 48 | } 49 | let mut ret = Ok(()); 50 | while (len-start_len) < amt { 51 | match reader.read(&mut dest[len..]) { 52 | Ok(0) => { 53 | // read 0 before amount 54 | ret = Err(Error::new(ErrorKind::InvalidData, 55 | "Stream ended before specified number of bytes could be read")); 56 | }, 57 | Ok(n) => len += n, 58 | Err(ref e) if e.kind() == ErrorKind::Interrupted => {} 59 | Err(e) => { 60 | ret = Err(e); 61 | break; 62 | } 63 | } 64 | } 65 | dest.truncate(len); 66 | ret 67 | } 68 | 69 | pub fn latin1_decode(s: &[u8]) -> String { 70 | use encoding::{Encoding, DecoderTrap}; 71 | use encoding::all::ISO_8859_1; 72 | use std::str; 73 | match ISO_8859_1.decode(s, DecoderTrap::Replace) { 74 | Ok(s) => s, 75 | Err(_) => match str::from_utf8(s) { 76 | Ok(s) => s.to_string(), 77 | Err(_) => format!("[invalid string data]"), 78 | } 79 | } 80 | } 81 | 82 | #[test] 83 | fn test_note_num_to_name() { 84 | assert_eq!(¬e_num_to_name(48)[..],"C3"); 85 | assert_eq!(¬e_num_to_name(49)[..],"C#3"); 86 | assert_eq!(¬e_num_to_name(65)[..],"F4"); 87 | assert_eq!(¬e_num_to_name(104)[..],"G#7"); 88 | } 89 | -------------------------------------------------------------------------------- /src/reader.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use SMF; 4 | use ::{Event,SMFError,SMFFormat,MetaCommand,MetaEvent,MidiMessage,Track,TrackEvent}; 5 | 6 | use util::{fill_buf, read_byte, latin1_decode}; 7 | 8 | /// An SMFReader can parse a byte stream into an SMF 9 | #[derive(Clone,Copy)] 10 | pub struct SMFReader; 11 | 12 | impl SMFReader { 13 | fn parse_header(reader: &mut dyn Read) -> Result { 14 | let mut header:[u8;14] = [0;14]; 15 | fill_buf(reader,&mut header)?; 16 | 17 | // skip RIFF header if present 18 | if header[0] == 0x52 && 19 | header[1] == 0x49 && 20 | header[2] == 0x46 && 21 | header[3] == 0x46 { 22 | let mut skip:[u8; 6] = [0; 6]; 23 | fill_buf(reader, &mut skip)?; 24 | fill_buf(reader, &mut header)?; 25 | } 26 | 27 | if header[0] != 0x4D || 28 | header[1] != 0x54 || 29 | header[2] != 0x68 || 30 | header[3] != 0x64 { 31 | return Err(SMFError::InvalidSMFFile("Invalid header magic")); 32 | } 33 | let format = match header[9] { 34 | 0 => SMFFormat::Single, 35 | 1 => SMFFormat::MultiTrack, 36 | 2 => SMFFormat::MultiSong, 37 | _ => return Err(SMFError::InvalidSMFFile("Invalid format bytes")), 38 | }; 39 | 40 | let tracks = (header[10] as u16) << 8 | header[11] as u16; 41 | let division = (header[12] as i16) << 8 | header[13] as i16; 42 | 43 | Ok(SMF { format: format, 44 | tracks: Vec::with_capacity(tracks as usize), 45 | division: division } ) 46 | } 47 | 48 | fn next_event(reader: &mut dyn Read, laststat: u8, was_running: &mut bool) -> Result { 49 | let time = SMFReader::read_vtime(reader)?; 50 | let stat = read_byte(reader)?; 51 | 52 | if (stat & 0x80) == 0 { 53 | *was_running = true; 54 | } else { 55 | *was_running = false; 56 | } 57 | 58 | match stat { 59 | 0xFF => { 60 | let event = MetaEvent::next_event(reader)?; 61 | Ok( TrackEvent { 62 | vtime: time, 63 | event: Event::Meta(event), 64 | }) 65 | } 66 | _ => { 67 | let msg = 68 | if (stat & 0x80) == 0 { 69 | // this is a running status, so assume we have the same status as last time 70 | MidiMessage::next_message_running_status(laststat,stat,reader)? 71 | } else { 72 | MidiMessage::next_message_given_status(stat,reader)? 73 | }; 74 | Ok( TrackEvent { 75 | vtime: time, 76 | event: Event::Midi(msg), 77 | }) 78 | } 79 | } 80 | } 81 | 82 | fn parse_track(reader: &mut dyn Read) -> Result { 83 | let mut res:Vec = Vec::new(); 84 | let mut buf:[u8;4] = [0;4]; 85 | 86 | let mut copyright = None; 87 | let mut name = None; 88 | 89 | fill_buf(reader,&mut buf)?; 90 | if buf[0] != 0x4D || // "MTrk" 91 | buf[1] != 0x54 || 92 | buf[2] != 0x72 || 93 | buf[3] != 0x6B { 94 | return Err(SMFError::InvalidSMFFile("Invalid track magic")); 95 | } 96 | fill_buf(reader,&mut buf)?; 97 | let len = 98 | ((buf[0] as u32) << 24 | 99 | (buf[1] as u32) << 16 | 100 | (buf[2] as u32) << 8 | 101 | (buf[3] as u32)) as usize; 102 | 103 | let mut read_so_far = 0; 104 | 105 | loop { 106 | let last = { // use status from last midi event, skip meta events 107 | let mut last = 0u8; 108 | for e in res.iter().rev() { 109 | match e.event { 110 | Event::Midi(ref m) => { last = m.data[0]; break; } 111 | _ => () 112 | } 113 | } 114 | last 115 | }; 116 | let mut was_running = false; 117 | match SMFReader::next_event(reader,last,&mut was_running) { 118 | Ok(event) => { 119 | match event.event { 120 | Event::Meta(ref me) => { 121 | match me.command { 122 | MetaCommand::CopyrightNotice => copyright = Some(latin1_decode(&me.data)), 123 | MetaCommand::SequenceOrTrackName => name = Some(latin1_decode(&me.data)), 124 | _ => {} 125 | } 126 | }, 127 | _ => {} 128 | } 129 | read_so_far += event.len(); 130 | if was_running { 131 | // used a running status, so didn't actually read a status byte 132 | read_so_far -= 1; 133 | } 134 | res.push(event); 135 | if read_so_far == len { 136 | break; 137 | } 138 | if read_so_far > len { 139 | return Err(SMFError::InvalidSMFFile("Invalid MIDI file")); 140 | } 141 | }, 142 | Err(err) => { 143 | /* // uncomment for debugging to print the last parsed events 144 | for e in &res[res.len()-10..] { 145 | match e.event { 146 | Event::Midi(MidiMessage {ref data}) | Event::Meta(MetaEvent {ref data, ..}) => { 147 | for b in data { 148 | print!("{:02X}", b); 149 | } 150 | } 151 | } 152 | println!(": {:?} {}", e, e); 153 | }*/ 154 | return Err(err); 155 | } 156 | } 157 | } 158 | Ok(Track { 159 | copyright: copyright, 160 | name: name, 161 | events: res 162 | }) 163 | } 164 | 165 | /// Read a variable sized value from the reader. 166 | /// This is usually used for the times of midi events but is used elsewhere as well. 167 | pub fn read_vtime(reader: &mut dyn Read) -> Result { 168 | let mut res: u64 = 0; 169 | let mut i = 0; 170 | let cont_mask = 0x80; 171 | let val_mask = 0x7F; 172 | loop { 173 | i+=1; 174 | if i > 9 { 175 | return Err(SMFError::InvalidSMFFile("Variable length value too long")); 176 | } 177 | let next = read_byte(reader)?; 178 | res |= next as u64 & val_mask; 179 | if (next & cont_mask) == 0 { 180 | break; 181 | } 182 | res = res << 7; 183 | } 184 | Ok(res) 185 | } 186 | 187 | /// Read an entire SMF file 188 | pub fn read_smf(reader: &mut dyn Read) -> Result { 189 | let mut smf = SMFReader::parse_header(reader); 190 | match smf { 191 | Ok(ref mut s) => { 192 | for _ in 0..s.tracks.capacity() { 193 | s.tracks.push(SMFReader::parse_track(reader)?); 194 | } 195 | } 196 | _ => {} 197 | } 198 | smf 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/writer.rs: -------------------------------------------------------------------------------- 1 | use std::fs::OpenOptions; 2 | use std::io::{Error,Write}; 3 | use std::path::Path; 4 | 5 | use byteorder::{BigEndian, WriteBytesExt}; 6 | 7 | use SMF; 8 | use ::{Event,AbsoluteEvent,MetaEvent,MetaCommand,SMFFormat}; 9 | 10 | /// An SMFWriter is used to write an SMF to a file. It can be either 11 | /// constructed empty and have tracks added, or created from an 12 | /// existing rimd::SMF. 13 | /// 14 | /// # Writing an existing SMF to a file 15 | /// ``` 16 | /// use rimd::{SMF,SMFWriter,SMFBuilder}; 17 | /// use std::path::Path; 18 | /// // Create smf 19 | /// let mut builder = SMFBuilder::new(); 20 | /// // add some events to builder 21 | /// let smf = builder.result(); 22 | /// let writer = SMFWriter::from_smf(smf); 23 | /// let result = writer.write_to_file(Path::new("/path/to/file.smf")); 24 | /// // handle result 25 | pub struct SMFWriter { 26 | format: u16, 27 | ticks: i16, 28 | tracks: Vec>, 29 | } 30 | 31 | impl SMFWriter { 32 | 33 | /// Create a new SMFWriter with the given number of units per 34 | /// beat. The SMFWriter will initially have no tracks. 35 | pub fn new_with_division(ticks: i16) -> SMFWriter { 36 | SMFWriter { 37 | format: 1, 38 | ticks: ticks, 39 | tracks: Vec::new(), 40 | } 41 | } 42 | 43 | /// Create a new SMFWriter with the given format and number of 44 | /// units per beat. The SMFWriter will initially have no tracks. 45 | pub fn new_with_division_and_format(format: SMFFormat, ticks: i16) -> SMFWriter { 46 | SMFWriter { 47 | format: format as u16, 48 | ticks: ticks, 49 | tracks: Vec::new(), 50 | } 51 | } 52 | 53 | /// Create a writer that has all the tracks from the given SMF already added 54 | pub fn from_smf(smf: SMF) -> SMFWriter { 55 | let mut writer = SMFWriter::new_with_division_and_format 56 | (smf.format, smf.division); 57 | 58 | for track in smf.tracks.iter() { 59 | let mut length = 0; 60 | let mut saw_eot = false; 61 | let mut vec = Vec::new(); 62 | writer.start_track_header(&mut vec); 63 | 64 | for event in track.events.iter() { 65 | length += SMFWriter::write_vtime(event.vtime as u64, &mut vec).unwrap(); // TODO: Handle error 66 | writer.write_event(&mut vec, &(event.event), &mut length, &mut saw_eot); 67 | } 68 | 69 | writer.finish_track_write(&mut vec, &mut length, saw_eot); 70 | writer.tracks.push(vec); 71 | } 72 | 73 | writer 74 | } 75 | 76 | pub fn vtime_to_vec(val: u64) -> Vec { 77 | let mut storage = Vec::new(); 78 | let mut cur = val; 79 | let mut continuation = false; 80 | let cont_mask = 0x80 as u8; 81 | let val_mask = 0x7F as u64; 82 | loop { 83 | let mut to_write = (cur & val_mask) as u8; 84 | cur = cur >> 7; 85 | if continuation { 86 | // we're writing a continuation byte, so set the bit 87 | to_write |= cont_mask; 88 | } 89 | storage.push(to_write); 90 | continuation = true; 91 | if cur == 0 { break; } 92 | } 93 | storage.reverse(); 94 | storage 95 | } 96 | 97 | // Write a variable length value. Return number of bytes written. 98 | pub fn write_vtime(val: u64, writer: &mut dyn Write) -> Result { 99 | let storage = SMFWriter::vtime_to_vec(val); 100 | writer.write_all(&storage[..])?; 101 | Ok(storage.len() as u32) 102 | } 103 | 104 | fn start_track_header(&self, vec: &mut Vec) { 105 | vec.push(0x4D); 106 | vec.push(0x54); 107 | vec.push(0x72); 108 | vec.push(0x6B); 109 | // reserve space for track len 110 | vec.push(0); 111 | vec.push(0); 112 | vec.push(0); 113 | vec.push(0); 114 | } 115 | 116 | fn write_event(&self, vec: &mut Vec, event: &Event, length: &mut u32, saw_eot: &mut bool) { 117 | match event { 118 | &Event::Midi(ref midi) => { 119 | vec.extend(midi.data.iter()); 120 | *length += midi.data.len() as u32; 121 | } 122 | &Event::Meta(ref meta) => { 123 | vec.push(0xff); // indicate we're writing a meta event 124 | vec.push(meta.command as u8); 125 | // +2 on next line for the 0xff and the command byte we just wrote 126 | *length += SMFWriter::write_vtime(meta.length,vec).unwrap() + 2; 127 | vec.extend(meta.data.iter()); 128 | *length += meta.data.len() as u32; 129 | if meta.command == MetaCommand::EndOfTrack { 130 | *saw_eot = true; 131 | } 132 | } 133 | } 134 | } 135 | 136 | fn finish_track_write(&self, vec: &mut Vec, length: &mut u32, saw_eot: bool) { 137 | if !saw_eot { 138 | // no end of track marker in passed data, add one 139 | *length += SMFWriter::write_vtime(0,vec).unwrap(); 140 | vec.push(0xff); // indicate we're writing a meta event 141 | vec.push(MetaCommand::EndOfTrack as u8); 142 | *length += SMFWriter::write_vtime(0,vec).unwrap() + 2; // write length of meta command: 0 143 | } 144 | 145 | // write in the length in the space we reserved 146 | for i in 0..4 { 147 | let lbyte = (*length & 0xFF) as u8; 148 | // 7-i because smf is big endian and we want to put this in bytes 4-7 149 | vec[7-i] = lbyte; 150 | *length = (*length)>>8; 151 | } 152 | } 153 | 154 | /// Add any sequence of AbsoluteEvents as a track to this writer 155 | pub fn add_track<'a,I>(&mut self, track: I) where I: Iterator { 156 | self.add_track_with_name(track,None) 157 | } 158 | 159 | /// Add any sequence of AbsoluteEvents as a track to this writer. A meta event with the given name will 160 | /// be added at the start of the track 161 | pub fn add_track_with_name<'a,I>(&mut self, track: I, name: Option) where I: Iterator { 162 | let mut vec = Vec::new(); 163 | 164 | self.start_track_header(&mut vec); 165 | 166 | let mut length = 0; 167 | let mut cur_time: u64 = 0; 168 | let mut saw_eot = false; 169 | 170 | match name { 171 | Some(n) => { 172 | let namemeta = Event::Meta(MetaEvent::sequence_or_track_name(n)); 173 | length += SMFWriter::write_vtime(0,&mut vec).unwrap(); 174 | self.write_event(&mut vec, &namemeta, &mut length, &mut saw_eot); 175 | } 176 | None => {} 177 | } 178 | 179 | for ev in track { 180 | let vtime = ev.get_time() - cur_time; 181 | cur_time = vtime; 182 | length += SMFWriter::write_vtime(vtime as u64,&mut vec).unwrap(); // TODO: Handle error 183 | self.write_event(&mut vec, ev.get_event(), &mut length, &mut saw_eot); 184 | } 185 | 186 | self.finish_track_write(&mut vec, &mut length, saw_eot); 187 | 188 | self.tracks.push(vec); 189 | } 190 | 191 | // actual writing stuff below 192 | 193 | fn write_header(&self, writer: &mut dyn Write) -> Result<(),Error> { 194 | writer.write_all(&[0x4D,0x54,0x68,0x64])?; 195 | writer.write_u32::(6)?; 196 | writer.write_u16::(self.format)?; 197 | writer.write_u16::(self.tracks.len() as u16)?; 198 | writer.write_i16::(self.ticks)?; 199 | Ok(()) 200 | } 201 | 202 | /// Write out all the tracks that have been added to this 203 | /// SMFWriter to the passed writer 204 | pub fn write_all(self, writer: &mut dyn Write) -> Result<(),Error> { 205 | self.write_header(writer)?; 206 | for track in self.tracks.into_iter() { 207 | writer.write_all(&track[..])?; 208 | } 209 | Ok(()) 210 | } 211 | 212 | /// Write out the result of the tracks that have been added to a 213 | /// file. 214 | /// Warning: This will overwrite an existing file 215 | pub fn write_to_file(self, path: &Path) -> Result<(),Error> { 216 | let mut file = OpenOptions::new().write(true).truncate(true).create(true).open(path)?; 217 | self.write_all(&mut file) 218 | } 219 | 220 | } 221 | 222 | #[test] 223 | fn vwrite() { 224 | let mut vec1 = Vec::new(); 225 | SMFWriter::write_vtime(127,&mut vec1).unwrap(); 226 | assert!(vec1[0] == 0x7f); 227 | 228 | vec1.clear(); 229 | SMFWriter::write_vtime(255,&mut vec1).unwrap(); 230 | assert!(vec1[0] == 0x81); 231 | assert!(vec1[1] == 0x7f); 232 | 233 | vec1.clear(); 234 | SMFWriter::write_vtime(32768,&mut vec1).unwrap(); 235 | assert!(vec1[0] == 0x82); 236 | assert!(vec1[1] == 0x80); 237 | assert!(vec1[2] == 0x00); 238 | } 239 | 240 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! rimd is a set of utilities to deal with midi messages and standard 2 | //! midi files (SMF). It handles both standard midi messages and the meta 3 | //! messages that are found in SMFs. 4 | //! 5 | //! rimd is fairly low level, and messages are stored and accessed in 6 | //! their underlying format (i.e. a vector of u8s). There are some 7 | //! utility methods for accessing the various pieces of a message, and 8 | //! for constructing new messages. 9 | //! 10 | //! For example usage see the bin directory. 11 | //! 12 | //! For a description of the underlying format of midi messages see:
13 | //! http://www.midi.org/techspecs/midimessages.php
14 | //! For a description of the underlying format of meta messages see:
15 | //! http://cs.fit.edu/~ryan/cse4051/projects/midi/midi.html#meta_event 16 | 17 | extern crate byteorder; 18 | extern crate encoding; 19 | extern crate num_traits; 20 | #[macro_use] extern crate num_derive; 21 | 22 | use std::error; 23 | use std::convert::From; 24 | use std::fs::File; 25 | use std::io::{Error,Read}; 26 | use std::path::Path; 27 | 28 | use std::fmt; 29 | use std::string::FromUtf8Error; 30 | 31 | pub use midi:: { 32 | Status, 33 | MidiError, 34 | MidiMessage, 35 | STATUS_MASK, 36 | CHANNEL_MASK, 37 | make_status, 38 | }; 39 | 40 | pub use meta:: { 41 | MetaCommand, 42 | MetaError, 43 | MetaEvent, 44 | }; 45 | 46 | pub use builder:: { 47 | SMFBuilder, 48 | AbsoluteEvent, 49 | }; 50 | 51 | use reader:: { 52 | SMFReader, 53 | }; 54 | 55 | pub use writer:: { 56 | SMFWriter, 57 | }; 58 | 59 | pub use util:: { 60 | note_num_to_name, 61 | }; 62 | 63 | mod builder; 64 | mod midi; 65 | mod meta; 66 | mod reader; 67 | mod writer; 68 | mod util; 69 | 70 | /// Format of the SMF 71 | #[derive(Debug,Clone,Copy,PartialEq)] 72 | pub enum SMFFormat { 73 | /// single track file format 74 | Single = 0, 75 | /// multiple track file format 76 | MultiTrack = 1, 77 | /// multiple song file format (i.e., a series of single type files) 78 | MultiSong = 2, 79 | } 80 | 81 | 82 | impl fmt::Display for SMFFormat { 83 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 84 | write!(f, "{}",match *self { 85 | SMFFormat::Single => "single track", 86 | SMFFormat::MultiTrack => "multiple track", 87 | SMFFormat::MultiSong => "multiple song", 88 | }) 89 | } 90 | } 91 | 92 | /// An event can be either a midi message or a meta event 93 | #[derive(Debug,Clone)] 94 | pub enum Event { 95 | Midi(MidiMessage), 96 | Meta(MetaEvent), 97 | } 98 | 99 | impl fmt::Display for Event { 100 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 101 | match *self { 102 | Event::Midi(ref m) => { write!(f, "{}", m) } 103 | Event::Meta(ref m) => { write!(f, "{}", m) } 104 | } 105 | } 106 | } 107 | 108 | impl Event { 109 | /// Return the number of bytes this event uses. 110 | pub fn len(&self) -> usize { 111 | match *self { 112 | Event::Midi(ref m) => { m.data.len() } 113 | Event::Meta(ref m) => { 114 | let v = SMFWriter::vtime_to_vec(m.length); 115 | // +1 for command byte +1 for 0xFF to indicate Meta event 116 | v.len() + m.data.len() + 2 117 | } 118 | } 119 | } 120 | } 121 | 122 | /// An event occuring in the track. 123 | #[derive(Debug,Clone)] 124 | pub struct TrackEvent { 125 | /// A delta offset, indicating how many ticks after the previous 126 | /// event this event occurs 127 | pub vtime: u64, 128 | /// The actual event 129 | pub event: Event, 130 | } 131 | 132 | 133 | impl fmt::Display for TrackEvent { 134 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 135 | write!(f, "vtime: {}\t{}",self.vtime,self.event) 136 | } 137 | } 138 | 139 | impl TrackEvent { 140 | pub fn fmt_with_time_offset(&self, cur_time: u64) -> String { 141 | format!("time: {}\t{}",(self.vtime+cur_time),self.event) 142 | } 143 | 144 | /// Return the number of bytes this event uses in the track, 145 | /// including the space for the time offset. 146 | pub fn len(&self) -> usize { 147 | let v = SMFWriter::vtime_to_vec(self.vtime); 148 | v.len() + self.event.len() 149 | } 150 | } 151 | 152 | /// A sequence of midi/meta events 153 | #[derive(Debug, Clone)] 154 | pub struct Track { 155 | /// Optional copyright notice 156 | pub copyright: Option, 157 | /// Optional name for this track 158 | pub name: Option, 159 | /// Vector of the events in this track 160 | pub events: Vec 161 | } 162 | 163 | impl fmt::Display for Track { 164 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 165 | write!(f, "Track, copyright: {}, name: {}", 166 | match self.copyright { 167 | Some(ref c) => &c[..], 168 | None => "[none]" 169 | }, 170 | match self.name { 171 | Some(ref n) => &n[..], 172 | None => "[none]" 173 | }) 174 | } 175 | } 176 | 177 | 178 | /// An error that occured in parsing an SMF 179 | #[derive(Debug)] 180 | pub enum SMFError { 181 | InvalidSMFFile(&'static str), 182 | MidiError(MidiError), 183 | MetaError(MetaError), 184 | Error(Error), 185 | } 186 | 187 | impl From for SMFError { 188 | fn from(err: Error) -> SMFError { 189 | SMFError::Error(err) 190 | } 191 | } 192 | 193 | impl From for SMFError { 194 | fn from(err: MidiError) -> SMFError { 195 | SMFError::MidiError(err) 196 | } 197 | } 198 | 199 | impl From for SMFError { 200 | fn from(err: MetaError) -> SMFError { 201 | SMFError::MetaError(err) 202 | } 203 | } 204 | 205 | impl From for SMFError { 206 | fn from(_: FromUtf8Error) -> SMFError { 207 | SMFError::InvalidSMFFile("Invalid UTF8 data in file") 208 | } 209 | } 210 | 211 | impl error::Error for SMFError { 212 | fn description(&self) -> &str { 213 | match *self { 214 | SMFError::InvalidSMFFile(_) => "The SMF file was invalid", 215 | SMFError::Error(ref e) => e.description(), 216 | SMFError::MidiError(ref m) => m.description(), 217 | SMFError::MetaError(ref m) => m.description(), 218 | } 219 | } 220 | 221 | fn cause(&self) -> Option<&dyn error::Error> { 222 | match *self { 223 | SMFError::MidiError(ref m) => Some(m as &dyn error::Error), 224 | SMFError::MetaError(ref m) => Some(m as &dyn error::Error), 225 | SMFError::Error(ref err) => Some(err as &dyn error::Error), 226 | _ => None, 227 | } 228 | } 229 | } 230 | 231 | impl fmt::Display for SMFError { 232 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 233 | match *self { 234 | SMFError::InvalidSMFFile(s) => write!(f,"SMF file is invalid: {}",s), 235 | SMFError::MidiError(ref err) => { write!(f,"{}",err) }, 236 | SMFError::MetaError(ref err) => { write!(f,"{}",err) }, 237 | SMFError::Error(ref err) => { write!(f,"{}",err) }, 238 | } 239 | } 240 | } 241 | 242 | /// A standard midi file 243 | #[derive(Debug, Clone)] 244 | pub struct SMF { 245 | /// The format of the SMF 246 | pub format: SMFFormat, 247 | /// Vector holding each track in this SMF 248 | pub tracks: Vec, 249 | /// The unit of time for delta timing. If the value is positive, 250 | /// then it represents the units per beat. For example, +96 would 251 | /// mean 96 ticks per beat. If the value is negative, delta times 252 | /// are in SMPTE compatible units. 253 | pub division: i16, 254 | } 255 | 256 | 257 | impl SMF { 258 | /// Read an SMF file at the given path 259 | pub fn from_file(path: &Path) -> Result { 260 | let mut file = File::open(path)?; 261 | SMFReader::read_smf(&mut file) 262 | } 263 | 264 | /// Read an SMF from the given reader 265 | pub fn from_reader(reader: &mut dyn Read) -> Result { 266 | SMFReader::read_smf(reader) 267 | } 268 | 269 | /// Convert a type 0 (single track) to type 1 (multi track) SMF 270 | /// Does nothing if the SMF is already in type 1 271 | /// Returns None if the SMF is in type 2 (multi song) 272 | pub fn to_multi_track(&self) -> Option { 273 | match self.format { 274 | SMFFormat::MultiTrack => Some(self.clone()), 275 | SMFFormat::MultiSong => None, 276 | SMFFormat::Single => { 277 | let mut tracks = vec![Vec::::new(); 1 + 16]; // meta track and 16 for the 16 channels 278 | let mut time = 0; 279 | for event in &self.tracks[0].events { 280 | time += event.vtime; 281 | match event.event { 282 | Event::Midi(ref msg) if msg.channel().is_some() => { 283 | let events = &mut tracks[msg.channel().unwrap() as usize + 1]; 284 | events.push(TrackEvent {vtime: time, event: event.event.clone()}); 285 | } 286 | /*MidiEvent::Meta(ref msg) if [ 287 | MetaCommand::MIDIChannelPrefixAssignment, 288 | MetaCommand::MIDIPortPrefixAssignment, 289 | MetaCommand::SequenceOrTrackName, 290 | MetaCommand::InstrumentName, 291 | ].contains(&msg.command) => { 292 | println!("prefix: {:?}", event); 293 | }*/ 294 | _ => { 295 | tracks[0].push(TrackEvent {vtime: time, event: event.event.clone()}); 296 | } 297 | } 298 | } 299 | let mut out = SMF { 300 | format: SMFFormat::MultiTrack, 301 | tracks: vec![], 302 | division: self.division, 303 | }; 304 | for events in &mut tracks { 305 | if events.len() > 0 { 306 | let mut time = 0; 307 | for event in events.iter_mut() { 308 | let tmp = event.vtime; 309 | event.vtime -= time; 310 | time = tmp; 311 | } 312 | out.tracks.push(Track {events: events.clone(), copyright: None, name: None}); 313 | } 314 | } 315 | out.tracks[0].name = self.tracks[0].name.clone(); 316 | out.tracks[0].copyright = self.tracks[0].copyright.clone(); 317 | Some(out) 318 | } 319 | } 320 | } 321 | } 322 | 323 | -------------------------------------------------------------------------------- /src/midi.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::fmt; 3 | use std::convert::From; 4 | use std::io::{Error,Read}; 5 | 6 | use num_traits::FromPrimitive; 7 | 8 | use util::read_byte; 9 | 10 | /// An error that can occur trying to parse a midi message 11 | #[derive(Debug)] 12 | pub enum MidiError { 13 | InvalidStatus(u8), 14 | OtherErr(&'static str), 15 | Error(Error), 16 | } 17 | 18 | impl From for MidiError { 19 | fn from(err: Error) -> MidiError { 20 | MidiError::Error(err) 21 | } 22 | } 23 | 24 | impl 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<&dyn error::Error> { 34 | match *self { 35 | MidiError::Error(ref err) => Some(err as &dyn error::Error), 36 | _ => None, 37 | } 38 | } 39 | } 40 | 41 | impl 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 53 | #[derive(Debug, PartialEq, Clone, Copy, FromPrimitive)] 54 | pub enum Status { 55 | // voice 56 | NoteOff = 0x80, 57 | NoteOn = 0x90, 58 | PolyphonicAftertouch = 0xA0, 59 | ControlChange = 0xB0, 60 | ProgramChange = 0xC0, 61 | ChannelAftertouch = 0xD0, 62 | PitchBend = 0xE0, 63 | 64 | // sysex 65 | SysExStart = 0xF0, 66 | MIDITimeCodeQtrFrame = 0xF1, 67 | SongPositionPointer = 0xF2, 68 | SongSelect = 0xF3, 69 | TuneRequest = 0xF6, // F4 anf 5 are reserved and unused 70 | SysExEnd = 0xF7, 71 | TimingClock = 0xF8, 72 | Start = 0xFA, 73 | Continue = 0xFB, 74 | Stop = 0xFC, 75 | ActiveSensing = 0xFE, // FD also res/unused 76 | SystemReset = 0xFF, 77 | } 78 | 79 | /// Midi message building and parsing. See 80 | /// http://www.midi.org/techspecs/midimessages.php for a description 81 | /// of the various Midi messages that exist. 82 | #[derive(Debug, Default)] 83 | pub struct MidiMessage { 84 | pub data: Vec, 85 | } 86 | 87 | impl Clone for MidiMessage { 88 | fn clone(&self) -> MidiMessage { 89 | MidiMessage { 90 | data: self.data.clone() 91 | } 92 | } 93 | } 94 | 95 | pub const STATUS_MASK: u8 = 0xF0; 96 | pub const CHANNEL_MASK: u8 = 0x0F; 97 | 98 | // Or in the channel bits to a status 99 | #[inline(always)] 100 | pub fn make_status(status: Status, channel: u8) -> u8 { 101 | status as u8 | channel 102 | } 103 | 104 | impl MidiMessage { 105 | /// Return the status (type) of this message 106 | pub fn status(&self) -> Status { 107 | Status::from_u8(self.data[0] & STATUS_MASK).unwrap() 108 | } 109 | 110 | /// Return the channel this message is on (TODO: return 0 for messages with no channel) 111 | pub fn channel(&self) -> Option { 112 | match self.status() { 113 | Status::NoteOff | 114 | Status::NoteOn | 115 | Status::PolyphonicAftertouch | 116 | Status::ControlChange | 117 | Status::ProgramChange | 118 | Status::ChannelAftertouch | 119 | Status::PitchBend => Some(self.data[0] & CHANNEL_MASK), 120 | Status::SysExStart | 121 | Status::MIDITimeCodeQtrFrame | 122 | Status::SongPositionPointer | 123 | Status::SongSelect | 124 | Status::TuneRequest | 125 | Status::SysExEnd | 126 | Status::TimingClock | 127 | Status::Start | 128 | Status::Continue | 129 | Status::Stop | 130 | Status::ActiveSensing | 131 | Status::SystemReset => None 132 | } 133 | } 134 | 135 | /// Get te data at index `index` from this message. Status is at 136 | /// index 0 137 | #[inline(always)] 138 | pub fn data(&self, index: usize) -> u8 { 139 | self.data[index] 140 | } 141 | 142 | /// Create a midi message from a vector of bytes 143 | #[inline(always)] 144 | pub fn from_bytes(bytes: Vec) -> MidiMessage{ 145 | // TODO: Validate bytes 146 | MidiMessage { 147 | data: bytes, 148 | } 149 | } 150 | 151 | // return the number of data bytes for a message with the given status 152 | // -1 -> variable sized message, call get_variable_size 153 | // -2 -> sysex, read until SysExEnd 154 | // -3 -> invalid status 155 | pub fn data_bytes(status: u8) -> isize { 156 | match Status::from_u8(status & STATUS_MASK) { 157 | Some(stat) => { 158 | match stat { 159 | Status::NoteOff | 160 | Status::NoteOn | 161 | Status::PolyphonicAftertouch | 162 | Status::ControlChange | 163 | Status::PitchBend | 164 | Status::SongPositionPointer => { 2 } 165 | 166 | Status::SysExStart => { -2 } 167 | 168 | Status::ProgramChange | 169 | Status::ChannelAftertouch | 170 | Status::MIDITimeCodeQtrFrame | 171 | Status::SongSelect => { 1 } 172 | 173 | Status::TuneRequest | 174 | Status::SysExEnd | 175 | Status::TimingClock | 176 | Status::Start | 177 | Status::Continue | 178 | Status::Stop | 179 | Status::ActiveSensing | 180 | Status::SystemReset => { 0 } 181 | } 182 | } 183 | None => -3 184 | } 185 | } 186 | 187 | /// Get the next midi message from the reader given that the 188 | /// status `stat` has just been read 189 | pub fn next_message_given_status(stat: u8, reader: &mut dyn Read) -> Result { 190 | let mut ret:Vec = Vec::with_capacity(3); 191 | ret.push(stat); 192 | match MidiMessage::data_bytes(stat) { 193 | 0 => {} 194 | 1 => { ret.push(read_byte(reader)?); } 195 | 2 => { ret.push(read_byte(reader)?); 196 | ret.push(read_byte(reader)?); } 197 | -1 => { return Err(MidiError::OtherErr("Don't handle variable sized yet")); } 198 | -2 => { 199 | // skip SysEx message 200 | while { 201 | let byte = read_byte(reader)?; 202 | ret.push(byte); 203 | byte != Status::SysExEnd as u8 204 | } {} 205 | } 206 | _ => { return Err(MidiError::InvalidStatus(stat)); } 207 | } 208 | Ok(MidiMessage{data: ret}) 209 | } 210 | 211 | /// Get the next midi message from the reader given that there's a running 212 | /// status of `stat` and that in place of a status was read `databyte` 213 | pub fn next_message_running_status(stat: u8, databyte: u8, reader: &mut dyn Read) -> Result { 214 | let mut ret:Vec = Vec::with_capacity(3); 215 | ret.push(stat); 216 | ret.push(databyte); 217 | match MidiMessage::data_bytes(stat) { 218 | 0 => { panic!("Can't have zero length message with running status"); } 219 | 1 => { } // already read it 220 | 2 => { ret.push(read_byte(reader)?); } // only need one more byte 221 | -1 => { return Err(MidiError::OtherErr("Don't handle variable sized yet")); } 222 | -2 => { return Err(MidiError::OtherErr("Running status not permitted with meta and sysex event")); } 223 | _ => { return Err(MidiError::InvalidStatus(stat)); } 224 | } 225 | Ok(MidiMessage{data: ret}) 226 | } 227 | 228 | /// Extract next midi message from a reader 229 | pub fn next_message(reader: &mut dyn Read) -> Result { 230 | let stat = read_byte(reader)?; 231 | MidiMessage::next_message_given_status(stat,reader) 232 | } 233 | 234 | 235 | // Functions to build midi messages 236 | 237 | /// Create a note on message 238 | pub fn note_on(note: u8, velocity: u8, channel: u8) -> MidiMessage { 239 | MidiMessage { 240 | data: vec![make_status(Status::NoteOn,channel), note, velocity], 241 | } 242 | } 243 | 244 | /// Create a note off message 245 | pub fn note_off(note: u8, velocity: u8, channel: u8) -> MidiMessage { 246 | MidiMessage { 247 | data: vec![make_status(Status::NoteOff,channel), note, velocity], 248 | } 249 | } 250 | 251 | /// Create a polyphonic aftertouch message 252 | /// This message is most often sent by pressing down on the key after it "bottoms out". 253 | pub fn polyphonic_aftertouch(note: u8, pressure: u8, channel: u8) -> MidiMessage { 254 | MidiMessage { 255 | data: vec![make_status(Status::PolyphonicAftertouch,channel), note, pressure], 256 | } 257 | } 258 | 259 | /// Create a control change message 260 | /// This message is sent when a controller value changes. Controllers include devices such as 261 | /// pedals and levers. Controller numbers 120-127 are reserved as "Channel Mode Messages". 262 | pub fn control_change(controler: u8, data: u8, channel: u8) -> MidiMessage { 263 | MidiMessage { 264 | data: vec![make_status(Status::ControlChange,channel), controler, data], 265 | } 266 | } 267 | 268 | /// Create a program change message 269 | /// This message sent when the patch number changes. `program` is the new program number. 270 | pub fn program_change(program: u8, channel: u8) -> MidiMessage { 271 | MidiMessage { 272 | data: vec![make_status(Status::ProgramChange,channel), program], 273 | } 274 | } 275 | 276 | /// Create a channel aftertouch 277 | /// This message is most often sent by pressing down on the key after it "bottoms out". This message 278 | /// is different from polyphonic after-touch. Use this message to send the single greatest pressure 279 | /// value (of all the current depressed keys). `pressure` is the pressure value. 280 | pub fn channel_aftertouch(pressure: u8, channel: u8) -> MidiMessage { 281 | MidiMessage { 282 | data: vec![make_status(Status::ChannelAftertouch,channel), pressure], 283 | } 284 | } 285 | 286 | /// Create a pitch bench message 287 | /// This message is sent to indicate a change in the pitch bender (wheel or lever, typically). 288 | /// The pitch bender is measured by a fourteen bit value. Center (no pitch change) is 2000H. 289 | /// Sensitivity is a function of the transmitter. `lsb` are the least significant 7 bits. 290 | /// `msb` are the most significant 7 bits. 291 | pub fn pitch_bend(lsb: u8, msb: u8, channel: u8) -> MidiMessage { 292 | MidiMessage { 293 | data: vec![make_status(Status::PitchBend,channel), lsb, msb], 294 | } 295 | } 296 | 297 | } 298 | 299 | impl fmt::Display for Status { 300 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 301 | write!(f, "{}", 302 | match *self { 303 | Status::NoteOff => "Note Off", 304 | Status::NoteOn => "Note On", 305 | Status::PolyphonicAftertouch => "Polyphonic Aftertouch", 306 | Status::ControlChange => "Control Change", 307 | Status::ProgramChange => "Program Change", 308 | Status::ChannelAftertouch => "Channel Aftertouch", 309 | Status::PitchBend => "Pitch Bend", 310 | Status::SysExStart => "SysEx Start", 311 | Status::MIDITimeCodeQtrFrame => "MIDI Time Code Qtr Frame", 312 | Status::SongPositionPointer => "Song Position Pointer", 313 | Status::SongSelect => "Song Select", 314 | Status::TuneRequest => "Tune Request", 315 | Status::SysExEnd => "SysEx End", 316 | Status::TimingClock => "Timing Clock", 317 | Status::Start => "Start", 318 | Status::Continue => "Continue", 319 | Status::Stop => "Stop", 320 | Status::ActiveSensing => "Active Sensing", 321 | Status::SystemReset => "System Reset", 322 | }) 323 | } 324 | } 325 | 326 | impl fmt::Display for MidiMessage { 327 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 328 | if self.data.len() == 2 { 329 | write!(f, "{}: [{}]\tchannel: {:?}", self.status(), self.data[1], self.channel()) 330 | } 331 | else if self.data.len() == 3 { 332 | write!(f, "{}: [{},{}]\tchannel: {:?}", self.status(), self.data[1], self.data[2], self.channel()) 333 | } 334 | else if self.data.len() == 0 { 335 | write!(f, "{}: [no data]\tchannel: {:?}", self.status(), self.channel()) 336 | } 337 | else { 338 | write!(f, "{}: {:?}\tchannel: {:?}", self.status(), self.data, self.channel()) 339 | } 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /src/builder.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use std::collections::BinaryHeap; 3 | use std::ops::IndexMut; 4 | 5 | use ::{SMF,Event,SMFFormat,MetaEvent,MidiMessage,Track,TrackEvent}; 6 | 7 | /// An AbsoluteEvent is an event that has an absolute time 8 | /// This is useful for apps that want to store events internally 9 | /// with absolute times and then quickly build an SMF file for saving etc... 10 | pub struct AbsoluteEvent { 11 | time: u64, 12 | event: Event, 13 | } 14 | 15 | impl AbsoluteEvent { 16 | pub fn new_midi(time: u64, midi: MidiMessage) -> AbsoluteEvent { 17 | AbsoluteEvent { 18 | time: time, 19 | event: Event::Midi(midi), 20 | } 21 | } 22 | pub fn new_meta(time: u64, meta: MetaEvent) -> AbsoluteEvent { 23 | AbsoluteEvent { 24 | time: time, 25 | event: Event::Meta(meta), 26 | } 27 | } 28 | 29 | /// Return true if the event inside this AbsoluteEvent is a midi 30 | /// event, false if it's a meta event 31 | pub fn is_midi(&self) -> bool { 32 | match self.event { 33 | Event::Midi(_) => true, 34 | Event::Meta(_) => false, 35 | } 36 | } 37 | 38 | /// Return true if the event inside this AbsoluteEvent is a meta 39 | /// event, false if it's a midi event 40 | pub fn is_meta(&self) -> bool { 41 | match self.event { 42 | Event::Midi(_) => false, 43 | Event::Meta(_) => true, 44 | } 45 | } 46 | 47 | pub fn get_event(&self) -> &Event { 48 | &self.event 49 | } 50 | 51 | pub fn get_time(&self) -> u64 { 52 | self.time 53 | } 54 | } 55 | 56 | impl Eq for AbsoluteEvent {} 57 | 58 | impl PartialEq for AbsoluteEvent { 59 | fn eq(&self, other: &AbsoluteEvent) -> bool { 60 | if self.time == other.time { 61 | match (&self.event,&other.event) { 62 | (&Event::Midi(_),&Event::Meta(_)) => false, 63 | (&Event::Meta(_),&Event::Midi(_)) => false, 64 | (&Event::Meta(ref me),&Event::Meta(ref you)) => { 65 | me.command == you.command 66 | }, 67 | (&Event::Midi(ref me),&Event::Midi(ref you)) => { 68 | me.data(0) == you.data(0) 69 | && 70 | me.data(1) == me.data(1) 71 | }, 72 | } 73 | } else { 74 | false 75 | } 76 | } 77 | 78 | fn ne(&self, other: &AbsoluteEvent) -> bool { 79 | !(self.eq(other)) 80 | } 81 | } 82 | 83 | // Implement `Ord` and sort messages by time 84 | impl Ord for AbsoluteEvent { 85 | fn cmp(&self, other: &AbsoluteEvent) -> Ordering { 86 | let res = self.time.cmp(&other.time); 87 | match res { 88 | // vtime takes priority 89 | Ordering::Less | Ordering::Greater => res, 90 | // if vtime is the same, check types and make meta events 91 | // sort before standard events 92 | Ordering::Equal => { 93 | match (&self.event,&other.event) { 94 | // I'm midi, other is meta, so I'm greater 95 | (&Event::Midi(_),&Event::Meta(_)) => Ordering::Greater, 96 | // I'm meta, other is midi, so I'm less 97 | (&Event::Meta(_),&Event::Midi(_)) => Ordering::Less, 98 | (&Event::Meta(ref me),&Event::Meta(ref you)) => { 99 | me.command.cmp(&you.command) 100 | }, 101 | (&Event::Midi(ref me),&Event::Midi(ref you)) => { 102 | if me.data(0) < you.data(0) { Ordering::Less } 103 | else if me.data(0) > you.data(0) { Ordering::Greater } 104 | else { 105 | if me.data(1) < you.data(1) { 106 | Ordering::Less 107 | } else if me.data(1) > you.data(1) { 108 | Ordering::Greater 109 | } else { 110 | res 111 | } 112 | } 113 | }, 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | impl PartialOrd for AbsoluteEvent { 121 | fn partial_cmp(&self, other: &AbsoluteEvent) -> Option { 122 | Some(self.cmp(other)) 123 | } 124 | } 125 | 126 | enum EventContainer { 127 | Heap(BinaryHeap), 128 | Static(Vec), 129 | } 130 | 131 | struct TrackBuilder { 132 | copyright: Option, 133 | name: Option, 134 | events: EventContainer, 135 | } 136 | 137 | impl TrackBuilder { 138 | 139 | fn result(self) -> Track { 140 | Track { 141 | copyright: self.copyright, 142 | name: self.name, 143 | events: match self.events { 144 | EventContainer::Heap(heap) => { 145 | let mut events = Vec::with_capacity(heap.len()); 146 | let absevents = heap.into_sorted_vec(); 147 | let mut prev_time = 0; 148 | for ev in absevents.into_iter() { 149 | let vtime = 150 | if prev_time == 0 { 151 | ev.time 152 | } else { 153 | ev.time - prev_time 154 | }; 155 | prev_time = ev.time; 156 | events.push(TrackEvent { 157 | vtime: vtime, 158 | event: ev.event, 159 | }); 160 | } 161 | events 162 | }, 163 | EventContainer::Static(vec) => vec, 164 | }, 165 | } 166 | } 167 | 168 | fn abs_time_from_delta(&self,delta: u64) -> u64 { 169 | match self.events { 170 | EventContainer::Heap(ref heap) => { 171 | match heap.peek() { 172 | Some(e) => { e.time + delta } 173 | None => { delta } 174 | } 175 | } 176 | _ => { panic!("Can't call abs_time_from_delta on non-heap builder") } 177 | } 178 | } 179 | } 180 | 181 | /// An SMFBuilder can be used to create an SMF file. This is done by 182 | /// adding tracks to the builder via `add_track` and then adding 183 | /// events to each track. 184 | pub struct SMFBuilder { 185 | tracks:Vec 186 | } 187 | 188 | impl SMFBuilder { 189 | /// Create a new SMFBuilder. Initially the builder will have no tracks 190 | pub fn new() -> SMFBuilder { 191 | SMFBuilder { 192 | tracks: Vec::new(), 193 | } 194 | } 195 | 196 | /// Get the number of tracks currenly in the builder 197 | pub fn num_tracks(&self) -> usize { 198 | self.tracks.len() 199 | } 200 | 201 | /// Add new a track to this builder 202 | pub fn add_track(&mut self) { 203 | self.tracks.push(TrackBuilder { 204 | copyright: None, 205 | name: None, 206 | events: EventContainer::Heap(BinaryHeap::new()), 207 | }); 208 | } 209 | 210 | /// Add a static track to the builder (note this will clone all events in the passed iterator) 211 | pub fn add_static_track<'a,I>(&mut self, track: I) where I: Iterator { 212 | let mut cur_time: u64 = 0; 213 | let vec = track.map(|bev| { 214 | assert!(bev.time >= cur_time); 215 | let vtime = bev.time - cur_time; 216 | cur_time = bev.time; 217 | TrackEvent { 218 | vtime: vtime, 219 | event: bev.event.clone(), 220 | } 221 | }).collect(); 222 | self.tracks.push(TrackBuilder { 223 | copyright: None, 224 | name: None, 225 | events: EventContainer::Static(vec), 226 | }); 227 | } 228 | 229 | /// Set the copyright for the track at index `track`. This will 230 | /// also cause a copyright meta event to be inserted. 231 | /// ## Panics 232 | /// 233 | /// Panics if `track` is >= to the number of tracks in this 234 | /// builder, or if the track already has a copyright set. 235 | pub fn set_copyright(&mut self, track: usize, copyright: String) { 236 | assert!(self.tracks.len() > track); 237 | assert!(self.tracks[track].copyright.is_none()); 238 | // let event = AbsoluteEvent { 239 | // time: 0, 240 | // event: Event::Meta(MetaEvent::copyright_notice(copyright.clone())), 241 | // }; 242 | self.tracks[track].copyright = Some(copyright); 243 | } 244 | 245 | /// Set the name for the track at index `track`. This will 246 | /// also cause a name meta event to be inserted. 247 | /// 248 | /// ## Panics 249 | /// 250 | /// Panics if `track` is >= to the number of tracks in this 251 | /// builder, or if the track already has a name set. 252 | pub fn set_name(&mut self, track: usize, name: String) { 253 | assert!(self.tracks.len() > track); 254 | assert!(self.tracks[track].name.is_none()); 255 | // let event = AbsoluteEvent{ 256 | // time: 0, 257 | // event: Event::Meta(MetaEvent::sequence_or_track_name(name.clone())), 258 | // }; 259 | self.tracks[track].name = Some(name); 260 | } 261 | 262 | /// Add a midi message to track at index `track` at absolute time 263 | /// `time`. 264 | /// 265 | /// ## Panics 266 | /// 267 | /// Panics if `track` is >= to the number of tracks in this builder 268 | pub fn add_midi_abs(&mut self, track: usize, time: u64, msg: MidiMessage) { 269 | assert!(self.tracks.len() > track); 270 | match self.tracks.index_mut(track).events { 271 | EventContainer::Heap(ref mut heap) => { 272 | heap.push(AbsoluteEvent { 273 | time: time, 274 | event: Event::Midi(msg), 275 | }); 276 | } 277 | _ => { panic!("Can't add events to static tracks") } 278 | } 279 | } 280 | 281 | /// Add a midi message to track at index `track` at `delta` ticks 282 | /// after the last message (or at `delta` if no current messages 283 | /// exist) 284 | /// 285 | /// ## Panics 286 | /// 287 | /// Panics if `track` is >= to the number of tracks in this builder 288 | pub fn add_midi_rel(&mut self, track: usize, delta: u64, msg: MidiMessage) { 289 | assert!(self.tracks.len() > track); 290 | let time = self.tracks[track].abs_time_from_delta(delta); 291 | self.add_midi_abs(track,time,msg); 292 | } 293 | 294 | /// Add a meta event to track at index `track` at absolute time 295 | /// `time`. 296 | /// 297 | /// ## Panics 298 | /// 299 | /// Panics if `track` is >= to the number of tracks in this builder 300 | pub fn add_meta_abs(&mut self, track: usize, time: u64, event: MetaEvent) { 301 | assert!(self.tracks.len() > track); 302 | match self.tracks.index_mut(track).events { 303 | EventContainer::Heap(ref mut heap) => { 304 | heap.push(AbsoluteEvent { 305 | time: time, 306 | event: Event::Meta(event), 307 | }); 308 | } 309 | _ => { panic!("Can't add events to static tracks") } 310 | } 311 | } 312 | 313 | /// Add a meta event to track at index `track` at `delta` ticks 314 | /// after the last message (or at `delta` if no current messages 315 | /// exist) 316 | /// 317 | /// ## Panics 318 | /// 319 | /// Panics if `track` is >= to the number of tracks in this builder 320 | pub fn add_meta_rel(&mut self, track: usize, delta: u64, event: MetaEvent) { 321 | assert!(self.tracks.len() > track); 322 | let time = self.tracks[track].abs_time_from_delta(delta); 323 | self.add_meta_abs(track,time,event); 324 | } 325 | 326 | /// Add a TrackEvent to the track at index `track`. The event 327 | /// will be added at `event.vtime` after the last event currently 328 | /// in the builder for the track. 329 | /// 330 | /// ## Panics 331 | /// 332 | /// Panics if `track` is >= to the number of tracks in this builder 333 | pub fn add_event(&mut self, track: usize, event: TrackEvent) { 334 | assert!(self.tracks.len() > track); 335 | let bevent = AbsoluteEvent { 336 | time: self.tracks[track].abs_time_from_delta(event.vtime), 337 | event: event.event, 338 | }; 339 | match self.tracks.index_mut(track).events { 340 | EventContainer::Heap(ref mut heap) => { 341 | heap.push(bevent); 342 | } 343 | _ => { panic!("Can't add events to static tracks") } 344 | } 345 | } 346 | 347 | /// Generate an SMF file with the events that have been added to 348 | /// the builder 349 | pub fn result(self) -> SMF { 350 | SMF { 351 | format: SMFFormat::MultiTrack, 352 | tracks: self.tracks.into_iter().map(|tb| tb.result()).collect(), 353 | division: 0, 354 | } 355 | } 356 | } 357 | 358 | #[test] 359 | fn simple_build() { 360 | let note_on = MidiMessage::note_on(69,100,0); 361 | let note_off = MidiMessage::note_off(69,100,0); 362 | 363 | 364 | let mut builder = SMFBuilder::new(); 365 | builder.add_track(); 366 | 367 | builder.add_event(0, TrackEvent{vtime: 0, event: Event::Midi(note_on)}); 368 | builder.add_event(0, TrackEvent{vtime: 10, event: Event::Midi(note_off)}); 369 | builder.result(); 370 | } 371 | -------------------------------------------------------------------------------- /src/meta.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::io::{Error, Read}; 3 | use std::fmt; 4 | 5 | use reader::SMFReader; 6 | 7 | use num_traits::FromPrimitive; 8 | 9 | use util::{read_byte, read_amount, latin1_decode}; 10 | 11 | /// An error that can occur parsing a meta command 12 | #[derive(Debug)] 13 | pub enum MetaError { 14 | InvalidCommand(u8), 15 | OtherErr(&'static str), 16 | Error(Error), 17 | } 18 | 19 | impl From for MetaError { 20 | fn from(err: Error) -> MetaError { 21 | MetaError::Error(err) 22 | } 23 | } 24 | 25 | impl error::Error for MetaError { 26 | fn description(&self) -> &str { 27 | match *self { 28 | MetaError::InvalidCommand(_) => "Invalid meta command", 29 | MetaError::OtherErr(_) => "A general midi error has occured", 30 | MetaError::Error(ref e) => e.description(), 31 | } 32 | } 33 | 34 | fn cause(&self) -> Option<&dyn error::Error> { 35 | match *self { 36 | MetaError::Error(ref err) => Some(err as &dyn error::Error), 37 | _ => None, 38 | } 39 | } 40 | } 41 | 42 | impl fmt::Display for MetaError { 43 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 44 | match *self { 45 | MetaError::InvalidCommand(ref c) => write!(f,"Invalid Meta command: {}",c), 46 | MetaError::OtherErr(ref s) => write!(f,"Meta Error: {}",s), 47 | MetaError::Error(ref e) => write!(f,"{}",e), 48 | } 49 | } 50 | } 51 | 52 | /// Commands that meta messages can represent 53 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd,Ord, FromPrimitive)] 54 | pub enum MetaCommand { 55 | SequenceNumber = 0x00, 56 | TextEvent = 0x01, 57 | CopyrightNotice = 0x02, 58 | SequenceOrTrackName = 0x03, 59 | InstrumentName = 0x04, 60 | LyricText = 0x05, 61 | MarkerText = 0x06, 62 | CuePoint = 0x07, 63 | MIDIChannelPrefixAssignment = 0x20, 64 | MIDIPortPrefixAssignment = 0x21, 65 | EndOfTrack = 0x2F, 66 | TempoSetting = 0x51, 67 | SMPTEOffset = 0x54, 68 | TimeSignature = 0x58, 69 | KeySignature = 0x59, 70 | SequencerSpecificEvent = 0x7F, 71 | Unknown, 72 | } 73 | 74 | /// Meta event building and parsing. See 75 | /// http://cs.fit.edu/~ryan/cse4051/projects/midi/midi.html#meta_event 76 | /// for a description of the various meta events and their formats 77 | #[derive(Debug)] 78 | pub struct MetaEvent { 79 | pub command: MetaCommand, 80 | pub length: u64, 81 | pub data: Vec, 82 | } 83 | 84 | impl Clone for MetaEvent { 85 | fn clone(&self) -> MetaEvent { 86 | MetaEvent { 87 | command: self.command, 88 | length: self.length, 89 | data: self.data.clone(), 90 | } 91 | } 92 | } 93 | 94 | impl fmt::Display for MetaEvent { 95 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 96 | write!(f, "Meta Event: {}", 97 | match self.command { 98 | MetaCommand::SequenceNumber => format!("Sequence Number: {}", ((self.data[0] as u16) << 8) | self.data[1] as u16), 99 | MetaCommand::TextEvent => { 100 | format!("Text Event. Len: {} Text: {}", self.length, latin1_decode(&self.data)) 101 | }, 102 | MetaCommand::CopyrightNotice => { 103 | format!("Copyright Notice: {}", latin1_decode(&self.data)) 104 | }, 105 | MetaCommand::SequenceOrTrackName => { 106 | format!("Sequence/Track Name, length: {}, name: {}", self.length, latin1_decode(&self.data)) 107 | }, 108 | MetaCommand::InstrumentName => { 109 | format!("InstrumentName: {}", latin1_decode(&self.data)) 110 | }, 111 | MetaCommand::LyricText => { 112 | format!("LyricText: {}", latin1_decode(&self.data)) 113 | } 114 | MetaCommand::MarkerText => { 115 | format!("MarkerText: {}", latin1_decode(&self.data)) 116 | } 117 | MetaCommand::CuePoint => format!("CuePoint: {}", latin1_decode(&self.data)), 118 | MetaCommand::MIDIChannelPrefixAssignment => format!("MIDI Channel Prefix Assignment, channel: {}", self.data[0]+1), 119 | MetaCommand::MIDIPortPrefixAssignment => format!("MIDI Port Prefix Assignment, port: {}", self.data[0]), 120 | MetaCommand::EndOfTrack => format!("End Of Track"), 121 | MetaCommand::TempoSetting => format!("Set Tempo, microseconds/quarter note: {}", self.data_as_u64(3)), 122 | MetaCommand::SMPTEOffset => format!("SMPTEOffset"), 123 | MetaCommand::TimeSignature => format!("Time Signature: {}/{}, {} ticks/metronome click, {} 32nd notes/quarter note", 124 | self.data[0], 125 | 2usize.pow(self.data[1] as u32), 126 | self.data[2], 127 | self.data[3]), 128 | MetaCommand::KeySignature => format!("Key Signature, {} sharps/flats, {}", 129 | self.data[0] as i8, 130 | match self.data[1] { 131 | 0 => "Major", 132 | 1 => "Minor", 133 | _ => "Invalid Signature", 134 | }), 135 | MetaCommand::SequencerSpecificEvent => format!("SequencerSpecificEvent"), 136 | MetaCommand::Unknown => format!("Unknown, length: {}", self.data.len()), 137 | }) 138 | } 139 | } 140 | 141 | impl MetaEvent { 142 | 143 | /// Turn `bytes` bytes of the data of this event into a u64 144 | pub fn data_as_u64(&self, bytes: usize) -> u64 { 145 | let mut res = 0; 146 | for i in 0..bytes { 147 | res <<= 8; 148 | res |= self.data[i] as u64; 149 | } 150 | res 151 | } 152 | 153 | /// Extract the next meta event from a reader 154 | pub fn next_event(reader: &mut dyn Read) -> Result { 155 | let command = 156 | match MetaCommand::from_u8(read_byte(reader)?) { 157 | Some(c) => {c}, 158 | None => MetaCommand::Unknown, 159 | }; 160 | let len = match SMFReader::read_vtime(reader) { 161 | Ok(t) => { t } 162 | Err(_) => { return Err(MetaError::OtherErr("Couldn't read time for meta command")); } 163 | }; 164 | let mut data = Vec::new(); 165 | read_amount(reader,&mut data,len as usize)?; 166 | Ok(MetaEvent{ 167 | command: command, 168 | length: len, 169 | data: data 170 | }) 171 | } 172 | 173 | 174 | // util functions for event constructors 175 | fn u16_to_vec(val: u16) -> Vec { 176 | let mut res = Vec::with_capacity(2); 177 | res.push((val >> 8) as u8); 178 | res.push(val as u8); 179 | res 180 | } 181 | 182 | fn u24_to_vec(val: u32) -> Vec { 183 | assert!(val <= 2u32.pow(24)); 184 | let mut res = Vec::with_capacity(3); 185 | res.push((val >> 16) as u8); 186 | res.push((val >> 8) as u8); 187 | res.push(val as u8); 188 | res 189 | } 190 | 191 | // event constructors below 192 | 193 | /// Create a sequence number meta event 194 | pub fn sequence_number(sequence_number: u16) -> MetaEvent { 195 | MetaEvent { 196 | command: MetaCommand::SequenceNumber, 197 | length: 0x02, 198 | data: MetaEvent::u16_to_vec(sequence_number), 199 | } 200 | } 201 | 202 | /// Create a text meta event 203 | pub fn text_event(text: String) -> MetaEvent { 204 | MetaEvent { 205 | command: MetaCommand::TextEvent, 206 | length: text.len() as u64, 207 | data: text.into_bytes(), 208 | } 209 | } 210 | 211 | /// Create a copyright notice meta event 212 | pub fn copyright_notice(copyright: String) -> MetaEvent { 213 | MetaEvent { 214 | command: MetaCommand::CopyrightNotice, 215 | length: copyright.len() as u64, 216 | data: copyright.into_bytes(), 217 | } 218 | } 219 | 220 | /// Create a name meta event 221 | pub fn sequence_or_track_name(name: String) -> MetaEvent { 222 | MetaEvent { 223 | command: MetaCommand::SequenceOrTrackName, 224 | length: name.len() as u64, 225 | data: name.into_bytes(), 226 | } 227 | } 228 | 229 | /// Create an instrument name meta event 230 | pub fn instrument_name(name: String) -> MetaEvent { 231 | MetaEvent { 232 | command: MetaCommand::InstrumentName, 233 | length: name.len() as u64, 234 | data: name.into_bytes(), 235 | } 236 | } 237 | 238 | /// Create a lyric text meta event 239 | pub fn lyric_text(text: String) -> MetaEvent { 240 | MetaEvent { 241 | command: MetaCommand::LyricText, 242 | length: text.len() as u64, 243 | data: text.into_bytes(), 244 | } 245 | } 246 | 247 | 248 | /// Create a marker text meta event 249 | pub fn marker_text(text: String) -> MetaEvent { 250 | MetaEvent { 251 | command: MetaCommand::MarkerText, 252 | length: text.len() as u64, 253 | data: text.into_bytes(), 254 | } 255 | } 256 | 257 | /// Create a cue point meta event 258 | pub fn cue_point(text: String) -> MetaEvent { 259 | MetaEvent { 260 | command: MetaCommand::CuePoint, 261 | length: text.len() as u64, 262 | data: text.into_bytes(), 263 | } 264 | } 265 | 266 | /// Create a midi channel prefix assignment meta event 267 | pub fn midichannel_prefix_assignment(channel: u8) -> MetaEvent { 268 | MetaEvent { 269 | command: MetaCommand::MIDIChannelPrefixAssignment, 270 | length: 1, 271 | data: vec![channel], 272 | } 273 | } 274 | 275 | /// Create a midi port prefix assignment meta event 276 | pub fn midiport_prefix_assignment(port: u8) -> MetaEvent { 277 | MetaEvent { 278 | command: MetaCommand::MIDIPortPrefixAssignment, 279 | length: 1, 280 | data: vec![port], 281 | } 282 | } 283 | 284 | /// Create an end of track meta event 285 | pub fn end_of_track() -> MetaEvent { 286 | MetaEvent { 287 | command: MetaCommand::EndOfTrack, 288 | length: 0, 289 | data: vec![], 290 | } 291 | } 292 | 293 | /// Create an event to set track tempo. This is stored 294 | /// as a 24-bit value. This method will fail an assertion if 295 | /// the supplied tempo is greater than 2^24. 296 | pub fn tempo_setting(tempo: u32) -> MetaEvent { 297 | MetaEvent { 298 | command: MetaCommand::TempoSetting, 299 | length: 3, 300 | data: MetaEvent::u24_to_vec(tempo), 301 | } 302 | } 303 | 304 | /// Create an smpte offset meta event 305 | pub fn smpte_offset(hours: u8, minutes: u8, seconds: u8, frames: u8, fractional: u8) -> MetaEvent { 306 | MetaEvent { 307 | command: MetaCommand::SMPTEOffset, 308 | length: 5, 309 | data: vec![hours,minutes,seconds,frames,fractional], 310 | } 311 | } 312 | 313 | /// Create a time signature event. 314 | /// Time signature of the form: 315 | /// `numerator`/2^`denominator` 316 | /// eg: 6/8 would be specified using `numerator`=6, `denominator`=3 317 | /// 318 | /// The parameter `clocks_per_tick` is the number of MIDI Clocks per metronome tick. 319 | 320 | /// Normally, there are 24 MIDI Clocks per quarter note. 321 | /// However, some software allows this to be set by the user. 322 | /// The parameter `num_32nd_notes_per_24_clocks` defines this in terms of the 323 | /// number of 1/32 notes which make up the usual 24 MIDI Clocks 324 | /// (the 'standard' quarter note). 8 is standard 325 | pub fn time_signature(numerator: u8, denominator: u8, clocks_per_tick: u8, num_32nd_notes_per_24_clocks: u8) -> MetaEvent { 326 | MetaEvent { 327 | command: MetaCommand::TimeSignature, 328 | length: 4, 329 | data: vec![numerator,denominator,clocks_per_tick,num_32nd_notes_per_24_clocks], 330 | } 331 | } 332 | 333 | /// Create a Key Signature event 334 | /// expressed as the number of sharps or flats, and a major/minor flag. 335 | 336 | /// `sharps_flats` of 0 represents a key of C, negative numbers represent 337 | /// 'flats', while positive numbers represent 'sharps'. 338 | pub fn key_signature(sharps_flats: u8, major_minor: u8) -> MetaEvent { 339 | MetaEvent { 340 | command: MetaCommand::KeySignature, 341 | length: 2, 342 | data: vec![sharps_flats, major_minor], 343 | } 344 | } 345 | 346 | /// This is the MIDI-file equivalent of the System Exclusive Message. 347 | /// sequencer-specific directives can be incorporated into a 348 | /// MIDI file using this event. 349 | pub fn sequencer_specific_event(data: Vec) -> MetaEvent { 350 | MetaEvent { 351 | command: MetaCommand::SequencerSpecificEvent, 352 | length: data.len() as u64, 353 | data: data, 354 | } 355 | } 356 | 357 | } 358 | --------------------------------------------------------------------------------