Browse Source

BLOCKS SDK: New and updated version of juce_blocks_basics, adding functionality and compatibility with latest Dashboard-compatible firmware

tags/2021-05-28
jules 8 years ago
parent
commit
f207ebb6d8
19 changed files with 1292 additions and 556 deletions
  1. +8
    -8
      examples/BLOCKS/BlocksDrawing/Source/MainComponent.h
  2. +1
    -1
      examples/BLOCKS/BlocksMonitor/Source/BlockComponents.h
  3. +13
    -7
      examples/BLOCKS/BlocksSynth/Source/MainComponent.h
  4. +9
    -14
      examples/BLOCKS/BlocksSynth/Source/WaveshapeProgram.h
  5. +323
    -0
      modules/juce_blocks_basics/LittleFootFunctions.txt
  6. +7
    -3
      modules/juce_blocks_basics/blocks/juce_Block.cpp
  7. +91
    -2
      modules/juce_blocks_basics/blocks/juce_Block.h
  8. +9
    -72
      modules/juce_blocks_basics/blocks/juce_LEDGrid.h
  9. +151
    -60
      modules/juce_blocks_basics/littlefoot/juce_LittleFootCompiler.h
  10. +74
    -63
      modules/juce_blocks_basics/littlefoot/juce_LittleFootRemoteHeap.h
  11. +84
    -28
      modules/juce_blocks_basics/littlefoot/juce_LittleFootRunner.h
  12. +67
    -12
      modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h
  13. +14
    -0
      modules/juce_blocks_basics/protocol/juce_HostPacketBuilder.h
  14. +47
    -0
      modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h
  15. +313
    -137
      modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp
  16. +26
    -19
      modules/juce_blocks_basics/visualisers/juce_BitmapLEDProgram.cpp
  17. +2
    -3
      modules/juce_blocks_basics/visualisers/juce_BitmapLEDProgram.h
  18. +50
    -119
      modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.cpp
  19. +3
    -8
      modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.h

+ 8
- 8
examples/BLOCKS/BlocksDrawing/Source/MainComponent.h View File

