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

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...
Set DSBuffer = DS.CreateSoundBufferFromFile(App.Path & "\Sample.wav", DSBDesc)
If DSBuffer Is Nothing Then GoTo BailOut:
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
End If
End Sub
Private Sub cmdPause_Click()
If Not bLoaded Then Exit Sub
End Sub
Private Sub cmdStop_Click()
If Not bLoaded Then Exit Sub
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
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":
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_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.

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