I've started making a new platform as the foundation of a music project I'm working on: https://gitlab.com/JanCVanB/roc-on
Ideally, I could have my Roc main
function return a Rust struct representation of a MIDI file (a multi-tracked series of MIDI messages), but I expect that to be very complicated to implement, due to memory mapping / boxing / whatever else I haven't learned yet about advanced platform interfaces.
I've started by cheating and just writing/reading a JSON to/from disk as my app-platform interface. However, doing that with serde_json
and roc-json
seems to not naively scale, currently because I can't figure out how to map tags to enums.
I'm open to any input on how to best proceed! What's the most straightforward way today for passing complex data to a platform? Will I be able to avoid implementing the MIDI bytes spec from scratch in a Roc library? Do you recommend avoiding midly
(over the similar midi-msg
crate) because of its exotic u7
s and u15
s, or is that a useful exercise for Roc to face?
Cool. I think you could have Roc return one of these... or at least something close and then write something like impl From<roc::Midi> for midly::Smf ...
So you can do
let midi : midly::Smf = rocMain().into();
What can you do with a midly once you have it? does it integrate well with other libraries?
I'm currently accepting that for my use cases it makes sense to write my own midly consumer to iterate the message tracks and populate the fundsp sequencers with timed events (and possibly also forward messages to external hardware).
The big benefit of using a (hopefully) complete/well-maintained MIDI representation crate is that I can use it as the common protocol between a variety of senders and receivers that I may plug into this system.
Do you know of any complete examples I can adapt for the From<roc::ComplexDeeplyNestedStructRecordWithEnumTags>
part?
I'm expecting that will require a Roc library with a record type alias that looks like Smf (which I hope can leverage very-idiomatic Roc tags) and a pile of for loops in the Rust impl
.
@JanCVanB I got carried away and build a minimal e2e demo that calls into roc and returns a midly:Smf
struct. It doesn't do much with it, just debug prints to stdio for now. But all the parts are there and look to be working ok.
https://github.com/lukewilliamboswell/roc-on-simple
app [main] { pf: platform "../platform/main.roc" }
main = \{} -> {
header: {
format: SingleTrack,
timing: Metrical 10,
},
tracks: [
[
{ delta: 1, kind: Escape [] },
{ delta: 2, kind: Midi { channel: 1, message: NoteOn { key: 1, vel: 2 } } },
{ delta: 3, kind: Meta EndOfTrack },
],
],
}
$ cargo run
Compiling roc-on-simple v0.1.0 (/Users/luke/Documents/GitHub/roc-on-simple)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.36s
Running `target/debug/roc-on-simple`
Smf { header: Header { format: SingleTrack, timing: Metrical(u15(10)) }, tracks: [[TrackEvent { delta: u28(1), kind: Escape([]) }, TrackEvent { delta: u28(2), kind: Midi { channel: u4(1), message: NoteOn { key: u7(1), vel: u7(2) } } }, TrackEvent { delta: u28(3), kind: Meta(EndOfTrack) }]] }
Note, it's dynamically linking the roc app... I need to dig in and fix our --no-link
build pipeline if you want to make a static executable instead.
Luke Boswell said:
Note, it's dynamically linking the roc app... I need to dig in and fix our
--no-link
build pipeline if you want to make a static executable instead.
Done :smiley:
OH WOW LUKE YOU ROC
Thank you for getting carried away, I'm excited to dig into this today :heart_eyes:
Last updated: Jul 05 2025 at 12:14 UTC