/* * Pixmap Keyboard, a custom Qt4 widget * Copyright (C) 2011-2012 Filipe Coelho * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For a full copy of the GNU General Public License see the COPYING file */ #include "pixmapkeyboard.hpp" #include #include #include #include #include QMap midi_key2rect_map_horizontal; QMap midi_key2rect_map_vertical; QMap midi_keyboard2key_map; QVector blackNotes; static bool pixmapkeyboard_initiated = false; void pixmapkeyboard_init() { if (pixmapkeyboard_initiated) return; pixmapkeyboard_initiated = true; // midi_key2rect_map_horizontal ------ midi_key2rect_map_horizontal[0] = QRectF(0, 0, 18, 64); // C midi_key2rect_map_horizontal[1] = QRectF(13, 0, 11, 42); // C# midi_key2rect_map_horizontal[2] = QRectF(18, 0, 25, 64); // D midi_key2rect_map_horizontal[3] = QRectF(37, 0, 11, 42); // D# midi_key2rect_map_horizontal[4] = QRectF(42, 0, 18, 64); // E midi_key2rect_map_horizontal[5] = QRectF(60, 0, 18, 64); // F midi_key2rect_map_horizontal[6] = QRectF(73, 0, 11, 42); // F# midi_key2rect_map_horizontal[7] = QRectF(78, 0, 25, 64); // G midi_key2rect_map_horizontal[8] = QRectF(97, 0, 11, 42); // G# midi_key2rect_map_horizontal[9] = QRectF(102, 0, 25, 64); // A midi_key2rect_map_horizontal[10] = QRectF(121, 0, 11, 42); // A# midi_key2rect_map_horizontal[11] = QRectF(126, 0, 18, 64); // B // midi_key2rect_map_vertical -------- midi_key2rect_map_vertical[11] = QRectF(0, 0, 64, 18); // B midi_key2rect_map_vertical[10] = QRectF(0, 14, 42, 7); // A# midi_key2rect_map_vertical[9] = QRectF(0, 18, 64, 24); // A midi_key2rect_map_vertical[8] = QRectF(0, 38, 42, 7); // G# midi_key2rect_map_vertical[7] = QRectF(0, 42, 64, 24); // G midi_key2rect_map_vertical[6] = QRectF(0, 62, 42, 7); // F# midi_key2rect_map_vertical[5] = QRectF(0, 66, 64, 18); // F midi_key2rect_map_vertical[4] = QRectF(0, 84, 64, 18); // E midi_key2rect_map_vertical[3] = QRectF(0, 98, 42, 7); // D# midi_key2rect_map_vertical[2] = QRectF(0, 102, 64, 24); // D midi_key2rect_map_vertical[1] = QRectF(0, 122, 42, 7); // C# midi_key2rect_map_vertical[0] = QRectF(0, 126, 64, 18); // C // midi_keyboard2key_map ------------- // 3th octave midi_keyboard2key_map[Qt::Key_Z] = 48; midi_keyboard2key_map[Qt::Key_S] = 49; midi_keyboard2key_map[Qt::Key_X] = 50; midi_keyboard2key_map[Qt::Key_D] = 51; midi_keyboard2key_map[Qt::Key_C] = 52; midi_keyboard2key_map[Qt::Key_V] = 53; midi_keyboard2key_map[Qt::Key_G] = 54; midi_keyboard2key_map[Qt::Key_B] = 55; midi_keyboard2key_map[Qt::Key_H] = 56; midi_keyboard2key_map[Qt::Key_N] = 57; midi_keyboard2key_map[Qt::Key_J] = 58; midi_keyboard2key_map[Qt::Key_M] = 59; // 4th octave midi_keyboard2key_map[Qt::Key_Q] = 60; midi_keyboard2key_map[Qt::Key_2] = 61; midi_keyboard2key_map[Qt::Key_W] = 62; midi_keyboard2key_map[Qt::Key_3] = 63; midi_keyboard2key_map[Qt::Key_E] = 64; midi_keyboard2key_map[Qt::Key_R] = 65; midi_keyboard2key_map[Qt::Key_5] = 66; midi_keyboard2key_map[Qt::Key_T] = 67; midi_keyboard2key_map[Qt::Key_6] = 68; midi_keyboard2key_map[Qt::Key_Y] = 69; midi_keyboard2key_map[Qt::Key_7] = 70; midi_keyboard2key_map[Qt::Key_U] = 71; blackNotes << 1; blackNotes << 3; blackNotes << 6; blackNotes << 8; blackNotes << 10; } PixmapKeyboard::PixmapKeyboard(QWidget* parent) : QWidget(parent), m_font("Monospace", 8, QFont::Normal) { pixmapkeyboard_init(); m_octaves = 6; m_lastMouseNote = -1; m_needsUpdate = false; setCursor(Qt::PointingHandCursor); setMode(HORIZONTAL); } void PixmapKeyboard::allNotesOff() { m_enabledKeys.clear(); m_needsUpdate = true; QTimer::singleShot(0, this, SLOT(updateOnce())); emit notesOff(); } void PixmapKeyboard::sendNoteOn(int note, bool sendSignal) { if (0 <= note && note <= 127 && ! m_enabledKeys.contains(note)) { m_enabledKeys.append(note); if (sendSignal) emit noteOn(note); m_needsUpdate = true; QTimer::singleShot(0, this, SLOT(updateOnce())); } if (m_enabledKeys.count() == 1) emit notesOn(); } void PixmapKeyboard::sendNoteOff(int note, bool sendSignal) { if (note >= 0 && note <= 127 && m_enabledKeys.contains(note)) { m_enabledKeys.removeOne(note); if (sendSignal) emit noteOff(note); m_needsUpdate = true; QTimer::singleShot(0, this, SLOT(updateOnce())); } if (m_enabledKeys.count() == 0) emit notesOff(); } void PixmapKeyboard::setMode(Orientation mode, Color color) { if (color == COLOR_CLASSIC) { m_colorStr = "classic"; } else if (color == COLOR_ORANGE) { m_colorStr = "orange"; } else { qCritical("PixmapKeyboard::setMode(%i, %i) - invalid color", mode, color); return setMode(mode); } if (mode == HORIZONTAL) { m_midi_map = &midi_key2rect_map_horizontal; m_pixmap.load(QString(":/bitmaps/kbd_h_%1.png").arg(m_colorStr)); m_pixmap_mode = HORIZONTAL; p_width = m_pixmap.width(); p_height = m_pixmap.height() / 2; } else if (mode == VERTICAL) { m_midi_map = &midi_key2rect_map_vertical; m_pixmap.load(QString(":/bitmaps/kbd_v_%1.png").arg(m_colorStr)); m_pixmap_mode = VERTICAL; p_width = m_pixmap.width() / 2; p_height = m_pixmap.height(); } else { qCritical("PixmapKeyboard::setMode(%i, %i) - invalid mode", mode, color); return setMode(HORIZONTAL); } setOctaves(m_octaves); } void PixmapKeyboard::setOctaves(int octaves) { Q_ASSERT(octaves >= 1 && octaves <= 6); if (octaves < 1) octaves = 1; else if (octaves > 6) octaves = 6; m_octaves = octaves; if (m_pixmap_mode == HORIZONTAL) { setMinimumSize(p_width * m_octaves, p_height); setMaximumSize(p_width * m_octaves, p_height); } else if (m_pixmap_mode == VERTICAL) { setMinimumSize(p_width, p_height * m_octaves); setMaximumSize(p_width, p_height * m_octaves); } update(); } void PixmapKeyboard::handleMousePos(const QPoint& pos) { int note, octave; QPointF n_pos; if (m_pixmap_mode == HORIZONTAL) { if (pos.x() < 0 or pos.x() > m_octaves * 144) return; int posX = pos.x() - 1; octave = posX / p_width; n_pos = QPointF(posX % p_width, pos.y()); } else if (m_pixmap_mode == VERTICAL) { if (pos.y() < 0 or pos.y() > m_octaves * 144) return; int posY = pos.y() - 1; octave = m_octaves - posY / p_height; n_pos = QPointF(pos.x(), posY % p_height); } else return; octave += 3; if ((*m_midi_map)[1].contains(n_pos)) // C# note = 1; else if ((*m_midi_map)[3].contains(n_pos)) // D# note = 3; else if ((*m_midi_map)[6].contains(n_pos)) // F# note = 6; else if ((*m_midi_map)[8].contains(n_pos)) // G# note = 8; else if ((*m_midi_map)[10].contains(n_pos))// A# note = 10; else if ((*m_midi_map)[0].contains(n_pos)) // C note = 0; else if ((*m_midi_map)[2].contains(n_pos)) // D note = 2; else if ((*m_midi_map)[4].contains(n_pos)) // E note = 4; else if ((*m_midi_map)[5].contains(n_pos)) // F note = 5; else if ((*m_midi_map)[7].contains(n_pos)) // G note = 7; else if ((*m_midi_map)[9].contains(n_pos)) // A note = 9; else if ((*m_midi_map)[11].contains(n_pos))// B note = 11; else note = -1; if (note != -1) { note += octave * 12; if (m_lastMouseNote != note) { sendNoteOff(m_lastMouseNote); sendNoteOn(note); } } else sendNoteOff(m_lastMouseNote); m_lastMouseNote = note; } void PixmapKeyboard::keyPressEvent(QKeyEvent* event) { if (! event->isAutoRepeat()) { int qKey = event->key(); if (midi_keyboard2key_map.keys().contains(qKey)) sendNoteOn(midi_keyboard2key_map[qKey]); } QWidget::keyPressEvent(event); } void PixmapKeyboard::keyReleaseEvent(QKeyEvent* event) { if (! event->isAutoRepeat()) { int qKey = event->key(); if (midi_keyboard2key_map.keys().contains(qKey)) sendNoteOff(midi_keyboard2key_map[qKey]); } QWidget::keyReleaseEvent(event); } void PixmapKeyboard::mousePressEvent(QMouseEvent* event) { m_lastMouseNote = -1; handleMousePos(event->pos()); setFocus(); QWidget::mousePressEvent(event); } void PixmapKeyboard::mouseMoveEvent(QMouseEvent* event) { handleMousePos(event->pos()); QWidget::mousePressEvent(event); } void PixmapKeyboard::mouseReleaseEvent(QMouseEvent* event) { if (m_lastMouseNote != -1) { sendNoteOff(m_lastMouseNote); m_lastMouseNote = -1; } QWidget::mouseReleaseEvent(event); } void PixmapKeyboard::paintEvent(QPaintEvent*) { QPainter painter(this); // ------------------------------------------------------------- // Paint clean keys (as background) for (int octave=0; octave < m_octaves; octave++) { QRectF target; if (m_pixmap_mode == HORIZONTAL) target = QRectF(p_width * octave, 0, p_width, p_height); else if (m_pixmap_mode == VERTICAL) target = QRectF(0, p_height * octave, p_width, p_height); else return; QRectF source = QRectF(0, 0, p_width, p_height); painter.drawPixmap(target, m_pixmap, source); } // ------------------------------------------------------------- // Paint (white) pressed keys bool paintedWhite = false; for (int i=0; i < m_enabledKeys.count(); i++) { int octave, note = m_enabledKeys[i]; QRectF pos = _getRectFromMidiNote(note); if (_isNoteBlack(note)) continue; if (note < 36) // cannot paint this note continue; else if (note < 48) octave = 0; else if (note < 60) octave = 1; else if (note < 72) octave = 2; else if (note < 84) octave = 3; else if (note < 96) octave = 4; else if (note < 108) octave = 5; else // cannot paint this note either continue; if (m_pixmap_mode == VERTICAL) octave = m_octaves - octave - 1; QRectF target, source; if (m_pixmap_mode == HORIZONTAL) { target = QRectF(pos.x() + (p_width * octave), 0, pos.width(), pos.height()); source = QRectF(pos.x(), p_height, pos.width(), pos.height()); } else if (m_pixmap_mode == VERTICAL) { target = QRectF(pos.x(), pos.y() + (p_height * octave), pos.width(), pos.height()); source = QRectF(p_width, pos.y(), pos.width(), pos.height()); } else return; paintedWhite = true; painter.drawPixmap(target, m_pixmap, source); } // ------------------------------------------------------------- // Clear white keys border if (paintedWhite) { for (int octave=0; octave < m_octaves; octave++) { foreach (int note, blackNotes) { QRectF target, source; QRectF pos = _getRectFromMidiNote(note); if (m_pixmap_mode == HORIZONTAL) { target = QRectF(pos.x() + (p_width * octave), 0, pos.width(), pos.height()); source = QRectF(pos.x(), 0, pos.width(), pos.height()); } else if (m_pixmap_mode == VERTICAL) { target = QRectF(pos.x(), pos.y() + (p_height * octave), pos.width(), pos.height()); source = QRectF(0, pos.y(), pos.width(), pos.height()); } else return; painter.drawPixmap(target, m_pixmap, source); } } } // ------------------------------------------------------------- // Paint (black) pressed keys for (int i=0; i < m_enabledKeys.count(); i++) { int octave, note = m_enabledKeys[i]; QRectF pos = _getRectFromMidiNote(note); if (! _isNoteBlack(note)) continue; if (note < 36) // cannot paint this note continue; else if (note < 48) octave = 0; else if (note < 60) octave = 1; else if (note < 72) octave = 2; else if (note < 84) octave = 3; else if (note < 96) octave = 4; else if (note < 108) octave = 5; else // cannot paint this note either continue; if (m_pixmap_mode == VERTICAL) octave = m_octaves - octave - 1; QRectF target, source; if (m_pixmap_mode == HORIZONTAL) { target = QRectF(pos.x() + (p_width * octave), 0, pos.width(), pos.height()); source = QRectF(pos.x(), p_height, pos.width(), pos.height()); } else if (m_pixmap_mode == VERTICAL) { target = QRectF(pos.x(), pos.y() + (p_height * octave), pos.width(), pos.height()); source = QRectF(p_width, pos.y(), pos.width(), pos.height()); } else return; painter.drawPixmap(target, m_pixmap, source); } // Paint C-number note info painter.setFont(m_font); painter.setPen(Qt::black); for (int i=0; i < m_octaves; i++) { if (m_pixmap_mode == HORIZONTAL) painter.drawText(i * 144, 48, 18, 18, Qt::AlignCenter, QString("C%1").arg(i + 2)); else if (m_pixmap_mode == VERTICAL) painter.drawText(45, (m_octaves * 144) - (i * 144) - 16, 18, 18, Qt::AlignCenter, QString("C%1").arg(i + 2)); } } void PixmapKeyboard::updateOnce() { if (m_needsUpdate) { update(); m_needsUpdate = false; } } bool PixmapKeyboard::_isNoteBlack(int note) { int baseNote = note % 12; return blackNotes.contains(baseNote); } QRectF PixmapKeyboard::_getRectFromMidiNote(int note) { return (*m_midi_map)[note % 12]; }