ShortSoundPlayer: A simple, short sound player class

bonmot_screenshot
My first iPhone app, Bon Mot!, was the result of a collaboration with Bill Cochran. Bill came up with the idea for the game, recorded the audio and created the graphics with a little help from his son, Ian, who created the sky background. Having a collaborator was great because it let me focus exclusively on programming the app. Well, maybe not exclusively. There was also starting a company. And building its web site. Still, it would have been a long haul without Bill's help. Thanks hombre!

Like most games, Bon Mot has a bunch of sound effects that augment the game play. Initally we considered having ambient background sounds, but we ditched that idea early on, figuring most people would rather listen to their own music while they play.

Without the need for continuous background audio in Bon Mot, I "simply" needed to play a bunch of short sound clips, all of them 30 seconds or less. Additionally I wanted to let our users turn down the volume of the game's sounds so they could better hear their music. If you're wondering why I put "simply" in quotes, well... you'll see.

The first stop in my hunt for audio code was Apple's Metronome sample. Metronome is a beautiful little app that behaves just like an old-school, mechanical metronome with a swinging, weighted arm. At that time, Metronome used Apple's System Sound Services to play its "tick" and "tock" sounds (Note: since the release of iPhone OS 3.0, Metronome has been updated to use AVAudioPlayer instead). Although Apple's documentation warns against using System Sound Services to play things like game sounds, I couldn't help notice that Apple's engineers were doing this very thing in Metronome. More importantly, it was clear that System Sound Services was by far the simplest audio API for the iPhone. I love simple.

metronome
It was a snap to get Bon Mot's sound clips playing with System Sound Services. There was just one hitch. SSS offered no control over the output volume of the sounds it played. I was determined to let our users turn down the game sounds so they could better hear their music. Apart from the volume issue though, my task appeared to be complete in less than a day of work. Sweet! I decided to ignore the volume thing for the time being. Sometimes these things have a way of fixing themselves.

Weeks passed with no magic fix. We sent out our first beta version to everyone we knew with an iPhone. The app was feature complete with one exception. The "Quiet" setting for Bon Mot's Game Sounds preference needed to be implemented and System Sound Services, while performing perfectly in every other way, refused to let me turn down the volume. Ignoring the problem hadn't fixed it and it was time for me to come up with a Plan B.

Plan B was Apple's promising, new (at the time) AVAudioPlayer class. Without laboring the details, AVAudioPlayer turned out to be a major headache. My understanding is that the iPhone OS 3.0 version of AVAudioPlayer is much improved. However, if you need to target iPhone OS 2.x or earlier, then I recommend you avoid AVAudioPlayer. I wasted a solid day or two trying to get it to work reliably, and I'm reasonably certain that the problems lay within AVAudioPlayer, not in my use of it. A bit of Googling confirmed that before iPhone OS 3.0, AVAudioPlayer was long on promise and short on delivery. Time to find a Plan C.

Plan C was Apple's mature but complicated Audio Queue Services API. I hate complicated. I had been avoiding Audio Queue Services because I could see the complexity oozing from its pores. But AQS had been around. Lots of code had been built upon it. It was no newcomer like AVAudioPlayer and was clearly up to the task of playing my sounds, letting me control the volume, and a ton of other stuff I didn't need.

I took a deep breath, rolled up my sleeves and dove into AQS with the goal of creating a super simple wrapper around it so the rest of my code would never need to know the gnarlyness that lay within. What I came up with is ShortSoundPlayer, a class that lets you play sounds that are small enough to fit entirely in RAM. This last bit is important. ShortSoundPlayer is not meant for playing long sounds that must be buffered a piece at a time. However, if all you need to play is short sound clips (say, 30 seconds or less), then ShortSoundPlayer is worth a look. It's simple. And you can control the dang volume!
@interface ShortSoundPlayer : NSObject
{
NSString* _fileName;
NSString* _filePath;
float _volume;
bool _isPlaying;

AudioQueueRef _queue;
AudioQueueBufferRef _buffer;
}

@property (nonatomic, readonly) NSString* fileName;
@property (nonatomic, readonly) NSString* filePath;
@property (nonatomic, assign) float volume; // 0.0 = silent, 1.0 = max volume.
@property (assign) bool isPlaying;

- (id) initWithContentsOfFile: (NSString*) path;

- (BOOL) prepareToPlay; // Optional. Happens automatically on play. YES = success.
- (BOOL) play; // Sound is played asynchronously. YES = success.
@end
Instead of getting long winded describing the implementation, I'll just zip it up and post it here: ShortSoundPlayer.zip. Hopefully you'll find that the code speaks for itself. If not, post a comment and let me know.

There is one little caveat to be aware of. I routinely blend Objective-C and C++ in my code, which has led me to use the .mm extension for all of my source files. If you prefer to avoid C++, then you may want to rename ShortSoundPlayer.mm to be ShortSoundPlayer.m before adding it to your Xcode project. This because this particular class doesn't use C++.

If you find any bugs, or make improvements to ShortSoundPlayer... please, Please, PLEASE post a comment and let me know!
0 Comments