A tool to allow users of the Behringer Xtouch to remap and assign controls arbitrarily.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
xtouchctl/scripts/xtouchctl.py

208 lines
7.2 KiB

#!/usr/bin/env python3
# XTouch Control Station
# This main script handles the I/O for the XTouch itself
# Libraries used
import mido
import jack
import time
# My own libraries and definitions
# The message to map functions connect up inputs with actions
from msg2map import *
from obshandler import *
# Headers define constants for messages and values used in the XTouch
from xtheaders import *
# This creates a Jack client, to receive and send MIDI commands
client = jack.Client('XTouchCTL')
midiInPort = client.midi_inports.register('midi_in')
midiOutPort = client.midi_outports.register('midi_out')
client.connect('XTouchCTL:midi_out', 'Midi-Bridge:X-Touch:(playback_0) X-Touch X-TOUCH_INT')
client.connect('Midi-Bridge:X-Touch:(capture_0) X-Touch X-TOUCH_INT', 'XTouchCTL:midi_in')
# clear the (Outgoing) message ready flag
msgReady = 0
msgOut = []
class Xtlayer:
"""This class stores an Xtouch snapshot of fader positions and LED indicators for encoders and buttons"""
# Items to be added for scribble strips and LED Segment display
def __init__(self, layer):
# Layer ID
self.layer = layer
# Fader positions default to 0dB
self.faders = [fad_zero, fad_zero, fad_zero, fad_zero, fad_zero, fad_zero, fad_zero, fad_zero, fad_zero]
# Encoder position indicator (bitmap) default to single centre line
self.encoder_ind = [rot_led_c0, rot_led_c0, rot_led_c0, rot_led_c0, rot_led_c0, rot_led_c0, rot_led_c0, rot_led_c0]
# Button LED states (by group) most default off
# Channel strips
self.rec = [but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off]
self.solo = [but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off]
self.mute = [but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off]
self.select = [but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off]
# Encoder Assign - pan set on by default (enc_assign(1))
self.enc_assign = [but_led_off, but_led_on, but_led_off, but_led_off, but_led_off, but_led_off]
# View Assign - Global View is on by default (view_assign(0))
self.view_assign = [but_led_on, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off]
# Function keys
self.fkeys = [but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off]
# Modifiers - Flip, Shift, Option, Control & Alt
self.modkeys = [but_led_off, but_led_off, but_led_off, but_led_off, but_led_off]
# Automation - Read/Off, Write, Trim, Touch, Latch & Group
self.autokeys = [but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off]
# Utility - Save, Undo, Cancel & Enter
self.utilkeys = [but_led_off, but_led_off, but_led_off, but_led_off]
# Transport section - Marker, Nudge, Cycle, Drop, Replace, Click, Solo, Rew, Fwd, Stop, Play Rec & Scrub
self.transkeys = [but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off, but_led_off]
# Bank switching - Fader Bank L & R and Channel L & R
self.bankswitch = [but_led_off, but_led_off, but_led_off, but_led_off]
# Cursor - Up, Down, Left, Right & Centre (Search)
self.cursor = [but_led_off, but_led_off, but_led_off, but_led_off, but_led_off]
# Display selectors - Display/Name/Value & SMPTE/Beats
self.display = [but_led_off, but_led_off]
# read and write midi messages in the background
@client.set_process_callback
def process(frames):
global msgReady
global msgOut
for offset, data in midiInPort.incoming_midi_events():
# print('OutMsgReady =', msgReady)
# Change data into a byte stream
parsedBytes = bytes(data)
# Change the byte stream to a formatted midi message
msgIn = mido.parse(parsedBytes)
handleMessage(msgIn)
# If there is an outgoing message ready then send it
if msgReady == 1:
midiOutPort.clear_buffer()
message = msgOut.bytes()
midiOutPort.write_midi_event(offset, message)
msgReady = 0
# Input Function
def handleMessage(msgIn):
# Incoming MIDI message handler
# print('MIDI message: ', msgIn)
if msgIn.type == 'sysex':
handleSysex(msgIn.data)
elif msgIn.type == 'note_on':
handleNoteOn(msgIn.note, msgIn.velocity)
elif msgIn.type == 'note_off':
handleNoteOff(msgIn.note, msgIn.velocity)
elif msgIn.type == 'pitchwheel':
handlePWh(msgIn.channel, msgIn.pitch)
elif msgIn.type == 'control_change':
handleCC(msgIn.channel, msgIn.control, msgIn.value)
else:
print('Message :', msgIn, ' is unhandled')
# Output function
def setOutMessage(msg):
global msgOut
global msgReady
msgOut = msg
msgReady = 1
# print('Outgoing message :', msgOut)
def handleSysex(data):
if data == alive:
# print('Sysex message: Alive!')
# Respond with the sysex message to the XTouch to keep connection alive
heartbeat_msg = mido.Message(hb_msg_type, data=heartbeat)
# print('heartbeat', heartbeat_msg)
setOutMessage(heartbeat_msg)
else:
# The heartbeat is the only incoming Sysex message we care about at present
pass
def handleNoteOn(note, velocity):
print('Note On Data :', note, velocity)
led_on(note)
def handleNoteOff(note, velocity):
print('Note Off Data :', note, velocity)
led_off(note)
def handlePWh(channel, pitch):
print('Pitchwheel Data :', channel, pitch)
def handleCC(channel, control, value):
print('Control Change Data :', channel, control, value)
# Output functions
def setScribbleStrip(channel, colour, line_1, line_2):
# Scribble strips colour and text are set with a sysex message
# Made up of a header, channel and then the data
msg = mido.Message(scrstr_msgtype, data=(0, 0, 102, 88, 32, 1, 84, 101, 115, 116, 0, 0, 0, 32, 32, 32, 32, 32, 32, 33))
setOutMessage(msg)
def led_on(button):
msg = mido.Message('note_on', channel=0, note=button, velocity=but_led_on)
setOutMessage(msg)
def led_off(button):
msg = mido.Message('note_on', channel=0, note=button, velocity=but_led_off)
setOutMessage(msg)
def led_flash(button):
msg = mido.Message('note_on', channel=0, note=button, velocity=but_led_flash)
setOutMessage(msg)
def fader_max(fader):
msg = mido.Message(fad_msg_type, channel=fader, pitch=fad_max)
setOutMessage(msg)
def fader_min(fader):
msg = mido.Message(fad_msg_type, channel=fader, pitch=fad_min)
setOutMessage(msg)
def fader_zero(fader):
msg = mido.Message(fad_msg_type, channel=fader, pitch=fad_zero)
setOutMessage(msg)
# Main program loop with midi despatcher running
with client:
print('Universal Contol Transalator for Behringer XTouch Controller')
while True:
# We check the input message and see if it has a mapped action
msg_to_map(input_message)