ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!
DirectSound!
You know what it would be like to play a game without sound? BORING, that's what! It'd be almost as boring
as programming in C++! Heh. So take heed, Lucky is about to render you DirectSound-literate. Learn well!
Lets jump right in, shall we? As always, we must first tackle the Initialization steps:
Dim ds As DirectSound
Dim dx As New DirectX7
Set ds = dx.DirectSoundCreate("")
ds.SetCooperativeLevel Me.hwnd, DSSCL_PRIORITY
You will have to create your DirectSound and DirectX7 objects and then create a DirectSound instance and assign it
to the DirectSound object. In this case ds is our DirectSound object and we use our DirectX7 object, dx,
to create a DirectSound instance. Once this is done we need to assign the appropriate cooperative level. I always use the
"Priority" level since it gives my application exclusive access to the sound hardware. To assign the cooperative level use the
"SetCooperativeLevel" method of the DirectSound object. For the first argument you must pass the handle to the window for
which you are assigning this cooperative level, for the second argument you have to pass one of the cooperative level constants,
in this case I use "DSSCL_PRIORITY".
Ok, now that we're initialized at the proper cooperative level we have to set up a buffer and the appropriate buffer descriptions:
Dim DSBuffer As DirectSoundBuffer
Dim DSBufferDescription As DSBUFFERDESC
Dim DSFormat As WAVEFORMATEX
DSBuffer will contain our wave data (it can contain other types of data, but waves are good enough for me) and we will
need to set up the DSBUFFERDESC and WAVEFORMATEX objects in order to appropriately fill the buffer with data. Using the DSBUFFERDESC,
DSBufferDescription, we can specify the various capabilities we would like our buffer to have by assigning the buffer
description's ".lFlags". There are four capabilities that I utilize:
DSBCAPS_CTRLFREQUENCY - This will give us the ability to modify a buffer's output frequency DSBCAPS_CTRLPAN - This gives us the ability to alter a sound's pan (ie. Which speaker it is coming out of) DSBCAPS_CTRLVOLUME - Volume, hm... what could this do? DSBCAPS_CTRLPOSITIONNOTIFY - A useful one. This allows us to trigger events at various locations within a sound's playback.
The WAVEFORMATEX object has a number of items we need to assign:
nFormatTag - What data format are we using? WAVES! So pass the constant WAVE_FORMAT_PCM. nChannels - How many channels do you want to use? 1 = mono, 2 = stereo. lSamplesPerSec - How many cycles/second (Hertz) will you use? Standard is 22050. nBitsPerSample - What "bit depth" will you use? Standard is 16. nBlockAlign - Just leave it at: "nBitsPerSample / 8 * nChannels" and you'll be fine :) lAvgBytesPerSec - Set this one to: "lSamplesPerSec * nBlockAlign" and you'll be fine again!
DSBufferDescription.lFlags = DSBCAPS_CTRLPOSITIONNOTIFY Or DSBCAPS_CTRLFREQUENCY Or DSBCAPS_CTRLPAN Or DSBCAPS_CTRLVOLUME
So now we've set up our DSBUFFERDESC and WAVEFORMATEX objects and we can proceed to load the sound buffer, DSBuffer, with
data:
Set DSBuffer = ds.CreateSoundBufferFromFile(App.Path & "\WaveFile.WAV", DSBufferDescription, DSFormat)
Alternatively, if we would like to load the wave from a resource we could do this:
Set DSBuffer = ds.CreateSoundBufferFromResource("", "WaveFile", DSBufferDescription, DSFormat)
Now, since we created this buffer using the DSBCAPS_CTRLPOSITIONNOTIFY flag we MUST set a notification position within it.
If your program does not need to be notified when a sound has finished or reached a certain way-point, then simply omit the
DSBCAPS_CTRLPOSITIONNOTIFY flag and ignore the following code:
Dim DSPosition(0) As DSBPOSITIONNOTIFY
Dim DSNotification as Long
This will take some explaining :) First, we have to define two new variables. DSPosition(0) will be our DSBPOSITIONNOTIFY
array with which we can set the locations within the wave data where we would like events to be triggered. DSNotification
will hold the event handle returned by the CreateEvent method.
Using the dx.CreateEvent method we make an event and store its handle in DSNotification. We have to pass a form object
when we use the CreateEvent method. The form that we pass will be the form within which the event is created (more on this later,
don't get flustered!). Now we store the event handle in DSPosition(0).hEventNotify and assign a "position offset" to
DSPosition(0).lOffset. Here I've used DSBPN_OFFSETSTOP which means that the event will be generated when the wave file
reaches the end. Once the DSPosition object has been filled we can make a call to the DSBuffer.SetNotificationsPosition method.
The first argument we pass is simply the number of positions to which we are assigning events (in this case we're only assigning
one event, to be triggered when the wave has played to the end). The second argument requires a DSBPOSITIONNOTIFY array so we
will pass our DSPosition() object.
NOTE: You MUST create an ARRAY for the DSBPOSITIONNOTIFY variable since the SetNotificationPositions method will only
accept arrays. In our example above our array contains only a single position description, but you could assign multiple events
to multiple positions within a wave file using a larger array.
The FormObject that we passed to the CreateEvent method will need some modifications made to its code as well:
Implements DirectXEvent
Private Sub DirectXEvent_DXCallback(ByVal eventid As Long)
End Sub
You must add the "Implements DirectXEvent" to the header of the FormObject's code. This will allow you to place a new
event in the form, DirectXEvent_DXCallback. This event will be triggered whenever one of your wave files reaches an offset
value for which you have assigned an event. When DirectXEvent_DXCallback is triggered, the eventid variable created
will contain the event handle used when the event was created. Use this to determine WHICH of your wave files triggered this
event and then take appropriate action.
An example will help to clarify: In the code above we created an event for the DSBuffer object and it was assigned an
event handle that we stored in DSNotification. When we play our DSBuffer and the end of the wave sound is
reached the DirectXEvent_DXCallback subroutine will be triggered within the form FormObject. The eventid generated
by the DirectXEvent_DXCallback will be identical to the event handle we've stored in DSNotification. Get it?
PHEW! All that just to load a sound file into a sound buffer! ONWARD!
Now you're probably anxious to actually PLAY the sound so you can test if things are working correctly. Well, it gets easier from
here on in, don't worry :)
DSBuffer.Play DSBPLAY_DEFAULT
That's all there is to it! You just use the DSBuffer.Play method and pass the DSBPLAY_DEFAULT constant to play the sound. If
you want your sound to loop:
DSBuffer.Play DSBPLAY_LOOPING
That's it! Stopping the sound during playback is also a simple matter:
DSBuffer.Stop
Well, actually, this is more like PAUSING since you haven't reset the position of the playback, all you've done is stop the
audio from continuing. To reset the position to zero, just use the SetCurrentPosition method:
DSBuffer.SetCurrentPosition 0
So, now you know how to load, play, pause, and stop your sound buffer. There are a few fun things left to learn, however.
Remember when we set the DSBUFFERDESC object back at the start? We included the ability to alter the frequency, pan, and volume
of our buffer. Since we included those flags we can use the following methods:
The GetFrequency method returns the frequency, in Hertz, of the wave currently stored in the buffer. Using the SetFrequency method
we can alter the frequency within the limits set out by the MaxFrequency and MinFrequency constants. The GetPan
method returns the current Pan value of the buffer. Center (ie. Audio split equally between left and right speakers) is at zero,
fully left is -10000, and fully right is 10000. Using SetPan we can alter the pan of the buffer. GetVolume will return the current
volume state of the buffer. Max volume is at zero, min volume is at -10000. Be wary, volume is measured in decibels which utilize
a logarithmic scale. Decreasing the volume slightly using the SetVolume method will have sharply defined effects on the output of
your wave file.
That's pretty much all there is to know about the basics of DirectSound. Don't forget to clean up your objects when you're done: