| @@ -233,7 +233,7 @@ public: | |||||
| scaleX = (float) (grid->getNumColumns()) / activeBlock->getWidth(); | scaleX = (float) (grid->getNumColumns()) / activeBlock->getWidth(); | ||||
| scaleY = (float) (grid->getNumRows()) / activeBlock->getHeight(); | scaleY = (float) (grid->getNumRows()) / activeBlock->getHeight(); | ||||
| setLEDProgram (grid); | |||||
| setLEDProgram (*activeBlock); | |||||
| } | } | ||||
| // Make the on screen Lighpad component visible | // Make the on screen Lighpad component visible | ||||
| @@ -286,7 +286,7 @@ private: | |||||
| { | { | ||||
| // Switch to canvas mode and set the LEDGrid program | // Switch to canvas mode and set the LEDGrid program | ||||
| currentMode = canvas; | currentMode = canvas; | ||||
| setLEDProgram (activeBlock->getLEDGrid()); | |||||
| setLEDProgram (*activeBlock); | |||||
| } | } | ||||
| } | } | ||||
| @@ -325,7 +325,7 @@ private: | |||||
| { | { | ||||
| // Switch to colour palette mode and set the LEDGrid program | // Switch to colour palette mode and set the LEDGrid program | ||||
| currentMode = colourPalette; | currentMode = colourPalette; | ||||
| setLEDProgram (activeBlock->getLEDGrid()); | |||||
| setLEDProgram (*activeBlock); | |||||
| } | } | ||||
| stopTimer(); | stopTimer(); | ||||
| @@ -350,7 +350,7 @@ private: | |||||
| } | } | ||||
| /** Sets the LEDGrid Program for the selected mode */ | /** Sets the LEDGrid Program for the selected mode */ | ||||
| void setLEDProgram (LEDGrid* grid) | |||||
| void setLEDProgram (Block& block) | |||||
| { | { | ||||
| canvasProgram = nullptr; | canvasProgram = nullptr; | ||||
| colourPaletteProgram = nullptr; | colourPaletteProgram = nullptr; | ||||
| @@ -358,10 +358,10 @@ private: | |||||
| if (currentMode == canvas) | if (currentMode == canvas) | ||||
| { | { | ||||
| // Create a new BitmapLEDProgram for the LEDGrid | // Create a new BitmapLEDProgram for the LEDGrid | ||||
| canvasProgram = new BitmapLEDProgram (*grid); | |||||
| canvasProgram = new BitmapLEDProgram (block); | |||||
| // Set the LEDGrid program | // Set the LEDGrid program | ||||
| grid->setProgram (canvasProgram); | |||||
| block.setProgram (canvasProgram); | |||||
| // Redraw any previously drawn LEDs | // Redraw any previously drawn LEDs | ||||
| redrawLEDs(); | redrawLEDs(); | ||||
| @@ -369,10 +369,10 @@ private: | |||||
| else if (currentMode == colourPalette) | else if (currentMode == colourPalette) | ||||
| { | { | ||||
| // Create a new DrumPadGridProgram for the LEDGrid | // Create a new DrumPadGridProgram for the LEDGrid | ||||
| colourPaletteProgram = new DrumPadGridProgram (*grid); | |||||
| colourPaletteProgram = new DrumPadGridProgram (block); | |||||
| // Set the LEDGrid program | // Set the LEDGrid program | ||||
| grid->setProgram (colourPaletteProgram); | |||||
| block.setProgram (colourPaletteProgram); | |||||
| // Setup the grid layout | // Setup the grid layout | ||||
| colourPaletteProgram->setGridFills (layout.numColumns, | colourPaletteProgram->setGridFills (layout.numColumns, | ||||
| @@ -28,7 +28,7 @@ public: | |||||
| // If this is a Lightpad then set the grid program to be blank | // If this is a Lightpad then set the grid program to be blank | ||||
| if (auto grid = block->getLEDGrid()) | if (auto grid = block->getLEDGrid()) | ||||
| grid->setProgram (new BitmapLEDProgram(*grid)); | |||||
| block->setProgram (new BitmapLEDProgram (*block)); | |||||
| // If this is a Lightpad then redraw it at 25Hz | // If this is a Lightpad then redraw it at 25Hz | ||||
| if (block->getType() == Block::lightPadBlock) | if (block->getType() == Block::lightPadBlock) | ||||
| @@ -145,7 +145,7 @@ public: | |||||
| scaleX = static_cast<float> (grid->getNumColumns() - 1) / activeBlock->getWidth(); | scaleX = static_cast<float> (grid->getNumColumns() - 1) / activeBlock->getWidth(); | ||||
| scaleY = static_cast<float> (grid->getNumRows() - 1) / activeBlock->getHeight(); | scaleY = static_cast<float> (grid->getNumRows() - 1) / activeBlock->getHeight(); | ||||
| setLEDProgram (grid); | |||||
| setLEDProgram (*activeBlock); | |||||
| } | } | ||||
| break; | break; | ||||
| @@ -227,7 +227,7 @@ private: | |||||
| currentMode = waveformSelectionMode; | currentMode = waveformSelectionMode; | ||||
| // Set the LEDGrid program to the new mode | // Set the LEDGrid program to the new mode | ||||
| setLEDProgram (activeBlock->getLEDGrid()); | |||||
| setLEDProgram (*activeBlock); | |||||
| } | } | ||||
| #if JUCE_IOS | #if JUCE_IOS | ||||
| @@ -259,15 +259,15 @@ private: | |||||
| } | } | ||||
| /** Sets the LEDGrid Program for the selected mode */ | /** Sets the LEDGrid Program for the selected mode */ | ||||
| void setLEDProgram (LEDGrid* grid) | |||||
| void setLEDProgram (Block& block) | |||||
| { | { | ||||
| if (currentMode == waveformSelectionMode) | if (currentMode == waveformSelectionMode) | ||||
| { | { | ||||
| // Create a new WaveshapeProgram for the LEDGrid | // Create a new WaveshapeProgram for the LEDGrid | ||||
| waveshapeProgram = new WaveshapeProgram (*grid); | |||||
| waveshapeProgram = new WaveshapeProgram (block); | |||||
| // Set the LEDGrid program | // Set the LEDGrid program | ||||
| grid->setProgram (waveshapeProgram); | |||||
| block.setProgram (waveshapeProgram); | |||||
| // Initialise the program | // Initialise the program | ||||
| waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode)); | waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode)); | ||||
| @@ -276,10 +276,16 @@ private: | |||||
| else if (currentMode == playMode) | else if (currentMode == playMode) | ||||
| { | { | ||||
| // Create a new DrumPadGridProgram for the LEDGrid | // Create a new DrumPadGridProgram for the LEDGrid | ||||
| gridProgram = new DrumPadGridProgram (*grid); | |||||
| gridProgram = new DrumPadGridProgram (block); | |||||
| // Set the LEDGrid program | // Set the LEDGrid program | ||||
| grid->setProgram (gridProgram); | |||||
| auto error = block.setProgram (gridProgram); | |||||
| if (error.failed()) | |||||
| { | |||||
| DBG (error.getErrorMessage()); | |||||
| jassertfalse; | |||||
| } | |||||
| // Setup the grid layout | // Setup the grid layout | ||||
| gridProgram->setGridFills (layout.numColumns, | gridProgram->setGridFills (layout.numColumns, | ||||
| @@ -3,15 +3,15 @@ | |||||
| /** | /** | ||||
| A Program to draw moving waveshapes onto the LEDGrid | A Program to draw moving waveshapes onto the LEDGrid | ||||
| */ | */ | ||||
| class WaveshapeProgram : public LEDGrid::Program | |||||
| class WaveshapeProgram : public Block::Program | |||||
| { | { | ||||
| public: | public: | ||||
| WaveshapeProgram (LEDGrid& lg) : Program (lg) {} | |||||
| WaveshapeProgram (Block& b) : Program (b) {} | |||||
| /** Sets the waveshape type to display on the grid */ | /** Sets the waveshape type to display on the grid */ | ||||
| void setWaveshapeType (uint8 type) | void setWaveshapeType (uint8 type) | ||||
| { | { | ||||
| ledGrid.setDataByte (0, type); | |||||
| block.setDataByte (0, type); | |||||
| } | } | ||||
| /** Generates the Y coordinates for 1.5 cycles of each of the four waveshapes and stores them | /** Generates the Y coordinates for 1.5 cycles of each of the four waveshapes and stores them | ||||
| @@ -74,22 +74,19 @@ public: | |||||
| // Store the values for each of the waveshapes at the correct offsets in the shared data heap | // Store the values for each of the waveshapes at the correct offsets in the shared data heap | ||||
| for (uint8 i = 0; i < 45; ++i) | for (uint8 i = 0; i < 45; ++i) | ||||
| { | { | ||||
| ledGrid.setDataByte (sineWaveOffset + i, sineWaveY[i]); | |||||
| ledGrid.setDataByte (squareWaveOffset + i, squareWaveY[i]); | |||||
| ledGrid.setDataByte (sawWaveOffset + i, sawWaveY[i]); | |||||
| ledGrid.setDataByte (triangleWaveOffset + i, triangleWaveY[i]); | |||||
| block.setDataByte (sineWaveOffset + i, sineWaveY[i]); | |||||
| block.setDataByte (squareWaveOffset + i, squareWaveY[i]); | |||||
| block.setDataByte (sawWaveOffset + i, sawWaveY[i]); | |||||
| block.setDataByte (triangleWaveOffset + i, triangleWaveY[i]); | |||||
| } | } | ||||
| } | } | ||||
| uint32 getHeapSize() override | |||||
| { | |||||
| return totalDataSize; | |||||
| } | |||||
| String getLittleFootProgram() override | String getLittleFootProgram() override | ||||
| { | { | ||||
| return R"littlefoot( | return R"littlefoot( | ||||
| #heapsize: 256 | |||||
| int yOffset; | int yOffset; | ||||
| int min (int a, int b) | int min (int a, int b) | ||||
| @@ -173,8 +170,6 @@ private: | |||||
| static constexpr uint32 sawWaveOffset = 91; // 1 byte * 45 | static constexpr uint32 sawWaveOffset = 91; // 1 byte * 45 | ||||
| static constexpr uint32 triangleWaveOffset = 136; // 1 byte * 45 | static constexpr uint32 triangleWaveOffset = 136; // 1 byte * 45 | ||||
| static constexpr uint32 totalDataSize = triangleWaveOffset + 45; | |||||
| //============================================================================== | //============================================================================== | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaveshapeProgram) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaveshapeProgram) | ||||
| }; | }; | ||||
| @@ -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::addDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.add (listener); } | ||||
| void Block::removeDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.remove (listener); } | void Block::removeDataInputPortListener (DataInputPortListener* listener) { dataInputPortListeners.remove (listener); } | ||||
| void Block::addProgramEventListener (ProgramEventListener* listener) { programEventListeners.add (listener); } | |||||
| void Block::removeProgramEventListener (ProgramEventListener* listener) { programEventListeners.remove (listener); } | |||||
| bool Block::ConnectionPort::operator== (const ConnectionPort& other) const noexcept { return edge == other.edge && index == other.index; } | bool Block::ConnectionPort::operator== (const ConnectionPort& other) const noexcept { return edge == other.edge && index == other.index; } | ||||
| bool Block::ConnectionPort::operator!= (const ConnectionPort& other) const noexcept { return ! operator== (other); } | bool Block::ConnectionPort::operator!= (const ConnectionPort& other) const noexcept { return ! operator== (other); } | ||||
| Block::Program::Program (Block& b) : block (b) {} | |||||
| Block::Program::~Program() {} | |||||
| //============================================================================== | //============================================================================== | ||||
| TouchSurface::TouchSurface (Block& b) : block (b) {} | TouchSurface::TouchSurface (Block& b) : block (b) {} | ||||
| TouchSurface::~TouchSurface() {} | TouchSurface::~TouchSurface() {} | ||||
| @@ -91,9 +98,6 @@ void ControlButton::removeListener (Listener* l) { listeners.remove (l); | |||||
| LEDGrid::LEDGrid (Block& b) : block (b) {} | LEDGrid::LEDGrid (Block& b) : block (b) {} | ||||
| LEDGrid::~LEDGrid() {} | LEDGrid::~LEDGrid() {} | ||||
| LEDGrid::Program::Program (LEDGrid& l) : ledGrid (l) {} | |||||
| LEDGrid::Program::~Program() {} | |||||
| LEDGrid::Renderer::~Renderer() {} | LEDGrid::Renderer::~Renderer() {} | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -184,6 +184,94 @@ public: | |||||
| /** Returns a list of the connectors that this device has. */ | /** Returns a list of the connectors that this device has. */ | ||||
| virtual juce::Array<ConnectionPort> getPorts() const = 0; | virtual juce::Array<ConnectionPort> getPorts() const = 0; | ||||
| //============================================================================== | |||||
| /** A program that can be loaded onto a block. */ | |||||
| struct Program | |||||
| { | |||||
| /** Creates a Program for the corresponding LEDGrid. */ | |||||
| Program (Block&); | |||||
| /** Destructor. */ | |||||
| virtual ~Program(); | |||||
| /** Returns the LittleFoot program to execute on the BLOCKS device. */ | |||||
| virtual juce::String getLittleFootProgram() = 0; | |||||
| Block& block; | |||||
| }; | |||||
| /** Sets the Program to run on this block. | |||||
| The supplied Program's lifetime will be managed by this class, so do not | |||||
| use the Program in other places in your code. | |||||
| */ | |||||
| virtual juce::Result setProgram (Program*) = 0; | |||||
| /** Returns a pointer to the currently loaded program. */ | |||||
| virtual Program* getProgram() const = 0; | |||||
| //============================================================================== | |||||
| /** A message that can be sent to the currently loaded program. */ | |||||
| struct ProgramEventMessage | |||||
| { | |||||
| int32 values[3]; | |||||
| }; | |||||
| /** Sends a message to the currently loaded program. | |||||
| To receive the message the program must provide a littlefoot function called | |||||
| handleMessage with the following form: | |||||
| @code | |||||
| void handleMessage (int param1, int param2) | |||||
| { | |||||
| // Do something with the two integer parameters that the app has sent... | |||||
| } | |||||
| @endcode | |||||
| */ | |||||
| virtual void sendProgramEvent (const ProgramEventMessage&) = 0; | |||||
| /** Interface for objects listening to custom program events. */ | |||||
| struct ProgramEventListener | |||||
| { | |||||
| virtual ~ProgramEventListener() {} | |||||
| /** Called whenever a message from a block is received. */ | |||||
| virtual void handleProgramEvent (Block& source, const ProgramEventMessage&) = 0; | |||||
| }; | |||||
| /** Adds a new listener for custom program events from the block. */ | |||||
| virtual void addProgramEventListener (ProgramEventListener*); | |||||
| /** Removes a listener for custom program events from the block. */ | |||||
| virtual void removeProgramEventListener (ProgramEventListener*); | |||||
| //============================================================================== | |||||
| /** Returns the size of the data block that setDataByte and other functions can write to. */ | |||||
| virtual uint32 getMemorySize() = 0; | |||||
| /** Sets a single byte on the littlefoot heap. */ | |||||
| virtual void setDataByte (size_t offset, uint8 value) = 0; | |||||
| /** Sets multiple bytes on the littlefoot heap. */ | |||||
| virtual void setDataBytes (size_t offset, const void* data, size_t num) = 0; | |||||
| /** Sets multiple bits on the littlefoot heap. */ | |||||
| virtual void setDataBits (uint32 startBit, uint32 numBits, uint32 value) = 0; | |||||
| /** Gets a byte from the littlefoot heap. */ | |||||
| virtual uint8 getDataByte (size_t offset) = 0; | |||||
| /** Sets the current program as the block's default state. */ | |||||
| virtual void saveProgramAsDefault() = 0; | |||||
| //============================================================================== | |||||
| /** Allows the user to provide a function that will receive log messages from the block. */ | |||||
| virtual void setLogger (std::function<void(const String&)> loggingCallback) = 0; | |||||
| /** Sends a firmware update packet to a block, and waits for a reply. Returns an error code. */ | |||||
| virtual bool sendFirmwareUpdatePacket (const uint8* data, uint8 size, | |||||
| std::function<void (uint8)> packetAckCallback) = 0; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Interface for objects listening to input data port. */ | /** Interface for objects listening to input data port. */ | ||||
| struct DataInputPortListener | struct DataInputPortListener | ||||
| @@ -194,10 +282,10 @@ public: | |||||
| virtual void handleIncomingDataPortMessage (Block& source, const void* messageData, size_t messageSize) = 0; | virtual void handleIncomingDataPortMessage (Block& source, const void* messageData, size_t messageSize) = 0; | ||||
| }; | }; | ||||
| /** Adds a new listener of data input port. */ | |||||
| /** Adds a new listener for the data input port. */ | |||||
| virtual void addDataInputPortListener (DataInputPortListener*); | virtual void addDataInputPortListener (DataInputPortListener*); | ||||
| /** Removes a listener of data input port. */ | |||||
| /** Removes a listener for the data input port. */ | |||||
| virtual void removeDataInputPortListener (DataInputPortListener*); | virtual void removeDataInputPortListener (DataInputPortListener*); | ||||
| /** Sends a message to the block. */ | /** Sends a message to the block. */ | ||||
| @@ -214,6 +302,7 @@ protected: | |||||
| Block (const juce::String& serialNumberToUse); | Block (const juce::String& serialNumberToUse); | ||||
| juce::ListenerList<DataInputPortListener> dataInputPortListeners; | juce::ListenerList<DataInputPortListener> dataInputPortListeners; | ||||
| juce::ListenerList<ProgramEventListener> programEventListeners; | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -73,95 +73,32 @@ public: | |||||
| /** Returns the number of rows in the LED grid. */ | /** Returns the number of rows in the LED grid. */ | ||||
| virtual int getNumRows() const = 0; | virtual int getNumRows() const = 0; | ||||
| /** A program that can be loaded onto an LEDGrid. | |||||
| This class facilitates the execution of a LittleFoot program on a BLOCKS | |||||
| device with an LEDGrid. | |||||
| */ | |||||
| struct Program | |||||
| { | |||||
| /** Creates a Program for the corresponding LEDGrid. */ | |||||
| Program (LEDGrid&); | |||||
| /** Destructor. */ | |||||
| virtual ~Program(); | |||||
| /** Returns the LittleFoot program to execute on the BLOCKS device. */ | |||||
| virtual juce::String getLittleFootProgram() = 0; | |||||
| /** Sets the size of the shared area of memory used to communicate with | |||||
| the host computer. | |||||
| */ | |||||
| virtual uint32 getHeapSize() = 0; | |||||
| LEDGrid& ledGrid; | |||||
| }; | |||||
| /** Sets the Program to run on this LEDGrid. | |||||
| The supplied Program's lifetime will be managed by this class, so do not | |||||
| use the Program in other places in your code. | |||||
| */ | |||||
| virtual juce::Result setProgram (Program*) = 0; | |||||
| /** Returns a pointer to the currently loaded program. */ | |||||
| virtual Program* getProgram() const = 0; | |||||
| /** A message that can be sent to the currently loaded program. */ | |||||
| struct ProgramEventMessage | |||||
| { | |||||
| int32 values[2]; | |||||
| }; | |||||
| /** Sends a message to the currently loaded program. | |||||
| To receive the message the program must provide a function called | |||||
| handleMessage with the following form: | |||||
| @code | |||||
| void handleMessage (int param1, int param2) | |||||
| { | |||||
| // Do something with the two integer parameters that the app has sent... | |||||
| } | |||||
| @endcode | |||||
| */ | |||||
| virtual void sendProgramEvent (const ProgramEventMessage&) = 0; | |||||
| /** Sets a single byte on the heap. */ | |||||
| virtual void setDataByte (size_t offset, uint8 value) = 0; | |||||
| /** Sets multiple bytes on the heap. */ | |||||
| virtual void setDataBytes (size_t offset, const void* data, size_t num) = 0; | |||||
| /** Sets multiple bits on the heap. */ | |||||
| virtual void setDataBits (uint32 startBit, uint32 numBits, uint32 value) = 0; | |||||
| /** Gets a byte from the heap. */ | |||||
| virtual uint8 getDataByte (size_t offset) = 0; | |||||
| /** Sets the current program as the block's default state. */ | |||||
| virtual void saveProgramAsDefault() = 0; | |||||
| //============================================================================== | //============================================================================== | ||||
| struct Renderer | |||||
| struct Renderer : public juce::ReferenceCountedObject | |||||
| { | { | ||||
| virtual ~Renderer(); | virtual ~Renderer(); | ||||
| virtual void renderLEDGrid (LEDGrid&) = 0; | virtual void renderLEDGrid (LEDGrid&) = 0; | ||||
| /** The Renderer class is reference-counted, so always use a Renderer::Ptr when | |||||
| you are keeping references to them. | |||||
| */ | |||||
| using Ptr = juce::ReferenceCountedObjectPtr<Renderer>; | |||||
| }; | }; | ||||
| /** Set the visualiser that will create visuals for this block (nullptr for none). | /** Set the visualiser that will create visuals for this block (nullptr for none). | ||||
| Note that the LEDGrid will NOT take ownership of this object, so the caller | Note that the LEDGrid will NOT take ownership of this object, so the caller | ||||
| must ensure that it doesn't get deleted while in use here. | must ensure that it doesn't get deleted while in use here. | ||||
| */ | */ | ||||
| void setRenderer (Renderer* newRenderer) noexcept { renderer = newRenderer; } | |||||
| void setRenderer (Renderer::Ptr newRenderer) noexcept { renderer = newRenderer; } | |||||
| /** Returns the visualiser currently attached to this block (nullptr for none). */ | /** Returns the visualiser currently attached to this block (nullptr for none). */ | ||||
| Renderer* getRenderer() const noexcept { return renderer; } | |||||
| Renderer::Ptr getRenderer() const noexcept { return renderer; } | |||||
| /** The device that this LEDGrid belongs to. */ | /** The device that this LEDGrid belongs to. */ | ||||
| Block& block; | Block& block; | ||||
| private: | private: | ||||
| Renderer* renderer = nullptr; | |||||
| Renderer::Ptr renderer; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGrid) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGrid) | ||||
| }; | }; | ||||
| @@ -39,9 +39,10 @@ using namespace juce; | |||||
| */ | */ | ||||
| struct Compiler | struct Compiler | ||||
| { | { | ||||
| Compiler() {} | |||||
| Compiler() = default; | |||||
| /** | |||||
| /** Gives the compiler a zero-terminated list of native function prototypes to | |||||
| use when parsing function calls. | |||||
| */ | */ | ||||
| void addNativeFunctions (const char* const* functionPrototypes) | void addNativeFunctions (const char* const* functionPrototypes) | ||||
| { | { | ||||
| @@ -49,7 +50,8 @@ struct Compiler | |||||
| nativeFunctions.add (NativeFunction (*functionPrototypes, nullptr)); | nativeFunctions.add (NativeFunction (*functionPrototypes, nullptr)); | ||||
| } | } | ||||
| /** | |||||
| /** Tells the compiler to use the list of native function prototypes from | |||||
| this littlefoot::Runner object. | |||||
| */ | */ | ||||
| template <typename RunnerType> | template <typename RunnerType> | ||||
| void addNativeFunctions (const RunnerType& runner) | void addNativeFunctions (const RunnerType& runner) | ||||
| @@ -58,20 +60,22 @@ struct Compiler | |||||
| nativeFunctions.add (runner.getNativeFunction (i)); | nativeFunctions.add (runner.getNativeFunction (i)); | ||||
| } | } | ||||
| /** | |||||
| /** Compiles a littlefoot program. | |||||
| If there's an error, this returns it, otherwise the compiled bytecode is | |||||
| placed in the compiledObjectCode member. | |||||
| */ | */ | ||||
| Result compile (const String& sourceCode, uint32 heapSizeBytesRequired) | |||||
| Result compile (const String& sourceCode, uint32 defaultHeapSize) | |||||
| { | { | ||||
| try | try | ||||
| { | { | ||||
| SyntaxTreeBuilder stb (sourceCode); | |||||
| SyntaxTreeBuilder stb (sourceCode, nativeFunctions, defaultHeapSize); | |||||
| stb.compile(); | stb.compile(); | ||||
| stb.simplify(); | stb.simplify(); | ||||
| compiledObjectCode.clear(); | compiledObjectCode.clear(); | ||||
| CodeGenerator codeGen (compiledObjectCode, nativeFunctions, stb.functions); | |||||
| codeGen.generateCode (stb.blockBeingParsed, (heapSizeBytesRequired + 3) & ~3u); | |||||
| CodeGenerator codeGen (compiledObjectCode, stb); | |||||
| codeGen.generateCode (stb.blockBeingParsed, stb.heapSizeRequired); | |||||
| return Result::ok(); | return Result::ok(); | ||||
| } | } | ||||
| catch (String error) | catch (String error) | ||||
| @@ -80,7 +84,14 @@ struct Compiler | |||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| /** After a successful compilation, this returns the finished Program. */ | |||||
| Program getCompiledProgram() const noexcept | |||||
| { | |||||
| return Program (compiledObjectCode.begin(), (uint32) compiledObjectCode.size()); | |||||
| } | |||||
| /** After a successful call to compile(), this contains the bytecode generated. | |||||
| A littlefoot::Program object can be created directly from this array. | |||||
| */ | */ | ||||
| Array<uint8> compiledObjectCode; | Array<uint8> compiledObjectCode; | ||||
| @@ -102,7 +113,7 @@ private: | |||||
| X(return_, "return") X(true_, "true") X(false_, "false") | X(return_, "return") X(true_, "true") X(false_, "false") | ||||
| #define LITTLEFOOT_OPERATORS(X) \ | #define LITTLEFOOT_OPERATORS(X) \ | ||||
| X(semicolon, ";") X(dot, ".") X(comma, ",") \ | |||||
| X(semicolon, ";") X(dot, ".") X(comma, ",") X(hash, "#") \ | |||||
| X(openParen, "(") X(closeParen, ")") X(openBrace, "{") X(closeBrace, "}") \ | X(openParen, "(") X(closeParen, ")") X(openBrace, "{") X(closeBrace, "}") \ | ||||
| X(openBracket, "[") X(closeBracket, "]") X(colon, ":") X(question, "?") \ | X(openBracket, "[") X(closeBracket, "]") X(colon, ":") X(question, "?") \ | ||||
| X(equals, "==") X(assign, "=") X(notEquals, "!=") X(logicalNot, "!") \ | X(equals, "==") X(assign, "=") X(notEquals, "!=") X(logicalNot, "!") \ | ||||
| @@ -354,7 +365,8 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| struct SyntaxTreeBuilder : private TokenIterator | struct SyntaxTreeBuilder : private TokenIterator | ||||
| { | { | ||||
| SyntaxTreeBuilder (const String& code) : TokenIterator (code) {} | |||||
| SyntaxTreeBuilder (const String& code, const Array<NativeFunction>& nativeFns, uint32 defaultHeapSize) | |||||
| : TokenIterator (code), nativeFunctions (nativeFns), heapSizeRequired (defaultHeapSize) {} | |||||
| void compile() | void compile() | ||||
| { | { | ||||
| @@ -362,6 +374,12 @@ private: | |||||
| while (currentType != Token::eof) | while (currentType != Token::eof) | ||||
| { | { | ||||
| if (matchIf (Token::hash)) | |||||
| { | |||||
| parseCompilerDirective(); | |||||
| continue; | |||||
| } | |||||
| if (! matchesAnyTypeOrVoid()) | if (! matchesAnyTypeOrVoid()) | ||||
| throwErrorExpecting ("a global variable or function"); | throwErrorExpecting ("a global variable or function"); | ||||
| @@ -377,7 +395,7 @@ private: | |||||
| if (type == Type::void_) | if (type == Type::void_) | ||||
| location.throwError ("A variable type cannot be 'void'"); | location.throwError ("A variable type cannot be 'void'"); | ||||
| int arraySize = matchIf (Token::openBracket) ? parseArraySize() : 0; | |||||
| int arraySize = matchIf (Token::openBracket) ? parseIntegerLiteral() : 0; | |||||
| if (arraySize > 0) | if (arraySize > 0) | ||||
| location.throwError ("Arrays not yet implemented!"); | location.throwError ("Arrays not yet implemented!"); | ||||
| @@ -399,9 +417,29 @@ private: | |||||
| f->block->simplify (*this); | f->block->simplify (*this); | ||||
| } | } | ||||
| Function* findFunction (FunctionID functionID) const noexcept | |||||
| { | |||||
| for (auto f : functions) | |||||
| if (f->functionID == functionID) | |||||
| return f; | |||||
| return nullptr; | |||||
| } | |||||
| NativeFunction* findNativeFunction (FunctionID functionID) const noexcept | |||||
| { | |||||
| for (auto& f : nativeFunctions) | |||||
| if (f.functionID == functionID) | |||||
| return &f; | |||||
| return nullptr; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| BlockPtr blockBeingParsed = nullptr; | BlockPtr blockBeingParsed = nullptr; | ||||
| Array<Function*> functions; | Array<Function*> functions; | ||||
| const Array<NativeFunction>& nativeFunctions; | |||||
| uint32 heapSizeRequired; | |||||
| template <typename Type, typename... Args> | template <typename Type, typename... Args> | ||||
| Type* allocate (Args... args) { auto o = new Type (args...); allAllocatedObjects.add (o); return o; } | Type* allocate (Args... args) { auto o = new Type (args...); allAllocatedObjects.add (o); return o; } | ||||
| @@ -410,10 +448,23 @@ private: | |||||
| OwnedArray<AllocatedObject> allAllocatedObjects; | OwnedArray<AllocatedObject> allAllocatedObjects; | ||||
| //============================================================================== | //============================================================================== | ||||
| void parseCompilerDirective() | |||||
| { | |||||
| auto name = parseIdentifier(); | |||||
| if (name == "heapsize") | |||||
| { | |||||
| match (Token::colon); | |||||
| heapSizeRequired = (((uint32) parseIntegerLiteral()) + 3) & ~3u; | |||||
| return; | |||||
| } | |||||
| location.throwError ("Unknown compiler directive"); | |||||
| } | |||||
| void parseFunctionDeclaration (Type returnType, const String& name) | void parseFunctionDeclaration (Type returnType, const String& name) | ||||
| { | { | ||||
| auto f = allocate<Function>(); | auto f = allocate<Function>(); | ||||
| functions.add (f); | |||||
| while (matchesAnyType()) | while (matchesAnyType()) | ||||
| { | { | ||||
| @@ -431,6 +482,12 @@ private: | |||||
| match (Token::closeParen); | match (Token::closeParen); | ||||
| f->functionID = createFunctionID (name, returnType, f->getArgumentTypes()); | f->functionID = createFunctionID (name, returnType, f->getArgumentTypes()); | ||||
| if (findFunction (f->functionID) != nullptr || findNativeFunction (f->functionID) != nullptr) | |||||
| location.throwError ("Duplicate function declaration"); | |||||
| functions.add (f); | |||||
| f->block = parseBlock (true); | f->block = parseBlock (true); | ||||
| f->returnType = returnType; | f->returnType = returnType; | ||||
| @@ -443,12 +500,11 @@ private: | |||||
| } | } | ||||
| } | } | ||||
| int parseArraySize() | |||||
| int parseIntegerLiteral() | |||||
| { | { | ||||
| auto e = parseExpression(); | auto e = parseExpression(); | ||||
| e->simplify (*this); | |||||
| if (auto literal = dynamic_cast<LiteralValue*> (e)) | |||||
| if (auto literal = dynamic_cast<LiteralValue*> (e->simplify (*this))) | |||||
| { | { | ||||
| if (literal->value.isInt() || literal->value.isInt64()) | if (literal->value.isInt() || literal->value.isInt64()) | ||||
| { | { | ||||
| @@ -459,7 +515,7 @@ private: | |||||
| } | } | ||||
| } | } | ||||
| location.throwError ("An array size must be a constant integer"); | |||||
| location.throwError ("Expected an integer constant"); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| @@ -618,8 +674,8 @@ private: | |||||
| { | { | ||||
| if (currentType == Token::identifier) return parseSuffixes (allocate<Identifier> (location, blockBeingParsed, parseIdentifier())); | if (currentType == Token::identifier) return parseSuffixes (allocate<Identifier> (location, blockBeingParsed, parseIdentifier())); | ||||
| if (matchIf (Token::openParen)) return parseSuffixes (matchCloseParen (parseExpression())); | if (matchIf (Token::openParen)) return parseSuffixes (matchCloseParen (parseExpression())); | ||||
| if (matchIf (Token::true_)) return parseSuffixes (allocate<LiteralValue> (location, blockBeingParsed, (int) 1)); | |||||
| if (matchIf (Token::false_)) return parseSuffixes (allocate<LiteralValue> (location, blockBeingParsed, (int) 0)); | |||||
| if (matchIf (Token::true_)) return parseSuffixes (allocate<LiteralValue> (location, blockBeingParsed, true)); | |||||
| if (matchIf (Token::false_)) return parseSuffixes (allocate<LiteralValue> (location, blockBeingParsed, false)); | |||||
| if (currentType == Token::literal) | if (currentType == Token::literal) | ||||
| { | { | ||||
| @@ -835,12 +891,12 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| struct CodeGenerator | struct CodeGenerator | ||||
| { | { | ||||
| CodeGenerator (Array<uint8>& output, const Array<NativeFunction>& nativeFns, const Array<Function*>& fns) | |||||
| : outputCode (output), nativeFunctions (nativeFns), functions (fns) {} | |||||
| CodeGenerator (Array<uint8>& output, const SyntaxTreeBuilder& stb) | |||||
| : outputCode (output), syntaxTree (stb) {} | |||||
| void generateCode (BlockPtr outerBlock, uint32 heapSizeBytesRequired) | void generateCode (BlockPtr outerBlock, uint32 heapSizeBytesRequired) | ||||
| { | { | ||||
| for (auto f : functions) | |||||
| for (auto f : syntaxTree.functions) | |||||
| { | { | ||||
| f->address = createMarker(); | f->address = createMarker(); | ||||
| f->unwindAddress = createMarker(); | f->unwindAddress = createMarker(); | ||||
| @@ -848,16 +904,19 @@ private: | |||||
| emit ((int16) 0); // checksum | emit ((int16) 0); // checksum | ||||
| emit ((int16) 0); // size | emit ((int16) 0); // size | ||||
| emit ((int16) functions.size()); | |||||
| emit ((int16) syntaxTree.functions.size()); | |||||
| emit ((int16) outerBlock->variables.size()); | emit ((int16) outerBlock->variables.size()); | ||||
| emit ((int16) heapSizeBytesRequired); | emit ((int16) heapSizeBytesRequired); | ||||
| for (auto f : functions) | |||||
| for (auto f : syntaxTree.functions) | |||||
| emit (f->functionID, f->address); | emit (f->functionID, f->address); | ||||
| for (auto f : functions) | |||||
| auto codeStart = outputCode.size(); | |||||
| for (auto f : syntaxTree.functions) | |||||
| f->emit (*this); | f->emit (*this); | ||||
| removeJumpsToNextInstruction (codeStart); | |||||
| resolveMarkers(); | resolveMarkers(); | ||||
| Program::writeInt16 (outputCode.begin() + 2, (int16) outputCode.size()); | Program::writeInt16 (outputCode.begin() + 2, (int16) outputCode.size()); | ||||
| @@ -868,39 +927,92 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| Array<uint8>& outputCode; | Array<uint8>& outputCode; | ||||
| const Array<NativeFunction>& nativeFunctions; | |||||
| const Array<Function*>& functions; | |||||
| const SyntaxTreeBuilder& syntaxTree; | |||||
| struct Marker { int index = 0; }; | struct Marker { int index = 0; }; | ||||
| struct MarkerAndAddress { int markerIndex, address; }; | |||||
| struct MarkerAndAddress { Marker marker; int address; }; | |||||
| int nextMarker = 0; | int nextMarker = 0; | ||||
| Array<MarkerAndAddress> markersToResolve, resolvedMarkers; | Array<MarkerAndAddress> markersToResolve, resolvedMarkers; | ||||
| Marker createMarker() noexcept { Marker m; m.index = ++nextMarker; return m; } | Marker createMarker() noexcept { Marker m; m.index = ++nextMarker; return m; } | ||||
| void attachMarker (Marker m) { resolvedMarkers.add ({ m.index, outputCode.size() }); } | |||||
| void attachMarker (Marker m) { resolvedMarkers.add ({ m, outputCode.size() }); } | |||||
| int getResolvedMarkerAddress (int markerIndex) const | |||||
| int getResolvedMarkerAddress (Marker marker) const | |||||
| { | { | ||||
| for (auto m : resolvedMarkers) | for (auto m : resolvedMarkers) | ||||
| if (m.markerIndex == markerIndex) | |||||
| if (m.marker.index == marker.index) | |||||
| return m.address; | return m.address; | ||||
| jassertfalse; | jassertfalse; | ||||
| return 0; | return 0; | ||||
| } | } | ||||
| Marker getMarkerAtAddress (int address) const noexcept | |||||
| { | |||||
| for (auto m : markersToResolve) | |||||
| if (m.address == address) | |||||
| return m.marker; | |||||
| jassertfalse; | |||||
| return {}; | |||||
| } | |||||
| void resolveMarkers() | void resolveMarkers() | ||||
| { | { | ||||
| for (auto m : markersToResolve) | for (auto m : markersToResolve) | ||||
| Program::writeInt16 (outputCode.begin() + m.address, (int16) getResolvedMarkerAddress (m.markerIndex)); | |||||
| Program::writeInt16 (outputCode.begin() + m.address, (int16) getResolvedMarkerAddress (m.marker)); | |||||
| } | |||||
| void removeCode (int address, int size) | |||||
| { | |||||
| outputCode.removeRange (address, size); | |||||
| for (int i = markersToResolve.size(); --i >= 0;) | |||||
| { | |||||
| auto& m = markersToResolve.getReference (i); | |||||
| if (m.address >= address + size) | |||||
| m.address -= size; | |||||
| else if (m.address >= address) | |||||
| markersToResolve.remove (i); | |||||
| } | |||||
| for (auto& m : resolvedMarkers) | |||||
| if (m.address >= address + size) | |||||
| m.address -= size; | |||||
| } | |||||
| void removeJumpsToNextInstruction (int address) | |||||
| { | |||||
| while (address < outputCode.size()) | |||||
| { | |||||
| auto op = (OpCode) outputCode.getUnchecked (address); | |||||
| auto opSize = 1 + Program::getNumExtraBytesForOpcode (op); | |||||
| if (op == OpCode::jump) | |||||
| { | |||||
| auto marker = getMarkerAtAddress (address + 1); | |||||
| if (marker.index != 0) | |||||
| { | |||||
| if (getResolvedMarkerAddress (marker) == address + opSize) | |||||
| { | |||||
| removeCode (address, opSize); | |||||
| continue; | |||||
| } | |||||
| } | |||||
| } | |||||
| address += opSize; | |||||
| } | |||||
| } | } | ||||
| Marker breakTarget, continueTarget; | Marker breakTarget, continueTarget; | ||||
| //============================================================================== | //============================================================================== | ||||
| void emit (OpCode op) { emit ((int8) op); } | void emit (OpCode op) { emit ((int8) op); } | ||||
| void emit (Marker m) { markersToResolve.add ({ m.index, outputCode.size() }); emit ((int16) 0); } | |||||
| void emit (Marker m) { markersToResolve.add ({ m, outputCode.size() }); emit ((int16) 0); } | |||||
| void emit (int8 value) { outputCode.add ((uint8) value); } | void emit (int8 value) { outputCode.add ((uint8) value); } | ||||
| void emit (int16 value) { uint8 d[2]; Program::writeInt16 (d, value); outputCode.insertArray (-1, d, (int) sizeof (d)); } | void emit (int16 value) { uint8 d[2]; Program::writeInt16 (d, value); outputCode.insertArray (-1, d, (int) sizeof (d)); } | ||||
| void emit (int32 value) { uint8 d[4]; Program::writeInt32 (d, value); outputCode.insertArray (-1, d, (int) sizeof (d)); } | void emit (int32 value) { uint8 d[4]; Program::writeInt32 (d, value); outputCode.insertArray (-1, d, (int) sizeof (d)); } | ||||
| @@ -955,7 +1067,7 @@ private: | |||||
| index += stackDepth; | index += stackDepth; | ||||
| if (index == 0) | if (index == 0) | ||||
| emit ((OpCode) ((int) OpCode::dup)); | |||||
| emit (OpCode::dup); | |||||
| else if (index < 8) | else if (index < 8) | ||||
| emit ((OpCode) ((int) OpCode::dupOffset_01 + index - 1)); | emit ((OpCode) ((int) OpCode::dupOffset_01 + index - 1)); | ||||
| else if (index >= 128) | else if (index >= 128) | ||||
| @@ -966,25 +1078,6 @@ private: | |||||
| emitCast (sourceType, requiredType, location); | emitCast (sourceType, requiredType, location); | ||||
| } | } | ||||
| //============================================================================== | |||||
| Function* findFunction (FunctionID functionID) const noexcept | |||||
| { | |||||
| for (auto f : functions) | |||||
| if (f->functionID == functionID) | |||||
| return f; | |||||
| return nullptr; | |||||
| } | |||||
| NativeFunction* findNativeFunction (FunctionID functionID) const noexcept | |||||
| { | |||||
| for (auto& f : nativeFunctions) | |||||
| if (f.functionID == functionID) | |||||
| return &f; | |||||
| return nullptr; | |||||
| } | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -1385,9 +1478,7 @@ private: | |||||
| else if (fn->returnType != Type::void_) | else if (fn->returnType != Type::void_) | ||||
| location.throwError ("Cannot return a value from a void function"); | location.throwError ("Cannot return a value from a void function"); | ||||
| if (parentBlock->statements.getLast() != this) | |||||
| cg.emit (OpCode::jump, fn->unwindAddress); | |||||
| cg.emit (OpCode::jump, fn->unwindAddress); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -1798,7 +1889,7 @@ private: | |||||
| auto functionID = getFunctionID (cg); | auto functionID = getFunctionID (cg); | ||||
| if (auto fn = cg.findFunction (functionID)) | |||||
| if (auto fn = cg.syntaxTree.findFunction (functionID)) | |||||
| { | { | ||||
| emitArgs (cg, fn->getArgumentTypes(), stackDepth); | emitArgs (cg, fn->getArgumentTypes(), stackDepth); | ||||
| cg.emit (OpCode::call, fn->address); | cg.emit (OpCode::call, fn->address); | ||||
| @@ -1806,7 +1897,7 @@ private: | |||||
| return; | return; | ||||
| } | } | ||||
| if (auto nativeFn = cg.findNativeFunction (functionID)) | |||||
| if (auto nativeFn = cg.syntaxTree.findNativeFunction (functionID)) | |||||
| { | { | ||||
| emitArgs (cg, getArgTypesFromFunctionName (nativeFn->nameAndArguments), stackDepth); | emitArgs (cg, getArgTypesFromFunctionName (nativeFn->nameAndArguments), stackDepth); | ||||
| cg.emit (OpCode::callNative, nativeFn->functionID); | cg.emit (OpCode::callNative, nativeFn->functionID); | ||||
| @@ -1836,10 +1927,10 @@ private: | |||||
| auto functionID = getFunctionID (cg); | auto functionID = getFunctionID (cg); | ||||
| if (auto fn = cg.findFunction (functionID)) | |||||
| if (auto fn = cg.syntaxTree.findFunction (functionID)) | |||||
| return fn->returnType; | return fn->returnType; | ||||
| if (auto nativeFn = cg.findNativeFunction (functionID)) | |||||
| if (auto nativeFn = cg.syntaxTree.findNativeFunction (functionID)) | |||||
| return nativeFn->returnType; | return nativeFn->returnType; | ||||
| if (auto b = findBuiltInFunction (functionID)) | if (auto b = findBuiltInFunction (functionID)) | ||||
| @@ -52,16 +52,35 @@ struct LittleFootRemoteHeap | |||||
| void clear() noexcept | void clear() noexcept | ||||
| { | { | ||||
| zeromem (targetData, sizeof (targetData)); | zeromem (targetData, sizeof (targetData)); | ||||
| invalidateData(); | |||||
| } | |||||
| void resetDeviceStateToUnknown() | |||||
| { | |||||
| invalidateData(); | |||||
| messagesSent.clear(); | |||||
| resetDataRangeToUnknown (0, ImplementationClass::maxBlockSize); | |||||
| } | |||||
| void resetDataRangeToUnknown (size_t offset, size_t size) noexcept | |||||
| { | |||||
| auto* latestState = getLatestExpectedDataState(); | |||||
| for (size_t i = 0; i < size; ++i) | |||||
| latestState[offset + i] = unknownByte; | |||||
| } | } | ||||
| void setByte (size_t offset, uint8 value) noexcept | void setByte (size_t offset, uint8 value) noexcept | ||||
| { | { | ||||
| if (offset < blockSize) | if (offset < blockSize) | ||||
| { | { | ||||
| if (targetData [offset] != value) | |||||
| if (targetData[offset] != value) | |||||
| { | { | ||||
| targetData [offset] = value; | |||||
| invalidateData(); | |||||
| targetData[offset] = value; | |||||
| needsSyncing = true; | |||||
| if (programStateKnown && offset < programSize) | |||||
| programStateKnown = false; | |||||
| } | } | ||||
| } | } | ||||
| else | else | ||||
| @@ -100,37 +119,31 @@ struct LittleFootRemoteHeap | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| void invalidateData() | |||||
| void invalidateData() noexcept | |||||
| { | { | ||||
| dataHasChanged = true; | |||||
| needsSyncing = true; | |||||
| programStateKnown = false; | programStateKnown = false; | ||||
| } | } | ||||
| void sendChanges (ImplementationClass& bi) | |||||
| bool isFullySynced() const noexcept | |||||
| { | { | ||||
| if (dataHasChanged && messagesSent.isEmpty()) | |||||
| return ! needsSyncing; | |||||
| } | |||||
| void sendChanges (ImplementationClass& bi, bool forceSend) | |||||
| { | |||||
| if ((needsSyncing && messagesSent.isEmpty()) || forceSend) | |||||
| { | { | ||||
| for (;;) | |||||
| for (int maxChanges = 30; --maxChanges >= 0;) | |||||
| { | { | ||||
| uint16 data[ImplementationClass::maxBlockSize]; | uint16 data[ImplementationClass::maxBlockSize]; | ||||
| uint32 packetIndex; | |||||
| if (messagesSent.isEmpty()) | |||||
| { | |||||
| for (uint32 i = 0; i < blockSize; ++i) | |||||
| data[i] = deviceState[i]; | |||||
| packetIndex = lastPacketIndexReceived; | |||||
| } | |||||
| else | |||||
| { | |||||
| auto& lastPacket = messagesSent.getReference (messagesSent.size() - 1); | |||||
| auto* latestState = getLatestExpectedDataState(); | |||||
| for (uint32 i = 0; i < blockSize; ++i) | |||||
| data[i] = lastPacket.resultDataState[i]; | |||||
| for (uint32 i = 0; i < blockSize; ++i) | |||||
| data[i] = latestState[i]; | |||||
| packetIndex = lastPacket.packetIndex; | |||||
| } | |||||
| uint32 packetIndex = messagesSent.isEmpty() ? lastPacketIndexReceived | |||||
| : messagesSent.getLast()->packetIndex; | |||||
| packetIndex = (packetIndex + 1) & ImplementationClass::maxPacketCounter; | packetIndex = (packetIndex + 1) & ImplementationClass::maxPacketCounter; | ||||
| @@ -141,14 +154,14 @@ struct LittleFootRemoteHeap | |||||
| } | } | ||||
| } | } | ||||
| for (auto& m : messagesSent) | |||||
| for (auto* m : messagesSent) | |||||
| { | { | ||||
| if (m.dispatchTime >= Time::getCurrentTime() - RelativeTime::milliseconds (250)) | |||||
| if (m->dispatchTime >= Time::getCurrentTime() - RelativeTime::milliseconds (250)) | |||||
| break; | break; | ||||
| m.dispatchTime = Time::getCurrentTime(); | |||||
| bi.sendMessageToDevice (m.packet); | |||||
| //DBG ("Sending packet " << (int) m.packetIndex << " - " << m.packet.size() << " bytes, device " << bi.getDeviceIndex()); | |||||
| m->dispatchTime = Time::getCurrentTime(); | |||||
| bi.sendMessageToDevice (m->packet); | |||||
| //DBG ("Sending packet " << (int) m->packetIndex << " - " << m->packet.size() << " bytes, device " << bi.getDeviceIndex()); | |||||
| if (getTotalSizeOfMessagesSent() > 200) | if (getTotalSizeOfMessagesSent() > 200) | ||||
| break; | break; | ||||
| @@ -157,7 +170,7 @@ struct LittleFootRemoteHeap | |||||
| void handleACKFromDevice (ImplementationClass& bi, uint32 packetIndex) noexcept | void handleACKFromDevice (ImplementationClass& bi, uint32 packetIndex) noexcept | ||||
| { | { | ||||
| //DBG ("ACK " << (int) packetIndex); | |||||
| //DBG ("ACK " << (int) packetIndex << " device " << (int) bi.getDeviceIndex()); | |||||
| if (lastPacketIndexReceived != packetIndex) | if (lastPacketIndexReceived != packetIndex) | ||||
| { | { | ||||
| @@ -165,7 +178,7 @@ struct LittleFootRemoteHeap | |||||
| for (int i = messagesSent.size(); --i >= 0;) | for (int i = messagesSent.size(); --i >= 0;) | ||||
| { | { | ||||
| auto& m = messagesSent.getReference(i); | |||||
| auto& m = *messagesSent.getUnchecked(i); | |||||
| if (m.packetIndex == packetIndex) | if (m.packetIndex == packetIndex) | ||||
| { | { | ||||
| @@ -174,10 +187,10 @@ struct LittleFootRemoteHeap | |||||
| messagesSent.removeRange (0, i + 1); | messagesSent.removeRange (0, i + 1); | ||||
| dumpStatus(); | dumpStatus(); | ||||
| sendChanges (bi); | |||||
| sendChanges (bi, false); | |||||
| if (messagesSent.isEmpty()) | if (messagesSent.isEmpty()) | ||||
| dataHasChanged = false; | |||||
| needsSyncing = false; | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -191,17 +204,16 @@ struct LittleFootRemoteHeap | |||||
| { | { | ||||
| if (! programStateKnown) | if (! programStateKnown) | ||||
| { | { | ||||
| programStateKnown = true; | |||||
| uint8 deviceMemory[ImplementationClass::maxBlockSize]; | uint8 deviceMemory[ImplementationClass::maxBlockSize]; | ||||
| bool anyUnknowns = false; | |||||
| for (size_t i = 0; i < blockSize; ++i) | for (size_t i = 0; i < blockSize; ++i) | ||||
| { | |||||
| anyUnknowns = (deviceState[i] > 255); | |||||
| deviceMemory[i] = (uint8) deviceState[i]; | deviceMemory[i] = (uint8) deviceState[i]; | ||||
| } | |||||
| programLoaded = ! anyUnknowns && littlefoot::Program (deviceMemory, (uint32) blockSize).checksumMatches(); | |||||
| programStateKnown = true; | |||||
| littlefoot::Program prog (deviceMemory, (uint32) blockSize); | |||||
| programLoaded = prog.checksumMatches(); | |||||
| programSize = prog.getProgramSize(); | |||||
| } | } | ||||
| return programLoaded; | return programLoaded; | ||||
| @@ -214,15 +226,13 @@ struct LittleFootRemoteHeap | |||||
| private: | private: | ||||
| uint16 deviceState[ImplementationClass::maxBlockSize]; | uint16 deviceState[ImplementationClass::maxBlockSize]; | ||||
| uint8 targetData[ImplementationClass::maxBlockSize] = { 0 }; | uint8 targetData[ImplementationClass::maxBlockSize] = { 0 }; | ||||
| bool dataHasChanged = true, programStateKnown = true, programLoaded = false; | |||||
| uint32 programSize = 0; | |||||
| bool needsSyncing = true, programStateKnown = true, programLoaded = false; | |||||
| void resetDeviceStateToUnknown() | |||||
| uint16* getLatestExpectedDataState() noexcept | |||||
| { | { | ||||
| invalidateData(); | |||||
| messagesSent.clear(); | |||||
| for (uint32 i = 0; i < ImplementationClass::maxBlockSize; ++i) | |||||
| deviceState[i] = unknownByte; | |||||
| return messagesSent.isEmpty() ? deviceState | |||||
| : messagesSent.getLast()->resultDataState; | |||||
| } | } | ||||
| struct ChangeMessage | struct ChangeMessage | ||||
| @@ -233,16 +243,16 @@ private: | |||||
| uint16 resultDataState[ImplementationClass::maxBlockSize]; | uint16 resultDataState[ImplementationClass::maxBlockSize]; | ||||
| }; | }; | ||||
| Array<ChangeMessage> messagesSent; | |||||
| OwnedArray<ChangeMessage> messagesSent; | |||||
| uint32 lastPacketIndexReceived = 0; | uint32 lastPacketIndexReceived = 0; | ||||
| int getTotalSizeOfMessagesSent() const noexcept | int getTotalSizeOfMessagesSent() const noexcept | ||||
| { | { | ||||
| int total = 0; | int total = 0; | ||||
| for (auto& m : messagesSent) | |||||
| if (m.dispatchTime != Time()) | |||||
| total += m.packet.size(); | |||||
| for (auto* m : messagesSent) | |||||
| if (m->dispatchTime != Time()) | |||||
| total += m->packet.size(); | |||||
| return total; | return total; | ||||
| } | } | ||||
| @@ -280,6 +290,8 @@ private: | |||||
| Diff (uint16* current, const uint8* target, size_t blockSizeToUse) | Diff (uint16* current, const uint8* target, size_t blockSizeToUse) | ||||
| : newData (target), blockSize (blockSizeToUse) | : newData (target), blockSize (blockSizeToUse) | ||||
| { | { | ||||
| ranges.ensureStorageAllocated ((int) blockSize); | |||||
| for (int i = 0; i < (int) blockSize; ++i) | for (int i = 0; i < (int) blockSize; ++i) | ||||
| ranges.add ({ i, 1, newData[i] == current[i], false }); | ranges.add ({ i, 1, newData[i] == current[i], false }); | ||||
| @@ -290,7 +302,7 @@ private: | |||||
| bool createChangeMessage (const ImplementationClass& bi, | bool createChangeMessage (const ImplementationClass& bi, | ||||
| const uint16* currentState, | const uint16* currentState, | ||||
| Array<ChangeMessage>& messagesCreated, | |||||
| OwnedArray<ChangeMessage>& messagesCreated, | |||||
| uint32 nextPacketIndex) | uint32 nextPacketIndex) | ||||
| { | { | ||||
| if (ranges.isEmpty()) | if (ranges.isEmpty()) | ||||
| @@ -301,8 +313,7 @@ private: | |||||
| if (deviceIndex < 0) | if (deviceIndex < 0) | ||||
| return false; | return false; | ||||
| messagesCreated.add ({}); | |||||
| auto& message = messagesCreated.getReference (messagesCreated.size() - 1); | |||||
| auto& message = *messagesCreated.add (new ChangeMessage()); | |||||
| message.packetIndex = nextPacketIndex; | message.packetIndex = nextPacketIndex; | ||||
| @@ -366,27 +377,27 @@ private: | |||||
| void coalesceUniformRegions() | void coalesceUniformRegions() | ||||
| { | { | ||||
| for (int i = 0; i < ranges.size() - 1; ++i) | |||||
| for (int i = ranges.size(); --i > 0;) | |||||
| { | { | ||||
| auto& r1 = ranges.getReference (i); | |||||
| auto r2 = ranges.getReference (i + 1); | |||||
| auto& r1 = ranges.getReference (i - 1); | |||||
| auto r2 = ranges.getReference (i); | |||||
| if (r1.isSkipped == r2.isSkipped | if (r1.isSkipped == r2.isSkipped | ||||
| && (r1.isSkipped || newData[r1.index] == newData[r2.index])) | && (r1.isSkipped || newData[r1.index] == newData[r2.index])) | ||||
| { | { | ||||
| r1.length += r2.length; | r1.length += r2.length; | ||||
| ranges.remove (i + 1); | |||||
| i = jmax (0, i - 2); | |||||
| ranges.remove (i); | |||||
| i = jmin (ranges.size() - 1, i + 1); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| void coalesceSequences() | void coalesceSequences() | ||||
| { | { | ||||
| for (int i = 0; i < ranges.size() - 1; ++i) | |||||
| for (int i = ranges.size(); --i > 0;) | |||||
| { | { | ||||
| auto& r1 = ranges.getReference (i); | |||||
| auto r2 = ranges.getReference (i + 1); | |||||
| auto& r1 = ranges.getReference (i - 1); | |||||
| auto r2 = ranges.getReference (i); | |||||
| if (! (r1.isSkipped || r2.isSkipped) | if (! (r1.isSkipped || r2.isSkipped) | ||||
| && (r1.isMixed || r1.length == 1) | && (r1.isMixed || r1.length == 1) | ||||
| @@ -396,8 +407,8 @@ private: | |||||
| { | { | ||||
| r1.length += r2.length; | r1.length += r2.length; | ||||
| r1.isMixed = true; | r1.isMixed = true; | ||||
| ranges.remove (i + 1); | |||||
| i = jmax (0, i - 2); | |||||
| ranges.remove (i); | |||||
| i = jmin (ranges.size() - 1, i + 1); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -44,11 +44,11 @@ namespace littlefoot | |||||
| #define LITTLEFOOT_DUMP_PROGRAM 0 | #define LITTLEFOOT_DUMP_PROGRAM 0 | ||||
| #endif | #endif | ||||
| using int8 = char; | |||||
| using int8 = signed char; | |||||
| using uint8 = unsigned char; | using uint8 = unsigned char; | ||||
| using int16 = short; | |||||
| using int16 = signed short; | |||||
| using uint16 = unsigned short; | using uint16 = unsigned short; | ||||
| using int32 = int; | |||||
| using int32 = signed int; | |||||
| using uint32 = unsigned int; | using uint32 = unsigned int; | ||||
| using FunctionID = int16; | using FunctionID = int16; | ||||
| @@ -180,8 +180,8 @@ struct NativeFunction | |||||
| jassert (nameAndArgTypes[slash + 1] != 0); // The slash must be followed by a return type character | jassert (nameAndArgTypes[slash + 1] != 0); // The slash must be followed by a return type character | ||||
| jassert (juce::String (nameAndArgTypes).substring (0, slash).containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")); | jassert (juce::String (nameAndArgTypes).substring (0, slash).containsOnly ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")); | ||||
| jassert (! juce::String ("0123456789").containsChar (nameAndArgTypes[0])); | jassert (! juce::String ("0123456789").containsChar (nameAndArgTypes[0])); | ||||
| jassert (juce::String (nameAndArgTypes).substring (slash + 1).containsOnly ("vif")); | |||||
| jassert (juce::String (nameAndArgTypes).substring (slash + 2).containsOnly ("if")); // arguments must only be of these types | |||||
| jassert (juce::String (nameAndArgTypes).substring (slash + 1).containsOnly ("vifb")); | |||||
| jassert (juce::String (nameAndArgTypes).substring (slash + 2).containsOnly ("ifb")); // arguments must only be of these types | |||||
| for (; nameAndArgTypes[i] != 0; ++i) | for (; nameAndArgTypes[i] != 0; ++i) | ||||
| if (i != slash + 1) | if (i != slash + 1) | ||||
| @@ -247,13 +247,6 @@ struct Program | |||||
| return calculateChecksum() == getStoredChecksum(); | return calculateChecksum() == getStoredChecksum(); | ||||
| } | } | ||||
| uint32 getProgramSize() const noexcept | |||||
| { | |||||
| auto size = (uint16) readInt16 (programStart + 2); | |||||
| return size < programHeaderSize ? programHeaderSize | |||||
| : (size > maxProgramSize ? maxProgramSize : size); | |||||
| } | |||||
| uint32 getNumFunctions() const noexcept | uint32 getNumFunctions() const noexcept | ||||
| { | { | ||||
| return (uint16) readInt16 (programStart + 4); | return (uint16) readInt16 (programStart + 4); | ||||
| @@ -286,10 +279,11 @@ struct Program | |||||
| : getFunctionStartAddress (functionIndex); | : getFunctionStartAddress (functionIndex); | ||||
| } | } | ||||
| /** Returns the number of global variables the program uses */ | |||||
| uint16 getNumGlobals() const noexcept | |||||
| uint32 getProgramSize() const noexcept | |||||
| { | { | ||||
| return (uint16) readInt16 (programStart + 6); | |||||
| auto size = (uint16) readInt16 (programStart + 2); | |||||
| return size < programHeaderSize ? programHeaderSize | |||||
| : (size > maxProgramSize ? maxProgramSize : size); | |||||
| } | } | ||||
| /** Returns the number of bytes of heap space the program needs */ | /** Returns the number of bytes of heap space the program needs */ | ||||
| @@ -298,6 +292,17 @@ struct Program | |||||
| return (uint16) readInt16 (programStart + 8); | return (uint16) readInt16 (programStart + 8); | ||||
| } | } | ||||
| /** Returns the number of global variables the program uses */ | |||||
| uint16 getNumGlobals() const noexcept | |||||
| { | |||||
| return (uint16) readInt16 (programStart + 6); | |||||
| } | |||||
| uint32 getTotalSpaceNeeded() const noexcept | |||||
| { | |||||
| return getProgramSize() + getHeapSizeBytes(); | |||||
| } | |||||
| #if JUCE_DEBUG | #if JUCE_DEBUG | ||||
| //============================================================================== | //============================================================================== | ||||
| /** Prints the assembly code for a given function. */ | /** Prints the assembly code for a given function. */ | ||||
| @@ -307,7 +312,7 @@ struct Program | |||||
| << " (" << juce::String::toHexString (getFunctionID (functionIndex)) << ")" << juce::newLine; | << " (" << juce::String::toHexString (getFunctionID (functionIndex)) << ")" << juce::newLine; | ||||
| if (auto codeStart = getFunctionStartAddress (functionIndex)) | if (auto codeStart = getFunctionStartAddress (functionIndex)) | ||||
| if (auto codeEnd = getFunctionEndAddress (functionIndex)) | |||||
| if (auto codeEnd = getFunctionEndAddress (functionIndex)) | |||||
| for (auto prog = codeStart; prog < codeEnd;) | for (auto prog = codeStart; prog < codeEnd;) | ||||
| out << getOpDisassembly (prog) << juce::newLine; | out << getOpDisassembly (prog) << juce::newLine; | ||||
| } | } | ||||
| @@ -339,19 +344,41 @@ struct Program | |||||
| /** Calls dumpFunctionDisassembly() for all functions. */ | /** Calls dumpFunctionDisassembly() for all functions. */ | ||||
| void dumpAllFunctions (juce::OutputStream& out) const | void dumpAllFunctions (juce::OutputStream& out) const | ||||
| { | { | ||||
| DBG ("Program size: " << (int) getProgramSize() << " bytes"); | |||||
| if (programStart != nullptr) | |||||
| { | |||||
| DBG ("Program size: " << (int) getProgramSize() << " bytes"); | |||||
| for (uint32 i = 0; i < getNumFunctions(); ++i) | |||||
| dumpFunctionDisassembly (out, i); | |||||
| for (uint32 i = 0; i < getNumFunctions(); ++i) | |||||
| dumpFunctionDisassembly (out, i); | |||||
| } | |||||
| } | } | ||||
| #endif | #endif | ||||
| /** For a given op code, this returns the number of program bytes that follow it. */ | |||||
| static uint8 getNumExtraBytesForOpcode (OpCode op) noexcept | |||||
| { | |||||
| switch (op) | |||||
| { | |||||
| #define LITTLEFOOT_OP(name) case OpCode::name: return 0; | |||||
| #define LITTLEFOOT_OP_INT8(name) case OpCode::name: return 1; | |||||
| #define LITTLEFOOT_OP_INT16(name) case OpCode::name: return 2; | |||||
| #define LITTLEFOOT_OP_INT32(name) case OpCode::name: return 4; | |||||
| LITTLEFOOT_OPCODES (LITTLEFOOT_OP, LITTLEFOOT_OP_INT8, LITTLEFOOT_OP_INT16, LITTLEFOOT_OP_INT32) | |||||
| #undef LITTLEFOOT_OP | |||||
| #undef LITTLEFOOT_OP_INT8 | |||||
| #undef LITTLEFOOT_OP_INT16 | |||||
| #undef LITTLEFOOT_OP_INT32 | |||||
| default: jassertfalse; return 0; | |||||
| } | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| static float intToFloat (int32 value) noexcept { float v; copyFloatMem (&v, &value); return v; } | static float intToFloat (int32 value) noexcept { float v; copyFloatMem (&v, &value); return v; } | ||||
| static int32 floatToInt (float value) noexcept { int32 v; copyFloatMem (&v, &value); return v; } | static int32 floatToInt (float value) noexcept { int32 v; copyFloatMem (&v, &value); return v; } | ||||
| static int16 readInt16 (const uint8* d) noexcept { return (int16) (d[0] + (((uint16) d[1]) << 8)); } | |||||
| static int32 readInt32 (const uint8* d) noexcept { return (int32) ((uint32) (uint16) readInt16 (d) + (((uint32) (uint16) readInt16 (d + 2)) << 16)); } | |||||
| static int16 readInt16 (const uint8* d) noexcept { return (int16) (d[0] | (((uint16) d[1]) << 8)); } | |||||
| static int32 readInt32 (const uint8* d) noexcept { return (int32) (d[0] | (((uint32) d[1]) << 8) | (((uint32) d[2]) << 16) | (((uint32) d[3]) << 24)); } | |||||
| static void writeInt16 (uint8* d, int16 v) noexcept { d[0] = (uint8) v; d[1] = (uint8) (v >> 8); } | static void writeInt16 (uint8* d, int16 v) noexcept { d[0] = (uint8) v; d[1] = (uint8) (v >> 8); } | ||||
| static void writeInt32 (uint8* d, int32 v) noexcept { d[0] = (uint8) v; d[1] = (uint8) (v >> 8); d[2] = (uint8) (v >> 16); d[3] = (uint8) (v >> 24); } | static void writeInt32 (uint8* d, int32 v) noexcept { d[0] = (uint8) v; d[1] = (uint8) (v >> 8); d[2] = (uint8) (v >> 16); d[3] = (uint8) (v >> 24); } | ||||
| @@ -416,6 +443,16 @@ struct Runner | |||||
| allMemory[i] = 0; | allMemory[i] = 0; | ||||
| } | } | ||||
| /** Clears all the non-program data. */ | |||||
| void clearHeapAndGlobals() noexcept | |||||
| { | |||||
| auto* start = getProgramAndDataStart() + program.getProgramSize(); | |||||
| auto* end = allMemory + sizeof (allMemory); | |||||
| for (auto m = start; m < end; ++m) | |||||
| *m = 0; | |||||
| } | |||||
| /** Return codes from a function call */ | /** Return codes from a function call */ | ||||
| enum class ErrorCode : uint8 | enum class ErrorCode : uint8 | ||||
| { | { | ||||
| @@ -429,6 +466,23 @@ struct Runner | |||||
| unknownFunction | unknownFunction | ||||
| }; | }; | ||||
| /** Returns a text description for an error code */ | |||||
| static const char* getErrorDescription (ErrorCode e) noexcept | |||||
| { | |||||
| switch (e) | |||||
| { | |||||
| case ErrorCode::ok: return "OK"; | |||||
| case ErrorCode::executionTimedOut: return "Timed-out"; | |||||
| case ErrorCode::unknownInstruction: return "Illegal instruction"; | |||||
| case ErrorCode::stackOverflow: return "Stack overflow"; | |||||
| case ErrorCode::stackUnderflow: return "Stack underflow"; | |||||
| case ErrorCode::illegalAddress: return "Illegal access"; | |||||
| case ErrorCode::divisionByZero: return "Division by zero"; | |||||
| case ErrorCode::unknownFunction: return "Unknown function"; | |||||
| default: return "Unknown error"; | |||||
| } | |||||
| } | |||||
| /** Calls one of the functions in the program, by its textual signature. */ | /** Calls one of the functions in the program, by its textual signature. */ | ||||
| ErrorCode callFunction (const char* functionSignature) noexcept | ErrorCode callFunction (const char* functionSignature) noexcept | ||||
| { | { | ||||
| @@ -467,15 +521,17 @@ struct Runner | |||||
| /** */ | /** */ | ||||
| bool isProgramValid() const noexcept { return heapStart != nullptr; } | bool isProgramValid() const noexcept { return heapStart != nullptr; } | ||||
| /** */ | |||||
| /** Sets a byte of data. */ | |||||
| void setDataByte (uint32 index, uint8 value) noexcept | void setDataByte (uint32 index, uint8 value) noexcept | ||||
| { | { | ||||
| if (index < programAndHeapSpace) | if (index < programAndHeapSpace) | ||||
| { | { | ||||
| if (index < program.getProgramSize()) | |||||
| auto& dest = getProgramAndDataStart()[index]; | |||||
| if (index < program.getProgramSize() && dest != value) | |||||
| heapStart = nullptr; // force a re-initialise of the memory layout when the program changes | heapStart = nullptr; // force a re-initialise of the memory layout when the program changes | ||||
| getProgramAndDataStart()[index] = value; | |||||
| dest = value; | |||||
| } | } | ||||
| } | } | ||||
| @@ -561,7 +617,7 @@ struct Runner | |||||
| if (prog.getFunctionID (i) == function) | if (prog.getFunctionID (i) == function) | ||||
| { | { | ||||
| programCounter = prog.getFunctionStartAddress (i); | programCounter = prog.getFunctionStartAddress (i); | ||||
| functionEnd = prog.getFunctionEndAddress (i); | |||||
| programEnd = r.getProgramHeapStart(); | |||||
| tos = *--stack = 0; | tos = *--stack = 0; | ||||
| return; | return; | ||||
| } | } | ||||
| @@ -599,7 +655,7 @@ struct Runner | |||||
| for (;;) | for (;;) | ||||
| { | { | ||||
| if (programCounter >= functionEnd) | |||||
| if (programCounter >= programEnd) | |||||
| return error; | return error; | ||||
| if ((++opsPerformed & 63) == 0 && hasTimedOut()) | if ((++opsPerformed & 63) == 0 && hasTimedOut()) | ||||
| @@ -628,7 +684,7 @@ struct Runner | |||||
| //============================================================================== | //============================================================================== | ||||
| Runner* runner; | Runner* runner; | ||||
| const uint8* programCounter; | const uint8* programCounter; | ||||
| const uint8* functionEnd; | |||||
| const uint8* programEnd; | |||||
| const uint8* programBase; | const uint8* programBase; | ||||
| uint8* heapStart; | uint8* heapStart; | ||||
| int32* stack; | int32* stack; | ||||
| @@ -646,7 +702,7 @@ struct Runner | |||||
| int16 readProgram16() noexcept { auto v = Program::readInt16 (programCounter); programCounter += sizeof (int16); return v; } | int16 readProgram16() noexcept { auto v = Program::readInt16 (programCounter); programCounter += sizeof (int16); return v; } | ||||
| int32 readProgram32() noexcept { auto v = Program::readInt32 (programCounter); programCounter += sizeof (int32); return v; } | int32 readProgram32() noexcept { auto v = Program::readInt32 (programCounter); programCounter += sizeof (int32); return v; } | ||||
| void setError (ErrorCode e) noexcept { error = e; programCounter = functionEnd; jassert (error == ErrorCode::ok); } | |||||
| void setError (ErrorCode e) noexcept { error = e; programCounter = programEnd; jassert (error == ErrorCode::ok); } | |||||
| bool checkStackUnderflow() noexcept { if (stack <= stackEnd) return true; setError (ErrorCode::stackUnderflow); return false; } | bool checkStackUnderflow() noexcept { if (stack <= stackEnd) return true; setError (ErrorCode::stackUnderflow); return false; } | ||||
| bool flushTopToStack() noexcept { if (--stack < stackStart) { setError (ErrorCode::stackOverflow); return false; } *stack = tos; return true; } | bool flushTopToStack() noexcept { if (--stack < stackStart) { setError (ErrorCode::stackOverflow); return false; } *stack = tos; return true; } | ||||
| @@ -52,6 +52,7 @@ enum class MessageFromDevice | |||||
| { | { | ||||
| deviceTopology = 0x01, | deviceTopology = 0x01, | ||||
| packetACK = 0x02, | packetACK = 0x02, | ||||
| firmwareUpdateACK = 0x03, | |||||
| touchStart = 0x10, | touchStart = 0x10, | ||||
| touchMove = 0x11, | touchMove = 0x11, | ||||
| @@ -62,7 +63,11 @@ enum class MessageFromDevice | |||||
| touchEndWithVelocity = 0x15, | touchEndWithVelocity = 0x15, | ||||
| controlButtonDown = 0x20, | controlButtonDown = 0x20, | ||||
| controlButtonUp = 0x21 | |||||
| controlButtonUp = 0x21, | |||||
| programEventMessage = 0x28, | |||||
| logMessage = 0x30 | |||||
| }; | }; | ||||
| /** Messages that the host may send to a device. */ | /** Messages that the host may send to a device. */ | ||||
| @@ -70,7 +75,8 @@ enum class MessageFromHost | |||||
| { | { | ||||
| deviceCommandMessage = 0x01, | deviceCommandMessage = 0x01, | ||||
| sharedDataChange = 0x02, | sharedDataChange = 0x02, | ||||
| programEventMessage = 0x03 | |||||
| programEventMessage = 0x03, | |||||
| firmwareUpdatePacket = 0x04 | |||||
| }; | }; | ||||
| @@ -224,15 +230,18 @@ using ByteCountMany = IntegerWithBitSize<8>; | |||||
| using ByteValue = IntegerWithBitSize<8>; | using ByteValue = IntegerWithBitSize<8>; | ||||
| using ByteSequenceContinues = IntegerWithBitSize<1>; | using ByteSequenceContinues = IntegerWithBitSize<1>; | ||||
| static constexpr uint32 numProgramMessageInts = 2; | |||||
| using FirmwareUpdateACKCode = IntegerWithBitSize<7>; | |||||
| using FirmwareUpdatePacketSize = IntegerWithBitSize<7>; | |||||
| static constexpr uint32 numProgramMessageInts = 3; | |||||
| static constexpr uint32 apiModeHostPingTimeoutMs = 5000; | static constexpr uint32 apiModeHostPingTimeoutMs = 5000; | ||||
| static constexpr uint32 padBlockProgramAndHeapSize = 3200; | |||||
| static constexpr uint32 padBlockProgramAndHeapSize = 7200; | |||||
| static constexpr uint32 padBlockStackSize = 800; | static constexpr uint32 padBlockStackSize = 800; | ||||
| static constexpr uint32 controlBlockProgramAndHeapSize = 1500; | |||||
| static constexpr uint32 controlBlockStackSize = 500; | |||||
| static constexpr uint32 controlBlockProgramAndHeapSize = 3000; | |||||
| static constexpr uint32 controlBlockStackSize = 800; | |||||
| //============================================================================== | //============================================================================== | ||||
| @@ -251,6 +260,8 @@ enum BitSizes | |||||
| programEventMessage = MessageType::bits + 32 * numProgramMessageInts, | programEventMessage = MessageType::bits + 32 * numProgramMessageInts, | ||||
| packetACK = MessageType::bits + PacketCounter::bits, | packetACK = MessageType::bits + PacketCounter::bits, | ||||
| firmwareUpdateACK = MessageType::bits + FirmwareUpdateACKCode::bits, | |||||
| controlButtonMessage = typeDeviceAndTime + ControlButtonID::bits, | controlButtonMessage = typeDeviceAndTime + ControlButtonID::bits, | ||||
| }; | }; | ||||
| @@ -258,18 +269,62 @@ enum BitSizes | |||||
| // These are the littlefoot functions provided for use in BLOCKS programs | // These are the littlefoot functions provided for use in BLOCKS programs | ||||
| static constexpr const char* ledProgramLittleFootFunctions[] = | static constexpr const char* ledProgramLittleFootFunctions[] = | ||||
| { | { | ||||
| "min/iii", | |||||
| "min/fff", | |||||
| "max/iii", | |||||
| "max/fff", | |||||
| "clamp/iiii", | |||||
| "clamp/ffff", | |||||
| "abs/ii", | |||||
| "abs/ff", | |||||
| "map/ffffff", | |||||
| "map/ffff", | |||||
| "mod/iii", | |||||
| "getRandomFloat/f", | |||||
| "getRandomInt/ii", | |||||
| "getMillisecondCounter/i", | |||||
| "getFirmwareVersion/i", | |||||
| "log/vi", | |||||
| "logHex/vi", | |||||
| "getTimeInCurrentFunctionCall/i", | |||||
| "getBatteryLevel/f", | |||||
| "isBatteryCharging/b", | |||||
| "isMasterBlock/b", | |||||
| "isConnectedToHost/b", | |||||
| "setStatusOverlayActive/vb", | |||||
| "getNumBlocksInTopology/i", | |||||
| "getBlockIDForIndex/ii", | |||||
| "getBlockIDOnPort/ii", | |||||
| "getPortToMaster/i", | |||||
| "getBlockTypeForID/ii", | |||||
| "sendMessageToBlock/viiii", | |||||
| "sendMessageToHost/viii", | |||||
| "makeARGB/iiiii", | "makeARGB/iiiii", | ||||
| "blendARGB/iii", | "blendARGB/iii", | ||||
| "setLED/viii", | |||||
| "blendLED/viii", | |||||
| "fillPixel/viii", | |||||
| "blendPixel/viii", | |||||
| "fillRect/viiiii", | "fillRect/viiiii", | ||||
| "blendRect/viiiii", | "blendRect/viiiii", | ||||
| "sendMIDI/vi", | |||||
| "sendMIDI/vii", | |||||
| "sendMIDI/viii", | |||||
| "blendGradientRect/viiiiiiii", | |||||
| "addPressurePoint/vifff", | "addPressurePoint/vifff", | ||||
| "drawPressureMap/v", | "drawPressureMap/v", | ||||
| "fadePressureMap/v", | "fadePressureMap/v", | ||||
| "enableDebug/viii", | |||||
| "drawNumber/viiii", | |||||
| "clearDisplay/v", | |||||
| "clearDisplay/vi", | |||||
| "sendMIDI/vi", | |||||
| "sendMIDI/vii", | |||||
| "sendMIDI/viii", | |||||
| "sendNoteOn/viii", | |||||
| "sendNoteOff/viii", | |||||
| "sendAftertouch/viii", | |||||
| "sendCC/viii", | |||||
| "sendPitchBend/vii", | |||||
| "sendChannelPressure/vii", | |||||
| "setChannelRange/vbii", | |||||
| "assignChannel/ii", | |||||
| "deassignChannel/vii", | |||||
| "getControlChannel/i", | |||||
| "useMPEDuplicateFilter/vb", | |||||
| nullptr | nullptr | ||||
| }; | }; | ||||
| @@ -218,6 +218,20 @@ struct HostPacketBuilder | |||||
| return true; | return true; | ||||
| } | } | ||||
| bool addFirmwareUpdatePacket (const uint8* packetData, uint8 size) | |||||
| { | |||||
| if (! data.hasCapacity (MessageType::bits + FirmwareUpdatePacketSize::bits + 7 * size)) | |||||
| return false; | |||||
| writeMessageType (MessageFromHost::firmwareUpdatePacket); | |||||
| data << FirmwareUpdatePacketSize (size); | |||||
| for (uint8 i = 0; i < size; ++i) | |||||
| data << IntegerWithBitSize<7> ((uint32) packetData[i]); | |||||
| return true; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| private: | private: | ||||
| Packed7BitArrayBuilder<maxPacketBytes> data; | Packed7BitArrayBuilder<maxPacketBytes> data; | ||||
| @@ -78,7 +78,10 @@ struct HostPacketDecoder | |||||
| case MessageFromDevice::touchEndWithVelocity: return handleTouchWithVelocity (handler, reader, deviceIndex, packetTimestamp, false, true); | case MessageFromDevice::touchEndWithVelocity: return handleTouchWithVelocity (handler, reader, deviceIndex, packetTimestamp, false, true); | ||||
| case MessageFromDevice::controlButtonDown: return handleButtonDownOrUp (handler, reader, deviceIndex, packetTimestamp, true); | case MessageFromDevice::controlButtonDown: return handleButtonDownOrUp (handler, reader, deviceIndex, packetTimestamp, true); | ||||
| case MessageFromDevice::controlButtonUp: return handleButtonDownOrUp (handler, reader, deviceIndex, packetTimestamp, false); | case MessageFromDevice::controlButtonUp: return handleButtonDownOrUp (handler, reader, deviceIndex, packetTimestamp, false); | ||||
| case MessageFromDevice::programEventMessage: return handleCustomMessage (handler, reader, deviceIndex, packetTimestamp); | |||||
| case MessageFromDevice::packetACK: return handlePacketACK (handler, reader, deviceIndex); | case MessageFromDevice::packetACK: return handlePacketACK (handler, reader, deviceIndex); | ||||
| case MessageFromDevice::firmwareUpdateACK: return handleFirmwareUpdateACK (handler, reader, deviceIndex); | |||||
| case MessageFromDevice::logMessage: return handleLogMessage (handler, reader, deviceIndex); | |||||
| default: | default: | ||||
| jassertfalse; // got an invalid message type, could be a corrupt packet, or a | jassertfalse; // got an invalid message type, could be a corrupt packet, or a | ||||
| @@ -217,6 +220,24 @@ struct HostPacketDecoder | |||||
| return true; | return true; | ||||
| } | } | ||||
| static bool handleCustomMessage (Handler& handler, Packed7BitArrayReader& reader, | |||||
| TopologyIndex deviceIndex, PacketTimestamp packetTimestamp) | |||||
| { | |||||
| if (reader.getRemainingBits() < BitSizes::programEventMessage - MessageType::bits) | |||||
| { | |||||
| jassertfalse; // not enough data available for this message type! | |||||
| return false; | |||||
| } | |||||
| int32 data[numProgramMessageInts] = {}; | |||||
| for (uint32 i = 0; i < numProgramMessageInts; ++i) | |||||
| data[i] = (int32) reader.read<IntegerWithBitSize<32>>().get(); | |||||
| handler.handleCustomMessage (deviceIndex, packetTimestamp.get(), data); | |||||
| return true; | |||||
| } | |||||
| static bool handlePacketACK (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex) | static bool handlePacketACK (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex) | ||||
| { | { | ||||
| if (reader.getRemainingBits() < BitSizes::packetACK - MessageType::bits) | if (reader.getRemainingBits() < BitSizes::packetACK - MessageType::bits) | ||||
| @@ -228,4 +249,30 @@ struct HostPacketDecoder | |||||
| handler.handlePacketACK (deviceIndex, reader.read<PacketCounter>()); | handler.handlePacketACK (deviceIndex, reader.read<PacketCounter>()); | ||||
| return true; | return true; | ||||
| } | } | ||||
| static bool handleFirmwareUpdateACK (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex) | |||||
| { | |||||
| if (reader.getRemainingBits() < FirmwareUpdateACKCode::bits) | |||||
| { | |||||
| jassertfalse; // not enough data available for this message type! | |||||
| return false; | |||||
| } | |||||
| handler.handleFirmwareUpdateACK (deviceIndex, reader.read<FirmwareUpdateACKCode>()); | |||||
| return true; | |||||
| } | |||||
| static bool handleLogMessage (Handler& handler, Packed7BitArrayReader& reader, TopologyIndex deviceIndex) | |||||
| { | |||||
| String message; | |||||
| while (reader.getRemainingBits() >= 7) | |||||
| { | |||||
| uint32 c = reader.read<IntegerWithBitSize<7>>(); | |||||
| message << (char) c; | |||||
| } | |||||
| handler.handleLogMessage (deviceIndex, message); | |||||
| return true; | |||||
| } | |||||
| }; | }; | ||||
| @@ -134,6 +134,21 @@ struct PhysicalTopologySource::Internal | |||||
| if (midiInput != nullptr) | if (midiInput != nullptr) | ||||
| midiInput->stop(); | midiInput->stop(); | ||||
| if (interprocessLock != nullptr) | |||||
| interprocessLock->exit(); | |||||
| } | |||||
| bool lockAgainstOtherProcesses (const String& midiInName, const String& midiOutName) | |||||
| { | |||||
| interprocessLock.reset (new juce::InterProcessLock ("blocks_sdk_" | |||||
| + File::createLegalFileName (midiInName) | |||||
| + "_" + File::createLegalFileName (midiOutName))); | |||||
| if (interprocessLock->enter (500)) | |||||
| return true; | |||||
| interprocessLock = nullptr; | |||||
| return false; | |||||
| } | } | ||||
| struct Listener | struct Listener | ||||
| @@ -189,6 +204,7 @@ struct PhysicalTopologySource::Internal | |||||
| private: | private: | ||||
| juce::ListenerList<Listener> listeners; | juce::ListenerList<Listener> listeners; | ||||
| std::unique_ptr<juce::InterProcessLock> interprocessLock; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceConnection) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MIDIDeviceConnection) | ||||
| }; | }; | ||||
| @@ -215,13 +231,16 @@ struct PhysicalTopologySource::Internal | |||||
| { | { | ||||
| std::unique_ptr<MIDIDeviceConnection> dev (new MIDIDeviceConnection()); | std::unique_ptr<MIDIDeviceConnection> dev (new MIDIDeviceConnection()); | ||||
| dev->midiInput.reset (juce::MidiInput::openDevice (pair.inputIndex, dev.get())); | |||||
| dev->midiOutput.reset (juce::MidiOutput::openDevice (pair.outputIndex)); | |||||
| if (dev->midiInput != nullptr) | |||||
| if (dev->lockAgainstOtherProcesses (pair.inputName, pair.outputName)) | |||||
| { | { | ||||
| dev->midiInput->start(); | |||||
| return dev.release(); | |||||
| dev->midiInput.reset (juce::MidiInput::openDevice (pair.inputIndex, dev.get())); | |||||
| dev->midiOutput.reset (juce::MidiOutput::openDevice (pair.outputIndex)); | |||||
| if (dev->midiInput != nullptr) | |||||
| { | |||||
| dev->midiInput->start(); | |||||
| return dev.release(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -486,6 +505,12 @@ struct PhysicalTopologySource::Internal | |||||
| detector.handleButtonChange (deviceID, deviceTimestampToHost (timestamp), buttonID.get(), isDown); | detector.handleButtonChange (deviceID, deviceTimestampToHost (timestamp), buttonID.get(), isDown); | ||||
| } | } | ||||
| void handleCustomMessage (BlocksProtocol::TopologyIndex deviceIndex, uint32 timestamp, const int32* data) | |||||
| { | |||||
| if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||||
| detector.handleCustomMessage (deviceID, deviceTimestampToHost (timestamp), data); | |||||
| } | |||||
| void handleTouchChange (BlocksProtocol::TopologyIndex deviceIndex, | void handleTouchChange (BlocksProtocol::TopologyIndex deviceIndex, | ||||
| uint32 timestamp, | uint32 timestamp, | ||||
| BlocksProtocol::TouchIndex touchIndex, | BlocksProtocol::TouchIndex touchIndex, | ||||
| @@ -532,6 +557,18 @@ struct PhysicalTopologySource::Internal | |||||
| detector.handleSharedDataACK (deviceID, counter); | detector.handleSharedDataACK (deviceID, counter); | ||||
| } | } | ||||
| void handleFirmwareUpdateACK (BlocksProtocol::TopologyIndex deviceIndex, BlocksProtocol::FirmwareUpdateACKCode resultCode) | |||||
| { | |||||
| if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||||
| detector.handleFirmwareUpdateACK (deviceID, (uint8) resultCode.get()); | |||||
| } | |||||
| void handleLogMessage (BlocksProtocol::TopologyIndex deviceIndex, const String& message) | |||||
| { | |||||
| if (auto deviceID = getDeviceIDFromMessageIndex (deviceIndex)) | |||||
| detector.handleLogMessage (deviceID, message); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| template <typename PacketBuilder> | template <typename PacketBuilder> | ||||
| bool sendMessageToDevice (const PacketBuilder& builder) const | bool sendMessageToDevice (const PacketBuilder& builder) const | ||||
| @@ -827,6 +864,24 @@ struct PhysicalTopologySource::Internal | |||||
| bi->handleSharedDataACK (packetCounter); | bi->handleSharedDataACK (packetCounter); | ||||
| } | } | ||||
| void handleFirmwareUpdateACK (Block::UID deviceID, uint8 resultCode) | |||||
| { | |||||
| for (auto&& b : currentTopology.blocks) | |||||
| if (b->uid == deviceID) | |||||
| if (auto bi = BlockImplementation::getFrom (*b)) | |||||
| bi->handleFirmwareUpdateACK (resultCode); | |||||
| } | |||||
| void handleLogMessage (Block::UID deviceID, const String& message) const | |||||
| { | |||||
| JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | |||||
| for (auto&& b : currentTopology.blocks) | |||||
| if (b->uid == deviceID) | |||||
| if (auto bi = BlockImplementation::getFrom (*b)) | |||||
| bi->handleLogMessage (message); | |||||
| } | |||||
| void handleButtonChange (Block::UID deviceID, Block::Timestamp timestamp, uint32 buttonIndex, bool isDown) const | void handleButtonChange (Block::UID deviceID, Block::Timestamp timestamp, uint32 buttonIndex, bool isDown) const | ||||
| { | { | ||||
| JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | ||||
| @@ -872,6 +927,14 @@ struct PhysicalTopologySource::Internal | |||||
| surface->cancelAllActiveTouches(); | surface->cancelAllActiveTouches(); | ||||
| } | } | ||||
| void handleCustomMessage (Block::UID deviceID, Block::Timestamp timestamp, const int32* data) | |||||
| { | |||||
| for (auto&& b : currentTopology.blocks) | |||||
| if (b->uid == deviceID) | |||||
| if (auto bi = BlockImplementation::getFrom (*b)) | |||||
| bi->handleCustomMessage (timestamp, data); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| int getIndexFromDeviceID (Block::UID deviceID) const noexcept | int getIndexFromDeviceID (Block::UID deviceID) const noexcept | ||||
| { | { | ||||
| @@ -992,7 +1055,8 @@ struct PhysicalTopologySource::Internal | |||||
| //============================================================================== | //============================================================================== | ||||
| struct BlockImplementation : public Block, | struct BlockImplementation : public Block, | ||||
| private MIDIDeviceConnection::Listener | |||||
| private MIDIDeviceConnection::Listener, | |||||
| private Timer | |||||
| { | { | ||||
| BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, Detector& detectorToUse, bool master) | BlockImplementation (const BlocksProtocol::BlockSerialNumber& serial, Detector& detectorToUse, bool master) | ||||
| : Block (juce::String ((const char*) serial.serial, sizeof (serial.serial))), modelData (serial), | : Block (juce::String ((const char*) serial.serial, sizeof (serial.serial))), modelData (serial), | ||||
| @@ -1108,6 +1172,16 @@ struct PhysicalTopologySource::Internal | |||||
| return sendMessageToDevice (p); | return sendMessageToDevice (p); | ||||
| } | } | ||||
| void handleCustomMessage (Block::Timestamp, const int32* data) | |||||
| { | |||||
| ProgramEventMessage m; | |||||
| for (uint32 i = 0; i < BlocksProtocol::numProgramMessageInts; ++i) | |||||
| m.values[i] = data[i]; | |||||
| programEventListeners.call (&Block::ProgramEventListener::handleProgramEvent, *this, m); | |||||
| } | |||||
| static BlockImplementation* getFrom (Block& b) noexcept | static BlockImplementation* getFrom (Block& b) noexcept | ||||
| { | { | ||||
| if (auto bi = dynamic_cast<BlockImplementation*> (&b)) | if (auto bi = dynamic_cast<BlockImplementation*> (&b)) | ||||
| @@ -1127,42 +1201,84 @@ struct PhysicalTopologySource::Internal | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void clearProgramAndData() | |||||
| { | |||||
| programSize = 0; | |||||
| remoteHeap.clear(); | |||||
| } | |||||
| std::function<void(const String&)> logger; | |||||
| void setProgram (const void* compiledCode, size_t codeSize) | |||||
| void setLogger (std::function<void(const String&)> newLogger) override | |||||
| { | { | ||||
| clearProgramAndData(); | |||||
| setDataBytes (0, compiledCode, codeSize); | |||||
| programSize = (uint32) codeSize; | |||||
| logger = newLogger; | |||||
| } | } | ||||
| void setDataByte (size_t offset, uint8 value) | |||||
| void handleLogMessage (const String& message) const | |||||
| { | { | ||||
| remoteHeap.setByte (programSize + offset, value); | |||||
| if (logger != nullptr) | |||||
| logger (message); | |||||
| } | } | ||||
| void setDataBytes (size_t offset, const void* newData, size_t num) | |||||
| //============================================================================== | |||||
| juce::Result setProgram (Program* newProgram) override | |||||
| { | { | ||||
| remoteHeap.setBytes (programSize + offset, static_cast<const uint8*> (newData), num); | |||||
| } | |||||
| if (newProgram == nullptr || program.get() != newProgram) | |||||
| { | |||||
| { | |||||
| std::unique_ptr<Program> p (newProgram); | |||||
| void setDataBits (uint32 startBit, uint32 numBits, uint32 value) | |||||
| { | |||||
| remoteHeap.setBits (programSize * 8 + startBit, numBits, value); | |||||
| } | |||||
| if (program != nullptr | |||||
| && newProgram != nullptr | |||||
| && program->getLittleFootProgram() == newProgram->getLittleFootProgram()) | |||||
| return juce::Result::ok(); | |||||
| uint8 getDataByte (size_t offset) | |||||
| { | |||||
| return remoteHeap.getByte (programSize + offset); | |||||
| stopTimer(); | |||||
| std::swap (program, p); | |||||
| } | |||||
| stopTimer(); | |||||
| programSize = 0; | |||||
| if (program != nullptr) | |||||
| { | |||||
| littlefoot::Compiler compiler; | |||||
| compiler.addNativeFunctions (PhysicalTopologySource::getStandardLittleFootFunctions()); | |||||
| auto err = compiler.compile (program->getLittleFootProgram(), 512); | |||||
| if (err.failed()) | |||||
| return err; | |||||
| DBG ("Compiled littlefoot program, space needed: " | |||||
| << (int) compiler.getCompiledProgram().getTotalSpaceNeeded() << " bytes"); | |||||
| if (compiler.getCompiledProgram().getTotalSpaceNeeded() > getMemorySize()) | |||||
| return Result::fail ("Program too large!"); | |||||
| size_t size = (size_t) compiler.compiledObjectCode.size(); | |||||
| programSize = (uint32) size; | |||||
| remoteHeap.resetDataRangeToUnknown (0, remoteHeap.blockSize); | |||||
| remoteHeap.clear(); | |||||
| remoteHeap.sendChanges (*this, true); | |||||
| remoteHeap.resetDataRangeToUnknown (0, (uint32) size); | |||||
| remoteHeap.setBytes (0, compiler.compiledObjectCode.begin(), size); | |||||
| remoteHeap.sendChanges (*this, true); | |||||
| } | |||||
| else | |||||
| { | |||||
| remoteHeap.clear(); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| jassertfalse; | |||||
| } | |||||
| return juce::Result::ok(); | |||||
| } | } | ||||
| void sendProgramEvent (const LEDGrid::ProgramEventMessage& message) | |||||
| Program* getProgram() const override { return program.get(); } | |||||
| void sendProgramEvent (const ProgramEventMessage& message) override | |||||
| { | { | ||||
| static_assert (sizeof (LEDGrid::ProgramEventMessage::values) == 4 * BlocksProtocol::numProgramMessageInts, | |||||
| static_assert (sizeof (ProgramEventMessage::values) == 4 * BlocksProtocol::numProgramMessageInts, | |||||
| "Need to keep the internal and external messages structures the same"); | "Need to keep the internal and external messages structures the same"); | ||||
| if (remoteHeap.isProgramLoaded()) | if (remoteHeap.isProgramLoaded()) | ||||
| @@ -1187,9 +1303,47 @@ struct PhysicalTopologySource::Internal | |||||
| } | } | ||||
| } | } | ||||
| void saveProgramAsDefault() | |||||
| void timerCallback() override | |||||
| { | |||||
| if (remoteHeap.isFullySynced() && remoteHeap.isProgramLoaded()) | |||||
| { | |||||
| stopTimer(); | |||||
| sendCommandMessage (BlocksProtocol::saveProgramAsDefault); | |||||
| } | |||||
| else | |||||
| { | |||||
| startTimer (100); | |||||
| } | |||||
| } | |||||
| void saveProgramAsDefault() override | |||||
| { | |||||
| startTimer (10); | |||||
| } | |||||
| uint32 getMemorySize() override | |||||
| { | |||||
| return modelData.programAndHeapSize; | |||||
| } | |||||
| void setDataByte (size_t offset, uint8 value) override | |||||
| { | |||||
| remoteHeap.setByte (programSize + offset, value); | |||||
| } | |||||
| void setDataBytes (size_t offset, const void* newData, size_t num) override | |||||
| { | |||||
| remoteHeap.setBytes (programSize + offset, static_cast<const uint8*> (newData), num); | |||||
| } | |||||
| void setDataBits (uint32 startBit, uint32 numBits, uint32 value) override | |||||
| { | |||||
| remoteHeap.setBits (programSize * 8 + startBit, numBits, value); | |||||
| } | |||||
| uint8 getDataByte (size_t offset) override | |||||
| { | { | ||||
| sendCommandMessage (BlocksProtocol::saveProgramAsDefault); | |||||
| return remoteHeap.getByte (programSize + offset); | |||||
| } | } | ||||
| void handleSharedDataACK (uint32 packetCounter) noexcept | void handleSharedDataACK (uint32 packetCounter) noexcept | ||||
| @@ -1198,6 +1352,45 @@ struct PhysicalTopologySource::Internal | |||||
| remoteHeap.handleACKFromDevice (*this, packetCounter); | remoteHeap.handleACKFromDevice (*this, packetCounter); | ||||
| } | } | ||||
| bool sendFirmwareUpdatePacket (const uint8* data, uint8 size, std::function<void (uint8)> callback) override | |||||
| { | |||||
| firmwarePacketAckCallback = {}; | |||||
| auto index = getDeviceIndex(); | |||||
| if (index >= 0) | |||||
| { | |||||
| BlocksProtocol::HostPacketBuilder<256> p; | |||||
| p.writePacketSysexHeaderBytes ((BlocksProtocol::TopologyIndex) index); | |||||
| if (p.addFirmwareUpdatePacket (data, size)) | |||||
| { | |||||
| p.writePacketSysexFooter(); | |||||
| if (sendMessageToDevice (p)) | |||||
| { | |||||
| firmwarePacketAckCallback = callback; | |||||
| return true; | |||||
| } | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| jassertfalse; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| void handleFirmwareUpdateACK (uint8 resultCode) | |||||
| { | |||||
| if (firmwarePacketAckCallback != nullptr) | |||||
| { | |||||
| firmwarePacketAckCallback (resultCode); | |||||
| firmwarePacketAckCallback = {}; | |||||
| } | |||||
| } | |||||
| void pingFromDevice() | void pingFromDevice() | ||||
| { | { | ||||
| lastMessageReceiveTime = juce::Time::getCurrentTime(); | lastMessageReceiveTime = juce::Time::getCurrentTime(); | ||||
| @@ -1232,7 +1425,7 @@ struct PhysicalTopologySource::Internal | |||||
| if (auto renderer = ledGrid->getRenderer()) | if (auto renderer = ledGrid->getRenderer()) | ||||
| renderer->renderLEDGrid (*ledGrid); | renderer->renderLEDGrid (*ledGrid); | ||||
| remoteHeap.sendChanges (*this); | |||||
| remoteHeap.sendChanges (*this, false); | |||||
| if (lastMessageSendTime < juce::Time::getCurrentTime() - juce::RelativeTime::milliseconds (pingIntervalMs)) | if (lastMessageSendTime < juce::Time::getCurrentTime() - juce::RelativeTime::milliseconds (pingIntervalMs)) | ||||
| sendCommandMessage (BlocksProtocol::ping); | sendCommandMessage (BlocksProtocol::ping); | ||||
| @@ -1260,12 +1453,15 @@ struct PhysicalTopologySource::Internal | |||||
| using RemoteHeapType = littlefoot::LittleFootRemoteHeap<BlockImplementation>; | using RemoteHeapType = littlefoot::LittleFootRemoteHeap<BlockImplementation>; | ||||
| RemoteHeapType remoteHeap; | RemoteHeapType remoteHeap; | ||||
| uint32 programSize = 0; | |||||
| Detector& detector; | Detector& detector; | ||||
| juce::Time lastMessageSendTime, lastMessageReceiveTime; | juce::Time lastMessageSendTime, lastMessageReceiveTime; | ||||
| private: | private: | ||||
| std::unique_ptr<Program> program; | |||||
| uint32 programSize = 0; | |||||
| std::function<void (uint8)> firmwarePacketAckCallback; | |||||
| uint32 resetMessagesSent = 0; | uint32 resetMessagesSent = 0; | ||||
| bool isStillConnected = true; | bool isStillConnected = true; | ||||
| bool isMaster = false; | bool isMaster = false; | ||||
| @@ -1316,114 +1512,137 @@ struct PhysicalTopologySource::Internal | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| struct LEDRowImplementation : public LEDRow | |||||
| struct LEDRowImplementation : public LEDRow, | |||||
| private Timer | |||||
| { | { | ||||
| LEDRowImplementation (BlockImplementation& b) : LEDRow (b), blockImpl (b) | |||||
| LEDRowImplementation (BlockImplementation& b) : LEDRow (b) | |||||
| { | { | ||||
| loadProgramOntoBlock(); | |||||
| startTimer (300); | |||||
| } | } | ||||
| /* Data format: | |||||
| 0: 10 x 5-6-5 bits for button LED RGBs | |||||
| 20: 15 x 5-6-5 bits for LED row colours | |||||
| 50: 1 x 5-6-5 bits for LED row overlay colour | |||||
| */ | |||||
| static constexpr uint32 totalDataSize = 256; | |||||
| //============================================================================== | |||||
| void setButtonColour (uint32 index, LEDColour colour) | void setButtonColour (uint32 index, LEDColour colour) | ||||
| { | { | ||||
| if (index < 10) | if (index < 10) | ||||
| write565Colour (16 * index, colour); | |||||
| { | |||||
| colours[index] = colour; | |||||
| flush(); | |||||
| } | |||||
| } | } | ||||
| int getNumLEDs() const override | int getNumLEDs() const override | ||||
| { | { | ||||
| return blockImpl.modelData.numLEDRowLEDs; | |||||
| return static_cast<const BlockImplementation&> (block).modelData.numLEDRowLEDs; | |||||
| } | } | ||||
| void setLEDColour (int index, LEDColour colour) override | void setLEDColour (int index, LEDColour colour) override | ||||
| { | { | ||||
| if ((uint32) index < 15u) | if ((uint32) index < 15u) | ||||
| write565Colour (20 * 8 + 16 * (uint32) index, colour); | |||||
| { | |||||
| colours[10 + index] = colour; | |||||
| flush(); | |||||
| } | |||||
| } | } | ||||
| void setOverlayColour (LEDColour colour) override | void setOverlayColour (LEDColour colour) override | ||||
| { | { | ||||
| write565Colour (50 * 8, colour); | |||||
| colours[25] = colour; | |||||
| flush(); | |||||
| } | } | ||||
| void resetOverlayColour() override | void resetOverlayColour() override | ||||
| { | { | ||||
| write565Colour (50 * 8, {}); | |||||
| setOverlayColour ({}); | |||||
| } | } | ||||
| private: | private: | ||||
| void loadProgramOntoBlock() | |||||
| { | |||||
| littlefoot::Compiler compiler; | |||||
| compiler.addNativeFunctions (PhysicalTopologySource::getStandardLittleFootFunctions()); | |||||
| LEDColour colours[26]; | |||||
| auto err = compiler.compile (getLittleFootProgram(), totalDataSize); | |||||
| void timerCallback() override | |||||
| { | |||||
| stopTimer(); | |||||
| loadProgramOntoBlock(); | |||||
| flush(); | |||||
| } | |||||
| if (err.failed()) | |||||
| void loadProgramOntoBlock() | |||||
| { | |||||
| if (block.getProgram() == nullptr) | |||||
| { | { | ||||
| DBG (err.getErrorMessage()); | |||||
| jassertfalse; | |||||
| return; | |||||
| auto err = block.setProgram (new DefaultLEDGridProgram (block)); | |||||
| if (err.failed()) | |||||
| { | |||||
| DBG (err.getErrorMessage()); | |||||
| jassertfalse; | |||||
| } | |||||
| } | } | ||||
| } | |||||
| blockImpl.setProgram (compiler.compiledObjectCode.begin(), (size_t) compiler.compiledObjectCode.size()); | |||||
| void flush() | |||||
| { | |||||
| if (block.getProgram() != nullptr) | |||||
| for (uint32 i = 0; i < (uint32) numElementsInArray (colours); ++i) | |||||
| write565Colour (16 * i, colours[i]); | |||||
| } | } | ||||
| void write565Colour (uint32 bitIndex, LEDColour colour) | void write565Colour (uint32 bitIndex, LEDColour colour) | ||||
| { | { | ||||
| blockImpl.setDataBits (bitIndex, 5, colour.getRed() >> 3); | |||||
| blockImpl.setDataBits (bitIndex + 5, 6, colour.getGreen() >> 2); | |||||
| blockImpl.setDataBits (bitIndex + 11, 5, colour.getBlue() >> 3); | |||||
| block.setDataBits (bitIndex, 5, colour.getRed() >> 3); | |||||
| block.setDataBits (bitIndex + 5, 6, colour.getGreen() >> 2); | |||||
| block.setDataBits (bitIndex + 11, 5, colour.getBlue() >> 3); | |||||
| } | } | ||||
| static const char* getLittleFootProgram() noexcept | |||||
| struct DefaultLEDGridProgram : public Block::Program | |||||
| { | { | ||||
| return R"littlefoot( | |||||
| DefaultLEDGridProgram (Block& b) : Block::Program (b) {} | |||||
| int getColour (int bitIndex) | |||||
| juce::String getLittleFootProgram() override | |||||
| { | { | ||||
| return makeARGB (255, | |||||
| getHeapBits (bitIndex, 5) << 3, | |||||
| getHeapBits (bitIndex + 5, 6) << 2, | |||||
| getHeapBits (bitIndex + 11, 5) << 3); | |||||
| } | |||||
| /* Data format: | |||||
| int getButtonColour (int index) | |||||
| { | |||||
| return getColour (16 * index); | |||||
| } | |||||
| 0: 10 x 5-6-5 bits for button LED RGBs | |||||
| 20: 15 x 5-6-5 bits for LED row colours | |||||
| 50: 1 x 5-6-5 bits for LED row overlay colour | |||||
| */ | |||||
| return R"littlefoot( | |||||
| int getLEDColour (int index) | |||||
| { | |||||
| if (getHeapInt (50)) | |||||
| return getColour (50 * 8); | |||||
| #heapsize: 128 | |||||
| return getColour (20 * 8 + 16 * index); | |||||
| } | |||||
| int getColour (int bitIndex) | |||||
| { | |||||
| return makeARGB (255, | |||||
| getHeapBits (bitIndex, 5) << 3, | |||||
| getHeapBits (bitIndex + 5, 6) << 2, | |||||
| getHeapBits (bitIndex + 11, 5) << 3); | |||||
| } | |||||
| void repaint() | |||||
| { | |||||
| for (int x = 0; x < 15; ++x) | |||||
| setLED (x, 0, getLEDColour (x)); | |||||
| int getButtonColour (int index) | |||||
| { | |||||
| return getColour (16 * index); | |||||
| } | |||||
| for (int i = 0; i < 10; ++i) | |||||
| setLED (i, 1, getButtonColour (i)); | |||||
| } | |||||
| int getLEDColour (int index) | |||||
| { | |||||
| if (getHeapInt (50)) | |||||
| return getColour (50 * 8); | |||||
| void handleMessage (int p1, int p2) {} | |||||
| return getColour (20 * 8 + 16 * index); | |||||
| } | |||||
| )littlefoot"; | |||||
| } | |||||
| void repaint() | |||||
| { | |||||
| for (int x = 0; x < 15; ++x) | |||||
| fillPixel (getLEDColour (x), x, 0); | |||||
| BlockImplementation& blockImpl; | |||||
| for (int i = 0; i < 10; ++i) | |||||
| fillPixel (getButtonColour (i), i, 1); | |||||
| } | |||||
| void handleMessage (int p1, int p2) {} | |||||
| )littlefoot"; | |||||
| } | |||||
| }; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDRowImplementation) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDRowImplementation) | ||||
| }; | }; | ||||
| @@ -1638,50 +1857,7 @@ struct PhysicalTopologySource::Internal | |||||
| int getNumColumns() const override { return blockImpl.modelData.lightGridWidth; } | int getNumColumns() const override { return blockImpl.modelData.lightGridWidth; } | ||||
| int getNumRows() const override { return blockImpl.modelData.lightGridHeight; } | int getNumRows() const override { return blockImpl.modelData.lightGridHeight; } | ||||
| juce::Result setProgram (Program* newProgram) override | |||||
| { | |||||
| if (program.get() != newProgram) | |||||
| { | |||||
| program.reset (newProgram); | |||||
| if (program != nullptr) | |||||
| { | |||||
| littlefoot::Compiler compiler; | |||||
| compiler.addNativeFunctions (PhysicalTopologySource::getStandardLittleFootFunctions()); | |||||
| auto err = compiler.compile (newProgram->getLittleFootProgram(), newProgram->getHeapSize()); | |||||
| if (err.failed()) | |||||
| return err; | |||||
| DBG ("Compiled littlefoot program, size = " << (int) compiler.compiledObjectCode.size() << " bytes"); | |||||
| blockImpl.setProgram (compiler.compiledObjectCode.begin(), (size_t) compiler.compiledObjectCode.size()); | |||||
| } | |||||
| else | |||||
| { | |||||
| blockImpl.clearProgramAndData(); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| jassertfalse; | |||||
| } | |||||
| return juce::Result::ok(); | |||||
| } | |||||
| Program* getProgram() const override { return program.get(); } | |||||
| void sendProgramEvent (const ProgramEventMessage& m) override { blockImpl.sendProgramEvent (m); } | |||||
| void saveProgramAsDefault() override { blockImpl.saveProgramAsDefault(); } | |||||
| void setDataByte (size_t offset, uint8 value) override { blockImpl.setDataByte (offset, value); } | |||||
| void setDataBytes (size_t offset, const void* data, size_t num) override { blockImpl.setDataBytes (offset, data, num); } | |||||
| void setDataBits (uint32 startBit, uint32 numBits, uint32 value) override { blockImpl.setDataBits (startBit, numBits, value); } | |||||
| uint8 getDataByte (size_t offset) override { return blockImpl.getDataByte (offset); } | |||||
| BlockImplementation& blockImpl; | BlockImplementation& blockImpl; | ||||
| std::unique_ptr<Program> program; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGridImplementation) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LEDGridImplementation) | ||||
| }; | }; | ||||
| @@ -29,7 +29,7 @@ | |||||
| */ | */ | ||||
| BitmapLEDProgram::BitmapLEDProgram (LEDGrid& lg) : Program (lg) {} | |||||
| BitmapLEDProgram::BitmapLEDProgram (Block& b) : Program (b) {} | |||||
| /* | /* | ||||
| The heap format for this program is just an array of 15x15 5:6:5 colours, | The heap format for this program is just an array of 15x15 5:6:5 colours, | ||||
| @@ -38,27 +38,31 @@ BitmapLEDProgram::BitmapLEDProgram (LEDGrid& lg) : Program (lg) {} | |||||
| void BitmapLEDProgram::setLED (uint32 x, uint32 y, LEDColour colour) | void BitmapLEDProgram::setLED (uint32 x, uint32 y, LEDColour colour) | ||||
| { | { | ||||
| auto w = (uint32) ledGrid.getNumColumns(); | |||||
| auto h = (uint32) ledGrid.getNumRows(); | |||||
| if (x < w && y < h) | |||||
| if (auto ledGrid = block.getLEDGrid()) | |||||
| { | { | ||||
| auto bit = (x + y * w) * 16; | |||||
| auto w = (uint32) ledGrid->getNumColumns(); | |||||
| auto h = (uint32) ledGrid->getNumRows(); | |||||
| ledGrid.setDataBits (bit, 5, colour.getRed() >> 3); | |||||
| ledGrid.setDataBits (bit + 5, 6, colour.getGreen() >> 2); | |||||
| ledGrid.setDataBits (bit + 11, 5, colour.getBlue() >> 3); | |||||
| } | |||||
| } | |||||
| if (x < w && y < h) | |||||
| { | |||||
| auto bit = (x + y * w) * 16; | |||||
| uint32 BitmapLEDProgram::getHeapSize() | |||||
| { | |||||
| return 15 * 15 * 16; | |||||
| block.setDataBits (bit, 5, colour.getRed() >> 3); | |||||
| block.setDataBits (bit + 5, 6, colour.getGreen() >> 2); | |||||
| block.setDataBits (bit + 11, 5, colour.getBlue() >> 3); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| jassertfalse; | |||||
| } | |||||
| } | } | ||||
| juce::String BitmapLEDProgram::getLittleFootProgram() | juce::String BitmapLEDProgram::getLittleFootProgram() | ||||
| { | { | ||||
| auto program = R"littlefoot( | |||||
| String program (R"littlefoot( | |||||
| #heapsize: 15 * 15 * 2 | |||||
| void repaint() | void repaint() | ||||
| { | { | ||||
| @@ -76,9 +80,12 @@ juce::String BitmapLEDProgram::getLittleFootProgram() | |||||
| } | } | ||||
| } | } | ||||
| )littlefoot"; | |||||
| )littlefoot"); | |||||
| if (auto ledGrid = block.getLEDGrid()) | |||||
| return program.replace ("NUM_COLUMNS", juce::String (ledGrid->getNumColumns())) | |||||
| .replace ("NUM_ROWS", juce::String (ledGrid->getNumRows())); | |||||
| return juce::String (program) | |||||
| .replace ("NUM_COLUMNS", juce::String (ledGrid.getNumColumns())) | |||||
| .replace ("NUM_ROWS", juce::String (ledGrid.getNumRows())); | |||||
| jassertfalse; | |||||
| return {}; | |||||
| } | } | ||||
| @@ -32,14 +32,13 @@ | |||||
| /** | /** | ||||
| A simple Program to set the colours of individual LEDs. | A simple Program to set the colours of individual LEDs. | ||||
| */ | */ | ||||
| struct BitmapLEDProgram : public LEDGrid::Program | |||||
| struct BitmapLEDProgram : public Block::Program | |||||
| { | { | ||||
| BitmapLEDProgram (LEDGrid&); | |||||
| BitmapLEDProgram (Block&); | |||||
| /** Set the colour of the LED at coordinates {x, y}. */ | /** Set the colour of the LED at coordinates {x, y}. */ | ||||
| void setLED (uint32 x, uint32 y, LEDColour); | void setLED (uint32 x, uint32 y, LEDColour); | ||||
| private: | private: | ||||
| juce::String getLittleFootProgram() override; | juce::String getLittleFootProgram() override; | ||||
| uint32 getHeapSize() override; | |||||
| }; | }; | ||||
| @@ -29,16 +29,16 @@ | |||||
| */ | */ | ||||
| DrumPadGridProgram::DrumPadGridProgram (LEDGrid& lg) : Program (lg) {} | |||||
| DrumPadGridProgram::DrumPadGridProgram (Block& b) : Program (b) {} | |||||
| int DrumPadGridProgram::getPadIndex (float posX, float posY) const | int DrumPadGridProgram::getPadIndex (float posX, float posY) const | ||||
| { | { | ||||
| posX = juce::jmin (0.99f, posX / ledGrid.block.getWidth()); | |||||
| posY = juce::jmin (0.99f, posY / ledGrid.block.getHeight()); | |||||
| posX = juce::jmin (0.99f, posX / block.getWidth()); | |||||
| posY = juce::jmin (0.99f, posY / block.getHeight()); | |||||
| const uint32 offset = ledGrid.getDataByte (visiblePads_byte) ? numColumns1_byte : numColumns0_byte; | |||||
| const int numColumns = ledGrid.getDataByte (offset + numColumns0_byte); | |||||
| const int numRows = ledGrid.getDataByte (offset + numRows0_byte); | |||||
| const uint32 offset = block.getDataByte (visiblePads_byte) ? numColumns1_byte : numColumns0_byte; | |||||
| const int numColumns = block.getDataByte (offset + numColumns0_byte); | |||||
| const int numRows = block.getDataByte (offset + numRows0_byte); | |||||
| return int (posX * numColumns) + int (posY * numRows) * numColumns; | return int (posX * numColumns) + int (posY * numRows) * numColumns; | ||||
| } | } | ||||
| @@ -49,9 +49,9 @@ void DrumPadGridProgram::startTouch (float startX, float startY) | |||||
| for (size_t i = 0; i < 4; ++i) | for (size_t i = 0; i < 4; ++i) | ||||
| { | { | ||||
| if (ledGrid.getDataByte (touchedPads_byte + i) == 0) | |||||
| if (block.getDataByte (touchedPads_byte + i) == 0) | |||||
| { | { | ||||
| ledGrid.setDataByte (touchedPads_byte + i, static_cast<uint8> (padIdx + 1)); | |||||
| block.setDataByte (touchedPads_byte + i, static_cast<uint8> (padIdx + 1)); | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| @@ -62,28 +62,28 @@ void DrumPadGridProgram::endTouch (float startX, float startY) | |||||
| const auto padIdx = getPadIndex (startX, startY); | const auto padIdx = getPadIndex (startX, startY); | ||||
| for (size_t i = 0; i < 4; ++i) | for (size_t i = 0; i < 4; ++i) | ||||
| if (ledGrid.getDataByte (touchedPads_byte + i) == (padIdx + 1)) | |||||
| ledGrid.setDataByte (touchedPads_byte + i, 0); | |||||
| if (block.getDataByte (touchedPads_byte + i) == (padIdx + 1)) | |||||
| block.setDataByte (touchedPads_byte + i, 0); | |||||
| } | } | ||||
| void DrumPadGridProgram::sendTouch (float x, float y, float z, LEDColour colour) | void DrumPadGridProgram::sendTouch (float x, float y, float z, LEDColour colour) | ||||
| { | { | ||||
| LEDGrid::ProgramEventMessage e; | |||||
| Block::ProgramEventMessage e; | |||||
| e.values[0] = 0x20000000 | e.values[0] = 0x20000000 | ||||
| + (juce::jlimit (0, 255, juce::roundToInt (x * (255.0f / ledGrid.block.getWidth()))) << 16) | |||||
| + (juce::jlimit (0, 255, juce::roundToInt (y * (255.0f / ledGrid.block.getHeight()))) << 8) | |||||
| + (juce::jlimit (0, 255, juce::roundToInt (x * (255.0f / block.getWidth()))) << 16) | |||||
| + (juce::jlimit (0, 255, juce::roundToInt (y * (255.0f / block.getHeight()))) << 8) | |||||
| + juce::jlimit (0, 255, juce::roundToInt (z * 255.0f)); | + juce::jlimit (0, 255, juce::roundToInt (z * 255.0f)); | ||||
| e.values[1] = (int32) colour.getARGB(); | e.values[1] = (int32) colour.getARGB(); | ||||
| ledGrid.sendProgramEvent (e); | |||||
| block.sendProgramEvent (e); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills) | void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills) | ||||
| { | { | ||||
| uint8 visiblePads = ledGrid.getDataByte (visiblePads_byte); | |||||
| uint8 visiblePads = block.getDataByte (visiblePads_byte); | |||||
| setGridFills (numColumns, numRows, fills, visiblePads * numColumns1_byte); | setGridFills (numColumns, numRows, fills, visiblePads * numColumns1_byte); | ||||
| } | } | ||||
| @@ -92,8 +92,8 @@ void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce:: | |||||
| { | { | ||||
| jassert (numColumns * numRows == fills.size()); | jassert (numColumns * numRows == fills.size()); | ||||
| ledGrid.setDataByte (byteOffset + numColumns0_byte, (uint8) numColumns); | |||||
| ledGrid.setDataByte (byteOffset + numRows0_byte, (uint8) numRows); | |||||
| block.setDataByte (byteOffset + numColumns0_byte, (uint8) numColumns); | |||||
| block.setDataByte (byteOffset + numRows0_byte, (uint8) numRows); | |||||
| uint32 i = 0; | uint32 i = 0; | ||||
| @@ -108,11 +108,11 @@ void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce:: | |||||
| const uint32 colourOffsetBytes = byteOffset + colours0_byte + i * colourSizeBytes; | const uint32 colourOffsetBytes = byteOffset + colours0_byte + i * colourSizeBytes; | ||||
| const uint32 colourOffsetBits = colourOffsetBytes * 8; | const uint32 colourOffsetBits = colourOffsetBytes * 8; | ||||
| ledGrid.setDataBits (colourOffsetBits, 5, fill.colour.getRed() >> 3); | |||||
| ledGrid.setDataBits (colourOffsetBits + 5, 6, fill.colour.getGreen() >> 2); | |||||
| ledGrid.setDataBits (colourOffsetBits + 11, 5, fill.colour.getBlue() >> 3); | |||||
| block.setDataBits (colourOffsetBits, 5, fill.colour.getRed() >> 3); | |||||
| block.setDataBits (colourOffsetBits + 5, 6, fill.colour.getGreen() >> 2); | |||||
| block.setDataBits (colourOffsetBits + 11, 5, fill.colour.getBlue() >> 3); | |||||
| ledGrid.setDataByte (byteOffset + fillTypes0_byte + i, static_cast<uint8> (fill.fillType)); | |||||
| block.setDataByte (byteOffset + fillTypes0_byte + i, static_cast<uint8> (fill.fillType)); | |||||
| ++i; | ++i; | ||||
| } | } | ||||
| @@ -121,12 +121,12 @@ void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce:: | |||||
| void DrumPadGridProgram::triggerSlideTransition (int newNumColumns, int newNumRows, | void DrumPadGridProgram::triggerSlideTransition (int newNumColumns, int newNumRows, | ||||
| const juce::Array<GridFill>& newFills, SlideDirection direction) | const juce::Array<GridFill>& newFills, SlideDirection direction) | ||||
| { | { | ||||
| uint8 newVisible = ledGrid.getDataByte (visiblePads_byte) ? 0 : 1; | |||||
| uint8 newVisible = block.getDataByte (visiblePads_byte) ? 0 : 1; | |||||
| setGridFills (newNumColumns, newNumRows, newFills, newVisible * numColumns1_byte); | setGridFills (newNumColumns, newNumRows, newFills, newVisible * numColumns1_byte); | ||||
| ledGrid.setDataByte (visiblePads_byte, newVisible); | |||||
| ledGrid.setDataByte (slideDirection_byte, (uint8) direction); | |||||
| block.setDataByte (visiblePads_byte, newVisible); | |||||
| block.setDataByte (slideDirection_byte, (uint8) direction); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -143,8 +143,8 @@ void DrumPadGridProgram::setPadAnimationState (uint32 padIdx, double loopTimeSec | |||||
| uint32 offset = 8 * animationTimers_byte + 32 * padIdx; | uint32 offset = 8 * animationTimers_byte + 32 * padIdx; | ||||
| ledGrid.setDataBits (offset, 16, aniValue); | |||||
| ledGrid.setDataBits (offset + 16, 16, aniIncrement); | |||||
| block.setDataBits (offset, 16, aniValue); | |||||
| block.setDataBits (offset + 16, 16, aniIncrement); | |||||
| } | } | ||||
| void DrumPadGridProgram::suspendAnimations() | void DrumPadGridProgram::suspendAnimations() | ||||
| @@ -152,29 +152,26 @@ void DrumPadGridProgram::suspendAnimations() | |||||
| for (uint32 i = 0; i < 16; ++i) | for (uint32 i = 0; i < 16; ++i) | ||||
| { | { | ||||
| uint32 offset = 8 * animationTimers_byte + 32 * i; | uint32 offset = 8 * animationTimers_byte + 32 * i; | ||||
| ledGrid.setDataBits (offset + 16, 16, 0); | |||||
| block.setDataBits (offset + 16, 16, 0); | |||||
| } | } | ||||
| // Hijack touch dimming | // Hijack touch dimming | ||||
| ledGrid.setDataByte (touchedPads_byte, 255); | |||||
| block.setDataByte (touchedPads_byte, 255); | |||||
| } | } | ||||
| void DrumPadGridProgram::resumeAnimations() | void DrumPadGridProgram::resumeAnimations() | ||||
| { | { | ||||
| // Unhijack touch dimming | // Unhijack touch dimming | ||||
| ledGrid.setDataByte (touchedPads_byte, 0); | |||||
| block.setDataByte (touchedPads_byte, 0); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| uint32 DrumPadGridProgram::getHeapSize() | |||||
| { | |||||
| return totalDataSize; | |||||
| } | |||||
| juce::String DrumPadGridProgram::getLittleFootProgram() | juce::String DrumPadGridProgram::getLittleFootProgram() | ||||
| { | { | ||||
| return R"littlefoot( | return R"littlefoot( | ||||
| #heapsize: 256 | |||||
| int dimFactor; | int dimFactor; | ||||
| int dimDelay; | int dimDelay; | ||||
| int slideAnimationProgress; | int slideAnimationProgress; | ||||
| @@ -236,8 +233,8 @@ juce::String DrumPadGridProgram::getLittleFootProgram() | |||||
| { | { | ||||
| int gradColour = blendARGB (colour, makeARGB (((xx + yy) * 250) / divisor, 0, 0, 0)); | int gradColour = blendARGB (colour, makeARGB (((xx + yy) * 250) / divisor, 0, 0, 0)); | ||||
| setLED (x + xx, y + yy, gradColour); | |||||
| setLED (x + yy, y + xx, gradColour); | |||||
| fillPixel (gradColour, x + xx, y + yy); | |||||
| fillPixel (gradColour, x + yy, y + xx); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -255,7 +252,7 @@ juce::String DrumPadGridProgram::getLittleFootProgram() | |||||
| for (int i = 1; i <= numToDo; ++i) | for (int i = 1; i <= numToDo; ++i) | ||||
| { | { | ||||
| setLED (x, y, colour); | |||||
| fillPixel (colour, x, y); | |||||
| if (i < w) | if (i < w) | ||||
| ++x; | ++x; | ||||
| @@ -278,89 +275,46 @@ juce::String DrumPadGridProgram::getLittleFootProgram() | |||||
| { | { | ||||
| fillGradientRect (colour, padX, padY, padW); | fillGradientRect (colour, padX, padY, padW); | ||||
| } | } | ||||
| else if (fill == 1) // Filled | else if (fill == 1) // Filled | ||||
| { | { | ||||
| fillRect (colour, padX, padY, padW, padW); | fillRect (colour, padX, padY, padW, padW); | ||||
| } | } | ||||
| else if (fill == 2) // Hollow | else if (fill == 2) // Hollow | ||||
| { | { | ||||
| outlineRect (colour, padX, padY, padW); | outlineRect (colour, padX, padY, padW); | ||||
| } | } | ||||
| else if (fill == 3) // Hollow with plus | else if (fill == 3) // Hollow with plus | ||||
| { | { | ||||
| outlineRect (colour, padX, padY, padW); | outlineRect (colour, padX, padY, padW); | ||||
| drawPlus (0xffffffff, padX, padY, padW); | drawPlus (0xffffffff, padX, padY, padW); | ||||
| } | } | ||||
| else if (fill == 4) // Pulsing dot | else if (fill == 4) // Pulsing dot | ||||
| { | { | ||||
| int pulseCol = blendARGB (colour, makeARGB (animateProgress, 0, 0, 0)); | int pulseCol = blendARGB (colour, makeARGB (animateProgress, 0, 0, 0)); | ||||
| setLED (padX + halfW, padY + halfW, pulseCol); | |||||
| fillPixel (pulseCol, padX + halfW, padY + halfW); | |||||
| } | } | ||||
| else if (fill == 5) // Blinking dot | else if (fill == 5) // Blinking dot | ||||
| { | { | ||||
| int blinkCol = animateProgress > 64 ? makeARGB (255, 0, 0, 0) : colour; | |||||
| int blinkCol = animateProgress > 64 ? 0xff000000 : colour; | |||||
| setLED (padX + halfW, padY + halfW, blinkCol); | |||||
| fillPixel (blinkCol, padX + halfW, padY + halfW); | |||||
| } | } | ||||
| else if (fill == 6) // Pizza filled | else if (fill == 6) // Pizza filled | ||||
| { | { | ||||
| outlineRect (blendARGB (colour, makeARGB (220, 0, 0, 0)), padX, padY, padW); // Dim outline | |||||
| setLED (padX + halfW, padY + halfW, colour); // Bright centre | |||||
| outlineRect (blendARGB (colour, 0xdc000000), padX, padY, padW); // Dim outline | |||||
| fillPixel (colour, padX + halfW, padY + halfW); // Bright centre | |||||
| drawPizzaLED (colour, padX, padY, padW, animateProgress); | drawPizzaLED (colour, padX, padY, padW, animateProgress); | ||||
| } | } | ||||
| else if (fill == 7) // Pizza hollow | |||||
| else // Pizza hollow | |||||
| { | { | ||||
| outlineRect (blendARGB (colour, makeARGB (220, 0, 0, 0)), padX, padY, padW); // Dim outline | |||||
| outlineRect (blendARGB (colour, 0xdc000000), padX, padY, padW); // Dim outline | |||||
| drawPizzaLED (colour, padX, padY, padW, animateProgress); | drawPizzaLED (colour, padX, padY, padW, animateProgress); | ||||
| return; | |||||
| } | |||||
| } | |||||
| void fadeHeatMap() | |||||
| { | |||||
| for (int i = 0; i < 225; ++i) | |||||
| { | |||||
| int colourOffset = 226 + i * 4; | |||||
| int colour = getHeapInt (colourOffset); | |||||
| int alpha = (colour >> 24) & 0xff; | |||||
| if (alpha > 0) | |||||
| { | |||||
| alpha -= getHeapByte (1126 + i); | |||||
| setHeapInt (colourOffset, alpha < 0 ? 0 : ((alpha << 24) | (colour & 0xffffff))); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| void addToHeatMap (int x, int y, int colour) | |||||
| { | |||||
| if (x >= 0 && y >= 0 && x < 15 && y < 15) | |||||
| { | |||||
| int offset = 226 + 4 * (x + y * 15); | |||||
| colour = blendARGB (getHeapInt (offset), colour); | |||||
| setHeapInt (offset, colour); | |||||
| int decay = ((colour >> 24) & 0xff) / 14; // change divisor to change trail times | |||||
| offset = 1126 + (x + y * 15); | |||||
| setHeapByte (offset, decay > 0 ? decay : 1); | |||||
| } | |||||
| } | |||||
| int getHeatmapColour (int x, int y) | |||||
| { | |||||
| return getHeapInt (226 + 4 * (x + y * 15)); | |||||
| } | |||||
| int isPadActive (int index) | int isPadActive (int index) | ||||
| { | { | ||||
| if (getHeapInt (158) == 0) // None active | if (getHeapInt (158) == 0) // None active | ||||
| @@ -499,48 +453,25 @@ juce::String DrumPadGridProgram::getLittleFootProgram() | |||||
| slideAnimatePads(); | slideAnimatePads(); | ||||
| // Overlay heatmap | // Overlay heatmap | ||||
| for (int y = 0; y < 15; ++y) | |||||
| for (int x = 0; x < 15; ++x) | |||||
| blendLED (x, y, getHeatmapColour (x, y)); | |||||
| fadeHeatMap(); | |||||
| drawPressureMap(); | |||||
| fadePressureMap(); | |||||
| } | } | ||||
| // DrumPadGridProgram::sendTouch results in this callback, giving | // DrumPadGridProgram::sendTouch results in this callback, giving | ||||
| // us more touch updates per frame and therefore smoother trails. | // us more touch updates per frame and therefore smoother trails. | ||||
| void handleMessage (int pos, int colour) | |||||
| void handleMessage (int pos, int colour, int dummy) | |||||
| { | { | ||||
| if ((pos >> 24) != 0x20) | if ((pos >> 24) != 0x20) | ||||
| return; | return; | ||||
| int tx = ((pos >> 16) & 0xff) - 13; | |||||
| int ty = ((pos >> 8) & 0xff) - 13; | |||||
| int tx = (pos >> 16) & 0xff; | |||||
| int ty = (pos >> 8) & 0xff; | |||||
| int tz = pos & 0xff; | int tz = pos & 0xff; | ||||
| tz = tz > 30 ? tz : 30; | |||||
| int ledCentreX = tx >> 4; | |||||
| int ledCentreY = ty >> 4; | |||||
| int adjustX = (tx - (ledCentreX << 4)) >> 2; | |||||
| int adjustY = (ty - (ledCentreY << 4)) >> 2; | |||||
| for (int dy = -2; dy <= 2; ++dy) | |||||
| { | |||||
| for (int dx = -2; dx <= 2; ++dx) | |||||
| { | |||||
| int distance = dx * dx + dy * dy; | |||||
| int level = distance == 0 ? 255 : (distance == 1 ? 132 : (distance < 5 ? 9 : (distance == 5 ? 2 : 0))); | |||||
| level += (dx * adjustX); | |||||
| level += (dy * adjustY); | |||||
| level = (tz * level) >> 8; | |||||
| if (level > 0) | |||||
| addToHeatMap (ledCentreX + dx, ledCentreY + dy, | |||||
| makeARGB (level, colour >> 16, colour >> 8, colour)); | |||||
| } | |||||
| } | |||||
| addPressurePoint (colour, | |||||
| tx * (2.0 / (256 + 20)), | |||||
| ty * (2.0 / (256 + 20)), | |||||
| tz * (1.0 / 3.0)); | |||||
| } | } | ||||
| )littlefoot"; | )littlefoot"; | ||||
| @@ -30,9 +30,9 @@ | |||||
| /** | /** | ||||
| */ | */ | ||||
| struct DrumPadGridProgram : public LEDGrid::Program | |||||
| struct DrumPadGridProgram : public Block::Program | |||||
| { | { | ||||
| DrumPadGridProgram (LEDGrid&); | |||||
| DrumPadGridProgram (Block&); | |||||
| //============================================================================== | //============================================================================== | ||||
| /** These let the program dim pads which aren't having gestures performed on them. */ | /** These let the program dim pads which aren't having gestures performed on them. */ | ||||
| @@ -114,18 +114,13 @@ private: | |||||
| static constexpr uint32 slideDirection_byte = 156; // 1 byte | static constexpr uint32 slideDirection_byte = 156; // 1 byte | ||||
| static constexpr uint32 touchedPads_byte = 158; // 1 byte x 4 (Zero means empty slot, so stores padIdx + 1) | static constexpr uint32 touchedPads_byte = 158; // 1 byte x 4 (Zero means empty slot, so stores padIdx + 1) | ||||
| static constexpr uint32 animationTimers_byte = 162; // 4 byte x 16 (16:16 bits counter:increment) | static constexpr uint32 animationTimers_byte = 162; // 4 byte x 16 (16:16 bits counter:increment) | ||||
| static constexpr uint32 heatMap_byte = 226; // 4 byte x 225 | |||||
| static constexpr uint32 heatDecayMap_byte = 1126; // 1 byte x 225 | |||||
| static constexpr uint32 totalHeapSize = 226; | |||||
| static constexpr uint32 maxNumPads = 25; | static constexpr uint32 maxNumPads = 25; | ||||
| static constexpr uint32 colourSizeBytes = 2; | static constexpr uint32 colourSizeBytes = 2; | ||||
| static constexpr uint32 heatMapSize = 15 * 15 * 4; | |||||
| static constexpr uint32 heatMapDecaySize = 15 * 15; | |||||
| static constexpr uint32 totalDataSize = heatDecayMap_byte + heatMapDecaySize; | |||||
| int getPadIndex (float posX, float posY) const; | int getPadIndex (float posX, float posY) const; | ||||
| void setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills, uint32 byteOffset); | void setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills, uint32 byteOffset); | ||||
| juce::String getLittleFootProgram() override; | juce::String getLittleFootProgram() override; | ||||
| uint32 getHeapSize() override; | |||||
| }; | }; | ||||