Beginning Game Audio Programming [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Beginning Game Audio Programming [Electronic resources] - نسخه متنی

Mason McCuskey

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید





OTHER WAYS TO LOAD WAV FILES

Now that you have a basic understanding of the WAV file format, it's time to switch gears back into DirectMusic mode and learn some different ways to load wave files. In the last chapter, you learned how to load wave files from disk; now you'll learn how to load them from memory or from your application's resources.


Loading from Memory


It's possible to tell the DirectMusic Loader object to read a string of bytes from memory instead of from disk. This is a very powerful feature because it allows you to store your wave files however you want, as long as you can read the entire file into memory. You could store your wave files in a ZIP file, or in a custom file format, or you could even download them from the Internet.

Chapter 3, "Wave Audio Playback," you used the LoadObjectFromFile method of IDirectMusicLoader8 to load a wave file from disk. It turns out that LoadObjectFromFile is just a shortcut you can use for loading things from disk; the real power lies in the loader's GetObject method, which can read from disk or memory.

To use GetObject, you must first fill in a DMUS_OBJECTDESC structure, which tells DirectMusic where your object is and how you want to load it. To load an object from memory, you need to fill in two important members in this structure. First, fill in the pbMemData byte pointer with the location of your "file" in memory. Next, fill in the llMemLength member, which tells DirectMusic the length of your memory chunk.

Using this knowledge, you can enhance the audio manager class by creating another LoadSound overload:


CSoundPtr CAudioManager::LoadSound(unsigned char *data, int datalen)
{
HRESULT hr;
CSound *snd = new CSound(this);
DMUS_OBJECTDESC desc;
memset(&desc, 0, sizeof(DMUS_OBJECTDESC));
desc.dwSize = sizeof(DMUS_OBJECTDESC);
desc.dwValidData = DMUS_OBJ_MEMORY | DMUS_OBJ_CLASS;
desc.guidClass = CLSID_DirectMusicSegment;
desc.llMemLength = datalen;
desc.pbMemData = data;
hr = m_Loader->GetObject(&desc, IID_IDirectMusicSegment8,
(void **)&snd->m_Segment);
ThrowIfFailed(hr, "CAudioManager::LoadSound: GetObject failed.");
snd->m_OriginalData = data;
return(CSoundPtr(snd));
}

This code starts off similar to the original LoadSound method—it creates a new CSound object. From there, the code paths diverge; whereas the original LoadSound called LoadObjectFromFile, this code sets up the DMUS_OBJECTDESC structure and calls GetObject.

Not every member of the DMUS_OBJECTDESC structure needs to be filled. The dwSize variable is mandatory, as is the dwValidData flags. Whether anything else needs to be filled in depends on the dwValidData flags. By specifying DMUS_OBJ_MEMORY, you'll tell DirectX Audio that you want it to load an object from memory. Specifying this flag obligates you to fill in the llMemLength and pbMemData structures with the size of the data, and the data itself, respectively.

The other flag, DMUS_OBJ_CLASS, tells DirectX Audio that you want to load the type of object specified in the guidClass member. The code sets guidClass to CLSID_DirectMusicSegment, telling DirectX Audio that it should give us back a segment interface.

After the call to GetObject, the code then sets the m_OriginalData member of the new sound object. This is the manager's way of telling the sound, "Hey, your segment object is using memory that the program has allocated, so you need to free this memory when you are destroyed." You can't free the memory immediately after the call to GetObject because DirectX Audio uses that exact memory inside the segment. It doesn't make a copy of the memory; instead, it relies on that data staying where it's at for the life of the segment.

However, when the segment does die, it's still your responsibility to free the memory.

Ch4p1_WAVFileXRay demonstrates the use of this method.


Loading from CWAVFile


Hmm… time for something fun. The audio engine currently has CWAVFile, and it currently has CSound. It'd be sweet if you could convert a CWAVFile into a CSound. If you could do that, your game could create sound effects at runtime, using CWAVFile, and then pump those sound effects into CSound for playback inside the game.

This glorious integration is really easy. Just make CWAVFile save itself out to memory, and then give that memory to the DirectMusic Loader, which will create a segment from it. Here's the code:


CSoundPtr CAudioManager::LoadSound(const CWAVFile &wavfile)
{
unsigned char *savedata = wavfile.Save();
return(LoadSound(savedata, wavfile.GetTotalSize()));
}

You've gotta love that! It's two lines of code! Just remember not to free the memory that Save allocated because this memory is in use by the CSound. Actually, because the raw memory LoadSound overload sets the new CSound's m_OriginalData member, there's nothing to worry about—when the CSound is destroyed, it will free the memory.


Loading from Resources


Once you know how to load a wave file from memory, loading from a resource is a snap. Just call a few Win32 API functions to get a pointer to your custom resource (for example, wave file), then give that pointer to the Loader via the DMUS_OBJECTDESC structure described before. Here's a code snippet:


CSoundPtr CAudioManager::LoadSound(HMODULE hmod,
char *type, WORD resID)
{
CSound *snd = new CSound(this);
HRESULT hr;
DMUS_OBJECTDESC ObjDesc;
HRSRC hFound = FindResource(hmod, MAKEINTRESOURCE(resID), type);
if (NULL == hFound) { Throw("couldn't find resource!"); }
HGLOBAL hRes = LoadResource(hmod, hFound);
ObjDesc.dwSize = sizeof(DMUS_OBJECTDESC);
ObjDesc.guidClass = CLSID_DirectMusicSegment;
ObjDesc.dwValidData = DMUS_OBJ_CLASS | DMUS_OBJ_MEMORY;
ObjDesc.pbMemData = (BYTE *) LockResource(hRes);
ObjDesc.llMemLength = SizeofResource(hmod, hFound);
hr = m_Loader->GetObject(&ObjDesc, IID_IDirectMusicSegment8,
(void**) &snd->m_Segment);
ThrowIfFailed(hr, "GetObject failed.");
return(CSoundPtr(snd));
}

Again, this code is very similar to the other LoadSound methods. The main difference is that before it fills in the DMUS_OBJECTDESC structure, the code has to find and load the resource. The call to FindResource gives back a HRSRC handle, which is then used in the call to LoadResource. LoadResource gives back a HGLOBAL, which is used by LockResource. LockResource takes a HGLOBAL and gives back a pointer to the resource data in memory. The code puts this pointer into the pbMemData member of the DMUS_OBJECTDESC structure, and it's good to go. Note also the call to SizeofResource to determine how big a resource is.

Ch4p2_WAVInResource demonstrates the use of this method.

But how do you get the wave file into the resource to begin with? Here's the recipe for adding a wave file to your program's resources:

If you don't already have a resource file, you need to create one and add it to your project. In DevStudio, select New from the File menu, and select Resource Script as the type of file. The convention is to name the program's main resource script <projectname>.rc, where project name is what you named your EXE, DSP, and DSW. For example, the resource script for Ch4p2_WAVInResource is Ch4p2_WAVInResource.rc. Close the file immediately after DevStudio opens it up for you.

Once you've created the file, you'll see a ResourceView tab appear on your workspace (see Figure 4.3). Click this tab, then right-click on the topmost folder. Select Insert from the context menu, then click the Import button (see Figure 4.4). Select the WAV file you'd like to add from the file browser.


Figure 4.3: After inserting an RC file into your project, you get a new ResourceView tab.


Figure 4.4: Importing a WAV file into your application's resources.

Next, you need to tell DevStudio what kind of resource this is. You can pick anything you want for the resource type, but you should remember to pass that same type to the LoadSound method of the audio manager. I use "WAVE" to denote wave files.

If all goes well, you should see your custom resource listed underneath a folder with a name identical to the resource type name you specified in the last step.

Compile your program and your resource will get sucked in and included in your EXE.

Make sure you include resource.h inside any CPP files that want to use resources. Resource.h uses #define to bind resource IDs (what the Win32 API wants) to the resource names you've specified in the resource editor.





Tip

By default, DevStudio doesn't store the entire custom resource in the RC file. Rather, it stores a callout, telling it where to go to get the original file. Usually this is fine, but if you would rather have DevStudio store a copy of the WAV you selected directly inside the resource script, right-click on the resource in question, select Properties, and you'll see a dialog like that in Figure 4.5.


Figure 4.5: Uncheck the external file checkbox to embed your WAV directly in the resource script of your program.

Uncheck the External File checkbox and your resource will get stored inside the script itself. Note that resources stored in the RC file take up significantly more space—roughly five times their normal size—due to the fact that they are "ASCII-ized" (for example, a single byte of 30 hex becomes an ASCII string of "0x30" or something).



Specifying a Search Directory


Most games like to store their data in special directories. I've seen many professional games store all of their audio data inside a separate "Sound" or "GameAudio" folder, underneath their main install location. Here's a trick for making this easier.

You can tell the Loader what directory it should look in to load objects. Then, instead of calculating and supplying the full pathname to each sound you load, you can supply the filename and the loader will look for that file in the directory you told it about earlier.

The method you want is called SetSearchDirectory. Here's a new audio engine function that tells the loader where to find the sounds:


void CAudioManager::SetSearchDirectory(std::string dirname)
{
// convert dirname to wide-string
WCHAR widedirname[MAX_PATH];
DXUtil_ConvertGenericStringToWide( widedirname, dirname.c_str());
HRESULT hr = m_Loader->SetSearchDirectory(
CLSID_DirectMusicSegment, widedirname, true);
ThrowIfFailed(hr, "SetSearchDirectory() failed.");
}

As you can see, there's not much to it. Just convert the directory name into a wide character string, and then call the SetSearchDirectory method. The first parameter tells DirectX Audio what type of object you want to set the directory for (in this case, segments). The next parameter is the directory name, and the last parameter, if true, tells DirectX Audio to "forget" the previous locations of objects it may have already loaded (usually a good idea).

The Ch4p3_SearchDir sample program demonstrates this new method.

/ 127