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

时间:2018-08-28 16:16:42

标签: audio timing audiokit aksequencer

我正在使用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()

        }

    }

}

有人可以帮忙吗?谢谢。

3 个答案:

答案 0 :(得分:3)

正如Aure所说,启动延迟是一个已知问题。即使使用预卷,仍然存在明显的延迟,尤其是在较高速度下。

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

请确保在需要之前调用setTime()(例如,在停止音序之后,而不是准备演奏时),因为setTime()调用本身会带来大约200毫秒的清醒时间。

编辑: 事后,您可以通过启用循环并使用任意长的序列长度来对非循环序列执行相同的操作。如果您需要在MIDI内容的结尾处停止播放,则可以使用AKCallbackInstrument来完成此操作,该AKCallbackInstrument由紧接在最后音符之后的MIDI事件触发。

答案 1 :(得分:0)

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

有趣的是,如果速度<400,则将按时演奏一个音符,而其他音符将提前播放;如果速度为<400 b = <400,则将正确演奏两个音符,而其他音符将被提前弹奏。依此类推,每增加400 bpm,您就会再正确演奏一个音符。

所以...由于音符是提前播放而不是延迟播放,因此为我解决的解决方案是:

1)使用采样器,该采样器未直接连接到音轨的midi输出,但其.play()方法在回调内部被调用。

2)跟踪音序器何时启动

3)在每个回调中,计算音符相对于开始时间的播放时间并存储实际时间,以便随后计算偏移量。

4)在您的.play()方法偏移之后,使用计算出的偏移量对dispatch_async进行操作。

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

答案 2 :(得分:0)

我遇到了同样的问题,预卷没有帮助,但是我设法使用专用的采样器解决了前几个音符。 我在另一个采样器上使用了一个延迟,大约为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