Audio kit is slowly - audiokit

I am trying to use the library to get data from Bluetooth device and play it live as I hit the button .
The Bluetooth input is fast and I get the input at the moment I hit the button but the library is a bottle neck , it takes about 80-100ms or more, till I hear the sound.
Also, if I hit twice fast he play only the first and wait for it to end till I can play next note.
What is the best way to use it to play live instrument ? this is my implelemnation:
First I load, then every time I have Bluetooth input, I play :
{
do {
let file = try AKAudioFile(readFileName: "A.wav", baseDir: .resources)
player = try AKAudioPlayer(file: file)
player.looping = false
AudioKit.output = player
AudioKit.start()
}
catch let error1 as NSError
{
} catch {
}
}
func play()
{
player.play()
}

Related

How to fade out one audio file while playing the next in AudioKit

I'm creating a traditional music player with AudioKit. Initially it plays one file, then you can tap the next button to skip to the next audio file. The songs aren't all known up-front, the playlist can change while a song is currently playing, so it's not known what the next audio file will be until we go to play it.
My current implementation for that works well. I create a player for the first audio file and set that to AudioKit.output and call AudioKit.start() then player.play(), then when next is tapped I call AudioKit.stop() and then create the new player, set it as the output, start AudioKit, and play the new player. If you don't stop AudioKit before modifying the output, you'll encounter an exception as I saw previously.
Now you should also be able to tap a fade button which will crossfade between the current song and the next song - fade out the current song for 3 seconds and immediately play the next song. This is proving to be difficult. I'm not sure how to properly implement it.
The AudioKit playgrounds have a Mixing Nodes example where multiple AKPlayers are created, AKMixer is used to combine them, and the mixer is assigned to the output. But it appears you cannot change the players in the mixer. So the solution I have currently is to stop AudioKit when the fade button is tapped, recreate the AKMixer adding a new player for the next song, start AudioKit, then resume playback of the first player and play the new player. This experience isn't smooth; you can certainly hear the audio stop and resume.
How can I properly fade out one song while playing the next song?
Please see my sample project on GitHub. I've included its code below:
final class Maestro: NSObject {
static let shared = Maestro()
private var trackPlayers = [AKPlayer]() {
didSet {
do {
try AudioKit.stop()
} catch {
print("Maestro AudioKit.stop error: \(error)")
}
mixer = AKMixer(trackPlayers)
AudioKit.output = mixer
do {
try AudioKit.start()
} catch {
print("Maestro AudioKit.start error: \(error)")
}
trackPlayers.forEach {
if $0.isPlaying {
let pos = $0.currentTime
$0.stop()
$0.play(from: pos)
}
}
}
}
private var mixer: AKMixer?
private let trackURLs = [
Bundle.main.url(forResource: "SampleAudio_0.4mb", withExtension: "mp3")!,
Bundle.main.url(forResource: "SampleAudio_0.7mb", withExtension: "mp3")!
]
func playFirstTrack() {
playNewPlayer(fileURL: trackURLs[0])
}
func next() {
trackPlayers.forEach { $0.stop() }
trackPlayers.removeAll()
playNewPlayer(fileURL: trackURLs[1])
}
func fadeAndStartNext() {
playNewPlayer(fileURL: trackURLs[1])
//here we would adjust the volume of the players and remove the first player after 3 seconds
}
private func playNewPlayer(fileURL: URL) {
let newPlayer = AKPlayer(url: fileURL)!
trackPlayers.append(newPlayer) //triggers didSet to update AudioKit.output
newPlayer.play()
}
}

Audio won't play after app interrupted by phone call iOS

