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.

865 lines
29KB

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