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.

556 lines
14KB

  1. /*
  2. * Pixmap Keyboard, a custom Qt4 widget
  3. * Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 2 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the GPL.txt file
  16. */
  17. #include "pixmapkeyboard.hpp"
  18. #include <QtCore/QTimer>
  19. #include <QtGui/QKeyEvent>
  20. #include <QtGui/QMouseEvent>
  21. #include <QtGui/QPainter>
  22. #ifndef HAVE_CPP11_SUPPORT
  23. static std::map<int, QRectF> kMidiKey2RectMapHorizontal;
  24. #else
  25. static const std::map<int, QRectF> kMidiKey2RectMapHorizontal = {
  26. {0, QRectF(0, 0, 18, 64)}, // C
  27. {1, QRectF(13, 0, 11, 42)}, // C#
  28. {2, QRectF(18, 0, 25, 64)}, // D
  29. {3, QRectF(37, 0, 11, 42)}, // D#
  30. {4, QRectF(42, 0, 18, 64)}, // E
  31. {5, QRectF(60, 0, 18, 64)}, // F
  32. {6, QRectF(73, 0, 11, 42)}, // F#
  33. {7, QRectF(78, 0, 25, 64)}, // G
  34. {8, QRectF(97, 0, 11, 42)}, // G#
  35. {9, QRectF(102, 0, 25, 64)}, // A
  36. {10, QRectF(121, 0, 11, 42)}, // A#
  37. {11, QRectF(126, 0, 18, 64)} // B
  38. };
  39. #endif
  40. #ifndef HAVE_CPP11_SUPPORT
  41. static std::map<int, QRectF> kMidiKey2RectMapVertical;
  42. #else
  43. static const std::map<int, QRectF> kMidiKey2RectMapVertical = {
  44. {11, QRectF(0, 0, 64, 18)}, // B
  45. {10, QRectF(0, 14, 42, 7)}, // A#
  46. {9, QRectF(0, 18, 64, 24)}, // A
  47. {8, QRectF(0, 38, 42, 7)}, // G#
  48. {7, QRectF(0, 42, 64, 24)}, // G
  49. {6, QRectF(0, 62, 42, 7)}, // F#
  50. {5, QRectF(0, 66, 64, 18)}, // F
  51. {4, QRectF(0, 84, 64, 18)}, // E
  52. {3, QRectF(0, 98, 42, 7)}, // D#
  53. {2, QRectF(0, 102, 64, 24)}, // D
  54. {1, QRectF(0, 122, 42, 7)}, // C#
  55. {0, QRectF(0, 126, 64, 18)} // C
  56. };
  57. #endif
  58. #ifndef HAVE_CPP11_SUPPORT
  59. static std::map<int, int> kMidiKeyboard2KeyMap;
  60. #else
  61. static const std::map<int, int> kMidiKeyboard2KeyMap = {
  62. // 3th octave
  63. {Qt::Key_Z, 48},
  64. {Qt::Key_S, 49},
  65. {Qt::Key_X, 50},
  66. {Qt::Key_D, 51},
  67. {Qt::Key_C, 52},
  68. {Qt::Key_V, 53},
  69. {Qt::Key_G, 54},
  70. {Qt::Key_B, 55},
  71. {Qt::Key_H, 56},
  72. {Qt::Key_N, 57},
  73. {Qt::Key_J, 58},
  74. {Qt::Key_M, 59},
  75. // 4th octave
  76. {Qt::Key_Q, 60},
  77. {Qt::Key_2, 61},
  78. {Qt::Key_W, 62},
  79. {Qt::Key_3, 63},
  80. {Qt::Key_E, 64},
  81. {Qt::Key_R, 65},
  82. {Qt::Key_5, 66},
  83. {Qt::Key_T, 67},
  84. {Qt::Key_6, 68},
  85. {Qt::Key_Y, 69},
  86. {Qt::Key_7, 70},
  87. {Qt::Key_U, 71}
  88. };
  89. #endif
  90. #ifndef HAVE_CPP11_SUPPORT
  91. static QVector<int> kBlackNotes;
  92. #else
  93. static const QVector<int> kBlackNotes = {1, 3, 6, 8, 10};
  94. #endif
  95. PixmapKeyboard::PixmapKeyboard(QWidget* parent)
  96. : QWidget(parent),
  97. fPixmap(""),
  98. fPixmapMode(HORIZONTAL),
  99. fColorStr("orange"),
  100. fFont("Monospace", 8, QFont::Normal),
  101. fOctaves(6),
  102. fLastMouseNote(-1),
  103. fWidth(0),
  104. fHeight(0),
  105. fNeedsUpdate(false),
  106. fMidiMap(kMidiKey2RectMapHorizontal)
  107. {
  108. setCursor(Qt::PointingHandCursor);
  109. setMode(HORIZONTAL);
  110. #ifndef HAVE_CPP11_SUPPORT
  111. if (kBlackNotes.count() == 0)
  112. {
  113. // TODO - rest of data
  114. kBlackNotes << 1;
  115. kBlackNotes << 3;
  116. kBlackNotes << 6;
  117. kBlackNotes << 8;
  118. kBlackNotes << 10;
  119. }
  120. #endif
  121. }
  122. void PixmapKeyboard::allNotesOff()
  123. {
  124. fEnabledKeys.clear();
  125. fNeedsUpdate = true;
  126. emit notesOff();
  127. QTimer::singleShot(0, this, SLOT(updateOnce()));
  128. }
  129. void PixmapKeyboard::sendNoteOn(int note, bool sendSignal)
  130. {
  131. if (0 <= note && note <= 127 && ! fEnabledKeys.contains(note))
  132. {
  133. fEnabledKeys.append(note);
  134. if (sendSignal)
  135. emit noteOn(note);
  136. fNeedsUpdate = true;
  137. QTimer::singleShot(0, this, SLOT(updateOnce()));
  138. }
  139. if (fEnabledKeys.count() == 1)
  140. emit notesOn();
  141. }
  142. void PixmapKeyboard::sendNoteOff(int note, bool sendSignal)
  143. {
  144. if (note >= 0 && note <= 127 && fEnabledKeys.contains(note))
  145. {
  146. fEnabledKeys.removeOne(note);
  147. if (sendSignal)
  148. emit noteOff(note);
  149. fNeedsUpdate = true;
  150. QTimer::singleShot(0, this, SLOT(updateOnce()));
  151. }
  152. if (fEnabledKeys.count() == 0)
  153. emit notesOff();
  154. }
  155. void PixmapKeyboard::setMode(Orientation mode, Color color)
  156. {
  157. if (color == COLOR_CLASSIC)
  158. {
  159. fColorStr = "classic";
  160. }
  161. else if (color == COLOR_ORANGE)
  162. {
  163. fColorStr = "orange";
  164. }
  165. else
  166. {
  167. qCritical("PixmapKeyboard::setMode(%i, %i) - invalid color", mode, color);
  168. return setMode(mode);
  169. }
  170. if (mode == HORIZONTAL)
  171. {
  172. fMidiMap = kMidiKey2RectMapHorizontal;
  173. fPixmap.load(QString(":/bitmaps/kbd_h_%1.png").arg(fColorStr));
  174. fPixmapMode = HORIZONTAL;
  175. fWidth = fPixmap.width();
  176. fHeight = fPixmap.height() / 2;
  177. }
  178. else if (mode == VERTICAL)
  179. {
  180. fMidiMap = kMidiKey2RectMapVertical;
  181. fPixmap.load(QString(":/bitmaps/kbd_v_%1.png").arg(fColorStr));
  182. fPixmapMode = VERTICAL;
  183. fWidth = fPixmap.width() / 2;
  184. fHeight = fPixmap.height();
  185. }
  186. else
  187. {
  188. qCritical("PixmapKeyboard::setMode(%i, %i) - invalid mode", mode, color);
  189. return setMode(HORIZONTAL);
  190. }
  191. setOctaves(fOctaves);
  192. }
  193. void PixmapKeyboard::setOctaves(int octaves)
  194. {
  195. Q_ASSERT(octaves >= 1 && octaves <= 8);
  196. if (octaves < 1)
  197. octaves = 1;
  198. else if (octaves > 8)
  199. octaves = 8;
  200. fOctaves = octaves;
  201. if (fPixmapMode == HORIZONTAL)
  202. {
  203. setMinimumSize(fWidth * fOctaves, fHeight);
  204. setMaximumSize(fWidth * fOctaves, fHeight);
  205. }
  206. else if (fPixmapMode == VERTICAL)
  207. {
  208. setMinimumSize(fWidth, fHeight * fOctaves);
  209. setMaximumSize(fWidth, fHeight * fOctaves);
  210. }
  211. update();
  212. }
  213. void PixmapKeyboard::handleMousePos(const QPoint& pos)
  214. {
  215. int note;
  216. int octave;
  217. QPointF keyPos;
  218. if (fPixmapMode == HORIZONTAL)
  219. {
  220. if (pos.x() < 0 or pos.x() > fOctaves * 144)
  221. return;
  222. int posX = pos.x() - 1;
  223. octave = posX / fWidth;
  224. keyPos = QPointF(posX % fWidth, pos.y());
  225. }
  226. else if (fPixmapMode == VERTICAL)
  227. {
  228. if (pos.y() < 0 or pos.y() > fOctaves * 144)
  229. return;
  230. int posY = pos.y() - 1;
  231. octave = fOctaves - posY / fHeight;
  232. keyPos = QPointF(pos.x(), posY % fHeight);
  233. }
  234. else
  235. return;
  236. octave += 3;
  237. if (fMidiMap[1].contains(keyPos)) // C#
  238. note = 1;
  239. else if (fMidiMap[3].contains(keyPos)) // D#
  240. note = 3;
  241. else if (fMidiMap[6].contains(keyPos)) // F#
  242. note = 6;
  243. else if (fMidiMap[8].contains(keyPos)) // G#
  244. note = 8;
  245. else if (fMidiMap[10].contains(keyPos))// A#
  246. note = 10;
  247. else if (fMidiMap[0].contains(keyPos)) // C
  248. note = 0;
  249. else if (fMidiMap[2].contains(keyPos)) // D
  250. note = 2;
  251. else if (fMidiMap[4].contains(keyPos)) // E
  252. note = 4;
  253. else if (fMidiMap[5].contains(keyPos)) // F
  254. note = 5;
  255. else if (fMidiMap[7].contains(keyPos)) // G
  256. note = 7;
  257. else if (fMidiMap[9].contains(keyPos)) // A
  258. note = 9;
  259. else if (fMidiMap[11].contains(keyPos))// B
  260. note = 11;
  261. else
  262. note = -1;
  263. if (note != -1)
  264. {
  265. note += octave * 12;
  266. if (fLastMouseNote != note)
  267. {
  268. sendNoteOff(fLastMouseNote);
  269. sendNoteOn(note);
  270. }
  271. }
  272. else if (fLastMouseNote != -1)
  273. sendNoteOff(fLastMouseNote);
  274. fLastMouseNote = note;
  275. }
  276. void PixmapKeyboard::keyPressEvent(QKeyEvent* event)
  277. {
  278. if (! event->isAutoRepeat())
  279. {
  280. int qKey = event->key();
  281. std::map<int, int>::const_iterator it = kMidiKeyboard2KeyMap.find(qKey);
  282. if (it != kMidiKeyboard2KeyMap.end())
  283. sendNoteOn(it->second);
  284. }
  285. QWidget::keyPressEvent(event);
  286. }
  287. void PixmapKeyboard::keyReleaseEvent(QKeyEvent* event)
  288. {
  289. if (! event->isAutoRepeat())
  290. {
  291. int qKey = event->key();
  292. std::map<int, int>::const_iterator it = kMidiKeyboard2KeyMap.find(qKey);
  293. if (it != kMidiKeyboard2KeyMap.end())
  294. sendNoteOff(it->second);
  295. }
  296. QWidget::keyReleaseEvent(event);
  297. }
  298. void PixmapKeyboard::mousePressEvent(QMouseEvent* event)
  299. {
  300. fLastMouseNote = -1;
  301. handleMousePos(event->pos());
  302. setFocus();
  303. QWidget::mousePressEvent(event);
  304. }
  305. void PixmapKeyboard::mouseMoveEvent(QMouseEvent* event)
  306. {
  307. handleMousePos(event->pos());
  308. QWidget::mousePressEvent(event);
  309. }
  310. void PixmapKeyboard::mouseReleaseEvent(QMouseEvent* event)
  311. {
  312. if (fLastMouseNote != -1)
  313. {
  314. sendNoteOff(fLastMouseNote);
  315. fLastMouseNote = -1;
  316. }
  317. QWidget::mouseReleaseEvent(event);
  318. }
  319. void PixmapKeyboard::paintEvent(QPaintEvent* event)
  320. {
  321. QPainter painter(this);
  322. event->accept();
  323. // -------------------------------------------------------------
  324. // Paint clean keys (as background)
  325. for (int octave=0; octave < fOctaves; ++octave)
  326. {
  327. QRectF target;
  328. if (fPixmapMode == HORIZONTAL)
  329. target = QRectF(fWidth * octave, 0, fWidth, fHeight);
  330. else if (fPixmapMode == VERTICAL)
  331. target = QRectF(0, fHeight * octave, fWidth, fHeight);
  332. else
  333. return;
  334. QRectF source = QRectF(0, 0, fWidth, fHeight);
  335. painter.drawPixmap(target, fPixmap, source);
  336. }
  337. // -------------------------------------------------------------
  338. // Paint (white) pressed keys
  339. bool paintedWhite = false;
  340. for (int i=0, count=fEnabledKeys.count(); i < count; ++i)
  341. {
  342. int octave, note = fEnabledKeys[i];
  343. const QRectF& pos(_getRectFromMidiNote(note));
  344. if (_isNoteBlack(note))
  345. continue;
  346. if (note < 36)
  347. // cannot paint this note
  348. continue;
  349. else if (note < 48)
  350. octave = 0;
  351. else if (note < 60)
  352. octave = 1;
  353. else if (note < 72)
  354. octave = 2;
  355. else if (note < 84)
  356. octave = 3;
  357. else if (note < 96)
  358. octave = 4;
  359. else if (note < 108)
  360. octave = 5;
  361. else if (note < 120)
  362. octave = 6;
  363. else if (note < 132)
  364. octave = 7;
  365. else
  366. // cannot paint this note either
  367. continue;
  368. if (fPixmapMode == VERTICAL)
  369. octave = fOctaves - octave - 1;
  370. QRectF target, source;
  371. if (fPixmapMode == HORIZONTAL)
  372. {
  373. target = QRectF(pos.x() + (fWidth * octave), 0, pos.width(), pos.height());
  374. source = QRectF(pos.x(), fHeight, pos.width(), pos.height());
  375. }
  376. else if (fPixmapMode == VERTICAL)
  377. {
  378. target = QRectF(pos.x(), pos.y() + (fHeight * octave), pos.width(), pos.height());
  379. source = QRectF(fWidth, pos.y(), pos.width(), pos.height());
  380. }
  381. else
  382. return;
  383. paintedWhite = true;
  384. painter.drawPixmap(target, fPixmap, source);
  385. }
  386. // -------------------------------------------------------------
  387. // Clear white keys border
  388. if (paintedWhite)
  389. {
  390. for (int octave=0; octave < fOctaves; ++octave)
  391. {
  392. foreach (int note, kBlackNotes)
  393. {
  394. QRectF target, source;
  395. const QRectF& pos(_getRectFromMidiNote(note));
  396. if (fPixmapMode == HORIZONTAL)
  397. {
  398. target = QRectF(pos.x() + (fWidth * octave), 0, pos.width(), pos.height());
  399. source = QRectF(pos.x(), 0, pos.width(), pos.height());
  400. }
  401. else if (fPixmapMode == VERTICAL)
  402. {
  403. target = QRectF(pos.x(), pos.y() + (fHeight * octave), pos.width(), pos.height());
  404. source = QRectF(0, pos.y(), pos.width(), pos.height());
  405. }
  406. else
  407. return;
  408. painter.drawPixmap(target, fPixmap, source);
  409. }
  410. }
  411. }
  412. // -------------------------------------------------------------
  413. // Paint (black) pressed keys
  414. for (int i=0, count=fEnabledKeys.count(); i < count; ++i)
  415. {
  416. int octave, note = fEnabledKeys[i];
  417. const QRectF& pos(_getRectFromMidiNote(note));
  418. if (! _isNoteBlack(note))
  419. continue;
  420. if (note < 36)
  421. // cannot paint this note
  422. continue;
  423. else if (note < 48)
  424. octave = 0;
  425. else if (note < 60)
  426. octave = 1;
  427. else if (note < 72)
  428. octave = 2;
  429. else if (note < 84)
  430. octave = 3;
  431. else if (note < 96)
  432. octave = 4;
  433. else if (note < 108)
  434. octave = 5;
  435. else if (note < 120)
  436. octave = 6;
  437. else if (note < 132)
  438. octave = 7;
  439. else
  440. // cannot paint this note either
  441. continue;
  442. if (fPixmapMode == VERTICAL)
  443. octave = fOctaves - octave - 1;
  444. QRectF target, source;
  445. if (fPixmapMode == HORIZONTAL)
  446. {
  447. target = QRectF(pos.x() + (fWidth * octave), 0, pos.width(), pos.height());
  448. source = QRectF(pos.x(), fHeight, pos.width(), pos.height());
  449. }
  450. else if (fPixmapMode == VERTICAL)
  451. {
  452. target = QRectF(pos.x(), pos.y() + (fHeight * octave), pos.width(), pos.height());
  453. source = QRectF(fWidth, pos.y(), pos.width(), pos.height());
  454. }
  455. else
  456. return;
  457. painter.drawPixmap(target, fPixmap, source);
  458. }
  459. // Paint C-number note info
  460. painter.setFont(fFont);
  461. painter.setPen(Qt::black);
  462. for (int i=0; i < fOctaves; ++i)
  463. {
  464. if (fPixmapMode == HORIZONTAL)
  465. painter.drawText(i * 144, 48, 18, 18, Qt::AlignCenter, QString("C%1").arg(i + 2));
  466. else if (fPixmapMode == VERTICAL)
  467. painter.drawText(45, (fOctaves * 144) - (i * 144) - 16, 18, 18, Qt::AlignCenter, QString("C%1").arg(i + 2));
  468. }
  469. }
  470. void PixmapKeyboard::updateOnce()
  471. {
  472. if (fNeedsUpdate)
  473. {
  474. update();
  475. fNeedsUpdate = false;
  476. }
  477. }
  478. bool PixmapKeyboard::_isNoteBlack(int note) const
  479. {
  480. int baseNote = note % 12;
  481. return kBlackNotes.contains(baseNote);
  482. }
  483. const QRectF& PixmapKeyboard::_getRectFromMidiNote(int note) const
  484. {
  485. return fMidiMap[note % 12];
  486. }