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.

542 lines
17KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2016 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. DrumPadGridProgram::DrumPadGridProgram (LEDGrid& lg) : Program (lg) {}
  18. int DrumPadGridProgram::getPadIndex (float posX, float posY) const
  19. {
  20. posX = juce::jmin (0.99f, posX / ledGrid.block.getWidth());
  21. posY = juce::jmin (0.99f, posY / ledGrid.block.getHeight());
  22. const uint32 offset = ledGrid.getDataByte (visiblePads_byte) ? numColumns1_byte : numColumns0_byte;
  23. const int numColumns = ledGrid.getDataByte (offset + numColumns0_byte);
  24. const int numRows = ledGrid.getDataByte (offset + numRows0_byte);
  25. return int (posX * numColumns) + int (posY * numRows) * numColumns;
  26. }
  27. void DrumPadGridProgram::startTouch (float startX, float startY)
  28. {
  29. const auto padIdx = getPadIndex (startX, startY);
  30. for (size_t i = 0; i < 4; ++i)
  31. {
  32. if (ledGrid.getDataByte (touchedPads_byte + i) == 0)
  33. {
  34. ledGrid.setDataByte (touchedPads_byte + i, static_cast<uint8> (padIdx + 1));
  35. break;
  36. }
  37. }
  38. }
  39. void DrumPadGridProgram::endTouch (float startX, float startY)
  40. {
  41. const auto padIdx = getPadIndex (startX, startY);
  42. for (size_t i = 0; i < 4; ++i)
  43. if (ledGrid.getDataByte (touchedPads_byte + i) == (padIdx + 1))
  44. ledGrid.setDataByte (touchedPads_byte + i, 0);
  45. }
  46. void DrumPadGridProgram::sendTouch (float x, float y, float z, LEDColour colour)
  47. {
  48. LEDGrid::ProgramEventMessage e;
  49. e.values[0] = 0x20000000
  50. + (juce::jlimit (0, 255, juce::roundToInt (x * (255.0f / ledGrid.block.getWidth()))) << 16)
  51. + (juce::jlimit (0, 255, juce::roundToInt (y * (255.0f / ledGrid.block.getHeight()))) << 8)
  52. + juce::jlimit (0, 255, juce::roundToInt (z * 255.0f));
  53. e.values[1] = (int32) colour.getARGB();
  54. ledGrid.sendProgramEvent (e);
  55. }
  56. //==============================================================================
  57. void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills)
  58. {
  59. uint8 visiblePads = ledGrid.getDataByte (visiblePads_byte);
  60. setGridFills (numColumns, numRows, fills, visiblePads * numColumns1_byte);
  61. }
  62. void DrumPadGridProgram::setGridFills (int numColumns, int numRows, const juce::Array<GridFill>& fills, uint32 byteOffset)
  63. {
  64. jassert (numColumns * numRows == fills.size());
  65. ledGrid.setDataByte (byteOffset + numColumns0_byte, (uint8) numColumns);
  66. ledGrid.setDataByte (byteOffset + numRows0_byte, (uint8) numRows);
  67. uint32 i = 0;
  68. for (auto fill : fills)
  69. {
  70. if (i >= maxNumPads)
  71. {
  72. jassertfalse;
  73. break;
  74. }
  75. const uint32 colourOffsetBytes = byteOffset + colours0_byte + i * colourSizeBytes;
  76. const uint32 colourOffsetBits = colourOffsetBytes * 8;
  77. ledGrid.setDataBits (colourOffsetBits, 5, fill.colour.getRed() >> 3);
  78. ledGrid.setDataBits (colourOffsetBits + 5, 6, fill.colour.getGreen() >> 2);
  79. ledGrid.setDataBits (colourOffsetBits + 11, 5, fill.colour.getBlue() >> 3);
  80. ledGrid.setDataByte (byteOffset + fillTypes0_byte + i, static_cast<uint8> (fill.fillType));
  81. ++i;
  82. }
  83. }
  84. void DrumPadGridProgram::triggerSlideTransition (int newNumColumns, int newNumRows,
  85. const juce::Array<GridFill>& newFills, SlideDirection direction)
  86. {
  87. uint8 newVisible = ledGrid.getDataByte (visiblePads_byte) ? 0 : 1;
  88. setGridFills (newNumColumns, newNumRows, newFills, newVisible * numColumns1_byte);
  89. ledGrid.setDataByte (visiblePads_byte, newVisible);
  90. ledGrid.setDataByte (slideDirection_byte, (uint8) direction);
  91. }
  92. //==============================================================================
  93. void DrumPadGridProgram::setPadAnimationState (uint32 padIdx, double loopTimeSecs, double currentProgress)
  94. {
  95. // Only 16 animated pads are supported.
  96. jassert (padIdx < 16);
  97. // Compensate for bluetooth latency & led resolution, tweaked by eye for POS app.
  98. currentProgress = std::fmod (currentProgress + 0.1, 1.0);
  99. uint16 aniValue = uint16 (juce::roundToInt ((255 << 8) * currentProgress));
  100. uint16 aniIncrement = loopTimeSecs > 0.0 ? uint16 (juce::roundToInt (((255 << 8) / 25.0) / loopTimeSecs)) : 0;
  101. uint32 offset = 8 * animationTimers_byte + 32 * padIdx;
  102. ledGrid.setDataBits (offset, 16, aniValue);
  103. ledGrid.setDataBits (offset + 16, 16, aniIncrement);
  104. }
  105. void DrumPadGridProgram::suspendAnimations()
  106. {
  107. for (uint32 i = 0; i < 16; ++i)
  108. {
  109. uint32 offset = 8 * animationTimers_byte + 32 * i;
  110. ledGrid.setDataBits (offset + 16, 16, 0);
  111. }
  112. // Hijack touch dimming
  113. ledGrid.setDataByte (touchedPads_byte, 255);
  114. }
  115. void DrumPadGridProgram::resumeAnimations()
  116. {
  117. // Unhijack touch dimming
  118. ledGrid.setDataByte (touchedPads_byte, 0);
  119. }
  120. //==============================================================================
  121. uint32 DrumPadGridProgram::getHeapSize()
  122. {
  123. return totalDataSize;
  124. }
  125. juce::String DrumPadGridProgram::getLittleFootProgram()
  126. {
  127. return R"littlefoot(
  128. int dimFactor;
  129. int dimDelay;
  130. int slideAnimationProgress;
  131. int lastVisiblePads;
  132. int getGridColour (int index, int colourMapOffset)
  133. {
  134. int bit = (2 + colourMapOffset) * 8 + index * 16;
  135. return makeARGB (255,
  136. getHeapBits (bit, 5) << 3,
  137. getHeapBits (bit + 5, 6) << 2,
  138. getHeapBits (bit + 11, 5) << 3);
  139. }
  140. // Returns the current progress and also increments it for next frame
  141. int getAnimationProgress (int index)
  142. {
  143. // Only 16 animated pads supported
  144. if (index > 15)
  145. return 0;
  146. int offsetBits = 162 * 8 + index * 32;
  147. int currentProgress = getHeapBits (offsetBits, 16);
  148. int increment = getHeapBits (offsetBits + 16, 16);
  149. int nextFrame = currentProgress + increment;
  150. // Set incremented 16 bit number.
  151. setHeapByte (162 + index * 4, nextFrame & 0xff);
  152. setHeapByte (163 + index * 4, nextFrame >> 8);
  153. return currentProgress;
  154. }
  155. void outlineRect (int colour, int x, int y, int w)
  156. {
  157. fillRect (colour, x, y, w, 1);
  158. fillRect (colour, x, y + w - 1, w, 1);
  159. fillRect (colour, x, y + 1, 1, w - 1);
  160. fillRect (colour, x + w - 1, y + 1, 1, w - 1);
  161. }
  162. void drawPlus (int colour, int x, int y, int w)
  163. {
  164. fillRect (colour, x, y + (w / 2), w, 1);
  165. fillRect (colour, x + (w / 2), y, 1, w);
  166. }
  167. void fillGradientRect (int colour, int x, int y, int w)
  168. {
  169. if (colour != 0xff000000)
  170. {
  171. int divisor = w + w - 1;
  172. for (int yy = 0; yy < w; ++yy)
  173. {
  174. for (int xx = yy; xx < w; ++xx)
  175. {
  176. int gradColour = blendARGB (colour, makeARGB (((xx + yy) * 250) / divisor, 0, 0, 0));
  177. setLED (x + xx, y + yy, gradColour);
  178. setLED (x + yy, y + xx, gradColour);
  179. }
  180. }
  181. }
  182. }
  183. // TODO: Tom M: This is massaged to work with 3x3 pads and for dots to sync
  184. // with Apple POS loop length. Rework to be more robust & flexible.
  185. void drawPizzaLED (int colour, int x, int y, int w, int progress)
  186. {
  187. --w;
  188. x += 1;
  189. int numToDo = ((8 * progress) / 255) + 1;
  190. int totalLen = w * 4;
  191. for (int i = 1; i <= numToDo; ++i)
  192. {
  193. setLED (x, y, colour);
  194. if (i < w)
  195. ++x;
  196. else if (i < (w * 2))
  197. ++y;
  198. else if (i < (w * 3))
  199. --x;
  200. else if (i < totalLen)
  201. --y;
  202. }
  203. }
  204. void drawPad (int padX, int padY, int padW,
  205. int colour, int fill, int animateProgress)
  206. {
  207. animateProgress >>= 8; // 16 bit to 8 bit
  208. int halfW = padW / 2;
  209. if (fill == 0) // Gradient fill
  210. {
  211. fillGradientRect (colour, padX, padY, padW);
  212. }
  213. else if (fill == 1) // Filled
  214. {
  215. fillRect (colour, padX, padY, padW, padW);
  216. }
  217. else if (fill == 2) // Hollow
  218. {
  219. outlineRect (colour, padX, padY, padW);
  220. }
  221. else if (fill == 3) // Hollow with plus
  222. {
  223. outlineRect (colour, padX, padY, padW);
  224. drawPlus (0xffffffff, padX, padY, padW);
  225. }
  226. else if (fill == 4) // Pulsing dot
  227. {
  228. int pulseCol = blendARGB (colour, makeARGB (animateProgress, 0, 0, 0));
  229. setLED (padX + halfW, padY + halfW, pulseCol);
  230. }
  231. else if (fill == 5) // Blinking dot
  232. {
  233. int blinkCol = animateProgress > 64 ? makeARGB (255, 0, 0, 0) : colour;
  234. setLED (padX + halfW, padY + halfW, blinkCol);
  235. }
  236. else if (fill == 6) // Pizza filled
  237. {
  238. outlineRect (blendARGB (colour, makeARGB (220, 0, 0, 0)), padX, padY, padW); // Dim outline
  239. setLED (padX + halfW, padY + halfW, colour); // Bright centre
  240. drawPizzaLED (colour, padX, padY, padW, animateProgress);
  241. }
  242. else if (fill == 7) // Pizza hollow
  243. {
  244. outlineRect (blendARGB (colour, makeARGB (220, 0, 0, 0)), padX, padY, padW); // Dim outline
  245. drawPizzaLED (colour, padX, padY, padW, animateProgress);
  246. return;
  247. }
  248. }
  249. void fadeHeatMap()
  250. {
  251. for (int i = 0; i < 225; ++i)
  252. {
  253. int colourOffset = 226 + i * 4;
  254. int colour = getHeapInt (colourOffset);
  255. int alpha = (colour >> 24) & 0xff;
  256. if (alpha > 0)
  257. {
  258. alpha -= getHeapByte (1126 + i);
  259. setHeapInt (colourOffset, alpha < 0 ? 0 : ((alpha << 24) | (colour & 0xffffff)));
  260. }
  261. }
  262. }
  263. void addToHeatMap (int x, int y, int colour)
  264. {
  265. if (x >= 0 && y >= 0 && x < 15 && y < 15)
  266. {
  267. int offset = 226 + 4 * (x + y * 15);
  268. colour = blendARGB (getHeapInt (offset), colour);
  269. setHeapInt (offset, colour);
  270. int decay = ((colour >> 24) & 0xff) / 14; // change divisor to change trail times
  271. offset = 1126 + (x + y * 15);
  272. setHeapByte (offset, decay > 0 ? decay : 1);
  273. }
  274. }
  275. int getHeatmapColour (int x, int y)
  276. {
  277. return getHeapInt (226 + 4 * (x + y * 15));
  278. }
  279. int isPadActive (int index)
  280. {
  281. if (getHeapInt (158) == 0) // None active
  282. return 0;
  283. ++index;
  284. return index == getHeapByte (158) ||
  285. index == getHeapByte (159) ||
  286. index == getHeapByte (160) ||
  287. index == getHeapByte (161);
  288. }
  289. void updateDimFactor()
  290. {
  291. if (getHeapInt (158) == 0)
  292. {
  293. if (--dimDelay <= 0)
  294. {
  295. dimFactor -= 12;
  296. if (dimFactor < 0)
  297. dimFactor = 0;
  298. }
  299. }
  300. else
  301. {
  302. dimFactor = 180;
  303. dimDelay = 12;
  304. }
  305. }
  306. void drawPads (int offsetX, int offsetY, int colourMapOffset)
  307. {
  308. int padsPerSide = getHeapByte (0 + colourMapOffset);
  309. if (padsPerSide < 2)
  310. return;
  311. int blockW = 15 / padsPerSide;
  312. int blockPlusGapW = blockW + (15 - padsPerSide * blockW) / (padsPerSide - 1);
  313. for (int padY = 0; padY < padsPerSide; ++padY)
  314. {
  315. for (int padX = 0; padX < padsPerSide; ++padX)
  316. {
  317. int ledX = offsetX + padX * blockPlusGapW;
  318. int ledY = offsetY + padY * blockPlusGapW;
  319. if (ledX < 15 &&
  320. ledY < 15 &&
  321. (ledX + blockW) >= 0 &&
  322. (ledY + blockW) >= 0)
  323. {
  324. int padIdx = padX + padY * padsPerSide;
  325. bool padActive = isPadActive (padIdx);
  326. int blendCol = padActive ? 255 : 0;
  327. int blendAmt = padActive ? dimFactor >> 1 : dimFactor;
  328. int colour = blendARGB (getGridColour (padIdx, colourMapOffset),
  329. makeARGB (blendAmt, blendCol, blendCol, blendCol));
  330. int fillType = getHeapByte (colourMapOffset + 52 + padIdx);
  331. int animate = getAnimationProgress (padIdx);
  332. drawPad (ledX, ledY, blockW, colour, fillType, animate);
  333. }
  334. }
  335. }
  336. }
  337. void slideAnimatePads()
  338. {
  339. int nowVisible = getHeapByte (155);
  340. if (lastVisiblePads != nowVisible)
  341. {
  342. lastVisiblePads = nowVisible;
  343. if (slideAnimationProgress <= 0)
  344. slideAnimationProgress = 15;
  345. }
  346. // If animation is complete, draw normally.
  347. if (slideAnimationProgress <= 0)
  348. {
  349. drawPads (0, 0, 78 * nowVisible);
  350. slideAnimationProgress = 0;
  351. }
  352. else
  353. {
  354. int direction = getHeapByte (156);
  355. slideAnimationProgress -= 1;
  356. int inPos = nowVisible == 0 ? 0 : 78;
  357. int outPos = nowVisible == 0 ? 78 : 0;
  358. if (direction == 0) // Up
  359. {
  360. drawPads (0, slideAnimationProgress - 16, outPos);
  361. drawPads (0, slideAnimationProgress, inPos);
  362. }
  363. else if (direction == 1) // Down
  364. {
  365. drawPads (0, 16 - slideAnimationProgress, outPos);
  366. drawPads (0, 0 - slideAnimationProgress, inPos);
  367. }
  368. else if (direction == 2) // Left
  369. {
  370. drawPads (16 - slideAnimationProgress, 0, outPos);
  371. drawPads (slideAnimationProgress, 0, inPos);
  372. }
  373. else if (direction == 3) // Right
  374. {
  375. drawPads (16 - slideAnimationProgress, 0, outPos);
  376. drawPads (0 - slideAnimationProgress, 0, inPos);
  377. }
  378. else // None
  379. {
  380. drawPads (0, 0, 78 * nowVisible);
  381. slideAnimationProgress = 0;
  382. }
  383. }
  384. }
  385. void repaint()
  386. {
  387. // showErrorOnFail, showRepaintTime, showMovingDot
  388. //enableDebug (true, true, false);
  389. // Clear LEDs to black, update dim animation
  390. fillRect (0xff000000, 0, 0, 15, 15);
  391. updateDimFactor();
  392. // Does the main painting of pads
  393. slideAnimatePads();
  394. // Overlay heatmap
  395. for (int y = 0; y < 15; ++y)
  396. for (int x = 0; x < 15; ++x)
  397. blendLED (x, y, getHeatmapColour (x, y));
  398. fadeHeatMap();
  399. }
  400. // DrumPadGridProgram::sendTouch results in this callback, giving
  401. // us more touch updates per frame and therefore smoother trails.
  402. void handleMessage (int pos, int colour)
  403. {
  404. if ((pos >> 24) != 0x20)
  405. return;
  406. int tx = ((pos >> 16) & 0xff) - 13;
  407. int ty = ((pos >> 8) & 0xff) - 13;
  408. int tz = pos & 0xff;
  409. tz = tz > 30 ? tz : 30;
  410. int ledCentreX = tx >> 4;
  411. int ledCentreY = ty >> 4;
  412. int adjustX = (tx - (ledCentreX << 4)) >> 2;
  413. int adjustY = (ty - (ledCentreY << 4)) >> 2;
  414. for (int dy = -2; dy <= 2; ++dy)
  415. {
  416. for (int dx = -2; dx <= 2; ++dx)
  417. {
  418. int distance = dx * dx + dy * dy;
  419. int level = distance == 0 ? 255 : (distance == 1 ? 132 : (distance < 5 ? 9 : (distance == 5 ? 2 : 0)));
  420. level += (dx * adjustX);
  421. level += (dy * adjustY);
  422. level = (tz * level) >> 8;
  423. if (level > 0)
  424. addToHeatMap (ledCentreX + dx, ledCentreY + dy,
  425. makeARGB (level, colour >> 16, colour >> 8, colour));
  426. }
  427. }
  428. }
  429. )littlefoot";
  430. }