ENVIRONMENTAL MODELING
3D sound is more than just sources and listeners. The last two chapters left one very important variable completely out of the picture: the environment in which the sound is occurring. For example, using knowledge from the last two chapters, you could create some realistic footstep sounds. Attach a listener at your player's head, and one sound source on each foot, and play the sound based on the animation of the leg—no problem. But there is still an element missing. Footsteps on the marble floor of a huge library's atrium sound very different from footsteps on the marble floor of a small kitchen. The massive size of the library atrium is going to cause the sound waves to behave very differently than they would in a small kitchen.Thankfully, there are APIs that can model these environmental effects and come up with reasonable approximations of how a given environment would change a sound. If you provide footsteps_on_marble.wav, the environmental effects processing can make it sound like a footstep on marble in a huge library or a small kitchen.
Environment Modeling Using I3DL2
Reverberation is arguably the most important property of an environment. In this section, you'll learn more about how to use reverberation to convey a particular environment, and you'll learn how to use I3DL2 to implement reverberation effects inside your game.
What Is I3DL2?
I3DL2 stands for Interactive 3D Audio, Level 2. I3DL2 was formed by the Interactive Audio Special Interest Group, an organization made up of audio professionals from all faces of the game development and multimedia industries. It originally started in 1994, and shortly thereafter formed a working group that in 1998 created the IA-SIG Interactive 3D Audio Rendering and Evaluation Guidelines (Level 1). In 1999 they enhanced the standard and released the IA-SIG Interactive 3D Audio Rendering Guidelines (Level 2), or I3DL2.
I3DL2 Terms and Vocabulary
Before you learn how to code environment modeling using I3DL2, you need to get a handle on the language of reverberation. To accurately model an environment, you'll need to understand some of the components that make up the echoes and muffles of an acoustic environment.Picture a big room (say, a cathedral) with a sound source (say, a grunting orc) and a listener (a valiant hero) separated by some distance (see Figure 15.1).

Figure 15.1: Shows direct path, early reflections, and late reverb.
When that orc grunts, the valiant hero is going to hear several distinct wave types. First, he will hear the sound waves coming directly from the orc (path 1). This is called the direct path. Second, he will hear the sound waves that started in the same general direction but bounced off a wall on the way (path 2)—these are called early reflections. Finally, the valiant hero will hear the sound waves that started off in the opposite direction and bounced off many different walls. Collectively, these sound waves are called the late reverberation. Figure 15.2 shows a graph of these three distinct wave types.

