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
#!/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]} <in pattern> <out pattern> # 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
#!/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]} <in pattern> # 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
#!/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]} <out pattern> 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()