tags: python osc launchpad title: Launchpad to OSC 001 h1: 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 ```py #!/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 ```py 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 ```py 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) ```