Dup Ver Goto 📝

Launchpad to OSC 001

DAW/python/osc python osc launchpad does not exist
To
230 lines, 571 words, 5805 chars Page 'LaunchPad_to_OSC_001' does not exist.

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)