Beispiel #1
0
 def __init__(self, c_instance):
     ControlSurface.__init__(self, c_instance)
     self.set_suppress_rebuild_requests(True)
     self._folder_location = '/ClyphX/'
     self._macrobat = Macrobat(self)
     self._extra_prefs = ExtraPrefs(self)
     self._track_actions = ClyphXTrackActions(self)
     self._snap_actions = ClyphXSnapActions(self)
     self._global_actions = ClyphXGlobalActions(self)
     self._device_actions = ClyphXDeviceActions(self)
     self._clip_actions = ClyphXClipActions(self)
     self._control_surface_actions = ClyphXControlSurfaceActions(self)
     self._control_component = ClyphXControlComponent(self)
     ClyphXCueComponent(self)
     self._startup_actions_complete = False
     self._user_variables = {}
     self._play_seq_clips = {}
     self._loop_seq_clips = {}
     self._current_tracks = []
     live = Live.Application.get_application()
     self._can_have_nested_devices = False
     if live.get_major_version() == 8 and live.get_minor_version(
     ) >= 2 and live.get_bugfix_version() >= 2:
         self._can_have_nested_devices = True
     self.setup_tracks()
     self.log_message(
         'nativeKONTROL LOG ------- nativeKONTROL ClyphX v2.0.3 for Live 8 ------- Live Version: '
         + str(live.get_major_version()) + '.' +
         str(live.get_minor_version()) + '.' +
         str(live.get_bugfix_version()) + ' ------- END LOG')
     self.show_message('nativeKONTROL ClyphX v2.0.3 for Live 8')
     self.set_suppress_rebuild_requests(False)
Beispiel #2
0
 def __init__(self, c_instance):
     ControlSurface.__init__(self, c_instance)
     self.set_suppress_rebuild_requests(True) 
     self._folder_location = '/ClyphX/'
     self._macrobat = Macrobat(self)
     self._extra_prefs = ExtraPrefs(self)
     self._track_actions = ClyphXTrackActions(self)
     self._snap_actions = ClyphXSnapActions(self)
     self._global_actions = ClyphXGlobalActions(self)
     self._device_actions = ClyphXDeviceActions(self)
     self._clip_actions = ClyphXClipActions(self)
     self._control_surface_actions = ClyphXControlSurfaceActions(self)
     self._control_component = ClyphXControlComponent(self)
     ClyphXCueComponent(self)
     self._startup_actions_complete = False
     self._user_variables = {}
     self._play_seq_clips = {}
     self._loop_seq_clips = {}
     self._current_tracks = []
     live = Live.Application.get_application()
     self._can_have_nested_devices = False
     if live.get_major_version() == 8 and live.get_minor_version() >= 2 and live.get_bugfix_version() >= 2:
         self._can_have_nested_devices = True
     self.setup_tracks()
     self.log_message('nativeKONTROL LOG ------- nativeKONTROL ClyphX v2.0.3 for Live 8 ------- Live Version: ' + str(live.get_major_version()) + '.' + str(live.get_minor_version()) + '.' + str(live.get_bugfix_version()) + ' ------- END LOG')
     self.show_message('nativeKONTROL ClyphX v2.0.3 for Live 8')
     self.set_suppress_rebuild_requests(False) 
Beispiel #3
0
 def __init__(self, c_instance):
     ControlSurface.__init__(self, c_instance)
     self._user_settings_logged = False
     self.set_suppress_rebuild_requests(True)
     self._is_debugging = False
     self._process_xclips_if_track_muted = True
     self._macrobat = Macrobat(self)
     self._extra_prefs = ExtraPrefs(self)
     self._track_actions = ClyphXTrackActions(self)
     self._snap_actions = ClyphXSnapActions(self)
     self._global_actions = ClyphXGlobalActions(self)
     self._device_actions = ClyphXDeviceActions(self)
     self._clip_actions = ClyphXClipActions(self)
     self._control_surface_actions = ClyphXControlSurfaceActions(self)
     self._user_actions = ClyphXUserActions(self)
     self._control_component = ClyphXControlComponent(self)
     ClyphXCueComponent(self)
     self._startup_actions_complete = False
     self._user_variables = {}
     self._play_seq_clips = {}
     self._loop_seq_clips = {}
     self._current_tracks = []
     live = Live.Application.get_application()
     self._can_have_nested_devices = False
     if live.get_major_version() == 9 or (
             live.get_major_version() == 8 and
         (live.get_minor_version() > 2 or
          (live.get_minor_version() == 2
           and live.get_bugfix_version() >= 2))):
         self._can_have_nested_devices = True
     self.setup_tracks()
     self.log_message('nativeKONTROL LOG ------- ' + SCRIPT_NAME +
                      ' ------- Live Version: ' +
                      str(live.get_major_version()) + '.' +
                      str(live.get_minor_version()) + '.' +
                      str(live.get_bugfix_version()) + ' ------- END LOG')
     self.show_message(SCRIPT_NAME)
     self.set_suppress_rebuild_requests(False)
