对CoreMIDI目的地的困惑

时间:2017-02-17 15:15:55

标签: swift macos coremidi

如果我使用if分支中的第一个方法获得MIDIDestination代码正常工作,并且发送了MIDI数据,则给出以下代码。如果我使用else分支中的第二种方法,则不会发送任何数据。

var client = MIDIClientRef()
var port = MIDIPortRef()
var dest = MIDIEndpointRef()

MIDIClientCreate("jveditor" as CFString, nil, nil, &client)
MIDIOutputPortCreate(client, "output" as CFString, &port)

if false {
    dest = MIDIGetDestination(1)
} else {
    var device = MIDIGetExternalDevice(0)
    var entity = MIDIDeviceGetEntity(device, 0)
    dest = MIDIEntityGetDestination(entity, 0)
}

var name: Unmanaged<CFString>?
MIDIObjectGetStringProperty(dest, kMIDIPropertyDisplayName, &name)
print(name?.takeUnretainedValue() as! String)

var gmOn : [UInt8] = [ 0xf0, 0x7e, 0x7f, 0x09, 0x01, 0xf7 ]

var pktlist = MIDIPacketList()
var current = MIDIPacketListInit(&pktlist)
current = MIDIPacketListAdd(&pktlist, MemoryLayout<MIDIPacketList>.stride, current, 0, gmOn.count, &gmOn)

MIDISend(port, dest, &pktlist)

在这两种情况下,打印的设备名称都是正确的,每次通话的状态都是noErr

我注意到如果我要求kMIDIManufacturerName属性我得到不同的结果 - 特别是使用第一种方法我得到Generic,来自MIDI设备所连接的USB MIDI接口,并使用第二种方法,我通过音频MIDI设置应用程序获得Roland的值。

我想要使用第二种方法的原因是我可以过滤掉没有所需制造商名称的设备,但如上所述,我无法获得工作输出。

任何人都可以解释这两种方法之间的区别,以及为什么后者不起作用,理想情况下提供一个关于如何解决这个问题的建议?

2 个答案:

答案 0 :(得分:1)

听起来您只想找到与某个制造商的设备通信的MIDI目标端点。不幸的是,这是不可能的,因为没有协议来发现存在什么MIDI设备,它们的属性是什么,以及它们如何连接到计算机。

(请记住,MIDI是20世纪80年代的原始技术。它甚至不需要双向通信。MIDI设备有完全有效的MIDI设置可以发送数据,但永远不会从,反之亦然。)

计算机知道哪些MIDI接口连接到它(例如,USB-MIDI接口)。 CoreMIDI称这些“设备”。您可以找到有多少端口,每个端口有多少端口等。但是没有办法找到有关物理MIDI设备的任何信息,例如连接到它们的键盘和合成器。

“外部设备”试图解决发现问题。当您按“添加设备”按钮时,它们就是音频MIDI设置中出现的内容。这就是全部!

理想情况下,您的用户会在其设置中为每个物理MIDI设备创建一个外部设备,输入每个设备的所有属性,并以完全镜像其物理MIDI线缆的方式设置所有连接。

不幸的是,实际上:

  • 可能没有任何外部设备。在音频MIDI设置中创建它们并没有太大的好处,并且它是很多无聊的数据输入,所以大多数人都不打扰。
  • 如果有外部设备,则您不能信任用户添加的任何信息。例如,制造商可能不对,或者拼写错误。
  • 强迫用户在使用您的软件之前在音频MIDI设置中进行设置是非常不友好的。因此,没有应用程序这样做......因此没有人在音频MIDI设置中设置任何内容。这是一个鸡蛋问题。
  • 即使有外部设备,您的用户也可能希望将MIDI发送到其他端点(如其他应用创建的虚拟端点),这些端点显然未连接到外部设备。你应该让他们做他们想做的事。

documentation for MIDIGetDevice()提出了一个很好的建议:

  

如果客户端遍历系统中的设备和实体,它将不会访问由其他客户端创建的任何虚拟源和目标。此外,设备迭代将返回“离线”(过去存在但当前不存在)的设备,而通过系统的源和目标的迭代将不包括离线设备的端点。

     

因此,客户端通常应使用MIDIGetNumberOfSources,MIDIGetSource,MIDIGetNumberOfDestinations和MIDIGetDestination,而不是遍历设备和实体来定位端点。

换句话说:使用MIDIGetNumberOfDestinationsMIDIGetDestination获取可能的目的地,然后让您的用户选择其中一个。就是这样。

如果确实想要做更多事情:

  • 给定目标端点,您可以使用MIDIEndpointGetEntityMIDIEndpointGetDevice进入MIDI界面。
  • 给定任何MIDI对象,您可以找到与其他对象的连接。使用MIDIObjectGetDataProperty获取属性kMIDIPropertyConnectionUniqueID的值,该属性是已连接对象的唯一ID数组。然后使用MIDIObjectFindByUniqueID来访问该对象。 outObjectType会告诉你它是什么类型的对象。

但这很尴尬,而且你不能保证找到任何有用的信息。

答案 1 :(得分:1)

根据Kurt Revis的回答提示,我找到了解决方案。

我需要查找的目标与外部设备的相关联,并使用该源的kMIDIPropertyConnectionUniqueID属性找到它们之间的连接。

使用以下代码替换问题中if / else分支中的代码:

var external = MIDIGetExternalDevice(0)
var entity = MIDIDeviceGetEntity(external, 0)
var src = MIDIEntityGetSource(entity, 0)

var connID : Int32 = 0
var dest = MIDIObjectRef()
var type = MIDIObjectType.other

MIDIObjectGetIntegerProperty(src, kMIDIPropertyConnectionUniqueID, &connID)
MIDIObjectFindByUniqueID(connID, &dest, &type)

属性转储表明连接唯一ID属性实际上是一个数据属性(可能包含多个ID),但结果CFData似乎是大端格式,因此将其作为整数属性读取似乎工作得很好。