我正在使用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()
}
}
}
有人可以帮忙吗?谢谢。
正如Aure评论的那样,启动延迟是一个已知问题。即使使用预卷,仍然会有明显的延迟,尤其是在更高的节奏下。
但是如果你使用循环序列,我发现你有时可以通过将序列的“起始点”设置到最终MIDI事件之后的位置,但在循环长度内来减轻延迟的显着程度。如果您能找到一个好的位置,您可以在它循环回到您的内容之前获得延迟效果。
确保在需要之前调用setTime()
(例如,在停止序列之后,而不是在你准备好玩的时候),因为setTime()
call本身可以引入大约200ms的难度。
编辑:作为事后的想法,你可以通过启用循环和使用任意长的序列长度在非循环序列上做同样的事情。如果您需要在MIDI内容结束时停止播放,则可以使用由最后音符后面的MIDI事件触发的AKCallbackInstrument来完成此操作。
经过一些测试后,我发现它并不是第一个播放的音符,而是随后播放的音符。此外,启动音序器时准确播放的音符量取决于设定的速度。
有趣的是,如果节奏<400会有一个音符按时播放,其他音符会提前播放,如果是400 <= bpm <800则会有两个音符正确播放而其他音符提前播放等等,每增加400 bpm,你可以正确播放一个音符。
所以...因为笔记是提前播放而不是迟到,所以解决它的解决方案是:
1)使用未直接连接到轨道midi输出的采样器,但在回调中调用.play()
方法。
2)跟踪音序器何时开始
3)在每次回调时计算音符应该与开始时间相关的时间并存储实际的时间,这样你就可以计算出偏移量。
4)在偏移你的.play()
方法之后使用计算的偏移量来调度dispas_async。
就是这样,我在多台设备上进行了测试,现在所有笔记都按时完美播放。
我有同样的问题,preroll没有帮助,但我设法用第一个笔记的专用采样器解决它。我在另一个采样器上使用延迟,大约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