mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
2038 lines
68 KiB
C++
2038 lines
68 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE framework.
|
|
Copyright (c) Raw Material Software Limited
|
|
|
|
JUCE is an open source framework subject to commercial or open source
|
|
licensing.
|
|
|
|
By downloading, installing, or using the JUCE framework, or combining the
|
|
JUCE framework with any other source code, object code, content or any other
|
|
copyrightable work, you agree to the terms of the JUCE End User Licence
|
|
Agreement, and all incorporated terms including the JUCE Privacy Policy and
|
|
the JUCE Website Terms of Service, as applicable, which will bind you. If you
|
|
do not agree to the terms of these agreements, we will not license the JUCE
|
|
framework to you, and you must discontinue the installation or download
|
|
process and cease use of the JUCE framework.
|
|
|
|
JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/
|
|
JUCE Privacy Policy: https://juce.com/juce-privacy-policy
|
|
JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/
|
|
|
|
Or:
|
|
|
|
You may also use this code under the terms of the AGPLv3:
|
|
https://www.gnu.org/licenses/agpl-3.0.en.html
|
|
|
|
THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL
|
|
WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF
|
|
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#ifndef DRV_QUERYDEVICEINTERFACE
|
|
#define DRV_RESERVED 0x0800
|
|
#define DRV_QUERYDEVICEINTERFACE (DRV_RESERVED + 12)
|
|
#define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13)
|
|
#endif
|
|
|
|
namespace juce
|
|
{
|
|
|
|
template <typename T>
|
|
class CheckedReference
|
|
{
|
|
public:
|
|
template <typename Ptr>
|
|
friend auto createCheckedReference (Ptr*);
|
|
|
|
void clear()
|
|
{
|
|
std::lock_guard lock { mutex };
|
|
ptr = nullptr;
|
|
}
|
|
|
|
template <typename Callback>
|
|
void access (Callback&& callback)
|
|
{
|
|
std::lock_guard lock { mutex };
|
|
callback (ptr);
|
|
}
|
|
|
|
private:
|
|
explicit CheckedReference (T* ptrIn) : ptr (ptrIn) {}
|
|
|
|
T* ptr;
|
|
std::mutex mutex;
|
|
};
|
|
|
|
template <typename Ptr>
|
|
auto createCheckedReference (Ptr* ptrIn)
|
|
{
|
|
return std::shared_ptr<CheckedReference<Ptr>> { new CheckedReference<Ptr> (ptrIn) };
|
|
}
|
|
|
|
class MidiInput::Pimpl
|
|
{
|
|
public:
|
|
virtual ~Pimpl() noexcept = default;
|
|
|
|
virtual String getDeviceIdentifier() = 0;
|
|
virtual String getDeviceName() = 0;
|
|
|
|
virtual void start() = 0;
|
|
virtual void stop() = 0;
|
|
};
|
|
|
|
class MidiOutput::Pimpl
|
|
{
|
|
public:
|
|
virtual ~Pimpl() noexcept = default;
|
|
|
|
virtual String getDeviceIdentifier() = 0;
|
|
virtual String getDeviceName() = 0;
|
|
|
|
virtual void sendMessageNow (const MidiMessage&) = 0;
|
|
};
|
|
|
|
struct MidiServiceType
|
|
{
|
|
MidiServiceType() = default;
|
|
virtual ~MidiServiceType() noexcept = default;
|
|
|
|
virtual Array<MidiDeviceInfo> getAvailableDevices (bool) = 0;
|
|
virtual MidiDeviceInfo getDefaultDevice (bool) = 0;
|
|
|
|
virtual MidiInput::Pimpl* createInputWrapper (MidiInput&, const String&, MidiInputCallback&) = 0;
|
|
virtual MidiOutput::Pimpl* createOutputWrapper (const String&) = 0;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType)
|
|
};
|
|
|
|
//==============================================================================
|
|
struct Win32MidiService final : public MidiServiceType,
|
|
private Timer
|
|
{
|
|
Win32MidiService() = default;
|
|
|
|
Array<MidiDeviceInfo> getAvailableDevices (bool isInput) override
|
|
{
|
|
return isInput ? Win32InputWrapper::getAvailableDevices()
|
|
: Win32OutputWrapper::getAvailableDevices();
|
|
}
|
|
|
|
MidiDeviceInfo getDefaultDevice (bool isInput) override
|
|
{
|
|
return isInput ? Win32InputWrapper::getDefaultDevice()
|
|
: Win32OutputWrapper::getDefaultDevice();
|
|
}
|
|
|
|
MidiInput::Pimpl* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override
|
|
{
|
|
return new Win32InputWrapper (*this, input, deviceIdentifier, callback);
|
|
}
|
|
|
|
MidiOutput::Pimpl* createOutputWrapper (const String& deviceIdentifier) override
|
|
{
|
|
return new Win32OutputWrapper (*this, deviceIdentifier);
|
|
}
|
|
|
|
private:
|
|
struct Win32InputWrapper;
|
|
|
|
//==============================================================================
|
|
struct MidiInCollector final : public ReferenceCountedObject
|
|
{
|
|
MidiInCollector (Win32MidiService& s, MidiDeviceInfo d)
|
|
: deviceInfo (d), midiService (s)
|
|
{
|
|
}
|
|
|
|
~MidiInCollector()
|
|
{
|
|
stop();
|
|
|
|
if (deviceHandle != nullptr)
|
|
{
|
|
for (int count = 5; --count >= 0;)
|
|
{
|
|
if (midiInClose (deviceHandle) == MMSYSERR_NOERROR)
|
|
break;
|
|
|
|
Sleep (20);
|
|
}
|
|
}
|
|
}
|
|
|
|
using Ptr = ReferenceCountedObjectPtr<MidiInCollector>;
|
|
|
|
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, uint32 timeStamp)
|
|
{
|
|
if (bytes[0] >= 0x80 && isStarted.load())
|
|
{
|
|
{
|
|
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* hdr, uint32 timeStamp)
|
|
{
|
|
if (isStarted.load() && hdr->dwBytesRecorded > 0)
|
|
{
|
|
{
|
|
auto time = convertTimeStamp (timeStamp);
|
|
const ScopedLock sl (clientLock);
|
|
|
|
for (auto* c : clients)
|
|
c->pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded, time);
|
|
}
|
|
|
|
writeFinishedBlocks();
|
|
}
|
|
}
|
|
|
|
void startOrStop()
|
|
{
|
|
const ScopedLock sl (clientLock);
|
|
|
|
if (countRunningClients() == 0)
|
|
stop();
|
|
else
|
|
start();
|
|
}
|
|
|
|
void start()
|
|
{
|
|
if (deviceHandle != nullptr && ! isStarted.load())
|
|
{
|
|
activeMidiCollectors.addIfNotAlreadyThere (this);
|
|
|
|
for (int i = 0; i < (int) numHeaders; ++i)
|
|
{
|
|
headers[i].prepare (deviceHandle);
|
|
headers[i].write (deviceHandle);
|
|
}
|
|
|
|
startTime = Time::getMillisecondCounterHiRes();
|
|
auto res = midiInStart (deviceHandle);
|
|
|
|
if (res == MMSYSERR_NOERROR)
|
|
isStarted = true;
|
|
else
|
|
unprepareAllHeaders();
|
|
}
|
|
}
|
|
|
|
void stop()
|
|
{
|
|
if (isStarted.load())
|
|
{
|
|
isStarted = false;
|
|
midiInReset (deviceHandle);
|
|
midiInStop (deviceHandle);
|
|
activeMidiCollectors.removeFirstMatchingValue (this);
|
|
unprepareAllHeaders();
|
|
}
|
|
}
|
|
|
|
static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance,
|
|
DWORD_PTR midiMessage, DWORD_PTR timeStamp)
|
|
{
|
|
auto* collector = reinterpret_cast<MidiInCollector*> (dwInstance);
|
|
|
|
// This is primarily a check for the collector being a dangling
|
|
// pointer, as the callback can sometimes be delayed
|
|
if (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);
|
|
}
|
|
}
|
|
|
|
MidiDeviceInfo deviceInfo;
|
|
HMIDIIN deviceHandle = nullptr;
|
|
|
|
private:
|
|
Win32MidiService& midiService;
|
|
CriticalSection clientLock;
|
|
Array<Win32InputWrapper*> clients;
|
|
std::atomic<bool> isStarted { false };
|
|
double startTime = 0;
|
|
|
|
// This static array is used to prevent occasional callbacks to objects that are
|
|
// in the process of being deleted
|
|
static Array<MidiInCollector*, CriticalSection> activeMidiCollectors;
|
|
|
|
int countRunningClients() const
|
|
{
|
|
int num = 0;
|
|
|
|
for (auto* c : clients)
|
|
if (c->started)
|
|
++num;
|
|
|
|
return num;
|
|
}
|
|
|
|
struct MidiHeader
|
|
{
|
|
MidiHeader() = default;
|
|
|
|
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)
|
|
};
|
|
|
|
//==============================================================================
|
|
template <class WrapperType>
|
|
struct Win32MidiDeviceQuery
|
|
{
|
|
virtual ~Win32MidiDeviceQuery() = default;
|
|
|
|
static Array<MidiDeviceInfo> getAvailableDevices()
|
|
{
|
|
StringArray deviceNames, deviceIDs;
|
|
auto deviceCaps = WrapperType::getDeviceCaps();
|
|
|
|
for (int i = 0; i < deviceCaps.size(); ++i)
|
|
{
|
|
deviceNames.add (deviceCaps[i].szPname);
|
|
|
|
auto identifier = getInterfaceIDForDevice ((UINT) i);
|
|
|
|
if (identifier.isNotEmpty())
|
|
deviceIDs.add (identifier);
|
|
else
|
|
deviceIDs.add (deviceNames[i]);
|
|
}
|
|
|
|
deviceNames.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
|
|
deviceIDs .appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
|
|
|
|
Array<MidiDeviceInfo> devices;
|
|
|
|
for (int i = 0; i < deviceNames.size(); ++i)
|
|
devices.add ({ deviceNames[i], deviceIDs[i] });
|
|
|
|
return devices;
|
|
}
|
|
|
|
private:
|
|
static String getInterfaceIDForDevice (UINT id)
|
|
{
|
|
ULONG size = 0;
|
|
|
|
if (WrapperType::sendMidiMessage ((UINT_PTR) id, DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR) &size, 0) == MMSYSERR_NOERROR)
|
|
{
|
|
WCHAR interfaceName[512] = {};
|
|
|
|
if (isPositiveAndBelow (size, sizeof (interfaceName))
|
|
&& WrapperType::sendMidiMessage ((UINT_PTR) id, DRV_QUERYDEVICEINTERFACE,
|
|
(DWORD_PTR) interfaceName, sizeof (interfaceName)) == MMSYSERR_NOERROR)
|
|
{
|
|
return interfaceName;
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
};
|
|
|
|
struct Win32InputWrapper final : public MidiInput::Pimpl,
|
|
public Win32MidiDeviceQuery<Win32InputWrapper>
|
|
{
|
|
Win32InputWrapper (Win32MidiService& parentService, MidiInput& midiInput, const String& deviceIdentifier, MidiInputCallback& c)
|
|
: input (midiInput), callback (c)
|
|
{
|
|
collector = getOrCreateCollector (parentService, deviceIdentifier);
|
|
collector->addClient (this);
|
|
}
|
|
|
|
~Win32InputWrapper() override
|
|
{
|
|
collector->removeClient (this);
|
|
}
|
|
|
|
static MidiInCollector::Ptr getOrCreateCollector (Win32MidiService& parentService, const String& deviceIdentifier)
|
|
{
|
|
UINT deviceID = MIDI_MAPPER;
|
|
String deviceName;
|
|
auto devices = getAvailableDevices();
|
|
|
|
for (int i = 0; i < devices.size(); ++i)
|
|
{
|
|
auto d = devices.getUnchecked (i);
|
|
|
|
if (d.identifier == deviceIdentifier)
|
|
{
|
|
deviceID = (UINT) i;
|
|
deviceName = d.name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
const ScopedLock sl (parentService.activeCollectorLock);
|
|
|
|
for (auto& c : parentService.activeCollectors)
|
|
if (c->deviceInfo.identifier == deviceIdentifier)
|
|
return c;
|
|
|
|
MidiInCollector::Ptr c (new MidiInCollector (parentService, { deviceName, deviceIdentifier }));
|
|
|
|
HMIDIIN h;
|
|
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");
|
|
|
|
c->deviceHandle = h;
|
|
parentService.activeCollectors.add (c);
|
|
return c;
|
|
}
|
|
|
|
static DWORD sendMidiMessage (UINT_PTR deviceID, UINT msg, DWORD_PTR arg1, DWORD_PTR arg2)
|
|
{
|
|
return midiInMessage ((HMIDIIN) deviceID, msg, arg1, arg2);
|
|
}
|
|
|
|
static Array<MIDIINCAPS> getDeviceCaps()
|
|
{
|
|
Array<MIDIINCAPS> devices;
|
|
|
|
for (UINT i = 0; i < midiInGetNumDevs(); ++i)
|
|
{
|
|
MIDIINCAPS mc = {};
|
|
|
|
if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
|
|
devices.add (mc);
|
|
}
|
|
|
|
return devices;
|
|
}
|
|
|
|
static MidiDeviceInfo getDefaultDevice() { return getAvailableDevices().getFirst(); }
|
|
|
|
void start() override { started = true; concatenator.reset(); collector->startOrStop(); }
|
|
void stop() override { started = false; collector->startOrStop(); concatenator.reset(); }
|
|
|
|
String getDeviceIdentifier() override { return collector->deviceInfo.identifier; }
|
|
String getDeviceName() override { return collector->deviceInfo.name; }
|
|
|
|
void pushMidiData (const void* inputData, int numBytes, double time)
|
|
{
|
|
concatenator.pushMidiData (inputData, numBytes, time, &input, callback);
|
|
}
|
|
|
|
MidiInput& input;
|
|
MidiInputCallback& callback;
|
|
MidiDataConcatenator concatenator { 4096 };
|
|
MidiInCollector::Ptr collector;
|
|
bool started = false;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32InputWrapper)
|
|
};
|
|
|
|
//==============================================================================
|
|
struct MidiOutHandle final : public ReferenceCountedObject
|
|
{
|
|
using Ptr = ReferenceCountedObjectPtr<MidiOutHandle>;
|
|
|
|
MidiOutHandle (Win32MidiService& parent, MidiDeviceInfo d, HMIDIOUT h)
|
|
: owner (parent), deviceInfo (d), handle (h)
|
|
{
|
|
owner.activeOutputHandles.add (this);
|
|
}
|
|
|
|
~MidiOutHandle()
|
|
{
|
|
if (handle != nullptr)
|
|
midiOutClose (handle);
|
|
|
|
owner.activeOutputHandles.removeFirstMatchingValue (this);
|
|
}
|
|
|
|
Win32MidiService& owner;
|
|
MidiDeviceInfo deviceInfo;
|
|
HMIDIOUT handle;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutHandle)
|
|
};
|
|
|
|
//==============================================================================
|
|
struct Win32OutputWrapper final : public MidiOutput::Pimpl,
|
|
public Win32MidiDeviceQuery<Win32OutputWrapper>
|
|
{
|
|
Win32OutputWrapper (Win32MidiService& p, const String& deviceIdentifier)
|
|
: parent (p)
|
|
{
|
|
auto devices = getAvailableDevices();
|
|
UINT deviceID = MIDI_MAPPER;
|
|
String deviceName;
|
|
|
|
for (int i = 0; i < devices.size(); ++i)
|
|
{
|
|
auto d = devices.getUnchecked (i);
|
|
|
|
if (d.identifier == deviceIdentifier)
|
|
{
|
|
deviceID = (UINT) i;
|
|
deviceName = d.name;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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 < devices.size(); ++i)
|
|
if (devices[i].name.containsIgnoreCase ("microsoft"))
|
|
deviceID = (UINT) i;
|
|
}
|
|
|
|
for (int i = parent.activeOutputHandles.size(); --i >= 0;)
|
|
{
|
|
auto* activeHandle = parent.activeOutputHandles.getUnchecked (i);
|
|
|
|
if (activeHandle->deviceInfo.identifier == deviceIdentifier)
|
|
{
|
|
han = activeHandle;
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int i = 4; --i >= 0;)
|
|
{
|
|
HMIDIOUT h = nullptr;
|
|
auto res = midiOutOpen (&h, deviceID, 0, 0, CALLBACK_NULL);
|
|
|
|
if (res == MMSYSERR_NOERROR)
|
|
{
|
|
han = new MidiOutHandle (parent, { deviceName, deviceIdentifier }, h);
|
|
return;
|
|
}
|
|
|
|
if (res == MMSYSERR_ALLOCATED)
|
|
Sleep (100);
|
|
else
|
|
break;
|
|
}
|
|
|
|
throw std::runtime_error ("Failed to create Windows output device wrapper");
|
|
}
|
|
|
|
void sendMessageNow (const MidiMessage& message) override
|
|
{
|
|
if (message.getRawDataSize() > 3 || message.isSysEx())
|
|
{
|
|
MIDIHDR h = {};
|
|
|
|
h.lpData = (char*) message.getRawData();
|
|
h.dwBytesRecorded = h.dwBufferLength = (DWORD) message.getRawDataSize();
|
|
|
|
if (midiOutPrepareHeader (han->handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR)
|
|
{
|
|
auto 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, *unalignedPointerCast<const unsigned int*> (message.getRawData())) != MIDIERR_NOTREADY)
|
|
break;
|
|
|
|
Sleep (1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static DWORD sendMidiMessage (UINT_PTR deviceID, UINT msg, DWORD_PTR arg1, DWORD_PTR arg2)
|
|
{
|
|
return midiOutMessage ((HMIDIOUT) deviceID, msg, arg1, arg2);
|
|
}
|
|
|
|
static Array<MIDIOUTCAPS> getDeviceCaps()
|
|
{
|
|
Array<MIDIOUTCAPS> devices;
|
|
|
|
for (UINT i = 0; i < midiOutGetNumDevs(); ++i)
|
|
{
|
|
MIDIOUTCAPS mc = {};
|
|
|
|
if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR)
|
|
devices.add (mc);
|
|
}
|
|
|
|
return devices;
|
|
}
|
|
|
|
static MidiDeviceInfo getDefaultDevice()
|
|
{
|
|
auto defaultIndex = []()
|
|
{
|
|
auto deviceCaps = getDeviceCaps();
|
|
|
|
for (int i = 0; i < deviceCaps.size(); ++i)
|
|
if ((deviceCaps[i].wTechnology & MOD_MAPPER) != 0)
|
|
return i;
|
|
|
|
return 0;
|
|
}();
|
|
|
|
return getAvailableDevices()[defaultIndex];
|
|
}
|
|
|
|
String getDeviceIdentifier() override { return han->deviceInfo.identifier; }
|
|
String getDeviceName() override { return han->deviceInfo.name; }
|
|
|
|
Win32MidiService& parent;
|
|
MidiOutHandle::Ptr han;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32OutputWrapper)
|
|
};
|
|
|
|
//==============================================================================
|
|
void asyncCheckForUnusedCollectors()
|
|
{
|
|
startTimer (10);
|
|
}
|
|
|
|
void timerCallback() override
|
|
{
|
|
stopTimer();
|
|
|
|
const ScopedLock sl (activeCollectorLock);
|
|
|
|
for (int i = activeCollectors.size(); --i >= 0;)
|
|
if (activeCollectors.getObjectPointer (i)->getReferenceCount() == 1)
|
|
activeCollectors.remove (i);
|
|
}
|
|
|
|
CriticalSection activeCollectorLock;
|
|
ReferenceCountedArray<MidiInCollector> activeCollectors;
|
|
Array<MidiOutHandle*> activeOutputHandles;
|
|
};
|
|
|
|
Array<Win32MidiService::MidiInCollector*, CriticalSection> Win32MidiService::MidiInCollector::activeMidiCollectors;
|
|
|
|
//==============================================================================
|
|
//==============================================================================
|
|
#if JUCE_USE_WINRT_MIDI
|
|
|
|
#ifndef JUCE_FORCE_WINRT_MIDI
|
|
#define JUCE_FORCE_WINRT_MIDI 0
|
|
#endif
|
|
|
|
#ifndef JUCE_WINRT_MIDI_LOGGING
|
|
#define JUCE_WINRT_MIDI_LOGGING 0
|
|
#endif
|
|
|
|
#if JUCE_WINRT_MIDI_LOGGING
|
|
#define JUCE_WINRT_MIDI_LOG(x) DBG(x)
|
|
#else
|
|
#define JUCE_WINRT_MIDI_LOG(x)
|
|
#endif
|
|
|
|
using namespace Microsoft::WRL;
|
|
|
|
using namespace ABI::Windows::Foundation;
|
|
using namespace ABI::Windows::Foundation::Collections;
|
|
using namespace ABI::Windows::Devices::Midi;
|
|
using namespace ABI::Windows::Devices::Enumeration;
|
|
using namespace ABI::Windows::Storage::Streams;
|
|
|
|
//==============================================================================
|
|
struct WinRTMidiService final : public MidiServiceType
|
|
{
|
|
public:
|
|
//==============================================================================
|
|
WinRTMidiService()
|
|
{
|
|
auto* wrtWrapper = WinRTWrapper::getInstance();
|
|
|
|
if (! wrtWrapper->isInitialised())
|
|
throw std::runtime_error ("Failed to initialise the WinRT wrapper");
|
|
|
|
midiInFactory = wrtWrapper->getWRLFactory<IMidiInPortStatics> (&RuntimeClass_Windows_Devices_Midi_MidiInPort[0]);
|
|
|
|
if (midiInFactory == nullptr)
|
|
throw std::runtime_error ("Failed to create midi in factory");
|
|
|
|
midiOutFactory = wrtWrapper->getWRLFactory<IMidiOutPortStatics> (&RuntimeClass_Windows_Devices_Midi_MidiOutPort[0]);
|
|
|
|
if (midiOutFactory == nullptr)
|
|
throw std::runtime_error ("Failed to create midi out factory");
|
|
|
|
// The WinRT BLE MIDI API doesn't provide callbacks when devices become disconnected,
|
|
// but it does require a disconnection via the API before a device will reconnect again.
|
|
// We can monitor the BLE connection state of paired devices to get callbacks when
|
|
// connections are broken.
|
|
bleDeviceWatcher.reset (new BLEDeviceWatcher());
|
|
|
|
if (! bleDeviceWatcher->start())
|
|
throw std::runtime_error ("Failed to start the BLE device watcher");
|
|
|
|
inputDeviceWatcher.reset (new MidiIODeviceWatcher<IMidiInPortStatics> (midiInFactory));
|
|
|
|
if (! inputDeviceWatcher->start())
|
|
throw std::runtime_error ("Failed to start the midi input device watcher");
|
|
|
|
outputDeviceWatcher.reset (new MidiIODeviceWatcher<IMidiOutPortStatics> (midiOutFactory));
|
|
|
|
if (! outputDeviceWatcher->start())
|
|
throw std::runtime_error ("Failed to start the midi output device watcher");
|
|
}
|
|
|
|
Array<MidiDeviceInfo> getAvailableDevices (bool isInput) override
|
|
{
|
|
return isInput ? inputDeviceWatcher ->getAvailableDevices()
|
|
: outputDeviceWatcher->getAvailableDevices();
|
|
}
|
|
|
|
MidiDeviceInfo getDefaultDevice (bool isInput) override
|
|
{
|
|
return isInput ? inputDeviceWatcher ->getDefaultDevice()
|
|
: outputDeviceWatcher->getDefaultDevice();
|
|
}
|
|
|
|
MidiInput::Pimpl* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override
|
|
{
|
|
return new WinRTInputWrapper (*this, input, deviceIdentifier, callback);
|
|
}
|
|
|
|
MidiOutput::Pimpl* createOutputWrapper (const String& deviceIdentifier) override
|
|
{
|
|
return new WinRTOutputWrapper (*this, deviceIdentifier);
|
|
}
|
|
|
|
private:
|
|
//==============================================================================
|
|
class DeviceCallbackHandler
|
|
{
|
|
public:
|
|
virtual ~DeviceCallbackHandler() {};
|
|
|
|
JUCE_COMCALL addDevice (IDeviceInformation*) = 0;
|
|
JUCE_COMCALL removeDevice (IDeviceInformationUpdate*) = 0;
|
|
JUCE_COMCALL updateDevice (IDeviceInformationUpdate*) = 0;
|
|
|
|
bool attach (HSTRING deviceSelector, DeviceInformationKind infoKind)
|
|
{
|
|
auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
|
|
|
|
if (wrtWrapper == nullptr)
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
|
|
return false;
|
|
}
|
|
|
|
auto deviceInfoFactory = wrtWrapper->getWRLFactory<IDeviceInformationStatics2> (&RuntimeClass_Windows_Devices_Enumeration_DeviceInformation[0]);
|
|
|
|
if (deviceInfoFactory == nullptr)
|
|
return false;
|
|
|
|
// A quick way of getting an IVector<HSTRING>...
|
|
auto requestedProperties = [wrtWrapper]
|
|
{
|
|
auto devicePicker = wrtWrapper->activateInstance<IDevicePicker> (&RuntimeClass_Windows_Devices_Enumeration_DevicePicker[0],
|
|
__uuidof (IDevicePicker));
|
|
jassert (devicePicker != nullptr);
|
|
|
|
IVector<HSTRING>* result;
|
|
auto hr = devicePicker->get_RequestedProperties (&result);
|
|
jassert (SUCCEEDED (hr));
|
|
|
|
hr = result->Clear();
|
|
jassert (SUCCEEDED (hr));
|
|
|
|
return result;
|
|
}();
|
|
|
|
StringArray propertyKeys ("System.Devices.ContainerId",
|
|
"System.Devices.Aep.ContainerId",
|
|
"System.Devices.Aep.IsConnected");
|
|
|
|
for (auto& key : propertyKeys)
|
|
{
|
|
WinRTWrapper::ScopedHString hstr (key);
|
|
auto hr = requestedProperties->Append (hstr.get());
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
jassertfalse;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ComSmartPtr<IIterable<HSTRING>> iter;
|
|
auto hr = requestedProperties->QueryInterface (__uuidof (IIterable<HSTRING>), (void**) iter.resetAndGetPointerAddress());
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
jassertfalse;
|
|
return false;
|
|
}
|
|
|
|
hr = deviceInfoFactory->CreateWatcherWithKindAqsFilterAndAdditionalProperties (deviceSelector, iter, infoKind,
|
|
watcher.resetAndGetPointerAddress());
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
jassertfalse;
|
|
return false;
|
|
}
|
|
|
|
enumerationThread.startThread();
|
|
|
|
return true;
|
|
};
|
|
|
|
void detach()
|
|
{
|
|
enumerationThread.stopThread (2000);
|
|
|
|
if (watcher == nullptr)
|
|
return;
|
|
|
|
auto hr = watcher->Stop();
|
|
jassert (SUCCEEDED (hr));
|
|
|
|
if (deviceAddedToken.value != 0)
|
|
{
|
|
hr = watcher->remove_Added (deviceAddedToken);
|
|
jassert (SUCCEEDED (hr));
|
|
deviceAddedToken.value = 0;
|
|
}
|
|
|
|
if (deviceUpdatedToken.value != 0)
|
|
{
|
|
hr = watcher->remove_Updated (deviceUpdatedToken);
|
|
jassert (SUCCEEDED (hr));
|
|
deviceUpdatedToken.value = 0;
|
|
}
|
|
|
|
if (deviceRemovedToken.value != 0)
|
|
{
|
|
hr = watcher->remove_Removed (deviceRemovedToken);
|
|
jassert (SUCCEEDED (hr));
|
|
deviceRemovedToken.value = 0;
|
|
}
|
|
|
|
watcher = nullptr;
|
|
}
|
|
|
|
template <typename InfoType>
|
|
IInspectable* getValueFromDeviceInfo (String key, InfoType* info)
|
|
{
|
|
__FIMapView_2_HSTRING_IInspectable* properties;
|
|
info->get_Properties (&properties);
|
|
|
|
boolean found = false;
|
|
WinRTWrapper::ScopedHString keyHstr (key);
|
|
auto hr = properties->HasKey (keyHstr.get(), &found);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
jassertfalse;
|
|
return nullptr;
|
|
}
|
|
|
|
if (! found)
|
|
return nullptr;
|
|
|
|
IInspectable* inspectable;
|
|
hr = properties->Lookup (keyHstr.get(), &inspectable);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
jassertfalse;
|
|
return nullptr;
|
|
}
|
|
|
|
return inspectable;
|
|
}
|
|
|
|
String getGUIDFromInspectable (IInspectable& inspectable)
|
|
{
|
|
ComSmartPtr<IReference<GUID>> guidRef;
|
|
auto hr = inspectable.QueryInterface (__uuidof (IReference<GUID>),
|
|
(void**) guidRef.resetAndGetPointerAddress());
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
jassertfalse;
|
|
return {};
|
|
}
|
|
|
|
GUID result;
|
|
hr = guidRef->get_Value (&result);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
jassertfalse;
|
|
return {};
|
|
}
|
|
|
|
OLECHAR* resultString;
|
|
StringFromCLSID (result, &resultString);
|
|
|
|
return resultString;
|
|
}
|
|
|
|
bool getBoolFromInspectable (IInspectable& inspectable)
|
|
{
|
|
ComSmartPtr<IReference<bool>> boolRef;
|
|
auto hr = inspectable.QueryInterface (__uuidof (IReference<bool>),
|
|
(void**) boolRef.resetAndGetPointerAddress());
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
jassertfalse;
|
|
return false;
|
|
}
|
|
|
|
boolean result;
|
|
hr = boolRef->get_Value (&result);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
jassertfalse;
|
|
return false;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
//==============================================================================
|
|
struct DeviceEnumerationThread final : public Thread
|
|
{
|
|
DeviceEnumerationThread (DeviceCallbackHandler& h,
|
|
ComSmartPtr<IDeviceWatcher>& w,
|
|
EventRegistrationToken& added,
|
|
EventRegistrationToken& removed,
|
|
EventRegistrationToken& updated)
|
|
: Thread ("WinRT Device Enumeration Thread"), handler (h), watcher (w),
|
|
deviceAddedToken (added), deviceRemovedToken (removed), deviceUpdatedToken (updated)
|
|
{}
|
|
|
|
void run() override
|
|
{
|
|
auto handlerPtr = std::addressof (handler);
|
|
|
|
watcher->add_Added (
|
|
Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformation*>> (
|
|
[handlerPtr] (IDeviceWatcher*, IDeviceInformation* info) { return handlerPtr->addDevice (info); }
|
|
).Get(),
|
|
&deviceAddedToken);
|
|
|
|
watcher->add_Removed (
|
|
Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>> (
|
|
[handlerPtr] (IDeviceWatcher*, IDeviceInformationUpdate* infoUpdate) { return handlerPtr->removeDevice (infoUpdate); }
|
|
).Get(),
|
|
&deviceRemovedToken);
|
|
|
|
watcher->add_Updated (
|
|
Callback<ITypedEventHandler<DeviceWatcher*, DeviceInformationUpdate*>> (
|
|
[handlerPtr] (IDeviceWatcher*, IDeviceInformationUpdate* infoUpdate) { return handlerPtr->updateDevice (infoUpdate); }
|
|
).Get(),
|
|
&deviceUpdatedToken);
|
|
|
|
watcher->Start();
|
|
}
|
|
|
|
DeviceCallbackHandler& handler;
|
|
ComSmartPtr<IDeviceWatcher>& watcher;
|
|
EventRegistrationToken& deviceAddedToken, deviceRemovedToken, deviceUpdatedToken;
|
|
};
|
|
|
|
//==============================================================================
|
|
ComSmartPtr<IDeviceWatcher> watcher;
|
|
|
|
EventRegistrationToken deviceAddedToken { 0 },
|
|
deviceRemovedToken { 0 },
|
|
deviceUpdatedToken { 0 };
|
|
|
|
DeviceEnumerationThread enumerationThread { *this, watcher,
|
|
deviceAddedToken,
|
|
deviceRemovedToken,
|
|
deviceUpdatedToken };
|
|
};
|
|
|
|
//==============================================================================
|
|
struct BLEDeviceWatcher final : private DeviceCallbackHandler
|
|
{
|
|
struct DeviceInfo
|
|
{
|
|
String containerID;
|
|
bool isConnected = false;
|
|
};
|
|
|
|
BLEDeviceWatcher() = default;
|
|
|
|
~BLEDeviceWatcher()
|
|
{
|
|
detach();
|
|
}
|
|
|
|
//==============================================================================
|
|
HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override
|
|
{
|
|
HSTRING deviceIDHst;
|
|
auto hr = addedDeviceInfo->get_Id (&deviceIDHst);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to query added BLE device ID!");
|
|
return S_OK;
|
|
}
|
|
|
|
auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
|
|
|
|
if (wrtWrapper == nullptr)
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
|
|
return false;
|
|
}
|
|
|
|
auto deviceID = wrtWrapper->hStringToString (deviceIDHst);
|
|
JUCE_WINRT_MIDI_LOG ("Detected paired BLE device: " << deviceID);
|
|
|
|
if (auto* containerIDValue = getValueFromDeviceInfo ("System.Devices.Aep.ContainerId", addedDeviceInfo))
|
|
{
|
|
auto containerID = getGUIDFromInspectable (*containerIDValue);
|
|
|
|
if (containerID.isNotEmpty())
|
|
{
|
|
DeviceInfo info = { containerID };
|
|
|
|
if (auto* connectedValue = getValueFromDeviceInfo ("System.Devices.Aep.IsConnected", addedDeviceInfo))
|
|
info.isConnected = getBoolFromInspectable (*connectedValue);
|
|
|
|
JUCE_WINRT_MIDI_LOG ("Adding BLE device: " << deviceID << " " << info.containerID
|
|
<< " " << (info.isConnected ? "connected" : "disconnected"));
|
|
devices.set (deviceID, info);
|
|
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
JUCE_WINRT_MIDI_LOG ("Failed to get a container ID for BLE device: " << deviceID);
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo) override
|
|
{
|
|
HSTRING removedDeviceIdHstr;
|
|
auto hr = removedDeviceInfo->get_Id (&removedDeviceIdHstr);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to query removed BLE device ID!");
|
|
return S_OK;
|
|
}
|
|
|
|
auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
|
|
|
|
if (wrtWrapper == nullptr)
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
|
|
return false;
|
|
}
|
|
|
|
auto removedDeviceId = wrtWrapper->hStringToString (removedDeviceIdHstr);
|
|
|
|
JUCE_WINRT_MIDI_LOG ("Removing BLE device: " << removedDeviceId);
|
|
|
|
{
|
|
const ScopedLock lock (deviceChanges);
|
|
|
|
if (devices.contains (removedDeviceId))
|
|
{
|
|
auto& info = devices.getReference (removedDeviceId);
|
|
listeners.call ([&info] (Listener& l) { l.bleDeviceDisconnected (info.containerID); });
|
|
devices.remove (removedDeviceId);
|
|
JUCE_WINRT_MIDI_LOG ("Removed BLE device: " << removedDeviceId);
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT updateDevice (IDeviceInformationUpdate* updatedDeviceInfo) override
|
|
{
|
|
HSTRING updatedDeviceIdHstr;
|
|
auto hr = updatedDeviceInfo->get_Id (&updatedDeviceIdHstr);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to query updated BLE device ID!");
|
|
return S_OK;
|
|
}
|
|
|
|
auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
|
|
|
|
if (wrtWrapper == nullptr)
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
|
|
return false;
|
|
}
|
|
|
|
auto updatedDeviceId = wrtWrapper->hStringToString (updatedDeviceIdHstr);
|
|
|
|
JUCE_WINRT_MIDI_LOG ("Updating BLE device: " << updatedDeviceId);
|
|
|
|
if (auto* connectedValue = getValueFromDeviceInfo ("System.Devices.Aep.IsConnected", updatedDeviceInfo))
|
|
{
|
|
auto isConnected = getBoolFromInspectable (*connectedValue);
|
|
|
|
{
|
|
const ScopedLock lock (deviceChanges);
|
|
|
|
if (! devices.contains (updatedDeviceId))
|
|
return S_OK;
|
|
|
|
auto& info = devices.getReference (updatedDeviceId);
|
|
|
|
if (info.isConnected && ! isConnected)
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("BLE device connection status change: " << updatedDeviceId << " " << info.containerID << " " << (isConnected ? "connected" : "disconnected"));
|
|
listeners.call ([&info] (Listener& l) { l.bleDeviceDisconnected (info.containerID); });
|
|
}
|
|
|
|
info.isConnected = isConnected;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
//==============================================================================
|
|
bool start()
|
|
{
|
|
WinRTWrapper::ScopedHString deviceSelector ("System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\""
|
|
" AND System.Devices.Aep.IsPaired:=System.StructuredQueryType.Boolean#True");
|
|
return attach (deviceSelector.get(), DeviceInformationKind::DeviceInformationKind_AssociationEndpoint);
|
|
}
|
|
|
|
//==============================================================================
|
|
struct Listener
|
|
{
|
|
virtual ~Listener() {};
|
|
virtual void bleDeviceAdded (const String& containerID) = 0;
|
|
virtual void bleDeviceDisconnected (const String& containerID) = 0;
|
|
};
|
|
|
|
void addListener (Listener* l)
|
|
{
|
|
listeners.add (l);
|
|
}
|
|
|
|
void removeListener (Listener* l)
|
|
{
|
|
listeners.remove (l);
|
|
}
|
|
|
|
//==============================================================================
|
|
ListenerList<Listener> listeners;
|
|
HashMap<String, DeviceInfo> devices;
|
|
CriticalSection deviceChanges;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BLEDeviceWatcher);
|
|
};
|
|
|
|
//==============================================================================
|
|
struct WinRTMIDIDeviceInfo
|
|
{
|
|
String deviceID, containerID, name;
|
|
bool isDefault = false;
|
|
};
|
|
|
|
//==============================================================================
|
|
template <typename COMFactoryType>
|
|
struct MidiIODeviceWatcher final : private DeviceCallbackHandler
|
|
{
|
|
MidiIODeviceWatcher (ComSmartPtr<COMFactoryType>& comFactory)
|
|
: factory (comFactory)
|
|
{
|
|
}
|
|
|
|
~MidiIODeviceWatcher()
|
|
{
|
|
detach();
|
|
}
|
|
|
|
HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override
|
|
{
|
|
WinRTMIDIDeviceInfo info;
|
|
|
|
HSTRING deviceID;
|
|
auto hr = addedDeviceInfo->get_Id (&deviceID);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to query added MIDI device ID!");
|
|
return S_OK;
|
|
}
|
|
|
|
auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
|
|
|
|
if (wrtWrapper == nullptr)
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
|
|
return false;
|
|
}
|
|
|
|
info.deviceID = wrtWrapper->hStringToString (deviceID);
|
|
|
|
JUCE_WINRT_MIDI_LOG ("Detected MIDI device: " << info.deviceID);
|
|
|
|
boolean isEnabled = false;
|
|
hr = addedDeviceInfo->get_IsEnabled (&isEnabled);
|
|
|
|
if (FAILED (hr) || ! isEnabled)
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("MIDI device not enabled: " << info.deviceID);
|
|
return S_OK;
|
|
}
|
|
|
|
// We use the container ID to match a MIDI device with a generic BLE device, if possible
|
|
if (auto* containerIDValue = getValueFromDeviceInfo ("System.Devices.ContainerId", addedDeviceInfo))
|
|
info.containerID = getGUIDFromInspectable (*containerIDValue);
|
|
|
|
HSTRING name;
|
|
hr = addedDeviceInfo->get_Name (&name);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to query detected MIDI device name for " << info.deviceID);
|
|
return S_OK;
|
|
}
|
|
|
|
info.name = wrtWrapper->hStringToString (name);
|
|
|
|
boolean isDefault = false;
|
|
hr = addedDeviceInfo->get_IsDefault (&isDefault);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to query detected MIDI device defaultness for " << info.deviceID << " " << info.name);
|
|
return S_OK;
|
|
}
|
|
|
|
info.isDefault = isDefault;
|
|
|
|
JUCE_WINRT_MIDI_LOG ("Adding MIDI device: " << info.deviceID << " " << info.containerID << " " << info.name);
|
|
|
|
{
|
|
const ScopedLock lock (deviceChanges);
|
|
connectedDevices.add (info);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo) override
|
|
{
|
|
HSTRING removedDeviceIdHstr;
|
|
auto hr = removedDeviceInfo->get_Id (&removedDeviceIdHstr);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to query removed MIDI device ID!");
|
|
return S_OK;
|
|
}
|
|
|
|
auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
|
|
|
|
if (wrtWrapper == nullptr)
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to get the WinRTWrapper singleton!");
|
|
return false;
|
|
}
|
|
|
|
auto removedDeviceId = wrtWrapper->hStringToString (removedDeviceIdHstr);
|
|
|
|
JUCE_WINRT_MIDI_LOG ("Removing MIDI device: " << removedDeviceId);
|
|
|
|
{
|
|
const ScopedLock lock (deviceChanges);
|
|
|
|
for (int i = 0; i < connectedDevices.size(); ++i)
|
|
{
|
|
if (connectedDevices[i].deviceID == removedDeviceId)
|
|
{
|
|
connectedDevices.remove (i);
|
|
JUCE_WINRT_MIDI_LOG ("Removed MIDI device: " << removedDeviceId);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// This is never called
|
|
HRESULT updateDevice (IDeviceInformationUpdate*) override { return S_OK; }
|
|
|
|
bool start()
|
|
{
|
|
HSTRING deviceSelector;
|
|
auto hr = factory->GetDeviceSelector (&deviceSelector);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to get MIDI device selector!");
|
|
return false;
|
|
}
|
|
|
|
return attach (deviceSelector, DeviceInformationKind::DeviceInformationKind_DeviceInterface);
|
|
}
|
|
|
|
Array<MidiDeviceInfo> getAvailableDevices()
|
|
{
|
|
{
|
|
const ScopedLock lock (deviceChanges);
|
|
lastQueriedConnectedDevices = connectedDevices;
|
|
}
|
|
|
|
StringArray deviceNames, deviceIDs;
|
|
|
|
for (auto info : lastQueriedConnectedDevices.get())
|
|
{
|
|
deviceNames.add (info.name);
|
|
deviceIDs .add (info.containerID);
|
|
}
|
|
|
|
deviceNames.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
|
|
deviceIDs .appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
|
|
|
|
Array<MidiDeviceInfo> devices;
|
|
|
|
for (int i = 0; i < deviceNames.size(); ++i)
|
|
devices.add ({ deviceNames[i], deviceIDs[i] });
|
|
|
|
return devices;
|
|
}
|
|
|
|
MidiDeviceInfo getDefaultDevice()
|
|
{
|
|
auto& lastDevices = lastQueriedConnectedDevices.get();
|
|
|
|
for (auto& d : lastDevices)
|
|
if (d.isDefault)
|
|
return { d.name, d.containerID };
|
|
|
|
return {};
|
|
}
|
|
|
|
WinRTMIDIDeviceInfo getWinRTDeviceInfoForDevice (const String& deviceIdentifier)
|
|
{
|
|
auto devices = getAvailableDevices();
|
|
|
|
for (int i = 0; i < devices.size(); ++i)
|
|
if (devices.getUnchecked (i).identifier == deviceIdentifier)
|
|
return lastQueriedConnectedDevices.get()[i];
|
|
|
|
return {};
|
|
}
|
|
|
|
ComSmartPtr<COMFactoryType>& factory;
|
|
|
|
Array<WinRTMIDIDeviceInfo> connectedDevices;
|
|
CriticalSection deviceChanges;
|
|
ThreadLocalValue<Array<WinRTMIDIDeviceInfo>> lastQueriedConnectedDevices;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher);
|
|
};
|
|
|
|
//==============================================================================
|
|
template <typename COMType, typename COMFactoryType, typename COMInterfaceType>
|
|
static void openMidiPortThread (String threadName,
|
|
String midiDeviceID,
|
|
ComSmartPtr<COMFactoryType>& comFactory,
|
|
ComSmartPtr<COMInterfaceType>& comPort)
|
|
{
|
|
std::thread { [&]
|
|
{
|
|
Thread::setCurrentThreadName (threadName);
|
|
|
|
const WinRTWrapper::ScopedHString hDeviceId { midiDeviceID };
|
|
ComSmartPtr<IAsyncOperation<COMType*>> asyncOp;
|
|
const auto hr = comFactory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress());
|
|
|
|
if (FAILED (hr))
|
|
return;
|
|
|
|
std::promise<ComSmartPtr<COMInterfaceType>> promise;
|
|
auto future = promise.get_future();
|
|
|
|
auto callback = [p = std::move (promise)] (IAsyncOperation<COMType*>* asyncOpPtr, AsyncStatus) mutable
|
|
{
|
|
if (asyncOpPtr == nullptr)
|
|
{
|
|
p.set_value (nullptr);
|
|
return E_ABORT;
|
|
}
|
|
|
|
ComSmartPtr<COMInterfaceType> result;
|
|
const auto hr = asyncOpPtr->GetResults (result.resetAndGetPointerAddress());
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
p.set_value (nullptr);
|
|
return hr;
|
|
}
|
|
|
|
p.set_value (std::move (result));
|
|
return S_OK;
|
|
};
|
|
|
|
const auto ir = asyncOp->put_Completed (Callback<IAsyncOperationCompletedHandler<COMType*>> (std::move (callback)).Get());
|
|
|
|
if (FAILED (ir))
|
|
return;
|
|
|
|
if (future.wait_for (std::chrono::milliseconds (2000)) == std::future_status::ready)
|
|
comPort = future.get();
|
|
} }.join();
|
|
}
|
|
|
|
//==============================================================================
|
|
template <typename MIDIIOStaticsType, typename MIDIPort>
|
|
class WinRTIOWrapper : private BLEDeviceWatcher::Listener
|
|
{
|
|
public:
|
|
WinRTIOWrapper (BLEDeviceWatcher& bleWatcher,
|
|
MidiIODeviceWatcher<MIDIIOStaticsType>& midiDeviceWatcher,
|
|
const String& deviceIdentifier)
|
|
: bleDeviceWatcher (bleWatcher)
|
|
{
|
|
{
|
|
const ScopedLock lock (midiDeviceWatcher.deviceChanges);
|
|
deviceInfo = midiDeviceWatcher.getWinRTDeviceInfoForDevice (deviceIdentifier);
|
|
}
|
|
|
|
if (deviceInfo.deviceID.isEmpty())
|
|
throw std::runtime_error ("Invalid device index");
|
|
|
|
JUCE_WINRT_MIDI_LOG ("Creating JUCE MIDI IO: " << deviceInfo.deviceID);
|
|
|
|
if (deviceInfo.containerID.isNotEmpty())
|
|
{
|
|
bleDeviceWatcher.addListener (this);
|
|
|
|
const ScopedLock lock (bleDeviceWatcher.deviceChanges);
|
|
|
|
HashMap<String, BLEDeviceWatcher::DeviceInfo>::Iterator iter (bleDeviceWatcher.devices);
|
|
|
|
while (iter.next())
|
|
{
|
|
if (iter.getValue().containerID == deviceInfo.containerID)
|
|
{
|
|
isBLEDevice = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual ~WinRTIOWrapper()
|
|
{
|
|
bleDeviceWatcher.removeListener (this);
|
|
|
|
disconnect();
|
|
}
|
|
|
|
//==============================================================================
|
|
virtual void disconnect()
|
|
{
|
|
if (midiPort != nullptr)
|
|
{
|
|
if (isBLEDevice)
|
|
midiPort->Release();
|
|
}
|
|
|
|
midiPort = nullptr;
|
|
}
|
|
|
|
private:
|
|
//==============================================================================
|
|
void bleDeviceAdded (const String& containerID) override
|
|
{
|
|
if (containerID == deviceInfo.containerID)
|
|
isBLEDevice = true;
|
|
}
|
|
|
|
void bleDeviceDisconnected (const String& containerID) override
|
|
{
|
|
if (containerID == deviceInfo.containerID)
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Disconnecting MIDI port from BLE disconnection: " << deviceInfo.deviceID
|
|
<< " " << deviceInfo.containerID << " " << deviceInfo.name);
|
|
disconnect();
|
|
}
|
|
}
|
|
|
|
protected:
|
|
//==============================================================================
|
|
BLEDeviceWatcher& bleDeviceWatcher;
|
|
WinRTMIDIDeviceInfo deviceInfo;
|
|
bool isBLEDevice = false;
|
|
ComSmartPtr<MIDIPort> midiPort;
|
|
};
|
|
|
|
//==============================================================================
|
|
struct WinRTInputWrapper final : public MidiInput::Pimpl,
|
|
private WinRTIOWrapper<IMidiInPortStatics, IMidiInPort>
|
|
|
|
{
|
|
WinRTInputWrapper (WinRTMidiService& service, MidiInput& input, const String& deviceIdentifier, MidiInputCallback& cb)
|
|
: WinRTIOWrapper <IMidiInPortStatics, IMidiInPort> (*service.bleDeviceWatcher, *service.inputDeviceWatcher, deviceIdentifier),
|
|
inputDevice (input),
|
|
callback (cb)
|
|
{
|
|
openMidiPortThread<MidiInPort> ("Open WinRT MIDI input port", deviceInfo.deviceID, service.midiInFactory, midiPort);
|
|
|
|
if (midiPort == nullptr)
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Timed out waiting for midi input port creation");
|
|
return;
|
|
}
|
|
|
|
startTime = Time::getMillisecondCounterHiRes();
|
|
|
|
auto hr = midiPort->add_MessageReceived (
|
|
Callback<ITypedEventHandler<MidiInPort*, MidiMessageReceivedEventArgs*>> (
|
|
[self = checkedReference] (IMidiInPort*, IMidiMessageReceivedEventArgs* args)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
self->access ([&hr, args] (auto* ptr)
|
|
{
|
|
if (ptr != nullptr)
|
|
hr = ptr->midiInMessageReceived (args);
|
|
});
|
|
|
|
return hr;
|
|
}
|
|
).Get(),
|
|
&midiInMessageToken);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
JUCE_WINRT_MIDI_LOG ("Failed to set MIDI input callback");
|
|
jassertfalse;
|
|
}
|
|
}
|
|
|
|
~WinRTInputWrapper()
|
|
{
|
|
checkedReference->clear();
|
|
disconnect();
|
|
}
|
|
|
|
//==============================================================================
|
|
void start() override
|
|
{
|
|
if (! isStarted)
|
|
{
|
|
concatenator.reset();
|
|
isStarted = true;
|
|
}
|
|
}
|
|
|
|
void stop() override
|
|
{
|
|
if (isStarted)
|
|
{
|
|
isStarted = false;
|
|
concatenator.reset();
|
|
}
|
|
}
|
|
|
|
String getDeviceIdentifier() override { return deviceInfo.containerID; }
|
|
String getDeviceName() override { return deviceInfo.name; }
|
|
|
|
//==============================================================================
|
|
void disconnect() override
|
|
{
|
|
stop();
|
|
|
|
if (midiPort != nullptr && midiInMessageToken.value != 0)
|
|
midiPort->remove_MessageReceived (midiInMessageToken);
|
|
|
|
WinRTIOWrapper<IMidiInPortStatics, IMidiInPort>::disconnect();
|
|
}
|
|
|
|
//==============================================================================
|
|
HRESULT midiInMessageReceived (IMidiMessageReceivedEventArgs* args)
|
|
{
|
|
if (! isStarted)
|
|
return S_OK;
|
|
|
|
ComSmartPtr<IMidiMessage> message;
|
|
auto 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 (×pan);
|
|
|
|
if (FAILED (hr))
|
|
return hr;
|
|
|
|
concatenator.pushMidiData (bufferData, numBytes,
|
|
convertTimeStamp (timespan.Duration),
|
|
&inputDevice, callback);
|
|
return S_OK;
|
|
}
|
|
|
|
double convertTimeStamp (int64 timestamp)
|
|
{
|
|
auto millisecondsSinceStart = static_cast<double> (timestamp) / 10000.0;
|
|
auto t = startTime + millisecondsSinceStart;
|
|
auto now = Time::getMillisecondCounterHiRes();
|
|
|
|
if (t > now)
|
|
{
|
|
if (t > now + 2.0)
|
|
startTime -= 1.0;
|
|
|
|
t = now;
|
|
}
|
|
|
|
return t * 0.001;
|
|
}
|
|
|
|
//==============================================================================
|
|
MidiInput& inputDevice;
|
|
MidiInputCallback& callback;
|
|
|
|
MidiDataConcatenator concatenator { 4096 };
|
|
EventRegistrationToken midiInMessageToken { 0 };
|
|
|
|
double startTime = 0;
|
|
bool isStarted = false;
|
|
|
|
std::shared_ptr<CheckedReference<WinRTInputWrapper>> checkedReference = createCheckedReference (this);
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTInputWrapper);
|
|
};
|
|
|
|
//==============================================================================
|
|
struct WinRTOutputWrapper final : public MidiOutput::Pimpl,
|
|
private WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort>
|
|
{
|
|
WinRTOutputWrapper (WinRTMidiService& service, const String& deviceIdentifier)
|
|
: WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort> (*service.bleDeviceWatcher, *service.outputDeviceWatcher, deviceIdentifier)
|
|
{
|
|
openMidiPortThread<IMidiOutPort> ("Open WinRT MIDI output port", deviceInfo.deviceID, service.midiOutFactory, midiPort);
|
|
|
|
if (midiPort == nullptr)
|
|
throw std::runtime_error ("Timed out waiting for midi output port creation");
|
|
|
|
auto* wrtWrapper = WinRTWrapper::getInstanceWithoutCreating();
|
|
|
|
if (wrtWrapper == nullptr)
|
|
throw std::runtime_error ("Failed to get the WinRTWrapper singleton!");
|
|
|
|
auto bufferFactory = wrtWrapper->getWRLFactory<IBufferFactory> (&RuntimeClass_Windows_Storage_Streams_Buffer[0]);
|
|
|
|
if (bufferFactory == nullptr)
|
|
throw std::runtime_error ("Failed to create output buffer factory");
|
|
|
|
auto 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");
|
|
}
|
|
|
|
//==============================================================================
|
|
void sendMessageNow (const MidiMessage& message) override
|
|
{
|
|
if (midiPort == nullptr)
|
|
return;
|
|
|
|
auto numBytes = message.getRawDataSize();
|
|
auto hr = buffer->put_Length (numBytes);
|
|
|
|
if (FAILED (hr))
|
|
{
|
|
jassertfalse;
|
|
return;
|
|
}
|
|
|
|
memcpy_s (bufferData, numBytes, message.getRawData(), numBytes);
|
|
midiPort->SendBuffer (buffer);
|
|
}
|
|
|
|
String getDeviceIdentifier() override { return deviceInfo.containerID; }
|
|
String getDeviceName() override { return deviceInfo.name; }
|
|
|
|
//==============================================================================
|
|
ComSmartPtr<IBuffer> buffer;
|
|
ComSmartPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
|
|
uint8_t* bufferData = nullptr;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTOutputWrapper);
|
|
};
|
|
|
|
ComSmartPtr<IMidiInPortStatics> midiInFactory;
|
|
ComSmartPtr<IMidiOutPortStatics> midiOutFactory;
|
|
|
|
std::unique_ptr<MidiIODeviceWatcher<IMidiInPortStatics>> inputDeviceWatcher;
|
|
std::unique_ptr<MidiIODeviceWatcher<IMidiOutPortStatics>> outputDeviceWatcher;
|
|
std::unique_ptr<BLEDeviceWatcher> bleDeviceWatcher;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTMidiService)
|
|
};
|
|
|
|
#endif // JUCE_USE_WINRT_MIDI
|
|
|
|
//==============================================================================
|
|
//==============================================================================
|
|
RTL_OSVERSIONINFOW getWindowsVersionInfo();
|
|
|
|
struct MidiService final : public DeletedAtShutdown
|
|
{
|
|
MidiService()
|
|
{
|
|
#if JUCE_USE_WINRT_MIDI
|
|
#if ! JUCE_FORCE_WINRT_MIDI
|
|
auto windowsVersionInfo = getWindowsVersionInfo();
|
|
if (windowsVersionInfo.dwMajorVersion >= 10 && windowsVersionInfo.dwBuildNumber >= 17763)
|
|
#endif
|
|
{
|
|
try
|
|
{
|
|
internal.reset (new WinRTMidiService());
|
|
return;
|
|
}
|
|
catch (std::runtime_error&) {}
|
|
}
|
|
#endif
|
|
|
|
internal.reset (new Win32MidiService());
|
|
}
|
|
|
|
~MidiService()
|
|
{
|
|
clearSingletonInstance();
|
|
}
|
|
|
|
static MidiServiceType& getService()
|
|
{
|
|
jassert (getInstance()->internal != nullptr);
|
|
return *getInstance()->internal.get();
|
|
}
|
|
|
|
JUCE_DECLARE_SINGLETON (MidiService, false)
|
|
|
|
private:
|
|
std::unique_ptr<MidiServiceType> internal;
|
|
DeviceChangeDetector detector { L"JuceMidiDeviceDetector_", []
|
|
{
|
|
MidiDeviceListConnectionBroadcaster::get().notify();
|
|
} };
|
|
};
|
|
|
|
JUCE_IMPLEMENT_SINGLETON (MidiService)
|
|
|
|
//==============================================================================
|
|
static int findDefaultDeviceIndex (const Array<MidiDeviceInfo>& available, const MidiDeviceInfo& defaultDevice)
|
|
{
|
|
for (int i = 0; i < available.size(); ++i)
|
|
if (available.getUnchecked (i) == defaultDevice)
|
|
return i;
|
|
|
|
return 0;
|
|
}
|
|
|
|
Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
|
|
{
|
|
return MidiService::getService().getAvailableDevices (true);
|
|
}
|
|
|
|
MidiDeviceInfo MidiInput::getDefaultDevice()
|
|
{
|
|
return MidiService::getService().getDefaultDevice (true);
|
|
}
|
|
|
|
std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
|
|
{
|
|
if (deviceIdentifier.isEmpty() || callback == nullptr)
|
|
return {};
|
|
|
|
std::unique_ptr<MidiInput> in (new MidiInput ({}, deviceIdentifier));
|
|
std::unique_ptr<Pimpl> wrapper;
|
|
|
|
try
|
|
{
|
|
wrapper.reset (MidiService::getService().createInputWrapper (*in, deviceIdentifier, *callback));
|
|
}
|
|
catch (std::runtime_error&)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
in->setName (wrapper->getDeviceName());
|
|
in->internal = std::move (wrapper);
|
|
|
|
return in;
|
|
}
|
|
|
|
StringArray MidiInput::getDevices()
|
|
{
|
|
StringArray deviceNames;
|
|
|
|
for (auto& d : getAvailableDevices())
|
|
deviceNames.add (d.name);
|
|
|
|
return deviceNames;
|
|
}
|
|
|
|
int MidiInput::getDefaultDeviceIndex()
|
|
{
|
|
return findDefaultDeviceIndex (getAvailableDevices(), getDefaultDevice());
|
|
}
|
|
|
|
std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback)
|
|
{
|
|
return openDevice (getAvailableDevices()[index].identifier, callback);
|
|
}
|
|
|
|
MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
|
|
: deviceInfo (deviceName, deviceIdentifier)
|
|
{
|
|
}
|
|
|
|
MidiInput::~MidiInput() = default;
|
|
|
|
void MidiInput::start() { internal->start(); }
|
|
void MidiInput::stop() { internal->stop(); }
|
|
|
|
//==============================================================================
|
|
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
|
|
{
|
|
return MidiService::getService().getAvailableDevices (false);
|
|
}
|
|
|
|
MidiDeviceInfo MidiOutput::getDefaultDevice()
|
|
{
|
|
return MidiService::getService().getDefaultDevice (false);
|
|
}
|
|
|
|
std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
|
|
{
|
|
if (deviceIdentifier.isEmpty())
|
|
return {};
|
|
|
|
std::unique_ptr<Pimpl> wrapper;
|
|
|
|
try
|
|
{
|
|
wrapper.reset (MidiService::getService().createOutputWrapper (deviceIdentifier));
|
|
}
|
|
catch (std::runtime_error&)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
std::unique_ptr<MidiOutput> out;
|
|
out.reset (new MidiOutput (wrapper->getDeviceName(), deviceIdentifier));
|
|
|
|
out->internal = std::move (wrapper);
|
|
|
|
return out;
|
|
}
|
|
|
|
StringArray MidiOutput::getDevices()
|
|
{
|
|
StringArray deviceNames;
|
|
|
|
for (auto& d : getAvailableDevices())
|
|
deviceNames.add (d.name);
|
|
|
|
return deviceNames;
|
|
}
|
|
|
|
int MidiOutput::getDefaultDeviceIndex()
|
|
{
|
|
return findDefaultDeviceIndex (getAvailableDevices(), getDefaultDevice());
|
|
}
|
|
|
|
std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index)
|
|
{
|
|
return openDevice (getAvailableDevices()[index].identifier);
|
|
}
|
|
|
|
MidiOutput::~MidiOutput()
|
|
{
|
|
stopBackgroundThread();
|
|
}
|
|
|
|
void MidiOutput::sendMessageNow (const MidiMessage& message)
|
|
{
|
|
internal->sendMessageNow (message);
|
|
}
|
|
|
|
MidiDeviceListConnection MidiDeviceListConnection::make (std::function<void()> cb)
|
|
{
|
|
auto& broadcaster = MidiDeviceListConnectionBroadcaster::get();
|
|
return { &broadcaster, broadcaster.add (std::move (cb)) };
|
|
}
|
|
|
|
} // namespace juce
|