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.

329 lines
11KB

  1. #pragma once
  2. #include "../JuceLibraryCode/JuceHeader.h"
  3. #include "Audio.h"
  4. #include "WaveshapeProgram.h"
  5. //==============================================================================
  6. /**
  7. A struct that handles the setup and layout of the DrumPadGridProgram
  8. */
  9. struct SynthGrid
  10. {
  11. SynthGrid (int cols, int rows)
  12. : numColumns (cols),
  13. numRows (rows)
  14. {
  15. constructGridFillArray();
  16. }
  17. /** Creates a GridFill object for each pad in the grid and sets its colour
  18. and fill before adding it to an array of GridFill objects
  19. */
  20. void constructGridFillArray()
  21. {
  22. gridFillArray.clear();
  23. for (int i = 0; i < numRows; ++i)
  24. {
  25. for (int j = 0; j < numColumns; ++j)
  26. {
  27. DrumPadGridProgram::GridFill fill;
  28. int padNum = (i * 5) + j;
  29. fill.colour = notes.contains (padNum) ? baseGridColour
  30. : tonics.contains (padNum) ? Colours::white
  31. : Colours::black;
  32. fill.fillType = DrumPadGridProgram::GridFill::FillType::gradient;
  33. gridFillArray.add (fill);
  34. }
  35. }
  36. }
  37. int getNoteNumberForPad (int x, int y) const
  38. {
  39. int xIndex = x / 3;
  40. int yIndex = y / 3;
  41. return 60 + ((4 - yIndex) * 5) + xIndex;
  42. }
  43. //==============================================================================
  44. int numColumns, numRows;
  45. float width, height;
  46. Array<DrumPadGridProgram::GridFill> gridFillArray;
  47. Colour baseGridColour = Colours::green;
  48. Colour touchColour = Colours::red;
  49. Array<int> tonics = { 4, 12, 20 };
  50. Array<int> notes = { 1, 3, 6, 7, 9, 11, 14, 15, 17, 19, 22, 24 };
  51. //==============================================================================
  52. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SynthGrid)
  53. };
  54. //==============================================================================
  55. /**
  56. The main component
  57. */
  58. class MainComponent : public Component,
  59. public TopologySource::Listener,
  60. private TouchSurface::Listener,
  61. private ControlButton::Listener,
  62. #if JUCE_IOS
  63. private Button::Listener,
  64. #endif
  65. private Timer
  66. {
  67. public:
  68. MainComponent()
  69. {
  70. setSize (600, 400);
  71. // Register MainContentComponent as a listener to the PhysicalTopologySource object
  72. topologySource.addListener (this);
  73. #if JUCE_IOS
  74. connectButton.setButtonText ("Connect");
  75. connectButton.addListener (this);
  76. addAndMakeVisible (connectButton);
  77. #endif
  78. };
  79. ~MainComponent()
  80. {
  81. if (activeBlock != nullptr)
  82. detachActiveBlock();
  83. }
  84. void paint (Graphics& g) override
  85. {
  86. g.fillAll (Colours::lightgrey);
  87. g.drawText ("Connect a Lightpad Block to play.",
  88. getLocalBounds(), Justification::centred, false);
  89. }
  90. void resized() override
  91. {
  92. #if JUCE_IOS
  93. connectButton.setBounds (getRight() - 100, 20, 80, 30);
  94. #endif
  95. }
  96. /** Overridden from TopologySource::Listener, called when the topology changes */
  97. void topologyChanged() override
  98. {
  99. // Reset the activeBlock object
  100. if (activeBlock != nullptr)
  101. detachActiveBlock();
  102. // Get the array of currently connected Block objects from the PhysicalTopologySource
  103. auto blocks = topologySource.getCurrentTopology().blocks;
  104. // Iterate over the array of Block objects
  105. for (auto b : blocks)
  106. {
  107. // Find the first Lightpad
  108. if (b->getType() == Block::Type::lightPadBlock)
  109. {
  110. activeBlock = b;
  111. // Register MainContentComponent as a listener to the touch surface
  112. if (auto surface = activeBlock->getTouchSurface())
  113. surface->addListener (this);
  114. // Register MainContentComponent as a listener to any buttons
  115. for (auto button : activeBlock->getButtons())
  116. button->addListener (this);
  117. // Get the LEDGrid object from the Lightpad and set its program to the program for the current mode
  118. if (auto grid = activeBlock->getLEDGrid())
  119. {
  120. // Work out scale factors to translate X and Y touches to LED indexes
  121. scaleX = static_cast<float> (grid->getNumColumns() - 1) / activeBlock->getWidth();
  122. scaleY = static_cast<float> (grid->getNumRows() - 1) / activeBlock->getHeight();
  123. setLEDProgram (grid);
  124. }
  125. break;
  126. }
  127. }
  128. }
  129. private:
  130. /** Overridden from TouchSurface::Listener. Called when a Touch is received on the Lightpad */
  131. void touchChanged (TouchSurface&, const TouchSurface::Touch& touch) override
  132. {
  133. if (currentMode == waveformSelectionMode && touch.isTouchStart && allowTouch)
  134. {
  135. // Change the displayed waveshape to the next one
  136. ++waveshapeMode;
  137. if (waveshapeMode > 3)
  138. waveshapeMode = 0;
  139. waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode));
  140. allowTouch = false;
  141. startTimer (250);
  142. }
  143. else if (currentMode == playMode)
  144. {
  145. // Translate X and Y touch events to LED indexes
  146. int xLed = roundToInt (touch.startX * scaleX);
  147. int yLed = roundToInt (touch.startY * scaleY);
  148. // Limit the number of touches per second
  149. constexpr int maxNumTouchMessagesPerSecond = 100;
  150. auto now = Time::getCurrentTime();
  151. clearOldTouchTimes (now);
  152. int midiChannel = waveshapeMode + 1;
  153. // Send the touch event to the DrumPadGridProgram and Audio class
  154. if (touch.isTouchStart)
  155. {
  156. gridProgram->startTouch (touch.startX, touch.startY);
  157. audio.noteOn (midiChannel, layout.getNoteNumberForPad (xLed, yLed), touch.z);
  158. }
  159. else if (touch.isTouchEnd)
  160. {
  161. gridProgram->endTouch (touch.startX, touch.startY);
  162. audio.noteOff (midiChannel, layout.getNoteNumberForPad (xLed, yLed), 1.0);
  163. }
  164. else
  165. {
  166. if (touchMessageTimesInLastSecond.size() > maxNumTouchMessagesPerSecond / 3)
  167. return;
  168. gridProgram->sendTouch (touch.x, touch.y, touch.z,
  169. layout.touchColour);
  170. // Send pitch change and pressure values to the Audio class
  171. audio.pitchChange (midiChannel, (touch.x - touch.startX) / activeBlock->getWidth());
  172. audio.pressureChange (midiChannel, touch.z);
  173. }
  174. touchMessageTimesInLastSecond.add (now);
  175. }
  176. }
  177. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is pressed */
  178. void buttonPressed (ControlButton&, Block::Timestamp) override {}
  179. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is released */
  180. void buttonReleased (ControlButton&, Block::Timestamp) override
  181. {
  182. // Turn any active synthesiser notes off
  183. audio.allNotesOff();
  184. // Switch modes
  185. if (currentMode == waveformSelectionMode)
  186. currentMode = playMode;
  187. else if (currentMode == playMode)
  188. currentMode = waveformSelectionMode;
  189. // Set the LEDGrid program to the new mode
  190. setLEDProgram (activeBlock->getLEDGrid());
  191. }
  192. #if JUCE_IOS
  193. void buttonClicked (Button* b) override
  194. {
  195. if (b == &connectButton)
  196. BluetoothMidiDevicePairingDialogue::open();
  197. }
  198. #endif
  199. /** Clears the old touch times */
  200. void clearOldTouchTimes (const Time now)
  201. {
  202. for (int i = touchMessageTimesInLastSecond.size(); --i >= 0;)
  203. if (touchMessageTimesInLastSecond.getReference(i) < now - juce::RelativeTime::seconds (0.33))
  204. touchMessageTimesInLastSecond.remove (i);
  205. }
  206. /** Removes TouchSurface and ControlButton listeners and sets activeBlock to nullptr */
  207. void detachActiveBlock()
  208. {
  209. if (auto surface = activeBlock->getTouchSurface())
  210. surface->removeListener (this);
  211. for (auto button : activeBlock->getButtons())
  212. button->removeListener (this);
  213. activeBlock = nullptr;
  214. }
  215. /** Sets the LEDGrid Program for the selected mode */
  216. void setLEDProgram (LEDGrid* grid)
  217. {
  218. if (currentMode == waveformSelectionMode)
  219. {
  220. // Create a new WaveshapeProgram for the LEDGrid
  221. waveshapeProgram = new WaveshapeProgram (*grid);
  222. // Set the LEDGrid program
  223. grid->setProgram (waveshapeProgram);
  224. // Initialise the program
  225. waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode));
  226. waveshapeProgram->generateWaveshapes();
  227. }
  228. else if (currentMode == playMode)
  229. {
  230. // Create a new DrumPadGridProgram for the LEDGrid
  231. gridProgram = new DrumPadGridProgram (*grid);
  232. // Set the LEDGrid program
  233. grid->setProgram (gridProgram);
  234. // Setup the grid layout
  235. gridProgram->setGridFills (layout.numColumns,
  236. layout.numRows,
  237. layout.gridFillArray);
  238. }
  239. }
  240. /** Stops touch events from triggering multiple waveshape mode changes */
  241. void timerCallback() override { allowTouch = true; }
  242. enum BlocksSynthMode
  243. {
  244. waveformSelectionMode = 0,
  245. playMode
  246. };
  247. BlocksSynthMode currentMode = playMode;
  248. //==============================================================================
  249. Audio audio;
  250. DrumPadGridProgram* gridProgram = nullptr;
  251. WaveshapeProgram* waveshapeProgram = nullptr;
  252. SynthGrid layout { 5, 5 };
  253. PhysicalTopologySource topologySource;
  254. Block::Ptr activeBlock;
  255. Array<juce::Time> touchMessageTimesInLastSecond;
  256. int waveshapeMode = 0;
  257. float scaleX = 0.0;
  258. float scaleY = 0.0;
  259. bool allowTouch = true;
  260. //==============================================================================
  261. #if JUCE_IOS
  262. TextButton connectButton;
  263. #endif
  264. //==============================================================================
  265. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
  266. };