/* Copyright (C) 2009 Jonathon Fowler This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * DirectSound output driver for MultiVoc */ #define NEED_MMSYSTEM_H #define NEED_DSOUND_H #include "driver_directsound.h" #include "compat.h" #include "multivoc.h" #include "mutex.h" #include "windows_inc.h" #define MIXBUFFERPOSITIONS 8 static int32_t ErrorCode; static int32_t Initialised; static int32_t Playing; static char * MixBuffer; static int32_t MixBufferSize; static int32_t MixBufferCount; static int32_t MixBufferCurrent; static int32_t MixBufferUsed; static void (*MixCallBack)(void); static LPDIRECTSOUND lpds; static LPDIRECTSOUNDBUFFER lpdsbprimary, lpdsbsec; static LPDIRECTSOUNDNOTIFY lpdsnotify; static HANDLE mixThread; static mutex_t mutex; static DSBPOSITIONNOTIFY notifyPositions[MIXBUFFERPOSITIONS + 1] = {}; static void FillBufferPosition(char * ptr, int32_t remaining) { int32_t len = 0; do { if (MixBufferUsed == MixBufferSize) { MixCallBack(); MixBufferUsed = 0; if (++MixBufferCurrent >= MixBufferCount) MixBufferCurrent -= MixBufferCount; } do { char *sptr = MixBuffer + (MixBufferCurrent * MixBufferSize) + MixBufferUsed; len = MixBufferSize - MixBufferUsed; if (remaining < len) len = remaining; memcpy(ptr, sptr, len); ptr += len; MixBufferUsed += len; remaining -= len; } while (remaining >= len && MixBufferUsed < MixBufferSize); } while (remaining >= len); } static void FillBuffer(int32_t bufnum) { LPVOID ptr, ptr2; DWORD remaining, remaining2; int32_t retries = 1; do { HRESULT err = IDirectSoundBuffer_Lock(lpdsbsec, notifyPositions[bufnum].dwOffset, notifyPositions[1].dwOffset, &ptr, &remaining, &ptr2, &remaining2, 0); if (EDUKE32_PREDICT_FALSE(FAILED(err))) { if (err == DSERR_BUFFERLOST) { if (FAILED(err = IDirectSoundBuffer_Restore(lpdsbsec))) goto fail; if (retries-- > 0) continue; } fail: if (MV_Printf) MV_Printf("DirectSound FillBuffer: err %x\n", (uint32_t)err); return; } break; } while (1); if (ptr && remaining) FillBufferPosition((char *)ptr, remaining); if (ptr2 && remaining2) FillBufferPosition((char *)ptr2, remaining2); IDirectSoundBuffer_Unlock(lpdsbsec, ptr, remaining, ptr2, remaining2); } static DWORD WINAPI fillDataThread(LPVOID lpParameter) { UNREFERENCED_PARAMETER(lpParameter); HANDLE handles[MIXBUFFERPOSITIONS+1]; for (int i = 0; i < ARRAY_SSIZE(handles); i++) handles[i] = notifyPositions[i].hEventNotify; do { DWORD const waitret = WaitForMultipleObjects(MIXBUFFERPOSITIONS, handles, FALSE, INFINITE); if (waitret >= WAIT_OBJECT_0 && waitret < WAIT_OBJECT_0+MIXBUFFERPOSITIONS) { mutex_lock(&mutex); FillBuffer((waitret + MIXBUFFERPOSITIONS - 1 - WAIT_OBJECT_0) % MIXBUFFERPOSITIONS); mutex_unlock(&mutex); } else { switch (waitret) { case WAIT_OBJECT_0 + MIXBUFFERPOSITIONS: ExitThread(0); break; default: if (MV_Printf) MV_Printf("DirectSound fillDataThread: wfmo err %d\n", (int32_t)waitret); break; } } } while (1); return 0; } static void TeardownDSound(HRESULT err) { if (FAILED(err)) { if (MV_Printf) MV_Printf("Dying error: %x\n", (uint32_t)err); } if (lpdsnotify) IDirectSoundNotify_Release(lpdsnotify), lpdsnotify = NULL; for (int i = 0; i < MIXBUFFERPOSITIONS + 1; i++) { if (notifyPositions[i].hEventNotify) CloseHandle(notifyPositions[i].hEventNotify); notifyPositions[i].hEventNotify = 0; } #ifdef RENDERTYPEWIN if (mutex) CloseHandle(mutex), mutex = NULL; #endif if (lpdsbsec) IDirectSoundBuffer_Release(lpdsbsec), lpdsbsec = NULL; if (lpdsbprimary) IDirectSoundBuffer_Release(lpdsbprimary), lpdsbprimary = NULL; if (lpds) IDirectSound_Release(lpds), lpds = NULL; } static int DirectSound_Error(HRESULT err, int code) { TeardownDSound(err); ErrorCode = code; return DSErr_Error; } int32_t DirectSoundDrv_PCM_Init(int32_t *mixrate, int32_t *numchannels, void * initdata) { HRESULT err; DSBUFFERDESC bufdesc = {}; WAVEFORMATEX wfex = {}; if (Initialised) DirectSoundDrv_PCM_Shutdown(); if (FAILED(err = DirectSoundCreate(0, &lpds, 0))) return DirectSound_Error(err, DSErr_DirectSoundCreate); if (FAILED(err = IDirectSound_SetCooperativeLevel(lpds, (HWND) initdata, DSSCL_PRIORITY))) return DirectSound_Error(err, DSErr_SetCooperativeLevel); bufdesc.dwSize = sizeof(DSBUFFERDESC); bufdesc.dwFlags = DSBCAPS_LOCSOFTWARE | DSBCAPS_PRIMARYBUFFER | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS; if (FAILED(err = IDirectSound_CreateSoundBuffer(lpds, &bufdesc, &lpdsbprimary, 0))) return DirectSound_Error(err, DSErr_CreateSoundBuffer); wfex.wFormatTag = WAVE_FORMAT_PCM; wfex.nChannels = *numchannels; wfex.nSamplesPerSec = *mixrate; wfex.wBitsPerSample = 16; wfex.nBlockAlign = wfex.nChannels * wfex.wBitsPerSample / 8; wfex.nAvgBytesPerSec = wfex.nSamplesPerSec * wfex.nBlockAlign; if (FAILED(err = IDirectSoundBuffer_SetFormat(lpdsbprimary, &wfex))) return DirectSound_Error(err, DSErr_SetFormat); bufdesc.dwFlags = DSBCAPS_LOCSOFTWARE | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS; bufdesc.dwBufferBytes = wfex.nBlockAlign * 2048 * 2; bufdesc.lpwfxFormat = &wfex; if (FAILED(err = IDirectSound_CreateSoundBuffer(lpds, &bufdesc, &lpdsbsec, 0))) return DirectSound_Error(err, DSErr_CreateSoundBufferSecondary); if (FAILED(err = IDirectSoundBuffer_QueryInterface(lpdsbsec, &IID_IDirectSoundNotify, (LPVOID *)&lpdsnotify))) return DirectSound_Error(err, DSErr_Notify); for (int i = 0; i < MIXBUFFERPOSITIONS; i++) { notifyPositions[i].dwOffset = (bufdesc.dwBufferBytes/MIXBUFFERPOSITIONS)*i; notifyPositions[i].hEventNotify = CreateEvent(NULL, FALSE, FALSE, NULL); if (!notifyPositions[i].hEventNotify) return DirectSound_Error(DS_OK, DSErr_NotifyEvents); } notifyPositions[MIXBUFFERPOSITIONS].dwOffset = DSBPN_OFFSETSTOP; notifyPositions[MIXBUFFERPOSITIONS].hEventNotify = CreateEvent(NULL, FALSE, FALSE, NULL); if (FAILED(err = IDirectSoundNotify_SetNotificationPositions(lpdsnotify, MIXBUFFERPOSITIONS+1, notifyPositions))) return DirectSound_Error(err, DSErr_SetNotificationPositions); if (FAILED(err = IDirectSoundBuffer_Play(lpdsbprimary, 0, 0, DSBPLAY_LOOPING))) return DirectSound_Error(err, DSErr_Play); mutex_init(&mutex); Initialised = 1; return DSErr_Ok; } void DirectSoundDrv_PCM_Shutdown(void) { if (!Initialised) return; DirectSoundDrv_PCM_StopPlayback(); TeardownDSound(DS_OK); Initialised = 0; } int32_t DirectSoundDrv_PCM_BeginPlayback(char *BufferStart, int32_t BufferSize, int32_t NumDivisions, void (*CallBackFunc)(void)) { if (!Initialised) { ErrorCode = DSErr_Uninitialised; return DSErr_Error; } DirectSoundDrv_PCM_StopPlayback(); MixBuffer = BufferStart; MixBufferSize = BufferSize; MixBufferCount = NumDivisions; MixBufferCurrent = 0; MixBufferUsed = 0; MixCallBack = CallBackFunc; // prime the buffer FillBuffer(0); if ((mixThread = CreateThread(NULL, 0, fillDataThread, 0, 0, 0)) == NULL) { ErrorCode = DSErr_CreateThread; return DSErr_Error; } SetThreadPriority(mixThread, THREAD_PRIORITY_ABOVE_NORMAL); HRESULT err = IDirectSoundBuffer_Play(lpdsbsec, 0, 0, DSBPLAY_LOOPING); if (FAILED(err)) { ErrorCode = DSErr_PlaySecondary; return DSErr_Error; } Playing = 1; return DSErr_Ok; } void DirectSoundDrv_PCM_StopPlayback(void) { if (!Playing) return; IDirectSoundBuffer_Stop(lpdsbsec); IDirectSoundBuffer_SetCurrentPosition(lpdsbsec, 0); Playing = 0; } void DirectSoundDrv_PCM_Lock(void) { mutex_lock(&mutex); } void DirectSoundDrv_PCM_Unlock(void) { mutex_unlock(&mutex); } int32_t DirectSoundDrv_GetError(void) { return ErrorCode; } const char *DirectSoundDrv_ErrorString(int32_t ErrorNumber) { const char *ErrorString; switch (ErrorNumber) { case DSErr_Warning: case DSErr_Error: ErrorString = DirectSoundDrv_ErrorString(ErrorCode); break; case DSErr_Ok: ErrorString = "DirectSound ok."; break; case DSErr_Uninitialised: ErrorString = "DirectSound uninitialised."; break; case DSErr_DirectSoundCreate: ErrorString = "DirectSound error: DirectSoundCreate failed."; break; case DSErr_SetCooperativeLevel: ErrorString = "DirectSound error: SetCooperativeLevel failed."; break; case DSErr_CreateSoundBuffer: ErrorString = "DirectSound error: primary CreateSoundBuffer failed."; break; case DSErr_CreateSoundBufferSecondary: ErrorString = "DirectSound error: secondary CreateSoundBuffer failed."; break; case DSErr_SetFormat: ErrorString = "DirectSound error: primary buffer SetFormat failed."; break; case DSErr_SetFormatSecondary: ErrorString = "DirectSound error: secondary buffer SetFormat failed."; break; case DSErr_Notify: ErrorString = "DirectSound error: failed querying secondary buffer for notify interface."; break; case DSErr_NotifyEvents: ErrorString = "DirectSound error: failed creating notify events."; break; case DSErr_SetNotificationPositions: ErrorString = "DirectSound error: failed setting notification positions."; break; case DSErr_Play: ErrorString = "DirectSound error: primary buffer Play failed."; break; case DSErr_PlaySecondary: ErrorString = "DirectSound error: secondary buffer Play failed."; break; case DSErr_CreateThread: ErrorString = "DirectSound error: failed creating mix thread."; break; default: ErrorString = "Unknown DirectSound error code."; break; } return ErrorString; }