在 iOS 上观察 MIDI 设备连接通知

Observe MIDI device connection notification on iOS

是否有 API 会在 MIDI 设备连接到 iOS 设备时通知我?我一直试图在 CoreMidi API 中找到它,但我没有成功。我只能在给定时刻列出所有连接的设备。

我想尽可能避免轮询,所以这会很有用。

是的,可以使用MIDIClientAPI。具体来说,这是一个简单的自包含程序,它会在添加、删除设备或更改其属性时打印消息:

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)
}

一些注意事项:

  • 在所有 Apple 系统 API 中,CoreMIDI 可能是 Swift 中使用最差的一个。它是纯 C API,大量使用指针、CoreFoundation 类型、回调过程、类型仅在运行时已知的结构等。您在此处看到 Unsafe(Raw)Pointers 的使用,重新绑定内存等。我认为从 Objective-C 开始使用它仍然更有意义,并在我自己的项目中这样做。
  • 并非所有 MIDI 设备实际上都显示为 MIDI 设备(即 MIDIDeviceRef)。有些使用创建虚拟端点的驱动程序,而那些只是显示为(不相关的)源端点和目标端点。您必须自己弄清楚如何处理这些问题。 Native Instruments 的设备就是这种行为的一个常见示例。
  • 您可以查看 MIKMIDI,这会使所有这些变得容易得多。具体来说,您只需执行以下操作:
var midiDevicesObserver: NSKeyValueObservation?
let deviceManager = MIKMIDIDeviceManager.shared
midiDevicesObserver = deviceManager.observe(\.availableDevices) { (dm, _) in
    print("Available MIDI devices changed: \(dm.availableDevices)")
}

或使用也可用的 MIKMIDIDeviceWasAddedNotification 和相关通知。它还处理自动将 source/endpoint 对合并到设备中,允许您 KVO 设备属性(名称等),以及一堆其他东西。

免责声明:我是 MIKMIDI 的主要作者和维护者。