AKSequencer 中播放的第一个音符已关闭

First note played in AKSequencer is off

我正在使用 AKSequencer 创建一系列由 AKMidiSampler 播放的音符。我的问题是,无论我做什么,在更高的速度下,第一个音符总是有一点延迟。

我试过预滚动序列,但没有用。用 AKSampler 或 AKSamplePlayer 替换 AKMidiSampler(并使用回调轨道来播放它们)也无济于事,尽管这让我认为问题可能出在音序器或我创建音符的方式上。

这是我正在做的一个例子(我尽量让它简单):

import UIKit
import AudioKit

class ViewController: UIViewController {

    let sequencer = AKSequencer()
    let sampler = AKMIDISampler()
    let callbackInst = AKCallbackInstrument()

    var metronomeTrack : AKMusicTrack?
    var callbackTrack : AKMusicTrack?

    let numberOfBeats = 8
    let tempo = 280.0

    var startTime : TimeInterval = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        print("Begin setup.")

        // Load .wav sample in AKMidiSampler

        do {
            try sampler.loadWav("tick")
        } catch {
            print("sampler.loadWav() failed")
        }

        // Create tracks for the sequencer and set midi outputs

        metronomeTrack = sequencer.newTrack("metronomeTrack")
        callbackTrack = sequencer.newTrack("callbackTrack")
        metronomeTrack?.setMIDIOutput(sampler.midiIn)
        callbackTrack?.setMIDIOutput(callbackInst.midiIn)

        // Setup and start AudioKit

        AudioKit.output = sampler

        do {
            try AudioKit.start()
        } catch {
            print("AudioKit.start() failed")
        }

        // Set sequencer tempo

        sequencer.setTempo(tempo)

        // Create the notes

        var midiSequenceIndex = 0

        for i in 0 ..< numberOfBeats {

            // Add notes to tracks

            metronomeTrack?.add(noteNumber: 60, velocity: 100, position: AKDuration(beats: Double(midiSequenceIndex)), duration: AKDuration(beats: 0.5))
            callbackTrack?.add(noteNumber: MIDINoteNumber(midiSequenceIndex), velocity: 100, position: AKDuration(beats: Double(midiSequenceIndex)), duration: AKDuration(beats: 0.5))

            print("Adding beat number \(i+1) at position: \(midiSequenceIndex)")
            midiSequenceIndex += 1

        }

        // Set the callback

        callbackInst.callback = {status, noteNumber, velocity in

            if status == .noteOn {

                let currentTime = Date().timeIntervalSinceReferenceDate
                let noteDelay = currentTime - ( self.startTime + ( 60.0 / self.tempo ) * Double(noteNumber) )
                print("Beat number: \(noteNumber) delay: \(noteDelay)")

            } else if ( noteNumber == midiSequenceIndex - 1 ) && ( status == .noteOff)  {

                print("Sequence ended.\n")
                self.toggleMetronomePlayback()

            } else {return}

        }

        // Preroll the sequencer

        sequencer.preroll()

        print("Setup ended.\n")

    }

    @IBAction func playButtonPressed(_ sender: UIButton) {

        toggleMetronomePlayback()

    }


    func toggleMetronomePlayback() {

        if sequencer.isPlaying == false {

            print("Playback started.")
            startTime = Date().timeIntervalSinceReferenceDate
            sequencer.play()

        } else {

            sequencer.stop()
            sequencer.rewind()

        }

    }

}

有人能帮忙吗?谢谢。

正如 Aure 评论的那样,启动延迟是一个已知问题。即使有预卷,仍然存在明显的延迟,尤其是在较高节奏时。

但是如果您使用的是循环序列,我发现您有时可以通过将序列的 'starting point' 设置为最终 MIDI 事件之后但在循环内的位置来减轻延迟的明显程度长度。如果您能找到一个好的位置,则可以在它循环回您的内容之前消除延迟影响。

确保在需要之前调用 setTime()(例如,在停止序列之后,而不是在您准备播放时),因为 setTime() 调用本身会引入大约 200 毫秒的不稳定。

编辑: 事后想想,您可以通过启用循环并使用任意长的序列长度在非循环序列上做同样的事情。如果您需要在 MIDI 内容结束时停止播放,您可以使用由紧跟在最后一个音符之后的 MIDI 事件触发的 AKCallbackInstrument 来实现。

经过一番测试,我发现实际上不是第一个音符播放,而是随后的音符提前播放。此外,启动音序器时准时播放的音符数量取决于设置的速度。

有趣的是,如果速度 < 400 会有一个音符按时弹奏,其他音符提前弹奏,如果 400 <= bpm < 800 会有两个音符弹奏正确,其他音符弹奏错误以此类推,每增加 400 bpm,您就会多一个正确演奏的音符。

所以...由于音符是提前弹奏而不是延迟弹奏,所以为我解决的解决方案是:

1) 使用未直接连接到轨道的 MIDI 输出但在回调中调用其 .play() 方法的采样器。

2) 跟踪音序器何时启动

3) 在每次回调时计算音符相对于开始时间的播放时间并存储实际时间,这样您就可以计算偏移量。

4) 在偏移你的 .play() 方法后使用计算出的偏移量到 dispatch_async。

就是这样,我在多个设备上测试了它,现在所有的音符都按时完美播放。

我有同样的问题,preroll 没有帮助,但我已经设法用一个专门的采样器解决了第一个音符。 我在另一个采样器上使用了延迟,大约 0.06 秒,效果很好。 有点愚蠢的解决方案,但它完成了工作,我可以继续这个项目:)

//This is for fixing AK bug that plays the first playback not in delay
    let fixDelay = AKDelay()
    fixDelay.dryWetMix = 1
    fixDelay.feedback = 0
    fixDelay.lowPassCutoff = 22000
    fixDelay.time = 0.06
    fixDelay.start()
    let preDelayMixer = AKMixer()
    let preFirstMixer = AKMixer()


    [playbackSampler,vocalSampler]  >>> preDelayMixer >>> fixDelay
    [firstNoteVocalSampler, firstRoundPlaybackSampler] >>> preFirstMixer
    [fixDelay,preFirstMixer] >>> endMixer