Fundamentals of Audio and Video Programming for Games (Pro-Developer)
If you look at the creation function for a CSound object, you will see the following code.
// Special effects. pFXManager = new CSoundFXManager*[dwNumBuffers]; for( i=0; i<dwNumBuffers; i++ ) { pFXManager[i] = new CSoundFXManager( ); pFXManager[i] ->Initialize( GetBuffer(i) ); }
This code first creates an array of CSoundFXManager pointers (one for each sound buffer), creates one CSoundFXManager object for each member of the array, and then calls Initialize on that object. The CSoundFXManager class (also defined in the
The declaration of this class (in the
class CSoundFXManager : public CSoundFXData { public: CSoundFXManager( ); ~CSoundFXManager( ); HRESULT Initialize ( LPDIRECTSOUNDBUFFER lpDSB ); HRESULT SetFXEnable( DWORD esfxType ); HRESULT ActivateFX( ); HRESULT LoadCurrentFXParameters( ); HRESULT DisableAllFX( ); LPDIRECTSOUNDFXCHORUS8 m_lpChorus; LPDIRECTSOUNDFXCOMPRESSOR8 m_lpCompressor; LPDIRECTSOUNDFXDISTORTION8 m_lpDistortion; LPDIRECTSOUNDFXECHO8 m_lpEcho; LPDIRECTSOUNDFXFLANGER8 m_lpFlanger; LPDIRECTSOUNDFXGARGLE8 m_lpGargle; LPDIRECTSOUNDFXPARAMEQ8 m_lpParamEq; LPDIRECTSOUNDFXWAVESREVERB8 m_lpReverb; LPDIRECTSOUNDFXI3DL2REVERB8 m_lpEnvironment; LPDIRECTSOUNDBUFFER8 m_lpDSB8; protected: DSEFFECTDESC m_rgFxDesc[eNUM_SFX]; const GUID * m_rgRefGuids[eNUM_SFX]; LPVOID * m_rgPtrs[eNUM_SFX]; BOOL m_rgLoaded[eNUM_SFX]; DWORD m_dwNumFX; HRESULT EnableGenericFX( GUID guidSFXClass, REFGUID rguidInterface, LPVOID * ppObj ); };
We will start by discussing the protected data members , and then look at the protected and public methods.
The protected member m_rgFxDesc holds an array of buffer descriptions (held in DSEFFECTDESC structures) for each effect. The DSEFFECTDESC structure is just a small one.
typedef struct _DSEFFECTDESC { DWORD dwSize; DWORD dwFlags; GUID guidDSFXClass; DWORD_PTR dwReserved1; DWORD_PTR dwReserved2; } DSEFFECTDESC, *LPDSEFFECTDESC;
The structure is initialized in the EnableGenericFX method, which we will discuss in a moment. The only member of much interest is the guidDSFXClass GUID. Of the other members, the size is set to the size of the structure, the flags to zero, and the reserved members are unused.
The next protected member is m_rgRefGuids , which holds an array of GUIDs identifying the effects to be applied. Then, m_rgPtrs holds the DirectSound interface pointer for each effect. There is a DirectSound interface for each of the nine effects, and they are named as follows.
IDirectSoundFXChorus; IDirectSoundFXCompressor; IDirectSoundFXDistortion; IDirectSoundFXEcho; IDirectSoundFXFlanger; IDirectSoundFXGargle; IDirectSoundFXParamEq; IDirectSoundFXWavesReverb; IDirectSoundFXI3DL2Reverb;
The only interface without an obvious name is the last one, which is the interface for environmental effects (I3DL2 stands for Interactive 3D Audio Rendering Guidelines, Level 2, and is a standard for environmental reverb).
The protected member m_rgLoaded simply holds a flag, set in the SetFXEnable method, and is simply there to prevent the effect from being initialized twice. Finally, the protected member m_dwNumFX holds a count of the special effects to be applied.
That covers the last of the data members, so now it s time to take a look at the methods.
The creation method of the CSoundFXData class simply sets all the memory locations to zero.
The Initialize method, called from the creation function of the CSound class, gets the DirectSoundBuffer8 interface pointer (which is the DirectSound interface that supports all the special effects).
HRESULT CSoundFXManager::Initialize( LPDIRECTSOUNDBUFFER lpDSB ) { HRESULT hr; if( NULL == lpDSB ) return S_FALSE; // Get the interface. if( FAILED( hr = lpDSB-> QueryInterface( IID_IDirectSoundBuffer8, (LPVOID*) &m_lpDSB8 ) ) ) return hr; return S_OK; }
The next method to look at is SetFXEnable . This method takes one member of the enumeration ESFXType as a parameter. This enumeration is used here and elsewhere in the code, often to step through the nine special effects.
enum ESFXType { eSFX_chorus = 0, eSFX_compressor, eSFX_distortion, eSFX_echo, eSFX_flanger, eSFX_gargle, eSFX_parameq, eSFX_reverb, eSFX_environment, // Number of enumerated effects. eNUM_SFX };
The SetFXEnable method first sets the loaded flag ( m_rgLoaded ), so a subsequent call to this method will not reload anything, and then uses a switch statement to call EnableGenericFX for each of the special effects. The code for these two methods follows, starting with SetFXEnable .
HRESULT CSoundFXManager::SetFXEnable( DWORD esfxType ) { HRESULT hr; if( esfxType >= eNUM_SFX ) return E_FAIL; if( m_rgLoaded[esfxType] ) return S_FALSE; else m_rgLoaded[esfxType] = TRUE; switch ( esfxType ) { case eSFX_chorus: hr = EnableGenericFX( GUID_DSFX_STANDARD_CHORUS, IID_IDirectSoundFXChorus8, (LPVOID*) &m_lpChorus ); break; case eSFX_compressor: hr = EnableGenericFX( GUID_DSFX_STANDARD_COMPRESSOR, IID_IDirectSoundFXCompressor8, (LPVOID*) &m_lpCompressor ); break; case eSFX_distortion: hr = EnableGenericFX( GUID_DSFX_STANDARD_DISTORTION, IID_IDirectSoundFXDistortion8, (LPVOID*) &m_lpDistortion ); break; case eSFX_echo: hr = EnableGenericFX( GUID_DSFX_STANDARD_ECHO, IID_IDirectSoundFXEcho8, (LPVOID*) &m_lpEcho ); break; case eSFX_flanger: hr = EnableGenericFX( GUID_DSFX_STANDARD_FLANGER, IID_IDirectSoundFXFlanger8, (LPVOID*) &m_lpFlanger ); break; case eSFX_gargle: hr = EnableGenericFX( GUID_DSFX_STANDARD_GARGLE, IID_IDirectSoundFXGargle8, (LPVOID*) &m_lpGargle ); break; case eSFX_parameq: hr = EnableGenericFX( GUID_DSFX_STANDARD_PARAMEQ, IID_IDirectSoundFXParamEq8, (LPVOID*) &m_lpParamEq ); break; case eSFX_reverb: hr = EnableGenericFX( GUID_DSFX_WAVES_REVERB, IID_IDirectSoundFXWavesReverb8, (LPVOID*) &m_lpReverb ); break; case eSFX_environment: hr = EnableGenericFX( GUID_DSFX_STANDARD_I3DL2REVERB, IID_IDirectSoundFXI3DL2Reverb8, (LPVOID*) &m_lpEnvironment ); break; default: hr = E_FAIL; break; } return hr; } HRESULT CSoundFXManager::EnableGenericFX( GUID guidSFXClass, REFGUID rguidInterface, LPVOID * ppObj ) { // If an effect is already allocated return S_FALSE. if( *ppObj ) return S_FALSE; if( m_dwNumFX >= eNUM_SFX ) return E_FAIL; // Set the effect to be enabled. ZeroMemory( &m_rgFxDesc[m_dwNumFX], sizeof(DSEFFECTDESC) ); m_rgFxDesc[m_dwNumFX].dwSize = sizeof(DSEFFECTDESC); m_rgFxDesc[m_dwNumFX].dwFlags = 0; CopyMemory( &m_rgFxDesc[m_dwNumFX].guidDSFXClass, &guidSFXClass, sizeof(GUID) ); m_rgRefGuids[m_dwNumFX] = &rguidInterface; m_rgPtrs[m_dwNumFX] = ppObj; m_dwNumFX++; return S_OK; }
Note that EnableGenericFX is the function that initializes the buffer description structure (held in the m_rgFxDesc array of DSEFFECTDESC structures), which only copies in the GUID class, GUID and interface pointer, which are all provided as parameters. Note that the m_rgPtrs array contains pointers to the special effect interface pointers (such as m_lpChorus ), which at this stage, should all be NULL.
The method that populates the interface pointers by using the m_rgPtrs array is ActivateFX . This method turns on a special effect with a call to the DirectSound SDK method GetObjectInPath , which changes the pointers such as m_lpChorus from NULL to the valid interface pointer for that effect. ActivateFX also provides DirectSound with the special effect information with a call to another DirectSound method, SetFX .
HRESULT CSoundFXManager::ActivateFX( ) { DWORD dwResults[eNUM_SFX]; HRESULT hr; DWORD i; if( NULL == m_lpDSB8 ) return E_FAIL; if( m_dwNumFX == 0 ) return S_FALSE; if( FAILED( hr = m_lpDSB8->SetFX( m_dwNumFX, m_rgFxDesc, dwResults ) ) ) return hr; // Get reference to the effect object. for( i = 0; i < m_dwNumFX; i++ ) if( FAILED( hr = m_lpDSB8-> GetObjectInPath( m_rgFxDesc[i].guidDSFXClass, 0, *m_rgRefGuids[i], m_rgPtrs[i] ) ) ) return DXTRACE_ERR( TEXT("GetObjectInPath"), hr ); return S_OK; }
Note that the DirectSound SetFX method takes as input parameters the number of effects to be applied and the array of buffer descriptions. It also takes, as an output parameter, the address of an array to hold the results of the loading of each effect.
|
The Rumpus tool strictly applies the special effects in the order they appear in the Special Effects dialog box: Chorus, Compression, Distortion, Echo, Gargle, Flanger , Parameq, Reverb, and Environmental Reverb. The order in which multiple effects are applied to a sound can dramatically change the output, and that order is determined by the SetFX method. The order in which the array of DSEFFECTDESC structures (pointed to by the m_rgFxDesc parameter in the sample code listed for the ActivateFX method) are sent to the SetFX method is the order that effects are applied. If your application involves the extensive use of multiple effects, you should ensure they are being applied in a meaningful order.
|
However, at this point, we will not take advantage of the information held in dwResults (there is not much that can be done if an effect fails to load, or is loaded in software rather than hardware).
Note that the SetFX method must be called before a call is made to the GetObjectInPath method.
The DisableAllFX method releases all the interface pointers, clears all the protected members, and then makes the following two calls.
// Buffer must be stopped before calling SetFx. if( m_lpDSB8 ) m_lpDSB8->Stop(); // This removes all effects from the buffer. m_lpDSB8->SetFX( 0, NULL, NULL );
The first call stops the special effects buffer. The second call to SetFX with zero and NULL parameters has the effect of turning off all effects. This must be done after the sound has been stopped.
The LoadCurrentFXParameters method tests to see if the interface pointers for each effect are not NULL, and then makes a call to SetAllParameters on that interface. Each statement in this method is similar to the following code.
if( m_lpChorus ) m_lpChorus->SetAllParameters( &m_paramsChorus );
Obviously, this takes the copy of the parameters in the CSoundFXManager object and provides them to the DirectSound SDK.
This completes our discussion of the CSoundFXManager class. Although its methods are somewhat exhausting to go through, now that we have them in place, applying special effects to the Rumpus tool will be easier.