text:
/*
  ==============================================================================

   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.

  ==============================================================================
*/

namespace juce
{

struct MidiServiceType
{
    struct InputWrapper
    {
        virtual ~InputWrapper() {}

        virtual String getDeviceName() = 0;
        virtual void start() = 0;
        virtual void stop() = 0;
    };

    struct OutputWrapper
    {
        virtual ~OutputWrapper() {}

        virtual String getDeviceName() = 0;
        virtual void sendMessageNow (const MidiMessage&) = 0;
    };

    MidiServiceType() {}
    virtual ~MidiServiceType() {}

    virtual StringArray getDevices (bool) = 0;
    virtual int getDefaultDeviceIndex (bool) = 0;

    virtual InputWrapper* createInputWrapper (MidiInput*, int, MidiInputCallback*) = 0;
    virtual OutputWrapper* createOutputWrapper (int) = 0;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType)
};

//==============================================================================
class WindowsMidiService  : public MidiServiceType
{
private:
    struct WindowsInputWrapper  : public InputWrapper
    {
        struct MidiInCollector
        {
            MidiInCollector (WindowsMidiService& s,
                             MidiInput* const inputDevice,
                             MidiInputCallback& cb)
                : midiService (s),
                  input (inputDevice),
                  callback (cb)
            {
            }

            ~MidiInCollector()
            {
                stop();

                if (deviceHandle != 0)
                {
                    for (int count = 5; --count >= 0;)
                    {
                        if (midiInClose (deviceHandle) == MMSYSERR_NOERROR)
                            break;

                        Sleep (20);
                    }
                }
            }

            void handleMessage (const uint8* bytes, const uint32 timeStamp)
            {
                if (bytes[0] >= 0x80 && isStarted)
                {
                    concatenator.pushMidiData (bytes,
                                               MidiMessage::getMessageLengthFromFirstByte (bytes[0]),
                                               convertTimeStamp (timeStamp),
                                               input,
                                               callback);
                    writeFinishedBlocks();
                }
            }

            void handleSysEx (MIDIHDR* const hdr, const uint32 timeStamp)
            {
                if (isStarted && hdr->dwBytesRecorded > 0)
                {
                    concatenator.pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded,
                                               convertTimeStamp (timeStamp), input, callback);
                    writeFinishedBlocks();
                }
            }

            void start()
            {
                if (deviceHandle != 0 && ! isStarted)
                {
                    midiService.activeMidiCollectors.addIfNotAlreadyThere (this);

                    for (int i = 0; i < (int) numHeaders; ++i)
                    {
                        headers[i].prepare (deviceHandle);
                        headers[i].write (deviceHandle);
                    }

                    startTime = Time::getMillisecondCounterHiRes();
                    MMRESULT res = midiInStart (deviceHandle);

                    if (res == MMSYSERR_NOERROR)
                    {
                        concatenator.reset();
                        isStarted = true;
                    }
                    else
                    {
                        unprepareAllHeaders();
                    }
                }
            }

            void stop()
            {
                if (isStarted)
                {
                    isStarted = false;
                    midiInReset (deviceHandle);
                    midiInStop (deviceHandle);
                    midiService.activeMidiCollectors.removeFirstMatchingValue (this);
                    unprepareAllHeaders();
                    concatenator.reset();
                }
            }

