fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).

This commit is contained in:
Alex Birch
2018-06-17 13:34:53 +01:00
parent a2be47c887
commit dff4d13a1d
1563 changed files with 601601 additions and 3466 deletions

View File

@ -0,0 +1,9 @@
This is a JUCE module which discovers any connected BLOCKS devices and provides low-level
access to their physical properties.
Its job is to provide:
- topology and status information about the list of connected devices
- callbacks for touch events, control button presses and rotary dial movements
- simple control over individual LEDs
- optional rule-based virtual device aggregation functionality

View File

@ -0,0 +1,110 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
static Block::UID getBlockUIDFromSerialNumber (const uint8* serial) noexcept
{
Block::UID n = {};
for (int i = 0; i < (int) sizeof (BlocksProtocol::BlockSerialNumber); ++i)
n += n * 127 + serial[i];
return n;
}
static Block::UID getBlockUIDFromSerialNumber (const BlocksProtocol::BlockSerialNumber& serial) noexcept
{
return getBlockUIDFromSerialNumber (serial.serial);
}
static Block::UID getBlockUIDFromSerialNumber (const juce::String& serial) noexcept
{
if (serial.length() < (int) sizeof (BlocksProtocol::BlockSerialNumber))
{
jassertfalse;
return getBlockUIDFromSerialNumber (serial.paddedRight ('0', sizeof (BlocksProtocol::BlockSerialNumber)));
}
return getBlockUIDFromSerialNumber ((const uint8*) serial.toRawUTF8());
}
Block::Block (const juce::String& serial)
: serialNumber (serial), uid (getBlockUIDFromSerialNumber (serial))
{
}
Block::Block (const juce::String& serial, const juce::String& version, const juce::String& blockName)
: serialNumber (serial), versionNumber (version), name (blockName), uid (getBlockUIDFromSerialNumber (serial))
{
}
Block::~Block() {}
void Block::addDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.add (listener); }
void Block::removeDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.remove (listener); }
void Block::addProgramEventListener (ProgramEventListener* listener) { programEventListeners.add (listener); }
void Block::removeProgramEventListener (ProgramEventListener* listener) { programEventListeners.remove (listener); }
bool Block::ConnectionPort::operator== (const ConnectionPort& other) const noexcept { return edge == other.edge && index == other.index; }
bool Block::ConnectionPort::operator!= (const ConnectionPort& other) const noexcept { return ! operator== (other); }
Block::Program::Program (Block& b) : block (b) {}
Block::Program::~Program() {}
//==============================================================================
TouchSurface::TouchSurface (Block& b) : block (b) {}
TouchSurface::~TouchSurface() {}
TouchSurface::Listener::~Listener() {}
void TouchSurface::addListener (Listener* l) { listeners.add (l); }
void TouchSurface::removeListener (Listener* l) { listeners.remove (l); }
//==============================================================================
ControlButton::ControlButton (Block& b) : block (b) {}
ControlButton::~ControlButton() {}
ControlButton::Listener::~Listener() {}
void ControlButton::addListener (Listener* l) { listeners.add (l); }
void ControlButton::removeListener (Listener* l) { listeners.remove (l); }
//==============================================================================
LEDGrid::LEDGrid (Block& b) : block (b) {}
LEDGrid::~LEDGrid() {}
LEDGrid::Renderer::~Renderer() {}
//==============================================================================
LEDRow::LEDRow (Block& b) : block (b) {}
LEDRow::~LEDRow() {}
//==============================================================================
StatusLight::StatusLight (Block& b) : block (b) {}
StatusLight::~StatusLight() {}
} // namespace juce

View File

@ -0,0 +1,444 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
Represents an individual BLOCKS device.
@tags{Blocks}
*/
class Block : public juce::ReferenceCountedObject
{
public:
//==============================================================================
/** Destructor. */
virtual ~Block();
/** The different block types.
@see Block::getType()
*/
enum Type
{
unknown = 0, /**< Unknown block type. */
lightPadBlock, /**< Lightpad block type. */
liveBlock, /**< Live control block type. */
loopBlock, /**< Loop control block type. */
developerControlBlock, /**< Developer control block type. */
touchBlock, /**< Touch control block type. */
seaboardBlock /**< Seaboard block type. */
};
/** The Block class is reference-counted, so always use a Block::Ptr when
you are keeping references to them.
*/
using Ptr = juce::ReferenceCountedObjectPtr<Block>;
/** The Block class is reference-counted, so Block::Array is useful when
you are storing lists of them.
*/
using Array = juce::ReferenceCountedArray<Block>;
/** The Block's serial number. */
const juce::String serialNumber;
/** The Block's version number */
juce::String versionNumber;
/** The Block's name */
juce::String name;
/** This type is used for the unique block identifier. */
using UID = uint64;
/** This Block's UID.
This will be globally unique, and remains constant for a particular device.
*/
const UID uid;
//==============================================================================
/** Two blocks are considered equal if they have the same UID. */
bool operator== (const Block& other) const noexcept { return uid == other.uid; }
/** Two blocks are considered equal if they have the same UID. */
bool operator!= (const Block& other) const noexcept { return uid != other.uid; }
//==============================================================================
/** Returns the type of this device.
@see Block::Type
*/
virtual Type getType() const = 0;
/** Returns a human-readable description of this device type. */
virtual juce::String getDeviceDescription() const = 0;
/** Returns the battery level in the range 0.0 to 1.0. */
virtual float getBatteryLevel() const = 0;
/** Returns true if the battery is charging. */
virtual bool isBatteryCharging() const = 0;
//==============================================================================
/** Returns true if this block is connected and active. */
virtual bool isConnected() const = 0;
/** Returns true if this block is directly connected to the application,
as opposed to only being connected to a different block via a connection port.
@see ConnectionPort
*/
virtual bool isMasterBlock() const = 0;
//==============================================================================
/** Returns the width of the device in logical device units. */
virtual int getWidth() const = 0;
/** Returns the height of the device in logical device units. */
virtual int getHeight() const = 0;
/** Returns true if the device is a physical hardware block (i.e. not a virtual block). */
virtual bool isHardwareBlock() const = 0;
/** Returns the length of one logical device unit as physical millimeters. */
virtual float getMillimetersPerUnit() const = 0;
//==============================================================================
/** If this block has a grid of LEDs, this will return an object to control it.
Note that the pointer that is returned belongs to this object, and the caller must
neither delete it or use it after the lifetime of this Block object has finished.
If there are no LEDs, then this method will return nullptr.
*/
virtual LEDGrid* getLEDGrid() const = 0;
/** If this block has a row of LEDs, this will return an object to control it.
Note that the pointer that is returned belongs to this object, and the caller must
neither delete it or use it after the lifetime of this Block object has finished.
If there are no LEDs, then this method will return nullptr.
*/
virtual LEDRow* getLEDRow() const = 0;
/** If this block has any status LEDs, this will return an array of objects to control them.
Note that the objects in the array belong to this Block object, and the caller must
neither delete them or use them after the lifetime of this Block object has finished.
*/
virtual juce::Array<StatusLight*> getStatusLights() const = 0;
/** If this block has a pressure-sensitive surface, this will return an object to
access its data.
Note that the pointer returned does is owned by this object, and the caller must
neither delete it or use it after the lifetime of this Block object has finished.
If the device is not touch-sensitive, then this method will return nullptr.
*/
virtual TouchSurface* getTouchSurface() const = 0;
/** If this block has any control buttons, this will return an array of objects to control them.
Note that the objects in the array belong to this Block object, and the caller must
neither delete them or use them after the lifetime of this Block object has finished.
*/
virtual juce::Array<ControlButton*> getButtons() const = 0;
//==============================================================================
/** This returns true if the block supports generating graphics by drawing into a JUCE
Graphics context. This should only be true for virtual on-screen blocks; hardware
blocks will instead use the LED Grid for visuals.
*/
virtual bool supportsGraphics() const = 0;
//==============================================================================
/** These are the edge-connectors that a device may have. */
struct ConnectionPort
{
enum class DeviceEdge
{
north,
south,
east,
west
};
/** The side of the device on which this port is located. */
DeviceEdge edge;
/** The index of this port along the device edge.
For north and south edges, index 0 is the left-most port.
For east and west edges, index 0 is the top-most port.
*/
int index;
bool operator== (const ConnectionPort&) const noexcept;
bool operator!= (const ConnectionPort&) const noexcept;
};
/** Returns a list of the connectors that this device has. */
virtual juce::Array<ConnectionPort> getPorts() const = 0;
//==============================================================================
/** A program that can be loaded onto a block. */
struct Program
{
/** Creates a Program for the corresponding LEDGrid. */
Program (Block&);
/** Destructor. */
virtual ~Program();
/** Returns the LittleFoot program to execute on the BLOCKS device. */
virtual juce::String getLittleFootProgram() = 0;
Block& block;
};
/** Sets the Program to run on this block.
The supplied Program's lifetime will be managed by this class, so do not
use the Program in other places in your code.
*/
virtual juce::Result setProgram (Program*) = 0;
/** Returns a pointer to the currently loaded program. */
virtual Program* getProgram() const = 0;
//==============================================================================
/** A message that can be sent to the currently loaded program. */
struct ProgramEventMessage
{
int32 values[3];
};
/** Sends a message to the currently loaded program.
To receive the message the program must provide a littlefoot function called
handleMessage with the following form:
@code
void handleMessage (int param1, int param2, int param3)
{
// Do something with the two integer parameters that the app has sent...
}
@endcode
*/
virtual void sendProgramEvent (const ProgramEventMessage&) = 0;
/** Interface for objects listening to custom program events. */
struct ProgramEventListener
{
virtual ~ProgramEventListener() {}
/** Called whenever a message from a block is received. */
virtual void handleProgramEvent (Block& source, const ProgramEventMessage&) = 0;
};
/** Adds a new listener for custom program events from the block. */
virtual void addProgramEventListener (ProgramEventListener*);
/** Removes a listener for custom program events from the block. */
virtual void removeProgramEventListener (ProgramEventListener*);
//==============================================================================
/** Returns the size of the data block that setDataByte and other functions can write to. */
virtual uint32 getMemorySize() = 0;
/** Sets a single byte on the littlefoot heap. */
virtual void setDataByte (size_t offset, uint8 value) = 0;
/** Sets multiple bytes on the littlefoot heap. */
virtual void setDataBytes (size_t offset, const void* data, size_t num) = 0;
/** Sets multiple bits on the littlefoot heap. */
virtual void setDataBits (uint32 startBit, uint32 numBits, uint32 value) = 0;
/** Gets a byte from the littlefoot heap. */
virtual uint8 getDataByte (size_t offset) = 0;
/** 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;
/** Perform factory reset on Block */
virtual void factoryReset() = 0;
/** Reset this Block */
virtual void blockReset() = 0;
/** Set Block name */
virtual bool setName (const juce::String& name) = 0;
//==============================================================================
/** Allows the user to provide a function that will receive log messages from the block. */
virtual void setLogger (std::function<void(const String&)> loggingCallback) = 0;
/** Sends a firmware update packet to a block, and waits for a reply. Returns an error code. */
virtual bool sendFirmwareUpdatePacket (const uint8* data, uint8 size,
std::function<void (uint8, uint32)> 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
{
virtual ~DataInputPortListener() {}
/** Called whenever a message from a block is received. */
virtual void handleIncomingDataPortMessage (Block& source, const void* messageData, size_t messageSize) = 0;
};
/** Adds a new listener for the data input port. */
virtual void addDataInputPortListener (DataInputPortListener*);
/** Removes a listener for the data input port. */
virtual void removeDataInputPortListener (DataInputPortListener*);
/** Sends a message to the block. */
virtual void sendMessage (const void* messageData, size_t messageSize) = 0;
//==============================================================================
/** This type is used for timestamping events. It represents a number of milliseconds since the block
device was booted.
*/
using Timestamp = uint32;
protected:
//==============================================================================
Block (const juce::String& serialNumberToUse);
Block (const juce::String& serial, const juce::String& version, const juce::String& name);
juce::ListenerList<DataInputPortListener> dataInputPortListeners;
juce::ListenerList<ProgramEventListener> programEventListeners;
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Block)
};
} // namespace juce

View File

@ -0,0 +1,358 @@
/*
==============================================================================
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.
==============================================================================
*/
namespace juce
{
// This file provides interfaces for managing the internal configuration of Blocks
// and synchronises with the connected Block
using namespace BlocksProtocol;
/** Manages the configuration of blocks
@tags{Blocks}
*/
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 = 61;
/** Structure describing a configuration */
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, 2, 1, 16, false, "MIDI Start Channel", ConfigType::integer, {}, "MIDI Settings" },
{ midiEndChannel, 16, 1, 16, false, "MIDI End Channel", ConfigType::integer, {}, "MIDI Settings" },
{ midiUseMPE, 1, 0, 1, false, "Use MPE", ConfigType::boolean, {}, "MIDI Settings" },
{ pitchBendRange, 48, 1, 96, false, "Pitch Bend Range", ConfigType::integer, {}, "MIDI Settings" },
{ octave, 0, -4, 6, false, "Octave", ConfigType::integer, {}, "Pitch" },
{ transpose, 0, -11, 11, false, "Transpose", ConfigType::integer, {}, "Pitch" },
{ slideCC, 74, 0, 127, false, "Slide CC", ConfigType::integer, {}, "Play mode" },
{ slideMode, 0, 0, 2, false, "Slide Mode", ConfigType::options, { "Absolute",
"Relative Unipolar",
"Relative Bipolar" }, "Play mode" },
{ velocitySensitivity, 100, 0, 127, false, "Strike Sensitivity", ConfigType::integer, {}, "5D Touch" },
{ glideSensitivity, 100, 0, 127, false, "Glide Sensitivity", ConfigType::integer, {}, "5D Touch" },
{ slideSensitivity, 100, 0, 127, false, "Slide Sensitivity", ConfigType::integer, {}, "5D Touch" },
{ 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, {}, "Play mode" },
{ glideLock, 0, 0, 127, false, "Glide Rate", ConfigType::integer, {}, "Play mode" },
{ glideLockEnable, 0, 0, 1, false, "Glide Lock Enable", ConfigType::boolean, {}, "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" }, "Play mode" },
{ yTrackingMode, 1, 0, 4, false, "Slide Tracking Mode", ConfigType::options, { "Multi-Channel",
"Last Played",
"Highest",
"Lowest",
"Disabled" }, "Play mode" },
{ zTrackingMode, 1, 0, 4, false, "Pressure Tracking Mode", ConfigType::options, { "Multi-Channel",
"Last Played",
"Highest",
"Lowest",
"Disabled",
"Hardest" }, "Play mode" },
{ gammaCorrection, 0, 0, 1, false, "Gamma Correction", ConfigType::boolean, {}, {} },
// 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 (auto& 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;
};
} // namespace juce

View File

@ -0,0 +1,141 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
Represents a button on a block device.
@tags{Blocks}
*/
class ControlButton
{
public:
ControlButton (Block&);
/** Destructor. */
virtual ~ControlButton();
/** These are all known types of control buttons.
You can find out which buttons a device has by calling getButtons(),
and can get the name of a button type with getButtonName().
*/
enum ButtonFunction
{
mode, /**< The side button on a lightpad block and the first button on a live/loop block. */
volume, /**< The volume button on a live/loop block. */
// common to all types of block
up, /**< The up button on a control block. */
down, /**< The down button on a control block. */
// live block buttons
scale, /**< The scale button on a live block. */
chord, /**< The chord button on a live block. */
arp, /**< The arp button on a live block. */
sustain, /**< The sustain button on a live block. */
octave, /**< The octave button on a live block. */
love, /**< The love button on a live block. */
// loop block buttons
click, /**< The click button on a loop block. */
snap, /**< The snap button on a loop block. */
back, /**< The back button on a loop block. */
playOrPause, /**< The play or pause button on a loop block. */
record, /**< The record button on a loop block. */
learn, /**< The learn button on a loop block. */
// developer block buttons
button0, /**< Button 0 on a developer block. */
button1, /**< Button 1 on a developer block. */
button2, /**< Button 2 on a developer block. */
button3, /**< Button 3 on a developer block. */
button4, /**< Button 4 on a developer block. */
button5, /**< Button 5 on a developer block. */
button6, /**< Button 6 on a developer block. */
button7, /**< Button 7 on a developer block. */
// touch block buttons
velocitySensitivity, /**< The velocity sensitivity button on a touch block. */
glideSensitivity, /**< The glide sensitivity button on a touch block. */
slideSensitivity, /**< The slide sensitivity button on a touch block. */
pressSensitivity, /**< The press sensitivity button on a touch block. */
liftSensitivity, /**< The lift sensitivity button on a touch block. */
fixedVelocity, /**< The fixed velocity button on a touch block. */
glideLock, /**< The glide lock button on a touch block. */
pianoMode /**< The piano mode button on a touch block. */
};
/** Returns the button's type. */
virtual ButtonFunction getType() const = 0;
/** Returns the button's description. */
virtual juce::String getName() const = 0;
/** Returns the position of this button on the device, in device units.
For buttons that are on the side of the device, this may want to return a value that
is beyond the phyiscal block size.
*/
virtual float getPositionX() const = 0;
/** Returns the position of this button on the device, in device units.
For buttons that are on the side of the device, this may want to return a value that
is beyond the phyiscal block size.
*/
virtual float getPositionY() const = 0;
/** Returns true if this button has a controllable light. */
virtual bool hasLight() const = 0;
/** If the button can light-up, this sets its colour. */
virtual bool setLightColour (LEDColour newColour) = 0;
/** A listener that can be attached to a ControlButton object so that it
gets called when the button is pushed or released.
*/
struct Listener
{
virtual ~Listener();
/** Called when the button is pressed. */
virtual void buttonPressed (ControlButton&, Block::Timestamp) = 0;
/** Called when the button is released. */
virtual void buttonReleased (ControlButton&, Block::Timestamp) = 0;
};
/** Adds a listener to the control button. */
void addListener (Listener*);
/** Removes a listener from the control button. */
void removeListener (Listener*);
/** The control block that this button belongs to. */
Block& block;
protected:
juce::ListenerList<Listener> listeners;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlButton)
};
} // namespace juce

View File

@ -0,0 +1,105 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
A simple ARGB colour class for setting LEDs.
@tags{Blocks}
*/
struct LEDColour
{
LEDColour() noexcept = default;
LEDColour (const LEDColour&) noexcept = default;
LEDColour& operator= (const LEDColour&) noexcept = default;
LEDColour (uint32 argbColour) noexcept : argb (argbColour) {}
template <typename ColourType>
LEDColour (const ColourType& colour) : LEDColour (colour.getARGB()) {}
uint8 getAlpha() const noexcept { return (uint8) (argb >> 24); }
uint8 getRed() const noexcept { return (uint8) (argb >> 16); }
uint8 getGreen() const noexcept { return (uint8) (argb >> 8); }
uint8 getBlue() const noexcept { return (uint8) argb; }
uint32 getARGB() const noexcept { return argb; }
uint32 argb = 0;
};
//==============================================================================
/**
Represents a 2D grid of LEDs on a block device.
@tags{Blocks}
*/
class LEDGrid
{
public:
LEDGrid (Block&);
/** Destructor. */
virtual ~LEDGrid();
//==============================================================================
/** Returns the number of columns in the LED grid. */
virtual int getNumColumns() const = 0;
/** Returns the number of rows in the LED grid. */
virtual int getNumRows() const = 0;
//==============================================================================
/** An interface to use for LEDGrid rendering. */
struct Renderer : public juce::ReferenceCountedObject
{
virtual ~Renderer();
virtual void renderLEDGrid (LEDGrid&) = 0;
/** The Renderer class is reference-counted, so always use a Renderer::Ptr when
you are keeping references to them.
*/
using Ptr = juce::ReferenceCountedObjectPtr<Renderer>;
};
/** Set the visualiser that will create visuals for this block (nullptr for none).
Note that the LEDGrid will NOT take ownership of this object, so the caller
must ensure that it doesn't get deleted while in use here.
*/
void setRenderer (Renderer::Ptr newRenderer) noexcept { renderer = newRenderer; }
/** Returns the visualiser currently attached to this block (nullptr for none). */
Renderer::Ptr getRenderer() const noexcept { return renderer; }
/** The device that this LEDGrid belongs to. */
Block& block;
private:
Renderer::Ptr renderer;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGrid)
};
} // namespace juce

View File

@ -0,0 +1,64 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
Represents an LED strip on a device.
@tags{Blocks}
*/
class LEDRow
{
public:
LEDRow (Block&);
/** Destructor. */
virtual ~LEDRow();
//==============================================================================
/** Return the number of LEDs in the row. */
virtual int getNumLEDs() const = 0;
/** Sets the colour of the corresponding LED. */
virtual void setLEDColour (int index, LEDColour newColour) = 0;
/** Overlays all LEDs with a single colour.
Whilst the overlay is set subsequent calls to setLEDColour will happen
*behind* the overlay, and will be invisible to the user until the
overlay is removed.
*/
virtual void setOverlayColour (LEDColour newColour) = 0;
/* Removes an overlay colour. */
virtual void resetOverlayColour() = 0;
/** The device that these lights belong to. */
Block& block;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDRow)
};
} // namespace juce

View File

@ -0,0 +1,53 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
Represents a status LED on a device.
@tags{Blocks}
*/
class StatusLight
{
public:
StatusLight (Block&);
/** Destructor. */
virtual ~StatusLight();
//==============================================================================
/** Returns a name to describe this light. */
virtual juce::String getName() const = 0;
/** Changes the light's colour. */
virtual bool setColour (LEDColour newColour) = 0;
/** The device that this LED belongs to. */
Block& block;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StatusLight)
};
} // namespace juce

View File

@ -0,0 +1,152 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
Utility class to hold a list of TouchSurface::Touch objects with different
indices and blockUIDs, where each touch has a mapping to some kind of
user-supplied data value.
The Type template is a user-defined type of object that will be stored for
each touch element. The type must be default-constructable and copyable.
@tags{Blocks}
*/
template <typename Type>
class TouchList
{
public:
/** Creates an empty touch list. */
TouchList() {}
/** Destructor. */
~TouchList() {}
/** Returns the number of entries in the touch list. */
int size() const noexcept { return touches.size(); }
/** Returns the user data object that corresponds to the given touch.
This will also update the stored state of the TouchEntry::touch value
for this touch index.
*/
Type& getValue (const TouchSurface::Touch& touch)
{
auto* t = find (touch);
if (t == nullptr)
{
touches.add ({ touch, {} });
return touches.getReference (touches.size() - 1).value;
}
else
{
t->touch = touch;
return t->value;
}
}
/** Returns true if a touch is already in the list. */
bool contains (const TouchSurface::Touch& touch) const noexcept
{
return find (touch) != nullptr;
}
/** Updates the entry for the given touch, copying in the new state.
If no entry with the same index and blockUID exists then a new entry is
created. If given a touch which is a touch-end, this will *remove* any
corresponding entries from the list.
*/
void updateTouch (const TouchSurface::Touch& touch)
{
if (touch.isTouchEnd)
{
for (int i = touches.size(); --i >= 0;)
if (matches (touches.getReference(i).touch, touch))
touches.remove (i);
jassert (! contains (touch));
}
else
{
auto t = find (touch);
if (t == nullptr)
touches.add ({ touch, {} });
else
t->touch = touch;
}
}
/** Holds the current state of a touch, along with the user-data associated with it. */
struct TouchEntry
{
TouchSurface::Touch touch;
Type value;
};
/** If a touch is in the list, returns a pointer to the TouchEntry.
Otherwise, returns nullptr.
*/
const TouchEntry* find (const TouchSurface::Touch& touch) const noexcept
{
for (auto& t : touches)
if (matches (t.touch, touch))
return &t;
return nullptr;
}
TouchEntry* find (const TouchSurface::Touch& touch) noexcept
{
return const_cast<TouchEntry*> (static_cast<const TouchList&> (*this).find (touch));
}
/** Allows iterator access to the list of touch entries. */
TouchEntry* begin() noexcept { return touches.begin(); }
const TouchEntry* begin() const noexcept { return touches.begin(); }
/** Allows iterator access to the list of touch entries. */
TouchEntry* end() noexcept { return touches.end(); }
const TouchEntry* end() const noexcept { return touches.end(); }
/** Retrieve a reference to particular item in the list of touch entires. */
TouchEntry& operator[] (const int index) { return touches.getReference (index); }
/** Resets all contents, doest not generate any call-backs. */
void clear() noexcept { touches.clear(); }
private:
//==========================================================================
static bool matches (const TouchSurface::Touch& t1,
const TouchSurface::Touch& t2) noexcept
{
return t1.index == t2.index && t1.blockUID == t2.blockUID;
}
juce::Array<TouchEntry> touches;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TouchList)
};
} // namespace juce

View File

@ -0,0 +1,138 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
Represents the touch surface of a BLOCKS device.
@tags{Blocks}
*/
class TouchSurface
{
public:
TouchSurface (Block&);
/** Destructor. */
virtual ~TouchSurface();
//==============================================================================
/** Structure used to describe touch properties */
struct Touch
{
/** A touch index, which will stay constant for each finger as it is tracked. */
int index;
/** The X position of this touch on the device, in logical units starting from 0 (left).
See Block::getWidth() for the maximum X value on the device.
*/
float x;
/** An approximation of the velocity at which the X value is changing, measured in
units/second. This is intended as a useful hint to help with gesture detection, but
may be 0 if the device doesn't provide this data.
*/
float xVelocity;
/** The Y position of this touch on the device, in logical units starting from 0 (top).
See Block::getHeight() to find the maximum Y on the device.
*/
float y;
/** An approximation of the velocity at which the Y value is changing, measured in
units/second. This is intended as a useful hint to help with gesture detection, but
may be 0 if the device doesn't provide this data.
*/
float yVelocity;
/** The current pressure of this touch, in the range 0.0 (no pressure) to 1.0 (very hard). */
float z;
/** The rate at which pressure is currently changing, measured in units/second. This is
intended as a useful hint to help with gesture detection, but may be 0 if the device
doesn't provide this data.
*/
float zVelocity;
/** The timestamp of this event, in milliseconds since the device was booted. */
Block::Timestamp eventTimestamp;
/** True if this is the first event for this finger/index. */
bool isTouchStart;
/** True if this is the final event as this finger/index is lifted off. */
bool isTouchEnd;
/** The ID of the block that generated this touch. */
Block::UID blockUID;
/** The initial X position of the touchStart event corresponding to this finger/index. */
float startX;
/** The initial Y position of the touchStart event corresponding to this finger/index. */
float startY;
};
//==============================================================================
/** Forces a touch-off message for all active touches. */
virtual void cancelAllActiveTouches() noexcept = 0;
/** For the on-screen seaboard view, this will return the number of keys.
For other types of touch-surface, it will return 0. */
virtual int getNumberOfKeywaves() const = 0;
//==============================================================================
/** Receives callbacks when a touch moves or changes pressure. */
struct Listener
{
virtual ~Listener();
virtual void touchChanged (TouchSurface&, const Touch&) = 0;
};
/** Testing feature: this allows you to inject touches onto a touch surface. */
void callListenersTouchChanged (const TouchSurface::Touch& t)
{
listeners.call ([this, &t] (Listener& l) { l.touchChanged (*this, t); });
}
/** Adds a listener to be called when the surface is touched. */
void addListener (Listener*);
/** Removes a previously-registered listener. */
void removeListener (Listener*);
//==============================================================================
/** The block that owns this touch surface. */
Block& block;
protected:
//==============================================================================
juce::ListenerList<Listener> listeners;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TouchSurface)
};
} // namespace juce

View File

@ -0,0 +1,781 @@
/** @defgroup LittleFootFunctions LittleFoot Functions
Functions available in the LittleFoot language
@{
*/
/** Reads and returns the value of a single byte from the heap.
@param byteIndex the index (in bytes) of the byte to read
@returns the value of the byte
*/
int getHeapByte (int byteIndex);
/** Reads 4 bytes from the heap and returns the value as an integer.
@param byteIndex the index (in bytes) of the start of the 4 bytes to read
@returns the value of the 4 bytes as an integer
*/
int getHeapInt (int byteIndex);
/** Reads a sequence of bits from the heap and returns the value as an integer.
@param startBitIndex the index (in bits) of the start of the sequence of bits to read
@param numBits how many bits to read
@returns the value of the sequence of bits as an integer
*/
int getHeapBits (int startBitIndex, int numBits);
/** Writes a single byte to the heap.
@param byteIndex the index (in bytes) of the byte to set
@param newValue the new value to set this byte to
*/
void setHeapByte (int byteIndex, int newValue);
/** Writes 4 bytes to the heap.
@param byteIndex the index (in bytes) of the start of the 4 bytes to set
@param newValue the new value to set the 4 bytes to
*/
void setHeapInt (int byteIndex, int newValue);
/** Returns the smaller of two integer values.
@param a The first parameter
@param b The second parameter
@retval The minimum of a and b
*/
int min (int a, int b);
/** Returns the smaller of two floating point values.
@param a The first parameter
@param b The second parameter
@retval The minimum of a and b
*/
float min (float a, float b);
/** Returns the larger of two integer values.
@param a The first parameter
@param b The second parameter
@retval The maximum of a and b
*/
int max (int a, int b);
/** Returns the larger of two floating point values.
@param a The first parameter
@param b The second parameter
@retval The maximum of a and b
*/
float max (float a, float b);
/** Constrains an integer value to keep it within a given range.
@param lowerLimit the minimum value to return
@param upperLimit the maximum value to return
@param valueToConstrain the value to try to return
@returns the closest value to valueToConstrain which lies between lowerLimit
and upperLimit (inclusive)
*/
int clamp (int lowerLimit, int upperLimit, int valueToConstrain);
/** Constrains a floating point value to keep it within a given range.
@param lowerLimit the minimum value to return
@param upperLimit the maximum value to return
@param valueToConstrain the value to try to return
@returns the closest value to valueToConstrain which lies between lowerLimit
and upperLimit (inclusive)
*/
float clamp (float lowerLimit, float upperLimit, float valueToConstrain);
/** Returns the absolute value of an integer value.
@param arg The argument to compute the absolute value of
@retval either -arg if arg is negative or arg if arg is positive
*/
int abs (int arg);
/** Returns the absolute value of a floating point value.
@param arg The argument to compute the absolute value of
@retval either -arg if arg is negative or arg if arg is positive
*/
float abs (float arg);
/** Remaps a value from a source range to a target range.
@param value the value within the source range to map
@param sourceMin the minimum value of the source range
@param sourceMax the maximum value of the source range
@param destMin the minumum value of the destination range
@param destMax the maximum value of the destination range
@returns the original value mapped to the destination range
*/
float map (float value, float sourceMin, float sourceMax, float destMin, float destMax);
/** Remaps a value from a source range to the range 0 - 1.0.
@param value the value within the source range to map
@param sourceMin the minimum value of the source range
@param sourceMax the maximum value of the source range
@returns the original value mapped to the range 0 - 1.0
*/
float map (float value, float sourceMin, float sourceMax);
/** Performs a modulo operation (can cope with the dividend being negative).
The divisor must be greater than zero.
@returns the result of the modulo operation
*/
int mod (int dividend, int divisor);
/** Returns a random floating-point number.
@returns a random value in the range 0 (inclusive) to 1.0 (exclusive)
*/
float getRandomFloat();
/** Returns a random integer, limited to a given range.
@returns a random integer between 0 (inclusive) and maxValue (exclusive).
*/
int getRandomInt (int maxValue);
/** Returns the number of milliseconds since a fixed event (usually system startup).
@returns a monotonically increasing value which is unaffected by changes to the
system clock. It should be accurate to within a few millisecseconds.
*/
int getMillisecondCounter();
/** Returns the length of time spent in the current function call in milliseconds.
@returns the length of time spent in the current function call in milliseconds.
*/
int getTimeInCurrentFunctionCall();
/** Logs an integer value to the console.
@param data The 32 bit signed integer to log to the topology as an integer
*/
void log (int data);
/** Logs a hexadecimal value to the console.
@param data The 32 bit signed integer to log to the topology as a hexidecimal int
*/
void logHex (int data);
/** Sends a 1-byte short midi message. */
void sendMIDI (int byte0);
/** Sends a 2-byte short midi message. */
void sendMIDI (int byte0, int byte1);
/** Sends a 3-byte short midi message. */
void sendMIDI (int byte0, int byte1, int byte2);
/** Sends a key-down message.
@param channel the midi channel, in the range 0 to 15
@param noteNumber the key number, in the range 0 to 127
@param velocity the velocity, in the range 0 to 127
*/
void sendNoteOn (int channel, int noteNumber, int velocity);
/** Sends a key-up message.
@param channel the midi channel, in the range 0 to 15
@param noteNumber the key number, in the range 0 to 127
@param velocity the velocity, in the range 0 to 127
*/
void sendNoteOff (int channel, int noteNumber, int velocity);
/** Sends an aftertouch message.
@param channel the midi channel, in the range 0 to 15
@param noteNumber the key number, in the range 0 to 127
@param level the amount of aftertouch, in the range 0 to 127
*/
void sendAftertouch (int channel, int noteNumber, int level);
/** Sends a controller message.
@param channel the midi channel, in the range 0 to 15
@param controller the type of controller
@param value the controller value
*/
void sendCC (int channel, int controller, int value);
/** Sends a pitch bend message.
@param channel the midi channel, in the range 0 to 15
@param position the wheel position, in the range 0 to 16383
*/
void sendPitchBend (int channel, int position);
/** Sends a channel-pressure change event.
@param channel the midi channel, in the range 0 to 15
@param pressure the pressure, in the range 0 to 127
*/
void sendChannelPressure (int channel, int pressure);
/** Sets the MIDI channel range.
@param useMPE whether to use MPE mode
@param lowChannel the lowest MIDI channel
@param highChannel the highest MIDI channel
*/
void setChannelRange (bool useMPE, int lowChannel, int highChannel);
/** Assigns a MIDI channel to a note number.
@param noteNumber the note number to assign the channel to
@returns the MIDI channel that has been assigned
*/
int assignChannel (int noteNumber);
/** Deassigns a channel from a note number.
@param noteNumber the note number to deassign
@param channel the MIDI channel
*/
void deassignChannel (int noteNumber, int channel);
/** Returns the channel that is being used for control messages.
@returns the channel that is being used for control messages. (If MPE is enabled then this will be the first channel.)
*/
int getControlChannel();
/** Sets whether duplicate notes should be filtered out when MPE is enabled. */
void useMPEDuplicateFilter (bool active);
/** Use this method to draw the display.
The block will call this approximately 25 times per second.
*/
void repaint();
/** Called when a button is pushed.
@param index the index of the button that was pushed
*/
void handleButtonDown (int index);
/** Called when a button is released.
@param index the index of the button that was released
*/
void handleButtonUp (int index);
/** Called when a control block button is pressed.
@param buttonIndex the index of the button
@note Requires >= 0.2.5 firmware
@note Only valid with a control block
*/
void onControlPress (int buttonIndex);
/** Called when a control block button is released.
@param buttonIndex the index of the button
@note Requires >= 0.2.5 firmware
@note Only valid with a control block
*/
void onControlRelease (int buttonIndex);
/** Called when a touch event starts.
Block units follow the number of DNA connectors on the side of the device.
For instance, a Lightpad Block is 2x2 and a Control Block is 2x1.
@param index the touch index, which will stay constant for each finger as it is tracked
@param x the X position of this touch on the device, in block units starting from 0 (left)
@param y the Y position of this touch on the device, in block units starting from 0 (top)
@param z the current pressure of this touch, in the range 0.0 (no pressure) to 1.0 (very hard)
@param vz the rate at which pressure is currently changing, measured in units/second
*/
void touchStart (int index, float x, float y, float z, float vz);
/** Called when a touch event moves.
Block units follow the number of DNA connectors on the side of the device.
For instance, a Lightpad Block is 2x2 and a Control Block is 2x1.
@param index the touch index, which will stay constant for each finger as it is tracked
@param x the X position of this touch on the device, in block units starting from 0 (left)
@param y the Y position of this touch on the device, in block units starting from 0 (top)
@param z the current pressure of this touch, in the range 0.0 (no pressure) to 1.0 (very hard)
@param vz the rate at which pressure is currently changing, measured in units/second
*/
void touchMove (int index, float x, float y, float z, float vz);
/** Called when a touch event ends.
Block units follow the number of DNA connectors on the side of the device.
For instance, a Lightpad Block is 2x2 and a Control Block is 2x1.
@param index the touch index, which will stay constant for each finger as it is tracked
@param x the X position of this touch on the device, in block units starting from 0 (left)
@param y the Y position of this touch on the device, in block units starting from 0 (top)
@param z the current pressure of this touch, in the range 0.0 (no pressure) to 1.0 (very hard)
@param vz the rate at which pressure is currently changing, measured in units/second
*/
void touchEnd (int index, float x, float y, float z, float vz);
/** Called when a program is loaded onto the block and is about to start. Do any setup here. */
void initialise();
/** Called when a block receives a MIDI message. */
void handleMIDI (int byte0, int byte1, int byte2);
/** Called when a block receives a message.
@see sendMessageToBlock
*/
void handleMessage (int param1, int param2, int param3);
/** Combines a set of 8-bit ARGB values into a 32-bit colour and returns the result.
@return a 32-bit colour
@param alpha The alpha in range 0 - 255 inclusive
@param red The red in range 0 - 255 inclusive
@param green The green in range 0 - 255 inclusive
@param blue The blue in range 0 - 255 inclusive
*/
int makeARGB (int alpha, int red, int green, int blue);
/** Blends the overlaid ARGB colour onto the base one and returns the new colour.
@param baseColour the colour to blend on to
@param overlaidColour The colour to blend in to the baseColour
@returns The blended colour
*/
int blendARGB (int baseColour, int overlaidColour);
/** Displays an animation indicating the current battery level of this block.
A control block will light up its top LEDs indicating battery level and a lightpad
block will draw the battery level on the display.
@note Requires >= 0.2.5 firmware
*/
void displayBatteryLevel();
/** Clears the display and sets all the LEDs to black. */
void clearDisplay();
/** Clears the display and sets all the LEDs to a specified colour.
@param rgb the colour to use (0xff...)
*/
void clearDisplay (int rgb);
/** Sets a pixel to a specified colour with full alpha.
@param rgb the colour to use (0xff...)
@param x the x coordinate of the pixel to fill
@param y the y coordinate of the pixel to fill
*/
void fillPixel (int rgb, int x, int y);
/** Blends the current pixel colour with a specified colour.
@param argb the colour to use
@param x the x coordinate of the pixel to blend
@param y the y coordinate of the pixel to blend
*/
void blendPixel (int argb, int x, int y);
/** Fills a rectangle on the display with a specified colour.
@param rgb the colour to use (0xff...)
@param x the x coordinate of the rectangle to draw
@param y the y coordinate of the rectangle to draw
@param width the width of the rectangle to draw
@param height the height of the rectangle to draw
*/
void fillRect (int rgb, int x, int y, int width, int height);
/** Blends a rectangle on the display with a specified colour.
@param argb the colour to use
@param x the x coordinate of the rectangle to blend
@param y the y coordinate of the rectangle to blend
@param width the width of the rectangle to blend
@param height the height of the rectangle to blend
*/
void blendRect (int argb, int x, int y, int width, int height);
/** Fills a rectangle on the display with four corner colours blended together.
@param colourNW the colour to use in the north west corner of the rectangle
@param colourNE the colour to use in the north east corner of the rectangle
@param colourSE the colour to use in the south east corner of the rectangle
@param colourSW the colour to use in the south west corner of the rectangle
@param x the x coordinate of the rectangle
@param y the y coordinate of the rectangle
@param width the width of the rectangle
@param height the height of the rectangle
*/
void blendGradientRect (int colourNW, int colourNE, int colourSE, int colourSW, int x, int y, int width, int height);
/** Blends a circle on the display with a specified colour.
@param argb the colour to use
@param xCentre the x position of the circle's centre in block units
@param yCentre the y position of the circle's centre in block units
@param radius the radius of the circle in block units
@param fill if true then the circle will be filled, if false the circle will be an outline
@note Requires >= 0.2.5 firmware
*/
void blendCircle (int argb, float xCentre, float yCentre, float radius, bool fill);
/** Draws a number on the display.
@param value the number to draw between 0 and 99
@param colour the colour to use
@param x the x coordinate to use
@param y the y coordinate to use
*/
void drawNumber (int value, int colour, int x, int y);
/** Returns the current firmware version of this block.
@returns The firmware version of the form 0xMJMIRV (where MJ = Major, MI = Minor, RV = Revision)
@note Requires >= 0.2.5 firmware
*/
int getFirmwareVersion();
/** Returns the battery level of this block, between 0 and 1.0.
@returns the battery level of this block, between 0 and 1.0.
@note Requires >= 0.2.5 firmware
*/
float getBatteryLevel();
/** Returns true if this block's battery is charging.
@returns true if this block's battery is charging
@note Requires >= 0.2.5 firmware
*/
bool isBatteryCharging();
/** Sets whether status overlays should be displayed on this block.
@note Requires >= 0.2.5 firmware
*/
void setStatusOverlayActive (bool active);
/** Sets whether power saving mode should be enabled on this block.
@note Requires >= 0.2.5 firmware
*/
void setPowerSavingEnabled (bool enabled);
/** Returns the type of the block with a given ID.
@returns an enum indicating the type of block @see Block::Type
@note Requires >= 0.2.5 firmware
*/
int getBlockTypeForID (int blockID);
/** Sends a message to the block with the specified ID.
This will be processed in the handleMessage() callback.
@param blockID the ID of the block to send this message to
@param param1 the first chunk of data to send
@param param2 the second chunk of data to send
@param param3 the third chunk of data to send
*/
void sendMessageToBlock (int blockID, int param1, int param2, int param3);
/** Sends a message to the host. To receive this the host will need to implement
the Block::ProgramEventListener::handleProgramEvent() method.
@param param1 the first chunk of data to send
@param param2 the second chunk of data to send
@param param3 the third chunk of data to send
*/
void sendMessageToHost (int param1, int param2, int param3);
/** Draws a pressure point with a specified colour and pressure.
@param argb the colour to use
@param touchX the x position of the touch in block units
@param touchY the y position of the touch in block units
@param touchZ the pressure value of the touch
*/
void addPressurePoint (int argb, float touchX, float touchY, float touchZ);
/** Draws the pressure map on the display. */
void drawPressureMap();
/** Fades the pressure map on the display. */
void fadePressureMap();
/** Links a another block to this control block.
@param blockID the ID of the block to link
@note Requires >= 0.2.5 firmware
@note Only valid with a control block
*/
void linkBlockIDtoController (int blockID);
/** Repaints the control block display.
@note Requires >= 0.2.5 firmware
@note Only valid with a control block
*/
void repaintControl();
/** Initialises one of the control block buttons.
@param buttonIndex the index of the button to initialise
@param modeToUse the mode to use for this button @see ControlMode
@param outputType the control type to use @see ControlType
@param val the current value to set this button to
@param min the minimum value for this button
@param max the maximum value for this button
@param index the item index
@param onColourToUse the colour to use when this button is on
@param offColourToUse the colour to use when this button is off
@note Requires >= 0.2.5 firmware
@note Only valid with a control block
*/
void initControl (int buttonIndex, int modeToUse, int outputType, int val, int min, int max,
int index, int onColourToUse, int offColourToUse);
/** Control type for use with initControl
@see initControl
*/
enum ControlType
{
internalConfig = 0,
midiCC,
sysex
};
/** Control mode for use with initControl
@see initControl
*/
enum ControlMode
{
toggle = 0,
togglePulse,
gate,
trigger,
cycle,
incDec,
triState
};
/** Forces a touch event to be handled as seaboard playing.
@param touchIndex the index of the touch event
@note Requires >= 0.2.5 firmware
@note Only valid on a Seaboard
*/
void handleTouchAsSeaboard (int touchIndex);
/** Returns the number of blocks in the current topology.
@note Requires >= 0.2.5 firmware
*/
int getNumBlocksInTopology();
/** Returns the ID of a block at a given index in the topology.
@param index The index of the block to find in the topology
@returns int The id of the block
@note Requires >= 0.2.5 firmware
*/
int getBlockIDForIndex (int index);
/** Returns true if this block is directly connected to the host,
as opposed to only being connected to a different block via a connection port.
@note Requires >= 0.2.5 firmware
*/
bool isMasterBlock();
/** Returns true if this block is connected to the host computer, this can be
directly or through connections to other blocks.
@note Requires >= 0.2.5 firmware
*/
bool isConnectedToHost();
/** Returns the ID of a block connected to a specified port on this block.
@note Requires >= 0.2.5 firmware
*/
int getBlockIDOnPort (int port);
/** Returns the port number that is connected to the master block. Returns 0xFF if there is
no port connected to master.
@returns the port number that is connected to the master block. Returns 0xFF if there is
no port connected to master.
@note Requires >= 0.2.5 firmware
*/
int getPortToMaster();
/** Returns the horizontal distance between this block and the master block in block units.
@note Requires >= 0.2.5 firmware
*/
int getHorizontalDistFromMaster();
/** Returns the vertical distance between this block and the master block in block units.
@note Requires >= 0.2.5 firmware
*/
int getVerticalDistFromMaster();
/** Returns the angle of this block relative to the master block in degrees.
@note Requires >= 0.2.5 firmware
*/
int getAngleFromMaster();
/** Sets whether this block should auto-rotate when its angle relative to the master block changes.
@note Requires >= 0.2.5 firmware
*/
void setAutoRotate (bool enabled);
/** Returns the index of this block in the current cluster.
@note Requires >= 0.2.5 firmware
*/
int getClusterIndex();
/** Returns how many blocks wide the current cluster is.
@returns the width of the cluster (note that a single block will return 1 here)
@note Requires >= 0.2.5 firmware
*/
int getClusterWidth();
/** Returns how many blocks high the current cluster is.
@returns the height of the cluster (note that a single block will return 1 here)
@note Requires >= 0.2.5 firmware
*/
int getClusterHeight();
/** Returns the x index of this block in the current cluster.
@returns int The cluster x position. (0, 0) is considered to be the top left block
@note Requires >= 0.2.5 firmware
*/
int getClusterXpos();
/** Returns the y index of this block in the current cluster.
@returns int The cluster x position. (0, 0) is considered to be the top left block
@note Requires >= 0.2.5 firmware
*/
int getClusterYpos();
/** Returns the number of blocks in the current cluster.
@returns the number of blocks in the current cluster.
@note Requires >= 0.2.5 firmware
*/
int getNumBlocksInCurrentCluster();
/** Returns the block ID for a block in the current cluster.
@param index the cluster index of the block to get the ID of
@note Requires >= 0.2.5 firmware
*/
int getBlockIdForBlockInCluster (int index);
/** Returns true if the master block is in the current cluster.
@note Requires >= 0.2.5 firmware
*/
bool isMasterInCurrentCluster();
/** Returns current value of one of the local config items.
@param item the config item to get (see ConfigItemId enum in juce_BlocksProtocolDefinitions.h)
@note Requires >= 0.2.5 firmware
*/
int getLocalConfig (int item);
/** Sets the current value of one of the local config items.
@param item the config item to set the value of (see ConfigItemId enum in juce_BlocksProtocolDefinitions.h)
@param value the value to set the config to
@note Requires >= 0.2.5 firmware
*/
void setLocalConfig (int item, int value);
/** Sets the local config of a block to the config item of a remote block.
@param longAddress the address of the remote block
@param item the config item (see ConfigItemId enum in juce_BlocksProtocolDefinitions.h)
@note Requires >= 0.2.5 firmware
*/
void requestRemoteConfig (int longAddress, int item);
/** Sets the config of a remote block.
@param longAddress the address of the remote block
@param item the config item (see ConfigItemId enum in juce_BlocksProtocolDefinitions.h)
@param value the value to set the config to
@note Requires >= 0.2.5 firmware
*/
void setRemoteConfig (int longAddress, int item, int value);
/** Sets the range of one of the local config items.
@param item the config item to set the range of (see ConfigItemId enum in juce_BlocksProtocolDefinitions.h)
@param min the minimum value this config item should use
@param max the maximum value this config item should use
@note Requires >= 0.2.5 firmware
*/
void setLocalConfigItemRange (int item, int min, int max);
/** Sets whether a local config item should be active.
@param item the config item to set active (see ConfigItemId enum in juce_BlocksProtocolDefinitions.h)
@param isActive sets whether the config should be active or not
@param saveToFlash if true then this config item will be saved to the flash memory of the block
@note Requires >= 0.2.5 firmware
*/
void setLocalConfigActiveState (int item, bool isActive, bool saveToFlash);
/** @} */

