Fundamentals of Audio and Video Programming for Games (Pro-Developer)

The EAX 2.0 and I3DL2 environments are both audio standards. The I3DL2 (Interactive 3D Audio Rendering Guidelines, Level 2) environments are supported in software by DirectSound, and are implemented in the Rumpus 3D SFX sample. The I3DL2 environment is not widely supported in hardware, which gives EAX 2.0 the obvious advantage of performance. The standards themselves are very similar; the following sections describe the environmental effects settings that are used in both.

Room Property

The Room property determines the master volume for reflected sound. It is expressed in both standards as units of hundredths of a decibel in the range -10,000 (-100 dB, the minimum value, indicating no reflections) to 0 (0 dB, the maximum value).

Both standards also use an identical default value of -1,000 (-10 dB).

Room HF Property

The Room HF (Room High Frequency) property is similar to the Room property, but it only applies to high-frequency sound, attenuating it differently from the low frequencies for that room.

Again both standards use the same range (-10,000 to 0) and units, with the default for I3DL2 being 0 (0 dB) and for EAX -10 (-1 dB).

One difference between EAX and I3DL2 in our implementation is that these values can be applied to both the source and listener in EAX (see the EAX Source Buffer section later in this chapter), but just to the source in I3DL2.

Room Rolloff Factor Property

The Room Rolloff Factor property has a very similar effect to the global rolloff factor that you can define in DirectSound (see Chapter 3), in that it determines how a sound attenuates over distance, but in this case applies to reflected sound.

A value of 0 indicates that the sound does not attenuate. A value of 1 indicates that the volume will decrease by 6 dB (in other words, by half) when the distance from the source doubles. See the description of rolloff and minimum distance in Chapter 3 for the effect that will happen when the values are less than or greater than 1.

Both standards use a range of 0 to 10, and both have a default of 0.

Decay Time Property

The Decay Time property sets the time that it takes for the reverberation to decay, in seconds. Both standards set ranges from a minimum of 0.1 seconds to a maximum of 20 seconds, with a default of 1.49 seconds.

Confined spaces containing sound-deadening material will have a very short decay time, whereas large rooms with reflective surfaces will have long decay times.

Decay HF Ratio Property

The Decay HF Ratio property provides a ratio that is used to determine how quickly high-frequency sounds decay relative to other sounds. A value of 1.0 for this ratio means that both decay at the same rate. A value of less than 1.0 indicates that high-frequency sound decays faster than other sounds, and a value of greater than 1.0 indicates that high-frequency sound decays at a slower rate.

Both standards use a range of 0.1 (the minimum setting, indicating that high frequencies decay ten times faster than other frequencies) to 2.0 (the maximum setting, indicating that high frequencies decay at half the rate of other frequencies), with a default setting of 0.83.

Reflections Property

The Reflections property sets the amount of early reflections for an environment. This is much higher with hard reflective surfaces than with softer absorbing ones. Both standards set the range from -10,000 (-100 dB, the minimum setting indicating no early reflections) to 1,000 (10 dB, the maximum setting). This property is used along with the initial Room property to set the final output of early reflections. Both standards set the default at -2,602 (-26.02 dB).

Reflections Delay Property

While the Reflections property sets the amount of early reflections, the Reflections Delay property sets the timing of those reflections. The time here is obviously the delay in seconds, and ranges from 0.0 (indicating no delay) to 0.3 (indicating a delay of three tenths of a second).

To simulate the audio properties of a narrow passageway, the Reflections property will be set high, and the delay very short. A wide open field, on the other hand, will have a very low Reflections setting, and a much longer delay. Both standards set the default for the Reflections Delay property at 0.007 seconds.

Reverb Property

The Reverb and Reverb Delay properties are very similar to the Reflections and Reflections Delay properties, except that they apply to late reverberations instead of early reflections. Again, the Room setting is used in conjunction with these properties to come up with a final output of late reverberations.

Both the EAX and I3DL2 standards use a minimum setting of -10,000 (-100 dB, indicating no late reverberations at all) to a maximum of 2,000 (20 dB). Note that the maximum level for late reverberations is double that for early reflections. Both standards set a default of 200 (2 dB).

Reverb Delay Property

