|
-
- #ifndef MAINCOMPONENT_H_INCLUDED
- #define MAINCOMPONENT_H_INCLUDED
-
- #include "../JuceLibraryCode/JuceHeader.h"
- #include "Audio.h"
-
- //==============================================================================
- /**
- 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 (int i = 0; i < numRows; ++i)
- {
- for (int j = 0; j < numColumns; ++j)
- {
- DrumPadGridProgram::GridFill fill;
-
- int 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
- {
- int xIndex = x / 3;
- int 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::cyan;
-
- 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 MainComponent : public Component,
- public TopologySource::Listener,
- private TouchSurface::Listener,
- private ControlButton::Listener,
- private Timer
- {
- public:
- MainComponent()
- {
- setSize (600, 400);
-
- // Register MainContentComponent as a listener to the PhysicalTopologySource object
- topologySource.addListener (this);
-
- generateWaveshapes();
- };
-
- ~MainComponent()
- {
- if (activeBlock != nullptr)
- detachActiveBlock();
- }
-
- void paint (Graphics& g) override
- {
- g.fillAll (Colours::lightgrey);
- g.drawText ("Connect a Lightpad Block to play.",
- getLocalBounds(), Justification::centred, false);
- }
-
- void resized() override {}
-
- /** 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.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 = static_cast<float> (grid->getNumColumns() - 1) / activeBlock->getWidth();
- scaleY = static_cast<float> (grid->getNumRows() - 1) / activeBlock->getHeight();
-
- setLEDProgram (grid);
- }
-
- 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)
- {
- // Change the displayed waveshape to the next one
- ++waveshapeMode;
-
- if (waveshapeMode > 3)
- waveshapeMode = 0;
- }
- else if (currentMode == playMode)
- {
- // Translate X and Y touch events to LED indexes
- int xLed = roundToInt (touch.startX * scaleX);
- int yLed = roundToInt (touch.startY * scaleY);
-
- // Limit the number of touches per second
- constexpr int maxNumTouchMessagesPerSecond = 100;
- auto now = Time::getCurrentTime();
- clearOldTouchTimes (now);
-
- int 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) / 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->getLEDGrid());
- }
-
- void timerCallback() override
- {
- // Clear all LEDs
- for (uint32 x = 0; x < 15; ++x)
- for (uint32 y = 0; y < 15; ++y)
- setLED (x, y, Colours::black);
-
- // Determine which array to use based on waveshapeMode
- int* waveshapeY = nullptr;
-
- switch (waveshapeMode)
- {
- 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 (uint32 x = 0; x < 15; ++x)
- {
- // Find the corresponding Y co-ordinate for the current waveshape
- int y = waveshapeY[x + yOffset];
-
- // Draw a vertical line if flag is set or draw an LED circle
- if (y == -1)
- {
- for (uint32 i = 0; i < 15; ++i)
- drawLEDCircle (x, i);
- }
- else if (x % 2 == 0)
- {
- drawLEDCircle (x, static_cast<uint32> (y));
- }
- }
-
- // Increment the offset to draw a 'moving' waveshape
- if (++yOffset == 30)
- yOffset -= 30;
- }
-
- /** Clears the old touch times */
- void clearOldTouchTimes (const Time now)
- {
- for (int i = touchMessageTimesInLastSecond.size(); --i >= 0;)
- if (touchMessageTimesInLastSecond.getReference(i) < now - juce::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 (LEDGrid* grid)
- {
- if (currentMode == waveformSelectionMode)
- {
- // Create a new BitmapLEDProgram for the LEDGrid
- bitmapProgram = new BitmapLEDProgram (*grid);
-
- // Set the LEDGrid program
- grid->setProgram (bitmapProgram);
-
- // Redraw at 25Hz
- startTimerHz (25);
- }
- else if (currentMode == playMode)
- {
- // Stop the redraw timer
- stopTimer();
-
- // Create a new DrumPadGridProgram for the LEDGrid
- gridProgram = new DrumPadGridProgram (*grid);
-
- // Set the LEDGrid program
- grid->setProgram (gridProgram);
-
- // Setup the grid layout
- gridProgram->setGridFills (layout.numColumns,
- layout.numRows,
- layout.gridFillArray);
- }
- }
-
- /** Generates the X and Y co-ordiantes for 1.5 cycles of each of the 4 waveshapes and stores them in arrays */
- void generateWaveshapes()
- {
- // Set current phase position to 0 and work out the required phase increment for one cycle
- double currentPhase = 0.0;
- double phaseInc = (1.0 / 30.0) * (2.0 * double_Pi);
-
- for (int x = 0; x < 30; ++x)
- {
- // Scale and offset the sin output to the Lightpad display
- double sineOutput = sin (currentPhase);
- sineWaveY[x] = roundToInt ((sineOutput * 6.5) + 7.0);
-
- // Square wave output, set flags for when vertical line should be drawn
- if (currentPhase < double_Pi)
- {
- if (x == 0)
- squareWaveY[x] = -1;
- else
- squareWaveY[x] = 1;
- }
- else
- {
- if (squareWaveY[x - 1] == 1)
- squareWaveY[x - 1] = -1;
-
- 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] != -1)
- sawWaveY[x] = -1;
-
- // Triangle wave output
- triangleWaveY[x] = x < 15 ? x : 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;
- }
- }
-
- /** 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 */
- void drawLEDCircle (uint32 x0, uint32 y0)
- {
- setLED (x0, y0, waveshapeColour);
-
- const uint32 minLedIndex = 0;
- const uint32 maxLedIndex = 14;
-
- 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));
-
- 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 BlocksSynthMode
- {
- waveformSelectionMode = 0,
- playMode
- };
-
- BlocksSynthMode currentMode = playMode;
-
- //==============================================================================
- Audio audio;
-
- DrumPadGridProgram* gridProgram = nullptr;
- BitmapLEDProgram* bitmapProgram = nullptr;
-
- SynthGrid layout { 5, 5 };
- PhysicalTopologySource topologySource;
- Block::Ptr activeBlock;
-
- Array<juce::Time> touchMessageTimesInLastSecond;
-
- Colour waveshapeColour = Colours::red;
-
- int sineWaveY[45];
- int squareWaveY[45];
- int sawWaveY[45];
- int triangleWaveY[45];
-
- int waveshapeMode = 0;
- uint32 yOffset = 0;
-
- float scaleX = 0.0;
- float scaleY = 0.0;
-
- //==============================================================================
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
- };
-
-
- #endif // MAINCOMPONENT_H_INCLUDED
|