Proper C++ for DirectSound module, get rid of all static variables, some encapsulation

This commit is contained in:
Vas Crabb 2015-04-09 18:05:44 +10:00
parent 9deeec3f9c
commit 94149cd5a6

View File

@ -43,135 +43,249 @@
// DEBUGGING
//============================================================
#define LOG_SOUND 0
#define LOG_SOUND 0
#define LOG(x) do { if (LOG_SOUND) logerror x; } while(0)
#define LOG(x) do { if (LOG_SOUND) logerror x; } while(0)
class sound_direct_sound : public osd_module, public sound_module
{
public:
sound_direct_sound()
: osd_module(OSD_SOUND_PROVIDER, "dsound"), sound_module()
sound_direct_sound() :
osd_module(OSD_SOUND_PROVIDER, "dsound"),
sound_module(),
m_dsound(NULL),
m_bytes_per_sample(0),
m_primary_buffer(),
m_stream_buffer(),
m_stream_buffer_in(0),
m_buffer_underflows(0),
m_buffer_overflows(0)
{
}
virtual ~sound_direct_sound() { }
virtual int init(const osd_options &options);
virtual int init(osd_options const &options);
virtual void exit();
// sound_module
virtual void update_audio_stream(bool is_throttled, const INT16 *buffer, int samples_this_frame);
virtual void update_audio_stream(bool is_throttled, INT16 const *buffer, int samples_this_frame);
virtual void set_mastervolume(int attenuation);
private:
HRESULT dsound_init();
void dsound_kill();
HRESULT dsound_create_buffers();
void dsound_destroy_buffers();
void copy_sample_data(const INT16 *data, int bytes_to_copy);
class buffer
{
public:
buffer() : m_buffer(NULL) { }
~buffer() { release(); }
ULONG release()
{
ULONG const result = m_buffer ? m_buffer->Release() : 0;
m_buffer = NULL;
return result;
}
operator bool() const { return m_buffer; }
protected:
LPDIRECTSOUNDBUFFER m_buffer;
};
class primary_buffer : public buffer
{
public:
HRESULT create(LPDIRECTSOUND dsound)
{
assert(!m_buffer);
DSBUFFERDESC desc;
memset(&desc, 0, sizeof(desc));
desc.dwSize = sizeof(desc);
desc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_GETCURRENTPOSITION2;
desc.lpwfxFormat = NULL;
return dsound->CreateSoundBuffer(&desc, &m_buffer, NULL);
}
HRESULT get_format(WAVEFORMATEX &format)
{
assert(m_buffer);
return m_buffer->GetFormat(&format, sizeof(format), NULL);
}
HRESULT set_format(WAVEFORMATEX const &format)
{
assert(m_buffer);
return m_buffer->SetFormat(&format);
}
};
class stream_buffer : public buffer
{
public:
stream_buffer() : m_size(0), m_bytes1(NULL), m_bytes2(NULL), m_locked1(0), m_locked2(0) { }
HRESULT create(LPDIRECTSOUND dsound, DWORD size, WAVEFORMATEX &format)
{
assert(!m_buffer);
DSBUFFERDESC desc;
memset(&desc, 0, sizeof(desc));
desc.dwSize = sizeof(desc);
desc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
desc.dwBufferBytes = size;
desc.lpwfxFormat = &format;
m_size = size;
return dsound->CreateSoundBuffer(&desc, &m_buffer, NULL);
}
HRESULT play_looping()
{
assert(m_buffer);
return m_buffer->Play(0, 0, DSBPLAY_LOOPING);
}
HRESULT stop()
{
assert(m_buffer);
return m_buffer->Stop();
}
HRESULT set_volume(LONG volume)
{
assert(m_buffer);
return m_buffer->SetVolume(volume);
}
HRESULT set_min_volume() { return set_volume(DSBVOLUME_MIN); }
HRESULT get_current_positions(DWORD &play_pos, DWORD &write_pos)
{
assert(m_buffer);
return m_buffer->GetCurrentPosition(&play_pos, &write_pos);
}
HRESULT copy_data(DWORD cursor, DWORD bytes, void const *data)
{
HRESULT result = lock(cursor, bytes);
if (DS_OK != result)
return result;
assert(m_bytes1);
assert((m_locked1 + m_locked2) >= bytes);
memcpy(m_bytes1, data, MIN(m_locked1, bytes));
if (m_locked1 < bytes)
{
assert(m_bytes2);
memcpy(m_bytes2, (UINT8 const *)data + m_locked1, bytes - m_locked1);
}
result = unlock();
return DS_OK;
}
HRESULT clear()
{
HRESULT result = lock_all();
if (DS_OK != result)
return result;
assert(m_bytes1);
assert(!m_bytes2);
assert(m_size == m_locked1);
assert(0U == m_locked2);
memset(m_bytes1, 0, m_locked1);
result = unlock();
return DS_OK;
}
DWORD size() const { return m_size; }
protected:
HRESULT lock(DWORD cursor, DWORD bytes)
{
assert(cursor < m_size);
assert(bytes <= m_size);
assert(m_buffer);
assert(!m_bytes1);
return m_buffer->Lock(
cursor, bytes,
&m_bytes1,
&m_locked1,
&m_bytes2,
&m_locked2,
0);
}
HRESULT lock_all() { return lock(0, m_size); }
HRESULT unlock()
{
assert(m_buffer);
assert(m_bytes1);
HRESULT const result = m_buffer->Unlock(
m_bytes1,
m_locked1,
m_bytes2,
m_locked2);
m_bytes1 = m_bytes2 = NULL;
m_locked1 = m_locked2 = 0;
return result;
}
DWORD m_size;
void *m_bytes1, *m_bytes2;
DWORD m_locked1, m_locked2;
};
HRESULT dsound_init();
void dsound_kill();
HRESULT create_buffers(DWORD size, WAVEFORMATEX &format);
void destroy_buffers();
// DirectSound objects
LPDIRECTSOUND m_dsound;
// descriptors and formats
UINT32 m_bytes_per_sample;
// sound buffers
primary_buffer m_primary_buffer;
stream_buffer m_stream_buffer;
UINT32 m_stream_buffer_in;
// buffer over/underflow counts
unsigned m_buffer_underflows;
unsigned m_buffer_overflows;
};
//============================================================
// LOCAL VARIABLES
// init
//============================================================
// DirectSound objects
static LPDIRECTSOUND dsound;
static DSCAPS dsound_caps;
// sound buffers
static LPDIRECTSOUNDBUFFER primary_buffer;
static LPDIRECTSOUNDBUFFER stream_buffer;
static UINT32 stream_buffer_size;
static UINT32 stream_buffer_in;
// descriptors and formats
static DSBUFFERDESC primary_desc;
static DSBUFFERDESC stream_desc;
static WAVEFORMATEX primary_format;
static WAVEFORMATEX stream_format;
// buffer over/underflow counts
static int buffer_underflows;
static int buffer_overflows;
//============================================================
// PROTOTYPES
//============================================================
//-------------------------------------------------
// sound_direct_sound - constructor
//-------------------------------------------------
int sound_direct_sound::init(const osd_options &options)
int sound_direct_sound::init(osd_options const &options)
{
// attempt to initialize directsound
// don't make it fatal if we can't -- we'll just run without sound
dsound_init();
m_buffer_underflows = m_buffer_overflows = 0;
return 0;
}
//============================================================
// exit
//============================================================
void sound_direct_sound::exit()
{
// kill the buffers and dsound
dsound_destroy_buffers();
destroy_buffers();
dsound_kill();
// print out over/underflow stats
if (buffer_overflows || buffer_underflows)
osd_printf_verbose("Sound: buffer overflows=%d underflows=%d\n", buffer_overflows, buffer_underflows);
LOG(("Sound buffer: overflows=%d underflows=%d\n", buffer_overflows, buffer_underflows));
}
//============================================================
// copy_sample_data
//============================================================
void sound_direct_sound::copy_sample_data(const INT16 *data, int bytes_to_copy)
{
void *buffer1, *buffer2;
DWORD length1, length2;
HRESULT result;
int cur_bytes;
// attempt to lock the stream buffer
result = IDirectSoundBuffer_Lock(stream_buffer, stream_buffer_in, bytes_to_copy, &buffer1, &length1, &buffer2, &length2, 0);
// if we failed, assume it was an underflow (i.e.,
if (result != DS_OK)
if (m_buffer_overflows || m_buffer_underflows)
{
buffer_underflows++;
return;
osd_printf_verbose(
"Sound: buffer overflows=%u underflows=%u\n",
m_buffer_overflows,
m_buffer_underflows);
}
// adjust the input pointer
stream_buffer_in = (stream_buffer_in + bytes_to_copy) % stream_buffer_size;
// copy the first chunk
cur_bytes = (bytes_to_copy > length1) ? length1 : bytes_to_copy;
memcpy(buffer1, data, cur_bytes);
// adjust for the number of bytes
bytes_to_copy -= cur_bytes;
data = (INT16 *)((UINT8 *)data + cur_bytes);
// copy the second chunk
if (bytes_to_copy != 0)
{
cur_bytes = (bytes_to_copy > length2) ? length2 : bytes_to_copy;
memcpy(buffer2, data, cur_bytes);
}
// unlock
result = IDirectSoundBuffer_Unlock(stream_buffer, buffer1, length1, buffer2, length2);
LOG(("Sound buffer: overflows=%u underflows=%u\n", m_buffer_overflows, m_buffer_underflows));
}
@ -179,55 +293,66 @@ void sound_direct_sound::copy_sample_data(const INT16 *data, int bytes_to_copy)
// update_audio_stream
//============================================================
void sound_direct_sound::update_audio_stream(bool is_throttled, const INT16 *buffer, int samples_this_frame)
void sound_direct_sound::update_audio_stream(
bool is_throttled,
INT16 const *buffer,
int samples_this_frame)
{
int bytes_this_frame = samples_this_frame * stream_format.nBlockAlign;
DWORD play_position, write_position;
int const bytes_this_frame = samples_this_frame * m_bytes_per_sample;
HRESULT result;
// if no sound, there is no buffer
if (stream_buffer == NULL)
if (!m_stream_buffer)
return;
// determine the current play position
result = IDirectSoundBuffer_GetCurrentPosition(stream_buffer, &play_position, &write_position);
if (result == DS_OK)
{
DWORD stream_in;
DWORD play_position, write_position;
result = m_stream_buffer.get_current_positions(play_position, write_position);
if (DS_OK != result)
return;
//DWORD orig_write = write_position;
// normalize the write position so it is always after the play position
if (write_position < play_position)
write_position += stream_buffer_size;
// normalize the write position so it is always after the play position
if (write_position < play_position)
write_position += m_stream_buffer.size();
// normalize the stream in position so it is always after the write position
stream_in = stream_buffer_in;
if (stream_in < write_position)
stream_in += stream_buffer_size;
// normalize the stream in position so it is always after the write position
DWORD stream_in = m_stream_buffer_in;
if (stream_in < write_position)
stream_in += m_stream_buffer.size();
// now we should have, in order:
// <------pp---wp---si--------------->
// now we should have, in order:
// <------pp---wp---si--------------->
// if we're between play and write positions, then bump forward, but only in full chunks
while (stream_in < write_position)
{
//printf("Underflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)stream_buffer_in, (int)bytes_this_frame);
buffer_underflows++;
stream_in += bytes_this_frame;
}
// if we're going to overlap the play position, just skip this chunk
if (stream_in + bytes_this_frame > play_position + stream_buffer_size)
{
//printf("Overflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)stream_buffer_in, (int)bytes_this_frame);
buffer_overflows++;
return;
}
// now we know where to copy; let's do it
stream_buffer_in = stream_in % stream_buffer_size;
copy_sample_data(buffer, bytes_this_frame);
// if we're between play and write positions, then bump forward, but only in full chunks
while (stream_in < write_position)
{
//printf("Underflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)m_stream_buffer_in, (int)bytes_this_frame);
m_buffer_underflows++;
stream_in += bytes_this_frame;
}
// if we're going to overlap the play position, just skip this chunk
if ((stream_in + bytes_this_frame) > (play_position + m_stream_buffer.size()))
{
//printf("Overflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)m_stream_buffer_in, (int)bytes_this_frame);
m_buffer_overflows++;
return;
}
// now we know where to copy; let's do it
m_stream_buffer_in = stream_in % m_stream_buffer.size();
result = m_stream_buffer.copy_data(m_stream_buffer_in, bytes_this_frame, buffer);
// if we failed, assume it was an underflow (i.e.,
if (result != DS_OK)
{
m_buffer_underflows++;
return;
}
// adjust the input pointer
m_stream_buffer_in = (m_stream_buffer_in + bytes_this_frame) % m_stream_buffer.size();
}
@ -238,14 +363,16 @@ void sound_direct_sound::update_audio_stream(bool is_throttled, const INT16 *buf
void sound_direct_sound::set_mastervolume(int attenuation)
{
// clamp the attenuation to 0-32 range
if (attenuation > 0)
attenuation = 0;
if (attenuation < -32)
attenuation = -32;
attenuation = MAX(MIN(attenuation, 0), -32);
// set the master volume
if (stream_buffer != NULL)
IDirectSoundBuffer_SetVolume(stream_buffer, (attenuation == -32) ? DSBVOLUME_MIN : attenuation * 100);
if (m_stream_buffer)
{
if (-32 == attenuation)
m_stream_buffer.set_min_volume();
else
m_stream_buffer.set_volume(100 * attenuation);
}
}
@ -255,22 +382,24 @@ void sound_direct_sound::set_mastervolume(int attenuation)
HRESULT sound_direct_sound::dsound_init()
{
assert(!m_dsound);
HRESULT result;
// create the DirectSound object
result = DirectSoundCreate(NULL, &dsound, NULL);
result = DirectSoundCreate(NULL, &m_dsound, NULL);
if (result != DS_OK)
{
osd_printf_error("Error creating DirectSound: %08x\n", (UINT32)result);
osd_printf_error("Error creating DirectSound: %08x\n", (unsigned)result);
goto error;
}
// get the capabilities
DSCAPS dsound_caps;
dsound_caps.dwSize = sizeof(dsound_caps);
result = IDirectSound_GetCaps(dsound, &dsound_caps);
result = m_dsound->GetCaps(&dsound_caps);
if (result != DS_OK)
{
osd_printf_error("Error getting DirectSound capabilities: %08x\n", (UINT32)result);
osd_printf_error("Error getting DirectSound capabilities: %08x\n", (unsigned)result);
goto error;
}
@ -280,47 +409,46 @@ HRESULT sound_direct_sound::dsound_init()
SDL_VERSION(&wminfo.version);
#if (SDLMAME_SDL2)
SDL_GetWindowWMInfo(sdl_window_list->sdl_window(), &wminfo);
result = IDirectSound_SetCooperativeLevel(dsound, wminfo.info.win.window, DSSCL_PRIORITY);
result = m_dsound->SetCooperativeLevel(wminfo.info.win.window, DSSCL_PRIORITY);
#else
SDL_GetWMInfo(&wminfo);
result = IDirectSound_SetCooperativeLevel(dsound, wminfo.window, DSSCL_PRIORITY);
result = m_dsound->SetCooperativeLevel(wminfo.window, DSSCL_PRIORITY);
#endif
#else
result = IDirectSound_SetCooperativeLevel(dsound, win_window_list->m_hwnd, DSSCL_PRIORITY);
result = m_dsound->SetCooperativeLevel(win_window_list->m_hwnd, DSSCL_PRIORITY);
#endif
if (result != DS_OK)
{
osd_printf_error("Error setting DirectSound cooperative level: %08x\n", (UINT32)result);
osd_printf_error("Error setting DirectSound cooperative level: %08x\n", (unsigned)result);
goto error;
}
// make a format description for what we want
stream_format.wBitsPerSample = 16;
stream_format.wFormatTag = WAVE_FORMAT_PCM;
stream_format.nChannels = 2;
stream_format.nSamplesPerSec = sample_rate();
stream_format.nBlockAlign = stream_format.wBitsPerSample * stream_format.nChannels / 8;
stream_format.nAvgBytesPerSec = stream_format.nSamplesPerSec * stream_format.nBlockAlign;
{
// make a format description for what we want
WAVEFORMATEX stream_format;
stream_format.wBitsPerSample = 16;
stream_format.wFormatTag = WAVE_FORMAT_PCM;
stream_format.nChannels = 2;
stream_format.nSamplesPerSec = sample_rate();
stream_format.nBlockAlign = stream_format.wBitsPerSample * stream_format.nChannels / 8;
stream_format.nAvgBytesPerSec = stream_format.nSamplesPerSec * stream_format.nBlockAlign;
// compute the buffer size based on the output sample rate
DWORD stream_buffer_size = stream_format.nSamplesPerSec * stream_format.nBlockAlign * m_audio_latency / 10;
stream_buffer_size = MAX(1024, (stream_buffer_size / 1024) * 1024);
// compute the buffer size based on the output sample rate
int audio_latency;
audio_latency = m_audio_latency;
LOG(("stream_buffer_size = %u\n", (unsigned)stream_buffer_size));
stream_buffer_size = stream_format.nSamplesPerSec * stream_format.nBlockAlign * audio_latency / 10;
stream_buffer_size = (stream_buffer_size / 1024) * 1024;
if (stream_buffer_size < 1024)
stream_buffer_size = 1024;
LOG(("stream_buffer_size = %d\n", stream_buffer_size));
// create the buffers
result = dsound_create_buffers();
if (result != DS_OK)
goto error;
// create the buffers
m_bytes_per_sample = stream_format.nBlockAlign;
m_stream_buffer_in = 0;
result = create_buffers(stream_buffer_size, stream_format);
if (result != DS_OK)
goto error;
}
// start playing
result = IDirectSoundBuffer_Play(stream_buffer, 0, 0, DSBPLAY_LOOPING);
result = m_stream_buffer.play_looping();
if (result != DS_OK)
{
osd_printf_error("Error playing: %08x\n", (UINT32)result);
@ -330,7 +458,7 @@ HRESULT sound_direct_sound::dsound_init()
// error handling
error:
dsound_destroy_buffers();
destroy_buffers();
dsound_kill();
return result;
}
@ -343,108 +471,93 @@ error:
void sound_direct_sound::dsound_kill()
{
// release the object
if (dsound != NULL)
IDirectSound_Release(dsound);
dsound = NULL;
if (m_dsound)
m_dsound->Release();
m_dsound = NULL;
}
//============================================================
// dsound_create_buffers
// create_buffers
//============================================================
HRESULT sound_direct_sound::dsound_create_buffers()
HRESULT sound_direct_sound::create_buffers(DWORD size, WAVEFORMATEX &format)
{
assert(m_dsound);
assert(!m_primary_buffer);
assert(!m_stream_buffer);
HRESULT result;
void *buffer;
DWORD locked;
// create a buffer desc for the primary buffer
memset(&primary_desc, 0, sizeof(primary_desc));
primary_desc.dwSize = sizeof(primary_desc);
primary_desc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_GETCURRENTPOSITION2;
primary_desc.lpwfxFormat = NULL;
// create the primary buffer
result = IDirectSound_CreateSoundBuffer(dsound, &primary_desc, &primary_buffer, NULL);
result = m_primary_buffer.create(m_dsound);
if (result != DS_OK)
{
osd_printf_error("Error creating primary DirectSound buffer: %08x\n", (UINT32)result);
osd_printf_error("Error creating primary DirectSound buffer: %08x\n", (unsigned)result);
goto error;
}
// attempt to set the primary format
result = IDirectSoundBuffer_SetFormat(primary_buffer, &stream_format);
result = m_primary_buffer.set_format(format);
if (result != DS_OK)
{
osd_printf_error("Error setting primary DirectSound buffer format: %08x\n", (UINT32)result);
osd_printf_error("Error setting primary DirectSound buffer format: %08x\n", (unsigned)result);
goto error;
}
// get the primary format
result = IDirectSoundBuffer_GetFormat(primary_buffer, &primary_format, sizeof(primary_format), NULL);
// log the primary format
WAVEFORMATEX primary_format;
result = m_primary_buffer.get_format(primary_format);
if (result != DS_OK)
{
osd_printf_error("Error getting primary DirectSound buffer format: %08x\n", (UINT32)result);
osd_printf_error("Error getting primary DirectSound buffer format: %08x\n", (unsigned)result);
goto error;
}
osd_printf_verbose("DirectSound: Primary buffer: %d Hz, %d bits, %d channels\n",
(int)primary_format.nSamplesPerSec, (int)primary_format.wBitsPerSample, (int)primary_format.nChannels);
// create a buffer desc for the stream buffer
memset(&stream_desc, 0, sizeof(stream_desc));
stream_desc.dwSize = sizeof(stream_desc);
stream_desc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
stream_desc.dwBufferBytes = stream_buffer_size;
stream_desc.lpwfxFormat = &stream_format;
osd_printf_verbose(
"DirectSound: Primary buffer: %d Hz, %d bits, %d channels\n",
(int)primary_format.nSamplesPerSec,
(int)primary_format.wBitsPerSample,
(int)primary_format.nChannels);
// create the stream buffer
result = IDirectSound_CreateSoundBuffer(dsound, &stream_desc, &stream_buffer, NULL);
result = m_stream_buffer.create(m_dsound, size, format);
if (result != DS_OK)
{
osd_printf_error("Error creating DirectSound stream buffer: %08x\n", (UINT32)result);
osd_printf_error("Error creating DirectSound stream buffer: %08x\n", (unsigned)result);
goto error;
}
// lock the buffer
result = IDirectSoundBuffer_Lock(stream_buffer, 0, stream_buffer_size, &buffer, &locked, NULL, NULL, 0);
// clear the buffer
result = m_stream_buffer.clear();
if (result != DS_OK)
{
osd_printf_error("Error locking DirectSound stream buffer: %08x\n", (UINT32)result);
osd_printf_error("Error locking DirectSound stream buffer: %08x\n", (unsigned)result);
goto error;
}
// clear the buffer and unlock it
memset(buffer, 0, locked);
IDirectSoundBuffer_Unlock(stream_buffer, buffer, locked, NULL, 0);
return DS_OK;
// error handling
error:
dsound_destroy_buffers();
destroy_buffers();
return result;
}
//============================================================
// dsound_destroy_buffers
// destroy_buffers
//============================================================
void sound_direct_sound::dsound_destroy_buffers(void)
void sound_direct_sound::destroy_buffers(void)
{
// stop any playback
if (stream_buffer != NULL)
IDirectSoundBuffer_Stop(stream_buffer);
if (m_stream_buffer)
m_stream_buffer.stop();
// release the stream buffer
if (stream_buffer != NULL)
IDirectSoundBuffer_Release(stream_buffer);
stream_buffer = NULL;
m_stream_buffer.release();
// release the primary buffer
if (primary_buffer != NULL)
IDirectSoundBuffer_Release(primary_buffer);
primary_buffer = NULL;
m_primary_buffer.release();
}
#else /* SDLMAME_UNIX */