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.

939 lines
29KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. class MidiKeyboardUpDownButton : public Button
  20. {
  21. public:
  22. MidiKeyboardUpDownButton (MidiKeyboardComponent& comp, const int d)
  23. : Button (String()), owner (comp), delta (d)
  24. {
  25. }
  26. void clicked() override
  27. {
  28. int note = owner.getLowestVisibleKey();
  29. if (delta < 0)
  30. note = (note - 1) / 12;
  31. else
  32. note = note / 12 + 1;
  33. owner.setLowestVisibleKey (note * 12);
  34. }
  35. void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override
  36. {
  37. owner.drawUpDownButton (g, getWidth(), getHeight(),
  38. isMouseOverButton, isButtonDown,
  39. delta > 0);
  40. }
  41. private:
  42. MidiKeyboardComponent& owner;
  43. const int delta;
  44. JUCE_DECLARE_NON_COPYABLE (MidiKeyboardUpDownButton)
  45. };
  46. //==============================================================================
  47. MidiKeyboardComponent::MidiKeyboardComponent (MidiKeyboardState& s, Orientation o)
  48. : state (s),
  49. blackNoteLengthRatio (0.7f),
  50. xOffset (0),
  51. keyWidth (16.0f),
  52. orientation (o),
  53. midiChannel (1),
  54. midiInChannelMask (0xffff),
  55. velocity (1.0f),
  56. shouldCheckState (false),
  57. rangeStart (0),
  58. rangeEnd (127),
  59. firstKey (12 * 4.0f),
  60. canScroll (true),
  61. useMousePositionForVelocity (true),
  62. shouldCheckMousePos (false),
  63. keyMappingOctave (6),
  64. octaveNumForMiddleC (3)
  65. {
  66. addChildComponent (scrollDown = new MidiKeyboardUpDownButton (*this, -1));
  67. addChildComponent (scrollUp = new MidiKeyboardUpDownButton (*this, 1));
  68. // initialise with a default set of qwerty key-mappings..
  69. const char* const keymap = "awsedftgyhujkolp;";
  70. for (int i = 0; keymap[i] != 0; ++i)
  71. setKeyPressForNote (KeyPress (keymap[i], 0, 0), i);
  72. mouseOverNotes.insertMultiple (0, -1, 32);
  73. mouseDownNotes.insertMultiple (0, -1, 32);
  74. colourChanged();
  75. setWantsKeyboardFocus (true);
  76. state.addListener (this);
  77. startTimerHz (20);
  78. }
  79. MidiKeyboardComponent::~MidiKeyboardComponent()
  80. {
  81. state.removeListener (this);
  82. }
  83. //==============================================================================
  84. void MidiKeyboardComponent::setKeyWidth (const float widthInPixels)
  85. {
  86. jassert (widthInPixels > 0);
  87. if (keyWidth != widthInPixels) // Prevent infinite recursion if the width is being computed in a 'resized()' call-back
  88. {
  89. keyWidth = widthInPixels;
  90. resized();
  91. }
  92. }
  93. void MidiKeyboardComponent::setOrientation (const Orientation newOrientation)
  94. {
  95. if (orientation != newOrientation)
  96. {
  97. orientation = newOrientation;
  98. resized();
  99. }
  100. }
  101. void MidiKeyboardComponent::setAvailableRange (const int lowestNote,
  102. const int highestNote)
  103. {
  104. jassert (lowestNote >= 0 && lowestNote <= 127);
  105. jassert (highestNote >= 0 && highestNote <= 127);
  106. jassert (lowestNote <= highestNote);
  107. if (rangeStart != lowestNote || rangeEnd != highestNote)
  108. {
  109. rangeStart = jlimit (0, 127, lowestNote);
  110. rangeEnd = jlimit (0, 127, highestNote);
  111. firstKey = jlimit ((float) rangeStart, (float) rangeEnd, firstKey);
  112. resized();
  113. }
  114. }
  115. void MidiKeyboardComponent::setLowestVisibleKey (int noteNumber)
  116. {
  117. setLowestVisibleKeyFloat ((float) noteNumber);
  118. }
  119. void MidiKeyboardComponent::setLowestVisibleKeyFloat (float noteNumber)
  120. {
  121. noteNumber = jlimit ((float) rangeStart, (float) rangeEnd, noteNumber);
  122. if (noteNumber != firstKey)
  123. {
  124. const bool hasMoved = (((int) firstKey) != (int) noteNumber);
  125. firstKey = noteNumber;
  126. if (hasMoved)
  127. sendChangeMessage();
  128. resized();
  129. }
  130. }
  131. void MidiKeyboardComponent::setScrollButtonsVisible (const bool newCanScroll)
  132. {
  133. if (canScroll != newCanScroll)
  134. {
  135. canScroll = newCanScroll;
  136. resized();
  137. }
  138. }
  139. void MidiKeyboardComponent::colourChanged()
  140. {
  141. setOpaque (findColour (whiteNoteColourId).isOpaque());
  142. repaint();
  143. }
  144. //==============================================================================
  145. void MidiKeyboardComponent::setMidiChannel (const int midiChannelNumber)
  146. {
  147. jassert (midiChannelNumber > 0 && midiChannelNumber <= 16);
  148. if (midiChannel != midiChannelNumber)
  149. {
  150. resetAnyKeysInUse();
  151. midiChannel = jlimit (1, 16, midiChannelNumber);
  152. }
  153. }
  154. void MidiKeyboardComponent::setMidiChannelsToDisplay (const int midiChannelMask)
  155. {
  156. midiInChannelMask = midiChannelMask;
  157. shouldCheckState = true;
  158. }
  159. void MidiKeyboardComponent::setVelocity (const float v, const bool useMousePosition)
  160. {
  161. velocity = jlimit (0.0f, 1.0f, v);
  162. useMousePositionForVelocity = useMousePosition;
  163. }
  164. //==============================================================================
  165. void MidiKeyboardComponent::getKeyPosition (int midiNoteNumber, const float keyWidth_, int& x, int& w) const
  166. {
  167. jassert (midiNoteNumber >= 0 && midiNoteNumber < 128);
  168. static const float blackNoteWidth = 0.7f;
  169. static const float notePos[] = { 0.0f, 1 - blackNoteWidth * 0.6f,
  170. 1.0f, 2 - blackNoteWidth * 0.4f,
  171. 2.0f,
  172. 3.0f, 4 - blackNoteWidth * 0.7f,
  173. 4.0f, 5 - blackNoteWidth * 0.5f,
  174. 5.0f, 6 - blackNoteWidth * 0.3f,
  175. 6.0f };
  176. const int octave = midiNoteNumber / 12;
  177. const int note = midiNoteNumber % 12;
  178. x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_);
  179. w = roundToInt (MidiMessage::isMidiNoteBlack (note) ? blackNoteWidth * keyWidth_ : keyWidth_);
  180. }
  181. void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const
  182. {
  183. getKeyPosition (midiNoteNumber, keyWidth, x, w);
  184. int rx, rw;
  185. getKeyPosition (rangeStart, keyWidth, rx, rw);
  186. x -= xOffset + rx;
  187. }
  188. Rectangle<int> MidiKeyboardComponent::getRectangleForKey (const int note) const
  189. {
  190. jassert (note >= rangeStart && note <= rangeEnd);
  191. int x, w;
  192. getKeyPos (note, x, w);
  193. if (MidiMessage::isMidiNoteBlack (note))
  194. {
  195. const int blackNoteLength = getBlackNoteLength();
  196. switch (orientation)
  197. {
  198. case horizontalKeyboard: return Rectangle<int> (x, 0, w, blackNoteLength);
  199. case verticalKeyboardFacingLeft: return Rectangle<int> (getWidth() - blackNoteLength, x, blackNoteLength, w);
  200. case verticalKeyboardFacingRight: return Rectangle<int> (0, getHeight() - x - w, blackNoteLength, w);
  201. default: jassertfalse; break;
  202. }
  203. }
  204. else
  205. {
  206. switch (orientation)
  207. {
  208. case horizontalKeyboard: return Rectangle<int> (x, 0, w, getHeight());
  209. case verticalKeyboardFacingLeft: return Rectangle<int> (0, x, getWidth(), w);
  210. case verticalKeyboardFacingRight: return Rectangle<int> (0, getHeight() - x - w, getWidth(), w);
  211. default: jassertfalse; break;
  212. }
  213. }
  214. return Rectangle<int>();
  215. }
  216. int MidiKeyboardComponent::getKeyStartPosition (const int midiNoteNumber) const
  217. {
  218. int x, w;
  219. getKeyPos (midiNoteNumber, x, w);
  220. return x;
  221. }
  222. int MidiKeyboardComponent::getTotalKeyboardWidth() const noexcept
  223. {
  224. int x, w;
  225. getKeyPos (rangeEnd, x, w);
  226. return x + w;
  227. }
  228. int MidiKeyboardComponent::getNoteAtPosition (Point<int> p)
  229. {
  230. float v;
  231. return xyToNote (p, v);
  232. }
  233. const uint8 MidiKeyboardComponent::whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
  234. const uint8 MidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 };
  235. int MidiKeyboardComponent::xyToNote (Point<int> pos, float& mousePositionVelocity)
  236. {
  237. if (! reallyContains (pos, false))
  238. return -1;
  239. Point<int> p (pos);
  240. if (orientation != horizontalKeyboard)
  241. {
  242. p = Point<int> (p.y, p.x);
  243. if (orientation == verticalKeyboardFacingLeft)
  244. p = Point<int> (p.x, getWidth() - p.y);
  245. else
  246. p = Point<int> (getHeight() - p.x, p.y);
  247. }
  248. return remappedXYToNote (p + Point<int> (xOffset, 0), mousePositionVelocity);
  249. }
  250. int MidiKeyboardComponent::remappedXYToNote (Point<int> pos, float& mousePositionVelocity) const
  251. {
  252. const int blackNoteLength = getBlackNoteLength();
  253. if (pos.getY() < blackNoteLength)
  254. {
  255. for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
  256. {
  257. for (int i = 0; i < 5; ++i)
  258. {
  259. const int note = octaveStart + blackNotes [i];
  260. if (note >= rangeStart && note <= rangeEnd)
  261. {
  262. int kx, kw;
  263. getKeyPos (note, kx, kw);
  264. kx += xOffset;
  265. if (pos.x >= kx && pos.x < kx + kw)
  266. {
  267. mousePositionVelocity = pos.y / (float) blackNoteLength;
  268. return note;
  269. }
  270. }
  271. }
  272. }
  273. }
  274. for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
  275. {
  276. for (int i = 0; i < 7; ++i)
  277. {
  278. const int note = octaveStart + whiteNotes [i];
  279. if (note >= rangeStart && note <= rangeEnd)
  280. {
  281. int kx, kw;
  282. getKeyPos (note, kx, kw);
  283. kx += xOffset;
  284. if (pos.x >= kx && pos.x < kx + kw)
  285. {
  286. const int whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth();
  287. mousePositionVelocity = pos.y / (float) whiteNoteLength;
  288. return note;
  289. }
  290. }
  291. }
  292. }
  293. mousePositionVelocity = 0;
  294. return -1;
  295. }
  296. //==============================================================================
  297. void MidiKeyboardComponent::repaintNote (const int noteNum)
  298. {
  299. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  300. repaint (getRectangleForKey (noteNum));
  301. }
  302. void MidiKeyboardComponent::paint (Graphics& g)
  303. {
  304. g.fillAll (findColour (whiteNoteColourId));
  305. const Colour lineColour (findColour (keySeparatorLineColourId));
  306. const Colour textColour (findColour (textLabelColourId));
  307. for (int octave = 0; octave < 128; octave += 12)
  308. {
  309. for (int white = 0; white < 7; ++white)
  310. {
  311. const int noteNum = octave + whiteNotes [white];
  312. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  313. {
  314. Rectangle<int> pos = getRectangleForKey (noteNum);
  315. drawWhiteNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
  316. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  317. mouseOverNotes.contains (noteNum), lineColour, textColour);
  318. }
  319. }
  320. }
  321. float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f;
  322. const int width = getWidth();
  323. const int height = getHeight();
  324. if (orientation == verticalKeyboardFacingLeft)
  325. {
  326. x1 = width - 1.0f;
  327. x2 = width - 5.0f;
  328. }
  329. else if (orientation == verticalKeyboardFacingRight)
  330. x2 = 5.0f;
  331. else
  332. y2 = 5.0f;
  333. int x, w;
  334. getKeyPos (rangeEnd, x, w);
  335. x += w;
  336. const Colour shadowCol (findColour (shadowColourId));
  337. if (! shadowCol.isTransparent())
  338. {
  339. g.setGradientFill (ColourGradient (shadowCol, x1, y1, shadowCol.withAlpha (0.0f), x2, y2, false));
  340. switch (orientation)
  341. {
  342. case horizontalKeyboard: g.fillRect (0, 0, x, 5); break;
  343. case verticalKeyboardFacingLeft: g.fillRect (width - 5, 0, 5, x); break;
  344. case verticalKeyboardFacingRight: g.fillRect (0, 0, 5, x); break;
  345. default: break;
  346. }
  347. }
  348. if (! lineColour.isTransparent())
  349. {
  350. g.setColour (lineColour);
  351. switch (orientation)
  352. {
  353. case horizontalKeyboard: g.fillRect (0, height - 1, x, 1); break;
  354. case verticalKeyboardFacingLeft: g.fillRect (0, 0, 1, x); break;
  355. case verticalKeyboardFacingRight: g.fillRect (width - 1, 0, 1, x); break;
  356. default: break;
  357. }
  358. }
  359. const Colour blackNoteColour (findColour (blackNoteColourId));
  360. for (int octave = 0; octave < 128; octave += 12)
  361. {
  362. for (int black = 0; black < 5; ++black)
  363. {
  364. const int noteNum = octave + blackNotes [black];
  365. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  366. {
  367. Rectangle<int> pos = getRectangleForKey (noteNum);
  368. drawBlackNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
  369. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  370. mouseOverNotes.contains (noteNum), blackNoteColour);
  371. }
  372. }
  373. }
  374. }
  375. void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber,
  376. Graphics& g, int x, int y, int w, int h,
  377. bool isDown, bool isOver,
  378. const Colour& lineColour,
  379. const Colour& textColour)
  380. {
  381. Colour c (Colours::transparentWhite);
  382. if (isDown) c = findColour (keyDownOverlayColourId);
  383. if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  384. g.setColour (c);
  385. g.fillRect (x, y, w, h);
  386. const String text (getWhiteNoteText (midiNoteNumber));
  387. if (text.isNotEmpty())
  388. {
  389. const float fontHeight = jmin (12.0f, keyWidth * 0.9f);
  390. g.setColour (textColour);
  391. g.setFont (Font (fontHeight).withHorizontalScale (0.8f));
  392. switch (orientation)
  393. {
  394. case horizontalKeyboard: g.drawText (text, x + 1, y, w - 1, h - 2, Justification::centredBottom, false); break;
  395. case verticalKeyboardFacingLeft: g.drawText (text, x + 2, y + 2, w - 4, h - 4, Justification::centredLeft, false); break;
  396. case verticalKeyboardFacingRight: g.drawText (text, x + 2, y + 2, w - 4, h - 4, Justification::centredRight, false); break;
  397. default: break;
  398. }
  399. }
  400. if (! lineColour.isTransparent())
  401. {
  402. g.setColour (lineColour);
  403. switch (orientation)
  404. {
  405. case horizontalKeyboard: g.fillRect (x, y, 1, h); break;
  406. case verticalKeyboardFacingLeft: g.fillRect (x, y, w, 1); break;
  407. case verticalKeyboardFacingRight: g.fillRect (x, y + h - 1, w, 1); break;
  408. default: break;
  409. }
  410. if (midiNoteNumber == rangeEnd)
  411. {
  412. switch (orientation)
  413. {
  414. case horizontalKeyboard: g.fillRect (x + w, y, 1, h); break;
  415. case verticalKeyboardFacingLeft: g.fillRect (x, y + h, w, 1); break;
  416. case verticalKeyboardFacingRight: g.fillRect (x, y - 1, w, 1); break;
  417. default: break;
  418. }
  419. }
  420. }
  421. }
  422. void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/,
  423. Graphics& g, int x, int y, int w, int h,
  424. bool isDown, bool isOver,
  425. const Colour& noteFillColour)
  426. {
  427. Colour c (noteFillColour);
  428. if (isDown) c = c.overlaidWith (findColour (keyDownOverlayColourId));
  429. if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  430. g.setColour (c);
  431. g.fillRect (x, y, w, h);
  432. if (isDown)
  433. {
  434. g.setColour (noteFillColour);
  435. g.drawRect (x, y, w, h);
  436. }
  437. else
  438. {
  439. g.setColour (c.brighter());
  440. const int xIndent = jmax (1, jmin (w, h) / 8);
  441. switch (orientation)
  442. {
  443. case horizontalKeyboard: g.fillRect (x + xIndent, y, w - xIndent * 2, 7 * h / 8); break;
  444. case verticalKeyboardFacingLeft: g.fillRect (x + w / 8, y + xIndent, w - w / 8, h - xIndent * 2); break;
  445. case verticalKeyboardFacingRight: g.fillRect (x, y + xIndent, 7 * w / 8, h - xIndent * 2); break;
  446. default: break;
  447. }
  448. }
  449. }
  450. void MidiKeyboardComponent::setOctaveForMiddleC (const int octaveNum)
  451. {
  452. octaveNumForMiddleC = octaveNum;
  453. repaint();
  454. }
  455. String MidiKeyboardComponent::getWhiteNoteText (const int midiNoteNumber)
  456. {
  457. if (midiNoteNumber % 12 == 0)
  458. return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
  459. return {};
  460. }
  461. void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h,
  462. const bool mouseOver,
  463. const bool buttonDown,
  464. const bool movesOctavesUp)
  465. {
  466. g.fillAll (findColour (upDownButtonBackgroundColourId));
  467. float angle;
  468. switch (orientation)
  469. {
  470. case horizontalKeyboard: angle = movesOctavesUp ? 0.0f : 0.5f; break;
  471. case verticalKeyboardFacingLeft: angle = movesOctavesUp ? 0.25f : 0.75f; break;
  472. case verticalKeyboardFacingRight: angle = movesOctavesUp ? 0.75f : 0.25f; break;
  473. default: jassertfalse; angle = 0; break;
  474. }
  475. Path path;
  476. path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
  477. path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * angle, 0.5f, 0.5f));
  478. g.setColour (findColour (upDownButtonArrowColourId)
  479. .withAlpha (buttonDown ? 1.0f : (mouseOver ? 0.6f : 0.4f)));
  480. g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f, w - 2.0f, h - 2.0f, true));
  481. }
  482. void MidiKeyboardComponent::setBlackNoteLengthProportion (float ratio) noexcept
  483. {
  484. jassert (ratio >= 0.0f && ratio <= 1.0f);
  485. if (blackNoteLengthRatio != ratio)
  486. {
  487. blackNoteLengthRatio = ratio;
  488. resized();
  489. }
  490. }
  491. int MidiKeyboardComponent::getBlackNoteLength() const noexcept
  492. {
  493. const int whiteNoteLength = orientation == horizontalKeyboard ? getHeight() : getWidth();
  494. return roundToInt (whiteNoteLength * blackNoteLengthRatio);
  495. }
  496. void MidiKeyboardComponent::resized()
  497. {
  498. int w = getWidth();
  499. int h = getHeight();
  500. if (w > 0 && h > 0)
  501. {
  502. if (orientation != horizontalKeyboard)
  503. std::swap (w, h);
  504. int kx2, kw2;
  505. getKeyPos (rangeEnd, kx2, kw2);
  506. kx2 += kw2;
  507. if ((int) firstKey != rangeStart)
  508. {
  509. int kx1, kw1;
  510. getKeyPos (rangeStart, kx1, kw1);
  511. if (kx2 - kx1 <= w)
  512. {
  513. firstKey = (float) rangeStart;
  514. sendChangeMessage();
  515. repaint();
  516. }
  517. }
  518. scrollDown->setVisible (canScroll && firstKey > (float) rangeStart);
  519. xOffset = 0;
  520. if (canScroll)
  521. {
  522. const int scrollButtonW = jmin (12, w / 2);
  523. Rectangle<int> r (getLocalBounds());
  524. if (orientation == horizontalKeyboard)
  525. {
  526. scrollDown->setBounds (r.removeFromLeft (scrollButtonW));
  527. scrollUp ->setBounds (r.removeFromRight (scrollButtonW));
  528. }
  529. else if (orientation == verticalKeyboardFacingLeft)
  530. {
  531. scrollDown->setBounds (r.removeFromTop (scrollButtonW));
  532. scrollUp ->setBounds (r.removeFromBottom (scrollButtonW));
  533. }
  534. else
  535. {
  536. scrollDown->setBounds (r.removeFromBottom (scrollButtonW));
  537. scrollUp ->setBounds (r.removeFromTop (scrollButtonW));
  538. }
  539. int endOfLastKey, kw;
  540. getKeyPos (rangeEnd, endOfLastKey, kw);
  541. endOfLastKey += kw;
  542. float mousePositionVelocity;
  543. const int spaceAvailable = w;
  544. const int lastStartKey = remappedXYToNote (Point<int> (endOfLastKey - spaceAvailable, 0), mousePositionVelocity) + 1;
  545. if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey)
  546. {
  547. firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey);
  548. sendChangeMessage();
  549. }
  550. int newOffset = 0;
  551. getKeyPos ((int) firstKey, newOffset, kw);
  552. xOffset = newOffset;
  553. }
  554. else
  555. {
  556. firstKey = (float) rangeStart;
  557. }
  558. getKeyPos (rangeEnd, kx2, kw2);
  559. scrollUp->setVisible (canScroll && kx2 > w);
  560. repaint();
  561. }
  562. }
  563. //==============================================================================
  564. void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
  565. {
  566. shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
  567. }
  568. void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
  569. {
  570. shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
  571. }
  572. //==============================================================================
  573. void MidiKeyboardComponent::resetAnyKeysInUse()
  574. {
  575. if (! keysPressed.isZero())
  576. {
  577. for (int i = 128; --i >= 0;)
  578. if (keysPressed[i])
  579. state.noteOff (midiChannel, i, 0.0f);
  580. keysPressed.clear();
  581. }
  582. for (int i = mouseDownNotes.size(); --i >= 0;)
  583. {
  584. const int noteDown = mouseDownNotes.getUnchecked(i);
  585. if (noteDown >= 0)
  586. {
  587. state.noteOff (midiChannel, noteDown, 0.0f);
  588. mouseDownNotes.set (i, -1);
  589. }
  590. mouseOverNotes.set (i, -1);
  591. }
  592. }
  593. void MidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown)
  594. {
  595. updateNoteUnderMouse (e.getEventRelativeTo (this).getPosition(), isDown, e.source.getIndex());
  596. }
  597. void MidiKeyboardComponent::updateNoteUnderMouse (Point<int> pos, bool isDown, int fingerNum)
  598. {
  599. float mousePositionVelocity = 0.0f;
  600. const int newNote = xyToNote (pos, mousePositionVelocity);
  601. const int oldNote = mouseOverNotes.getUnchecked (fingerNum);
  602. const int oldNoteDown = mouseDownNotes.getUnchecked (fingerNum);
  603. const float eventVelocity = useMousePositionForVelocity ? mousePositionVelocity * velocity : 1.0f;
  604. if (oldNote != newNote)
  605. {
  606. repaintNote (oldNote);
  607. repaintNote (newNote);
  608. mouseOverNotes.set (fingerNum, newNote);
  609. }
  610. if (isDown)
  611. {
  612. if (newNote != oldNoteDown)
  613. {
  614. if (oldNoteDown >= 0)
  615. {
  616. mouseDownNotes.set (fingerNum, -1);
  617. if (! mouseDownNotes.contains (oldNoteDown))
  618. state.noteOff (midiChannel, oldNoteDown, eventVelocity);
  619. }
  620. if (newNote >= 0 && ! mouseDownNotes.contains (newNote))
  621. {
  622. state.noteOn (midiChannel, newNote, eventVelocity);
  623. mouseDownNotes.set (fingerNum, newNote);
  624. }
  625. }
  626. }
  627. else if (oldNoteDown >= 0)
  628. {
  629. mouseDownNotes.set (fingerNum, -1);
  630. if (! mouseDownNotes.contains (oldNoteDown))
  631. state.noteOff (midiChannel, oldNoteDown, eventVelocity);
  632. }
  633. }
  634. void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
  635. {
  636. updateNoteUnderMouse (e, false);
  637. shouldCheckMousePos = false;
  638. }
  639. void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
  640. {
  641. float mousePositionVelocity;
  642. const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  643. if (newNote >= 0)
  644. mouseDraggedToKey (newNote, e);
  645. updateNoteUnderMouse (e, true);
  646. }
  647. bool MidiKeyboardComponent::mouseDownOnKey (int, const MouseEvent&) { return true; }
  648. void MidiKeyboardComponent::mouseDraggedToKey (int, const MouseEvent&) {}
  649. void MidiKeyboardComponent::mouseUpOnKey (int, const MouseEvent&) {}
  650. void MidiKeyboardComponent::mouseDown (const MouseEvent& e)
  651. {
  652. float mousePositionVelocity;
  653. const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  654. if (newNote >= 0 && mouseDownOnKey (newNote, e))
  655. {
  656. updateNoteUnderMouse (e, true);
  657. shouldCheckMousePos = true;
  658. }
  659. }
  660. void MidiKeyboardComponent::mouseUp (const MouseEvent& e)
  661. {
  662. updateNoteUnderMouse (e, false);
  663. shouldCheckMousePos = false;
  664. float mousePositionVelocity;
  665. const int note = xyToNote (e.getPosition(), mousePositionVelocity);
  666. if (note >= 0)
  667. mouseUpOnKey (note, e);
  668. }
  669. void MidiKeyboardComponent::mouseEnter (const MouseEvent& e)
  670. {
  671. updateNoteUnderMouse (e, false);
  672. }
  673. void MidiKeyboardComponent::mouseExit (const MouseEvent& e)
  674. {
  675. updateNoteUnderMouse (e, false);
  676. }
  677. void MidiKeyboardComponent::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
  678. {
  679. const float amount = (orientation == horizontalKeyboard && wheel.deltaX != 0)
  680. ? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY
  681. : -wheel.deltaY);
  682. setLowestVisibleKeyFloat (firstKey - amount * keyWidth);
  683. }
  684. void MidiKeyboardComponent::timerCallback()
  685. {
  686. if (shouldCheckState)
  687. {
  688. shouldCheckState = false;
  689. for (int i = rangeStart; i <= rangeEnd; ++i)
  690. {
  691. if (keysCurrentlyDrawnDown[i] != state.isNoteOnForChannels (midiInChannelMask, i))
  692. {
  693. keysCurrentlyDrawnDown.setBit (i, state.isNoteOnForChannels (midiInChannelMask, i));
  694. repaintNote (i);
  695. }
  696. }
  697. }
  698. if (shouldCheckMousePos)
  699. {
  700. for (auto& ms : Desktop::getInstance().getMouseSources())
  701. if (ms.getComponentUnderMouse() == this || isParentOf (ms.getComponentUnderMouse()))
  702. updateNoteUnderMouse (getLocalPoint (nullptr, ms.getScreenPosition()).roundToInt(), ms.isDragging(), ms.getIndex());
  703. }
  704. }
  705. //==============================================================================
  706. void MidiKeyboardComponent::clearKeyMappings()
  707. {
  708. resetAnyKeysInUse();
  709. keyPressNotes.clear();
  710. keyPresses.clear();
  711. }
  712. void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key, int midiNoteOffsetFromC)
  713. {
  714. removeKeyPressForNote (midiNoteOffsetFromC);
  715. keyPressNotes.add (midiNoteOffsetFromC);
  716. keyPresses.add (key);
  717. }
  718. void MidiKeyboardComponent::removeKeyPressForNote (const int midiNoteOffsetFromC)
  719. {
  720. for (int i = keyPressNotes.size(); --i >= 0;)
  721. {
  722. if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
  723. {
  724. keyPressNotes.remove (i);
  725. keyPresses.remove (i);
  726. }
  727. }
  728. }
  729. void MidiKeyboardComponent::setKeyPressBaseOctave (const int newOctaveNumber)
  730. {
  731. jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
  732. keyMappingOctave = newOctaveNumber;
  733. }
  734. bool MidiKeyboardComponent::keyStateChanged (const bool /*isKeyDown*/)
  735. {
  736. bool keyPressUsed = false;
  737. for (int i = keyPresses.size(); --i >= 0;)
  738. {
  739. const int note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i);
  740. if (keyPresses.getReference(i).isCurrentlyDown())
  741. {
  742. if (! keysPressed [note])
  743. {
  744. keysPressed.setBit (note);
  745. state.noteOn (midiChannel, note, velocity);
  746. keyPressUsed = true;
  747. }
  748. }
  749. else
  750. {
  751. if (keysPressed [note])
  752. {
  753. keysPressed.clearBit (note);
  754. state.noteOff (midiChannel, note, 0.0f);
  755. keyPressUsed = true;
  756. }
  757. }
  758. }
  759. return keyPressUsed;
  760. }
  761. bool MidiKeyboardComponent::keyPressed (const KeyPress& key)
  762. {
  763. return keyPresses.contains (key);
  764. }
  765. void MidiKeyboardComponent::focusLost (FocusChangeType)
  766. {
  767. resetAnyKeysInUse();
  768. }