Category

Core MIDI: MIDIPacket, MIDIPacketList, and Builders

The “traditional” structures for sending and receiving MIDI data in Core MIDI are MIDIPacket and MIDIPacketList.

Here is the original MIDIPacketList – which is now deprecated – but still works well. You use it like this (sort of) to send. 

To receive MIDI data, you are handed one in an inputPort’s MIDIReadProc

N.B. Besides MIDIPacketList there is the new MIDIEventPacket and its pals which are ready for MIDI 2. That’s another blog post!

MIDIPacket and MIDIPacketList

So, how do you fill the packet list. And what’s a packet?

Core MIDI defines a packet as the following struct. The timeStamp is in “host” time which is in nanoseconds. (q.v.) The actual MIDI data is in the tuple of 256 unsigned bytes. The tuple is always the same size, but you specify the number of bytes MIDISend and pals will pay attention to. If it’s a Note On message for example, you’d set the length field to 3, and then set the first 3 bytes of the tuple to the data you want.

The easiest way to make a packet is to use the init that takes no parameters. You really don’t want to make that tuple and then pass it in, do you? Ok, I see that one guy over there, but no, I don’t.

Of course midiStatus, data1, and data2 are UInt8 values for a MIDI message that you probably passed in. 

Now make a MIDIPacketList with your packet, then send it. See my post on setting up the MIDI Client, ports, and endpoints if that’s new to you.

Yay, a stuck note if that message was a note on! Would you like to send a note off too? Of course. How do you add that using this init for the list? It takes one packet. You want two packets.

Sending more than one packet.

The MIDI data that MIDISend sends are simply the messages separated by variable-length values for the timestamps in between.

This time I’m going to make an empty packetList. This needs to be followed by a special init function. Why? Because this is a C API and they need to do “stuff”. What stuff? I’ve not seen the source code, and this is not open source, so who knows? Just call it. 

The init func gives you the first empty packet. So we can safely assume that one item in the “stuff” is making a packet, frobbing around a bit, then giving it to you. Now what?

Let’s say your sending func passes in channel, pitch, and velocity values. Make local variables like this for note messages.

Then you use MIDIPacketListAdd to fill that empty packet with your data like this. Each call will return the packet that you can use in another call. 

Yeah, a bit weird. But not as weird as Objective-C. The packetList was simply cast from a byte buffer. That would explain those weird params to the add func.

If you’d like to see not just weird code, but psychotic, take a look at the Swift examples on Snack Overblown.

Packet Timestamp

If you assign 0 (zero) to the packet’s times stamp field, that means “send it right away”. But what if you want to send one packet, then another a while later as is necessary for the Note on/Note off messages.

My opinion is that in real life, you would either use a Sequencer such as the Audio Toolbox MusicPlayer – or write one yourself – or use MIDISend in response to UI events. Button press = note on, Button release = note off for example. But if you want those gestures to kick off multiple packets, you need to grok this timing thing.

The time stamps are nanoseconds in “host time”. In iOS, you would use mach_absolute_time() to get the current host time. In macOS, there are a few more hoops to jump through.

Here’s a first cut at timing.

Logical. But what I hear – and my MIDI monitor confirms – is a very short note. What’s gives?

I set the note on time to the host time “right now”. But that “right now” is in the past when the packet is sent!

So, add a delay to the note on time! How much? Not too much, otherwise the user will press a button, and then be waiting for the note on! Not too little, or you’ll get the short note thing. This works on my macBook:

Most likely, the best way to set the timestamps would be to iterate through the packet list to set each packet’s timestamp immediately before calling MIDISend to decrease the “in-between” time. More on iterating a packet list below.

I dislike hacks like this. If you know a better way, let me know!

Multiple Messages in one MIDIPacket

Why would you want to put multiple MIDI messages inside one MIDIPacket? The clue that this is possible is that huge tuple for 1…3 byte messages (besides Sysex messages). The other clue is that each message in the packet have the same time stamp. Yeah, a chord!

For a C Major chord (middle c root, mezzo forte), we need to make the packet’s data tuple look like this:

0x90, 0x3C, 0x40, 0x90, 0x40, 0x40, 0x90, 0x43, 0x40

Just the bytes for the midi messages! And of course, you’ll need a note off version of this packet.

So, how do you get all those bytes in there apart from filling a 256 byte tuple like the following?

Yikes!

Builders

There are hacky pointer frobs I’ve used to fill in tuples. Apple might have taken my Radars to heart (unlikely), because they have introduced Builders in several Core MIDI structures in iOS 14. The Builders are embedded in their structures. 

Here’s how to use the builder for MIDIPacket

One unknown (to me) is that they specify the time stamp as an Int and not MIDITimeStamp (UInt64) which is used everyplace else.

Groovy. You have your data in a builder now. 

Rumination: Why doesn’t append use a fluent interface? It’s not hard. SwiftUI uses it all over the place.

How do you get the MIDIPacket out of it? builder.getPacket() ? Nah. Too easy.

So, you need to use withUnsafePointer to extract it. The builder that you’e using to avoid unsafe pointers makes you use them anyway. Convenient.

The func signature specifies a Result as the return type. You can include an Error if any validation you wish to perform fails. 

MIDIPacketList.Builder

1MIDIPacketList.Builder works in much the same way as the packet builder.

I have no idea what byteSize means. There is no documentation. (Surprise!). The name could mean “number of bits in a byte” or “how many bytes in the packet’s data field”. I take it to mean the latter. Remember the Objective-C buffer casting thing? If I’m wrong (not a new thing!), let me know. This works, at least.

Retrieving the result is done just like MIDIPacket.

Iterators

Yay, they addressed the need for updated iterators. Many people have “rolled their own” way to do this. AudioKit has them for example.

If you wish to iterate the data tuple in a MIDIPacket, you need to get an UnsafePointer to the packet. Then you can iterate using the sequence MIDIPacket.ByteSequence. The packet’s ByteSequence is obtained from the unsafe pointer via its sequence() function.

There is also the MIDIPacketList.UnsafeSequence to iterate over MIDIPacketList’s MIDIPackets.

This MIDIReadProc shows the use of two of these iterations. Parsing the MIDI data is “left as an exercise for the reader.”

Summary

MIDIPacket and its pal MIDIPacketList is old skool, but it has had useful updates recently. 

MIDIEvent is the new frobbery, so that’s in my next blog post.

3 thoughts on “Core MIDI: MIDIPacket, MIDIPacketList, and Builders”

  1. “MIDIEvent is the new frobbery, so that’s in my next blog post.”
    …and there’s no link and next post is about something else 🙁

  2. MIDIPacket.Builder lets you set maximumNumberMIDIBytes as long as they don’t exceed 256?
    /// Copying the MIDIPacket value that this pointer references will lead to
    /// undefined behavior. (The MIDIPacket type declares space for exactly 256 bytes, but
    /// this builder allocates exactly as much storage as specified in its initializer.
    /// This means that if the builder was created with a maximum number of bytes other than 256,
    /// then copying out the value of the packet by dereferencing the pointer will result in a
    /// corrupt/truncated packet and subsequent out of bounds memory accesses.)
    public func withUnsafePointer(_ body: (UnsafePointer) -> Result) -> Result

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.