1293 lines
44 KiB
C++
1293 lines
44 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2017 - ROLI Ltd.
|
|
|
|
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
|
|
{
|
|
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;
|
|
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 = { 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 {};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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) % 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;
|
|
unsigned int lastPlayCursor;
|
|
|
|
bool firstPlayTime;
|
|
int64 lastPlayTime, ticksPerBuffer;
|
|
|
|
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)
|
|
: 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 = { 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 {};
|
|
}
|
|
}
|
|
|
|
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 = 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()
|
|
{
|
|
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 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 (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()
|
|
{
|
|
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 = false;
|
|
|
|
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();
|
|
}
|
|
|
|
} // namespace juce
|