Browse Source

Added support for ID-based MIDI devices

tags/2021-05-28
ed 6 years ago
parent
commit
09ebd1d257
14 changed files with 1798 additions and 1328 deletions
  1. +23
    -23
      examples/Audio/MidiDemo.h
  2. +3
    -0
      extras/UnitTestRunner/Source/Main.cpp
  3. +1
    -1
      modules/juce_audio_devices/juce_audio_devices.cpp
  4. +1
    -2
      modules/juce_audio_devices/juce_audio_devices.h
  5. +105
    -13
      modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp
  6. +365
    -0
      modules/juce_audio_devices/midi_io/juce_MidiDevices.h
  7. +0
    -180
      modules/juce_audio_devices/midi_io/juce_MidiInput.h
  8. +0
    -145
      modules/juce_audio_devices/midi_io/juce_MidiOutput.h
  9. +47
    -50
      modules/juce_audio_devices/native/java/app/com/roli/juce/JuceMidiSupport.java
  10. +455
    -433
      modules/juce_audio_devices/native/juce_android_Midi.cpp
  11. +56
    -39
      modules/juce_audio_devices/native/juce_linux_Bela.cpp
  12. +161
    -93
      modules/juce_audio_devices/native/juce_linux_Midi.cpp
  13. +313
    -216
      modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp
  14. +268
    -133
      modules/juce_audio_devices/native/juce_win32_Midi.cpp

+ 23
- 23
examples/Audio/MidiDemo.h View File

@@ -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);
}


+ 3
- 0
extras/UnitTestRunner/Source/Main.cpp View File

@@ -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);


+ 1
- 1
modules/juce_audio_devices/juce_audio_devices.cpp View File

@@ -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"


+ 1
- 2
modules/juce_audio_devices/juce_audio_devices.h View File

@@ -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"


modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp → modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp View File

@@ -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
- 0
modules/juce_audio_devices/midi_io/juce_MidiDevices.h View 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

+ 0
- 180
modules/juce_audio_devices/midi_io/juce_MidiInput.h View File

@@ -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

+ 0
- 145
modules/juce_audio_devices/midi_io/juce_MidiOutput.h View File

@@ -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

+ 47
- 50
modules/juce_audio_devices/native/java/app/com/roli/juce/JuceMidiSupport.java View File

@@ -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;
ArrayList<MidiPortPath> ports = new ArrayList<MidiPortPath> ();
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 ());
ports.add (new MidiPortPath (info.getId (), (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT),
portInfo.getPortNumber ()));
portIdx++;
}
}
}
return ports;
}
public MidiPortPath getPortPathForID (int portType, int deviceID)
{
for (MidiPortPath port : getAllPorts (portType))
if (port.hashCode () == deviceID)
return port;
return null;
}


+ 455
- 433
modules/juce_audio_devices/native/juce_android_Midi.cpp
File diff suppressed because it is too large
View File


+ 56
- 39
modules/juce_audio_devices/native/juce_linux_Bela.cpp View File

@@ -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& deviceName, const String& deviceID)
: deviceInfo (deviceName, deviceID)
{
}
//==============================================================================
MidiInput::MidiInput (const String& nm) : name (nm) {}
MidiInput::~MidiInput() { delete static_cast<BelaMidiInput*> (internal); }
void MidiInput::start() { static_cast<BelaMidiInput*> (internal)->start(); }
void MidiInput::stop() { static_cast<BelaMidiInput*> (internal)->stop(); }
MidiInput::~MidiInput()
void Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
{
delete static_cast<BelaMidiInput*> (internal);
return BelaMidiInput::getDevices (true);
}
void MidiInput::start() { static_cast<BelaMidiInput*> (internal)->start(); }
void MidiInput::stop() { static_cast<BelaMidiInput*> (internal)->stop(); }
MidiDeviceInfo MidiInput::getDefaultDevice()
{
return getAvailableDevices().getFirst();
}
int MidiInput::getDefaultDeviceIndex()
MidiInput* MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
{
return 0;
if (deviceIdentifier.isEmpty())
return nullptr;
std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceIdentifier, deviceIdentifier));
midiInput->internal = new BelaMidiInput (deviceIdentifier, result, callback);
return midiInput.release();
}
StringArray MidiInput::getDevices()
MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*)
{
return BelaMidiInput::getDevices (true);
// N/A on Bela
jassertfalse;
return nullptr;
}
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
StringArray MidiInput::getDevices()
{
auto devices = getDevices();
StringArray deviceNames;
if (index >= 0 && index < devices.size())
{
auto deviceName = devices[index];
auto result = new MidiInput (deviceName);
result->internal = new BelaMidiInput (deviceName, result, callback);
return result;
}
for (auto& d : getAvailableDevices())
deviceNames.add (d.name);
return deviceNames;
}
return {};
int MidiInput::getDefaultDeviceIndex()
{
return 0;
}
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
{
jassertfalse; // N/A on Bela
return {};
return openDevice (getAvailableDevices()[index].identifier, callback);
}
//==============================================================================
// TODO: Add Bela MidiOutput support
MidiOutput::~MidiOutput() {}
void MidiOutput::sendMessageNow (const MidiMessage&) {}
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; }
MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; }
MidiOutput* MidiOutput::openDevice (const String&) { return nullptr; }
MidiOutput* MidiOutput::createNewDevice (const String&) { return nullptr; }
StringArray MidiOutput::getDevices() { return {}; }
int MidiOutput::getDefaultDeviceIndex() { return 0;}
MidiOutput* MidiOutput::openDevice (int) { return nullptr; }
} // namespace juce

+ 161
- 93
modules/juce_audio_devices/native/juce_linux_Midi.cpp View File

@@ -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());
std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId)));
return newDevice;
port->setupInput (midiInput.get(), callback);
midiInput->internal = port;
return midiInput.release();
}
MidiOutput::~MidiOutput()
StringArray MidiInput::getDevices()
{
stopBackgroundThread();
AlsaClient::getInstance()->deletePort (static_cast<AlsaClient::Port*> (internal));
StringArray deviceNames;
for (auto& d : getAvailableDevices())
deviceNames.add (d.name);
deviceNames.appendNumbersToDuplicates (true, true);
return deviceNames;
}
void MidiOutput::sendMessageNow (const MidiMessage& message)
int MidiInput::getDefaultDeviceIndex()
{
static_cast<AlsaClient::Port*> (internal)->sendMessageNow (message);
return 0;
}
//==============================================================================
MidiInput::MidiInput (const String& nm) : name (nm)
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
{
return openDevice (getAvailableDevices()[index].identifier, callback);
}
MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
: deviceInfo (deviceName, deviceIdentifier)
{
}
@@ -530,68 +548,118 @@ void MidiInput::stop()
static_cast<AlsaClient::Port*> (internal)->enableCallback (false);
}
int MidiInput::getDefaultDeviceIndex()
//==============================================================================
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
{
return 0;
Array<MidiDeviceInfo> devices;
iterateMidiDevices (false, devices, {});
return devices;
}
StringArray MidiInput::getDevices()
MidiDeviceInfo MidiOutput::getDefaultDevice()
{
StringArray devices;
iterateMidiDevices (true, devices, -1);
return devices;
return getAvailableDevices().getFirst();
}
MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback)
MidiOutput* MidiOutput::openDevice (const String& deviceIdentifier)
{
StringArray devices;
auto* port = iterateMidiDevices (true, devices, deviceIndex);
if (deviceIdentifier.isEmpty())
return nullptr;
Array<MidiDeviceInfo> devices;
auto* port = iterateMidiDevices (false, devices, deviceIdentifier);
if (port == nullptr)
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());
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId)));
auto newDevice = new MidiInput (deviceName);
port->setupInput (newDevice, callback);
newDevice->internal = port;
return newDevice;
port->setupOutput();
midiOutput->internal = port;
return midiOutput.release();
}
StringArray MidiOutput::getDevices()
{
StringArray deviceNames;
for (auto& d : getAvailableDevices())
deviceNames.add (d.name);
deviceNames.appendNumbersToDuplicates (true, true);
return deviceNames;
}
int MidiOutput::getDefaultDeviceIndex()
{
return 0;
}
MidiOutput* MidiOutput::openDevice (int index)
{
return openDevice (getAvailableDevices()[index].identifier);
}
MidiOutput::~MidiOutput()
{
stopBackgroundThread();
AlsaClient::getInstance()->deletePort (static_cast<AlsaClient::Port*> (internal));
}
void MidiOutput::sendMessageNow (const MidiMessage& message)
{
static_cast<AlsaClient::Port*> (internal)->sendMessageNow (message);
}
//==============================================================================
#else
// (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


