本篇是创建游戏内核(17)【OO改良版】的续篇,关于该内核的细节说明请参阅创建游戏内核(18)。
接口:
#define EVENT_NUM 32
BOOL set_ds_buffer_volume(LPDIRECTSOUNDBUFFER ds_buffer, long percent);
BOOL set_audio_path_volume(IDirectMusicPerformance8* dm_perf, long percent);
BOOL set_performance_tempo(IDirectMusicPerformance8* dm_perf, long percent);
//======================================================================================
// This class encapsulate for DirectPerformance and DirectLoader and sound play event handle.
//======================================================================================
typedef class SOUND
{
public:
SOUND();
~SOUND();
// assign and release events
BOOL assign_event_for_sound_channel(SOUND_CHANNEL* sound_channel, short* event_index, HANDLE* event_handle);
BOOL release_event(SOUND_CHANNEL* sound_channel, short* event_index);
// function to retrieve com interfaces
IDirectSound8* get_directsound();
IDirectSoundBuffer* get_ds_buffer();
IDirectMusicPerformance8* get_dm_performance();
IDirectMusicLoader8* get_dm_loader();
// init and shutdown functions
BOOL init(HWND hwnd, long frequency, short channels, short bits_per_sample, long coop_level);
void shutdown();
// volume get/get
long get_volume();
BOOL set_volume(long percent);
// restore system to known state
void restore();
private:
/////////////////////////////// Sound system related ///////////////////////////////
HWND m_hwnd; // pointer to parent window handle
long m_volume; // global sound buffer volume
// Events for all sound channel, the last event takes charge to close all other events.
HANDLE m_event_handle[EVENT_NUM+1];
// all sound channel pointers
SOUND_CHANNEL* m_sound_channel[EVENT_NUM];
HANDLE m_thread_handle;
DWORD m_thread_id;
BOOL m_thread_active;
static DWORD handle_notifications(LPVOID data);
/////////////////////////////// Sound related ///////////////////////////////
IDirectSound8* m_ds;
IDirectSoundBuffer* m_ds_buffer;
long m_coop_level;
long m_frequency;
short m_channels;
short m_bits_per_sample;
/////////////////////////////// Music related - MIDI ///////////////////////////////
IDirectMusicPerformance8* m_dm_perf;
IDirectMusicLoader8* m_dm_loader;
} *SOUND_PTR;
实现:
#define err_msg_box(msg) MessageBox(NULL, msg, "Error", MB_OK)
//------------------------------------------------------------------------------
// Set volume for direct sound buffer.
//------------------------------------------------------------------------------
BOOL set_ds_buffer_volume(LPDIRECTSOUNDBUFFER ds_buffer, long percent)
{
long volume;
if(ds_buffer == NULL)
return FALSE;
// calculate a usable volume level
if(percent == 0)
volume = DSBVOLUME_MIN;
else
volume = -20 * (100 - (percent % 101));
if(FAILED(ds_buffer->SetVolume(volume)))
return FALSE;
return TRUE;
}
//------------------------------------------------------------------------------
// Set volume for the default audio path of DirectMusic perforfamce.
//------------------------------------------------------------------------------
BOOL set_audio_path_volume(IDirectMusicPerformance8* dm_perf, long percent)
{
IDirectMusicAudioPath8* audio_path;
long volume;
if(dm_perf == NULL)
return FALSE;
// retrieves the default audiopath
if(FAILED(dm_perf->GetDefaultAudioPath(&audio_path)))
return FALSE;
// calculate a usable volume level
if(percent == 0)
volume = -9600;
else
volume = (long) (-32.0 * (100.0 - (float)(percent % 101)));
// set the audio volume on the audiopath, the volume can be faded in or out.
if(FAILED(audio_path->SetVolume(volume, 0)))
{
audio_path->Release();
return FALSE;
}
audio_path->Release();
return TRUE;
}
//------------------------------------------------------------------------------
// Set tempo for DirectMusic performance.
//------------------------------------------------------------------------------
BOOL set_performance_tempo(IDirectMusicPerformance8* dm_perf, long percent)
{
if(dm_perf == NULL)
return FALSE;
// calculate tempo setting based on percentage
float tempo = (float) percent / 100.0f;
// set master performance tempo
if(FAILED(dm_perf->SetGlobalParam(GUID_PerfMasterTempo, (void*) &tempo, sizeof(float))))
return FALSE;
return TRUE;
}
//////////////////////////////////// defines for class SOUND ////////////////////////////////////
//------------------------------------------------------------------------------
// Constructor, initialize member data.
//------------------------------------------------------------------------------
SOUND::SOUND()
{
// initialize com
CoInitialize(NULL);
memset(this, 0, sizeof(*this));
}
//------------------------------------------------------------------------------
// Destructor, release main sound buffer, close all event and thread.
//------------------------------------------------------------------------------
SOUND::~SOUND()
{
shutdown();
// uninitialize com
CoUninitialize();
}
//------------------------------------------------------------------------------
// Release main sound buffer, close all events and threads.
//------------------------------------------------------------------------------
void SOUND::shutdown()
{
// stop the music, and close down.
if(m_dm_perf)
{
m_dm_perf->Stop(NULL, NULL, 0, 0);
m_dm_perf->CloseDown();
}
// release the DirectMusic objects
release_com(m_dm_perf);
release_com(m_dm_loader);
// go through all used sound channels and free them
for(short i = 0; i < EVENT_NUM; i++)
{
if(m_sound_channel[i])
{
m_sound_channel[i]->free();
m_sound_channel[i] = NULL;
}
// clear the event status
if(m_event_handle[i])
ResetEvent(m_event_handle[i]);
}
// stop the primary channel from playing
if(m_ds_buffer)
m_ds_buffer->Stop();
// release the DirectSound objects
release_com(m_ds_buffer);
release_com(m_ds);
// force a closure of the thread by triggering the last event and waiting for it to terminate
if(m_thread_handle)
{
if(m_event_handle[EVENT_NUM])
{
while(m_thread_active)
// set the specified event object to the signaled state
SetEvent(m_event_handle[EVENT_NUM]);
}
}
// close all event handles
for(short i = 0; i < EVENT_NUM+1; i++)
{
if(m_event_handle[i])
{
CloseHandle(m_event_handle[i]);
m_event_handle[i] = NULL;
}
}
// free the thread handle
if(m_thread_handle)
{
CloseHandle(m_thread_handle);
m_thread_handle = NULL;
}
m_thread_id = 0;
}
//------------------------------------------------------------------------------
// Initialize DierctSound and DirectMusic, create a thread for handling notifications.
//------------------------------------------------------------------------------
BOOL SOUND::init(HWND hwnd, long frequency, short channels, short bits_per_sample, long coop_level)
{
// shutdown system in case of prior install
shutdown();
// save parent window handle
if((m_hwnd = hwnd) == NULL)
return FALSE;
///////////////////////////////////////////////////////////////////
// Initialize DirectSound
///////////////////////////////////////////////////////////////////
// save settings for sound setup
if(coop_level == DSSCL_NORMAL)
coop_level = DSSCL_PRIORITY;
m_coop_level = coop_level;
m_frequency = frequency;
m_channels = channels;
m_bits_per_sample = bits_per_sample;
// create an IDirectSound8 object
if(FAILED(DirectSoundCreate8(NULL, &m_ds, NULL)))
return FALSE;
// set cooperative mode
if(FAILED(m_ds->SetCooperativeLevel(m_hwnd, m_coop_level)))
return FALSE;
// create sound buffer
DSBUFFERDESC _ds_buffer_desc;
// set sound buffer description
ZeroMemory(&_ds_buffer_desc, sizeof(DSBUFFERDESC));
_ds_buffer_desc.dwSize = sizeof(DSBUFFERDESC);
_ds_buffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
_ds_buffer_desc.dwBufferBytes = 0;
_ds_buffer_desc.lpwfxFormat = NULL;
if(FAILED(m_ds->CreateSoundBuffer(&_ds_buffer_desc, &m_ds_buffer, NULL)))
return FALSE;
// set wave format for sound buffer
WAVEFORMATEX _wave_format;
// set the primary buffer format
ZeroMemory(&_wave_format, sizeof(WAVEFORMATEX));
_wave_format.wFormatTag = WAVE_FORMAT_PCM;
_wave_format.nChannels = (WORD) m_channels;
_wave_format.nSamplesPerSec = m_frequency;
_wave_format.wBitsPerSample = (WORD) m_bits_per_sample;
_wave_format.nBlockAlign = _wave_format.wBitsPerSample / 8 * _wave_format.nChannels;
_wave_format.nAvgBytesPerSec = _wave_format.nSamplesPerSec * _wave_format.nBlockAlign;
if(FAILED(m_ds_buffer->SetFormat(&_wave_format)))
return FALSE;
// create the events, plus an extra one for thread termination.
for(short i = 0; i < EVENT_NUM+1; i++)
{
if((m_event_handle[i] = CreateEvent(NULL, FALSE, FALSE, NULL)) == NULL)
return FALSE;
}
// create a thread for handling notifications
m_thread_handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) handle_notifications, this, 0, &m_thread_id);
if(m_thread_handle == NULL)
return FALSE;
///////////////////////////////////////////////////////////////////
// Initialize DirectMusic
///////////////////////////////////////////////////////////////////
// create the DirectMusic loader object
CoCreateInstance(CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8, (void**) &m_dm_loader);
// create the DirectMusic performance object
CoCreateInstance(CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC, IID_IDirectMusicPerformance8, (void**) &m_dm_perf);
// Initialize the performance with the standard audio path.
// This initializes both DirectMusic and DirectSound and sets up the synthesizer.
m_dm_perf->InitAudio(NULL, NULL, m_hwnd, DMUS_APATH_SHARED_STEREOPLUSREVERB, 128, DMUS_AUDIOF_ALL, NULL);
// set the performance global volume to +10 decibels
long _volume_level = 1000;
if(FAILED(m_dm_perf->SetGlobalParam(GUID_PerfMasterVolume, &_volume_level, sizeof(long))))
return FALSE;
CHAR _path[MAX_PATH];
WCHAR _w_path[MAX_PATH];
// tell DirectMusic where the default search path is
GetCurrentDirectory(MAX_PATH, _path);
MultiByteToWideChar(CP_ACP, 0, _path, -1, _w_path, MAX_PATH);
m_dm_loader->SetSearchDirectory(GUID_DirectMusicAllTypes, _w_path, FALSE);
// set default volume to full
set_volume(100);
return TRUE;
}
//------------------------------------------------------------------------------
// Return pointer to DirectSound.
//------------------------------------------------------------------------------
IDirectSound8* SOUND::get_directsound()
{
return m_ds;
}
//------------------------------------------------------------------------------
// Return pointer to primary DirectSound buffer.
//------------------------------------------------------------------------------
IDirectSoundBuffer* SOUND::get_ds_buffer()
{
return m_ds_buffer;
}
//------------------------------------------------------------------------------
// Return pointer to DirectMusic performance object.
//------------------------------------------------------------------------------
IDirectMusicPerformance8* SOUND::get_dm_performance()
{
return m_dm_perf;
}
//------------------------------------------------------------------------------
// Return pointer to DirectMusic loader object.
//------------------------------------------------------------------------------
IDirectMusicLoader8* SOUND::get_dm_loader()
{
return m_dm_loader;
}
//------------------------------------------------------------------------------
// Assign sound channel with specified event.
//------------------------------------------------------------------------------
BOOL SOUND::assign_event_for_sound_channel(SOUND_CHANNEL* sound_channel, short* event_index, HANDLE* event_handle)
{
for(short i = 0; i < EVENT_NUM; i++)
{
if(m_event_handle[i] && m_sound_channel[i] == NULL)
{
// set the specified event object to the nonsignaled state
ResetEvent(m_event_handle[i]);
m_sound_channel[i] = sound_channel;
*event_index = i;
*event_handle = m_event_handle[i];
return TRUE;
}
}
return FALSE;
}
//------------------------------------------------------------------------------
// Set the event state to nonsignaled.
//------------------------------------------------------------------------------
BOOL SOUND::release_event(SOUND_CHANNEL* sound_channel, short* event_index)
{
if((unsigned short)(*event_index) < EVENT_NUM && m_sound_channel[*event_index] == sound_channel)
{
ResetEvent(m_event_handle[*event_index]);
// set event channel pointer with NULL
m_sound_channel[*event_index] = NULL;
*event_index = -1;
return TRUE;
}
return FALSE;
}
//------------------------------------------------------------------------------
// Get global sound volume.
//------------------------------------------------------------------------------
long SOUND::get_volume()
{
return m_volume;
}
//------------------------------------------------------------------------------
// Set the global sound volume.
//------------------------------------------------------------------------------
BOOL SOUND::set_volume(long percent)
{
if(! set_ds_buffer_volume(m_ds_buffer, percent))
return FALSE;
m_volume = percent % 101;
return TRUE;
}
//------------------------------------------------------------------------------
// Handle all sound events.
//------------------------------------------------------------------------------
DWORD SOUND::handle_notifications(LPVOID data)
{
MSG _msg;
SOUND* _sound = (SOUND*) data;
_sound->m_thread_active = TRUE;
BOOL _complete = FALSE;
while(! _complete)
{
// wait for a message
DWORD _result = MsgWaitForMultipleObjects(EVENT_NUM+1, _sound->m_event_handle, FALSE, INFINITE, QS_ALLEVENTS);
// get channel index to update
DWORD _channel_index = _result - WAIT_OBJECT_0;
// check for channel update
if(_channel_index >= 0 && _channel_index < EVENT_NUM)
{
if(_sound->m_sound_channel[_channel_index])
_sound->m_sound_channel[_channel_index]->_update();
}
else if(_channel_index == EVENT_NUM) // check for thread closure
_complete = TRUE;
else if(_channel_index > EVENT_NUM) // check for waiting messages
{
while(PeekMessage(&_msg, NULL, 0, 0, PM_REMOVE))
{
if(_msg.message == WM_QUIT)
{
_complete = TRUE;
break;
}
}
}
}
_sound->m_thread_active = FALSE;
return 0L;
}
//------------------------------------------------------------------------------
// Restore primary DirectSound buffer and DirectSound channel buffer.
//------------------------------------------------------------------------------
void SOUND::restore()
{
// restore primary sound buffer
if(m_ds_buffer != NULL)
m_ds_buffer->Restore();
// restore all used sound channels buffer
for(short i = 0; i < EVENT_NUM; i++)
{
if(m_sound_channel[i] != NULL)
m_sound_channel[i]->m_ds_buffer->Restore();
}
}