Jump to content

  • Log In with Google      Sign In   
  • Create Account






Playing simultaneous sounds in Win32

Posted by jwezorek, 11 February 2012 · 1,731 views

There is a nice unscary Win32 API call for playing sounds that is unpretentiously called "PlaySound". It is extremely easy to use. Unfortunately, PlaySound(...) has one problem that makes it unsuitable for even casual game development: although it will play audio asynchronously, it will not play two sounds simultaneously. Depending on what you are doing this may not be a big deal, e.g. warning beeps in an editor app and such, but for anything that is media rich it basically means that you can't use PlaySound.

This leaves you with several options:
  • Use a third party library.
  • Use the new Windows Audio Session API which came in with Vista.
  • Use the (legacy) low-level Win32 MCI routines.
  • Use the (legacy) low-level Win32 waveOut interface

So if you have serious audio needs The Right Thing is 1. or 2. above.

A little background on what I'm doing: I'm working on a Win32 C++ implementation of a puzzle game that I am eventually going to directly port to iOS. I don't really need serious audio as I just want something that is playable for game design debugging, i.e. balancing. I do want simultaneous sound effects though because I plan on releasing this Win32 prototype to gather feedback and I think sound effects add playability in this case.

Anyway, I looked into 1. and 2. On 1., I couldn't find a library that was dirt simple enough to justify its use in my project given all I am really looking for is a drop-in replacement for PlaySound that supports asynchronous simultaneous audio. On 2., well, the API is dense; I'm not looking for a research project and I couldn't find a lot of sample code -- maybe, it's too new? or maybe I wasn't looking in the right places.

On 3., if you don't know what I'm talking about, I'm talking about this functionand friends. I remember using the MCI (multimedia command interface, I believe) routines back in the day, probably around 1997, and they are pretty easy to use. It's a weird little API relative to the rest of win32. You send command strings to a device object that look like "LOAD 'foo.wav' as Foo" and so forth, and I think that they will do simultaneous audio output. The problem is the MCI routines don't let you play sounds from memory and I want to play from WAVE resources that are embedded into the executable. To use MCI I would have to write my resources to a temp directory at start up and then play the serialized files. This seemed too ugly.

Which leaves 4. The problem with 4. is that waveOut et. al. are like the opposite of PlaySound: super low-level and absolutely un-user friendly. Fortunately there is a lot of code out there. In particular I found CWaveBox on CodeProject by a CodeProject user named Zenith__ that does basically what I need. I had lots of problems with this code, however, and ended up significantly re-working some code that was itself a re-work of the original CWaveBox that is posted in the CodeProject comments. I basically chose to work with the comments version because it executed the demo app as well as the original, but it was much shorter.

The code I started with is verbose, baroque, and in C rather than C++. I re-factored it in the following ways:
  • Added the ability to load WAV's from resources
  • Replaced two C-style arrays: one with a std::vector and the other with an std::map
  • Cleaned up the .h file by making things that should be private private and moving as many implementation details into the .cpp file as I could
  • Simplified some of the thread synchronization stuff by using WaitSingleObject instead of looping and polling
  • Got rid of all C-isms i.e. mallocs, callocs, memsets, memcpys, etc. Replaced with C++-style allocation, std::copy, std::fill, etc.
  • Got rid of magic constants by noticing many of them served as booleans, so replaced them with booleans
  • Changed names of crazily named variables and functions
  • Generally got rid of craziness ... we're talking goto's.

I honestly don't understand the code involved, which is weird given the amount of work I did on it. I mean, I get that there's a thread that's running and that it is playing chunks of waves in a loop, etc. That's the level at which I understand it ... kind of reminds me of this time I was working a DARPA contract and ported a function for converting from Military Grid Reference Numbers to longitude and latitude from Fortran to C without actually understanding the algorithm or knowing Fortran...

My code is here. Usage goes likes this:

[source lang="c++"]#include "WaveManager.h"#include "resource.h"// ...{ WaveManager wave_mgr( kMaxNumSimultaneousWaves ); wave_mgr.LoadFromResource(ID_SND_BUZZ, MAKEINTRESOURCE(ID_SND_BUZZ)); wave_mgr.LoadFromResource(ID_SND_CLICK, MAKEINTRESOURCE(ID_SND_CLICK)); // ... wave_mgr.Play(ID_SND_BUZZ); // ... // etc.}[/source]

It actually works remarkably well.

However, the code still needs work if anyone is interested. There are a lot of functions returning error codes that are never checked. I think most of these should be changed to return void and should just throw if something serious happens. Also I think in the original implementation there could have been a race condition if you tried to load a wave after the playing thread was running. I wrapped the loading stuff in the critical section the playing thread uses, but this still might be a problem -- I'm not sure. If you use this code it is safest to load all your sound assets at start up before you start playing anything. But in terms of fixing it up more, I think the main thing is that someone who actually knows what they are doing vis-à-vis wave output could probably cut the verbosity in half or so.

Oh, and one more note on my work, I used some C++11 stuff in there ... basically I implemented loading from memory by taking the guts of the existing LoadFromFile implementation and making it into a function template that takes a handle type as the template parameter and has an additional formal parameter that is an std::function used for loading data. In the LoadFromFile case I instantiate the template with a file HANDLE as the template parameter and pass the function the Win32 call ReadFile wrapped thinly in a lambda. In the load from resource case, the template parameter is a custom buffer struct and the functor argument becomes, basically, a lambda wrapper around std::copy. But, anyway, just a heads up that this code will only compile under Visual Studio 2010 because of the lambdas. If you're interested in using it but don't do lambdas, it would be pretty easy to replace them with regular function pointers.

From The Curiously Recurring Gimlet Pattern




April 2014 »

S M T W T F S
  12345
6789101112
131415161718 19
20212223242526
27282930   
PARTNERS