|
|
@@ -0,0 +1,406 @@ |
|
|
|
#!/usr/bin/env python |
|
|
|
# -*- coding: utf-8 -*- |
|
|
|
|
|
|
|
# Pixmap Keyboard, a custom Qt4 widget |
|
|
|
# Copyright (C) 2012 Filipe Coelho <falktx@gmail.com> |
|
|
|
# |
|
|
|
# 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 |
|
|
|
|
|
|
|
# Imports (Global) |
|
|
|
from PyQt4.QtCore import pyqtSlot, qCritical, Qt, QPointF, QRectF, QTimer, SIGNAL, SLOT |
|
|
|
from PyQt4.QtGui import QFont, QPainter, QPixmap, QWidget |
|
|
|
|
|
|
|
# Imports (Custom Stuff) |
|
|
|
import icons_rc |
|
|
|
|
|
|
|
midi_key2rect_map_horizontal = { |
|
|
|
'0': QRectF( 0, 0, 18, 64), # C |
|
|
|
'1': QRectF( 13, 0, 11, 42), # C# |
|
|
|
'2': QRectF( 18, 0, 25, 64), # D |
|
|
|
'3': QRectF( 37, 0, 11, 42), # D# |
|
|
|
'4': QRectF( 42, 0, 18, 64), # E |
|
|
|
'5': QRectF( 60, 0, 18, 64), # F |
|
|
|
'6': QRectF( 73, 0, 11, 42), # F# |
|
|
|
'7': QRectF( 78, 0, 25, 64), # G |
|
|
|
'8': QRectF( 97, 0, 11, 42), # G# |
|
|
|
'9': QRectF(102, 0, 25, 64), # A |
|
|
|
'10': QRectF(121, 0, 11, 42), # A# |
|
|
|
'11': QRectF(126, 0, 18, 64) # B |
|
|
|
} |
|
|
|
|
|
|
|
midi_key2rect_map_vertical = { |
|
|
|
'11': QRectF(0, 0, 64, 18), # B |
|
|
|
'10': QRectF(0, 14, 42, 7), # A# |
|
|
|
'9': QRectF(0, 18, 64, 24), # A |
|
|
|
'8': QRectF(0, 38, 42, 7), # G# |
|
|
|
'7': QRectF(0, 42, 64, 24), # G |
|
|
|
'6': QRectF(0, 62, 42, 7), # F# |
|
|
|
'5': QRectF(0, 66, 64, 18), # F |
|
|
|
'4': QRectF(0, 84, 64, 18), # E |
|
|
|
'3': QRectF(0, 98, 42, 7), # D# |
|
|
|
'2': QRectF(0, 102, 64, 24), # D |
|
|
|
'1': QRectF(0, 122, 42, 7), # C# |
|
|
|
'0': QRectF(0, 126, 64, 18) # C |
|
|
|
} |
|
|
|
|
|
|
|
midi_keyboard2key_map = { |
|
|
|
# 3th octave |
|
|
|
'%i' % (Qt.Key_Z): 48, |
|
|
|
'%i' % (Qt.Key_S): 49, |
|
|
|
'%i' % (Qt.Key_X): 50, |
|
|
|
'%i' % (Qt.Key_D): 51, |
|
|
|
'%i' % (Qt.Key_C): 52, |
|
|
|
'%i' % (Qt.Key_V): 53, |
|
|
|
'%i' % (Qt.Key_G): 54, |
|
|
|
'%i' % (Qt.Key_B): 55, |
|
|
|
'%i' % (Qt.Key_H): 56, |
|
|
|
'%i' % (Qt.Key_N): 57, |
|
|
|
'%i' % (Qt.Key_J): 58, |
|
|
|
'%i' % (Qt.Key_M): 59, |
|
|
|
# 4th octave |
|
|
|
'%i' % (Qt.Key_Q): 60, |
|
|
|
'%i' % (Qt.Key_2): 61, |
|
|
|
'%i' % (Qt.Key_W): 62, |
|
|
|
'%i' % (Qt.Key_3): 63, |
|
|
|
'%i' % (Qt.Key_E): 64, |
|
|
|
'%i' % (Qt.Key_R): 65, |
|
|
|
'%i' % (Qt.Key_5): 66, |
|
|
|
'%i' % (Qt.Key_T): 67, |
|
|
|
'%i' % (Qt.Key_6): 68, |
|
|
|
'%i' % (Qt.Key_Y): 69, |
|
|
|
'%i' % (Qt.Key_7): 70, |
|
|
|
'%i' % (Qt.Key_U): 71, |
|
|
|
} |
|
|
|
|
|
|
|
# MIDI Keyboard, using a pixmap for painting |
|
|
|
class PixmapKeyboard(QWidget): |
|
|
|
|
|
|
|
COLOR_CLASSIC = 0 |
|
|
|
COLOR_ORANGE = 1 |
|
|
|
|
|
|
|
HORIZONTAL = 0 |
|
|
|
VERTICAL = 1 |
|
|
|
|
|
|
|
def __init__(self, parent): |
|
|
|
super(PixmapKeyboard, self).__init__(parent) |
|
|
|
|
|
|
|
self.m_octaves = 6 |
|
|
|
self.m_lastMouseNote = -1 |
|
|
|
|
|
|
|
self.m_needsUpdate = False |
|
|
|
self.m_enabledKeys = [] |
|
|
|
|
|
|
|
self.m_font = QFont("Monospace", 8, QFont.Normal) |
|
|
|
self.m_pixmap = QPixmap("") |
|
|
|
|
|
|
|
self.setMode(self.HORIZONTAL) |
|
|
|
|
|
|
|
def noteOn(self, note, sendSignal=True): |
|
|
|
if (note >= 0 and note <= 127 and note not in self.m_enabledKeys): |
|
|
|
self.m_enabledKeys.append(note) |
|
|
|
if (sendSignal): |
|
|
|
self.emit(SIGNAL("noteOn(int)"), note) |
|
|
|
|
|
|
|
self.m_needsUpdate = True |
|
|
|
QTimer.singleShot(0, self, SLOT("slot_updateOnce()")) |
|
|
|
|
|
|
|
if (len(self.m_enabledKeys) == 1): |
|
|
|
self.emit(SIGNAL("notesOn()")) |
|
|
|
|
|
|
|
def noteOff(self, note, sendSignal=True): |
|
|
|
if (note >= 0 and note <= 127 and note in self.m_enabledKeys): |
|
|
|
self.m_enabledKeys.remove(note) |
|
|
|
if (sendSignal): |
|
|
|
self.emit(SIGNAL("noteOff(int)"), note) |
|
|
|
|
|
|
|
self.m_needsUpdate = True |
|
|
|
QTimer.singleShot(0, self, SLOT("slot_updateOnce()")) |
|
|
|
|
|
|
|
if (len(self.m_enabledKeys) == 0): |
|
|
|
self.emit(SIGNAL("notesOff()")) |
|
|
|
|
|
|
|
def _isNoteBlack(self, note): |
|
|
|
baseNote = note % 12 |
|
|
|
return bool(baseNote in (1, 3, 6, 8, 10)) |
|
|
|
|
|
|
|
def _getRectFromMidiNote(self, note): |
|
|
|
return self.m_midi_map.get(str(note % 12)) |
|
|
|
|
|
|
|
def setMode(self, mode, color=COLOR_ORANGE): |
|
|
|
if (color == self.COLOR_CLASSIC): |
|
|
|
self.m_colorStr = "classic" |
|
|
|
elif (color == self.COLOR_ORANGE): |
|
|
|
self.m_colorStr = "orange" |
|
|
|
else: |
|
|
|
qCritical("PixmapKeyboard::setMode(%i, %i) - Invalid keyboard color", mode, color) |
|
|
|
self.setMode(mode) |
|
|
|
return |
|
|
|
|
|
|
|
if (mode == self.HORIZONTAL): |
|
|
|
self.m_midi_map = midi_key2rect_map_horizontal |
|
|
|
self.m_pixmap.load(":/bitmaps/kbd_h_%s.png" % (self.m_colorStr)) |
|
|
|
self.m_pixmap_mode = self.HORIZONTAL |
|
|
|
self.p_width = self.m_pixmap.width() |
|
|
|
self.p_height = self.m_pixmap.height()/2 |
|
|
|
elif (mode == self.VERTICAL): |
|
|
|
self.m_midi_map = midi_key2rect_map_vertical |
|
|
|
self.m_pixmap.load(":/bitmaps/kbd_v_%s.png" % (self.m_colorStr)) |
|
|
|
self.m_pixmap_mode = self.VERTICAL |
|
|
|
self.p_width = self.m_pixmap.width()/2 |
|
|
|
self.p_height = self.m_pixmap.height() |
|
|
|
else: |
|
|
|
qCritical("PixmapKeyboard::setMode(%i, %i) - Invalid keyboard mode", mode, color) |
|
|
|
self.setMode(self.HORIZONTAL) |
|
|
|
return |
|
|
|
|
|
|
|
self.setOctaves(self.m_octaves) |
|
|
|
|
|
|
|
def setOctaves(self, octaves): |
|
|
|
if (octaves < 1): |
|
|
|
octaves = 1 |
|
|
|
elif (octaves > 6): |
|
|
|
octaves = 6 |
|
|
|
self.m_octaves = octaves |
|
|
|
|
|
|
|
if (self.m_pixmap_mode == self.HORIZONTAL): |
|
|
|
self.setMinimumSize(self.p_width*self.m_octaves, self.p_height) |
|
|
|
self.setMaximumSize(self.p_width*self.m_octaves, self.p_height) |
|
|
|
elif (self.m_pixmap_mode == self.VERTICAL): |
|
|
|
self.setMinimumSize(self.p_width, self.p_height*self.m_octaves) |
|
|
|
self.setMaximumSize(self.p_width, self.p_height*self.m_octaves) |
|
|
|
|
|
|
|
self.update() |
|
|
|
|
|
|
|
@pyqtSlot() |
|
|
|
def slot_updateOnce(self): |
|
|
|
if (self.m_needsUpdate): |
|
|
|
self.update() |
|
|
|
self.m_needsUpdate = False |
|
|
|
|
|
|
|
def keyPressEvent(self, event): |
|
|
|
qKey = str(event.key()) |
|
|
|
|
|
|
|
if (qKey in midi_keyboard2key_map.keys()): |
|
|
|
self.noteOn(midi_keyboard2key_map.get(qKey)) |
|
|
|
|
|
|
|
QWidget.keyPressEvent(self, event) |
|
|
|
|
|
|
|
def keyReleaseEvent(self, event): |
|
|
|
qKey = str(event.key()) |
|
|
|
|
|
|
|
if (qKey in midi_keyboard2key_map.keys()): |
|
|
|
self.noteOff(midi_keyboard2key_map.get(qKey)) |
|
|
|
|
|
|
|
QWidget.keyReleaseEvent(self, event) |
|
|
|
|
|
|
|
def mousePressEvent(self, event): |
|
|
|
self.m_lastMouseNote = -1 |
|
|
|
self.handleMousePos(event.pos()) |
|
|
|
self.setFocus() |
|
|
|
QWidget.mousePressEvent(self, event) |
|
|
|
|
|
|
|
def mouseMoveEvent(self, event): |
|
|
|
self.handleMousePos(event.pos()) |
|
|
|
QWidget.mousePressEvent(self, event) |
|
|
|
|
|
|
|
def mouseReleaseEvent(self, event): |
|
|
|
if (self.m_lastMouseNote != -1): |
|
|
|
self.noteOff(self.m_lastMouseNote) |
|
|
|
self.m_lastMouseNote = -1 |
|
|
|
QWidget.mouseReleaseEvent(self, event) |
|
|
|
|
|
|
|
def handleMousePos(self, pos): |
|
|
|
if (self.m_pixmap_mode == self.HORIZONTAL): |
|
|
|
if (pos.x() < 0 or pos.x() > self.m_octaves*144): |
|
|
|
return |
|
|
|
octave = int(pos.x()/self.p_width) |
|
|
|
n_pos = QPointF(pos.x()%self.p_width, pos.y()) |
|
|
|
elif (self.m_pixmap_mode == self.VERTICAL): |
|
|
|
if (pos.y() < 0 or pos.y() > self.m_octaves*144): |
|
|
|
return |
|
|
|
octave = int(self.m_octaves-pos.y()/self.p_height) |
|
|
|
n_pos = QPointF(pos.x(), pos.y()%self.p_height) |
|
|
|
else: |
|
|
|
return |
|
|
|
|
|
|
|
octave += 3 |
|
|
|
|
|
|
|
if (self.m_midi_map['1'].contains(n_pos)): # C# |
|
|
|
note = 1 |
|
|
|
elif (self.m_midi_map['3'].contains(n_pos)): # D# |
|
|
|
note = 3 |
|
|
|
elif (self.m_midi_map['6'].contains(n_pos)): # F# |
|
|
|
note = 6 |
|
|
|
elif (self.m_midi_map['8'].contains(n_pos)): # G# |
|
|
|
note = 8 |
|
|
|
elif (self.m_midi_map['10'].contains(n_pos)):# A# |
|
|
|
note = 10 |
|
|
|
elif (self.m_midi_map['0'].contains(n_pos)): # C |
|
|
|
note = 0 |
|
|
|
elif (self.m_midi_map['2'].contains(n_pos)): # D |
|
|
|
note = 2 |
|
|
|
elif (self.m_midi_map['4'].contains(n_pos)): # E |
|
|
|
note = 4 |
|
|
|
elif (self.m_midi_map['5'].contains(n_pos)): # F |
|
|
|
note = 5 |
|
|
|
elif (self.m_midi_map['7'].contains(n_pos)): # G |
|
|
|
note = 7 |
|
|
|
elif (self.m_midi_map['9'].contains(n_pos)): # A |
|
|
|
note = 9 |
|
|
|
elif (self.m_midi_map['11'].contains(n_pos)):# B |
|
|
|
note = 11 |
|
|
|
else: |
|
|
|
note = -1 |
|
|
|
|
|
|
|
if (note != -1): |
|
|
|
note += octave*12 |
|
|
|
if (self.m_lastMouseNote != note): |
|
|
|
self.noteOff(self.m_lastMouseNote) |
|
|
|
self.noteOn(note) |
|
|
|
else: |
|
|
|
self.noteOff(self.m_lastMouseNote) |
|
|
|
|
|
|
|
self.m_lastMouseNote = note |
|
|
|
|
|
|
|
def paintEvent(self, event): |
|
|
|
painter = QPainter(self) |
|
|
|
|
|
|
|
# ------------------------------------------------------------- |
|
|
|
# Paint clean keys (as background) |
|
|
|
|
|
|
|
for octave in range(self.m_octaves): |
|
|
|
if (self.m_pixmap_mode == self.HORIZONTAL): |
|
|
|
target = QRectF(self.p_width*octave, 0, self.p_width, self.p_height) |
|
|
|
elif (self.m_pixmap_mode == self.VERTICAL): |
|
|
|
target = QRectF(0, self.p_height*octave, self.p_width, self.p_height) |
|
|
|
else: |
|
|
|
return |
|
|
|
|
|
|
|
source = QRectF(0, 0, self.p_width, self.p_height) |
|
|
|
painter.drawPixmap(target, self.m_pixmap, source) |
|
|
|
|
|
|
|
# ------------------------------------------------------------- |
|
|
|
# Paint (white) pressed keys |
|
|
|
|
|
|
|
paintedWhite = False |
|
|
|
|
|
|
|
for i in range(len(self.m_enabledKeys)): |
|
|
|
note = self.m_enabledKeys[i] |
|
|
|
pos = self._getRectFromMidiNote(note) |
|
|
|
|
|
|
|
if (self._isNoteBlack(note)): |
|
|
|
continue |
|
|
|
|
|
|
|
if (note < 35): |
|
|
|
# cannot paint this note |
|
|
|
continue |
|
|
|
elif (note < 48): |
|
|
|
octave = 0 |
|
|
|
elif (note < 60): |
|
|
|
octave = 1 |
|
|
|
elif (note < 72): |
|
|
|
octave = 2 |
|
|
|
elif (note < 84): |
|
|
|
octave = 3 |
|
|
|
elif (note < 96): |
|
|
|
octave = 4 |
|
|
|
elif (note < 108): |
|
|
|
octave = 5 |
|
|
|
else: |
|
|
|
# cannot paint this note either |
|
|
|
continue |
|
|
|
|
|
|
|
if (self.m_pixmap_mode == self.VERTICAL): |
|
|
|
octave = self.m_octaves - octave - 1 |
|
|
|
|
|
|
|
if (self.m_pixmap_mode == self.HORIZONTAL): |
|
|
|
target = QRectF(pos.x()+(self.p_width*octave), 0, pos.width(), pos.height()) |
|
|
|
source = QRectF(pos.x(), self.p_height, pos.width(), pos.height()) |
|
|
|
elif (self.m_pixmap_mode == self.VERTICAL): |
|
|
|
target = QRectF(pos.x(), pos.y()+(self.p_height*octave), pos.width(), pos.height()) |
|
|
|
source = QRectF(self.p_width, pos.y(), pos.width(), pos.height()) |
|
|
|
else: |
|
|
|
return |
|
|
|
|
|
|
|
paintedWhite = True |
|
|
|
painter.drawPixmap(target, self.m_pixmap, source) |
|
|
|
|
|
|
|
# ------------------------------------------------------------- |
|
|
|
# Clear white keys border |
|
|
|
|
|
|
|
if (paintedWhite): |
|
|
|
for octave in range(self.m_octaves): |
|
|
|
for note in (1, 3, 6, 8, 10): |
|
|
|
pos = self._getRectFromMidiNote(note) |
|
|
|
if (self.m_pixmap_mode == self.HORIZONTAL): |
|
|
|
target = QRectF(pos.x()+(self.p_width*octave), 0, pos.width(), pos.height()) |
|
|
|
source = QRectF(pos.x(), 0, pos.width(), pos.height()) |
|
|
|
elif (self.m_pixmap_mode == self.VERTICAL): |
|
|
|
target = QRectF(pos.x(), pos.y()+(self.p_height*octave), pos.width(), pos.height()) |
|
|
|
source = QRectF(0, pos.y(), pos.width(), pos.height()) |
|
|
|
else: |
|
|
|
return |
|
|
|
|
|
|
|
painter.drawPixmap(target, self.m_pixmap, source) |
|
|
|
|
|
|
|
# ------------------------------------------------------------- |
|
|
|
# Paint (black) pressed keys |
|
|
|
|
|
|
|
for i in range(len(self.m_enabledKeys)): |
|
|
|
note = self.m_enabledKeys[i] |
|
|
|
pos = self._getRectFromMidiNote(note) |
|
|
|
|
|
|
|
if (self._isNoteBlack(note) == False): |
|
|
|
continue |
|
|
|
|
|
|
|
if (note < 35): |
|
|
|
# cannot paint this note |
|
|
|
continue |
|
|
|
elif (note < 48): |
|
|
|
octave = 0 |
|
|
|
elif (note < 60): |
|
|
|
octave = 1 |
|
|
|
elif (note < 72): |
|
|
|
octave = 2 |
|
|
|
elif (note < 84): |
|
|
|
octave = 3 |
|
|
|
elif (note < 96): |
|
|
|
octave = 4 |
|
|
|
elif (note < 108): |
|
|
|
octave = 5 |
|
|
|
else: |
|
|
|
# cannot paint this note either |
|
|
|
continue |
|
|
|
|
|
|
|
if (self.m_pixmap_mode == self.VERTICAL): |
|
|
|
octave = self.m_octaves - octave - 1 |
|
|
|
|
|
|
|
if (self.m_pixmap_mode == self.HORIZONTAL): |
|
|
|
target = QRectF(pos.x()+(self.p_width*octave), 0, pos.width(), pos.height()) |
|
|
|
source = QRectF(pos.x(), self.p_height, pos.width(), pos.height()) |
|
|
|
elif (self.m_pixmap_mode == self.VERTICAL): |
|
|
|
target = QRectF(pos.x(), pos.y()+(self.p_height*octave), pos.width(), pos.height()) |
|
|
|
source = QRectF(self.p_width, pos.y(), pos.width(), pos.height()) |
|
|
|
else: |
|
|
|
return |
|
|
|
|
|
|
|
painter.drawPixmap(target, self.m_pixmap, source) |
|
|
|
|
|
|
|
# Paint C-number note info |
|
|
|
painter.setFont(self.m_font) |
|
|
|
painter.setPen(Qt.black) |
|
|
|
for i in range(self.m_octaves): |
|
|
|
if (self.m_pixmap_mode == self.HORIZONTAL): |
|
|
|
painter.drawText(i*144, 48, 18, 18, Qt.AlignCenter, "C%s" % (i+2)) |
|
|
|
elif (self.m_pixmap_mode == self.VERTICAL): |
|
|
|
painter.drawText(45, (self.m_octaves*144)-(i*144)-16, 18, 18, Qt.AlignCenter, "C%s" % (i+2)) |