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.
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 identifiersJust 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 ::PlaySound(MAKEINTRESOURCE(IDR_WAVE1), NULL, SND_RESOURCE SND_ASYNC); 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. |