You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
dexed/JuceLibraryCode/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp

1277 lines
44 KiB

/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2013 - Raw Material Software 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 CATCH JUCE_CATCH_EXCEPTION
#define JUCE_DS_LOG(a) DSoundLogging::logMessage(a);
#define JUCE_DS_LOG_ERROR(a) DSoundLogging::logError(a, __LINE__);
#else
#define CATCH JUCE_CATCH_ALL
#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); (void) 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::empty;
}
}
}
}
}
}
}
}
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); (void) 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::empty;
}
}
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;
HRESULT 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 sampleRate, int bufferSizeSamples) override
{
lastError = openDevice (inputChannels, outputChannels, sampleRate, bufferSizeSamples);
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
{
static const double rates[] = { 44100.0, 48000.0, 88200.0, 96000.0 };
return Array<double> (rates, numElementsInArray (rates));
}
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)
{
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<DSoundInternalInChannel> inChans;
OwnedArray<DSoundInternalOutChannel> 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<GUID> 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<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_;
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 <DSoundAudioIODevice*> (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();
}