使用AVAudioEngine为低延迟节拍器安排声音

时间:2015-09-18 00:21:21

标签: ios swift avaudioengine avaudioplayernode

我正在创建一个节拍器作为更大的应用程序的一部分,我有一些非常短的wav文件用作单独的声音。我想使用AVAudioEngine,因为NSTimer存在严重的延迟问题,而Core Audio在Swift中实现起来似乎相当艰巨。我尝试了以下操作,但我目前无法执行前3个步骤,并且我想知道是否有更好的方法。

代码大纲:

  1. 根据节拍器的当前设置(每个节拍的节拍数和每个节拍的细分数;节拍的文件A,细分的文件B)创建文件URL数组
  2. 根据文件的速度和长度,以编程方式创建一个具有适当静音帧数的wav文件,并将其插入每个声音之间的数组中
  3. 将这些文件读入单个AudioBuffer或AudioBufferList
  4. audioPlayer.scheduleBuffer(buffer, atTime:nil, options:.Loops, completionHandler:nil)
  5. 到目前为止,我已经能够播放单个声音文件的循环缓冲区(步骤4),但我还没有能够从文件数组构建缓冲区或以编程方式创建静默,也没有在StackOverflow上找到了解决此问题的任何答案。所以我猜这不是最好的方法。

    我的问题是:是否可以使用AVAudioEngine安排低延迟的声音序列然后循环该序列?如果没有,哪个框架/方法最适合在Swift编码时调度声音?

3 个答案:

答案 0 :(得分:3)

我认为以尽可能低的时间错误播放声音的一种可能方法是直接通过回调提供音频样本。在iOS中,您可以使用AudioUnit执行此操作。

在此回调中,您可以跟踪样本计数并了解您现在的样本。从样本计数器,您可以转到时间值(使用采样率)并将其用于节拍器等高级任务。如果您发现是时候播放节拍器声音,那么您只需开始将声音样本从该声音复制到缓冲区。

这是一个没有任何代码的理论部分,但你可以找到许多AudioUnit和回调技术的例子。

答案 1 :(得分:2)

我能够制作一个包含文件声音和所需长度静音的缓冲区。希望这会有所帮助:

// audioFile here – an instance of AVAudioFile initialized with wav-file
func tickBuffer(forBpm bpm: Int) -> AVAudioPCMBuffer {
    audioFile.framePosition = 0 // position in file from where to read, required if you're read several times from one AVAudioFile
    let periodLength = AVAudioFrameCount(audioFile.processingFormat.sampleRate * 60 / Double(bpm)) // tick's length for given bpm (sound length + silence length)
    let buffer = AVAudioPCMBuffer(PCMFormat: audioFile.processingFormat, frameCapacity: periodLength)
    try! audioFile.readIntoBuffer(buffer) // sorry for forcing try
    buffer.frameLength = periodLength // key to success. This will append silcence to sound
    return buffer
}

// player – instance of AVAudioPlayerNode within your AVAudioEngine
func startLoop() {
    player.stop()
    let buffer = tickBuffer(forBpm: bpm)
    player.scheduleBuffer(buffer, atTime: nil, options: .Loops, completionHandler: nil)
    player.play()
}

答案 2 :(得分:2)

扩展5hrp的答案:

采用简单的情况,你有两个节拍,一个乐观(tone1)和一个downbeat(tone2),你希望它们彼此不同步,所以音频将(上,下,上,下)到某个bpm。

你需要两个AVAudioPlayerNode实例(每个节拍一个),我们称之为 audioNode1 audioNode2

你想要的第一个节拍,所以正常设置:

let buffer = tickBuffer(forBpm: bpm)
audioNode1player.scheduleBuffer(buffer, atTime: nil, options: .loops, completionHandler: nil)

然后对于第二拍,你希望它完全不同相,或者从t = bpm / 2开始。为此,您可以使用 AVAudioTime 变量:

audioTime2 = AVAudioTime(sampleTime: AVAudioFramePosition(AVAudioFrameCount(audioFile2.processingFormat.sampleRate * 60 / Double(bpm) * 0.5)), atRate: Double(1))

您可以在缓冲区中使用此变量,如下所示:

audioNode2player.scheduleBuffer(buffer, atTime: audioTime2, options: .loops, completionHandler: nil)

这将循环播放你的两个节拍,bpm / 2彼此不同步!

很容易看出如何将其概括为更多节拍,以创建一个完整的条形图。这不是最优雅的解决方案,因为如果你想说第16个音符你必须创建16个节点。