            static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance,
                                                 DWORD_PTR midiMessage, DWORD_PTR timeStamp)
            {
                auto* collector = reinterpret_cast<MidiInCollector*> (dwInstance);

                if (collector->midiService.activeMidiCollectors.contains (collector))
                {
                    if (uMsg == MIM_DATA)
                        collector->handleMessage ((const uint8*) &midiMessage, (uint32) timeStamp);
                    else if (uMsg == MIM_LONGDATA)
                        collector->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp);
                }
            }

            HMIDIIN deviceHandle = 0;

        private:
            WindowsMidiService& midiService;
            MidiInput* input;
            MidiInputCallback& callback;
            MidiDataConcatenator concatenator { 4096 };
            bool volatile isStarted = false;
            double startTime = 0;

            struct MidiHeader
            {
                MidiHeader() {}

                void prepare (HMIDIIN device)
                {
                    zerostruct (hdr);
                    hdr.lpData = data;
                    hdr.dwBufferLength = (DWORD) numElementsInArray (data);

                    midiInPrepareHeader (device, &hdr, sizeof (hdr));
                }

                void unprepare (HMIDIIN device)
                {
                    if ((hdr.dwFlags & WHDR_DONE) != 0)
                    {
                        int c = 10;
                        while (--c >= 0 && midiInUnprepareHeader (device, &hdr, sizeof (hdr)) == MIDIERR_STILLPLAYING)
                            Thread::sleep (20);

                        jassert (c >= 0);
                    }
                }

                void write (HMIDIIN device)
                {
                    hdr.dwBytesRecorded = 0;
                    midiInAddBuffer (device, &hdr, sizeof (hdr));
                }

                void writeIfFinished (HMIDIIN device)
                {
                    if ((hdr.dwFlags & WHDR_DONE) != 0)
                        write (device);
                }

                MIDIHDR hdr;
                char data [256];

                JUCE_DECLARE_NON_COPYABLE (MidiHeader)
            };

            enum { numHeaders = 32 };
            MidiHeader headers [numHeaders];

            void writeFinishedBlocks()
            {
                for (int i = 0; i < (int) numHeaders; ++i)
                    headers[i].writeIfFinished (deviceHandle);
            }

            void unprepareAllHeaders()
            {
                for (int i = 0; i < (int) numHeaders; ++i)
                    headers[i].unprepare (deviceHandle);
            }

            double convertTimeStamp (uint32 timeStamp)
            {
                auto t = startTime + timeStamp;
                auto now = Time::getMillisecondCounterHiRes();

                if (t > now)
                {
                    if (t > now + 2.0)
                        startTime -= 1.0;

                    t = now;
                }

                return t * 0.001;
            }

            JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInCollector)
        };

        //==============================================================================
        WindowsInputWrapper (WindowsMidiService& parentService,
                             MidiInput* const input,
                             const int index,
                             MidiInputCallback* const callback)
        {
            auto names = getDevices();
            UINT deviceId = MIDI_MAPPER;

            if (isPositiveAndBelow (index, names.size()))
            {
                deviceName = names[index];
                deviceId = index;
            }

            collector = new MidiInCollector (parentService, input, *callback);

            HMIDIIN h;
            MMRESULT err = midiInOpen (&h, deviceId,
                                       (DWORD_PTR) &MidiInCollector::midiInCallback,
                                       (DWORD_PTR) (MidiInCollector*) collector.get(),
                                       CALLBACK_FUNCTION);

            if (err != MMSYSERR_NOERROR)
                throw std::runtime_error ("Failed to create Windows input device wrapper");

            collector->deviceHandle = h;
        }

        ~WindowsInputWrapper() {}

        static StringArray getDevices()
        {
            StringArray s;
            const UINT num = midiInGetNumDevs();

            for (UINT i = 0; i < num; ++i)
            {
                MIDIINCAPS mc = { 0 };

                if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
                    s.add (String (mc.szPname, (size_t) numElementsInArray (mc.szPname)));
            }

            s.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
            return s;
        }

        static int getDefaultDeviceIndex()
        {
            return 0;
        }

        void start() override   { collector->start(); }
        void stop() override    { collector->stop(); }

        String getDeviceName() override
        {
            return deviceName;
        }

        String deviceName;
        ScopedPointer<MidiInCollector> collector;

        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsInputWrapper)
    };

    //==============================================================================
    struct WindowsOutputWrapper  : public OutputWrapper
    {
        struct MidiOutHandle
        {
            int refCount;
            UINT deviceId;
            HMIDIOUT handle;

            JUCE_LEAK_DETECTOR (MidiOutHandle)
        };

        WindowsOutputWrapper (WindowsMidiService& p, int index) : parent (p)
        {
            auto names = getDevices();
            UINT deviceId = MIDI_MAPPER;

            if (isPositiveAndBelow (index, names.size()))
            {
                deviceName = names[index];
                deviceId = index;
            }

            if (deviceId == MIDI_MAPPER)
            {
                // use the microsoft sw synth as a default - best not to allow deviceId
                // to be MIDI_MAPPER, or else device sharing breaks
                for (int i = 0; i < names.size(); ++i)
                    if (names[i].containsIgnoreCase ("microsoft"))
                        deviceId = (UINT) i;
            }

            for (int i = parent.activeOutputHandles.size(); --i >= 0;)
            {
                auto* activeHandle = parent.activeOutputHandles.getUnchecked (i);

                if (activeHandle->deviceId == deviceId)
                {
                    activeHandle->refCount++;
                    han = activeHandle;
                    return;
                }
            }

            for (int i = 4; --i >= 0;)
            {
                HMIDIOUT h = 0;
                MMRESULT res = midiOutOpen (&h, deviceId, 0, 0, CALLBACK_NULL);

                if (res == MMSYSERR_NOERROR)
                {
                    han = new MidiOutHandle();
                    han->deviceId = deviceId;
                    han->refCount = 1;
                    han->handle = h;
                    parent.activeOutputHandles.add (han);
                    return;
                }

                if (res == MMSYSERR_ALLOCATED)
                    Sleep (100);
                else
                    break;
            }

            throw std::runtime_error ("Failed to create Windows output device wrapper");
        }

        ~WindowsOutputWrapper()
        {
            if (parent.activeOutputHandles.contains (han.get()) && --(han->refCount) == 0)
            {
                midiOutClose (han->handle);
                parent.activeOutputHandles.removeFirstMatchingValue (han.get());
            }
        }

        void sendMessageNow (const MidiMessage& message) override
        {
            if (message.getRawDataSize() > 3 || message.isSysEx())
            {
                MIDIHDR h = { 0 };

                h.lpData = (char*) message.getRawData();
                h.dwBytesRecorded = h.dwBufferLength  = (DWORD) message.getRawDataSize();

                if (midiOutPrepareHeader (han->handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR)
                {
                    MMRESULT res = midiOutLongMsg (han->handle, &h, sizeof (MIDIHDR));

                    if (res == MMSYSERR_NOERROR)
                    {
                        while ((h.dwFlags & MHDR_DONE) == 0)
                            Sleep (1);

                        int count = 500; // 1 sec timeout

                        while (--count >= 0)
                        {
                            res = midiOutUnprepareHeader (han->handle, &h, sizeof (MIDIHDR));

                            if (res == MIDIERR_STILLPLAYING)
                                Sleep (2);
                            else
                                break;
                        }
                    }
                }
            }
            else
            {
                for (int i = 0; i < 50; ++i)
                {
                    if (midiOutShortMsg (han->handle, *(unsigned int*) message.getRawData()) != MIDIERR_NOTREADY)
                        break;

                    Sleep (1);
                }
            }
        }

        static Array<MIDIOUTCAPS> getDeviceCaps()
        {
            Array<MIDIOUTCAPS> devices;
            const UINT num = midiOutGetNumDevs();

            for (UINT i = 0; i < num; ++i)
            {
                MIDIOUTCAPS mc = { 0 };

                if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
                    devices.add (mc);
            }

            return devices;
        }

        static StringArray getDevices()
        {
            StringArray s;

            for (auto& mc : getDeviceCaps())
                s.add (String (mc.szPname, (size_t) numElementsInArray (mc.szPname)));

            s.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
            return s;
        }

        static int getDefaultDeviceIndex()
        {
            int n = 0;

            for (auto& mc : getDeviceCaps())
            {
                if ((mc.wTechnology & MOD_MAPPER) != 0)
                    return n;

                ++n;
            }

            return 0;
        }

        String getDeviceName() override
        {
            return deviceName;
        }

        WindowsMidiService& parent;
        String deviceName;

        ScopedPointer<MidiOutHandle> han;

        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsOutputWrapper)
    };

public:
    WindowsMidiService() {}

    StringArray getDevices (bool isInput) override
    {
        return isInput ? WindowsInputWrapper::getDevices()
                       : WindowsOutputWrapper::getDevices();
    }

    int getDefaultDeviceIndex (bool isInput) override
    {
        return isInput ? WindowsInputWrapper::getDefaultDeviceIndex()
                       : WindowsOutputWrapper::getDefaultDeviceIndex();
    }

    InputWrapper* createInputWrapper (MidiInput* input, int index, MidiInputCallback* callback) override
    {
        return new WindowsInputWrapper (*this, input, index, callback);
    }

    OutputWrapper* createOutputWrapper (int index) override
    {
        return new WindowsOutputWrapper (*this, index);
    }

private:
    Array<WindowsInputWrapper::MidiInCollector*, CriticalSection> activeMidiCollectors;
    Array<WindowsOutputWrapper::MidiOutHandle*> activeOutputHandles;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMidiService)
};

//==============================================================================
#if JUCE_USE_WINRT_MIDI

using namespace Microsoft::WRL;

using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Devices::Midi;
using namespace ABI::Windows::Devices::Enumeration;
using namespace ABI::Windows::Storage::Streams;

class WinRTMidiService  : public MidiServiceType
{
private:
    template <typename COMFactoryType>
    struct MidiIODeviceWatcher
    {
        struct DeviceInfo
        {
            String name;
            String id;
            bool isDefault = false;
        };

        MidiIODeviceWatcher (ComSmartPtr<COMFactoryType>& comFactory)
            : factory (comFactory)
        {
        }

        ~MidiIODeviceWatcher()
        {
            stop();
        }