@@ -233,7 +233,7 @@ public:
scaleX = (float) (grid->getNumColumns()) / activeBlock->getWidth(); scaleX = (float) (grid->getNumColumns()) / activeBlock->getWidth();
scaleY = (float) (grid->getNumRows()) / activeBlock->getHeight(); scaleY = (float) (grid->getNumRows()) / activeBlock->getHeight();
setLEDProgram (grid);
setLEDProgram (*activeBlock);
} }
// Make the on screen Lighpad component visible // Make the on screen Lighpad component visible
@@ -286,7 +286,7 @@ private:
{ {
// Switch to canvas mode and set the LEDGrid program // Switch to canvas mode and set the LEDGrid program
currentMode = canvas; currentMode = canvas;
setLEDProgram (activeBlock->getLEDGrid());
setLEDProgram (*activeBlock);
} }
} }
@@ -325,7 +325,7 @@ private:
{ {
// Switch to colour palette mode and set the LEDGrid program // Switch to colour palette mode and set the LEDGrid program
currentMode = colourPalette; currentMode = colourPalette;
setLEDProgram (activeBlock->getLEDGrid());
setLEDProgram (*activeBlock);
} }
stopTimer(); stopTimer();
@@ -350,7 +350,7 @@ private:
} }
/** Sets the LEDGrid Program for the selected mode */ /** Sets the LEDGrid Program for the selected mode */
void setLEDProgram (LEDGrid* grid)
void setLEDProgram (Block& block)
{ {
canvasProgram = nullptr; canvasProgram = nullptr;
colourPaletteProgram = nullptr; colourPaletteProgram = nullptr;
@@ -358,10 +358,10 @@ private:
if (currentMode == canvas) if (currentMode == canvas)
{ {
// Create a new BitmapLEDProgram for the LEDGrid // Create a new BitmapLEDProgram for the LEDGrid
canvasProgram = new BitmapLEDProgram (*grid);
canvasProgram = new BitmapLEDProgram (block);
// Set the LEDGrid program // Set the LEDGrid program
grid->setProgram (canvasProgram);
block.setProgram (canvasProgram);
// Redraw any previously drawn LEDs // Redraw any previously drawn LEDs
redrawLEDs(); redrawLEDs();
@@ -369,10 +369,10 @@ private:
else if (currentMode == colourPalette) else if (currentMode == colourPalette)
{ {
// Create a new DrumPadGridProgram for the LEDGrid // Create a new DrumPadGridProgram for the LEDGrid
colourPaletteProgram = new DrumPadGridProgram (*grid);
colourPaletteProgram = new DrumPadGridProgram (block);
// Set the LEDGrid program // Set the LEDGrid program
grid->setProgram (colourPaletteProgram);
block.setProgram (colourPaletteProgram);
// Setup the grid layout // Setup the grid layout
colourPaletteProgram->setGridFills (layout.numColumns, colourPaletteProgram->setGridFills (layout.numColumns,


+ 1
- 1
examples/BLOCKS/BlocksMonitor/Source/BlockComponents.h View File

@@ -28,7 +28,7 @@ public:
// If this is a Lightpad then set the grid program to be blank // If this is a Lightpad then set the grid program to be blank
if (auto grid = block->getLEDGrid()) if (auto grid = block->getLEDGrid())
grid->setProgram (new BitmapLEDProgram(*grid));
block->setProgram (new BitmapLEDProgram (*block));
// If this is a Lightpad then redraw it at 25Hz // If this is a Lightpad then redraw it at 25Hz
if (block->getType() == Block::lightPadBlock) if (block->getType() == Block::lightPadBlock)


+ 13
- 7
examples/BLOCKS/BlocksSynth/Source/MainComponent.h View File

@@ -145,7 +145,7 @@ public:
scaleX = static_cast<float> (grid->getNumColumns() - 1) / activeBlock->getWidth(); scaleX = static_cast<float> (grid->getNumColumns() - 1) / activeBlock->getWidth();
scaleY = static_cast<float> (grid->getNumRows() - 1) / activeBlock->getHeight(); scaleY = static_cast<float> (grid->getNumRows() - 1) / activeBlock->getHeight();
setLEDProgram (grid);
setLEDProgram (*activeBlock);
} }
break; break;
@@ -227,7 +227,7 @@ private:
currentMode = waveformSelectionMode; currentMode = waveformSelectionMode;
// Set the LEDGrid program to the new mode // Set the LEDGrid program to the new mode
setLEDProgram (activeBlock->getLEDGrid());
setLEDProgram (*activeBlock);
} }
#if JUCE_IOS #if JUCE_IOS
@@ -259,15 +259,15 @@ private:
} }
/** Sets the LEDGrid Program for the selected mode */ /** Sets the LEDGrid Program for the selected mode */
void setLEDProgram (LEDGrid* grid)
void setLEDProgram (Block& block)
{ {
if (currentMode == waveformSelectionMode) if (currentMode == waveformSelectionMode)
{ {
// Create a new WaveshapeProgram for the LEDGrid // Create a new WaveshapeProgram for the LEDGrid
waveshapeProgram = new WaveshapeProgram (*grid);
waveshapeProgram = new WaveshapeProgram (block);
// Set the LEDGrid program // Set the LEDGrid program
grid->setProgram (waveshapeProgram);
block.setProgram (waveshapeProgram);
// Initialise the program // Initialise the program
waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode)); waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode));
@@ -276,10 +276,16 @@ private:
else if (currentMode == playMode) else if (currentMode == playMode)
{ {
// Create a new DrumPadGridProgram for the LEDGrid // Create a new DrumPadGridProgram for the LEDGrid
gridProgram = new DrumPadGridProgram (*grid);
gridProgram = new DrumPadGridProgram (block);
// Set the LEDGrid program // Set the LEDGrid program
grid->setProgram (gridProgram);
auto error = block.setProgram (gridProgram);
if (error.failed())
{
DBG (error.getErrorMessage());
jassertfalse;
}
// Setup the grid layout // Setup the grid layout
gridProgram->setGridFills (layout.numColumns, gridProgram->setGridFills (layout.numColumns,


+ 9
- 14
examples/BLOCKS/BlocksSynth/Source/WaveshapeProgram.h View File

@@ -3,15 +3,15 @@
/** /**
A Program to draw moving waveshapes onto the LEDGrid A Program to draw moving waveshapes onto the LEDGrid
*/ */
class WaveshapeProgram : public LEDGrid::Program
class WaveshapeProgram : public Block::Program
{ {
public: public:
WaveshapeProgram (LEDGrid& lg) : Program (lg) {}
WaveshapeProgram (Block& b) : Program (b) {}
/** Sets the waveshape type to display on the grid */ /** Sets the waveshape type to display on the grid */
void setWaveshapeType (uint8 type) void setWaveshapeType (uint8 type)
{ {
ledGrid.setDataByte (0, type);
block.setDataByte (0, type);
} }
/** Generates the Y coordinates for 1.5 cycles of each of the four waveshapes and stores them /** Generates the Y coordinates for 1.5 cycles of each of the four waveshapes and stores them
@@ -74,22 +74,19 @@ public:
// Store the values for each of the waveshapes at the correct offsets in the shared data heap // Store the values for each of the waveshapes at the correct offsets in the shared data heap
for (uint8 i = 0; i < 45; ++i) for (uint8 i = 0; i < 45; ++i)
{ {
ledGrid.setDataByte (sineWaveOffset + i, sineWaveY[i]);
ledGrid.setDataByte (squareWaveOffset + i, squareWaveY[i]);
ledGrid.setDataByte (sawWaveOffset + i, sawWaveY[i]);
ledGrid.setDataByte (triangleWaveOffset + i, triangleWaveY[i]);
block.setDataByte (sineWaveOffset + i, sineWaveY[i]);
block.setDataByte (squareWaveOffset + i, squareWaveY[i]);
block.setDataByte (sawWaveOffset + i, sawWaveY[i]);
block.setDataByte (triangleWaveOffset + i, triangleWaveY[i]);
} }
} }
uint32 getHeapSize() override
{
return totalDataSize;
}
String getLittleFootProgram() override String getLittleFootProgram() override
{ {
return R"littlefoot( return R"littlefoot(
#heapsize: 256
int yOffset; int yOffset;
int min (int a, int b) int min (int a, int b)
@@ -173,8 +170,6 @@ private:
static constexpr uint32 sawWaveOffset = 91; // 1 byte * 45 static constexpr uint32 sawWaveOffset = 91; // 1 byte * 45
static constexpr uint32 triangleWaveOffset = 136; // 1 byte * 45 static constexpr uint32 triangleWaveOffset = 136; // 1 byte * 45
static constexpr uint32 totalDataSize = triangleWaveOffset + 45;
//============================================================================== //==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaveshapeProgram) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaveshapeProgram)
}; };

+ 323
- 0
modules/juce_blocks_basics/LittleFootFunctions.txt View File

@@ -0,0 +1,323 @@

/** Returns the smaller of two integer values. */
int min (int a, int b);

/** Returns the smaller of two floating point values. */
float min (float a, float b);

/** Returns the larger of two integer values. */
int max (int a, int b);

/** Returns the larger of two floating point values. */
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. */
int abs (int arg);

/** Returns the absolute value of a floating point value. */
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.
*/
int mod (int dividend, int divisor);

/** Returns the number of milliseconds since a fixed event (usually system startup).
This returns a monotonically increasing value which it unaffected by changes to the
system clock. It should be accurate to within a few millisecseconds.
*/
int getMillisecondCounter();

/** Returns the current firmware version. */
int getFirmwareVersion();

/** Logs an integer value to the console. */
void log (int data);

/** Logs a hexadecimal value to the console. */
void logHex (int data);

/** Returns the length of time spent in the function call in milliseconds. */
int getTimeInCurrentFunctionCall();

/** Returns the battery level between 0 and 1.0. */
float getBatteryLevel();

/** Returns true if the battery is charging. */
bool isBatteryCharging();

/** 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.
*/
bool isMasterBlock();

/** Returns true if this block is directly connected to the host computer. */
bool isConnectedToHost();

/** Sets whether status overlays should be displayed. */
void setStatusOverlayActive (bool active);

/** Returns the number of blocks in the current topology. */
int getNumBlocksInTopology();

/** Returns the ID of the block at a given index in the topology. */
int getBlockIDForIndex (int index);

/** Returns the ID of the block connected to a specified port. */
int getBlockIDOnPort (int port);

/** Returns the port number that is connected to the master block. Returns 0xFF if there is no path. */
int getPortToMaster();

/** Returns the block type of the block with this ID. */
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 data0 the first chunk of data to send
@param data1 the second chunk of data to send
@param data2 the third chunk of data to send
*/
void sendMessageToBlock (int blockID, int data0, int data1, int data2);

/** Combines a set of 8-bit ARGB values into a 32-bit colour and returns the result. */
int makeARGB (int alpha, int red, int green, int blue);

/** Blends the overlaid ARGB colour onto the base one and returns the new colour. */
int blendARGB (int baseColour, int overlaidColour);

/** 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 a 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);

/** Draws a rectangle on the block 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);

/** Draws a pressure point at a given centre LED with a specified colour and pressure.
@param argb the colour to use
@param touchX the x position of the touch
@param touchY the y position of the touch
@param touchZ the pressure value of the touch
*/
void addPressurePoint (int argb, float touchX, float touchY, float touchZ);

/** Draws the pressure map on the block display. */
void drawPressureMap();

/** Fades the pressure map on the block display. */
void fadePressureMap();

/** Draws a number on the block 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);

/** Clears the display setting all the LEDs to black. */
void clearDisplay();

/** Clears the display setting all the LEDs to a specified colour. */
void clearDisplay (int rgb);

/** 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);

//==============================================================================

/** 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 touch event starts.
@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 logical units starting from 0 (left)
@param y the Y position of this touch on the device, in logical 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.
@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 logical units starting from 0 (left)
@param y the Y position of this touch on the device, in logical 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.
@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 logical units starting from 0 (left)
@param y the Y position of this touch on the device, in logical 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 data0, int data1, int data2);

+ 7
- 3
modules/juce_blocks_basics/blocks/juce_Block.cpp View File

@@ -65,9 +65,16 @@ Block::~Block() {}
void Block::addDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.add (listener); } void Block::addDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.add (listener); }
void Block::removeDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.remove (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 edge == other.edge && index == other.index; }
bool Block::ConnectionPort::operator!= (const ConnectionPort& other) const noexcept { return ! operator== (other); } 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 (Block& b) : block (b) {}
TouchSurface::~TouchSurface() {} TouchSurface::~TouchSurface() {}
@@ -91,9 +98,6 @@ void ControlButton::removeListener (Listener* l) { listeners.remove (l);
LEDGrid::LEDGrid (Block& b) : block (b) {} LEDGrid::LEDGrid (Block& b) : block (b) {}
LEDGrid::~LEDGrid() {} LEDGrid::~LEDGrid() {}
LEDGrid::Program::Program (LEDGrid& l) : ledGrid (l) {}
LEDGrid::Program::~Program() {}
LEDGrid::Renderer::~Renderer() {} LEDGrid::Renderer::~Renderer() {}
//============================================================================== //==============================================================================


+ 91
- 2
modules/juce_blocks_basics/blocks/juce_Block.h View File

@@ -184,6 +184,94 @@ public:
/** Returns a list of the connectors that this device has. */ /** Returns a list of the connectors that this device has. */
virtual juce::Array<ConnectionPort> getPorts() const = 0; 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)
{
// 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;
//==============================================================================
/** 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)> packetAckCallback) = 0;
//============================================================================== //==============================================================================
/** Interface for objects listening to input data port. */ /** Interface for objects listening to input data port. */
struct DataInputPortListener struct DataInputPortListener
@@ -194,10 +282,10 @@ public:
virtual void handleIncomingDataPortMessage (Block& source, const void* messageData, size_t messageSize) = 0; virtual void handleIncomingDataPortMessage (Block& source, const void* messageData, size_t messageSize) = 0;
}; };
/** Adds a new listener of data input port. */
/** Adds a new listener for the data input port. */
virtual void addDataInputPortListener (DataInputPortListener*); virtual void addDataInputPortListener (DataInputPortListener*);
/** Removes a listener of data input port. */
/** Removes a listener for the data input port. */
virtual void removeDataInputPortListener (DataInputPortListener*); virtual void removeDataInputPortListener (DataInputPortListener*);
/** Sends a message to the block. */ /** Sends a message to the block. */
@@ -214,6 +302,7 @@ protected:
Block (const juce::String& serialNumberToUse); Block (const juce::String& serialNumberToUse);
juce::ListenerList<DataInputPortListener> dataInputPortListeners; juce::ListenerList<DataInputPortListener> dataInputPortListeners;
juce::ListenerList<ProgramEventListener> programEventListeners;
private: private:
//============================================================================== //==============================================================================


+ 9
- 72
modules/juce_blocks_basics/blocks/juce_LEDGrid.h View File

@@ -73,95 +73,32 @@ public:
/** Returns the number of rows in the LED grid. */ /** Returns the number of rows in the LED grid. */
virtual int getNumRows() const = 0; virtual int getNumRows() const = 0;
/** A program that can be loaded onto an LEDGrid.
This class facilitates the execution of a LittleFoot program on a BLOCKS
device with an LEDGrid.
*/
struct Program
{
/** Creates a Program for the corresponding LEDGrid. */
Program (LEDGrid&);
/** Destructor. */
virtual ~Program();
/** Returns the LittleFoot program to execute on the BLOCKS device. */
virtual juce::String getLittleFootProgram() = 0;
/** Sets the size of the shared area of memory used to communicate with
the host computer.
*/
virtual uint32 getHeapSize() = 0;
LEDGrid& ledGrid;
};
/** Sets the Program to run on this LEDGrid.
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[2];
};
/** Sends a message to the currently loaded program.
To receive the message the program must provide a function called
handleMessage with the following form:
@code
void handleMessage (int param1, int param2)
{
// Do something with the two integer parameters that the app has sent...
}
@endcode
*/
virtual void sendProgramEvent (const ProgramEventMessage&) = 0;
/** Sets a single byte on the heap. */
virtual void setDataByte (size_t offset, uint8 value) = 0;
/** Sets multiple bytes on the heap. */
virtual void setDataBytes (size_t offset, const void* data, size_t num) = 0;
/** Sets multiple bits on the heap. */
virtual void setDataBits (uint32 startBit, uint32 numBits, uint32 value) = 0;
/** Gets a byte from the heap. */
virtual uint8 getDataByte (size_t offset) = 0;
/** Sets the current program as the block's default state. */
virtual void saveProgramAsDefault() = 0;
//============================================================================== //==============================================================================
struct Renderer
struct Renderer : public juce::ReferenceCountedObject
{ {
virtual ~Renderer(); virtual ~Renderer();
virtual void renderLEDGrid (LEDGrid&) = 0; 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). /** 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 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. must ensure that it doesn't get deleted while in use here.
*/ */
void setRenderer (Renderer* newRenderer) noexcept { renderer = newRenderer; }
void setRenderer (Renderer::Ptr newRenderer) noexcept { renderer = newRenderer; }
/** Returns the visualiser currently attached to this block (nullptr for none). */ /** Returns the visualiser currently attached to this block (nullptr for none). */
Renderer* getRenderer() const noexcept { return renderer; }
Renderer::Ptr getRenderer() const noexcept { return renderer; }
/** The device that this LEDGrid belongs to. */ /** The device that this LEDGrid belongs to. */
Block& block; Block& block;
private: private:
Renderer* renderer = nullptr;
Renderer::Ptr renderer;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGrid) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGrid)
}; };

+ 151
- 60
modules/juce_blocks_basics/littlefoot/juce_LittleFootCompiler.h View File

@@ -39,9 +39,10 @@ using namespace juce;
*/ */
struct Compiler struct Compiler
{ {
Compiler() {}
Compiler() = default;
/**
/** Gives the compiler a zero-terminated list of native function prototypes to
use when parsing function calls.
*/ */
void addNativeFunctions (const char* const* functionPrototypes) void addNativeFunctions (const char* const* functionPrototypes)
{ {
@@ -49,7 +50,8 @@ struct Compiler
nativeFunctions.add (NativeFunction (*functionPrototypes, nullptr)); nativeFunctions.add (NativeFunction (*functionPrototypes, nullptr));
} }
/**
/** Tells the compiler to use the list of native function prototypes from
this littlefoot::Runner object.
*/ */
template <typename RunnerType> template <typename RunnerType>
void addNativeFunctions (const RunnerType& runner) void addNativeFunctions (const RunnerType& runner)
@@ -58,20 +60,22 @@ struct Compiler
nativeFunctions.add (runner.getNativeFunction (i)); nativeFunctions.add (runner.getNativeFunction (i));
} }
/**
/** Compiles a littlefoot program.
If there's an error, this returns it, otherwise the compiled bytecode is
placed in the compiledObjectCode member.
*/ */
Result compile (const String& sourceCode, uint32 heapSizeBytesRequired)
Result compile (const String& sourceCode, uint32 defaultHeapSize)
{ {
try try
{ {
SyntaxTreeBuilder stb (sourceCode);
SyntaxTreeBuilder stb (sourceCode, nativeFunctions, defaultHeapSize);
stb.compile(); stb.compile();
stb.simplify(); stb.simplify();
compiledObjectCode.clear(); compiledObjectCode.clear();
CodeGenerator codeGen (compiledObjectCode, nativeFunctions, stb.functions);
codeGen.generateCode (stb.blockBeingParsed, (heapSizeBytesRequired + 3) & ~3u);
CodeGenerator codeGen (compiledObjectCode, stb);
codeGen.generateCode (stb.blockBeingParsed, stb.heapSizeRequired);
return Result::ok(); return Result::ok();
} }
catch (String error) catch (String error)
@@ -80,7 +84,14 @@ struct Compiler
} }
} }
/**
/** After a successful compilation, this returns the finished Program. */
Program getCompiledProgram() const noexcept
{
return Program (compiledObjectCode.begin(), (uint32) compiledObjectCode.size());
}
/** After a successful call to compile(), this contains the bytecode generated.
A littlefoot::Program object can be created directly from this array.
*/ */
Array<uint8> compiledObjectCode; Array<uint8> compiledObjectCode;
@@ -102,7 +113,7 @@ private:
X(return_, "return") X(true_, "true") X(false_, "false") X(return_, "return") X(true_, "true") X(false_, "false")
#define LITTLEFOOT_OPERATORS(X) \ #define LITTLEFOOT_OPERATORS(X) \
X(semicolon, ";") X(dot, ".") X(comma, ",") \
X(semicolon, ";") X(dot, ".") X(comma, ",") X(hash, "#") \
X(openParen, "(") X(closeParen, ")") X(openBrace, "{") X(closeBrace, "}") \ X(openParen, "(") X(closeParen, ")") X(openBrace, "{") X(closeBrace, "}") \
X(openBracket, "[") X(closeBracket, "]") X(colon, ":") X(question, "?") \ X(openBracket, "[") X(closeBracket, "]") X(colon, ":") X(question, "?") \
X(equals, "==") X(assign, "=") X(notEquals, "!=") X(logicalNot, "!") \ X(equals, "==") X(assign, "=") X(notEquals, "!=") X(logicalNot, "!") \
@@ -354,7 +365,8 @@ private:
//============================================================================== //==============================================================================
struct SyntaxTreeBuilder : private TokenIterator struct SyntaxTreeBuilder : private TokenIterator
{ {
SyntaxTreeBuilder (const String& code) : TokenIterator (code) {}
SyntaxTreeBuilder (const String& code, const Array<NativeFunction>& nativeFns, uint32 defaultHeapSize)
: TokenIterator (code), nativeFunctions (nativeFns), heapSizeRequired (defaultHeapSize) {}
void compile() void compile()
{ {
@@ -362,6 +374,12 @@ private:
while (currentType != Token::eof) while (currentType != Token::eof)
{ {
if (matchIf (Token::hash))
{
parseCompilerDirective();
continue;
}
if (! matchesAnyTypeOrVoid()) if (! matchesAnyTypeOrVoid())
throwErrorExpecting ("a global variable or function"); throwErrorExpecting ("a global variable or function");
@@ -377,7 +395,7 @@ private:
if (type == Type::void_) if (type == Type::void_)
location.throwError ("A variable type cannot be 'void'"); location.throwError ("A variable type cannot be 'void'");
int arraySize = matchIf (Token::openBracket) ? parseArraySize() : 0;
int arraySize = matchIf (Token::openBracket) ? parseIntegerLiteral() : 0;
if (arraySize > 0) if (arraySize > 0)
location.throwError ("Arrays not yet implemented!"); location.throwError ("Arrays not yet implemented!");
@@ -399,9 +417,29 @@ private:
f->block->simplify (*this); f->block->simplify (*this);
} }
Function* findFunction (FunctionID functionID) const noexcept
{
for (auto f : functions)
if (f->functionID == functionID)
return f;
return nullptr;
}
NativeFunction* findNativeFunction (FunctionID functionID) const noexcept
{
for (auto& f : nativeFunctions)
if (f.functionID == functionID)
return &f;
return nullptr;
}
//============================================================================== //==============================================================================
BlockPtr blockBeingParsed = nullptr; BlockPtr blockBeingParsed = nullptr;
Array<Function*> functions; Array<Function*> functions;
const Array<NativeFunction>& nativeFunctions;
uint32 heapSizeRequired;
template <typename Type, typename... Args> template <typename Type, typename... Args>
Type* allocate (Args... args) { auto o = new Type (args...); allAllocatedObjects.add (o); return o; } Type* allocate (Args... args) { auto o = new Type (args...); allAllocatedObjects.add (o); return o; }
@@ -410,10 +448,23 @@ private:
OwnedArray<AllocatedObject> allAllocatedObjects; OwnedArray<AllocatedObject> allAllocatedObjects;
//============================================================================== //==============================================================================
void parseCompilerDirective()
{
auto name = parseIdentifier();
if (name == "heapsize")
{
match (Token::colon);
heapSizeRequired = (((uint32) parseIntegerLiteral()) + 3) & ~3u;
return;
}
location.throwError ("Unknown compiler directive");
}
void parseFunctionDeclaration (Type returnType, const String& name) void parseFunctionDeclaration (Type returnType, const String& name)
{ {
auto f = allocate<Function>(); auto f = allocate<Function>();
functions.add (f);
while (matchesAnyType()) while (matchesAnyType())
{ {
@@ -431,6 +482,12 @@ private:
match (Token::closeParen); match (Token::closeParen);
f->functionID = createFunctionID (name, returnType, f->getArgumentTypes()); f->functionID = createFunctionID (name, returnType, f->getArgumentTypes());
if (findFunction (f->functionID) != nullptr || findNativeFunction (f->functionID) != nullptr)
location.throwError ("Duplicate function declaration");
functions.add (f);
f->block = parseBlock (true); f->block = parseBlock (true);
f->returnType = returnType; f->returnType = returnType;
@@ -443,12 +500,11 @@ private:
} }
} }
int parseArraySize()
int parseIntegerLiteral()
{ {
auto e = parseExpression(); auto e = parseExpression();
e->simplify (*this);
if (auto literal = dynamic_cast<LiteralValue*> (e))
if (auto literal = dynamic_cast<LiteralValue*> (e->simplify (*this)))
{ {
if (literal->value.isInt() || literal->value.isInt64()) if (literal->value.isInt() || literal->value.isInt64())
{ {
@@ -459,7 +515,7 @@ private:
} }
} }
location.throwError ("An array size must be a constant integer");
location.throwError ("Expected an integer constant");
return 0; return 0;
} }
@@ -618,8 +674,8 @@ private:
{ {
if (currentType == Token::identifier) return parseSuffixes (allocate<Identifier> (location, blockBeingParsed, parseIdentifier())); if (currentType == Token::identifier) return parseSuffixes (allocate<Identifier> (location, blockBeingParsed, parseIdentifier()));
if (matchIf (Token::openParen)) return parseSuffixes (matchCloseParen (parseExpression())); if (matchIf (Token::openParen)) return parseSuffixes (matchCloseParen (parseExpression()));
if (matchIf (Token::true_)) return parseSuffixes (allocate<LiteralValue> (location, blockBeingParsed, (int) 1));
if (matchIf (Token::false_)) return parseSuffixes (allocate<LiteralValue> (location, blockBeingParsed, (int) 0));
if (matchIf (Token::true_)) return parseSuffixes (allocate<LiteralValue> (location, blockBeingParsed, true));
if (matchIf (Token::false_)) return parseSuffixes (allocate<LiteralValue> (location, blockBeingParsed, false));
if (currentType == Token::literal) if (currentType == Token::literal)
{ {
@@ -835,12 +891,12 @@ private:
//============================================================================== //==============================================================================
struct CodeGenerator struct CodeGenerator
{ {
CodeGenerator (Array<uint8>& output, const Array<NativeFunction>& nativeFns, const Array<Function*>& fns)
: outputCode (output), nativeFunctions (nativeFns), functions (fns) {}
CodeGenerator (Array<uint8>& output, const SyntaxTreeBuilder& stb)
: outputCode (output), syntaxTree (stb) {}
void generateCode (BlockPtr outerBlock, uint32 heapSizeBytesRequired) void generateCode (BlockPtr outerBlock, uint32 heapSizeBytesRequired)
{ {
for (auto f : functions)
for (auto f : syntaxTree.functions)
{ {
f->address = createMarker(); f->address = createMarker();
f->unwindAddress = createMarker(); f->unwindAddress = createMarker();
@@ -848,16 +904,19 @@ private:
emit ((int16) 0); // checksum emit ((int16) 0); // checksum
emit ((int16) 0); // size emit ((int16) 0); // size
emit ((int16) functions.size());
emit ((int16) syntaxTree.functions.size());
emit ((int16) outerBlock->variables.size()); emit ((int16) outerBlock->variables.size());
emit ((int16) heapSizeBytesRequired); emit ((int16) heapSizeBytesRequired);
for (auto f : functions)
for (auto f : syntaxTree.functions)
emit (f->functionID, f->address); emit (f->functionID, f->address);
for (auto f : functions)
auto codeStart = outputCode.size();
for (auto f : syntaxTree.functions)
f->emit (*this); f->emit (*this);
removeJumpsToNextInstruction (codeStart);
resolveMarkers(); resolveMarkers();
Program::writeInt16 (outputCode.begin() + 2, (int16) outputCode.size()); Program::writeInt16 (outputCode.begin() + 2, (int16) outputCode.size());
@@ -868,39 +927,92 @@ private:
//============================================================================== //==============================================================================
Array<uint8>& outputCode; Array<uint8>& outputCode;
const Array<NativeFunction>& nativeFunctions;
const Array<Function*>& functions;
const SyntaxTreeBuilder& syntaxTree;
struct Marker { int index = 0; }; struct Marker { int index = 0; };
struct MarkerAndAddress { int markerIndex, address; };
struct MarkerAndAddress { Marker marker; int address; };
int nextMarker = 0; int nextMarker = 0;
Array<MarkerAndAddress> markersToResolve, resolvedMarkers; Array<MarkerAndAddress> markersToResolve, resolvedMarkers;
Marker createMarker() noexcept { Marker m; m.index = ++nextMarker; return m; } Marker createMarker() noexcept { Marker m; m.index = ++nextMarker; return m; }
void attachMarker (Marker m) { resolvedMarkers.add ({ m.index, outputCode.size() }); }
void attachMarker (Marker m) { resolvedMarkers.add ({ m, outputCode.size() }); }
int getResolvedMarkerAddress (int markerIndex) const
int getResolvedMarkerAddress (Marker marker) const
{ {
for (auto m : resolvedMarkers) for (auto m : resolvedMarkers)
if (m.markerIndex == markerIndex)
if (m.marker.index == marker.index)
return m.address; return m.address;
jassertfalse; jassertfalse;
return 0; return 0;
} }
Marker getMarkerAtAddress (int address) const noexcept
{
for (auto m : markersToResolve)
if (m.address == address)
return m.marker;
jassertfalse;
return {};
}
void resolveMarkers() void resolveMarkers()
{ {
for (auto m : markersToResolve) for (auto m : markersToResolve)
Program::writeInt16 (outputCode.begin() + m.address, (int16) getResolvedMarkerAddress (m.markerIndex));
Program::writeInt16 (outputCode.begin() + m.address, (int16) getResolvedMarkerAddress (m.marker));
}
void removeCode (int address, int size)
{
outputCode.removeRange (address, size);
for (int i = markersToResolve.size(); --i >= 0;)
{
auto& m = markersToResolve.getReference (i);
if (m.address >= address + size)
m.address -= size;
else if (m.address >= address)
markersToResolve.remove (i);
}
for (auto& m : resolvedMarkers)
if (m.address >= address + size)
m.address -= size;
}
void removeJumpsToNextInstruction (int address)
{
while (address < outputCode.size())
{
auto op = (OpCode) outputCode.getUnchecked (address);
auto opSize = 1 + Program::getNumExtraBytesForOpcode (op);
if (op == OpCode::jump)
{
auto marker = getMarkerAtAddress (address + 1);
if (marker.index != 0)
{
if (getResolvedMarkerAddress (marker) == address + opSize)
{
removeCode (address, opSize);
continue;
}
}
}
address += opSize;
}
} }
Marker breakTarget, continueTarget; Marker breakTarget, continueTarget;
//============================================================================== //==============================================================================
void emit (OpCode op) { emit ((int8) op); } void emit (OpCode op) { emit ((int8) op); }
void emit (Marker m) { markersToResolve.add ({ m.index, outputCode.size() }); emit ((int16) 0); }
void emit (Marker m) { markersToResolve.add ({ m, outputCode.size() }); emit ((int16) 0); }
void emit (int8 value) { outputCode.add ((uint8) value); } void emit (int8 value) { outputCode.add ((uint8) value); }
void emit (int16 value) { uint8 d[2]; Program::writeInt16 (d, value); outputCode.insertArray (-1, d, (int) sizeof (d)); } void emit (int16 value) { uint8 d[2]; Program::writeInt16 (d, value); outputCode.insertArray (-1, d, (int) sizeof (d)); }
void emit (int32 value) { uint8 d[4]; Program::writeInt32 (d, value); outputCode.insertArray (-1, d, (int) sizeof (d)); } void emit (int32 value) { uint8 d[4]; Program::writeInt32 (d, value); outputCode.insertArray (-1, d, (int) sizeof (d)); }
@@ -955,7 +1067,7 @@ private:
index += stackDepth; index += stackDepth;
if (index == 0) if (index == 0)
emit ((OpCode) ((int) OpCode::dup));
emit (OpCode::dup);
else if (index < 8) else if (index < 8)
emit ((OpCode) ((int) OpCode::dupOffset_01 + index - 1)); emit ((OpCode) ((int) OpCode::dupOffset_01 + index - 1));
else if (index >= 128) else if (index >= 128)
@@ -966,25 +1078,6 @@ private:
emitCast (sourceType, requiredType, location); emitCast (sourceType, requiredType, location);
} }
//==============================================================================
Function* findFunction (FunctionID functionID) const noexcept
{
for (auto f : functions)
if (f->functionID == functionID)
return f;
return nullptr;
}
NativeFunction* findNativeFunction (FunctionID functionID) const noexcept
{
for (auto& f : nativeFunctions)
if (f.functionID == functionID)
return &f;
return nullptr;
}
}; };
//============================================================================== //==============================================================================
@@ -1385,9 +1478,7 @@ private:
else if (fn->returnType != Type::void_) else if (fn->returnType != Type::void_)
location.throwError ("Cannot return a value from a void function"); location.throwError ("Cannot return a value from a void function");
if (parentBlock->statements.getLast() != this)
cg.emit (OpCode::jump, fn->unwindAddress);
cg.emit (OpCode::jump, fn->unwindAddress);
return; return;
} }
@@ -1798,7 +1889,7 @@ private:
auto functionID = getFunctionID (cg); auto functionID = getFunctionID (cg);
if (auto fn = cg.findFunction (functionID))
if (auto fn = cg.syntaxTree.findFunction (functionID))
{ {
emitArgs (cg, fn->getArgumentTypes(), stackDepth); emitArgs (cg, fn->getArgumentTypes(), stackDepth);
cg.emit (OpCode::call, fn->address); cg.emit (OpCode::call, fn->address);
@@ -1806,7 +1897,7 @@ private:
return; return;
} }
if (auto nativeFn = cg.findNativeFunction (functionID))
if (auto nativeFn = cg.syntaxTree.findNativeFunction (functionID))
{ {
emitArgs (cg, getArgTypesFromFunctionName (nativeFn->nameAndArguments), stackDepth); emitArgs (cg, getArgTypesFromFunctionName (nativeFn->nameAndArguments), stackDepth);
cg.emit (OpCode::callNative, nativeFn->functionID); cg.emit (OpCode::callNative, nativeFn->functionID);
@@ -1836,10 +1927,10 @@ private:
auto functionID = getFunctionID (cg); auto functionID = getFunctionID (cg);
if (auto fn = cg.findFunction (functionID))
if (auto fn = cg.syntaxTree.findFunction (functionID))
return fn->returnType; return fn->returnType;
if (auto nativeFn = cg.findNativeFunction (functionID))
if (auto nativeFn = cg.syntaxTree.findNativeFunction (functionID))
return nativeFn->returnType; return nativeFn->returnType;
if (auto b = findBuiltInFunction (functionID)) if (auto b = findBuiltInFunction (functionID))


+ 74
- 63
modules/juce_blocks_basics/littlefoot/juce_LittleFootRemoteHeap.h View File

@@ -52,16 +52,35 @@ struct LittleFootRemoteHeap
void clear() noexcept void clear() noexcept
{ {
zeromem (targetData, sizeof (targetData)); 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 void setByte (size_t offset, uint8 value) noexcept
{ {
if (offset < blockSize) if (offset < blockSize)
{ {
if (targetData [offset] != value)
if (targetData[offset] != value)
{ {
targetData [offset] = value;
invalidateData();
targetData[offset] = value;
needsSyncing = true;
if (programStateKnown && offset < programSize)
programStateKnown = false;
} }
} }
else else
@@ -100,37 +119,31 @@ struct LittleFootRemoteHeap
return 0; return 0;
} }
void invalidateData()
void invalidateData() noexcept
{ {
dataHasChanged = true;
needsSyncing = true;
programStateKnown = false; programStateKnown = false;
} }
void sendChanges (ImplementationClass& bi)
bool isFullySynced() const noexcept
{ {
if (dataHasChanged && messagesSent.isEmpty())
return ! needsSyncing;
}
void sendChanges (ImplementationClass& bi, bool forceSend)
{
if ((needsSyncing && messagesSent.isEmpty()) || forceSend)
{ {
for (;;)
for (int maxChanges = 30; --maxChanges >= 0;)
{ {
uint16 data[ImplementationClass::maxBlockSize]; uint16 data[ImplementationClass::maxBlockSize];
uint32 packetIndex;
if (messagesSent.isEmpty())
{
for (uint32 i = 0; i < blockSize; ++i)
data[i] = deviceState[i];
packetIndex = lastPacketIndexReceived;
}
else
{
auto& lastPacket = messagesSent.getReference (messagesSent.size() - 1);
auto* latestState = getLatestExpectedDataState();
for (uint32 i = 0; i < blockSize; ++i)
data[i] = lastPacket.resultDataState[i];
for (uint32 i = 0; i < blockSize; ++i)
data[i] = latestState[i];
packetIndex = lastPacket.packetIndex;
}
uint32 packetIndex = messagesSent.isEmpty() ? lastPacketIndexReceived
: messagesSent.getLast()->packetIndex;
packetIndex = (packetIndex + 1) & ImplementationClass::maxPacketCounter; packetIndex = (packetIndex + 1) & ImplementationClass::maxPacketCounter;
@@ -141,14 +154,14 @@ struct LittleFootRemoteHeap
} }
} }
for (auto& m : messagesSent)
for (auto* m : messagesSent)
{ {
if (m.dispatchTime >= Time::getCurrentTime() - RelativeTime::milliseconds (250))
if (m->dispatchTime >= Time::getCurrentTime() - RelativeTime::milliseconds (250))
break; break;
m.dispatchTime = Time::getCurrentTime();
bi.sendMessageToDevice (m.packet);
//DBG ("Sending packet " << (int) m.packetIndex << " - " << m.packet.size() << " bytes, device " << bi.getDeviceIndex());
m->dispatchTime = Time::getCurrentTime();
bi.sendMessageToDevice (m->packet);
//DBG ("Sending packet " << (int) m->packetIndex << " - " << m->packet.size() << " bytes, device " << bi.getDeviceIndex());
if (getTotalSizeOfMessagesSent() > 200) if (getTotalSizeOfMessagesSent() > 200)
break; break;
@@ -157,7 +170,7 @@ struct LittleFootRemoteHeap
void handleACKFromDevice (ImplementationClass& bi, uint32 packetIndex) noexcept void handleACKFromDevice (ImplementationClass& bi, uint32 packetIndex) noexcept
{ {
//DBG ("ACK " << (int) packetIndex);
//DBG ("ACK " << (int) packetIndex << " device " << (int) bi.getDeviceIndex());
if (lastPacketIndexReceived != packetIndex) if (lastPacketIndexReceived != packetIndex)
{ {
@@ -165,7 +178,7 @@ struct LittleFootRemoteHeap
for (int i = messagesSent.size(); --i >= 0;) for (int i = messagesSent.size(); --i >= 0;)
{ {
auto& m = messagesSent.getReference(i);
auto& m = *messagesSent.getUnchecked(i);
if (m.packetIndex == packetIndex) if (m.packetIndex == packetIndex)
{ {
@@ -174,10 +187,10 @@ struct LittleFootRemoteHeap
messagesSent.removeRange (0, i + 1); messagesSent.removeRange (0, i + 1);
dumpStatus(); dumpStatus();
sendChanges (bi);
sendChanges (bi, false);
if (messagesSent.isEmpty()) if (messagesSent.isEmpty())
dataHasChanged = false;
needsSyncing = false;
return; return;
} }
@@ -191,17 +204,16 @@ struct LittleFootRemoteHeap
{ {
if (! programStateKnown) if (! programStateKnown)
{ {
programStateKnown = true;
uint8 deviceMemory[ImplementationClass::maxBlockSize]; uint8 deviceMemory[ImplementationClass::maxBlockSize];
bool anyUnknowns = false;
for (size_t i = 0; i < blockSize; ++i) for (size_t i = 0; i < blockSize; ++i)
{
anyUnknowns = (deviceState[i] > 255);
deviceMemory[i] = (uint8) deviceState[i]; deviceMemory[i] = (uint8) deviceState[i];
}
programLoaded = ! anyUnknowns && littlefoot::Program (deviceMemory, (uint32) blockSize).checksumMatches();
programStateKnown = true;
littlefoot::Program prog (deviceMemory, (uint32) blockSize);
programLoaded = prog.checksumMatches();
programSize = prog.getProgramSize();
} }
return programLoaded; return programLoaded;
@@ -214,15 +226,13 @@ struct LittleFootRemoteHeap
private: private:
uint16 deviceState[ImplementationClass::maxBlockSize]; uint16 deviceState[ImplementationClass::maxBlockSize];
uint8 targetData[ImplementationClass::maxBlockSize] = { 0 }; uint8 targetData[ImplementationClass::maxBlockSize] = { 0 };
bool dataHasChanged = true, programStateKnown = true, programLoaded = false;
uint32 programSize = 0;
bool needsSyncing = true, programStateKnown = true, programLoaded = false;
void resetDeviceStateToUnknown()
uint16* getLatestExpectedDataState() noexcept
{ {
invalidateData();
messagesSent.clear();
for (uint32 i = 0; i < ImplementationClass::maxBlockSize; ++i)
deviceState[i] = unknownByte;
return messagesSent.isEmpty() ? deviceState
: messagesSent.getLast()->resultDataState;
} }
struct ChangeMessage struct ChangeMessage
@@ -233,16 +243,16 @@ private:
uint16 resultDataState[ImplementationClass::maxBlockSize]; uint16 resultDataState[ImplementationClass::maxBlockSize];
}; };
Array<ChangeMessage> messagesSent;
OwnedArray<ChangeMessage> messagesSent;
uint32 lastPacketIndexReceived = 0; uint32 lastPacketIndexReceived = 0;
int getTotalSizeOfMessagesSent() const noexcept int getTotalSizeOfMessagesSent() const noexcept
{ {
int total = 0; int total = 0;
for (auto& m : messagesSent)
if (m.dispatchTime != Time())
total += m.packet.size();
for (auto* m : messagesSent)
if (m->dispatchTime != Time())
total += m->packet.size();
return total; return total;
} }
@@ -280,6 +290,8 @@ private:
Diff (uint16* current, const uint8* target, size_t blockSizeToUse) Diff (uint16* current, const uint8* target, size_t blockSizeToUse)
: newData (target), blockSize (blockSizeToUse) : newData (target), blockSize (blockSizeToUse)
{ {
ranges.ensureStorageAllocated ((int) blockSize);
for (int i = 0; i < (int) blockSize; ++i) for (int i = 0; i < (int) blockSize; ++i)
ranges.add ({ i, 1, newData[i] == current[i], false }); ranges.add ({ i, 1, newData[i] == current[i], false });
@@ -290,7 +302,7 @@ private:
bool createChangeMessage (const ImplementationClass& bi, bool createChangeMessage (const ImplementationClass& bi,
const uint16* currentState, const uint16* currentState,
Array<ChangeMessage>& messagesCreated,
OwnedArray<ChangeMessage>& messagesCreated,
uint32 nextPacketIndex) uint32 nextPacketIndex)
{ {
if (ranges.isEmpty()) if (ranges.isEmpty())
@@ -301,8 +313,7 @@ private:
if (deviceIndex < 0) if (deviceIndex < 0)
return false; return false;
messagesCreated.add ({});
auto& message = messagesCreated.getReference (messagesCreated.size() - 1);
auto& message = *messagesCreated.add (new ChangeMessage());
message.packetIndex = nextPacketIndex; message.packetIndex = nextPacketIndex;
@@ -366,27 +377,27 @@ private:
void coalesceUniformRegions() void coalesceUniformRegions()
{ {
for (int i = 0; i < ranges.size() - 1; ++i)
for (int i = ranges.size(); --i > 0;)
{ {
auto& r1 = ranges.getReference (i);
auto r2 = ranges.getReference (i + 1);
auto& r1 = ranges.getReference (i - 1);
auto r2 = ranges.getReference (i);
if (r1.isSkipped == r2.isSkipped if (r1.isSkipped == r2.isSkipped
&& (r1.isSkipped || newData[r1.index] == newData[r2.index])) && (r1.isSkipped || newData[r1.index] == newData[r2.index]))
{ {
r1.length += r2.length; r1.length += r2.length;
ranges.remove (i + 1);
i = jmax (0, i - 2);
ranges.remove (i);
i = jmin (ranges.size() - 1, i + 1);
} }
} }
} }
void coalesceSequences() void coalesceSequences()
{ {
for (int i = 0; i < ranges.size() - 1; ++i)
for (int i = ranges.size(); --i > 0;)
{ {
auto& r1 = ranges.getReference (i);
auto r2 = ranges.getReference (i + 1);
auto& r1 = ranges.getReference (i - 1);
auto r2 = ranges.getReference (i);
if (! (r1.isSkipped || r2.isSkipped) if (! (r1.isSkipped || r2.isSkipped)
&& (r1.isMixed || r1.length == 1) && (r1.isMixed || r1.length == 1)
@@ -396,8 +407,8 @@ private:
{ {
r1.length += r2.length; r1.length += r2.length;
r1.isMixed = true; r1.isMixed = true;
ranges.remove (i + 1);
i = jmax (0, i - 2);
ranges.remove (i);
i = jmin (ranges.size() - 1, i + 1);
} }
} }
} }


+ 84
- 28
modules/juce_blocks_basics/littlefoot/juce_LittleFootRunner.h View File

@@ -44,11 +44,11 @@ namespace littlefoot
#define LITTLEFOOT_DUMP_PROGRAM 0 #define LITTLEFOOT_DUMP_PROGRAM 0
#endif #endif
using int8 = char;
using int8 = signed char;
using uint8 = unsigned char; using uint8 = unsigned char;
using int16 = short;
using int16 = signed short;
using uint16 = unsigned short; using uint16 = unsigned short;
using int32 = int;
using int32 = signed int;
using uint32 = unsigned int; using uint32 = unsigned int;
using FunctionID = int16; using FunctionID = int16;
@@ -180,8 +180,8 @@ struct NativeFunction
jassert (nameAndArgTypes[slash + 1] != 0); // The slash must be followed by a return type character 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 (nameAndArgTypes).substring (0, slash).containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"));
jassert (! juce::String ("0123456789").containsChar (nameAndArgTypes[0])); jassert (! juce::String ("0123456789").containsChar (nameAndArgTypes[0]));
jassert (juce::String (nameAndArgTypes).substring (slash + 1).containsOnly ("vif"));
jassert (juce::String (nameAndArgTypes).substring (slash + 2).containsOnly ("if")); // arguments must only be of these types
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) for (; nameAndArgTypes[i] != 0; ++i)
if (i != slash + 1) if (i != slash + 1)
@@ -247,13 +247,6 @@ struct Program
return calculateChecksum() == getStoredChecksum(); return calculateChecksum() == getStoredChecksum();
} }
uint32 getProgramSize() const noexcept
{
auto size = (uint16) readInt16 (programStart + 2);
return size < programHeaderSize ? programHeaderSize
: (size > maxProgramSize ? maxProgramSize : size);
}
uint32 getNumFunctions() const noexcept uint32 getNumFunctions() const noexcept
{ {
return (uint16) readInt16 (programStart + 4); return (uint16) readInt16 (programStart + 4);
@@ -286,10 +279,11 @@ struct Program
: getFunctionStartAddress (functionIndex); : getFunctionStartAddress (functionIndex);
} }
/** Returns the number of global variables the program uses */
uint16 getNumGlobals() const noexcept
uint32 getProgramSize() const noexcept
{ {
return (uint16) readInt16 (programStart + 6);
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 */ /** Returns the number of bytes of heap space the program needs */
@@ -298,6 +292,17 @@ struct Program
return (uint16) readInt16 (programStart + 8); 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 #if JUCE_DEBUG
//============================================================================== //==============================================================================
/** Prints the assembly code for a given function. */ /** Prints the assembly code for a given function. */
@@ -307,7 +312,7 @@ struct Program
<< " (" << juce::String::toHexString (getFunctionID (functionIndex)) << ")" << juce::newLine; << " (" << juce::String::toHexString (getFunctionID (functionIndex)) << ")" << juce::newLine;
if (auto codeStart = getFunctionStartAddress (functionIndex)) if (auto codeStart = getFunctionStartAddress (functionIndex))
if (auto codeEnd = getFunctionEndAddress (functionIndex))
if (auto codeEnd = getFunctionEndAddress (functionIndex))
for (auto prog = codeStart; prog < codeEnd;) for (auto prog = codeStart; prog < codeEnd;)
out << getOpDisassembly (prog) << juce::newLine; out << getOpDisassembly (prog) << juce::newLine;
} }
@@ -339,19 +344,41 @@ struct Program
/** Calls dumpFunctionDisassembly() for all functions. */ /** Calls dumpFunctionDisassembly() for all functions. */
void dumpAllFunctions (juce::OutputStream& out) const void dumpAllFunctions (juce::OutputStream& out) const
{ {
DBG ("Program size: " << (int) getProgramSize() << " bytes");
if (programStart != nullptr)
{
DBG ("Program size: " << (int) getProgramSize() << " bytes");
for (uint32 i = 0; i < getNumFunctions(); ++i)
dumpFunctionDisassembly (out, i);
for (uint32 i = 0; i < getNumFunctions(); ++i)
dumpFunctionDisassembly (out, i);
}
} }
#endif #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 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 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) ((uint32) (uint16) readInt16 (d) + (((uint32) (uint16) readInt16 (d + 2)) << 16)); }
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 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 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); }
@@ -416,6 +443,16 @@ struct Runner
allMemory[i] = 0; 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 */ /** Return codes from a function call */
enum class ErrorCode : uint8 enum class ErrorCode : uint8
{ {
@@ -429,6 +466,23 @@ struct Runner
unknownFunction 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. */ /** Calls one of the functions in the program, by its textual signature. */
ErrorCode callFunction (const char* functionSignature) noexcept ErrorCode callFunction (const char* functionSignature) noexcept
{ {
@@ -467,15 +521,17 @@ struct Runner
/** */ /** */
bool isProgramValid() const noexcept { return heapStart != nullptr; } bool isProgramValid() const noexcept { return heapStart != nullptr; }
/** */
/** Sets a byte of data. */
void setDataByte (uint32 index, uint8 value) noexcept void setDataByte (uint32 index, uint8 value) noexcept
{ {
if (index < programAndHeapSpace) if (index < programAndHeapSpace)
{ {
if (index < program.getProgramSize())
auto& dest = getProgramAndDataStart()[index];
if (index < program.getProgramSize() && dest != value)
heapStart = nullptr; // force a re-initialise of the memory layout when the program changes heapStart = nullptr; // force a re-initialise of the memory layout when the program changes
getProgramAndDataStart()[index] = value;
dest = value;
} }
} }
@@ -561,7 +617,7 @@ struct Runner
if (prog.getFunctionID (i) == function) if (prog.getFunctionID (i) == function)
{ {
programCounter = prog.getFunctionStartAddress (i); programCounter = prog.getFunctionStartAddress (i);
functionEnd = prog.getFunctionEndAddress (i);
programEnd = r.getProgramHeapStart();
tos = *--stack = 0; tos = *--stack = 0;
return; return;
} }
@@ -599,7 +655,7 @@ struct Runner
for (;;) for (;;)
{ {
if (programCounter >= functionEnd)
if (programCounter >= programEnd)
return error; return error;
if ((++opsPerformed & 63) == 0 && hasTimedOut()) if ((++opsPerformed & 63) == 0 && hasTimedOut())
@@ -628,7 +684,7 @@ struct Runner
//============================================================================== //==============================================================================
Runner* runner; Runner* runner;
const uint8* programCounter; const uint8* programCounter;
const uint8* functionEnd;
const uint8* programEnd;
const uint8* programBase; const uint8* programBase;
uint8* heapStart; uint8* heapStart;
int32* stack; int32* stack;
@@ -646,7 +702,7 @@ struct Runner
int16 readProgram16() noexcept { auto v = Program::readInt16 (programCounter); programCounter += sizeof (int16); return v; } 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; } int32 readProgram32() noexcept { auto v = Program::readInt32 (programCounter); programCounter += sizeof (int32); return v; }
void setError (ErrorCode e) noexcept { error = e; programCounter = functionEnd; jassert (error == ErrorCode::ok); }
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 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; } bool flushTopToStack() noexcept { if (--stack < stackStart) { setError (ErrorCode::stackOverflow); return false; } *stack = tos; return true; }


+ 67
- 12
modules/juce_blocks_basics/protocol/juce_BlocksProtocolDefinitions.h View File

@@ -52,6 +52,7 @@ enum class MessageFromDevice
{ {
deviceTopology = 0x01, deviceTopology = 0x01,
packetACK = 0x02, packetACK = 0x02,
firmwareUpdateACK = 0x03,
touchStart = 0x10, touchStart = 0x10,
touchMove = 0x11, touchMove = 0x11,
@@ -62,7 +63,11 @@ enum class MessageFromDevice
touchEndWithVelocity = 0x15, touchEndWithVelocity = 0x15,
controlButtonDown = 0x20, controlButtonDown = 0x20,
controlButtonUp = 0x21
controlButtonUp = 0x21,
programEventMessage = 0x28,
logMessage = 0x30
}; };
/** Messages that the host may send to a device. */ /** Messages that the host may send to a device. */
@@ -70,7 +75,8 @@ enum class MessageFromHost
{ {
deviceCommandMessage = 0x01, deviceCommandMessage = 0x01,
sharedDataChange = 0x02, sharedDataChange = 0x02,
programEventMessage = 0x03
programEventMessage = 0x03,
firmwareUpdatePacket = 0x04
}; };
@@ -224,15 +230,18 @@ using ByteCountMany = IntegerWithBitSize<8>;
using ByteValue = IntegerWithBitSize<8>; using ByteValue = IntegerWithBitSize<8>;
using ByteSequenceContinues = IntegerWithBitSize<1>; using ByteSequenceContinues = IntegerWithBitSize<1>;
static constexpr uint32 numProgramMessageInts = 2;
using FirmwareUpdateACKCode = IntegerWithBitSize<7>;
using FirmwareUpdatePacketSize = IntegerWithBitSize<7>;
static constexpr uint32 numProgramMessageInts = 3;
static constexpr uint32 apiModeHostPingTimeoutMs = 5000; static constexpr uint32 apiModeHostPingTimeoutMs = 5000;
static constexpr uint32 padBlockProgramAndHeapSize = 3200;
static constexpr uint32 padBlockProgramAndHeapSize = 7200;
static constexpr uint32 padBlockStackSize = 800; static constexpr uint32 padBlockStackSize = 800;
static constexpr uint32 controlBlockProgramAndHeapSize = 1500;
static constexpr uint32 controlBlockStackSize = 500;
static constexpr uint32 controlBlockProgramAndHeapSize = 3000;
static constexpr uint32 controlBlockStackSize = 800;
//============================================================================== //==============================================================================
@@ -251,6 +260,8 @@ enum BitSizes
programEventMessage = MessageType::bits + 32 * numProgramMessageInts, programEventMessage = MessageType::bits + 32 * numProgramMessageInts,
packetACK = MessageType::bits + PacketCounter::bits, packetACK = MessageType::bits + PacketCounter::bits,
firmwareUpdateACK = MessageType::bits + FirmwareUpdateACKCode::bits,
controlButtonMessage = typeDeviceAndTime + ControlButtonID::bits, controlButtonMessage = typeDeviceAndTime + ControlButtonID::bits,
}; };
@@ -258,18 +269,62 @@ enum BitSizes
// These are the littlefoot functions provided for use in BLOCKS programs // These are the littlefoot functions provided for use in BLOCKS programs
static constexpr const char* ledProgramLittleFootFunctions[] = 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",
"getMillisecondCounter/i",
"getFirmwareVersion/i",
"log/vi",
"logHex/vi",
"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",
"makeARGB/iiiii", "makeARGB/iiiii",
"blendARGB/iii", "blendARGB/iii",
"setLED/viii",
"blendLED/viii",
"fillPixel/viii",
"blendPixel/viii",
"fillRect/viiiii", "fillRect/viiiii",
"blendRect/viiiii", "blendRect/viiiii",
"sendMIDI/vi",
"sendMIDI/vii",
"sendMIDI/viii",
"blendGradientRect/viiiiiiii",
"addPressurePoint/vifff", "addPressurePoint/vifff",
"drawPressureMap/v", "drawPressureMap/v",
"fadePressureMap/v", "fadePressureMap/v",
"enableDebug/viii",
"drawNumber/viiii",
"clearDisplay/v",
"clearDisplay/vi",
"sendMIDI/vi",
"sendMIDI/vii",
"sendMIDI/viii",
"sendNoteOn/viii",
"sendNoteOff/viii",
"sendAftertouch/viii",
"sendCC/viii",
"sendPitchBend/vii",
"sendChannelPressure/vii",
"setChannelRange/vbii",
"assignChannel/ii",
"deassignChannel/vii",
"getControlChannel/i",
"useMPEDuplicateFilter/vb",
nullptr nullptr
}; };

+ 14
- 0
modules/juce_blocks_basics/protocol/juce_HostPacketBuilder.h View File

@@ -218,6 +218,20 @@ struct HostPacketBuilder
return true; 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;
}
//============================================================================== //==============================================================================
private: private:
Packed7BitArrayBuilder<maxPacketBytes> data; Packed7BitArrayBuilder<maxPacketBytes> data;


+ 47
- 0
modules/juce_blocks_basics/protocol/juce_HostPacketDecoder.h View File

@@ -78,7 +78,10 @@ struct HostPacketDecoder
case MessageFromDevice::touchEndWithVelocity: return handleTouchWithVelocity (handler, reader, deviceIndex, packetTimestamp, false, true); case MessageFromDevice::touchEndWithVelocity: return handleTouchWithVelocity (handler, reader, deviceIndex, packetTimestamp, false, true);
case MessageFromDevice::controlButtonDown: return handleButtonDownOrUp (handler, reader, deviceIndex, packetTimestamp, true); case MessageFromDevice::controlButtonDown: return handleButtonDownOrUp (handler, reader, deviceIndex, packetTimestamp, true);
case MessageFromDevice::controlButtonUp: return handleButtonDownOrUp (handler, reader, deviceIndex, packetTimestamp, false); 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::packetACK: return handlePacketACK (handler, reader, deviceIndex);
case MessageFromDevice::firmwareUpdateACK: return handleFirmwareUpdateACK (handler, reader, deviceIndex);
case MessageFromDevice::logMessage: return handleLogMessage (handler, reader, deviceIndex);
default: default:
jassertfalse; // got an invalid message type, could be a corrupt packet, or a jassertfalse; // got an invalid message type, could be a corrupt packet, or a
@@ -217,6 +220,24 @@ struct HostPacketDecoder
return true; 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) static bool handlePacketACK (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex)
{ {
if (reader.getRemainingBits() < BitSizes::packetACK - MessageType::bits) if (reader.getRemainingBits() < BitSizes::packetACK - MessageType::bits)
@@ -228,4 +249,30 @@ struct HostPacketDecoder
handler.handlePacketACK (deviceIndex, reader.read<PacketCounter>()); handler.handlePacketACK (deviceIndex, reader.read<PacketCounter>());
return true; 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>());
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;
}
}; };

+ 313
- 137
modules/juce_blocks_basics/topology/juce_PhysicalTopologySource.cpp View File

@@ -134,6 +134,21 @@ struct PhysicalTopologySource::Internal
if (midiInput != nullptr) if (midiInput != nullptr)
midiInput->stop(); midiInput->stop();
if (interprocessLock != nullptr)
interprocessLock->exit();
}
bool lockAgainstOtherProcesses (const String& midiInName, const String& midiOutName)
{
interprocessLock.reset (new juce::InterProcessLock ("blocks_sdk_"
+ File::createLegalFileName (midiInName)
+ "_" + File::createLegalFileName (midiOutName)));
if (interprocessLock->enter (500))
return true;
interprocessLock = nullptr;
return false;
} }
struct Listener struct Listener
@@ -189,6 +204,7 @@ struct PhysicalTopologySource::Internal
private: private:
juce::ListenerList<Listener> listeners; juce::ListenerList<Listener> listeners;
std::unique_ptr<juce::InterProcessLock> interprocessLock;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceConnection) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceConnection)
}; };
@@ -215,13 +231,16 @@ struct PhysicalTopologySource::Internal
{ {
std::unique_ptr<MIDIDeviceConnection> dev (new MIDIDeviceConnection()); std::unique_ptr<MIDIDeviceConnection> dev (new MIDIDeviceConnection());
dev->midiInput.reset (juce::MidiInput::openDevice (pair.inputIndex, dev.get()));
dev->midiOutput.reset (juce::MidiOutput::openDevice (pair.outputIndex));
if (dev->midiInput != nullptr)
if (dev->lockAgainstOtherProcesses (pair.inputName, pair.outputName))
{ {
dev->midiInput->start();
return dev.release();
dev->midiInput.reset (juce::MidiInput::openDevice (pair.inputIndex, dev.get()));
dev->midiOutput.reset (juce::MidiOutput::openDevice (pair.outputIndex));
if (dev->midiInput != nullptr)
{
dev->midiInput->start();
return dev.release();
}
} }
} }
@@ -486,6 +505,12 @@ struct PhysicalTopologySource::Internal
detector.handleButtonChange (deviceID, deviceTimestampToHost (timestamp), buttonID.get(), isDown); detector.handleButtonChange (deviceID, deviceTimestampToHost (timestamp), buttonID.get(), isDown);
} }
void handleCustomMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, const int32* data)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
detector.handleCustomMessage (deviceID, deviceTimestampToHost (timestamp), data);
}
void handleTouchChange (BlocksProtocol::TopologyIndex deviceIndex, void handleTouchChange (BlocksProtocol::TopologyIndex deviceIndex,
uint32 timestamp, uint32 timestamp,
BlocksProtocol::TouchIndex touchIndex, BlocksProtocol::TouchIndex touchIndex,
@@ -532,6 +557,18 @@ struct PhysicalTopologySource::Internal
detector.handleSharedDataACK (deviceID, counter); detector.handleSharedDataACK (deviceID, counter);
} }
void handleFirmwareUpdateACK (BlocksProtocol::TopologyIndex deviceIndex, BlocksProtocol::FirmwareUpdateACKCode resultCode)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
detector.handleFirmwareUpdateACK (deviceID, (uint8) resultCode.get());
}
void handleLogMessage (BlocksProtocol::TopologyIndex deviceIndex, const String& message)
{
if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex))
detector.handleLogMessage (deviceID, message);
}
//============================================================================== //==============================================================================
template <typename PacketBuilder> template <typename PacketBuilder>
bool sendMessageToDevice (const PacketBuilder& builder) const bool sendMessageToDevice (const PacketBuilder& builder) const
@@ -827,6 +864,24 @@ struct PhysicalTopologySource::Internal
bi->handleSharedDataACK (packetCounter); bi->handleSharedDataACK (packetCounter);
} }
void handleFirmwareUpdateACK (Block::UID deviceID, uint8 resultCode)
{
for (auto&& b : currentTopology.blocks)
if (b->uid == deviceID)
if (auto bi = BlockImplementation::getFrom (*b))
bi->handleFirmwareUpdateACK (resultCode);
}
void handleLogMessage (Block::UID deviceID, const String& message) const
{
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
for (auto&& b : currentTopology.blocks)
if (b->uid == deviceID)
if (auto bi = BlockImplementation::getFrom (*b))
bi->handleLogMessage (message);
}
void handleButtonChange (Block::UID deviceID, Block::Timestamp timestamp, uint32 buttonIndex, bool isDown) const void handleButtonChange (Block::UID deviceID, Block::Timestamp timestamp, uint32 buttonIndex, bool isDown) const
{ {
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
@@ -872,6 +927,14 @@ struct PhysicalTopologySource::Internal
surface->cancelAllActiveTouches(); surface->cancelAllActiveTouches();
} }
void handleCustomMessage (Block::UID deviceID, Block::Timestamp timestamp, const int32* data)
{
for (auto&& b : currentTopology.blocks)
if (b->uid == deviceID)
if (auto bi = BlockImplementation::getFrom (*b))
bi->handleCustomMessage (timestamp, data);
}
//============================================================================== //==============================================================================
int getIndexFromDeviceID (Block::UID deviceID) const noexcept int getIndexFromDeviceID (Block::UID deviceID) const noexcept
{ {
@@ -992,7 +1055,8 @@ struct PhysicalTopologySource::Internal
//============================================================================== //==============================================================================
struct BlockImplementation : public Block, struct BlockImplementation : public Block,
private MIDIDeviceConnection::Listener
private MIDIDeviceConnection::Listener,
private Timer
{ {
BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, Detector& detectorToUse, bool master) BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, Detector& detectorToUse, bool master)
: Block (juce::String ((const char*) serial.serial, sizeof (serial.serial))), modelData (serial), : Block (juce::String ((const char*) serial.serial, sizeof (serial.serial))), modelData (serial),
@@ -1108,6 +1172,16 @@ struct PhysicalTopologySource::Internal
return sendMessageToDevice (p); return sendMessageToDevice (p);
} }
void handleCustomMessage (Block::Timestamp, const int32* data)
{
ProgramEventMessage m;
for (uint32 i = 0; i < BlocksProtocol::numProgramMessageInts; ++i)
m.values[i] = data[i];
programEventListeners.call (&Block::ProgramEventListener::handleProgramEvent, *this, m);
}
static BlockImplementation* getFrom (Block& b) noexcept static BlockImplementation* getFrom (Block& b) noexcept
{ {
if (auto bi = dynamic_cast<BlockImplementation*> (&b)) if (auto bi = dynamic_cast<BlockImplementation*> (&b))
@@ -1127,42 +1201,84 @@ struct PhysicalTopologySource::Internal
} }
//============================================================================== //==============================================================================
void clearProgramAndData()
{
programSize = 0;
remoteHeap.clear();
}
std::function<void(const String&)> logger;
void setProgram (const void* compiledCode, size_t codeSize)
void setLogger (std::function<void(const String&)> newLogger) override
{ {
clearProgramAndData();
setDataBytes (0, compiledCode, codeSize);
programSize = (uint32) codeSize;
logger = newLogger;
} }
void setDataByte (size_t offset, uint8 value)
void handleLogMessage (const String& message) const
{ {
remoteHeap.setByte (programSize + offset, value);
if (logger != nullptr)
logger (message);
} }
void setDataBytes (size_t offset, const void* newData, size_t num)
//==============================================================================
juce::Result setProgram (Program* newProgram) override
{ {
remoteHeap.setBytes (programSize + offset, static_cast<const uint8*> (newData), num);
}
if (newProgram == nullptr || program.get() != newProgram)
{
{
std::unique_ptr<Program> p (newProgram);
void setDataBits (uint32 startBit, uint32 numBits, uint32 value)
{
remoteHeap.setBits (programSize * 8 + startBit, numBits, value);
}
if (program != nullptr
&& newProgram != nullptr
&& program->getLittleFootProgram() == newProgram->getLittleFootProgram())
return juce::Result::ok();
uint8 getDataByte (size_t offset)
{
return remoteHeap.getByte (programSize + offset);
stopTimer();
std::swap (program, p);
}
stopTimer();
programSize = 0;
if (program != nullptr)
{
littlefoot::Compiler compiler;
compiler.addNativeFunctions (PhysicalTopologySource::getStandardLittleFootFunctions());
auto err = compiler.compile (program->getLittleFootProgram(), 512);
if (err.failed())
return err;
DBG ("Compiled littlefoot program, space needed: "
<< (int) compiler.getCompiledProgram().getTotalSpaceNeeded() << " bytes");
if (compiler.getCompiledProgram().getTotalSpaceNeeded() > getMemorySize())
return Result::fail ("Program too large!");
size_t size = (size_t) compiler.compiledObjectCode.size();
programSize = (uint32) size;
remoteHeap.resetDataRangeToUnknown (0, remoteHeap.blockSize);
remoteHeap.clear();
remoteHeap.sendChanges (*this, true);
remoteHeap.resetDataRangeToUnknown (0, (uint32) size);
remoteHeap.setBytes (0, compiler.compiledObjectCode.begin(), size);
remoteHeap.sendChanges (*this, true);
}
else
{
remoteHeap.clear();
}
}
else
{
jassertfalse;
}
return juce::Result::ok();
} }
void sendProgramEvent (const LEDGrid::ProgramEventMessage& message)
Program* getProgram() const override { return program.get(); }
void sendProgramEvent (const ProgramEventMessage& message) override
{ {
static_assert (sizeof (LEDGrid::ProgramEventMessage::values) == 4 * BlocksProtocol::numProgramMessageInts,
static_assert (sizeof (ProgramEventMessage::values) == 4 * BlocksProtocol::numProgramMessageInts,
"Need to keep the internal and external messages structures the same"); "Need to keep the internal and external messages structures the same");
if (remoteHeap.isProgramLoaded()) if (remoteHeap.isProgramLoaded())
@@ -1187,9 +1303,47 @@ struct PhysicalTopologySource::Internal
} }
} }
void saveProgramAsDefault()
void timerCallback() override
{
if (remoteHeap.isFullySynced() && remoteHeap.isProgramLoaded())
{
stopTimer();
sendCommandMessage (BlocksProtocol::saveProgramAsDefault);
}
else
{
startTimer (100);
}
}
void saveProgramAsDefault() override
{
startTimer (10);
}
uint32 getMemorySize() override
{
return modelData.programAndHeapSize;
}
void setDataByte (size_t offset, uint8 value) override
{
remoteHeap.setByte (programSize + offset, value);
}
void setDataBytes (size_t offset, const void* newData, size_t num) override
{
remoteHeap.setBytes (programSize + offset, static_cast<const uint8*> (newData), num);
}
void setDataBits (uint32 startBit, uint32 numBits, uint32 value) override
{
remoteHeap.setBits (programSize * 8 + startBit, numBits, value);
}
uint8 getDataByte (size_t offset) override
{ {
sendCommandMessage (BlocksProtocol::saveProgramAsDefault);
return remoteHeap.getByte (programSize + offset);
} }
void handleSharedDataACK (uint32 packetCounter) noexcept void handleSharedDataACK (uint32 packetCounter) noexcept
@@ -1198,6 +1352,45 @@ struct PhysicalTopologySource::Internal
remoteHeap.handleACKFromDevice (*this, packetCounter); remoteHeap.handleACKFromDevice (*this, packetCounter);
} }
bool sendFirmwareUpdatePacket (const uint8* data, uint8 size, std::function<void (uint8)> callback) override
{
firmwarePacketAckCallback = {};
auto index = getDeviceIndex();
if (index >= 0)
{
BlocksProtocol::HostPacketBuilder<256> p;
p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index);
if (p.addFirmwareUpdatePacket (data, size))
{
p.writePacketSysexFooter();
if (sendMessageToDevice (p))
{
firmwarePacketAckCallback = callback;
return true;
}
}
}
else
{
jassertfalse;
}
return false;
}
void handleFirmwareUpdateACK (uint8 resultCode)
{
if (firmwarePacketAckCallback != nullptr)
{
firmwarePacketAckCallback (resultCode);
firmwarePacketAckCallback = {};
}
}
void pingFromDevice() void pingFromDevice()
{ {
lastMessageReceiveTime = juce::Time::getCurrentTime(); lastMessageReceiveTime = juce::Time::getCurrentTime();
@@ -1232,7 +1425,7 @@ struct PhysicalTopologySource::Internal
if (auto renderer = ledGrid->getRenderer()) if (auto renderer = ledGrid->getRenderer())
renderer->renderLEDGrid (*ledGrid); renderer->renderLEDGrid (*ledGrid);
remoteHeap.sendChanges (*this);
remoteHeap.sendChanges (*this, false);
if (lastMessageSendTime < juce::Time::getCurrentTime() - juce::RelativeTime::milliseconds (pingIntervalMs)) if (lastMessageSendTime < juce::Time::getCurrentTime() - juce::RelativeTime::milliseconds (pingIntervalMs))
sendCommandMessage (BlocksProtocol::ping); sendCommandMessage (BlocksProtocol::ping);
@@ -1260,12 +1453,15 @@ struct PhysicalTopologySource::Internal
using RemoteHeapType = littlefoot::LittleFootRemoteHeap<BlockImplementation>; using RemoteHeapType = littlefoot::LittleFootRemoteHeap<BlockImplementation>;
RemoteHeapType remoteHeap; RemoteHeapType remoteHeap;
uint32 programSize = 0;
Detector& detector; Detector& detector;
juce::Time lastMessageSendTime, lastMessageReceiveTime; juce::Time lastMessageSendTime, lastMessageReceiveTime;
private: private:
std::unique_ptr<Program> program;
uint32 programSize = 0;
std::function<void (uint8)> firmwarePacketAckCallback;
uint32 resetMessagesSent = 0; uint32 resetMessagesSent = 0;
bool isStillConnected = true; bool isStillConnected = true;
bool isMaster = false; bool isMaster = false;
@@ -1316,114 +1512,137 @@ struct PhysicalTopologySource::Internal
}; };
//============================================================================== //==============================================================================
struct LEDRowImplementation : public LEDRow
struct LEDRowImplementation : public LEDRow,
private Timer
{ {
LEDRowImplementation (BlockImplementation& b) : LEDRow (b), blockImpl (b)
LEDRowImplementation (BlockImplementation& b) : LEDRow (b)
{ {
loadProgramOntoBlock();
startTimer (300);
} }
/* Data format:
0: 10 x 5-6-5 bits for button LED RGBs
20: 15 x 5-6-5 bits for LED row colours
50: 1 x 5-6-5 bits for LED row overlay colour
*/
static constexpr uint32 totalDataSize = 256;
//==============================================================================
void setButtonColour (uint32 index, LEDColour colour) void setButtonColour (uint32 index, LEDColour colour)
{ {
if (index < 10) if (index < 10)
write565Colour (16 * index, colour);
{
colours[index] = colour;
flush();
}
} }
int getNumLEDs() const override int getNumLEDs() const override
{ {
return blockImpl.modelData.numLEDRowLEDs;
return static_cast<const BlockImplementation&> (block).modelData.numLEDRowLEDs;
} }
void setLEDColour (int index, LEDColour colour) override void setLEDColour (int index, LEDColour colour) override
{ {
if ((uint32) index < 15u) if ((uint32) index < 15u)
write565Colour (20 * 8 + 16 * (uint32) index, colour);
{
colours[10 + index] = colour;
flush();
}
} }
void setOverlayColour (LEDColour colour) override void setOverlayColour (LEDColour colour) override
{ {
write565Colour (50 * 8, colour);
colours[25] = colour;
flush();
} }
void resetOverlayColour() override void resetOverlayColour() override
{ {
write565Colour (50 * 8, {});
setOverlayColour ({});
} }
private: private:
void loadProgramOntoBlock()
{
littlefoot::Compiler compiler;
compiler.addNativeFunctions (PhysicalTopologySource::getStandardLittleFootFunctions());
LEDColour colours[26];
auto err = compiler.compile (getLittleFootProgram(), totalDataSize);
void timerCallback() override
{
stopTimer();
loadProgramOntoBlock();
flush();
}
if (err.failed())
void loadProgramOntoBlock()
{
if (block.getProgram() == nullptr)
{ {
DBG (err.getErrorMessage());
jassertfalse;
return;
auto err = block.setProgram (new DefaultLEDGridProgram (block));
if (err.failed())
{
DBG (err.getErrorMessage());
jassertfalse;
}
} }
}
blockImpl.setProgram (compiler.compiledObjectCode.begin(), (size_t) compiler.compiledObjectCode.size());
void flush()
{
if (block.getProgram() != nullptr)
for (uint32 i = 0; i < (uint32) numElementsInArray (colours); ++i)
write565Colour (16 * i, colours[i]);
} }
void write565Colour (uint32 bitIndex, LEDColour colour) void write565Colour (uint32 bitIndex, LEDColour colour)
{ {
blockImpl.setDataBits (bitIndex, 5, colour.getRed() >> 3);
blockImpl.setDataBits (bitIndex + 5, 6, colour.getGreen() >> 2);
blockImpl.setDataBits (bitIndex + 11, 5, colour.getBlue() >> 3);
block.setDataBits (bitIndex, 5, colour.getRed() >> 3);
block.setDataBits (bitIndex + 5, 6, colour.getGreen() >> 2);
block.setDataBits (bitIndex + 11, 5, colour.getBlue() >> 3);
} }
static const char* getLittleFootProgram() noexcept
struct DefaultLEDGridProgram : public Block::Program
{ {
return R"littlefoot(
DefaultLEDGridProgram (Block& b) : Block::Program (b) {}
int getColour (int bitIndex)
juce::String getLittleFootProgram() override
{ {
return makeARGB (255,
getHeapBits (bitIndex, 5) << 3,
getHeapBits (bitIndex + 5, 6) << 2,
getHeapBits (bitIndex + 11, 5) << 3);
}
/* Data format:
int getButtonColour (int index)
{
return getColour (16 * index);
}
0: 10 x 5-6-5 bits for button LED RGBs
20: 15 x 5-6-5 bits for LED row colours
50: 1 x 5-6-5 bits for LED row overlay colour
*/
return R"littlefoot(
int getLEDColour (int index)
{
if (getHeapInt (50))
return getColour (50 * 8);
#heapsize: 128
return getColour (20 * 8 + 16 * index);
}
int getColour (int bitIndex)
{
return makeARGB (255,
getHeapBits (bitIndex, 5) << 3,
getHeapBits (bitIndex + 5, 6) << 2,
getHeapBits (bitIndex + 11, 5) << 3);
}
void repaint()
{
for (int x = 0; x < 15; ++x)
setLED (x, 0, getLEDColour (x));
int getButtonColour (int index)
{
return getColour (16 * index);
}
for (int i = 0; i < 10; ++i)
setLED (i, 1, getButtonColour (i));
}
int getLEDColour (int index)
{
if (getHeapInt (50))
return getColour (50 * 8);
void handleMessage (int p1, int p2) {}
return getColour (20 * 8 + 16 * index);
}
)littlefoot";
}
void repaint()
{
for (int x = 0; x < 15; ++x)
fillPixel (getLEDColour (x), x, 0);
BlockImplementation& blockImpl;
for (int i = 0; i < 10; ++i)
fillPixel (getButtonColour (i), i, 1);
}
void handleMessage (int p1, int p2) {}
)littlefoot";
}
};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDRowImplementation) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDRowImplementation)
}; };
@@ -1638,50 +1857,7 @@ struct PhysicalTopologySource::Internal
int getNumColumns() const override { return blockImpl.modelData.lightGridWidth; } int getNumColumns() const override { return blockImpl.modelData.lightGridWidth; }
int getNumRows() const override { return blockImpl.modelData.lightGridHeight; } int getNumRows() const override { return blockImpl.modelData.lightGridHeight; }
juce::Result setProgram (Program* newProgram) override
{
if (program.get() != newProgram)
{
program.reset (newProgram);
if (program != nullptr)
{
littlefoot::Compiler compiler;
compiler.addNativeFunctions (PhysicalTopologySource::getStandardLittleFootFunctions());
auto err = compiler.compile (newProgram->getLittleFootProgram(), newProgram->getHeapSize());
if (err.failed())
return err;
DBG ("Compiled littlefoot program, size = " << (int) compiler.compiledObjectCode.size() << " bytes");
blockImpl.setProgram (compiler.compiledObjectCode.begin(), (size_t) compiler.compiledObjectCode.size());
}
else
{
blockImpl.clearProgramAndData();
}
}
else
{
jassertfalse;
}
return juce::Result::ok();
}
Program* getProgram() const override { return program.get(); }
void sendProgramEvent (const ProgramEventMessage& m) override { blockImpl.sendProgramEvent (m); }
void saveProgramAsDefault() override { blockImpl.saveProgramAsDefault(); }
void setDataByte (size_t offset, uint8 value) override { blockImpl.setDataByte (offset, value); }
void setDataBytes (size_t offset, const void* data, size_t num) override { blockImpl.setDataBytes (offset, data, num); }
void setDataBits (uint32 startBit, uint32 numBits, uint32 value) override { blockImpl.setDataBits (startBit, numBits, value); }
uint8 getDataByte (size_t offset) override { return blockImpl.getDataByte (offset); }
BlockImplementation& blockImpl; BlockImplementation& blockImpl;
std::unique_ptr<Program> program;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGridImplementation) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGridImplementation)
}; };


