@@ -52,8 +52,8 @@ Block::Block (const juce::String& serial) | |||||
{ | { | ||||
} | } | ||||
Block::Block (const juce::String& serial, const juce::String& version) | |||||
: serialNumber (serial), versionNumber (version), uid (getBlockUIDFromSerialNumber (serial)) | |||||
Block::Block (const juce::String& serial, const juce::String& version, const juce::String& blockName) | |||||
: serialNumber (serial), versionNumber (version), name (blockName), uid (getBlockUIDFromSerialNumber (serial)) | |||||
{ | { | ||||
} | } | ||||
@@ -62,6 +62,9 @@ public: | |||||
/** The Block's version number */ | /** The Block's version number */ | ||||
juce::String versionNumber; | juce::String versionNumber; | ||||
/** The Block's name */ | |||||
juce::String name; | |||||
using UID = uint64; | using UID = uint64; | ||||
/** This Block's UID. | /** This Block's UID. | ||||
@@ -371,6 +374,15 @@ public: | |||||
/** Reset all items active status */ | /** Reset all items active status */ | ||||
virtual void resetConfigListActiveStatus() = 0; | virtual void resetConfigListActiveStatus() = 0; | ||||
/** Perform factory reset on Block */ | |||||
virtual void factoryReset() = 0; | |||||
/** Reset this Block */ | |||||
virtual void blockReset() = 0; | |||||
/** Set Block name */ | |||||
virtual bool setName (const juce::String& name) = 0; | |||||
//============================================================================== | //============================================================================== | ||||
/** Allows the user to provide a function that will receive log messages from the block. */ | /** Allows the user to provide a function that will receive log messages from the block. */ | ||||
virtual void setLogger (std::function<void(const String&)> loggingCallback) = 0; | virtual void setLogger (std::function<void(const String&)> loggingCallback) = 0; | ||||
@@ -410,7 +422,7 @@ public: | |||||
protected: | protected: | ||||
//============================================================================== | //============================================================================== | ||||
Block (const juce::String& serialNumberToUse); | Block (const juce::String& serialNumberToUse); | ||||
Block (const juce::String& serial, const juce::String& version); | |||||
Block (const juce::String& serial, const juce::String& version, const juce::String& name); | |||||
juce::ListenerList<DataInputPortListener> dataInputPortListeners; | juce::ListenerList<DataInputPortListener> dataInputPortListeners; | ||||
juce::ListenerList<ProgramEventListener> programEventListeners; | juce::ListenerList<ProgramEventListener> programEventListeners; | ||||
@@ -73,16 +73,16 @@ struct BlockConfigManager | |||||
ConfigDescription configList[numConfigItems] = | ConfigDescription configList[numConfigItems] = | ||||
{ | { | ||||
{ midiStartChannel, 1, 0, 15, false, "MIDI Start Channel", ConfigType::integer, {}, "MIDI Settings" }, | |||||
{ midiEndChannel, 15, 0, 15, false, "MIDI End Channel", ConfigType::integer, {}, "MIDI Settings" }, | |||||
{ midiStartChannel, 2, 1, 16, false, "MIDI Start Channel", ConfigType::integer, {}, "MIDI Settings" }, | |||||
{ midiEndChannel, 16, 1, 16, false, "MIDI End Channel", ConfigType::integer, {}, "MIDI Settings" }, | |||||
{ midiUseMPE, 1, 0, 1, false, "Use MPE", ConfigType::boolean, {}, "MIDI Settings" }, | { midiUseMPE, 1, 0, 1, false, "Use MPE", ConfigType::boolean, {}, "MIDI Settings" }, | ||||
{ pitchBendRange, 48, 1, 96, false, "Pitch Bend Range", ConfigType::integer, {}, "MIDI Settings" }, | { pitchBendRange, 48, 1, 96, false, "Pitch Bend Range", ConfigType::integer, {}, "MIDI Settings" }, | ||||
{ octave, 0, -4, 6, false, "Octave", ConfigType::integer, {}, "Pitch" }, | { octave, 0, -4, 6, false, "Octave", ConfigType::integer, {}, "Pitch" }, | ||||
{ transpose, 0, -11, 11, false, "Transpose", ConfigType::integer, {}, "Pitch" }, | { transpose, 0, -11, 11, false, "Transpose", ConfigType::integer, {}, "Pitch" }, | ||||
{ slideCC, 74, 0, 127, false, "Slide CC", ConfigType::integer, {}, "5D Touch" }, | |||||
{ slideCC, 74, 0, 127, false, "Slide CC", ConfigType::integer, {}, "Play mode" }, | |||||
{ slideMode, 0, 0, 2, false, "Slide Mode", ConfigType::options, { "Absolute", | { slideMode, 0, 0, 2, false, "Slide Mode", ConfigType::options, { "Absolute", | ||||
"Relative Unipolar", | "Relative Unipolar", | ||||
"Relative Bipolar" }, "5D Touch" }, | |||||
"Relative Bipolar" }, "Play mode" }, | |||||
{ velocitySensitivity, 100, 0, 127, false, "Strike Sensitivity", ConfigType::integer, {}, "5D Touch" }, | { velocitySensitivity, 100, 0, 127, false, "Strike Sensitivity", ConfigType::integer, {}, "5D Touch" }, | ||||
{ glideSensitivity, 100, 0, 127, false, "Glide Sensitivity", ConfigType::integer, {}, "5D Touch" }, | { glideSensitivity, 100, 0, 127, false, "Glide Sensitivity", ConfigType::integer, {}, "5D Touch" }, | ||||
{ slideSensitivity, 100, 0, 127, false, "Slide Sensitivity", ConfigType::integer, {}, "5D Touch" }, | { slideSensitivity, 100, 0, 127, false, "Slide Sensitivity", ConfigType::integer, {}, "5D Touch" }, | ||||
@@ -90,8 +90,8 @@ struct BlockConfigManager | |||||
{ liftSensitivity, 100, 0, 127, false, "Lift Sensitivity", ConfigType::integer, {}, "5D Touch" }, | { liftSensitivity, 100, 0, 127, false, "Lift Sensitivity", ConfigType::integer, {}, "5D Touch" }, | ||||
{ fixedVelocity, 0, 0, 1, false, "Fixed Velocity", ConfigType::boolean, {}, "5D Touch" }, | { fixedVelocity, 0, 0, 1, false, "Fixed Velocity", ConfigType::boolean, {}, "5D Touch" }, | ||||
{ fixedVelocityValue, 127, 1, 127, false, "Fixed Velocity Value", ConfigType::integer, {}, "5D Touch" }, | { fixedVelocityValue, 127, 1, 127, false, "Fixed Velocity Value", ConfigType::integer, {}, "5D Touch" }, | ||||
{ pianoMode, 0, 0, 1, false, "Piano Mode", ConfigType::boolean, {}, "5D Touch" }, | |||||
{ glideLock, 0, 0, 127, false, "Glide Lock", ConfigType::integer, {}, "Play mode" }, | |||||
{ pianoMode, 0, 0, 1, false, "Piano Mode", ConfigType::boolean, {}, "Play mode" }, | |||||
{ glideLock, 0, 0, 127, false, "Glide Rate", ConfigType::integer, {}, "Play mode" }, | |||||
{ mode, 4, 1, 5, false, "Mode", ConfigType::integer, {}, "Play mode" }, | { mode, 4, 1, 5, false, "Mode", ConfigType::integer, {}, "Play mode" }, | ||||
{ volume, 100, 0, 127, false, "Volume", ConfigType::integer, {}, "Play mode" }, | { volume, 100, 0, 127, false, "Volume", ConfigType::integer, {}, "Play mode" }, | ||||
{ scale, 0, 0, 18, false, "Scale", ConfigType::integer, {}, "Play mode" }, // NOTE: Should be options | { scale, 0, 0, 18, false, "Scale", ConfigType::integer, {}, "Play mode" }, // NOTE: Should be options | ||||
@@ -103,18 +103,18 @@ struct BlockConfigManager | |||||
"Last Played", | "Last Played", | ||||
"Highest", | "Highest", | ||||
"Lowest", | "Lowest", | ||||
"Disabled" }, "5D Touch" }, | |||||
"Disabled" }, "Play mode" }, | |||||
{ yTrackingMode, 1, 0, 4, false, "Slide Tracking Mode", ConfigType::options, { "Multi-Channel", | { yTrackingMode, 1, 0, 4, false, "Slide Tracking Mode", ConfigType::options, { "Multi-Channel", | ||||
"Last Played", | "Last Played", | ||||
"Highest", | "Highest", | ||||
"Lowest", | "Lowest", | ||||
"Disabled" }, "5D Touch" }, | |||||
"Disabled" }, "Play mode" }, | |||||
{ zTrackingMode, 1, 0, 4, false, "Pressure Tracking Mode", ConfigType::options, { "Multi-Channel", | { zTrackingMode, 1, 0, 4, false, "Pressure Tracking Mode", ConfigType::options, { "Multi-Channel", | ||||
"Last Played", | "Last Played", | ||||
"Highest", | "Highest", | ||||
"Lowest", | "Lowest", | ||||
"Disabled", | "Disabled", | ||||
"Hardest" }, "5D Touch" }, | |||||
"Hardest" }, "Play mode" }, | |||||
// These can be defined for unique usage for a given Littlefoot script | // These can be defined for unique usage for a given Littlefoot script | ||||
{ user0, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | { user0, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | ||||
{ user1, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | { user1, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | ||||
@@ -208,8 +208,10 @@ private: | |||||
lightGridWidth = 0; | lightGridWidth = 0; | ||||
lightGridHeight = 0; | lightGridHeight = 0; | ||||
numKeywaves = 24; | numKeywaves = 24; | ||||
addPorts (2, 1, 0, 1); | |||||
addPortsSW (Block::ConnectionPort::DeviceEdge::west, 1); | |||||
addPortsNE (Block::ConnectionPort::DeviceEdge::north, 2); | |||||
addPortsNE (Block::ConnectionPort::DeviceEdge::east, 1); | |||||
hasTouchSurface = true; | hasTouchSurface = true; | ||||
programAndHeapSize = BlocksProtocol::padBlockProgramAndHeapSize; | programAndHeapSize = BlocksProtocol::padBlockProgramAndHeapSize; | ||||
@@ -48,6 +48,7 @@ enum class MessageFromDevice | |||||
deviceTopologyExtend = 0x04, | deviceTopologyExtend = 0x04, | ||||
deviceTopologyEnd = 0x05, | deviceTopologyEnd = 0x05, | ||||
deviceVersionList = 0x06, | deviceVersionList = 0x06, | ||||
deviceNameList = 0x07, | |||||
touchStart = 0x10, | touchStart = 0x10, | ||||
touchMove = 0x11, | touchMove = 0x11, | ||||
@@ -75,7 +76,11 @@ enum class MessageFromHost | |||||
programEventMessage = 0x03, | programEventMessage = 0x03, | ||||
firmwareUpdatePacket = 0x04, | firmwareUpdatePacket = 0x04, | ||||
configMessage = 0x10 | |||||
configMessage = 0x10, | |||||
factoryReset = 0x11, | |||||
blockReset = 0x12, | |||||
setName = 0x20 | |||||
}; | }; | ||||
@@ -146,6 +151,12 @@ struct VersionNumber | |||||
uint8 length = 0; | uint8 length = 0; | ||||
}; | }; | ||||
struct BlockName | |||||
{ | |||||
uint8 name[33] = {}; | |||||
uint8 length = 0; | |||||
}; | |||||
struct DeviceStatus | struct DeviceStatus | ||||
{ | { | ||||
BlockSerialNumber serialNumber; | BlockSerialNumber serialNumber; | ||||
@@ -166,6 +177,12 @@ struct DeviceVersion | |||||
VersionNumber version; | VersionNumber version; | ||||
}; | }; | ||||
struct DeviceName | |||||
{ | |||||
TopologyIndex index; | |||||
BlockName name; | |||||
}; | |||||
static constexpr uint8 maxBlocksInTopologyPacket = 6; | static constexpr uint8 maxBlocksInTopologyPacket = 6; | ||||
static constexpr uint8 maxConnectionsInTopologyPacket = 24; | static constexpr uint8 maxConnectionsInTopologyPacket = 24; | ||||
@@ -425,6 +442,7 @@ static constexpr const char* ledProgramLittleFootFunctions[] = | |||||
"getNumBlocksInCurrentCluster/i", | "getNumBlocksInCurrentCluster/i", | ||||
"getBlockIdForBlockInCluster/ii", | "getBlockIdForBlockInCluster/ii", | ||||
"isMasterInCurrentCluster/b", | "isMasterInCurrentCluster/b", | ||||
"setClusteringActive/vb", | |||||
"makeARGB/iiiii", | "makeARGB/iiiii", | ||||
"blendARGB/iii", | "blendARGB/iii", | ||||
"fillPixel/viii", | "fillPixel/viii", | ||||
@@ -227,15 +227,21 @@ struct HostPacketBuilder | |||||
//============================================================================== | //============================================================================== | ||||
bool addConfigSetMessage (int32 item, int32 value) | bool addConfigSetMessage (int32 item, int32 value) | ||||
{ | { | ||||
writeMessageType (MessageFromHost::configMessage); | |||||
if (! data.hasCapacity (BitSizes::configSetMessage)) | |||||
return false; | |||||
writeMessageType(MessageFromHost::configMessage); | |||||
ConfigCommand type = ConfigCommands::setConfig; | ConfigCommand type = ConfigCommands::setConfig; | ||||
data << type << IntegerWithBitSize<8> ((uint32) item) << IntegerWithBitSize<32> ((uint32) value); | |||||
data << type << IntegerWithBitSize<8> ((uint32) item) << IntegerWithBitSize<32>((uint32) value); | |||||
return true; | return true; | ||||
} | } | ||||
bool addRequestMessage (int32 item) | bool addRequestMessage (int32 item) | ||||
{ | { | ||||
writeMessageType (MessageFromHost::configMessage); | |||||
if (! data.hasCapacity (BitSizes::configSetMessage)) | |||||
return false; | |||||
writeMessageType(MessageFromHost::configMessage); | |||||
ConfigCommand type = ConfigCommands::requestConfig; | ConfigCommand type = ConfigCommands::requestConfig; | ||||
data << type << IntegerWithBitSize<32> (0) << IntegerWithBitSize<8> ((uint32) item); | data << type << IntegerWithBitSize<32> (0) << IntegerWithBitSize<8> ((uint32) item); | ||||
return true; | return true; | ||||
@@ -243,6 +249,9 @@ struct HostPacketBuilder | |||||
bool addRequestFactorySyncMessage() | bool addRequestFactorySyncMessage() | ||||
{ | { | ||||
if (! data.hasCapacity (MessageType::bits + ConfigCommand::bits)) | |||||
return false; | |||||
writeMessageType (MessageFromHost::configMessage); | writeMessageType (MessageFromHost::configMessage); | ||||
ConfigCommand type = ConfigCommands::requestFactorySync; | ConfigCommand type = ConfigCommands::requestFactorySync; | ||||
data << type; | data << type; | ||||
@@ -251,12 +260,51 @@ struct HostPacketBuilder | |||||
bool addRequestUserSyncMessage() | bool addRequestUserSyncMessage() | ||||
{ | { | ||||
if (! data.hasCapacity (MessageType::bits + ConfigCommand::bits)) | |||||
return false; | |||||
writeMessageType (MessageFromHost::configMessage); | writeMessageType (MessageFromHost::configMessage); | ||||
ConfigCommand type = ConfigCommands::requestUserSync; | ConfigCommand type = ConfigCommands::requestUserSync; | ||||
data << type; | data << type; | ||||
return true; | return true; | ||||
} | } | ||||
//============================================================================== | |||||
bool addFactoryReset() | |||||
{ | |||||
if (! data.hasCapacity (MessageType::bits)) | |||||
return false; | |||||
writeMessageType(MessageFromHost::factoryReset); | |||||
return true; | |||||
} | |||||
bool addBlockReset() | |||||
{ | |||||
if (! data.hasCapacity (MessageType::bits)) | |||||
return false; | |||||
writeMessageType(MessageFromHost::blockReset); | |||||
return true; | |||||
} | |||||
bool addSetBlockName (const juce::String& name) | |||||
{ | |||||
if (name.length() > 32 || ! data.hasCapacity (MessageType::bits + 7 + (7 * name.length()))) | |||||
return false; | |||||
writeMessageType (MessageFromHost::setName); | |||||
data << IntegerWithBitSize<7> ((uint32) name.length()); | |||||
for (auto i = 0; i < name.length(); ++i) | |||||
data << IntegerWithBitSize<7> ((uint32) name.toRawUTF8()[i]); | |||||
data << IntegerWithBitSize<7> (0); | |||||
return true; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
private: | private: | ||||
Packed7BitArrayBuilder<maxPacketBytes> data; | Packed7BitArrayBuilder<maxPacketBytes> data; | ||||
@@ -65,6 +65,7 @@ struct HostPacketDecoder | |||||
case MessageFromDevice::deviceTopologyExtend: return handleTopology (handler, reader, false); | case MessageFromDevice::deviceTopologyExtend: return handleTopology (handler, reader, false); | ||||
case MessageFromDevice::deviceTopologyEnd: return handleTopologyEnd (handler, reader); | case MessageFromDevice::deviceTopologyEnd: return handleTopologyEnd (handler, reader); | ||||
case MessageFromDevice::deviceVersionList: return handleVersion (handler, reader); | case MessageFromDevice::deviceVersionList: return handleVersion (handler, reader); | ||||
case MessageFromDevice::deviceNameList: return handleName (handler, reader); | |||||
case MessageFromDevice::touchStart: return handleTouch (handler, reader, deviceIndex, packetTimestamp, true, false); | case MessageFromDevice::touchStart: return handleTouch (handler, reader, deviceIndex, packetTimestamp, true, false); | ||||
case MessageFromDevice::touchMove: return handleTouch (handler, reader, deviceIndex, packetTimestamp, false, false); | case MessageFromDevice::touchMove: return handleTouch (handler, reader, deviceIndex, packetTimestamp, false, false); | ||||
case MessageFromDevice::touchEnd: return handleTouch (handler, reader, deviceIndex, packetTimestamp, false, true); | case MessageFromDevice::touchEnd: return handleTouch (handler, reader, deviceIndex, packetTimestamp, false, true); | ||||
@@ -184,6 +185,20 @@ struct HostPacketDecoder | |||||
return true; | return true; | ||||
} | } | ||||
static bool handleName (Handler& handler, Packed7BitArrayReader& reader) | |||||
{ | |||||
DeviceName name; | |||||
name.index = (TopologyIndex) reader.readBits (topologyIndexBits); | |||||
name.name.length = (uint8) reader.readBits (7); | |||||
for (uint32 i = 0; i < name.name.length; ++i) | |||||
name.name.name[i] = (uint8) reader.readBits (7); | |||||
handler.handleName (name); | |||||
return true; | |||||
} | |||||
static bool handleTouch (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex, | static bool handleTouch (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex, | ||||
PacketTimestamp packetTimestamp, bool isStart, bool isEnd) | PacketTimestamp packetTimestamp, bool isStart, bool isEnd) | ||||
{ | { | ||||
@@ -310,6 +310,7 @@ struct PhysicalTopologySource::Internal | |||||
BlocksProtocol::TopologyIndex index; | BlocksProtocol::TopologyIndex index; | ||||
BlocksProtocol::BlockSerialNumber serial; | BlocksProtocol::BlockSerialNumber serial; | ||||
BlocksProtocol::VersionNumber version; | BlocksProtocol::VersionNumber version; | ||||
BlocksProtocol::BlockName name; | |||||
bool isMaster; | bool isMaster; | ||||
}; | }; | ||||
@@ -326,11 +327,13 @@ struct PhysicalTopologySource::Internal | |||||
for (auto& device : devices) | for (auto& device : devices) | ||||
{ | { | ||||
BlocksProtocol::VersionNumber version; | BlocksProtocol::VersionNumber version; | ||||
BlocksProtocol::BlockName name; | |||||
result.add ({ getBlockUIDFromSerialNumber (device.serialNumber), | result.add ({ getBlockUIDFromSerialNumber (device.serialNumber), | ||||
device.index, | device.index, | ||||
device.serialNumber, | device.serialNumber, | ||||
version, | version, | ||||
name, | |||||
isFirst }); | isFirst }); | ||||
isFirst = false; | isFirst = false; | ||||
@@ -367,6 +370,15 @@ struct PhysicalTopologySource::Internal | |||||
return false; | return false; | ||||
} | } | ||||
static bool nameAddedToBlock (const juce::Array<DeviceInfo>& devices, Block::UID uid) noexcept | |||||
{ | |||||
for (auto&& d : devices) | |||||
if (d.uid == uid && d.name.length) | |||||
return true; | |||||
return false; | |||||
} | |||||
static void setVersionNumberForBlock (const juce::Array<DeviceInfo>& devices, Block& block) noexcept | static void setVersionNumberForBlock (const juce::Array<DeviceInfo>& devices, Block& block) noexcept | ||||
{ | { | ||||
for (auto&& d : devices) | for (auto&& d : devices) | ||||
@@ -374,6 +386,13 @@ struct PhysicalTopologySource::Internal | |||||
block.versionNumber = juce::String ((const char*) d.version.version, d.version.length); | block.versionNumber = juce::String ((const char*) d.version.version, d.version.length); | ||||
} | } | ||||
static void setNameForBlock (const juce::Array<DeviceInfo>& devices, Block& block) noexcept | |||||
{ | |||||
for (auto&& d : devices) | |||||
if (d.uid == block.uid) | |||||
block.name = juce::String ((const char*) d.name.name, d.name.length); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
struct ConnectedDeviceGroup : private juce::AsyncUpdater, | struct ConnectedDeviceGroup : private juce::AsyncUpdater, | ||||
private juce::Timer | private juce::Timer | ||||
@@ -519,13 +538,30 @@ struct PhysicalTopologySource::Internal | |||||
void handleVersion (BlocksProtocol::DeviceVersion version) | void handleVersion (BlocksProtocol::DeviceVersion version) | ||||
{ | { | ||||
for (auto& d : currentDeviceInfo) | |||||
for (auto i = 0; i < currentDeviceInfo.size(); ++i) | |||||
{ | { | ||||
if (d.index == version.index) | |||||
if (currentDeviceInfo[i].index == version.index && version.version.length > 1) | |||||
{ | { | ||||
d.version = version.version; | |||||
detector.handleTopologyChange(); | |||||
return; | |||||
if (memcmp (currentDeviceInfo.getReference (i).version.version, version.version.version, sizeof (version.version))) | |||||
{ | |||||
currentDeviceInfo.getReference(i).version = version.version; | |||||
detector.handleTopologyChange(); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void handleName (BlocksProtocol::DeviceName name) | |||||
{ | |||||
for (auto i = 0; i < currentDeviceInfo.size(); ++i) | |||||
{ | |||||
if (currentDeviceInfo[i].index == name.index && name.name.length > 1) | |||||
{ | |||||
if (memcmp (currentDeviceInfo.getReference (i).name.name, name.name.name, sizeof (name.name))) | |||||
{ | |||||
currentDeviceInfo.getReference (i).name = name.name; | |||||
detector.handleTopologyChange(); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -886,16 +922,20 @@ struct PhysicalTopologySource::Internal | |||||
currentTopology.blocks.remove (i); | currentTopology.blocks.remove (i); | ||||
} | } | ||||
else if (versionNumberAddedToBlock (newDeviceInfo, block->uid, block->versionNumber)) | |||||
else | |||||
{ | { | ||||
setVersionNumberForBlock (newDeviceInfo, *block); | |||||
if (versionNumberAddedToBlock (newDeviceInfo, block->uid, block->versionNumber)) | |||||
setVersionNumberForBlock (newDeviceInfo, *block); | |||||
if (nameAddedToBlock (newDeviceInfo, block->uid)) | |||||
setNameForBlock (newDeviceInfo, *block); | |||||
} | } | ||||
} | } | ||||
for (auto& info : newDeviceInfo) | for (auto& info : newDeviceInfo) | ||||
if (info.serial.isValid()) | if (info.serial.isValid()) | ||||
if (! containsBlockWithUID (currentTopology.blocks, getBlockUIDFromSerialNumber (info.serial))) | if (! containsBlockWithUID (currentTopology.blocks, getBlockUIDFromSerialNumber (info.serial))) | ||||
currentTopology.blocks.add (new BlockImplementation (info.serial, *this, info.version, info.isMaster)); | |||||
currentTopology.blocks.add (new BlockImplementation (info.serial, *this, info.version, info.name, info.isMaster)); | |||||
currentTopology.connections.swapWith (newDeviceConnections); | currentTopology.connections.swapWith (newDeviceConnections); | ||||
} | } | ||||
@@ -1154,9 +1194,10 @@ struct PhysicalTopologySource::Internal | |||||
private MIDIDeviceConnection::Listener, | private MIDIDeviceConnection::Listener, | ||||
private Timer | private Timer | ||||
{ | { | ||||
BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, Detector& detectorToUse, BlocksProtocol::VersionNumber version, bool master) | |||||
BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, Detector& detectorToUse, BlocksProtocol::VersionNumber version, BlocksProtocol::BlockName name, bool master) | |||||
: Block (juce::String ((const char*) serial.serial, sizeof (serial.serial)), | : Block (juce::String ((const char*) serial.serial, sizeof (serial.serial)), | ||||
juce::String ((const char*) version.version, version.length)), | |||||
juce::String ((const char*) version.version, version.length), | |||||
juce::String ((const char*) name.name, name.length)), | |||||
modelData (serial), | modelData (serial), | ||||
remoteHeap (modelData.programAndHeapSize), | remoteHeap (modelData.programAndHeapSize), | ||||
detector (detectorToUse), | detector (detectorToUse), | ||||
@@ -1615,6 +1656,67 @@ struct PhysicalTopologySource::Internal | |||||
configChangedCallback = configChanged; | configChangedCallback = configChanged; | ||||
} | } | ||||
void factoryReset() override | |||||
{ | |||||
auto index = getDeviceIndex(); | |||||
if (index >= 0) | |||||
{ | |||||
BlocksProtocol::HostPacketBuilder<32> p; | |||||
p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index); | |||||
p.addFactoryReset(); | |||||
p.writePacketSysexFooter(); | |||||
sendMessageToDevice (p); | |||||
} | |||||
else | |||||
{ | |||||
jassertfalse; | |||||
} | |||||
} | |||||
void blockReset() override | |||||
{ | |||||
auto index = getDeviceIndex(); | |||||
if (index >= 0) | |||||
{ | |||||
BlocksProtocol::HostPacketBuilder<32> p; | |||||
p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index); | |||||
p.addBlockReset(); | |||||
p.writePacketSysexFooter(); | |||||
sendMessageToDevice (p); | |||||
} | |||||
else | |||||
{ | |||||
jassertfalse; | |||||
} | |||||
} | |||||
bool setName (const juce::String& name) override | |||||
{ | |||||
auto index = getDeviceIndex(); | |||||
if (index >= 0) | |||||
{ | |||||
BlocksProtocol::HostPacketBuilder<128> p; | |||||
p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index); | |||||
if (p.addSetBlockName (name)) | |||||
{ | |||||
p.writePacketSysexFooter(); | |||||
if (sendMessageToDevice (p)) | |||||
return true; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
jassertfalse; | |||||
} | |||||
return false; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
std::unique_ptr<TouchSurface> touchSurface; | std::unique_ptr<TouchSurface> touchSurface; | ||||
juce::OwnedArray<ControlButton> controlButtons; | juce::OwnedArray<ControlButton> controlButtons; | ||||
@@ -160,6 +160,402 @@ void DrumPadGridProgram::resumeAnimations() | |||||
//============================================================================== | //============================================================================== | ||||
juce::String DrumPadGridProgram::getLittleFootProgram() | juce::String DrumPadGridProgram::getLittleFootProgram() | ||||
{ | { | ||||
if (block.versionNumber.isEmpty() || block.versionNumber.compare ("0.2.5") < 0) | |||||
return getLittleFootProgramPre25(); | |||||
return getLittleFootProgramPost25(); | |||||
} | |||||
juce::String DrumPadGridProgram::getLittleFootProgramPre25() const | |||||
{ | |||||
// Uses its own heatmap, not the one provided in newer firmware | |||||
// Also can't use blocks config, introduced in 2.5. | |||||
return R"littlefoot( | |||||
#heapsize: 1351 | |||||
int dimFactor; | |||||
int dimDelay; | |||||
int slideAnimationProgress; | |||||
int lastVisiblePads; | |||||
int getGridColour (int index, int colourMapOffset) | |||||
{ | |||||
int bit = (2 + colourMapOffset) * 8 + index * 16; | |||||
return makeARGB (255, | |||||
getHeapBits (bit, 5) << 3, | |||||
getHeapBits (bit + 5, 6) << 2, | |||||
getHeapBits (bit + 11, 5) << 3); | |||||
} | |||||
// Returns the current progress and also increments it for next frame | |||||
int getAnimationProgress (int index) | |||||
{ | |||||
// Only 16 animated pads supported | |||||
if (index > 15) | |||||
return 0; | |||||
int offsetBits = 162 * 8 + index * 32; | |||||
int currentProgress = getHeapBits (offsetBits, 16); | |||||
int increment = getHeapBits (offsetBits + 16, 16); | |||||
int nextFrame = currentProgress + increment; | |||||
// Set incremented 16 bit number. | |||||
setHeapByte (162 + index * 4, nextFrame & 0xff); | |||||
setHeapByte (163 + index * 4, nextFrame >> 8); | |||||
return currentProgress; | |||||
} | |||||
void outlineRect (int colour, int x, int y, int w) | |||||
{ | |||||
fillRect (colour, x, y, w, 1); | |||||
fillRect (colour, x, y + w - 1, w, 1); | |||||
fillRect (colour, x, y + 1, 1, w - 1); | |||||
fillRect (colour, x + w - 1, y + 1, 1, w - 1); | |||||
} | |||||
void drawPlus (int colour, int x, int y, int w) | |||||
{ | |||||
fillRect (colour, x, y + (w / 2), w, 1); | |||||
fillRect (colour, x + (w / 2), y, 1, w); | |||||
} | |||||
void fillGradientRect (int colour, int x, int y, int w) | |||||
{ | |||||
if (colour != 0xff000000) | |||||
{ | |||||
int divisor = w + w - 1; | |||||
for (int yy = 0; yy < w; ++yy) | |||||
{ | |||||
for (int xx = yy; xx < w; ++xx) | |||||
{ | |||||
int gradColour = blendARGB (colour, makeARGB (((xx + yy) * 250) / divisor, 0, 0, 0)); | |||||
setLED (x + xx, y + yy, gradColour); | |||||
setLED (x + yy, y + xx, gradColour); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// TODO: Tom M: This is massaged to work with 3x3 pads and for dots to sync | |||||
// with Apple POS loop length. Rework to be more robust & flexible. | |||||
void drawPizzaLED (int colour, int x, int y, int w, int progress) | |||||
{ | |||||
--w; | |||||
x += 1; | |||||
int numToDo = ((8 * progress) / 255) + 1; | |||||
int totalLen = w * 4; | |||||
for (int i = 1; i <= numToDo; ++i) | |||||
{ | |||||
setLED (x, y, colour); | |||||
if (i < w) | |||||
++x; | |||||
else if (i < (w * 2)) | |||||
++y; | |||||
else if (i < (w * 3)) | |||||
--x; | |||||
else if (i < totalLen) | |||||
--y; | |||||
} | |||||
} | |||||
void drawPad (int padX, int padY, int padW, | |||||
int colour, int fill, int animateProgress) | |||||
{ | |||||
animateProgress >>= 8; // 16 bit to 8 bit | |||||
int halfW = padW / 2; | |||||
if (fill == 0) // Gradient fill | |||||
{ | |||||
fillGradientRect (colour, padX, padY, padW); | |||||
} | |||||
else if (fill == 1) // Filled | |||||
{ | |||||
fillRect (colour, padX, padY, padW, padW); | |||||
} | |||||
else if (fill == 2) // Hollow | |||||
{ | |||||
outlineRect (colour, padX, padY, padW); | |||||
} | |||||
else if (fill == 3) // Hollow with plus | |||||
{ | |||||
outlineRect (colour, padX, padY, padW); | |||||
drawPlus (0xffffffff, padX, padY, padW); | |||||
} | |||||
else if (fill == 4) // Pulsing dot | |||||
{ | |||||
int pulseCol = blendARGB (colour, makeARGB (animateProgress, 0, 0, 0)); | |||||
setLED (padX + halfW, padY + halfW, pulseCol); | |||||
} | |||||
else if (fill == 5) // Blinking dot | |||||
{ | |||||
int blinkCol = animateProgress > 64 ? makeARGB (255, 0, 0, 0) : colour; | |||||
setLED (padX + halfW, padY + halfW, blinkCol); | |||||
} | |||||
else if (fill == 6) // Pizza filled | |||||
{ | |||||
outlineRect (blendARGB (colour, makeARGB (220, 0, 0, 0)), padX, padY, padW); // Dim outline | |||||
setLED (padX + halfW, padY + halfW, colour); // Bright centre | |||||
drawPizzaLED (colour, padX, padY, padW, animateProgress); | |||||
} | |||||
else if (fill == 7) // Pizza hollow | |||||
{ | |||||
outlineRect (blendARGB (colour, makeARGB (220, 0, 0, 0)), padX, padY, padW); // Dim outline | |||||
drawPizzaLED (colour, padX, padY, padW, animateProgress); | |||||
return; | |||||
} | |||||
} | |||||
void fadeHeatMap() | |||||
{ | |||||
for (int i = 0; i < 225; ++i) | |||||
{ | |||||
int colourOffset = 226 + i * 4; | |||||
int colour = getHeapInt (colourOffset); | |||||
int alpha = (colour >> 24) & 0xff; | |||||
if (alpha > 0) | |||||
{ | |||||
alpha -= getHeapByte (1126 + i); | |||||
setHeapInt (colourOffset, alpha < 0 ? 0 : ((alpha << 24) | (colour & 0xffffff))); | |||||
} | |||||
} | |||||
} | |||||
void addToHeatMap (int x, int y, int colour) | |||||
{ | |||||
if (x >= 0 && y >= 0 && x < 15 && y < 15) | |||||
{ | |||||
int offset = 226 + 4 * (x + y * 15); | |||||
colour = blendARGB (getHeapInt (offset), colour); | |||||
setHeapInt (offset, colour); | |||||
int decay = ((colour >> 24) & 0xff) / 14; // change divisor to change trail times | |||||
offset = 1126 + (x + y * 15); | |||||
setHeapByte (offset, decay > 0 ? decay : 1); | |||||
} | |||||
} | |||||
int getHeatmapColour (int x, int y) | |||||
{ | |||||
return getHeapInt (226 + 4 * (x + y * 15)); | |||||
} | |||||
int isPadActive (int index) | |||||
{ | |||||
if (getHeapInt (158) == 0) // None active | |||||
return 0; | |||||
++index; | |||||
return index == getHeapByte (158) || | |||||
index == getHeapByte (159) || | |||||
index == getHeapByte (160) || | |||||
index == getHeapByte (161); | |||||
} | |||||
void updateDimFactor() | |||||
{ | |||||
if (getHeapInt (158) == 0) | |||||
{ | |||||
if (--dimDelay <= 0) | |||||
{ | |||||
dimFactor -= 12; | |||||
if (dimFactor < 0) | |||||
dimFactor = 0; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
dimFactor = 180; | |||||
dimDelay = 12; | |||||
} | |||||
} | |||||
void drawPads (int offsetX, int offsetY, int colourMapOffset) | |||||
{ | |||||
int padsPerSide = getHeapByte (0 + colourMapOffset); | |||||
if (padsPerSide < 2) | |||||
return; | |||||
int blockW = 15 / padsPerSide; | |||||
int blockPlusGapW = blockW + (15 - padsPerSide * blockW) / (padsPerSide - 1); | |||||
for (int padY = 0; padY < padsPerSide; ++padY) | |||||
{ | |||||
for (int padX = 0; padX < padsPerSide; ++padX) | |||||
{ | |||||
int ledX = offsetX + padX * blockPlusGapW; | |||||
int ledY = offsetY + padY * blockPlusGapW; | |||||
if (ledX < 15 && | |||||
ledY < 15 && | |||||
(ledX + blockW) >= 0 && | |||||
(ledY + blockW) >= 0) | |||||
{ | |||||
int padIdx = padX + padY * padsPerSide; | |||||
bool padActive = isPadActive (padIdx); | |||||
int blendCol = padActive ? 255 : 0; | |||||
int blendAmt = padActive ? dimFactor >> 1 : dimFactor; | |||||
int colour = blendARGB (getGridColour (padIdx, colourMapOffset), | |||||
makeARGB (blendAmt, blendCol, blendCol, blendCol)); | |||||
int fillType = getHeapByte (colourMapOffset + 52 + padIdx); | |||||
int animate = getAnimationProgress (padIdx); | |||||
drawPad (ledX, ledY, blockW, colour, fillType, animate); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void slideAnimatePads() | |||||
{ | |||||
int nowVisible = getHeapByte (155); | |||||
if (lastVisiblePads != nowVisible) | |||||
{ | |||||
lastVisiblePads = nowVisible; | |||||
if (slideAnimationProgress <= 0) | |||||
slideAnimationProgress = 15; | |||||
} | |||||
// If animation is complete, draw normally. | |||||
if (slideAnimationProgress <= 0) | |||||
{ | |||||
drawPads (0, 0, 78 * nowVisible); | |||||
slideAnimationProgress = 0; | |||||
} | |||||
else | |||||
{ | |||||
int direction = getHeapByte (156); | |||||
slideAnimationProgress -= 1; | |||||
int inPos = nowVisible == 0 ? 0 : 78; | |||||
int outPos = nowVisible == 0 ? 78 : 0; | |||||
if (direction == 0) // Up | |||||
{ | |||||
drawPads (0, slideAnimationProgress - 16, outPos); | |||||
drawPads (0, slideAnimationProgress, inPos); | |||||
} | |||||
else if (direction == 1) // Down | |||||
{ | |||||
drawPads (0, 16 - slideAnimationProgress, outPos); | |||||
drawPads (0, 0 - slideAnimationProgress, inPos); | |||||
} | |||||
else if (direction == 2) // Left | |||||
{ | |||||
drawPads (16 - slideAnimationProgress, 0, outPos); | |||||
drawPads (slideAnimationProgress, 0, inPos); | |||||
} | |||||
else if (direction == 3) // Right | |||||
{ | |||||
drawPads (16 - slideAnimationProgress, 0, outPos); | |||||
drawPads (0 - slideAnimationProgress, 0, inPos); | |||||
} | |||||
else // None | |||||
{ | |||||
drawPads (0, 0, 78 * nowVisible); | |||||
slideAnimationProgress = 0; | |||||
} | |||||
} | |||||
} | |||||
void repaint() | |||||
{ | |||||
// showErrorOnFail, showRepaintTime, showMovingDot | |||||
//enableDebug (true, true, false); | |||||
// Clear LEDs to black, update dim animation | |||||
fillRect (0xff000000, 0, 0, 15, 15); | |||||
updateDimFactor(); | |||||
// Does the main painting of pads | |||||
slideAnimatePads(); | |||||
// Overlay heatmap | |||||
for (int y = 0; y < 15; ++y) | |||||
for (int x = 0; x < 15; ++x) | |||||
blendLED (x, y, getHeatmapColour (x, y)); | |||||
fadeHeatMap(); | |||||
} | |||||
// DrumPadGridProgram::sendTouch results in this callback, giving | |||||
// us more touch updates per frame and therefore smoother trails. | |||||
void handleMessage (int pos, int colour, int xx) | |||||
{ | |||||
handleMessage (pos, colour); | |||||
} | |||||
void handleMessage (int pos, int colour) | |||||
{ | |||||
if ((pos >> 24) != 0x20) | |||||
return; | |||||
int tx = ((pos >> 16) & 0xff) - 13; | |||||
int ty = ((pos >> 8) & 0xff) - 13; | |||||
int tz = pos & 0xff; | |||||
tz = tz > 30 ? tz : 30; | |||||
int ledCentreX = tx >> 4; | |||||
int ledCentreY = ty >> 4; | |||||
int adjustX = (tx - (ledCentreX << 4)) >> 2; | |||||
int adjustY = (ty - (ledCentreY << 4)) >> 2; | |||||
for (int dy = -2; dy <= 2; ++dy) | |||||
{ | |||||
for (int dx = -2; dx <= 2; ++dx) | |||||
{ | |||||
int distance = dx * dx + dy * dy; | |||||
int level = distance == 0 ? 255 : (distance == 1 ? 132 : (distance < 5 ? 9 : (distance == 5 ? 2 : 0))); | |||||
level += (dx * adjustX); | |||||
level += (dy * adjustY); | |||||
level = (tz * level) >> 8; | |||||
if (level > 0) | |||||
addToHeatMap (ledCentreX + dx, ledCentreY + dy, | |||||
makeARGB (level, colour >> 16, colour >> 8, colour)); | |||||
} | |||||
} | |||||
} | |||||
)littlefoot"; | |||||
} | |||||
juce::String DrumPadGridProgram::getLittleFootProgramPost25() const | |||||
{ | |||||
// Uses heatmap provided in firmware (so the program's smaller) | |||||
// Initialises config items introduced in firmware 2.5 | |||||
return R"littlefoot( | return R"littlefoot( | ||||
#heapsize: 256 | #heapsize: 256 | ||||
@@ -169,6 +565,12 @@ juce::String DrumPadGridProgram::getLittleFootProgram() | |||||
int slideAnimationProgress; | int slideAnimationProgress; | ||||
int lastVisiblePads; | int lastVisiblePads; | ||||
void initialise() | |||||
{ | |||||
for (int i = 0; i < 32; ++i) | |||||
setLocalConfigActiveState (i, true, true); | |||||
} | |||||
int getGridColour (int index, int colourMapOffset) | int getGridColour (int index, int colourMapOffset) | ||||
{ | { | ||||
int bit = (2 + colourMapOffset) * 8 + index * 16; | int bit = (2 + colourMapOffset) * 8 + index * 16; | ||||
@@ -115,4 +115,6 @@ private: | |||||
void setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills, uint32 byteOffset); | void setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills, uint32 byteOffset); | ||||
juce::String getLittleFootProgram() override; | juce::String getLittleFootProgram() override; | ||||
juce::String getLittleFootProgramPre25() const; | |||||
juce::String getLittleFootProgramPost25() const; | |||||
}; | }; |