From 09ebd1d257614571c47af0d08a5f90ba01a7f5c0 Mon Sep 17 00:00:00 2001 From: ed Date: Thu, 28 Feb 2019 16:29:54 +0000 Subject: [PATCH] Added support for ID-based MIDI devices --- examples/Audio/MidiDemo.h | 46 +- extras/UnitTestRunner/Source/Main.cpp | 3 + .../juce_audio_devices/juce_audio_devices.cpp | 2 +- .../juce_audio_devices/juce_audio_devices.h | 3 +- ...ce_MidiOutput.cpp => juce_MidiDevices.cpp} | 118 ++- .../midi_io/juce_MidiDevices.h | 365 +++++++ .../midi_io/juce_MidiInput.h | 180 ---- .../midi_io/juce_MidiOutput.h | 145 --- .../app/com/roli/juce/JuceMidiSupport.java | 97 +- .../native/juce_android_Midi.cpp | 888 +++++++++--------- .../native/juce_linux_Bela.cpp | 95 +- .../native/juce_linux_Midi.cpp | 254 +++-- .../native/juce_mac_CoreMidi.cpp | 529 ++++++----- .../native/juce_win32_Midi.cpp | 401 +++++--- 14 files changed, 1798 insertions(+), 1328 deletions(-) rename modules/juce_audio_devices/midi_io/{juce_MidiOutput.cpp => juce_MidiDevices.cpp} (50%) create mode 100644 modules/juce_audio_devices/midi_io/juce_MidiDevices.h delete mode 100644 modules/juce_audio_devices/midi_io/juce_MidiInput.h delete mode 100644 modules/juce_audio_devices/midi_io/juce_MidiOutput.h diff --git a/examples/Audio/MidiDemo.h b/examples/Audio/MidiDemo.h index c12776c706..ea3e8fcfb0 100644 --- a/examples/Audio/MidiDemo.h +++ b/examples/Audio/MidiDemo.h @@ -52,9 +52,9 @@ //============================================================================== struct MidiDeviceListEntry : ReferenceCountedObject { - MidiDeviceListEntry (const String& deviceName) : name (deviceName) {} + MidiDeviceListEntry (MidiDeviceInfo info) : deviceInfo (info) {} - String name; + MidiDeviceInfo deviceInfo; std::unique_ptr inDevice; std::unique_ptr outDevice; @@ -187,7 +187,7 @@ public: if (isInput) { jassert (midiInputs[index]->inDevice.get() == nullptr); - midiInputs[index]->inDevice.reset (MidiInput::openDevice (index, this)); + midiInputs[index]->inDevice.reset (MidiInput::openDevice (midiInputs[index]->deviceInfo.identifier, this)); if (midiInputs[index]->inDevice.get() == nullptr) { @@ -200,7 +200,7 @@ public: else { jassert (midiOutputs[index]->outDevice.get() == nullptr); - midiOutputs[index]->outDevice.reset (MidiOutput::openDevice (index)); + midiOutputs[index]->outDevice.reset (MidiOutput::openDevice (midiOutputs[index]->deviceInfo.identifier)); if (midiOutputs[index]->outDevice.get() == nullptr) { @@ -278,14 +278,14 @@ private: if (isInput) { if (rowNumber < parent.getNumMidiInputs()) - g.drawText (parent.getMidiDevice (rowNumber, true)->name, + g.drawText (parent.getMidiDevice (rowNumber, true)->deviceInfo.name, 5, 0, width, height, Justification::centredLeft, true); } else { if (rowNumber < parent.getNumMidiOutputs()) - g.drawText (parent.getMidiDevice (rowNumber, false)->name, + g.drawText (parent.getMidiDevice (rowNumber, false)->deviceInfo.name, 5, 0, width, height, Justification::centredLeft, true); } @@ -368,34 +368,34 @@ private: } //============================================================================== - bool hasDeviceListChanged (const StringArray& deviceNames, bool isInputDevice) + bool hasDeviceListChanged (const Array& availableDevices, bool isInputDevice) { ReferenceCountedArray& midiDevices = isInputDevice ? midiInputs : midiOutputs; - if (deviceNames.size() != midiDevices.size()) + if (availableDevices.size() != midiDevices.size()) return true; - for (auto i = 0; i < deviceNames.size(); ++i) - if (deviceNames[i] != midiDevices[i]->name) + for (auto i = 0; i < availableDevices.size(); ++i) + if (availableDevices[i] != midiDevices[i]->deviceInfo) return true; return false; } - ReferenceCountedObjectPtr findDeviceWithName (const String& name, bool isInputDevice) const + ReferenceCountedObjectPtr findDevice (MidiDeviceInfo device, bool isInputDevice) const { const ReferenceCountedArray& midiDevices = isInputDevice ? midiInputs : midiOutputs; - for (auto midiDevice : midiDevices) - if (midiDevice->name == name) - return midiDevice; + for (auto& d : midiDevices) + if (d->deviceInfo == device) + return d; return nullptr; } - void closeUnpluggedDevices (StringArray& currentlyPluggedInDevices, bool isInputDevice) + void closeUnpluggedDevices (const Array& currentlyPluggedInDevices, bool isInputDevice) { ReferenceCountedArray& midiDevices = isInputDevice ? midiInputs : midiOutputs; @@ -404,7 +404,7 @@ private: { auto& d = *midiDevices[i]; - if (! currentlyPluggedInDevices.contains (d.name)) + if (! currentlyPluggedInDevices.contains (d.deviceInfo)) { if (isInputDevice ? d.inDevice .get() != nullptr : d.outDevice.get() != nullptr) @@ -417,26 +417,26 @@ private: void updateDeviceList (bool isInputDeviceList) { - auto newDeviceNames = isInputDeviceList ? MidiInput::getDevices() - : MidiOutput::getDevices(); + auto availableDevices = isInputDeviceList ? MidiInput::getAvailableDevices() + : MidiOutput::getAvailableDevices(); - if (hasDeviceListChanged (newDeviceNames, isInputDeviceList)) + if (hasDeviceListChanged (availableDevices, isInputDeviceList)) { ReferenceCountedArray& midiDevices = isInputDeviceList ? midiInputs : midiOutputs; - closeUnpluggedDevices (newDeviceNames, isInputDeviceList); + closeUnpluggedDevices (availableDevices, isInputDeviceList); ReferenceCountedArray newDeviceList; // add all currently plugged-in devices to the device list - for (auto newDeviceName : newDeviceNames) + for (auto& newDevice : availableDevices) { - MidiDeviceListEntry::Ptr entry = findDeviceWithName (newDeviceName, isInputDeviceList); + MidiDeviceListEntry::Ptr entry = findDevice (newDevice, isInputDeviceList); if (entry == nullptr) - entry = new MidiDeviceListEntry (newDeviceName); + entry = new MidiDeviceListEntry (newDevice); newDeviceList.add (entry); } diff --git a/extras/UnitTestRunner/Source/Main.cpp b/extras/UnitTestRunner/Source/Main.cpp index 18e5fcdcc9..cc76eff772 100644 --- a/extras/UnitTestRunner/Source/Main.cpp +++ b/extras/UnitTestRunner/Source/Main.cpp @@ -52,6 +52,9 @@ class ConsoleUnitTestRunner : public UnitTestRunner //============================================================================== int main (int argc, char **argv) { + // Needed for tests that require a message thread + ScopedJuceInitialiser_GUI guiInitialiser; + ConsoleLogger logger; Logger::setCurrentLogger (&logger); diff --git a/modules/juce_audio_devices/juce_audio_devices.cpp b/modules/juce_audio_devices/juce_audio_devices.cpp index 11636db0b7..d6e1e99076 100644 --- a/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/modules/juce_audio_devices/juce_audio_devices.cpp @@ -179,7 +179,7 @@ #include "audio_io/juce_AudioIODevice.cpp" #include "audio_io/juce_AudioIODeviceType.cpp" #include "midi_io/juce_MidiMessageCollector.cpp" -#include "midi_io/juce_MidiOutput.cpp" +#include "midi_io/juce_MidiDevices.cpp" #include "sources/juce_AudioSourcePlayer.cpp" #include "sources/juce_AudioTransportSource.cpp" #include "native/juce_MidiDataConcatenator.h" diff --git a/modules/juce_audio_devices/juce_audio_devices.h b/modules/juce_audio_devices/juce_audio_devices.h index 7b55910cc5..9c4868f56f 100644 --- a/modules/juce_audio_devices/juce_audio_devices.h +++ b/modules/juce_audio_devices/juce_audio_devices.h @@ -171,9 +171,8 @@ #endif //============================================================================== -#include "midi_io/juce_MidiInput.h" +#include "midi_io/juce_MidiDevices.h" #include "midi_io/juce_MidiMessageCollector.h" -#include "midi_io/juce_MidiOutput.h" #include "audio_io/juce_AudioIODevice.h" #include "audio_io/juce_AudioIODeviceType.h" #include "audio_io/juce_SystemAudioVolume.h" diff --git a/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp b/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp similarity index 50% rename from modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp rename to modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp index a707997aa0..552af79eb7 100644 --- a/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp +++ b/modules/juce_audio_devices/midi_io/juce_MidiDevices.cpp @@ -23,18 +23,8 @@ namespace juce { -struct MidiOutput::PendingMessage -{ - PendingMessage (const void* data, int len, double timeStamp) - : message (data, len, timeStamp) - {} - - MidiMessage message; - PendingMessage* next; -}; - -MidiOutput::MidiOutput (const String& deviceName) - : Thread ("midi out"), name (deviceName) +MidiOutput::MidiOutput (const String& deviceName, const String& deviceIdentifier) + : Thread ("midi out"), deviceInfo (deviceName, deviceIdentifier) { } @@ -116,7 +106,7 @@ void MidiOutput::run() { while (! threadShouldExit()) { - uint32 now = Time::getMillisecondCounter(); + auto now = Time::getMillisecondCounter(); uint32 eventTime = 0; uint32 timeToWait = 500; @@ -167,4 +157,106 @@ void MidiOutput::run() clearAllPendingMessages(); } +#if JUCE_UNIT_TESTS + class MidiDevicesUnitTests : public UnitTest + { + public: + MidiDevicesUnitTests() : UnitTest ("MidiInput/MidiOutput", "MIDI/MPE") {} + + void runTest() override + { + beginTest ("default device (input)"); + { + auto devices = MidiInput::getAvailableDevices(); + auto defaultDevice = MidiInput::getDefaultDevice(); + + if (devices.size() == 0) + expect (defaultDevice == MidiDeviceInfo()); + else + expect (devices.contains (defaultDevice)); + } + + beginTest ("default device (output)"); + { + auto devices = MidiOutput::getAvailableDevices(); + auto defaultDevice = MidiOutput::getDefaultDevice(); + + if (devices.size() == 0) + expect (defaultDevice == MidiDeviceInfo()); + else + expect (devices.contains (defaultDevice)); + } + + #if JUCE_MAC || JUCE_LINUX || JUCE_IOS + String testDeviceName ("TestDevice"); + String testDeviceName2 ("TestDevice2"); + + struct MessageCallbackHandler : public MidiInputCallback + { + void handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) override + { + messageSource = source; + messageReceived = message; + } + + MidiInput* messageSource = nullptr; + MidiMessage messageReceived; + }; + + MessageCallbackHandler handler; + + beginTest ("create device (input)"); + { + std::unique_ptr 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 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::createNewDevice (testDeviceName, &handler)); + expect (midiInput.get() != nullptr); + midiInput->start(); + + auto inputInfo = midiInput->getDeviceInfo(); + + expect (MidiOutput::getAvailableDevices().contains (inputInfo)); + + std::unique_ptr 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 diff --git a/modules/juce_audio_devices/midi_io/juce_MidiDevices.h b/modules/juce_audio_devices/midi_io/juce_MidiDevices.h new file mode 100644 index 0000000000..809f8a1279 --- /dev/null +++ b/modules/juce_audio_devices/midi_io/juce_MidiDevices.h @@ -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 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 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 diff --git a/modules/juce_audio_devices/midi_io/juce_MidiInput.h b/modules/juce_audio_devices/midi_io/juce_MidiInput.h deleted file mode 100644 index 49af5bfcde..0000000000 --- a/modules/juce_audio_devices/midi_io/juce_MidiInput.h +++ /dev/null @@ -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 diff --git a/modules/juce_audio_devices/midi_io/juce_MidiOutput.h b/modules/juce_audio_devices/midi_io/juce_MidiOutput.h deleted file mode 100644 index c41dde7061..0000000000 --- a/modules/juce_audio_devices/midi_io/juce_MidiOutput.h +++ /dev/null @@ -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 diff --git a/modules/juce_audio_devices/native/java/app/com/roli/juce/JuceMidiSupport.java b/modules/juce_audio_devices/native/java/app/com/roli/juce/JuceMidiSupport.java index cfe99180f9..713f3350f9 100644 --- a/modules/juce_audio_devices/native/java/app/com/roli/juce/JuceMidiSupport.java +++ b/modules/juce_audio_devices/native/java/app/com/roli/juce/JuceMidiSupport.java @@ -51,6 +51,8 @@ public class JuceMidiSupport // send will do nothing on an input port void sendMidi (byte[] msg, int offset, int count); + + String getName (); } //============================================================================== @@ -256,6 +258,12 @@ public class JuceMidiSupport { } + @Override + public String getName () + { + return owner.getPortName (portPath); + } + MidiDeviceManager owner; MidiOutputPort androidPort; MidiPortPath portPath; @@ -331,6 +339,12 @@ public class JuceMidiSupport androidPort = null; } + @Override + public String getName () + { + return owner.getPortName (portPath); + } + MidiDeviceManager owner; MidiInputPort androidPort; MidiPortPath portPath; @@ -343,7 +357,6 @@ public class JuceMidiSupport deviceId = deviceIdToUse; isInput = direction; portIndex = androidIndex; - } public int deviceId; @@ -555,17 +568,17 @@ public class JuceMidiSupport super.finalize (); } - public String[] getJuceAndroidMidiInputDevices () + public String[] getJuceAndroidMidiOutputDeviceNameAndIDs () { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); + return getJuceAndroidMidiDeviceNameAndIDs (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); } - public String[] getJuceAndroidMidiOutputDevices () + public String[] getJuceAndroidMidiInputDeviceNameAndIDs () { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); + return getJuceAndroidMidiDeviceNameAndIDs (MidiDeviceInfo.PortInfo.TYPE_INPUT); } - private String[] getJuceAndroidMidiDevices (int portType) + private String[] getJuceAndroidMidiDeviceNameAndIDs (int portType) { // only update the list when JUCE asks for a new list synchronized (MidiDeviceManager.class) @@ -573,22 +586,24 @@ public class JuceMidiSupport deviceInfos = getDeviceInfos (); } - ArrayList portNames = new ArrayList (); + ArrayList portNameAndIDs = new ArrayList (); - int index = 0; - for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index)) - portNames.add (getPortName (portInfo)); + for (MidiPortPath portInfo : getAllPorts (portType)) + { + portNameAndIDs.add (getPortName (portInfo)); + portNameAndIDs.add (Integer.toString (portInfo.hashCode ())); + } - String[] names = new String[portNames.size ()]; - return portNames.toArray (names); + String[] names = new String[portNameAndIDs.size ()]; + return portNameAndIDs.toArray (names); } - private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput) + private JuceMidiPort openMidiPortWithID (int deviceID, long host, boolean isInput) { synchronized (MidiDeviceManager.class) { - int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT); - MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index); + int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT); + MidiPortPath portInfo = getPortPathForID (portTypeToFind, deviceID); if (portInfo != null) { @@ -633,14 +648,14 @@ public class JuceMidiSupport return null; } - public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) + public JuceMidiPort openMidiInputPortWithID (int deviceID, long host) { - return openMidiPortWithJuceIndex (index, host, true); + return openMidiPortWithID (deviceID, host, true); } - public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) + public JuceMidiPort openMidiOutputPortWithID (int deviceID) { - return openMidiPortWithJuceIndex (index, 0, false); + return openMidiPortWithID (deviceID, 0, false); } /* 0: unpaired, 1: paired, 2: pairing */ @@ -773,24 +788,6 @@ public class JuceMidiSupport openPorts.remove (path); } - public String getInputPortNameForJuceIndex (int index) - { - MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index); - if (portInfo != null) - return getPortName (portInfo); - - return ""; - } - - public String getOutputPortNameForJuceIndex (int index) - { - MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index); - if (portInfo != null) - return getPortName (portInfo); - - return ""; - } - public void onDeviceAdded (MidiDeviceInfo info) { // only add standard midi devices @@ -980,24 +977,24 @@ public class JuceMidiSupport return ""; } - public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex) + public ArrayList getAllPorts (int portType) { - int portIdx = 0; + ArrayList ports = new ArrayList (); + for (MidiDeviceInfo info : deviceInfos) - { for (MidiDeviceInfo.PortInfo portInfo : info.getPorts ()) - { if (portInfo.getType () == portType) - { - if (portIdx == juceIndex) - return new MidiPortPath (info.getId (), - (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT), - portInfo.getPortNumber ()); + ports.add (new MidiPortPath (info.getId (), (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT), + portInfo.getPortNumber ())); - portIdx++; - } - } - } + return ports; + } + + public MidiPortPath getPortPathForID (int portType, int deviceID) + { + for (MidiPortPath port : getAllPorts (portType)) + if (port.hashCode () == deviceID) + return port; return null; } diff --git a/modules/juce_audio_devices/native/juce_android_Midi.cpp b/modules/juce_audio_devices/native/juce_android_Midi.cpp index 449ff1c31f..5d40834d61 100644 --- a/modules/juce_audio_devices/native/juce_android_Midi.cpp +++ b/modules/juce_audio_devices/native/juce_android_Midi.cpp @@ -27,339 +27,325 @@ namespace juce // This byte-code is generated from native/java/com/roli/juce/JuceMidiSupport.java with min sdk version 23 // See juce_core/native/java/README.txt on how to generate this byte-code. static const uint8 javaMidiByteCode[] = -{31,139,8,8,173,175,226,91,0,3,106,117,99,101,95,97,117,100,105,111,95,100,101,118,105,99,101,115,46,100,101,120,0,149, -124,11,124,220,69,181,255,153,223,99,119,179,217,164,155,77,218,164,105,178,217,164,73,179,133,60,155,182,164,77,26,154, -164,105,155,118,251,160,217,22,105,144,186,77,182,237,150,100,55,236,110,250,0,175,148,135,180,32,42,42,143,42,92,254,40, -175,130,168,232,5,46,34,42,8,87,20,69,184,92,212,122,125,1,194,223,130,200,67,65,68,196,222,239,153,153,125,164,45,84, -203,231,187,103,126,103,206,156,153,57,115,230,204,153,223,46,25,141,238,113,183,117,44,160,7,183,254,71,91,73,96,244, -225,231,218,251,238,107,108,49,191,55,239,138,244,25,191,14,80,247,85,235,137,38,136,104,207,166,249,62,210,255,30,90,71, -116,150,80,252,1,224,25,155,104,19,232,17,7,81,0,244,117,55,209,93,76,11,137,10,64,215,151,128,223,3,121,104,216,209,76, -180,19,24,7,146,192,36,112,61,112,35,112,8,184,27,184,7,120,0,120,8,120,26,40,110,33,90,7,156,11,196,129,36,176,27,56,8, -124,23,248,30,240,3,224,151,192,107,192,123,64,105,43,209,12,160,18,168,1,234,129,83,128,22,160,3,88,4,108,1,46,5,110,7, -158,6,204,54,162,54,96,24,216,7,124,25,248,9,240,6,224,111,39,234,7,206,1,46,4,190,2,60,13,188,3,84,207,35,90,11,236,7, -238,0,126,2,188,1,56,58,136,170,128,5,192,32,240,97,32,9,252,16,120,23,104,153,79,180,21,184,10,248,13,80,191,128,232,76, -224,227,192,109,192,227,192,203,64,209,66,162,38,96,37,176,5,72,2,251,129,235,129,175,1,223,7,94,0,204,211,136,102,2,109, -192,48,144,0,46,1,14,2,119,1,15,3,63,7,142,0,127,3,90,59,137,150,2,67,192,78,224,223,128,135,128,103,128,231,129,130,69, -68,30,192,11,76,7,102,1,179,129,70,160,9,152,7,44,2,122,128,126,96,16,88,7,108,2,62,12,68,128,81,96,7,48,6,76,0,87,3,143, -0,47,1,255,0,74,23,195,55,128,38,96,1,176,24,232,7,206,0,206,1,118,2,73,224,82,224,38,224,30,224,251,192,175,128,87,129, -119,0,179,11,227,3,170,129,122,96,30,176,24,88,5,156,1,124,8,136,0,113,96,15,240,113,224,83,192,13,192,205,192,109,192, -55,128,7,128,71,129,159,0,191,6,94,6,222,1,236,110,216,0,168,2,234,129,86,96,49,176,19,72,3,215,1,247,3,223,5,158,0,126, -1,252,22,120,25,120,13,120,23,40,95,66,52,31,88,13,124,4,56,31,248,12,112,61,240,85,224,155,192,15,129,159,2,191,1,10, -176,95,188,64,121,143,218,59,61,192,89,192,94,224,11,192,189,192,19,192,207,128,183,0,227,116,162,18,224,20,96,62,208,15, -132,129,109,64,2,248,24,176,31,248,12,112,29,240,37,224,86,224,78,224,235,192,127,2,223,1,126,8,252,12,248,29,240,10,240, -22,96,44,133,31,0,51,128,90,160,21,152,15,116,3,43,129,51,128,51,129,17,96,23,112,25,240,57,224,58,224,6,224,139,192,237, -192,87,129,251,129,239,0,143,0,63,2,126,11,252,21,40,238,37,106,0,122,128,245,192,8,144,4,46,6,62,15,220,9,60,12,60,14, -28,6,94,6,172,62,34,31,16,0,130,64,15,48,4,108,1,226,192,167,128,175,1,143,3,255,11,188,8,188,6,252,13,16,253,216,87,64, -25,48,23,24,0,206,1,70,129,113,96,15,112,49,112,37,112,29,240,69,224,110,224,1,224,113,224,73,224,247,192,107,192,187, -128,107,25,246,6,112,42,48,31,232,6,250,128,21,192,102,32,1,236,1,46,6,174,0,174,2,238,2,238,3,30,3,158,1,94,0,222,0,222, -6,254,1,20,32,184,86,0,1,96,14,208,6,116,1,189,192,32,16,6,62,12,164,129,79,3,119,0,119,3,211,16,115,203,128,58,96,54,80, -15,52,0,115,128,70,32,8,204,5,78,1,78,5,154,0,132,89,66,216,36,132,65,66,184,35,132,53,66,8,35,132,41,66,104,34,132,33, -66,136,33,132,13,66,104,32,108,93,194,214,35,108,15,130,123,19,92,150,224,42,132,101,33,152,151,96,18,90,166,207,135,229, -192,10,96,37,48,8,172,2,86,3,33,96,13,176,22,88,199,231,4,112,6,176,1,24,2,194,164,206,149,15,1,155,129,179,129,115,128, -45,64,4,24,1,70,129,40,112,46,112,1,240,49,224,66,96,31,112,17,112,49,112,9,41,155,100,254,121,53,125,22,147,47,209,229, -35,40,87,130,26,250,153,203,166,46,215,232,242,179,90,198,210,252,90,93,126,93,243,93,121,242,56,2,233,239,154,95,168, -249,179,128,34,110,211,164,248,197,121,125,77,203,43,251,242,228,203,180,60,151,43,242,218,86,230,245,85,165,199,198,50, -126,45,83,163,203,204,175,209,114,30,173,167,78,203,84,235,114,89,147,146,229,114,149,110,91,159,215,182,65,183,229,126, -216,135,130,122,12,45,121,227,108,205,27,91,91,222,216,184,220,214,164,242,2,46,119,54,229,248,25,123,182,231,233,105, -207,27,63,151,151,230,149,51,242,157,121,250,217,15,87,234,126,23,107,62,251,194,18,93,30,211,101,214,57,174,203,235,81, -142,235,242,135,80,78,232,242,104,147,202,105,184,156,70,121,183,46,127,20,229,243,116,249,0,202,73,93,190,10,229,73,93, -190,1,229,93,186,124,75,94,249,238,60,157,15,230,149,61,121,229,71,243,202,63,206,235,247,153,60,254,179,121,229,35,121, -253,190,158,199,255,107,94,91,222,208,123,50,125,53,231,228,43,80,222,171,203,129,230,92,219,165,121,122,218,242,244,119, -230,143,225,212,92,185,169,57,215,215,124,148,211,25,61,40,159,175,203,43,155,115,182,90,143,114,74,151,207,110,86,123, -181,71,175,209,71,117,153,215,232,223,116,57,157,87,110,203,43,103,124,160,87,183,229,114,95,158,63,244,231,249,195,50, -205,159,165,203,150,244,243,54,186,143,20,93,42,184,205,52,250,132,108,219,78,159,146,244,52,250,140,164,46,234,17,236, -183,21,116,5,219,10,189,191,36,169,160,87,37,109,160,217,178,126,14,53,11,142,5,101,82,174,86,243,107,53,127,182,126,102, -122,134,224,125,101,209,181,196,212,75,127,145,84,213,215,235,250,6,61,158,6,68,219,107,36,237,163,59,37,45,167,55,37, -157,79,239,232,122,191,80,52,32,212,190,60,68,76,123,232,15,164,158,231,10,142,247,53,116,21,49,109,160,183,137,227,155, -139,190,39,169,73,143,74,106,211,255,18,199,55,39,221,40,105,29,61,168,233,255,176,205,112,82,220,160,233,87,37,181,232, -191,36,93,67,11,161,223,6,223,73,28,251,86,208,42,193,116,1,173,21,156,247,43,190,59,75,221,116,157,164,5,180,28,245,30, -173,167,72,215,23,129,115,157,164,133,180,76,40,58,32,56,46,22,209,119,137,105,45,253,146,56,118,171,241,120,17,61,127, -44,233,52,154,41,152,122,169,90,112,60,87,227,230,184,254,51,77,127,69,42,166,254,72,210,51,232,176,164,37,244,11,205, -231,250,50,173,183,12,167,83,47,244,76,215,227,42,199,105,244,152,164,173,84,46,152,46,162,10,73,187,105,158,164,93,180, -73,112,108,86,237,43,224,225,255,174,41,219,107,166,214,83,137,241,127,139,56,134,250,232,126,226,216,107,208,45,210,15, -151,201,250,26,172,163,162,130,30,150,180,129,126,40,105,152,254,91,210,229,100,74,127,61,133,74,37,61,149,202,36,93,79, -115,36,29,164,213,146,14,208,70,233,151,167,75,125,1,61,46,166,255,33,105,51,253,90,210,16,189,162,249,211,164,252,106, -154,46,233,42,234,23,138,63,168,233,26,233,207,61,82,95,173,214,87,171,245,213,106,61,181,186,93,173,110,87,171,219,213, -105,249,58,45,87,167,229,234,180,92,157,150,155,77,75,165,254,217,200,58,44,249,220,65,182,166,14,73,219,201,41,233,124, -114,105,90,160,249,94,77,75,36,109,35,159,166,51,228,190,234,149,122,235,209,255,231,37,173,163,135,36,117,208,15,72,157, -115,143,75,58,151,186,244,126,114,202,253,165,230,215,0,143,184,71,210,153,116,175,164,106,125,26,224,23,223,151,116,54, -61,33,233,70,250,137,164,97,122,82,211,167,36,45,165,167,181,220,51,146,214,211,79,37,157,69,63,151,180,147,220,178,223, -211,168,80,83,143,80,252,34,73,23,83,177,80,251,191,82,210,25,52,75,210,10,170,146,116,45,213,75,218,66,13,66,197,139, -249,146,14,80,88,198,133,38,57,159,57,200,176,190,166,227,194,111,101,60,152,11,11,40,234,148,116,58,125,91,210,74,250, -14,241,121,126,138,228,183,106,249,86,120,236,135,4,159,219,74,190,77,219,167,13,30,253,8,241,249,172,244,183,195,206,47, -19,231,141,253,82,174,3,30,206,126,63,95,183,155,15,185,253,250,249,122,253,252,255,36,157,67,127,212,207,29,66,229,156, -43,37,29,162,33,193,249,103,35,93,73,156,131,42,61,11,117,251,133,144,191,73,210,26,217,207,66,100,183,127,146,52,64,237, -66,241,89,223,105,186,221,105,186,255,211,116,63,167,233,126,78,211,253,116,98,252,191,33,166,200,136,4,231,23,106,92, -139,53,237,210,122,186,144,205,158,46,56,247,85,207,221,218,191,248,12,2,91,190,247,32,185,255,113,94,115,162,141,36,247, -189,53,42,199,18,142,92,142,196,245,46,156,105,11,215,170,231,128,110,207,252,167,78,85,180,2,116,173,174,175,213,245, -109,121,245,109,160,151,233,250,217,90,175,157,167,127,37,234,191,170,235,235,53,191,59,175,254,67,168,127,81,215,55,104, -253,211,129,195,90,255,14,80,207,58,85,63,71,183,203,31,255,221,168,63,160,235,27,243,198,151,169,127,8,245,55,233,122, -206,175,127,129,196,255,217,144,146,251,131,166,127,11,229,234,10,215,228,202,229,107,84,125,93,30,239,84,93,94,8,186,36, -175,188,114,141,202,211,89,102,8,229,115,116,219,152,166,231,107,250,9,77,111,210,244,94,77,127,156,215,199,111,53,239, -101,169,211,144,229,111,14,168,187,195,132,183,8,207,117,240,153,9,47,251,238,176,215,66,84,31,246,26,52,236,51,112,46, -177,60,235,121,124,64,229,254,97,212,156,231,189,148,248,212,139,7,118,96,141,221,50,223,183,180,220,127,15,168,123,193, -121,178,23,143,136,7,12,236,35,200,122,109,249,204,113,205,68,29,203,254,102,64,157,105,225,128,69,225,90,11,50,95,66, -141,91,204,46,89,6,221,55,99,124,30,248,224,50,41,99,203,83,30,119,121,180,153,1,154,244,222,134,62,61,34,233,189,133, -219,24,157,70,17,120,183,162,204,109,60,228,243,197,219,22,193,131,130,175,23,235,145,17,189,51,160,236,192,119,21,135, -156,25,158,151,171,187,160,175,100,94,153,77,190,218,142,178,18,140,163,4,253,121,176,111,10,41,220,206,227,226,155,145, -199,136,7,110,130,207,250,122,59,202,106,16,191,166,83,165,177,147,206,11,180,131,151,107,225,59,166,197,23,101,173,165, -109,209,141,8,90,44,231,194,125,7,150,171,187,74,190,173,122,161,101,17,250,85,250,191,161,245,251,196,52,17,110,87,150, -23,82,242,124,105,169,224,91,110,104,98,237,109,208,117,38,207,163,220,231,208,250,160,199,77,149,22,244,216,69,82,79,24, -125,199,229,133,209,35,22,137,76,157,71,215,5,255,212,89,176,128,234,12,55,60,129,109,86,105,89,232,175,141,173,108,197, -3,94,196,226,58,179,8,117,62,88,46,30,40,67,6,204,252,233,184,181,122,44,95,3,151,194,52,59,189,20,61,76,67,107,143,189, -198,182,28,231,121,175,83,237,189,165,104,229,177,227,75,11,169,247,227,193,111,199,3,30,220,104,131,223,164,172,127,141, -46,87,247,204,169,254,245,111,240,175,98,228,97,14,57,163,241,229,234,110,57,225,109,65,155,225,217,5,52,92,239,160,225, -6,55,109,158,227,130,229,207,14,56,229,218,218,210,191,4,93,180,92,197,16,159,25,238,117,80,167,112,18,211,184,247,84, -212,133,123,11,192,41,144,52,220,231,70,95,31,133,157,135,251,161,179,223,1,45,69,122,5,124,122,5,130,71,84,28,98,221,66, -52,227,248,18,114,76,215,162,15,142,153,113,126,193,69,9,239,229,218,191,212,46,35,186,113,185,218,135,62,88,69,104,222, -45,203,115,126,88,140,185,241,93,251,206,229,106,223,44,41,244,208,208,69,46,114,238,115,126,78,220,34,238,181,190,191, -203,181,84,203,90,250,182,254,64,94,123,67,239,165,239,47,87,113,46,236,45,80,94,232,197,140,33,177,209,235,148,126,192, -207,241,64,19,246,148,207,123,182,215,57,165,237,147,31,208,182,51,219,182,153,219,82,166,45,143,133,199,112,88,175,219, -132,247,0,71,15,225,161,97,163,144,134,225,41,197,217,117,56,146,183,14,133,122,29,10,97,177,217,114,29,60,122,29,60,88, -135,162,236,58,64,79,127,225,191,176,14,239,101,215,97,229,9,215,193,94,161,215,1,30,228,212,163,47,4,175,148,231,221, -174,71,5,26,95,138,44,43,154,235,183,79,232,126,223,86,239,71,116,191,238,204,90,214,173,200,173,69,134,23,204,227,153, -50,218,17,181,172,80,239,84,134,197,52,216,138,45,54,108,20,203,248,170,222,204,44,206,107,147,89,231,101,39,224,173,95, -145,31,195,44,57,167,179,87,168,56,234,11,116,216,211,176,190,241,64,1,180,134,3,188,219,93,188,239,16,47,46,144,183,140, -156,158,115,79,160,123,247,9,120,151,156,128,247,233,99,230,199,255,174,63,1,239,214,60,158,45,45,71,244,181,21,188,179, -217,14,165,176,195,157,210,14,56,111,204,18,26,182,120,132,150,180,162,73,15,173,80,239,100,170,140,122,170,54,124,98, -184,221,135,85,253,50,106,224,143,237,94,172,215,52,73,227,240,71,161,75,28,37,88,210,139,231,18,72,184,37,141,123,43,52, -191,132,252,6,238,96,194,111,52,138,34,17,124,155,103,51,19,117,53,114,124,166,204,23,28,210,103,231,94,216,220,52,87, -151,13,250,245,10,149,59,86,153,24,139,25,222,0,221,198,108,98,26,247,206,228,88,39,226,222,58,121,10,249,230,119,244, -205,0,183,150,163,180,81,105,125,22,254,220,138,200,201,103,146,137,189,228,202,158,12,126,179,4,168,196,242,5,255,94, -132,82,163,161,206,251,57,104,217,172,125,73,32,223,205,248,239,187,43,84,125,216,235,149,107,109,104,111,19,43,51,231, -178,26,141,140,155,94,245,214,78,157,203,106,15,58,87,242,253,14,115,16,152,131,8,183,249,56,39,167,112,27,230,130,252, -142,159,195,243,160,33,144,70,196,245,11,246,127,191,104,36,213,103,169,236,171,12,251,70,157,193,190,149,234,125,166, -207,154,240,54,192,114,195,225,50,26,222,84,6,139,148,81,165,249,22,180,148,227,230,227,49,106,140,114,26,222,80,14,62, -110,154,56,87,42,13,236,40,115,187,220,201,243,176,214,117,198,66,248,192,65,142,229,27,102,226,105,62,158,62,45,159,42, -166,212,85,78,121,154,46,245,197,189,115,216,242,84,107,250,140,5,243,220,124,15,199,105,155,68,28,59,108,24,88,91,41, -211,54,151,54,88,193,31,187,244,25,115,250,74,149,43,134,71,42,208,254,115,188,51,44,206,31,44,114,155,157,102,155,204, -31,44,57,110,63,133,17,176,106,76,175,180,169,153,91,97,211,183,160,99,232,247,71,245,10,155,149,182,90,225,161,236,10, -255,226,168,94,97,51,30,248,4,206,72,214,252,228,209,18,195,103,4,255,225,200,156,117,43,213,187,234,112,95,37,244,223, -192,243,48,146,222,219,53,189,5,148,91,205,146,227,49,164,230,86,10,247,67,54,112,189,28,75,13,214,48,238,253,136,28,1, -247,50,36,229,95,56,90,34,124,34,248,15,142,51,202,123,46,92,169,222,123,87,217,13,84,109,135,211,60,235,107,120,182,214, -236,190,62,142,243,105,101,7,204,217,33,100,38,229,64,93,167,53,67,246,236,128,246,26,211,79,135,17,239,194,184,194,212, -88,202,26,124,158,175,177,12,33,68,240,247,126,187,196,40,178,252,118,163,197,126,209,34,123,109,37,151,244,19,131,238, -91,169,238,62,170,255,9,111,140,117,90,195,163,51,200,111,215,153,106,181,77,140,35,222,118,30,237,194,220,59,141,82,204, -113,2,123,128,51,139,171,112,235,58,12,7,207,212,6,223,80,61,241,28,76,158,67,47,103,137,215,194,59,61,86,141,133,44,81, -142,129,163,65,67,118,255,240,205,88,237,148,63,106,155,87,25,24,139,17,238,85,51,103,11,242,188,161,79,116,138,10,57, -111,83,90,220,47,111,156,53,66,205,89,230,92,222,26,153,115,117,156,113,228,168,223,224,88,226,163,224,123,42,154,168, -125,210,40,123,10,18,231,130,220,175,99,16,209,141,251,116,163,79,119,167,183,140,124,115,121,54,55,20,121,176,94,107,41, -252,240,76,204,224,11,200,143,185,119,39,44,225,119,151,144,111,102,240,213,13,237,179,104,34,16,199,125,212,227,236,116, -46,162,240,46,140,197,129,168,231,232,128,84,39,124,113,67,123,53,218,206,66,46,204,118,43,64,6,31,32,231,195,214,11,187, -28,242,77,39,191,107,129,246,58,171,3,122,62,137,53,139,183,253,59,181,91,126,119,240,41,140,216,221,40,84,251,74,110, -239,234,116,189,116,180,14,103,220,196,210,21,244,80,103,240,121,191,27,51,123,144,228,157,186,31,51,226,239,56,84,246, -178,66,250,22,123,241,126,204,141,223,253,249,28,225,20,118,90,224,20,248,122,149,133,121,90,225,212,116,216,236,243,124, -2,165,148,127,155,210,202,183,203,12,144,173,109,75,127,45,151,214,182,165,63,183,42,89,248,119,169,244,58,246,239,45, -144,15,190,84,100,250,173,70,211,103,14,163,222,15,221,51,143,213,152,183,131,43,243,118,240,28,146,178,208,56,91,106, -236,64,187,79,201,118,53,102,29,228,214,178,246,223,13,239,154,9,185,19,69,131,186,140,46,229,11,166,160,78,68,3,166,53, -166,37,79,29,51,239,201,33,159,50,17,163,130,117,255,82,233,253,12,202,156,141,112,214,196,247,232,185,176,223,124,82, -119,32,129,155,165,186,11,24,244,237,65,229,159,190,64,149,128,21,225,109,87,242,72,140,69,70,0,43,184,13,235,192,235, -140,27,138,215,207,39,223,188,233,210,115,250,228,205,199,137,88,29,252,139,138,216,19,129,237,90,214,98,238,31,252,194, -39,115,38,142,73,179,208,99,128,248,62,207,125,87,203,236,129,253,246,233,65,149,87,251,106,217,59,125,38,223,99,156,114, -39,26,242,198,198,239,106,130,239,229,242,197,231,7,149,111,248,2,19,129,115,229,141,171,36,91,119,36,83,231,205,213,101, -250,121,53,211,79,201,7,247,227,211,54,249,7,228,183,18,199,179,18,218,232,170,162,78,215,90,220,111,212,110,114,33,159, -9,23,206,160,218,7,125,46,113,249,130,31,174,194,9,80,88,224,51,244,46,117,113,155,13,69,179,168,227,240,34,172,2,191, -233,246,20,213,254,198,231,90,112,164,157,150,91,69,174,184,215,207,246,117,116,236,154,131,250,106,185,234,178,205,180, -106,234,248,115,21,120,85,188,250,54,175,3,252,21,123,164,56,115,174,187,42,11,222,147,81,255,32,234,107,236,118,222,181, -182,175,49,120,207,97,151,75,4,159,58,236,42,16,226,242,224,127,250,221,149,72,84,131,127,46,114,97,191,185,56,227,219, -128,214,31,201,198,166,45,176,133,41,215,37,182,74,125,247,232,43,245,89,62,17,126,12,179,218,35,150,47,184,108,30,45,39, -167,131,71,143,243,164,160,246,11,98,96,193,173,245,180,220,40,176,121,244,56,37,10,59,126,57,147,58,158,155,78,117,246, -156,204,169,239,232,88,82,40,103,132,122,107,98,233,199,40,218,239,28,42,49,69,127,240,47,135,17,179,15,219,182,8,254, -250,176,141,128,191,41,248,164,207,12,254,89,221,221,121,28,95,90,165,214,135,207,58,206,179,124,237,29,94,19,126,27,198, -121,238,19,241,165,65,106,243,5,223,225,247,202,134,204,151,238,130,252,77,28,223,138,176,34,69,105,49,147,119,175,139, -199,225,194,56,124,78,95,1,223,202,194,158,74,220,232,246,115,236,47,102,159,125,133,220,158,69,158,115,100,47,144,243, -248,122,58,94,105,67,212,99,235,186,200,227,169,44,86,103,234,43,210,186,56,83,109,117,131,70,164,176,148,62,229,251,33, -180,237,244,116,83,142,119,61,120,30,103,141,211,202,227,221,8,94,93,97,189,228,184,176,23,92,56,99,38,206,60,135,10,107, -143,27,27,34,228,43,133,117,197,93,56,151,14,97,214,157,5,109,84,234,121,202,121,209,67,182,151,243,67,236,236,165,119, -208,119,188,254,162,226,204,120,60,172,131,119,231,67,228,113,119,186,225,145,119,33,211,122,168,6,214,116,34,110,187, -100,156,112,202,248,224,164,52,206,151,82,242,23,5,159,43,242,248,139,26,61,165,158,81,10,62,93,228,9,190,205,123,99,18, -30,193,223,101,85,235,239,84,84,222,118,80,92,216,124,163,56,40,56,199,51,100,94,221,182,90,125,231,85,229,132,205,157, -124,54,185,113,122,179,205,113,118,27,225,107,213,124,12,94,7,216,232,50,172,67,167,3,209,245,90,196,162,192,103,233,54, -60,47,114,52,208,84,185,235,33,231,113,212,56,56,234,70,228,185,63,181,254,70,212,179,134,58,151,159,38,218,230,209,33, -147,79,231,203,200,239,44,38,61,2,139,87,143,243,136,74,151,90,189,203,100,92,199,234,137,42,105,45,33,243,82,73,29,108, -177,24,86,183,211,134,111,13,169,24,218,105,186,116,84,85,209,148,163,168,155,130,79,22,57,130,79,20,57,252,206,70,135, -58,99,199,97,247,9,29,63,119,203,189,132,8,122,97,115,122,23,114,132,66,25,53,4,237,131,141,62,204,54,194,76,171,165,109, -220,228,182,249,60,61,75,190,163,89,147,141,213,97,129,222,133,69,190,50,223,244,14,36,57,62,43,124,141,58,97,108,121,86, -221,174,169,60,179,176,162,175,30,213,103,150,60,97,54,52,206,34,173,221,213,177,255,213,163,178,45,172,217,32,61,87,157, -56,182,44,171,19,7,209,187,44,248,227,78,225,210,183,22,117,99,9,95,195,107,115,53,106,253,14,228,221,182,223,209,104, -171,185,158,37,227,196,230,236,251,171,71,86,79,189,251,241,61,248,71,171,179,241,118,253,94,154,23,246,200,236,195,144, -117,63,93,173,238,156,252,190,204,103,76,108,216,75,253,94,174,47,144,251,29,247,154,213,234,183,12,136,59,6,175,141,75, -174,17,191,153,230,44,208,164,105,134,58,209,249,61,159,19,22,236,180,113,210,88,193,183,112,95,177,26,141,112,114,58, -233,86,182,207,193,187,103,131,35,156,44,3,111,134,204,100,125,211,235,28,179,225,47,67,180,203,25,95,138,19,49,234,65, -150,131,21,207,107,39,91,9,191,37,230,5,31,231,59,234,126,10,254,77,189,151,116,99,166,252,93,231,169,210,6,21,217,24, -229,8,169,123,126,38,38,53,34,38,169,59,167,178,210,180,144,178,71,216,59,83,238,126,126,127,164,252,194,162,138,144,250, -189,5,207,209,45,119,6,71,55,181,147,194,215,170,168,114,155,228,227,206,114,173,138,40,183,201,181,134,127,26,106,213, -12,185,106,134,174,191,17,245,124,226,222,33,189,25,153,172,119,43,123,138,204,94,236,236,46,163,236,46,98,207,135,44, -172,11,223,235,159,114,195,112,196,3,147,216,21,42,123,240,59,131,247,40,175,47,18,149,14,220,154,92,165,242,214,116,25, -113,188,230,179,100,25,172,178,86,223,181,207,208,241,226,76,157,255,26,52,116,97,243,166,161,236,59,154,177,80,254,59, -154,179,196,44,58,219,168,162,179,204,106,249,246,73,221,48,63,26,82,239,203,125,98,17,98,238,52,228,31,87,200,76,138,41, -124,216,236,152,247,246,81,231,250,26,228,230,27,250,170,105,3,218,118,204,123,245,232,198,190,42,218,104,86,161,124,228, -232,134,190,89,224,227,196,157,247,252,81,95,73,240,183,153,247,112,68,159,12,169,239,10,234,112,55,216,208,59,139,158, -246,237,3,173,166,82,115,31,45,104,47,149,229,187,107,39,2,31,231,24,235,189,140,87,223,216,216,139,243,30,59,197,247, -198,55,106,167,137,82,113,33,5,95,131,214,191,169,219,48,209,23,245,58,251,96,179,26,192,143,185,100,222,5,29,10,169,124, -71,205,183,88,199,82,131,190,18,210,239,161,68,238,13,169,73,37,66,191,19,69,206,244,238,209,42,163,17,247,131,173,194, -79,139,4,178,106,81,131,53,235,64,134,30,3,199,47,249,193,35,153,156,191,68,234,16,250,123,12,33,179,43,83,247,245,88,72, -125,127,82,73,234,62,108,200,222,44,249,93,109,21,242,180,106,177,21,245,139,136,51,247,6,244,49,138,182,60,19,191,228,7, -95,205,220,191,139,116,31,213,217,62,170,228,111,79,229,221,95,219,130,231,250,81,159,250,254,231,82,208,43,179,191,110, -85,255,14,234,231,140,60,255,182,233,22,240,238,58,134,63,168,249,247,31,211,254,209,99,158,159,242,169,62,51,191,41,226, -119,135,207,250,212,111,122,94,247,169,239,73,254,234,83,191,91,56,226,83,191,105,98,189,219,88,184,84,253,94,5,14,77, -222,210,169,122,171,142,121,150,223,149,104,58,67,83,254,237,140,33,105,187,236,127,26,245,200,88,199,117,85,121,115,17, -164,114,25,67,63,153,154,246,80,238,119,81,153,239,118,12,73,219,228,243,28,205,239,207,202,121,116,91,245,11,15,213,215, -233,217,246,66,219,65,97,134,140,89,220,46,99,159,204,239,171,20,207,161,121,14,201,83,101,103,86,87,129,166,94,77,125, -90,198,167,245,10,101,62,202,124,167,101,72,58,139,50,223,177,177,44,127,199,59,71,247,203,223,171,206,209,123,176,1,255, -89,154,122,117,92,88,164,219,44,210,119,19,150,235,214,123,168,71,215,157,174,199,111,101,203,114,214,65,50,131,131,184, -203,204,165,250,182,142,190,206,182,129,5,189,205,3,203,6,58,155,231,247,117,116,52,247,158,182,160,189,121,97,255,64, -199,252,129,254,249,253,167,181,193,180,200,211,186,71,198,98,241,88,186,135,28,221,138,26,61,93,100,245,116,205,221,196, -159,40,123,251,198,38,163,233,68,34,189,99,77,36,30,217,30,77,210,226,99,57,129,104,50,153,72,46,14,140,36,38,199,70,3, -241,68,58,176,61,154,14,100,165,2,161,129,64,106,36,18,143,163,237,233,255,92,219,209,232,182,200,228,88,190,142,200,104, -100,34,13,5,149,203,38,199,199,247,102,249,43,34,233,116,127,100,108,108,107,100,4,23,155,65,50,6,67,100,14,134,66,84,51, -184,46,48,176,103,36,58,145,142,37,226,129,221,59,98,99,209,192,200,88,34,21,139,111,15,76,36,146,105,106,24,92,247,126, -245,227,177,209,24,134,176,43,54,18,37,177,138,172,85,27,251,7,168,100,213,228,72,116,13,106,6,227,19,147,233,245,172, -194,151,97,173,155,76,103,120,158,12,79,62,149,101,158,134,38,39,184,215,150,157,145,93,17,18,33,50,66,131,100,134,6,229, -7,122,192,7,50,10,12,219,12,225,195,10,133,54,135,168,62,20,137,143,38,19,177,209,214,173,153,217,182,102,231,221,171, -204,209,69,179,63,72,106,153,156,67,23,213,126,144,16,155,176,139,230,158,76,36,99,229,46,106,61,169,232,142,72,50,50, -130,225,197,82,233,216,72,23,157,122,178,6,203,162,169,145,100,108,34,157,72,158,120,32,99,209,156,124,40,58,164,124,233, -196,115,135,40,215,231,70,251,62,250,88,104,121,108,12,131,172,239,155,140,141,141,178,190,19,153,105,138,232,7,138,108, -136,166,224,178,39,158,173,22,25,138,166,211,112,176,84,174,203,15,152,66,70,184,139,102,102,133,70,18,241,116,52,158, -110,237,103,186,7,157,213,100,171,198,163,163,177,72,43,187,110,43,59,92,102,233,155,62,88,96,48,190,45,81,207,174,202, -133,252,225,188,175,116,23,53,124,176,208,80,58,146,158,196,168,235,222,79,44,187,129,242,93,233,24,25,29,29,234,149,202, -220,106,158,118,178,6,235,226,170,201,186,137,104,60,58,26,130,7,70,165,175,4,78,210,240,3,230,158,219,221,249,235,127, -140,208,134,232,72,52,182,139,245,148,102,69,18,169,214,190,201,248,232,24,150,161,44,159,185,50,194,76,136,150,231,115, -215,71,146,35,209,177,141,147,177,209,46,242,101,43,38,211,177,177,214,80,98,251,113,188,245,145,88,50,175,175,44,175, -139,54,30,207,236,62,137,155,156,52,62,224,32,104,11,141,36,198,91,147,137,177,88,235,78,68,181,214,99,66,91,253,177,145, -189,139,218,79,210,226,184,136,218,69,243,254,201,38,249,107,210,244,79,182,81,210,161,147,72,231,172,146,245,193,247,61, -113,186,104,217,191,172,45,199,97,23,13,71,82,231,158,220,80,199,105,57,249,164,51,19,94,31,73,239,224,48,241,129,210, -188,89,71,35,99,187,98,231,182,34,180,38,176,129,113,40,182,14,196,245,129,216,63,22,73,97,67,251,79,32,51,200,145,88, -215,215,158,160,126,77,116,124,171,22,136,66,164,250,4,34,67,177,237,113,68,140,36,118,73,229,9,170,195,59,146,137,221, -104,58,61,196,103,103,107,44,209,154,119,112,119,81,137,98,143,69,226,219,91,245,56,74,243,88,131,136,147,210,94,190,60, -230,186,173,59,163,35,233,169,188,161,116,18,51,205,118,35,121,178,235,200,86,222,191,85,121,236,100,116,91,235,153,209, -200,185,27,162,219,162,201,104,28,73,66,245,7,213,242,230,151,213,114,55,246,38,147,145,189,28,150,50,61,77,229,242,121, -117,2,118,247,241,35,237,201,142,63,39,154,154,202,91,25,73,97,51,78,100,12,146,207,59,94,16,199,205,113,130,224,77,29, -253,32,14,193,136,60,166,167,229,113,229,116,188,199,48,186,168,227,24,78,247,73,207,206,158,169,122,101,247,37,121,140, -112,108,156,215,114,250,177,44,181,139,74,142,219,38,212,123,28,235,196,249,102,222,65,16,72,237,197,153,49,30,72,69,147, -50,1,244,29,191,97,201,147,191,187,168,33,255,180,110,233,239,13,133,250,122,251,87,111,9,159,181,126,96,203,154,222,112, -255,202,45,161,117,67,97,18,155,200,216,132,132,111,19,82,84,107,211,224,230,65,114,108,90,133,20,112,21,216,72,252,54, -33,35,180,54,113,74,104,111,146,92,112,228,7,75,135,84,37,202,54,127,174,82,4,105,228,166,205,36,144,57,66,153,129,148, -209,24,238,163,186,225,147,103,49,205,195,255,82,86,80,255,79,136,99,219,13,159,96,139,77,97,102,246,88,97,100,100,36, -154,74,45,31,139,108,79,145,27,153,226,100,100,76,166,203,206,76,150,111,70,70,71,249,105,52,9,57,242,232,222,7,227,163, -209,61,104,173,158,100,11,119,100,98,66,39,67,228,136,164,148,39,110,61,38,75,166,202,44,39,52,32,195,158,90,219,141,27, -7,151,145,111,235,113,153,101,158,134,140,35,149,229,56,217,105,167,242,228,182,232,235,66,193,214,116,175,30,181,107, -107,90,201,65,76,151,82,124,22,195,4,228,216,154,230,115,132,236,173,156,8,146,103,68,31,40,225,189,19,81,114,96,20,200, -4,168,120,100,74,30,77,246,200,88,52,146,100,146,72,69,201,137,92,48,14,27,83,161,46,72,133,46,206,16,35,177,120,74,178, -101,105,117,116,175,20,150,54,242,232,66,56,177,17,58,108,236,130,120,154,196,40,185,71,179,41,56,57,244,92,92,138,194, -70,153,210,40,21,101,74,74,65,225,104,214,1,82,153,186,140,201,220,234,81,230,41,5,163,177,36,134,136,136,13,118,44,149, -25,186,35,122,30,150,62,69,5,114,83,246,39,70,97,192,104,38,182,83,203,182,8,110,101,163,129,116,34,48,146,140,70,210, -209,192,214,201,49,125,29,84,186,3,219,146,137,241,64,198,77,92,219,98,241,200,88,236,252,40,213,162,52,154,91,168,229, -137,100,222,197,73,9,215,176,72,102,67,159,72,192,222,22,75,194,153,60,219,96,162,209,204,130,187,185,67,229,198,100,109, -103,131,23,240,167,50,134,137,72,66,110,124,100,84,84,114,89,57,235,113,151,232,89,185,186,227,195,214,116,174,156,152, -24,139,141,200,51,48,227,224,165,96,31,55,206,138,124,102,126,6,46,181,28,127,109,34,23,216,242,164,164,18,148,150,169, -155,118,102,167,20,72,150,92,254,226,108,81,45,175,59,251,156,34,39,202,210,223,230,162,176,114,114,156,35,56,246,46,142, -74,101,156,19,26,20,162,240,37,73,70,165,6,214,75,85,178,160,147,185,181,145,113,94,45,78,79,212,102,159,137,90,126,58, -206,84,41,242,31,95,37,245,100,234,107,142,175,87,89,99,70,128,117,51,247,216,145,162,106,134,174,90,150,117,97,140,74, -143,154,199,72,213,40,228,114,208,227,134,93,136,234,76,5,21,101,30,38,57,23,162,114,253,200,103,199,148,70,46,93,145,82, -45,146,137,137,104,50,29,195,104,166,225,113,67,116,60,145,142,102,2,10,24,67,242,152,210,145,76,14,76,6,143,162,29,242, -114,161,175,35,228,220,17,73,173,101,223,113,161,176,67,238,48,107,71,2,126,93,192,159,202,111,69,140,204,216,232,30,178, -99,114,24,86,140,151,197,142,201,69,47,136,101,223,118,20,198,82,89,75,241,67,191,218,196,81,88,37,150,26,24,159,72,239, -229,130,92,2,174,206,189,38,113,197,116,214,64,46,78,62,87,202,238,119,102,167,237,217,153,255,198,196,60,23,225,202,129, -15,206,71,220,99,9,68,70,37,230,28,215,155,195,226,211,135,220,227,217,229,161,146,241,227,118,80,241,248,148,213,163, -194,241,60,207,49,198,199,201,28,79,109,199,71,122,146,172,56,47,146,205,159,136,33,241,232,110,222,62,48,83,156,205,102, -38,182,238,36,71,98,219,182,20,134,227,75,196,251,34,233,145,29,185,140,37,69,229,216,158,83,194,52,158,226,219,97,148, -178,99,43,120,135,208,244,99,185,103,38,97,29,169,69,153,19,219,93,246,175,212,144,55,17,207,189,28,145,26,74,242,57,170, -117,81,66,95,122,225,192,232,185,56,49,229,14,204,125,230,63,47,139,142,69,246,130,61,45,195,102,215,218,149,47,167,226, -71,102,34,206,68,124,249,216,100,106,7,121,18,241,53,233,201,12,27,35,227,241,40,191,220,144,74,197,168,130,57,99,49,142, -2,114,92,253,137,241,9,196,107,200,162,165,76,63,100,60,207,60,41,11,194,184,200,157,226,210,94,218,153,83,203,248,132, -192,93,26,178,165,216,4,241,99,194,27,185,153,169,203,69,92,206,249,154,159,31,167,220,41,207,140,165,119,228,246,88,77, -166,62,183,121,167,10,204,204,8,28,95,85,204,85,121,175,250,10,248,89,109,88,87,34,147,26,22,100,74,8,120,24,49,159,131, -137,92,19,59,177,155,67,112,233,4,124,242,216,89,85,158,128,57,148,142,78,132,119,39,168,124,74,93,46,50,145,53,193,25, -168,147,47,113,131,216,194,5,178,160,162,201,132,206,223,84,73,70,162,130,76,41,165,152,50,125,45,202,148,84,52,144,21, -50,148,20,103,74,225,196,114,132,6,50,121,103,207,72,70,183,243,75,149,228,212,55,51,228,72,74,47,34,183,162,42,98,168, -178,202,212,102,38,113,216,71,83,233,156,159,175,79,198,18,240,147,189,220,86,186,130,51,169,55,21,24,233,93,145,49,178, -146,236,87,102,114,50,78,37,169,108,254,170,95,158,81,105,42,47,239,206,48,157,153,55,205,174,212,200,142,232,40,18,6, -114,164,162,72,56,70,201,74,177,159,85,242,167,122,197,187,35,50,26,24,92,23,200,101,28,46,174,99,235,210,52,236,247,254, -252,164,172,16,12,246,218,53,28,66,139,249,65,231,144,147,177,81,84,238,224,235,4,246,13,38,106,165,56,5,177,83,242,161, -64,18,110,72,69,170,152,78,76,200,71,71,74,157,210,86,10,28,244,156,225,23,192,105,50,139,155,222,17,131,49,248,179,190, -13,21,184,234,160,209,248,4,57,211,9,121,223,163,233,147,241,19,185,210,204,99,216,121,14,83,49,25,127,159,21,180,97,241, -73,156,24,146,172,219,70,93,226,102,225,44,54,222,160,174,61,198,167,47,236,106,166,144,184,28,12,90,46,201,126,147,174, -17,22,255,191,111,197,116,171,16,100,89,223,52,22,143,56,139,143,154,116,191,81,180,217,38,186,92,136,175,179,252,39,133, -241,121,113,191,225,44,62,55,100,210,237,194,106,62,104,211,146,61,33,7,181,92,123,62,196,118,75,117,7,164,186,150,61,1, -58,67,252,192,112,54,65,244,83,194,108,49,252,187,141,109,213,33,83,92,37,10,90,174,104,217,108,26,223,54,10,175,219,108, -154,223,49,138,87,111,94,242,200,224,122,219,176,77,186,84,72,37,215,210,189,194,122,87,92,38,190,102,60,143,199,238,102, -252,235,166,223,10,114,86,135,182,172,222,219,220,108,236,170,246,155,244,13,209,66,15,129,89,220,221,77,143,49,165,183, -229,231,27,194,250,187,184,196,184,69,252,15,6,219,124,43,253,93,152,234,25,117,79,176,196,35,155,151,208,207,50,133,3, -134,169,186,82,29,209,125,198,9,186,249,140,161,186,57,200,148,14,25,89,133,107,182,24,23,84,215,40,161,27,101,229,205, -242,243,77,195,160,119,81,223,220,221,76,151,154,198,3,226,122,238,253,98,211,228,210,19,232,139,46,201,43,191,107,160, -252,132,177,15,229,37,171,175,163,143,115,213,109,170,234,178,188,242,229,92,126,79,149,15,112,249,91,134,44,239,231,14, -100,233,162,108,233,179,166,69,119,137,219,197,183,208,239,102,158,215,245,38,198,181,164,27,11,242,128,113,122,104,243, -112,207,218,115,122,154,109,50,246,118,57,136,30,144,149,161,152,41,254,191,40,221,251,136,92,196,230,115,108,178,197, -172,154,69,244,35,174,165,39,229,231,79,165,228,254,61,254,42,250,157,201,158,85,109,220,96,117,25,47,94,208,212,252,104, -200,40,222,13,155,237,217,179,103,111,12,238,35,250,149,190,197,61,182,160,95,216,114,105,133,207,107,25,111,139,154,222, -253,249,93,61,198,61,217,6,189,164,133,102,120,77,58,36,218,32,115,147,209,112,136,43,233,57,39,247,123,192,52,126,47, -250,177,40,47,8,33,156,54,153,2,133,71,44,147,53,10,195,22,14,18,133,54,57,68,141,109,54,75,141,119,88,226,110,182,106, -172,102,216,50,191,98,44,216,140,182,47,162,173,101,26,95,54,26,155,119,25,23,236,134,122,248,94,191,131,28,134,195,196, -136,143,138,1,122,205,41,238,192,64,150,8,223,52,139,88,249,115,1,155,102,213,156,66,15,88,230,139,226,57,241,10,87,118, -155,5,95,55,68,200,52,161,169,117,191,209,208,100,132,171,109,211,46,88,224,48,29,5,59,45,231,87,208,174,101,181,233,184, -93,148,180,192,69,222,16,179,91,118,154,198,205,198,204,102,12,149,70,108,3,91,231,182,118,116,111,218,14,219,105,156, -135,133,64,75,135,195,185,211,116,29,17,211,165,148,48,29,100,120,170,33,4,17,219,85,67,239,194,254,213,195,75,134,197, -140,105,166,128,141,254,203,22,139,187,107,248,201,248,147,104,132,121,109,163,133,141,140,29,90,211,27,218,115,254,229, -54,13,119,211,95,45,54,108,245,146,157,177,133,198,158,234,253,237,157,177,150,26,250,155,45,94,100,239,48,137,77,17,162, -27,45,233,191,242,243,18,199,212,186,59,156,226,16,236,184,215,116,98,202,75,238,149,139,231,55,141,55,133,184,237,54, -211,250,178,225,195,60,239,54,4,166,107,222,105,136,157,171,77,251,46,99,94,76,184,109,187,197,97,55,240,82,96,150,150, -109,219,14,99,164,218,118,46,112,8,88,219,226,201,26,23,116,161,194,97,148,175,98,41,246,54,167,113,183,241,186,112,54, -249,77,113,135,33,16,81,208,215,109,188,94,190,157,205,7,62,201,203,37,4,175,150,177,187,186,214,216,221,180,200,54,12, -239,42,68,160,79,58,48,42,103,241,5,85,244,176,67,60,37,247,146,233,66,91,12,237,144,16,126,211,249,128,176,107,204,130, -151,196,233,207,239,245,63,98,218,28,168,86,155,214,213,98,222,109,194,101,59,155,49,150,234,221,60,200,123,204,66,248, -221,189,162,196,107,23,250,141,209,106,12,212,190,213,114,191,195,235,185,179,165,251,244,165,182,123,161,154,12,175,137, -93,176,136,231,225,112,57,10,28,133,198,100,151,93,200,242,244,174,30,3,92,22,189,63,122,224,40,111,88,251,90,163,230,22, -219,244,35,24,162,103,140,237,17,83,168,97,16,134,177,223,178,101,31,33,135,217,210,221,39,29,195,14,181,74,227,89,116, -141,154,221,146,3,22,105,199,130,215,151,172,49,133,156,149,117,159,113,250,18,63,111,28,118,131,3,162,100,154,191,14, -158,208,221,51,140,230,176,188,191,10,6,122,208,161,98,195,31,45,131,157,24,165,63,89,114,153,233,74,27,195,112,54,169, -97,24,51,118,24,227,213,75,98,70,113,19,166,115,171,240,122,101,39,45,215,183,204,164,67,82,176,56,70,111,217,210,33,54, -211,87,212,158,53,130,59,140,225,234,37,7,100,44,178,209,241,217,182,1,47,128,29,121,114,176,64,55,142,139,213,150,245, -78,110,236,231,247,217,38,124,28,134,132,15,192,243,97,181,26,83,112,36,125,194,48,239,20,95,21,87,168,224,223,66,207,27, -226,98,195,89,221,49,243,190,102,147,190,100,52,210,47,57,200,98,115,154,244,59,209,246,13,155,186,17,195,110,118,97,103, -52,211,28,118,223,207,186,196,151,224,239,8,118,251,133,105,120,154,224,42,231,46,89,189,7,222,117,169,172,41,222,223,96, -156,95,173,56,70,80,252,220,168,104,52,230,26,151,10,171,224,61,81,94,104,156,10,78,149,85,254,225,114,187,124,168,220, -165,30,237,114,81,254,17,48,154,202,11,164,104,125,129,3,178,21,185,102,51,140,38,150,19,21,243,114,188,114,165,188,65, -113,12,112,138,115,197,29,57,185,109,198,41,220,214,168,168,175,152,157,233,110,88,246,94,159,235,159,25,103,148,47,207, -48,156,229,103,130,209,3,180,65,202,157,97,178,212,202,242,141,248,92,150,97,186,48,244,161,114,51,43,171,21,56,202,141, -60,150,28,139,27,99,137,169,177,56,42,26,42,230,84,212,86,4,42,106,42,234,68,185,37,76,81,96,86,26,248,39,140,142,125, -251,172,91,230,204,23,207,204,17,226,239,192,161,70,33,126,5,28,12,10,113,23,112,104,174,16,111,2,247,240,143,174,237, -181,23,185,72,28,3,108,98,151,67,255,230,70,208,146,139,246,89,135,79,53,47,54,168,71,28,104,18,214,43,77,134,184,178,89, -136,155,128,123,128,103,129,55,129,187,91,16,241,29,133,234,215,242,104,243,74,203,74,113,85,171,176,222,108,21,226,234, -54,244,11,188,2,236,107,39,67,184,139,13,113,85,96,8,98,87,183,135,197,61,237,66,252,0,248,21,240,58,112,203,60,33,238,7, -126,12,60,11,188,9,28,232,32,75,216,94,76,80,112,211,115,208,244,134,142,45,226,209,14,33,158,154,47,196,51,11,160,29, -184,122,33,57,157,165,101,74,76,255,55,10,217,103,23,26,198,193,78,97,220,180,200,48,14,44,6,186,76,227,234,238,66,227, -192,146,168,245,66,143,41,238,233,53,196,221,125,134,120,182,15,86,3,61,216,143,33,1,119,15,96,40,203,133,56,2,252,106, -16,163,15,9,113,120,13,250,2,14,172,53,196,161,181,224,175,67,27,224,224,122,88,192,152,37,248,223,199,208,231,85,27,46, -20,247,108,16,226,170,33,254,13,158,223,45,220,23,137,3,251,172,23,134,4,42,247,133,69,193,65,224,210,141,148,251,251,69, -249,191,225,201,252,109,62,254,109,74,230,239,243,241,239,82,50,127,163,143,127,151,18,32,245,119,250,248,183,57,153,191, -213,231,160,220,223,235,51,189,234,183,52,242,119,83,1,245,183,154,234,75,33,19,80,50,252,255,180,11,175,250,253,61,255, -127,232,70,64,245,203,127,223,207,212,242,252,255,75,91,1,245,187,27,254,127,170,237,128,26,31,255,127,240,164,245,200, -31,228,121,21,159,255,174,224,255,1,51,160,178,16,144,80,0,0}; +{31,139,8,8,116,138,97,92,0,3,74,97,118,97,68,101,120,66,121,116,101,67,111,100,101,46,100,101,120,0,149,124,9,124,219,197, +149,255,155,223,33,201,178,108,203,178,19,59,142,45,203,142,29,43,36,190,226,28,78,236,28,62,146,216,137,115,96,43,161,196,252,161, +138,173,36,10,182,228,88,114,14,216,46,161,7,9,45,44,148,35,77,41,165,180,92,225,40,176,20,10,109,129,210,66,91,216,101,129, +109,217,54,244,164,45,252,75,11,165,244,96,129,150,146,253,190,153,145,252,75,98,72,155,124,190,122,243,123,243,230,205,204,155, +55,111,222,252,164,100,56,182,207,219,212,178,144,238,255,216,139,247,126,127,96,206,252,159,157,251,212,223,22,175,248,196, +61,193,55,6,163,75,42,189,255,218,116,38,209,24,17,237,219,178,32,64,250,207,135,55,17,157,45,20,127,21,240,188,77,116,22,232, +43,46,162,16,232,187,94,162,123,152,230,18,229,128,166,11,137,174,95,78,116,13,52,188,93,79,244,87,224,239,128,209,64,100,3, +115,129,6,160,21,88,9,116,3,107,129,77,192,54,224,6,224,23,192,223,128,247,0,163,145,200,13,132,129,141,64,63,240,33,224,124,224, +34,224,114,224,38,224,86,224,14,224,30,224,126,224,235,192,163,192,227,192,83,192,31,128,226,38,162,197,192,54,224,106,224, +49,224,85,192,223,76,212,6,156,11,92,12,220,13,252,0,120,29,40,152,79,180,12,216,6,92,10,124,25,248,57,80,210,66,180,4,56,23,184, +24,56,12,220,5,124,7,120,1,248,61,96,44,128,237,128,79,1,143,2,127,2,66,11,137,18,192,253,192,111,128,105,139,136,22,1,91,129, +11,128,207,3,15,3,199,128,63,0,198,98,244,5,204,5,86,0,91,128,52,112,53,112,59,240,24,96,183,18,53,1,221,192,135,128,81,224, +98,224,48,112,23,240,8,240,44,96,45,65,127,64,24,88,1,244,3,215,2,183,3,15,2,63,7,126,9,188,12,252,14,120,3,120,27,120,23, +16,75,177,14,64,62,80,4,148,2,65,160,6,152,11,204,7,22,1,75,128,101,64,7,176,10,136,3,71,128,71,129,31,0,175,0,111,2,162,141,200, +11,20,0,165,192,108,160,5,88,1,172,1,206,6,38,128,75,129,207,1,255,14,60,14,60,3,28,3,126,7,252,9,120,7,112,183,67,15,80,9, +204,6,234,129,69,64,23,176,30,216,10,12,1,187,129,125,192,133,192,65,224,10,224,179,192,77,192,125,192,55,129,103,128,31,3,47,3, +127,4,222,1,172,101,208,15,172,0,122,129,17,224,114,224,26,224,75,192,221,192,253,192,55,129,39,128,231,128,159,1,239,0,211, +177,23,234,129,78,224,28,32,5,124,4,184,18,248,28,112,39,240,32,240,24,240,42,240,39,224,29,192,88,129,185,0,27,129,253,192,53, +192,93,192,3,192,183,129,255,6,126,13,252,30,120,23,200,91,137,249,3,97,96,49,176,18,88,11,108,2,6,129,115,129,33,32,14,140, +1,23,2,135,128,107,129,27,128,219,128,251,129,71,129,39,129,31,2,63,1,126,13,252,1,120,27,120,15,240,118,16,85,0,245,192,82,96, +57,208,5,244,0,27,128,205,192,121,64,12,216,5,140,3,151,0,95,0,30,0,158,6,94,6,222,2,188,157,68,51,128,57,192,74,224,76,96, +39,176,27,184,8,248,52,112,39,240,53,224,123,192,179,192,203,192,95,1,87,23,124,25,104,0,122,128,115,128,17,96,47,112,49,112,25, +112,53,240,5,224,86,224,27,192,143,128,55,128,255,5,222,5,220,221,68,133,192,76,96,46,176,16,232,6,54,0,231,2,49,96,23,240, +81,224,147,192,97,224,38,224,81,224,187,192,179,192,143,128,159,1,191,2,222,4,188,8,146,69,64,5,80,11,204,5,214,0,103,2,219,128, +36,112,17,112,25,112,21,240,89,224,102,224,43,192,55,128,111,1,255,9,252,16,248,41,240,18,240,7,224,109,192,88,13,123,1,11, +128,13,192,102,160,0,49,183,24,168,6,102,1,53,64,45,48,27,168,3,194,192,28,224,12,96,46,48,15,64,56,38,132,86,66,72,36,132,63,66, +152,35,132,52,66,200,34,132,40,66,88,34,132,30,66,104,33,132,13,194,246,39,108,89,194,86,35,108,7,130,91,19,92,142,176,132, +132,165,32,152,146,186,245,249,128,33,209,26,160,7,232,5,214,2,235,128,62,96,61,176,1,216,8,224,88,33,28,55,212,15,12,0,17,96,11, +240,33,96,16,248,127,192,121,124,254,0,219,128,97,32,6,108,7,70,128,127,1,46,2,14,0,23,3,31,5,62,6,124,156,148,77,50,127, +252,154,142,97,226,133,186,188,15,229,50,80,67,63,115,217,212,229,74,93,30,211,50,150,230,87,233,242,1,205,247,56,228,113,4,210,101, +154,159,171,249,51,129,60,224,26,205,207,119,244,85,224,40,7,28,242,197,90,158,203,165,142,182,101,142,190,202,245,216,88, +38,168,101,42,117,121,76,151,25,215,107,153,106,45,83,161,203,55,207,83,178,92,190,75,203,215,56,218,214,234,182,220,15,251,208, +67,122,12,13,142,113,54,58,198,214,228,24,27,151,31,155,167,242,2,46,63,57,111,146,159,177,103,179,67,79,179,99,252,92,126, +206,81,206,204,113,129,163,175,86,71,95,236,147,199,52,127,169,230,179,95,44,211,229,81,93,230,182,9,93,126,17,229,164,46,191,50, +79,229,52,92,254,11,202,187,117,217,194,230,216,167,203,62,148,199,117,185,20,229,148,46,135,80,222,163,203,243,80,222,171, +203,11,28,229,149,245,147,58,251,28,229,235,29,125,69,28,252,115,28,253,14,59,248,99,142,242,62,71,191,7,28,252,67,142,182,87,162, +188,63,211,151,67,254,40,202,23,232,242,189,142,182,207,57,198,195,107,151,145,127,210,193,31,115,148,31,118,244,245,4,202, +19,25,61,40,95,168,203,199,28,182,122,17,229,180,46,191,86,175,246,237,114,189,70,31,209,101,94,163,127,213,101,182,127,166,252, +152,131,159,241,159,14,221,150,203,157,14,127,232,114,248,67,183,230,207,212,229,107,164,207,55,209,131,164,232,10,193,109, +10,232,50,217,182,153,174,144,116,49,93,37,169,135,150,9,246,225,82,250,20,175,53,122,127,69,82,65,191,151,180,150,170,100,253, +108,154,43,56,46,20,75,185,42,205,175,210,252,89,250,153,233,38,193,123,204,162,207,16,83,63,253,69,82,85,95,163,235,107, +245,120,106,17,121,15,75,218,73,119,74,90,66,127,146,116,1,189,165,235,203,133,162,65,161,246,232,237,196,116,57,253,142,116,220, +23,28,251,43,233,211,92,134,228,155,196,177,206,67,79,72,106,210,119,37,181,233,199,196,177,206,77,95,144,180,154,30,209, +244,121,94,7,156,24,159,215,244,30,73,45,250,158,164,27,104,33,244,219,224,187,137,227,96,15,245,10,166,139,104,189,224,59,128,226, +123,179,212,75,71,36,205,161,85,168,247,105,61,121,186,62,15,156,35,146,230,82,151,80,180,91,112,140,204,163,111,17,211,42, +250,9,113,28,87,227,241,35,146,62,35,105,1,149,8,166,126,154,41,56,182,171,113,115,140,255,161,166,63,37,21,95,255,75,210,126,58, +38,105,33,189,160,249,92,95,172,245,22,227,148,90,9,61,211,244,184,74,112,42,61,37,105,19,77,19,76,151,210,116,73,151,81,179, +164,237,180,69,112,156,86,237,75,97,255,27,52,101,123,205,208,122,202,48,254,135,137,227,105,128,190,70,28,135,13,186,69,250, +225,42,89,207,126,167,168,160,199,37,173,165,255,148,116,51,125,95,210,53,36,164,191,206,165,66,73,231,81,64,210,51,169,70, +210,181,180,86,210,213,180,89,250,229,74,169,47,164,199,197,244,126,73,149,125,66,136,228,63,147,116,61,189,170,235,243,100,187, +62,42,146,116,29,117,10,197,239,209,180,79,250,245,10,169,183,74,235,173,210,122,171,180,222,42,173,175,74,183,175,210,237, +171,116,251,106,221,174,90,203,87,107,249,106,45,95,173,229,171,181,252,44,236,116,238,111,22,178,18,67,62,47,32,83,83,75,210,249, +100,75,186,144,92,154,186,53,63,95,211,2,73,155,201,175,105,177,220,111,157,82,111,13,250,191,78,210,106,250,182,164,46,250, +15,82,103,225,211,146,158,65,75,229,62,83,235,83,171,231,91,11,79,121,64,210,25,244,85,73,103,211,163,146,170,245,171,133,223, +60,41,233,22,122,86,210,205,244,156,166,255,45,105,17,253,64,210,26,250,31,73,103,210,143,36,93,66,30,217,95,43,229,104,234, +21,138,159,43,105,27,249,132,138,7,165,146,78,167,25,146,150,82,153,164,27,169,90,210,70,154,37,105,23,181,72,186,154,34,50,78, +212,203,121,204,70,230,117,175,142,19,191,144,241,225,12,204,92,81,183,164,211,232,155,146,150,209,99,196,103,253,92,201,111, +212,242,208,78,3,130,105,5,125,72,240,217,174,218,53,105,251,52,193,211,191,67,124,134,171,126,154,97,231,223,18,231,150,221, +82,174,5,158,207,251,97,129,110,183,0,114,135,244,243,245,250,249,70,73,235,232,53,253,60,95,168,60,96,141,164,17,234,23,156, +163,134,233,114,226,60,85,233,89,164,219,47,130,252,23,37,173,148,253,44,66,246,251,134,164,33,106,18,138,207,250,22,235,118, +139,117,255,139,117,63,139,117,63,139,117,63,173,24,255,207,137,105,144,222,35,206,59,212,184,150,106,218,166,245,180,33,219, +93,46,56,63,86,207,237,218,191,248,108,2,91,190,27,33,25,23,112,150,33,17,63,130,68,248,218,13,42,15,19,174,201,60,138,235,175, +68,253,51,27,212,115,72,183,103,254,135,231,41,122,35,234,127,167,235,171,116,125,147,163,254,33,212,207,222,168,234,103,105, +189,182,67,255,115,168,239,215,245,53,154,223,238,168,127,17,245,151,234,250,90,173,127,26,176,83,235,127,3,245,247,234,250,217, +186,157,115,252,43,33,23,222,164,158,235,28,227,203,212,111,66,125,167,174,231,28,252,195,184,24,236,88,175,228,198,53,189, +120,253,100,221,85,142,242,13,186,254,118,7,239,62,93,126,4,244,9,71,249,217,245,42,151,103,153,31,3,47,235,182,111,104,42,54,40, +26,208,180,78,211,118,77,7,52,29,222,48,217,215,94,205,251,232,6,214,109,200,242,185,171,213,61,99,204,159,135,231,106,248, +206,152,255,75,120,30,244,91,136,250,131,126,131,6,3,6,206,45,150,103,61,201,213,234,158,16,65,205,110,255,37,196,167,98,34,52, +130,181,246,202,187,129,165,229,246,172,86,119,136,221,178,23,159,72,132,12,236,39,200,250,109,249,204,231,129,137,58,150,253, +216,106,117,230,69,66,22,69,170,44,200,220,130,26,175,152,133,11,110,34,116,43,198,231,131,47,118,75,25,91,102,1,200,27,209, +102,58,219,220,127,59,250,244,137,113,255,109,220,198,104,53,242,192,59,138,50,183,241,81,32,144,104,90,2,79,10,191,145,175, +71,134,117,88,173,236,192,247,26,151,156,25,238,217,171,213,189,49,80,56,191,216,166,64,85,75,113,33,198,81,136,254,124,216,63, +185,20,105,230,113,241,45,202,103,36,66,55,193,119,3,29,45,197,149,136,95,211,168,204,56,159,118,135,154,193,155,108,17,56, +169,197,205,178,214,210,182,104,71,36,205,151,115,225,190,191,179,90,221,107,156,182,234,128,22,68,79,173,255,43,90,127,64,20,136, +72,179,178,188,144,146,255,34,45,21,126,211,11,77,172,253,127,86,171,119,156,129,146,128,75,235,131,30,47,149,89,208,99,231, +73,61,17,244,157,144,151,75,159,88,34,50,117,62,93,23,254,83,107,206,66,170,54,188,240,4,182,89,153,101,161,191,38,182,178,149, +8,249,113,6,85,155,121,168,11,192,114,137,80,49,178,101,230,79,195,45,215,103,5,106,185,20,161,89,233,149,232,161,0,173,125, +246,122,219,114,237,246,95,167,218,251,139,208,202,103,39,86,230,82,199,39,194,143,36,66,62,220,128,195,95,163,172,127,229,172, +81,119,210,19,253,235,34,248,87,62,242,52,151,242,249,53,234,30,58,230,111,64,155,193,89,57,52,88,227,162,193,90,47,109,157, +237,129,229,207,9,185,229,218,218,210,191,4,205,94,163,98,73,192,140,116,184,168,85,184,137,105,194,63,23,117,145,142,28,112,114, +36,141,116,122,209,215,191,194,206,131,93,208,217,229,130,150,60,189,2,1,189,2,225,87,84,60,98,221,66,212,227,248,18,114,76, +109,232,131,99,103,194,207,25,127,210,127,153,246,175,140,143,119,173,81,113,52,18,66,63,85,220,207,184,244,235,66,25,71,132,244, +203,222,53,106,175,6,96,57,161,121,27,215,76,250,106,62,230,207,119,247,205,107,212,222,90,150,235,163,129,139,61,228,62,224, +190,90,220,44,30,176,190,187,199,211,161,101,45,125,251,143,57,218,27,122,44,187,215,168,152,24,241,231,40,79,245,195,42,144, +216,236,119,75,95,225,231,68,104,30,198,23,240,159,227,119,159,208,246,130,15,104,219,154,109,91,207,109,41,211,150,199,194, +99,248,152,94,219,49,63,223,58,6,133,143,6,141,92,26,132,55,229,103,215,234,106,199,90,229,234,181,202,133,85,103,201,181,242, +233,181,242,97,173,242,178,107,5,61,93,185,255,196,90,29,205,174,85,239,148,107,117,111,118,173,208,79,85,222,148,107,245, +213,204,90,193,19,221,122,134,15,131,87,196,237,154,245,200,65,19,43,107,168,35,54,57,182,14,161,199,246,150,122,39,163,199,230, +205,172,247,15,28,235,149,225,189,224,224,153,114,148,200,103,214,168,247,56,131,162,0,246,100,171,14,26,249,50,78,171,183, +65,175,58,218,100,124,225,205,41,120,162,199,25,11,45,57,167,188,30,21,143,3,161,22,187,0,62,144,128,207,154,176,6,71,13,15,239, +95,196,157,143,200,219,204,164,158,242,158,83,117,135,167,224,45,152,130,183,178,231,196,249,241,159,190,41,120,91,28,60,91, +90,14,231,90,15,71,8,182,67,17,236,240,101,105,7,156,91,102,33,13,90,60,66,75,90,209,164,221,61,234,61,80,185,81,67,21,70,64,12, +54,7,176,242,119,163,6,62,219,236,199,122,21,72,154,128,207,10,93,226,104,195,146,126,60,23,66,194,43,105,194,95,170,249,133, +20,52,112,215,19,65,163,78,228,137,240,91,60,155,25,168,171,148,227,51,101,254,225,146,62,53,231,162,250,121,115,116,217,160, +127,235,81,57,105,185,137,177,152,145,126,232,54,102,17,211,132,127,6,199,76,145,240,87,203,211,44,176,160,165,115,58,184,85, +28,237,141,50,235,106,248,98,35,34,48,159,109,38,246,155,39,123,194,4,205,66,160,12,203,23,126,55,15,165,58,67,229,15,179,209, +178,94,251,146,192,253,43,227,191,119,245,168,250,136,223,159,245,107,174,185,175,39,115,190,171,209,200,248,235,87,111,10, +213,249,174,246,233,67,61,124,143,196,28,4,230,32,34,77,1,140,38,143,34,77,133,124,7,32,126,142,204,135,134,208,30,68,238,160,96, +255,15,138,58,82,125,22,201,190,138,177,111,212,89,254,120,143,122,135,26,176,198,252,181,176,220,96,164,152,6,183,20,195,34, +197,84,102,254,25,90,74,112,35,242,25,149,70,9,13,246,151,128,95,66,75,112,62,149,25,216,81,230,78,185,219,113,115,194,153,181, +8,62,240,57,62,19,250,103,224,105,1,158,174,146,79,165,39,212,149,157,240,52,77,234,75,248,103,179,229,169,202,12,24,11,231, +123,105,53,166,153,8,165,17,235,142,25,6,214,86,202,52,205,161,126,43,252,180,71,159,85,111,244,168,220,51,50,84,138,246,135,121, +103,88,156,135,88,228,53,91,205,38,153,135,88,114,220,65,138,32,168,85,154,126,105,83,115,114,133,205,192,194,150,129,223,28, +215,43,108,150,217,106,133,7,178,43,252,194,113,189,194,102,34,244,111,56,107,89,243,179,199,11,141,128,17,126,207,165,199,49, +173,87,189,31,143,116,150,65,255,23,120,30,198,184,255,14,77,111,3,229,86,51,229,120,12,169,185,145,34,93,144,13,221,32,199, +82,137,53,76,248,135,228,8,184,151,1,41,255,210,241,66,17,16,225,247,56,206,40,239,105,234,85,239,218,203,237,90,170,176,35,105, +158,245,17,158,173,53,171,179,147,207,130,180,178,3,230,236,18,50,35,115,161,174,213,154,46,123,118,65,123,165,25,164,99,136, +119,17,92,137,42,45,101,13,206,11,214,91,134,16,34,252,155,160,93,104,228,89,65,187,206,98,191,104,144,189,54,74,63,225,191,59, +122,213,59,250,114,11,253,195,87,70,57,215,50,7,7,166,83,208,154,92,237,68,232,124,138,241,76,212,138,152,60,14,83,142,35,40, +199,161,102,60,91,70,245,105,50,119,185,26,59,42,225,255,40,175,135,181,219,127,169,206,116,152,27,254,85,158,25,180,234,76,158, +39,91,113,86,19,103,164,159,101,43,98,46,200,72,77,30,39,71,140,197,217,61,182,136,92,122,55,93,165,215,165,220,192,120,141, +72,135,178,142,152,28,147,104,21,165,153,49,97,85,130,242,182,91,41,148,93,100,126,231,175,148,249,93,203,153,175,28,15,26,28, +111,2,20,254,187,138,56,106,47,213,201,158,194,196,121,39,247,123,103,175,250,14,162,220,139,62,189,173,254,98,10,204,72,52, +165,232,250,60,31,214,116,3,69,190,53,3,51,248,60,114,113,238,221,141,85,11,122,11,41,48,39,252,122,127,243,76,26,11,237,198,29, +216,231,110,117,47,161,200,30,140,197,133,200,232,106,129,84,43,252,181,191,185,2,109,103,34,239,246,33,207,206,193,109,33, +68,238,111,89,47,237,113,241,27,210,86,204,128,181,87,91,45,208,115,37,172,152,104,186,145,154,173,160,55,252,28,70,236,173,19, +170,125,25,183,247,180,122,126,123,188,26,231,224,24,130,255,99,173,225,95,5,189,152,217,195,36,239,243,93,152,17,127,247, +162,178,160,53,210,255,120,253,231,129,201,239,23,3,174,72,10,187,49,116,6,246,131,242,133,72,106,26,108,118,61,159,82,41,181,7, +212,202,223,33,179,77,182,182,45,125,186,68,90,219,150,30,208,168,100,177,7,138,228,106,242,30,216,6,249,240,111,213,154,7, +204,65,212,7,161,123,198,201,26,29,187,188,204,177,203,103,147,148,133,198,89,82,99,11,218,125,90,182,171,52,171,33,183,137,181, +255,122,112,207,12,200,77,21,49,170,51,186,148,47,152,130,90,17,49,152,86,154,150,60,153,76,199,147,75,62,101,162,74,41,235, +254,137,210,123,13,202,156,213,112,246,197,121,204,28,216,111,129,206,111,176,139,169,64,223,21,182,173,85,239,167,39,247,78,228, +135,211,169,106,71,192,20,11,23,78,180,96,175,185,236,132,63,196,209,199,83,117,41,184,11,22,94,83,131,200,232,65,30,31,148, +209,171,229,215,211,169,218,59,59,115,66,228,6,138,90,150,195,155,138,18,254,10,174,247,141,173,60,64,95,127,156,247,210,151,232, +152,105,9,177,32,252,139,128,25,254,227,49,211,22,98,97,248,153,66,195,214,123,251,208,90,181,87,2,161,114,129,21,133,231,95, +193,86,49,150,24,33,120,83,28,62,193,62,135,155,153,63,200,39,245,252,105,210,139,59,229,141,207,141,179,37,252,191,234,132,25, +11,237,210,178,22,115,95,13,138,128,204,3,121,190,51,49,38,158,77,142,180,67,69,54,167,253,252,90,229,119,1,255,88,40,33,111, +142,133,217,186,155,50,117,161,201,186,128,30,243,237,107,213,247,123,30,240,54,123,202,169,213,179,1,247,46,181,243,60,200,143, +34,185,176,230,195,1,143,184,116,225,83,107,97,183,220,156,128,161,119,180,135,219,244,231,205,164,150,99,75,136,109,12,205, +121,85,63,15,120,22,190,210,76,171,173,60,15,91,24,243,119,181,236,97,235,86,72,15,145,109,10,42,168,229,207,229,224,149,179,167, +216,108,39,248,54,246,83,126,38,79,240,148,229,252,77,158,34,71,80,95,105,55,243,14,183,3,117,225,251,143,121,60,34,252,220, +49,79,142,16,151,134,31,12,122,203,144,28,135,255,156,231,193,222,244,112,6,217,143,214,31,206,198,177,243,178,119,227,226,117, +234,59,56,142,172,93,50,42,201,40,230,184,115,23,56,238,220,179,101,44,133,55,24,45,253,111,28,103,107,241,25,98,105,159,155, +179,78,221,221,248,252,227,220,43,208,220,226,55,225,167,17,156,241,1,145,88,25,166,166,64,248,29,126,167,109,200,28,170,5,242, +55,114,60,203,131,85,243,210,98,6,239,86,15,91,209,3,235,4,220,129,28,190,241,69,124,101,184,45,126,146,207,152,124,246,139, +215,224,123,75,124,231,202,94,32,231,11,44,111,121,173,137,35,33,44,228,33,159,175,44,95,157,179,175,73,11,225,156,181,213,237, +28,145,193,82,250,148,127,245,161,109,171,175,157,38,121,55,128,231,115,87,186,45,7,239,139,224,85,231,214,72,142,7,254,230, +49,144,139,158,21,165,220,170,83,198,134,136,248,90,110,117,126,27,246,219,157,152,117,107,78,19,21,249,158,115,95,252,152,237, +231,156,17,59,121,229,93,244,168,63,152,151,159,25,143,143,117,240,14,120,140,124,222,86,47,188,234,46,65,238,199,42,97,77, +55,226,180,71,198,5,183,140,7,110,74,227,60,41,162,96,94,248,151,121,190,96,94,157,175,200,55,76,225,239,231,249,194,111,241,89, +49,129,21,226,239,215,216,155,14,103,115,185,35,226,162,250,27,196,17,193,121,159,33,115,237,7,215,169,239,225,202,221,176, +185,155,207,34,47,78,116,182,57,206,115,35,114,88,205,199,224,117,128,141,46,193,58,180,186,16,77,15,35,246,132,174,165,91,241, +188,196,85,75,39,202,221,0,57,159,171,210,197,81,118,88,230,2,39,214,127,17,245,172,161,218,19,164,177,166,249,116,212,228, +168,113,9,5,221,249,164,71,96,241,234,113,110,81,230,81,171,119,137,140,227,88,61,81,46,173,37,100,174,42,169,139,45,22,199,234, +182,218,240,173,1,21,51,91,77,143,142,162,42,122,114,212,244,82,248,217,60,87,248,191,242,92,65,119,157,75,157,169,156,87,140, +233,120,185,87,238,7,68,169,139,234,211,123,104,143,180,17,199,6,119,159,250,158,191,28,51,173,144,182,241,146,215,230,243,243, +108,249,254,103,125,54,54,71,4,122,23,22,5,138,3,211,90,144,248,4,172,200,181,234,68,177,229,217,116,135,166,242,140,194,138, +190,126,92,159,81,242,68,233,175,155,73,90,187,167,229,224,235,199,101,91,88,179,86,122,174,58,97,108,89,86,39,12,34,100,113, +248,233,86,225,209,55,25,117,139,137,92,203,107,243,25,212,6,93,200,197,237,160,171,206,86,115,61,91,238,245,173,217,119,99, +107,251,78,188,15,242,253,185,191,47,27,31,55,93,72,243,35,62,153,109,24,178,238,156,62,117,15,229,119,113,1,99,172,255,66,234, +242,115,125,142,220,239,6,13,247,169,223,84,4,138,56,246,241,174,225,53,226,111,45,57,35,51,169,192,80,39,56,191,67,116,195, +130,173,54,162,185,21,126,19,119,24,171,206,136,140,79,35,221,202,14,184,120,247,244,187,34,227,197,224,77,151,217,109,96,90,181, +107,22,252,101,11,237,113,39,86,226,4,140,249,144,213,96,197,29,237,100,43,17,180,196,252,240,127,240,189,245,32,133,255,170, +222,121,122,49,83,254,158,117,174,180,65,105,54,70,93,222,167,222,15,100,98,82,29,98,146,186,135,42,43,29,214,246,136,248,103, +200,221,207,239,166,148,95,88,244,133,62,245,219,15,158,163,87,238,12,142,110,106,39,69,14,171,168,114,171,228,227,30,115,88, +69,148,91,229,90,195,63,13,181,106,134,92,53,67,215,127,17,245,124,170,221,46,189,57,128,186,24,123,138,204,86,236,236,46,163, +236,46,98,207,135,44,172,11,223,235,58,225,214,225,74,132,246,98,87,168,108,33,232,14,223,175,188,62,79,148,185,112,147,242, +20,201,155,212,37,56,19,212,123,154,110,88,101,131,190,127,159,169,227,197,89,58,223,53,104,224,162,250,45,3,217,119,59,127,238, +115,190,219,57,91,204,164,115,140,114,58,219,172,144,111,173,212,173,211,92,175,222,201,7,196,18,196,220,2,156,35,151,203,204, +137,41,103,242,45,243,223,58,238,222,196,39,72,127,103,5,245,163,109,203,252,215,143,111,238,44,167,205,102,57,202,175,28,239, +239,156,9,62,78,205,249,191,58,30,40,12,255,34,243,142,15,231,197,122,245,125,68,53,238,11,253,29,51,233,251,129,3,160,21,84, +100,30,160,133,205,69,178,124,111,213,88,232,32,199,88,255,33,121,62,109,238,192,153,141,157,18,248,227,125,85,5,162,72,92,68, +225,63,64,235,95,213,13,25,103,227,122,237,247,176,89,37,16,196,92,50,239,144,26,215,171,156,66,205,55,95,199,82,131,22,172, +215,239,175,196,228,219,87,147,10,133,126,223,138,179,242,111,199,203,141,58,220,7,182,137,32,45,17,200,162,69,37,214,172,5,25, +121,28,156,160,228,135,95,201,228,248,133,82,135,208,223,149,8,153,193,152,186,175,13,235,213,119,52,101,164,238,200,134,236, +205,146,223,19,151,35,23,170,16,219,80,191,132,56,83,175,69,31,59,208,150,103,18,148,252,240,235,153,59,121,158,238,163,34,219, +71,185,252,13,172,252,158,65,219,130,231,122,99,64,125,199,116,20,244,222,236,175,108,213,159,135,245,115,70,158,127,99,245, +36,120,207,157,196,95,171,249,63,61,169,253,43,39,61,255,37,160,250,204,252,134,137,223,57,90,69,234,183,69,197,69,234,187,152, +242,34,245,155,9,31,232,78,173,55,14,90,131,231,243,65,231,21,169,223,126,44,0,109,47,58,81,127,207,73,207,130,244,247,89,164, +98,25,83,254,45,143,33,105,179,140,149,5,200,150,109,93,87,238,152,147,32,149,211,24,250,201,212,116,57,77,254,78,43,243,61, +146,33,105,147,124,158,173,249,93,89,57,159,110,171,126,101,162,250,90,145,109,47,247,80,22,211,101,236,50,180,141,76,135,173, +20,207,165,121,46,201,83,101,119,86,87,142,166,126,77,3,90,38,160,245,50,175,136,38,191,63,147,239,25,100,6,173,108,207,178, +252,125,242,108,221,47,127,151,59,91,239,197,90,252,181,52,245,235,248,176,68,183,89,162,239,36,44,215,174,247,210,114,93,183, +66,143,223,202,150,229,172,195,100,134,123,113,135,153,67,53,77,45,157,173,77,171,22,118,212,175,234,94,213,90,191,160,179,165, +165,190,99,241,194,230,250,69,93,171,90,22,172,234,90,208,181,184,9,166,69,190,214,62,52,18,79,196,211,203,201,213,174,168, +177,188,141,172,229,109,115,182,240,39,202,254,206,145,137,88,58,153,76,239,92,31,77,68,119,196,198,105,233,201,156,80,108,124, +60,57,190,52,52,148,156,24,25,14,37,146,233,208,142,88,58,148,149,10,245,173,10,165,134,162,137,4,218,174,248,199,218,14,199, +182,71,39,70,156,58,162,195,209,177,52,20,148,117,79,140,142,238,207,242,215,68,211,233,174,232,200,200,182,232,208,249,36,122, +201,232,237,35,179,183,175,143,42,123,55,134,86,237,27,138,141,165,227,73,4,243,157,241,145,88,104,104,36,153,138,39,118,132, +198,146,227,105,170,237,221,248,126,245,163,241,225,56,134,176,39,62,20,35,177,150,172,181,155,187,86,81,225,218,137,161,216, +122,212,244,38,198,38,210,155,88,69,32,195,218,56,145,206,240,124,25,158,124,42,206,60,13,76,140,113,175,13,187,162,123,162, +36,250,200,232,235,37,179,175,87,126,160,7,124,32,179,192,176,205,62,124,88,125,125,91,251,168,166,47,154,24,30,79,198,135,27, +183,101,102,219,152,157,119,135,50,71,27,205,250,32,169,110,57,135,54,170,250,32,33,54,97,27,205,57,157,72,198,202,109,212, +120,90,209,157,209,241,232,16,134,23,79,165,227,67,109,52,247,116,13,186,99,169,161,241,248,88,58,57,62,245,64,70,98,147,242,125, +177,1,229,75,83,207,29,162,92,63,57,218,247,209,199,66,171,227,35,24,100,77,231,68,124,100,152,245,77,101,166,19,68,63,80,164, +63,150,130,203,78,61,91,45,50,16,75,167,225,96,169,201,46,63,96,10,25,225,54,154,145,21,26,74,38,210,177,68,186,177,139,233, +62,116,86,153,173,26,141,13,199,163,141,236,186,141,236,112,153,165,159,247,193,2,189,137,237,201,26,118,85,46,56,135,243,190, +210,109,84,251,193,66,3,233,104,122,2,163,174,126,63,177,236,6,114,186,210,73,50,58,58,212,40,149,147,171,185,248,116,13,54, +38,84,147,141,99,177,68,108,184,15,30,24,147,190,18,58,77,195,15,152,251,228,238,118,174,255,73,66,253,177,161,88,124,15,235, +41,202,138,36,83,141,157,19,137,225,17,44,67,177,147,217,19,101,38,68,75,156,220,77,209,241,161,216,200,230,137,248,112,27,5, +178,21,19,233,248,72,99,95,114,199,41,188,77,209,248,184,163,175,44,175,141,54,159,202,108,63,141,155,156,54,62,224,32,104, +234,27,74,142,54,142,39,71,226,141,187,16,213,26,79,10,109,53,39,71,246,54,106,62,77,139,83,34,106,27,205,255,7,155,56,215,100, +222,63,216,70,73,247,157,70,122,210,42,89,31,124,223,19,167,141,186,255,105,109,147,28,118,209,72,52,117,254,233,13,117,138, +150,211,79,58,51,225,77,209,244,78,14,19,31,40,205,155,117,56,58,178,39,126,126,35,66,107,18,27,24,135,98,227,170,132,62,16,187, +70,162,41,108,232,224,20,50,189,28,137,117,125,213,20,245,235,99,163,219,180,64,12,34,21,83,136,12,196,119,36,16,49,198,177, +75,202,166,168,142,236,28,79,238,69,211,105,125,124,118,54,198,147,141,142,131,187,141,10,21,123,36,154,216,209,168,199,81,228, +96,245,34,78,74,123,5,28,204,141,219,118,197,134,210,39,242,6,210,227,152,105,182,27,201,147,93,71,183,241,254,45,119,176,199, +99,219,27,207,138,69,207,239,143,109,143,141,199,18,72,18,42,62,168,150,55,191,172,150,187,177,99,124,60,186,159,195,82,166, +167,19,185,109,212,53,21,187,253,159,89,237,229,124,232,77,169,228,148,233,46,207,26,97,82,52,117,34,175,39,154,194,142,30, +203,88,213,201,59,85,16,103,214,41,130,224,157,104,130,94,156,164,81,121,214,23,56,184,210,38,254,147,24,109,212,114,18,167,253, +180,7,240,242,19,245,202,238,11,29,140,72,124,148,29,98,218,201,44,181,21,11,79,217,107,212,113,10,107,234,164,213,113,154, +132,82,251,113,240,140,134,82,177,113,153,69,6,78,221,245,228,115,46,26,213,58,143,252,134,174,142,190,190,206,142,174,117,231, +69,206,222,180,234,188,245,29,145,174,158,243,250,54,14,68,72,108,33,99,11,178,198,45,200,115,173,45,189,91,123,201,181,101, +45,242,200,181,96,35,123,220,130,180,210,218,194,121,165,189,69,114,193,145,31,44,221,167,42,81,182,249,115,173,34,200,69,183, +108,37,129,244,19,202,12,228,157,198,96,39,85,15,158,62,21,170,31,252,167,82,139,154,127,64,28,123,119,112,138,125,122,2,51, +179,81,115,163,67,67,177,84,106,245,72,116,71,138,188,72,55,39,162,35,50,231,118,103,174,10,102,116,120,152,159,134,199,33,71, +62,221,123,111,98,56,182,15,173,213,147,108,225,141,142,141,233,140,138,92,209,148,242,196,109,39,165,218,84,150,229,244,173, +146,123,79,173,237,230,205,189,221,20,216,118,74,122,234,208,144,113,164,226,73,78,118,218,41,135,220,121,250,206,145,179,45, +221,161,71,237,217,150,86,114,16,211,165,20,31,232,48,1,185,182,165,249,48,34,123,27,103,147,228,27,210,167,82,100,255,88,140, +92,24,5,210,9,202,31,58,33,25,39,123,104,36,22,29,103,146,76,197,200,141,132,50,1,27,83,174,46,72,133,30,78,51,163,241,68,74, +178,101,105,93,108,191,20,150,54,242,233,66,36,185,25,58,108,236,130,68,154,196,48,121,135,179,121,60,185,244,92,60,138,194, +70,153,210,48,229,101,74,74,65,238,112,214,1,82,153,186,140,201,188,234,81,38,59,57,195,241,113,12,17,97,31,236,120,42,51,116, +87,108,55,150,62,69,57,114,83,118,37,135,97,192,88,230,128,160,134,237,81,92,237,134,67,233,100,104,104,60,22,77,199,66,219, +38,70,244,157,82,233,14,109,31,79,142,134,50,110,226,217,30,79,68,71,226,23,196,168,10,165,225,201,133,90,157,28,119,220,190,148, +112,37,139,100,54,244,84,2,246,246,248,56,156,201,183,29,38,26,206,44,184,151,59,84,110,76,214,14,54,120,14,127,42,99,152,136, +36,228,197,71,70,69,46,151,71,164,107,167,168,140,31,148,231,158,114,45,159,57,89,119,106,12,155,198,149,99,99,35,241,33,121, +170,102,188,189,8,236,83,6,93,234,100,58,115,122,169,229,212,139,24,121,192,150,103,47,21,162,212,173,238,238,153,109,147,35, +89,210,23,242,179,69,181,214,222,236,115,138,220,40,75,231,155,131,66,207,196,40,135,115,108,100,28,190,202,82,83,90,23,162,112, +44,73,134,165,6,214,75,213,40,240,1,121,138,49,54,68,71,153,217,219,157,162,186,83,101,100,22,122,138,96,248,84,65,149,123, +158,34,57,3,146,92,125,242,48,49,185,233,186,170,59,235,204,152,142,30,50,107,144,139,204,43,44,31,242,50,15,19,156,59,145,95,63, +242,49,193,205,186,165,189,149,63,72,209,241,228,88,108,60,29,71,63,5,120,236,143,141,38,211,177,76,208,0,99,64,30,69,58,90, +201,46,101,128,200,219,41,111,33,250,222,66,238,157,209,212,6,118,9,15,10,59,229,46,178,118,38,225,187,57,252,169,124,83,196,201, +140,15,239,35,43,206,102,182,227,114,17,115,226,217,247,33,185,241,84,118,242,252,208,165,118,104,12,19,141,167,86,141,142, +165,247,115,65,218,153,171,39,95,164,120,226,58,37,32,15,167,55,61,220,175,111,151,243,69,138,121,62,2,144,11,31,156,97,120,71, +146,136,117,42,144,187,71,181,135,91,124,158,144,119,52,107,102,42,28,61,101,27,228,143,158,176,10,148,59,234,8,196,198,232, +40,153,163,169,29,248,72,79,144,149,224,181,176,249,19,81,33,17,219,203,123,0,70,73,176,145,204,228,182,93,228,74,110,223,158, +194,112,2,201,68,103,52,61,180,115,50,7,73,81,9,246,216,9,129,23,79,137,29,176,68,241,201,21,236,230,52,237,100,238,89,227, +48,137,212,162,108,136,61,43,251,87,106,200,159,76,76,190,51,145,26,10,157,28,213,58,47,169,239,194,112,68,244,156,159,60,225,106, +204,125,58,159,187,99,35,209,253,96,23,100,216,236,72,123,156,114,42,8,100,38,226,78,38,86,143,76,164,118,146,47,153,88,159, +158,200,176,49,50,30,143,242,194,254,84,42,78,165,204,25,137,243,86,150,227,234,74,142,142,33,2,67,22,45,101,66,33,35,116,230, +73,89,16,198,69,54,148,144,246,210,174,155,234,230,152,143,43,54,100,139,224,242,137,147,98,20,121,153,169,203,121,92,158,116, +176,18,126,60,225,170,121,86,60,189,19,91,169,52,83,49,121,161,212,53,129,76,141,131,151,207,60,199,203,190,28,126,86,59,209, +147,204,228,117,57,153,18,2,20,6,199,135,88,114,178,137,157,220,203,33,179,104,12,238,119,242,4,202,166,96,14,164,99,99,145, +189,73,42,57,161,110,50,152,144,53,198,233,163,37,223,105,230,140,201,116,139,247,133,103,76,103,94,170,36,3,75,126,166,164,35, +150,172,145,217,103,94,166,164,54,186,172,144,81,34,63,83,138,36,87,227,172,35,123,76,206,214,228,45,60,125,60,182,131,223, +175,140,159,248,146,134,92,227,210,115,200,171,168,10,13,170,172,242,173,25,227,56,178,99,169,244,164,111,111,26,143,39,225,27, +251,185,173,92,126,247,184,222,72,96,164,247,68,71,200,26,103,95,50,199,39,18,84,152,202,102,161,250,61,26,21,165,28,217,115, +134,233,206,188,116,246,164,134,118,198,134,113,236,147,43,21,67,218,48,76,86,138,125,171,140,63,213,219,222,157,209,225,80,239, +198,208,100,222,224,225,58,54,51,21,96,143,119,57,83,171,92,48,216,83,215,115,144,204,231,7,157,9,78,196,135,81,185,147,47, +5,216,43,152,168,149,226,68,194,78,201,135,28,73,184,33,229,169,98,58,57,38,31,93,41,117,188,90,41,112,208,115,134,159,3,239,201, +172,114,122,103,28,198,224,207,154,38,84,224,194,130,70,163,99,228,78,39,229,173,141,60,233,164,206,41,166,77,36,166,242,174, +25,39,177,29,62,84,58,145,120,159,181,180,97,251,9,156,14,146,108,220,78,203,197,205,194,157,111,188,65,109,251,140,43,47,106, +171,167,141,226,147,96,80,175,36,7,77,58,44,44,254,71,96,249,116,155,16,100,89,15,25,75,135,221,249,199,77,122,208,200,219, +106,19,125,74,136,251,88,254,10,97,92,39,30,52,220,249,231,247,153,116,84,88,245,71,108,90,182,175,207,69,13,135,47,128,216,126, +169,238,144,84,215,176,47,68,17,241,61,195,61,15,162,87,8,179,193,168,216,107,236,168,232,51,197,167,69,78,195,39,27,182,154, +198,195,70,238,103,182,154,230,35,70,254,186,173,203,30,239,221,104,27,182,73,151,8,169,228,48,61,40,172,119,196,65,241,101,227, +87,120,108,175,199,159,118,250,181,32,119,197,134,15,175,219,95,95,111,76,84,84,154,244,21,209,64,223,6,51,191,189,157,14,25, +60,129,167,248,137,222,147,159,111,11,235,175,226,98,227,22,241,125,12,185,254,22,250,152,97,170,103,212,61,203,18,79,108,93, +70,47,100,10,215,26,166,234,80,117,71,79,24,83,116,118,131,161,58,123,65,118,118,139,252,124,96,82,237,134,243,140,11,51,162, +119,202,202,123,229,231,39,77,131,222,65,125,125,123,61,221,96,26,223,16,215,243,24,174,51,77,46,61,141,30,233,122,71,249,211, +92,126,218,120,15,50,203,214,125,134,110,228,199,219,84,213,151,28,229,163,92,254,155,42,223,202,229,175,27,178,124,51,119, +32,75,71,178,165,127,55,45,250,178,56,42,30,134,206,173,60,187,175,155,24,215,178,118,44,206,215,140,21,125,91,7,151,111,56,119, +121,189,77,198,190,54,23,209,11,178,178,47,110,138,151,69,209,254,199,229,130,214,159,107,147,45,102,86,46,161,223,113,45,189, +46,63,255,34,37,15,238,11,150,211,71,45,246,178,10,227,144,213,102,188,115,225,188,250,39,250,140,252,189,198,158,138,125,251, +246,237,143,163,27,209,165,244,45,93,110,11,122,219,150,203,44,2,126,203,120,83,84,118,28,116,118,245,61,238,201,54,232,144, +75,9,77,247,155,116,187,104,130,204,13,70,237,81,174,164,215,220,220,239,33,211,248,255,162,43,104,210,75,66,8,183,77,166,64,225, +113,203,100,141,194,176,133,139,68,174,77,46,81,105,155,245,82,227,147,150,184,27,230,88,22,175,28,180,140,59,141,133,131,162, +216,111,138,219,141,186,125,198,254,111,179,196,42,151,129,177,254,93,172,162,191,186,197,29,188,0,34,80,96,17,43,252,101,200, +166,153,149,103,208,79,44,243,118,241,75,241,42,87,182,155,57,247,24,162,207,52,161,162,241,160,81,51,207,216,92,97,155,118, +206,66,151,233,202,217,101,185,239,70,187,134,117,166,235,168,40,108,128,91,252,65,204,106,216,101,26,95,52,102,212,99,120,180, +205,54,176,117,110,109,198,140,76,219,101,187,141,221,48,62,90,186,92,238,93,166,231,55,98,154,148,18,166,139,12,95,5,132,32, +98,123,42,233,58,11,142,57,184,108,80,76,47,192,216,69,211,119,108,177,180,189,146,159,140,55,68,29,76,106,27,13,108,88,236,208, +202,142,190,125,23,92,106,211,96,59,253,208,45,167,142,121,223,107,44,220,122,216,36,216,251,126,158,123,32,126,203,158,37, +125,115,108,177,9,91,248,136,92,151,138,101,187,226,139,140,125,21,7,155,91,227,13,149,244,109,139,151,250,17,249,121,191,91,220, +10,53,251,77,55,38,190,236,1,185,108,65,211,248,147,16,183,222,106,90,208,134,217,222,109,8,76,218,60,106,136,93,235,76,251, +14,99,126,92,120,109,187,193,101,215,178,137,49,87,203,182,109,151,49,84,97,187,23,186,132,203,112,89,60,101,227,194,54,84,184, +140,146,181,44,5,63,251,181,219,184,219,184,157,29,160,184,192,164,219,140,186,243,49,192,235,92,232,209,157,127,97,57,61,227, +18,207,242,90,174,51,61,24,3,186,189,93,136,160,233,254,134,176,43,205,156,87,196,138,95,237,15,62,110,218,28,138,214,153,214, +181,98,254,173,194,99,187,235,209,79,249,94,30,192,253,102,46,188,233,1,81,232,183,115,131,198,112,5,6,97,223,98,121,223,226, +21,219,213,208,190,98,165,237,93,164,6,202,86,183,115,150,240,24,93,30,87,142,43,215,72,183,217,185,44,79,7,221,106,12,112,68, +244,254,196,161,227,188,13,237,195,70,240,102,219,12,26,219,43,208,51,198,246,184,41,212,48,8,195,56,104,217,178,143,62,151, +217,208,222,41,151,222,94,215,36,13,99,209,151,212,236,150,29,178,72,187,14,124,185,112,189,41,228,172,172,7,140,21,203,130,188, +29,120,161,15,137,194,130,96,53,214,186,125,249,32,154,195,170,193,114,24,232,41,151,218,241,151,219,6,187,41,74,87,217,130, +173,72,119,217,24,134,123,158,26,134,49,109,167,145,168,88,22,55,242,230,25,123,218,110,17,126,191,236,164,225,186,134,25,244, +61,41,152,31,167,107,92,114,177,183,210,211,106,187,26,225,157,198,96,197,178,67,7,57,174,216,232,248,28,219,192,10,195,142, +60,57,88,160,29,7,194,58,203,122,107,114,236,23,118,216,38,188,24,134,196,250,194,183,97,181,74,83,112,124,124,201,48,239,20,247, +136,79,233,240,78,111,27,226,128,225,174,104,153,241,213,122,147,110,52,234,232,143,28,58,177,253,76,196,253,166,251,108,106, +71,100,186,219,3,223,175,167,217,236,138,159,247,136,155,224,171,8,97,135,132,105,248,230,25,123,43,206,95,182,110,31,60,231, +10,89,147,127,176,214,184,160,66,113,140,176,248,145,81,90,103,204,49,62,33,172,156,191,137,146,92,99,46,56,229,86,201,185, +37,118,201,64,137,71,61,218,37,162,36,10,198,188,146,28,41,90,147,227,130,108,233,100,179,233,198,60,150,19,165,243,39,121,37,74, +121,173,226,24,224,228,79,22,227,147,114,59,140,51,184,173,81,90,83,58,43,211,221,57,178,247,154,201,254,153,113,102,201,234, +12,195,93,114,22,24,203,129,38,72,121,51,76,150,234,41,217,140,207,238,12,211,131,161,15,148,152,89,217,172,70,67,170,232,201, +48,92,96,76,202,200,193,121,49,184,93,106,112,174,210,218,210,217,165,85,165,161,210,202,210,106,81,98,9,83,228,152,101,6,254, +8,163,229,192,1,235,185,217,11,196,129,58,33,142,2,207,3,135,194,112,123,224,24,240,252,28,33,110,60,67,8,254,55,202,228,218, +116,177,135,196,20,32,219,200,113,233,223,225,8,90,118,241,1,235,227,243,172,143,26,200,81,238,159,39,172,35,245,66,60,84,111, +136,159,130,190,1,124,188,65,136,123,129,39,128,3,141,8,240,238,92,217,174,7,237,30,110,236,21,47,54,10,235,137,38,33,94,2,14, +53,11,113,61,240,34,240,151,102,50,132,55,223,16,87,134,182,64,244,208,252,179,196,209,249,66,60,12,60,7,188,4,28,105,17,226, +46,224,49,224,121,224,21,224,221,22,178,132,237,199,100,5,55,141,162,233,149,11,182,137,135,22,96,4,11,133,120,114,17,180,3,135, +22,147,219,29,40,86,98,250,239,14,200,62,191,216,48,46,91,34,140,107,150,154,198,187,75,133,241,110,155,105,220,181,204,107, +220,184,124,167,245,238,10,83,60,221,9,75,117,153,226,249,110,204,174,219,16,151,173,194,72,87,99,8,107,240,12,188,180,22,186, +215,163,143,13,224,3,87,110,52,196,189,27,193,223,4,75,156,9,235,158,9,11,24,51,5,255,57,32,208,225,145,129,139,49,169,1,76, +38,194,191,206,11,122,133,247,99,226,208,1,235,181,8,215,30,218,44,114,110,4,46,219,146,253,255,149,156,191,233,201,252,223,129, +252,91,149,204,255,31,200,191,83,201,252,31,130,252,59,149,16,169,255,71,144,127,171,147,249,191,4,93,52,249,255,9,154,126, +245,59,26,249,123,170,144,250,127,164,54,129,225,10,41,25,254,247,244,194,175,126,251,206,255,6,222,8,169,126,249,255,31,52,181, +60,255,27,109,43,164,126,151,196,255,142,219,14,169,241,241,191,193,39,173,135,255,77,62,255,152,71,254,155,184,77,68,255,7, +100,114,50,46,48,81,0,0,0,0}; #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/roli/juce/JuceMidiSupport$MidiDeviceManager;") \ - STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/roli/juce/JuceMidiSupport$BluetoothManager;") + STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/roli/juce/JuceMidiSupport$BluetoothManager;") DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/roli/juce/JuceMidiSupport", 23, javaMidiByteCode, sizeof (javaMidiByteCode)) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (getJuceAndroidMidiInputDevices, "getJuceAndroidMidiInputDevices", "()[Ljava/lang/String;") \ - METHOD (getJuceAndroidMidiOutputDevices, "getJuceAndroidMidiOutputDevices", "()[Ljava/lang/String;") \ - METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)Lcom/roli/juce/JuceMidiSupport$JuceMidiPort;") \ - METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)Lcom/roli/juce/JuceMidiSupport$JuceMidiPort;") \ - METHOD (getInputPortNameForJuceIndex, "getInputPortNameForJuceIndex", "(I)Ljava/lang/String;") \ - METHOD (getOutputPortNameForJuceIndex, "getOutputPortNameForJuceIndex", "(I)Ljava/lang/String;") + METHOD (getJuceAndroidMidiInputDeviceNameAndIDs, "getJuceAndroidMidiInputDeviceNameAndIDs", "()[Ljava/lang/String;") \ + METHOD (getJuceAndroidMidiOutputDeviceNameAndIDs, "getJuceAndroidMidiOutputDeviceNameAndIDs", "()[Ljava/lang/String;") \ + METHOD (openMidiInputPortWithID, "openMidiInputPortWithID", "(IJ)Lcom/roli/juce/JuceMidiSupport$JuceMidiPort;") \ + METHOD (openMidiOutputPortWithID, "openMidiOutputPortWithID", "(I)Lcom/roli/juce/JuceMidiSupport$JuceMidiPort;") DECLARE_JNI_CLASS_WITH_MIN_SDK (MidiDeviceManager, "com/roli/juce/JuceMidiSupport$MidiDeviceManager", 23) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (start, "start", "()V" )\ - METHOD (stop, "stop", "()V") \ - METHOD (close, "close", "()V") \ - METHOD (sendMidi, "sendMidi", "([BII)V") + METHOD (start, "start", "()V") \ + METHOD (stop, "stop", "()V") \ + METHOD (close, "close", "()V") \ + METHOD (sendMidi, "sendMidi", "([BII)V") \ + METHOD (getName, "getName", "()Ljava/lang/String;") DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/roli/juce/JuceMidiSupport$JuceMidiPort", 23) #undef JNI_CLASS_MEMBERS @@ -368,15 +354,10 @@ DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/roli/juce/JuceMidiSupport$Juc class AndroidMidiInput { public: - AndroidMidiInput (MidiInput* midiInput, int portIdx, - juce::MidiInputCallback* midiInputCallback, jobject deviceManager) - : juceMidiInput (midiInput), - callback (midiInputCallback), - midiConcatenator (2048), - javaMidiDevice (LocalRef(getEnv()->CallObjectMethod (deviceManager, - MidiDeviceManager.openMidiInputPortWithJuceIndex, - (jint) portIdx, - (jlong) this))) + AndroidMidiInput (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager) + : juceMidiInput (midiInput), callback (midiInputCallback), midiConcatenator (2048), + javaMidiDevice (LocalRef(getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID, + (jint) deviceID, (jlong) this))) { } @@ -408,12 +389,20 @@ public: callback = nullptr; } + String getName() const noexcept + { + if (jobject d = javaMidiDevice.get()) + return juceString (LocalRef ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); + + return {}; + } + void handleMidi (jbyteArray byteArray, jlong offset, jint len, jlong timestamp) { auto* env = getEnv(); jassert (byteArray != nullptr); - jbyte* data = env->GetByteArrayElements (byteArray, nullptr); + auto* data = env->GetByteArrayElements (byteArray, nullptr); HeapBlock buffer (static_cast (len)); std::memcpy (buffer.get(), data + offset, static_cast (len)); @@ -466,6 +455,14 @@ public: byteArray, offset, len); } + String getName() const noexcept + { + if (jobject d = javaMidiDevice.get()) + return juceString (LocalRef ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName))); + + return {}; + } + private: GlobalRef javaMidiDevice; }; @@ -482,54 +479,42 @@ class AndroidMidiDeviceManager { public: AndroidMidiDeviceManager() - : deviceManager (LocalRef(getEnv()->CallStaticObjectMethod (JuceMidiSupport, JuceMidiSupport.getAndroidMidiDeviceManager, getAppContext().get()))) + : deviceManager (LocalRef(getEnv()->CallStaticObjectMethod (JuceMidiSupport, + JuceMidiSupport.getAndroidMidiDeviceManager, + getAppContext().get()))) { } - String getInputPortNameForJuceIndex (int idx) + Array getDevices (bool input) { if (jobject dm = deviceManager.get()) { - LocalRef string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getInputPortNameForJuceIndex, idx)); - return juceString (string); - } + jobjectArray jDeviceNameAndIDs + = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDeviceNameAndIDs + : MidiDeviceManager.getJuceAndroidMidiOutputDeviceNameAndIDs); - return {}; - } + // Create a local reference as converting this to a JUCE string will call into JNI + LocalRef localDeviceNameAndIDs (jDeviceNameAndIDs); - String getOutputPortNameForJuceIndex (int idx) - { - if (jobject dm = deviceManager.get()) - { - LocalRef string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getOutputPortNameForJuceIndex, idx)); - return juceString (string); - } + auto deviceNameAndIDs = javaStringArrayToJuce (localDeviceNameAndIDs); + deviceNameAndIDs.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); - return {}; - } + Array devices; - StringArray getDevices (bool input) - { - if (jobject dm = deviceManager.get()) - { - jobjectArray jDevices - = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDevices - : MidiDeviceManager.getJuceAndroidMidiOutputDevices); - - // Create a local reference as converting this - // to a JUCE string will call into JNI - LocalRef devices (jDevices); - return javaStringArrayToJuce (devices); + for (int i = 0; i < deviceNameAndIDs.size(); i += 2) + devices.add ({ deviceNameAndIDs[i], deviceNameAndIDs[i + 1] }); + + return devices; } return {}; } - AndroidMidiInput* openMidiInputPortWithIndex (int idx, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) + AndroidMidiInput* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) { - if (jobject dm = deviceManager.get()) + if (auto dm = deviceManager.get()) { - std::unique_ptr androidMidiInput (new AndroidMidiInput (juceMidiInput, idx, callback, dm)); + std::unique_ptr androidMidiInput (new AndroidMidiInput (juceMidiInput, deviceID, callback, dm)); if (androidMidiInput->isOpen()) return androidMidiInput.release(); @@ -538,10 +523,10 @@ public: return nullptr; } - AndroidMidiOutput* openMidiOutputPortWithIndex (int idx) + AndroidMidiOutput* openMidiOutputPortWithID (int deviceID) { - if (jobject dm = deviceManager.get()) - if (jobject javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithJuceIndex, (jint) idx)) + if (auto dm = deviceManager.get()) + if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID)) return new AndroidMidiOutput (LocalRef(javaMidiPort)); return nullptr; @@ -552,134 +537,171 @@ private: }; //============================================================================== -StringArray MidiOutput::getDevices() +Array MidiInput::getAvailableDevices() { - if (getAndroidSDKVersion() >= 23) - { - AndroidMidiDeviceManager manager; - return manager.getDevices (false); - } + if (getAndroidSDKVersion() < 23) + return {}; - return {}; + AndroidMidiDeviceManager manager; + return manager.getDevices (true); } -int MidiOutput::getDefaultDeviceIndex() +MidiDeviceInfo MidiInput::getDefaultDevice() { - return (getAndroidSDKVersion() >= 23 ? 0 : -1); + if (getAndroidSDKVersion() < 23) + return {}; + + return getAvailableDevices().getFirst(); } -MidiOutput* MidiOutput::openDevice (int index) +MidiInput* MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) { - if (index < 0 || getAndroidSDKVersion() < 23) + if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) return nullptr; AndroidMidiDeviceManager manager; - String midiOutputName = manager.getOutputPortNameForJuceIndex (index); + std::unique_ptr midiInput (new MidiInput ({}, deviceIdentifier)); - if (midiOutputName.isEmpty()) + if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback)) { - // you supplied an invalid device index! - jassertfalse; - return nullptr; - } - - if (AndroidMidiOutput* midiOutput = manager.openMidiOutputPortWithIndex (index)) - { - MidiOutput* retval = new MidiOutput (midiOutputName); - retval->internal = midiOutput; + midiInput->internal = port; + midiInput->setName (port->getName()); - return retval; + return midiInput.release(); } return nullptr; } -MidiOutput::~MidiOutput() +StringArray MidiInput::getDevices() { - stopBackgroundThread(); + if (getAndroidSDKVersion() < 23) + return {}; - delete reinterpret_cast (internal); + StringArray deviceNames; + + for (auto& d : getAvailableDevices()) + deviceNames.add (d.name); + + return deviceNames; } -void MidiOutput::sendMessageNow (const MidiMessage& message) +int MidiInput::getDefaultDeviceIndex() { - if (AndroidMidiOutput* androidMidi = reinterpret_cast(internal)) - { - JNIEnv* env = getEnv(); - const int messageSize = message.getRawDataSize(); + return (getAndroidSDKVersion() < 23 ? -1 : 0); +} - LocalRef messageContent = LocalRef (env->NewByteArray (messageSize)); - jbyteArray content = messageContent.get(); +MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) +{ + return openDevice (getAvailableDevices()[index].identifier, callback); +} - jbyte* rawBytes = env->GetByteArrayElements (content, nullptr); - std::memcpy (rawBytes, message.getRawData(), static_cast (messageSize)); - env->ReleaseByteArrayElements (content, rawBytes, 0); +MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) + : deviceInfo (deviceName, deviceIdentifier) +{ +} - androidMidi->send (content, (jint) 0, (jint) messageSize); - } +MidiInput::~MidiInput() +{ + delete reinterpret_cast (internal); } -//============================================================================== -MidiInput::MidiInput (const String& nm) : name (nm) +void MidiInput::start() { + if (auto* mi = reinterpret_cast (internal)) + mi->start(); } -StringArray MidiInput::getDevices() +void MidiInput::stop() { - if (getAndroidSDKVersion() >= 23) - { - AndroidMidiDeviceManager manager; - return manager.getDevices (true); - } + if (auto* mi = reinterpret_cast (internal)) + mi->stop(); +} - return {}; +//============================================================================== +Array MidiOutput::getAvailableDevices() +{ + if (getAndroidSDKVersion() < 23) + return {}; + + AndroidMidiDeviceManager manager; + return manager.getDevices (false); } -int MidiInput::getDefaultDeviceIndex() +MidiDeviceInfo MidiOutput::getDefaultDevice() { - return (getAndroidSDKVersion() >= 23 ? 0 : -1); + if (getAndroidSDKVersion() < 23) + return {}; + + return getAvailableDevices().getFirst(); } -MidiInput* MidiInput::openDevice (int index, juce::MidiInputCallback* callback) +MidiOutput* MidiOutput::openDevice (const String& deviceIdentifier) { - if (getAndroidSDKVersion() < 23 || index < 0) + if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty()) return nullptr; AndroidMidiDeviceManager manager; - String midiInputName (manager.getInputPortNameForJuceIndex (index)); - - if (midiInputName.isEmpty()) + if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue())) { - // you supplied an invalid device index! - jassertfalse; - return nullptr; + std::unique_ptr midiOutput (new MidiOutput ({}, deviceIdentifier)); + midiOutput->internal = port; + midiOutput->setName (port->getName()); + + return midiOutput.release(); } - std::unique_ptr midiInput (new MidiInput (midiInputName)); + return nullptr; +} - midiInput->internal = manager.openMidiInputPortWithIndex (index, midiInput.get(), callback); +StringArray MidiOutput::getDevices() +{ + if (getAndroidSDKVersion() < 23) + return {}; + + StringArray deviceNames; - return midiInput->internal != nullptr ? midiInput.release() - : nullptr; + for (auto& d : getAvailableDevices()) + deviceNames.add (d.name); + + return deviceNames; } -void MidiInput::start() +int MidiOutput::getDefaultDeviceIndex() { - if (AndroidMidiInput* mi = reinterpret_cast (internal)) - mi->start(); + return (getAndroidSDKVersion() < 23 ? -1 : 0); } -void MidiInput::stop() +MidiOutput* MidiOutput::openDevice (int index) { - if (AndroidMidiInput* mi = reinterpret_cast (internal)) - mi->stop(); + return openDevice (getAvailableDevices()[index].identifier); } -MidiInput::~MidiInput() +MidiOutput::~MidiOutput() { - delete reinterpret_cast (internal); + stopBackgroundThread(); + + delete reinterpret_cast (internal); +} + +void MidiOutput::sendMessageNow (const MidiMessage& message) +{ + if (auto* androidMidi = reinterpret_cast(internal)) + { + auto* env = getEnv(); + auto messageSize = message.getRawDataSize(); + + LocalRef messageContent (env->NewByteArray (messageSize)); + auto content = messageContent.get(); + + auto* rawBytes = env->GetByteArrayElements (content, nullptr); + std::memcpy (rawBytes, message.getRawData(), static_cast (messageSize)); + env->ReleaseByteArrayElements (content, rawBytes, 0); + + androidMidi->send (content, (jint) 0, (jint) messageSize); + } } } // namespace juce diff --git a/modules/juce_audio_devices/native/juce_linux_Bela.cpp b/modules/juce_audio_devices/native/juce_linux_Bela.cpp index a4d000eab4..6f6314e1fc 100644 --- a/modules/juce_audio_devices/native/juce_linux_Bela.cpp +++ b/modules/juce_audio_devices/native/juce_linux_Bela.cpp @@ -66,9 +66,9 @@ public: } } - static StringArray getDevices (bool input) + static Array getDevices (bool input) { - StringArray devices; + Array devices; for (auto& card : findAllALSACardIDs()) findMidiDevices (devices, input, card); @@ -96,7 +96,7 @@ private: } // Adds all midi devices to the devices array of the given input/output type on the given card - static void findMidiDevices (StringArray& devices, bool input, int cardNum) + static void findMidiDevices (Array& devices, bool input, int cardNum) { snd_ctl_t* ctl = nullptr; auto status = snd_ctl_open (&ctl, ("hw:" + String (cardNum)).toRawUTF8(), 0); @@ -131,9 +131,10 @@ private: status = snd_ctl_rawmidi_info (ctl, info); if (status == 0) - devices.add ("hw:" + String (cardNum) + "," - + String (device) + "," - + String (sub)); + { + String deviceName ("hw:" + String (cardNum) + "," + String (device) + "," + String (sub)); + devices.add (MidiDeviceInfo (deviceName, deviceName)); + } } } @@ -507,58 +508,74 @@ AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() return new BelaAudioIODeviceType(); } - //============================================================================== -// TODO: Add Bela MidiOutput support - -StringArray MidiOutput::getDevices() { return {}; } -int MidiOutput::getDefaultDeviceIndex() { return 0; } -MidiOutput* MidiOutput::openDevice (int) { return {}; } -MidiOutput* MidiOutput::createNewDevice (const String&) { return {}; } -MidiOutput::~MidiOutput() {} -void MidiOutput::sendMessageNow (const MidiMessage&) {} - +MidiInput::MidiInput (const String& deviceName, const String& deviceID) + : deviceInfo (deviceName, deviceID) +{ +} -//============================================================================== -MidiInput::MidiInput (const String& nm) : name (nm) {} +MidiInput::~MidiInput() { delete static_cast (internal); } +void MidiInput::start() { static_cast (internal)->start(); } +void MidiInput::stop() { static_cast (internal)->stop(); } -MidiInput::~MidiInput() +void Array MidiInput::getAvailableDevices() { - delete static_cast (internal); + return BelaMidiInput::getDevices (true); } -void MidiInput::start() { static_cast (internal)->start(); } -void MidiInput::stop() { static_cast (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 (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 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 diff --git a/modules/juce_audio_devices/native/juce_linux_Midi.cpp b/modules/juce_audio_devices/native/juce_linux_Midi.cpp index 8fa26434fd..5f6d67e1d4 100644 --- a/modules/juce_audio_devices/native/juce_linux_Midi.cpp +++ b/modules/juce_audio_devices/native/juce_linux_Midi.cpp @@ -67,12 +67,12 @@ public: static String getAlsaMidiName() { #ifdef JUCE_ALSA_MIDI_NAME - return JUCE_ALSA_MIDI_NAME; + return JUCE_ALSA_MIDI_NAME; #else - if (auto* app = JUCEApplicationBase::getInstance()) - return app->getApplicationName(); + if (auto* app = JUCEApplicationBase::getInstance()) + return app->getApplicationName(); - return "JUCE"; + return "JUCE"; #endif } @@ -198,7 +198,8 @@ public: isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0)) : (SND_SEQ_PORT_CAP_READ | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0)); - portId = snd_seq_create_simple_port (seqHandle, name.toUTF8(), caps, + portName = name; + portId = snd_seq_create_simple_port (seqHandle, portName.toUTF8(), caps, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); } @@ -215,13 +216,15 @@ public: } AlsaClient& client; + MidiInputCallback* callback = nullptr; snd_midi_event_t* midiParser = nullptr; MidiInput* midiInput = nullptr; - int maxEventSize = 4096; - int portId = -1; - bool callbackEnabled = false; - bool isInput = false; + + String portName; + + int maxEventSize = 4096, portId = -1; + bool callbackEnabled = false, isInput = false; }; static Ptr getInstance() @@ -359,11 +362,16 @@ private: AlsaClient* AlsaClient::instance = nullptr; //============================================================================== +static String getFormattedPortIdentifier (int clientId, int portId) +{ + return String (clientId) + "-" + String (portId); +} + static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, snd_seq_client_info_t* clientInfo, bool forInput, - StringArray& deviceNamesFound, - int deviceIndexToOpen) + Array& devices, + const String& deviceIdentifierToOpen) { AlsaClient::Port* port = nullptr; @@ -371,7 +379,7 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, snd_seq_port_info_t* portInfo = nullptr; snd_seq_port_info_alloca (&portInfo); - jassert (portInfo); + jassert (portInfo != nullptr); auto numPorts = snd_seq_client_info_get_num_ports (clientInfo); auto sourceClient = snd_seq_client_info_get_client (clientInfo); @@ -384,19 +392,19 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, && (snd_seq_port_info_get_capability (portInfo) & (forInput ? SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_SUBS_WRITE)) != 0) { - String portName = snd_seq_port_info_get_name(portInfo); + String portName (snd_seq_port_info_get_name (portInfo)); + auto portID = snd_seq_port_info_get_port (portInfo); - deviceNamesFound.add (portName); + MidiDeviceInfo device (portName, getFormattedPortIdentifier (sourceClient, portID)); + devices.add (device); - if (deviceNamesFound.size() == deviceIndexToOpen + 1) + if (deviceIdentifierToOpen.isNotEmpty() && deviceIdentifierToOpen == device.identifier) { - auto sourcePort = snd_seq_port_info_get_port (portInfo); - - if (sourcePort != -1) + if (portID != -1) { port = client->createPort (portName, forInput, false); jassert (port->isValid()); - port->connectWith (sourceClient, sourcePort); + port->connectWith (sourceClient, portID); break; } } @@ -407,8 +415,8 @@ static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, } static AlsaClient::Port* iterateMidiDevices (bool forInput, - StringArray& deviceNamesFound, - int deviceIndexToOpen) + Array& devices, + const String& deviceIdentifierToOpen) { AlsaClient::Port* port = nullptr; auto client = AlsaClient::getInstance(); @@ -432,85 +440,95 @@ static AlsaClient::Port* iterateMidiDevices (bool forInput, { if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) { - auto sourceClient = snd_seq_client_info_get_client (clientInfo); + port = iterateMidiClient (client, clientInfo, forInput, + devices, deviceIdentifierToOpen); - if (sourceClient != client->getId() && sourceClient != SND_SEQ_CLIENT_SYSTEM) - { - port = iterateMidiClient (client, clientInfo, forInput, - deviceNamesFound, deviceIndexToOpen); - if (port != nullptr) - break; - } + if (port != nullptr) + break; } } } } - deviceNamesFound.appendNumbersToDuplicates (true, true); - return port; } } // namespace -StringArray MidiOutput::getDevices() +//============================================================================== +Array MidiInput::getAvailableDevices() { - StringArray devices; - iterateMidiDevices (false, devices, -1); + Array devices; + iterateMidiDevices (true, devices, {}); + return devices; } -int MidiOutput::getDefaultDeviceIndex() +MidiDeviceInfo MidiInput::getDefaultDevice() { - return 0; + return getAvailableDevices().getFirst(); } -MidiOutput* MidiOutput::openDevice (int deviceIndex) +MidiInput* MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) { - MidiOutput* newDevice = nullptr; + if (deviceIdentifier.isEmpty()) + return nullptr; - StringArray devices; - auto* port = iterateMidiDevices (false, devices, deviceIndex); + Array devices; + auto* port = iterateMidiDevices (true, devices, deviceIdentifier); if (port == nullptr) return nullptr; jassert (port->isValid()); - newDevice = new MidiOutput (devices [deviceIndex]); - port->setupOutput(); - newDevice->internal = port; + std::unique_ptr midiInput (new MidiInput (port->portName, deviceIdentifier)); - return newDevice; + port->setupInput (midiInput.get(), callback); + midiInput->internal = port; + + return midiInput.release(); } -MidiOutput* MidiOutput::createNewDevice (const String& deviceName) +MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) { - MidiOutput* newDevice = nullptr; auto client = AlsaClient::getInstance(); - auto* port = client->createPort (deviceName, false, true); - jassert (port != nullptr && port->isValid()); + auto* port = client->createPort (deviceName, true, true); - newDevice = new MidiOutput (deviceName); - port->setupOutput(); - newDevice->internal = port; + jassert (port->isValid()); + + std::unique_ptr midiInput (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 (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 (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 (internal)->enableCallback (false); } -int MidiInput::getDefaultDeviceIndex() +//============================================================================== +Array MidiOutput::getAvailableDevices() { - return 0; + Array 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 devices; + auto* port = iterateMidiDevices (false, devices, deviceIdentifier); if (port == nullptr) return nullptr; jassert (port->isValid()); - auto newDevice = new MidiInput (devices [deviceIndex]); - port->setupInput (newDevice, callback); - newDevice->internal = port; - return newDevice; + std::unique_ptr midiOutput (new MidiOutput (port->portName, deviceIdentifier)); + + port->setupOutput(); + midiOutput->internal = port; + + return midiOutput.release(); } -MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) +MidiOutput* MidiOutput::createNewDevice (const String& deviceName) { auto client = AlsaClient::getInstance(); - auto* port = client->createPort (deviceName, true, true); + auto* port = client->createPort (deviceName, false, true); - jassert (port->isValid()); + jassert (port != nullptr && port->isValid()); + + std::unique_ptr midiOutput (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 (internal)); +} + +void MidiOutput::sendMessageNow (const MidiMessage& message) +{ + static_cast (internal)->sendMessageNow (message); +} //============================================================================== #else // (These are just stub functions if ALSA is unavailable...) +MidiInput::MidiInput (const String& deviceName, const String& deviceID) + : deviceInfo (deviceName, deviceID) +{ +} -StringArray MidiOutput::getDevices() { return {}; } -int MidiOutput::getDefaultDeviceIndex() { return 0; } -MidiOutput* MidiOutput::openDevice (int) { return nullptr; } -MidiOutput* MidiOutput::createNewDevice (const String&) { return nullptr; } -MidiOutput::~MidiOutput() {} -void MidiOutput::sendMessageNow (const MidiMessage&) {} - -MidiInput::MidiInput (const String& nm) : name (nm) {} -MidiInput::~MidiInput() {} -void MidiInput::start() {} -void MidiInput::stop() {} -int MidiInput::getDefaultDeviceIndex() { return 0; } -StringArray MidiInput::getDevices() { return {}; } -MidiInput* MidiInput::openDevice (int, MidiInputCallback*) { return nullptr; } +MidiInput::~MidiInput() {} +void MidiInput::start() {} +void MidiInput::stop() {} +Array MidiInput::getAvailableDevices() { return {}; } +MidiDeviceInfo MidiInput::getDefaultDevice() { return {}; } +MidiInput* MidiInput::openDevice (const String&, MidiInputCallback*) { return nullptr; } MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*) { return nullptr; } +StringArray MidiInput::getDevices() { return {}; } +int MidiInput::getDefaultDeviceIndex() { return 0;} +MidiInput* MidiInput::openDevice (int, MidiInputCallback*) { return nullptr; } + +MidiOutput::~MidiOutput() {} +void MidiOutput::sendMessageNow (const MidiMessage&) {} +Array 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 diff --git a/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp b/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp index c5beef2cd1..58de5bac56 100644 --- a/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp +++ b/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp @@ -29,6 +29,22 @@ namespace juce namespace CoreMidiHelpers { + //============================================================================== + struct ScopedCFString + { + ScopedCFString() = default; + ScopedCFString (String s) : cfString (s.toCFString()) {} + + ~ScopedCFString() noexcept + { + if (cfString != nullptr) + CFRelease (cfString); + } + + CFStringRef cfString = {}; + }; + + //============================================================================== static bool checkError (OSStatus err, int lineNum) { if (err == noErr) @@ -45,79 +61,62 @@ namespace CoreMidiHelpers #undef CHECK_ERROR #define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__) - //============================================================================== - struct ScopedCFString + static MidiDeviceInfo getMidiObjectInfo (MIDIObjectRef entity) { - ScopedCFString() noexcept {} - ~ScopedCFString() noexcept { if (cfString != nullptr) CFRelease (cfString); } + MidiDeviceInfo info; - CFStringRef cfString = {}; - }; - - static String getMidiObjectName (MIDIObjectRef entity) - { - String result; - CFStringRef str = nullptr; - MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str); - - if (str != nullptr) { - result = String::fromCFString (str); - CFRelease (str); - } + ScopedCFString str; - return result; - } + if (CHECK_ERROR (MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str.cfString))) + info.name = String::fromCFString (str.cfString); + } - static void enableSimulatorMidiSession() - { - #if TARGET_OS_SIMULATOR - static bool hasEnabledNetworkSession = false; + SInt32 objectID = 0; - if (! hasEnabledNetworkSession) - { - MIDINetworkSession* session = [MIDINetworkSession defaultSession]; - session.enabled = YES; - session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; + if (CHECK_ERROR (MIDIObjectGetIntegerProperty (entity, kMIDIPropertyUniqueID, &objectID))) + info.identifier = String (objectID); - hasEnabledNetworkSession = true; - } - #endif + return info; } - static String getEndpointName (MIDIEndpointRef endpoint, bool isExternal) + static MidiDeviceInfo getEndpointInfo (MIDIEndpointRef endpoint, bool isExternal) { - auto result = getMidiObjectName (endpoint); - - MIDIEntityRef entity = 0; // NB: don't attempt to use nullptr for refs - it fails in some types of build. + // NB: don't attempt to use nullptr for refs - it fails in some types of build. + MIDIEntityRef entity = 0; MIDIEndpointGetEntity (endpoint, &entity); + // probably virtual if (entity == 0) - return result; // probably virtual + return getMidiObjectInfo (endpoint); + + auto result = getMidiObjectInfo (endpoint); - if (result.isEmpty()) - result = getMidiObjectName (entity); // endpoint name is empty - try the entity + // endpoint is empty - try the entity + if (result == MidiDeviceInfo()) + result = getMidiObjectInfo (entity); - // now consider the device's name + // now consider the device MIDIDeviceRef device = 0; MIDIEntityGetDevice (entity, &device); if (device != 0) { - auto deviceName = getMidiObjectName (device); + auto info = getMidiObjectInfo (device); - if (deviceName.isNotEmpty()) + if (info != MidiDeviceInfo()) { // if an external device has only one entity, throw away // the endpoint name and just use the device name if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2) { - result = deviceName; + result = info; } - else if (! result.startsWithIgnoreCase (deviceName)) + else if (! result.name.startsWithIgnoreCase (info.name)) { - // prepend the device name to the entity name - result = (deviceName + " " + result).trimEnd(); + // prepend the device name and identifier to the entity's + result.name = (info.name + " " + result.name).trimEnd(); + result.identifier = info.identifier + " " + result.identifier; } } } @@ -125,9 +124,9 @@ namespace CoreMidiHelpers return result; } - static String getConnectedEndpointName (MIDIEndpointRef endpoint) + static MidiDeviceInfo getConnectedEndpointInfo (MIDIEndpointRef endpoint) { - String result; + MidiDeviceInfo result; // Does the endpoint have connections? CFDataRef connections = nullptr; @@ -141,37 +140,38 @@ namespace CoreMidiHelpers if (numConnections > 0) { - auto pid = reinterpret_cast (CFDataGetBytePtr (connections)); + auto* pid = reinterpret_cast (CFDataGetBytePtr (connections)); for (int i = 0; i < numConnections; ++i, ++pid) { - auto uid = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid); + auto id = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid); MIDIObjectRef connObject; MIDIObjectType connObjectType; - auto err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType); + auto err = MIDIObjectFindByUniqueID (id, &connObject, &connObjectType); if (err == noErr) { - String s; + MidiDeviceInfo deviceInfo; if (connObjectType == kMIDIObjectType_ExternalSource || connObjectType == kMIDIObjectType_ExternalDestination) { // Connected to an external device's endpoint (10.3 and later). - s = getEndpointName (static_cast (connObject), true); + deviceInfo = getEndpointInfo (static_cast (connObject), true); } else { // Connected to an external device (10.2) (or something else, catch-all) - s = getMidiObjectName (connObject); + deviceInfo = getMidiObjectInfo (connObject); } - if (s.isNotEmpty()) + if (deviceInfo != MidiDeviceInfo()) { - if (result.isNotEmpty()) - result += ", "; + if (result.name.isNotEmpty()) result.name += ", "; + if (result.identifier.isNotEmpty()) result.identifier += ", "; - result += s; + result.name += deviceInfo.name; + result.identifier += deviceInfo.identifier; } } } @@ -180,17 +180,19 @@ namespace CoreMidiHelpers CFRelease (connections); } - if (result.isEmpty()) // Here, either the endpoint had no connections, or we failed to obtain names for them. - result = getEndpointName (endpoint, false); + // Here, either the endpoint had no connections, or we failed to obtain names for them. + if (result == MidiDeviceInfo()) + return getEndpointInfo (endpoint, false); return result; } - static void setUniqueIdForMidiPort (MIDIObjectRef device, const String& portName, bool isInput) + static int createUniqueIDForMidiPort (String deviceName, bool isInput) { - String portUniqueId; - #if defined (JucePlugin_CFBundleIdentifier) - portUniqueId = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier); + String uniqueID; + + #ifdef JucePlugin_CFBundleIdentifier + uniqueID = JUCE_STRINGIFY (JucePlugin_CFBundleIdentifier); #else auto appBundle = File::getSpecialLocation (File::currentApplicationFile); @@ -203,19 +205,33 @@ namespace CoreMidiHelpers if (bundleRef != nullptr) { if (auto bundleId = CFBundleGetIdentifier (bundleRef)) - portUniqueId = String::fromCFString (bundleId); + uniqueID = String::fromCFString (bundleId); CFRelease (bundleRef); } } #endif - if (portUniqueId.isNotEmpty()) + if (uniqueID.isNotEmpty()) + uniqueID += "." + deviceName + (isInput ? ".input" : ".output"); + + return uniqueID.hashCode(); + } + + static void enableSimulatorMidiSession() + { + #if TARGET_OS_SIMULATOR + static bool hasEnabledNetworkSession = false; + + if (! hasEnabledNetworkSession) { - portUniqueId += "." + portName + (isInput ? ".input" : ".output"); + MIDINetworkSession* session = [MIDINetworkSession defaultSession]; + session.enabled = YES; + session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; - CHECK_ERROR (MIDIObjectSetStringProperty (device, kMIDIPropertyUniqueID, portUniqueId.toCFString())); + hasEnabledNetworkSession = true; } + #endif } static void globalSystemChangeCallback (const MIDINotification*, void*) @@ -243,15 +259,14 @@ namespace CoreMidiHelpers enableSimulatorMidiSession(); - CoreMidiHelpers::ScopedCFString name; - name.cfString = getGlobalMidiClientName().toCFString(); + CoreMidiHelpers::ScopedCFString name (getGlobalMidiClientName()); CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); } return globalMidiClient; } - static StringArray findDevices (bool forInput) + static Array findDevices (bool forInput) { // It seems that OSX can be a bit picky about the thread that's first used to // search for devices. It's safest to use the message thread for calling this. @@ -263,26 +278,25 @@ namespace CoreMidiHelpers return {}; } - StringArray s; enableSimulatorMidiSession(); - auto num = forInput ? MIDIGetNumberOfSources() - : MIDIGetNumberOfDestinations(); + Array devices; + auto numDevices = (forInput ? MIDIGetNumberOfSources() : MIDIGetNumberOfDestinations()); - for (ItemCount i = 0; i < num; ++i) + for (ItemCount i = 0; i < numDevices; ++i) { - String name; + MidiDeviceInfo deviceInfo; if (auto dest = forInput ? MIDIGetSource (i) : MIDIGetDestination (i)) - name = getConnectedEndpointName (dest); + deviceInfo = getConnectedEndpointInfo (dest); - if (name.isEmpty()) - name = ""; + if (deviceInfo == MidiDeviceInfo()) + deviceInfo.name = deviceInfo.identifier = ""; - s.add (name); + devices.add (deviceInfo); } - return s; + return devices; } //============================================================================== @@ -290,7 +304,7 @@ namespace CoreMidiHelpers { public: MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept - : port (p), endPoint (ep) + : port (p), endpoint (ep) { } @@ -299,20 +313,21 @@ namespace CoreMidiHelpers if (port != 0) MIDIPortDispose (port); - if (port == 0 && endPoint != 0) // if port == nullptr, it means we created the endpoint, so it's safe to delete it - MIDIEndpointDispose (endPoint); + // if port == nullptr, it means we created the endpoint, so it's safe to delete it + if (port == 0 && endpoint != 0) + MIDIEndpointDispose (endpoint); } void send (const MIDIPacketList* packets) noexcept { if (port != 0) - MIDISend (port, endPoint, packets); + MIDISend (port, endpoint, packets); else - MIDIReceived (endPoint, packets); + MIDIReceived (endpoint, packets); } MIDIPortRef port; - MIDIEndpointRef endPoint; + MIDIEndpointRef endpoint; }; //============================================================================== @@ -334,7 +349,7 @@ namespace CoreMidiHelpers } if (portAndEndpoint != nullptr && portAndEndpoint->port != 0) - CHECK_ERROR (MIDIPortDisconnectSource (portAndEndpoint->port, portAndEndpoint->endPoint)); + CHECK_ERROR (MIDIPortDisconnectSource (portAndEndpoint->port, portAndEndpoint->endpoint)); } void handlePackets (const MIDIPacketList* pktlist) @@ -370,63 +385,250 @@ namespace CoreMidiHelpers { static_cast (readProcRefCon)->handlePackets (pktlist); } + + static Array getEndpoints (bool isInput) + { + Array 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 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 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 (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 (*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 (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 (internal); +} - if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString))) +void MidiInput::start() +{ + const ScopedLock sl (CoreMidiHelpers::callbackLock); + static_cast (internal)->active = true; +} + +void MidiInput::stop() +{ + const ScopedLock sl (CoreMidiHelpers::callbackLock); + static_cast (internal)->active = false; +} + +//============================================================================== +Array 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 (new MidiOutput (String::fromCFString (cfName.cfString), deviceIdentifier)); + midiOutput->internal = new MidiPortAndEndpoint (port, endpoint); + + return midiOutput.release(); + } } } } } - return mo; + return nullptr; } MidiOutput* MidiOutput::createNewDevice (const String& deviceName) { - if (auto client = CoreMidiHelpers::getGlobalMidiClient()) + using namespace CoreMidiHelpers; + + if (auto client = getGlobalMidiClient()) { - MIDIEndpointRef endPoint; + MIDIEndpointRef endpoint; - CoreMidiHelpers::ScopedCFString name; - name.cfString = deviceName.toCFString(); + ScopedCFString name (deviceName); - if (CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endPoint))) + if (CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endpoint))) { - CoreMidiHelpers::setUniqueIdForMidiPort (endPoint, deviceName, false); + auto deviceIdentifier = createUniqueIDForMidiPort (deviceName, true); + + if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) + { + std::unique_ptr midiOutput (new MidiOutput (deviceName, String (deviceIdentifier))); + midiOutput->internal = new MidiPortAndEndpoint (0, endpoint); - auto mo = new MidiOutput (deviceName); - mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint); - return mo; + return midiOutput.release(); + } } } return nullptr; } +StringArray MidiOutput::getDevices() +{ + StringArray deviceNames; + + for (auto& d : getAvailableDevices()) + deviceNames.add (d.name); + + return deviceNames; +} + +int MidiOutput::getDefaultDeviceIndex() +{ + return 0; +} + +MidiOutput* MidiOutput::openDevice (int index) +{ + return openDevice (getAvailableDevices()[index].identifier); +} + MidiOutput::~MidiOutput() { stopBackgroundThread(); @@ -493,111 +695,6 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) static_cast (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 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 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 (internal); -} - -void MidiInput::start() -{ - const ScopedLock sl (CoreMidiHelpers::callbackLock); - static_cast (internal)->active = true; -} - -void MidiInput::stop() -{ - const ScopedLock sl (CoreMidiHelpers::callbackLock); - static_cast (internal)->active = false; -} - #undef CHECK_ERROR } // namespace juce diff --git a/modules/juce_audio_devices/native/juce_win32_Midi.cpp b/modules/juce_audio_devices/native/juce_win32_Midi.cpp index 0864a053ce..385a0c08af 100644 --- a/modules/juce_audio_devices/native/juce_win32_Midi.cpp +++ b/modules/juce_audio_devices/native/juce_win32_Midi.cpp @@ -20,6 +20,12 @@ ============================================================================== */ +#ifndef DRV_QUERYDEVICEINTERFACE + #define DRV_RESERVED 0x0800 + #define DRV_QUERYDEVICEINTERFACE (DRV_RESERVED + 12) + #define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13) +#endif + namespace juce { @@ -29,7 +35,9 @@ struct MidiServiceType { virtual ~InputWrapper() {} + virtual String getDeviceIdentifier() = 0; virtual String getDeviceName() = 0; + virtual void start() = 0; virtual void stop() = 0; }; @@ -38,18 +46,20 @@ struct MidiServiceType { virtual ~OutputWrapper() {} + virtual String getDeviceIdentifier() = 0; virtual String getDeviceName() = 0; + virtual void sendMessageNow (const MidiMessage&) = 0; }; MidiServiceType() {} virtual ~MidiServiceType() {} - virtual StringArray getDevices (bool) = 0; - virtual int getDefaultDeviceIndex (bool) = 0; + virtual Array getAvailableDevices (bool) = 0; + virtual MidiDeviceInfo getDefaultDevice (bool) = 0; - virtual InputWrapper* createInputWrapper (MidiInput&, int, MidiInputCallback&) = 0; - virtual OutputWrapper* createOutputWrapper (int) = 0; + virtual InputWrapper* createInputWrapper (MidiInput&, const String&, MidiInputCallback&) = 0; + virtual OutputWrapper* createOutputWrapper (const String&) = 0; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType) }; @@ -60,26 +70,26 @@ struct Win32MidiService : public MidiServiceType, { Win32MidiService() {} - StringArray getDevices (bool isInput) override + Array getAvailableDevices (bool isInput) override { - return isInput ? Win32InputWrapper::getDevices() - : Win32OutputWrapper::getDevices(); + return isInput ? Win32InputWrapper::getAvailableDevices() + : Win32OutputWrapper::getAvailableDevices(); } - int getDefaultDeviceIndex (bool isInput) override + MidiDeviceInfo getDefaultDevice (bool isInput) override { - return isInput ? Win32InputWrapper::getDefaultDeviceIndex() - : Win32OutputWrapper::getDefaultDeviceIndex(); + return isInput ? Win32InputWrapper::getDefaultDevice() + : Win32OutputWrapper::getDefaultDevice(); } - InputWrapper* createInputWrapper (MidiInput& input, int index, MidiInputCallback& callback) override + InputWrapper* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override { - return new Win32InputWrapper (*this, input, index, callback); + return new Win32InputWrapper (*this, input, deviceIdentifier, callback); } - OutputWrapper* createOutputWrapper (int index) override + OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override { - return new Win32OutputWrapper (*this, index); + return new Win32OutputWrapper (*this, deviceIdentifier); } private: @@ -88,7 +98,10 @@ private: //============================================================================== struct MidiInCollector : public ReferenceCountedObject { - MidiInCollector (Win32MidiService& s, const String& name) : deviceName (name), midiService (s) {} + MidiInCollector (Win32MidiService& s, MidiDeviceInfo d) + : deviceInfo (d), midiService (s) + { + } ~MidiInCollector() { @@ -216,7 +229,7 @@ private: } } - String deviceName; + MidiDeviceInfo deviceInfo; HMIDIIN deviceHandle = 0; private: @@ -319,13 +332,59 @@ private: }; //============================================================================== - struct Win32InputWrapper : public InputWrapper + template + struct Win32MidiDeviceQuery { - Win32InputWrapper (Win32MidiService& parentService, - MidiInput& midiInput, int index, MidiInputCallback& c) + static Array 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 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 (Win32MidiService& parentService, MidiInput& midiInput, const String& deviceIdentifier, MidiInputCallback& c) : input (midiInput), callback (c) { - collector = getOrCreateCollector (parentService, index); + collector = getOrCreateCollector (parentService, deviceIdentifier); collector->addClient (this); } @@ -334,25 +393,31 @@ private: collector->removeClient (this); } - static MidiInCollector::Ptr getOrCreateCollector (Win32MidiService& parentService, int index) + static MidiInCollector::Ptr getOrCreateCollector (Win32MidiService& parentService, const String& deviceIdentifier) { - auto names = getDevices(); UINT deviceID = MIDI_MAPPER; String deviceName; + auto devices = getAvailableDevices(); - if (isPositiveAndBelow (index, names.size())) + for (int i = 0; i < devices.size(); ++i) { - deviceName = names[index]; - deviceID = index; + auto d = devices.getUnchecked (i); + + if (d.identifier == deviceIdentifier) + { + deviceID = i; + deviceName = d.name; + break; + } } const ScopedLock sl (parentService.activeCollectorLock); for (auto& c : parentService.activeCollectors) - if (c->deviceName == deviceName) + if (c->deviceInfo.identifier == deviceIdentifier) return c; - MidiInCollector::Ptr c (new MidiInCollector (parentService, deviceName)); + MidiInCollector::Ptr c (new MidiInCollector (parentService, { deviceName, deviceIdentifier })); HMIDIIN h; auto err = midiInOpen (&h, deviceID, @@ -368,29 +433,33 @@ private: return c; } - static StringArray getDevices() + static DWORD sendMidiMessage (UINT_PTR deviceID, UINT msg, DWORD_PTR arg1, DWORD_PTR arg2) { - StringArray s; - auto num = midiInGetNumDevs(); + return midiInMessage ((HMIDIIN) deviceID, msg, arg1, arg2); + } + + static Array getDeviceCaps() + { + Array devices; - for (UINT i = 0; i < num; ++i) + for (UINT i = 0; i < midiInGetNumDevs(); ++i) { MIDIINCAPS mc = { 0 }; if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) - s.add (String (mc.szPname, (size_t) numElementsInArray (mc.szPname))); + devices.add (mc); } - s.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); - return s; + return devices; } - static int getDefaultDeviceIndex() { return 0; } + static MidiDeviceInfo getDefaultDevice() { return getAvailableDevices().getFirst(); } void start() override { started = true; concatenator.reset(); collector->startOrStop(); } void stop() override { started = false; collector->startOrStop(); concatenator.reset(); } - String getDeviceName() override { return collector->deviceName; } + String getDeviceIdentifier() override { return collector->deviceInfo.identifier; } + String getDeviceName() override { return collector->deviceInfo.name; } void pushMidiData (const void* inputData, int numBytes, double time) { @@ -411,8 +480,8 @@ private: { using Ptr = ReferenceCountedObjectPtr; - MidiOutHandle (Win32MidiService& parent, const String& name, HMIDIOUT h) - : owner (parent), deviceName (name), handle (h) + MidiOutHandle (Win32MidiService& parent, MidiDeviceInfo d, HMIDIOUT h) + : owner (parent), deviceInfo (d), handle (h) { owner.activeOutputHandles.add (this); } @@ -426,32 +495,41 @@ private: } Win32MidiService& owner; - String deviceName; + MidiDeviceInfo deviceInfo; HMIDIOUT handle; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutHandle) }; //============================================================================== - struct Win32OutputWrapper : public OutputWrapper + struct Win32OutputWrapper : public OutputWrapper, + public Win32MidiDeviceQuery { - Win32OutputWrapper (Win32MidiService& p, int index) : parent (p) + Win32OutputWrapper (Win32MidiService& p, const String& deviceIdentifier) + : parent (p) { - auto names = getDevices(); + auto devices = getAvailableDevices(); UINT deviceID = MIDI_MAPPER; + String deviceName; - if (isPositiveAndBelow (index, names.size())) + for (int i = 0; i < devices.size(); ++i) { - deviceName = names[index]; - deviceID = index; + auto d = devices.getUnchecked (i); + + if (d.identifier == deviceIdentifier) + { + deviceID = i; + deviceName = d.name; + break; + } } if (deviceID == MIDI_MAPPER) { // use the microsoft sw synth as a default - best not to allow deviceID // to be MIDI_MAPPER, or else device sharing breaks - for (int i = 0; i < names.size(); ++i) - if (names[i].containsIgnoreCase ("microsoft")) + for (int i = 0; i < devices.size(); ++i) + if (devices[i].name.containsIgnoreCase ("microsoft")) deviceID = (UINT) i; } @@ -459,7 +537,7 @@ private: { auto* activeHandle = parent.activeOutputHandles.getUnchecked (i); - if (activeHandle->deviceName == deviceName) + if (activeHandle->deviceInfo.identifier == deviceIdentifier) { han = activeHandle; return; @@ -473,7 +551,7 @@ private: if (res == MMSYSERR_NOERROR) { - han = new MidiOutHandle (parent, deviceName, h); + han = new MidiOutHandle (parent, { deviceName, deviceIdentifier }, h); return; } @@ -530,12 +608,16 @@ private: } } + static DWORD sendMidiMessage (UINT_PTR deviceID, UINT msg, DWORD_PTR arg1, DWORD_PTR arg2) + { + return midiOutMessage ((HMIDIOUT) deviceID, msg, arg1, arg2); + } + static Array getDeviceCaps() { Array devices; - auto num = midiOutGetNumDevs(); - for (UINT i = 0; i < num; ++i) + for (UINT i = 0; i < midiOutGetNumDevs(); ++i) { MIDIOUTCAPS mc = { 0 }; @@ -546,36 +628,26 @@ private: return devices; } - static StringArray getDevices() + static MidiDeviceInfo getDefaultDevice() { - StringArray s; - - for (auto& mc : getDeviceCaps()) - s.add (String (mc.szPname, (size_t) numElementsInArray (mc.szPname))); - - s.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); - return s; - } - - static int getDefaultDeviceIndex() - { - int n = 0; - - for (auto& mc : getDeviceCaps()) + auto defaultIndex = []() { - if ((mc.wTechnology & MOD_MAPPER) != 0) - return n; + auto deviceCaps = getDeviceCaps(); - ++n; - } + for (int i = 0; i < deviceCaps.size(); ++i) + if ((deviceCaps[i].wTechnology & MOD_MAPPER) != 0) + return i; - return 0; + return 0; + }(); + + return getAvailableDevices()[defaultIndex]; } - String getDeviceName() override { return deviceName; } + String getDeviceIdentifier() override { return han->deviceInfo.identifier; } + String getDeviceName() override { return han->deviceInfo.name; } Win32MidiService& parent; - String deviceName; MidiOutHandle::Ptr han; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Win32OutputWrapper) @@ -671,26 +743,26 @@ public: throw std::runtime_error ("Failed to start the midi output device watcher"); } - StringArray getDevices (bool isInput) override + Array getAvailableDevices (bool isInput) override { - return isInput ? inputDeviceWatcher ->getDevices() - : outputDeviceWatcher->getDevices(); + return isInput ? inputDeviceWatcher ->getAvailableDevices() + : outputDeviceWatcher->getAvailableDevices(); } - int getDefaultDeviceIndex (bool isInput) override + MidiDeviceInfo getDefaultDevice (bool isInput) override { - return isInput ? inputDeviceWatcher ->getDefaultDeviceIndex() - : outputDeviceWatcher->getDefaultDeviceIndex(); + return isInput ? inputDeviceWatcher ->getDefaultDevice() + : outputDeviceWatcher->getDefaultDevice(); } - InputWrapper* createInputWrapper (MidiInput& input, int index, MidiInputCallback& callback) override + InputWrapper* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override { - return new WinRTInputWrapper (*this, input, index, callback); + return new WinRTInputWrapper (*this, input, deviceIdentifier, callback); } - OutputWrapper* createOutputWrapper (int index) override + OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override { - return new WinRTOutputWrapper (*this, index); + return new WinRTOutputWrapper (*this, deviceIdentifier); } private: @@ -1098,7 +1170,7 @@ private: }; //============================================================================== - struct MIDIDeviceInfo + struct WinRTMIDIDeviceInfo { String deviceID, containerID, name; bool isDefault = false; @@ -1120,7 +1192,7 @@ private: HRESULT addDevice (IDeviceInformation* addedDeviceInfo) override { - MIDIDeviceInfo info; + WinRTMIDIDeviceInfo info; HSTRING deviceID; auto hr = addedDeviceInfo->get_Id (&deviceID); @@ -1229,56 +1301,59 @@ private: return attach (deviceSelector, DeviceInformationKind::DeviceInformationKind_DeviceInterface); } - StringArray getDevices() + Array getAvailableDevices() { { const ScopedLock lock (deviceChanges); lastQueriedConnectedDevices = connectedDevices; } - StringArray result; + StringArray deviceNames, deviceIDs; for (auto info : lastQueriedConnectedDevices.get()) - result.add (info.name); + { + deviceNames.add (info.name); + deviceIDs .add (info.containerID); + } - return result; - } + deviceNames.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); + deviceIDs .appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); - int getDefaultDeviceIndex() - { - auto& lastDevices = lastQueriedConnectedDevices.get(); + Array devices; - for (int i = 0; i < lastDevices.size(); ++i) - if (lastDevices[i].isDefault) - return i; + for (int i = 0; i < deviceNames.size(); ++i) + devices.add ({ deviceNames[i], deviceIDs[i] }); - return 0; + return devices; } - MIDIDeviceInfo getDeviceInfoFromIndex (int index) + MidiDeviceInfo getDefaultDevice() { - if (isPositiveAndBelow (index, lastQueriedConnectedDevices.get().size())) - return lastQueriedConnectedDevices.get()[index]; + auto& lastDevices = lastQueriedConnectedDevices.get(); + + for (auto& d : lastDevices) + if (d.isDefault) + return { d.name, d.containerID }; return {}; } - String getDeviceID (const String& name) + WinRTMIDIDeviceInfo getWinRTDeviceInfoForDevice (const String& deviceIdentifier) { - const ScopedLock lock (deviceChanges); + auto devices = getAvailableDevices(); - for (auto info : connectedDevices) - if (info.name == name) - return info.deviceID; + for (int i = 0; i < devices.size(); ++i) + if (devices.getUnchecked (i).identifier == deviceIdentifier) + return lastQueriedConnectedDevices.get()[i]; return {}; } WinRTWrapper::ComPtr& factory; - Array connectedDevices; + Array connectedDevices; CriticalSection deviceChanges; - ThreadLocalValue> lastQueriedConnectedDevices; + ThreadLocalValue> lastQueriedConnectedDevices; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher); }; @@ -1345,12 +1420,12 @@ private: public: WinRTIOWrapper (BLEDeviceWatcher& bleWatcher, MidiIODeviceWatcher& midiDeviceWatcher, - int index) + const String& deviceIdentifier) : bleDeviceWatcher (bleWatcher) { { const ScopedLock lock (midiDeviceWatcher.deviceChanges); - deviceInfo = midiDeviceWatcher.getDeviceInfoFromIndex (index); + deviceInfo = midiDeviceWatcher.getWinRTDeviceInfoForDevice (deviceIdentifier); } if (deviceInfo.deviceID.isEmpty()) @@ -1417,7 +1492,7 @@ private: protected: //============================================================================== BLEDeviceWatcher& bleDeviceWatcher; - MIDIDeviceInfo deviceInfo; + WinRTMIDIDeviceInfo deviceInfo; bool isBLEDevice = false; WinRTWrapper::ComPtr midiPort; }; @@ -1427,8 +1502,8 @@ private: private WinRTIOWrapper { - WinRTInputWrapper (WinRTMidiService& service, MidiInput& input, int index, MidiInputCallback& cb) - : WinRTIOWrapper (*service.bleDeviceWatcher, *service.inputDeviceWatcher, index), + WinRTInputWrapper (WinRTMidiService& service, MidiInput& input, const String& deviceIdentifier, MidiInputCallback& cb) + : WinRTIOWrapper (*service.bleDeviceWatcher, *service.inputDeviceWatcher, deviceIdentifier), inputDevice (input), callback (cb) { @@ -1484,7 +1559,8 @@ private: } } - String getDeviceName() override { return deviceInfo.name; } + String getDeviceIdentifier() override { return deviceInfo.containerID; } + String getDeviceName() override { return deviceInfo.name; } //============================================================================== void disconnect() override @@ -1579,8 +1655,8 @@ private: struct WinRTOutputWrapper final : public OutputWrapper, private WinRTIOWrapper { - WinRTOutputWrapper (WinRTMidiService& service, int index) - : WinRTIOWrapper (*service.bleDeviceWatcher, *service.outputDeviceWatcher, index) + WinRTOutputWrapper (WinRTMidiService& service, const String& deviceIdentifier) + : WinRTIOWrapper (*service.bleDeviceWatcher, *service.outputDeviceWatcher, deviceIdentifier) { OpenMidiPortThread portThread ("Open WinRT MIDI output port", deviceInfo.deviceID, @@ -1632,7 +1708,8 @@ private: midiPort->SendBuffer (buffer); } - String getDeviceName() override { return deviceInfo.name; } + String getDeviceIdentifier() override { return deviceInfo.containerID; } + String getDeviceName() override { return deviceInfo.name; } //============================================================================== WinRTWrapper::ComPtr buffer; @@ -1718,42 +1795,74 @@ private: JUCE_IMPLEMENT_SINGLETON (MidiService) //============================================================================== -StringArray MidiInput::getDevices() +static int findDefaultDeviceIndex (const Array& 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 MidiInput::getAvailableDevices() { - return MidiService::getService().getDefaultDeviceIndex (true); + return MidiService::getService().getAvailableDevices (true); } -MidiInput::MidiInput (const String& deviceName) : name (deviceName) +MidiDeviceInfo MidiInput::getDefaultDevice() { + return MidiService::getService().getDefaultDevice (true); } -MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) +MidiInput* MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback) { - if (callback == nullptr) + if (deviceIdentifier.isEmpty() || callback == nullptr) return nullptr; - std::unique_ptr in (new MidiInput (String())); + MidiInput input ({}, {}); std::unique_ptr wrapper; try { - wrapper.reset (MidiService::getService().createInputWrapper (*in, index, *callback)); + wrapper.reset (MidiService::getService().createInputWrapper (input, deviceIdentifier, *callback)); } catch (std::runtime_error&) { return nullptr; } - in->setName (wrapper->getDeviceName()); + std::unique_ptr in; + in.reset (new MidiInput (wrapper->getDeviceName(), deviceIdentifier)); in->internal = wrapper.release(); + return in.release(); } +StringArray MidiInput::getDevices() +{ + StringArray deviceNames; + + for (auto& d : getAvailableDevices()) + deviceNames.add (d.name); + + return deviceNames; +} + +int MidiInput::getDefaultDeviceIndex() +{ + return findDefaultDeviceIndex (getAvailableDevices(), getDefaultDevice()); +} + +MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) +{ + return openDevice (getAvailableDevices()[index].identifier, callback); +} + +MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) + : deviceInfo (deviceName, deviceIdentifier) +{ +} + MidiInput::~MidiInput() { delete static_cast (internal); @@ -1763,34 +1872,60 @@ void MidiInput::start() { static_cast (interna void MidiInput::stop() { static_cast (internal)->stop(); } //============================================================================== -StringArray MidiOutput::getDevices() +Array 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 wrapper; try { - wrapper.reset (MidiService::getService().createOutputWrapper (index)); + wrapper.reset (MidiService::getService().createOutputWrapper (deviceIdentifier)); } catch (std::runtime_error&) { return nullptr; } - std::unique_ptr out (new MidiOutput (wrapper->getDeviceName())); + std::unique_ptr out; + out.reset (new MidiOutput (wrapper->getDeviceName(), deviceIdentifier)); + out->internal = wrapper.release(); + return out.release(); } +StringArray MidiOutput::getDevices() +{ + StringArray deviceNames; + + for (auto& d : getAvailableDevices()) + deviceNames.add (d.name); + + return deviceNames; +} + +int MidiOutput::getDefaultDeviceIndex() +{ + return findDefaultDeviceIndex (getAvailableDevices(), getDefaultDevice()); +} + +MidiOutput* MidiOutput::openDevice (int index) +{ + return openDevice (getAvailableDevices()[index].identifier); +} + MidiOutput::~MidiOutput() { stopBackgroundThread();