DirectXAudio:
Sound Effects
Author:
Jack Hoxley
Written: 7th October 2001
Contact: [EMail]
Download: AUD_01.Zip
(47kb)
Contents
of this lesson
1. Introduction
2. Initialising DirectSound
3. Creating and Playing a buffer
4. Setting the properties for the
buffer.
1.
Introduction
Welcome
to the first tutorial in using DirectXAudio; by
the end of this tutorial you should have all the
information you need to play sound effects in
your game. That simple? really? well, yes...
For
those of you who've already played around with
Direct3D - particularly the full-3D variety, will
be aware that there is a considerable amount of
theory work and understanding involved before
you are anywhere near making a usable/complete
engine. Luckily for you/me, DirectXAudio doesn't
share this steep learning curve - yes, there are
still things to be learnt before you're a master,
but as you'll soon see, there isn't much to learn
before you have an acceptable sound effects player.
2.
Initialising DirectSound
DirectSound
is what we're starting with, whilst DirectXAudio
is the general name given to the audio components
of DirectX8, there is still a difference between
Sound and Music. There are several cross-over's
between the two that we'll see later, and once
you've learnt one it isn't too hard to pick up
the other.
DirectSound
deals with playing back all the sound effects,
simple as that. As with Direct3D it uses devices
- hardware or software, and sound effects are
stored in buffers (similiar to textures/vertex
buffers/index buffers in 3D) in memory.
The
first step to setting up DirectSound is to attach
the DirectX8 type-library to your project - if
you've used DirectX before then you can skip this
part, otherwise, here's what you need to do:
1.
Open up VB (version 5 or greater required) and
start a "Standard EXE" project.
2. Click on the "Project" menu and select
"References". A window will appear
3. Scroll down until you find and entry "DirectX
8 for visual basic type library"
4. Check the box next to it and click okay.
Done!
Save your project and you're ready to get programming
in DirectX8. If the above didn't go according
to plan there are two solutions. Firstly, check
you have DirectX8 installed on your system (if
you dont know, run DxDiag.exe). Secondly, manually
browse for the file - search for "Dx8VB.dll"
in the \system\ folder.
The
next step is to define the variables and objects
we're going to use. DirectX is an extremely stream-lined
set of COM objects (read up on it in the MSDN
if you dont know what COM is), and hence we'll
be dealing quite a lot with object pointers, references
and constructors/destructors etc... It isn't hard
if you haven't seen it before.
These
are the following variables/objects that you will
need:
'DirectX8: The master controlling object Private DX As DirectX8 'DirectSound8: Looks after all of the sound playback interfaces Private DS As DirectSound8 'DirectSoundSecondaryBuffer8: Stores the actual audio data for playback Private DSBuffer As DirectSoundSecondaryBuffer8 'DirectSoundEnum8: Allows us to get information on available hardware/software devices. Private DSEnum As DirectSoundEnum8
'bLoaded: Status flag insuring that we have actually loaded our sound properly...
Private bLoaded As Boolean
|
|
|
|
The
first step for this program is to list all available
devices. It is quite possible that a given computer
will have more than one device available to DirectSound;
and at the same time we could also find out if
there are any devices at all (it is also possible
that the computer doesn't have any DirectSound
compatable devices). We're going to fill out a
standard Combo-Box with available devices that
the user can select and then call the main initialisation
function.
Private Sub Form_Load()
bLoaded = False
Dim I As Long
' Enumerate Available Audio Devices, it is possible that
' the host computer has multiple sound drivers, or has no
' DirectSound compatable devices
Set DX = New DirectX8
Set DSEnum = DX.GetDSEnum
For I = 1 To DSEnum.GetCount
Debug.Print "DEVICE NUMBER", I, vbCr
Debug.Print "DESC: " & DSEnum.GetDescription(I), vbCr
Debug.Print "GUID: " & DSEnum.GetGuid(I), vbCr
Debug.Print "NAME: " & DSEnum.GetName(I), vbCr
cmbDevices.AddItem DSEnum.GetDescription(I)
Next I
scrlVol.Enabled = False
scrlBalance.Enabled = False
scrlFrq.Enabled = False
cmdPlay.Enabled = False
cmdStop.Enabled = False
cmdPause.Enabled = False
chkLooping.Enabled = False
Set DX = Nothing
cmbDevices.ListIndex = 0
lblmisc(0).Caption = "Not Yet initialised"
DoEvents
End Sub
|
|
|
|
Assuming
the user has now selected a sound device to use
we now need the program to actually setup the
device. This only needs to be done once, and is
relatively simple - in fact, it's almost trivial
really. This is what it looks like:
If bLoaded Then Set DSBuffer = Nothing Set DS = Nothing Set DX = Nothing End If
'//0. Any Variables Required
Dim DSBDesc As DSBUFFERDESC 'describes our sound file
'//1. Create the master DirectX8 class, essential for all
' DirectX programs - must be the first line executed
Set DX = New DirectX8
'//2. Use the enumerated device to create a DirectSound Object,
' This must be done before we do any processing
Set DS = DX.DirectSoundCreate(DSEnum.GetGuid(cmbDevices.ListIndex + 1))
'//3. Set the cooperative level to normal, we must tell DS how
' we want to share audio privelages; Normal will mean we only play
' when we have focus, priority means we get priority over all other
' programs; which is better for a game environment...
DS.SetCooperativeLevel frmMain.hWnd, DSSCL_NORMAL
|
|
|
|
Okay,
the above code may well be trivial, but it requires
a bit of explanation. The first section clears
any object references that already exist - when
you run the code from the download archive you'll
see it's possible to create and recreate DirectSound,
to avoid errors we must terminate any existing
instances of DirectSound before we start initialisation
again. Step 0 isn't too important right now, we'll
find out what a buffer description is in a little
while. Step 1 is important, all DirectX subobjects
(Sound, Graphics, Input etc...) are all derived
from this master object, so we must create the
object and let it perform any initialisation before
we attempt to create any further objects. This
is then demonstrated in step 2; where we create
a DirectSound8 object from the master DirectX8
object, note that we must specify a GUID (Globally
Unique IDentifier), this is a particular
string that identifies a device attached to the
system - as shown in the earlier enumeration code,
we can have multiple devices attached - and this
is how we differentiate between them. Once we've
created the device we do the only required bit
of initialisation - setting the cooperative level.
This is important as it tells DirectSound how
to manage it's resources, and how to interact
with the rest of windows. It is quite possible
that other programs will be running (including
windows) that want to play sound effects, this
line indicates whether they are allowed to or
not, with DDSSCL_NORMAL they will be allowed to
make noises IF our application DOESNT have focus;
but if our application has got focus then all
other applications sound-effects are muted. Alternatively,
DSSCL_PRIORITY will give our application priority
(funny that) over all other applications, effectively
muting all other applications all of the time.
You must specify the windows handle in this call
as well so that DirectSound can identify when
our application has focus or not.
If
any of the COM theory involved (class heirachies
etc...) then I strongly suggest you read the MSDN
archives, or get a good book out of the library;
You dont need a perfect knowledge of COM or COM+,
but DirectX is a COM based system so it's a good
idea to be familiar with the ideas.
Thats
about it really for initialisation. Currently
the program will do nothing useful at all, but
we're getting somewhere at least :-)
3.
Creating And Playing A Buffer
So
we've now initialised DirectSound, that process
is fairly simple really - and you can effectively
copy-and-paste a similiar version of the above
code into almost every application you want.
But
it's all entirely useless if we aren't playing
any sounds! Which is where it starts getting a
little more interesting, but luckily it's still
a fairly trivial process, but it requires a little
more background theory.
Those
of you familiar to either DirectSound7 in particular,
or any other DirectX component will be aware that
data is traditionally stored in buffers. This
is still true with DirectSound8, we create a "DirectSoundSecondaryBuffer8"
object and load in some audio data - and thats
about it. We can also "describe" the
buffer for DirectSound, there are a couple of
important flags that we should specify, but the
others are optional - stereo/mono, 8/16 bit, frequency
(22khz/44khz etc..); if we dont specify these
parameters then DirectSound will use those stored
in the sound file... if we dont specify these
values we can examine the buffer description AFTER
loading and see what parameters DirectSound decided
to use.
In
the sample application we only create one sound
buffer from one file, and it's done when the device
is created. It is perfectly acceptable to create
many buffers at the same time, and you can also
play several sounds at the same time - but remember
there is only a finite amount of resources and
processing time available, so dont store or play
any more sounds than you actually need to.
'//4. Setup and create our sound buffer, we must tell DS what properties/options
' we will be using, and then copy the memory from the drive to memory (simple loading)
' when specifying lFlags values ONLY EVER include those you intend to use, the less of
' these parameters you specify the faster DirectSound can operate...
DSBDesc.lFlags = DSBCAPS_CTRLFREQUENCY Or DSBCAPS_CTRLPAN Or DSBCAPS_CTRLVOLUME
Set DSBuffer = DS.CreateSoundBufferFromFile(App.Path & "\Sample.wav", DSBDesc)
If DSBuffer Is Nothing Then GoTo BailOut:
Debug.Print "SOUND BUFFER CREATED:"
Debug.Print "Buffer Size: " & DSBDesc.lBufferBytes & "bytes (" & _ Round(DSBDesc.lBufferBytes / 1024, 3) & "kb)"
Debug.Print "Buffer Channel Count:" & DSBDesc.fxFormat.nChannels & _ IIf(DSBDesc.fxFormat.nChannels = 1, " (Mono)", " (Stereo)")
Debug.Print "Buffer Bits per channel: " & DSBDesc.fxFormat.nBitsPerSample & " bits"
|
|
|
|
There,
not too complicated really. The only three important
lines are the "DSBDesc.lFlags...", "Set
DSBuffer..." and "If DSBuffer..."
lines. The rest is either comment or simple stats
- run the program in the IDE and look at the immediate
window and you'll see it has outputted the size
of buffer, the number of channels and the number
of bits per channel.
The
main use for pre-specifying the buffer description
is that you can set quality levels, many commercial
games allow you to choose between high and low
quality sound, in most cases it will be simply
switching between mono/stereo and/or 16/8 bit.
The less bits used per channel, the less data
needs to be stored, and processed, likewise with
the number of channels, the less channels the
less data, the less processing.
Now
that we've loaded our sound into memory (unless
an error has occured), we can go about actually
playing it. It is a good idea to turn off any
CD-Audio, Winamp or similiar programs whilst testing
this program - as I've noticed that they can sometimes
cause DirectSound to fail initialisation or fail
loading of buffers. These next three clips of
code play, pause and stop the code - as you can
see, it really is not at all complicated!
Private Sub cmdPlay_Click()
If Not bLoaded Then Exit Sub
If chkLooping.Value = 1 Then
DSBuffer.Play DSBPLAY_LOOPING
Else
DSBuffer.Play DSBPLAY_DEFAULT
End If
End Sub
Private Sub cmdPause_Click()
If Not bLoaded Then Exit Sub
DSBuffer.Stop
End Sub
Private Sub cmdStop_Click()
If Not bLoaded Then Exit Sub
DSBuffer.Stop
DSBuffer.SetCurrentPosition 0
End Sub
|
|
|
|
You'll
notice that both pause and stop use the "DSBuffer.Stop"
method, which is perfectly logical, but for a
proper-stop to occur we must set the current position
back to 0 (the beginning). You'll also notice
the inclusion of the "If Not bLoaded Then
Exit Sub" line, this is required so that
we dont attempt to play/stop a sound that hasn't
been created, bLoaded is toggled true if we successfully
initialise and create our sound, otherwise it's
left as false.
If
you want to (and you probably will) play multiple
sound effects then feel free! DirectSound does
do mixing pretty well, even better if the hardware
can do it. Although bare in mind that the more
sounds playing the slower it goes, and on some
hardware it will start cutting sounds out (not
playing them) if the load is too high, or you're
attempting to use too many channels.
4.
Setting the Properties for the Buffer.
Now
that we've got sounds loaded and we can play them
back to ourselves we want to be able to change
their properties. There are three properties that
we'll be playing with today, panning, volume and
frequency. For those veterans of DirectSound7
(and those new to the whole affair) we'll be playing
with sound effects a bit later on - echo, distortion,
reverb to name a few.
Panning
is first up, and is pretty simple actually. Being
that we're using 2D sound effects, we will only
be panning between left and right speakers; if
you use a mono sound file then you'll just get
more of the sound in one speaker than the other,
if you use a stereo sound file then you tend to
get more of one channel and less of the other
(in one speaker). Play around and see. The defined
range is:
DSBPAN_LEFT = -10,000
DSBPAN_CENTER = 0
DSBPAN_RIGHT = 10,000
and you're free to specify anywhere in between.
The sample application has a slider that you can
use to change this setting. The code is shown
here:
If not bLoaded Then Exit Sub
DSBuffer.SetPan scrlBalance.Value
|
|
|
|
Volume
is next up, and this is a funny one. DirectSound
does not amplify sounds, it attenuates them (makes
them quieter), therefore the maximum volume is
the voume at which the sound was recorded at.
This also means that the defined range is "backwards":
DSBVOLUME_MAX = 0
DSBVOLUME_MIN = -10000
with any value in that range, in my experience
anything below about -3000 tends to be pretty
much silent, but you can experiment to see what
works best for you. The code for changing the
volume is shown here:
If Not bLoaded Then Exit Sub
DSBuffer.SetVolume scrlVol.Value
|
|
|
|
Lastly
we've got frequency. This can be used to generate
"new" versions of existing sounds to
a certain extent, I've seen it used to turn a
simple engine-rumble into the full dynamic range
of a formula 1 car (low speed > high speed).
Alternatively, you can play people voices through
at higher/lower frequencies for a good laugh (try
it if you've never done it before)...
Frequency
is measured internally in hertz (hz), and has
the following range:
DSBFREQUENCY_MIN = 100 (hz)
DSBFREQUENCY_MAX = 100000 (hz) = 100khz
You'll tend to find that most pre-recorded sounds
are either at 11, 22 or 44 khz. The code to set
the frequency is shown here:
If Not bLoaded Then Exit Sub
DSBuffer.SetFrequency scrlFrq.Value
|
|
|
|
Until
we get onto doing the proper special audio effects
this will have to do...
With
the code covered in this article you should be
more than capable of creating a simple sound effects
engine for your game, unless you're requiring
some of the more advanced audio features this
is possibly all you'll need from DirectSound for
sample playback. I strongly suggest that you download
the accompanying source code, either from the
top of this page, or from the downloads
page.
|