@@ -52,9 +52,9 @@ | |||||
//============================================================================== | //============================================================================== | ||||
struct MidiDeviceListEntry : ReferenceCountedObject | struct MidiDeviceListEntry : ReferenceCountedObject | ||||
{ | { | ||||
MidiDeviceListEntry (const String& deviceName) : name (deviceName) {} | |||||
MidiDeviceListEntry (MidiDeviceInfo info) : deviceInfo (info) {} | |||||
String name; | |||||
MidiDeviceInfo deviceInfo; | |||||
std::unique_ptr<MidiInput> inDevice; | std::unique_ptr<MidiInput> inDevice; | ||||
std::unique_ptr<MidiOutput> outDevice; | std::unique_ptr<MidiOutput> outDevice; | ||||
@@ -187,7 +187,7 @@ public: | |||||
if (isInput) | if (isInput) | ||||
{ | { | ||||
jassert (midiInputs[index]->inDevice.get() == nullptr); | jassert (midiInputs[index]->inDevice.get() == nullptr); | ||||
midiInputs[index]->inDevice.reset (MidiInput::openDevice (index, this)); | |||||
midiInputs[index]->inDevice.reset (MidiInput::openDevice (midiInputs[index]->deviceInfo.identifier, this)); | |||||
if (midiInputs[index]->inDevice.get() == nullptr) | if (midiInputs[index]->inDevice.get() == nullptr) | ||||
{ | { | ||||
@@ -200,7 +200,7 @@ public: | |||||
else | else | ||||
{ | { | ||||
jassert (midiOutputs[index]->outDevice.get() == nullptr); | jassert (midiOutputs[index]->outDevice.get() == nullptr); | ||||
midiOutputs[index]->outDevice.reset (MidiOutput::openDevice (index)); | |||||
midiOutputs[index]->outDevice.reset (MidiOutput::openDevice (midiOutputs[index]->deviceInfo.identifier)); | |||||
if (midiOutputs[index]->outDevice.get() == nullptr) | if (midiOutputs[index]->outDevice.get() == nullptr) | ||||
{ | { | ||||
@@ -278,14 +278,14 @@ private: | |||||
if (isInput) | if (isInput) | ||||
{ | { | ||||
if (rowNumber < parent.getNumMidiInputs()) | if (rowNumber < parent.getNumMidiInputs()) | ||||
g.drawText (parent.getMidiDevice (rowNumber, true)->name, | |||||
g.drawText (parent.getMidiDevice (rowNumber, true)->deviceInfo.name, | |||||
5, 0, width, height, | 5, 0, width, height, | ||||
Justification::centredLeft, true); | Justification::centredLeft, true); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
if (rowNumber < parent.getNumMidiOutputs()) | if (rowNumber < parent.getNumMidiOutputs()) | ||||
g.drawText (parent.getMidiDevice (rowNumber, false)->name, | |||||
g.drawText (parent.getMidiDevice (rowNumber, false)->deviceInfo.name, | |||||
5, 0, width, height, | 5, 0, width, height, | ||||
Justification::centredLeft, true); | Justification::centredLeft, true); | ||||
} | } | ||||
@@ -368,34 +368,34 @@ private: | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
bool hasDeviceListChanged (const StringArray& deviceNames, bool isInputDevice) | |||||
bool hasDeviceListChanged (const Array<MidiDeviceInfo>& availableDevices, bool isInputDevice) | |||||
{ | { | ||||
ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs | ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs | ||||
: midiOutputs; | : midiOutputs; | ||||
if (deviceNames.size() != midiDevices.size()) | |||||
if (availableDevices.size() != midiDevices.size()) | |||||
return true; | return true; | ||||
for (auto i = 0; i < deviceNames.size(); ++i) | |||||
if (deviceNames[i] != midiDevices[i]->name) | |||||
for (auto i = 0; i < availableDevices.size(); ++i) | |||||
if (availableDevices[i] != midiDevices[i]->deviceInfo) | |||||
return true; | return true; | ||||
return false; | return false; | ||||
} | } | ||||
ReferenceCountedObjectPtr<MidiDeviceListEntry> findDeviceWithName (const String& name, bool isInputDevice) const | |||||
ReferenceCountedObjectPtr<MidiDeviceListEntry> findDevice (MidiDeviceInfo device, bool isInputDevice) const | |||||
{ | { | ||||
const ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs | const ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs | ||||
: midiOutputs; | : midiOutputs; | ||||
for (auto midiDevice : midiDevices) | |||||
if (midiDevice->name == name) | |||||
return midiDevice; | |||||
for (auto& d : midiDevices) | |||||
if (d->deviceInfo == device) | |||||
return d; | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
void closeUnpluggedDevices (StringArray& currentlyPluggedInDevices, bool isInputDevice) | |||||
void closeUnpluggedDevices (const Array<MidiDeviceInfo>& currentlyPluggedInDevices, bool isInputDevice) | |||||
{ | { | ||||
ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs | ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs | ||||
: midiOutputs; | : midiOutputs; | ||||
@@ -404,7 +404,7 @@ private: | |||||
{ | { | ||||
auto& d = *midiDevices[i]; | auto& d = *midiDevices[i]; | ||||
if (! currentlyPluggedInDevices.contains (d.name)) | |||||
if (! currentlyPluggedInDevices.contains (d.deviceInfo)) | |||||
{ | { | ||||
if (isInputDevice ? d.inDevice .get() != nullptr | if (isInputDevice ? d.inDevice .get() != nullptr | ||||
: d.outDevice.get() != nullptr) | : d.outDevice.get() != nullptr) | ||||
@@ -417,26 +417,26 @@ private: | |||||
void updateDeviceList (bool isInputDeviceList) | void updateDeviceList (bool isInputDeviceList) | ||||
{ | { | ||||
auto newDeviceNames = isInputDeviceList ? MidiInput::getDevices() | |||||
: MidiOutput::getDevices(); | |||||
auto availableDevices = isInputDeviceList ? MidiInput::getAvailableDevices() | |||||
: MidiOutput::getAvailableDevices(); | |||||
if (hasDeviceListChanged (newDeviceNames, isInputDeviceList)) | |||||
if (hasDeviceListChanged (availableDevices, isInputDeviceList)) | |||||
{ | { | ||||
ReferenceCountedArray<MidiDeviceListEntry>& midiDevices | ReferenceCountedArray<MidiDeviceListEntry>& midiDevices | ||||
= isInputDeviceList ? midiInputs : midiOutputs; | = isInputDeviceList ? midiInputs : midiOutputs; | ||||
closeUnpluggedDevices (newDeviceNames, isInputDeviceList); | |||||
closeUnpluggedDevices (availableDevices, isInputDeviceList); | |||||
ReferenceCountedArray<MidiDeviceListEntry> newDeviceList; | ReferenceCountedArray<MidiDeviceListEntry> newDeviceList; | ||||
// add all currently plugged-in devices to the device list | // add all currently plugged-in devices to the device list | ||||
for (auto newDeviceName : newDeviceNames) | |||||
for (auto& newDevice : availableDevices) | |||||
{ | { | ||||
MidiDeviceListEntry::Ptr entry = findDeviceWithName (newDeviceName, isInputDeviceList); | |||||
MidiDeviceListEntry::Ptr entry = findDevice (newDevice, isInputDeviceList); | |||||
if (entry == nullptr) | if (entry == nullptr) | ||||
entry = new MidiDeviceListEntry (newDeviceName); | |||||
entry = new MidiDeviceListEntry (newDevice); | |||||
newDeviceList.add (entry); | newDeviceList.add (entry); | ||||
} | } | ||||
@@ -52,6 +52,9 @@ class ConsoleUnitTestRunner : public UnitTestRunner | |||||
//============================================================================== | //============================================================================== | ||||
int main (int argc, char **argv) | int main (int argc, char **argv) | ||||
{ | { | ||||
// Needed for tests that require a message thread | |||||
ScopedJuceInitialiser_GUI guiInitialiser; | |||||
ConsoleLogger logger; | ConsoleLogger logger; | ||||
Logger::setCurrentLogger (&logger); | Logger::setCurrentLogger (&logger); | ||||
@@ -179,7 +179,7 @@ | |||||
#include "audio_io/juce_AudioIODevice.cpp" | #include "audio_io/juce_AudioIODevice.cpp" | ||||
#include "audio_io/juce_AudioIODeviceType.cpp" | #include "audio_io/juce_AudioIODeviceType.cpp" | ||||
#include "midi_io/juce_MidiMessageCollector.cpp" | #include "midi_io/juce_MidiMessageCollector.cpp" | ||||
#include "midi_io/juce_MidiOutput.cpp" | |||||
#include "midi_io/juce_MidiDevices.cpp" | |||||
#include "sources/juce_AudioSourcePlayer.cpp" | #include "sources/juce_AudioSourcePlayer.cpp" | ||||
#include "sources/juce_AudioTransportSource.cpp" | #include "sources/juce_AudioTransportSource.cpp" | ||||
#include "native/juce_MidiDataConcatenator.h" | #include "native/juce_MidiDataConcatenator.h" | ||||
@@ -171,9 +171,8 @@ | |||||
#endif | #endif | ||||
//============================================================================== | //============================================================================== | ||||
#include "midi_io/juce_MidiInput.h" | |||||
#include "midi_io/juce_MidiDevices.h" | |||||
#include "midi_io/juce_MidiMessageCollector.h" | #include "midi_io/juce_MidiMessageCollector.h" | ||||
#include "midi_io/juce_MidiOutput.h" | |||||
#include "audio_io/juce_AudioIODevice.h" | #include "audio_io/juce_AudioIODevice.h" | ||||
#include "audio_io/juce_AudioIODeviceType.h" | #include "audio_io/juce_AudioIODeviceType.h" | ||||
#include "audio_io/juce_SystemAudioVolume.h" | #include "audio_io/juce_SystemAudioVolume.h" | ||||
@@ -23,18 +23,8 @@ | |||||
namespace juce | namespace juce | ||||
{ | { | ||||
struct MidiOutput::PendingMessage | |||||
{ | |||||
PendingMessage (const void* data, int len, double timeStamp) | |||||
: message (data, len, timeStamp) | |||||
{} | |||||
MidiMessage message; | |||||
PendingMessage* next; | |||||
}; | |||||
MidiOutput::MidiOutput (const String& deviceName) | |||||
: Thread ("midi out"), name (deviceName) | |||||
MidiOutput::MidiOutput (const String& deviceName, const String& deviceIdentifier) | |||||
: Thread ("midi out"), deviceInfo (deviceName, deviceIdentifier) | |||||
{ | { | ||||
} | } | ||||
@@ -116,7 +106,7 @@ void MidiOutput::run() | |||||
{ | { | ||||
while (! threadShouldExit()) | while (! threadShouldExit()) | ||||
{ | { | ||||
uint32 now = Time::getMillisecondCounter(); | |||||
auto now = Time::getMillisecondCounter(); | |||||
uint32 eventTime = 0; | uint32 eventTime = 0; | ||||
uint32 timeToWait = 500; | uint32 timeToWait = 500; | ||||
@@ -167,4 +157,106 @@ void MidiOutput::run() | |||||
clearAllPendingMessages(); | clearAllPendingMessages(); | ||||
} | } | ||||
#if JUCE_UNIT_TESTS | |||||
class MidiDevicesUnitTests : public UnitTest | |||||
{ | |||||
public: | |||||
MidiDevicesUnitTests() : UnitTest ("MidiInput/MidiOutput", "MIDI/MPE") {} | |||||
void runTest() override | |||||
{ | |||||
beginTest ("default device (input)"); | |||||
{ | |||||
auto devices = MidiInput::getAvailableDevices(); | |||||
auto defaultDevice = MidiInput::getDefaultDevice(); | |||||
if (devices.size() == 0) | |||||
expect (defaultDevice == MidiDeviceInfo()); | |||||
else | |||||
expect (devices.contains (defaultDevice)); | |||||
} | |||||
beginTest ("default device (output)"); | |||||
{ | |||||
auto devices = MidiOutput::getAvailableDevices(); | |||||
auto defaultDevice = MidiOutput::getDefaultDevice(); | |||||
if (devices.size() == 0) | |||||
expect (defaultDevice == MidiDeviceInfo()); | |||||
else | |||||
expect (devices.contains (defaultDevice)); | |||||
} | |||||
#if JUCE_MAC || JUCE_LINUX || JUCE_IOS | |||||
String testDeviceName ("TestDevice"); | |||||
String testDeviceName2 ("TestDevice2"); | |||||
struct MessageCallbackHandler : public MidiInputCallback | |||||
{ | |||||
void handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) override | |||||
{ | |||||
messageSource = source; | |||||
messageReceived = message; | |||||
} | |||||
MidiInput* messageSource = nullptr; | |||||
MidiMessage messageReceived; | |||||
}; | |||||
MessageCallbackHandler handler; | |||||
beginTest ("create device (input)"); | |||||
{ | |||||
std::unique_ptr<MidiInput> device (MidiInput::createNewDevice (testDeviceName, &handler)); | |||||
expect (device.get() != nullptr); | |||||
expect (device->getName() == testDeviceName); | |||||
device->setName (testDeviceName2); | |||||
expect (device->getName() == testDeviceName2); | |||||
} | |||||
beginTest ("create device (output)"); | |||||
{ | |||||
std::unique_ptr<MidiOutput> device (MidiOutput::createNewDevice (testDeviceName)); | |||||
expect (device.get() != nullptr); | |||||
expect (device->getName() == testDeviceName); | |||||
} | |||||
auto testMessage = MidiMessage::noteOn (5, 12, (uint8) 51); | |||||
beginTest ("send messages"); | |||||
{ | |||||
std::unique_ptr<MidiInput> midiInput (MidiInput::createNewDevice (testDeviceName, &handler)); | |||||
expect (midiInput.get() != nullptr); | |||||
midiInput->start(); | |||||
auto inputInfo = midiInput->getDeviceInfo(); | |||||
expect (MidiOutput::getAvailableDevices().contains (inputInfo)); | |||||
std::unique_ptr<MidiOutput> midiOutput (MidiOutput::openDevice (midiInput->getIdentifier())); | |||||
expect (midiOutput.get() != nullptr); | |||||
midiOutput->sendMessageNow (testMessage); | |||||
// Pump the message thread for a bit to allow the message to be delivered | |||||
MessageManager::getInstance()->runDispatchLoopUntil (100); | |||||
expect (handler.messageSource == midiInput.get()); | |||||
expect (handler.messageReceived.getChannel() == testMessage.getChannel()); | |||||
expect (handler.messageReceived.getNoteNumber() == testMessage.getNoteNumber()); | |||||
expect (handler.messageReceived.getVelocity() == testMessage.getVelocity()); | |||||
midiInput->stop(); | |||||
} | |||||
#endif | |||||
} | |||||
}; | |||||
static MidiDevicesUnitTests MidiDevicesUnitTests; | |||||
#endif | |||||
} // namespace juce | } // namespace juce |
@@ -0,0 +1,365 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
This struct contains information about a MIDI input or output device. | |||||
You can get one of these structs by calling the static getAvailableDevices() or | |||||
getDefaultDevice() methods of MidiInput and MidiOutput or by calling getDeviceInfo() | |||||
on an instance of these classes. Devices can be opened by passing the identifier to | |||||
the openDevice() method. | |||||
*/ | |||||
struct MidiDeviceInfo | |||||
{ | |||||
MidiDeviceInfo() = default; | |||||
MidiDeviceInfo (const String& deviceName, const String& deviceIdentifier) | |||||
: name (deviceName), identifier (deviceIdentifier) | |||||
{ | |||||
} | |||||
/** The name of this device. | |||||
This will be provided by the OS unless the device has been created with the | |||||
createNewDevice() method. | |||||
Note that the name is not guaranteed to be unique and two devices with the | |||||
same name will be indistinguishable. If you want to address a specific device | |||||
it is better to use the identifier. | |||||
*/ | |||||
String name; | |||||
/** The identifier for this device. | |||||
This will be provided by the OS and it's format will differ on different systems | |||||
e.g. on macOS it will be a number whereas on Windows it will be a long alphanumeric string. | |||||
*/ | |||||
String identifier; | |||||
//============================================================================== | |||||
bool operator== (const MidiDeviceInfo& other) const noexcept { return name == other.name && identifier == other.identifier; } | |||||
bool operator!= (const MidiDeviceInfo& other) const noexcept { return ! operator== (other); } | |||||
}; | |||||
class MidiInputCallback; | |||||
//============================================================================== | |||||
/** | |||||
Represents a midi input device. | |||||
To create one of these, use the static getAvailableDevices() method to find out what | |||||
inputs are available, and then use the openDevice() method to try to open one. | |||||
@see MidiOutput | |||||
@tags{Audio} | |||||
*/ | |||||
class JUCE_API MidiInput final | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns a list of the available midi input devices. | |||||
You can open one of the devices by passing its identifier into the openDevice() method. | |||||
@see MidiDeviceInfo, getDevices, getDefaultDeviceIndex, openDevice | |||||
*/ | |||||
static Array<MidiDeviceInfo> getAvailableDevices(); | |||||
/** Returns the MidiDeviceInfo of the default midi input device to use. */ | |||||
static MidiDeviceInfo getDefaultDevice(); | |||||
/** Tries to open one of the midi input devices. | |||||
This will return a MidiInput object if it manages to open it. You can then | |||||
call start() and stop() on this device, and delete it when no longer needed. | |||||
If the device can't be opened, this will return nullptr. | |||||
@param deviceIdentifier the ID of the device to open - use the getAvailableDevices() method to | |||||
find the available devices that can be opened | |||||
@param callback the object that will receive the midi messages from this device | |||||
@see MidiInputCallback, getDevices | |||||
*/ | |||||
static MidiInput* openDevice (const String& deviceIdentifier, MidiInputCallback* callback); | |||||
#if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||||
/** This will try to create a new midi input device (only available on Linux, macOS and iOS). | |||||
This will attempt to create a new midi input device with the specified name for other | |||||
apps to connect to. | |||||
Returns nullptr if a device can't be created. | |||||
@param deviceName the name of the device to create | |||||
@param callback the object that will receive the midi messages from this device | |||||
*/ | |||||
static MidiInput* createNewDevice (const String& deviceName, MidiInputCallback* callback); | |||||
#endif | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
~MidiInput(); | |||||
/** Starts the device running. | |||||
After calling this, the device will start sending midi messages to the MidiInputCallback | |||||
object that was specified when the openDevice() method was called. | |||||
@see stop | |||||
*/ | |||||
void start(); | |||||
/** Stops the device running. | |||||
@see start | |||||
*/ | |||||
void stop(); | |||||
/** Returns the MidiDeviceInfo struct containing some information about this device. */ | |||||
MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; } | |||||
/** Returns the identifier of this device. */ | |||||
String getIdentifier() const noexcept { return deviceInfo.identifier; } | |||||
/** Returns the name of this device. */ | |||||
String getName() const noexcept { return deviceInfo.name; } | |||||
/** Sets a custom name for the device. */ | |||||
void setName (const String& newName) noexcept { deviceInfo.name = newName; } | |||||
//============================================================================== | |||||
/** Deprecated. */ | |||||
static StringArray getDevices(); | |||||
/** Deprecated. */ | |||||
static int getDefaultDeviceIndex(); | |||||
/** Deprecated. */ | |||||
static MidiInput* openDevice (int, MidiInputCallback*); | |||||
private: | |||||
//============================================================================== | |||||
explicit MidiInput (const String&, const String&); | |||||
MidiDeviceInfo deviceInfo; | |||||
void* internal = nullptr; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput) | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Receives incoming messages from a physical MIDI input device. | |||||
This class is overridden to handle incoming midi messages. See the MidiInput | |||||
class for more details. | |||||
@see MidiInput | |||||
@tags{Audio} | |||||
*/ | |||||
class JUCE_API MidiInputCallback | |||||
{ | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~MidiInputCallback() = default; | |||||
/** Receives an incoming message. | |||||
A MidiInput object will call this method when a midi event arrives. It'll be | |||||
called on a high-priority system thread, so avoid doing anything time-consuming | |||||
in here, and avoid making any UI calls. You might find the MidiBuffer class helpful | |||||
for queueing incoming messages for use later. | |||||
@param source the MidiInput object that generated the message | |||||
@param message the incoming message. The message's timestamp is set to a value | |||||
equivalent to (Time::getMillisecondCounter() / 1000.0) to specify the | |||||
time when the message arrived | |||||
*/ | |||||
virtual void handleIncomingMidiMessage (MidiInput* source, | |||||
const MidiMessage& message) = 0; | |||||
/** Notification sent each time a packet of a multi-packet sysex message arrives. | |||||
If a long sysex message is broken up into multiple packets, this callback is made | |||||
for each packet that arrives until the message is finished, at which point | |||||
the normal handleIncomingMidiMessage() callback will be made with the entire | |||||
message. | |||||
The message passed in will contain the start of a sysex, but won't be finished | |||||
with the terminating 0xf7 byte. | |||||
*/ | |||||
virtual void handlePartialSysexMessage (MidiInput* source, | |||||
const uint8* messageData, | |||||
int numBytesSoFar, | |||||
double timestamp) | |||||
{ | |||||
ignoreUnused (source, messageData, numBytesSoFar, timestamp); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Represents a midi output device. | |||||
To create one of these, use the static getAvailableDevices() method to find out what | |||||
outputs are available, and then use the openDevice() method to try to open one. | |||||
@see MidiInput | |||||
@tags{Audio} | |||||
*/ | |||||
class JUCE_API MidiOutput final : private Thread | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns a list of the available midi output devices. | |||||
You can open one of the devices by passing its identifier into the openDevice() method. | |||||
@see MidiDeviceInfo, getDevices, getDefaultDeviceIndex, openDevice | |||||
*/ | |||||
static Array<MidiDeviceInfo> getAvailableDevices(); | |||||
/** Returns the MidiDeviceInfo of the default midi output device to use. */ | |||||
static MidiDeviceInfo getDefaultDevice(); | |||||
/** Tries to open one of the midi output devices. | |||||
This will return a MidiOutput object if it manages to open it. You can then | |||||
send messages to this device, and delete it when no longer needed. | |||||
If the device can't be opened, this will return nullptr. | |||||
@param deviceIdentifier the ID of the device to open - use the getAvailableDevices() method to | |||||
find the available devices that can be opened | |||||
@see getDevices | |||||
*/ | |||||
static MidiOutput* openDevice (const String& deviceIdentifier); | |||||
#if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||||
/** This will try to create a new midi output device (only available on Linux, macOS and iOS). | |||||
This will attempt to create a new midi output device with the specified name that other | |||||
apps can connect to and use as their midi input. | |||||
Returns nullptr if a device can't be created. | |||||
@param deviceName the name of the device to create | |||||
*/ | |||||
static MidiOutput* createNewDevice (const String& deviceName); | |||||
#endif | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
~MidiOutput() override; | |||||
/** Returns the MidiDeviceInfo struct containing some information about this device. */ | |||||
MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; } | |||||
/** Returns the identifier of this device. */ | |||||
String getIdentifier() const noexcept { return deviceInfo.identifier; } | |||||
/** Returns the name of this device. */ | |||||
String getName() const noexcept { return deviceInfo.name; } | |||||
/** Sets a custom name for the device. */ | |||||
void setName (const String& newName) noexcept { deviceInfo.name = newName; } | |||||
//============================================================================== | |||||
/** Sends out a MIDI message immediately. */ | |||||
void sendMessageNow (const MidiMessage& message); | |||||
/** Sends out a sequence of MIDI messages immediately. */ | |||||
void sendBlockOfMessagesNow (const MidiBuffer& buffer); | |||||
/** This lets you supply a block of messages that will be sent out at some point | |||||
in the future. | |||||
The MidiOutput class has an internal thread that can send out timestamped | |||||
messages - this appends a set of messages to its internal buffer, ready for | |||||
sending. | |||||
This will only work if you've already started the thread with startBackgroundThread(). | |||||
A time is specified, at which the block of messages should be sent. This time uses | |||||
the same time base as Time::getMillisecondCounter(), and must be in the future. | |||||
The samplesPerSecondForBuffer parameter indicates the number of samples per second | |||||
used by the MidiBuffer. Each event in a MidiBuffer has a sample position, and the | |||||
samplesPerSecondForBuffer value is needed to convert this sample position to a | |||||
real time. | |||||
*/ | |||||
void sendBlockOfMessages (const MidiBuffer& buffer, | |||||
double millisecondCounterToStartAt, | |||||
double samplesPerSecondForBuffer); | |||||
/** Gets rid of any midi messages that had been added by sendBlockOfMessages(). */ | |||||
void clearAllPendingMessages(); | |||||
/** Starts up a background thread so that the device can send blocks of data. | |||||
Call this to get the device ready, before using sendBlockOfMessages(). | |||||
*/ | |||||
void startBackgroundThread(); | |||||
/** Stops the background thread, and clears any pending midi events. | |||||
@see startBackgroundThread | |||||
*/ | |||||
void stopBackgroundThread(); | |||||
//============================================================================== | |||||
/** Deprecated. */ | |||||
static StringArray getDevices(); | |||||
/** Deprecated. */ | |||||
static int getDefaultDeviceIndex(); | |||||
/** Deprecated. */ | |||||
static MidiOutput* openDevice (int); | |||||
private: | |||||
//============================================================================== | |||||
struct PendingMessage | |||||
{ | |||||
PendingMessage (const void* data, int len, double timeStamp) | |||||
: message (data, len, timeStamp) | |||||
{ | |||||
} | |||||
MidiMessage message; | |||||
PendingMessage* next; | |||||
}; | |||||
//============================================================================== | |||||
explicit MidiOutput (const String&, const String&); | |||||
void run() override; | |||||
MidiDeviceInfo deviceInfo; | |||||
void* internal = nullptr; | |||||
CriticalSection lock; | |||||
PendingMessage* firstMessage = nullptr; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput) | |||||
}; | |||||
} // namespace juce |
@@ -1,180 +0,0 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
class MidiInput; | |||||
//============================================================================== | |||||
/** | |||||
Receives incoming messages from a physical MIDI input device. | |||||
This class is overridden to handle incoming midi messages. See the MidiInput | |||||
class for more details. | |||||
@see MidiInput | |||||
@tags{Audio} | |||||
*/ | |||||
class JUCE_API MidiInputCallback | |||||
{ | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~MidiInputCallback() = default; | |||||
/** Receives an incoming message. | |||||
A MidiInput object will call this method when a midi event arrives. It'll be | |||||
called on a high-priority system thread, so avoid doing anything time-consuming | |||||
in here, and avoid making any UI calls. You might find the MidiBuffer class helpful | |||||
for queueing incoming messages for use later. | |||||
@param source the MidiInput object that generated the message | |||||
@param message the incoming message. The message's timestamp is set to a value | |||||
equivalent to (Time::getMillisecondCounter() / 1000.0) to specify the | |||||
time when the message arrived. | |||||
*/ | |||||
virtual void handleIncomingMidiMessage (MidiInput* source, | |||||
const MidiMessage& message) = 0; | |||||
/** Notification sent each time a packet of a multi-packet sysex message arrives. | |||||
If a long sysex message is broken up into multiple packets, this callback is made | |||||
for each packet that arrives until the message is finished, at which point | |||||
the normal handleIncomingMidiMessage() callback will be made with the entire | |||||
message. | |||||
The message passed in will contain the start of a sysex, but won't be finished | |||||
with the terminating 0xf7 byte. | |||||
*/ | |||||
virtual void handlePartialSysexMessage (MidiInput* source, | |||||
const uint8* messageData, | |||||
int numBytesSoFar, | |||||
double timestamp) | |||||
{ | |||||
ignoreUnused (source, messageData, numBytesSoFar, timestamp); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Represents a midi input device. | |||||
To create one of these, use the static getDevices() method to find out what inputs are | |||||
available, and then use the openDevice() method to try to open one. | |||||
@see MidiOutput | |||||
@tags{Audio} | |||||
*/ | |||||
class JUCE_API MidiInput final | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns a list of the available midi input devices. | |||||
You can open one of the devices by passing its index into the | |||||
openDevice() method. | |||||
@see getDefaultDeviceIndex, openDevice | |||||
*/ | |||||
static StringArray getDevices(); | |||||
/** Returns the index of the default midi input device to use. | |||||
This refers to the index in the list returned by getDevices(). | |||||
*/ | |||||
static int getDefaultDeviceIndex(); | |||||
/** Tries to open one of the midi input devices. | |||||
This will return a MidiInput object if it manages to open it. You can then | |||||
call start() and stop() on this device, and delete it when no longer needed. | |||||
If the device can't be opened, this will return a null pointer. | |||||
@param deviceIndex the index of a device from the list returned by getDevices() | |||||
@param callback the object that will receive the midi messages from this device. | |||||
@see MidiInputCallback, getDevices | |||||
*/ | |||||
static MidiInput* openDevice (int deviceIndex, | |||||
MidiInputCallback* callback); | |||||
#if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||||
/** This will try to create a new midi input device (Not available on Windows). | |||||
This will attempt to create a new midi input device with the specified name, | |||||
for other apps to connect to. | |||||
Returns nullptr if a device can't be created. | |||||
@param deviceName the name to use for the new device | |||||
@param callback the object that will receive the midi messages from this device. | |||||
*/ | |||||
static MidiInput* createNewDevice (const String& deviceName, | |||||
MidiInputCallback* callback); | |||||
#endif | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
~MidiInput(); | |||||
/** Returns the name of this device. */ | |||||
const String& getName() const noexcept { return name; } | |||||
/** Allows you to set a custom name for the device, in case you don't like the name | |||||
it was given when created. | |||||
*/ | |||||
void setName (const String& newName) noexcept { name = newName; } | |||||
//============================================================================== | |||||
/** Starts the device running. | |||||
After calling this, the device will start sending midi messages to the | |||||
MidiInputCallback object that was specified when the openDevice() method | |||||
was called. | |||||
@see stop | |||||
*/ | |||||
void start(); | |||||
/** Stops the device running. | |||||
@see start | |||||
*/ | |||||
void stop(); | |||||
private: | |||||
//============================================================================== | |||||
String name; | |||||
void* internal = nullptr; | |||||
// The input objects are created with the openDevice() method. | |||||
explicit MidiInput (const String&); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput) | |||||
}; | |||||
} // namespace juce |
@@ -1,145 +0,0 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Controls a physical MIDI output device. | |||||
To create one of these, use the static getDevices() method to get a list of the | |||||
available output devices, then use the openDevice() method to try to open one. | |||||
@see MidiInput | |||||
@tags{Audio} | |||||
*/ | |||||
class JUCE_API MidiOutput final : private Thread | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns a list of the available midi output devices. | |||||
You can open one of the devices by passing its index into the | |||||
openDevice() method. | |||||
@see getDefaultDeviceIndex, openDevice | |||||
*/ | |||||
static StringArray getDevices(); | |||||
/** Returns the index of the default midi output device to use. | |||||
This refers to the index in the list returned by getDevices(). | |||||
*/ | |||||
static int getDefaultDeviceIndex(); | |||||
/** Tries to open one of the midi output devices. | |||||
This will return a MidiOutput object if it manages to open it. You can then | |||||
send messages to this device, and delete it when no longer needed. | |||||
If the device can't be opened, this will return a null pointer. | |||||
@param deviceIndex the index of a device from the list returned by getDevices() | |||||
@see getDevices | |||||
*/ | |||||
static MidiOutput* openDevice (int deviceIndex); | |||||
#if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||||
/** This will try to create a new midi output device (Not available on Windows). | |||||
This will attempt to create a new midi output device that other apps can connect | |||||
to and use as their midi input. | |||||
Returns nullptr if a device can't be created. | |||||
@param deviceName the name to use for the new device | |||||
*/ | |||||
static MidiOutput* createNewDevice (const String& deviceName); | |||||
#endif | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
~MidiOutput() override; | |||||
/** Returns the name of this device. */ | |||||
const String& getName() const noexcept { return name; } | |||||
/** Sends out a MIDI message immediately. */ | |||||
void sendMessageNow (const MidiMessage& message); | |||||
/** Sends out a sequence of MIDI messages immediately. */ | |||||
void sendBlockOfMessagesNow (const MidiBuffer& buffer); | |||||
//============================================================================== | |||||
/** This lets you supply a block of messages that will be sent out at some point | |||||
in the future. | |||||
The MidiOutput class has an internal thread that can send out timestamped | |||||
messages - this appends a set of messages to its internal buffer, ready for | |||||
sending. | |||||
This will only work if you've already started the thread with startBackgroundThread(). | |||||
A time is specified, at which the block of messages should be sent. This time uses | |||||
the same time base as Time::getMillisecondCounter(), and must be in the future. | |||||
The samplesPerSecondForBuffer parameter indicates the number of samples per second | |||||
used by the MidiBuffer. Each event in a MidiBuffer has a sample position, and the | |||||
samplesPerSecondForBuffer value is needed to convert this sample position to a | |||||
real time. | |||||
*/ | |||||
void sendBlockOfMessages (const MidiBuffer& buffer, | |||||
double millisecondCounterToStartAt, | |||||
double samplesPerSecondForBuffer); | |||||
/** Gets rid of any midi messages that had been added by sendBlockOfMessages(). */ | |||||
void clearAllPendingMessages(); | |||||
/** Starts up a background thread so that the device can send blocks of data. | |||||
Call this to get the device ready, before using sendBlockOfMessages(). | |||||
*/ | |||||
void startBackgroundThread(); | |||||
/** Stops the background thread, and clears any pending midi events. | |||||
@see startBackgroundThread | |||||
*/ | |||||
void stopBackgroundThread(); | |||||
private: | |||||
//============================================================================== | |||||
void* internal = nullptr; | |||||
CriticalSection lock; | |||||
struct PendingMessage; | |||||
PendingMessage* firstMessage = nullptr; | |||||
String name; | |||||
MidiOutput (const String& midiName); // These objects are created with the openDevice() method. | |||||
void run() override; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput) | |||||
}; | |||||
} // namespace juce |
@@ -51,6 +51,8 @@ public class JuceMidiSupport | |||||
// send will do nothing on an input port | // send will do nothing on an input port | ||||
void sendMidi (byte[] msg, int offset, int count); | void sendMidi (byte[] msg, int offset, int count); | ||||
String getName (); | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -256,6 +258,12 @@ public class JuceMidiSupport | |||||
{ | { | ||||
} | } | ||||
@Override | |||||
public String getName () | |||||
{ | |||||
return owner.getPortName (portPath); | |||||
} | |||||
MidiDeviceManager owner; | MidiDeviceManager owner; | ||||
MidiOutputPort androidPort; | MidiOutputPort androidPort; | ||||
MidiPortPath portPath; | MidiPortPath portPath; | ||||
@@ -331,6 +339,12 @@ public class JuceMidiSupport | |||||
androidPort = null; | androidPort = null; | ||||
} | } | ||||
@Override | |||||
public String getName () | |||||
{ | |||||
return owner.getPortName (portPath); | |||||
} | |||||
MidiDeviceManager owner; | MidiDeviceManager owner; | ||||
MidiInputPort androidPort; | MidiInputPort androidPort; | ||||
MidiPortPath portPath; | MidiPortPath portPath; | ||||
@@ -343,7 +357,6 @@ public class JuceMidiSupport | |||||
deviceId = deviceIdToUse; | deviceId = deviceIdToUse; | ||||
isInput = direction; | isInput = direction; | ||||
portIndex = androidIndex; | portIndex = androidIndex; | ||||
} | } | ||||
public int deviceId; | public int deviceId; | ||||
@@ -555,17 +568,17 @@ public class JuceMidiSupport | |||||
super.finalize (); | super.finalize (); | ||||
} | } | ||||
public String[] getJuceAndroidMidiInputDevices () | |||||
public String[] getJuceAndroidMidiOutputDeviceNameAndIDs () | |||||
{ | { | ||||
return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); | |||||
return getJuceAndroidMidiDeviceNameAndIDs (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); | |||||
} | } | ||||
public String[] getJuceAndroidMidiOutputDevices () | |||||
public String[] getJuceAndroidMidiInputDeviceNameAndIDs () | |||||
{ | { | ||||
return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); | |||||
return getJuceAndroidMidiDeviceNameAndIDs (MidiDeviceInfo.PortInfo.TYPE_INPUT); | |||||
} | } | ||||
private String[] getJuceAndroidMidiDevices (int portType) | |||||
private String[] getJuceAndroidMidiDeviceNameAndIDs (int portType) | |||||
{ | { | ||||
// only update the list when JUCE asks for a new list | // only update the list when JUCE asks for a new list | ||||
synchronized (MidiDeviceManager.class) | synchronized (MidiDeviceManager.class) | ||||
@@ -573,22 +586,24 @@ public class JuceMidiSupport | |||||
deviceInfos = getDeviceInfos (); | deviceInfos = getDeviceInfos (); | ||||
} | } | ||||
ArrayList<String> portNames = new ArrayList<String> (); | |||||
ArrayList<String> portNameAndIDs = new ArrayList<String> (); | |||||
int index = 0; | |||||
for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index)) | |||||
portNames.add (getPortName (portInfo)); | |||||
for (MidiPortPath portInfo : getAllPorts (portType)) | |||||
{ | |||||
portNameAndIDs.add (getPortName (portInfo)); | |||||
portNameAndIDs.add (Integer.toString (portInfo.hashCode ())); | |||||
} | |||||
String[] names = new String[portNames.size ()]; | |||||
return portNames.toArray (names); | |||||
String[] names = new String[portNameAndIDs.size ()]; | |||||
return portNameAndIDs.toArray (names); | |||||
} | } | ||||
private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput) | |||||
private JuceMidiPort openMidiPortWithID (int deviceID, long host, boolean isInput) | |||||
{ | { | ||||
synchronized (MidiDeviceManager.class) | synchronized (MidiDeviceManager.class) | ||||
{ | { | ||||
int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT); | |||||
MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index); | |||||
int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT); | |||||
MidiPortPath portInfo = getPortPathForID (portTypeToFind, deviceID); | |||||
if (portInfo != null) | if (portInfo != null) | ||||
{ | { | ||||
@@ -633,14 +648,14 @@ public class JuceMidiSupport | |||||
return null; | return null; | ||||
} | } | ||||
public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) | |||||
public JuceMidiPort openMidiInputPortWithID (int deviceID, long host) | |||||
{ | { | ||||
return openMidiPortWithJuceIndex (index, host, true); | |||||
return openMidiPortWithID (deviceID, host, true); | |||||
} | } | ||||
public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) | |||||
public JuceMidiPort openMidiOutputPortWithID (int deviceID) | |||||
{ | { | ||||
return openMidiPortWithJuceIndex (index, 0, false); | |||||
return openMidiPortWithID (deviceID, 0, false); | |||||
} | } | ||||
/* 0: unpaired, 1: paired, 2: pairing */ | /* 0: unpaired, 1: paired, 2: pairing */ | ||||
@@ -773,24 +788,6 @@ public class JuceMidiSupport | |||||
openPorts.remove (path); | openPorts.remove (path); | ||||
} | } | ||||
public String getInputPortNameForJuceIndex (int index) | |||||
{ | |||||
MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index); | |||||
if (portInfo != null) | |||||
return getPortName (portInfo); | |||||
return ""; | |||||
} | |||||
public String getOutputPortNameForJuceIndex (int index) | |||||
{ | |||||
MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index); | |||||
if (portInfo != null) | |||||
return getPortName (portInfo); | |||||
return ""; | |||||
} | |||||
public void onDeviceAdded (MidiDeviceInfo info) | public void onDeviceAdded (MidiDeviceInfo info) | ||||
{ | { | ||||
// only add standard midi devices | // only add standard midi devices | ||||
@@ -980,24 +977,24 @@ public class JuceMidiSupport | |||||
return ""; | return ""; | ||||
} | } | ||||
public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex) | |||||
public ArrayList<MidiPortPath> getAllPorts (int portType) | |||||
{ | { | ||||
int portIdx = 0; | |||||
ArrayList<MidiPortPath> ports = new ArrayList<MidiPortPath> (); | |||||
for (MidiDeviceInfo info : deviceInfos) | for (MidiDeviceInfo info : deviceInfos) | ||||
{ | |||||
for (MidiDeviceInfo.PortInfo portInfo : info.getPorts ()) | for (MidiDeviceInfo.PortInfo portInfo : info.getPorts ()) | ||||
{ | |||||
if (portInfo.getType () == portType) | if (portInfo.getType () == portType) | ||||
{ | |||||
if (portIdx == juceIndex) | |||||
return new MidiPortPath (info.getId (), | |||||
(portType == MidiDeviceInfo.PortInfo.TYPE_INPUT), | |||||
portInfo.getPortNumber ()); | |||||
ports.add (new MidiPortPath (info.getId (), (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT), | |||||
portInfo.getPortNumber ())); | |||||
portIdx++; | |||||
} | |||||
} | |||||
} | |||||
return ports; | |||||
} | |||||
public MidiPortPath getPortPathForID (int portType, int deviceID) | |||||
{ | |||||
for (MidiPortPath port : getAllPorts (portType)) | |||||
if (port.hashCode () == deviceID) | |||||
return port; | |||||
return null; | return null; | ||||
} | } | ||||
@@ -66,9 +66,9 @@ public: | |||||
} | } | ||||
} | } | ||||
static StringArray getDevices (bool input) | |||||
static Array<MidiDeviceInfo> getDevices (bool input) | |||||
{ | { | ||||
StringArray devices; | |||||
Array<MidiDeviceInfo> devices; | |||||
for (auto& card : findAllALSACardIDs()) | for (auto& card : findAllALSACardIDs()) | ||||
findMidiDevices (devices, input, card); | findMidiDevices (devices, input, card); | ||||
@@ -96,7 +96,7 @@ private: | |||||
} | } | ||||
// Adds all midi devices to the devices array of the given input/output type on the given card | // Adds all midi devices to the devices array of the given input/output type on the given card | ||||
static void findMidiDevices (StringArray& devices, bool input, int cardNum) | |||||
static void findMidiDevices (Array<MidiDeviceInfo>& devices, bool input, int cardNum) | |||||
{ | { | ||||
snd_ctl_t* ctl = nullptr; | snd_ctl_t* ctl = nullptr; | ||||
auto status = snd_ctl_open (&ctl, ("hw:" + String (cardNum)).toRawUTF8(), 0); | auto status = snd_ctl_open (&ctl, ("hw:" + String (cardNum)).toRawUTF8(), 0); | ||||
@@ -131,9 +131,10 @@ private: | |||||
status = snd_ctl_rawmidi_info (ctl, info); | status = snd_ctl_rawmidi_info (ctl, info); | ||||
if (status == 0) | if (status == 0) | ||||
devices.add ("hw:" + String (cardNum) + "," | |||||
+ String (device) + "," | |||||
+ String (sub)); | |||||
{ | |||||
String deviceName ("hw:" + String (cardNum) + "," + String (device) + "," + String (sub)); | |||||
devices.add (MidiDeviceInfo (deviceName, deviceName)); | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -507,58 +508,74 @@ AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() | |||||
return new BelaAudioIODeviceType(); | return new BelaAudioIODeviceType(); | ||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
// TODO: Add Bela MidiOutput support | |||||
StringArray MidiOutput::getDevices() { return {}; } | |||||
int MidiOutput::getDefaultDeviceIndex() { return 0; } | |||||
MidiOutput* MidiOutput::openDevice (int) { return {}; } | |||||
MidiOutput* MidiOutput::createNewDevice (const String&) { return {}; } | |||||
MidiOutput::~MidiOutput() {} | |||||
void MidiOutput::sendMessageNow (const MidiMessage&) {} | |||||
MidiInput::MidiInput (const String& deviceName, const String& deviceID) | |||||
: deviceInfo (deviceName, deviceID) | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
MidiInput::MidiInput (const String& nm) : name (nm) {} | |||||
MidiInput::~MidiInput() { delete static_cast<BelaMidiInput*> (internal); } | |||||
void MidiInput::start() { static_cast<BelaMidiInput*> (internal)->start(); } | |||||
void MidiInput::stop() { static_cast<BelaMidiInput*> (internal)->stop(); } | |||||
MidiInput::~MidiInput() | |||||
void Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | |||||
{ | { | ||||
delete static_cast<BelaMidiInput*> (internal); | |||||
return BelaMidiInput::getDevices (true); | |||||
} | } | ||||
void MidiInput::start() { static_cast<BelaMidiInput*> (internal)->start(); } | |||||
void MidiInput::stop() { static_cast<BelaMidiInput*> (internal)->stop(); } | |||||
MidiDeviceInfo MidiInput::getDefaultDevice() | |||||
{ | |||||
return getAvailableDevices().getFirst(); | |||||
} | |||||
int MidiInput::getDefaultDeviceIndex() | |||||
MidiInput* MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) | |||||
{ | { | ||||
return 0; | |||||
if (deviceIdentifier.isEmpty()) | |||||
return nullptr; | |||||
std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceIdentifier, deviceIdentifier)); | |||||
midiInput->internal = new BelaMidiInput (deviceIdentifier, result, callback); | |||||
return midiInput.release(); | |||||
} | } | ||||
StringArray MidiInput::getDevices() | |||||
MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*) | |||||
{ | { | ||||
return BelaMidiInput::getDevices (true); | |||||
// N/A on Bela | |||||
jassertfalse; | |||||
return nullptr; | |||||
} | } | ||||
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||||
StringArray MidiInput::getDevices() | |||||
{ | { | ||||
auto devices = getDevices(); | |||||
StringArray deviceNames; | |||||
if (index >= 0 && index < devices.size()) | |||||
{ | |||||
auto deviceName = devices[index]; | |||||
auto result = new MidiInput (deviceName); | |||||
result->internal = new BelaMidiInput (deviceName, result, callback); | |||||
return result; | |||||
} | |||||
for (auto& d : getAvailableDevices()) | |||||
deviceNames.add (d.name); | |||||
return deviceNames; | |||||
} | |||||
return {}; | |||||
int MidiInput::getDefaultDeviceIndex() | |||||
{ | |||||
return 0; | |||||
} | } | ||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||||
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||||
{ | { | ||||
jassertfalse; // N/A on Bela | |||||
return {}; | |||||
return openDevice (getAvailableDevices()[index].identifier, callback); | |||||
} | } | ||||
//============================================================================== | |||||
// TODO: Add Bela MidiOutput support | |||||
MidiOutput::~MidiOutput() {} | |||||
void MidiOutput::sendMessageNow (const MidiMessage&) {} | |||||
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; } | |||||
MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; } | |||||
MidiOutput* MidiOutput::openDevice (const String&) { return nullptr; } | |||||
MidiOutput* MidiOutput::createNewDevice (const String&) { return nullptr; } | |||||
StringArray MidiOutput::getDevices() { return {}; } | |||||
int MidiOutput::getDefaultDeviceIndex() { return 0;} | |||||
MidiOutput* MidiOutput::openDevice (int) { return nullptr; } | |||||
} // namespace juce | } // namespace juce |
@@ -67,12 +67,12 @@ public: | |||||
static String getAlsaMidiName() | static String getAlsaMidiName() | ||||
{ | { | ||||
#ifdef JUCE_ALSA_MIDI_NAME | #ifdef JUCE_ALSA_MIDI_NAME | ||||
return JUCE_ALSA_MIDI_NAME; | |||||
return JUCE_ALSA_MIDI_NAME; | |||||
#else | #else | ||||
if (auto* app = JUCEApplicationBase::getInstance()) | |||||
return app->getApplicationName(); | |||||
if (auto* app = JUCEApplicationBase::getInstance()) | |||||
return app->getApplicationName(); | |||||
return "JUCE"; | |||||
return "JUCE"; | |||||
#endif | #endif | ||||
} | } | ||||
@@ -198,7 +198,8 @@ public: | |||||
isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0)) | isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0)) | ||||
: (SND_SEQ_PORT_CAP_READ | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0)); | : (SND_SEQ_PORT_CAP_READ | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0)); | ||||
portId = snd_seq_create_simple_port (seqHandle, name.toUTF8(), caps, | |||||
portName = name; | |||||
portId = snd_seq_create_simple_port (seqHandle, portName.toUTF8(), caps, | |||||
SND_SEQ_PORT_TYPE_MIDI_GENERIC | | SND_SEQ_PORT_TYPE_MIDI_GENERIC | | ||||
SND_SEQ_PORT_TYPE_APPLICATION); | SND_SEQ_PORT_TYPE_APPLICATION); | ||||
} | } | ||||
@@ -215,13 +216,15 @@ public: | |||||
} | } | ||||
AlsaClient& client; | AlsaClient& client; | ||||
MidiInputCallback* callback = nullptr; | MidiInputCallback* callback = nullptr; | ||||
snd_midi_event_t* midiParser = nullptr; | snd_midi_event_t* midiParser = nullptr; | ||||
MidiInput* midiInput = nullptr; | MidiInput* midiInput = nullptr; | ||||
int maxEventSize = 4096; | |||||
int portId = -1; | |||||
bool callbackEnabled = false; | |||||
bool isInput = false; | |||||
String portName; | |||||
int maxEventSize = 4096, portId = -1; | |||||
bool callbackEnabled = false, isInput = false; | |||||
}; | }; | ||||
static Ptr getInstance() | static Ptr getInstance() | ||||
@@ -359,11 +362,16 @@ private: | |||||
AlsaClient* AlsaClient::instance = nullptr; | AlsaClient* AlsaClient::instance = nullptr; | ||||
//============================================================================== | //============================================================================== | ||||
static String getFormattedPortIdentifier (int clientId, int portId) | |||||
{ | |||||
return String (clientId) + "-" + String (portId); | |||||
} | |||||
static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, | static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, | ||||
snd_seq_client_info_t* clientInfo, | snd_seq_client_info_t* clientInfo, | ||||
bool forInput, | bool forInput, | ||||
StringArray& deviceNamesFound, | |||||
int deviceIndexToOpen) | |||||
Array<MidiDeviceInfo>& devices, | |||||
const String& deviceIdentifierToOpen) | |||||
{ | { | ||||
AlsaClient::Port* port = nullptr; | AlsaClient::Port* port = nullptr; | ||||
@@ -371,7 +379,7 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, | |||||
snd_seq_port_info_t* portInfo = nullptr; | snd_seq_port_info_t* portInfo = nullptr; | ||||
snd_seq_port_info_alloca (&portInfo); | snd_seq_port_info_alloca (&portInfo); | ||||
jassert (portInfo); | |||||
jassert (portInfo != nullptr); | |||||
auto numPorts = snd_seq_client_info_get_num_ports (clientInfo); | auto numPorts = snd_seq_client_info_get_num_ports (clientInfo); | ||||
auto sourceClient = snd_seq_client_info_get_client (clientInfo); | auto sourceClient = snd_seq_client_info_get_client (clientInfo); | ||||
@@ -384,19 +392,19 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, | |||||
&& (snd_seq_port_info_get_capability (portInfo) | && (snd_seq_port_info_get_capability (portInfo) | ||||
& (forInput ? SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_SUBS_WRITE)) != 0) | & (forInput ? SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_SUBS_WRITE)) != 0) | ||||
{ | { | ||||
String portName = snd_seq_port_info_get_name(portInfo); | |||||
String portName (snd_seq_port_info_get_name (portInfo)); | |||||
auto portID = snd_seq_port_info_get_port (portInfo); | |||||
deviceNamesFound.add (portName); | |||||
MidiDeviceInfo device (portName, getFormattedPortIdentifier (sourceClient, portID)); | |||||
devices.add (device); | |||||
if (deviceNamesFound.size() == deviceIndexToOpen + 1) | |||||
if (deviceIdentifierToOpen.isNotEmpty() && deviceIdentifierToOpen == device.identifier) | |||||
{ | { | ||||
auto sourcePort = snd_seq_port_info_get_port (portInfo); | |||||
if (sourcePort != -1) | |||||
if (portID != -1) | |||||
{ | { | ||||
port = client->createPort (portName, forInput, false); | port = client->createPort (portName, forInput, false); | ||||
jassert (port->isValid()); | jassert (port->isValid()); | ||||
port->connectWith (sourceClient, sourcePort); | |||||
port->connectWith (sourceClient, portID); | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
@@ -407,8 +415,8 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, | |||||
} | } | ||||
static AlsaClient::Port* iterateMidiDevices (bool forInput, | static AlsaClient::Port* iterateMidiDevices (bool forInput, | ||||
StringArray& deviceNamesFound, | |||||
int deviceIndexToOpen) | |||||
Array<MidiDeviceInfo>& devices, | |||||
const String& deviceIdentifierToOpen) | |||||
{ | { | ||||
AlsaClient::Port* port = nullptr; | AlsaClient::Port* port = nullptr; | ||||
auto client = AlsaClient::getInstance(); | auto client = AlsaClient::getInstance(); | ||||
@@ -432,85 +440,95 @@ static AlsaClient::Port* iterateMidiDevices (bool forInput, | |||||
{ | { | ||||
if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) | if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) | ||||
{ | { | ||||
auto sourceClient = snd_seq_client_info_get_client (clientInfo); | |||||
port = iterateMidiClient (client, clientInfo, forInput, | |||||
devices, deviceIdentifierToOpen); | |||||
if (sourceClient != client->getId() && sourceClient != SND_SEQ_CLIENT_SYSTEM) | |||||
{ | |||||
port = iterateMidiClient (client, clientInfo, forInput, | |||||
deviceNamesFound, deviceIndexToOpen); | |||||
if (port != nullptr) | |||||
break; | |||||
} | |||||
if (port != nullptr) | |||||
break; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
deviceNamesFound.appendNumbersToDuplicates (true, true); | |||||
return port; | return port; | ||||
} | } | ||||
} // namespace | } // namespace | ||||
StringArray MidiOutput::getDevices() | |||||
//============================================================================== | |||||
Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | |||||
{ | { | ||||
StringArray devices; | |||||
iterateMidiDevices (false, devices, -1); | |||||
Array<MidiDeviceInfo> devices; | |||||
iterateMidiDevices (true, devices, {}); | |||||
return devices; | return devices; | ||||
} | } | ||||
int MidiOutput::getDefaultDeviceIndex() | |||||
MidiDeviceInfo MidiInput::getDefaultDevice() | |||||
{ | { | ||||
return 0; | |||||
return getAvailableDevices().getFirst(); | |||||
} | } | ||||
MidiOutput* MidiOutput::openDevice (int deviceIndex) | |||||
MidiInput* MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) | |||||
{ | { | ||||
MidiOutput* newDevice = nullptr; | |||||
if (deviceIdentifier.isEmpty()) | |||||
return nullptr; | |||||
StringArray devices; | |||||
auto* port = iterateMidiDevices (false, devices, deviceIndex); | |||||
Array<MidiDeviceInfo> devices; | |||||
auto* port = iterateMidiDevices (true, devices, deviceIdentifier); | |||||
if (port == nullptr) | if (port == nullptr) | ||||
return nullptr; | return nullptr; | ||||
jassert (port->isValid()); | jassert (port->isValid()); | ||||
newDevice = new MidiOutput (devices [deviceIndex]); | |||||
port->setupOutput(); | |||||
newDevice->internal = port; | |||||
std::unique_ptr<MidiInput> midiInput (new MidiInput (port->portName, deviceIdentifier)); | |||||
return newDevice; | |||||
port->setupInput (midiInput.get(), callback); | |||||
midiInput->internal = port; | |||||
return midiInput.release(); | |||||
} | } | ||||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||||
{ | { | ||||
MidiOutput* newDevice = nullptr; | |||||
auto client = AlsaClient::getInstance(); | auto client = AlsaClient::getInstance(); | ||||
auto* port = client->createPort (deviceName, false, true); | |||||
jassert (port != nullptr && port->isValid()); | |||||
auto* port = client->createPort (deviceName, true, true); | |||||
newDevice = new MidiOutput (deviceName); | |||||
port->setupOutput(); | |||||
newDevice->internal = port; | |||||
jassert (port->isValid()); | |||||
std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId))); | |||||
return newDevice; | |||||
port->setupInput (midiInput.get(), callback); | |||||
midiInput->internal = port; | |||||
return midiInput.release(); | |||||
} | } | ||||
MidiOutput::~MidiOutput() | |||||
StringArray MidiInput::getDevices() | |||||
{ | { | ||||
stopBackgroundThread(); | |||||
AlsaClient::getInstance()->deletePort (static_cast<AlsaClient::Port*> (internal)); | |||||
StringArray deviceNames; | |||||
for (auto& d : getAvailableDevices()) | |||||
deviceNames.add (d.name); | |||||
deviceNames.appendNumbersToDuplicates (true, true); | |||||
return deviceNames; | |||||
} | } | ||||
void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
int MidiInput::getDefaultDeviceIndex() | |||||
{ | { | ||||
static_cast<AlsaClient::Port*> (internal)->sendMessageNow (message); | |||||
return 0; | |||||
} | } | ||||
//============================================================================== | |||||
MidiInput::MidiInput (const String& nm) : name (nm) | |||||
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||||
{ | |||||
return openDevice (getAvailableDevices()[index].identifier, callback); | |||||
} | |||||
MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) | |||||
: deviceInfo (deviceName, deviceIdentifier) | |||||
{ | { | ||||
} | } | ||||
@@ -530,68 +548,118 @@ void MidiInput::stop() | |||||
static_cast<AlsaClient::Port*> (internal)->enableCallback (false); | static_cast<AlsaClient::Port*> (internal)->enableCallback (false); | ||||
} | } | ||||
int MidiInput::getDefaultDeviceIndex() | |||||
//============================================================================== | |||||
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | |||||
{ | { | ||||
return 0; | |||||
Array<MidiDeviceInfo> devices; | |||||
iterateMidiDevices (false, devices, {}); | |||||
return devices; | |||||
} | } | ||||
StringArray MidiInput::getDevices() | |||||
MidiDeviceInfo MidiOutput::getDefaultDevice() | |||||
{ | { | ||||
StringArray devices; | |||||
iterateMidiDevices (true, devices, -1); | |||||
return devices; | |||||
return getAvailableDevices().getFirst(); | |||||
} | } | ||||
MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | |||||
MidiOutput* MidiOutput::openDevice (const String& deviceIdentifier) | |||||
{ | { | ||||
StringArray devices; | |||||
auto* port = iterateMidiDevices (true, devices, deviceIndex); | |||||
if (deviceIdentifier.isEmpty()) | |||||
return nullptr; | |||||
Array<MidiDeviceInfo> devices; | |||||
auto* port = iterateMidiDevices (false, devices, deviceIdentifier); | |||||
if (port == nullptr) | if (port == nullptr) | ||||
return nullptr; | return nullptr; | ||||
jassert (port->isValid()); | jassert (port->isValid()); | ||||
auto newDevice = new MidiInput (devices [deviceIndex]); | |||||
port->setupInput (newDevice, callback); | |||||
newDevice->internal = port; | |||||
return newDevice; | |||||
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (port->portName, deviceIdentifier)); | |||||
port->setupOutput(); | |||||
midiOutput->internal = port; | |||||
return midiOutput.release(); | |||||
} | } | ||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||||
{ | { | ||||
auto client = AlsaClient::getInstance(); | auto client = AlsaClient::getInstance(); | ||||
auto* port = client->createPort (deviceName, true, true); | |||||
auto* port = client->createPort (deviceName, false, true); | |||||
jassert (port->isValid()); | |||||
jassert (port != nullptr && port->isValid()); | |||||
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId))); | |||||
auto newDevice = new MidiInput (deviceName); | |||||
port->setupInput (newDevice, callback); | |||||
newDevice->internal = port; | |||||
return newDevice; | |||||
port->setupOutput(); | |||||
midiOutput->internal = port; | |||||
return midiOutput.release(); | |||||
} | } | ||||
StringArray MidiOutput::getDevices() | |||||
{ | |||||
StringArray deviceNames; | |||||
for (auto& d : getAvailableDevices()) | |||||
deviceNames.add (d.name); | |||||
deviceNames.appendNumbersToDuplicates (true, true); | |||||
return deviceNames; | |||||
} | |||||
int MidiOutput::getDefaultDeviceIndex() | |||||
{ | |||||
return 0; | |||||
} | |||||
MidiOutput* MidiOutput::openDevice (int index) | |||||
{ | |||||
return openDevice (getAvailableDevices()[index].identifier); | |||||
} | |||||
MidiOutput::~MidiOutput() | |||||
{ | |||||
stopBackgroundThread(); | |||||
AlsaClient::getInstance()->deletePort (static_cast<AlsaClient::Port*> (internal)); | |||||
} | |||||
void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
{ | |||||
static_cast<AlsaClient::Port*> (internal)->sendMessageNow (message); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
#else | #else | ||||
// (These are just stub functions if ALSA is unavailable...) | // (These are just stub functions if ALSA is unavailable...) | ||||
MidiInput::MidiInput (const String& deviceName, const String& deviceID) | |||||
: deviceInfo (deviceName, deviceID) | |||||
{ | |||||
} | |||||
StringArray MidiOutput::getDevices() { return {}; } | |||||
int MidiOutput::getDefaultDeviceIndex() { return 0; } | |||||
MidiOutput* MidiOutput::openDevice (int) { return nullptr; } | |||||
MidiOutput* MidiOutput::createNewDevice (const String&) { return nullptr; } | |||||
MidiOutput::~MidiOutput() {} | |||||
void MidiOutput::sendMessageNow (const MidiMessage&) {} | |||||
MidiInput::MidiInput (const String& nm) : name (nm) {} | |||||
MidiInput::~MidiInput() {} | |||||
void MidiInput::start() {} | |||||
void MidiInput::stop() {} | |||||
int MidiInput::getDefaultDeviceIndex() { return 0; } | |||||
StringArray MidiInput::getDevices() { return {}; } | |||||
MidiInput* MidiInput::openDevice (int, MidiInputCallback*) { return nullptr; } | |||||
MidiInput::~MidiInput() {} | |||||
void MidiInput::start() {} | |||||
void MidiInput::stop() {} | |||||
Array<MidiDeviceInfo> MidiInput::getAvailableDevices() { return {}; } | |||||
MidiDeviceInfo MidiInput::getDefaultDevice() { return {}; } | |||||
MidiInput* MidiInput::openDevice (const String&, MidiInputCallback*) { return nullptr; } | |||||
MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*) { return nullptr; } | MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*) { return nullptr; } | ||||
StringArray MidiInput::getDevices() { return {}; } | |||||
int MidiInput::getDefaultDeviceIndex() { return 0;} | |||||
MidiInput* MidiInput::openDevice (int, MidiInputCallback*) { return nullptr; } | |||||
MidiOutput::~MidiOutput() {} | |||||
void MidiOutput::sendMessageNow (const MidiMessage&) {} | |||||
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; } | |||||
MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; } | |||||
MidiOutput* MidiOutput::openDevice (const String&) { return nullptr; } | |||||
MidiOutput* MidiOutput::createNewDevice (const String&) { return nullptr; } | |||||
StringArray MidiOutput::getDevices() { return {}; } | |||||
int MidiOutput::getDefaultDeviceIndex() { return 0;} | |||||
MidiOutput* MidiOutput::openDevice (int) { return nullptr; } | |||||
#endif | #endif | ||||
@@ -29,6 +29,22 @@ namespace juce | |||||
namespace CoreMidiHelpers | namespace CoreMidiHelpers | ||||
{ | { | ||||
//============================================================================== | |||||
struct ScopedCFString | |||||
{ | |||||
ScopedCFString() = default; | |||||
ScopedCFString (String s) : cfString (s.toCFString()) {} | |||||
~ScopedCFString() noexcept | |||||
{ | |||||
if (cfString != nullptr) | |||||
CFRelease (cfString); | |||||
} | |||||
CFStringRef cfString = {}; | |||||
}; | |||||
//============================================================================== | |||||
static bool checkError (OSStatus err, int lineNum) | static bool checkError (OSStatus err, int lineNum) | ||||
{ | { | ||||
if (err == noErr) | if (err == noErr) | ||||
@@ -45,79 +61,62 @@ namespace CoreMidiHelpers | |||||
#undef CHECK_ERROR | #undef CHECK_ERROR | ||||
#define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__) | #define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__) | ||||
//============================================================================== | |||||
struct ScopedCFString | |||||
static MidiDeviceInfo getMidiObjectInfo (MIDIObjectRef entity) | |||||
{ | { | ||||
ScopedCFString() noexcept {} | |||||
~ScopedCFString() noexcept { if (cfString != nullptr) CFRelease (cfString); } | |||||
MidiDeviceInfo info; | |||||
CFStringRef cfString = {}; | |||||
}; | |||||
static String getMidiObjectName (MIDIObjectRef entity) | |||||
{ | |||||
String result; | |||||
CFStringRef str = nullptr; | |||||
MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str); | |||||
if (str != nullptr) | |||||
{ | { | ||||
result = String::fromCFString (str); | |||||
CFRelease (str); | |||||
} | |||||
ScopedCFString str; | |||||
return result; | |||||
} | |||||
if (CHECK_ERROR (MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str.cfString))) | |||||
info.name = String::fromCFString (str.cfString); | |||||
} | |||||
static void enableSimulatorMidiSession() | |||||
{ | |||||
#if TARGET_OS_SIMULATOR | |||||
static bool hasEnabledNetworkSession = false; | |||||
SInt32 objectID = 0; | |||||
if (! hasEnabledNetworkSession) | |||||
{ | |||||
MIDINetworkSession* session = [MIDINetworkSession defaultSession]; | |||||
session.enabled = YES; | |||||
session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; | |||||
if (CHECK_ERROR (MIDIObjectGetIntegerProperty (entity, kMIDIPropertyUniqueID, &objectID))) | |||||
info.identifier = String (objectID); | |||||
hasEnabledNetworkSession = true; | |||||
} | |||||
#endif | |||||
return info; | |||||
} | } | ||||
static String getEndpointName (MIDIEndpointRef endpoint, bool isExternal) | |||||
static MidiDeviceInfo getEndpointInfo (MIDIEndpointRef endpoint, bool isExternal) | |||||
{ | { | ||||
auto result = getMidiObjectName (endpoint); | |||||
MIDIEntityRef entity = 0; // NB: don't attempt to use nullptr for refs - it fails in some types of build. | |||||
// NB: don't attempt to use nullptr for refs - it fails in some types of build. | |||||
MIDIEntityRef entity = 0; | |||||
MIDIEndpointGetEntity (endpoint, &entity); | MIDIEndpointGetEntity (endpoint, &entity); | ||||
// probably virtual | |||||
if (entity == 0) | if (entity == 0) | ||||
return result; // probably virtual | |||||
return getMidiObjectInfo (endpoint); | |||||
auto result = getMidiObjectInfo (endpoint); | |||||
if (result.isEmpty()) | |||||
result = getMidiObjectName (entity); // endpoint name is empty - try the entity | |||||
// endpoint is empty - try the entity | |||||
if (result == MidiDeviceInfo()) | |||||
result = getMidiObjectInfo (entity); | |||||
// now consider the device's name | |||||
// now consider the device | |||||
MIDIDeviceRef device = 0; | MIDIDeviceRef device = 0; | ||||
MIDIEntityGetDevice (entity, &device); | MIDIEntityGetDevice (entity, &device); | ||||
if (device != 0) | if (device != 0) | ||||
{ | { | ||||
auto deviceName = getMidiObjectName (device); | |||||
auto info = getMidiObjectInfo (device); | |||||
if (deviceName.isNotEmpty()) | |||||
if (info != MidiDeviceInfo()) | |||||
{ | { | ||||
// if an external device has only one entity, throw away | // if an external device has only one entity, throw away | ||||
// the endpoint name and just use the device name | // the endpoint name and just use the device name | ||||
if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2) | if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2) | ||||
{ | { | ||||
result = deviceName; | |||||
result = info; | |||||
} | } | ||||
else if (! result.startsWithIgnoreCase (deviceName)) | |||||
else if (! result.name.startsWithIgnoreCase (info.name)) | |||||
{ | { | ||||
// prepend the device name to the entity name | |||||
result = (deviceName + " " + result).trimEnd(); | |||||
// prepend the device name and identifier to the entity's | |||||
result.name = (info.name + " " + result.name).trimEnd(); | |||||
result.identifier = info.identifier + " " + result.identifier; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -125,9 +124,9 @@ namespace CoreMidiHelpers | |||||
return result; | return result; | ||||
} | } | ||||
static String getConnectedEndpointName (MIDIEndpointRef endpoint) | |||||
static MidiDeviceInfo getConnectedEndpointInfo (MIDIEndpointRef endpoint) | |||||
{ | { | ||||
String result; | |||||
MidiDeviceInfo result; | |||||
// Does the endpoint have connections? | // Does the endpoint have connections? | ||||
CFDataRef connections = nullptr; | CFDataRef connections = nullptr; | ||||
@@ -141,37 +140,38 @@ namespace CoreMidiHelpers | |||||
if (numConnections > 0) | if (numConnections > 0) | ||||
{ | { | ||||
auto pid = reinterpret_cast<const SInt32*> (CFDataGetBytePtr (connections)); | |||||
auto* pid = reinterpret_cast<const SInt32*> (CFDataGetBytePtr (connections)); | |||||
for (int i = 0; i < numConnections; ++i, ++pid) | for (int i = 0; i < numConnections; ++i, ++pid) | ||||
{ | { | ||||
auto uid = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid); | |||||
auto id = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid); | |||||
MIDIObjectRef connObject; | MIDIObjectRef connObject; | ||||
MIDIObjectType connObjectType; | MIDIObjectType connObjectType; | ||||
auto err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType); | |||||
auto err = MIDIObjectFindByUniqueID (id, &connObject, &connObjectType); | |||||
if (err == noErr) | if (err == noErr) | ||||
{ | { | ||||
String s; | |||||
MidiDeviceInfo deviceInfo; | |||||
if (connObjectType == kMIDIObjectType_ExternalSource | if (connObjectType == kMIDIObjectType_ExternalSource | ||||
|| connObjectType == kMIDIObjectType_ExternalDestination) | || connObjectType == kMIDIObjectType_ExternalDestination) | ||||
{ | { | ||||
// Connected to an external device's endpoint (10.3 and later). | // Connected to an external device's endpoint (10.3 and later). | ||||
s = getEndpointName (static_cast<MIDIEndpointRef> (connObject), true); | |||||
deviceInfo = getEndpointInfo (static_cast<MIDIEndpointRef> (connObject), true); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
// Connected to an external device (10.2) (or something else, catch-all) | // Connected to an external device (10.2) (or something else, catch-all) | ||||
s = getMidiObjectName (connObject); | |||||
deviceInfo = getMidiObjectInfo (connObject); | |||||
} | } | ||||
if (s.isNotEmpty()) | |||||
if (deviceInfo != MidiDeviceInfo()) | |||||
{ | { | ||||
if (result.isNotEmpty()) | |||||
result += ", "; | |||||
if (result.name.isNotEmpty()) result.name += ", "; | |||||
if (result.identifier.isNotEmpty()) result.identifier += ", "; | |||||
result += s; | |||||
result.name += deviceInfo.name; | |||||
result.identifier += deviceInfo.identifier; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -180,17 +180,19 @@ namespace CoreMidiHelpers | |||||
CFRelease (connections); | CFRelease (connections); | ||||
} | } | ||||
if (result.isEmpty()) // Here, either the endpoint had no connections, or we failed to obtain names for them. | |||||
result = getEndpointName (endpoint, false); | |||||
// Here, either the endpoint had no connections, or we failed to obtain names for them. | |||||
if (result == MidiDeviceInfo()) | |||||
return getEndpointInfo (endpoint, false); | |||||
return result; | return result; | ||||
} | } | ||||
static void setUniqueIdForMidiPort (MIDIObjectRef device, const String& portName, bool isInput) | |||||
static int createUniqueIDForMidiPort (String deviceName, bool isInput) | |||||
{ | { | ||||
String portUniqueId; | |||||
#if defined (JucePlugin_CFBundleIdentifier) | |||||
portUniqueId = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier); | |||||
String uniqueID; | |||||
#ifdef JucePlugin_CFBundleIdentifier | |||||
uniqueID = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier); | |||||
#else | #else | ||||
auto appBundle = File::getSpecialLocation (File::currentApplicationFile); | auto appBundle = File::getSpecialLocation (File::currentApplicationFile); | ||||
@@ -203,19 +205,33 @@ namespace CoreMidiHelpers | |||||
if (bundleRef != nullptr) | if (bundleRef != nullptr) | ||||
{ | { | ||||
if (auto bundleId = CFBundleGetIdentifier (bundleRef)) | if (auto bundleId = CFBundleGetIdentifier (bundleRef)) | ||||
portUniqueId = String::fromCFString (bundleId); | |||||
uniqueID = String::fromCFString (bundleId); | |||||
CFRelease (bundleRef); | CFRelease (bundleRef); | ||||
} | } | ||||
} | } | ||||
#endif | #endif | ||||
if (portUniqueId.isNotEmpty()) | |||||
if (uniqueID.isNotEmpty()) | |||||
uniqueID += "." + deviceName + (isInput ? ".input" : ".output"); | |||||
return uniqueID.hashCode(); | |||||
} | |||||
static void enableSimulatorMidiSession() | |||||
{ | |||||
#if TARGET_OS_SIMULATOR | |||||
static bool hasEnabledNetworkSession = false; | |||||
if (! hasEnabledNetworkSession) | |||||
{ | { | ||||
portUniqueId += "." + portName + (isInput ? ".input" : ".output"); | |||||
MIDINetworkSession* session = [MIDINetworkSession defaultSession]; | |||||
session.enabled = YES; | |||||
session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; | |||||
CHECK_ERROR (MIDIObjectSetStringProperty (device, kMIDIPropertyUniqueID, portUniqueId.toCFString())); | |||||
hasEnabledNetworkSession = true; | |||||
} | } | ||||
#endif | |||||
} | } | ||||
static void globalSystemChangeCallback (const MIDINotification*, void*) | static void globalSystemChangeCallback (const MIDINotification*, void*) | ||||
@@ -243,15 +259,14 @@ namespace CoreMidiHelpers | |||||
enableSimulatorMidiSession(); | enableSimulatorMidiSession(); | ||||
CoreMidiHelpers::ScopedCFString name; | |||||
name.cfString = getGlobalMidiClientName().toCFString(); | |||||
CoreMidiHelpers::ScopedCFString name (getGlobalMidiClientName()); | |||||
CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | ||||
} | } | ||||
return globalMidiClient; | return globalMidiClient; | ||||
} | } | ||||
static StringArray findDevices (bool forInput) | |||||
static Array<MidiDeviceInfo> findDevices (bool forInput) | |||||
{ | { | ||||
// It seems that OSX can be a bit picky about the thread that's first used to | // It seems that OSX can be a bit picky about the thread that's first used to | ||||
// search for devices. It's safest to use the message thread for calling this. | // search for devices. It's safest to use the message thread for calling this. | ||||
@@ -263,26 +278,25 @@ namespace CoreMidiHelpers | |||||
return {}; | return {}; | ||||
} | } | ||||
StringArray s; | |||||
enableSimulatorMidiSession(); | enableSimulatorMidiSession(); | ||||
auto num = forInput ? MIDIGetNumberOfSources() | |||||
: MIDIGetNumberOfDestinations(); | |||||
Array<MidiDeviceInfo> devices; | |||||
auto numDevices = (forInput ? MIDIGetNumberOfSources() : MIDIGetNumberOfDestinations()); | |||||
for (ItemCount i = 0; i < num; ++i) | |||||
for (ItemCount i = 0; i < numDevices; ++i) | |||||
{ | { | ||||
String name; | |||||
MidiDeviceInfo deviceInfo; | |||||
if (auto dest = forInput ? MIDIGetSource (i) : MIDIGetDestination (i)) | if (auto dest = forInput ? MIDIGetSource (i) : MIDIGetDestination (i)) | ||||
name = getConnectedEndpointName (dest); | |||||
deviceInfo = getConnectedEndpointInfo (dest); | |||||
if (name.isEmpty()) | |||||
name = "<error>"; | |||||
if (deviceInfo == MidiDeviceInfo()) | |||||
deviceInfo.name = deviceInfo.identifier = "<error>"; | |||||
s.add (name); | |||||
devices.add (deviceInfo); | |||||
} | } | ||||
return s; | |||||
return devices; | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -290,7 +304,7 @@ namespace CoreMidiHelpers | |||||
{ | { | ||||
public: | public: | ||||
MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept | MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept | ||||
: port (p), endPoint (ep) | |||||
: port (p), endpoint (ep) | |||||
{ | { | ||||
} | } | ||||
@@ -299,20 +313,21 @@ namespace CoreMidiHelpers | |||||
if (port != 0) | if (port != 0) | ||||
MIDIPortDispose (port); | MIDIPortDispose (port); | ||||
if (port == 0 && endPoint != 0) // if port == nullptr, it means we created the endpoint, so it's safe to delete it | |||||
MIDIEndpointDispose (endPoint); | |||||
// if port == nullptr, it means we created the endpoint, so it's safe to delete it | |||||
if (port == 0 && endpoint != 0) | |||||
MIDIEndpointDispose (endpoint); | |||||
} | } | ||||
void send (const MIDIPacketList* packets) noexcept | void send (const MIDIPacketList* packets) noexcept | ||||
{ | { | ||||
if (port != 0) | if (port != 0) | ||||
MIDISend (port, endPoint, packets); | |||||
MIDISend (port, endpoint, packets); | |||||
else | else | ||||
MIDIReceived (endPoint, packets); | |||||
MIDIReceived (endpoint, packets); | |||||
} | } | ||||
MIDIPortRef port; | MIDIPortRef port; | ||||
MIDIEndpointRef endPoint; | |||||
MIDIEndpointRef endpoint; | |||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
@@ -334,7 +349,7 @@ namespace CoreMidiHelpers | |||||
} | } | ||||
if (portAndEndpoint != nullptr && portAndEndpoint->port != 0) | if (portAndEndpoint != nullptr && portAndEndpoint->port != 0) | ||||
CHECK_ERROR (MIDIPortDisconnectSource (portAndEndpoint->port, portAndEndpoint->endPoint)); | |||||
CHECK_ERROR (MIDIPortDisconnectSource (portAndEndpoint->port, portAndEndpoint->endpoint)); | |||||
} | } | ||||
void handlePackets (const MIDIPacketList* pktlist) | void handlePackets (const MIDIPacketList* pktlist) | ||||
@@ -370,63 +385,250 @@ namespace CoreMidiHelpers | |||||
{ | { | ||||
static_cast<MidiPortAndCallback*> (readProcRefCon)->handlePackets (pktlist); | static_cast<MidiPortAndCallback*> (readProcRefCon)->handlePackets (pktlist); | ||||
} | } | ||||
static Array<MIDIEndpointRef> getEndpoints (bool isInput) | |||||
{ | |||||
Array<MIDIEndpointRef> endpoints; | |||||
auto numDevices = (isInput ? MIDIGetNumberOfSources() : MIDIGetNumberOfDevices()); | |||||
for (ItemCount i = 0; i < numDevices; ++i) | |||||
endpoints.add (isInput ? MIDIGetSource (i) : MIDIGetDestination (i)); | |||||
return endpoints; | |||||
} | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
StringArray MidiOutput::getDevices() { return CoreMidiHelpers::findDevices (false); } | |||||
int MidiOutput::getDefaultDeviceIndex() { return 0; } | |||||
Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | |||||
{ | |||||
return CoreMidiHelpers::findDevices (true); | |||||
} | |||||
MidiOutput* MidiOutput::openDevice (int index) | |||||
MidiDeviceInfo MidiInput::getDefaultDevice() | |||||
{ | |||||
return getAvailableDevices().getFirst(); | |||||
} | |||||
MidiInput* MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) | |||||
{ | |||||
if (deviceIdentifier.isEmpty()) | |||||
return nullptr; | |||||
using namespace CoreMidiHelpers; | |||||
if (auto client = getGlobalMidiClient()) | |||||
{ | |||||
for (auto& endpoint : getEndpoints (true)) | |||||
{ | |||||
if (deviceIdentifier == getConnectedEndpointInfo (endpoint).identifier) | |||||
{ | |||||
ScopedCFString cfName; | |||||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString))) | |||||
{ | |||||
MIDIPortRef port; | |||||
std::unique_ptr<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||||
if (CHECK_ERROR (MIDIInputPortCreate (client, cfName.cfString, midiInputProc, mpc.get(), &port))) | |||||
{ | |||||
if (CHECK_ERROR (MIDIPortConnectSource (port, endpoint, nullptr))) | |||||
{ | |||||
mpc->portAndEndpoint.reset (new MidiPortAndEndpoint (port, endpoint)); | |||||
std::unique_ptr<MidiInput> midiInput (new MidiInput (String::fromCFString (cfName.cfString), deviceIdentifier)); | |||||
mpc->input = midiInput.get(); | |||||
midiInput->internal = mpc.get(); | |||||
const ScopedLock sl (callbackLock); | |||||
activeCallbacks.add (mpc.release()); | |||||
return midiInput.release(); | |||||
} | |||||
else | |||||
{ | |||||
CHECK_ERROR (MIDIPortDispose (port)); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return nullptr; | |||||
} | |||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||||
{ | { | ||||
MidiOutput* mo = nullptr; | |||||
using namespace CoreMidiHelpers; | |||||
jassert (callback != nullptr); | |||||
if (auto client = CoreMidiHelpers::getGlobalMidiClient()) | |||||
if (auto client = getGlobalMidiClient()) | |||||
{ | { | ||||
if (isPositiveAndBelow (index, MIDIGetNumberOfDestinations())) | |||||
auto mpc = std::make_unique<MidiPortAndCallback> (*callback); | |||||
mpc->active = false; | |||||
MIDIEndpointRef endpoint; | |||||
ScopedCFString name (deviceName); | |||||
if (CHECK_ERROR (MIDIDestinationCreate (client, name.cfString, midiInputProc, mpc.get(), &endpoint))) | |||||
{ | { | ||||
auto endPoint = MIDIGetDestination ((ItemCount) index); | |||||
auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, true); | |||||
if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) | |||||
{ | |||||
mpc->portAndEndpoint.reset (new MidiPortAndEndpoint (0, endpoint)); | |||||
std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, String (deviceIdentifier))); | |||||
mpc->input = midiInput.get(); | |||||
midiInput->internal = mpc.get(); | |||||
const ScopedLock sl (callbackLock); | |||||
activeCallbacks.add (mpc.release()); | |||||
return midiInput.release(); | |||||
} | |||||
} | |||||
} | |||||
return nullptr; | |||||
} | |||||
StringArray MidiInput::getDevices() | |||||
{ | |||||
StringArray deviceNames; | |||||
for (auto& d : getAvailableDevices()) | |||||
deviceNames.add (d.name); | |||||
return deviceNames; | |||||
} | |||||
int MidiInput::getDefaultDeviceIndex() | |||||
{ | |||||
return 0; | |||||
} | |||||
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||||
{ | |||||
return openDevice (getAvailableDevices()[index].identifier, callback); | |||||
} | |||||
MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) | |||||
: deviceInfo (deviceName, deviceIdentifier) | |||||
{ | |||||
} | |||||
CoreMidiHelpers::ScopedCFString pname; | |||||
MidiInput::~MidiInput() | |||||
{ | |||||
delete static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal); | |||||
} | |||||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString))) | |||||
void MidiInput::start() | |||||
{ | |||||
const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||||
static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = true; | |||||
} | |||||
void MidiInput::stop() | |||||
{ | |||||
const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||||
static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = false; | |||||
} | |||||
//============================================================================== | |||||
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | |||||
{ | |||||
return CoreMidiHelpers::findDevices (false); | |||||
} | |||||
MidiDeviceInfo MidiOutput::getDefaultDevice() | |||||
{ | |||||
return getAvailableDevices().getFirst(); | |||||
} | |||||
MidiOutput* MidiOutput::openDevice (const String& deviceIdentifier) | |||||
{ | |||||
if (deviceIdentifier.isEmpty()) | |||||
return nullptr; | |||||
using namespace CoreMidiHelpers; | |||||
if (auto client = getGlobalMidiClient()) | |||||
{ | |||||
for (auto& endpoint : getEndpoints (false)) | |||||
{ | |||||
if (deviceIdentifier == getConnectedEndpointInfo (endpoint).identifier) | |||||
{ | { | ||||
MIDIPortRef port; | |||||
auto deviceName = CoreMidiHelpers::getConnectedEndpointName (endPoint); | |||||
ScopedCFString cfName; | |||||
if (CHECK_ERROR (MIDIOutputPortCreate (client, pname.cfString, &port))) | |||||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString))) | |||||
{ | { | ||||
mo = new MidiOutput (deviceName); | |||||
mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint); | |||||
MIDIPortRef port; | |||||
if (CHECK_ERROR (MIDIOutputPortCreate (client, cfName.cfString, &port))) | |||||
{ | |||||
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (String::fromCFString (cfName.cfString), deviceIdentifier)); | |||||
midiOutput->internal = new MidiPortAndEndpoint (port, endpoint); | |||||
return midiOutput.release(); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return mo; | |||||
return nullptr; | |||||
} | } | ||||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | ||||
{ | { | ||||
if (auto client = CoreMidiHelpers::getGlobalMidiClient()) | |||||
using namespace CoreMidiHelpers; | |||||
if (auto client = getGlobalMidiClient()) | |||||
{ | { | ||||
MIDIEndpointRef endPoint; | |||||
MIDIEndpointRef endpoint; | |||||
CoreMidiHelpers::ScopedCFString name; | |||||
name.cfString = deviceName.toCFString(); | |||||
ScopedCFString name (deviceName); | |||||
if (CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endPoint))) | |||||
if (CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endpoint))) | |||||
{ | { | ||||
CoreMidiHelpers::setUniqueIdForMidiPort (endPoint, deviceName, false); | |||||
auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, true); | |||||
if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) | |||||
{ | |||||
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, String (deviceIdentifier))); | |||||
midiOutput->internal = new MidiPortAndEndpoint (0, endpoint); | |||||
auto mo = new MidiOutput (deviceName); | |||||
mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint); | |||||
return mo; | |||||
return midiOutput.release(); | |||||
} | |||||
} | } | ||||
} | } | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
StringArray MidiOutput::getDevices() | |||||
{ | |||||
StringArray deviceNames; | |||||
for (auto& d : getAvailableDevices()) | |||||
deviceNames.add (d.name); | |||||
return deviceNames; | |||||
} | |||||
int MidiOutput::getDefaultDeviceIndex() | |||||
{ | |||||
return 0; | |||||
} | |||||
MidiOutput* MidiOutput::openDevice (int index) | |||||
{ | |||||
return openDevice (getAvailableDevices()[index].identifier); | |||||
} | |||||
MidiOutput::~MidiOutput() | MidiOutput::~MidiOutput() | ||||
{ | { | ||||
stopBackgroundThread(); | stopBackgroundThread(); | ||||
@@ -493,111 +695,6 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal)->send (packetToSend); | static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal)->send (packetToSend); | ||||
} | } | ||||
//============================================================================== | |||||
StringArray MidiInput::getDevices() { return CoreMidiHelpers::findDevices (true); } | |||||
int MidiInput::getDefaultDeviceIndex() { return 0; } | |||||
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||||
{ | |||||
jassert (callback != nullptr); | |||||
using namespace CoreMidiHelpers; | |||||
MidiInput* newInput = nullptr; | |||||
if (auto client = getGlobalMidiClient()) | |||||
{ | |||||
if (isPositiveAndBelow (index, MIDIGetNumberOfSources())) | |||||
{ | |||||
if (auto endPoint = MIDIGetSource ((ItemCount) index)) | |||||
{ | |||||
ScopedCFString name; | |||||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name.cfString))) | |||||
{ | |||||
MIDIPortRef port; | |||||
std::unique_ptr<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||||
if (CHECK_ERROR (MIDIInputPortCreate (client, name.cfString, midiInputProc, mpc.get(), &port))) | |||||
{ | |||||
if (CHECK_ERROR (MIDIPortConnectSource (port, endPoint, nullptr))) | |||||
{ | |||||
mpc->portAndEndpoint.reset (new MidiPortAndEndpoint (port, endPoint)); | |||||
newInput = new MidiInput (getDevices() [index]); | |||||
mpc->input = newInput; | |||||
newInput->internal = mpc.get(); | |||||
const ScopedLock sl (callbackLock); | |||||
activeCallbacks.add (mpc.release()); | |||||
} | |||||
else | |||||
{ | |||||
CHECK_ERROR (MIDIPortDispose (port)); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return newInput; | |||||
} | |||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||||
{ | |||||
jassert (callback != nullptr); | |||||
using namespace CoreMidiHelpers; | |||||
if (auto client = getGlobalMidiClient()) | |||||
{ | |||||
std::unique_ptr<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback)); | |||||
mpc->active = false; | |||||
MIDIEndpointRef endPoint; | |||||
ScopedCFString name; | |||||
name.cfString = deviceName.toCFString(); | |||||
if (CHECK_ERROR (MIDIDestinationCreate (client, name.cfString, midiInputProc, mpc.get(), &endPoint))) | |||||
{ | |||||
setUniqueIdForMidiPort (endPoint, deviceName, true); | |||||
mpc->portAndEndpoint.reset (new MidiPortAndEndpoint (0, endPoint)); | |||||
auto mi = new MidiInput (deviceName); | |||||
mpc->input = mi; | |||||
mi->internal = mpc.get(); | |||||
const ScopedLock sl (callbackLock); | |||||
activeCallbacks.add (mpc.release()); | |||||
return mi; | |||||
} | |||||
} | |||||
return nullptr; | |||||
} | |||||
MidiInput::MidiInput (const String& nm) : name (nm) | |||||
{ | |||||
} | |||||
MidiInput::~MidiInput() | |||||
{ | |||||
delete static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal); | |||||
} | |||||
void MidiInput::start() | |||||
{ | |||||
const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||||
static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = true; | |||||
} | |||||
void MidiInput::stop() | |||||
{ | |||||
const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||||
static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = false; | |||||
} | |||||
#undef CHECK_ERROR | #undef CHECK_ERROR | ||||
} // namespace juce | } // namespace juce |
@@ -20,6 +20,12 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#ifndef DRV_QUERYDEVICEINTERFACE | |||||
#define DRV_RESERVED 0x0800 | |||||
#define DRV_QUERYDEVICEINTERFACE (DRV_RESERVED + 12) | |||||
#define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13) | |||||
#endif | |||||
namespace juce | namespace juce | ||||
{ | { | ||||
@@ -29,7 +35,9 @@ struct MidiServiceType | |||||
{ | { | ||||
virtual ~InputWrapper() {} | virtual ~InputWrapper() {} | ||||
virtual String getDeviceIdentifier() = 0; | |||||
virtual String getDeviceName() = 0; | virtual String getDeviceName() = 0; | ||||
virtual void start() = 0; | virtual void start() = 0; | ||||
virtual void stop() = 0; | virtual void stop() = 0; | ||||
}; | }; | ||||
@@ -38,18 +46,20 @@ struct MidiServiceType | |||||
{ | { | ||||
virtual ~OutputWrapper() {} | virtual ~OutputWrapper() {} | ||||
virtual String getDeviceIdentifier() = 0; | |||||
virtual String getDeviceName() = 0; | virtual String getDeviceName() = 0; | ||||
virtual void sendMessageNow (const MidiMessage&) = 0; | virtual void sendMessageNow (const MidiMessage&) = 0; | ||||
}; | }; | ||||
MidiServiceType() {} | MidiServiceType() {} | ||||
virtual ~MidiServiceType() {} | virtual ~MidiServiceType() {} | ||||
virtual StringArray getDevices (bool) = 0; | |||||
virtual int getDefaultDeviceIndex (bool) = 0; | |||||
virtual Array<MidiDeviceInfo> getAvailableDevices (bool) = 0; | |||||
virtual MidiDeviceInfo getDefaultDevice (bool) = 0; | |||||
virtual InputWrapper* createInputWrapper (MidiInput&, int, MidiInputCallback&) = 0; | |||||
virtual OutputWrapper* createOutputWrapper (int) = 0; | |||||
virtual InputWrapper* createInputWrapper (MidiInput&, const String&, MidiInputCallback&) = 0; | |||||
virtual OutputWrapper* createOutputWrapper (const String&) = 0; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType) | ||||
}; | }; | ||||
@@ -60,26 +70,26 @@ struct Win32MidiService : public MidiServiceType, | |||||
{ | { | ||||
Win32MidiService() {} | Win32MidiService() {} | ||||
StringArray getDevices (bool isInput) override | |||||
Array<MidiDeviceInfo> getAvailableDevices (bool isInput) override | |||||
{ | { | ||||
return isInput ? Win32InputWrapper::getDevices() | |||||
: Win32OutputWrapper::getDevices(); | |||||
return isInput ? Win32InputWrapper::getAvailableDevices() | |||||
: Win32OutputWrapper::getAvailableDevices(); | |||||
} | } | ||||
int getDefaultDeviceIndex (bool isInput) override | |||||
MidiDeviceInfo getDefaultDevice (bool isInput) override | |||||
{ | { | ||||
return isInput ? Win32InputWrapper::getDefaultDeviceIndex() | |||||
: Win32OutputWrapper::getDefaultDeviceIndex(); | |||||
return isInput ? Win32InputWrapper::getDefaultDevice() | |||||
: Win32OutputWrapper::getDefaultDevice(); | |||||
} | } | ||||
InputWrapper* createInputWrapper (MidiInput& input, int index, MidiInputCallback& callback) override | |||||
InputWrapper* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override | |||||
{ | { | ||||
return new Win32InputWrapper (*this, input, index, callback); | |||||
return new Win32InputWrapper (*this, input, deviceIdentifier, callback); | |||||
} | } | ||||
OutputWrapper* createOutputWrapper (int index) override | |||||
OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override | |||||
{ | { | ||||
return new Win32OutputWrapper (*this, index); | |||||
return new Win32OutputWrapper (*this, deviceIdentifier); | |||||
} | } | ||||
private: | private: | ||||
@@ -88,7 +98,10 @@ private: | |||||
//============================================================================== | //============================================================================== | ||||
struct MidiInCollector : public ReferenceCountedObject | struct MidiInCollector : public ReferenceCountedObject | ||||
{ | { | ||||
MidiInCollector (Win32MidiService& s, const String& name) : deviceName (name), midiService (s) {} | |||||
MidiInCollector (Win32MidiService& s, MidiDeviceInfo d) | |||||
: deviceInfo (d), midiService (s) | |||||
{ | |||||
} | |||||
~MidiInCollector() | ~MidiInCollector() | ||||
{ | { | ||||
@@ -216,7 +229,7 @@ private: | |||||
} | } | ||||
} | } | ||||
String deviceName; | |||||
MidiDeviceInfo deviceInfo; | |||||
HMIDIIN deviceHandle = 0; | HMIDIIN deviceHandle = 0; | ||||
private: | private: | ||||
@@ -319,13 +332,59 @@ private: | |||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
struct Win32InputWrapper : public InputWrapper | |||||
template<class WrapperType> | |||||
struct Win32MidiDeviceQuery | |||||
{ | { | ||||
Win32InputWrapper (Win32MidiService& parentService, | |||||
MidiInput& midiInput, int index, MidiInputCallback& c) | |||||
static Array<MidiDeviceInfo> getAvailableDevices() | |||||
{ | |||||
StringArray deviceNames, deviceIDs; | |||||
auto deviceCaps = WrapperType::getDeviceCaps(); | |||||
for (int i = 0; i < deviceCaps.size(); ++i) | |||||
{ | |||||
deviceNames.add ({ deviceCaps[i].szPname, (size_t) numElementsInArray (deviceCaps[i].szPname) }); | |||||
deviceIDs.add (getInterfaceIDForDevice ((UINT) 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 : public InputWrapper, | |||||
public Win32MidiDeviceQuery<Win32InputWrapper> | |||||
{ | |||||
Win32InputWrapper (Win32MidiService& parentService, MidiInput& midiInput, const String& deviceIdentifier, MidiInputCallback& c) | |||||
: input (midiInput), callback (c) | : input (midiInput), callback (c) | ||||
{ | { | ||||
collector = getOrCreateCollector (parentService, index); | |||||
collector = getOrCreateCollector (parentService, deviceIdentifier); | |||||
collector->addClient (this); | collector->addClient (this); | ||||
} | } | ||||
@@ -334,25 +393,31 @@ private: | |||||
collector->removeClient (this); | collector->removeClient (this); | ||||
} | } | ||||
static MidiInCollector::Ptr getOrCreateCollector (Win32MidiService& parentService, int index) | |||||
static MidiInCollector::Ptr getOrCreateCollector (Win32MidiService& parentService, const String& deviceIdentifier) | |||||
{ | { | ||||
auto names = getDevices(); | |||||
UINT deviceID = MIDI_MAPPER; | UINT deviceID = MIDI_MAPPER; | ||||
String deviceName; | String deviceName; | ||||
auto devices = getAvailableDevices(); | |||||
if (isPositiveAndBelow (index, names.size())) | |||||
for (int i = 0; i < devices.size(); ++i) | |||||
{ | { | ||||
deviceName = names[index]; | |||||
deviceID = index; | |||||
auto d = devices.getUnchecked (i); | |||||
if (d.identifier == deviceIdentifier) | |||||
{ | |||||
deviceID = i; | |||||
deviceName = d.name; | |||||
break; | |||||
} | |||||
} | } | ||||
const ScopedLock sl (parentService.activeCollectorLock); | const ScopedLock sl (parentService.activeCollectorLock); | ||||
for (auto& c : parentService.activeCollectors) | for (auto& c : parentService.activeCollectors) | ||||
if (c->deviceName == deviceName) | |||||
if (c->deviceInfo.identifier == deviceIdentifier) | |||||
return c; | return c; | ||||
MidiInCollector::Ptr c (new MidiInCollector (parentService, deviceName)); | |||||
MidiInCollector::Ptr c (new MidiInCollector (parentService, { deviceName, deviceIdentifier })); | |||||
HMIDIIN h; | HMIDIIN h; | ||||
auto err = midiInOpen (&h, deviceID, | auto err = midiInOpen (&h, deviceID, | ||||
@@ -368,29 +433,33 @@ private: | |||||
return c; | return c; | ||||
} | } | ||||
static StringArray getDevices() | |||||
static DWORD sendMidiMessage (UINT_PTR deviceID, UINT msg, DWORD_PTR arg1, DWORD_PTR arg2) | |||||
{ | { | ||||
StringArray s; | |||||
auto num = midiInGetNumDevs(); | |||||
return midiInMessage ((HMIDIIN) deviceID, msg, arg1, arg2); | |||||
} | |||||
static Array<MIDIINCAPS> getDeviceCaps() | |||||
{ | |||||
Array<MIDIINCAPS> devices; | |||||
for (UINT i = 0; i < num; ++i) | |||||
for (UINT i = 0; i < midiInGetNumDevs(); ++i) | |||||
{ | { | ||||
MIDIINCAPS mc = { 0 }; | MIDIINCAPS mc = { 0 }; | ||||
if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) | ||||
s.add (String (mc.szPname, (size_t) numElementsInArray (mc.szPname))); | |||||
devices.add (mc); | |||||
} | } | ||||
s.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); | |||||
return s; | |||||
return devices; | |||||
} | } | ||||
static int getDefaultDeviceIndex() { return 0; } | |||||
static MidiDeviceInfo getDefaultDevice() { return getAvailableDevices().getFirst(); } | |||||
void start() override { started = true; concatenator.reset(); collector->startOrStop(); } | void start() override { started = true; concatenator.reset(); collector->startOrStop(); } | ||||
void stop() override { started = false; collector->startOrStop(); concatenator.reset(); } | void stop() override { started = false; collector->startOrStop(); concatenator.reset(); } | ||||
String getDeviceName() override { return collector->deviceName; } | |||||
String getDeviceIdentifier() override { return collector->deviceInfo.identifier; } | |||||
String getDeviceName() override { return collector->deviceInfo.name; } | |||||
void pushMidiData (const void* inputData, int numBytes, double time) | void pushMidiData (const void* inputData, int numBytes, double time) | ||||
{ | { | ||||
@@ -411,8 +480,8 @@ private: | |||||
{ | { | ||||
using Ptr = ReferenceCountedObjectPtr<MidiOutHandle>; | using Ptr = ReferenceCountedObjectPtr<MidiOutHandle>; | ||||
MidiOutHandle (Win32MidiService& parent, const String& name, HMIDIOUT h) | |||||
: owner (parent), deviceName (name), handle (h) | |||||
MidiOutHandle (Win32MidiService& parent, MidiDeviceInfo d, HMIDIOUT h) | |||||
: owner (parent), deviceInfo (d), handle (h) | |||||
{ | { | ||||
owner.activeOutputHandles.add (this); | owner.activeOutputHandles.add (this); | ||||
} | } | ||||
@@ -426,32 +495,41 @@ private: | |||||
} | } | ||||
Win32MidiService& owner; | Win32MidiService& owner; | ||||
String deviceName; | |||||
MidiDeviceInfo deviceInfo; | |||||
HMIDIOUT handle; | HMIDIOUT handle; | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutHandle) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutHandle) | ||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
struct Win32OutputWrapper : public OutputWrapper | |||||
struct Win32OutputWrapper : public OutputWrapper, | |||||
public Win32MidiDeviceQuery<Win32OutputWrapper> | |||||
{ | { | ||||
Win32OutputWrapper (Win32MidiService& p, int index) : parent (p) | |||||
Win32OutputWrapper (Win32MidiService& p, const String& deviceIdentifier) | |||||
: parent (p) | |||||
{ | { | ||||
auto names = getDevices(); | |||||
auto devices = getAvailableDevices(); | |||||
UINT deviceID = MIDI_MAPPER; | UINT deviceID = MIDI_MAPPER; | ||||
String deviceName; | |||||
if (isPositiveAndBelow (index, names.size())) | |||||
for (int i = 0; i < devices.size(); ++i) | |||||
{ | { | ||||
deviceName = names[index]; | |||||
deviceID = index; | |||||
auto d = devices.getUnchecked (i); | |||||
if (d.identifier == deviceIdentifier) | |||||
{ | |||||
deviceID = i; | |||||
deviceName = d.name; | |||||
break; | |||||
} | |||||
} | } | ||||
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 | // to be MIDI_MAPPER, or else device sharing breaks | ||||
for (int i = 0; i < names.size(); ++i) | |||||
if (names[i].containsIgnoreCase ("microsoft")) | |||||
for (int i = 0; i < devices.size(); ++i) | |||||
if (devices[i].name.containsIgnoreCase ("microsoft")) | |||||
deviceID = (UINT) i; | deviceID = (UINT) i; | ||||
} | } | ||||
@@ -459,7 +537,7 @@ private: | |||||
{ | { | ||||
auto* activeHandle = parent.activeOutputHandles.getUnchecked (i); | auto* activeHandle = parent.activeOutputHandles.getUnchecked (i); | ||||
if (activeHandle->deviceName == deviceName) | |||||
if (activeHandle->deviceInfo.identifier == deviceIdentifier) | |||||
{ | { | ||||
han = activeHandle; | han = activeHandle; | ||||
return; | return; | ||||
@@ -473,7 +551,7 @@ private: | |||||
if (res == MMSYSERR_NOERROR) | if (res == MMSYSERR_NOERROR) | ||||
{ | { | ||||
han = new MidiOutHandle (parent, deviceName, h); | |||||
han = new MidiOutHandle (parent, { deviceName, deviceIdentifier }, h); | |||||
return; | return; | ||||
} | } | ||||
@@ -530,12 +608,16 @@ private: | |||||
} | } | ||||
} | } | ||||
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() | static Array<MIDIOUTCAPS> getDeviceCaps() | ||||
{ | { | ||||
Array<MIDIOUTCAPS> devices; | Array<MIDIOUTCAPS> devices; | ||||
auto num = midiOutGetNumDevs(); | |||||
for (UINT i = 0; i < num; ++i) | |||||
for (UINT i = 0; i < midiOutGetNumDevs(); ++i) | |||||
{ | { | ||||
MIDIOUTCAPS mc = { 0 }; | MIDIOUTCAPS mc = { 0 }; | ||||
@@ -546,36 +628,26 @@ private: | |||||
return devices; | return devices; | ||||
} | } | ||||
static StringArray getDevices() | |||||
static MidiDeviceInfo getDefaultDevice() | |||||
{ | { | ||||
StringArray s; | |||||
for (auto& mc : getDeviceCaps()) | |||||
s.add (String (mc.szPname, (size_t) numElementsInArray (mc.szPname))); | |||||
s.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); | |||||
return s; | |||||
} | |||||
static int getDefaultDeviceIndex() | |||||
{ | |||||
int n = 0; | |||||
for (auto& mc : getDeviceCaps()) | |||||
auto defaultIndex = []() | |||||
{ | { | ||||
if ((mc.wTechnology & MOD_MAPPER) != 0) | |||||
return n; | |||||
auto deviceCaps = getDeviceCaps(); | |||||
++n; | |||||
} | |||||
for (int i = 0; i < deviceCaps.size(); ++i) | |||||
if ((deviceCaps[i].wTechnology & MOD_MAPPER) != 0) | |||||
return i; | |||||
return 0; | |||||
return 0; | |||||
}(); | |||||
return getAvailableDevices()[defaultIndex]; | |||||
} | } | ||||
String getDeviceName() override { return deviceName; } | |||||
String getDeviceIdentifier() override { return han->deviceInfo.identifier; } | |||||
String getDeviceName() override { return han->deviceInfo.name; } | |||||
Win32MidiService& parent; | Win32MidiService& parent; | ||||
String deviceName; | |||||
MidiOutHandle::Ptr han; | MidiOutHandle::Ptr han; | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32OutputWrapper) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32OutputWrapper) | ||||
@@ -671,26 +743,26 @@ public: | |||||
throw std::runtime_error ("Failed to start the midi output device watcher"); | throw std::runtime_error ("Failed to start the midi output device watcher"); | ||||
} | } | ||||
StringArray getDevices (bool isInput) override | |||||
Array<MidiDeviceInfo> getAvailableDevices (bool isInput) override | |||||
{ | { | ||||
return isInput ? inputDeviceWatcher ->getDevices() | |||||
: outputDeviceWatcher->getDevices(); | |||||
return isInput ? inputDeviceWatcher ->getAvailableDevices() | |||||
: outputDeviceWatcher->getAvailableDevices(); | |||||
} | } | ||||
int getDefaultDeviceIndex (bool isInput) override | |||||
MidiDeviceInfo getDefaultDevice (bool isInput) override | |||||
{ | { | ||||
return isInput ? inputDeviceWatcher ->getDefaultDeviceIndex() | |||||
: outputDeviceWatcher->getDefaultDeviceIndex(); | |||||
return isInput ? inputDeviceWatcher ->getDefaultDevice() | |||||
: outputDeviceWatcher->getDefaultDevice(); | |||||
} | } | ||||
InputWrapper* createInputWrapper (MidiInput& input, int index, MidiInputCallback& callback) override | |||||
InputWrapper* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override | |||||
{ | { | ||||
return new WinRTInputWrapper (*this, input, index, callback); | |||||
return new WinRTInputWrapper (*this, input, deviceIdentifier, callback); | |||||
} | } | ||||
OutputWrapper* createOutputWrapper (int index) override | |||||
OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override | |||||
{ | { | ||||
return new WinRTOutputWrapper (*this, index); | |||||
return new WinRTOutputWrapper (*this, deviceIdentifier); | |||||
} | } | ||||
private: | private: | ||||
@@ -1098,7 +1170,7 @@ private: | |||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
struct MIDIDeviceInfo | |||||
struct WinRTMIDIDeviceInfo | |||||
{ | { | ||||
String deviceID, containerID, name; | String deviceID, containerID, name; | ||||
bool isDefault = false; | bool isDefault = false; | ||||
@@ -1120,7 +1192,7 @@ private: | |||||
HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override | HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override | ||||
{ | { | ||||
MIDIDeviceInfo info; | |||||
WinRTMIDIDeviceInfo info; | |||||
HSTRING deviceID; | HSTRING deviceID; | ||||
auto hr = addedDeviceInfo->get_Id (&deviceID); | auto hr = addedDeviceInfo->get_Id (&deviceID); | ||||
@@ -1229,56 +1301,59 @@ private: | |||||
return attach (deviceSelector, DeviceInformationKind::DeviceInformationKind_DeviceInterface); | return attach (deviceSelector, DeviceInformationKind::DeviceInformationKind_DeviceInterface); | ||||
} | } | ||||
StringArray getDevices() | |||||
Array<MidiDeviceInfo> getAvailableDevices() | |||||
{ | { | ||||
{ | { | ||||
const ScopedLock lock (deviceChanges); | const ScopedLock lock (deviceChanges); | ||||
lastQueriedConnectedDevices = connectedDevices; | lastQueriedConnectedDevices = connectedDevices; | ||||
} | } | ||||
StringArray result; | |||||
StringArray deviceNames, deviceIDs; | |||||
for (auto info : lastQueriedConnectedDevices.get()) | for (auto info : lastQueriedConnectedDevices.get()) | ||||
result.add (info.name); | |||||
{ | |||||
deviceNames.add (info.name); | |||||
deviceIDs .add (info.containerID); | |||||
} | |||||
return result; | |||||
} | |||||
deviceNames.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); | |||||
deviceIDs .appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); | |||||
int getDefaultDeviceIndex() | |||||
{ | |||||
auto& lastDevices = lastQueriedConnectedDevices.get(); | |||||
Array<MidiDeviceInfo> devices; | |||||
for (int i = 0; i < lastDevices.size(); ++i) | |||||
if (lastDevices[i].isDefault) | |||||
return i; | |||||
for (int i = 0; i < deviceNames.size(); ++i) | |||||
devices.add ({ deviceNames[i], deviceIDs[i] }); | |||||
return 0; | |||||
return devices; | |||||
} | } | ||||
MIDIDeviceInfo getDeviceInfoFromIndex (int index) | |||||
MidiDeviceInfo getDefaultDevice() | |||||
{ | { | ||||
if (isPositiveAndBelow (index, lastQueriedConnectedDevices.get().size())) | |||||
return lastQueriedConnectedDevices.get()[index]; | |||||
auto& lastDevices = lastQueriedConnectedDevices.get(); | |||||
for (auto& d : lastDevices) | |||||
if (d.isDefault) | |||||
return { d.name, d.containerID }; | |||||
return {}; | return {}; | ||||
} | } | ||||
String getDeviceID (const String& name) | |||||
WinRTMIDIDeviceInfo getWinRTDeviceInfoForDevice (const String& deviceIdentifier) | |||||
{ | { | ||||
const ScopedLock lock (deviceChanges); | |||||
auto devices = getAvailableDevices(); | |||||
for (auto info : connectedDevices) | |||||
if (info.name == name) | |||||
return info.deviceID; | |||||
for (int i = 0; i < devices.size(); ++i) | |||||
if (devices.getUnchecked (i).identifier == deviceIdentifier) | |||||
return lastQueriedConnectedDevices.get()[i]; | |||||
return {}; | return {}; | ||||
} | } | ||||
WinRTWrapper::ComPtr<COMFactoryType>& factory; | WinRTWrapper::ComPtr<COMFactoryType>& factory; | ||||
Array<MIDIDeviceInfo> connectedDevices; | |||||
Array<WinRTMIDIDeviceInfo> connectedDevices; | |||||
CriticalSection deviceChanges; | CriticalSection deviceChanges; | ||||
ThreadLocalValue<Array<MIDIDeviceInfo>> lastQueriedConnectedDevices; | |||||
ThreadLocalValue<Array<WinRTMIDIDeviceInfo>> lastQueriedConnectedDevices; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher); | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher); | ||||
}; | }; | ||||
@@ -1345,12 +1420,12 @@ private: | |||||
public: | public: | ||||
WinRTIOWrapper (BLEDeviceWatcher& bleWatcher, | WinRTIOWrapper (BLEDeviceWatcher& bleWatcher, | ||||
MidiIODeviceWatcher<MIDIIOStaticsType>& midiDeviceWatcher, | MidiIODeviceWatcher<MIDIIOStaticsType>& midiDeviceWatcher, | ||||
int index) | |||||
const String& deviceIdentifier) | |||||
: bleDeviceWatcher (bleWatcher) | : bleDeviceWatcher (bleWatcher) | ||||
{ | { | ||||
{ | { | ||||
const ScopedLock lock (midiDeviceWatcher.deviceChanges); | const ScopedLock lock (midiDeviceWatcher.deviceChanges); | ||||
deviceInfo = midiDeviceWatcher.getDeviceInfoFromIndex (index); | |||||
deviceInfo = midiDeviceWatcher.getWinRTDeviceInfoForDevice (deviceIdentifier); | |||||
} | } | ||||
if (deviceInfo.deviceID.isEmpty()) | if (deviceInfo.deviceID.isEmpty()) | ||||
@@ -1417,7 +1492,7 @@ private: | |||||
protected: | protected: | ||||
//============================================================================== | //============================================================================== | ||||
BLEDeviceWatcher& bleDeviceWatcher; | BLEDeviceWatcher& bleDeviceWatcher; | ||||
MIDIDeviceInfo deviceInfo; | |||||
WinRTMIDIDeviceInfo deviceInfo; | |||||
bool isBLEDevice = false; | bool isBLEDevice = false; | ||||
WinRTWrapper::ComPtr<MIDIPort> midiPort; | WinRTWrapper::ComPtr<MIDIPort> midiPort; | ||||
}; | }; | ||||
@@ -1427,8 +1502,8 @@ private: | |||||
private WinRTIOWrapper<IMidiInPortStatics, IMidiInPort> | private WinRTIOWrapper<IMidiInPortStatics, IMidiInPort> | ||||
{ | { | ||||
WinRTInputWrapper (WinRTMidiService& service, MidiInput& input, int index, MidiInputCallback& cb) | |||||
: WinRTIOWrapper <IMidiInPortStatics, IMidiInPort> (*service.bleDeviceWatcher, *service.inputDeviceWatcher, index), | |||||
WinRTInputWrapper (WinRTMidiService& service, MidiInput& input, const String& deviceIdentifier, MidiInputCallback& cb) | |||||
: WinRTIOWrapper <IMidiInPortStatics, IMidiInPort> (*service.bleDeviceWatcher, *service.inputDeviceWatcher, deviceIdentifier), | |||||
inputDevice (input), | inputDevice (input), | ||||
callback (cb) | callback (cb) | ||||
{ | { | ||||
@@ -1484,7 +1559,8 @@ private: | |||||
} | } | ||||
} | } | ||||
String getDeviceName() override { return deviceInfo.name; } | |||||
String getDeviceIdentifier() override { return deviceInfo.containerID; } | |||||
String getDeviceName() override { return deviceInfo.name; } | |||||
//============================================================================== | //============================================================================== | ||||
void disconnect() override | void disconnect() override | ||||
@@ -1579,8 +1655,8 @@ private: | |||||
struct WinRTOutputWrapper final : public OutputWrapper, | struct WinRTOutputWrapper final : public OutputWrapper, | ||||
private WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort> | private WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort> | ||||
{ | { | ||||
WinRTOutputWrapper (WinRTMidiService& service, int index) | |||||
: WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort> (*service.bleDeviceWatcher, *service.outputDeviceWatcher, index) | |||||
WinRTOutputWrapper (WinRTMidiService& service, const String& deviceIdentifier) | |||||
: WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort> (*service.bleDeviceWatcher, *service.outputDeviceWatcher, deviceIdentifier) | |||||
{ | { | ||||
OpenMidiPortThread<IMidiOutPortStatics, IMidiOutPort, IMidiOutPort> portThread ("Open WinRT MIDI output port", | OpenMidiPortThread<IMidiOutPortStatics, IMidiOutPort, IMidiOutPort> portThread ("Open WinRT MIDI output port", | ||||
deviceInfo.deviceID, | deviceInfo.deviceID, | ||||
@@ -1632,7 +1708,8 @@ private: | |||||
midiPort->SendBuffer (buffer); | midiPort->SendBuffer (buffer); | ||||
} | } | ||||
String getDeviceName() override { return deviceInfo.name; } | |||||
String getDeviceIdentifier() override { return deviceInfo.containerID; } | |||||
String getDeviceName() override { return deviceInfo.name; } | |||||
//============================================================================== | //============================================================================== | ||||
WinRTWrapper::ComPtr<IBuffer> buffer; | WinRTWrapper::ComPtr<IBuffer> buffer; | ||||
@@ -1718,42 +1795,74 @@ private: | |||||
JUCE_IMPLEMENT_SINGLETON (MidiService) | JUCE_IMPLEMENT_SINGLETON (MidiService) | ||||
//============================================================================== | //============================================================================== | ||||
StringArray MidiInput::getDevices() | |||||
static int findDefaultDeviceIndex (const Array<MidiDeviceInfo>& available, const MidiDeviceInfo& defaultDevice) | |||||
{ | { | ||||
return MidiService::getService().getDevices (true); | |||||
for (int i = 0; i < available.size(); ++i) | |||||
if (available.getUnchecked (i) == defaultDevice) | |||||
return i; | |||||
return 0; | |||||
} | } | ||||
int MidiInput::getDefaultDeviceIndex() | |||||
Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | |||||
{ | { | ||||
return MidiService::getService().getDefaultDeviceIndex (true); | |||||
return MidiService::getService().getAvailableDevices (true); | |||||
} | } | ||||
MidiInput::MidiInput (const String& deviceName) : name (deviceName) | |||||
MidiDeviceInfo MidiInput::getDefaultDevice() | |||||
{ | { | ||||
return MidiService::getService().getDefaultDevice (true); | |||||
} | } | ||||
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) | |||||
MidiInput* MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) | |||||
{ | { | ||||
if (callback == nullptr) | |||||
if (deviceIdentifier.isEmpty() || callback == nullptr) | |||||
return nullptr; | return nullptr; | ||||
std::unique_ptr<MidiInput> in (new MidiInput (String())); | |||||
MidiInput input ({}, {}); | |||||
std::unique_ptr<MidiServiceType::InputWrapper> wrapper; | std::unique_ptr<MidiServiceType::InputWrapper> wrapper; | ||||
try | try | ||||
{ | { | ||||
wrapper.reset (MidiService::getService().createInputWrapper (*in, index, *callback)); | |||||
wrapper.reset (MidiService::getService().createInputWrapper (input, deviceIdentifier, *callback)); | |||||
} | } | ||||
catch (std::runtime_error&) | catch (std::runtime_error&) | ||||
{ | { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
in->setName (wrapper->getDeviceName()); | |||||
std::unique_ptr<MidiInput> in; | |||||
in.reset (new MidiInput (wrapper->getDeviceName(), deviceIdentifier)); | |||||
in->internal = wrapper.release(); | in->internal = wrapper.release(); | ||||
return in.release(); | return in.release(); | ||||
} | } | ||||
StringArray MidiInput::getDevices() | |||||
{ | |||||
StringArray deviceNames; | |||||
for (auto& d : getAvailableDevices()) | |||||
deviceNames.add (d.name); | |||||
return deviceNames; | |||||
} | |||||
int MidiInput::getDefaultDeviceIndex() | |||||
{ | |||||
return findDefaultDeviceIndex (getAvailableDevices(), getDefaultDevice()); | |||||
} | |||||
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() | MidiInput::~MidiInput() | ||||
{ | { | ||||
delete static_cast<MidiServiceType::InputWrapper*> (internal); | delete static_cast<MidiServiceType::InputWrapper*> (internal); | ||||
@@ -1763,34 +1872,60 @@ void MidiInput::start() { static_cast<MidiServiceType::InputWrapper*> (interna | |||||
void MidiInput::stop() { static_cast<MidiServiceType::InputWrapper*> (internal)->stop(); } | void MidiInput::stop() { static_cast<MidiServiceType::InputWrapper*> (internal)->stop(); } | ||||
//============================================================================== | //============================================================================== | ||||
StringArray MidiOutput::getDevices() | |||||
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | |||||
{ | { | ||||
return MidiService::getService().getDevices (false); | |||||
return MidiService::getService().getAvailableDevices (false); | |||||
} | } | ||||
int MidiOutput::getDefaultDeviceIndex() | |||||
MidiDeviceInfo MidiOutput::getDefaultDevice() | |||||
{ | { | ||||
return MidiService::getService().getDefaultDeviceIndex (false); | |||||
return MidiService::getService().getDefaultDevice (false); | |||||
} | } | ||||
MidiOutput* MidiOutput::openDevice (int index) | |||||
MidiOutput* MidiOutput::openDevice (const String& deviceIdentifier) | |||||
{ | { | ||||
if (deviceIdentifier.isEmpty()) | |||||
return nullptr; | |||||
std::unique_ptr<MidiServiceType::OutputWrapper> wrapper; | std::unique_ptr<MidiServiceType::OutputWrapper> wrapper; | ||||
try | try | ||||
{ | { | ||||
wrapper.reset (MidiService::getService().createOutputWrapper (index)); | |||||
wrapper.reset (MidiService::getService().createOutputWrapper (deviceIdentifier)); | |||||
} | } | ||||
catch (std::runtime_error&) | catch (std::runtime_error&) | ||||
{ | { | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
std::unique_ptr<MidiOutput> out (new MidiOutput (wrapper->getDeviceName())); | |||||
std::unique_ptr<MidiOutput> out; | |||||
out.reset (new MidiOutput (wrapper->getDeviceName(), deviceIdentifier)); | |||||
out->internal = wrapper.release(); | out->internal = wrapper.release(); | ||||
return out.release(); | return out.release(); | ||||
} | } | ||||
StringArray MidiOutput::getDevices() | |||||
{ | |||||
StringArray deviceNames; | |||||
for (auto& d : getAvailableDevices()) | |||||
deviceNames.add (d.name); | |||||
return deviceNames; | |||||
} | |||||
int MidiOutput::getDefaultDeviceIndex() | |||||
{ | |||||
return findDefaultDeviceIndex (getAvailableDevices(), getDefaultDevice()); | |||||
} | |||||
MidiOutput* MidiOutput::openDevice (int index) | |||||
{ | |||||
return openDevice (getAvailableDevices()[index].identifier); | |||||
} | |||||
MidiOutput::~MidiOutput() | MidiOutput::~MidiOutput() | ||||
{ | { | ||||
stopBackgroundThread(); | stopBackgroundThread(); | ||||