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

如何让AppleSequencer与MIDISampler忽略NoteOff?Release模式异常排查

AudioKit鼓机样本被NoteOff截断及Release环境MIDI数据包异常问题

我正在使用AudioKit 5.6.2开发简易鼓机Sequencer,所使用的样本自带混响,时长较长(如1.5-2.0秒)。若将音符时长设为样本长度,会出现第一个样本播放时,第二个样本被第一个的NoteOff事件提前截断的问题。

参考AudioKit CookBook编写的鼓机代码如下:

import SwiftUI
import AudioKit
import AVFoundation

class DrumsConductor: ObservableObject {
    
    let engine = AudioEngine()
    let sequencer = AppleSequencer()
    private var drums = MIDISampler(name: "Drums")
    
    var tempo: Double = 60.0 {
        didSet {
            sequencer.setTempo(tempo)
        }
    }
    
    init() {
        engine.output = drums
    }
    
    func destroy() {
        drums.destroyEndpoint()
    }
    
    func playSequence() {
        sequencer.stop()
        sequencer.setTempo(tempo)
        if sequencer.trackCount < 1 {
            let _ = sequencer.newTrack()
        }
        sequencer.setGlobalMIDIOutput(drums.midiIn)
        
        for index in 0..<8 {
            sequencer.tracks[0].add(noteNumber: 48, velocity: 60, position: Duration(beats: Double(index)), duration: Duration(beats: 1.2))
        }
        
        sequencer.rewind()
        sequencer.play()
    }
    
    func stopSequence() {
        guard sequencer.isPlaying else { return }
        sequencer.stop()
        sequencer.preroll()
    }
    
    func start() {
        do {
            try engine.start()
        } catch let err {
            Log(err)
        }
        setupSound()
    }
    
    func stop() {
        engine.stop()
    }
    
    func setupSound() {
        let path = "Sounds/Sampler Instruments/Drums"
        
        do {
            try drums.loadSoundFont(path, preset: 0, bank: 0)
        } catch {
            fatalError(error.localizedDescription)
        }
    }
}

我尝试让AppleSequencer或MIDISampler忽略NoteOff事件但未找到原生方法,虽可通过计算样本间距或交替使用多轨道规避,但更希望直接避免样本被截断,询问是否有AudioKit原生解决方案。

后续我复制MIDISampler创建自定义DrumMIDISampler类,修改enableMIDI方法过滤NoteOff事件:

public func enableMIDI(_ midiClient: MIDIClientRef = MIDI.sharedInstance.client,
                       name: String? = nil) {
    let cfName = (name ?? self.name) as CFString
    guard let midiBlock = avAudioNode.auAudioUnit.scheduleMIDIEventBlock else {
        fatalError("Expected AU to respond to MIDI.")
    }
    CheckError(MIDIDestinationCreateWithBlock(midiClient, cfName, &midiIn) { packetList, _ in
        for e in packetList.pointee {
            print("Call: \(e)")
            for event in e {
                print("Event: \(event)")
                event.data.withUnsafeBufferPointer { ptr in
                    guard let ptr = ptr.baseAddress else { return }
                    
                    // Check if the event is a NoteOff event
                    let statusByte = ptr[0]
                    let isNoteOff = (statusByte >= 0x80 && statusByte <= 0x8F) || (ptr[0] == 0x90 && ptr[2] == 0) // NoteOff Event oder NoteOn Event mit einer Velocity von 0
                    
                    // Only process the event if it's not a NoteOff event
                    if !isNoteOff {
                        midiBlock(AUEventSampleTimeImmediate, 0, event.data.count, ptr)
                    }
                }
            }
        }
    })
}

该方案在Debug配置下正常工作,但在Release环境中所有MIDIPacket均为空(Debug下能获取正常MIDIPacket,Release下返回时间戳0、长度0的空数据包),询问该现象原因及解决方法。


内容的提问来源于stack exchange,提问作者Herr der Töne

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 15:17:11