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
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.
|