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.

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