Figure 15.2: A graph of the three distinct wave types.
Of course, this is oversimplified, but the basic model is the same. To model an environment in I3DL2, you adjust variables inside this model. For example, in I3DL2 there's one particular variable that controls how much time passes between the listener hearing the direct path of the sound, and hearing the first early reflection. To simulate a large room, you'd increase this value, because in a large room the sound waves following path 2 have to travel far before they hit a wall. To simulate a smaller room, you'd make the time difference between direct path and early reflection very small, possibly so small that the listener can't tell the difference. I3DL2 lets you adjust the delays and volumes of all three paths (direct path, early reflections, and late reverberation).Also, when sounds are reflected, the high frequencies behave differently than the low ones. Specifically, the high frequencies fade out much faster. Also, different rooms reflect sounds with different diffusion and density values. I3DL2 lets you adjust all of these parameters to simulate a wide variety of acoustical environments.
I3DL2: Just Another Effect
In DirectX Audio, I3DL2 is implemented as an effect, through an effect interface named IDirectSoundFXI3DL2Reverb8. This means you use it the same way you use any effect—the same way you learned back in Chapter 12. This is great, because it makes using I3DL2 very simple—you attach this effect to your source sound, set its parameters, and the effect will take care of morphing your sound based on those parameters.To integrate the I3DL2 effect into the audio engine, you need a new effect class. Here's its declaration:
class CI3DL2Effect : public CEffect
{
public:
CI3DL2Effect() { ZeroMemory(&m_Params, sizeof(DSFXI3DL2Reverb)); ClearPreset(); }
virtual ~CI3DL2Effect() { }
virtual void SetAllParameters(IDirectMusicAudioPath8 *audiopath);
virtual GUID GetGuid() { return(GUID_DSFX_STANDARD_I3DL2REVERB); }
virtual void SetPreset(DWORD i3dl2preset);
virtual void ClearPreset() { m_Preset = -1; }
DSFXI3DL2Reverb m_Params;
protected:
DWORD m_Preset;
};
Notice that this class, while similar to the other effect classes, has a few additional methods and members. There are two new SetPreset and ClearPreset calls, as well as a new protected member, m_Preset. You'll learn how all these work in the Preset Effects section, coming in a couple of pages.
I3DL2 Reverb Parameters
For now though, take a closer look at the DSFXI3DL2Reverb structure (summarized in Table 15.1). This is the heart of the I3DL2 reverb processing. The variables in this structure describe to the DirectX effect how to morph the sound. Here's what each member means in detail (note that I'm purposely describing them out of order, since some explanations depend on others):
Parameter Name | Description |
---|---|
flHFReference | Reference high frequency, in Hertz. Several upcoming parameters operate on high frequencies. This parameter tells DirectX what a high frequency is. For example, if you set this to its default 5,000Hz value, you're telling DirectX that anything above 5,000Hz should be considered a high frequency. |
lRoom | Room attenuation value, in millibels (mB). This parameter influences how loud the room effect is. Remember, these are attenuations, which means that full volume is zero, and anything less than full volume is a negative number. The default value is –1,000mB (–10 decibels). |
lRoomHF | Room attenuation value for high frequencies, in millibels. In real life, different rooms muffle high frequencies differently than low frequencies. Using this variable, you can tell DirectX how much to attenuate high frequencies (frequencies over the flHFReference parameter, above). For example, you could assign this value to –10,000mB, and lRoom to –1,000mB to create an environment that completely muffles high frequencies but lets low ones go through. The effect would be similar to being underwater. |
flRoomRolloffFactor | The room rolloff factor tells DirectX how quickly the reverb and reflection effects fade as the listener and source get farther away. Note that this parameter only influences the reverb effects; the rolloff factor for the original sound is still controlled by the listener. |
flDecayTime | How long (in seconds) the echoes persist. If you set this to a high value, it'll sound like you're in the Grand Canyon (lots of echoes!). A lower value will simulate muffled rooms without any echoes. |
flDecayHFRatio | Ratio of the high frequency decay time to the low frequency decay time. If you set this to 1, high frequency and low frequency echoes will both last for the exact same amount of time. If you set this lower than 1, high frequencies will fade out before lows. For values greater than 1, high frequencies will persist longer than low ones (usually unrealistic). The default value is set to 0.83. |
lReflections | Early reflection attenuation relative to lRoom, in mB. Early reflections are the first echoes one hears in a room (see Figure 15.1). This parameter tells DirectX whether the volume of the early reflection effects is the same as lRoom,or whether they're softer or louder. The default is –2,602mB, making them softer. |
flReflectionsDelay | Number of seconds that elapse between the direct path sound and the early reflections. Simply put, this is how long DirectX waits after a sound is played before it starts playing echoes. The default is 0.007 seconds. To simulate larger rooms, increase this value; to simulate small spaces, decrease it. |
lReverb | Attenuation of the late reverb relative to lRoom, in millibels. Similar to lReflections, this specifies whether reverb effects should be louder or softer than the lRoom attenuation. The default is 200mB, making them slightly louder. |
flReverbDelay | The time between the first early reflection and the reverb, in seconds. In other words, this tells DirectX how long to wait once the first reflection has been played before playing the reverb effects. |
flDiffusion | Late reverberation delay echo density, in percent. The default is 100%. |
flDensity | Late reverberation delay modal density, in percent. The default is 100%. |
Tip | In case you're wondering, a millibel (abbreviated mB) is one one/hundreth of a decibel. That is, 100 millibels is the same as one decibel. Several of the upcoming parameters are attenuations measured in millibels. If you specify an attenuation of –50 mB, you're specifying a volume reduction of half a decibel. |
All of these parameters have a minimum, maximum, and default value predefined in the DirectX headers. These follow the convention DSFX_I3DL2REVERB_(parameter)_MIN, DSFX_I3DL2REVERB_(parameter)_MAX, and DSFX_I3DL2REVERB_(parameter)_DEFAULT. For example, the three predefined values for lRoom are DSFX_I3DL2_REVERB_ROOM_MIN, DSFX_I3DL2_REVERB_ROOM_MAX, and DSFX_I3DL2_REVERB_ROOM_DEFAULT.
Preset Effects
sound.h header.
Creating an Environment Editor
As you can see, the numbers aren't exactly intuitive. To properly create stunning audio environments using I3DL2 reverberation, you're going to need a program that will allow you to adjust these numbers and instantly hear the effects of those adjustments. In other words, you'll need an environment editor.Thankfully, there aren't that many variables. A simple dialog with 12 trackbars (also called sliders) should be enough for a basic editor, so that's what you'll learn how to create in this section. Figure 15.3 shows a screenshot of this editor. As you can see, it's just a bunch of trackbars, along with load and save buttons, and a couple of buttons to browse for and play a WAV.

