GameSoundEngine: A simple sound engine for iOS games

Recently while working on my latest SpriteKit game, it came time to start adding sound to the game. Looking back on how I implemented sound in my prevoous games I had always relied on a sound library to help with the sounds. In my early games I used the cocos2d SimpleAudioEngine. More recently I have been using ObjectAL by Karl Stenerud. Both work great and are easy to use, but were not an option this time. Neither has been updated in a while and I also wanted a swift interface. After looking for another sound engine and not finding anything that meet all my needs I decided to create my own sound engine called GameSoundEngine.

As I started working on GameSoundEngine I had some requirements and features in mind:

  1. Keep it as simple as possible.
  2. It needs to support playing background music, and at the same time be able to play several sound effects effects simultaneously.
  3. There needs to be separate volume controls for background music and sound effects.
  4. Even though there is a master volume for all sounds effects, each sound effect needs its own volume control to be able to adjust the volume relative to the other sounds.
  5. Each sound effect needs options to allow for random volume levels and random pitch. More about this later.
  6. Need to be able to preload sound effects to avoid loading the sound file every time the sound is played. When looking back at my previous games, I noticed that I was always doing an explicit preload on every sound effect. So I decided for GameSoundEngine that preloading should be automatic and built in for all sound effects.
  7. The sound engine should automatically configure the apps AVAudioSession as recommended by Apple’s audio guidelines for game apps.

Sonic Variety

There is one requirement I want to elaborate a little more, and that has to do with sonic variety for sound effects. A few years ago I was working with a sound designer (Nate Madsen of Madsen Studios LLC ) who created some sound effects and music for one of my games. He recommended that when playing the sound effects to randomly adjust the volume +/- 1-2 dbs each time it is played. And to also randomly adjust the pitch +/- 1-2 semitones. His explanation was simple. Playing the same sound effect over and over again can become very repetitive and boring to the user. To avoid the repetition slightly vary the volume and pitch each time it is played.

Another way to add more sonic variety is to simply have different variations of the same sound effect and randomly play one the different variations each time the sound effect is played. Combining the two techniques can create lots of sonic variety and make the sounds appear more natural.

In my previous games I always built a wrapper around the sound engine to do the sonic randomness. But for GameSoundEngine I wanted to build it in the core library.

Deciding which audio API to use

After reviewing several different iOS audio APIs, such as AVAudioPlayerSKAudioNode,  Open AL, AudioKit, and others, I decided on using Apple’s AVAudioEngine to build the sound engine. If you are not familiar with AVAudioEngine, it allows you to create different audio nodes such as player nodes, effect nodes, and mixer nodes, and connect them all together to create the type of sound you desire. It has everything necessary to meet my requirements. It has player nodes that can support preloading into buffers and each player has its own volume controls. There is a pitch effect node to vary the pitch. And a mixer node can be used to mix all the sound effects together allowing for one master volume of all sound effects.

Configuring AVAudioEngine

Getting the background music player to work was pretty straight forward. After creating an instance of AVAudioEngine, simply attach an AVAudioPlayerNode node to the engine, and then connect the player node to the engines mainMixerNode. Then to play the music call the player node’s scheduleFile method and its play() method.

To create sound effect players I first created a new mixer node and connected the output of it to the engine mainMixerNode. Then to play a sound created a new player node and connected it to the sound effect mixer. Since each player node has it is volume, this configuration gave me independent volume control on each sound effect, but also and overall master sound effect volume control using sound effect mixer volume. My node configuration looked like this:

Each time a sound effect was played a new player node, and a new pitch node if needed, were added to the engine, connected together and then played. When the sound effect completed the player node and pitch node where removed from the engine. This worked for the most part. But there were times with noticeable delays and sometimes the engine would stop playing altogether or even crash. After doing some investigation I determined the problems were caused by dynamically adding and remove nodes to the AVAudioEngine.

To remedy the situation I decided it was better to just add a pool of player nodes and pitch nodes to the engine at startup time. Then when a sound effect needs to be played find a free player node, and possibly a pitch node, connect them up and play the sound. When sound is done playing disconnect them, but leave them in the engine. This should also give better performance as we dont need to add and remove the nodes. So the final node configuration looks something like this:

The downside to this approach is that there is a fixed number of player nodes for sound effects. Which means there is a maximum number of sound effects that can be played simultaneously. However I made the number of sound effect players configurable so it should be able to accommodate any game.