Beispiel #4
0
class ClyphX(ControlSurface):
    __module__ = __name__
    __doc__ = " ClyphX Main "
    
    def __init__(self, c_instance):
        ControlSurface.__init__(self, c_instance)
        self.set_suppress_rebuild_requests(True) 
        self._folder_location = '/ClyphX/'
        self._macrobat = Macrobat(self)
        self._extra_prefs = ExtraPrefs(self)
        self._track_actions = ClyphXTrackActions(self)
        self._snap_actions = ClyphXSnapActions(self)
        self._global_actions = ClyphXGlobalActions(self)
        self._device_actions = ClyphXDeviceActions(self)
        self._clip_actions = ClyphXClipActions(self)
        self._control_surface_actions = ClyphXControlSurfaceActions(self)
        self._control_component = ClyphXControlComponent(self)
        ClyphXCueComponent(self)
        self._startup_actions_complete = False
        self._user_variables = {}
        self._play_seq_clips = {}
        self._loop_seq_clips = {}
        self._current_tracks = []
        live = Live.Application.get_application()
        self._can_have_nested_devices = False
        if live.get_major_version() == 8 and live.get_minor_version() >= 2 and live.get_bugfix_version() >= 2:
            self._can_have_nested_devices = True
        self.setup_tracks()
        self.log_message('nativeKONTROL LOG ------- nativeKONTROL ClyphX v2.0.3 for Live 8 ------- Live Version: ' + str(live.get_major_version()) + '.' + str(live.get_minor_version()) + '.' + str(live.get_bugfix_version()) + ' ------- END LOG')
        self.show_message('nativeKONTROL ClyphX v2.0.3 for Live 8')
        self.set_suppress_rebuild_requests(False) 
        
        
    def action_dispatch(self, tracks, xclip, action_name, args, ident):
        """ Main dispatch for calling appropriate class of actions, passes all necessary arguments to class method """
        if tracks:
            if action_name.startswith('SNAP'):
                self._snap_actions.store_track_snapshot(tracks, xclip, ident, action_name, args) 
            elif action_name.startswith('SURFACE'):
                self._control_surface_actions.dispatch_cs_action(tracks[0], xclip, ident, action_name, args)
            elif action_name in GLOBAL_ACTIONS:
                getattr(self._global_actions, GLOBAL_ACTIONS[action_name])(tracks[0], xclip, ident, args)
            for t in tracks:            
                if action_name in TRACK_ACTIONS:
                    getattr(self._track_actions, TRACK_ACTIONS[action_name])(t, xclip, ident, args)
                elif action_name == 'LOOPER': 
                    if args and args.split()[0] in LOOPER_ACTIONS: 
                        getattr(self._device_actions, LOOPER_ACTIONS[args.split()[0]])(t, xclip, ident, args)
                    elif action_name in LOOPER_ACTIONS:
                        getattr(self._device_actions, LOOPER_ACTIONS[action_name])(t, xclip, ident, args)
                elif action_name.startswith('DEV'): 
                    device = self.get_device_to_operate_on(t, action_name)
                    if device:
                        if args and args.split()[0] in DEVICE_ACTIONS: 
                            getattr(self._device_actions, DEVICE_ACTIONS[args.split()[0]])(device, t, xclip, ident, args)
                        elif args and 'CHAIN' in args: 
                            self._device_actions.dispatch_chain_action(device, t, xclip, ident, args)
                        elif action_name.startswith('DEV'):
                            self._device_actions.set_device_on_off(device, t, xclip, ident, args)
                elif action_name.startswith('CLIP') and t in self.song().tracks:
                    clip = self.get_clip_to_operate_on(t, xclip, action_name)
                    if clip:
                        if args and args.split()[0] in CLIP_ACTIONS: 
                            getattr(self._clip_actions, CLIP_ACTIONS[args.split()[0]])(clip, t, xclip, ident, args.replace(args.split()[0], ''))
                        elif args and args.split()[0].startswith('NOTES'):
                            self._clip_actions.do_clip_note_action(clip, t, xclip, ident, args)
                        elif action_name.startswith('CLIP'):
                            self._clip_actions.set_clip_on_off(clip, t, xclip, ident, args)         
                                
    
    def handle_xclip_name(self, track, xclip): 
        """ Directly dispatches snapshot recall, X-Control overrides and Seq X-Clips.    Otherwise, seperates ident from action names, splits up lists of action names and calls action dispatch. """
        name = self.get_name(xclip.name)
        if name:
            if name[0] == '[' and ']' in name and ' || ' in name:
                self._snap_actions.recall_track_snapshot(name, xclip)
                return()
            if '[[' in name and ']]' in name:
                self._control_component.assign_new_actions(name)
                return()
            if name[0] == '[' and name.count('[') == 1 and name.count(']') == 1:
                ident = name[name.index('['):name.index(']')+1].strip() 
                xclip_name = name.replace(ident, '')
                xclip_name = xclip_name.strip()
                is_play_seq = False
                is_loop_seq = False
                if xclip_name == '':
                    return()
                if type(xclip) is Live.Clip.Clip and xclip_name[0] == '(' and '(PSEQ)' in xclip_name: 
                    is_play_seq = True
                    xclip_name = xclip_name.replace('(PSEQ)', '')
                elif type(xclip) is Live.Clip.Clip and xclip_name[0] == '(' and '(LSEQ)' in xclip_name: 
                    is_loop_seq = True
                    xclip_name = xclip_name.replace('(LSEQ)', '')
                action_list = []
                if ';' in xclip_name:
                    for name in xclip_name.split(';'): 
                        action_data = self.format_action_name(track, name.strip())
                        if action_data:
                            action_list.append(action_data)
                else:
                    action_data = self.format_action_name(track, xclip_name.strip())
                    if action_data:
                        action_list.append(action_data)
                if action_list:
                    if is_play_seq: 
                        self.handle_play_seq_action_list(action_list, xclip, ident)
                    elif is_loop_seq:
                        self._loop_seq_clips[xclip.name] = [ident, action_list]
                        self.handle_loop_seq_action_list(xclip, 0)
                    else:
                        for a in action_list:
                            self.action_dispatch(a['track'], xclip, a['action'], a['args'], ident)
                        
                        
    def format_action_name(self, origin_track, origin_name): 
        """ Replaces vars (if any) then splits up track, action name and arguments (if any) and returns dict """
        if '$' in origin_name:
            origin_name = self.handle_user_variables(origin_name)
            if origin_name == None:
                return()
        result_track = [origin_track]
        result_name = origin_name
        if len(origin_name) >= 4 and (('/' in origin_name[:4]) or ('-' in origin_name[:4] and '/' in origin_name[4:]) or (origin_name[0] == '"' and '"/' in origin_name[1:])):
            track_data = self.get_track_to_operate_on(origin_name)
            result_track = track_data[0]
            result_name = track_data[1]
        args = ''
        name = result_name.split()
        if len(name) > 1:
            args = result_name.replace(name[0], '', 1)
            result_name = result_name.replace(args, '')
        return {'track' : result_track, 'action' : result_name.strip(), 'args' : args.strip()}    
    
    
    def handle_loop_seq_action_list(self, xclip, count):
        """ Handles sequenced action lists, triggered by xclip looping """
        if self._loop_seq_clips.has_key(xclip.name):
            if count >= len(self._loop_seq_clips[xclip.name][1]):
                count = count - ((count / len(self._loop_seq_clips[xclip.name][1]))*len(self._loop_seq_clips[xclip.name][1]))
            action = self._loop_seq_clips[xclip.name][1][count]
            self.action_dispatch(action['track'], xclip, action['action'], action['args'], self._loop_seq_clips[xclip.name][0])
                        
                        
    def handle_play_seq_action_list(self, action_list, xclip, ident):
        """ Handles sequenced action lists, triggered by replaying the xclip """
        if self._play_seq_clips.has_key(xclip.name): 
            count = self._play_seq_clips[xclip.name][1] + 1
            if count > len(self._play_seq_clips[xclip.name][2])-1:
                count = 0
            self._play_seq_clips[xclip.name] = [ident, count, action_list]
        else:
            self._play_seq_clips[xclip.name] = [ident, 0, action_list]
        action = self._play_seq_clips[xclip.name][2][self._play_seq_clips[xclip.name][1]]
        self.action_dispatch(action['track'], xclip, action['action'], action['args'], ident)

        
    def do_parameter_adjustment(self, param, value):
        """" Adjust (</>, reset, random, set val) continuous params, also handles quantized param adjustment (should just use +1/-1 for those) """
        if not param.is_enabled:
            return()
        step = (param.max - param.min) / 127
        new_value = param.value
        if value.startswith(('<', '>')):
            factor = self.get_adjustment_factor(value)
            if not param.is_quantized:
                new_value += step * factor
            else:
                new_value += factor
        elif value == 'RESET' and not param.is_quantized:
            new_value = param.default_value
        elif value == 'RND' and not param.is_quantized:
            new_value = (Live.Application.get_random_int(0, 128) * step) + param.min
        else:
            try:
                if int(value) in range (128):
                    try: new_value = (int(value) * step) + param.min
                    except: new_value = param.value
            except: pass
        if new_value >= param.min and new_value <= param.max:
            param.value = new_value
            
            
    def get_adjustment_factor(self, string, as_float = False):
        """ Get factor for use with < > actions """
        factor = 1
        if len(string) > 1:
            if as_float:
                try: factor = float(string[1:])
                except: factor = 1
            else:
                try: factor = int(string[1:])
                except: factor = 1
        if string.startswith('<'): 
            factor = -(factor)
        return factor         
    
    
    def get_track_to_operate_on(self, origin_name):    
        """ Gets track or tracks to operate on """
        result_track = []
        tracks = list(self.song().tracks + self.song().return_tracks + (self.song().master_track,))
        track_range = []
        num = origin_name.split('/')[0]
        if num[0] == '"':
            track_name = num[1:num[1:].index('"') + 1]
            def_name = None
            if ' AUDIO' or ' MIDI' in track_name:
                def_name = track_name.replace(' ', '-')# In Live GUI, default names are 'n Audio' or 'n MIDI', in API it's 'n-Audio' or 'n-MIDI' 
            for track in self.song().tracks:
                name = self.get_name(track.name)
                if name == track_name or name == def_name:
                    result_track = [track]
        elif num == 'SEL':
            result_track = [self.song().view.selected_track]
        elif num == 'MST':
            result_track = [self.song().master_track]
        elif num == 'ALL':
            result_track = tracks
        elif 'GRP' in num:
            """ The form for Group tracks is GRP-"starttrackname"-"endtrackname" """
            self.log_message('GRP')
            start_track = num.split('-')[1].replace('"', '')
            end_track = num.split('-')[2].replace('"', '')
            self.log_message(start_track + ' ' + end_track)
            range_start = 0
            range_end = 0
            for i in range(len(tracks)):
                trackname = self.get_name(tracks[i].name)
                if trackname == start_track:
                    self.log_message(trackname + ' ' + str(i))
                    range_start = i
                if trackname == end_track:
                    self.log_message(trackname + ' ' + str(i))
                    range_end = i
                    break
            
            for t in range(range_start, range_end + 1):
                self.log_message(tracks[t].name + ' ' + str(t))
                result_track.append(tracks[t])
        elif '-' in num:
            t_range = num.split('-')
            try:
                for t in t_range:
                    t = t.strip()
                    if t == 'MST':
                        t = len(tracks)
                    else:
                        try: t = int(t) - 1
                        except: t = (ord(t.strip(' ')) - 65) + len(self.song().tracks)
                    track_range.append(t)
            except: pass
            if len(track_range) == 2 and track_range[0] >= 0 and track_range[1] > track_range[0]:
                for t in range(track_range[0], track_range[1] + 1):
                    if t < len(tracks):
                        result_track.append(tracks[t])
        
        else:
            try:
                try: track = self.song().tracks[(int(num))-1]
                except: track = self.song().return_tracks[(ord(num))-65]
                if track:
                    result_track = [track]
            except: pass
        result_name = origin_name[origin_name.index('/')+1:].strip()
        return (result_track, result_name)
    
    
    def get_device_to_operate_on(self, track, name):
        """ Get device to operate on """
        device = None
        name_split = name.split()
        if name_split[0] == 'DEV':
            device = track.view.selected_device
            if device == None:
                if track.devices:
                    device = track.devices[0]
        else:
            try:
                dev_num = name_split[0].replace('DEV', '')
                if '.' in dev_num and self._can_have_nested_devices:
                    dev_split = dev_num.split('.')
                    top_level = track.devices[int(dev_split[0]) - 1]
                    if top_level and top_level.can_have_chains:
                        device = top_level.chains[int(dev_split[1]) - 1].devices[0]
                        if len(dev_split) > 2:
                            device = top_level.chains[int(dev_split[1]) - 1].devices[int(dev_split[2]) - 1]
                else:
                    device = track.devices[int(dev_num) - 1]
            except: pass
        return device
    
    
    def get_clip_to_operate_on(self, track, xclip, name):
        """ Get clip to operate on """
        clip = None
        name_split = name.split()
        if name_split[0] == 'CLIP':
            slot = list(self.song().scenes).index(self.song().view.selected_scene)
            if track.playing_slot_index >= 0:
                slot = track.playing_slot_index
            if track.clip_slots[slot].has_clip:
                clip = track.clip_slots[slot].clip
        else:
            try:
                if track.clip_slots[int(name_split[0].replace('CLIP', ''))-1].has_clip:
                    clip = track.clip_slots[int(name_split[0].replace('CLIP', ''))-1].clip
            except: pass
        return clip
                        
    
    def handle_user_variables(self, name):
        """ Add/update entry in user variable dict or retrieves value of variable from dict """
        result = None
        if '=' in name:
            name_split = name.split('=')
            if len(name_split) == 2:
                var_name = name_split[0].replace('$', '')
                self._user_variables[str(var_name.strip())] = str(name_split[1].strip())
        else:
            var_name = name[name.index('$')+1:].split()[0]
            if self._user_variables.has_key(var_name):
                result = name.replace('$'+var_name, self._user_variables[var_name])
        return result
                
                
    def get_user_settings(self, midi_map_handle):
        """ Get user settings (variables, prefs and control settings) from text file and perform startup actions if any """
        list_to_build = None
        ctrl_data = []
        prefs_data = []
        try:
            for line in open(sys.path[1] + self._folder_location + 'UserSettings.txt'): 
                line = self.get_name(line.rstrip('\n'))
                if not line.startswith(('#', '"', 'STARTUP_ACTIONS =', 'INCLUDE_NESTED_DEVICES_IN_SNAPSHOTS =', 'SNAPSHOT_PARAMETER_LIMIT =')) and not line == '':
                    if '[USER CONTROLS]' in line:
                        list_to_build = 'controls'
                    elif '[USER VARIABLES]' in line:
                        list_to_build = 'vars'
                    elif '[EXTRA PREFS]' in line:
                        list_to_build = 'prefs'
                    else:
                        if list_to_build == 'vars' and '=' in line:
                            name_split = line.split('=')
                            if len(name_split) == 2:
                                var_name = name_split[0].replace('$', '')
                                self._user_variables[str(var_name.strip(' '))] = str(name_split[1].strip(' '))
                        elif list_to_build == 'controls' and '=' in line:
                            ctrl_data.append(line)
                        elif list_to_build == 'prefs' and '=' in line:
                            prefs_data.append(line)
                elif line.startswith('INCLUDE_NESTED_DEVICES_IN_SNAPSHOTS ='):
                    include_nested = self.get_name(line[37:].strip())
                    include_nested_devices = False
                    if include_nested.startswith('ON'):
                        include_nested_devices = True
                    self._snap_actions._include_nested_devices = include_nested_devices
                elif line.startswith('SNAPSHOT_PARAMETER_LIMIT ='):
                    try: limit = int(line[26:].strip())
                    except: limit = 500
                    self._snap_actions._parameter_limit = limit
                elif line.startswith('STARTUP_ACTIONS =') and not self._startup_actions_complete:
                    actions = line[17:].strip()
                    if actions != 'OFF':
                        action_list = '[]' + actions
                        self.schedule_message(2, self.perform_startup_actions, action_list)
                        self._startup_actions_complete = True
            if ctrl_data:
                self._control_component.get_user_control_settings(ctrl_data, midi_map_handle)
            if prefs_data:
                self._extra_prefs.get_user_settings(prefs_data)
        except: pass
            
            
    def perform_startup_actions(self, action_list):
        """ Delay startup action so it can perform actions on values that are changed upon set load (like overdub) """
        self.handle_xclip_name(self.song().view.selected_track, StartName(action_list))
            
            
    def setup_tracks(self):    
        """ Setup component tracks on ini and track list changes.    Also call Macrobat's get rack """
        for t in self.song().tracks:
            self._macrobat.setup_tracks(t)
            if (self._current_tracks and t in self._current_tracks):
                pass
            else:
                self._current_tracks.append(t)
                ClyphXTrackComponent(self, t)
        for r in self.song().return_tracks + (self.song().master_track,):
            self._macrobat.setup_tracks(r)
        self._snap_actions.setup_tracks()
            
    
    def get_name(self, name):
        """ Convert name to upper-case string or return blank string if couldn't be converted """
        try: name = str(name).upper()
        except: name = ''
        return name
            
    
    def _on_track_list_changed(self):
        ControlSurface._on_track_list_changed(self)
        self.setup_tracks()
        
        
    def connect_script_instances(self, instanciated_scripts):
        """ Pass connect scripts call to control component """
        self._control_component.connect_script_instances(instanciated_scripts) 
        self._control_surface_actions.connect_script_instances(instanciated_scripts) 
        
            
    def build_midi_map(self, midi_map_handle):
        """ Build user-defined list of midi messages for controlling ClyphX track """
        ControlSurface.build_midi_map(self, midi_map_handle)
        self.get_user_settings(midi_map_handle)
        
        
    def receive_midi(self, midi_bytes):
        """ Receive user-specified messages and send to control script """
        ControlSurface.receive_midi(self, midi_bytes)
        self._control_component.receive_midi(midi_bytes)
        
        
    def handle_sysex(self, midi_bytes):
        """ Handle sysex received from controller """
        pass    
        
    
    def disconnect(self):
        ControlSurface.disconnect(self)
        return None
