Для синхронизации звука io часто лучше создать эталонное время, а затем использовать это время для всех расчетов, связанных с синхронизацией.
AVAudioPlayerNode.play (at:) - это то, что вам нужно для игрока.Для касания вам необходимо отфильтровать (частичные) буферы вручную, используя время, указанное в закрытии.К сожалению, AVAudioSequencer не имеет возможности запуска в определенное время, но вы можете получить эталонное время, соотнесенное с ударом, с помощью уже воспроизводимого секвенсора, используя hostTime (forBeats) .Если я правильно помню, вы не можете установить секвенсор в отрицательное положение, так что это не идеально.
Вот хакерский обходной путь, который должен дать очень точные результаты:
AVAudioSequencer должен быть запущен до получения эталонного времени, сместить все ваши midi-данные на 1, запустить секвенсор, а затем сразу получитьэталонное время соотносится с ударом 1, затем синхронизирует запуск проигрывателя с этим временем, а также использует его для фильтрации нежелательного звука, захваченного касанием.
func syncStart() throws {
//setup
sequencer.currentPositionInBeats = 0
player.scheduleFile(myFile, at: nil)
player.prepare(withFrameCount: 4096)
// Start and get reference time of beat 1
try sequencer.start()
// Wait until first render cycle completes or hostTime(forBeats) will err - AVAudioSequencer is fragile :/
while (self.sequencer.currentPositionInBeats <= 0) { usleep(UInt32(0.001 * 1000000.0)) }
var nsError: NSError?
let hostTime = sequencer.hostTime(forBeats: 1, error: &nsError)
let referenceTime = AVAudioTime(hostTime: hostTime)
// AVAudioPlayer is great for this.
player.play(at: referenceTime)
// This just rejects buffers that come too soon. To do this right you need to record partial buffers.
engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: nil) { (buffer, audioTime) in
guard audioTime.hostTime >= referenceTime.hostTime else { return }
self.recordBuffer(buffer: buffer)
}
}