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.

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