DirectX:DirectSound:Tutorials:VB:DX7:Introduction

From GPWiki
Jump to: navigation, search
40px-sprout.png   This article is a stub. You can help out by expanding it.

You know what it would be like to play a game without sound? BORING, that's what! So take heed, I'm about to render you DirectSound-literate.

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
 
    With DSFormat
        .nFormatTag = WAVE_FORMAT_PCM
        .nChannels = 2
        .lSamplesPerSec = 22050
        .nBitsPerSample = 16
        .nBlockAlign = .nBitsPerSample / 8 * .nChannels
        .lAvgBytesPerSec = .lSamplesPerSec * .nBlockAlign
    End With

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
 
    DSNotification = dx.CreateEvent(FormObject)
    DSPosition(0).hEventNotify = DSNotification
    DSPosition(0).lOffset = DSBPN_OFFSETSTOP
    DSBuffer.SetNotificationPositions 1, DSPosition()

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

Const LeftPan = -10000
Const RightPan = 10000
Const CenterPan = 0
Const MaxVolume = 0
Const MinVolume = -10000
Const MaxFrequency = 100000
Const MinFrequency = 100
 
Dim Freq as Long
Dim Pan as Long
Dim Vol as Long
 
    Freq = DSBuffer.GetFrequency()
    DSBuffer.SetFrequency Freq - 1
 
    Pan = DSBuffer.GetPan()
    DSBuffer.SetPan Pan - 1
 
    Vol = DSBuffer.GetVolume()
    DSBuffer.SetVolume = Vol - 1

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 is 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:

    Set DSBuffer = Nothing

You can have a look at this DirectSound sample source code to see these principles in action.