The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

867 lines
29KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. The code included in this file is provided under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  7. To use, copy, modify, and/or distribute this software for any purpose with or
  8. without fee is hereby granted provided that the above copyright notice and
  9. this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
  11. WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
  12. PURPOSE, ARE DISCLAIMED.
  13. ==============================================================================
  14. */
  15. /*******************************************************************************
  16. The block below describes the properties of this PIP. A PIP is a short snippet
  17. of code that can be read by the Projucer and used to generate a JUCE project.
  18. BEGIN_JUCE_PIP_METADATA
  19. name: BlocksSynthDemo
  20. version: 1.0.0
  21. vendor: JUCE
  22. website: http://juce.com
  23. description: Blocks synthesiser application.
  24. dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats,
  25. juce_audio_processors, juce_audio_utils, juce_blocks_basics,
  26. juce_core, juce_data_structures, juce_events, juce_graphics,
  27. juce_gui_basics, juce_gui_extra
  28. exporters: xcode_mac, vs2019, linux_make, xcode_iphone
  29. moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
  30. type: Component
  31. mainClass: BlocksSynthDemo
  32. useLocalCopy: 1
  33. END_JUCE_PIP_METADATA
  34. *******************************************************************************/
  35. #pragma once
  36. //==============================================================================
  37. /**
  38. Base class for oscillators
  39. */
  40. class OscillatorBase : public SynthesiserVoice
  41. {
  42. public:
  43. OscillatorBase()
  44. {
  45. amplitude.reset (getSampleRate(), 0.1);
  46. phaseIncrement.reset (getSampleRate(), 0.1);
  47. }
  48. void startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int) override
  49. {
  50. frequency = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
  51. phaseIncrement.setTargetValue (((MathConstants<double>::twoPi) * frequency) / sampleRate);
  52. amplitude.setTargetValue (velocity);
  53. // Store the initial note and work out the maximum frequency deviations for pitch bend
  54. initialNote = midiNoteNumber;
  55. maxFreq = MidiMessage::getMidiNoteInHertz (initialNote + 4) - frequency;
  56. minFreq = frequency - MidiMessage::getMidiNoteInHertz (initialNote - 4);
  57. }
  58. void stopNote (float, bool) override
  59. {
  60. clearCurrentNote();
  61. amplitude.setTargetValue (0.0);
  62. }
  63. void pitchWheelMoved (int newValue) override
  64. {
  65. // Change the phase increment based on pitch bend amount
  66. auto frequencyOffset = ((newValue > 0 ? maxFreq : minFreq) * (newValue / 127.0));
  67. phaseIncrement.setTargetValue (((MathConstants<double>::twoPi) * (frequency + frequencyOffset)) / sampleRate);
  68. }
  69. void controllerMoved (int, int) override {}
  70. void channelPressureChanged (int newChannelPressureValue) override
  71. {
  72. // Set the amplitude based on pressure value
  73. amplitude.setTargetValue (newChannelPressureValue / 127.0);
  74. }
  75. void renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
  76. {
  77. while (--numSamples >= 0)
  78. {
  79. auto output = getSample() * amplitude.getNextValue();
  80. for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
  81. outputBuffer.addSample (i, startSample, static_cast<float> (output));
  82. ++startSample;
  83. }
  84. }
  85. using SynthesiserVoice::renderNextBlock;
  86. /** Returns the next sample */
  87. double getSample()
  88. {
  89. auto output = renderWaveShape (phasePos);
  90. phasePos += phaseIncrement.getNextValue();
  91. if (phasePos > MathConstants<double>::twoPi)
  92. phasePos -= MathConstants<double>::twoPi;
  93. return output;
  94. }
  95. /** Subclasses should override this to say whether they can play the given sound */
  96. bool canPlaySound (SynthesiserSound*) override = 0;
  97. /** Subclasses should override this to render a waveshape */
  98. virtual double renderWaveShape (const double currentPhase) = 0;
  99. private:
  100. SmoothedValue<double> amplitude, phaseIncrement;
  101. double frequency = 0.0;
  102. double phasePos = 0.0;
  103. double sampleRate = 44100.0;
  104. int initialNote = 0;
  105. double maxFreq = 0.0, minFreq = 0.0;
  106. //==============================================================================
  107. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OscillatorBase)
  108. };
  109. //==============================================================================
  110. /**
  111. Sine sound struct - applies to MIDI channel 1
  112. */
  113. struct SineSound : public SynthesiserSound
  114. {
  115. SineSound () {}
  116. bool appliesToNote (int) override { return true; }
  117. bool appliesToChannel (int midiChannel) override { return (midiChannel == 1); }
  118. //==============================================================================
  119. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SineSound)
  120. };
  121. /**
  122. Sine voice struct that renders a sin waveshape
  123. */
  124. struct SineVoice : public OscillatorBase
  125. {
  126. SineVoice() {}
  127. bool canPlaySound (SynthesiserSound* sound) override { return dynamic_cast<SineSound*> (sound) != nullptr; }
  128. double renderWaveShape (const double currentPhase) override { return sin (currentPhase); }
  129. //==============================================================================
  130. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SineVoice)
  131. };
  132. //==============================================================================
  133. /**
  134. Square sound struct - applies to MIDI channel 2
  135. */
  136. struct SquareSound : public SynthesiserSound
  137. {
  138. SquareSound() {}
  139. bool appliesToNote (int) override { return true; }
  140. bool appliesToChannel (int midiChannel) override { return (midiChannel == 2); }
  141. //==============================================================================
  142. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SquareSound)
  143. };
  144. /**
  145. Square voice struct that renders a square waveshape
  146. */
  147. struct SquareVoice : public OscillatorBase
  148. {
  149. SquareVoice() {}
  150. bool canPlaySound (SynthesiserSound* sound) override { return dynamic_cast<SquareSound*> (sound) != nullptr; }
  151. double renderWaveShape (const double currentPhase) override { return (currentPhase < MathConstants<double>::pi ? 0.0 : 1.0); }
  152. //==============================================================================
  153. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SquareVoice)
  154. };
  155. //==============================================================================
  156. /**
  157. Sawtooth sound - applies to MIDI channel 3
  158. */
  159. struct SawSound : public SynthesiserSound
  160. {
  161. SawSound() {}
  162. bool appliesToNote (int) override { return true; }
  163. bool appliesToChannel (int midiChannel) override { return (midiChannel == 3); }
  164. //==============================================================================
  165. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SawSound)
  166. };
  167. /**
  168. Sawtooth voice that renders a sawtooth waveshape
  169. */
  170. struct SawVoice : public OscillatorBase
  171. {
  172. SawVoice() {}
  173. bool canPlaySound (SynthesiserSound* sound) override { return dynamic_cast<SawSound*> (sound) != nullptr; }
  174. double renderWaveShape (const double currentPhase) override { return (1.0 / MathConstants<double>::pi) * currentPhase - 1.0; }
  175. //==============================================================================
  176. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SawVoice)
  177. };
  178. //==============================================================================
  179. /**
  180. Triangle sound - applies to MIDI channel 4
  181. */
  182. struct TriangleSound : public SynthesiserSound
  183. {
  184. TriangleSound() {}
  185. bool appliesToNote (int) override { return true; }
  186. bool appliesToChannel (int midiChannel) override { return (midiChannel == 4); }
  187. //==============================================================================
  188. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TriangleSound)
  189. };
  190. /**
  191. Triangle voice that renders a triangle waveshape
  192. */
  193. struct TriangleVoice : public OscillatorBase
  194. {
  195. TriangleVoice() {}
  196. bool canPlaySound (SynthesiserSound* sound) override { return dynamic_cast<TriangleSound*> (sound) != nullptr; }
  197. double renderWaveShape (const double currentPhase) override
  198. {
  199. return currentPhase < MathConstants<double>::pi ? -1.0 + (2.0 / MathConstants<double>::pi) * currentPhase
  200. : 3.0 - (2.0 / MathConstants<double>::pi) * currentPhase;
  201. }
  202. //==============================================================================
  203. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TriangleVoice)
  204. };
  205. //==============================================================================
  206. /**
  207. Class to handle the Audio functionality
  208. */
  209. class Audio : public AudioIODeviceCallback
  210. {
  211. public:
  212. Audio()
  213. {
  214. // Set up the audio device manager
  215. #ifndef JUCE_DEMO_RUNNER
  216. audioDeviceManager.initialiseWithDefaultDevices (0, 2);
  217. #endif
  218. audioDeviceManager.addAudioCallback (this);
  219. // Set up the synthesiser and add each of the waveshapes
  220. synthesiser.clearVoices();
  221. synthesiser.clearSounds();
  222. synthesiser.addVoice (new SineVoice());
  223. synthesiser.addVoice (new SquareVoice());
  224. synthesiser.addVoice (new SawVoice());
  225. synthesiser.addVoice (new TriangleVoice());
  226. synthesiser.addSound (new SineSound());
  227. synthesiser.addSound (new SquareSound());
  228. synthesiser.addSound (new SawSound());
  229. synthesiser.addSound (new TriangleSound());
  230. }
  231. ~Audio() override
  232. {
  233. audioDeviceManager.removeAudioCallback (this);
  234. }
  235. /** Audio callback */
  236. void audioDeviceIOCallback (const float** /*inputChannelData*/, int /*numInputChannels*/,
  237. float** outputChannelData, int numOutputChannels, int numSamples) override
  238. {
  239. AudioBuffer<float> sampleBuffer (outputChannelData, numOutputChannels, numSamples);
  240. sampleBuffer.clear();
  241. synthesiser.renderNextBlock (sampleBuffer, MidiBuffer(), 0, numSamples);
  242. }
  243. void audioDeviceAboutToStart (AudioIODevice* device) override
  244. {
  245. synthesiser.setCurrentPlaybackSampleRate (device->getCurrentSampleRate());
  246. }
  247. void audioDeviceStopped() override {}
  248. /** Called to turn a synthesiser note on */
  249. void noteOn (int channel, int noteNum, float velocity)
  250. {
  251. synthesiser.noteOn (channel, noteNum, velocity);
  252. }
  253. /** Called to turn a synthesiser note off */
  254. void noteOff (int channel, int noteNum, float velocity)
  255. {
  256. synthesiser.noteOff (channel, noteNum, velocity, false);
  257. }
  258. /** Called to turn all synthesiser notes off */
  259. void allNotesOff()
  260. {
  261. for (auto i = 1; i < 5; ++i)
  262. synthesiser.allNotesOff (i, false);
  263. }
  264. /** Send pressure change message to synthesiser */
  265. void pressureChange (int channel, float newPressure)
  266. {
  267. synthesiser.handleChannelPressure (channel, static_cast<int> (newPressure * 127));
  268. }
  269. /** Send pitch change message to synthesiser */
  270. void pitchChange (int channel, float pitchChange)
  271. {
  272. synthesiser.handlePitchWheel (channel, static_cast<int> (pitchChange * 127));
  273. }
  274. private:
  275. #ifndef JUCE_DEMO_RUNNER
  276. AudioDeviceManager audioDeviceManager;
  277. #else
  278. AudioDeviceManager& audioDeviceManager { getSharedAudioDeviceManager (0, 2) };
  279. #endif
  280. Synthesiser synthesiser;
  281. //==============================================================================
  282. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Audio)
  283. };
  284. //==============================================================================
  285. /**
  286. A Program to draw moving waveshapes onto the LEDGrid
  287. */
  288. class WaveshapeProgram : public Block::Program
  289. {
  290. public:
  291. WaveshapeProgram (Block& b) : Program (b) {}
  292. /** Sets the waveshape type to display on the grid */
  293. void setWaveshapeType (uint8 type)
  294. {
  295. block.setDataByte (0, type);
  296. }
  297. /** Generates the Y coordinates for 1.5 cycles of each of the four waveshapes and stores them
  298. at the correct offsets in the shared data heap. */
  299. void generateWaveshapes()
  300. {
  301. uint8 sineWaveY[45];
  302. uint8 squareWaveY[45];
  303. uint8 sawWaveY[45];
  304. uint8 triangleWaveY[45];
  305. // Set current phase position to 0 and work out the required phase increment for one cycle
  306. auto currentPhase = 0.0;
  307. auto phaseInc = (1.0 / 30.0) * MathConstants<double>::twoPi;
  308. for (auto x = 0; x < 30; ++x)
  309. {
  310. // Scale and offset the sin output to the Lightpad display
  311. auto sineOutput = std::sin (currentPhase);
  312. sineWaveY[x] = static_cast<uint8> (roundToInt ((sineOutput * 6.5) + 7.0));
  313. // Square wave output, set flags for when vertical line should be drawn
  314. if (currentPhase < MathConstants<double>::pi)
  315. {
  316. if (x == 0)
  317. squareWaveY[x] = 255;
  318. else
  319. squareWaveY[x] = 1;
  320. }
  321. else
  322. {
  323. if (x > 0 && squareWaveY[x - 1] == 1)
  324. squareWaveY[x - 1] = 255;
  325. squareWaveY[x] = 13;
  326. }
  327. // Saw wave output, set flags for when vertical line should be drawn
  328. sawWaveY[x] = 14 - ((x / 2) % 15);
  329. if (sawWaveY[x] == 0 && sawWaveY[x - 1] != 255)
  330. sawWaveY[x] = 255;
  331. // Triangle wave output
  332. triangleWaveY[x] = x < 15 ? static_cast<uint8> (x) : static_cast<uint8> (14 - (x % 15));
  333. // Add half cycle to end of array so it loops correctly
  334. if (x < 15)
  335. {
  336. sineWaveY[x + 30] = sineWaveY[x];
  337. squareWaveY[x + 30] = squareWaveY[x];
  338. sawWaveY[x + 30] = sawWaveY[x];
  339. triangleWaveY[x + 30] = triangleWaveY[x];
  340. }
  341. // Increment the current phase
  342. currentPhase += phaseInc;
  343. }
  344. // Store the values for each of the waveshapes at the correct offsets in the shared data heap
  345. for (uint8 i = 0; i < 45; ++i)
  346. {
  347. block.setDataByte (sineWaveOffset + i, sineWaveY[i]);
  348. block.setDataByte (squareWaveOffset + i, squareWaveY[i]);
  349. block.setDataByte (sawWaveOffset + i, sawWaveY[i]);
  350. block.setDataByte (triangleWaveOffset + i, triangleWaveY[i]);
  351. }
  352. }
  353. String getLittleFootProgram() override
  354. {
  355. return R"littlefoot(
  356. #heapsize: 256
  357. int yOffset;
  358. void drawLEDCircle (int x0, int y0)
  359. {
  360. blendPixel (0xffff0000, x0, y0);
  361. int minLedIndex = 0;
  362. int maxLedIndex = 14;
  363. blendPixel (0xff660000, min (x0 + 1, maxLedIndex), y0);
  364. blendPixel (0xff660000, max (x0 - 1, minLedIndex), y0);
  365. blendPixel (0xff660000, x0, min (y0 + 1, maxLedIndex));
  366. blendPixel (0xff660000, x0, max (y0 - 1, minLedIndex));
  367. blendPixel (0xff1a0000, min (x0 + 1, maxLedIndex), min (y0 + 1, maxLedIndex));
  368. blendPixel (0xff1a0000, min (x0 + 1, maxLedIndex), max (y0 - 1, minLedIndex));
  369. blendPixel (0xff1a0000, max (x0 - 1, minLedIndex), min (y0 + 1, maxLedIndex));
  370. blendPixel (0xff1a0000, max (x0 - 1, minLedIndex), max (y0 - 1, minLedIndex));
  371. }
  372. void repaint()
  373. {
  374. // Clear LEDs to black
  375. fillRect (0xff000000, 0, 0, 15, 15);
  376. // Get the waveshape type
  377. int type = getHeapByte (0);
  378. // Calculate the heap offset
  379. int offset = 1 + (type * 45) + yOffset;
  380. for (int x = 0; x < 15; ++x)
  381. {
  382. // Get the corresponding Y coordinate for each X coordinate
  383. int y = getHeapByte (offset + x);
  384. // Draw a vertical line if flag is set or draw an LED circle
  385. if (y == 255)
  386. {
  387. for (int i = 0; i < 15; ++i)
  388. drawLEDCircle (x, i);
  389. }
  390. else if (x % 2 == 0)
  391. {
  392. drawLEDCircle (x, y);
  393. }
  394. }
  395. // Increment and wrap the Y offset to draw a 'moving' waveshape
  396. if (++yOffset == 30)
  397. yOffset = 0;
  398. }
  399. )littlefoot";
  400. }
  401. private:
  402. //==============================================================================
  403. /** Shared data heap is laid out as below. There is room for the waveshape type and
  404. the Y coordinates for 1.5 cycles of each of the four waveshapes. */
  405. static constexpr uint32 waveshapeType = 0; // 1 byte
  406. static constexpr uint32 sineWaveOffset = 1; // 1 byte * 45
  407. static constexpr uint32 squareWaveOffset = 46; // 1 byte * 45
  408. static constexpr uint32 sawWaveOffset = 91; // 1 byte * 45
  409. static constexpr uint32 triangleWaveOffset = 136; // 1 byte * 45
  410. //==============================================================================
  411. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaveshapeProgram)
  412. };
  413. //==============================================================================
  414. /**
  415. A struct that handles the setup and layout of the DrumPadGridProgram
  416. */
  417. struct SynthGrid
  418. {
  419. SynthGrid (int cols, int rows)
  420. : numColumns (cols),
  421. numRows (rows)
  422. {
  423. constructGridFillArray();
  424. }
  425. /** Creates a GridFill object for each pad in the grid and sets its colour
  426. and fill before adding it to an array of GridFill objects
  427. */
  428. void constructGridFillArray()
  429. {
  430. gridFillArray.clear();
  431. for (auto i = 0; i < numRows; ++i)
  432. {
  433. for (auto j = 0; j < numColumns; ++j)
  434. {
  435. DrumPadGridProgram::GridFill fill;
  436. auto padNum = (i * 5) + j;
  437. fill.colour = notes.contains (padNum) ? baseGridColour
  438. : tonics.contains (padNum) ? Colours::white
  439. : Colours::black;
  440. fill.fillType = DrumPadGridProgram::GridFill::FillType::gradient;
  441. gridFillArray.add (fill);
  442. }
  443. }
  444. }
  445. int getNoteNumberForPad (int x, int y) const
  446. {
  447. auto xIndex = x / 3;
  448. auto yIndex = y / 3;
  449. return 60 + ((4 - yIndex) * 5) + xIndex;
  450. }
  451. //==============================================================================
  452. int numColumns, numRows;
  453. float width, height;
  454. Array<DrumPadGridProgram::GridFill> gridFillArray;
  455. Colour baseGridColour = Colours::green;
  456. Colour touchColour = Colours::red;
  457. Array<int> tonics = { 4, 12, 20 };
  458. Array<int> notes = { 1, 3, 6, 7, 9, 11, 14, 15, 17, 19, 22, 24 };
  459. //==============================================================================
  460. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SynthGrid)
  461. };
  462. //==============================================================================
  463. /**
  464. The main component
  465. */
  466. class BlocksSynthDemo : public Component,
  467. public TopologySource::Listener,
  468. private TouchSurface::Listener,
  469. private ControlButton::Listener,
  470. private Timer
  471. {
  472. public:
  473. BlocksSynthDemo()
  474. {
  475. // Register BlocksSynthDemo as a listener to the PhysicalTopologySource object
  476. topologySource.addListener (this);
  477. #if JUCE_IOS
  478. connectButton.setButtonText ("Connect");
  479. connectButton.onClick = [] { BluetoothMidiDevicePairingDialogue::open(); };
  480. addAndMakeVisible (connectButton);
  481. #endif
  482. setSize (600, 400);
  483. topologyChanged();
  484. }
  485. ~BlocksSynthDemo() override
  486. {
  487. if (activeBlock != nullptr)
  488. detachActiveBlock();
  489. topologySource.removeListener (this);
  490. }
  491. void paint (Graphics& g) override
  492. {
  493. g.setColour (getLookAndFeel().findColour (Label::textColourId));
  494. g.drawText ("Connect a Lightpad Block to play.",
  495. getLocalBounds(), Justification::centred, false);
  496. }
  497. void resized() override
  498. {
  499. #if JUCE_IOS
  500. connectButton.setBounds (getRight() - 100, 20, 80, 30);
  501. #endif
  502. }
  503. /** Overridden from TopologySource::Listener, called when the topology changes */
  504. void topologyChanged() override
  505. {
  506. // Reset the activeBlock object
  507. if (activeBlock != nullptr)
  508. detachActiveBlock();
  509. // Get the array of currently connected Block objects from the PhysicalTopologySource
  510. auto blocks = topologySource.getBlocks();
  511. // Iterate over the array of Block objects
  512. for (auto b : blocks)
  513. {
  514. // Find the first Lightpad
  515. if (b->getType() == Block::Type::lightPadBlock)
  516. {
  517. activeBlock = b;
  518. // Register BlocksSynthDemo as a listener to the touch surface
  519. if (auto surface = activeBlock->getTouchSurface())
  520. surface->addListener (this);
  521. // Register BlocksSynthDemo as a listener to any buttons
  522. for (auto button : activeBlock->getButtons())
  523. button->addListener (this);
  524. // Get the LEDGrid object from the Lightpad and set its program to the program for the current mode
  525. if (auto grid = activeBlock->getLEDGrid())
  526. {
  527. // Work out scale factors to translate X and Y touches to LED indexes
  528. scaleX = static_cast<float> (grid->getNumColumns() - 1) / (float) activeBlock->getWidth();
  529. scaleY = static_cast<float> (grid->getNumRows() - 1) / (float) activeBlock->getHeight();
  530. setLEDProgram (*activeBlock);
  531. }
  532. break;
  533. }
  534. }
  535. }
  536. private:
  537. /** Overridden from TouchSurface::Listener. Called when a Touch is received on the Lightpad */
  538. void touchChanged (TouchSurface&, const TouchSurface::Touch& touch) override
  539. {
  540. if (currentMode == waveformSelectionMode && touch.isTouchStart && allowTouch)
  541. {
  542. if (auto* waveshapeProgram = getWaveshapeProgram())
  543. {
  544. // Change the displayed waveshape to the next one
  545. ++waveshapeMode;
  546. if (waveshapeMode > 3)
  547. waveshapeMode = 0;
  548. waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode));
  549. allowTouch = false;
  550. startTimer (250);
  551. }
  552. }
  553. else if (currentMode == playMode)
  554. {
  555. if (auto* gridProgram = getGridProgram())
  556. {
  557. // Translate X and Y touch events to LED indexes
  558. auto xLed = roundToInt (touch.startX * scaleX);
  559. auto yLed = roundToInt (touch.startY * scaleY);
  560. // Limit the number of touches per second
  561. constexpr auto maxNumTouchMessagesPerSecond = 100;
  562. auto now = Time::getCurrentTime();
  563. clearOldTouchTimes (now);
  564. auto midiChannel = waveshapeMode + 1;
  565. // Send the touch event to the DrumPadGridProgram and Audio class
  566. if (touch.isTouchStart)
  567. {
  568. gridProgram->startTouch (touch.startX, touch.startY);
  569. audio.noteOn (midiChannel, layout.getNoteNumberForPad (xLed, yLed), touch.z);
  570. }
  571. else if (touch.isTouchEnd)
  572. {
  573. gridProgram->endTouch (touch.startX, touch.startY);
  574. audio.noteOff (midiChannel, layout.getNoteNumberForPad (xLed, yLed), 1.0);
  575. }
  576. else
  577. {
  578. if (touchMessageTimesInLastSecond.size() > maxNumTouchMessagesPerSecond / 3)
  579. return;
  580. gridProgram->sendTouch (touch.x, touch.y, touch.z,
  581. layout.touchColour);
  582. // Send pitch change and pressure values to the Audio class
  583. audio.pitchChange (midiChannel, (touch.x - touch.startX) / (float) activeBlock->getWidth());
  584. audio.pressureChange (midiChannel, touch.z);
  585. }
  586. touchMessageTimesInLastSecond.add (now);
  587. }
  588. }
  589. }
  590. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is pressed */
  591. void buttonPressed (ControlButton&, Block::Timestamp) override {}
  592. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is released */
  593. void buttonReleased (ControlButton&, Block::Timestamp) override
  594. {
  595. // Turn any active synthesiser notes off
  596. audio.allNotesOff();
  597. // Switch modes
  598. if (currentMode == waveformSelectionMode)
  599. currentMode = playMode;
  600. else if (currentMode == playMode)
  601. currentMode = waveformSelectionMode;
  602. // Set the LEDGrid program to the new mode
  603. setLEDProgram (*activeBlock);
  604. }
  605. /** Clears the old touch times */
  606. void clearOldTouchTimes (const Time now)
  607. {
  608. for (auto i = touchMessageTimesInLastSecond.size(); --i >= 0;)
  609. if (touchMessageTimesInLastSecond.getReference(i) < now - RelativeTime::seconds (0.33))
  610. touchMessageTimesInLastSecond.remove (i);
  611. }
  612. /** Removes TouchSurface and ControlButton listeners and sets activeBlock to nullptr */
  613. void detachActiveBlock()
  614. {
  615. if (auto surface = activeBlock->getTouchSurface())
  616. surface->removeListener (this);
  617. for (auto button : activeBlock->getButtons())
  618. button->removeListener (this);
  619. activeBlock = nullptr;
  620. }
  621. /** Sets the LEDGrid Program for the selected mode */
  622. void setLEDProgram (Block& block)
  623. {
  624. if (currentMode == waveformSelectionMode)
  625. {
  626. // Set the LEDGrid program
  627. block.setProgram (std::make_unique<WaveshapeProgram>(block));
  628. // Initialise the program
  629. if (auto* waveshapeProgram = getWaveshapeProgram())
  630. {
  631. waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode));
  632. waveshapeProgram->generateWaveshapes();
  633. }
  634. }
  635. else if (currentMode == playMode)
  636. {
  637. // Set the LEDGrid program
  638. auto error = block.setProgram (std::make_unique<DrumPadGridProgram>(block));
  639. if (error.failed())
  640. {
  641. DBG (error.getErrorMessage());
  642. jassertfalse;
  643. }
  644. // Setup the grid layout
  645. if (auto* gridProgram = getGridProgram())
  646. gridProgram->setGridFills (layout.numColumns, layout.numRows, layout.gridFillArray);
  647. }
  648. }
  649. /** Stops touch events from triggering multiple waveshape mode changes */
  650. void timerCallback() override { allowTouch = true; }
  651. //==============================================================================
  652. DrumPadGridProgram* getGridProgram()
  653. {
  654. if (activeBlock != nullptr)
  655. return dynamic_cast<DrumPadGridProgram*> (activeBlock->getProgram());
  656. return nullptr;
  657. }
  658. WaveshapeProgram* getWaveshapeProgram()
  659. {
  660. if (activeBlock != nullptr)
  661. return dynamic_cast<WaveshapeProgram*> (activeBlock->getProgram());
  662. return nullptr;
  663. }
  664. //==============================================================================
  665. enum BlocksSynthMode
  666. {
  667. waveformSelectionMode = 0,
  668. playMode
  669. };
  670. BlocksSynthMode currentMode = playMode;
  671. //==============================================================================
  672. Audio audio;
  673. SynthGrid layout { 5, 5 };
  674. PhysicalTopologySource topologySource;
  675. Block::Ptr activeBlock;
  676. Array<Time> touchMessageTimesInLastSecond;
  677. int waveshapeMode = 0;
  678. float scaleX = 0.0f;
  679. float scaleY = 0.0f;
  680. bool allowTouch = true;
  681. //==============================================================================
  682. #if JUCE_IOS
  683. TextButton connectButton;
  684. #endif
  685. //==============================================================================
  686. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BlocksSynthDemo)
  687. };