        bool start()
        {
            HSTRING deviceSelector;
            HRESULT hr = factory->GetDeviceSelector (&deviceSelector);
            if (FAILED (hr))
                return false;

            auto deviceInformationFactory = WinRTWrapper::getInstance()->getWRLFactory<IDeviceInformationStatics> (&RuntimeClass_Windows_Devices_Enumeration_DeviceInformation[0]);
            if (deviceInformationFactory == nullptr)
                return false;

            hr = deviceInformationFactory->CreateWatcherAqsFilter (deviceSelector, watcher.resetAndGetPointerAddress());
            if (FAILED (hr))
                return false;

            class DeviceEnumerationThread  : public Thread
            {
            public:
                DeviceEnumerationThread (String threadName, MidiIODeviceWatcher<COMFactoryType>& p)
                    : Thread (threadName), parent (p)
                {}

                void run() override
                {
                    auto parentPtr = &parent;

                    parent.watcher->add_Added (
                        Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformation*>> (
                            [parentPtr](IDeviceWatcher*, IDeviceInformation* info) { return parentPtr->addDevice (info); }
                        ).Get(),
                        &parent.deviceAddedToken);

                    parent.watcher->add_Removed (
                        Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>> (
                            [parentPtr](IDeviceWatcher*, IDeviceInformationUpdate* info) { return parentPtr->removeDevice (info); }
                        ).Get(),
                        &parent.deviceRemovedToken);

                    EventRegistrationToken deviceEnumerationCompletedToken { 0 };
                    parent.watcher->add_EnumerationCompleted (
                        Callback<ITypedEventHandler<DeviceWatcher*, IInspectable*>> (
                            [this](IDeviceWatcher*, IInspectable*) { enumerationCompleted.signal(); return S_OK; }
                        ).Get(),
                        &deviceEnumerationCompletedToken);

                    parent.watcher->Start();
                    enumerationCompleted.wait();

                    if (deviceEnumerationCompletedToken.value != 0)
                        parent.watcher->remove_EnumerationCompleted (deviceEnumerationCompletedToken);
                }

            private:
                MidiIODeviceWatcher<COMFactoryType>& parent;
                WaitableEvent enumerationCompleted;
            };

            DeviceEnumerationThread enumerationThread ("WinRT Device Enumeration Thread", *this);
            enumerationThread.startThread();
            enumerationThread.waitForThreadToExit (4000);

            return true;
        }

        bool stop()
        {
            if (watcher == nullptr)
                return true;

            if (deviceAddedToken.value != 0)
            {
                HRESULT hr = watcher->remove_Added (deviceAddedToken);
                if (FAILED (hr))
                    return false;

                deviceAddedToken.value = 0;
            }

            if (deviceRemovedToken.value != 0)
            {
                HRESULT hr = watcher->remove_Removed (deviceRemovedToken);
                if (FAILED (hr))
                    return false;

                deviceRemovedToken.value = 0;
            }

            HRESULT hr = watcher->Stop();
            if (FAILED (hr))
                return false;

            watcher = nullptr;
            return true;
        }

        HRESULT addDevice (IDeviceInformation* addedDeviceInfo)
        {
            boolean isEnabled;
            HRESULT hr = addedDeviceInfo->get_IsEnabled (&isEnabled);
            if (FAILED (hr))
                return S_OK;

            if (! isEnabled)
                return S_OK;

            const ScopedLock lock (deviceChanges);

            DeviceInfo info;

            HSTRING name;
            hr = addedDeviceInfo->get_Name (&name);
            if (FAILED (hr))
                return S_OK;

            info.name = WinRTWrapper::getInstance()->hStringToString (name);

            HSTRING id;
            hr = addedDeviceInfo->get_Id (&id);
            if (FAILED (hr))
                return S_OK;

            info.id = WinRTWrapper::getInstance()->hStringToString (id);

            boolean isDefault;
            hr = addedDeviceInfo->get_IsDefault (&isDefault);
            if (FAILED (hr))
                return S_OK;

            info.isDefault = isDefault != 0;

            connectedDevices.add (info);

            return S_OK;
        }

        HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo)
        {
            const ScopedLock lock (deviceChanges);

            HSTRING removedDeviceIdHstr;
            removedDeviceInfo->get_Id (&removedDeviceIdHstr);
            String removedDeviceId = WinRTWrapper::getInstance()->hStringToString (removedDeviceIdHstr);

            for (int i = 0; i < connectedDevices.size(); ++i)
            {
                if (connectedDevices[i].id == removedDeviceId)
                {
                    connectedDevices.remove (i);
                    break;
                }
            }

            return S_OK;
        }

        StringArray getDevices()
        {
            {
                const ScopedLock lock (deviceChanges);
                lastQueriedConnectedDevices = connectedDevices;
            }

            StringArray result;
            for (auto info : lastQueriedConnectedDevices.get())
                result.add (info.name);

            return result;
        }

        int getDefaultDeviceIndex()
        {
            auto& lastDevices = lastQueriedConnectedDevices.get();
            for (int i = 0; i < lastDevices.size(); ++i)
                if (lastDevices[i].isDefault)
                    return i;

            return 0;
        }

        String getDeviceNameFromIndex (const int index)
        {
            if (isPositiveAndBelow (index, lastQueriedConnectedDevices.get().size()))
                return lastQueriedConnectedDevices.get()[index].name;

            return {};
        }

        String getDeviceID (const String name)
        {
            const ScopedLock lock (deviceChanges);

            for (auto info : connectedDevices)
                if (info.name == name)
                    return info.id;

            return {};
        }

        ComSmartPtr<COMFactoryType>& factory;

        EventRegistrationToken deviceAddedToken   { 0 },
                               deviceRemovedToken { 0 };

        ComSmartPtr<IDeviceWatcher> watcher;

