Browse Source

Add pixmapkeyboard, fixed and adapted to python3

tags/v0.9.0
falkTX 13 years ago
parent
commit
afd6f6bd62
4 changed files with 514 additions and 1 deletions
  1. +14
    -0
      src/jacklib_helpers.py
  2. +1
    -1
      src/logs.py
  3. +406
    -0
      src/pixmapkeyboard.py
  4. +93
    -0
      src/shared.py

+ 14
- 0
src/jacklib_helpers.py View File

@@ -76,3 +76,17 @@ def c_char_p_p_to_list(c_char_p_p):
# C cast void* -> jack_default_audio_sample_t*
def translate_audio_port_buffer(void_p):
return jacklib.cast(void_p, jacklib.POINTER(jacklib.jack_default_audio_sample_t))

def translate_midi_event_buffer(void_p, size):
if (not void_p):
return list()
elif (size == 1):
return (void_p[0],)
elif (size == 2):
return (void_p[0], void_p[1])
elif (size == 3):
return (void_p[0], void_p[1], void_p[2])
elif (size == 4):
return (void_p[0], void_p[1], void_p[2], void_p[3])
else:
return list()

+ 1
- 1
src/logs.py View File

@@ -347,7 +347,7 @@ class LogsW(QDialog, ui_logs.Ui_LogsW):

def closeEvent(self, event):
self.m_readThread.quit()
return QDialog.closeEvent(self, event)
QDialog.closeEvent(self, event)

# -------------------------------------------------------------
# Allow to use this as a standalone app


+ 406
- 0
src/pixmapkeyboard.py View File

@@ -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))

+ 93
- 0
src/shared.py View File

@@ -58,6 +58,99 @@ else:
PATH = PATH_env.split(os.pathsep)
del PATH_env

MIDI_CC_LIST = (
#"0x00 Bank Select",
"0x01 Modulation",
"0x02 Breath",
"0x03 (Undefined)",
"0x04 Foot",
"0x05 Portamento",
#"0x06 (Data Entry MSB)",
"0x07 Volume",
"0x08 Balance",
"0x09 (Undefined)",
"0x0A Pan",
"0x0B Expression",
"0x0C FX Control 1",
"0x0D FX Control 2",
"0x0E (Undefined)",
"0x0F (Undefined)",
"0x10 General Purpose 1",
"0x11 General Purpose 2",
"0x12 General Purpose 3",
"0x13 General Purpose 4",
"0x14 (Undefined)",
"0x15 (Undefined)",
"0x16 (Undefined)",
"0x17 (Undefined)",
"0x18 (Undefined)",
"0x19 (Undefined)",
"0x1A (Undefined)",
"0x1B (Undefined)",
"0x1C (Undefined)",
"0x1D (Undefined)",
"0x1E (Undefined)",
"0x1F (Undefined)",
#"0x20 *Bank Select",
#"0x21 *Modulation",
#"0x22 *Breath",
#"0x23 *(Undefined)",
#"0x24 *Foot",
#"0x25 *Portamento",
#"0x26 *(Data Entry MSB)",
#"0x27 *Volume",
#"0x28 *Balance",
#"0x29 *(Undefined)",
#"0x2A *Pan",
#"0x2B *Expression",
#"0x2C *FX *Control 1",
#"0x2D *FX *Control 2",
#"0x2E *(Undefined)",
#"0x2F *(Undefined)",
#"0x30 *General Purpose 1",
#"0x31 *General Purpose 2",
#"0x32 *General Purpose 3",
#"0x33 *General Purpose 4",
#"0x34 *(Undefined)",
#"0x35 *(Undefined)",
#"0x36 *(Undefined)",
#"0x37 *(Undefined)",
#"0x38 *(Undefined)",
#"0x39 *(Undefined)",
#"0x3A *(Undefined)",
#"0x3B *(Undefined)",
#"0x3C *(Undefined)",
#"0x3D *(Undefined)",
#"0x3E *(Undefined)",
#"0x3F *(Undefined)",
#"0x40 Damper On/Off", # <63 off, >64 on
#"0x41 Portamento On/Off", # <63 off, >64 on
#"0x42 Sostenuto On/Off", # <63 off, >64 on
#"0x43 Soft Pedal On/Off", # <63 off, >64 on
#"0x44 Legato Footswitch", # <63 Normal, >64 Legato
#"0x45 Hold 2", # <63 off, >64 on
"0x46 Control 1 [Variation]",
"0x47 Control 2 [Timbre]",
"0x48 Control 3 [Release]",
"0x49 Control 4 [Attack]",
"0x4A Control 5 [Brightness]",
"0x4B Control 6 [Decay]",
"0x4C Control 7 [Vib Rate]",
"0x4D Control 8 [Vib Depth]",
"0x4E Control 9 [Vib Delay]",
"0x4F Control 10 [Undefined]",
"0x50 General Purpose 5",
"0x51 General Purpose 6",
"0x52 General Purpose 8",
"0x53 General Purpose 9",
"0x54 Portamento Control",
"0x5B FX 1 Depth [Reverb]",
"0x5C FX 2 Depth [Tremolo]",
"0x5D FX 3 Depth [Chorus]",
"0x5E FX 4 Depth [Detune]",
"0x5F FX 5 Depth [Phaser]"
)

# Get Icon from user theme, using our own as backup (Oxygen)
def getIcon(icon, size=16):
return QIcon.fromTheme(icon, QIcon(":/%ix%i/%s.png" % (size, size, icon)))


Loading…
Cancel
Save