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

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

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

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

Mason McCuskey

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

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

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





INTEGRATING OPENAL INTO THE AUDIO ENGINE

Adding OpenAL support to the audio engine is pretty straightforward. This section will guide you through the process of installing the OpenAL SDK and using it to create a handful of new AudioEngine classes.


Installing OpenAL


To begin the installation of the OpenAL SDK, go to your CD's goodies folder and double-click the OpenALSDK.exe file contained in the OpenAL goodies folder. Navigate through the wizard, installing the SDK at C:\program files\creative labs\OpenAL 1.0 SDK.





Tip

If you're using OpenAL, you should include as part of your game distribution the OpenAL redistributables, found in the dll\Redist folder of your OpenAL SDK. The files in this package will allow anyone who needs it to install the OpenAL runtime files on his system.


The SDK includes a few sample programs and some documentation, both of which I encourage you to look at later. For now though, to use OpenAL, all you need to do is make sure that the OpenAL Include and Lib folders are in the appropriate search paths for any projects that use OpenAL. The sample program for this chapter expects to find the OpenAL include files at ..\..\OpenAL 1.0 SDK\Include, and the lib files at ..\..\OpenAL 1.0 SDK\lib.


Structure of the OpenAL Library


The OpenAL SDK is divided into two different libraries. First, there's the core OpenAL library (OpenAL32.lib), and second, there's the OpenAL Utility Library (ALut.lib). Usage of the OpenAL Utility Library is optional, but most of the time you'll want to use it, since it makes working with OpenAL a bit easier.

The OpenAL library functions, however, are grouped into three different categories. Most of the functions fall into the core OpenAL category. All of the core OpenAL category functions begin with al. However, there are also some functions that fall into the OpenAL utilities category (and begin with alut), and some context functions not discussed here that fall into the OpenAL context category (and begin with alc). The context functions are useful if you're working with more than one audio device, or if you want explicit control over the single audio device you're using. Most games don't have to worry about them, instead relying on the OpenAL utility library to automatically select the best context and use that.


New OpenAL AudioEngine Classes


Supporting OpenAL in the audio engine means creating a handful of new classes. First and most important, there's COpenALManager, a new class that's the OpenAL equivalent of CAudioManager. I did things this way so that the OpenAL section of the engine would remain completely independent from the other parts that use DirectX.

Here's the definition of COpenALManager:


class COpenALManager
{
public:
COpenALManager();
virtual ~COpenALManager();
virtual void Init();
virtual void UnInit();
COpenALBufferPtr CreateBufferAndLoadWave(std::string filename);
COpenALSourcePtr CreateSource(COpenALBufferPtr linktobuffer);
COpenALListener &GetListener() { return(m_Listener); }
protected:
static COpenALListener m_Listener;
};

There's not much in it—just methods to initialize and uninitialize OpenAL, a couple methods to create buffers and sources, and an OpenAL listener accessed via the GetListener method.

The code that uses the OpenAL section of the audio engine is responsible for instantiating the actual COpenALManager class, as well as providing an accessor function to it (just like for CAudioManager).

The other new classes are COpenALBuffer, COpenALSource, and COpenALListener, all of which you'll learn about in the following sections.

Note also that COpenALBuffer and COpenALSource both have smart pointer classes (COpenALBufferPtr and COpenALSourcePtr). These smart pointers are the same as the ones you've seen before; they're derived from the CRefCountPtr template and specialized to work with COpenALBuffers and COpenALSources. These smart pointers free us from having to remember to delete buffers and sources when we no longer need them—the pointers will do it automatically.


Initializing and Uninitializing OpenAL


Now that you have OpenAL installed, and you have a brief idea of the new class structure, you can learn how to write code that uses it. Take a look at Figure 14.2, which shows an overview of using OpenAL.


Figure 14.2: Typical flow when using OpenAL

All code that uses OpenAL must begin by initializing the OpenAL library, and end by uninitializing it. The easiest way to do this is by calling the OpenAL utility functions alutInit and alutExit. Here's how it looks in code, in the Init and UnInit methods of COpenALManager:

The alutInit function takes two parameters (argc and argv), but currently they're not used, so you can just pass 0 for argc and NULL for argv. The alutExit function doesn't take any parameters.


void COpenALManager::Init()
{
alutInit(0, NULL);
alGetError(); // clear error code
}
void COpenALManager::UnInit()
{
alutExit();
}

Notice that the code clears the error value immediately after initializing OpenAL. This brings us to our next topic, namely, how to error check in OpenAL.


Error Checking in OpenAL


There are two main ways you can detect errors when you call OpenAL functions. First, and most obvious, some functions will return values that denote error (if a function returns AL_FALSE, something bad has happened).

Second, you can use the alGetError function to figure out if the last thing you did errored out. The alGetError function returns an enumerated value, which hopefully is AL_NO_ERROR, but could be one of the values shown in Table 14.4 if something's gone wrong.




























Table 14.4: OpenAL Error Codes

Return Value


Meaning


AL_INVALID_NAME


You gave a bad name as input.


AL_INVALID_ENUM


You passed an invalid integer value to a function that expected a value defined by an enumeration.


AL_INVALID_VALUE