Beispiel #5
0
class ClyphX(ControlSurface):
    __module__ = __name__
    __doc__ = " ClyphX Main for Live 8 "

    def __init__(self, c_instance):
        ControlSurface.__init__(self, c_instance)
        self._user_settings_logged = False
        self.set_suppress_rebuild_requests(True)
        self._is_debugging = False
        self._process_xclips_if_track_muted = True
        self._macrobat = Macrobat(self)
        self._extra_prefs = ExtraPrefs(self)
        self._track_actions = ClyphXTrackActions(self)
        self._snap_actions = ClyphXSnapActions(self)
        self._global_actions = ClyphXGlobalActions(self)
        self._device_actions = ClyphXDeviceActions(self)
        self._clip_actions = ClyphXClipActions(self)
        self._control_surface_actions = ClyphXControlSurfaceActions(self)
        self._user_actions = ClyphXUserActions(self)
        self._control_component = ClyphXControlComponent(self)
        ClyphXCueComponent(self)
        self._startup_actions_complete = False
        self._user_variables = {}
        self._play_seq_clips = {}
        self._loop_seq_clips = {}
        self._current_tracks = []
        live = Live.Application.get_application()
        self._can_have_nested_devices = False
        if live.get_major_version() == 9 or (
                live.get_major_version() == 8 and
            (live.get_minor_version() > 2 or
             (live.get_minor_version() == 2
              and live.get_bugfix_version() >= 2))):
            self._can_have_nested_devices = True
        self.setup_tracks()
        self.log_message('nativeKONTROL LOG ------- ' + SCRIPT_NAME +
                         ' ------- Live Version: ' +
                         str(live.get_major_version()) + '.' +
                         str(live.get_minor_version()) + '.' +
                         str(live.get_bugfix_version()) + ' ------- END LOG')
        self.show_message(SCRIPT_NAME)
        self.set_suppress_rebuild_requests(False)

    def disconnect(self):
        self._macrobat = None
        self._extra_prefs = None
        self._track_actions = None
        self._snap_actions = None
        self._global_actions = None
        self._device_actions = None
        self._clip_actions = None
        self._control_surface_actions = None
        self._user_actions = None
        self._control_component = None
        self._user_variables = {}
        self._play_seq_clips = {}
        self._loop_seq_clips = {}
        self._current_tracks = []
        ControlSurface.disconnect(self)

    def action_dispatch(self, tracks, xclip, action_name, args, ident):
        """ Main dispatch for calling appropriate class of actions, passes all necessary arguments to class method """
        if tracks:
            if action_name.startswith('SNAP'):
                self._snap_actions.store_track_snapshot(
                    tracks, xclip, ident, action_name, args)
            elif action_name.startswith('SURFACE') or action_name.startswith(
                    'CS'):
                self._control_surface_actions.dispatch_cs_action(
                    tracks[0], xclip, ident, action_name, args)
            elif action_name in GLOBAL_ACTIONS:
                getattr(self._global_actions,
                        GLOBAL_ACTIONS[action_name])(tracks[0], xclip, ident,
                                                     args)
            elif action_name == 'PSEQ' and args == 'RESET':
                for key, value in self._play_seq_clips.items():
                    value[1] = -1
            elif action_name == 'DEBUG':
                if type(xclip) is Live.Clip.Clip:
                    xclip.name = str(xclip.name).upper().replace(
                        'DEBUG', 'Debugging Activated')
                self.start_debugging()
            else:
                for t in tracks:
                    if action_name in TRACK_ACTIONS:
                        getattr(self._track_actions,
                                TRACK_ACTIONS[action_name])(t, xclip, ident,
                                                            args)
                    elif action_name == 'LOOPER':
                        if args and args.split()[0] in LOOPER_ACTIONS:
                            getattr(self._device_actions,
                                    LOOPER_ACTIONS[args.split()[0]])(t, xclip,
                                                                     ident,
                                                                     args)
                        elif action_name in LOOPER_ACTIONS:
                            getattr(self._device_actions,
                                    LOOPER_ACTIONS[action_name])(t, xclip,
                                                                 ident, args)
                    elif action_name.startswith('DEV'):
                        device_action = self.get_device_to_operate_on(
                            t, action_name, args)
                        device_args = None
                        if device_action[0]:
                            if len(device_action) > 1:
                                device_args = device_action[1]
                            if device_args and device_args.split(
                            )[0] in DEVICE_ACTIONS:
                                getattr(
                                    self._device_actions,
                                    DEVICE_ACTIONS[device_args.split()[0]])(
                                        device_action[0], t, xclip, ident,
                                        device_args)
                            elif device_args and 'CHAIN' in device_args:
                                self._device_actions.dispatch_chain_action(
                                    device_action[0], t, xclip, ident,
                                    device_args)
                            elif action_name.startswith('DEV'):
                                self._device_actions.set_device_on_off(
                                    device_action[0], t, xclip, ident,
                                    device_args)
                    elif action_name.startswith(
                            'CLIP') and t in self.song().tracks:
                        clip_action = self.get_clip_to_operate_on(
                            t, action_name, args)
                        clip_args = None
                        if clip_action[0]:
                            if len(clip_action) > 1:
                                clip_args = clip_action[1]
                            if clip_args and clip_args.split(
                            )[0] in CLIP_ACTIONS:
                                getattr(self._clip_actions,
                                        CLIP_ACTIONS[clip_args.split()[0]])(
                                            clip_action[0], t, xclip, ident,
                                            clip_args.replace(
                                                clip_args.split()[0], ''))
                            elif clip_args and clip_args.split()[0].startswith(
                                    'NOTES'):
                                self._clip_actions.do_clip_note_action(
                                    clip_action[0], t, xclip, ident, args)
                            elif action_name.startswith('CLIP'):
                                self._clip_actions.set_clip_on_off(
                                    clip_action[0], t, xclip, ident, args)
                    elif action_name in self._user_actions._action_dict:
                        getattr(self._user_actions,
                                self._user_actions._action_dict[action_name])(
                                    t, args)
        if self._is_debugging:
            self.log_message('action_dispatch triggered, ident=' + str(ident) +
                             ' and track(s)=' +
                             str(self.track_list_to_string(tracks)) +
                             ' and action=' + str(action_name) + ' and args=' +
                             str(args))

    def handle_xclip_name(self, track, xclip):
        """ This is just here for backwards compatibility (primarily with MapEase ClyphX Strip and ClyphX XT) and shouldn't be used if possible. """
        self.handle_action_list_trigger(track, xclip)

    def handle_m4l_trigger(self, name):
        """ Convenience method for triggering actions from M4L by simply supplying an action name. """
        self.handle_action_list_trigger(self.song().view.selected_track,
                                        ActionList('[]' + name))

    def handle_action_list_trigger(self, track, xtrigger):
        """ Directly dispatches snapshot recall, X-Control overrides and Seq X-Clips.  Otherwise, seperates ident from action names, splits up lists of action names and calls action dispatch. """
        if self._is_debugging:
            self.log_message('---')
        name = None
        if xtrigger is not None:
            name = self.get_name(xtrigger.name).strip()
        if name and name[0] == '[' and ']' in name:
            # Snap action, so pass directly to snap component
            if ' || (' in name and type(
                    xtrigger) is Live.Clip.Clip and xtrigger.is_playing:
                self._snap_actions.recall_track_snapshot(name, xtrigger)
            # Control reassignment, so pass directly to control component
            elif '[[' in name and ']]' in name:
                self._control_component.assign_new_actions(name)
            # Standard trigger
            else:
                ident = name[name.index('['):name.index(']') + 1].strip()
                raw_action_list = name.replace(ident, '', 1).strip()
                if raw_action_list == '':
                    return
                is_play_seq = False
                is_loop_seq = False

                # X-Clips can have on and off action lists, the following handles this
                if type(xtrigger) is Live.Clip.Clip:
                    raw_action_list = self.get_xclip_action_list(
                        xtrigger, raw_action_list)
                    if not raw_action_list:
                        return

                # Check if the trigger is a PSEQ (accessible to any type of X-Trigger)
                if raw_action_list[0] == '(' and '(PSEQ)' in raw_action_list:
                    is_play_seq = True
                    raw_action_list = raw_action_list.replace('(PSEQ)',
                                                              '').strip()

                # Check if the trigger is a LSEQ (accessible only to X-Clips)
                elif type(xtrigger) is Live.Clip.Clip and raw_action_list[
                        0] == '(' and '(LSEQ)' in raw_action_list:
                    is_loop_seq = True
                    raw_action_list = raw_action_list.replace('(LSEQ)',
                                                              '').strip()

                # Build formatted action list
                formatted_action_list = []
                for action in raw_action_list.split(';'):
                    action_data = self.format_action_name(
                        track, action.strip())
                    if action_data:
                        formatted_action_list.append(action_data)

                # If seq, pass to appropriate function, else call action dispatch for each action in the formatted action list
                if formatted_action_list:
                    if is_play_seq:
                        self.handle_play_seq_action_list(
                            formatted_action_list, xtrigger, ident)
                    elif is_loop_seq:
                        self._loop_seq_clips[xtrigger.name] = [
                            ident, formatted_action_list
                        ]
                        self.handle_loop_seq_action_list(xtrigger, 0)
                    else:
                        for action in formatted_action_list:
                            self.action_dispatch(action['track'], xtrigger,
                                                 action['action'],
                                                 action['args'], ident)
                            if self._is_debugging:
                                self.log_message(
                                    'handle_action_list_trigger triggered, ident='
                                    + str(ident) + ' and track(s)=' + str(
                                        self.track_list_to_string(
                                            action['track'])) +
                                    ' and action=' + str(action['action']) +
                                    ' and args=' + str(action['args']))

    def get_xclip_action_list(self, xclip, full_action_list):
        """ Get the action list to perform. X-Clips can have an on and off action list seperated by a comma. This will return which action list to perform 
	based on whether the clip is playing. If the clip is not playing and there is no off action, this returns None. """
        result = None
        split_list = full_action_list.split(',')
        if xclip.is_playing:
            result = split_list[0]
        else:
            if len(split_list) == 2:
                if split_list[1].strip() == '*':
                    result = split_list[0]
                else:
                    result = split_list[1]
        if self._is_debugging:
            self.log_message('get_xclip_action_list returning ' + str(result))
        return result

    def replace_user_variables(self, string_with_vars):
        """ Replace any user variables in the given string with the value the variable represents. """
        while '%' in string_with_vars:
            var_name = string_with_vars[string_with_vars.index('%') + 1:]
            if '%' in var_name:
                var_name = var_name[0:var_name.index('%')]
                string_with_vars = string_with_vars.replace(
                    '%' + var_name + '%',
                    self.get_user_variable_value(var_name), 1)
            else:
                string_with_vars = string_with_vars.replace('%', '', 1)
        if '$' in string_with_vars:  # For compat with old-style variables
            for string in string_with_vars.split():
                if '$' in string and not '=' in string:
                    var_name = string.replace('$', '')
                    string_with_vars = string_with_vars.replace(
                        '$' + var_name, self.get_user_variable_value(var_name),
                        1)
        if self._is_debugging:
            self.log_message('replace_user_variables returning ' +
                             str(string_with_vars))
        return string_with_vars

    def get_user_variable_value(self, var_name):
        """ Get the value of the given variable name or 0 if var name not found. """
        result = '0'
        if self._user_variables.has_key(var_name):
            result = self._user_variables[var_name]
        if self._is_debugging:
            self.log_message('get_user_variable_value returning ' +
                             str(var_name) + '=' + str(result))
        return result

    def handle_user_variable_assignment(self, string_with_assign):
        """ Handle assigning new value to variable with either assignment or expression enclosed in parens. """
        string_with_assign = string_with_assign.replace(
            '$', '')  # For compat with old-style variables
        var_data = string_with_assign.split('=')
        if len(var_data) >= 2 and not ';' in var_data[
                1] and not '%' in var_data[1] and not '=' in var_data[1]:
            if '(' in var_data[1] and ')' in var_data[1]:
                try:
                    self._user_variables[var_data[0].strip()] = str(
                        eval(var_data[1].strip()))
                except:
                    pass
            else:
                self._user_variables[var_data[0].strip()] = var_data[1].strip()
            if self._is_debugging:
                self.log_message('handle_user_variable_assignment, ' +
                                 str(var_data[0].strip()) + '=' +
                                 str(var_data[1].strip()))

    def format_action_name(self, origin_track, origin_name):
        """ Replaces vars (if any) then splits up track, action name and arguments (if any) and returns dict """
        result_name = self.replace_user_variables(origin_name)
        if '=' in result_name:
            self.handle_user_variable_assignment(result_name)
            return
        result_track = [origin_track]
        if len(result_name) >= 4 and (
            ('/' in result_name[:4]) or
            ('-' in result_name[:4] and '/' in result_name[4:]) or
            (result_name[0] == '"' and '"' in result_name[1:])):
            track_data = self.get_track_to_operate_on(result_name)
            result_track = track_data[0]
            result_name = track_data[1]
        args = ''
        name = result_name.split()
        if len(name) > 1:
            args = result_name.replace(name[0], '', 1)
            result_name = result_name.replace(args, '')
        if self._is_debugging:
            self.log_message('format_action_name returning, track(s)=' +
                             str(self.track_list_to_string(result_track)) +
                             ' and action=' + str(result_name.strip()) +
                             ' and args=' + str(args.strip()))
        return {
            'track': result_track,
            'action': result_name.strip(),
            'args': args.strip()
        }

    def handle_loop_seq_action_list(self, xclip, count):
        """ Handles sequenced action lists, triggered by xclip looping """
        if self._loop_seq_clips.has_key(xclip.name):
            if count >= len(self._loop_seq_clips[xclip.name][1]):
                count = count - (
                    (count / len(self._loop_seq_clips[xclip.name][1])) *
                    len(self._loop_seq_clips[xclip.name][1]))
            action = self._loop_seq_clips[xclip.name][1][count]
            self.action_dispatch(action['track'], xclip, action['action'],
                                 action['args'],
                                 self._loop_seq_clips[xclip.name][0])
            if self._is_debugging:
                self.log_message(
                    'handle_loop_seq_action_list triggered, xclip.name=' +
                    str(xclip.name) + ' and track(s)=' +
                    str(self.track_list_to_string(action['track'])) +
                    ' and action=' + str(action['action']) + ' and args=' +
                    str(action['args']))

    def handle_play_seq_action_list(self, action_list, xclip, ident):
        """ Handles sequenced action lists, triggered by replaying/retriggering the xtrigger """
        if self._play_seq_clips.has_key(xclip.name):
            count = self._play_seq_clips[xclip.name][1] + 1
            if count > len(self._play_seq_clips[xclip.name][2]) - 1:
                count = 0
            self._play_seq_clips[xclip.name] = [ident, count, action_list]
        else:
            self._play_seq_clips[xclip.name] = [ident, 0, action_list]
        action = self._play_seq_clips[xclip.name][2][self._play_seq_clips[
            xclip.name][1]]
        self.action_dispatch(action['track'], xclip, action['action'],
                             action['args'], ident)
        if self._is_debugging:
            self.log_message('handle_play_seq_action_list triggered, ident=' +
                             str(ident) + ' and track(s)=' +
                             str(self.track_list_to_string(action['track'])) +
                             ' and action=' + str(action['action']) +
                             ' and args=' + str(action['args']))

    def do_parameter_adjustment(self, param, value):
        """" Adjust (</>, reset, random, set val) continuous params, also handles quantized param adjustment (should just use +1/-1 for those) """
        if not param.is_enabled:
            return ()
        step = (param.max - param.min) / 127
        new_value = param.value
        if value.startswith(('<', '>')):
            factor = self.get_adjustment_factor(value)
            if not param.is_quantized:
                new_value += step * factor
            else:
                new_value += factor
        elif value == 'RESET' and not param.is_quantized:
            new_value = param.default_value
        elif 'RND' in value and not param.is_quantized:
            rnd_min = 0
            rnd_max = 128
            if value != 'RND' and '-' in value:
                rnd_range_data = value.replace('RND', '').split('-')
                if len(rnd_range_data) == 2:
                    new_min = 0
                    new_max = 128
                    try:
                        new_min = int(rnd_range_data[0])
                    except:
                        new_min = 0
                    try:
                        new_max = int(rnd_range_data[1]) + 1
                    except:
                        new_max = 128
                    if new_min in range(0, 129) and new_max in range(
                            0, 129) and new_min < new_max:
                        rnd_min = new_min
                        rnd_max = new_max
            rnd_value = (Live.Application.get_random_int(0, 128) *
                         (rnd_max - rnd_min) / 127) + rnd_min
            new_value = (rnd_value * step) + param.min

        else:
            try:
                if int(value) in range(128):
                    try:
                        new_value = (int(value) * step) + param.min
                    except:
                        new_value = param.value
            except:
                pass
        if new_value >= param.min and new_value <= param.max:
            param.value = new_value
            if self._is_debugging:
                self.log_message('do_parameter_adjustment called on ' +
                                 str(param.name) + ', set value to ' +
                                 str(new_value))

    def get_adjustment_factor(self, string, as_float=False):
        """ Get factor for use with < > actions """
        factor = 1
        if len(string) > 1:
            if as_float:
                try:
                    factor = float(string[1:])
                except:
                    factor = 1
            else:
                try:
                    factor = int(string[1:])
                except:
                    factor = 1
        if string.startswith('<'):
            factor = -(factor)
        if self._is_debugging:
            self.log_message('get_adjustment_factor returning factor=' +
                             str(factor))
        return factor

    def get_track_to_operate_on(self, origin_name):
        """ Gets track or tracks to operate on """
        result_tracks = []
        result_name = origin_name
        if '/' in origin_name:
            tracks = list(
                tuple(self.song().tracks) + tuple(self.song().return_tracks) +
                (self.song().master_track, ))
            sel_track_index = tracks.index(self.song().view.selected_track)
            if (origin_name.index('/') > 0):
                track_spec = origin_name.split('/')[0].strip()
                if '"' in track_spec:
                    track_spec = self.get_track_index_by_name(
                        track_spec, tracks)
                if 'SEL' in track_spec:
                    track_spec = track_spec.replace('SEL',
                                                    str(sel_track_index + 1),
                                                    1)
                if 'MST' in track_spec:
                    track_spec = track_spec.replace('MST', str(len(tracks)), 1)
                if track_spec == 'ALL':
                    result_tracks = tracks
                else:
                    track_range_spec = track_spec.split('-')
                    if len(track_range_spec) <= 2:
                        track_range = []
                        try:
                            for spec in track_range_spec:
                                track_index = -1
                                if spec.startswith(('<', '>')):
                                    try:
                                        track_index = self.get_adjustment_factor(
                                            spec) + sel_track_index
                                    except:
                                        pass
                                else:
                                    try:
                                        track_index = int(spec) - 1
                                    except:
                                        track_index = (ord(spec) - 65) + len(
                                            self.song().tracks)
                                if track_index in range(len(tracks)):
                                    track_range.append(track_index)
                        except:
                            track_range = []
                        if track_range:
                            if len(track_range) == 2:
                                if (track_range[0] < track_range[1]):
                                    for index in range(track_range[0],
                                                       track_range[1] + 1):
                                        result_tracks.append(tracks[index])
                            else:
                                result_tracks = [tracks[track_range[0]]]
            result_name = origin_name[origin_name.index('/') + 1:].strip()
        if self._is_debugging:
            self.log_message(
                'get_track_to_operate_on returning result_tracks=' +
                str(self.track_list_to_string(result_tracks)) +
                ' and result_name=' + str(result_name))
        return (result_tracks, result_name)

    def get_track_index_by_name(self, name, tracks):
        """ Gets the index(es) associated with the track name(s) specified in name. """
        while '"' in name:
            track_name = name[name.index('"') + 1:]
            if '"' in track_name:
                track_name = track_name[0:track_name.index('"')]
                track_index = ''
                def_name = ''
                if ' AUDIO' or ' MIDI' in track_name:
                    def_name = track_name.replace(
                        ' ', '-'
                    )  # In Live GUI, default names are 'n Audio' or 'n MIDI', in API it's 'n-Audio' or 'n-MIDI'
                for track in tracks:
                    current_track_name = self.get_name(track.name)
                    if current_track_name == track_name or current_track_name == def_name:
                        track_index = str(tracks.index(track) + 1)
                        break
                name = name.replace('"' + track_name + '"', track_index, 1)
                name = name.replace('"' + def_name + '"', track_index, 1)
            else:
                name = name.replace('"', '', 1)
        return name

    def get_device_to_operate_on(self, track, action_name, args):
        """ Get device to operate on and action to perform with args """
        device = None
        device_args = args
        if 'DEV"' in action_name:
            dev_name = action_name[action_name.index('"') + 1:]
            if '"' in args:
                dev_name = action_name[action_name.index('"') +
                                       1:] + ' ' + args
                device_args = args[args.index('"') + 1:].strip()
            if '"' in dev_name:
                dev_name = dev_name[0:dev_name.index('"')]
            for dev in track.devices:
                if dev.name.upper() == dev_name:
                    device = dev
                    break
        else:
            if action_name == 'DEV':
                device = track.view.selected_device
                if device == None:
                    if track.devices:
                        device = track.devices[0]
            else:
                try:
                    dev_num = action_name.replace('DEV', '')
                    if '.' in dev_num and self._can_have_nested_devices:
                        dev_split = dev_num.split('.')
                        top_level = track.devices[int(dev_split[0]) - 1]
                        if top_level and top_level.can_have_chains:
                            device = top_level.chains[int(dev_split[1]) -
                                                      1].devices[0]
                            if len(dev_split) > 2:
                                device = top_level.chains[
                                    int(dev_split[1]) -
                                    1].devices[int(dev_split[2]) - 1]
                    else:
                        device = track.devices[int(dev_num) - 1]
                except:
                    pass
        if self._is_debugging:
            debug_string = 'None'
            if device:
                debug_string = device.name
            self.log_message('get_device_to_operate_on returning device=' +
                             str(debug_string) + ' and device args=' +
                             str(device_args))
        return (device, device_args)

    def get_clip_to_operate_on(self, track, action_name, args):
        """ Get clip to operate on and action to perform with args """
        clip = None
        clip_args = args
        if 'CLIP"' in action_name:
            clip_name = action_name[action_name.index('"') + 1:]
            if '"' in args:
                clip_name = action_name[action_name.index('"') +
                                        1:] + ' ' + args
                clip_args = args[args.index('"') + 1:].strip()
            if '"' in clip_name:
                clip_name = clip_name[0:clip_name.index('"')]
            for slot in track.clip_slots:
                if slot.has_clip and slot.clip.name.upper() == clip_name:
                    clip = slot.clip
                    break
        else:
            sel_slot_idx = list(self.song().scenes).index(
                self.song().view.selected_scene)
            slot_idx = sel_slot_idx
            if action_name == 'CLIP':
                if track.playing_slot_index >= 0:
                    slot_idx = track.playing_slot_index
            elif action_name == 'CLIPSEL':
                if self.application().view.is_view_visible('Arranger'):
                    clip = self.song().view.detail_clip
            else:
                try:
                    slot_idx = int(action_name.replace('CLIP', '')) - 1
                except:
                    slot_idx = sel_slot_idx
            if clip == None and track.clip_slots[slot_idx].has_clip:
                clip = track.clip_slots[slot_idx].clip
        if self._is_debugging:
            debug_string = 'None'
            if clip:
                debug_string = clip.name
            self.log_message('get_clip_to_operate_on returning clip=' +
                             str(debug_string) + ' and clip args=' +
                             str(clip_args))
        return (clip, clip_args)

    def get_user_settings(self, midi_map_handle):
        """ Get user settings (variables, prefs and control settings) from text file and perform startup actions if any """
        list_to_build = None
        ctrl_data = []
        prefs_data = []
        try:
            mrs_path = ''
            for path in sys.path:
                if 'MIDI Remote Scripts' in path:
                    mrs_path = path
                    break
            user_file = mrs_path + FOLDER + 'UserSettings.txt'
            if not self._user_settings_logged:
                self.log_message(
                    ' ------- Attempting to read UserSettings file: ' +
                    user_file + '------- ')
            for line in open(user_file):
                line = self.get_name(line.rstrip('\n'))
                if not line.startswith(
                    ('#', '"', '*')) and not line.strip() == '':
                    if not self._user_settings_logged:
                        self.log_message(str(line))
                if not line.startswith((
                        '#',
                        '"',
                        'STARTUP_ACTIONS =',
                        'INCLUDE_NESTED_DEVICES_IN_SNAPSHOTS =',
                        'SNAPSHOT_PARAMETER_LIMIT =',
                        'PROCESS_XCLIPS_',
                )) and not line == '':
                    if '[USER CONTROLS]' in line:
                        list_to_build = 'controls'
                    elif '[USER VARIABLES]' in line:
                        list_to_build = 'vars'
                    elif '[EXTRA PREFS]' in line:
                        list_to_build = 'prefs'
                    else:
                        if list_to_build == 'vars' and '=' in line:
                            line = self.replace_user_variables(line)
                            self.handle_user_variable_assignment(line)
                        elif list_to_build == 'controls' and '=' in line:
                            ctrl_data.append(line)
                        elif list_to_build == 'prefs' and '=' in line:
                            prefs_data.append(line)
                elif line.startswith('INCLUDE_NESTED_DEVICES_IN_SNAPSHOTS ='):
                    include_nested = self.get_name(line[37:].strip())
                    include_nested_devices = False
                    if include_nested.startswith('ON'):
                        include_nested_devices = True
                    self._snap_actions._include_nested_devices = include_nested_devices
                elif line.startswith('SNAPSHOT_PARAMETER_LIMIT ='):
                    try:
                        limit = int(line[26:].strip())
                    except:
                        limit = 500
                    self._snap_actions._parameter_limit = limit
                elif line.startswith('PROCESS_XCLIPS_IF_TRACK_MUTED ='):
                    self._process_xclips_if_track_muted = line.split(
                        '=')[1].strip() == 'TRUE'
                elif line.startswith('STARTUP_ACTIONS ='
                                     ) and not self._startup_actions_complete:
                    actions = line[17:].strip()
                    if actions != 'OFF':
                        action_list = '[]' + actions
                        self.schedule_message(2, self.perform_startup_actions,
                                              action_list)
                        self._startup_actions_complete = True
            if ctrl_data:
                self._control_component.get_user_control_settings(
                    ctrl_data, midi_map_handle)
            if prefs_data:
                self._extra_prefs.get_user_settings(prefs_data)
        except:
            pass

    def start_debugging(self):
        """ Turn on debugging and write all user vars/controls/actions to Live's log file to assist in troubleshooting. """
        if not self._is_debugging:
            self._is_debugging = True
            self.log_message(
                '------- ClyphX Log: Logging User Variables -------')
            for key, value in self._user_variables.items():
                self.log_message(str(key) + '=' + str(value))
            self.log_message(
                '------- ClyphX Log: Logging User Controls -------')
            for key, value in self._control_component._control_list.items():
                self.log_message(
                    str(key) + ' on_action=' + str(value['on_action']) +
                    ' and off_action=' + str(value['off_action']))
            self.log_message(
                '------- ClyphX Log: Logging User Actions -------')
            for key, value in self._user_actions._action_dict.items():
                self.log_message(str(key) + '=' + str(value))
            self.log_message('------- ClyphX Log: Debugging Started -------')

    def track_list_to_string(self, track_list):
        """ Convert list of tracks to a string of track names or None if no tracks. This is used for debugging. """
        result = 'None'
        if track_list:
            result = '['
            for track in track_list:
                result += track.name + ', '
            result = result[:len(result) - 2]
        return result + ']'

    def perform_startup_actions(self, action_list):
        """ Delay startup action so it can perform actions on values that are changed upon set load (like overdub) """
        self.handle_action_list_trigger(self.song().view.selected_track,
                                        ActionList(action_list))

    def setup_tracks(self):
        """ Setup component tracks on ini and track list changes.  Also call Macrobat's get rack """
        for t in self.song().tracks:
            self._macrobat.setup_tracks(t)
            if (self._current_tracks and t in self._current_tracks):
                pass
            else:
                self._current_tracks.append(t)
                ClyphXTrackComponent(self, t)
        for r in tuple(
                self.song().return_tracks) + (self.song().master_track, ):
            self._macrobat.setup_tracks(r)
        self._snap_actions.setup_tracks()

    def get_name(self, name):
        """ Convert name to upper-case string or return blank string if couldn't be converted """
        try:
            name = str(name).upper()
        except:
            name = ''
        return name

    def _on_track_list_changed(self):
        ControlSurface._on_track_list_changed(self)
        self.setup_tracks()

    def connect_script_instances(self, instanciated_scripts):
        """ Pass connect scripts call to control component """
        self._control_component.connect_script_instances(instanciated_scripts)
        self._control_surface_actions.connect_script_instances(
            instanciated_scripts)

    def build_midi_map(self, midi_map_handle):
        """ Build user-defined list of midi messages for controlling ClyphX track """
        ControlSurface.build_midi_map(self, midi_map_handle)
        if self._user_settings_logged:
            self._control_component.rebuild_control_map(midi_map_handle)
        else:
            self.get_user_settings(midi_map_handle)
            self._user_settings_logged = True

    def receive_midi(self, midi_bytes):
        """ Receive user-specified messages and send to control script """
        ControlSurface.receive_midi(self, midi_bytes)
        self._control_component.receive_midi(midi_bytes)

    def handle_sysex(self, midi_bytes):
        """ Handle sysex received from controller """
        pass

    def handle_nonsysex(self, midi_bytes):
        """ Override so that ControlSurface doesn't report this to Log.txt """
        pass
