Первая нота, сыгранная в AKSequencer, выключена - PullRequest
0 голосов
/ 28 августа 2018

Я использую AKSequencer для создания последовательности нот, которые играют AKMidiSampler. Моя проблема в том, что при более высоких темпах первая нота всегда играет с небольшой задержкой, независимо от того, что я делаю.

Я пробовал предварительно выполнить последовательность, но это не поможет. Замена AKMidiSampler на AKSampler или AKSamplePlayer (и использование дорожки обратного вызова для их воспроизведения) также не помогла, хотя и заставила меня подумать, что проблема, вероятно, заключается в секвенсоре или в способе создания заметок.

Вот пример того, что я делаю (я пытался сделать это настолько просто, насколько мог):

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 голосов
/ 13 сентября 2018

После небольшого тестирования я на самом деле обнаружил, что воспроизводится не первая нота, а последующие ноты, которые играют заранее. Кроме того, количество нот, которые воспроизводятся точно во время запуска секвенсора, зависит от установленного темпа.

Самое смешное, что если темп <400, будет одна нота, сыгранная вовремя, а другие заранее, если это 400 <= bpm <800, то две ноты будут сыграны правильно, а другие заранее и и так далее, за каждые 400 ударов в минуту вы получаете еще одну ноту, сыгранную правильно. </p>

Итак ... поскольку ноты разыгрываются заранее, а не поздно, решение, которое решило это для меня:

1) Используйте сэмплер, который не подключен напрямую к миди-выходу трека, но имеет метод .play(), вызываемый внутри обратного вызова.

2) Следите за тем, когда запускается секвенсор

3) При каждом обратном вызове подсчитайте, когда нота должна воспроизводиться относительно времени начала, и укажите, сколько времени на самом деле, чтобы вы могли рассчитать смещение.

4) использовать вычисленное смещение для dispatch_async после смещения вашего .play() метода.

И все, я протестировал это на нескольких устройствах, и теперь все ноты отлично воспроизводятся вовремя.

0 голосов
/ 01 апреля 2019

У меня была та же проблема, предварительная проверка не помогла, но мне удалось решить ее с помощью специального сэмплера для первых заметок. Я использовал задержку на другом сэмплере, около 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
0 голосов
/ 28 августа 2018

Как прокомментировал Aure, задержка запуска является известной проблемой. Даже с предварительным просмотром все еще заметны задержки, особенно при высоких темпах.

Но если вы используете циклическую последовательность, я обнаружил, что иногда вы можете уменьшить, насколько заметна задержка, установив «начальную точку» последовательности в положение после последнего события MIDI, но в пределах длины цикла. Если вы можете найти хорошую позицию, вы можете убрать эффекты задержки, прежде чем она вернется к вашему контенту.

Обязательно вызовите setTime(), прежде чем вам это понадобится (например, после остановки последовательности, а не когда вы готовы играть), потому что сам вызов setTime() может принести около 200 мс выигрыша.

Edit: В качестве запоздалой мысли вы могли бы сделать то же самое в нецикличной последовательности, включив зацикливание и используя произвольно большую длину последовательности. Если вам необходимо остановить воспроизведение в конце содержимого MIDI, вы можете сделать это с помощью AKCallbackInstrument, запускаемого событием MIDI, помещенным сразу после последней ноты.

...