Audio plugin host https://kx.studio/carla
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.

juce_MidiKeyboardComponent.cpp 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  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() override
  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) override
  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& s,
  49. const Orientation o)
  50. : state (s),
  51. xOffset (0),
  52. blackNoteLength (1),
  53. keyWidth (16.0f),
  54. orientation (o),
  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. mouseOverNotes.insertMultiple (0, -1, 32);
  75. mouseDownNotes.insertMultiple (0, -1, 32);
  76. setOpaque (true);
  77. setWantsKeyboardFocus (true);
  78. state.addListener (this);
  79. startTimer (1000 / 20);
  80. }
  81. MidiKeyboardComponent::~MidiKeyboardComponent()
  82. {
  83. state.removeListener (this);
  84. }
  85. //==============================================================================
  86. void MidiKeyboardComponent::setKeyWidth (const float widthInPixels)
  87. {
  88. keyWidth = widthInPixels;
  89. resized();
  90. }
  91. void MidiKeyboardComponent::setOrientation (const Orientation newOrientation)
  92. {
  93. if (orientation != newOrientation)
  94. {
  95. orientation = newOrientation;
  96. resized();
  97. }
  98. }
  99. void MidiKeyboardComponent::setAvailableRange (const int lowestNote,
  100. const int highestNote)
  101. {
  102. jassert (lowestNote >= 0 && lowestNote <= 127);
  103. jassert (highestNote >= 0 && highestNote <= 127);
  104. jassert (lowestNote <= highestNote);
  105. if (rangeStart != lowestNote || rangeEnd != highestNote)
  106. {
  107. rangeStart = jlimit (0, 127, lowestNote);
  108. rangeEnd = jlimit (0, 127, highestNote);
  109. firstKey = jlimit ((float) rangeStart, (float) rangeEnd, firstKey);
  110. resized();
  111. }
  112. }
  113. void MidiKeyboardComponent::setLowestVisibleKey (int noteNumber)
  114. {
  115. setLowestVisibleKeyFloat ((float) noteNumber);
  116. }
  117. void MidiKeyboardComponent::setLowestVisibleKeyFloat (float noteNumber)
  118. {
  119. noteNumber = jlimit ((float) rangeStart, (float) rangeEnd, noteNumber);
  120. if (noteNumber != firstKey)
  121. {
  122. const bool hasMoved = (((int) firstKey) != (int) noteNumber);
  123. firstKey = noteNumber;
  124. if (hasMoved)
  125. sendChangeMessage();
  126. resized();
  127. }
  128. }
  129. void MidiKeyboardComponent::setScrollButtonsVisible (const bool newCanScroll)
  130. {
  131. if (canScroll != newCanScroll)
  132. {
  133. canScroll = newCanScroll;
  134. resized();
  135. }
  136. }
  137. void MidiKeyboardComponent::colourChanged()
  138. {
  139. repaint();
  140. }
  141. //==============================================================================
  142. void MidiKeyboardComponent::setMidiChannel (const int midiChannelNumber)
  143. {
  144. jassert (midiChannelNumber > 0 && midiChannelNumber <= 16);
  145. if (midiChannel != midiChannelNumber)
  146. {
  147. resetAnyKeysInUse();
  148. midiChannel = jlimit (1, 16, midiChannelNumber);
  149. }
  150. }
  151. void MidiKeyboardComponent::setMidiChannelsToDisplay (const int midiChannelMask)
  152. {
  153. midiInChannelMask = midiChannelMask;
  154. shouldCheckState = true;
  155. }
  156. void MidiKeyboardComponent::setVelocity (const float v, const bool useMousePosition)
  157. {
  158. velocity = jlimit (0.0f, 1.0f, v);
  159. useMousePositionForVelocity = useMousePosition;
  160. }
  161. //==============================================================================
  162. void MidiKeyboardComponent::getKeyPosition (int midiNoteNumber, const float keyWidth_, int& x, int& w) const
  163. {
  164. jassert (midiNoteNumber >= 0 && midiNoteNumber < 128);
  165. static const float blackNoteWidth = 0.7f;
  166. static const float notePos[] = { 0.0f, 1 - blackNoteWidth * 0.6f,
  167. 1.0f, 2 - blackNoteWidth * 0.4f,
  168. 2.0f,
  169. 3.0f, 4 - blackNoteWidth * 0.7f,
  170. 4.0f, 5 - blackNoteWidth * 0.5f,
  171. 5.0f, 6 - blackNoteWidth * 0.3f,
  172. 6.0f };
  173. static const float widths[] = { 1.0f, blackNoteWidth,
  174. 1.0f, blackNoteWidth,
  175. 1.0f,
  176. 1.0f, blackNoteWidth,
  177. 1.0f, blackNoteWidth,
  178. 1.0f, blackNoteWidth,
  179. 1.0f };
  180. const int octave = midiNoteNumber / 12;
  181. const int note = midiNoteNumber % 12;
  182. x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_);
  183. w = roundToInt (widths [note] * keyWidth_);
  184. }
  185. void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const
  186. {
  187. getKeyPosition (midiNoteNumber, keyWidth, x, w);
  188. int rx, rw;
  189. getKeyPosition (rangeStart, keyWidth, rx, rw);
  190. x -= xOffset + rx;
  191. }
  192. Rectangle<int> MidiKeyboardComponent::getWhiteNotePos (int noteNum) const
  193. {
  194. int x, w;
  195. getKeyPos (noteNum, x, w);
  196. Rectangle<int> pos;
  197. switch (orientation)
  198. {
  199. case horizontalKeyboard: pos.setBounds (x, 0, w, getHeight()); break;
  200. case verticalKeyboardFacingLeft: pos.setBounds (0, x, getWidth(), w); break;
  201. case verticalKeyboardFacingRight: pos.setBounds (0, getHeight() - x - w, getWidth(), w); break;
  202. default: break;
  203. }
  204. return pos;
  205. }
  206. int MidiKeyboardComponent::getKeyStartPosition (const int midiNoteNumber) const
  207. {
  208. int x, w;
  209. getKeyPos (midiNoteNumber, x, w);
  210. return x;
  211. }
  212. int MidiKeyboardComponent::getNoteAtPosition (Point<int> p)
  213. {
  214. float v;
  215. return xyToNote (p, v);
  216. }
  217. const uint8 MidiKeyboardComponent::whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
  218. const uint8 MidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 };
  219. int MidiKeyboardComponent::xyToNote (Point<int> pos, float& mousePositionVelocity)
  220. {
  221. if (! reallyContains (pos, false))
  222. return -1;
  223. Point<int> p (pos);
  224. if (orientation != horizontalKeyboard)
  225. {
  226. p = Point<int> (p.y, p.x);
  227. if (orientation == verticalKeyboardFacingLeft)
  228. p = Point<int> (p.x, getWidth() - p.y);
  229. else
  230. p = Point<int> (getHeight() - p.x, p.y);
  231. }
  232. return remappedXYToNote (p + Point<int> (xOffset, 0), mousePositionVelocity);
  233. }
  234. int MidiKeyboardComponent::remappedXYToNote (Point<int> pos, float& mousePositionVelocity) const
  235. {
  236. if (pos.getY() < blackNoteLength)
  237. {
  238. for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
  239. {
  240. for (int i = 0; i < 5; ++i)
  241. {
  242. const int note = octaveStart + blackNotes [i];
  243. if (note >= rangeStart && note <= rangeEnd)
  244. {
  245. int kx, kw;
  246. getKeyPos (note, kx, kw);
  247. kx += xOffset;
  248. if (pos.x >= kx && pos.x < kx + kw)
  249. {
  250. mousePositionVelocity = pos.y / (float) blackNoteLength;
  251. return note;
  252. }
  253. }
  254. }
  255. }
  256. }
  257. for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
  258. {
  259. for (int i = 0; i < 7; ++i)
  260. {
  261. const int note = octaveStart + whiteNotes [i];
  262. if (note >= rangeStart && note <= rangeEnd)
  263. {
  264. int kx, kw;
  265. getKeyPos (note, kx, kw);
  266. kx += xOffset;
  267. if (pos.x >= kx && pos.x < kx + kw)
  268. {
  269. const int whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth();
  270. mousePositionVelocity = pos.y / (float) whiteNoteLength;
  271. return note;
  272. }
  273. }
  274. }
  275. }
  276. mousePositionVelocity = 0;
  277. return -1;
  278. }
  279. //==============================================================================
  280. void MidiKeyboardComponent::repaintNote (const int noteNum)
  281. {
  282. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  283. repaint (getWhiteNotePos (noteNum));
  284. }
  285. void MidiKeyboardComponent::paint (Graphics& g)
  286. {
  287. g.fillAll (Colours::white.overlaidWith (findColour (whiteNoteColourId)));
  288. const Colour lineColour (findColour (keySeparatorLineColourId));
  289. const Colour textColour (findColour (textLabelColourId));
  290. int octave;
  291. for (octave = 0; octave < 128; octave += 12)
  292. {
  293. for (int white = 0; white < 7; ++white)
  294. {
  295. const int noteNum = octave + whiteNotes [white];
  296. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  297. {
  298. const Rectangle<int> pos (getWhiteNotePos (noteNum));
  299. drawWhiteNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
  300. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  301. mouseOverNotes.contains (noteNum), lineColour, textColour);
  302. }
  303. }
  304. }
  305. float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f;
  306. const int width = getWidth();
  307. const int height = getHeight();
  308. if (orientation == verticalKeyboardFacingLeft)
  309. {
  310. x1 = width - 1.0f;
  311. x2 = width - 5.0f;
  312. }
  313. else if (orientation == verticalKeyboardFacingRight)
  314. x2 = 5.0f;
  315. else
  316. y2 = 5.0f;
  317. int x, w;
  318. getKeyPos (rangeEnd, x, w);
  319. x += w;
  320. const Colour shadowCol (findColour (shadowColourId));
  321. g.setGradientFill (ColourGradient (shadowCol, x1, y1, shadowCol.withAlpha (0.0f), x2, y2, false));
  322. switch (orientation)
  323. {
  324. case horizontalKeyboard: g.fillRect (0, 0, x, 5); break;
  325. case verticalKeyboardFacingLeft: g.fillRect (width - 5, 0, 5, x); break;
  326. case verticalKeyboardFacingRight: g.fillRect (0, 0, 5, x); break;
  327. default: break;
  328. }
  329. g.setColour (lineColour);
  330. switch (orientation)
  331. {
  332. case horizontalKeyboard: g.fillRect (0, height - 1, x, 1); break;
  333. case verticalKeyboardFacingLeft: g.fillRect (0, 0, 1, x); break;
  334. case verticalKeyboardFacingRight: g.fillRect (width - 1, 0, 1, x); break;
  335. default: break;
  336. }
  337. const Colour blackNoteColour (findColour (blackNoteColourId));
  338. for (octave = 0; octave < 128; octave += 12)
  339. {
  340. for (int black = 0; black < 5; ++black)
  341. {
  342. const int noteNum = octave + blackNotes [black];
  343. if (noteNum >= rangeStart && noteNum <= rangeEnd)
  344. {
  345. getKeyPos (noteNum, x, w);
  346. Rectangle<int> pos;
  347. switch (orientation)
  348. {
  349. case horizontalKeyboard: pos.setBounds (x, 0, w, blackNoteLength); break;
  350. case verticalKeyboardFacingLeft: pos.setBounds (width - blackNoteLength, x, blackNoteLength, w); break;
  351. case verticalKeyboardFacingRight: pos.setBounds (0, height - x - w, blackNoteLength, w); break;
  352. default: break;
  353. }
  354. drawBlackNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(),
  355. state.isNoteOnForChannels (midiInChannelMask, noteNum),
  356. mouseOverNotes.contains (noteNum), 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) c = findColour (keyDownOverlayColourId);
  369. if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  370. g.setColour (c);
  371. g.fillRect (x, y, w, h);
  372. const String text (getWhiteNoteText (midiNoteNumber));
  373. if (text.isNotEmpty())
  374. {
  375. g.setColour (textColour);
  376. g.setFont (Font (jmin (12.0f, keyWidth * 0.9f)).withHorizontalScale (0.8f));
  377. switch (orientation)
  378. {
  379. case horizontalKeyboard: g.drawFittedText (text, x + 1, y, w - 1, h - 2, Justification::centredBottom, 1); break;
  380. case verticalKeyboardFacingLeft: g.drawFittedText (text, x + 2, y + 2, w - 4, h - 4, Justification::centredLeft, 1); break;
  381. case verticalKeyboardFacingRight: g.drawFittedText (text, x + 2, y + 2, w - 4, h - 4, Justification::centredRight, 1); break;
  382. default: break;
  383. }
  384. }
  385. g.setColour (lineColour);
  386. switch (orientation)
  387. {
  388. case horizontalKeyboard: g.fillRect (x, y, 1, h); break;
  389. case verticalKeyboardFacingLeft: g.fillRect (x, y, w, 1); break;
  390. case verticalKeyboardFacingRight: g.fillRect (x, y + h - 1, w, 1); break;
  391. default: break;
  392. }
  393. if (midiNoteNumber == rangeEnd)
  394. {
  395. switch (orientation)
  396. {
  397. case horizontalKeyboard: g.fillRect (x + w, y, 1, h); break;
  398. case verticalKeyboardFacingLeft: g.fillRect (x, y + h, w, 1); break;
  399. case verticalKeyboardFacingRight: g.fillRect (x, y - 1, w, 1); break;
  400. default: break;
  401. }
  402. }
  403. }
  404. void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/,
  405. Graphics& g, int x, int y, int w, int h,
  406. bool isDown, bool isOver,
  407. const Colour& noteFillColour)
  408. {
  409. Colour c (noteFillColour);
  410. if (isDown) c = c.overlaidWith (findColour (keyDownOverlayColourId));
  411. if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
  412. g.setColour (c);
  413. g.fillRect (x, y, w, h);
  414. if (isDown)
  415. {
  416. g.setColour (noteFillColour);
  417. g.drawRect (x, y, w, h);
  418. }
  419. else
  420. {
  421. g.setColour (c.brighter());
  422. const int xIndent = jmax (1, jmin (w, h) / 8);
  423. switch (orientation)
  424. {
  425. case horizontalKeyboard: g.fillRect (x + xIndent, y, w - xIndent * 2, 7 * h / 8); break;
  426. case verticalKeyboardFacingLeft: g.fillRect (x + w / 8, y + xIndent, w - w / 8, h - xIndent * 2); break;
  427. case verticalKeyboardFacingRight: g.fillRect (x, y + xIndent, 7 * w / 8, h - xIndent * 2); break;
  428. default: break;
  429. }
  430. }
  431. }
  432. void MidiKeyboardComponent::setOctaveForMiddleC (const int octaveNum)
  433. {
  434. octaveNumForMiddleC = octaveNum;
  435. repaint();
  436. }
  437. String MidiKeyboardComponent::getWhiteNoteText (const int midiNoteNumber)
  438. {
  439. if (keyWidth > 11.0f && midiNoteNumber % 12 == 0)
  440. return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
  441. return String::empty;
  442. }
  443. void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h,
  444. const bool mouseOver,
  445. const bool buttonDown,
  446. const bool movesOctavesUp)
  447. {
  448. g.fillAll (findColour (upDownButtonBackgroundColourId));
  449. float angle;
  450. switch (orientation)
  451. {
  452. case horizontalKeyboard: angle = movesOctavesUp ? 0.0f : 0.5f; break;
  453. case verticalKeyboardFacingLeft: angle = movesOctavesUp ? 0.25f : 0.75f; break;
  454. case verticalKeyboardFacingRight: angle = movesOctavesUp ? 0.75f : 0.25f; break;
  455. default: jassertfalse; angle = 0; break;
  456. }
  457. Path path;
  458. path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
  459. path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * angle, 0.5f, 0.5f));
  460. g.setColour (findColour (upDownButtonArrowColourId)
  461. .withAlpha (buttonDown ? 1.0f : (mouseOver ? 0.6f : 0.4f)));
  462. g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f, w - 2.0f, h - 2.0f, true));
  463. }
  464. void MidiKeyboardComponent::resized()
  465. {
  466. int w = getWidth();
  467. int h = getHeight();
  468. if (w > 0 && h > 0)
  469. {
  470. if (orientation != horizontalKeyboard)
  471. std::swap (w, h);
  472. blackNoteLength = roundToInt (h * 0.7f);
  473. int kx2, kw2;
  474. getKeyPos (rangeEnd, kx2, kw2);
  475. kx2 += kw2;
  476. if ((int) firstKey != rangeStart)
  477. {
  478. int kx1, kw1;
  479. getKeyPos (rangeStart, kx1, kw1);
  480. if (kx2 - kx1 <= w)
  481. {
  482. firstKey = (float) rangeStart;
  483. sendChangeMessage();
  484. repaint();
  485. }
  486. }
  487. scrollDown->setVisible (canScroll && firstKey > (float) rangeStart);
  488. xOffset = 0;
  489. if (canScroll)
  490. {
  491. const int scrollButtonW = jmin (12, w / 2);
  492. Rectangle<int> r (getLocalBounds());
  493. if (orientation == horizontalKeyboard)
  494. {
  495. scrollDown->setBounds (r.removeFromLeft (scrollButtonW));
  496. scrollUp ->setBounds (r.removeFromRight (scrollButtonW));
  497. }
  498. else if (orientation == verticalKeyboardFacingLeft)
  499. {
  500. scrollDown->setBounds (r.removeFromTop (scrollButtonW));
  501. scrollUp ->setBounds (r.removeFromBottom (scrollButtonW));
  502. }
  503. else
  504. {
  505. scrollDown->setBounds (r.removeFromBottom (scrollButtonW));
  506. scrollUp ->setBounds (r.removeFromTop (scrollButtonW));
  507. }
  508. int endOfLastKey, kw;
  509. getKeyPos (rangeEnd, endOfLastKey, kw);
  510. endOfLastKey += kw;
  511. float mousePositionVelocity;
  512. const int spaceAvailable = w;
  513. const int lastStartKey = remappedXYToNote (Point<int> (endOfLastKey - spaceAvailable, 0), mousePositionVelocity) + 1;
  514. if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey)
  515. {
  516. firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey);
  517. sendChangeMessage();
  518. }
  519. int newOffset = 0;
  520. getKeyPos ((int) firstKey, newOffset, kw);
  521. xOffset = newOffset;
  522. }
  523. else
  524. {
  525. firstKey = (float) rangeStart;
  526. }
  527. getKeyPos (rangeEnd, kx2, kw2);
  528. scrollUp->setVisible (canScroll && kx2 > w);
  529. repaint();
  530. }
  531. }
  532. //==============================================================================
  533. void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
  534. {
  535. shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
  536. }
  537. void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/)
  538. {
  539. shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here)
  540. }
  541. //==============================================================================
  542. void MidiKeyboardComponent::resetAnyKeysInUse()
  543. {
  544. if (! keysPressed.isZero())
  545. {
  546. for (int i = 128; --i >= 0;)
  547. if (keysPressed[i])
  548. state.noteOff (midiChannel, i);
  549. keysPressed.clear();
  550. }
  551. for (int i = mouseDownNotes.size(); --i >= 0;)
  552. {
  553. const int noteDown = mouseDownNotes.getUnchecked(i);
  554. if (noteDown >= 0)
  555. {
  556. state.noteOff (midiChannel, noteDown);
  557. mouseDownNotes.set (i, -1);
  558. }
  559. mouseOverNotes.set (i, -1);
  560. }
  561. }
  562. void MidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown)
  563. {
  564. updateNoteUnderMouse (e.getPosition(), isDown, e.source.getIndex());
  565. }
  566. void MidiKeyboardComponent::updateNoteUnderMouse (Point<int> pos, bool isDown, int fingerNum)
  567. {
  568. float mousePositionVelocity = 0.0f;
  569. const int newNote = xyToNote (pos, mousePositionVelocity);
  570. const int oldNote = mouseOverNotes.getUnchecked (fingerNum);
  571. if (oldNote != newNote)
  572. {
  573. repaintNote (oldNote);
  574. repaintNote (newNote);
  575. mouseOverNotes.set (fingerNum, newNote);
  576. }
  577. int oldNoteDown = mouseDownNotes.getUnchecked (fingerNum);
  578. if (isDown)
  579. {
  580. if (newNote != oldNoteDown)
  581. {
  582. if (oldNoteDown >= 0)
  583. {
  584. mouseDownNotes.set (fingerNum, -1);
  585. if (! mouseDownNotes.contains (oldNoteDown))
  586. state.noteOff (midiChannel, oldNoteDown);
  587. }
  588. if (newNote >= 0)
  589. {
  590. if (! useMousePositionForVelocity)
  591. mousePositionVelocity = 1.0f;
  592. state.noteOn (midiChannel, newNote, mousePositionVelocity * velocity);
  593. mouseDownNotes.set (fingerNum, newNote);
  594. }
  595. }
  596. }
  597. else if (oldNoteDown >= 0)
  598. {
  599. mouseDownNotes.set (fingerNum, -1);
  600. if (! mouseDownNotes.contains (oldNoteDown))
  601. state.noteOff (midiChannel, oldNoteDown);
  602. }
  603. }
  604. void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
  605. {
  606. updateNoteUnderMouse (e, false);
  607. shouldCheckMousePos = false;
  608. }
  609. void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
  610. {
  611. float mousePositionVelocity;
  612. const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
  613. if (newNote >= 0)
  614. mouseDraggedToKey (newNote, e);
  615. updateNoteUnderMouse (e, true);
  616. }
  617. bool MidiKeyboardComponent::mouseDownOnKey (int, const MouseEvent&) { return true; }
  618. void MidiKeyboardComponent::mouseDraggedToKey (int, const MouseEvent&) {}
  619. void MidiKeyboardComponent::mouseUpOnKey (int, const MouseEvent&) {}
  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. float mousePositionVelocity;
  635. const int note = xyToNote (e.getPosition(), mousePositionVelocity);
  636. if (note >= 0)
  637. mouseUpOnKey (note, e);
  638. }
  639. void MidiKeyboardComponent::mouseEnter (const MouseEvent& e)
  640. {
  641. updateNoteUnderMouse (e, false);
  642. }
  643. void MidiKeyboardComponent::mouseExit (const MouseEvent& e)
  644. {
  645. updateNoteUnderMouse (e, false);
  646. }
  647. void MidiKeyboardComponent::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel)
  648. {
  649. const float amount = (orientation == horizontalKeyboard && wheel.deltaX != 0)
  650. ? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY
  651. : -wheel.deltaY);
  652. setLowestVisibleKeyFloat (firstKey - amount * keyWidth);
  653. }
  654. void MidiKeyboardComponent::timerCallback()
  655. {
  656. if (shouldCheckState)
  657. {
  658. shouldCheckState = false;
  659. for (int i = rangeStart; i <= rangeEnd; ++i)
  660. {
  661. if (keysCurrentlyDrawnDown[i] != state.isNoteOnForChannels (midiInChannelMask, i))
  662. {
  663. keysCurrentlyDrawnDown.setBit (i, state.isNoteOnForChannels (midiInChannelMask, i));
  664. repaintNote (i);
  665. }
  666. }
  667. }
  668. if (shouldCheckMousePos)
  669. {
  670. const Array<MouseInputSource>& mouseSources = Desktop::getInstance().getMouseSources();
  671. for (MouseInputSource* mi = mouseSources.begin(), * const e = mouseSources.end(); mi != e; ++mi)
  672. updateNoteUnderMouse (getLocalPoint (nullptr, mi->getScreenPosition()), mi->isDragging(), mi->getIndex());
  673. }
  674. }
  675. //==============================================================================
  676. void MidiKeyboardComponent::clearKeyMappings()
  677. {
  678. resetAnyKeysInUse();
  679. keyPressNotes.clear();
  680. keyPresses.clear();
  681. }
  682. void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key,
  683. const int midiNoteOffsetFromC)
  684. {
  685. removeKeyPressForNote (midiNoteOffsetFromC);
  686. keyPressNotes.add (midiNoteOffsetFromC);
  687. keyPresses.add (key);
  688. }
  689. void MidiKeyboardComponent::removeKeyPressForNote (const int midiNoteOffsetFromC)
  690. {
  691. for (int i = keyPressNotes.size(); --i >= 0;)
  692. {
  693. if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
  694. {
  695. keyPressNotes.remove (i);
  696. keyPresses.remove (i);
  697. }
  698. }
  699. }
  700. void MidiKeyboardComponent::setKeyPressBaseOctave (const int newOctaveNumber)
  701. {
  702. jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
  703. keyMappingOctave = newOctaveNumber;
  704. }
  705. bool MidiKeyboardComponent::keyStateChanged (const bool /*isKeyDown*/)
  706. {
  707. bool keyPressUsed = false;
  708. for (int i = keyPresses.size(); --i >= 0;)
  709. {
  710. const int note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i);
  711. if (keyPresses.getReference(i).isCurrentlyDown())
  712. {
  713. if (! keysPressed [note])
  714. {
  715. keysPressed.setBit (note);
  716. state.noteOn (midiChannel, note, velocity);
  717. keyPressUsed = true;
  718. }
  719. }
  720. else
  721. {
  722. if (keysPressed [note])
  723. {
  724. keysPressed.clearBit (note);
  725. state.noteOff (midiChannel, note);
  726. keyPressUsed = true;
  727. }
  728. }
  729. }
  730. return keyPressUsed;
  731. }
  732. void MidiKeyboardComponent::focusLost (FocusChangeType)
  733. {
  734. resetAnyKeysInUse();
  735. }