mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-28 02:30:05 +00:00
BLOCKS API: Added support for custom block names
This commit is contained in:
parent
21f14c478b
commit
13b20de150
10 changed files with 629 additions and 28 deletions
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue