You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 18:39:04