View File

@ -0,0 +1,44 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "juce_blocks_basics.h"
#if ! JUCE_HAS_CONSTEXPR
#ifndef JUCE_DEMO_RUNNER
#error "The juce_blocks_basics module requires a compiler that supports constexpr"
#endif
#else
#include "protocol/juce_BitPackingUtilities.h"
#include "protocol/juce_BlocksProtocolDefinitions.h"
#include "protocol/juce_HostPacketDecoder.h"
#include "protocol/juce_HostPacketBuilder.h"
#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"
#include "visualisers/juce_DrumPadLEDProgram.cpp"
#include "visualisers/juce_BitmapLEDProgram.cpp"
#endif

View File

@ -0,0 +1,90 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this module, and is read by
the Projucer to automatically generate project code that uses it.
For details about the syntax and how to create or use a module, see the
JUCE Module Format.txt file.
BEGIN_JUCE_MODULE_DECLARATION
ID: juce_blocks_basics
vendor: juce
version: 5.3.2
name: Provides low-level control over ROLI BLOCKS devices
description: JUCE wrapper for low-level control over ROLI BLOCKS devices.
website: http://developer.roli.com
license: ISC
dependencies: juce_events juce_audio_devices
END_JUCE_MODULE_DECLARATION
*******************************************************************************/
#pragma once
//==============================================================================
#include <juce_events/juce_events.h>
#include <juce_audio_devices/juce_audio_devices.h>
#if ! JUCE_HAS_CONSTEXPR
#ifndef JUCE_DEMO_RUNNER
#error "The juce_blocks_basics module requires a compiler that supports constexpr"
#endif
#else
namespace juce
{
class TouchSurface;
class LEDGrid;
class LEDRow;
class StatusLight;
class LightRing;
class ControlButton;
}
#include "blocks/juce_Block.h"
#include "blocks/juce_TouchSurface.h"
#include "blocks/juce_LEDGrid.h"
#include "blocks/juce_LEDRow.h"
#include "blocks/juce_ControlButton.h"
#include "blocks/juce_TouchList.h"
#include "blocks/juce_StatusLight.h"
#include "topology/juce_Topology.h"
#include "topology/juce_TopologySource.h"
#include "topology/juce_PhysicalTopologySource.h"
#include "topology/juce_RuleBasedTopologySource.h"
#include "visualisers/juce_DrumPadLEDProgram.h"
#include "visualisers/juce_BitmapLEDProgram.h"
namespace juce
{
#include "littlefoot/juce_LittleFootRunner.h"
#include "littlefoot/juce_LittleFootCompiler.h"
#include "littlefoot/juce_LittleFootRemoteHeap.h"
}
#endif

View File

@ -0,0 +1,111 @@
@verbatim
This is a brief description of the LittleFoot language syntax
Littlefoot basically looks like C, but has no pointers, and the only types are:
- int (32-bit signed integer)
- float (32-bit float)
- bool
The top-level syntax of a program is a list of global variables and global functions. Order
of declaration isn't important, you can use functions and variables that are declared later
in the file without needing to pre-declare anything.
Comments are the same format as C/C++/java/etc
So for example:
// global variables. These are initialised to 0 or false when the program is loaded, and
// you can't currently provide any other initial values
int foo, bar;
int getTheNextNumber()
{
return addTwoNumbers (++foo, 2.0) * 3;
}
float addTwoNumbers (int x, float y)
{
return float (x) + y;
}
The usual control-flow operators are provided, all with C++ style syntax:
if/else
for
while
do...while
continue
break
return
(There isn't currently a switch statement though)
Arithmetic ops are the usual suspects, (with the standard operator precedence):
+, -, *, /, %
||, &&, |, &, ~, ^
++, --, +=, -=, *=, /=, %=, |=, &=, ^=
==, !=, <, >, <=, >=, !
<<, >>, <<=, >>=, >>>
Ternary operator (x ? y : z)
Local variables are declared in C++-style syntax:
void foo()
{
int x = 123;
float y = 12.0, z = 1.0e5;
bool b = y > 20.0;
}
Casts of primitive types are done with function-style syntax, e.g.
int x = int (123.0);
float f = float (getIntegerValue());
The program communicates with the host computer by using a shared area of memory
called the heap which the host can change. There are some built-in functions
available for the program to use to read from the heap:
int getHeapByte (int byteIndex); // reads a single byte from the heap
int getHeapInt (int byteIndex); // reads 4 bytes from the heap as an integer
int getHeapBits (int startBitIndex, int numBits); // reads a sequence of bits from the heap and returns it as an integer
void setHeapByte (int byteIndex, int newValue); // writes a single byte to the heap
void setHeapInt (int byteIndex, int newValue); // writes 4 bytes to the heap
Depending on the context, there will also be some built-in functions that the
program can use to do what it needs to do. Currently in the standard Pad BLOCK program,
you have the following functions available:
int makeARGB (int alpha, int red, int green, int blue); // combines a set of 8-bit ARGB values into a 32-bit colour
int blendARGB (int baseColour, int overlaidColour); // blends the overlaid ARGB colour onto the base one and returns the new colour
void fillPixel (int rgb, int x, int y); // sets a LED colour on the display
void fillRect (int rgb, int x, int y, int width, int height); // fills a rectangle on the display
A BLOCKs program needs to provide a repaint() function which the block will call
at approximately 25Hz to draw the display. For example, here's a simple program that
draws a moving rectangle:
int rectangleX;
void repaint()
{
fillRect (0xff000044, 0, 0, 15, 15); // fill the display with dark blue
fillRect (0xffffffff, rectangleX, 5, 4, 4); // draw a white rectangle
rectangleX = (rectangleX + 1) % 15; // animate our position and make it wrap
}
The host can also send simple event messages to the program, and to receive these you must
provide a function called "handleMessage", e.g.
void handleMessage (int param1, int param2)
{
// do something with the two integer parameters that the app has sent...
}
@endverbatim

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,429 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace littlefoot
{
//==============================================================================
/**
This class manages the synchronisation of a remote block of heap memory used
by a littlefoot program running on a block.
Data in the block can be changed by calling setByte, setBytes, setBits etc, and
these changes will be flushed to the device when sendChanges is called.
@tags{Blocks}
*/
template <typename ImplementationClass>
struct LittleFootRemoteHeap
{
LittleFootRemoteHeap (uint32 blockSizeToUse) noexcept : blockSize (blockSizeToUse)
{
resetDeviceStateToUnknown();
}
void clear() noexcept
{
zeromem (targetData, sizeof (targetData));
invalidateData();
}
void resetDeviceStateToUnknown()
{
invalidateData();
messagesSent.clear();
resetDataRangeToUnknown (0, ImplementationClass::maxBlockSize);
}
void resetDataRangeToUnknown (size_t offset, size_t size) noexcept
{
auto* latestState = getLatestExpectedDataState();
for (size_t i = 0; i < size; ++i)
latestState[offset + i] = unknownByte;
}
void setByte (size_t offset, uint8 value) noexcept
{
if (offset < blockSize)
{
if (targetData[offset] != value)
{
targetData[offset] = value;
needsSyncing = true;
if (programStateKnown && offset < programSize)
programStateKnown = false;
}
}
else
{
jassertfalse;
}
}
void setBytes (size_t offset, const uint8* data, size_t num) noexcept
{
for (size_t i = 0; i < num; ++i)
setByte (offset + i, data[i]);
}
void setBits (uint32 startBit, uint32 numBits, uint32 value) noexcept
{
if (startBit + numBits > 8 * blockSize)
{
jassertfalse;
return;
}
if (readLittleEndianBitsInBuffer (targetData, startBit, numBits) != value)
{
writeLittleEndianBitsInBuffer (targetData, startBit, numBits, value);
invalidateData();
}
}
uint8 getByte (size_t offset) noexcept
{
if (offset < blockSize)
return targetData [offset];
jassertfalse;
return 0;
}
void invalidateData() noexcept
{
needsSyncing = true;
programStateKnown = false;
}
bool isFullySynced() const noexcept
{
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();
for (uint32 i = 0; i < blockSize; ++i)
data[i] = latestState[i];
uint32 packetIndex = messagesSent.isEmpty() ? lastPacketIndexReceived
: messagesSent.getLast()->packetIndex;
packetIndex = (packetIndex + 1) & ImplementationClass::maxPacketCounter;
if (! Diff (data, targetData, blockSize).createChangeMessage (bi, data, messagesSent, packetIndex))
break;
dumpStatus();
}
}
for (auto* m : messagesSent)
{
if (m->dispatchTime >= Time::getCurrentTime() - RelativeTime::milliseconds (250))
break;
m->dispatchTime = Time::getCurrentTime();
bi.sendMessageToDevice (m->packet);
//DBG ("Sending packet " << (int) m->packetIndex << " - " << m->packet.size() << " bytes, device " << bi.getDeviceIndex());
if (getTotalSizeOfMessagesSent() > 200)
break;
}
}
void handleACKFromDevice (ImplementationClass& bi, uint32 packetIndex) noexcept
{
//DBG ("ACK " << (int) packetIndex << " device " << (int) bi.getDeviceIndex());
if (lastPacketIndexReceived != packetIndex)
{
lastPacketIndexReceived = packetIndex;
for (int i = messagesSent.size(); --i >= 0;)
{
auto& m = *messagesSent.getUnchecked(i);
if (m.packetIndex == packetIndex)
{
for (uint32 j = 0; j < blockSize; ++j)
deviceState[j] = m.resultDataState[j];
messagesSent.removeRange (0, i + 1);
dumpStatus();
sendChanges (bi, false);
if (messagesSent.isEmpty())
needsSyncing = false;
return;
}
}
resetDeviceStateToUnknown();
}
}
bool isProgramLoaded() noexcept
{
if (! programStateKnown)
{
programStateKnown = true;
uint8 deviceMemory[ImplementationClass::maxBlockSize];
for (size_t i = 0; i < blockSize; ++i)
deviceMemory[i] = (uint8) deviceState[i];
littlefoot::Program prog (deviceMemory, (uint32) blockSize);
programLoaded = prog.checksumMatches();
programSize = prog.getProgramSize();
}
return programLoaded;
}
const size_t blockSize;
static constexpr uint16 unknownByte = 0x100;
private:
uint16 deviceState[ImplementationClass::maxBlockSize] = { 0 };
uint8 targetData[ImplementationClass::maxBlockSize] = { 0 };
uint32 programSize = 0;
bool needsSyncing = true, programStateKnown = true, programLoaded = false;
uint16* getLatestExpectedDataState() noexcept
{
return messagesSent.isEmpty() ? deviceState
: messagesSent.getLast()->resultDataState;
}
struct ChangeMessage
{
typename ImplementationClass::PacketBuilder packet;
Time dispatchTime;
uint32 packetIndex;
uint16 resultDataState[ImplementationClass::maxBlockSize];
};
OwnedArray<ChangeMessage> messagesSent;
uint32 lastPacketIndexReceived = 0;
int getTotalSizeOfMessagesSent() const noexcept
{
int total = 0;
for (auto* m : messagesSent)
if (m->dispatchTime != Time())
total += m->packet.size();
return total;
}
void dumpStatus()
{
#if DUMP_LITTLEFOOT_HEAP_STATUS
int differences = 0;
constexpr int diffLen = 50;
char areas[diffLen + 1] = { 0 };
for (int i = 0; i < diffLen; ++i)
areas[i] = '.';
for (int i = 0; i < (int) blockSize; ++i)
{
if (targetData[i] != deviceState[i])
{
++differences;
areas[i * diffLen / (int) blockSize] = 'X';
}
}
double proportionOK = ((int) blockSize - differences) / (double) blockSize;
juce::ignoreUnused (proportionOK);
DBG ("Heap: " << areas << " " << String (roundToInt (100 * proportionOK)) << "% "
<< (isProgramLoaded() ? "Ready" : "Loading"));
#endif
}
struct Diff
{
Diff (uint16* current, const uint8* target, size_t blockSizeToUse)
: newData (target), blockSize (blockSizeToUse)
{
ranges.ensureStorageAllocated ((int) blockSize);
for (int i = 0; i < (int) blockSize; ++i)
ranges.add ({ i, 1, newData[i] == current[i], false });
coalesceUniformRegions();
coalesceSequences();
trim();
}
bool createChangeMessage (const ImplementationClass& bi,
const uint16* currentState,
OwnedArray<ChangeMessage>& messagesCreated,
uint32 nextPacketIndex)
{
if (ranges.isEmpty())
return false;
auto deviceIndex = bi.getDeviceIndex();
if (deviceIndex < 0)
return false;
auto& message = *messagesCreated.add (new ChangeMessage());
message.packetIndex = nextPacketIndex;
for (uint32 i = 0; i < blockSize; ++i)
message.resultDataState[i] = currentState[i];
auto& p = message.packet;
p.writePacketSysexHeaderBytes ((uint8) deviceIndex);
p.beginDataChanges (nextPacketIndex);
uint8 lastValue = 0;
bool packetOverflow = false;
for (auto& r : ranges)
{
if (r.isSkipped)
{
packetOverflow = ! p.skipBytes (r.length);
}
else if (r.isMixed)
{
jassert (r.length > 1);
packetOverflow = ! p.setMultipleBytes (newData + r.index, r.length);
if (! packetOverflow)
lastValue = newData[r.index + r.length - 1];
}
else
{
auto value = newData[r.index];
packetOverflow = ! p.setMultipleBytes (value, lastValue, r.length);
if (! packetOverflow)
lastValue = value;
}
if (packetOverflow)
break;
if (! r.isSkipped)
for (int i = r.index; i < r.index + r.length; ++i)
message.resultDataState[i] = newData[i];
}
p.endDataChanges (! packetOverflow);
p.writePacketSysexFooter();
return packetOverflow;
}
private:
struct ByteSequence
{
int index, length;
bool isSkipped, isMixed;
};
const uint8* const newData;
const size_t blockSize;
Array<ByteSequence> ranges;
void coalesceUniformRegions()
{
for (int i = ranges.size(); --i > 0;)
{
auto& r1 = ranges.getReference (i - 1);
auto r2 = ranges.getReference (i);
if (r1.isSkipped == r2.isSkipped
&& (r1.isSkipped || newData[r1.index] == newData[r2.index]))
{
r1.length += r2.length;
ranges.remove (i);
i = jmin (ranges.size() - 1, i + 1);
}
}
}
void coalesceSequences()
{
for (int i = ranges.size(); --i > 0;)
{
auto& r1 = ranges.getReference (i - 1);
auto r2 = ranges.getReference (i);
if (! (r1.isSkipped || r2.isSkipped)
&& (r1.isMixed || r1.length == 1)
&& (r2.isMixed || r2.length == 1))
{
if (r1.length + r2.length < 32)
{
r1.length += r2.length;
r1.isMixed = true;
ranges.remove (i);
i = jmin (ranges.size() - 1, i + 1);
}
}
}
}
void trim()
{
while (ranges.size() > 0 && ranges.getLast().isSkipped)
ranges.removeLast();
}
};
};
}

View File

