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 } #[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), 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; pub struct MidiFile { pub div: u16, pub tracks: Vec } fn write_u16be(f: &mut W, v: u16) -> io::Result<()> where W: Write { let bytes = v.to_be_bytes(); f.write_all(&bytes) } fn write_u32be(f: &mut W, v: u32) -> io::Result<()> where W: Write { let bytes = v.to_be_bytes(); f.write_all(&bytes) } fn write_varlen(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 = 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 W, re: &RawMidiEvent, runst: u8) -> io::Result where W: Write { let mut buf: Vec = 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(f: &mut W, trk: &MidiTrack) -> io::Result<()> where W: Write { let header = "MTrk".as_bytes(); f.write_all(header)?; let mut buf: Vec = 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(()) }