Beispiel #6
0
class ClyphX(ControlSurface):
    __module__ = __name__
    __doc__ = " ClyphX Main "

    def __init__(self, c_instance):
        ControlSurface.__init__(self, c_instance)
        self.set_suppress_rebuild_requests(True)
        self._folder_location = '/ClyphX/'
        self._macrobat = Macrobat(self)
        self._extra_prefs = ExtraPrefs(self)
        self._track_actions = ClyphXTrackActions(self)
        self._snap_actions = ClyphXSnapActions(self)
        self._global_actions = ClyphXGlobalActions(self)
        self._device_actions = ClyphXDeviceActions(self)
        self._clip_actions = ClyphXClipActions(self)
        self._control_surface_actions = ClyphXControlSurfaceActions(self)
        self._control_component = ClyphXControlComponent(self)
        ClyphXCueComponent(self)
        self._startup_actions_complete = False
        self._user_variables = {}
        self._play_seq_clips = {}
        self._loop_seq_clips = {}
        self._current_tracks = []
        live = Live.Application.get_application()
        self._can_have_nested_devices = False
        if live.get_major_version() == 8 and live.get_minor_version(
        ) >= 2 and live.get_bugfix_version() >= 2:
            self._can_have_nested_devices = True
        self.setup_tracks()
        self.log_message(
            'nativeKONTROL LOG ------- nativeKONTROL ClyphX v2.0.3 for Live 8 ------- Live Version: '
            + str(live.get_major_version()) + '.' +
            str(live.get_minor_version()) + '.' +
            str(live.get_bugfix_version()) + ' ------- END LOG')
        self.show_message('nativeKONTROL ClyphX v2.0.3 for Live 8')
        self.set_suppress_rebuild_requests(False)

    def action_dispatch(self, tracks, xclip, action_name, args, ident):
        """ Main dispatch for calling appropriate class of actions, passes all necessary arguments to class method """
        if tracks:
            if action_name.startswith('SNAP'):
                self._snap_actions.store_track_snapshot(
                    tracks, xclip, ident, action_name, args)
            elif action_name.startswith('SURFACE'):
                self._control_surface_actions.dispatch_cs_action(
                    tracks[0], xclip, ident, action_name, args)
            elif action_name in GLOBAL_ACTIONS:
                getattr(self._global_actions,
                        GLOBAL_ACTIONS[action_name])(tracks[0], xclip, ident,
                                                     args)
            for t in tracks:
                if action_name in TRACK_ACTIONS:
                    getattr(self._track_actions,
                            TRACK_ACTIONS[action_name])(t, xclip, ident, args)
                elif action_name == 'LOOPER':
                    if args and args.split()[0] in LOOPER_ACTIONS:
                        getattr(self._device_actions,
                                LOOPER_ACTIONS[args.split()[0]])(t, xclip,
                                                                 ident, args)
                    elif action_name in LOOPER_ACTIONS:
                        getattr(self._device_actions,
                                LOOPER_ACTIONS[action_name])(t, xclip, ident,
                                                             args)
                elif action_name.startswith('DEV'):
                    device = self.get_device_to_operate_on(t, action_name)
                    if device:
                        if args and args.split()[0] in DEVICE_ACTIONS:
                            getattr(self._device_actions,
                                    DEVICE_ACTIONS[args.split()[0]])(device, t,
                                                                     xclip,
                                                                     ident,
                                                                     args)
                        elif args and 'CHAIN' in args:
                            self._device_actions.dispatch_chain_action(
                                device, t, xclip, ident, args)
                        elif action_name.startswith('DEV'):
                            self._device_actions.set_device_on_off(
                                device, t, xclip, ident, args)
                elif action_name.startswith(
                        'CLIP') and t in self.song().tracks:
                    clip = self.get_clip_to_operate_on(t, xclip, action_name)
                    if clip:
                        if args and args.split()[0] in CLIP_ACTIONS:
                            getattr(self._clip_actions,
                                    CLIP_ACTIONS[args.split()[0]])(
                                        clip, t, xclip, ident,
                                        args.replace(args.split()[0], ''))
                        elif args and args.split()[0].startswith('NOTES'):
                            self._clip_actions.do_clip_note_action(
                                clip, t, xclip, ident, args)
                        elif action_name.startswith('CLIP'):
                            self._clip_actions.set_clip_on_off(
                                clip, t, xclip, ident, args)

    def handle_xclip_name(self, track, xclip):
        """ Directly dispatches snapshot recall, X-Control overrides and Seq X-Clips.    Otherwise, seperates ident from action names, splits up lists of action names and calls action dispatch. """
        name = self.get_name(xclip.name)
        if name:
            if name[0] == '[' and ']' in name and ' || ' in name:
                self._snap_actions.recall_track_snapshot(name, xclip)
                return ()
            if '[[' in name and ']]' in name:
                self._control_component.assign_new_actions(name)
                return ()
            if name[0] == '[' and name.count('[') == 1 and name.count(
                    ']') == 1:
                ident = name[name.index('['):name.index(']') + 1].strip()
                xclip_name = name.replace(ident, '')
                xclip_name = xclip_name.strip()
                is_play_seq = False
                is_loop_seq = False
                if xclip_name == '':
                    return ()
                if type(xclip) is Live.Clip.Clip and xclip_name[
                        0] == '(' and '(PSEQ)' in xclip_name:
                    is_play_seq = True
                    xclip_name = xclip_name.replace('(PSEQ)', '')
                elif type(xclip) is Live.Clip.Clip and xclip_name[
                        0] == '(' and '(LSEQ)' in xclip_name:
                    is_loop_seq = True
                    xclip_name = xclip_name.replace('(LSEQ)', '')
                action_list = []
                if ';' in xclip_name:
                    for name in xclip_name.split(';'):
                        action_data = self.format_action_name(
                            track, name.strip())
                        if action_data:
                            action_list.append(action_data)
                else:
                    action_data = self.format_action_name(
                        track, xclip_name.strip())
                    if action_data:
                        action_list.append(action_data)
                if action_list:
                    if is_play_seq:
                        self.handle_play_seq_action_list(
                            action_list, xclip, ident)
                    elif is_loop_seq:
                        self._loop_seq_clips[xclip.name] = [ident, action_list]
                        self.handle_loop_seq_action_list(xclip, 0)
                    else:
                        for a in action_list:
                            self.action_dispatch(a['track'], xclip,
                                                 a['action'], a['args'], ident)

    def format_action_name(self, origin_track, origin_name):
        """ Replaces vars (if any) then splits up track, action name and arguments (if any) and returns dict """
        if '$' in origin_name:
            origin_name = self.handle_user_variables(origin_name)
            if origin_name == None:
                return ()
        result_track = [origin_track]
        result_name = origin_name
        if len(origin_name) >= 4 and (
            ('/' in origin_name[:4]) or
            ('-' in origin_name[:4] and '/' in origin_name[4:]) or
            (origin_name[0] == '"' and '"/' in origin_name[1:])):
            track_data = self.get_track_to_operate_on(origin_name)
            result_track = track_data[0]
            result_name = track_data[1]
        args = ''
        name = result_name.split()
        if len(name) > 1:
            args = result_name.replace(name[0], '', 1)
            result_name = result_name.replace(args, '')
        return {
            'track': result_track,
            'action': result_name.strip(),
            'args': args.strip()
        }

    def handle_loop_seq_action_list(self, xclip, count):
        """ Handles sequenced action lists, triggered by xclip looping """
        if self._loop_seq_clips.has_key(xclip.name):
            if count >= len(self._loop_seq_clips[xclip.name][1]):
                count = count - (
                    (count / len(self._loop_seq_clips[xclip.name][1])) *
                    len(self._loop_seq_clips[xclip.name][1]))
            action = self._loop_seq_clips[xclip.name][1][count]
            self.action_dispatch(action['track'], xclip, action['action'],
                                 action['args'],
                                 self._loop_seq_clips[xclip.name][0])

    def handle_play_seq_action_list(self, action_list, xclip, ident):
        """ Handles sequenced action lists, triggered by replaying the xclip """
        if self._play_seq_clips.has_key(xclip.name):
            count = self._play_seq_clips[xclip.name][1] + 1
            if count > len(self._play_seq_clips[xclip.name][2]) - 1:
                count = 0
            self._play_seq_clips[xclip.name] = [ident, count, action_list]
        else:
            self._play_seq_clips[xclip.name] = [ident, 0, action_list]
        action = self._play_seq_clips[xclip.name][2][self._play_seq_clips[
            xclip.name][1]]
        self.action_dispatch(action['track'], xclip, action['action'],
                             action['args'], ident)

    def do_parameter_adjustment(self, param, value):
        """" Adjust (</>, reset, random, set val) continuous params, also handles quantized param adjustment (should just use +1/-1 for those) """
        if not param.is_enabled:
            return ()
        step = (param.max - param.min) / 127
        new_value = param.value
        if value.startswith(('<', '>')):
            factor = self.get_adjustment_factor(value)
            if not param.is_quantized:
                new_value += step * factor
            else:
                new_value += factor
        elif value == 'RESET' and not param.is_quantized:
            new_value = param.default_value
        elif value == 'RND' and not param.is_quantized:
            new_value = (Live.Application.get_random_int(0, 128) *
                         step) + param.min
        else:
            try:
                if int(value) in range(128):
                    try:
                        new_value = (int(value) * step) + param.min
                    except:
                        new_value = param.value
            except:
                pass
        if new_value >= param.min and new_value <= param.max:
            param.value = new_value

    def get_adjustment_factor(self, string, as_float=False):
        """ Get factor for use with < > actions """
        factor = 1
        if len(string) > 1:
            if as_float:
                try:
                    factor = float(string[1:])
                except:
                    factor = 1
            else:
                try:
                    factor = int(string[1:])
                except:
                    factor = 1
        if string.startswith('<'):
            factor = -(factor)
        return factor

    def get_track_to_operate_on(self, origin_name):
        """ Gets track or tracks to operate on """
        result_track = []
        tracks = list(self.song().tracks + self.song().return_tracks +
                      (self.song().master_track, ))
        track_range = []
        num = origin_name.split('/')[0]
        if num[0] == '"':
            track_name = num[1:num[1:].index('"') + 1]
            def_name = None
            if ' AUDIO' or ' MIDI' in track_name:
                def_name = track_name.replace(
                    ' ', '-'
                )  # In Live GUI, default names are 'n Audio' or 'n MIDI', in API it's 'n-Audio' or 'n-MIDI'
            for track in self.song().tracks:
                name = self.get_name(track.name)
                if name == track_name or name == def_name:
                    result_track = [track]
        elif num == 'SEL':
            result_track = [self.song().view.selected_track]
        elif num == 'MST':
            result_track = [self.song().master_track]
        elif num == 'ALL':
            result_track = tracks
        elif 'GRP' in num:
            """ The form for Group tracks is GRP-"starttrackname"-"endtrackname" """
            self.log_message('GRP')
            start_track = num.split('-')[1].replace('"', '')
            end_track = num.split('-')[2].replace('"', '')
            self.log_message(start_track + ' ' + end_track)
            range_start = 0
            range_end = 0
            for i in range(len(tracks)):
                trackname = self.get_name(tracks[i].name)
                if trackname == start_track:
                    self.log_message(trackname + ' ' + str(i))
                    range_start = i
                if trackname == end_track:
                    self.log_message(trackname + ' ' + str(i))
                    range_end = i
                    break

            for t in range(range_start, range_end + 1):
                self.log_message(tracks[t].name + ' ' + str(t))
                result_track.append(tracks[t])
        elif '-' in num:
            t_range = num.split('-')
            try:
                for t in t_range:
                    t = t.strip()
                    if t == 'MST':
                        t = len(tracks)
                    else:
                        try:
                            t = int(t) - 1
                        except:
                            t = (ord(t.strip(' ')) - 65) + len(
                                self.song().tracks)
                    track_range.append(t)
            except:
                pass
            if len(track_range) == 2 and track_range[0] >= 0 and track_range[
                    1] > track_range[0]:
                for t in range(track_range[0], track_range[1] + 1):
                    if t < len(tracks):
                        result_track.append(tracks[t])

        else:
            try:
                try:
                    track = self.song().tracks[(int(num)) - 1]
                except:
                    track = self.song().return_tracks[(ord(num)) - 65]
                if track:
                    result_track = [track]
            except:
                pass
        result_name = origin_name[origin_name.index('/') + 1:].strip()
        return (result_track, result_name)

    def get_device_to_operate_on(self, track, name):
        """ Get device to operate on """
        device = None
        name_split = name.split()
        if name_split[0] == 'DEV':
            device = track.view.selected_device
            if device == None:
                if track.devices:
                    device = track.devices[0]
        else:
            try:
                dev_num = name_split[0].replace('DEV', '')
                if '.' in dev_num and self._can_have_nested_devices:
                    dev_split = dev_num.split('.')
                    top_level = track.devices[int(dev_split[0]) - 1]
                    if top_level and top_level.can_have_chains:
                        device = top_level.chains[int(dev_split[1]) -
                                                  1].devices[0]
                        if len(dev_split) > 2:
                            device = top_level.chains[
                                int(dev_split[1]) -
                                1].devices[int(dev_split[2]) - 1]
                else:
                    device = track.devices[int(dev_num) - 1]
            except:
                pass
        return device

    def get_clip_to_operate_on(self, track, xclip, name):
        """ Get clip to operate on """
        clip = None
        name_split = name.split()
        if name_split[0] == 'CLIP':
            slot = list(self.song().scenes).index(
                self.song().view.selected_scene)
            if track.playing_slot_index >= 0:
                slot = track.playing_slot_index
            if track.clip_slots[slot].has_clip:
                clip = track.clip_slots[slot].clip
        else:
            try:
                if track.clip_slots[int(name_split[0].replace('CLIP', '')) -
                                    1].has_clip:
                    clip = track.clip_slots[
                        int(name_split[0].replace('CLIP', '')) - 1].clip
            except:
                pass
        return clip

    def handle_user_variables(self, name):
        """ Add/update entry in user variable dict or retrieves value of variable from dict """
        result = None
        if '=' in name:
            name_split = name.split('=')
            if len(name_split) == 2:
                var_name = name_split[0].replace('$', '')
                self._user_variables[str(var_name.strip())] = str(
                    name_split[1].strip())
        else:
            var_name = name[name.index('$') + 1:].split()[0]
            if self._user_variables.has_key(var_name):
                result = name.replace('$' + var_name,
                                      self._user_variables[var_name])
        return result

    def get_user_settings(self, midi_map_handle):
        """ Get user settings (variables, prefs and control settings) from text file and perform startup actions if any """
        list_to_build = None
        ctrl_data = []
        prefs_data = []
        try:
            for line in open(sys.path[1] + self._folder_location +
                             'UserSettings.txt'):
                line = self.get_name(line.rstrip('\n'))
                if not line.startswith(
                    ('#', '"', 'STARTUP_ACTIONS =',
                     'INCLUDE_NESTED_DEVICES_IN_SNAPSHOTS =',
                     'SNAPSHOT_PARAMETER_LIMIT =')) and not line == '':
                    if '[USER CONTROLS]' in line:
                        list_to_build = 'controls'
                    elif '[USER VARIABLES]' in line:
                        list_to_build = 'vars'
                    elif '[EXTRA PREFS]' in line:
                        list_to_build = 'prefs'
                    else:
                        if list_to_build == 'vars' and '=' in line:
                            name_split = line.split('=')
                            if len(name_split) == 2:
                                var_name = name_split[0].replace('$', '')
                                self._user_variables[str(
                                    var_name.strip(' '))] = str(
                                        name_split[1].strip(' '))
                        elif list_to_build == 'controls' and '=' in line:
                            ctrl_data.append(line)
                        elif list_to_build == 'prefs' and '=' in line:
                            prefs_data.append(line)
                elif line.startswith('INCLUDE_NESTED_DEVICES_IN_SNAPSHOTS ='):
                    include_nested = self.get_name(line[37:].strip())
                    include_nested_devices = False
                    if include_nested.startswith('ON'):
                        include_nested_devices = True
                    self._snap_actions._include_nested_devices = include_nested_devices
                elif line.startswith('SNAPSHOT_PARAMETER_LIMIT ='):
                    try:
                        limit = int(line[26:].strip())
                    except:
                        limit = 500
                    self._snap_actions._parameter_limit = limit
                elif line.startswith('STARTUP_ACTIONS ='
                                     ) and not self._startup_actions_complete:
                    actions = line[17:].strip()
                    if actions != 'OFF':
                        action_list = '[]' + actions
                        self.schedule_message(2, self.perform_startup_actions,
                                              action_list)
                        self._startup_actions_complete = True
            if ctrl_data:
                self._control_component.get_user_control_settings(
                    ctrl_data, midi_map_handle)
            if prefs_data:
                self._extra_prefs.get_user_settings(prefs_data)
        except:
            pass

    def perform_startup_actions(self, action_list):
        """ Delay startup action so it can perform actions on values that are changed upon set load (like overdub) """
        self.handle_xclip_name(self.song().view.selected_track,
                               StartName(action_list))

    def setup_tracks(self):
        """ Setup component tracks on ini and track list changes.    Also call Macrobat's get rack """
        for t in self.song().tracks:
            self._macrobat.setup_tracks(t)
            if (self._current_tracks and t in self._current_tracks):
                pass
            else:
                self._current_tracks.append(t)
                ClyphXTrackComponent(self, t)
        for r in self.song().return_tracks + (self.song().master_track, ):
            self._macrobat.setup_tracks(r)
        self._snap_actions.setup_tracks()

    def get_name(self, name):
        """ Convert name to upper-case string or return blank string if couldn't be converted """
        try:
            name = str(name).upper()
        except:
            name = ''
        return name

    def _on_track_list_changed(self):
        ControlSurface._on_track_list_changed(self)
        self.setup_tracks()

    def connect_script_instances(self, instanciated_scripts):
        """ Pass connect scripts call to control component """
        self._control_component.connect_script_instances(instanciated_scripts)
        self._control_surface_actions.connect_script_instances(
            instanciated_scripts)

    def build_midi_map(self, midi_map_handle):
        """ Build user-defined list of midi messages for controlling ClyphX track """
        ControlSurface.build_midi_map(self, midi_map_handle)
        self.get_user_settings(midi_map_handle)

    def receive_midi(self, midi_bytes):
        """ Receive user-specified messages and send to control script """
        ControlSurface.receive_midi(self, midi_bytes)
        self._control_component.receive_midi(midi_bytes)

    def handle_sysex(self, midi_bytes):
        """ Handle sysex received from controller """
        pass

    def disconnect(self):
        ControlSurface.disconnect(self)
        return None