Fade in background sound with SKAudioNode

From the first glance, it looked like SKAudioNode would deliver all the sound functionaliy I need for a simple game-like app.


But it seems to be making life difficult.


I wanted to create an AudioNode, and have it start with zero volume, and then fade up to an audio level.


After a few attempts, I now have this.

     //create audio in paused state
      amb.runAction(SKAction.pause())          
      amb.runAction(SKAction.changeVolumeTo(0.0, duration: 0.0))

     //and then after a delay - unpause and fade in
      amb.runAction(SKAction.play())
      amb.runAction(SKAction.changeVolumeTo(0.2, duration: 20))

But the duration parameter seems to be ignored.


Also finding that scene transitions don't like scenes with audio. I am occasionally seeing crashes.

XCode itself crashes when previewing a scene with looping audio.


And (just a little) worried that Demo Bots example code does not include any sound at all.


I am puzzled about how best to proceed.

Replies

Yep. My experience is the SK audio is broken in lots of places. This is just one of them. In fact, I came across so many problems, that I've dropped down to implementing everything myself using AVAudioEngine.


However, while I was using SKAudioNode, I implemented my own replacement actions for the audio. Something like:


extension SKAction
  {
  // All the change... actions for SKAudioNode are broken. They do not work with looped audio. These are replacements

  public class func _changeVolumeTo(endVolume: Float, duration: NSTimeInterval) -> SKAction
       {
       var startVolume : Float!
       var distance : Float!

       let action = SKAction.customActionWithDuration(duration)
            {
            node, elapsedTime in
  
            if let soundNode = node as? SoundNode
                 {
                 if startVolume == nil
                      {
                      startVolume = soundNode.volume
                      distance = endVolume - startVolume
                      }

                 let fraction = Float(elapsedTime / CGFloat(duration))
                 let newVolume = startVolume + (distance * fraction)

                 soundNode.volume = newVolume
                 }
            }

       return action
       }
  }


Where SoundNode is a subclass of SKAudioNode. Something like:


class SoundNode : SKAudioNode
     {
     var volume : Float = 0
          {
          didSet
               {
               runAction(SKAction.changeVolumeTo(volume, duration: 0)
               }
          }
     }


Not fantastic, but get's you there.

Yep. That would do it.


But I am fininding that persistent audio seems to cause other problems when transitioning between scenes. I'm now doing background audio with AVAudioPlayer.


It's oddly frustrating that Apple are representing SKAudioNode as a production-ready solution.

Sounds like you are treading the same path I went my friend. I also tried using AVAudioPlayer for music and ambient sounds. But in the end, the whole mess of AVAudioPlayer and SKAudioNode just didn't work well. And once past anything trivial, the performance of SKAudioNode is not great. ALL SKAudioNode sounds are read (streamed) from files instead of memory buffers at the AVAudioEngine level, and this starts to impact on frame rate and audio quality once you have more than a few sounds to deal with.


It's a pity it doesn't work well out of the box. It's a pretty nice high level system, that should "just work". But alas, not in my experience. And there is limited documentation and examples (especially how to use the seemingly useless avAudioNode property or constructors), which makes it all that much harder. Oh well. 😟

Thanks for sharing your experience guys. Just wondering, could the choice of specific audio file formats (mp3, wav, aac etc...) impact performance (CPU, memory footprint) in any sgnificant way when played using AVAudioEngine?

Hey alayouni,


Yes, I think the formats can effect performance, at least when streaming from a file (which is what SKAudioNode does from my testing). I'm not sure about when using memory buffers -- it was a while since I went down that rabbit hole, but I seem to remember that the files are uncompressed when initially loading them into a buffer. This is one of the reason why I gained performance when using AVAudioEngine with memory buffers as opposed to SKAudioNode using the default file streaming.

Thanks for the reply, in fact I found valuable info on the subject here.


Basically the answer is linear PCM is the best format for sound effects (short duration audio) especially if size doesn't matter. For background music (larger duration audio) compressed formats should be used with aac being the recommended one apparently. Playing compressed background music using the hardware decoder is extremely efficient, but can play only one input at a time as opposed to using software decoders which are significantly less efficient and can handle multiple input. The bitrate and sample rate also affect size and decoding performane. Finally afconvert by Apple is a great tool to convert audio file formats.


It also seems that ObjectAL which uses AVAudioPlayer for background music, and OpenAL for sound effects performs better than AVFoundation particularly with multiple simultaneous sound effects, its future is uncertain though given Apple might deprecate/remove OpenAL in the future.

Hi,


i hope this will be fixed soon.


Same problem here.


I've tried to use Skrappys custom changeVolume Action, this works but it causes new problems. So for example as the node is declared as "SoundNode" instead of a SKAudioNode the .pause/.play functions will not (always) work.


Please Apple give us a useable SKAudioNode with methods which work.

It's now 2017 January but this is still broken. 😟

Hi

Started work on a new project. And have setup my sound stage for the game using SKAudioNode for spot effects and AVAudioPlayer for the background music. I am working with xcode 8.3 (8E162) and sofar it seems to be working as of April 2017. Have Apple fixed this now or is it going to fall flat on its face ?

I've tried a whole bunch of things without any luck. Finally, I just created my own method using a sequence, which has been very reliable. Since the "duration" part of the changeVolume SKAction doesn't work, I had to use a wait SKAction to accomplish the same thing. Hope this helps!

func changeVolume(audioNode: SKAudioNode, direction: String) {
   var time: Double = 0.0

   if direction == "up" {
        time = 0.1
    }
   else {
        time = -0.1
    }

    let wait = SKAction.wait(forDuration: 0.2)
    let down = SKAction.changeVolume(by: time, duration: 0)
    let sequence = SKAction.sequence([down, wait, down, wait, down, wait, down, wait, down, wait, down, wait, down, wait, down, wait, down, wait, down, wait
    audioNode.run(sequence)
}

It's November 2017 and SKAudio still seems buggy:

- sometimes I don't hear parameter changes and I don't know why

- audio actions are lost after the app has been in the background 😠


But timing is alright now. It seems to have gotten better since i am defining audio actions in the node's init code. I think they are preloaded and kept then.


Lots of little sound effects that are part of action sequences are timed better that way than they are when I define actions that use AVAudioplayer.