From 13b20de15073a69e414a0177977dbc825bb99abd Mon Sep 17 00:00:00 2001 From: jules Date: Tue, 20 Jun 2017 12:12:49 +0100 Subject: [PATCH] BLOCKS API: Added support for custom block names --- .../juce_blocks_basics/blocks/juce_Block.cpp | 4 +- .../juce_blocks_basics/blocks/juce_Block.h | 14 +- .../blocks/juce_BlockConfigManager.h | 18 +- .../protocol/juce_BlockModels.h | 6 +- .../protocol/juce_BlocksProtocolDefinitions.h | 20 +- .../protocol/juce_HostPacketBuilder.h | 54 ++- .../protocol/juce_HostPacketDecoder.h | 15 + .../topology/juce_PhysicalTopologySource.cpp | 122 +++++- .../visualisers/juce_DrumPadLEDProgram.cpp | 402 ++++++++++++++++++ .../visualisers/juce_DrumPadLEDProgram.h | 2 + 10 files changed, 629 insertions(+), 28 deletions(-) diff --git a/modules/juce_blocks_basics/blocks/juce_Block.cpp b/modules/juce_blocks_basics/blocks/juce_Block.cpp index 1fefb13946..6b4093899b 100644 --- a/modules/juce_blocks_basics/blocks/juce_Block.cpp +++ b/modules/juce_blocks_basics/blocks/juce_Block.cpp @@ -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)) { } diff --git a/modules/juce_blocks_basics/blocks/juce_Block.h b/modules/juce_blocks_basics/blocks/juce_Block.h index ef05c21080..5686f59bba 100644 --- a/modules/juce_blocks_basics/blocks/juce_Block.h +++ b/modules/juce_blocks_basics/blocks/juce_Block.h @@ -62,6 +62,9 @@ public: /** The Block's version number */ juce::String versionNumber; + /** The Block's name */ + juce::String name; + using UID = uint64; /** This Block's UID. @@ -371,6 +374,15 @@ public: /** Reset all items active status */ 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. */ virtual void setLogger (std::function loggingCallback) = 0; @@ -410,7 +422,7 @@ public: protected: //============================================================================== 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 dataInputPortListeners; juce::ListenerList programEventListeners; diff --git a/modules/juce_blocks_basics/blocks/juce_BlockConfigManager.h b/modules/juce_blocks_basics/blocks/juce_BlockConfigManager.h index c47464f4c6..ad1d70b585 100644 --- a/modules/juce_blocks_basics/blocks/juce_BlockConfigManager.h +++ b/modules/juce_blocks_basics/blocks/juce_BlockConfigManager.h @@ -73,16 +73,16 @@ struct BlockConfigManager 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" }, { pitchBendRange, 48, 1, 96, false, "Pitch Bend Range", ConfigType::integer, {}, "MIDI Settings" }, { octave, 0, -4, 6, false, "Octave", 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", "Relative Unipolar", - "Relative Bipolar" }, "5D Touch" }, + "Relative Bipolar" }, "Play mode" }, { velocitySensitivity, 100, 0, 127, false, "Strike 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" }, @@ -90,8 +90,8 @@ struct BlockConfigManager { liftSensitivity, 100, 0, 127, false, "Lift Sensitivity", ConfigType::integer, {}, "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" }, - { 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" }, { volume, 100, 0, 127, false, "Volume", ConfigType::integer, {}, "Play mode" }, { scale, 0, 0, 18, false, "Scale", ConfigType::integer, {}, "Play mode" }, // NOTE: Should be options @@ -103,18 +103,18 @@ struct BlockConfigManager "Last Played", "Highest", "Lowest", - "Disabled" }, "5D Touch" }, + "Disabled" }, "Play mode" }, { yTrackingMode, 1, 0, 4, false, "Slide Tracking Mode", ConfigType::options, { "Multi-Channel", "Last Played", "Highest", "Lowest", - "Disabled" }, "5D Touch" }, + "Disabled" }, "Play mode" }, { zTrackingMode, 1, 0, 4, false, "Pressure Tracking Mode", ConfigType::options, { "Multi-Channel", "Last Played", "Highest", "Lowest", "Disabled", - "Hardest" }, "5D Touch" }, + "Hardest" }, "Play mode" }, // These can be defined for unique usage for a given Littlefoot script { user0, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, { user1, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, diff --git a/modules/juce_blocks_basics/protocol/juce_BlockModels.h b/modules/juce_blocks_basics/protocol/juce_BlockModels.h index 4eb46236cf..c45f82c313 100644 --- a/modules/juce_blocks_basics/protocol/juce_BlockModels.h +++ b/modules/juce_blocks_basics/protocol/juce_BlockModels.h @@ -208,8 +208,10 @@ private: lightGridWidth = 0; lightGridHeight = 0; 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; programAndHeapSize = BlocksProtocol::padBlockProgramAndHeapSize; diff --git a/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h b/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h index ac291216b7..3cd54704d4 100644 --- a/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h +++ b/modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h @@ -48,6 +48,7 @@ enum class MessageFromDevice deviceTopologyExtend = 0x04, deviceTopologyEnd = 0x05, deviceVersionList = 0x06, + deviceNameList = 0x07, touchStart = 0x10, touchMove = 0x11, @@ -75,7 +76,11 @@ enum class MessageFromHost programEventMessage = 0x03, firmwareUpdatePacket = 0x04, - configMessage = 0x10 + configMessage = 0x10, + factoryReset = 0x11, + blockReset = 0x12, + + setName = 0x20 }; @@ -146,6 +151,12 @@ struct VersionNumber uint8 length = 0; }; +struct BlockName +{ + uint8 name[33] = {}; + uint8 length = 0; +}; + struct DeviceStatus { BlockSerialNumber serialNumber; @@ -166,6 +177,12 @@ struct DeviceVersion VersionNumber version; }; +struct DeviceName +{ + TopologyIndex index; + BlockName name; +}; + static constexpr uint8 maxBlocksInTopologyPacket = 6; static constexpr uint8 maxConnectionsInTopologyPacket = 24; @@ -425,6 +442,7 @@ static constexpr const char* ledProgramLittleFootFunctions[] = "getNumBlocksInCurrentCluster/i", "getBlockIdForBlockInCluster/ii", "isMasterInCurrentCluster/b", + "setClusteringActive/vb", "makeARGB/iiiii", "blendARGB/iii", "fillPixel/viii", diff --git a/modules/juce_blocks_basics/protocol/juce_HostPacketBuilder.h b/modules/juce_blocks_basics/protocol/juce_HostPacketBuilder.h index f537aaac7d..84f9bf4963 100644 --- a/modules/juce_blocks_basics/protocol/juce_HostPacketBuilder.h +++ b/modules/juce_blocks_basics/protocol/juce_HostPacketBuilder.h @@ -227,15 +227,21 @@ struct HostPacketBuilder //============================================================================== bool addConfigSetMessage (int32 item, int32 value) { - writeMessageType (MessageFromHost::configMessage); + if (! data.hasCapacity (BitSizes::configSetMessage)) + return false; + + writeMessageType(MessageFromHost::configMessage); 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; } bool addRequestMessage (int32 item) { - writeMessageType (MessageFromHost::configMessage); + if (! data.hasCapacity (BitSizes::configSetMessage)) + return false; + + writeMessageType(MessageFromHost::configMessage); ConfigCommand type = ConfigCommands::requestConfig; data << type << IntegerWithBitSize<32> (0) << IntegerWithBitSize<8> ((uint32) item); return true; @@ -243,6 +249,9 @@ struct HostPacketBuilder bool addRequestFactorySyncMessage() { + if (! data.hasCapacity (MessageType::bits + ConfigCommand::bits)) + return false; + writeMessageType (MessageFromHost::configMessage); ConfigCommand type = ConfigCommands::requestFactorySync; data << type; @@ -251,12 +260,51 @@ struct HostPacketBuilder bool addRequestUserSyncMessage() { + if (! data.hasCapacity (MessageType::bits + ConfigCommand::bits)) + return false; + writeMessageType (MessageFromHost::configMessage); ConfigCommand type = ConfigCommands::requestUserSync; data << type; 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: Packed7BitArrayBuilder data; diff --git a/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h b/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h index b26e03534f..7689b260f5 100644 --- a/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h +++ b/modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h @@ -65,6 +65,7 @@ struct HostPacketDecoder case MessageFromDevice::deviceTopologyExtend: return handleTopology (handler, reader, false); case MessageFromDevice::deviceTopologyEnd: return handleTopologyEnd (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::touchMove: return handleTouch (handler, reader, deviceIndex, packetTimestamp, false, false); case MessageFromDevice::touchEnd: return handleTouch (handler, reader, deviceIndex, packetTimestamp, false, true); @@ -184,6 +185,20 @@ struct HostPacketDecoder 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, PacketTimestamp packetTimestamp, bool isStart, bool isEnd) { diff --git a/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp b/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp index f604823e93..b1ad75a5b8 100644 --- a/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp +++ b/modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp @@ -310,6 +310,7 @@ struct PhysicalTopologySource::Internal BlocksProtocol::TopologyIndex index; BlocksProtocol::BlockSerialNumber serial; BlocksProtocol::VersionNumber version; + BlocksProtocol::BlockName name; bool isMaster; }; @@ -326,11 +327,13 @@ struct PhysicalTopologySource::Internal for (auto& device : devices) { BlocksProtocol::VersionNumber version; + BlocksProtocol::BlockName name; result.add ({ getBlockUIDFromSerialNumber (device.serialNumber), device.index, device.serialNumber, version, + name, isFirst }); isFirst = false; @@ -367,6 +370,15 @@ struct PhysicalTopologySource::Internal return false; } + static bool nameAddedToBlock (const juce::Array& 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& devices, Block& block) noexcept { for (auto&& d : devices) @@ -374,6 +386,13 @@ struct PhysicalTopologySource::Internal block.versionNumber = juce::String ((const char*) d.version.version, d.version.length); } + static void setNameForBlock (const juce::Array& 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, private juce::Timer @@ -519,13 +538,30 @@ struct PhysicalTopologySource::Internal 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); } - 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) if (info.serial.isValid()) 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); } @@ -1154,9 +1194,10 @@ struct PhysicalTopologySource::Internal private MIDIDeviceConnection::Listener, 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)), - 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), remoteHeap (modelData.programAndHeapSize), detector (detectorToUse), @@ -1615,6 +1656,67 @@ struct PhysicalTopologySource::Internal 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; juce::OwnedArray controlButtons; diff --git a/modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.cpp b/modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.cpp index 33fa367f71..263561a066 100644 --- a/modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.cpp +++ b/modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.cpp @@ -160,6 +160,402 @@ void DrumPadGridProgram::resumeAnimations() //============================================================================== 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( #heapsize: 256 @@ -169,6 +565,12 @@ juce::String DrumPadGridProgram::getLittleFootProgram() int slideAnimationProgress; int lastVisiblePads; + void initialise() + { + for (int i = 0; i < 32; ++i) + setLocalConfigActiveState (i, true, true); + } + int getGridColour (int index, int colourMapOffset) { int bit = (2 + colourMapOffset) * 8 + index * 16; diff --git a/modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.h b/modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.h index a182bf8e3a..a92e82e635 100644 --- a/modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.h +++ b/modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.h @@ -115,4 +115,6 @@ private: void setGridFills (int numColumns, int numRows, const juce::Array& fills, uint32 byteOffset); juce::String getLittleFootProgram() override; + juce::String getLittleFootProgramPre25() const; + juce::String getLittleFootProgramPost25() const; };