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) 2020 - Raw Material Software Limited
  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 6 End-User License
  8. Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
  9. End User License Agreement: www.juce.com/juce-6-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. static const uint8 whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
  21. static const uint8 blackNotes[] = { 1, 3, 6, 8, 10 };
  22. struct MidiKeyboardComponent::UpDownButton : public Button
  23. {
  24. UpDownButton (MidiKeyboardComponent& c, int d)
  25. : Button ({}), owner (c), delta (d)
  26. {
  27. }
  28. void clicked() override
  29. {
  30. auto note = owner.getLowestVisibleKey();
  31. if (delta < 0)
  32. note = (note - 1) / 12;
  33. else
  34. note = note / 12 + 1;
  35. owner.setLowestVisibleKey (note * 12);
  36. }
  37. using Button::clicked;
  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. noPendingUpdates.store (false);
  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 = (float) 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 { (float) getWidth() - blackNoteLength, x, blackNoteLength, w };
  197. case verticalKeyboardFacingRight: return { 0, (float) 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, (float) 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. return xyToNote (p).note;
  224. }
  225. MidiKeyboardComponent::NoteAndVelocity MidiKeyboardComponent::xyToNote (Point<float> pos)
  226. {
  227. if (! reallyContains (pos, false))
  228. return { -1, 0.0f };
  229. auto p = pos;
  230. if (orientation != horizontalKeyboard)
  231. {
  232. p = { p.y, p.x };
  233. if (orientation == verticalKeyboardFacingLeft)
  234. p = { p.x, (float) getWidth() - p.y };
  235. else
  236. p = { (float) getHeight() - p.x, p.y };
  237. }
  238. return remappedXYToNote (p + Point<float> (xOffset, 0));
  239. }
  240. MidiKeyboardComponent::NoteAndVelocity MidiKeyboardComponent::remappedXYToNote (Point<float> pos) const
  241. {
  242. auto blackNoteLength = getBlackNoteLength();
  243. if (pos.getY() < blackNoteLength)
  244. {
  245. for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
  246. {
  247. for (int i = 0; i < 5; ++i)
  248. {
  249. auto note = octaveStart + blackNotes[i];
  250. if (rangeStart <= note && note <= rangeEnd)
  251. {
  252. if (getKeyPos (note).contains (pos.x - xOffset))
  253. {
  254. return { note, jmax (0.0f, pos.y / blackNoteLength) };
  255. }
  256. }
  257. }
  258. }
  259. }
  260. for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
  261. {
  262. for (int i = 0; i < 7; ++i)
  263. {
  264. auto note = octaveStart + whiteNotes[i];
  265. if (note >= rangeStart && note <= rangeEnd)
  266. {
  267. if (getKeyPos (note).contains (pos.x - xOffset))
  268. {
  269. auto whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth();
  270. return { note, jmax (0.0f, pos.y / (float) whiteNoteLength) };
  271. }
  272. }
  273. }
  274. }
  275. return { -1, 0 };
  276. }
  277. //==============================================================================
  278. void MidiKeyboardComponent::repaintNote (int noteNum)
  279. {
  280. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  281. repaint (getRectangleForKey (noteNum).getSmallestIntegerContainer());
  282. }
  283. void MidiKeyboardComponent::paint (Graphics& g)
  284. {
  285. g.fillAll (findColour (whiteNoteColourId));
  286. auto lineColour = findColour (keySeparatorLineColourId);
  287. auto textColour = findColour (textLabelColourId);
  288. for (int octave = 0; octave < 128; octave += 12)
  289. {
  290. for (int white = 0; white < 7; ++white)
  291. {
  292. auto noteNum = octave + whiteNotes[white];
  293. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  294. drawWhiteNote (noteNum, g, getRectangleForKey (noteNum),
  295. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  296. mouseOverNotes.contains (noteNum), lineColour, textColour);
  297. }
  298. }
  299. float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f;
  300. auto width = getWidth();
  301. auto height = getHeight();
  302. if (orientation == verticalKeyboardFacingLeft)
  303. {
  304. x1 = (float) width - 1.0f;
  305. x2 = (float) width - 5.0f;
  306. }
  307. else if (orientation == verticalKeyboardFacingRight)
  308. x2 = 5.0f;
  309. else
  310. y2 = 5.0f;
  311. auto x = getKeyPos (rangeEnd).getEnd();
  312. auto shadowCol = findColour (shadowColourId);
  313. if (! shadowCol.isTransparent())
  314. {
  315. g.setGradientFill (ColourGradient (shadowCol, x1, y1, shadowCol.withAlpha (0.0f), x2, y2, false));
  316. switch (orientation)
  317. {
  318. case horizontalKeyboard: g.fillRect (0.0f, 0.0f, x, 5.0f); break;
  319. case verticalKeyboardFacingLeft: g.fillRect ((float) width - 5.0f, 0.0f, 5.0f, x); break;
  320. case verticalKeyboardFacingRight: g.fillRect (0.0f, 0.0f, 5.0f, x); break;
  321. default: break;
  322. }
  323. }
  324. if (! lineColour.isTransparent())
  325. {
  326. g.setColour (lineColour);
  327. switch (orientation)
  328. {
  329. case horizontalKeyboard: g.fillRect (0.0f, (float) height - 1.0f, x, 1.0f); break;
  330. case verticalKeyboardFacingLeft: g.fillRect (0.0f, 0.0f, 1.0f, x); break;
  331. case verticalKeyboardFacingRight: g.fillRect ((float) width - 1.0f, 0.0f, 1.0f, x); break;
  332. default: break;
  333. }
  334. }
  335. auto blackNoteColour = findColour (blackNoteColourId);
  336. for (int octave = 0; octave < 128; octave += 12)
  337. {
  338. for (int black = 0; black < 5; ++black)
  339. {
  340. auto noteNum = octave + blackNotes[black];
  341. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  342. drawBlackNote (noteNum, g, getRectangleForKey (noteNum),
  343. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  344. mouseOverNotes.contains (noteNum), blackNoteColour);
  345. }
  346. }
  347. }
  348. void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber, Graphics& g, Rectangle<float> area,
  349. bool isDown, bool isOver, Colour lineColour, Colour textColour)
  350. {
  351. auto c = Colours::transparentWhite;
  352. if (isDown) c = findColour (keyDownOverlayColourId);
  353. if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  354. g.setColour (c);
  355. g.fillRect (area);
  356. auto text = getWhiteNoteText (midiNoteNumber);
  357. if (text.isNotEmpty())
  358. {
  359. auto fontHeight = jmin (12.0f, keyWidth * 0.9f);
  360. g.setColour (textColour);
  361. g.setFont (Font (fontHeight).withHorizontalScale (0.8f));
  362. switch (orientation)
  363. {
  364. case horizontalKeyboard: g.drawText (text, area.withTrimmedLeft (1.0f).withTrimmedBottom (2.0f), Justification::centredBottom, false); break;
  365. case verticalKeyboardFacingLeft: g.drawText (text, area.reduced (2.0f), Justification::centredLeft, false); break;
  366. case verticalKeyboardFacingRight: g.drawText (text, area.reduced (2.0f), Justification::centredRight, false); break;
  367. default: break;
  368. }
  369. }
  370. if (! lineColour.isTransparent())
  371. {
  372. g.setColour (lineColour);
  373. switch (orientation)
  374. {
  375. case horizontalKeyboard: g.fillRect (area.withWidth (1.0f)); break;
  376. case verticalKeyboardFacingLeft: g.fillRect (area.withHeight (1.0f)); break;
  377. case verticalKeyboardFacingRight: g.fillRect (area.removeFromBottom (1.0f)); break;
  378. default: break;
  379. }
  380. if (midiNoteNumber == rangeEnd)
  381. {
  382. switch (orientation)
  383. {
  384. case horizontalKeyboard: g.fillRect (area.expanded (1.0f, 0).removeFromRight (1.0f)); break;
  385. case verticalKeyboardFacingLeft: g.fillRect (area.expanded (0, 1.0f).removeFromBottom (1.0f)); break;
  386. case verticalKeyboardFacingRight: g.fillRect (area.expanded (0, 1.0f).removeFromTop (1.0f)); break;
  387. default: break;
  388. }
  389. }
  390. }
  391. }
  392. void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/, Graphics& g, Rectangle<float> area,
  393. bool isDown, bool isOver, Colour noteFillColour)
  394. {
  395. auto c = noteFillColour;
  396. if (isDown) c = c.overlaidWith (findColour (keyDownOverlayColourId));
  397. if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  398. g.setColour (c);
  399. g.fillRect (area);
  400. if (isDown)
  401. {
  402. g.setColour (noteFillColour);
  403. g.drawRect (area);
  404. }
  405. else
  406. {
  407. g.setColour (c.brighter());
  408. auto sideIndent = 1.0f / 8.0f;
  409. auto topIndent = 7.0f / 8.0f;
  410. auto w = area.getWidth();
  411. auto h = area.getHeight();
  412. switch (orientation)
  413. {
  414. case horizontalKeyboard: g.fillRect (area.reduced (w * sideIndent, 0).removeFromTop (h * topIndent)); break;
  415. case verticalKeyboardFacingLeft: g.fillRect (area.reduced (0, h * sideIndent).removeFromRight (w * topIndent)); break;
  416. case verticalKeyboardFacingRight: g.fillRect (area.reduced (0, h * sideIndent).removeFromLeft (w * topIndent)); break;
  417. default: break;
  418. }
  419. }
  420. }
  421. void MidiKeyboardComponent::setOctaveForMiddleC (int octaveNum)
  422. {
  423. octaveNumForMiddleC = octaveNum;
  424. repaint();
  425. }
  426. String MidiKeyboardComponent::getWhiteNoteText (int midiNoteNumber)
  427. {
  428. if (midiNoteNumber % 12 == 0)
  429. return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
  430. return {};
  431. }
  432. void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h,
  433. bool mouseOver,
  434. bool buttonDown,
  435. bool movesOctavesUp)
  436. {
  437. g.fillAll (findColour (upDownButtonBackgroundColourId));
  438. float angle = 0;
  439. switch (orientation)
  440. {
  441. case horizontalKeyboard: angle = movesOctavesUp ? 0.0f : 0.5f; break;
  442. case verticalKeyboardFacingLeft: angle = movesOctavesUp ? 0.25f : 0.75f; break;
  443. case verticalKeyboardFacingRight: angle = movesOctavesUp ? 0.75f : 0.25f; break;
  444. default: jassertfalse; break;
  445. }
  446. Path path;
  447. path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
  448. path.applyTransform (AffineTransform::rotation (MathConstants<float>::twoPi * angle, 0.5f, 0.5f));
  449. g.setColour (findColour (upDownButtonArrowColourId)
  450. .withAlpha (buttonDown ? 1.0f : (mouseOver ? 0.6f : 0.4f)));
  451. g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f, (float) w - 2.0f, (float) h - 2.0f, true));
  452. }
  453. void MidiKeyboardComponent::setBlackNoteLengthProportion (float ratio) noexcept
  454. {
  455. jassert (ratio >= 0.0f && ratio <= 1.0f);
  456. if (blackNoteLengthRatio != ratio)
  457. {
  458. blackNoteLengthRatio = ratio;
  459. resized();
  460. }
  461. }
  462. float MidiKeyboardComponent::getBlackNoteLength() const noexcept
  463. {
  464. auto whiteNoteLength = orientation == horizontalKeyboard ? getHeight() : getWidth();
  465. return (float) whiteNoteLength * blackNoteLengthRatio;
  466. }
  467. void MidiKeyboardComponent::setBlackNoteWidthProportion (float ratio) noexcept
  468. {
  469. jassert (ratio >= 0.0f && ratio <= 1.0f);
  470. if (blackNoteWidthRatio != ratio)
  471. {
  472. blackNoteWidthRatio = ratio;
  473. resized();
  474. }
  475. }
  476. void MidiKeyboardComponent::resized()
  477. {
  478. auto w = getWidth();
  479. auto h = getHeight();
  480. if (w > 0 && h > 0)
  481. {
  482. if (orientation != horizontalKeyboard)
  483. std::swap (w, h);
  484. auto kx2 = getKeyPos (rangeEnd).getEnd();
  485. if ((int) firstKey != rangeStart)
  486. {
  487. auto kx1 = getKeyPos (rangeStart).getStart();
  488. if (kx2 - kx1 <= (float) w)
  489. {
  490. firstKey = (float) rangeStart;
  491. sendChangeMessage();
  492. repaint();
  493. }
  494. }
  495. scrollDown->setVisible (canScroll && firstKey > (float) rangeStart);
  496. xOffset = 0;
  497. if (canScroll)
  498. {
  499. auto scrollButtonW = jmin (scrollButtonWidth, w / 2);
  500. auto r = getLocalBounds();
  501. if (orientation == horizontalKeyboard)
  502. {
  503. scrollDown->setBounds (r.removeFromLeft (scrollButtonW));
  504. scrollUp ->setBounds (r.removeFromRight (scrollButtonW));
  505. }
  506. else if (orientation == verticalKeyboardFacingLeft)
  507. {
  508. scrollDown->setBounds (r.removeFromTop (scrollButtonW));
  509. scrollUp ->setBounds (r.removeFromBottom (scrollButtonW));
  510. }
  511. else
  512. {
  513. scrollDown->setBounds (r.removeFromBottom (scrollButtonW));
  514. scrollUp ->setBounds (r.removeFromTop (scrollButtonW));
  515. }
  516. auto endOfLastKey = getKeyPos (rangeEnd).getEnd();
  517. auto spaceAvailable = w;
  518. auto lastStartKey = remappedXYToNote ({ endOfLastKey - (float) spaceAvailable, 0 }).note + 1;
  519. if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey)
  520. {
  521. firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey);
  522. sendChangeMessage();
  523. }
  524. xOffset = getKeyPos ((int) firstKey).getStart();
  525. }
  526. else
  527. {
  528. firstKey = (float) rangeStart;
  529. }
  530. scrollUp->setVisible (canScroll && getKeyPos (rangeEnd).getStart() > (float) w);
  531. repaint();
  532. }
  533. }
  534. //==============================================================================
  535. void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
  536. {
  537. noPendingUpdates.store (false);
  538. }
  539. void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
  540. {
  541. noPendingUpdates.store (false);
  542. }
  543. //==============================================================================
  544. void MidiKeyboardComponent::resetAnyKeysInUse()
  545. {
  546. if (! keysPressed.isZero())
  547. {
  548. for (int i = 128; --i >= 0;)
  549. if (keysPressed[i])
  550. state.noteOff (midiChannel, i, 0.0f);
  551. keysPressed.clear();
  552. }
  553. for (int i = mouseDownNotes.size(); --i >= 0;)
  554. {
  555. auto noteDown = mouseDownNotes.getUnchecked(i);
  556. if (noteDown >= 0)
  557. {
  558. state.noteOff (midiChannel, noteDown, 0.0f);
  559. mouseDownNotes.set (i, -1);
  560. }
  561. mouseOverNotes.set (i, -1);
  562. }
  563. }
  564. void MidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown)
  565. {
  566. updateNoteUnderMouse (e.getEventRelativeTo (this).position, isDown, e.source.getIndex());
  567. }
  568. void MidiKeyboardComponent::updateNoteUnderMouse (Point<float> pos, bool isDown, int fingerNum)
  569. {
  570. const auto noteInfo = xyToNote (pos);
  571. const auto newNote = noteInfo.note;
  572. const auto oldNote = mouseOverNotes.getUnchecked (fingerNum);
  573. const auto oldNoteDown = mouseDownNotes.getUnchecked (fingerNum);
  574. const auto eventVelocity = useMousePositionForVelocity ? noteInfo.velocity * velocity : velocity;
  575. if (oldNote != newNote)
  576. {
  577. repaintNote (oldNote);
  578. repaintNote (newNote);
  579. mouseOverNotes.set (fingerNum, newNote);
  580. }
  581. if (isDown)
  582. {
  583. if (newNote != oldNoteDown)
  584. {
  585. if (oldNoteDown >= 0)
  586. {
  587. mouseDownNotes.set (fingerNum, -1);
  588. if (! mouseDownNotes.contains (oldNoteDown))
  589. state.noteOff (midiChannel, oldNoteDown, eventVelocity);
  590. }
  591. if (newNote >= 0 && ! mouseDownNotes.contains (newNote))
  592. {
  593. state.noteOn (midiChannel, newNote, eventVelocity);
  594. mouseDownNotes.set (fingerNum, newNote);
  595. }
  596. }
  597. }
  598. else if (oldNoteDown >= 0)
  599. {
  600. mouseDownNotes.set (fingerNum, -1);
  601. if (! mouseDownNotes.contains (oldNoteDown))
  602. state.noteOff (midiChannel, oldNoteDown, eventVelocity);
  603. }
  604. }
  605. void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
  606. {
  607. updateNoteUnderMouse (e, false);
  608. }
  609. void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
  610. {
  611. auto newNote = xyToNote (e.position).note;
  612. if (newNote >= 0 && mouseDraggedToKey (newNote, e))
  613. updateNoteUnderMouse (e, true);
  614. }
  615. bool MidiKeyboardComponent::mouseDownOnKey (int, const MouseEvent&) { return true; }
  616. bool MidiKeyboardComponent::mouseDraggedToKey (int, const MouseEvent&) { return true; }
  617. void MidiKeyboardComponent::mouseUpOnKey (int, const MouseEvent&) {}
  618. void MidiKeyboardComponent::mouseDown (const MouseEvent& e)
  619. {
  620. auto newNote = xyToNote (e.position).note;
  621. if (newNote >= 0 && mouseDownOnKey (newNote, e))
  622. updateNoteUnderMouse (e, true);
  623. }
  624. void MidiKeyboardComponent::mouseUp (const MouseEvent& e)
  625. {
  626. updateNoteUnderMouse (e, false);
  627. auto note = xyToNote (e.position).note;
  628. if (note >= 0)
  629. mouseUpOnKey (note, e);
  630. }
  631. void MidiKeyboardComponent::mouseEnter (const MouseEvent& e)
  632. {
  633. updateNoteUnderMouse (e, false);
  634. }
  635. void MidiKeyboardComponent::mouseExit (const MouseEvent& e)
  636. {
  637. updateNoteUnderMouse (e, false);
  638. }
  639. void MidiKeyboardComponent::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
  640. {
  641. auto amount = (orientation == horizontalKeyboard && wheel.deltaX != 0)
  642. ? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY
  643. : -wheel.deltaY);
  644. setLowestVisibleKeyFloat (firstKey - amount * keyWidth);
  645. }
  646. void MidiKeyboardComponent::timerCallback()
  647. {
  648. if (noPendingUpdates.exchange (true))
  649. return;
  650. for (int i = rangeStart; i <= rangeEnd; ++i)
  651. {
  652. bool isOn = state.isNoteOnForChannels (midiInChannelMask, i);
  653. if (keysCurrentlyDrawnDown[i] != isOn)
  654. {
  655. keysCurrentlyDrawnDown.setBit (i, isOn);
  656. repaintNote (i);
  657. }
  658. }
  659. }
  660. //==============================================================================
  661. void MidiKeyboardComponent::clearKeyMappings()
  662. {
  663. resetAnyKeysInUse();
  664. keyPressNotes.clear();
  665. keyPresses.clear();
  666. }
  667. void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key, int midiNoteOffsetFromC)
  668. {
  669. removeKeyPressForNote (midiNoteOffsetFromC);
  670. keyPressNotes.add (midiNoteOffsetFromC);
  671. keyPresses.add (key);
  672. }
  673. void MidiKeyboardComponent::removeKeyPressForNote (int midiNoteOffsetFromC)
  674. {
  675. for (int i = keyPressNotes.size(); --i >= 0;)
  676. {
  677. if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
  678. {
  679. keyPressNotes.remove (i);
  680. keyPresses.remove (i);
  681. }
  682. }
  683. }
  684. void MidiKeyboardComponent::setKeyPressBaseOctave (int newOctaveNumber)
  685. {
  686. jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
  687. keyMappingOctave = newOctaveNumber;
  688. }
  689. bool MidiKeyboardComponent::keyStateChanged (bool /*isKeyDown*/)
  690. {
  691. bool keyPressUsed = false;
  692. for (int i = keyPresses.size(); --i >= 0;)
  693. {
  694. auto note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i);
  695. if (keyPresses.getReference(i).isCurrentlyDown())
  696. {
  697. if (! keysPressed[note])
  698. {
  699. keysPressed.setBit (note);
  700. state.noteOn (midiChannel, note, velocity);
  701. keyPressUsed = true;
  702. }
  703. }
  704. else
  705. {
  706. if (keysPressed[note])
  707. {
  708. keysPressed.clearBit (note);
  709. state.noteOff (midiChannel, note, 0.0f);
  710. keyPressUsed = true;
  711. }
  712. }
  713. }
  714. return keyPressUsed;
  715. }
  716. bool MidiKeyboardComponent::keyPressed (const KeyPress& key)
  717. {
  718. return keyPresses.contains (key);
  719. }
  720. void MidiKeyboardComponent::focusLost (FocusChangeType)
  721. {
  722. resetAnyKeysInUse();
  723. }
  724. } // namespace juce