diff --git a/stepmania/src/arch/Sound/DSoundHelpers.cpp b/stepmania/src/arch/Sound/DSoundHelpers.cpp new file mode 100644 index 0000000000..b2a5284f77 --- /dev/null +++ b/stepmania/src/arch/Sound/DSoundHelpers.cpp @@ -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 + */ diff --git a/stepmania/src/arch/Sound/DSoundHelpers.h b/stepmania/src/arch/Sound/DSoundHelpers.h new file mode 100644 index 0000000000..13e41f56d7 --- /dev/null +++ b/stepmania/src/arch/Sound/DSoundHelpers.h @@ -0,0 +1,20 @@ +#ifndef DSOUND_HELPERS +#define DSOUND_HELPERS 1 + +#define DIRECTSOUND_VERSION 0x0800 +#include +#include +#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 + */ diff --git a/stepmania/src/arch/Sound/RageSoundDriver.h b/stepmania/src/arch/Sound/RageSoundDriver.h new file mode 100644 index 0000000000..c908267ab6 --- /dev/null +++ b/stepmania/src/arch/Sound/RageSoundDriver.h @@ -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 diff --git a/stepmania/src/arch/Sound/RageSoundDriver_DSound.cpp b/stepmania/src/arch/Sound/RageSoundDriver_DSound.cpp new file mode 100644 index 0000000000..ef65b3d9b9 --- /dev/null +++ b/stepmania/src/arch/Sound/RageSoundDriver_DSound.cpp @@ -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 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 + */ diff --git a/stepmania/src/arch/Sound/RageSoundDriver_DSound.h b/stepmania/src/arch/Sound/RageSoundDriver_DSound.h new file mode 100644 index 0000000000..2ee5428f1d --- /dev/null +++ b/stepmania/src/arch/Sound/RageSoundDriver_DSound.h @@ -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_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 + */ diff --git a/stepmania/src/arch/Sound/RageSoundDriver_DSound_Software.cpp b/stepmania/src/arch/Sound/RageSoundDriver_DSound_Software.cpp new file mode 100644 index 0000000000..52a1ce2750 --- /dev/null +++ b/stepmania/src/arch/Sound/RageSoundDriver_DSound_Software.cpp @@ -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 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 + */ diff --git a/stepmania/src/arch/Sound/RageSoundDriver_DSound_Software.h b/stepmania/src/arch/Sound/RageSoundDriver_DSound_Software.h new file mode 100644 index 0000000000..ee1dfa9765 --- /dev/null +++ b/stepmania/src/arch/Sound/RageSoundDriver_DSound_Software.h @@ -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 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 + */ diff --git a/stepmania/src/arch/arch.cpp b/stepmania/src/arch/arch.cpp index 81bd8d2c51..670e013083 100644 --- a/stepmania/src/arch/arch.cpp +++ b/stepmania/src/arch/arch.cpp @@ -3,7 +3,10 @@ */ #include "../stdafx.h" +#include "../RageLog.h" +#include "../RageUtil.h" +#include "../PrefsManager.h" #include "arch.h" /* Load default drivers. */ @@ -16,6 +19,32 @@ 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. * diff --git a/stepmania/src/arch/arch.h b/stepmania/src/arch/arch.h index 0155a7ff89..5db58b6ac8 100644 --- a/stepmania/src/arch/arch.h +++ b/stepmania/src/arch/arch.h @@ -3,8 +3,15 @@ /* Include this file if you need to create an instance of a driver object. */ class LoadingWindow; +class RageSoundDriver; 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 diff --git a/stepmania/src/arch/arch_Win32.h b/stepmania/src/arch/arch_Win32.h index 9ec41d276e..5bf91ff5e1 100644 --- a/stepmania/src/arch/arch_Win32.h +++ b/stepmania/src/arch/arch_Win32.h @@ -4,6 +4,9 @@ /* Load drivers for Win32. */ #include "LoadingWindow/LoadingWindow_Win32.h" +#include "Sound/RageSoundDriver_DSound.h" +#include "Sound/RageSoundDriver_DSound_Software.h" + #endif /* diff --git a/stepmania/src/arch/arch_default.h b/stepmania/src/arch/arch_default.h index 3423ef11d7..6529fddb36 100644 --- a/stepmania/src/arch/arch_default.h +++ b/stepmania/src/arch/arch_default.h @@ -7,6 +7,8 @@ /* Load default fallback drivers; some of these may be overridden by arch-specific drivers. */ #include "LoadingWindow/LoadingWindow_SDL.h" +/* no default sound driver */ + #endif /* diff --git a/stepmania/src/arch/arch_internal.h b/stepmania/src/arch/arch_internal.h new file mode 100644 index 0000000000..55ec73e33c --- /dev/null +++ b/stepmania/src/arch/arch_internal.h @@ -0,0 +1,49 @@ +#ifndef ARCH_INTERNAL +#define ARCH_INTERNAL 1 + +#include + +template +class DriverList +{ +public: + typedef Driver *(*Generator)(); + static void AddDriver(int priority, Generator gen) + { + drivers.insert(pair(priority, gen)); + } + + static Driver *Generate() + { + Driver *ret = NULL; + + for(multimap::iterator i = drivers.begin(); + i != drivers.end(); ++i) + { + try { + ret = i->second(); + } catch(RageException e) { + /* XXX */ + } + } + + return ret; + } + + static multimap drivers; +}; + +template +class DriverEntry +{ +public: + static Driver *Generate() { return new Type; } + + DriverEntry() + { + DriverList::AddDriver(1, + DriverEntry::Generate); + } +}; + +#endif