I have a Simon-like memory game where a sequence of tones is played and the user attempts to repeat the tones. The problem is that I get a horrible crackly, popping sound from time to time. It could be after just a few notes or as many as twenty. The sounds are all wav files that are two seconds in length. I cycle through ten players so that none of the sounds are clipped. (I've tried up to 50 but that didn't help anything.) I have also tried implementing audioPlayerDidFinishPlaying
so that I could stop the player once the sound is complete but that didn't help either. And finally, I added prepareToPlay()
- sadly, no difference.
One interesting issue that may or may not be related is that after a sequence of about 15 notes, the audio stops working all together. The app continues like normal, but without any sound.
Here is the audio portion of the code:
func playSound(_ soundID: Int) {
var soundName = ""
switch soundID {
case 0:
soundName = "beep1"
case 1:
soundName = "beep2"
case 2:
soundName = "beep3"
case 3:
soundName = "beep4"
case 4:
soundName = "swish3"
default:
soundName = "clank"
}
guard let url = Bundle.main.url(forResource: soundName, withExtension: "wav") else { return }
do {
buttonSound.insert(try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.wav.rawValue), at: playerIndex)
buttonSound[playerIndex].prepareToPlay()
buttonSound[playerIndex].delegate = self
if playerIndex < 10 {
buttonSound[playerIndex].play()
playerIndex += 1
} else {
buttonSound[playerIndex].play()
playerIndex = 0
}
} catch {
// error
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if flag {
player.stop()
}
}
/******** UPDATE *********/
I moved the AVAudioPlayer creation to its own class based on an outdated example I ran across...somewhere. It solved the audio completely cutting out after 15 tones problem, however, the cracks and pops are still there! I also tried rerecording the sounds with no luck.
Here is the class for creating AVAudioPlayers as needed:
import UIKit
import AVFoundation
private var players: [AVAudioPlayer] = []
class AVAudioPlayerPool: NSObject {
class func playerWithURL(url: URL) -> AVAudioPlayer? {
let availablePlayers = players.filter { (player) -> Bool in
return player.isPlaying == false && player.url == url
}
if let playerToUse = availablePlayers.first {
playerToUse.prepareToPlay()
return playerToUse
}
do {
let newPlayer = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.wav.rawValue)
players.append(newPlayer)
newPlayer.prepareToPlay()
return newPlayer
} catch {
return nil
}
}
}
Playing a sound is simple:
func playSound(_ soundID: Int) {
var soundName = ""
switch soundID {
case 0:
soundName = "tt1"
case 1:
soundName = "tt2"
case 2:
soundName = "tt3"
case 3:
soundName = "tt4"
case 4:
soundName = "swish3"
default:
soundName = "clank"
}
guard let url = Bundle.main.url(forResource: soundName, withExtension: "wav") else { return }
let player = AVAudioPlayerPool.playerWithURL(url: url)
player?.play()
}
If anyone has any thoughts - even if it's just the next step you might try...
After many hours of experimentation, I'm going to say that the problem was caused by the sound I had chosen - a big, hollow, square wave synth sound with reverb. I first tried reducing the level thinking that might help. Next I tried all sorts of different sounds, but everything crackled. I think that had more to do with the way I was assigning players. Once I fixed the player problem, I didn't think to go back and try different sounds again. Until, I did...and it works!