mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-11 23:54:18 +00:00
BLOCKS SDK: New and updated version of juce_blocks_basics, adding functionality and compatibility with latest Dashboard-compatible firmware
This commit is contained in:
parent
beab10fca7
commit
f207ebb6d8
19 changed files with 1302 additions and 566 deletions
|
|
@ -233,7 +233,7 @@ public:
|
|||
scaleX = (float) (grid->getNumColumns()) / activeBlock->getWidth();
|
||||
scaleY = (float) (grid->getNumRows()) / activeBlock->getHeight();
|
||||
|
||||
setLEDProgram (grid);
|
||||
setLEDProgram (*activeBlock);
|
||||
}
|
||||
|
||||
// Make the on screen Lighpad component visible
|
||||
|
|
@ -286,7 +286,7 @@ private:
|
|||
{
|
||||
// Switch to canvas mode and set the LEDGrid program
|
||||
currentMode = canvas;
|
||||
setLEDProgram (activeBlock->getLEDGrid());
|
||||
setLEDProgram (*activeBlock);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -325,7 +325,7 @@ private:
|
|||
{
|
||||
// Switch to colour palette mode and set the LEDGrid program
|
||||
currentMode = colourPalette;
|
||||
setLEDProgram (activeBlock->getLEDGrid());
|
||||
setLEDProgram (*activeBlock);
|
||||
}
|
||||
|
||||
stopTimer();
|
||||
|
|
@ -350,7 +350,7 @@ private:
|
|||
}
|
||||
|
||||
/** Sets the LEDGrid Program for the selected mode */
|
||||
void setLEDProgram (LEDGrid* grid)
|
||||
void setLEDProgram (Block& block)
|
||||
{
|
||||
canvasProgram = nullptr;
|
||||
colourPaletteProgram = nullptr;
|
||||
|
|
@ -358,10 +358,10 @@ private:
|
|||
if (currentMode == canvas)
|
||||
{
|
||||
// Create a new BitmapLEDProgram for the LEDGrid
|
||||
canvasProgram = new BitmapLEDProgram (*grid);
|
||||
canvasProgram = new BitmapLEDProgram (block);
|
||||
|
||||
// Set the LEDGrid program
|
||||
grid->setProgram (canvasProgram);
|
||||
block.setProgram (canvasProgram);
|
||||
|
||||
// Redraw any previously drawn LEDs
|
||||
redrawLEDs();
|
||||
|
|
@ -369,10 +369,10 @@ private:
|
|||
else if (currentMode == colourPalette)
|
||||
{
|
||||
// Create a new DrumPadGridProgram for the LEDGrid
|
||||
colourPaletteProgram = new DrumPadGridProgram (*grid);
|
||||
colourPaletteProgram = new DrumPadGridProgram (block);
|
||||
|
||||
// Set the LEDGrid program
|
||||
grid->setProgram (colourPaletteProgram);
|
||||
block.setProgram (colourPaletteProgram);
|
||||
|
||||
// Setup the grid layout
|
||||
colourPaletteProgram->setGridFills (layout.numColumns,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ public:
|
|||
|
||||
// If this is a Lightpad then set the grid program to be blank
|
||||
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 (block->getType() == Block::lightPadBlock)
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ public:
|
|||
scaleX = static_cast<float> (grid->getNumColumns() - 1) / activeBlock->getWidth();
|
||||
scaleY = static_cast<float> (grid->getNumRows() - 1) / activeBlock->getHeight();
|
||||
|
||||
setLEDProgram (grid);
|
||||
setLEDProgram (*activeBlock);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -227,7 +227,7 @@ private:
|
|||
currentMode = waveformSelectionMode;
|
||||
|
||||
// Set the LEDGrid program to the new mode
|
||||
setLEDProgram (activeBlock->getLEDGrid());
|
||||
setLEDProgram (*activeBlock);
|
||||
}
|
||||
|
||||
#if JUCE_IOS
|
||||
|
|
@ -259,15 +259,15 @@ private:
|
|||
}
|
||||
|
||||
/** Sets the LEDGrid Program for the selected mode */
|
||||
void setLEDProgram (LEDGrid* grid)
|
||||
void setLEDProgram (Block& block)
|
||||
{
|
||||
if (currentMode == waveformSelectionMode)
|
||||
{
|
||||
// Create a new WaveshapeProgram for the LEDGrid
|
||||
waveshapeProgram = new WaveshapeProgram (*grid);
|
||||
waveshapeProgram = new WaveshapeProgram (block);
|
||||
|
||||
// Set the LEDGrid program
|
||||
grid->setProgram (waveshapeProgram);
|
||||
block.setProgram (waveshapeProgram);
|
||||
|
||||
// Initialise the program
|
||||
waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode));
|
||||
|
|
@ -276,10 +276,16 @@ private:
|
|||
else if (currentMode == playMode)
|
||||
{
|
||||
// Create a new DrumPadGridProgram for the LEDGrid
|
||||
gridProgram = new DrumPadGridProgram (*grid);
|
||||
gridProgram = new DrumPadGridProgram (block);
|
||||
|
||||
// Set the LEDGrid program
|
||||
grid->setProgram (gridProgram);
|
||||
auto error = block.setProgram (gridProgram);
|
||||
|
||||
if (error.failed())
|
||||
{
|
||||
DBG (error.getErrorMessage());
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
// Setup the grid layout
|
||||
gridProgram->setGridFills (layout.numColumns,
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@
|
|||
/**
|
||||
A Program to draw moving waveshapes onto the LEDGrid
|
||||
*/
|
||||
class WaveshapeProgram : public LEDGrid::Program
|
||||
class WaveshapeProgram : public Block::Program
|
||||
{
|
||||
public:
|
||||
WaveshapeProgram (LEDGrid& lg) : Program (lg) {}
|
||||
WaveshapeProgram (Block& b) : Program (b) {}
|
||||
|
||||
/** Sets the waveshape type to display on the grid */
|
||||
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
|
||||
|
|
@ -74,22 +74,19 @@ public:
|
|||
// Store the values for each of the waveshapes at the correct offsets in the shared data heap
|
||||
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
|
||||
{
|
||||
return R"littlefoot(
|
||||
|
||||
#heapsize: 256
|
||||
|
||||
int yOffset;
|
||||
|
||||
int min (int a, int b)
|
||||
|
|
@ -173,8 +170,6 @@ private:
|
|||
static constexpr uint32 sawWaveOffset = 91; // 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)
|
||||
};
|
||||
|
|
|
|||
323
modules/juce_blocks_basics/LittleFootFunctions.txt
Normal file
323
modules/juce_blocks_basics/LittleFootFunctions.txt
Normal 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);
|
||||
|
|
@ -65,9 +65,16 @@ Block::~Block() {}
|
|||
void Block::addDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.add (listener); }
|
||||
void Block::removeDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.remove (listener); }
|
||||
|
||||
void Block::addProgramEventListener (ProgramEventListener* listener) { programEventListeners.add (listener); }
|
||||
void Block::removeProgramEventListener (ProgramEventListener* listener) { programEventListeners.remove (listener); }
|
||||
|
||||
|
||||
bool Block::ConnectionPort::operator== (const ConnectionPort& other) const noexcept { return edge == other.edge && index == other.index; }
|
||||
bool Block::ConnectionPort::operator!= (const ConnectionPort& other) const noexcept { return ! operator== (other); }
|
||||
|
||||
Block::Program::Program (Block& b) : block (b) {}
|
||||
Block::Program::~Program() {}
|
||||
|
||||
//==============================================================================
|
||||
TouchSurface::TouchSurface (Block& b) : block (b) {}
|
||||
TouchSurface::~TouchSurface() {}
|
||||
|
|
@ -91,9 +98,6 @@ void ControlButton::removeListener (Listener* l) { listeners.remove (l);
|
|||
LEDGrid::LEDGrid (Block& b) : block (b) {}
|
||||
LEDGrid::~LEDGrid() {}
|
||||
|
||||
LEDGrid::Program::Program (LEDGrid& l) : ledGrid (l) {}
|
||||
LEDGrid::Program::~Program() {}
|
||||
|
||||
LEDGrid::Renderer::~Renderer() {}
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -184,6 +184,94 @@ public:
|
|||
/** Returns a list of the connectors that this device has. */
|
||||
virtual juce::Array<ConnectionPort> getPorts() const = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** A program that can be loaded onto a block. */
|
||||
struct Program
|
||||
{
|
||||
/** Creates a Program for the corresponding LEDGrid. */
|
||||
Program (Block&);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~Program();
|
||||
|
||||
/** Returns the LittleFoot program to execute on the BLOCKS device. */
|
||||
virtual juce::String getLittleFootProgram() = 0;
|
||||
|
||||
Block& block;
|
||||
};
|
||||
|
||||
/** Sets the Program to run on this block.
|
||||
|
||||
The supplied Program's lifetime will be managed by this class, so do not
|
||||
use the Program in other places in your code.
|
||||
*/
|
||||
virtual juce::Result setProgram (Program*) = 0;
|
||||
|
||||
/** Returns a pointer to the currently loaded program. */
|
||||
virtual Program* getProgram() const = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** A message that can be sent to the currently loaded program. */
|
||||
struct ProgramEventMessage
|
||||
{
|
||||
int32 values[3];
|
||||
};
|
||||
|
||||
/** Sends a message to the currently loaded program.
|
||||
|
||||
To receive the message the program must provide a littlefoot function called
|
||||
handleMessage with the following form:
|
||||
@code
|
||||
void handleMessage (int param1, int param2)
|
||||
{
|
||||
// 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. */
|
||||
struct DataInputPortListener
|
||||
|
|
@ -194,10 +282,10 @@ public:
|
|||
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*);
|
||||
|
||||
/** Removes a listener of data input port. */
|
||||
/** Removes a listener for the data input port. */
|
||||
virtual void removeDataInputPortListener (DataInputPortListener*);
|
||||
|
||||
/** Sends a message to the block. */
|
||||
|
|
@ -214,6 +302,7 @@ protected:
|
|||
Block (const juce::String& serialNumberToUse);
|
||||
|
||||
juce::ListenerList<DataInputPortListener> dataInputPortListeners;
|
||||
juce::ListenerList<ProgramEventListener> programEventListeners;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -73,95 +73,32 @@ public:
|
|||
/** Returns the number of rows in the LED grid. */
|
||||
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 void renderLEDGrid (LEDGrid&) = 0;
|
||||
|
||||
/** The Renderer class is reference-counted, so always use a Renderer::Ptr when
|
||||
you are keeping references to them.
|
||||
*/
|
||||
using Ptr = juce::ReferenceCountedObjectPtr<Renderer>;
|
||||
};
|
||||
|
||||
/** Set the visualiser that will create visuals for this block (nullptr for none).
|
||||
Note that the LEDGrid will NOT take ownership of this object, so the caller
|
||||
must ensure that it doesn't get deleted while in use here.
|
||||
*/
|
||||
void setRenderer (Renderer* newRenderer) noexcept { renderer = newRenderer; }
|
||||
void setRenderer (Renderer::Ptr newRenderer) noexcept { renderer = newRenderer; }
|
||||
|
||||
/** 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. */
|
||||
Block& block;
|
||||
|
||||
private:
|
||||
Renderer* renderer = nullptr;
|
||||
Renderer::Ptr renderer;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGrid)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -39,9 +39,10 @@ using namespace juce;
|
|||
*/
|
||||
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)
|
||||
{
|
||||
|
|
@ -49,7 +50,8 @@ struct Compiler
|
|||
nativeFunctions.add (NativeFunction (*functionPrototypes, nullptr));
|
||||
}
|
||||
|
||||
/**
|
||||
/** Tells the compiler to use the list of native function prototypes from
|
||||
this littlefoot::Runner object.
|
||||
*/
|
||||
template <typename RunnerType>
|
||||
void addNativeFunctions (const RunnerType& runner)
|
||||
|
|
@ -58,20 +60,22 @@ struct Compiler
|
|||
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
|
||||
{
|
||||
SyntaxTreeBuilder stb (sourceCode);
|
||||
SyntaxTreeBuilder stb (sourceCode, nativeFunctions, defaultHeapSize);
|
||||
stb.compile();
|
||||
stb.simplify();
|
||||
|
||||
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();
|
||||
}
|
||||
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;
|
||||
|
||||
|
|
@ -102,7 +113,7 @@ private:
|
|||
X(return_, "return") X(true_, "true") X(false_, "false")
|
||||
|
||||
#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(openBracket, "[") X(closeBracket, "]") X(colon, ":") X(question, "?") \
|
||||
X(equals, "==") X(assign, "=") X(notEquals, "!=") X(logicalNot, "!") \
|
||||
|
|
@ -354,7 +365,8 @@ private:
|
|||
//==============================================================================
|
||||
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()
|
||||
{
|
||||
|
|
@ -362,6 +374,12 @@ private:
|
|||
|
||||
while (currentType != Token::eof)
|
||||
{
|
||||
if (matchIf (Token::hash))
|
||||
{
|
||||
parseCompilerDirective();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! matchesAnyTypeOrVoid())
|
||||
throwErrorExpecting ("a global variable or function");
|
||||
|
||||
|
|
@ -377,7 +395,7 @@ private:
|
|||
if (type == Type::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)
|
||||
location.throwError ("Arrays not yet implemented!");
|
||||
|
|
@ -399,9 +417,29 @@ private:
|
|||
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;
|
||||
Array<Function*> functions;
|
||||
const Array<NativeFunction>& nativeFunctions;
|
||||
uint32 heapSizeRequired;
|
||||
|
||||
template <typename Type, typename... Args>
|
||||
Type* allocate (Args... args) { auto o = new Type (args...); allAllocatedObjects.add (o); return o; }
|
||||
|
|
@ -410,10 +448,23 @@ private:
|
|||
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)
|
||||
{
|
||||
auto f = allocate<Function>();
|
||||
functions.add (f);
|
||||
|
||||
while (matchesAnyType())
|
||||
{
|
||||
|
|
@ -431,6 +482,12 @@ private:
|
|||
|
||||
match (Token::closeParen);
|
||||
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->returnType = returnType;
|
||||
|
||||
|
|
@ -443,12 +500,11 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
int parseArraySize()
|
||||
int parseIntegerLiteral()
|
||||
{
|
||||
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())
|
||||
{
|
||||
|
|
@ -459,7 +515,7 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
location.throwError ("An array size must be a constant integer");
|
||||
location.throwError ("Expected an integer constant");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -618,8 +674,8 @@ private:
|
|||
{
|
||||
if (currentType == Token::identifier) return parseSuffixes (allocate<Identifier> (location, blockBeingParsed, parseIdentifier()));
|
||||
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)
|
||||
{
|
||||
|
|
@ -835,12 +891,12 @@ private:
|
|||
//==============================================================================
|
||||
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)
|
||||
{
|
||||
for (auto f : functions)
|
||||
for (auto f : syntaxTree.functions)
|
||||
{
|
||||
f->address = createMarker();
|
||||
f->unwindAddress = createMarker();
|
||||
|
|
@ -848,16 +904,19 @@ private:
|
|||
|
||||
emit ((int16) 0); // checksum
|
||||
emit ((int16) 0); // size
|
||||
emit ((int16) functions.size());
|
||||
emit ((int16) syntaxTree.functions.size());
|
||||
emit ((int16) outerBlock->variables.size());
|
||||
emit ((int16) heapSizeBytesRequired);
|
||||
|
||||
for (auto f : functions)
|
||||
for (auto f : syntaxTree.functions)
|
||||
emit (f->functionID, f->address);
|
||||
|
||||
for (auto f : functions)
|
||||
auto codeStart = outputCode.size();
|
||||
|
||||
for (auto f : syntaxTree.functions)
|
||||
f->emit (*this);
|
||||
|
||||
removeJumpsToNextInstruction (codeStart);
|
||||
resolveMarkers();
|
||||
|
||||
Program::writeInt16 (outputCode.begin() + 2, (int16) outputCode.size());
|
||||
|
|
@ -868,39 +927,92 @@ private:
|
|||
|
||||
//==============================================================================
|
||||
Array<uint8>& outputCode;
|
||||
const Array<NativeFunction>& nativeFunctions;
|
||||
const Array<Function*>& functions;
|
||||
const SyntaxTreeBuilder& syntaxTree;
|
||||
|
||||
struct Marker { int index = 0; };
|
||||
struct MarkerAndAddress { int markerIndex, address; };
|
||||
struct MarkerAndAddress { Marker marker; int address; };
|
||||
|
||||
int nextMarker = 0;
|
||||
Array<MarkerAndAddress> markersToResolve, resolvedMarkers;
|
||||
|
||||
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)
|
||||
if (m.markerIndex == markerIndex)
|
||||
if (m.marker.index == marker.index)
|
||||
return m.address;
|
||||
|
||||
jassertfalse;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Marker getMarkerAtAddress (int address) const noexcept
|
||||
{
|
||||
for (auto m : markersToResolve)
|
||||
if (m.address == address)
|
||||
return m.marker;
|
||||
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
||||
void resolveMarkers()
|
||||
{
|
||||
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;
|
||||
|
||||
//==============================================================================
|
||||
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 (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)); }
|
||||
|
|
@ -955,7 +1067,7 @@ private:
|
|||
index += stackDepth;
|
||||
|
||||
if (index == 0)
|
||||
emit ((OpCode) ((int) OpCode::dup));
|
||||
emit (OpCode::dup);
|
||||
else if (index < 8)
|
||||
emit ((OpCode) ((int) OpCode::dupOffset_01 + index - 1));
|
||||
else if (index >= 128)
|
||||
|
|
@ -966,25 +1078,6 @@ private:
|
|||
|
||||
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_)
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -1798,7 +1889,7 @@ private:
|
|||
|
||||
auto functionID = getFunctionID (cg);
|
||||
|
||||
if (auto fn = cg.findFunction (functionID))
|
||||
if (auto fn = cg.syntaxTree.findFunction (functionID))
|
||||
{
|
||||
emitArgs (cg, fn->getArgumentTypes(), stackDepth);
|
||||
cg.emit (OpCode::call, fn->address);
|
||||
|
|
@ -1806,7 +1897,7 @@ private:
|
|||
return;
|
||||
}
|
||||
|
||||
if (auto nativeFn = cg.findNativeFunction (functionID))
|
||||
if (auto nativeFn = cg.syntaxTree.findNativeFunction (functionID))
|
||||
{
|
||||
emitArgs (cg, getArgTypesFromFunctionName (nativeFn->nameAndArguments), stackDepth);
|
||||
cg.emit (OpCode::callNative, nativeFn->functionID);
|
||||
|
|
@ -1836,10 +1927,10 @@ private:
|
|||
|
||||
auto functionID = getFunctionID (cg);
|
||||
|
||||
if (auto fn = cg.findFunction (functionID))
|
||||
if (auto fn = cg.syntaxTree.findFunction (functionID))
|
||||
return fn->returnType;
|
||||
|
||||
if (auto nativeFn = cg.findNativeFunction (functionID))
|
||||
if (auto nativeFn = cg.syntaxTree.findNativeFunction (functionID))
|
||||
return nativeFn->returnType;
|
||||
|
||||
if (auto b = findBuiltInFunction (functionID))
|
||||
|
|
|
|||
|
|
@ -52,16 +52,35 @@ struct LittleFootRemoteHeap
|
|||
void clear() noexcept
|
||||
{
|
||||
zeromem (targetData, sizeof (targetData));
|
||||
invalidateData();
|
||||
}
|
||||
|
||||
void resetDeviceStateToUnknown()
|
||||
{
|
||||
invalidateData();
|
||||
messagesSent.clear();
|
||||
resetDataRangeToUnknown (0, ImplementationClass::maxBlockSize);
|
||||
}
|
||||
|
||||
void resetDataRangeToUnknown (size_t offset, size_t size) noexcept
|
||||
{
|
||||
auto* latestState = getLatestExpectedDataState();
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
latestState[offset + i] = unknownByte;
|
||||
}
|
||||
|
||||
void setByte (size_t offset, uint8 value) noexcept
|
||||
{
|
||||
if (offset < blockSize)
|
||||
{
|
||||
if (targetData [offset] != value)
|
||||
if (targetData[offset] != value)
|
||||
{
|
||||
targetData [offset] = value;
|
||||
invalidateData();
|
||||
targetData[offset] = value;
|
||||
needsSyncing = true;
|
||||
|
||||
if (programStateKnown && offset < programSize)
|
||||
programStateKnown = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -100,37 +119,31 @@ struct LittleFootRemoteHeap
|
|||
return 0;
|
||||
}
|
||||
|
||||
void invalidateData()
|
||||
void invalidateData() noexcept
|
||||
{
|
||||
dataHasChanged = true;
|
||||
needsSyncing = true;
|
||||
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];
|
||||
uint32 packetIndex;
|
||||
auto* latestState = getLatestExpectedDataState();
|
||||
|
||||
if (messagesSent.isEmpty())
|
||||
{
|
||||
for (uint32 i = 0; i < blockSize; ++i)
|
||||
data[i] = deviceState[i];
|
||||
for (uint32 i = 0; i < blockSize; ++i)
|
||||
data[i] = latestState[i];
|
||||
|
||||
packetIndex = lastPacketIndexReceived;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& lastPacket = messagesSent.getReference (messagesSent.size() - 1);
|
||||
|
||||
for (uint32 i = 0; i < blockSize; ++i)
|
||||
data[i] = lastPacket.resultDataState[i];
|
||||
|
||||
packetIndex = lastPacket.packetIndex;
|
||||
}
|
||||
uint32 packetIndex = messagesSent.isEmpty() ? lastPacketIndexReceived
|
||||
: messagesSent.getLast()->packetIndex;
|
||||
|
||||
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;
|
||||
|
||||
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)
|
||||
break;
|
||||
|
|
@ -157,7 +170,7 @@ struct LittleFootRemoteHeap
|
|||
|
||||
void handleACKFromDevice (ImplementationClass& bi, uint32 packetIndex) noexcept
|
||||
{
|
||||
//DBG ("ACK " << (int) packetIndex);
|
||||
//DBG ("ACK " << (int) packetIndex << " device " << (int) bi.getDeviceIndex());
|
||||
|
||||
if (lastPacketIndexReceived != packetIndex)
|
||||
{
|
||||
|
|
@ -165,7 +178,7 @@ struct LittleFootRemoteHeap
|
|||
|
||||
for (int i = messagesSent.size(); --i >= 0;)
|
||||
{
|
||||
auto& m = messagesSent.getReference(i);
|
||||
auto& m = *messagesSent.getUnchecked(i);
|
||||
|
||||
if (m.packetIndex == packetIndex)
|
||||
{
|
||||
|
|
@ -174,10 +187,10 @@ struct LittleFootRemoteHeap
|
|||
|
||||
messagesSent.removeRange (0, i + 1);
|
||||
dumpStatus();
|
||||
sendChanges (bi);
|
||||
sendChanges (bi, false);
|
||||
|
||||
if (messagesSent.isEmpty())
|
||||
dataHasChanged = false;
|
||||
needsSyncing = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -191,17 +204,16 @@ struct LittleFootRemoteHeap
|
|||
{
|
||||
if (! programStateKnown)
|
||||
{
|
||||
programStateKnown = true;
|
||||
|
||||
uint8 deviceMemory[ImplementationClass::maxBlockSize];
|
||||
bool anyUnknowns = false;
|
||||
|
||||
for (size_t i = 0; i < blockSize; ++i)
|
||||
{
|
||||
anyUnknowns = (deviceState[i] > 255);
|
||||
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;
|
||||
|
|
@ -214,15 +226,13 @@ struct LittleFootRemoteHeap
|
|||
private:
|
||||
uint16 deviceState[ImplementationClass::maxBlockSize];
|
||||
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
|
||||
|
|
@ -233,16 +243,16 @@ private:
|
|||
uint16 resultDataState[ImplementationClass::maxBlockSize];
|
||||
};
|
||||
|
||||
Array<ChangeMessage> messagesSent;
|
||||
OwnedArray<ChangeMessage> messagesSent;
|
||||
uint32 lastPacketIndexReceived = 0;
|
||||
|
||||
int getTotalSizeOfMessagesSent() const noexcept
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
for (auto& m : messagesSent)
|
||||
if (m.dispatchTime != Time())
|
||||
total += m.packet.size();
|
||||
for (auto* m : messagesSent)
|
||||
if (m->dispatchTime != Time())
|
||||
total += m->packet.size();
|
||||
|
||||
return total;
|
||||
}
|
||||
|
|
@ -280,6 +290,8 @@ private:
|
|||
Diff (uint16* current, const uint8* target, size_t blockSizeToUse)
|
||||
: newData (target), blockSize (blockSizeToUse)
|
||||
{
|
||||
ranges.ensureStorageAllocated ((int) blockSize);
|
||||
|
||||
for (int i = 0; i < (int) blockSize; ++i)
|
||||
ranges.add ({ i, 1, newData[i] == current[i], false });
|
||||
|
||||
|
|
@ -290,7 +302,7 @@ private:
|
|||
|
||||
bool createChangeMessage (const ImplementationClass& bi,
|
||||
const uint16* currentState,
|
||||
Array<ChangeMessage>& messagesCreated,
|
||||
OwnedArray<ChangeMessage>& messagesCreated,
|
||||
uint32 nextPacketIndex)
|
||||
{
|
||||
if (ranges.isEmpty())
|
||||
|
|
@ -301,8 +313,7 @@ private:
|
|||
if (deviceIndex < 0)
|
||||
return false;
|
||||
|
||||
messagesCreated.add ({});
|
||||
auto& message = messagesCreated.getReference (messagesCreated.size() - 1);
|
||||
auto& message = *messagesCreated.add (new ChangeMessage());
|
||||
|
||||
message.packetIndex = nextPacketIndex;
|
||||
|
||||
|
|
@ -366,27 +377,27 @@ private:
|
|||
|
||||
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
|
||||
&& (r1.isSkipped || newData[r1.index] == newData[r2.index]))
|
||||
{
|
||||
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()
|
||||
{
|
||||
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)
|
||||
&& (r1.isMixed || r1.length == 1)
|
||||
|
|
@ -396,8 +407,8 @@ private:
|
|||
{
|
||||
r1.length += r2.length;
|
||||
r1.isMixed = true;
|
||||
ranges.remove (i + 1);
|
||||
i = jmax (0, i - 2);
|
||||
ranges.remove (i);
|
||||
i = jmin (ranges.size() - 1, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,11 +44,11 @@ namespace littlefoot
|
|||
#define LITTLEFOOT_DUMP_PROGRAM 0
|
||||
#endif
|
||||
|
||||
using int8 = char;
|
||||
using int8 = signed char;
|
||||
using uint8 = unsigned char;
|
||||
using int16 = short;
|
||||
using int16 = signed short;
|
||||
using uint16 = unsigned short;
|
||||
using int32 = int;
|
||||
using int32 = signed int;
|
||||
using uint32 = unsigned int;
|
||||
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 (juce::String (nameAndArgTypes).substring (0, slash).containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"));
|
||||
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)
|
||||
if (i != slash + 1)
|
||||
|
|
@ -247,13 +247,6 @@ struct Program
|
|||
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
|
||||
{
|
||||
return (uint16) readInt16 (programStart + 4);
|
||||
|
|
@ -286,10 +279,11 @@ struct Program
|
|||
: 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 */
|
||||
|
|
@ -298,6 +292,17 @@ struct Program
|
|||
return (uint16) readInt16 (programStart + 8);
|
||||
}
|
||||
|
||||
/** Returns the number of global variables the program uses */
|
||||
uint16 getNumGlobals() const noexcept
|
||||
{
|
||||
return (uint16) readInt16 (programStart + 6);
|
||||
}
|
||||
|
||||
uint32 getTotalSpaceNeeded() const noexcept
|
||||
{
|
||||
return getProgramSize() + getHeapSizeBytes();
|
||||
}
|
||||
|
||||
#if JUCE_DEBUG
|
||||
//==============================================================================
|
||||
/** Prints the assembly code for a given function. */
|
||||
|
|
@ -307,7 +312,7 @@ struct Program
|
|||
<< " (" << juce::String::toHexString (getFunctionID (functionIndex)) << ")" << juce::newLine;
|
||||
|
||||
if (auto codeStart = getFunctionStartAddress (functionIndex))
|
||||
if (auto codeEnd = getFunctionEndAddress (functionIndex))
|
||||
if (auto codeEnd = getFunctionEndAddress (functionIndex))
|
||||
for (auto prog = codeStart; prog < codeEnd;)
|
||||
out << getOpDisassembly (prog) << juce::newLine;
|
||||
}
|
||||
|
|
@ -339,19 +344,41 @@ struct Program
|
|||
/** Calls dumpFunctionDisassembly() for all functions. */
|
||||
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
|
||||
|
||||
/** For a given op code, this returns the number of program bytes that follow it. */
|
||||
static uint8 getNumExtraBytesForOpcode (OpCode op) noexcept
|
||||
{
|
||||
switch (op)
|
||||
{
|
||||
#define LITTLEFOOT_OP(name) case OpCode::name: return 0;
|
||||
#define LITTLEFOOT_OP_INT8(name) case OpCode::name: return 1;
|
||||
#define LITTLEFOOT_OP_INT16(name) case OpCode::name: return 2;
|
||||
#define LITTLEFOOT_OP_INT32(name) case OpCode::name: return 4;
|
||||
LITTLEFOOT_OPCODES (LITTLEFOOT_OP, LITTLEFOOT_OP_INT8, LITTLEFOOT_OP_INT16, LITTLEFOOT_OP_INT32)
|
||||
#undef LITTLEFOOT_OP
|
||||
#undef LITTLEFOOT_OP_INT8
|
||||
#undef LITTLEFOOT_OP_INT16
|
||||
#undef LITTLEFOOT_OP_INT32
|
||||
|
||||
default: jassertfalse; return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static float intToFloat (int32 value) noexcept { float v; copyFloatMem (&v, &value); return v; }
|
||||
static int32 floatToInt (float value) noexcept { int32 v; copyFloatMem (&v, &value); return v; }
|
||||
|
||||
static int16 readInt16 (const uint8* d) noexcept { return (int16) (d[0] + (((uint16) d[1]) << 8)); }
|
||||
static int32 readInt32 (const uint8* d) noexcept { return (int32) ((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 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;
|
||||
}
|
||||
|
||||
/** Clears all the non-program data. */
|
||||
void clearHeapAndGlobals() noexcept
|
||||
{
|
||||
auto* start = getProgramAndDataStart() + program.getProgramSize();
|
||||
auto* end = allMemory + sizeof (allMemory);
|
||||
|
||||
for (auto m = start; m < end; ++m)
|
||||
*m = 0;
|
||||
}
|
||||
|
||||
/** Return codes from a function call */
|
||||
enum class ErrorCode : uint8
|
||||
{
|
||||
|
|
@ -429,6 +466,23 @@ struct Runner
|
|||
unknownFunction
|
||||
};
|
||||
|
||||
/** Returns a text description for an error code */
|
||||
static const char* getErrorDescription (ErrorCode e) noexcept
|
||||
{
|
||||
switch (e)
|
||||
{
|
||||
case ErrorCode::ok: return "OK";
|
||||
case ErrorCode::executionTimedOut: return "Timed-out";
|
||||
case ErrorCode::unknownInstruction: return "Illegal instruction";
|
||||
case ErrorCode::stackOverflow: return "Stack overflow";
|
||||
case ErrorCode::stackUnderflow: return "Stack underflow";
|
||||
case ErrorCode::illegalAddress: return "Illegal access";
|
||||
case ErrorCode::divisionByZero: return "Division by zero";
|
||||
case ErrorCode::unknownFunction: return "Unknown function";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
/** Calls one of the functions in the program, by its textual signature. */
|
||||
ErrorCode callFunction (const char* functionSignature) noexcept
|
||||
{
|
||||
|
|
@ -467,15 +521,17 @@ struct Runner
|
|||
/** */
|
||||
bool isProgramValid() const noexcept { return heapStart != nullptr; }
|
||||
|
||||
/** */
|
||||
/** Sets a byte of data. */
|
||||
void setDataByte (uint32 index, uint8 value) noexcept
|
||||
{
|
||||
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
|
||||
|
||||
getProgramAndDataStart()[index] = value;
|
||||
dest = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -561,7 +617,7 @@ struct Runner
|
|||
if (prog.getFunctionID (i) == function)
|
||||
{
|
||||
programCounter = prog.getFunctionStartAddress (i);
|
||||
functionEnd = prog.getFunctionEndAddress (i);
|
||||
programEnd = r.getProgramHeapStart();
|
||||
tos = *--stack = 0;
|
||||
return;
|
||||
}
|
||||
|
|
@ -599,7 +655,7 @@ struct Runner
|
|||
|
||||
for (;;)
|
||||
{
|
||||
if (programCounter >= functionEnd)
|
||||
if (programCounter >= programEnd)
|
||||
return error;
|
||||
|
||||
if ((++opsPerformed & 63) == 0 && hasTimedOut())
|
||||
|
|
@ -628,7 +684,7 @@ struct Runner
|
|||
//==============================================================================
|
||||
Runner* runner;
|
||||
const uint8* programCounter;
|
||||
const uint8* functionEnd;
|
||||
const uint8* programEnd;
|
||||
const uint8* programBase;
|
||||
uint8* heapStart;
|
||||
int32* stack;
|
||||
|
|
@ -646,7 +702,7 @@ struct Runner
|
|||
int16 readProgram16() noexcept { auto v = Program::readInt16 (programCounter); programCounter += sizeof (int16); return v; }
|
||||
int32 readProgram32() noexcept { auto v = Program::readInt32 (programCounter); programCounter += sizeof (int32); return v; }
|
||||
|
||||
void setError (ErrorCode e) noexcept { error = e; programCounter = 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 flushTopToStack() noexcept { if (--stack < stackStart) { setError (ErrorCode::stackOverflow); return false; } *stack = tos; return true; }
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ enum class MessageFromDevice
|
|||
{
|
||||
deviceTopology = 0x01,
|
||||
packetACK = 0x02,
|
||||
firmwareUpdateACK = 0x03,
|
||||
|
||||
touchStart = 0x10,
|
||||
touchMove = 0x11,
|
||||
|
|
@ -62,7 +63,11 @@ enum class MessageFromDevice
|
|||
touchEndWithVelocity = 0x15,
|
||||
|
||||
controlButtonDown = 0x20,
|
||||
controlButtonUp = 0x21
|
||||
controlButtonUp = 0x21,
|
||||
|
||||
programEventMessage = 0x28,
|
||||
|
||||
logMessage = 0x30
|
||||
};
|
||||
|
||||
/** Messages that the host may send to a device. */
|
||||
|
|
@ -70,7 +75,8 @@ enum class MessageFromHost
|
|||
{
|
||||
deviceCommandMessage = 0x01,
|
||||
sharedDataChange = 0x02,
|
||||
programEventMessage = 0x03
|
||||
programEventMessage = 0x03,
|
||||
firmwareUpdatePacket = 0x04
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -224,15 +230,18 @@ using ByteCountMany = IntegerWithBitSize<8>;
|
|||
using ByteValue = IntegerWithBitSize<8>;
|
||||
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 padBlockProgramAndHeapSize = 3200;
|
||||
static constexpr uint32 padBlockProgramAndHeapSize = 7200;
|
||||
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,
|
||||
packetACK = MessageType::bits + PacketCounter::bits,
|
||||
|
||||
firmwareUpdateACK = MessageType::bits + FirmwareUpdateACKCode::bits,
|
||||
|
||||
controlButtonMessage = typeDeviceAndTime + ControlButtonID::bits,
|
||||
};
|
||||
|
||||
|
|
@ -258,18 +269,62 @@ enum BitSizes
|
|||
// These are the littlefoot functions provided for use in BLOCKS programs
|
||||
static constexpr const char* ledProgramLittleFootFunctions[] =
|
||||
{
|
||||
"min/iii",
|
||||
"min/fff",
|
||||
"max/iii",
|
||||
"max/fff",
|
||||
"clamp/iiii",
|
||||
"clamp/ffff",
|
||||
"abs/ii",
|
||||
"abs/ff",
|
||||
"map/ffffff",
|
||||
"map/ffff",
|
||||
"mod/iii",
|
||||
"getRandomFloat/f",
|
||||
"getRandomInt/ii",
|
||||
"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",
|
||||
"blendARGB/iii",
|
||||
"setLED/viii",
|
||||
"blendLED/viii",
|
||||
"fillPixel/viii",
|
||||
"blendPixel/viii",
|
||||
"fillRect/viiiii",
|
||||
"blendRect/viiiii",
|
||||
"sendMIDI/vi",
|
||||
"sendMIDI/vii",
|
||||
"sendMIDI/viii",
|
||||
"blendGradientRect/viiiiiiii",
|
||||
"addPressurePoint/vifff",
|
||||
"drawPressureMap/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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -218,6 +218,20 @@ struct HostPacketBuilder
|
|||
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:
|
||||
Packed7BitArrayBuilder<maxPacketBytes> data;
|
||||
|
|
|
|||
|
|
@ -78,7 +78,10 @@ struct HostPacketDecoder
|
|||
case MessageFromDevice::touchEndWithVelocity: return handleTouchWithVelocity (handler, reader, deviceIndex, packetTimestamp, false, true);
|
||||
case MessageFromDevice::controlButtonDown: return handleButtonDownOrUp (handler, reader, deviceIndex, packetTimestamp, true);
|
||||
case MessageFromDevice::controlButtonUp: return handleButtonDownOrUp (handler, reader, deviceIndex, packetTimestamp, false);
|
||||
case MessageFromDevice::programEventMessage: return handleCustomMessage (handler, reader, deviceIndex, packetTimestamp);
|
||||
case MessageFromDevice::packetACK: return handlePacketACK (handler, reader, deviceIndex);
|
||||
case MessageFromDevice::firmwareUpdateACK: return handleFirmwareUpdateACK (handler, reader, deviceIndex);
|
||||
case MessageFromDevice::logMessage: return handleLogMessage (handler, reader, deviceIndex);
|
||||
|
||||
default:
|
||||
jassertfalse; // got an invalid message type, could be a corrupt packet, or a
|
||||
|
|
@ -217,6 +220,24 @@ struct HostPacketDecoder
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool handleCustomMessage (Handler& handler, Packed7BitArrayReader& reader,
|
||||
TopologyIndex deviceIndex, PacketTimestamp packetTimestamp)
|
||||
{
|
||||
if (reader.getRemainingBits() < BitSizes::programEventMessage - MessageType::bits)
|
||||
{
|
||||
jassertfalse; // not enough data available for this message type!
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 data[numProgramMessageInts] = {};
|
||||
|
||||
for (uint32 i = 0; i < numProgramMessageInts; ++i)
|
||||
data[i] = (int32) reader.read<IntegerWithBitSize<32>>().get();
|
||||
|
||||
handler.handleCustomMessage (deviceIndex, packetTimestamp.get(), data);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool handlePacketACK (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex)
|
||||
{
|
||||
if (reader.getRemainingBits() < BitSizes::packetACK - MessageType::bits)
|
||||
|
|
@ -228,4 +249,30 @@ struct HostPacketDecoder
|
|||
handler.handlePacketACK (deviceIndex, reader.read<PacketCounter>());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool handleFirmwareUpdateACK (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex)
|
||||
{
|
||||
if (reader.getRemainingBits() < FirmwareUpdateACKCode::bits)
|
||||
{
|
||||
jassertfalse; // not enough data available for this message type!
|
||||
return false;
|
||||
}
|
||||
|
||||
handler.handleFirmwareUpdateACK (deviceIndex, reader.read<FirmwareUpdateACKCode>());
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -134,6 +134,21 @@ struct PhysicalTopologySource::Internal
|
|||
|
||||
if (midiInput != nullptr)
|
||||
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
|
||||
|
|
@ -189,6 +204,7 @@ struct PhysicalTopologySource::Internal
|
|||
|
||||
private:
|
||||
juce::ListenerList<Listener> listeners;
|
||||
std::unique_ptr<juce::InterProcessLock> interprocessLock;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceConnection)
|
||||
};
|
||||
|
|
@ -215,13 +231,16 @@ struct PhysicalTopologySource::Internal
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
uint32 timestamp,
|
||||
BlocksProtocol::TouchIndex touchIndex,
|
||||
|
|
@ -532,6 +557,18 @@ struct PhysicalTopologySource::Internal
|
|||
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>
|
||||
bool sendMessageToDevice (const PacketBuilder& builder) const
|
||||
|
|
@ -827,6 +864,24 @@ struct PhysicalTopologySource::Internal
|
|||
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
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
|
@ -872,6 +927,14 @@ struct PhysicalTopologySource::Internal
|
|||
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
|
||||
{
|
||||
|
|
@ -992,7 +1055,8 @@ struct PhysicalTopologySource::Internal
|
|||
|
||||
//==============================================================================
|
||||
struct BlockImplementation : public Block,
|
||||
private MIDIDeviceConnection::Listener
|
||||
private MIDIDeviceConnection::Listener,
|
||||
private Timer
|
||||
{
|
||||
BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, Detector& detectorToUse, bool master)
|
||||
: Block (juce::String ((const char*) serial.serial, sizeof (serial.serial))), modelData (serial),
|
||||
|
|
@ -1108,6 +1172,16 @@ struct PhysicalTopologySource::Internal
|
|||
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
|
||||
{
|
||||
if (auto bi = dynamic_cast<BlockImplementation*> (&b))
|
||||
|
|
@ -1127,42 +1201,84 @@ struct PhysicalTopologySource::Internal
|
|||
}
|
||||
|
||||
//==============================================================================
|
||||
void clearProgramAndData()
|
||||
std::function<void(const String&)> logger;
|
||||
|
||||
void setLogger (std::function<void(const String&)> newLogger) override
|
||||
{
|
||||
programSize = 0;
|
||||
remoteHeap.clear();
|
||||
logger = newLogger;
|
||||
}
|
||||
|
||||
void setProgram (const void* compiledCode, size_t codeSize)
|
||||
void handleLogMessage (const String& message) const
|
||||
{
|
||||
clearProgramAndData();
|
||||
setDataBytes (0, compiledCode, codeSize);
|
||||
programSize = (uint32) codeSize;
|
||||
if (logger != nullptr)
|
||||
logger (message);
|
||||
}
|
||||
|
||||
void setDataByte (size_t offset, uint8 value)
|
||||
//==============================================================================
|
||||
juce::Result setProgram (Program* newProgram) override
|
||||
{
|
||||
remoteHeap.setByte (programSize + offset, value);
|
||||
if (newProgram == nullptr || program.get() != newProgram)
|
||||
{
|
||||
{
|
||||
std::unique_ptr<Program> p (newProgram);
|
||||
|
||||
if (program != nullptr
|
||||
&& newProgram != nullptr
|
||||
&& program->getLittleFootProgram() == newProgram->getLittleFootProgram())
|
||||
return juce::Result::ok();
|
||||
|
||||
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 setDataBytes (size_t offset, const void* newData, size_t num)
|
||||
{
|
||||
remoteHeap.setBytes (programSize + offset, static_cast<const uint8*> (newData), num);
|
||||
}
|
||||
Program* getProgram() const override { return program.get(); }
|
||||
|
||||
void setDataBits (uint32 startBit, uint32 numBits, uint32 value)
|
||||
void sendProgramEvent (const ProgramEventMessage& message) override
|
||||
{
|
||||
remoteHeap.setBits (programSize * 8 + startBit, numBits, value);
|
||||
}
|
||||
|
||||
uint8 getDataByte (size_t offset)
|
||||
{
|
||||
return remoteHeap.getByte (programSize + offset);
|
||||
}
|
||||
|
||||
void sendProgramEvent (const LEDGrid::ProgramEventMessage& message)
|
||||
{
|
||||
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");
|
||||
|
||||
if (remoteHeap.isProgramLoaded())
|
||||
|
|
@ -1187,9 +1303,47 @@ struct PhysicalTopologySource::Internal
|
|||
}
|
||||
}
|
||||
|
||||
void saveProgramAsDefault()
|
||||
void timerCallback() override
|
||||
{
|
||||
sendCommandMessage (BlocksProtocol::saveProgramAsDefault);
|
||||
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
|
||||
{
|
||||
return remoteHeap.getByte (programSize + offset);
|
||||
}
|
||||
|
||||
void handleSharedDataACK (uint32 packetCounter) noexcept
|
||||
|
|
@ -1198,6 +1352,45 @@ struct PhysicalTopologySource::Internal
|
|||
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()
|
||||
{
|
||||
lastMessageReceiveTime = juce::Time::getCurrentTime();
|
||||
|
|
@ -1232,7 +1425,7 @@ struct PhysicalTopologySource::Internal
|
|||
if (auto renderer = ledGrid->getRenderer())
|
||||
renderer->renderLEDGrid (*ledGrid);
|
||||
|
||||
remoteHeap.sendChanges (*this);
|
||||
remoteHeap.sendChanges (*this, false);
|
||||
|
||||
if (lastMessageSendTime < juce::Time::getCurrentTime() - juce::RelativeTime::milliseconds (pingIntervalMs))
|
||||
sendCommandMessage (BlocksProtocol::ping);
|
||||
|
|
@ -1260,12 +1453,15 @@ struct PhysicalTopologySource::Internal
|
|||
using RemoteHeapType = littlefoot::LittleFootRemoteHeap<BlockImplementation>;
|
||||
RemoteHeapType remoteHeap;
|
||||
|
||||
uint32 programSize = 0;
|
||||
|
||||
Detector& detector;
|
||||
juce::Time lastMessageSendTime, lastMessageReceiveTime;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Program> program;
|
||||
uint32 programSize = 0;
|
||||
|
||||
std::function<void (uint8)> firmwarePacketAckCallback;
|
||||
|
||||
uint32 resetMessagesSent = 0;
|
||||
bool isStillConnected = true;
|
||||
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)
|
||||
{
|
||||
if (index < 10)
|
||||
write565Colour (16 * index, colour);
|
||||
{
|
||||
colours[index] = colour;
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
int getNumLEDs() const override
|
||||
{
|
||||
return blockImpl.modelData.numLEDRowLEDs;
|
||||
return static_cast<const BlockImplementation&> (block).modelData.numLEDRowLEDs;
|
||||
}
|
||||
|
||||
void setLEDColour (int index, LEDColour colour) override
|
||||
{
|
||||
if ((uint32) index < 15u)
|
||||
write565Colour (20 * 8 + 16 * (uint32) index, colour);
|
||||
{
|
||||
colours[10 + index] = colour;
|
||||
flush();
|
||||
}
|
||||
}
|
||||
|
||||
void setOverlayColour (LEDColour colour) override
|
||||
{
|
||||
write565Colour (50 * 8, colour);
|
||||
colours[25] = colour;
|
||||
flush();
|
||||
}
|
||||
|
||||
void resetOverlayColour() override
|
||||
{
|
||||
write565Colour (50 * 8, {});
|
||||
setOverlayColour ({});
|
||||
}
|
||||
|
||||
private:
|
||||
LEDColour colours[26];
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
stopTimer();
|
||||
loadProgramOntoBlock();
|
||||
flush();
|
||||
}
|
||||
|
||||
void loadProgramOntoBlock()
|
||||
{
|
||||
littlefoot::Compiler compiler;
|
||||
compiler.addNativeFunctions (PhysicalTopologySource::getStandardLittleFootFunctions());
|
||||
|
||||
auto err = compiler.compile (getLittleFootProgram(), totalDataSize);
|
||||
|
||||
if (err.failed())
|
||||
if (block.getProgram() == nullptr)
|
||||
{
|
||||
DBG (err.getErrorMessage());
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
auto err = block.setProgram (new DefaultLEDGridProgram (block));
|
||||
|
||||
blockImpl.setProgram (compiler.compiledObjectCode.begin(), (size_t) compiler.compiledObjectCode.size());
|
||||
if (err.failed())
|
||||
{
|
||||
DBG (err.getErrorMessage());
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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:
|
||||
|
||||
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(
|
||||
|
||||
#heapsize: 128
|
||||
|
||||
int getColour (int bitIndex)
|
||||
{
|
||||
return makeARGB (255,
|
||||
getHeapBits (bitIndex, 5) << 3,
|
||||
getHeapBits (bitIndex + 5, 6) << 2,
|
||||
getHeapBits (bitIndex + 11, 5) << 3);
|
||||
}
|
||||
|
||||
int getButtonColour (int index)
|
||||
{
|
||||
return getColour (16 * index);
|
||||
}
|
||||
|
||||
int getLEDColour (int index)
|
||||
{
|
||||
if (getHeapInt (50))
|
||||
return getColour (50 * 8);
|
||||
|
||||
return getColour (20 * 8 + 16 * index);
|
||||
}
|
||||
|
||||
void repaint()
|
||||
{
|
||||
for (int x = 0; x < 15; ++x)
|
||||
fillPixel (getLEDColour (x), x, 0);
|
||||
|
||||
for (int i = 0; i < 10; ++i)
|
||||
fillPixel (getButtonColour (i), i, 1);
|
||||
}
|
||||
|
||||
void handleMessage (int p1, int p2) {}
|
||||
|
||||
)littlefoot";
|
||||
}
|
||||
|
||||
int getButtonColour (int index)
|
||||
{
|
||||
return getColour (16 * index);
|
||||
}
|
||||
|
||||
int getLEDColour (int index)
|
||||
{
|
||||
if (getHeapInt (50))
|
||||
return getColour (50 * 8);
|
||||
|
||||
return getColour (20 * 8 + 16 * index);
|
||||
}
|
||||
|
||||
void repaint()
|
||||
{
|
||||
for (int x = 0; x < 15; ++x)
|
||||
setLED (x, 0, getLEDColour (x));
|
||||
|
||||
for (int i = 0; i < 10; ++i)
|
||||
setLED (i, 1, getButtonColour (i));
|
||||
}
|
||||
|
||||
void handleMessage (int p1, int p2) {}
|
||||
|
||||
)littlefoot";
|
||||
}
|
||||
|
||||
BlockImplementation& blockImpl;
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDRowImplementation)
|
||||
};
|
||||
|
|
@ -1638,50 +1857,7 @@ struct PhysicalTopologySource::Internal
|
|||
int getNumColumns() const override { return blockImpl.modelData.lightGridWidth; }
|
||||
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;
|
||||
std::unique_ptr<Program> program;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGridImplementation)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -38,27 +38,31 @@ BitmapLEDProgram::BitmapLEDProgram (LEDGrid& lg) : Program (lg) {}
|
|||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 BitmapLEDProgram::getHeapSize()
|
||||
{
|
||||
return 15 * 15 * 16;
|
||||
}
|
||||
|
||||
juce::String BitmapLEDProgram::getLittleFootProgram()
|
||||
{
|
||||
auto program = R"littlefoot(
|
||||
String program (R"littlefoot(
|
||||
|
||||
#heapsize: 15 * 15 * 2
|
||||
|
||||
void repaint()
|
||||
{
|
||||
|
|
@ -76,9 +80,12 @@ juce::String BitmapLEDProgram::getLittleFootProgram()
|
|||
}
|
||||
}
|
||||
|
||||
)littlefoot";
|
||||
)littlefoot");
|
||||
|
||||
return juce::String (program)
|
||||
.replace ("NUM_COLUMNS", juce::String (ledGrid.getNumColumns()))
|
||||
.replace ("NUM_ROWS", juce::String (ledGrid.getNumRows()));
|
||||
if (auto ledGrid = block.getLEDGrid())
|
||||
return program.replace ("NUM_COLUMNS", juce::String (ledGrid->getNumColumns()))
|
||||
.replace ("NUM_ROWS", juce::String (ledGrid->getNumRows()));
|
||||
|
||||
jassertfalse;
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,14 +32,13 @@
|
|||
/**
|
||||
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}. */
|
||||
void setLED (uint32 x, uint32 y, LEDColour);
|
||||
|
||||
private:
|
||||
juce::String getLittleFootProgram() override;
|
||||
uint32 getHeapSize() override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,16 +29,16 @@
|
|||
*/
|
||||
|
||||
|
||||
DrumPadGridProgram::DrumPadGridProgram (LEDGrid& lg) : Program (lg) {}
|
||||
DrumPadGridProgram::DrumPadGridProgram (Block& b) : Program (b) {}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -49,9 +49,9 @@ void DrumPadGridProgram::startTouch (float startX, float startY)
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -62,28 +62,28 @@ void DrumPadGridProgram::endTouch (float startX, float startY)
|
|||
const auto padIdx = getPadIndex (startX, startY);
|
||||
|
||||
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)
|
||||
{
|
||||
LEDGrid::ProgramEventMessage e;
|
||||
Block::ProgramEventMessage e;
|
||||
|
||||
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));
|
||||
|
||||
e.values[1] = (int32) colour.getARGB();
|
||||
|
||||
ledGrid.sendProgramEvent (e);
|
||||
block.sendProgramEvent (e);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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);
|
||||
}
|
||||
|
|
@ -92,8 +92,8 @@ void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::
|
|||
{
|
||||
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;
|
||||
|
||||
|
|
@ -108,11 +108,11 @@ void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::
|
|||
const uint32 colourOffsetBytes = byteOffset + colours0_byte + i * colourSizeBytes;
|
||||
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;
|
||||
}
|
||||
|
|
@ -121,12 +121,12 @@ void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::
|
|||
void DrumPadGridProgram::triggerSlideTransition (int newNumColumns, int newNumRows,
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
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()
|
||||
|
|
@ -152,29 +152,26 @@ void DrumPadGridProgram::suspendAnimations()
|
|||
for (uint32 i = 0; i < 16; ++i)
|
||||
{
|
||||
uint32 offset = 8 * animationTimers_byte + 32 * i;
|
||||
ledGrid.setDataBits (offset + 16, 16, 0);
|
||||
block.setDataBits (offset + 16, 16, 0);
|
||||
}
|
||||
|
||||
// Hijack touch dimming
|
||||
ledGrid.setDataByte (touchedPads_byte, 255);
|
||||
block.setDataByte (touchedPads_byte, 255);
|
||||
}
|
||||
|
||||
void DrumPadGridProgram::resumeAnimations()
|
||||
{
|
||||
// Unhijack touch dimming
|
||||
ledGrid.setDataByte (touchedPads_byte, 0);
|
||||
block.setDataByte (touchedPads_byte, 0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
uint32 DrumPadGridProgram::getHeapSize()
|
||||
{
|
||||
return totalDataSize;
|
||||
}
|
||||
|
||||
juce::String DrumPadGridProgram::getLittleFootProgram()
|
||||
{
|
||||
return R"littlefoot(
|
||||
|
||||
#heapsize: 256
|
||||
|
||||
int dimFactor;
|
||||
int dimDelay;
|
||||
int slideAnimationProgress;
|
||||
|
|
@ -236,8 +233,8 @@ juce::String DrumPadGridProgram::getLittleFootProgram()
|
|||
{
|
||||
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)
|
||||
{
|
||||
setLED (x, y, colour);
|
||||
fillPixel (colour, x, y);
|
||||
|
||||
if (i < w)
|
||||
++x;
|
||||
|
|
@ -278,89 +275,46 @@ juce::String DrumPadGridProgram::getLittleFootProgram()
|
|||
{
|
||||
fillGradientRect (colour, padX, padY, padW);
|
||||
}
|
||||
|
||||
else if (fill == 1) // Filled
|
||||
{
|
||||
fillRect (colour, padX, padY, padW, padW);
|
||||
}
|
||||
|
||||
else if (fill == 2) // Hollow
|
||||
{
|
||||
outlineRect (colour, padX, padY, padW);
|
||||
}
|
||||
|
||||
else if (fill == 3) // Hollow with plus
|
||||
{
|
||||
outlineRect (colour, padX, padY, padW);
|
||||
drawPlus (0xffffffff, padX, padY, padW);
|
||||
}
|
||||
|
||||
else if (fill == 4) // Pulsing dot
|
||||
{
|
||||
int pulseCol = blendARGB (colour, makeARGB (animateProgress, 0, 0, 0));
|
||||
|
||||
setLED (padX + halfW, padY + halfW, pulseCol);
|
||||
fillPixel (pulseCol, padX + halfW, padY + halfW);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void fadeHeatMap()
|
||||
{
|
||||
for (int i = 0; i < 225; ++i)
|
||||
{
|
||||
int colourOffset = 226 + i * 4;
|
||||
int colour = getHeapInt (colourOffset);
|
||||
int alpha = (colour >> 24) & 0xff;
|
||||
|
||||
if (alpha > 0)
|
||||
{
|
||||
alpha -= getHeapByte (1126 + i);
|
||||
setHeapInt (colourOffset, alpha < 0 ? 0 : ((alpha << 24) | (colour & 0xffffff)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addToHeatMap (int x, int y, int colour)
|
||||
{
|
||||
if (x >= 0 && y >= 0 && x < 15 && y < 15)
|
||||
{
|
||||
int offset = 226 + 4 * (x + y * 15);
|
||||
colour = blendARGB (getHeapInt (offset), colour);
|
||||
setHeapInt (offset, colour);
|
||||
|
||||
int decay = ((colour >> 24) & 0xff) / 14; // change divisor to change trail times
|
||||
offset = 1126 + (x + y * 15);
|
||||
setHeapByte (offset, decay > 0 ? decay : 1);
|
||||
}
|
||||
}
|
||||
|
||||
int getHeatmapColour (int x, int y)
|
||||
{
|
||||
return getHeapInt (226 + 4 * (x + y * 15));
|
||||
}
|
||||
|
||||
int isPadActive (int index)
|
||||
{
|
||||
if (getHeapInt (158) == 0) // None active
|
||||
|
|
@ -499,48 +453,25 @@ juce::String DrumPadGridProgram::getLittleFootProgram()
|
|||
slideAnimatePads();
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
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;
|
||||
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";
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
@ -114,18 +114,13 @@ private:
|
|||
static constexpr uint32 slideDirection_byte = 156; // 1 byte
|
||||
static constexpr uint32 touchedPads_byte = 158; // 1 byte x 4 (Zero means empty slot, so stores padIdx + 1)
|
||||
static constexpr uint32 animationTimers_byte = 162; // 4 byte x 16 (16:16 bits counter:increment)
|
||||
static constexpr uint32 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 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;
|
||||
void setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills, uint32 byteOffset);
|
||||
|
||||
juce::String getLittleFootProgram() override;
|
||||
uint32 getHeapSize() override;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue