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 , , .

Leave a Reply

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