Unless the audio data is small enough to be contained in a single data block, applications must continually supply the device driver with data blocks until playback or recording is complete. This is true for waveform input and output, and for MIDI system-exclusive input and output. Regular MIDI channel messages don't require data blocks for input or output.
Even if a single data block is used, applications must be able to determine when a device driver is finished with the data block so the application can free the memory associated with the data block and header structure. There are three ways to determine when a device driver is finished with a data block:
By specifying a window to receive a message sent by the driver when it is finished with a data block.
By specifying a callback function to receive a message sent by the driver when it is finished with a data block.
By polling a bit in the dwFlags field of the WAVEHDR or MIDIHDR data structure sent with each data block.
If an application doesn't get a data block to the device driver when needed, there can be an audible gap in playback or a loss of incoming recorded information. This requires at least a double-buffering scheme—staying at least one data block ahead of the device driver.
Note:
To get time stamped MIDI input data, you must use a callback function.
The easiest type of callback to use to process driver messages is a window callback. To use a window callback, specify the CALLBACK_WINDOW flag in the dwFlags parameter and a window handle in the low-order word of the dwCallback parameter of the . . . Open function. Driver messages will be sent to the window-procedure function for the window identified by the handle in dwCallback.
Messages sent to the window function are specific to the audio device type used. For details on these messages, see the sections later in this chapter on using window messages for each specific audio device type.
You can also write your own low-level callback function to process messages sent by the device driver. To use a low-level callback function, specify the CALLBACK_FUNCTION flag in the dwFlags parameter and the address of the callback in the dwCallback parameter of the . . . Open function.
Messages sent to a callback function are similar to messages sent to a window, except they have two DWORD parameters instead of a WORD and a DWORD parameter. For details on these messages, see the sections on using low-level callbacks for each specific audio device type.
Summary: Writing Low-Level Callback Functions
Callback functions for the low-level audio services are accessed at interrupt time, and therefore must be carefully written to adhere to the following set of rules:
The callback function must reside in a dynamic-link library (DLL) and be exported in the module-definition file for the DLL.
The code and data segments for the callback functions must be specified as FIXED in the module-definition file for the DLL.
Any data the callback function accesses must be handled in one of the following ways:
Declared in the FIXED data segment of the callback DLL.
Allocated with GlobalAlloc using the GMEM_MOVEABLE and GMEM_SHARE flags, and locked using GlobalLock and GlobalPageLock.
Allocated with LocalAlloc from a FIXED local heap.
The callback cannot make any Windows or Multimedia extensions calls except PostMessage, timeGetTime, timeGetSystemTime, timeSetEvent, timeKillEvent, midiOutShortMsg, midiOutLongMsg, and OutputDebugStr.
Note:
Since low-level callback functions must reside in a DLL, you don't need to use MakeProcInstance to get a procedure instance address for the callback.
Summary: Passing Instance Data to Callbacks
To pass instance data from an application to a low-level callback residing in a DLL, use one of the following techniques:
Pass the instance data using the dwInstance parameter of the function that opens the device driver.
Pass the instance data using the dwUser field of the WAVEHDR and MIDIHDR data structures that identify an audio data block being sent to a device driver.
If you need more than 32 bits of instance data, pass a pointer to a data structure containing the additional information. Be sure to follow the memory-allocation guidelines listed in “Using a Callback Function to Process Driver Messages,” earlier in this chapter.
In addition to using a callback, you can poll the dwFlags field of a WAVEHDR or MIDIHDR structure to determine when an audio device is finished with a data block. There are times when it's better to poll dwFlags rather than waiting for a window to receive messages from the drivers. For example, after you call waveOutReset to release pending data blocks, you can immediately poll to be sure that the data blocks are indeed done before proceeding to call waveOutUnprepareHeader and free the memory for the data block.
MMSYSTEM.H defines two flags for testing the dwFlags field: WHDR_DONE for a WAVEHDR structure, and MHDR_DONE for a MIDIHDR structure. For example, to test a MIDIHDR structure, use the following technique:
if(lpMidiHdr->dwFlags & MHDR_DONE)
/* Driver is finished with the data block */
else
/* Driver is not finished with the data block */