/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2015 - ROLI Ltd. Permission is granted to use this software under the terms of either: a) the GPL v2 (or any later version) b) the Affero GPL v3 Details of these licenses can be found at: www.gnu.org/licenses JUCE 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. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.juce.com for more information. ============================================================================== */ } // (juce namespace) extern "C" { // Declare just the minimum number of interfaces for the DSound objects that we need.. typedef struct typeDSBUFFERDESC { DWORD dwSize; DWORD dwFlags; DWORD dwBufferBytes; DWORD dwReserved; LPWAVEFORMATEX lpwfxFormat; GUID guid3DAlgorithm; } DSBUFFERDESC; struct IDirectSoundBuffer; #undef INTERFACE #define INTERFACE IDirectSound DECLARE_INTERFACE_(IDirectSound, IUnknown) { STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID*) PURE; STDMETHOD_(ULONG,AddRef) (THIS) PURE; STDMETHOD_(ULONG,Release) (THIS) PURE; STDMETHOD(CreateSoundBuffer) (THIS_ DSBUFFERDESC*, IDirectSoundBuffer**, LPUNKNOWN) PURE; STDMETHOD(GetCaps) (THIS_ void*) PURE; STDMETHOD(DuplicateSoundBuffer) (THIS_ IDirectSoundBuffer*, IDirectSoundBuffer**) PURE; STDMETHOD(SetCooperativeLevel) (THIS_ HWND, DWORD) PURE; STDMETHOD(Compact) (THIS) PURE; STDMETHOD(GetSpeakerConfig) (THIS_ LPDWORD) PURE; STDMETHOD(SetSpeakerConfig) (THIS_ DWORD) PURE; STDMETHOD(Initialize) (THIS_ const GUID*) PURE; }; #undef INTERFACE #define INTERFACE IDirectSoundBuffer DECLARE_INTERFACE_(IDirectSoundBuffer, IUnknown) { STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID*) PURE; STDMETHOD_(ULONG,AddRef) (THIS) PURE; STDMETHOD_(ULONG,Release) (THIS) PURE; STDMETHOD(GetCaps) (THIS_ void*) PURE; STDMETHOD(GetCurrentPosition) (THIS_ LPDWORD, LPDWORD) PURE; STDMETHOD(GetFormat) (THIS_ LPWAVEFORMATEX, DWORD, LPDWORD) PURE; STDMETHOD(GetVolume) (THIS_ LPLONG) PURE; STDMETHOD(GetPan) (THIS_ LPLONG) PURE; STDMETHOD(GetFrequency) (THIS_ LPDWORD) PURE; STDMETHOD(GetStatus) (THIS_ LPDWORD) PURE; STDMETHOD(Initialize) (THIS_ IDirectSound*, DSBUFFERDESC*) PURE; STDMETHOD(Lock) (THIS_ DWORD, DWORD, LPVOID*, LPDWORD, LPVOID*, LPDWORD, DWORD) PURE; STDMETHOD(Play) (THIS_ DWORD, DWORD, DWORD) PURE; STDMETHOD(SetCurrentPosition) (THIS_ DWORD) PURE; STDMETHOD(SetFormat) (THIS_ const WAVEFORMATEX*) PURE; STDMETHOD(SetVolume) (THIS_ LONG) PURE; STDMETHOD(SetPan) (THIS_ LONG) PURE; STDMETHOD(SetFrequency) (THIS_ DWORD) PURE; STDMETHOD(Stop) (THIS) PURE; STDMETHOD(Unlock) (THIS_ LPVOID, DWORD, LPVOID, DWORD) PURE; STDMETHOD(Restore) (THIS) PURE; }; //============================================================================== typedef struct typeDSCBUFFERDESC { DWORD dwSize; DWORD dwFlags; DWORD dwBufferBytes; DWORD dwReserved; LPWAVEFORMATEX lpwfxFormat; } DSCBUFFERDESC; struct IDirectSoundCaptureBuffer; #undef INTERFACE #define INTERFACE IDirectSoundCapture DECLARE_INTERFACE_(IDirectSoundCapture, IUnknown) { STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID*) PURE; STDMETHOD_(ULONG,AddRef) (THIS) PURE; STDMETHOD_(ULONG,Release) (THIS) PURE; STDMETHOD(CreateCaptureBuffer) (THIS_ DSCBUFFERDESC*, IDirectSoundCaptureBuffer**, LPUNKNOWN) PURE; STDMETHOD(GetCaps) (THIS_ void*) PURE; STDMETHOD(Initialize) (THIS_ const GUID*) PURE; }; #undef INTERFACE #define INTERFACE IDirectSoundCaptureBuffer DECLARE_INTERFACE_(IDirectSoundCaptureBuffer, IUnknown) { STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID*) PURE; STDMETHOD_(ULONG,AddRef) (THIS) PURE; STDMETHOD_(ULONG,Release) (THIS) PURE; STDMETHOD(GetCaps) (THIS_ void*) PURE; STDMETHOD(GetCurrentPosition) (THIS_ LPDWORD, LPDWORD) PURE; STDMETHOD(GetFormat) (THIS_ LPWAVEFORMATEX, DWORD, LPDWORD) PURE; STDMETHOD(GetStatus) (THIS_ LPDWORD) PURE; STDMETHOD(Initialize) (THIS_ IDirectSoundCapture*, DSCBUFFERDESC*) PURE; STDMETHOD(Lock) (THIS_ DWORD, DWORD, LPVOID*, LPDWORD, LPVOID*, LPDWORD, DWORD) PURE; STDMETHOD(Start) (THIS_ DWORD) PURE; STDMETHOD(Stop) (THIS) PURE; STDMETHOD(Unlock) (THIS_ LPVOID, DWORD, LPVOID, DWORD) PURE; }; #undef INTERFACE } namespace juce { //============================================================================== namespace DSoundLogging { String getErrorMessage (HRESULT hr) { const char* result = nullptr; switch (hr) { case MAKE_HRESULT(1, 0x878, 10): result = "Device already allocated"; break; case MAKE_HRESULT(1, 0x878, 30): result = "Control unavailable"; break; case E_INVALIDARG: result = "Invalid parameter"; break; case MAKE_HRESULT(1, 0x878, 50): result = "Invalid call"; break; case E_FAIL: result = "Generic error"; break; case MAKE_HRESULT(1, 0x878, 70): result = "Priority level error"; break; case E_OUTOFMEMORY: result = "Out of memory"; break; case MAKE_HRESULT(1, 0x878, 100): result = "Bad format"; break; case E_NOTIMPL: result = "Unsupported function"; break; case MAKE_HRESULT(1, 0x878, 120): result = "No driver"; break; case MAKE_HRESULT(1, 0x878, 130): result = "Already initialised"; break; case CLASS_E_NOAGGREGATION: result = "No aggregation"; break; case MAKE_HRESULT(1, 0x878, 150): result = "Buffer lost"; break; case MAKE_HRESULT(1, 0x878, 160): result = "Another app has priority"; break; case MAKE_HRESULT(1, 0x878, 170): result = "Uninitialised"; break; case E_NOINTERFACE: result = "No interface"; break; case S_OK: result = "No error"; break; default: return "Unknown error: " + String ((int) hr); } return result; } //============================================================================== #if JUCE_DIRECTSOUND_LOGGING static void logMessage (String message) { message = "DSOUND: " + message; DBG (message); Logger::writeToLog (message); } static void logError (HRESULT hr, int lineNum) { if (FAILED (hr)) { String error ("Error at line "); error << lineNum << ": " << getErrorMessage (hr); logMessage (error); } } #define JUCE_DS_LOG(a) DSoundLogging::logMessage(a); #define JUCE_DS_LOG_ERROR(a) DSoundLogging::logError(a, __LINE__); #else #define JUCE_DS_LOG(a) #define JUCE_DS_LOG_ERROR(a) #endif } //============================================================================== namespace { #define DSOUND_FUNCTION(functionName, params) \ typedef HRESULT (WINAPI *type##functionName) params; \ static type##functionName ds##functionName = nullptr; #define DSOUND_FUNCTION_LOAD(functionName) \ ds##functionName = (type##functionName) GetProcAddress (h, #functionName); \ jassert (ds##functionName != nullptr); typedef BOOL (CALLBACK *LPDSENUMCALLBACKW) (LPGUID, LPCWSTR, LPCWSTR, LPVOID); typedef BOOL (CALLBACK *LPDSENUMCALLBACKA) (LPGUID, LPCSTR, LPCSTR, LPVOID); DSOUND_FUNCTION (DirectSoundCreate, (const GUID*, IDirectSound**, LPUNKNOWN)) DSOUND_FUNCTION (DirectSoundCaptureCreate, (const GUID*, IDirectSoundCapture**, LPUNKNOWN)) DSOUND_FUNCTION (DirectSoundEnumerateW, (LPDSENUMCALLBACKW, LPVOID)) DSOUND_FUNCTION (DirectSoundCaptureEnumerateW, (LPDSENUMCALLBACKW, LPVOID)) void initialiseDSoundFunctions() { if (dsDirectSoundCreate == nullptr) { HMODULE h = LoadLibraryA ("dsound.dll"); DSOUND_FUNCTION_LOAD (DirectSoundCreate) DSOUND_FUNCTION_LOAD (DirectSoundCaptureCreate) DSOUND_FUNCTION_LOAD (DirectSoundEnumerateW) DSOUND_FUNCTION_LOAD (DirectSoundCaptureEnumerateW) } } // the overall size of buffer used is this value x the block size enum { blocksPerOverallBuffer = 16 }; } //============================================================================== class DSoundInternalOutChannel { public: DSoundInternalOutChannel (const String& name_, const GUID& guid_, int rate, int bufferSize, float* left, float* right) : bitDepth (16), name (name_), guid (guid_), sampleRate (rate), bufferSizeSamples (bufferSize), leftBuffer (left), rightBuffer (right), pDirectSound (nullptr), pOutputBuffer (nullptr) { } ~DSoundInternalOutChannel() { close(); } void close() { if (pOutputBuffer != nullptr) { JUCE_DS_LOG ("closing output: " + name); HRESULT hr = pOutputBuffer->Stop(); JUCE_DS_LOG_ERROR (hr); ignoreUnused (hr); pOutputBuffer->Release(); pOutputBuffer = nullptr; } if (pDirectSound != nullptr) { pDirectSound->Release(); pDirectSound = nullptr; } } String open() { JUCE_DS_LOG ("opening output: " + name + " rate=" + String (sampleRate) + " bits=" + String (bitDepth) + " buf=" + String (bufferSizeSamples)); pDirectSound = nullptr; pOutputBuffer = nullptr; writeOffset = 0; String error; HRESULT hr = E_NOINTERFACE; if (dsDirectSoundCreate != nullptr) hr = dsDirectSoundCreate (&guid, &pDirectSound, nullptr); if (SUCCEEDED (hr)) { bytesPerBuffer = (bufferSizeSamples * (bitDepth >> 2)) & ~15; totalBytesPerBuffer = (blocksPerOverallBuffer * bytesPerBuffer) & ~15; const int numChannels = 2; hr = pDirectSound->SetCooperativeLevel (GetDesktopWindow(), 2 /* DSSCL_PRIORITY */); JUCE_DS_LOG_ERROR (hr); if (SUCCEEDED (hr)) { IDirectSoundBuffer* pPrimaryBuffer; DSBUFFERDESC primaryDesc = { 0 }; primaryDesc.dwSize = sizeof (DSBUFFERDESC); primaryDesc.dwFlags = 1 /* DSBCAPS_PRIMARYBUFFER */; primaryDesc.dwBufferBytes = 0; primaryDesc.lpwfxFormat = 0; JUCE_DS_LOG ("co-op level set"); hr = pDirectSound->CreateSoundBuffer (&primaryDesc, &pPrimaryBuffer, 0); JUCE_DS_LOG_ERROR (hr); if (SUCCEEDED (hr)) { WAVEFORMATEX wfFormat; wfFormat.wFormatTag = WAVE_FORMAT_PCM; wfFormat.nChannels = (unsigned short) numChannels; wfFormat.nSamplesPerSec = (DWORD) sampleRate; wfFormat.wBitsPerSample = (unsigned short) bitDepth; wfFormat.nBlockAlign = (unsigned short) (wfFormat.nChannels * wfFormat.wBitsPerSample / 8); wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; wfFormat.cbSize = 0; hr = pPrimaryBuffer->SetFormat (&wfFormat); JUCE_DS_LOG_ERROR (hr); if (SUCCEEDED (hr)) { DSBUFFERDESC secondaryDesc = { 0 }; secondaryDesc.dwSize = sizeof (DSBUFFERDESC); secondaryDesc.dwFlags = 0x8000 /* DSBCAPS_GLOBALFOCUS */ | 0x10000 /* DSBCAPS_GETCURRENTPOSITION2 */; secondaryDesc.dwBufferBytes = (DWORD) totalBytesPerBuffer; secondaryDesc.lpwfxFormat = &wfFormat; hr = pDirectSound->CreateSoundBuffer (&secondaryDesc, &pOutputBuffer, 0); JUCE_DS_LOG_ERROR (hr); if (SUCCEEDED (hr)) { JUCE_DS_LOG ("buffer created"); DWORD dwDataLen; unsigned char* pDSBuffData; hr = pOutputBuffer->Lock (0, (DWORD) totalBytesPerBuffer, (LPVOID*) &pDSBuffData, &dwDataLen, 0, 0, 0); JUCE_DS_LOG_ERROR (hr); if (SUCCEEDED (hr)) { zeromem (pDSBuffData, dwDataLen); hr = pOutputBuffer->Unlock (pDSBuffData, dwDataLen, 0, 0); if (SUCCEEDED (hr)) { hr = pOutputBuffer->SetCurrentPosition (0); if (SUCCEEDED (hr)) { hr = pOutputBuffer->Play (0, 0, 1 /* DSBPLAY_LOOPING */); if (SUCCEEDED (hr)) return String(); } } } } } } } } error = DSoundLogging::getErrorMessage (hr); close(); return error; } void synchronisePosition() { if (pOutputBuffer != nullptr) { DWORD playCursor; pOutputBuffer->GetCurrentPosition (&playCursor, &writeOffset); } } bool service() { if (pOutputBuffer == 0) return true; DWORD playCursor, writeCursor; for (;;) { HRESULT hr = pOutputBuffer->GetCurrentPosition (&playCursor, &writeCursor); if (hr == MAKE_HRESULT (1, 0x878, 150)) // DSERR_BUFFERLOST { pOutputBuffer->Restore(); continue; } if (SUCCEEDED (hr)) break; JUCE_DS_LOG_ERROR (hr); jassertfalse; return true; } int playWriteGap = (int) (writeCursor - playCursor); if (playWriteGap < 0) playWriteGap += totalBytesPerBuffer; int bytesEmpty = (int) (playCursor - writeOffset); if (bytesEmpty < 0) bytesEmpty += totalBytesPerBuffer; if (bytesEmpty > (totalBytesPerBuffer - playWriteGap)) { writeOffset = writeCursor; bytesEmpty = totalBytesPerBuffer - playWriteGap; } if (bytesEmpty >= bytesPerBuffer) { int* buf1 = nullptr; int* buf2 = nullptr; DWORD dwSize1 = 0; DWORD dwSize2 = 0; HRESULT hr = pOutputBuffer->Lock (writeOffset, (DWORD) bytesPerBuffer, (void**) &buf1, &dwSize1, (void**) &buf2, &dwSize2, 0); if (hr == MAKE_HRESULT (1, 0x878, 150)) // DSERR_BUFFERLOST { pOutputBuffer->Restore(); hr = pOutputBuffer->Lock (writeOffset, (DWORD) bytesPerBuffer, (void**) &buf1, &dwSize1, (void**) &buf2, &dwSize2, 0); } if (SUCCEEDED (hr)) { if (bitDepth == 16) { const float* left = leftBuffer; const float* right = rightBuffer; int samples1 = (int) (dwSize1 >> 2); int samples2 = (int) (dwSize2 >> 2); if (left == nullptr) { for (int* dest = buf1; --samples1 >= 0;) *dest++ = convertInputValues (0, *right++); for (int* dest = buf2; --samples2 >= 0;) *dest++ = convertInputValues (0, *right++); } else if (right == nullptr) { for (int* dest = buf1; --samples1 >= 0;) *dest++ = convertInputValues (*left++, 0); for (int* dest = buf2; --samples2 >= 0;) *dest++ = convertInputValues (*left++, 0); } else { for (int* dest = buf1; --samples1 >= 0;) *dest++ = convertInputValues (*left++, *right++); for (int* dest = buf2; --samples2 >= 0;) *dest++ = convertInputValues (*left++, *right++); } } else { jassertfalse; } writeOffset = (writeOffset + dwSize1 + dwSize2) % totalBytesPerBuffer; pOutputBuffer->Unlock (buf1, dwSize1, buf2, dwSize2); } else { jassertfalse; JUCE_DS_LOG_ERROR (hr); } bytesEmpty -= bytesPerBuffer; return true; } else { return false; } } int bitDepth; bool doneFlag; private: String name; GUID guid; int sampleRate, bufferSizeSamples; float* leftBuffer; float* rightBuffer; IDirectSound* pDirectSound; IDirectSoundBuffer* pOutputBuffer; DWORD writeOffset; int totalBytesPerBuffer, bytesPerBuffer; unsigned int lastPlayCursor; static inline int convertInputValues (const float l, const float r) noexcept { return jlimit (-32768, 32767, roundToInt (32767.0f * r)) << 16 | (0xffff & jlimit (-32768, 32767, roundToInt (32767.0f * l))); } JUCE_DECLARE_NON_COPYABLE (DSoundInternalOutChannel) }; //============================================================================== struct DSoundInternalInChannel { public: DSoundInternalInChannel (const String& name_, const GUID& guid_, int rate, int bufferSize, float* left, float* right) : bitDepth (16), name (name_), guid (guid_), sampleRate (rate), bufferSizeSamples (bufferSize), leftBuffer (left), rightBuffer (right), pDirectSound (nullptr), pDirectSoundCapture (nullptr), pInputBuffer (nullptr) { } ~DSoundInternalInChannel() { close(); } void close() { if (pInputBuffer != nullptr) { JUCE_DS_LOG ("closing input: " + name); HRESULT hr = pInputBuffer->Stop(); JUCE_DS_LOG_ERROR (hr); ignoreUnused (hr); pInputBuffer->Release(); pInputBuffer = nullptr; } if (pDirectSoundCapture != nullptr) { pDirectSoundCapture->Release(); pDirectSoundCapture = nullptr; } if (pDirectSound != nullptr) { pDirectSound->Release(); pDirectSound = nullptr; } } String open() { JUCE_DS_LOG ("opening input: " + name + " rate=" + String (sampleRate) + " bits=" + String (bitDepth) + " buf=" + String (bufferSizeSamples)); pDirectSound = nullptr; pDirectSoundCapture = nullptr; pInputBuffer = nullptr; readOffset = 0; totalBytesPerBuffer = 0; HRESULT hr = dsDirectSoundCaptureCreate != nullptr ? dsDirectSoundCaptureCreate (&guid, &pDirectSoundCapture, nullptr) : E_NOINTERFACE; if (SUCCEEDED (hr)) { const int numChannels = 2; bytesPerBuffer = (bufferSizeSamples * (bitDepth >> 2)) & ~15; totalBytesPerBuffer = (blocksPerOverallBuffer * bytesPerBuffer) & ~15; WAVEFORMATEX wfFormat; wfFormat.wFormatTag = WAVE_FORMAT_PCM; wfFormat.nChannels = (unsigned short)numChannels; wfFormat.nSamplesPerSec = (DWORD) sampleRate; wfFormat.wBitsPerSample = (unsigned short) bitDepth; wfFormat.nBlockAlign = (unsigned short) (wfFormat.nChannels * (wfFormat.wBitsPerSample / 8)); wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; wfFormat.cbSize = 0; DSCBUFFERDESC captureDesc = { 0 }; captureDesc.dwSize = sizeof (DSCBUFFERDESC); captureDesc.dwFlags = 0; captureDesc.dwBufferBytes = (DWORD) totalBytesPerBuffer; captureDesc.lpwfxFormat = &wfFormat; JUCE_DS_LOG ("object created"); hr = pDirectSoundCapture->CreateCaptureBuffer (&captureDesc, &pInputBuffer, 0); if (SUCCEEDED (hr)) { hr = pInputBuffer->Start (1 /* DSCBSTART_LOOPING */); if (SUCCEEDED (hr)) return String(); } } JUCE_DS_LOG_ERROR (hr); const String error (DSoundLogging::getErrorMessage (hr)); close(); return error; } void synchronisePosition() { if (pInputBuffer != nullptr) { DWORD capturePos; pInputBuffer->GetCurrentPosition (&capturePos, (DWORD*) &readOffset); } } bool service() { if (pInputBuffer == 0) return true; DWORD capturePos, readPos; HRESULT hr = pInputBuffer->GetCurrentPosition (&capturePos, &readPos); JUCE_DS_LOG_ERROR (hr); if (FAILED (hr)) return true; int bytesFilled = (int) (readPos - readOffset); if (bytesFilled < 0) bytesFilled += totalBytesPerBuffer; if (bytesFilled >= bytesPerBuffer) { short* buf1 = nullptr; short* buf2 = nullptr; DWORD dwsize1 = 0; DWORD dwsize2 = 0; hr = pInputBuffer->Lock ((DWORD) readOffset, (DWORD) bytesPerBuffer, (void**) &buf1, &dwsize1, (void**) &buf2, &dwsize2, 0); if (SUCCEEDED (hr)) { if (bitDepth == 16) { const float g = 1.0f / 32768.0f; float* destL = leftBuffer; float* destR = rightBuffer; int samples1 = (int) (dwsize1 >> 2); int samples2 = (int) (dwsize2 >> 2); if (destL == nullptr) { for (const short* src = buf1; --samples1 >= 0;) { ++src; *destR++ = *src++ * g; } for (const short* src = buf2; --samples2 >= 0;) { ++src; *destR++ = *src++ * g; } } else if (destR == nullptr) { for (const short* src = buf1; --samples1 >= 0;) { *destL++ = *src++ * g; ++src; } for (const short* src = buf2; --samples2 >= 0;) { *destL++ = *src++ * g; ++src; } } else { for (const short* src = buf1; --samples1 >= 0;) { *destL++ = *src++ * g; *destR++ = *src++ * g; } for (const short* src = buf2; --samples2 >= 0;) { *destL++ = *src++ * g; *destR++ = *src++ * g; } } } else { jassertfalse; } readOffset = (readOffset + dwsize1 + dwsize2) % totalBytesPerBuffer; pInputBuffer->Unlock (buf1, dwsize1, buf2, dwsize2); } else { JUCE_DS_LOG_ERROR (hr); jassertfalse; } bytesFilled -= bytesPerBuffer; return true; } else { return false; } } unsigned int readOffset; int bytesPerBuffer, totalBytesPerBuffer; int bitDepth; bool doneFlag; private: String name; GUID guid; int sampleRate, bufferSizeSamples; float* leftBuffer; float* rightBuffer; IDirectSound* pDirectSound; IDirectSoundCapture* pDirectSoundCapture; IDirectSoundCaptureBuffer* pInputBuffer; JUCE_DECLARE_NON_COPYABLE (DSoundInternalInChannel) }; //============================================================================== class DSoundAudioIODevice : public AudioIODevice, public Thread { public: DSoundAudioIODevice (const String& deviceName, const int outputDeviceIndex_, const int inputDeviceIndex_) : AudioIODevice (deviceName, "DirectSound"), Thread ("Juce DSound"), outputDeviceIndex (outputDeviceIndex_), inputDeviceIndex (inputDeviceIndex_), isOpen_ (false), isStarted (false), bufferSizeSamples (0), sampleRate (0.0), callback (nullptr) { if (outputDeviceIndex_ >= 0) { outChannels.add (TRANS("Left")); outChannels.add (TRANS("Right")); } if (inputDeviceIndex_ >= 0) { inChannels.add (TRANS("Left")); inChannels.add (TRANS("Right")); } } ~DSoundAudioIODevice() { close(); } String open (const BigInteger& inputChannels, const BigInteger& outputChannels, double newSampleRate, int newBufferSize) override { lastError = openDevice (inputChannels, outputChannels, newSampleRate, newBufferSize); isOpen_ = lastError.isEmpty(); return lastError; } void close() override { stop(); if (isOpen_) { closeDevice(); isOpen_ = false; } } bool isOpen() override { return isOpen_ && isThreadRunning(); } int getCurrentBufferSizeSamples() override { return bufferSizeSamples; } double getCurrentSampleRate() override { return sampleRate; } BigInteger getActiveOutputChannels() const override { return enabledOutputs; } BigInteger getActiveInputChannels() const override { return enabledInputs; } int getOutputLatencyInSamples() override { return (int) (getCurrentBufferSizeSamples() * 1.5); } int getInputLatencyInSamples() override { return getOutputLatencyInSamples(); } StringArray getOutputChannelNames() override { return outChannels; } StringArray getInputChannelNames() override { return inChannels; } Array getAvailableSampleRates() override { static const double rates[] = { 44100.0, 48000.0, 88200.0, 96000.0 }; return Array (rates, numElementsInArray (rates)); } Array getAvailableBufferSizes() override { Array r; int n = 64; for (int i = 0; i < 50; ++i) { r.add (n); n += (n < 512) ? 32 : ((n < 1024) ? 64 : ((n < 2048) ? 128 : 256)); } return r; } int getDefaultBufferSize() override { return 2560; } int getCurrentBitDepth() override { int bits = 256; for (int i = inChans.size(); --i >= 0;) bits = jmin (bits, inChans[i]->bitDepth); for (int i = outChans.size(); --i >= 0;) bits = jmin (bits, outChans[i]->bitDepth); if (bits > 32) bits = 16; return bits; } void start (AudioIODeviceCallback* call) override { if (isOpen_ && call != nullptr && ! isStarted) { if (! isThreadRunning()) { // something gone wrong and the thread's stopped.. isOpen_ = false; return; } call->audioDeviceAboutToStart (this); const ScopedLock sl (startStopLock); callback = call; isStarted = true; } } void stop() override { if (isStarted) { AudioIODeviceCallback* const callbackLocal = callback; { const ScopedLock sl (startStopLock); isStarted = false; } if (callbackLocal != nullptr) callbackLocal->audioDeviceStopped(); } } bool isPlaying() override { return isStarted && isOpen_ && isThreadRunning(); } String getLastError() override { return lastError; } //============================================================================== StringArray inChannels, outChannels; int outputDeviceIndex, inputDeviceIndex; private: bool isOpen_; bool isStarted; String lastError; OwnedArray inChans; OwnedArray outChans; WaitableEvent startEvent; int bufferSizeSamples; double sampleRate; BigInteger enabledInputs, enabledOutputs; AudioSampleBuffer inputBuffers, outputBuffers; AudioIODeviceCallback* callback; CriticalSection startStopLock; String openDevice (const BigInteger& inputChannels, const BigInteger& outputChannels, double sampleRate_, int bufferSizeSamples_); void closeDevice() { isStarted = false; stopThread (5000); inChans.clear(); outChans.clear(); inputBuffers.setSize (1, 1); outputBuffers.setSize (1, 1); } void resync() { if (! threadShouldExit()) { sleep (5); for (int i = 0; i < outChans.size(); ++i) outChans.getUnchecked(i)->synchronisePosition(); for (int i = 0; i < inChans.size(); ++i) inChans.getUnchecked(i)->synchronisePosition(); } } public: void run() override { while (! threadShouldExit()) { if (wait (100)) break; } const int latencyMs = (int) (bufferSizeSamples * 1000.0 / sampleRate); const int maxTimeMS = jmax (5, 3 * latencyMs); while (! threadShouldExit()) { int numToDo = 0; uint32 startTime = Time::getMillisecondCounter(); for (int i = inChans.size(); --i >= 0;) { inChans.getUnchecked(i)->doneFlag = false; ++numToDo; } for (int i = outChans.size(); --i >= 0;) { outChans.getUnchecked(i)->doneFlag = false; ++numToDo; } if (numToDo > 0) { const int maxCount = 3; int count = maxCount; for (;;) { for (int i = inChans.size(); --i >= 0;) { DSoundInternalInChannel* const in = inChans.getUnchecked(i); if ((! in->doneFlag) && in->service()) { in->doneFlag = true; --numToDo; } } for (int i = outChans.size(); --i >= 0;) { DSoundInternalOutChannel* const out = outChans.getUnchecked(i); if ((! out->doneFlag) && out->service()) { out->doneFlag = true; --numToDo; } } if (numToDo <= 0) break; if (Time::getMillisecondCounter() > startTime + maxTimeMS) { resync(); break; } if (--count <= 0) { Sleep (1); count = maxCount; } if (threadShouldExit()) return; } } else { sleep (1); } const ScopedLock sl (startStopLock); if (isStarted) { callback->audioDeviceIOCallback (inputBuffers.getArrayOfReadPointers(), inputBuffers.getNumChannels(), outputBuffers.getArrayOfWritePointers(), outputBuffers.getNumChannels(), bufferSizeSamples); } else { outputBuffers.clear(); sleep (1); } } } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODevice) }; //============================================================================== struct DSoundDeviceList { StringArray outputDeviceNames, inputDeviceNames; Array outputGuids, inputGuids; void scan() { outputDeviceNames.clear(); inputDeviceNames.clear(); outputGuids.clear(); inputGuids.clear(); if (dsDirectSoundEnumerateW != 0) { dsDirectSoundEnumerateW (outputEnumProcW, this); dsDirectSoundCaptureEnumerateW (inputEnumProcW, this); } } bool operator!= (const DSoundDeviceList& other) const noexcept { return outputDeviceNames != other.outputDeviceNames || inputDeviceNames != other.inputDeviceNames || outputGuids != other.outputGuids || inputGuids != other.inputGuids; } private: static BOOL enumProc (LPGUID lpGUID, String desc, StringArray& names, Array& guids) { desc = desc.trim(); if (desc.isNotEmpty()) { const String origDesc (desc); int n = 2; while (names.contains (desc)) desc = origDesc + " (" + String (n++) + ")"; names.add (desc); guids.add (lpGUID != nullptr ? *lpGUID : GUID()); } return TRUE; } BOOL outputEnumProc (LPGUID guid, LPCWSTR desc) { return enumProc (guid, desc, outputDeviceNames, outputGuids); } BOOL inputEnumProc (LPGUID guid, LPCWSTR desc) { return enumProc (guid, desc, inputDeviceNames, inputGuids); } static BOOL CALLBACK outputEnumProcW (LPGUID lpGUID, LPCWSTR description, LPCWSTR, LPVOID object) { return static_cast (object)->outputEnumProc (lpGUID, description); } static BOOL CALLBACK inputEnumProcW (LPGUID lpGUID, LPCWSTR description, LPCWSTR, LPVOID object) { return static_cast (object)->inputEnumProc (lpGUID, description); } }; //============================================================================== String DSoundAudioIODevice::openDevice (const BigInteger& inputChannels, const BigInteger& outputChannels, double sampleRate_, int bufferSizeSamples_) { closeDevice(); sampleRate = sampleRate_; if (bufferSizeSamples_ <= 0) bufferSizeSamples_ = 960; // use as a default size if none is set. bufferSizeSamples = bufferSizeSamples_ & ~7; DSoundDeviceList dlh; dlh.scan(); enabledInputs = inputChannels; enabledInputs.setRange (inChannels.size(), enabledInputs.getHighestBit() + 1 - inChannels.size(), false); inputBuffers.setSize (jmax (1, enabledInputs.countNumberOfSetBits()), bufferSizeSamples); inputBuffers.clear(); int numIns = 0; for (int i = 0; i <= enabledInputs.getHighestBit(); i += 2) { float* left = enabledInputs[i] ? inputBuffers.getWritePointer (numIns++) : nullptr; float* right = enabledInputs[i + 1] ? inputBuffers.getWritePointer (numIns++) : nullptr; if (left != nullptr || right != nullptr) inChans.add (new DSoundInternalInChannel (dlh.inputDeviceNames [inputDeviceIndex], dlh.inputGuids [inputDeviceIndex], (int) sampleRate, bufferSizeSamples, left, right)); } enabledOutputs = outputChannels; enabledOutputs.setRange (outChannels.size(), enabledOutputs.getHighestBit() + 1 - outChannels.size(), false); outputBuffers.setSize (jmax (1, enabledOutputs.countNumberOfSetBits()), bufferSizeSamples); outputBuffers.clear(); int numOuts = 0; for (int i = 0; i <= enabledOutputs.getHighestBit(); i += 2) { float* left = enabledOutputs[i] ? outputBuffers.getWritePointer (numOuts++) : nullptr; float* right = enabledOutputs[i + 1] ? outputBuffers.getWritePointer (numOuts++) : nullptr; if (left != nullptr || right != nullptr) outChans.add (new DSoundInternalOutChannel (dlh.outputDeviceNames[outputDeviceIndex], dlh.outputGuids [outputDeviceIndex], (int) sampleRate, bufferSizeSamples, left, right)); } String error; // boost our priority while opening the devices to try to get better sync between them const int oldThreadPri = GetThreadPriority (GetCurrentThread()); const DWORD oldProcPri = GetPriorityClass (GetCurrentProcess()); SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); SetPriorityClass (GetCurrentProcess(), REALTIME_PRIORITY_CLASS); for (int i = 0; i < outChans.size(); ++i) { error = outChans[i]->open(); if (error.isNotEmpty()) { error = "Error opening " + dlh.outputDeviceNames[i] + ": \"" + error + "\""; break; } } if (error.isEmpty()) { for (int i = 0; i < inChans.size(); ++i) { error = inChans[i]->open(); if (error.isNotEmpty()) { error = "Error opening " + dlh.inputDeviceNames[i] + ": \"" + error + "\""; break; } } } if (error.isEmpty()) { for (int i = 0; i < outChans.size(); ++i) outChans.getUnchecked(i)->synchronisePosition(); for (int i = 0; i < inChans.size(); ++i) inChans.getUnchecked(i)->synchronisePosition(); startThread (9); sleep (10); notify(); } else { JUCE_DS_LOG ("Opening failed: " + error); } SetThreadPriority (GetCurrentThread(), oldThreadPri); SetPriorityClass (GetCurrentProcess(), oldProcPri); return error; } //============================================================================== class DSoundAudioIODeviceType : public AudioIODeviceType, private DeviceChangeDetector { public: DSoundAudioIODeviceType() : AudioIODeviceType ("DirectSound"), DeviceChangeDetector (L"DirectSound"), hasScanned (false) { initialiseDSoundFunctions(); } void scanForDevices() { hasScanned = true; deviceList.scan(); } StringArray getDeviceNames (bool wantInputNames) const { jassert (hasScanned); // need to call scanForDevices() before doing this return wantInputNames ? deviceList.inputDeviceNames : deviceList.outputDeviceNames; } int getDefaultDeviceIndex (bool /*forInput*/) const { jassert (hasScanned); // need to call scanForDevices() before doing this return 0; } int getIndexOfDevice (AudioIODevice* device, bool asInput) const { jassert (hasScanned); // need to call scanForDevices() before doing this if (DSoundAudioIODevice* const d = dynamic_cast (device)) return asInput ? d->inputDeviceIndex : d->outputDeviceIndex; return -1; } bool hasSeparateInputsAndOutputs() const { return true; } AudioIODevice* createDevice (const String& outputDeviceName, const String& inputDeviceName) { jassert (hasScanned); // need to call scanForDevices() before doing this const int outputIndex = deviceList.outputDeviceNames.indexOf (outputDeviceName); const int inputIndex = deviceList.inputDeviceNames.indexOf (inputDeviceName); if (outputIndex >= 0 || inputIndex >= 0) return new DSoundAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName : inputDeviceName, outputIndex, inputIndex); return nullptr; } private: DSoundDeviceList deviceList; bool hasScanned; void systemDeviceChanged() override { DSoundDeviceList newList; newList.scan(); if (newList != deviceList) { deviceList = newList; callDeviceChangeListeners(); } } JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType) }; //============================================================================== AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return new DSoundAudioIODeviceType(); }