Browse Source

BLOCKS: Split PhysicalTopologySource internal classes into separate files

tags/2021-05-28
ed 6 years ago
parent
commit
77c8a873f3
11 changed files with 2780 additions and 2556 deletions
  1. +20
    -0
      modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h
  2. +101
    -0
      modules/juce_blocks_basics/topology/internal/juce_BandwidthStatsLogger.cpp
  3. +1030
    -0
      modules/juce_blocks_basics/topology/internal/juce_BlockImplementation.cpp
  4. +561
    -0
      modules/juce_blocks_basics/topology/internal/juce_ConnectedDeviceGroup.cpp
  5. +698
    -0
      modules/juce_blocks_basics/topology/internal/juce_Detector.cpp
  6. +59
    -0
      modules/juce_blocks_basics/topology/internal/juce_DetectorHolder.cpp
  7. +43
    -0
      modules/juce_blocks_basics/topology/internal/juce_DeviceInfo.cpp
  8. +142
    -0
      modules/juce_blocks_basics/topology/internal/juce_MIDIDeviceDetector.cpp
  9. +114
    -0
      modules/juce_blocks_basics/topology/internal/juce_MidiDeviceConnection.cpp
  10. +11
    -2555
      modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp
  11. +1
    -1
      modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.h

+ 20
- 0
modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h View File

