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.

307 lines
10KB

  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. private Timer
  64. {
  65. public:
  66. MainComponent()
  67. {
  68. setSize (600, 400);
  69. // Register MainContentComponent as a listener to the PhysicalTopologySource object
  70. topologySource.addListener (this);
  71. };
  72. ~MainComponent()
  73. {
  74. if (activeBlock != nullptr)
  75. detachActiveBlock();
  76. }
  77. void paint (Graphics& g) override
  78. {
  79. g.fillAll (Colours::lightgrey);
  80. g.drawText ("Connect a Lightpad Block to play.",
  81. getLocalBounds(), Justification::centred, false);
  82. }
  83. void resized() override {}
  84. /** Overridden from TopologySource::Listener, called when the topology changes */
  85. void topologyChanged() override
  86. {
  87. // Reset the activeBlock object
  88. if (activeBlock != nullptr)
  89. detachActiveBlock();
  90. // Get the array of currently connected Block objects from the PhysicalTopologySource
  91. auto blocks = topologySource.getCurrentTopology().blocks;
  92. // Iterate over the array of Block objects
  93. for (auto b : blocks)
  94. {
  95. // Find the first Lightpad
  96. if (b->getType() == Block::Type::lightPadBlock)
  97. {
  98. activeBlock = b;
  99. // Register MainContentComponent as a listener to the touch surface
  100. if (auto surface = activeBlock->getTouchSurface())
  101. surface->addListener (this);
  102. // Register MainContentComponent as a listener to any buttons
  103. for (auto button : activeBlock->getButtons())
  104. button->addListener (this);
  105. // Get the LEDGrid object from the Lightpad and set its program to the program for the current mode
  106. if (auto grid = activeBlock->getLEDGrid())
  107. {
  108. // Work out scale factors to translate X and Y touches to LED indexes
  109. scaleX = static_cast<float> (grid->getNumColumns() - 1) / activeBlock->getWidth();
  110. scaleY = static_cast<float> (grid->getNumRows() - 1) / activeBlock->getHeight();
  111. setLEDProgram (grid);
  112. }
  113. break;
  114. }
  115. }
  116. }
  117. private:
  118. /** Overridden from TouchSurface::Listener. Called when a Touch is received on the Lightpad */
  119. void touchChanged (TouchSurface&, const TouchSurface::Touch& touch) override
  120. {
  121. if (currentMode == waveformSelectionMode && touch.isTouchStart && allowTouch)
  122. {
  123. // Change the displayed waveshape to the next one
  124. ++waveshapeMode;
  125. if (waveshapeMode > 3)
  126. waveshapeMode = 0;
  127. waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode));
  128. allowTouch = false;
  129. startTimer (250);
  130. }
  131. else if (currentMode == playMode)
  132. {
  133. // Translate X and Y touch events to LED indexes
  134. int xLed = roundToInt (touch.startX * scaleX);
  135. int yLed = roundToInt (touch.startY * scaleY);
  136. // Limit the number of touches per second
  137. constexpr int maxNumTouchMessagesPerSecond = 100;
  138. auto now = Time::getCurrentTime();
  139. clearOldTouchTimes (now);
  140. int midiChannel = waveshapeMode + 1;
  141. // Send the touch event to the DrumPadGridProgram and Audio class
  142. if (touch.isTouchStart)
  143. {
  144. gridProgram->startTouch (touch.startX, touch.startY);
  145. audio.noteOn (midiChannel, layout.getNoteNumberForPad (xLed, yLed), touch.z);
  146. }
  147. else if (touch.isTouchEnd)
  148. {
  149. gridProgram->endTouch (touch.startX, touch.startY);
  150. audio.noteOff (midiChannel, layout.getNoteNumberForPad (xLed, yLed), 1.0);
  151. }
  152. else
  153. {
  154. if (touchMessageTimesInLastSecond.size() > maxNumTouchMessagesPerSecond / 3)
  155. return;
  156. gridProgram->sendTouch (touch.x, touch.y, touch.z,
  157. layout.touchColour);
  158. // Send pitch change and pressure values to the Audio class
  159. audio.pitchChange (midiChannel, (touch.x - touch.startX) / activeBlock->getWidth());
  160. audio.pressureChange (midiChannel, touch.z);
  161. }
  162. touchMessageTimesInLastSecond.add (now);
  163. }
  164. }
  165. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is pressed */
  166. void buttonPressed (ControlButton&, Block::Timestamp) override {}
  167. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is released */
  168. void buttonReleased (ControlButton&, Block::Timestamp) override
  169. {
  170. // Turn any active synthesiser notes off
  171. audio.allNotesOff();
  172. // Switch modes
  173. if (currentMode == waveformSelectionMode)
  174. currentMode = playMode;
  175. else if (currentMode == playMode)
  176. currentMode = waveformSelectionMode;
  177. // Set the LEDGrid program to the new mode
  178. setLEDProgram (activeBlock->getLEDGrid());
  179. }
  180. /** Clears the old touch times */
  181. void clearOldTouchTimes (const Time now)
  182. {
  183. for (int i = touchMessageTimesInLastSecond.size(); --i >= 0;)
  184. if (touchMessageTimesInLastSecond.getReference(i) < now - juce::RelativeTime::seconds (0.33))
  185. touchMessageTimesInLastSecond.remove (i);
  186. }
  187. /** Removes TouchSurface and ControlButton listeners and sets activeBlock to nullptr */
  188. void detachActiveBlock()
  189. {
  190. if (auto surface = activeBlock->getTouchSurface())
  191. surface->removeListener (this);
  192. for (auto button : activeBlock->getButtons())
  193. button->removeListener (this);
  194. activeBlock = nullptr;
  195. }
  196. /** Sets the LEDGrid Program for the selected mode */
  197. void setLEDProgram (LEDGrid* grid)
  198. {
  199. if (currentMode == waveformSelectionMode)
  200. {
  201. // Create a new WaveshapeProgram for the LEDGrid
  202. waveshapeProgram = new WaveshapeProgram (*grid);
  203. // Set the LEDGrid program
  204. grid->setProgram (waveshapeProgram);
  205. // Initialise the program
  206. waveshapeProgram->setWaveshapeType (static_cast<uint8> (waveshapeMode));
  207. waveshapeProgram->generateWaveshapes();
  208. }
  209. else if (currentMode == playMode)
  210. {
  211. // Create a new DrumPadGridProgram for the LEDGrid
  212. gridProgram = new DrumPadGridProgram (*grid);
  213. // Set the LEDGrid program
  214. grid->setProgram (gridProgram);
  215. // Setup the grid layout
  216. gridProgram->setGridFills (layout.numColumns,
  217. layout.numRows,
  218. layout.gridFillArray);
  219. }
  220. }
  221. /** Stops touch events from triggering multiple waveshape mode changes */
  222. void timerCallback() override { allowTouch = true; }
  223. enum BlocksSynthMode
  224. {
  225. waveformSelectionMode = 0,
  226. playMode
  227. };
  228. BlocksSynthMode currentMode = playMode;
  229. //==============================================================================
  230. Audio audio;
  231. DrumPadGridProgram* gridProgram = nullptr;
  232. WaveshapeProgram* waveshapeProgram = nullptr;
  233. SynthGrid layout { 5, 5 };
  234. PhysicalTopologySource topologySource;
  235. Block::Ptr activeBlock;
  236. Array<juce::Time> touchMessageTimesInLastSecond;
  237. int waveshapeMode = 0;
  238. float scaleX = 0.0;
  239. float scaleY = 0.0;
  240. bool allowTouch = true;
  241. //==============================================================================
  242. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
  243. };
  244. #endif // MAINCOMPONENT_H_INCLUDED