The Reverb Delay property obviously sets the delay time for the late reverberations, and the important point here is that this time is relative to the Reflections Delay property time described previously. Reverb Delay can be set in the range 0.0 (indicating no addition to the Reflections Delay time) to 0.1 (indicating that one tenth of a second is added to the Reflections Delay value to give the start time for late reverberations). Both standards use the default setting of 0.011 (11 hundredths of a second added to the Reflections Delay).

Very metallic surfaces, such as the sewer pipe environment (see Figure 7.3), will have high levels of late reverberation. Natural settings will often have none at all.

Figure 7.3: Sewer pipe environment.

Diffusion Property

The Diffusion property is slightly more complicated to explain. It refers to the density of the sound in the reverberations. A high value here makes the echoes of reverberation sound most solid, and a low value will give a grainy effect closer to a series of distinct echoes. In the I3DL2 standard, the range for this setting is given as a percentage (from 0.0 to 100.0), with the default set at 100 percent. For EAX, the property is called Environment Diffusion, and the range is from 0.0 to 1.0. So an I3DL2 setting of 50.0 corresponds to an EAX setting of 0.5. The EAX default is 1.0 (so is identical to the I3DL2 default of 100 percent).

An appropriate use of diffusion settings below the default would be for percussive sound sources such as drums, or perhaps public address systems.

Environment Size Property

The Environment Size property is unique to EAX, and sets the apparent size of the environment in meters. This value can range from 1.0 to 100.0, with the default set at 7.5 meters . By default, varying this property scales several of the other properties to match, including the Reflections, Reflections Delay, Reverb, Reverb Delay and Decay Time properties. This default effect can be prevented by setting some of the flags in the EAX Flags property described later in this chapter.

Air Absorption HF Property

The Air Absorption HF property is unique to EAX. It provides a setting to adjust the medium that the sound is traveling through, to simulate humid, foggy, or other air conditions. Its units are in hundredths of a decibel per meter, and the range is from -100 (-1 dB) to 0 (0 dB, no deduction ). The default value is set at -0.05 dB, so there is this small attenuation of both the source and reflected sound. This setting should be lowered if the air is humid, or raised if the air is very dry.

Density Property

The Density property is unique to the I3DL2 standard. It refers to the number of resonances per hertz in late reverberations. The range for this is a percentage (from 0.0 to 100.0), with the default at 100 percent. Typically, you might set lower densities in smaller rooms to produce more hollow sounds.

HF Reference Property

The High Frequency (HF) Reference property is unique to the I3DL2 standard, and simply provides a setting for what is considered high-frequency sound. The units are in Hz and the range is from 20.0 to 20000.0. The default value is set at 5000.0 Hz. In the EAX standard, the definition of high frequency is fixed at 5000 Hz.

EAX Flags

There are six flags that can be set for EAX environments, all of which are set to true by default. The first five, if set to true, indicate that a change to the Environment Size property will have a proportional effect on the property identified by the flag, or, if false, that a change in Environment Size will have no effect. The flags are:

EAXLISTENERFLAGS_DECAYTIMESCALE

EAXLISTENERFLAGS_REFLECTIONSSCALE

EAXLISTENERFLAGS_REFLECTIONSDELAYSCALE

EAXLISTENERFLAGS_REVERBSCALE

EAXLISTENERFLAGS_REVERBDELAYSCALE

The final flag helps limit unnaturally long decay times of high-frequency sounds by forcing a limit to the decay time to be calculated from the Air Absorption HF value:

EAXLISTENERFLAGS_DECAYHFLIMIT

EAX Source Buffer

EAX also allows a range of settings to be applied to sound sources. They are less important than listener settings because they only apply to the one sound, rather than all sounds. In our sample we use these settings to apply obstruction and occlusion effects only, but they can be used to add some reverberation settings only for that sound source. The full description of the source and listener settings is available in a document entitled Environmental Audio Extensions: EAX 2.0, which can be downloaded from http://developer.creative.com.

Obstruction Property

The Obstruction property is in the range of -10,000 (-100 dB, indicating that no direct sound gets through) to 0 (0 dB, indicating that all direct sound gets through). The lower this value is below zero, the greater the muffling effect of the obstruction.

Remember that an obstructed sound source is one where the direct path to the listener is blocked, but where there is a clear airway to the listener, so that early and late reflections and reverberations are not affected by the obstruction (see Figure 7.1).

Obstruction LF Ratio Property

