Swift 对CoreMIDI目的地的困惑

Swift 对CoreMIDI目的地的困惑,swift,macos,coremidi,Swift,Macos,Coremidi,给定以下代码,如果我使用if分支中的第一个方法来获得MIDI目的地,则代码正常工作,并且发送MIDI数据。如果使用else分支中的第二种方法,则不会发送数据 var client = MIDIClientRef() var port = MIDIPortRef() var dest = MIDIEndpointRef() MIDIClientCreate("jveditor" as CFString, nil, nil, &client) MIDIOutputPortCreate(cl

给定以下代码,如果我使用
if
分支中的第一个方法来获得
MIDI目的地
,则代码正常工作,并且发送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)
var client=MIDIClientRef()
var port=MIDIPortRef()
var dest=MIDIEndpointRef()
midclientcreate(“jveditor”作为CFString、nil、nil和client)
MIDIOutputPortCreate(客户端,“输出”为CFString和端口)
如果错误{
dest=MIDIGetDestination(1)
}否则{
var device=MIDIGetExternalDevice(0)
var entity=MIDIDeviceGetEntity(设备,0)
dest=MIDIEntityGetDestination(实体,0)
}
变量名称:非托管?
MIDIObjectGetStringProperty(目标、kMIDIPropertyDisplayName和名称)
打印(名称?.takeUnretainedValue()为!字符串)
变量gmOn:[UInt8]=[0xf0、0x7e、0x7f、0x09、0x01、0xf7]
var pktlist=MIDIPacketList()
var current=MIDIPacketListInit(&pktlist)
current=MIDIPacketListAdd(&pktlist,MemoryLayout.stride,current,0,gmOn.count,&gmOn)
MIDISend(端口、目标和pktlist)
在这两种情况下,打印的设备名称都是正确的,每次呼叫的状态都是
noErr

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

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


有谁能解释一下这两种方法之间的区别,以及为什么后者不起作用,并就如何解决这一问题提供一个理想的建议吗?

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

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

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

“外部设备”是试图绕过发现问题的一种尝试。当您按下“添加设备”按钮时,它们会出现在音频MIDI设置中。就这些

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

不幸的是,在现实中:

  • 可能没有任何外部设备。在音频MIDI设置中创建它们并没有多大好处,而且它需要大量枯燥的数据输入,所以大多数人不必费心
  • 如果存在外部设备,则无法信任用户添加的任何信息。例如,制造商可能不对,或者可能拼写错误
  • 在用户可以使用您的软件之前,强迫用户在音频MIDI设置中进行设置是非常不友好的。因此,没有应用程序能做到这一点。。。因此,没有人在音频MIDI设置中设置任何内容。这是一个鸡和蛋的问题
  • 即使存在外部设备,您的用户也可能希望将MIDI发送到其他显然未连接到外部设备的端点(如其他应用程序创建的虚拟端点)。你应该让他们为所欲为
报告提出了一个很好的建议:

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

因此,客户端通常应该使用MIDIGetNumberOfSources、MIDIGetSource、MIDIGetNumberOfDestinations和MIDIGetDestination,而不是通过迭代设备和实体来定位端点

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

如果你真的想做更多的事情:

  • 给定目标端点,您可以使用
    MIDIEndpointGetEntity
    MIDIEndpointGetDevice
    访问MIDI接口
  • 给定任何MIDI对象,您可以找到它与其他对象的连接。用于获取属性的值,该属性是连接对象的唯一ID的数组。然后使用以到达对象。
    outObjectType
    将告诉您它是什么类型的对象

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

根据库尔特·雷维斯回答的提示,我找到了解决方案

我需要查找的目的地与外部设备的源关联,它们之间的连接可以使用该源的
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属性实际上是一个数据属性(可能是containin)