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.

944 lines
29KB

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