Christian Cerri
Christian Cerri

Reputation: 1273

AVAudioPlayerNode volume change is not applied immediately

Using AVFoundation and AVAudioPlayerNode to play sound with Xcode Version 9.2 (9C40b) and deploying to iOS 11.2

The problem is that when you change volume, the change is not applied the first time you play the sound, and there are other weird effects.

Start a new project in Xcode, select iOS Game, and give any name. Then replace the code in GameScene.swift with:

import SpriteKit
import GameplayKit
import AVFoundation

class GameScene: SKScene {

    var entities = [GKEntity]()
    var graphs = [String : GKGraph]()

    let audioFilePlayer = AVAudioPlayerNode()
    var audioFile:AVAudioFile! = nil
    var audioFileBuffer:AVAudioPCMBuffer! = nil

    override func sceneDidLoad() {
        do {
            let path = Bundle.main.path(forResource: "sound", ofType: "caf")!
            let url = URL(fileURLWithPath: path)
            audioFile = try AVAudioFile(forReading: url)
            audioFileBuffer = AVAudioPCMBuffer(pcmFormat: audioFile.processingFormat, frameCapacity: UInt32(audioFile.length))
            try audioFile.read(into: audioFileBuffer!)
            audioEngine.attach(audioFilePlayer)
            audioEngine.connect(audioFilePlayer, to: audioEngine.mainMixerNode, format: audioFileBuffer?.format)
            try audioEngine.start()
        }
        catch {
            print(error)
        }

        let playAction = SKAction.sequence([
            SKAction.wait(forDuration: 3.0),
            SKAction.run { self.play(volume: 1.0) },
            SKAction.wait(forDuration: 1.0),
            SKAction.run { self.play(volume: 1.0) },
            SKAction.wait(forDuration: 1.0),
            SKAction.run { self.play(volume: 0.0) },
            SKAction.wait(forDuration: 1.0),
            SKAction.run { self.play(volume: 0.0) },
            SKAction.wait(forDuration: 1.0),
            SKAction.run { self.play(volume: 1.0) },
            SKAction.wait(forDuration: 1.0),
            SKAction.run { self.play(volume: 1.0) },
            ])
        self.run(playAction)

    }

    func play(volume:Float) {
        print("playing at \(volume)")
        audioFilePlayer.stop()
        audioFilePlayer.volume = volume
        audioFilePlayer.scheduleFile(audioFile, at: nil, completionHandler: nil)
        audioFilePlayer.play()
    }

}

Apologies for poor optionals... Also, add a sound file called sound.caf to the project.

Console output is:

playing at 1.0
playing at 1.0
playing at 0.0
playing at 0.0
playing at 1.0
playing at 1.0

I would expect to hear: loud, loud, nothing, nothing, loud, loud.

I am actually hearing: loud, loud, loud, nothing, soft, loud

(the 'soft' is particularly weird)

I have also tried changing the master volume with:

audioEngine.mainMixerNode.outputVolume = volume

but the sounds are the same.

In older games, used to use OpenAL, but this requires Obj-C and is pretty messy with bridging headers etc. Also, OpenAL is supposedly not supported any more. SKAction based audio cannot handle lots of sounds repeating fast without glitches and scratches (it's a space shooter game...) Problem is the same in simulator and on device. Any help appreciated!

Upvotes: 5

Views: 3134

Answers (2)

Obmit
Obmit

Reputation: 41

Yep, in case anyone else is interested I also solved it by adding an AVAudioMixerNode between the input source and engine.mainMixerNode

Upvotes: 2

Christian Cerri
Christian Cerri

Reputation: 1273

OK I found the answer. AudioEngine needs to be reset after volume changes in order for the changes to be immediate:

audioEngine.reset()

this play() function works:

    func play(volume:Float) {
        print("playing at \(volume)")
        audioFilePlayer.volume = volume
        audioEngine.reset()
        audioFilePlayer.stop()
        audioFilePlayer.scheduleFile(audioFile, at: nil, completionHandler: nil)
        audioFilePlayer.play()
    }

Upvotes: 2

Related Questions