Introduction
This article focuses on giving an example of using the DirectX API in PC game software development by
using a DirectSound wrapper class.
The DirectSound wrapper class
The wrapper class has the functionality to handle various aspects of sound
playback.
The structure of the wrapper class
The DirectSound object uses double buffers to manipulate the sound data processing
and playback.
The DirectSound object creates a single primary buffer to deal with output devices to play
the sound data. The DirectSound object creates multiple second buffers to load, store various
sound data, and configure sound data performance. The object loads and mixes
sound data from the secondary buffers and sends the processed sound data to the primary buffer to
play it through the output device.
The wrapper class contains a pointer to the DirectSound object, a primary buffer object
(a pointer of type DirectSoundBuffer), and the secondary buffer array (DirectSoundBuffer
object array)
DirectSound object in the wrapper class
DirectSound is a COM interface, so the DirectSound object is the COM component
object (a pointer to DirectSound). The DirectSound object is the core of
sound playback and all of buffers, both primary buffer and secondary buffers are created by it,
so it must be instantiated before doing any sound play back.
Because the DirectSound object is a COM object, it can be instantiated by either the
generic way of creation COM components, or by calling DirectSoundCreate.
In my wrapper class, I find the generic method of creating a COM object is more reliable.
Remember: The DirectX interfaces are a subset of the COM interfaces, so before
you use them, call CoInitialize/
CoInitializeEx, and call CoUninitialize at the end of your
application.
BOOL CSoundManager::CoInitializeDSound(void)
{
HRESULT hr;
GUID guID;
::memset(&guID, 0, sizeof(GUID));
hr = ::CoInitialize(NULL);
.................................
.................................
hr = CoCreateInstance(CLSID_DirectSound, NULL, CLSCTX_INPROC_SERVER,
IID_IDirectSound, (void**)&m_pIDS);
.................................
................................
hr = IDirectSound_Initialize(m_pIDS, &guID);
.................................
.................................
return TRUE;
}
void CSoundManager::CoUnInitializeDSound(void)
{
if(m_pIDS)
{
m_pIDS->Release();
m_pIDS = NULL;
::CoUninitialize();
}
}
Primary Buffer
The primary buffer is a DirectSoundBuffer object, and its main task is to
receive the
processed sound data from the DirectSound object and send it to the output device to play.
The primary’s format settings determine the sound play effect of the output device.
The primary’s format is a WAVEFORMATEX structure and must be set before play.
Second Buffer
The second buffer loads sound data and sets the sound performance, then sends the sound
to the DirectSound object. The second buffer is a DirectSoundBuffer
object and is created by the DirectSound object.
In my library, I use a wrapper class of DirectSoundBuffer for the second buffer. This wrapper class
has the DirectSoundBuffer object, and the methods to set sound performances, such as adjustment
of volume, frequency and channel.
A special feature of the DirectSoundBuffer wrapper class is the special sound effect of
Fade-In (volume up step by step) and Fade-Out (volume down step by step).
To avoid the concurrency of incoming function calls, a mutex object is used in the
DirectSoundBuffer wrapper class to synchronize the function calls.
Timer Event
The DirectSound wrapper class needs a timer to monitor and manipulate the sound status
and performance in a special time interval. The DirectSound wrapper class is a generic class,
so it is hard to use a windows timer in the class. As an alternative, a waitable timer is used in
the class. A thread is created to check the timer event when the class object is instantiated.
The test program
The test program is a Dialog box application written in MFC. It tests the various wrapper class
features, such as volume, frequency, channel, looping, mixing and fade-in and fade-out, etc.