add new sound code
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
#include "../../stdafx.h"
|
||||||
|
#include "DSoundHelpers.h"
|
||||||
|
|
||||||
|
/* Stuff shared between the two DirectSound drivers. */
|
||||||
|
static IDirectSoundBuffer8 *CreateBuf(IDirectSound8 *ds8, WAVEFORMATEX *wavefmt, Uint32 buffersize, bool Hardware)
|
||||||
|
{
|
||||||
|
/* Try to create the secondary buffer */
|
||||||
|
DSBUFFERDESC format;
|
||||||
|
memset(&format, 0, sizeof(format));
|
||||||
|
format.dwSize = sizeof(format);
|
||||||
|
format.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
|
||||||
|
if(Hardware)
|
||||||
|
format.dwFlags |= DSBCAPS_LOCHARDWARE;
|
||||||
|
format.dwBufferBytes = buffersize;
|
||||||
|
format.dwReserved = 0;
|
||||||
|
format.lpwfxFormat = wavefmt;
|
||||||
|
|
||||||
|
IDirectSoundBuffer *sndbuf_buf;
|
||||||
|
HRESULT hr = ds8->CreateSoundBuffer(&format, &sndbuf_buf, NULL);
|
||||||
|
if (FAILED(hr))
|
||||||
|
throw "CreateSoundBuffer failed";
|
||||||
|
|
||||||
|
IDirectSoundBuffer8 *buf;
|
||||||
|
sndbuf_buf->QueryInterface(IID_IDirectSoundBuffer8, (LPVOID*) &buf);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDirectSoundBuffer8 *CreateBuf(IDirectSound8 *ds8,
|
||||||
|
int channels, int samplerate, int bits,
|
||||||
|
Uint32 buffersize, bool Hardware)
|
||||||
|
{
|
||||||
|
WAVEFORMATEX waveformat;
|
||||||
|
memset(&waveformat, 0, sizeof(waveformat));
|
||||||
|
waveformat.cbSize = sizeof(waveformat);
|
||||||
|
waveformat.wFormatTag = WAVE_FORMAT_PCM;
|
||||||
|
|
||||||
|
int bytes = bits/8;
|
||||||
|
waveformat.wBitsPerSample = WORD(bits);
|
||||||
|
waveformat.nChannels = WORD(channels);
|
||||||
|
waveformat.nSamplesPerSec = DWORD(samplerate);
|
||||||
|
waveformat.nBlockAlign = WORD(bytes*channels);
|
||||||
|
waveformat.nAvgBytesPerSec = samplerate * bytes*channels;
|
||||||
|
|
||||||
|
return CreateBuf(ds8, &waveformat, buffersize, Hardware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
|
||||||
|
*
|
||||||
|
* Glenn Maynard
|
||||||
|
*/
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef DSOUND_HELPERS
|
||||||
|
#define DSOUND_HELPERS 1
|
||||||
|
|
||||||
|
#define DIRECTSOUND_VERSION 0x0800
|
||||||
|
#include <mmsystem.h>
|
||||||
|
#include <dsound.h>
|
||||||
|
#include "SDL.h"
|
||||||
|
|
||||||
|
/* Create a DS buffer of the given format. */
|
||||||
|
IDirectSoundBuffer8 *CreateBuf(IDirectSound8 *ds8,
|
||||||
|
int channels, int samplerate, int bits,
|
||||||
|
Uint32 buffersize, bool Hardware);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
|
||||||
|
*
|
||||||
|
* Glenn Maynard
|
||||||
|
*/
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef RAGE_SOUND_DRIVER
|
||||||
|
#define RAGE_SOUND_DRIVER
|
||||||
|
|
||||||
|
class RageSoundDriver
|
||||||
|
{
|
||||||
|
friend class RageSound;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class RageSoundManager;
|
||||||
|
/* A RageSound calls this to request to be played.
|
||||||
|
* XXX: define what we should do when it can't be played (eg. out of
|
||||||
|
* channels) */
|
||||||
|
virtual void StartMixing(RageSound *snd) = 0;
|
||||||
|
|
||||||
|
/* A RageSound calls this to request it not be played. When this function
|
||||||
|
* returns, snd is no longer valid; ensure no running threads are still
|
||||||
|
* accessing it before returning. This must handle gracefully the case where
|
||||||
|
* snd was not actually being played, though it may print a warning. */
|
||||||
|
virtual void StopMixing(RageSound *snd) = 0;
|
||||||
|
|
||||||
|
/* Get the current position of a given buffer, in the same units and time base
|
||||||
|
* as passed to RageSound::GetPCM. */
|
||||||
|
virtual int GetPosition(const RageSound *snd) const = 0;
|
||||||
|
|
||||||
|
/* When a sound is finished playing (GetPCM returns less than requested) and
|
||||||
|
* the sound has been completely flushed (so GetPosition is no longer meaningful),
|
||||||
|
* call RageSound::SoundStopped(). Do *not* call this when StopMixing is
|
||||||
|
* called. */
|
||||||
|
|
||||||
|
/* Optional, if needed: */
|
||||||
|
virtual void Update(float delta) { }
|
||||||
|
|
||||||
|
/* Sound startup latency--delay between Play() being called and actually
|
||||||
|
* hearing it. (This isn't necessarily the same as the buffer latency.) */
|
||||||
|
virtual float GetPlayLatency() const { return 0.0f; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~RageSoundDriver() { }
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
|
||||||
|
*
|
||||||
|
* Glenn Maynard
|
||||||
|
*/
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
/* D3D implementation that uses multiple streams.
|
||||||
|
*
|
||||||
|
* Each sound gets its own stream, which allows for very little startup
|
||||||
|
* latency for each sound and lower CPU usage. */
|
||||||
|
|
||||||
|
#include "../../stdafx.h"
|
||||||
|
#include "RageSoundDriver_DSound.h"
|
||||||
|
#include "DSoundHelpers.h"
|
||||||
|
|
||||||
|
#include "../../RageSoundManager.h"
|
||||||
|
#include "../../RageException.h"
|
||||||
|
#include "../../RageUtil.h"
|
||||||
|
#include "../../RageSound.h"
|
||||||
|
#include "../../RageLog.h"
|
||||||
|
#include "../../tls.h"
|
||||||
|
#include "SDL.h"
|
||||||
|
|
||||||
|
#include "../../RageTimer.h"
|
||||||
|
|
||||||
|
RageSoundManager *SOUNDMAN = NULL;
|
||||||
|
|
||||||
|
#pragma comment(lib, "dsound.lib")
|
||||||
|
#pragma comment(lib, "dxguid.lib")
|
||||||
|
|
||||||
|
const int channels = 2;
|
||||||
|
const int samplesize = 2 * channels; /* 16-bit */
|
||||||
|
const int samplerate = 44100;
|
||||||
|
const int buffersize_frames = 4096; /* in frames */
|
||||||
|
const int buffersize = buffersize_frames * samplesize; /* in bytes */
|
||||||
|
|
||||||
|
int RageSound_DSound::MixerThread_start(void *p)
|
||||||
|
{
|
||||||
|
((RageSound_DSound *) p)->MixerThread();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RageSound_DSound::MixerThread()
|
||||||
|
{
|
||||||
|
InitThreadData("Mixer thread");
|
||||||
|
VDCHECKPOINT;
|
||||||
|
|
||||||
|
while(!shutdown) {
|
||||||
|
Sleep(10);
|
||||||
|
|
||||||
|
LockMutex L(SOUNDMAN->lock);
|
||||||
|
for(unsigned i = 0; i < stream_pool.size(); ++i)
|
||||||
|
{
|
||||||
|
if(stream_pool[i]->state == stream_pool[i]->INACTIVE)
|
||||||
|
continue; /* inactive */
|
||||||
|
|
||||||
|
stream_pool[i]->GetPCM(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RageSound_DSound::Update(float delta)
|
||||||
|
{
|
||||||
|
/* SoundStopped might erase sounds out from under us, so make a copy
|
||||||
|
* of the sound list. */
|
||||||
|
vector<stream *> str = stream_pool;
|
||||||
|
LockMutex L(SOUNDMAN->lock);
|
||||||
|
|
||||||
|
for(unsigned i = 0; i < str.size(); ++i)
|
||||||
|
{
|
||||||
|
if(str[i]->state != str[i]->STOPPING)
|
||||||
|
continue;
|
||||||
|
if(str[i]->flush_bufs)
|
||||||
|
continue; /* stopping but still flushing */
|
||||||
|
|
||||||
|
/* The sound has stopped and flushed all of its buffers. */
|
||||||
|
if(str[i]->snd != NULL)
|
||||||
|
str[i]->snd->SoundStopped();
|
||||||
|
str[i]->snd = NULL;
|
||||||
|
|
||||||
|
str[i]->str_ds->Stop();
|
||||||
|
str[i]->str_ds->SetCurrentPosition(0);
|
||||||
|
str[i]->LastPosition = -1;
|
||||||
|
str[i]->state = str[i]->INACTIVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If init is true, we're filling the buffer while it's stopped, so put
|
||||||
|
* data in the current buffer (where the play cursor is); otherwise put
|
||||||
|
* it in the opposite buffer. */
|
||||||
|
void RageSound_DSound::stream::GetPCM(bool init)
|
||||||
|
{
|
||||||
|
DWORD cursor, junk;
|
||||||
|
|
||||||
|
HRESULT result;
|
||||||
|
|
||||||
|
if(init) {
|
||||||
|
cursor = 0;
|
||||||
|
last_cursor_pos = 0;
|
||||||
|
} else {
|
||||||
|
result = str_ds->GetCurrentPosition(&cursor, &junk);
|
||||||
|
int playcursor = cursor;
|
||||||
|
if ( result == DSERR_BUFFERLOST ) {
|
||||||
|
str_ds->Restore();
|
||||||
|
result = str_ds->GetCurrentPosition(&cursor, &junk);
|
||||||
|
}
|
||||||
|
if ( result != DS_OK ) {
|
||||||
|
LOG->Warn(hr_ssprintf(result, "DirectSound::GetCurrentPosition failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The DSound buffer is equal to two of our buffers. Round cursor
|
||||||
|
* down to our buffer size. */
|
||||||
|
cursor = (cursor / buffersize) * buffersize;
|
||||||
|
|
||||||
|
/* Cursor points to the buffer that's currently playing. We want to
|
||||||
|
* fill the buffer that *isn't* playing. */
|
||||||
|
cursor += buffersize;
|
||||||
|
cursor %= buffersize*2;
|
||||||
|
|
||||||
|
/* If it hasn't changed, we have nothing to do yet. */
|
||||||
|
if(int(cursor) == last_cursor_filled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Increment last_cursor_pos to point at where the data we're about to
|
||||||
|
* ask for will actually be played. */
|
||||||
|
last_cursor_pos += buffersize_frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
last_cursor_filled = cursor;
|
||||||
|
|
||||||
|
if(state == STOPPING)
|
||||||
|
{
|
||||||
|
if(!flush_bufs)
|
||||||
|
return; /* sound is finished and will be cleaned up in the main thread */
|
||||||
|
|
||||||
|
flush_bufs--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lock the audio buffer. */
|
||||||
|
DWORD len;
|
||||||
|
char *locked_buf = NULL;
|
||||||
|
result = str_ds->Lock(cursor, buffersize, (LPVOID *)&locked_buf, &len, NULL, &junk, 0);
|
||||||
|
if ( result == DSERR_BUFFERLOST ) {
|
||||||
|
str_ds->Restore();
|
||||||
|
result = str_ds->Lock(cursor, buffersize, (LPVOID *)&locked_buf, &len, NULL, &junk, 0);
|
||||||
|
}
|
||||||
|
if ( result != DS_OK ) {
|
||||||
|
LOG->Warn(hr_ssprintf(result, "Couldn't lock the DirectSound buffer."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* It might be INACTIVE, when we're prebuffering. We just don't want to
|
||||||
|
* fill anything in STOPPING; in that case, we just clear the audio buffer. */
|
||||||
|
if(state != STOPPING)
|
||||||
|
CallAudioCallback(locked_buf, len, last_cursor_pos, this);
|
||||||
|
else
|
||||||
|
/* Silence the buffer. */
|
||||||
|
memset(locked_buf, 0, len);
|
||||||
|
|
||||||
|
str_ds->Unlock(locked_buf, len, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RageSound_DSound::stream::CallAudioCallback(
|
||||||
|
char *buf, unsigned long bytes,
|
||||||
|
int outTime, stream *str )
|
||||||
|
{
|
||||||
|
unsigned got = str->snd->GetPCM(buf, bytes, outTime);
|
||||||
|
|
||||||
|
if(got < bytes) {
|
||||||
|
/* Fill the remainder of the buffer with silence. */
|
||||||
|
memset(buf+got, 0, bytes-got);
|
||||||
|
|
||||||
|
/* STOPPING tells the mixer thread to release the stream once str->flush_bufs
|
||||||
|
* buffers have been flushed. */
|
||||||
|
str->state = str->STOPPING;
|
||||||
|
|
||||||
|
/* Flush two buffers worth of data. */
|
||||||
|
str->flush_bufs = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RageSound_DSound::stream::~stream()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
RageSound_DSound::RageSound_DSound()
|
||||||
|
{
|
||||||
|
shutdown = false;
|
||||||
|
|
||||||
|
/* Fire up DSound. */
|
||||||
|
int hr;
|
||||||
|
if(FAILED(hr=DirectSoundCreate8(NULL, &ds8, NULL)))
|
||||||
|
throw RageException(hr_ssprintf(hr, "DirectSoundCreate8"));
|
||||||
|
|
||||||
|
/* Try to set primary mixing privileges */
|
||||||
|
hr = ds8->SetCooperativeLevel(GetDesktopWindow(), DSSCL_PRIORITY);
|
||||||
|
|
||||||
|
/* Create a bunch of streams and put them into the stream pool. */
|
||||||
|
for(int i = 0; i < 32; ++i) {
|
||||||
|
IDirectSoundBuffer8 *newbuf;
|
||||||
|
try {
|
||||||
|
newbuf = CreateBuf(ds8, channels, samplerate, 16, buffersize*2, true);
|
||||||
|
} catch(const char *e) {
|
||||||
|
/* If we didn't get at least 8, fail. */
|
||||||
|
if(i >= 8) break; /* OK */
|
||||||
|
|
||||||
|
/* Clean up; the dtor won't be called. */
|
||||||
|
for(int n = 0; n < i; ++n)
|
||||||
|
delete stream_pool[n];
|
||||||
|
ds8->Release();
|
||||||
|
|
||||||
|
if(i)
|
||||||
|
{
|
||||||
|
/* We created at least one hardware buffer. */
|
||||||
|
LOG->Trace("Could only create %i buffers; need at least 8 (failed with %s). DirectSound driver can't be used.", i, e);
|
||||||
|
throw "Driver unusable (not enough hardware buffers)";
|
||||||
|
}
|
||||||
|
throw "Driver unusable (no hardware buffers)";
|
||||||
|
}
|
||||||
|
|
||||||
|
stream *s = new stream;
|
||||||
|
s->str_ds = newbuf;
|
||||||
|
stream_pool.push_back(s);
|
||||||
|
}
|
||||||
|
LOG->Trace("Got %i hardware buffers", stream_pool.size());
|
||||||
|
|
||||||
|
MixerThreadPtr = SDL_CreateThread(MixerThread_start, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
RageSound_DSound::~RageSound_DSound()
|
||||||
|
{
|
||||||
|
/* Signal the mixing thread to quit. */
|
||||||
|
shutdown = true;
|
||||||
|
SDL_WaitThread(MixerThreadPtr, NULL);
|
||||||
|
|
||||||
|
ds8->Release();
|
||||||
|
|
||||||
|
for(unsigned i = 0; i < stream_pool.size(); ++i)
|
||||||
|
delete stream_pool[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
void RageSound_DSound::StartMixing(RageSound *snd)
|
||||||
|
{
|
||||||
|
/* Find an unused buffer. */
|
||||||
|
unsigned i;
|
||||||
|
for(i = 0; i < stream_pool.size(); ++i) {
|
||||||
|
if(stream_pool[i]->state == stream_pool[i]->INACTIVE)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i == stream_pool.size()) {
|
||||||
|
/* We don't have a free sound buffer. XXX fake it */
|
||||||
|
ASSERT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Give the stream to the playing sound and remove it from the pool. */
|
||||||
|
stream_pool[i]->snd = snd;
|
||||||
|
|
||||||
|
/* Pre-buffer the stream. */
|
||||||
|
/* There are two buffers of data; fill them both ahead of time so the
|
||||||
|
* sound can start almost immediately. */
|
||||||
|
stream_pool[i]->GetPCM(true); /* first call = true */
|
||||||
|
stream_pool[i]->GetPCM(false);
|
||||||
|
HRESULT hr = stream_pool[i]->str_ds->Play(0, 0, DSBPLAY_LOOPING);
|
||||||
|
if ( hr != DS_OK ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Normally, at this point we should still be INACTIVE, in which case,
|
||||||
|
* tell the mixer thread to start mixing this channel. However, if it's
|
||||||
|
* been changed to STOPPING, then we actually finished the whole file
|
||||||
|
* in the prebuffering GetPCM calls above, so leave it alone and let it
|
||||||
|
* finish on its own. */
|
||||||
|
if(stream_pool[i]->state == stream_pool[i]->INACTIVE)
|
||||||
|
stream_pool[i]->state = stream_pool[i]->PLAYING;
|
||||||
|
|
||||||
|
LOG->Trace("new sound assigned to channel %i", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called by a RageSound; asks us to stop mixing them. When this
|
||||||
|
* call completes, snd->GetPCM (which runs in a separate thread) will
|
||||||
|
* not be running and will not be called unless StartMixing is called
|
||||||
|
* again. */
|
||||||
|
void RageSound_DSound::StopMixing(RageSound *snd)
|
||||||
|
{
|
||||||
|
ASSERT(snd != NULL);
|
||||||
|
LockMutex L(SOUNDMAN->lock);
|
||||||
|
|
||||||
|
unsigned i;
|
||||||
|
for(i = 0; i < stream_pool.size(); ++i)
|
||||||
|
if(stream_pool[i]->snd == snd) break;
|
||||||
|
|
||||||
|
if(i == stream_pool.size()) {
|
||||||
|
LOG->Trace("not stopping a sound because it's not playing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* STOPPING tells the mixer thread to release the stream once str->flush_bufs
|
||||||
|
* buffers have been flushed. */
|
||||||
|
stream_pool[i]->state = stream_pool[i]->STOPPING;
|
||||||
|
|
||||||
|
/* Flush two buffers worth of data. */
|
||||||
|
stream_pool[i]->flush_bufs = 2;
|
||||||
|
|
||||||
|
/* This function is called externally (by RageSound) to stop immediately.
|
||||||
|
* We need to prevent SoundStopped from being called; it should only be
|
||||||
|
* called when we stop implicitely at the end of a sound. Set snd to NULL. */
|
||||||
|
stream_pool[i]->snd = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RageSound_DSound::GetPosition(const RageSound *snd) const
|
||||||
|
{
|
||||||
|
LockMutex L(SOUNDMAN->lock);
|
||||||
|
|
||||||
|
unsigned i;
|
||||||
|
for(i = 0; i < stream_pool.size(); ++i)
|
||||||
|
if(stream_pool[i]->snd == snd) break;
|
||||||
|
|
||||||
|
if(i == stream_pool.size())
|
||||||
|
throw RageException("GetPosition: Sound %s is not being played", snd->GetLoadedFilePath());
|
||||||
|
|
||||||
|
ASSERT(i != stream_pool.size());
|
||||||
|
|
||||||
|
DWORD cursor, junk;
|
||||||
|
stream_pool[i]->str_ds->GetCurrentPosition(&cursor, &junk);
|
||||||
|
int last_fill = stream_pool[i]->last_cursor_filled;
|
||||||
|
|
||||||
|
if(last_fill == 0)
|
||||||
|
{
|
||||||
|
if(int(cursor) < last_fill + buffersize)
|
||||||
|
cursor += buffersize*2;
|
||||||
|
last_fill += buffersize*2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int last_pos = stream_pool[i]->last_cursor_pos;
|
||||||
|
int ret = (int(cursor) - last_fill)/samplesize + /* bytes -> samples */
|
||||||
|
last_pos;
|
||||||
|
|
||||||
|
/* Failsafe: never return a value smaller than we've already returned.
|
||||||
|
* This can happen once in a while in underrun conditions. */
|
||||||
|
ret = max(stream_pool[i]->LastPosition, ret);
|
||||||
|
stream_pool[i]->LastPosition = ret;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
|
||||||
|
*
|
||||||
|
* Glenn Maynard
|
||||||
|
*/
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#ifndef RAGE_SOUND_DSOUND
|
||||||
|
#define RAGE_SOUND_DSOUND
|
||||||
|
|
||||||
|
#include "RageSoundDriver.h"
|
||||||
|
#include "SDL_Thread.h"
|
||||||
|
|
||||||
|
struct IDirectSound8;
|
||||||
|
struct IDirectSoundBuffer8;
|
||||||
|
|
||||||
|
class RageSound_DSound: public RageSoundDriver
|
||||||
|
{
|
||||||
|
struct stream {
|
||||||
|
/* Actual audio stream: */
|
||||||
|
IDirectSoundBuffer8 *str_ds;
|
||||||
|
|
||||||
|
/* Sound object that's playing on this stream, or NULL if this
|
||||||
|
* channel is available: */
|
||||||
|
RageSound *snd;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
INACTIVE,
|
||||||
|
PLAYING,
|
||||||
|
STOPPING
|
||||||
|
} state;
|
||||||
|
|
||||||
|
int flush_bufs; /* state == STOPPING only */
|
||||||
|
/* Position in the DS buffer that we filled last; always
|
||||||
|
* either 0 or halfway through the buffer: */
|
||||||
|
int last_cursor_filled;
|
||||||
|
/* Position, in samples, of the last buffer filled: */
|
||||||
|
int last_cursor_pos;
|
||||||
|
|
||||||
|
/* Last time returned for this stream; used to make sure
|
||||||
|
* we never go backwards. */
|
||||||
|
mutable int LastPosition;
|
||||||
|
|
||||||
|
void GetPCM(bool init);
|
||||||
|
|
||||||
|
stream() { str_ds = NULL; snd = NULL; state=INACTIVE; LastPosition = -1; }
|
||||||
|
~stream();
|
||||||
|
void CallAudioCallback(char *buf, unsigned long frames, int outTime, stream *str );
|
||||||
|
};
|
||||||
|
friend struct stream;
|
||||||
|
|
||||||
|
/* Pool of available streams. */
|
||||||
|
vector<stream *> stream_pool;
|
||||||
|
|
||||||
|
IDirectSound8 *ds8;
|
||||||
|
|
||||||
|
bool shutdown; /* tells the MixerThread to shut down */
|
||||||
|
static int MixerThread_start(void *p);
|
||||||
|
void MixerThread();
|
||||||
|
SDL_Thread *MixerThreadPtr;
|
||||||
|
|
||||||
|
/* virtuals: */
|
||||||
|
void StartMixing(RageSound *snd); /* used by RageSound */
|
||||||
|
void StopMixing(RageSound *snd); /* used by RageSound */
|
||||||
|
int GetPosition(const RageSound *snd) const;
|
||||||
|
void Update(float delta);
|
||||||
|
|
||||||
|
public:
|
||||||
|
RageSound_DSound();
|
||||||
|
~RageSound_DSound();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
|
||||||
|
*
|
||||||
|
* Glenn Maynard
|
||||||
|
*/
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
#include "../../stdafx.h"
|
||||||
|
#include "RageSoundDriver_DSound_Software.h"
|
||||||
|
#include "DSoundHelpers.h"
|
||||||
|
|
||||||
|
/* Known problems:
|
||||||
|
*
|
||||||
|
* Skips between screen changes in the software mixer. This is because we use
|
||||||
|
* a smaller buffer. The hardware mixer uses a larger buffer, so the CPU used
|
||||||
|
* loading the new screen isn't a problem. We can't just stop sounds; we want
|
||||||
|
* music to keep playing while we're between screens. Throw a few SDL_Sleep(0)s
|
||||||
|
* in strategic places?
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../../RageTimer.h"
|
||||||
|
#include "../../RageLog.h"
|
||||||
|
#include "../../RageSound.h"
|
||||||
|
#include "../../RageUtil.h"
|
||||||
|
#include "../../tls.h"
|
||||||
|
|
||||||
|
#include "SDL.h"
|
||||||
|
|
||||||
|
/* samples */
|
||||||
|
const int channels = 2;
|
||||||
|
const int samplesize = channels*2; /* 16-bit */
|
||||||
|
const int samplerate = 44100;
|
||||||
|
const int buffersize_frames = 2048; /* in frames */
|
||||||
|
const int buffersize = buffersize_frames * samplesize; /* in bytes */
|
||||||
|
|
||||||
|
int RageSound_DSound_Software::MixerThread_start(void *p)
|
||||||
|
{
|
||||||
|
((RageSound_DSound_Software *) p)->MixerThread();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RageSound_DSound_Software::MixerThread()
|
||||||
|
{
|
||||||
|
InitThreadData("Mixer thread");
|
||||||
|
VDCHECKPOINT;
|
||||||
|
|
||||||
|
while(!shutdown) {
|
||||||
|
Sleep(10);
|
||||||
|
|
||||||
|
LockMutex L(SOUNDMAN->lock);
|
||||||
|
GetPCM();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RageSound_DSound_Software::GetPCM()
|
||||||
|
{
|
||||||
|
DWORD cursor, junk;
|
||||||
|
|
||||||
|
HRESULT result;
|
||||||
|
|
||||||
|
result = str_ds->GetCurrentPosition(&cursor, &junk);
|
||||||
|
if ( result == DSERR_BUFFERLOST ) {
|
||||||
|
str_ds->Restore();
|
||||||
|
result = str_ds->GetCurrentPosition(&cursor, &junk);
|
||||||
|
}
|
||||||
|
if ( result != DS_OK ) {
|
||||||
|
LOG->Warn(hr_ssprintf(result, "DirectSound::GetCurrentPosition failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The DSound buffer is equal to two of our buffers. Round cursor
|
||||||
|
* down to our buffer size. */
|
||||||
|
cursor = (cursor / buffersize) * buffersize;
|
||||||
|
|
||||||
|
/* Cursor points to the buffer that's currently playing. We want to
|
||||||
|
* fill the buffer that *isn't* playing. */
|
||||||
|
cursor += buffersize;
|
||||||
|
cursor %= buffersize*2;
|
||||||
|
|
||||||
|
/* If it hasn't changed, we have nothing to do yet. */
|
||||||
|
if(int(cursor) == last_cursor_filled)
|
||||||
|
return;
|
||||||
|
last_cursor_filled = cursor;
|
||||||
|
|
||||||
|
/* Increment last_cursor_pos to point at where the data we're about to
|
||||||
|
* ask for will actually be played. */
|
||||||
|
last_cursor_pos += buffersize_frames;
|
||||||
|
|
||||||
|
/* Lock the audio buffer. */
|
||||||
|
DWORD len;
|
||||||
|
char *locked_buf = NULL;
|
||||||
|
result = str_ds->Lock(cursor, buffersize, (LPVOID *)&locked_buf, &len, NULL, &junk, 0);
|
||||||
|
if ( result == DSERR_BUFFERLOST ) {
|
||||||
|
str_ds->Restore();
|
||||||
|
result = str_ds->Lock(cursor, buffersize, (LPVOID *)&locked_buf, &len, NULL, &junk, 0);
|
||||||
|
}
|
||||||
|
if ( result != DS_OK ) {
|
||||||
|
LOG->Warn(hr_ssprintf(result, "Couldn't lock the DirectSound buffer."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Silence the buffer. */
|
||||||
|
memset(locked_buf, 0, len);
|
||||||
|
|
||||||
|
/* Create a 32-bit buffer to mix sounds. */
|
||||||
|
static Sint32 *mixbuf = NULL;
|
||||||
|
static Sint16 *buf = NULL;
|
||||||
|
int bufsize = buffersize_frames * channels;
|
||||||
|
if(!buf)
|
||||||
|
{
|
||||||
|
buf = new Sint16[bufsize];
|
||||||
|
mixbuf = new Sint32[bufsize];
|
||||||
|
}
|
||||||
|
memset(buf, 0, bufsize*sizeof(Uint16));
|
||||||
|
memset(mixbuf, 0, bufsize*sizeof(Uint32));
|
||||||
|
|
||||||
|
for(unsigned i = 0; i < sounds.size(); ++i)
|
||||||
|
{
|
||||||
|
if(sounds[i]->stopping)
|
||||||
|
{
|
||||||
|
if(sounds[i]->flush_bufs)
|
||||||
|
sounds[i]->flush_bufs--;
|
||||||
|
} else {
|
||||||
|
/* Call the callback. */
|
||||||
|
unsigned got = sounds[i]->snd->GetPCM((char *) buf, len, last_cursor_pos);
|
||||||
|
|
||||||
|
SOUNDMAN->MixAudio(
|
||||||
|
(Uint8 *) locked_buf, (Uint8 *) buf, got, SDL_MIX_MAXVOLUME/2);
|
||||||
|
|
||||||
|
if(got < len)
|
||||||
|
{
|
||||||
|
/* This sound is finishing. */
|
||||||
|
sounds[i]->stopping = true;
|
||||||
|
sounds[i]->flush_bufs = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
str_ds->Unlock(locked_buf, len, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void RageSound_DSound_Software::StartMixing(RageSound *snd)
|
||||||
|
{
|
||||||
|
sound *s = new sound;
|
||||||
|
s->snd = snd;
|
||||||
|
|
||||||
|
SDL_LockAudio();
|
||||||
|
sounds.push_back(s);
|
||||||
|
SDL_UnlockAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RageSound_DSound_Software::Update(float delta)
|
||||||
|
{
|
||||||
|
LockMutex L(SOUNDMAN->lock);
|
||||||
|
|
||||||
|
/* SoundStopped might erase sounds out from under us, so make a copy
|
||||||
|
* of the sound list. */
|
||||||
|
vector<sound *> snds = sounds;
|
||||||
|
for(unsigned i = 0; i < snds.size(); ++i)
|
||||||
|
{
|
||||||
|
if(sounds[i]->stopping && !sounds[i]->flush_bufs)
|
||||||
|
{
|
||||||
|
/* This sound is done. */
|
||||||
|
snds[i]->snd->SoundStopped();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RageSound_DSound_Software::StopMixing(RageSound *snd)
|
||||||
|
{
|
||||||
|
LockMutex L(SOUNDMAN->lock);
|
||||||
|
|
||||||
|
/* Find the sound. */
|
||||||
|
unsigned i;
|
||||||
|
for(i = 0; i < sounds.size(); ++i)
|
||||||
|
if(sounds[i]->snd == snd) break;
|
||||||
|
if(i == sounds.size())
|
||||||
|
{
|
||||||
|
LOG->Trace("not stopping a sound because it's not playing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete sounds[i];
|
||||||
|
sounds.erase(sounds.begin()+i, sounds.begin()+i+1);
|
||||||
|
|
||||||
|
if(sounds.empty())
|
||||||
|
{
|
||||||
|
/* Nothing is playing. Reset the sample count; this is just to
|
||||||
|
* prevent eventual overflow. */
|
||||||
|
last_cursor_pos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int RageSound_DSound_Software::GetPosition(const RageSound *snd) const
|
||||||
|
{
|
||||||
|
LockMutex L(SOUNDMAN->lock);
|
||||||
|
|
||||||
|
DWORD cursor, junk;
|
||||||
|
str_ds->GetCurrentPosition(&cursor, &junk);
|
||||||
|
int last_fill = last_cursor_filled;
|
||||||
|
|
||||||
|
if(last_fill == 0)
|
||||||
|
{
|
||||||
|
/* Unwrap. */
|
||||||
|
if(int(cursor) < last_fill + buffersize)
|
||||||
|
cursor += buffersize*2;
|
||||||
|
last_fill += buffersize*2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = (int(cursor) - last_fill)/samplesize + /* bytes -> samples */
|
||||||
|
last_cursor_pos;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
RageSound_DSound_Software::RageSound_DSound_Software()
|
||||||
|
{
|
||||||
|
shutdown = false;
|
||||||
|
last_cursor_pos = last_cursor_filled = 0;
|
||||||
|
LastPosition = -1;
|
||||||
|
|
||||||
|
/* Fire up DSound. */
|
||||||
|
int hr;
|
||||||
|
if(FAILED(hr=DirectSoundCreate8(NULL, &ds8, NULL)))
|
||||||
|
throw RageException(hr_ssprintf(hr, "DirectSoundCreate8"));
|
||||||
|
|
||||||
|
/* Try to set primary mixing privileges */
|
||||||
|
hr = ds8->SetCooperativeLevel(GetDesktopWindow(), DSSCL_PRIORITY);
|
||||||
|
|
||||||
|
/* Create a DirectSound stream, but don't force it into hardware. */
|
||||||
|
str_ds = CreateBuf(ds8, channels, samplerate, 16, buffersize*2, false);
|
||||||
|
|
||||||
|
MixerThreadPtr = SDL_CreateThread(MixerThread_start, this);
|
||||||
|
|
||||||
|
hr = str_ds->Play(0, 0, DSBPLAY_LOOPING);
|
||||||
|
}
|
||||||
|
|
||||||
|
RageSound_DSound_Software::~RageSound_DSound_Software()
|
||||||
|
{
|
||||||
|
/* Signal the mixing thread to quit. */
|
||||||
|
shutdown = true;
|
||||||
|
LOG->Trace("Shutting down mixer thread ...");
|
||||||
|
SDL_WaitThread(MixerThreadPtr, NULL);
|
||||||
|
LOG->Trace("Mixer thread shut down.");
|
||||||
|
|
||||||
|
str_ds->Release();
|
||||||
|
ds8->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
float RageSound_DSound_Software::GetPlayLatency() const
|
||||||
|
{
|
||||||
|
return (1.0f / samplerate) * buffersize_frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
|
||||||
|
*
|
||||||
|
* Glenn Maynard
|
||||||
|
*/
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#ifndef RAGE_SOUND_DSOUND_SOFTWARE
|
||||||
|
#define RAGE_SOUND_DSOUND_SOFTWARE
|
||||||
|
|
||||||
|
#include "RageSoundDriver.h"
|
||||||
|
#include "SDL_Thread.h"
|
||||||
|
|
||||||
|
struct IDirectSound8;
|
||||||
|
struct IDirectSoundBuffer8;
|
||||||
|
|
||||||
|
class RageSound_DSound_Software: public RageSoundDriver
|
||||||
|
{
|
||||||
|
struct sound {
|
||||||
|
RageSound *snd;
|
||||||
|
|
||||||
|
bool stopping;
|
||||||
|
|
||||||
|
int flush_bufs; /* state == STOPPING only */
|
||||||
|
|
||||||
|
sound() { snd = NULL; stopping=false; }
|
||||||
|
};
|
||||||
|
|
||||||
|
void GetPCM();
|
||||||
|
|
||||||
|
bool shutdown;
|
||||||
|
|
||||||
|
int last_cursor_filled, last_cursor_pos;
|
||||||
|
|
||||||
|
void Update(float delta);
|
||||||
|
|
||||||
|
IDirectSound8 *ds8;
|
||||||
|
IDirectSoundBuffer8 *str_ds;
|
||||||
|
|
||||||
|
static int MixerThread_start(void *p);
|
||||||
|
void MixerThread();
|
||||||
|
SDL_Thread *MixerThreadPtr;
|
||||||
|
|
||||||
|
/* List of currently playing sounds: */
|
||||||
|
vector<sound *> sounds;
|
||||||
|
|
||||||
|
mutable int LastPosition;
|
||||||
|
|
||||||
|
/* virtuals: */
|
||||||
|
void StartMixing(RageSound *snd); /* used by RageSound */
|
||||||
|
void StopMixing(RageSound *snd); /* used by RageSound */
|
||||||
|
int GetPosition(const RageSound *snd) const;
|
||||||
|
float GetPlayLatency() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RageSound_DSound_Software();
|
||||||
|
~RageSound_DSound_Software();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
|
||||||
|
*
|
||||||
|
* Glenn Maynard
|
||||||
|
*/
|
||||||
@@ -3,7 +3,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "../stdafx.h"
|
#include "../stdafx.h"
|
||||||
|
#include "../RageLog.h"
|
||||||
|
#include "../RageUtil.h"
|
||||||
|
|
||||||
|
#include "../PrefsManager.h"
|
||||||
#include "arch.h"
|
#include "arch.h"
|
||||||
|
|
||||||
/* Load default drivers. */
|
/* Load default drivers. */
|
||||||
@@ -16,6 +19,32 @@
|
|||||||
|
|
||||||
LoadingWindow *MakeLoadingWindow() { return new ARCH_LOADING_WINDOW; }
|
LoadingWindow *MakeLoadingWindow() { return new ARCH_LOADING_WINDOW; }
|
||||||
|
|
||||||
|
/* Err, this is ugly--breaks arch encapsulation. Hmm. */
|
||||||
|
RageSoundDriver *MakeRageSoundDriver(CString drivers)
|
||||||
|
{
|
||||||
|
CStringArray DriversToTry;
|
||||||
|
split(drivers, ",", DriversToTry, true);
|
||||||
|
|
||||||
|
for(unsigned i = 0; i < DriversToTry.size(); ++i)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
LOG->Trace("Initializing driver: %s", DriversToTry[i].GetString());
|
||||||
|
|
||||||
|
#if defined(WIN32)
|
||||||
|
if(DriversToTry[i] == "DirectSound") return new RageSound_DSound;
|
||||||
|
if(DriversToTry[i] == "DirectSound-sw") return new RageSound_DSound_Software;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOG->Warn("Unknown sound driver name: %s", DriversToTry[i].GetString());
|
||||||
|
}
|
||||||
|
catch(const char *e) {
|
||||||
|
LOG->Trace("Couldn't load driver %s: %s", DriversToTry[i].GetString(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
|
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,8 +3,15 @@
|
|||||||
|
|
||||||
/* Include this file if you need to create an instance of a driver object. */
|
/* Include this file if you need to create an instance of a driver object. */
|
||||||
class LoadingWindow;
|
class LoadingWindow;
|
||||||
|
class RageSoundDriver;
|
||||||
|
|
||||||
LoadingWindow *MakeLoadingWindow();
|
LoadingWindow *MakeLoadingWindow();
|
||||||
|
RageSoundDriver *MakeRageSoundDriver(CString drivers);
|
||||||
|
|
||||||
|
/* Define the default list of sound drivers for each arch. */
|
||||||
|
#if defined(WIN32)
|
||||||
|
#define DEFAULT_SOUND_DRIVER_LIST "DirectSound,DirectSound-sw"
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
/* Load drivers for Win32. */
|
/* Load drivers for Win32. */
|
||||||
#include "LoadingWindow/LoadingWindow_Win32.h"
|
#include "LoadingWindow/LoadingWindow_Win32.h"
|
||||||
|
|
||||||
|
#include "Sound/RageSoundDriver_DSound.h"
|
||||||
|
#include "Sound/RageSoundDriver_DSound_Software.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
/* Load default fallback drivers; some of these may be overridden by arch-specific drivers. */
|
/* Load default fallback drivers; some of these may be overridden by arch-specific drivers. */
|
||||||
#include "LoadingWindow/LoadingWindow_SDL.h"
|
#include "LoadingWindow/LoadingWindow_SDL.h"
|
||||||
|
|
||||||
|
/* no default sound driver */
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#ifndef ARCH_INTERNAL
|
||||||
|
#define ARCH_INTERNAL 1
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
template <class Driver>
|
||||||
|
class DriverList
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef Driver *(*Generator)();
|
||||||
|
static void AddDriver(int priority, Generator gen)
|
||||||
|
{
|
||||||
|
drivers.insert(pair<int, Generator>(priority, gen));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Driver *Generate()
|
||||||
|
{
|
||||||
|
Driver *ret = NULL;
|
||||||
|
|
||||||
|
for(multimap<int, Generator>::iterator i = drivers.begin();
|
||||||
|
i != drivers.end(); ++i)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
ret = i->second();
|
||||||
|
} catch(RageException e) {
|
||||||
|
/* XXX */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static multimap<int, Generator> drivers;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Driver, class Type>
|
||||||
|
class DriverEntry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static Driver *Generate() { return new Type; }
|
||||||
|
|
||||||
|
DriverEntry()
|
||||||
|
{
|
||||||
|
DriverList<RageSoundDriver>::AddDriver(1,
|
||||||
|
DriverEntry<Driver,Type>::Generate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user