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.

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