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