Modernizing MML for 2022 (4) take best of MIDI 2.0 Capabilities into existing DAWs
part 1 / part 2 / part 3 / part 4 / part 5
When I created the old C#/.NET version of augene, it was nothing but an alternative SMF importer for Tracktion Engine (which offers MIDI import feature by itself). I indeed needed it to workaround an importer bug in tracktion_engine, but that is a minor issue (it was very important to people like me who loves irregular meters. It was not very fascinating feature either, as MIDI 1.0 capability is not very impressive.
But it is 2021 now, and there is a new standard for more expressive musical instructions: MIDI 2.0!
New MIDI 2.0 channel voice messages
Now that the new Kotlin version of mugene(-ng) supports MIDI 2.0, it is possible to achieve some advanced features, such as...
- Per-note expression (PNE) and per-note pitchbend (PNP). There are "registered" per-note controllers and custom "assignable" per-note controllers that each MIDI2 recipient supports.
- Note articulation. You can specify either standard attribute (microtonal pitch 7.9) or custom attribute type and data for each note message.
- 16-bit velocity, 256 channels, 32-bit data.
They are of course, not always supported in DAWs, or this time, Tracktion Engine in particular. Although Tracktion Engine supports MPE, so it is fairly doable to convert those PNEs and PNPs, but only pitchbend, timber and pressure (along with MPE's minimal-ish requirement).
Also, at this stage there is no room for note articulation in Tracktion Engine (most DAWs would be the same). They might be useful to replace keyswitches and/or velocity switches in some samplers (I use them a lot in my augene-ng sample on sfizz/VPO3 tracks) but that needs certain transmit from host to the instrument plugins in use, which needs engineering at host level. We may be able to patch Tracktion Engine, but that means the resulting music is not loadable on Tracktion Waveform, which is not what I want this time.
On data ranges, Tracktion Engine uses 16-bit data (or 15-bit for program changes, somehow) so we kind of benefit from MIDI 2.0, if we appreciate them much.
Data range wise, MIDI 2.0 comes with notable extensibility - it did expand data ranges to 32-bit, but it still does not have control change index extended beyond 127. It is to assure backward compatibility with MIDI 1.0 channel messages. I have seen some software and/or data formats extending the range of Control Change index to, say, 0..255 (for example some SFZ implementations seem to "support" that), but that never happened in MIDI 2.0. IMHO MIDI 2.0 makes better sense here. Even if you want to transmit any CC messages of index up to 255 to the app expecting those 8 bits are preserved, any transmits in the middle may drop that extra 1 bit (e.g. sanitizing value range by v & 7Fh
).
The MSB/LSB ranges for RPN/NRPN are likewise preserved - UMP specification describes that they should not be composed as set of CCs in MIDI 2.0 channel messages as there are now tailored status codes for RPN and NRPN, which comes with backward-compatible MSB/LSB ranges. If you take a close look at JUCE code for MIDI 2.0 UMP support, you can see juce::universal_midi_packets::Conversion.midi2ToMidi1DefaultTranslation()
is a static stateless function while there is no midi1ToMidi2DefaultTranslation()
- it's a dispatch()
function in the stateful Midi1ToMidi2DefaultTranslation
class... because it converts CCs for RPN, NRPN and DTE into RPN/NRPN messages and those unfinished states have be preserved. (I haven't come up with those converters in my ktmidi library which I should at some stage...)
Anyhow, thus there would not be any concern on dropping significant parts while converting from MIDI 2.0 UMPs for those message kinds to *.tracktionedit
.
proprietary music format until SMF 2.0 emerges
I mentioned this issue last time here, but let me repeat - MIDI 2.0 still lacks alternative format to SMF (Standard MIDI File). It is an interoperability problem as no DAWs would be able to support "import MIDI 2.0" feature. There is a DAW which claims to support MIDI 2.0: Multitrack Studio. It also seems to save music data in its own format (it does not support Linux so I haven't tried it).
The SMF file structure is not very complicated, and it is possible enough to apply the concept to MIDI 2.0 UMPs too, but there are certainly differences that we should care:
(1) Delta time representation: MIDI 2.0 UMP comes with JR (Jitter Reduction) Timestamp message which is essentially a delayed NOP. We could reuse this message to alter the delta time part in MIDI message (the 7-bit encoded variable length thing), but it is not very intuitive for a couple of reasons (the maximum delay a JR timestamp can represent is 2.0+ sec. at most, and we will have to iterate all the former tempo changes to calculate music note length aka step count ... which is annoying).
In ktmidi Midi2Music
serialization format, we have an option on how to interpret JR Timestamp messages like SMF delta time spec, that (a) it indicates an SMPTE value, or (b) it indicates steps. On mode (b), the JR Timestamp value is not compliant to MIDI 2.0 UMP specification, but rather stores the step count just like SMF did. And our Midi2Music
header part contains exactly the same master step count indicator like SMF did.
(2) Meta events: In SMF, FFh
is used to indicate a meta event, while anytihng else in SMF are interpreted as a MIDI message (which is either a channel message or a system message). FFh
was available because it is for System Reset and there is no point of having System Reset as a message in an SMF file.
When it comes to MIDI 2.0 UMP, however, it became impossible as system messages have their own message type (10h
) and they are only 32-bit integers. Thus stealing FFh
does not really help. To store strings of flexible lengths, we have to use either system exclusive (7bits or 8bits) or an MDS (Mixed Data Set) message. I ended up using sysex8 with with universal sysex and certain identifiers (like FFh
, FFh
, FFh
).