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.

905 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& comp, const int d)
  22. : Button (String::empty),
  23. owner (comp),
  24. delta (d)
  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 newCanScroll)
  134. {
  135. if (canScroll != newCanScroll)
  136. {
  137. canScroll = newCanScroll;
  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 v, const bool useMousePosition)
  161. {
  162. velocity = jlimit (0.0f, 1.0f, v);
  163. useMousePositionForVelocity = useMousePosition;
  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 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. const int width = getWidth();
  306. const int height = getHeight();
  307. if (orientation == verticalKeyboardFacingLeft)
  308. {
  309. x1 = width - 1.0f;
  310. x2 = width - 5.0f;
  311. }
  312. else if (orientation == verticalKeyboardFacingRight)
  313. x2 = 5.0f;
  314. else
  315. y2 = 5.0f;
  316. g.setGradientFill (ColourGradient (Colours::black.withAlpha (0.3f), x1, y1,
  317. Colours::transparentBlack, x2, y2, false));
  318. int x, w;
  319. getKeyPos (rangeEnd, x, w);
  320. x += w;
  321. switch (orientation)
  322. {
  323. case horizontalKeyboard: g.fillRect (0, 0, x, 5); break;
  324. case verticalKeyboardFacingLeft: g.fillRect (width - 5, 0, 5, x); break;
  325. case verticalKeyboardFacingRight: g.fillRect (0, 0, 5, x); break;
  326. default: break;
  327. }
  328. g.setColour (lineColour);
  329. switch (orientation)
  330. {
  331. case horizontalKeyboard: g.fillRect (0, height - 1, x, 1); break;
  332. case verticalKeyboardFacingLeft: g.fillRect (0, 0, 1, x); break;
  333. case verticalKeyboardFacingRight: g.fillRect (width - 1, 0, 1, x); break;
  334. default: break;
  335. }
  336. const Colour blackNoteColour (findColour (blackNoteColourId));
  337. for (octave = 0; octave < 128; octave += 12)
  338. {
  339. for (int black = 0; black < 5; ++black)
  340. {
  341. const int noteNum = octave + blackNotes [black];
  342. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  343. {
  344. getKeyPos (noteNum, x, w);
  345. Rectangle<int> pos;
  346. switch (orientation)
  347. {
  348. case horizontalKeyboard: pos.setBounds (x, 0, w, blackNoteLength); break;
  349. case verticalKeyboardFacingLeft: pos.setBounds (width - blackNoteLength, x, blackNoteLength, w); break;
  350. case verticalKeyboardFacingRight: pos.setBounds (0, height - x - w, blackNoteLength, w); break;
  351. default: break;
  352. }
  353. drawBlackNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
  354. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  355. mouseOverNotes.contains (noteNum), blackNoteColour);
  356. }
  357. }
  358. }
  359. }
  360. void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber,
  361. Graphics& g, int x, int y, int w, int h,
  362. bool isDown, bool isOver,
  363. const Colour& lineColour,
  364. const Colour& textColour)
  365. {
  366. Colour c (Colours::transparentWhite);
  367. if (isDown) c = findColour (keyDownOverlayColourId);
  368. if (isOver) 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.isNotEmpty())
  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) c = c.overlaidWith (findColour (keyDownOverlayColourId));
  409. if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  410. g.setColour (c);
  411. g.fillRect (x, y, w, h);
  412. if (isDown)
  413. {
  414. g.setColour (noteFillColour);
  415. g.drawRect (x, y, w, h);
  416. }
  417. else
  418. {
  419. g.setColour (c.brighter());
  420. const int xIndent = jmax (1, jmin (w, h) / 8);
  421. switch (orientation)
  422. {
  423. case horizontalKeyboard: g.fillRect (x + xIndent, y, w - xIndent * 2, 7 * h / 8); break;
  424. case verticalKeyboardFacingLeft: g.fillRect (x + w / 8, y + xIndent, w - w / 8, h - xIndent * 2); break;
  425. case verticalKeyboardFacingRight: g.fillRect (x, y + xIndent, 7 * w / 8, h - xIndent * 2); break;
  426. default: break;
  427. }
  428. }
  429. }
  430. void MidiKeyboardComponent::setOctaveForMiddleC (const int octaveNum)
  431. {
  432. octaveNumForMiddleC = octaveNum;
  433. repaint();
  434. }
  435. String MidiKeyboardComponent::getWhiteNoteText (const int midiNoteNumber)
  436. {
  437. if (keyWidth > 14.0f && midiNoteNumber % 12 == 0)
  438. return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
  439. return String::empty;
  440. }
  441. void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h,
  442. const bool isMouseOver_,
  443. const bool isButtonDown,
  444. const bool movesOctavesUp)
  445. {
  446. g.fillAll (findColour (upDownButtonBackgroundColourId));
  447. float angle;
  448. switch (orientation)
  449. {
  450. case horizontalKeyboard: angle = movesOctavesUp ? 0.0f : 0.5f; break;
  451. case verticalKeyboardFacingLeft: angle = movesOctavesUp ? 0.25f : 0.75f; break;
  452. case verticalKeyboardFacingRight: angle = movesOctavesUp ? 0.75f : 0.25f; break;
  453. default: jassertfalse; angle = 0; break;
  454. }
  455. Path path;
  456. path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
  457. path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * angle, 0.5f, 0.5f));
  458. g.setColour (findColour (upDownButtonArrowColourId)
  459. .withAlpha (isButtonDown ? 1.0f : (isMouseOver_ ? 0.6f : 0.4f)));
  460. g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f, w - 2.0f, h - 2.0f, true));
  461. }
  462. void MidiKeyboardComponent::resized()
  463. {
  464. int w = getWidth();
  465. int h = getHeight();
  466. if (w > 0 && h > 0)
  467. {
  468. if (orientation != horizontalKeyboard)
  469. std::swap (w, h);
  470. blackNoteLength = roundToInt (h * 0.7f);
  471. int kx2, kw2;
  472. getKeyPos (rangeEnd, kx2, kw2);
  473. kx2 += kw2;
  474. if ((int) firstKey != rangeStart)
  475. {
  476. int kx1, kw1;
  477. getKeyPos (rangeStart, kx1, kw1);
  478. if (kx2 - kx1 <= w)
  479. {
  480. firstKey = (float) rangeStart;
  481. sendChangeMessage();
  482. repaint();
  483. }
  484. }
  485. const bool showScrollButtons = canScroll && (((int) firstKey) > rangeStart || kx2 > w + xOffset * 2);
  486. scrollDown->setVisible (showScrollButtons);
  487. scrollUp->setVisible (showScrollButtons);
  488. xOffset = 0;
  489. if (showScrollButtons)
  490. {
  491. const int scrollButtonW = jmin (12, w / 2);
  492. if (orientation == horizontalKeyboard)
  493. {
  494. scrollDown->setBounds (0, 0, scrollButtonW, getHeight());
  495. scrollUp->setBounds (getWidth() - scrollButtonW, 0, scrollButtonW, getHeight());
  496. }
  497. else if (orientation == verticalKeyboardFacingLeft)
  498. {
  499. scrollDown->setBounds (0, 0, getWidth(), scrollButtonW);
  500. scrollUp->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
  501. }
  502. else
  503. {
  504. scrollDown->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
  505. scrollUp->setBounds (0, 0, getWidth(), scrollButtonW);
  506. }
  507. int endOfLastKey, kw;
  508. getKeyPos (rangeEnd, endOfLastKey, kw);
  509. endOfLastKey += kw;
  510. float mousePositionVelocity;
  511. const int spaceAvailable = w - scrollButtonW * 2;
  512. const int lastStartKey = remappedXYToNote (Point<int> (endOfLastKey - spaceAvailable, 0), mousePositionVelocity) + 1;
  513. if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey)
  514. {
  515. firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey);
  516. sendChangeMessage();
  517. }
  518. int newOffset = 0;
  519. getKeyPos (((int) firstKey), newOffset, kw);
  520. xOffset = newOffset - scrollButtonW;
  521. }
  522. else
  523. {
  524. firstKey = (float) rangeStart;
  525. }
  526. repaint();
  527. }
  528. }
  529. //==============================================================================
  530. void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
  531. {
  532. shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
  533. }
  534. void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/)
  535. {
  536. shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
  537. }
  538. //==============================================================================
  539. void MidiKeyboardComponent::resetAnyKeysInUse()
  540. {
  541. if (! keysPressed.isZero())
  542. {
  543. for (int i = 128; --i >= 0;)
  544. if (keysPressed[i])
  545. state.noteOff (midiChannel, i);
  546. keysPressed.clear();
  547. }
  548. for (int i = mouseDownNotes.size(); --i >= 0;)
  549. {
  550. const int noteDown = mouseDownNotes.getUnchecked(i);
  551. if (noteDown >= 0)
  552. {
  553. state.noteOff (midiChannel, noteDown);
  554. mouseDownNotes.set (i, -1);
  555. }
  556. mouseOverNotes.set (i, -1);
  557. }
  558. }
  559. void MidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown)
  560. {
  561. updateNoteUnderMouse (e.getPosition(), isDown, e.source.getIndex());
  562. }
  563. void MidiKeyboardComponent::updateNoteUnderMouse (const Point<int>& pos, bool isDown, int fingerNum)
  564. {
  565. float mousePositionVelocity = 0.0f;
  566. const int newNote = xyToNote (pos, mousePositionVelocity);
  567. const int oldNote = mouseOverNotes.getUnchecked (fingerNum);
  568. if (oldNote != newNote)
  569. {
  570. repaintNote (oldNote);
  571. repaintNote (newNote);
  572. mouseOverNotes.set (fingerNum, newNote);
  573. }
  574. int oldNoteDown = mouseDownNotes.getUnchecked (fingerNum);
  575. if (isDown)
  576. {
  577. if (newNote != oldNoteDown)
  578. {
  579. if (oldNoteDown >= 0)
  580. {
  581. mouseDownNotes.set (fingerNum, -1);
  582. if (! mouseDownNotes.contains (oldNoteDown))
  583. state.noteOff (midiChannel, oldNoteDown);
  584. }
  585. if (newNote >= 0)
  586. {
  587. if (! useMousePositionForVelocity)
  588. mousePositionVelocity = 1.0f;
  589. state.noteOn (midiChannel, newNote, mousePositionVelocity * velocity);
  590. mouseDownNotes.set (fingerNum, newNote);
  591. }
  592. }
  593. }
  594. else if (oldNoteDown >= 0)
  595. {
  596. mouseDownNotes.set (fingerNum, -1);
  597. if (! mouseDownNotes.contains (oldNoteDown))
  598. state.noteOff (midiChannel, oldNoteDown);
  599. }
  600. }
  601. void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
  602. {
  603. updateNoteUnderMouse (e, false);
  604. shouldCheckMousePos = false;
  605. }
  606. void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
  607. {
  608. float mousePositionVelocity;
  609. const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  610. if (newNote >= 0)
  611. mouseDraggedToKey (newNote, e);
  612. updateNoteUnderMouse (e, true);
  613. }
  614. bool MidiKeyboardComponent::mouseDownOnKey (int /*midiNoteNumber*/, const MouseEvent&)
  615. {
  616. return true;
  617. }
  618. void MidiKeyboardComponent::mouseDraggedToKey (int /*midiNoteNumber*/, const MouseEvent&)
  619. {
  620. }
  621. void MidiKeyboardComponent::mouseDown (const MouseEvent& e)
  622. {
  623. float mousePositionVelocity;
  624. const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  625. if (newNote >= 0 && mouseDownOnKey (newNote, e))
  626. {
  627. updateNoteUnderMouse (e, true);
  628. shouldCheckMousePos = true;
  629. }
  630. }
  631. void MidiKeyboardComponent::mouseUp (const MouseEvent& e)
  632. {
  633. updateNoteUnderMouse (e, false);
  634. shouldCheckMousePos = false;
  635. }
  636. void MidiKeyboardComponent::mouseEnter (const MouseEvent& e)
  637. {
  638. updateNoteUnderMouse (e, false);
  639. }
  640. void MidiKeyboardComponent::mouseExit (const MouseEvent& e)
  641. {
  642. updateNoteUnderMouse (e, false);
  643. }
  644. void MidiKeyboardComponent::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
  645. {
  646. const float amount = (orientation == horizontalKeyboard && wheel.deltaX != 0)
  647. ? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY
  648. : -wheel.deltaY);
  649. setLowestVisibleKeyFloat (firstKey - amount * keyWidth);
  650. }
  651. void MidiKeyboardComponent::timerCallback()
  652. {
  653. if (shouldCheckState)
  654. {
  655. shouldCheckState = false;
  656. for (int i = rangeStart; i <= rangeEnd; ++i)
  657. {
  658. if (keysCurrentlyDrawnDown[i] != state.isNoteOnForChannels (midiInChannelMask, i))
  659. {
  660. keysCurrentlyDrawnDown.setBit (i, state.isNoteOnForChannels (midiInChannelMask, i));
  661. repaintNote (i);
  662. }
  663. }
  664. }
  665. if (shouldCheckMousePos)
  666. {
  667. Desktop& desktop = Desktop::getInstance();
  668. for (int i = desktop.getNumMouseSources(); --i >= 0;)
  669. {
  670. MouseInputSource* source = desktop.getMouseSource (i);
  671. jassert (source != nullptr);
  672. updateNoteUnderMouse (getLocalPoint (nullptr, source->getScreenPosition()),
  673. source->isDragging(), source->getIndex());
  674. }
  675. }
  676. }
  677. //==============================================================================
  678. void MidiKeyboardComponent::clearKeyMappings()
  679. {
  680. resetAnyKeysInUse();
  681. keyPressNotes.clear();
  682. keyPresses.clear();
  683. }
  684. void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key,
  685. const int midiNoteOffsetFromC)
  686. {
  687. removeKeyPressForNote (midiNoteOffsetFromC);
  688. keyPressNotes.add (midiNoteOffsetFromC);
  689. keyPresses.add (key);
  690. }
  691. void MidiKeyboardComponent::removeKeyPressForNote (const int midiNoteOffsetFromC)
  692. {
  693. for (int i = keyPressNotes.size(); --i >= 0;)
  694. {
  695. if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
  696. {
  697. keyPressNotes.remove (i);
  698. keyPresses.remove (i);
  699. }
  700. }
  701. }
  702. void MidiKeyboardComponent::setKeyPressBaseOctave (const int newOctaveNumber)
  703. {
  704. jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
  705. keyMappingOctave = newOctaveNumber;
  706. }
  707. bool MidiKeyboardComponent::keyStateChanged (const bool /*isKeyDown*/)
  708. {
  709. bool keyPressUsed = false;
  710. for (int i = keyPresses.size(); --i >= 0;)
  711. {
  712. const int note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i);
  713. if (keyPresses.getReference(i).isCurrentlyDown())
  714. {
  715. if (! keysPressed [note])
  716. {
  717. keysPressed.setBit (note);
  718. state.noteOn (midiChannel, note, velocity);
  719. keyPressUsed = true;
  720. }
  721. }
  722. else
  723. {
  724. if (keysPressed [note])
  725. {
  726. keysPressed.clearBit (note);
  727. state.noteOff (midiChannel, note);
  728. keyPressUsed = true;
  729. }
  730. }
  731. }
  732. return keyPressUsed;
  733. }
  734. void MidiKeyboardComponent::focusLost (FocusChangeType)
  735. {
  736. resetAnyKeysInUse();
  737. }