| @@ -110,14 +110,23 @@ public: | |||
| /** Returns the time this block object was connected to the topology. | |||
| Only valid when isConnected == true. | |||
| @see isConnected | |||
| @see Block::isConnected | |||
| */ | |||
| virtual Time getConnectionTime() const = 0; | |||
| /** Returns true if this block or the master block this block is connected to, | |||
| is connected via bluetooth. | |||
| Only valid when isConnected == true. | |||
| @see Block::isConnected, Block::isMasterBlock | |||
| */ | |||
| virtual bool isConnectedViaBluetooth() const = 0; | |||
| /** Returns true if this block is directly connected to the application, | |||
| as opposed to only being connected to a different block via a connection port. | |||
| @see ConnectionPort | |||
| @see Block::ConnectionPort | |||
| */ | |||
| virtual bool isMasterBlock() const = 0; | |||
| @@ -63,11 +63,7 @@ public: | |||
| ~BlockImplementation() | |||
| { | |||
| if (listenerToMidiConnection != nullptr) | |||
| { | |||
| config.setDeviceComms (nullptr); | |||
| listenerToMidiConnection->removeListener (this); | |||
| } | |||
| markDisconnected(); | |||
| } | |||
| void markDisconnected() | |||
| @@ -75,6 +71,7 @@ public: | |||
| if (auto surface = dynamic_cast<TouchSurfaceImplementation*> (touchSurface.get())) | |||
| surface->disableTouchSurface(); | |||
| disconnectMidiConnectionListener(); | |||
| connectionTime = Time(); | |||
| } | |||
| @@ -121,6 +118,32 @@ public: | |||
| config.setDeviceComms (listenerToMidiConnection); | |||
| } | |||
| void disconnectMidiConnectionListener() | |||
| { | |||
| if (listenerToMidiConnection != nullptr) | |||
| { | |||
| config.setDeviceComms (nullptr); | |||
| listenerToMidiConnection->removeListener (this); | |||
| listenerToMidiConnection = nullptr; | |||
| } | |||
| } | |||
| bool isConnected() const override | |||
| { | |||
| if (detector != nullptr) | |||
| return detector->isConnected (uid); | |||
| return false; | |||
| } | |||
| bool isConnectedViaBluetooth() const override | |||
| { | |||
| if (detector != nullptr) | |||
| return detector->isConnectedViaBluetooth (*this); | |||
| return false; | |||
| } | |||
| Type getType() const override { return modelData.apiType; } | |||
| String getDeviceDescription() const override { return modelData.description; } | |||
| int getWidth() const override { return modelData.widthUnits; } | |||
| @@ -128,7 +151,6 @@ public: | |||
| float getMillimetersPerUnit() const override { return 47.0f; } | |||
| bool isHardwareBlock() const override { return true; } | |||
| juce::Array<Block::ConnectionPort> getPorts() const override { return modelData.ports; } | |||
| bool isConnected() const override { return detector && detector->isConnected (uid); } | |||
| Time getConnectionTime() const override { return connectionTime; } | |||
| bool isMasterBlock() const override { return isMaster; } | |||
| Block::UID getConnectedMasterUID() const override { return masterUID; } | |||
| @@ -696,9 +718,7 @@ private: | |||
| { | |||
| jassert (listenerToMidiConnection == &c); | |||
| ignoreUnused (c); | |||
| listenerToMidiConnection->removeListener (this); | |||
| listenerToMidiConnection = nullptr; | |||
| config.setDeviceComms (nullptr); | |||
| disconnectMidiConnectionListener(); | |||
| } | |||
| void doSaveProgramAsDefault() | |||
| @@ -41,8 +41,6 @@ struct ConnectedDeviceGroup : private AsyncUpdater, | |||
| { | |||
| if (auto midiDeviceConnection = static_cast<MIDIDeviceConnection*> (deviceConnection.get())) | |||
| { | |||
| depreciatedVersionReader = std::make_unique<DepreciatedVersionReader> (*midiDeviceConnection); | |||
| ScopedLock lock (midiDeviceConnection->criticalSecton); | |||
| setMidiMessageCallback(); | |||
| } | |||
| @@ -300,7 +298,8 @@ private: | |||
| struct TouchStart { float x, y; }; | |||
| TouchList<TouchStart> touchStartPositions; | |||
| Block::UID masterBlock = 0; | |||
| static constexpr Block::UID invalidUid = 0; | |||
| Block::UID masterBlock = invalidUid; | |||
| //============================================================================== | |||
| void timerCallback() override | |||
| @@ -499,6 +498,9 @@ private: | |||
| removeDevice (uid); | |||
| if (uid == masterBlock) | |||
| masterBlock = invalidUid; | |||
| scheduleNewTopologyRequest(); | |||
| } | |||
| @@ -643,9 +645,14 @@ private: | |||
| if (! getDeviceInfoFromUID (uid)) | |||
| { | |||
| // For backwards compatibility we assume the first device we see in a group is the master and won't change | |||
| if (masterBlock == 0) | |||
| if (masterBlock == invalidUid) | |||
| { | |||
| masterBlock = uid; | |||
| if (auto midiDeviceConnection = static_cast<MIDIDeviceConnection*> (deviceConnection.get())) | |||
| depreciatedVersionReader = std::make_unique<DepreciatedVersionReader> (*midiDeviceConnection); | |||
| } | |||
| currentDeviceInfo.add ({ uid, | |||
| device.index, | |||
| device.serialNumber, | |||
| @@ -24,7 +24,7 @@ namespace juce | |||
| { | |||
| /** | |||
| Firmware below 0.2.5 does not report its version over the Blocks API. | |||
| Firmware below 0.3.0 does not report its version over the Blocks API. | |||
| This class can make requests and process responses to retreive the master Block version. | |||
| */ | |||
| class DepreciatedVersionReader : private MIDIDeviceConnection::Listener, | |||
| @@ -94,6 +94,16 @@ struct Detector : public ReferenceCountedObject, | |||
| return false; | |||
| } | |||
| bool isConnectedViaBluetooth (const Block& block) const noexcept | |||
| { | |||
| if (const auto connection = getDeviceConnectionFor (block)) | |||
| if (const auto midiConnection = dynamic_cast<const MIDIDeviceConnection*> (connection)) | |||
| if (midiConnection->midiInput != nullptr) | |||
| return midiConnection->midiInput->getName().containsIgnoreCase ("bluetooth"); | |||
| return false; | |||
| } | |||
| void handleDeviceAdded (const DeviceInfo& info) | |||
| { | |||
| JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | |||
| @@ -146,9 +156,9 @@ struct Detector : public ReferenceCountedObject, | |||
| if (blockIt != currentTopology.blocks.end()) | |||
| { | |||
| const auto block = *blockIt; | |||
| const Block::Ptr block { *blockIt }; | |||
| if (auto blockImpl = BlockImpl::getFrom (block)) | |||
| if (auto blockImpl = BlockImpl::getFrom (block.get())) | |||
| blockImpl->markDisconnected(); | |||
| currentTopology.blocks.removeObject (block); | |||
| @@ -177,9 +187,9 @@ struct Detector : public ReferenceCountedObject, | |||
| if (blockIt != currentTopology.blocks.end()) | |||
| { | |||
| const auto block = *blockIt; | |||
| const Block::Ptr block { *blockIt }; | |||
| if (auto blockImpl = BlockImpl::getFrom (block)) | |||
| if (auto blockImpl = BlockImpl::getFrom (block.get())) | |||
| blockImpl->markReconnected (info); | |||
| if (! containsBlockWithUID (blocksToAdd, info.uid)) | |||
| @@ -32,25 +32,27 @@ struct MIDIDeviceDetector : public PhysicalTopologySource::DeviceDetector | |||
| StringArray result; | |||
| for (auto& pair : findDevices()) | |||
| result.add (pair.inputName + " & " + pair.outputName); | |||
| result.add (pair.input.identifier + " & " + pair.output.identifier); | |||
| return result; | |||
| } | |||
| PhysicalTopologySource::DeviceConnection* openDevice (int index) override | |||
| { | |||
| auto pair = findDevices()[index]; | |||
| const auto allDevices = findDevices(); | |||
| if (pair.inputIndex >= 0 && pair.outputIndex >= 0) | |||
| if (allDevices.size() > index) | |||
| { | |||
| std::unique_ptr<MIDIDeviceConnection> dev (new MIDIDeviceConnection()); | |||
| const auto pair = allDevices[index]; | |||
| auto dev = std::make_unique<MIDIDeviceConnection>(); | |||
| if (dev->lockAgainstOtherProcesses (pair.inputName, pair.outputName)) | |||
| if (auto lock = createMidiPortLock (pair.input.name, pair.output.name)) | |||
| { | |||
| lockedFromOutside = false; | |||
| dev->midiInput.reset (MidiInput::openDevice (pair.inputIndex, dev.get())); | |||
| dev->midiOutput.reset (MidiOutput::openDevice (pair.outputIndex)); | |||
| dev->setLockAgainstOtherProcesses (lock); | |||
| dev->midiInput.reset (MidiInput::openDevice (pair.input.identifier, dev.get())); | |||
| dev->midiOutput.reset (MidiOutput::openDevice (pair.output.identifier)); | |||
| if (dev->midiInput != nullptr) | |||
| { | |||
| @@ -96,33 +98,43 @@ struct MIDIDeviceDetector : public PhysicalTopologySource::DeviceDetector | |||
| struct MidiInputOutputPair | |||
| { | |||
| String outputName, inputName; | |||
| int outputIndex = -1, inputIndex = -1; | |||
| MidiDeviceInfo input, output; | |||
| }; | |||
| static Array<MidiInputOutputPair> findDevices() | |||
| { | |||
| Array<MidiInputOutputPair> result; | |||
| auto midiInputs = MidiInput::getDevices(); | |||
| auto midiOutputs = MidiOutput::getDevices(); | |||
| auto midiInputs = MidiInput::getAvailableDevices(); | |||
| auto midiOutputs = MidiOutput::getAvailableDevices(); | |||
| for (int j = 0; j < midiInputs.size(); ++j) | |||
| for (const auto& input : midiInputs) | |||
| { | |||
| if (isBlocksMidiDeviceName (midiInputs[j])) | |||
| if (isBlocksMidiDeviceName (input.name)) | |||
| { | |||
| MidiInputOutputPair pair; | |||
| pair.inputName = midiInputs[j]; | |||
| pair.inputIndex = j; | |||
| pair.input = input; | |||
| String cleanedInputName = cleanBlocksDeviceName (pair.inputName); | |||
| for (int i = 0; i < midiOutputs.size(); ++i) | |||
| String cleanedInputName = cleanBlocksDeviceName (input.name); | |||
| int inputOccurences = 0; | |||
| int outputOccurences = 0; | |||
| for (const auto& p : result) | |||
| if (cleanBlocksDeviceName (p.input.name) == cleanedInputName) | |||
| ++inputOccurences; | |||
| for (const auto& output : midiOutputs) | |||
| { | |||
| if (cleanBlocksDeviceName (midiOutputs[i]) == cleanedInputName) | |||
| if (cleanBlocksDeviceName (output.name) == cleanedInputName) | |||
| { | |||
| pair.outputName = midiOutputs[i]; | |||
| pair.outputIndex = i; | |||
| break; | |||
| if (outputOccurences == inputOccurences) | |||
| { | |||
| pair.output = output; | |||
| break; | |||
| } | |||
| ++outputOccurences; | |||
| } | |||
| } | |||
| @@ -136,6 +148,35 @@ struct MIDIDeviceDetector : public PhysicalTopologySource::DeviceDetector | |||
| private: | |||
| bool lockedFromOutside = true; | |||
| /** For backwards compatibility, the block interprocess lock has to use the midi input name. | |||
| The below is necceccary because blocks of the same type might duplicate a port name, so | |||
| must share an interporcess lock. | |||
| */ | |||
| std::shared_ptr<InterProcessLock> createMidiPortLock (const String& midiInName, const String& midiOutName) | |||
| { | |||
| const juce::String lockIdentifier = "blocks_sdk_" | |||
| + File::createLegalFileName (midiInName) | |||
| + "_" + File::createLegalFileName (midiOutName); | |||
| const auto existingLock = midiPortLocks.find (lockIdentifier); | |||
| if (existingLock != midiPortLocks.end()) | |||
| if (existingLock->second.use_count() > 0) | |||
| return existingLock->second.lock(); | |||
| auto interprocessLock = std::make_shared<InterProcessLock> (lockIdentifier); | |||
| if (interprocessLock->enter (500)) | |||
| { | |||
| midiPortLocks[lockIdentifier] = interprocessLock; | |||
| return interprocessLock; | |||
| } | |||
| return nullptr; | |||
| } | |||
| std::map<juce::String, std::weak_ptr<InterProcessLock>> midiPortLocks; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceDetector) | |||
| }; | |||
| @@ -36,21 +36,11 @@ struct MIDIDeviceConnection : public PhysicalTopologySource::DeviceConnection, | |||
| if (midiInput != nullptr) | |||
| midiInput->stop(); | |||
| if (interprocessLock != nullptr) | |||
| interprocessLock->exit(); | |||
| } | |||
| bool lockAgainstOtherProcesses (const String& midiInName, const String& midiOutName) | |||
| void setLockAgainstOtherProcesses (std::shared_ptr<InterProcessLock> newLock) | |||
| { | |||
| interprocessLock.reset (new InterProcessLock ("blocks_sdk_" | |||
| + File::createLegalFileName (midiInName) | |||
| + "_" + File::createLegalFileName (midiOutName))); | |||
| if (interprocessLock->enter (500)) | |||
| return true; | |||
| interprocessLock = nullptr; | |||
| return false; | |||
| midiPortLock = newLock; | |||
| } | |||
| struct Listener | |||
| @@ -115,7 +105,7 @@ struct MIDIDeviceConnection : public PhysicalTopologySource::DeviceConnection, | |||
| private: | |||
| ListenerList<Listener> listeners; | |||
| std::unique_ptr<InterProcessLock> interprocessLock; | |||
| std::shared_ptr<InterProcessLock> midiPortLock; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceConnection) | |||
| }; | |||