| @@ -52,9 +52,9 @@ | |||
| //============================================================================== | |||
| struct MidiDeviceListEntry : ReferenceCountedObject | |||
| { | |||
| MidiDeviceListEntry (const String& deviceName) : name (deviceName) {} | |||
| MidiDeviceListEntry (MidiDeviceInfo info) : deviceInfo (info) {} | |||
| String name; | |||
| MidiDeviceInfo deviceInfo; | |||
| std::unique_ptr<MidiInput> inDevice; | |||
| std::unique_ptr<MidiOutput> outDevice; | |||
| @@ -187,7 +187,7 @@ public: | |||
| if (isInput) | |||
| { | |||
| jassert (midiInputs[index]->inDevice.get() == nullptr); | |||
| midiInputs[index]->inDevice.reset (MidiInput::openDevice (index, this)); | |||
| midiInputs[index]->inDevice.reset (MidiInput::openDevice (midiInputs[index]->deviceInfo.identifier, this)); | |||
| if (midiInputs[index]->inDevice.get() == nullptr) | |||
| { | |||
| @@ -200,7 +200,7 @@ public: | |||
| else | |||
| { | |||
| jassert (midiOutputs[index]->outDevice.get() == nullptr); | |||
| midiOutputs[index]->outDevice.reset (MidiOutput::openDevice (index)); | |||
| midiOutputs[index]->outDevice.reset (MidiOutput::openDevice (midiOutputs[index]->deviceInfo.identifier)); | |||
| if (midiOutputs[index]->outDevice.get() == nullptr) | |||
| { | |||
| @@ -278,14 +278,14 @@ private: | |||
| if (isInput) | |||
| { | |||
| if (rowNumber < parent.getNumMidiInputs()) | |||
| g.drawText (parent.getMidiDevice (rowNumber, true)->name, | |||
| g.drawText (parent.getMidiDevice (rowNumber, true)->deviceInfo.name, | |||
| 5, 0, width, height, | |||
| Justification::centredLeft, true); | |||
| } | |||
| else | |||
| { | |||
| if (rowNumber < parent.getNumMidiOutputs()) | |||
| g.drawText (parent.getMidiDevice (rowNumber, false)->name, | |||
| g.drawText (parent.getMidiDevice (rowNumber, false)->deviceInfo.name, | |||
| 5, 0, width, height, | |||
| Justification::centredLeft, true); | |||
| } | |||
| @@ -368,34 +368,34 @@ private: | |||
| } | |||
| //============================================================================== | |||
| bool hasDeviceListChanged (const StringArray& deviceNames, bool isInputDevice) | |||
| bool hasDeviceListChanged (const Array<MidiDeviceInfo>& availableDevices, bool isInputDevice) | |||
| { | |||
| ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs | |||
| : midiOutputs; | |||
| if (deviceNames.size() != midiDevices.size()) | |||
| if (availableDevices.size() != midiDevices.size()) | |||
| return true; | |||
| for (auto i = 0; i < deviceNames.size(); ++i) | |||
| if (deviceNames[i] != midiDevices[i]->name) | |||
| for (auto i = 0; i < availableDevices.size(); ++i) | |||
| if (availableDevices[i] != midiDevices[i]->deviceInfo) | |||
| return true; | |||
| return false; | |||
| } | |||
| ReferenceCountedObjectPtr<MidiDeviceListEntry> findDeviceWithName (const String& name, bool isInputDevice) const | |||
| ReferenceCountedObjectPtr<MidiDeviceListEntry> findDevice (MidiDeviceInfo device, bool isInputDevice) const | |||
| { | |||
| const ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs | |||
| : midiOutputs; | |||
| for (auto midiDevice : midiDevices) | |||
| if (midiDevice->name == name) | |||
| return midiDevice; | |||
| for (auto& d : midiDevices) | |||
| if (d->deviceInfo == device) | |||
| return d; | |||
| return nullptr; | |||
| } | |||
| void closeUnpluggedDevices (StringArray& currentlyPluggedInDevices, bool isInputDevice) | |||
| void closeUnpluggedDevices (const Array<MidiDeviceInfo>& currentlyPluggedInDevices, bool isInputDevice) | |||
| { | |||
| ReferenceCountedArray<MidiDeviceListEntry>& midiDevices = isInputDevice ? midiInputs | |||
| : midiOutputs; | |||
| @@ -404,7 +404,7 @@ private: | |||
| { | |||
| auto& d = *midiDevices[i]; | |||
| if (! currentlyPluggedInDevices.contains (d.name)) | |||
| if (! currentlyPluggedInDevices.contains (d.deviceInfo)) | |||
| { | |||
| if (isInputDevice ? d.inDevice .get() != nullptr | |||
| : d.outDevice.get() != nullptr) | |||
| @@ -417,26 +417,26 @@ private: | |||
| void updateDeviceList (bool isInputDeviceList) | |||
| { | |||
| auto newDeviceNames = isInputDeviceList ? MidiInput::getDevices() | |||
| : MidiOutput::getDevices(); | |||
| auto availableDevices = isInputDeviceList ? MidiInput::getAvailableDevices() | |||
| : MidiOutput::getAvailableDevices(); | |||
| if (hasDeviceListChanged (newDeviceNames, isInputDeviceList)) | |||
| if (hasDeviceListChanged (availableDevices, isInputDeviceList)) | |||
| { | |||
| ReferenceCountedArray<MidiDeviceListEntry>& midiDevices | |||
| = isInputDeviceList ? midiInputs : midiOutputs; | |||
| closeUnpluggedDevices (newDeviceNames, isInputDeviceList); | |||
| closeUnpluggedDevices (availableDevices, isInputDeviceList); | |||
| ReferenceCountedArray<MidiDeviceListEntry> newDeviceList; | |||
| // add all currently plugged-in devices to the device list | |||
| for (auto newDeviceName : newDeviceNames) | |||
| for (auto& newDevice : availableDevices) | |||
| { | |||
| MidiDeviceListEntry::Ptr entry = findDeviceWithName (newDeviceName, isInputDeviceList); | |||
| MidiDeviceListEntry::Ptr entry = findDevice (newDevice, isInputDeviceList); | |||
| if (entry == nullptr) | |||
| entry = new MidiDeviceListEntry (newDeviceName); | |||
| entry = new MidiDeviceListEntry (newDevice); | |||
| newDeviceList.add (entry); | |||
| } | |||
| @@ -52,6 +52,9 @@ class ConsoleUnitTestRunner : public UnitTestRunner | |||
| //============================================================================== | |||
| int main (int argc, char **argv) | |||
| { | |||
| // Needed for tests that require a message thread | |||
| ScopedJuceInitialiser_GUI guiInitialiser; | |||
| ConsoleLogger logger; | |||
| Logger::setCurrentLogger (&logger); | |||
| @@ -179,7 +179,7 @@ | |||
| #include "audio_io/juce_AudioIODevice.cpp" | |||
| #include "audio_io/juce_AudioIODeviceType.cpp" | |||
| #include "midi_io/juce_MidiMessageCollector.cpp" | |||
| #include "midi_io/juce_MidiOutput.cpp" | |||
| #include "midi_io/juce_MidiDevices.cpp" | |||
| #include "sources/juce_AudioSourcePlayer.cpp" | |||
| #include "sources/juce_AudioTransportSource.cpp" | |||
| #include "native/juce_MidiDataConcatenator.h" | |||
| @@ -171,9 +171,8 @@ | |||
| #endif | |||
| //============================================================================== | |||
| #include "midi_io/juce_MidiInput.h" | |||
| #include "midi_io/juce_MidiDevices.h" | |||
| #include "midi_io/juce_MidiMessageCollector.h" | |||
| #include "midi_io/juce_MidiOutput.h" | |||
| #include "audio_io/juce_AudioIODevice.h" | |||
| #include "audio_io/juce_AudioIODeviceType.h" | |||
| #include "audio_io/juce_SystemAudioVolume.h" | |||
| @@ -23,18 +23,8 @@ | |||
| namespace juce | |||
| { | |||
| struct MidiOutput::PendingMessage | |||
| { | |||
| PendingMessage (const void* data, int len, double timeStamp) | |||
| : message (data, len, timeStamp) | |||
| {} | |||
| MidiMessage message; | |||
| PendingMessage* next; | |||
| }; | |||
| MidiOutput::MidiOutput (const String& deviceName) | |||
| : Thread ("midi out"), name (deviceName) | |||
| MidiOutput::MidiOutput (const String& deviceName, const String& deviceIdentifier) | |||
| : Thread ("midi out"), deviceInfo (deviceName, deviceIdentifier) | |||
| { | |||
| } | |||
| @@ -116,7 +106,7 @@ void MidiOutput::run() | |||
| { | |||
| while (! threadShouldExit()) | |||
| { | |||
| uint32 now = Time::getMillisecondCounter(); | |||
| auto now = Time::getMillisecondCounter(); | |||
| uint32 eventTime = 0; | |||
| uint32 timeToWait = 500; | |||
| @@ -167,4 +157,106 @@ void MidiOutput::run() | |||
| clearAllPendingMessages(); | |||
| } | |||
| #if JUCE_UNIT_TESTS | |||
| class MidiDevicesUnitTests : public UnitTest | |||
| { | |||
| public: | |||
| MidiDevicesUnitTests() : UnitTest ("MidiInput/MidiOutput", "MIDI/MPE") {} | |||
| void runTest() override | |||
| { | |||
| beginTest ("default device (input)"); | |||
| { | |||
| auto devices = MidiInput::getAvailableDevices(); | |||
| auto defaultDevice = MidiInput::getDefaultDevice(); | |||
| if (devices.size() == 0) | |||
| expect (defaultDevice == MidiDeviceInfo()); | |||
| else | |||
| expect (devices.contains (defaultDevice)); | |||
| } | |||
| beginTest ("default device (output)"); | |||
| { | |||
| auto devices = MidiOutput::getAvailableDevices(); | |||
| auto defaultDevice = MidiOutput::getDefaultDevice(); | |||
| if (devices.size() == 0) | |||
| expect (defaultDevice == MidiDeviceInfo()); | |||
| else | |||
| expect (devices.contains (defaultDevice)); | |||
| } | |||
| #if JUCE_MAC || JUCE_LINUX || JUCE_IOS | |||
| String testDeviceName ("TestDevice"); | |||
| String testDeviceName2 ("TestDevice2"); | |||
| struct MessageCallbackHandler : public MidiInputCallback | |||
| { | |||
| void handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) override | |||
| { | |||
| messageSource = source; | |||
| messageReceived = message; | |||
| } | |||
| MidiInput* messageSource = nullptr; | |||
| MidiMessage messageReceived; | |||
| }; | |||
| MessageCallbackHandler handler; | |||
| beginTest ("create device (input)"); | |||
| { | |||
| std::unique_ptr<MidiInput> device (MidiInput::createNewDevice (testDeviceName, &handler)); | |||
| expect (device.get() != nullptr); | |||
| expect (device->getName() == testDeviceName); | |||
| device->setName (testDeviceName2); | |||
| expect (device->getName() == testDeviceName2); | |||
| } | |||
| beginTest ("create device (output)"); | |||
| { | |||
| std::unique_ptr<MidiOutput> device (MidiOutput::createNewDevice (testDeviceName)); | |||
| expect (device.get() != nullptr); | |||
| expect (device->getName() == testDeviceName); | |||
| } | |||
| auto testMessage = MidiMessage::noteOn (5, 12, (uint8) 51); | |||
| beginTest ("send messages"); | |||
| { | |||
| std::unique_ptr<MidiInput> midiInput (MidiInput::createNewDevice (testDeviceName, &handler)); | |||
| expect (midiInput.get() != nullptr); | |||
| midiInput->start(); | |||
| auto inputInfo = midiInput->getDeviceInfo(); | |||
| expect (MidiOutput::getAvailableDevices().contains (inputInfo)); | |||
| std::unique_ptr<MidiOutput> midiOutput (MidiOutput::openDevice (midiInput->getIdentifier())); | |||
| expect (midiOutput.get() != nullptr); | |||
| midiOutput->sendMessageNow (testMessage); | |||
| // Pump the message thread for a bit to allow the message to be delivered | |||
| MessageManager::getInstance()->runDispatchLoopUntil (100); | |||
| expect (handler.messageSource == midiInput.get()); | |||
| expect (handler.messageReceived.getChannel() == testMessage.getChannel()); | |||
| expect (handler.messageReceived.getNoteNumber() == testMessage.getNoteNumber()); | |||
| expect (handler.messageReceived.getVelocity() == testMessage.getVelocity()); | |||
| midiInput->stop(); | |||
| } | |||
| #endif | |||
| } | |||
| }; | |||
| static MidiDevicesUnitTests MidiDevicesUnitTests; | |||
| #endif | |||
| } // namespace juce | |||
| @@ -0,0 +1,365 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| This struct contains information about a MIDI input or output device. | |||
| You can get one of these structs by calling the static getAvailableDevices() or | |||
| getDefaultDevice() methods of MidiInput and MidiOutput or by calling getDeviceInfo() | |||
| on an instance of these classes. Devices can be opened by passing the identifier to | |||
| the openDevice() method. | |||
| */ | |||
| struct MidiDeviceInfo | |||
| { | |||
| MidiDeviceInfo() = default; | |||
| MidiDeviceInfo (const String& deviceName, const String& deviceIdentifier) | |||
| : name (deviceName), identifier (deviceIdentifier) | |||
| { | |||
| } | |||
| /** The name of this device. | |||
| This will be provided by the OS unless the device has been created with the | |||
| createNewDevice() method. | |||
| Note that the name is not guaranteed to be unique and two devices with the | |||
| same name will be indistinguishable. If you want to address a specific device | |||
| it is better to use the identifier. | |||
| */ | |||
| String name; | |||
| /** The identifier for this device. | |||
| This will be provided by the OS and it's format will differ on different systems | |||
| e.g. on macOS it will be a number whereas on Windows it will be a long alphanumeric string. | |||
| */ | |||
| String identifier; | |||
| //============================================================================== | |||
| bool operator== (const MidiDeviceInfo& other) const noexcept { return name == other.name && identifier == other.identifier; } | |||
| bool operator!= (const MidiDeviceInfo& other) const noexcept { return ! operator== (other); } | |||
| }; | |||
| class MidiInputCallback; | |||
| //============================================================================== | |||
| /** | |||
| Represents a midi input device. | |||
| To create one of these, use the static getAvailableDevices() method to find out what | |||
| inputs are available, and then use the openDevice() method to try to open one. | |||
| @see MidiOutput | |||
| @tags{Audio} | |||
| */ | |||
| class JUCE_API MidiInput final | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns a list of the available midi input devices. | |||
| You can open one of the devices by passing its identifier into the openDevice() method. | |||
| @see MidiDeviceInfo, getDevices, getDefaultDeviceIndex, openDevice | |||
| */ | |||
| static Array<MidiDeviceInfo> getAvailableDevices(); | |||
| /** Returns the MidiDeviceInfo of the default midi input device to use. */ | |||
| static MidiDeviceInfo getDefaultDevice(); | |||
| /** Tries to open one of the midi input devices. | |||
| This will return a MidiInput object if it manages to open it. You can then | |||
| call start() and stop() on this device, and delete it when no longer needed. | |||
| If the device can't be opened, this will return nullptr. | |||
| @param deviceIdentifier the ID of the device to open - use the getAvailableDevices() method to | |||
| find the available devices that can be opened | |||
| @param callback the object that will receive the midi messages from this device | |||
| @see MidiInputCallback, getDevices | |||
| */ | |||
| static MidiInput* openDevice (const String& deviceIdentifier, MidiInputCallback* callback); | |||
| #if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||
| /** This will try to create a new midi input device (only available on Linux, macOS and iOS). | |||
| This will attempt to create a new midi input device with the specified name for other | |||
| apps to connect to. | |||
| Returns nullptr if a device can't be created. | |||
| @param deviceName the name of the device to create | |||
| @param callback the object that will receive the midi messages from this device | |||
| */ | |||
| static MidiInput* createNewDevice (const String& deviceName, MidiInputCallback* callback); | |||
| #endif | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| ~MidiInput(); | |||
| /** Starts the device running. | |||
| After calling this, the device will start sending midi messages to the MidiInputCallback | |||
| object that was specified when the openDevice() method was called. | |||
| @see stop | |||
| */ | |||
| void start(); | |||
| /** Stops the device running. | |||
| @see start | |||
| */ | |||
| void stop(); | |||
| /** Returns the MidiDeviceInfo struct containing some information about this device. */ | |||
| MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; } | |||
| /** Returns the identifier of this device. */ | |||
| String getIdentifier() const noexcept { return deviceInfo.identifier; } | |||
| /** Returns the name of this device. */ | |||
| String getName() const noexcept { return deviceInfo.name; } | |||
| /** Sets a custom name for the device. */ | |||
| void setName (const String& newName) noexcept { deviceInfo.name = newName; } | |||
| //============================================================================== | |||
| /** Deprecated. */ | |||
| static StringArray getDevices(); | |||
| /** Deprecated. */ | |||
| static int getDefaultDeviceIndex(); | |||
| /** Deprecated. */ | |||
| static MidiInput* openDevice (int, MidiInputCallback*); | |||
| private: | |||
| //============================================================================== | |||
| explicit MidiInput (const String&, const String&); | |||
| MidiDeviceInfo deviceInfo; | |||
| void* internal = nullptr; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput) | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| Receives incoming messages from a physical MIDI input device. | |||
| This class is overridden to handle incoming midi messages. See the MidiInput | |||
| class for more details. | |||
| @see MidiInput | |||
| @tags{Audio} | |||
| */ | |||
| class JUCE_API MidiInputCallback | |||
| { | |||
| public: | |||
| /** Destructor. */ | |||
| virtual ~MidiInputCallback() = default; | |||
| /** Receives an incoming message. | |||
| A MidiInput object will call this method when a midi event arrives. It'll be | |||
| called on a high-priority system thread, so avoid doing anything time-consuming | |||
| in here, and avoid making any UI calls. You might find the MidiBuffer class helpful | |||
| for queueing incoming messages for use later. | |||
| @param source the MidiInput object that generated the message | |||
| @param message the incoming message. The message's timestamp is set to a value | |||
| equivalent to (Time::getMillisecondCounter() / 1000.0) to specify the | |||
| time when the message arrived | |||
| */ | |||
| virtual void handleIncomingMidiMessage (MidiInput* source, | |||
| const MidiMessage& message) = 0; | |||
| /** Notification sent each time a packet of a multi-packet sysex message arrives. | |||
| If a long sysex message is broken up into multiple packets, this callback is made | |||
| for each packet that arrives until the message is finished, at which point | |||
| the normal handleIncomingMidiMessage() callback will be made with the entire | |||
| message. | |||
| The message passed in will contain the start of a sysex, but won't be finished | |||
| with the terminating 0xf7 byte. | |||
| */ | |||
| virtual void handlePartialSysexMessage (MidiInput* source, | |||
| const uint8* messageData, | |||
| int numBytesSoFar, | |||
| double timestamp) | |||
| { | |||
| ignoreUnused (source, messageData, numBytesSoFar, timestamp); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| Represents a midi output device. | |||
| To create one of these, use the static getAvailableDevices() method to find out what | |||
| outputs are available, and then use the openDevice() method to try to open one. | |||
| @see MidiInput | |||
| @tags{Audio} | |||
| */ | |||
| class JUCE_API MidiOutput final : private Thread | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns a list of the available midi output devices. | |||
| You can open one of the devices by passing its identifier into the openDevice() method. | |||
| @see MidiDeviceInfo, getDevices, getDefaultDeviceIndex, openDevice | |||
| */ | |||
| static Array<MidiDeviceInfo> getAvailableDevices(); | |||
| /** Returns the MidiDeviceInfo of the default midi output device to use. */ | |||
| static MidiDeviceInfo getDefaultDevice(); | |||
| /** Tries to open one of the midi output devices. | |||
| This will return a MidiOutput object if it manages to open it. You can then | |||
| send messages to this device, and delete it when no longer needed. | |||
| If the device can't be opened, this will return nullptr. | |||
| @param deviceIdentifier the ID of the device to open - use the getAvailableDevices() method to | |||
| find the available devices that can be opened | |||
| @see getDevices | |||
| */ | |||
| static MidiOutput* openDevice (const String& deviceIdentifier); | |||
| #if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||
| /** This will try to create a new midi output device (only available on Linux, macOS and iOS). | |||
| This will attempt to create a new midi output device with the specified name that other | |||
| apps can connect to and use as their midi input. | |||
| Returns nullptr if a device can't be created. | |||
| @param deviceName the name of the device to create | |||
| */ | |||
| static MidiOutput* createNewDevice (const String& deviceName); | |||
| #endif | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| ~MidiOutput() override; | |||
| /** Returns the MidiDeviceInfo struct containing some information about this device. */ | |||
| MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; } | |||
| /** Returns the identifier of this device. */ | |||
| String getIdentifier() const noexcept { return deviceInfo.identifier; } | |||
| /** Returns the name of this device. */ | |||
| String getName() const noexcept { return deviceInfo.name; } | |||
| /** Sets a custom name for the device. */ | |||
| void setName (const String& newName) noexcept { deviceInfo.name = newName; } | |||
| //============================================================================== | |||
| /** Sends out a MIDI message immediately. */ | |||
| void sendMessageNow (const MidiMessage& message); | |||
| /** Sends out a sequence of MIDI messages immediately. */ | |||
| void sendBlockOfMessagesNow (const MidiBuffer& buffer); | |||
| /** This lets you supply a block of messages that will be sent out at some point | |||
| in the future. | |||
| The MidiOutput class has an internal thread that can send out timestamped | |||
| messages - this appends a set of messages to its internal buffer, ready for | |||
| sending. | |||
| This will only work if you've already started the thread with startBackgroundThread(). | |||
| A time is specified, at which the block of messages should be sent. This time uses | |||
| the same time base as Time::getMillisecondCounter(), and must be in the future. | |||
| The samplesPerSecondForBuffer parameter indicates the number of samples per second | |||
| used by the MidiBuffer. Each event in a MidiBuffer has a sample position, and the | |||
| samplesPerSecondForBuffer value is needed to convert this sample position to a | |||
| real time. | |||
| */ | |||
| void sendBlockOfMessages (const MidiBuffer& buffer, | |||
| double millisecondCounterToStartAt, | |||
| double samplesPerSecondForBuffer); | |||
| /** Gets rid of any midi messages that had been added by sendBlockOfMessages(). */ | |||
| void clearAllPendingMessages(); | |||
| /** Starts up a background thread so that the device can send blocks of data. | |||
| Call this to get the device ready, before using sendBlockOfMessages(). | |||
| */ | |||
| void startBackgroundThread(); | |||
| /** Stops the background thread, and clears any pending midi events. | |||
| @see startBackgroundThread | |||
| */ | |||
| void stopBackgroundThread(); | |||
| //============================================================================== | |||
| /** Deprecated. */ | |||
| static StringArray getDevices(); | |||
| /** Deprecated. */ | |||
| static int getDefaultDeviceIndex(); | |||
| /** Deprecated. */ | |||
| static MidiOutput* openDevice (int); | |||
| private: | |||
| //============================================================================== | |||
| struct PendingMessage | |||
| { | |||
| PendingMessage (const void* data, int len, double timeStamp) | |||
| : message (data, len, timeStamp) | |||
| { | |||
| } | |||
| MidiMessage message; | |||
| PendingMessage* next; | |||
| }; | |||
| //============================================================================== | |||
| explicit MidiOutput (const String&, const String&); | |||
| void run() override; | |||
| MidiDeviceInfo deviceInfo; | |||
| void* internal = nullptr; | |||
| CriticalSection lock; | |||
| PendingMessage* firstMessage = nullptr; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput) | |||
| }; | |||
| } // namespace juce | |||
| @@ -1,180 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| class MidiInput; | |||
| //============================================================================== | |||
| /** | |||
| Receives incoming messages from a physical MIDI input device. | |||
| This class is overridden to handle incoming midi messages. See the MidiInput | |||
| class for more details. | |||
| @see MidiInput | |||
| @tags{Audio} | |||
| */ | |||
| class JUCE_API MidiInputCallback | |||
| { | |||
| public: | |||
| /** Destructor. */ | |||
| virtual ~MidiInputCallback() = default; | |||
| /** Receives an incoming message. | |||
| A MidiInput object will call this method when a midi event arrives. It'll be | |||
| called on a high-priority system thread, so avoid doing anything time-consuming | |||
| in here, and avoid making any UI calls. You might find the MidiBuffer class helpful | |||
| for queueing incoming messages for use later. | |||
| @param source the MidiInput object that generated the message | |||
| @param message the incoming message. The message's timestamp is set to a value | |||
| equivalent to (Time::getMillisecondCounter() / 1000.0) to specify the | |||
| time when the message arrived. | |||
| */ | |||
| virtual void handleIncomingMidiMessage (MidiInput* source, | |||
| const MidiMessage& message) = 0; | |||
| /** Notification sent each time a packet of a multi-packet sysex message arrives. | |||
| If a long sysex message is broken up into multiple packets, this callback is made | |||
| for each packet that arrives until the message is finished, at which point | |||
| the normal handleIncomingMidiMessage() callback will be made with the entire | |||
| message. | |||
| The message passed in will contain the start of a sysex, but won't be finished | |||
| with the terminating 0xf7 byte. | |||
| */ | |||
| virtual void handlePartialSysexMessage (MidiInput* source, | |||
| const uint8* messageData, | |||
| int numBytesSoFar, | |||
| double timestamp) | |||
| { | |||
| ignoreUnused (source, messageData, numBytesSoFar, timestamp); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| Represents a midi input device. | |||
| To create one of these, use the static getDevices() method to find out what inputs are | |||
| available, and then use the openDevice() method to try to open one. | |||
| @see MidiOutput | |||
| @tags{Audio} | |||
| */ | |||
| class JUCE_API MidiInput final | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns a list of the available midi input devices. | |||
| You can open one of the devices by passing its index into the | |||
| openDevice() method. | |||
| @see getDefaultDeviceIndex, openDevice | |||
| */ | |||
| static StringArray getDevices(); | |||
| /** Returns the index of the default midi input device to use. | |||
| This refers to the index in the list returned by getDevices(). | |||
| */ | |||
| static int getDefaultDeviceIndex(); | |||
| /** Tries to open one of the midi input devices. | |||
| This will return a MidiInput object if it manages to open it. You can then | |||
| call start() and stop() on this device, and delete it when no longer needed. | |||
| If the device can't be opened, this will return a null pointer. | |||
| @param deviceIndex the index of a device from the list returned by getDevices() | |||
| @param callback the object that will receive the midi messages from this device. | |||
| @see MidiInputCallback, getDevices | |||
| */ | |||
| static MidiInput* openDevice (int deviceIndex, | |||
| MidiInputCallback* callback); | |||
| #if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||
| /** This will try to create a new midi input device (Not available on Windows). | |||
| This will attempt to create a new midi input device with the specified name, | |||
| for other apps to connect to. | |||
| Returns nullptr if a device can't be created. | |||
| @param deviceName the name to use for the new device | |||
| @param callback the object that will receive the midi messages from this device. | |||
| */ | |||
| static MidiInput* createNewDevice (const String& deviceName, | |||
| MidiInputCallback* callback); | |||
| #endif | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| ~MidiInput(); | |||
| /** Returns the name of this device. */ | |||
| const String& getName() const noexcept { return name; } | |||
| /** Allows you to set a custom name for the device, in case you don't like the name | |||
| it was given when created. | |||
| */ | |||
| void setName (const String& newName) noexcept { name = newName; } | |||
| //============================================================================== | |||
| /** Starts the device running. | |||
| After calling this, the device will start sending midi messages to the | |||
| MidiInputCallback object that was specified when the openDevice() method | |||
| was called. | |||
| @see stop | |||
| */ | |||
| void start(); | |||
| /** Stops the device running. | |||
| @see start | |||
| */ | |||
| void stop(); | |||
| private: | |||
| //============================================================================== | |||
| String name; | |||
| void* internal = nullptr; | |||
| // The input objects are created with the openDevice() method. | |||
| explicit MidiInput (const String&); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput) | |||
| }; | |||
| } // namespace juce | |||
| @@ -1,145 +0,0 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| The code included in this file is provided under the terms of the ISC license | |||
| http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||
| To use, copy, modify, and/or distribute this software for any purpose with or | |||
| without fee is hereby granted provided that the above copyright notice and | |||
| this permission notice appear in all copies. | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| Controls a physical MIDI output device. | |||
| To create one of these, use the static getDevices() method to get a list of the | |||
| available output devices, then use the openDevice() method to try to open one. | |||
| @see MidiInput | |||
| @tags{Audio} | |||
| */ | |||
| class JUCE_API MidiOutput final : private Thread | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Returns a list of the available midi output devices. | |||
| You can open one of the devices by passing its index into the | |||
| openDevice() method. | |||
| @see getDefaultDeviceIndex, openDevice | |||
| */ | |||
| static StringArray getDevices(); | |||
| /** Returns the index of the default midi output device to use. | |||
| This refers to the index in the list returned by getDevices(). | |||
| */ | |||
| static int getDefaultDeviceIndex(); | |||
| /** Tries to open one of the midi output devices. | |||
| This will return a MidiOutput object if it manages to open it. You can then | |||
| send messages to this device, and delete it when no longer needed. | |||
| If the device can't be opened, this will return a null pointer. | |||
| @param deviceIndex the index of a device from the list returned by getDevices() | |||
| @see getDevices | |||
| */ | |||
| static MidiOutput* openDevice (int deviceIndex); | |||
| #if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN | |||
| /** This will try to create a new midi output device (Not available on Windows). | |||
| This will attempt to create a new midi output device that other apps can connect | |||
| to and use as their midi input. | |||
| Returns nullptr if a device can't be created. | |||
| @param deviceName the name to use for the new device | |||
| */ | |||
| static MidiOutput* createNewDevice (const String& deviceName); | |||
| #endif | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| ~MidiOutput() override; | |||
| /** Returns the name of this device. */ | |||
| const String& getName() const noexcept { return name; } | |||
| /** Sends out a MIDI message immediately. */ | |||
| void sendMessageNow (const MidiMessage& message); | |||
| /** Sends out a sequence of MIDI messages immediately. */ | |||
| void sendBlockOfMessagesNow (const MidiBuffer& buffer); | |||
| //============================================================================== | |||
| /** This lets you supply a block of messages that will be sent out at some point | |||
| in the future. | |||
| The MidiOutput class has an internal thread that can send out timestamped | |||
| messages - this appends a set of messages to its internal buffer, ready for | |||
| sending. | |||
| This will only work if you've already started the thread with startBackgroundThread(). | |||
| A time is specified, at which the block of messages should be sent. This time uses | |||
| the same time base as Time::getMillisecondCounter(), and must be in the future. | |||
| The samplesPerSecondForBuffer parameter indicates the number of samples per second | |||
| used by the MidiBuffer. Each event in a MidiBuffer has a sample position, and the | |||
| samplesPerSecondForBuffer value is needed to convert this sample position to a | |||
| real time. | |||
| */ | |||
| void sendBlockOfMessages (const MidiBuffer& buffer, | |||
| double millisecondCounterToStartAt, | |||
| double samplesPerSecondForBuffer); | |||
| /** Gets rid of any midi messages that had been added by sendBlockOfMessages(). */ | |||
| void clearAllPendingMessages(); | |||
| /** Starts up a background thread so that the device can send blocks of data. | |||
| Call this to get the device ready, before using sendBlockOfMessages(). | |||
| */ | |||
| void startBackgroundThread(); | |||
| /** Stops the background thread, and clears any pending midi events. | |||
| @see startBackgroundThread | |||
| */ | |||
| void stopBackgroundThread(); | |||
| private: | |||
| //============================================================================== | |||
| void* internal = nullptr; | |||
| CriticalSection lock; | |||
| struct PendingMessage; | |||
| PendingMessage* firstMessage = nullptr; | |||
| String name; | |||
| MidiOutput (const String& midiName); // These objects are created with the openDevice() method. | |||
| void run() override; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput) | |||
| }; | |||
| } // namespace juce | |||
| @@ -51,6 +51,8 @@ public class JuceMidiSupport | |||
| // send will do nothing on an input port | |||
| void sendMidi (byte[] msg, int offset, int count); | |||
| String getName (); | |||
| } | |||
| //============================================================================== | |||
| @@ -256,6 +258,12 @@ public class JuceMidiSupport | |||
| { | |||
| } | |||
| @Override | |||
| public String getName () | |||
| { | |||
| return owner.getPortName (portPath); | |||
| } | |||
| MidiDeviceManager owner; | |||
| MidiOutputPort androidPort; | |||
| MidiPortPath portPath; | |||
| @@ -331,6 +339,12 @@ public class JuceMidiSupport | |||
| androidPort = null; | |||
| } | |||
| @Override | |||
| public String getName () | |||
| { | |||
| return owner.getPortName (portPath); | |||
| } | |||
| MidiDeviceManager owner; | |||
| MidiInputPort androidPort; | |||
| MidiPortPath portPath; | |||
| @@ -343,7 +357,6 @@ public class JuceMidiSupport | |||
| deviceId = deviceIdToUse; | |||
| isInput = direction; | |||
| portIndex = androidIndex; | |||
| } | |||
| public int deviceId; | |||
| @@ -555,17 +568,17 @@ public class JuceMidiSupport | |||
| super.finalize (); | |||
| } | |||
| public String[] getJuceAndroidMidiInputDevices () | |||
| public String[] getJuceAndroidMidiOutputDeviceNameAndIDs () | |||
| { | |||
| return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); | |||
| return getJuceAndroidMidiDeviceNameAndIDs (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); | |||
| } | |||
| public String[] getJuceAndroidMidiOutputDevices () | |||
| public String[] getJuceAndroidMidiInputDeviceNameAndIDs () | |||
| { | |||
| return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); | |||
| return getJuceAndroidMidiDeviceNameAndIDs (MidiDeviceInfo.PortInfo.TYPE_INPUT); | |||
| } | |||
| private String[] getJuceAndroidMidiDevices (int portType) | |||
| private String[] getJuceAndroidMidiDeviceNameAndIDs (int portType) | |||
| { | |||
| // only update the list when JUCE asks for a new list | |||
| synchronized (MidiDeviceManager.class) | |||
| @@ -573,22 +586,24 @@ public class JuceMidiSupport | |||
| deviceInfos = getDeviceInfos (); | |||
| } | |||
| ArrayList<String> portNames = new ArrayList<String> (); | |||
| ArrayList<String> portNameAndIDs = new ArrayList<String> (); | |||
| int index = 0; | |||
| for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index)) | |||
| portNames.add (getPortName (portInfo)); | |||
| for (MidiPortPath portInfo : getAllPorts (portType)) | |||
| { | |||
| portNameAndIDs.add (getPortName (portInfo)); | |||
| portNameAndIDs.add (Integer.toString (portInfo.hashCode ())); | |||
| } | |||
| String[] names = new String[portNames.size ()]; | |||
| return portNames.toArray (names); | |||
| String[] names = new String[portNameAndIDs.size ()]; | |||
| return portNameAndIDs.toArray (names); | |||
| } | |||
| private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput) | |||
| private JuceMidiPort openMidiPortWithID (int deviceID, long host, boolean isInput) | |||
| { | |||
| synchronized (MidiDeviceManager.class) | |||
| { | |||
| int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT); | |||
| MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index); | |||
| int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT); | |||
| MidiPortPath portInfo = getPortPathForID (portTypeToFind, deviceID); | |||
| if (portInfo != null) | |||
| { | |||
| @@ -633,14 +648,14 @@ public class JuceMidiSupport | |||
| return null; | |||
| } | |||
| public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) | |||
| public JuceMidiPort openMidiInputPortWithID (int deviceID, long host) | |||
| { | |||
| return openMidiPortWithJuceIndex (index, host, true); | |||
| return openMidiPortWithID (deviceID, host, true); | |||
| } | |||
| public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) | |||
| public JuceMidiPort openMidiOutputPortWithID (int deviceID) | |||
| { | |||
| return openMidiPortWithJuceIndex (index, 0, false); | |||
| return openMidiPortWithID (deviceID, 0, false); | |||
| } | |||
| /* 0: unpaired, 1: paired, 2: pairing */ | |||
| @@ -773,24 +788,6 @@ public class JuceMidiSupport | |||
| openPorts.remove (path); | |||
| } | |||
| public String getInputPortNameForJuceIndex (int index) | |||
| { | |||
| MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index); | |||
| if (portInfo != null) | |||
| return getPortName (portInfo); | |||
| return ""; | |||
| } | |||
| public String getOutputPortNameForJuceIndex (int index) | |||
| { | |||
| MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index); | |||
| if (portInfo != null) | |||
| return getPortName (portInfo); | |||
| return ""; | |||
| } | |||
| public void onDeviceAdded (MidiDeviceInfo info) | |||
| { | |||
| // only add standard midi devices | |||
| @@ -980,24 +977,24 @@ public class JuceMidiSupport | |||
| return ""; | |||
| } | |||
| public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex) | |||
| public ArrayList<MidiPortPath> getAllPorts (int portType) | |||
| { | |||
| int portIdx = 0; | |||
| 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; | |||
| } | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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 | |||
| @@ -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(); | |||