diff options
author | Chris Xiong <chirs241097@gmail.com> | 2022-11-07 17:15:15 -0500 |
---|---|---|
committer | Chris Xiong <chirs241097@gmail.com> | 2022-11-07 17:15:15 -0500 |
commit | 7f49730fd8a6dc52fc45937e868749e5612c5234 (patch) | |
tree | 9729ec896e42279f9cc49e862c9405ac60cc6b42 /src/midifile.rs | |
download | it2midi-7f49730fd8a6dc52fc45937e868749e5612c5234.tar.xz |
(internal) initial commit
Diffstat (limited to 'src/midifile.rs')
-rw-r--r-- | src/midifile.rs | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/src/midifile.rs b/src/midifile.rs new file mode 100644 index 0000000..71a861c --- /dev/null +++ b/src/midifile.rs @@ -0,0 +1,144 @@ +use std::io; +use std::io::prelude::*; +use std::fs::File; + +pub struct RawMidiEvent +{ + ty: u8, + p1: u8, + p2: u8, //any value > 0x7f won't be written to file + data: Vec<u8> +} + +pub enum MidiEvent +{ + NoteOn{ch: u8, key: u8, vel: u8}, + NoteOff{ch: u8, key: u8, vel: u8}, + KeyAfterTouch{ch: u8, key: u8, val: u8}, + CtrlChange{ch: u8, no: u8, val: u8}, + ProgChange{ch: u8, val: u8}, + ChannelAfterTouch{ch: u8, val: u8}, + PitchBend{ch: u8, val: u16}, + //SysExc(Vec<u8>), + MetaTempo(f64), + MetaTimeSig{n: u8, d_pot: u8}, + MetaTrackName(String), + MetaEndOfTrack +} + +pub struct TimedMidiEvent +{ + pub t: u32, + pub e: MidiEvent +} + +impl From<&MidiEvent> for RawMidiEvent +{ + fn from(e: &MidiEvent) -> Self + { + match e + { + MidiEvent::NoteOff{ch, key, vel} + if *vel == 0x40 => RawMidiEvent{ty: 0x90 | ch, p1: *key, p2: 0, data:vec![]}, + MidiEvent::NoteOff{ch, key, vel} => RawMidiEvent{ty: 0x80 | ch, p1: *key, p2: *vel, data: vec![]}, + MidiEvent::NoteOn {ch, key, vel} => RawMidiEvent{ty: 0x90 | ch, p1: *key, p2: *vel, data: vec![]}, + MidiEvent::KeyAfterTouch{ch, key, val} => RawMidiEvent{ty: 0xa0 | ch, p1: *key, p2: *val, data: vec![]}, + MidiEvent::CtrlChange{ch, no, val} => RawMidiEvent{ty: 0xb0 | ch, p1: *no, p2: *val, data: vec![]}, + MidiEvent::ProgChange{ch, val} => RawMidiEvent{ty: 0xc0 | ch, p1: *val, p2: 0xff, data: vec![]}, + MidiEvent::ChannelAfterTouch{ch, val} => RawMidiEvent{ty: 0xd0 | ch, p1: *val, p2: 0xff, data: vec![]}, + MidiEvent::PitchBend{ch, val} => RawMidiEvent{ty: 0xe0 | ch, p1: (val & 0x7f) as u8, p2: (val >> 7) as u8, data: vec![]}, + MidiEvent::MetaTempo(tempo) => + { + let us = (60000000. / tempo) as u32; + let usbuf = us.to_be_bytes(); + RawMidiEvent{ty: 0xff, p1: 0x51, p2: 3, data: Vec::from(&usbuf[1..])} + } + MidiEvent::MetaTimeSig{n, d_pot} => + { + let x: [u8; 4] = [*n, *d_pot, 24, 8]; + RawMidiEvent{ty: 0xff, p1: 0x58, p2: 4, data: Vec::from(x)} + } + MidiEvent::MetaTrackName(s) => + { + let sb = s.as_bytes(); + RawMidiEvent{ty: 0xff, p1: 0x03, p2: s.len() as u8, data: Vec::from(sb)} + } + MidiEvent::MetaEndOfTrack => + RawMidiEvent{ty: 0xff, p1: 0x2f, p2: 0xff, data: vec![]} + } + } +} + +type MidiTrack = Vec<TimedMidiEvent>; + +pub struct MidiFile +{ + pub div: u16, + pub tracks: Vec<MidiTrack> +} + +fn write_u16be(f: &mut File, v: u16) -> io::Result<()> +{ + let bytes = v.to_be_bytes(); + f.write_all(&bytes) +} + +fn write_u32be(f: &mut File, v: u32) -> io::Result<()> +{ + let bytes = v.to_be_bytes(); + f.write_all(&bytes) +} + +fn write_varlen(f: &mut File, v: u32) -> io::Result<()> +{ + if v > 0x0fffffff + { return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid variable length value")); } + let mut sh: u32 = 4 * 7; + while sh > 0 && (v >> sh) == 0 + { sh -= 7; } + let mut buf: Vec<u8> = Vec::new(); + while sh > 0 + { buf.push((((v >> sh) & 0x7f) | 0x80) as u8); sh -= 7; } + buf.push((v & 0x7f) as u8); + f.write_all(&buf[..]) +} + +fn write_raw_event(f: &mut File, re: &RawMidiEvent) -> io::Result<()> +{ + let mut buf: Vec<u8> = Vec::new(); + buf.push(re.ty); + buf.push(re.p1); + if re.p2 < 0x80 { buf.push(re.p2); } + for d in &re.data { buf.push(*d); } + f.write_all(&buf[..]) +} + +fn write_track(f: &mut File, trk: &MidiTrack) -> io::Result<()> +{ + let header = "MTrk".as_bytes(); + f.write_all(header)?; + let mut curt = 0u32; + for te in trk + { + let TimedMidiEvent{t, e} = te; + write_varlen(f, t - curt)?; + curt = *t; + let re = RawMidiEvent::from(e); + write_raw_event(f, &re)?; + } + Ok(()) +} + +fn write_file(filename: &str, mf: &MidiFile) -> io::Result<()> +{ + let mut f = File::create(filename)?; + let header = "MThd".as_bytes(); + f.write_all(header)?; + write_u32be(&mut f, 6)?; + write_u16be(&mut f, 1)?; + write_u16be(&mut f, mf.tracks.len() as u16)?; + write_u16be(&mut f, mf.div)?; + for trk in &mf.tracks + { write_track(&mut f, &trk)?; } + Ok(()) +} |