        Array<DeviceInfo> connectedDevices;
        CriticalSection deviceChanges;
        ThreadLocalValue<Array<DeviceInfo>> lastQueriedConnectedDevices;

        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher);
    };

    template <typename COMFactoryType, typename COMInterfaceType, typename COMType>
    class OpenMidiPortThread  : public Thread
    {
    public:
        OpenMidiPortThread (String threadName,
                            String midiDeviceId,
                            ComSmartPtr<COMFactoryType>& comFactory,
                            ComSmartPtr<COMInterfaceType>& comPort)
            : Thread (threadName),
              deviceId (midiDeviceId),
              factory (comFactory),
              port (comPort)
        {
        }

        ~OpenMidiPortThread()
        {
        }

        void run() override
        {
            WinRTWrapper::ScopedHString hDeviceId (deviceId);
            ComSmartPtr<IAsyncOperation<COMType*>> asyncOp;
            HRESULT hr = factory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress());
            if (FAILED (hr))
                return;

            hr = asyncOp->put_Completed (Callback<IAsyncOperationCompletedHandler<COMType*>> (
                [this] (IAsyncOperation<COMType*>* asyncOpPtr, AsyncStatus)
                {
                    if (asyncOpPtr == nullptr)
                        return E_ABORT;

                    HRESULT hr = asyncOpPtr->GetResults (port.resetAndGetPointerAddress());
                    if (FAILED (hr))
                        return hr;

                    portOpened.signal();
                    return S_OK;
                }
            ).Get());

            // When using Bluetooth the asynchronous port opening operation will occasionally
            // hang, so we use a timeout. We will be able to remove this when Microsoft
            // improves the Bluetooth MIDI stack.
            portOpened.wait (2000);
        }

        const String deviceId;
        ComSmartPtr<COMFactoryType>& factory;
        ComSmartPtr<COMInterfaceType>& port;

        WaitableEvent portOpened { true };
    };

    struct WinRTInputWrapper  : public InputWrapper
    {
        WinRTInputWrapper (WinRTMidiService& service,
                           MidiInput* const input,
                           const int index,
                           MidiInputCallback& cb)
            : inputDevice (input),
              callback (cb),
              concatenator (4096)
        {
            const ScopedLock lock (service.inputDeviceWatcher->deviceChanges);

            deviceName = service.inputDeviceWatcher->getDeviceNameFromIndex (index);
            if (deviceName.isEmpty())
                throw std::runtime_error ("Invalid device index");

            const auto deviceID = service.inputDeviceWatcher->getDeviceID (deviceName);
            if (deviceID.isEmpty())
                throw std::runtime_error ("Device unavailable");

            OpenMidiPortThread<IMidiInPortStatics, IMidiInPort, MidiInPort> portThread ("Open WinRT MIDI input port",
                                                                                deviceID,
                                                                                service.midiInFactory,
                                                                                midiInPort);
            portThread.startThread();
            portThread.waitForThreadToExit (-1);
            if (midiInPort == nullptr)
                throw std::runtime_error ("Timed out waiting for midi input port creation");

            startTime = Time::getMillisecondCounterHiRes();

            HRESULT hr = midiInPort->add_MessageReceived (
                Callback<ITypedEventHandler<MidiInPort*, MidiMessageReceivedEventArgs*>> (
                    [this] (IMidiInPort*, IMidiMessageReceivedEventArgs* args) { return midiInMessageReceived (args); }
                ).Get(),
                &midiInMessageToken);
            if (FAILED (hr))
                throw std::runtime_error ("Failed to set midi input callback");
        }

        ~WinRTInputWrapper()
        {
            if (midiInMessageToken.value != 0)
                midiInPort->remove_MessageReceived (midiInMessageToken);

            midiInPort = nullptr;
        }

        void start() override
        {
            if (!isStarted)
            {
                concatenator.reset();
                isStarted = true;
            }
        }

        void stop() override
        {
            if (isStarted)
            {
                isStarted = false;
                concatenator.reset();
            }
        }

        String getDeviceName() override
        {
            return deviceName;
        }

        HRESULT midiInMessageReceived (IMidiMessageReceivedEventArgs* args)
        {
            if (! isStarted)
                return S_OK;

            ComSmartPtr<IMidiMessage> message;
            HRESULT hr = args->get_Message (message.resetAndGetPointerAddress());
            if (FAILED (hr))
                return hr;

            ComSmartPtr<IBuffer> buffer;
            hr = message->get_RawData (buffer.resetAndGetPointerAddress());
            if (FAILED (hr))
                return hr;

            ComSmartPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
            hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress());
            if (FAILED (hr))
                return hr;

            uint8_t* bufferData = nullptr;
            hr = bufferByteAccess->Buffer (&bufferData);
            if (FAILED (hr))
                return hr;

            uint32_t numBytes = 0;
            hr = buffer->get_Length (&numBytes);
            if (FAILED (hr))
                return hr;

            ABI::Windows::Foundation::TimeSpan timespan;
            hr = message->get_Timestamp (&timespan);
            if (FAILED (hr))
                return hr;

            concatenator.pushMidiData (bufferData,
                                       numBytes,
                                       convertTimeStamp (timespan.Duration),
                                       inputDevice,
                                       callback);

            return S_OK;
        }

        double convertTimeStamp (int64 timestamp)
        {
            const auto millisecondsSinceStart = static_cast<double> (timestamp) / 10000.0;
            double t = startTime + millisecondsSinceStart;

            const double now = Time::getMillisecondCounterHiRes();
            if (t > now)
            {
                if (t > now + 2.0)
                    startTime -= 1.0;

                t = now;
            }

            return t * 0.001;
        }

        MidiInput* inputDevice;
        MidiInputCallback& callback;
        String deviceName;
        MidiDataConcatenator concatenator;
        ComSmartPtr<IMidiInPort> midiInPort;
        EventRegistrationToken midiInMessageToken { 0 };

        double startTime = 0;
        bool isStarted = false;

        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTInputWrapper);
    };

    struct WinRTOutputWrapper  : public OutputWrapper
    {
        WinRTOutputWrapper (WinRTMidiService& service, const int index)
        {
            const ScopedLock lock (service.outputDeviceWatcher->deviceChanges);

            deviceName = service.outputDeviceWatcher->getDeviceNameFromIndex (index);
            if (deviceName.isEmpty())
                throw std::runtime_error ("Invalid device index");

            const auto deviceID = service.outputDeviceWatcher->getDeviceID (deviceName);
            if (deviceID.isEmpty())
                throw std::runtime_error ("Device unavailable");

            OpenMidiPortThread<IMidiOutPortStatics, IMidiOutPort, IMidiOutPort> portThread ("Open WinRT MIDI output port",
                                                                                deviceID,
                                                                                service.midiOutFactory,
                                                                                midiOutPort);
            portThread.startThread();
            portThread.waitForThreadToExit (-1);
            if (midiOutPort == nullptr)
                throw std::runtime_error ("Timed out waiting for midi output port creation");

            auto bufferFactory = WinRTWrapper::getInstance()->getWRLFactory<IBufferFactory> (&RuntimeClass_Windows_Storage_Streams_Buffer[0]);
            if (bufferFactory == nullptr)
                throw std::runtime_error ("Failed to create output buffer factory");

            HRESULT hr = bufferFactory->Create (static_cast<UINT32> (65536), buffer.resetAndGetPointerAddress());
            if (FAILED (hr))
                throw std::runtime_error ("Failed to create output buffer");

            hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress());
            if (FAILED (hr))
                throw std::runtime_error ("Failed to get buffer byte access");

            hr = bufferByteAccess->Buffer (&bufferData);
            if (FAILED (hr))
                throw std::runtime_error ("Failed to get buffer data pointer");
        }

        ~WinRTOutputWrapper() {}

        void sendMessageNow (const MidiMessage& message) override
        {
            const UINT32 numBytes = message.getRawDataSize();
            HRESULT hr = buffer->put_Length (numBytes);
            if (FAILED (hr))
                jassertfalse;

            memcpy_s (bufferData, numBytes, message.getRawData(), numBytes);

            midiOutPort->SendBuffer (buffer);
        }

        String getDeviceName() override
        {
            return deviceName;
        }

        String deviceName;
        ComSmartPtr<IMidiOutPort> midiOutPort;
        ComSmartPtr<IBuffer> buffer;
        ComSmartPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
        uint8_t* bufferData = nullptr;

        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTOutputWrapper);
    };

