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.

428 lines
14KB

  1. #ifndef MAINCOMPONENT_H_INCLUDED
  2. #define MAINCOMPONENT_H_INCLUDED
  3. #include "../JuceLibraryCode/JuceHeader.h"
  4. #include "Audio.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::cyan;
  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. private Timer
  63. {
  64. public:
  65. MainComponent()
  66. {
  67. setSize (600, 400);
  68. // Register MainContentComponent as a listener to the PhysicalTopologySource object
  69. topologySource.addListener (this);
  70. generateWaveshapes();
  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)
  122. {
  123. // Change the displayed waveshape to the next one
  124. ++waveshapeMode;
  125. if (waveshapeMode > 3)
  126. waveshapeMode = 0;
  127. }
  128. else if (currentMode == playMode)
  129. {
  130. // Translate X and Y touch events to LED indexes
  131. int xLed = roundToInt (touch.startX * scaleX);
  132. int yLed = roundToInt (touch.startY * scaleY);
  133. // Limit the number of touches per second
  134. constexpr int maxNumTouchMessagesPerSecond = 100;
  135. auto now = Time::getCurrentTime();
  136. clearOldTouchTimes (now);
  137. int midiChannel = waveshapeMode + 1;
  138. // Send the touch event to the DrumPadGridProgram and Audio class
  139. if (touch.isTouchStart)
  140. {
  141. gridProgram->startTouch (touch.startX, touch.startY);
  142. audio.noteOn (midiChannel, layout.getNoteNumberForPad (xLed, yLed), touch.z);
  143. }
  144. else if (touch.isTouchEnd)
  145. {
  146. gridProgram->endTouch (touch.startX, touch.startY);
  147. audio.noteOff (midiChannel, layout.getNoteNumberForPad (xLed, yLed), 1.0);
  148. }
  149. else
  150. {
  151. if (touchMessageTimesInLastSecond.size() > maxNumTouchMessagesPerSecond / 3)
  152. return;
  153. gridProgram->sendTouch (touch.x, touch.y, touch.z,
  154. layout.touchColour);
  155. // Send pitch change and pressure values to the Audio class
  156. audio.pitchChange (midiChannel, (touch.x - touch.startX) / activeBlock->getWidth());
  157. audio.pressureChange (midiChannel, touch.z);
  158. }
  159. touchMessageTimesInLastSecond.add (now);
  160. }
  161. }
  162. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is pressed */
  163. void buttonPressed (ControlButton&, Block::Timestamp) override {}
  164. /** Overridden from ControlButton::Listener. Called when a button on the Lightpad is released */
  165. void buttonReleased (ControlButton&, Block::Timestamp) override
  166. {
  167. // Turn any active synthesiser notes off
  168. audio.allNotesOff();
  169. // Switch modes
  170. if (currentMode == waveformSelectionMode)
  171. currentMode = playMode;
  172. else if (currentMode == playMode)
  173. currentMode = waveformSelectionMode;
  174. // Set the LEDGrid program to the new mode
  175. setLEDProgram (activeBlock->getLEDGrid());
  176. }
  177. void timerCallback() override
  178. {
  179. // Clear all LEDs
  180. for (uint32 x = 0; x < 15; ++x)
  181. for (uint32 y = 0; y < 15; ++y)
  182. setLED (x, y, Colours::black);
  183. // Determine which array to use based on waveshapeMode
  184. int* waveshapeY = nullptr;
  185. switch (waveshapeMode)
  186. {
  187. case 0: waveshapeY = sineWaveY; break;
  188. case 1: waveshapeY = squareWaveY; break;
  189. case 2: waveshapeY = sawWaveY; break;
  190. case 3: waveshapeY = triangleWaveY; break;
  191. default: break;
  192. }
  193. // For each X co-ordinate
  194. for (uint32 x = 0; x < 15; ++x)
  195. {
  196. // Find the corresponding Y co-ordinate for the current waveshape
  197. int y = waveshapeY[x + yOffset];
  198. // Draw a vertical line if flag is set or draw an LED circle
  199. if (y == -1)
  200. {
  201. for (uint32 i = 0; i < 15; ++i)
  202. drawLEDCircle (x, i);
  203. }
  204. else if (x % 2 == 0)
  205. {
  206. drawLEDCircle (x, static_cast<uint32> (y));
  207. }
  208. }
  209. // Increment the offset to draw a 'moving' waveshape
  210. if (++yOffset == 30)
  211. yOffset -= 30;
  212. }
  213. /** Clears the old touch times */
  214. void clearOldTouchTimes (const Time now)
  215. {
  216. for (int i = touchMessageTimesInLastSecond.size(); --i >= 0;)
  217. if (touchMessageTimesInLastSecond.getReference(i) < now - juce::RelativeTime::seconds (0.33))
  218. touchMessageTimesInLastSecond.remove (i);
  219. }
  220. /** Removes TouchSurface and ControlButton listeners and sets activeBlock to nullptr */
  221. void detachActiveBlock()
  222. {
  223. if (auto surface = activeBlock->getTouchSurface())
  224. surface->removeListener (this);
  225. for (auto button : activeBlock->getButtons())
  226. button->removeListener (this);
  227. activeBlock = nullptr;
  228. }
  229. /** Sets the LEDGrid Program for the selected mode */
  230. void setLEDProgram (LEDGrid* grid)
  231. {
  232. if (currentMode == waveformSelectionMode)
  233. {
  234. // Create a new BitmapLEDProgram for the LEDGrid
  235. bitmapProgram = new BitmapLEDProgram (*grid);
  236. // Set the LEDGrid program
  237. grid->setProgram (bitmapProgram);
  238. // Redraw at 25Hz
  239. startTimerHz (25);
  240. }
  241. else if (currentMode == playMode)
  242. {
  243. // Stop the redraw timer
  244. stopTimer();
  245. // Create a new DrumPadGridProgram for the LEDGrid
  246. gridProgram = new DrumPadGridProgram (*grid);
  247. // Set the LEDGrid program
  248. grid->setProgram (gridProgram);
  249. // Setup the grid layout
  250. gridProgram->setGridFills (layout.numColumns,
  251. layout.numRows,
  252. layout.gridFillArray);
  253. }
  254. }
  255. /** Generates the X and Y co-ordiantes for 1.5 cycles of each of the 4 waveshapes and stores them in arrays */
  256. void generateWaveshapes()
  257. {
  258. // Set current phase position to 0 and work out the required phase increment for one cycle
  259. double currentPhase = 0.0;
  260. double phaseInc = (1.0 / 30.0) * (2.0 * double_Pi);
  261. for (int x = 0; x < 30; ++x)
  262. {
  263. // Scale and offset the sin output to the Lightpad display
  264. double sineOutput = sin (currentPhase);
  265. sineWaveY[x] = roundToInt ((sineOutput * 6.5) + 7.0);
  266. // Square wave output, set flags for when vertical line should be drawn
  267. if (currentPhase < double_Pi)
  268. {
  269. if (x == 0)
  270. squareWaveY[x] = -1;
  271. else
  272. squareWaveY[x] = 1;
  273. }
  274. else
  275. {
  276. if (squareWaveY[x - 1] == 1)
  277. squareWaveY[x - 1] = -1;
  278. squareWaveY[x] = 13;
  279. }
  280. // Saw wave output, set flags for when vertical line should be drawn
  281. sawWaveY[x] = 14 - ((x / 2) % 15);
  282. if (sawWaveY[x] == 0 && sawWaveY[x - 1] != -1)
  283. sawWaveY[x] = -1;
  284. // Triangle wave output
  285. triangleWaveY[x] = x < 15 ? x : 14 - (x % 15);
  286. // Add half cycle to end of array so it loops correctly
  287. if (x < 15)
  288. {
  289. sineWaveY[x + 30] = sineWaveY[x];
  290. squareWaveY[x + 30] = squareWaveY[x];
  291. sawWaveY[x + 30] = sawWaveY[x];
  292. triangleWaveY[x + 30] = triangleWaveY[x];
  293. }
  294. // Increment the current phase
  295. currentPhase += phaseInc;
  296. }
  297. }
  298. /** Simple wrapper function to set a LED colour */
  299. void setLED (uint32 x, uint32 y, Colour colour)
  300. {
  301. if (bitmapProgram != nullptr)
  302. bitmapProgram->setLED (x, y, colour);
  303. }
  304. /** Draws a 'circle' on the Lightpad around an origin co-ordinate */
  305. void drawLEDCircle (uint32 x0, uint32 y0)
  306. {
  307. setLED (x0, y0, waveshapeColour);
  308. const uint32 minLedIndex = 0;
  309. const uint32 maxLedIndex = 14;
  310. setLED (jmin (x0 + 1, maxLedIndex), y0, waveshapeColour.withBrightness (0.4f));
  311. setLED (jmax (x0 - 1, minLedIndex), y0, waveshapeColour.withBrightness (0.4f));
  312. setLED (x0, jmin (y0 + 1, maxLedIndex), waveshapeColour.withBrightness (0.4f));
  313. setLED (x0, jmax (y0 - 1, minLedIndex), waveshapeColour.withBrightness (0.4f));
  314. setLED (jmin (x0 + 1, maxLedIndex), jmin (y0 + 1, maxLedIndex), waveshapeColour.withBrightness (0.1f));
  315. setLED (jmin (x0 + 1, maxLedIndex), jmax (y0 - 1, minLedIndex), waveshapeColour.withBrightness (0.1f));
  316. setLED (jmax (x0 - 1, minLedIndex), jmin (y0 + 1, maxLedIndex), waveshapeColour.withBrightness (0.1f));
  317. setLED (jmax (x0 - 1, minLedIndex), jmax (y0 - 1, minLedIndex), waveshapeColour.withBrightness (0.1f));
  318. }
  319. enum BlocksSynthMode
  320. {
  321. waveformSelectionMode = 0,
  322. playMode
  323. };
  324. BlocksSynthMode currentMode = playMode;
  325. //==============================================================================
  326. Audio audio;
  327. DrumPadGridProgram* gridProgram = nullptr;
  328. BitmapLEDProgram* bitmapProgram = nullptr;
  329. SynthGrid layout { 5, 5 };
  330. PhysicalTopologySource topologySource;
  331. Block::Ptr activeBlock;
  332. Array<juce::Time> touchMessageTimesInLastSecond;
  333. Colour waveshapeColour = Colours::red;
  334. int sineWaveY[45];
  335. int squareWaveY[45];
  336. int sawWaveY[45];
  337. int triangleWaveY[45];
  338. int waveshapeMode = 0;
  339. uint32 yOffset = 0;
  340. float scaleX = 0.0;
  341. float scaleY = 0.0;
  342. //==============================================================================
  343. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
  344. };
  345. #endif // MAINCOMPONENT_H_INCLUDED