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.

899 lines
28KB

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