iosswiftcoremidi

Observe MIDI device connection notification on iOS


Is there an API that will notify me when a MIDI device has been connected to iOS device? I have been trying to find it among the CoreMidi APIs, but I wasn't successful. I am only able to list all connected devices at a given moment.

I would like to avoid polling if possible, so this would be beneficial.


Solution

  • Yes, you can use the MIDIClient API. Specifically, here's a simple, self-contained program that will print messages when devices are added, removed, or have their properties changed:

    import Cocoa
    import CoreMIDI
    
    var client = MIDIClientRef()
    let clientName = "MyMIDIClient" as CFString
    let err = MIDIClientCreateWithBlock(clientName, &client) { (notificationPtr: UnsafePointer<MIDINotification>) in
        let notification = notificationPtr.pointee
        switch notification.messageID {
            case .msgSetupChanged: // Can ignore, really
                break
    
            case .msgObjectAdded:
                let rawPtr = UnsafeRawPointer(notificationPtr)
                let message = rawPtr.assumingMemoryBound(to: MIDIObjectAddRemoveNotification.self).pointee
                print("MIDI \(message.childType) added: \(message.child)")
    
            case .msgObjectRemoved:
                let rawPtr = UnsafeRawPointer(notificationPtr)
                let message = rawPtr.assumingMemoryBound(to: MIDIObjectAddRemoveNotification.self).pointee
                print("MIDI \(message.childType) removed: \(message.child)")
    
            case .msgPropertyChanged:
                let rawPtr = UnsafeRawPointer(notificationPtr)
                let message = rawPtr.assumingMemoryBound(to: MIDIObjectPropertyChangeNotification.self).pointee
                print("MIDI \(message.object) property \(message.propertyName.takeUnretainedValue()) changed.")
    
            case .msgThruConnectionsChanged:
                fallthrough
            case .msgSerialPortOwnerChanged:
                print("MIDI Thru connection was created or destroyed")
    
            case .msgIOError:
                let rawPtr = UnsafeRawPointer(notificationPtr)
                let message = rawPtr.assumingMemoryBound(to: MIDIIOErrorNotification.self).pointee
                print("MIDI I/O error \(message.errorCode) occurred")
    
            default:
                break
        }
    }
    
    if err != noErr {
        print("Error creating MIDI client: \(err)")
    }
    
    let rl = RunLoop.current
    while true { 
        rl.run(mode: .default, before: .distantFuture)
    }
    

    A few notes:

    var midiDevicesObserver: NSKeyValueObservation?
    let deviceManager = MIKMIDIDeviceManager.shared
    midiDevicesObserver = deviceManager.observe(\.availableDevices) { (dm, _) in
        print("Available MIDI devices changed: \(dm.availableDevices)")
    }
    

    or use the also-available MIKMIDIDeviceWasAddedNotification and related notifications. It also handles automatically coalescing source/endpoint pairs into devices, allows you to KVO device properties (name, etc.), and a bunch of other stuff.

    Disclaimer: I'm the primary author and maintainer of MIKMIDI.