对CoreMIDI目标的混淆
如果我使用if
分支中的第一个方法获取MIDIDestination
代码,并且发送了MIDI数据,则给定以下代码。如果我使用else
分支中的第二种方法,则不会发送数据。对CoreMIDI目标的混淆
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
,从USB MIDI接口,MIDI设备连接,并用第二种方法,我得到通过音频MIDI设置应用程序配置的值Roland
。
我想用第二种方法是专门这样我就可以过滤掉不具有所期望的制造商名称设备,但如上述,我不能再获得工作输出的原因。
谁能解释这两种方法之间的区别,为什么后者不起作用,最好提供一个建议,我怎么能解决呢?
基于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
看起来是以big-endian格式,所以读取它作为一个整数属性反而似乎工作正常。
很高兴你得到它的工作!该属性的值可能是*单个整数(在这种情况下,您的代码工作)或包含多个唯一ID的'CFData'。在后一种情况下,MIDIObjectGetIntegerProperty应该返回一个非零的错误。 –
@KurtRevis似乎可以读取一个'CFData',它只包含一个整数作为一个整数而没有任何问题 - 我怀疑反序列化代码并不在意,但我应该试着从编程上弄清楚哪一个是'得到。文档确实说了“从CoreMIDI 1.3开始,这个属性也可以是CFDataRef,它包含一个大端的SInt32数组,允许指定一个驱动对象连接到多个外部对象(通过MIDI通过或分割)_” – Alnitak
这听起来像你想找到只MIDI目的地端点谈某制造商的设备。不幸的是,这是不可能的,因为没有发现MIDI设备存在的协议,它们的属性是什么以及它们如何连接到计算机。 (记住,MIDI是原始的1980s技术,它甚至不需要双向通信。有MIDI设备的完全有效的MIDI设置,你可以发送数据但永远不能接收数据,反之亦然。)
计算机知道MIDI接口连接到它(例如,一个USB-MIDI接口)。 CoreMIDI称这些“设备”。您可以找出有多少个端口,每个端口有多少个端口等等。但是没有办法找到有关物理MIDI设备的任何信息,例如连接到它们的键盘和合成器。
“外部设备”是企图绕过发现问题。当您按下“添加设备”按钮时,它们出现在音频MIDI设置中。就这样!
理想的情况下你的用户将创造他们设置每个物理MIDI设备的外部设备,进入每一个的所有属性,并建立在完全反映其物理MIDI线的方式全部连接。
不幸的是,在现实中:
- 可能没有任何外部设备。在“音频MIDI设置”中创建它们并没有多大好处,而且这是很多无聊的数据输入,所以大多数人都不会感到困扰。
- 如果有外部设备,则不能相信用户添加的任何信息。例如,制造商可能不对,或者可能拼错了。
- 在用户使用软件之前强制用户在Audio MIDI Setup中进行设置是非常不友好的。因此,没有应用程序可以这么做......因此没有人在“音频MIDI设置”中设置任何内容。这是一个鸡与鸡蛋的问题。
- 即使有外部设备,用户可能也想将MIDI发送到其他端点(如其他应用程序创建的虚拟端点),这些端点并未明显连接到外部设备。你应该让他们做他们想做的。
的documentation for MIDIGetDevice()做一个很好的建议:
如果通过系统中的设备和实体客户端迭代,它永远不会访问任何虚拟源和其他客户端创建的目标。此外,设备迭代将返回“脱机”(过去存在但目前不存在)的设备,而通过系统源和目标的迭代将不包括脱机设备的端点。
因此,客户端通常应该使用MIDIGetNumberOfSources,MIDIGetSource,MIDIGetNumberOfDestinations和MIDIGetDestination,而不是遍历设备和实体来定位端点。
换句话说:使用MIDIGetNumberOfDestinations
和MIDIGetDestination
以获得可能的目的地,然后让你的用户选择其中之一。就这样。
如果你真的希望做更多:
- 给定一个目标端点,可以使用
MIDIEndpointGetEntity
和MIDIEndpointGetDevice
去的MIDI接口。 - 给定任何MIDI对象,你可以找到它与其他对象的连接。使用
MIDIObjectGetDataProperty
可以获取属性kMIDIPropertyConnectionUniqueID
的值,该值是连接对象的唯一ID的数组。然后使用MIDIObjectFindByUniqueID
到达对象。outObjectType
会告诉你它是什么样的对象。
但是,这是相当尴尬,你不能保证找到任何有用的信息。
我得到你来自哪里,但这是针对不需要虚拟设备的MIDI补丁编辑器的,我希望通过Audio MIDI Setup应用程序配置的信息不仅可以获得制造商,还可以获得SysEx设备ID。通过属性检索的连接唯一ID是否采用big-endian格式?但是,如果我直接要求这个属性是一个整数,他们看起来是正确的。 – Alnitak
我已经在我的系统上倾倒了所有目的地,设备和外部设备的树。目的地中的唯一连接ID与任何外部设备的连接ID之间没有明显的重叠。 http://pastebin.com/93xhZCCJ – Alnitak
是的,唯一的ID应该是big-endian。不幸的是,从'NSData'中提取它们是一件痛苦的事情。我从来没有在Swift中做过这个,所以桥接可能有问题吗? [这是我在Objective-C中的做法](https://github.com/krevis/MIDIApps/blob/master/Frameworks/SnoizeMIDI/SMEndpoint.m#L306)。 –
你确定你正在得到正确的'实体'吗?那么'MIDIDeviceGetNumberOfEntities'呢? – Sulthan
@Sulthan是的。只有一个“实体”,根据上面的说法,显示的名称是相同的(并且是正确的)。但是,返回的唯一ID不一样。 – Alnitak
好的,有趣的 - 如果我用'MIDIGetDevice(4)'而不是'MIDIGetExternalDevice(1)'代码实际工作,并且我获得相同的唯一ID,除非我仍然得到错误的制造商ID(即,一个用于实际的接口,而不是连接到它的单元)。该文档不清楚“外部”设备和普通设备之间的差异,这里:( – Alnitak