本篇是创建游戏内核(18)【OO改良版】的续篇,关于该内核的细节说明请参阅创建游戏内核(19)。
接口:
//======================================================================================
// This class encapsulate sound buffer playing.
//======================================================================================
typedef class SOUND_CHANNEL
{
friend class SOUND;
public:
SOUND_CHANNEL();
~SOUND_CHANNEL();
IDirectSoundBuffer8* get_ds_buffer();
IDirectSoundNotify8* get_ds_notify();
BOOL create(SOUND_PTR sound, long frequency, short channels, short bits_per_sample);
void free();
BOOL play(const SOUND_DATA_PTR sound_data, long volume_percent, long loop_times);
void stop();
long get_volume();
BOOL set_volume(long percent);
signed long get_pan();
BOOL set_pan(signed long level);
long get_frequency();
BOOL set_frequency(long frequency);
BOOL is_playing();
protected:
BOOL _buffer_data();
void _update();
protected:
SOUND_PTR m_sound; // pointer to parent sound object
IDirectSoundBuffer8* m_ds_buffer; // pointer to DirectSound buffer object
IDirectSoundNotify8* m_ds_notify; // pointer to DirectSound notify object
short m_event_index;
long m_volume; // sound buffer volume
signed long m_pan; // sound buffer pan
BOOL m_is_playing; // sound buffer playing flag
long m_loop_times; // loop times
long m_frequency;
short m_bits_per_sample;
short m_channels;
SOUND_DATA m_sound_data;
short m_load_section; // sound section will to be loaded
short m_stop_section; // sound section will to be stoped
short m_next_notify; // sound notification index will to be played
} *SOUND_CHANNEL_PTR;
实现:
//------------------------------------------------------------------------------
// Constructor, initialize member data.
//------------------------------------------------------------------------------
SOUND_CHANNEL::SOUND_CHANNEL()
{
m_sound = NULL;
m_ds_buffer = NULL;
m_ds_notify = NULL;
m_event_index = -1;
m_volume = 0;
m_pan = 0;
m_frequency = 0;
m_is_playing = FALSE;
}
//------------------------------------------------------------------------------
// Destructor, release sound buffer and sound notification, set the event state
// to nonsignaled.
//------------------------------------------------------------------------------
SOUND_CHANNEL::~SOUND_CHANNEL()
{
free();
}
//------------------------------------------------------------------------------
// Release sound buffer and sound notification, set the event state to nonsignaled.
//------------------------------------------------------------------------------
void SOUND_CHANNEL::free()
{
// stop any playback
stop();
// release the sound notification and sound buffer
release_com(m_ds_notify);
release_com(m_ds_buffer);
// release event from parent SOUND class
m_sound->release_event(this, &m_event_index);
// set to no parent sound
m_sound = NULL;
}
//------------------------------------------------------------------------------
// Stop playing DirectSound buffer.
//------------------------------------------------------------------------------
void SOUND_CHANNEL::stop()
{
if(m_ds_buffer)
m_ds_buffer->Stop();
m_is_playing = FALSE;
}
//------------------------------------------------------------------------------
// Return pointer to DirectSound buffer.
//------------------------------------------------------------------------------
IDirectSoundBuffer8* SOUND_CHANNEL::get_ds_buffer()
{
return m_ds_buffer;
}
//------------------------------------------------------------------------------
// Return pointer to DirectSound notify.
//------------------------------------------------------------------------------
IDirectSoundNotify8* SOUND_CHANNEL::get_ds_notify()
{
return m_ds_notify;
}
//------------------------------------------------------------------------------
// Create sound buffer, set sound notification and event.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::create(SOUND_PTR sound, long frequency, short channels, short bits_per_sample)
{
// free a prior channel
free();
m_sound = sound;
if(m_sound == NULL || m_sound->get_directsound() == NULL)
return FALSE;
// save playback format
m_frequency = frequency;
m_bits_per_sample = bits_per_sample;
m_channels = channels;
WAVEFORMATEX _wave_format;
// create a new sound buffer for this channel, using specified 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;
DSBUFFERDESC _buffer_desc;
ZeroMemory(&_buffer_desc, sizeof(DSBUFFERDESC));
_buffer_desc.dwSize = sizeof(DSBUFFERDESC);
_buffer_desc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPAN | DSBCAPS_CTRLFREQUENCY |
DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_LOCSOFTWARE;
_buffer_desc.dwBufferBytes = g_sound_buffer_size;
_buffer_desc.lpwfxFormat = &_wave_format;
IDirectSoundBuffer* _ds_buffer;
if(FAILED(m_sound->get_directsound()->CreateSoundBuffer(&_buffer_desc, &_ds_buffer, NULL)))
return FALSE;
// query for newer interface
if(FAILED(_ds_buffer->QueryInterface(IID_IDirectSoundBuffer8, (void**) &m_ds_buffer)))
{
_ds_buffer->Release();
return FALSE;
}
// release old object - we have the newer one now
_ds_buffer->Release();
// create the notification interface
if(FAILED(m_ds_buffer->QueryInterface(IID_IDirectSoundNotify8, (void**) &m_ds_notify)))
return FALSE;
HANDLE _event_handle;
// get an event for this sound channel
if(! m_sound->assign_event_for_sound_channel(this, &m_event_index, &_event_handle))
return FALSE;
DSBPOSITIONNOTIFY _pos_notify[4];
// setup the 4 notification positions
_pos_notify[0].dwOffset = g_sound_buffer_chunk - 1;
_pos_notify[0].hEventNotify = _event_handle;
_pos_notify[1].dwOffset = g_sound_buffer_chunk * 2 - 1;
_pos_notify[1].hEventNotify = _event_handle;
_pos_notify[2].dwOffset = g_sound_buffer_chunk * 3 - 1;
_pos_notify[2].hEventNotify = _event_handle;
_pos_notify[3].dwOffset = g_sound_buffer_size - 1;
_pos_notify[3].hEventNotify = _event_handle;
if(FAILED(m_ds_notify->SetNotificationPositions(4, _pos_notify)))
return FALSE;
// set pan and default volume
set_volume(100);
set_pan(0);
return TRUE;
}
//------------------------------------------------------------------------------
// Play sound buffer.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::play(const SOUND_DATA_PTR sound_data, long volume_percent, long loop_times)
{
if(sound_data == NULL || m_ds_buffer == NULL || m_ds_notify == NULL)
return FALSE;
// stop any playback
stop();
// restore a lost buffer just in case
m_ds_buffer->Restore();
// setup playing information
m_sound_data.copy_sound_data(sound_data);
// set looping times
m_loop_times = loop_times;
// calculate stop section position
if(m_loop_times == 0)
m_stop_section = -1;
else
m_stop_section = (short)
(((m_sound_data.m_buffer_size * m_loop_times) % g_sound_buffer_size) / g_sound_buffer_chunk) ;
m_load_section = 0;
// load sound data into sound buffer from sound file or sound data object
_buffer_data();
_buffer_data();
_buffer_data();
_buffer_data();
// set the volume
set_volume(volume_percent);
// set position and begin play
m_next_notify = 0;
if(FAILED(m_ds_buffer->SetCurrentPosition(0)))
return FALSE;
if(FAILED(m_ds_buffer->Play(0, 0, DSBPLAY_LOOPING)))
return FALSE;
// flag as playing
m_is_playing = TRUE;
return TRUE;
}
//------------------------------------------------------------------------------
// Get sound buffer volume.
//------------------------------------------------------------------------------
long SOUND_CHANNEL::get_volume()
{
return m_volume;
}
//------------------------------------------------------------------------------
// Set volume for sound buffer.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::set_volume(long percent)
{
if(! set_ds_buffer_volume(m_ds_buffer, percent))
return FALSE;
m_volume = percent % 101;
return TRUE;
}
//------------------------------------------------------------------------------
// Get sound buffer pan.
//------------------------------------------------------------------------------
signed long SOUND_CHANNEL::get_pan()
{
return m_pan;
}
//------------------------------------------------------------------------------
// Set pan for sound buffer.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::set_pan(long level)
{
if(! set_ds_buffer_pan(m_ds_buffer, level))
return FALSE;
m_pan = level % 101;
return TRUE;
}
//------------------------------------------------------------------------------
// Get sound buffer frequency.
//------------------------------------------------------------------------------
long SOUND_CHANNEL::get_frequency()
{
return m_frequency;
}
//------------------------------------------------------------------------------
// Set frequency for sound buffer.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::set_frequency(long frequency)
{
if(m_ds_buffer == NULL)
return FALSE;
if(FAILED(m_ds_buffer->SetFrequency(frequency)))
return FALSE;
m_frequency = frequency;
return TRUE;
}
//------------------------------------------------------------------------------
// Checks whether sound buffer is playing.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::is_playing()
{
if(m_sound == NULL || m_ds_buffer == NULL || m_ds_notify == NULL)
return FALSE;
return m_is_playing;
}
//------------------------------------------------------------------------------
// Load sound data into sound buffer from sound file or sound data object.
//------------------------------------------------------------------------------
BOOL SOUND_CHANNEL::_buffer_data()
{
if(m_ds_buffer == NULL)
return FALSE;
// setup position to load in
long _lock_pos = (m_load_section % 4) * g_sound_buffer_chunk;
long _size;
char* _ptr;
// lock sound buffer to get pointer to sound data
if(FAILED(m_ds_buffer->Lock(_lock_pos, g_sound_buffer_chunk, (void**) &_ptr, (DWORD*) &_size, NULL, NULL, 0)))
return FALSE;
// clear out buffer if nothing left to load
if(m_sound_data.m_left_size == 0)
ZeroMemory(_ptr, _size);
else
{
// load in the data - take looping into account
long _load_size = _size;
long _load_pos = 0;
// load sound data until specfied load _size is satisfied
for(;;)
{
if(m_sound_data.m_left_size > _load_size)
{
// load into sound data from buffer
memcpy(&_ptr[_load_pos], &m_sound_data.m_ptr[m_sound_data.m_file_curr_pos], _load_size);
// decrease _size of sound data needed to load, advance current sound buffer position.
m_sound_data.m_left_size -= _load_size;
m_sound_data.m_file_curr_pos += _load_size;
break;
}
else // m_sound_data._left_size <= _load_size
{
// load in sound data from buffer
memcpy(&_ptr[_load_pos], &m_sound_data.m_ptr[m_sound_data.m_file_curr_pos], m_sound_data.m_left_size);
// decrease _size of sound data needed to load, advance current sound buffer position.
_load_size -= m_sound_data.m_left_size;
_load_pos += m_sound_data.m_left_size;
// check if we need to stop loop
if(m_loop_times >= 1)
{
m_loop_times--;
if(m_loop_times == 0)
{
// clear out remaining buffer space
if(_load_size)
ZeroMemory(&_ptr[_load_pos], _load_size);
m_sound_data.m_left_size = 0L;
break;
}
}
// reset sound data current position and left _size
m_sound_data.m_file_curr_pos = m_sound_data.m_file_start_pos;
m_sound_data.m_left_size = m_sound_data.m_buffer_size;
// set if we need to stop loading data
if(_load_size == 0)
break;
}
}
}
// unlock the buffer
m_ds_buffer->Unlock(_ptr, _size, NULL, 0);
// mark next section to load
if(++m_load_section > 3)
m_load_section = 0;
return TRUE;
}
//------------------------------------------------------------------------------
// Update for sound buffer playing.
//------------------------------------------------------------------------------
void SOUND_CHANNEL::_update()
{
// check for end of sound
if(m_next_notify == m_stop_section && m_sound_data.m_left_size == 0)
stop();
else
{
// buffer in more data
_buffer_data();
if(++m_next_notify > 3)
m_next_notify = 0;
}
}
测试代码:
/*****************************************************************************
PURPOSE:
Test for class SOUND, SOUND_DATA, SOUND_CHANNEL.
*****************************************************************************/
#include "core_common.h"
#include "core_framework.h"
#include "core_sound.h"
class APP : public FRAMEWORK
{
public:
BOOL init()
{
// Initialize DierctSound and DirectMusic.
m_sound.init(g_hwnd, 22050, 1, 16, DSSCL_NORMAL);
// load into sound data from wave file
m_sound_data[0].load_wav("test1.wav");
m_sound_data[1].load_wav("test2.wav");
// create sound channel
m_sound_channel[0].create(&m_sound,
m_sound_data[0].get_frequency(), m_sound_data[0].get_channels(), m_sound_data[0].get_bits_per_sample());
m_sound_channel[1].create(&m_sound,
m_sound_data[1].get_frequency(), m_sound_data[1].get_channels(), m_sound_data[1].get_bits_per_sample());
// play sound
m_sound_channel[0].play(&m_sound_data[0], 100, 1);
m_sound_channel[1].play(&m_sound_data[1], 50, 0); // lopping forever
return TRUE;
}
BOOL frame()
{
return TRUE;
}
BOOL shutdown()
{
return TRUE;
}
private:
SOUND m_sound;
SOUND_DATA m_sound_data[2];
SOUND_CHANNEL m_sound_channel[2];
FILE* _fp;
};
int PASCAL WinMain(HINSTANCE inst, HINSTANCE, LPSTR cmd_line, int cmd_show)
{
APP app;
if(! build_window(inst, "MainClass", "MainWindow", WS_OVERLAPPEDWINDOW, 0, 0, 640, 480))
return -1;
app.run();
return 0;
}