AudioKit如何配置输出节点使Sequencer音符输出到外部MIDI设备
AudioKit Sequencer 输出切换到外部MIDI设备实现方案
核心实现逻辑
原来的代码是将Sequencer的MIDI事件发送到本地MIDISampler实现本机播放,切换到外部MIDI设备只需要将Sequencer的事件直接转发到已连接的外部MIDI设备端口即可,不需要走本地AudioEngine的采样器播放链路。
具体实现步骤
- 持有全局MIDI实例,枚举已连接的外部MIDI设备,选择目标输出端口
- 替换Sequencer的目标节点为自定义回调,将收到的MIDI事件直接发送到外部MIDI端口
- 可选:如果需要同时支持本机播放+外部MIDI输出,可保留原Sampler链路,同时在回调中转发事件到外部设备
修改后完整代码
import AudioKit import AudioKitEX import SwiftUI struct SongInfoView: View { let engine = AudioEngine() let sampler = MIDISampler(name: "MIDISampler") var mixer = Mixer() var sequencer = Sequencer() // 持有MIDI实例,不能是局部变量 let midi = MIDI() // 保存选中的外部MIDI输出端口ID var selectedOutputPortID: MIDIUniqueID? func initMIDI() { // 初始化MIDI,开启输出 midi.openOutput() // 枚举所有已连接的外部MIDI输出设备 let outputDestinations = midi.destinations print("已检测到的MIDI输出设备:\(outputDestinations.map{$0.name})") // 这里默认选第一个检测到的设备,你也可以做UI让用户手动选择 selectedOutputPortID = outputDestinations.first?.uniqueID } func playMIDI() { // 初始化MIDI设备 initMIDI() guard let targetPortID = selectedOutputPortID else { print("未检测到可用的外部MIDI输出设备") return } // 如果不需要本地播放,下面的Sampler加载代码可以删掉 do { try sampler.loadSoundFont("Grand Piano", preset: 0, bank: 0) print("Soundfont Grand Piano loaded") } catch { print("Error loading Grand Piano soundfont") } mixer.addInput(sampler) // 创建Sequencer轨道,绑定MIDI实例处理事件 let track = sequencer.addTrack(for: midi) track.loopEnabled = false track.tempo = 60 // 添加音符事件,和之前逻辑一致 track.sequence.add(noteNumber: 80, velocity: 100, position: 0, duration: 1) track.sequence.add(noteNumber: 81, velocity: 100, position: 1, duration: 1) track.sequence.add(noteNumber: 82, velocity: 100, position: 2, duration: 1) // 设置MIDI事件回调,转发到目标外部设备 track.midiOutput = { status, data1, data2 in guard let status = MIDIStatus(byte: status) else { return } // 发送MIDI事件到选中的外部端口 self.midi.sendEvent([status.byte, data1, data2], to: targetPortID) // 如果需要同时本地播放,保留下面的代码,否则删掉即可 switch status { case .noteOn: self.sampler.play(noteNumber: data1, velocity: data2, channel: 0) case .noteOff: self.sampler.stop(noteNumber: data1, channel: 0) default: break } } // 如果不需要本地播放,下面的AudioEngine启动代码可以删掉 try? engine.stop() engine.output = mixer do { try engine.start() print("AudioKit engine started") } catch { Log("AudioKit did not start! \(error)") } sequencer.playFromStart() } var body: some View { Button("播放MIDI") { playMIDI() } } }
注意事项
- 项目需要在Info.plist中添加
NSBluetoothAlwaysUsageDescription权限,蓝牙MIDI设备需要该权限才能被检测到 - 外部MIDI设备需要先和iOS设备完成配对,才会出现在
midi.destinations列表中 - 如果你需要将MIDI信号发送到本机其他App,而非硬件设备,才需要调用
midi.createVirtualOutputPorts()创建虚拟端口,连接硬件的场景不需要调用该方法
内容的提问来源于stack exchange,提问作者ahihi
相关产品推荐
相关产品推荐

