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.

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