@@ -153,6 +153,7 @@ struct BlockSerialNumber
bool hasPrefix (const char* prefix) const noexcept { return memcmp (serial, prefix, 3) == 0; }
};
//==============================================================================
/** Structure for the version number
@tags{Blocks}
@@ -161,8 +162,15 @@ struct VersionNumber
{
uint8 version[21] = {};
uint8 length = 0;
juce::String asString() const
{
return juce::String (reinterpret_cast<const char*> (version),
std::min (sizeof (version), static_cast<size_t> (length)));
}
};
//==============================================================================
/** Structure for the block name
@tags{Blocks}
@@ -171,8 +179,17 @@ struct BlockName
{
uint8 name[33] = {};
uint8 length = 0;
bool isValid() const { return length > 0; }
juce::String asString() const
{
return juce::String (reinterpret_cast<const char*> (name),
std::min (sizeof (name), static_cast<size_t> (length)));
}
};
//==============================================================================
/** Structure for the device status
@tags{Blocks}
@@ -185,6 +202,7 @@ struct DeviceStatus
BatteryCharging batteryCharging;
};
//==============================================================================
/** Structure for the device connection
@tags{Blocks}
@@ -195,6 +213,7 @@ struct DeviceConnection
ConnectorPort port1, port2;
};
//==============================================================================
/** Structure for the device version
@tags{Blocks}
@@ -205,6 +224,7 @@ struct DeviceVersion
VersionNumber version;
};
//==============================================================================
/** Structure used for the device name
@tags{Blocks}


+ 101
- 0
modules/juce_blocks_basics/topology/internal/juce_BandwidthStatsLogger.cpp View File

@@ -0,0 +1,101 @@
/*
==============================================================================
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
{
namespace
{
struct PortIOStats
{
PortIOStats (const char* nm) : name (nm) {}
const char* const name;
int byteCount = 0;
int messageCount = 0;
int bytesPerSec = 0;
int largestMessageBytes = 0;
int lastMessageBytes = 0;
void update (double elapsedSec)
{
if (byteCount > 0)
{
bytesPerSec = (int) (byteCount / elapsedSec);
byteCount = 0;
juce::Logger::writeToLog (getString());
}
}
juce::String getString() const
{
return juce::String (name) + ": "
+ "count=" + juce::String (messageCount).paddedRight (' ', 7)
+ "rate=" + (juce::String (bytesPerSec / 1024.0f, 1) + " Kb/sec").paddedRight (' ', 11)
+ "largest=" + (juce::String (largestMessageBytes) + " bytes").paddedRight (' ', 11)
+ "last=" + (juce::String (lastMessageBytes) + " bytes").paddedRight (' ', 11);
}
void registerMessage (int numBytes) noexcept
{
byteCount += numBytes;
++messageCount;
lastMessageBytes = numBytes;
largestMessageBytes = juce::jmax (largestMessageBytes, numBytes);
}
};
static PortIOStats inputStats { "Input" }, outputStats { "Output" };
static uint32 startTime = 0;
static inline void resetOnSecondBoundary()
{
auto now = juce::Time::getMillisecondCounter();
double elapsedSec = (now - startTime) / 1000.0;
if (elapsedSec >= 1.0)
{
inputStats.update (elapsedSec);
outputStats.update (elapsedSec);
startTime = now;
}
}
static inline void registerBytesOut (int numBytes)
{
outputStats.registerMessage (numBytes);
resetOnSecondBoundary();
}
static inline void registerBytesIn (int numBytes)
{
inputStats.registerMessage (numBytes);
resetOnSecondBoundary();
}
}
juce::String getMidiIOStats()
{
return inputStats.getString() + " " + outputStats.getString();
}
} // namespace juce

+ 1030
- 0
modules/juce_blocks_basics/topology/internal/juce_BlockImplementation.cpp
File diff suppressed because it is too large
View File


+ 561
- 0
modules/juce_blocks_basics/topology/internal/juce_ConnectedDeviceGroup.cpp View File

@@ -0,0 +1,561 @@
/*
==============================================================================
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
{
namespace
{
static Block::Timestamp deviceTimestampToHost (uint32 timestamp) noexcept
{
return static_cast<Block::Timestamp> (timestamp);
}
}
template <typename Detector>
struct ConnectedDeviceGroup : private juce::AsyncUpdater,
private juce::Timer
{
//==============================================================================
ConnectedDeviceGroup (Detector& d, const juce::String& name, PhysicalTopologySource::DeviceConnection* connection)
: detector (d), deviceName (name), deviceConnection (connection)
{
deviceConnection->handleMessageFromDevice = [this] (const void* data, size_t dataSize)
{
this->handleIncomingMessage (data, dataSize);
};
startTimer (200);
sendTopologyRequest();
}
bool isStillConnected (const juce::StringArray& detectedDevices) const noexcept
{
return detectedDevices.contains (deviceName)
&& ! failedToGetTopology();
}
int getIndexFromDeviceID (Block::UID uid) const noexcept
{
for (auto& d : currentDeviceInfo)
if (d.uid == uid)
return d.index;
return -1;
}
const DeviceInfo* getDeviceInfoFromUID (Block::UID uid) const noexcept
{
for (auto& d : currentDeviceInfo)
if (d.uid == uid)
return &d;
return nullptr;
}
const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept
{
for (auto&& status : currentTopologyDevices)
if (getBlockUIDFromSerialNumber (status.serialNumber) == deviceID)
return &status;
return nullptr;
}
void notifyBlockIsRestarting (Block::UID deviceID)
{
forceApiDisconnected (deviceID);
}
//==============================================================================
// The following methods will be called by the HostPacketDecoder:
void beginTopology (int numDevices, int numConnections)
{
incomingTopologyDevices.clearQuick();
incomingTopologyDevices.ensureStorageAllocated (numDevices);
incomingTopologyConnections.clearQuick();
incomingTopologyConnections.ensureStorageAllocated (numConnections);
}
void extendTopology (int numDevices, int numConnections)
{
incomingTopologyDevices.ensureStorageAllocated (incomingTopologyDevices.size() + numDevices);
incomingTopologyConnections.ensureStorageAllocated (incomingTopologyConnections.size() + numConnections);
}
void handleTopologyDevice (BlocksProtocol::DeviceStatus status)
{
incomingTopologyDevices.add (status);
}
void handleTopologyConnection (BlocksProtocol::DeviceConnection connection)
{
incomingTopologyConnections.add (connection);
}
void endTopology()
{
currentDeviceInfo = getArrayOfDeviceInfo (incomingTopologyDevices);
currentDeviceConnections = getArrayOfConnections (incomingTopologyConnections);
currentTopologyDevices = incomingTopologyDevices;
lastTopologyReceiveTime = juce::Time::getCurrentTime();
const int numRemoved = blockPings.removeIf ([this] (auto& ping)
{
for (auto& info : currentDeviceInfo)
if (info.uid == ping.blockUID)
return false;
LOG_CONNECTIVITY ("API Disconnected by topology update " << ping.blockUID);
return true;
});
if (numRemoved > 0)
detector.handleTopologyChange();
}
void handleVersion (BlocksProtocol::DeviceVersion version)
{
for (auto& d : currentDeviceInfo)
if (d.index == version.index && version.version.length > 1)
d.version = version.version;
}
void handleName (BlocksProtocol::DeviceName name)
{
for (auto& d : currentDeviceInfo)
if (d.index == name.index && name.name.length > 1)
d.name = name.name;
}
void handleControlButtonUpDown (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp,
BlocksProtocol::ControlButtonID buttonID, bool isDown)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
detector.handleButtonChange (deviceID, deviceTimestampToHost (timestamp), buttonID.get(), isDown);
}
void handleCustomMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, const int32* data)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
detector.handleCustomMessage (deviceID, deviceTimestampToHost (timestamp), data);
}
void handleTouchChange (BlocksProtocol::TopologyIndex deviceIndex,
uint32 timestamp,
BlocksProtocol::TouchIndex touchIndex,
BlocksProtocol::TouchPosition position,
BlocksProtocol::TouchVelocity velocity,
bool isStart, bool isEnd)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
{
TouchSurface::Touch touch;
touch.index = (int) touchIndex.get();
touch.x = position.x.toUnipolarFloat();
touch.y = position.y.toUnipolarFloat();
touch.z = position.z.toUnipolarFloat();
touch.xVelocity = velocity.vx.toBipolarFloat();
touch.yVelocity = velocity.vy.toBipolarFloat();
touch.zVelocity = velocity.vz.toBipolarFloat();
touch.eventTimestamp = deviceTimestampToHost (timestamp);
touch.isTouchStart = isStart;
touch.isTouchEnd = isEnd;
touch.blockUID = deviceID;
setTouchStartPosition (touch);
detector.handleTouchChange (deviceID, touch);
}
}
void setTouchStartPosition (TouchSurface::Touch& touch)
{
auto& startPos = touchStartPositions.getValue (touch);
if (touch.isTouchStart)
startPos = { touch.x, touch.y };
touch.startX = startPos.x;
touch.startY = startPos.y;
}
void handlePacketACK (BlocksProtocol::TopologyIndex deviceIndex,
BlocksProtocol::PacketCounter counter)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
{
detector.handleSharedDataACK (deviceID, counter);
updateApiPing (deviceID);
}
}
void handleFirmwareUpdateACK (BlocksProtocol::TopologyIndex deviceIndex,
BlocksProtocol::FirmwareUpdateACKCode resultCode,
BlocksProtocol::FirmwareUpdateACKDetail resultDetail)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
{
detector.handleFirmwareUpdateACK (deviceID, (uint8) resultCode.get(), (uint32) resultDetail.get());
updateApiPing (deviceID);
}
}
void handleConfigUpdateMessage (BlocksProtocol::TopologyIndex deviceIndex,
int32 item, int32 value, int32 min, int32 max)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
detector.handleConfigUpdateMessage (deviceID, item, value, min, max);
}
void handleConfigSetMessage (BlocksProtocol::TopologyIndex deviceIndex,
int32 item, int32 value)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
detector.handleConfigSetMessage (deviceID, item, value);
}
void handleConfigFactorySyncEndMessage (BlocksProtocol::TopologyIndex deviceIndex)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
detector.handleConfigFactorySyncEndMessage (deviceID);
}
void handleConfigFactorySyncResetMessage (BlocksProtocol::TopologyIndex deviceIndex)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
detector.handleConfigFactorySyncResetMessage (deviceID);
}
void handleLogMessage (BlocksProtocol::TopologyIndex deviceIndex, const String& message)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
detector.handleLogMessage (deviceID, message);
}
//==============================================================================
template <typename PacketBuilder>
bool sendMessageToDevice (const PacketBuilder& builder) const
{
if (deviceConnection->sendMessageToDevice (builder.getData(), (size_t) builder.size()))
{
#if DUMP_BANDWIDTH_STATS
registerBytesOut (builder.size());
#endif
return true;
}
return false;
}
PhysicalTopologySource::DeviceConnection* getDeviceConnection()
{
return deviceConnection.get();
}
juce::Array<DeviceInfo> getCurrentDeviceInfo()
{
auto blocks = currentDeviceInfo;
blocks.removeIf ([this] (DeviceInfo& info) { return ! isApiConnected (info.uid); });
return blocks;
}
juce::Array<BlockDeviceConnection> getCurrentDeviceConnections()
{
auto connections = currentDeviceConnections;
connections.removeIf ([this] (BlockDeviceConnection& c) { return ! isApiConnected (c.device1) || ! isApiConnected (c.device2); });
return connections;
}
Detector& detector;
juce::String deviceName;
static constexpr double pingTimeoutSeconds = 6.0;
private:
//==============================================================================
juce::Array<DeviceInfo> currentDeviceInfo;
juce::Array<BlockDeviceConnection> currentDeviceConnections;
std::unique_ptr<PhysicalTopologySource::DeviceConnection> deviceConnection;
juce::Array<BlocksProtocol::DeviceStatus> incomingTopologyDevices, currentTopologyDevices;
juce::Array<BlocksProtocol::DeviceConnection> incomingTopologyConnections;
juce::CriticalSection incomingPacketLock;
juce::Array<juce::MemoryBlock> incomingPackets;
struct TouchStart { float x, y; };
TouchList<TouchStart> touchStartPositions;
//==============================================================================
juce::Time lastTopologyRequestTime, lastTopologyReceiveTime;
int numTopologyRequestsSent = 0;
void scheduleNewTopologyRequest()
{
numTopologyRequestsSent = 0;
lastTopologyReceiveTime = juce::Time();
lastTopologyRequestTime = juce::Time::getCurrentTime();
}
void sendTopologyRequest()
{
++numTopologyRequestsSent;
lastTopologyRequestTime = juce::Time::getCurrentTime();
sendCommandMessage (0, BlocksProtocol::requestTopologyMessage);
}
void timerCallback() override
{
const auto now = juce::Time::getCurrentTime();
if ((now > lastTopologyReceiveTime + juce::RelativeTime::seconds (30.0))
&& now > lastTopologyRequestTime + juce::RelativeTime::seconds (1.0)
&& numTopologyRequestsSent < 4)
sendTopologyRequest();
checkApiTimeouts (now);
startApiModeOnConnectedBlocks();
}
bool failedToGetTopology() const noexcept
{
return numTopologyRequestsSent > 4 && lastTopologyReceiveTime == juce::Time();
}
bool sendCommandMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 commandID) const
{
BlocksProtocol::HostPacketBuilder<64> p;
p.writePacketSysexHeaderBytes (deviceIndex);
p.deviceControlMessage (commandID);
p.writePacketSysexFooter();
return sendMessageToDevice (p);
}
//==============================================================================
struct BlockPingTime
{
Block::UID blockUID;
juce::Time lastPing;
};
juce::Array<BlockPingTime> blockPings;
void updateApiPing (Block::UID uid)
{
const auto now = juce::Time::getCurrentTime();
if (auto* ping = getPing (uid))
{
LOG_PING ("Ping: " << uid << " " << now.formatted ("%Mm %Ss"));
ping->lastPing = now;
}
else
{
LOG_CONNECTIVITY ("API Connected " << uid);
blockPings.add ({ uid, now });
detector.handleTopologyChange();
}
}
BlockPingTime* getPing (Block::UID uid)
{
for (auto& ping : blockPings)
if (uid == ping.blockUID)
return &ping;
return nullptr;
}
void removeDeviceInfo (Block::UID uid)
{
currentDeviceInfo.removeIf ([uid] (DeviceInfo& info) { return uid == info.uid; });
}
bool isApiConnected (Block::UID uid)
{
return getPing (uid) != nullptr;
}
void forceApiDisconnected (Block::UID uid)
{
if (isApiConnected (uid))
{
// Clear all known API connections and broadcast an empty topology,
// as DNA blocks connected to the restarting block may be offline.
LOG_CONNECTIVITY ("API Disconnected " << uid << ", re-probing topology");
currentDeviceInfo.clearQuick();
blockPings.clearQuick();
detector.handleTopologyChange();
scheduleNewTopologyRequest();
}
}
void checkApiTimeouts (juce::Time now)
{
const auto timedOut = [this, now] (BlockPingTime& ping)
{
if (ping.lastPing >= now - juce::RelativeTime::seconds (pingTimeoutSeconds))
return false;
LOG_CONNECTIVITY ("Ping timeout: " << ping.blockUID);
removeDeviceInfo (ping.blockUID);
return true;
};
if (blockPings.removeIf (timedOut) > 0)
{
scheduleNewTopologyRequest();
detector.handleTopologyChange();
}
}
void startApiModeOnConnectedBlocks()
{
for (auto& info : currentDeviceInfo)
{
if (! isApiConnected (info.uid))
{
LOG_CONNECTIVITY ("API Try " << info.uid);
sendCommandMessage (info.index, BlocksProtocol::endAPIMode);
sendCommandMessage (info.index, BlocksProtocol::beginAPIMode);
}
}
}
//==============================================================================
Block::UID getDeviceIDFromIndex (BlocksProtocol::TopologyIndex index) const noexcept
{
for (auto& d : currentDeviceInfo)
if (d.index == index)
return d.uid;
return {};
}
Block::UID getDeviceIDFromMessageIndex (BlocksProtocol::TopologyIndex index) noexcept
{
const auto uid = getDeviceIDFromIndex (index);
// re-request topology if we get an event from an unknown block
if (uid == Block::UID())
scheduleNewTopologyRequest();
return uid;
}
juce::Array<BlockDeviceConnection> getArrayOfConnections (const juce::Array<BlocksProtocol::DeviceConnection>& connections)
{
juce::Array<BlockDeviceConnection> result;
for (auto&& c : connections)
{
BlockDeviceConnection dc;
dc.device1 = getDeviceIDFromIndex (c.device1);
dc.device2 = getDeviceIDFromIndex (c.device2);
if (dc.device1 <= 0 || dc.device2 <= 0)
continue;
dc.connectionPortOnDevice1 = convertConnectionPort (dc.device1, c.port1);
dc.connectionPortOnDevice2 = convertConnectionPort (dc.device2, c.port2);
result.add (dc);
}
return result;
}
Block::ConnectionPort convertConnectionPort (Block::UID uid, BlocksProtocol::ConnectorPort p) noexcept
{
if (auto* info = getDeviceInfoFromUID (uid))
return BlocksProtocol::BlockDataSheet (info->serial).convertPortIndexToConnectorPort (p);
jassertfalse;
return { Block::ConnectionPort::DeviceEdge::north, 0 };
}
//==============================================================================
void handleIncomingMessage (const void* data, size_t dataSize)
{
juce::MemoryBlock mb (data, dataSize);
{
const juce::ScopedLock sl (incomingPacketLock);
incomingPackets.add (std::move (mb));
}
triggerAsyncUpdate();
#if DUMP_BANDWIDTH_STATS
registerBytesIn ((int) dataSize);
#endif
}
void handleAsyncUpdate() override
{
juce::Array<juce::MemoryBlock> packets;
packets.ensureStorageAllocated (32);
{
const juce::ScopedLock sl (incomingPacketLock);
incomingPackets.swapWith (packets);
}
for (auto& packet : packets)
{
auto data = static_cast<const uint8*> (packet.getData());
BlocksProtocol::HostPacketDecoder<ConnectedDeviceGroup>
::processNextPacket (*this, *data, data + 1, (int) packet.getSize() - 1);
}
}
//==============================================================================
static juce::Array<DeviceInfo> getArrayOfDeviceInfo (const juce::Array<BlocksProtocol::DeviceStatus>& devices)
{
juce::Array<DeviceInfo> result;
bool isFirst = true; // TODO: First block not always master block! Assumption violated.
for (auto& device : devices)
{
BlocksProtocol::VersionNumber version;
BlocksProtocol::BlockName name;
result.add ({ getBlockUIDFromSerialNumber (device.serialNumber),
device.index,
device.serialNumber,
version,
name,
isFirst });
isFirst = false;
}
return result;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectedDeviceGroup)
};
} // namespace juce

+ 698
- 0
modules/juce_blocks_basics/topology/internal/juce_Detector.cpp View File

@@ -0,0 +1,698 @@
/*
==============================================================================
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
{
namespace
{
static bool containsBlockWithUID (const Block::Array& blocks, Block::UID uid) noexcept
{
for (auto&& block : blocks)
if (block->uid == uid)
return true;
return false;
}
static bool versionNumberChanged (const DeviceInfo& device, juce::String version) noexcept
{
auto deviceVersion = device.version.asString();
return deviceVersion != version && deviceVersion.isNotEmpty();
}
static void setVersionNumberForBlock (const DeviceInfo& deviceInfo, Block& block) noexcept
{
jassert (deviceInfo.uid == block.uid);
block.versionNumber = deviceInfo.version.asString();
}
static void setNameForBlock (const DeviceInfo& deviceInfo, Block& block)
{
jassert (deviceInfo.uid == block.uid);
block.name = deviceInfo.name.asString();
}
//==============================================================================
#if DUMP_TOPOLOGY
static juce::String idToSerialNum (const BlockTopology& topology, Block::UID uid)
{
for (auto* b : topology.blocks)
if (b->uid == uid)
return b->serialNumber;
return "???";
}
static juce::String portEdgeToString (Block::ConnectionPort port)
{
switch (port.edge)
{
case Block::ConnectionPort::DeviceEdge::north: return "north";
case Block::ConnectionPort::DeviceEdge::south: return "south";
case Block::ConnectionPort::DeviceEdge::east: return "east";
case Block::ConnectionPort::DeviceEdge::west: return "west";
}
return {};
}
static juce::String portToString (Block::ConnectionPort port)
{
return portEdgeToString (port) + "_" + juce::String (port.index);
}
static void dumpTopology (const BlockTopology& topology)
{
MemoryOutputStream m;
m << "=============================================================================" << newLine
<< "Topology: " << topology.blocks.size() << " device(s)" << newLine
<< newLine;
int index = 0;
for (auto block : topology.blocks)
{
m << "Device " << index++ << (block->isMasterBlock() ? ": (MASTER)" : ":") << newLine;
m << " Description: " << block->getDeviceDescription() << newLine
<< " Serial: " << block->serialNumber << newLine;
if (auto bi = BlockImpl<Detector>::getFrom (*block))
m << " Short address: " << (int) bi->getDeviceIndex() << newLine;
m << " Battery level: " + juce::String (juce::roundToInt (100.0f * block->getBatteryLevel())) + "%" << newLine
<< " Battery charging: " + juce::String (block->isBatteryCharging() ? "y" : "n") << newLine
<< " Width: " << block->getWidth() << newLine
<< " Height: " << block->getHeight() << newLine
<< " Millimeters per unit: " << block->getMillimetersPerUnit() << newLine
<< newLine;
}
for (auto& connection : topology.connections)
{
m << idToSerialNum (topology, connection.device1)
<< ":" << portToString (connection.connectionPortOnDevice1)
<< " <-> "
<< idToSerialNum (topology, connection.device2)
<< ":" << portToString (connection.connectionPortOnDevice2) << newLine;
}
m << "=============================================================================" << newLine;
Logger::outputDebugString (m.toString());
}
#endif
}
//==============================================================================
/** This is the main singleton object that keeps track of connected blocks */
struct Detector : public juce::ReferenceCountedObject,
private juce::Timer
{
using BlockImpl = BlockImplementation<Detector>;
Detector() : defaultDetector (new MIDIDeviceDetector()), deviceDetector (*defaultDetector)
{
startTimer (10);
}
Detector (PhysicalTopologySource::DeviceDetector& dd) : deviceDetector (dd)
{
startTimer (10);
}
~Detector()
{
jassert (activeTopologySources.isEmpty());
}
using Ptr = juce::ReferenceCountedObjectPtr<Detector>;
static Detector::Ptr getDefaultDetector()
{
auto& d = getDefaultDetectorPointer();
if (d == nullptr)
d = new Detector();
return d;
}
static Detector::Ptr& getDefaultDetectorPointer()
{
static Detector::Ptr defaultDetector;
return defaultDetector;
}
void detach (PhysicalTopologySource* pts)
{
activeTopologySources.removeAllInstancesOf (pts);
if (activeTopologySources.isEmpty())
{
for (auto& b : currentTopology.blocks)
if (auto bi = BlockImpl::getFrom (*b))
bi->sendCommandMessage (BlocksProtocol::endAPIMode);
currentTopology = {};
lastTopology = {};
auto& d = getDefaultDetectorPointer();
if (d != nullptr && d->getReferenceCount() == 2)
getDefaultDetectorPointer() = nullptr;
}
}
bool isConnected (Block::UID deviceID) const noexcept
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED // This method must only be called from the message thread!
for (auto&& b : currentTopology.blocks)
if (b->uid == deviceID)
return true;
return false;
}
const BlocksProtocol::DeviceStatus* getLastStatus (Block::UID deviceID) const noexcept
{
for (auto d : connectedDeviceGroups)
if (auto status = d->getLastStatus (deviceID))
return status;
return nullptr;
}
void handleTopologyChange()
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
{
juce::Array<DeviceInfo> newDeviceInfo;
juce::Array<BlockDeviceConnection> newDeviceConnections;
for (auto d : connectedDeviceGroups)
{
newDeviceInfo.addArray (d->getCurrentDeviceInfo());
newDeviceConnections.addArray (d->getCurrentDeviceConnections());
}
for (int i = currentTopology.blocks.size(); --i >= 0;)
{
auto currentBlock = currentTopology.blocks.getUnchecked (i);
auto newDeviceIter = std::find_if (newDeviceInfo.begin(), newDeviceInfo.end(),
[&] (DeviceInfo& info) { return info.uid == currentBlock->uid; });
auto* blockImpl = BlockImpl::getFrom (*currentBlock);
if (newDeviceIter == newDeviceInfo.end())
{
if (blockImpl != nullptr)
blockImpl->markDisconnected();
disconnectedBlocks.addIfNotAlreadyThere (currentTopology.blocks.removeAndReturn (i).get());
}
else
{
if (blockImpl != nullptr && blockImpl->wasPowerCycled())
{
blockImpl->resetPowerCycleFlag();
blockImpl->markReconnected (*newDeviceIter);
}
updateCurrentBlockInfo (currentBlock, *newDeviceIter);
}
}
static const int maxBlocksToSave = 100;
if (disconnectedBlocks.size() > maxBlocksToSave)
disconnectedBlocks.removeRange (0, 2 * (disconnectedBlocks.size() - maxBlocksToSave));
for (auto& info : newDeviceInfo)
if (info.serial.isValid() && ! containsBlockWithUID (currentTopology.blocks, getBlockUIDFromSerialNumber (info.serial)))
addBlock (info);
currentTopology.connections.swapWith (newDeviceConnections);
}
broadcastTopology();
}
void notifyBlockIsRestarting (Block::UID deviceID)
{
for (auto& group : connectedDeviceGroups)
group->notifyBlockIsRestarting (deviceID);
}
void handleSharedDataACK (Block::UID deviceID, uint32 packetCounter) const
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
if (auto* bi = getBlockImplementationWithUID (deviceID))
bi->handleSharedDataACK (packetCounter);
}
void handleFirmwareUpdateACK (Block::UID deviceID, uint8 resultCode, uint32 resultDetail)
{
if (auto* bi = getBlockImplementationWithUID (deviceID))
bi->handleFirmwareUpdateACK (resultCode, resultDetail);
}
void handleConfigUpdateMessage (Block::UID deviceID, int32 item, int32 value, int32 min, int32 max)
{
if (auto* bi = getBlockImplementationWithUID (deviceID))
bi->handleConfigUpdateMessage (item, value, min, max);
}
void notifyBlockOfConfigChange (BlockImpl& bi, uint32 item)
{
if (auto configChangedCallback = bi.configChangedCallback)
{
if (item >= bi.getMaxConfigIndex())
configChangedCallback (bi, {}, item);
else
configChangedCallback (bi, bi.getLocalConfigMetaData (item), item);
}
}
void handleConfigSetMessage (Block::UID deviceID, int32 item, int32 value)
{
if (auto* bi = getBlockImplementationWithUID (deviceID))
{
bi->handleConfigSetMessage (item, value);
notifyBlockOfConfigChange (*bi, uint32 (item));
}
}
void handleConfigFactorySyncEndMessage (Block::UID deviceID)
{
if (auto* bi = getBlockImplementationWithUID (deviceID))
notifyBlockOfConfigChange (*bi, bi->getMaxConfigIndex());
}
void handleConfigFactorySyncResetMessage (Block::UID deviceID)
{
if (auto* bi = getBlockImplementationWithUID (deviceID))
bi->resetConfigListActiveStatus();
}
void handleLogMessage (Block::UID deviceID, const String& message) const
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
if (auto* bi = getBlockImplementationWithUID (deviceID))
bi->handleLogMessage (message);
}
void handleButtonChange (Block::UID deviceID, Block::Timestamp timestamp, uint32 buttonIndex, bool isDown) const
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
if (auto* bi = getBlockImplementationWithUID (deviceID))
{
bi->pingFromDevice();
if (isPositiveAndBelow (buttonIndex, bi->getButtons().size()))
if (auto* cbi = dynamic_cast<BlockImpl::ControlButtonImplementation*> (bi->getButtons().getUnchecked (int (buttonIndex))))
cbi->broadcastButtonChange (timestamp, bi->modelData.buttons[(int) buttonIndex].type, isDown);
}
}
void handleTouchChange (Block::UID deviceID, const TouchSurface::Touch& touchEvent)
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
auto block = currentTopology.getBlockWithUID (deviceID);
if (block != nullptr)
{
if (auto* surface = dynamic_cast<BlockImpl::TouchSurfaceImplementation*> (block->getTouchSurface()))
{
TouchSurface::Touch scaledEvent (touchEvent);
scaledEvent.x *= block->getWidth();
scaledEvent.y *= block->getHeight();
scaledEvent.startX *= block->getWidth();
scaledEvent.startY *= block->getHeight();
surface->broadcastTouchChange (scaledEvent);
}
}
}
void cancelAllActiveTouches() noexcept
{
for (auto& block : currentTopology.blocks)
if (auto* surface = block->getTouchSurface())
surface->cancelAllActiveTouches();
}
void handleCustomMessage (Block::UID deviceID, Block::Timestamp timestamp, const int32* data)
{
if (auto* bi = getBlockImplementationWithUID (deviceID))
bi->handleCustomMessage (timestamp, data);
}
//==============================================================================
int getIndexFromDeviceID (Block::UID deviceID) const noexcept
{
for (auto* c : connectedDeviceGroups)
{
auto index = c->getIndexFromDeviceID (deviceID);
if (index >= 0)
return index;
}
return -1;
}
template <typename PacketBuilder>
bool sendMessageToDevice (Block::UID deviceID, const PacketBuilder& builder) const
{
for (auto* c : connectedDeviceGroups)
if (c->getIndexFromDeviceID (deviceID) >= 0)
return c->sendMessageToDevice (builder);
return false;
}
static Detector* getFrom (Block& b) noexcept
{
if (auto* bi = BlockImpl::getFrom (b))
return (bi->detector);
jassertfalse;
return nullptr;
}
PhysicalTopologySource::DeviceConnection* getDeviceConnectionFor (const Block& b)
{
for (const auto& d : connectedDeviceGroups)
{
for (const auto& info : d->getCurrentDeviceInfo())
{
if (info.uid == b.uid)
return d->getDeviceConnection();
}
}
return nullptr;
}
const PhysicalTopologySource::DeviceConnection* getDeviceConnectionFor (const Block& b) const
{
for (const auto& d : connectedDeviceGroups)
{
for (const auto& info : d->getCurrentDeviceInfo())
{
if (info.uid == b.uid)
return d->getDeviceConnection();
}
}
return nullptr;
}
std::unique_ptr<MIDIDeviceDetector> defaultDetector;
PhysicalTopologySource::DeviceDetector& deviceDetector;
juce::Array<PhysicalTopologySource*> activeTopologySources;
BlockTopology currentTopology, lastTopology;
juce::ReferenceCountedArray<Block, CriticalSection> disconnectedBlocks;
private:
void timerCallback() override
{
startTimer (1500);
auto detectedDevices = deviceDetector.scanForDevices();
handleDevicesRemoved (detectedDevices);
handleDevicesAdded (detectedDevices);
}
void handleDevicesRemoved (const juce::StringArray& detectedDevices)
{
bool anyDevicesRemoved = false;
for (int i = connectedDeviceGroups.size(); --i >= 0;)
{
if (! connectedDeviceGroups.getUnchecked(i)->isStillConnected (detectedDevices))
{
connectedDeviceGroups.remove (i);
anyDevicesRemoved = true;
}
}
if (anyDevicesRemoved)
handleTopologyChange();
}
void handleDevicesAdded (const juce::StringArray& detectedDevices)
{
for (const auto& devName : detectedDevices)
{
if (! hasDeviceFor (devName))
{
if (auto d = deviceDetector.openDevice (detectedDevices.indexOf (devName)))
{
connectedDeviceGroups.add (new ConnectedDeviceGroup<Detector> (*this, devName, d));
}
}
}
}
bool hasDeviceFor (const juce::String& devName) const
{
for (auto d : connectedDeviceGroups)
if (d->deviceName == devName)
return true;
return false;
}
void addBlock (DeviceInfo info)
{
if (! reactivateBlockIfKnown (info))
addNewBlock (info);
}
bool reactivateBlockIfKnown (DeviceInfo info)
{
const auto uid = getBlockUIDFromSerialNumber (info.serial);
for (int i = disconnectedBlocks.size(); --i >= 0;)
{
if (uid != disconnectedBlocks.getUnchecked (i)->uid)
continue;
auto block = disconnectedBlocks.removeAndReturn (i);
if (auto* blockImpl = BlockImpl::getFrom (*block))
{
blockImpl->markReconnected (info);
currentTopology.blocks.add (block);
return true;
}
}
return false;
}
void addNewBlock (DeviceInfo info)
{
currentTopology.blocks.add (new BlockImpl (info.serial, *this, info.version,
info.name, info.isMaster));
}
void updateCurrentBlockInfo (Block::Ptr blockToUpdate, DeviceInfo& updatedInfo)
{
jassert (updatedInfo.uid == blockToUpdate->uid);
if (versionNumberChanged (updatedInfo, blockToUpdate->versionNumber))
setVersionNumberForBlock (updatedInfo, *blockToUpdate);
if (updatedInfo.name.isValid())
setNameForBlock (updatedInfo, *blockToUpdate);
if (updatedInfo.isMaster != blockToUpdate->isMasterBlock())
BlockImpl::getFrom (*blockToUpdate)->setToMaster (updatedInfo.isMaster);
}
BlockImpl* getBlockImplementationWithUID (Block::UID deviceID) const noexcept
{
if (auto&& block = currentTopology.getBlockWithUID (deviceID))
return BlockImpl::getFrom (*block);
return nullptr;
}
juce::OwnedArray<ConnectedDeviceGroup<Detector>> connectedDeviceGroups;
//==============================================================================
/** This is a friend of the BlocksImplementation that will scan and set the
physical positions of the blocks */
struct BlocksTraverser
{
void traverseBlockArray (const BlockTopology& topology)
{
juce::Array<Block::UID> visited;
for (auto& block : topology.blocks)
{
if (block->isMasterBlock() && ! visited.contains (block->uid))
{
if (auto* bi = dynamic_cast<BlockImpl*> (block))
{
bi->masterUID = {};
bi->position = {};
bi->rotation = 0;
}
layoutNeighbours (*block, topology, block->uid, visited);
}
}
}
// returns the distance from corner clockwise
int getUnitForIndex (Block::Ptr block, Block::ConnectionPort::DeviceEdge edge, int index)
{
if (block->getType() == Block::seaboardBlock)
{
if (edge == Block::ConnectionPort::DeviceEdge::north)
{
if (index == 0) return 1;
if (index == 1) return 4;
}
else if (edge != Block::ConnectionPort::DeviceEdge::south)
{
return 1;
}
}
if (edge == Block::ConnectionPort::DeviceEdge::south)
return block->getWidth() - (index + 1);
if (edge == Block::ConnectionPort::DeviceEdge::west)
return block->getHeight() - (index + 1);
return index;
}
// returns how often north needs to rotate by 90 degrees
int getRotationForEdge (Block::ConnectionPort::DeviceEdge edge)
{
switch (edge)
{
case Block::ConnectionPort::DeviceEdge::north: return 0;
case Block::ConnectionPort::DeviceEdge::east: return 1;
case Block::ConnectionPort::DeviceEdge::south: return 2;
case Block::ConnectionPort::DeviceEdge::west: return 3;
}
jassertfalse;
return 0;
}
void layoutNeighbours (Block::Ptr block, const BlockTopology& topology,
Block::UID masterUid, juce::Array<Block::UID>& visited)
{
visited.add (block->uid);
for (auto& connection : topology.connections)
{
if ((connection.device1 == block->uid && ! visited.contains (connection.device2))
|| (connection.device2 == block->uid && ! visited.contains (connection.device1)))
{
const auto theirUid = connection.device1 == block->uid ? connection.device2 : connection.device1;
const auto neighbourPtr = topology.getBlockWithUID (theirUid);
if (auto* neighbour = dynamic_cast<BlockImpl*> (neighbourPtr.get()))
{
const auto myBounds = block->getBlockAreaWithinLayout();
const auto& myPort = connection.device1 == block->uid ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2;
const auto& theirPort = connection.device1 == block->uid ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1;
const auto myOffset = getUnitForIndex (block, myPort.edge, myPort.index);
const auto theirOffset = getUnitForIndex (neighbourPtr, theirPort.edge, theirPort.index);
neighbour->masterUID = masterUid;
neighbour->rotation = (2 + block->getRotation()
+ getRotationForEdge (myPort.edge)
- getRotationForEdge (theirPort.edge)) % 4;
Point<int> delta;
const auto theirBounds = neighbour->getBlockAreaWithinLayout();
switch ((block->getRotation() + getRotationForEdge (myPort.edge)) % 4)
{
case 0: // over me
delta = { myOffset - (theirBounds.getWidth() - (theirOffset + 1)), -theirBounds.getHeight() };
break;
case 1: // right of me
delta = { myBounds.getWidth(), myOffset - (theirBounds.getHeight() - (theirOffset + 1)) };
break;
case 2: // under me
delta = { (myBounds.getWidth() - (myOffset + 1)) - theirOffset, myBounds.getHeight() };
break;
case 3: // left of me
delta = { -theirBounds.getWidth(), (myBounds.getHeight() - (myOffset + 1)) - theirOffset };
break;
}
neighbour->position = myBounds.getPosition() + delta;
}
layoutNeighbours (neighbourPtr, topology, masterUid, visited);
}
}
}
};
void broadcastTopology()
{
if (currentTopology != lastTopology)
{
lastTopology = currentTopology;
BlocksTraverser traverser;
traverser.traverseBlockArray (currentTopology);
for (auto* d : activeTopologySources)
d->listeners.call ([] (TopologySource::Listener& l) { l.topologyChanged(); });
#if DUMP_TOPOLOGY
dumpTopology (lastTopology);
#endif
}
}
JUCE_DECLARE_WEAK_REFERENCEABLE (Detector)
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Detector)
};
} // namespace juce

