The following was tested with Swift 2.1.1. I am far from a Swift expert. I do use “;” and “()” in Swift sometimes even though not necessary. This is because I develop in many languages and the code flows from my fingers this way.
Suppose you have a Cocoa app with a ViewController and you want to update the UI when a MIDI source or destination is added or removed, or a MIDI packet is received. There are a few snags along the way that you may encounter. CoreMIDI provides references for the application to pass in that can be used in the callbacks. But converting between Swift and C structs may not be intuitively obvious. Another difficulty is casting MIDINotification to MIDIObjectAddRemoveNotification. If this isn’t done properly the bits will get converted but the actual data in the MIDIObjectAddRemoveNotification struct will be trash. This is how I handled callbacks in Swift.
First I created a protocol that my ViewController implemented so that casting would be a little cleaner.
protocol MIDICallbackHandler { func processMidiPacket(packet: MIDIPacket, msgSrc: MIDIEndpointRef) -> Void; func processMidiObjectChange(message: MIDIObjectAddRemoveNotification) -> Void; }
Then I implemented the callbacks as global Swift functions. The bridgeMutable() and unbridgeMutable() utility functions were based on information I found on stackoverflow.com
func bridgeMutable(obj : T) -> UnsafeMutablePointer { return UnsafeMutablePointer(Unmanaged.passUnretained(obj).toOpaque()) } func unbridgeMutable (ptr : UnsafeMutablePointer ) -> T { return Unmanaged .fromOpaque(COpaquePointer(ptr)).takeUnretainedValue() } func MIDIUtil_MIDIReadProc(pktList: UnsafePointer , readProcRefCon: UnsafeMutablePointer , srcConnRefCon: UnsafeMutablePointer ) -> Void { let src:MIDIEndpointRef = UnsafeMutablePointer (COpaquePointer(srcConnRefCon)).memory; let packetList:MIDIPacketList = pktList.memory; let h:AnyObject = unbridgeMutable(readProcRefCon); let handler:MIDICallbackHandler = h as! MIDICallbackHandler; var packet:MIDIPacket = packetList.packet; for _ in 1...packetList.numPackets { handler.processMidiPacket(packet, msgSrc: src); packet = MIDIPacketNext(&packet).memory; } } func MIDIUtil_MIDINotifyProc(message: UnsafePointer , refCon: UnsafeMutablePointer ) -> Void { let notification:MIDINotification = message.memory; if (notification.messageID == .MsgObjectAdded || notification.messageID == .MsgObjectRemoved) { let msgPtr:UnsafePointer = UnsafePointer (message); let changeMsg:MIDIObjectAddRemoveNotification = msgPtr.memory; let h:AnyObject = unbridgeMutable(refCon); let handler:MIDICallbackHandler = h as! MIDICallbackHandler; handler.processMidiObjectChange(changeMsg); } }
Finally the view controller was implemented using the following code snippets. Note that another utility function invokeLater is used so that the UI can be updated off of the high priority CoreMIDI event thread.
func invokeLater(codeBlock: dispatch_block_t) { dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { dispatch_async(dispatch_get_main_queue(),codeBlock) }) } class ViewController: NSViewController, MIDICallbackHandler { ... var midiClient: MIDIClientRef = 0; var inPort:MIDIPortRef = 0; ... override func viewDidLoad() { super.viewDidLoad() ... // These Functions use the global callbacks and pass a reference to this class instance MIDIClientCreate("MidiTest1Client", MIDIUtil_MIDINotifyProc, bridgeMutable(self), &midiClient); MIDIInputPortCreate(midiClient, "MidiTest1_InPort", MIDIUtil_MIDIReadProc, bridgeMutable(self), &inPort); } ... // MIDI Callback Handler for packet receipt // In this case I had an NSTableView named monitorTable I was updating func processMidiPacket(packet: MIDIPacket, msgSrc: MIDIEndpointRef) -> Void { dataSource.append((packet: packet, msgSrc: msgSrc)); // Don't Update UI on this thread! invokeLater({ self.monitorTable.reloadData(); self.monitorTable.scrollToEndOfDocument(self); }); } // MIDI Callback Handler for source or destination changes func processMidiObjectChange(message: MIDIObjectAddRemoveNotification) -> Void { // print("MIDI Notification ChildObjectType: \(message.childType.rawValue) "); switch (message.messageID) { case .MsgObjectAdded: if (message.childType == MIDIObjectType.Destination || message.childType == MIDIObjectType.Source) { let src:MIDIEndpointRef = message.child; print("MIDI Source/Destination Added: \(MIDIUtil.getDisplayName(src))"); } else { print("MIDI Notification Object Added MsgObjectType: \(message.childType.rawValue) "); } case .MsgObjectRemoved: if (message.childType == MIDIObjectType.Destination || message.childType == MIDIObjectType.Source) { let x:MIDIEndpointRef = message.child; // Note: you can't get displayname if object is removed! if (x == currDest) { print("Current Destination Removed") invokeLater( { self.showAlert("MIDI Destination Change", infoText:"The current MIDI destination has been removed!") }); } } else { print("MIDI Notification Object Removed MsgObjectType: \(message.childType.rawValue) "); } default: print("processMidiNotification messageID:\(message.messageID.rawValue)"); break; } } }