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.

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