diff --git a/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp b/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp index 80282af8ff..98fdd6cd12 100644 --- a/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp +++ b/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp @@ -25,7 +25,7 @@ namespace juce struct MidiOutput::PendingMessage { - PendingMessage (const void* const data, const int len, const double timeStamp) + PendingMessage (const void* data, int len, double timeStamp) : message (data, len, timeStamp) {} @@ -33,11 +33,8 @@ struct MidiOutput::PendingMessage PendingMessage* next; }; -MidiOutput::MidiOutput (const String& midiName) - : Thread ("midi out"), - internal (nullptr), - firstMessage (nullptr), - name (midiName) +MidiOutput::MidiOutput (const String& deviceName) + : Thread ("midi out"), name (deviceName) { } @@ -52,7 +49,7 @@ void MidiOutput::sendBlockOfMessagesNow (const MidiBuffer& buffer) } void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer, - const double millisecondCounterToStartAt, + double millisecondCounterToStartAt, double samplesPerSecondForBuffer) { // You've got to call startBackgroundThread() for this to actually work.. @@ -61,18 +58,15 @@ void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer, // this needs to be a value in the future - RTFM for this method! jassert (millisecondCounterToStartAt > 0); - const double timeScaleFactor = 1000.0 / samplesPerSecondForBuffer; - - MidiBuffer::Iterator i (buffer); + auto timeScaleFactor = 1000.0 / samplesPerSecondForBuffer; const uint8* data; int len, time; - while (i.getNextEvent (data, len, time)) + for (MidiBuffer::Iterator i (buffer); i.getNextEvent (data, len, time);) { - const double eventTime = millisecondCounterToStartAt + timeScaleFactor * time; - - PendingMessage* const m = new PendingMessage (data, len, eventTime); + auto eventTime = millisecondCounterToStartAt + timeScaleFactor * time; + auto* m = new PendingMessage (data, len, eventTime); const ScopedLock sl (lock); @@ -83,7 +77,7 @@ void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer, } else { - PendingMessage* mm = firstMessage; + auto* mm = firstMessage; while (mm->next != nullptr && mm->next->message.getTimeStamp() <= eventTime) mm = mm->next; @@ -102,7 +96,7 @@ void MidiOutput::clearAllPendingMessages() while (firstMessage != nullptr) { - PendingMessage* const m = firstMessage; + auto* m = firstMessage; firstMessage = firstMessage->next; delete m; } @@ -150,7 +144,7 @@ void MidiOutput::run() if (message != nullptr) { - const std::unique_ptr messageDeleter (message); + std::unique_ptr messageDeleter (message); if (eventTime > now) { diff --git a/modules/juce_audio_devices/midi_io/juce_MidiOutput.h b/modules/juce_audio_devices/midi_io/juce_MidiOutput.h index 3a102990b1..5c8e621416 100644 --- a/modules/juce_audio_devices/midi_io/juce_MidiOutput.h +++ b/modules/juce_audio_devices/midi_io/juce_MidiOutput.h @@ -133,7 +133,7 @@ private: void* internal = nullptr; CriticalSection lock; struct PendingMessage; - PendingMessage* firstMessage; + PendingMessage* firstMessage = nullptr; String name; MidiOutput (const String& midiName); // These objects are created with the openDevice() method. diff --git a/modules/juce_audio_devices/native/juce_win32_Midi.cpp b/modules/juce_audio_devices/native/juce_win32_Midi.cpp index 0384dae77b..13a5159cf5 100644 --- a/modules/juce_audio_devices/native/juce_win32_Midi.cpp +++ b/modules/juce_audio_devices/native/juce_win32_Midi.cpp @@ -48,244 +48,330 @@ struct MidiServiceType virtual StringArray getDevices (bool) = 0; virtual int getDefaultDeviceIndex (bool) = 0; - virtual InputWrapper* createInputWrapper (MidiInput*, int, MidiInputCallback*) = 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 +struct Win32MidiService : public MidiServiceType, + private Timer { + Win32MidiService() {} + + StringArray getDevices (bool isInput) override + { + return isInput ? Win32InputWrapper::getDevices() + : Win32OutputWrapper::getDevices(); + } + + int getDefaultDeviceIndex (bool isInput) override + { + return isInput ? Win32InputWrapper::getDefaultDeviceIndex() + : Win32OutputWrapper::getDefaultDeviceIndex(); + } + + InputWrapper* createInputWrapper (MidiInput& input, int index, MidiInputCallback& callback) override + { + return new Win32InputWrapper (*this, input, index, callback); + } + + OutputWrapper* createOutputWrapper (int index) override + { + return new Win32OutputWrapper (*this, index); + } + private: - struct WindowsInputWrapper : public InputWrapper + struct Win32InputWrapper; + + //============================================================================== + struct MidiInCollector : public ReferenceCountedObject { - struct MidiInCollector + MidiInCollector (Win32MidiService& s, const String& name) : deviceName (name), midiService (s) {} + + ~MidiInCollector() { - MidiInCollector (WindowsMidiService& s, - MidiInput* const inputDevice, - MidiInputCallback& cb) - : midiService (s), - input (inputDevice), - callback (cb) - { - } + stop(); - ~MidiInCollector() + if (deviceHandle != 0) { - stop(); - - if (deviceHandle != 0) + for (int count = 5; --count >= 0;) { - for (int count = 5; --count >= 0;) - { - if (midiInClose (deviceHandle) == MMSYSERR_NOERROR) - break; + if (midiInClose (deviceHandle) == MMSYSERR_NOERROR) + break; - Sleep (20); - } + Sleep (20); } } + } + + using Ptr = ReferenceCountedObjectPtr; + + void addClient (Win32InputWrapper* c) + { + const ScopedLock sl (clientLock); + jassert (! clients.contains (c)); + clients.add (c); + } + + void removeClient (Win32InputWrapper* c) + { + const ScopedLock sl (clientLock); + clients.removeFirstMatchingValue (c); + startOrStop(); + midiService.asyncCheckForUnusedCollectors(); + } - void handleMessage (const uint8* bytes, const uint32 timeStamp) + void handleMessage (const uint8* bytes, uint32 timeStamp) + { + if (bytes[0] >= 0x80 && isStarted.load()) { - if (bytes[0] >= 0x80 && isStarted) { - concatenator.pushMidiData (bytes, - MidiMessage::getMessageLengthFromFirstByte (bytes[0]), - convertTimeStamp (timeStamp), - input, - callback); - writeFinishedBlocks(); + auto len = MidiMessage::getMessageLengthFromFirstByte (bytes[0]); + auto time = convertTimeStamp (timeStamp); + const ScopedLock sl (clientLock); + + for (auto* c : clients) + c->pushMidiData (bytes, len, time); } + + writeFinishedBlocks(); } + } - void handleSysEx (MIDIHDR* const hdr, const uint32 timeStamp) + void handleSysEx (MIDIHDR* hdr, uint32 timeStamp) + { + if (isStarted.load() && hdr->dwBytesRecorded > 0) { - if (isStarted && hdr->dwBytesRecorded > 0) { - concatenator.pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded, - convertTimeStamp (timeStamp), input, callback); - writeFinishedBlocks(); + auto time = convertTimeStamp (timeStamp); + const ScopedLock sl (clientLock); + + for (auto* c : clients) + c->pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded, time); } + + writeFinishedBlocks(); } + } - void start() - { - if (deviceHandle != 0 && ! isStarted) - { - midiService.activeMidiCollectors.addIfNotAlreadyThere (this); + void startOrStop() + { + const ScopedLock sl (clientLock); - for (int i = 0; i < (int) numHeaders; ++i) - { - headers[i].prepare (deviceHandle); - headers[i].write (deviceHandle); - } + if (countRunningClients() == 0) + stop(); + else + start(); + } - startTime = Time::getMillisecondCounterHiRes(); - MMRESULT res = midiInStart (deviceHandle); + void start() + { + if (deviceHandle != 0 && ! isStarted.load()) + { + activeMidiCollectors.addIfNotAlreadyThere (this); - if (res == MMSYSERR_NOERROR) - { - concatenator.reset(); - isStarted = true; - } - else - { - unprepareAllHeaders(); - } + for (int i = 0; i < (int) numHeaders; ++i) + { + headers[i].prepare (deviceHandle); + headers[i].write (deviceHandle); } - } - void stop() - { - if (isStarted) - { - isStarted = false; - midiInReset (deviceHandle); - midiInStop (deviceHandle); - midiService.activeMidiCollectors.removeFirstMatchingValue (this); + startTime = Time::getMillisecondCounterHiRes(); + auto res = midiInStart (deviceHandle); + + if (res == MMSYSERR_NOERROR) + isStarted = true; + else unprepareAllHeaders(); - concatenator.reset(); - } } + } - static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, - DWORD_PTR midiMessage, DWORD_PTR timeStamp) + void stop() + { + if (isStarted.load()) { - auto* collector = reinterpret_cast (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); - } + isStarted = false; + midiInReset (deviceHandle); + midiInStop (deviceHandle); + activeMidiCollectors.removeFirstMatchingValue (this); + unprepareAllHeaders(); } + } - HMIDIIN deviceHandle = 0; - - private: - WindowsMidiService& midiService; - MidiInput* input; - MidiInputCallback& callback; - MidiDataConcatenator concatenator { 4096 }; - bool volatile isStarted = false; - double startTime = 0; + static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, + DWORD_PTR midiMessage, DWORD_PTR timeStamp) + { + auto* collector = reinterpret_cast (dwInstance); - struct MidiHeader + // This is primarily a check for the collector being a dangling + // pointer, as the callback can sometimes be delayed + if (activeMidiCollectors.contains (collector)) { - MidiHeader() {} + if (uMsg == MIM_DATA) + collector->handleMessage ((const uint8*) &midiMessage, (uint32) timeStamp); + else if (uMsg == MIM_LONGDATA) + collector->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp); + } + } - void prepare (HMIDIIN device) - { - zerostruct (hdr); - hdr.lpData = data; - hdr.dwBufferLength = (DWORD) numElementsInArray (data); + String deviceName; + HMIDIIN deviceHandle = 0; - midiInPrepareHeader (device, &hdr, sizeof (hdr)); - } + private: + Win32MidiService& midiService; + CriticalSection clientLock; + Array clients; + std::atomic isStarted { false }; + double startTime = 0; - 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); + // This static array is used to prevent occasional callbacks to objects that are + // in the process of being deleted + static Array activeMidiCollectors; - jassert (c >= 0); - } - } + int countRunningClients() const + { + int num = 0; - void write (HMIDIIN device) - { - hdr.dwBytesRecorded = 0; - midiInAddBuffer (device, &hdr, sizeof (hdr)); - } + for (auto* c : clients) + if (c->started) + ++num; - void writeIfFinished (HMIDIIN device) - { - if ((hdr.dwFlags & WHDR_DONE) != 0) - write (device); - } + return num; + } - MIDIHDR hdr; - char data [256]; + struct MidiHeader + { + MidiHeader() {} - JUCE_DECLARE_NON_COPYABLE (MidiHeader) - }; + void prepare (HMIDIIN device) + { + zerostruct (hdr); + hdr.lpData = data; + hdr.dwBufferLength = (DWORD) numElementsInArray (data); - enum { numHeaders = 32 }; - MidiHeader headers [numHeaders]; + midiInPrepareHeader (device, &hdr, sizeof (hdr)); + } - void writeFinishedBlocks() + void unprepare (HMIDIIN device) { - for (int i = 0; i < (int) numHeaders; ++i) - headers[i].writeIfFinished (deviceHandle); + 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 unprepareAllHeaders() + void write (HMIDIIN device) { - for (int i = 0; i < (int) numHeaders; ++i) - headers[i].unprepare (deviceHandle); + hdr.dwBytesRecorded = 0; + midiInAddBuffer (device, &hdr, sizeof (hdr)); } - double convertTimeStamp (uint32 timeStamp) + void writeIfFinished (HMIDIIN device) { - auto t = startTime + timeStamp; - auto now = Time::getMillisecondCounterHiRes(); + if ((hdr.dwFlags & WHDR_DONE) != 0) + write (device); + } - if (t > now) - { - if (t > now + 2.0) - startTime -= 1.0; + MIDIHDR hdr; + char data[256]; - t = now; - } + 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); + } - return t * 0.001; + 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; } - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInCollector) - }; + return t * 0.001; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInCollector) + }; + + //============================================================================== + struct Win32InputWrapper : public InputWrapper + { + Win32InputWrapper (Win32MidiService& parentService, + MidiInput& midiInput, int index, MidiInputCallback& c) + : input (midiInput), callback (c) + { + collector = getOrCreateCollector (parentService, index); + collector->addClient (this); + } - //============================================================================== - WindowsInputWrapper (WindowsMidiService& parentService, - MidiInput* const input, - const int index, - MidiInputCallback* const callback) + ~Win32InputWrapper() + { + collector->removeClient (this); + } + + static MidiInCollector::Ptr getOrCreateCollector (Win32MidiService& parentService, int index) { auto names = getDevices(); - UINT deviceId = MIDI_MAPPER; + UINT deviceID = MIDI_MAPPER; + String deviceName; if (isPositiveAndBelow (index, names.size())) { deviceName = names[index]; - deviceId = index; + deviceID = index; } - collector.reset (new MidiInCollector (parentService, input, *callback)); + const ScopedLock sl (parentService.activeCollectorLock); + + for (auto& c : parentService.activeCollectors) + if (c->deviceName == deviceName) + return c; + + MidiInCollector::Ptr c (new MidiInCollector (parentService, deviceName)); HMIDIIN h; - MMRESULT err = midiInOpen (&h, deviceId, - (DWORD_PTR) &MidiInCollector::midiInCallback, - (DWORD_PTR) (MidiInCollector*) collector.get(), - CALLBACK_FUNCTION); + auto err = midiInOpen (&h, deviceID, + (DWORD_PTR) &MidiInCollector::midiInCallback, + (DWORD_PTR) (MidiInCollector*) c.get(), + CALLBACK_FUNCTION); if (err != MMSYSERR_NOERROR) throw std::runtime_error ("Failed to create Windows input device wrapper"); - collector->deviceHandle = h; + c->deviceHandle = h; + parentService.activeCollectors.add (c); + return c; } - ~WindowsInputWrapper() {} - static StringArray getDevices() { StringArray s; - const UINT num = midiInGetNumDevs(); + auto num = midiInGetNumDevs(); for (UINT i = 0; i < num; ++i) { @@ -299,78 +385,81 @@ private: return s; } - static int getDefaultDeviceIndex() - { - return 0; - } + static int getDefaultDeviceIndex() { return 0; } + + void start() override { started = true; concatenator.reset(); collector->startOrStop(); } + void stop() override { started = false; collector->startOrStop(); concatenator.reset(); } - void start() override { collector->start(); } - void stop() override { collector->stop(); } + String getDeviceName() override { return collector->deviceName; } - String getDeviceName() override + void pushMidiData (const void* inputData, int numBytes, double time) { - return deviceName; + concatenator.pushMidiData (inputData, numBytes, time, &input, callback); } - String deviceName; - std::unique_ptr collector; + MidiInput& input; + MidiInputCallback& callback; + MidiDataConcatenator concatenator { 4096 }; + MidiInCollector::Ptr collector; + bool started = false; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsInputWrapper) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32InputWrapper) }; //============================================================================== - struct WindowsOutputWrapper : public OutputWrapper + struct MidiOutHandle : public ReferenceCountedObject { - struct MidiOutHandle : public ReferenceCountedObject - { - using Ptr = ReferenceCountedObjectPtr; + using Ptr = ReferenceCountedObjectPtr; - MidiOutHandle (WindowsMidiService& parent, UINT id, HMIDIOUT h) - : owner (parent), deviceId (id), handle (h) - { - owner.activeOutputHandles.add (this); - } + MidiOutHandle (Win32MidiService& parent, const String& name, HMIDIOUT h) + : owner (parent), deviceName (name), handle (h) + { + owner.activeOutputHandles.add (this); + } - ~MidiOutHandle() - { - if (handle != nullptr) - midiOutClose (handle); + ~MidiOutHandle() + { + if (handle != nullptr) + midiOutClose (handle); - owner.activeOutputHandles.removeFirstMatchingValue (this); - } + owner.activeOutputHandles.removeFirstMatchingValue (this); + } - WindowsMidiService& owner; - UINT deviceId; - HMIDIOUT handle; + Win32MidiService& owner; + String deviceName; + HMIDIOUT handle; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutHandle) - }; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutHandle) + }; - WindowsOutputWrapper (WindowsMidiService& p, int index) : parent (p) + //============================================================================== + struct Win32OutputWrapper : public OutputWrapper + { + Win32OutputWrapper (Win32MidiService& p, int index) : parent (p) { auto names = getDevices(); - UINT deviceId = MIDI_MAPPER; + UINT deviceID = MIDI_MAPPER; if (isPositiveAndBelow (index, names.size())) { deviceName = names[index]; - deviceId = index; + deviceID = index; } - if (deviceId == MIDI_MAPPER) + if (deviceID == MIDI_MAPPER) { - // use the microsoft sw synth as a default - best not to allow deviceId + // 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; + deviceID = (UINT) i; } for (int i = parent.activeOutputHandles.size(); --i >= 0;) { auto* activeHandle = parent.activeOutputHandles.getUnchecked (i); - if (activeHandle->deviceId == deviceId) + if (activeHandle->deviceName == deviceName) { han = activeHandle; return; @@ -380,11 +469,11 @@ private: for (int i = 4; --i >= 0;) { HMIDIOUT h = 0; - MMRESULT res = midiOutOpen (&h, deviceId, 0, 0, CALLBACK_NULL); + auto res = midiOutOpen (&h, deviceID, 0, 0, CALLBACK_NULL); if (res == MMSYSERR_NOERROR) { - han = new MidiOutHandle (parent, deviceId, h); + han = new MidiOutHandle (parent, deviceName, h); return; } @@ -408,7 +497,7 @@ private: if (midiOutPrepareHeader (han->handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR) { - MMRESULT res = midiOutLongMsg (han->handle, &h, sizeof (MIDIHDR)); + auto res = midiOutLongMsg (han->handle, &h, sizeof (MIDIHDR)); if (res == MMSYSERR_NOERROR) { @@ -444,7 +533,7 @@ private: static Array getDeviceCaps() { Array devices; - const UINT num = midiOutGetNumDevs(); + auto num = midiOutGetNumDevs(); for (UINT i = 0; i < num; ++i) { @@ -483,54 +572,42 @@ private: return 0; } - String getDeviceName() override - { - return deviceName; - } + String getDeviceName() override { return deviceName; } - WindowsMidiService& parent; + Win32MidiService& parent; String deviceName; - MidiOutHandle::Ptr han; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsOutputWrapper) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32OutputWrapper) }; -public: - WindowsMidiService() {} - - StringArray getDevices (bool isInput) override + //============================================================================== + void asyncCheckForUnusedCollectors() { - return isInput ? WindowsInputWrapper::getDevices() - : WindowsOutputWrapper::getDevices(); + startTimer (500); } - int getDefaultDeviceIndex (bool isInput) override + void timerCallback() override { - return isInput ? WindowsInputWrapper::getDefaultDeviceIndex() - : WindowsOutputWrapper::getDefaultDeviceIndex(); - } + stopTimer(); - InputWrapper* createInputWrapper (MidiInput* input, int index, MidiInputCallback* callback) override - { - return new WindowsInputWrapper (*this, input, index, callback); - } + const ScopedLock sl (activeCollectorLock); - OutputWrapper* createOutputWrapper (int index) override - { - return new WindowsOutputWrapper (*this, index); + for (int i = activeCollectors.size(); --i >= 0;) + if (activeCollectors.getObjectPointer(i)->getReferenceCount() == 1) + activeCollectors.remove (i); } -private: - Array activeMidiCollectors; - Array activeOutputHandles; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMidiService) + CriticalSection activeCollectorLock; + ReferenceCountedArray activeCollectors; + Array activeOutputHandles; }; +Array Win32MidiService::MidiInCollector::activeMidiCollectors; + +//============================================================================== //============================================================================== #if JUCE_USE_WINRT_MIDI - using namespace Microsoft::WRL; using namespace ABI::Windows::Foundation; @@ -538,21 +615,71 @@ using namespace ABI::Windows::Devices::Midi; using namespace ABI::Windows::Devices::Enumeration; using namespace ABI::Windows::Storage::Streams; +//============================================================================== class WinRTMidiService : public MidiServiceType { -private: +public: + //============================================================================== + WinRTMidiService() + { + if (! WinRTWrapper::getInstance()->isInitialised()) + throw std::runtime_error ("Failed to initialise the WinRT wrapper"); + + midiInFactory = WinRTWrapper::getInstance()->getWRLFactory (&RuntimeClass_Windows_Devices_Midi_MidiInPort[0]); + + if (midiInFactory == nullptr) + throw std::runtime_error ("Failed to create midi in factory"); + + midiOutFactory = WinRTWrapper::getInstance()->getWRLFactory (&RuntimeClass_Windows_Devices_Midi_MidiOutPort[0]); + + if (midiOutFactory == nullptr) + throw std::runtime_error ("Failed to create midi out factory"); + + inputDeviceWatcher.reset (new MidiIODeviceWatcher (midiInFactory)); + + if (! inputDeviceWatcher->start()) + throw std::runtime_error ("Failed to start midi input device watcher"); + + outputDeviceWatcher.reset (new MidiIODeviceWatcher (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); + } + template struct MidiIODeviceWatcher { struct DeviceInfo { - String name; - String id; + String name, id; bool isDefault = false; }; - MidiIODeviceWatcher (ComSmartPtr& comFactory) - : factory (comFactory) + MidiIODeviceWatcher (ComSmartPtr& comFactory) : factory (comFactory) { } @@ -564,21 +691,23 @@ private: bool start() { HSTRING deviceSelector; - HRESULT hr = factory->GetDeviceSelector (&deviceSelector); + auto hr = factory->GetDeviceSelector (&deviceSelector); + if (FAILED (hr)) return false; auto deviceInformationFactory = WinRTWrapper::getInstance()->getWRLFactory (&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 + struct DeviceEnumerationThread : public Thread { - public: DeviceEnumerationThread (String threadName, MidiIODeviceWatcher& p) : Thread (threadName), parent (p) {} @@ -613,7 +742,6 @@ private: parent.watcher->remove_EnumerationCompleted (deviceEnumerationCompletedToken); } - private: MidiIODeviceWatcher& parent; WaitableEvent enumerationCompleted; }; @@ -632,7 +760,8 @@ private: if (deviceAddedToken.value != 0) { - HRESULT hr = watcher->remove_Added (deviceAddedToken); + auto hr = watcher->remove_Added (deviceAddedToken); + if (FAILED (hr)) return false; @@ -641,14 +770,16 @@ private: if (deviceRemovedToken.value != 0) { - HRESULT hr = watcher->remove_Removed (deviceRemovedToken); + auto hr = watcher->remove_Removed (deviceRemovedToken); + if (FAILED (hr)) return false; deviceRemovedToken.value = 0; } - HRESULT hr = watcher->Stop(); + auto hr = watcher->Stop(); + if (FAILED (hr)) return false; @@ -659,7 +790,8 @@ private: HRESULT addDevice (IDeviceInformation* addedDeviceInfo) { boolean isEnabled; - HRESULT hr = addedDeviceInfo->get_IsEnabled (&isEnabled); + auto hr = addedDeviceInfo->get_IsEnabled (&isEnabled); + if (FAILED (hr)) return S_OK; @@ -672,6 +804,7 @@ private: HSTRING name; hr = addedDeviceInfo->get_Name (&name); + if (FAILED (hr)) return S_OK; @@ -679,6 +812,7 @@ private: HSTRING id; hr = addedDeviceInfo->get_Id (&id); + if (FAILED (hr)) return S_OK; @@ -686,13 +820,12 @@ private: boolean isDefault; hr = addedDeviceInfo->get_IsDefault (&isDefault); + if (FAILED (hr)) return S_OK; info.isDefault = isDefault != 0; - connectedDevices.add (info); - return S_OK; } @@ -702,7 +835,7 @@ private: HSTRING removedDeviceIdHstr; removedDeviceInfo->get_Id (&removedDeviceIdHstr); - String removedDeviceId = WinRTWrapper::getInstance()->hStringToString (removedDeviceIdHstr); + auto removedDeviceId = WinRTWrapper::getInstance()->hStringToString (removedDeviceIdHstr); for (int i = 0; i < connectedDevices.size(); ++i) { @@ -724,6 +857,7 @@ private: } StringArray result; + for (auto info : lastQueriedConnectedDevices.get()) result.add (info.name); @@ -733,6 +867,7 @@ private: int getDefaultDeviceIndex() { auto& lastDevices = lastQueriedConnectedDevices.get(); + for (int i = 0; i < lastDevices.size(); ++i) if (lastDevices[i].isDefault) return i; @@ -740,7 +875,7 @@ private: return 0; } - String getDeviceNameFromIndex (const int index) + String getDeviceNameFromIndex (int index) { if (isPositiveAndBelow (index, lastQueriedConnectedDevices.get().size())) return lastQueriedConnectedDevices.get()[index].name; @@ -748,7 +883,7 @@ private: return {}; } - String getDeviceID (const String name) + String getDeviceID (const String& name) { const ScopedLock lock (deviceChanges); @@ -773,16 +908,15 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher); }; + //============================================================================== template - class OpenMidiPortThread : public Thread + struct OpenMidiPortThread : public Thread { - public: - OpenMidiPortThread (String threadName, - String midiDeviceId, + OpenMidiPortThread (String threadName, String midiDeviceID, ComSmartPtr& comFactory, ComSmartPtr& comPort) : Thread (threadName), - deviceId (midiDeviceId), + deviceID (midiDeviceID), factory (comFactory), port (comPort) { @@ -790,13 +924,15 @@ private: ~OpenMidiPortThread() { + stopThread (2000); } void run() override { - WinRTWrapper::ScopedHString hDeviceId (deviceId); + WinRTWrapper::ScopedHString hDeviceId (deviceID); ComSmartPtr> asyncOp; - HRESULT hr = factory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress()); + auto hr = factory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress()); + if (FAILED (hr)) return; @@ -806,7 +942,8 @@ private: if (asyncOpPtr == nullptr) return E_ABORT; - HRESULT hr = asyncOpPtr->GetResults (port.resetAndGetPointerAddress()); + auto hr = asyncOpPtr->GetResults (port.resetAndGetPointerAddress()); + if (FAILED (hr)) return hr; @@ -821,30 +958,28 @@ private: portOpened.wait (2000); } - const String deviceId; + const String deviceID; ComSmartPtr& factory; ComSmartPtr& port; - WaitableEvent portOpened { true }; }; + //============================================================================== struct WinRTInputWrapper : public InputWrapper { - WinRTInputWrapper (WinRTMidiService& service, - MidiInput* const input, - const int index, - MidiInputCallback& cb) + WinRTInputWrapper (WinRTMidiService& service, MidiInput& input, int index, MidiInputCallback& cb) : inputDevice (input), - callback (cb), - concatenator (4096) + callback (cb) { 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); + auto deviceID = service.inputDeviceWatcher->getDeviceID (deviceName); + if (deviceID.isEmpty()) throw std::runtime_error ("Device unavailable"); @@ -854,16 +989,18 @@ private: 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 ( + auto hr = midiInPort->add_MessageReceived ( Callback> ( [this] (IMidiInPort*, IMidiMessageReceivedEventArgs* args) { return midiInMessageReceived (args); } ).Get(), &midiInMessageToken); + if (FAILED (hr)) throw std::runtime_error ("Failed to set midi input callback"); } @@ -878,7 +1015,7 @@ private: void start() override { - if (!isStarted) + if (! isStarted) { concatenator.reset(); isStarted = true; @@ -894,10 +1031,7 @@ private: } } - String getDeviceName() override - { - return deviceName; - } + String getDeviceName() override { return deviceName; } HRESULT midiInMessageReceived (IMidiMessageReceivedEventArgs* args) { @@ -905,50 +1039,53 @@ private: return S_OK; ComSmartPtr message; - HRESULT hr = args->get_Message (message.resetAndGetPointerAddress()); + auto hr = args->get_Message (message.resetAndGetPointerAddress()); + if (FAILED (hr)) return hr; ComSmartPtr buffer; hr = message->get_RawData (buffer.resetAndGetPointerAddress()); + if (FAILED (hr)) return hr; ComSmartPtr 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 (×pan); + if (FAILED (hr)) return hr; - concatenator.pushMidiData (bufferData, - numBytes, + concatenator.pushMidiData (bufferData, numBytes, convertTimeStamp (timespan.Duration), - inputDevice, - callback); - + &inputDevice, callback); return S_OK; } double convertTimeStamp (int64 timestamp) { - const auto millisecondsSinceStart = static_cast (timestamp) / 10000.0; - double t = startTime + millisecondsSinceStart; + auto millisecondsSinceStart = static_cast (timestamp) / 10000.0; + auto t = startTime + millisecondsSinceStart; + auto now = Time::getMillisecondCounterHiRes(); - const double now = Time::getMillisecondCounterHiRes(); if (t > now) { if (t > now + 2.0) @@ -960,10 +1097,10 @@ private: return t * 0.001; } - MidiInput* inputDevice; + MidiInput& inputDevice; MidiInputCallback& callback; String deviceName; - MidiDataConcatenator concatenator; + MidiDataConcatenator concatenator { 4096 }; ComSmartPtr midiInPort; EventRegistrationToken midiInMessageToken { 0 }; @@ -973,17 +1110,20 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTInputWrapper); }; + //============================================================================== struct WinRTOutputWrapper : public OutputWrapper { - WinRTOutputWrapper (WinRTMidiService& service, const int index) + WinRTOutputWrapper (WinRTMidiService& service, 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); + auto deviceID = service.outputDeviceWatcher->getDeviceID (deviceName); + if (deviceID.isEmpty()) throw std::runtime_error ("Device unavailable"); @@ -993,22 +1133,27 @@ private: 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 (&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 (65536), buffer.resetAndGetPointerAddress()); + auto hr = bufferFactory->Create (static_cast (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"); } @@ -1017,20 +1162,17 @@ private: void sendMessageNow (const MidiMessage& message) override { - const UINT32 numBytes = message.getRawDataSize(); - HRESULT hr = buffer->put_Length (numBytes); + auto numBytes = message.getRawDataSize(); + auto 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 getDeviceName() override { return deviceName; } String deviceName; ComSmartPtr midiOutPort; @@ -1041,55 +1183,6 @@ private: 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 (&RuntimeClass_Windows_Devices_Midi_MidiInPort[0]); - if (midiInFactory == nullptr) - throw std::runtime_error ("Failed to create midi in factory"); - - midiOutFactory = WinRTWrapper::getInstance()->getWRLFactory (&RuntimeClass_Windows_Devices_Midi_MidiOutPort[0]); - if (midiOutFactory == nullptr) - throw std::runtime_error ("Failed to create midi out factory"); - - inputDeviceWatcher.reset (new MidiIODeviceWatcher (midiInFactory)); - if (! inputDeviceWatcher->start()) - throw std::runtime_error ("Failed to start midi input device watcher"); - - outputDeviceWatcher.reset (new MidiIODeviceWatcher (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 midiInFactory; ComSmartPtr midiOutFactory; @@ -1102,66 +1195,57 @@ public: #endif // JUCE_USE_WINRT_MIDI //============================================================================== -class MidiService : public DeletedAtShutdown +struct MidiService : public DeletedAtShutdown { -public: - ~MidiService(); + MidiService() + { + #if JUCE_USE_WINRT_MIDI + try + { + internal.reset (new WinRTMidiService()); + return; + } + catch (std::runtime_error&) {} + #endif - MidiServiceType* getService(); + internal.reset (new Win32MidiService()); + } + + ~MidiService() + { + clearSingletonInstance(); + } + + static MidiServiceType& getService() + { + jassert (getInstance()->internal != nullptr); + return *getInstance()->internal.get(); + } JUCE_DECLARE_SINGLETON (MidiService, false) private: - MidiService(); - std::unique_ptr internal; }; JUCE_IMPLEMENT_SINGLETON (MidiService) -MidiService::~MidiService() -{ - clearSingletonInstance(); -} - -MidiServiceType* MidiService::getService() -{ - return internal.get(); -} - -MidiService::MidiService() -{ - #if JUCE_USE_WINRT_MIDI - try - { - internal.reset (new WinRTMidiService()); - return; - } - catch (std::runtime_error&) - { - } - #endif - - internal.reset (new WindowsMidiService()); -} - //============================================================================== StringArray MidiInput::getDevices() { - return MidiService::getInstance()->getService()->getDevices (true); + return MidiService::getService().getDevices (true); } int MidiInput::getDefaultDeviceIndex() { - return MidiService::getInstance()->getService()->getDefaultDeviceIndex (true); + return MidiService::getService().getDefaultDeviceIndex (true); } -MidiInput::MidiInput (const String& deviceName) - : name (deviceName) +MidiInput::MidiInput (const String& deviceName) : name (deviceName) { } -MidiInput* MidiInput::openDevice (const int index, MidiInputCallback* const callback) +MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) { if (callback == nullptr) return nullptr; @@ -1171,7 +1255,7 @@ MidiInput* MidiInput::openDevice (const int index, MidiInputCallback* const call try { - wrapper.reset (MidiService::getInstance()->getService()->createInputWrapper (in.get(), index, callback)); + wrapper.reset (MidiService::getService().createInputWrapper (*in, index, *callback)); } catch (std::runtime_error&) { @@ -1194,21 +1278,21 @@ void MidiInput::stop() { static_cast (interna //============================================================================== StringArray MidiOutput::getDevices() { - return MidiService::getInstance()->getService()->getDevices (false); + return MidiService::getService().getDevices (false); } int MidiOutput::getDefaultDeviceIndex() { - return MidiService::getInstance()->getService()->getDefaultDeviceIndex (false); + return MidiService::getService().getDefaultDeviceIndex (false); } -MidiOutput* MidiOutput::openDevice (const int index) +MidiOutput* MidiOutput::openDevice (int index) { std::unique_ptr wrapper; try { - wrapper.reset (MidiService::getInstance()->getService()->createOutputWrapper (index)); + wrapper.reset (MidiService::getService().createOutputWrapper (index)); } catch (std::runtime_error&) {