Add full support for realtime MIDI streams
Closed this issue · 5 comments
Raw MIDI streams work differently, with different kinds of events and whatnot.
This is an opportunity to simplify the API to just 2 entry points: SMF parsing and raw MIDI event parsing. Something similar should be done with the write API.
You mean to support for example VST plugins that process midi input and send midi output? Yeah, that would be nice :)
But what do you mean by "different kinds of events and whatnot"? It's a subset of the events that occur in a midi file (no meta events).
I haven't played around with VST plugins, but if they read and write raw MIDI data just like MIDI devices do, then yes. Currently the library can be used for these kind of tasks, but it will error out on System Common/System Realtime events, and will attempt to parse Reset events as meta messages.
This is because SMF events are not normal MIDI events, they are just similar.
In the SMF spec, events are defined like this:
<event> = <MIDI event> | <sysex event> | <meta-event>
While I can't seem to find an analogous definition for generic MIDI messages, they would be defined more like this:
<event> = <MIDI event> | <system common event> | <system realtime event>
SysEx events here would be defined both by an F0
system common event marking the start as well as an F7
system common event marking the finish of the SysEx
event.
Additionally, system realtime events require special handling, since they can appear at any position within a stream, event inside the data bytes of another event or between the status and data bytes.
They are different beasts, but I've parsed MIDI streams using midly
before and it mostly "just works". Doesn't mean it could be better though.
Ah right.. Btw, if you find a spec for this, I'd be interested, because I wrote some midi-processing VSTs and they currently aren't dealing with system realtime events..
So, the realtime events can literally occur between any 2 bytes in the midi stream?
And they are uniquely identified by encountering a byte that has the 5 most-significant bits set (b & 0xF8 == 0xF8
), right?
http://www.music.mcgill.ca/~ich/classes/mumt306/StandardMIDIfileformat.html#BMA1_1
Support for realtime streams was added in 0.5
.
So, the realtime events can literally occur between any 2 bytes in the midi stream?
And they are uniquely identified by encountering a byte that has the 5 most-significant bits set (b & 0xF8 == 0xF8), right?
Yes, but most MIDI APIs filter them out before handing them to the user (it's a pain to be wary at all times of system realtime events). I'm not sure whether VST filters them out or not, but as a quick rule-of-thumb, if you're handed MIDI events in a self-contained array, then realtime events have already been handled for you. If instead MIDI comes in through a non-delimited undefinitely long stream, then most likely you'll have to deal with them.
And about the specification, there's the official Complete MIDI 1.0 Detailed Specification, available for free after registering at midi.org, or you can just search the name up on Google and download it from here, for example.
For "live" MIDI messages, as seen in a 5-pin MIDI connector, there is a nice graph in page 35 indicating the different message types, and a summary of status bytes in page 100. Quoting that page:
00 - 7F: Data bytes
80 - EF: Channel message (MidiMessage in midly
, consumes a set amount of bytes)
F0: System exclusive (consumes any number of data bytes until a byte with its top bit set is received)
F1 - F7: System common (similarly to "system exclusive", consumes until a byte with its top bit set is received)
F8 - FF: System realtime (single byte)
For .mid
files, in page 135 there's a line that states: <event> = <MIDI event> | <sysex event> | <meta-event>
, although there's no nice short summary.
In that same page it's clarified that SysEx events include both actual SysEx events (indicated by byte F0) and "escape events" (indicated by byte F7). Meta events are indicated by the FF status byte (which would correspond to "System Reset" for live events). Channel messages are represented the same as live messages. Summarizing:
00 - 7F: Data bytes
80 - EF: Channel message (MidiMessage in midly, consumes a set amount of bytes)
F0: System exclusive (followed by a length indicating how many bytes are consumed)
F1 - F6: Undefined
F7: Escape (followed by a length indicating how many bytes to consume)
F8 - FE: Undefined
FF: Meta event (followed by a type byte and a length indicating the amount of bytes to consume)
As you can see, in the range 00 - EF (the bulk of a MIDI file and stream) both match, the last 16 possible status bytes are completely incompatible. The reason it "just works" when you parse a live stream using an SMF parser is because most MIDI streams don't contain system * messages. However, the right thing™️ is to interpret them differently.
Sorry for the wall of text, hope it helps!