Playing Sounds

Playing a sound consists of the following steps:

  1. Lock a portion of the secondary buffer (IDirectSoundBuffer::Lock). This method returns a pointer to the address where writing will begin, based on the offset from the beginning of the buffer that you pass in.
  2. Write the audio data to the buffer.
  3. Unlock the buffer (IDirectSoundBuffer::Unlock).
  4. Send the sound to the primary buffer and from there to the output device (IDirectSoundBuffer::Play).

Because streaming sound buffers usually play continually and are conceptually circular, DirectSound returns two write pointers when locking a sound buffer. For example, if you tried to lock 300 bytes beginning at the midpoint of a 400-byte buffer, the Lock method would return one pointer to the last 200 bytes of the buffer, and a second pointer to the first 100 bytes. The second pointer is NULL if the locked portion of the buffer does not wrap around.

Normally the buffer stops playing automatically when the end is reached. However, if the DSBPLAY_LOOPING flag was set in the dwFlags parameter to the Play method, the buffer will play repeatedly until the application calls the IDirectSoundBuffer::Stop method, at which point the play cursor is moved to the beginning of the buffer.

For streaming sound buffers, your application is responsible for ensuring that the next block of data is written to the buffer before the current play position loops back to the beginning. (For more on the play position, see Current Play and Write Positions.) You can do this by setting notification positions so that an event is signaled whenever the current play position reaches a certain point. Applications should write at least 1 second ahead of the current play position to minimize the possibility of gaps in the audio output during playback.

The following C example writes data to a sound buffer, starting at the offset into the buffer passed in dwOffset:

BOOL AppWriteDataToBuffer( 
    LPDIRECTSOUNDBUFFER lpDsb,  // the DirectSound buffer
    DWORD dwOffset,             // our own write cursor
    LPBYTE lpbSoundData,        // start of our data
    DWORD dwSoundBytes)         // size of block to copy
{ 
    LPVOID lpvPtr1; 
    DWORD dwBytes1; 
    LPVOID lpvPtr2; 
    DWORD dwBytes2; 
    HRESULT hr; 
    // Obtain memory address of write block. This will be in two parts
    // if the block wraps around.
    hr = lpDsb->lpVtbl->Lock(lpDsb, dwOffset, dwSoundBytes, &lpvPtr1, 
        &dwBytes1, &lpvPtr2, &dwBytes2, 0); 
 
    // If DSERR_BUFFERLOST is returned, restore and retry lock. 
    if(DSERR_BUFFERLOST == hr) { 
        lpDsb->lpVtbl->Restore(lpDsb); 
        hr = lpDsb->lpVtbl->Lock(lpDsb, dwOffset, dwSoundBytes, 
            &lpvPtr1, &dwAudio1, &lpvPtr2, &dwAudio2, 0); 
    } 
    if(DS_OK == hr) { 
        // Write to pointers. 
        CopyMemory(lpvPtr1, lpbSoundData, dwBytes1); 
        if(NULL != lpvPtr2) { 
            CopyMemory(lpvPtr2, lpbSoundData+dwBytes1, dwBytes2); 
        } 
        // Release the data back to DirectSound. 
        hr = lpDsb->lpVtbl->Unlock(lpDsb, lpvPtr1, dwBytes1, lpvPtr2, 
            dwBytes2); 
        if(DS_OK == hr) { 
            // Success. 
            return TRUE; 
        } 
    } 
    // Lock, Unlock, or Restore failed. 
    return FALSE; 
}