@@ -17,7 +17,7 @@ | |||||
//============================================================================== | //============================================================================== | ||||
// [BEGIN_USER_CODE_SECTION] | // [BEGIN_USER_CODE_SECTION] | ||||
// (You can add your own code in this section, and the Projucer will not overwrite it) | |||||
#define DUMP_TOPOLOGY 1 | |||||
// [END_USER_CODE_SECTION] | // [END_USER_CODE_SECTION] | ||||
@@ -4,6 +4,7 @@ | |||||
#include "../JuceLibraryCode/JuceHeader.h" | #include "../JuceLibraryCode/JuceHeader.h" | ||||
//============================================================================== | |||||
/** | /** | ||||
A struct that handles the setup and layout of the DrumPadGridProgram | A struct that handles the setup and layout of the DrumPadGridProgram | ||||
*/ | */ | ||||
@@ -70,28 +71,31 @@ struct ColourGrid | |||||
//============================================================================== | //============================================================================== | ||||
int numColumns, numRows; | int numColumns, numRows; | ||||
float width, height; | |||||
Array<DrumPadGridProgram::GridFill> gridFillArray; | Array<DrumPadGridProgram::GridFill> gridFillArray; | ||||
Array<Colour> colourArray = { Colours::white, Colours::red, Colours::green, Colours::blue, Colours::hotpink, | |||||
Colours::orange, Colours::magenta, Colours::cyan, Colours::black }; | |||||
Array<Colour> colourArray = { Colours::white, Colours::red, Colours::green, | |||||
Colours::blue, Colours::hotpink, Colours::orange, | |||||
Colours::magenta, Colours::cyan, Colours::black }; | |||||
Colour currentColour = Colours::hotpink; | Colour currentColour = Colours::hotpink; | ||||
//============================================================================== | //============================================================================== | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ColourGrid) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ColourGrid) | ||||
}; | }; | ||||
//============================================================================== | |||||
/** | /** | ||||
The main component | The main component | ||||
*/ | */ | ||||
class MainComponent : public Component, | class MainComponent : public Component, | ||||
public TopologySource::Listener, | |||||
private TouchSurface::Listener, | |||||
private ControlButton::Listener, | |||||
private Timer | |||||
public TopologySource::Listener, | |||||
private TouchSurface::Listener, | |||||
private ControlButton::Listener, | |||||
private Timer | |||||
{ | { | ||||
public: | public: | ||||
MainComponent() : layout (3, 3) | |||||
MainComponent() | |||||
{ | { | ||||
setSize (600, 400); | setSize (600, 400); | ||||
@@ -110,7 +114,8 @@ public: | |||||
void paint (Graphics& g) override | void paint (Graphics& g) override | ||||
{ | { | ||||
g.fillAll (Colours::lightgrey); | g.fillAll (Colours::lightgrey); | ||||
g.drawText ("Connect a Lightpad Block to draw.", getLocalBounds(), Justification::centred, false); | |||||
g.drawText ("Connect a Lightpad Block to draw.", | |||||
getLocalBounds(), Justification::centred, false); | |||||
} | } | ||||
void resized() override {} | void resized() override {} | ||||
@@ -176,7 +181,7 @@ private: | |||||
} | } | ||||
/** Overridden from ControlButton::Listener. Called when a button on the Lightpad is pressed */ | /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is pressed */ | ||||
void buttonPressed (ControlButton&, Block::Timestamp) override {}; | |||||
void buttonPressed (ControlButton&, Block::Timestamp) override {} | |||||
/** Overridden from ControlButton::Listener. Called when a button on the Lightpad is released */ | /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is released */ | ||||
void buttonReleased (ControlButton&, Block::Timestamp) override | void buttonReleased (ControlButton&, Block::Timestamp) override | ||||
@@ -257,7 +262,9 @@ private: | |||||
grid->setProgram (colourPaletteProgram); | grid->setProgram (colourPaletteProgram); | ||||
// Setup the grid layout | // Setup the grid layout | ||||
colourPaletteProgram->setGridFills (layout.numColumns, layout.numRows, layout.gridFillArray); | |||||
colourPaletteProgram->setGridFills (layout.numColumns, | |||||
layout.numRows, | |||||
layout.gridFillArray); | |||||
} | } | ||||
} | } | ||||
@@ -265,20 +272,12 @@ private: | |||||
void drawLEDs (uint32 x0, uint32 y0, float z, Colour drawColour) | void drawLEDs (uint32 x0, uint32 y0, float z, Colour drawColour) | ||||
{ | { | ||||
// Check if the activeLeds array already contains an ActiveLED object for this LED | // Check if the activeLeds array already contains an ActiveLED object for this LED | ||||
int index = -1; | |||||
for (int i = 0; i < activeLeds.size(); ++i) | |||||
{ | |||||
if (activeLeds.getReference(i).occupies (x0, y0)) | |||||
{ | |||||
index = i; | |||||
break; | |||||
} | |||||
} | |||||
auto index = getLEDAt (x0, y0); | |||||
// If the colour is black then just set the LED to black and return | // If the colour is black then just set the LED to black and return | ||||
if (drawColour == Colours::black) | if (drawColour == Colours::black) | ||||
{ | { | ||||
if (index != -1) | |||||
if (index >= 0) | |||||
{ | { | ||||
canvasProgram->setLED (x0, y0, Colours::black); | canvasProgram->setLED (x0, y0, Colours::black); | ||||
activeLeds.remove (index); | activeLeds.remove (index); | ||||
@@ -289,7 +288,7 @@ private: | |||||
// If there is no ActiveLED obejct for this LED then create one, | // If there is no ActiveLED obejct for this LED then create one, | ||||
// add it to the array, set the LED on the Block and return | // add it to the array, set the LED on the Block and return | ||||
if (index == -1) | |||||
if (index < 0) | |||||
{ | { | ||||
ActiveLED led; | ActiveLED led; | ||||
led.x = x0; | led.x = x0; | ||||
@@ -333,37 +332,42 @@ private: | |||||
*/ | */ | ||||
struct ActiveLED | struct ActiveLED | ||||
{ | { | ||||
uint32 x; | |||||
uint32 y; | |||||
uint32 x, y; | |||||
Colour colour; | Colour colour; | ||||
float brightness; | float brightness; | ||||
/** Returns true if this LED occupies the given co-ordiantes */ | |||||
/** Returns true if this LED occupies the given co-ordinates */ | |||||
bool occupies (uint32 xPos, uint32 yPos) const | bool occupies (uint32 xPos, uint32 yPos) const | ||||
{ | { | ||||
if (xPos == x && yPos == y) | |||||
return true; | |||||
return false; | |||||
return xPos == x && yPos == y; | |||||
} | } | ||||
}; | }; | ||||
Array<ActiveLED> activeLeds; | Array<ActiveLED> activeLeds; | ||||
/** | |||||
enum for the two modes | |||||
*/ | |||||
int getLEDAt (uint32 x, uint32 y) const | |||||
{ | |||||
for (int i = 0; i < activeLeds.size(); ++i) | |||||
if (activeLeds.getReference(i).occupies (x, y)) | |||||
return i; | |||||
return -1; | |||||
} | |||||
//============================================================================== | |||||
enum DisplayMode | enum DisplayMode | ||||
{ | { | ||||
colourPalette = 0, | colourPalette = 0, | ||||
canvas | canvas | ||||
}; | }; | ||||
DisplayMode currentMode = colourPalette; | DisplayMode currentMode = colourPalette; | ||||
//============================================================================== | //============================================================================== | ||||
BitmapLEDProgram* canvasProgram; | |||||
DrumPadGridProgram* colourPaletteProgram; | |||||
BitmapLEDProgram* canvasProgram = nullptr; | |||||
DrumPadGridProgram* colourPaletteProgram = nullptr; | |||||
ColourGrid layout; | |||||
ColourGrid layout { 3, 3 }; | |||||
PhysicalTopologySource topologySource; | PhysicalTopologySource topologySource; | ||||
Block::Ptr activeBlock; | Block::Ptr activeBlock; | ||||
@@ -17,7 +17,7 @@ | |||||
//============================================================================== | //============================================================================== | ||||
// [BEGIN_USER_CODE_SECTION] | // [BEGIN_USER_CODE_SECTION] | ||||
// (You can add your own code in this section, and the Projucer will not overwrite it) | |||||
#define DUMP_TOPOLOGY 1 | |||||
// [END_USER_CODE_SECTION] | // [END_USER_CODE_SECTION] | ||||
@@ -15,7 +15,6 @@ class BlockComponent : public Component, | |||||
private Timer | private Timer | ||||
{ | { | ||||
public: | public: | ||||
/** Constructor */ | |||||
BlockComponent (Block::Ptr blockToUse) | BlockComponent (Block::Ptr blockToUse) | ||||
: block (blockToUse) | : block (blockToUse) | ||||
{ | { | ||||
@@ -38,7 +37,6 @@ public: | |||||
startTimerHz (25); | startTimerHz (25); | ||||
} | } | ||||
/** Destructor */ | |||||
~BlockComponent() | ~BlockComponent() | ||||
{ | { | ||||
// Remove any listeners | // Remove any listeners | ||||
@@ -53,19 +51,16 @@ public: | |||||
void updateStatsAndTooltip() | void updateStatsAndTooltip() | ||||
{ | { | ||||
// Get the battery level of this Block and inform any subclasses | // Get the battery level of this Block and inform any subclasses | ||||
const float batteryLevel = block->getBatteryLevel(); | |||||
auto batteryLevel = block->getBatteryLevel(); | |||||
handleBatteryLevelUpdate (batteryLevel); | handleBatteryLevelUpdate (batteryLevel); | ||||
// Format the tooltip string | |||||
const String ttString = "Name = " + block->getDeviceDescription() + "\n" | |||||
+ "UID = " + String (block->uid) + "\n" | |||||
+ "Serial number = " + block->serialNumber + "\n" | |||||
+ "Battery level = " + String ((int) (batteryLevel * 100)) + "%" | |||||
+ (block->isBatteryCharging() ? "++" : "--"); | |||||
// Update the tooltip string if it has changed | |||||
if (ttString != getTooltip()) | |||||
setTooltip (ttString); | |||||
// 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 */ | /** Subclasses should override this to paint the Block object on the screen */ | ||||
@@ -88,31 +83,35 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns an integer index corresponding to a physical position on the hardware | /** Returns an integer index corresponding to a physical position on the hardware | ||||
for each type of Control Block. */ | |||||
for each type of Control Block. */ | |||||
static int controlButtonFunctionToIndex (ControlButton::ButtonFunction f) | static int controlButtonFunctionToIndex (ControlButton::ButtonFunction f) | ||||
{ | { | ||||
static std::initializer_list<ControlButton::ButtonFunction> map[] = | |||||
{{ControlButton::mode, ControlButton::button0}, | |||||
{ControlButton::volume, ControlButton::button1}, | |||||
{ControlButton::scale, ControlButton::button2, ControlButton::click}, | |||||
{ControlButton::chord, ControlButton::button3, ControlButton::snap}, | |||||
{ControlButton::arp,ControlButton:: button4, ControlButton::back}, | |||||
{ControlButton::sustain, ControlButton::button5, ControlButton::playOrPause}, | |||||
{ControlButton::octave, ControlButton::button6, ControlButton::record}, | |||||
{ControlButton::love, ControlButton::button7, ControlButton::learn}, | |||||
{ControlButton::up}, | |||||
{ControlButton::down}}; | |||||
for (size_t i = 0; i < (sizeof (map) / sizeof (map[0])); ++i) | |||||
if (std::find (map[i].begin(), map[i].end(), f) != map[i].end()) | |||||
return static_cast<int> (i); | |||||
using CB = ControlButton; | |||||
static Array<ControlButton::ButtonFunction> map[] = | |||||
{ | |||||
{ CB::mode, CB::button0 }, | |||||
{ CB::volume, CB::button1 }, | |||||
{ CB::scale, CB::button2, CB::click }, | |||||
{ CB::chord, CB::button3, CB::snap }, | |||||
{ CB::arp, CB::button4, CB::back }, | |||||
{ CB::sustain, CB::button5, CB::playOrPause }, | |||||
{ CB::octave, CB::button6, CB::record }, | |||||
{ CB::love, CB::button7, CB::learn }, | |||||
{ CB::up }, | |||||
{ CB::down } | |||||
}; | |||||
for (int i = 0; i < numElementsInArray (map); ++i) | |||||
if (map[i].contains (f)) | |||||
return i; | |||||
return -1; | return -1; | ||||
} | } | ||||
private: | private: | ||||
/** Used to call repaint() periodically */ | /** Used to call repaint() periodically */ | ||||
void timerCallback() override { repaint(); } | |||||
void timerCallback() override { repaint(); } | |||||
/** Overridden from TouchSurface::Listener */ | /** Overridden from TouchSurface::Listener */ | ||||
void touchChanged (TouchSurface&, const TouchSurface::Touch& t) override { handleTouchChange (t); } | void touchChanged (TouchSurface&, const TouchSurface::Touch& t) override { handleTouchChange (t); } | ||||
@@ -123,14 +122,14 @@ private: | |||||
/** Overridden from ControlButton::Listener */ | /** Overridden from ControlButton::Listener */ | ||||
void buttonReleased (ControlButton& b, Block::Timestamp t) override { handleButtonReleased (b.getType(), t); } | void buttonReleased (ControlButton& b, Block::Timestamp t) override { handleButtonReleased (b.getType(), t); } | ||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlockComponent) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlockComponent) | ||||
}; | }; | ||||
//============================================================================== | |||||
/** | /** | ||||
Class that renders a Lightpad on the screen | Class that renders a Lightpad on the screen | ||||
*/ | */ | ||||
class LightpadComponent : public BlockComponent | |||||
class LightpadComponent : public BlockComponent | |||||
{ | { | ||||
public: | public: | ||||
LightpadComponent (Block::Ptr blockToUse) | LightpadComponent (Block::Ptr blockToUse) | ||||
@@ -154,23 +153,25 @@ public: | |||||
g.fillAll (Colours::black); | g.fillAll (Colours::black); | ||||
// size ration between physical and on-screen blocks | // size ration between physical and on-screen blocks | ||||
const Point<float> ratio (r.getWidth() / block->getWidth(), | |||||
r.getHeight() / block->getHeight()); | |||||
const float maxCircleSize = block->getWidth() / 3.0f; | |||||
Point<float> ratio (r.getWidth() / block->getWidth(), | |||||
r.getHeight() / block->getHeight()); | |||||
float maxCircleSize = block->getWidth() / 3.0f; | |||||
// iterate over the list of current touches and draw them on the onscreen Block | // iterate over the list of current touches and draw them on the onscreen Block | ||||
for (auto touch : touches) | for (auto touch : touches) | ||||
{ | { | ||||
const float circleSize = touch.touch.z * maxCircleSize; | |||||
const Point<float> touchPosition = Point<float> (touch.touch.x, touch.touch.y); | |||||
float circleSize = touch.touch.z * maxCircleSize; | |||||
Point<float> touchPosition (touch.touch.x, | |||||
touch.touch.y); | |||||
const Colour c = colourArray[touch.touch.index]; | |||||
const Rectangle<float> blob = | |||||
(Rectangle<float> (circleSize, circleSize).withCentre (touchPosition)) * ratio; | |||||
auto blob = Rectangle<float> (circleSize, circleSize) | |||||
.withCentre (touchPosition) * ratio; | |||||
const ColourGradient cg = ColourGradient (colourArray[touch.touch.index], blob.getCentreX(), blob.getCentreY(), | |||||
Colours::transparentBlack, blob.getRight(), blob.getBottom(), | |||||
true); | |||||
ColourGradient cg (colourArray[touch.touch.index], blob.getCentreX(), blob.getCentreY(), | |||||
Colours::transparentBlack, blob.getRight(), blob.getBottom(), | |||||
true); | |||||
g.setGradientFill (cg); | g.setGradientFill (cg); | ||||
g.fillEllipse (blob); | g.fillEllipse (blob); | ||||
@@ -181,21 +182,26 @@ public: | |||||
private: | private: | ||||
/** An Array of colours to use for touches */ | /** An Array of colours to use for touches */ | ||||
Array<Colour> colourArray = { Colours::red, Colours::blue, Colours::green, | |||||
Colours::yellow, Colours::white, Colours::hotpink, | |||||
Array<Colour> colourArray = { Colours::red, | |||||
Colours::blue, | |||||
Colours::green, | |||||
Colours::yellow, | |||||
Colours::white, | |||||
Colours::hotpink, | |||||
Colours::mediumpurple }; | Colours::mediumpurple }; | ||||
/** A list of current Touch events */ | /** A list of current Touch events */ | ||||
TouchList<TouchSurface::Touch> touches; | TouchList<TouchSurface::Touch> touches; | ||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LightpadComponent) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LightpadComponent) | ||||
}; | }; | ||||
//============================================================================== | |||||
/** | /** | ||||
Class that renders a Control Block on the screen | Class that renders a Control Block on the screen | ||||
*/ | */ | ||||
class ControlBlockComponent : public BlockComponent | |||||
class ControlBlockComponent : public BlockComponent | |||||
{ | { | ||||
public: | public: | ||||
ControlBlockComponent (Block::Ptr blockToUse) | ControlBlockComponent (Block::Ptr blockToUse) | ||||
@@ -205,13 +211,12 @@ public: | |||||
addAndMakeVisible (roundedRectangleButton); | addAndMakeVisible (roundedRectangleButton); | ||||
// Display the battery level on the LEDRow | // Display the battery level on the LEDRow | ||||
int numLedsToTurnOn = static_cast<int> (static_cast<float> (numLeds) * block->getBatteryLevel()); | |||||
auto numLedsToTurnOn = static_cast<int> (numLeds * block->getBatteryLevel()); | |||||
// add LEDs | // add LEDs | ||||
LEDComponent* ledComponent; | |||||
for (int i = 0; i < numLeds; ++i) | for (int i = 0; i < numLeds; ++i) | ||||
{ | { | ||||
ledComponent = new LEDComponent(); | |||||
auto ledComponent = new LEDComponent(); | |||||
ledComponent->setOnState (i < numLedsToTurnOn); | ledComponent->setOnState (i < numLedsToTurnOn); | ||||
addAndMakeVisible (leds.add (ledComponent)); | addAndMakeVisible (leds.add (ledComponent)); | ||||
@@ -238,9 +243,9 @@ public: | |||||
auto buttonRow1 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth); | auto buttonRow1 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth); | ||||
auto buttonRow2 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth); | auto buttonRow2 = row.removeFromTop (rowHeight * 2).withSizeKeepingCentre (r.getWidth(), buttonWidth); | ||||
for (int i = 0; i < numLeds; ++i) | |||||
for (auto* led : leds) | |||||
{ | { | ||||
leds.getUnchecked (i)->setBounds (ledRow.removeFromLeft (ledWidth).reduced (2)); | |||||
led->setBounds (ledRow.removeFromLeft (ledWidth).reduced (2)); | |||||
ledRow.removeFromLeft (5); | ledRow.removeFromLeft (5); | ||||
} | } | ||||
@@ -261,7 +266,7 @@ public: | |||||
void paint (Graphics& g) override | void paint (Graphics& g) override | ||||
{ | { | ||||
const auto r = getLocalBounds().toFloat(); | |||||
auto r = getLocalBounds().toFloat(); | |||||
// Fill a black rectangle for the Control Block | // Fill a black rectangle for the Control Block | ||||
g.setColour (Colours::black); | g.setColour (Colours::black); | ||||
@@ -281,7 +286,7 @@ public: | |||||
void handleBatteryLevelUpdate (float batteryLevel) override | void handleBatteryLevelUpdate (float batteryLevel) override | ||||
{ | { | ||||
// Update the number of LEDs that are on to represent the battery level | // Update the number of LEDs that are on to represent the battery level | ||||
int numLedsOn = static_cast<int> (static_cast<float> (numLeds) * batteryLevel); | |||||
int numLedsOn = static_cast<int> (numLeds * batteryLevel); | |||||
if (numLedsOn != previousNumLedsOn) | if (numLedsOn != previousNumLedsOn) | ||||
for (int i = 0; i < numLeds; ++i) | for (int i = 0; i < numLeds; ++i) | ||||
@@ -292,22 +297,22 @@ public: | |||||
} | } | ||||
private: | private: | ||||
//============================================================================== | |||||
/** | /** | ||||
Base class that renders a Control Block button | Base class that renders a Control Block button | ||||
*/ | */ | ||||
struct ControlBlockSubComponent : public Component, | |||||
public TooltipClient | |||||
struct ControlBlockSubComponent : public Component, | |||||
public TooltipClient | |||||
{ | { | ||||
ControlBlockSubComponent (Colour componentColourToUse) | ControlBlockSubComponent (Colour componentColourToUse) | ||||
: componentColour (componentColourToUse), | |||||
onState (false) | |||||
: componentColour (componentColourToUse) | |||||
{} | {} | ||||
/** Subclasses should override this to paint the button on the screen */ | /** Subclasses should override this to paint the button on the screen */ | ||||
virtual void paint (Graphics&) override = 0; | virtual void paint (Graphics&) override = 0; | ||||
/** Sets the colour of the button */ | /** Sets the colour of the button */ | ||||
void setColour (Colour c) { componentColour = c; } | |||||
void setColour (Colour c) { componentColour = c; } | |||||
/** Sets the on state of the button */ | /** Sets the on state of the button */ | ||||
void setOnState (bool isOn) | void setOnState (bool isOn) | ||||
@@ -320,15 +325,15 @@ private: | |||||
String getTooltip() override | String getTooltip() override | ||||
{ | { | ||||
for (Component* comp = this; comp != nullptr; comp = comp->getParentComponent()) | for (Component* comp = this; comp != nullptr; comp = comp->getParentComponent()) | ||||
if (SettableTooltipClient* sttc = dynamic_cast<SettableTooltipClient*> (comp)) | |||||
if (auto* sttc = dynamic_cast<SettableTooltipClient*> (comp)) | |||||
return sttc->getTooltip(); | return sttc->getTooltip(); | ||||
return String(); | |||||
return {}; | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
Colour componentColour; | Colour componentColour; | ||||
bool onState; | |||||
bool onState = false; | |||||
//============================================================================== | //============================================================================== | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlBlockSubComponent) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ControlBlockSubComponent) | ||||
@@ -372,8 +377,7 @@ private: | |||||
void paint (Graphics& g) override | void paint (Graphics& g) override | ||||
{ | { | ||||
const auto r = getLocalBounds().toFloat(); | |||||
auto r = getLocalBounds().toFloat(); | |||||
g.setColour (componentColour.withAlpha (0.2f)); | g.setColour (componentColour.withAlpha (0.2f)); | ||||
g.fillRoundedRectangle (r.toFloat(), 20.0f); | g.fillRoundedRectangle (r.toFloat(), 20.0f); | ||||
@@ -382,11 +386,11 @@ private: | |||||
// is a button pressed? | // is a button pressed? | ||||
if (doubleButtonOnState[0] || doubleButtonOnState[1]) | if (doubleButtonOnState[0] || doubleButtonOnState[1]) | ||||
{ | { | ||||
const float semiButtonWidth = r.getWidth() / 2.0f; | |||||
const auto semiButtonBounds = r.withWidth (semiButtonWidth) | |||||
.withX (doubleButtonOnState[1] ? semiButtonWidth : 0) | |||||
.reduced (5.0f, 2.0f); | |||||
auto semiButtonWidth = r.getWidth() / 2.0f; | |||||
auto semiButtonBounds = r.withWidth (semiButtonWidth) | |||||
.withX (doubleButtonOnState[1] ? semiButtonWidth : 0) | |||||
.reduced (5.0f, 2.0f); | |||||
g.fillEllipse (semiButtonBounds); | g.fillEllipse (semiButtonBounds); | ||||
} | } | ||||
@@ -399,7 +403,7 @@ private: | |||||
} | } | ||||
private: | private: | ||||
bool doubleButtonOnState[2] = {false, false}; | |||||
bool doubleButtonOnState[2] = { false, false }; | |||||
}; | }; | ||||
/** Displays a button press or release interaction for a button at a given index */ | /** Displays a button press or release interaction for a button at a given index */ | ||||
@@ -46,10 +46,10 @@ public: | |||||
} | } | ||||
// Work out the maximum diplay area for each Block | // Work out the maximum diplay area for each Block | ||||
const Rectangle<int> bounds = getLocalBounds().reduced (20); | |||||
auto bounds = getLocalBounds().reduced (20); | |||||
auto squareRoot = sqrt (numBlockComponents); | |||||
int gridSize = (int)squareRoot; | |||||
auto squareRoot = std::sqrt (numBlockComponents); | |||||
int gridSize = (int) squareRoot; | |||||
if (squareRoot - gridSize > 0) | if (squareRoot - gridSize > 0) | ||||
gridSize++; | gridSize++; | ||||
@@ -64,7 +64,7 @@ public: | |||||
for (auto block : blockComponents) | for (auto block : blockComponents) | ||||
{ | { | ||||
Rectangle<int> blockBounds; | Rectangle<int> blockBounds; | ||||
const Block::Type type = block->block->getType(); | |||||
auto type = block->block->getType(); | |||||
// Can fit 2 ControlBlockComponents in the space of one LightpadBlockComponent | // Can fit 2 ControlBlockComponents in the space of one LightpadBlockComponent | ||||
if (type == Block::liveBlock || type == Block::loopBlock) | if (type == Block::liveBlock || type == Block::loopBlock) | ||||
@@ -105,11 +105,11 @@ public: | |||||
blockComponents.clear(); | blockComponents.clear(); | ||||
// Get the array of currently connected Block objects from the PhysicalTopologySource | // Get the array of currently connected Block objects from the PhysicalTopologySource | ||||
Block::Array blocksArray = topologySource.getCurrentTopology().blocks; | |||||
auto blocksArray = topologySource.getCurrentTopology().blocks; | |||||
// Create a BlockComponent object for each Block object | // Create a BlockComponent object for each Block object | ||||
for (auto& block : blocksArray) | for (auto& block : blocksArray) | ||||
if (BlockComponent* blockComponent = createBlockComponent (block)) | |||||
if (auto* blockComponent = createBlockComponent (block)) | |||||
addAndMakeVisible (blockComponents.add (blockComponent)); | addAndMakeVisible (blockComponents.add (blockComponent)); | ||||
// Update the display | // Update the display | ||||
@@ -120,16 +120,16 @@ private: | |||||
/** Creates a BlockComponent object for a new Block and adds it to the content component */ | /** Creates a BlockComponent object for a new Block and adds it to the content component */ | ||||
BlockComponent* createBlockComponent (Block::Ptr newBlock) | BlockComponent* createBlockComponent (Block::Ptr newBlock) | ||||
{ | { | ||||
const Block::Type type = newBlock->getType(); | |||||
auto type = newBlock->getType(); | |||||
if (type == Block::lightPadBlock) | if (type == Block::lightPadBlock) | ||||
return new LightpadComponent (newBlock); | return new LightpadComponent (newBlock); | ||||
if (type == Block::loopBlock || type == Block::liveBlock) | if (type == Block::loopBlock || type == Block::liveBlock) | ||||
return new ControlBlockComponent (newBlock); | return new ControlBlockComponent (newBlock); | ||||
// should only be connecting a Lightpad or Control Block! | // should only be connecting a Lightpad or Control Block! | ||||
jassertfalse; | jassertfalse; | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
@@ -17,7 +17,7 @@ | |||||
//============================================================================== | //============================================================================== | ||||
// [BEGIN_USER_CODE_SECTION] | // [BEGIN_USER_CODE_SECTION] | ||||
// (You can add your own code in this section, and the Projucer will not overwrite it) | |||||
#define DUMP_TOPOLOGY 1 | |||||
// [END_USER_CODE_SECTION] | // [END_USER_CODE_SECTION] | ||||
@@ -27,9 +27,8 @@ public: | |||||
void shutdown() override { mainWindow = nullptr; } | void shutdown() override { mainWindow = nullptr; } | ||||
//============================================================================== | //============================================================================== | ||||
class MainWindow : public DocumentWindow | |||||
struct MainWindow : public DocumentWindow | |||||
{ | { | ||||
public: | |||||
MainWindow (String name) : DocumentWindow (name, | MainWindow (String name) : DocumentWindow (name, | ||||
Colours::lightgrey, | Colours::lightgrey, | ||||
DocumentWindow::allButtons) | DocumentWindow::allButtons) | ||||
@@ -47,10 +46,8 @@ public: | |||||
JUCEApplication::getInstance()->systemRequestedQuit(); | JUCEApplication::getInstance()->systemRequestedQuit(); | ||||
} | } | ||||
private: | |||||
TooltipWindow tooltipWindow; | TooltipWindow tooltipWindow; | ||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow) | ||||
}; | }; | ||||
@@ -5,6 +5,7 @@ | |||||
#include "../JuceLibraryCode/JuceHeader.h" | #include "../JuceLibraryCode/JuceHeader.h" | ||||
#include "Audio.h" | #include "Audio.h" | ||||
//============================================================================== | |||||
/** | /** | ||||
A struct that handles the setup and layout of the DrumPadGridProgram | A struct that handles the setup and layout of the DrumPadGridProgram | ||||
*/ | */ | ||||
@@ -41,7 +42,7 @@ struct SynthGrid | |||||
} | } | ||||
} | } | ||||
int getNoteNumberForPad (int x, int y) | |||||
int getNoteNumberForPad (int x, int y) const | |||||
{ | { | ||||
int xIndex = x / 3; | int xIndex = x / 3; | ||||
int yIndex = y / 3; | int yIndex = y / 3; | ||||
@@ -64,6 +65,7 @@ struct SynthGrid | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SynthGrid) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SynthGrid) | ||||
}; | }; | ||||
//============================================================================== | |||||
/** | /** | ||||
The main component | The main component | ||||
*/ | */ | ||||
@@ -74,7 +76,7 @@ class MainComponent : public Component, | |||||
private Timer | private Timer | ||||
{ | { | ||||
public: | public: | ||||
MainComponent() : layout (5, 5) | |||||
MainComponent() | |||||
{ | { | ||||
setSize (600, 400); | setSize (600, 400); | ||||
@@ -93,7 +95,8 @@ public: | |||||
void paint (Graphics& g) override | void paint (Graphics& g) override | ||||
{ | { | ||||
g.fillAll (Colours::lightgrey); | g.fillAll (Colours::lightgrey); | ||||
g.drawText ("Connect a Lightpad Block to play.", getLocalBounds(), Justification::centred, false); | |||||
g.drawText ("Connect a Lightpad Block to play.", | |||||
getLocalBounds(), Justification::centred, false); | |||||
} | } | ||||
void resized() override {} | void resized() override {} | ||||
@@ -106,7 +109,7 @@ public: | |||||
detachActiveBlock(); | detachActiveBlock(); | ||||
// Get the array of currently connected Block objects from the PhysicalTopologySource | // Get the array of currently connected Block objects from the PhysicalTopologySource | ||||
Block::Array blocks = topologySource.getCurrentTopology().blocks; | |||||
auto blocks = topologySource.getCurrentTopology().blocks; | |||||
// Iterate over the array of Block objects | // Iterate over the array of Block objects | ||||
for (auto b : blocks) | for (auto b : blocks) | ||||
@@ -180,10 +183,11 @@ private: | |||||
if (touchMessageTimesInLastSecond.size() > maxNumTouchMessagesPerSecond / 3) | if (touchMessageTimesInLastSecond.size() > maxNumTouchMessagesPerSecond / 3) | ||||
return; | return; | ||||
gridProgram->sendTouch (touch.x, touch.y, touch.z, layout.touchColour); | |||||
gridProgram->sendTouch (touch.x, touch.y, touch.z, | |||||
layout.touchColour); | |||||
// Send pitch change and pressure values to the Audio class | // Send pitch change and pressure values to the Audio class | ||||
audio.pitchChange (midiChannel, (touch.x - touch.startX) / static_cast<float> (activeBlock->getWidth())); | |||||
audio.pitchChange (midiChannel, (touch.x - touch.startX) / activeBlock->getWidth()); | |||||
audio.pressureChange (midiChannel, touch.z); | audio.pressureChange (midiChannel, touch.z); | ||||
} | } | ||||
@@ -215,26 +219,18 @@ private: | |||||
// Clear all LEDs | // Clear all LEDs | ||||
for (uint32 x = 0; x < 15; ++x) | for (uint32 x = 0; x < 15; ++x) | ||||
for (uint32 y = 0; y < 15; ++y) | for (uint32 y = 0; y < 15; ++y) | ||||
bitmapProgram->setLED (x, y, Colours::black); | |||||
setLED (x, y, Colours::black); | |||||
// Determine which array to use based on waveshapeMode | // Determine which array to use based on waveshapeMode | ||||
int* waveshapeY = nullptr; | int* waveshapeY = nullptr; | ||||
switch (waveshapeMode) | switch (waveshapeMode) | ||||
{ | { | ||||
case 0: | |||||
waveshapeY = sineWaveY; | |||||
break; | |||||
case 1: | |||||
waveshapeY = squareWaveY; | |||||
break; | |||||
case 2: | |||||
waveshapeY = sawWaveY; | |||||
break; | |||||
case 3: | |||||
waveshapeY = triangleWaveY; | |||||
break; | |||||
default: | |||||
break; | |||||
case 0: waveshapeY = sineWaveY; break; | |||||
case 1: waveshapeY = squareWaveY; break; | |||||
case 2: waveshapeY = sawWaveY; break; | |||||
case 3: waveshapeY = triangleWaveY; break; | |||||
default: break; | |||||
} | } | ||||
// For each X co-ordinate | // For each X co-ordinate | ||||
@@ -306,7 +302,9 @@ private: | |||||
grid->setProgram (gridProgram); | grid->setProgram (gridProgram); | ||||
// Setup the grid layout | // Setup the grid layout | ||||
gridProgram->setGridFills (layout.numColumns, layout.numRows, layout.gridFillArray); | |||||
gridProgram->setGridFills (layout.numColumns, | |||||
layout.numRows, | |||||
layout.gridFillArray); | |||||
} | } | ||||
} | } | ||||
@@ -341,6 +339,7 @@ private: | |||||
// Saw wave output, set flags for when vertical line should be drawn | // Saw wave output, set flags for when vertical line should be drawn | ||||
sawWaveY[x] = 14 - ((x / 2) % 15); | sawWaveY[x] = 14 - ((x / 2) % 15); | ||||
if (sawWaveY[x] == 0 && sawWaveY[x - 1] != -1) | if (sawWaveY[x] == 0 && sawWaveY[x - 1] != -1) | ||||
sawWaveY[x] = -1; | sawWaveY[x] = -1; | ||||
@@ -361,42 +360,47 @@ private: | |||||
} | } | ||||
} | } | ||||
/** Simple wrapper function to set a LED colour */ | |||||
void setLED (uint32 x, uint32 y, Colour colour) | |||||
{ | |||||
if (bitmapProgram != nullptr) | |||||
bitmapProgram->setLED (x, y, colour); | |||||
} | |||||
/** Draws a 'circle' on the Lightpad around an origin co-ordinate */ | /** Draws a 'circle' on the Lightpad around an origin co-ordinate */ | ||||
void drawLEDCircle (uint32 x0, uint32 y0) | void drawLEDCircle (uint32 x0, uint32 y0) | ||||
{ | { | ||||
bitmapProgram->setLED (x0, y0, waveshapeColour); | |||||
setLED (x0, y0, waveshapeColour); | |||||
const uint32 minLedIndex = 0; | const uint32 minLedIndex = 0; | ||||
const uint32 maxLedIndex = 14; | const uint32 maxLedIndex = 14; | ||||
bitmapProgram->setLED (jmin (x0 + 1, maxLedIndex), y0, waveshapeColour.withBrightness (0.4f)); | |||||
bitmapProgram->setLED (jmax (x0 - 1, minLedIndex), y0, waveshapeColour.withBrightness (0.4f)); | |||||
bitmapProgram->setLED (x0, jmin (y0 + 1, maxLedIndex), waveshapeColour.withBrightness (0.4f)); | |||||
bitmapProgram->setLED (x0, jmax (y0 - 1, minLedIndex), waveshapeColour.withBrightness (0.4f)); | |||||
setLED (jmin (x0 + 1, maxLedIndex), y0, waveshapeColour.withBrightness (0.4f)); | |||||
setLED (jmax (x0 - 1, minLedIndex), y0, waveshapeColour.withBrightness (0.4f)); | |||||
setLED (x0, jmin (y0 + 1, maxLedIndex), waveshapeColour.withBrightness (0.4f)); | |||||
setLED (x0, jmax (y0 - 1, minLedIndex), waveshapeColour.withBrightness (0.4f)); | |||||
bitmapProgram->setLED (jmin (x0 + 1, maxLedIndex), jmin (y0 + 1, maxLedIndex), waveshapeColour.withBrightness (0.1f)); | |||||
bitmapProgram->setLED (jmin (x0 + 1, maxLedIndex), jmax (y0 - 1, minLedIndex), waveshapeColour.withBrightness (0.1f)); | |||||
bitmapProgram->setLED (jmax (x0 - 1, minLedIndex), jmin (y0 + 1, maxLedIndex), waveshapeColour.withBrightness (0.1f)); | |||||
bitmapProgram->setLED (jmax (x0 - 1, minLedIndex), jmax (y0 - 1, minLedIndex), waveshapeColour.withBrightness (0.1f)); | |||||
setLED (jmin (x0 + 1, maxLedIndex), jmin (y0 + 1, maxLedIndex), waveshapeColour.withBrightness (0.1f)); | |||||
setLED (jmin (x0 + 1, maxLedIndex), jmax (y0 - 1, minLedIndex), waveshapeColour.withBrightness (0.1f)); | |||||
setLED (jmax (x0 - 1, minLedIndex), jmin (y0 + 1, maxLedIndex), waveshapeColour.withBrightness (0.1f)); | |||||
setLED (jmax (x0 - 1, minLedIndex), jmax (y0 - 1, minLedIndex), waveshapeColour.withBrightness (0.1f)); | |||||
} | } | ||||
/** | |||||
enum for the two modes | |||||
*/ | |||||
enum BlocksSynthMode | enum BlocksSynthMode | ||||
{ | { | ||||
waveformSelectionMode = 0, | waveformSelectionMode = 0, | ||||
playMode | playMode | ||||
}; | }; | ||||
BlocksSynthMode currentMode = playMode; | BlocksSynthMode currentMode = playMode; | ||||
//============================================================================== | //============================================================================== | ||||
Audio audio; | Audio audio; | ||||
DrumPadGridProgram* gridProgram; | |||||
BitmapLEDProgram* bitmapProgram; | |||||
DrumPadGridProgram* gridProgram = nullptr; | |||||
BitmapLEDProgram* bitmapProgram = nullptr; | |||||
SynthGrid layout; | |||||
SynthGrid layout { 5, 5 }; | |||||
PhysicalTopologySource topologySource; | PhysicalTopologySource topologySource; | ||||
Block::Ptr activeBlock; | Block::Ptr activeBlock; | ||||
@@ -7,7 +7,7 @@ | |||||
/** | /** | ||||
Base class for oscillators | Base class for oscillators | ||||
*/ | */ | ||||
class Oscillator : public SynthesiserVoice | |||||
class Oscillator : public SynthesiserVoice | |||||
{ | { | ||||
public: | public: | ||||
Oscillator() | Oscillator() | ||||
@@ -16,10 +16,6 @@ public: | |||||
phaseIncrement.reset (getSampleRate(), 0.1); | phaseIncrement.reset (getSampleRate(), 0.1); | ||||
} | } | ||||
virtual ~Oscillator() | |||||
{ | |||||
} | |||||
void startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int) override | void startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int) override | ||||
{ | { | ||||
frequency = MidiMessage::getMidiNoteInHertz (midiNoteNumber); | frequency = MidiMessage::getMidiNoteInHertz (midiNoteNumber); | ||||
@@ -57,7 +53,7 @@ public: | |||||
void renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override | void renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) override | ||||
{ | { | ||||
while(--numSamples >= 0) | |||||
while (--numSamples >= 0) | |||||
{ | { | ||||
double output = getSample() * amplitude.getNextValue(); | double output = getSample() * amplitude.getNextValue(); | ||||
@@ -82,22 +78,20 @@ public: | |||||
} | } | ||||
/** Subclasses should override this to say whether they can play the given sound */ | /** Subclasses should override this to say whether they can play the given sound */ | ||||
virtual bool canPlaySound (SynthesiserSound* sound) override = 0; | |||||
virtual bool canPlaySound (SynthesiserSound*) override = 0; | |||||
/** Subclasses should override this to render a waveshape */ | /** Subclasses should override this to render a waveshape */ | ||||
virtual double renderWaveShape (const double currentPhase) = 0; | |||||
virtual double renderWaveShape (double currentPhase) = 0; | |||||
private: | private: | ||||
LinearSmoothedValue<double> amplitude; | |||||
LinearSmoothedValue<double> phaseIncrement; | |||||
LinearSmoothedValue<double> amplitude, phaseIncrement; | |||||
double frequency; | |||||
double frequency = 0; | |||||
double phasePos = 0.0f; | double phasePos = 0.0f; | ||||
double sampleRate = 44100.0; | double sampleRate = 44100.0; | ||||
int initialNote; | |||||
double maxFreq; | |||||
double minFreq; | |||||
int initialNote = 0; | |||||
double maxFreq = 0, minFreq = 0; | |||||
//============================================================================== | //============================================================================== | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oscillator) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oscillator) | ||||