1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

BLOCKS: Remove juce_blocks_basics module

This commit is contained in:
reuk 2020-12-15 19:55:44 +00:00
parent 6bb3b9208b
commit 06544850fb
No known key found for this signature in database
GPG key ID: 9ADCD339CFC98A11
78 changed files with 27 additions and 18700 deletions

View file

@ -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<int> (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<LEDComponent> leds;
ListenerList<Listener> listeners;
Time lastMouseEventTime;
Point<int> 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<DrumPadGridProgram::GridFill> gridFillArray;
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;
//==============================================================================
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<float> (brightnessSlider.getValue())));
};
addAndMakeVisible (brightnessSlider);
brightnessLED.setAlwaysOnTop (true);
brightnessLED.setColour (layout.currentColour.withBrightness (static_cast<float> (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<float> (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<float> (brightnessSlider.getValue())
: z * static_cast<float> (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<BitmapLEDProgram>(block));
// Redraw any previously drawn LEDs
redrawLEDs();
}
else if (currentMode == colourPalette)
{
block.setProgram (std::make_unique <DrumPadGridProgram>(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<BitmapLEDProgram*> (activeBlock->getProgram());
return nullptr;
}
DrumPadGridProgram* getPaletteProgram()
{
if (activeBlock != nullptr)
return dynamic_cast<DrumPadGridProgram*> (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<ActiveLED> 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)
};

File diff suppressed because it is too large Load diff

View file

@ -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<double>::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<double>::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<float>& 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<float> (output));
++startSample;
}
}
using SynthesiserVoice::renderNextBlock;
/** Returns the next sample */
double getSample()
{
auto output = renderWaveShape (phasePos);
phasePos += phaseIncrement.getNextValue();
if (phasePos > MathConstants<double>::twoPi)
phasePos -= MathConstants<double>::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<double> 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<SineSound*> (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<SquareSound*> (sound) != nullptr; }
double renderWaveShape (const double currentPhase) override { return (currentPhase < MathConstants<double>::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<SawSound*> (sound) != nullptr; }
double renderWaveShape (const double currentPhase) override { return (1.0 / MathConstants<double>::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<TriangleSound*> (sound) != nullptr; }
double renderWaveShape (const double currentPhase) override
{
return currentPhase < MathConstants<double>::pi ? -1.0 + (2.0 / MathConstants<double>::pi) * currentPhase
: 3.0 - (2.0 / MathConstants<double>::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<float> 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<int> (newPressure * 127));
}
/** Send pitch change message to synthesiser */
void pitchChange (int channel, float pitchChange)
{
synthesiser.handlePitchWheel (channel, static_cast<int> (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<double>::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<uint8> (roundToInt ((sineOutput * 6.5) + 7.0));
// Square wave output, set flags for when vertical line should be drawn
if (currentPhase < MathConstants<double>::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<uint8> (x) : static_cast<uint8> (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<DrumPadGridProgram::GridFill> gridFillArray;
Colour baseGridColour = Colours::green;
Colour touchColour = Colours::red;
Array<int> tonics = { 4, 12, 20 };
Array<int> 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<float> (grid->getNumColumns() - 1) / (float) activeBlock->getWidth();
scaleY = static_cast<float> (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<uint8> (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<WaveshapeProgram>(block));
// Initialise the program
if (auto* waveshapeProgram = getWaveshapeProgram())
{
waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode));
waveshapeProgram->generateWaveshapes();
}
}
else if (currentMode == playMode)
{
// Set the LEDGrid program
auto error = block.setProgram (std::make_unique<DrumPadGridProgram>(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<DrumPadGridProgram*> (activeBlock->getProgram());
return nullptr;
}
WaveshapeProgram* getWaveshapeProgram()
{
if (activeBlock != nullptr)
return dynamic_cast<WaveshapeProgram*> (activeBlock->getProgram());
return nullptr;
}
//==============================================================================
enum BlocksSynthMode
{
waveformSelectionMode = 0,
playMode
};
BlocksSynthMode currentMode = playMode;
//==============================================================================
Audio audio;
SynthGrid layout { 5, 5 };
PhysicalTopologySource topologySource;
Block::Ptr activeBlock;
Array<Time> touchMessageTimesInLastSecond;
int waveshapeMode = 0;
float scaleX = 0.0f;
float scaleY = 0.0f;
bool allowTouch = true;
//==============================================================================
#if JUCE_IOS
TextButton connectButton;
#endif
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlocksSynthDemo)
};

View file

@ -1,24 +0,0 @@
# ==============================================================================
#
# This file is part of the JUCE library.
# Copyright (c) 2020 - Raw Material Software Limited
#
# JUCE is an open source library subject to commercial or open-source
# licensing.
#
# By using JUCE, you agree to the terms of both the JUCE 6 End-User License
# Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
#
# End User License Agreement: www.juce.com/juce-6-licence
# Privacy Policy: www.juce.com/juce-privacy-policy
#
# Or: You may also use this code under the terms of the GPL v3 (see
# www.gnu.org/licenses).
#
# JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
# EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
# DISCLAIMED.
#
# ==============================================================================
_juce_add_pips()

View file

@ -51,7 +51,6 @@ function(_juce_add_pips)
endfunction()
add_subdirectory(Audio)
add_subdirectory(BLOCKS)
add_subdirectory(DSP)
add_subdirectory(GUI)
add_subdirectory(Plugins)

View file

@ -65,7 +65,6 @@ target_compile_definitions(DemoRunner PRIVATE
target_link_libraries(DemoRunner PRIVATE
juce::juce_analytics
juce::juce_audio_utils
juce::juce_blocks_basics
juce::juce_box2d
juce::juce_dsp
juce::juce_opengl
@ -76,6 +75,6 @@ target_link_libraries(DemoRunner PRIVATE
juce::juce_recommended_lto_flags
juce::juce_recommended_warning_flags)
foreach(folder IN ITEMS ../Assets ../Audio ../BLOCKS ../DSP ../GUI ../Utilities)
foreach(folder IN ITEMS ../Assets ../Audio ../DSP ../GUI ../Utilities)
juce_add_bundle_resources_directory(DemoRunner ${folder})
endforeach()

View file

@ -42,10 +42,6 @@
#include "../../../Audio/PluckedStringsDemo.h"
#include "../../../Audio/SimpleFFTDemo.h"
#include "../../../BLOCKS/BlocksDrawingDemo.h"
#include "../../../BLOCKS/BlocksMonitorDemo.h"
#include "../../../BLOCKS/BlocksSynthDemo.h"
#include "../../../DSP/ConvolutionDemo.h"
#include "../../../DSP/FIRFilterDemo.h"
#include "../../../DSP/GainDemo.h"
@ -85,11 +81,7 @@ void registerDemos_One() noexcept
REGISTER_DEMO (MidiDemo, Audio, false)
REGISTER_DEMO (MPEDemo, Audio, false)
REGISTER_DEMO (PluckedStringsDemo, Audio, false)
REGISTER_DEMO (SimpleFFTDemo, Audio, false)
REGISTER_DEMO (BlocksDrawingDemo, BLOCKS, false)
REGISTER_DEMO (BlocksMonitorDemo, BLOCKS, false)
REGISTER_DEMO (BlocksSynthDemo, BLOCKS, false)
REGISTER_DEMO (ConvolutionDemo, DSP, false)
REGISTER_DEMO (FIRFilterDemo, DSP, false)