+ 59
- 0
modules/juce_blocks_basics/topology/internal/juce_DetectorHolder.cpp View File

@@ -0,0 +1,59 @@
/*
==============================================================================
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
{
struct PhysicalTopologySource::DetectorHolder : private juce::Timer
{
DetectorHolder (PhysicalTopologySource& pts)
: topologySource (pts),
detector (Detector::getDefaultDetector())
{
startTimerHz (30);
}
DetectorHolder (PhysicalTopologySource& pts, DeviceDetector& dd)
: topologySource (pts),
detector (new Detector (dd))
{
startTimerHz (30);
}
void timerCallback() override
{
if (! topologySource.hasOwnServiceTimer())
handleTimerTick();
}
void handleTimerTick()
{
for (auto& b : detector->currentTopology.blocks)
if (auto bi = BlockImplementation<Detector>::getFrom (*b))
bi->handleTimerTick();
}
PhysicalTopologySource& topologySource;
Detector::Ptr detector;
};
} // namespace juce

+ 43
- 0
modules/juce_blocks_basics/topology/internal/juce_DeviceInfo.cpp View File

@@ -0,0 +1,43 @@
/*
==============================================================================
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
{
struct DeviceInfo
{
// VS2015 requires a constructor to avoid aggregate initialization
DeviceInfo (Block::UID buid, BlocksProtocol::TopologyIndex tidx, BlocksProtocol::BlockSerialNumber s,
BlocksProtocol::VersionNumber v, BlocksProtocol::BlockName n, bool master = false)
: uid (buid), index (tidx), serial (s), version (v), name (n), isMaster (master)
{
}
Block::UID uid {};
BlocksProtocol::TopologyIndex index;
BlocksProtocol::BlockSerialNumber serial;
BlocksProtocol::VersionNumber version;
BlocksProtocol::BlockName name;
bool isMaster {};
};
} // namespace juce

+ 142
- 0
modules/juce_blocks_basics/topology/internal/juce_MIDIDeviceDetector.cpp View File

@@ -0,0 +1,142 @@
/*
==============================================================================
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
{
struct MIDIDeviceDetector : public PhysicalTopologySource::DeviceDetector
{
MIDIDeviceDetector() {}
juce::StringArray scanForDevices() override
{
juce::StringArray result;
for (auto& pair : findDevices())
result.add (pair.inputName + " & " + pair.outputName);
return result;
}
PhysicalTopologySource::DeviceConnection* openDevice (int index) override
{
auto pair = findDevices()[index];
if (pair.inputIndex >= 0 && pair.outputIndex >= 0)
{
std::unique_ptr<MIDIDeviceConnection> dev (new MIDIDeviceConnection());
if (dev->lockAgainstOtherProcesses (pair.inputName, pair.outputName))
{
lockedFromOutside = false;
dev->midiInput.reset (juce::MidiInput::openDevice (pair.inputIndex, dev.get()));
dev->midiOutput.reset (juce::MidiOutput::openDevice (pair.outputIndex));
if (dev->midiInput != nullptr)
{
dev->midiInput->start();
return dev.release();
}
}
else
{
lockedFromOutside = true;
}
}
return nullptr;
}
bool isLockedFromOutside() const override
{
return lockedFromOutside && ! findDevices().isEmpty();
}
static bool isBlocksMidiDeviceName (const juce::String& name)
{
return name.indexOf (" BLOCK") > 0 || name.indexOf (" Block") > 0;
}
static String cleanBlocksDeviceName (juce::String name)
{
name = name.trim();
if (name.endsWith (" IN)"))
return name.dropLastCharacters (4);
if (name.endsWith (" OUT)"))
return name.dropLastCharacters (5);
const int openBracketPosition = name.lastIndexOfChar ('[');
if (openBracketPosition != -1 && name.endsWith ("]"))
return name.dropLastCharacters (name.length() - openBracketPosition);
return name;
}
struct MidiInputOutputPair
{
juce::String outputName, inputName;
int outputIndex = -1, inputIndex = -1;
};
static juce::Array<MidiInputOutputPair> findDevices()
{
juce::Array<MidiInputOutputPair> result;
auto midiInputs = juce::MidiInput::getDevices();
auto midiOutputs = juce::MidiOutput::getDevices();
for (int j = 0; j < midiInputs.size(); ++j)
{
if (isBlocksMidiDeviceName (midiInputs[j]))
{
MidiInputOutputPair pair;
pair.inputName = midiInputs[j];
pair.inputIndex = j;
String cleanedInputName = cleanBlocksDeviceName (pair.inputName);
for (int i = 0; i < midiOutputs.size(); ++i)
{
if (cleanBlocksDeviceName (midiOutputs[i]) == cleanedInputName)
{
pair.outputName = midiOutputs[i];
pair.outputIndex = i;
break;
}
}
result.add (pair);
}
}
return result;
}
private:
bool lockedFromOutside = true;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceDetector)
};
} // namespace juce

+ 114
- 0
modules/juce_blocks_basics/topology/internal/juce_MidiDeviceConnection.cpp View File

@@ -0,0 +1,114 @@
/*
==============================================================================
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
{
struct MIDIDeviceConnection : public PhysicalTopologySource::DeviceConnection,
public juce::MidiInputCallback
{
MIDIDeviceConnection() {}
~MIDIDeviceConnection()
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
listeners.call ([this] (Listener& l) { l.connectionBeingDeleted (*this); });
if (midiInput != nullptr)
midiInput->stop();
if (interprocessLock != nullptr)
interprocessLock->exit();
}
bool lockAgainstOtherProcesses (const String& midiInName, const String& midiOutName)
{
interprocessLock.reset (new juce::InterProcessLock ("blocks_sdk_"
+ File::createLegalFileName (midiInName)
+ "_" + File::createLegalFileName (midiOutName)));
if (interprocessLock->enter (500))
return true;
interprocessLock = nullptr;
return false;
}
struct Listener
{
virtual ~Listener() {}
virtual void handleIncomingMidiMessage (const juce::MidiMessage& message) = 0;
virtual void connectionBeingDeleted (const MIDIDeviceConnection&) = 0;
};
void addListener (Listener* l)
{
listeners.add (l);
}
void removeListener (Listener* l)
{
listeners.remove (l);
}
bool sendMessageToDevice (const void* data, size_t dataSize) override
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED // This method must only be called from the message thread!
jassert (dataSize > sizeof (BlocksProtocol::roliSysexHeader) + 2);
jassert (memcmp (data, BlocksProtocol::roliSysexHeader, sizeof (BlocksProtocol::roliSysexHeader)) == 0);
jassert (static_cast<const uint8*> (data)[dataSize - 1] == 0xf7);
if (midiOutput != nullptr)
{
midiOutput->sendMessageNow (juce::MidiMessage (data, (int) dataSize));
return true;
}
return false;
}
void handleIncomingMidiMessage (juce::MidiInput*, const juce::MidiMessage& message) override
{
const auto data = message.getRawData();
const int dataSize = message.getRawDataSize();
const int bodySize = dataSize - (int) (sizeof (BlocksProtocol::roliSysexHeader) + 1);
if (bodySize > 0 && memcmp (data, BlocksProtocol::roliSysexHeader, sizeof (BlocksProtocol::roliSysexHeader)) == 0)
if (handleMessageFromDevice != nullptr)
handleMessageFromDevice (data + sizeof (BlocksProtocol::roliSysexHeader), (size_t) bodySize);
listeners.call ([&] (Listener& l) { l.handleIncomingMidiMessage (message); });
}
std::unique_ptr<juce::MidiInput> midiInput;
std::unique_ptr<juce::MidiOutput> midiOutput;
private:
juce::ListenerList<Listener> listeners;
std::unique_ptr<juce::InterProcessLock> interprocessLock;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceConnection)
};
} // namespace juce

+ 11
- 2555
modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp
File diff suppressed because it is too large
View File


+ 1
- 1
modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.h View File

@@ -88,7 +88,7 @@ protected:
private:
//==========================================================================
DeviceDetector* customDetector = nullptr;
struct Internal;
friend struct Detector;
struct DetectorHolder;
std::unique_ptr<DetectorHolder> detector;


Loading…
Cancel
Save