public:
    WinRTMidiService()
    {
        if (! WinRTWrapper::getInstance()->isInitialised())
            throw std::runtime_error ("Failed to initialise the WinRT wrapper");

        midiInFactory = WinRTWrapper::getInstance()->getWRLFactory<IMidiInPortStatics> (&RuntimeClass_Windows_Devices_Midi_MidiInPort[0]);
        if (midiInFactory == nullptr)
            throw std::runtime_error ("Failed to create midi in factory");

        midiOutFactory = WinRTWrapper::getInstance()->getWRLFactory<IMidiOutPortStatics> (&RuntimeClass_Windows_Devices_Midi_MidiOutPort[0]);
        if (midiOutFactory == nullptr)
            throw std::runtime_error ("Failed to create midi out factory");

        inputDeviceWatcher  = new MidiIODeviceWatcher<IMidiInPortStatics>  (midiInFactory);
        if (! inputDeviceWatcher->start())
            throw std::runtime_error ("Failed to start midi input device watcher");

        outputDeviceWatcher = new MidiIODeviceWatcher<IMidiOutPortStatics> (midiOutFactory);
        if (! outputDeviceWatcher->start())
            throw std::runtime_error ("Failed to start midi output device watcher");
    }

    ~WinRTMidiService()
    {
    }

    StringArray getDevices (bool isInput) override
    {
        return isInput ? inputDeviceWatcher ->getDevices()
                       : outputDeviceWatcher->getDevices();
    }

    int getDefaultDeviceIndex (bool isInput) override
    {
        return isInput ? inputDeviceWatcher ->getDefaultDeviceIndex()
                       : outputDeviceWatcher->getDefaultDeviceIndex();
    }

    InputWrapper* createInputWrapper (MidiInput* input, int index, MidiInputCallback* callback) override
    {
        return new WinRTInputWrapper (*this, input, index, *callback);
    }

    OutputWrapper* createOutputWrapper (int index) override
    {
        return new WinRTOutputWrapper (*this, index);
    }

    ComSmartPtr<IMidiInPortStatics>  midiInFactory;
    ComSmartPtr<IMidiOutPortStatics> midiOutFactory;

    ScopedPointer<MidiIODeviceWatcher<IMidiInPortStatics>>  inputDeviceWatcher;
    ScopedPointer<MidiIODeviceWatcher<IMidiOutPortStatics>> outputDeviceWatcher;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTMidiService)
};

#endif   // JUCE_USE_WINRT_MIDI

//==============================================================================
class MidiService :  public DeletedAtShutdown
{
public:
    ~MidiService();

    MidiServiceType* getService();

    juce_DeclareSingleton (MidiService, false)

private:
    MidiService();

    ScopedPointer<MidiServiceType> internal;
};

juce_ImplementSingleton (MidiService)

MidiService::~MidiService()
{
    clearSingletonInstance();
}

MidiServiceType* MidiService::getService()
{
    return internal.get();
}

MidiService::MidiService()
{
   #if JUCE_USE_WINRT_MIDI
    try
    {
        internal = new WinRTMidiService();
        return;
    }
    catch (std::runtime_error&)
    {
    }
   #endif

    internal = new WindowsMidiService();
}

//==============================================================================
StringArray MidiInput::getDevices()
{
    return MidiService::getInstance()->getService()->getDevices (true);
}

int MidiInput::getDefaultDeviceIndex()
{
    return MidiService::getInstance()->getService()->getDefaultDeviceIndex (true);
}

MidiInput::MidiInput (const String& deviceName)
    : name (deviceName)
{
}

MidiInput* MidiInput::openDevice (const int index, MidiInputCallback* const callback)
{
    if (callback == nullptr)
        return nullptr;

    ScopedPointer<MidiInput> in (new MidiInput (String()));
    ScopedPointer<MidiServiceType::InputWrapper> wrapper;

    try
    {
        wrapper = MidiService::getInstance()->getService()->createInputWrapper (in, index, callback);
    }
    catch (std::runtime_error&)
    {
        return nullptr;
    }

    in->setName (wrapper->getDeviceName());
    in->internal = wrapper.release();
    return in.release();
}

MidiInput::~MidiInput()
{
    delete static_cast<MidiServiceType::InputWrapper*> (internal);
}

void MidiInput::start()   { static_cast<MidiServiceType::InputWrapper*> (internal)->start(); }
void MidiInput::stop()    { static_cast<MidiServiceType::InputWrapper*> (internal)->stop(); }

//==============================================================================
StringArray MidiOutput::getDevices()
{
    return MidiService::getInstance()->getService()->getDevices (false);
}

int MidiOutput::getDefaultDeviceIndex()
{
    return MidiService::getInstance()->getService()->getDefaultDeviceIndex (false);
}

MidiOutput* MidiOutput::openDevice (const int index)
{
    ScopedPointer<MidiServiceType::OutputWrapper> wrapper;

    try
    {
        wrapper = MidiService::getInstance()->getService()->createOutputWrapper (index);
    }
    catch (std::runtime_error&)
    {
        return nullptr;
    }

    ScopedPointer<MidiOutput> out (new MidiOutput (wrapper->getDeviceName()));
    out->internal = wrapper.release();
    return out.release();
}

MidiOutput::~MidiOutput()
{
    stopBackgroundThread();
    delete static_cast<MidiServiceType::OutputWrapper*> (internal);
}

