I'm trying to create a solid metronome that has the option to play accented notes (ie a different note at the beginning of each bar). Having done research on the topic, I've learnt that using Timer (formerly NSTimer) isn't the way to go due to it's inaccuracy.
One solution i came across was using the AVAudioEngine, which works great for a steady metronome, however I haven't figured out how to keep track of where in the bar you would be, and therefore putting in a statement to change which audio file you play depending on your position in time seems tricky. Here is the code for the metronome class.
import Foundation
import UIKit
import AVFoundation
class Metronome {
var audioPlayerNode:AVAudioPlayerNode
var audioFile:AVAudioFile
var audioEngine:AVAudioEngine
init (fileURL: URL) {
audioFile = try! AVAudioFile(forReading: fileURL)
audioPlayerNode = AVAudioPlayerNode()
audioEngine = AVAudioEngine()
audioEngine.attach(self.audioPlayerNode)
audioEngine.connect(audioPlayerNode, to: audioEngine.mainMixerNode, format: audioFile.processingFormat)
try! audioEngine.start()
}
func generateBuffer(forBpm bpm: Int) -> AVAudioPCMBuffer {
audioFile.framePosition = 0
let periodLength = AVAudioFrameCount(audioFile.processingFormat.sampleRate * 60 / Double(bpm))
let buffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: periodLength)
try! audioFile.read(into: buffer!)
buffer?.frameLength = periodLength
return buffer!
}
func play(bpm: Int) {
let buffer = generateBuffer(forBpm: bpm)
self.audioPlayerNode.play()
self.audioPlayerNode.scheduleBuffer(buffer, at: nil, options: .loops, completionHandler: nil)
}
func stop() {
audioPlayerNode.stop()
}
}
And to actually play the sound, within the ViewController I have the following code:
required init?(coder aDecoder: NSCoder) {
let fileUrl = Bundle.main.url(forResource: "tone", withExtension: "wav")
metronome = Metronome(fileURL: fileUrl!)
super.init(coder: aDecoder)
}
with the following code to start and stop that I put inside of an @IBAction button.
metronome.play(bpm: 120)
metronome.stop()
As you can see it's not straight forward to count the amount of beats, as the file is essentially played on repeat with a set buffer time in between (as far as I understand it).
Any help/advice is appreciated, as I am pretty new to Swift.
Thanks!