The value you passed doesn't make any sense, or is illegal (for example, you passed a negative number when specifying the pitch of a source).


AL_INVALID_OPERATION


You called something at the wrong time, or in the wrong way.


AL_OUT_OF_MEMORY


Memory allocation failed.




Creating and Destroying Buffers


The first thing you'll usually do to set up the sound for your game is to load the WAV files you need and create your buffers. This is accomplished by calling the long-winded, but very descriptively named COpenALManager method: CreateBufferAndLoadWave. Here's how it looks:


COpenALBufferPtr COpenALManager::CreateBufferAndLoadWave(
std::string filename)
{
COpenALBuffer *newbuffer = new COpenALBuffer();
unsigned int newid;
alGenBuffers(1, &newid);
if (alGetError() != AL_NO_ERROR) {
Throw("Error generating AL buffer.");
}
newbuffer->m_ID = newid;
newbuffer->LoadWave(filename);
return(COpenALBufferPtr(newbuffer));
}

Here you can see the code creating a new COpenALBuffer instance, and then calling alGenBuffers. The alGenBuffers function takes two arguments: the number of buffers you want to create (one in this case) and a pointer to an array of integers (or just a single integer, if you're creating one buffer) that will receive the OpenAL IDs of the new buffer(s). Here the code creates one buffer and stores its ID in the newid variable, which it then gives to the new COpenALBuffer class. After the class has got its ID, the OpenAL manager calls the buffer's LoadWave function, which loads the wave specified into the buffer. Once that's done, the code creates and returns a new smart pointer for the new buffer.

Here's how LoadWave looks:


void COpenALBuffer::LoadWave(std::string filename)
{
unsigned char *data = NULL;
alutLoadWAVFile(const_cast<char *>(filename.c_str()),
&m_Format, (void **)&data, &m_Size, &m_Freq, &m_Loop);

// copy wave data into buffer
alBufferData(m_ID, m_Format, data, m_Size, m_Freq);
if (alGetError() != AL_NO_ERROR) {
alutUnloadWAV(m_Format, data, m_Size, m_Freq);
Throw("Error loading WAV data into buffer.");
}
// free original data
alutUnloadWAV(m_Format, data, m_Size, m_Freq);
}

The real work here is done by the OpenAL utility function alutLoadWAVFile. This function takes two inputs—a filename and an address of a pointer. It allocates memory, loads the wave into that memory, sets the pointer to it, and returns the properties of the wave file. The code hands all this stuff off to the alBufferData function, which copies the wave data out of data and into the OpenAL buffer with the specified ID (m_ID). Once that's done, the original WAV data is unloaded via a call to the OpenAL Utility function alutUnloadWAV.

When the buffer is no longer needed, its associated smart pointer (COpenALBufferPtr) takes care of deleting the object. As part of the deletion process, COpenALBuffer's destructor will be called. The destructor looks like this:


COpenALBuffer::~COpenALBuffer()
{
if (alIsBuffer(m_ID) == AL_TRUE) alDeleteBuffers(1, &m_ID);
}

Essentially, it asks OpenAL if its ID (m_ID) exists, and if it does, it tells OpenAL to delete it, by calling the alDeleteBuffers function.


Creating and Destroying Sources


Once the buffers have been created and have had WAV data put into them, it's time to create a few sources that reference those buffers. This is done by calling the CreateSource method of COpenALManager:


COpenALSourcePtr COpenALManager::CreateSource(
COpenALBufferPtr linktobuffer)
{
COpenALSource *newsource = new COpenALSource();
unsigned int newid;
alGenSources(1, &newid);

if (alGetError() != AL_NO_ERROR) {
Throw("Error generating AL source.");
}
newsource->m_ID = newid;
newsource->LinkToBuffer(linktobuffer);
return(COpenALSourcePtr(newsource));
}

The pattern here is the same as creating a buffer. The code makes a new COpenALSource class, calls alGenSources to create the OpenAL source, takes the ID supplied by that function and gives it to the new class, and then calls the LinkToBuffer method of the class to tell the source that it should use the given buffer. It finishes by returning a new smart pointer to the new source.

The LinkToBuffer method of COpenALSource is really just setting the AL_BUFFER property for that source:


void COpenALSource::LinkToBuffer(COpenALBufferPtr linkto)
{
alSourcei(m_ID, AL_BUFFER, linkto->GetID());
if (alGetError() != AL_NO_ERROR) {
Throw("Error linking to OpenAL buffer.");
}
m_Buffer = linkto;
}

Notice that the COpenALSource class keeps a pointer to the buffer it's linked to (m_Buffer). This guarantees that a buffer won't be deleted until all sources that use it have also been deleted (or have been linked to another buffer). Again, the power of smart pointers makes this really easy.

Source destruction looks just like buffer destruction—it happens in the destructor and calls alIsSource to make sure the source's ID is valid before calling alDeleteSources to purge the source from OpenAL.


Playing Sources


To play a source, call the PlayFromStart method of COpenALSource. There are also additional methods to pause, play from pause, stop, and rewind sources. These use the alSourcePlay, alSourcePause, alSourceStop, and alSourceRewind OpenAL calls, and are easy enough to follow just by looking at the code.

/ 127