|
- /*
- ==============================================================================
-
- 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)
- };
|