diff options
| author | sbahling <sbahling@mudgum.net> | 2018-10-26 14:23:08 +0200 |
|---|---|---|
| committer | sbahling <sbahling@mudgum.net> | 2018-10-26 14:23:08 +0200 |
| commit | 1d1b23e5154e093a3e3e3e40b05946ab06c9738c (patch) | |
| tree | a57773bf632c21cecc6427619ec23fd8c88d8b5d /tascam_fw_console | |
| parent | 38c4e8310ef6bde30d3628ee93103c631caed47b (diff) | |
| download | tascam-fw-osc-1d1b23e5154e093a3e3e3e40b05946ab06c9738c.tar.gz tascam-fw-osc-1d1b23e5154e093a3e3e3e40b05946ab06c9738c.tar.xz tascam-fw-osc-1d1b23e5154e093a3e3e3e40b05946ab06c9738c.zip | |
Create initial package setup with setuptools
Diffstat (limited to 'tascam_fw_console')
| -rw-r--r-- | tascam_fw_console/__init__.py | 0 | ||||
| -rw-r--r-- | tascam_fw_console/buttons.py | 252 | ||||
| -rw-r--r-- | tascam_fw_console/cli.py | 60 | ||||
| -rw-r--r-- | tascam_fw_console/console.py | 340 | ||||
| -rw-r--r-- | tascam_fw_console/faders.py | 67 | ||||
| -rw-r--r-- | tascam_fw_console/fw_1884_buttons.py | 193 | ||||
| -rw-r--r-- | tascam_fw_console/osc.py | 60 | ||||
| -rw-r--r-- | tascam_fw_console/strips.py | 130 |
8 files changed, 1102 insertions, 0 deletions
diff --git a/tascam_fw_console/__init__.py b/tascam_fw_console/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tascam_fw_console/__init__.py diff --git a/tascam_fw_console/buttons.py b/tascam_fw_console/buttons.py new file mode 100644 index 0000000..c818760 --- /dev/null +++ b/tascam_fw_console/buttons.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python3 +""" + Open Sound Control send/recieve daemon for Tascam Firewire control surface + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: Copyright (c) 2018 Scott Bahling + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program (see the file COPYING); if not, write to the + Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + :license: GPL-2.0, see COPYING for details +""" + +import re +import pyautogui +from tascam_fw_console import osc + + +keymap = {'CTRL': 'ctrl', + 'ALT': 'alt', + 'SHIFT': 'shift', + } + + +osc_addrs = {'STOP': '/transport_stop', + 'PLAY': '/transport_play', + 'F.FWD': '/ffwd', + 'REW': '/rewind', + 'REC': '/rec_enable_toggle', + } + +re_strip_num = re.compile('.*([1-9])') + +current_bank = 1 +more_banks_up = False +more_banks_down = False + + +class Button(): + def __init__(self, console, name): + self.name = name + self.state = 0 + self.console = console + + def press(self): + self.state = 1 + print('%s pressed' % self.name) + + def release(self): + self.state = 0 + print('%s released' % self.name) + + @property + def pressed(self): + if self.state: + return True + return False + + +class EncoderButton(Button): + def __init__(self, console, name): + super().__init__(console, name) + + def press(self): + + encoder_mode = self.console.state.get('encoder_mode', None) + print(encoder_mode) + if encoder_mode == self.name: + return + + self.state = 1 + if encoder_mode: + self.console.unit.leds.turn_off(encoder_mode) + self.console.state[encoder_mode] = 0 + self.console.unit.leds.turn_on(self.name) + self.console.state['encoder_mode'] = self.name + super().press() + + +class StripSelButton(Button): + def __init__(self, console, name, strip): + super().__init__(console, name) + self.strip = strip + self.strip.select_button = self + + def press(self): + + strip = self.strip + print('handle_strip_sel', strip) + recenable = self.console.buttons.get('REC ENABLE', None) + if recenable.pressed: + if strip.rec: + osc.client.send_message('/strip/recenable', (strip.num, 0)) + else: + osc.client.send_message('/strip/recenable', (strip.num, 1)) + super().press() + + +class StripMuteButton(Button): + def __init__(self, console, name, strip): + super().__init__(console, name) + self.strip = strip + self.strip.mute_button = self + + def press(self): + + strip = self.strip + if strip.mute: + strip.mute = False + osc.client.send_message('/strip/mute', (strip.num, 0)) + else: + strip.mute = True + osc.client.send_message('/strip/mute', (strip.num, 1)) + super().press() + + +class StripSoloButton(Button): + def __init__(self, console, name, strip): + super().__init__(console, name) + self.strip = strip + self.strip.solo_button = self + + def press(self): + + strip = self.strip + if strip.solo: + strip.solo = False + osc.client.send_message('/strip/solo', (strip.num, 0)) + else: + strip.solo = True + osc.client.send_message('/strip/solo', (strip.num, 1)) + super().press() + + +class ArrowButton(Button): + def __init__(self, console, name): + super().__init__(console, name) + + def press(self): + key = keymap.get(self.name.lower(), self.name.lower()) + pyautogui.press(key) + + +class ModButton(Button): + def __init__(self, console, name): + super().__init__(console, name) + + def mod_button_press(self): + key = keymap.get(self.name, None) + if key is None: + return + + pyautogui.keyDown(key) + super().press() + + def release(self): + key = keymap.get(self.name, None) + if key is None: + return + + pyautogui.keyUp(key) + super().release() + + +class ComputerButton(Button): + def __init__(self, console, name): + super().__init__(console, name) + + def press(self): + osc.client.send_message('/set_surface', (8, 7, 19, 1, 8, 11)) + super().press() + + +class ClrSoloButton(Button): + def __init__(self, console, name): + super().__init__(console, name) + + def press(self): + osc.client.send_message('/cancel_all_solos', 1) + super().press() + + +class LoopButton(Button): + def __init__(self, console, name): + super().__init__(console, name) + + def press(self, *args): + if self.console.state.get('loop', 0): + print('******* loop off') + osc.client.send_message('/loop_toggle', 0) + else: + print('******* loop on') + osc.client.send_message('/loop_toggle', 1) + super().press() + + +class TransportButton(Button): + def __init__(self, console, name): + super().__init__(console, name) + + def press(self): + addr = osc_addrs.get(self.name, None) + if addr: + osc.client.send_message(addr, 1) + super().press() + + +class BankSwitchButton(Button): + def __init__(self, console, name, direction): + super().__init__(console, name) + self.direction = direction + + def press(self): + + direction = self.direction + print(direction, more_banks_up, more_banks_down) + if direction > 0 and more_banks_up: + print('calling /bank_up 1') + osc.client.send_message('/bank_up', 1) + elif direction < 0 and more_banks_down: + print('calling /bank_down 1') + osc.client.send_message('/bank_down', 1) + super().press() + + +class NudgeButton(Button): + def __init__(self, console, name, direction): + super().__init__(console, name) + self.direction = direction + + def nudge_press(self): + direction = self.direction + print(direction) + if direction > 0: + osc.client.send_message('/access_action', + 'Common/nudge-next-forward') + else: + osc.client.send_message('/access_action', + 'Common/nudge-next-backward') + super().press() + + +NULLBUTTON = Button(None, None) diff --git a/tascam_fw_console/cli.py b/tascam_fw_console/cli.py new file mode 100644 index 0000000..401a953 --- /dev/null +++ b/tascam_fw_console/cli.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +""" + Open Sound Control send/recieve daemon for Tascam Firewire control surface + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: Copyright (c) 2018 Scott Bahling + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program (see the file COPYING); if not, write to the + Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + :license: GPL-2.0, see COPYING for details + +Usage: + tascam-fw-osc [options] + +Options: + -h --help Show this screen. + -l --list List connected Tascam FW console units + -c CARD_NUM --card=CARD_NUM Number of the ALSA sound card + -d GUID --device=GUID GUID of the FW unit + -I IP --listen-ip=IP IP to listen on [default: 127.0.0.1] + -P PORT --listen-port=PORT Port to listen on [default: 5005] + -i IP --ip=IP IP to communicate to [default: 127.0.0.1] + -p PORT --port=PORT Port to communicate to [default: 3919] +""" + +from docopt import docopt +from tascam_fw_console.console import Console, list_units +from tascam_fw_console import osc + + +def main(): + + args = docopt(__doc__, version='0.1') + + if args['--list']: + for model, fullpath, guid in list_units(): + print('{0} path: {1} GUID: 0x{2:016x}'.format(model, + fullpath, guid)) + exit() + + console = Console(card_id=args['--card'], guid=args['--device']) + osc.init_client(args['--ip'], int(args['--port'])) + osc.init_server(args['--listen-ip'], int(args['--listen-port']), console) + + print("Serving on {}".format(osc.server.server_address)) + osc.server.serve_forever() + + +if __name__ == '__main__': + main() diff --git a/tascam_fw_console/console.py b/tascam_fw_console/console.py new file mode 100644 index 0000000..103f755 --- /dev/null +++ b/tascam_fw_console/console.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +""" + Open Sound Control send/recieve daemon for Tascam Firewire control surface + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: Copyright (c) 2018 Scott Bahling + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program (see the file COPYING); if not, write to the + Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + :license: GPL-2.0, see COPYING for details +""" + +import string +import time +import threading +from pathlib import Path + +from tascam_fw_console import strips +from tascam_fw_console import fw_1884_buttons + +import gi + +gi.require_version('Hinawa', '2.0') +from gi.repository import Hinawa # noqa: E402 +from hinawa_utils.tscm.config_rom_parser import TscmConfigRomParser # noqa: E402 E501 +from hinawa_utils.tscm.tscm_console_unit import TscmConsoleUnit # noqa: E402 + + +bits32 = '{:032b}'.format + + +class ConsoleStatus(): + def __init__(self, quadlets): + self.quadlets = quadlets + + def field(self, quadlet, first_bit=0, last_bit=32): + bits = self.quadlets[quadlet] + + # reverse the bit order before slicing so the index '0' is the LSB + # and reverse back before converting to int + return int(bits32(bits)[::-1][first_bit-1:last_bit][::-1], 2) + + +class RunningStatusThread(): + def __init__(self, console, interval=0.1): + """ Constructor + :type interval: int + :param interval: Check interval, in seconds + """ + self.console = console + self.interval = interval + self.callbacks = set() + self.last_status = [] + + thread = threading.Thread(target=self.run) + thread.daemon = True # Daemonize thread + thread.start() # Start the execution + + def add_callback(self, obj): + self.callbacks.add(obj) + + def remove_callback(self, obj): + self.callbacks.remove(obj) + + def run(self): + """ Method that runs forever """ + while True: + for obj in self.callbacks: + value = self.console.status.field(obj.status_quadlet, + *obj.status_bits, + ) + obj.status_callback(value) + + time.sleep(self.interval) + + +class Console(): + def __init__(self, card_id=None, guid=None): + + fullpath = None + if guid: + fullpath = self._seek_snd_unit_from_guid(card_id) + elif card_id: + fullpath = '/dev/snd/hwC{0}D0'.format(card_id) + else: + try: + units = list_units() + if units: + model, fullpath, guid = units[0] + except Exception: + raise Exception('No Tascam FW Console unit found') + + if fullpath: + self.unit = TscmConsoleUnit(fullpath) + model = self.unit.model_name + guid = self.unit.get_property('guid') + print('Found Tascam {0} unit with GUID: {1:016x}'.format(model, guid)) # noqa E501 + else: + raise Exception('No Tascam FW Console unit found') + + self.state = {} + self.current_bank = 1 + self.more_banks_up = False + self.more_banks_down = False + self.strips = strips.init_strips(self) + self.buttons = {} + self.init_buttons() + + self.unit.connect('control', self.handle_control) + + self.status_thread = RunningStatusThread(self) # noqa F841 + + def _seek_snd_unit_from_guid(self, guid): + for fullpath in Path('/dev/snd').glob('hw*'): + fullpath = str(fullpath) + try: + unit = Hinawa.SndUnit() + unit.open(fullpath) + if unit.get_property('guid') == guid: + return fullpath + except Exception as e: + pass + finally: + del unit + return None + + def init_buttons(self): + self.button_map = fw_1884_buttons.init_buttons(self) + for index, items in self.button_map.items(): + for item in items: + if item is None: + continue + self.buttons[item.name] = item + + @property + def status(self): + try: + return ConsoleStatus(self.unit.get_status()) + except Exception as e: + raise e + + def handle_bit_flags(self, index, before, after): + + changed = before ^ after + + bits = reversed(bits32(changed)) + for bit in [i for i, b in enumerate(bits) if int(b)]: + high = bool(after & (0b1 << bit)) + button = self.button_map[index][int(bit)] + if button is None: + print('unhandled control bit {}:{}'.format(index, bit)) + continue + + if high: + button.release() + else: + button.press() + + def handle_encoder(self, index, before, after): + strip1 = (index * 2 - 19) + strip2 = (index * 2 - 18) + bval1 = before & 0xffff + bval2 = before >> 0x10 + aval1 = after & 0xffff + aval2 = after >> 0x10 + + delta1 = roll_over_delta(aval1 - bval1) + delta2 = roll_over_delta(aval2 - bval2) + + if delta1: + if self.state.get('encoder_mode', '') == 'PAN': + self.strips[strip1].send_pan(delta1) + + if delta2: + if self.state.get('encoder_mode', '') == 'PAN': + self.strips[strip2].send_pan(delta2) + + def handle_control(self, unit, index, before, after): + + print('{0:02d}: {1:08x} {2:08x}'.format(index, before, after)) + + if index in [5, 6, 7, 8, 9]: + self.handle_bit_flags(index, before, after) + return + + if index in [10, 11, 12, 13, 14, 15]: + self.handle_encoder(index, before, after) + return + + def strip_fader_handler(self, addr, ssid, pos): + print('fader_handler', addr, ssid, pos) + strip = self.strips[int(ssid)] + if strip.name == ' ': + return + pos = pos * 1023 + strip.fader.position = pos + + def master_fader_handler(self, addr, pos): + print('master_fader_handler', pos) + self.strips[0].fader.position(1023 * pos) + + def default_handler(self, addr, *args): + print(addr, args) + + def strip_mute_handler(self, addr, ssid, state): + strip = self.strips[int(ssid)] + print('mute_handler', strip, state) + if strip.name == ' ': + return + if state: + strip.mute = True + else: + strip.mute = False + + def strip_solo_handler(self, addr, ssid, state): + strip = self.strips[int(ssid)] + print('solo_handler', strip, state) + if strip.name == ' ': + return + if state: + strip.solo = True + else: + strip.solo = False + + def strip_recenable_handler(self, addr, ssid, state): + strip = self.strips[int(ssid)] + print('recenable_handler', strip, state) + if strip.name == ' ': + return + if state: + strip.rec = True + else: + strip.rec = False + + def loop_toggle_handler(self, addr, state): + print(addr, state) + if state: + self.state['LOOP'] = 1 + self.unit.leds.loop.turn_on() + else: + self.state['LOOP'] = 0 + self.unit.leds.loop.turn_off() + + def transport_stop_handler(self, addr, state): + print(addr, state) + if state: + self.unit.leds.stop.turn_on() + else: + self.unit.leds.stop.turn_off() + + def transport_play_handler(self, addr, state): + print(addr, state) + if state: + self.unit.leds.play.turn_on() + else: + self.unit.leds.play.turn_off() + + def ffwd_handler(self, addr, state): + if state: + self.unit.leds.f_fwd.turn_on() + else: + self.unit.leds.f_fwd.turn_off() + + def rewind_handler(self, addr, state): + if state: + self.unit.leds.rew.turn_on() + else: + self.unit.leds.rew.turn_off() + + def rec_enable_toggle_handler(self, addr, state): + if state: + self.unit.leds.rec.turn_on() + else: + self.unit.leds.rec.turn_off() + + def pan_stereo_position_handler(self, addr, pos, pan): + self.strips[pos].recv_pan(pan) + + def strip_name_handler(self, addr, ssid, name): + self.strips[int(ssid)].name = name + + def bank_up_handler(self, addr, more_up): + print(addr, more_up) + self.more_banks_up = bool(more_up) + + def bank_down_handler(self, addr, more_down): + print(addr, more_down) + self.more_banks_down = bool(more_down) + + +def roll_over_delta(delta, ceiling=0xffff): + + print(delta) + if delta > ceiling - 10: + return delta - (ceiling + 1) + if delta < -ceiling + 10: + return delta + (ceiling + 1) + return delta + + +def _check_hexadecimal(literal): + if literal.find('0x') == 0: + literal = literal[2:] + if len(literal) != 16: + return False + for character in literal: + if character not in string.hexdigits: + return False + else: + return True + + +def list_units(): + units = [] + for fullpath in Path('/dev/snd').glob('hw*'): + fullpath = str(fullpath) + unit = None + try: + unit = Hinawa.SndUnit() + unit.open(fullpath) + parser = TscmConfigRomParser() + info = parser.parse_rom(unit.get_config_rom()) + model = info['model-name'] + guid = unit.get_property('guid') + if model in ('FW-1082', 'FW-1884'): + units.append((model, fullpath, guid)) + except Exception as e: + pass + + return units diff --git a/tascam_fw_console/faders.py b/tascam_fw_console/faders.py new file mode 100644 index 0000000..c9145a2 --- /dev/null +++ b/tascam_fw_console/faders.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +""" + Open Sound Control send/recieve daemon for Tascam Firewire control surface + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: Copyright (c) 2018 Scott Bahling + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program (see the file COPYING); if not, write to the + Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + :license: GPL-2.0, see COPYING for details +""" + +from tascam_fw_console.buttons import Button +from tascam_fw_console import osc + +status_quadlets = (4, 0, 0, 1, 1, 2, 2, 3, 3) +status_bits = ((1, 16), + (1, 16), + (17, 32), + (1, 16), + (17, 32), + (1, 16), + (17, 32), + (1, 16), + (17, 32), + ) + + +class Fader(Button): + def __init__(self, console, strip): + name = 'Strip {} Fader'. format(strip.num) + super().__init__(console, name) + self.strip = strip + self.addr = '/strip/fader' + self.status_quadlet = status_quadlets[self.strip.num] + self.status_bits = status_bits[self.strip.num] + self.status_callback = self.send_pos + + @property + def position(self): + return self.console.status.field(self.status_quadlet, self.status_bits) + + @position.setter + def position(self, pos): + self.console.unit.strips[self.strip.num].fader.set_position(pos) + + def send_pos(self, pos): + print('{}, ({}, {})'.format(self.addr, self.strip.num, pos/1023)) + osc.client.send_message(self.addr, (self.strip.num, pos/1023)) + + def press(self): + self.console.status_thread.add_callback(self) + super().press() + + def release(self): + self.console.status_thread.remove_callback(self) + super().press() diff --git a/tascam_fw_console/fw_1884_buttons.py b/tascam_fw_console/fw_1884_buttons.py new file mode 100644 index 0000000..44f3b60 --- /dev/null +++ b/tascam_fw_console/fw_1884_buttons.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +""" + Open Sound Control send/recieve daemon for Tascam Firewire control surface + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: Copyright (c) 2018 Scott Bahling + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program (see the file COPYING); if not, write to the + Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + :license: GPL-2.0, see COPYING for details +""" + +from tascam_fw_console import buttons + + +def init_buttons(console): + strips = console.strips + return {5: [None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + strips[1].fader, + strips[2].fader, + strips[3].fader, + strips[4].fader, + strips[5].fader, + strips[6].fader, + strips[7].fader, + strips[8].fader, + strips[0].fader, + None, + None, + None, + None, + None, + None, + None, + ], + 6: [None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + buttons.StripSelButton(console, 'Strip 1 Sel', strips[1]), + buttons.StripSelButton(console, 'Strip 2 Sel', strips[2]), + buttons.StripSelButton(console, 'Strip 3 Sel', strips[3]), + buttons.StripSelButton(console, 'Strip 4 Sel', strips[4]), + buttons.StripSelButton(console, 'Strip 5 Sel', strips[5]), + buttons.StripSelButton(console, 'Strip 6 Sel', strips[6]), + buttons.StripSelButton(console, 'Strip 7 Sel', strips[7]), + buttons.StripSelButton(console, 'Strip 8 Sel', strips[8]), + buttons.StripSoloButton(console, 'Strip 1 Solo', strips[1]), + buttons.StripSoloButton(console, 'Strip 2 Solo', strips[2]), + buttons.StripSoloButton(console, 'Strip 3 Solo', strips[3]), + buttons.StripSoloButton(console, 'Strip 4 Solo', strips[4]), + buttons.StripSoloButton(console, 'Strip 5 Solo', strips[5]), + buttons.StripSoloButton(console, 'Strip 6 Solo', strips[6]), + buttons.StripSoloButton(console, 'Strip 7 Solo', strips[7]), + buttons.StripSoloButton(console, 'Strip 8 Solo', strips[8]), + ], + 7: [buttons.StripMuteButton(console, 'Strip 1 Mute', strips[1]), + buttons.StripMuteButton(console, 'Strip 2 Mute', strips[2]), + buttons.StripMuteButton(console, 'Strip 3 Mute', strips[3]), + buttons.StripMuteButton(console, 'Strip 4 Mute', strips[4]), + buttons.StripMuteButton(console, 'Strip 5 Mute', strips[5]), + buttons.StripMuteButton(console, 'Strip 6 Mute', strips[6]), + buttons.StripMuteButton(console, 'Strip 7 Mute', strips[7]), + buttons.StripMuteButton(console, 'Strip 8 Mute', strips[8]), + buttons.EncoderButton(console, 'AUX5'), + buttons.EncoderButton(console, 'AUX7'), + buttons.EncoderButton(console, 'AUX6'), + buttons.EncoderButton(console, 'AUX8'), + None, + None, + None, + None, + buttons.Button(console, 'FLIP'), + buttons.EncoderButton(console, 'AUX1'), + buttons.EncoderButton(console, 'AUX3'), + buttons.EncoderButton(console, 'PAN'), + buttons.EncoderButton(console, 'AUX2'), + buttons.EncoderButton(console, 'AUX4'), + None, + None, + buttons.Button(console, 'Control Panel'), + buttons.Button(console, 'F1'), + buttons.Button(console, 'ALL SAFE'), + buttons.Button(console, 'F5'), + buttons.Button(console, 'CUT'), + buttons.Button(console, 'COPY'), + buttons.ModButton(console, 'ALT'), + buttons.ModButton(console, 'SHIFT'), + ], + 8: [buttons.Button(console, 'F2'), + buttons.ClrSoloButton(console, 'CLR SOLO'), + buttons.LoopButton(console, 'LOOP'), + buttons.Button(console, 'DEL'), + buttons.Button(console, 'PASTE'), + buttons.Button(console, 'UNDO'), + buttons.ModButton(console, 'CTRL'), + buttons.Button(console, 'Foot Switch'), + buttons.Button(console, 'Gain Enc A'), + buttons.Button(console, 'Gain Enc B'), + buttons.Button(console, 'Q Enc A'), + buttons.Button(console, 'Q Enc B'), + None, + None, + None, + None, + buttons.Button(console, 'Freq Enc A'), + buttons.Button(console, 'Freq Enc B'), + None, + None, + None, + None, + None, + None, + None, + buttons.ComputerButton(console, 'COMPUTER'), + None, + None, + buttons.Button(console, 'CLOCK'), + buttons.Button(console, 'ROUTE'), + None, + None, + ], + 9: [buttons.Button(console, 'F7'), + buttons.Button(console, 'F8'), + buttons.Button(console, 'F9'), + buttons.Button(console, 'F10'), + buttons.Button(console, 'READ'), + buttons.Button(console, 'WRT'), + buttons.Button(console, 'TCH'), + buttons.Button(console, 'LATCH'), + buttons.Button(console, 'HIGH'), + buttons.Button(console, 'HI-MID'), + buttons.Button(console, 'LOW-MID'), + buttons.Button(console, 'LOW'), + buttons.ArrowButton(console, 'UP'), + buttons.ArrowButton(console, 'LEFT'), + buttons.ArrowButton(console, 'DOWN'), + buttons.ArrowButton(console, 'RIGHT'), + buttons.Button(console, 'REC ENABLE'), + buttons.NudgeButton(console, 'NUDGE LEFT', -1), + buttons.NudgeButton(console, 'NUDGE RIGHT', 1), + buttons.BankSwitchButton(console, 'BANK LEFT', -1), + buttons.BankSwitchButton(console, 'BANK RIGHT', 1), + buttons.Button(console, 'LOCATE LEFT'), + buttons.Button(console, 'LOCATE RIGHT'), + buttons.Button(console, 'SHTL'), + buttons.Button(console, 'SET'), + buttons.Button(console, 'IN'), + buttons.Button(console, 'OUT'), + buttons.TransportButton(console, 'REW'), + buttons.TransportButton(console, 'F.FWD'), + buttons.TransportButton(console, 'STOP'), + buttons.TransportButton(console, 'PLAY'), + buttons.TransportButton(console, 'REC'), + ] + } diff --git a/tascam_fw_console/osc.py b/tascam_fw_console/osc.py new file mode 100644 index 0000000..fb17449 --- /dev/null +++ b/tascam_fw_console/osc.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +""" + Open Sound Control send/recieve daemon for Tascam Firewire control surface + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: Copyright (c) 2018 Scott Bahling + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program (see the file COPYING); if not, write to the + Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + :license: GPL-2.0, see COPYING for details +""" + +from pythonosc import dispatcher +from pythonosc import osc_server +from pythonosc import udp_client + +client = None +server = None +dispatcher = dispatcher.Dispatcher() + + +def init_dispatcher(console): + dispatcher.map("/loop_toggle", console.loop_toggle_handler) + dispatcher.map("/transport_stop", console.transport_stop_handler) + dispatcher.map("/transport_play", console.transport_play_handler) + dispatcher.map("/ffwd", console.ffwd_handler) + dispatcher.map("/rewind", console.rewind_handler) + dispatcher.map("/rec_enable_toggle", console.rec_enable_toggle_handler) + dispatcher.map('/strip/fader', console.strip_fader_handler) + dispatcher.map('/strip/mute', console.strip_mute_handler) + dispatcher.map('/strip/solo', console.strip_solo_handler) + dispatcher.map('/strip/recenable', console.strip_recenable_handler) + dispatcher.map('/master/fader', console.master_fader_handler) + dispatcher.map('/strip/pan_stereo_position', + console.pan_stereo_position_handler) + dispatcher.map('/strip/name', console.strip_name_handler) + dispatcher.map('/bank_up', console.bank_up_handler) + dispatcher.map('/bank_down', console.bank_down_handler) + dispatcher.set_default_handler(console.default_handler) + + +def init_client(ip, port): + global client + client = udp_client.SimpleUDPClient(ip, port) + + +def init_server(ip, port, console): + global server + init_dispatcher(console) + server = osc_server.ThreadingOSCUDPServer((ip, port), dispatcher) diff --git a/tascam_fw_console/strips.py b/tascam_fw_console/strips.py new file mode 100644 index 0000000..8d195f8 --- /dev/null +++ b/tascam_fw_console/strips.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +""" + Open Sound Control send/recieve daemon for Tascam Firewire control surface + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: Copyright (c) 2018 Scott Bahling + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program (see the file COPYING); if not, write to the + Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + :license: GPL-2.0, see COPYING for details +""" + +from tascam_fw_console.faders import Fader +from tascam_fw_console import osc + + +class Strip(): + + def __init__(self, console, num): + self.console = console + self.num = num + self.mute_button = None + self._mute = False + self.solo_button = None + self._solo = False + self.select_button = None + self._select = False + self._rec = False + self.pan = 0.5 + self.fader = Fader(self.console, self) + self.name = None + + @property + def mute_led(self): + return self.console.unit.strips[self.num].mute_led + + @property + def solo_led(self): + return self.console.unit.strips[self.num].solo_led + + @property + def sel_led(self): + return self.console.unit.strips[self.num].sel_led + + @property + def rec_led(self): + return self.console.unit.strips[self.num].rec_led + + @property + def mute(self): + return self._mute + + @mute.setter + def mute(self, state): + state = bool(state) + self._mute = state + if state: + self.mute_led.turn_on() + else: + self.mute_led.turn_off() + + @property + def solo(self): + return self._solo + + @solo.setter + def solo(self, state): + state = bool(state) + self._solo = state + if state: + self.solo_led.turn_on() + else: + self.solo_led.turn_off() + + @property + def select(self): + return self._select + + @select.setter + def select(self, state): + state = bool(state) + self._select = state + if state: + self.sel_led.turn_on() + else: + self.sel_led.turn_off() + + @property + def rec(self): + return self._rec + + @rec.setter + def rec(self, state): + state = bool(state) + self._rec = state + if state: + self.rec_led.turn_on() + else: + self.rec_led.turn_off() + + def send_pan(self, delta): + addr = '/strip/pan_stereo_position' + pan = self.pan + delta * 0.02 + if pan < 0: + pan = 0 + if pan > 1.0: + pan = 1.0 + osc.client.send_message(addr, (self.num, pan)) + + def recv_pan(self, pan): + print('Pan: {} {}'.format(self.num, pan)) + self.pan = pan + + +def init_strips(unit): + strips = [] + for strip_num in range(0, 9): + strips.append(Strip(unit, strip_num)) + + return strips |