void MidiOutput::sendMessageNow (const MidiMessage& message)
{
    static_cast<MidiServiceType::OutputWrapper*> (internal)->sendMessageNow (message);
}

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   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..
    typedef struct typeDSBUFFERDESC
    {
        DWORD dwSize;
        DWORD dwFlags;
        DWORD dwBufferBytes;
        DWORD dwReserved;
        LPWAVEFORMATEX lpwfxFormat;
        GUID guid3DAlgorithm;
    } DSBUFFERDESC;

    struct IDirectSoundBuffer;

    #undef INTERFACE
    #define INTERFACE IDirectSound
    DECLARE_INTERFACE_(IDirectSound, IUnknown)
    {
        STDMETHOD(QueryInterface)       (THIS_ REFIID, LPVOID*) PURE;
        STDMETHOD_(ULONG,AddRef)        (THIS) PURE;
        STDMETHOD_(ULONG,Release)       (THIS) PURE;
        STDMETHOD(CreateSoundBuffer)    (THIS_ DSBUFFERDESC*, IDirectSoundBuffer**, LPUNKNOWN) PURE;
        STDMETHOD(GetCaps)              (THIS_ void*) PURE;
        STDMETHOD(DuplicateSoundBuffer) (THIS_ IDirectSoundBuffer*, IDirectSoundBuffer**) PURE;
        STDMETHOD(SetCooperativeLevel)  (THIS_ HWND, DWORD) PURE;
        STDMETHOD(Compact)              (THIS) PURE;
        STDMETHOD(GetSpeakerConfig)     (THIS_ LPDWORD) PURE;
        STDMETHOD(SetSpeakerConfig)     (THIS_ DWORD) PURE;
        STDMETHOD(Initialize)           (THIS_ const GUID*) PURE;
    };

    #undef INTERFACE
    #define INTERFACE IDirectSoundBuffer
    DECLARE_INTERFACE_(IDirectSoundBuffer, IUnknown)
    {
        STDMETHOD(QueryInterface)       (THIS_ REFIID, LPVOID*) PURE;
        STDMETHOD_(ULONG,AddRef)        (THIS) PURE;
        STDMETHOD_(ULONG,Release)       (THIS) PURE;
        STDMETHOD(GetCaps)              (THIS_ void*) PURE;
        STDMETHOD(GetCurrentPosition)   (THIS_ LPDWORD, LPDWORD) PURE;
        STDMETHOD(GetFormat)            (THIS_ LPWAVEFORMATEX, DWORD, LPDWORD) PURE;
        STDMETHOD(GetVolume)            (THIS_ LPLONG) PURE;
        STDMETHOD(GetPan)               (THIS_ LPLONG) PURE;
        STDMETHOD(GetFrequency)         (THIS_ LPDWORD) PURE;
        STDMETHOD(GetStatus)            (THIS_ LPDWORD) PURE;
        STDMETHOD(Initialize)           (THIS_ IDirectSound*, DSBUFFERDESC*) PURE;
        STDMETHOD(Lock)                 (THIS_ DWORD, DWORD, LPVOID*, LPDWORD, LPVOID*, LPDWORD, DWORD) PURE;
        STDMETHOD(Play)                 (THIS_ DWORD, DWORD, DWORD) PURE;
        STDMETHOD(SetCurrentPosition)   (THIS_ DWORD) PURE;
        STDMETHOD(SetFormat)            (THIS_ const WAVEFORMATEX*) PURE;
        STDMETHOD(SetVolume)            (THIS_ LONG) PURE;
        STDMETHOD(SetPan)               (THIS_ LONG) PURE;
        STDMETHOD(SetFrequency)         (THIS_ DWORD) PURE;
        STDMETHOD(Stop)                 (THIS) PURE;
        STDMETHOD(Unlock)               (THIS_ LPVOID, DWORD, LPVOID, DWORD) PURE;
        STDMETHOD(Restore)              (THIS) PURE;
    };

    //==============================================================================
    typedef struct typeDSCBUFFERDESC
    {
        DWORD dwSize;
        DWORD dwFlags;
        DWORD dwBufferBytes;
        DWORD dwReserved;
        LPWAVEFORMATEX lpwfxFormat;
    } DSCBUFFERDESC;

    struct IDirectSoundCaptureBuffer;

    #undef INTERFACE
    #define INTERFACE IDirectSoundCapture
    DECLARE_INTERFACE_(IDirectSoundCapture, IUnknown)
    {
        STDMETHOD(QueryInterface)       (THIS_ REFIID, LPVOID*) PURE;
        STDMETHOD_(ULONG,AddRef)        (THIS) PURE;
        STDMETHOD_(ULONG,Release)       (THIS) PURE;
        STDMETHOD(CreateCaptureBuffer)  (THIS_ DSCBUFFERDESC*, IDirectSoundCaptureBuffer**, LPUNKNOWN) PURE;
        STDMETHOD(GetCaps)              (THIS_ void*) PURE;
        STDMETHOD(Initialize)           (THIS_ const GUID*) PURE;
    };

    #undef INTERFACE
    #define INTERFACE IDirectSoundCaptureBuffer
    DECLARE_INTERFACE_(IDirectSoundCaptureBuffer, IUnknown)
    {
        STDMETHOD(QueryInterface)       (THIS_ REFIID, LPVOID*) PURE;
        STDMETHOD_(ULONG,AddRef)        (THIS) PURE;
        STDMETHOD_(ULONG,Release)       (THIS) PURE;
        STDMETHOD(GetCaps)              (THIS_ void*) PURE;
        STDMETHOD(GetCurrentPosition)   (THIS_ LPDWORD, LPDWORD) PURE;
        STDMETHOD(GetFormat)            (THIS_ LPWAVEFORMATEX, DWORD, LPDWORD) PURE;
        STDMETHOD(GetStatus)            (THIS_ LPDWORD) PURE;
        STDMETHOD(Initialize)           (THIS_ IDirectSoundCapture*, DSCBUFFERDESC*) PURE;
        STDMETHOD(Lock)                 (THIS_ DWORD, DWORD, LPVOID*, LPDWORD, LPVOID*, LPDWORD, DWORD) PURE;
        STDMETHOD(Start)                (THIS_ DWORD) PURE;
        STDMETHOD(Stop)                 (THIS) PURE;
        STDMETHOD(Unlock)               (THIS_ LPVOID, DWORD, LPVOID, DWORD) PURE;
    };

    #undef INTERFACE
}

namespace juce
{

//==============================================================================
namespace DSoundLogging
{
    String getErrorMessage (HRESULT hr)
    {
        const char* result = nullptr;

        switch (hr)
        {
            case MAKE_HRESULT(1, 0x878, 10):    result = "Device already allocated"; break;
            case MAKE_HRESULT(1, 0x878, 30):    result = "Control unavailable"; break;
            case E_INVALIDARG:                  result = "Invalid parameter"; break;
            case MAKE_HRESULT(1, 0x878, 50):    result = "Invalid call"; break;
            case E_FAIL:                        result = "Generic error"; break;
            case MAKE_HRESULT(1, 0x878, 70):    result = "Priority level error"; break;
            case E_OUTOFMEMORY:                 result = "Out of memory"; break;
            case MAKE_HRESULT(1, 0x878, 100):   result = "Bad format"; break;
            case E_NOTIMPL:                     result = "Unsupported function"; break;
            case MAKE_HRESULT(1, 0x878, 120):   result = "No driver"; break;
            case MAKE_HRESULT(1, 0x878, 130):   result = "Already initialised"; break;
            case CLASS_E_NOAGGREGATION:         result = "No aggregation"; break;
            case MAKE_HRESULT(1, 0x878, 150):   result = "Buffer lost"; break;
            case MAKE_HRESULT(1, 0x878, 160):   result = "Another app has priority"; break;
            case MAKE_HRESULT(1, 0x878, 170):   result = "Uninitialised"; break;
            case E_NOINTERFACE:                 result = "No interface"; break;
            case S_OK:                          result = "No error"; break;
            default:                            return "Unknown error: " + String ((int) hr);
        }

        return result;
    }

