Step 6: Stream Data from the Wave File

The purpose of the notifications you set up and handled in the previous two steps is to alert you whenever half the data in the buffer has been played. As soon as the current play position passes the beginning or halfway point of the buffer, you write data into the segment of the buffer that has just been played. (When the buffer starts, you immediately receive a notification and write to the second half of the buffer.) Because the buffer holds 2 seconds' worth of data, your data write begins almost 1 second ahead of the current play position, which should allow ample time for the process to be completed before the play position reaches the new data.

In the WinMain function, whenever an event was signaled you passed the index of that event to the StreamToBuffer function. This index corresponds to the index of the notification position in the DSBPOSITIONNOTIFY array.

Here is the first part of the StreamToBuffer function:

BOOL StreamToBuffer(DWORD dwPos)

{

LONG lNumToWrite;

DWORD dwStartOfs;

VOID *lpvPtr1, *lpvPtr2;

DWORD dwBytes1, dwBytes2;

UINT cbBytesRead;

static DWORD dwStopNextTime = 0xFFFF;

if (dwStopNextTime == dwPos) // All data has been played

{

lpdsb->Stop();

dwStopNextTime = 0xFFFF;

return TRUE;

}

if (dwStopNextTime != 0xFFFF) // No more to stream, but keep

// playing to end of data

return TRUE;

The dwStopNextTime variable is a flag to indicate that the end of the file has been reached; this is set later in the function. If it is non-zero, there is no more data to be streamed. If the value of dwStopNextTime equals the index of the notification being handled, then you know that the current play position has returned to where it was when the end of the file was reached; that is, the last segment of data you copied to the buffer has been played. In this case, it's time to stop the buffer so it doesn't keep playing old data.

The next part of the StreamToBuffer function determines the offset within the buffer where you will start copying the new data. Although the buffer in this tutorial has only two notification positions, the code is designed to work with any number. Remember, dwPos is the index of the notification position that has just been passed by the current play position. You are going to write to the segment of the buffer that starts at the previous notification position.

if (dwPos == 0)

dwStartOfs = rgdsbpn[NUMEVENTS - 1].dwOffset;

else

dwStartOfs = rgdsbpn[dwPos-1].dwOffset;

Now you determine the size of this segment of the buffer:

lNumToWrite = (LONG) rgdsbpn[dwPos].dwOffset - dwStartOfs;

if (lNumToWrite < 0) lNumToWrite += dsbdesc.dwBufferBytes;

You now have all the information you need to lock the buffer in preparation for the data write.

IDirectSoundBuffer_Lock(lpdsb,

dwStartOfs, // Offset of lock start

lNumToWrite, // Number of bytes to lock

&lpvPtr1, // Address of lock start

&dwBytes1, // Count of bytes locked

&lpvPtr2, // Address of wraparound

&dwBytes2, // Count of wraparound bytes

0); // Flags

In this example the lock will never wrap around, so you need to do only a single data copy from the file. You get the data by calling the WaveReadFile function in Wave.c:

WaveReadFile(hmmio, // File handle

dwBytes1, // Number of bytes to get

(BYTE *) lpvPtr1, // Destination

&mmckinfoData, // File chunk info

&cbBytesRead); // Actual bytes read

Now determine if the end of the file has been reached. If it has, close it, write silence to the rest of this segment (because it will play all the way through before the buffer is stopped), and set the dwStopNextTime flag.

if (cbBytesRead < dwBytes1) // Reached end of file

{

WaveCloseReadFile(&hmmio, &pwfx);

FillMemory((PBYTE)lpvPtr1 + cbBytesRead,

dwBytes1 - cbBytesRead,

(dsbdesc.lpwfxFormat->wBitsPerSample==8) ? 128 : 0);

dwStopNextTime = dwPos;

}

Finally, unlock the buffer and return to the message loop:

IDirectSoundBuffer_Unlock(lpdsb,

lpvPtr1, dwBytes1, lpvPtr2, dwBytes2);

return TRUE;

} // end StreamToBuffer()