3

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

        }

    }

}

有人可以帮忙吗?谢谢你。

4

3 回答 3

3

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

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

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

编辑:作为事后的想法,您可以通过启用循环并使用任意长的序列长度对非循环序列执行相同的操作。如果您需要在 MIDI 内容结束时停止播放,您可以使用 AKCallbackInstrument 执行此操作,该 AKCallbackInstrument 由放置在最后一个音符之后的 MIDI 事件触发。

于 2018-08-28T17:35:37.710 回答
2

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

有趣的是,如果节奏 < 400 会有一个音符按时演奏,其他音符会提前,如果是 400 <= bpm < 800,则会有两个音符正确演奏,其他音符会提前等等,每增加 400 bpm,您就可以再正确弹奏一个音符。

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

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

2) 跟踪定序器何时启动

3)在每个回调计算音符应该何时播放相对于开始时间并存储它实际是什么时间,这样你就可以计算偏移量。

4)在您的方法偏移后使用计算出的偏移量来dispatch_async .play()

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

于 2018-09-13T08:38:23.947 回答
1

我有同样的问题,预卷没有帮助,但我已经设法用一个专门的采样器来解决这个问题。我在另一个采样器上使用了大约 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
于 2019-04-01T09:05:33.007 回答