我正在创建一个节拍器作为更大的应用程序的一部分,我有一些非常短的wav文件用作单独的声音。我想使用AVAudioEngine,因为NSTimer存在严重的延迟问题,而Core Audio在Swift中实现起来似乎相当艰巨。我尝试了以下操作,但我目前无法执行前3个步骤,并且我想知道是否有更好的方法。
代码大纲:
audioPlayer.scheduleBuffer(buffer, atTime:nil, options:.Loops, completionHandler:nil)
到目前为止,我已经能够播放单个声音文件的循环缓冲区(步骤4),但我还没有能够从文件数组构建缓冲区或以编程方式创建静默,也没有在StackOverflow上找到了解决此问题的任何答案。所以我猜这不是最好的方法。
我的问题是:是否可以使用AVAudioEngine安排低延迟的声音序列然后循环该序列?如果没有,哪个框架/方法最适合在Swift编码时调度声音?
答案 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个节点。