+ 26
- 19
modules/juce_blocks_basics/visualisers/juce_BitmapLEDProgram.cpp View File

@@ -29,7 +29,7 @@
*/ */
BitmapLEDProgram::BitmapLEDProgram (LEDGrid& lg) : Program (lg) {}
BitmapLEDProgram::BitmapLEDProgram (Block& b) : Program (b) {}
/* /*
The heap format for this program is just an array of 15x15 5:6:5 colours, The heap format for this program is just an array of 15x15 5:6:5 colours,
@@ -38,27 +38,31 @@ BitmapLEDProgram::BitmapLEDProgram (LEDGrid& lg) : Program (lg) {}
void BitmapLEDProgram::setLED (uint32 x, uint32 y, LEDColour colour) void BitmapLEDProgram::setLED (uint32 x, uint32 y, LEDColour colour)
{ {
auto w = (uint32) ledGrid.getNumColumns();
auto h = (uint32) ledGrid.getNumRows();
if (x < w && y < h)
if (auto ledGrid = block.getLEDGrid())
{ {
auto bit = (x + y * w) * 16;
auto w = (uint32) ledGrid->getNumColumns();
auto h = (uint32) ledGrid->getNumRows();
ledGrid.setDataBits (bit, 5, colour.getRed() >> 3);
ledGrid.setDataBits (bit + 5, 6, colour.getGreen() >> 2);
ledGrid.setDataBits (bit + 11, 5, colour.getBlue() >> 3);
}
}
if (x < w && y < h)
{
auto bit = (x + y * w) * 16;
uint32 BitmapLEDProgram::getHeapSize()
{
return 15 * 15 * 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() juce::String BitmapLEDProgram::getLittleFootProgram()
{ {
auto program = R"littlefoot(
String program (R"littlefoot(
#heapsize: 15 * 15 * 2
void repaint() void repaint()
{ {
@@ -76,9 +80,12 @@ juce::String BitmapLEDProgram::getLittleFootProgram()
} }
} }
)littlefoot";
)littlefoot");
if (auto ledGrid = block.getLEDGrid())
return program.replace ("NUM_COLUMNS", juce::String (ledGrid->getNumColumns()))
.replace ("NUM_ROWS", juce::String (ledGrid->getNumRows()));
return juce::String (program)
.replace ("NUM_COLUMNS", juce::String (ledGrid.getNumColumns()))
.replace ("NUM_ROWS", juce::String (ledGrid.getNumRows()));
jassertfalse;
return {};
} }

+ 2
- 3
modules/juce_blocks_basics/visualisers/juce_BitmapLEDProgram.h View File

@@ -32,14 +32,13 @@
/** /**
A simple Program to set the colours of individual LEDs. A simple Program to set the colours of individual LEDs.
*/ */
struct BitmapLEDProgram : public LEDGrid::Program
struct BitmapLEDProgram : public Block::Program
{ {
BitmapLEDProgram (LEDGrid&);
BitmapLEDProgram (Block&);
/** Set the colour of the LED at coordinates {x, y}. */ /** Set the colour of the LED at coordinates {x, y}. */
void setLED (uint32 x, uint32 y, LEDColour); void setLED (uint32 x, uint32 y, LEDColour);
private: private:
juce::String getLittleFootProgram() override; juce::String getLittleFootProgram() override;
uint32 getHeapSize() override;
}; };

