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.

253 lines
11KB

  1. /**
  2. @page example_blocks_drawing BlocksDrawing
  3. In order to compile and run this application you need to first download the @jucelink{JUCE framework}, which can be obtained from GitHub @jucegithub{here}.
  4. @section blocks_drawing_introduction Introduction
  5. BlocksDrawing is a JUCE application that allows you to use your Lightpad as a drawing surface. You can choose from a palette of 9 base colours and paint them on the 15x15 LED grid, blending between colours using touch pressure.
  6. Generate a Projucer project from the PIP file located in the @s_file{JUCE/examples/BLOCKS/} folder, then navigate to the @s_file{BlocksDrawingDemo/Builds/} directory and open the code project in your IDE of choice. Run the application and connect your Lightpad (if you do not know how to do this, see @ref connecting_blocks) - it should now display a 3x3 grid of colours to choose from. Touch a colour to set it as the current brush colour and then press the mode button to switch to canvas mode where you will be presented with a blank touch surface. Touch anywhere on the LED grid to start painting and use the pressure of your touch to control how bright the colour is. Try painting over an already painted LED to increase its brightness and blend between different colours by doing this with a different brush colour. To clear the canvas and start over, double-click the mode button.
  7. The concept of a BLOCKS topology and the methods for receiving callbacks from a Block object are covered in the @ref example_blocks_monitor example and this tutorial will cover the methods in the API for displaying grids and setting LEDs on the Lightpad.
  8. @section blocks_drawing_led_grid The LEDGrid Object
  9. Lightpads have a 15x15 LED grid which can be accessed and controlled through the LEDGrid object, a pointer to which is returned by the Block::getLEDGrid() method @s_item{[1]} (for more details on how the %LEDGrid object operates, see @ref controlling_led_grids).
  10. @code{.cpp}
  11. void topologyChanged() override
  12. {
  13. //...
  14. auto blocks = topologySource.getCurrentTopology().blocks;
  15. for (auto b : blocks)
  16. {
  17. if (b->getType() == Block::Type::lightPadBlock)
  18. {
  19. activeBlock = b;
  20. //...
  21. if (auto grid = activeBlock->getLEDGrid()) // [1]
  22. {
  23. //...
  24. setLEDProgram (*activeBlock); // [2]
  25. }
  26. //...
  27. }
  28. }
  29. }
  30. @endcode
  31. In the @s_projcode{topologyChanged()} method of @s_projcode{BlocksDrawingDemo} this %LEDGrid pointer is passed to the @s_projcode{setLEDProgram()} method @s_item{[2]}, which sets the Block::Program to either a DrumPadGridProgram @s_item{[3]} or a BitmapLEDProgram @s_item{[4]}, depending on the selected mode.
  32. @code{.cpp}
  33. void setLEDProgram (Block& block)
  34. {
  35. if (currentMode == canvas)
  36. {
  37. block.setProgram (new BitmapLEDProgram (block)); // [4]
  38. //...
  39. }
  40. else if (currentMode == colourPalette)
  41. {
  42. block.setProgram (new DrumPadGridProgram (block)); // [3]
  43. //...
  44. }
  45. }
  46. @endcode
  47. @section blocks_drawing_colour_palette Colour Palette
  48. In the colour palette mode the Lightpad displays a 3x3 grid of colours, constructed using the %DrumPadGridProgram class. A %DrumPadGridProgram pointer is retrieved by calling the @s_projcode{getPaletteProgram()} helper function of @s_projcode{BlocksDrawingDemo} and in the @s_projcode{BlocksDrawingDemo::setLEDProgram()} method the %Block::Program is set to point to a new %DrumPadGridProgram object and is passed the %Block object of the Lightpad in its constructor.
  49. @code{.cpp}
  50. DrumPadGridProgram* getPaletteProgram()
  51. {
  52. if (activeBlock != nullptr)
  53. return dynamic_cast<DrumPadGridProgram*> (activeBlock->getProgram());
  54. return nullptr;
  55. }
  56. @endcode
  57. After the program has been initialised, it is passed to the LEDGrid to display using the Block::setProgram() method and the layout of the grid is set up using the DrumPadGridProgram::setGridFills() method. This function takes 3 arguments: the number of rows, number of columns and an array of DrumPadGridProgram::GridFill objects containing a @s_projcode{GridFill} for each pad that controls its colour and fill type.
  58. @code{.cpp}
  59. void setLEDProgram (Block& block)
  60. {
  61. //...
  62. else if (currentMode == colourPalette)
  63. {
  64. //...
  65. if (auto* program = getPaletteProgram())
  66. program->setGridFills (layout.numColumns, layout.numRows, layout.gridFillArray);
  67. }
  68. }
  69. @endcode
  70. The @s_projcode{ColourGrid} struct contains all of this information and handles the construction of the @s_projcode{GridFill} array in the @s_projcode{ColourGrid::constructGridFillArray()} method.
  71. @code{.cpp}
  72. void constructGridFillArray()
  73. {
  74. gridFillArray.clear();
  75. auto counter = 0;
  76. for (auto i = 0; i < numColumns; ++i)
  77. {
  78. for (auto j = 0; j < numRows; ++j)
  79. {
  80. DrumPadGridProgram::GridFill fill;
  81. Colour colourToUse = colourArray.getUnchecked (counter);
  82. fill.colour = colourToUse.withBrightness (colourToUse == currentColour ? 1.0f : 0.1f);
  83. if (colourToUse == Colours::black)
  84. fill.fillType = DrumPadGridProgram::GridFill::FillType::hollow;
  85. else
  86. fill.fillType = DrumPadGridProgram::GridFill::FillType::filled;
  87. gridFillArray.add (fill);
  88. if (++counter == colourArray.size())
  89. counter = 0;
  90. }
  91. }
  92. }
  93. @endcode
  94. An instance of this object called @s_projcode{layout} is declared as a member variable of @s_projcode{BlocksDrawingDemo} to easily change how the grid looks.
  95. @code{.cpp}
  96. ColourGrid layout { 3, 3 };
  97. @endcode
  98. The @s_projcode{ColourGrid::setActiveColourForTouch()} method is called in the @s_projcode{BlocksDrawingDemo::touchChanged()} callback and is used to determine which brush colour has been selected based on a Touch coordinate from the Lightpad.
  99. @code{.cpp}
  100. bool setActiveColourForTouch (int x, int y)
  101. {
  102. auto colourHasChanged = false;
  103. auto xindex = x / 5;
  104. auto yindex = y / 5;
  105. auto newColour = colourArray.getUnchecked ((yindex * 3) + xindex);
  106. if (currentColour != newColour)
  107. {
  108. currentColour = newColour;
  109. constructGridFillArray();
  110. colourHasChanged = true;
  111. }
  112. return colourHasChanged;
  113. }
  114. @endcode
  115. When the application is run, the colour palette mode would look like this:
  116. @image html BlocksDrawing_palette.JPG "Colour palette mode"
  117. @section blocks_drawing_canvas Canvas
  118. In canvas mode, the %Block program is set to an instance of %BitmapLEDProgram and uses the BitmapLEDProgram::setLED() method to set individual LEDs on the Lightpad to a particular colour. The @s_projcode{ActiveLED} struct declared in the private section of @s_projcode{BlocksDrawingDemo} is used to keep track of which LEDs are on and their colour and brightness. @s_projcode{BlocksDrawingDemo} contains an %Array of these objects called @s_projcode{activeLeds}.
  119. @code{.cpp}
  120. struct ActiveLED
  121. {
  122. uint32 x, y;
  123. Colour colour;
  124. float brightness;
  125. //...
  126. };
  127. Array<ActiveLED> activeLeds;
  128. @endcode
  129. In the @s_projcode{BlocksDrawingDemo::setLEDProgram()} method the program is set up and passed to the %LEDGrid object the same way as in the colour palette mode but the @s_projcode{BlocksDrawingDemo::redrawLEDs()} method is also called which iterates over the @s_projcode{activeLeds} array and sets the appropriate LEDs on the Lightpad so the LED states persist between mode switches.
  130. @code{.cpp}
  131. void redrawLEDs()
  132. {
  133. if (auto* canvasProgram = getCanvasProgram())
  134. {
  135. for (auto led : activeLeds)
  136. {
  137. canvasProgram->setLED (led.x, led.y, led.colour.withBrightness (led.brightness));
  138. lightpadComponent.setLEDColour (led.x, led.y, led.colour.withBrightness (led.brightness));
  139. }
  140. }
  141. }
  142. @endcode
  143. When a Touch is received in the @s_projcode{BlocksDrawingDemo::touchChanged()} callback the @s_projcode{BlocksDrawingDemo::drawLEDs()} method is called with 4 arguments: x and y coordinates, touch pressure and brush colour. This method iterates over the @s_projcode{activeLed} array and checks to see if there is an active LED at the given coordinate. If it is blank, an @s_projcode{ActiveLED} object is created and added to the array with the given coordinates and colour using touch pressure for brightness. If there is already an active LED at the coordinate, the colour of that LED will be blended with the current brush colour, the proportion of which is determined by the touch pressure.
  144. @code{.cpp}
  145. void drawLED (uint32 x0, uint32 y0, float z, Colour drawColour)
  146. {
  147. if (auto* canvasProgram = getCanvasProgram())
  148. {
  149. auto index = getLEDAt (x0, y0);
  150. if (drawColour == Colours::black)
  151. {
  152. if (index >= 0)
  153. {
  154. canvasProgram->setLED (x0, y0, Colours::black);
  155. lightpadComponent.setLEDColour (x0, y0, Colours::black);
  156. activeLeds.remove (index);
  157. }
  158. return;
  159. }
  160. if (index < 0)
  161. {
  162. ActiveLED led;
  163. led.x = x0;
  164. led.y = y0;
  165. led.colour = drawColour;
  166. led.brightness = z;
  167. activeLeds.add (led);
  168. canvasProgram->setLED (led.x, led.y, led.colour.withBrightness (led.brightness));
  169. lightpadComponent.setLEDColour (led.x, led.y, led.colour.withBrightness (led.brightness));
  170. return;
  171. }
  172. auto currentLed = activeLeds.getReference (index);
  173. if (currentLed.colour == drawColour)
  174. currentLed.brightness = jmin (currentLed.brightness + z, 1.0f);
  175. else
  176. currentLed.colour = currentLed.colour.interpolatedWith (drawColour, z);
  177. if (canvasProgram != nullptr)
  178. canvasProgram->setLED (currentLed.x, currentLed.y, currentLed.colour.withBrightness (currentLed.brightness));
  179. lightpadComponent.setLEDColour (currentLed.x, currentLed.y, currentLed.colour.withBrightness (currentLed.brightness));
  180. activeLeds.set (index, currentLed);
  181. }
  182. }
  183. @endcode
  184. When the application is run, the canvas mode would look like this:
  185. @image html BlocksDrawing_canvas.JPG "Unleash your inner Picasso!"
  186. @section blocks_drawing_summary Summary
  187. This tutorial and the accompanying code project have introduced the %LEDGrid object and shown how to use the %Block::Program object to display basic grids and set individual LEDs on the Lightpad.
  188. @section blocks_drawing_see_also See also
  189. - @ref example_blocks_monitor
  190. - @ref example_blocks_synth
  191. */