DISTRHO Plugin Framework
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.

486 lines
11KB

  1. #include "Widget.hpp"
  2. #include "Image.hpp"
  3. #include "SVG.hpp"
  4. #include "resources/MidiKeyboardResources.hpp"
  5. START_NAMESPACE_DISTRHO
  6. class PianoKey
  7. {
  8. public:
  9. PianoKey()
  10. : fPressed(false),
  11. fImageNormal(),
  12. fImageDown(),
  13. fIndex(-1)
  14. {
  15. }
  16. /**
  17. Set the images that will be used when drawing the key.
  18. */
  19. void setImages(const Image& imageNormal, const Image& imageDown)
  20. {
  21. fImageNormal = imageNormal;
  22. fImageDown = imageDown;
  23. fBoundingBox.setSize(imageNormal.getWidth(), imageNormal.getHeight());
  24. }
  25. /**
  26. Set the state of the key.
  27. */
  28. void setPressed(bool pressed) noexcept
  29. {
  30. fPressed = pressed;
  31. }
  32. /**
  33. Indicate if the key is currently down.
  34. */
  35. bool isPressed() noexcept
  36. {
  37. return fPressed;
  38. }
  39. /**
  40. Set the note index of the key.
  41. */
  42. void setIndex(const int index)
  43. {
  44. fIndex = index;
  45. }
  46. /**
  47. Get the note index of the key.
  48. */
  49. int getIndex()
  50. {
  51. return fIndex;
  52. }
  53. /**
  54. Determine if a point intersects with the key's bounding box.
  55. */
  56. bool contains(Point<int> point) noexcept
  57. {
  58. return fBoundingBox.contains(point);
  59. }
  60. /**
  61. Set the position of the key, relative to its parent KeyboardWidget.
  62. */
  63. void setPosition(const int x, const int y) noexcept
  64. {
  65. fBoundingBox.setPos(x, y);
  66. }
  67. /**
  68. Get the width of the key.
  69. */
  70. uint getWidth() noexcept
  71. {
  72. return fBoundingBox.getWidth();
  73. }
  74. /**
  75. Draw the key at its bounding box's position.
  76. */
  77. void draw()
  78. {
  79. Image* img;
  80. if (isPressed())
  81. {
  82. img = &fImageDown;
  83. }
  84. else
  85. {
  86. img = &fImageNormal;
  87. }
  88. img->drawAt(fBoundingBox.getPos());
  89. }
  90. private:
  91. Rectangle<int> fBoundingBox;
  92. bool fPressed;
  93. Image fImageNormal;
  94. Image fImageDown;
  95. int fIndex;
  96. };
  97. class KeyboardWidget : public Widget
  98. {
  99. public:
  100. class Callback
  101. {
  102. public:
  103. virtual ~Callback() {}
  104. virtual void keyboardKeyPressed(const uint keyIndex) = 0;
  105. virtual void keyboardKeyReleased(const uint keyIndex) = 0;
  106. };
  107. KeyboardWidget(Window& parent)
  108. : Widget(parent),
  109. fCallback(nullptr),
  110. fMouseDown(false),
  111. fHeldKey(nullptr)
  112. {
  113. fSVGs[kWhiteKeyResourceIndex].loadFromMemory(MidiKeyboardResources::white_keyData,
  114. MidiKeyboardResources::white_keyDataSize,
  115. 1.0f);
  116. fSVGs[kWhiteKeyPressedResourceIndex].loadFromMemory(MidiKeyboardResources::white_key_pressedData,
  117. MidiKeyboardResources::white_key_pressedDataSize,
  118. 1.0f);
  119. fSVGs[kBlackKeyResourceIndex].loadFromMemory(MidiKeyboardResources::black_keyData,
  120. MidiKeyboardResources::black_keyDataSize,
  121. 1.0f);
  122. fSVGs[kBlackKeyPressedResourceIndex].loadFromMemory(MidiKeyboardResources::black_key_pressedData,
  123. MidiKeyboardResources::black_key_pressedDataSize,
  124. 1.0f);
  125. for (int i = 0; i < kResourcesCount; ++i)
  126. {
  127. fImages[i].loadFromSVG(fSVGs[i]);
  128. }
  129. const int whiteKeysTotalSpacing = kWhiteKeySpacing * kWhiteKeysCount;
  130. const uint width = fImages[kWhiteKeyResourceIndex].getWidth() * kWhiteKeysCount + whiteKeysTotalSpacing;
  131. const uint height = fImages[kWhiteKeyResourceIndex].getHeight();
  132. setSize(width, height);
  133. setupKeyLookupTable();
  134. setKeyImages();
  135. positionKeys();
  136. }
  137. /**
  138. Set the 'pressed' state of a key in the keyboard.
  139. */
  140. void setKeyPressed(const uint keyIndex, const bool pressed, const bool sendCallback = false)
  141. {
  142. DISTRHO_SAFE_ASSERT_RETURN(keyIndex < kKeyCount, )
  143. PianoKey& key = *fKeysLookup[keyIndex];
  144. if (key.isPressed() == pressed)
  145. return;
  146. key.setPressed(pressed);
  147. if (fCallback != nullptr && sendCallback)
  148. {
  149. if (pressed)
  150. {
  151. fCallback->keyboardKeyPressed(keyIndex);
  152. }
  153. else
  154. {
  155. fCallback->keyboardKeyReleased(keyIndex);
  156. }
  157. }
  158. repaint();
  159. }
  160. void setCallback(Callback* callback)
  161. {
  162. fCallback = callback;
  163. }
  164. /**
  165. Draw the piano keys.
  166. */
  167. void onDisplay() override
  168. {
  169. // Draw the white keys.
  170. for (int i = 0; i < kWhiteKeysCount; ++i)
  171. {
  172. fWhiteKeys[i].draw();
  173. }
  174. // Draw the black keys, on top of the white keys.
  175. for (int i = 0; i < kBlackKeysCount; ++i)
  176. {
  177. fBlackKeys[i].draw();
  178. }
  179. }
  180. /**
  181. Get the key that is under the specified point.
  182. Return nullptr if the point is not hovering any key.
  183. */
  184. PianoKey* tryGetHoveredKey(const Point<int>& point)
  185. {
  186. // Since the black keys are on top of the white keys, we check for a mouse event on the black ones first
  187. for (int i = 0; i < kBlackKeysCount; ++i)
  188. {
  189. if (fBlackKeys[i].contains(point))
  190. {
  191. return &fBlackKeys[i];
  192. }
  193. }
  194. // Check for mouse event on white keys
  195. for (int i = 0; i < kWhiteKeysCount; ++i)
  196. {
  197. if (fWhiteKeys[i].contains(point))
  198. {
  199. return &fWhiteKeys[i];
  200. }
  201. }
  202. return nullptr;
  203. }
  204. /**
  205. Handle mouse events.
  206. */
  207. bool onMouse(const MouseEvent& ev) override
  208. {
  209. // We only care about left mouse button events.
  210. if (ev.button != 1)
  211. {
  212. return false;
  213. }
  214. fMouseDown = ev.press;
  215. if (!fMouseDown)
  216. {
  217. if (fHeldKey != nullptr)
  218. {
  219. setKeyPressed(fHeldKey->getIndex(), false, true);
  220. fHeldKey = nullptr;
  221. return true;
  222. }
  223. }
  224. if (!contains(ev.pos))
  225. {
  226. return false;
  227. }
  228. PianoKey* key = tryGetHoveredKey(ev.pos);
  229. if (key != nullptr)
  230. {
  231. setKeyPressed(key->getIndex(), ev.press, true);
  232. fHeldKey = key;
  233. return true;
  234. }
  235. return false;
  236. }
  237. /**
  238. Handle mouse motion.
  239. */
  240. bool onMotion(const MotionEvent& ev) override
  241. {
  242. if (!fMouseDown)
  243. {
  244. return false;
  245. }
  246. PianoKey* key = tryGetHoveredKey(ev.pos);
  247. if (key != fHeldKey)
  248. {
  249. if (fHeldKey != nullptr)
  250. {
  251. setKeyPressed(fHeldKey->getIndex(), false, true);
  252. }
  253. if (key != nullptr)
  254. {
  255. setKeyPressed(key->getIndex(), true, true);
  256. }
  257. fHeldKey = key;
  258. repaint();
  259. }
  260. return true;
  261. }
  262. private:
  263. void setupKeyLookupTable()
  264. {
  265. int whiteKeysCounter = 0;
  266. int blackKeysCounter = 0;
  267. for (int i = 0; i < kKeyCount; ++i)
  268. {
  269. if (isBlackKey(i))
  270. {
  271. fKeysLookup[i] = &fBlackKeys[blackKeysCounter++];
  272. }
  273. else
  274. {
  275. fKeysLookup[i] = &fWhiteKeys[whiteKeysCounter++];
  276. }
  277. }
  278. }
  279. void setKeyImages()
  280. {
  281. for (int i = 0; i < kWhiteKeysCount; ++i)
  282. {
  283. fWhiteKeys[i].setImages(fImages[kWhiteKeyResourceIndex], fImages[kWhiteKeyPressedResourceIndex]);
  284. }
  285. for (int i = 0; i < kBlackKeysCount; ++i)
  286. {
  287. fBlackKeys[i].setImages(fImages[kBlackKeyResourceIndex], fImages[kBlackKeyPressedResourceIndex]);
  288. }
  289. }
  290. /**
  291. Put the keys at their proper position in the keyboard.
  292. */
  293. void positionKeys()
  294. {
  295. const int whiteKeyWidth = fImages[kWhiteKeyResourceIndex].getWidth();
  296. const int blackKeyWidth = fImages[kBlackKeyResourceIndex].getWidth();
  297. int whiteKeysCounter = 0;
  298. int blackKeysCounter = 0;
  299. for (int i = 0; i < kKeyCount; ++i)
  300. {
  301. const int totalSpacing = kWhiteKeySpacing * whiteKeysCounter;
  302. PianoKey* key;
  303. int xPos;
  304. if (isBlackKey(i))
  305. {
  306. key = &fBlackKeys[blackKeysCounter];
  307. xPos = whiteKeysCounter * whiteKeyWidth + totalSpacing - blackKeyWidth / 2;
  308. blackKeysCounter++;
  309. }
  310. else
  311. {
  312. key = &fWhiteKeys[whiteKeysCounter];
  313. xPos = whiteKeysCounter * whiteKeyWidth + totalSpacing;
  314. whiteKeysCounter++;
  315. }
  316. key->setPosition(xPos, 0);
  317. key->setIndex(i);
  318. }
  319. }
  320. /**
  321. * Determine if a note at a certain index is associated with a white key.
  322. */
  323. bool isWhiteKey(const uint noteIndex)
  324. {
  325. return !isBlackKey(noteIndex);
  326. }
  327. /**
  328. * Determine if a note at a certain index is associated with a black key.
  329. */
  330. bool isBlackKey(const uint noteIndex)
  331. {
  332. // Bring the index down to the first octave
  333. const uint adjustedIndex = noteIndex % 12;
  334. return adjustedIndex == 1
  335. || adjustedIndex == 3
  336. || adjustedIndex == 6
  337. || adjustedIndex == 8
  338. || adjustedIndex == 10;
  339. }
  340. /**
  341. * Identifiers used for accessing the graphical resources of the widget.
  342. */
  343. enum Resources
  344. {
  345. kWhiteKeyResourceIndex = 0,
  346. kWhiteKeyPressedResourceIndex = 1,
  347. kBlackKeyResourceIndex = 2,
  348. kBlackKeyPressedResourceIndex = 3,
  349. kResourcesCount
  350. };
  351. /**
  352. The number of octaves displayed in the keyboard.
  353. */
  354. static constexpr int kOctaves = 2;
  355. /**
  356. The number of white keys displayed in the keyboard.
  357. */
  358. static constexpr int kWhiteKeysCount = 7 * kOctaves + 1;
  359. /**
  360. The spacing in pixels between the white keys.
  361. */
  362. static constexpr int kWhiteKeySpacing = 1;
  363. /**
  364. The number of black keys in the keyboard.
  365. */
  366. static constexpr int kBlackKeysCount = 5 * kOctaves;
  367. /**
  368. The number of keys in the keyboard.
  369. */
  370. static constexpr int kKeyCount = kWhiteKeysCount + kBlackKeysCount;
  371. /**
  372. The keyboard's white keys.
  373. */
  374. PianoKey fWhiteKeys[kWhiteKeysCount];
  375. /**
  376. The keyboard's black keys.
  377. */
  378. PianoKey fBlackKeys[kBlackKeysCount];
  379. /**
  380. Zero-indexed lookup table that maps notes to piano keys.
  381. In this example, 0 is equal to C4.
  382. */
  383. PianoKey* fKeysLookup[kKeyCount];
  384. /**
  385. Graphical resources.
  386. */
  387. SVG fSVGs[kResourcesCount];
  388. Image fImages[kResourcesCount];
  389. /**
  390. The callback pointer, used for notifying the UI of piano key presses and releases.
  391. */
  392. Callback* fCallback;
  393. /**
  394. Whether or not the left mouse button is currently pressed.
  395. */
  396. bool fMouseDown;
  397. /**
  398. The piano key that is currently pressed with the mouse.
  399. */
  400. PianoKey* fHeldKey;
  401. };
  402. END_NAMESPACE_DISTRHO