Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9
      Miscellaneous

DirectXAudio: Sound Effects Part 2
Author: Jack Hoxley
Written: 29th July 2002
Contact: [EMail]
Download: AUD_04.Zip (489kb)


Contents of this lesson
1. Introduction
2. Overview of Sound effects
3. Setting up DirectSound for Effects
4. Applying and Configuring Sound Effects


1. Introduction

Welcome back to part four of the DirectXAudio series. This is the second tutorial covering the more clever aspects of DirectSound8 - in particular, the capabilities you can't get using the Windows API. The first tutorial covered 3D-sounds, a very clever and very useful feature of DirectSound8. This tutorial will extend DirectSound8 to include real-time sound effects.


2. Overview Of Sound Effects

Sound effects are a new feature in DirectSound8, hence are quite an interesting little feature to play with. However, as with all good things - there's a catch. Only the newer sound cards available will support these effects in hardware (ie, the CPU does little/no work); thus you are quite likely to chew up more precious CPU time when using these effects...

If you've already read the tutorial on 3D sound effects, you may be interested to know that these sound effects can also be applied to your 3D sound sources. This opens up huge potential for future games - the person that can master the subtle and effective use of 3D sound effects will be the person that can create the most believable gaming atmospheres. 

Take a simple case where you have a long metal corridor with a PA speaker at one end. The recorded voice file would be that of someone talking normally - clearly, without any distortion. This sound can then be played, in 3D to sound like it's coming from the PA speaker, a given distance and direction from the player. Already this will sound quite believable if you have some decent speakers. How about you add some sound effects to the file being played - first off, some distortion: PA speakers are never particularly clear/high quality. Then, add a softened echo/reverb to the sound - to simulate how the sound might distort along the concrete corridor. Quite simply you now have a very very believable sound-engine, one that with a 1/2 decent graphics engine would have no problem in immersing the player in the game world.

