mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Added support for ID-based MIDI devices
This commit is contained in:
parent
6eab395204
commit
09ebd1d257
14 changed files with 1921 additions and 1451 deletions
|
|
@ -52,9 +52,9 @@
|
|||
//==============================================================================
|
||||
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<MidiOutput> outDevice;
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ public:
|
|||
if (isInput)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
@ -200,7 +200,7 @@ public:
|
|||
else
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
@ -278,14 +278,14 @@ private:
|
|||
if (isInput)
|
||||
{
|
||||
if (rowNumber < parent.getNumMidiInputs())
|
||||
g.drawText (parent.getMidiDevice (rowNumber, true)->name,
|
||||
g.drawText (parent.getMidiDevice (rowNumber, true)->deviceInfo.name,
|
||||
5, 0, width, height,
|
||||
Justification::centredLeft, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rowNumber < parent.getNumMidiOutputs())
|
||||
g.drawText (parent.getMidiDevice (rowNumber, false)->name,
|
||||
g.drawText (parent.getMidiDevice (rowNumber, false)->deviceInfo.name,
|
||||
5, 0, width, height,
|
||||
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
|
||||
: midiOutputs;
|
||||
|
||||
if (deviceNames.size() != midiDevices.size())
|
||||
if (availableDevices.size() != midiDevices.size())
|
||||
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 false;
|
||||
}
|
||||
|
||||
ReferenceCountedObjectPtr<MidiDeviceListEntry> findDeviceWithName (const String& name, bool isInputDevice) const
|
||||
ReferenceCountedObjectPtr<MidiDeviceListEntry> findDevice (MidiDeviceInfo device, bool isInputDevice) const
|
||||
{
|
||||
const ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs
|
||||
: midiOutputs;
|
||||
|
||||
for (auto midiDevice : midiDevices)
|
||||
if (midiDevice->name == name)
|
||||
return midiDevice;
|
||||
for (auto& d : midiDevices)
|
||||
if (d->deviceInfo == device)
|
||||
return d;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void closeUnpluggedDevices (StringArray& currentlyPluggedInDevices, bool isInputDevice)
|
||||
void closeUnpluggedDevices (const Array<MidiDeviceInfo>& currentlyPluggedInDevices, bool isInputDevice)
|
||||
{
|
||||
ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs
|
||||
: midiOutputs;
|
||||
|
|
@ -404,7 +404,7 @@ private:
|
|||
{
|
||||
auto& d = *midiDevices[i];
|
||||
|
||||
if (! currentlyPluggedInDevices.contains (d.name))
|
||||
if (! currentlyPluggedInDevices.contains (d.deviceInfo))
|
||||
{
|
||||
if (isInputDevice ? d.inDevice .get() != nullptr
|
||||
: d.outDevice.get() != nullptr)
|
||||
|
|
@ -417,26 +417,26 @@ private:
|
|||
|
||||
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
|
||||
= isInputDeviceList ? midiInputs : midiOutputs;
|
||||
|
||||
closeUnpluggedDevices (newDeviceNames, isInputDeviceList);
|
||||
closeUnpluggedDevices (availableDevices, isInputDeviceList);
|
||||
|
||||
ReferenceCountedArray<MidiDeviceListEntry> newDeviceList;
|
||||
|
||||
// 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)
|
||||
entry = new MidiDeviceListEntry (newDeviceName);
|
||||
entry = new MidiDeviceListEntry (newDevice);
|
||||
|
||||
newDeviceList.add (entry);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ class ConsoleUnitTestRunner : public UnitTestRunner
|
|||
//==============================================================================
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
// Needed for tests that require a message thread
|
||||
ScopedJuceInitialiser_GUI guiInitialiser;
|
||||
|
||||
ConsoleLogger logger;
|
||||
Logger::setCurrentLogger (&logger);
|
||||
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@
|
|||
#include "audio_io/juce_AudioIODevice.cpp"
|
||||
#include "audio_io/juce_AudioIODeviceType.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_AudioTransportSource.cpp"
|
||||
#include "native/juce_MidiDataConcatenator.h"
|
||||
|
|
|
|||
|
|
@ -171,9 +171,8 @@
|
|||
#endif
|
||||
|
||||
//==============================================================================
|
||||
#include "midi_io/juce_MidiInput.h"
|
||||
#include "midi_io/juce_MidiDevices.h"
|
||||
#include "midi_io/juce_MidiMessageCollector.h"
|
||||
#include "midi_io/juce_MidiOutput.h"
|
||||
#include "audio_io/juce_AudioIODevice.h"
|
||||
#include "audio_io/juce_AudioIODeviceType.h"
|
||||
#include "audio_io/juce_SystemAudioVolume.h"
|
||||
|
|
|
|||
|
|
@ -23,18 +23,8 @@
|
|||
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())
|
||||
{
|
||||
uint32 now = Time::getMillisecondCounter();
|
||||
auto now = Time::getMillisecondCounter();
|
||||
uint32 eventTime = 0;
|
||||
uint32 timeToWait = 500;
|
||||
|
||||
|
|
@ -167,4 +157,106 @@ void MidiOutput::run()
|
|||
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
|
||||
365
modules/juce_audio_devices/midi_io/juce_MidiDevices.h
Normal file
365
modules/juce_audio_devices/midi_io/juce_MidiDevices.h
Normal file
|
|
@ -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
|
||||
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;
|
||||
MidiOutputPort androidPort;
|
||||
MidiPortPath portPath;
|
||||
|
|
@ -331,6 +339,12 @@ public class JuceMidiSupport
|
|||
androidPort = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName ()
|
||||
{
|
||||
return owner.getPortName (portPath);
|
||||
}
|
||||
|
||||
MidiDeviceManager owner;
|
||||
MidiInputPort androidPort;
|
||||
MidiPortPath portPath;
|
||||
|
|
@ -343,7 +357,6 @@ public class JuceMidiSupport
|
|||
deviceId = deviceIdToUse;
|
||||
isInput = direction;
|
||||
portIndex = androidIndex;
|
||||
|
||||
}
|
||||
|
||||
public int deviceId;
|
||||
|
|
@ -555,17 +568,17 @@ public class JuceMidiSupport
|
|||
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
|
||||
synchronized (MidiDeviceManager.class)
|
||||
|
|
@ -573,22 +586,24 @@ public class JuceMidiSupport
|
|||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
@ -633,14 +648,14 @@ public class JuceMidiSupport
|
|||
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 */
|
||||
|
|
@ -773,24 +788,6 @@ public class JuceMidiSupport
|
|||
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)
|
||||
{
|
||||
// only add standard midi devices
|
||||
|
|
@ -980,24 +977,24 @@ public class JuceMidiSupport
|
|||
return "";
|
||||
}
|
||||
|
||||
public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex)
|
||||
public ArrayList<MidiPortPath> getAllPorts (int portType)
|
||||
{
|
||||
int portIdx = 0;
|
||||
for (MidiDeviceInfo info : deviceInfos)
|
||||
{
|
||||
for (MidiDeviceInfo.PortInfo portInfo : info.getPorts ())
|
||||
{
|
||||
if (portInfo.getType () == portType)
|
||||
{
|
||||
if (portIdx == juceIndex)
|
||||
return new MidiPortPath (info.getId (),
|
||||
(portType == MidiDeviceInfo.PortInfo.TYPE_INPUT),
|
||||
portInfo.getPortNumber ());
|
||||
ArrayList<MidiPortPath> ports = new ArrayList<MidiPortPath> ();
|
||||
|
||||
portIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (MidiDeviceInfo info : deviceInfos)
|
||||
for (MidiDeviceInfo.PortInfo portInfo : info.getPorts ())
|
||||
if (portInfo.getType () == portType)
|
||||
ports.add (new MidiPortPath (info.getId (), (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT),
|
||||
portInfo.getPortNumber ()));
|
||||
|
||||
return ports;
|
||||
}
|
||||
|
||||
public MidiPortPath getPortPathForID (int portType, int deviceID)
|
||||
{
|
||||
for (MidiPortPath port : getAllPorts (portType))
|
||||
if (port.hashCode () == deviceID)
|
||||
return port;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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())
|
||||
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
|
||||
static void findMidiDevices (StringArray& devices, bool input, int cardNum)
|
||||
static void findMidiDevices (Array<MidiDeviceInfo>& devices, bool input, int cardNum)
|
||||
{
|
||||
snd_ctl_t* ctl = nullptr;
|
||||
auto status = snd_ctl_open (&ctl, ("hw:" + String (cardNum)).toRawUTF8(), 0);
|
||||
|
|
@ -131,9 +131,10 @@ private:
|
|||
status = snd_ctl_rawmidi_info (ctl, info);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
// 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& nm) : name (nm) {}
|
||||
|
||||
MidiInput::~MidiInput()
|
||||
MidiInput::MidiInput (const String& deviceName, const String& deviceID)
|
||||
: deviceInfo (deviceName, deviceID)
|
||||
{
|
||||
delete static_cast<BelaMidiInput*> (internal);
|
||||
}
|
||||
|
||||
void MidiInput::start() { static_cast<BelaMidiInput*> (internal)->start(); }
|
||||
void MidiInput::stop() { static_cast<BelaMidiInput*> (internal)->stop(); }
|
||||
MidiInput::~MidiInput() { delete static_cast<BelaMidiInput*> (internal); }
|
||||
void MidiInput::start() { static_cast<BelaMidiInput*> (internal)->start(); }
|
||||
void MidiInput::stop() { static_cast<BelaMidiInput*> (internal)->stop(); }
|
||||
|
||||
void Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
|
||||
{
|
||||
return BelaMidiInput::getDevices (true);
|
||||
}
|
||||
|
||||
MidiDeviceInfo MidiInput::getDefaultDevice()
|
||||
{
|
||||
return getAvailableDevices().getFirst();
|
||||
}
|
||||
|
||||
MidiInput* MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
|
||||
{
|
||||
if (deviceIdentifier.isEmpty())
|
||||
return nullptr;
|
||||
|
||||
std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceIdentifier, deviceIdentifier));
|
||||
midiInput->internal = new BelaMidiInput (deviceIdentifier, result, callback);
|
||||
|
||||
return midiInput.release();
|
||||
}
|
||||
|
||||
MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*)
|
||||
{
|
||||
// N/A on Bela
|
||||
jassertfalse;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
StringArray MidiInput::getDevices()
|
||||
{
|
||||
StringArray deviceNames;
|
||||
|
||||
for (auto& d : getAvailableDevices())
|
||||
deviceNames.add (d.name);
|
||||
|
||||
return deviceNames;
|
||||
}
|
||||
|
||||
int MidiInput::getDefaultDeviceIndex()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
StringArray MidiInput::getDevices()
|
||||
{
|
||||
return BelaMidiInput::getDevices (true);
|
||||
}
|
||||
|
||||
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
|
||||
{
|
||||
auto devices = getDevices();
|
||||
|
||||
if (index >= 0 && index < devices.size())
|
||||
{
|
||||
auto deviceName = devices[index];
|
||||
auto result = new MidiInput (deviceName);
|
||||
result->internal = new BelaMidiInput (deviceName, result, callback);
|
||||
return result;
|
||||
}
|
||||
|
||||
return {};
|
||||
return openDevice (getAvailableDevices()[index].identifier, callback);
|
||||
}
|
||||
|
||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
|
||||
{
|
||||
jassertfalse; // N/A on Bela
|
||||
return {};
|
||||
}
|
||||
//==============================================================================
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -67,12 +67,12 @@ public:
|
|||
static String getAlsaMidiName()
|
||||
{
|
||||
#ifdef JUCE_ALSA_MIDI_NAME
|
||||
return JUCE_ALSA_MIDI_NAME;
|
||||
return JUCE_ALSA_MIDI_NAME;
|
||||
#else
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
return app->getApplicationName();
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
return app->getApplicationName();
|
||||
|
||||
return "JUCE";
|
||||
return "JUCE";
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +198,8 @@ public:
|
|||
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));
|
||||
|
||||
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_APPLICATION);
|
||||
}
|
||||
|
|
@ -215,13 +216,15 @@ public:
|
|||
}
|
||||
|
||||
AlsaClient& client;
|
||||
|
||||
MidiInputCallback* callback = nullptr;
|
||||
snd_midi_event_t* midiParser = 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()
|
||||
|
|
@ -359,11 +362,16 @@ private:
|
|||
AlsaClient* AlsaClient::instance = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
static String getFormattedPortIdentifier (int clientId, int portId)
|
||||
{
|
||||
return String (clientId) + "-" + String (portId);
|
||||
}
|
||||
|
||||
static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client,
|
||||
snd_seq_client_info_t* clientInfo,
|
||||
bool forInput,
|
||||
StringArray& deviceNamesFound,
|
||||
int deviceIndexToOpen)
|
||||
Array<MidiDeviceInfo>& devices,
|
||||
const String& deviceIdentifierToOpen)
|
||||
{
|
||||
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_alloca (&portInfo);
|
||||
jassert (portInfo);
|
||||
jassert (portInfo != nullptr);
|
||||
auto numPorts = snd_seq_client_info_get_num_ports (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)
|
||||
& (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);
|
||||
jassert (port->isValid());
|
||||
port->connectWith (sourceClient, sourcePort);
|
||||
port->connectWith (sourceClient, portID);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -407,8 +415,8 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client,
|
|||
}
|
||||
|
||||
static AlsaClient::Port* iterateMidiDevices (bool forInput,
|
||||
StringArray& deviceNamesFound,
|
||||
int deviceIndexToOpen)
|
||||
Array<MidiDeviceInfo>& devices,
|
||||
const String& deviceIdentifierToOpen)
|
||||
{
|
||||
AlsaClient::Port* port = nullptr;
|
||||
auto client = AlsaClient::getInstance();
|
||||
|
|
@ -432,85 +440,95 @@ static AlsaClient::Port* iterateMidiDevices (bool forInput,
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
StringArray MidiOutput::getDevices()
|
||||
//==============================================================================
|
||||
Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
|
||||
{
|
||||
StringArray devices;
|
||||
iterateMidiDevices (false, devices, -1);
|
||||
Array<MidiDeviceInfo> devices;
|
||||
iterateMidiDevices (true, 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)
|
||||
return nullptr;
|
||||
|
||||
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* 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());
|
||||
|
||||
return newDevice;
|
||||
std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId)));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int MidiInput::getDefaultDeviceIndex()
|
||||
//==============================================================================
|
||||
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
Array<MidiDeviceInfo> devices;
|
||||
iterateMidiDevices (false, devices, {});
|
||||
|
||||
StringArray MidiInput::getDevices()
|
||||
{
|
||||
StringArray devices;
|
||||
iterateMidiDevices (true, devices, -1);
|
||||
return devices;
|
||||
}
|
||||
|
||||
MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback)
|
||||
MidiDeviceInfo MidiOutput::getDefaultDevice()
|
||||
{
|
||||
StringArray devices;
|
||||
auto* port = iterateMidiDevices (true, devices, deviceIndex);
|
||||
return getAvailableDevices().getFirst();
|
||||
}
|
||||
|
||||
MidiOutput* MidiOutput::openDevice (const String& deviceIdentifier)
|
||||
{
|
||||
if (deviceIdentifier.isEmpty())
|
||||
return nullptr;
|
||||
|
||||
Array<MidiDeviceInfo> devices;
|
||||
auto* port = iterateMidiDevices (false, devices, deviceIdentifier);
|
||||
|
||||
if (port == nullptr)
|
||||
return nullptr;
|
||||
|
||||
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* port = client->createPort (deviceName, true, true);
|
||||
auto* port = client->createPort (deviceName, false, true);
|
||||
|
||||
jassert (port->isValid());
|
||||
jassert (port != nullptr && port->isValid());
|
||||
|
||||
auto newDevice = new MidiInput (deviceName);
|
||||
port->setupInput (newDevice, callback);
|
||||
newDevice->internal = port;
|
||||
return newDevice;
|
||||
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId)));
|
||||
|
||||
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
|
||||
|
||||
// (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; }
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,22 @@ namespace juce
|
|||
|
||||
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)
|
||||
{
|
||||
if (err == noErr)
|
||||
|
|
@ -45,30 +61,163 @@ namespace CoreMidiHelpers
|
|||
#undef CHECK_ERROR
|
||||
#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;
|
||||
|
||||
if (CHECK_ERROR (MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str.cfString)))
|
||||
info.name = String::fromCFString (str.cfString);
|
||||
}
|
||||
|
||||
SInt32 objectID = 0;
|
||||
|
||||
if (CHECK_ERROR (MIDIObjectGetIntegerProperty (entity, kMIDIPropertyUniqueID, &objectID)))
|
||||
info.identifier = String (objectID);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static MidiDeviceInfo getEndpointInfo (MIDIEndpointRef endpoint, bool isExternal)
|
||||
{
|
||||
// NB: don't attempt to use nullptr for refs - it fails in some types of build.
|
||||
MIDIEntityRef entity = 0;
|
||||
MIDIEndpointGetEntity (endpoint, &entity);
|
||||
|
||||
// probably virtual
|
||||
if (entity == 0)
|
||||
return getMidiObjectInfo (endpoint);
|
||||
|
||||
auto result = getMidiObjectInfo (endpoint);
|
||||
|
||||
// endpoint is empty - try the entity
|
||||
if (result == MidiDeviceInfo())
|
||||
result = getMidiObjectInfo (entity);
|
||||
|
||||
// now consider the device
|
||||
MIDIDeviceRef device = 0;
|
||||
MIDIEntityGetDevice (entity, &device);
|
||||
|
||||
if (device != 0)
|
||||
{
|
||||
auto info = getMidiObjectInfo (device);
|
||||
|
||||
if (info != MidiDeviceInfo())
|
||||
{
|
||||
// if an external device has only one entity, throw away
|
||||
// the endpoint name and just use the device name
|
||||
if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2)
|
||||
{
|
||||
result = info;
|
||||
}
|
||||
else if (! result.name.startsWithIgnoreCase (info.name))
|
||||
{
|
||||
// prepend the device name and identifier to the entity's
|
||||
result.name = (info.name + " " + result.name).trimEnd();
|
||||
result.identifier = info.identifier + " " + result.identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static MidiDeviceInfo getConnectedEndpointInfo (MIDIEndpointRef endpoint)
|
||||
{
|
||||
MidiDeviceInfo result;
|
||||
|
||||
// Does the endpoint have connections?
|
||||
CFDataRef connections = nullptr;
|
||||
int numConnections = 0;
|
||||
|
||||
MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections);
|
||||
|
||||
if (connections != nullptr)
|
||||
{
|
||||
numConnections = ((int) CFDataGetLength (connections)) / (int) sizeof (MIDIUniqueID);
|
||||
|
||||
if (numConnections > 0)
|
||||
{
|
||||
auto* pid = reinterpret_cast<const SInt32*> (CFDataGetBytePtr (connections));
|
||||
|
||||
for (int i = 0; i < numConnections; ++i, ++pid)
|
||||
{
|
||||
auto id = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid);
|
||||
MIDIObjectRef connObject;
|
||||
MIDIObjectType connObjectType;
|
||||
auto err = MIDIObjectFindByUniqueID (id, &connObject, &connObjectType);
|
||||
|
||||
if (err == noErr)
|
||||
{
|
||||
MidiDeviceInfo deviceInfo;
|
||||
|
||||
if (connObjectType == kMIDIObjectType_ExternalSource
|
||||
|| connObjectType == kMIDIObjectType_ExternalDestination)
|
||||
{
|
||||
// Connected to an external device's endpoint (10.3 and later).
|
||||
deviceInfo = getEndpointInfo (static_cast<MIDIEndpointRef> (connObject), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Connected to an external device (10.2) (or something else, catch-all)
|
||||
deviceInfo = getMidiObjectInfo (connObject);
|
||||
}
|
||||
|
||||
if (deviceInfo != MidiDeviceInfo())
|
||||
{
|
||||
if (result.name.isNotEmpty()) result.name += ", ";
|
||||
if (result.identifier.isNotEmpty()) result.identifier += ", ";
|
||||
|
||||
result.name += deviceInfo.name;
|
||||
result.identifier += deviceInfo.identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease (connections);
|
||||
}
|
||||
|
||||
// Here, either the endpoint had no connections, or we failed to obtain names for them.
|
||||
if (result == MidiDeviceInfo())
|
||||
return getEndpointInfo (endpoint, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int createUniqueIDForMidiPort (String deviceName, bool isInput)
|
||||
{
|
||||
String uniqueID;
|
||||
|
||||
#ifdef JucePlugin_CFBundleIdentifier
|
||||
uniqueID = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier);
|
||||
#else
|
||||
auto appBundle = File::getSpecialLocation (File::currentApplicationFile);
|
||||
|
||||
if (auto bundleURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, appBundle.getFullPathName().toCFString(),
|
||||
kCFURLPOSIXPathStyle, true))
|
||||
{
|
||||
auto bundleRef = CFBundleCreate (kCFAllocatorDefault, bundleURL);
|
||||
CFRelease (bundleURL);
|
||||
|
||||
if (bundleRef != nullptr)
|
||||
{
|
||||
if (auto bundleId = CFBundleGetIdentifier (bundleRef))
|
||||
uniqueID = String::fromCFString (bundleId);
|
||||
|
||||
CFRelease (bundleRef);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (uniqueID.isNotEmpty())
|
||||
uniqueID += "." + deviceName + (isInput ? ".input" : ".output");
|
||||
|
||||
return uniqueID.hashCode();
|
||||
}
|
||||
|
||||
static void enableSimulatorMidiSession()
|
||||
{
|
||||
#if TARGET_OS_SIMULATOR
|
||||
|
|
@ -85,139 +234,6 @@ namespace CoreMidiHelpers
|
|||
#endif
|
||||
}
|
||||
|
||||
static String getEndpointName (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.
|
||||
MIDIEndpointGetEntity (endpoint, &entity);
|
||||
|
||||
if (entity == 0)
|
||||
return result; // probably virtual
|
||||
|
||||
if (result.isEmpty())
|
||||
result = getMidiObjectName (entity); // endpoint name is empty - try the entity
|
||||
|
||||
// now consider the device's name
|
||||
MIDIDeviceRef device = 0;
|
||||
MIDIEntityGetDevice (entity, &device);
|
||||
|
||||
if (device != 0)
|
||||
{
|
||||
auto deviceName = getMidiObjectName (device);
|
||||
|
||||
if (deviceName.isNotEmpty())
|
||||
{
|
||||
// if an external device has only one entity, throw away
|
||||
// the endpoint name and just use the device name
|
||||
if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2)
|
||||
{
|
||||
result = deviceName;
|
||||
}
|
||||
else if (! result.startsWithIgnoreCase (deviceName))
|
||||
{
|
||||
// prepend the device name to the entity name
|
||||
result = (deviceName + " " + result).trimEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static String getConnectedEndpointName (MIDIEndpointRef endpoint)
|
||||
{
|
||||
String result;
|
||||
|
||||
// Does the endpoint have connections?
|
||||
CFDataRef connections = nullptr;
|
||||
int numConnections = 0;
|
||||
|
||||
MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections);
|
||||
|
||||
if (connections != nullptr)
|
||||
{
|
||||
numConnections = ((int) CFDataGetLength (connections)) / (int) sizeof (MIDIUniqueID);
|
||||
|
||||
if (numConnections > 0)
|
||||
{
|
||||
auto pid = reinterpret_cast<const SInt32*> (CFDataGetBytePtr (connections));
|
||||
|
||||
for (int i = 0; i < numConnections; ++i, ++pid)
|
||||
{
|
||||
auto uid = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid);
|
||||
MIDIObjectRef connObject;
|
||||
MIDIObjectType connObjectType;
|
||||
auto err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType);
|
||||
|
||||
if (err == noErr)
|
||||
{
|
||||
String s;
|
||||
|
||||
if (connObjectType == kMIDIObjectType_ExternalSource
|
||||
|| connObjectType == kMIDIObjectType_ExternalDestination)
|
||||
{
|
||||
// Connected to an external device's endpoint (10.3 and later).
|
||||
s = getEndpointName (static_cast<MIDIEndpointRef> (connObject), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Connected to an external device (10.2) (or something else, catch-all)
|
||||
s = getMidiObjectName (connObject);
|
||||
}
|
||||
|
||||
if (s.isNotEmpty())
|
||||
{
|
||||
if (result.isNotEmpty())
|
||||
result += ", ";
|
||||
|
||||
result += s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFRelease (connections);
|
||||
}
|
||||
|
||||
if (result.isEmpty()) // Here, either the endpoint had no connections, or we failed to obtain names for them.
|
||||
result = getEndpointName (endpoint, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void setUniqueIdForMidiPort (MIDIObjectRef device, const String& portName, bool isInput)
|
||||
{
|
||||
String portUniqueId;
|
||||
#if defined (JucePlugin_CFBundleIdentifier)
|
||||
portUniqueId = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier);
|
||||
#else
|
||||
auto appBundle = File::getSpecialLocation (File::currentApplicationFile);
|
||||
|
||||
if (auto bundleURL = CFURLCreateWithFileSystemPath (kCFAllocatorDefault, appBundle.getFullPathName().toCFString(),
|
||||
kCFURLPOSIXPathStyle, true))
|
||||
{
|
||||
auto bundleRef = CFBundleCreate (kCFAllocatorDefault, bundleURL);
|
||||
CFRelease (bundleURL);
|
||||
|
||||
if (bundleRef != nullptr)
|
||||
{
|
||||
if (auto bundleId = CFBundleGetIdentifier (bundleRef))
|
||||
portUniqueId = String::fromCFString (bundleId);
|
||||
|
||||
CFRelease (bundleRef);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (portUniqueId.isNotEmpty())
|
||||
{
|
||||
portUniqueId += "." + portName + (isInput ? ".input" : ".output");
|
||||
|
||||
CHECK_ERROR (MIDIObjectSetStringProperty (device, kMIDIPropertyUniqueID, portUniqueId.toCFString()));
|
||||
}
|
||||
}
|
||||
|
||||
static void globalSystemChangeCallback (const MIDINotification*, void*)
|
||||
{
|
||||
// TODO.. Should pass-on this notification..
|
||||
|
|
@ -243,15 +259,14 @@ namespace CoreMidiHelpers
|
|||
|
||||
enableSimulatorMidiSession();
|
||||
|
||||
CoreMidiHelpers::ScopedCFString name;
|
||||
name.cfString = getGlobalMidiClientName().toCFString();
|
||||
CoreMidiHelpers::ScopedCFString name (getGlobalMidiClientName());
|
||||
CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &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
|
||||
// search for devices. It's safest to use the message thread for calling this.
|
||||
|
|
@ -263,26 +278,25 @@ namespace CoreMidiHelpers
|
|||
return {};
|
||||
}
|
||||
|
||||
StringArray s;
|
||||
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))
|
||||
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:
|
||||
MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept
|
||||
: port (p), endPoint (ep)
|
||||
: port (p), endpoint (ep)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -299,20 +313,21 @@ namespace CoreMidiHelpers
|
|||
if (port != 0)
|
||||
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
|
||||
{
|
||||
if (port != 0)
|
||||
MIDISend (port, endPoint, packets);
|
||||
MIDISend (port, endpoint, packets);
|
||||
else
|
||||
MIDIReceived (endPoint, packets);
|
||||
MIDIReceived (endpoint, packets);
|
||||
}
|
||||
|
||||
MIDIPortRef port;
|
||||
MIDIEndpointRef endPoint;
|
||||
MIDIEndpointRef endpoint;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -334,7 +349,7 @@ namespace CoreMidiHelpers
|
|||
}
|
||||
|
||||
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)
|
||||
|
|
@ -370,63 +385,250 @@ namespace CoreMidiHelpers
|
|||
{
|
||||
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; }
|
||||
|
||||
MidiOutput* MidiOutput::openDevice (int index)
|
||||
Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
|
||||
{
|
||||
MidiOutput* mo = nullptr;
|
||||
return CoreMidiHelpers::findDevices (true);
|
||||
}
|
||||
|
||||
if (auto client = CoreMidiHelpers::getGlobalMidiClient())
|
||||
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())
|
||||
{
|
||||
if (isPositiveAndBelow (index, MIDIGetNumberOfDestinations()))
|
||||
for (auto& endpoint : getEndpoints (true))
|
||||
{
|
||||
auto endPoint = MIDIGetDestination ((ItemCount) index);
|
||||
|
||||
CoreMidiHelpers::ScopedCFString pname;
|
||||
|
||||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString)))
|
||||
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;
|
||||
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 mo;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName)
|
||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
|
||||
{
|
||||
if (auto client = CoreMidiHelpers::getGlobalMidiClient())
|
||||
using namespace CoreMidiHelpers;
|
||||
jassert (callback != nullptr);
|
||||
|
||||
if (auto client = getGlobalMidiClient())
|
||||
{
|
||||
MIDIEndpointRef endPoint;
|
||||
auto mpc = std::make_unique<MidiPortAndCallback> (*callback);
|
||||
mpc->active = false;
|
||||
|
||||
CoreMidiHelpers::ScopedCFString name;
|
||||
name.cfString = deviceName.toCFString();
|
||||
MIDIEndpointRef endpoint;
|
||||
ScopedCFString name (deviceName);
|
||||
|
||||
if (CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endPoint)))
|
||||
if (CHECK_ERROR (MIDIDestinationCreate (client, name.cfString, midiInputProc, mpc.get(), &endpoint)))
|
||||
{
|
||||
CoreMidiHelpers::setUniqueIdForMidiPort (endPoint, deviceName, false);
|
||||
auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, true);
|
||||
|
||||
auto mo = new MidiOutput (deviceName);
|
||||
mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint);
|
||||
return mo;
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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)
|
||||
{
|
||||
ScopedCFString cfName;
|
||||
|
||||
if (CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString)))
|
||||
{
|
||||
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 nullptr;
|
||||
}
|
||||
|
||||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName)
|
||||
{
|
||||
using namespace CoreMidiHelpers;
|
||||
|
||||
if (auto client = getGlobalMidiClient())
|
||||
{
|
||||
MIDIEndpointRef endpoint;
|
||||
|
||||
ScopedCFString name (deviceName);
|
||||
|
||||
if (CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endpoint)))
|
||||
{
|
||||
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);
|
||||
|
||||
return midiOutput.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
stopBackgroundThread();
|
||||
|
|
@ -493,111 +695,6 @@ void MidiOutput::sendMessageNow (const MidiMessage& message)
|
|||
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
|
||||
|
||||
} // 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
|
||||
{
|
||||
|
||||
|
|
@ -29,7 +35,9 @@ struct MidiServiceType
|
|||
{
|
||||
virtual ~InputWrapper() {}
|
||||
|
||||
virtual String getDeviceIdentifier() = 0;
|
||||
virtual String getDeviceName() = 0;
|
||||
|
||||
virtual void start() = 0;
|
||||
virtual void stop() = 0;
|
||||
};
|
||||
|
|
@ -38,18 +46,20 @@ struct MidiServiceType
|
|||
{
|
||||
virtual ~OutputWrapper() {}
|
||||
|
||||
virtual String getDeviceIdentifier() = 0;
|
||||
virtual String getDeviceName() = 0;
|
||||
|
||||
virtual void sendMessageNow (const MidiMessage&) = 0;
|
||||
};
|
||||
|
||||
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)
|
||||
};
|
||||
|
|
@ -60,26 +70,26 @@ struct Win32MidiService : public MidiServiceType,
|
|||
{
|
||||
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:
|
||||
|
|
@ -88,7 +98,10 @@ private:
|
|||
//==============================================================================
|
||||
struct MidiInCollector : public ReferenceCountedObject
|
||||
{
|
||||
MidiInCollector (Win32MidiService& s, const String& name) : deviceName (name), midiService (s) {}
|
||||
MidiInCollector (Win32MidiService& s, MidiDeviceInfo d)
|
||||
: deviceInfo (d), midiService (s)
|
||||
{
|
||||
}
|
||||
|
||||
~MidiInCollector()
|
||||
{
|
||||
|
|
@ -216,7 +229,7 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
String deviceName;
|
||||
MidiDeviceInfo deviceInfo;
|
||||
HMIDIIN deviceHandle = 0;
|
||||
|
||||
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)
|
||||
{
|
||||
collector = getOrCreateCollector (parentService, index);
|
||||
collector = getOrCreateCollector (parentService, deviceIdentifier);
|
||||
collector->addClient (this);
|
||||
}
|
||||
|
||||
|
|
@ -334,25 +393,31 @@ private:
|
|||
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;
|
||||
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);
|
||||
|
||||
for (auto& c : parentService.activeCollectors)
|
||||
if (c->deviceName == deviceName)
|
||||
if (c->deviceInfo.identifier == deviceIdentifier)
|
||||
return c;
|
||||
|
||||
MidiInCollector::Ptr c (new MidiInCollector (parentService, deviceName));
|
||||
MidiInCollector::Ptr c (new MidiInCollector (parentService, { deviceName, deviceIdentifier }));
|
||||
|
||||
HMIDIIN h;
|
||||
auto err = midiInOpen (&h, deviceID,
|
||||
|
|
@ -368,29 +433,33 @@ private:
|
|||
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);
|
||||
}
|
||||
|
||||
for (UINT i = 0; i < num; ++i)
|
||||
static Array<MIDIINCAPS> getDeviceCaps()
|
||||
{
|
||||
Array<MIDIINCAPS> devices;
|
||||
|
||||
for (UINT i = 0; i < midiInGetNumDevs(); ++i)
|
||||
{
|
||||
MIDIINCAPS mc = { 0 };
|
||||
|
||||
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 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)
|
||||
{
|
||||
|
|
@ -411,8 +480,8 @@ private:
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
|
@ -426,32 +495,41 @@ private:
|
|||
}
|
||||
|
||||
Win32MidiService& owner;
|
||||
String deviceName;
|
||||
MidiDeviceInfo deviceInfo;
|
||||
HMIDIOUT handle;
|
||||
|
||||
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;
|
||||
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)
|
||||
{
|
||||
// use the microsoft sw synth as a default - best not to allow deviceID
|
||||
// to be MIDI_MAPPER, or else device sharing breaks
|
||||
for (int i = 0; i < names.size(); ++i)
|
||||
if (names[i].containsIgnoreCase ("microsoft"))
|
||||
for (int i = 0; i < devices.size(); ++i)
|
||||
if (devices[i].name.containsIgnoreCase ("microsoft"))
|
||||
deviceID = (UINT) i;
|
||||
}
|
||||
|
||||
|
|
@ -459,7 +537,7 @@ private:
|
|||
{
|
||||
auto* activeHandle = parent.activeOutputHandles.getUnchecked (i);
|
||||
|
||||
if (activeHandle->deviceName == deviceName)
|
||||
if (activeHandle->deviceInfo.identifier == deviceIdentifier)
|
||||
{
|
||||
han = activeHandle;
|
||||
return;
|
||||
|
|
@ -473,7 +551,7 @@ private:
|
|||
|
||||
if (res == MMSYSERR_NOERROR)
|
||||
{
|
||||
han = new MidiOutHandle (parent, deviceName, h);
|
||||
han = new MidiOutHandle (parent, { deviceName, deviceIdentifier }, h);
|
||||
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()
|
||||
{
|
||||
Array<MIDIOUTCAPS> devices;
|
||||
auto num = midiOutGetNumDevs();
|
||||
|
||||
for (UINT i = 0; i < num; ++i)
|
||||
for (UINT i = 0; i < midiOutGetNumDevs(); ++i)
|
||||
{
|
||||
MIDIOUTCAPS mc = { 0 };
|
||||
|
||||
|
|
@ -546,36 +628,26 @@ private:
|
|||
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;
|
||||
String deviceName;
|
||||
MidiOutHandle::Ptr han;
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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:
|
||||
|
|
@ -1098,7 +1170,7 @@ private:
|
|||
};
|
||||
|
||||
//==============================================================================
|
||||
struct MIDIDeviceInfo
|
||||
struct WinRTMIDIDeviceInfo
|
||||
{
|
||||
String deviceID, containerID, name;
|
||||
bool isDefault = false;
|
||||
|
|
@ -1120,7 +1192,7 @@ private:
|
|||
|
||||
HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override
|
||||
{
|
||||
MIDIDeviceInfo info;
|
||||
WinRTMIDIDeviceInfo info;
|
||||
|
||||
HSTRING deviceID;
|
||||
auto hr = addedDeviceInfo->get_Id (&deviceID);
|
||||
|
|
@ -1229,56 +1301,59 @@ private:
|
|||
return attach (deviceSelector, DeviceInformationKind::DeviceInformationKind_DeviceInterface);
|
||||
}
|
||||
|
||||
StringArray getDevices()
|
||||
Array<MidiDeviceInfo> getAvailableDevices()
|
||||
{
|
||||
{
|
||||
const ScopedLock lock (deviceChanges);
|
||||
lastQueriedConnectedDevices = connectedDevices;
|
||||
}
|
||||
|
||||
StringArray result;
|
||||
StringArray deviceNames, deviceIDs;
|
||||
|
||||
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 (""));
|
||||
|
||||
Array<MidiDeviceInfo> devices;
|
||||
|
||||
for (int i = 0; i < deviceNames.size(); ++i)
|
||||
devices.add ({ deviceNames[i], deviceIDs[i] });
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
int getDefaultDeviceIndex()
|
||||
MidiDeviceInfo getDefaultDevice()
|
||||
{
|
||||
auto& lastDevices = lastQueriedConnectedDevices.get();
|
||||
|
||||
for (int i = 0; i < lastDevices.size(); ++i)
|
||||
if (lastDevices[i].isDefault)
|
||||
return i;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MIDIDeviceInfo getDeviceInfoFromIndex (int index)
|
||||
{
|
||||
if (isPositiveAndBelow (index, lastQueriedConnectedDevices.get().size()))
|
||||
return lastQueriedConnectedDevices.get()[index];
|
||||
for (auto& d : lastDevices)
|
||||
if (d.isDefault)
|
||||
return { d.name, d.containerID };
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
WinRTWrapper::ComPtr<COMFactoryType>& factory;
|
||||
|
||||
Array<MIDIDeviceInfo> connectedDevices;
|
||||
Array<WinRTMIDIDeviceInfo> connectedDevices;
|
||||
CriticalSection deviceChanges;
|
||||
ThreadLocalValue<Array<MIDIDeviceInfo>> lastQueriedConnectedDevices;
|
||||
ThreadLocalValue<Array<WinRTMIDIDeviceInfo>> lastQueriedConnectedDevices;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher);
|
||||
};
|
||||
|
|
@ -1345,12 +1420,12 @@ private:
|
|||
public:
|
||||
WinRTIOWrapper (BLEDeviceWatcher& bleWatcher,
|
||||
MidiIODeviceWatcher<MIDIIOStaticsType>& midiDeviceWatcher,
|
||||
int index)
|
||||
const String& deviceIdentifier)
|
||||
: bleDeviceWatcher (bleWatcher)
|
||||
{
|
||||
{
|
||||
const ScopedLock lock (midiDeviceWatcher.deviceChanges);
|
||||
deviceInfo = midiDeviceWatcher.getDeviceInfoFromIndex (index);
|
||||
deviceInfo = midiDeviceWatcher.getWinRTDeviceInfoForDevice (deviceIdentifier);
|
||||
}
|
||||
|
||||
if (deviceInfo.deviceID.isEmpty())
|
||||
|
|
@ -1417,7 +1492,7 @@ private:
|
|||
protected:
|
||||
//==============================================================================
|
||||
BLEDeviceWatcher& bleDeviceWatcher;
|
||||
MIDIDeviceInfo deviceInfo;
|
||||
WinRTMIDIDeviceInfo deviceInfo;
|
||||
bool isBLEDevice = false;
|
||||
WinRTWrapper::ComPtr<MIDIPort> midiPort;
|
||||
};
|
||||
|
|
@ -1427,8 +1502,8 @@ private:
|
|||
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),
|
||||
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
|
||||
|
|
@ -1579,8 +1655,8 @@ private:
|
|||
struct WinRTOutputWrapper final : public OutputWrapper,
|
||||
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",
|
||||
deviceInfo.deviceID,
|
||||
|
|
@ -1632,7 +1708,8 @@ private:
|
|||
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;
|
||||
|
|
@ -1718,42 +1795,74 @@ private:
|
|||
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;
|
||||
|
||||
std::unique_ptr<MidiInput> in (new MidiInput (String()));
|
||||
MidiInput input ({}, {});
|
||||
std::unique_ptr<MidiServiceType::InputWrapper> wrapper;
|
||||
|
||||
try
|
||||
{
|
||||
wrapper.reset (MidiService::getService().createInputWrapper (*in, index, *callback));
|
||||
wrapper.reset (MidiService::getService().createInputWrapper (input, deviceIdentifier, *callback));
|
||||
}
|
||||
catch (std::runtime_error&)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
in->setName (wrapper->getDeviceName());
|
||||
std::unique_ptr<MidiInput> in;
|
||||
in.reset (new MidiInput (wrapper->getDeviceName(), deviceIdentifier));
|
||||
in->internal = wrapper.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()
|
||||
{
|
||||
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(); }
|
||||
|
||||
//==============================================================================
|
||||
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;
|
||||
|
||||
try
|
||||
{
|
||||
wrapper.reset (MidiService::getService().createOutputWrapper (index));
|
||||
wrapper.reset (MidiService::getService().createOutputWrapper (deviceIdentifier));
|
||||
}
|
||||
catch (std::runtime_error&)
|
||||
{
|
||||
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();
|
||||
|
||||
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()
|
||||
{
|
||||
stopBackgroundThread();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue