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.

333 lines
11KB

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