|
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295 |
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2022 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- The code included in this file is provided under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license. Permission
- To use, copy, modify, and/or distribute this software for any purpose with or
- without fee is hereby granted provided that the above copyright notice and
- this permission notice appear in all copies.
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- extern "C"
- {
- // Declare just the minimum number of interfaces for the DSound objects that we need..
- struct DSBUFFERDESC
- {
- DWORD dwSize;
- DWORD dwFlags;
- DWORD dwBufferBytes;
- DWORD dwReserved;
- LPWAVEFORMATEX lpwfxFormat;
- GUID guid3DAlgorithm;
- };
-
- 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;
- };
-
- //==============================================================================
- struct DSCBUFFERDESC
- {
- DWORD dwSize;
- DWORD dwFlags;
- DWORD dwBufferBytes;
- DWORD dwReserved;
- LPWAVEFORMATEX lpwfxFormat;
- };
-
- 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
- {
- static 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) \
- JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wcast-function-type") \
- ds##functionName = (type##functionName) GetProcAddress (h, #functionName); \
- JUCE_END_IGNORE_WARNINGS_GCC_LIKE \
- 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)
- {
- if (auto* h = LoadLibraryA ("dsound.dll"))
- {
- DSOUND_FUNCTION_LOAD (DirectSoundCreate)
- DSOUND_FUNCTION_LOAD (DirectSoundCaptureCreate)
- DSOUND_FUNCTION_LOAD (DirectSoundEnumerateW)
- DSOUND_FUNCTION_LOAD (DirectSoundCaptureEnumerateW)
-
- return;
- }
-
- jassertfalse;
- }
- }
-
- // 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;
- xruns = 0;
-
- firstPlayTime = true;
- lastPlayTime = 0;
-
- String error;
- HRESULT hr = E_NOINTERFACE;
-
- if (dsDirectSoundCreate != nullptr)
- hr = dsDirectSoundCreate (&guid, &pDirectSound, nullptr);
-
- if (SUCCEEDED (hr))
- {
- bytesPerBuffer = (bufferSizeSamples * (bitDepth >> 2)) & ~15;
- ticksPerBuffer = bytesPerBuffer * Time::getHighResolutionTicksPerSecond() / (sampleRate * (bitDepth >> 2));
- 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 = {};
- primaryDesc.dwSize = sizeof (DSBUFFERDESC);
- primaryDesc.dwFlags = 1 /* DSBCAPS_PRIMARYBUFFER */;
- primaryDesc.dwBufferBytes = 0;
- primaryDesc.lpwfxFormat = nullptr;
-
- JUCE_DS_LOG ("co-op level set");
- hr = pDirectSound->CreateSoundBuffer (&primaryDesc, &pPrimaryBuffer, nullptr);
- 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 = {};
- secondaryDesc.dwSize = sizeof (DSBUFFERDESC);
- secondaryDesc.dwFlags = 0x8000 /* DSBCAPS_GLOBALFOCUS */
- | 0x10000 /* DSBCAPS_GETCURRENTPOSITION2 */;
- secondaryDesc.dwBufferBytes = (DWORD) totalBytesPerBuffer;
- secondaryDesc.lpwfxFormat = &wfFormat;
-
- hr = pDirectSound->CreateSoundBuffer (&secondaryDesc, &pOutputBuffer, nullptr);
- 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, nullptr, nullptr, 0);
- JUCE_DS_LOG_ERROR (hr);
-
- if (SUCCEEDED (hr))
- {
- zeromem (pDSBuffData, dwDataLen);
-
- hr = pOutputBuffer->Unlock (pDSBuffData, dwDataLen, nullptr, 0);
-
- if (SUCCEEDED (hr))
- {
- hr = pOutputBuffer->SetCurrentPosition (0);
-
- if (SUCCEEDED (hr))
- {
- hr = pOutputBuffer->Play (0, 0, 1 /* DSBPLAY_LOOPING */);
-
- if (SUCCEEDED (hr))
- return {};
- }
- }
- }
- }
- }
- }
- }
- }
-
- error = DSoundLogging::getErrorMessage (hr);
- close();
- return error;
- }
-
- void synchronisePosition()
- {
- if (pOutputBuffer != nullptr)
- {
- DWORD playCursor;
- pOutputBuffer->GetCurrentPosition (&playCursor, &writeOffset);
- }
- }
-
- bool service()
- {
- if (pOutputBuffer == nullptr)
- 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;
- }
-
- auto currentPlayTime = Time::getHighResolutionTicks();
- if (! firstPlayTime)
- {
- auto expectedBuffers = (currentPlayTime - lastPlayTime) / ticksPerBuffer;
-
- playCursor += static_cast<DWORD> (expectedBuffers * bytesPerBuffer);
- }
- else
- firstPlayTime = false;
-
- lastPlayTime = currentPlayTime;
-
- 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;
-
- // buffer underflow
- xruns++;
- }
-
- 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) % (DWORD) totalBytesPerBuffer;
-
- pOutputBuffer->Unlock (buf1, dwSize1, buf2, dwSize2);
- }
- else
- {
- jassertfalse;
- JUCE_DS_LOG_ERROR (hr);
- }
-
- bytesEmpty -= bytesPerBuffer;
- return true;
- }
- else
- {
- return false;
- }
- }
-
- int bitDepth, xruns;
- bool doneFlag;
-
- private:
- String name;
- GUID guid;
- int sampleRate, bufferSizeSamples;
- float* leftBuffer;
- float* rightBuffer;
-
- IDirectSound* pDirectSound;
- IDirectSoundBuffer* pOutputBuffer;
- DWORD writeOffset;
- int totalBytesPerBuffer, bytesPerBuffer;
-
- bool firstPlayTime;
- int64 lastPlayTime, ticksPerBuffer;
-
- static 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)
- : name (name_), guid (guid_), sampleRate (rate),
- bufferSizeSamples (bufferSize), leftBuffer (left), rightBuffer (right)
- {
- }
-
- ~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 = {};
- captureDesc.dwSize = sizeof (DSCBUFFERDESC);
- captureDesc.dwFlags = 0;
- captureDesc.dwBufferBytes = (DWORD) totalBytesPerBuffer;
- captureDesc.lpwfxFormat = &wfFormat;
-
- JUCE_DS_LOG ("object created");
- hr = pDirectSoundCapture->CreateCaptureBuffer (&captureDesc, &pInputBuffer, nullptr);
-
- if (SUCCEEDED (hr))
- {
- hr = pInputBuffer->Start (1 /* DSCBSTART_LOOPING */);
-
- if (SUCCEEDED (hr))
- return {};
- }
- }
-
- 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 == nullptr)
- 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) % (DWORD) 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 = 16;
- bool doneFlag;
-
- private:
- String name;
- GUID guid;
- int sampleRate, bufferSizeSamples;
- float* leftBuffer;
- float* rightBuffer;
-
- IDirectSound* pDirectSound = nullptr;
- IDirectSoundCapture* pDirectSoundCapture = nullptr;
- IDirectSoundCaptureBuffer* pInputBuffer = nullptr;
-
- 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_)
- {
- if (outputDeviceIndex_ >= 0)
- {
- outChannels.add (TRANS("Left"));
- outChannels.add (TRANS("Right"));
- }
-
- if (inputDeviceIndex_ >= 0)
- {
- inChannels.add (TRANS("Left"));
- inChannels.add (TRANS("Right"));
- }
- }
-
- ~DSoundAudioIODevice() override
- {
- 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<double> getAvailableSampleRates() override
- {
- return { 44100.0, 48000.0, 88200.0, 96000.0 };
- }
-
- Array<int> getAvailableBufferSizes() override
- {
- Array<int> 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)
- {
- auto* 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; }
-
- int getXRunCount() const noexcept override
- {
- return outChans[0] != nullptr ? outChans[0]->xruns : -1;
- }
-
- //==============================================================================
- StringArray inChannels, outChannels;
- int outputDeviceIndex, inputDeviceIndex;
-
- private:
- bool isOpen_ = false;
- bool isStarted = false;
- String lastError;
-
- OwnedArray<DSoundInternalInChannel> inChans;
- OwnedArray<DSoundInternalOutChannel> outChans;
- WaitableEvent startEvent;
-
- int bufferSizeSamples = 0;
- double sampleRate = 0;
- BigInteger enabledInputs, enabledOutputs;
- AudioBuffer<float> inputBuffers, outputBuffers;
-
- AudioIODeviceCallback* callback = nullptr;
- CriticalSection startStopLock;
-
- String openDevice (const BigInteger& inputChannels,
- const BigInteger& outputChannels,
- double sampleRate_, int bufferSizeSamples_);
-
- void closeDevice()
- {
- isStarted = false;
- stopThread (5000);
-
- inChans.clear();
- outChans.clear();
- }
-
- 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 auto latencyMs = (uint32) (bufferSizeSamples * 1000.0 / sampleRate);
- const auto maxTimeMS = jmax ((uint32) 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->audioDeviceIOCallbackWithContext (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<GUID> outputGuids, inputGuids;
-
- void scan()
- {
- outputDeviceNames.clear();
- inputDeviceNames.clear();
- outputGuids.clear();
- inputGuids.clear();
-
- if (dsDirectSoundEnumerateW != nullptr)
- {
- 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<GUID>& 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<DSoundDeviceList*> (object)->outputEnumProc (lpGUID, description);
- }
-
- static BOOL CALLBACK inputEnumProcW (LPGUID lpGUID, LPCWSTR description, LPCWSTR, LPVOID object)
- {
- return static_cast<DSoundDeviceList*> (object)->inputEnumProc (lpGUID, description);
- }
- };
-
- //==============================================================================
- String DSoundAudioIODevice::openDevice (const BigInteger& inputChannels,
- const BigInteger& outputChannels,
- double sampleRate_, int bufferSizeSamples_)
- {
- closeDevice();
-
- sampleRate = sampleRate_ > 0.0 ? sampleRate_ : 44100.0;
-
- 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 (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 (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")
- {
- initialiseDSoundFunctions();
- }
-
- void scanForDevices() override
- {
- hasScanned = true;
- deviceList.scan();
- }
-
- StringArray getDeviceNames (bool wantInputNames) const override
- {
- jassert (hasScanned); // need to call scanForDevices() before doing this
-
- return wantInputNames ? deviceList.inputDeviceNames
- : deviceList.outputDeviceNames;
- }
-
- int getDefaultDeviceIndex (bool /*forInput*/) const override
- {
- jassert (hasScanned); // need to call scanForDevices() before doing this
- return 0;
- }
-
- int getIndexOfDevice (AudioIODevice* device, bool asInput) const override
- {
- jassert (hasScanned); // need to call scanForDevices() before doing this
-
- if (DSoundAudioIODevice* const d = dynamic_cast<DSoundAudioIODevice*> (device))
- return asInput ? d->inputDeviceIndex
- : d->outputDeviceIndex;
-
- return -1;
- }
-
- bool hasSeparateInputsAndOutputs() const override { return true; }
-
- AudioIODevice* createDevice (const String& outputDeviceName,
- const String& inputDeviceName) override
- {
- 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 = false;
-
- void systemDeviceChanged() override
- {
- DSoundDeviceList newList;
- newList.scan();
-
- if (newList != deviceList)
- {
- deviceList = newList;
- callDeviceChangeListeners();
- }
- }
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType)
- };
-
- } // namespace juce
|