#![forbid(unsafe_code)]
use sim_lib_midi_core::{MetaEvent, MidiEvent, MidiPayload, RawBytes, SysExEvent};
use crate::{
SmfError, SmfFile, SmfFormat, SmfTrack, SmfWriteOptions, canonicalize_track, encode_vlq,
reader::{is_realtime_status, system_data_len},
};
pub(crate) const MAX_SMF_VLQ: u32 = 0x0fff_ffff;
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 file.tracks.is_empty()
|| (matches!(file.format, SmfFormat::SingleTrack) && file.tracks.len() != 1)
{
return Err(SmfError::FormatTrackMismatch);
}
let track_count = u16::try_from(file.tracks.len())
.map_err(|_| SmfError::TrackCountOutOfRange(file.tracks.len()))?;
let division = file.division.header_word();
let event_time_base = file.division.event_time_base();
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(&track_count.to_be_bytes());
out.extend_from_slice(&division.to_be_bytes());
for track in &file.tracks {
let body = write_track(track, event_time_base, options)?;
out.extend_from_slice(b"MTrk");
out.extend_from_slice(&checked_chunk_len(body.len())?.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(checked_delta(delta)?));
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(checked_payload_len(data.len())?));
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(checked_payload_len(data.len())?));
out.extend_from_slice(data);
Ok(None)
}
MidiPayload::Raw(raw) => {
write_raw(out, raw)?;
Ok(if is_realtime_status(raw.status) {
last_status
} else {
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![
masked_u8(us_per_quarter >> 16),
masked_u8(us_per_quarter >> 8),
masked_u8(*us_per_quarter),
],
),
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![
u8::from_ne_bytes(sharps_flats.to_ne_bytes()),
u8::from(*minor),
],
),
MetaEvent::Other(bucket) => (bucket.type_byte, bucket.data.clone()),
}
}
pub(crate) fn checked_delta(delta: i64) -> Result<u32, SmfError> {
if delta > i64::from(MAX_SMF_VLQ) {
return Err(SmfError::DeltaOutOfRange(delta));
}
u32::try_from(delta).map_err(|_| SmfError::NegativeDelta)
}
pub(crate) fn checked_chunk_len(len: usize) -> Result<u32, SmfError> {
u32::try_from(len).map_err(|_| SmfError::ChunkTooLarge(len))
}
pub(crate) fn checked_payload_len(len: usize) -> Result<u32, SmfError> {
let max = usize::try_from(MAX_SMF_VLQ).expect("SMF VLQ maximum fits usize");
if len > max {
return Err(SmfError::PayloadTooLarge(len));
}
u32::try_from(len).map_err(|_| SmfError::PayloadTooLarge(len))
}
fn masked_u8(value: u32) -> u8 {
u8::try_from(value & 0xff).expect("masked MIDI byte fits u8")
}
fn write_raw(out: &mut Vec<u8>, raw: &RawBytes) -> Result<(), SmfError> {
let expected = system_data_len(raw.status).ok_or(SmfError::InvalidSystemEvent {
offset: out.len(),
status: raw.status,
})?;
if raw.data.len() != expected {
return Err(SmfError::InvalidSystemEvent {
offset: out.len(),
status: raw.status,
});
}
if let Some(index) = raw.data.iter().position(|byte| *byte >= 0x80) {
return Err(SmfError::InvalidSystemEvent {
offset: out.len() + 1 + index,
status: raw.status,
});
}
out.push(raw.status);
out.extend_from_slice(&raw.data);
Ok(())
}