|  | /*
  ==============================================================================
   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-11 by Raw Material Software Ltd.
  ------------------------------------------------------------------------------
   JUCE can be redistributed and/or modified under the terms of the GNU General
   Public License (Version 2), as published by the Free Software Foundation.
   A copy of the license is included in the JUCE distribution, or can be found
   online 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.rawmaterialsoftware.com/juce 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;
    };
}
//==============================================================================
namespace juce
{
namespace
{
    String getDSErrorMessage (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;
    }
    //==============================================================================
    #define DS_DEBUGGING 1
   #ifdef DS_DEBUGGING
    #define CATCH JUCE_CATCH_EXCEPTION
    #undef log
    #define log(a) Logger::writeToLog(a);
    #undef logError
    #define logError(a) logDSError(a, __LINE__);
    static void logDSError (HRESULT hr, int lineNum)
    {
        if (FAILED (hr))
        {
            String error ("DS error at line ");
            error << lineNum << " - " << getDSErrorMessage (hr);
            log (error);
        }
    }
   #else
    #define CATCH JUCE_CATCH_ALL
    #define log(a)
    #define logError(a)
   #endif
    //==============================================================================
    #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)
        }
    }
}
//==============================================================================
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)
        {
            log ("closing dsound out: " + name);
            HRESULT hr = pOutputBuffer->Stop();
            logError (hr);
            pOutputBuffer->Release();
            pOutputBuffer = nullptr;
        }
        if (pDirectSound != nullptr)
        {
            pDirectSound->Release();
            pDirectSound = nullptr;
        }
    }
    String open()
    {
        log ("opening dsound out device: " + 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 = (3 * bytesPerBuffer) & ~15;
            const int numChannels = 2;
            hr = pDirectSound->SetCooperativeLevel (GetDesktopWindow(), 2 /* DSSCL_PRIORITY  */);
            logError (hr);
            if (SUCCEEDED (hr))
            {
                IDirectSoundBuffer* pPrimaryBuffer;
                DSBUFFERDESC primaryDesc = { 0 };
                primaryDesc.dwSize = sizeof (DSBUFFERDESC);
                primaryDesc.dwFlags = 1 /* DSBCAPS_PRIMARYBUFFER */;
                primaryDesc.dwBufferBytes = 0;
                primaryDesc.lpwfxFormat = 0;
                log ("opening dsound out step 2");
                hr = pDirectSound->CreateSoundBuffer (&primaryDesc, &pPrimaryBuffer, 0);
                logError (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);
                    logError (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);
                        logError (hr);
                        if (SUCCEEDED (hr))
                        {
                            log ("opening dsound out step 3");
                            DWORD dwDataLen;
                            unsigned char* pDSBuffData;
                            hr = pOutputBuffer->Lock (0, (DWORD) totalBytesPerBuffer,
                                                      (LPVOID*) &pDSBuffData, &dwDataLen, 0, 0, 0);
                            logError (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 = getDSErrorMessage (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;
            logError (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;
                logError (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()
    {
        HRESULT hr;
        if (pInputBuffer != nullptr)
        {
            log ("closing dsound in: " + name);
            hr = pInputBuffer->Stop();
            logError (hr);
            pInputBuffer->Release();
            pInputBuffer = nullptr;
        }
        if (pDirectSoundCapture != nullptr)
        {
            pDirectSoundCapture->Release();
            pDirectSoundCapture = nullptr;
        }
        if (pDirectSound != nullptr)
        {
            pDirectSound->Release();
            pDirectSound = nullptr;
        }
    }
    String open()
    {
        log ("opening dsound in device: " + 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 = (3 * 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;
            log ("opening dsound in step 2");
            hr = pDirectSoundCapture->CreateCaptureBuffer (&captureDesc, &pInputBuffer, 0);
            if (SUCCEEDED (hr))
            {
                hr = pInputBuffer->Start (1 /* DSCBSTART_LOOPING */);
                if (SUCCEEDED (hr))
                    return String::empty;
            }
        }
        logError (hr);
        const String error (getDSErrorMessage (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);
        logError (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
            {
                logError (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),
          inputBuffers (1, 1),
          outputBuffers (1, 1),
          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)
    {
        lastError = openDevice (inputChannels, outputChannels, sampleRate, bufferSizeSamples);
        isOpen_ = lastError.isEmpty();
        return lastError;
    }
    void close()
    {
        stop();
        if (isOpen_)
        {
            closeDevice();
            isOpen_ = false;
        }
    }
    bool isOpen()                                       { return isOpen_ && isThreadRunning(); }
    int getCurrentBufferSizeSamples()                   { return bufferSizeSamples; }
    double getCurrentSampleRate()                       { return sampleRate; }
    BigInteger getActiveOutputChannels() const          { return enabledOutputs; }
    BigInteger getActiveInputChannels() const           { return enabledInputs; }
    int getOutputLatencyInSamples()                     { return (int) (getCurrentBufferSizeSamples() * 1.5); }
    int getInputLatencyInSamples()                      { return getOutputLatencyInSamples(); }
    StringArray getOutputChannelNames()                 { return outChannels; }
    StringArray getInputChannelNames()                  { return inChannels; }
    int getNumSampleRates()                             { return 4; }
    int getDefaultBufferSize()                          { return 2560; }
    int getNumBufferSizesAvailable()                    { return 50; }
    double getSampleRate (int index)
    {
        const double samps[] = { 44100.0, 48000.0, 88200.0, 96000.0 };
        return samps [jlimit (0, 3, index)];
    }
    int getBufferSizeSamples (int index)
    {
        int n = 64;
        for (int i = 0; i < index; ++i)
            n += (n < 512) ? 32
                           : ((n < 1024) ? 64
                                         : ((n < 2048) ? 128 : 256));
        return n;
    }
    int getCurrentBitDepth()
    {
        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)
    {
        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()
    {
        if (isStarted)
        {
            AudioIODeviceCallback* const callbackLocal = callback;
            {
                const ScopedLock sl (startStopLock);
                isStarted = false;
            }
            if (callbackLocal != nullptr)
                callbackLocal->audioDeviceStopped();
        }
    }
    bool isPlaying()                { return isStarted && isOpen_ && isThreadRunning(); }
    String getLastError()           { 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()
    {
        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)
            {
                JUCE_TRY
                {
                    callback->audioDeviceIOCallback (const_cast <const float**> (inputBuffers.getArrayOfChannels()),
                                                     inputBuffers.getNumChannels(),
                                                     outputBuffers.getArrayOfChannels(),
                                                     outputBuffers.getNumChannels(),
                                                     bufferSizeSamples);
                }
                JUCE_CATCH_EXCEPTION
            }
            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 = nullptr;
        if (enabledInputs[i])
            left = inputBuffers.getSampleData (numIns++);
        float* right = nullptr;
        if (enabledInputs[i + 1])
            right = inputBuffers.getSampleData (numIns++);
        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 = nullptr;
        if (enabledOutputs[i])
            left = outputBuffers.getSampleData (numOuts++);
        float* right = nullptr;
        if (enabledOutputs[i + 1])
            right = outputBuffers.getSampleData (numOuts++);
        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
    {
        log (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
        DSoundAudioIODevice* const d = dynamic_cast <DSoundAudioIODevice*> (device);
        if (d == 0)
            return -1;
        return asInput ? d->inputDeviceIndex
                       : d->outputDeviceIndex;
    }
    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()
    {
        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();
}
#undef log
#undef logError
 |