Swift MIDI Trampoline
Swift MIDI Trampoline
Swift does not support C Function pointers. I’ve written about that a few times.
So, what do you do if you need to use a C API that relies on C callbacks? Core MIDI is just one example of C APIs that rely on function pointers.
Introduction
Core MIDI has three C callbacks – to read incoming MIDI data, to read incoming Sysex data, and to be notified when MIDI inputs or outputs have changed. You can write a pure Swift app using MIDI if you only want to send data, but not if you want to read it.
To read incoming MIDI data (or use the other callbacks), you need to write the callbacks in Objective-C. Then if you want to handle the data in Swift, you give the Objective-C callback a Swift closure. So, the Objective-C code is like a trampoline – it hits the Objective-C code then bounces the data to Swift.
Notify Callback
To use Core MIDI, the first thing you need to do is create the client reference by calling MIDIClientCreate. This function takes the notification callback as a parameter.
Core MIDI defines the notification callback like this:
1 |
typedef void (*MIDINotifyProc)(const MIDINotification *message, void *refCon); |
We will write this callback in Objective-C.
MIDIClientCreate also takes a void pointer (notifyRefCon) to a parameter that will be passed into your callback as the second parameter. We will use this parameter to pass in a Swift closure. The callback will then invoke the Swift closure.
1 2 3 4 |
extern OSStatus MIDIClientCreate(CFStringRef name, MIDINotifyProc notifyProc, void * notifyRefCon, MIDIClientRef * outClient) |
So, let’s wrap MIDIClientCreate with an Objective-C utility that will register the callback. This utility will take a block (your Swift closure) as a parameter. The signature of the Swift closure is specified here: it takes a MIDINotification as a parameter. This utility will copy the block and store it in the refCon passed to MIDIClientCreate. The trampoline will convert it back to a block and invoke it.
1 2 3 4 |
OSStatus MIDIClientCreate_withBlock(CFStringRef name, MIDIClientRef *outClient, void (^notifyRefCon)(const MIDINotification *message)) { void *refCon = (__bridge_retained void*)notifyRefCon; return MIDIClientCreate(name, &midiNotify_BlockTrampoline, refCon, outClient); } |
This is the trampoline. The void pointer refCon is converted back into a block and then invoked with the notification.
1 2 3 4 5 |
static void midiNotify_BlockTrampoline(const MIDINotification *message, void *refCon) { void (^block)(const MIDINotification*) = (__bridge typeof(block))refCon; if (!block) return; block(message); } |
Here is the Swift code to call the Objective-C utility. myNotifyCallback is the Swift function that will be the callback.
1 2 3 4 5 6 7 8 9 10 |
var midiClientRef = MIDIClientRef() var status = OSStatus(noErr) var s:CFString = "MyClient" status = MIDIClientCreate_withBlock(s, &midiClientRef, myNotifyCallback) if status != noErr { println("error creating client: \(status)") return } else { println("midi client created \(midiClientRef)") } |
The Swift callback is defined with the single MIDINotification parameter like this.
1 2 3 |
func myNotifyCallback(message:UnsafePointer<MIDINotification>) -> Void { println("got a MIDINotification!") } |
Cool, huh?
More like a pain in the neck, IMHO. But this is what you have to do until Swift supports function pointers.
Read callback
You specify the read callback when you create the MIDI Input Port via MIDIInputPortCreate.
This is how the read callback is defined:
1 |
typedef void (*MIDIReadProc)(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon); |
Notice that it also has a void pointer parameter that we will use to point to a Swift closure.
So, here is the Objective-C utility to create the input port. It uses the same pattern as the notify function. The last parameter readRefCon will be your Swift closure.
1 2 3 4 5 6 7 8 9 10 |
OSStatus MIDIInputPortCreate_withBlock(CFStringRef name, MIDIClientRef midiClient, MIDIPortRef* outport, void (^readRefCon)(const MIDITimeStamp ts, const UInt8 *data, const UInt16 len)) { void *refCon = (__bridge_retained void*)readRefCon; return MIDIInputPortCreate(midiClient, name, &midiReadPacket_BlockTrampoline, refCon, outport); } |
The read trampoline converts the void pointer back into a block that is then invoked.
I’m not passing the MIDIPacketList back to Swift, because Swift is not able to handle it. We have to iterate through it in Objective-C. (If you know a way to iterate through it in Swift, let me know!). So, the callback simply takes each MIDIPacket‘s data.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static void midiReadPacket_BlockTrampoline(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) { void (^block)(MIDITimeStamp, UInt8 *, UInt16) = (__bridge typeof(block))readProcRefCon; if (!block) return; const MIDIPacket *packet = &pktlist->packet[0]; for (int i = 0;i < pktlist->numPackets; ++i) { UInt8 *data = (UInt8 *)packet->data; MIDITimeStamp ts = packet->timeStamp; block(ts, data, packet->length); packet = MIDIPacketNext(packet); } } |
Here is how you call it from Swift.
1 2 3 4 5 6 |
var portString:CFString = "MyClient In" var midiInputPortref = MIDIPortRef() status = MIDIInputPortCreate_withBlock(portString, midiClientRef, &midiInputPortref, myPacketReadCallback) |
And your Swift callback will be something like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
func myPacketReadCallback(ts:MIDITimeStamp, data:UnsafePointer<UInt8>, len:UInt16) { let status = data[0] // without channel let rawStatus = data[0] & 0xF0 switch rawStatus { case 0x80: var channel = status & 0x0F println("note off. channel \(channel) note \(data[1]) velocity \(data[2])") case 0x90: var channel = status & 0x0F println("note on. channel \(channel) note \(data[1]) velocity \(data[2])") default: break // handle the rest of the MIDI message types } } |
Summary
Yeah, I know. You have to jump through hoops. That’s just the status of Swift and C APIs at this point.
Regards:
My name is Juan Manuel, I am currently working in the cathedral mosque of Córdoba as a pipe organ builder, for two years we have been working so that our organ has the communication capabilities with the “RTP-MIDI” protocol and we have achieved it. But now we have arrived at the moment to implement an application that allows us to manage all the functions of the organ of tubes from a mobile device or an iPad.
Thanks to its example code, I managed to send MIDI packages to the organ, activating and deactivating notes and even playing the same with a small virtual keyboard on the screen of my Ipad. The problem comes when I have tried to implement in my code, the creation of a ” MIDIInputPortCreate” using Swift. I have seen that there are some problems to receive the calls since swift does not support the reference to pointers.
In your example project “Swift Midi trampoline” but the code is written in swift 2 and I can not make the application work.
I would be very grateful if you could help me in some way to understand how I can create a ” MIDIInputPortCreate”.
Yes, this is an old blog post containing a hack to get Core MIDI to run with Swift 2. As you’ve noted, Apple actually fixed things a bit and this is no longer necessary. You will find dozens of repos with working Swift MIDI code on my github account.
Try this one: https://github.com/genedelisa/Swift3MIDI It works for Swift 4 also.