Skip to main content

sim_lib_stream_file/
midi.rs

1use std::path::Path;
2
3use sim_kernel::{Error, Result};
4use sim_lib_midi_core::MemoryMidiSource;
5use sim_lib_midi_smf::{SmfFile, SmfFormat, SmfTrack, read_smf, write_smf};
6use sim_lib_stream_core::{StreamMetadata, StreamValue};
7use sim_lib_stream_midi::{midi_source_to_stream, midi_stream_to_sink};
8
9use crate::effect_io::{read_file_with_effect, write_file_with_effect};
10
11/// Reads a Standard MIDI File from `path` and opens it as a MIDI stream.
12///
13/// The read is gated by the filesystem read capability and recorded as a
14/// KERNEL 6 filesystem effect. Events are packetized at most `max_events` per
15/// packet.
16pub fn read_smf_stream(
17    cx: &mut sim_kernel::Cx,
18    path: impl AsRef<Path>,
19    max_events: usize,
20    metadata: StreamMetadata,
21) -> Result<StreamValue> {
22    let bytes = read_file_with_effect(cx, path)?;
23    smf_bytes_to_stream(&bytes, max_events, metadata)
24}
25
26/// Parses Standard MIDI File bytes and opens them as a MIDI stream.
27pub fn smf_bytes_to_stream(
28    bytes: &[u8],
29    max_events: usize,
30    metadata: StreamMetadata,
31) -> Result<StreamValue> {
32    let file = read_smf(bytes).map_err(|err| Error::Eval(format!("malformed SMF file: {err}")))?;
33    smf_file_to_stream(&file, max_events, metadata)
34}
35
36/// Opens an already-parsed Standard MIDI File as a MIDI stream.
37///
38/// Merges every track into a single ordered timeline and packetizes it at most
39/// `max_events` events per packet.
40pub fn smf_file_to_stream(
41    file: &SmfFile,
42    max_events: usize,
43    metadata: StreamMetadata,
44) -> Result<StreamValue> {
45    let events = file
46        .merged_events()
47        .into_iter()
48        .map(|tracked| tracked.event)
49        .collect();
50    let mut source = MemoryMidiSource::new(file.tpq, events);
51    midi_source_to_stream(&mut source, max_events, metadata)
52}
53
54/// Drains a MIDI stream and writes it to `path` as a Standard MIDI File.
55///
56/// The write is gated by the filesystem write capability and recorded as a
57/// KERNEL 6 filesystem effect. Returns the number of events written.
58pub fn write_smf_stream(
59    cx: &mut sim_kernel::Cx,
60    path: impl AsRef<Path>,
61    stream: &StreamValue,
62    tpq: u32,
63) -> Result<usize> {
64    let (file, count) = stream_to_smf_file(stream, tpq)?;
65    let bytes = write_smf(&file).map_err(|err| Error::Eval(format!("cannot write SMF: {err}")))?;
66    write_file_with_effect(cx, path, bytes)?;
67    Ok(count)
68}
69
70/// Drains a MIDI stream and encodes it as Standard MIDI File bytes.
71///
72/// Returns the encoded bytes together with the number of events written.
73pub fn stream_to_smf_bytes(stream: &StreamValue, tpq: u32) -> Result<(Vec<u8>, usize)> {
74    let (file, count) = stream_to_smf_file(stream, tpq)?;
75    let bytes = write_smf(&file).map_err(|err| Error::Eval(format!("cannot write SMF: {err}")))?;
76    Ok((bytes, count))
77}
78
79/// Drains a MIDI stream into a single-track [`SmfFile`] with `tpq` resolution.
80///
81/// Returns the assembled file and the number of events written. Errors when
82/// `tpq` exceeds the Standard MIDI File range.
83pub fn stream_to_smf_file(stream: &StreamValue, tpq: u32) -> Result<(SmfFile, usize)> {
84    if u16::try_from(tpq).is_err() {
85        return Err(Error::Eval(format!(
86            "SMF TPQ {tpq} exceeds the Standard MIDI File range"
87        )));
88    }
89    let mut sink = sim_lib_midi_core::MemoryMidiSink::new(tpq);
90    let count = midi_stream_to_sink(stream, &mut sink)?;
91    let file = SmfFile {
92        format: SmfFormat::SingleTrack,
93        tpq,
94        tracks: vec![SmfTrack {
95            events: sink.events().to_vec(),
96        }],
97    };
98    Ok((file, count))
99}