How to play a a sound from the app resources folder on button press, with AudioKit? - audiokit

I I'm trying to play a random sound from the app resources folder in an iOS app.
I am using AudioKit v5 v5-main branch, and Swift 5.3 on XCode 12. The app builds but crashes when I press the play button in the UI; the error is listed below the code:
class ViewController: UIViewController {
...
// For audio playback
let engine = AudioEngine()
let player = AudioPlayer()
let mixer = Mixer()
override func viewDidLoad() {
super.viewDidLoad()
...
// Initialize AudioKit pipeline
mixer.addInput(player)
engine.output = mixer
do {
try engine.start()
} catch let err {
print(err)
}
}
#IBAction func playButton(_ sender: UIButton) {
playSound()
}
func playSound() {
let randomSound = sounds.randomElement()
let url = Bundle.main.url(forResource: randomSound, withExtension: "wav")
print("URL: \(url)")
// player = try! AVAudioPlayer(contentsOf: url!)
player.scheduleFile(url!, at: nil)
player.play()
}
}
The error produced when the button is pressed is:
*** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: _outputFormat.channelCount == buffer.format.channelCount'
terminating with uncaught exception of type NSException
(lldb)
I believe that I may be failing to initialize the audio engine properly, but I'm not sure how/where to do that. Any pointers?

Related

"AudioKit v5-main" crashes with mic Pitch Tap

I use "AudioKit v5-main" to implement the recording function.
The recording function is working properly, but it crashes when I try to tap the input from the microphone.
The code is:
class ViewController: UIViewController {
let engine = AudioEngine()
var silence: Fader!
var boostMic: Fader!
var tracker: PitchTap!
var recorder: NodeRecorder!
var player = AudioPlayer()
var mixer = Mixer()
override func viewDidLoad() {
super.viewDidLoad()
guard let mic = engine.input else {
debugPrint("error")
return
}
boostMic = Fader(mic, gain: 5)
tracker = PitchTap(boostMic, handler: { (pitch, amp) in
DispatchQueue.main.async {
debugPrint("amp: \(amp.first!.description)")
}
})
do {
recorder = try NodeRecorder(node: boostMic)
} catch {
debugPrint(error.localizedDescription)
}
silence = Fader(boostMic, gain: 0)
mixer.addInput(silence)
mixer.addInput(player)
engine.output = mixer
Settings.audioInputEnabled = true
do {
try engine.start()
tracker.start()
NodeRecorder.removeTempFiles()
try recorder.record()
} catch {
debugPrint(error.localizedDescription)
}
}
}
The errors output during a crash are:
[avae] AVAEInternal.h:76 required condition is false: [AVAEGraphNode.mm:817:CreateRecordingTap: (nullptr == Tap())]
*** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: nullptr == Tap()'
The development environment is as follows:
macOS 10.15.7
xCode 12.2 (12B45b)
AudioKit v5-main branch
Test with actual iPhoneX
Is there a way to avoid the crash?
I solved this problem by modifying the AudioKit framework.

How do I perform segue AFTER the sound has stopped playing

I have made a custom LaunchScreen in the Main.Storyboard to make it seem that a sound is actually coming from the LaunchScreen. The sound works fine and the segue to the next view controller as well. The only problem is that the segue happens before the sound has stopped playing. I would like the sound to complete before making the segue. Logically, it should work since the performSegue is directly after the .play(). But it seems that the two happens simultaneously. Here's my code:
super.viewDidLoad()
//PLAY SOUND CLIP//
let musicFile = Bundle.main.path(forResource: "fanfare", ofType: ".mp3")
do {
try musicSound = AVAudioPlayer(contentsOf: URL (fileURLWithPath: musicFile!))
} catch { print("ERROR PLAYING MUSIC")}
musicSound.play() //WORKS
//
DispatchQueue.main.async() {
self.performSegue(withIdentifier: "myLaunchSegue", sender: self) //WORKS
}
I have tried to add:
perform(Selector(("showNavController")), with: self, afterDelay: 3)
where "showNavController" simply is the segue:
func showNavController() {
performSegue(withIdentifier: "myLaunchSegue", sender: nil)
}
but the program crashes with the error "uncaught exception.... ....unrecognized selector sent to instance"
I have also tried to add a boolean to keep the program from progressing until the sound has played, but didn't get it to work. Any ideas?
//////////////////////////
Update:
Trying Russels answer, but have a few questions. Setting AVAudioPlayer as delegate, does that mean setting it next to the class like this:
class MyLaunchViewController: UIViewController, AVAudioPlayer { ...
Also, how do I call the function audioPlayerDidFinishPlaying? Like so:
audioPlayerDidFinishPlaying(musicSound, successfully: true)
I'll post the whole code block. Makes it easier to understand...
import UIKit
import AVFoundation //FOR SOUND
class MyLaunchViewController: UIViewController, AVAudioPlayer {
var musicSound: AVAudioPlayer = AVAudioPlayer() //FOR SOUND
override func viewDidLoad() {
super.viewDidLoad()
//PLAY SOUND CLIP//
let musicFile = Bundle.main.path(forResource: "fanfare", ofType: ".mp3")
do {
try musicSound = AVAudioPlayer(contentsOf: URL (fileURLWithPath: musicFile!))
} catch { print("ERROR PLAYING MUSIC")}
musicSound.play() //WORKS
//
audioPlayerDidFinishPlaying(musicSound, successfully: true)
}
optional func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
DispatchQueue.main.async() {
self.performSegue(withIdentifier: "myLaunchSegue", sender: self)
}
}
I get an error when writing AVAudioPlayer in the class (perhaps I misunderstood what I was supposed to do?). It says I have multiple inheritances. Also, it doesn't want me to set the new function as optional, as its only for protocol members. Finally, If I correct the errors and run the program, the next segue runs before the sound has finished playing... :( sad panda.
You need to make your LaunchScreen an AVAudioPlayerDelegate, and then use the audioPlayerDidFinishPlaying callback. Here's all you need in the first controller
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate
{
var musicSound: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
//PLAY SOUND CLIP//
let musicFile = Bundle.main.path(forResource: "sound", ofType: ".wav")
do {
try musicSound = AVAudioPlayer(contentsOf: URL (fileURLWithPath: musicFile!))
} catch { print("ERROR PLAYING MUSIC")}
musicSound?.delegate = self
musicSound!.play() //WORKS
print("Next line after play")
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
DispatchQueue.main.async() {
print("audioPlayerDidFinishPlaying")
self.performSegue(withIdentifier: "myLaunchSegue", sender: self)
}
}
}
You can get more details here https://developer.apple.com/documentation/avfoundation/avaudioplayerdelegate/1389160-audioplayerdidfinishplaying

