In this tutorial we are going to look at audio programming in Cocos2d-x. We will look at playing music and sound effects using SimpleAudioEngine, one of two engines built into Cocos2d-x. There is a second, newer, more commplex and experimental audio engine AudioEngine, that we will discuss later. Let’s start straight away by playing some music. To make use of SimpleAudioEngine we need to add an additional include call:
#include "SimpleAudioEngine.h"
Next we need a song of some sorts to play, simply copy an appropriate file into your resources folder. Myself I used an mp3 file named creatively enough song.mp3.
Supported Audio File Formats
The audio formats supported by Cocos2D-x depend entirely on what platform you run on. The primary thing to be aware of is the ogg format is the preferred music format on Android platforms, while it is completely unsupported on iOS, which prefers MP3.
You should be aware that the MP3 format is patent encumbered format and generally should be avoided when possible. If your app reaches certain sales thresholds, you may be required to pay license fees. Sadly this generally isn’t an option on iOS devices as MP3 is the primary audio format used. For sound effects, WAV are commonly used offering quick playback ability at the cost of file size. Here are the details of supported audio files on iOS.
Time now for some coding. Implement the following init() method:
bool HelloWorld::init() { if (!Layer::init()) return false; auto audio = CocosDenshion::SimpleAudioEngine::getInstance(); audio->preloadBackgroundMusic("song.mp3"); audio->playBackgroundMusic("song.mp3"); return true; }
That is all that is required to load and play a music file. In fact the preloadBackgroundMusic() call wasn’t even required so we could have used even less code. However preloading your music guarantees that you will not suffer a slow down the first time a song plays. You can also pause and resume playback of background music, or switch tracks completely, like so:
eventListener->onKeyPressed = [audio](EventKeyboard::KeyCode keyCode, Event* event) { switch (keyCode) { case EventKeyboard::KeyCode::KEY_SPACE: if (audio->isBackgroundMusicPlaying()) audio->pauseBackgroundMusic(); else audio->resumeBackgroundMusic(); break; case EventKeyboard::KeyCode::KEY_RIGHT_ARROW: audio->playBackgroundMusic("song2.mp3"); break; case EventKeyboard::KeyCode::KEY_LEFT_ARROW: audio->playBackgroundMusic("song.mp3"); break; } }; _eventDispatcher->addEventListenerWithFixedPriority(eventListener, 2);
Hitting the spacebar will toggle the playback of the currently playing song. Hitting the right arrow will start playing (or start over if already playing) song2.mp3. Hitting the left arrow will start or re-start playback of song.mp3. You will notice from this example that only one song can be played at a time. Generally this isn’t a limitation as it is normal to only have one active sound track at a time.
setBackgroundMusicVolume() doesn’t work!
A bit of a warning, at least on Windows, calling setBackgroundMusicVolume() does nothing, making it impossible to change the volume of a playing music file. This may not be the case on other platforms, I did not test. It was filed as a bug a long time back and does not appear to have been addressed.
Now let’s look at playing sound effects instead. Playing music and effects is almost identical. The biggest difference is that sound effects are expected to support multiple concurrent instances. That is to say, while you can only play one song at a time, you can play multiple sound effects at once. Consider this sample:
bool HelloWorld::init() { if (!Layer::init()) return false; auto audio = CocosDenshion::SimpleAudioEngine::getInstance(); audio->preloadEffect("gun-cocking-01.wav"); audio->preloadEffect("gun-shot-01.wav"); audio->playEffect("gun-cocking-01.wav"); Director::getInstance()->getScheduler()->schedule([audio](float delta) { audio->playEffect("gun-gunshot-01.wav"); audio->unloadEffect("gun-cocking-01.wav"); }, this, 1.5f, 0, 0.0f, false, "myCallbackKey"); return true; }
In this example we preload two WAV sound effects, a gun cocking and a gun shot. Playing a sound effect is as simple as calling playEffect() passing in the file name. Of course, be certain to copy the appropriate sound files to your project’s resource folder before running this example. Next this example queues up a lambda method to be called 1.5 seconds of the gun cocking sound is played to play our gun shot sound. At this point we are done with our gun cocking effect so we unload it from memory using unloadEffect(). You can still call playEffect with that file in the future, but it will result in the file being loaded again.
This example might be somewhat convoluted, but it illustrates and works around a key weakness in the CocosDenshion audio library. It is a very simple and straight forward library but if you want to do “advanced” things like detecting when a song or audio effect has ended, unfortunately this functionality is not available. You either have to use the experimental AudioEngine, which we will cover later, or use an external audio library such as FMOD. SimpleAudioEngine is extremely easy to use, but not very powerful, so it’s certainly a trade off. If you just need background music and fire and forget sound effects SimpleAudioEngine should be just fine for you.
One final topic to cover is handling when your app is minimized or forced into the background, you most certainly want to stop audio playback. This is thankfully easily accomplished in your AppDelegate there are a pair of methods, applicationDidEnterBackground() and applicationWillEnterForeground(). Simply add the following code:
void AppDelegate::applicationDidEnterBackground() { auto audio = CocosDenshion::SimpleAudioEngine::getInstance(); audio->pauseAllEffects(); audio->pauseBackgroundMusic(); } void AppDelegate::applicationWillEnterForeground() { auto audio = CocosDenshion::SimpleAudioEngine::getInstance(); audio->resumeAllEffects(); audio->resumeBackgroundMusic(); }
This will cause all of your currently playing sound effects and music files to be paused when your application enters the background and they will all result when your application regains focus.