@ -0,0 +1,868 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace littlefoot
{
/*
This file contains classes and definitions for executing a littlefoot
bytecode program that was created with the littlefoot compiler.
*/
#if ! (defined (LITTLEFOOT_DEBUG_TRACE) || RUNNING_ON_REAL_BLOCK_DEVICE)
#define LITTLEFOOT_DEBUG_TRACE 0
#endif
#if ! (defined (LITTLEFOOT_DUMP_PROGRAM) || RUNNING_ON_REAL_BLOCK_DEVICE)
#define LITTLEFOOT_DUMP_PROGRAM 0
#endif
using int8 = signed char;
using uint8 = unsigned char;
using int16 = signed short;
using uint16 = unsigned short;
using int32 = signed int;
using uint32 = unsigned int;
using FunctionID = int16;
#define LITTLEFOOT_OPCODES(OP, OP_INT8, OP_INT16, OP_INT32) \
OP (halt) \
OP_INT16 (jump) \
OP_INT16 (jumpIfTrue) \
OP_INT16 (jumpIfFalse) \
OP_INT16 (call) \
OP_INT8 (retVoid) \
OP_INT8 (retValue) \
OP_INT16 (callNative) \
OP (drop) \
OP_INT8 (dropMultiple) \
OP_INT8 (pushMultiple0) \
OP (push0) \
OP (push1) \
OP_INT8 (push8) \
OP_INT16 (push16) \
OP_INT32 (push32) \
OP (dup) \
OP (dupOffset_01) \
OP (dupOffset_02) \
OP (dupOffset_03) \
OP (dupOffset_04) \
OP (dupOffset_05) \
OP (dupOffset_06) \
OP (dupOffset_07) \
OP_INT8 (dupOffset) \
OP_INT16 (dupOffset16) \
OP_INT8 (dropToStack) \
OP_INT16 (dropToStack16) \
OP_INT16 (dupFromGlobal) \
OP_INT16 (dropToGlobal) \
OP (int32ToFloat) \
OP (floatToInt32) \
OP (add_int32) \
OP (add_float) \
OP (mul_int32) \
OP (mul_float) \
OP (sub_int32) \
OP (sub_float) \
OP (div_int32) \
OP (div_float) \
OP (mod_int32) \
OP (bitwiseOr) \
OP (bitwiseAnd) \
OP (bitwiseXor) \
OP (bitwiseNot) \
OP (bitShiftLeft) \
OP (bitShiftRight) \
OP (logicalOr) \
OP (logicalAnd) \
OP (logicalNot) \
OP (testZE_int32) \
OP (testNZ_int32) \
OP (testGT_int32) \
OP (testGE_int32) \
OP (testLT_int32) \
OP (testLE_int32) \
OP (testZE_float) \
OP (testNZ_float) \
OP (testGT_float) \
OP (testGE_float) \
OP (testLT_float) \
OP (testLE_float) \
OP (getHeapByte) \
OP (getHeapInt) \
OP (getHeapBits) \
OP (setHeapByte) \
OP (setHeapInt) \
enum class OpCode : uint8
{
#define LITTLEFOOT_OP(name) name,
LITTLEFOOT_OPCODES (LITTLEFOOT_OP, LITTLEFOOT_OP, LITTLEFOOT_OP, LITTLEFOOT_OP)
#undef LITTLEFOOT_OP
endOfOpcodes
};
/** Available value types */
enum class Type : uint8
{
void_ = 'v',
int_ = 'i',
bool_ = 'b',
float_ = 'f'
};
//==============================================================================
/** Defines a native function that the program can call.
@tags{Blocks}
*/
struct NativeFunction
{
using ImplementationFunction = int32 (*) (void*, const int32*);
/** Creates a NativeFunction from its signature and an implementation function.
The format of nameAndArgumentTypes is "name/[return type][arg1][arg2..]"
So for example "int foobar (float, bool)" would be "foobar/ifb"
*/
NativeFunction (const char* nameAndArgumentTypes, ImplementationFunction fn) noexcept
: nameAndArguments (nameAndArgumentTypes), function (fn),
functionID (createID (nameAndArgumentTypes)), returnType(), numArgs()
{
const int slash = indexOfSlash (nameAndArgumentTypes);
if (slash > 0)
{
returnType = static_cast<Type> (nameAndArgumentTypes[slash + 1]);
while (nameAndArgumentTypes[slash + 2 + numArgs] != 0)
++numArgs;
}
}
const char* nameAndArguments; /**< This signature must have the form "name/[return type][arg1][arg2..]" */
ImplementationFunction function; /**< A static function that will be called. */
FunctionID functionID; /**< The ID is a hash of the name + arguments, but not the return type. */
Type returnType; /**< The function's return type. */
uint8 numArgs; /**< The number of arguments that the function takes. */
/** Converts a function signature to its hashed ID. */
static FunctionID createID (const char* nameAndArgTypes) noexcept
{
jassert (nameAndArgTypes != nullptr && nameAndArgTypes[0] != 0); // the name cannot be an empty string!
int hash = 0, i = 0;
const int slash = indexOfSlash (nameAndArgTypes);
jassert (slash > 0); // The slash can't be the first character in this string!
jassert (nameAndArgTypes[slash + 1] != 0); // The slash must be followed by a return type character
jassert (juce::String (nameAndArgTypes).substring (0, slash).containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"));
jassert (! juce::String ("0123456789").containsChar (nameAndArgTypes[0]));
jassert (juce::String (nameAndArgTypes).substring (slash + 1).containsOnly ("vifb"));
jassert (juce::String (nameAndArgTypes).substring (slash + 2).containsOnly ("ifb")); // arguments must only be of these types
for (; nameAndArgTypes[i] != 0; ++i)
if (i != slash + 1)
hash = hash * 31 + nameAndArgTypes[i];
return static_cast<FunctionID> (hash + i);
}
private:
static int indexOfSlash (const char* nameAndArgs) noexcept
{
for (int i = 0; nameAndArgs[i] != 0; ++i)
if (nameAndArgs[i] == '/')
return i;
return -1;
}
};
//==============================================================================
/**
A reference to a block of memory which contains a complete program.
Data format:
2 bytes - program checksum
2 bytes - program size
2 bytes - num functions
2 bytes - num globals
2 bytes - amount of heap space needed (bytes)
2 bytes - ID of function 1
2 bytes - byte offset of function 1 code
2 bytes - ID of function 2
2 bytes - byte offset of function 2 code
etc..
...function code...
@tags{Blocks}
*/
struct Program
{
Program (const void* data, uint32 totalMemorySize) noexcept
: programStart (static_cast<const uint8*> (data)), maxProgramSize (totalMemorySize)
{
jassert (data != nullptr);
}
uint16 getStoredChecksum() const noexcept
{
return (uint16) readInt16 (programStart);
}
uint16 calculateChecksum() const noexcept
{
auto size = getProgramSize();
uint16 n = (uint16) size;
for (uint32 i = 2; i < size; ++i)
n += (n * 2) + programStart[i];
return n;
}
bool checksumMatches() const noexcept
{
return calculateChecksum() == getStoredChecksum();
}
uint32 getNumFunctions() const noexcept
{
return (uint16) readInt16 (programStart + 4);
}
FunctionID getFunctionID (uint32 functionIndex) const noexcept
{
if (auto f = getFunctionEntry (functionIndex))
return static_cast<FunctionID> (readInt16 (f));
return {};
}
const uint8* getFunctionStartAddress (uint32 functionIndex) const noexcept
{
if (auto f = getFunctionEntry (functionIndex))
{
uint32 address = (uint16) readInt16 (f + sizeof (FunctionID));
if (address < getProgramSize())
return programStart + address;
}
return {};
}
const uint8* getFunctionEndAddress (uint32 functionIndex) const noexcept
{
return ++functionIndex >= getNumFunctions() ? programStart + getProgramSize()
: getFunctionStartAddress (functionIndex);
}
uint32 getProgramSize() const noexcept
{
auto size = (uint16) readInt16 (programStart + 2);
return size < programHeaderSize ? programHeaderSize
: (size > maxProgramSize ? maxProgramSize : size);
}
/** Returns the number of bytes of heap space the program needs */
uint16 getHeapSizeBytes() const noexcept
{
return (uint16) readInt16 (programStart + 8);
}
/** Returns the number of global variables the program uses */
uint16 getNumGlobals() const noexcept
{
return (uint16) readInt16 (programStart + 6);
}
uint32 getTotalSpaceNeeded() const noexcept
{
return getProgramSize() + getHeapSizeBytes();
}
#if JUCE_DEBUG
//==============================================================================
/** Prints the assembly code for a given function. */
void dumpFunctionDisassembly (juce::OutputStream& out, uint32 functionIndex) const
{
out << juce::newLine << "Function #" << (int) functionIndex
<< " (" << juce::String::toHexString (getFunctionID (functionIndex)) << ")" << juce::newLine;
if (auto codeStart = getFunctionStartAddress (functionIndex))
if (auto codeEnd = getFunctionEndAddress (functionIndex))
for (auto prog = codeStart; prog < codeEnd;)
out << getOpDisassembly (prog) << juce::newLine;
}
juce::String getOpDisassembly (const uint8*& prog) const
{
juce::String s;
s << juce::String::toHexString ((int) (prog - programStart)).paddedLeft ('0', 4) << ": ";
auto op = (OpCode) *prog++;
switch (op)
{
#define LITTLEFOOT_OP(name) case OpCode::name: s << #name; break;
#define LITTLEFOOT_OP_INT8(name) case OpCode::name: s << #name << " " << juce::String::toHexString ((int) *prog++).paddedLeft ('0', 2); break;
#define LITTLEFOOT_OP_INT16(name) case OpCode::name: s << #name << " " << juce::String::toHexString ((int) readInt16 (prog)).paddedLeft ('0', 4); prog += 2; break;
#define LITTLEFOOT_OP_INT32(name) case OpCode::name: s << #name << " " << juce::String::toHexString ((int) readInt32 (prog)).paddedLeft ('0', 8); prog += 4; break;
LITTLEFOOT_OPCODES (LITTLEFOOT_OP, LITTLEFOOT_OP_INT8, LITTLEFOOT_OP_INT16, LITTLEFOOT_OP_INT32)
#undef LITTLEFOOT_OP
#undef LITTLEFOOT_OP_INT8
#undef LITTLEFOOT_OP_INT16
#undef LITTLEFOOT_OP_INT32
default: s << "???"; break;
}
return s;
}
/** Calls dumpFunctionDisassembly() for all functions. */
void dumpAllFunctions (juce::OutputStream& out) const
{
if (programStart != nullptr)
{
DBG ("Program size: " << (int) getProgramSize() << " bytes");
for (uint32 i = 0; i < getNumFunctions(); ++i)
dumpFunctionDisassembly (out, i);
}
}
#endif
/** For a given op code, this returns the number of program bytes that follow it. */
static uint8 getNumExtraBytesForOpcode (OpCode op) noexcept
{
switch (op)
{
#define LITTLEFOOT_OP(name) case OpCode::name: return 0;
#define LITTLEFOOT_OP_INT8(name) case OpCode::name: return 1;
#define LITTLEFOOT_OP_INT16(name) case OpCode::name: return 2;
#define LITTLEFOOT_OP_INT32(name) case OpCode::name: return 4;
LITTLEFOOT_OPCODES (LITTLEFOOT_OP, LITTLEFOOT_OP_INT8, LITTLEFOOT_OP_INT16, LITTLEFOOT_OP_INT32)
#undef LITTLEFOOT_OP
#undef LITTLEFOOT_OP_INT8
#undef LITTLEFOOT_OP_INT16
#undef LITTLEFOOT_OP_INT32
default: jassertfalse; return 0;
}
}
//==============================================================================
static float intToFloat (int32 value) noexcept { float v; copyFloatMem (&v, &value); return v; }
static int32 floatToInt (float value) noexcept { int32 v; copyFloatMem (&v, &value); return v; }
static int16 readInt16 (const uint8* d) noexcept { return (int16) (d[0] | (((uint16) d[1]) << 8)); }
static int32 readInt32 (const uint8* d) noexcept { return (int32) (d[0] | (((uint32) d[1]) << 8) | (((uint32) d[2]) << 16) | (((uint32) d[3]) << 24)); }
static void writeInt16 (uint8* d, int16 v) noexcept { d[0] = (uint8) v; d[1] = (uint8) (v >> 8); }
static void writeInt32 (uint8* d, int32 v) noexcept { d[0] = (uint8) v; d[1] = (uint8) (v >> 8); d[2] = (uint8) (v >> 16); d[3] = (uint8) (v >> 24); }
//==============================================================================
static constexpr uint32 programHeaderSize = 10;
const uint8* programStart = 0;
const uint32 maxProgramSize;
private:
const uint8* getFunctionEntry (uint32 index) const noexcept
{
auto offset = programHeaderSize + index * (sizeof (FunctionID) + sizeof (int16));
return offset <= (uint32) (getProgramSize() - 4) ? programStart + offset : nullptr;
}
static void copyFloatMem (void* dest, const void* src) noexcept
{
for (int i = 0; i < 4; ++i)
((uint8*) dest)[i] = ((const uint8*) src)[i];
}
};
//==============================================================================
/**
Loads a program, and lets the user execute its functions.
The programAndHeapSpace is the number of bytes allocated for program + heap.
stackAndGlobalsSpace is the size of the globals + stack area.
Memory layout:
Program code goes at address 0, followed by any shared data the program needs
globals are at the top end of the buffer
stack space stretches downwards from the start of the globals
@tags{Blocks}
*/
template <int programAndHeapSpace, int stackAndGlobalsSpace>
struct Runner
{
Runner() noexcept : program (allMemory, sizeof (allMemory)) { reset(); }
/** Installs an array of native functions that the code can use.
Note that this doesn't take ownership of any memory involved, so the caller mustn't pass any dangling pointers
*/
void setNativeFunctions (const NativeFunction* functions, int numFunctions, void* userDataForCallback) noexcept
{
nativeFunctions = functions;
numNativeFunctions = numFunctions;
nativeFunctionCallbackContext = userDataForCallback;
}
/** Returns the number of native functions available. */
int getNumNativeFunctions() const noexcept { return numNativeFunctions; }
/** Returns one of the native functions available. The index must not be out of range. */
const NativeFunction& getNativeFunction (int index) const noexcept { jassert (index >= 0 && index < numNativeFunctions); return nativeFunctions[index]; }
/** Clears the memory state. */
void reset() noexcept
{
for (uint32 i = 0; i < sizeof (allMemory); ++i)
allMemory[i] = 0;
}
/** Clears all the non-program data. */
void clearHeapAndGlobals() noexcept
{
auto* start = getProgramAndDataStart() + program.getProgramSize();
auto* end = allMemory + sizeof (allMemory);
for (auto m = start; m < end; ++m)
*m = 0;
}
/** Return codes from a function call */
enum class ErrorCode : uint8
{
ok = 0,
executionTimedOut,
unknownInstruction,
stackOverflow,
stackUnderflow,
illegalAddress,
divisionByZero,
unknownFunction
};
/** Returns a text description for an error code */
static const char* getErrorDescription (ErrorCode e) noexcept
{
switch (e)
{
case ErrorCode::ok: return "OK";
case ErrorCode::executionTimedOut: return "Timed-out";
case ErrorCode::unknownInstruction: return "Illegal instruction";
case ErrorCode::stackOverflow: return "Stack overflow";
case ErrorCode::stackUnderflow: return "Stack underflow";
case ErrorCode::illegalAddress: return "Illegal access";
case ErrorCode::divisionByZero: return "Division by zero";
case ErrorCode::unknownFunction: return "Unknown function";
default: return "Unknown error";
}
}
/** Calls one of the functions in the program, by its textual signature. */
ErrorCode callFunction (const char* functionSignature) noexcept
{
return FunctionExecutionContext (*this, functionSignature).run();
}
/** Calls one of the functions in the program, by its function ID. */
ErrorCode callFunction (FunctionID function) noexcept
{
return FunctionExecutionContext (*this, function).run();
}
/** */
static constexpr uint32 totalProgramAndHeapSpace = programAndHeapSpace;
/** */
static constexpr uint32 totalStackAndGlobalsSpace = stackAndGlobalsSpace;
/** */
static uint32 getMaximumProgramSize() noexcept { return programAndHeapSpace; }
/** */
uint8* getProgramAndDataStart() const noexcept { return const_cast<uint8*> (allMemory); }
/** */
uint8* getProgramAndDataEnd() const noexcept { return reinterpret_cast<uint8*> (stackStart); }
/** */
uint32 getProgramAndDataSize() const noexcept { return (uint32) (getProgramAndDataEnd() - getProgramAndDataStart()); }
/** */
uint8* getProgramHeapStart() const noexcept { return heapStart; }
/** */
uint8* getProgramHeapEnd() const noexcept { return getProgramAndDataEnd(); }
/** */
uint16 getProgramHeapSize() const noexcept { return heapSize; }
/** */
bool isProgramValid() const noexcept { return heapStart != nullptr; }
/** Sets a byte of data. */
void setDataByte (uint32 index, uint8 value) noexcept
{
if (index < programAndHeapSpace)
{
auto& dest = getProgramAndDataStart()[index];
if (index < program.getProgramSize() && dest != value)
heapStart = nullptr; // force a re-initialise of the memory layout when the program changes
dest = value;
}
}
/** */
void setHeapByte (uint32 index, uint8 value) noexcept
{
auto* addr = getProgramHeapStart() + index;
if (addr < getProgramHeapEnd())
*addr = value;
}
/** */
uint8 getHeapByte (uint32 index) const noexcept
{
const auto* addr = getProgramHeapStart() + index;
return addr < getProgramHeapEnd() ? *addr : 0;
}
/** */
uint32 getHeapBits (uint32 startBit, uint32 numBits) const noexcept
{
if (startBit + numBits > 8 * getProgramHeapSize())
{
jassertfalse;
return 0;
}
return readLittleEndianBitsInBuffer (getProgramHeapStart(), startBit, numBits);
}
/** */
int32 setHeapInt (uint32 byteOffset, uint32 value) noexcept
{
if (byteOffset + 3 < getProgramHeapSize())
Program::writeInt32 (getProgramHeapStart() + byteOffset, (int32) value);
return 0;
}
/** */
int32 getHeapInt (uint32 byteOffset) const noexcept
{
return byteOffset + 3 < getProgramHeapSize() ? Program::readInt32 (getProgramHeapStart() + byteOffset) : 0;
}
//==============================================================================
/** */
uint8 allMemory[((programAndHeapSpace + stackAndGlobalsSpace) + 3) & ~3];
/** */
Program program;
//==============================================================================
/**
*/
struct FunctionExecutionContext
{
FunctionExecutionContext() noexcept : programCounter (nullptr) {}
FunctionExecutionContext (const FunctionExecutionContext&) noexcept = default;
FunctionExecutionContext& operator= (const FunctionExecutionContext&) noexcept = default;
/** */
FunctionExecutionContext (Runner& r, const char* functionSignature) noexcept
: FunctionExecutionContext (r, NativeFunction::createID (functionSignature)) {}
/** */
FunctionExecutionContext (Runner& r, FunctionID function) noexcept
: runner (&r.reinitialiseProgramLayoutIfProgramHasChanged()),
programBase (r.program.programStart), heapStart (r.heapStart),
stack (r.stackEnd), stackStart (r.stackStart), stackEnd (r.stackEnd),
globals (r.globals), heapSize (r.heapSize),
programSize (r.program.getProgramSize()),
numGlobals (r.program.getNumGlobals())
{
if (r.heapStart != nullptr)
{
auto& prog = r.program;
auto numFunctions = prog.getNumFunctions();
for (uint32 i = 0; i < numFunctions; ++i)
{
if (prog.getFunctionID (i) == function)
{
programCounter = prog.getFunctionStartAddress (i);
programEnd = r.getProgramHeapStart();
tos = *--stack = 0;
return;
}
}
}
programCounter = nullptr;
}
/** */
bool isValid() const noexcept
{
return programCounter != nullptr && runner->heapStart != nullptr;
}
/** */
void reset() noexcept
{
programCounter = nullptr;
}
/** */
template <typename... Args>
void setArguments (Args... args) noexcept { pushArguments (args...); push0(); /* (dummy return address) */ }
/** */
template <typename TimeOutCheckFunction>
ErrorCode run (TimeOutCheckFunction hasTimedOut) noexcept
{
if (! isValid())
return ErrorCode::unknownFunction;
error = ErrorCode::unknownInstruction;
uint16 opsPerformed = 0;
for (;;)
{
if (programCounter >= programEnd)
return error;
if ((++opsPerformed & 63) == 0 && hasTimedOut())
return ErrorCode::executionTimedOut;
dumpDebugTrace();
auto op = (OpCode) *programCounter++;
#define LITTLEFOOT_PERFORM_OP(name) case OpCode::name: name(); break;
#define LITTLEFOOT_PERFORM_OP_INT8(name) case OpCode::name: name ((int8) *programCounter++); break;
#define LITTLEFOOT_PERFORM_OP_INT16(name) case OpCode::name: name (readProgram16()); break;
#define LITTLEFOOT_PERFORM_OP_INT32(name) case OpCode::name: name (readProgram32()); break;
switch (op)
{
LITTLEFOOT_OPCODES (LITTLEFOOT_PERFORM_OP, LITTLEFOOT_PERFORM_OP_INT8, LITTLEFOOT_PERFORM_OP_INT16, LITTLEFOOT_PERFORM_OP_INT32)
default: setError (ErrorCode::unknownInstruction); break;
}
jassert (programCounter != nullptr);
}
}
private:
//==============================================================================
Runner* runner;
const uint8* programCounter;
const uint8* programEnd;
const uint8* programBase;
uint8* heapStart;
int32* stack;
int32* stackStart;
int32* stackEnd;
int32* globals;
uint16 heapSize, programSize, numGlobals;
int32 tos; // top of stack
ErrorCode error;
template <typename Type1, typename... Args> void pushArguments (Type1 arg1, Args... args) noexcept { pushArguments (args...); pushArguments (arg1); }
void pushArguments (int32 arg1) noexcept { push32 (arg1); }
void pushArguments (float arg1) noexcept { push32 (Program::floatToInt (arg1)); }
int16 readProgram16() noexcept { auto v = Program::readInt16 (programCounter); programCounter += sizeof (int16); return v; }
int32 readProgram32() noexcept { auto v = Program::readInt32 (programCounter); programCounter += sizeof (int32); return v; }
void setError (ErrorCode e) noexcept { error = e; programCounter = programEnd; jassert (error == ErrorCode::ok); }
bool checkStackUnderflow() noexcept { if (stack <= stackEnd) return true; setError (ErrorCode::stackUnderflow); return false; }
bool flushTopToStack() noexcept { if (--stack < stackStart) { setError (ErrorCode::stackOverflow); return false; } *stack = tos; return true; }
using IntBinaryOp = int32 (int32, int32);
using FloatBinaryOp = float (float, float);
void binaryOp (IntBinaryOp f) noexcept { if (checkStackUnderflow()) tos = f (*stack++, tos); }
void binaryOp (FloatBinaryOp f) noexcept { if (checkStackUnderflow()) tos = Program::floatToInt (f (Program::intToFloat (*stack++), Program::intToFloat (tos))); }
void halt() noexcept { setError (ErrorCode::ok); }
void jump (int16 addr) noexcept { if (((uint16) addr) >= programSize) return setError (ErrorCode::illegalAddress); programCounter = programBase + (uint16) addr; }
void jumpIfTrue (int16 addr) noexcept { bool v = tos; drop(); if (v) jump (addr); }
void jumpIfFalse (int16 addr) noexcept { bool v = tos; drop(); if (! v) jump (addr); }
void call (int16 fnAddr) noexcept { if (flushTopToStack()) { tos = (int32) (programCounter - programBase); jump (fnAddr); } }
void retVoid (int8 numArgs) noexcept { if (tos == 0) return setError (ErrorCode::ok); auto retAddr = (int16) tos; stack += (uint8) numArgs; if (checkStackUnderflow()) { tos = *stack++; jump (retAddr); } }
void retValue (int8 numArgs) noexcept { auto retAddr = (int16) *stack++; if (retAddr == 0) return setError (ErrorCode::ok); stack += (uint8) numArgs; if (checkStackUnderflow()) jump (retAddr); }
void drop() noexcept { if (checkStackUnderflow()) tos = *stack++; }
void dropMultiple (int8 num) noexcept { if (num < 0) { stack -= num; checkStackUnderflow(); } else { stack += num - 1; drop(); }}
void pushMultiple0 (int8 num) noexcept { if (stack - num <= stackStart) return setError (ErrorCode::stackOverflow); flushTopToStack(); for (int i = (uint8) num; --i > 0;) *--stack = 0; tos = 0; }
void push0() noexcept { push32 (0); }
void push1() noexcept { push32 (1); }
void push8 (int8 value) noexcept { push32 (value); }
void push16 (int16 value) noexcept { push32 (value); }
void push32 (int32 value) noexcept { flushTopToStack(); tos = value; }
void dup() noexcept { flushTopToStack(); }
void dupOffset_01() noexcept { dupOffset16 (1); }
void dupOffset_02() noexcept { dupOffset16 (2); }
void dupOffset_03() noexcept { dupOffset16 (3); }
void dupOffset_04() noexcept { dupOffset16 (4); }
void dupOffset_05() noexcept { dupOffset16 (5); }
void dupOffset_06() noexcept { dupOffset16 (6); }
void dupOffset_07() noexcept { dupOffset16 (7); }
void dupOffset (int8 offset) noexcept { dupOffset16 ((uint8) offset); }
void dupOffset16 (int16 offset) noexcept { if (flushTopToStack()) { auto addr = stack + offset; if (addr < stackStart || addr >= stackEnd) return setError (ErrorCode::illegalAddress); tos = *addr; } }
void dropToStack (int8 offset) noexcept { dropToStack16 ((uint8) offset); }
void dropToStack16 (int16 offset) noexcept { auto addr = stack + offset; if (addr < stackStart || addr >= stackEnd) return setError (ErrorCode::illegalAddress); *addr = tos; drop(); }
void dupFromGlobal (int16 index) noexcept { if (flushTopToStack()) { if (((uint16) index) >= numGlobals) return setError (ErrorCode::illegalAddress); tos = globals [(uint16) index]; } }
void dropToGlobal (int16 index) noexcept { if (((uint16) index) >= numGlobals) return setError (ErrorCode::illegalAddress); globals [(uint16) index] = tos; drop(); }
void int32ToFloat() noexcept { tos = Program::floatToInt (static_cast<float> (tos)); }
void floatToInt32() noexcept { tos = static_cast<int32> (Program::intToFloat (tos)); }
void add_int32() noexcept { binaryOp ([] (int32 a, int32 b) { return a + b; }); }
void add_float() noexcept { binaryOp ([] (float a, float b) { return a + b; }); }
void mul_int32() noexcept { binaryOp ([] (int32 a, int32 b) { return a * b; }); }
void mul_float() noexcept { binaryOp ([] (float a, float b) { return a * b; }); }
void sub_int32() noexcept { binaryOp ([] (int32 a, int32 b) { return a - b; }); }
void sub_float() noexcept { binaryOp ([] (float a, float b) { return a - b; }); }
void div_int32() noexcept { if (tos == 0) return setError (ErrorCode::divisionByZero); binaryOp ([] (int32 a, int32 b) { return a / b; }); }
void div_float() noexcept { if (tos == 0) return setError (ErrorCode::divisionByZero); binaryOp ([] (float a, float b) { return a / b; }); }
void mod_int32() noexcept { if (tos == 0) return setError (ErrorCode::divisionByZero); binaryOp ([] (int32 a, int32 b) { return a % b; }); }
void bitwiseOr() noexcept { binaryOp ([] (int32 a, int32 b) { return a | b; }); }
void bitwiseAnd() noexcept { binaryOp ([] (int32 a, int32 b) { return a & b; }); }
void bitwiseXor() noexcept { binaryOp ([] (int32 a, int32 b) { return a ^ b; }); }
void bitShiftLeft() noexcept { binaryOp ([] (int32 a, int32 b) { return a << b; }); }
void bitShiftRight() noexcept { binaryOp ([] (int32 a, int32 b) { return a >> b; }); }
void logicalOr() noexcept { binaryOp ([] (int32 a, int32 b) { return (int32) (a || b); }); }
void logicalAnd() noexcept { binaryOp ([] (int32 a, int32 b) { return (int32) (a && b); }); }
void logicalNot() noexcept { tos = ! tos; }
void bitwiseNot() noexcept { tos = ~tos; }
void testZE_int32() noexcept { tos = (tos == 0); }
void testNZ_int32() noexcept { tos = (tos != 0); }
void testGT_int32() noexcept { tos = (tos > 0); }
void testGE_int32() noexcept { tos = (tos >= 0); }
void testLT_int32() noexcept { tos = (tos < 0); }
void testLE_int32() noexcept { tos = (tos <= 0); }
void testZE_float() noexcept { tos = (Program::intToFloat (tos) == 0.0f); }
void testNZ_float() noexcept { tos = (Program::intToFloat (tos) != 0.0f); }
void testGT_float() noexcept { tos = (Program::intToFloat (tos) > 0.0f); }
void testGE_float() noexcept { tos = (Program::intToFloat (tos) >= 0.0f); }
void testLT_float() noexcept { tos = (Program::intToFloat (tos) < 0.0f); }
void testLE_float() noexcept { tos = (Program::intToFloat (tos) <= 0.0f); }
void getHeapByte() noexcept { tos = runner->getHeapByte ((uint32) tos); }
void getHeapInt() noexcept { tos = runner->getHeapInt ((uint32) tos); }
void getHeapBits() noexcept { if (checkStackUnderflow()) tos = runner->getHeapBits ((uint32) tos, (uint32) *stack++); }
void setHeapByte() noexcept { if (checkStackUnderflow()) runner->setHeapByte ((uint32) tos, (uint8) *stack++); drop(); }
void setHeapInt() noexcept { if (checkStackUnderflow()) runner->setHeapInt ((uint32) tos, (uint32) *stack++); drop(); }
void callNative (FunctionID functionID) noexcept
{
auto numFunctions = runner->numNativeFunctions;
auto* functions = runner->nativeFunctions;
for (int i = 0; i < numFunctions; ++i)
{
const auto& f = functions[i];
if (f.functionID == functionID)
{
if (flushTopToStack())
{
tos = f.function (runner->nativeFunctionCallbackContext, stack);
stack += f.numArgs;
if (checkStackUnderflow() && f.returnType == Type::void_)
drop();
}
return;
}
}
setError (ErrorCode::unknownFunction);
}
void dumpDebugTrace() const
{
#if LITTLEFOOT_DEBUG_TRACE // Dumps the program counter and stack, for debugging
juce::MemoryOutputStream dump;
auto progCopy = programCounter;
dump << juce::String (runner->program.getOpDisassembly (progCopy)).paddedRight (' ', 26)
<< juce::String::toHexString (tos) << ' ';
for (auto s = stack; s < stackEnd; ++s)
dump << juce::String::toHexString (*s) << ' ';
DBG (dump.toString());
#endif
}
};
private:
//==============================================================================
const NativeFunction* nativeFunctions;
int numNativeFunctions = 0;
void* nativeFunctionCallbackContext = nullptr;
uint8* heapStart = nullptr;
int32* stackStart = nullptr;
int32* stackEnd = nullptr;
int32* globals = nullptr;
uint16 heapSize = 0;
Runner& reinitialiseProgramLayoutIfProgramHasChanged() noexcept
{
if (heapStart == nullptr && program.checksumMatches())
{
auto numGlobals = program.getNumGlobals();
globals = reinterpret_cast<int32*> (allMemory + sizeof (allMemory)) - numGlobals;
heapStart = getProgramAndDataStart() + program.getProgramSize();
heapSize = program.getHeapSizeBytes();
stackEnd = globals;
stackStart = reinterpret_cast<int32*> (heapStart + heapSize);
if ((uint8*) globals < heapStart || stackStart + 32 > stackEnd)
{
jassertfalse;
heapStart = nullptr;
}
else
{
for (uint32 i = 0; i < numGlobals; ++i)
globals[i] = 0; // clear globals
#if LITTLEFOOT_DUMP_PROGRAM
juce::MemoryOutputStream m;
program.dumpAllFunctions (m);
DBG (m.toString());
#endif
}
}
return *this;
}
};
}

View File

@ -0,0 +1,96 @@
A blocks packet consists of a stream of packed bits.
The first 7 bits of a message is the index of the device within the topology to which the
message should be delivered, or from which it originated. The 0x40 bit of this will be set
to indicate that it's a device->host message, or clear for host->device
The next 32 bits are a timestamp, in milliseconds since the source device was booted.
If sending from the host to the device, or for types of packet
where the timestamp is irrelevant, this can be 0.
This is followed by a sequence of 1 or more messages. Each message starts with
a 7 bit message type, followed by message-type-specific data.
---------------------------------------------------------------------------------------
Device topology update (device -> host)
7 bits - message type (0x01)
7 bits - number of device-info blocks to follow
8 bits - number of connection-info blocks to follow
This is followed by a series of device-info blocks of the form:
10 bits - device model identifier (see code for list of types)
32 bits - device GUID
10 bits - device firmware version
6 bits - battery status
Next follows by a series of connection-info blocks of ths form:
7 bits - device 1 index (i.e. index in the list of devices sent above)
5 bits - device 1 port type
7 bits - device 2 index
5 bits - device 2 port type
Ports are indicated by being either North, South, East or West on a device, plus
an index to indicate their position along that edge.
---------------------------------------------------------------------------------------
Control button down/up (device -> host)
7 bits - message type (down = 0x20, up = 0x21)
7 bits - device index
12 bits - control button ID (see code for values)
---------------------------------------------------------------------------------------
Touch start/move/end (device -> host)
7 bits - message type (start = 0x10, move = 0x11, end = 0x12)
7 bits - device index
5 bits - touch index
12 bits - touch X position
12 bits - touch Y position
8 bits - touch Z position
---------------------------------------------------------------------------------------
Touch start/move/end with velocity (device -> host)
7 bits - message type (start = 0x13, move = 0x14, end = 0x15)
7 bits - device index
5 bits - touch index
12 bits - touch X position
12 bits - touch Y position
8 bits - touch Z position
8 bits - X velocity
8 bits - Y velocity
8 bits - Z velocity
---------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------
Device command message (host -> device)
7 bits - message type (0x01)
8 bits - command type
Command types:
resetDevice = 0x00,
requestTopologyMessage = 0x01,
setHighResTouchDetectionMode = 0x02,
setLowResTouchDetectionMode = 0x03,
---------------------------------------------------------------------------------------
Modify shared state data block
7 bits - message type (0x02)
..then repeatedly:
3 bits - type of data change command
...extra command-specific bits..
3 bits - type of data change command
..etc..
3 bits - end of sequence command

View File

@ -0,0 +1,289 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace BlocksProtocol
{
/**
All sysex messages to or from a BLOCKS device begin with these header bytes.
The next byte that follows indicates the device index within the topology, where
the 0x40 bit is set for device->host messages, and clear for host->device messages.
The lower 6 bits contain the topology index of the destination or source device.
*/
static const uint8 roliSysexHeader[] = { 0xf0, 0x00, 0x21, 0x10, 0x77 };
static uint8 calculatePacketChecksum (const uint8* data, uint32 size) noexcept
{
uint8 checksum = (uint8) size;
for (uint32 i = 0; i < size; ++i)
checksum += checksum * 2 + data[i];
return checksum & 0x7f;
}
//==============================================================================
/**
Helper class to define an integer with a specific bit size.
@tags{Blocks}
*/
template <int numBits>
struct IntegerWithBitSize
{
IntegerWithBitSize() noexcept = default;
IntegerWithBitSize (const IntegerWithBitSize&) noexcept = default;
IntegerWithBitSize& operator= (const IntegerWithBitSize&) noexcept = default;
IntegerWithBitSize (uint32 v) noexcept : value (v)
{
static_assert (numBits <= 32, "numBits must be <= 32");
jassert (v >= 0 && v <= maxValue);
}
enum
{
bits = numBits,
maxValue = static_cast<uint32> ((1ULL << numBits) - 1ULL)
};
operator uint32() const noexcept { return value; }
uint32 get() const noexcept { return value; }
uint8 getScaledToByte() const noexcept
{
return (uint8) (numBits < 8 ? (uint32) (value << (8 - numBits))
: (uint32) (value >> (numBits - 8)));
}
float toUnipolarFloat() const noexcept { return value / (float) maxValue; }
float toBipolarFloat() const noexcept { return static_cast<int32> (value << (32 - numBits)) / (float) 0x80000000u; }
static IntegerWithBitSize fromUnipolarFloat (float value) noexcept
{
static_assert (numBits <= 31, "numBits must be <= 31");
return IntegerWithBitSize ((uint32) jlimit (0, (int) maxValue, (int) (value * maxValue)));
}
static IntegerWithBitSize fromBipolarFloat (float value) noexcept
{
static_assert (numBits <= 31, "numBits must be <= 31");
return IntegerWithBitSize (maxValue & (uint32) jlimit ((int) -(maxValue / 2), (int) (maxValue / 2), (int) (value * (maxValue / 2))));
}
uint32 value = 0;
};
//==============================================================================
/**
This helper class allocates a block of 7-bit bytes and can push sequences of bits into it.
@see Packed7BitArrayReader
@tags{Blocks}
*/
template <int allocatedBytes>
struct Packed7BitArrayBuilder
{
const void* getData() const noexcept { return data; }
int size() const noexcept { return bytesWritten + (bitsInCurrentByte > 0 ? 1 : 0); }
bool hasCapacity (int bitsNeeded) const noexcept
{
return ((bytesWritten + 2) * 7 + bitsInCurrentByte + bitsNeeded) <= allocatedBytes * 7;
}
void writeHeaderSysexBytes (uint8 deviceIndex) noexcept
{
jassert (bytesWritten + bitsInCurrentByte == 0);
for (int i = 0; i < (int) sizeof (roliSysexHeader); ++i)
data[bytesWritten++] = roliSysexHeader[i];
jassert (deviceIndex < 128);
data[bytesWritten++] = deviceIndex & 0x7f;
}
void writePacketSysexFooter() noexcept
{
if (bitsInCurrentByte != 0)
{
bitsInCurrentByte = 0;
++bytesWritten;
}
jassert (hasCapacity (0));
uint32 headerBytes = (uint32) sizeof (roliSysexHeader) + 1;
data[bytesWritten] = calculatePacketChecksum (data + headerBytes, (uint32) bytesWritten - headerBytes);
++bytesWritten;
data[bytesWritten++] = 0xf7;
}
template <int numBits>
Packed7BitArrayBuilder& operator<< (IntegerWithBitSize<numBits> value) noexcept
{
writeBits (value.value, numBits);
return *this;
}
void writeBits (uint32 value, int numBits) noexcept
{
jassert (numBits <= 32);
jassert (hasCapacity (numBits));
jassert (numBits == 32 || (value >> numBits) == 0);
while (numBits > 0)
{
if (bitsInCurrentByte == 0)
{
if (numBits < 7)
{
data[bytesWritten] = (uint8) value;
bitsInCurrentByte = numBits;
return;
}
if (numBits == 7)
{
data[bytesWritten++] = (uint8) value;
return;
}
data[bytesWritten++] = (uint8) (value & 0x7f);
value >>= 7;
numBits -= 7;
}
else
{
const int bitsToDo = jmin (7 - bitsInCurrentByte, numBits);
data[bytesWritten] |= ((value & ((1 << bitsToDo) - 1)) << bitsInCurrentByte);
value >>= bitsToDo;
numBits -= bitsToDo;
bitsInCurrentByte += bitsToDo;
if (bitsInCurrentByte == 7)
{
bitsInCurrentByte = 0;
++bytesWritten;
}
}
}
}
/** Describes the current building state */
struct State
{
int bytesWritten, bitsInCurrentByte;
};
State getState() const noexcept
{
return { bytesWritten, bitsInCurrentByte };
}
void restore (State state) noexcept
{
bytesWritten = state.bytesWritten;
bitsInCurrentByte = state.bitsInCurrentByte;
}
private:
uint8 data[allocatedBytes];
int bytesWritten = 0, bitsInCurrentByte = 0;
};
//==============================================================================
/**
This helper class reads from a block of 7-bit bytes as sequences of bits.
@see Packed7BitArrayBuilder
@tags{Blocks}
*/
struct Packed7BitArrayReader
{
Packed7BitArrayReader (const void* sourceData, int numBytes) noexcept
: data (static_cast<const uint8*> (sourceData)), totalBits (numBytes * 7)
{
}
int getRemainingBits() const noexcept
{
return totalBits - bitsReadInCurrentByte;
}
template <typename Target>
Target read() noexcept
{
return Target (readBits (Target::bits));
}
uint32 readBits (int numBits) noexcept
{
jassert (numBits <= 32);
jassert (getRemainingBits() >= numBits);
uint32 value = 0;
int bitsSoFar = 0;
while (numBits > 0)
{
const uint32 valueInCurrentByte = (*data >> bitsReadInCurrentByte);
const int bitsAvailable = 7 - bitsReadInCurrentByte;
if (bitsAvailable > numBits)
{
value |= ((valueInCurrentByte & ((1 << numBits) - 1)) << bitsSoFar);
bitsReadInCurrentByte += numBits;
break;
}
value |= (valueInCurrentByte << bitsSoFar);
numBits -= bitsAvailable;
bitsSoFar += bitsAvailable;
bitsReadInCurrentByte = 0;
++data;
totalBits -= 7;
}
return value;
}
static bool checksumIsOK (const uint8* data, uint32 size) noexcept
{
return size > 1 && calculatePacketChecksum (data, size - 1) == data[size - 1];
}
private:
const uint8* data;
int totalBits, bitsReadInCurrentByte = 0;
};
} // namespace BlocksProtocol
} // namespace juce

