class BCRXL(ControlSurface, IsotonikCommon):
    def __init__(self, c_instance):
        self._dynamic = True
        self._LOG_BCL = False
        self._preset = 0
        self._use_32 = False
        self._enable_function = False
        self._track_navigation = False
        self._swap_arm = False
        self._direct_bank = False
        self._relative = False
        self._bit_14 = False
        self._fold_enabled = True
        self._can_fold = False
        self._show_returns = 0
        self._max_returns = -1
        self._returns_toggled = False
        self._has_pans = False
        self._track_bank_size = 1
        self._show_master = 0
        self._show_selected = 0
        self._button_row_1 = 'select'
        self._button_row_2 = 'mute'
        self._button_row_3 = 'solo'
        self._button_reselect_track = 'fold'
        self._channel_strip = False
        self._toggle_1 = False
        self._toggle_2 = False
        self._toggle_3 = False
        self._toggle_4 = False
        self._has_strip_device_enable = True
        self._USB = True
        self._has_transport = False
        self._showing_selected = False
        self._x_offset = 0
        self._y_offset = 0
        self._track_left = 113
        self._track_right = 114
        self._device_toggle = 109
        self._track_fold = 110
        self._refresh = 111
        self._device_lock = 110
        self._bank_up = 111
        self._bank_down = 112
        self._toggle_returns = False
        self._user_custom = False
        IsotonikCommon.__init__(self, parent=self, c_instance=c_instance)
        self._init(path.dirname(path.realpath(__file__)), 'BCR_XL', 24)
        super(BCRXL, self).__init__(c_instance)
        with self.component_guard():
            self._startup()

    def disconnect(self):
        if self._dynamic:
            pass
        self._shutdown()
        super(BCRXL, self).disconnect()

    def _sigabort_create(self):
        if self._show_returns == 3 or self._show_returns == 4:
            self._fold_enabled = False
            self._toggle_returns = True
        if not self._enable_function:
            self._direct_bank = False
            self._has_transport = False
        if self._channel_strip:
            self._fold_enabled = False
        self._user_custom = self._enable_function and not self._direct_bank and not self._has_transport
        self._bcr_controls = BCL(self, self._preset, self._relative,
                                 self._bit_14)
        self._device_selection_follows_track_selection = True
        self._default_skin = make_default_skin()
        with inject(skin=const(self._default_skin)).everywhere():
            self._create_controls()
        self._set_offsets_task = self._tasks.add(
            Task.sequence(Task.wait(0), Task.run(self._set_session_offsets)))
        self._set_offsets_task.kill()
        if self._show_selected:
            self._show_selected_task = self._tasks.add(
                Task.sequence(Task.wait(1),
                              Task.run(self._show_selected_track)))
        return False

    def _enable_components(self):
        with self.component_guard():
            for component in self.components:
                self.log('Enable: ' + component.name)
                component.set_enabled(True)

    def set_session_offsets(self, x, y):
        self.log('bcrxl: set_session_offsets: ' + str(x) + ',' + str(y))
        self._scene = y
        self._x_offset = x
        self._y_offset = y
        self._set_offsets_task.restart()

    def _set_session_offsets(self):
        self._set_offsets_task.kill()
        self.log('_set_session_offsets')
        self._session.set_offsets(self._x_offset, self._y_offset)

    def _show_controlled_tracks_message(self, session):
        track_offset = session.track_offset()
        self.log('_show_controlled_tracks_message: ' + str(track_offset) +
                 ', ' + str(self._scene))
        self._set_session_highlight(track_offset, self._scene, 8, 1, False)

    def _set_session_highlight(self, track_offset, scene_offset, width, height,
                               include_return_tracks):
        self.log('_set_session_highlight: ' + str(track_offset) + ', ' +
                 str(scene_offset) + ', ' + str(width) + ', ' + str(height) +
                 ', ' + str(include_return_tracks))
        super(BCRXL,
              self)._set_session_highlight(track_offset, scene_offset, width,
                                           height, include_return_tracks)

    def _on_selected_scene_changed(self):
        if self._box_follows_scene:
            self._scene = list(self.song().scenes).index(
                self.song().view.selected_scene)
            self._show_controlled_tracks_message(
                self._on_session_offset_changed.subject)
            self.log('_on_selected_scene_changed: index: ' + str(self._scene))
        super(BCRXL, self)._on_selected_scene_changed()

    def _show_selected_track(self):
        self.log('_show_selected_track: ' + str(self._showing_selected) +
                 ', index: ' + str(self._mixer._selected_track_index))
        self._showing_selected = not self._showing_selected
        volume_cc = 0 if self._bit_14 else 1
        if self._mixer._selected_track_index != -1:
            curr_val = self._mixer._channel_strips[
                self._mixer.
                _selected_track_index]._track.mixer_device.volume.value
            self._do_send_midi(
                (176 + MIXER_MODE,
                 volume_cc + self._mixer._selected_track_index,
                 int(math.floor(curr_val *
                                127.0)) if self._showing_selected else 0))
        self._show_selected_task = self._tasks.add(
            Task.sequence(Task.delay(2), Task.run(self._show_selected_track)))

    def on_identified(self):
        self.log('on_identified')
        self._identified = True
        if hasattr(self, 'default_session'):
            self.default_session.set_show_highlight(True)
            self._set_session_highlight(0, 0, 8, 1, False)
        super(BCRXL, self).on_identified()

    def _create_controls(self):
        self.log(
            '_create_controls: dynamic: ' + str(self._dynamic) +
            ', function: ' + str(self._enable_function), True)
        if not self._dynamic:
            self._track_left = 105
            self._track_right = 106
            self._device_toggle = 107
        elif not self._enable_function:
            self._device_toggle = 105
            self._track_fold = 106
            self._device_lock = 106
            self._bank_up = 107
            self._bank_down = 108
        self.log('left: ' + str(self._track_left) + ', right: ' +
                 str(self._track_right) + ', toggle: ' +
                 str(self._device_toggle) + ', user_custom: ' +
                 str(self._user_custom))
        if self._dynamic:
            if self._user_custom:
                self._bcr_controls.connect(self._toggle_1, self._toggle_2,
                                           self._toggle_3, self._toggle_4)
            else:
                self._bcr_controls.connect()
        self._modes = ModesComponent()
        self._create_our_controls()
        self._create_mixer()
        self._create_session()
        self._create_device()
        self._create_transport()

    def log(self, msg, force=False):
        if self._LOG or force:
            if isinstance(msg, str):
                logger.info(msg)
            else:
                logger.error('**++** Invalid log msg: ' + str(type(msg)) +
                             ': ' + IsotonikCommon.repr3(msg))

    def _create_our_controls(self):
        self.log('_create_our_controls')

        def make_button(identifier,
                        name,
                        midi_type=MIDI_CC_TYPE,
                        skin=self._default_skin):
            control = ButtonElement(False,
                                    midi_type,
                                    MIXER_MODE,
                                    identifier,
                                    name=name,
                                    skin=skin)
            return control

        def make_button_list(identifiers, name, midi_type=MIDI_NOTE_TYPE):
            return [
                make_button(identifier, name % (i + 1), midi_type,
                            self._default_skin)
                for i, identifier in enumerate(identifiers)
            ]

        def make_encoder(identifier, name, bit_14=True):
            if self._bit_14:
                self.log('make_encoder: ' + name + ', CC: ' + str(identifier) +
                         ', 14_bit: ' + str(bit_14))
                control = EncoderElement(
                    MIDI_CC_TYPE,
                    MIXER_MODE,
                    identifier,
                    Live.MidiMap.MapMode.absolute_14_bit
                    if bit_14 else Live.MidiMap.MapMode.absolute,
                    name=name)
            else:
                control = EncoderElement(
                    MIDI_CC_TYPE,
                    MIXER_MODE,
                    identifier,
                    Live.MidiMap.MapMode.absolute if not self._relative else
                    Live.MidiMap.MapMode.relative_two_compliment,
                    name=name)
            return control

        self._top_buttons = ButtonMatrixElement(rows=[
            make_button_list(chain(xrange(65, 73)), 'BCR_Top_Buttons_%d',
                             MIDI_CC_TYPE)
        ])
        self._bottom_buttons = ButtonMatrixElement(rows=[
            make_button_list(chain(xrange(73, 81)), 'BCR_Bottom_Buttons_%d',
                             MIDI_CC_TYPE)
        ])
        self._strip_enable_buttons = ButtonMatrixElement(rows=[
            make_button_list(chain(xrange(73, 81)), 'BCR_Bottom_Buttons_%d',
                             MIDI_CC_TYPE)
        ])
        if self._direct_bank or self._has_transport:
            self._direct_bank_buttons = ButtonMatrixElement(rows=[
                make_button_list(chain(xrange(105, 109)),
                                 'BCR_User_Buttons_%d', MIDI_CC_TYPE)
            ])
        self._left_button = make_button(self._track_left, 'Track_Left',
                                        MIDI_CC_TYPE)
        self._right_button = make_button(self._track_right, 'Track_Right',
                                         MIDI_CC_TYPE)
        self._device_engage_button = make_button(self._device_toggle,
                                                 'XXL_Pan_Device_Mode')
        self._track_fold_button = make_button(self._track_fold, 'Track_Fold',
                                              MIDI_CC_TYPE)
        if self._fold_enabled or self._toggle_returns or self._channel_strip:
            self._on_track_fold.subject = self._track_fold_button
            self._track_fold_button.enabled = False
        self._bank_up_button = make_button(self._bank_up, 'Device_Bank_Up',
                                           MIDI_CC_TYPE)
        self._bank_down_button = make_button(self._bank_down,
                                             'Device_Bank_Down', MIDI_CC_TYPE)
        if self._bit_14:
            volume_cc = 0
            sends_cc = 8
            pans_cc = 81
            select_cc = 89
        else:
            volume_cc = 1
            sends_cc = 81
            pans_cc = 9
            select_cc = 33
        self._volume_encoders = ButtonMatrixElement(rows=[[
            make_encoder(volume_cc + i, 'Volume_%d' % (i + 1))
            for i in xrange(8)
        ]])
        if self._has_pans:
            self._pan_encoders = ButtonMatrixElement(rows=[[
                make_encoder(pans_cc + i, 'Pan_%d' % (i + 1), bit_14=False)
                for i in xrange(8)
            ]])
        self._send_encoders = ButtonMatrixElement(
            rows=[[
                make_encoder(sends_cc + i, 'Send_0_%d' % (i + 1))
                for i in xrange(8)
            ],
                  [
                      make_encoder(sends_cc + 8 + i, 'Send_1_%d' % (i + 1))
                      for i in xrange(8)
                  ],
                  [
                      make_encoder(sends_cc + 16 + i, 'Send_2_%d' % (i + 1))
                      for i in xrange(8)
                  ]])
        self._push_buttons = ButtonMatrixElement(rows=[
            make_button_list(chain(xrange(select_cc, select_cc + 8)),
                             'BCR_Track_Select_%d', MIDI_CC_TYPE)
        ])

    def _do_send_midi(self, midi_bytes):
        is_sysex = midi_bytes[0] == 240
        if self._LOG_MIDI and not is_sysex:
            self.log('_do_send_midi: ' + str(midi_bytes))
        if self._LOG_SYSEX and is_sysex:
            self.log('_do_send_midi: ' + str(midi_bytes))
        super(BCRXL, self)._do_send_midi(midi_bytes)

    def receive_midi(self, midi_bytes):
        is_sysex = midi_bytes[0] == 240
        send_off = False
        midi_id = 176 + MIXER_MODE
        adjusted = False
        if self._LOG_MIDI and not is_sysex:
            self.log('receive_midi: ' + str(midi_bytes) + ', id: ' +
                     str(midi_id))
        if self._LOG_SYSEX and is_sysex:
            self.log('receive_midi: ' + str(midi_bytes))
        if midi_bytes[0] == midi_id:
            if midi_bytes[2] == 0:
                if self._has_transport:
                    if midi_bytes[1] in (107, 108):
                        midi_bytes = (midi_id, midi_bytes[1], 127)
                        adjusted = True
                    elif midi_bytes[1] == 106:
                        midi_bytes = (midi_id, midi_bytes[1], 127)
                        self._do_send_midi(midi_bytes)
                        adjusted = True
                if not self._device_mode:
                    if midi_bytes[1] in pad_identifiers:
                        midi_bytes = (midi_id, midi_bytes[1], 127)
                        adjusted = True
                if midi_bytes[1] in [self._track_left, self._track_right]:
                    midi_bytes = (midi_id, midi_bytes[1], 127)
                    adjusted = True
        if self._LOG_MIDI and adjusted:
            self.log('receive_midi(adjusted): ' + str(midi_bytes) +
                     ', send_off: ' + str(send_off))
        if send_off:
            midi_bytes_new = (midi_id, midi_bytes[1], 0)
            if self._LOG_MIDI:
                self.log('send_off:receive_midi: ' + str(midi_bytes_new), True)
            super(BCRXL, self).receive_midi(midi_bytes_new)
        super(BCRXL, self).receive_midi(midi_bytes)

    def _create_user(self):
        def make_control_button(identifier, name, channel=0, is_pad=False):
            button = ButtonElement(True,
                                   MIDI_NOTE_TYPE if is_pad else MIDI_CC_TYPE,
                                   channel, identifier)
            button.name = name
            button.set_on_off_values(127, 0)
            return button

        def make_control_encoder(identifier, name, channel=0):
            encoder = EncoderElement(
                MIDI_CC_TYPE, channel, identifier,
                Live.MidiMap.MapMode.absolute if not self._relative else
                Live.MidiMap.MapMode.relative_two_compliment)
            encoder.reset = nop
            encoder.set_feedback_delay(-1)
            encoder.name = name
            return encoder

        def make_all_encoders(name_prefix='',
                              make_encoder=make_control_encoder):
            return ([
                make_encoder(13 + index, name_prefix + '_' + str(index) + '_0')
                for index in xrange(8)
            ], [
                make_encoder(80 + index, name_prefix + '_' + str(index) + '_1')
                for index in xrange(8)
            ], [
                make_encoder(88 + index, name_prefix + '_' + str(index) + '_2')
                for index in xrange(8)
            ], [
                make_encoder(96 + index, name_prefix + '_' + str(index) + '_3')
                for index in xrange(8)
            ])

        make_button = partial(make_control_button, channel=USER_MODE)
        make_encoder = partial(make_control_encoder, channel=USER_MODE)
        encoders_row_1, encoders_row_2, encoders_row_3, encoders_row_4 = make_all_encoders(
            'User_Encoder', make_encoder)
        buttons_0 = [
            make_button(pad_identifiers[i],
                        'User_Button_Matrix_' + str(i) + '_0',
                        is_pad=True) for i in xrange(8)
        ]
        buttons_1 = [
            make_button(pad_identifiers[i + 8],
                        'User_Button_Matrix_' + str(i) + '_1',
                        is_pad=True) for i in xrange(8)
        ]
        self._matrix = ButtonMatrixElement()
        self._matrix.name = 'User_Button_Matrix'
        self._matrix.add_row(tuple(buttons_0))
        self._matrix.add_row(tuple(buttons_1))
        self.request_rebuild_midi_map()

    def on_selected_track_changed(self):
        self._selected_track = self.song().view.selected_track
        if hasattr(self, '_mixer'):
            if self._show_selected and self._mixer._last_selected_track_index != -1:
                volume_cc = 0 if self._bit_14 else 1
                curr_val = self._mixer._channel_strips[
                    self._mixer.
                    _last_selected_track_index]._track.mixer_device.volume.value
                self.log(
                    'reset value for track: ' +
                    str(self._mixer._last_selected_track_index) + ' to ' +
                    str(curr_val), True)
                self._do_send_midi(
                    (176 + MIXER_MODE,
                     volume_cc + self._mixer._last_selected_track_index,
                     int(math.floor(curr_val * 127.0))))
        if self._fold_enabled:
            can_fold = False
            if self._selected_track != None:
                can_fold = self._selected_track.is_foldable
            self._can_fold = can_fold
            self.log('on_selected_track_changed: can_fold: ' + str(can_fold))
            self._track_fold_button.enabled = can_fold
            self._track_fold_button.send_value(127 if can_fold else 0)
        if hasattr(self, '_device'):
            self._device.on_selected_track_changed()

    @subject_slot('value')
    def _on_track_fold(self, value):
        self.log('_on_track_fold: ' + str(value) + ', fold_enabled: ' +
                 str(self._fold_enabled) + ', can_fold: ' +
                 str(self._can_fold) + ', toggle_returns: ' +
                 str(self._toggle_returns) + ', channel_strip: ' +
                 str(self._channel_strip))
        if self._channel_strip and self._modes.selected_mode != 'device':
            self.log('process channel_strip')
            if value:
                self.device_mode.layer = None
                self._on_device_engage(127, True)
                self._device.enable_channel_strip(True, True)
                self._on_strip_up.subject = self._bank_down_button
                self._on_strip_down.subject = self._bank_up_button
                if self._has_strip_device_enable:
                    self._on_strip_enable.replace_subjects(
                        self._strip_enable_buttons)
                self._update_strip_bank_buttons()
                self._update_strip_enable_buttons()
            else:
                self.device_mode.layer = Layer(
                    device_mode_button=self._device_engage_button)
                self._device.enable_channel_strip(False, True)
                self._on_device_engage(0, True)
                self._on_strip_up.subject = None
                self._on_strip_down.subject = None
                if self._has_strip_device_enable:
                    self._on_strip_enable.replace_subjects([])
        elif self._fold_enabled:
            self.log('process fold')
            self._track_fold_button.send_value(127 if self._can_fold else 0)
            if self._selected_track:
                self.log('can_fold: ' + str(self._selected_track.is_foldable))
                if self._selected_track.is_foldable:
                    self.log('currently folded: ' +
                             str(self._selected_track.fold_state))
                    self._selected_track.fold_state = not self._selected_track.fold_state
        elif self._toggle_returns:
            self.log('process toggle returns')
            self._returns_toggled = value == 127
            self.log('returns_toggled: ' + str(self._returns_toggled))
            self._mixer._reassign_tracks(1 if self._returns_toggled else 0)

    @subject_slot('value')
    def _on_strip_up(self, value):
        self.log('_on_strip_up', True)
        self._device._do_strip_up()
        self._update_strip_bank_buttons()

    @subject_slot('value')
    def _on_strip_down(self, value):
        self.log('_on_strip_down', True)
        self._device._do_strip_down()
        self._update_strip_bank_buttons()

    @subject_slot_group('value')
    def _on_strip_enable(self, value, button):
        index = list(self._strip_enable_buttons).index(button)
        self.log('_on_strip_enable: ' + str(index) + ', ' + str(value), True)
        self._device._do_strip_enable(index)

    def _update_strip_bank_buttons(self):
        self._bank_up_button.send_value(
            127 if self._device._channel_strip_bank != 0 else 0)
        self._bank_down_button.send_value(
            127 if self._device._channel_strip_bank != 2 else 0)

    def _update_strip_enable_buttons(self):
        for i in range(0, len(self._device._iso_rack_enable)):
            if self._strip_enable_buttons[i] is not None:
                on = self._device._iso_rack_enable[
                    i] and self._device._iso_rack_enable[i].value
                self._strip_enable_buttons[i].send_value(127 if on else 0)

    @subject_slot('value')
    def _on_skip_racks(self, value):
        if value == 127:
            self._device.toggle_enable_or_select()

    def _release_parameters(self, controls):
        if controls != None:
            for control in controls:
                if control != None:
                    control.release_parameter()

    def _create_transport(self):
        self.log('_create_transport: ' + str(self._has_transport), True)
        if not self._has_transport:
            return
        self._transport = TransportComponent()
        self._transport.set_stop_button(self._direct_bank_buttons[0])
        self._transport.set_play_button(self._direct_bank_buttons[1])
        self._transport.set_record_button(self._direct_bank_buttons[2])
        self._transport.set_overdub_button(self._direct_bank_buttons[3])

    def _create_session(self):
        self.log('_create_session', True)
        self._session = SessionComponent(self,
                                         name='BCR2K_Session',
                                         num_tracks=NUM_TRACKS,
                                         track_bank_size=self._track_bank_size,
                                         is_enabled=True,
                                         auto_name=True)
        self._session.layer = Layer(track_bank_left_button=self._left_button,
                                    track_bank_right_button=self._right_button)
        self._session.set_mixer(self._mixer)
        self._session._setup_controls()
        self._on_session_offset_changed.subject = self._session
        self._session.set_show_highlight(True)
        self._set_session_highlight(0, 0, 8, 1, False)

    @subject_slot('offset')
    def _on_session_offset_changed(self):
        self.log('_on_session_offset_changed')
        session = self._on_session_offset_changed.subject
        self._show_controlled_tracks_message(session)
        if hasattr(self._device, '_assign_channel_strip'
                   ) and self._device._assign_channel_strip:
            self._device._on_session_offset_changed()

    def _create_mixer(self):
        self.log('_create_mixer', True)
        mixer = MixerComponent(self,
                               NUM_TRACKS,
                               3,
                               show_returns=self._show_returns,
                               max_returns=self._max_returns,
                               show_master=self._show_master,
                               delayed_update=True,
                               is_enabled=True,
                               auto_name=True)
        layer = Layer(volume_controls=self._volume_encoders,
                      send_controls=self._send_encoders,
                      next_sends_button=self._bank_down_button,
                      prev_sends_button=self._bank_up_button)
        valid_opts = self._get_button_options()
        layer += Layer(**valid_opts)
        mixer.layer = layer
        self._mixer = mixer
        self._bcr_controls.setup_mixer_controls()
        self._modes.add_mode('mixer', [AddLayerMode(self._mixer, layer)])
        self._modes.selected_mode = 'mixer'

    def _get_button_options(self, channel_strip=False):
        opts = {}
        use_bottom_row_for_channel_strip = self._has_strip_device_enable
        if not use_bottom_row_for_channel_strip:
            channel_strip = False
        if self._has_pans:
            opts['pan_controls'] = self._pan_encoders
        opts[
            'track_select_buttons'] = self._push_buttons if self._button_row_1 == 'select' else (
                self._top_buttons if self._button_row_2 == 'select' else
                (self._bottom_buttons if self._button_row_3 == 'select'
                 and not channel_strip else None))
        opts[
            'mute_buttons'] = self._push_buttons if self._button_row_1 == 'mute' else (
                self._top_buttons if self._button_row_2 == 'mute' else
                (self._bottom_buttons if self._button_row_3 == 'mute'
                 and not channel_strip else None))
        opts[
            'solo_buttons'] = self._push_buttons if self._button_row_1 == 'solo' else (
                self._top_buttons if self._button_row_2 == 'solo' else
                (self._bottom_buttons if self._button_row_3 == 'solo'
                 and not channel_strip else None))
        opts[
            'arm_buttons'] = self._push_buttons if self._button_row_1 == 'arm' else (
                self._top_buttons if self._button_row_2 == 'arm' else
                (self._bottom_buttons if self._button_row_3 == 'arm'
                 and not channel_strip else None))
        valid_opts = {}
        for k, v in opts.iteritems():
            if v is not None:
                valid_opts[k] = v

        return valid_opts

    def _on_device_engage(self, value, channel_strip=False):
        self.log('_on_device_engage: ' + str(value) + ', channel_strip: ' +
                 str(channel_strip))
        if value == 127:
            if not channel_strip:
                self._session.set_mixer(None)
                self._session.layer = None
            self.set_device_component(self._device)
            self._device_mode = True
            self._bcr_controls._mode = 1
            self._modes.selected_mode = 'device_strip' if channel_strip else 'device'
            if self._use_32:
                self._mixer.set_volume_controls(None)
        else:
            self._device_mode = False
            self._bcr_controls._mode = 0
            self._bcr_controls.setup_mixer_controls(-1, -1, True, -1)
            if not channel_strip:
                self._session.set_mixer(self._mixer)
                self._session.layer = Layer(
                    track_bank_left_button=self._left_button,
                    track_bank_right_button=self._right_button)
            if self._fold_enabled or self._toggle_returns:
                if self._fold_enabled:
                    self._can_fold = self._selected_track != None and self._selected_track.is_foldable
                self._on_track_fold.subject = self._track_fold_button
            self._modes.selected_mode = 'mixer'
            if self._use_32:
                self._mixer.set_volume_controls(self._volume_encoders)

    def _create_device(self):
        self.log('_create_device', True)

        def make_button(identifier,
                        name,
                        midi_type=MIDI_CC_TYPE,
                        skin=self._default_skin):
            return ButtonElement(True,
                                 midi_type,
                                 MIXER_MODE,
                                 identifier,
                                 name=name,
                                 skin=skin)

        def make_encoder(identifier, name, bit_14=True):
            if self._bit_14:
                self.log('make_encoder: ' + name + ', CC: ' + str(identifier) +
                         ', 14_bit: ' + str(bit_14))
                control = EncoderElement(
                    MIDI_CC_TYPE,
                    MIXER_MODE,
                    identifier,
                    Live.MidiMap.MapMode.absolute_14_bit
                    if bit_14 else Live.MidiMap.MapMode.absolute,
                    name=name)
            else:
                control = EncoderElement(
                    MIDI_CC_TYPE,
                    MIXER_MODE,
                    identifier,
                    Live.MidiMap.MapMode.absolute if not self._relative else
                    Live.MidiMap.MapMode.relative_two_compliment,
                    name=name)
            return control

        if self._bit_14:
            volume_cc = 0
            sends_cc = 8
        else:
            volume_cc = 1
            sends_cc = 81
        if self._use_32:
            self._device_encoders = ButtonMatrixElement(
                rows=[[
                    make_encoder(volume_cc + i, 'Volume_%d' % (i + 1))
                    for i in xrange(8)
                ],
                      [
                          make_encoder(sends_cc + i, 'BCR_Device_0_%d' %
                                       (i + 1)) for i in xrange(8)
                      ],
                      [
                          make_encoder(sends_cc + 8 + i, 'BCR_Device_1_%d' %
                                       (i + 1)) for i in xrange(8)
                      ],
                      [
                          make_encoder(sends_cc + 16 + i, 'BCR_Device_2_%d' %
                                       (i + 1)) for i in xrange(8)
                      ]])
            self._send_encoders = self._device_encoders.submatrix[:8, 1:4]
        else:
            self._device_encoders = self._send_encoders
        if self._channel_strip:
            self._device = DeviceComponent_Strip(
                self,
                name='XL_Device',
                is_enabled=True,
                direct_bank=self._direct_bank,
                num_params=32 if self._use_32 else 24,
                path=self._my_path,
                delayed_update=True)
        else:
            self._device = DeviceComponent(
                self,
                name='XL_Device',
                is_enabled=True,
                direct_bank=self._direct_bank,
                num_params=32 if self._use_32 else 24,
                path=self._my_path,
                delayed_update=True)
        self.set_preditor_device_component(self._device)
        if self._direct_bank:
            device_layer = Layer(parameter_controls=self._device_encoders,
                                 device_select=self._top_buttons,
                                 device_enable=self._bottom_buttons,
                                 device_lock_button=self._track_fold_button,
                                 bank_up_button=self._bank_down_button,
                                 bank_down_button=self._bank_up_button,
                                 direct_bank=self._direct_bank_buttons)
        else:
            device_layer = Layer(parameter_controls=self._device_encoders,
                                 device_select=self._top_buttons,
                                 device_enable=self._bottom_buttons,
                                 device_lock_button=self._track_fold_button,
                                 bank_up_button=self._bank_down_button,
                                 bank_down_button=self._bank_up_button)
        device_settings_layer = Layer()
        self.device_mode = DeviceModeComponent(script=self,
                                               component=self._device,
                                               device_settings_mode=[
                                                   AddLayerMode(
                                                       self._device,
                                                       device_settings_layer)
                                               ],
                                               is_enabled=True)
        self.device_mode.layer = Layer(
            device_mode_button=self._device_engage_button)
        if self._has_pans:
            mixer_layer = Layer(volume_controls=self._volume_encoders,
                                pan_controls=self._pan_encoders)
        else:
            mixer_layer = Layer(volume_controls=self._volume_encoders)
        self._modes.add_mode('device', [
            AddLayerMode(self._device, device_layer),
            AddLayerMode(self._mixer, mixer_layer)
        ])
        if self._channel_strip:
            if hasattr(self._device, 'enable_channel_strip'):
                strip_layer = Layer(parameter_controls=self._device_encoders)
                valid_opts = self._get_button_options(True)
                mixer_layer += Layer(**valid_opts)
                self._modes.add_mode('device_strip', [
                    AddLayerMode(self._device, strip_layer),
                    AddLayerMode(self._mixer, mixer_layer),
                    AddLayerMode(
                        self._session,
                        Layer(track_bank_left_button=self._left_button,
                              track_bank_right_button=self._right_button))
                ])
            else:
                self._channel_strip = False

    def update(self):
        self.log('update: device_mode: ' + str(self._device_mode))
        super(BCRXL, self).update()

    def refresh_state(self):
        self.log('refresh_state')
        super(BCRXL, self).refresh_state()

    def handle_sysex(self, midi_bytes):
        if self._LOG_SYSEX:
            self.log('handle_sysex: ' + str(midi_bytes))
        if midi_bytes[:4] == BCR_SYSEX[:4] and midi_bytes[5] == 21:
            if self._bcr_controls.receive_ack(midi_bytes):
                if self._device_mode:
                    self._device.map_controls()
                else:
                    self._mixer.map_controls()
            if midi_bytes[9:10] != (0, ):
                self.log('Received sysex error code: ' + str(midi_bytes), True)

    @subject_slot_group('value')
    def _on_select_pressed(self, value, button):
        pass

    def same_track_selected(self):
        self.log('BCRXL: same_track_selected')
        selected_track = self.song().view.selected_track
        if selected_track != None:
            if self._device_mode:
                self.song().view.selected_track = self.song().master_track
            elif self._button_reselect_track == 'mute':
                selected_track.mute = not selected_track.mute
            elif self._button_reselect_track == 'solo':
                selected_track.solo = not selected_track.solo
            elif self._button_reselect_track == 'arm':
                if selected_track.can_be_armed:
                    selected_track.arm = not selected_track.arm
            elif self._button_reselect_track == 'fold':
                if self._selected_track.is_foldable:
                    self._selected_track.fold_state = not self._selected_track.fold_state
        return False