Launchpad to OSC 001
This is a simple script I wrote to send OSC to Reaper triggered by the pads on a mark 1 Novation Launchpad.
The top and side buttons select one of 64 pages. Then each pad generates an OSC message
in a simple /lp/page23/but43 schema.
The Program
#!/usr/bin/env python3
import sys
from time import sleep
from pythonosc import udp_client
from midi_device import MidiDevice
from lp_device import LpDevice
try:
from setproctitle import setproctitle
setproctitle(sys.argv[0].split("/")[-1])
except ModuleNotFoundError:
pass
class LpOsc(LpDevice):
def set_target(self,host="127.0.0.1",port=9000):
self.host = host
self.port = port
self.client = udp_client.SimpleUDPClient(host, port)
def setup(self):
# OSC
self.host = None
self.port = None
self.client = None
# State
self.page_row = 0
self.page_col = 0
self.update_top()
self.update_side()
self.update_lower()
def update_top(self):
for i in range(8):
if i == self.page_col:
self.set_tbut(i,3,3)
else:
self.set_tbut(i,0,0)
def update_side(self):
for i in range(8):
if i == self.page_row:
self.set_rbut(i,3,3)
else:
self.set_rbut(i,0,0)
def update_lower(self):
for i in range(8):
for j in range(8):
self.set_but(i,j,0,0)
def tbut(self,c,v):
if v > 0:
print(f"tbut {c=}")
self.page_col = c
self.update_top()
def rbut(self,r,v):
if v > 0:
print(f"rbut {r=}")
self.page_row = r
self.update_side()
def but(self,r,c,v):
if v == 0:
return
print(f"but {r=} {c=}")
pr = self.page_row
pc = self.page_col
addr = f"lp/page{pr}{pc}/but{r}{c}"
params = [1]
if self.client is None:
print("no target")
return
print("send",addr," ".join(map(str,params)))
self.client.send_message(addr,params)
midi_device = MidiDevice(pattern="Launchpad")
lp = LpOsc(midi_device)
lp.set_target("192.168.1.4",9000)
try:
while True:
sleep(1)
except KeyboardInterrupt:
print("Ctrl-C")
except:
print("Other Exception")
lp.set_all(0,0)
midi_device.close_ports()
Support Code
midi_device.py manages a MIDI device
from rtmidi import MidiOut, MidiIn
from rtmidi._rtmidi import SystemError as RtMidiSystemError
class MidiDevice:
def __init__(self,input_port=None,output_port=None,pattern=None,index=0):
self.midi_in = MidiIn()
self.midi_out = MidiOut()
self.input_port = input_port
self.output_port = output_port
self.input_open = False
self.output_open = False
self.set_callback(None)
input_port_candidates = []
output_port_candidates = []
if pattern is not None:
for i,x in enumerate(self.midi_in.get_ports()):
if pattern in x:
input_port_candidates.append(i)
break
for i,x in enumerate(self.midi_out.get_ports()):
if pattern in x:
output_port_candidates.append(i)
break
if not input_port:
self.input_port = input_port_candidates[index]
if not output_port:
self.output_port = output_port_candidates[index]
def open(self):
if not self.input_open and self.input_port is not None:
self.midi_in.open_port(self.input_port)
self.input_open = True
if not self.output_open and self.output_port is not None:
self.midi_out.open_port(self.output_port)
self.output_open = True
def __del__(self):
self.close_ports()
def close_ports(self):
if self.input_open:
self.midi_in.close_port()
if self.output_open:
self.midi_out.close_port()
def set_callback(self,callback):
self.midi_in.set_callback(callback)
def send_message(self,msg):
try:
self.midi_out.send_message(msg)
except RtMidiSystemError as e:
print("send_message",repr(e))
def cc(self,n,v):
msg = bytes([0xB0,n,v])
self.send_message(msg)
def on(self,p,v):
msg = bytes([0x90,p,v])
self.send_message(msg)
def off(self,p,v=0):
msg = bytes([0x90,p,0])
self.send_message(msg)
lp_device.py manages a Launchpad, using MidiDevice to talk to the hardware
class LpDevice:
def __init__(self,device):
self.device = device
device.set_callback(self.midi_callback)
delegate = None
device.open()
self.setup()
def set_delegate(self,delegate):
self.delegate = delegate
def make_color(self,idx,br):
idx = idx & 3
br = br & 3
r = (idx&1) * br
g = ((idx&2)>>1) * br
g <<= 4
#print(f"color {idx=} {br=} {r:b=} {g:b=}")
return r | g
def midi_callback(self,x,y):
msg,t = x
#ic("midi",msg)
if msg[0] & 0xF0 == 0xB0:
#ic("top")
n,v = msg[1:3]
n -= 0x68
self.tbut(n,v)
elif msg[0] & 0xF0 == 0x90:
#ic("rest of pads")
n,v = msg[1:3]
row = (n>>4)
col = (n&0xf)
if col < 8:
self.but(row,col,v)
else:
self.rbut(row,v)
def setup(self):
pass
def tbut(self,c,v):
if self.delegate is not None:
return self.delegate.tbut(c,v)
def rbut(self,r,v):
if self.delegate is not None:
return self.delegate.rbut(r,v)
def but(self,r,c,v):
if self.delegate is not None:
return self.delegate.but(r,c,v)
def set_all(self,idx,br):
for i in range(8):
self.set_tbut(i,idx,br)
self.set_rbut(i,idx,br)
for j in range(8):
self.set_but(i,j,idx,br)
def set_but(self,r,c,idx,br):
color = self.make_color(idx,br)
n = (r << 4) | c
self.device.on(n,color)
def set_tbut(self,c,idx,br):
color = self.make_color(idx,br)
n = 0x68 + c
self.device.cc(n,color)
def set_rbut(self,r,idx,br):
color = self.make_color(idx,br)
return self.set_but(r,8,idx,br)