+ 313
- 216
modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp View File

@@ -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,79 +61,62 @@ 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;
return result;
}
if (CHECK_ERROR (MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str.cfString)))
info.name = String::fromCFString (str.cfString);
}
static void enableSimulatorMidiSession()
{
#if TARGET_OS_SIMULATOR
static bool hasEnabledNetworkSession = false;
SInt32 objectID = 0;
if (! hasEnabledNetworkSession)
{
MIDINetworkSession* session = [MIDINetworkSession defaultSession];
session.enabled = YES;
session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone;
if (CHECK_ERROR (MIDIObjectGetIntegerProperty (entity, kMIDIPropertyUniqueID, &objectID)))
info.identifier = String (objectID);
hasEnabledNetworkSession = true;
}
#endif
return info;
}
static String getEndpointName (MIDIEndpointRef endpoint, bool isExternal)
static MidiDeviceInfo getEndpointInfo (MIDIEndpointRef endpoint, bool isExternal)
{
auto result = getMidiObjectName (endpoint);
MIDIEntityRef entity = 0; // NB: don't attempt to use nullptr for refs - it fails in some types of build.
// NB: don't attempt to use nullptr for refs - it fails in some types of build.
MIDIEntityRef entity = 0;
MIDIEndpointGetEntity (endpoint, &entity);
// probably virtual
if (entity == 0)
return result; // probably virtual
return getMidiObjectInfo (endpoint);
auto result = getMidiObjectInfo (endpoint);
if (result.isEmpty())
result = getMidiObjectName (entity); // endpoint name is empty - try the entity
// endpoint is empty - try the entity
if (result == MidiDeviceInfo())
result = getMidiObjectInfo (entity);
// now consider the device's name
// now consider the device
MIDIDeviceRef device = 0;
MIDIEntityGetDevice (entity, &device);
if (device != 0)
{
auto deviceName = getMidiObjectName (device);
auto info = getMidiObjectInfo (device);
if (deviceName.isNotEmpty())
if (info != MidiDeviceInfo())
{
// if an external device has only one entity, throw away
// the endpoint name and just use the device name
if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2)
{
result = deviceName;
result = info;
}
else if (! result.startsWithIgnoreCase (deviceName))
else if (! result.name.startsWithIgnoreCase (info.name))
{
// prepend the device name to the entity name
result = (deviceName + " " + result).trimEnd();
// prepend the device name and identifier to the entity's
result.name = (info.name + " " + result.name).trimEnd();
result.identifier = info.identifier + " " + result.identifier;
}
}
}
@@ -125,9 +124,9 @@ namespace CoreMidiHelpers
return result;
}
static String getConnectedEndpointName (MIDIEndpointRef endpoint)
static MidiDeviceInfo getConnectedEndpointInfo (MIDIEndpointRef endpoint)
{
String result;
MidiDeviceInfo result;
// Does the endpoint have connections?
CFDataRef connections = nullptr;
@@ -141,37 +140,38 @@ namespace CoreMidiHelpers
if (numConnections > 0)
{
auto pid = reinterpret_cast<const SInt32*> (CFDataGetBytePtr (connections));
auto* pid = reinterpret_cast<const SInt32*> (CFDataGetBytePtr (connections));
for (int i = 0; i < numConnections; ++i, ++pid)
{
auto uid = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid);
auto id = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid);
MIDIObjectRef connObject;
MIDIObjectType connObjectType;
auto err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType);
auto err = MIDIObjectFindByUniqueID (id, &connObject, &connObjectType);
if (err == noErr)
{
String s;
MidiDeviceInfo deviceInfo;
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);
deviceInfo = getEndpointInfo (static_cast<MIDIEndpointRef> (connObject), true);
}
else
{
// Connected to an external device (10.2) (or something else, catch-all)
s = getMidiObjectName (connObject);
deviceInfo = getMidiObjectInfo (connObject);
}
if (s.isNotEmpty())
if (deviceInfo != MidiDeviceInfo())
{
if (result.isNotEmpty())
result += ", ";
if (result.name.isNotEmpty()) result.name += ", ";
if (result.identifier.isNotEmpty()) result.identifier += ", ";
result += s;
result.name += deviceInfo.name;
result.identifier += deviceInfo.identifier;
}
}
}
@@ -180,17 +180,19 @@ namespace CoreMidiHelpers
CFRelease (connections);
}
if (result.isEmpty()) // Here, either the endpoint had no connections, or we failed to obtain names for them.
result = getEndpointName (endpoint, false);
// Here, either the endpoint had no connections, or we failed to obtain names for them.
if (result == MidiDeviceInfo())
return getEndpointInfo (endpoint, false);
return result;
}
static void setUniqueIdForMidiPort (MIDIObjectRef device, const String& portName, bool isInput)
static int createUniqueIDForMidiPort (String deviceName, bool isInput)
{
String portUniqueId;
#if defined (JucePlugin_CFBundleIdentifier)
portUniqueId = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier);
String uniqueID;
#ifdef JucePlugin_CFBundleIdentifier
uniqueID = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier);
#else
auto appBundle = File::getSpecialLocation (File::currentApplicationFile);
@@ -203,19 +205,33 @@ namespace CoreMidiHelpers
if (bundleRef != nullptr)
{
if (auto bundleId = CFBundleGetIdentifier (bundleRef))
portUniqueId = String::fromCFString (bundleId);
uniqueID = String::fromCFString (bundleId);
CFRelease (bundleRef);
}
}
#endif
if (portUniqueId.isNotEmpty())
if (uniqueID.isNotEmpty())
uniqueID += "." + deviceName + (isInput ? ".input" : ".output");
return uniqueID.hashCode();
}
static void enableSimulatorMidiSession()
{
#if TARGET_OS_SIMULATOR
static bool hasEnabledNetworkSession = false;
if (! hasEnabledNetworkSession)
{
portUniqueId += "." + portName + (isInput ? ".input" : ".output");
MIDINetworkSession* session = [MIDINetworkSession defaultSession];
session.enabled = YES;
session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone;
CHECK_ERROR (MIDIObjectSetStringProperty (device, kMIDIPropertyUniqueID, portUniqueId.toCFString()));
hasEnabledNetworkSession = true;
}
#endif
}
static void globalSystemChangeCallback (const MIDINotification*, void*)
@@ -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; }
Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
{
return CoreMidiHelpers::findDevices (true);
}
MidiOutput* MidiOutput::openDevice (int index)
MidiDeviceInfo MidiInput::getDefaultDevice()
{
return getAvailableDevices().getFirst();
}
MidiInput* MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
{
if (deviceIdentifier.isEmpty())
return nullptr;
using namespace CoreMidiHelpers;
if (auto client = getGlobalMidiClient())
{
for (auto& endpoint : getEndpoints (true))
{
if (deviceIdentifier == getConnectedEndpointInfo (endpoint).identifier)
{
ScopedCFString cfName;
if (CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString)))
{
MIDIPortRef port;
std::unique_ptr<MidiPortAndCallback> mpc (new MidiPortAndCallback (*callback));
if (CHECK_ERROR (MIDIInputPortCreate (client, cfName.cfString, midiInputProc, mpc.get(), &port)))
{
if (CHECK_ERROR (MIDIPortConnectSource (port, endpoint, nullptr)))
{
mpc->portAndEndpoint.reset (new MidiPortAndEndpoint (port, endpoint));
std::unique_ptr<MidiInput> midiInput (new MidiInput (String::fromCFString (cfName.cfString), deviceIdentifier));
mpc->input = midiInput.get();
midiInput->internal = mpc.get();
const ScopedLock sl (callbackLock);
activeCallbacks.add (mpc.release());
return midiInput.release();
}
else
{
CHECK_ERROR (MIDIPortDispose (port));
}
}
}
}
}
}
return nullptr;
}
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
{
MidiOutput* mo = nullptr;
using namespace CoreMidiHelpers;
jassert (callback != nullptr);
if (auto client = CoreMidiHelpers::getGlobalMidiClient())
if (auto client = getGlobalMidiClient())
{
if (isPositiveAndBelow (index, MIDIGetNumberOfDestinations()))
auto mpc = std::make_unique<MidiPortAndCallback> (*callback);
mpc->active = false;
MIDIEndpointRef endpoint;
ScopedCFString name (deviceName);
if (CHECK_ERROR (MIDIDestinationCreate (client, name.cfString, midiInputProc, mpc.get(), &endpoint)))
{
auto endPoint = MIDIGetDestination ((ItemCount) index);
auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, true);
if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier)))
{
mpc->portAndEndpoint.reset (new MidiPortAndEndpoint (0, endpoint));
std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, String (deviceIdentifier)));
mpc->input = midiInput.get();
midiInput->internal = mpc.get();
const ScopedLock sl (callbackLock);
activeCallbacks.add (mpc.release());
return midiInput.release();
}
}
}
return nullptr;
}
StringArray MidiInput::getDevices()
{
StringArray deviceNames;
for (auto& d : getAvailableDevices())
deviceNames.add (d.name);
return deviceNames;
}
int MidiInput::getDefaultDeviceIndex()
{
return 0;
}
MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback)
{
return openDevice (getAvailableDevices()[index].identifier, callback);
}
MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
: deviceInfo (deviceName, deviceIdentifier)
{
}
CoreMidiHelpers::ScopedCFString pname;
MidiInput::~MidiInput()
{
delete static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal);
}
if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString)))
void MidiInput::start()
{
const ScopedLock sl (CoreMidiHelpers::callbackLock);
static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = true;
}
void MidiInput::stop()
{
const ScopedLock sl (CoreMidiHelpers::callbackLock);
static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = false;
}
//==============================================================================
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
{
return CoreMidiHelpers::findDevices (false);
}
MidiDeviceInfo MidiOutput::getDefaultDevice()
{
return getAvailableDevices().getFirst();
}
MidiOutput* MidiOutput::openDevice (const String& deviceIdentifier)
{
if (deviceIdentifier.isEmpty())
return nullptr;
using namespace CoreMidiHelpers;
if (auto client = getGlobalMidiClient())
{
for (auto& endpoint : getEndpoints (false))
{
if (deviceIdentifier == getConnectedEndpointInfo (endpoint).identifier)
{
MIDIPortRef port;
auto deviceName = CoreMidiHelpers::getConnectedEndpointName (endPoint);
ScopedCFString cfName;
if (CHECK_ERROR (MIDIOutputPortCreate (client, pname.cfString, &port)))
if (CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString)))
{
mo = new MidiOutput (deviceName);
mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint);
MIDIPortRef port;
if (CHECK_ERROR (MIDIOutputPortCreate (client, cfName.cfString, &port)))
{
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (String::fromCFString (cfName.cfString), deviceIdentifier));
midiOutput->internal = new MidiPortAndEndpoint (port, endpoint);
return midiOutput.release();
}
}
}
}
}
return mo;
return nullptr;
}
MidiOutput* MidiOutput::createNewDevice (const String& deviceName)
{
if (auto client = CoreMidiHelpers::getGlobalMidiClient())
using namespace CoreMidiHelpers;
if (auto client = getGlobalMidiClient())
{
MIDIEndpointRef endPoint;
MIDIEndpointRef endpoint;
CoreMidiHelpers::ScopedCFString name;
name.cfString = deviceName.toCFString();
ScopedCFString name (deviceName);
if (CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endPoint)))
if (CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endpoint)))
{
CoreMidiHelpers::setUniqueIdForMidiPort (endPoint, deviceName, false);
auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, true);
if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier)))
{
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, String (deviceIdentifier)));
midiOutput->internal = new MidiPortAndEndpoint (0, endpoint);
auto mo = new MidiOutput (deviceName);
mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint);
return mo;
return midiOutput.release();
}
}
}
return nullptr;
}
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

+ 268
- 133
modules/juce_audio_devices/native/juce_win32_Midi.cpp View File

@@ -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);
}
static Array<MIDIINCAPS> getDeviceCaps()
{
Array<MIDIINCAPS> devices;
for (UINT i = 0; i < num; ++i)
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 (""));
int getDefaultDeviceIndex()
{
auto& lastDevices = lastQueriedConnectedDevices.get();
Array<MidiDeviceInfo> devices;
for (int i = 0; i < lastDevices.size(); ++i)
if (lastDevices[i].isDefault)
return i;
for (int i = 0; i < deviceNames.size(); ++i)
devices.add ({ deviceNames[i], deviceIDs[i] });
return 0;
return devices;
}
MIDIDeviceInfo getDeviceInfoFromIndex (int index)
MidiDeviceInfo getDefaultDevice()
{
if (isPositiveAndBelow (index, lastQueriedConnectedDevices.get().size()))
return lastQueriedConnectedDevices.get()[index];
auto& lastDevices = lastQueriedConnectedDevices.get();
for (auto& d : lastDevices)
if (d.isDefault)
return { d.name, d.containerID };
return {};
}
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…
Cancel
Save