I have a problem in my SpriteKit game where audio using playSoundFileNamed(_ soundFile:, waitForCompletion:) will not play after the app is interrupted by a phone call. (I also use SKAudioNodes in my app which aren't affected but I really really really want to be able to use the SKAction playSoundFileNamed as well.)
Here's the gameScene.swift file from a stripped down SpriteKit game template which reproduces the problem. You just need to add an audio file to the project and call it "note"
I've attached the code that should reside in appDelegate to a toggle on/off button to simulate the phone call interruption. That code 1) Stops AudioEngine then deactivates AVAudioSession - (normally in applicationWillResignActive) ... and 2) Activates AVAudioSession then Starts AudioEngine - (normally in applicationDidBecomeActive)
The error:
AVAudioSession.mm:1079:-[AVAudioSession setActive:withOptions:error:]: Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.
This occurs when attempting to deactivate the audio session but only after a sound has been played at least once.
to reproduce:
1) Run the app
2) toggle the engine off and on a few times. No error will occur.
3) Tap the playSoundFileNamed button 1 or more times to play the sound.
4) Wait for sound to stop
5) Wait some more to be sure
6) Tap Toggle Audio Engine button to stop the audioEngine and deactivate session -
the error occurs.
7) Toggle the engine on and of a few times to see session activated, session deactivated, session activated printed in debug area - i.e. no errors reported.
8) Now with session active and engine running, playSoundFileNamed button will not play the sound anymore.
What am I doing wrong?
import SpriteKit
import AVFoundation
class GameScene: SKScene {
var toggleAudioButton: SKLabelNode?
var playSoundFileButton: SKLabelNode?
var engineIsRunning = true
override func didMove(to view: SKView) {
toggleAudioButton = SKLabelNode(text: "toggle Audio Engine")
toggleAudioButton?.position = CGPoint(x:20, y:100)
toggleAudioButton?.name = "toggleAudioEngine"
toggleAudioButton?.fontSize = 80
addChild(toggleAudioButton!)
playSoundFileButton = SKLabelNode(text: "playSoundFileNamed")
playSoundFileButton?.position = CGPoint(x: (toggleAudioButton?.frame.midX)!, y: (toggleAudioButton?.frame.midY)!-240)
playSoundFileButton?.name = "playSoundFileNamed"
playSoundFileButton?.fontSize = 80
addChild(playSoundFileButton!)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
let location = touch.location(in: self)
let nodes = self.nodes(at: location)
for spriteNode in nodes {
if spriteNode.name == "toggleAudioEngine" {
if engineIsRunning { // 1 stop engine, 2 deactivate session
scene?.audioEngine.stop() // 1
toggleAudioButton!.text = "engine is paused"
engineIsRunning = !engineIsRunning
do{
// this is the line that fails when hit anytime after the playSoundFileButton has played a sound
try AVAudioSession.sharedInstance().setActive(false) // 2
print("session deactivated")
}
catch{
print("DEACTIVATE SESSION FAILED")
}
}
else { // 1 activate session/ 2 start engine
do{
try AVAudioSession.sharedInstance().setActive(true) // 1
print("session activated")
}
catch{
print("couldn't setActive = true")
}
do {
try scene?.audioEngine.start() // 2
toggleAudioButton!.text = "engine is running"
engineIsRunning = !engineIsRunning
}
catch {
//
}
}
}
if spriteNode.name == "playSoundFileNamed" {
self.run(SKAction.playSoundFileNamed("note", waitForCompletion: false))
}
}
}
}
}
Let me save you some time here: playSoundFileNamed sounds wonderful in theory, so wonderful that you might say use it in an app you spent 4 years developing until one day you realize it’s not just totally broken on interruptions but will even crash your app in the most critical of interruptions, your IAP. Don’t do it. I’m still not entirely sure whether SKAudioNode or AVPlayer is the answer, but it may depend on your use case. Just don’t do it.
If you need scientific evidence, create an app and create a for loop that playSoundFileNamed whatever you want in touchesBegan, and see what happens to your memory usage. The method is a leaky piece of garbage.
EDITED FOR OUR FINAL SOLUTION:
We found having a proper number of preloaded instances of AVAudioPlayer in memory with prepareToPlay() was the best method. The SwiftySound audio class uses an on-the-fly generator, but making AVAudioPlayers on the fly created slowdown in animation. We found having a max number of AVAudioPlayers and checking an array for those where isPlaying == false was simplest and best; if one isn't available you don't get sound, similar to what you likely saw with PSFN if you had it playing lots of sounds on top of each other. Overall, we have not found an ideal solution, but this was close for us.
In response to Mike Pandolfini’s advice not to use playSoundFileNamed I’ve converted my code to only use SKAudioNodes.
(and sent the bug report to apple).
I then found that some of these SKAudioNodes don’t play after app interruption either … and I’ve stumbled across a fix.
You need to tell each SKAudioNode to stop() as the app resigns to, or returns from the background - even if they’re not playing.
(I'm now not using any of the code in my first post which stops the audio engine and deactivates the session)
The problem then became how to play the same sound rapidly where it possibly plays over itself. That was what was so good about playSoundFileNamed.
1) The SKAudioNode fix:
Preload your SKAudioNodes i.e.
let sound = SKAudioNode(fileNamed: "super-20")
In didMoveToView add them
sound.autoplayLooped = false
addChild(sound)
Add a willResignActive notification
notificationCenter.addObserver(self, selector:#selector(willResignActive), name:UIApplication.willResignActiveNotification, object: nil)
Then create the selector’s function which stops all audioNodes playing:
#objc func willResignActive() {
for node in self.children {
if NSStringFromClass(type(of: node)) == “SKAudioNode" {
node.run(SKAction.stop())
}
}
}
All SKAudioNodes now play reliably after app interrupt.
2) To replicate playSoundFileNamed’s ability to play the short rapid repeating sounds or longer sounds that may need to play more than once and therefore could overlap, create/preload more than 1 property for each sound and use them like this:
let sound1 = SKAudioNode(fileNamed: "super-20")
let sound2 = SKAudioNode(fileNamed: "super-20")
let sound3 = SKAudioNode(fileNamed: "super-20")
let sound4 = SKAudioNode(fileNamed: "super-20")
var soundArray: [SKAudioNode] = []
var soundCounter: Int = 0
in didMoveToView
soundArray = [sound1, sound2, sound3, sound4]
for sound in soundArray {
sound.autoplayLooped = false
addChild(sound)
}
Create a play function
func playFastSound(from array:[SKAudioNode], with counter:inout Int) {
counter += 1
if counter > array.count-1 {
counter = 0
}
array[counter].run(SKAction.play())
}
To play a sound pass that particular sound's array and its counter to the play function.
playFastSound(from: soundArray, with: &soundCounter)

How can i control my headset for my music player?

I am creating an music player iOS app and getting data from firebase. I can able to control play, pause, next and previous in simulator or iPhone. While headset is connect to device play, next and previous functionalities are not working properly.
Here is the code which i've used;
func setupRemoteCommandCenter() {
// Get the shared MPRemoteCommandCenter
let commandCenter = MPRemoteCommandCenter.shared()
// Add handler for Play Command
commandCenter.playCommand.addTarget { event in
player?.play()
print("headset play")
return .success
}
// Add handler for Pause Command
commandCenter.pauseCommand.addTarget { event in
player?.pause()
print("headset pause")
return .success
}
// Add handler for Next Command
commandCenter.nextTrackCommand.addTarget { event in
return .success
}
// Add handler for Previous Command
commandCenter.previousTrackCommand.addTarget { event in
return .success
}
}
And calling setupRemoteCommandCenter function in viewdidload
This document says that to receive player event notifications you need to
begin playing audio
be the "Now playing app"
The definition of "Now playing app" is hard to pin down, but it seems to be any app that has an active, non-mixable audio session and is playing audio (or has very recently played audio, there seems to be a brief grace period here) . One possible non-mixable audio session is:
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSessionCategoryPlayback)
try session.setActive(true)
} catch let err as NSError {
print("Error setting up non mixable audio session \(err)")
}
p.s. if you want the headset controls to work while the screen is locked (or if you want the lockscreen controls to work for that matter), you will need to add audio to Background Modes, because technically, if the screen is locked then your app has been backgrounded:

AVAudioPlayer audio not playing when its set and played from different methods

I have an app with different button press sounds. I'm trying to use two functions to set the next button press sound and then play it. The idea is to load the audio file for when the next button is pressed, and then finally play it through a different function when a button is pressed. Using prepareToPlay(), this should make playback faster.
The problem is that when I press a button, I hear 'silence'. I say 'silence' because my speakers actually receive some signal, but its too week to be heard.
Here's my code:
func loadNextButtonPressSound() {
let randomSoundIndex = Int(arc4random_uniform(UInt32(pressSounds.count)))
pressSoundPlayer = try! AVAudioPlayer(contentsOfURL: pressSounds[randomSoundIndex])
pressSoundPlayer.prepareToPlay()
pressSoundPlayer.delegate = self
}
func playButtonPressSound() { // called when a button is pressed
if(StoredSettings.instance.getEnableSoundsSetting()) {
pressSoundPlayer.play()
loadNextButtonPressSound()
}
}
If I instead merge the codes to form this:
internal func playButterPressSound() {
if(StoredSettings.instance.getEnableSoundsSetting()) {
let randomSoundIndex = Int(arc4random_uniform(UInt32(pressSounds.count)))
pressSoundPlayer = try! AVAudioPlayer(contentsOfURL: pressSounds[randomSoundIndex])
pressSoundPlayer.play()
}
}
It starts working (i.e. I hear button presses), but it defeats the purpose of using prepareToPlay() to preload the next sound.
Any suggestions on how to make the first code above work ?

Keep AVAudioPlayer sound in the memory

I use AVAudioPlayer to play a click sound if the user taps on a button.
Because there is a delay between the tap and the sound, I play the sound once in viewDidAppear with volume = 0
I found that if the user taps on the button within a time period the sound plays immediately, but after a certain time there is a delay between the tap and the sound in this case also.
It seems like in the first case the sound comes from cache of the initial play, and in the second case the app has to load the sound again.
Therefore now I play the sound every 2 seconds with volume = 0 and when the user actually taps on the button the sound comes right away.
My question is there a better approach for this?
My goal would be to keep the sound in cache within the whole lifetime of the app.
Thank you,
To avoid audio lag, use the .prepareToPlay() method of AVAudioPlayer.
Apple's Documentation on Prepare To Play
Calling this method preloads buffers and acquires the audio hardware
needed for playback, which minimizes the lag between calling the
play() method and the start of sound output.
If player is declared as an AVAudioPlayer then player.prepareToPlay() can be called to avoid the audio lag. Example code:
struct AudioPlayerManager {
var player: AVAudioPlayer? = AVAudioPlayer()
mutating func setupPlayer(soundName: String, soundType: SoundType) {
if let soundURL = Bundle.main.url(forResource: soundName, withExtension: soundType.rawValue) {
do {
player = try AVAudioPlayer(contentsOf: soundURL)
player?.prepareToPlay()
}
catch {
print(error.localizedDescription)
}
} else {
print("Sound file was missing, name is misspelled or wrong case.")
}
}
Then play() can be called with minimal lag:
player?.play()
If you save the pointer to AVAudioPlayer then your sound remains in memory and no other lag will occur.
First delay is caused by sound loading, so your 1st playback in viewDidAppear is right.

Resources