ump.h
Universal MIDI Packets: the MIDI 2.0 wire format.
#include <sonore/ump.h>- 1type
- 9other declarations
- Guides: MIDI and MPE
On this page
Design notes
What a UMP is
MIDI 1.0 is a byte stream where the length of a message is worked out from its status byte. MIDI 2.0 is a stream of 32-bit WORDS, and how many words a message occupies is read from the top nibble of the first one. That single change is what lets it carry a 32-bit controller value, sixteen groups of sixteen channels, and per-note controllers, without the receiver ever guessing.
This SDK already speaks the negotiation half of MIDI 2.0: midi_ci.h does discovery, profiles and property exchange. It could not speak the transport half at all, which is a strange place to stop -- a plugin could agree with a controller about what it supports and then have no way to receive it.
What is here, and what is deliberately not
Packet SIZING, MIDI 1.0 channel voice both directions, MIDI 2.0 channel voice down to MidiMessage, and SysEx7 assembly. That is the set a plugin actually meets: a host translating a MIDI 2.0 stream sends channel voice and SysEx, and everything else in the specification is stream configuration the host handles before the plugin sees anything.
Not here: Flex Data (score metadata), UMP Stream (endpoint discovery), and SysEx8 with the Mixed Data Set. Those are the 128-bit packets, and they live in ump128.h -- a plugin that is an endpoint in its own right needs them; one fed by a host that flattens MIDI 2.0 never sees them.
On losing precision, and saying so
MIDI 2.0 velocity is 16 bits and MidiMessage carries 7. Converting down LOSES information, and every function here that does it says so in its name or its comment. The alternative -- widening MidiMessage - would change the type every DSP in this SDK is written against, for a resolution no generated plugin has asked for yet. When one does, this is the file that records what was given up.
Types 1
Assembles a SysEx from the 64-bit packets that carry it.
UMP does not put a SysEx on the wire as bytes. It carries up to six data bytes per packet with a status nibble saying whether this is the whole thing, the start, the middle or the end -- so the reassembly is a different problem from the serial one in sysex.h, and shares only the rule that a partial message is never handed on.
The bytes carried are the PAYLOAD: the 0xF0 and 0xF7 that delimit a MIDI 1.0 SysEx are not transmitted. They are added back here, because every consumer in this SDK -- midi_ci.h included -- expects a complete MIDI 1.0 message.
One message in progress PER GROUP. UMP lets SysEx7 streams on different groups interleave packet by packet -- they are sixteen separate cables -- and a single buffer stitched two 13-byte messages on groups 0 and 1 into one 21-byte message made of parts of both.
A packet with a reserved status is ignored. One whose byte count is over six abandons the message open on its group, counted in dropped(): that message is missing the bytes the packet could not carry.
Functions 9
- fn
inline uint8_t messageType(uint32_t word0)A packet's message type, a kMt value, from its first word.ump::messageType · ump.h:71 - fn
inline uint8_t group(uint32_t word0)A packet's group, 0..15, from its first word.ump::group · ump.h:73 - fn
inline int packetWords(uint32_t word0)How many 32-bit words this packet occupies, read from its type.ump::packetWords · ump.h:86 - fn
inline uint32_t fromSystem(int status, int d1 = 0, int d2 = 0, uint8_t groupIndex = 0)A system message -- clock, start, stop -- as message type 1.ump::fromSystem · ump.h:101 - fn
inline uint32_t fromMidi1(const MidiMessage& m, uint8_t groupIndex = 0)One MidiMessage as a MIDI 1.0 channel voice packet -- or, for a system message (clock, start, song position...), as the system packet UMP carries it in. Type 2 is for channel statuses only: a clock written there, as it was, is a malformed channel message to a receiver, never a clock.ump::fromMidi1 · ump.h:113 - fn
inline bool toMidi1(uint32_t word0, MidiMessage* out, uint8_t* groupOut = nullptr)And back. Returns false for a packet that is not MIDI 1.0 channel voice, rather than inventing a message out of one that is not.ump::toMidi1 · ump.h:140 - fn
inline bool toSystem(uint32_t word0, MidiMessage* out, uint8_t* groupOut = nullptr)And a system packet back: false for one that is not a system common or realtime message. A type-1 packet carrying 0x93 came back as a note-on.ump::toSystem · ump.h:154 - fn
inline bool toMidi1Narrowing(const uint32_t* words, size_t count, MidiMessage* out, uint8_t* groupOut = nullptr)A MIDI 2.0 channel voice packet as a MidiMessage, LOSING RESOLUTION.ump::toMidi1Narrowing · ump.h:182 - fn
template <typename Fn> inline int toSysex7(const uint8_t* message, size_t bytes, Fn&& fn, uint8_t groupIndex = 0)A complete MIDI 1.0 SysEx as SysEx7 packets.ump::toSysex7 · ump.h:381