Collection of tools useful for audio production
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.

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