aboutsummaryrefslogtreecommitdiff
path: root/src/midifile.rs
diff options
context:
space:
mode:
authorGravatar Chris Xiong <chirs241097@gmail.com> 2022-11-07 17:15:15 -0500
committerGravatar Chris Xiong <chirs241097@gmail.com> 2022-11-07 17:15:15 -0500
commit7f49730fd8a6dc52fc45937e868749e5612c5234 (patch)
tree9729ec896e42279f9cc49e862c9405ac60cc6b42 /src/midifile.rs
downloadit2midi-7f49730fd8a6dc52fc45937e868749e5612c5234.tar.xz
(internal) initial commit
Diffstat (limited to 'src/midifile.rs')
-rw-r--r--src/midifile.rs144
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(())
+}