This final configuration works great. Sounds perform well with low latency, and the default number of sound players is more than enough for my games. Although I did bump the number of sound players up into the hundreds while testing and everything continued to work smoothly. I am happy with the result. It meet all my initial requirements, performs well, and all written in Swift.

Using GameSoundEngine in your game

The GameSoundEngine framework can be installed using CocoaPods. Add the following line to your project pod file and the run “pod install”:

Or you can download it directly from github at: https://github.com/tkier/GameSoundEngine

The README.md file on github explains all the details on how to use GameSoundEngine. I wont repeat it all here, but here is a quick overview of the key features:

To play background music:

or

To play a sound effect, first create an instance of a SoundSFX object:

Then to play the sound:

or for SpriteKit games you can also use the library’s SKAction extension to play the sound:

Note that preloading of the sound effect occurs automatically when the SoundSFX object is created. It is recommended to create your SoundSFX objects once, when your scene is first created and then it can be played multiple times without having to reload the sound. For best performance avoid creating the SoundSFX object at the time it needs to be played.

To add random volume and pitch to your sound effect there are a couple of optional parameters on the initializer of SoundSFX:

Now each time mySound is played the sound engine will set random volume and pitch levels within the ranges indicated by the vary parameters.

If you have different variations of the same sound effect, you can have the sound engine randomly play one of those variations each time the sound is played. To do this create in instance of RandomSoundSFX passing it an array of SoundSFX objects, one for each variation:

When mySound.play() is called the sound engine will randomly play either “mySoundVersionA.caf” or “mySoundVersionB.caf”

Example of using sonic variety

To demonstrate how the sound effect sonic variety features work I created a simple fireworks example that uses a particle emitter and a SoundSFX. Here is a video of the effect:

The sound effect was setup using 4 different variations of the sound and also varies the volume and pitch:

I hope you find GameSoundEngine useful for your iOS games. Feel free to leave a comment below if have any suggestions or ideas for improvements.

This entry was posted in Game Programming and tagged , , .

5 Responses to GameSoundEngine: A simple sound engine for iOS games

  1. someone says:

    I tried implementing it as a replacement to OALSimpleAudio but I get unacceptable framedrops everytime i play an effect,
    you can observe this if you play a few sounds in fast progression,
    the framerate drops from 60 to 50 for a couple frames making the game feel much less responsive.

    still looking for a viable alternative for game audio.

    • Tom Kier says:

      I would expect OALSimpleAudio to slightly out perform AVAudioEngine and GameSoundEngine. But the numbers you are reporting don’t match the numbers I received during my tests. I created a test that plays the firework sound effect (same sound effect in the demo video) every 0.1 seconds. While playing that sound 10 times every second I was able to easily get 60 fps with the average frame times below 6ms.

      There are a couple of things you may want to check to make sure you are getting the best performance:
      1) Make sure you are using an efficient sound file format. Some file formats are much more efficient than others, since they can be decoded by iPhone hardware instead of the CPU. As described in the README.md I recommend using AF format, linear PCM, little-endian, 16-bit. The README.md contains instructions on how to convert to that format.
      2) Create each SoundSFX object once, early in the execution of your scene and call its play method several times. Don’t recreate the SoundSFX instance each time you want to play the sound. As described in the blog article, instantiating a SoundSFX causes it preload the sound file and load into a memory buffer. Then calling play() later will use the cached in memory version of the sound. But the preloading can take some time, so you only want to do that once.

      I also went back and reviewed the code and I should be able to make some performance improvements by not always disconnecting and reconnecting the AVAudioNodes. Instead only disconnect and reconnect the nodes if the audio format has changed. Therefore if all your sound files are the same format (which is typically the case) there should be some performance improvements. Working on the update now, hope to have it out soon.

      Thanks for the feedback and I hope this helps.

      • Tom Kier says:

        I have finished updating GameSoundEngine with the performance improvements and released it as version 1.2. Please update your pods. Please not that to achieve the best performance use the same file format for all your sound effect files.

        • someone says:

          nice!
          I will give it another go thx

          I really prefer having a nice simple audio engine like this over something like AudioKit but the alternatives aren’t supported anymore and are actually causing bugs
          (for example using Siri while in game breaks the sound effects, etc.)

  2. John Frank says:

    Remarkable article about gamesoundengine for ios games..Great stuff..I am very thankful to you for sharing this kind of news.

Leave a Reply

Your email address will not be published. Required fields are marked *