#![forbid(unsafe_code)]
use sim_lib_midi_core::{MetaEvent, MidiEvent, MidiPayload, RawBytes, SysExEvent};
use crate::{
SmfError, SmfFile, SmfFormat, SmfTrack, SmfWriteOptions, canonicalize_track, encode_vlq,
};
pub fn write_smf(file: &SmfFile) -> Result<Vec<u8>, SmfError> {
write_smf_with_options(file, SmfWriteOptions::default())
}
pub fn write_smf_with_options(
file: &SmfFile,
options: SmfWriteOptions,
) -> Result<Vec<u8>, SmfError> {
if matches!(file.format, SmfFormat::SingleTrack) && file.tracks.len() != 1 {
return Err(SmfError::FormatTrackMismatch);
}
let mut out = Vec::new();
out.extend_from_slice(b"MThd");
out.extend_from_slice(&6u32.to_be_bytes());
let format = match file.format {
SmfFormat::SingleTrack => 0u16,
SmfFormat::Simultaneous => 1u16,
SmfFormat::Independent => 2u16,
};
out.extend_from_slice(&format.to_be_bytes());
out.extend_from_slice(&(file.tracks.len() as u16).to_be_bytes());
out.extend_from_slice(&(file.tpq as u16).to_be_bytes());
for track in &file.tracks {
let body = write_track(track, file.tpq, options)?;
out.extend_from_slice(b"MTrk");
out.extend_from_slice(&(body.len() as u32).to_be_bytes());
out.extend_from_slice(&body);
}
Ok(out)
}
fn write_track(
track: &SmfTrack,
file_tpq: u32,
options: SmfWriteOptions,
) -> Result<Vec<u8>, SmfError> {
let mut track = track.clone();
canonicalize_track(&mut track, file_tpq);
let mut body = Vec::new();
let mut last_tick = 0i64;
let mut last_status: Option<u8> = None;
for event in &track.events {
let time = if event.time.tpq == file_tpq {
event.time
} else {
event
.time
.rebase(file_tpq)
.map_err(|_| SmfError::InexactEventTime)?
};
let delta = time.ticks - last_tick;
if delta < 0 {
return Err(SmfError::NegativeDelta);
}
body.extend_from_slice(&encode_vlq(delta as u32));
last_tick = time.ticks;
let status = write_payload(&mut body, event, options, last_status)?;
last_status = status;
}
Ok(body)
}
fn write_payload(
out: &mut Vec<u8>,
event: &MidiEvent,
options: SmfWriteOptions,
last_status: Option<u8>,
) -> Result<Option<u8>, SmfError> {
match &event.payload {
MidiPayload::Channel(message) => {
let (status, data) = encode_channel(message);
if !(options.running_status && last_status == Some(status)) {
out.push(status);
}
out.extend_from_slice(&data);
Ok(Some(status))
}
MidiPayload::Meta(event) => {
let (type_byte, data) = encode_meta(event);
out.push(0xff);
out.push(type_byte);
out.extend_from_slice(&encode_vlq(data.len() as u32));
out.extend_from_slice(&data);
Ok(None)
}
MidiPayload::SysEx(event) => {
let (status, data) = match event {
SysExEvent::F0 { data } => (0xf0, data.as_slice()),
SysExEvent::F7 { data } => (0xf7, data.as_slice()),
};
out.push(status);
out.extend_from_slice(&encode_vlq(data.len() as u32));
out.extend_from_slice(data);
Ok(None)
}
MidiPayload::Raw(raw) => {
write_raw(out, raw);
Ok(None)
}
}
}
use sim_lib_midi_core::wire::encode_channel;
fn encode_meta(event: &MetaEvent) -> (u8, Vec<u8>) {
match event {
MetaEvent::EndOfTrack => (0x2f, Vec::new()),
MetaEvent::Tempo { us_per_quarter } => (
0x51,
vec![
((us_per_quarter >> 16) & 0xff) as u8,
((us_per_quarter >> 8) & 0xff) as u8,
(us_per_quarter & 0xff) as u8,
],
),
MetaEvent::TimeSig {
num,
den_pow2,
clocks_per_click,
thirty_seconds_per_quarter,
} => (
0x58,
vec![
*num,
*den_pow2,
*clocks_per_click,
*thirty_seconds_per_quarter,
],
),
MetaEvent::KeySig {
sharps_flats,
minor,
} => (0x59, vec![*sharps_flats as u8, u8::from(*minor)]),
MetaEvent::Other(bucket) => (bucket.type_byte, bucket.data.clone()),
}
}
fn write_raw(out: &mut Vec<u8>, raw: &RawBytes) {
out.push(raw.status);
out.extend_from_slice(&raw.data);
}