Первая нота, сыгранная в AKSequencer, выключена
Я использую 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()
}
}
}
Кто-нибудь может помочь? Спасибо.
2 ответа
Как прокомментировал Aure, задержка запуска является известной проблемой. Даже с предварительным просмотром все еще заметны задержки, особенно при высоких темпах.
Но если вы используете последовательность зацикливания, я обнаружил, что иногда вы можете уменьшить, насколько заметна задержка, установив "начальную точку" последовательности в положение после последнего события MIDI, но в пределах длины цикла. Если вы можете найти хорошую позицию, вы можете устранить эффекты задержки, прежде чем она вернется к вашему контенту.
Обязательно позвони setTime()
до того, как вам это понадобится (например, после остановки последовательности, а не когда вы будете готовы играть), потому что setTime()
Сам вызов может ввести около 200 мс выигрыша.
Редактировать: в качестве запоздалой мысли, вы можете сделать то же самое на нециклической последовательности, включив зацикливание и используя произвольно большую длину последовательности. Если вам необходимо остановить воспроизведение в конце содержимого MIDI, вы можете сделать это с помощью AKCallbackInstrument, запускаемого событием MIDI, помещенным сразу после последней ноты.
После небольшого тестирования я фактически обнаружил, что это не первая нота, которая играет, а последующие ноты, которые играют заранее. Кроме того, количество нот, которые воспроизводятся точно во время запуска секвенсора, зависит от установленного темпа.
Самое смешное, что если темп < 400, будет одна нота, сыгранная вовремя, а другие заранее, если 400 <= bpm < 800, то две ноты будут сыграны правильно, а другие заранее и так далее, за каждые 400 ударов в минуту вы получаете еще одну ноту, сыгранную правильно.
Итак... поскольку ноты играются заранее, а не поздно, решение, которое решило это для меня:
1) Используйте сэмплер, который не подключен напрямую к MIDI-выходу трека, но имеет свой .play()
Метод вызывается внутри обратного вызова.
2) Следите за тем, когда запускается секвенсор
3) При каждом обратном вызове подсчитайте, когда нота должна воспроизводиться относительно времени начала, и сохраните, сколько времени на самом деле, так что вы можете рассчитать смещение.
4) использовать вычисленное смещение для dispatch_async после смещения вашего .play()
метод.
И все, я протестировал это на нескольких устройствах, и теперь все ноты отлично воспроизводятся в срок.
Спасибо, @c_booth, вы всегда очень полезны, к сожалению, хотя я не использую циклическую последовательность, но я все равно нашел обходной путь (он далек от совершенства, но пока работает).
Я пропускаю добавление первой ноты к последовательности и воспроизводю ее напрямую, как только пользователь нажимает кнопку воспроизведения. Я также добавляю небольшую задержку (обратно пропорциональную заданному темпу, так как при более низких темпах задержка незначительна) к позиции нот последовательности, чтобы дополнительно компенсировать эффект. Я знаю, что добавление задержки - это не то, что можно считать хорошим кодированием, поскольку оно зависит от используемого устройства, но я проверил его на нескольких устройствах, и время лучше, чем задержка, которую я получаю без нее.
У меня была та же проблема, предварительная проверка не помогла, но мне удалось решить ее с помощью специального сэмплера для первых заметок. Я использовал задержку на другом сэмплере, около 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