Godsend Programming #10: Audio Implementation

A relatively “big” and not-so-straightforward task that I was assigned was to get audio into the game – at least the proof of concept to show in the alpha stage. There were a lot of options to do this and a lot of it required understanding encoding of audio files as such. I naturally got confused about it and asked Joe from the other team how he did it, as I knew Team B had audio already in the game. He showed me that it takes two lines of code or so, and knowing that saved me loads of time. However, I noticed that they weren’t pre-loading the audio and figured that I wanted to do that. After all, I don’t want to be reading in files at run-time when they are needed.

Doing a bit of research, I found that it was indeed possible to pre-load audio files into memory. So I created a small class that wraps up the pre-loading of a track and playing it – CGCAudioTrack:

Audio_Track

So now I had the basic means of playing a single audio track. I then thought that pre-loaded tracks need to be stored somewhere, so I wrote a different class – CGCSFXManager (excuse me for the poorly chosen name – it manages UI sounds and background music as well):

CGCSFXManager1

CGCSFXManager2

As it becomes apparent from the code, it stores audio samples in a map, keyed by the audio file name and mapped to the audio object. I had to use strings, as the comparison method I used for const chars didn’t always work – I suspect due to file path symbols like forward slashes. I did not have the time to figure it out, and it could be a future improvement, replacing the system to use strings for identifying keys was simply faster to implement. Also, as noted before, using std::map fragments memory, so I would love to re-write it to use an array instead if I did it again.

The CGCSFXManager is a classic singleton (excluding lazy initialisation – it needs to be explicitly initialised). It persists through the life cycle of the game and gets destroyed when shutting down the game. It is initialised at the start of the application in AppDelegate.

My next dilemma was how to manage the memory used by the sounds. I came up with the idea that there should be a separate chunk of memory to manage Music, as music has the particular property that there can only be one track playing at any given time:

CGCSFXManager_BGM

It would delete the current music track and replace it with a new one if the client code tells it to play a new one. There is nothing enforcing this to be done at scene initialisation, which is bad and an area of improvement. A minor optimisation that I did was to not replace the music if the scene wants to play the same track that is currently playing, as that’s just wasteful reloading.

The second area of sounds I identified are the SFX sounds. These have the particular property of being specific to each level. This means that we want to purge the sounds when a level finishes. This contrasts with the other type of sounds – UI sounds. These are re-used everywhere throughout the game and should not be purged. So I created two containers to separate them.

The actual code that pre-loads sounds and plays them is largely similar, and there could be some more effort to eliminate code duplication:

CGCSFXManager_Play_Sound

The SFX sound container, as mentioned, gets purged at the end of each level. I could improve on this concept by also moving the player sounds into a persistent container, as the player is present in every level at least.

The next bit of code is what I would consider to be bad as well and was done in an absolute rush on the Wednesday of the beta sprint. After the focus group testing, when we put all of the sounds into the game, it turned out that they were causing significant lag. The root of the issue turned out to be the fact that a lot of the sounds are played on collision impact, which was triggered loads of times. This is needed sometimes for when the BeginContact and EndContact checks are not enough – mainly when a game object switches state mid-contact. So the sound system was getting spammed.

The proper solution would have been to clean up our collision code and ensure this does not happen. It would have also taken us several days to do all of that, but we didn’t have several days. We had half a day. So the quickest solution I could come up with was to check if a particular sound is already playing and not play it again if so.

I set up an array to keep track of the channels and the sounds that were currently playing on those channels. In the process of doing that, I also refined the system to check the number of channels available and search of a free one for every sound effect. This meant that we could now utilise the maximum amount of channels available for concurrent sounds to be played. However, due to not playing the same sound several times, it meant that rapidly jumping on an enemies head several times would not play the sound on each bounce if it hasn’t finished playing yet. This is not so noticeable and it was the best I could do within the time given. The whole system to manage this is detailed below:

CGCSFXManager_Globals

CGCSFXManager_Searching

Note that I use global variables and a global callback function. When a sound is requested to play, it checks if it’s not already playing and marks the channel occupied. When the sound finishes playing, it issues a callback which marks the channel as free again. The reason I used a global callback function (and global variables to accompany it) was the poorest excuse ever – we could not figure out how to set up a callback to be a member function.

If I did it again

To sum up, I think the protection from spam is actually good for this system, as it doesn’t rely on other code being correctly executed. Furthermore, the fact that we utilise all of the available channels is good as well, no resources wasted thus. Finally, I implemented the audio file naming well, compared to animation names, and contained all of it in a single file. Furthermore, I was able to mark the sounds that weren’t set up yet in the code via comments and easily hand over to Alvaro to put them in. This was well documented, easily understood and good team communication. Alvaro did not need to delve into the details of the audio system and he quickly knew which audio needed to be implemented. We could easily specify audio file paths in a single place as well.

However, if I were to do it again, I would first address the usage of std::map, and then address the usage of global variables/functions. For the PS4 project and for my knowledge as a professional, I will put effort in to research setting member callback functions, as it is definitely embarrassing to not be able to achieve something because you don’t know how to.

Related files

GCAudioTrack.h – https://drive.google.com/open?id=0B_BnvnFZLH7mYTBjVzhtZ095cjQ

GCAudioTrack.cpp – https://drive.google.com/open?id=0B_BnvnFZLH7mRDZiZDR4SUIyWW8

GCSFXManager.h – https://drive.google.com/open?id=0B_BnvnFZLH7mS2dHM05xanRXZDg

GCSFXManager.cpp – https://drive.google.com/open?id=0B_BnvnFZLH7mcGhBdzBFQ0cyQTA

SGCAudioNames.h – https://drive.google.com/open?id=0B_BnvnFZLH7mdUZlOVZYY3JpSXc