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

AudioKit iOS中receivedMIDINoteOn未触发,需实现音符播放时UILabel闪烁

解决AudioKit中音序器播放时触发UI闪烁的问题

你遇到的核心问题是MIDI消息的路由没有正确指向你用来监听的对象——不管是Conductor的AKMIDIListener还是Flasher子类,都没接收到音序器发送的MIDI事件。而AKCallbackInstrument正是AudioKit专门设计用来捕获MIDI事件、执行自定义逻辑的工具,完美适配你需要触发UI闪烁的场景。

为什么之前的方法失效?

  1. Conductor的AKMIDIListener:你设置的是监听系统MIDI输入端口(midi.openInput("Session 1")),但音序器大概率是直接把MIDI消息发送给了AKMIDISampler实例,而非通过系统MIDI端口传递,所以Conductor的receivedMIDINoteOn永远不会被触发。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:31:40