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.

876 lines
27KB

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