Tricks of the Windows Game Programming Gurus:Sounding Off with DirectSound and DirectMusic

All ITKnowledge Source Code Search Tips Advanced Search |

| Tricks of the Windows Game Programming Gurus by Andre LaMothe Sams, Macmillan Computer Publishing ISBN: 0672313618 Pub Date: 09/27/99

Search this book:

Previous | Table of Contents | Next

Now let’s move on to the `WAVEFORMATEX` structure. It contains a description of the sound that you want the buffer to represent (it’s a standard Win32 structure also). Parameters like playback rate, number of channels (1-mono or 2-stereo), bits per sample, and so forth are recorded in this structure. Here it is for your review:

    typedef struct

    {

    WORD  wFormatTag;      // always WAVE_FORMAT_PCM

    WORD  nChannels;       // number of audio channels 1 or 2

    DWORD nSamplesPerSec;  // samples per second

    DWORD nAvgBytesPerSec; // average data rate

    WORD  nBlockAlign;     // nchannels * bytespersmaple

    WORD  wBitsPerSample;  // bits per sample

    WORD  cbSize;          // advanced, set to 0

    } WAVEFORMATEX;

Simple enough. Basically, `WAVEFORMATEX` contains the description of the sound. In addition, you need to set up one of these as part of `DSBUFFERDESC`. Let’s see how to do that, beginning with the prototype of the `CreateSoundBuffer()` function:

    HRESULT CreateSoundBuffer(

     LPCDSBUFFERDESC lpcDSBuffDesc,   // ptr to DSBUFFERDESC

     LPLPDIRECTSOUNDBUFFER lplpDSBuff,// ptr to sound buffer

     IUnknown FAR *pUnkOuter);        // always NULL

And here’s an example of creating a secondary DirectSound buffer at 11KHz mono 8-bit with enough storage for two seconds:

    // ptr to directsound

    LPDIRECTSOUNDBUFFER lpdsbuffer; buffer

    DSBUFFERDESC dsbd;   // directsound buffer description

    WAVEFORMATEX  pcmwf; // holds the format description

    // set up the format data structure

    memset(&pcmwf, 0, sizeof(WAVEFORMATEX));

    pcmwf.wFormatTag     = WAVE_FORMAT_PCM; // always need this

    pcmwf.nChannels      = 1; // MONO, so channels = 1

    pcmwf.nSamplesPerSec = 11025; // sample rate 11khz

    pcmwf.nBlockAlign    = 1; // see below

    // set to the total data per

    // block, in our case 1 channel times 1 byte per sample

    // so 1 byte total, if it was stereo then it would be

    // 2 and if stereo and 16 bit then it would be 4

    pcmwf.nAvgBytesPerSec =

                     pcmwf.nSamplesPerSec * pcmwf.nBlockAlign;

    pcmwf.wBitsPerSample = 8; // 8 bits per sample

    pcmwf.cbSize         = 0; // always 0

    // set up the directsound buffer description

    memset(dsbd,0,sizeof(DSBUFFERDESC));

    dsbd.dwSize = sizeof(DSBUFFERDESC);

    dsbd.dwFlags= DSBCAPS_CTRLDEFAULT | DSBCAPS_STATIC |

                  DSBCAPS_LOCSOFTWARE ;

    dsbd.dwBufferBytes    = 22050; // enough for 2 seconds at

                                 // a sample rate of 11025

    dsbd.lpwfxFormat    = &pcmwf; // the WAVEFORMATEX struct

    // create the buffer

    if (FAILED(lpds->CreateSoundBuffer(&dsbd,&lpdsbuffer,NULL)))

       { /* error */ }

If the function call is successful, a new sound buffer is created and passed in `lpdsbuffer`, which is ready to be played. The only problem is that there isn’t anything in it! You must fill the sound buffer with data yourself. You can do this by reading in a sound file data stored in `.VOC`, `.WAV`, `.AU`, or whatever, and then parse the data and fill up the buffer. Or you could generate algorithmic data and write into the buffer yourself for a test. Let’s see how to write the data into the buffer, and later I’ll show you how to read sound files from disk.

#### Writing Data to Secondary Buffers

As I said, secondary sound buffers are circular in nature, and hence are a little more complex to write to than a standard linear array of data. For example, with DirectDraw surfaces, you just locked the surface memory and wrote to it. (This is only possible because there is a driver living down there that turns nonlinear memory to linear.) DirectSound works in a similar fashion: You lock it, but instead of getting one pointer back, you get two! Therefore, you must write some of your data to the first pointer and the rest to the second. Take a look at the prototype for `Lock()` to understand what I mean:

    HRESULT Lock(

      DWORD dwWriteCursor,    // position of write cursor

      DWORD dwWriteBytes,     // size you want to lock

      LPVOID lplpvAudioPtr1,  // ret ptr to first chunk

      LPDWORD lpdwAudioBytes1,// num bytes in first chunk

      LPVOID lplpvAudioPtr2,  // ret ptr to second chunk

      LPDWORD lpdwAudioBytes2,// num of bytes in second chunk

      DWORD dwFlags);         // how to lock it

If you set `dwFlags` to `DSBLOCK_FROMWRITECURSOR`, the buffer will be locked from the current write cursor of the buffer. If you set `dwFlags` to `DSBLOCK_ENTIREBUFFER`, the entire buffer will be locked. This is the way to go. Keep it simple.

For example, say you create a sound buffer that has enough storage for 1,000 bytes. When you lock the buffer for writing, you’ll get two pointers back along with the length of each memory segment to write to. The first chunk might be 900 bytes long, and the second might be 100 bytes long. The point is that you have to write your first 900 bytes to the first memory region and the second 100 bytes to the second memory region. Take a look at Figure 10.11 to clarify this.

Figure 10.11 Locking a sound buffer.

And here’s an example of locking the 1,000-byte sound buffer:

    UCHAR *audio_ptr_1,  // used to retrieve buffer memory

          *audio_ptr_2;

    int audio_length_1, // length of each buffer section

        audio_length_2;

    // lock the buffer

    if (FAILED(lpdsbuffer->Lock(0,1000,

        (void **)&audio_ptr_1, &audio_length_1,

        (void **)&audio_ptr_2, &audio_length_2,

         DSBLOCK_ENTIREBUFFER)))

       { /* error / }

Once you’ve locked the buffer, you’re free to write into the memory. The data can be from a file or can be generated algorithmically. When you’re done with the sound buffer, you must unlock it with `Unlock()`. `Unlock()` takes both pointers and both lengths, like this:

    if (FAILED(lpdsbuffer->Unlock(audio_ptr_1,audio_length_1,

                               audio_ptr_2,audio_length_2)))

       { /* problem unlocking */}

And as usual, when you’re done with the sound buffer, you must destroy it with `Release()`, like this:

    lpdsbuffer->Release();

However, don’t destroy the sound until you don’t need it anymore. Otherwise you’ll have to load it again.

Now let’s see how to play sounds with DirectSound.

### Rendering Sounds

Once you’ve created all your sound buffers and loaded them with sounds, you’re ready to rock. (Of course, you’re allowed to create and destroy sounds on-the-fly if you want.) DirectSound has a number of control functions to play sounds and alter their parameters as they play. You can change the volume, frequency, stereo panning, and so forth.

Previous | Table of Contents | Next

Copyright © Macmillan Computer Publishing, Inc.

Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.