| @@ -110,14 +110,23 @@ public: | |||||
| /** Returns the time this block object was connected to the topology. | /** Returns the time this block object was connected to the topology. | ||||
| Only valid when isConnected == true. | Only valid when isConnected == true. | ||||
| @see isConnected | |||||
| @see Block::isConnected | |||||
| */ | */ | ||||
| virtual Time getConnectionTime() const = 0; | 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, | /** 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. | as opposed to only being connected to a different block via a connection port. | ||||
| @see ConnectionPort | |||||
| @see Block::ConnectionPort | |||||
| */ | */ | ||||
| virtual bool isMasterBlock() const = 0; | virtual bool isMasterBlock() const = 0; | ||||
| @@ -63,11 +63,7 @@ public: | |||||
| ~BlockImplementation() | ~BlockImplementation() | ||||
| { | { | ||||
| if (listenerToMidiConnection != nullptr) | |||||
| { | |||||
| config.setDeviceComms (nullptr); | |||||
| listenerToMidiConnection->removeListener (this); | |||||
| } | |||||
| markDisconnected(); | |||||
| } | } | ||||
| void markDisconnected() | void markDisconnected() | ||||
| @@ -75,6 +71,7 @@ public: | |||||
| if (auto surface = dynamic_cast<TouchSurfaceImplementation*> (touchSurface.get())) | if (auto surface = dynamic_cast<TouchSurfaceImplementation*> (touchSurface.get())) | ||||
| surface->disableTouchSurface(); | surface->disableTouchSurface(); | ||||
| disconnectMidiConnectionListener(); | |||||
| connectionTime = Time(); | connectionTime = Time(); | ||||
| } | } | ||||
| @@ -121,6 +118,32 @@ public: | |||||
| config.setDeviceComms (listenerToMidiConnection); | 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; } | Type getType() const override { return modelData.apiType; } | ||||
| String getDeviceDescription() const override { return modelData.description; } | String getDeviceDescription() const override { return modelData.description; } | ||||
| int getWidth() const override { return modelData.widthUnits; } | int getWidth() const override { return modelData.widthUnits; } | ||||
| @@ -128,7 +151,6 @@ public: | |||||
| float getMillimetersPerUnit() const override { return 47.0f; } | float getMillimetersPerUnit() const override { return 47.0f; } | ||||
| bool isHardwareBlock() const override { return true; } | bool isHardwareBlock() const override { return true; } | ||||
| juce::Array<Block::ConnectionPort> getPorts() const override { return modelData.ports; } | 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; } | Time getConnectionTime() const override { return connectionTime; } | ||||
| bool isMasterBlock() const override { return isMaster; } | bool isMasterBlock() const override { return isMaster; } | ||||
| Block::UID getConnectedMasterUID() const override { return masterUID; } | Block::UID getConnectedMasterUID() const override { return masterUID; } | ||||
| @@ -696,9 +718,7 @@ private: | |||||
| { | { | ||||
| jassert (listenerToMidiConnection == &c); | jassert (listenerToMidiConnection == &c); | ||||
| ignoreUnused (c); | ignoreUnused (c); | ||||
| listenerToMidiConnection->removeListener (this); | |||||
| listenerToMidiConnection = nullptr; | |||||
| config.setDeviceComms (nullptr); | |||||
| disconnectMidiConnectionListener(); | |||||
| } | } | ||||
| void doSaveProgramAsDefault() | void doSaveProgramAsDefault() | ||||
| @@ -41,8 +41,6 @@ struct ConnectedDeviceGroup : private AsyncUpdater, | |||||
| { | { | ||||
| if (auto midiDeviceConnection = static_cast<MIDIDeviceConnection*> (deviceConnection.get())) | if (auto midiDeviceConnection = static_cast<MIDIDeviceConnection*> (deviceConnection.get())) | ||||
| { | { | ||||
| depreciatedVersionReader = std::make_unique<DepreciatedVersionReader> (*midiDeviceConnection); | |||||
| ScopedLock lock (midiDeviceConnection->criticalSecton); | ScopedLock lock (midiDeviceConnection->criticalSecton); | ||||
| setMidiMessageCallback(); | setMidiMessageCallback(); | ||||
| } | } | ||||
| @@ -300,7 +298,8 @@ private: | |||||
| struct TouchStart { float x, y; }; | struct TouchStart { float x, y; }; | ||||
| TouchList<TouchStart> touchStartPositions; | TouchList<TouchStart> touchStartPositions; | ||||
| Block::UID masterBlock = 0; | |||||
| static constexpr Block::UID invalidUid = 0; | |||||
| Block::UID masterBlock = invalidUid; | |||||
| //============================================================================== | //============================================================================== | ||||
| void timerCallback() override | void timerCallback() override | ||||
| @@ -499,6 +498,9 @@ private: | |||||
| removeDevice (uid); | removeDevice (uid); | ||||
| if (uid == masterBlock) | |||||
| masterBlock = invalidUid; | |||||
| scheduleNewTopologyRequest(); | scheduleNewTopologyRequest(); | ||||
| } | } | ||||
| @@ -643,9 +645,14 @@ private: | |||||
| if (! getDeviceInfoFromUID (uid)) | if (! getDeviceInfoFromUID (uid)) | ||||
| { | { | ||||
| // For backwards compatibility we assume the first device we see in a group is the master and won't change | // 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; | masterBlock = uid; | ||||
| if (auto midiDeviceConnection = static_cast<MIDIDeviceConnection*> (deviceConnection.get())) | |||||
| depreciatedVersionReader = std::make_unique<DepreciatedVersionReader> (*midiDeviceConnection); | |||||
| } | |||||
| currentDeviceInfo.add ({ uid, | currentDeviceInfo.add ({ uid, | ||||
| device.index, | device.index, | ||||
| device.serialNumber, | 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. | This class can make requests and process responses to retreive the master Block version. | ||||
| */ | */ | ||||
| class DepreciatedVersionReader : private MIDIDeviceConnection::Listener, | class DepreciatedVersionReader : private MIDIDeviceConnection::Listener, | ||||
| @@ -94,6 +94,16 @@ struct Detector : public ReferenceCountedObject, | |||||
| return false; | 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) | void handleDeviceAdded (const DeviceInfo& info) | ||||
| { | { | ||||
| JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | ||||
| @@ -146,9 +156,9 @@ struct Detector : public ReferenceCountedObject, | |||||
| if (blockIt != currentTopology.blocks.end()) | 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(); | blockImpl->markDisconnected(); | ||||
| currentTopology.blocks.removeObject (block); | currentTopology.blocks.removeObject (block); | ||||
| @@ -177,9 +187,9 @@ struct Detector : public ReferenceCountedObject, | |||||
| if (blockIt != currentTopology.blocks.end()) | 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); | blockImpl->markReconnected (info); | ||||
| if (! containsBlockWithUID (blocksToAdd, info.uid)) | if (! containsBlockWithUID (blocksToAdd, info.uid)) | ||||
| @@ -32,25 +32,27 @@ struct MIDIDeviceDetector : public PhysicalTopologySource::DeviceDetector | |||||
| StringArray result; | StringArray result; | ||||
| for (auto& pair : findDevices()) | for (auto& pair : findDevices()) | ||||
| result.add (pair.inputName + " & " + pair.outputName); | |||||
| result.add (pair.input.identifier + " & " + pair.output.identifier); | |||||
| return result; | return result; | ||||
| } | } | ||||
| PhysicalTopologySource::DeviceConnection* openDevice (int index) override | 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; | 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) | if (dev->midiInput != nullptr) | ||||
| { | { | ||||
| @@ -96,33 +98,43 @@ struct MIDIDeviceDetector : public PhysicalTopologySource::DeviceDetector | |||||
| struct MidiInputOutputPair | struct MidiInputOutputPair | ||||
| { | { | ||||
| String outputName, inputName; | |||||
| int outputIndex = -1, inputIndex = -1; | |||||
| MidiDeviceInfo input, output; | |||||
| }; | }; | ||||
| static Array<MidiInputOutputPair> findDevices() | static Array<MidiInputOutputPair> findDevices() | ||||
| { | { | ||||
| Array<MidiInputOutputPair> result; | 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; | 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: | private: | ||||
| bool lockedFromOutside = true; | 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) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceDetector) | ||||
| }; | }; | ||||
| @@ -36,21 +36,11 @@ struct MIDIDeviceConnection : public PhysicalTopologySource::DeviceConnection, | |||||
| if (midiInput != nullptr) | if (midiInput != nullptr) | ||||
| midiInput->stop(); | 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 | struct Listener | ||||
| @@ -115,7 +105,7 @@ struct MIDIDeviceConnection : public PhysicalTopologySource::DeviceConnection, | |||||
| private: | private: | ||||
| ListenerList<Listener> listeners; | ListenerList<Listener> listeners; | ||||
| std::unique_ptr<InterProcessLock> interprocessLock; | |||||
| std::shared_ptr<InterProcessLock> midiPortLock; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceConnection) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceConnection) | ||||
| }; | }; | ||||