ump128.h
The 128-bit Universal MIDI Packets ump.h left out: Flex Data (message type 0xD), UMP Stream (0xF), and SysEx8 with its Mixed Data Set (0x5).
#include <sonore/ump128.h>- 14types
- 45other declarations
- Guides: MIDI and MPE
On this page
Design notes
Why these three, and why together
ump.h stopped at what a host bridging a MIDI 2.0 stream hands a plugin: channel voice and SysEx7. That is still the common case. But a plugin that SPEAKS UMP -- an endpoint in its own right, which is where MIDI 2.0 goes once hosts pass packets through instead of flattening them -- meets all three of these the moment it is connected:
- UMP Stream is how an endpoint introduces itself. Before anything else crosses, the other side asks "who are you, which protocol, how many function blocks", and a plugin that cannot answer is not discovered.
- Flex Data carries what MIDI 1.0 put in a Standard MIDI File's meta events: tempo, time and key signature, chord names, lyrics, the song's name. A clip launched from a MIDI 2.0 sequencer brings them inline.
- SysEx8 is SysEx without the seven-bit tax, and the Mixed Data Set is its bulk-transfer sibling. A device that has both will use them.
They are one file because they share one shape: four words, a two-bit or four-bit "form" saying whether this packet is a whole message, its start, its middle or its end, and bytes packed most-significant-first into the words. The reassembly problem is the same for all three, so it is written once (detail::Reassembly) and each message type supplies only its key and where its bytes sit.
Bit positions
Every layout here is the field table of the UMP specification (M2-104-UM, version 1.1), and each encoder states the word it builds in that table's own terms, MSB first. Packets are arrays of host-order 32-bit words, as in ump.h: byte order is the transport's business, not the message's.
Reassembly, and what it refuses
The rule from ump.h's SysEx7 assembler holds: a partial message is never handed on. A start inside an open message abandons the old one, a continue or end with nothing open is an orphan from a stream joined half way, and a message longer than its buffer is DROPPED rather than truncated -- a truncated SysEx is not a smaller message, it is a different one. Every refusal is counted, because "it silently did nothing" is the failure an assembler is otherwise indistinguishable from.
Interleaving is legal and handled: SysEx8 carries a stream ID precisely so several messages can be in flight on one group, Flex Data text on different channels or of different kinds is independent, and a function block's name can arrive between two halves of the endpoint's. Each assembler keys an open message by everything that distinguishes it, in a fixed pool of slots, so nothing here allocates except the Mixed Data Set -- see that class for why it is the exception.
Types 14
Word 0 of a Flex Data packet, taken apart.
A Flex Data time signature, as setTimeSignature() writes it and decodeSetTimeSignature() fills it. The denominator is held as its power of two, the way the packet carries it; denominator() gives the number a score shows. Defaults to 4/4.
A Flex Data metronome setting, field for field as the packet carries it: the primary click's spacing in MIDI clocks (24 a quarter note), the three bar-accent bytes and the two subdivision click counts, each carried as it is.
A Flex Data key signature: sharps (positive) or flats (negative), and the tonic as one of the kNote letters -- kNoteUnknown when the sender does not say. Both travel in four-bit fields, so a value outside them is cut to four bits on the way out.
One alteration of a chord -- add, subtract, raise or lower a scale degree -- as the spec's two nibbles. Type 0 is an empty slot.
A Flex Data chord name, for a lead sheet or a chord track: the chord's tonic and type with up to four alterations, and a bass chord with up to two. Codes are the spec's tables, carried as numbers (see above); notes are kNote letters with a signed sharps/flats count. setChordName() addresses a channel by default, unlike the other setup messages.
Reassembles Flex Data text -- metadata and lyrics -- from its packets.
Keyed by group, address, channel, bank and status: a lyric line on channel 1 and the clip's name on the group are two messages, and may arrive packet by packet in alternation. Setup messages (tempo and the rest) are single packets with nothing to assemble; push() ignores them, so a caller can hand it every Flex Data packet without sorting first.
The zero padding is stripped per packet: a packet's text ends at its first zero byte, which UTF-8 never contains as part of a character.
What an endpoint says about itself in an Endpoint Info Notification -- the answer to Endpoint Discovery: the UMP version it speaks, how many function blocks it has and whether they can change, which protocols it can be switched to, and its jitter-reduction timestamp support.
The protocol a stream runs (kProtocolMidi2 or the MIDI 1.0 one) and whether jitter-reduction timestamps go each way. The same fields serve the request that asks for a configuration and the notification that confirms one.
One function block, as a Function Block Info Notification describes it: its number, whether it is active, the groups it spans (firstGroup and numGroups, groups counted from 0), its direction, UI hint and MIDI 1.0 role (the kBlock constants), and its MIDI-CI and SysEx8 capacity. Fields narrower than a byte are cut to their width on the way out.
Reassembles the three name notifications.
Keyed by status and, for a function block's name, by the block: an endpoint answering a discovery sends its own name and every block's, and a sender that interleaves them is within its rights.
Reassembles SysEx8 from its packets, several streams at once.
A packet whose byte count is 0 (not even the stream ID) or above 14 is malformed. If it claimed to continue or end a message that is open, that message is now missing bytes, so it is abandoned rather than finished with a hole in it.
A Mixed Data Set chunk header, field for field: which group and data set (mdsId) it belongs to, how many payload bytes the chunk carries, its place among the set's chunks, and the manufacturer, device and sub IDs that say what the data is. Written by mixedDataSetHeader(), and filled for each completed chunk by MixedDataSetAssembler.
Reassembles Mixed Data Set chunks, one per group and MDS ID at a time.
The one assembler here that allocates. A chunk may be 65535 bytes and 256 may be open at once; a fixed pool for that is sixteen megabytes held against data that is almost never sent. So each open chunk reserves what its header declares -- once, on the header, never while payloads arrive -- and a Mixed Data Set is not something to assemble on the audio thread. It is a bulk transfer: a sample, a firmware image, a file.
Delivered per CHUNK. Putting the chunks of a set back together is the caller's, because only the caller knows whether chunk numbers it never saw mean a gap or a sender that numbers from somewhere else.
Functions 44
- fn
template <typename Fn> inline size_t forEachPacket(const uint32_t* words, size_t count, Fn&& fn)Split a run of words into packets, by the size each one's type declares.ump::forEachPacket · ump128.h:91 - fn
inline uint32_t flexWord0(const FlexHeader& h)Word 0 of a Flex Data packet fromump::flexWord0 · ump128.h:357h; each field is cut to its width. - fn
inline bool flexHeaderOf(const uint32_t* words, size_t count, FlexHeader* out)Word 0 taken apart. False for anything that is not a whole Flex Data packet.ump::flexHeaderOf · ump128.h:365 - fn
inline Packet128 flexDataFrom(const FlexHeader& h, uint32_t w1, uint32_t w2 = 0, uint32_t w3 = 0)A Flex Data packet from its header and three data words.ump::flexDataFrom · ump128.h:377 - fn
inline uint32_t tempoFromBpm(double bpm)120 BPM is 50,000,000. Zero for a tempo that is not positive.ump::tempoFromBpm · ump128.h:416 - fn
inline double bpmFromTempo(uint32_t tenNanosecondsPerQuarter)The reverse of tempoFromBpm(): 50,000,000 is 120 BPM. 0 for 0.ump::bpmFromTempo · ump128.h:423 - fn
inline Packet128 setTempo(uint32_t tenNanosecondsPerQuarter, uint8_t groupIndex = 0, uint8_t address = kFlexAddressGroup, uint8_t channel = 0)A Set Tempo packet, in 10 ns units per quarter note (tempoFromBpm()), addressed to the group by default.ump::setTempo · ump128.h:429 - fn
inline bool decodeSetTempo(const uint32_t* words, size_t count, uint32_t* tenNanoseconds, FlexHeader* header = nullptr)The tempo out of a whole Set Tempo packet, in 10 ns units per quarter note, and its header whenump::decodeSetTempo · ump128.h:437headeris given. False for anything else. - fn
inline Packet128 setTimeSignature(const TimeSignature& ts, uint8_t groupIndex = 0, uint8_t address = kFlexAddressGroup, uint8_t channel = 0)A Set Time Signature packet, addressed to the group by default.ump::setTimeSignature · ump128.h:469 - fn
inline bool decodeSetTimeSignature(const uint32_t* words, size_t count, TimeSignature* out, FlexHeader* header = nullptr)The signature out of a whole Set Time Signature packet, and its header whenump::decodeSetTimeSignature · ump128.h:478headeris given. False for anything else. - fn
inline Packet128 setMetronome(const Metronome& m, uint8_t groupIndex = 0, uint8_t address = kFlexAddressGroup, uint8_t channel = 0)A Set Metronome packet, addressed to the group by default.ump::setMetronome · ump128.h:505 - fn
inline bool decodeSetMetronome(const uint32_t* words, size_t count, Metronome* out, FlexHeader* header = nullptr)The setting out of a whole Set Metronome packet, and its header whenump::decodeSetMetronome · ump128.h:517headeris given. False for anything else. - fn
inline Packet128 setKeySignature(const KeySignature& k, uint8_t groupIndex = 0, uint8_t address = kFlexAddressGroup, uint8_t channel = 0)A Set Key Signature packet, addressed to the group by default.ump::setKeySignature · ump128.h:546 - fn
inline bool decodeSetKeySignature(const uint32_t* words, size_t count, KeySignature* out, FlexHeader* header = nullptr)The key out of a whole Set Key Signature packet, and its header whenump::decodeSetKeySignature · ump128.h:554headeris given. False for anything else. - fn
inline Packet128 setChordName(const ChordName& c, uint8_t groupIndex = 0, uint8_t address = kFlexAddressChannel, uint8_t channel = 0)A Set Chord Name packet, addressed to a CHANNEL by default.ump::setChordName · ump128.h:612 - fn
inline bool decodeSetChordName(const uint32_t* words, size_t count, ChordName* out, FlexHeader* header = nullptr)The chord out of a whole Set Chord Name packet, and its header whenump::decodeSetChordName · ump128.h:630headeris given. False for anything else. - fn
template <typename Fn> inline int toFlexText(uint8_t bank, uint8_t status, const char* utf8, size_t bytes, Fn&& fn, uint8_t groupIndex = 0, uint8_t address = kFlexAddressGroup, uint8_t channel = 0)Text as Flex Data packets.ump::toFlexText · ump128.h:662fn(const uint32_t* words, size_t count)per packet; returns how many. Empty text is one complete packet of padding -- "the song's name is now nothing" is a message, not an absence of one. - fn
inline uint16_t streamStatus(uint32_t word0)A UMP Stream packet's 10-bit status, a kStream value, from its word 0.ump::streamStatus · ump128.h:817 - fn
inline uint8_t streamForm(uint32_t word0)A UMP Stream packet's form, kFormComplete .. kFormEnd, from its word 0.ump::streamForm · ump128.h:819 - fn
inline uint32_t streamWord0(uint8_t form, uint16_t status, uint16_t low16)Word 0 of a UMP Stream packet: message type 0xF,ump::streamWord0 · ump128.h:823form,statusand the message's own low 16 bits. - fn
inline Packet128 endpointDiscovery(uint8_t filter, uint8_t versionMajor = 1, uint8_t versionMinor = 1)Endpoint Discovery: ask an endpoint for whatump::endpointDiscovery · ump128.h:841filter(kDiscover flags, ORed) names, saying this side speaks UMPversionMajor.versionMinor. - fn
inline bool decodeEndpointDiscovery(const uint32_t* words, size_t count, uint8_t* filter, uint8_t* versionMajor = nullptr, uint8_t* versionMinor = nullptr)The filter, and the UMP version when asked, out of a whole Endpoint Discovery packet. False for anything else.ump::decodeEndpointDiscovery · ump128.h:850 - fn
inline Packet128 endpointInfoNotification(const EndpointInfo& info)An Endpoint Info Notification: the answer to kDiscoverEndpointInfo.ump::endpointInfoNotification · ump128.h:882 - fn
inline bool decodeEndpointInfo(const uint32_t* words, size_t count, EndpointInfo* out)ump::decodeEndpointInfo · ump128.h:895outfilled from a whole Endpoint Info Notification. False for anything else. - fn
inline Packet128 deviceIdentityNotification(const midici::DeviceIdentity& id)A Device Identity Notification: manufacturer, family, model and version fromump::deviceIdentityNotification · ump128.h:919id, each byte cut to seven bits. - fn
inline bool decodeDeviceIdentity(const uint32_t* words, size_t count, midici::DeviceIdentity* out)The manufacturer, family, model and version fields ofump::decodeDeviceIdentity · ump128.h:934outfilled from a whole Device Identity Notification; the rest ofoutis left as it was. False for anything else. - fn
inline Packet128 streamConfigurationRequest(const StreamConfiguration& c)A Stream Configuration Request: ask the endpoint to switch toump::streamConfigurationRequest · ump128.h:969c. - fn
inline Packet128 streamConfigurationNotification(const StreamConfiguration& c)A Stream Configuration Notification: say this side now runsump::streamConfigurationNotification · ump128.h:973c. - fn
inline bool decodeStreamConfiguration(const uint32_t* words, size_t count, StreamConfiguration* out, bool* isRequest = nullptr)Either one;ump::decodeStreamConfiguration · ump128.h:978isRequestsays which arrived. - fn
inline Packet128 functionBlockDiscovery(uint8_t block, uint8_t filter)Function Block Discovery: ask about blockump::functionBlockDiscovery · ump128.h:998block(0..31, or kAllFunctionBlocks) for whatfilter(kDiscoverFunctionBlock flags) names. - fn
inline bool decodeFunctionBlockDiscovery(const uint32_t* words, size_t count, uint8_t* block, uint8_t* filter)The block and filter out of a whole Function Block Discovery packet. False for anything else.ump::decodeFunctionBlockDiscovery · ump128.h:1006 - fn
inline Packet128 functionBlockInfoNotification(const FunctionBlockInfo& b)A Function Block Info Notification describingump::functionBlockInfoNotification · ump128.h:1033b. - fn
inline bool decodeFunctionBlockInfo(const uint32_t* words, size_t count, FunctionBlockInfo* out)ump::decodeFunctionBlockInfo · ump128.h:1045outfilled from a whole Function Block Info Notification. False for anything else. - fn
inline Packet128 startOfClip()A Start of Clip packet.ump::startOfClip · ump128.h:1064 - fn
inline Packet128 endOfClip()An End of Clip packet.ump::endOfClip · ump128.h:1068 - fn
inline bool isStartOfClip(const uint32_t* words, size_t count)True for a whole Start of Clip packet.ump::isStartOfClip · ump128.h:1072 - fn
inline bool isEndOfClip(const uint32_t* words, size_t count)True for a whole End of Clip packet.ump::isEndOfClip · ump128.h:1076 - fn
template <typename Fn> inline int endpointNameNotification(const char* utf8, size_t bytes, Fn&& fn)The endpoint's name, at most kEndpointNameMax bytes -- longer is cut at a character boundary, not mid-character. Returns the packet count.ump::endpointNameNotification · ump128.h:1116 - fn
template <typename Fn> inline int productInstanceIdNotification(const char* utf8, size_t bytes, Fn&& fn)A string that tells two identical devices apart -- a serial number.ump::productInstanceIdNotification · ump128.h:1122 - fn
template <typename Fn> inline int functionBlockNameNotification(uint8_t block, const char* utf8, size_t bytes, Fn&& fn)Blockump::functionBlockNameNotification · ump128.h:1129block's name (cut to seven bits), at most kFunctionBlockNameMax bytes, cut at a character boundary. Returns the packet count. - fn
template <typename Fn> inline int toSysex8(const uint8_t* data, size_t bytes, Fn&& fn, uint8_t streamId = 0, uint8_t groupIndex = 0)Bytes as SysEx8 packets.ump::toSysex8 · ump128.h:1225datais the payload -- manufacturer ID onward; there is no framing to strip.fn(const uint32_t* words, size_t count)per packet; returns how many. An empty message is one complete packet carrying only its stream ID. - fn
inline Packet128 mixedDataSetHeader(const MixedDataSetHeader& h)A Mixed Data Set chunk header fromump::mixedDataSetHeader · ump128.h:1340h, as it is: toMixedDataSetChunk() sets validBytes for a caller sending a whole chunk. - fn
inline bool decodeMixedDataSetHeader(const uint32_t* words, size_t count, MixedDataSetHeader* out)ump::decodeMixedDataSetHeader · ump128.h:1351outfilled from a Mixed Data Set header packet. False for anything else. - fn
template <typename Fn> inline int toMixedDataSetChunk(MixedDataSetHeader header, const uint8_t* data, size_t bytes, Fn&& fn)One chunk: its header, then its payloads. The header's validBytes is set fromump::toMixedDataSetChunk · ump128.h:1374bytes; more than 65535 is refused (0 packets), because the field cannot say it -- a longer set is several chunks, and which way to split it is the sender's decision.
Aliases 1
- using
using Packet128 = std::array<uint32_t, 4>One 128-bit packet, word 0 first.ump::Packet128 · ump128.h:70