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.

pixmapkeyboard.cpp 15KB

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