Dup Ver Goto 📝

MidiKeyboardMonitor_001

PT2/music/midi/python/rtmidi does not exist
To
153 lines, 491 words, 3804 chars Page 'MidiKeyboardMonitor_001' does not exist.

Very quick and dirty, but enough to show what key is being pressed.

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.