@@ -52,6 +52,11 @@ 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() {} | Block::~Block() {} | ||||
void Block::addDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.add (listener); } | void Block::addDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.add (listener); } | ||||
@@ -42,6 +42,7 @@ public: | |||||
liveBlock, | liveBlock, | ||||
loopBlock, | loopBlock, | ||||
developerControlBlock, | developerControlBlock, | ||||
touchBlock, | |||||
seaboardBlock // on-screen seaboard view | seaboardBlock // on-screen seaboard view | ||||
}; | }; | ||||
@@ -58,6 +59,9 @@ public: | |||||
/** The Block's serial number. */ | /** The Block's serial number. */ | ||||
const juce::String serialNumber; | const juce::String serialNumber; | ||||
/** The Block's version number */ | |||||
juce::String versionNumber; | |||||
using UID = uint64; | using UID = uint64; | ||||
/** This Block's UID. | /** This Block's UID. | ||||
@@ -256,6 +260,117 @@ public: | |||||
/** Sets the current program as the block's default state. */ | /** Sets the current program as the block's default state. */ | ||||
virtual void saveProgramAsDefault() = 0; | virtual void saveProgramAsDefault() = 0; | ||||
//============================================================================== | |||||
/** Metadata for a given config item */ | |||||
struct ConfigMetaData | |||||
{ | |||||
static constexpr int32 numOptionNames = 8; | |||||
ConfigMetaData() {} | |||||
// Constructor to work around VS2015 bugs... | |||||
ConfigMetaData (uint32 itemIndex, | |||||
int32 itemValue, | |||||
juce::Range<int32> rangeToUse, | |||||
bool active, | |||||
const char* itemName, | |||||
uint32 itemType, | |||||
const char* options[ConfigMetaData::numOptionNames], | |||||
const char* groupName) | |||||
: item (itemIndex), | |||||
value (itemValue), | |||||
range (rangeToUse), | |||||
isActive (active), | |||||
name (itemName), | |||||
type (itemType), | |||||
group (groupName) | |||||
{ | |||||
for (int i = 0; i < numOptionNames; ++i) | |||||
optionNames[i] = options[i]; | |||||
} | |||||
ConfigMetaData (const ConfigMetaData& other) | |||||
{ | |||||
*this = other; | |||||
} | |||||
const ConfigMetaData& operator= (const ConfigMetaData& other) | |||||
{ | |||||
if (this != &other) | |||||
{ | |||||
item = other.item; | |||||
value = other.value; | |||||
range = other.range; | |||||
isActive = other.isActive; | |||||
name = other.name; | |||||
type = other.type; | |||||
group = other.group; | |||||
for (int i = 0; i < numOptionNames; ++i) | |||||
optionNames[i] = other.optionNames[i]; | |||||
} | |||||
return *this; | |||||
} | |||||
bool operator== (const ConfigMetaData& other) const | |||||
{ | |||||
for (int32 optionIndex = 0; optionIndex < numOptionNames; ++optionIndex) | |||||
if (optionNames[optionIndex] != other.optionNames[optionIndex]) | |||||
return false; | |||||
return item == other.item | |||||
&& value == other.value | |||||
&& range == other.range | |||||
&& isActive == other.isActive | |||||
&& name == other.name | |||||
&& group == other.group; | |||||
} | |||||
bool operator != (const ConfigMetaData& other) const | |||||
{ | |||||
return ! (*this == other); | |||||
} | |||||
uint32 item = 0; | |||||
int32 value = 0; | |||||
juce::Range<int32> range; | |||||
bool isActive = false; | |||||
juce::String name; | |||||
uint32 type = 0; | |||||
juce::String optionNames[numOptionNames] = {}; | |||||
juce::String group; | |||||
}; | |||||
/** Returns the maximum number of config items available */ | |||||
virtual uint32 getMaxConfigIndex() = 0; | |||||
/** Determine if this is a valid config item index */ | |||||
virtual bool isValidUserConfigIndex (uint32 item) = 0; | |||||
/** Get local config item value */ | |||||
virtual int32 getLocalConfigValue (uint32 item) = 0; | |||||
/** Set local config item value */ | |||||
virtual void setLocalConfigValue (uint32 item, int32 value) = 0; | |||||
/** Set local config item range */ | |||||
virtual void setLocalConfigRange (uint32 item, int32 min, int32 max) = 0; | |||||
/** Set if config item is active or not */ | |||||
virtual void setLocalConfigItemActive (uint32 item, bool isActive) = 0; | |||||
/** Determine if config item is active or not */ | |||||
virtual bool isLocalConfigItemActive (uint32 item) = 0; | |||||
/** Get config item metadata */ | |||||
virtual ConfigMetaData getLocalConfigMetaData (uint32 item) = 0; | |||||
/** Request sync of factory config with block */ | |||||
virtual void requestFactoryConfigSync() = 0; | |||||
/** Reset all items active status */ | |||||
virtual void resetConfigListActiveStatus() = 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; | ||||
@@ -264,6 +379,9 @@ public: | |||||
virtual bool sendFirmwareUpdatePacket (const uint8* data, uint8 size, | virtual bool sendFirmwareUpdatePacket (const uint8* data, uint8 size, | ||||
std::function<void (uint8)> packetAckCallback) = 0; | std::function<void (uint8)> packetAckCallback) = 0; | ||||
/** Provides a callback that will be called when a config changes. */ | |||||
virtual void setConfigChangedCallback (std::function<void(Block&, const ConfigMetaData&, uint32)>) = 0; | |||||
//============================================================================== | //============================================================================== | ||||
/** Interface for objects listening to input data port. */ | /** Interface for objects listening to input data port. */ | ||||
struct DataInputPortListener | struct DataInputPortListener | ||||
@@ -292,6 +410,7 @@ public: | |||||
protected: | protected: | ||||
//============================================================================== | //============================================================================== | ||||
Block (const juce::String& serialNumberToUse); | Block (const juce::String& serialNumberToUse); | ||||
Block (const juce::String& serial, const juce::String& version); | |||||
juce::ListenerList<DataInputPortListener> dataInputPortListeners; | juce::ListenerList<DataInputPortListener> dataInputPortListeners; | ||||
juce::ListenerList<ProgramEventListener> programEventListeners; | juce::ListenerList<ProgramEventListener> programEventListeners; | ||||
@@ -0,0 +1,346 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license/ | |||||
Permission to use, copy, modify, and/or distribute this software for any | |||||
purpose with or without fee is hereby granted, provided that the above | |||||
copyright notice and this permission notice appear in all copies. | |||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD | |||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||||
FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, | |||||
OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF | |||||
USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |||||
OF THIS SOFTWARE. | |||||
----------------------------------------------------------------------------- | |||||
To release a closed-source product which uses other parts of JUCE not | |||||
licensed under the ISC terms, commercial licenses are available: visit | |||||
www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#pragma once | |||||
// This file provides interfaces for managing the internal configuration of Blocks | |||||
// and synchronises with the connected Block | |||||
using namespace BlocksProtocol; | |||||
struct BlockConfigManager | |||||
{ | |||||
void setDeviceIndex (TopologyIndex newDeviceIndex) { deviceIndex = newDeviceIndex; } | |||||
void setDeviceComms (PhysicalTopologySource::DeviceConnection* newConn) { deviceConnection = newConn; } | |||||
enum ConfigType | |||||
{ | |||||
integer, | |||||
floating, | |||||
boolean, | |||||
colour, | |||||
options | |||||
}; | |||||
static constexpr uint32 numConfigItems = 59; | |||||
struct ConfigDescription | |||||
{ | |||||
ConfigItemId item; | |||||
int32 value; | |||||
int32 min; | |||||
int32 max; | |||||
bool isActive; | |||||
const char* name; | |||||
ConfigType type; | |||||
const char* optionNames[configMaxOptions]; | |||||
const char* group; | |||||
static_assert (configMaxOptions == Block::ConfigMetaData::numOptionNames, "Config options size and config metadata size should be the same"); | |||||
Block::ConfigMetaData toConfigMetaData() const | |||||
{ | |||||
return Block::ConfigMetaData ((uint32) item, value, { min, max }, isActive, name, (uint32) type, (const char**) optionNames, group); | |||||
} | |||||
}; | |||||
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" }, | |||||
{ 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" }, | |||||
{ slideMode, 0, 0, 2, false, "Slide Mode", ConfigType::options, { "Absolute", | |||||
"Relative Unipolar", | |||||
"Relative Bipolar" }, "5D Touch" }, | |||||
{ 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" }, | |||||
{ pressureSensitivity, 100, 0, 127, false, "Pressure 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" }, | |||||
{ 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" }, | |||||
{ 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 | |||||
{ hideMode, 0, 0, 1, false, "Hide Mode", ConfigType::boolean, {}, "Play mode" }, | |||||
{ chord, 0, 0, 127, false, "Chord", ConfigType::integer, {}, "Play mode" }, // NOTE: Should be options | |||||
{ arpPattern, 0, 0, 127, false, "Arp Pattern", ConfigType::integer, {}, "Play mode" }, | |||||
{ tempo, 120, 1, 300, false, "Tempo", ConfigType::integer, {}, "Rhythm" }, | |||||
{ xTrackingMode, 1, 0, 4, false, "Glide Tracking Mode", ConfigType::options, { "Multi-Channel", | |||||
"Last Played", | |||||
"Highest", | |||||
"Lowest", | |||||
"Disabled" }, "5D Touch" }, | |||||
{ yTrackingMode, 1, 0, 4, false, "Slide Tracking Mode", ConfigType::options, { "Multi-Channel", | |||||
"Last Played", | |||||
"Highest", | |||||
"Lowest", | |||||
"Disabled" }, "5D Touch" }, | |||||
{ zTrackingMode, 1, 0, 4, false, "Pressure Tracking Mode", ConfigType::options, { "Multi-Channel", | |||||
"Last Played", | |||||
"Highest", | |||||
"Lowest", | |||||
"Disabled", | |||||
"Hardest" }, "5D Touch" }, | |||||
// 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, {}, {} }, | |||||
{ user2, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user3, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user4, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user5, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user6, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user7, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user8, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user9, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user10, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user11, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user12, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user13, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user14, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user15, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user16, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user17, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user18, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user19, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user20, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user21, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user22, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user23, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user24, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user25, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user26, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user27, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user28, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user29, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user30, 0, 0, 127, false, {}, ConfigType::integer, {}, {} }, | |||||
{ user31, 0, 0, 127, false, {}, ConfigType::integer, {}, {} } | |||||
}; | |||||
//============================================================================== | |||||
int32 getItemValue (ConfigItemId item) | |||||
{ | |||||
uint32 itemIndex; | |||||
if (getIndexForItem (item, itemIndex)) | |||||
return configList[itemIndex].value; | |||||
return 0; | |||||
} | |||||
void setItemValue (ConfigItemId item, int32 value) | |||||
{ | |||||
uint32 itemIndex; | |||||
if (getIndexForItem (item, itemIndex)) | |||||
configList[itemIndex].value = value; | |||||
setBlockConfig (item, value); | |||||
} | |||||
int32 getItemMin (ConfigItemId item) | |||||
{ | |||||
uint32 itemIndex; | |||||
if (getIndexForItem (item, itemIndex)) | |||||
return configList[itemIndex].min; | |||||
return 0; | |||||
} | |||||
void setItemMin (ConfigItemId item, int32 min) | |||||
{ | |||||
uint32 itemIndex; | |||||
if (getIndexForItem (item, itemIndex)) | |||||
configList[itemIndex].min = min; | |||||
} | |||||
int32 getItemMax (ConfigItemId item) | |||||
{ | |||||
uint32 itemIndex; | |||||
if (getIndexForItem (item, itemIndex)) | |||||
return configList[itemIndex].max; | |||||
return 0; | |||||
} | |||||
void setItemMax (ConfigItemId item, int32 max) | |||||
{ | |||||
uint32 itemIndex; | |||||
if (getIndexForItem (item, itemIndex)) | |||||
configList[itemIndex].max = max; | |||||
// Send updateConfig message to Block | |||||
} | |||||
bool getItemActive (ConfigItemId item) | |||||
{ | |||||
uint32 itemIndex; | |||||
if (getIndexForItem (item, itemIndex)) | |||||
return configList[itemIndex].isActive; | |||||
return false; | |||||
} | |||||
void setItemActive (ConfigItemId item, bool isActive) | |||||
{ | |||||
uint32 itemIndex; | |||||
if (getIndexForItem (item, itemIndex)) | |||||
configList[itemIndex].isActive = isActive; | |||||
// Send setConfigState message to Block | |||||
} | |||||
juce::String getOptionName (ConfigItemId item, uint8 optionIndex) | |||||
{ | |||||
uint32 itemIndex; | |||||
if (getIndexForItem (item, itemIndex) && optionIndex < configMaxOptions) | |||||
return configList[itemIndex].optionNames[optionIndex]; | |||||
return {}; | |||||
} | |||||
Block::ConfigMetaData getMetaData (ConfigItemId item) | |||||
{ | |||||
uint32 itemIndex; | |||||
if (getIndexForItem (item, itemIndex)) | |||||
return configList[itemIndex].toConfigMetaData(); | |||||
return {}; | |||||
} | |||||
void resetConfigListActiveStatus() | |||||
{ | |||||
for (uint32 i = 0; i < numConfigItems; ++i) | |||||
configList[i].isActive = false; | |||||
} | |||||
//============================================================================== | |||||
// Set Block Configuration | |||||
void setBlockConfig (ConfigItemId item, int32 value) | |||||
{ | |||||
HostPacketBuilder<32> packet; | |||||
packet.writePacketSysexHeaderBytes (deviceIndex); | |||||
packet.addConfigSetMessage (item, value); | |||||
packet.writePacketSysexFooter(); | |||||
if (deviceConnection != nullptr) | |||||
deviceConnection->sendMessageToDevice (packet.getData(), (size_t) packet.size()); | |||||
} | |||||
void requestBlockConfig (ConfigItemId item) | |||||
{ | |||||
HostPacketBuilder<32> packet; | |||||
packet.writePacketSysexHeaderBytes (deviceIndex); | |||||
packet.addRequestMessage (item); | |||||
packet.writePacketSysexFooter(); | |||||
if (deviceConnection != nullptr) | |||||
deviceConnection->sendMessageToDevice(packet.getData(), (size_t) packet.size()); | |||||
} | |||||
void requestFactoryConfigSync() | |||||
{ | |||||
HostPacketBuilder<32> packet; | |||||
packet.writePacketSysexHeaderBytes(deviceIndex); | |||||
packet.addRequestFactorySyncMessage(); | |||||
packet.writePacketSysexFooter(); | |||||
if (deviceConnection != nullptr) | |||||
deviceConnection->sendMessageToDevice(packet.getData(), (size_t) packet.size()); | |||||
} | |||||
void requestUserConfigSync() | |||||
{ | |||||
HostPacketBuilder<32> packet; | |||||
packet.writePacketSysexHeaderBytes(deviceIndex); | |||||
packet.addRequestUserSyncMessage(); | |||||
packet.writePacketSysexFooter(); | |||||
if (deviceConnection != nullptr) | |||||
deviceConnection->sendMessageToDevice(packet.getData(), (size_t) packet.size()); | |||||
} | |||||
void handleConfigUpdateMessage (int32 item, int32 value, int32 min, int32 max) | |||||
{ | |||||
uint32 index; | |||||
if (getIndexForItem ((ConfigItemId) item, index)) | |||||
{ | |||||
configList[index].value = value; | |||||
configList[index].min = min; | |||||
configList[index].max = max; | |||||
configList[index].isActive = true; | |||||
} | |||||
} | |||||
void handleConfigSetMessage(int32 item, int32 value) | |||||
{ | |||||
uint32 index; | |||||
if (getIndexForItem ((ConfigItemId) item, index)) | |||||
configList[index].value = value; | |||||
} | |||||
private: | |||||
bool getIndexForItem (ConfigItemId item, uint32& index) | |||||
{ | |||||
for (uint32 i = 0; i < numConfigItems; ++i) | |||||
{ | |||||
if (configList[i].item == item) | |||||
{ | |||||
index = i; | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
TopologyIndex deviceIndex; | |||||
PhysicalTopologySource::DeviceConnection* deviceConnection; | |||||
}; |
@@ -70,7 +70,17 @@ public: | |||||
button4, | button4, | ||||
button5, | button5, | ||||
button6, | button6, | ||||
button7 | |||||
button7, | |||||
// touch block buttons | |||||
velocitySensitivity, | |||||
glideSensitivity, | |||||
slideSensitivity, | |||||
pressSensitivity, | |||||
liftSensitivity, | |||||
fixedVelocity, | |||||
glideLock, | |||||
pianoMode | |||||
}; | }; | ||||
/** Returns the button's type. */ | /** Returns the button's type. */ | ||||
@@ -34,6 +34,7 @@ namespace juce | |||||
#include "protocol/juce_BlockModels.h" | #include "protocol/juce_BlockModels.h" | ||||
} | } | ||||
#include "blocks/juce_BlockConfigManager.h" | |||||
#include "blocks/juce_Block.cpp" | #include "blocks/juce_Block.cpp" | ||||
#include "topology/juce_PhysicalTopologySource.cpp" | #include "topology/juce_PhysicalTopologySource.cpp" | ||||
#include "topology/juce_RuleBasedTopologySource.cpp" | #include "topology/juce_RuleBasedTopologySource.cpp" | ||||
@@ -122,12 +122,24 @@ struct LittleFootRemoteHeap | |||||
return ! needsSyncing; | return ! needsSyncing; | ||||
} | } | ||||
static bool isAllZero (const uint8* data, size_t size) noexcept | |||||
{ | |||||
for (size_t i = 0; i < size; ++i) | |||||
if (data[i] != 0) | |||||
return false; | |||||
return true; | |||||
} | |||||
void sendChanges (ImplementationClass& bi, bool forceSend) | void sendChanges (ImplementationClass& bi, bool forceSend) | ||||
{ | { | ||||
if ((needsSyncing && messagesSent.isEmpty()) || forceSend) | if ((needsSyncing && messagesSent.isEmpty()) || forceSend) | ||||
{ | { | ||||
for (int maxChanges = 30; --maxChanges >= 0;) | for (int maxChanges = 30; --maxChanges >= 0;) | ||||
{ | { | ||||
if (isAllZero (targetData, blockSize)) | |||||
break; | |||||
uint16 data[ImplementationClass::maxBlockSize]; | uint16 data[ImplementationClass::maxBlockSize]; | ||||
auto* latestState = getLatestExpectedDataState(); | auto* latestState = getLatestExpectedDataState(); | ||||
@@ -216,7 +228,7 @@ struct LittleFootRemoteHeap | |||||
static constexpr uint16 unknownByte = 0x100; | static constexpr uint16 unknownByte = 0x100; | ||||
private: | private: | ||||
uint16 deviceState[ImplementationClass::maxBlockSize]; | |||||
uint16 deviceState[ImplementationClass::maxBlockSize] = { 0 }; | |||||
uint8 targetData[ImplementationClass::maxBlockSize] = { 0 }; | uint8 targetData[ImplementationClass::maxBlockSize] = { 0 }; | ||||
uint32 programSize = 0; | uint32 programSize = 0; | ||||
bool needsSyncing = true, programStateKnown = true, programLoaded = false; | bool needsSyncing = true, programStateKnown = true, programLoaded = false; | ||||
@@ -32,6 +32,8 @@ struct BlockDataSheet | |||||
if (serialNumber.isLiveBlock()) initialiseForControlBlockLive(); | if (serialNumber.isLiveBlock()) initialiseForControlBlockLive(); | ||||
if (serialNumber.isLoopBlock()) initialiseForControlBlockLoop(); | if (serialNumber.isLoopBlock()) initialiseForControlBlockLoop(); | ||||
if (serialNumber.isDevCtrlBlock()) initialiseForControlBlockDeveloper(); | if (serialNumber.isDevCtrlBlock()) initialiseForControlBlockDeveloper(); | ||||
if (serialNumber.isTouchBlock()) initialiseForControlBlockTouch(); | |||||
if (serialNumber.isSeaboardBlock()) initialiseForSeaboardBlock(); | |||||
} | } | ||||
Block::ConnectionPort convertPortIndexToConnectorPort (BlocksProtocol::ConnectorPort port) const noexcept | Block::ConnectionPort convertPortIndexToConnectorPort (BlocksProtocol::ConnectorPort port) const noexcept | ||||
@@ -139,6 +141,21 @@ private: | |||||
ControlButton::ButtonFunction::up); | ControlButton::ButtonFunction::up); | ||||
} | } | ||||
void initialiseForControlBlockTouch() | |||||
{ | |||||
initialiseControlBlock ("Touch BLOCK", Block::Type::touchBlock, | |||||
ControlButton::ButtonFunction::velocitySensitivity, | |||||
ControlButton::ButtonFunction::glideSensitivity, | |||||
ControlButton::ButtonFunction::slideSensitivity, | |||||
ControlButton::ButtonFunction::pressSensitivity, | |||||
ControlButton::ButtonFunction::liftSensitivity, | |||||
ControlButton::ButtonFunction::fixedVelocity, | |||||
ControlButton::ButtonFunction::glideLock, | |||||
ControlButton::ButtonFunction::pianoMode, | |||||
ControlButton::ButtonFunction::down, | |||||
ControlButton::ButtonFunction::up); | |||||
} | |||||
void initialiseControlBlock (const char* name, Block::Type type, | void initialiseControlBlock (const char* name, Block::Type type, | ||||
ControlButton::ButtonFunction b1, ControlButton::ButtonFunction b2, | ControlButton::ButtonFunction b1, ControlButton::ButtonFunction b2, | ||||
ControlButton::ButtonFunction b3, ControlButton::ButtonFunction b4, | ControlButton::ButtonFunction b3, ControlButton::ButtonFunction b4, | ||||
@@ -179,6 +196,27 @@ private: | |||||
numLEDRowLEDs = 15; | numLEDRowLEDs = 15; | ||||
} | } | ||||
void initialiseForSeaboardBlock() | |||||
{ | |||||
apiType = Block::Type::seaboardBlock; | |||||
description = "Seaboard BLOCK (6x3)"; | |||||
widthUnits = 6; | |||||
heightUnits = 3; | |||||
lightGridWidth = 0; | |||||
lightGridHeight = 0; | |||||
numKeywaves = 24; | |||||
addPorts (2, 1, 0, 1); | |||||
hasTouchSurface = true; | |||||
programAndHeapSize = BlocksProtocol::padBlockProgramAndHeapSize; | |||||
addModeButton(); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
void addStatusLED (const char* name, float x, float y) | void addStatusLED (const char* name, float x, float y) | ||||
{ | { | ||||
@@ -258,6 +296,15 @@ static const char* getButtonNameForFunction (ControlButton::ButtonFunction fn) n | |||||
case BF::button5: return "5"; | case BF::button5: return "5"; | ||||
case BF::button6: return "6"; | case BF::button6: return "6"; | ||||
case BF::button7: return "7"; | case BF::button7: return "7"; | ||||
case BF::velocitySensitivity: return "Velocity Sensitivity"; | |||||
case BF::glideSensitivity: return "Glide Sensitivity"; | |||||
case BF::slideSensitivity: return "Slide Sensitivity"; | |||||
case BF::pressSensitivity: return "Press Sensitivity"; | |||||
case BF::liftSensitivity: return "Lift Sensitivity"; | |||||
case BF::fixedVelocity: return "Fixed Velocity"; | |||||
case BF::glideLock: return "Glide Lock"; | |||||
case BF::pianoMode: return "Piano Mode"; | |||||
} | } | ||||
jassertfalse; | jassertfalse; | ||||
@@ -47,6 +47,7 @@ enum class MessageFromDevice | |||||
firmwareUpdateACK = 0x03, | firmwareUpdateACK = 0x03, | ||||
deviceTopologyExtend = 0x04, | deviceTopologyExtend = 0x04, | ||||
deviceTopologyEnd = 0x05, | deviceTopologyEnd = 0x05, | ||||
deviceVersionList = 0x06, | |||||
touchStart = 0x10, | touchStart = 0x10, | ||||
touchMove = 0x11, | touchMove = 0x11, | ||||
@@ -56,6 +57,8 @@ enum class MessageFromDevice | |||||
touchMoveWithVelocity = 0x14, | touchMoveWithVelocity = 0x14, | ||||
touchEndWithVelocity = 0x15, | touchEndWithVelocity = 0x15, | ||||
configMessage = 0x18, | |||||
controlButtonDown = 0x20, | controlButtonDown = 0x20, | ||||
controlButtonUp = 0x21, | controlButtonUp = 0x21, | ||||
@@ -70,7 +73,9 @@ enum class MessageFromHost | |||||
deviceCommandMessage = 0x01, | deviceCommandMessage = 0x01, | ||||
sharedDataChange = 0x02, | sharedDataChange = 0x02, | ||||
programEventMessage = 0x03, | programEventMessage = 0x03, | ||||
firmwareUpdatePacket = 0x04 | |||||
firmwareUpdatePacket = 0x04, | |||||
configMessage = 0x10 | |||||
}; | }; | ||||
@@ -120,19 +125,27 @@ struct BlockSerialNumber | |||||
if (c == 0) | if (c == 0) | ||||
return false; | return false; | ||||
return isAnyControlBlock() || isPadBlock(); | |||||
return isAnyControlBlock() || isPadBlock() || isSeaboardBlock(); | |||||
} | } | ||||
bool isPadBlock() const noexcept { return hasPrefix ("LPB"); } | bool isPadBlock() const noexcept { return hasPrefix ("LPB"); } | ||||
bool isLiveBlock() const noexcept { return hasPrefix ("LIC"); } | bool isLiveBlock() const noexcept { return hasPrefix ("LIC"); } | ||||
bool isLoopBlock() const noexcept { return hasPrefix ("LOC"); } | bool isLoopBlock() const noexcept { return hasPrefix ("LOC"); } | ||||
bool isDevCtrlBlock() const noexcept { return hasPrefix ("DCB"); } | bool isDevCtrlBlock() const noexcept { return hasPrefix ("DCB"); } | ||||
bool isTouchBlock() const noexcept { return hasPrefix ("TCB"); } | |||||
bool isSeaboardBlock() const noexcept { return hasPrefix ("SBB"); } | |||||
bool isAnyControlBlock() const noexcept { return isLiveBlock() || isLoopBlock() || isDevCtrlBlock(); } | |||||
bool isAnyControlBlock() const noexcept { return isLiveBlock() || isLoopBlock() || isDevCtrlBlock() || isTouchBlock(); } | |||||
bool hasPrefix (const char* prefix) const noexcept { return memcmp (serial, prefix, 3) == 0; } | bool hasPrefix (const char* prefix) const noexcept { return memcmp (serial, prefix, 3) == 0; } | ||||
}; | }; | ||||
struct VersionNumber | |||||
{ | |||||
uint8 version[21] = {}; | |||||
uint8 length = 0; | |||||
}; | |||||
struct DeviceStatus | struct DeviceStatus | ||||
{ | { | ||||
BlockSerialNumber serialNumber; | BlockSerialNumber serialNumber; | ||||
@@ -147,9 +160,93 @@ struct DeviceConnection | |||||
ConnectorPort port1, port2; | ConnectorPort port1, port2; | ||||
}; | }; | ||||
struct DeviceVersion | |||||
{ | |||||
TopologyIndex index; | |||||
VersionNumber version; | |||||
}; | |||||
static constexpr uint8 maxBlocksInTopologyPacket = 6; | static constexpr uint8 maxBlocksInTopologyPacket = 6; | ||||
static constexpr uint8 maxConnectionsInTopologyPacket = 24; | static constexpr uint8 maxConnectionsInTopologyPacket = 24; | ||||
//============================================================================== | |||||
/** Configuration Item Identifiers. */ | |||||
enum ConfigItemId | |||||
{ | |||||
// MIDI | |||||
midiStartChannel = 0, | |||||
midiEndChannel = 1, | |||||
midiUseMPE = 2, | |||||
pitchBendRange = 3, | |||||
octave = 4, | |||||
transpose = 5, | |||||
slideCC = 6, | |||||
slideMode = 7, | |||||
octaveTopology = 8, | |||||
// Touch | |||||
velocitySensitivity = 10, | |||||
glideSensitivity = 11, | |||||
slideSensitivity = 12, | |||||
pressureSensitivity = 13, | |||||
liftSensitivity = 14, | |||||
fixedVelocity = 15, | |||||
fixedVelocityValue = 16, | |||||
pianoMode = 17, | |||||
glideLock = 18, | |||||
// Live | |||||
mode = 20, | |||||
volume = 21, | |||||
scale = 22, | |||||
hideMode = 23, | |||||
chord = 24, | |||||
arpPattern = 25, | |||||
tempo = 26, | |||||
// Tracking | |||||
xTrackingMode = 30, | |||||
yTrackingMode = 31, | |||||
zTrackingMode = 32, | |||||
// User | |||||
user0 = 64, | |||||
user1 = 65, | |||||
user2 = 66, | |||||
user3 = 67, | |||||
user4 = 68, | |||||
user5 = 69, | |||||
user6 = 70, | |||||
user7 = 71, | |||||
user8 = 72, | |||||
user9 = 73, | |||||
user10 = 74, | |||||
user11 = 75, | |||||
user12 = 76, | |||||
user13 = 77, | |||||
user14 = 78, | |||||
user15 = 79, | |||||
user16 = 80, | |||||
user17 = 81, | |||||
user18 = 82, | |||||
user19 = 83, | |||||
user20 = 84, | |||||
user21 = 85, | |||||
user22 = 86, | |||||
user23 = 87, | |||||
user24 = 88, | |||||
user25 = 89, | |||||
user26 = 90, | |||||
user27 = 91, | |||||
user28 = 92, | |||||
user29 = 93, | |||||
user30 = 94, | |||||
user31 = 95 | |||||
}; | |||||
static constexpr uint8 numberOfUserConfigs = 32; | |||||
static constexpr uint8 maxConfigIndex = uint8 (ConfigItemId::user0) + numberOfUserConfigs; | |||||
static constexpr uint8 configUserConfigNameLength = 32; | |||||
static constexpr uint8 configMaxOptions = 8; | |||||
static constexpr uint8 configOptionNameLength = 16; | |||||
//============================================================================== | //============================================================================== | ||||
/** The coordinates of a touch. */ | /** The coordinates of a touch. */ | ||||
struct TouchPosition | struct TouchPosition | ||||
@@ -197,6 +294,23 @@ enum DeviceCommands | |||||
using DeviceCommand = IntegerWithBitSize<9>; | using DeviceCommand = IntegerWithBitSize<9>; | ||||
//============================================================================== | |||||
enum ConfigCommands | |||||
{ | |||||
setConfig = 0x00, | |||||
requestConfig = 0x01, // Request a config update | |||||
requestFactorySync = 0x02, // Requests all active factory config data | |||||
requestUserSync = 0x03, // Requests all active user config data | |||||
updateConfig = 0x04, // Set value, min and max | |||||
updateUserConfig = 0x05, // As above but contains user config metadata | |||||
setConfigState = 0x06, // Set config activation state and whether it is saved in flash | |||||
factorySyncEnd = 0x07 | |||||
}; | |||||
using ConfigCommand = IntegerWithBitSize<4>; | |||||
using ConfigItemIndex = IntegerWithBitSize<8>; | |||||
using ConfigItemValue = IntegerWithBitSize<32>; | |||||
//============================================================================== | //============================================================================== | ||||
/** An ID for a control-block button type */ | /** An ID for a control-block button type */ | ||||
using ControlButtonID = IntegerWithBitSize<12>; | using ControlButtonID = IntegerWithBitSize<12>; | ||||
@@ -259,6 +373,10 @@ enum BitSizes | |||||
firmwareUpdateACK = MessageType::bits + FirmwareUpdateACKCode::bits, | firmwareUpdateACK = MessageType::bits + FirmwareUpdateACKCode::bits, | ||||
controlButtonMessage = typeDeviceAndTime + ControlButtonID::bits, | controlButtonMessage = typeDeviceAndTime + ControlButtonID::bits, | ||||
configSetMessage = MessageType::bits + ConfigCommand::bits + ConfigItemIndex::bits + ConfigItemValue::bits, | |||||
configRespMessage = MessageType::bits + ConfigCommand::bits + ConfigItemIndex::bits + (ConfigItemValue::bits * 3), | |||||
configSyncEndMessage = MessageType::bits + ConfigCommand::bits, | |||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
@@ -299,10 +417,14 @@ static constexpr const char* ledProgramLittleFootFunctions[] = | |||||
"getVerticalDistFromMaster/i", | "getVerticalDistFromMaster/i", | ||||
"getAngleFromMaster/i", | "getAngleFromMaster/i", | ||||
"setAutoRotate/vb", | "setAutoRotate/vb", | ||||
"getClusterIndex/i", | |||||
"getClusterWidth/i", | "getClusterWidth/i", | ||||
"getClusterHeight/i", | "getClusterHeight/i", | ||||
"getClusterXpos/i", | "getClusterXpos/i", | ||||
"getClusterYpos/i", | "getClusterYpos/i", | ||||
"getNumBlocksInCurrentCluster/i", | |||||
"getBlockIdForBlockInCluster/ii", | |||||
"isMasterInCurrentCluster/b", | |||||
"makeARGB/iiiii", | "makeARGB/iiiii", | ||||
"blendARGB/iii", | "blendARGB/iii", | ||||
"fillPixel/viii", | "fillPixel/viii", | ||||
@@ -317,6 +439,7 @@ static constexpr const char* ledProgramLittleFootFunctions[] = | |||||
"drawNumber/viiii", | "drawNumber/viiii", | ||||
"clearDisplay/v", | "clearDisplay/v", | ||||
"clearDisplay/vi", | "clearDisplay/vi", | ||||
"displayBatteryLevel/v", | |||||
"sendMIDI/vi", | "sendMIDI/vi", | ||||
"sendMIDI/vii", | "sendMIDI/vii", | ||||
"sendMIDI/viii", | "sendMIDI/viii", | ||||
@@ -331,5 +454,19 @@ static constexpr const char* ledProgramLittleFootFunctions[] = | |||||
"deassignChannel/vii", | "deassignChannel/vii", | ||||
"getControlChannel/i", | "getControlChannel/i", | ||||
"useMPEDuplicateFilter/vb", | "useMPEDuplicateFilter/vb", | ||||
"getSensorValue/iii", | |||||
"handleTouchAsSeaboard/vi", | |||||
"setPowerSavingEnabled/vb", | |||||
"getLocalConfig/ii", | |||||
"setLocalConfig/vii", | |||||
"requestRemoteConfig/vii", | |||||
"setRemoteConfig/viii", | |||||
"setLocalConfigItemRange/viii", | |||||
"setLocalConfigActiveState/vibb", | |||||
"linkBlockIDtoController/vi", | |||||
"repaintControl/v", | |||||
"onControlPress/vi", | |||||
"onControlRelease/vi", | |||||
"initControl/viiiiiiiii", | |||||
nullptr | nullptr | ||||
}; | }; |
@@ -224,6 +224,39 @@ struct HostPacketBuilder | |||||
return true; | return true; | ||||
} | } | ||||
//============================================================================== | |||||
bool addConfigSetMessage (int32 item, int32 value) | |||||
{ | |||||
writeMessageType (MessageFromHost::configMessage); | |||||
ConfigCommand type = ConfigCommands::setConfig; | |||||
data << type << IntegerWithBitSize<8> ((uint32) item) << IntegerWithBitSize<32> ((uint32) value); | |||||
return true; | |||||
} | |||||
bool addRequestMessage (int32 item) | |||||
{ | |||||
writeMessageType (MessageFromHost::configMessage); | |||||
ConfigCommand type = ConfigCommands::requestConfig; | |||||
data << type << IntegerWithBitSize<32> (0) << IntegerWithBitSize<8> ((uint32) item); | |||||
return true; | |||||
} | |||||
bool addRequestFactorySyncMessage() | |||||
{ | |||||
writeMessageType (MessageFromHost::configMessage); | |||||
ConfigCommand type = ConfigCommands::requestFactorySync; | |||||
data << type; | |||||
return true; | |||||
} | |||||
bool addRequestUserSyncMessage() | |||||
{ | |||||
writeMessageType (MessageFromHost::configMessage); | |||||
ConfigCommand type = ConfigCommands::requestUserSync; | |||||
data << type; | |||||
return true; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
private: | private: | ||||
Packed7BitArrayBuilder<maxPacketBytes> data; | Packed7BitArrayBuilder<maxPacketBytes> data; | ||||
@@ -64,6 +64,7 @@ struct HostPacketDecoder | |||||
case MessageFromDevice::deviceTopology: return handleTopology (handler, reader, true); | case MessageFromDevice::deviceTopology: return handleTopology (handler, reader, true); | ||||
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::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); | ||||
@@ -75,6 +76,7 @@ struct HostPacketDecoder | |||||
case MessageFromDevice::programEventMessage: return handleCustomMessage (handler, reader, deviceIndex, packetTimestamp); | case MessageFromDevice::programEventMessage: return handleCustomMessage (handler, reader, deviceIndex, packetTimestamp); | ||||
case MessageFromDevice::packetACK: return handlePacketACK (handler, reader, deviceIndex); | case MessageFromDevice::packetACK: return handlePacketACK (handler, reader, deviceIndex); | ||||
case MessageFromDevice::firmwareUpdateACK: return handleFirmwareUpdateACK (handler, reader, deviceIndex); | case MessageFromDevice::firmwareUpdateACK: return handleFirmwareUpdateACK (handler, reader, deviceIndex); | ||||
case MessageFromDevice::configMessage: return handleConfigMessage (handler, reader, deviceIndex); | |||||
case MessageFromDevice::logMessage: return handleLogMessage (handler, reader, deviceIndex); | case MessageFromDevice::logMessage: return handleLogMessage (handler, reader, deviceIndex); | ||||
default: | default: | ||||
@@ -112,6 +114,8 @@ struct HostPacketDecoder | |||||
if (newTopology) | if (newTopology) | ||||
handler.beginTopology ((int) numDevices, (int) numConnections); | handler.beginTopology ((int) numDevices, (int) numConnections); | ||||
else | |||||
handler.extendTopology ((int) numDevices, (int) numConnections); | |||||
for (uint32 i = 0; i < numDevices; ++i) | for (uint32 i = 0; i < numDevices; ++i) | ||||
handleTopologyDevice (handler, reader); | handleTopologyDevice (handler, reader); | ||||
@@ -166,6 +170,20 @@ struct HostPacketDecoder | |||||
handler.handleTopologyConnection (connection); | handler.handleTopologyConnection (connection); | ||||
} | } | ||||
static bool handleVersion (Handler& handler, Packed7BitArrayReader& reader) | |||||
{ | |||||
DeviceVersion version; | |||||
version.index = (TopologyIndex) reader.readBits (topologyIndexBits); | |||||
version.version.length = (uint8) reader.readBits (7); | |||||
for (uint32 i = 0; i < version.version.length; ++i) | |||||
version.version.version[i] = (uint8) reader.readBits (7); | |||||
handler.handleVersion (version); | |||||
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) | ||||
{ | { | ||||
@@ -273,6 +291,38 @@ struct HostPacketDecoder | |||||
return true; | return true; | ||||
} | } | ||||
static bool handleConfigMessage (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex) | |||||
{ | |||||
ConfigCommand type = reader.read<ConfigCommand>().get(); | |||||
if (type == updateConfig) | |||||
{ | |||||
auto item = (int32) reader.read<IntegerWithBitSize<8>>().get(); | |||||
auto value = (int32) reader.read<IntegerWithBitSize<32>>().get(); | |||||
auto min = (int32) reader.read<IntegerWithBitSize<32>>().get(); | |||||
auto max = (int32) reader.read<IntegerWithBitSize<32>>().get(); | |||||
handler.handleConfigUpdateMessage (deviceIndex, item, value, min, max); | |||||
return true; | |||||
} | |||||
if (type == setConfig) | |||||
{ | |||||
auto item = (int32) reader.read<IntegerWithBitSize<8>>().get(); | |||||
auto value = (int32) reader.read<IntegerWithBitSize<32>>().get(); | |||||
handler.handleConfigSetMessage (deviceIndex, item, value); | |||||
return true; | |||||
} | |||||
if (type == factorySyncEnd) | |||||
{ | |||||
handler.handleConfigFactorySyncEndMessage (deviceIndex); | |||||
} | |||||
return true; | |||||
} | |||||
static bool handleLogMessage (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex) | static bool handleLogMessage (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex) | ||||
{ | { | ||||
String message; | String message; | ||||
@@ -309,6 +309,7 @@ struct PhysicalTopologySource::Internal | |||||
Block::UID uid; | Block::UID uid; | ||||
BlocksProtocol::TopologyIndex index; | BlocksProtocol::TopologyIndex index; | ||||
BlocksProtocol::BlockSerialNumber serial; | BlocksProtocol::BlockSerialNumber serial; | ||||
BlocksProtocol::VersionNumber version; | |||||
bool isMaster; | bool isMaster; | ||||
}; | }; | ||||
@@ -324,9 +325,12 @@ struct PhysicalTopologySource::Internal | |||||
for (auto& device : devices) | for (auto& device : devices) | ||||
{ | { | ||||
BlocksProtocol::VersionNumber version; | |||||
result.add ({ getBlockUIDFromSerialNumber (device.serialNumber), | result.add ({ getBlockUIDFromSerialNumber (device.serialNumber), | ||||
device.index, | device.index, | ||||
device.serialNumber, | device.serialNumber, | ||||
version, | |||||
isFirst }); | isFirst }); | ||||
isFirst = false; | isFirst = false; | ||||
@@ -353,6 +357,23 @@ struct PhysicalTopologySource::Internal | |||||
return false; | return false; | ||||
} | } | ||||
static bool versionNumberAddedToBlock (const juce::Array<DeviceInfo>& devices, Block::UID uid, juce::String version) noexcept | |||||
{ | |||||
if (version.length() == 0) | |||||
for (auto&& d : devices) | |||||
if (d.uid == uid && d.version.length) | |||||
return true; | |||||
return false; | |||||
} | |||||
static void setVersionNumberForBlock (const juce::Array<DeviceInfo>& devices, Block& block) noexcept | |||||
{ | |||||
for (auto&& d : devices) | |||||
if (d.uid == block.uid) | |||||
block.versionNumber = juce::String ((const char*) d.version.version, d.version.length); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
struct ConnectedDeviceGroup : private juce::AsyncUpdater, | struct ConnectedDeviceGroup : private juce::AsyncUpdater, | ||||
private juce::Timer | private juce::Timer | ||||
@@ -468,6 +489,12 @@ struct PhysicalTopologySource::Internal | |||||
incomingTopologyConnections.ensureStorageAllocated (numConnections); | incomingTopologyConnections.ensureStorageAllocated (numConnections); | ||||
} | } | ||||
void extendTopology (int numDevices, int numConnections) | |||||
{ | |||||
incomingTopologyDevices.ensureStorageAllocated (incomingTopologyDevices.size() + numDevices); | |||||
incomingTopologyConnections.ensureStorageAllocated (incomingTopologyConnections.size() + numConnections); | |||||
} | |||||
void handleTopologyDevice (BlocksProtocol::DeviceStatus status) | void handleTopologyDevice (BlocksProtocol::DeviceStatus status) | ||||
{ | { | ||||
incomingTopologyDevices.add (status); | incomingTopologyDevices.add (status); | ||||
@@ -490,6 +517,19 @@ struct PhysicalTopologySource::Internal | |||||
blockPings.clear(); | blockPings.clear(); | ||||
} | } | ||||
void handleVersion (BlocksProtocol::DeviceVersion version) | |||||
{ | |||||
for (auto& d : currentDeviceInfo) | |||||
{ | |||||
if (d.index == version.index) | |||||
{ | |||||
d.version = version.version; | |||||
detector.handleTopologyChange(); | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
void handleControlButtonUpDown (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, | void handleControlButtonUpDown (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, | ||||
BlocksProtocol::ControlButtonID buttonID, bool isDown) | BlocksProtocol::ControlButtonID buttonID, bool isDown) | ||||
{ | { | ||||
@@ -555,6 +595,24 @@ struct PhysicalTopologySource::Internal | |||||
detector.handleFirmwareUpdateACK (deviceID, (uint8) resultCode.get()); | detector.handleFirmwareUpdateACK (deviceID, (uint8) resultCode.get()); | ||||
} | } | ||||
void handleConfigUpdateMessage (BlocksProtocol::TopologyIndex deviceIndex, int32 item, int32 value, int32 min, int32 max) | |||||
{ | |||||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||||
detector.handleConfigUpdateMessage (deviceID, item, value, min, max); | |||||
} | |||||
void handleConfigSetMessage (BlocksProtocol::TopologyIndex deviceIndex, int32 item, int32 value) | |||||
{ | |||||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||||
detector.handleConfigSetMessage (deviceID, item, value); | |||||
} | |||||
void handleConfigFactorySyncEndMessage (BlocksProtocol::TopologyIndex deviceIndex) | |||||
{ | |||||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||||
detector.handleConfigFactorySyncEndMessage (deviceID); | |||||
} | |||||
void handleLogMessage (BlocksProtocol::TopologyIndex deviceIndex, const String& message) | void handleLogMessage (BlocksProtocol::TopologyIndex deviceIndex, const String& message) | ||||
{ | { | ||||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | ||||
@@ -828,12 +886,16 @@ struct PhysicalTopologySource::Internal | |||||
currentTopology.blocks.remove (i); | currentTopology.blocks.remove (i); | ||||
} | } | ||||
else if (versionNumberAddedToBlock (newDeviceInfo, block->uid, block->versionNumber)) | |||||
{ | |||||
setVersionNumberForBlock (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.isMaster)); | |||||
currentTopology.blocks.add (new BlockImplementation (info.serial, *this, info.version, info.isMaster)); | |||||
currentTopology.connections.swapWith (newDeviceConnections); | currentTopology.connections.swapWith (newDeviceConnections); | ||||
} | } | ||||
@@ -864,6 +926,48 @@ struct PhysicalTopologySource::Internal | |||||
bi->handleFirmwareUpdateACK (resultCode); | bi->handleFirmwareUpdateACK (resultCode); | ||||
} | } | ||||
void handleConfigUpdateMessage (Block::UID deviceID, int32 item, int32 value, int32 min, int32 max) | |||||
{ | |||||
for (auto&& b : currentTopology.blocks) | |||||
if (b->uid == deviceID) | |||||
if (auto bi = BlockImplementation::getFrom (*b)) | |||||
bi->handleConfigUpdateMessage (item, value, min, max); | |||||
} | |||||
void notifyBlockOfConfigChange (BlockImplementation& bi, uint32 item) | |||||
{ | |||||
if (auto configChangedCallback = bi.configChangedCallback) | |||||
{ | |||||
if (item >= bi.getMaxConfigIndex()) | |||||
configChangedCallback (bi, {}, item); | |||||
else | |||||
configChangedCallback (bi, bi.getLocalConfigMetaData (item), item); | |||||
} | |||||
} | |||||
void handleConfigSetMessage (Block::UID deviceID, int32 item, int32 value) | |||||
{ | |||||
for (auto&& b : currentTopology.blocks) | |||||
{ | |||||
if (b->uid == deviceID) | |||||
{ | |||||
if (auto bi = BlockImplementation::getFrom (*b)) | |||||
{ | |||||
bi->handleConfigSetMessage (item, value); | |||||
notifyBlockOfConfigChange (*bi, uint32 (item)); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void handleConfigFactorySyncEndMessage (Block::UID deviceID) | |||||
{ | |||||
for (auto&& b : currentTopology.blocks) | |||||
if (b->uid == deviceID) | |||||
if (auto bi = BlockImplementation::getFrom (*b)) | |||||
notifyBlockOfConfigChange (*bi, bi->getMaxConfigIndex()); | |||||
} | |||||
void handleLogMessage (Block::UID deviceID, const String& message) const | void handleLogMessage (Block::UID deviceID, const String& message) const | ||||
{ | { | ||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | ||||
@@ -930,9 +1034,9 @@ struct PhysicalTopologySource::Internal | |||||
//============================================================================== | //============================================================================== | ||||
int getIndexFromDeviceID (Block::UID deviceID) const noexcept | int getIndexFromDeviceID (Block::UID deviceID) const noexcept | ||||
{ | { | ||||
for (auto c : connectedDeviceGroups) | |||||
for (auto* c : connectedDeviceGroups) | |||||
{ | { | ||||
const int index = c->getIndexFromDeviceID (deviceID); | |||||
auto index = c->getIndexFromDeviceID (deviceID); | |||||
if (index >= 0) | if (index >= 0) | ||||
return index; | return index; | ||||
@@ -944,7 +1048,7 @@ struct PhysicalTopologySource::Internal | |||||
template <typename PacketBuilder> | template <typename PacketBuilder> | ||||
bool sendMessageToDevice (Block::UID deviceID, const PacketBuilder& builder) const | bool sendMessageToDevice (Block::UID deviceID, const PacketBuilder& builder) const | ||||
{ | { | ||||
for (auto c : connectedDeviceGroups) | |||||
for (auto* c : connectedDeviceGroups) | |||||
if (c->getIndexFromDeviceID (deviceID) >= 0) | if (c->getIndexFromDeviceID (deviceID) >= 0) | ||||
return c->sendMessageToDevice (builder); | return c->sendMessageToDevice (builder); | ||||
@@ -953,7 +1057,7 @@ struct PhysicalTopologySource::Internal | |||||
static Detector* getFrom (Block& b) noexcept | static Detector* getFrom (Block& b) noexcept | ||||
{ | { | ||||
if (auto bi = BlockImplementation::getFrom (b)) | |||||
if (auto* bi = BlockImplementation::getFrom (b)) | |||||
return &(bi->detector); | return &(bi->detector); | ||||
jassertfalse; | jassertfalse; | ||||
@@ -1050,9 +1154,13 @@ struct PhysicalTopologySource::Internal | |||||
private MIDIDeviceConnection::Listener, | private MIDIDeviceConnection::Listener, | ||||
private Timer | private Timer | ||||
{ | { | ||||
BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, Detector& detectorToUse, bool master) | |||||
: Block (juce::String ((const char*) serial.serial, sizeof (serial.serial))), modelData (serial), | |||||
remoteHeap (modelData.programAndHeapSize), detector (detectorToUse), isMaster (master) | |||||
BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, Detector& detectorToUse, BlocksProtocol::VersionNumber version, bool master) | |||||
: Block (juce::String ((const char*) serial.serial, sizeof (serial.serial)), | |||||
juce::String ((const char*) version.version, version.length)), | |||||
modelData (serial), | |||||
remoteHeap (modelData.programAndHeapSize), | |||||
detector (detectorToUse), | |||||
isMaster (master) | |||||
{ | { | ||||
sendCommandMessage (BlocksProtocol::beginAPIMode); | sendCommandMessage (BlocksProtocol::beginAPIMode); | ||||
@@ -1060,21 +1168,24 @@ struct PhysicalTopologySource::Internal | |||||
touchSurface.reset (new TouchSurfaceImplementation (*this)); | touchSurface.reset (new TouchSurfaceImplementation (*this)); | ||||
int i = 0; | int i = 0; | ||||
for (auto b : modelData.buttons) | |||||
for (auto&& b : modelData.buttons) | |||||
controlButtons.add (new ControlButtonImplementation (*this, i++, b)); | controlButtons.add (new ControlButtonImplementation (*this, i++, b)); | ||||
if (modelData.lightGridWidth > 0 && modelData.lightGridHeight > 0) | if (modelData.lightGridWidth > 0 && modelData.lightGridHeight > 0) | ||||
ledGrid.reset (new LEDGridImplementation (*this)); | ledGrid.reset (new LEDGridImplementation (*this)); | ||||
for (auto s : modelData.statusLEDs) | |||||
for (auto&& s : modelData.statusLEDs) | |||||
statusLights.add (new StatusLightImplementation (*this, s)); | statusLights.add (new StatusLightImplementation (*this, s)); | ||||
if (modelData.numLEDRowLEDs > 0) | if (modelData.numLEDRowLEDs > 0) | ||||
ledRow.reset (new LEDRowImplementation (*this)); | ledRow.reset (new LEDRowImplementation (*this)); | ||||
listenerToMidiConnection = dynamic_cast<MIDIDeviceConnection*> (detector.getDeviceConnectionFor (*this)); | listenerToMidiConnection = dynamic_cast<MIDIDeviceConnection*> (detector.getDeviceConnectionFor (*this)); | ||||
if (listenerToMidiConnection != nullptr) | if (listenerToMidiConnection != nullptr) | ||||
listenerToMidiConnection->addListener (this); | listenerToMidiConnection->addListener (this); | ||||
config.setDeviceComms (listenerToMidiConnection); | |||||
} | } | ||||
~BlockImplementation() | ~BlockImplementation() | ||||
@@ -1189,6 +1300,7 @@ struct PhysicalTopologySource::Internal | |||||
return type == Block::Type::liveBlock | return type == Block::Type::liveBlock | ||||
|| type == Block::Type::loopBlock | || type == Block::Type::loopBlock | ||||
|| type == Block::Type::touchBlock | |||||
|| type == Block::Type::developerControlBlock; | || type == Block::Type::developerControlBlock; | ||||
} | } | ||||
@@ -1242,7 +1354,7 @@ struct PhysicalTopologySource::Internal | |||||
if (compiler.getCompiledProgram().getTotalSpaceNeeded() > getMemorySize()) | if (compiler.getCompiledProgram().getTotalSpaceNeeded() > getMemorySize()) | ||||
return Result::fail ("Program too large!"); | return Result::fail ("Program too large!"); | ||||
size_t size = (size_t) compiler.compiledObjectCode.size(); | |||||
auto size = (size_t) compiler.compiledObjectCode.size(); | |||||
programSize = (uint32) size; | programSize = (uint32) size; | ||||
remoteHeap.resetDataRangeToUnknown (0, remoteHeap.blockSize); | remoteHeap.resetDataRangeToUnknown (0, remoteHeap.blockSize); | ||||
@@ -1252,6 +1364,11 @@ struct PhysicalTopologySource::Internal | |||||
remoteHeap.resetDataRangeToUnknown (0, (uint32) size); | remoteHeap.resetDataRangeToUnknown (0, (uint32) size); | ||||
remoteHeap.setBytes (0, compiler.compiledObjectCode.begin(), size); | remoteHeap.setBytes (0, compiler.compiledObjectCode.begin(), size); | ||||
remoteHeap.sendChanges (*this, true); | remoteHeap.sendChanges (*this, true); | ||||
this->resetConfigListActiveStatus(); | |||||
if (auto changeCallback = this->configChangedCallback) | |||||
changeCallback (*this, {}, this->getMaxConfigIndex()); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -1383,6 +1500,16 @@ struct PhysicalTopologySource::Internal | |||||
} | } | ||||
} | } | ||||
void handleConfigUpdateMessage (int32 item, int32 value, int32 min, int32 max) | |||||
{ | |||||
config.handleConfigUpdateMessage (item, value, min, max); | |||||
} | |||||
void handleConfigSetMessage(int32 item, int32 value) | |||||
{ | |||||
config.handleConfigSetMessage (item, value); | |||||
} | |||||
void pingFromDevice() | void pingFromDevice() | ||||
{ | { | ||||
lastMessageReceiveTime = juce::Time::getCurrentTime(); | lastMessageReceiveTime = juce::Time::getCurrentTime(); | ||||
@@ -1423,6 +1550,71 @@ struct PhysicalTopologySource::Internal | |||||
sendCommandMessage (BlocksProtocol::ping); | sendCommandMessage (BlocksProtocol::ping); | ||||
} | } | ||||
//============================================================================== | |||||
int32 getLocalConfigValue (uint32 item) override | |||||
{ | |||||
config.setDeviceIndex ((TopologyIndex) getDeviceIndex()); | |||||
return config.getItemValue ((BlocksProtocol::ConfigItemId) item); | |||||
} | |||||
void setLocalConfigValue (uint32 item, int32 value) override | |||||
{ | |||||
config.setDeviceIndex ((TopologyIndex) getDeviceIndex()); | |||||
config.setItemValue ((BlocksProtocol::ConfigItemId) item, value); | |||||
} | |||||
void setLocalConfigRange (uint32 item, int32 min, int32 max) override | |||||
{ | |||||
config.setDeviceIndex ((TopologyIndex) getDeviceIndex()); | |||||
config.setItemMin ((BlocksProtocol::ConfigItemId) item, min); | |||||
config.setItemMax ((BlocksProtocol::ConfigItemId) item, max); | |||||
} | |||||
void setLocalConfigItemActive (uint32 item, bool isActive) override | |||||
{ | |||||
config.setDeviceIndex ((TopologyIndex) getDeviceIndex()); | |||||
config.setItemActive ((BlocksProtocol::ConfigItemId) item, isActive); | |||||
} | |||||
bool isLocalConfigItemActive (uint32 item) override | |||||
{ | |||||
config.setDeviceIndex ((TopologyIndex) getDeviceIndex()); | |||||
return config.getItemActive ((BlocksProtocol::ConfigItemId) item); | |||||
} | |||||
uint32 getMaxConfigIndex () override | |||||
{ | |||||
return uint32 (BlocksProtocol::maxConfigIndex); | |||||
} | |||||
bool isValidUserConfigIndex (uint32 item) override | |||||
{ | |||||
return item >= (uint32) BlocksProtocol::ConfigItemId::user0 | |||||
&& item < (uint32) (BlocksProtocol::ConfigItemId::user0 + numberOfUserConfigs); | |||||
} | |||||
ConfigMetaData getLocalConfigMetaData (uint32 item) override | |||||
{ | |||||
config.setDeviceIndex ((TopologyIndex) getDeviceIndex()); | |||||
return config.getMetaData ((BlocksProtocol::ConfigItemId) item); | |||||
} | |||||
void requestFactoryConfigSync() override | |||||
{ | |||||
config.setDeviceIndex ((TopologyIndex) getDeviceIndex()); | |||||
config.requestFactoryConfigSync(); | |||||
} | |||||
void resetConfigListActiveStatus() override | |||||
{ | |||||
config.resetConfigListActiveStatus(); | |||||
} | |||||
void setConfigChangedCallback (std::function<void(Block&, const ConfigMetaData&, uint32)> configChanged) override | |||||
{ | |||||
configChangedCallback = configChanged; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
std::unique_ptr<TouchSurface> touchSurface; | std::unique_ptr<TouchSurface> touchSurface; | ||||
juce::OwnedArray<ControlButton> controlButtons; | juce::OwnedArray<ControlButton> controlButtons; | ||||
@@ -1448,11 +1640,14 @@ struct PhysicalTopologySource::Internal | |||||
Detector& detector; | Detector& detector; | ||||
juce::Time lastMessageSendTime, lastMessageReceiveTime; | juce::Time lastMessageSendTime, lastMessageReceiveTime; | ||||
BlockConfigManager config; | |||||
std::function<void(Block&, const ConfigMetaData&, uint32)> configChangedCallback; | |||||
private: | private: | ||||
std::unique_ptr<Program> program; | std::unique_ptr<Program> program; | ||||
uint32 programSize = 0; | uint32 programSize = 0; | ||||
std::function<void (uint8)> firmwarePacketAckCallback; | |||||
std::function<void(uint8)> firmwarePacketAckCallback; | |||||
uint32 resetMessagesSent = 0; | uint32 resetMessagesSent = 0; | ||||
bool isStillConnected = true; | bool isStillConnected = true; | ||||