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>
}
#[derive(Clone)]
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
}
#[derive(Clone)]
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: 0, data: vec![]}
}
}
}
pub type MidiTrack = Vec<TimedMidiEvent>;
pub struct MidiFile
{
pub div: u16,
pub tracks: Vec<MidiTrack>
}
fn write_u16be<W>(f: &mut W, v: u16) -> io::Result<()> where W: Write
{
let bytes = v.to_be_bytes();
f.write_all(&bytes)
}
fn write_u32be<W>(f: &mut W, v: u32) -> io::Result<()> where W: Write
{
let bytes = v.to_be_bytes();
f.write_all(&bytes)
}
fn write_varlen<W>(f: &mut W, v: u32) -> io::Result<()> where W: Write
{
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<W>(f: &mut W, re: &RawMidiEvent, runst: u8) -> io::Result<u8> where W: Write
{
let mut buf: Vec<u8> = Vec::new();
if re.ty != runst || runst >= 0xf0 || re.ty >= 0xf0
{ 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[..])?;
Ok(re.ty)
}
fn write_track<W>(f: &mut W, trk: &MidiTrack) -> io::Result<()> where W: Write
{
let header = "MTrk".as_bytes();
f.write_all(header)?;
let mut buf: Vec<u8> = Vec::new();
let mut curt = 0u32;
let mut runst = 0u8;
for te in trk
{
let TimedMidiEvent{t, e} = te;
write_varlen(&mut buf, t - curt)?;
curt = *t;
let re = RawMidiEvent::from(e);
runst = write_raw_event(&mut buf, &re, runst)?;
}
write_u32be(f, buf.len() as u32)?;
f.write_all(&buf[..])?;
Ok(())
}
pub fn lint_track(trk: &MidiTrack) -> bool
{
let mut ret = true;
let mut notest = [false; 128];
for te in trk
{
let TimedMidiEvent{t, e} = te;
match e
{
MidiEvent::NoteOff{ch: _, key: n, vel: _} | MidiEvent::NoteOn{ch: _, key: n, vel: 0} => {
if !notest[*n as usize] { ret = false; println!("Invalid note off {} @ {}", n, t); }
notest[*n as usize] = false;
},
MidiEvent::NoteOn{ch: _, key: n, vel: _} => {
if notest[*n as usize] { ret = false; println!("Unterminated note {} @ {}", n, t); }
notest[*n as usize] = true;
},
MidiEvent::MetaTrackName(s) => {
println!("Linting track {}", s);
},
_ => {}
}
}
if ! matches!(trk.last().unwrap_or(&TimedMidiEvent{t: 0, e: MidiEvent::NoteOn{ch: 0, key: 0, vel: 0}}).e , MidiEvent::MetaEndOfTrack)
{
println!("Missing end of track event");
ret = false;
}
ret
}
pub 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(())
}