The Obstruction LF Ratio property is a ratio that sets how low-frequency sound is attenuated relative to high-frequency sound. If the value is 0.0, there is no attenuation for low-frequency sound (it all gets through). If the value is 1.0 (the maximum), it is attenuated at the same rate as high-frequency sound.

Occlusion Property

The values and ranges for the Occlusion and Occlusion LF Ratio properties have a very similar effect as those for obstruction. A value of -10,000 (-100 dB) for Occlusion indicates full occlusion (no sound gets through), and any value above this indicates that some source sound gets through.

Remember that occlusion occurs when there is no direct airway from the sound source to the listener. An example of occlusion occurs when you are in a room with the doors and windows closed, but can still hear noises from outside (see Figure 7.1).

Occlusion LF Ratio Property

The Occlusion LF Ratio property determines the ratio of how the occlusion muffling of low-frequency sound compares to high-frequency sound. A fully occluded listener (indicated by an occlusion setting of -10,000) will still be able to hear plenty of low-frequency sound if Occlusion LF Ratio is set between 0.0 and 0.5 (0.25 is the default).

Occlusion Room Ratio Property

The Occlusion Room Ratio property applies only to early reflections and late reverberations; it does not apply to the source sound. It applies an additional attenuation factor for these sounds. The value ranges from 0.0 (indicating no additional attenuation) up to 10 (indicating ten times the attenuation). The default value is 0.5.

The characteristics of an occlusion depend on the type of material creating the occlusion; in other words, the material that stands between the sound source and the listener (for example, a metal door, a glass window, a stone wall, or a wooden floor). The  eax.h header file includes some presets to give you an idea of the occlusion properties of different materials.

Implementation of EAX Properties

All the EAX 2.0 properties previously described are supported by property sets, and many are implemented both in the Property Values sample described in this chapter, and in the Concertina framework in the following chapter.

With all this theory now out of the way, we ll start by looking at the code for the sample.

The Property Values Project

In order to do any development work on the Property Values sample, you will need to download the EAX 2.0 development package from the Creative Labs Web site: http://developer.creative.com .

After you download the EAX 2.0 package, copy the  eax.h header into the Property Values project. You can then compile the project. You will also receive the eax.lib library in the download, which you will need to place in an appropriate directory for the linker to find. The PropertyValues.exe program will run if you do not download the EAX 2.0 package. The download is only necessary to examine the  eax.h file, and to compile and link the sample after you have made any changes to it.

Other than the  eax.h header file, there is one other unique file for the Property Values project,  PropertyValues.cpp . In addition, the  extended_dsutil.cpp and  extended_dsutil.h files also form part of the project.

PropertyValues.cpp

One difference between this project and the other projects in this book is the following definition.

#define INITGUID

This is necessary to prevent obscure link errors relating to GUIDs. This is because GUID constants are generally used across multiple source files, but each constant can only be initialized once, otherwise it appears in multiple object files and the linker throws up errors. To make this easier to manage, the DEFINE_GUID macro is used to declare GUID constants. If INITGUID is not defined, DEFINE_GUID resolves to the following code.

extern const GUID name;

However, if INITGUID is defined, DEFINE_GUID resolves as follows .

extern const GUID name = { <guid data> };

So if you declare your GUIDs in a header file that might be included multiple times, use the define INITGUID statement. One and only one source file must define INITGUID , and it should be defined before including any header files.

The global variables that are unique to this project are as follows.

LPKSPROPERTYSET lpPropertySet = NULL; // Property Set Interface. LPDIRECTSOUND3DBUFFER lpDS3DBuffer; int g_Environment = -1; // Environment number. EAXBUFFERPROPERTIES EAXBufferProperties; // Structure to hold // default values. Long lObstruction = -4000; // Sample obstruction value. Float fObstructionLFRatio = 0.2f; Long lOcclusion = -1500; // Sample occlusion value. float fOcclusionLFRatio = 0.2f;

The lpPropertySet value is the most important global variable. It is through a property set interface that you gain access to all the listener and source buffer properties that you want to use. In this sample, we have only one property set pointer (corresponding to our one sound). In a full application, you would need one for every sound buffer that you want to apply the source buffer effects to, and at least one in order to apply the listener environmental effects. Although the listener properties are accessed through a property set for one individual sound buffer, they in fact apply to all sounds.

