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.

872 lines
27KB

  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. noteUnderMouse (-1),
  60. mouseDownNote (-1),
  61. shouldCheckState (false),
  62. rangeStart (0),
  63. rangeEnd (127),
  64. firstKey (12 * 4.0f),
  65. canScroll (true),
  66. mouseDragging (false),
  67. useMousePositionForVelocity (true),
  68. shouldCheckMousePos (false),
  69. keyMappingOctave (6),
  70. octaveNumForMiddleC (3)
  71. {
  72. addChildComponent (scrollDown = new MidiKeyboardUpDownButton (*this, -1));
  73. addChildComponent (scrollUp = new MidiKeyboardUpDownButton (*this, 1));
  74. // initialise with a default set of querty key-mappings..
  75. const char* const keymap = "awsedftgyhujkolp;";
  76. for (int i = String (keymap).length(); --i >= 0;)
  77. setKeyPressForNote (KeyPress (keymap[i], 0, 0), i);
  78. setOpaque (true);
  79. setWantsKeyboardFocus (true);
  80. state.addListener (this);
  81. startTimer (1000 / 20);
  82. }
  83. MidiKeyboardComponent::~MidiKeyboardComponent()
  84. {
  85. state.removeListener (this);
  86. jassert (mouseDownNote < 0 && keysPressed.countNumberOfSetBits() == 0); // leaving stuck notes!
  87. }
  88. //==============================================================================
  89. void MidiKeyboardComponent::setKeyWidth (const float widthInPixels)
  90. {
  91. keyWidth = widthInPixels;
  92. resized();
  93. }
  94. void MidiKeyboardComponent::setOrientation (const Orientation newOrientation)
  95. {
  96. if (orientation != newOrientation)
  97. {
  98. orientation = newOrientation;
  99. resized();
  100. }
  101. }
  102. void MidiKeyboardComponent::setAvailableRange (const int lowestNote,
  103. const int highestNote)
  104. {
  105. jassert (lowestNote >= 0 && lowestNote <= 127);
  106. jassert (highestNote >= 0 && highestNote <= 127);
  107. jassert (lowestNote <= highestNote);
  108. if (rangeStart != lowestNote || rangeEnd != highestNote)
  109. {
  110. rangeStart = jlimit (0, 127, lowestNote);
  111. rangeEnd = jlimit (0, 127, highestNote);
  112. firstKey = jlimit ((float) rangeStart, (float) rangeEnd, firstKey);
  113. resized();
  114. }
  115. }
  116. void MidiKeyboardComponent::setLowestVisibleKey (int noteNumber)
  117. {
  118. setLowestVisibleKeyFloat ((float) noteNumber);
  119. }
  120. void MidiKeyboardComponent::setLowestVisibleKeyFloat (float noteNumber)
  121. {
  122. noteNumber = jlimit ((float) rangeStart, (float) rangeEnd, noteNumber);
  123. if (noteNumber != firstKey)
  124. {
  125. const bool hasMoved = (((int) firstKey) != (int) noteNumber);
  126. firstKey = noteNumber;
  127. if (hasMoved)
  128. {
  129. sendChangeMessage();
  130. resized();
  131. }
  132. }
  133. }
  134. void MidiKeyboardComponent::setScrollButtonsVisible (const bool canScroll_)
  135. {
  136. if (canScroll != canScroll_)
  137. {
  138. canScroll = canScroll_;
  139. resized();
  140. }
  141. }
  142. void MidiKeyboardComponent::colourChanged()
  143. {
  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 velocity_, const bool useMousePositionForVelocity_)
  162. {
  163. velocity = jlimit (0.0f, 1.0f, velocity_);
  164. useMousePositionForVelocity = useMousePositionForVelocity_;
  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. static const float widths[] = { 1.0f, blackNoteWidth,
  179. 1.0f, blackNoteWidth,
  180. 1.0f,
  181. 1.0f, blackNoteWidth,
  182. 1.0f, blackNoteWidth,
  183. 1.0f, blackNoteWidth,
  184. 1.0f };
  185. const int octave = midiNoteNumber / 12;
  186. const int note = midiNoteNumber % 12;
  187. x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_);
  188. w = roundToInt (widths [note] * keyWidth_);
  189. }
  190. void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const
  191. {
  192. getKeyPosition (midiNoteNumber, keyWidth, x, w);
  193. int rx, rw;
  194. getKeyPosition (rangeStart, keyWidth, rx, rw);
  195. x -= xOffset + rx;
  196. }
  197. Rectangle<int> MidiKeyboardComponent::getWhiteNotePos (int noteNum) const
  198. {
  199. int x, w;
  200. getKeyPos (noteNum, x, w);
  201. Rectangle<int> pos;
  202. switch (orientation)
  203. {
  204. case horizontalKeyboard: pos.setBounds (x, 0, w, getHeight()); break;
  205. case verticalKeyboardFacingLeft: pos.setBounds (0, x, getWidth(), w); break;
  206. case verticalKeyboardFacingRight: pos.setBounds (0, getHeight() - x - w, getWidth(), w); break;
  207. default: break;
  208. }
  209. return pos;
  210. }
  211. int MidiKeyboardComponent::getKeyStartPosition (const int midiNoteNumber) const
  212. {
  213. int x, y;
  214. getKeyPos (midiNoteNumber, x, y);
  215. return x;
  216. }
  217. const uint8 MidiKeyboardComponent::whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
  218. const uint8 MidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 };
  219. int MidiKeyboardComponent::xyToNote (const Point<int>& pos, float& mousePositionVelocity)
  220. {
  221. if (! reallyContains (pos, false))
  222. return -1;
  223. Point<int> p (pos);
  224. if (orientation != horizontalKeyboard)
  225. {
  226. p = Point<int> (p.y, p.x);
  227. if (orientation == verticalKeyboardFacingLeft)
  228. p = Point<int> (p.x, getWidth() - p.y);
  229. else
  230. p = Point<int> (getHeight() - p.x, p.y);
  231. }
  232. return remappedXYToNote (p + Point<int> (xOffset, 0), mousePositionVelocity);
  233. }
  234. int MidiKeyboardComponent::remappedXYToNote (const Point<int>& pos, float& mousePositionVelocity) const
  235. {
  236. if (pos.getY() < blackNoteLength)
  237. {
  238. for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
  239. {
  240. for (int i = 0; i < 5; ++i)
  241. {
  242. const int note = octaveStart + blackNotes [i];
  243. if (note >= rangeStart && note <= rangeEnd)
  244. {
  245. int kx, kw;
  246. getKeyPos (note, kx, kw);
  247. kx += xOffset;
  248. if (pos.x >= kx && pos.x < kx + kw)
  249. {
  250. mousePositionVelocity = pos.y / (float) 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. const int note = octaveStart + whiteNotes [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. const int whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth();
  270. mousePositionVelocity = pos.y / (float) whiteNoteLength;
  271. return note;
  272. }
  273. }
  274. }
  275. }
  276. mousePositionVelocity = 0;
  277. return -1;
  278. }
  279. //==============================================================================
  280. void MidiKeyboardComponent::repaintNote (const int noteNum)
  281. {
  282. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  283. repaint (getWhiteNotePos (noteNum));
  284. }
  285. void MidiKeyboardComponent::paint (Graphics& g)
  286. {
  287. g.fillAll (Colours::white.overlaidWith (findColour (whiteNoteColourId)));
  288. const Colour lineColour (findColour (keySeparatorLineColourId));
  289. const Colour textColour (findColour (textLabelColourId));
  290. int x, w, octave;
  291. for (octave = 0; octave < 128; octave += 12)
  292. {
  293. for (int white = 0; white < 7; ++white)
  294. {
  295. const int noteNum = octave + whiteNotes [white];
  296. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  297. {
  298. const Rectangle<int> pos (getWhiteNotePos (noteNum));
  299. drawWhiteNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
  300. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  301. noteUnderMouse == noteNum, lineColour, textColour);
  302. }
  303. }
  304. }
  305. float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f;
  306. if (orientation == verticalKeyboardFacingLeft)
  307. {
  308. x1 = getWidth() - 1.0f;
  309. x2 = getWidth() - 5.0f;
  310. }
  311. else if (orientation == verticalKeyboardFacingRight)
  312. x2 = 5.0f;
  313. else
  314. y2 = 5.0f;
  315. g.setGradientFill (ColourGradient (Colours::black.withAlpha (0.3f), x1, y1,
  316. Colours::transparentBlack, x2, y2, false));
  317. getKeyPos (rangeEnd, x, w);
  318. x += w;
  319. switch (orientation)
  320. {
  321. case horizontalKeyboard: g.fillRect (0, 0, x, 5); break;
  322. case verticalKeyboardFacingLeft: g.fillRect (getWidth() - 5, 0, 5, x); break;
  323. case verticalKeyboardFacingRight: g.fillRect (0, 0, 5, x); break;
  324. default: break;
  325. }
  326. g.setColour (lineColour);
  327. switch (orientation)
  328. {
  329. case horizontalKeyboard: g.fillRect (0, getHeight() - 1, x, 1); break;
  330. case verticalKeyboardFacingLeft: g.fillRect (0, 0, 1, x); break;
  331. case verticalKeyboardFacingRight: g.fillRect (getWidth() - 1, 0, 1, x); break;
  332. default: break;
  333. }
  334. const Colour blackNoteColour (findColour (blackNoteColourId));
  335. for (octave = 0; octave < 128; octave += 12)
  336. {
  337. for (int black = 0; black < 5; ++black)
  338. {
  339. const int noteNum = octave + blackNotes [black];
  340. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  341. {
  342. getKeyPos (noteNum, x, w);
  343. Rectangle<int> pos;
  344. switch (orientation)
  345. {
  346. case horizontalKeyboard: pos.setBounds (x, 0, w, blackNoteLength); break;
  347. case verticalKeyboardFacingLeft: pos.setBounds (getWidth() - blackNoteLength, x, blackNoteLength, w); break;
  348. case verticalKeyboardFacingRight: pos.setBounds (0, getHeight() - x - w, blackNoteLength, w); break;
  349. default: break;
  350. }
  351. drawBlackNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
  352. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  353. noteUnderMouse == noteNum, blackNoteColour);
  354. }
  355. }
  356. }
  357. }
  358. void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber,
  359. Graphics& g, int x, int y, int w, int h,
  360. bool isDown, bool isOver,
  361. const Colour& lineColour,
  362. const Colour& textColour)
  363. {
  364. Colour c (Colours::transparentWhite);
  365. if (isDown)
  366. c = findColour (keyDownOverlayColourId);
  367. if (isOver)
  368. c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  369. g.setColour (c);
  370. g.fillRect (x, y, w, h);
  371. const String text (getWhiteNoteText (midiNoteNumber));
  372. if (! text.isEmpty())
  373. {
  374. g.setColour (textColour);
  375. g.setFont (Font (jmin (12.0f, keyWidth * 0.9f)).withHorizontalScale (0.8f));
  376. Justification justification (Justification::centredBottom);
  377. if (orientation == verticalKeyboardFacingLeft)
  378. justification = Justification::centredLeft;
  379. else if (orientation == verticalKeyboardFacingRight)
  380. justification = Justification::centredRight;
  381. g.drawFittedText (text, x + 2, y + 2, w - 4, h - 4, justification, 1);
  382. }
  383. g.setColour (lineColour);
  384. switch (orientation)
  385. {
  386. case horizontalKeyboard: g.fillRect (x, y, 1, h); break;
  387. case verticalKeyboardFacingLeft: g.fillRect (x, y, w, 1); break;
  388. case verticalKeyboardFacingRight: g.fillRect (x, y + h - 1, w, 1); break;
  389. default: break;
  390. }
  391. if (midiNoteNumber == rangeEnd)
  392. {
  393. switch (orientation)
  394. {
  395. case horizontalKeyboard: g.fillRect (x + w, y, 1, h); break;
  396. case verticalKeyboardFacingLeft: g.fillRect (x, y + h, w, 1); break;
  397. case verticalKeyboardFacingRight: g.fillRect (x, y - 1, w, 1); break;
  398. default: break;
  399. }
  400. }
  401. }
  402. void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/,
  403. Graphics& g, int x, int y, int w, int h,
  404. bool isDown, bool isOver,
  405. const Colour& noteFillColour)
  406. {
  407. Colour c (noteFillColour);
  408. if (isDown)
  409. c = c.overlaidWith (findColour (keyDownOverlayColourId));
  410. if (isOver)
  411. c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  412. g.setColour (c);
  413. g.fillRect (x, y, w, h);
  414. if (isDown)
  415. {
  416. g.setColour (noteFillColour);
  417. g.drawRect (x, y, w, h);
  418. }
  419. else
  420. {
  421. g.setColour (c.brighter());
  422. const int xIndent = jmax (1, jmin (w, h) / 8);
  423. switch (orientation)
  424. {
  425. case horizontalKeyboard: g.fillRect (x + xIndent, y, w - xIndent * 2, 7 * h / 8); break;
  426. case verticalKeyboardFacingLeft: g.fillRect (x + w / 8, y + xIndent, w - w / 8, h - xIndent * 2); break;
  427. case verticalKeyboardFacingRight: g.fillRect (x, y + xIndent, 7 * w / 8, h - xIndent * 2); break;
  428. default: break;
  429. }
  430. }
  431. }
  432. void MidiKeyboardComponent::setOctaveForMiddleC (const int octaveNumForMiddleC_)
  433. {
  434. octaveNumForMiddleC = octaveNumForMiddleC_;
  435. repaint();
  436. }
  437. String MidiKeyboardComponent::getWhiteNoteText (const int midiNoteNumber)
  438. {
  439. if (keyWidth > 14.0f && midiNoteNumber % 12 == 0)
  440. return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
  441. return String::empty;
  442. }
  443. void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h,
  444. const bool isMouseOver_,
  445. const bool isButtonDown,
  446. const bool movesOctavesUp)
  447. {
  448. g.fillAll (findColour (upDownButtonBackgroundColourId));
  449. float angle;
  450. switch (orientation)
  451. {
  452. case horizontalKeyboard: angle = movesOctavesUp ? 0.0f : 0.5f; break;
  453. case verticalKeyboardFacingLeft: angle = movesOctavesUp ? 0.25f : 0.75f; break;
  454. case verticalKeyboardFacingRight: angle = movesOctavesUp ? 0.75f : 0.25f; break;
  455. default: jassertfalse; angle = 0; break;
  456. }
  457. Path path;
  458. path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
  459. path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * angle, 0.5f, 0.5f));
  460. g.setColour (findColour (upDownButtonArrowColourId)
  461. .withAlpha (isButtonDown ? 1.0f : (isMouseOver_ ? 0.6f : 0.4f)));
  462. g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f, w - 2.0f, h - 2.0f, true));
  463. }
  464. void MidiKeyboardComponent::resized()
  465. {
  466. int w = getWidth();
  467. int h = getHeight();
  468. if (w > 0 && h > 0)
  469. {
  470. if (orientation != horizontalKeyboard)
  471. std::swap (w, h);
  472. blackNoteLength = roundToInt (h * 0.7f);
  473. int kx2, kw2;
  474. getKeyPos (rangeEnd, kx2, kw2);
  475. kx2 += kw2;
  476. if ((int) firstKey != rangeStart)
  477. {
  478. int kx1, kw1;
  479. getKeyPos (rangeStart, kx1, kw1);
  480. if (kx2 - kx1 <= w)
  481. {
  482. firstKey = (float) rangeStart;
  483. sendChangeMessage();
  484. repaint();
  485. }
  486. }
  487. const bool showScrollButtons = canScroll && (((int) firstKey) > rangeStart || kx2 > w + xOffset * 2);
  488. scrollDown->setVisible (showScrollButtons);
  489. scrollUp->setVisible (showScrollButtons);
  490. xOffset = 0;
  491. if (showScrollButtons)
  492. {
  493. const int scrollButtonW = jmin (12, w / 2);
  494. if (orientation == horizontalKeyboard)
  495. {
  496. scrollDown->setBounds (0, 0, scrollButtonW, getHeight());
  497. scrollUp->setBounds (getWidth() - scrollButtonW, 0, scrollButtonW, getHeight());
  498. }
  499. else if (orientation == verticalKeyboardFacingLeft)
  500. {
  501. scrollDown->setBounds (0, 0, getWidth(), scrollButtonW);
  502. scrollUp->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
  503. }
  504. else
  505. {
  506. scrollDown->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
  507. scrollUp->setBounds (0, 0, getWidth(), scrollButtonW);
  508. }
  509. int endOfLastKey, kw;
  510. getKeyPos (rangeEnd, endOfLastKey, kw);
  511. endOfLastKey += kw;
  512. float mousePositionVelocity;
  513. const int spaceAvailable = w - scrollButtonW * 2;
  514. const int lastStartKey = remappedXYToNote (Point<int> (endOfLastKey - spaceAvailable, 0), mousePositionVelocity) + 1;
  515. if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey)
  516. {
  517. firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey);
  518. sendChangeMessage();
  519. }
  520. int newOffset = 0;
  521. getKeyPos (((int) firstKey), newOffset, kw);
  522. xOffset = newOffset - scrollButtonW;
  523. }
  524. else
  525. {
  526. firstKey = (float) rangeStart;
  527. }
  528. repaint();
  529. }
  530. }
  531. //==============================================================================
  532. void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
  533. {
  534. shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
  535. }
  536. void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/)
  537. {
  538. shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
  539. }
  540. //==============================================================================
  541. void MidiKeyboardComponent::resetAnyKeysInUse()
  542. {
  543. if (keysPressed.countNumberOfSetBits() > 0 || mouseDownNote > 0)
  544. {
  545. state.allNotesOff (midiChannel);
  546. keysPressed.clear();
  547. mouseDownNote = -1;
  548. }
  549. }
  550. void MidiKeyboardComponent::updateNoteUnderMouse (const Point<int>& pos)
  551. {
  552. float mousePositionVelocity = 0.0f;
  553. const int newNote = (mouseDragging || isMouseOver())
  554. ? xyToNote (pos, mousePositionVelocity) : -1;
  555. if (noteUnderMouse != newNote)
  556. {
  557. if (mouseDownNote >= 0)
  558. {
  559. state.noteOff (midiChannel, mouseDownNote);
  560. mouseDownNote = -1;
  561. }
  562. if (mouseDragging && newNote >= 0)
  563. {
  564. if (! useMousePositionForVelocity)
  565. mousePositionVelocity = 1.0f;
  566. state.noteOn (midiChannel, newNote, mousePositionVelocity * velocity);
  567. mouseDownNote = newNote;
  568. }
  569. repaintNote (noteUnderMouse);
  570. noteUnderMouse = newNote;
  571. repaintNote (noteUnderMouse);
  572. }
  573. else if (mouseDownNote >= 0 && ! mouseDragging)
  574. {
  575. state.noteOff (midiChannel, mouseDownNote);
  576. mouseDownNote = -1;
  577. }
  578. }
  579. void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
  580. {
  581. updateNoteUnderMouse (e.getPosition());
  582. shouldCheckMousePos = false;
  583. }
  584. void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
  585. {
  586. float mousePositionVelocity;
  587. const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  588. if (newNote >= 0)
  589. mouseDraggedToKey (newNote, e);
  590. updateNoteUnderMouse (e.getPosition());
  591. }
  592. bool MidiKeyboardComponent::mouseDownOnKey (int /*midiNoteNumber*/, const MouseEvent&)
  593. {
  594. return true;
  595. }
  596. void MidiKeyboardComponent::mouseDraggedToKey (int /*midiNoteNumber*/, const MouseEvent&)
  597. {
  598. }
  599. void MidiKeyboardComponent::mouseDown (const MouseEvent& e)
  600. {
  601. float mousePositionVelocity;
  602. const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  603. mouseDragging = false;
  604. if (newNote >= 0 && mouseDownOnKey (newNote, e))
  605. {
  606. repaintNote (noteUnderMouse);
  607. noteUnderMouse = -1;
  608. mouseDragging = true;
  609. updateNoteUnderMouse (e.getPosition());
  610. shouldCheckMousePos = true;
  611. }
  612. }
  613. void MidiKeyboardComponent::mouseUp (const MouseEvent& e)
  614. {
  615. mouseDragging = false;
  616. updateNoteUnderMouse (e.getPosition());
  617. shouldCheckMousePos = false;
  618. }
  619. void MidiKeyboardComponent::mouseEnter (const MouseEvent& e)
  620. {
  621. updateNoteUnderMouse (e.getPosition());
  622. }
  623. void MidiKeyboardComponent::mouseExit (const MouseEvent& e)
  624. {
  625. updateNoteUnderMouse (e.getPosition());
  626. }
  627. void MidiKeyboardComponent::mouseWheelMove (const MouseEvent&, float ix, float iy)
  628. {
  629. const float amount = (orientation == horizontalKeyboard && ix != 0)
  630. ? ix : (orientation == verticalKeyboardFacingLeft ? iy : -iy);
  631. setLowestVisibleKeyFloat (firstKey - amount * keyWidth);
  632. }
  633. void MidiKeyboardComponent::timerCallback()
  634. {
  635. if (shouldCheckState)
  636. {
  637. shouldCheckState = false;
  638. for (int i = rangeStart; i <= rangeEnd; ++i)
  639. {
  640. if (keysCurrentlyDrawnDown[i] != state.isNoteOnForChannels (midiInChannelMask, i))
  641. {
  642. keysCurrentlyDrawnDown.setBit (i, state.isNoteOnForChannels (midiInChannelMask, i));
  643. repaintNote (i);
  644. }
  645. }
  646. }
  647. if (shouldCheckMousePos)
  648. updateNoteUnderMouse (getMouseXYRelative());
  649. }
  650. //==============================================================================
  651. void MidiKeyboardComponent::clearKeyMappings()
  652. {
  653. resetAnyKeysInUse();
  654. keyPressNotes.clear();
  655. keyPresses.clear();
  656. }
  657. void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key,
  658. const int midiNoteOffsetFromC)
  659. {
  660. removeKeyPressForNote (midiNoteOffsetFromC);
  661. keyPressNotes.add (midiNoteOffsetFromC);
  662. keyPresses.add (key);
  663. }
  664. void MidiKeyboardComponent::removeKeyPressForNote (const int midiNoteOffsetFromC)
  665. {
  666. for (int i = keyPressNotes.size(); --i >= 0;)
  667. {
  668. if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
  669. {
  670. keyPressNotes.remove (i);
  671. keyPresses.remove (i);
  672. }
  673. }
  674. }
  675. void MidiKeyboardComponent::setKeyPressBaseOctave (const int newOctaveNumber)
  676. {
  677. jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
  678. keyMappingOctave = newOctaveNumber;
  679. }
  680. bool MidiKeyboardComponent::keyStateChanged (const bool /*isKeyDown*/)
  681. {
  682. bool keyPressUsed = false;
  683. for (int i = keyPresses.size(); --i >= 0;)
  684. {
  685. const int note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i);
  686. if (keyPresses.getReference(i).isCurrentlyDown())
  687. {
  688. if (! keysPressed [note])
  689. {
  690. keysPressed.setBit (note);
  691. state.noteOn (midiChannel, note, velocity);
  692. keyPressUsed = true;
  693. }
  694. }
  695. else
  696. {
  697. if (keysPressed [note])
  698. {
  699. keysPressed.clearBit (note);
  700. state.noteOff (midiChannel, note);
  701. keyPressUsed = true;
  702. }
  703. }
  704. }
  705. return keyPressUsed;
  706. }
  707. void MidiKeyboardComponent::focusLost (FocusChangeType)
  708. {
  709. resetAnyKeysInUse();
  710. }