So if I close the TourBox Console, I can access the TourBox as a simple usb-serial device. The only issue is finding the correct port. It seems to be `COM5` on Windows, or `/dev/ttyS4` under cygwin, and `/dev/ttyACM?` under Linux, and `/dev/tty...something-weird-about-usb-modem` on a mac. Once you know that, here's a simple script that turns tourbox events into OSC which you can target to an application. Obviously, if you are familiar with python, you can do things way more complicated than this. So I turn the knob, and it sends ``` tour/knob/cw ``` to wherever. I don't have bank switching like I do with my nocturn script. Though I could easily send ``` tour/0/0/knob/cw ``` instead, using the `0/0` bit to specify which bank of 'virtual tourboxes' the event is coming from. This bank switching will be a common thread in what I'm doing. The other issue is finding a decent way to indicate what is mapped to what button. In Reaper, the OSC bindings are stored in ``` %reaperres%/OSC/reaper-osc-actions.ini ``` where `%reaperres%` is your reaper resource folder (e.g. `%APPDATA%/REAPER` on Windows). ## My first TourBox→OSC script ```python #!/usr/bin/env python import serial # python -m pip install pyserial import time from pythonosc import udp_client # python -m pip install python-osc import random import time import sys import os import platform import socket def get_ip(): # workaround since reaper doesn't receive packets if sent to localhost rather than 192.168.0.x s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.settimeout(0) try: # doesn't even have to be reachable s.connect(('10.254.254.254', 1)) IP = s.getsockname()[0] except Exception: IP = '127.0.0.1' finally: s.close() return IP port = os.getenv("port",os.getenv("PORT",9000)) try: port = int(port) except ValueError: print(f"Port '{port}' must be an integer") exit(1) host = os.getenv("host",os.getenv("HOST",get_ip())) print(f"{host=} {port=}") # xxd /dev/ttyACM3 def main(): global client client = udp_client.SimpleUDPClient(host,port) com_port = os.getenv("com_port",None) if com_port is None: if platform.system() == "Linux": com_port = "/dev/ttyACM0" elif platform.system() == "Darwin": com_port = "weird" elif "cygwin" in platform.system().lower(): com_port = "/dev/ttyS4" elif platform.system() == "Windows": com_port = "COM5:" else: print(f"Don't support {platform.system()}") exit(2) with serial.Serial(com_port,19200) as ser: try: while True: x = ser.read() y = ord(x) print(f"{y:02x}") handle(y) except KeyboardInterrupt: print(f"Ctrl-c") exit() def button(n,dirn): if dirn == 0: action(f"button_{n:02x}") def rotary(n,dirn): if dirn > 0: return action('rotary_{n:02x}_{dirn}') def action(x,args=[]): print(x,args) client.send_message(f"tour/{x}",args) knobs = { 0x04: "knob", 0x0f: "dial", 0x09: "vdial" } dpad = { 0x10: "up", 0x11: "down", 0x12: "left", 0x13: "right" } def handle(x): print(f"{x=}") dirn = x >> 7 x &= 0x7F kdirn = x >> 4 x &= 0x3f if x in knobs: if dirn > 0: return action(f"{knobs[x]}/"+("cw" if kdirn > 0 else "ccw")) elif x in dpad: if dirn > 0: return action(f"dpad/{dpad[x]}") else: if dirn > 0: return action(f"button/{x:02x}") if __name__ == "__main__": main() ```