Figure 15.3: A screenshot of the Ch15p2_I3DL2EnvEdit or sample program.
Go ahead and spend some time playing with the Ch15p2_I3DL2EnvEditor sample program—the sections that follow will dissect this program for you. If you haven't had extensive experience writing for the Win32 GUI, this sample program may teach you a few tricks in that arena.
Initialization
Trackbars, in Win32 programming, are easy to work with as long as you remember to call the InitCommonControls Win32 API function. If you fail to call that, you won't be able to create any window that has a trackbar in it. Trackbars, unlike other controls (buttons, edit boxes, and so forth), weren't part of the original release of the Windows GUI. They were grafted on later, hence the need to call this function.
Dealing with Trackbars
I'm not going to cover the standard mechanism of creating a modeless dialog box and writing a message loop for that—there are several good introductory Windows Programming books that cover that material, and cover it well. But I would like to spend some time showing you how the GUI functionality works.The trackbars, like every other control, operate using windows messages. The three most important messages you can send to a trackbar are detailed in Table 15.2.
Track Bar Message | Description |
---|---|
TBM_SETRANGE | Set the minimum and maximum value of the trackbar. |
TBM_SETPOS | Set the current position of the trackbar. |
TBM_GETPOS | Return the current position of the trackbar. When the user moves a trackbar, it notifies its parent window by sending a WM_HSCROLL message (for trackbars aligned horizontally) or a WM_VSCROLL message (for trackbars aligned vertically). |
This looks pretty easy, right? All you need to do is send each trackbar a TBM_SETRANGE and TBM_SETPOS message to initialize the dialog box. Then, you watch out for WM_HSCROLL messages, and send TBM_GETPOS to get the trackbar's new position.There's one small catch—trackbars only take integers. For most applications, this isn't a problem, but this sample program has several values that must be floats. For example, there's the reverb delay variable, flReverbDelay, which has a minimum value of 0.0 and maximum value of 0.1.To accommodate this, you need to scale everything up by multiplying. For example, you might choose to multiply the flReverbDelay values by 10,000, giving you a nice integer range of zero to 1,000. Everything is then fine, as long as you remember to multiply the flReverbDelay on the way into the slider, and divide the slider's position by that value after you get it using TBM_GETPOS.
Tip | Trackbars can do much more than what I've described here. For example, you can also give them tick marks, and autobuddy them to another control! For more information, check out the MSDN documentation, at Platform SDK/User Interface Services/Shell and Common Controls/Trackbar Controls. |
In the Ch15p2_I3DL2EnvEditor sample program, there are two overloaded functions called RefreshSlider. The function that takes integer values does nothing special; the floating point function takes a scaling factor.
Loading and Saving Environments
The easy way to load and save something that's in a structure is to simply save that structure's memory footprint directly to disk, as a binary file. And that's exactly what this simple editor does. Here are the functions that load and save from disk:
void OnLoad(HWND hDlg)
{
char filename[MAX_PATH] = { 0 };
OPENFILENAME ofn = { sizeof(OPENFILENAME), hDlg, NULL,
"Reverb Files\0*.reverb\0All Files\0*.*\0\0",
NULL,
0, 1, filename, MAX_PATH, NULL, 0, NULL,
"Open Reverb File",
OFN_FILEMUSTEXIST|OFN_HIDEREADONLY, 0, 0,
TEXT(".reverb"), 0, NULL, NULL };
// Display the OpenFileName dialog. Then, try to load the
// specified file
if(GetOpenFileName(&ofn))
{
int handle = open(filename, O_RDONLY | O_BINARY);
if (handle == -1) Throw("Can't open file!");
read(handle, &g_ReverbValues, sizeof(DSFXI3DL2Reverb));
close(handle);
RefreshGUI(hDlg);
}
}
void OnSave(HWND hDlg)
{
char filename[MAX_PATH] = { 0 };
OPENFILENAME ofn = { sizeof(OPENFILENAME), hDlg, NULL,
"Reverb Files\0*.reverb\0All Files\0*.*\0\0",
NULL,
0, 1, filename, MAX_PATH, NULL, 0, NULL,
"Save Reverb File",
OFN_HIDEREADONLY, 0, 0,
TEXT(".reverb"), 0, NULL, NULL };
// Display the OpenFileName dialog. Then, try to load the
// specified file
if(GetOpenFileName(&ofn))
{
int handle = open(filename, O_RDWR | O_BINARY | O_CREAT | O_TRUNC);
if (handle == -1) Throw("Can't save file!");
write(handle, &g_ReverbValues, sizeof(DSFXI3DL2Reverb));
close(handle);
}
}
As you can see, the bulk of the code deals with the common file open and file save dialog boxes. To use these dialogs, you need to fill up an OPENFILENAME structure. You can look up the GetOpenFileName function in MSDN to figure out what each value in the OPENFILENAME structure means.Once you get the structure filled up, displaying the dialog is as easy as calling GetOpenFileName. If the user hits OK, the function will return true, and the preceding code will go about the business of saving or loading the file.
Editor-Wrap Up
Of course, this is just for a simple editor. An editor for a professional game would be much more complex than this—it would take into account the player's position in the game world, and would allow audio designers to set up different sections of a level, each section with its own environmental audio parameters.Just to get your mind warmed up, here are a few things you could do to enhance the program:
Make it save its variables in a more standard format, such as XML.
Add support for presets—I envision a drop-down menu that lets you choose a preset, tweak it, and save it out as a new environment.
Add features that allow the user to edit DirectSound 3D listener and source properties in addition to these environment variables. Quite frequently, an adjustment to one will necessitate an adjustment to another.
I3DL2 Wrap-Up
Keep in mind that there's more to I3DL2 than just reverberation. I3DL2 also specifies an interface for occlusion and obstruction, two principles that are equally important in 3D sound (see below for their definitions). Through DirectSound property extensions, you can access this occlusion and obstruction functionality and use it to make your game sound even more realistic.If you'd like to learn more about I3DL2, check out the IA-SIG's 3D Audio Working Group, at http://www.iasig.org.