fceux/src/drivers/win/OutputDS.cpp

271 lines
6.9 KiB
C++

//this code is sloppily ripped from an unfinished sound system written for internal use by m.gambrell
//it is released into the public domain and its stability is not warranted
#include "oakra.h"
#include "dsound.h"
#include <vector>
#define LATENCY_MS (100)
class DSVoice : public OAKRA_Voice {
public:
OAKRA_Module_OutputDS *driver;
OAKRA_Format format;
int formatShift;
IDirectSoundBuffer *ds_buf;
int buflen;
unsigned int cPlay;
int vol,pan;
virtual void setPan(int pan) {
//removed for FCEU
}
virtual void setVol(int vol) {
//removed for FCEU
}
virtual int getVol() { return vol; }
virtual int getPan() { return pan; }
void setSource(OAKRA_Module *source) {
this->source = source;
}
virtual ~DSVoice() {
driver->freeVoiceInternal(this,true);
ds_buf->Release();
}
DSVoice(OAKRA_Module_OutputDS *driver, OAKRA_Format &format, IDirectSound *ds_dev, bool global) {
this->driver = driver;
vol = 255;
pan = 0;
source = 0;
this->format = format;
formatShift = getFormatShift(format);
buflen = (format.rate * LATENCY_MS / 1000);
WAVEFORMATEX wfx;
memset(&wfx, 0, sizeof(wfx));
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = format.channels;
wfx.nSamplesPerSec = format.rate;
wfx.nAvgBytesPerSec = format.rate * format.size;
wfx.nBlockAlign = format.size;
wfx.wBitsPerSample = (format.format==OAKRA_S16?16:8);
wfx.cbSize = sizeof(wfx);
DSBUFFERDESC dsbd;
memset(&dsbd, 0, sizeof(dsbd));
dsbd.dwSize = sizeof(dsbd);
//commented out for FCEU
dsbd.dwFlags = DSBCAPS_GETCURRENTPOSITION2;// | DSBCAPS_LOCSOFTWARE | DSBCAPS_CTRLVOLUME ;
if(global) dsbd.dwFlags |= DSBCAPS_GLOBALFOCUS ;
dsbd.dwBufferBytes = buflen * format.size;
dsbd.lpwfxFormat = &wfx;
HRESULT hr = ds_dev->CreateSoundBuffer(&dsbd,&ds_buf,0);
cPlay = 0;
hr = ds_buf->Play(0,0,DSBPLAY_LOOPING);
}
//not supported
virtual void volFade(int start, int end, int ms) {}
void update() {
DWORD play, write;
HRESULT hr = ds_buf->GetCurrentPosition(&play, &write);
play >>= formatShift;
write >>= formatShift;
int todo;
if(play<cPlay) todo = play + buflen - cPlay;
else todo = play - cPlay;
if(!todo) return;
void* buffer1;
void* buffer2;
DWORD buffer1_length;
DWORD buffer2_length;
hr = ds_buf->Lock(
cPlay<<formatShift,todo<<formatShift,
&buffer1, &buffer1_length,
&buffer2, &buffer2_length,0
);
buffer1_length >>= formatShift;
buffer2_length >>= formatShift;
int done = 0;
if(source) {
done = source->generate(buffer1_length,buffer1);
if(done != buffer1_length) {
generateSilence(buffer1_length - done,(char *)buffer1 + (done<<formatShift),format.size);
generateSilence(buffer2_length,buffer2,format.size);
die();
} else {
if(buffer2_length) {
done = source->generate(buffer2_length,buffer2);
if(done != buffer2_length) {
generateSilence(buffer2_length - done,(char *)buffer2 + (done<<formatShift),format.size);
die();
}
}
}
}
ds_buf->Unlock(
buffer1, buffer1_length,
buffer2, buffer2_length);
cPlay = play;
}
};
class Data {
public:
bool global;
IDirectSound* ds_dev;
std::vector<DSVoice *> voices;
CRITICAL_SECTION criticalSection;
};
class ThreadData {
public:
ThreadData() { kill = dead = false; }
OAKRA_Module_OutputDS *ds;
bool kill,dead;
};
OAKRA_Module_OutputDS::OAKRA_Module_OutputDS() {
data = new Data();
((Data *)data)->global = false;
InitializeCriticalSection(&((Data *)data)->criticalSection);
}
OAKRA_Module_OutputDS::~OAKRA_Module_OutputDS() {
//ask the driver to shutdown, and wait for it to do so
((ThreadData *)threadData)->kill = true;
while(!((ThreadData *)threadData)->dead) Sleep(1);
////kill all the voices
std::vector<DSVoice *> voicesCopy = ((Data *)data)->voices;
int voices = (int)voicesCopy.size();
for(int i=0;i<voices;i++)
delete voicesCopy[i];
////free other resources
DeleteCriticalSection(&((Data *)data)->criticalSection);
((Data *)data)->ds_dev->Release();
delete (Data *)data;
delete (ThreadData *)threadData;
}
OAKRA_Voice *OAKRA_Module_OutputDS::getVoice(OAKRA_Format &format, OAKRA_Module *source) {
DSVoice *dsv = (DSVoice *)getVoice(format);
dsv->setSource(source);
return dsv;
}
OAKRA_Voice *OAKRA_Module_OutputDS::getVoice(OAKRA_Format &format) {
DSVoice *voice = new DSVoice(this,format,((Data *)data)->ds_dev,((Data *)data)->global);
((Data *)data)->voices.push_back(voice);
return voice;
}
void OAKRA_Module_OutputDS::freeVoice(OAKRA_Voice *voice) {
freeVoiceInternal(voice,false);
}
void OAKRA_Module_OutputDS::freeVoiceInternal(OAKRA_Voice *voice, bool internal) {
lock();
Data *data = (Data *)this->data;
int j = -1;
for(int i=0;i<(int)data->voices.size();i++)
if(data->voices[i] == voice) j = i;
if(j!=-1)
data->voices.erase(data->voices.begin()+j);
if(!internal)
delete voice;
unlock();
}
void OAKRA_Module_OutputDS::start(void *hwnd) {
HRESULT hr = CoInitialize(NULL);
IDirectSound* ds_dev;
hr = CoCreateInstance(CLSID_DirectSound,0,CLSCTX_INPROC_SERVER,IID_IDirectSound,(void**)&ds_dev);
if(!hwnd) {
hwnd = GetDesktopWindow();
((Data *)data)->global = true;
}
//use default device
hr = ds_dev->Initialize(0);
hr = ds_dev->SetCooperativeLevel((HWND)hwnd, DSSCL_NORMAL);
((Data *)data)->ds_dev = ds_dev;
}
DWORD WINAPI updateProc(LPVOID lpParameter) {
ThreadData *data = (ThreadData *)lpParameter;
for(;;) {
if(data->kill) break;
data->ds->update();
Sleep(1);
}
data->dead = true;
return 0;
}
void OAKRA_Module_OutputDS::beginThread() {
DWORD updateThreadId;
threadData = new ThreadData();
((ThreadData *)threadData)->ds = this;
HANDLE updateThread = CreateThread(0,0,updateProc,threadData,0,&updateThreadId);
SetThreadPriority(updateThread,THREAD_PRIORITY_TIME_CRITICAL);
//SetThreadPriority(updateThread,THREAD_PRIORITY_HIGHEST);
}
void OAKRA_Module_OutputDS::endThread() {
((ThreadData *)threadData)->kill = true;
}
void OAKRA_Module_OutputDS::update() {
lock();
int voices = (int)((Data *)data)->voices.size();
//render all the voices
for(int i=0;i<voices;i++)
((Data *)data)->voices[i]->update();
//look for voices that are dead
std::vector<DSVoice *> deaders;
for(int i=0;i<voices;i++)
if(((Data *)data)->voices[i]->dead)
deaders.push_back(((Data *)data)->voices[i]);
//unlock the driver before killing voices!
//that way, the voice's death callback won't occur within the driver lock
unlock();
//kill those voices
for(int i=0;i<(int)deaders.size();i++) {
deaders[i]->callbackDied();
freeVoice(deaders[i]);
}
}
void OAKRA_Module_OutputDS::lock() {
EnterCriticalSection( &((Data *)this->data)->criticalSection );
}
void OAKRA_Module_OutputDS::unlock() {
LeaveCriticalSection( &((Data *)this->data)->criticalSection );
}