@@ -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() {} | |||
void Block::addDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.add (listener); } | |||
@@ -42,6 +42,7 @@ public: | |||
liveBlock, | |||
loopBlock, | |||
developerControlBlock, | |||
touchBlock, | |||
seaboardBlock // on-screen seaboard view | |||
}; | |||
@@ -58,6 +59,9 @@ public: | |||
/** The Block's serial number. */ | |||
const juce::String serialNumber; | |||
/** The Block's version number */ | |||
juce::String versionNumber; | |||
using UID = uint64; | |||
/** This Block's UID. | |||
@@ -256,6 +260,117 @@ public: | |||
/** Sets the current program as the block's default state. */ | |||
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. */ | |||
virtual void setLogger (std::function<void(const String&)> loggingCallback) = 0; | |||
@@ -264,6 +379,9 @@ public: | |||
virtual bool sendFirmwareUpdatePacket (const uint8* data, uint8 size, | |||
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. */ | |||
struct DataInputPortListener | |||
@@ -292,6 +410,7 @@ public: | |||
protected: | |||
//============================================================================== | |||
Block (const juce::String& serialNumberToUse); | |||
Block (const juce::String& serial, const juce::String& version); | |||
juce::ListenerList<DataInputPortListener> dataInputPortListeners; | |||
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, | |||
button5, | |||
button6, | |||
button7 | |||
button7, | |||
// touch block buttons | |||
velocitySensitivity, | |||
glideSensitivity, | |||
slideSensitivity, | |||
pressSensitivity, | |||
liftSensitivity, | |||
fixedVelocity, | |||
glideLock, | |||
pianoMode | |||
}; | |||
/** Returns the button's type. */ | |||
@@ -34,6 +34,7 @@ namespace juce | |||
#include "protocol/juce_BlockModels.h" | |||
} | |||
#include "blocks/juce_BlockConfigManager.h" | |||
#include "blocks/juce_Block.cpp" | |||
#include "topology/juce_PhysicalTopologySource.cpp" | |||
#include "topology/juce_RuleBasedTopologySource.cpp" | |||
@@ -122,12 +122,24 @@ struct LittleFootRemoteHeap | |||
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) | |||
{ | |||
if ((needsSyncing && messagesSent.isEmpty()) || forceSend) | |||
{ | |||
for (int maxChanges = 30; --maxChanges >= 0;) | |||
{ | |||
if (isAllZero (targetData, blockSize)) | |||
break; | |||
uint16 data[ImplementationClass::maxBlockSize]; | |||
auto* latestState = getLatestExpectedDataState(); | |||
@@ -216,7 +228,7 @@ struct LittleFootRemoteHeap | |||
static constexpr uint16 unknownByte = 0x100; | |||
private: | |||
uint16 deviceState[ImplementationClass::maxBlockSize]; | |||
uint16 deviceState[ImplementationClass::maxBlockSize] = { 0 }; | |||
uint8 targetData[ImplementationClass::maxBlockSize] = { 0 }; | |||
uint32 programSize = 0; | |||
bool needsSyncing = true, programStateKnown = true, programLoaded = false; | |||
@@ -32,6 +32,8 @@ struct BlockDataSheet | |||
if (serialNumber.isLiveBlock()) initialiseForControlBlockLive(); | |||
if (serialNumber.isLoopBlock()) initialiseForControlBlockLoop(); | |||
if (serialNumber.isDevCtrlBlock()) initialiseForControlBlockDeveloper(); | |||
if (serialNumber.isTouchBlock()) initialiseForControlBlockTouch(); | |||
if (serialNumber.isSeaboardBlock()) initialiseForSeaboardBlock(); | |||
} | |||
Block::ConnectionPort convertPortIndexToConnectorPort (BlocksProtocol::ConnectorPort port) const noexcept | |||
@@ -139,6 +141,21 @@ private: | |||
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, | |||
ControlButton::ButtonFunction b1, ControlButton::ButtonFunction b2, | |||
ControlButton::ButtonFunction b3, ControlButton::ButtonFunction b4, | |||
@@ -179,6 +196,27 @@ private: | |||
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) | |||
{ | |||
@@ -258,6 +296,15 @@ static const char* getButtonNameForFunction (ControlButton::ButtonFunction fn) n | |||
case BF::button5: return "5"; | |||
case BF::button6: return "6"; | |||
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; | |||
@@ -47,6 +47,7 @@ enum class MessageFromDevice | |||
firmwareUpdateACK = 0x03, | |||
deviceTopologyExtend = 0x04, | |||
deviceTopologyEnd = 0x05, | |||
deviceVersionList = 0x06, | |||
touchStart = 0x10, | |||
touchMove = 0x11, | |||
@@ -56,6 +57,8 @@ enum class MessageFromDevice | |||
touchMoveWithVelocity = 0x14, | |||
touchEndWithVelocity = 0x15, | |||
configMessage = 0x18, | |||
controlButtonDown = 0x20, | |||
controlButtonUp = 0x21, | |||
@@ -70,7 +73,9 @@ enum class MessageFromHost | |||
deviceCommandMessage = 0x01, | |||
sharedDataChange = 0x02, | |||
programEventMessage = 0x03, | |||
firmwareUpdatePacket = 0x04 | |||
firmwareUpdatePacket = 0x04, | |||
configMessage = 0x10 | |||
}; | |||
@@ -120,19 +125,27 @@ struct BlockSerialNumber | |||
if (c == 0) | |||
return false; | |||
return isAnyControlBlock() || isPadBlock(); | |||
return isAnyControlBlock() || isPadBlock() || isSeaboardBlock(); | |||
} | |||
bool isPadBlock() const noexcept { return hasPrefix ("LPB"); } | |||
bool isLiveBlock() const noexcept { return hasPrefix ("LIC"); } | |||
bool isLoopBlock() const noexcept { return hasPrefix ("LOC"); } | |||
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; } | |||
}; | |||
struct VersionNumber | |||
{ | |||
uint8 version[21] = {}; | |||
uint8 length = 0; | |||
}; | |||
struct DeviceStatus | |||
{ | |||
BlockSerialNumber serialNumber; | |||
@@ -147,9 +160,93 @@ struct DeviceConnection | |||
ConnectorPort port1, port2; | |||
}; | |||
struct DeviceVersion | |||
{ | |||
TopologyIndex index; | |||
VersionNumber version; | |||
}; | |||
static constexpr uint8 maxBlocksInTopologyPacket = 6; | |||
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. */ | |||
struct TouchPosition | |||
@@ -197,6 +294,23 @@ enum DeviceCommands | |||
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 */ | |||
using ControlButtonID = IntegerWithBitSize<12>; | |||
@@ -259,6 +373,10 @@ enum BitSizes | |||
firmwareUpdateACK = MessageType::bits + FirmwareUpdateACKCode::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", | |||
"getAngleFromMaster/i", | |||
"setAutoRotate/vb", | |||
"getClusterIndex/i", | |||
"getClusterWidth/i", | |||
"getClusterHeight/i", | |||
"getClusterXpos/i", | |||
"getClusterYpos/i", | |||
"getNumBlocksInCurrentCluster/i", | |||
"getBlockIdForBlockInCluster/ii", | |||
"isMasterInCurrentCluster/b", | |||
"makeARGB/iiiii", | |||
"blendARGB/iii", | |||
"fillPixel/viii", | |||
@@ -317,6 +439,7 @@ static constexpr const char* ledProgramLittleFootFunctions[] = | |||
"drawNumber/viiii", | |||
"clearDisplay/v", | |||
"clearDisplay/vi", | |||
"displayBatteryLevel/v", | |||
"sendMIDI/vi", | |||
"sendMIDI/vii", | |||
"sendMIDI/viii", | |||
@@ -331,5 +454,19 @@ static constexpr const char* ledProgramLittleFootFunctions[] = | |||
"deassignChannel/vii", | |||
"getControlChannel/i", | |||
"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 | |||
}; |
@@ -224,6 +224,39 @@ struct HostPacketBuilder | |||
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: | |||
Packed7BitArrayBuilder<maxPacketBytes> data; | |||
@@ -64,6 +64,7 @@ struct HostPacketDecoder | |||
case MessageFromDevice::deviceTopology: return handleTopology (handler, reader, true); | |||
case MessageFromDevice::deviceTopologyExtend: return handleTopology (handler, reader, false); | |||
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::touchMove: return handleTouch (handler, reader, deviceIndex, packetTimestamp, false, false); | |||
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::packetACK: return handlePacketACK (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); | |||
default: | |||
@@ -112,6 +114,8 @@ struct HostPacketDecoder | |||
if (newTopology) | |||
handler.beginTopology ((int) numDevices, (int) numConnections); | |||
else | |||
handler.extendTopology ((int) numDevices, (int) numConnections); | |||
for (uint32 i = 0; i < numDevices; ++i) | |||
handleTopologyDevice (handler, reader); | |||
@@ -166,6 +170,20 @@ struct HostPacketDecoder | |||
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, | |||
PacketTimestamp packetTimestamp, bool isStart, bool isEnd) | |||
{ | |||
@@ -273,6 +291,38 @@ struct HostPacketDecoder | |||
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) | |||
{ | |||
String message; | |||
@@ -309,6 +309,7 @@ struct PhysicalTopologySource::Internal | |||
Block::UID uid; | |||
BlocksProtocol::TopologyIndex index; | |||
BlocksProtocol::BlockSerialNumber serial; | |||
BlocksProtocol::VersionNumber version; | |||
bool isMaster; | |||
}; | |||
@@ -324,9 +325,12 @@ struct PhysicalTopologySource::Internal | |||
for (auto& device : devices) | |||
{ | |||
BlocksProtocol::VersionNumber version; | |||
result.add ({ getBlockUIDFromSerialNumber (device.serialNumber), | |||
device.index, | |||
device.serialNumber, | |||
version, | |||
isFirst }); | |||
isFirst = false; | |||
@@ -353,6 +357,23 @@ struct PhysicalTopologySource::Internal | |||
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, | |||
private juce::Timer | |||
@@ -468,6 +489,12 @@ struct PhysicalTopologySource::Internal | |||
incomingTopologyConnections.ensureStorageAllocated (numConnections); | |||
} | |||
void extendTopology (int numDevices, int numConnections) | |||
{ | |||
incomingTopologyDevices.ensureStorageAllocated (incomingTopologyDevices.size() + numDevices); | |||
incomingTopologyConnections.ensureStorageAllocated (incomingTopologyConnections.size() + numConnections); | |||
} | |||
void handleTopologyDevice (BlocksProtocol::DeviceStatus status) | |||
{ | |||
incomingTopologyDevices.add (status); | |||
@@ -490,6 +517,19 @@ struct PhysicalTopologySource::Internal | |||
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, | |||
BlocksProtocol::ControlButtonID buttonID, bool isDown) | |||
{ | |||
@@ -555,6 +595,24 @@ struct PhysicalTopologySource::Internal | |||
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) | |||
{ | |||
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||
@@ -828,12 +886,16 @@ struct PhysicalTopologySource::Internal | |||
currentTopology.blocks.remove (i); | |||
} | |||
else if (versionNumberAddedToBlock (newDeviceInfo, block->uid, block->versionNumber)) | |||
{ | |||
setVersionNumberForBlock (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.isMaster)); | |||
currentTopology.blocks.add (new BlockImplementation (info.serial, *this, info.version, info.isMaster)); | |||
currentTopology.connections.swapWith (newDeviceConnections); | |||
} | |||
@@ -864,6 +926,48 @@ struct PhysicalTopologySource::Internal | |||
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 | |||
{ | |||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | |||
@@ -930,9 +1034,9 @@ struct PhysicalTopologySource::Internal | |||
//============================================================================== | |||
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) | |||
return index; | |||
@@ -944,7 +1048,7 @@ struct PhysicalTopologySource::Internal | |||
template <typename PacketBuilder> | |||
bool sendMessageToDevice (Block::UID deviceID, const PacketBuilder& builder) const | |||
{ | |||
for (auto c : connectedDeviceGroups) | |||
for (auto* c : connectedDeviceGroups) | |||
if (c->getIndexFromDeviceID (deviceID) >= 0) | |||
return c->sendMessageToDevice (builder); | |||
@@ -953,7 +1057,7 @@ struct PhysicalTopologySource::Internal | |||
static Detector* getFrom (Block& b) noexcept | |||
{ | |||
if (auto bi = BlockImplementation::getFrom (b)) | |||
if (auto* bi = BlockImplementation::getFrom (b)) | |||
return &(bi->detector); | |||
jassertfalse; | |||
@@ -1050,9 +1154,13 @@ struct PhysicalTopologySource::Internal | |||
private MIDIDeviceConnection::Listener, | |||
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); | |||
@@ -1060,21 +1168,24 @@ struct PhysicalTopologySource::Internal | |||
touchSurface.reset (new TouchSurfaceImplementation (*this)); | |||
int i = 0; | |||
for (auto b : modelData.buttons) | |||
for (auto&& b : modelData.buttons) | |||
controlButtons.add (new ControlButtonImplementation (*this, i++, b)); | |||
if (modelData.lightGridWidth > 0 && modelData.lightGridHeight > 0) | |||
ledGrid.reset (new LEDGridImplementation (*this)); | |||
for (auto s : modelData.statusLEDs) | |||
for (auto&& s : modelData.statusLEDs) | |||
statusLights.add (new StatusLightImplementation (*this, s)); | |||
if (modelData.numLEDRowLEDs > 0) | |||
ledRow.reset (new LEDRowImplementation (*this)); | |||
listenerToMidiConnection = dynamic_cast<MIDIDeviceConnection*> (detector.getDeviceConnectionFor (*this)); | |||
if (listenerToMidiConnection != nullptr) | |||
listenerToMidiConnection->addListener (this); | |||
config.setDeviceComms (listenerToMidiConnection); | |||
} | |||
~BlockImplementation() | |||
@@ -1189,6 +1300,7 @@ struct PhysicalTopologySource::Internal | |||
return type == Block::Type::liveBlock | |||
|| type == Block::Type::loopBlock | |||
|| type == Block::Type::touchBlock | |||
|| type == Block::Type::developerControlBlock; | |||
} | |||
@@ -1242,7 +1354,7 @@ struct PhysicalTopologySource::Internal | |||
if (compiler.getCompiledProgram().getTotalSpaceNeeded() > getMemorySize()) | |||
return Result::fail ("Program too large!"); | |||
size_t size = (size_t) compiler.compiledObjectCode.size(); | |||
auto size = (size_t) compiler.compiledObjectCode.size(); | |||
programSize = (uint32) size; | |||
remoteHeap.resetDataRangeToUnknown (0, remoteHeap.blockSize); | |||
@@ -1252,6 +1364,11 @@ struct PhysicalTopologySource::Internal | |||
remoteHeap.resetDataRangeToUnknown (0, (uint32) size); | |||
remoteHeap.setBytes (0, compiler.compiledObjectCode.begin(), size); | |||
remoteHeap.sendChanges (*this, true); | |||
this->resetConfigListActiveStatus(); | |||
if (auto changeCallback = this->configChangedCallback) | |||
changeCallback (*this, {}, this->getMaxConfigIndex()); | |||
} | |||
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() | |||
{ | |||
lastMessageReceiveTime = juce::Time::getCurrentTime(); | |||
@@ -1423,6 +1550,71 @@ struct PhysicalTopologySource::Internal | |||
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; | |||
juce::OwnedArray<ControlButton> controlButtons; | |||
@@ -1448,11 +1640,14 @@ struct PhysicalTopologySource::Internal | |||
Detector& detector; | |||
juce::Time lastMessageSendTime, lastMessageReceiveTime; | |||
BlockConfigManager config; | |||
std::function<void(Block&, const ConfigMetaData&, uint32)> configChangedCallback; | |||
private: | |||
std::unique_ptr<Program> program; | |||
uint32 programSize = 0; | |||
std::function<void (uint8)> firmwarePacketAckCallback; | |||
std::function<void(uint8)> firmwarePacketAckCallback; | |||
uint32 resetMessagesSent = 0; | |||
bool isStillConnected = true; | |||