30.1 Adding sound to your program

How do we get our program to make noise? The easiest way is to use the multimedia features of Windows to play so-called wave or *.wav files. A wave file is to sound as a bitmap is to graphics: it's a binary representation. Although Java code can also play files in the *.au format, the Visual C++ libraries don't supply this capability.

And what about music? What about playing *.mp3, or generating sounds with algorithms? Certainly a full-featured game ought to have some music and some more sophisticated sound-blending modes. Also you'd like to be able to use MIDI to avoid having to store a sound file in such a large format. But, in this chapter, we're only going to give you the bare minimum.

If you use the Windows Start | Find dialog you can look for *.wav files on your machine. Assuming that you have a sound card, and assuming that your speakers are turned on, you can 'play' these sounds by double-clicking on them. Before starting to work with sound programming, you should check that your system will indeed play sounds, otherwise you won't be able to tell if your program is working properly. Note that it often takes some fiddling to get sound to work, as there are usually several ways to turn it on and off, including both a control bar dialog and a physical knob on the speakers.

Let's take a look at an example of where the Pop Framework code makes a sound, the case where an asteroid is hit by a bullet in gamespacewar.cpp.

int cCritterAsteroid::damage(int hitstrength) 
    int deathreward = cCritter::damage(hitstrength); /* This is _value 
        (typically nonzero) you get 
        for killing off the critter. */ 
    ((CPopApp*)::AfxGetApp())->playSound("Ding", SND_RESOURCE | 
        SND_ASYNC); //Signal the hit. 
    return deathreward; 

The call to the CPopApp::playSound method simply wraps a call to the Windows multimedia API call ::PlaySound. The reason we wrap it like this is so that we can check every call for sound against a 'global' _soundflag that belongs to CPopApp. Here's the code from pop.cpp.

void CPopApp::playSound(LPCSTR pszSound, DWORD fdwSound) 
    if (_soundflag) 
        ::PlaySound(pszSound, NULL, fdwSound); 

Windows has an API function called PlaySound(CString soundname, HINSTANCE programinstance, int flags). This function is not a member of any MFC class, and we put a :: in front of it to remind ourselves of this fact.

The soundname is the name of some sound. There are three kinds of sound names you can use, with the type of sound indicated by a flag which is OR ed into the third argument. We'll come back to this in a minute.

The HINSTANCE argument in the second place is a throwback to the old Win32 programming and is not needed in MFC applications. In Win32 we need this argument when we want to use a sound that's stored as a program resource. But in MFC applications the second argument can always be NULL.

The flags in the third argument is made by combining various bitflags with the OR operation. A variety of SND_ flags are defined in C:\Program Files\Microsoft Visual Studio\VC98\Include\MMSYTEM.H. Ordinarily we OR in a flag to tell PlaySound what kind of soundname you are giving it: SND_ALIAS, SND_RESOURCE, or SND_FILENAME.

Another flag which we almost always OR in is the SND_ASYNC. This tells the program to send the work of playing the sound off to the sound card and not to wait for the sound to finish playing before continuing program execution. If this flag is present in a first call of PlaySound, then this means that if a second sound wants to start up, the first sound will stop and let the second sound start. If the SND_ASYNC flag is not present in the first call of PlaySound, then the first sound insists on playing to conclusion before the second sound is allowed to start.

Usually it's better to use the SND_ASYNC flag when you are doing action-generated sounds, as otherwise the sounds can lag behind the actions. And you don't want to stop the action just for a sound. Better to cut a sound short than to have the next sound come too late.

Now let's talk about the three kinds of sound names we can use. The names usually have the form of a string in quotes. The string is not case-sensitive, by the way, so it doesn't really matter how it's capitalized.

  • System sound name. You can use the name of some standard Windows event, and Windows will play whatever sound is associated with that event. In this case your call is of the form

    ::PlaySound("SystemExclamation", NULL, SND_ALIAS | SND_ASYNC);. 

    Some of the system sound names you can use are: 'SystemExclamation,' 'SystemAsterisk,' 'SystemStart,' 'SystemExit,' and 'SystemDefault.' If you look at the sound dialog in your Windows Start | Settings | Control Panel | Sounds you can find other candidates. The names for system events are usually the obvious ones, consisting of the word 'System' run on with the name listed in the dialog.

  • External sound file. You can use the name of some special *.wav file which holds a binary description of a sound. In this case your call is of the form

    ::PlaySound("Bonk.wav", NULL, SND_FILENAME | SND_ASYNC); 

    With a call like this, PlaySound looks in the same directory as the executable for the requested *.wav file. If it doesn't find the file it looks in the Windows and the Windows\System directory. If it still doesn't find the *.wav, it makes the default 'system ding' sound.

  • Resource sound file. The third option is that you can have the sound file description bound into your executable as a resource. This is the one where Win32 requires something special for the second argument. But in MFC, NULL is still okay. Your call is of the form

    PlaySound("Ding", NULL, SND_RESOURCE | SND_ASYNC); 

    You add a *.wav to your resource by placing the desired *.wav into the \res subdirectory under your source code and then using the Project | Add resource... | Import... dialog to import the file. [This is the Insert | Resource... | Import... dialog in Version 6.0.] Once you do this, the Resource View will show a 'WAVE' category. It will assign an integer-valued name like IDR_WAVE1 to your wave resource. Right-click on this name, select Properties... on the context menu and use the dialog to change the ID from IDR_WAVE1 to a string like 'Ding,' being sure to type in the quotation marks as well as the string. If PlaySound doesn't find a given resource it doesn't make any sound at all.

The benefit of the system sound approach is that you are fairly certain that the program will make some sound, as usually Windows has various sounds associated with different kinds of events. Also these sounds are user programmable. The drawback is that you have no control over which sound the user will hear. It depends on how he or she has configured the sounds on his or her system.

The benefit of the external sound file approach and the resource sound file approach is that you can control which sound the user hears.

In the external sound file approach, if the user wants to change the sound, he or she can rename a favorite *.wav file to match the one your program looks for. And at least PlaySound will make some kind of system sound even if it can't find the requested *.wav file. A drawback is that you need to distribute the necessary *.wav files along with your executable, and it's nicer to just be able to give someone a single *.exe that includes everything.

The benefit of the third approach is that you control which sound the user hears, and the sound is certain to be available in the *.exe. The drawback is that the *.exe will end up being a little larger than before: a small *.wav file is about 10 K, and they can be much larger.

The most professional approach might be to combine the second and third approaches. Include resource files, but allow the user to use File | Open to load external files if he or she likes. This would be an example of adding flexibility to the user interface.

Oh, one final point. Whenever your program includes sound, it must include a control for turning the sound off! Of course the user can turn sound off by using the Windows controls, but your program must be polite enough to be willing to turn its own sound off. That's, again, the reason that we pass all our sound call requests to CPopApp, and let it check against _soundflag before making noise.

Resource identifiers

Just as PlaySound with the SND_RESOURCE flag turned on loads sound files from the program resources, there is a LoadBitmap function that loads bitmap files from the resources, and a LoadCursor function to load cursor images from the resources. These functions take resource identifiers as arguments.

A resource identifier can be either an integer or a string. By default the Resource Editor assigns integers as identifiers for resources and then makes up mnemonic names like IDR_EDIT_UNDO or IDR_WAVE1 or IDR_BITMAP1 for them. But you can change the identifier to a string.

The MFC versions of CBitmap::LoadBitmap and CWinApp::LoadCursor are polymorphic; that is, they'll accept a resource identifier which is either an integer or a string.

The old-style Win32 non-MFC functions like ::PlaySound will only accept resource identifiers which are strings. If you don't feel like replacing your resource's integer ID by a string you can fake it by using the Windows macro MAKEINTRESOURCE to convert the integer ID into a string ID which ::PlaySound is willing to use. Thus you could call something like


If you want to dynamically select which resource to use it's sometimes handy to use integers to stand for them. If you need for the integer values to be consecutive numbers you can directly edit them in the resource.h file or indirectly in the View | Resource Symbols... dialog.

    Part I: Software Engineering and Computer Games
    Part II: Software Engineering and Computer Games Reference