| @@ -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 */ | |||
| 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<void(const String&)> 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<DataInputPortListener> dataInputPortListeners; | |||
| juce::ListenerList<ProgramEventListener> programEventListeners; | |||
| @@ -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, {}, {} }, | |||
| @@ -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; | |||
| @@ -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", | |||
| @@ -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<maxPacketBytes> data; | |||
| @@ -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) | |||
| { | |||
| @@ -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<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 | |||
| { | |||
| 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<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, | |||
| 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> touchSurface; | |||
| juce::OwnedArray<ControlButton> controlButtons; | |||
| @@ -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; | |||
| @@ -115,4 +115,6 @@ private: | |||
| void setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills, uint32 byteOffset); | |||
| juce::String getLittleFootProgram() override; | |||
| juce::String getLittleFootProgramPre25() const; | |||
| juce::String getLittleFootProgramPost25() const; | |||
| }; | |||