August 1998

Playing wave resources

by Kent Reisdorph

Most of the time, you play wave audio from a file on disk. Sometimes, however, you want to be able to play a sound contained as a resource. The resource might be in a DLL, or it might be in your EXE. This article will show you how to bind a wave audio resource to your EXE and how to play that wave resource at runtime.

Creating the resource

In order to bind a wave audio resource to your EXE, you must create a resource script (RC) file. A resource script file is nothing more than a text file with an extension of RC, containing text in a format that can be read by C++Builder's resource compiler. By far the easiest way of getting wave audio data into an executable is to start with a wave file on disk. Then, you can create a wave resource with a single line in the RC file:

 

MY_WAVE_RESOURCE WAVE "chimes.wav"
That's it! The format for creating a custom resource is as follows:
RESNAME RESTYPE FILENAME
To create a resource script file, create a new text file from the C++Builder Object Repository. Enter your resource text in the blank file, then save the file with an RC extension. (Don't forget to remove the default *.TXT extension in the File Save dialog box or you'll end up with a filename that looks like MYRES.TXT.RC.) Here's a sample RC file that defines four wave resources:

 

// RESNAME       RESTYPE      FILENAME
// ---------------------------------------
   CHIMES         Wave        "chimes.wav"
   CHORD          Wave        "chord.wav"
   DING           Wave        "ding.wav"
   TADA           Wave        "tada.wav"
(We put comments in the file so you could see the format of the resources, but they aren't necessary.) You'll typically see resource names in all uppercase, but that isn't a requirement. In fact, it doesn't matter whether you use uppercase or lowercase, because resource names aren't case sensitive.

Binding the resources to the EXE

Binding the resources to the EXE sounds like a big job. In reality, doing so requires nothing more than adding the RC file you just created to your application's project. Simply use C++Builder's Add To Project option and add the resource script to your project. The next time you compile your application, the resource script file will be compiled (by the resource compiler) and bound to your EXE (or DLL, if you have a DLL project). Be sure your resource file doesn't contain errors. If the resource compiler encounters errors, you'll get a message like the following:

 

[RCError] Wave.RC(4): Cannot open file: Wave.
[RCFatal Error] Compile.
The format for specifying wave resources in RC files is pretty basic, so you shouldn't have problems with resource compiler errors. Naturally, you'll want to check that any wave files referenced in your resource script are in your project directory, or that you fully qualify the path to the wave file.

 
Tip: Quick resource compile
If you have C++Builder 3.0, you can right-click on the RC file in the Project Manager to compile the resource file.

Putting the wave resource to work

Now that you have a wave resource bound to your EXE, you can put it to work. There are two ways to play a wave file contained as a resource. Let's take a look.

 

The easy way

It's easiest to play a sound resource by using the Windows API function PlaySound. (We discussed PlaySound last month in the article "Low-level Wave Audio, Part 1.") Here's how the code looks:
PlaySound("Chimes", HInstance, SND_RESOURCE);
You didn't know it would be so easy, did you? Note that the resource name is passed as the first parameter to PlaySound. The second parameter is the instance handle to the module that contains the wave resource. In this case, we use HInstance to tell Windows to look in the EXE for the wave resource. If your wave resources are contained in a DLL, you can load the DLL using LoadLibrary and use the returned instance handle in your call to PlaySound. For example:

 

HINSTANCE dllInstance;
dllInstance = LoadLibrary("myres.dll");
PlaySound("raygun", dllInstance, SND_RESOURCE);
The final parameter of PlaySound can contain many flags, but you must specify the SND_RESOURCE flag in addition to any other flags you provide. This flag tells Windows that the sound you're playing is a resource ( as opposed to a file on disk or an alias for a system sound).

 

The hard way

The hard way to play a sound resource involves using the low-level wave audio functions as discussed in the article, "Low-level Wave Audio, Part 2." You won't typically have to go to this extent to play wave resources, but sometimes it's necessary. For example, TAPI (the telephony API) allows you to play a wave file through a voice modem. The mechanism specified by TAPI for playing wave files uses the low-level wave audio functions (waveOutOpen and others). Hence, if you want to use wave resources with TAPI, you must go to the trouble of implementing the low-level wave audio functions.

The TResourceStream class is great for loading a resource into memory. Once it's loaded, you can manipulate the resource as needed. Here's the code to load the CHIMES resource used in the previous example:

 

TResourceStream* res = new TResourceStream(
  (int)HInstance, "CHIMES", "Wave");
The TResourceStream constructor takes three parameters. The first parameter is the instance handle of the module that contains the resource. In this case, the instance handle must be cast to an int because VCL expects the instance handle to be an integer. The second parameter is the name of the resource to load, and the final parameter is the resource type as defined in the RC file. Now that you have the wave resource loaded into memory, you can copy the important bits of data from the resource stream. A wave file in memory is, of course, in a pre-defined format. The format is identical to a wave file stored on disk. You can go the hard way and use mmioOpen to open the resource as a memory-mapped file, or you can just cheat a little and locate the wave format header and wave data using TResourceStream. For example, here's how to read the wave format header of a wave resource:

 

// Locate the fmt chunk.
int x, i;
int ckid = mmioStringToFOURCC("fmt ", 0);
for (i=0;i<100;i++) {
  res->Read(&x, 4);
  if (x == ckid) break;
  res->Position -= 3;
}
// Read the size of the format data.
int size;
res->Read(&size, 4);
// Read the wave format.
memset(&WaveFmt, 0, sizeof(WAVEFORMATEX));
res->Read(&WaveFmt, size);
You can do the same thing to read the actual wave data. For instance, the ResourceChange method in Listing A of the accompanying article, "Low-level Wave Audio, Part 2," shows how you can load a wave resource into a buffer in preparation for playback. Once you've loaded the wave format header and loaded the wave data into a buffer, you can play the wave data using the low-level audio functions as described in the accompanying article.

 

Conclusion

Playing a wave resource isn't difficult once you know how to go about it. Whenever possible, you should opt for the easy way and use the PlaySound function to play the wave resource. When nothing else will do, though, the low-level wave audio functions provide an alternate means of playing a wave resource.