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.

904 lines
28KB

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