In this chapter we will add DirectSound to Space Adventure.
Just like DirectDraw, DirectSound uses buffers. Unlike DirectDraw, DirectSound uses circular buffers. You can think of these buffers like a record, or CD if you don't remember records. But unlike records and CDs there is only one track and unless you change what is on the track it will repeat itself. On the circular buffer there is a read position. The read position is like the laser eye on a CD player or the needle of a record player. You don't want to write your data onto the read position because it will ruin the sound. A circular buffer also has a write position that is a little after the read position and at the same time a little before the read position. Below is an example of a circular buffer:
In DirectSound there are two types of circular buffers. First there is the static buffer, which we will use in our sample. Second there is a streaming buffer. A static buffer is used for a short and often repeated sound. In our game we will use a static buffer for all of our sounds because they are short and all but one of them can be often repeated. A streaming buffer is for longer sounds, like background music. Since I don't have anything for background music, I did not implement a streaming buffer into Space Adventure. I did add some code for streaming buffers that you can look at and use.
The first thing we need to do in our game is initialization. We have to initialize DirectSound and choose the device. A DirectSound device is any combination of a sound card and speaker. On my computer their are three devices. Every computer with DirectSound has a primary device. The primary device is the default device that the user has decided to have in his or her system by configuring it in the control panel. Usually the primary device will be the best device, why would the user set-up a cheesy device? Well, the user may not know what (s)he is doing. In our example we will use the primary device for simplicities sake.
To find out what devices
are available we can call the following function:
BOOL DirectSoundEnumarate(LPDSENUMCALLBACK
lpDSEnumCallback, // Pointer to our callback
// function
LPVOID lpContext); // A 32 bit value, can be used for
return data
You can
right a callback function to do what-ever you want with the data
but it must accept the correct number and type of variables so
that it's prototype would look like this:
bool
CALLBACK DSEnumCallBack(LPGUID lpGuid, //
Pointer to the user interface
LPCSTR lpcstrDescription, //The name of the device like
"Sound Blaster 16"
LPCSTR lpcstrModule, // Module name
LPVOID lpContext); // A 32 bit value, can be used for return
data
To use the above
callback function use the following code:
HRESULT hr = DirectSoundEnumerate(
(LPDSENUMCALLBACK)EnumCallBack, hwnd);
DirectSoundEnumerate
will then call your callback function for each device.
After your code decides which of the devices is the best for your
evil purposes, you can create the DirectSound Object by using the
following:
HRESULT DirectSoundCreate(LPGUID lpGuid, //
Pointer to the GUID of the device, or
// NULL for the default device
LPDIRECTSOUND * ppDS, // Address of the pointer
to be initialized
// with the call
LPUNKNOWN pUnkOuter); // NULL
After creating the
object you can set the cooperative level. As with
DirectSound this is how much control you want or need over the
device, if your greedy your user may not be happy. Now look
at the SetCooperativeLevel function:
HRESULT SetCooperativeLevel(HWND hwnd,
//The application window
DWORD dwLevel); //Cooperative level flag
Here is a table of the
cooperative levels along with their pros and cons:
| Level | Pros | Cons |
| DSSCL_NORMAL | Best cooperation with other applications. | The primary format must be DirectSound's default output format. |
| DSSCL_PRIORITY | This lets you change the primary format. | You might change the output format of another application! |
| DSSCL_EXCLUSIVE | Grants exclusive use of the device and mutes background applications. | The user may not want the background applications muted. |
| DSSCL_WRITEPRIMARY | Allows access to the primary buffer for mixing. | It requires a DirectSound driver. No secondary buffers can be played. The other applications lose their buffers! |
To create a sound buffer use the following function:
HRESULT CreateSoundBuffer(LPCDSBUFFERDESC
lpDSBufferDesc, // Pointer to the buffer's
// description
LPLPDIRECTSOUNDBUFFER lplpDirectSoundBuffer, //
A pointer to the
// interface
IUnknown FAR * pUnkOter); // NULL
You'll want to lock your
buffer after you fill it. Locking it will prevent another
application from overwriting it. After your done with it
you should unlock it. If you leave it locked when
your done with it the user will have one less buffer until your
application is closed. The unlock function must be passed
the same values as the lock function. Here is the lock and
unlock functions:
HRESULT Lock(DWORD dwWriteCursor, // Offset
of lock start
DWORD dwWriteBytes, // Size of lock, if zero it
is ignored
LPVOID lplpvAudioPtr1, // Address of lock start
LPDWORD lpdwAudioBytes1, // Number of bytes to
lock or NULL
LPVOID lplpvAudioPtr2, // Address of the
wrap-around start or 0
LPDWORD lpdwAudioBytes2, // Number of bytes in
wrap-around or 0
DWORD dwFlags); // DSBLOCK_FROMWRITECURSOR or
DSBLOCK_ENTIREBLOCK
HRESULT
Unlock(LPVOID lpvAudioPtr1, // Address of lock
start
DWORD dwAudioBytes1, // Number of bytes locked
or NULL
LPVOID lpvAudioPtr2, // Address of the
wrap-around start or 0
DWORD dwAudioBytes2); // Number of bytes in
wrap-around or 0
Although wave files are
memory hogs they are well documented and easy to use. In a
wave file their is header section. The header section is
filled with information about the wave like the wave's
format. In C++ their is a structure called WAVEFORMATEX
that can hold the format information. After the header, the
file is in a streaming format. By streaming format I mean
that the start of the music is in the beginning, the middle is in
the middle, and the end is at the end. In Space Adventure
we will use the following code to create a buffer and read a
file:
bool LoadStatic( LPDIRECTSOUND lpds, //Pointer to Direct
Sound
LPSTR lpzFileName ) //File name of
the sound
{
WAVEFORMATEX
*pwfx;
// Wave format info.
HMMIO
hmmio;
// File handle.
MMCKINFO
mmckinfo; //
Chunk info.
MMCKINFO
mmckinfoParent; // Parent chunk info.
//Open the
file
if ( WaveOpenFile( lpzFileName,
&hmmio, &pwfx, &mmckinfoParent ) != 0 )
return FALSE;
//Read the file
if ( WaveStartDataRead( &hmmio,
&mmckinfo, &mmckinfoParent ) != 0 )
return FALSE;
// Create buffer if it doesn't yet exist.
DSBUFFERDESC
dsbdesc; //buffer description
// If the buffer already exists, we're just reloading the
// wave file after a call to Restore().
//get buffer
info
memset( &dsbdesc, 0, sizeof(
DSBUFFERDESC ) );
dsbdesc.dwSize = sizeof( DSBUFFERDESC );
dsbdesc.dwFlags = DSBCAPS_STATIC;
dsbdesc.dwBufferBytes = mmckinfo.cksize;
dsbdesc.lpwfxFormat = pwfx;
//create the
buffer
if ( FAILED( lpds->CreateSoundBuffer(
&dsbdesc, &lpdsbStatic, NULL ) ) )
{
//we failed so close the file and leave
WaveCloseReadFile(
&hmmio, &pwfx );
return FALSE;
}
LPVOID lpvAudio1;
DWORD dwBytes1;
//Lock the
buffer
if ( FAILED( lpdsbStatic->Lock(
0,
// Offset of lock start.
0,
// Size of lock; ignored in this case.
&lpvAudio1, // Address of lock start.
&dwBytes1, // Number of bytes
locked.
NULL,
// Address of wraparound start.
NULL,
// Number of wraparound bytes.
DSBLOCK_ENTIREBUFFER ) ) ) // Flag.
{
// Error handling.
WaveCloseReadFile(
&hmmio, &pwfx );
return FALSE;
}
UINT cbBytesRead; //Number of bytes read
if ( WaveReadFile(
hmmio, // File handle.
dwBytes1,
// Number of bytes to read.
( BYTE * )lpvAudio1, // Destination.
&mmckinfo,
// File chunk info.
&cbBytesRead ) ) // Actual
number of bytes read.
{
// Handle failure
on nonzero return.
WaveCloseReadFile(
&hmmio, &pwfx );
return FALSE;
}
//unlock the
buffer
lpdsbStatic->Unlock( lpvAudio1,
dwBytes1, NULL, 0 );
//close the
file
WaveCloseReadFile( &hmmio, &pwfx
);
return TRUE;
} // LoadStatic
The wave functions are derived from functions Microsoft created for programmer's like us to utilize wave files. They are not standard library functions. You can get a copy of them from the sample code.
After loading the data
with the above function we can play the music with the below
customizable function:
void PlayStatic( int wavenumber ) //This number identifies
the sound
{
HRESULT hr; //The return result
//If there is
no buffer we can't play anything so leave
if ( lpdsbStatic == NULL ) return;
//If the
buffer is already playing we MUST set the play position
lpdsbStatic->SetCurrentPosition( 0 );
hr = lpdsbStatic->Play( 0, 0, 0
); // OK if redundant.
//Did we lose the buffer to another, more devious app?
if ( hr == DSERR_BUFFERLOST )
{
//restore the buffer
if ( SUCCEEDED(
lpdsbStatic->Restore() ) )
{
//Play the correct sound
if(wavenumber == 1)
{
if ( LoadStatic( lpds, SHOTWAVE ) )
lpdsbStatic->Play( 0, 0, 0 );
}
else if(wavenumber == 2)
{
if ( LoadStatic( lpds, HITWAVE ) )
lpdsbStatic->Play( 0, 0, 0 );
}
else
{
if ( LoadStatic( lpds, WELCOMEWAVE ) )
lpdsbStatic->Play( 0, 0, 0 );
}
}
}
}
Notice that if the buffer is lost we have to reload it. To
identify which wave to use if it needs to be reloaded we will use
an integer. Instead of hard coding the waves name by typing
"shotwave.wav" we will use SHOTWAVE which is defined as
followed by the pre-proccessor:
#define SHOTWAVE "shot.wav"
This is only for
housekeeping purposes. Now say I wanted to play the shot
wave and DirectSound isn't initialized yet I would then do the
following:
// Initialize DirectSound
InitDSound( g_hwnd, hInstance, pguid);
// Load our
shot sound
LoadStatic( lpds, SHOTWAVE);
// Play our
shot sound
PlayStatic(1);
That is
all we need to get us to this version of Space Adventure:
< Space Adventure Files >
That's it for this chapter. In the next chapter we will add TCP/IP support by using DirectPlay.
PREVIOUS CHAPTER HOME NEXT CHAPTER
new roman"> //If there is no buffer we can't
play anything so leave
if ( lpdsbStatic == NULL ) return;
//If the
buffer is already playing we MUST set the play position
lpdsbStatic->SetCurrentPosition( 0 );
hr = lpdsbStatic->Play( 0, 0, 0
); // OK if redundant.
//Did we lose the buffer to another, more devious app?
if ( hr == DSERR_BUFFERLOST )
{
//restore the buffer
if ( SUCCEEDED(
lpdsbStatic->Restore() ) )
{
//Play the correct sound
if(wavenumber == 1)
{
if ( LoadStatic( lpds, SHOTWAVE ) )
lpdsbStatic->Play( 0, 0, 0 );
}
else if(wavenumber == 2)
{
if ( LoadStatic( lpds, HITWAVE ) )
lpdsbStatic->Play( 0, 0, 0 );
}
else
{
if ( LoadStatic( lpds, WELCOMEWAVE ) )
lpdsbStatic->Play( 0, 0, 0 );
}
}
}
}
Notice that if the buffer is lost we have to reload it. To
identify which wave to use if it needs to be reloaded we will use
an integer. Instead of hard coding the waves name by typing
"shotwave.wav" we will use SHOTWAVE which is defined as
followed by the pre-proccessor:
#define SHOTWAVE "shot.wav"
This is only for
housekeeping purposes. Now say I wanted to play the shot
wave and DirectSound isn't initialized yet I would then do the
following:
// Initialize DirectSound
InitDSound( g_hwnd, hInstance, pguid);
// Load our
shot sound
LoadStatic( lpds, SHOTWAVE);
// Play our
shot sound
PlayStatic(1);
That is all we need to get us to this version of Space Adventure:
That's it for this chapter. In the next chapter we will add TCP/IP support by using DirectPlay.
PREVIOUS CHAPTER HOME NEXT CHAPTER