Amazon Honor System Click Here to Pay Learn More

by Forest J. Handford

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:

< 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

Amazon Honor System Click Here to Pay Learn More