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.
MY_WAVE_RESOURCE WAVE "chimes.wav"That's it! The format for creating a custom resource is as follows:
RESNAME RESTYPE FILENAMETo 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.
[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. |
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 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.