Very quick and dirty, but enough to show what key is being pressed. ```py import sys from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * from rtmidi import MidiIn args = sys.argv[1:] if len(args) == 0: pat = "Through Port-3" else: pat = args[0] white_notes = [ 0, 2, 4, 5, 7, 9, 11 ] black_notes = [ i for i in range(12) if not i in white_notes ] class Midi: def __init__(self,kb,pat=pat): self.midi_in = MidiIn() self.kb = kb ports = self.midi_in.get_ports() for i,x in enumerate(ports): if pat in x: self.port = i break else: print(f"Can't find midi port matching '{pat}'") exit(1) print(f"Port {i=} {x=}") self.midi_in.set_callback(self) self.midi_in.open_port(self.port) def __del__(self): self.midi_in.close_port() def __call__(self,x,y): data, t = x print(f"recv {data=}") if len(data) > 0: status = data[0]&0xf0 if status == 0x90: # noteon a,p,v = data if v > 0: self.kb.on(p) else: self.kb.off(p) elif status == 0x80: # noteoff a,p,v = data self.kb.off(p) class K(QWidget): def __init__(self,*xs,lo=25,hi=108,**kw): super().__init__(*xs,**kw) self.setRange(lo,hi) self.noteson = set() def on(self,p): print(f"on {p=}") self.noteson.add(p) print(self.noteson) self.update() def off(self,p): print(f"off {p=}") if p in self.noteson: self.noteson.remove(p) print(self.noteson) self.update() def setRange(self,lo=21,hi=108): # expand range if needed so that top and bottom notes are white if lo % 12 in black_notes: lo -= 1 if hi % 12 in black_notes: hi += 1 if hi - lo < 4: raise ValueError("Keyboard range too small") self.lo = lo self.hi = hi nw = 0 nb = 0 for i in range(lo,hi+1): if i % 12 in white_notes: nw += 1 else: nb += 1 self.nw = nw self.nb = nb self.calcRects() def calcRects(self): self.wrects = [] self.brects = [] w = 1/self.nw r = 0 for i,p in enumerate(range(self.lo,self.hi+1)): if p % 12 in white_notes: self.wrects.append((p,r,r+w)) r += w else: self.brects.append((p,r-w/3,r+w/3)) #print(self.wrects) def paintEvent(self,e): num_wnotes = self.nw rect = self.rect() w = rect.width() h = rect.height() bh = int(h * 3/5) # we need to identify exactly what rectangle is what. # since we need to light them up when played. with QPainter(self) as qp: qp.fillRect(rect,Qt.white) qp.setPen(QPen(Qt.black,2)) for nr in self.wrects: p,l,r = nr if p in self.noteson: qp.setBrush(Qt.red) else: qp.setBrush(Qt.white) l *= w r *= w l = int(l) r = int(r) dx = r-l qp.drawRect(l,0,dx,h) for nr in self.brects: p,l,r = nr if p in self.noteson: qp.setBrush(Qt.green) else: qp.setBrush(Qt.black) l *= w r *= w l = int(l) r = int(r) dx = r-l qp.drawRect(l,0,dx,bh) app = QApplication([]) k = K() k.resize(880,50) k.show() m = Midi(k) exit(app.exec()) ``` # OSC ## OSC Version This listens for OSC messages of the form `/on 45` and `/off 75` rather than listening to a MIDI port. This is so that the keyboard display works on devices over the network. ## Midi Forward This is the counterpart to the above `keymon-osc.py`. It listens to a midi port and forwards OSC messages. For now it only handles noteon and noteoff, and disregards channel, since for the keyboard monitor example, that is all that's required.