Last active
December 20, 2015 20:56
-
-
Save radixvinni/d6b52677c0ee2e66bee0 to your computer and use it in GitHub Desktop.
virtual MIDI keyboard on PyQt4. To run this use commands: jack_control start && timidity -iA -Oj -B2,8
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
"virtual MIDI keyboard on PyQt4. To run this use commands: jack_control start && timidity -iA -Oj -B2,8" | |
from PyQt4.QtGui import QGridLayout, QHBoxLayout, QLineEdit, QPushButton, QSizePolicy, QVBoxLayout, QWidget | |
from PyQt4.QtCore import QSize, SIGNAL, QEvent | |
# this app will list all MIDI devices and associated numbers | |
# Pick the correct number associated, and restart the app. | |
MIDI_DEVICE = 1 | |
class InputState: | |
LOWER = 0 | |
CAPITAL = 1 | |
class KeyButton(QPushButton): | |
def __init__(self, key): | |
super(KeyButton, self).__init__() | |
self._key = key | |
self._activeSize = QSize(50,50) | |
self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) | |
def mousePressEvent(self, event): | |
self.emit(SIGNAL("sigKeyButtonClicked"), self._key) | |
def mouseReleaseEvent(self, event): | |
self.emit(SIGNAL("sigKeyButtonClicked"), "") | |
def enterEvent(self, event): | |
self.setFixedSize(self._activeSize) | |
def leaveEvent(self, event): | |
self.setFixedSize(self.sizeHint()) | |
def sizeHint(self): | |
return QSize(40, 40) | |
class NoteEdit(QLineEdit): | |
def __init__(self, main, midi): | |
self.midi = midi | |
self.main = main | |
self.pressed = set() | |
QLineEdit.__init__(self) | |
def event(self, event): | |
if (event.type() == QEvent.KeyPress): | |
if 90 >= event.key() >= 65: | |
if event.key() in self.pressed: return False | |
self.pressed.add(event.key()) | |
midi.send_message([0x90, event.key(), 100]) | |
self.main.getButtonByKey(str(event.text())).enterEvent(event) | |
self.main.inputString += (str(event.text()).lower(), str(event.text()).capitalize())[self.main.state] | |
elif (event.type() == QEvent.KeyRelease): | |
if 90 >= event.key() >= 65: | |
if event.isAutoRepeat(): return False | |
self.pressed.remove(event.key()) | |
midi.send_message([0x80, event.key(), 100]) | |
self.main.getButtonByKey(str(event.text())).leaveEvent(event) | |
else: return QLineEdit.event(self, event) | |
return True | |
class VirtualKeyboard(QWidget): | |
def __init__(self, midi): | |
super(VirtualKeyboard, self).__init__() | |
self.midi = midi | |
self.globalLayout = QVBoxLayout(self) | |
self.keysLayout = QGridLayout() | |
self.buttonLayout = QHBoxLayout() | |
self.keyListByLines = [ | |
['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'], | |
['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';'], | |
['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/'], | |
] | |
self.inputString = "" | |
self.state = InputState.LOWER | |
self.stateButton = QPushButton() | |
self.stateButton.setText('Note') | |
self.stateButton.setCheckable(True) | |
self.backButton = QPushButton() | |
self.backButton.setText('<-') | |
self.okButton = QPushButton() | |
self.okButton.setText('OK') | |
self.cancelButton = QPushButton() | |
self.cancelButton.setText("Cancel") | |
self.inputLine = NoteEdit(self, midi) | |
for lineIndex, line in enumerate(self.keyListByLines): | |
for keyIndex, key in enumerate(line): | |
buttonName = "keyButton" + key.capitalize() | |
self.__setattr__(buttonName, KeyButton(key)) | |
self.keysLayout.addWidget(self.getButtonByKey(key), self.keyListByLines.index(line), line.index(key)) | |
self.getButtonByKey(key).setText(key) | |
self.connect(self.getButtonByKey(key), SIGNAL("sigKeyButtonClicked"), self.addInputByKey) | |
self.keysLayout.setColumnMinimumWidth(keyIndex, 50) | |
self.keysLayout.setRowMinimumHeight(lineIndex, 50) | |
self.connect(self.stateButton, SIGNAL("clicked()"), self.switchState) | |
self.connect(self.backButton, SIGNAL("clicked()"), self.backspace) | |
self.connect(self.okButton, SIGNAL("clicked()"), self.emitInputString) | |
self.connect(self.cancelButton, SIGNAL("clicked()"), self.emitCancel) | |
self.buttonLayout.addWidget(self.cancelButton) | |
self.buttonLayout.addWidget(self.backButton) | |
self.buttonLayout.addWidget(self.stateButton) | |
self.buttonLayout.addWidget(self.okButton) | |
self.globalLayout.addWidget(self.inputLine) | |
self.globalLayout.addLayout(self.keysLayout) | |
self.globalLayout.addLayout(self.buttonLayout) | |
self.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) | |
def getButtonByKey(self, key): | |
return getattr(self, "keyButton" + key.capitalize()) | |
def getLineForButtonByKey(self, key): | |
return [key in keyList for keyList in self.keyListByLines].index(True) | |
def switchState(self): | |
self.state = not self.state | |
def addInputByKey(self, key): | |
if key: | |
self.midi.send_message([0x90, ord(key)-32, 100]) | |
self.key=key | |
else: | |
self.midi.send_message([0x80, ord(self.key)-32, 100]) | |
self.inputString += (key.lower(), key.capitalize())[self.state] | |
self.inputLine.setFocus() | |
def backspace(self): | |
self.inputLine.setText("") | |
self.inputString = "" | |
def emitInputString(self): | |
self.emit(SIGNAL("sigInputString"), self.inputString) | |
def emitCancel(self): | |
self.emit(SIGNAL("sigInputString"), "") | |
def sizeHint(self): | |
return QSize(480, 272) | |
if __name__ == '__main__': | |
import rtmidi | |
print "Available MIDI devices:" | |
midi = rtmidi.MidiOut() | |
for i, port in enumerate(midi.get_ports()): | |
print str(i) + ": " + port | |
print "Connecting to device: " + midi.get_ports()[MIDI_DEVICE] | |
midi.open_port(MIDI_DEVICE) | |
def printText(text): | |
print text | |
import sys | |
from PyQt4.QtGui import QApplication | |
app = QApplication(sys.argv) | |
win = VirtualKeyboard(midi) | |
app.connect(win, SIGNAL("sigInputString"), printText) | |
win.show() | |
app.exec_() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment