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.

868 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. rangeStart (0),
  64. rangeEnd (127),
  65. firstKey (12 * 4),
  66. canScroll (true),
  67. mouseDragging (false),
  68. useMousePositionForVelocity (true),
  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. }
  82. MidiKeyboardComponent::~MidiKeyboardComponent()
  83. {
  84. state.removeListener (this);
  85. jassert (mouseDownNote < 0 && keysPressed.countNumberOfSetBits() == 0); // leaving stuck notes!
  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 (rangeStart, rangeEnd, firstKey);
  112. resized();
  113. }
  114. }
  115. void MidiKeyboardComponent::setLowestVisibleKey (int noteNumber)
  116. {
  117. noteNumber = jlimit (rangeStart, rangeEnd, noteNumber);
  118. if (noteNumber != firstKey)
  119. {
  120. firstKey = noteNumber;
  121. sendChangeMessage();
  122. resized();
  123. }
  124. }
  125. void MidiKeyboardComponent::setScrollButtonsVisible (const bool canScroll_)
  126. {
  127. if (canScroll != canScroll_)
  128. {
  129. canScroll = canScroll_;
  130. resized();
  131. }
  132. }
  133. void MidiKeyboardComponent::colourChanged()
  134. {
  135. repaint();
  136. }
  137. //==============================================================================
  138. void MidiKeyboardComponent::setMidiChannel (const int midiChannelNumber)
  139. {
  140. jassert (midiChannelNumber > 0 && midiChannelNumber <= 16);
  141. if (midiChannel != midiChannelNumber)
  142. {
  143. resetAnyKeysInUse();
  144. midiChannel = jlimit (1, 16, midiChannelNumber);
  145. }
  146. }
  147. void MidiKeyboardComponent::setMidiChannelsToDisplay (const int midiChannelMask)
  148. {
  149. midiInChannelMask = midiChannelMask;
  150. triggerAsyncUpdate();
  151. }
  152. void MidiKeyboardComponent::setVelocity (const float velocity_, const bool useMousePositionForVelocity_)
  153. {
  154. velocity = jlimit (0.0f, 1.0f, velocity_);
  155. useMousePositionForVelocity = useMousePositionForVelocity_;
  156. }
  157. //==============================================================================
  158. void MidiKeyboardComponent::getKeyPosition (int midiNoteNumber, const float keyWidth_, int& x, int& w) const
  159. {
  160. jassert (midiNoteNumber >= 0 && midiNoteNumber < 128);
  161. static const float blackNoteWidth = 0.7f;
  162. static const float notePos[] = { 0.0f, 1 - blackNoteWidth * 0.6f,
  163. 1.0f, 2 - blackNoteWidth * 0.4f,
  164. 2.0f, 3.0f, 4 - blackNoteWidth * 0.7f,
  165. 4.0f, 5 - blackNoteWidth * 0.5f,
  166. 5.0f, 6 - blackNoteWidth * 0.3f,
  167. 6.0f };
  168. static const float widths[] = { 1.0f, blackNoteWidth,
  169. 1.0f, blackNoteWidth,
  170. 1.0f, 1.0f, blackNoteWidth,
  171. 1.0f, blackNoteWidth,
  172. 1.0f, blackNoteWidth,
  173. 1.0f };
  174. const int octave = midiNoteNumber / 12;
  175. const int note = midiNoteNumber % 12;
  176. x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_);
  177. w = roundToInt (widths [note] * keyWidth_);
  178. }
  179. void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const
  180. {
  181. getKeyPosition (midiNoteNumber, keyWidth, x, w);
  182. int rx, rw;
  183. getKeyPosition (rangeStart, keyWidth, rx, rw);
  184. x -= xOffset + rx;
  185. }
  186. int MidiKeyboardComponent::getKeyStartPosition (const int midiNoteNumber) const
  187. {
  188. int x, y;
  189. getKeyPos (midiNoteNumber, x, y);
  190. return x;
  191. }
  192. const uint8 MidiKeyboardComponent::whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
  193. const uint8 MidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 };
  194. int MidiKeyboardComponent::xyToNote (const Point<int>& pos, float& mousePositionVelocity)
  195. {
  196. if (! reallyContains (pos, false))
  197. return -1;
  198. Point<int> p (pos);
  199. if (orientation != horizontalKeyboard)
  200. {
  201. p = Point<int> (p.getY(), p.getX());
  202. if (orientation == verticalKeyboardFacingLeft)
  203. p = Point<int> (p.getX(), getWidth() - p.getY());
  204. else
  205. p = Point<int> (getHeight() - p.getX(), p.getY());
  206. }
  207. return remappedXYToNote (p + Point<int> (xOffset, 0), mousePositionVelocity);
  208. }
  209. int MidiKeyboardComponent::remappedXYToNote (const Point<int>& pos, float& mousePositionVelocity) const
  210. {
  211. if (pos.getY() < blackNoteLength)
  212. {
  213. for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
  214. {
  215. for (int i = 0; i < 5; ++i)
  216. {
  217. const int note = octaveStart + blackNotes [i];
  218. if (note >= rangeStart && note <= rangeEnd)
  219. {
  220. int kx, kw;
  221. getKeyPos (note, kx, kw);
  222. kx += xOffset;
  223. if (pos.getX() >= kx && pos.getX() < kx + kw)
  224. {
  225. mousePositionVelocity = pos.getY() / (float) blackNoteLength;
  226. return note;
  227. }
  228. }
  229. }
  230. }
  231. }
  232. for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
  233. {
  234. for (int i = 0; i < 7; ++i)
  235. {
  236. const int note = octaveStart + whiteNotes [i];
  237. if (note >= rangeStart && note <= rangeEnd)
  238. {
  239. int kx, kw;
  240. getKeyPos (note, kx, kw);
  241. kx += xOffset;
  242. if (pos.getX() >= kx && pos.getX() < kx + kw)
  243. {
  244. const int whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth();
  245. mousePositionVelocity = pos.getY() / (float) whiteNoteLength;
  246. return note;
  247. }
  248. }
  249. }
  250. }
  251. mousePositionVelocity = 0;
  252. return -1;
  253. }
  254. //==============================================================================
  255. void MidiKeyboardComponent::repaintNote (const int noteNum)
  256. {
  257. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  258. {
  259. int x, w;
  260. getKeyPos (noteNum, x, w);
  261. if (orientation == horizontalKeyboard)
  262. repaint (x, 0, w, getHeight());
  263. else if (orientation == verticalKeyboardFacingLeft)
  264. repaint (0, x, getWidth(), w);
  265. else if (orientation == verticalKeyboardFacingRight)
  266. repaint (0, getHeight() - x - w, getWidth(), w);
  267. }
  268. }
  269. void MidiKeyboardComponent::paint (Graphics& g)
  270. {
  271. g.fillAll (Colours::white.overlaidWith (findColour (whiteNoteColourId)));
  272. const Colour lineColour (findColour (keySeparatorLineColourId));
  273. const Colour textColour (findColour (textLabelColourId));
  274. int x, w, octave;
  275. for (octave = 0; octave < 128; octave += 12)
  276. {
  277. for (int white = 0; white < 7; ++white)
  278. {
  279. const int noteNum = octave + whiteNotes [white];
  280. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  281. {
  282. getKeyPos (noteNum, x, w);
  283. if (orientation == horizontalKeyboard)
  284. drawWhiteNote (noteNum, g, x, 0, w, getHeight(),
  285. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  286. noteUnderMouse == noteNum,
  287. lineColour, textColour);
  288. else if (orientation == verticalKeyboardFacingLeft)
  289. drawWhiteNote (noteNum, g, 0, x, getWidth(), w,
  290. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  291. noteUnderMouse == noteNum,
  292. lineColour, textColour);
  293. else if (orientation == verticalKeyboardFacingRight)
  294. drawWhiteNote (noteNum, g, 0, getHeight() - x - w, getWidth(), w,
  295. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  296. noteUnderMouse == noteNum,
  297. lineColour, textColour);
  298. }
  299. }
  300. }
  301. float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f;
  302. if (orientation == verticalKeyboardFacingLeft)
  303. {
  304. x1 = getWidth() - 1.0f;
  305. x2 = getWidth() - 5.0f;
  306. }
  307. else if (orientation == verticalKeyboardFacingRight)
  308. x2 = 5.0f;
  309. else
  310. y2 = 5.0f;
  311. g.setGradientFill (ColourGradient (Colours::black.withAlpha (0.3f), x1, y1,
  312. Colours::transparentBlack, x2, y2, false));
  313. getKeyPos (rangeEnd, x, w);
  314. x += w;
  315. if (orientation == verticalKeyboardFacingLeft)
  316. g.fillRect (getWidth() - 5, 0, 5, x);
  317. else if (orientation == verticalKeyboardFacingRight)
  318. g.fillRect (0, 0, 5, x);
  319. else
  320. g.fillRect (0, 0, x, 5);
  321. g.setColour (lineColour);
  322. if (orientation == verticalKeyboardFacingLeft)
  323. g.fillRect (0, 0, 1, x);
  324. else if (orientation == verticalKeyboardFacingRight)
  325. g.fillRect (getWidth() - 1, 0, 1, x);
  326. else
  327. g.fillRect (0, getHeight() - 1, x, 1);
  328. const Colour blackNoteColour (findColour (blackNoteColourId));
  329. for (octave = 0; octave < 128; octave += 12)
  330. {
  331. for (int black = 0; black < 5; ++black)
  332. {
  333. const int noteNum = octave + blackNotes [black];
  334. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  335. {
  336. getKeyPos (noteNum, x, w);
  337. if (orientation == horizontalKeyboard)
  338. drawBlackNote (noteNum, g, x, 0, w, blackNoteLength,
  339. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  340. noteUnderMouse == noteNum,
  341. blackNoteColour);
  342. else if (orientation == verticalKeyboardFacingLeft)
  343. drawBlackNote (noteNum, g, getWidth() - blackNoteLength, x, blackNoteLength, w,
  344. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  345. noteUnderMouse == noteNum,
  346. blackNoteColour);
  347. else if (orientation == verticalKeyboardFacingRight)
  348. drawBlackNote (noteNum, g, 0, getHeight() - x - w, blackNoteLength, w,
  349. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  350. noteUnderMouse == noteNum,
  351. blackNoteColour);
  352. }
  353. }
  354. }
  355. }
  356. void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber,
  357. Graphics& g, int x, int y, int w, int h,
  358. bool isDown, bool isOver,
  359. const Colour& lineColour,
  360. const Colour& textColour)
  361. {
  362. Colour c (Colours::transparentWhite);
  363. if (isDown)
  364. c = findColour (keyDownOverlayColourId);
  365. if (isOver)
  366. c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  367. g.setColour (c);
  368. g.fillRect (x, y, w, h);
  369. const String text (getWhiteNoteText (midiNoteNumber));
  370. if (! text.isEmpty())
  371. {
  372. g.setColour (textColour);
  373. Font f (jmin (12.0f, keyWidth * 0.9f));
  374. f.setHorizontalScale (0.8f);
  375. g.setFont (f);
  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. if (orientation == horizontalKeyboard)
  385. g.fillRect (x, y, 1, h);
  386. else if (orientation == verticalKeyboardFacingLeft)
  387. g.fillRect (x, y, w, 1);
  388. else if (orientation == verticalKeyboardFacingRight)
  389. g.fillRect (x, y + h - 1, w, 1);
  390. if (midiNoteNumber == rangeEnd)
  391. {
  392. if (orientation == horizontalKeyboard)
  393. g.fillRect (x + w, y, 1, h);
  394. else if (orientation == verticalKeyboardFacingLeft)
  395. g.fillRect (x, y + h, w, 1);
  396. else if (orientation == verticalKeyboardFacingRight)
  397. g.fillRect (x, y - 1, w, 1);
  398. }
  399. }
  400. void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/,
  401. Graphics& g, int x, int y, int w, int h,
  402. bool isDown, bool isOver,
  403. const Colour& noteFillColour)
  404. {
  405. Colour c (noteFillColour);
  406. if (isDown)
  407. c = c.overlaidWith (findColour (keyDownOverlayColourId));
  408. if (isOver)
  409. 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. const int xIndent = jmax (1, jmin (w, h) / 8);
  420. g.setColour (c.brighter());
  421. if (orientation == horizontalKeyboard)
  422. g.fillRect (x + xIndent, y, w - xIndent * 2, 7 * h / 8);
  423. else if (orientation == verticalKeyboardFacingLeft)
  424. g.fillRect (x + w / 8, y + xIndent, w - w / 8, h - xIndent * 2);
  425. else if (orientation == verticalKeyboardFacingRight)
  426. g.fillRect (x, y + xIndent, 7 * w / 8, h - xIndent * 2);
  427. }
  428. }
  429. void MidiKeyboardComponent::setOctaveForMiddleC (const int octaveNumForMiddleC_)
  430. {
  431. octaveNumForMiddleC = octaveNumForMiddleC_;
  432. repaint();
  433. }
  434. String MidiKeyboardComponent::getWhiteNoteText (const int midiNoteNumber)
  435. {
  436. if (keyWidth > 14.0f && midiNoteNumber % 12 == 0)
  437. return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
  438. return String::empty;
  439. }
  440. void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h,
  441. const bool isMouseOver_,
  442. const bool isButtonDown,
  443. const bool movesOctavesUp)
  444. {
  445. g.fillAll (findColour (upDownButtonBackgroundColourId));
  446. float angle;
  447. if (orientation == MidiKeyboardComponent::horizontalKeyboard)
  448. angle = movesOctavesUp ? 0.0f : 0.5f;
  449. else if (orientation == MidiKeyboardComponent::verticalKeyboardFacingLeft)
  450. angle = movesOctavesUp ? 0.25f : 0.75f;
  451. else
  452. angle = movesOctavesUp ? 0.75f : 0.25f;
  453. Path path;
  454. path.lineTo (0.0f, 1.0f);
  455. path.lineTo (1.0f, 0.5f);
  456. path.closeSubPath();
  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,
  461. w - 2.0f,
  462. h - 2.0f,
  463. true));
  464. }
  465. void MidiKeyboardComponent::resized()
  466. {
  467. int w = getWidth();
  468. int h = getHeight();
  469. if (w > 0 && h > 0)
  470. {
  471. if (orientation != horizontalKeyboard)
  472. std::swap (w, h);
  473. blackNoteLength = roundToInt (h * 0.7f);
  474. int kx2, kw2;
  475. getKeyPos (rangeEnd, kx2, kw2);
  476. kx2 += kw2;
  477. if (firstKey != rangeStart)
  478. {
  479. int kx1, kw1;
  480. getKeyPos (rangeStart, kx1, kw1);
  481. if (kx2 - kx1 <= w)
  482. {
  483. firstKey = rangeStart;
  484. sendChangeMessage();
  485. repaint();
  486. }
  487. }
  488. const bool showScrollButtons = canScroll && (firstKey > rangeStart || kx2 > w + xOffset * 2);
  489. scrollDown->setVisible (showScrollButtons);
  490. scrollUp->setVisible (showScrollButtons);
  491. xOffset = 0;
  492. if (showScrollButtons)
  493. {
  494. const int scrollButtonW = jmin (12, w / 2);
  495. if (orientation == horizontalKeyboard)
  496. {
  497. scrollDown->setBounds (0, 0, scrollButtonW, getHeight());
  498. scrollUp->setBounds (getWidth() - scrollButtonW, 0, scrollButtonW, getHeight());
  499. }
  500. else if (orientation == verticalKeyboardFacingLeft)
  501. {
  502. scrollDown->setBounds (0, 0, getWidth(), scrollButtonW);
  503. scrollUp->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
  504. }
  505. else if (orientation == verticalKeyboardFacingRight)
  506. {
  507. scrollDown->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
  508. scrollUp->setBounds (0, 0, getWidth(), scrollButtonW);
  509. }
  510. int endOfLastKey, kw;
  511. getKeyPos (rangeEnd, endOfLastKey, kw);
  512. endOfLastKey += kw;
  513. float mousePositionVelocity;
  514. const int spaceAvailable = w - scrollButtonW * 2;
  515. const int lastStartKey = remappedXYToNote (Point<int> (endOfLastKey - spaceAvailable, 0), mousePositionVelocity) + 1;
  516. if (lastStartKey >= 0 && firstKey > lastStartKey)
  517. {
  518. firstKey = jlimit (rangeStart, rangeEnd, lastStartKey);
  519. sendChangeMessage();
  520. }
  521. int newOffset = 0;
  522. getKeyPos (firstKey, newOffset, kw);
  523. xOffset = newOffset - scrollButtonW;
  524. }
  525. else
  526. {
  527. firstKey = rangeStart;
  528. }
  529. timerCallback();
  530. repaint();
  531. }
  532. }
  533. //==============================================================================
  534. void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
  535. {
  536. triggerAsyncUpdate();
  537. }
  538. void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/)
  539. {
  540. triggerAsyncUpdate();
  541. }
  542. void MidiKeyboardComponent::handleAsyncUpdate()
  543. {
  544. for (int i = rangeStart; i <= rangeEnd; ++i)
  545. {
  546. if (keysCurrentlyDrawnDown[i] != state.isNoteOnForChannels (midiInChannelMask, i))
  547. {
  548. keysCurrentlyDrawnDown.setBit (i, state.isNoteOnForChannels (midiInChannelMask, i));
  549. repaintNote (i);
  550. }
  551. }
  552. }
  553. //==============================================================================
  554. void MidiKeyboardComponent::resetAnyKeysInUse()
  555. {
  556. if (keysPressed.countNumberOfSetBits() > 0 || mouseDownNote > 0)
  557. {
  558. state.allNotesOff (midiChannel);
  559. keysPressed.clear();
  560. mouseDownNote = -1;
  561. }
  562. }
  563. void MidiKeyboardComponent::updateNoteUnderMouse (const Point<int>& pos)
  564. {
  565. float mousePositionVelocity = 0.0f;
  566. const int newNote = (mouseDragging || isMouseOver())
  567. ? xyToNote (pos, mousePositionVelocity) : -1;
  568. if (noteUnderMouse != newNote)
  569. {
  570. if (mouseDownNote >= 0)
  571. {
  572. state.noteOff (midiChannel, mouseDownNote);
  573. mouseDownNote = -1;
  574. }
  575. if (mouseDragging && newNote >= 0)
  576. {
  577. if (! useMousePositionForVelocity)
  578. mousePositionVelocity = 1.0f;
  579. state.noteOn (midiChannel, newNote, mousePositionVelocity * velocity);
  580. mouseDownNote = newNote;
  581. }
  582. repaintNote (noteUnderMouse);
  583. noteUnderMouse = newNote;
  584. repaintNote (noteUnderMouse);
  585. }
  586. else if (mouseDownNote >= 0 && ! mouseDragging)
  587. {
  588. state.noteOff (midiChannel, mouseDownNote);
  589. mouseDownNote = -1;
  590. }
  591. }
  592. void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
  593. {
  594. updateNoteUnderMouse (e.getPosition());
  595. stopTimer();
  596. }
  597. void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
  598. {
  599. float mousePositionVelocity;
  600. const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  601. if (newNote >= 0)
  602. mouseDraggedToKey (newNote, e);
  603. updateNoteUnderMouse (e.getPosition());
  604. }
  605. bool MidiKeyboardComponent::mouseDownOnKey (int /*midiNoteNumber*/, const MouseEvent&)
  606. {
  607. return true;
  608. }
  609. void MidiKeyboardComponent::mouseDraggedToKey (int /*midiNoteNumber*/, const MouseEvent&)
  610. {
  611. }
  612. void MidiKeyboardComponent::mouseDown (const MouseEvent& e)
  613. {
  614. float mousePositionVelocity;
  615. const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  616. mouseDragging = false;
  617. if (newNote >= 0 && mouseDownOnKey (newNote, e))
  618. {
  619. repaintNote (noteUnderMouse);
  620. noteUnderMouse = -1;
  621. mouseDragging = true;
  622. updateNoteUnderMouse (e.getPosition());
  623. startTimer (500);
  624. }
  625. }
  626. void MidiKeyboardComponent::mouseUp (const MouseEvent& e)
  627. {
  628. mouseDragging = false;
  629. updateNoteUnderMouse (e.getPosition());
  630. stopTimer();
  631. }
  632. void MidiKeyboardComponent::mouseEnter (const MouseEvent& e)
  633. {
  634. updateNoteUnderMouse (e.getPosition());
  635. }
  636. void MidiKeyboardComponent::mouseExit (const MouseEvent& e)
  637. {
  638. updateNoteUnderMouse (e.getPosition());
  639. }
  640. void MidiKeyboardComponent::mouseWheelMove (const MouseEvent&, float ix, float iy)
  641. {
  642. setLowestVisibleKey (getLowestVisibleKey() + roundToInt ((ix != 0 ? ix : iy) * 5.0f));
  643. }
  644. void MidiKeyboardComponent::timerCallback()
  645. {
  646. updateNoteUnderMouse (getMouseXYRelative());
  647. }
  648. //==============================================================================
  649. void MidiKeyboardComponent::clearKeyMappings()
  650. {
  651. resetAnyKeysInUse();
  652. keyPressNotes.clear();
  653. keyPresses.clear();
  654. }
  655. void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key,
  656. const int midiNoteOffsetFromC)
  657. {
  658. removeKeyPressForNote (midiNoteOffsetFromC);
  659. keyPressNotes.add (midiNoteOffsetFromC);
  660. keyPresses.add (key);
  661. }
  662. void MidiKeyboardComponent::removeKeyPressForNote (const int midiNoteOffsetFromC)
  663. {
  664. for (int i = keyPressNotes.size(); --i >= 0;)
  665. {
  666. if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
  667. {
  668. keyPressNotes.remove (i);
  669. keyPresses.remove (i);
  670. }
  671. }
  672. }
  673. void MidiKeyboardComponent::setKeyPressBaseOctave (const int newOctaveNumber)
  674. {
  675. jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
  676. keyMappingOctave = newOctaveNumber;
  677. }
  678. bool MidiKeyboardComponent::keyStateChanged (const bool /*isKeyDown*/)
  679. {
  680. bool keyPressUsed = false;
  681. for (int i = keyPresses.size(); --i >= 0;)
  682. {
  683. const int note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i);
  684. if (keyPresses.getReference(i).isCurrentlyDown())
  685. {
  686. if (! keysPressed [note])
  687. {
  688. keysPressed.setBit (note);
  689. state.noteOn (midiChannel, note, velocity);
  690. keyPressUsed = true;
  691. }
  692. }
  693. else
  694. {
  695. if (keysPressed [note])
  696. {
  697. keysPressed.clearBit (note);
  698. state.noteOff (midiChannel, note);
  699. keyPressUsed = true;
  700. }
  701. }
  702. }
  703. return keyPressUsed;
  704. }
  705. void MidiKeyboardComponent::focusLost (FocusChangeType)
  706. {
  707. resetAnyKeysInUse();
  708. }
  709. END_JUCE_NAMESPACE