From 28e22b18485dff9c6e8b27b28fd96c37536b6761 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 13 Aug 2021 12:16:05 -0400 Subject: [PATCH 01/25] Added .DS_Store so that editing on a mac show unnecessary files --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 46627bc..711f276 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ *.pyc .vscode - +.DS_Store From c7633c01c00fe4126f86371ce8683aabb07268cc Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 13 Aug 2021 14:15:35 -0400 Subject: [PATCH 02/25] clean up pylint errors, add code to cleanup state on error conditions prior to exit, moved activation of monitor thread from main function into object, added debug printouts --- lib/midicontroller.py | 90 +++++++++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/lib/midicontroller.py b/lib/midicontroller.py index f610ea8..09b5551 100644 --- a/lib/midicontroller.py +++ b/lib/midicontroller.py @@ -1,7 +1,7 @@ +"See class docstring" import threading import time import os -from .mixerstate import MixerState from mido import Message, open_input, open_output, get_input_names, get_output_names class TempoDetector: @@ -55,7 +55,7 @@ class MidiController: Fader 1-8: CC16 - CC23, Note 32 - 39 on push Turn right: Values 1 - 10 (Increment) Turn left: Values 65 - 72 (Decrement) - Buttons 1-8: Note 89, 90, 40, 41, 42, 43, 44, 45 + Buttons 1-8: Note 89, 90, 40, 41, 42, 43, 44, 45 Buttons 9-16: Note 87, 88, 91, 92, 86, 93, 94, 95 Master Fader: Pitch Wheel """ @@ -76,10 +76,10 @@ class MidiController: inport = None outport = None - + def __init__(self, state): self.state = state - + for name in get_input_names(): if "x-touch mini" in name.lower(): print('Using MIDI input: ' + name) @@ -87,7 +87,10 @@ def __init__(self, state): self.inport = open_input(name) except IOError: print('Error: Can not open MIDI input port ' + name) - exit() + self.state.quit_called = True + self.state = None + #exit() + return break for name in get_output_names(): @@ -97,35 +100,72 @@ def __init__(self, state): self.outport = open_output(name) except IOError: print('Error: Can not open MIDI input port ' + name) - exit() + self.state.quit_called = True + self.state = None + #exit() + return break - + if self.inport is None or self.outport is None: print('X-Touch Mini not found. Make sure device is connected!') - exit() + self.state.quit_called = True + self.cleanup_controller() + #exit() + return self.tempo_detector = TempoDetector(self) self.change_layer(0) self.activate_bank(0) #self.activate_bus(0) - worker = threading.Thread(target = self.midi_listener) + for i in range(0, 18): + self.set_button(i, self.LED_OFF) # clear all buttons + + worker = threading.Thread(target=self.midi_listener) worker.daemon = True worker.start() + if self.state.monitor: + print('Monitoring X-Touch connection enabled') + monitor = threading.Thread(target=self.monitor_ports) + monitor.daemon = True + monitor.start() + + def cleanup_controller(self): + "Cleanup mixer state if we see a quit call. Called from _init_ or worker thread." + for i in range(0, 18): + self.set_button(i, self.LED_OFF) # clear all buttons + for i in range(0,8): + self.set_ring(i,-1) + if self.inport is not None: + self.inport.close() + if self.outport is not None: + self.outport.close() + def monitor_ports(self): + "Method to exit if / when the X Touch disconnects" try: - while True: + while not self.state.quit_called: if self.inport.name not in get_input_names(): print("X-Touch disconnected - Exiting") - os._exit(1) + #os._exit(1) + self.state.quit_called = True + return + if self.state.quit_called: + return # end the thread if other threads have signed exit time.sleep(1) except KeyboardInterrupt: + if self.state is not None: + self.state.quit_called = True exit() def midi_listener(self): + "Listen to midit inputs and respond." try: for msg in self.inport: + if self.state is None or self.state.quit_called: + self.cleanup_controller() + return #print('Received {}'.format(msg)) if msg.type == 'control_change': if msg.control in self.MIDI_ENCODER: @@ -139,6 +179,8 @@ def midi_listener(self): else: print('Received unknown {}'.format(msg)) elif msg.type == 'note_on' and msg.velocity == 127: + if self.state.debug: + print('Note {} pushed'.format(msg.note)) if msg.note in self.MIDI_PUSH: self.knob_pushed(self.MIDI_PUSH.index(msg.note)) elif msg.note in self.MIDI_BUTTONS: @@ -153,11 +195,13 @@ def midi_listener(self): elif msg.type != 'note_off' and msg.type != 'note_on': print('Received unknown {}'.format(msg)) except KeyboardInterrupt: - self.inport.close() - self.outport.close() + if self.state is not None: + self.state.quit_called = True + self.cleanup_controller() exit() - + def button_pushed(self, button): + "On button press, call the relevant function" if button == 8: # mute grp 4 pressed self.state.toggle_mute_group(3) @@ -181,6 +225,7 @@ def button_pushed(self, button): self.refresh_controls(self.state.active_bank) def knob_pushed(self, knob): + "On knob push reset the correct value" if knob >= 0 and knob <= 3: self.state.toggle_mute_group(knob) elif knob == 7: @@ -246,10 +291,12 @@ def set_mute_grp(self, group, on): self.set_button(8, on == 1) def set_channel_mute(self, channel, on): + "Send the mute value to the button" if self.active_layer == 0: self.set_button(channel, on == 0) def set_channel_fader(self, channel, value): + "Send the fader value to the encoder ring" if self.active_layer == 0: self.set_ring(channel, value) @@ -258,17 +305,18 @@ def set_bus_send(self, bus, knob, value): self.set_ring(knob, value) def set_ring(self, ring, value): - # 0 = off, 1-11 = single, 17-27 = trim, 33-43 = fan, 49-54 = spread + "Turn on the appropriate LEDs on the encoder ring." + # 0 = off, 1-11 = single, 17-27 = pan, 33-43 = fan, 49-54 = spread # normalize value (0.0 - 1.0) to 0 - 11 range # values below 0 mean disabled if value >= 0.0: - self.outport.send(Message('control_change', channel = self.MC_CHANNEL, - control = self.MIDI_RING[ring], - value = 33 + round(value * 11))) + self.outport.send(Message('control_change', channel=self.MC_CHANNEL, + control = self.MIDI_RING[ring], + value = 33 + round(value * 11))) else: - self.outport.send(Message('control_change', channel = self.MC_CHANNEL, - control = self.MIDI_RING[ring], - value = 0)) + self.outport.send(Message('control_change', channel=self.MC_CHANNEL, + control=self.MIDI_RING[ring], + value=0)) def set_button(self, button, on): if on == True: From 4b8fc128357c0096fac545c1db1d78a866699c2f Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 13 Aug 2021 15:34:31 -0400 Subject: [PATCH 03/25] clean up pylint errors, added code to cleanup state on error conditions prior to exit, moved initialization of state from main function into object, added debug printouts --- lib/mixerstate.py | 262 +++++++++++++++++++++++++++++----------------- 1 file changed, 165 insertions(+), 97 deletions(-) diff --git a/lib/mixerstate.py b/lib/mixerstate.py index 160adc4..0d16ef8 100644 --- a/lib/mixerstate.py +++ b/lib/mixerstate.py @@ -1,13 +1,15 @@ -import time -import subprocess - """ This module holds the mixer state of the X-Air device """ +import time +import subprocess +from lib.xair import XAirClient, find_mixer +from lib.midicontroller import MidiController + class Channel: - """ - Represents a single channel strip + """ + Represents a single channel strip """ def __init__(self, addr): self.fader = 0.0 @@ -15,9 +17,8 @@ def __init__(self, addr): self.sends = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] else: self.sends = None - self.on = 1 + self.ch_on = 1 self.osc_base_addr = addr - class MixerState: """ @@ -26,67 +27,17 @@ class MixerState: decide whether state changes from the X-Air device need to be sent to the midi controller. """ - + + quit_called = False + # ID numbers for all available delay effects _DELAY_FX_IDS = [10, 11, 12, 21, 24, 25, 26] fx_slots = [0, 0, 0, 0] - + active_bank = -1 - - # Each layer has 8 encoders and 8 buttons - banks = [ - [ - Channel('/ch/01/mix'), - Channel('/ch/02/mix'), - Channel('/ch/03/mix'), - Channel('/ch/04/mix'), - Channel('/ch/05/mix'), - Channel('/ch/06/mix'), - Channel('/ch/07/mix'), - Channel('/ch/08/mix') - ], - [ - Channel('/ch/09/mix'), - Channel('/ch/10/mix'), - Channel('/ch/11/mix'), - Channel('/ch/12/mix'), - Channel('/ch/13/mix'), - Channel('/ch/14/mix'), - Channel('/ch/15/mix'), - Channel('/ch/16/mix') - ], - [ - Channel('/rtn/aux/mix'), - None, - None, - None, - Channel('/dca/1'), - Channel('/dca/2'), - Channel('/dca/3'), - Channel('/dca/4') - ], - [ - Channel('/fxsend/1/mix'), - Channel('/fxsend/2/mix'), - Channel('/fxsend/3/mix'), - Channel('/fxsend/4/mix'), - Channel('/rtn/1/mix'), - Channel('/rtn/2/mix'), - Channel('/rtn/3/mix'), - Channel('/rtn/4/mix') - ], - [ - Channel('/bus/1/mix'), - Channel('/bus/2/mix'), - Channel('/bus/3/mix'), - Channel('/bus/4/mix'), - Channel('/bus/5/mix'), - Channel('/bus/6/mix'), - None, - None - ] - ] + + banks = [] lr = Channel('/lr/mix') @@ -102,25 +53,127 @@ class MixerState: midi_controller = None xair_client = None + def __init__(self, args) -> None: + # split the arguments out to useful values + self.debug = args.debug + self.xair_address = args.xair_address + self.monitor = args.monitor + + # Each layer has 8 encoders and 8 buttons + if True: # enable addition of command line flags to alter bank layout + self.banks = [[ + Channel('/ch/01/mix'), + Channel('/ch/02/mix'), + Channel('/ch/03/mix'), + Channel('/ch/04/mix'), + Channel('/ch/05/mix'), + Channel('/ch/06/mix'), + Channel('/ch/07/mix'), + Channel('/ch/08/mix') + ]] + self.banks.append( + [ + Channel('/ch/09/mix'), + Channel('/ch/10/mix'), + Channel('/ch/11/mix'), + Channel('/ch/12/mix'), + Channel('/ch/13/mix'), + Channel('/ch/14/mix'), + Channel('/ch/15/mix'), + Channel('/ch/16/mix') + ]) + self.banks.append( + [ + Channel('/rtn/aux/mix'), + None, + None, + None, + Channel('/dca/1'), + Channel('/dca/2'), + Channel('/dca/3'), + Channel('/dca/4') + ]) + self.banks.append( + [ + Channel('/fxsend/1/mix'), + Channel('/fxsend/2/mix'), + Channel('/fxsend/3/mix'), + Channel('/fxsend/4/mix'), + Channel('/rtn/1/mix'), + Channel('/rtn/2/mix'), + Channel('/rtn/3/mix'), + Channel('/rtn/4/mix') + ]) + self.banks.append( + [ + Channel('/bus/1/mix'), + Channel('/bus/2/mix'), + Channel('/bus/3/mix'), + Channel('/bus/4/mix'), + Channel('/bus/5/mix'), + Channel('/bus/6/mix'), + None, + None + ]) + + def initialize_state(self): + self.quit_called = False + # determine the mixer address + if self.xair_address is None: + self.xair_address = find_mixer() + if self.xair_address is None: + print('Error: Could not find any mixers in network.', + 'Using default ip address.') + return False + + # setup other modules + self.midi_controller = MidiController(self) + if self.quit_called: + self.midi_controller = None + return False + self.xair_client = XAirClient(self.xair_address, self) + self.xair_client.validate_connection() + if self.quit_called: + self.midi_controller = None + self.xair_client = None + return False + + self.read_initial_state() + return True + + def shutdown(self): + self.quit_called = True + "safely shutdown all threads" + if self.xair_client is not None: + self.xair_client.stop_server() + self.xair_client = None + if self.midi_controller is not None: + self.midi_controller.cleanup_controller() + self.midi_controller = None + def toggle_mute_group(self, group): - if self.mute_groups[group].on == 1: - self.mute_groups[group].on = 0 + if self.mute_groups[group].ch_on == 1: + self.mute_groups[group].ch_on = 0 else: - self.mute_groups[group].on = 1 - self.xair_client.send(address = self.mute_groups[group].osc_base_addr, - param = self.mute_groups[group].on) - self.midi_controller.set_mute_grp(group, self.mute_groups[group].on) + self.mute_groups[group].ch_on = 1 + self.xair_client.send( + address=self.mute_groups[group].osc_base_addr, + param=self.mute_groups[group].ch_on) + self.midi_controller.set_mute_grp(group, self.mute_groups[group].ch_on) def toggle_channel_mute(self, channel): - if self.banks[self.active_bank][channel] != None: - if self.banks[self.active_bank][channel].on == 1: - self.banks[self.active_bank][channel].on = 0 + """Toggle the state of a channel mute button.""" + if self.banks[self.active_bank][channel] is not None: + if self.banks[self.active_bank][channel].ch_on == 1: + self.banks[self.active_bank][channel].ch_on = 0 else: - self.banks[self.active_bank][channel].on = 1 - self.xair_client.send(address = self.banks[self.active_bank][channel].osc_base_addr + '/on', - param = self.banks[self.active_bank][channel].on) - self.midi_controller.set_channel_mute(channel, self.banks[self.active_bank][channel].on) - + self.banks[self.active_bank][channel].ch_on = 1 + self.xair_client.send( + address=self.banks[self.active_bank][channel].osc_base_addr + '/on', + param=self.banks[self.active_bank][channel].ch_on) + self.midi_controller.set_channel_mute( + channel, self.banks[self.active_bank][channel].ch_on) + def toggle_mpc(self): if self.mpd_playing: try: @@ -136,26 +189,35 @@ def toggle_mpc(self): self.mpd_playing = True def change_fader(self, fader, delta): - if self.banks[self.active_bank][fader] != None: - self.banks[self.active_bank][fader].fader = min(max(0.0, self.banks[self.active_bank][fader].fader + (delta / 200)), 1.0) - self.xair_client.send(address = self.banks[self.active_bank][fader].osc_base_addr + '/fader', - param = self.banks[self.active_bank][fader].fader) + """Change the level of a fader.""" + if self.banks[self.active_bank][fader] is not None: + self.banks[self.active_bank][fader].fader = \ + min(max(0.0, self.banks[self.active_bank][fader].fader + (delta / 200)), 1.0) + self.xair_client.send( + address=self.banks[self.active_bank][fader].osc_base_addr + '/fader', + param=self.banks[self.active_bank][fader].fader) self.midi_controller.set_channel_fader(fader, self.banks[self.active_bank][fader].fader) def change_bus_send(self, bus, channel, delta): - if self.banks[self.active_bank][channel] != None and self.banks[self.active_bank][channel].sends != None: - self.banks[self.active_bank][channel].sends[bus] = min(max(0.0, self.banks[self.active_bank][channel].sends[bus] + (delta / 200)), 1.0) - self.xair_client.send(address = self.banks[self.active_bank][channel].osc_base_addr + '/{:0>2d}/level'.format(bus + 1), - param = self.banks[self.active_bank][channel].sends[bus]) + """Change the level of a bus send.""" + if ((self.banks[self.active_bank][channel] is not None) + and self.banks[self.active_bank][channel].sends is not None): + self.banks[self.active_bank][channel].sends[bus] = \ + min(max(0.0, self.banks[self.active_bank][channel].sends[bus] + (delta / 200)), 1.0) + self.xair_client.send( + address=self.banks[self.active_bank][channel].osc_base_addr + \ + '/{:0>2d}/level'.format(bus + 1), + param=self.banks[self.active_bank][channel].sends[bus]) self.midi_controller.set_bus_send(bus, channel, self.banks[self.active_bank][channel].sends[bus]) def set_lr_fader(self, value): self.xair_client.send(address = self.lr.osc_base_addr + '/fader', param = value) def received_osc(self, addr, value): + """Process an OSC input.""" if addr.startswith('/config/mute'): group = int(addr[-1:]) - 1 - self.mute_groups[group].on = value + self.mute_groups[group].ch_on = value self.midi_controller.set_mute_grp(group, value) elif addr.startswith('/fx') and (addr.endswith('/par/01') or addr.endswith('/par/02')): if self.fx_slots[int(addr[4:5]) - 1] in self._DELAY_FX_IDS: @@ -171,16 +233,22 @@ def received_osc(self, addr, value): else: for i in range(0, 5): for j in range(0, 8): - if self.banks[i][j] != None and addr.startswith(self.banks[i][j].osc_base_addr): - if addr.endswith('/fader'): + if self.banks[i][j] is not None and addr.startswith(self.banks[i][j].osc_base_addr): + if addr.endswith('/fader'): # chanel fader level + if self.debug: + print('Channel %s level %f' % (addr, value)) self.banks[i][j].fader = value if i == self.active_bank: self.midi_controller.set_ring(j, value) - elif addr.endswith('/on'): - self.banks[i][j].on = value + elif addr.endswith('/on'): # channel enable + if self.debug: + print('%s unMute %d' % (addr, value)) + self.banks[i][j].ch_on = value if i == self.active_bank: self.midi_controller.set_channel_mute(j, value) - elif self.banks[i][j].sends != None and addr.endswith('/level'): + elif self.banks[i][j].sends is not None and addr.endswith('/level'): + if self.debug: + print('%s level %f' % (addr, value)) bus = int(addr[-8:-6]) - 1 self.banks[i][j].sends[bus] = value if i == self.active_bank: @@ -189,17 +257,17 @@ def received_osc(self, addr, value): else: continue break - + def read_initial_state(self): - # Refresh state for all faders and mutes + """ Refresh state for all faders and mutes.""" for i in range(0, 5): for j in range(0, 8): - if self.banks[i][j] != None: - self.xair_client.send(address = self.banks[i][j].osc_base_addr + '/fader') + if self.banks[i][j] is not None: + self.xair_client.send(address=self.banks[i][j].osc_base_addr + '/fader') time.sleep(0.01) - self.xair_client.send(address = self.banks[i][j].osc_base_addr + '/on') + self.xair_client.send(address=self.banks[i][j].osc_base_addr + '/on') time.sleep(0.01) - if self.banks[i][j].sends != None: + if self.banks[i][j].sends is not None: for k in range(len(self.banks[i][j].sends)): self.xair_client.send(address = self.banks[i][j].osc_base_addr + '/{:0>2d}/level'.format(k + 1)) time.sleep(0.01) From 3f39a56b5194163c10bc1c7466d2e1b28012e447 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 13 Aug 2021 15:35:14 -0400 Subject: [PATCH 04/25] clean up pylint errors, moved initialization of state from main function into objects --- xair-remote.py | 46 ++++++++++++++-------------------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/xair-remote.py b/xair-remote.py index 897a6af..565b3c0 100755 --- a/xair-remote.py +++ b/xair-remote.py @@ -1,38 +1,20 @@ +"Starts an xAir remote, see main help string for more" #!/usr/bin/env python3 import argparse -import threading -from lib.midicontroller import MidiController -from lib.xair import XAirClient, find_mixer from lib.mixerstate import MixerState if __name__ == '__main__': - parser = argparse.ArgumentParser(description = 'Remote control X-Air mixers with a midi controller') - parser.add_argument('xair_address', help = 'ip address of your X-Air mixer (optional)', nargs = '?') - parser.add_argument('-m', '--monitor', help='monitor X-Touch connection and exit when disconnected', action="store_true") - args = parser.parse_args() + PARSER = argparse.ArgumentParser(description=""" + Remote control X-Air mixers with an X-Touch midi controller. + """) + PARSER.add_argument('xair_address', help='ip address of your X-Air mixer (optional)', nargs='?') + PARSER.add_argument('-m', '--monitor', + help='monitor X-Touch connection and exit when disconnected', + action="store_true") + PARSER.add_argument('-d', '--debug', help='enable debug output', action="store_true") + ARGS = PARSER.parse_args() - if args.xair_address is None: - address = find_mixer() - if address is None: - print('Error: Could not find any mixers in network. Please specify ip address manually.') - exit() - else: - args.xair_address = address - - state = MixerState() - midi = MidiController(state) - state.midi_controller = midi - xair = XAirClient(args.xair_address, state) - state.xair_client = xair - xair.validate_connection() - - if args.monitor: - print('Monitoring X-Touch connection enabled') - monitor = threading.Thread(target = midi.monitor_ports) - monitor.daemon = True - monitor.start() - - state.read_initial_state() - - # now refresh /xremote command while running - xair.refresh_connection() + STATE = MixerState(ARGS) + if STATE.initialize_state(): + # now start polling refresh /xremote command while running + STATE.xair_client.refresh_connection() From 6ace3cad0819424437a8ec0e97f1692668bdeaed Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 13 Aug 2021 15:49:13 -0400 Subject: [PATCH 05/25] clean up pylint errors, added code to cleanup state on error conditions prior to exit, moved initialization of state from main function into object, added debug printouts --- lib/xair.py | 88 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 24 deletions(-) diff --git a/lib/xair.py b/lib/xair.py index 7ff9b35..0537b94 100644 --- a/lib/xair.py +++ b/lib/xair.py @@ -1,3 +1,4 @@ +"This modules managed communications with the XAir mixer" import time import threading import socket @@ -5,15 +6,16 @@ from pythonosc.osc_server import BlockingOSCUDPServer from pythonosc.osc_message import OscMessage from pythonosc.osc_message_builder import OscMessageBuilder -from .mixerstate import MixerState class OSCClientServer(BlockingOSCUDPServer): + "The OSC communications agent" def __init__(self, address, dispatcher): super().__init__(('', 0), dispatcher) self.xr_address = address def send_message(self, address, value): - builder = OscMessageBuilder(address = address) + "Packs a message for sending via OSC over UDB." + builder = OscMessageBuilder(address=address) if value is None: values = [] elif isinstance(value, list): @@ -36,55 +38,93 @@ class XAirClient: XAIR_PORT = 10024 info_response = [] - + def __init__(self, address, state): self.state = state dispatcher = Dispatcher() dispatcher.set_default_handler(self.msg_handler) self.server = OSCClientServer((address, self.XAIR_PORT), dispatcher) - worker = threading.Thread(target = self.run_server) + worker = threading.Thread(target=self.run_server) worker.daemon = True worker.start() - + def validate_connection(self): + "Confirm that the connection to the XAir is live, otherwise initiaties shutdown." self.send('/xinfo') time.sleep(self._CONNECT_TIMEOUT) if len(self.info_response) > 0: - print('Successfully connected to %s with firmware %s at %s.' % (self.info_response[2], + print('Successfully connected to %s with firmware %s at %s.' % (self.info_response[2], self.info_response[3], self.info_response[0])) else: - print('Error: Failed to setup OSC connection to mixer. Please check for correct ip address.') - exit() - + print('Error: Failed to setup OSC connection to mixer.', + 'Please check for correct ip address.') + self.state.quit_called = True + if self.server is not None: + self.server.shutdown() + self.server = None + #exit() + def run_server(self): + "Start the OSC communications agent in a seperate thread." try: self.server.serve_forever() except KeyboardInterrupt: - self.server.shutdown() + self.quit() exit() - + + def stop_server(self): + if self.server is not None: + self.server.shutdown() + self.server = None + + def quit(self): + if self.state is not None: + self.state.quit_called = True + if self.state.midi_controller is not None: + self.state.midi_controller.cleanup_controller() + self.stop_server() + def msg_handler(self, addr, *data): - #print 'OSCReceived("%s", %s, %s)' % (addr, tags, data) - if addr.endswith('/fader') or addr.endswith('/on') or addr.endswith('/level') or addr.startswith('/config/mute') or addr.startswith('/fx/'): - self.state.received_osc(addr, data[0]) - elif addr == '/xinfo': - self.info_response = data[:] - - def refresh_connection(self): - # Tells mixer to send changes in state that have not been recieved from this OSC Client - # /xremote - all parameter changes are broadcast to all active clients (Max 4) - # /xremotefnb - No Feed Back. Parameter changes are only sent to the active clients which didn't initiate the change + "Dispatch received OSC messages based on message type." + if self.state is None or self.state.quit_called: + self.stop_server() + return + #print 'OSCReceived("%s", %s, %s)' % (addr, tags, data) + if addr.endswith('/fader') or addr.endswith('/on') or addr.endswith('/level') or \ + addr.startswith('/config/mute') or addr.startswith('/fx/'): + self.state.received_osc(addr, data[0]) + elif addr == '/xinfo': + self.info_response = data[:] + else: #if self.state.debug and addr.start: + print('OSCReceived("%s", %s)' % (addr, data)) + + def refresh_connection(self): # the main loop + """ + Tells mixer to send changes in state that have not been received from this OSC Client + /xremote - all parameter changes are broadcast to all active clients (Max 4) + /xremotefnb - No Feed Back. Parameter changes are only sent to the active clients + which didn't initiate the change + """ + if self.state.debug: + print("Refresh Connection %s" % self.state.levels) try: - while True: + while not self.state.quit_called and self.server is not None: self.server.send_message("/xremotenfb", None) time.sleep(self._REFRESH_TIMEOUT) + if self.state.quit_called: + return except KeyboardInterrupt: + self.quit() exit() - - def send(self, address, param = None): + except socket.error: + self.quit() + + def send(self, address, param=None): + "Call the OSC agent to send a message" self.server.send_message(address, param) def find_mixer(): + "Search for the IP address of the XAir mixer" print('Searching for mixer...') client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True) From 1caf0718b0df624b7270909c9a5677cc0e88eb95 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 13 Aug 2021 16:42:26 -0400 Subject: [PATCH 06/25] updated readme to match reconfigured fader operation --- README.md | 176 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 100 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 88ea5a8..7cf4d82 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,14 @@ # xair-remote -Use a Behringer X-Touch Mini MIDI controller to remote control a Behringer X-Air digital mixer via the OSC network protocol. Multiple layers allow you to control volume, mute and bus sends for every input and output channel with only 8 physical encoders. Changes on the mixer will also be displayed accurately on the X-Touch controller. +With thanks peterdikant who created the [xair-remote +project](https://github.com/peterdikant/xair-remote). This version is a seperate +fork as there is not obvious way to meet both my need and Peter's at the same time. + +Use a Behringer X-Touch Mini MIDI controller to remote control a Behringer X-Air +digital mixer via the OSC network protocol. Multiple layers allow you to control +volume, mute and bus sends for every input and output channel with only 8 +physical encoders. Changes on the mixer will also be displayed accurately on the +X-Touch controller. ## Installing @@ -10,9 +18,12 @@ You need Python 3.5 or later. Please make sure to install required libraries: ## Update -If you update from a previous version, please make sure that you run at least Python 3.5 and install all required libraries as described in the previous section. +If you update from a previous version, please make sure that you run at least +Python 3.5 and install all required libraries as described in the previous +section. -The X-Touch device now needs to be running in MC mode. Make sure that the MC LED is lit after connecting the device by pressing the MC button during startup. +The X-Touch device needs to be running in MC mode. Make sure that the MC LED is +lit after connecting the device by pressing the MC button during startup. ## Running @@ -20,26 +31,35 @@ To get help run: $ python3 xair-remote.py -h -The app will automatically detect both your X-Touch controller and the XR18. So connect the controller and make sure the XR18 is reachable on your network. Now you can start the app: +The app will automatically detect both your X-Touch controller and the XR18. So +connect the controller and make sure the XR18 is reachable on your network. Now +you can start the app: $ python3 xair-remote.py -If the app can not find your controller or connect to the X-Air mixer, it will terminate with an error message explaining the problem. If everything started up successfully, the X-Touch mini will reflect the current mixer state and the console output will look like this: +If the app can not find your controller or connect to the X-Air mixer, it will +terminate with an error message explaining the problem. If everything started up +successfully, the X-Touch mini will reflect the current mixer state and the +console output will look like this: Found XR18 with firmware 1.17 on IP 192.168.178.31 Using MIDI input: X-TOUCH MINI Using MIDI output: X-TOUCH MINI Successfully connected to XR18 with firmware 1.17 at 192.168.178.31. -Startup will fail if the X-Touch controller is not connected or the XR18 could not be located on the network. In the latter case you could try to specify the IP address of the mixer on the command line: +Startup will fail if the X-Touch controller is not connected or the XR18 could +not be located on the network. In the latter case you could try to specify the +IP address of the mixer on the command line: $ python3 xair-remote.py 192.168.178.37 -The app can monitor the X-Touch connection and exit if the controller is disconnected. This functionality is enabled by setting the parameter `-m`: +The app can monitor the X-Touch connection and exit if the controller is +disconnected. This functionality is enabled by setting the parameter `-m`: $ python3 xair-remote.py -m -Note: Monitoring does not work on all platforms. Linux works fine while MacOS does not detect disconnects. +Note: Monitoring does not work on all platforms. Linux works fine while MacOS +does not detect disconnects. ## Using @@ -47,77 +67,81 @@ The following image is a schematic of all available controls on the X-Touch Mini ![X-Touch Mini controls](img/xtm-layout.png) -You need to configure the device to start in MC mode. If the MC mode LED is not lighted, hold down the MC button while connecting the device. +You need to configure the device to start in MC mode. If the MC mode LED is not +lighted, hold down the MC button while connecting the device. To exit press `CTRL + C`. -The folder `labels` contains labels to print and attach to your X-Touch as PDF and Excel files. You can change the labels to your specific setup. Make sure you print them in 100% size. - -### Layer A - -Layer A is used to control the main mix. - -This layer is active when the button `LA` is selected. The main volume is always mapped to the Fader `F1`. The lower button row is assigned globally with the following functions: - -Button | Function ------- | ------------------------------------------------ -B09 | Mute Group 4 (this is always my FX mute group) -B10 | Tap Tempo (will set tempo for all delay effects) -B11 | Unassigned -B12 | Fader Bank 1 -B13 | Fader Bank 2 -B14 | Fader Bank 3 -B15 | Fader Bank 4 -B16 | Fader Bank 5 - -The upper row buttons and the top encoders are used to control volume and mute for different channels depending on the selected fader bank: - -Bank | Encoders (Volume) & Buttons (Mute) ----- | ---------------------------------- -1 | Channels 1 - 8 -2 | Channels 9 - 16 -3 | Aux, 3 unassigned, DCA 1 - 4 -4 | FX Sends 1 - 4, FX Returns 1 - 4 -5 | Bus 1 - 6, 2 unassigned - -The Push function of encoders `E1` to `E4` can be used to toggle mute groups 1 to 4 in all fader banks. I always use mute group 4 for FX mute, therefore I have placed this mute group on button `B09`. This way I also get visual feedback on the status of my FX returns. - -### Layer B - -Layer B can control your bus sends. - -This layer is active when the button `LB` is selected. The main volume is always mapped to the Fader `F1`. - -In this layer all buttons have a static assignment: - -Button | Function ------- | ------------------------------------------------ -B01 | Bus 1 -B02 | Bus 2 -B03 | Bus 3 -B04 | Bus 4 -B05 | Bus 5 -B06 | Bus 6 -B07 | FX 1 -B08 | FX 2 -B09 | Mute Group 4 (this is always my FX mute group) -B10 | Tap Tempo (will set tempo for all delay effects) -B11 | Unassigned -B12 | Fader Bank 1 -B13 | Fader Bank 2 -B14 | Fader Bank 3 -B15 | FX 3 -B16 | FX 4 - -The top encoders control the volume for different channels depending on the selected fader bank and bus/FX: - -Layer | Encoders (Volume) ------ | ---------------------------------- -1 | Channels 1 - 8 -2 | Channels 9 - 16 -3 | Aux, 7 unassigned - -The Push function of encoders `E1` to `E4` can be used to toggle mute groups 1 to 4 in all fader banks. +The folder `labels` contains labels to print and attach to your X-Touch as PDF +and Excel files. You can change the labels to your specific setup. Make sure you +print them in 100% size. + +### General Operation + +The X-Touch is configured as a set of 8 channel strips that can control an 8 +channel bank of the mixer. Each channel strip consists of a rotary encoder and +button, `E1` and `B1` through `E8` and `B8`. The encoder sets the level or gain +of the channel and the ring of lights around the encoder indicate the level. +Pressing the encoder returns the channel to unity gain or a useful default where +one can be found. The button mutes the channel and lights to indicate the +channel is muted. The lower row of buttons, `B09` through `B16` and the two +layer buttons `LA` and `LB` control bank selection. The Fader `F1` is not used. + +Pressing a bank select button selects the first layer of the bank and causes the +bank button to light. Pressing the bank button again selects the second layer +and causes the button to blink. Pressing a third time returns to the first +layer. Selecting another bank always selects the first layer even if the +previous bank was on the second layer. The banks are configured as follows: + +Button | Bank Function +------ | ------------- +`LA` | Channel Send to Main LR Bus +`LB` | Bus Output and USB AUX In Levels +`B09` | Channel Send to AUX 1 +`B10` | Channel Send to AUX 2 +`B11` | Channel Send to AUX 3 +`B12` | Channel Send to AUX 4 +`B13` | Channel Send to AUX 5 +`B14` | Channel Send to AUX 6 +`B15` | Channel Send to Aux 7/Fx 1 +`B16` | Mic Preamps + +### Bank A + +Bank A is used to control the level of channels in the main mix and has two +layers. In the first layer the encoders and buttons map to channels 1 through 8, +and the second layer they map to channels 9 through 16. Pressing the encoders +returns the channel to unity gain, the top row of buttons (B01 through B08) mute +the channel. + +### Bank B + +Bank B is used to control the output levels and USB AUX in level, it has one +layer. The encoders and mute buttons are as per the table: + +Channel | Encoders (Volume) & Buttons (Mute) | Encoder Push Value +------- | ---------------------------------- | ------------------ +1-6 | Aux 1-6 Output Levels | Return to -20 db +7 | USB/Aux Input Gain | Return to 0 db +8 | Main L/R Output Level | Return to -20 db + +The Push function of encoders return to -20 db as most of the monitor and PA +speakers I use get over distort at 0db output. + +### Banks 1 through 6 + +Banks 1 though 6 control the AUX Bus sends for each channel and have two layers. +Similar to Bank A layer 1 maps to channels 1 through 8 and layer 2 maps channels +9 through 16. As the X-Air does not have a mute function on sends, the mute is +implemented by setting the send value to -∞ while storing the current send +value. If you restart the application while a send is muted the stored send +level will be lost. Pressing the encoders returns the send to unity gain. These +banks have no effect for channels set to "Sub Group" send mode. + +### Bank 7 + +Bank 7 works the same as Banks 1 through 6 though it sends the channels to AUX +Bus 7 which is also known as FX 1. ## License From 0190c73587bc2853f2ae5662e1d2c8c6975b2146 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 13 Aug 2021 16:43:36 -0400 Subject: [PATCH 07/25] rearranged mapping of buttons and added metering options --- lib/mixerstate.py | 285 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 226 insertions(+), 59 deletions(-) diff --git a/lib/mixerstate.py b/lib/mixerstate.py index 0d16ef8..58b5c04 100644 --- a/lib/mixerstate.py +++ b/lib/mixerstate.py @@ -4,6 +4,8 @@ import time import subprocess +import struct +from collections import deque from lib.xair import XAirClient, find_mixer from lib.midicontroller import MidiController @@ -13,13 +15,33 @@ class Channel: """ def __init__(self, addr): self.fader = 0.0 - if addr.startswith('/ch') or addr.startswith('/rtn'): + if addr.startswith('/ch'): + # the 6 aux bus followed by the 4 effects self.sends = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + self.enables = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] else: self.sends = None + self.enables = None self.ch_on = 1 self.osc_base_addr = addr +class Meter: + """ + Calculates the .2 second running average of the value of a channel meter + """ + values = 4 + def __init__(self): + self.levels = deque(maxlen=self.values) + for _ in range(self.values): + self.levels.append(-102400) + self.mean = -102400 * self.values + + def insert_level(self, value): + 'push a vlue into the fixed FIFO and update the mean' + self.mean = self.mean - self.levels.popleft() + value + self.levels.append(value) + return self.mean + class MixerState: """ This stores the mixer state in the application. It also keeps @@ -36,6 +58,7 @@ class MixerState: fx_slots = [0, 0, 0, 0] active_bank = -1 + active_bus = -1 banks = [] @@ -49,18 +72,40 @@ class MixerState: ] mpd_playing = True - + midi_controller = None xair_client = None + meters = [] + for i in range(16): + meters.append(Meter()) + def __init__(self, args) -> None: # split the arguments out to useful values self.debug = args.debug self.xair_address = args.xair_address self.monitor = args.monitor + self.clip = args.clip + self.mac = args.mac + self.levels = args.levels + + # initialize internal data structures + if self.clip: # clipping protection doesn't work without level info + self.levels = True # Each layer has 8 encoders and 8 buttons - if True: # enable addition of command line flags to alter bank layout + if self.mac: # only a single layer, use second row of buttons as transport control + self.banks = [[ + Channel('/ch/08/mix'), + Channel('/ch/07/mix'), + Channel('/ch/06/mix'), + Channel('/ch/05/mix'), + Channel('/bus/1/mix'), + Channel('/bus/2/mix'), + Channel('/rtn/aux/mix'), + Channel('/lr/mix') + ]] + else: self.banks = [[ Channel('/ch/01/mix'), Channel('/ch/02/mix'), @@ -84,25 +129,25 @@ def __init__(self, args) -> None: ]) self.banks.append( [ - Channel('/rtn/aux/mix'), - None, - None, - None, - Channel('/dca/1'), - Channel('/dca/2'), - Channel('/dca/3'), - Channel('/dca/4') + Channel('/headamp/01'), + Channel('/headamp/02'), + Channel('/headamp/03'), + Channel('/headamp/04'), + Channel('/headamp/05'), + Channel('/headamp/06'), + Channel('/headamp/07'), + Channel('/headamp/08') ]) self.banks.append( [ - Channel('/fxsend/1/mix'), - Channel('/fxsend/2/mix'), - Channel('/fxsend/3/mix'), - Channel('/fxsend/4/mix'), - Channel('/rtn/1/mix'), - Channel('/rtn/2/mix'), - Channel('/rtn/3/mix'), - Channel('/rtn/4/mix') + Channel('/headamp/09'), + Channel('/headamp/10'), + Channel('/headamp/11'), + Channel('/headamp/12'), + Channel('/headamp/13'), + Channel('/headamp/14'), + Channel('/headamp/15'), + Channel('/headamp/16') ]) self.banks.append( [ @@ -112,8 +157,8 @@ def __init__(self, args) -> None: Channel('/bus/4/mix'), Channel('/bus/5/mix'), Channel('/bus/6/mix'), - None, - None + Channel('/rtn/aux/mix'), + Channel('/lr/mix') ]) def initialize_state(self): @@ -124,7 +169,7 @@ def initialize_state(self): if self.xair_address is None: print('Error: Could not find any mixers in network.', 'Using default ip address.') - return False + self.xair_address = "192.168.50.146" # setup other modules self.midi_controller = MidiController(self) @@ -139,6 +184,8 @@ def initialize_state(self): return False self.read_initial_state() + self.midi_controller.activate_bus(0) # in case of reset + self.midi_controller.activate_bus(8) # set chanel level as initial bus return True def shutdown(self): @@ -174,6 +221,27 @@ def toggle_channel_mute(self, channel): self.midi_controller.set_channel_mute( channel, self.banks[self.active_bank][channel].ch_on) + def toggle_send_mute(self, channel, bus): + """Toggle the state of a send mute.""" + if self.debug: + print('Toggle Send Mute %d %d' % (channel, bus)) + if ((self.banks[self.active_bank][channel] is not None) + and self.banks[self.active_bank][channel].enables is not None): + if self.banks[self.active_bank][channel].enables[bus] == 1: + self.banks[self.active_bank][channel].enables[bus] = 0 + self.xair_client.send( + address=self.banks[self.active_bank][channel].osc_base_addr + + '/{:0>2d}/level'.format(bus + 1), + param=0.0) + else: + self.banks[self.active_bank][channel].enables[bus] = 1 + self.xair_client.send( + address=self.banks[self.active_bank][channel].osc_base_addr + + '/{:0>2d}/level'.format(bus + 1), + param=self.banks[self.active_bank][channel].sends[bus]) + self.midi_controller.set_channel_mute(channel, \ + self.banks[self.active_bank][channel].enables[bus]) + def toggle_mpc(self): if self.mpd_playing: try: @@ -198,6 +266,15 @@ def change_fader(self, fader, delta): param=self.banks[self.active_bank][fader].fader) self.midi_controller.set_channel_fader(fader, self.banks[self.active_bank][fader].fader) + def set_fader(self, fader, value): + """Set the level of a fader.""" + if self.banks[self.active_bank][fader] is not None: + self.banks[self.active_bank][fader].fader = value + self.xair_client.send( + address=self.banks[self.active_bank][fader].osc_base_addr + '/fader', + param=self.banks[self.active_bank][fader].fader) + self.midi_controller.set_channel_fader(fader, self.banks[self.active_bank][fader].fader) + def change_bus_send(self, bus, channel, delta): """Change the level of a bus send.""" if ((self.banks[self.active_bank][channel] is not None) @@ -208,29 +285,52 @@ def change_bus_send(self, bus, channel, delta): address=self.banks[self.active_bank][channel].osc_base_addr + \ '/{:0>2d}/level'.format(bus + 1), param=self.banks[self.active_bank][channel].sends[bus]) - self.midi_controller.set_bus_send(bus, channel, self.banks[self.active_bank][channel].sends[bus]) + self.midi_controller.set_channel_fader(channel, \ + self.banks[self.active_bank][channel].sends[bus]) + + def set_bus_send(self, bus, channel, value): + """Set the level of a bus send.""" + if ((self.banks[self.active_bank][channel] is not None) + and self.banks[self.active_bank][channel].sends is not None): + self.banks[self.active_bank][channel].sends[bus] = value + self.xair_client.send( + address=self.banks[self.active_bank][channel].osc_base_addr + \ + '/{:0>2d}/level'.format(bus + 1), + param=self.banks[self.active_bank][channel].sends[bus]) + self.midi_controller.set_channel_fader(channel, \ + self.banks[self.active_bank][channel].sends[bus]) + + def change_headamp(self, fader, delta): # aka mic pre + """Change the level of a mic pre aka headamp.""" + if self.banks[self.active_bank][fader] is not None: + self.banks[self.active_bank][fader].fader = \ + min(max(0.0, self.banks[self.active_bank][fader].fader + (delta / 200)), 1.0) + self.xair_client.send( + address=self.banks[self.active_bank][fader].osc_base_addr + '/gain', + param=self.banks[self.active_bank][fader].fader) + self.midi_controller.set_channel_fader(fader, self.banks[self.active_bank][fader].fader) def set_lr_fader(self, value): self.xair_client.send(address = self.lr.osc_base_addr + '/fader', param = value) def received_osc(self, addr, value): """Process an OSC input.""" - if addr.startswith('/config/mute'): - group = int(addr[-1:]) - 1 - self.mute_groups[group].ch_on = value - self.midi_controller.set_mute_grp(group, value) - elif addr.startswith('/fx') and (addr.endswith('/par/01') or addr.endswith('/par/02')): - if self.fx_slots[int(addr[4:5]) - 1] in self._DELAY_FX_IDS: - self.midi_controller.update_tempo(value * 3) - elif addr.startswith('/fx/') and addr.endswith('/type'): - self.fx_slots[int(addr[4:5]) - 1] = value - if value in self._DELAY_FX_IDS: - # slot contains a delay, get current time value - param_id = '01' - if value == 10: - param_id = '02' - self.xair_client.send(address = '/fx/%s/par/%s' % (addr[4:5], param_id)) - else: +# if addr.startswith('/config/mute'): +# group = int(addr[-1:]) - 1 +# self.mute_groups[group].ch_on = value +# self.midi_controller.set_mute_grp(group, value) +# elif addr.startswith('/fx') and (addr.endswith('/par/01') or addr.endswith('/par/02')): +# if self.fx_slots[int(addr[4:5]) - 1] in self._DELAY_FX_IDS: +# self.midi_controller.update_tempo(value * 3) +# elif addr.startswith('/fx/') and addr.endswith('/type'): +# self.fx_slots[int(addr[4:5]) - 1] = value +# if value in self._DELAY_FX_IDS: +# # slot contains a delay, get current time value +# param_id = '01' +# if value == 10: +# param_id = '02' +# self.xair_client.send(address = '/fx/%s/par/%s' % (addr[4:5], param_id)) + if True: for i in range(0, 5): for j in range(0, 8): if self.banks[i][j] is not None and addr.startswith(self.banks[i][j].osc_base_addr): @@ -238,21 +338,27 @@ def received_osc(self, addr, value): if self.debug: print('Channel %s level %f' % (addr, value)) self.banks[i][j].fader = value - if i == self.active_bank: - self.midi_controller.set_ring(j, value) + if i == self.active_bank and self.active_bus in [8, 9]: + self.midi_controller.set_channel_fader(j, value) elif addr.endswith('/on'): # channel enable if self.debug: print('%s unMute %d' % (addr, value)) self.banks[i][j].ch_on = value - if i == self.active_bank: + if i == self.active_bank and self.active_bus in [8, 9]: self.midi_controller.set_channel_mute(j, value) elif self.banks[i][j].sends is not None and addr.endswith('/level'): if self.debug: print('%s level %f' % (addr, value)) bus = int(addr[-8:-6]) - 1 self.banks[i][j].sends[bus] = value - if i == self.active_bank: - self.midi_controller.set_bus_send(bus, j, value) + if i == self.active_bank and bus == self.active_bus: + self.midi_controller.set_channel_fader(j, value) + elif addr.endswith('/gain'): + if self.debug: + print('%s Gain level %f' % (addr, value)) + self.banks[i][j].fader = value + if i == self.active_bank: #doesn't need a bus check as only on one bus + self.midi_controller.set_channel_fader(j, value) break else: continue @@ -263,24 +369,29 @@ def read_initial_state(self): for i in range(0, 5): for j in range(0, 8): if self.banks[i][j] is not None: - self.xair_client.send(address=self.banks[i][j].osc_base_addr + '/fader') - time.sleep(0.01) - self.xair_client.send(address=self.banks[i][j].osc_base_addr + '/on') - time.sleep(0.01) - if self.banks[i][j].sends is not None: - for k in range(len(self.banks[i][j].sends)): - self.xair_client.send(address = self.banks[i][j].osc_base_addr + '/{:0>2d}/level'.format(k + 1)) - time.sleep(0.01) + if self.banks[i][j].osc_base_addr.startswith('/head'): + self.xair_client.send(address=self.banks[i][j].osc_base_addr + '/gain') + time.sleep(0.002) + else: + self.xair_client.send(address=self.banks[i][j].osc_base_addr + '/fader') + time.sleep(0.002) + self.xair_client.send(address=self.banks[i][j].osc_base_addr + '/on') + time.sleep(0.002) + if self.banks[i][j].sends is not None: + for k in range(len(self.banks[i][j].sends)): + self.xair_client.send(address=self.banks[i][j].osc_base_addr + \ + '/{:0>2d}/level'.format(k + 1)) + time.sleep(0.002) # get all mute groups - for i in range(0, 4): - self.xair_client.send(address = self.mute_groups[i].osc_base_addr) - time.sleep(0.01) + #for i in range(0, 4): + # self.xair_client.send(address = self.mute_groups[i].osc_base_addr) + # time.sleep(0.01) # get all fx types - for i in range(1, 5): - self.xair_client.send(address = '/fx/%d/type' % i) - time.sleep(0.01) + #for i in range(1, 5): + # self.xair_client.send(address = '/fx/%d/type' % i) + # time.sleep(0.01) def update_tempo(self, tempo): for i in range(0, 4): @@ -290,4 +401,60 @@ def update_tempo(self, tempo): # only delay where time is set as parameter 02 param_id = '02' self.xair_client.send(address = '/fx/%d/par/%s' % (i + 1, param_id), param = tempo / 3) - \ No newline at end of file + +# how to set up a meter subscription +# values send every 50ms for 10s +# the actual call is located in xair.py in the refresch connecton method that runs every 5 s +# +# meters 1 provide data per channel and bus +# self.xair_client.send(address="/meters", param=["/meters/1"]) # pre faid ch levels +# time.sleep(0.002) +# +# using meters 2, input levels, as these will match the headamps even if mapped to other channels +# self.xair_client.send(address="/meters", param=["/meters/2"]) +# time.sleep(0.002) +# channel levels, not entirely clear +# self.xair_client.send(address="/meters", param=["/meters/0", 7]) +# time.sleep(0.002) +#self.xair_client.send(address="/meters/13") +#time.sleep(0.002) + + def received_meters(self, addr, data): + "receive an OSC Meters packet" + data_size = struct.unpack(" 15: + break + # get the current meter as a 16bit signed int mapped to -128db to 128db + # 1/256 db resolution, aka .004 dB + # realistic values max at 0db + value = struct.unpack(" -3 and (i < 8 or i > 11): + # if clip protection is enabled and not a drum and above -3 db + active_bank = self.active_bank + fader = i + if fader < 8: + self.active_bank = 2 + else: + self.active_bank = 3 + fader = fader - 8 + self.change_headamp(fader, -1) + if self.debug: + print("Clipping Detected headamp changed to %s" % + self.banks[self.active_bank][fader].fader) + self.active_bank = active_bank + if self.debug: + #print('Meters("%s", %s) size %s length %s' % (addr, data[0], len(data[0]), data_size)) + print('Meters %s ch 8 %s %s %s' % (addr, values[7], short[7], med[7])) + #print(values) + if self.screen_obj is not None: + self.screen_obj.screen_loop() From 6a8b3751fbd79dca204170f8391ff3b10ce4affb Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 13 Aug 2021 16:44:13 -0400 Subject: [PATCH 08/25] updated button operation --- lib/midicontroller.py | 237 +++++++++++++++++++++--------------------- 1 file changed, 120 insertions(+), 117 deletions(-) diff --git a/lib/midicontroller.py b/lib/midicontroller.py index 09b5551..f76b847 100644 --- a/lib/midicontroller.py +++ b/lib/midicontroller.py @@ -61,18 +61,18 @@ class MidiController: """ MC_CHANNEL = 0 - MIDI_BUTTONS = [89, 90, 40, 41, 42, 43, 44, 45, 87, 88, 91, 92, 86, 93, 94, 95] +# MIDI_BUTTONS = [89, 90, 40, 41, 42, 43, 44, 45, 87, 88, 91, 92, 86, 93, 94, 95] + MIDI_BUTTONS = [89, 90, 40, 41, 42, 43, 44, 45, 87, 88, 91, 92, 86, 93, 94, 95, 84, 85] MIDI_PUSH = [32, 33, 34, 35, 36, 37, 38, 39] MIDI_ENCODER = [16, 17, 18, 19, 20, 21, 22, 23] MIDI_RING = [48, 49, 50, 51, 52, 53, 54, 55] - MIDI_LAYER = [84, 85] +# MIDI_LAYER = [84, 85] LED_OFF = 0 LED_BLINK = 1 LED_ON = 127 active_layer = 0 - active_bus = 0 inport = None outport = None @@ -113,11 +113,6 @@ def __init__(self, state): #exit() return - self.tempo_detector = TempoDetector(self) - self.change_layer(0) - self.activate_bank(0) - #self.activate_bus(0) - for i in range(0, 18): self.set_button(i, self.LED_OFF) # clear all buttons @@ -172,10 +167,13 @@ def midi_listener(self): delta = msg.value if delta > 64: delta = (delta - 64) * -1 - if self.active_layer == 0: + if self.state.active_bus < 7: # one of the 7 AUX bus + self.state.change_bus_send( \ + self.state.active_bus, self.MIDI_ENCODER.index(msg.control), delta) + elif self.state.active_bus == 7: # preamp gain + self.state.change_headamp(self.MIDI_ENCODER.index(msg.control), delta) + else: # channels faders and output faders self.state.change_fader(self.MIDI_ENCODER.index(msg.control), delta) - else: - self.state.change_bus_send(self.active_bus, self.MIDI_ENCODER.index(msg.control), delta) else: print('Received unknown {}'.format(msg)) elif msg.type == 'note_on' and msg.velocity == 127: @@ -185,15 +183,26 @@ def midi_listener(self): self.knob_pushed(self.MIDI_PUSH.index(msg.note)) elif msg.note in self.MIDI_BUTTONS: self.button_pushed(self.MIDI_BUTTONS.index(msg.note)) - elif msg.note in self.MIDI_LAYER: - self.change_layer(self.MIDI_LAYER.index(msg.note)) +# elif msg.note in self.MIDI_LAYER: +# self.change_layer(self.MIDI_LAYER.index(msg.note)) else: print('Received unknown {}'.format(msg)) elif msg.type == 'pitchwheel': value = (msg.pitch + 8192) / 16384 - self.state.set_lr_fader(value) + if self.state.debug: + print('Wheel set to {}'.format(msg)) + if msg.pitch > 8000: + if self.state is not None: + self.state.quit_called = True + self.cleanup_controller() + if self.state.screen_obj is not None: + self.state.screen_obj.quit() + exit() elif msg.type != 'note_off' and msg.type != 'note_on': print('Received unknown {}'.format(msg)) + if self.state.quit_called: + self.cleanup_controller() + return except KeyboardInterrupt: if self.state is not None: self.state.quit_called = True @@ -202,107 +211,102 @@ def midi_listener(self): def button_pushed(self, button): "On button press, call the relevant function" - if button == 8: - # mute grp 4 pressed - self.state.toggle_mute_group(3) - elif button == 9: - # tempo button pressed - self.tempo_detector.tap() - elif button != 10: - if self.active_layer == 0: - if button >= 11 and button <= 15: - # bank select buttons pressed - self.activate_bank(button - 11) - else: - # mute buttons pressed - self.state.toggle_channel_mute(button) + if self.state.debug: + print('Button {} pushed'.format(button)) + if button < 8: # mute button pressed, upper row + if self.state.active_bus < 7: # sending to a bus + self.state.toggle_send_mute(button, self.state.active_bus) + # skip preamps as meaningless + elif self.state.active_bus > 7: # sending to a channel + self.state.toggle_channel_mute(button) + else: # bank select button + if(self.state.mac and (button in [10, 11, 12, 13, 14])): + self.mac_button(button) else: - if button >= 11 and button <= 13: - # bank select buttons pressed - self.activate_bank(button - 11) - else: - self.active_bus = button if button < 8 else button - 6 - self.refresh_controls(self.state.active_bank) + self.activate_bus(button - 8) + + def mac_button(self, button): + "call a function for transport buttons on mac" + if button == 10: + os.system("""osascript -e 'tell application "music" to previous track'""") + elif button == 11: + os.system("""osascript -e 'tell application "music" to next track'""") + elif button == 12: + self.state.shutdown() + self.cleanup_controller() + exit() + elif button == 13: + os.system("""osascript -e 'tell application "music" to pause'""") + elif button == 14: + os.system("""osascript -e 'tell application "music" to play'""") def knob_pushed(self, knob): "On knob push reset the correct value" - if knob >= 0 and knob <= 3: - self.state.toggle_mute_group(knob) - elif knob == 7: - self.state.toggle_mpc() + # reset to unity gain + if self.state.active_bus < 7: + self.state.set_bus_send(self.state.active_bus, knob, 0.750000) + elif self.state.active_bus == 8: # channel levels + if self.state.mac and knob in [4, 5, 7]: + self.state.set_fader(knob, 0.375367) + else: + self.state.set_fader(knob, 0.750000) + elif self.state.active_bus == 9: + if knob == 6: # USB Return + self.state.set_fader(knob, 0.750000) + else: # output levels -20 db + self.state.set_fader(knob, 0.375367) + #elif self.state.active_bus == 7: # no obvious default for mic pre + #self.state.toggle_mpc() - def activate_bank(self, bank): - #print("Switching to fader bank %d" % (bank + 1)) - for i in range(0, 5): - if i == bank: - self.set_button(11 + i, True) + def activate_bus(self, bus): + "chagne the active bus" + if self.state.debug: + print('Activating bus {}'.format(bus)) + # set LEDs and layer + if self.state.active_bus == bus and bus != 9: # button 9 has only one layer + # switch layers for buttons with layers + if self.active_layer == 0: + self.set_button(bus + 8, self.LED_BLINK) + self.active_layer = 1 else: - self.set_button(11 + i, False) - if self.state.active_bank != bank: - self.refresh_controls(bank) - self.state.active_bank = bank - - def change_layer(self, layer): - if layer == 0: - self.outport.send(Message('note_on', channel = self.MC_CHANNEL, note = self.MIDI_LAYER[0], velocity = self.LED_ON)) - self.outport.send(Message('note_on', channel = self.MC_CHANNEL, note = self.MIDI_LAYER[1], velocity = self.LED_OFF)) - self.activate_bank(self.state.active_bank) + self.set_button(bus + 8, self.LED_ON) + self.active_layer = 0 + else: # switching to a different bus, or one that only has a single layer + self.set_button(self.state.active_bus + 8, self.LED_OFF) + self.set_button(bus + 8, self.LED_ON) + self.active_layer = 0 + # set bus and bank + self.state.active_bus = bus + if bus == 7: # set mic pre banks + self.state.active_bank = self.active_layer + 2 + elif bus == 9: # set outputs bank + self.state.active_bank = 4 else: - self.outport.send(Message('note_on', channel = self.MC_CHANNEL, note = self.MIDI_LAYER[0], velocity = self.LED_OFF)) - self.outport.send(Message('note_on', channel = self.MC_CHANNEL, note = self.MIDI_LAYER[1], velocity = self.LED_ON)) - if self.state.active_bank > 2: - self.activate_bank(2) - self.active_layer = layer - self.refresh_controls(self.state.active_bank) - - def refresh_controls(self, bank): - if self.active_layer == 0: - for i in range(0, 8): - if self.state.banks[bank][i] != None: - # send fader for Layer A - self.set_ring(i, self.state.banks[bank][i].fader) - self.set_channel_mute(i, self.state.banks[bank][i].on) - else: - # unassigned channel, disbale encoder and button - self.set_ring(i, -1) - self.set_button(i, False) - else: - for i in range(0, 10): - if i < 8: - btn = i - else: - # sends 9 & 10 need other button numbers - btn = i + 6 - if i != self.active_bus: - self.set_button(btn, False) - else: - self.set_button(btn, True) - for i in range(0, 8): - if self.state.banks[bank][i] != None and self.state.banks[bank][i].sends != None: - self.set_ring(i, self.state.banks[bank][i].sends[self.active_bus]) - else: - self.set_ring(i, -1) - # set indicator for mute group 4 - self.set_mute_grp(3, self.state.mute_groups[3].on) + self.state.active_bank = self.active_layer - def set_mute_grp(self, group, on): - if group == 3: - # we only need to update this group as all others have no led indicator - self.set_button(8, on == 1) + # reset lights + for i in range(0, 8): + if self.state.banks[self.state.active_bank][i] is not None: + if self.state.active_bus > 6: # send fader or preamp level + self.set_channel_fader(i, self.state.banks[self.state.active_bank][i].fader) + self.set_channel_mute(i, self.state.banks[self.state.active_bank][i].ch_on) + else: # send bus send + if self.state.banks[self.state.active_bank][i].sends is not None: + self.set_channel_fader(i, self.state.banks[ \ + self.state.active_bank][i].sends[self.state.active_bus]) + self.set_channel_mute(i, self.state.banks[ \ + self.state.active_bank][i].enables[self.state.active_bus]) + else: # unassigned channel, disbale encoder and button + self.set_channel_fader(i, -1) + self.set_button(i, self.LED_OFF) - def set_channel_mute(self, channel, on): + def set_channel_mute(self, channel, ch_on): "Send the mute value to the button" - if self.active_layer == 0: - self.set_button(channel, on == 0) + self.set_button(channel, self.LED_ON if ch_on == 0 else self.LED_OFF) def set_channel_fader(self, channel, value): "Send the fader value to the encoder ring" - if self.active_layer == 0: - self.set_ring(channel, value) - - def set_bus_send(self, bus, knob, value): - if self.active_layer == 1 and self.active_bus == bus: - self.set_ring(knob, value) + self.set_ring(channel, value) def set_ring(self, ring, value): "Turn on the appropriate LEDs on the encoder ring." @@ -311,24 +315,23 @@ def set_ring(self, ring, value): # values below 0 mean disabled if value >= 0.0: self.outport.send(Message('control_change', channel=self.MC_CHANNEL, - control = self.MIDI_RING[ring], - value = 33 + round(value * 11))) + control=self.MIDI_RING[ring], + value=self.map_lights(value))) +# value=33 + round(value * 11))) else: self.outport.send(Message('control_change', channel=self.MC_CHANNEL, control=self.MIDI_RING[ring], value=0)) - def set_button(self, button, on): - if on == True: - self.outport.send(Message('note_on', channel = self.MC_CHANNEL, note = self.MIDI_BUTTONS[button], velocity = self.LED_ON)) - else: - self.outport.send(Message('note_on', channel = self.MC_CHANNEL, note = self.MIDI_BUTTONS[button], velocity = self.LED_OFF)) + def map_lights(self, value): + "map the (0:1) range of fader values to ring light patterns" + # for faders -oo to -10.2db map to single lights while -10 to +10 map to the fan + value = 1 + round(value * 21) + if value > 11: + value = value + 22 + return value - def tempo_led(self, on): - self.set_button(9, on) - - def update_tempo(self, tempo, detected = False): - if detected == True: - self.state.update_tempo(tempo) - else: - self.tempo_detector.current_tempo = tempo + def set_button(self, button, ch_on): + "Turn the button LED on or off" + self.outport.send(Message('note_on', channel=self.MC_CHANNEL, + note=self.MIDI_BUTTONS[button], velocity=ch_on)) From dcfa78e5c1a8596893630647909b251f4f779082 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 13 Aug 2021 16:52:21 -0400 Subject: [PATCH 09/25] updated button operation, added metering and level output --- lib/xair.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/xair.py b/lib/xair.py index 0537b94..70f2336 100644 --- a/lib/xair.py +++ b/lib/xair.py @@ -2,6 +2,7 @@ import time import threading import socket +import netifaces from pythonosc.dispatcher import Dispatcher from pythonosc.osc_server import BlockingOSCUDPServer from pythonosc.osc_message import OscMessage @@ -91,10 +92,12 @@ def msg_handler(self, addr, *data): return #print 'OSCReceived("%s", %s, %s)' % (addr, tags, data) if addr.endswith('/fader') or addr.endswith('/on') or addr.endswith('/level') or \ - addr.startswith('/config/mute') or addr.startswith('/fx/'): + addr.startswith('/config/mute') or addr.endswith('/gain'): self.state.received_osc(addr, data[0]) elif addr == '/xinfo': self.info_response = data[:] + elif addr.startswith('/meters'): + self.state.received_meters(addr, data) else: #if self.state.debug and addr.start: print('OSCReceived("%s", %s)' % (addr, data)) @@ -110,6 +113,16 @@ def refresh_connection(self): # the main loop try: while not self.state.quit_called and self.server is not None: self.server.send_message("/xremotenfb", None) + if self.state.levels: + # using input levels, as these match the headamps when channels are remapped + time.sleep(0.002) + self.send(address="/meters", param=["/meters/2"]) + if self.state.clip: # seems to crash if clipping protection runs for too long + if self.state.debug: + print("start auto level") + self.state.clip = False + if self.state.screen_obj is not None: + self.state.screen_obj.gpio_button[1].disable[0] = 1 time.sleep(self._REFRESH_TIMEOUT) if self.state.quit_called: return @@ -122,14 +135,19 @@ def refresh_connection(self): # the main loop def send(self, address, param=None): "Call the OSC agent to send a message" self.server.send_message(address, param) - + def find_mixer(): "Search for the IP address of the XAir mixer" print('Searching for mixer...') client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) client.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True) - client.settimeout(15) - client.sendto("/xinfo\0\0".encode(), ("", XAirClient.XAIR_PORT)) + client.settimeout(5) + for iface in netifaces.interfaces(): + try: + bcast = netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['broadcast'] + client.sendto("/xinfo\0\0".encode(), (bcast, XAirClient.XAIR_PORT)) + except: + pass try: response = OscMessage(client.recv(512)) except socket.timeout: From afe0d0b31fd927ecb00d22d569a9aa07938f41ef Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 13 Aug 2021 16:53:15 -0400 Subject: [PATCH 10/25] added netifaces requirement --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 4a16393..3870e78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ mido python-rtmidi python-osc +netifaces From e5696bc23c8a9a14bbf9b1095db6b5a58556b9e7 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 13 Aug 2021 16:59:59 -0400 Subject: [PATCH 11/25] updated main to add additional arguments and additional help text --- xair-remote.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/xair-remote.py b/xair-remote.py index 565b3c0..a98e5af 100755 --- a/xair-remote.py +++ b/xair-remote.py @@ -6,12 +6,32 @@ if __name__ == '__main__': PARSER = argparse.ArgumentParser(description=""" Remote control X-Air mixers with an X-Touch midi controller. - """) + + The X-Touch is setup so that the encoders are configured as faders on any of + 10 banks with the first row of button configured as mutes for the selected + bank. Banks are selected with the buttons in the lower row and the layer + buttons. Most banks have two layers, the second access by pressing the + button a second time, causing the button to blink. The fader is used as a + master exit control. Pressing an encoder returns the level to unity gain, + not used for mic pre. + """, + epilog=""" + Bank 1-6 - Aux Bus 1-6 levels for Channels 1-8 and 9-16. + Bank 7 - Aux Bus 7 aka FX 1 for Channels 1-8 and 9-16. + Bank 8 - Mic PreAmp levels for Channels 1-8 and 9-16. + Layer A - Main LR Mix levels of Channels 1-8 and 9-16 on second layer. + Layer B - Aux Bus 1-6 output levels, USB IN Gain, Main/LR Bus output level. + """, + formatter_class=argparse.RawDescriptionHelpFormatter) PARSER.add_argument('xair_address', help='ip address of your X-Air mixer (optional)', nargs='?') PARSER.add_argument('-m', '--monitor', help='monitor X-Touch connection and exit when disconnected', action="store_true") PARSER.add_argument('-d', '--debug', help='enable debug output', action="store_true") + PARSER.add_argument('-l', '--levels', help='get levels from the mixer', action="store_true") + PARSER.add_argument('-c', '--clip', help='enabling auto leveling to avoid clipping', + action="store_true") + PARSER.add_argument('-a', '--mac', help="use alternate mapping for mac", action="store_true") ARGS = PARSER.parse_args() STATE = MixerState(ARGS) From 512f215a64797587c3460bdc8f1ef99c4ed7439f Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Thu, 26 Aug 2021 16:57:56 -0400 Subject: [PATCH 12/25] updated exit condition --- lib/midicontroller.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/midicontroller.py b/lib/midicontroller.py index f76b847..05a8714 100644 --- a/lib/midicontroller.py +++ b/lib/midicontroller.py @@ -192,12 +192,14 @@ def midi_listener(self): if self.state.debug: print('Wheel set to {}'.format(msg)) if msg.pitch > 8000: + self.cleanup_controller() if self.state is not None: self.state.quit_called = True - self.cleanup_controller() - if self.state.screen_obj is not None: - self.state.screen_obj.quit() - exit() + if self.state.screen_obj is not None: + self.state.screen_obj.quit() + else: + # we can't assert a quit signal so simply exit + exit() elif msg.type != 'note_off' and msg.type != 'note_on': print('Received unknown {}'.format(msg)) if self.state.quit_called: From d094fd955b6967bde804b04b56d692e3300db9cc Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Sun, 29 Aug 2021 16:04:32 -0400 Subject: [PATCH 13/25] updated exit code --- lib/xair.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/xair.py b/lib/xair.py index 70f2336..028938a 100644 --- a/lib/xair.py +++ b/lib/xair.py @@ -80,10 +80,9 @@ def stop_server(self): def quit(self): if self.state is not None: - self.state.quit_called = True - if self.state.midi_controller is not None: - self.state.midi_controller.cleanup_controller() - self.stop_server() + self.state.shutdown() + else: + self.stop_server() def msg_handler(self, addr, *data): "Dispatch received OSC messages based on message type." @@ -125,6 +124,7 @@ def refresh_connection(self): # the main loop self.state.screen_obj.gpio_button[1].disable[0] = 1 time.sleep(self._REFRESH_TIMEOUT) if self.state.quit_called: + self.quit() return except KeyboardInterrupt: self.quit() From 0fe75138edc1ee3d440abe42d409c2a96d7ada70 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Sun, 29 Aug 2021 16:26:25 -0400 Subject: [PATCH 14/25] updated exit code to return on error leaving exit() to other modules --- lib/xair.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/xair.py b/lib/xair.py index 028938a..bffb981 100644 --- a/lib/xair.py +++ b/lib/xair.py @@ -63,7 +63,6 @@ def validate_connection(self): if self.server is not None: self.server.shutdown() self.server = None - #exit() def run_server(self): "Start the OSC communications agent in a seperate thread." @@ -71,7 +70,6 @@ def run_server(self): self.server.serve_forever() except KeyboardInterrupt: self.quit() - exit() def stop_server(self): if self.server is not None: @@ -128,7 +126,6 @@ def refresh_connection(self): # the main loop return except KeyboardInterrupt: self.quit() - exit() except socket.error: self.quit() From 35f3334350cb7459e8bc095ffe63db19b0b8d67b Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Thu, 2 Sep 2021 20:13:52 -0400 Subject: [PATCH 15/25] Initial creation of a simple config file that uses the 16 buttons as channel mutes and the two layer buttons to select the bank controlled by encoders --- simple.json | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 simple.json diff --git a/simple.json b/simple.json new file mode 100644 index 0000000..7646e75 --- /dev/null +++ b/simple.json @@ -0,0 +1,66 @@ +{ + "A": { + "encoders": [ + ["fader", "/ch/01/mix", "0.750000"], + ["fader", "/ch/02/mix", "0.750000"], + ["fader", "/ch/03/mix", "0.750000"], + ["fader", "/ch/04/mix", "0.750000"], + ["fader", "/ch/05/mix", "0.750000"], + ["fader", "/ch/06/mix", "0.750000"], + ["fader", "/ch/07/mix", "0.750000"], + ["fader", "/ch/08/mix", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/01/mix", "on", "State"], + ["mute", "/ch/02/mix", "on", "State"], + ["mute", "/ch/03/mix", "on", "State"], + ["mute", "/ch/04/mix", "on", "State"], + ["mute", "/ch/05/mix", "on", "State"], + ["mute", "/ch/06/mix", "on", "State"], + ["mute", "/ch/07/mix", "on", "State"], + ["mute", "/ch/08/mix", "on", "State"], + ["mute", "/ch/09/mix", "on", "State"], + ["mute", "/ch/10/mix", "on", "State"], + ["mute", "/ch/11/mix", "on", "State"], + ["mute", "/ch/12/mix", "on", "State"], + ["mute", "/ch/13/mix", "on", "State"], + ["mute", "/ch/14/mix", "on", "State"], + ["mute", "/ch/15/mix", "on", "State"], + ["mute", "/ch/16/mix", "on", "State"], + ["layer", "A", "On"], + ["layer", "B", "Off"] + ] + }, + "B": { + "encoders": [ + ["fader", "/ch/09/mix", "0.750000"], + ["fader", "/ch/10/mix", "0.750000"], + ["fader", "/ch/11/mix", "0.750000"], + ["fader", "/ch/12/mix", "0.750000"], + ["fader", "/ch/13/mix", "0.750000"], + ["fader", "/ch/14/mix", "0.750000"], + ["fader", "/ch/15/mix", "0.750000"], + ["fader", "/ch/16/mix", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/01/mix", "on", "State"], + ["mute", "/ch/02/mix", "on", "State"], + ["mute", "/ch/03/mix", "on", "State"], + ["mute", "/ch/04/mix", "on", "State"], + ["mute", "/ch/05/mix", "on", "State"], + ["mute", "/ch/06/mix", "on", "State"], + ["mute", "/ch/07/mix", "on", "State"], + ["mute", "/ch/08/mix", "on", "State"], + ["mute", "/ch/09/mix", "on", "State"], + ["mute", "/ch/10/mix", "on", "State"], + ["mute", "/ch/11/mix", "on", "State"], + ["mute", "/ch/12/mix", "on", "State"], + ["mute", "/ch/13/mix", "on", "State"], + ["mute", "/ch/14/mix", "on", "State"], + ["mute", "/ch/15/mix", "on", "State"], + ["mute", "/ch/16/mix", "on", "State"], + ["layer", "A", "Off"], + ["layer", "B", "On"] + ] + } +} \ No newline at end of file From 197e877c82ef6ef0962163462dfb022e6879b035 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Thu, 2 Sep 2021 20:14:23 -0400 Subject: [PATCH 16/25] Initial creation of a config file that enables control of mix levels and monitor levels for all channels. --- monitors.json | 610 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 610 insertions(+) create mode 100644 monitors.json diff --git a/monitors.json b/monitors.json new file mode 100644 index 0000000..5938c45 --- /dev/null +++ b/monitors.json @@ -0,0 +1,610 @@ +{ + "A": { + "encoders": [ + ["fader", "/ch/01/mix", "0.750000"], + ["fader", "/ch/02/mix", "0.750000"], + ["fader", "/ch/03/mix", "0.750000"], + ["fader", "/ch/04/mix", "0.750000"], + ["fader", "/ch/05/mix", "0.750000"], + ["fader", "/ch/06/mix", "0.750000"], + ["fader", "/ch/07/mix", "0.750000"], + ["fader", "/ch/08/mix", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/01/mix", "on", "State"], + ["mute", "/ch/02/mix", "on", "State"], + ["mute", "/ch/03/mix", "on", "State"], + ["mute", "/ch/04/mix", "on", "State"], + ["mute", "/ch/05/mix", "on", "State"], + ["mute", "/ch/06/mix", "on", "State"], + ["mute", "/ch/07/mix", "on", "State"], + ["mute", "/ch/08/mix", "on", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "Aprime", "On"], + ["layer", "B", "Off"] + ] + }, + "Aprime": { + "encoders": [ + ["fader", "/ch/09/mix", "0.750000"], + ["fader", "/ch/10/mix", "0.750000"], + ["fader", "/ch/11/mix", "0.750000"], + ["fader", "/ch/12/mix", "0.750000"], + ["fader", "/ch/13/mix", "0.750000"], + ["fader", "/ch/14/mix", "0.750000"], + ["fader", "/ch/15/mix", "0.750000"], + ["fader", "/ch/16/mix", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/09/mix", "on", "State"], + ["mute", "/ch/10/mix", "on", "State"], + ["mute", "/ch/11/mix", "on", "State"], + ["mute", "/ch/12/mix", "on", "State"], + ["mute", "/ch/13/mix", "on", "State"], + ["mute", "/ch/14/mix", "on", "State"], + ["mute", "/ch/15/mix", "on", "State"], + ["mute", "/ch/16/mix", "on", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Blink"], + ["layer", "B", "Off"] + ] + }, + "B": { + "encoders": [ + ["fader", "/bus/1/mix", "0.375367"], + ["fader", "/bus/2/mix", "0.375367"], + ["fader", "/bus/3/mix", "0.375367"], + ["fader", "/bus/4/mix", "0.375367"], + ["fader", "/bus/5/mix", "0.375367"], + ["fader", "/bus/6/mix", "0.375367"], + ["fader", "/rtn/aux/mix", "0.750000"], + ["fader", "/lr/mix", "0.375367"] + ], + "buttons": [ + ["mute", "/bus/1/mix", "on", "State"], + ["mute", "/bus/2/mix", "on", "State"], + ["mute", "/bus/3/mix", "on", "State"], + ["mute", "/bus/4/mix", "on", "State"], + ["mute", "/bus/5/mix", "on", "State"], + ["mute", "/bus/6/mix", "on", "State"], + ["mute", "/rtn/aux/mix", "on", "State"], + ["mute", "/lr/mix", "on", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "On"] + ] + }, + "1": { + "encoders": [ + ["level", "/ch/01/mix", "01", "0.750000"], + ["level", "/ch/02/mix", "01", "0.750000"], + ["level", "/ch/03/mix", "01", "0.750000"], + ["level", "/ch/04/mix", "01", "0.750000"], + ["level", "/ch/05/mix", "01", "0.750000"], + ["level", "/ch/06/mix", "01", "0.750000"], + ["level", "/ch/07/mix", "01", "0.750000"], + ["level", "/ch/08/mix", "01", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/01/mix", "01", "State"], + ["mute", "/ch/02/mix", "01", "State"], + ["mute", "/ch/03/mix", "01", "State"], + ["mute", "/ch/04/mix", "01", "State"], + ["mute", "/ch/05/mix", "01", "State"], + ["mute", "/ch/06/mix", "01", "State"], + ["mute", "/ch/07/mix", "01", "State"], + ["mute", "/ch/08/mix", "01", "State"], + ["layer", "1prime", "On"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "1prime": { + "encoders": [ + ["level", "/ch/09/mix", "01", "0.750000"], + ["level", "/ch/10/mix", "01", "0.750000"], + ["level", "/ch/11/mix", "01", "0.750000"], + ["level", "/ch/12/mix", "01", "0.750000"], + ["level", "/ch/13/mix", "01", "0.750000"], + ["level", "/ch/14/mix", "01", "0.750000"], + ["level", "/ch/15/mix", "01", "0.750000"], + ["level", "/ch/16/mix", "01", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/09/mix", "01", "State"], + ["mute", "/ch/10/mix", "01", "State"], + ["mute", "/ch/11/mix", "01", "State"], + ["mute", "/ch/12/mix", "01", "State"], + ["mute", "/ch/13/mix", "01", "State"], + ["mute", "/ch/14/mix", "01", "State"], + ["mute", "/ch/15/mix", "01", "State"], + ["mute", "/ch/16/mix", "01", "State"], + ["layer", "1", "Blink"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "2": { + "encoders": [ + ["level", "/ch/01/mix", "02", "0.750000"], + ["level", "/ch/02/mix", "02", "0.750000"], + ["level", "/ch/03/mix", "02", "0.750000"], + ["level", "/ch/04/mix", "02", "0.750000"], + ["level", "/ch/05/mix", "02", "0.750000"], + ["level", "/ch/06/mix", "02", "0.750000"], + ["level", "/ch/07/mix", "02", "0.750000"], + ["level", "/ch/08/mix", "02", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/01/mix", "02", "State"], + ["mute", "/ch/02/mix", "02", "State"], + ["mute", "/ch/03/mix", "02", "State"], + ["mute", "/ch/04/mix", "02", "State"], + ["mute", "/ch/05/mix", "02", "State"], + ["mute", "/ch/06/mix", "02", "State"], + ["mute", "/ch/07/mix", "02", "State"], + ["mute", "/ch/08/mix", "02", "State"], + ["layer", "1", "Off"], + ["layer", "2prime", "On"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "2prime": { + "encoders": [ + ["level", "/ch/09/mix", "02", "0.750000"], + ["level", "/ch/10/mix", "02", "0.750000"], + ["level", "/ch/11/mix", "02", "0.750000"], + ["level", "/ch/12/mix", "02", "0.750000"], + ["level", "/ch/13/mix", "02", "0.750000"], + ["level", "/ch/14/mix", "02", "0.750000"], + ["level", "/ch/15/mix", "02", "0.750000"], + ["level", "/ch/16/mix", "02", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/09/mix", "02", "State"], + ["mute", "/ch/10/mix", "02", "State"], + ["mute", "/ch/11/mix", "02", "State"], + ["mute", "/ch/12/mix", "02", "State"], + ["mute", "/ch/13/mix", "02", "State"], + ["mute", "/ch/14/mix", "02", "State"], + ["mute", "/ch/15/mix", "02", "State"], + ["mute", "/ch/16/mix", "02", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Blink"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "3": { + "encoders": [ + ["level", "/ch/01/mix", "03", "0.750000"], + ["level", "/ch/02/mix", "03", "0.750000"], + ["level", "/ch/03/mix", "03", "0.750000"], + ["level", "/ch/04/mix", "03", "0.750000"], + ["level", "/ch/05/mix", "03", "0.750000"], + ["level", "/ch/06/mix", "03", "0.750000"], + ["level", "/ch/07/mix", "03", "0.750000"], + ["level", "/ch/08/mix", "03", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/01/mix", "03", "State"], + ["mute", "/ch/02/mix", "03", "State"], + ["mute", "/ch/03/mix", "03", "State"], + ["mute", "/ch/04/mix", "03", "State"], + ["mute", "/ch/05/mix", "03", "State"], + ["mute", "/ch/06/mix", "03", "State"], + ["mute", "/ch/07/mix", "03", "State"], + ["mute", "/ch/08/mix", "03", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3prime", "On"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "3prime": { + "encoders": [ + ["level", "/ch/09/mix", "03", "0.750000"], + ["level", "/ch/10/mix", "03", "0.750000"], + ["level", "/ch/11/mix", "03", "0.750000"], + ["level", "/ch/12/mix", "03", "0.750000"], + ["level", "/ch/13/mix", "03", "0.750000"], + ["level", "/ch/14/mix", "03", "0.750000"], + ["level", "/ch/15/mix", "03", "0.750000"], + ["level", "/ch/16/mix", "03", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/09/mix", "03", "State"], + ["mute", "/ch/10/mix", "03", "State"], + ["mute", "/ch/11/mix", "03", "State"], + ["mute", "/ch/12/mix", "03", "State"], + ["mute", "/ch/13/mix", "03", "State"], + ["mute", "/ch/14/mix", "03", "State"], + ["mute", "/ch/15/mix", "03", "State"], + ["mute", "/ch/16/mix", "03", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Blink"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "4": { + "encoders": [ + ["level", "/ch/01/mix", "04", "0.750000"], + ["level", "/ch/02/mix", "04", "0.750000"], + ["level", "/ch/03/mix", "04", "0.750000"], + ["level", "/ch/04/mix", "04", "0.750000"], + ["level", "/ch/05/mix", "04", "0.750000"], + ["level", "/ch/06/mix", "04", "0.750000"], + ["level", "/ch/07/mix", "04", "0.750000"], + ["level", "/ch/08/mix", "04", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/01/mix", "04", "State"], + ["mute", "/ch/02/mix", "04", "State"], + ["mute", "/ch/03/mix", "04", "State"], + ["mute", "/ch/04/mix", "04", "State"], + ["mute", "/ch/05/mix", "04", "State"], + ["mute", "/ch/06/mix", "04", "State"], + ["mute", "/ch/07/mix", "04", "State"], + ["mute", "/ch/08/mix", "04", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4prime", "On"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "4prime": { + "encoders": [ + ["level", "/ch/09/mix", "04", "0.750000"], + ["level", "/ch/10/mix", "04", "0.750000"], + ["level", "/ch/11/mix", "04", "0.750000"], + ["level", "/ch/12/mix", "04", "0.750000"], + ["level", "/ch/13/mix", "04", "0.750000"], + ["level", "/ch/14/mix", "04", "0.750000"], + ["level", "/ch/15/mix", "04", "0.750000"], + ["level", "/ch/16/mix", "04", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/09/mix", "04", "State"], + ["mute", "/ch/10/mix", "04", "State"], + ["mute", "/ch/11/mix", "04", "State"], + ["mute", "/ch/12/mix", "04", "State"], + ["mute", "/ch/13/mix", "04", "State"], + ["mute", "/ch/14/mix", "04", "State"], + ["mute", "/ch/15/mix", "04", "State"], + ["mute", "/ch/16/mix", "04", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Blink"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "5": { + "encoders": [ + ["level", "/ch/01/mix", "05", "0.750000"], + ["level", "/ch/02/mix", "05", "0.750000"], + ["level", "/ch/03/mix", "05", "0.750000"], + ["level", "/ch/04/mix", "05", "0.750000"], + ["level", "/ch/05/mix", "05", "0.750000"], + ["level", "/ch/06/mix", "05", "0.750000"], + ["level", "/ch/07/mix", "05", "0.750000"], + ["level", "/ch/08/mix", "05", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/01/mix", "05", "State"], + ["mute", "/ch/02/mix", "05", "State"], + ["mute", "/ch/03/mix", "05", "State"], + ["mute", "/ch/04/mix", "05", "State"], + ["mute", "/ch/05/mix", "05", "State"], + ["mute", "/ch/06/mix", "05", "State"], + ["mute", "/ch/07/mix", "05", "State"], + ["mute", "/ch/08/mix", "05", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5prime", "On"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "5prime": { + "encoders": [ + ["level", "/ch/09/mix", "05", "0.750000"], + ["level", "/ch/10/mix", "05", "0.750000"], + ["level", "/ch/11/mix", "05", "0.750000"], + ["level", "/ch/12/mix", "05", "0.750000"], + ["level", "/ch/13/mix", "05", "0.750000"], + ["level", "/ch/14/mix", "05", "0.750000"], + ["level", "/ch/15/mix", "05", "0.750000"], + ["level", "/ch/16/mix", "05", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/09/mix", "05", "State"], + ["mute", "/ch/10/mix", "05", "State"], + ["mute", "/ch/11/mix", "05", "State"], + ["mute", "/ch/12/mix", "05", "State"], + ["mute", "/ch/13/mix", "05", "State"], + ["mute", "/ch/14/mix", "05", "State"], + ["mute", "/ch/15/mix", "05", "State"], + ["mute", "/ch/16/mix", "05", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "On"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "6": { + "encoders": [ + ["level", "/ch/01/mix", "06", "0.750000"], + ["level", "/ch/02/mix", "06", "0.750000"], + ["level", "/ch/03/mix", "06", "0.750000"], + ["level", "/ch/04/mix", "06", "0.750000"], + ["level", "/ch/05/mix", "06", "0.750000"], + ["level", "/ch/06/mix", "06", "0.750000"], + ["level", "/ch/07/mix", "06", "0.750000"], + ["level", "/ch/08/mix", "06", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/01/mix", "06", "State"], + ["mute", "/ch/02/mix", "06", "State"], + ["mute", "/ch/03/mix", "06", "State"], + ["mute", "/ch/04/mix", "06", "State"], + ["mute", "/ch/05/mix", "06", "State"], + ["mute", "/ch/06/mix", "06", "State"], + ["mute", "/ch/07/mix", "06", "State"], + ["mute", "/ch/08/mix", "06", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6prime", "On"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "6prime": { + "encoders": [ + ["level", "/ch/09/mix", "06", "0.750000"], + ["level", "/ch/10/mix", "06", "0.750000"], + ["level", "/ch/11/mix", "06", "0.750000"], + ["level", "/ch/12/mix", "06", "0.750000"], + ["level", "/ch/13/mix", "06", "0.750000"], + ["level", "/ch/14/mix", "06", "0.750000"], + ["level", "/ch/15/mix", "06", "0.750000"], + ["level", "/ch/16/mix", "06", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/09/mix", "06", "State"], + ["mute", "/ch/10/mix", "06", "State"], + ["mute", "/ch/11/mix", "06", "State"], + ["mute", "/ch/12/mix", "06", "State"], + ["mute", "/ch/13/mix", "06", "State"], + ["mute", "/ch/14/mix", "06", "State"], + ["mute", "/ch/15/mix", "06", "State"], + ["mute", "/ch/16/mix", "06", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Blink"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "FX1": { + "encoders": [ + ["level", "/ch/01/mix", "07", "0.750000"], + ["level", "/ch/02/mix", "07", "0.750000"], + ["level", "/ch/03/mix", "07", "0.750000"], + ["level", "/ch/04/mix", "07", "0.750000"], + ["level", "/ch/05/mix", "07", "0.750000"], + ["level", "/ch/06/mix", "07", "0.750000"], + ["level", "/ch/07/mix", "07", "0.750000"], + ["level", "/ch/08/mix", "07", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/01/mix", "07", "State"], + ["mute", "/ch/02/mix", "07", "State"], + ["mute", "/ch/03/mix", "07", "State"], + ["mute", "/ch/04/mix", "07", "State"], + ["mute", "/ch/05/mix", "07", "State"], + ["mute", "/ch/06/mix", "07", "State"], + ["mute", "/ch/07/mix", "07", "State"], + ["mute", "/ch/08/mix", "07", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1prime", "On"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "FX1prime": { + "encoders": [ + ["level", "/ch/09/mix", "07", "0.750000"], + ["level", "/ch/10/mix", "07", "0.750000"], + ["level", "/ch/11/mix", "07", "0.750000"], + ["level", "/ch/12/mix", "07", "0.750000"], + ["level", "/ch/13/mix", "07", "0.750000"], + ["level", "/ch/14/mix", "07", "0.750000"], + ["level", "/ch/15/mix", "07", "0.750000"], + ["level", "/ch/16/mix", "07", "0.750000"] + ], + "buttons": [ + ["mute", "/ch/09/mix", "07", "State"], + ["mute", "/ch/10/mix", "07", "State"], + ["mute", "/ch/11/mix", "07", "State"], + ["mute", "/ch/12/mix", "07", "State"], + ["mute", "/ch/13/mix", "07", "State"], + ["mute", "/ch/14/mix", "07", "State"], + ["mute", "/ch/15/mix", "07", "State"], + ["mute", "/ch/16/mix", "07", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Blink"], + ["layer", "Gain", "Off"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "Gain": { + "encoders": [ + ["gain", "/headamp/01"], + ["gain", "/headamp/02"], + ["gain", "/headamp/03"], + ["gain", "/headamp/04"], + ["gain", "/headamp/05"], + ["gain", "/headamp/06"], + ["gain", "/headamp/07"], + ["gain", "/headamp/08"] + ], + "buttons": [ + ["none", "Off"], + ["none", "Off"], + ["quit", "1", "State"], + ["quit", "2", "State"], + ["none", "Off"], + ["none", "Off"], + ["clip", "State"], + ["record", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gainprime", "On"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + }, + "Gainprime": { + "encoders": [ + ["gain", "/headamp/09"], + ["gain", "/headamp/10"], + ["gain", "/headamp/11"], + ["gain", "/headamp/12"], + ["gain", "/headamp/13"], + ["gain", "/headamp/14"], + ["gain", "/headamp/15"], + ["gain", "/headamp/16"] + ], + "buttons": [ + ["none", "Off"], + ["none", "Off"], + ["quit", "1", "State"], + ["quit", "2", "State"], + ["none", "Off"], + ["none", "Off"], + ["clip", "State"], + ["record", "State"], + ["layer", "1", "Off"], + ["layer", "2", "Off"], + ["layer", "3", "Off"], + ["layer", "4", "Off"], + ["layer", "5", "Off"], + ["layer", "6", "Off"], + ["layer", "FX1", "Off"], + ["layer", "Gain", "Blink"], + ["layer", "A", "Off"], + ["layer", "B", "Off"] + ] + } +} \ No newline at end of file From 4fec800a9e4c95cf0c6a27e44bb8b99b581219e1 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Thu, 2 Sep 2021 20:15:49 -0400 Subject: [PATCH 17/25] Almost complete rewrite to move all control definition to a config file and to restructure the way state is stored to be more flexible --- lib/midicontroller.py | 153 +++-------- lib/mixerstate.py | 598 ++++++++++++++++++++++++++---------------- lib/xair.py | 4 +- xair-remote.py | 2 +- 4 files changed, 410 insertions(+), 347 deletions(-) diff --git a/lib/midicontroller.py b/lib/midicontroller.py index 05a8714..2d32c90 100644 --- a/lib/midicontroller.py +++ b/lib/midicontroller.py @@ -51,22 +51,20 @@ class MidiController: Handles communication with the MIDI surface. X-Touch Mini must be in MC mode! - LA/LB: Note 84/85 Fader 1-8: CC16 - CC23, Note 32 - 39 on push Turn right: Values 1 - 10 (Increment) Turn left: Values 65 - 72 (Decrement) Buttons 1-8: Note 89, 90, 40, 41, 42, 43, 44, 45 Buttons 9-16: Note 87, 88, 91, 92, 86, 93, 94, 95 + Buttons LA/LB (aka 17/18): Note 84/85 Master Fader: Pitch Wheel """ MC_CHANNEL = 0 -# MIDI_BUTTONS = [89, 90, 40, 41, 42, 43, 44, 45, 87, 88, 91, 92, 86, 93, 94, 95] MIDI_BUTTONS = [89, 90, 40, 41, 42, 43, 44, 45, 87, 88, 91, 92, 86, 93, 94, 95, 84, 85] MIDI_PUSH = [32, 33, 34, 35, 36, 37, 38, 39] MIDI_ENCODER = [16, 17, 18, 19, 20, 21, 22, 23] MIDI_RING = [48, 49, 50, 51, 52, 53, 54, 55] -# MIDI_LAYER = [84, 85] LED_OFF = 0 LED_BLINK = 1 @@ -89,7 +87,6 @@ def __init__(self, state): print('Error: Can not open MIDI input port ' + name) self.state.quit_called = True self.state = None - #exit() return break @@ -102,7 +99,6 @@ def __init__(self, state): print('Error: Can not open MIDI input port ' + name) self.state.quit_called = True self.state = None - #exit() return break @@ -110,7 +106,6 @@ def __init__(self, state): print('X-Touch Mini not found. Make sure device is connected!') self.state.quit_called = True self.cleanup_controller() - #exit() return for i in range(0, 18): @@ -151,7 +146,9 @@ def monitor_ports(self): time.sleep(1) except KeyboardInterrupt: if self.state is not None: - self.state.quit_called = True + self.state.shutdown() + else: + self.cleanup_controller() exit() def midi_listener(self): @@ -167,24 +164,22 @@ def midi_listener(self): delta = msg.value if delta > 64: delta = (delta - 64) * -1 - if self.state.active_bus < 7: # one of the 7 AUX bus - self.state.change_bus_send( \ - self.state.active_bus, self.MIDI_ENCODER.index(msg.control), delta) - elif self.state.active_bus == 7: # preamp gain - self.state.change_headamp(self.MIDI_ENCODER.index(msg.control), delta) - else: # channels faders and output faders - self.state.change_fader(self.MIDI_ENCODER.index(msg.control), delta) + encoder_num = self.MIDI_ENCODER.index(msg.control) + LED = self.state.encoder_turn(encoder_num, delta) + self.set_ring(encoder_num, LED) else: print('Received unknown {}'.format(msg)) elif msg.type == 'note_on' and msg.velocity == 127: if self.state.debug: print('Note {} pushed'.format(msg.note)) if msg.note in self.MIDI_PUSH: - self.knob_pushed(self.MIDI_PUSH.index(msg.note)) + encoder_num = self.MIDI_PUSH.index(msg.note) + LED = self.state.encoder_press(encoder_num) + self.set_ring(encoder_num, LED) elif msg.note in self.MIDI_BUTTONS: - self.button_pushed(self.MIDI_BUTTONS.index(msg.note)) -# elif msg.note in self.MIDI_LAYER: -# self.change_layer(self.MIDI_LAYER.index(msg.note)) + button_num = self.MIDI_BUTTONS.index(msg.note) + LED = self.state.button_press(button_num) + self.set_channel_mute(button_num, LED) else: print('Received unknown {}'.format(msg)) elif msg.type == 'pitchwheel': @@ -192,125 +187,43 @@ def midi_listener(self): if self.state.debug: print('Wheel set to {}'.format(msg)) if msg.pitch > 8000: - self.cleanup_controller() if self.state is not None: - self.state.quit_called = True - if self.state.screen_obj is not None: - self.state.screen_obj.quit() + self.state.shutdown() else: - # we can't assert a quit signal so simply exit - exit() + self.cleanup_controller() + exit() elif msg.type != 'note_off' and msg.type != 'note_on': print('Received unknown {}'.format(msg)) if self.state.quit_called: - self.cleanup_controller() + self.state.shutdown() return except KeyboardInterrupt: if self.state is not None: - self.state.quit_called = True - self.cleanup_controller() - exit() - - def button_pushed(self, button): - "On button press, call the relevant function" - if self.state.debug: - print('Button {} pushed'.format(button)) - if button < 8: # mute button pressed, upper row - if self.state.active_bus < 7: # sending to a bus - self.state.toggle_send_mute(button, self.state.active_bus) - # skip preamps as meaningless - elif self.state.active_bus > 7: # sending to a channel - self.state.toggle_channel_mute(button) - else: # bank select button - if(self.state.mac and (button in [10, 11, 12, 13, 14])): - self.mac_button(button) + self.state.shutdown() else: - self.activate_bus(button - 8) - - def mac_button(self, button): - "call a function for transport buttons on mac" - if button == 10: - os.system("""osascript -e 'tell application "music" to previous track'""") - elif button == 11: - os.system("""osascript -e 'tell application "music" to next track'""") - elif button == 12: - self.state.shutdown() - self.cleanup_controller() + self.cleanup_controller() exit() - elif button == 13: - os.system("""osascript -e 'tell application "music" to pause'""") - elif button == 14: - os.system("""osascript -e 'tell application "music" to play'""") - - def knob_pushed(self, knob): - "On knob push reset the correct value" - # reset to unity gain - if self.state.active_bus < 7: - self.state.set_bus_send(self.state.active_bus, knob, 0.750000) - elif self.state.active_bus == 8: # channel levels - if self.state.mac and knob in [4, 5, 7]: - self.state.set_fader(knob, 0.375367) - else: - self.state.set_fader(knob, 0.750000) - elif self.state.active_bus == 9: - if knob == 6: # USB Return - self.state.set_fader(knob, 0.750000) - else: # output levels -20 db - self.state.set_fader(knob, 0.375367) - #elif self.state.active_bus == 7: # no obvious default for mic pre - #self.state.toggle_mpc() - - def activate_bus(self, bus): - "chagne the active bus" - if self.state.debug: - print('Activating bus {}'.format(bus)) - # set LEDs and layer - if self.state.active_bus == bus and bus != 9: # button 9 has only one layer - # switch layers for buttons with layers - if self.active_layer == 0: - self.set_button(bus + 8, self.LED_BLINK) - self.active_layer = 1 - else: - self.set_button(bus + 8, self.LED_ON) - self.active_layer = 0 - else: # switching to a different bus, or one that only has a single layer - self.set_button(self.state.active_bus + 8, self.LED_OFF) - self.set_button(bus + 8, self.LED_ON) - self.active_layer = 0 - # set bus and bank - self.state.active_bus = bus - if bus == 7: # set mic pre banks - self.state.active_bank = self.active_layer + 2 - elif bus == 9: # set outputs bank - self.state.active_bank = 4 - else: - self.state.active_bank = self.active_layer + def activate_bus(self): + "refresh the lights for the current layer" # reset lights for i in range(0, 8): - if self.state.banks[self.state.active_bank][i] is not None: - if self.state.active_bus > 6: # send fader or preamp level - self.set_channel_fader(i, self.state.banks[self.state.active_bank][i].fader) - self.set_channel_mute(i, self.state.banks[self.state.active_bank][i].ch_on) - else: # send bus send - if self.state.banks[self.state.active_bank][i].sends is not None: - self.set_channel_fader(i, self.state.banks[ \ - self.state.active_bank][i].sends[self.state.active_bus]) - self.set_channel_mute(i, self.state.banks[ \ - self.state.active_bank][i].enables[self.state.active_bus]) - else: # unassigned channel, disbale encoder and button - self.set_channel_fader(i, -1) - self.set_button(i, self.LED_OFF) + self.set_ring(i, self.state.get_encoder(i)) + self.set_channel_mute(i, self.state.get_button(i)) + for i in range(8, 18): + self.set_channel_mute(i, self.state.get_button(i)) - def set_channel_mute(self, channel, ch_on): + def set_channel_mute(self, channel, LED): "Send the mute value to the button" - self.set_button(channel, self.LED_ON if ch_on == 0 else self.LED_OFF) - - def set_channel_fader(self, channel, value): - "Send the fader value to the encoder ring" - self.set_ring(channel, value) + if LED == "On" or LED == 0: # LED is sense Negative for mute + self.set_button(channel, self.LED_ON) + elif LED == "Off" or LED == 1: # LED is sense Negative for mute + self.set_button(channel, self.LED_OFF) + else: + self.set_button(channel, self.LED_BLINK) def set_ring(self, ring, value): + "Send the fader value to the encoder ring" "Turn on the appropriate LEDs on the encoder ring." # 0 = off, 1-11 = single, 17-27 = pan, 33-43 = fan, 49-54 = spread # normalize value (0.0 - 1.0) to 0 - 11 range diff --git a/lib/mixerstate.py b/lib/mixerstate.py index 58b5c04..d4547f3 100644 --- a/lib/mixerstate.py +++ b/lib/mixerstate.py @@ -5,13 +5,14 @@ import time import subprocess import struct +import json from collections import deque from lib.xair import XAirClient, find_mixer from lib.midicontroller import MidiController class Channel: """ - Represents a single channel strip + Represents a single channel or bus """ def __init__(self, addr): self.fader = 0.0 @@ -25,6 +26,236 @@ def __init__(self, addr): self.ch_on = 1 self.osc_base_addr = addr + def toggle_mute(self, bus): + """Toggle a mute on or off.""" + if bus == "on": + """Toggle the state of the channel mute.""" + if self.ch_on == 1: + self.ch_on = 0 + else: + self.ch_on = 1 + return(self.osc_base_addr + '/on', + self.ch_on, self.ch_on) + else: + """Toggle the state of a send mute.""" + bus_num = int(bus) - 1 + if self.enables is not None: + if self.enables[bus_num] == 1: + self.enables[bus_num] = 0 + param=0.0 + else: + self.enables[bus_num] = 1 + param=self.sends[bus_num] + return(self.osc_base_addr + '/' + bus + '/level', + param, self.enables[bus_num]) + return(None, None, "Off") + + def set_mute(self, value): + """Set the state of the channel mute.""" + self.ch_on = value + return(self.osc_base_addr + '/on', + self.ch_on, self.ch_on) + + def get_mute(self, bus): + """Return the curent mute on/off.""" + if bus == "on": + return(self.ch_on) + else: + if self.enables is not None: + bus_num = int(bus) - 1 + return(self.enables[bus_num]) + return ("Off") + + def change_fader(self, delta): + """Change the level of the fader.""" + self.fader = min(max(0.0, self.fader + (delta / 200)), 1.0) + return(self.osc_base_addr + '/fader', + self.fader, self.fader) + + def set_fader(self, value): + """Set the level of the fader.""" + self.fader = value + return(self.osc_base_addr + '/fader', + self.fader, self.fader) + + def get_fader(self): + """Return the curent fader value.""" + return(self.fader) + + def change_gain(self, delta): + """Change the gain of the mic pre aka headamp.""" + self.fader = min(max(0.0, self.fader + (delta / 200)), 1.0) + return(self.osc_base_addr + '/gain', + self.fader, self.fader) + + def change_bus_send(self, bus, delta): + """Change the level of a bus send.""" + if self.sends is not None: + bus_num = int(bus) - 1 + print(" changing channel %s/%s by %s from %s %s" % \ + (self.osc_base_addr, bus_num, delta, self.sends[bus_num], bus)) + self.sends[bus_num] = \ + min(max(0.0, self.sends[bus_num] + (delta / 200)), 1.0) + return('%s/%s/level' %(self.osc_base_addr, bus), + self.sends[bus_num], self.sends[bus_num]) + return (None, None,"Off") + + def set_bus_send(self, bus, value): + """Set the level of a bus send.""" + if self.sends is not None: + bus_num = int(bus) - 1 + print(" setting channel %s/%s to %s from %s %s" % \ + (self.osc_base_addr, bus_num, value, self.sends[bus_num], bus)) + self.sends[bus_num] = value + return('%s/%s/level' %(self.osc_base_addr, bus), + self.sends[bus_num], self.sends[bus_num]) + return (None, None,"Off") + + def get_bus_send(self, bus): + """Return the current send level.""" + if self.sends is not None: + bus_num = int(bus) - 1 + return(self.sends[bus_num]) + return (0) + + +# the config json file specifies a number of layers each idendified by a name +# within the layer there are currently two sections: encoders and buttons +# there are 8 encoders per section of types: lengths +encoder_def = {'fader': 3, 'gain': 2, 'level': 4, 'none': 1} +encoder_types = set(encoder_def.keys()) +# fader, channel, [button] +# gain, channel +# level, channel, bus, [button] +# There are 18 buttons per section to types: lengths +button_def = {'quit': 3, 'none': 2, 'layer': 3, 'clip': 2, 'record': 2, 'mute': 4} +button_types = set(button_def.keys()) + +class Layer: + """ + Represents a logical layer as defined by the config file + """ + encoders = [] + buttons = [] + channels = {} + + def __init__(self, layer_name, config_layer, channels, layer_names) -> None: + "Initialize a Layer" + # first check for gross configurations errors + errors = 0 + if "encoders" not in config_layer.keys(): + print("Error: Layer %s does not contain an encoders section." % layer_name) + errors += 1 + if "buttons" not in config_layer.keys(): + print("Error: Layer %s contains an unknown section %s." % layer_name) + errors += 1 + if errors > 0: + print("Errors in config file, exiting.") + exit() + number = len(config_layer.keys()) + if number > 2: + print("Warning: expected exactly two types of controls %d found" % number) + + # Process the encoders + self.encoders = config_layer["encoders"] + if len(self.encoders) != 8: + print("Layer %s does not contain 8 'encoder' definitions." % layer_name) + for encoder in self.encoders: + if encoder[0] not in encoder_types: + print("Error: Layer %s contains an unknown 'encoder' %s, exiting." % \ + layer_name, encoder[0]) + exit() + if len(encoder) != encoder_def[encoder[0]]: + print("Error: Encoder %s of layer %s does not contain %d elements, exiting." % \ + (encoder[0], layer_name, encoder_def[encoder[0]])) + exit() + if encoder[0] != "none" and encoder[1] not in channels.keys(): + channels[encoder[1]] = Channel(encoder[1]) + + # Process the buttons + self.buttons = config_layer["buttons"] + if len(self.buttons) != 18: + print("Layer %s does not contain 18 'button' definitions." % layer_name) + for button in self.buttons: + if button[0] not in button_types: + print("Layer %s contains an unknown 'button' %s." % \ + (layer_name, button[0])) + exit() + if len(button) != button_def[button[0]]: + print("Error: Button %s of layer %s does not contain %d elements" % \ + (button[0], layer_name, button_def[button[0]])) + if button[0] == "layer" and button[1] not in layer_names: + print("Error: Layer %s has a change to undefined layer %s." % \ + (layer_name, button[1])) + if button[1] == "mute" and button[1] not in channels.keys(): + channels[button[1]] = Channel(button[1]) + + self.channels = channels + + def encoder_turn(self, number, value): + encoder = self.encoders[number] + if encoder[0] == "fader": + return(self.channels[encoder[1]].change_fader(value)) + elif encoder[0] == "gain": + return(self.channels[encoder[1]].change_gain(value)) + elif encoder[0] == "level": + return(self.channels[encoder[1]].change_bus_send(encoder[2], value)) + return(None, None,"Off") + + def encoder_press(self, number): + encoder = self.encoders[number] + if encoder[0] == "fader": + if encoder[2] != "none": + return(self.channels[encoder[1]].set_fader(float(encoder[2]))) + else: + return(None, None, self.channels[encoder[1]].get_fader()) + elif encoder[0] == "level": + if encoder[3] != "none": + return(self.channels[encoder[1]].set_bus_send(encoder[2], float(encoder[3]))) + else: + return(None, None, self.channels[encoder[1]].get_bus_send(encoder[2])) + elif encoder[0] == "gain": + return(None, None, self.channels[encoder[1]].get_fader()) + return(None, None,"Off") + + def encoder_state(self, number): + encoder = self.encoders[number] + if encoder[0] == "level": + return(self.channels[encoder[1]].get_bus_send(encoder[2])) + else: + return(self.channels[encoder[1]].get_fader()) + + def toggle_mute(self, number): + button = self.buttons[number] + if button[0] == "mute": + return(self.channels[button[1]].toggle_mute(button[2])) + else: + # return the relevant action for the non mixer based button + return(button[0], button[1], button[2] if len(button) > 2 else None ) + + def mute_state(self, number): + button = self.buttons[number] + if button[0] == "mute": + return(self.channels[button[1]].get_mute(button[2])) + else: + return(button[-1]) + + def encoder_number(self, name): + number = 0 + for encoder in self.encoders: + if name == encoder[1]: + return number + number += 1 + return -1 + + def button_number(self, name): + number = 0 + for button in self.buttons: + if name == button[1]: + return number + number += 1 + return -1 + class Meter: """ Calculates the .2 second running average of the value of a channel meter @@ -51,17 +282,15 @@ class MixerState: """ quit_called = False + layers = {} + channels = {} + current_layer = None # ID numbers for all available delay effects _DELAY_FX_IDS = [10, 11, 12, 21, 24, 25, 26] fx_slots = [0, 0, 0, 0] - active_bank = -1 - active_bus = -1 - - banks = [] - lr = Channel('/lr/mix') mute_groups = [ @@ -86,80 +315,26 @@ def __init__(self, args) -> None: self.xair_address = args.xair_address self.monitor = args.monitor self.clip = args.clip - self.mac = args.mac + self.mac = False # args.mac self.levels = args.levels # initialize internal data structures if self.clip: # clipping protection doesn't work without level info self.levels = True - # Each layer has 8 encoders and 8 buttons - if self.mac: # only a single layer, use second row of buttons as transport control - self.banks = [[ - Channel('/ch/08/mix'), - Channel('/ch/07/mix'), - Channel('/ch/06/mix'), - Channel('/ch/05/mix'), - Channel('/bus/1/mix'), - Channel('/bus/2/mix'), - Channel('/rtn/aux/mix'), - Channel('/lr/mix') - ]] - else: - self.banks = [[ - Channel('/ch/01/mix'), - Channel('/ch/02/mix'), - Channel('/ch/03/mix'), - Channel('/ch/04/mix'), - Channel('/ch/05/mix'), - Channel('/ch/06/mix'), - Channel('/ch/07/mix'), - Channel('/ch/08/mix') - ]] - self.banks.append( - [ - Channel('/ch/09/mix'), - Channel('/ch/10/mix'), - Channel('/ch/11/mix'), - Channel('/ch/12/mix'), - Channel('/ch/13/mix'), - Channel('/ch/14/mix'), - Channel('/ch/15/mix'), - Channel('/ch/16/mix') - ]) - self.banks.append( - [ - Channel('/headamp/01'), - Channel('/headamp/02'), - Channel('/headamp/03'), - Channel('/headamp/04'), - Channel('/headamp/05'), - Channel('/headamp/06'), - Channel('/headamp/07'), - Channel('/headamp/08') - ]) - self.banks.append( - [ - Channel('/headamp/09'), - Channel('/headamp/10'), - Channel('/headamp/11'), - Channel('/headamp/12'), - Channel('/headamp/13'), - Channel('/headamp/14'), - Channel('/headamp/15'), - Channel('/headamp/16') - ]) - self.banks.append( - [ - Channel('/bus/1/mix'), - Channel('/bus/2/mix'), - Channel('/bus/3/mix'), - Channel('/bus/4/mix'), - Channel('/bus/5/mix'), - Channel('/bus/6/mix'), - Channel('/rtn/aux/mix'), - Channel('/lr/mix') - ]) + config_json = "simple.json" + if args.config_file is not None: + config_json = args.config_file[0] + with open(config_json) as config_file: + config = json.load(config_file) + + layer_names = config.keys() + + for layer_name in layer_names: + if self.current_layer == None: + self.current_layer = layer_name + self.layers[layer_name] = Layer(layer_name, config[layer_name], + self.channels, layer_names) def initialize_state(self): self.quit_called = False @@ -184,8 +359,7 @@ def initialize_state(self): return False self.read_initial_state() - self.midi_controller.activate_bus(0) # in case of reset - self.midi_controller.activate_bus(8) # set chanel level as initial bus + self.midi_controller.activate_bus() return True def shutdown(self): @@ -197,6 +371,8 @@ def shutdown(self): if self.midi_controller is not None: self.midi_controller.cleanup_controller() self.midi_controller = None +# if self.screen_obj is not None: +# self.screen_obj.quit() def toggle_mute_group(self, group): if self.mute_groups[group].ch_on == 1: @@ -208,40 +384,6 @@ def toggle_mute_group(self, group): param=self.mute_groups[group].ch_on) self.midi_controller.set_mute_grp(group, self.mute_groups[group].ch_on) - def toggle_channel_mute(self, channel): - """Toggle the state of a channel mute button.""" - if self.banks[self.active_bank][channel] is not None: - if self.banks[self.active_bank][channel].ch_on == 1: - self.banks[self.active_bank][channel].ch_on = 0 - else: - self.banks[self.active_bank][channel].ch_on = 1 - self.xair_client.send( - address=self.banks[self.active_bank][channel].osc_base_addr + '/on', - param=self.banks[self.active_bank][channel].ch_on) - self.midi_controller.set_channel_mute( - channel, self.banks[self.active_bank][channel].ch_on) - - def toggle_send_mute(self, channel, bus): - """Toggle the state of a send mute.""" - if self.debug: - print('Toggle Send Mute %d %d' % (channel, bus)) - if ((self.banks[self.active_bank][channel] is not None) - and self.banks[self.active_bank][channel].enables is not None): - if self.banks[self.active_bank][channel].enables[bus] == 1: - self.banks[self.active_bank][channel].enables[bus] = 0 - self.xair_client.send( - address=self.banks[self.active_bank][channel].osc_base_addr + - '/{:0>2d}/level'.format(bus + 1), - param=0.0) - else: - self.banks[self.active_bank][channel].enables[bus] = 1 - self.xair_client.send( - address=self.banks[self.active_bank][channel].osc_base_addr + - '/{:0>2d}/level'.format(bus + 1), - param=self.banks[self.active_bank][channel].sends[bus]) - self.midi_controller.set_channel_mute(channel, \ - self.banks[self.active_bank][channel].enables[bus]) - def toggle_mpc(self): if self.mpd_playing: try: @@ -256,62 +398,69 @@ def toggle_mpc(self): pass self.mpd_playing = True - def change_fader(self, fader, delta): - """Change the level of a fader.""" - if self.banks[self.active_bank][fader] is not None: - self.banks[self.active_bank][fader].fader = \ - min(max(0.0, self.banks[self.active_bank][fader].fader + (delta / 200)), 1.0) - self.xair_client.send( - address=self.banks[self.active_bank][fader].osc_base_addr + '/fader', - param=self.banks[self.active_bank][fader].fader) - self.midi_controller.set_channel_fader(fader, self.banks[self.active_bank][fader].fader) - - def set_fader(self, fader, value): - """Set the level of a fader.""" - if self.banks[self.active_bank][fader] is not None: - self.banks[self.active_bank][fader].fader = value - self.xair_client.send( - address=self.banks[self.active_bank][fader].osc_base_addr + '/fader', - param=self.banks[self.active_bank][fader].fader) - self.midi_controller.set_channel_fader(fader, self.banks[self.active_bank][fader].fader) - - def change_bus_send(self, bus, channel, delta): - """Change the level of a bus send.""" - if ((self.banks[self.active_bank][channel] is not None) - and self.banks[self.active_bank][channel].sends is not None): - self.banks[self.active_bank][channel].sends[bus] = \ - min(max(0.0, self.banks[self.active_bank][channel].sends[bus] + (delta / 200)), 1.0) - self.xair_client.send( - address=self.banks[self.active_bank][channel].osc_base_addr + \ - '/{:0>2d}/level'.format(bus + 1), - param=self.banks[self.active_bank][channel].sends[bus]) - self.midi_controller.set_channel_fader(channel, \ - self.banks[self.active_bank][channel].sends[bus]) - - def set_bus_send(self, bus, channel, value): - """Set the level of a bus send.""" - if ((self.banks[self.active_bank][channel] is not None) - and self.banks[self.active_bank][channel].sends is not None): - self.banks[self.active_bank][channel].sends[bus] = value - self.xair_client.send( - address=self.banks[self.active_bank][channel].osc_base_addr + \ - '/{:0>2d}/level'.format(bus + 1), - param=self.banks[self.active_bank][channel].sends[bus]) - self.midi_controller.set_channel_fader(channel, \ - self.banks[self.active_bank][channel].sends[bus]) - - def change_headamp(self, fader, delta): # aka mic pre - """Change the level of a mic pre aka headamp.""" - if self.banks[self.active_bank][fader] is not None: - self.banks[self.active_bank][fader].fader = \ - min(max(0.0, self.banks[self.active_bank][fader].fader + (delta / 200)), 1.0) - self.xair_client.send( - address=self.banks[self.active_bank][fader].osc_base_addr + '/gain', - param=self.banks[self.active_bank][fader].fader) - self.midi_controller.set_channel_fader(fader, self.banks[self.active_bank][fader].fader) - - def set_lr_fader(self, value): - self.xair_client.send(address = self.lr.osc_base_addr + '/fader', param = value) + def button_press(self, number): + """Handle a button press.""" + if self.debug: + print('Button %d pressed' % number) + (address, param, LED) = self.layers[self.current_layer].toggle_mute(number) + if address == 'layer': + self.current_layer = param + self.midi_controller.activate_bus() + return self.get_button(number) + elif address == 'clip': + self.clip = not self.clip + if self.clip: + self.levels = True + return "Off" + else: + return "On" + elif address == 'quit': + self.shutdown() + exit() + elif address == 'record': + pass + return "Off" + if address != None: + self.xair_client.send(address=address, param=param) + return LED + + def get_button(self, number): + if self.debug: + print('Getting state of button number %d' % number) + return(self.layers[self.current_layer].mute_state(number)) + +# def mac_button(self, button): +# "call a function for transport buttons on mac" +# if button == 10: +# os.system("""osascript -e 'tell application "music" to previous track'""") +# elif button == 11: +# os.system("""osascript -e 'tell application "music" to next track'""") +# elif button == 12: +# self.state.shutdown() +# self.cleanup_controller() +# exit() +# elif button == 13: +# os.system("""osascript -e 'tell application "music" to pause'""") +# elif button == 14: +# os.system("""osascript -e 'tell application "music" to play'""") + + def encoder_turn(self, number, delta): + """Change the level of an encoder.""" + (address, param, LED) = self.layers[self.current_layer].encoder_turn(number, delta) + if address != None: + self.xair_client.send(address=address, param=param) + return LED + + def encoder_press(self, number): + (address, param, LED) = self.layers[self.current_layer].encoder_press(number) + if address != None: + self.xair_client.send(address=address, param=param) + return LED + + def get_encoder(self, number): + if self.debug: + print('Getting state of encoder number %d' % number) + return(self.layers[self.current_layer].encoder_state(number)) def received_osc(self, addr, value): """Process an OSC input.""" @@ -330,59 +479,65 @@ def received_osc(self, addr, value): # if value == 10: # param_id = '02' # self.xair_client.send(address = '/fx/%s/par/%s' % (addr[4:5], param_id)) - if True: - for i in range(0, 5): - for j in range(0, 8): - if self.banks[i][j] is not None and addr.startswith(self.banks[i][j].osc_base_addr): - if addr.endswith('/fader'): # chanel fader level - if self.debug: - print('Channel %s level %f' % (addr, value)) - self.banks[i][j].fader = value - if i == self.active_bank and self.active_bus in [8, 9]: - self.midi_controller.set_channel_fader(j, value) - elif addr.endswith('/on'): # channel enable - if self.debug: - print('%s unMute %d' % (addr, value)) - self.banks[i][j].ch_on = value - if i == self.active_bank and self.active_bus in [8, 9]: - self.midi_controller.set_channel_mute(j, value) - elif self.banks[i][j].sends is not None and addr.endswith('/level'): - if self.debug: - print('%s level %f' % (addr, value)) - bus = int(addr[-8:-6]) - 1 - self.banks[i][j].sends[bus] = value - if i == self.active_bank and bus == self.active_bus: - self.midi_controller.set_channel_fader(j, value) - elif addr.endswith('/gain'): - if self.debug: - print('%s Gain level %f' % (addr, value)) - self.banks[i][j].fader = value - if i == self.active_bank: #doesn't need a bus check as only on one bus - self.midi_controller.set_channel_fader(j, value) - break - else: - continue - break + + prefix = None + three_element = '/'.join(addr.split('/',4)[:-1]) # first three parts of the OSC path + two_element = '/'.join(addr.split('/',3)[:-1]) # first two parts of the OSC path + if self.debug: + print('processing OSC message with %s and %s value.' % (addr, value)) + if three_element in self.channels.keys(): + prefix = three_element + elif two_element in self.channels.keys(): + prefix = two_element + if prefix is not None: + if addr.endswith('/fader'): # chanel fader level + if self.debug: + print('Channel %s level %f' % (addr, value)) + self.channels[prefix].set_fader(value) + number = self.layers[self.current_layer].encoder_number(prefix) + if number != -1: + self.midi_controller.set_ring(number, value) + elif addr.endswith('/on'): # channel enable + if self.debug: + print('%s unMute %d' % (addr, value)) + self.channels[prefix].set_mute(value) + number = self.layers[self.current_layer].button_number(prefix) + if number != -1: + self.midi_controller.set_channel_mute(number, value) + elif addr.endswith('/level'): + if self.debug: + print('%s level %f' % (addr, value)) + bus = int(addr[-8:-6]) + self.channels[prefix].set_bus_send(bus, value) + number = self.layers[self.current_layer].encoder_number(prefix) + if number != -1: + self.midi_controller.set_ring(number, value) + elif addr.endswith('/gain'): + if self.debug: + print('%s Gain level %f' % (addr, value)) + self.channels[prefix].set_fader(value) + number = self.layers[self.current_layer].encoder_number(prefix) + if number != -1: + self.midi_controller.set_ring(number, value) def read_initial_state(self): """ Refresh state for all faders and mutes.""" - for i in range(0, 5): - for j in range(0, 8): - if self.banks[i][j] is not None: - if self.banks[i][j].osc_base_addr.startswith('/head'): - self.xair_client.send(address=self.banks[i][j].osc_base_addr + '/gain') - time.sleep(0.002) - else: - self.xair_client.send(address=self.banks[i][j].osc_base_addr + '/fader') - time.sleep(0.002) - self.xair_client.send(address=self.banks[i][j].osc_base_addr + '/on') + for channel in self.channels.values(): + addr = channel.osc_base_addr + if addr.startswith('/head'): + self.xair_client.send(address=addr + '/gain') + time.sleep(0.002) + else: + self.xair_client.send(address=addr + '/fader') + time.sleep(0.002) + self.xair_client.send(address=addr + '/on') + time.sleep(0.002) + if channel.sends is not None: + for k in range(len(channel.sends)): + self.xair_client.send(address=addr + + '/{:0>2d}/level'.format(k + 1)) time.sleep(0.002) - if self.banks[i][j].sends is not None: - for k in range(len(self.banks[i][j].sends)): - self.xair_client.send(address=self.banks[i][j].osc_base_addr + \ - '/{:0>2d}/level'.format(k + 1)) - time.sleep(0.002) - + # get all mute groups #for i in range(0, 4): # self.xair_client.send(address = self.mute_groups[i].osc_base_addr) @@ -404,7 +559,7 @@ def update_tempo(self, tempo): # how to set up a meter subscription # values send every 50ms for 10s -# the actual call is located in xair.py in the refresch connecton method that runs every 5 s +# the actual call is located in xair.py in the refresh connecton method that runs every 5 s # # meters 1 provide data per channel and bus # self.xair_client.send(address="/meters", param=["/meters/1"]) # pre faid ch levels @@ -413,11 +568,6 @@ def update_tempo(self, tempo): # using meters 2, input levels, as these will match the headamps even if mapped to other channels # self.xair_client.send(address="/meters", param=["/meters/2"]) # time.sleep(0.002) -# channel levels, not entirely clear -# self.xair_client.send(address="/meters", param=["/meters/0", 7]) -# time.sleep(0.002) -#self.xair_client.send(address="/meters/13") -#time.sleep(0.002) def received_meters(self, addr, data): "receive an OSC Meters packet" @@ -447,7 +597,7 @@ def received_meters(self, addr, data): else: self.active_bank = 3 fader = fader - 8 - self.change_headamp(fader, -1) + self.change_headamp(fader, -1) ## needs FIXME if self.debug: print("Clipping Detected headamp changed to %s" % self.banks[self.active_bank][fader].fader) @@ -456,5 +606,5 @@ def received_meters(self, addr, data): #print('Meters("%s", %s) size %s length %s' % (addr, data[0], len(data[0]), data_size)) print('Meters %s ch 8 %s %s %s' % (addr, values[7], short[7], med[7])) #print(values) - if self.screen_obj is not None: - self.screen_obj.screen_loop() +# if self.screen_obj is not None: +# self.screen_obj.screen_loop() diff --git a/lib/xair.py b/lib/xair.py index bffb981..cedcbdb 100644 --- a/lib/xair.py +++ b/lib/xair.py @@ -118,8 +118,8 @@ def refresh_connection(self): # the main loop if self.state.debug: print("start auto level") self.state.clip = False - if self.state.screen_obj is not None: - self.state.screen_obj.gpio_button[1].disable[0] = 1 +# if self.state.screen_obj is not None: +# self.state.screen_obj.gpio_button[1].disable[0] = 1 time.sleep(self._REFRESH_TIMEOUT) if self.state.quit_called: self.quit() diff --git a/xair-remote.py b/xair-remote.py index a98e5af..652c93a 100755 --- a/xair-remote.py +++ b/xair-remote.py @@ -31,7 +31,7 @@ PARSER.add_argument('-l', '--levels', help='get levels from the mixer', action="store_true") PARSER.add_argument('-c', '--clip', help='enabling auto leveling to avoid clipping', action="store_true") - PARSER.add_argument('-a', '--mac', help="use alternate mapping for mac", action="store_true") + PARSER.add_argument('-f', '--config_file', help="JSON formated config file", nargs=1) ARGS = PARSER.parse_args() STATE = MixerState(ARGS) From 9b25807cb76d9a09c6524ad87f57e0f869956b67 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Fri, 3 Sep 2021 11:58:59 -0400 Subject: [PATCH 18/25] updated file format to support merge of peter's layout --- lib/mixerstate.py | 153 +++++------- monitors.json | 578 +++++++++++++++++++++++----------------------- simple.json | 98 ++++---- 3 files changed, 398 insertions(+), 431 deletions(-) diff --git a/lib/mixerstate.py b/lib/mixerstate.py index d4547f3..62b8cec 100644 --- a/lib/mixerstate.py +++ b/lib/mixerstate.py @@ -28,13 +28,13 @@ def __init__(self, addr): def toggle_mute(self, bus): """Toggle a mute on or off.""" - if bus == "on": + if bus == "/on" or bus == "": """Toggle the state of the channel mute.""" if self.ch_on == 1: self.ch_on = 0 else: self.ch_on = 1 - return(self.osc_base_addr + '/on', + return(self.osc_base_addr + bus, self.ch_on, self.ch_on) else: """Toggle the state of a send mute.""" @@ -50,15 +50,15 @@ def toggle_mute(self, bus): param, self.enables[bus_num]) return(None, None, "Off") - def set_mute(self, value): + def set_mute(self, bus, value): """Set the state of the channel mute.""" self.ch_on = value - return(self.osc_base_addr + '/on', + return(self.osc_base_addr + bus, self.ch_on, self.ch_on) def get_mute(self, bus): """Return the curent mute on/off.""" - if bus == "on": + if bus == "/on": return(self.ch_on) else: if self.enables is not None: @@ -66,53 +66,42 @@ def get_mute(self, bus): return(self.enables[bus_num]) return ("Off") - def change_fader(self, delta): - """Change the level of the fader.""" - self.fader = min(max(0.0, self.fader + (delta / 200)), 1.0) - return(self.osc_base_addr + '/fader', - self.fader, self.fader) - - def set_fader(self, value): - """Set the level of the fader.""" - self.fader = value - return(self.osc_base_addr + '/fader', - self.fader, self.fader) - - def get_fader(self): - """Return the curent fader value.""" - return(self.fader) - - def change_gain(self, delta): - """Change the gain of the mic pre aka headamp.""" - self.fader = min(max(0.0, self.fader + (delta / 200)), 1.0) - return(self.osc_base_addr + '/gain', - self.fader, self.fader) - - def change_bus_send(self, bus, delta): - """Change the level of a bus send.""" + def change_level(self, bus, delta): + """Change the level of a fader, mic pre, or bus send.""" + print("changing %s bus %s by %s" % (self.osc_base_addr, bus, delta)) + if bus == "fader" or bus == "gain": + self.fader = min(max(0.0, self.fader + (delta / 200)), 1.0) + return(self.osc_base_addr + '/' + bus, + self.fader, self.fader) if self.sends is not None: bus_num = int(bus) - 1 - print(" changing channel %s/%s by %s from %s %s" % \ - (self.osc_base_addr, bus_num, delta, self.sends[bus_num], bus)) +# print(" changing channel %s/%s by %s from %s %s" % \ +# (self.osc_base_addr, bus_num, delta, self.sends[bus_num], bus)) self.sends[bus_num] = \ min(max(0.0, self.sends[bus_num] + (delta / 200)), 1.0) return('%s/%s/level' %(self.osc_base_addr, bus), self.sends[bus_num], self.sends[bus_num]) return (None, None,"Off") - def set_bus_send(self, bus, value): - """Set the level of a bus send.""" + def set_level(self, bus, value): + """Set the level of a fader, mic pre, or bus send.""" + if bus == "fader" or bus == "gain": + self.fader = value + return(self.osc_base_addr + '/' + bus, + self.fader, self.fader) if self.sends is not None: bus_num = int(bus) - 1 - print(" setting channel %s/%s to %s from %s %s" % \ - (self.osc_base_addr, bus_num, value, self.sends[bus_num], bus)) +# print(" setting channel %s/%s to %s from %s %s" % \ +# (self.osc_base_addr, bus_num, value, self.sends[bus_num], bus)) self.sends[bus_num] = value return('%s/%s/level' %(self.osc_base_addr, bus), self.sends[bus_num], self.sends[bus_num]) return (None, None,"Off") - def get_bus_send(self, bus): - """Return the current send level.""" + def get_level(self, bus): + """Return the current level of a fader, mic pre, or bus send.""" + if bus == "fader" or bus == "gain": + return(self.fader) if self.sends is not None: bus_num = int(bus) - 1 return(self.sends[bus_num]) @@ -121,14 +110,13 @@ def get_bus_send(self, bus): # the config json file specifies a number of layers each idendified by a name # within the layer there are currently two sections: encoders and buttons -# there are 8 encoders per section of types: lengths -encoder_def = {'fader': 3, 'gain': 2, 'level': 4, 'none': 1} -encoder_types = set(encoder_def.keys()) +# there are 8 encoders per section with three parts: channel, send/fader/gain, button + # fader, channel, [button] # gain, channel # level, channel, bus, [button] # There are 18 buttons per section to types: lengths -button_def = {'quit': 3, 'none': 2, 'layer': 3, 'clip': 2, 'record': 2, 'mute': 4} +button_def = {'quit': 3, 'none': 2, 'layer': 3, 'clip': 2, 'record': 2, 'mute': 3} button_types = set(button_def.keys()) class Layer: @@ -161,16 +149,14 @@ def __init__(self, layer_name, config_layer, channels, layer_names) -> None: if len(self.encoders) != 8: print("Layer %s does not contain 8 'encoder' definitions." % layer_name) for encoder in self.encoders: - if encoder[0] not in encoder_types: - print("Error: Layer %s contains an unknown 'encoder' %s, exiting." % \ - layer_name, encoder[0]) - exit() - if len(encoder) != encoder_def[encoder[0]]: - print("Error: Encoder %s of layer %s does not contain %d elements, exiting." % \ - (encoder[0], layer_name, encoder_def[encoder[0]])) + if len(encoder) != 3: + print("Error: Encoder %s of layer %s does not contain 3 elements, exiting." % \ + (encoder[0], layer_name)) exit() - if encoder[0] != "none" and encoder[1] not in channels.keys(): - channels[encoder[1]] = Channel(encoder[1]) + if encoder[0] != "none" and encoder[0] not in channels.keys(): + channels[encoder[0]] = Channel(encoder[0]) + if encoder[2][0] == "mute" and encoder[2][1] not in channels.keys(): + channels[encoder[2][1]] = Channel(encoder[2][1]) # Process the buttons self.buttons = config_layer["buttons"] @@ -194,36 +180,25 @@ def __init__(self, layer_name, config_layer, channels, layer_names) -> None: def encoder_turn(self, number, value): encoder = self.encoders[number] - if encoder[0] == "fader": - return(self.channels[encoder[1]].change_fader(value)) - elif encoder[0] == "gain": - return(self.channels[encoder[1]].change_gain(value)) - elif encoder[0] == "level": - return(self.channels[encoder[1]].change_bus_send(encoder[2], value)) + if encoder[0] != "none": + return(self.channels[encoder[0]].change_level(encoder[1], value)) return(None, None,"Off") def encoder_press(self, number): encoder = self.encoders[number] - if encoder[0] == "fader": - if encoder[2] != "none": - return(self.channels[encoder[1]].set_fader(float(encoder[2]))) - else: - return(None, None, self.channels[encoder[1]].get_fader()) - elif encoder[0] == "level": - if encoder[3] != "none": - return(self.channels[encoder[1]].set_bus_send(encoder[2], float(encoder[3]))) - else: - return(None, None, self.channels[encoder[1]].get_bus_send(encoder[2])) - elif encoder[0] == "gain": - return(None, None, self.channels[encoder[1]].get_fader()) - return(None, None,"Off") + if encoder[2][0] == "reset": + return(self.channels[encoder[0]].set_level(encoder[1], float(encoder[2][1]))) + elif encoder[2][0] == "mute": + (address, param, LED) = self.channels[encoder[2][1]].toggle_mute(encoder[2][2]) + return(address, param, self.channels[encoder[0]].get_level(encoder[1])) + if encoder[0] == "none": + return(None, None,"Off") + else: + return(None, None, self.channels[encoder[0]].get_level(encoder[1])) def encoder_state(self, number): encoder = self.encoders[number] - if encoder[0] == "level": - return(self.channels[encoder[1]].get_bus_send(encoder[2])) - else: - return(self.channels[encoder[1]].get_fader()) + return(self.channels[encoder[0]].get_level(encoder[1])) def toggle_mute(self, number): button = self.buttons[number] @@ -374,16 +349,6 @@ def shutdown(self): # if self.screen_obj is not None: # self.screen_obj.quit() - def toggle_mute_group(self, group): - if self.mute_groups[group].ch_on == 1: - self.mute_groups[group].ch_on = 0 - else: - self.mute_groups[group].ch_on = 1 - self.xair_client.send( - address=self.mute_groups[group].osc_base_addr, - param=self.mute_groups[group].ch_on) - self.midi_controller.set_mute_grp(group, self.mute_groups[group].ch_on) - def toggle_mpc(self): if self.mpd_playing: try: @@ -464,10 +429,6 @@ def get_encoder(self, number): def received_osc(self, addr, value): """Process an OSC input.""" -# if addr.startswith('/config/mute'): -# group = int(addr[-1:]) - 1 -# self.mute_groups[group].ch_on = value -# self.midi_controller.set_mute_grp(group, value) # elif addr.startswith('/fx') and (addr.endswith('/par/01') or addr.endswith('/par/02')): # if self.fx_slots[int(addr[4:5]) - 1] in self._DELAY_FX_IDS: # self.midi_controller.update_tempo(value * 3) @@ -490,17 +451,23 @@ def received_osc(self, addr, value): elif two_element in self.channels.keys(): prefix = two_element if prefix is not None: - if addr.endswith('/fader'): # chanel fader level + if addr.startswith('/config/mute'): + number = '/'.join(addr.split('/')[-1]) + self.channels[prefix].set_mute(number, value) + number = self.layers[self.current_layer].encoder_number(prefix) + if number != -1: + self.midi_controller.set_channel_mute(number, value) + elif addr.endswith('/fader'): # chanel fader level if self.debug: print('Channel %s level %f' % (addr, value)) - self.channels[prefix].set_fader(value) + self.channels[prefix].set_level("fader", value) number = self.layers[self.current_layer].encoder_number(prefix) if number != -1: self.midi_controller.set_ring(number, value) elif addr.endswith('/on'): # channel enable if self.debug: print('%s unMute %d' % (addr, value)) - self.channels[prefix].set_mute(value) + self.channels[prefix].set_mute("/on", value) number = self.layers[self.current_layer].button_number(prefix) if number != -1: self.midi_controller.set_channel_mute(number, value) @@ -508,14 +475,14 @@ def received_osc(self, addr, value): if self.debug: print('%s level %f' % (addr, value)) bus = int(addr[-8:-6]) - self.channels[prefix].set_bus_send(bus, value) + self.channels[prefix].set_level(bus, value) number = self.layers[self.current_layer].encoder_number(prefix) if number != -1: self.midi_controller.set_ring(number, value) elif addr.endswith('/gain'): if self.debug: print('%s Gain level %f' % (addr, value)) - self.channels[prefix].set_fader(value) + self.channels[prefix].set_level("gain", value) number = self.layers[self.current_layer].encoder_number(prefix) if number != -1: self.midi_controller.set_ring(number, value) @@ -597,7 +564,7 @@ def received_meters(self, addr, data): else: self.active_bank = 3 fader = fader - 8 - self.change_headamp(fader, -1) ## needs FIXME + self.change_level(fader, -1) ## needs FIXME if self.debug: print("Clipping Detected headamp changed to %s" % self.banks[self.active_bank][fader].fader) diff --git a/monitors.json b/monitors.json index 5938c45..aa522f8 100644 --- a/monitors.json +++ b/monitors.json @@ -1,24 +1,24 @@ { "A": { "encoders": [ - ["fader", "/ch/01/mix", "0.750000"], - ["fader", "/ch/02/mix", "0.750000"], - ["fader", "/ch/03/mix", "0.750000"], - ["fader", "/ch/04/mix", "0.750000"], - ["fader", "/ch/05/mix", "0.750000"], - ["fader", "/ch/06/mix", "0.750000"], - ["fader", "/ch/07/mix", "0.750000"], - ["fader", "/ch/08/mix", "0.750000"] + ["/ch/01/mix", "fader", ["reset", "0.750000"]], + ["/ch/02/mix", "fader", ["reset", "0.750000"]], + ["/ch/03/mix", "fader", ["reset", "0.750000"]], + ["/ch/04/mix", "fader", ["reset", "0.750000"]], + ["/ch/05/mix", "fader", ["reset", "0.750000"]], + ["/ch/06/mix", "fader", ["reset", "0.750000"]], + ["/ch/07/mix", "fader", ["reset", "0.750000"]], + ["/ch/08/mix", "fader", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "on", "State"], - ["mute", "/ch/02/mix", "on", "State"], - ["mute", "/ch/03/mix", "on", "State"], - ["mute", "/ch/04/mix", "on", "State"], - ["mute", "/ch/05/mix", "on", "State"], - ["mute", "/ch/06/mix", "on", "State"], - ["mute", "/ch/07/mix", "on", "State"], - ["mute", "/ch/08/mix", "on", "State"], + ["mute", "/ch/01/mix", "/on"], + ["mute", "/ch/02/mix", "/on"], + ["mute", "/ch/03/mix", "/on"], + ["mute", "/ch/04/mix", "/on"], + ["mute", "/ch/05/mix", "/on"], + ["mute", "/ch/06/mix", "/on"], + ["mute", "/ch/07/mix", "/on"], + ["mute", "/ch/08/mix", "/on"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -33,24 +33,24 @@ }, "Aprime": { "encoders": [ - ["fader", "/ch/09/mix", "0.750000"], - ["fader", "/ch/10/mix", "0.750000"], - ["fader", "/ch/11/mix", "0.750000"], - ["fader", "/ch/12/mix", "0.750000"], - ["fader", "/ch/13/mix", "0.750000"], - ["fader", "/ch/14/mix", "0.750000"], - ["fader", "/ch/15/mix", "0.750000"], - ["fader", "/ch/16/mix", "0.750000"] + ["/ch/09/mix", "fader", ["reset", "0.750000"]], + ["/ch/10/mix", "fader", ["reset", "0.750000"]], + ["/ch/11/mix", "fader", ["reset", "0.750000"]], + ["/ch/12/mix", "fader", ["reset", "0.750000"]], + ["/ch/13/mix", "fader", ["reset", "0.750000"]], + ["/ch/14/mix", "fader", ["reset", "0.750000"]], + ["/ch/15/mix", "fader", ["reset", "0.750000"]], + ["/ch/16/mix", "fader", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/09/mix", "on", "State"], - ["mute", "/ch/10/mix", "on", "State"], - ["mute", "/ch/11/mix", "on", "State"], - ["mute", "/ch/12/mix", "on", "State"], - ["mute", "/ch/13/mix", "on", "State"], - ["mute", "/ch/14/mix", "on", "State"], - ["mute", "/ch/15/mix", "on", "State"], - ["mute", "/ch/16/mix", "on", "State"], + ["mute", "/ch/09/mix", "/on"], + ["mute", "/ch/10/mix", "/on"], + ["mute", "/ch/11/mix", "/on"], + ["mute", "/ch/12/mix", "/on"], + ["mute", "/ch/13/mix", "/on"], + ["mute", "/ch/14/mix", "/on"], + ["mute", "/ch/15/mix", "/on"], + ["mute", "/ch/16/mix", "/on"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -65,24 +65,24 @@ }, "B": { "encoders": [ - ["fader", "/bus/1/mix", "0.375367"], - ["fader", "/bus/2/mix", "0.375367"], - ["fader", "/bus/3/mix", "0.375367"], - ["fader", "/bus/4/mix", "0.375367"], - ["fader", "/bus/5/mix", "0.375367"], - ["fader", "/bus/6/mix", "0.375367"], - ["fader", "/rtn/aux/mix", "0.750000"], - ["fader", "/lr/mix", "0.375367"] + ["/bus/1/mix", "fader", ["reset", "0.375367"]], + ["/bus/2/mix", "fader", ["reset", "0.375367"]], + ["/bus/3/mix", "fader", ["reset", "0.375367"]], + ["/bus/4/mix", "fader", ["reset", "0.375367"]], + ["/bus/5/mix", "fader", ["reset", "0.375367"]], + ["/bus/6/mix", "fader", ["reset", "0.375367"]], + ["/rtn/aux/mix", "fader", ["reset", "0.750000"]], + ["/lr/mix", "fader", ["reset", "0.375367"]] ], "buttons": [ - ["mute", "/bus/1/mix", "on", "State"], - ["mute", "/bus/2/mix", "on", "State"], - ["mute", "/bus/3/mix", "on", "State"], - ["mute", "/bus/4/mix", "on", "State"], - ["mute", "/bus/5/mix", "on", "State"], - ["mute", "/bus/6/mix", "on", "State"], - ["mute", "/rtn/aux/mix", "on", "State"], - ["mute", "/lr/mix", "on", "State"], + ["mute", "/bus/1/mix", "/on"], + ["mute", "/bus/2/mix", "/on"], + ["mute", "/bus/3/mix", "/on"], + ["mute", "/bus/4/mix", "/on"], + ["mute", "/bus/5/mix", "/on"], + ["mute", "/bus/6/mix", "/on"], + ["mute", "/rtn/aux/mix", "/on"], + ["mute", "/lr/mix", "/on"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -97,24 +97,24 @@ }, "1": { "encoders": [ - ["level", "/ch/01/mix", "01", "0.750000"], - ["level", "/ch/02/mix", "01", "0.750000"], - ["level", "/ch/03/mix", "01", "0.750000"], - ["level", "/ch/04/mix", "01", "0.750000"], - ["level", "/ch/05/mix", "01", "0.750000"], - ["level", "/ch/06/mix", "01", "0.750000"], - ["level", "/ch/07/mix", "01", "0.750000"], - ["level", "/ch/08/mix", "01", "0.750000"] + ["/ch/01/mix", "01", ["reset", "0.750000"]], + ["/ch/02/mix", "01", ["reset", "0.750000"]], + ["/ch/03/mix", "01", ["reset", "0.750000"]], + ["/ch/04/mix", "01", ["reset", "0.750000"]], + ["/ch/05/mix", "01", ["reset", "0.750000"]], + ["/ch/06/mix", "01", ["reset", "0.750000"]], + ["/ch/07/mix", "01", ["reset", "0.750000"]], + ["/ch/08/mix", "01", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "01", "State"], - ["mute", "/ch/02/mix", "01", "State"], - ["mute", "/ch/03/mix", "01", "State"], - ["mute", "/ch/04/mix", "01", "State"], - ["mute", "/ch/05/mix", "01", "State"], - ["mute", "/ch/06/mix", "01", "State"], - ["mute", "/ch/07/mix", "01", "State"], - ["mute", "/ch/08/mix", "01", "State"], + ["mute", "/ch/01/mix", "01"], + ["mute", "/ch/02/mix", "01"], + ["mute", "/ch/03/mix", "01"], + ["mute", "/ch/04/mix", "01"], + ["mute", "/ch/05/mix", "01"], + ["mute", "/ch/06/mix", "01"], + ["mute", "/ch/07/mix", "01"], + ["mute", "/ch/08/mix", "01"], ["layer", "1prime", "On"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -129,24 +129,24 @@ }, "1prime": { "encoders": [ - ["level", "/ch/09/mix", "01", "0.750000"], - ["level", "/ch/10/mix", "01", "0.750000"], - ["level", "/ch/11/mix", "01", "0.750000"], - ["level", "/ch/12/mix", "01", "0.750000"], - ["level", "/ch/13/mix", "01", "0.750000"], - ["level", "/ch/14/mix", "01", "0.750000"], - ["level", "/ch/15/mix", "01", "0.750000"], - ["level", "/ch/16/mix", "01", "0.750000"] + ["/ch/09/mix", "01", ["reset", "0.750000"]], + ["/ch/10/mix", "01", ["reset", "0.750000"]], + ["/ch/11/mix", "01", ["reset", "0.750000"]], + ["/ch/12/mix", "01", ["reset", "0.750000"]], + ["/ch/13/mix", "01", ["reset", "0.750000"]], + ["/ch/14/mix", "01", ["reset", "0.750000"]], + ["/ch/15/mix", "01", ["reset", "0.750000"]], + ["/ch/16/mix", "01", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/09/mix", "01", "State"], - ["mute", "/ch/10/mix", "01", "State"], - ["mute", "/ch/11/mix", "01", "State"], - ["mute", "/ch/12/mix", "01", "State"], - ["mute", "/ch/13/mix", "01", "State"], - ["mute", "/ch/14/mix", "01", "State"], - ["mute", "/ch/15/mix", "01", "State"], - ["mute", "/ch/16/mix", "01", "State"], + ["mute", "/ch/09/mix", "01"], + ["mute", "/ch/10/mix", "01"], + ["mute", "/ch/11/mix", "01"], + ["mute", "/ch/12/mix", "01"], + ["mute", "/ch/13/mix", "01"], + ["mute", "/ch/14/mix", "01"], + ["mute", "/ch/15/mix", "01"], + ["mute", "/ch/16/mix", "01"], ["layer", "1", "Blink"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -161,24 +161,24 @@ }, "2": { "encoders": [ - ["level", "/ch/01/mix", "02", "0.750000"], - ["level", "/ch/02/mix", "02", "0.750000"], - ["level", "/ch/03/mix", "02", "0.750000"], - ["level", "/ch/04/mix", "02", "0.750000"], - ["level", "/ch/05/mix", "02", "0.750000"], - ["level", "/ch/06/mix", "02", "0.750000"], - ["level", "/ch/07/mix", "02", "0.750000"], - ["level", "/ch/08/mix", "02", "0.750000"] + ["/ch/01/mix", "02", ["reset", "0.750000"]], + ["/ch/02/mix", "02", ["reset", "0.750000"]], + ["/ch/03/mix", "02", ["reset", "0.750000"]], + ["/ch/04/mix", "02", ["reset", "0.750000"]], + ["/ch/05/mix", "02", ["reset", "0.750000"]], + ["/ch/06/mix", "02", ["reset", "0.750000"]], + ["/ch/07/mix", "02", ["reset", "0.750000"]], + ["/ch/08/mix", "02", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "02", "State"], - ["mute", "/ch/02/mix", "02", "State"], - ["mute", "/ch/03/mix", "02", "State"], - ["mute", "/ch/04/mix", "02", "State"], - ["mute", "/ch/05/mix", "02", "State"], - ["mute", "/ch/06/mix", "02", "State"], - ["mute", "/ch/07/mix", "02", "State"], - ["mute", "/ch/08/mix", "02", "State"], + ["mute", "/ch/01/mix", "02"], + ["mute", "/ch/02/mix", "02"], + ["mute", "/ch/03/mix", "02"], + ["mute", "/ch/04/mix", "02"], + ["mute", "/ch/05/mix", "02"], + ["mute", "/ch/06/mix", "02"], + ["mute", "/ch/07/mix", "02"], + ["mute", "/ch/08/mix", "02"], ["layer", "1", "Off"], ["layer", "2prime", "On"], ["layer", "3", "Off"], @@ -193,24 +193,24 @@ }, "2prime": { "encoders": [ - ["level", "/ch/09/mix", "02", "0.750000"], - ["level", "/ch/10/mix", "02", "0.750000"], - ["level", "/ch/11/mix", "02", "0.750000"], - ["level", "/ch/12/mix", "02", "0.750000"], - ["level", "/ch/13/mix", "02", "0.750000"], - ["level", "/ch/14/mix", "02", "0.750000"], - ["level", "/ch/15/mix", "02", "0.750000"], - ["level", "/ch/16/mix", "02", "0.750000"] + ["/ch/09/mix", "02", ["reset", "0.750000"]], + ["/ch/10/mix", "02", ["reset", "0.750000"]], + ["/ch/11/mix", "02", ["reset", "0.750000"]], + ["/ch/12/mix", "02", ["reset", "0.750000"]], + ["/ch/13/mix", "02", ["reset", "0.750000"]], + ["/ch/14/mix", "02", ["reset", "0.750000"]], + ["/ch/15/mix", "02", ["reset", "0.750000"]], + ["/ch/16/mix", "02", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/09/mix", "02", "State"], - ["mute", "/ch/10/mix", "02", "State"], - ["mute", "/ch/11/mix", "02", "State"], - ["mute", "/ch/12/mix", "02", "State"], - ["mute", "/ch/13/mix", "02", "State"], - ["mute", "/ch/14/mix", "02", "State"], - ["mute", "/ch/15/mix", "02", "State"], - ["mute", "/ch/16/mix", "02", "State"], + ["mute", "/ch/09/mix", "02"], + ["mute", "/ch/10/mix", "02"], + ["mute", "/ch/11/mix", "02"], + ["mute", "/ch/12/mix", "02"], + ["mute", "/ch/13/mix", "02"], + ["mute", "/ch/14/mix", "02"], + ["mute", "/ch/15/mix", "02"], + ["mute", "/ch/16/mix", "02"], ["layer", "1", "Off"], ["layer", "2", "Blink"], ["layer", "3", "Off"], @@ -225,24 +225,24 @@ }, "3": { "encoders": [ - ["level", "/ch/01/mix", "03", "0.750000"], - ["level", "/ch/02/mix", "03", "0.750000"], - ["level", "/ch/03/mix", "03", "0.750000"], - ["level", "/ch/04/mix", "03", "0.750000"], - ["level", "/ch/05/mix", "03", "0.750000"], - ["level", "/ch/06/mix", "03", "0.750000"], - ["level", "/ch/07/mix", "03", "0.750000"], - ["level", "/ch/08/mix", "03", "0.750000"] + ["/ch/01/mix", "03", ["reset", "0.750000"]], + ["/ch/02/mix", "03", ["reset", "0.750000"]], + ["/ch/03/mix", "03", ["reset", "0.750000"]], + ["/ch/04/mix", "03", ["reset", "0.750000"]], + ["/ch/05/mix", "03", ["reset", "0.750000"]], + ["/ch/06/mix", "03", ["reset", "0.750000"]], + ["/ch/07/mix", "03", ["reset", "0.750000"]], + ["/ch/08/mix", "03", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "03", "State"], - ["mute", "/ch/02/mix", "03", "State"], - ["mute", "/ch/03/mix", "03", "State"], - ["mute", "/ch/04/mix", "03", "State"], - ["mute", "/ch/05/mix", "03", "State"], - ["mute", "/ch/06/mix", "03", "State"], - ["mute", "/ch/07/mix", "03", "State"], - ["mute", "/ch/08/mix", "03", "State"], + ["mute", "/ch/01/mix", "03"], + ["mute", "/ch/02/mix", "03"], + ["mute", "/ch/03/mix", "03"], + ["mute", "/ch/04/mix", "03"], + ["mute", "/ch/05/mix", "03"], + ["mute", "/ch/06/mix", "03"], + ["mute", "/ch/07/mix", "03"], + ["mute", "/ch/08/mix", "03"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3prime", "On"], @@ -257,24 +257,24 @@ }, "3prime": { "encoders": [ - ["level", "/ch/09/mix", "03", "0.750000"], - ["level", "/ch/10/mix", "03", "0.750000"], - ["level", "/ch/11/mix", "03", "0.750000"], - ["level", "/ch/12/mix", "03", "0.750000"], - ["level", "/ch/13/mix", "03", "0.750000"], - ["level", "/ch/14/mix", "03", "0.750000"], - ["level", "/ch/15/mix", "03", "0.750000"], - ["level", "/ch/16/mix", "03", "0.750000"] + ["/ch/09/mix", "03", ["reset", "0.750000"]], + ["/ch/10/mix", "03", ["reset", "0.750000"]], + ["/ch/11/mix", "03", ["reset", "0.750000"]], + ["/ch/12/mix", "03", ["reset", "0.750000"]], + ["/ch/13/mix", "03", ["reset", "0.750000"]], + ["/ch/14/mix", "03", ["reset", "0.750000"]], + ["/ch/15/mix", "03", ["reset", "0.750000"]], + ["/ch/16/mix", "03", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/09/mix", "03", "State"], - ["mute", "/ch/10/mix", "03", "State"], - ["mute", "/ch/11/mix", "03", "State"], - ["mute", "/ch/12/mix", "03", "State"], - ["mute", "/ch/13/mix", "03", "State"], - ["mute", "/ch/14/mix", "03", "State"], - ["mute", "/ch/15/mix", "03", "State"], - ["mute", "/ch/16/mix", "03", "State"], + ["mute", "/ch/09/mix", "03"], + ["mute", "/ch/10/mix", "03"], + ["mute", "/ch/11/mix", "03"], + ["mute", "/ch/12/mix", "03"], + ["mute", "/ch/13/mix", "03"], + ["mute", "/ch/14/mix", "03"], + ["mute", "/ch/15/mix", "03"], + ["mute", "/ch/16/mix", "03"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Blink"], @@ -289,24 +289,24 @@ }, "4": { "encoders": [ - ["level", "/ch/01/mix", "04", "0.750000"], - ["level", "/ch/02/mix", "04", "0.750000"], - ["level", "/ch/03/mix", "04", "0.750000"], - ["level", "/ch/04/mix", "04", "0.750000"], - ["level", "/ch/05/mix", "04", "0.750000"], - ["level", "/ch/06/mix", "04", "0.750000"], - ["level", "/ch/07/mix", "04", "0.750000"], - ["level", "/ch/08/mix", "04", "0.750000"] + ["/ch/01/mix", "04", ["reset", "0.750000"]], + ["/ch/02/mix", "04", ["reset", "0.750000"]], + ["/ch/03/mix", "04", ["reset", "0.750000"]], + ["/ch/04/mix", "04", ["reset", "0.750000"]], + ["/ch/05/mix", "04", ["reset", "0.750000"]], + ["/ch/06/mix", "04", ["reset", "0.750000"]], + ["/ch/07/mix", "04", ["reset", "0.750000"]], + ["/ch/08/mix", "04", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "04", "State"], - ["mute", "/ch/02/mix", "04", "State"], - ["mute", "/ch/03/mix", "04", "State"], - ["mute", "/ch/04/mix", "04", "State"], - ["mute", "/ch/05/mix", "04", "State"], - ["mute", "/ch/06/mix", "04", "State"], - ["mute", "/ch/07/mix", "04", "State"], - ["mute", "/ch/08/mix", "04", "State"], + ["mute", "/ch/01/mix", "04"], + ["mute", "/ch/02/mix", "04"], + ["mute", "/ch/03/mix", "04"], + ["mute", "/ch/04/mix", "04"], + ["mute", "/ch/05/mix", "04"], + ["mute", "/ch/06/mix", "04"], + ["mute", "/ch/07/mix", "04"], + ["mute", "/ch/08/mix", "04"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -321,24 +321,24 @@ }, "4prime": { "encoders": [ - ["level", "/ch/09/mix", "04", "0.750000"], - ["level", "/ch/10/mix", "04", "0.750000"], - ["level", "/ch/11/mix", "04", "0.750000"], - ["level", "/ch/12/mix", "04", "0.750000"], - ["level", "/ch/13/mix", "04", "0.750000"], - ["level", "/ch/14/mix", "04", "0.750000"], - ["level", "/ch/15/mix", "04", "0.750000"], - ["level", "/ch/16/mix", "04", "0.750000"] + ["/ch/09/mix", "04", ["reset", "0.750000"]], + ["/ch/10/mix", "04", ["reset", "0.750000"]], + ["/ch/11/mix", "04", ["reset", "0.750000"]], + ["/ch/12/mix", "04", ["reset", "0.750000"]], + ["/ch/13/mix", "04", ["reset", "0.750000"]], + ["/ch/14/mix", "04", ["reset", "0.750000"]], + ["/ch/15/mix", "04", ["reset", "0.750000"]], + ["/ch/16/mix", "04", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/09/mix", "04", "State"], - ["mute", "/ch/10/mix", "04", "State"], - ["mute", "/ch/11/mix", "04", "State"], - ["mute", "/ch/12/mix", "04", "State"], - ["mute", "/ch/13/mix", "04", "State"], - ["mute", "/ch/14/mix", "04", "State"], - ["mute", "/ch/15/mix", "04", "State"], - ["mute", "/ch/16/mix", "04", "State"], + ["mute", "/ch/09/mix", "04"], + ["mute", "/ch/10/mix", "04"], + ["mute", "/ch/11/mix", "04"], + ["mute", "/ch/12/mix", "04"], + ["mute", "/ch/13/mix", "04"], + ["mute", "/ch/14/mix", "04"], + ["mute", "/ch/15/mix", "04"], + ["mute", "/ch/16/mix", "04"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -353,24 +353,24 @@ }, "5": { "encoders": [ - ["level", "/ch/01/mix", "05", "0.750000"], - ["level", "/ch/02/mix", "05", "0.750000"], - ["level", "/ch/03/mix", "05", "0.750000"], - ["level", "/ch/04/mix", "05", "0.750000"], - ["level", "/ch/05/mix", "05", "0.750000"], - ["level", "/ch/06/mix", "05", "0.750000"], - ["level", "/ch/07/mix", "05", "0.750000"], - ["level", "/ch/08/mix", "05", "0.750000"] + ["/ch/01/mix", "05", ["reset", "0.750000"]], + ["/ch/02/mix", "05", ["reset", "0.750000"]], + ["/ch/03/mix", "05", ["reset", "0.750000"]], + ["/ch/04/mix", "05", ["reset", "0.750000"]], + ["/ch/05/mix", "05", ["reset", "0.750000"]], + ["/ch/06/mix", "05", ["reset", "0.750000"]], + ["/ch/07/mix", "05", ["reset", "0.750000"]], + ["/ch/08/mix", "05", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "05", "State"], - ["mute", "/ch/02/mix", "05", "State"], - ["mute", "/ch/03/mix", "05", "State"], - ["mute", "/ch/04/mix", "05", "State"], - ["mute", "/ch/05/mix", "05", "State"], - ["mute", "/ch/06/mix", "05", "State"], - ["mute", "/ch/07/mix", "05", "State"], - ["mute", "/ch/08/mix", "05", "State"], + ["mute", "/ch/01/mix", "05"], + ["mute", "/ch/02/mix", "05"], + ["mute", "/ch/03/mix", "05"], + ["mute", "/ch/04/mix", "05"], + ["mute", "/ch/05/mix", "05"], + ["mute", "/ch/06/mix", "05"], + ["mute", "/ch/07/mix", "05"], + ["mute", "/ch/08/mix", "05"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -385,24 +385,24 @@ }, "5prime": { "encoders": [ - ["level", "/ch/09/mix", "05", "0.750000"], - ["level", "/ch/10/mix", "05", "0.750000"], - ["level", "/ch/11/mix", "05", "0.750000"], - ["level", "/ch/12/mix", "05", "0.750000"], - ["level", "/ch/13/mix", "05", "0.750000"], - ["level", "/ch/14/mix", "05", "0.750000"], - ["level", "/ch/15/mix", "05", "0.750000"], - ["level", "/ch/16/mix", "05", "0.750000"] + ["/ch/09/mix", "05", ["reset", "0.750000"]], + ["/ch/10/mix", "05", ["reset", "0.750000"]], + ["/ch/11/mix", "05", ["reset", "0.750000"]], + ["/ch/12/mix", "05", ["reset", "0.750000"]], + ["/ch/13/mix", "05", ["reset", "0.750000"]], + ["/ch/14/mix", "05", ["reset", "0.750000"]], + ["/ch/15/mix", "05", ["reset", "0.750000"]], + ["/ch/16/mix", "05", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/09/mix", "05", "State"], - ["mute", "/ch/10/mix", "05", "State"], - ["mute", "/ch/11/mix", "05", "State"], - ["mute", "/ch/12/mix", "05", "State"], - ["mute", "/ch/13/mix", "05", "State"], - ["mute", "/ch/14/mix", "05", "State"], - ["mute", "/ch/15/mix", "05", "State"], - ["mute", "/ch/16/mix", "05", "State"], + ["mute", "/ch/09/mix", "05"], + ["mute", "/ch/10/mix", "05"], + ["mute", "/ch/11/mix", "05"], + ["mute", "/ch/12/mix", "05"], + ["mute", "/ch/13/mix", "05"], + ["mute", "/ch/14/mix", "05"], + ["mute", "/ch/15/mix", "05"], + ["mute", "/ch/16/mix", "05"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -417,24 +417,24 @@ }, "6": { "encoders": [ - ["level", "/ch/01/mix", "06", "0.750000"], - ["level", "/ch/02/mix", "06", "0.750000"], - ["level", "/ch/03/mix", "06", "0.750000"], - ["level", "/ch/04/mix", "06", "0.750000"], - ["level", "/ch/05/mix", "06", "0.750000"], - ["level", "/ch/06/mix", "06", "0.750000"], - ["level", "/ch/07/mix", "06", "0.750000"], - ["level", "/ch/08/mix", "06", "0.750000"] + ["/ch/01/mix", "06", ["reset", "0.750000"]], + ["/ch/02/mix", "06", ["reset", "0.750000"]], + ["/ch/03/mix", "06", ["reset", "0.750000"]], + ["/ch/04/mix", "06", ["reset", "0.750000"]], + ["/ch/05/mix", "06", ["reset", "0.750000"]], + ["/ch/06/mix", "06", ["reset", "0.750000"]], + ["/ch/07/mix", "06", ["reset", "0.750000"]], + ["/ch/08/mix", "06", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "06", "State"], - ["mute", "/ch/02/mix", "06", "State"], - ["mute", "/ch/03/mix", "06", "State"], - ["mute", "/ch/04/mix", "06", "State"], - ["mute", "/ch/05/mix", "06", "State"], - ["mute", "/ch/06/mix", "06", "State"], - ["mute", "/ch/07/mix", "06", "State"], - ["mute", "/ch/08/mix", "06", "State"], + ["mute", "/ch/01/mix", "06"], + ["mute", "/ch/02/mix", "06"], + ["mute", "/ch/03/mix", "06"], + ["mute", "/ch/04/mix", "06"], + ["mute", "/ch/05/mix", "06"], + ["mute", "/ch/06/mix", "06"], + ["mute", "/ch/07/mix", "06"], + ["mute", "/ch/08/mix", "06"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -449,24 +449,24 @@ }, "6prime": { "encoders": [ - ["level", "/ch/09/mix", "06", "0.750000"], - ["level", "/ch/10/mix", "06", "0.750000"], - ["level", "/ch/11/mix", "06", "0.750000"], - ["level", "/ch/12/mix", "06", "0.750000"], - ["level", "/ch/13/mix", "06", "0.750000"], - ["level", "/ch/14/mix", "06", "0.750000"], - ["level", "/ch/15/mix", "06", "0.750000"], - ["level", "/ch/16/mix", "06", "0.750000"] + ["/ch/09/mix", "06", ["reset", "0.750000"]], + ["/ch/10/mix", "06", ["reset", "0.750000"]], + ["/ch/11/mix", "06", ["reset", "0.750000"]], + ["/ch/12/mix", "06", ["reset", "0.750000"]], + ["/ch/13/mix", "06", ["reset", "0.750000"]], + ["/ch/14/mix", "06", ["reset", "0.750000"]], + ["/ch/15/mix", "06", ["reset", "0.750000"]], + ["/ch/16/mix", "06", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/09/mix", "06", "State"], - ["mute", "/ch/10/mix", "06", "State"], - ["mute", "/ch/11/mix", "06", "State"], - ["mute", "/ch/12/mix", "06", "State"], - ["mute", "/ch/13/mix", "06", "State"], - ["mute", "/ch/14/mix", "06", "State"], - ["mute", "/ch/15/mix", "06", "State"], - ["mute", "/ch/16/mix", "06", "State"], + ["mute", "/ch/09/mix", "06"], + ["mute", "/ch/10/mix", "06"], + ["mute", "/ch/11/mix", "06"], + ["mute", "/ch/12/mix", "06"], + ["mute", "/ch/13/mix", "06"], + ["mute", "/ch/14/mix", "06"], + ["mute", "/ch/15/mix", "06"], + ["mute", "/ch/16/mix", "06"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -481,24 +481,24 @@ }, "FX1": { "encoders": [ - ["level", "/ch/01/mix", "07", "0.750000"], - ["level", "/ch/02/mix", "07", "0.750000"], - ["level", "/ch/03/mix", "07", "0.750000"], - ["level", "/ch/04/mix", "07", "0.750000"], - ["level", "/ch/05/mix", "07", "0.750000"], - ["level", "/ch/06/mix", "07", "0.750000"], - ["level", "/ch/07/mix", "07", "0.750000"], - ["level", "/ch/08/mix", "07", "0.750000"] + ["/ch/01/mix", "07", ["reset", "0.750000"]], + ["/ch/02/mix", "07", ["reset", "0.750000"]], + ["/ch/03/mix", "07", ["reset", "0.750000"]], + ["/ch/04/mix", "07", ["reset", "0.750000"]], + ["/ch/05/mix", "07", ["reset", "0.750000"]], + ["/ch/06/mix", "07", ["reset", "0.750000"]], + ["/ch/07/mix", "07", ["reset", "0.750000"]], + ["/ch/08/mix", "07", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "07", "State"], - ["mute", "/ch/02/mix", "07", "State"], - ["mute", "/ch/03/mix", "07", "State"], - ["mute", "/ch/04/mix", "07", "State"], - ["mute", "/ch/05/mix", "07", "State"], - ["mute", "/ch/06/mix", "07", "State"], - ["mute", "/ch/07/mix", "07", "State"], - ["mute", "/ch/08/mix", "07", "State"], + ["mute", "/ch/01/mix", "07"], + ["mute", "/ch/02/mix", "07"], + ["mute", "/ch/03/mix", "07"], + ["mute", "/ch/04/mix", "07"], + ["mute", "/ch/05/mix", "07"], + ["mute", "/ch/06/mix", "07"], + ["mute", "/ch/07/mix", "07"], + ["mute", "/ch/08/mix", "07"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -513,24 +513,24 @@ }, "FX1prime": { "encoders": [ - ["level", "/ch/09/mix", "07", "0.750000"], - ["level", "/ch/10/mix", "07", "0.750000"], - ["level", "/ch/11/mix", "07", "0.750000"], - ["level", "/ch/12/mix", "07", "0.750000"], - ["level", "/ch/13/mix", "07", "0.750000"], - ["level", "/ch/14/mix", "07", "0.750000"], - ["level", "/ch/15/mix", "07", "0.750000"], - ["level", "/ch/16/mix", "07", "0.750000"] + ["/ch/09/mix", "07", ["reset", "0.750000"]], + ["/ch/10/mix", "07", ["reset", "0.750000"]], + ["/ch/11/mix", "07", ["reset", "0.750000"]], + ["/ch/12/mix", "07", ["reset", "0.750000"]], + ["/ch/13/mix", "07", ["reset", "0.750000"]], + ["/ch/14/mix", "07", ["reset", "0.750000"]], + ["/ch/15/mix", "07", ["reset", "0.750000"]], + ["/ch/16/mix", "07", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/09/mix", "07", "State"], - ["mute", "/ch/10/mix", "07", "State"], - ["mute", "/ch/11/mix", "07", "State"], - ["mute", "/ch/12/mix", "07", "State"], - ["mute", "/ch/13/mix", "07", "State"], - ["mute", "/ch/14/mix", "07", "State"], - ["mute", "/ch/15/mix", "07", "State"], - ["mute", "/ch/16/mix", "07", "State"], + ["mute", "/ch/09/mix", "07"], + ["mute", "/ch/10/mix", "07"], + ["mute", "/ch/11/mix", "07"], + ["mute", "/ch/12/mix", "07"], + ["mute", "/ch/13/mix", "07"], + ["mute", "/ch/14/mix", "07"], + ["mute", "/ch/15/mix", "07"], + ["mute", "/ch/16/mix", "07"], ["layer", "1", "Off"], ["layer", "2", "Off"], ["layer", "3", "Off"], @@ -545,14 +545,14 @@ }, "Gain": { "encoders": [ - ["gain", "/headamp/01"], - ["gain", "/headamp/02"], - ["gain", "/headamp/03"], - ["gain", "/headamp/04"], - ["gain", "/headamp/05"], - ["gain", "/headamp/06"], - ["gain", "/headamp/07"], - ["gain", "/headamp/08"] + ["/headamp/01", "gain", ["none"]], + ["/headamp/02", "gain", ["none"]], + ["/headamp/03", "gain", ["none"]], + ["/headamp/04", "gain", ["none"]], + ["/headamp/05", "gain", ["none"]], + ["/headamp/06", "gain", ["none"]], + ["/headamp/07", "gain", ["none"]], + ["/headamp/08", "gain", ["none"]] ], "buttons": [ ["none", "Off"], @@ -577,14 +577,14 @@ }, "Gainprime": { "encoders": [ - ["gain", "/headamp/09"], - ["gain", "/headamp/10"], - ["gain", "/headamp/11"], - ["gain", "/headamp/12"], - ["gain", "/headamp/13"], - ["gain", "/headamp/14"], - ["gain", "/headamp/15"], - ["gain", "/headamp/16"] + ["/headamp/09", "gain", ["none"]], + ["/headamp/10", "gain", ["none"]], + ["/headamp/11", "gain", ["none"]], + ["/headamp/12", "gain", ["none"]], + ["/headamp/13", "gain", ["none"]], + ["/headamp/14", "gain", ["none"]], + ["/headamp/15", "gain", ["none"]], + ["/headamp/16", "gain", ["none"]] ], "buttons": [ ["none", "Off"], @@ -607,4 +607,4 @@ ["layer", "B", "Off"] ] } -} \ No newline at end of file +} diff --git a/simple.json b/simple.json index 7646e75..f6d7d9e 100644 --- a/simple.json +++ b/simple.json @@ -1,66 +1,66 @@ { "A": { "encoders": [ - ["fader", "/ch/01/mix", "0.750000"], - ["fader", "/ch/02/mix", "0.750000"], - ["fader", "/ch/03/mix", "0.750000"], - ["fader", "/ch/04/mix", "0.750000"], - ["fader", "/ch/05/mix", "0.750000"], - ["fader", "/ch/06/mix", "0.750000"], - ["fader", "/ch/07/mix", "0.750000"], - ["fader", "/ch/08/mix", "0.750000"] + ["/ch/01/mix", "fader", ["reset", "0.750000"]], + ["/ch/02/mix", "fader", ["reset", "0.750000"]], + ["/ch/03/mix", "fader", ["reset", "0.750000"]], + ["/ch/04/mix", "fader", ["reset", "0.750000"]], + ["/ch/05/mix", "fader", ["reset", "0.750000"]], + ["/ch/06/mix", "fader", ["reset", "0.750000"]], + ["/ch/07/mix", "fader", ["reset", "0.750000"]], + ["/ch/08/mix", "fader", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "on", "State"], - ["mute", "/ch/02/mix", "on", "State"], - ["mute", "/ch/03/mix", "on", "State"], - ["mute", "/ch/04/mix", "on", "State"], - ["mute", "/ch/05/mix", "on", "State"], - ["mute", "/ch/06/mix", "on", "State"], - ["mute", "/ch/07/mix", "on", "State"], - ["mute", "/ch/08/mix", "on", "State"], - ["mute", "/ch/09/mix", "on", "State"], - ["mute", "/ch/10/mix", "on", "State"], - ["mute", "/ch/11/mix", "on", "State"], - ["mute", "/ch/12/mix", "on", "State"], - ["mute", "/ch/13/mix", "on", "State"], - ["mute", "/ch/14/mix", "on", "State"], - ["mute", "/ch/15/mix", "on", "State"], - ["mute", "/ch/16/mix", "on", "State"], + ["mute", "/ch/01/mix", "/on"], + ["mute", "/ch/02/mix", "/on"], + ["mute", "/ch/03/mix", "/on"], + ["mute", "/ch/04/mix", "/on"], + ["mute", "/ch/05/mix", "/on"], + ["mute", "/ch/06/mix", "/on"], + ["mute", "/ch/07/mix", "/on"], + ["mute", "/ch/08/mix", "/on"], + ["mute", "/ch/09/mix", "/on"], + ["mute", "/ch/10/mix", "/on"], + ["mute", "/ch/11/mix", "/on"], + ["mute", "/ch/12/mix", "/on"], + ["mute", "/ch/13/mix", "/on"], + ["mute", "/ch/14/mix", "/on"], + ["mute", "/ch/15/mix", "/on"], + ["mute", "/ch/16/mix", "/on"], ["layer", "A", "On"], ["layer", "B", "Off"] ] }, "B": { "encoders": [ - ["fader", "/ch/09/mix", "0.750000"], - ["fader", "/ch/10/mix", "0.750000"], - ["fader", "/ch/11/mix", "0.750000"], - ["fader", "/ch/12/mix", "0.750000"], - ["fader", "/ch/13/mix", "0.750000"], - ["fader", "/ch/14/mix", "0.750000"], - ["fader", "/ch/15/mix", "0.750000"], - ["fader", "/ch/16/mix", "0.750000"] + ["/ch/09/mix", "fader", ["reset", "0.750000"]], + ["/ch/10/mix", "fader", ["reset", "0.750000"]], + ["/ch/11/mix", "fader", ["reset", "0.750000"]], + ["/ch/12/mix", "fader", ["reset", "0.750000"]], + ["/ch/13/mix", "fader", ["reset", "0.750000"]], + ["/ch/14/mix", "fader", ["reset", "0.750000"]], + ["/ch/15/mix", "fader", ["reset", "0.750000"]], + ["/ch/16/mix", "fader", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "on", "State"], - ["mute", "/ch/02/mix", "on", "State"], - ["mute", "/ch/03/mix", "on", "State"], - ["mute", "/ch/04/mix", "on", "State"], - ["mute", "/ch/05/mix", "on", "State"], - ["mute", "/ch/06/mix", "on", "State"], - ["mute", "/ch/07/mix", "on", "State"], - ["mute", "/ch/08/mix", "on", "State"], - ["mute", "/ch/09/mix", "on", "State"], - ["mute", "/ch/10/mix", "on", "State"], - ["mute", "/ch/11/mix", "on", "State"], - ["mute", "/ch/12/mix", "on", "State"], - ["mute", "/ch/13/mix", "on", "State"], - ["mute", "/ch/14/mix", "on", "State"], - ["mute", "/ch/15/mix", "on", "State"], - ["mute", "/ch/16/mix", "on", "State"], + ["mute", "/ch/01/mix", "/on"], + ["mute", "/ch/02/mix", "/on"], + ["mute", "/ch/03/mix", "/on"], + ["mute", "/ch/04/mix", "/on"], + ["mute", "/ch/05/mix", "/on"], + ["mute", "/ch/06/mix", "/on"], + ["mute", "/ch/07/mix", "/on"], + ["mute", "/ch/08/mix", "/on"], + ["mute", "/ch/09/mix", "/on"], + ["mute", "/ch/10/mix", "/on"], + ["mute", "/ch/11/mix", "/on"], + ["mute", "/ch/12/mix", "/on"], + ["mute", "/ch/13/mix", "/on"], + ["mute", "/ch/14/mix", "/on"], + ["mute", "/ch/15/mix", "/on"], + ["mute", "/ch/16/mix", "/on"], ["layer", "A", "Off"], ["layer", "B", "On"] ] } -} \ No newline at end of file +} From 23fa26d32e7f705eb00d6ee179ce112b7e4b2880 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Sat, 4 Sep 2021 17:57:22 -0400 Subject: [PATCH 19/25] updated to support the additional functions used in peter's config but not mine --- lib/midicontroller.py | 40 ++- lib/mixerstate.py | 395 ++++++++++++++-------------- lib/xair.py | 12 +- monitors.json | 581 ++++++------------------------------------ simple.json | 14 +- 5 files changed, 319 insertions(+), 723 deletions(-) diff --git a/lib/midicontroller.py b/lib/midicontroller.py index 2d32c90..6319217 100644 --- a/lib/midicontroller.py +++ b/lib/midicontroller.py @@ -9,18 +9,19 @@ class TempoDetector: Detect song tempo via a tap button """ _MAX_TAP_DURATION = 3.0 - current_tempo = 0.5 - - def __init__(self, midi_controller): - self.midi_controller = midi_controller + number = -1 + + def __init__(self, state): + self.state = state + self.midi_controller = state.midi_controller self.last_tap = 0 self.tap_num = 0 self.tap_delta = 0 worker = threading.Thread(target = self.blink) worker.daemon = True worker.start() - + def tap(self): current_time = time.time() if current_time - self.last_tap > self._MAX_TAP_DURATION: @@ -32,18 +33,21 @@ def tap(self): self.tap_delta += current_time - self.last_tap if self.tap_num > 0: # Update tempo in mixer after at least 2 taps - self.midi_controller.update_tempo(self.tap_delta / self.tap_num, True) + self.state.update_tempo(self.tap_delta / self.tap_num) self.current_tempo = self.tap_delta / self.tap_num self.last_tap = current_time - + def blink(self): try: - while True: - self.midi_controller.tempo_led(True) - time.sleep(self.current_tempo * 0.2) - self.midi_controller.tempo_led(False) - time.sleep(self.current_tempo * 0.8) + while self.state is not None and self.state.quit_called != True: + if self.number != -1: + self.midi_controller.set_channel_mute(self.number, "On") + time.sleep(self.current_tempo * 0.2) + self.midi_controller.set_channel_mute(self.number, "Off") + time.sleep(self.current_tempo * 0.8) except KeyboardInterrupt: + if self.state is not None: + self.state.shutdown() exit() class MidiController: @@ -183,15 +187,7 @@ def midi_listener(self): else: print('Received unknown {}'.format(msg)) elif msg.type == 'pitchwheel': - value = (msg.pitch + 8192) / 16384 - if self.state.debug: - print('Wheel set to {}'.format(msg)) - if msg.pitch > 8000: - if self.state is not None: - self.state.shutdown() - else: - self.cleanup_controller() - exit() + self.state.fader_move(msg) elif msg.type != 'note_off' and msg.type != 'note_on': print('Received unknown {}'.format(msg)) if self.state.quit_called: @@ -219,6 +215,8 @@ def set_channel_mute(self, channel, LED): self.set_button(channel, self.LED_ON) elif LED == "Off" or LED == 1: # LED is sense Negative for mute self.set_button(channel, self.LED_OFF) + elif LED == "none": + return else: self.set_button(channel, self.LED_BLINK) diff --git a/lib/mixerstate.py b/lib/mixerstate.py index 62b8cec..9660515 100644 --- a/lib/mixerstate.py +++ b/lib/mixerstate.py @@ -8,105 +8,98 @@ import json from collections import deque from lib.xair import XAirClient, find_mixer -from lib.midicontroller import MidiController +from lib.midicontroller import MidiController, TempoDetector class Channel: """ Represents a single channel or bus """ def __init__(self, addr): - self.fader = 0.0 - if addr.startswith('/ch'): - # the 6 aux bus followed by the 4 effects - self.sends = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] - self.enables = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - else: - self.sends = None - self.enables = None - self.ch_on = 1 + # LR then 6 aux bus followed by the 4 effects + self.sends = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + self.enables = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] self.osc_base_addr = addr + def get_m_addr(self, bus): + if bus == 0: + if self.osc_base_addr.startswith('/config'): + return(self.osc_base_addr) + else: + return(self.osc_base_addr + "/on") + else: + return(self.osc_base_addr + '/{:0>2d}/level'.format(bus)) + + def get_l_addr(self, bus): + if bus == 0: + if self.osc_base_addr.startswith('/head'): + return(self.osc_base_addr + "/gain") + else: + return(self.osc_base_addr + "/fader") + else: + return(self.osc_base_addr + '/{:0>2d}/level'.format(bus)) + def toggle_mute(self, bus): """Toggle a mute on or off.""" - if bus == "/on" or bus == "": - """Toggle the state of the channel mute.""" - if self.ch_on == 1: - self.ch_on = 0 - else: - self.ch_on = 1 - return(self.osc_base_addr + bus, - self.ch_on, self.ch_on) +# print("toggle mute of %s bus %s from %s" % (self.get_m_addr(bus), bus, self.enables[bus])) + if self.enables[bus] == 1: + self.enables[bus] = 0 + param=0.0 else: - """Toggle the state of a send mute.""" - bus_num = int(bus) - 1 - if self.enables is not None: - if self.enables[bus_num] == 1: - self.enables[bus_num] = 0 - param=0.0 - else: - self.enables[bus_num] = 1 - param=self.sends[bus_num] - return(self.osc_base_addr + '/' + bus + '/level', - param, self.enables[bus_num]) - return(None, None, "Off") + self.enables[bus] = 1 + param=self.sends[bus] + if bus == 0: + return(self.get_m_addr(bus), self.enables[bus], self.enables[bus]) + else: + return(self.get_m_addr(bus), param, self.enables[bus]) def set_mute(self, bus, value): """Set the state of the channel mute.""" - self.ch_on = value - return(self.osc_base_addr + bus, - self.ch_on, self.ch_on) + self.enables[bus] = value + return(self.get_m_addr(bus), self.enables[bus], self.enables[bus]) def get_mute(self, bus): - """Return the curent mute on/off.""" - if bus == "/on": - return(self.ch_on) - else: - if self.enables is not None: - bus_num = int(bus) - 1 - return(self.enables[bus_num]) - return ("Off") + return(self.enables[bus]) def change_level(self, bus, delta): """Change the level of a fader, mic pre, or bus send.""" - print("changing %s bus %s by %s" % (self.osc_base_addr, bus, delta)) - if bus == "fader" or bus == "gain": - self.fader = min(max(0.0, self.fader + (delta / 200)), 1.0) - return(self.osc_base_addr + '/' + bus, - self.fader, self.fader) - if self.sends is not None: - bus_num = int(bus) - 1 -# print(" changing channel %s/%s by %s from %s %s" % \ -# (self.osc_base_addr, bus_num, delta, self.sends[bus_num], bus)) - self.sends[bus_num] = \ - min(max(0.0, self.sends[bus_num] + (delta / 200)), 1.0) - return('%s/%s/level' %(self.osc_base_addr, bus), - self.sends[bus_num], self.sends[bus_num]) - return (None, None,"Off") +# print("changing %s bus %s by %s" % (self.osc_base_addr, bus, delta)) + if bus == "gain": + bus = 0 + self.sends[bus] = min(max(0.0, self.sends[bus] + (delta / 200)), 1.0) + return(self.get_l_addr(bus), self.sends[bus], self.sends[bus]) def set_level(self, bus, value): """Set the level of a fader, mic pre, or bus send.""" - if bus == "fader" or bus == "gain": - self.fader = value - return(self.osc_base_addr + '/' + bus, - self.fader, self.fader) - if self.sends is not None: - bus_num = int(bus) - 1 -# print(" setting channel %s/%s to %s from %s %s" % \ -# (self.osc_base_addr, bus_num, value, self.sends[bus_num], bus)) - self.sends[bus_num] = value - return('%s/%s/level' %(self.osc_base_addr, bus), - self.sends[bus_num], self.sends[bus_num]) - return (None, None,"Off") + if bus == "gain": + bus = 0 + self.sends[bus] = value + return(self.get_l_addr(bus), + self.sends[bus], self.sends[bus]) def get_level(self, bus): """Return the current level of a fader, mic pre, or bus send.""" - if bus == "fader" or bus == "gain": - return(self.fader) - if self.sends is not None: - bus_num = int(bus) - 1 - return(self.sends[bus_num]) - return (0) + if bus == "gain": + bus = 0 + return(self.sends[bus]) +class SubProc: + """ + Manages a subprocess + """ + def __init__(self, call_type, proc_name, args) -> None: + self.call_type = call_type + self.proc_name = proc_name + self.args = args + self.max = len(args) + self.current = 0 + + def toggle(self): + if self.call_type == "subprocess": + try: + subprocess.call([self.proc_name, self.args[self.current]]) + except OSError: + pass + self.current += (self.current + 1) % self.max # the config json file specifies a number of layers each idendified by a name # within the layer there are currently two sections: encoders and buttons @@ -116,7 +109,7 @@ def get_level(self, bus): # gain, channel # level, channel, bus, [button] # There are 18 buttons per section to types: lengths -button_def = {'quit': 3, 'none': 2, 'layer': 3, 'clip': 2, 'record': 2, 'mute': 3} +button_def = {'quit': 3, 'none': 2, 'layer': 4, 'clip': 2, 'mute': 3, 'tap': 2, "send": 3} button_types = set(button_def.keys()) class Layer: @@ -125,24 +118,17 @@ class Layer: """ encoders = [] buttons = [] + faders = [] channels = {} + proc_list = {} + active_bus = 0 - def __init__(self, layer_name, config_layer, channels, layer_names) -> None: + def __init__(self, layer_name, config_layer, channels, layer_names, proc_list) -> None: "Initialize a Layer" # first check for gross configurations errors - errors = 0 - if "encoders" not in config_layer.keys(): - print("Error: Layer %s does not contain an encoders section." % layer_name) - errors += 1 - if "buttons" not in config_layer.keys(): - print("Error: Layer %s contains an unknown section %s." % layer_name) - errors += 1 - if errors > 0: - print("Errors in config file, exiting.") - exit() number = len(config_layer.keys()) - if number > 2: - print("Warning: expected exactly two types of controls %d found" % number) + if number > 3: + print("Warning: expected exactly three types of controls %d found" % number) # Process the encoders self.encoders = config_layer["encoders"] @@ -157,6 +143,8 @@ def __init__(self, layer_name, config_layer, channels, layer_names) -> None: channels[encoder[0]] = Channel(encoder[0]) if encoder[2][0] == "mute" and encoder[2][1] not in channels.keys(): channels[encoder[2][1]] = Channel(encoder[2][1]) + elif encoder[2][0] == "subprocess" and encoder[2][1] not in proc_list.keys(): + proc_list[encoder[2][1]] = SubProc(encoder[2][0], encoder[2][1], encoder[2][2]) # Process the buttons self.buttons = config_layer["buttons"] @@ -173,52 +161,86 @@ def __init__(self, layer_name, config_layer, channels, layer_names) -> None: if button[0] == "layer" and button[1] not in layer_names: print("Error: Layer %s has a change to undefined layer %s." % \ (layer_name, button[1])) - if button[1] == "mute" and button[1] not in channels.keys(): + if button[0] == "mute" and button[1] not in channels.keys(): channels[button[1]] = Channel(button[1]) + # Process the fader + self.faders = config_layer["fader"] + if len(self.faders) != 1: + print("Layer %s does not contain 1 'fader' definitions." % layer_name) + for fader in self.faders: + if len(fader) != 2: + print("Error: Fader %s of layer %s does not contain 2 elements, exiting." % \ + (fader[0], layer_name)) + exit() + if fader[0] != "none" and fader[0] != "quit" and fader[0] not in channels.keys(): + channels[fader[0]] = Channel(fader[0]) + self.channels = channels + self.proc_list = proc_list def encoder_turn(self, number, value): encoder = self.encoders[number] - if encoder[0] != "none": - return(self.channels[encoder[0]].change_level(encoder[1], value)) - return(None, None,"Off") + if encoder[0] == "none": + return(None, None, 0.0) + return(self.channels[encoder[0]].change_level(self.active_bus, value)) def encoder_press(self, number): encoder = self.encoders[number] if encoder[2][0] == "reset": - return(self.channels[encoder[0]].set_level(encoder[1], float(encoder[2][1]))) + return(self.channels[encoder[0]].set_level(self.active_bus, float(encoder[2][1]))) elif encoder[2][0] == "mute": - (address, param, LED) = self.channels[encoder[2][1]].toggle_mute(encoder[2][2]) - return(address, param, self.channels[encoder[0]].get_level(encoder[1])) + (address, param, LED) = self.channels[encoder[2][1]].toggle_mute(int(encoder[2][2])) + return(address, param, self.channels[encoder[0]].get_level(self.active_bus)) + elif encoder[2][0] == "subprocess": + self.proc_list[encoder[2][1]].toggle() + return(None, None, self.channels[encoder[0]].get_level(self.active_bus)) if encoder[0] == "none": - return(None, None,"Off") - else: - return(None, None, self.channels[encoder[0]].get_level(encoder[1])) + return(None, None, 0.0) + return(None, None, self.channels[encoder[0]].get_level(self.active_bus)) def encoder_state(self, number): encoder = self.encoders[number] - return(self.channels[encoder[0]].get_level(encoder[1])) + if encoder[0] == "none": + return 0.0 + return(self.channels[encoder[0]].get_level(self.active_bus)) - def toggle_mute(self, number): + def toggle_button(self, number): button = self.buttons[number] if button[0] == "mute": - return(self.channels[button[1]].toggle_mute(button[2])) - else: - # return the relevant action for the non mixer based button - return(button[0], button[1], button[2] if len(button) > 2 else None ) + return (self.channels[button[1]].toggle_mute(self.active_bus)) + if button[0] == "layer": + max_bus = int(button[2]) + return ("layer", button[1], max_bus if self.active_bus > max_bus else self.active_bus) + if button[0] == "send": + if self.active_bus == int(button[1]): + self.active_bus = 0 + return ("send", None, "Off") + else: + self.active_bus = int(button[1]) + return ("send", None, "On") + # return the relevant action for the non mixer based button + return (button[0], button[1], button[-1]) - def mute_state(self, number): + def button_state(self, number): button = self.buttons[number] if button[0] == "mute": - return(self.channels[button[1]].get_mute(button[2])) + return(self.channels[button[1]].get_mute(self.active_bus)) + elif button[0] == "send": + return("On" if self.active_bus == int(button[1]) else "Off") else: return(button[-1]) + def fader_move(self, value): + if self.faders[0][0] == "quit": + return("quit", "none", "none") + else: + return(self.channels[self.faders[0][0]].set_level(int(self.faders[0][1]), value)) + def encoder_number(self, name): number = 0 for encoder in self.encoders: - if name == encoder[1]: + if name == encoder[0]: return number number += 1 return -1 @@ -226,7 +248,7 @@ def encoder_number(self, name): def button_number(self, name): number = 0 for button in self.buttons: - if name == button[1]: + if len(button) > 1 and name == button[1]: return number number += 1 return -1 @@ -259,6 +281,7 @@ class MixerState: quit_called = False layers = {} channels = {} + proc_list = {} current_layer = None # ID numbers for all available delay effects @@ -268,17 +291,11 @@ class MixerState: lr = Channel('/lr/mix') - mute_groups = [ - Channel('/config/mute/1'), - Channel('/config/mute/2'), - Channel('/config/mute/3'), - Channel('/config/mute/4') - ] - mpd_playing = True midi_controller = None xair_client = None + tempo_detector = None meters = [] for i in range(16): @@ -294,9 +311,6 @@ def __init__(self, args) -> None: self.levels = args.levels # initialize internal data structures - if self.clip: # clipping protection doesn't work without level info - self.levels = True - config_json = "simple.json" if args.config_file is not None: config_json = args.config_file[0] @@ -309,7 +323,7 @@ def __init__(self, args) -> None: if self.current_layer == None: self.current_layer = layer_name self.layers[layer_name] = Layer(layer_name, config[layer_name], - self.channels, layer_names) + self.channels, layer_names, self.proc_list) def initialize_state(self): self.quit_called = False @@ -332,9 +346,11 @@ def initialize_state(self): self.midi_controller = None self.xair_client = None return False + self.tempo_detector = TempoDetector(self) self.read_initial_state() self.midi_controller.activate_bus() + self.tempo_detector.number = self.layers[self.current_layer].button_number("tap") return True def shutdown(self): @@ -349,50 +365,42 @@ def shutdown(self): # if self.screen_obj is not None: # self.screen_obj.quit() - def toggle_mpc(self): - if self.mpd_playing: - try: - subprocess.call(['mpc', 'pause']) - except OSError: - pass - self.mpd_playing = False - else: - try: - subprocess.call(['mpc', 'play']) - except OSError: - pass - self.mpd_playing = True - def button_press(self, number): """Handle a button press.""" if self.debug: print('Button %d pressed' % number) - (address, param, LED) = self.layers[self.current_layer].toggle_mute(number) + (address, param, LED) = self.layers[self.current_layer].toggle_button(number) if address == 'layer': self.current_layer = param + self.layers[self.current_layer].active_bus = LED self.midi_controller.activate_bus() + self.tempo_detector.number = self.layers[self.current_layer].button_number("tap") return self.get_button(number) + elif address == 'send': + self.midi_controller.activate_bus() + return LED elif address == 'clip': self.clip = not self.clip - if self.clip: - self.levels = True - return "Off" - else: - return "On" + return "On" if self.clip else "Off" elif address == 'quit': self.shutdown() exit() elif address == 'record': pass return "Off" + elif address == 'tap': + self.tempo_detector.tap() + return "none" if address != None: + if address.startswith('/config/mute'): + param = 1 if param == 0 else 0 self.xair_client.send(address=address, param=param) return LED def get_button(self, number): if self.debug: print('Getting state of button number %d' % number) - return(self.layers[self.current_layer].mute_state(number)) + return(self.layers[self.current_layer].button_state(number)) # def mac_button(self, button): # "call a function for transport buttons on mac" @@ -419,7 +427,15 @@ def encoder_turn(self, number, delta): def encoder_press(self, number): (address, param, LED) = self.layers[self.current_layer].encoder_press(number) if address != None: - self.xair_client.send(address=address, param=param) + print("sending %s %s" % (address, param)) + if address.startswith('/config/mute'): + invert = 1 if param == 0 else 0 + self.xair_client.send(address=address, param=invert) + number = self.layers[self.current_layer].button_number(address) + if number != -1: + self.midi_controller.set_channel_mute(number, param) + else: + self.xair_client.send(address=address, param=param) return LED def get_encoder(self, number): @@ -427,20 +443,20 @@ def get_encoder(self, number): print('Getting state of encoder number %d' % number) return(self.layers[self.current_layer].encoder_state(number)) + def fader_move(self, msg): + value = (msg.pitch + 8192) / 16384 + if self.debug: + print('Wheel set to {}'.format(msg)) + (address, param, LED) = self.layers[self.current_layer].fader_move(value) + if address == "quit": + if value > .98: + self.shutdown() + exit() + elif address != None: + self.xair_client.send(address=address, param=param) + def received_osc(self, addr, value): """Process an OSC input.""" -# elif addr.startswith('/fx') and (addr.endswith('/par/01') or addr.endswith('/par/02')): -# if self.fx_slots[int(addr[4:5]) - 1] in self._DELAY_FX_IDS: -# self.midi_controller.update_tempo(value * 3) -# elif addr.startswith('/fx/') and addr.endswith('/type'): -# self.fx_slots[int(addr[4:5]) - 1] = value -# if value in self._DELAY_FX_IDS: -# # slot contains a delay, get current time value -# param_id = '01' -# if value == 10: -# param_id = '02' -# self.xair_client.send(address = '/fx/%s/par/%s' % (addr[4:5], param_id)) - prefix = None three_element = '/'.join(addr.split('/',4)[:-1]) # first three parts of the OSC path two_element = '/'.join(addr.split('/',3)[:-1]) # first two parts of the OSC path @@ -450,30 +466,34 @@ def received_osc(self, addr, value): prefix = three_element elif two_element in self.channels.keys(): prefix = two_element + elif addr in self.channels.keys(): + prefix = addr if prefix is not None: if addr.startswith('/config/mute'): - number = '/'.join(addr.split('/')[-1]) - self.channels[prefix].set_mute(number, value) - number = self.layers[self.current_layer].encoder_number(prefix) + invert = 1 if value == 0 else 0 + if self.debug: + print (" received mute channel %s" % prefix) + self.channels[prefix].set_mute(0, invert) + number = self.layers[self.current_layer].button_number(prefix) if number != -1: - self.midi_controller.set_channel_mute(number, value) + self.midi_controller.set_channel_mute(number, invert) elif addr.endswith('/fader'): # chanel fader level if self.debug: - print('Channel %s level %f' % (addr, value)) - self.channels[prefix].set_level("fader", value) + print(' Channel %s level %f' % (addr, value)) + self.channels[prefix].set_level(0, value) number = self.layers[self.current_layer].encoder_number(prefix) if number != -1: self.midi_controller.set_ring(number, value) elif addr.endswith('/on'): # channel enable if self.debug: - print('%s unMute %d' % (addr, value)) - self.channels[prefix].set_mute("/on", value) + print(' %s unMute %d' % (addr, value)) + self.channels[prefix].set_mute(0, value) number = self.layers[self.current_layer].button_number(prefix) if number != -1: self.midi_controller.set_channel_mute(number, value) elif addr.endswith('/level'): if self.debug: - print('%s level %f' % (addr, value)) + print(' %s level %f' % (addr, value)) bus = int(addr[-8:-6]) self.channels[prefix].set_level(bus, value) number = self.layers[self.current_layer].encoder_number(prefix) @@ -481,11 +501,24 @@ def received_osc(self, addr, value): self.midi_controller.set_ring(number, value) elif addr.endswith('/gain'): if self.debug: - print('%s Gain level %f' % (addr, value)) + print(' %s Gain level %f' % (addr, value)) self.channels[prefix].set_level("gain", value) number = self.layers[self.current_layer].encoder_number(prefix) if number != -1: self.midi_controller.set_ring(number, value) + elif addr.startswith('/fx') and (addr.endswith('/par/01') or addr.endswith('/par/02')): + if self.fx_slots[int(addr[4:5]) - 1] in self._DELAY_FX_IDS: + self.tempo_detector.current_tempo = value * 3 + elif addr.startswith('/fx/') and addr.endswith('/type'): + self.fx_slots[int(addr[4:5]) - 1] = value + if value in self._DELAY_FX_IDS: + # slot contains a delay, get current time value + param_id = '01' + if value == 10: + param_id = '02' + self.xair_client.send(address = '/fx/%s/par/%s' % (addr[4:5], param_id)) + elif self.debug: + print('processing unknown OSC message with %s and %s value.' % (addr, value)) def read_initial_state(self): """ Refresh state for all faders and mutes.""" @@ -493,27 +526,24 @@ def read_initial_state(self): addr = channel.osc_base_addr if addr.startswith('/head'): self.xair_client.send(address=addr + '/gain') - time.sleep(0.002) + time.sleep(self.xair_client._WAIT_TIME) + elif addr.startswith('/config'): + self.xair_client.send(address=addr) + time.sleep(self.xair_client._WAIT_TIME) else: self.xair_client.send(address=addr + '/fader') - time.sleep(0.002) + time.sleep(self.xair_client._WAIT_TIME) self.xair_client.send(address=addr + '/on') - time.sleep(0.002) + time.sleep(self.xair_client._WAIT_TIME) if channel.sends is not None: for k in range(len(channel.sends)): self.xair_client.send(address=addr + '/{:0>2d}/level'.format(k + 1)) - time.sleep(0.002) - - # get all mute groups - #for i in range(0, 4): - # self.xair_client.send(address = self.mute_groups[i].osc_base_addr) - # time.sleep(0.01) - + time.sleep(self.xair_client._WAIT_TIME) # get all fx types - #for i in range(1, 5): - # self.xair_client.send(address = '/fx/%d/type' % i) - # time.sleep(0.01) + for i in range(1, 5): + self.xair_client.send(address = '/fx/%d/type' % i) + time.sleep(self.xair_client._WAIT_TIME) def update_tempo(self, tempo): for i in range(0, 4): @@ -524,17 +554,12 @@ def update_tempo(self, tempo): param_id = '02' self.xair_client.send(address = '/fx/%d/par/%s' % (i + 1, param_id), param = tempo / 3) -# how to set up a meter subscription -# values send every 50ms for 10s -# the actual call is located in xair.py in the refresh connecton method that runs every 5 s +# the meter subscription is setup in the xair_client in the refresh method that runs every 5s +# a subscription sends values every 50ms for 10s # # meters 1 provide data per channel and bus -# self.xair_client.send(address="/meters", param=["/meters/1"]) # pre faid ch levels -# time.sleep(0.002) -# -# using meters 2, input levels, as these will match the headamps even if mapped to other channels -# self.xair_client.send(address="/meters", param=["/meters/2"]) -# time.sleep(0.002) +# meters 2, input levels, as these will match the headamps even if mapped to other channels +# layout below assumes meters 2 def received_meters(self, addr, data): "receive an OSC Meters packet" @@ -570,8 +595,6 @@ def received_meters(self, addr, data): self.banks[self.active_bank][fader].fader) self.active_bank = active_bank if self.debug: - #print('Meters("%s", %s) size %s length %s' % (addr, data[0], len(data[0]), data_size)) print('Meters %s ch 8 %s %s %s' % (addr, values[7], short[7], med[7])) - #print(values) # if self.screen_obj is not None: # self.screen_obj.screen_loop() diff --git a/lib/xair.py b/lib/xair.py index cedcbdb..671e063 100644 --- a/lib/xair.py +++ b/lib/xair.py @@ -33,7 +33,7 @@ class XAirClient: Handles the communication with the X-Air mixer via the OSC protocol """ _CONNECT_TIMEOUT = 0.5 - _WAIT_TIME = 0.02 + _WAIT_TIME = 0.002 _REFRESH_TIMEOUT = 5 XAIR_PORT = 10024 @@ -89,12 +89,14 @@ def msg_handler(self, addr, *data): return #print 'OSCReceived("%s", %s, %s)' % (addr, tags, data) if addr.endswith('/fader') or addr.endswith('/on') or addr.endswith('/level') or \ - addr.startswith('/config/mute') or addr.endswith('/gain'): + addr.startswith('/config/mute') or addr.endswith('/gain') or addr.startswith('/fx/'): self.state.received_osc(addr, data[0]) elif addr == '/xinfo': self.info_response = data[:] elif addr.startswith('/meters'): self.state.received_meters(addr, data) + elif addr.startswith('/-'): + pass else: #if self.state.debug and addr.start: print('OSCReceived("%s", %s)' % (addr, data)) @@ -110,11 +112,11 @@ def refresh_connection(self): # the main loop try: while not self.state.quit_called and self.server is not None: self.server.send_message("/xremotenfb", None) - if self.state.levels: + if self.state.levels or self.state.clip: # using input levels, as these match the headamps when channels are remapped - time.sleep(0.002) + time.sleep(self._REFRESH_TIMEOUT) self.send(address="/meters", param=["/meters/2"]) - if self.state.clip: # seems to crash if clipping protection runs for too long + if self.state.clip: # seems to crash if clipping protection runs for more than one cycle if self.state.debug: print("start auto level") self.state.clip = False diff --git a/monitors.json b/monitors.json index aa522f8..6cdfa00 100644 --- a/monitors.json +++ b/monitors.json @@ -19,16 +19,19 @@ ["mute", "/ch/06/mix", "/on"], ["mute", "/ch/07/mix", "/on"], ["mute", "/ch/08/mix", "/on"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "Aprime", "On"], - ["layer", "B", "Off"] + ["send", "1", "send"], + ["send", "2", "send"], + ["send", "3", "send"], + ["send", "4", "send"], + ["send", "5", "send"], + ["send", "6", "send"], + ["send", "7", "send"], + ["layer", "Gain", "0", "Off"], + ["layer", "Aprime", "7", "On"], + ["layer", "B", "0", "Off"] + ], + "fader": [ + ["quit", "0"] ] }, "Aprime": { @@ -51,28 +54,31 @@ ["mute", "/ch/14/mix", "/on"], ["mute", "/ch/15/mix", "/on"], ["mute", "/ch/16/mix", "/on"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Blink"], - ["layer", "B", "Off"] + ["send", "1", "send"], + ["send", "2", "send"], + ["send", "3", "send"], + ["send", "4", "send"], + ["send", "5", "send"], + ["send", "6", "send"], + ["send", "7", "send"], + ["layer", "Gain", "0", "Off"], + ["layer", "A", "7", "Blink"], + ["layer", "B", "0", "Off"] + ], + "fader": [ + ["quit", "0"] ] }, "B": { "encoders": [ - ["/bus/1/mix", "fader", ["reset", "0.375367"]], - ["/bus/2/mix", "fader", ["reset", "0.375367"]], - ["/bus/3/mix", "fader", ["reset", "0.375367"]], - ["/bus/4/mix", "fader", ["reset", "0.375367"]], - ["/bus/5/mix", "fader", ["reset", "0.375367"]], - ["/bus/6/mix", "fader", ["reset", "0.375367"]], - ["/rtn/aux/mix", "fader", ["reset", "0.750000"]], - ["/lr/mix", "fader", ["reset", "0.375367"]] + ["/bus/1/mix", "0", ["reset", "0.375367"]], + ["/bus/2/mix", "0", ["reset", "0.375367"]], + ["/bus/3/mix", "0", ["reset", "0.375367"]], + ["/bus/4/mix", "0", ["reset", "0.375367"]], + ["/bus/5/mix", "0", ["reset", "0.375367"]], + ["/bus/6/mix", "0", ["reset", "0.375367"]], + ["/rtn/aux/mix", "0", ["reset", "0.750000"]], + ["/lr/mix", "0", ["reset", "0.375367"]] ], "buttons": [ ["mute", "/bus/1/mix", "/on"], @@ -83,464 +89,19 @@ ["mute", "/bus/6/mix", "/on"], ["mute", "/rtn/aux/mix", "/on"], ["mute", "/lr/mix", "/on"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "On"] - ] - }, - "1": { - "encoders": [ - ["/ch/01/mix", "01", ["reset", "0.750000"]], - ["/ch/02/mix", "01", ["reset", "0.750000"]], - ["/ch/03/mix", "01", ["reset", "0.750000"]], - ["/ch/04/mix", "01", ["reset", "0.750000"]], - ["/ch/05/mix", "01", ["reset", "0.750000"]], - ["/ch/06/mix", "01", ["reset", "0.750000"]], - ["/ch/07/mix", "01", ["reset", "0.750000"]], - ["/ch/08/mix", "01", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/01/mix", "01"], - ["mute", "/ch/02/mix", "01"], - ["mute", "/ch/03/mix", "01"], - ["mute", "/ch/04/mix", "01"], - ["mute", "/ch/05/mix", "01"], - ["mute", "/ch/06/mix", "01"], - ["mute", "/ch/07/mix", "01"], - ["mute", "/ch/08/mix", "01"], - ["layer", "1prime", "On"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "1prime": { - "encoders": [ - ["/ch/09/mix", "01", ["reset", "0.750000"]], - ["/ch/10/mix", "01", ["reset", "0.750000"]], - ["/ch/11/mix", "01", ["reset", "0.750000"]], - ["/ch/12/mix", "01", ["reset", "0.750000"]], - ["/ch/13/mix", "01", ["reset", "0.750000"]], - ["/ch/14/mix", "01", ["reset", "0.750000"]], - ["/ch/15/mix", "01", ["reset", "0.750000"]], - ["/ch/16/mix", "01", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/09/mix", "01"], - ["mute", "/ch/10/mix", "01"], - ["mute", "/ch/11/mix", "01"], - ["mute", "/ch/12/mix", "01"], - ["mute", "/ch/13/mix", "01"], - ["mute", "/ch/14/mix", "01"], - ["mute", "/ch/15/mix", "01"], - ["mute", "/ch/16/mix", "01"], - ["layer", "1", "Blink"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "2": { - "encoders": [ - ["/ch/01/mix", "02", ["reset", "0.750000"]], - ["/ch/02/mix", "02", ["reset", "0.750000"]], - ["/ch/03/mix", "02", ["reset", "0.750000"]], - ["/ch/04/mix", "02", ["reset", "0.750000"]], - ["/ch/05/mix", "02", ["reset", "0.750000"]], - ["/ch/06/mix", "02", ["reset", "0.750000"]], - ["/ch/07/mix", "02", ["reset", "0.750000"]], - ["/ch/08/mix", "02", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/01/mix", "02"], - ["mute", "/ch/02/mix", "02"], - ["mute", "/ch/03/mix", "02"], - ["mute", "/ch/04/mix", "02"], - ["mute", "/ch/05/mix", "02"], - ["mute", "/ch/06/mix", "02"], - ["mute", "/ch/07/mix", "02"], - ["mute", "/ch/08/mix", "02"], - ["layer", "1", "Off"], - ["layer", "2prime", "On"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "2prime": { - "encoders": [ - ["/ch/09/mix", "02", ["reset", "0.750000"]], - ["/ch/10/mix", "02", ["reset", "0.750000"]], - ["/ch/11/mix", "02", ["reset", "0.750000"]], - ["/ch/12/mix", "02", ["reset", "0.750000"]], - ["/ch/13/mix", "02", ["reset", "0.750000"]], - ["/ch/14/mix", "02", ["reset", "0.750000"]], - ["/ch/15/mix", "02", ["reset", "0.750000"]], - ["/ch/16/mix", "02", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/09/mix", "02"], - ["mute", "/ch/10/mix", "02"], - ["mute", "/ch/11/mix", "02"], - ["mute", "/ch/12/mix", "02"], - ["mute", "/ch/13/mix", "02"], - ["mute", "/ch/14/mix", "02"], - ["mute", "/ch/15/mix", "02"], - ["mute", "/ch/16/mix", "02"], - ["layer", "1", "Off"], - ["layer", "2", "Blink"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "3": { - "encoders": [ - ["/ch/01/mix", "03", ["reset", "0.750000"]], - ["/ch/02/mix", "03", ["reset", "0.750000"]], - ["/ch/03/mix", "03", ["reset", "0.750000"]], - ["/ch/04/mix", "03", ["reset", "0.750000"]], - ["/ch/05/mix", "03", ["reset", "0.750000"]], - ["/ch/06/mix", "03", ["reset", "0.750000"]], - ["/ch/07/mix", "03", ["reset", "0.750000"]], - ["/ch/08/mix", "03", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/01/mix", "03"], - ["mute", "/ch/02/mix", "03"], - ["mute", "/ch/03/mix", "03"], - ["mute", "/ch/04/mix", "03"], - ["mute", "/ch/05/mix", "03"], - ["mute", "/ch/06/mix", "03"], - ["mute", "/ch/07/mix", "03"], - ["mute", "/ch/08/mix", "03"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3prime", "On"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "3prime": { - "encoders": [ - ["/ch/09/mix", "03", ["reset", "0.750000"]], - ["/ch/10/mix", "03", ["reset", "0.750000"]], - ["/ch/11/mix", "03", ["reset", "0.750000"]], - ["/ch/12/mix", "03", ["reset", "0.750000"]], - ["/ch/13/mix", "03", ["reset", "0.750000"]], - ["/ch/14/mix", "03", ["reset", "0.750000"]], - ["/ch/15/mix", "03", ["reset", "0.750000"]], - ["/ch/16/mix", "03", ["reset", "0.750000"]] + ["none", "1"], + ["none", "2"], + ["none", "3"], + ["none", "4"], + ["none", "5"], + ["none", "6"], + ["none", "7"], + ["layer", "Gain", "0", "Off"], + ["layer", "A", "7", "Off"], + ["layer", "B", "0", "On"] ], - "buttons": [ - ["mute", "/ch/09/mix", "03"], - ["mute", "/ch/10/mix", "03"], - ["mute", "/ch/11/mix", "03"], - ["mute", "/ch/12/mix", "03"], - ["mute", "/ch/13/mix", "03"], - ["mute", "/ch/14/mix", "03"], - ["mute", "/ch/15/mix", "03"], - ["mute", "/ch/16/mix", "03"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Blink"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "4": { - "encoders": [ - ["/ch/01/mix", "04", ["reset", "0.750000"]], - ["/ch/02/mix", "04", ["reset", "0.750000"]], - ["/ch/03/mix", "04", ["reset", "0.750000"]], - ["/ch/04/mix", "04", ["reset", "0.750000"]], - ["/ch/05/mix", "04", ["reset", "0.750000"]], - ["/ch/06/mix", "04", ["reset", "0.750000"]], - ["/ch/07/mix", "04", ["reset", "0.750000"]], - ["/ch/08/mix", "04", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/01/mix", "04"], - ["mute", "/ch/02/mix", "04"], - ["mute", "/ch/03/mix", "04"], - ["mute", "/ch/04/mix", "04"], - ["mute", "/ch/05/mix", "04"], - ["mute", "/ch/06/mix", "04"], - ["mute", "/ch/07/mix", "04"], - ["mute", "/ch/08/mix", "04"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4prime", "On"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "4prime": { - "encoders": [ - ["/ch/09/mix", "04", ["reset", "0.750000"]], - ["/ch/10/mix", "04", ["reset", "0.750000"]], - ["/ch/11/mix", "04", ["reset", "0.750000"]], - ["/ch/12/mix", "04", ["reset", "0.750000"]], - ["/ch/13/mix", "04", ["reset", "0.750000"]], - ["/ch/14/mix", "04", ["reset", "0.750000"]], - ["/ch/15/mix", "04", ["reset", "0.750000"]], - ["/ch/16/mix", "04", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/09/mix", "04"], - ["mute", "/ch/10/mix", "04"], - ["mute", "/ch/11/mix", "04"], - ["mute", "/ch/12/mix", "04"], - ["mute", "/ch/13/mix", "04"], - ["mute", "/ch/14/mix", "04"], - ["mute", "/ch/15/mix", "04"], - ["mute", "/ch/16/mix", "04"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Blink"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "5": { - "encoders": [ - ["/ch/01/mix", "05", ["reset", "0.750000"]], - ["/ch/02/mix", "05", ["reset", "0.750000"]], - ["/ch/03/mix", "05", ["reset", "0.750000"]], - ["/ch/04/mix", "05", ["reset", "0.750000"]], - ["/ch/05/mix", "05", ["reset", "0.750000"]], - ["/ch/06/mix", "05", ["reset", "0.750000"]], - ["/ch/07/mix", "05", ["reset", "0.750000"]], - ["/ch/08/mix", "05", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/01/mix", "05"], - ["mute", "/ch/02/mix", "05"], - ["mute", "/ch/03/mix", "05"], - ["mute", "/ch/04/mix", "05"], - ["mute", "/ch/05/mix", "05"], - ["mute", "/ch/06/mix", "05"], - ["mute", "/ch/07/mix", "05"], - ["mute", "/ch/08/mix", "05"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5prime", "On"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "5prime": { - "encoders": [ - ["/ch/09/mix", "05", ["reset", "0.750000"]], - ["/ch/10/mix", "05", ["reset", "0.750000"]], - ["/ch/11/mix", "05", ["reset", "0.750000"]], - ["/ch/12/mix", "05", ["reset", "0.750000"]], - ["/ch/13/mix", "05", ["reset", "0.750000"]], - ["/ch/14/mix", "05", ["reset", "0.750000"]], - ["/ch/15/mix", "05", ["reset", "0.750000"]], - ["/ch/16/mix", "05", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/09/mix", "05"], - ["mute", "/ch/10/mix", "05"], - ["mute", "/ch/11/mix", "05"], - ["mute", "/ch/12/mix", "05"], - ["mute", "/ch/13/mix", "05"], - ["mute", "/ch/14/mix", "05"], - ["mute", "/ch/15/mix", "05"], - ["mute", "/ch/16/mix", "05"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "On"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "6": { - "encoders": [ - ["/ch/01/mix", "06", ["reset", "0.750000"]], - ["/ch/02/mix", "06", ["reset", "0.750000"]], - ["/ch/03/mix", "06", ["reset", "0.750000"]], - ["/ch/04/mix", "06", ["reset", "0.750000"]], - ["/ch/05/mix", "06", ["reset", "0.750000"]], - ["/ch/06/mix", "06", ["reset", "0.750000"]], - ["/ch/07/mix", "06", ["reset", "0.750000"]], - ["/ch/08/mix", "06", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/01/mix", "06"], - ["mute", "/ch/02/mix", "06"], - ["mute", "/ch/03/mix", "06"], - ["mute", "/ch/04/mix", "06"], - ["mute", "/ch/05/mix", "06"], - ["mute", "/ch/06/mix", "06"], - ["mute", "/ch/07/mix", "06"], - ["mute", "/ch/08/mix", "06"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6prime", "On"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "6prime": { - "encoders": [ - ["/ch/09/mix", "06", ["reset", "0.750000"]], - ["/ch/10/mix", "06", ["reset", "0.750000"]], - ["/ch/11/mix", "06", ["reset", "0.750000"]], - ["/ch/12/mix", "06", ["reset", "0.750000"]], - ["/ch/13/mix", "06", ["reset", "0.750000"]], - ["/ch/14/mix", "06", ["reset", "0.750000"]], - ["/ch/15/mix", "06", ["reset", "0.750000"]], - ["/ch/16/mix", "06", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/09/mix", "06"], - ["mute", "/ch/10/mix", "06"], - ["mute", "/ch/11/mix", "06"], - ["mute", "/ch/12/mix", "06"], - ["mute", "/ch/13/mix", "06"], - ["mute", "/ch/14/mix", "06"], - ["mute", "/ch/15/mix", "06"], - ["mute", "/ch/16/mix", "06"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Blink"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "FX1": { - "encoders": [ - ["/ch/01/mix", "07", ["reset", "0.750000"]], - ["/ch/02/mix", "07", ["reset", "0.750000"]], - ["/ch/03/mix", "07", ["reset", "0.750000"]], - ["/ch/04/mix", "07", ["reset", "0.750000"]], - ["/ch/05/mix", "07", ["reset", "0.750000"]], - ["/ch/06/mix", "07", ["reset", "0.750000"]], - ["/ch/07/mix", "07", ["reset", "0.750000"]], - ["/ch/08/mix", "07", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/01/mix", "07"], - ["mute", "/ch/02/mix", "07"], - ["mute", "/ch/03/mix", "07"], - ["mute", "/ch/04/mix", "07"], - ["mute", "/ch/05/mix", "07"], - ["mute", "/ch/06/mix", "07"], - ["mute", "/ch/07/mix", "07"], - ["mute", "/ch/08/mix", "07"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1prime", "On"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] - ] - }, - "FX1prime": { - "encoders": [ - ["/ch/09/mix", "07", ["reset", "0.750000"]], - ["/ch/10/mix", "07", ["reset", "0.750000"]], - ["/ch/11/mix", "07", ["reset", "0.750000"]], - ["/ch/12/mix", "07", ["reset", "0.750000"]], - ["/ch/13/mix", "07", ["reset", "0.750000"]], - ["/ch/14/mix", "07", ["reset", "0.750000"]], - ["/ch/15/mix", "07", ["reset", "0.750000"]], - ["/ch/16/mix", "07", ["reset", "0.750000"]] - ], - "buttons": [ - ["mute", "/ch/09/mix", "07"], - ["mute", "/ch/10/mix", "07"], - ["mute", "/ch/11/mix", "07"], - ["mute", "/ch/12/mix", "07"], - ["mute", "/ch/13/mix", "07"], - ["mute", "/ch/14/mix", "07"], - ["mute", "/ch/15/mix", "07"], - ["mute", "/ch/16/mix", "07"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Blink"], - ["layer", "Gain", "Off"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] + "fader": [ + ["quit", "0"] ] }, "Gain": { @@ -562,17 +123,20 @@ ["none", "Off"], ["none", "Off"], ["clip", "State"], - ["record", "State"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gainprime", "On"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] + ["none", "State"], + ["none", "1"], + ["none", "2"], + ["none", "3"], + ["none", "4"], + ["none", "5"], + ["none", "6"], + ["none", "7"], + ["layer", "Gainprime", "0", "On"], + ["layer", "A", "0", "Off"], + ["layer", "B", "0", "Off"] + ], + "fader": [ + ["quit", "0"] ] }, "Gainprime": { @@ -594,17 +158,20 @@ ["none", "Off"], ["none", "Off"], ["clip", "State"], - ["record", "State"], - ["layer", "1", "Off"], - ["layer", "2", "Off"], - ["layer", "3", "Off"], - ["layer", "4", "Off"], - ["layer", "5", "Off"], - ["layer", "6", "Off"], - ["layer", "FX1", "Off"], - ["layer", "Gain", "Blink"], - ["layer", "A", "Off"], - ["layer", "B", "Off"] + ["none", "State"], + ["none", "1"], + ["none", "2"], + ["none", "3"], + ["none", "4"], + ["none", "5"], + ["none", "6"], + ["none", "7"], + ["layer", "Gain", "0", "Blink"], + ["layer", "A", "0", "Off"], + ["layer", "B", "0", "Off"] + ], + "fader": [ + ["quit", "0"] ] } } diff --git a/simple.json b/simple.json index f6d7d9e..034939d 100644 --- a/simple.json +++ b/simple.json @@ -27,8 +27,11 @@ ["mute", "/ch/14/mix", "/on"], ["mute", "/ch/15/mix", "/on"], ["mute", "/ch/16/mix", "/on"], - ["layer", "A", "On"], - ["layer", "B", "Off"] + ["layer", "A", "0", "On"], + ["layer", "B", "0", "Off"] + ], + "fader": [ + ["none", "0"] ] }, "B": { @@ -59,8 +62,11 @@ ["mute", "/ch/14/mix", "/on"], ["mute", "/ch/15/mix", "/on"], ["mute", "/ch/16/mix", "/on"], - ["layer", "A", "Off"], - ["layer", "B", "On"] + ["layer", "A", "0", "Off"], + ["layer", "B", "0", "On"] + ], + "fader": [ + ["none", "0"] ] } } From 7af3d036e859aef32738e496befce173888c3d10 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Sat, 4 Sep 2021 17:57:54 -0400 Subject: [PATCH 20/25] config file matchind Peter's original functionality --- peterdikant.json | 282 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 peterdikant.json diff --git a/peterdikant.json b/peterdikant.json new file mode 100644 index 0000000..22eec4c --- /dev/null +++ b/peterdikant.json @@ -0,0 +1,282 @@ +{ + "LA0": { + "encoders": [ + ["/ch/01/mix", "fader", ["mute", "/config/mute/1", "0"]], + ["/ch/02/mix", "fader", ["mute", "/config/mute/2", "0"]], + ["/ch/03/mix", "fader", ["mute", "/config/mute/3", "0"]], + ["/ch/04/mix", "fader", ["mute", "/config/mute/4", "0"]], + ["/ch/05/mix", "fader", ["none"]], + ["/ch/06/mix", "fader", ["none"]], + ["/ch/07/mix", "fader", ["none"]], + ["/ch/08/mix", "fader", ["subprocess", "mpc", ["play", "pause"]]] + ], + "buttons": [ + ["mute", "/ch/01/mix", "/on"], + ["mute", "/ch/02/mix", "/on"], + ["mute", "/ch/03/mix", "/on"], + ["mute", "/ch/04/mix", "/on"], + ["mute", "/ch/05/mix", "/on"], + ["mute", "/ch/06/mix", "/on"], + ["mute", "/ch/07/mix", "/on"], + ["mute", "/ch/08/mix", "/on"], + ["mute", "/config/mute/4", ""], + ["tap", "tap"], + ["none", "Off"], + ["layer", "LA0", "0", "On"], + ["layer", "LA1", "0", "Off"], + ["layer", "LA2", "0", "Off"], + ["layer", "LA3", "0", "Off"], + ["layer", "LA4", "0", "Off"], + ["layer", "LA0", "0", "On"], + ["layer", "LB0", "10", "Off"] + ], + "fader": [ + ["/lr/mix", "0"] + ] + }, + "LA1": { + "encoders": [ + ["/ch/09/mix", "fader", ["mute", "/config/mute/1", "0"]], + ["/ch/10/mix", "fader", ["mute", "/config/mute/2", "0"]], + ["/ch/11/mix", "fader", ["mute", "/config/mute/3", "0"]], + ["/ch/12/mix", "fader", ["mute", "/config/mute/4", "0"]], + ["/ch/13/mix", "fader", ["none"]], + ["/ch/14/mix", "fader", ["none"]], + ["/ch/15/mix", "fader", ["none"]], + ["/ch/16/mix", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ], + "buttons": [ + ["mute", "/ch/09/mix", "/on"], + ["mute", "/ch/10/mix", "/on"], + ["mute", "/ch/11/mix", "/on"], + ["mute", "/ch/12/mix", "/on"], + ["mute", "/ch/13/mix", "/on"], + ["mute", "/ch/14/mix", "/on"], + ["mute", "/ch/15/mix", "/on"], + ["mute", "/ch/16/mix", "/on"], + ["mute", "/config/mute/4", ""], + ["tap", "tap"], + ["none", "Off"], + ["layer", "LA0", "0", "Off"], + ["layer", "LA1", "0", "On"], + ["layer", "LA2", "0", "Off"], + ["layer", "LA3", "0", "Off"], + ["layer", "LA4", "0", "Off"], + ["layer", "LA1", "0", "On"], + ["layer", "LB1", "10", "Off"] + ], + "fader": [ + ["/lr/mix", "0"] + ] + }, + "LA2": { + "encoders": [ + ["/rtn/aux/mix", "fader", ["mute", "/config/mute/1", "0"]], + ["none", "", ["mute", "/config/mute/2", "0"]], + ["none", "", ["mute", "/config/mute/3", "0"]], + ["none", "", ["mute", "/config/mute/4", "0"]], + ["/dca/1", "fader", ["none"]], + ["/dca/2", "fader", ["none"]], + ["/dca/3", "fader", ["none"]], + ["/dca/4", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ], + "buttons": [ + ["mute", "/rtn/aux/mix", "/on"], + ["none", "Off"], + ["none", "Off"], + ["none", "Off"], + ["mute", "/dca/1", "/on"], + ["mute", "/dca/2", "/on"], + ["mute", "/dca/3", "/on"], + ["mute", "/dca/4", "/on"], + ["mute", "/config/mute/4", ""], + ["tap", "tap"], + ["none", "Off"], + ["layer", "LA0", "0", "Off"], + ["layer", "LA1", "0", "Off"], + ["layer", "LA2", "0", "On"], + ["layer", "LA3", "0", "Off"], + ["layer", "LA4", "0", "Off"], + ["layer", "LA2", "0", "On"], + ["layer", "LB2", "10", "Off"] + ], + "fader": [ + ["/lr/mix", "0"] + ] + }, + "LA3": { + "encoders": [ + ["/fxsend/1/mix", "fader", ["mute", "/config/mute/1", "0"]], + ["/fxsend/2/mix", "fader", ["mute", "/config/mute/2", "0"]], + ["/fxsend/3/mix", "fader", ["mute", "/config/mute/3", "0"]], + ["/fxsend/4/mix", "fader", ["mute", "/config/mute/4", "0"]], + ["/rtn/1/mix", "fader", ["none"]], + ["/rtn/2/mix", "fader", ["none"]], + ["/rtn/3/mix", "fader", ["none"]], + ["/rtn/4/mix", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ], + "buttons": [ + ["mute", "/fxsend/1/mix", "/on"], + ["mute", "/fxsend/2/mix", "/on"], + ["mute", "/fxsend/3/mix", "/on"], + ["mute", "/fxsend/4/mix", "/on"], + ["mute", "/rtn/1/mix", "/on"], + ["mute", "/rtn/2/mix", "/on"], + ["mute", "/rtn/3/mix", "/on"], + ["mute", "/rtn/4/mix", "/on"], + ["mute", "/config/mute/4", ""], + ["tap", "tap"], + ["none", "Off"], + ["layer", "LA0", "0", "Off"], + ["layer", "LA1", "0", "Off"], + ["layer", "LA2", "0", "Off"], + ["layer", "LA3", "0", "On"], + ["layer", "LA4", "0", "Off"], + ["layer", "LA3", "0", "On"], + ["layer", "LB2", "10", "Off"] + ], + "fader": [ + ["/lr/mix", "0"] + ] + }, + "LA4": { + "encoders": [ + ["/bus/1/mix", "fader", ["mute", "/config/mute/1", "0"]], + ["/bus/2/mix", "fader", ["mute", "/config/mute/2", "0"]], + ["/bus/3/mix", "fader", ["mute", "/config/mute/3", "0"]], + ["/bus/4/mix", "fader", ["mute", "/config/mute/4", "0"]], + ["/bus/5/mix", "fader", ["none"]], + ["/bus/6/mix", "fader", ["none"]], + ["none", "", [""]], + ["none", "", [["subprocess", "mpc", ["play", "pause"]]]] + ], + "buttons": [ + ["mute", "/bus/1/mix", "/on"], + ["mute", "/bus/2/mix", "/on"], + ["mute", "/bus/3/mix", "/on"], + ["mute", "/bus/4/mix", "/on"], + ["mute", "/bus/5/mix", "/on"], + ["mute", "/bus/6/mix", "/on"], + ["none", "Off"], + ["none", "Off"], + ["mute", "/config/mute/4", ""], + ["tap", "tap"], + ["none", "Off"], + ["layer", "LA0", "0", "Off"], + ["layer", "LA1", "0", "Off"], + ["layer", "LA2", "0", "Off"], + ["layer", "LA3", "0", "Off"], + ["layer", "LA4", "0", "On"], + ["layer", "LA4", "0", "On"], + ["layer", "LB2", "10", "Off"] + ], + "fader": [ + ["/lr/mix", "0"] + ] + }, + "LB0": { + "encoders": [ + ["/ch/01/mix", "fader", ["mute", "/config/mute/1", "0"]], + ["/ch/02/mix", "fader", ["mute", "/config/mute/2", "0"]], + ["/ch/03/mix", "fader", ["mute", "/config/mute/3", "0"]], + ["/ch/04/mix", "fader", ["mute", "/config/mute/4", "0"]], + ["/ch/05/mix", "fader", ["none"]], + ["/ch/06/mix", "fader", ["none"]], + ["/ch/07/mix", "fader", ["none"]], + ["/ch/08/mix", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ], + "buttons": [ + ["send", "1", "send"], + ["send", "2", "send"], + ["send", "3", "send"], + ["send", "4", "send"], + ["send", "5", "send"], + ["send", "6", "send"], + ["send", "7", "send"], + ["send", "8", "send"], + ["mute", "/config/mute/4", ""], + ["tap", "tap"], + ["none", "Off"], + ["layer", "LB0", "10", "On"], + ["layer", "LB1", "10", "Off"], + ["layer", "LB2", "10", "Off"], + ["send", "9", "send"], + ["send", "10", "send"], + ["layer", "LA0", "0", "Off"], + ["layer", "LB0", "10", "On"] + ], + "fader": [ + ["/lr/mix", "0"] + ] + }, + "LB1": { + "encoders": [ + ["/ch/09/mix", "fader", ["mute", "/config/mute/1", "0"]], + ["/ch/10/mix", "fader", ["mute", "/config/mute/2", "0"]], + ["/ch/11/mix", "fader", ["mute", "/config/mute/3", "0"]], + ["/ch/12/mix", "fader", ["mute", "/config/mute/4", "0"]], + ["/ch/13/mix", "fader", ["none"]], + ["/ch/14/mix", "fader", ["none"]], + ["/ch/15/mix", "fader", ["none"]], + ["/ch/16/mix", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ], + "buttons": [ + ["send", "1", "send"], + ["send", "2", "send"], + ["send", "3", "send"], + ["send", "4", "send"], + ["send", "5", "send"], + ["send", "6", "send"], + ["send", "7", "send"], + ["send", "8", "send"], + ["mute", "/config/mute/4", ""], + ["tap", "tap"], + ["none", "Off"], + ["layer", "LB0", "10", "Off"], + ["layer", "LB1", "10", "On"], + ["layer", "LB2", "10", "Off"], + ["send", "9", "send"], + ["send", "10", "send"], + ["layer", "LA1", "0", "Off"], + ["layer", "LB1", "10", "On"] + ], + "fader": [ + ["/lr/mix", "0"] + ] + }, + "LB2": { + "encoders": [ + ["/rtn/aux/mix", "fader", ["mute", "/config/mute/1", "0"]], + ["none", "", ["mute", "/config/mute/2", "0"]], + ["none", "", ["mute", "/config/mute/3", "0"]], + ["none", "", ["mute", "/config/mute/4", "0"]], + ["/dca/1", "fader", ["none"]], + ["/dca/2", "fader", ["none"]], + ["/dca/3", "fader", ["none"]], + ["/dca/4", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ], + "buttons": [ + ["send", "1", "send"], + ["send", "2", "send"], + ["send", "3", "send"], + ["send", "4", "send"], + ["send", "5", "send"], + ["send", "6", "send"], + ["send", "7", "send"], + ["send", "8", "send"], + ["mute", "/config/mute/4", ""], + ["tap", "tap"], + ["none", "Off"], + ["layer", "LB0", "10", "Off"], + ["layer", "LB1", "10", "Off"], + ["layer", "LB2", "10", "On"], + ["send", "9", "send"], + ["send", "10", "send"], + ["layer", "LA2", "0", "Off"], + ["layer", "LB2", "10", "On"] + ], + "fader": [ + ["/lr/mix", "0"] + ] + } +} From 5f582b78b7718da4cd0bacf56d082b0765c5e2f9 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Sun, 5 Sep 2021 12:17:47 -0400 Subject: [PATCH 21/25] updated copyright information --- LICENSE | 3 ++- lib/midicontroller.py | 7 ++++++- lib/mixerstate.py | 10 ++++++---- lib/xair.py | 5 +++++ xair-remote.py | 6 ++++++ 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/LICENSE b/LICENSE index 0439191..c1f387e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License -Copyright (c) 2018 Peter Dikant +Copyright (c) 2018,2021 Peter Dikant +Additions Copyright (c) 2021 Ross Dickson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/lib/midicontroller.py b/lib/midicontroller.py index 6319217..aa65603 100644 --- a/lib/midicontroller.py +++ b/lib/midicontroller.py @@ -1,4 +1,9 @@ -"See class docstring" +"See MidiController docstring" +# part of xair-remote.py +# Copyright (c) 2018, 2021 Peter Dikant +# Additions Copyright (c) 2021 Ross Dickson +# Some rights reserved. See LICENSE. + import threading import time import os diff --git a/lib/mixerstate.py b/lib/mixerstate.py index 9660515..5cd4921 100644 --- a/lib/mixerstate.py +++ b/lib/mixerstate.py @@ -1,6 +1,8 @@ -""" -This module holds the mixer state of the X-Air device -""" +"This module holds the mixer state of the X-Air device" +# part of xair-remote.py +# Copyright (c) 2018, 2021 Peter Dikant +# Additions Copyright (c) 2021 Ross Dickson +# Some rights reserved. See LICENSE. import time import subprocess @@ -311,7 +313,7 @@ def __init__(self, args) -> None: self.levels = args.levels # initialize internal data structures - config_json = "simple.json" + config_json = "peterdikant.json" if args.config_file is not None: config_json = args.config_file[0] with open(config_json) as config_file: diff --git a/lib/xair.py b/lib/xair.py index 671e063..c3f3864 100644 --- a/lib/xair.py +++ b/lib/xair.py @@ -1,4 +1,9 @@ "This modules managed communications with the XAir mixer" +# part of xair-remote.py +# Copyright (c) 2018, 2021 Peter Dikant +# Additions Copyright (c) 2021 Ross Dickson +# Some rights reserved. See LICENSE. + import time import threading import socket diff --git a/xair-remote.py b/xair-remote.py index 652c93a..66077af 100755 --- a/xair-remote.py +++ b/xair-remote.py @@ -1,5 +1,11 @@ "Starts an xAir remote, see main help string for more" #!/usr/bin/env python3 + +# part of xair-remote.py +# Copyright (c) 2018, 2021 Peter Dikant +# Additions Copyright (c) 2021 Ross Dickson +# Some rights reserved. See LICENSE. + import argparse from lib.mixerstate import MixerState From 39f3fad6912ae6618aca7e081e60b13539b44162 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Sun, 5 Sep 2021 15:18:05 -0400 Subject: [PATCH 22/25] complete rewrite of documentation to match new code format --- README.md | 278 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 200 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 7cf4d82..1b9fb88 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ # xair-remote -With thanks peterdikant who created the [xair-remote -project](https://github.com/peterdikant/xair-remote). This version is a seperate -fork as there is not obvious way to meet both my need and Peter's at the same time. - Use a Behringer X-Touch Mini MIDI controller to remote control a Behringer X-Air digital mixer via the OSC network protocol. Multiple layers allow you to control volume, mute and bus sends for every input and output channel with only 8 @@ -14,7 +10,7 @@ X-Touch controller. You need Python 3.5 or later. Please make sure to install required libraries: - $ sudo pip3 install -r requirements.txt + $ python3 -m pip install -r requirements.txt ## Update @@ -29,35 +25,35 @@ lit after connecting the device by pressing the MC button during startup. To get help run: - $ python3 xair-remote.py -h + $ python3 xair-remote.py -h The app will automatically detect both your X-Touch controller and the XR18. So connect the controller and make sure the XR18 is reachable on your network. Now you can start the app: - $ python3 xair-remote.py + $ python3 xair-remote.py If the app can not find your controller or connect to the X-Air mixer, it will terminate with an error message explaining the problem. If everything started up successfully, the X-Touch mini will reflect the current mixer state and the console output will look like this: - Found XR18 with firmware 1.17 on IP 192.168.178.31 - Using MIDI input: X-TOUCH MINI - Using MIDI output: X-TOUCH MINI - Successfully connected to XR18 with firmware 1.17 at 192.168.178.31. + Found XR18 with firmware 1.17 on IP 192.168.178.31 + Using MIDI input: X-TOUCH MINI + Using MIDI output: X-TOUCH MINI + Successfully connected to XR18 with firmware 1.17 at 192.168.178.31. Startup will fail if the X-Touch controller is not connected or the XR18 could not be located on the network. In the latter case you could try to specify the IP address of the mixer on the command line: - $ python3 xair-remote.py 192.168.178.37 + $ python3 xair-remote.py 192.168.178.37 The app can monitor the X-Touch connection and exit if the controller is disconnected. This functionality is enabled by setting the parameter `-m`: - $ python3 xair-remote.py -m - + $ python3 xair-remote.py -m + Note: Monitoring does not work on all platforms. Linux works fine while MacOS does not detect disconnects. @@ -78,70 +74,196 @@ print them in 100% size. ### General Operation -The X-Touch is configured as a set of 8 channel strips that can control an 8 -channel bank of the mixer. Each channel strip consists of a rotary encoder and -button, `E1` and `B1` through `E8` and `B8`. The encoder sets the level or gain -of the channel and the ring of lights around the encoder indicate the level. -Pressing the encoder returns the channel to unity gain or a useful default where -one can be found. The button mutes the channel and lights to indicate the -channel is muted. The lower row of buttons, `B09` through `B16` and the two -layer buttons `LA` and `LB` control bank selection. The Fader `F1` is not used. - -Pressing a bank select button selects the first layer of the bank and causes the -bank button to light. Pressing the bank button again selects the second layer -and causes the button to blink. Pressing a third time returns to the first -layer. Selecting another bank always selects the first layer even if the -previous bank was on the second layer. The banks are configured as follows: - -Button | Bank Function ------- | ------------- -`LA` | Channel Send to Main LR Bus -`LB` | Bus Output and USB AUX In Levels -`B09` | Channel Send to AUX 1 -`B10` | Channel Send to AUX 2 -`B11` | Channel Send to AUX 3 -`B12` | Channel Send to AUX 4 -`B13` | Channel Send to AUX 5 -`B14` | Channel Send to AUX 6 -`B15` | Channel Send to Aux 7/Fx 1 -`B16` | Mic Preamps - -### Bank A - -Bank A is used to control the level of channels in the main mix and has two -layers. In the first layer the encoders and buttons map to channels 1 through 8, -and the second layer they map to channels 9 through 16. Pressing the encoders -returns the channel to unity gain, the top row of buttons (B01 through B08) mute -the channel. - -### Bank B - -Bank B is used to control the output levels and USB AUX in level, it has one -layer. The encoders and mute buttons are as per the table: - -Channel | Encoders (Volume) & Buttons (Mute) | Encoder Push Value -------- | ---------------------------------- | ------------------ -1-6 | Aux 1-6 Output Levels | Return to -20 db -7 | USB/Aux Input Gain | Return to 0 db -8 | Main L/R Output Level | Return to -20 db - -The Push function of encoders return to -20 db as most of the monitor and PA -speakers I use get over distort at 0db output. - -### Banks 1 through 6 - -Banks 1 though 6 control the AUX Bus sends for each channel and have two layers. -Similar to Bank A layer 1 maps to channels 1 through 8 and layer 2 maps channels -9 through 16. As the X-Air does not have a mute function on sends, the mute is -implemented by setting the send value to -∞ while storing the current send -value. If you restart the application while a send is muted the stored send -level will be lost. Pressing the encoders returns the send to unity gain. These -banks have no effect for channels set to "Sub Group" send mode. - -### Bank 7 - -Bank 7 works the same as Banks 1 through 6 though it sends the channels to AUX -Bus 7 which is also known as FX 1. +The general operation is fully defined in a config file in JSON format. Three +examples config files are provided and described below. The default is to use peterdikant.jason as it matches the opertion of the original project. + +### Config File Format + +The config file uses JSON for easy parsing and reasonably easy editing. The file +is structured as a sequence of dictonaries each representing a "layer" that +defines all buttons, encoders, and the fader of the X-Touch Mini. Each layer +contains three dictonaries named 'encoders', 'buttons', and 'fader' that define +the respective elements of the X-Touch. Each control specifies the "channel" +that control effects and the command sent to the channel. The "channel" is +technically defined as an OSC address supported by the X-Air protocol that takes +an argument. The most common case in the X-Air command set is an address that is +a common prefix of a set of addresses defining how an audio source is to be +processed. Many aspects of the X-Air follow this pattern even though they are +aspects of the mixer that would not be considered a channel in an analog mixer. +In a large number of cases the channel not only has a level in the main mix but +also a level on each of the auxiliary bus or effects procesor the source can +'send' to. + +The **encoders** dictonary is a list of 8 lists each specifying the operation of +one encoder from `E1` to `E8`. These lists contain two elements: + +* channel, one of: + * an X-AIR OSC address + * 'none' - if turning the encoder is to have no effect +* a list defining the function of pressing the encoder as a button supporting + three commands: + * 'none' - if presssing the encoder has no effect + * 'mute' followed by a 'channel' and 'send' to mute + * 'reset' followed by a 'value' to set the encoder channel to + * 'subprocess' - as the first element of a list defining a set of external + commands to cycle through formatted as ['subprocess', 'external call', + ['command', 'command', ...]] + +The **buttons** dictonary is a list of 18 lists each specifying the operation of +one button from `B01` to `B16` followed by `LA` and `LB`. These lists contain a +variable number of elements based on the command: + +* 'none' followed by a 'comment' which defines if the button LED is to be on or off +* 'mute' which is followed by the 'channel' to mute, the light matches the mute state +* 'send' followed by number of the send with 0 defined as the main mix for this layer +* 'quit' followed by a 'comment' which defines if the button LED is on or off +* 'layer' followed by the destination 'layer' the max 'send level' and the LED status +* 'clip' followed by the LED state +* 'tap' followed by the LED state + +The **fader** dictonary is a list of one list specifying the operation of the +fader. The list is formatted the same as an encoder without the press fucntion +or 'quit' if setting the fader to 100% quits. + +## Example Configuraitons + +### peterdikant.json + +This file configures the X-Touch to operate as defined by Peter Dikant to +support live sound mixing. The description is his original defintion of the +xair-remote and thus does not match the organization of hte config file. + +### Layer A + +Layer A is used to control the main mix. + +This layer is active when the button `LA` is selected. The main volume is always mapped to the Fader `F1`. The lower button row is assigned globally with the following functions: + +Button | Function +------ | ------------------------------------------------ +B09 | Mute Group 4 (this is always my FX mute group) +B10 | Tap Tempo (will set tempo for all delay effects) +B11 | Unassigned +B12 | Fader Bank 1 +B13 | Fader Bank 2 +B14 | Fader Bank 3 +B15 | Fader Bank 4 +B16 | Fader Bank 5 + +The upper row buttons and the top encoders are used to control volume and mute for different channels depending on the selected fader bank: + +Bank | Encoders (Volume) & Buttons (Mute) +---- | ---------------------------------- +1 | Channels 1 - 8 +2 | Channels 9 - 16 +3 | Aux, 3 unassigned, DCA 1 - 4 +4 | FX Sends 1 - 4, FX Returns 1 - 4 +5 | Bus 1 - 6, 2 unassigned + +The Push function of encoders `E1` to `E4` can be used to toggle mute groups 1 to 4 in all fader banks. I always use mute group 4 for FX mute, therefore I have placed this mute group on button `B09`. This way I also get visual feedback on the status of my FX returns. + +### Layer B + +Layer B can control your bus sends. + +This layer is active when the button `LB` is selected. The main volume is always mapped to the Fader `F1`. + +In this layer all buttons have a static assignment: + +Button | Function +------ | ------------------------------------------------ +B01 | Bus 1 +B02 | Bus 2 +B03 | Bus 3 +B04 | Bus 4 +B05 | Bus 5 +B06 | Bus 6 +B07 | FX 1 +B08 | FX 2 +B09 | Mute Group 4 (this is always my FX mute group) +B10 | Tap Tempo (will set tempo for all delay effects) +B11 | Unassigned +B12 | Fader Bank 1 +B13 | Fader Bank 2 +B14 | Fader Bank 3 +B15 | FX 3 +B16 | FX 4 + +The top encoders control the volume for different channels depending on the selected fader bank and bus/FX: + +Layer | Encoders (Volume) +----- | ---------------------------------- +1 | Channels 1 - 8 +2 | Channels 9 - 16 +3 | Aux, 7 unassigned + +The Push function of encoders `E1` to `E4` can be used to toggle mute groups 1 to 4 in all fader banks. +### rossdickson.json + +This file configures the X-Touch to operate as defined by Ross Dickson to +support live sound mixing. The basic concept of the design is to concieve of the +encoders `E1` through `E8` and top row of buttons `B01` through `B08` as a set +of channel strips where the each encoder adjusts a channel level on turn, resets +the level on press, and the button mutes the channel. The rings around the +encoders and lights of the mute buttons show the values of the selected +channels. The lower row of buttons and the layer buttons control what channels +the channel strips control. + +The Fader `F1` is used used as a master quit signal when set to the top. +### Layer A and A' + +Pressing `LA` configures the channel strips to control the first 8 channels and +is indicated by a lighted button. The reset value is the equivalent of the 0db +level on a fader. Pressing `LA` again switches to the second 8 channels and is +indicated by a blinking button. Further presses toggle between the two. + +Buttons, `B09` through `B15`, the first 7 buttons of the lower row define which +'send' bus is being controlled by the channel strips. When no lights are lit the +encoders control the main LR mix bus. Pressing a send button switches to the +corresponding aux bus and the first FX processor. Pressing the same send agains +reverts to the main LR Mix. + +### Layer B + +Pressing `LB` configures the channel strips to control the output levels on the +6 AUX ports, the level of the USB return, and the main LR output level. The +reset level is -20db for outputs and 0db for the USB return. -20db is used as it +is about 6db lower than max level for somehting assuming consumer audio line +level rather than pro audio line levels and thus is generally safe no matter +what sort of amp or speakers you might be driving. + +Buttons, `B09` through `B15`, are unused as there are no "sends" for outputs. + +### Layer `Gain` and `Gain'` + +Pressing `B16` configured the channel strips to control the gain levels of the +mic preamps on the first 8 channels and is indicated by a lighted button. +Similar to `LA` pressing `E16` again toggles to the second 8 mic preamps. As +preamps have neither mute nor sends most of the buttons are unused though a few +are used to control additional functions, `B03` and `B04` blink and are used to +quit while `B07` also blinks and is used to enable the clipping protection +function. When enabled, any channel that has more than 3 sequential samples +within 3db of max it will lower the gain of the mic preamp by on step out of a +16 bit range. + +Somewhat graphically this confiugres the xtouch as: + + + +1/9 | 2/10 | ... | 5/14 | 7/15 | 8/16 | | +--- | ---- | --- | ---- | ---- | ---- | --- | +Mute | Mute | ... | Mute | Mute | Mute | Mix Channels | +AUX 1| AUX 2| ... | AUX 6| Fx 1 | Pre | Outputs | + +### simple.json + +This file configures the X-Touch to operate as a simple set of levels and mutes +for the X-Air to support a "dumb mixer" install. The basic concept of the design +is to concieve of the encoders `E1` through `E8` as levels and the buttons `B01` +through `B16` as a mutes for the 16 channels of the X-Air. The buttons `LA` and +`LB` switch the levels to match the upper or lower row of buttons. The rings +around the encoders show the levels whil prssing the encoders returns the level +to 0db to quickly reset the mixer. The main fader `F1` is not used. ## License From 75287bb812ca27f0bfed7cf8f2dc1d25ecfeb0e4 Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Sun, 5 Sep 2021 15:18:19 -0400 Subject: [PATCH 23/25] updated file name to match documetation --- monitors.json => rossdickson.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename monitors.json => rossdickson.json (100%) diff --git a/monitors.json b/rossdickson.json similarity index 100% rename from monitors.json rename to rossdickson.json From a0cd044fa177d86393e50284be77ce4fc156825e Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Sun, 5 Sep 2021 15:18:58 -0400 Subject: [PATCH 24/25] refined code to removed unused json configuation discovered while documenting file format --- lib/mixerstate.py | 30 ++--- peterdikant.json | 274 +++++++++++++++++++++++----------------------- rossdickson.json | 164 +++++++++++++-------------- simple.json | 96 ++++++++-------- 4 files changed, 283 insertions(+), 281 deletions(-) diff --git a/lib/mixerstate.py b/lib/mixerstate.py index 5cd4921..994e0c4 100644 --- a/lib/mixerstate.py +++ b/lib/mixerstate.py @@ -111,7 +111,7 @@ def toggle(self): # gain, channel # level, channel, bus, [button] # There are 18 buttons per section to types: lengths -button_def = {'quit': 3, 'none': 2, 'layer': 4, 'clip': 2, 'mute': 3, 'tap': 2, "send": 3} +button_def = {'quit': 2, 'none': 2, 'layer': 4, 'clip': 2, 'mute': 2, 'tap': 2, "send": 2} button_types = set(button_def.keys()) class Layer: @@ -137,16 +137,16 @@ def __init__(self, layer_name, config_layer, channels, layer_names, proc_list) - if len(self.encoders) != 8: print("Layer %s does not contain 8 'encoder' definitions." % layer_name) for encoder in self.encoders: - if len(encoder) != 3: - print("Error: Encoder %s of layer %s does not contain 3 elements, exiting." % \ + if len(encoder) != 2: + print("Error: Encoder %s of layer %s does not contain 2 elements, exiting." % \ (encoder[0], layer_name)) exit() if encoder[0] != "none" and encoder[0] not in channels.keys(): channels[encoder[0]] = Channel(encoder[0]) - if encoder[2][0] == "mute" and encoder[2][1] not in channels.keys(): - channels[encoder[2][1]] = Channel(encoder[2][1]) - elif encoder[2][0] == "subprocess" and encoder[2][1] not in proc_list.keys(): - proc_list[encoder[2][1]] = SubProc(encoder[2][0], encoder[2][1], encoder[2][2]) + if encoder[1][0] == "mute" and encoder[1][1] not in channels.keys(): + channels[encoder[1][1]] = Channel(encoder[1][1]) + elif encoder[1][0] == "subprocess" and encoder[1][1] not in proc_list.keys(): + proc_list[encoder[1][1]] = SubProc(encoder[1][0], encoder[1][1], encoder[1][2]) # Process the buttons self.buttons = config_layer["buttons"] @@ -189,13 +189,13 @@ def encoder_turn(self, number, value): def encoder_press(self, number): encoder = self.encoders[number] - if encoder[2][0] == "reset": - return(self.channels[encoder[0]].set_level(self.active_bus, float(encoder[2][1]))) - elif encoder[2][0] == "mute": - (address, param, LED) = self.channels[encoder[2][1]].toggle_mute(int(encoder[2][2])) + if encoder[1][0] == "reset": + return(self.channels[encoder[0]].set_level(self.active_bus, float(encoder[1][1]))) + elif encoder[1][0] == "mute": + (address, param, LED) = self.channels[encoder[1][1]].toggle_mute(int(encoder[1][2])) return(address, param, self.channels[encoder[0]].get_level(self.active_bus)) - elif encoder[2][0] == "subprocess": - self.proc_list[encoder[2][1]].toggle() + elif encoder[1][0] == "subprocess": + self.proc_list[encoder[1][1]].toggle() return(None, None, self.channels[encoder[0]].get_level(self.active_bus)) if encoder[0] == "none": return(None, None, 0.0) @@ -236,6 +236,8 @@ def button_state(self, number): def fader_move(self, value): if self.faders[0][0] == "quit": return("quit", "none", "none") + if self.faders[0][0] == "none": + return("none", "none", "none") else: return(self.channels[self.faders[0][0]].set_level(int(self.faders[0][1]), value)) @@ -454,7 +456,7 @@ def fader_move(self, msg): if value > .98: self.shutdown() exit() - elif address != None: + elif address != 'none': self.xair_client.send(address=address, param=param) def received_osc(self, addr, value): diff --git a/peterdikant.json b/peterdikant.json index 22eec4c..c27a641 100644 --- a/peterdikant.json +++ b/peterdikant.json @@ -1,25 +1,25 @@ { "LA0": { "encoders": [ - ["/ch/01/mix", "fader", ["mute", "/config/mute/1", "0"]], - ["/ch/02/mix", "fader", ["mute", "/config/mute/2", "0"]], - ["/ch/03/mix", "fader", ["mute", "/config/mute/3", "0"]], - ["/ch/04/mix", "fader", ["mute", "/config/mute/4", "0"]], - ["/ch/05/mix", "fader", ["none"]], - ["/ch/06/mix", "fader", ["none"]], - ["/ch/07/mix", "fader", ["none"]], - ["/ch/08/mix", "fader", ["subprocess", "mpc", ["play", "pause"]]] + ["/ch/01/mix", ["mute", "/config/mute/1", "0"]], + ["/ch/02/mix", ["mute", "/config/mute/2", "0"]], + ["/ch/03/mix", ["mute", "/config/mute/3", "0"]], + ["/ch/04/mix", ["mute", "/config/mute/4", "0"]], + ["/ch/05/mix", ["none"]], + ["/ch/06/mix", ["none"]], + ["/ch/07/mix", ["none"]], + ["/ch/08/mix", ["subprocess", "mpc", ["play", "pause"]]] ], "buttons": [ - ["mute", "/ch/01/mix", "/on"], - ["mute", "/ch/02/mix", "/on"], - ["mute", "/ch/03/mix", "/on"], - ["mute", "/ch/04/mix", "/on"], - ["mute", "/ch/05/mix", "/on"], - ["mute", "/ch/06/mix", "/on"], - ["mute", "/ch/07/mix", "/on"], - ["mute", "/ch/08/mix", "/on"], - ["mute", "/config/mute/4", ""], + ["mute", "/ch/01/mix"], + ["mute", "/ch/02/mix"], + ["mute", "/ch/03/mix"], + ["mute", "/ch/04/mix"], + ["mute", "/ch/05/mix"], + ["mute", "/ch/06/mix"], + ["mute", "/ch/07/mix"], + ["mute", "/ch/08/mix"], + ["mute", "/config/mute/4"], ["tap", "tap"], ["none", "Off"], ["layer", "LA0", "0", "On"], @@ -36,25 +36,25 @@ }, "LA1": { "encoders": [ - ["/ch/09/mix", "fader", ["mute", "/config/mute/1", "0"]], - ["/ch/10/mix", "fader", ["mute", "/config/mute/2", "0"]], - ["/ch/11/mix", "fader", ["mute", "/config/mute/3", "0"]], - ["/ch/12/mix", "fader", ["mute", "/config/mute/4", "0"]], - ["/ch/13/mix", "fader", ["none"]], - ["/ch/14/mix", "fader", ["none"]], - ["/ch/15/mix", "fader", ["none"]], - ["/ch/16/mix", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ["/ch/09/mix", ["mute", "/config/mute/1", "0"]], + ["/ch/10/mix", ["mute", "/config/mute/2", "0"]], + ["/ch/11/mix", ["mute", "/config/mute/3", "0"]], + ["/ch/12/mix", ["mute", "/config/mute/4", "0"]], + ["/ch/13/mix", ["none"]], + ["/ch/14/mix", ["none"]], + ["/ch/15/mix", ["none"]], + ["/ch/16/mix", [["subprocess", "mpc", ["play", "pause"]]]] ], "buttons": [ - ["mute", "/ch/09/mix", "/on"], - ["mute", "/ch/10/mix", "/on"], - ["mute", "/ch/11/mix", "/on"], - ["mute", "/ch/12/mix", "/on"], - ["mute", "/ch/13/mix", "/on"], - ["mute", "/ch/14/mix", "/on"], - ["mute", "/ch/15/mix", "/on"], - ["mute", "/ch/16/mix", "/on"], - ["mute", "/config/mute/4", ""], + ["mute", "/ch/09/mix"], + ["mute", "/ch/10/mix"], + ["mute", "/ch/11/mix"], + ["mute", "/ch/12/mix"], + ["mute", "/ch/13/mix"], + ["mute", "/ch/14/mix"], + ["mute", "/ch/15/mix"], + ["mute", "/ch/16/mix"], + ["mute", "/config/mute/4"], ["tap", "tap"], ["none", "Off"], ["layer", "LA0", "0", "Off"], @@ -71,25 +71,25 @@ }, "LA2": { "encoders": [ - ["/rtn/aux/mix", "fader", ["mute", "/config/mute/1", "0"]], - ["none", "", ["mute", "/config/mute/2", "0"]], - ["none", "", ["mute", "/config/mute/3", "0"]], - ["none", "", ["mute", "/config/mute/4", "0"]], - ["/dca/1", "fader", ["none"]], - ["/dca/2", "fader", ["none"]], - ["/dca/3", "fader", ["none"]], - ["/dca/4", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ["/rtn/aux/mix", ["mute", "/config/mute/1", "0"]], + ["none", ["mute", "/config/mute/2", "0"]], + ["none", ["mute", "/config/mute/3", "0"]], + ["none", ["mute", "/config/mute/4", "0"]], + ["/dca/1", ["none"]], + ["/dca/2", ["none"]], + ["/dca/3", ["none"]], + ["/dca/4", [["subprocess", "mpc", ["play", "pause"]]]] ], "buttons": [ - ["mute", "/rtn/aux/mix", "/on"], + ["mute", "/rtn/aux/mix"], ["none", "Off"], ["none", "Off"], ["none", "Off"], - ["mute", "/dca/1", "/on"], - ["mute", "/dca/2", "/on"], - ["mute", "/dca/3", "/on"], - ["mute", "/dca/4", "/on"], - ["mute", "/config/mute/4", ""], + ["mute", "/dca/1"], + ["mute", "/dca/2"], + ["mute", "/dca/3"], + ["mute", "/dca/4"], + ["mute", "/config/mute/4"], ["tap", "tap"], ["none", "Off"], ["layer", "LA0", "0", "Off"], @@ -106,25 +106,25 @@ }, "LA3": { "encoders": [ - ["/fxsend/1/mix", "fader", ["mute", "/config/mute/1", "0"]], - ["/fxsend/2/mix", "fader", ["mute", "/config/mute/2", "0"]], - ["/fxsend/3/mix", "fader", ["mute", "/config/mute/3", "0"]], - ["/fxsend/4/mix", "fader", ["mute", "/config/mute/4", "0"]], - ["/rtn/1/mix", "fader", ["none"]], - ["/rtn/2/mix", "fader", ["none"]], - ["/rtn/3/mix", "fader", ["none"]], - ["/rtn/4/mix", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ["/fxsend/1/mix", ["mute", "/config/mute/1", "0"]], + ["/fxsend/2/mix", ["mute", "/config/mute/2", "0"]], + ["/fxsend/3/mix", ["mute", "/config/mute/3", "0"]], + ["/fxsend/4/mix", ["mute", "/config/mute/4", "0"]], + ["/rtn/1/mix", ["none"]], + ["/rtn/2/mix", ["none"]], + ["/rtn/3/mix", ["none"]], + ["/rtn/4/mix", [["subprocess", "mpc", ["play", "pause"]]]] ], "buttons": [ - ["mute", "/fxsend/1/mix", "/on"], - ["mute", "/fxsend/2/mix", "/on"], - ["mute", "/fxsend/3/mix", "/on"], - ["mute", "/fxsend/4/mix", "/on"], - ["mute", "/rtn/1/mix", "/on"], - ["mute", "/rtn/2/mix", "/on"], - ["mute", "/rtn/3/mix", "/on"], - ["mute", "/rtn/4/mix", "/on"], - ["mute", "/config/mute/4", ""], + ["mute", "/fxsend/1/mix"], + ["mute", "/fxsend/2/mix"], + ["mute", "/fxsend/3/mix"], + ["mute", "/fxsend/4/mix"], + ["mute", "/rtn/1/mix"], + ["mute", "/rtn/2/mix"], + ["mute", "/rtn/3/mix"], + ["mute", "/rtn/4/mix"], + ["mute", "/config/mute/4"], ["tap", "tap"], ["none", "Off"], ["layer", "LA0", "0", "Off"], @@ -141,25 +141,25 @@ }, "LA4": { "encoders": [ - ["/bus/1/mix", "fader", ["mute", "/config/mute/1", "0"]], - ["/bus/2/mix", "fader", ["mute", "/config/mute/2", "0"]], - ["/bus/3/mix", "fader", ["mute", "/config/mute/3", "0"]], - ["/bus/4/mix", "fader", ["mute", "/config/mute/4", "0"]], - ["/bus/5/mix", "fader", ["none"]], - ["/bus/6/mix", "fader", ["none"]], - ["none", "", [""]], - ["none", "", [["subprocess", "mpc", ["play", "pause"]]]] + ["/bus/1/mix", ["mute", "/config/mute/1", "0"]], + ["/bus/2/mix", ["mute", "/config/mute/2", "0"]], + ["/bus/3/mix", ["mute", "/config/mute/3", "0"]], + ["/bus/4/mix", ["mute", "/config/mute/4", "0"]], + ["/bus/5/mix", ["none"]], + ["/bus/6/mix", ["none"]], + ["none", [""]], + ["none", [["subprocess", "mpc", ["play", "pause"]]]] ], "buttons": [ - ["mute", "/bus/1/mix", "/on"], - ["mute", "/bus/2/mix", "/on"], - ["mute", "/bus/3/mix", "/on"], - ["mute", "/bus/4/mix", "/on"], - ["mute", "/bus/5/mix", "/on"], - ["mute", "/bus/6/mix", "/on"], + ["mute", "/bus/1/mix"], + ["mute", "/bus/2/mix"], + ["mute", "/bus/3/mix"], + ["mute", "/bus/4/mix"], + ["mute", "/bus/5/mix"], + ["mute", "/bus/6/mix"], ["none", "Off"], ["none", "Off"], - ["mute", "/config/mute/4", ""], + ["mute", "/config/mute/4"], ["tap", "tap"], ["none", "Off"], ["layer", "LA0", "0", "Off"], @@ -176,32 +176,32 @@ }, "LB0": { "encoders": [ - ["/ch/01/mix", "fader", ["mute", "/config/mute/1", "0"]], - ["/ch/02/mix", "fader", ["mute", "/config/mute/2", "0"]], - ["/ch/03/mix", "fader", ["mute", "/config/mute/3", "0"]], - ["/ch/04/mix", "fader", ["mute", "/config/mute/4", "0"]], - ["/ch/05/mix", "fader", ["none"]], - ["/ch/06/mix", "fader", ["none"]], - ["/ch/07/mix", "fader", ["none"]], - ["/ch/08/mix", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ["/ch/01/mix", ["mute", "/config/mute/1", "0"]], + ["/ch/02/mix", ["mute", "/config/mute/2", "0"]], + ["/ch/03/mix", ["mute", "/config/mute/3", "0"]], + ["/ch/04/mix", ["mute", "/config/mute/4", "0"]], + ["/ch/05/mix", ["none"]], + ["/ch/06/mix", ["none"]], + ["/ch/07/mix", ["none"]], + ["/ch/08/mix", [["subprocess", "mpc", ["play", "pause"]]]] ], "buttons": [ - ["send", "1", "send"], - ["send", "2", "send"], - ["send", "3", "send"], - ["send", "4", "send"], - ["send", "5", "send"], - ["send", "6", "send"], - ["send", "7", "send"], - ["send", "8", "send"], - ["mute", "/config/mute/4", ""], + ["send", "1"], + ["send", "2"], + ["send", "3"], + ["send", "4"], + ["send", "5"], + ["send", "6"], + ["send", "7"], + ["send", "8"], + ["mute", "/config/mute/4"], ["tap", "tap"], ["none", "Off"], ["layer", "LB0", "10", "On"], ["layer", "LB1", "10", "Off"], ["layer", "LB2", "10", "Off"], - ["send", "9", "send"], - ["send", "10", "send"], + ["send", "9"], + ["send", "10"], ["layer", "LA0", "0", "Off"], ["layer", "LB0", "10", "On"] ], @@ -211,32 +211,32 @@ }, "LB1": { "encoders": [ - ["/ch/09/mix", "fader", ["mute", "/config/mute/1", "0"]], - ["/ch/10/mix", "fader", ["mute", "/config/mute/2", "0"]], - ["/ch/11/mix", "fader", ["mute", "/config/mute/3", "0"]], - ["/ch/12/mix", "fader", ["mute", "/config/mute/4", "0"]], - ["/ch/13/mix", "fader", ["none"]], - ["/ch/14/mix", "fader", ["none"]], - ["/ch/15/mix", "fader", ["none"]], - ["/ch/16/mix", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ["/ch/09/mix", ["mute", "/config/mute/1", "0"]], + ["/ch/10/mix", ["mute", "/config/mute/2", "0"]], + ["/ch/11/mix", ["mute", "/config/mute/3", "0"]], + ["/ch/12/mix", ["mute", "/config/mute/4", "0"]], + ["/ch/13/mix", ["none"]], + ["/ch/14/mix", ["none"]], + ["/ch/15/mix", ["none"]], + ["/ch/16/mix", [["subprocess", "mpc", ["play", "pause"]]]] ], "buttons": [ - ["send", "1", "send"], - ["send", "2", "send"], - ["send", "3", "send"], - ["send", "4", "send"], - ["send", "5", "send"], - ["send", "6", "send"], - ["send", "7", "send"], - ["send", "8", "send"], - ["mute", "/config/mute/4", ""], + ["send", "1"], + ["send", "2"], + ["send", "3"], + ["send", "4"], + ["send", "5"], + ["send", "6"], + ["send", "7"], + ["send", "8"], + ["mute", "/config/mute/4"], ["tap", "tap"], ["none", "Off"], ["layer", "LB0", "10", "Off"], ["layer", "LB1", "10", "On"], ["layer", "LB2", "10", "Off"], - ["send", "9", "send"], - ["send", "10", "send"], + ["send", "9"], + ["send", "10"], ["layer", "LA1", "0", "Off"], ["layer", "LB1", "10", "On"] ], @@ -246,32 +246,32 @@ }, "LB2": { "encoders": [ - ["/rtn/aux/mix", "fader", ["mute", "/config/mute/1", "0"]], - ["none", "", ["mute", "/config/mute/2", "0"]], - ["none", "", ["mute", "/config/mute/3", "0"]], - ["none", "", ["mute", "/config/mute/4", "0"]], - ["/dca/1", "fader", ["none"]], - ["/dca/2", "fader", ["none"]], - ["/dca/3", "fader", ["none"]], - ["/dca/4", "fader", [["subprocess", "mpc", ["play", "pause"]]]] + ["/rtn/aux/mix", ["mute", "/config/mute/1", "0"]], + ["none", ["mute", "/config/mute/2", "0"]], + ["none", ["mute", "/config/mute/3", "0"]], + ["none", ["mute", "/config/mute/4", "0"]], + ["/dca/1", ["none"]], + ["/dca/2", ["none"]], + ["/dca/3", ["none"]], + ["/dca/4", [["subprocess", "mpc", ["play", "pause"]]]] ], "buttons": [ - ["send", "1", "send"], - ["send", "2", "send"], - ["send", "3", "send"], - ["send", "4", "send"], - ["send", "5", "send"], - ["send", "6", "send"], - ["send", "7", "send"], - ["send", "8", "send"], - ["mute", "/config/mute/4", ""], + ["send", "1"], + ["send", "2"], + ["send", "3"], + ["send", "4"], + ["send", "5"], + ["send", "6"], + ["send", "7"], + ["send", "8"], + ["mute", "/config/mute/4"], ["tap", "tap"], ["none", "Off"], ["layer", "LB0", "10", "Off"], ["layer", "LB1", "10", "Off"], ["layer", "LB2", "10", "On"], - ["send", "9", "send"], - ["send", "10", "send"], + ["send", "9"], + ["send", "10"], ["layer", "LA2", "0", "Off"], ["layer", "LB2", "10", "On"] ], diff --git a/rossdickson.json b/rossdickson.json index 6cdfa00..8cc3484 100644 --- a/rossdickson.json +++ b/rossdickson.json @@ -1,31 +1,31 @@ { "A": { "encoders": [ - ["/ch/01/mix", "fader", ["reset", "0.750000"]], - ["/ch/02/mix", "fader", ["reset", "0.750000"]], - ["/ch/03/mix", "fader", ["reset", "0.750000"]], - ["/ch/04/mix", "fader", ["reset", "0.750000"]], - ["/ch/05/mix", "fader", ["reset", "0.750000"]], - ["/ch/06/mix", "fader", ["reset", "0.750000"]], - ["/ch/07/mix", "fader", ["reset", "0.750000"]], - ["/ch/08/mix", "fader", ["reset", "0.750000"]] + ["/ch/01/mix", ["reset", "0.750000"]], + ["/ch/02/mix", ["reset", "0.750000"]], + ["/ch/03/mix", ["reset", "0.750000"]], + ["/ch/04/mix", ["reset", "0.750000"]], + ["/ch/05/mix", ["reset", "0.750000"]], + ["/ch/06/mix", ["reset", "0.750000"]], + ["/ch/07/mix", ["reset", "0.750000"]], + ["/ch/08/mix", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "/on"], - ["mute", "/ch/02/mix", "/on"], - ["mute", "/ch/03/mix", "/on"], - ["mute", "/ch/04/mix", "/on"], - ["mute", "/ch/05/mix", "/on"], - ["mute", "/ch/06/mix", "/on"], - ["mute", "/ch/07/mix", "/on"], - ["mute", "/ch/08/mix", "/on"], - ["send", "1", "send"], - ["send", "2", "send"], - ["send", "3", "send"], - ["send", "4", "send"], - ["send", "5", "send"], - ["send", "6", "send"], - ["send", "7", "send"], + ["mute", "/ch/01/mix"], + ["mute", "/ch/02/mix"], + ["mute", "/ch/03/mix"], + ["mute", "/ch/04/mix"], + ["mute", "/ch/05/mix"], + ["mute", "/ch/06/mix"], + ["mute", "/ch/07/mix"], + ["mute", "/ch/08/mix"], + ["send", "1"], + ["send", "2"], + ["send", "3"], + ["send", "4"], + ["send", "5"], + ["send", "6"], + ["send", "7"], ["layer", "Gain", "0", "Off"], ["layer", "Aprime", "7", "On"], ["layer", "B", "0", "Off"] @@ -36,31 +36,31 @@ }, "Aprime": { "encoders": [ - ["/ch/09/mix", "fader", ["reset", "0.750000"]], - ["/ch/10/mix", "fader", ["reset", "0.750000"]], - ["/ch/11/mix", "fader", ["reset", "0.750000"]], - ["/ch/12/mix", "fader", ["reset", "0.750000"]], - ["/ch/13/mix", "fader", ["reset", "0.750000"]], - ["/ch/14/mix", "fader", ["reset", "0.750000"]], - ["/ch/15/mix", "fader", ["reset", "0.750000"]], - ["/ch/16/mix", "fader", ["reset", "0.750000"]] + ["/ch/09/mix", ["reset", "0.750000"]], + ["/ch/10/mix", ["reset", "0.750000"]], + ["/ch/11/mix", ["reset", "0.750000"]], + ["/ch/12/mix", ["reset", "0.750000"]], + ["/ch/13/mix", ["reset", "0.750000"]], + ["/ch/14/mix", ["reset", "0.750000"]], + ["/ch/15/mix", ["reset", "0.750000"]], + ["/ch/16/mix", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/09/mix", "/on"], - ["mute", "/ch/10/mix", "/on"], - ["mute", "/ch/11/mix", "/on"], - ["mute", "/ch/12/mix", "/on"], - ["mute", "/ch/13/mix", "/on"], - ["mute", "/ch/14/mix", "/on"], - ["mute", "/ch/15/mix", "/on"], - ["mute", "/ch/16/mix", "/on"], - ["send", "1", "send"], - ["send", "2", "send"], - ["send", "3", "send"], - ["send", "4", "send"], - ["send", "5", "send"], - ["send", "6", "send"], - ["send", "7", "send"], + ["mute", "/ch/09/mix"], + ["mute", "/ch/10/mix"], + ["mute", "/ch/11/mix"], + ["mute", "/ch/12/mix"], + ["mute", "/ch/13/mix"], + ["mute", "/ch/14/mix"], + ["mute", "/ch/15/mix"], + ["mute", "/ch/16/mix"], + ["send", "1"], + ["send", "2"], + ["send", "3"], + ["send", "4"], + ["send", "5"], + ["send", "6"], + ["send", "7"], ["layer", "Gain", "0", "Off"], ["layer", "A", "7", "Blink"], ["layer", "B", "0", "Off"] @@ -71,24 +71,24 @@ }, "B": { "encoders": [ - ["/bus/1/mix", "0", ["reset", "0.375367"]], - ["/bus/2/mix", "0", ["reset", "0.375367"]], - ["/bus/3/mix", "0", ["reset", "0.375367"]], - ["/bus/4/mix", "0", ["reset", "0.375367"]], - ["/bus/5/mix", "0", ["reset", "0.375367"]], - ["/bus/6/mix", "0", ["reset", "0.375367"]], - ["/rtn/aux/mix", "0", ["reset", "0.750000"]], - ["/lr/mix", "0", ["reset", "0.375367"]] + ["/bus/1/mix", ["reset", "0.375367"]], + ["/bus/2/mix", ["reset", "0.375367"]], + ["/bus/3/mix", ["reset", "0.375367"]], + ["/bus/4/mix", ["reset", "0.375367"]], + ["/bus/5/mix", ["reset", "0.375367"]], + ["/bus/6/mix", ["reset", "0.375367"]], + ["/rtn/aux/mix", ["reset", "0.750000"]], + ["/lr/mix", ["reset", "0.375367"]] ], "buttons": [ - ["mute", "/bus/1/mix", "/on"], - ["mute", "/bus/2/mix", "/on"], - ["mute", "/bus/3/mix", "/on"], - ["mute", "/bus/4/mix", "/on"], - ["mute", "/bus/5/mix", "/on"], - ["mute", "/bus/6/mix", "/on"], - ["mute", "/rtn/aux/mix", "/on"], - ["mute", "/lr/mix", "/on"], + ["mute", "/bus/1/mix"], + ["mute", "/bus/2/mix"], + ["mute", "/bus/3/mix"], + ["mute", "/bus/4/mix"], + ["mute", "/bus/5/mix"], + ["mute", "/bus/6/mix"], + ["mute", "/rtn/aux/mix"], + ["mute", "/lr/mix"], ["none", "1"], ["none", "2"], ["none", "3"], @@ -106,20 +106,20 @@ }, "Gain": { "encoders": [ - ["/headamp/01", "gain", ["none"]], - ["/headamp/02", "gain", ["none"]], - ["/headamp/03", "gain", ["none"]], - ["/headamp/04", "gain", ["none"]], - ["/headamp/05", "gain", ["none"]], - ["/headamp/06", "gain", ["none"]], - ["/headamp/07", "gain", ["none"]], - ["/headamp/08", "gain", ["none"]] + ["/headamp/01", ["none"]], + ["/headamp/02", ["none"]], + ["/headamp/03", ["none"]], + ["/headamp/04", ["none"]], + ["/headamp/05", ["none"]], + ["/headamp/06", ["none"]], + ["/headamp/07", ["none"]], + ["/headamp/08", ["none"]] ], "buttons": [ ["none", "Off"], ["none", "Off"], - ["quit", "1", "State"], - ["quit", "2", "State"], + ["quit", "State"], + ["quit", "State"], ["none", "Off"], ["none", "Off"], ["clip", "State"], @@ -141,20 +141,20 @@ }, "Gainprime": { "encoders": [ - ["/headamp/09", "gain", ["none"]], - ["/headamp/10", "gain", ["none"]], - ["/headamp/11", "gain", ["none"]], - ["/headamp/12", "gain", ["none"]], - ["/headamp/13", "gain", ["none"]], - ["/headamp/14", "gain", ["none"]], - ["/headamp/15", "gain", ["none"]], - ["/headamp/16", "gain", ["none"]] + ["/headamp/09", ["none"]], + ["/headamp/10", ["none"]], + ["/headamp/11", ["none"]], + ["/headamp/12", ["none"]], + ["/headamp/13", ["none"]], + ["/headamp/14", ["none"]], + ["/headamp/15", ["none"]], + ["/headamp/16", ["none"]] ], "buttons": [ ["none", "Off"], ["none", "Off"], - ["quit", "1", "State"], - ["quit", "2", "State"], + ["quit", "State"], + ["quit", "State"], ["none", "Off"], ["none", "Off"], ["clip", "State"], diff --git a/simple.json b/simple.json index 034939d..31f0757 100644 --- a/simple.json +++ b/simple.json @@ -1,32 +1,32 @@ { "A": { "encoders": [ - ["/ch/01/mix", "fader", ["reset", "0.750000"]], - ["/ch/02/mix", "fader", ["reset", "0.750000"]], - ["/ch/03/mix", "fader", ["reset", "0.750000"]], - ["/ch/04/mix", "fader", ["reset", "0.750000"]], - ["/ch/05/mix", "fader", ["reset", "0.750000"]], - ["/ch/06/mix", "fader", ["reset", "0.750000"]], - ["/ch/07/mix", "fader", ["reset", "0.750000"]], - ["/ch/08/mix", "fader", ["reset", "0.750000"]] + ["/ch/01/mix", ["reset", "0.750000"]], + ["/ch/02/mix", ["reset", "0.750000"]], + ["/ch/03/mix", ["reset", "0.750000"]], + ["/ch/04/mix", ["reset", "0.750000"]], + ["/ch/05/mix", ["reset", "0.750000"]], + ["/ch/06/mix", ["reset", "0.750000"]], + ["/ch/07/mix", ["reset", "0.750000"]], + ["/ch/08/mix", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "/on"], - ["mute", "/ch/02/mix", "/on"], - ["mute", "/ch/03/mix", "/on"], - ["mute", "/ch/04/mix", "/on"], - ["mute", "/ch/05/mix", "/on"], - ["mute", "/ch/06/mix", "/on"], - ["mute", "/ch/07/mix", "/on"], - ["mute", "/ch/08/mix", "/on"], - ["mute", "/ch/09/mix", "/on"], - ["mute", "/ch/10/mix", "/on"], - ["mute", "/ch/11/mix", "/on"], - ["mute", "/ch/12/mix", "/on"], - ["mute", "/ch/13/mix", "/on"], - ["mute", "/ch/14/mix", "/on"], - ["mute", "/ch/15/mix", "/on"], - ["mute", "/ch/16/mix", "/on"], + ["mute", "/ch/01/mix"], + ["mute", "/ch/02/mix"], + ["mute", "/ch/03/mix"], + ["mute", "/ch/04/mix"], + ["mute", "/ch/05/mix"], + ["mute", "/ch/06/mix"], + ["mute", "/ch/07/mix"], + ["mute", "/ch/08/mix"], + ["mute", "/ch/09/mix"], + ["mute", "/ch/10/mix"], + ["mute", "/ch/11/mix"], + ["mute", "/ch/12/mix"], + ["mute", "/ch/13/mix"], + ["mute", "/ch/14/mix"], + ["mute", "/ch/15/mix"], + ["mute", "/ch/16/mix"], ["layer", "A", "0", "On"], ["layer", "B", "0", "Off"] ], @@ -36,32 +36,32 @@ }, "B": { "encoders": [ - ["/ch/09/mix", "fader", ["reset", "0.750000"]], - ["/ch/10/mix", "fader", ["reset", "0.750000"]], - ["/ch/11/mix", "fader", ["reset", "0.750000"]], - ["/ch/12/mix", "fader", ["reset", "0.750000"]], - ["/ch/13/mix", "fader", ["reset", "0.750000"]], - ["/ch/14/mix", "fader", ["reset", "0.750000"]], - ["/ch/15/mix", "fader", ["reset", "0.750000"]], - ["/ch/16/mix", "fader", ["reset", "0.750000"]] + ["/ch/09/mix", ["reset", "0.750000"]], + ["/ch/10/mix", ["reset", "0.750000"]], + ["/ch/11/mix", ["reset", "0.750000"]], + ["/ch/12/mix", ["reset", "0.750000"]], + ["/ch/13/mix", ["reset", "0.750000"]], + ["/ch/14/mix", ["reset", "0.750000"]], + ["/ch/15/mix", ["reset", "0.750000"]], + ["/ch/16/mix", ["reset", "0.750000"]] ], "buttons": [ - ["mute", "/ch/01/mix", "/on"], - ["mute", "/ch/02/mix", "/on"], - ["mute", "/ch/03/mix", "/on"], - ["mute", "/ch/04/mix", "/on"], - ["mute", "/ch/05/mix", "/on"], - ["mute", "/ch/06/mix", "/on"], - ["mute", "/ch/07/mix", "/on"], - ["mute", "/ch/08/mix", "/on"], - ["mute", "/ch/09/mix", "/on"], - ["mute", "/ch/10/mix", "/on"], - ["mute", "/ch/11/mix", "/on"], - ["mute", "/ch/12/mix", "/on"], - ["mute", "/ch/13/mix", "/on"], - ["mute", "/ch/14/mix", "/on"], - ["mute", "/ch/15/mix", "/on"], - ["mute", "/ch/16/mix", "/on"], + ["mute", "/ch/01/mix"], + ["mute", "/ch/02/mix"], + ["mute", "/ch/03/mix"], + ["mute", "/ch/04/mix"], + ["mute", "/ch/05/mix"], + ["mute", "/ch/06/mix"], + ["mute", "/ch/07/mix"], + ["mute", "/ch/08/mix"], + ["mute", "/ch/09/mix"], + ["mute", "/ch/10/mix"], + ["mute", "/ch/11/mix"], + ["mute", "/ch/12/mix"], + ["mute", "/ch/13/mix"], + ["mute", "/ch/14/mix"], + ["mute", "/ch/15/mix"], + ["mute", "/ch/16/mix"], ["layer", "A", "0", "Off"], ["layer", "B", "0", "On"] ], From 1161612843c2bc4bd87aba9ded9d51c4d32af13a Mon Sep 17 00:00:00 2001 From: Ross Dickson Date: Sun, 5 Sep 2021 15:24:53 -0400 Subject: [PATCH 25/25] fixed typos --- README.md | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 1b9fb88..51ac0e4 100644 --- a/README.md +++ b/README.md @@ -75,14 +75,14 @@ print them in 100% size. ### General Operation The general operation is fully defined in a config file in JSON format. Three -examples config files are provided and described below. The default is to use peterdikant.jason as it matches the opertion of the original project. +examples config files are provided and described below. The default is to use peterdikant.jason as it matches the operation of the original project. ### Config File Format The config file uses JSON for easy parsing and reasonably easy editing. The file -is structured as a sequence of dictonaries each representing a "layer" that +is structured as a sequence of dictionaries each representing a "layer" that defines all buttons, encoders, and the fader of the X-Touch Mini. Each layer -contains three dictonaries named 'encoders', 'buttons', and 'fader' that define +contains three dictionaries named 'encoders', 'buttons', and 'fader' that define the respective elements of the X-Touch. Each control specifies the "channel" that control effects and the command sent to the channel. The "channel" is technically defined as an OSC address supported by the X-Air protocol that takes @@ -91,10 +91,10 @@ a common prefix of a set of addresses defining how an audio source is to be processed. Many aspects of the X-Air follow this pattern even though they are aspects of the mixer that would not be considered a channel in an analog mixer. In a large number of cases the channel not only has a level in the main mix but -also a level on each of the auxiliary bus or effects procesor the source can +also a level on each of the auxiliary bus or effects processor the source can 'send' to. -The **encoders** dictonary is a list of 8 lists each specifying the operation of +The **encoders** dictionary is a list of 8 lists each specifying the operation of one encoder from `E1` to `E8`. These lists contain two elements: * channel, one of: @@ -102,14 +102,14 @@ one encoder from `E1` to `E8`. These lists contain two elements: * 'none' - if turning the encoder is to have no effect * a list defining the function of pressing the encoder as a button supporting three commands: - * 'none' - if presssing the encoder has no effect + * 'none' - if pressing the encoder has no effect * 'mute' followed by a 'channel' and 'send' to mute * 'reset' followed by a 'value' to set the encoder channel to * 'subprocess' - as the first element of a list defining a set of external commands to cycle through formatted as ['subprocess', 'external call', ['command', 'command', ...]] -The **buttons** dictonary is a list of 18 lists each specifying the operation of +The **buttons** dictionary is a list of 18 lists each specifying the operation of one button from `B01` to `B16` followed by `LA` and `LB`. These lists contain a variable number of elements based on the command: @@ -121,8 +121,8 @@ variable number of elements based on the command: * 'clip' followed by the LED state * 'tap' followed by the LED state -The **fader** dictonary is a list of one list specifying the operation of the -fader. The list is formatted the same as an encoder without the press fucntion +The **fader** dictionary is a list of one list specifying the operation of the +fader. The list is formatted the same as an encoder without the press function or 'quit' if setting the fader to 100% quits. ## Example Configuraitons @@ -130,8 +130,8 @@ or 'quit' if setting the fader to 100% quits. ### peterdikant.json This file configures the X-Touch to operate as defined by Peter Dikant to -support live sound mixing. The description is his original defintion of the -xair-remote and thus does not match the organization of hte config file. +support live sound mixing. The description is his original definition of the +xair-remote and thus does not match the organization of the config file. ### Layer A @@ -201,7 +201,7 @@ The Push function of encoders `E1` to `E4` can be used to toggle mute groups 1 t ### rossdickson.json This file configures the X-Touch to operate as defined by Ross Dickson to -support live sound mixing. The basic concept of the design is to concieve of the +support live sound mixing. The basic concept of the design is to conceive of the encoders `E1` through `E8` and top row of buttons `B01` through `B08` as a set of channel strips where the each encoder adjusts a channel level on turn, resets the level on press, and the button mutes the channel. The rings around the @@ -210,6 +210,7 @@ channels. The lower row of buttons and the layer buttons control what channels the channel strips control. The Fader `F1` is used used as a master quit signal when set to the top. + ### Layer A and A' Pressing `LA` configures the channel strips to control the first 8 channels and @@ -228,7 +229,7 @@ reverts to the main LR Mix. Pressing `LB` configures the channel strips to control the output levels on the 6 AUX ports, the level of the USB return, and the main LR output level. The reset level is -20db for outputs and 0db for the USB return. -20db is used as it -is about 6db lower than max level for somehting assuming consumer audio line +is about 6db lower than max level for something assuming consumer audio line level rather than pro audio line levels and thus is generally safe no matter what sort of amp or speakers you might be driving. @@ -246,7 +247,7 @@ function. When enabled, any channel that has more than 3 sequential samples within 3db of max it will lower the gain of the mic preamp by on step out of a 16 bit range. -Somewhat graphically this confiugres the xtouch as: +Somewhat graphically this configures the X-Touch as: @@ -259,10 +260,10 @@ AUX 1| AUX 2| ... | AUX 6| Fx 1 | Pre | Outputs | This file configures the X-Touch to operate as a simple set of levels and mutes for the X-Air to support a "dumb mixer" install. The basic concept of the design -is to concieve of the encoders `E1` through `E8` as levels and the buttons `B01` +is to conceive of the encoders `E1` through `E8` as levels and the buttons `B01` through `B16` as a mutes for the 16 channels of the X-Air. The buttons `LA` and `LB` switch the levels to match the upper or lower row of buttons. The rings -around the encoders show the levels whil prssing the encoders returns the level +around the encoders show the levels while pressing the encoders returns the level to 0db to quickly reset the mixer. The main fader `F1` is not used. ## License