Expect this sort of attention-to-detail in future games! (I'm certainly looking forward to it!)


3. Setting up DirectSound for Effects

Setting up DirectSound isn't very different from standard playback (2D or 3D). Given that Sound Effects are a new technology you want to choose (or allow the user to choose) the best audio device attached to the system. Ideally, a sound card that can support DS8 sound effects in hardware (frees up the CPU).

The main change comes in how you initialize the secondary buffer - you must include an additional flag to let DirectX know that you're intending to apply sound effects to the buffer.

'//This line will change if you're using 3D sounds. The only important flag here is DSBCAPS_CTRLFX
DSBDesc.lFlags = DSBCAPS_CTRLFX Or DSBCAPS_CTRLVOLUME Or DSBCAPS_CTRLPAN

Once you've added the DSBCAPS_CTRLFX flag, you can carry on as normal and load the sound from the hard drive. Bare in mind that if you're using 3D sounds you'll need to keep the source in mono (1 channel) format. For all other purposes, it doesn't matter whether it's a mono or stereo sound source.

4. Applying and Configuring Sound Effects

This is where it starts to get harder. Before we get into the code, I'm going to run down a list of the available sound effects included in DirectX8 (data taken from the SDK help file):

Chorus: 
GUID: DSFX_STANDARD_CHORUS
Object: DirectSoundFXChorus8
Configuration: DSFXCHORUS
Description: Chorus is a voice-doubling effect created by echoing the original sound with a slight delay and slightly modulating the delay of the echo.

Distortion: 
GUID: DSFX_STANDARD_DISTORTION
Object: DirectSoundFXDistortion8
Configuration: DSFXDISTORTION
Description: Distortion is achieved by adding harmonics to the signal in such a way that, as the level increases, the top of the waveform becomes squared off or clipped.

Echo: 
GUID: DSFX_STANDARD_ECHO
Object: DirectSoundFXEcho8
Configuration: DSFXECHO
Description: An echo effect causes an entire sound to be repeated after a fixed delay.

Flange: 
GUID: DSFX_STANDARD_FLANGER
Object: DirectSoundFXFlanger8
Configuration: DSFXFLANGER
Description: Flange is an echo effect in which the delay between the original signal and its echo is very short and varies over time. The result is sometimes referred to as a sweeping sound. The term flange originated with the practice of grabbing the flanges of a tape reel to change the speed.

Gargle: 
GUID: DSFX_STANDARD_GARGLE
Object: DirectSoundFXGargle8
Configuration: DSFXGARGLE
Description: The gargle effect modulates the amplitude of the signal.

Environmental Reverberation: 
GUID: DSFX_STANDARD_I3DL2REVERB
Object: DirectSoundFXI3DL2Reverb8
Configuration: DSFXI3DL2REVERB
Description: DirectX supports environmental reverberation in accordance with the Interactive 3-D Audio, Level 2 (I3DL2) specification, published by the Interactive Audio Special Interest Group.

Parametric Equalizer: 
GUID: DSFX_STANDARD_PARAMEQ
Object: DirectSoundFXParamEq8
Configuration: DSFXPARAMEQ
Description: A parametric equalizer amplifies or attenuates signals of a given frequency.

Waves Reverb: 
GUID: DSFX_STANDARD_WAVES_REVERB
Object: DirectSoundFXWavesReverb8
Configuration: DSFXWAVESREVERB
Description: The Waves reverberation effect is intended for use with music. The Waves reverberation DMO is based on the Waves MaxxVerb technology, which is licenced to Microsoft.

Working out the results of individual effects on a general basis is not really very meaningful - most have several parameters which can drastically alter the eventual result, and when you combine multiple effects it's even less general! I strongly suggest you download the sample code and play around with the various settings to get a better idea.

The basic idea behind applying effects to a buffer is very simple - you fill out the relevant structure's (eg, DSFXWAVESREVERB). You then create a list of GUID's representing each effect you want to apply (you can apply more than one of the same type). Once you've registered a list of effects with the device you can then store the structure you initially setup.

The sample code is strongly linked to it's user-interface, so it looks a little less friendly than if you had it running in the background of a game (where the user has no control over the effect parameters). In order to initially configure the effects the following code is executed:

Private Sub SetupEffects()
    If DSBuffer Is Nothing Then Exit Sub
    If Not bLoaded Then Exit Sub
    If (DSBuffer.GetStatus And DSBSTATUS_PLAYING) = DSBSTATUS_PLAYING Then Exit Sub
    
    Dim lEffects As Long
    Dim lCurrFX As Long
    Dim FXList() As DSEFFECTDESC
    Dim lReturn() As Long
    
    If chkEcho.Value Then lEffects = lEffects + 1
    If chkChorus.Value Then lEffects = lEffects + 1
    If chkDistortion.Value Then lEffects = lEffects + 1
    If chkGargle.Value Then lEffects = lEffects + 1
    If chkParamEQ.Value Then lEffects = lEffects + 1
    If chkWaves.Value Then lEffects = lEffects + 1
    If chkCompressor.Value Then lEffects = lEffects + 1
    If chkFlanger.Value Then lEffects = lEffects + 1
    
    ReDim FXList(lEffects) As DSEFFECTDESC
    ReDim lReturn(lEffects) As Long
        
    lCurrFX = 0
    If chkEcho.Value Then
        FXList(lCurrFX).guidDSFXClass = DSFX_STANDARD_ECHO
        lCurrFX = lCurrFX + 1
    End If
    
    If chkChorus.Value Then
        FXList(lCurrFX).guidDSFXClass = DSFX_STANDARD_CHORUS
        lCurrFX = lCurrFX + 1
    End If
    
    If chkDistortion.Value Then
        FXList(lCurrFX).guidDSFXClass = DSFX_STANDARD_DISTORTION
        lCurrFX = lCurrFX + 1
    End If
    
    If chkGargle.Value Then
        FXList(lCurrFX).guidDSFXClass = DSFX_STANDARD_GARGLE
        lCurrFX = lCurrFX + 1
    End If
    
    If chkParamEQ.Value Then
        FXList(lCurrFX).guidDSFXClass = DSFX_STANDARD_PARAMEQ
        lCurrFX = lCurrFX + 1
    End If
    
    If chkWaves.Value Then
        FXList(lCurrFX).guidDSFXClass = DSFX_STANDARD_WAVES_REVERB
        lCurrFX = lCurrFX + 1
    End If
    
    If chkCompressor.Value Then
        FXList(lCurrFX).guidDSFXClass = DSFX_STANDARD_COMPRESSOR
        lCurrFX = lCurrFX + 1
    End If
    
    If chkFlanger.Value Then
        FXList(lCurrFX).guidDSFXClass = DSFX_STANDARD_FLANGER
        lCurrFX = lCurrFX + 1
    End If
    
    Debug.Print "SetupEffects() :: " & lEffects & " effects added"
    DSBuffer.SetFX lEffects, FXList, lReturn
    
    Dim I As Long
    For I = 0 To lEffects - 1
        'this next line is ugly ;) but it gives the correct output...
        Debug.Print "lReturn(" & I & ") - ", IIf(lReturn(I) = DSFXR_FAILED, "FAILED", _
                                             IIf(lReturn(I) = DSFXR_LOCHARDWARE, "LOCHARDWARE", _
                                             IIf(lReturn(I) = DSFXR_LOCSOFTWARE, "LOCSOFTWARE", _
                                             IIf(lReturn(I) = DSFXR_UNALLOCATED, "UNALLOCATED", _
                                             IIf(lReturn(I) = DSFXR_PRESENT, "PRESENT", _
                                             IIf(lReturn(I) = DSFXR_UNKNOWN, "UNKNOWN", _
                                             "???"))))))
    Next I
    
End Sub

The sample code for this tutorial is designed to allow at most 1 of each main effect type, therefore there can only be a maximum of 8 effects in the list. The above code checks if the user selected the effect (via a simple checkbox), if it was selected then it adds it to the list. The final important line is "DSBuffer.SetFX( )", this line sends the template list to the buffer. The function will return a list of return codes for each effect, which is then processed in the For loop. If the effect is going to work it needs to have a return code of DSFXR_LOCHARDWARE or DSFXR_LOCSOFTWARE.

This part is executed when the sound device is created, and can be left alone for a while. As far as the sample source code is concerned, the user can now configure the actual parameters for each effect they wish to use. Once the properties are selected the user needs to click "Apply Effects", when this happens the following code is executed to actually apply the changes.

'//Declarations necessary
Dim lEffects As Long
Dim FXList() As DSEFFECTDESC
Dim lReturn() As Long

'//These variables store the actual configuration values for each effect
Dim FX_Echo As DSFXECHO
Dim FX_Chorus As DSFXCHORUS
Dim FX_Distortion As DSFXDISTORTION
Dim FX_Gargle As DSFXGARGLE
Dim FX_ParamEQ As DSFXPARAMEQ
Dim FX_Waves As DSFXWAVESREVERB
Dim FX_Compressor As DSFXCOMPRESSOR
Dim FX_Flanger As DSFXFLANGER

'//These variables provide us with the access necessary to get/set
'//the configuration variables defined above.
Dim objFX_Echo As DirectSoundFXEcho8
Dim objFX_Chorus As DirectSoundFXChorus8
Dim objFX_Distortion As DirectSoundFXDistortion8
Dim objFX_Gargle As DirectSoundFXGargle8
Dim objFX_ParamEQ As DirectSoundFXParamEq8
Dim objFX_Waves As DirectSoundFXWavesReverb8
Dim objFX_Compressor As DirectSoundFXCompressor8
Dim objFX_Flanger As DirectSoundFXFlanger8

It is necessary to stop the sound playback before attempting to alter the effects settings. The sample code just informs the user of this requirement, but doesn't actually stop the music. The code for this:
If Not bLoaded Then Exit Sub
If DSBuffer Is Nothing Then Exit Sub
If ((DSBuffer.GetStatus And DSBSTATUS_PLAYING) = DSBSTATUS_PLAYING) Or _
   ((DSBuffer.GetStatus And DSBSTATUS_LOOPING) = DSBSTATUS_LOOPING) Then
    MsgBox "You must stop the sound first...!", vbInformation, "Warning"
    Exit Sub
End If

The next step is quite lengthy, but rather simple. All it involves is copying the values specified by the user in the user-interface to the DirectX structures. An example is shown below:
If chkEcho.Value Then
    lEffects = lEffects + 1
    FX_Echo.fFeedback = sld_Echo_Feedback.Value
    FX_Echo.fLeftDelay = sld_Echo_LeftDelay.Value
    FX_Echo.fRightDelay = sld_Echo_RightDelay.Value
    FX_Echo.fWetDryMix = sld_Echo_WetDry.Value
    FX_Echo.lPanDelay = chkEcho_PanDelay.Value
End If

You can see the full source code if you download the archive. The sld_Echo_* objects referenced above are sliders contained in Microsoft's common controls library.

After all the structures have been appropriately filled, we can go about informing the sound buffer. First we must get a reference to a "linking" object - this object acts as gateway between us and the actual sound buffer. It allows us to get and set the effect parameters.

Set objFX_Echo = DSBuffer.GetObjectinPath(DSFX_STANDARD_ECHO, _
                                                                  0, IID_DirectSoundFXEcho)
Set objFX_Chorus = DSBuffer.GetObjectinPath(DSFX_STANDARD_CHORUS, _
                                                                     0, IID_DirectSoundFXChorus)
Set objFX_Distortion = DSBuffer.GetObjectinPath(DSFX_STANDARD_DISTORTION, _
                                                                        0, IID_DirectSoundFXDistortion)
Set objFX_Gargle = DSBuffer.GetObjectinPath(DSFX_STANDARD_GARGLE, _
                                                                    0, IID_DirectSoundFXGargle)
Set objFX_ParamEQ = DSBuffer.GetObjectinPath(DSFX_STANDARD_PARAMEQ, _
                                                                         0, IID_DirectSoundFXParamEq)
Set objFX_Waves = DSBuffer.GetObjectinPath(DSFX_STANDARD_WAVES_REVERB, _
                                                                     0, IID_DirectSoundFXWavesReverb)
Set objFX_Compressor = DSBuffer.GetObjectinPath(DSFX_STANDARD_COMPRESSOR, _
                                                                            0, IID_DirectSoundFXCompressor)
Set objFX_Flanger = DSBuffer.GetObjectinPath(DSFX_STANDARD_FLANGER, _
                                                                      0, IID_DirectSoundFXFlanger)

The GetObjectinPath( ) call will fail if the requested effect doesn't actually exist for the current buffer. You can get around this easily if you know which effects you are/aren't using - for the sample code I implemented a simple error handler that will skip past these errors:
    Exit Sub
BailOut:
    If Err.Number = DMUS_E_NOT_FOUND Then
        Err.Clear
        Resume Next
    End If
End Sub

Once we've acquired our "gateway" objects to each effect, we can then use them to store our already-configured effect settings. This is done using the following simple code:
If chkEcho.Value Then objFX_Echo.SetAllParameters FX_Echo
If chkChorus.Value Then objFX_Chorus.SetAllParameters FX_Chorus
If chkDistortion.Value Then objFX_Distortion.SetAllParameters FX_Distortion
If chkGargle.Value Then objFX_Gargle.SetAllParameters FX_Gargle
If chkParamEQ.Value Then objFX_ParamEQ.SetAllParameters FX_ParamEQ
If chkWaves.Value Then objFX_Waves.SetAllParameters FX_Waves
If chkCompressor.Value Then objFX_Compressor.SetAllParameters FX_Compressor
If chkFlanger.Value Then objFX_Flanger.SetAllParameters FX_Flanger
 

From this point onwards, each time you play the sound effect (DSBuffer.Play) the effects will be applied. 


To fully appreciate the code presented in this tutorial you'll need to download the sample archive. You can download this from the top of the page, or from the DirectX8 downloads page.

DirectX 4 VB 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster