A very quick and dirty script to map the white keys to a chosen major/minor scale. I've not tried out out with a real keyboard yet, just tested by sending a few note messages into it. # mapper ```py #!/usr/bin/env python from icecream import ic from rtmidi import MidiIn, MidiOut import time whitel = [0,2,4,5,7,9,11] whites = set(whitel) whited = { x:i for i,x in enumerate(whitel) } major = [0,2,4,5,7,9,11] minor = [0,2,3,5,7,8,10] class Mapper: def __init__(self,inport,outport): self.midiin = MidiIn() self.midiout = MidiOut() self.root = 0 self.select = None self.scale = major self.isminor = False self.midiin.set_callback(self.midi_callback) self.midiout.open_port(outport) self.midiin.open_port(inport) def close(self): self.midiin.close_port() self.midiout.close_port() def midi_callback(self,x,y): msg,dt = x status = msg[0] & 0xF0 ch = status & 0x0F match status: case 0x80: p = msg[1] return self.key(p,0,ch) case 0x90: p,v = msg[1:3] return self.key(p,v,ch) case _: return passthru(msg) def passthru(self,msg): self.midiout.send_message(msg) def key(self,p,v=100,ch=0): if p % 12 in whites: self.whitekey(p,v) else: self.blackkey(p,v) def blackkey(self,p,v,ch=0): p %= 12 match p: case 1: # C# ic("select major") self.select = "major" self.scale = major self.isminor = False case 3: ic("select minor") self.select = "minor" self.scale = minor self.isminor = True case _: self.select = None def whitekey(self,p,v,ch=0): a = p % 12 o = p // 12 match self.select: case "major": self.root = a self.select = None case "minor": self.root = (a + 3) % 12 self.select = None case None: return self.mapwhitekey(p,v) # turn note offs into note ons with velocity 0 and discard note off velocity def mapwhitekey(self,p,v,ch=0): #ic(p,v,self.root) a = p % 12 o = p // 12 b = whited[a] c = self.root + self.scale[b] if self.isminor: c -= 3 c += 12*o self.output(c,v,ch) def output(self,p,v,ch): status = (ch & 0x0F) | 0x90 msg = bytes((status,p,v)) ic(p,v) self.midiout.send_message(msg) def findin(pat): pat = pat.lower() m = MidiIn() xs = [] for i,x in enumerate(m.get_ports()): if pat in x.lower(): print(f"Found {i}: {x}") xs.append(i) return xs def findout(pat): pat = pat.lower() m = MidiOut() xs = [] for i,x in enumerate(m.get_ports()): if pat in x.lower(): print(f"Found {i}: {x}") xs.append(i) return xs import sys args = sys.argv[1:] if len(args) == 0: m = MidiIn() print("Inputs") for i,x in enumerate(m.get_ports()): print(i,x) print("Outputs") m = MidiOut() for i,x in enumerate(m.get_ports()): print(i,x) exit(0) try: inpat,outpat = args[:2] except Exception: print(f"{sys.argv[0]} # with no arguments lists midi ports") print(f"{sys.argv[0]} # runs the mapper") exit(1) ins = findin(inpat) outs = findout(outpat) if len(ins) == 0: print(f"Found no input") exit(2) if len(outs) == 0: print(f"Found no output") exit(3) m = Mapper(ins[0],outs[0]) try: while True: time.sleep(1) except KeyboardInterrupt: print(f"Ctrl-C") m.close() exit(0) ``` # recv ```py #!/usr/bin/env python from icecream import ic from rtmidi import MidiIn, MidiOut import time def midi_callback(x,y): msg,dt = x ic(msg) def findin(pat): pat = pat.lower() m = MidiIn() xs = [] for i,x in enumerate(m.get_ports()): if pat in x.lower(): print(f"Found {i}: {x}") xs.append(i) return xs import sys args = sys.argv[1:] if len(args) == 0: m = MidiIn() print("Inputs") for i,x in enumerate(m.get_ports()): print(i,x) exit(0) try: inpat = args[0] except Exception: print(f"{sys.argv[0]} # with no arguments lists midi ports") print(f"{sys.argv[0]} # runs the mapper") exit(1) ins = findin(inpat) if len(ins) == 0: print(f"Found no input") exit(2) i = ins[0] m = MidiIn() m.set_callback(midi_callback) m.open_port(i) try: while True: time.sleep(1) except KeyboardInterrupt: print(f"Ctrl-C") m.close_port() exit(0) ``` # send ```py #!/usr/bin/env python from icecream import ic from rtmidi import MidiIn, MidiOut import time def findin(pat): pat = pat.lower() m = MidiIn() xs = [] for i,x in enumerate(m.get_ports()): if pat in x.lower(): print(f"Found {i}: {x}") xs.append(i) return xs def findout(pat): pat = pat.lower() m = MidiOut() xs = [] for i,x in enumerate(m.get_ports()): if pat in x.lower(): print(f"Found {i}: {x}") xs.append(i) return xs import sys args = sys.argv[1:] if len(args) == 0: print("Outputs") m = MidiOut() for i,x in enumerate(m.get_ports()): print(i,x) exit(0) try: outpat,*ps = args ps = map(int,ps) except Exception: print(f"{sys.argv[0]} # with no arguments lists midi ports") print(f"{sys.argv[0]} pitches # runs the mapper") exit(1) outs = findout(outpat) if len(outs) == 0: print(f"Found no output") exit(3) o = outs[0] m = MidiOut() m.open_port(o) for p in ps: ic("Sending",p) msg = bytes((0x90,p&127,100)) m.send_message(msg) time.sleep(0.1) msg = bytes((0x90,p&127,0)) m.send_message(msg) time.sleep(0.1) m.close_port() ```