View File

@ -0,0 +1,324 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace BlocksProtocol
{
#ifndef DOXYGEN
// This file isn't part of the public API, it's where we encode the knowledge base
// of all the different types of block we know about..
struct BlockDataSheet
{
BlockDataSheet (const BlocksProtocol::BlockSerialNumber& serial) : serialNumber (serial)
{
if (serialNumber.isPadBlock()) initialiseForPadBlock2x2();
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
{
return ports[(int) port.get()];
}
const BlocksProtocol::BlockSerialNumber serialNumber;
Block::Type apiType = Block::Type::unknown;
const char* description = nullptr;
int widthUnits = 0, heightUnits = 0;
int lightGridWidth = 0, lightGridHeight = 0, lightGridStartIndex = 0;
bool hasTouchSurface = false;
int numKeywaves = 0;
int numLEDRowLEDs = 0;
uint32 programAndHeapSize = 0;
struct ButtonInfo
{
ControlButton::ButtonFunction type;
float x, y;
};
struct StatusLEDInfo
{
juce::String name;
float x, y;
};
juce::Array<ButtonInfo> buttons;
juce::Array<StatusLEDInfo> statusLEDs;
juce::Array<Block::ConnectionPort> ports;
juce::Array<const char*> dials;
private:
//==============================================================================
void initialiseForPadBlock2x2()
{
apiType = Block::Type::lightPadBlock;
description = "Pad BLOCK (2x2)";
widthUnits = 2;
heightUnits = 2;
lightGridWidth = 15;
lightGridHeight = 15;
addPorts (2, 2, 2, 2);
hasTouchSurface = true;
programAndHeapSize = BlocksProtocol::padBlockProgramAndHeapSize;
addModeButton();
}
void initialiseForControlBlockLoop()
{
initialiseControlBlock ("Loop BLOCK", Block::Type::loopBlock,
ControlButton::ButtonFunction::mode,
ControlButton::ButtonFunction::volume,
ControlButton::ButtonFunction::click,
ControlButton::ButtonFunction::snap,
ControlButton::ButtonFunction::back,
ControlButton::ButtonFunction::playOrPause,
ControlButton::ButtonFunction::record,
ControlButton::ButtonFunction::learn,
ControlButton::ButtonFunction::down,
ControlButton::ButtonFunction::up);
}
void initialiseForControlBlockLive()
{
initialiseControlBlock ("Live BLOCK", Block::Type::liveBlock,
ControlButton::ButtonFunction::mode,
ControlButton::ButtonFunction::volume,
ControlButton::ButtonFunction::scale,
ControlButton::ButtonFunction::chord,
ControlButton::ButtonFunction::arp,
ControlButton::ButtonFunction::sustain,
ControlButton::ButtonFunction::octave,
ControlButton::ButtonFunction::love,
ControlButton::ButtonFunction::down,
ControlButton::ButtonFunction::up);
}
void initialiseForControlBlockDeveloper()
{
initialiseControlBlock ("Dev Ctrl BLOCK", Block::Type::developerControlBlock,
ControlButton::ButtonFunction::button0,
ControlButton::ButtonFunction::button1,
ControlButton::ButtonFunction::button2,
ControlButton::ButtonFunction::button3,
ControlButton::ButtonFunction::button4,
ControlButton::ButtonFunction::button5,
ControlButton::ButtonFunction::button6,
ControlButton::ButtonFunction::button7,
ControlButton::ButtonFunction::down,
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,
ControlButton::ButtonFunction b5, ControlButton::ButtonFunction b6,
ControlButton::ButtonFunction b7, ControlButton::ButtonFunction b8,
ControlButton::ButtonFunction b9, ControlButton::ButtonFunction b10)
{
apiType = type;
description = name;
widthUnits = 2;
heightUnits = 1;
programAndHeapSize = BlocksProtocol::controlBlockProgramAndHeapSize;
addPorts (2, 1, 2, 1);
float x1 = 0.2f;
float x2 = 0.6f;
float x3 = 1.0f;
float x4 = 1.4f;
float x5 = 1.8f;
float y1 = 0.405f;
float y2 = 0.798f;
addButtons (b1, x1, y1,
b2, x2, y1,
b3, x3, y1,
b4, x4, y1,
b5, x5, y1,
b6, x1, y2,
b7, x2, y2,
b8, x3, y2,
b9, x4, y2,
b10, x5, y2);
numLEDRowLEDs = 15;
}
void initialiseForSeaboardBlock()
{
apiType = Block::Type::seaboardBlock;
description = "Seaboard BLOCK (6x3)";
widthUnits = 6;
heightUnits = 3;
lightGridWidth = 0;
lightGridHeight = 0;
numKeywaves = 24;
addPortsSW (Block::ConnectionPort::DeviceEdge::west, 1);
addPortsNE (Block::ConnectionPort::DeviceEdge::north, 2);
addPortsNE (Block::ConnectionPort::DeviceEdge::east, 1);
hasTouchSurface = true;
programAndHeapSize = BlocksProtocol::padBlockProgramAndHeapSize;
addModeButton();
}
//==============================================================================
void addStatusLED (const char* name, float x, float y)
{
statusLEDs.add ({ name, x, y });
}
template <typename... Args>
void addButtons (ControlButton::ButtonFunction fn, float x, float y, Args... others)
{
addButtons (fn, x, y);
addButtons (others...);
}
void addButtons (ControlButton::ButtonFunction fn, float x, float y)
{
buttons.add ({ fn, x, y });
}
void addModeButton()
{
addButtons (ControlButton::ButtonFunction::mode, -1.0f, -1.0f);
}
void addPorts (int numNorth, int numEast, int numSouth, int numWest)
{
addPortsNE (Block::ConnectionPort::DeviceEdge::north, numNorth);
addPortsNE (Block::ConnectionPort::DeviceEdge::east, numEast);
addPortsSW (Block::ConnectionPort::DeviceEdge::south, numSouth);
addPortsSW (Block::ConnectionPort::DeviceEdge::west, numWest);
}
void addPortsNE (Block::ConnectionPort::DeviceEdge edge, int num)
{
for (int i = 0; i < num; ++i)
ports.add ({ edge, i});
}
void addPortsSW (Block::ConnectionPort::DeviceEdge edge, int num)
{
for (int i = 0; i < num; ++i)
ports.add ({ edge, num - i - 1});
}
};
//==============================================================================
static const char* getButtonNameForFunction (ControlButton::ButtonFunction fn) noexcept
{
using BF = ControlButton::ButtonFunction;
switch (fn)
{
case BF::mode: return "Mode";
case BF::volume: return "Volume";
case BF::up: return "Up";
case BF::down: return "Down";
case BF::scale: return "Scale";
case BF::chord: return "Chord";
case BF::arp: return "Arp";
case BF::sustain: return "Sustain";
case BF::octave: return "Octave";
case BF::love: return "Love";
case BF::click: return "Click";
case BF::snap: return "Snap";
case BF::back: return "Back";
case BF::playOrPause: return "Play/Pause";
case BF::record: return "Record";
case BF::learn: return "Learn";
case BF::button0: return "0";
case BF::button1: return "1";
case BF::button2: return "2";
case BF::button3: return "3";
case BF::button4: return "4";
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;
return nullptr;
}
#endif
} // namespace BlocksProtocol
} // namespace juce

View File

@ -0,0 +1,545 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace BlocksProtocol
{
/** This value is incremented when the format of the API changes in a way which
breaks compatibility.
*/
static constexpr uint32 currentProtocolVersion = 1;
using ProtocolVersion = IntegerWithBitSize<8>;
//==============================================================================
/** A timestamp for a packet, in milliseconds since device boot-up */
using PacketTimestamp = IntegerWithBitSize<32>;
/** This relative timestamp is for use inside a packet, and it represents a
number of milliseconds that should be added to the packet's timestamp.
*/
using PacketTimestampOffset = IntegerWithBitSize<5>;
//==============================================================================
/** Messages that a device may send to the host. */
enum class MessageFromDevice
{
deviceTopology = 0x01,
packetACK = 0x02,
firmwareUpdateACK = 0x03,
deviceTopologyExtend = 0x04,
deviceTopologyEnd = 0x05,
deviceVersionList = 0x06,
deviceNameList = 0x07,
touchStart = 0x10,
touchMove = 0x11,
touchEnd = 0x12,
touchStartWithVelocity = 0x13,
touchMoveWithVelocity = 0x14,
touchEndWithVelocity = 0x15,
configMessage = 0x18,
controlButtonDown = 0x20,
controlButtonUp = 0x21,
programEventMessage = 0x28,
logMessage = 0x30
};
/** Messages that the host may send to a device. */
enum class MessageFromHost
{
deviceCommandMessage = 0x01,
sharedDataChange = 0x02,
programEventMessage = 0x03,
firmwareUpdatePacket = 0x04,
configMessage = 0x10,
factoryReset = 0x11,
blockReset = 0x12,
setName = 0x20
};
/** This is the first item in a BLOCKS message, identifying the message type. */
using MessageType = IntegerWithBitSize<7>;
//==============================================================================
/** This is a type of index identifier used to refer to a block within a group.
It refers to the index of a device in the list of devices that was most recently
sent via a topology change message
(It's not a global UID for a block unit).
NB: to send a message to all devices, pass the getDeviceIndexForBroadcast() value.
*/
using TopologyIndex = uint8;
static constexpr int topologyIndexBits = 7;
/** Use this value as the index if you want a message to be sent to all devices in
the group.
*/
static constexpr TopologyIndex topologyIndexForBroadcast = 63;
using DeviceCount = IntegerWithBitSize<7>;
using ConnectionCount = IntegerWithBitSize<8>;
//==============================================================================
/** Battery charge level. */
using BatteryLevel = IntegerWithBitSize<5>;
/** Battery charger connection flag. */
using BatteryCharging = IntegerWithBitSize<1>;
//==============================================================================
/** ConnectorPort is an index, starting at 0 for the leftmost port on the
top edge, and going clockwise.
*/
using ConnectorPort = IntegerWithBitSize<5>;
//==============================================================================
/** Structure describing a block's serial number
@tags{Blocks}
*/
struct BlockSerialNumber
{
uint8 serial[16];
bool isValid() const noexcept
{
for (auto c : serial)
if (c == 0)
return false;
return isAnyControlBlock() || isPadBlock() || isSeaboardBlock();
}
bool isPadBlock() const noexcept { return hasPrefix ("LPB") || hasPrefix ("LPM"); }
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() || isTouchBlock(); }
bool hasPrefix (const char* prefix) const noexcept { return memcmp (serial, prefix, 3) == 0; }
};
/** Structure for the version number
@tags{Blocks}
*/
struct VersionNumber
{
uint8 version[21] = {};
uint8 length = 0;
};
/** Structure for the block name
@tags{Blocks}
*/
struct BlockName
{
uint8 name[33] = {};
uint8 length = 0;
};
/** Structure for the device status
@tags{Blocks}
*/
struct DeviceStatus
{
BlockSerialNumber serialNumber;
TopologyIndex index;
BatteryLevel batteryLevel;
BatteryCharging batteryCharging;
};
/** Structure for the device connection
@tags{Blocks}
*/
struct DeviceConnection
{
TopologyIndex device1, device2;
ConnectorPort port1, port2;
};
/** Structure for the device version
@tags{Blocks}
*/
struct DeviceVersion
{
TopologyIndex index;
VersionNumber version;
};
/** Structure used for the device name
@tags{Blocks}
*/
struct DeviceName
{
TopologyIndex index;
BlockName name;
};
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,
glideLockEnable = 19,
// Live
mode = 20,
volume = 21,
scale = 22,
hideMode = 23,
chord = 24,
arpPattern = 25,
tempo = 26,
// Tracking
xTrackingMode = 30,
yTrackingMode = 31,
zTrackingMode = 32,
// Graphics
gammaCorrection = 33,
// 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.
@tags{Blocks}
*/
struct TouchPosition
{
using Xcoord = IntegerWithBitSize<12>;
using Ycoord = IntegerWithBitSize<12>;
using Zcoord = IntegerWithBitSize<8>;
Xcoord x;
Ycoord y;
Zcoord z;
enum { bits = Xcoord::bits + Ycoord::bits + Zcoord::bits };
};
/** The velocities for each dimension of a touch.
@tags{Blocks}
*/
struct TouchVelocity
{
using VXcoord = IntegerWithBitSize<8>;
using VYcoord = IntegerWithBitSize<8>;
using VZcoord = IntegerWithBitSize<8>;
VXcoord vx;
VYcoord vy;
VZcoord vz;
enum { bits = VXcoord::bits + VYcoord::bits + VZcoord::bits };
};
/** The index of a touch, i.e. finger number. */
using TouchIndex = IntegerWithBitSize<5>;
using PacketCounter = IntegerWithBitSize<10>;
//==============================================================================
enum DeviceCommands
{
beginAPIMode = 0x00,
requestTopologyMessage = 0x01,
endAPIMode = 0x02,
ping = 0x03,
debugMode = 0x04,
saveProgramAsDefault = 0x05
};
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,
clusterConfigSync = 0x08
};
using ConfigCommand = IntegerWithBitSize<4>;
using ConfigItemIndex = IntegerWithBitSize<8>;
using ConfigItemValue = IntegerWithBitSize<32>;
//==============================================================================
/** An ID for a control-block button type */
using ControlButtonID = IntegerWithBitSize<12>;
//==============================================================================
using RotaryDialIndex = IntegerWithBitSize<7>;
using RotaryDialAngle = IntegerWithBitSize<14>;
using RotaryDialDelta = IntegerWithBitSize<14>;
//==============================================================================
enum DataChangeCommands
{
endOfPacket = 0,
endOfChanges = 1,
skipBytesFew = 2,
skipBytesMany = 3,
setSequenceOfBytes = 4,
setFewBytesWithValue = 5,
setFewBytesWithLastValue = 6,
setManyBytesWithValue = 7
};
using PacketIndex = IntegerWithBitSize<16>;
using DataChangeCommand = IntegerWithBitSize<3>;
using ByteCountFew = IntegerWithBitSize<4>;
using ByteCountMany = IntegerWithBitSize<8>;
using ByteValue = IntegerWithBitSize<8>;
using ByteSequenceContinues = IntegerWithBitSize<1>;
using FirmwareUpdateACKCode = IntegerWithBitSize<7>;
using FirmwareUpdateACKDetail = IntegerWithBitSize<32>;
using FirmwareUpdatePacketSize = IntegerWithBitSize<7>;
static constexpr uint32 numProgramMessageInts = 3;
static constexpr uint32 apiModeHostPingTimeoutMs = 5000;
static constexpr uint32 padBlockProgramAndHeapSize = 7200;
static constexpr uint32 padBlockStackSize = 800;
static constexpr uint32 controlBlockProgramAndHeapSize = 3000;
static constexpr uint32 controlBlockStackSize = 800;
//==============================================================================
/** Contains the number of bits required to encode various items in the packets */
enum BitSizes
{
topologyMessageHeader = MessageType::bits + ProtocolVersion::bits + DeviceCount::bits + ConnectionCount::bits,
topologyDeviceInfo = sizeof (BlockSerialNumber) * 7 + BatteryLevel::bits + BatteryCharging::bits,
topologyConnectionInfo = topologyIndexBits + ConnectorPort::bits + topologyIndexBits + ConnectorPort::bits,
typeDeviceAndTime = MessageType::bits + PacketTimestampOffset::bits,
touchMessage = typeDeviceAndTime + TouchIndex::bits + TouchPosition::bits,
touchMessageWithVelocity = touchMessage + TouchVelocity::bits,
programEventMessage = MessageType::bits + 32 * numProgramMessageInts,
packetACK = MessageType::bits + PacketCounter::bits,
firmwareUpdateACK = MessageType::bits + FirmwareUpdateACKCode::bits + FirmwareUpdateACKDetail::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,
};
//==============================================================================
// These are the littlefoot functions provided for use in BLOCKS programs
static constexpr const char* ledProgramLittleFootFunctions[] =
{
"min/iii",
"min/fff",
"max/iii",
"max/fff",
"clamp/iiii",
"clamp/ffff",
"abs/ii",
"abs/ff",
"map/ffffff",
"map/ffff",
"mod/iii",
"getRandomFloat/f",
"getRandomInt/ii",
"log/vi",
"logHex/vi",
"getMillisecondCounter/i",
"getFirmwareVersion/i",
"getTimeInCurrentFunctionCall/i",
"getBatteryLevel/f",
"isBatteryCharging/b",
"isMasterBlock/b",
"isConnectedToHost/b",
"setStatusOverlayActive/vb",
"getNumBlocksInTopology/i",
"getBlockIDForIndex/ii",
"getBlockIDOnPort/ii",
"getPortToMaster/i",
"getBlockTypeForID/ii",
"sendMessageToBlock/viiii",
"sendMessageToHost/viii",
"getHorizontalDistFromMaster/i",
"getVerticalDistFromMaster/i",
"getAngleFromMaster/i",
"setAutoRotate/vb",
"getClusterIndex/i",
"getClusterWidth/i",
"getClusterHeight/i",
"getClusterXpos/i",
"getClusterYpos/i",
"getNumBlocksInCurrentCluster/i",
"getBlockIdForBlockInCluster/ii",
"isMasterInCurrentCluster/b",
"setClusteringActive/vb",
"makeARGB/iiiii",
"blendARGB/iii",
"fillPixel/viii",
"blendPixel/viii",
"fillRect/viiiii",
"blendRect/viiiii",
"blendGradientRect/viiiiiiii",
"blendCircle/vifffb",
"addPressurePoint/vifff",
"drawPressureMap/v",
"fadePressureMap/v",
"drawNumber/viiii",
"clearDisplay/v",
"clearDisplay/vi",
"displayBatteryLevel/v",
"sendMIDI/vi",
"sendMIDI/vii",
"sendMIDI/viii",
"sendNoteOn/viii",
"sendNoteOff/viii",
"sendAftertouch/viii",
"sendCC/viii",
"sendPitchBend/vii",
"sendPitchBend/viii",
"sendChannelPressure/vii",
"addPitchCorrectionPad/viiffff",
"setPitchCorrectionEnabled/vb",
"getPitchCorrectionPitchBend/iii",
"setChannelRange/vbii",
"assignChannel/ii",
"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",
"setButtonMode/vii",
"setButtonType/viii",
"setButtonMinMaxDefault/viiii",
"setButtonColours/viii",
"setButtonTriState/vii",
nullptr
};
} // namespace BlocksProtocol
} // namespace juce

