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.

898 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. class MidiKeyboardUpDownButton : public Button
  19. {
  20. public:
  21. MidiKeyboardUpDownButton (MidiKeyboardComponent& owner_, const int delta_)
  22. : Button (String::empty),
  23. owner (owner_),
  24. delta (delta_)
  25. {
  26. setOpaque (true);
  27. }
  28. void clicked()
  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)
  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& state_,
  50. const Orientation orientation_)
  51. : state (state_),
  52. xOffset (0),
  53. blackNoteLength (1),
  54. keyWidth (16.0f),
  55. orientation (orientation_),
  56. midiChannel (1),
  57. midiInChannelMask (0xffff),
  58. velocity (1.0f),
  59. shouldCheckState (false),
  60. rangeStart (0),
  61. rangeEnd (127),
  62. firstKey (12 * 4.0f),
  63. canScroll (true),
  64. useMousePositionForVelocity (true),
  65. shouldCheckMousePos (false),
  66. keyMappingOctave (6),
  67. octaveNumForMiddleC (3)
  68. {
  69. addChildComponent (scrollDown = new MidiKeyboardUpDownButton (*this, -1));
  70. addChildComponent (scrollUp = new MidiKeyboardUpDownButton (*this, 1));
  71. // initialise with a default set of querty key-mappings..
  72. const char* const keymap = "awsedftgyhujkolp;";
  73. for (int i = 0; keymap[i] != 0; ++i)
  74. setKeyPressForNote (KeyPress (keymap[i], 0, 0), i);
  75. const int numSources = Desktop::getInstance().getNumMouseSources();
  76. mouseOverNotes.insertMultiple (0, -1, numSources);
  77. mouseDownNotes.insertMultiple (0, -1, numSources);
  78. setOpaque (true);
  79. setWantsKeyboardFocus (true);
  80. state.addListener (this);
  81. startTimer (1000 / 20);
  82. }
  83. MidiKeyboardComponent::~MidiKeyboardComponent()
  84. {
  85. state.removeListener (this);
  86. }
  87. //==============================================================================
  88. void MidiKeyboardComponent::setKeyWidth (const float widthInPixels)
  89. {
  90. keyWidth = widthInPixels;
  91. resized();
  92. }
  93. void MidiKeyboardComponent::setOrientation (const Orientation newOrientation)
  94. {
  95. if (orientation != newOrientation)
  96. {
  97. orientation = newOrientation;
  98. resized();
  99. }
  100. }
  101. void MidiKeyboardComponent::setAvailableRange (const int lowestNote,
  102. const int highestNote)
  103. {
  104. jassert (lowestNote >= 0 && lowestNote <= 127);
  105. jassert (highestNote >= 0 && highestNote <= 127);
  106. jassert (lowestNote <= highestNote);
  107. if (rangeStart != lowestNote || rangeEnd != highestNote)
  108. {
  109. rangeStart = jlimit (0, 127, lowestNote);
  110. rangeEnd = jlimit (0, 127, highestNote);
  111. firstKey = jlimit ((float) rangeStart, (float) rangeEnd, firstKey);
  112. resized();
  113. }
  114. }
  115. void MidiKeyboardComponent::setLowestVisibleKey (int noteNumber)
  116. {
  117. setLowestVisibleKeyFloat ((float) noteNumber);
  118. }
  119. void MidiKeyboardComponent::setLowestVisibleKeyFloat (float noteNumber)
  120. {
  121. noteNumber = jlimit ((float) rangeStart, (float) rangeEnd, noteNumber);
  122. if (noteNumber != firstKey)
  123. {
  124. const bool hasMoved = (((int) firstKey) != (int) noteNumber);
  125. firstKey = noteNumber;
  126. if (hasMoved)
  127. {
  128. sendChangeMessage();
  129. resized();
  130. }
  131. }
  132. }
  133. void MidiKeyboardComponent::setScrollButtonsVisible (const bool canScroll_)
  134. {
  135. if (canScroll != canScroll_)
  136. {
  137. canScroll = canScroll_;
  138. resized();
  139. }
  140. }
  141. void MidiKeyboardComponent::colourChanged()
  142. {
  143. repaint();
  144. }
  145. //==============================================================================
  146. void MidiKeyboardComponent::setMidiChannel (const int midiChannelNumber)
  147. {
  148. jassert (midiChannelNumber > 0 && midiChannelNumber <= 16);
  149. if (midiChannel != midiChannelNumber)
  150. {
  151. resetAnyKeysInUse();
  152. midiChannel = jlimit (1, 16, midiChannelNumber);
  153. }
  154. }
  155. void MidiKeyboardComponent::setMidiChannelsToDisplay (const int midiChannelMask)
  156. {
  157. midiInChannelMask = midiChannelMask;
  158. shouldCheckState = true;
  159. }
  160. void MidiKeyboardComponent::setVelocity (const float velocity_, const bool useMousePositionForVelocity_)
  161. {
  162. velocity = jlimit (0.0f, 1.0f, velocity_);
  163. useMousePositionForVelocity = useMousePositionForVelocity_;
  164. }
  165. //==============================================================================
  166. void MidiKeyboardComponent::getKeyPosition (int midiNoteNumber, const float keyWidth_, int& x, int& w) const
  167. {
  168. jassert (midiNoteNumber >= 0 && midiNoteNumber < 128);
  169. static const float blackNoteWidth = 0.7f;
  170. static const float notePos[] = { 0.0f, 1 - blackNoteWidth * 0.6f,
  171. 1.0f, 2 - blackNoteWidth * 0.4f,
  172. 2.0f,
  173. 3.0f, 4 - blackNoteWidth * 0.7f,
  174. 4.0f, 5 - blackNoteWidth * 0.5f,
  175. 5.0f, 6 - blackNoteWidth * 0.3f,
  176. 6.0f };
  177. static const float widths[] = { 1.0f, blackNoteWidth,
  178. 1.0f, blackNoteWidth,
  179. 1.0f,
  180. 1.0f, blackNoteWidth,
  181. 1.0f, blackNoteWidth,
  182. 1.0f, blackNoteWidth,
  183. 1.0f };
  184. const int octave = midiNoteNumber / 12;
  185. const int note = midiNoteNumber % 12;
  186. x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_);
  187. w = roundToInt (widths [note] * keyWidth_);
  188. }
  189. void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const
  190. {
  191. getKeyPosition (midiNoteNumber, keyWidth, x, w);
  192. int rx, rw;
  193. getKeyPosition (rangeStart, keyWidth, rx, rw);
  194. x -= xOffset + rx;
  195. }
  196. Rectangle<int> MidiKeyboardComponent::getWhiteNotePos (int noteNum) const
  197. {
  198. int x, w;
  199. getKeyPos (noteNum, x, w);
  200. Rectangle<int> pos;
  201. switch (orientation)
  202. {
  203. case horizontalKeyboard: pos.setBounds (x, 0, w, getHeight()); break;
  204. case verticalKeyboardFacingLeft: pos.setBounds (0, x, getWidth(), w); break;
  205. case verticalKeyboardFacingRight: pos.setBounds (0, getHeight() - x - w, getWidth(), w); break;
  206. default: break;
  207. }
  208. return pos;
  209. }
  210. int MidiKeyboardComponent::getKeyStartPosition (const int midiNoteNumber) const
  211. {
  212. int x, y;
  213. getKeyPos (midiNoteNumber, x, y);
  214. return x;
  215. }
  216. const uint8 MidiKeyboardComponent::whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
  217. const uint8 MidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 };
  218. int MidiKeyboardComponent::xyToNote (const Point<int>& pos, float& mousePositionVelocity)
  219. {
  220. if (! reallyContains (pos, false))
  221. return -1;
  222. Point<int> p (pos);
  223. if (orientation != horizontalKeyboard)
  224. {
  225. p = Point<int> (p.y, p.x);
  226. if (orientation == verticalKeyboardFacingLeft)
  227. p = Point<int> (p.x, getWidth() - p.y);
  228. else
  229. p = Point<int> (getHeight() - p.x, p.y);
  230. }
  231. return remappedXYToNote (p + Point<int> (xOffset, 0), mousePositionVelocity);
  232. }
  233. int MidiKeyboardComponent::remappedXYToNote (const Point<int>& pos, float& mousePositionVelocity) const
  234. {
  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. const int note = octaveStart + blackNotes [i];
  242. if (note >= rangeStart && note <= rangeEnd)
  243. {
  244. int kx, kw;
  245. getKeyPos (note, kx, kw);
  246. kx += xOffset;
  247. if (pos.x >= kx && pos.x < kx + kw)
  248. {
  249. mousePositionVelocity = pos.y / (float) blackNoteLength;
  250. return note;
  251. }
  252. }
  253. }
  254. }
  255. }
  256. for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
  257. {
  258. for (int i = 0; i < 7; ++i)
  259. {
  260. const int note = octaveStart + whiteNotes [i];
  261. if (note >= rangeStart && note <= rangeEnd)
  262. {
  263. int kx, kw;
  264. getKeyPos (note, kx, kw);
  265. kx += xOffset;
  266. if (pos.x >= kx && pos.x < kx + kw)
  267. {
  268. const int whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth();
  269. mousePositionVelocity = pos.y / (float) whiteNoteLength;
  270. return note;
  271. }
  272. }
  273. }
  274. }
  275. mousePositionVelocity = 0;
  276. return -1;
  277. }
  278. //==============================================================================
  279. void MidiKeyboardComponent::repaintNote (const int noteNum)
  280. {
  281. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  282. repaint (getWhiteNotePos (noteNum));
  283. }
  284. void MidiKeyboardComponent::paint (Graphics& g)
  285. {
  286. g.fillAll (Colours::white.overlaidWith (findColour (whiteNoteColourId)));
  287. const Colour lineColour (findColour (keySeparatorLineColourId));
  288. const Colour textColour (findColour (textLabelColourId));
  289. int x, w, octave;
  290. for (octave = 0; octave < 128; octave += 12)
  291. {
  292. for (int white = 0; white < 7; ++white)
  293. {
  294. const int noteNum = octave + whiteNotes [white];
  295. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  296. {
  297. const Rectangle<int> pos (getWhiteNotePos (noteNum));
  298. drawWhiteNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
  299. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  300. mouseOverNotes.contains (noteNum), lineColour, textColour);
  301. }
  302. }
  303. }
  304. float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f;
  305. if (orientation == verticalKeyboardFacingLeft)
  306. {
  307. x1 = getWidth() - 1.0f;
  308. x2 = getWidth() - 5.0f;
  309. }
  310. else if (orientation == verticalKeyboardFacingRight)
  311. x2 = 5.0f;
  312. else
  313. y2 = 5.0f;
  314. g.setGradientFill (ColourGradient (Colours::black.withAlpha (0.3f), x1, y1,
  315. Colours::transparentBlack, x2, y2, false));
  316. getKeyPos (rangeEnd, x, w);
  317. x += w;
  318. switch (orientation)
  319. {
  320. case horizontalKeyboard: g.fillRect (0, 0, x, 5); break;
  321. case verticalKeyboardFacingLeft: g.fillRect (getWidth() - 5, 0, 5, x); break;
  322. case verticalKeyboardFacingRight: g.fillRect (0, 0, 5, x); break;
  323. default: break;
  324. }
  325. g.setColour (lineColour);
  326. switch (orientation)
  327. {
  328. case horizontalKeyboard: g.fillRect (0, getHeight() - 1, x, 1); break;
  329. case verticalKeyboardFacingLeft: g.fillRect (0, 0, 1, x); break;
  330. case verticalKeyboardFacingRight: g.fillRect (getWidth() - 1, 0, 1, x); break;
  331. default: break;
  332. }
  333. const Colour blackNoteColour (findColour (blackNoteColourId));
  334. for (octave = 0; octave < 128; octave += 12)
  335. {
  336. for (int black = 0; black < 5; ++black)
  337. {
  338. const int noteNum = octave + blackNotes [black];
  339. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  340. {
  341. getKeyPos (noteNum, x, w);
  342. Rectangle<int> pos;
  343. switch (orientation)
  344. {
  345. case horizontalKeyboard: pos.setBounds (x, 0, w, blackNoteLength); break;
  346. case verticalKeyboardFacingLeft: pos.setBounds (getWidth() - blackNoteLength, x, blackNoteLength, w); break;
  347. case verticalKeyboardFacingRight: pos.setBounds (0, getHeight() - x - w, blackNoteLength, w); break;
  348. default: break;
  349. }
  350. drawBlackNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
  351. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  352. mouseOverNotes.contains (noteNum), blackNoteColour);
  353. }
  354. }
  355. }
  356. }
  357. void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber,
  358. Graphics& g, int x, int y, int w, int h,
  359. bool isDown, bool isOver,
  360. const Colour& lineColour,
  361. const Colour& textColour)
  362. {
  363. Colour c (Colours::transparentWhite);
  364. if (isDown)
  365. c = findColour (keyDownOverlayColourId);
  366. if (isOver)
  367. c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  368. g.setColour (c);
  369. g.fillRect (x, y, w, h);
  370. const String text (getWhiteNoteText (midiNoteNumber));
  371. if (! text.isEmpty())
  372. {
  373. g.setColour (textColour);
  374. g.setFont (Font (jmin (12.0f, keyWidth * 0.9f)).withHorizontalScale (0.8f));
  375. Justification justification (Justification::centredBottom);
  376. if (orientation == verticalKeyboardFacingLeft)
  377. justification = Justification::centredLeft;
  378. else if (orientation == verticalKeyboardFacingRight)
  379. justification = Justification::centredRight;
  380. g.drawFittedText (text, x + 2, y + 2, w - 4, h - 4, justification, 1);
  381. }
  382. g.setColour (lineColour);
  383. switch (orientation)
  384. {
  385. case horizontalKeyboard: g.fillRect (x, y, 1, h); break;
  386. case verticalKeyboardFacingLeft: g.fillRect (x, y, w, 1); break;
  387. case verticalKeyboardFacingRight: g.fillRect (x, y + h - 1, w, 1); break;
  388. default: break;
  389. }
  390. if (midiNoteNumber == rangeEnd)
  391. {
  392. switch (orientation)
  393. {
  394. case horizontalKeyboard: g.fillRect (x + w, y, 1, h); break;
  395. case verticalKeyboardFacingLeft: g.fillRect (x, y + h, w, 1); break;
  396. case verticalKeyboardFacingRight: g.fillRect (x, y - 1, w, 1); break;
  397. default: break;
  398. }
  399. }
  400. }
  401. void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/,
  402. Graphics& g, int x, int y, int w, int h,
  403. bool isDown, bool isOver,
  404. const Colour& noteFillColour)
  405. {
  406. Colour c (noteFillColour);
  407. if (isDown)
  408. c = c.overlaidWith (findColour (keyDownOverlayColourId));
  409. if (isOver)
  410. c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  411. g.setColour (c);
  412. g.fillRect (x, y, w, h);
  413. if (isDown)
  414. {
  415. g.setColour (noteFillColour);
  416. g.drawRect (x, y, w, h);
  417. }
  418. else
  419. {
  420. g.setColour (c.brighter());
  421. const int xIndent = jmax (1, jmin (w, h) / 8);
  422. switch (orientation)
  423. {
  424. case horizontalKeyboard: g.fillRect (x + xIndent, y, w - xIndent * 2, 7 * h / 8); break;
  425. case verticalKeyboardFacingLeft: g.fillRect (x + w / 8, y + xIndent, w - w / 8, h - xIndent * 2); break;
  426. case verticalKeyboardFacingRight: g.fillRect (x, y + xIndent, 7 * w / 8, h - xIndent * 2); break;
  427. default: break;
  428. }
  429. }
  430. }
  431. void MidiKeyboardComponent::setOctaveForMiddleC (const int octaveNumForMiddleC_)
  432. {
  433. octaveNumForMiddleC = octaveNumForMiddleC_;
  434. repaint();
  435. }
  436. String MidiKeyboardComponent::getWhiteNoteText (const int midiNoteNumber)
  437. {
  438. if (keyWidth > 14.0f && midiNoteNumber % 12 == 0)
  439. return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
  440. return String::empty;
  441. }
  442. void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h,
  443. const bool isMouseOver_,
  444. const bool isButtonDown,
  445. const bool movesOctavesUp)
  446. {
  447. g.fillAll (findColour (upDownButtonBackgroundColourId));
  448. float angle;
  449. switch (orientation)
  450. {
  451. case horizontalKeyboard: angle = movesOctavesUp ? 0.0f : 0.5f; break;
  452. case verticalKeyboardFacingLeft: angle = movesOctavesUp ? 0.25f : 0.75f; break;
  453. case verticalKeyboardFacingRight: angle = movesOctavesUp ? 0.75f : 0.25f; break;
  454. default: jassertfalse; angle = 0; break;
  455. }
  456. Path path;
  457. path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
  458. path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * angle, 0.5f, 0.5f));
  459. g.setColour (findColour (upDownButtonArrowColourId)
  460. .withAlpha (isButtonDown ? 1.0f : (isMouseOver_ ? 0.6f : 0.4f)));
  461. g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f, w - 2.0f, h - 2.0f, true));
  462. }
  463. void MidiKeyboardComponent::resized()
  464. {
  465. int w = getWidth();
  466. int h = getHeight();
  467. if (w > 0 && h > 0)
  468. {
  469. if (orientation != horizontalKeyboard)
  470. std::swap (w, h);
  471. blackNoteLength = roundToInt (h * 0.7f);
  472. int kx2, kw2;
  473. getKeyPos (rangeEnd, kx2, kw2);
  474. kx2 += kw2;
  475. if ((int) firstKey != rangeStart)
  476. {
  477. int kx1, kw1;
  478. getKeyPos (rangeStart, kx1, kw1);
  479. if (kx2 - kx1 <= w)
  480. {
  481. firstKey = (float) rangeStart;
  482. sendChangeMessage();
  483. repaint();
  484. }
  485. }
  486. const bool showScrollButtons = canScroll && (((int) firstKey) > rangeStart || kx2 > w + xOffset * 2);
  487. scrollDown->setVisible (showScrollButtons);
  488. scrollUp->setVisible (showScrollButtons);
  489. xOffset = 0;
  490. if (showScrollButtons)
  491. {
  492. const int scrollButtonW = jmin (12, w / 2);
  493. if (orientation == horizontalKeyboard)
  494. {
  495. scrollDown->setBounds (0, 0, scrollButtonW, getHeight());
  496. scrollUp->setBounds (getWidth() - scrollButtonW, 0, scrollButtonW, getHeight());
  497. }
  498. else if (orientation == verticalKeyboardFacingLeft)
  499. {
  500. scrollDown->setBounds (0, 0, getWidth(), scrollButtonW);
  501. scrollUp->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
  502. }
  503. else
  504. {
  505. scrollDown->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
  506. scrollUp->setBounds (0, 0, getWidth(), scrollButtonW);
  507. }
  508. int endOfLastKey, kw;
  509. getKeyPos (rangeEnd, endOfLastKey, kw);
  510. endOfLastKey += kw;
  511. float mousePositionVelocity;
  512. const int spaceAvailable = w - scrollButtonW * 2;
  513. const int lastStartKey = remappedXYToNote (Point<int> (endOfLastKey - spaceAvailable, 0), mousePositionVelocity) + 1;
  514. if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey)
  515. {
  516. firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey);
  517. sendChangeMessage();
  518. }
  519. int newOffset = 0;
  520. getKeyPos (((int) firstKey), newOffset, kw);
  521. xOffset = newOffset - scrollButtonW;
  522. }
  523. else
  524. {
  525. firstKey = (float) rangeStart;
  526. }
  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*/)
  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.countNumberOfSetBits() > 0 || mouseDownNotes.size() > 0)
  543. {
  544. state.allNotesOff (midiChannel);
  545. keysPressed.clear();
  546. for (int i = mouseDownNotes.size(); --i >= 0;)
  547. {
  548. mouseDownNotes.set (i, -1);
  549. mouseOverNotes.set (i, -1);
  550. }
  551. }
  552. }
  553. void MidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown)
  554. {
  555. updateNoteUnderMouse (e.getPosition(), isDown, e.source.getIndex());
  556. }
  557. void MidiKeyboardComponent::updateNoteUnderMouse (const Point<int>& pos, bool isDown, int fingerNum)
  558. {
  559. float mousePositionVelocity = 0.0f;
  560. const int newNote = xyToNote (pos, mousePositionVelocity);
  561. const int oldNote = mouseOverNotes.getUnchecked (fingerNum);
  562. if (oldNote != newNote)
  563. {
  564. repaintNote (oldNote);
  565. repaintNote (newNote);
  566. mouseOverNotes.set (fingerNum, newNote);
  567. }
  568. int oldNoteDown = mouseDownNotes.getUnchecked (fingerNum);
  569. if (isDown)
  570. {
  571. if (newNote != oldNoteDown)
  572. {
  573. if (oldNoteDown >= 0)
  574. {
  575. mouseDownNotes.set (fingerNum, -1);
  576. if (! mouseDownNotes.contains (oldNoteDown))
  577. state.noteOff (midiChannel, oldNoteDown);
  578. }
  579. if (newNote >= 0)
  580. {
  581. if (! useMousePositionForVelocity)
  582. mousePositionVelocity = 1.0f;
  583. state.noteOn (midiChannel, newNote, mousePositionVelocity * velocity);
  584. mouseDownNotes.set (fingerNum, newNote);
  585. }
  586. }
  587. }
  588. else if (oldNoteDown >= 0)
  589. {
  590. mouseDownNotes.set (fingerNum, -1);
  591. if (! mouseDownNotes.contains (oldNoteDown))
  592. state.noteOff (midiChannel, oldNoteDown);
  593. }
  594. }
  595. void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
  596. {
  597. updateNoteUnderMouse (e, false);
  598. shouldCheckMousePos = false;
  599. }
  600. void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
  601. {
  602. float mousePositionVelocity;
  603. const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  604. if (newNote >= 0)
  605. mouseDraggedToKey (newNote, e);
  606. updateNoteUnderMouse (e, true);
  607. }
  608. bool MidiKeyboardComponent::mouseDownOnKey (int /*midiNoteNumber*/, const MouseEvent&)
  609. {
  610. return true;
  611. }
  612. void MidiKeyboardComponent::mouseDraggedToKey (int /*midiNoteNumber*/, const MouseEvent&)
  613. {
  614. }
  615. void MidiKeyboardComponent::mouseDown (const MouseEvent& e)
  616. {
  617. float mousePositionVelocity;
  618. const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  619. if (newNote >= 0 && mouseDownOnKey (newNote, e))
  620. {
  621. updateNoteUnderMouse (e, true);
  622. shouldCheckMousePos = true;
  623. }
  624. }
  625. void MidiKeyboardComponent::mouseUp (const MouseEvent& e)
  626. {
  627. updateNoteUnderMouse (e, false);
  628. shouldCheckMousePos = false;
  629. }
  630. void MidiKeyboardComponent::mouseEnter (const MouseEvent& e)
  631. {
  632. updateNoteUnderMouse (e, false);
  633. }
  634. void MidiKeyboardComponent::mouseExit (const MouseEvent& e)
  635. {
  636. updateNoteUnderMouse (e, false);
  637. }
  638. void MidiKeyboardComponent::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
  639. {
  640. const float amount = (orientation == horizontalKeyboard && wheel.deltaX != 0)
  641. ? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY
  642. : -wheel.deltaY);
  643. setLowestVisibleKeyFloat (firstKey - amount * keyWidth);
  644. }
  645. void MidiKeyboardComponent::timerCallback()
  646. {
  647. if (shouldCheckState)
  648. {
  649. shouldCheckState = false;
  650. for (int i = rangeStart; i <= rangeEnd; ++i)
  651. {
  652. if (keysCurrentlyDrawnDown[i] != state.isNoteOnForChannels (midiInChannelMask, i))
  653. {
  654. keysCurrentlyDrawnDown.setBit (i, state.isNoteOnForChannels (midiInChannelMask, i));
  655. repaintNote (i);
  656. }
  657. }
  658. }
  659. if (shouldCheckMousePos)
  660. {
  661. Desktop& desktop = Desktop::getInstance();
  662. for (int i = desktop.getNumMouseSources(); --i >= 0;)
  663. {
  664. MouseInputSource* source = desktop.getMouseSource (i);
  665. jassert (source != nullptr);
  666. updateNoteUnderMouse (getLocalPoint (nullptr, source->getScreenPosition()),
  667. source->isDragging(), source->getIndex());
  668. }
  669. }
  670. }
  671. //==============================================================================
  672. void MidiKeyboardComponent::clearKeyMappings()
  673. {
  674. resetAnyKeysInUse();
  675. keyPressNotes.clear();
  676. keyPresses.clear();
  677. }
  678. void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key,
  679. const int midiNoteOffsetFromC)
  680. {
  681. removeKeyPressForNote (midiNoteOffsetFromC);
  682. keyPressNotes.add (midiNoteOffsetFromC);
  683. keyPresses.add (key);
  684. }
  685. void MidiKeyboardComponent::removeKeyPressForNote (const int midiNoteOffsetFromC)
  686. {
  687. for (int i = keyPressNotes.size(); --i >= 0;)
  688. {
  689. if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
  690. {
  691. keyPressNotes.remove (i);
  692. keyPresses.remove (i);
  693. }
  694. }
  695. }
  696. void MidiKeyboardComponent::setKeyPressBaseOctave (const int newOctaveNumber)
  697. {
  698. jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
  699. keyMappingOctave = newOctaveNumber;
  700. }
  701. bool MidiKeyboardComponent::keyStateChanged (const bool /*isKeyDown*/)
  702. {
  703. bool keyPressUsed = false;
  704. for (int i = keyPresses.size(); --i >= 0;)
  705. {
  706. const int note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i);
  707. if (keyPresses.getReference(i).isCurrentlyDown())
  708. {
  709. if (! keysPressed [note])
  710. {
  711. keysPressed.setBit (note);
  712. state.noteOn (midiChannel, note, velocity);
  713. keyPressUsed = true;
  714. }
  715. }
  716. else
  717. {
  718. if (keysPressed [note])
  719. {
  720. keysPressed.clearBit (note);
  721. state.noteOff (midiChannel, note);
  722. keyPressUsed = true;
  723. }
  724. }
  725. }
  726. return keyPressUsed;
  727. }
  728. void MidiKeyboardComponent::focusLost (FocusChangeType)
  729. {
  730. resetAnyKeysInUse();
  731. }