+ 50
- 119
modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.cpp View File

@@ -29,16 +29,16 @@
*/ */
DrumPadGridProgram::DrumPadGridProgram (LEDGrid& lg) : Program (lg) {}
DrumPadGridProgram::DrumPadGridProgram (Block& b) : Program (b) {}
int DrumPadGridProgram::getPadIndex (float posX, float posY) const int DrumPadGridProgram::getPadIndex (float posX, float posY) const
{ {
posX = juce::jmin (0.99f, posX / ledGrid.block.getWidth());
posY = juce::jmin (0.99f, posY / ledGrid.block.getHeight());
posX = juce::jmin (0.99f, posX / block.getWidth());
posY = juce::jmin (0.99f, posY / block.getHeight());
const uint32 offset = ledGrid.getDataByte (visiblePads_byte) ? numColumns1_byte : numColumns0_byte;
const int numColumns = ledGrid.getDataByte (offset + numColumns0_byte);
const int numRows = ledGrid.getDataByte (offset + numRows0_byte);
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; return int (posX * numColumns) + int (posY * numRows) * numColumns;
} }
@@ -49,9 +49,9 @@ void DrumPadGridProgram::startTouch (float startX, float startY)
for (size_t i = 0; i < 4; ++i) for (size_t i = 0; i < 4; ++i)
{ {
if (ledGrid.getDataByte (touchedPads_byte + i) == 0)
if (block.getDataByte (touchedPads_byte + i) == 0)
{ {
ledGrid.setDataByte (touchedPads_byte + i, static_cast<uint8> (padIdx + 1));
block.setDataByte (touchedPads_byte + i, static_cast<uint8> (padIdx + 1));
break; break;
} }
} }
@@ -62,28 +62,28 @@ void DrumPadGridProgram::endTouch (float startX, float startY)
const auto padIdx = getPadIndex (startX, startY); const auto padIdx = getPadIndex (startX, startY);
for (size_t i = 0; i < 4; ++i) for (size_t i = 0; i < 4; ++i)
if (ledGrid.getDataByte (touchedPads_byte + i) == (padIdx + 1))
ledGrid.setDataByte (touchedPads_byte + i, 0);
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) void DrumPadGridProgram::sendTouch (float x, float y, float z, LEDColour colour)
{ {
LEDGrid::ProgramEventMessage e;
Block::ProgramEventMessage e;
e.values[0] = 0x20000000 e.values[0] = 0x20000000
+ (juce::jlimit (0, 255, juce::roundToInt (x * (255.0f / ledGrid.block.getWidth()))) << 16)
+ (juce::jlimit (0, 255, juce::roundToInt (y * (255.0f / ledGrid.block.getHeight()))) << 8)
+ (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)); + juce::jlimit (0, 255, juce::roundToInt (z * 255.0f));
e.values[1] = (int32) colour.getARGB(); e.values[1] = (int32) colour.getARGB();
ledGrid.sendProgramEvent (e);
block.sendProgramEvent (e);
} }
//============================================================================== //==============================================================================
void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills) void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills)
{ {
uint8 visiblePads = ledGrid.getDataByte (visiblePads_byte);
uint8 visiblePads = block.getDataByte (visiblePads_byte);
setGridFills (numColumns, numRows, fills, visiblePads * numColumns1_byte); setGridFills (numColumns, numRows, fills, visiblePads * numColumns1_byte);
} }
@@ -92,8 +92,8 @@ void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::
{ {
jassert (numColumns * numRows == fills.size()); jassert (numColumns * numRows == fills.size());
ledGrid.setDataByte (byteOffset + numColumns0_byte, (uint8) numColumns);
ledGrid.setDataByte (byteOffset + numRows0_byte, (uint8) numRows);
block.setDataByte (byteOffset + numColumns0_byte, (uint8) numColumns);
block.setDataByte (byteOffset + numRows0_byte, (uint8) numRows);
uint32 i = 0; uint32 i = 0;
@@ -108,11 +108,11 @@ void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::
const uint32 colourOffsetBytes = byteOffset + colours0_byte + i * colourSizeBytes; const uint32 colourOffsetBytes = byteOffset + colours0_byte + i * colourSizeBytes;
const uint32 colourOffsetBits = colourOffsetBytes * 8; const uint32 colourOffsetBits = colourOffsetBytes * 8;
ledGrid.setDataBits (colourOffsetBits, 5, fill.colour.getRed() >> 3);
ledGrid.setDataBits (colourOffsetBits + 5, 6, fill.colour.getGreen() >> 2);
ledGrid.setDataBits (colourOffsetBits + 11, 5, fill.colour.getBlue() >> 3);
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);
ledGrid.setDataByte (byteOffset + fillTypes0_byte + i, static_cast<uint8> (fill.fillType));
block.setDataByte (byteOffset + fillTypes0_byte + i, static_cast<uint8> (fill.fillType));
++i; ++i;
} }
@@ -121,12 +121,12 @@ void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::
void DrumPadGridProgram::triggerSlideTransition (int newNumColumns, int newNumRows, void DrumPadGridProgram::triggerSlideTransition (int newNumColumns, int newNumRows,
const juce::Array<GridFill>& newFills, SlideDirection direction) const juce::Array<GridFill>& newFills, SlideDirection direction)
{ {
uint8 newVisible = ledGrid.getDataByte (visiblePads_byte) ? 0 : 1;
uint8 newVisible = block.getDataByte (visiblePads_byte) ? 0 : 1;
setGridFills (newNumColumns, newNumRows, newFills, newVisible * numColumns1_byte); setGridFills (newNumColumns, newNumRows, newFills, newVisible * numColumns1_byte);
ledGrid.setDataByte (visiblePads_byte, newVisible);
ledGrid.setDataByte (slideDirection_byte, (uint8) direction);
block.setDataByte (visiblePads_byte, newVisible);
block.setDataByte (slideDirection_byte, (uint8) direction);
} }
//============================================================================== //==============================================================================
@@ -143,8 +143,8 @@ void DrumPadGridProgram::setPadAnimationState (uint32 padIdx, double loopTimeSec
uint32 offset = 8 * animationTimers_byte + 32 * padIdx; uint32 offset = 8 * animationTimers_byte + 32 * padIdx;
ledGrid.setDataBits (offset, 16, aniValue);
ledGrid.setDataBits (offset + 16, 16, aniIncrement);
block.setDataBits (offset, 16, aniValue);
block.setDataBits (offset + 16, 16, aniIncrement);
} }
void DrumPadGridProgram::suspendAnimations() void DrumPadGridProgram::suspendAnimations()
@@ -152,29 +152,26 @@ void DrumPadGridProgram::suspendAnimations()
for (uint32 i = 0; i < 16; ++i) for (uint32 i = 0; i < 16; ++i)
{ {
uint32 offset = 8 * animationTimers_byte + 32 * i; uint32 offset = 8 * animationTimers_byte + 32 * i;
ledGrid.setDataBits (offset + 16, 16, 0);
block.setDataBits (offset + 16, 16, 0);
} }
// Hijack touch dimming // Hijack touch dimming
ledGrid.setDataByte (touchedPads_byte, 255);
block.setDataByte (touchedPads_byte, 255);
} }
void DrumPadGridProgram::resumeAnimations() void DrumPadGridProgram::resumeAnimations()
{ {
// Unhijack touch dimming // Unhijack touch dimming
ledGrid.setDataByte (touchedPads_byte, 0);
block.setDataByte (touchedPads_byte, 0);
} }
//============================================================================== //==============================================================================
uint32 DrumPadGridProgram::getHeapSize()
{
return totalDataSize;
}
juce::String DrumPadGridProgram::getLittleFootProgram() juce::String DrumPadGridProgram::getLittleFootProgram()
{ {
return R"littlefoot( return R"littlefoot(
#heapsize: 256
int dimFactor; int dimFactor;
int dimDelay; int dimDelay;
int slideAnimationProgress; int slideAnimationProgress;
@@ -236,8 +233,8 @@ juce::String DrumPadGridProgram::getLittleFootProgram()
{ {
int gradColour = blendARGB (colour, makeARGB (((xx + yy) * 250) / divisor, 0, 0, 0)); int gradColour = blendARGB (colour, makeARGB (((xx + yy) * 250) / divisor, 0, 0, 0));
setLED (x + xx, y + yy, gradColour);
setLED (x + yy, y + xx, gradColour);
fillPixel (gradColour, x + xx, y + yy);
fillPixel (gradColour, x + yy, y + xx);
} }
} }
} }
@@ -255,7 +252,7 @@ juce::String DrumPadGridProgram::getLittleFootProgram()
for (int i = 1; i <= numToDo; ++i) for (int i = 1; i <= numToDo; ++i)
{ {
setLED (x, y, colour);
fillPixel (colour, x, y);
if (i < w) if (i < w)
++x; ++x;
@@ -278,89 +275,46 @@ juce::String DrumPadGridProgram::getLittleFootProgram()
{ {
fillGradientRect (colour, padX, padY, padW); fillGradientRect (colour, padX, padY, padW);
} }
else if (fill == 1) // Filled else if (fill == 1) // Filled
{ {
fillRect (colour, padX, padY, padW, padW); fillRect (colour, padX, padY, padW, padW);
} }
else if (fill == 2) // Hollow else if (fill == 2) // Hollow
{ {
outlineRect (colour, padX, padY, padW); outlineRect (colour, padX, padY, padW);
} }
else if (fill == 3) // Hollow with plus else if (fill == 3) // Hollow with plus
{ {
outlineRect (colour, padX, padY, padW); outlineRect (colour, padX, padY, padW);
drawPlus (0xffffffff, padX, padY, padW); drawPlus (0xffffffff, padX, padY, padW);
} }
else if (fill == 4) // Pulsing dot else if (fill == 4) // Pulsing dot
{ {
int pulseCol = blendARGB (colour, makeARGB (animateProgress, 0, 0, 0)); int pulseCol = blendARGB (colour, makeARGB (animateProgress, 0, 0, 0));
setLED (padX + halfW, padY + halfW, pulseCol);
fillPixel (pulseCol, padX + halfW, padY + halfW);
} }
else if (fill == 5) // Blinking dot else if (fill == 5) // Blinking dot
{ {
int blinkCol = animateProgress > 64 ? makeARGB (255, 0, 0, 0) : colour;
int blinkCol = animateProgress > 64 ? 0xff000000 : colour;
setLED (padX + halfW, padY + halfW, blinkCol);
fillPixel (blinkCol, padX + halfW, padY + halfW);
} }
else if (fill == 6) // Pizza filled 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
outlineRect (blendARGB (colour, 0xdc000000), padX, padY, padW); // Dim outline
fillPixel (colour, padX + halfW, padY + halfW); // Bright centre
drawPizzaLED (colour, padX, padY, padW, animateProgress); drawPizzaLED (colour, padX, padY, padW, animateProgress);
} }
else if (fill == 7) // Pizza hollow
else // Pizza hollow
{ {
outlineRect (blendARGB (colour, makeARGB (220, 0, 0, 0)), padX, padY, padW); // Dim outline
outlineRect (blendARGB (colour, 0xdc000000), padX, padY, padW); // Dim outline
drawPizzaLED (colour, padX, padY, padW, animateProgress); 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) int isPadActive (int index)
{ {
if (getHeapInt (158) == 0) // None active if (getHeapInt (158) == 0) // None active
@@ -499,48 +453,25 @@ juce::String DrumPadGridProgram::getLittleFootProgram()
slideAnimatePads(); slideAnimatePads();
// Overlay heatmap // Overlay heatmap
for (int y = 0; y < 15; ++y)
for (int x = 0; x < 15; ++x)
blendLED (x, y, getHeatmapColour (x, y));
fadeHeatMap();
drawPressureMap();
fadePressureMap();
} }
// DrumPadGridProgram::sendTouch results in this callback, giving // DrumPadGridProgram::sendTouch results in this callback, giving
// us more touch updates per frame and therefore smoother trails. // us more touch updates per frame and therefore smoother trails.
void handleMessage (int pos, int colour)
void handleMessage (int pos, int colour, int dummy)
{ {
if ((pos >> 24) != 0x20) if ((pos >> 24) != 0x20)
return; return;
int tx = ((pos >> 16) & 0xff) - 13;
int ty = ((pos >> 8) & 0xff) - 13;
int tx = (pos >> 16) & 0xff;
int ty = (pos >> 8) & 0xff;
int tz = pos & 0xff; 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));
}
}
addPressurePoint (colour,
tx * (2.0 / (256 + 20)),
ty * (2.0 / (256 + 20)),
tz * (1.0 / 3.0));
} }
)littlefoot"; )littlefoot";


+ 3
- 8
modules/juce_blocks_basics/visualisers/juce_DrumPadLEDProgram.h View File

@@ -30,9 +30,9 @@
/** /**
*/ */
struct DrumPadGridProgram : public LEDGrid::Program
struct DrumPadGridProgram : public Block::Program
{ {
DrumPadGridProgram (LEDGrid&);
DrumPadGridProgram (Block&);
//============================================================================== //==============================================================================
/** These let the program dim pads which aren't having gestures performed on them. */ /** These let the program dim pads which aren't having gestures performed on them. */
@@ -114,18 +114,13 @@ private:
static constexpr uint32 slideDirection_byte = 156; // 1 byte 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 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 animationTimers_byte = 162; // 4 byte x 16 (16:16 bits counter:increment)
static constexpr uint32 heatMap_byte = 226; // 4 byte x 225
static constexpr uint32 heatDecayMap_byte = 1126; // 1 byte x 225
static constexpr uint32 totalHeapSize = 226;
static constexpr uint32 maxNumPads = 25; static constexpr uint32 maxNumPads = 25;
static constexpr uint32 colourSizeBytes = 2; static constexpr uint32 colourSizeBytes = 2;
static constexpr uint32 heatMapSize = 15 * 15 * 4;
static constexpr uint32 heatMapDecaySize = 15 * 15;
static constexpr uint32 totalDataSize = heatDecayMap_byte + heatMapDecaySize;
int getPadIndex (float posX, float posY) const; int getPadIndex (float posX, float posY) const;
void setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills, uint32 byteOffset); void setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills, uint32 byteOffset);
juce::String getLittleFootProgram() override; juce::String getLittleFootProgram() override;
uint32 getHeapSize() override;
}; };

Loading…
Cancel
Save