AudioKit iOS中receivedMIDINoteOn未触发,需实现音符播放时UILabel闪烁
解决AudioKit中音序器播放时触发UI闪烁的问题
你遇到的核心问题是MIDI消息的路由没有正确指向你用来监听的对象——不管是Conductor的AKMIDIListener还是Flasher子类,都没接收到音序器发送的MIDI事件。而AKCallbackInstrument正是AudioKit专门设计用来捕获MIDI事件、执行自定义逻辑的工具,完美适配你需要触发UI闪烁的场景。
为什么之前的方法失效?
- Conductor的
AKMIDIListener:你设置的是监听系统MIDI输入端口(midi.openInput("Session 1")),但音序器大概率是直接把MIDI消息发送给了AKMIDISampler实例,而非通过系统MIDI端口传递,所以Conductor的receivedMIDINoteOn永远不会被触发。 Flasher子类:AKMIDISampler内部已经实现了完整的MIDI消息处理逻辑,重写receivedMIDINoteOn通常不会被调用——除非你能确保音序器的MIDI消息完全路由到这个子类的专属MIDI通道,且没有被内部逻辑拦截,这在实际开发中很难控制。
正确的实现方式:使用AKCallbackInstrument
以下是完整的修改步骤,帮你快速实现需求:
1. 在Conductor中集成AKCallbackInstrument
首先在Conductor里声明AKCallbackInstrument实例,用来捕获MIDI的noteOn/noteOff事件,同时保留原有采样器的声音播放逻辑:
import AudioKit import Combine class Conductor: ObservableObject { let ahSampler = AKMIDISampler() let beeSampler = AKMIDISampler() let gooSampler = AKMIDISampler() let samplerMixer = AKMixer() // 新增:专门用于捕获MIDI事件的回调乐器 let midiCallback = AKCallbackInstrument() // 用于驱动UI闪烁的状态变量 @Published var shouldFlashLabel = false init() { // 将所有采样器和回调乐器连接到混音器 [ahSampler, beeSampler, gooSampler, midiCallback] >>> samplerMixer AudioKit.output = samplerMixer do { try AudioKit.start() } catch { print("AudioKit启动失败:\(error.localizedDescription)") } // 配置MIDI事件回调 setupMidiCallbacks() // 这里假设你已经有音序器实例,后续需要把MIDI输出同时指向采样器和回调乐器 } private func setupMidiCallbacks() { midiCallback.callback = { status, note, velocity in // 判断是否是有效的Note On事件(排除Note Off或其他MIDI消息) if status == AKMIDIStatus.noteOn.rawValue && velocity > 0 { // UI操作必须切换到主线程执行 DispatchQueue.main.async { self.shouldFlashLabel = true // 延迟0.1秒恢复状态,实现闪烁效果 Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { _ in self.shouldFlashLabel = false } } print("捕获到Note On事件:音符\(note)") } } // 可以给回调乐器静音,避免它发出额外声音 midiCallback.volume = 0 } }
2. 确保音序器的MIDI输出路由正确
关键要让音序器把MIDI消息同时发送给采样器(负责播放声音)和AKCallbackInstrument(负责触发UI更新)。假设你使用AKSequencer,可以这样配置:
// 初始化并加载音序器文件 let sequencer = AKSequencer(filename: "your_sequence.mid") // 给每个采样器添加MIDI输出,保证声音正常播放 sequencer.addMIDIOutput(ahSampler.midiIn) sequencer.addMIDIOutput(beeSampler.midiIn) sequencer.addMIDIOutput(gooSampler.midiIn) // 给回调乐器添加MIDI输出,捕获事件 sequencer.addMIDIOutput(midiCallback.midiIn) // 启动音序器 sequencer.play()
3. 实现UILabel闪烁的UI逻辑
在你的View中,通过Conductor的@Published变量来控制Label的视觉状态:
import SwiftUI struct ContentView: View { @StateObject var conductor = Conductor() @State private var labelOpacity: Double = 1.0 var body: some View { Text("正在播放") .font(.title) .bold() .opacity(labelOpacity) .onReceive(conductor.$shouldFlashLabel) { isFlashing in withAnimation(.easeInOut(duration: 0.1)) { labelOpacity = isFlashing ? 0.3 : 1.0 } } } }
额外提示
- 避免在MIDI回调中执行耗时操作,所有UI更新必须通过
DispatchQueue.main.async切换到主线程,否则会导致界面卡顿或崩溃。 - 如果需要更复杂的闪烁效果(比如颜色变化、缩放),可以修改
onReceive中的动画逻辑,替换opacity为其他视觉属性即可。
内容的提问来源于stack exchange,提问作者Ben Spector
相关产品推荐
相关产品推荐