The DirectSound 3-D pointer ( lpDS3DBuffer ) is needed for an interim stage in gaining access to the property set. The EAXBufferProperties structure is used in this sample simply to hold the default values of a source buffer, so we can restore them when obstruction or occlusion is turned off. The four obstruction and occlusion values were chosen to provide the appropriate example effects to the sample.

Most of the code in this sample will be familiar to you from our previous samples, so we will concentrate only on the new requirements of property sets.

The first function to look at is initDirectSound. Note the call to CoInitialize with a NULL parameter. This call is necessary to initialize COM, which is required for the property sets to work. It is also necessary for DirectSound special effects to work. However, this call is not necessary for 3-D positioning and for the playback of 2-D or 3-D sounds.

The code for the load3DEAXSound function should be examined in full.

bool load3DEAXSound(char filename[]) { HRESULT hr; unsigned long bytesReturned; DWORD bufferFlags = DSBCAPS_CTRL3D; if (g_pSound) { g_pSound ->Stop(); SAFE_DELETE( g_pSound ); } if (SUCCEEDED(g_pSoundManager->Create( &g_pSound ,filename, bufferFlags, DS3DALG_HRTF_FULL, 1 ))) { lpDS3DBuffer = g_pSound -> Get3DInterface(0); if (lpDS3DBuffer != NULL) { if(SUCCEEDED(lpDS3DBuffer->QueryInterface(IID_IKsPropertySet, (void**)&lpPropertySet))) { // Check for EAX 2.0 support. if (SUCCEEDED(hr = lpPropertySet->QuerySupport( DSPROPSETID_EAX20_ListenerProperties, DSPROPERTY_EAXLISTENER_ALLPARAMETERS, &ulSupport))) { // Record the default buffer properties settings. hr = lpPropertySet->Get(DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_ALLPARAMETERS, NULL, 0, &EAXBufferProperties, sizeof(EAXBufferProperties), &bytesReturned); return true; } } } } return false; }

In this function, the bufferFlags variable is set to DSBCAPS_CTRL3D . We set this flag because a 3-D buffer is required in order to retrieve a property set interface.

Next we test to see if the sound already exists. If it does, we first stop it, and then we delete it.

To create the sound buffer, we call the Create method of the CSoundManager object. The bufferFlags parameter of the Create method indicates that we need a 3-D buffer. The next step is to retrieve a pointer to that 3-D buffer, using the get3DInterface method that we added to the CSound class just for this purpose.

Performance Tip

To apply environmental effects to 2-D sounds, set up the buffers for 3-D sounds using the DSBCAPS_CTRL3D flag, and then make the following DirectSound SDK call to disable the actual 3-D processing for the buffer.

SetMode(DS3DMODE_DISABLE, DS3D_IMMEDIATE);

 

Then comes the critical call to QueryInterface to retrieve a property set interface using the IID_IKsPropertySet GUID .

Next we check whether the hardware supports EAX 2.0 properties. This is done by using the QuerySupport call on the lpPropertySet interface pointer. The three arguments are: a GUID that identifies the properties we are looking for, in this case DSPROPSETID_EAX20_ListenerProperties ; an identifier for the properties we want to use, in this case DSPROPERTY_EAXLISTENER_ALLPARAMETERS ; and the address of a DWORD (or unsigned long) that will contain certain additional flags that can be used beyond the success or failure of the call. Nothing further is tested here; the success of the call gives us enough information.

That is all that is essential in this function. However, for the purposes of the sample we also retrieve the current settings (the default settings in this case) for the source buffer properties. This is done using the Get method of a property set object.

Get Method

Notice that the first parameter to the Get method is a GUID, DSPROPSETID_EAX_BufferProperties , which is defined in  eax.h . The second parameter is a member of an enumeration, DSPROPERTY_EAXBUFFER_ALL-PARAMETERS , also defined in  eax.h . The third and fourth parameters should be set to NULL and 0 respectively (we do not need these to get and set properties). The fifth parameter is the address of where the Get call is to store its findings “ in this case an EAXBUFFERPROPERTIES structure, also defined in  eax.h but declared as global data in the  PropertyValues.cpp file. The sixth parameter provides the Get call with the size of the object passed to it (in this case a structure, but it is often a float or a long). Finally, we add the address of a DWORD to record the actual amount of data copied to the location provided. We do not use this last parameter in our code, but if you get errors, it may be useful to check that it does equal the buffer size that is passed in.

There are a number of methods of a property set interface, but we are mainly interested in three of them: Get , Set , and QuerySupport . The other three are the ubiquitous COM methods: QueryInterface , AddRef and Release . The QuerySupport method is used once in our sample to test for the correct hardware support.

We use another Get call in the showEAXSettings function to help display all the listener settings in an edit box of the Property Values dialog box. This is provided so you can easily see which listener parameters change with the environment setting.

Finally, in the main callback function MainDlgProc , we actually make some Set calls.

Starting with the easy example of the 26 radio buttons , clicking any one of these initiates the following code.

case IDC_RADIO1: case IDC_RADIO26: g_Environment = LOWORD(wParam) - IDC_RADIO1; hr = lpPropertySet ->Set(DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENT, NULL,0,&g_Environment,sizeof(int)); showEAXSettings( hDlg ); break;

The g_Environment parameter simply holds an integer value from 0 to 25, indicating which of the 26 preset environments to apply. All that is necessary to set a preset environment is to call Set with this integer and the DSPROPERTY_EAXLISTENER_ENVIRONMENT property. Note that the Set call uses the identical first six parameters to the Get call (see the discussion of the Get method earlier in this section).

After selecting an environment by using the radio buttons, the user might press the Play button. This will run the following code.

case IDC_PLAY: if (g_Environment == -1) { g_Environment = EAX_ENVIRONMENT_GENERIC; hr = lpPropertySet ->Set(DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENT, NULL,0,&g_Environment,sizeof(int)); showEAXSettings( hDlg ); } if(!play3DEAXSoundBuffer(true) ) { EndDialog( hDlg, IDABORT ); } EnablePlayUI( hDlg, FALSE ); break;

This code first sets the preset generic environment if none has yet been set, then calls the play3DEAXSoundBuffer function. This function does nothing special; all the work to activate the environment and sound source property sets has already been done.

bool play3DEAXSoundBuffer(bool loopit) { DWORD dwFlags; if (g_pSound != NULL) ( if (loopit) dwFlags = DSBPLAY_LOOPING; else dwFlags = 0; if (SUCCEEDED(g_pSound ->Play3DBuffer( 0, 0, dwFlags ))) return true; } return false; }

Finally, the following code is run when the occlusion check box is selected. This code snippet shows an example of occlusion, although the code is almost identical for obstruction.

case IDC_OCCLUSION: if ( IsDlgButtonChecked( hDlg, IDC_OCCLUSION ) == BST_CHECKED ) { hr = lpPropertySet->Set(DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSION, NULL, 0, &lOcclusion, sizeof(long)); hr = lpPropertySet->Set(DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSIONLFRATIO, NULL, 0, &fOcclusionLFRatio,sizeof(float)); hr = lpPropertySet->Set(DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSIONROOMRATIO, NULL, 0, &fOcclusionLFRatio, sizeof(float)); } else { hr = lpPropertySet->Set(DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSION, NULL, 0, &EAXBufferProperties.lOcclusion, sizeof(long)); hr = lpPropertySet->Set(DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSIONLFRATIO, NULL, 0, &EAXBufferProperties.flOcclusionLFRatio, sizeof(float)); hr = lpPropertySet->Set(DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSIONROOMRATIO, NULL, 0, &EAXBufferProperties.flOcclusionLFRatio, sizeof(float)); } break;

The first block of code is run if the Occlusion check box is selected. Notice that there are three Set calls, setting the three source buffer properties:

DSPROPERTY_EAXBUFFER_OCCLUSION

DSPROPERTY_EAXBUFFER_OCCLUSIONLFRATIO

DSPROPERTY_EAXBUFFER_OCCLUSIONROOMRATIO

These properties are set to the global values that we defined at the top of the source file. For simplicity, we set the low-frequency ratio and room ratio to be identical.

The second block of code is run if the Occlusion check box is cleared. In this case, the three properties are set back to their defaults “ remember that the load3DEAXSound function stored a copy of all the source buffer settings in an EAXBufferProperties structure, precisely for this purpose. When coding your application, you might also want to get the default listener properties and store them in a similar structure.

We have not shown how to set individual environment properties in this sample, only the environment presets. However, the procedure for setting individual environment properties is very similar to the setting of individual properties on the source buffer, as shown earlier for occlusion and obstruction.

Категории