View File

@ -0,0 +1,325 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace BlocksProtocol
{
/**
Helper class for constructing a packet for sending to a BLOCKS device
@tags{Blocks}
*/
template <int maxPacketBytes>
struct HostPacketBuilder
{
HostPacketBuilder() noexcept {}
HostPacketBuilder (const HostPacketBuilder&) = delete;
HostPacketBuilder (HostPacketBuilder&&) = default;
const void* getData() const noexcept { return data.getData(); }
int size() const noexcept { return data.size(); }
//==============================================================================
void writePacketSysexHeaderBytes (TopologyIndex deviceIndex) noexcept
{
static_assert (maxPacketBytes > 10, "Not enough bytes for a sensible message!");
jassert ((deviceIndex & 64) == 0);
data.writeHeaderSysexBytes (deviceIndex);
}
void writePacketSysexFooter() noexcept
{
data.writePacketSysexFooter();
}
//==============================================================================
bool deviceControlMessage (DeviceCommand command) noexcept
{
if (! data.hasCapacity (MessageType::bits + DeviceCommand::bits))
return false;
writeMessageType (MessageFromHost::deviceCommandMessage);
data << command;
return true;
}
//==============================================================================
bool beginDataChanges (PacketIndex packetIndex) noexcept
{
if (! data.hasCapacity (MessageType::bits + PacketIndex::bits + DataChangeCommand::bits))
return false;
writeMessageType (MessageFromHost::sharedDataChange);
data << packetIndex;
return true;
}
bool endDataChanges (bool isLastChange) noexcept
{
if (! data.hasCapacity (DataChangeCommand::bits))
return false;
data << DataChangeCommand ((uint32) isLastChange ? endOfChanges : endOfPacket);
return true;
}
bool skipBytes (int numToSkip) noexcept
{
if (numToSkip <= 0)
return true;
auto state = data.getState();
while (numToSkip > ByteCountMany::maxValue)
{
if (! skipBytes (ByteCountMany::maxValue))
{
data.restore (state);
return false;
}
numToSkip -= ByteCountMany::maxValue;
}
if (numToSkip > ByteCountFew::maxValue)
{
if (! data.hasCapacity (DataChangeCommand::bits * 2 + ByteCountMany::bits))
{
data.restore (state);
return false;
}
data << DataChangeCommand ((uint32) skipBytesMany) << ByteCountMany ((uint32) numToSkip);
return true;
}
if (! data.hasCapacity (DataChangeCommand::bits * 2 + ByteCountFew::bits))
{
data.restore (state);
return false;
}
data << DataChangeCommand ((uint32) skipBytesFew) << ByteCountFew ((uint32) numToSkip);
return true;
}
bool setMultipleBytes (const uint8* values, int num) noexcept
{
if (num <= 0)
return true;
if (! data.hasCapacity (DataChangeCommand::bits * 2 + num * (1 + ByteValue::bits)))
return false;
data << DataChangeCommand ((uint32) setSequenceOfBytes);
for (int i = 0; i < num; ++i)
data << ByteValue ((uint32) values[i])
<< ByteSequenceContinues (i < num - 1 ? 1 : 0);
return true;
}
bool setMultipleBytes (uint8 value, uint8 lastValue, int num) noexcept
{
if (num <= 0)
return true;
if (num == 1)
return setMultipleBytes (&value, 1); // (this is a more compact message)
auto state = data.getState();
if (num > ByteCountMany::maxValue)
{
if (! setMultipleBytes (value, lastValue, ByteCountMany::maxValue))
{
data.restore (state);
return false;
}
return setMultipleBytes (value, lastValue, num - ByteCountMany::maxValue);
}
if (num > ByteCountFew::maxValue)
{
if (! data.hasCapacity (DataChangeCommand::bits * 2 + ByteCountMany::bits + ByteValue::bits))
{
data.restore (state);
return false;
}
data << DataChangeCommand ((uint32) setManyBytesWithValue)
<< ByteCountMany ((uint32) num)
<< ByteValue ((uint32) value);
return true;
}
if (value == lastValue)
{
if (! data.hasCapacity (DataChangeCommand::bits * 2 + ByteCountFew::bits))
{
data.restore (state);
return false;
}
data << DataChangeCommand ((uint32) setFewBytesWithLastValue) << ByteCountFew ((uint32) num);
return true;
}
if (! data.hasCapacity (DataChangeCommand::bits * 2 + ByteCountFew::bits + ByteValue::bits))
{
data.restore (state);
return false;
}
data << DataChangeCommand ((uint32) setFewBytesWithValue) << ByteCountFew ((uint32) num)
<< ByteValue ((uint32) value);
return true;
}
bool addProgramEventMessage (const int32* messageData)
{
if (! data.hasCapacity (BitSizes::programEventMessage))
return false;
writeMessageType (MessageFromHost::programEventMessage);
for (uint32 i = 0; i < numProgramMessageInts; ++i)
data << IntegerWithBitSize<32> ((uint32) messageData[i]);
return true;
}
bool addFirmwareUpdatePacket (const uint8* packetData, uint8 size)
{
if (! data.hasCapacity (MessageType::bits + FirmwareUpdatePacketSize::bits + 7 * size))
return false;
writeMessageType (MessageFromHost::firmwareUpdatePacket);
data << FirmwareUpdatePacketSize (size);
for (uint8 i = 0; i < size; ++i)
data << IntegerWithBitSize<7> ((uint32) packetData[i]);
return true;
}
//==============================================================================
bool addConfigSetMessage (int32 item, int32 value)
{
if (! data.hasCapacity (BitSizes::configSetMessage))
return false;
writeMessageType(MessageFromHost::configMessage);
ConfigCommand type = ConfigCommands::setConfig;
data << type << IntegerWithBitSize<8> ((uint32) item) << IntegerWithBitSize<32>((uint32) value);
return true;
}
bool addRequestMessage (int32 item)
{
if (! data.hasCapacity (BitSizes::configSetMessage))
return false;
writeMessageType(MessageFromHost::configMessage);
ConfigCommand type = ConfigCommands::requestConfig;
data << type << IntegerWithBitSize<32> (0) << IntegerWithBitSize<8> ((uint32) item);
return true;
}
bool addRequestFactorySyncMessage()
{
if (! data.hasCapacity (MessageType::bits + ConfigCommand::bits))
return false;
writeMessageType (MessageFromHost::configMessage);
ConfigCommand type = ConfigCommands::requestFactorySync;
data << type;
return true;
}
bool addRequestUserSyncMessage()
{
if (! data.hasCapacity (MessageType::bits + ConfigCommand::bits))
return false;
writeMessageType (MessageFromHost::configMessage);
ConfigCommand type = ConfigCommands::requestUserSync;
data << type;
return true;
}
//==============================================================================
bool addFactoryReset()
{
if (! data.hasCapacity (MessageType::bits))
return false;
writeMessageType(MessageFromHost::factoryReset);
return true;
}
bool addBlockReset()
{
if (! data.hasCapacity (MessageType::bits))
return false;
writeMessageType(MessageFromHost::blockReset);
return true;
}
bool addSetBlockName (const juce::String& name)
{
if (name.length() > 32 || ! data.hasCapacity (MessageType::bits + 7 + (7 * name.length())))
return false;
writeMessageType (MessageFromHost::setName);
data << IntegerWithBitSize<7> ((uint32) name.length());
for (auto i = 0; i < name.length(); ++i)
data << IntegerWithBitSize<7> ((uint32) name.toRawUTF8()[i]);
data << IntegerWithBitSize<7> (0);
return true;
}
//==============================================================================
private:
Packed7BitArrayBuilder<maxPacketBytes> data;
void writeMessageType (MessageFromHost type) noexcept
{
data << MessageType ((uint32) type);
}
};
} // namespace BlocksProtocol
} // namespace juce

View File

