diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 5a931e892d..cde55acaee 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -4,6 +4,28 @@ JUCE breaking changes Develop ======= +Change +------ +The `juce_blocks_basics` module was removed. + +Possible Issues +--------------- +Projects depending on `juce_blocks_basics` will not build. + +Workaround +---------- +The BLOCKS API is now located in a separate repository: +https://github.com/WeAreROLI/roli_blocks_basics +Projects which used to depend on `juce_blocks_basics` can use +`roli_blocks_basics` instead. + +Rationale +--------- +ROLI is no longer involved with the development of JUCE. Therefore, development +on the BLOCKS API has been moved out of the JUCE repository, and to a new +repository managed by ROLI. + + Change ------ The live build functionality of the Projucer has been removed. diff --git a/LICENSE.md b/LICENSE.md index 7c7554bd6f..75e59b30fe 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -24,8 +24,8 @@ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. -The juce_audio_basics, juce_audio_devices, juce_blocks_basics, juce_core and -juce_events modules are permissively licensed under the terms of the [ISC +The juce_audio_basics, juce_audio_devices, juce_core and juce_events modules +are permissively licensed under the terms of the [ISC license](http://www.isc.org/downloads/software-support-policy/isc-license). For more information, visit the website: diff --git a/README.md b/README.md index a90024d2ed..de716e4660 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,8 @@ but if you would like to contribute any changes please contact us. ## License -The core JUCE modules (juce_audio_basics, juce_audio_devices, juce_blocks_basics, juce_core -and juce_events) are permissively licensed under the terms of the +The core JUCE modules (juce_audio_basics, juce_audio_devices, juce_core and juce_events) +are permissively licensed under the terms of the [ISC license](http://www.isc.org/downloads/software-support-policy/isc-license/). Other modules are covered by a [GPL/Commercial license](https://www.gnu.org/licenses/gpl-3.0.en.html). diff --git a/examples/BLOCKS/BlocksDrawingDemo.h b/examples/BLOCKS/BlocksDrawingDemo.h deleted file mode 100644 index c072c9bbd0..0000000000 --- a/examples/BLOCKS/BlocksDrawingDemo.h +++ /dev/null @@ -1,699 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE examples. - Copyright (c) 2020 - Raw Material Software Limited - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, - WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR - PURPOSE, ARE DISCLAIMED. - - ============================================================================== -*/ - -/******************************************************************************* - The block below describes the properties of this PIP. A PIP is a short snippet - of code that can be read by the Projucer and used to generate a JUCE project. - - BEGIN_JUCE_PIP_METADATA - - name: BlocksDrawingDemo - version: 1.0.0 - vendor: JUCE - website: http://juce.com - description: Blocks application to draw shapes. - - dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, - juce_audio_processors, juce_audio_utils, juce_blocks_basics, - juce_core, juce_data_structures, juce_events, juce_graphics, - juce_gui_basics, juce_gui_extra - exporters: xcode_mac, vs2019, linux_make, xcode_iphone - - moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 - - type: Component - mainClass: BlocksDrawingDemo - - useLocalCopy: 1 - - END_JUCE_PIP_METADATA - -*******************************************************************************/ - -#pragma once - - -//============================================================================== -/** - Represents a single LED on a Lightpad -*/ -struct LEDComponent : public Component -{ - LEDComponent() : ledColour (Colours::black) { setInterceptsMouseClicks (false, false); } - - void setColour (Colour newColour) - { - ledColour = newColour; - repaint(); - } - - void paint (Graphics& g) override - { - g.setColour (ledColour); - g.fillEllipse (getLocalBounds().toFloat()); - } - - Colour ledColour; -}; - -//============================================================================== -/** - A component that is used to represent a Lightpad on-screen -*/ -class DrawableLightpadComponent : public Component -{ -public: - DrawableLightpadComponent () - { - for (auto x = 0; x < 15; ++x) - for (auto y = 0; y < 15; ++y) - addAndMakeVisible (leds.add (new LEDComponent())); - } - - void paint (Graphics& g) override - { - auto r = getLocalBounds().toFloat(); - - // Clip the drawing area to only draw in the block area - { - Path clipArea; - clipArea.addRoundedRectangle (r, r.getWidth() / 20.0f); - - g.reduceClipRegion (clipArea); - } - - // Fill a black square for the Lightpad - g.fillAll (Colours::black); - } - - void resized() override - { - auto r = getLocalBounds().reduced (10); - - auto circleWidth = r.getWidth() / 15; - auto circleHeight = r.getHeight() / 15; - - for (auto x = 0; x < 15; ++x) - for (auto y = 0; y < 15; ++y) - leds.getUnchecked ((x * 15) + y)->setBounds (r.getX() + (x * circleWidth), - r.getY() + (y * circleHeight), - circleWidth, circleHeight); - } - - void mouseDown (const MouseEvent& e) override - { - for (auto x = 0; x < 15; ++x) - for (auto y = 0; y < 15; ++y) - if (leds.getUnchecked ((x * 15) + y)->getBounds().contains (e.position.toInt())) - listeners.call ([&] (Listener& l) { l.ledClicked (x, y, e.pressure); }); - } - - void mouseDrag (const MouseEvent& e) override - { - for (auto x = 0; x < 15; ++x) - { - for (auto y = 0; y < 15; ++y) - { - if (leds.getUnchecked ((x * 15) + y)->getBounds().contains (e.position.toInt())) - { - auto t = e.eventTime; - - if (lastLED == Point (x, y) && t.toMilliseconds() - lastMouseEventTime.toMilliseconds() < 50) - return; - - listeners.call ([&] (Listener& l) { l.ledClicked (x, y, e.pressure); }); - - lastLED = { x, y }; - lastMouseEventTime = t; - } - } - } - } - - //============================================================================== - /** Sets the colour of one of the LEDComponents */ - void setLEDColour (int x, int y, Colour c) - { - x = jmin (x, 14); - y = jmin (y, 14); - - leds.getUnchecked ((x * 15) + y)->setColour (c); - } - - //============================================================================== - struct Listener - { - virtual ~Listener() {} - - /** Called when an LEDComponent has been clicked */ - virtual void ledClicked (int x, int y, float z) = 0; - }; - - void addListener (Listener* l) { listeners.add (l); } - void removeListener (Listener* l) { listeners.remove (l); } - -private: - OwnedArray leds; - ListenerList listeners; - - Time lastMouseEventTime; - Point lastLED; -}; - -//============================================================================== -/** - A struct that handles the setup and layout of the DrumPadGridProgram -*/ -struct ColourGrid -{ - ColourGrid (int cols, int rows) - : numColumns (cols), - numRows (rows) - { - constructGridFillArray(); - } - - /** Creates a GridFill object for each pad in the grid and sets its colour - and fill before adding it to an array of GridFill objects - */ - void constructGridFillArray() - { - gridFillArray.clear(); - - auto counter = 0; - - for (auto i = 0; i < numColumns; ++i) - { - for (auto j = 0; j < numRows; ++j) - { - DrumPadGridProgram::GridFill fill; - Colour colourToUse = colourArray.getUnchecked (counter); - - fill.colour = colourToUse.withBrightness (colourToUse == currentColour ? 1.0f : 0.1f); - - if (colourToUse == Colours::black) - fill.fillType = DrumPadGridProgram::GridFill::FillType::hollow; - else - fill.fillType = DrumPadGridProgram::GridFill::FillType::filled; - - gridFillArray.add (fill); - - if (++counter == colourArray.size()) - counter = 0; - } - } - } - - /** Sets which colour should be active for a given touch co-ordinate. Returns - true if the colour has changed - */ - bool setActiveColourForTouch (int x, int y) - { - auto colourHasChanged = false; - - auto xindex = x / 5; - auto yindex = y / 5; - - auto newColour = colourArray.getUnchecked ((yindex * 3) + xindex); - if (currentColour != newColour) - { - currentColour = newColour; - constructGridFillArray(); - colourHasChanged = true; - } - - return colourHasChanged; - } - - //============================================================================== - int numColumns, numRows; - - Array gridFillArray; - - Array colourArray = { Colours::white, Colours::red, Colours::green, - Colours::blue, Colours::hotpink, Colours::orange, - Colours::magenta, Colours::cyan, Colours::black }; - - Colour currentColour = Colours::hotpink; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ColourGrid) -}; - -//============================================================================== -/** - The main component -*/ -class BlocksDrawingDemo : public Component, - public TopologySource::Listener, - private TouchSurface::Listener, - private ControlButton::Listener, - private DrawableLightpadComponent::Listener, - private Timer -{ -public: - //============================================================================== - BlocksDrawingDemo() - { - activeLeds.clear(); - - // Register MainContentComponent as a listener to the PhysicalTopologySource object - topologySource.addListener (this); - - infoLabel.setText ("Connect a Lightpad Block to draw.", dontSendNotification); - infoLabel.setJustificationType (Justification::centred); - addAndMakeVisible (infoLabel); - - addAndMakeVisible (lightpadComponent); - lightpadComponent.setVisible (false); - lightpadComponent.addListener (this); - - clearButton.setButtonText ("Clear"); - clearButton.onClick = [this] { clearLEDs(); }; - clearButton.setAlwaysOnTop (true); - addAndMakeVisible (clearButton); - - brightnessSlider.setRange (0.0, 1.0); - brightnessSlider.setValue (1.0); - brightnessSlider.setAlwaysOnTop (true); - brightnessSlider.setTextBoxStyle (Slider::TextEntryBoxPosition::NoTextBox, false, 0, 0); - brightnessSlider.onValueChange = [this] - { - brightnessLED.setColour (layout.currentColour - .withBrightness (layout.currentColour == Colours::black ? 0.0f - : static_cast (brightnessSlider.getValue()))); - }; - addAndMakeVisible (brightnessSlider); - - brightnessLED.setAlwaysOnTop (true); - brightnessLED.setColour (layout.currentColour.withBrightness (static_cast (brightnessSlider.getValue()))); - addAndMakeVisible (brightnessLED); - - #if JUCE_IOS - connectButton.setButtonText ("Connect"); - connectButton.onClick = [] { BluetoothMidiDevicePairingDialogue::open(); }; - connectButton.setAlwaysOnTop (true); - addAndMakeVisible (connectButton); - #endif - - setSize (600, 600); - - topologyChanged(); - } - - ~BlocksDrawingDemo() override - { - if (activeBlock != nullptr) - detachActiveBlock(); - - lightpadComponent.removeListener (this); - topologySource.removeListener (this); - } - - void resized() override - { - infoLabel.centreWithSize (getWidth(), 100); - - auto bounds = getLocalBounds().reduced (20); - - // top buttons - auto topButtonArea = bounds.removeFromTop (getHeight() / 20); - - topButtonArea.removeFromLeft (20); - clearButton.setBounds (topButtonArea.removeFromLeft (80)); - - #if JUCE_IOS - topButtonArea.removeFromRight (20); - connectButton.setBounds (topButtonArea.removeFromRight (80)); - #endif - - bounds.removeFromTop (20); - - auto orientation = Desktop::getInstance().getCurrentOrientation(); - - if (orientation == Desktop::DisplayOrientation::upright - || orientation == Desktop::DisplayOrientation::upsideDown) - { - auto brightnessControlBounds = bounds.removeFromBottom (getHeight() / 10); - - brightnessSlider.setSliderStyle (Slider::SliderStyle::LinearHorizontal); - brightnessLED.setBounds (brightnessControlBounds.removeFromLeft (getHeight() / 10)); - brightnessSlider.setBounds (brightnessControlBounds); - } - else - { - auto brightnessControlBounds = bounds.removeFromRight (getWidth() / 10); - - brightnessSlider.setSliderStyle (Slider::SliderStyle::LinearVertical); - brightnessLED.setBounds (brightnessControlBounds.removeFromTop (getWidth() / 10)); - brightnessSlider.setBounds (brightnessControlBounds); - } - - // lightpad component - auto sideLength = jmin (bounds.getWidth() - 40, bounds.getHeight() - 40); - lightpadComponent.centreWithSize (sideLength, sideLength); - } - - /** Overridden from TopologySource::Listener. Called when the topology changes */ - void topologyChanged() override - { - lightpadComponent.setVisible (false); - infoLabel.setVisible (true); - - // Reset the activeBlock object - if (activeBlock != nullptr) - detachActiveBlock(); - - // Get the array of currently connected Block objects from the PhysicalTopologySource - auto blocks = topologySource.getCurrentTopology().blocks; - - // Iterate over the array of Block objects - for (auto b : blocks) - { - // Find the first Lightpad - if (b->getType() == Block::Type::lightPadBlock) - { - activeBlock = b; - - // Register MainContentComponent as a listener to the touch surface - if (auto surface = activeBlock->getTouchSurface()) - surface->addListener (this); - - // Register MainContentComponent as a listener to any buttons - for (auto button : activeBlock->getButtons()) - button->addListener (this); - - // Get the LEDGrid object from the Lightpad and set its program to the program for the current mode - if (auto grid = activeBlock->getLEDGrid()) - { - // Work out scale factors to translate X and Y touches to LED indexes - scaleX = (float) (grid->getNumColumns() - 1) / (float) activeBlock->getWidth(); - scaleY = (float) (grid->getNumRows() - 1) / (float) activeBlock->getHeight(); - - setLEDProgram (*activeBlock); - } - - // Make the on screen Lightpad component visible - lightpadComponent.setVisible (true); - infoLabel.setVisible (false); - - break; - } - } - } - -private: - //============================================================================== - /** Overridden from TouchSurface::Listener. Called when a Touch is received on the Lightpad */ - void touchChanged (TouchSurface&, const TouchSurface::Touch& touch) override - { - // Translate X and Y touch events to LED indexes - auto xLed = roundToInt (touch.x * scaleX); - auto yLed = roundToInt (touch.y * scaleY); - - if (currentMode == colourPalette) - { - if (layout.setActiveColourForTouch (xLed, yLed)) - { - if (auto* colourPaletteProgram = getPaletteProgram()) - { - colourPaletteProgram->setGridFills (layout.numColumns, layout.numRows, layout.gridFillArray); - brightnessLED.setColour (layout.currentColour - .withBrightness (layout.currentColour == Colours::black ? 0.0f - : static_cast (brightnessSlider.getValue()))); - } - } - } - else if (currentMode == canvas) - { - drawLED ((uint32) xLed, (uint32) yLed, touch.z, layout.currentColour); - } - } - - /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is pressed */ - void buttonPressed (ControlButton&, Block::Timestamp) override {} - - /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is released */ - void buttonReleased (ControlButton&, Block::Timestamp) override - { - if (currentMode == canvas) - { - // Wait 500ms to see if there is a second press - if (! isTimerRunning()) - startTimer (500); - else - doublePress = true; - } - else if (currentMode == colourPalette) - { - // Switch to canvas mode and set the LEDGrid program - currentMode = canvas; - setLEDProgram (*activeBlock); - } - } - - void ledClicked (int x, int y, float z) override - { - drawLED ((uint32) x, (uint32) y, - z == 0.0f ? static_cast (brightnessSlider.getValue()) - : z * static_cast (brightnessSlider.getValue()), layout.currentColour); - } - - void timerCallback() override - { - if (doublePress) - { - clearLEDs(); - - // Reset the doublePress flag - doublePress = false; - } - else - { - // Switch to colour palette mode and set the LEDGrid program - currentMode = colourPalette; - setLEDProgram (*activeBlock); - } - - stopTimer(); - } - - /** Removes TouchSurface and ControlButton listeners and sets activeBlock to nullptr */ - void detachActiveBlock() - { - if (auto surface = activeBlock->getTouchSurface()) - surface->removeListener (this); - - for (auto button : activeBlock->getButtons()) - button->removeListener (this); - - activeBlock = nullptr; - } - - /** Sets the LEDGrid Program for the selected mode */ - void setLEDProgram (Block& block) - { - if (currentMode == canvas) - { - block.setProgram (std::make_unique(block)); - - // Redraw any previously drawn LEDs - redrawLEDs(); - } - else if (currentMode == colourPalette) - { - block.setProgram (std::make_unique (block)); - - // Setup the grid layout - if (auto* program = getPaletteProgram()) - program->setGridFills (layout.numColumns, layout.numRows, layout.gridFillArray); - } - } - - void clearLEDs() - { - if (auto* canvasProgram = getCanvasProgram()) - { - // Clear the LED grid - for (uint32 x = 0; x < 15; ++x) - { - for (uint32 y = 0; y < 15; ++ y) - { - canvasProgram->setLED (x, y, Colours::black); - lightpadComponent.setLEDColour ((int) x, (int) y, Colours::black); - } - } - - // Clear the ActiveLED array - activeLeds.clear(); - } - } - - /** Sets an LED on the Lightpad for a given touch co-ordinate and pressure */ - void drawLED (uint32 x0, uint32 y0, float z, Colour drawColour) - { - if (auto* canvasProgram = getCanvasProgram()) - { - // Check if the activeLeds array already contains an ActiveLED object for this LED - auto index = getLEDAt (x0, y0); - - // If the colour is black then just set the LED to black and return - if (drawColour == Colours::black) - { - if (index >= 0) - { - canvasProgram->setLED (x0, y0, Colours::black); - lightpadComponent.setLEDColour ((int) x0, (int) y0, Colours::black); - activeLeds.remove (index); - } - - return; - } - - // If there is no ActiveLED object for this LED then create one, - // add it to the array, set the LED on the Block and return - if (index < 0) - { - ActiveLED led; - led.x = x0; - led.y = y0; - led.colour = drawColour; - led.brightness = z; - - activeLeds.add (led); - canvasProgram->setLED (led.x, led.y, led.colour.withBrightness (led.brightness)); - - lightpadComponent.setLEDColour ((int) led.x, (int) led.y, led.colour.withBrightness (led.brightness)); - - return; - } - - // Get the ActiveLED object for this LED - auto currentLed = activeLeds.getReference (index); - - // If the LED colour is the same as the draw colour, add the brightnesses together. - // If it is different, blend the colours - if (currentLed.colour == drawColour) - currentLed.brightness = jmin (currentLed.brightness + z, 1.0f); - else - currentLed.colour = currentLed.colour.interpolatedWith (drawColour, z); - - - // Set the LED on the Block and change the ActiveLED object in the activeLeds array - if (canvasProgram != nullptr) - canvasProgram->setLED (currentLed.x, currentLed.y, currentLed.colour.withBrightness (currentLed.brightness)); - - lightpadComponent.setLEDColour ((int) currentLed.x, (int) currentLed.y, currentLed.colour.withBrightness (currentLed.brightness)); - - activeLeds.set (index, currentLed); - } - } - - /** Redraws the LEDs on the Lightpad from the activeLeds array */ - void redrawLEDs() - { - if (auto* canvasProgram = getCanvasProgram()) - { - // Iterate over the activeLeds array and set the LEDs on the Block - for (auto led : activeLeds) - { - canvasProgram->setLED (led.x, led.y, led.colour.withBrightness (led.brightness)); - lightpadComponent.setLEDColour ((int) led.x, (int) led.y, led.colour.withBrightness (led.brightness)); - } - } - } - - //============================================================================== - BitmapLEDProgram* getCanvasProgram() - { - if (activeBlock != nullptr) - return dynamic_cast (activeBlock->getProgram()); - - return nullptr; - } - - DrumPadGridProgram* getPaletteProgram() - { - if (activeBlock != nullptr) - return dynamic_cast (activeBlock->getProgram()); - - return nullptr; - } - - //============================================================================== - /** - A struct that represents an active LED on the Lightpad. - Has a position, colour and brightness. - */ - struct ActiveLED - { - uint32 x, y; - Colour colour; - float brightness; - - /** Returns true if this LED occupies the given co-ordinates */ - bool occupies (uint32 xPos, uint32 yPos) const - { - return xPos == x && yPos == y; - } - }; - - Array activeLeds; - - int getLEDAt (uint32 x, uint32 y) const - { - for (auto i = 0; i < activeLeds.size(); ++i) - if (activeLeds.getReference (i).occupies (x, y)) - return i; - - return -1; - } - - //============================================================================== - enum DisplayMode - { - colourPalette = 0, - canvas - }; - - DisplayMode currentMode = colourPalette; - - //============================================================================== - ColourGrid layout { 3, 3 }; - PhysicalTopologySource topologySource; - Block::Ptr activeBlock; - - float scaleX = 0.0f; - float scaleY = 0.0f; - - bool doublePress = false; - - Label infoLabel; - DrawableLightpadComponent lightpadComponent; - TextButton clearButton; - LEDComponent brightnessLED; - Slider brightnessSlider; - - #if JUCE_IOS - TextButton connectButton; - #endif - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlocksDrawingDemo) -}; diff --git a/examples/BLOCKS/BlocksMonitorDemo.h b/examples/BLOCKS/BlocksMonitorDemo.h deleted file mode 100644 index c1ca368e3a..0000000000 --- a/examples/BLOCKS/BlocksMonitorDemo.h +++ /dev/null @@ -1,1036 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE examples. - Copyright (c) 2020 - Raw Material Software Limited - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, - WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR - PURPOSE, ARE DISCLAIMED. - - ============================================================================== -*/ - -/******************************************************************************* - The block below describes the properties of this PIP. A PIP is a short snippet - of code that can be read by the Projucer and used to generate a JUCE project. - - BEGIN_JUCE_PIP_METADATA - - name: BlocksMonitorDemo - version: 1.0.0 - vendor: JUCE - website: http://juce.com - description: Application to monitor Blocks devices. - - dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, - juce_audio_processors, juce_audio_utils, juce_blocks_basics, - juce_core, juce_data_structures, juce_events, juce_graphics, - juce_gui_basics, juce_gui_extra - exporters: xcode_mac, vs2019, linux_make, xcode_iphone - - moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 - - type: Component - mainClass: BlocksMonitorDemo - - useLocalCopy: 1 - - END_JUCE_PIP_METADATA - -*******************************************************************************/ - -#pragma once - - -//============================================================================== -/** - Base class that renders a Block on the screen -*/ -class BlockComponent : public Component, - public SettableTooltipClient, - private TouchSurface::Listener, - private ControlButton::Listener, - private Timer -{ -public: - BlockComponent (Block::Ptr blockToUse) - : block (blockToUse) - { - updateStatsAndTooltip(); - - // Register BlockComponent as a listener to the touch surface - if (auto touchSurface = block->getTouchSurface()) - touchSurface->addListener (this); - - // Register BlockComponent as a listener to any buttons - for (auto button : block->getButtons()) - button->addListener (this); - - // If this is a Lightpad then set the grid program to be blank - if (block->getLEDGrid() != nullptr) - block->setProgram (std::make_unique(*block)); - - // If this is a Lightpad then redraw it at 25Hz - if (block->getType() == Block::lightPadBlock) - startTimerHz (25); - - // Make sure the component can't go offscreen if it is draggable - constrainer.setMinimumOnscreenAmounts (50, 50, 50, 50); - } - - ~BlockComponent() override - { - // Remove any listeners - if (auto touchSurface = block->getTouchSurface()) - touchSurface->removeListener (this); - - for (auto button : block->getButtons()) - button->removeListener (this); - } - - /** Called periodically to update the tooltip with information about the Block */ - void updateStatsAndTooltip() - { - // Get the battery level of this Block and inform any subclasses - auto batteryLevel = block->getBatteryLevel(); - handleBatteryLevelUpdate (batteryLevel); - - // Update the tooltip - setTooltip ("Name = " + block->getDeviceDescription() + "\n" - + "UID = " + String (block->uid) + "\n" - + "Serial number = " + block->serialNumber + "\n" - + "Battery level = " + String ((int) (batteryLevel * 100)) + "%" - + (block->isBatteryCharging() ? "++" - : "--")); - } - - /** Subclasses should override this to paint the Block object on the screen */ - void paint (Graphics&) override = 0; - - /** Subclasses can override this to receive button down events from the Block */ - virtual void handleButtonPressed (ControlButton::ButtonFunction, uint32) {} - - /** Subclasses can override this to receive button up events from the Block */ - virtual void handleButtonReleased (ControlButton::ButtonFunction, uint32) {} - - /** Subclasses can override this to receive touch events from the Block */ - virtual void handleTouchChange (TouchSurface::Touch) {} - - /** Subclasses can override this to battery level updates from the Block */ - virtual void handleBatteryLevelUpdate (float) {} - - /** The Block object that this class represents */ - Block::Ptr block; - - //============================================================================== - /** Returns an integer index corresponding to a physical position on the hardware - for each type of Control Block. */ - static int controlButtonFunctionToIndex (ControlButton::ButtonFunction f) - { - using CB = ControlButton; - - static Array map[] = - { - { CB::mode, CB::button0, CB::velocitySensitivity }, - { CB::volume, CB::button1, CB::glideSensitivity }, - { CB::scale, CB::button2, CB::slideSensitivity, CB::click }, - { CB::chord, CB::button3, CB::pressSensitivity, CB::snap }, - { CB::arp, CB::button4, CB::liftSensitivity, CB::back }, - { CB::sustain, CB::button5, CB::fixedVelocity, CB::playOrPause }, - { CB::octave, CB::button6, CB::glideLock, CB::record }, - { CB::love, CB::button7, CB::pianoMode, CB::learn }, - { CB::up }, - { CB::down } - }; - - for (int i = 0; i < numElementsInArray (map); ++i) - if (map[i].contains (f)) - return i; - - return -1; - } - - Point getOffsetForPort (Block::ConnectionPort port) - { - using e = Block::ConnectionPort::DeviceEdge; - - switch (rotation) - { - case 0: - { - switch (port.edge) - { - case e::north: - return { static_cast (port.index), 0.0f }; - case e::east: - return { static_cast (block->getWidth()), static_cast (port.index) }; - case e::south: - return { static_cast (port.index), static_cast (block->getHeight()) }; - case e::west: - return { 0.0f, static_cast (port.index) }; - default: - break; - } - - break; - } - case 90: - { - switch (port.edge) - { - case e::north: - return { 0.0f, static_cast (port.index) }; - case e::east: - return { static_cast (-1.0f - (float) port.index), static_cast (block->getWidth()) }; - case e::south: - return { static_cast (0.0f - (float) block->getHeight()), static_cast (port.index) }; - case e::west: - return { static_cast (-1.0f - (float) port.index), 0.0f }; - default: - break; - } - - break; - } - case 180: - { - switch (port.edge) - { - case e::north: - return { static_cast (-1.0f - (float) port.index), 0.0f }; - case e::east: - return { static_cast (0.0f - (float) block->getWidth()), static_cast (-1.0f - (float) port.index) }; - case e::south: - return { static_cast (-1.0f - (float) port.index), static_cast (0.0f - (float) block->getHeight()) }; - case e::west: - return { 0.0f, static_cast (-1.0f - (float) port.index) }; - default: - break; - } - - break; - } - case 270: - { - switch (port.edge) - { - case e::north: - return { 0.0f, static_cast (-1.0f - (float) port.index) }; - case e::east: - return { static_cast (port.index), static_cast (0 - (float) block->getWidth()) }; - case e::south: - return { static_cast (block->getHeight()), static_cast (-1.0f - (float) port.index) }; - case e::west: - return { static_cast (port.index), 0.0f }; - default: - break; - } - - break; - } - - default: - break; - } - - return {}; - } - - int rotation = 0; - Point topLeft = { 0.0f, 0.0f }; - -private: - /** Used to call repaint() periodically */ - void timerCallback() override { repaint(); } - - /** Overridden from TouchSurface::Listener */ - void touchChanged (TouchSurface&, const TouchSurface::Touch& t) override { handleTouchChange (t); } - - /** Overridden from ControlButton::Listener */ - void buttonPressed (ControlButton& b, Block::Timestamp t) override { handleButtonPressed (b.getType(), t); } - - /** Overridden from ControlButton::Listener */ - void buttonReleased (ControlButton& b, Block::Timestamp t) override { handleButtonReleased (b.getType(), t); } - - /** Overridden from MouseListener. Prepares the master Block component for dragging. */ - void mouseDown (const MouseEvent& e) override - { - if (block->isMasterBlock()) - componentDragger.startDraggingComponent (this, e); - } - - /** Overridden from MouseListener. Drags the master Block component */ - void mouseDrag (const MouseEvent& e) override - { - if (block->isMasterBlock()) - { - componentDragger.dragComponent (this, e, &constrainer); - getParentComponent()->resized(); - } - } - - ComponentDragger componentDragger; - ComponentBoundsConstrainer constrainer; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlockComponent) -}; - -//============================================================================== -/** - Class that renders a Lightpad on the screen -*/ -class LightpadComponent : public BlockComponent -{ -public: - LightpadComponent (Block::Ptr blockToUse) - : BlockComponent (blockToUse) - {} - - void paint (Graphics& g) override - { - auto r = getLocalBounds().toFloat(); - - // clip the drawing area to only draw in the block area - { - Path clipArea; - clipArea.addRoundedRectangle (r, r.getWidth() / 20.0f); - - g.reduceClipRegion (clipArea); - } - - // Fill a black square for the Lightpad - g.fillAll (Colours::black); - - // size ration between physical and on-screen blocks - Point ratio (r.getWidth() / (float) block->getWidth(), - r.getHeight() / (float) block->getHeight()); - - auto maxCircleSize = (float) block->getWidth() / 3.0f; - - // iterate over the list of current touches and draw them on the onscreen Block - for (auto touch : touches) - { - auto circleSize = touch.touch.z * maxCircleSize; - - Point touchPosition (touch.touch.x, - touch.touch.y); - - auto blob = Rectangle (circleSize, circleSize) - .withCentre (touchPosition) * ratio; - - ColourGradient cg (colourArray[touch.touch.index], blob.getCentreX(), blob.getCentreY(), - Colours::transparentBlack, blob.getRight(), blob.getBottom(), - true); - - g.setGradientFill (cg); - g.fillEllipse (blob); - } - } - - void handleTouchChange (TouchSurface::Touch touch) override { touches.updateTouch (touch); } - -private: - /** An Array of colours to use for touches */ - Array colourArray = { Colours::red, - Colours::blue, - Colours::green, - Colours::yellow, - Colours::white, - Colours::hotpink, - Colours::mediumpurple }; - - /** A list of current Touch events */ - TouchList touches; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LightpadComponent) -}; - - -//============================================================================== -/** - Class that renders a Control Block on the screen -*/ -class ControlBlockComponent : public BlockComponent -{ -public: - ControlBlockComponent (Block::Ptr blockToUse) - : BlockComponent (blockToUse), - numLeds (block->getLEDRow()->getNumLEDs()) - { - addAndMakeVisible (roundedRectangleButton); - - // Display the battery level on the LEDRow - auto numLedsToTurnOn = static_cast ((float) numLeds * block->getBatteryLevel()); - - // add LEDs - for (auto i = 0; i < numLeds; ++i) - { - auto ledComponent = new LEDComponent(); - ledComponent->setOnState (i < numLedsToTurnOn); - - addAndMakeVisible (leds.add (ledComponent)); - } - - previousNumLedsOn = numLedsToTurnOn; - - // add buttons - for (auto i = 0; i < 8; ++i) - addAndMakeVisible (circleButtons[i]); - } - - void resized() override - { - auto r = getLocalBounds().reduced (10); - - auto rowHeight = r.getHeight() / 5; - auto ledWidth = (r.getWidth() - 70) / numLeds; - auto buttonWidth = (r.getWidth() - 40) / 5; - - auto row = r; - - auto ledRow = row.removeFromTop (rowHeight) .withSizeKeepingCentre (r.getWidth(), ledWidth); - auto buttonRow1 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth); - auto buttonRow2 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth); - - for (auto* led : leds) - { - led->setBounds (ledRow.removeFromLeft (ledWidth).reduced (2)); - ledRow.removeFromLeft (5); - } - - for (auto i = 0; i < 5; ++i) - { - circleButtons[i].setBounds (buttonRow1.removeFromLeft (buttonWidth).reduced (2)); - buttonRow1.removeFromLeft (10); - } - - for (auto i = 5; i < 8; ++i) - { - circleButtons[i].setBounds (buttonRow2.removeFromLeft (buttonWidth).reduced (2)); - buttonRow2.removeFromLeft (10); - } - - roundedRectangleButton.setBounds (buttonRow2); - } - - void paint (Graphics& g) override - { - auto r = getLocalBounds().toFloat(); - - // Fill a black rectangle for the Control Block - g.setColour (Colours::black); - g.fillRoundedRectangle (r, r.getWidth() / 20.0f); - } - - void handleButtonPressed (ControlButton::ButtonFunction function, uint32) override - { - displayButtonInteraction (controlButtonFunctionToIndex (function), true); - } - - void handleButtonReleased (ControlButton::ButtonFunction function, uint32) override - { - displayButtonInteraction (controlButtonFunctionToIndex (function), false); - } - - void handleBatteryLevelUpdate (float batteryLevel) override - { - // Update the number of LEDs that are on to represent the battery level - auto numLedsOn = static_cast ((float) numLeds * batteryLevel); - - if (numLedsOn != previousNumLedsOn) - for (auto i = 0; i < numLeds; ++i) - leds.getUnchecked (i)->setOnState (i < numLedsOn); - - previousNumLedsOn = numLedsOn; - repaint(); - } - -private: - //============================================================================== - /** - Base class that renders a Control Block button - */ - struct ControlBlockSubComponent : public Component, - public TooltipClient - { - ControlBlockSubComponent (Colour componentColourToUse) - : componentColour (componentColourToUse) - {} - - /** Subclasses should override this to paint the button on the screen */ - void paint (Graphics&) override = 0; - - /** Sets the colour of the button */ - void setColour (Colour c) { componentColour = c; } - - /** Sets the on state of the button */ - void setOnState (bool isOn) - { - onState = isOn; - repaint(); - } - - /** Returns the Control Block tooltip */ - String getTooltip() override - { - for (Component* comp = this; comp != nullptr; comp = comp->getParentComponent()) - if (auto* sttc = dynamic_cast (comp)) - return sttc->getTooltip(); - - return {}; - } - - //============================================================================== - Colour componentColour; - bool onState = false; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlBlockSubComponent) - }; - - /** - Class that renders a Control Block LED on the screen - */ - struct LEDComponent : public ControlBlockSubComponent - { - LEDComponent() : ControlBlockSubComponent (Colours::green) {} - - void paint (Graphics& g) override - { - g.setColour (componentColour.withAlpha (onState ? 1.0f : 0.2f)); - g.fillEllipse (getLocalBounds().toFloat()); - } - }; - - /** - Class that renders a Control Block single circular button on the screen - */ - struct CircleButtonComponent : public ControlBlockSubComponent - { - CircleButtonComponent() : ControlBlockSubComponent (Colours::blue) {} - - void paint (Graphics& g) override - { - g.setColour (componentColour.withAlpha (onState ? 1.0f : 0.2f)); - g.fillEllipse (getLocalBounds().toFloat()); - } - }; - - /** - Class that renders a Control Block rounded rectangular button containing two buttons - on the screen - */ - struct RoundedRectangleButtonComponent : public ControlBlockSubComponent - { - RoundedRectangleButtonComponent() : ControlBlockSubComponent (Colours::blue) {} - - void paint (Graphics& g) override - { - auto r = getLocalBounds().toFloat(); - - g.setColour (componentColour.withAlpha (0.2f)); - g.fillRoundedRectangle (r.toFloat(), 20.0f); - g.setColour (componentColour.withAlpha (1.0f)); - - // is a button pressed? - if (doubleButtonOnState[0] || doubleButtonOnState[1]) - { - auto semiButtonWidth = r.getWidth() / 2.0f; - - auto semiButtonBounds = r.withWidth (semiButtonWidth) - .withX (doubleButtonOnState[1] ? semiButtonWidth : 0) - .reduced (5.0f, 2.0f); - - g.fillEllipse (semiButtonBounds); - } - } - - void setPressedState (bool isPressed, int button) - { - doubleButtonOnState[button] = isPressed; - repaint(); - } - - private: - bool doubleButtonOnState[2] = { false, false }; - }; - - /** Displays a button press or release interaction for a button at a given index */ - void displayButtonInteraction (int buttonIndex, bool isPressed) - { - if (! isPositiveAndBelow (buttonIndex, 10)) - return; - - if (buttonIndex >= 8) - roundedRectangleButton.setPressedState (isPressed, buttonIndex == 8); - else - circleButtons[buttonIndex].setOnState (isPressed); - } - - //============================================================================== - int numLeds; - OwnedArray leds; - CircleButtonComponent circleButtons[8]; - RoundedRectangleButtonComponent roundedRectangleButton; - int previousNumLedsOn; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlBlockComponent) -}; - -//============================================================================== -/** - The main component where the Block components will be displayed -*/ -class BlocksMonitorDemo : public Component, - public TopologySource::Listener, - private Timer -{ -public: - BlocksMonitorDemo() - { - noBlocksLabel.setText ("No BLOCKS connected...", dontSendNotification); - noBlocksLabel.setJustificationType (Justification::centred); - - zoomOutButton.setButtonText ("+"); - zoomOutButton.onClick = [this] { blockUnitInPixels = (int) ((float) blockUnitInPixels * 1.05f); resized(); }; - zoomOutButton.setAlwaysOnTop (true); - - zoomInButton.setButtonText ("-"); - zoomInButton.onClick = [this] { blockUnitInPixels = (int) ((float) blockUnitInPixels * 0.95f); resized(); }; - zoomInButton.setAlwaysOnTop (true); - - // Register BlocksMonitorDemo as a listener to the PhysicalTopologySource object - topologySource.addListener (this); - - startTimer (10000); - - addAndMakeVisible (noBlocksLabel); - addAndMakeVisible (zoomOutButton); - addAndMakeVisible (zoomInButton); - - #if JUCE_IOS - connectButton.setButtonText ("Connect"); - connectButton.onClick = [] { BluetoothMidiDevicePairingDialogue::open(); }; - connectButton.setAlwaysOnTop (true); - addAndMakeVisible (connectButton); - #endif - - setSize (600, 600); - - topologyChanged(); - } - - ~BlocksMonitorDemo() override - { - topologySource.removeListener (this); - } - - void paint (Graphics&) override {} - - void resized() override - { - #if JUCE_IOS - connectButton.setBounds (getRight() - 100, 20, 80, 30); - #endif - - noBlocksLabel.setVisible (false); - auto numBlockComponents = blockComponents.size(); - - // If there are no currently connected Blocks then display some text on the screen - if (masterBlockComponent == nullptr || numBlockComponents == 0) - { - noBlocksLabel.setVisible (true); - noBlocksLabel.setBounds (0, (getHeight() / 2) - 50, getWidth(), 100); - return; - } - - zoomOutButton.setBounds (10, getHeight() - 40, 40, 30); - zoomInButton.setBounds (zoomOutButton.getRight(), zoomOutButton.getY(), 40, 30); - - if (isInitialResized) - { - // Work out the area needed in terms of Block units - Rectangle maxArea; - for (auto blockComponent : blockComponents) - { - auto topLeft = blockComponent->topLeft; - auto rotation = blockComponent->rotation; - auto blockSize = 0; - - if (rotation == 180) - blockSize = blockComponent->block->getWidth(); - else if (rotation == 90) - blockSize = blockComponent->block->getHeight(); - - if (topLeft.x - (float) blockSize < maxArea.getX()) - maxArea.setX (topLeft.x - (float) blockSize); - - blockSize = 0; - if (rotation == 0) - blockSize = blockComponent->block->getWidth(); - else if (rotation == 270) - blockSize = blockComponent->block->getHeight(); - - if (topLeft.x + (float) blockSize > maxArea.getRight()) - maxArea.setWidth (topLeft.x + (float) blockSize); - - blockSize = 0; - if (rotation == 180) - blockSize = blockComponent->block->getHeight(); - else if (rotation == 270) - blockSize = blockComponent->block->getWidth(); - - if (topLeft.y - (float) blockSize < maxArea.getY()) - maxArea.setY (topLeft.y - (float) blockSize); - - blockSize = 0; - if (rotation == 0) - blockSize = blockComponent->block->getHeight(); - else if (rotation == 90) - blockSize = blockComponent->block->getWidth(); - - if (topLeft.y + (float) blockSize > maxArea.getBottom()) - maxArea.setHeight (topLeft.y + (float) blockSize); - } - - auto totalWidth = std::abs (maxArea.getX()) + maxArea.getWidth(); - auto totalHeight = std::abs (maxArea.getY()) + maxArea.getHeight(); - - blockUnitInPixels = static_cast (jmin (((float) getHeight() / totalHeight) - 50, ((float) getWidth() / totalWidth) - 50)); - - masterBlockComponent->centreWithSize (masterBlockComponent->block->getWidth() * blockUnitInPixels, - masterBlockComponent->block->getHeight() * blockUnitInPixels); - - isInitialResized = false; - } - else - { - masterBlockComponent->setSize (masterBlockComponent->block->getWidth() * blockUnitInPixels, masterBlockComponent->block->getHeight() * blockUnitInPixels); - } - - for (auto blockComponent : blockComponents) - { - if (blockComponent == masterBlockComponent) - continue; - - blockComponent->setBounds (masterBlockComponent->getX() + static_cast (blockComponent->topLeft.x * (float) blockUnitInPixels), - masterBlockComponent->getY() + static_cast (blockComponent->topLeft.y * (float) blockUnitInPixels), - blockComponent->block->getWidth() * blockUnitInPixels, - blockComponent->block->getHeight() * blockUnitInPixels); - - if (blockComponent->rotation != 0) - blockComponent->setTransform (AffineTransform::rotation (static_cast (degreesToRadians (blockComponent->rotation)), - static_cast (blockComponent->getX()), - static_cast (blockComponent->getY()))); - } - } - - /** Overridden from TopologySource::Listener, called when the topology changes */ - void topologyChanged() override - { - // Clear the array of Block components - blockComponents.clear(); - masterBlockComponent = nullptr; - - // Get the current topology - auto topology = topologySource.getCurrentTopology(); - - // Create a BlockComponent object for each Block object and store a pointer to the master - for (auto& block : topology.blocks) - { - if (auto* blockComponent = createBlockComponent (block)) - { - addAndMakeVisible (blockComponents.add (blockComponent)); - - if (blockComponent->block->isMasterBlock()) - masterBlockComponent = blockComponent; - } - } - - // Must have a master Block! - if (topology.blocks.size() > 0) - jassert (masterBlockComponent != nullptr); - - // Calculate the relative position and rotation for each Block - positionBlocks (topology); - - // Update the display - isInitialResized = true; - resized(); - } - -private: - /** Creates a BlockComponent object for a new Block and adds it to the content component */ - BlockComponent* createBlockComponent (Block::Ptr newBlock) - { - auto type = newBlock->getType(); - - if (type == Block::lightPadBlock) - return new LightpadComponent (newBlock); - - if (type == Block::loopBlock || type == Block::liveBlock - || type == Block::touchBlock || type == Block::developerControlBlock) - return new ControlBlockComponent (newBlock); - - // Should only be connecting a Lightpad or Control Block! - jassertfalse; - return nullptr; - } - - /** Periodically updates the displayed BlockComponent tooltips */ - void timerCallback() override - { - for (auto c : blockComponents) - c->updateStatsAndTooltip(); - } - - /** Calculates the position and rotation of each connected Block relative to the master Block */ - void positionBlocks (BlockTopology topology) - { - if (masterBlockComponent == nullptr) - return; - - Array blocksConnectedToMaster; - - auto maxDelta = std::numeric_limits::max(); - auto maxLoops = 50; - - // Store all the connections to the master Block - Array masterBlockConnections; - for (auto connection : topology.connections) - if (connection.device1 == masterBlockComponent->block->uid || connection.device2 == masterBlockComponent->block->uid) - masterBlockConnections.add (connection); - - // Position all the Blocks that are connected to the master Block - while (maxDelta > 0.001f && --maxLoops) - { - maxDelta = 0.0f; - - // Loop through each connection on the master Block - for (auto connection : masterBlockConnections) - { - // Work out whether the master Block is device 1 or device 2 in the BlockDeviceConnection struct - bool isDevice1 = true; - if (masterBlockComponent->block->uid == connection.device2) - isDevice1 = false; - - // Get the connected ports - auto masterPort = isDevice1 ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2; - auto otherPort = isDevice1 ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1; - - for (auto otherBlockComponent : blockComponents) - { - // Get the other block - if (otherBlockComponent->block->uid == (isDevice1 ? connection.device2 : connection.device1)) - { - blocksConnectedToMaster.addIfNotAlreadyThere (otherBlockComponent); - - // Get the rotation of the other Block relative to the master Block - otherBlockComponent->rotation = getRotation (masterPort.edge, otherPort.edge); - - // Get the offsets for the connected ports - auto masterBlockOffset = masterBlockComponent->getOffsetForPort (masterPort); - auto otherBlockOffset = otherBlockComponent->topLeft + otherBlockComponent->getOffsetForPort (otherPort); - - // Work out the distance between the two connected ports - auto delta = masterBlockOffset - otherBlockOffset; - - // Move the other block half the distance to the connection - otherBlockComponent->topLeft += delta / 2.0f; - - // Work out whether we are close enough for the loop to end - maxDelta = jmax (maxDelta, std::abs (delta.x), std::abs (delta.y)); - } - } - } - } - - // Check if there are any Blocks that have not been positioned yet - Array unpositionedBlocks; - - for (auto blockComponent : blockComponents) - if (blockComponent != masterBlockComponent && ! blocksConnectedToMaster.contains (blockComponent)) - unpositionedBlocks.add (blockComponent); - - if (unpositionedBlocks.size() > 0) - { - // Reset the loop conditions - maxDelta = std::numeric_limits::max(); - maxLoops = 50; - - // Position all the remaining Blocks - while (maxDelta > 0.001f && --maxLoops) - { - maxDelta = 0.0f; - - // Loop through each unpositioned Block - for (auto blockComponent : unpositionedBlocks) - { - // Store all the connections to this Block - Array blockConnections; - for (auto connection : topology.connections) - if (connection.device1 == blockComponent->block->uid || connection.device2 == blockComponent->block->uid) - blockConnections.add (connection); - - // Loop through each connection on this Block - for (auto connection : blockConnections) - { - // Work out whether this Block is device 1 or device 2 in the BlockDeviceConnection struct - auto isDevice1 = true; - if (blockComponent->block->uid == connection.device2) - isDevice1 = false; - - // Get the connected ports - auto thisPort = isDevice1 ? connection.connectionPortOnDevice1 : connection.connectionPortOnDevice2; - auto otherPort = isDevice1 ? connection.connectionPortOnDevice2 : connection.connectionPortOnDevice1; - - // Get the other Block - for (auto otherBlockComponent : blockComponents) - { - if (otherBlockComponent->block->uid == (isDevice1 ? connection.device2 : connection.device1)) - { - // Get the rotation - auto rotation = getRotation (otherPort.edge, thisPort.edge) + otherBlockComponent->rotation; - if (rotation > 360) - rotation -= 360; - - blockComponent->rotation = rotation; - - // Get the offsets for the connected ports - auto otherBlockOffset = (otherBlockComponent->topLeft + otherBlockComponent->getOffsetForPort (otherPort)); - auto thisBlockOffset = (blockComponent->topLeft + blockComponent->getOffsetForPort (thisPort)); - - // Work out the distance between the two connected ports - auto delta = otherBlockOffset - thisBlockOffset; - - // Move this block half the distance to the connection - blockComponent->topLeft += delta / 2.0f; - - // Work out whether we are close enough for the loop to end - maxDelta = jmax (maxDelta, std::abs (delta.x), std::abs (delta.y)); - } - } - } - } - } - } - } - - /** Returns a rotation in degrees based on the connected edges of two blocks */ - int getRotation (Block::ConnectionPort::DeviceEdge staticEdge, Block::ConnectionPort::DeviceEdge rotatedEdge) - { - using edge = Block::ConnectionPort::DeviceEdge; - - switch (staticEdge) - { - case edge::north: - { - switch (rotatedEdge) - { - case edge::north: - return 180; - case edge::south: - return 0; - case edge::east: - return 90; - case edge::west: - return 270; - default: - break; - } - - break; - } - case edge::south: - { - switch (rotatedEdge) - { - case edge::north: - return 0; - case edge::south: - return 180; - case edge::east: - return 270; - case edge::west: - return 90; - default: - break; - } - - break; - } - case edge::east: - { - switch (rotatedEdge) - { - case edge::north: - return 270; - case edge::south: - return 90; - case edge::east: - return 180; - case edge::west: - return 0; - default: - break; - } - - break; - } - - case edge::west: - { - switch (rotatedEdge) - { - case edge::north: - return 90; - case edge::south: - return 270; - case edge::east: - return 0; - case edge::west: - return 180; - default: - break; - } - - break; - } - - default: - break; - } - - return 0; - } - - //============================================================================== - TooltipWindow tooltipWindow; - - PhysicalTopologySource topologySource; - OwnedArray blockComponents; - BlockComponent* masterBlockComponent = nullptr; - - Label noBlocksLabel; - - TextButton zoomOutButton; - TextButton zoomInButton; - - int blockUnitInPixels; - bool isInitialResized; - - #if JUCE_IOS - TextButton connectButton; - #endif - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlocksMonitorDemo) -}; diff --git a/examples/BLOCKS/BlocksSynthDemo.h b/examples/BLOCKS/BlocksSynthDemo.h deleted file mode 100644 index 995410381e..0000000000 --- a/examples/BLOCKS/BlocksSynthDemo.h +++ /dev/null @@ -1,866 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE examples. - Copyright (c) 2020 - Raw Material Software Limited - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, - WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR - PURPOSE, ARE DISCLAIMED. - - ============================================================================== -*/ - -/******************************************************************************* - The block below describes the properties of this PIP. A PIP is a short snippet - of code that can be read by the Projucer and used to generate a JUCE project. - - BEGIN_JUCE_PIP_METADATA - - name: BlocksSynthDemo - version: 1.0.0 - vendor: JUCE - website: http://juce.com - description: Blocks synthesiser application. - - dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, - juce_audio_processors, juce_audio_utils, juce_blocks_basics, - juce_core, juce_data_structures, juce_events, juce_graphics, - juce_gui_basics, juce_gui_extra - exporters: xcode_mac, vs2019, linux_make, xcode_iphone - - moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 - - type: Component - mainClass: BlocksSynthDemo - - useLocalCopy: 1 - - END_JUCE_PIP_METADATA - -*******************************************************************************/ - -#pragma once - - -//============================================================================== -/** - Base class for oscillators -*/ -class OscillatorBase : public SynthesiserVoice -{ -public: - OscillatorBase() - { - amplitude.reset (getSampleRate(), 0.1); - phaseIncrement.reset (getSampleRate(), 0.1); - } - - void startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int) override - { - frequency = MidiMessage::getMidiNoteInHertz (midiNoteNumber); - phaseIncrement.setTargetValue (((MathConstants::twoPi) * frequency) / sampleRate); - amplitude.setTargetValue (velocity); - - // Store the initial note and work out the maximum frequency deviations for pitch bend - initialNote = midiNoteNumber; - maxFreq = MidiMessage::getMidiNoteInHertz (initialNote + 4) - frequency; - minFreq = frequency - MidiMessage::getMidiNoteInHertz (initialNote - 4); - } - - void stopNote (float, bool) override - { - clearCurrentNote(); - amplitude.setTargetValue (0.0); - } - - void pitchWheelMoved (int newValue) override - { - // Change the phase increment based on pitch bend amount - auto frequencyOffset = ((newValue > 0 ? maxFreq : minFreq) * (newValue / 127.0)); - phaseIncrement.setTargetValue (((MathConstants::twoPi) * (frequency + frequencyOffset)) / sampleRate); - } - - void controllerMoved (int, int) override {} - - void channelPressureChanged (int newChannelPressureValue) override - { - // Set the amplitude based on pressure value - amplitude.setTargetValue (newChannelPressureValue / 127.0); - } - - void renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) override - { - while (--numSamples >= 0) - { - auto output = getSample() * amplitude.getNextValue(); - - for (auto i = outputBuffer.getNumChannels(); --i >= 0;) - outputBuffer.addSample (i, startSample, static_cast (output)); - - ++startSample; - } - } - - using SynthesiserVoice::renderNextBlock; - - /** Returns the next sample */ - double getSample() - { - auto output = renderWaveShape (phasePos); - - phasePos += phaseIncrement.getNextValue(); - - if (phasePos > MathConstants::twoPi) - phasePos -= MathConstants::twoPi; - - return output; - } - - /** Subclasses should override this to say whether they can play the given sound */ - bool canPlaySound (SynthesiserSound*) override = 0; - - /** Subclasses should override this to render a waveshape */ - virtual double renderWaveShape (const double currentPhase) = 0; - -private: - SmoothedValue amplitude, phaseIncrement; - - double frequency = 0.0; - double phasePos = 0.0; - double sampleRate = 44100.0; - - int initialNote = 0; - double maxFreq = 0.0, minFreq = 0.0; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscillatorBase) -}; - -//============================================================================== -/** - Sine sound struct - applies to MIDI channel 1 -*/ -struct SineSound : public SynthesiserSound -{ - SineSound () {} - - bool appliesToNote (int) override { return true; } - - bool appliesToChannel (int midiChannel) override { return (midiChannel == 1); } - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SineSound) -}; - -/** - Sine voice struct that renders a sin waveshape -*/ -struct SineVoice : public OscillatorBase -{ - SineVoice() {} - - bool canPlaySound (SynthesiserSound* sound) override { return dynamic_cast (sound) != nullptr; } - - double renderWaveShape (const double currentPhase) override { return sin (currentPhase); } - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SineVoice) -}; - -//============================================================================== -/** - Square sound struct - applies to MIDI channel 2 -*/ -struct SquareSound : public SynthesiserSound -{ - SquareSound() {} - - bool appliesToNote (int) override { return true; } - - bool appliesToChannel (int midiChannel) override { return (midiChannel == 2); } - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SquareSound) -}; - -/** - Square voice struct that renders a square waveshape -*/ -struct SquareVoice : public OscillatorBase -{ - SquareVoice() {} - - bool canPlaySound (SynthesiserSound* sound) override { return dynamic_cast (sound) != nullptr; } - - double renderWaveShape (const double currentPhase) override { return (currentPhase < MathConstants::pi ? 0.0 : 1.0); } - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SquareVoice) -}; - -//============================================================================== -/** - Sawtooth sound - applies to MIDI channel 3 -*/ -struct SawSound : public SynthesiserSound -{ - SawSound() {} - - bool appliesToNote (int) override { return true; } - - bool appliesToChannel (int midiChannel) override { return (midiChannel == 3); } - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SawSound) -}; - -/** - Sawtooth voice that renders a sawtooth waveshape -*/ -struct SawVoice : public OscillatorBase -{ - SawVoice() {} - - bool canPlaySound (SynthesiserSound* sound) override { return dynamic_cast (sound) != nullptr; } - - double renderWaveShape (const double currentPhase) override { return (1.0 / MathConstants::pi) * currentPhase - 1.0; } - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SawVoice) -}; - -//============================================================================== -/** - Triangle sound - applies to MIDI channel 4 -*/ -struct TriangleSound : public SynthesiserSound -{ - TriangleSound() {} - - bool appliesToNote (int) override { return true; } - - bool appliesToChannel (int midiChannel) override { return (midiChannel == 4); } - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TriangleSound) -}; - -/** - Triangle voice that renders a triangle waveshape -*/ -struct TriangleVoice : public OscillatorBase -{ - TriangleVoice() {} - - bool canPlaySound (SynthesiserSound* sound) override { return dynamic_cast (sound) != nullptr; } - - double renderWaveShape (const double currentPhase) override - { - return currentPhase < MathConstants::pi ? -1.0 + (2.0 / MathConstants::pi) * currentPhase - : 3.0 - (2.0 / MathConstants::pi) * currentPhase; - } - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TriangleVoice) -}; - -//============================================================================== -/** - Class to handle the Audio functionality -*/ -class Audio : public AudioIODeviceCallback -{ -public: - Audio() - { - // Set up the audio device manager - #ifndef JUCE_DEMO_RUNNER - audioDeviceManager.initialiseWithDefaultDevices (0, 2); - #endif - - audioDeviceManager.addAudioCallback (this); - - // Set up the synthesiser and add each of the waveshapes - synthesiser.clearVoices(); - synthesiser.clearSounds(); - - synthesiser.addVoice (new SineVoice()); - synthesiser.addVoice (new SquareVoice()); - synthesiser.addVoice (new SawVoice()); - synthesiser.addVoice (new TriangleVoice()); - - synthesiser.addSound (new SineSound()); - synthesiser.addSound (new SquareSound()); - synthesiser.addSound (new SawSound()); - synthesiser.addSound (new TriangleSound()); - } - - ~Audio() override - { - audioDeviceManager.removeAudioCallback (this); - } - - /** Audio callback */ - void audioDeviceIOCallback (const float** /*inputChannelData*/, int /*numInputChannels*/, - float** outputChannelData, int numOutputChannels, int numSamples) override - { - AudioBuffer sampleBuffer (outputChannelData, numOutputChannels, numSamples); - sampleBuffer.clear(); - - synthesiser.renderNextBlock (sampleBuffer, MidiBuffer(), 0, numSamples); - } - - void audioDeviceAboutToStart (AudioIODevice* device) override - { - synthesiser.setCurrentPlaybackSampleRate (device->getCurrentSampleRate()); - } - - void audioDeviceStopped() override {} - - /** Called to turn a synthesiser note on */ - void noteOn (int channel, int noteNum, float velocity) - { - synthesiser.noteOn (channel, noteNum, velocity); - } - - /** Called to turn a synthesiser note off */ - void noteOff (int channel, int noteNum, float velocity) - { - synthesiser.noteOff (channel, noteNum, velocity, false); - } - - /** Called to turn all synthesiser notes off */ - void allNotesOff() - { - for (auto i = 1; i < 5; ++i) - synthesiser.allNotesOff (i, false); - } - - /** Send pressure change message to synthesiser */ - void pressureChange (int channel, float newPressure) - { - synthesiser.handleChannelPressure (channel, static_cast (newPressure * 127)); - } - - /** Send pitch change message to synthesiser */ - void pitchChange (int channel, float pitchChange) - { - synthesiser.handlePitchWheel (channel, static_cast (pitchChange * 127)); - } - -private: - #ifndef JUCE_DEMO_RUNNER - AudioDeviceManager audioDeviceManager; - #else - AudioDeviceManager& audioDeviceManager { getSharedAudioDeviceManager (0, 2) }; - #endif - Synthesiser synthesiser; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Audio) -}; - -//============================================================================== -/** - A Program to draw moving waveshapes onto the LEDGrid -*/ -class WaveshapeProgram : public Block::Program -{ -public: - WaveshapeProgram (Block& b) : Program (b) {} - - /** Sets the waveshape type to display on the grid */ - void setWaveshapeType (uint8 type) - { - block.setDataByte (0, type); - } - - /** Generates the Y coordinates for 1.5 cycles of each of the four waveshapes and stores them - at the correct offsets in the shared data heap. */ - void generateWaveshapes() - { - uint8 sineWaveY[45]; - uint8 squareWaveY[45]; - uint8 sawWaveY[45]; - uint8 triangleWaveY[45]; - - // Set current phase position to 0 and work out the required phase increment for one cycle - auto currentPhase = 0.0; - auto phaseInc = (1.0 / 30.0) * MathConstants::twoPi; - - for (auto x = 0; x < 30; ++x) - { - // Scale and offset the sin output to the Lightpad display - auto sineOutput = std::sin (currentPhase); - sineWaveY[x] = static_cast (roundToInt ((sineOutput * 6.5) + 7.0)); - - // Square wave output, set flags for when vertical line should be drawn - if (currentPhase < MathConstants::pi) - { - if (x == 0) - squareWaveY[x] = 255; - else - squareWaveY[x] = 1; - } - else - { - if (x > 0 && squareWaveY[x - 1] == 1) - squareWaveY[x - 1] = 255; - - squareWaveY[x] = 13; - } - - // Saw wave output, set flags for when vertical line should be drawn - sawWaveY[x] = 14 - ((x / 2) % 15); - - if (sawWaveY[x] == 0 && sawWaveY[x - 1] != 255) - sawWaveY[x] = 255; - - // Triangle wave output - triangleWaveY[x] = x < 15 ? static_cast (x) : static_cast (14 - (x % 15)); - - // Add half cycle to end of array so it loops correctly - if (x < 15) - { - sineWaveY[x + 30] = sineWaveY[x]; - squareWaveY[x + 30] = squareWaveY[x]; - sawWaveY[x + 30] = sawWaveY[x]; - triangleWaveY[x + 30] = triangleWaveY[x]; - } - - // Increment the current phase - currentPhase += phaseInc; - } - - // Store the values for each of the waveshapes at the correct offsets in the shared data heap - for (uint8 i = 0; i < 45; ++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]); - } - } - - String getLittleFootProgram() override - { - return R"littlefoot( - - #heapsize: 256 - - int yOffset; - - void drawLEDCircle (int x0, int y0) - { - blendPixel (0xffff0000, x0, y0); - - int minLedIndex = 0; - int maxLedIndex = 14; - - blendPixel (0xff660000, min (x0 + 1, maxLedIndex), y0); - blendPixel (0xff660000, max (x0 - 1, minLedIndex), y0); - blendPixel (0xff660000, x0, min (y0 + 1, maxLedIndex)); - blendPixel (0xff660000, x0, max (y0 - 1, minLedIndex)); - - blendPixel (0xff1a0000, min (x0 + 1, maxLedIndex), min (y0 + 1, maxLedIndex)); - blendPixel (0xff1a0000, min (x0 + 1, maxLedIndex), max (y0 - 1, minLedIndex)); - blendPixel (0xff1a0000, max (x0 - 1, minLedIndex), min (y0 + 1, maxLedIndex)); - blendPixel (0xff1a0000, max (x0 - 1, minLedIndex), max (y0 - 1, minLedIndex)); - } - - void repaint() - { - // Clear LEDs to black - fillRect (0xff000000, 0, 0, 15, 15); - - // Get the waveshape type - int type = getHeapByte (0); - - // Calculate the heap offset - int offset = 1 + (type * 45) + yOffset; - - for (int x = 0; x < 15; ++x) - { - // Get the corresponding Y coordinate for each X coordinate - int y = getHeapByte (offset + x); - - // Draw a vertical line if flag is set or draw an LED circle - if (y == 255) - { - for (int i = 0; i < 15; ++i) - drawLEDCircle (x, i); - } - else if (x % 2 == 0) - { - drawLEDCircle (x, y); - } - } - - // Increment and wrap the Y offset to draw a 'moving' waveshape - if (++yOffset == 30) - yOffset = 0; - } - - )littlefoot"; - } - -private: - //============================================================================== - /** Shared data heap is laid out as below. There is room for the waveshape type and - the Y coordinates for 1.5 cycles of each of the four waveshapes. */ - - static constexpr uint32 waveshapeType = 0; // 1 byte - static constexpr uint32 sineWaveOffset = 1; // 1 byte * 45 - static constexpr uint32 squareWaveOffset = 46; // 1 byte * 45 - static constexpr uint32 sawWaveOffset = 91; // 1 byte * 45 - static constexpr uint32 triangleWaveOffset = 136; // 1 byte * 45 - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaveshapeProgram) -}; - -//============================================================================== -/** - A struct that handles the setup and layout of the DrumPadGridProgram -*/ -struct SynthGrid -{ - SynthGrid (int cols, int rows) - : numColumns (cols), - numRows (rows) - { - constructGridFillArray(); - } - - /** Creates a GridFill object for each pad in the grid and sets its colour - and fill before adding it to an array of GridFill objects - */ - void constructGridFillArray() - { - gridFillArray.clear(); - - for (auto i = 0; i < numRows; ++i) - { - for (auto j = 0; j < numColumns; ++j) - { - DrumPadGridProgram::GridFill fill; - - auto padNum = (i * 5) + j; - - fill.colour = notes.contains (padNum) ? baseGridColour - : tonics.contains (padNum) ? Colours::white - : Colours::black; - fill.fillType = DrumPadGridProgram::GridFill::FillType::gradient; - gridFillArray.add (fill); - } - } - } - - int getNoteNumberForPad (int x, int y) const - { - auto xIndex = x / 3; - auto yIndex = y / 3; - - return 60 + ((4 - yIndex) * 5) + xIndex; - } - - //============================================================================== - int numColumns, numRows; - float width, height; - - Array gridFillArray; - Colour baseGridColour = Colours::green; - Colour touchColour = Colours::red; - - Array tonics = { 4, 12, 20 }; - Array notes = { 1, 3, 6, 7, 9, 11, 14, 15, 17, 19, 22, 24 }; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SynthGrid) -}; - -//============================================================================== -/** - The main component -*/ -class BlocksSynthDemo : public Component, - public TopologySource::Listener, - private TouchSurface::Listener, - private ControlButton::Listener, - private Timer -{ -public: - BlocksSynthDemo() - { - // Register BlocksSynthDemo as a listener to the PhysicalTopologySource object - topologySource.addListener (this); - - #if JUCE_IOS - connectButton.setButtonText ("Connect"); - connectButton.onClick = [] { BluetoothMidiDevicePairingDialogue::open(); }; - addAndMakeVisible (connectButton); - #endif - - setSize (600, 400); - - topologyChanged(); - } - - ~BlocksSynthDemo() override - { - if (activeBlock != nullptr) - detachActiveBlock(); - - topologySource.removeListener (this); - } - - void paint (Graphics& g) override - { - g.setColour (getLookAndFeel().findColour (Label::textColourId)); - g.drawText ("Connect a Lightpad Block to play.", - getLocalBounds(), Justification::centred, false); - } - - void resized() override - { - #if JUCE_IOS - connectButton.setBounds (getRight() - 100, 20, 80, 30); - #endif - } - - /** Overridden from TopologySource::Listener, called when the topology changes */ - void topologyChanged() override - { - // Reset the activeBlock object - if (activeBlock != nullptr) - detachActiveBlock(); - - // Get the array of currently connected Block objects from the PhysicalTopologySource - auto blocks = topologySource.getBlocks(); - - // Iterate over the array of Block objects - for (auto b : blocks) - { - // Find the first Lightpad - if (b->getType() == Block::Type::lightPadBlock) - { - activeBlock = b; - - // Register BlocksSynthDemo as a listener to the touch surface - if (auto surface = activeBlock->getTouchSurface()) - surface->addListener (this); - - // Register BlocksSynthDemo as a listener to any buttons - for (auto button : activeBlock->getButtons()) - button->addListener (this); - - // Get the LEDGrid object from the Lightpad and set its program to the program for the current mode - if (auto grid = activeBlock->getLEDGrid()) - { - // Work out scale factors to translate X and Y touches to LED indexes - scaleX = static_cast (grid->getNumColumns() - 1) / (float) activeBlock->getWidth(); - scaleY = static_cast (grid->getNumRows() - 1) / (float) activeBlock->getHeight(); - - setLEDProgram (*activeBlock); - } - - break; - } - } - } - -private: - /** Overridden from TouchSurface::Listener. Called when a Touch is received on the Lightpad */ - void touchChanged (TouchSurface&, const TouchSurface::Touch& touch) override - { - if (currentMode == waveformSelectionMode && touch.isTouchStart && allowTouch) - { - if (auto* waveshapeProgram = getWaveshapeProgram()) - { - // Change the displayed waveshape to the next one - ++waveshapeMode; - - if (waveshapeMode > 3) - waveshapeMode = 0; - - waveshapeProgram->setWaveshapeType (static_cast (waveshapeMode)); - - allowTouch = false; - startTimer (250); - } - } - else if (currentMode == playMode) - { - if (auto* gridProgram = getGridProgram()) - { - // Translate X and Y touch events to LED indexes - auto xLed = roundToInt (touch.startX * scaleX); - auto yLed = roundToInt (touch.startY * scaleY); - - // Limit the number of touches per second - constexpr auto maxNumTouchMessagesPerSecond = 100; - auto now = Time::getCurrentTime(); - clearOldTouchTimes (now); - - auto midiChannel = waveshapeMode + 1; - - // Send the touch event to the DrumPadGridProgram and Audio class - if (touch.isTouchStart) - { - gridProgram->startTouch (touch.startX, touch.startY); - audio.noteOn (midiChannel, layout.getNoteNumberForPad (xLed, yLed), touch.z); - } - else if (touch.isTouchEnd) - { - gridProgram->endTouch (touch.startX, touch.startY); - audio.noteOff (midiChannel, layout.getNoteNumberForPad (xLed, yLed), 1.0); - } - else - { - if (touchMessageTimesInLastSecond.size() > maxNumTouchMessagesPerSecond / 3) - return; - - gridProgram->sendTouch (touch.x, touch.y, touch.z, - layout.touchColour); - - // Send pitch change and pressure values to the Audio class - audio.pitchChange (midiChannel, (touch.x - touch.startX) / (float) activeBlock->getWidth()); - audio.pressureChange (midiChannel, touch.z); - } - - touchMessageTimesInLastSecond.add (now); - } - } - } - - /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is pressed */ - void buttonPressed (ControlButton&, Block::Timestamp) override {} - - /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is released */ - void buttonReleased (ControlButton&, Block::Timestamp) override - { - // Turn any active synthesiser notes off - audio.allNotesOff(); - - // Switch modes - if (currentMode == waveformSelectionMode) - currentMode = playMode; - else if (currentMode == playMode) - currentMode = waveformSelectionMode; - - // Set the LEDGrid program to the new mode - setLEDProgram (*activeBlock); - } - - /** Clears the old touch times */ - void clearOldTouchTimes (const Time now) - { - for (auto i = touchMessageTimesInLastSecond.size(); --i >= 0;) - if (touchMessageTimesInLastSecond.getReference(i) < now - RelativeTime::seconds (0.33)) - touchMessageTimesInLastSecond.remove (i); - } - - /** Removes TouchSurface and ControlButton listeners and sets activeBlock to nullptr */ - void detachActiveBlock() - { - if (auto surface = activeBlock->getTouchSurface()) - surface->removeListener (this); - - for (auto button : activeBlock->getButtons()) - button->removeListener (this); - - activeBlock = nullptr; - } - - /** Sets the LEDGrid Program for the selected mode */ - void setLEDProgram (Block& block) - { - if (currentMode == waveformSelectionMode) - { - // Set the LEDGrid program - block.setProgram (std::make_unique(block)); - - // Initialise the program - if (auto* waveshapeProgram = getWaveshapeProgram()) - { - waveshapeProgram->setWaveshapeType (static_cast (waveshapeMode)); - waveshapeProgram->generateWaveshapes(); - } - } - else if (currentMode == playMode) - { - // Set the LEDGrid program - auto error = block.setProgram (std::make_unique(block)); - - if (error.failed()) - { - DBG (error.getErrorMessage()); - jassertfalse; - } - - // Setup the grid layout - if (auto* gridProgram = getGridProgram()) - gridProgram->setGridFills (layout.numColumns, layout.numRows, layout.gridFillArray); - } - } - - /** Stops touch events from triggering multiple waveshape mode changes */ - void timerCallback() override { allowTouch = true; } - - //============================================================================== - DrumPadGridProgram* getGridProgram() - { - if (activeBlock != nullptr) - return dynamic_cast (activeBlock->getProgram()); - - return nullptr; - } - - WaveshapeProgram* getWaveshapeProgram() - { - if (activeBlock != nullptr) - return dynamic_cast (activeBlock->getProgram()); - - return nullptr; - } - - //============================================================================== - enum BlocksSynthMode - { - waveformSelectionMode = 0, - playMode - }; - - BlocksSynthMode currentMode = playMode; - - //============================================================================== - Audio audio; - - SynthGrid layout { 5, 5 }; - PhysicalTopologySource topologySource; - Block::Ptr activeBlock; - - Array