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.

490 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. /**
  98. The number of octaves displayed in the keyboard.
  99. */
  100. static const int kOctaves = 2;
  101. /**
  102. The number of white keys displayed in the keyboard.
  103. */
  104. static const int kWhiteKeysCount = 7 * kOctaves + 1;
  105. /**
  106. The spacing in pixels between the white keys.
  107. */
  108. static const int kWhiteKeySpacing = 1;
  109. /**
  110. The number of black keys in the keyboard.
  111. */
  112. static const int kBlackKeysCount = 5 * kOctaves;
  113. /**
  114. The number of keys in the keyboard.
  115. */
  116. static const int kKeyCount = kWhiteKeysCount + kBlackKeysCount;
  117. class KeyboardWidget : public Widget
  118. {
  119. public:
  120. class Callback
  121. {
  122. public:
  123. virtual ~Callback() {}
  124. virtual void keyboardKeyPressed(const uint keyIndex) = 0;
  125. virtual void keyboardKeyReleased(const uint keyIndex) = 0;
  126. };
  127. KeyboardWidget(Window& parent)
  128. : Widget(parent),
  129. fCallback(nullptr),
  130. fMouseDown(false),
  131. fHeldKey(nullptr)
  132. {
  133. fSVGs[kWhiteKeyResourceIndex].loadFromMemory(MidiKeyboardResources::white_keyData,
  134. MidiKeyboardResources::white_keyDataSize,
  135. 1.0f);
  136. fSVGs[kWhiteKeyPressedResourceIndex].loadFromMemory(MidiKeyboardResources::white_key_pressedData,
  137. MidiKeyboardResources::white_key_pressedDataSize,
  138. 1.0f);
  139. fSVGs[kBlackKeyResourceIndex].loadFromMemory(MidiKeyboardResources::black_keyData,
  140. MidiKeyboardResources::black_keyDataSize,
  141. 1.0f);
  142. fSVGs[kBlackKeyPressedResourceIndex].loadFromMemory(MidiKeyboardResources::black_key_pressedData,
  143. MidiKeyboardResources::black_key_pressedDataSize,
  144. 1.0f);
  145. for (int i = 0; i < kResourcesCount; ++i)
  146. {
  147. fImages[i].loadFromSVG(fSVGs[i]);
  148. }
  149. const int whiteKeysTotalSpacing = kWhiteKeySpacing * kWhiteKeysCount;
  150. const uint width = fImages[kWhiteKeyResourceIndex].getWidth() * kWhiteKeysCount + whiteKeysTotalSpacing;
  151. const uint height = fImages[kWhiteKeyResourceIndex].getHeight();
  152. setSize(width, height);
  153. setupKeyLookupTable();
  154. setKeyImages();
  155. positionKeys();
  156. }
  157. /**
  158. Set the 'pressed' state of a key in the keyboard.
  159. */
  160. void setKeyPressed(const int keyIndex, const bool pressed, const bool sendCallback = false)
  161. {
  162. DISTRHO_SAFE_ASSERT_RETURN(keyIndex >= 0 && keyIndex < kKeyCount, )
  163. PianoKey& key = *fKeysLookup[keyIndex];
  164. if (key.isPressed() == pressed)
  165. return;
  166. key.setPressed(pressed);
  167. if (fCallback != nullptr && sendCallback)
  168. {
  169. if (pressed)
  170. {
  171. fCallback->keyboardKeyPressed(keyIndex);
  172. }
  173. else
  174. {
  175. fCallback->keyboardKeyReleased(keyIndex);
  176. }
  177. }
  178. repaint();
  179. }
  180. void setCallback(Callback* callback)
  181. {
  182. fCallback = callback;
  183. }
  184. /**
  185. Draw the piano keys.
  186. */
  187. void onDisplay() override
  188. {
  189. // Draw the white keys.
  190. for (int i = 0; i < kWhiteKeysCount; ++i)
  191. {
  192. fWhiteKeys[i].draw();
  193. }
  194. // Draw the black keys, on top of the white keys.
  195. for (int i = 0; i < kBlackKeysCount; ++i)
  196. {
  197. fBlackKeys[i].draw();
  198. }
  199. }
  200. /**
  201. Get the key that is under the specified point.
  202. Return nullptr if the point is not hovering any key.
  203. */
  204. PianoKey* tryGetHoveredKey(const Point<int>& point)
  205. {
  206. // Since the black keys are on top of the white keys, we check for a mouse event on the black ones first
  207. for (int i = 0; i < kBlackKeysCount; ++i)
  208. {
  209. if (fBlackKeys[i].contains(point))
  210. {
  211. return &fBlackKeys[i];
  212. }
  213. }
  214. // Check for mouse event on white keys
  215. for (int i = 0; i < kWhiteKeysCount; ++i)
  216. {
  217. if (fWhiteKeys[i].contains(point))
  218. {
  219. return &fWhiteKeys[i];
  220. }
  221. }
  222. return nullptr;
  223. }
  224. /**
  225. Handle mouse events.
  226. */
  227. bool onMouse(const MouseEvent& ev) override
  228. {
  229. // We only care about left mouse button events.
  230. if (ev.button != 1)
  231. {
  232. return false;
  233. }
  234. fMouseDown = ev.press;
  235. if (!fMouseDown)
  236. {
  237. if (fHeldKey != nullptr)
  238. {
  239. setKeyPressed(fHeldKey->getIndex(), false, true);
  240. fHeldKey = nullptr;
  241. return true;
  242. }
  243. }
  244. if (!contains(ev.pos))
  245. {
  246. return false;
  247. }
  248. PianoKey* key = tryGetHoveredKey(ev.pos);
  249. if (key != nullptr)
  250. {
  251. setKeyPressed(key->getIndex(), ev.press, true);
  252. fHeldKey = key;
  253. return true;
  254. }
  255. return false;
  256. }
  257. /**
  258. Handle mouse motion.
  259. */
  260. bool onMotion(const MotionEvent& ev) override
  261. {
  262. if (!fMouseDown)
  263. {
  264. return false;
  265. }
  266. PianoKey* key = tryGetHoveredKey(ev.pos);
  267. if (key != fHeldKey)
  268. {
  269. if (fHeldKey != nullptr)
  270. {
  271. setKeyPressed(fHeldKey->getIndex(), false, true);
  272. }
  273. if (key != nullptr)
  274. {
  275. setKeyPressed(key->getIndex(), true, true);
  276. }
  277. fHeldKey = key;
  278. repaint();
  279. }
  280. return true;
  281. }
  282. private:
  283. void setupKeyLookupTable()
  284. {
  285. int whiteKeysCounter = 0;
  286. int blackKeysCounter = 0;
  287. for (int i = 0; i < kKeyCount; ++i)
  288. {
  289. if (isBlackKey(i))
  290. {
  291. fKeysLookup[i] = &fBlackKeys[blackKeysCounter++];
  292. }
  293. else
  294. {
  295. fKeysLookup[i] = &fWhiteKeys[whiteKeysCounter++];
  296. }
  297. }
  298. }
  299. void setKeyImages()
  300. {
  301. for (int i = 0; i < kWhiteKeysCount; ++i)
  302. {
  303. fWhiteKeys[i].setImages(fImages[kWhiteKeyResourceIndex], fImages[kWhiteKeyPressedResourceIndex]);
  304. }
  305. for (int i = 0; i < kBlackKeysCount; ++i)
  306. {
  307. fBlackKeys[i].setImages(fImages[kBlackKeyResourceIndex], fImages[kBlackKeyPressedResourceIndex]);
  308. }
  309. }
  310. /**
  311. Put the keys at their proper position in the keyboard.
  312. */
  313. void positionKeys()
  314. {
  315. const int whiteKeyWidth = fImages[kWhiteKeyResourceIndex].getWidth();
  316. const int blackKeyWidth = fImages[kBlackKeyResourceIndex].getWidth();
  317. int whiteKeysCounter = 0;
  318. int blackKeysCounter = 0;
  319. for (int i = 0; i < kKeyCount; ++i)
  320. {
  321. const int totalSpacing = kWhiteKeySpacing * whiteKeysCounter;
  322. PianoKey* key;
  323. int xPos;
  324. if (isBlackKey(i))
  325. {
  326. key = &fBlackKeys[blackKeysCounter];
  327. xPos = whiteKeysCounter * whiteKeyWidth + totalSpacing - blackKeyWidth / 2;
  328. blackKeysCounter++;
  329. }
  330. else
  331. {
  332. key = &fWhiteKeys[whiteKeysCounter];
  333. xPos = whiteKeysCounter * whiteKeyWidth + totalSpacing;
  334. whiteKeysCounter++;
  335. }
  336. key->setPosition(xPos, 0);
  337. key->setIndex(i);
  338. }
  339. }
  340. /**
  341. * Determine if a note at a certain index is associated with a white key.
  342. */
  343. bool isWhiteKey(const uint noteIndex)
  344. {
  345. return !isBlackKey(noteIndex);
  346. }
  347. /**
  348. * Determine if a note at a certain index is associated with a black key.
  349. */
  350. bool isBlackKey(const uint noteIndex)
  351. {
  352. // Bring the index down to the first octave
  353. const uint adjustedIndex = noteIndex % 12;
  354. return adjustedIndex == 1
  355. || adjustedIndex == 3
  356. || adjustedIndex == 6
  357. || adjustedIndex == 8
  358. || adjustedIndex == 10;
  359. }
  360. /**
  361. * Identifiers used for accessing the graphical resources of the widget.
  362. */
  363. enum Resources
  364. {
  365. kWhiteKeyResourceIndex = 0,
  366. kWhiteKeyPressedResourceIndex,
  367. kBlackKeyResourceIndex,
  368. kBlackKeyPressedResourceIndex,
  369. kResourcesCount
  370. };
  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. It is a nullptr if no key is currently being held.
  400. */
  401. PianoKey* fHeldKey;
  402. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(KeyboardWidget)
  403. };
  404. END_NAMESPACE_DISTRHO