@ -0,0 +1,363 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace BlocksProtocol
{
/**
Parses data packets from a BLOCKS device, and translates them into callbacks
on a handler object
@tags{Blocks}
*/
template <typename Handler>
struct HostPacketDecoder
{
static void processNextPacket (Handler& handler, TopologyIndex deviceIndex, const void* data, int size)
{
if (Packed7BitArrayReader::checksumIsOK (static_cast<const uint8*> (data), (uint32) size))
{
Packed7BitArrayReader reader (data, size - 1);
if (reader.getRemainingBits() < (int) PacketTimestamp::bits)
{
jassertfalse; // not a valid message..
return;
}
auto packetTimestamp = reader.read<PacketTimestamp>();
deviceIndex &= 63; // top bit is used as a direction indicator
while (processNextMessage (handler, reader, deviceIndex, packetTimestamp))
{}
}
}
static bool processNextMessage (Handler& handler, Packed7BitArrayReader& reader,
TopologyIndex deviceIndex, PacketTimestamp packetTimestamp)
{
if (reader.getRemainingBits() < MessageType::bits)
return false;
auto messageType = reader.read<MessageType>().get();
if (messageType == 0)
return false;
switch ((MessageFromDevice) messageType)
{
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::deviceNameList: return handleName (handler, reader);
case MessageFromDevice::touchStart: return handleTouch (handler, reader, deviceIndex, packetTimestamp, true, false);
case MessageFromDevice::touchMove: return handleTouch (handler, reader, deviceIndex, packetTimestamp, false, false);
case MessageFromDevice::touchEnd: return handleTouch (handler, reader, deviceIndex, packetTimestamp, false, true);
case MessageFromDevice::touchStartWithVelocity: return handleTouchWithVelocity (handler, reader, deviceIndex, packetTimestamp, true, false);
case MessageFromDevice::touchMoveWithVelocity: return handleTouchWithVelocity (handler, reader, deviceIndex, packetTimestamp, false, false);
case MessageFromDevice::touchEndWithVelocity: return handleTouchWithVelocity (handler, reader, deviceIndex, packetTimestamp, false, true);
case MessageFromDevice::controlButtonDown: return handleButtonDownOrUp (handler, reader, deviceIndex, packetTimestamp, true);
case MessageFromDevice::controlButtonUp: return handleButtonDownOrUp (handler, reader, deviceIndex, packetTimestamp, false);
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:
jassertfalse; // got an invalid message type, could be a corrupt packet, or a
// message type that the host doesn't expect to get
return false;
}
}
static bool handleTopology (Handler& handler, Packed7BitArrayReader& reader, bool newTopology)
{
if (reader.getRemainingBits() < DeviceCount::bits + ConnectionCount::bits)
{
jassertfalse; // not enough data available for this message type!
return false;
}
auto deviceProtocolVersion = reader.read<ProtocolVersion>();
if (deviceProtocolVersion > currentProtocolVersion)
{
jassertfalse;
return false;
}
const uint32 numDevices = reader.read<DeviceCount>();
const uint32 numConnections = reader.read<ConnectionCount>();
if ((uint32) reader.getRemainingBits() < numDevices * BitSizes::topologyDeviceInfo
+ numConnections * BitSizes::topologyConnectionInfo)
{
jassertfalse; // not enough data available for this message type!
return false;
}
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);
for (uint32 i = 0; i < numConnections; ++i)
handleTopologyConnection (handler, reader);
// Packet must be last in topology, otherwise wait for topology end message
if (numDevices < maxBlocksInTopologyPacket && numConnections < maxConnectionsInTopologyPacket)
handler.endTopology();
return true;
}
static bool handleTopologyEnd (Handler& handler, Packed7BitArrayReader& reader)
{
auto deviceProtocolVersion = reader.read<ProtocolVersion>();
if (deviceProtocolVersion > currentProtocolVersion)
{
jassertfalse;
return false;
}
handler.endTopology();
return true;
}
static void handleTopologyDevice (Handler& handler, Packed7BitArrayReader& reader)
{
DeviceStatus status;
for (uint32 i = 0; i < sizeof (BlockSerialNumber); ++i)
status.serialNumber.serial[i] = (uint8) reader.readBits (7);
status.index = (TopologyIndex) reader.readBits (topologyIndexBits);
status.batteryLevel = reader.read<BatteryLevel>();
status.batteryCharging = reader.read<BatteryCharging>();
handler.handleTopologyDevice (status);
}
static void handleTopologyConnection (Handler& handler, Packed7BitArrayReader& reader)
{
DeviceConnection connection;
connection.device1 = (uint8) reader.readBits (topologyIndexBits);
connection.port1 = reader.read<ConnectorPort>();
connection.device2 = (uint8) reader.readBits (topologyIndexBits);
connection.port2 = reader.read<ConnectorPort>();
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 handleName (Handler& handler, Packed7BitArrayReader& reader)
{
DeviceName name;
name.index = (TopologyIndex) reader.readBits (topologyIndexBits);
name.name.length = (uint8) reader.readBits (7);
for (uint32 i = 0; i < name.name.length; ++i)
name.name.name[i] = (uint8) reader.readBits (7);
handler.handleName (name);
return true;
}
static bool handleTouch (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex,
PacketTimestamp packetTimestamp, bool isStart, bool isEnd)
{
if (reader.getRemainingBits() < BitSizes::touchMessage - MessageType::bits)
{
jassertfalse; // not enough data available for this message type!
return false;
}
auto timeOffset = reader.read<PacketTimestampOffset>();
auto touchIndex = reader.read<TouchIndex>();
auto x = reader.read<TouchPosition::Xcoord>();
auto y = reader.read<TouchPosition::Ycoord>();
auto z = reader.read<TouchPosition::Zcoord>();
handleTouch (handler, deviceIndex, packetTimestamp.get() + timeOffset.get(),
touchIndex, { x, y, z }, { 0, 0, 0 }, isStart, isEnd);
return true;
}
static bool handleTouchWithVelocity (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex,
PacketTimestamp packetTimestamp, bool isStart, bool isEnd)
{
if (reader.getRemainingBits() < BitSizes::touchMessageWithVelocity - MessageType::bits)
{
jassertfalse; // not enough data available for this message type!
return false;
}
auto timeOffset = reader.read<PacketTimestampOffset>();
auto touchIndex = reader.read<TouchIndex>();
auto x = reader.read<TouchPosition::Xcoord>();
auto y = reader.read<TouchPosition::Ycoord>();
auto z = reader.read<TouchPosition::Zcoord>();
auto vx = reader.read<TouchVelocity::VXcoord>();
auto vy = reader.read<TouchVelocity::VYcoord>();
auto vz = reader.read<TouchVelocity::VZcoord>();
handleTouch (handler, deviceIndex, packetTimestamp.get() + timeOffset.get(),
touchIndex, { x, y, z }, { vx, vy, vz }, isStart, isEnd);
return true;
}
static void handleTouch (Handler& handler, TopologyIndex deviceIndex, uint32 timestamp, TouchIndex touchIndex,
TouchPosition position, TouchVelocity velocity, bool isStart, bool isEnd)
{
handler.handleTouchChange (deviceIndex, timestamp, touchIndex, position, velocity, isStart, isEnd);
}
static bool handleButtonDownOrUp (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex,
PacketTimestamp packetTimestamp, bool isDown)
{
if (reader.getRemainingBits() < BitSizes::controlButtonMessage - MessageType::bits)
{
jassertfalse; // not enough data available for this message type!
return false;
}
auto timeOffset = reader.read<PacketTimestampOffset>();
auto buttonID = reader.read<ControlButtonID>();
handler.handleControlButtonUpDown (deviceIndex, packetTimestamp.get() + timeOffset.get(), buttonID, isDown);
return true;
}
static bool handleCustomMessage (Handler& handler, Packed7BitArrayReader& reader,
TopologyIndex deviceIndex, PacketTimestamp packetTimestamp)
{
if (reader.getRemainingBits() < BitSizes::programEventMessage - MessageType::bits)
{
jassertfalse; // not enough data available for this message type!
return false;
}
int32 data[numProgramMessageInts] = {};
for (uint32 i = 0; i < numProgramMessageInts; ++i)
data[i] = (int32) reader.read<IntegerWithBitSize<32>>().get();
handler.handleCustomMessage (deviceIndex, packetTimestamp.get(), data);
return true;
}
static bool handlePacketACK (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex)
{
if (reader.getRemainingBits() < BitSizes::packetACK - MessageType::bits)
{
jassertfalse; // not enough data available for this message type!
return false;
}
handler.handlePacketACK (deviceIndex, reader.read<PacketCounter>());
return true;
}
static bool handleFirmwareUpdateACK (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex)
{
if (reader.getRemainingBits() < FirmwareUpdateACKCode::bits)
{
jassertfalse; // not enough data available for this message type!
return false;
}
handler.handleFirmwareUpdateACK (deviceIndex, reader.read<FirmwareUpdateACKCode>(), reader.read<FirmwareUpdateACKDetail>());
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;
while (reader.getRemainingBits() >= 7)
{
uint32 c = reader.read<IntegerWithBitSize<7>>();
message << (char) c;
}
handler.handleLogMessage (deviceIndex, message);
return true;
}
};
} // namespace BlocksProtocol
} // namespace juce

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,88 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
This topology source manages the topology of the physical Blocks devices
that are currently connected. It maintains a list of them and tells
listeners when physical devices are added or removed.
@tags{Blocks}
*/
class PhysicalTopologySource : public TopologySource
{
public:
/** Constructor. */
PhysicalTopologySource();
/** Destructor. */
~PhysicalTopologySource();
/** Returns the current physical topology. */
BlockTopology getCurrentTopology() const override;
/** Reset all touches */
void cancelAllActiveTouches() noexcept override;
//==========================================================================
/** For custom transport systems, this represents a connected device */
struct DeviceConnection
{
DeviceConnection();
virtual ~DeviceConnection();
virtual bool sendMessageToDevice (const void* data, size_t dataSize) = 0;
std::function<void (const void* data, size_t dataSize)> handleMessageFromDevice;
};
/** For custom transport systems, this represents a connected device */
struct DeviceDetector
{
DeviceDetector();
virtual ~DeviceDetector();
virtual juce::StringArray scanForDevices() = 0;
virtual DeviceConnection* openDevice (int index) = 0;
};
/** Constructor for custom transport systems. */
PhysicalTopologySource (DeviceDetector& detectorToUse);
static const char* const* getStandardLittleFootFunctions() noexcept;
protected:
virtual bool hasOwnServiceTimer() const;
virtual void handleTimerTick();
private:
//==========================================================================
struct Internal;
struct DetectorHolder;
std::unique_ptr<DetectorHolder> detector;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PhysicalTopologySource)
};
} // namespace juce

View File

@ -0,0 +1,106 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct RuleBasedTopologySource::Internal : public TopologySource::Listener,
private juce::AsyncUpdater
{
Internal (RuleBasedTopologySource& da, TopologySource& bd) : owner (da), detector (bd)
{
detector.addListener (this);
}
~Internal()
{
detector.removeListener (this);
}
void clearRules()
{
if (! rules.isEmpty())
{
rules.clear();
triggerAsyncUpdate();
}
}
void addRule (Rule* r)
{
if (r != nullptr)
{
rules.add (r);
triggerAsyncUpdate();
}
}
void topologyChanged() override
{
cancelPendingUpdate();
regenerateTopology();
}
void handleAsyncUpdate() override
{
topologyChanged();
}
void regenerateTopology()
{
auto newTopology = detector.getCurrentTopology();
for (auto rule : rules)
rule->transformTopology (newTopology);
if (topology != newTopology)
{
topology = newTopology;
owner.listeners.call ([] (TopologySource::Listener& l) { l.topologyChanged(); });
}
}
RuleBasedTopologySource& owner;
TopologySource& detector;
BlockTopology topology;
juce::OwnedArray<Rule> rules;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Internal)
};
RuleBasedTopologySource::RuleBasedTopologySource (TopologySource& d)
{
internal.reset (new Internal (*this, d));
}
RuleBasedTopologySource::~RuleBasedTopologySource()
{
internal = nullptr;
}
BlockTopology RuleBasedTopologySource::getCurrentTopology() const { return internal->topology; }
void RuleBasedTopologySource::clearRules() { internal->clearRules(); }
void RuleBasedTopologySource::addRule (Rule* r) { internal->addRule (r); }
} // namespace juce

View File

@ -0,0 +1,82 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** This topology source holds and applies a set of rules for transforming
one device topology into another one that may involve virtual and/or
aggregate devices.
Given an input PhysicalTopologySource and a set of Rule objects, this class
will apply the rules and present the resulting topology to clients.
@tags{Blocks}
*/
class RuleBasedTopologySource : public TopologySource
{
public:
/** Creates a RuleBasedTopologySource which wraps another TopologySource
passed in here.
*/
RuleBasedTopologySource (TopologySource&);
/** Destructor. */
~RuleBasedTopologySource();
//==========================================================================
/** Returns the currently active topology. */
BlockTopology getCurrentTopology() const;
/** A rule that can transform parts of a topology. */
struct Rule
{
virtual ~Rule() {}
/** Subclasses should implement this method and use it as their opportunity to
examine the given topology and modify it. For example they may want to substitute
one or more blocks for more specialised, aggregated Block objects.
*/
virtual void transformTopology (BlockTopology&) = 0;
};
/** Clears the list of active rules.
Calling this method will cause an asynchronous topology update if the new rule-set
results in a change to the topology.
*/
void clearRules();
/** Adds a rule to the list that will be applied.
The object passed-in will be owned by this object, so don't keep any references
to it.
Calling this method will cause an asynchronous topology update if the new rule-set
results in a change to the topology.
*/
void addRule (Rule*);
private:
//==========================================================================
struct Internal;
std::unique_ptr<Internal> internal;
};
} // namespace juce

View File

@ -0,0 +1,53 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** Describes a phyiscal connection between two ports of two block devices.
@tags{Blocks}
*/
struct BlockDeviceConnection
{
Block::UID device1, device2;
Block::ConnectionPort connectionPortOnDevice1, connectionPortOnDevice2;
bool operator== (const BlockDeviceConnection&) const noexcept;
bool operator!= (const BlockDeviceConnection&) const noexcept;
};
/** Describes a set of blocks and the connections between them.
@tags{Blocks}
*/
struct BlockTopology
{
Block::Array blocks;
juce::Array<BlockDeviceConnection> connections;
bool operator== (const BlockTopology&) const noexcept;
bool operator!= (const BlockTopology&) const noexcept;
};
} // namespace juce

View File

@ -0,0 +1,59 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/** Base class for an entity that provides access to a blocks topology.
@tags{Blocks}
*/
class TopologySource
{
public:
//==========================================================================
/** Destructor. */
virtual ~TopologySource() {}
/** Returns the current topology that this object manages. */
virtual BlockTopology getCurrentTopology() const = 0;
//==========================================================================
/** Used to receive callbacks for topology changes */
struct Listener
{
virtual ~Listener() {}
virtual void topologyChanged() = 0;
};
void addListener (Listener* l) { listeners.add (l); }
void removeListener (Listener* l) { listeners.remove (l); }
/** Invoke this to force touches-off on all physical devices. */
virtual void cancelAllActiveTouches() noexcept {}
protected:
//==========================================================================
juce::ListenerList<Listener> listeners;
};
} // namespace juce

View File

@ -0,0 +1,87 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
BitmapLEDProgram::BitmapLEDProgram (Block& b) : Program (b) {}
/*
The heap format for this program is just an array of 15x15 5:6:5 colours,
and the program just copies them onto the screen each frame.
*/
void BitmapLEDProgram::setLED (uint32 x, uint32 y, LEDColour colour)
{
if (auto ledGrid = block.getLEDGrid())
{
auto w = (uint32) ledGrid->getNumColumns();
auto h = (uint32) ledGrid->getNumRows();
if (x < w && y < h)
{
auto bit = (x + y * w) * 16;
block.setDataBits (bit, 5, colour.getRed() >> 3);
block.setDataBits (bit + 5, 6, colour.getGreen() >> 2);
block.setDataBits (bit + 11, 5, colour.getBlue() >> 3);
}
}
else
{
jassertfalse;
}
}
juce::String BitmapLEDProgram::getLittleFootProgram()
{
String program (R"littlefoot(
#heapsize: 15 * 15 * 2
void repaint()
{
for (int y = 0; y < NUM_ROWS; ++y)
{
for (int x = 0; x < NUM_COLUMNS; ++x)
{
int bit = (x + y * NUM_COLUMNS) * 16;
fillPixel (makeARGB (255,
getHeapBits (bit, 5) << 3,
getHeapBits (bit + 5, 6) << 2,
getHeapBits (bit + 11, 5) << 3), x, y);
}
}
}
)littlefoot");
if (auto ledGrid = block.getLEDGrid())
return program.replace ("NUM_COLUMNS", juce::String (ledGrid->getNumColumns()))
.replace ("NUM_ROWS", juce::String (ledGrid->getNumRows()));
jassertfalse;
return {};
}
} // namespace juce

View File

@ -0,0 +1,42 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
A simple Program to set the colours of individual LEDs.
@tags{Blocks}
*/
struct BitmapLEDProgram : public Block::Program
{
BitmapLEDProgram (Block&);
/** Set the colour of the LED at coordinates {x, y}. */
void setLED (uint32 x, uint32 y, LEDColour);
private:
juce::String getLittleFootProgram() override;
};
} // namespace juce

View File

