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.

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