Skip to content

Instantly share code, notes, and snippets.

@radixvinni
Last active December 20, 2015 20:56
Show Gist options
  • Save radixvinni/d6b52677c0ee2e66bee0 to your computer and use it in GitHub Desktop.
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
#!/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