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.

905 lines
28KB

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