Files
itgmania212121/stepmania/src/arch/Sound/DSoundHelpers.cpp
T
Glenn Maynard 3612106dd4 Oops, forgot to commit subdirs ...
This also includes code to use varying sample rates with DirectSound,
so we don't have to do sample rate conversions.  Experimental.
2003-04-25 04:10:18 +00:00

350 lines
10 KiB
C++

#include "../../global.h"
#include "DSoundHelpers.h"
#include "../../RageUtil.h"
#include "../../RageLog.h"
#define DIRECTSOUND_VERSION 0x0700
#include <mmsystem.h>
#include <dsound.h>
#include <math.h>
#pragma comment(lib, "dsound.lib")
#pragma comment(lib, "dxguid.lib")
BOOL CALLBACK DSound::EnumCallback(LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext)
{
LOG->Info("DirectSound Driver: %s (%s)", lpcstrDescription, lpcstrModule);
if(lpGuid)
LOG->Info(" ID: {%8.8x-%4.4x-%4.4x-%6.6x}", lpGuid->Data1, lpGuid->Data2, lpGuid->Data3, lpGuid->Data4);
return TRUE;
}
DSound::DSound()
{
HRESULT hr;
// Initialize COM
if( FAILED( hr = CoInitialize( NULL ) ) )
RageException::ThrowNonfatal(hr_ssprintf(hr, "CoInitialize"));
// Create IDirectSound using the primary sound device
if( FAILED( hr = DirectSoundCreate( NULL, &ds, NULL ) ) )
RageException::ThrowNonfatal(hr_ssprintf(hr, "DirectSoundCreate"));
DirectSoundEnumerate(EnumCallback, 0);
/* Try to set primary mixing privileges */
hr = ds->SetCooperativeLevel(GetDesktopWindow(), DSSCL_PRIORITY);
}
DSound::~DSound()
{
ds->Release();
CoUninitialize();
}
bool DSound::IsEmulated() const
{
/* Don't bother wasting time trying to create buffers if we're
* emulated. This also gives us better diagnostic information. */
DSCAPS Caps;
Caps.dwSize = sizeof(Caps);
HRESULT hr;
if(FAILED(hr = ds->GetCaps(&Caps)))
{
LOG->Warn(hr_ssprintf(hr, "ds->GetCaps failed"));
/* This is strange, so let's be conservative. */
return true;
}
return !!(Caps.dwFlags & DSCAPS_EMULDRIVER);
}
DSoundBuf::DSoundBuf(DSound &ds, DSoundBuf::hw hardware,
int channels_, int samplerate_, int samplebits_, int writeahead_)
{
channels = channels_;
samplerate = samplerate_;
samplebits = samplebits_;
writeahead = writeahead_;
buffer_locked = false;
last_cursor_pos = write_cursor = LastPosition = buffer_bytes_filled = 0;
/* The size of the actual DSound buffer. This can be large; we generally
* won't fill it completely. */
buffersize = 1024*64;
WAVEFORMATEX waveformat;
memset(&waveformat, 0, sizeof(waveformat));
waveformat.cbSize = sizeof(waveformat);
waveformat.wFormatTag = WAVE_FORMAT_PCM;
bool NeedCtrlFrequency = false;
if(samplerate == DYNAMIC_SAMPLERATE)
{
samplerate = 44100;
NeedCtrlFrequency = true;
}
int bytes = samplebits/8;
waveformat.wBitsPerSample = WORD(samplebits);
waveformat.nChannels = WORD(channels);
waveformat.nSamplesPerSec = DWORD(samplerate);
waveformat.nBlockAlign = WORD(bytes*channels);
waveformat.nAvgBytesPerSec = samplerate * bytes*channels;
/* Try to create the secondary buffer */
DSBUFFERDESC format;
memset(&format, 0, sizeof(format));
format.dwSize = sizeof(format);
format.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME;
/* Don't use DSBCAPS_STATIC. It's meant for static buffers, and we
* only use streaming buffers. */
if(hardware == HW_HARDWARE)
format.dwFlags |= DSBCAPS_LOCHARDWARE;
else
format.dwFlags |= DSBCAPS_LOCSOFTWARE;
if(NeedCtrlFrequency)
format.dwFlags |= DSBCAPS_CTRLFREQUENCY;
format.dwBufferBytes = buffersize;
format.dwReserved = 0;
format.lpwfxFormat = &waveformat;
IDirectSoundBuffer *sndbuf_buf;
HRESULT hr = ds.GetDS()->CreateSoundBuffer(&format, &sndbuf_buf, NULL);
if (FAILED(hr))
RageException::ThrowNonfatal(hr_ssprintf(hr, "CreateSoundBuffer failed"));
sndbuf_buf->QueryInterface(IID_IDirectSoundBuffer, (LPVOID*) &buf);
ASSERT(buf);
/* I'm not sure this should ever be needed, but ... */
DSBCAPS bcaps;
bcaps.dwSize=sizeof(bcaps);
hr = buf->GetCaps(&bcaps);
if(FAILED(hr))
RageException::Throw(hr_ssprintf(hr, "buf->GetCaps"));
if(int(bcaps.dwBufferBytes) != buffersize)
{
LOG->Warn("bcaps.dwBufferBytes (%i) != buffersize(%i); adjusting",
bcaps.dwBufferBytes, buffersize);
buffersize = bcaps.dwBufferBytes;
writeahead = min(writeahead, buffersize);
}
}
void DSoundBuf::SetSampleRate(int hz)
{
samplerate = hz;
HRESULT hr = buf->SetFrequency(hz);
LOG->Trace(hr_ssprintf(hr, "samp"));
}
void DSoundBuf::SetVolume(float vol)
{
ASSERT(vol >= 0);
ASSERT(vol <= 1);
if( vol == 0 )
vol = 0.001f; // fix log10f(0) == -INF
float vl2 = log10f(vol) / log10f(2); /* vol log 2 */
/* Volume is a multiplier; SetVolume wants attenuation in thousands of a
* decibel. */
if(vol != 1)
buf->SetVolume(max(int(1000 * vl2), DSBVOLUME_MIN));
}
/* Determine if "pos" is between "start" and "end", for a circular buffer. */
bool contained(int start, int end, int pos)
{
if(end >= start) /* start ... pos ... end */
return start <= pos && pos <= end;
else
return pos >= start || pos <= end;
}
DSoundBuf::~DSoundBuf()
{
buf->Release();
}
bool DSoundBuf::get_output_buf(char **buffer, unsigned *bufsiz, int *play_pos, int chunksize)
{
ASSERT(!buffer_locked);
DWORD cursorstart, junk, cursorend;
HRESULT result;
/* It's easiest to think of the cursor as a block, starting and ending at
* the two values returned by GetCurrentPosition, that we can't write to. */
result = buf->GetCurrentPosition(&cursorstart, &cursorend);
if ( result == DSERR_BUFFERLOST ) {
buf->Restore();
result = buf->GetCurrentPosition(&cursorstart, &cursorend);
}
if ( result != DS_OK ) {
LOG->Warn(hr_ssprintf(result, "DirectSound::GetCurrentPosition failed"));
return false;
}
/* XXX We can figure out if we've underrun, and increase the write-ahead
* when it happens. Two problems:
* 1. It's ugly to wait until we actually underrun. (We could store the
* write-ahead, though.)
* 2. We don't want a random underrun (eg. virus scanner starts) to
* permanently increase our write-ahead. We want the smallest possible
* that will give us reliable audio in normal conditions. I'm not sure
* of a robust way to do this.
*
* Also, writeahead should be a static (all buffers write ahead the same
* amount); writeahead in the ctor should be a hint only (initial value),
* and the sound driver should query a sound to get the current writeahead
* in GetLatencySeconds().
*/
{
int first_byte_filled = write_cursor-buffer_bytes_filled;
if(first_byte_filled < 0) first_byte_filled += buffersize; /* unwrap */
int current_cursor = cursorstart;
if(current_cursor < first_byte_filled) current_cursor += buffersize;
/* The number of bytes that have been played since the last time we got here: */
int bytes_played = current_cursor - first_byte_filled;
buffer_bytes_filled -= bytes_played;
/* Data between the play cursor and the write cursor is committed to be
* played. If we don't actually have data there, we've underrun. */
if(!contained(first_byte_filled, write_cursor, cursorstart) ||
!contained(first_byte_filled, write_cursor, cursorend))
{
LOG->Trace("underrun: %i..%i filled but cursor at %i..%i (missed it by %i)",
first_byte_filled, write_cursor, cursorstart, cursorend,
(cursorend - write_cursor + buffersize) % buffersize);
// (cursorend - first_byte_filled + buffersize) % buffersize);
/* Pretend the space between the play and write cursor is filled
* with data, and continue filling from there. */
int no_write_zone_size = cursorend - cursorstart;
if(no_write_zone_size < 0) no_write_zone_size += buffersize; /* unwrap */
buffer_bytes_filled = no_write_zone_size;
write_cursor = cursorend;
/* Don't register another buffer underrun until the play cursor
* passes the new write cursor. */
}
}
/* If we already have enough bytes written ahead, stop. */
if(buffer_bytes_filled > writeahead)
return false;
int num_bytes_empty = buffersize-buffer_bytes_filled;
/* num_bytes_empty is the amount of free buffer space. If it's
* too small, come back later. */
if(num_bytes_empty < chunksize)
return false;
/* I don't want to deal with DSound's split-circular-buffer locking stuff, so clamp
* the writing space at the end of the physical buffer. */
num_bytes_empty = min(num_bytes_empty, buffersize - write_cursor);
/* Don't fill more than one chunk at a time. This reduces the maximum
* amount of time until we give data; that way, if we're short on time,
* we'll give some data soon instead of lots of data later. */
num_bytes_empty = min(num_bytes_empty, chunksize);
// LOG->Trace("gave %i at %i (%i, %i) %i filled", num_bytes_empty, write_cursor, cursor, write, buffer_bytes_filled);
/* Lock the audio buffer. */
result = buf->Lock(write_cursor, num_bytes_empty, (LPVOID *)buffer, (DWORD *) bufsiz, NULL, &junk, 0);
if ( result == DSERR_BUFFERLOST ) {
buf->Restore();
result = buf->Lock(write_cursor, num_bytes_empty, (LPVOID *)buffer, (DWORD *) bufsiz, NULL, &junk, 0);
}
if ( result != DS_OK ) {
LOG->Warn(hr_ssprintf(result, "Couldn't lock the DirectSound buffer."));
return false;
}
write_cursor += num_bytes_empty;
if(write_cursor >= buffersize) write_cursor -= buffersize;
buffer_bytes_filled += num_bytes_empty;
*play_pos = last_cursor_pos;
/* Increment last_cursor_pos to point at where the data we're about to
* ask for will actually be played. */
last_cursor_pos += num_bytes_empty / samplesize();
buffer_locked = true;
return true;
}
void DSoundBuf::release_output_buf(char *buffer, unsigned bufsiz)
{
buf->Unlock(buffer, bufsiz, NULL, 0);
buffer_locked = false;
}
int DSoundBuf::GetPosition() const
{
DWORD cursor, junk;
buf->GetCurrentPosition(&cursor, &junk);
int cursor_frames = int(cursor) / samplesize();
int write_cursor_frames = write_cursor / samplesize();
int frames_behind = write_cursor_frames - cursor_frames;
if(frames_behind <= 0)
frames_behind += buffersize_frames(); /* unwrap */
int ret = last_cursor_pos - frames_behind;
/* Failsafe: never return a value smaller than we've already returned.
* This can happen once in a while in underrun conditions. */
ret = max(LastPosition, ret);
LastPosition = ret;
return ret;
}
void DSoundBuf::Play()
{
buf->Play(0, 0, DSBPLAY_LOOPING);
}
void DSoundBuf::Stop()
{
buf->Stop();
buf->SetCurrentPosition(0);
last_cursor_pos = LastPosition = write_cursor = buffer_bytes_filled = 0;
}
void DSoundBuf::Reset()
{
/* Nothing is playing. Reset the sample count; this is just to
* prevent eventual overflow. */
last_cursor_pos = LastPosition = 0;
}
/*
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
*
* Glenn Maynard
*/