Given a class that plays a file named "WoodClaves.aif" repeatedly based on a given BPM value.
import Foundation
import SwiftUI
import AVFoundation
class MetronomTimerModel: ObservableObject {
var bpm: Double = 120 // Default BPM
@Published var beatCounterIndex = 0
var metronomeTimer: Timer?
var audioPlayerNode:AVAudioPlayerNode
var audioFile:AVAudioFile
var audioEngine:AVAudioEngine
init() {
audioFile = try! AVAudioFile(forReading: Bundle.main.url(forResource: "WoodClaves", withExtension: "aif")!)
audioPlayerNode = AVAudioPlayerNode()
audioEngine = AVAudioEngine()
audioEngine.attach(self.audioPlayerNode)
audioEngine.connect(audioPlayerNode, to: audioEngine.mainMixerNode, format: audioFile.processingFormat)
do {
try AVAudioSession.sharedInstance().setCategory(.playback)
} catch let error {
print(error.localizedDescription)
}
try! audioEngine.start()
}
func startMetronome() {
self.bpm = Double(UserDefaults.standard.integer(forKey: "bpm"))
let interval = 60.0 / bpm
metronomeTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { timer in
if self.beatCounterIndex == 0 {
self.playTick(bpm: Int(self.bpm))
} else {
//...
}
// Increment the beatCounter and reset it after each 4 measure
self.beatCounterIndex = (self.beatCounterIndex + 1) % 4
}
// Run the timer on the main run loop
if let metronomeTimer = metronomeTimer {
RunLoop.main.add(metronomeTimer, forMode: .common)
}
}
// Function to stop the metronome, invalidates timer and resets beat counter
func stopMetronome() {
//...
}
func playTick(bpm: Int) {
do {
let buffer = generateBuffer(forBpm: bpm)
self.audioPlayerNode.play()
self.audioPlayerNode.scheduleBuffer(buffer, at: nil, options: .loops, completionHandler: nil)
} catch let error {
print(error.localizedDescription)
}
}
func stopTicking() {
audioPlayerNode.stop()
}
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!
}
}
This works fine as long as I run it in the simulator. However, when I run it on a physical device, there's no audio output nor any error.
Silent mode is turned off.
Buy the way, the same problems occurs if I utilize AVAudioPlayer.
What's wrong?