@ -0,0 +1,881 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
DrumPadGridProgram::DrumPadGridProgram (Block& b) : Program (b) {}
int DrumPadGridProgram::getPadIndex (float posX, float posY) const
{
posX = juce::jmin (0.99f, posX / block.getWidth());
posY = juce::jmin (0.99f, posY / block.getHeight());
const uint32 offset = block.getDataByte (visiblePads_byte) ? numColumns1_byte : numColumns0_byte;
const int numColumns = block.getDataByte (offset + numColumns0_byte);
const int numRows = block.getDataByte (offset + numRows0_byte);
return int (posX * numColumns) + int (posY * numRows) * numColumns;
}
void DrumPadGridProgram::startTouch (float startX, float startY)
{
const auto padIdx = getPadIndex (startX, startY);
for (size_t i = 0; i < 4; ++i)
{
if (block.getDataByte (touchedPads_byte + i) == 0)
{
block.setDataByte (touchedPads_byte + i, static_cast<uint8> (padIdx + 1));
break;
}
}
}
void DrumPadGridProgram::endTouch (float startX, float startY)
{
const auto padIdx = getPadIndex (startX, startY);
for (size_t i = 0; i < 4; ++i)
if (block.getDataByte (touchedPads_byte + i) == (padIdx + 1))
block.setDataByte (touchedPads_byte + i, 0);
}
void DrumPadGridProgram::sendTouch (float x, float y, float z, LEDColour colour)
{
Block::ProgramEventMessage e;
e.values[0] = 0x20000000
+ (juce::jlimit (0, 255, juce::roundToInt (x * (255.0f / block.getWidth()))) << 16)
+ (juce::jlimit (0, 255, juce::roundToInt (y * (255.0f / block.getHeight()))) << 8)
+ juce::jlimit (0, 255, juce::roundToInt (z * 255.0f));
e.values[1] = (int32) colour.getARGB();
block.sendProgramEvent (e);
}
//==============================================================================
void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills)
{
uint8 visiblePads = block.getDataByte (visiblePads_byte);
setGridFills (numColumns, numRows, fills, visiblePads * numColumns1_byte);
}
void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills, uint32 byteOffset)
{
jassert (numColumns * numRows == fills.size());
block.setDataByte (byteOffset + numColumns0_byte, (uint8) numColumns);
block.setDataByte (byteOffset + numRows0_byte, (uint8) numRows);
uint32 i = 0;
for (auto fill : fills)
{
if (i >= maxNumPads)
{
jassertfalse;
break;
}
const uint32 colourOffsetBytes = byteOffset + colours0_byte + i * colourSizeBytes;
const uint32 colourOffsetBits = colourOffsetBytes * 8;
block.setDataBits (colourOffsetBits, 5, fill.colour.getRed() >> 3);
block.setDataBits (colourOffsetBits + 5, 6, fill.colour.getGreen() >> 2);
block.setDataBits (colourOffsetBits + 11, 5, fill.colour.getBlue() >> 3);
block.setDataByte (byteOffset + fillTypes0_byte + i, static_cast<uint8> (fill.fillType));
++i;
}
}
void DrumPadGridProgram::triggerSlideTransition (int newNumColumns, int newNumRows,
const juce::Array<GridFill>& newFills, SlideDirection direction)
{
uint8 newVisible = block.getDataByte (visiblePads_byte) ? 0 : 1;
setGridFills (newNumColumns, newNumRows, newFills, newVisible * numColumns1_byte);
block.setDataByte (visiblePads_byte, newVisible);
block.setDataByte (slideDirection_byte, (uint8) direction);
}
//==============================================================================
void DrumPadGridProgram::setPadAnimationState (uint32 padIdx, double loopTimeSecs, double currentProgress)
{
// Only 16 animated pads are supported.
jassert (padIdx < 16);
// Compensate for bluetooth latency & led resolution, tweaked by eye for POS app.
currentProgress = std::fmod (currentProgress + 0.1, 1.0);
uint16 aniValue = uint16 (juce::roundToInt ((255 << 8) * currentProgress));
uint16 aniIncrement = loopTimeSecs > 0.0 ? uint16 (juce::roundToInt (((255 << 8) / 25.0) / loopTimeSecs)) : 0;
uint32 offset = 8 * animationTimers_byte + 32 * padIdx;
block.setDataBits (offset, 16, aniValue);
block.setDataBits (offset + 16, 16, aniIncrement);
}
void DrumPadGridProgram::suspendAnimations()
{
for (uint32 i = 0; i < 16; ++i)
{
uint32 offset = 8 * animationTimers_byte + 32 * i;
block.setDataBits (offset + 16, 16, 0);
}
// Hijack touch dimming
block.setDataByte (touchedPads_byte, 255);
}
void DrumPadGridProgram::resumeAnimations()
{
// Unhijack touch dimming
block.setDataByte (touchedPads_byte, 0);
}
//==============================================================================
juce::String DrumPadGridProgram::getLittleFootProgram()
{
if (block.versionNumber.isEmpty() || block.versionNumber.compare ("0.2.5") < 0)
return getLittleFootProgramPre25();
return getLittleFootProgramPost25();
}
juce::String DrumPadGridProgram::getLittleFootProgramPre25() const
{
// Uses its own heatmap, not the one provided in newer firmware
// Also can't use blocks config, introduced in 2.5.
return R"littlefoot(
#heapsize: 1351
int dimFactor;
int dimDelay;
int slideAnimationProgress;
int lastVisiblePads;
int getGridColour (int index, int colourMapOffset)
{
int bit = (2 + colourMapOffset) * 8 + index * 16;
return makeARGB (255,
getHeapBits (bit, 5) << 3,
getHeapBits (bit + 5, 6) << 2,
getHeapBits (bit + 11, 5) << 3);
}
// Returns the current progress and also increments it for next frame
int getAnimationProgress (int index)
{
// Only 16 animated pads supported
if (index > 15)
return 0;
int offsetBits = 162 * 8 + index * 32;
int currentProgress = getHeapBits (offsetBits, 16);
int increment = getHeapBits (offsetBits + 16, 16);
int nextFrame = currentProgress + increment;
// Set incremented 16 bit number.
setHeapByte (162 + index * 4, nextFrame & 0xff);
setHeapByte (163 + index * 4, nextFrame >> 8);
return currentProgress;
}
void outlineRect (int colour, int x, int y, int w)
{
fillRect (colour, x, y, w, 1);
fillRect (colour, x, y + w - 1, w, 1);
fillRect (colour, x, y + 1, 1, w - 1);
fillRect (colour, x + w - 1, y + 1, 1, w - 1);
}
void drawPlus (int colour, int x, int y, int w)
{
fillRect (colour, x, y + (w / 2), w, 1);
fillRect (colour, x + (w / 2), y, 1, w);
}
void fillGradientRect (int colour, int x, int y, int w)
{
if (colour != 0xff000000)
{
int divisor = w + w - 1;
for (int yy = 0; yy < w; ++yy)
{
for (int xx = yy; xx < w; ++xx)
{
int gradColour = blendARGB (colour, makeARGB (((xx + yy) * 250) / divisor, 0, 0, 0));
setLED (x + xx, y + yy, gradColour);
setLED (x + yy, y + xx, gradColour);
}
}
}
}
// TODO: Tom M: This is massaged to work with 3x3 pads and for dots to sync
// with Apple POS loop length. Rework to be more robust & flexible.
void drawPizzaLED (int colour, int x, int y, int w, int progress)
{
--w;
x += 1;
int numToDo = ((8 * progress) / 255) + 1;
int totalLen = w * 4;
for (int i = 1; i <= numToDo; ++i)
{
setLED (x, y, colour);
if (i < w)
++x;
else if (i < (w * 2))
++y;
else if (i < (w * 3))
--x;
else if (i < totalLen)
--y;
}
}
void drawPad (int padX, int padY, int padW,
int colour, int fill, int animateProgress)
{
animateProgress >>= 8; // 16 bit to 8 bit
int halfW = padW / 2;
if (fill == 0) // Gradient fill
{
fillGradientRect (colour, padX, padY, padW);
}
else if (fill == 1) // Filled
{
fillRect (colour, padX, padY, padW, padW);
}
else if (fill == 2) // Hollow
{
outlineRect (colour, padX, padY, padW);
}
else if (fill == 3) // Hollow with plus
{
outlineRect (colour, padX, padY, padW);
drawPlus (0xffffffff, padX, padY, padW);
}
else if (fill == 4) // Pulsing dot
{
int pulseCol = blendARGB (colour, makeARGB (animateProgress, 0, 0, 0));
setLED (padX + halfW, padY + halfW, pulseCol);
}
else if (fill == 5) // Blinking dot
{
int blinkCol = animateProgress > 64 ? makeARGB (255, 0, 0, 0) : colour;
setLED (padX + halfW, padY + halfW, blinkCol);
}
else if (fill == 6) // Pizza filled
{
outlineRect (blendARGB (colour, makeARGB (220, 0, 0, 0)), padX, padY, padW); // Dim outline
setLED (padX + halfW, padY + halfW, colour); // Bright centre
drawPizzaLED (colour, padX, padY, padW, animateProgress);
}
else if (fill == 7) // Pizza hollow
{
outlineRect (blendARGB (colour, makeARGB (220, 0, 0, 0)), padX, padY, padW); // Dim outline
drawPizzaLED (colour, padX, padY, padW, animateProgress);
return;
}
}
void fadeHeatMap()
{
for (int i = 0; i < 225; ++i)
{
int colourOffset = 226 + i * 4;
int colour = getHeapInt (colourOffset);
int alpha = (colour >> 24) & 0xff;
if (alpha > 0)
{
alpha -= getHeapByte (1126 + i);
setHeapInt (colourOffset, alpha < 0 ? 0 : ((alpha << 24) | (colour & 0xffffff)));
}
}
}
void addToHeatMap (int x, int y, int colour)
{
if (x >= 0 && y >= 0 && x < 15 && y < 15)
{
int offset = 226 + 4 * (x + y * 15);
colour = blendARGB (getHeapInt (offset), colour);
setHeapInt (offset, colour);
int decay = ((colour >> 24) & 0xff) / 14; // change divisor to change trail times
offset = 1126 + (x + y * 15);
setHeapByte (offset, decay > 0 ? decay : 1);
}
}
int getHeatmapColour (int x, int y)
{
return getHeapInt (226 + 4 * (x + y * 15));
}
int isPadActive (int index)
{
if (getHeapInt (158) == 0) // None active
return 0;
++index;
return index == getHeapByte (158) ||
index == getHeapByte (159) ||
index == getHeapByte (160) ||
index == getHeapByte (161);
}
void updateDimFactor()
{
if (getHeapInt (158) == 0)
{
if (--dimDelay <= 0)
{
dimFactor -= 12;
if (dimFactor < 0)
dimFactor = 0;
}
}
else
{
dimFactor = 180;
dimDelay = 12;
}
}
void drawPads (int offsetX, int offsetY, int colourMapOffset)
{
int padsPerSide = getHeapByte (0 + colourMapOffset);
if (padsPerSide < 2)
return;
int blockW = 15 / padsPerSide;
int blockPlusGapW = blockW + (15 - padsPerSide * blockW) / (padsPerSide - 1);
for (int padY = 0; padY < padsPerSide; ++padY)
{
for (int padX = 0; padX < padsPerSide; ++padX)
{
int ledX = offsetX + padX * blockPlusGapW;
int ledY = offsetY + padY * blockPlusGapW;
if (ledX < 15 &&
ledY < 15 &&
(ledX + blockW) >= 0 &&
(ledY + blockW) >= 0)
{
int padIdx = padX + padY * padsPerSide;
bool padActive = isPadActive (padIdx);
int blendCol = padActive ? 255 : 0;
int blendAmt = padActive ? dimFactor >> 1 : dimFactor;
int colour = blendARGB (getGridColour (padIdx, colourMapOffset),
makeARGB (blendAmt, blendCol, blendCol, blendCol));
int fillType = getHeapByte (colourMapOffset + 52 + padIdx);
int animate = getAnimationProgress (padIdx);
drawPad (ledX, ledY, blockW, colour, fillType, animate);
}
}
}
}
void slideAnimatePads()
{
int nowVisible = getHeapByte (155);
if (lastVisiblePads != nowVisible)
{
lastVisiblePads = nowVisible;
if (slideAnimationProgress <= 0)
slideAnimationProgress = 15;
}
// If animation is complete, draw normally.
if (slideAnimationProgress <= 0)
{
drawPads (0, 0, 78 * nowVisible);
slideAnimationProgress = 0;
}
else
{
int direction = getHeapByte (156);
slideAnimationProgress -= 1;
int inPos = nowVisible == 0 ? 0 : 78;
int outPos = nowVisible == 0 ? 78 : 0;
if (direction == 0) // Up
{
drawPads (0, slideAnimationProgress - 16, outPos);
drawPads (0, slideAnimationProgress, inPos);
}
else if (direction == 1) // Down
{
drawPads (0, 16 - slideAnimationProgress, outPos);
drawPads (0, 0 - slideAnimationProgress, inPos);
}
else if (direction == 2) // Left
{
drawPads (16 - slideAnimationProgress, 0, outPos);
drawPads (slideAnimationProgress, 0, inPos);
}
else if (direction == 3) // Right
{
drawPads (16 - slideAnimationProgress, 0, outPos);
drawPads (0 - slideAnimationProgress, 0, inPos);
}
else // None
{
drawPads (0, 0, 78 * nowVisible);
slideAnimationProgress = 0;
}
}
}
void repaint()
{
// showErrorOnFail, showRepaintTime, showMovingDot
//enableDebug (true, true, false);
// Clear LEDs to black, update dim animation
fillRect (0xff000000, 0, 0, 15, 15);
updateDimFactor();
// Does the main painting of pads
slideAnimatePads();
// Overlay heatmap
for (int y = 0; y < 15; ++y)
for (int x = 0; x < 15; ++x)
blendLED (x, y, getHeatmapColour (x, y));
fadeHeatMap();
}
// DrumPadGridProgram::sendTouch results in this callback, giving
// us more touch updates per frame and therefore smoother trails.
void handleMessage (int pos, int colour, int xx)
{
handleMessage (pos, colour);
}
void handleMessage (int pos, int colour)
{
if ((pos >> 24) != 0x20)
return;
int tx = ((pos >> 16) & 0xff) - 13;
int ty = ((pos >> 8) & 0xff) - 13;
int tz = pos & 0xff;
tz = tz > 30 ? tz : 30;
int ledCentreX = tx >> 4;
int ledCentreY = ty >> 4;
int adjustX = (tx - (ledCentreX << 4)) >> 2;
int adjustY = (ty - (ledCentreY << 4)) >> 2;
for (int dy = -2; dy <= 2; ++dy)
{
for (int dx = -2; dx <= 2; ++dx)
{
int distance = dx * dx + dy * dy;
int level = distance == 0 ? 255 : (distance == 1 ? 132 : (distance < 5 ? 9 : (distance == 5 ? 2 : 0)));
level += (dx * adjustX);
level += (dy * adjustY);
level = (tz * level) >> 8;
if (level > 0)
addToHeatMap (ledCentreX + dx, ledCentreY + dy,
makeARGB (level, colour >> 16, colour >> 8, colour));
}
}
}
)littlefoot";
}
juce::String DrumPadGridProgram::getLittleFootProgramPost25() const
{
// Uses heatmap provided in firmware (so the program's smaller)
// Initialises config items introduced in firmware 2.5
return R"littlefoot(
#heapsize: 256
int dimFactor;
int dimDelay;
int slideAnimationProgress;
int lastVisiblePads;
bool gammaCorrected;
void initialise()
{
for (int i = 0; i < 32; ++i)
setLocalConfigActiveState (i, true, true);
// Enable gamma correction if supported on hardware
setLocalConfig (33, 1);
gammaCorrected = getLocalConfig (33) > 0;
}
int getGridColour (int index, int colourMapOffset)
{
int bit = (2 + colourMapOffset) * 8 + index * 16;
return makeARGB (255,
getHeapBits (bit, 5) << 3,
getHeapBits (bit + 5, 6) << 2,
getHeapBits (bit + 11, 5) << 3);
}
// Returns the current progress and also increments it for next frame
int getAnimationProgress (int index)
{
// Only 16 animated pads supported
if (index > 15)
return 0;
int offsetBits = 162 * 8 + index * 32;
int currentProgress = getHeapBits (offsetBits, 16);
int increment = getHeapBits (offsetBits + 16, 16);
int nextFrame = currentProgress + increment;
// Set incremented 16 bit number.
setHeapByte (162 + index * 4, nextFrame & 0xff);
setHeapByte (163 + index * 4, nextFrame >> 8);
return currentProgress;
}
void outlineRect (int colour, int x, int y, int w)
{
fillRect (colour, x, y, w, 1);
fillRect (colour, x, y + w - 1, w, 1);
fillRect (colour, x, y + 1, 1, w - 1);
fillRect (colour, x + w - 1, y + 1, 1, w - 1);
}
void drawPlus (int colour, int x, int y, int w)
{
fillRect (colour, x, y + (w / 2), w, 1);
fillRect (colour, x + (w / 2), y, 1, w);
}
void fillGradientRect (int colour, int x, int y, int w)
{
if (colour != 0xff000000)
{
int divisor = w + w - 1;
for (int yy = 0; yy < w; ++yy)
{
for (int xx = yy; xx < w; ++xx)
{
int gradColour = blendARGB (colour, makeARGB (((xx + yy) * 250) / divisor, 0, 0, 0));
fillPixel (gradColour, x + xx, y + yy);
fillPixel (gradColour, x + yy, y + xx);
}
}
}
}
// TODO: Tom M: This is massaged to work with 3x3 pads and for dots to sync
// with Apple POS loop length. Rework to be more robust & flexible.
void drawPizzaLED (int colour, int x, int y, int w, int progress)
{
--w;
x += 1;
int numToDo = ((8 * progress) / 255) + 1;
int totalLen = w * 4;
for (int i = 1; i <= numToDo; ++i)
{
fillPixel (colour, x, y);
if (i < w)
++x;
else if (i < (w * 2))
++y;
else if (i < (w * 3))
--x;
else if (i < totalLen)
--y;
}
}
void drawPad (int padX, int padY, int padW,
int colour, int fill, int animateProgress)
{
animateProgress >>= 8; // 16 bit to 8 bit
int halfW = padW / 2;
if (fill == 0) // Gradient fill
{
fillGradientRect (colour, padX, padY, padW);
}
else if (fill == 1) // Filled
{
fillRect (colour, padX, padY, padW, padW);
}
else if (fill == 2) // Hollow
{
outlineRect (colour, padX, padY, padW);
}
else if (fill == 3) // Hollow with plus
{
outlineRect (colour, padX, padY, padW);
drawPlus (0xffffffff, padX, padY, padW);
}
else if (fill == 4) // Pulsing dot
{
int pulseCol = blendARGB (colour, makeARGB (animateProgress, 0, 0, 0));
fillPixel (pulseCol, padX + halfW, padY + halfW);
}
else if (fill == 5) // Blinking dot
{
int blinkCol = animateProgress > 64 ? 0xff000000 : colour;
fillPixel (blinkCol, padX + halfW, padY + halfW);
}
else if (fill == 6) // Pizza filled
{
outlineRect (blendARGB (colour, 0xdc000000), padX, padY, padW); // Dim outline
fillPixel (colour, padX + halfW, padY + halfW); // Bright centre
drawPizzaLED (colour, padX, padY, padW, animateProgress);
}
else // Pizza hollow
{
outlineRect (blendARGB (colour, 0xdc000000), padX, padY, padW); // Dim outline
drawPizzaLED (colour, padX, padY, padW, animateProgress);
}
}
int isPadActive (int index)
{
if (getHeapInt (158) == 0) // None active
return 0;
++index;
return index == getHeapByte (158) ||
index == getHeapByte (159) ||
index == getHeapByte (160) ||
index == getHeapByte (161);
}
void updateDimFactor()
{
if (getHeapInt (158) == 0)
{
if (--dimDelay <= 0)
{
dimFactor -= 12;
if (dimFactor < 0)
dimFactor = 0;
}
}
else
{
dimFactor = gammaCorrected ? 100 : 180;
dimDelay = 12;
}
}
void drawPads (int offsetX, int offsetY, int colourMapOffset)
{
int padsPerSide = getHeapByte (0 + colourMapOffset);
if (padsPerSide < 2)
return;
int blockW = 15 / padsPerSide;
int blockPlusGapW = blockW + (15 - padsPerSide * blockW) / (padsPerSide - 1);
for (int padY = 0; padY < padsPerSide; ++padY)
{
for (int padX = 0; padX < padsPerSide; ++padX)
{
int ledX = offsetX + padX * blockPlusGapW;
int ledY = offsetY + padY * blockPlusGapW;
if (ledX < 15 &&
ledY < 15 &&
(ledX + blockW) >= 0 &&
(ledY + blockW) >= 0)
{
int padIdx = padX + padY * padsPerSide;
bool padActive = isPadActive (padIdx);
int blendCol = padActive ? 255 : 0;
int blendAmt = padActive ? dimFactor >> 1 : dimFactor;
int colour = blendARGB (getGridColour (padIdx, colourMapOffset),
makeARGB (blendAmt, blendCol, blendCol, blendCol));
int fillType = getHeapByte (colourMapOffset + 52 + padIdx);
int animate = getAnimationProgress (padIdx);
drawPad (ledX, ledY, blockW, colour, fillType, animate);
}
}
}
}
void slideAnimatePads()
{
int nowVisible = getHeapByte (155);
if (lastVisiblePads != nowVisible)
{
lastVisiblePads = nowVisible;
if (slideAnimationProgress <= 0)
slideAnimationProgress = 15;
}
// If animation is complete, draw normally.
if (slideAnimationProgress <= 0)
{
drawPads (0, 0, 78 * nowVisible);
slideAnimationProgress = 0;
}
else
{
int direction = getHeapByte (156);
slideAnimationProgress -= 1;
int inPos = nowVisible == 0 ? 0 : 78;
int outPos = nowVisible == 0 ? 78 : 0;
if (direction == 0) // Up
{
drawPads (0, slideAnimationProgress - 16, outPos);
drawPads (0, slideAnimationProgress, inPos);
}
else if (direction == 1) // Down
{
drawPads (0, 16 - slideAnimationProgress, outPos);
drawPads (0, 0 - slideAnimationProgress, inPos);
}
else if (direction == 2) // Left
{
drawPads (16 - slideAnimationProgress, 0, outPos);
drawPads (slideAnimationProgress, 0, inPos);
}
else if (direction == 3) // Right
{
drawPads (16 - slideAnimationProgress, 0, outPos);
drawPads (0 - slideAnimationProgress, 0, inPos);
}
else // None
{
drawPads (0, 0, 78 * nowVisible);
slideAnimationProgress = 0;
}
}
}
void repaint()
{
// showErrorOnFail, showRepaintTime, showMovingDot
//enableDebug (true, true, false);
// Clear LEDs to black, update dim animation
fillRect (0xff000000, 0, 0, 15, 15);
updateDimFactor();
// Does the main painting of pads
slideAnimatePads();
// Overlay heatmap
drawPressureMap();
fadePressureMap();
}
// DrumPadGridProgram::sendTouch results in this callback, giving
// us more touch updates per frame and therefore smoother trails.
void handleMessage (int pos, int colour, int dummy)
{
if ((pos >> 24) != 0x20)
return;
int tx = (pos >> 16) & 0xff;
int ty = (pos >> 8) & 0xff;
int tz = pos & 0xff;
addPressurePoint (colour,
tx * (2.0 / (256 + 20)),
ty * (2.0 / (256 + 20)),
tz * (1.0 / 3.0));
}
)littlefoot";
}
} // namespace juce

View File

@ -0,0 +1,126 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2017 - ROLI Ltd.
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided 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.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
@tags{Blocks}
*/
struct DrumPadGridProgram : public Block::Program
{
DrumPadGridProgram (Block&);
//==============================================================================
/** These let the program dim pads which aren't having gestures performed on them. */
void startTouch (float startX, float startY);
void endTouch (float startX, float startY);
/** Creates trail effects similar to the onscreen pad trails. */
void sendTouch (float x, float y, float z, LEDColour);
//==============================================================================
/** Call this to match animations to the project tempo.
@param padIdx The pad to update. 16 animated pads are supported, so 0 - 15.
@param loopTimeSecs The length of time for the pad's animation to loop in seconds. 0 will stop the animation.
@param currentProgress The starting progress of the animation. 0.0 - 1.0.
*/
void setPadAnimationState (uint32 padIdx, double loopTimeSecs, double currentProgress);
/** If the app needs to close down or suspend, use these to pause & dim animations. */
void suspendAnimations();
void resumeAnimations();
//==============================================================================
/** Set how each pad in the grid looks. */
struct GridFill
{
enum FillType : uint8
{
gradient = 0,
filled = 1,
hollow = 2,
hollowPlus = 3,
// Animated pads
dotPulsing = 4,
dotBlinking = 5,
pizzaFilled = 6,
pizzaHollow = 7,
};
LEDColour colour;
FillType fillType;
};
void setGridFills (int numColumns, int numRows,
const juce::Array<GridFill>&);
/** Set up a new pad layout, with a slide animation from the old to the new. */
enum SlideDirection : uint8
{
up = 0,
down = 1,
left = 2,
right = 3,
none = 255
};
void triggerSlideTransition (int newNumColumns, int newNumRows,
const juce::Array<GridFill>& newFills, SlideDirection);
private:
//==============================================================================
/** Shared data heap is laid out as below. There is room for two sets of
pad layouts, colours and fill types to allow animation between two states. */
static constexpr uint32 numColumns0_byte = 0; // 1 byte
static constexpr uint32 numRows0_byte = 1; // 1 byte (ignored for the moment: always square pads to save cycles)
static constexpr uint32 colours0_byte = 2; // 2 byte x 25 (5:6:5 bits for rgb)
static constexpr uint32 fillTypes0_byte = 52; // 1 byte x 25
static constexpr uint32 numColumns1_byte = 78; // 1 byte
static constexpr uint32 numRows1_byte = 79; // 1 byte
static constexpr uint32 colours1_byte = 80; // 2 byte x 25 (5:6:5 bits for rgb)
static constexpr uint32 fillTypes1_byte = 130; // 1 byte x 25
static constexpr uint32 visiblePads_byte = 155; // 1 byte (i.e. which set of colours/fills to use, 0 or 1)
static constexpr uint32 slideDirection_byte = 156; // 1 byte
static constexpr uint32 touchedPads_byte = 158; // 1 byte x 4 (Zero means empty slot, so stores padIdx + 1)
static constexpr uint32 animationTimers_byte = 162; // 4 byte x 16 (16:16 bits counter:increment)
static constexpr uint32 totalHeapSize = 226;
static constexpr uint32 maxNumPads = 25;
static constexpr uint32 colourSizeBytes = 2;
int getPadIndex (float posX, float posY) const;
void setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills, uint32 byteOffset);
juce::String getLittleFootProgram() override;
juce::String getLittleFootProgramPre25() const;
juce::String getLittleFootProgramPost25() const;
};
} // namespace juce