AudioKit crash - How to play audio file with it?

I'm trying to play a sound file such as
/Users/NEO/Library/Developer/CoreSimulator/Devices/D21A7767-E6F6-4B43-A76B-DC6E0F628B16/data/Containers/Data/Application/3F8A82F6-739F-40EA-BEA9-DF3560D0D8BB/Documents/Remember-When.mp3
I'm using AudioKit/Swift 3, here are my code:
do {
debugPrint(sound.filePath)
let audioFile = try AKAudioFile(forReading: URL(fileURLWithPath: sound.filePath))
player = try AKAudioPlayer(file: audioFile)
player.play()
}catch let exception {
}
the Xcode crash and console said:
Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'required condition is false: _engine->IsRunning()'
I tried with AVAudioFoundation so It's work well.
do {
player = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: sound.filePath))
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch let error {
print(error.localizedDescription)
}
Could you give me an advise ?
I believe when ever you receive "'required condition is false: _engine->IsRunning()'" the AudioKit engine hasn't been started yet. So calling AudioKit.start() prior to playing should solve your issue

Background Music Stop/Mute on iOS

I'm creating my first app. I have an app with music playing in the background with the following code:
var backgroundMusicPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
//background Music
func playBackgroundMusic(filename: String) {
let url = NSBundle.mainBundle().URLForResource(filename, withExtension: nil)
guard let newURL = url else {
print("Could not find file: \(filename)")
return
}
do {
backgroundMusicPlayer = try AVAudioPlayer(contentsOfURL: newURL)
backgroundMusicPlayer.numberOfLoops = -1
backgroundMusicPlayer.prepareToPlay()
backgroundMusicPlayer.play()
} catch let error as NSError {
print(error.description)
}
}
playBackgroundMusic("Starship.wav")
}
So what should I do in order to stop/mute the background music when I switch to another ViewController? Should I do this my FirstViewController or SecondViewController?
Obviously, I don't want the sound to be off in the SecondViewController as I have other stuff that will be playing there.
To mute sound I simply mute the volume.
backgroundMusicPlayer.volume = 0
and set it to normal if I want sound
backgroundMusicPlayer.volume = 1
If you just want to pause music you can call
backgroundMusicPlayer.pause()
To resume you call
backgroundMusicPlayer.resume()
If you want to stop music and reset it to the beginning you say this
backgroundMusicPlayer.stop()
backgroundMusicPlayer.currentTime = 0
backgroundMusicPlayer.prepareToPlay()
Did you also consider putting your music into a singleton class so its easier to play music in your different viewControllers.
Not sure this is what you are looking for as your question is a bit vague.

How to stop background music with button and if/else in Swift?

In my game I added a button to the GameScene and played music:
let url = NSBundle.mainBundle().URLForResource("Happy Background Music", withExtension: "mp3")
let bgMusic = AVAudioPlayer(contentsOfURL: url, error: nil)
#IBAction func SoundOnOff(sender: UIButton) {
bgMusic.numberOfLoops = -1
bgMusic.play()
}
and the music played, but I want to add an if so when the button is pressed the music will stop. What if should I write?
Change your code with this,
let url = NSBundle.mainBundle().URLForResource("Happy Background Music", withExtension: "mp3")
do{
let bgMusic = try AVAudioPlayer(contentsOfURL: url!)
}catch{
print(error)
}
#IBAction func SoundOnOff(sender: UIButton) {
if bgMusic.playing {
bgMusic.stop()
} else {
self.bgMusic.play()
}
}
Edit: Description
playing- Property
-A Boolean value that indicates whether the audio player is playing (true) or not (false). (read-only)
AVAudioPlayer has a built-in property playing, which you can use to decide whether to play or pause the music:
#IBAction func SoundOnOff(sender: UIButton) {
if bgMusic.playing {
bgMusic.pause()
} else {
bgMusic.numberOfLoops = -1
bgMusic.play()
}
}
Just seen that this is only available in iOS9 and later. So if you're targeting iOS9 then this is possible.
If you're using SpriteKit to write your game I would recommend using the SKAudioNode class to play your BGMusic.
let url = NSBundle.mainBundle().URLForResource("Happy Background Music", withExtension: "mp3")
let audioNode = SKAudioNode(URL:url)
audioNode.positional = false
audioNode.autoplayLooped = true
self.addChild(audioNode)
To pause the music you create an SKAction ...
let pauseAction = SKAction.pause()
audioNode.runAction(pauseAction)
You can resume it also but just trying to find the code for that...

Resources