通过采样器播放传入的MIDI

苹果公司的Sampler Audio UnitAVFoundation框架的一部分,它使我们能够从预加载的声音中播放音符。 与CoreMIDI一起,我们可以将其用作输出到外部MIDI键盘。

我们通过调用默认的初始化程序并选择加载声音库乐器来设置采样器:

  var sampler = AVAudioUnitSampler() 
 let soundbank = Bundle.main.url(for资源:“ FluidR3 GM2-2”,扩展名:“ SF2”) 
让melodicBank:UInt8 = UInt8(kAUSampler_DefaultMelodicBankMSB)
让gmHarpsichord:UInt8 = 6
 尝试!  sampler.loadSoundBankInstrument(at:soundbank !, program:gmHarpsichord,bankMSB:melodicBank,bankLSB:UInt8(kAUSampler_DefaultBankLSB)) 

为了使我们能够听到它,我们将采样器连接到声音引擎,将其连接到混音器节点,最后启动引擎:

  var engine = AVAudioEngine() 
engine.attach(采样器)
engine.connect(sampler,to:engine.mainMixerNode,format:nil)
尝试! 启动引擎()

采样器本身就是MIDI乐器,因此可以接收任意事件,例如音符,弯音和通道压力。 但是首先,我们需要从外部源捕获这些消息。

接收MIDI的过程很简单:我们创建一个输入端口,发现源并将两者连接在一起:

  var midiClient = MIDIClientRef() 
var inputPort = MIDIPortRef()
 让_ = MIDIClientCreateWithBlock(“ com.diegolavalle.SamplerOutput.MidiClient” as CFString,&midiClient,nil) 
 让_ = MIDIInputPortCreateWithBlock(midiClient,“ com.diegolavalle.SamplerOutput.MidiInputPort”作为CFString,&inputPort){...数据包解析} 
  for sourceIndex in 0 .. <MIDIGetNumberOfSources(){ 
让_ = MIDIPortConnectSource(inputPort,MIDIGetSource(sourceIndex),nil)
}

不幸的是,MIDI乐器音频单元不符合基于数据包列表的端点接口,因此它们不能直接用作目的地。 相反,我们需要在将各个消息发送到采样器之前手动解析每个数据包。

首先,我们在输入端口的读取块中处理数据包列表:

  (packetsPointer,_)在 
 让数据包= packetsPointer.pointee 
var packetPointer = UnsafeMutablePointer .allocate(容量:1)
packetPointer.initialize(发送至:packets.packet)
对于_ in 0 .. <packet.numPackets {
self.parsePacket(packetPointer.pointee)// P
packetPointer = MIDIPacketNext(packetPointer)
}

每个数据包都需要特殊处理,因为它可以包含以固定大小的元组编码的多个事件:

  //下一行将元组转换为数组 
让packetBytes = Mirror(reflecting:packet.data).children.map({$ 0.value})为! [UInt8]
  var previousStatus:UInt8!  =无 
var messageData:[UInt8] = []
var i = 0 //准备迭代数组并提取MIDI消息
 而我<= packetBytes.count { 
如果i == packet.length || isStatusByte(packetBytes [i]){
...将消息转发给采样器
}
如果我== packet.length {
break //我们已经到达数据包的末尾并发送了最后一条消息
}
如果isStatusByte(packetBytes [i]){
previousStatus = packetBytes [i]
messageData = []
} else {//是数据字节,保存
messageData.append(packetBytes [i])
}
我+ = 1
}

一旦我们为离散的MIDI事件收集了足够的信息,就可以使用适当的实例方法将数据转发到采样器:

 切换messageData.count { 
情况1:
sampler.sendMIDIEvent(previousStatus,data1:messageData [0])
 情况2: 
sampler.sendMIDIEvent(previousStatus,data1:messageData [0],data2:messageData [1])
 默认值:break 
}

我们应该能够立即听到结果,而不会拖延。

完整的源代码如下。