Blog

We write about software development and our products

Historically, if you wanted to receive MIDI events using CoreMIDI, you would have to create a MIDI input port using a call to MIDIInputPortCreateWithBlock and then parse the incoming MIDI packets in the block-based callback. This would get complicated quickly because you would have to write logic for parsing MIDI packets. For example, to extract “note on” events, you would have to look for events that start with the byte 0x90 and are also followed by two bytes for the note value and velocity, respectively. You would also have to handle situations where multiple MIDI events arrive in one packet, and so on.

As of macOS 12 and iOS 15, this became easier with the introduction of the little-known MIDIEventListForEachEvent API. To use it, we create a MIDI 1.0 input port with the newer MIDIInputPortCreateWithProtocol function (and MIDI 2.0 is also supported):

MIDIInputPortCreateWithProtocol(client,
                                name,
                                MIDIProtocolID._1_0,
                                &inputPort) { list, _ in
                                ...

The block here gives us a UnsafePointer<MIDIEventList> pointer to the event list (as opposed to a pointer to MIDIPackets in the older API). The MIDIEventListForEachEvent function lets us iterate over the list by invoking a visitor function that will get called for every MIDI event. Unfortunately, it takes a C callback function that follows the C function pointer calling convention (public typealias MIDIEventVisitor = @convention(c) ...). If you simply try capturing self in a block passed to MIDIEventListForEachEvent, you’ll get an error:

MIDIEventListForEachEvent(list, { _, timestamp, message in
                                ^^^^ A C function pointer cannot be formed from a closure that captures context
    self.doSomething(message, timestamp)
}, nil)

This makes it harder to use this in situations where we want to capture surrounding context, which is probably most cases, so we created a more Swift-friendly wrapper that makes this easier:

import CoreMIDI
import Foundation

typealias MIDIForEachBlock = (MIDIUniversalMessage, MIDITimeStamp) -> Void

private final class MIDIForEachContext {
    var block: MIDIForEachBlock

    init(block: @escaping MIDIForEachBlock) {
        self.block = block
    }
}

func MIDIEventListForEach(_ list: UnsafePointer<MIDIEventList>, _ block: MIDIForEachBlock) {
    withoutActuallyEscaping(block) { escapingClosure in
        let context = MIDIForEachContext(block: escapingClosure)
        withExtendedLifetime(context) {
            let contextPointer = Unmanaged.passUnretained(context).toOpaque()
            MIDIEventListForEachEvent(list, { contextPointer, timestamp, message in
                guard let contextPointer else {
                    return
                }

                let localContext = Unmanaged<MIDIForEachContext>.fromOpaque(contextPointer).takeUnretainedValue()
                localContext.block(message, timestamp)
            }, contextPointer)
        }
    }
}

In a nutshell, this code wraps a regular Swift closure in a class that gets passed as the “context” pointer to MIDIEventListForEachEvent. Inside of the C callback function, we then unwrap the context and invoke our Swift closure; this one is not restricted with @convention(c), so we can capture surrounding context just fine. It will get invoked with two parameters for every MIDI event: a MIDIUniversalMessage and MIDITimeStamp. Using it is as simple as switching on the message type and looking at the nested union types. Here, we look for a message type of channelVoice1 and status of noteOn.

MIDIEventListForEach(list) { message, timestamp in
    switch message.type {
    case .channelVoice1:
        switch message.channelVoice1.status {
        case .noteOn:
            self.handleNoteOn(message.channelVoice1.note.number,
                              velocity: message.channelVoice1.note.velocity,
                              timestamp: timestamp)
            ...

We can now trivially obtain the MIDI note value as well as its velocity, all without having to parse MIDI packets ourselves.