    //==============================================================================
   #if JUCE_DIRECTSOUND_LOGGING
    static void logMessage (String message)
    {
        message = "DSOUND: " + message;
        DBG (message);
        Logger::writeToLog (message);
    }

    static void logError (HRESULT hr, int lineNum)
    {
        if (FAILED (hr))
        {
            String error ("Error at line ");
            error << lineNum << ": " << getErrorMessage (hr);
            logMessage (error);
        }
    }

    #define JUCE_DS_LOG(a)        DSoundLogging::logMessage(a);
    #define JUCE_DS_LOG_ERROR(a)  DSoundLogging::logError(a, __LINE__);
   #else
    #define JUCE_DS_LOG(a)
    #define JUCE_DS_LOG_ERROR(a)
   #endif
}

//==============================================================================
namespace
{
    #define DSOUND_FUNCTION(functionName, params) \
        typedef HRESULT (WINAPI *type##functionName) params; \
        static type##functionName ds##functionName = nullptr;

    #define DSOUND_FUNCTION_LOAD(functionName) \
        ds##functionName = (type##functionName) GetProcAddress (h, #functionName);  \
        jassert (ds##functionName != nullptr);

    typedef BOOL (CALLBACK *LPDSENUMCALLBACKW) (LPGUID, LPCWSTR, LPCWSTR, LPVOID);
    typedef BOOL (CALLBACK *LPDSENUMCALLBACKA) (LPGUID, LPCSTR, LPCSTR, LPVOID);

    DSOUND_FUNCTION (DirectSoundCreate, (const GUID*, IDirectSound**, LPUNKNOWN))
    DSOUND_FUNCTION (DirectSoundCaptureCreate, (const GUID*, IDirectSoundCapture**, LPUNKNOWN))
    DSOUND_FUNCTION (DirectSoundEnumerateW, (LPDSENUMCALLBACKW, LPVOID))
    DSOUND_FUNCTION (DirectSoundCaptureEnumerateW, (LPDSENUMCALLBACKW, LPVOID))

    void initialiseDSoundFunctions()
    {
        if (dsDirectSoundCreate == nullptr)
        {
            HMODULE h = LoadLibraryA ("dsound.dll");

            DSOUND_FUNCTION_LOAD (DirectSoundCreate)
            DSOUND_FUNCTION_LOAD (DirectSoundCaptureCreate)
            DSOUND_FUNCTION_LOAD (DirectSoundEnumerateW)
            DSOUND_FUNCTION_LOAD (DirectSoundCaptureEnumerateW)
        }
    }

    // the overall size of buffer used is this value x the block size
    enum { blocksPerOverallBuffer = 16 };
}

//==============================================================================
class DSoundInternalOutChannel
{
public:
    DSoundInternalOutChannel (const String& name_, const GUID& guid_, int rate,
                              int bufferSize, float* left, float* right)
        : bitDepth (16), name (name_), guid (guid_), sampleRate (rate),
          bufferSizeSamples (bufferSize), leftBuffer (left), rightBuffer (right),
          pDirectSound (nullptr), pOutputBuffer (nullptr)
    {
    }

    ~DSoundInternalOutChannel()
    {
        close();
    }

    void close()
    {
        if (pOutputBuffer != nullptr)
        {
            JUCE_DS_LOG ("closing output: " + name);
            HRESULT hr = pOutputBuffer->Stop();
            JUCE_DS_LOG_ERROR (hr); ignoreUnused (hr);

            pOutputBuffer->Release();
            pOutputBuffer = nullptr;
        }

        if (pDirectSound != nullptr)
        {
            pDirectSound->Release();
            pDirectSound = nullptr;
        }
    }

    String open()
    {
        JUCE_DS_LOG ("opening output: " + name + "  rate=" + String (sampleRate)
                       + " bits=" + String (bitDepth) + " buf=" + String (bufferSizeSamples));

        pDirectSound = nullptr;
        pOutputBuffer = nullptr;
        writeOffset = 0;
        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;
    AudioSampleBuffer 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();
        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")
    {
        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


text:
/*
  ==============================================================================

   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.

  ==============================================================================
*/

namespace juce
{

AudioTransportSource::AudioTransportSource()
{
}

AudioTransportSource::~AudioTransportSource()
{
    setSource (nullptr);
    releaseMasterResources();
}

void AudioTransportSource::setSource (PositionableAudioSource* const newSource,
                                      int readAheadSize, TimeSliceThread* readAheadThread,
                                      double sourceSampleRateToCorrectFor, int maxNumChannels)
{
    if (source == newSource)
    {
        if (source == nullptr)
            return;

        setSource (nullptr, 0, nullptr); // deselect and reselect to avoid releasing resources wrongly
    }

    readAheadBufferSize = readAheadSize;
    sourceSampleRate = sourceSampleRateToCorrectFor;

    ResamplingAudioSource* newResamplerSource = nullptr;
    BufferingAudioSource* newBufferingSource = nullptr;
    PositionableAudioSource* newPositionableSource = nullptr;
    AudioSource* newMasterSource = nullptr;

    ScopedPointer<ResamplingAudioSource> oldResamplerSource (resamplerSource);
    ScopedPointer<BufferingAudioSource> oldBufferingSource (bufferingSource);
    AudioSource* oldMasterSource = masterSource;

    if (newSource != nullptr)
    {
        newPositionableSource = newSource;

        if (readAheadSize > 0)
        {
            // If you want to use a read-ahead buffer, you must also provide a TimeSliceThread
            // for it to use!
            jassert (readAheadThread != nullptr);

            newPositionableSource = newBufferingSource
                = new BufferingAudioSource (newPositionableSource, *readAheadThread,
                                            false, readAheadSize, maxNumChannels);
        }

        newPositionableSource->setNextReadPosition (0);

        if (sourceSampleRateToCorrectFor > 0)
            newMasterSource = newResamplerSource
                = new ResamplingAudioSource (newPositionableSource, false, maxNumChannels);
        else
            newMasterSource = newPositionableSource;

        if (isPrepared)
        {
            if (newResamplerSource != nullptr && sourceSampleRate > 0 && sampleRate > 0)
                newResamplerSource->setResamplingRatio (sourceSampleRate / sampleRate);

            newMasterSource->prepareToPlay (blockSize, sampleRate);
        }
    }

    {
        const ScopedLock sl (callbackLock);

        source = newSource;
        resamplerSource = newResamplerSource;
        bufferingSource = newBufferingSource;
        masterSource = newMasterSource;
        positionableSource = newPositionableSource;

        inputStreamEOF = false;
        playing = false;
    }

    if (oldMasterSource != nullptr)
        oldMasterSource->releaseResources();
}

void AudioTransportSource::start()
{
    if ((! playing) && masterSource != nullptr)
    {
        {
            const ScopedLock sl (callbackLock);
            playing = true;
            stopped = false;
            inputStreamEOF = false;
        }

        sendChangeMessage();
    }
}

void AudioTransportSource::stop()
{
    if (playing)
    {
        {
            const ScopedLock sl (callbackLock);
            playing = false;
        }

        int n = 500;
        while (--n >= 0 && ! stopped)
            Thread::sleep (2);

        sendChangeMessage();
    }
}

void AudioTransportSource::setPosition (double newPosition)
{
    if (sampleRate > 0.0)
        setNextReadPosition ((int64) (newPosition * sampleRate));
}

double AudioTransportSource::getCurrentPosition() const
{
    if (sampleRate > 0.0)
        return (double) getNextReadPosition() / sampleRate;

    return 0.0;
}

double AudioTransportSource::getLengthInSeconds() const
{
    if (sampleRate > 0.0)
        return (double) getTotalLength() / sampleRate;

    return 0.0;
}

void AudioTransportSource::setNextReadPosition (int64 newPosition)
{
    if (positionableSource != nullptr)
    {
        if (sampleRate > 0 && sourceSampleRate > 0)
            newPosition = (int64) ((double) newPosition * sourceSampleRate / sampleRate);

        positionableSource->setNextReadPosition (newPosition);

        if (resamplerSource != nullptr)
            resamplerSource->flushBuffers();

        inputStreamEOF = false;
    }
}

int64 AudioTransportSource::getNextReadPosition() const
{
    if (positionableSource != nullptr)
    {
        const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0;
        return (int64) ((double) positionableSource->getNextReadPosition() * ratio);
    }

    return 0;
}

int64 AudioTransportSource::getTotalLength() const
{
    const ScopedLock sl (callbackLock);

    if (positionableSource != nullptr)
    {
        const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0;
        return (int64) ((double) positionableSource->getTotalLength() * ratio);
    }

    return 0;
}

bool AudioTransportSource::isLooping() const
{
    const ScopedLock sl (callbackLock);
    return positionableSource != nullptr && positionableSource->isLooping();
}

void AudioTransportSource::setGain (const float newGain) noexcept
{
    gain = newGain;
}

void AudioTransportSource::prepareToPlay (int samplesPerBlockExpected, double newSampleRate)
{
    const ScopedLock sl (callbackLock);

    sampleRate = newSampleRate;
    blockSize = samplesPerBlockExpected;

    if (masterSource != nullptr)
        masterSource->prepareToPlay (samplesPerBlockExpected, sampleRate);

    if (resamplerSource != nullptr && sourceSampleRate > 0)
        resamplerSource->setResamplingRatio (sourceSampleRate / sampleRate);

    inputStreamEOF = false;
    isPrepared = true;
}

void AudioTransportSource::releaseMasterResources()
{
    const ScopedLock sl (callbackLock);

    if (masterSource != nullptr)
        masterSource->releaseResources();

    isPrepared = false;
}

void AudioTransportSource::releaseResources()
{
    releaseMasterResources();
}

void AudioTransportSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
{
    const ScopedLock sl (callbackLock);

    if (masterSource != nullptr && ! stopped)
    {
        masterSource->getNextAudioBlock (info);

        if (! playing)
        {
            // just stopped playing, so fade out the last block..
            for (int i = info.buffer->getNumChannels(); --i >= 0;)
                info.buffer->applyGainRamp (i, info.startSample, jmin (256, info.numSamples), 1.0f, 0.0f);

            if (info.numSamples > 256)
                info.buffer->clear (info.startSample + 256, info.numSamples - 256);
        }

        if (positionableSource->getNextReadPosition() > positionableSource->getTotalLength() + 1
              && ! positionableSource->isLooping())
        {
            playing = false;
            inputStreamEOF = true;
            sendChangeMessage();
        }

        stopped = ! playing;

        for (int i = info.buffer->getNumChannels(); --i >= 0;)
            info.buffer->applyGainRamp (i, info.startSample, info.numSamples, lastGain, gain);
    }
    else
    {
        info.clearActiveBufferRegion();
        stopped = true;
    }

    lastGain = gain;
}

} // namespace juce


text:
/*
  ==============================================================================

   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.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    Wrapper class to continuously stream audio from an audio source to an
    AudioIODevice.

    This object acts as an AudioIODeviceCallback, so can be attached to an
    output device, and will stream audio from an AudioSource.
*/
class JUCE_API  AudioSourcePlayer  : public AudioIODeviceCallback
{
public:
    //==============================================================================
    /** Creates an empty AudioSourcePlayer. */
    AudioSourcePlayer();

    /** Destructor.

        Make sure this object isn't still being used by an AudioIODevice before
        deleting it!
    */
    virtual ~AudioSourcePlayer();

    //==============================================================================
    /** Changes the current audio source to play from.

        If the source passed in is already being used, this method will do nothing.
        If the source is not null, its prepareToPlay() method will be called
        before it starts being used for playback.

        If there's another source currently playing, its releaseResources() method
        will be called after it has been swapped for the new one.

        @param newSource                the new source to use - this will NOT be deleted
                                        by this object when no longer needed, so it's the
                                        caller's responsibility to manage it.
    */
    void setSource (AudioSource* newSource);

    /** Returns the source that's playing.
        May return nullptr if there's no source.
    */
    AudioSource* getCurrentSource() const noexcept      { return source; }

    /** Sets a gain to apply to the audio data.
        @see getGain
    */
    void setGain (float newGain) noexcept;

    /** Returns the current gain.
        @see setGain
    */
    float getGain() const noexcept                      { return gain; }

    //==============================================================================
    /** Implementation of the AudioIODeviceCallback method. */
    void audioDeviceIOCallback (const float** inputChannelData,
                                int totalNumInputChannels,
                                float** outputChannelData,
                                int totalNumOutputChannels,
                                int numSamples) override;

    /** Implementation of the AudioIODeviceCallback method. */
    void audioDeviceAboutToStart (AudioIODevice* device) override;

    /** Implementation of the AudioIODeviceCallback method. */
    void audioDeviceStopped() override;

    /** An alternative method for initialising the source without an AudioIODevice. */
    void prepareToPlay (double sampleRate, int blockSize);

private:
    //==============================================================================
    CriticalSection readLock;
    AudioSource* source;
    double sampleRate;
    int bufferSize;
    float* channels [128];
    float* outputChans [128];
    const float* inputChans [128];
    AudioSampleBuffer tempBuffer;
    float lastGain, gain;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourcePlayer)
};

} // namespace juce