class Launchpad_Pro(IdentifiableControlSurface, OptimizedControlSurface):

    def __init__(self, c_instance, *a, **k):
        product_id_bytes = consts.MANUFACTURER_ID + consts.DEVICE_CODE
        super(Launchpad_Pro, self).__init__(c_instance=c_instance, product_id_bytes=product_id_bytes, *a, **k)
        self._challenge = Live.Application.get_random_int(0, 400000000) & 2139062143
        with self.component_guard():
            self._skin = make_default_skin()
            with inject(skin=const(self._skin)).everywhere():
                self._midimap = MidiMap()
            self._target_track_component = TargetTrackComponent(name='Target_Track')
            self._create_background()
            self._create_global_component()
            self._last_sent_mode_byte = None
            with inject(layout_setup=const(self._layout_setup), should_arm=const(self._should_arm_track)).everywhere():
                self._create_session()
                self._create_recording()
                self._create_actions()
                self._create_drums()
                self._create_mixer()
                self._create_device()
                self._create_modes()
                self._create_m4l_interface()
            self._on_session_record_changed.subject = self.song()
        self.set_highlighting_session_component(self._session)
        self.set_device_component(self._device)
        self._device_selection_follows_track_selection = True
        self._on_session_record_changed()

    def disconnect(self):
        self._send_midi(consts.TURN_OFF_LEDS)
        self._send_midi(consts.QUIT_MESSAGE)
        super(Launchpad_Pro, self).disconnect()

    def _create_background(self):
        self._modifier_background_component = ModifierBackgroundComponent(name='Background_Component', is_enabled=False, layer=Layer(shift_button=self._midimap['Shift_Button']))
        self._shifted_background = BackgroundComponent(name='No_Op_Shifted_Buttons', is_enabled=False, layer=Layer(click_bitton=self._midimap.with_shift('Click_Button'), delete_button=self._midimap.with_shift('Delete_Button'), duplicate_button=self._midimap.with_shift('Duplicate_Button'), double_button=self._midimap.with_shift('Double_Loop_Button'), session_record_button=self._midimap.with_shift('Session_Record_Button')))

    def _create_global_component(self):
        self._actions_component = ActionsComponent(name='Global_Actions', is_enabled=False, layer=Layer(undo_button=self._midimap['Undo_Button'], redo_button=self._midimap.with_shift('Undo_Button'), metronome_button=self._midimap['Click_Button'], quantization_on_button=self._midimap.with_shift('Quantize_Button')))

    def _create_session(self):
        self._session = SessionComponent(NUM_TRACKS, NUM_SCENES, auto_name=True, is_enabled=False, enable_skinning=True, layer=Layer(track_bank_left_button=self._midimap['Arrow_Left_Button'], track_bank_right_button=self._midimap['Arrow_Right_Button'], scene_bank_up_button=self._midimap['Arrow_Up_Button'], scene_bank_down_button=self._midimap['Arrow_Down_Button']))
        self._session.set_enabled(True)
        self._session.set_rgb_mode(CLIP_COLOR_TABLE, RGB_COLOR_TABLE)
        SpecialClipSlotComponent.quantization_component = self._actions_component
        for scene_index in xrange(NUM_SCENES):
            scene = self._session.scene(scene_index)
            scene.layer = Layer(select_button=self._midimap['Shift_Button'], delete_button=self._midimap['Delete_Button'], duplicate_button=self._midimap['Duplicate_Button'])
            for track_index in xrange(NUM_TRACKS):
                slot = scene.clip_slot(track_index)
                slot.layer = Layer(select_button=self._midimap['Shift_Button'], delete_button=self._midimap['Delete_Button'], duplicate_button=self._midimap['Duplicate_Button'], double_loop_button=self._midimap['Double_Loop_Button'], quantize_button=self._midimap['Quantize_Button'])

        self._session_zoom = SessionZoomingComponent(self._session, name='Session_Overview', is_enabled=True, enable_skinning=True)

    def _create_recording(self):
        self._session_record = SpecialSessionRecordingComponent(self._target_track_component, name='Session_Recording', is_enabled=False, layer=Layer(record_button=self._midimap['Session_Record_Button']))

    def _create_actions(self):
        self._clip_actions_component = ClipActionsComponent(self._target_track_component, name='Clip_Actions', is_enabled=False, layer=Layer(duplicate_button=self._midimap['Duplicate_Button'], double_button=self._midimap['Double_Loop_Button'], quantize_button=self._midimap['Quantize_Button']))
        ClipActionsComponent.quantization_component = self._actions_component

    def _create_drums(self):
        self._drum_group_finder = DrumGroupFinderComponent(self._target_track_component, name='Drum_Group_Finder', is_enabled=False, layer=None)
        self._on_drum_group_changed.subject = self._drum_group_finder
        self._drum_group_finder.set_enabled(True)
        self._drum_group = DrumGroupComponent(self._clip_actions_component, name='Drum_Group_Control', translation_channel=consts.DR_MAP_CHANNEL)
        self._drum_group.set_enabled(True)

    def _create_mixer(self):
        self._mixer = SpecialMixerComponent(NUM_TRACKS, auto_name=True, is_enabled=True, invert_mute_feedback=True)
        self._mixer.name = 'Mixer_Control'
        self._session.set_mixer(self._mixer)

    def _create_device(self):
        self._device = SpecialDeviceComponent(name='Device_Control', is_enabled=False)
        self._device_navigation = DeviceNavigationComponent(name='Device_Navigation')
        self._device_background = BackgroundComponent(name='Device_Background_Component')

    def _setup_drum_group(self):
        self._drum_group.set_drum_group_device(self._drum_group_finder.drum_group)

    def _create_translation(self, comp_name, channel, button_layer, should_enable = True, should_reset = True):
        translation_component = TranslationComponent(name=comp_name, translated_channel=channel, should_enable=should_enable, should_reset=should_reset, is_enabled=False, layer=button_layer)
        setattr(self, '_' + comp_name.lower(), translation_component)
        return translation_component

    def _create_modes(self):
        self._modes = ModesComponent(name='Launchpad_Modes', is_enabled=False)
        self._session_layer_mode = AddLayerMode(self._session, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix'], clip_launch_buttons=self._midimap['Main_Button_Matrix'], delete_button=self._midimap['Delete_Button'], duplicate_button=self._midimap['Duplicate_Button'], double_button=self._midimap['Double_Loop_Button'], quantize_button=self._midimap['Quantize_Button']))
        action_button_background = BackgroundComponent(name='No_Op_Buttons')
        self._action_button_background_layer_mode = LayerMode(action_button_background, Layer(delete_button=self._midimap['Delete_Button'], quantize_button=self._midimap['Quantize_Button'], duplicate_button=self._midimap['Duplicate_Button'], double_button=self._midimap['Double_Loop_Button']))
        self._clip_delete_layer_mode = AddLayerMode(self._clip_actions_component, layer=Layer(delete_button=self._midimap['Delete_Button']))
        self._create_session_zooming_modes()
        self._create_session_mode()
        self._create_note_modes()
        self._create_device_mode()
        self._create_user_mode()
        self._create_record_arm_mode()
        self._create_track_select_mode()
        self._create_mute_mode()
        self._create_solo_mode()
        self._create_volume_mode()
        self._create_pan_mode()
        self._create_sends_mode()
        self._create_stop_clips_mode()
        self._modes.layer = Layer(session_mode_button=self._midimap['Session_Mode_Button'], note_mode_button=self._midimap['Note_Mode_Button'], device_mode_button=self._midimap['Device_Mode_Button'], user_mode_button=self._midimap['User_Mode_Button'], record_arm_mode_button=self._midimap['Record_Arm_Mode_Button'], track_select_mode_button=self._midimap['Track_Select_Mode_Button'], mute_mode_button=self._midimap['Mute_Mode_Button'], solo_mode_button=self._midimap['Solo_Mode_Button'], volume_mode_button=self._midimap['Volume_Mode_Button'], pan_mode_button=self._midimap['Pan_Mode_Button'], sends_mode_button=self._midimap['Sends_Mode_Button'], stop_clip_mode_button=self._midimap['Stop_Clip_Mode_Button'])
        self._modes.selected_mode = 'session_mode'
        self._on_layout_changed.subject = self._modes

    def _create_session_zooming_modes(self):
        session_zoom_layer = Layer(button_matrix=self._midimap['Main_Button_Matrix'], nav_left_button=self._midimap['Arrow_Left_Button'], nav_right_button=self._midimap['Arrow_Right_Button'], nav_up_button=self._midimap['Arrow_Up_Button'], nav_down_button=self._midimap['Arrow_Down_Button'])
        session_zooming_layer_mode = LayerMode(self._session_zoom, session_zoom_layer)
        self._session_zooming_manager = SessionZoomingManagerComponent(self._modes, is_enabled=False)
        session_zooming_button_layer_mode = LayerMode(self._session_zooming_manager, Layer(session_zooming_button=self._midimap['Session_Mode_Button']))
        self._prioritized_session_zooming_button_layer_mode = LayerMode(self._session_zooming_manager, Layer(session_zooming_button=self._midimap['Session_Mode_Button'], priority=1))
        self._session_zooming_background = BackgroundComponent(name='Session_Zooming_Background')
        session_zooming_background_layer_mode = LayerMode(self._session_zooming_background, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix'], delete_button=self._midimap['Delete_Button'], quantize_button=self._midimap['Quantize_Button'], duplicate_button=self._midimap['Duplicate_Button'], double_loop_button=self._midimap['Double_Loop_Button']))
        self._modes.add_mode('session_zooming_mode', [self._session_zooming_manager,
         session_zooming_button_layer_mode,
         session_zooming_layer_mode,
         session_zooming_background_layer_mode])
        self._modes.add_mode('prioritized_session_zooming_mode', [partial(self._layout_switch, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         session_zooming_layer_mode,
         session_zooming_background_layer_mode,
         self.update])

    def _create_session_mode(self):
        self._modes.add_mode('session_mode', [partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE), self._session_layer_mode, self._session.update_navigation_buttons], behaviour=CancelingReenterBehaviour('session_zooming_mode'))

    def _create_note_modes(self):
        note_mode_matrix_translation = self._create_translation('Note_Mode_Matrix_Translation', consts.CHROM_MAP_CHANNEL, Layer(button_matrix=self._midimap['Main_Button_Matrix'], note_button_matrix=self._midimap['Note_Button_Matrix'], drum_matrix=self._midimap['Drum_Button_Matrix'], mixer_button_matrix=self._midimap['Mixer_Button_Matrix']), should_enable=False)
        note_mode_scene_launch_translation = self._create_translation('Note_Mode_Scene_Launch_Translation', consts.CHROM_MAP_CHANNEL, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix']))
        drum_mode_note_matrix_translation = self._create_translation('Drum_Mode_Note_Button_Translation', 0, Layer(note_button_matrix=self._midimap['Note_Button_Matrix']), should_enable=False, should_reset=False)
        drum_group_layer_mode = LayerMode(self._drum_group, layer=Layer(scroll_up_button=self._midimap['Arrow_Left_Button'], scroll_down_button=self._midimap['Arrow_Right_Button'], scroll_page_up_button=self._midimap['Arrow_Up_Button'], scroll_page_down_button=self._midimap['Arrow_Down_Button'], drum_matrix=self._midimap['Drum_Button_Matrix'], select_button=self._midimap['Shift_Button'], delete_button=self._midimap['Delete_Button']))
        self._note_modes = SpecialModesComponent(name='Note_Modes')
        self._note_modes.add_mode('chromatic_mode', [partial(self._layout_setup, consts.NOTE_LAYOUT_SYSEX_BYTE), self._clip_delete_layer_mode, note_mode_matrix_translation])
        self._note_modes.add_mode('drum_mode', [partial(self._layout_setup, consts.DRUM_LAYOUT_SYSEX_BYTE),
         self._setup_drum_group,
         drum_group_layer_mode,
         drum_mode_note_matrix_translation])
        self._note_modes.add_mode('audio_mode', [partial(self._layout_setup, consts.AUDIO_LAYOUT_SYSEX_BYTE), self._clip_delete_layer_mode])
        self._note_modes.set_enabled(False)
        self._modes.add_mode('note_mode', [note_mode_scene_launch_translation,
         self._note_modes,
         self._select_note_mode,
         self._select_target_track,
         self._clip_actions_component,
         self._show_playing_clip,
         self._set_clip_actions_type], behaviour=ReenterBehaviour(self.toggle_detail_view))
        self._session_record.set_modes_component(self._modes)
        self._session_record.set_note_mode_name('note_mode')

    def _create_device_mode(self):
        device_mode_scene_launch_translation = self._create_translation('Device_Mode_Scene_Launch_Translation', consts.DEVICE_MAP_CHANNEL, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix']))
        device_layer_mode = LayerMode(self._device, layer=Layer(parameter_controls=self._midimap['Slider_Button_Matrix']))
        device_nav_layer_mode = LayerMode(self._device_navigation, layer=Layer(device_nav_left_button=self._midimap['Arrow_Left_Button'], device_nav_right_button=self._midimap['Arrow_Right_Button']))
        device_background_layer_mode = LayerMode(self._device_background, layer=Layer(arrow_up_button=self._midimap['Arrow_Up_Button'], arrow_down_button=self._midimap['Arrow_Down_Button']))
        self._modes.add_mode('device_mode', [partial(self._layout_setup, consts.FADER_LAYOUT_SYSEX_BYTE),
         self._device,
         device_layer_mode,
         device_nav_layer_mode,
         device_background_layer_mode,
         self._clip_actions_component,
         self._clip_delete_layer_mode,
         device_mode_scene_launch_translation,
         self._show_playing_clip,
         self._set_clip_actions_type], behaviour=ReenterBehaviour(self.toggle_detail_view))

    def _create_user_mode(self):
        self._modes.add_mode('user_mode', [partial(self._layout_setup, consts.USER_LAYOUT_SYSEX_BYTE)])

    def _create_record_arm_mode(self):
        arm_layer_mode = LayerMode(self._mixer, layer=Layer(arm_buttons=self._midimap['Mixer_Button_Matrix']))
        self._modes.add_mode('record_arm_mode', [partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_layer_mode,
         arm_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_track_select_mode(self):
        track_select_layer_mode = LayerMode(self._mixer, layer=Layer(track_select_buttons=self._midimap['Mixer_Button_Matrix']))
        self._modes.add_mode('track_select_mode', [partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_layer_mode,
         track_select_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_mute_mode(self):
        mute_layer_mode = LayerMode(self._mixer, layer=Layer(mute_buttons=self._midimap['Mixer_Button_Matrix']))
        self._modes.add_mode('mute_mode', [partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_layer_mode,
         mute_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_solo_mode(self):
        solo_layer_mode = LayerMode(self._mixer, layer=Layer(solo_buttons=self._midimap['Mixer_Button_Matrix']))
        self._modes.add_mode('solo_mode', [partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_layer_mode,
         solo_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_volume_mode(self):
        volume_mode_scene_launch_translation = self._create_translation('Volume_Mode_Scene_Launch_Translation', consts.VOLUME_MAP_CHANNEL, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix']))
        volume_layer_mode = LayerMode(self._mixer, layer=Layer(volume_controls=self._midimap['Slider_Button_Matrix']))
        self._modes.add_mode('volume_mode', [partial(self._layout_setup, consts.FADER_LAYOUT_SYSEX_BYTE),
         volume_layer_mode,
         self._action_button_background_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         volume_mode_scene_launch_translation,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_pan_mode(self):
        pan_mode_scene_launch_translation = self._create_translation('Pan_Mode_Scene_Launch_Translation', consts.PAN_MAP_CHANNEL, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix']))
        pan_layer_mode = LayerMode(self._mixer, layer=Layer(pan_controls=self._midimap['Slider_Button_Matrix']))
        self._modes.add_mode('pan_mode', [partial(self._layout_setup, consts.FADER_LAYOUT_SYSEX_BYTE),
         pan_layer_mode,
         self._action_button_background_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         pan_mode_scene_launch_translation,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_sends_mode(self):
        send_layer_mode = LayerMode(self._mixer, layer=Layer(send_controls=self._midimap['Slider_Button_Matrix'], send_select_buttons=self._midimap['Scene_Launch_Button_Matrix']))
        self._modes.add_mode('sends_mode', [partial(self._layout_setup, consts.FADER_LAYOUT_SYSEX_BYTE),
         send_layer_mode,
         self._action_button_background_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_stop_clips_mode(self):
        stop_layer_mode = AddLayerMode(self._session, Layer(stop_track_clip_buttons=self._midimap['Mixer_Button_Matrix'], stop_scene_clip_buttons=self._midimap['Scene_Stop_Button_Matrix'], stop_all_clips_button=self._midimap['Stop_All_Clips_Button']))
        self._modes.add_mode('stop_clip_mode', [partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_layer_mode,
         stop_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_m4l_interface(self):
        self._m4l_interface = M4LInterfaceComponent(controls=self.controls, component_guard=self.component_guard, priority=1)
        self.get_control_names = self._m4l_interface.get_control_names
        self.get_control = self._m4l_interface.get_control
        self.grab_control = self._m4l_interface.grab_control
        self.release_control = self._m4l_interface.release_control

    def toggle_detail_view(self):
        view = self.application().view
        if view.is_view_visible('Detail'):
            if view.is_view_visible('Detail/DeviceChain'):
                view.show_view('Detail/Clip')
            else:
                view.show_view('Detail/DeviceChain')

    @subject_slot('drum_group')
    def _on_drum_group_changed(self):
        if self._note_modes.selected_mode == 'drum_mode':
            self._drum_group.set_drum_group_device(self._drum_group_finder.drum_group)
        if self._modes.selected_mode == 'note_mode':
            self._select_note_mode()
        else:
            self.release_controlled_track()
        self._update_note_mode_button(self._drum_group_finder.drum_group is not None)

    def _select_note_mode(self):
        """
        Selects which note mode to use depending on the kind of
        current target track and its device chain.  Will also
        select the target if specified.
        """
        track = self._target_track_component.target_track
        drum_device = self._drum_group_finder.drum_group
        if track is None or track.is_foldable or track in self.song().return_tracks or track == self.song().master_track or track.is_frozen or track.has_audio_input:
            self._note_modes.selected_mode = 'audio_mode'
        elif drum_device:
            self._note_modes.selected_mode = 'drum_mode'
        else:
            self._note_modes.selected_mode = 'chromatic_mode'
        self._modes.update()
        if self._note_modes.selected_mode == 'audio_mode':
            self.release_controlled_track()
        else:
            self.set_controlled_track(self._target_track_component.target_track)

    def _select_target_track(self):
        track = self._target_track_component.target_track
        if track != self.song().view.selected_track:
            self.song().view.selected_track = track

    def _update_note_mode_button(self, focused_track_is_drum_track):
        button = self._midimap['Note_Mode_Button']
        if focused_track_is_drum_track:
            button.default_states = {True: 'Mode.Drum.On',
             False: 'Mode.Drum.Off'}
        else:
            button.default_states = {True: 'Mode.Chromatic.On',
             False: 'Mode.Chromatic.Off'}
        button.reset_state()
        self._modes.update()

    def _show_playing_clip(self):
        track = None
        if self._use_sel_track():
            track = self.song().view.selected_track
        else:
            track = self._target_track_component.target_track
        if track in self.song().tracks:
            slot_index = track.fired_slot_index
            if slot_index < 0:
                slot_index = track.playing_slot_index
            if slot_index >= 0:
                clip_slot = track.clip_slots[slot_index]
                self.song().view.highlighted_clip_slot = clip_slot

    def _set_clip_actions_type(self):
        self._clip_actions_component.use_selected_track(self._use_sel_track())
        self._clip_actions_component.update()

    def _use_sel_track(self):
        return self._modes.selected_mode == 'device_mode'

    def _should_arm_track(self):
        return self._modes.selected_mode == 'record_arm_mode'

    @subject_slot('selected_mode')
    def _on_layout_changed(self, mode):
        if mode == 'note_mode':
            self.set_controlled_track(self._target_track_component.target_track)
        else:
            self.release_controlled_track()
        self._session_record.set_enabled(mode != 'user_mode')

    @subject_slot('session_record')
    def _on_session_record_changed(self):
        status = self.song().session_record
        feedback_color = int(self._skin['Instrument.FeedbackRecord'] if status else self._skin['Instrument.Feedback'])
        self._c_instance.set_feedback_velocity(feedback_color)

    def _clear_send_cache(self):
        with self.component_guard():
            for control in self.controls:
                control.clear_send_cache()

    def _update_global_components(self):
        self._actions_component.update()
        self._session_record.update()
        self._modifier_background_component.update()

    def _layout_setup(self, mode):
        self._layout_switch(mode)
        self._clear_send_cache()
        self._update_global_components()

    def _layout_switch(self, mode):
        prefix = consts.SYSEX_STANDARD_PREFIX + consts.SYSEX_PARAM_BYTE_LAYOUT
        suffix = consts.SYSEX_STANDARD_SUFFIX
        self._send_midi(prefix + mode + suffix)
        self._last_sent_mode_byte = mode

    def _send_identity_request(self):
        self._send_midi(consts.SYSEX_IDENTITY_REQUEST)

    def on_identified(self):
        self._send_challenge()

    def _send_challenge(self):
        challenge_bytes = []
        for index in range(4):
            challenge_bytes.append(self._challenge >> 8 * index & 127)

        challenge = consts.CHALLENGE_PREFIX + tuple(challenge_bytes) + (247,)
        self._send_midi(challenge)

    def _on_handshake_successful(self):
        self._do_send_midi(consts.LIVE_MODE_SWITCH_REQUEST)
        with self.component_guard():
            self._modes.set_enabled(True)
            self._actions_component.set_enabled(True)
            self._session_record.set_enabled(True)
            self._modifier_background_component.set_enabled(True)
            self._shifted_background.set_enabled(True)
            self.release_controlled_track()
            self.set_feedback_channels(consts.FEEDBACK_CHANNELS)
        if self._last_sent_mode_byte is not None:
            self._layout_setup(self._last_sent_mode_byte)
        self.update()

    def _is_challenge_response(self, midi_bytes):
        return len(midi_bytes) == 10 and midi_bytes[:7] == consts.SYSEX_STANDARD_PREFIX + consts.SYSEX_CHALLENGE_RESPONSE_BYTE

    def _is_response_valid(self, midi_bytes):
        response = long(midi_bytes[7])
        response += long(midi_bytes[8] << 8)
        return response == Live.Application.encrypt_challenge2(self._challenge)

    def handle_sysex(self, midi_bytes):
        if len(midi_bytes) < 7:
            pass
        if self._is_challenge_response(midi_bytes) and self._is_response_valid(midi_bytes):
            self._on_handshake_successful()
        elif midi_bytes[6] in (consts.SYSEX_STATUS_BYTE_MODE, consts.SYSEX_STATUS_BYTE_LAYOUT):
            pass
        else:
            super(Launchpad_Pro, self).handle_sysex(midi_bytes)
Exemplo n.º 2
0
class InstrumentComponent(ControlSurfaceComponent):
    """
	handles notes mode and switches between note, drum and dummy audio mode.
	"""
    def __init__(self, control_surface, drum_component=None, *a, **k):

        self._control_surface = control_surface
        self._drum_component = drum_component
        self._implicit_arm = True
        self._modes = ModesComponent(name='Instrument_Modes', is_enabled=False)
        self._modes.set_enabled = self.set_enabled
        self._feedback_channels = [
            consts.DR_MAP_CHANNEL, consts.DR_MAP_CHANNEL + 1,
            consts.DR_MAP_CHANNEL + 2, consts.DR_MAP_CHANNEL + 3,
            consts.DR_MAP_CHANNEL + 4
        ]
        self._non_feedback_channel = consts.DR_MAP_CHANNEL + 5

        self._common_component = CommonModeComponent(
            instrument_component=self, control_surface=self._control_surface)
        self._scale_component = ScaleComponent(
            control_surface=self._control_surface, enabled=True)
        self._note_component = NoteComponent(
            control_surface=self._control_surface,
            feedback_channels=self._feedback_channels,
            non_feedback_channel=self._non_feedback_channel,
            get_pattern=self._scale_component.get_pattern)

        super(InstrumentComponent, self).__init__(*a, **k)

    def set_layers(self, midimap):
        common_layer_mode = LayerMode(
            self._common_component,
            layer=Layer(
                arrow_left_button=midimap['Arrow_Left_Button'],
                arrow_right_button=midimap['Arrow_Right_Button'],
                arrow_up_button=midimap['Arrow_Up_Button'],
                arrow_down_button=midimap['Arrow_Down_Button'],
                scale_button=midimap['Scene_Launch_Button_Matrix_Raw'][0][0],
                scale_up_button=midimap['Scene_Launch_Button_Matrix_Raw'][0]
                [1],
                scale_down_button=midimap['Scene_Launch_Button_Matrix_Raw'][0]
                [2],
                dummy_button=midimap['Scene_Launch_Button_Matrix_Raw'][0][3],
                play_button=midimap['Scene_Launch_Button_Matrix_Raw'][0][6],
                stop_button=midimap['Scene_Launch_Button_Matrix_Raw'][0][7]))

        drum_group_layer_mode = LayerMode(
            self._control_surface._drum_group,
            layer=Layer(
                #scroll_up_button = midimap['Scene_Launch_Button_Matrix_Raw'][0][1],
                #scroll_down_button = midimap['Scene_Launch_Button_Matrix_Raw'][0][2],
                mute_button=midimap['Scene_Launch_Button_Matrix_Raw'][0][4],
                solo_button=midimap['Scene_Launch_Button_Matrix_Raw'][0][5],
                #scroll_up_button=midimap['Arrow_Left_Button'],
                #scroll_down_button=midimap['Arrow_Right_Button'],
                #scroll_page_up_button=midimap['Arrow_Up_Button'],
                #scroll_page_down_button=midimap['Arrow_Down_Button'],
                #drum_matrix = midimap['Drum_Button_Matrix']#,
                drum_matrix=midimap['Main_Button_Matrix']
                #select_button = midimap['Shift_Button'],
                #delete_button = midimap['Delete_Button']
            ))

        self._modes.add_mode(
            'drum_mode',
            [
                partial(self._control_surface._layout_setup,
                        consts.SESSION_LAYOUT_SYSEX_BYTE),
                partial(self._control_surface._layout_setup,
                        consts.USER_LAYOUT_SYSEX_BYTE,
                        consts.SYSEX_PARAM_BYTE_STANDALONE_LAYOUT),
                #partial(self._control_surface._layout_setup, consts.DRUM_LAYOUT_SYSEX_BYTE),
                self._control_surface._setup_drum_group(),
                drum_group_layer_mode,
                common_layer_mode  #,
                #drum_mode_note_matrix_translation
            ])

        scale_layer_mode = LayerMode(
            self._scale_component,
            layer=Layer(matrix=midimap['Main_Button_Matrix']))
        self._modes.add_mode(
            'scale_mode',
            [
                partial(self._control_surface._layout_setup,
                        consts.SESSION_LAYOUT_SYSEX_BYTE),
                partial(self._control_surface._layout_setup,
                        consts.USER_LAYOUT_SYSEX_BYTE,
                        consts.SYSEX_PARAM_BYTE_STANDALONE_LAYOUT),
                scale_layer_mode,
                common_layer_mode  #,
                #drum_mode_note_matrix_translation
            ])
        note_layer_mode = LayerMode(
            self._note_component,
            layer=Layer(
                matrix=midimap['Main_Button_Matrix']
                #select_button = midimap['Shift_Button'],
                #delete_button = midimap['Delete_Button']
            ))
        self._modes.add_mode(
            'note_mode',
            [
                partial(self._control_surface._layout_setup,
                        consts.SESSION_LAYOUT_SYSEX_BYTE),
                partial(self._control_surface._layout_setup,
                        consts.USER_LAYOUT_SYSEX_BYTE,
                        consts.SYSEX_PARAM_BYTE_STANDALONE_LAYOUT),
                note_layer_mode,
                common_layer_mode  #,
                #drum_mode_note_matrix_translation
            ])

        #Audio mode
        audio_layer_mode = LayerMode(
            AudioModeComponent(),
            layer=Layer(matrix=midimap['Main_Button_Matrix']))
        self._modes.add_mode(
            'audio_mode',
            [
                partial(self._control_surface._layout_setup,
                        consts.SESSION_LAYOUT_SYSEX_BYTE),
                partial(self._control_surface._layout_setup,
                        consts.DRUM_LAYOUT_SYSEX_BYTE,
                        consts.SYSEX_PARAM_BYTE_STANDALONE_LAYOUT),
                #partial(self._control_surface._layout_setup, consts.DRUM_LAYOUT_SYSEX_BYTE),#consts.AUDIO_LAYOUT_SYSEX_BYTE),
                self._control_surface._clip_delete_layer_mode,
                common_layer_mode,
                audio_layer_mode
            ])

    def set_enabled(self, enabled):
        if not enabled:
            self._do_implicit_arm(enabled)
        ControlSurfaceComponent.set_enabled(self, enabled)
        ModesComponent.set_enabled(self._modes, enabled)
        if enabled:
            self._do_implicit_arm(enabled)

    def _enter_scale_mode(self):
        self._previous_mode = self._modes.selected_mode
        self.set_mode('scale_mode')

    def _leave_scale_mode(self):
        self.set_mode(self._previous_mode)

    def _detect_mode(self):
        track = self._control_surface._target_track_component.target_track
        #drum_device = self._control_surface._drum_group_finder.drum_group
        self._control_surface._setup_drum_group()
        if track is None or track.is_foldable or track in self.song(
        ).return_tracks or track == self.song(
        ).master_track or track.is_frozen or track.has_audio_input:
            self.set_mode('audio_mode')
            self._scale_component._is_drumrack = False
        elif self._control_surface._drum_group._drum_group_device:
            self._scale_component._is_drumrack = True
            self.set_mode('drum_mode')
        else:
            self._scale_component._is_drumrack = False
            self.set_mode('note_mode')

    def get_pattern(self):
        return self._scale_component.get_pattern()

    def on_selected_track_changed(self):
        if self._implicit_arm:
            #self._control_surface._setup_drum_group()
            self._detect_mode()
            self._do_implicit_arm()

    def update(self):
        self._modes.update()

    def on_selected_scene_changed(self):
        #self._do_implicit_arm()
        self.update()

    def set_mode(self, mode):
        self._modes.selected_mode = mode
        self._modes.update()

    def get_mode(self):
        return self._modes.selected_mode

    def can_implicit_arm_track(self, track):
        #todo
        return track.can_be_armed and track.has_midi_input

    def _do_implicit_arm(self, arm=True):
        if self._is_enabled:
            if self._implicit_arm and arm:
                self._control_surface.set_feedback_channels(
                    self._feedback_channels)
                self._control_surface.set_controlled_track(
                    self._control_surface._target_track_component.target_track)
            else:
                self._control_surface.release_controlled_track()

            for track in self.song().tracks:
                if self.can_implicit_arm_track(track):
                    track.implicit_arm = self._is_enabled and self._implicit_arm and arm and self._control_surface._target_track_component.target_track == track
Exemplo n.º 3
0
class InstrumentComponent(ControlSurfaceComponent):
    """
	handles notes mode and switches between note, drum and dummy audio mode.
	"""

    def __init__(self, control_surface, drum_component=None, *a, **k):

        self._control_surface = control_surface
        self._drum_component = drum_component
        self._implicit_arm = True
        self._modes = ModesComponent(name="Instrument_Modes", is_enabled=False)
        self._modes.set_enabled = self.set_enabled
        self._feedback_channels = [
            consts.DR_MAP_CHANNEL,
            consts.DR_MAP_CHANNEL + 1,
            consts.DR_MAP_CHANNEL + 2,
            consts.DR_MAP_CHANNEL + 3,
            consts.DR_MAP_CHANNEL + 4,
        ]
        self._non_feedback_channel = consts.DR_MAP_CHANNEL + 5

        self._common_component = CommonModeComponent(instrument_component=self, control_surface=self._control_surface)
        self._scale_component = ScaleComponent(control_surface=self._control_surface, enabled=True)
        self._note_component = NoteComponent(
            control_surface=self._control_surface,
            feedback_channels=self._feedback_channels,
            non_feedback_channel=self._non_feedback_channel,
            get_pattern=self._scale_component.get_pattern,
        )

        super(InstrumentComponent, self).__init__(*a, **k)

    def set_layers(self, midimap):
        common_layer_mode = LayerMode(
            self._common_component,
            layer=Layer(
                arrow_left_button=midimap["Arrow_Left_Button"],
                arrow_right_button=midimap["Arrow_Right_Button"],
                arrow_up_button=midimap["Arrow_Up_Button"],
                arrow_down_button=midimap["Arrow_Down_Button"],
                scale_button=midimap["Scene_Launch_Button_Matrix_Raw"][0][0],
                scale_up_button=midimap["Scene_Launch_Button_Matrix_Raw"][0][1],
                scale_down_button=midimap["Scene_Launch_Button_Matrix_Raw"][0][2],
                dummy_button=midimap["Scene_Launch_Button_Matrix_Raw"][0][3],
                play_button=midimap["Scene_Launch_Button_Matrix_Raw"][0][6],
                stop_button=midimap["Scene_Launch_Button_Matrix_Raw"][0][7],
            ),
        )

        drum_group_layer_mode = LayerMode(
            self._control_surface._drum_group,
            layer=Layer(
                # scroll_up_button = midimap['Scene_Launch_Button_Matrix_Raw'][0][1],
                # scroll_down_button = midimap['Scene_Launch_Button_Matrix_Raw'][0][2],
                mute_button=midimap["Scene_Launch_Button_Matrix_Raw"][0][4],
                solo_button=midimap["Scene_Launch_Button_Matrix_Raw"][0][5],
                # scroll_up_button=midimap['Arrow_Left_Button'],
                # scroll_down_button=midimap['Arrow_Right_Button'],
                # scroll_page_up_button=midimap['Arrow_Up_Button'],
                # scroll_page_down_button=midimap['Arrow_Down_Button'],
                # drum_matrix = midimap['Drum_Button_Matrix']#,
                drum_matrix=midimap["Main_Button_Matrix"]
                # select_button = midimap['Shift_Button'],
                # delete_button = midimap['Delete_Button']
            ),
        )

        self._modes.add_mode(
            "drum_mode",
            [
                partial(self._control_surface._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
                partial(
                    self._control_surface._layout_setup,
                    consts.USER_LAYOUT_SYSEX_BYTE,
                    consts.SYSEX_PARAM_BYTE_STANDALONE_LAYOUT,
                ),
                # partial(self._control_surface._layout_setup, consts.DRUM_LAYOUT_SYSEX_BYTE),
                self._control_surface._setup_drum_group(),
                drum_group_layer_mode,
                common_layer_mode  # ,
                # drum_mode_note_matrix_translation
            ],
        )

        scale_layer_mode = LayerMode(self._scale_component, layer=Layer(matrix=midimap["Main_Button_Matrix"]))
        self._modes.add_mode(
            "scale_mode",
            [
                partial(self._control_surface._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
                partial(
                    self._control_surface._layout_setup,
                    consts.USER_LAYOUT_SYSEX_BYTE,
                    consts.SYSEX_PARAM_BYTE_STANDALONE_LAYOUT,
                ),
                scale_layer_mode,
                common_layer_mode  # ,
                # drum_mode_note_matrix_translation
            ],
        )
        note_layer_mode = LayerMode(
            self._note_component,
            layer=Layer(
                matrix=midimap["Main_Button_Matrix"]
                # select_button = midimap['Shift_Button'],
                # delete_button = midimap['Delete_Button']
            ),
        )
        self._modes.add_mode(
            "note_mode",
            [
                partial(self._control_surface._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
                partial(
                    self._control_surface._layout_setup,
                    consts.USER_LAYOUT_SYSEX_BYTE,
                    consts.SYSEX_PARAM_BYTE_STANDALONE_LAYOUT,
                ),
                note_layer_mode,
                common_layer_mode  # ,
                # drum_mode_note_matrix_translation
            ],
        )

        # Audio mode
        audio_layer_mode = LayerMode(AudioModeComponent(), layer=Layer(matrix=midimap["Main_Button_Matrix"]))
        self._modes.add_mode(
            "audio_mode",
            [
                partial(self._control_surface._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
                partial(
                    self._control_surface._layout_setup,
                    consts.DRUM_LAYOUT_SYSEX_BYTE,
                    consts.SYSEX_PARAM_BYTE_STANDALONE_LAYOUT,
                ),
                # partial(self._control_surface._layout_setup, consts.DRUM_LAYOUT_SYSEX_BYTE),#consts.AUDIO_LAYOUT_SYSEX_BYTE),
                self._control_surface._clip_delete_layer_mode,
                common_layer_mode,
                audio_layer_mode,
            ],
        )

    def set_enabled(self, enabled):
        if not enabled:
            self._do_implicit_arm(enabled)
        ControlSurfaceComponent.set_enabled(self, enabled)
        ModesComponent.set_enabled(self._modes, enabled)
        if enabled:
            self._do_implicit_arm(enabled)

    def _enter_scale_mode(self):
        self._previous_mode = self._modes.selected_mode
        self.set_mode("scale_mode")

    def _leave_scale_mode(self):
        self.set_mode(self._previous_mode)

    def _detect_mode(self):
        track = self._control_surface._target_track_component.target_track
        # drum_device = self._control_surface._drum_group_finder.drum_group
        self._control_surface._setup_drum_group()
        if (
            track is None
            or track.is_foldable
            or track in self.song().return_tracks
            or track == self.song().master_track
            or track.is_frozen
            or track.has_audio_input
        ):
            self.set_mode("audio_mode")
            self._scale_component._is_drumrack = False
        elif self._control_surface._drum_group._drum_group_device:
            self._scale_component._is_drumrack = True
            self.set_mode("drum_mode")
        else:
            self._scale_component._is_drumrack = False
            self.set_mode("note_mode")

    def get_pattern(self):
        return self._scale_component.get_pattern()

    def on_selected_track_changed(self):
        if self._implicit_arm:
            # self._control_surface._setup_drum_group()
            self._detect_mode()
            self._do_implicit_arm()

    def update(self):
        self._modes.update()

    def on_selected_scene_changed(self):
        # self._do_implicit_arm()
        self.update()

    def set_mode(self, mode):
        self._modes.selected_mode = mode
        self._modes.update()

    def get_mode(self):
        return self._modes.selected_mode

    def can_implicit_arm_track(self, track):
        # todo
        return track.can_be_armed and track.has_midi_input

    def _do_implicit_arm(self, arm=True):
        if self._is_enabled:
            if self._implicit_arm and arm:
                self._control_surface.set_feedback_channels(self._feedback_channels)
                self._control_surface.set_controlled_track(self._control_surface._target_track_component.target_track)
            else:
                self._control_surface.release_controlled_track()

            for track in self.song().tracks:
                if self.can_implicit_arm_track(track):
                    track.implicit_arm = (
                        self._is_enabled
                        and self._implicit_arm
                        and arm
                        and self._control_surface._target_track_component.target_track == track
                    )
class Launchpad_Pro(IdentifiableControlSurface, OptimizedControlSurface):
    identity_request = consts.SYSEX_IDENTITY_REQUEST

    def __init__(self, c_instance, *a, **k):
        product_id_bytes = consts.MANUFACTURER_ID + consts.DEVICE_CODE
        super(Launchpad_Pro, self).__init__(c_instance=c_instance, product_id_bytes=product_id_bytes, *a, **k)
        self._challenge = Live.Application.get_random_int(0, 400000000) & 2139062143
        with self.component_guard():
            self._skin = make_default_skin()
            with inject(skin=const(self._skin)).everywhere():
                self._midimap = MidiMap()
            self._target_track_component = TargetTrackComponent(name='Target_Track')
            self._create_background()
            self._create_global_component()
            self._last_sent_mode_byte = None
            with inject(layout_setup=const(self._layout_setup), should_arm=const(self._should_arm_track)).everywhere():
                self._create_session()
                self._create_recording()
                self._create_actions()
                self._create_drums()
                self._create_mixer()
                self._create_device()
                self._create_modes()
                self._create_user()
            self._on_session_record_changed.subject = self.song()
        self.set_device_component(self._device)
        self._on_session_record_changed()
        return

    def disconnect(self):
        self._send_midi(consts.TURN_OFF_LEDS)
        self._send_midi(consts.QUIT_MESSAGE)
        super(Launchpad_Pro, self).disconnect()

    def _create_background(self):
        self._modifier_background_component = ModifierBackgroundComponent(name='Background_Component', is_enabled=False, layer=Layer(shift_button=self._midimap['Shift_Button']))
        self._shifted_background = BackgroundComponent(name='No_Op_Shifted_Buttons', is_enabled=False, layer=Layer(click_bitton=self._midimap.with_shift('Click_Button'), delete_button=self._midimap.with_shift('Delete_Button'), duplicate_button=self._midimap.with_shift('Duplicate_Button'), double_button=self._midimap.with_shift('Double_Loop_Button'), session_record_button=self._midimap.with_shift('Session_Record_Button')))

    def _create_global_component(self):
        self._actions_component = ActionsComponent(name='Global_Actions', is_enabled=False, layer=Layer(undo_button=self._midimap['Undo_Button'], redo_button=self._midimap.with_shift('Undo_Button'), metronome_button=self._midimap['Click_Button'], quantization_on_button=self._midimap.with_shift('Quantize_Button')))

    def _create_session(self):
        self._session = SessionComponent(NUM_TRACKS, NUM_SCENES, auto_name=True, is_enabled=False, enable_skinning=True, layer=Layer(track_bank_left_button=self._midimap['Arrow_Left_Button'], track_bank_right_button=self._midimap['Arrow_Right_Button'], scene_bank_up_button=self._midimap['Arrow_Up_Button'], scene_bank_down_button=self._midimap['Arrow_Down_Button']))
        self._session.set_enabled(True)
        self._session.set_rgb_mode(LIVE_COLORS_TO_MIDI_VALUES, RGB_COLOR_TABLE)
        SpecialClipSlotComponent.quantization_component = self._actions_component
        for scene_index in xrange(NUM_SCENES):
            scene = self._session.scene(scene_index)
            scene.layer = Layer(select_button=self._midimap['Shift_Button'], delete_button=self._midimap['Delete_Button'], duplicate_button=self._midimap['Duplicate_Button'])
            for track_index in xrange(NUM_TRACKS):
                slot = scene.clip_slot(track_index)
                slot.layer = Layer(select_button=self._midimap['Shift_Button'], delete_button=self._midimap['Delete_Button'], duplicate_button=self._midimap['Duplicate_Button'], double_loop_button=self._midimap['Double_Loop_Button'], quantize_button=self._midimap['Quantize_Button'])

        self._session_zoom = SessionZoomingComponent(self._session, name='Session_Overview', is_enabled=True, enable_skinning=True)

    def _create_recording(self):
        self._session_record = SpecialSessionRecordingComponent(self._target_track_component, name='Session_Recording', is_enabled=False, layer=Layer(record_button=self._midimap['Session_Record_Button']))

    def _create_actions(self):
        self._clip_actions_component = ClipActionsComponent(self._target_track_component, name='Clip_Actions', is_enabled=False, layer=Layer(duplicate_button=self._midimap['Duplicate_Button'], double_button=self._midimap['Double_Loop_Button'], quantize_button=self._midimap['Quantize_Button']))
        ClipActionsComponent.quantization_component = self._actions_component

    def _create_drums(self):
        self._drum_group_finder = DrumGroupFinderComponent(self._target_track_component, name='Drum_Group_Finder', is_enabled=False, layer=None)
        self._on_drum_group_changed.subject = self._drum_group_finder
        self._drum_group_finder.set_enabled(True)
        self._drum_group = DrumGroupComponent(self._clip_actions_component, name='Drum_Group_Control', translation_channel=consts.DR_MAP_CHANNEL)
        self._drum_group.set_enabled(True)
        return

    def _create_mixer(self):
        self._mixer = SpecialMixerComponent(NUM_TRACKS, auto_name=True, is_enabled=True, invert_mute_feedback=True)
        self._mixer.name = 'Mixer_Control'
        self._session.set_mixer(self._mixer)

    def _create_device(self):
        self._device = SpecialDeviceComponent(name='Device_Control', is_enabled=False, device_selection_follows_track_selection=True)
        self._device_navigation = DeviceNavigationComponent(name='Device_Navigation')
        self._device_background = BackgroundComponent(name='Device_Background_Component')

    def _setup_drum_group(self):
        self._drum_group.set_drum_group_device(self._drum_group_finder.drum_group)

    def _create_translation(self, comp_name, channel, button_layer, should_enable=True, should_reset=True):
        translation_component = TranslationComponent(name=comp_name, translated_channel=channel, should_enable=should_enable, should_reset=should_reset, is_enabled=False, layer=button_layer)
        setattr(self, '_' + comp_name.lower(), translation_component)
        return translation_component

    def _create_modes(self):
        self._modes = ModesComponent(name='Launchpad_Modes', is_enabled=False)
        self._session_layer_mode = AddLayerMode(self._session, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix'], clip_launch_buttons=self._midimap['Main_Button_Matrix'], delete_button=self._midimap['Delete_Button'], duplicate_button=self._midimap['Duplicate_Button'], double_button=self._midimap['Double_Loop_Button'], quantize_button=self._midimap['Quantize_Button']))
        action_button_background = BackgroundComponent(name='No_Op_Buttons')
        self._action_button_background_layer_mode = LayerMode(action_button_background, Layer(delete_button=self._midimap['Delete_Button'], quantize_button=self._midimap['Quantize_Button'], duplicate_button=self._midimap['Duplicate_Button'], double_button=self._midimap['Double_Loop_Button']))
        self._clip_delete_layer_mode = AddLayerMode(self._clip_actions_component, layer=Layer(delete_button=self._midimap['Delete_Button']))
        self._create_session_zooming_modes()
        self._create_session_mode()
        self._create_note_modes()
        self._create_device_mode()
        self._create_user_mode()
        self._create_record_arm_mode()
        self._create_track_select_mode()
        self._create_mute_mode()
        self._create_solo_mode()
        self._create_volume_mode()
        self._create_pan_mode()
        self._create_sends_mode()
        self._create_stop_clips_mode()
        self._modes.layer = Layer(session_mode_button=self._midimap['Session_Mode_Button'], note_mode_button=self._midimap['Note_Mode_Button'], device_mode_button=self._midimap['Device_Mode_Button'], user_mode_button=self._midimap['User_Mode_Button'], record_arm_mode_button=self._midimap['Record_Arm_Mode_Button'], track_select_mode_button=self._midimap['Track_Select_Mode_Button'], mute_mode_button=self._midimap['Mute_Mode_Button'], solo_mode_button=self._midimap['Solo_Mode_Button'], volume_mode_button=self._midimap['Volume_Mode_Button'], pan_mode_button=self._midimap['Pan_Mode_Button'], sends_mode_button=self._midimap['Sends_Mode_Button'], stop_clip_mode_button=self._midimap['Stop_Clip_Mode_Button'])
        self._modes.selected_mode = 'session_mode'
        self._on_layout_changed.subject = self._modes

    def _create_session_zooming_modes(self):
        session_zoom_layer = Layer(button_matrix=self._midimap['Main_Button_Matrix'], nav_left_button=self._midimap['Arrow_Left_Button'], nav_right_button=self._midimap['Arrow_Right_Button'], nav_up_button=self._midimap['Arrow_Up_Button'], nav_down_button=self._midimap['Arrow_Down_Button'])
        session_zooming_layer_mode = LayerMode(self._session_zoom, session_zoom_layer)
        self._session_zooming_manager = SessionZoomingManagerComponent(self._modes, is_enabled=False)
        session_zooming_button_layer_mode = LayerMode(self._session_zooming_manager, Layer(session_zooming_button=self._midimap['Session_Mode_Button']))
        self._prioritized_session_zooming_button_layer_mode = LayerMode(self._session_zooming_manager, Layer(session_zooming_button=self._midimap['Session_Mode_Button'], priority=1))
        self._session_zooming_background = BackgroundComponent(name='Session_Zooming_Background')
        session_zooming_background_layer_mode = LayerMode(self._session_zooming_background, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix'], delete_button=self._midimap['Delete_Button'], quantize_button=self._midimap['Quantize_Button'], duplicate_button=self._midimap['Duplicate_Button'], double_loop_button=self._midimap['Double_Loop_Button']))
        self._modes.add_mode('session_zooming_mode', [
         self._session_zooming_manager,
         session_zooming_button_layer_mode,
         session_zooming_layer_mode,
         session_zooming_background_layer_mode])
        self._modes.add_mode('prioritized_session_zooming_mode', [
         partial(self._layout_switch, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         session_zooming_layer_mode,
         session_zooming_background_layer_mode,
         self.update])

    def _create_session_mode(self):
        self._modes.add_mode('session_mode', [
         partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_layer_mode,
         self._session.update_navigation_buttons], behaviour=CancelingReenterBehaviour('session_zooming_mode'))

    def _create_note_modes(self):
        note_mode_matrix_translation = self._create_translation('Note_Mode_Matrix_Translation', consts.CHROM_MAP_CHANNEL, Layer(button_matrix=self._midimap['Main_Button_Matrix'], note_button_matrix=self._midimap['Note_Button_Matrix'], drum_matrix=self._midimap['Drum_Button_Matrix'], mixer_button_matrix=self._midimap['Mixer_Button_Matrix']), should_enable=False)
        note_mode_scene_launch_translation = self._create_translation('Note_Mode_Scene_Launch_Translation', consts.CHROM_MAP_CHANNEL, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix']))
        scale_setup_mode_button_lighting = LedLightingComponent(name='LED_Lighting_Component', is_enabled=False, layer=Layer(button=self._midimap.with_shift('Note_Mode_Button')))
        drum_mode_note_matrix_translation = self._create_translation('Drum_Mode_Note_Button_Translation', 0, Layer(note_button_matrix=self._midimap['Note_Button_Matrix']), should_enable=False, should_reset=False)
        drum_group_layer_mode = LayerMode(self._drum_group, layer=Layer(scroll_up_button=self._midimap['Arrow_Left_Button'], scroll_down_button=self._midimap['Arrow_Right_Button'], scroll_page_up_button=self._midimap['Arrow_Up_Button'], scroll_page_down_button=self._midimap['Arrow_Down_Button'], drum_matrix=self._midimap['Drum_Button_Matrix'], select_button=self._midimap['Shift_Button'], delete_button=self._midimap['Delete_Button']))
        self._note_modes = SpecialModesComponent(name='Note_Modes')
        self._note_modes.add_mode('chromatic_mode', [
         partial(self._layout_setup, consts.NOTE_LAYOUT_SYSEX_BYTE),
         self._clip_delete_layer_mode,
         note_mode_matrix_translation,
         scale_setup_mode_button_lighting])
        self._note_modes.add_mode('drum_mode', [
         partial(self._layout_setup, consts.DRUM_LAYOUT_SYSEX_BYTE),
         self._setup_drum_group,
         drum_group_layer_mode,
         drum_mode_note_matrix_translation])
        self._note_modes.add_mode('audio_mode', [
         partial(self._layout_setup, consts.AUDIO_LAYOUT_SYSEX_BYTE),
         self._clip_delete_layer_mode])
        self._note_modes.set_enabled(False)
        self._modes.add_mode('note_mode', [
         note_mode_scene_launch_translation,
         self._note_modes,
         self._select_note_mode,
         self._select_target_track,
         self._clip_actions_component,
         self._show_playing_clip,
         self._set_clip_actions_type], behaviour=ReenterBehaviour(self.toggle_detail_view))
        self._session_record.set_modes_component(self._modes)
        self._session_record.set_note_mode_name('note_mode')

    def _create_device_mode(self):
        device_mode_scene_launch_translation = self._create_translation('Device_Mode_Scene_Launch_Translation', consts.DEVICE_MAP_CHANNEL, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix']))
        device_layer_mode = LayerMode(self._device, layer=Layer(parameter_controls=self._midimap['Slider_Button_Matrix']))
        device_nav_layer_mode = LayerMode(self._device_navigation, layer=Layer(device_nav_left_button=self._midimap['Arrow_Left_Button'], device_nav_right_button=self._midimap['Arrow_Right_Button']))
        device_background_layer_mode = LayerMode(self._device_background, layer=Layer(arrow_up_button=self._midimap['Arrow_Up_Button'], arrow_down_button=self._midimap['Arrow_Down_Button']))
        self._modes.add_mode('device_mode', [
         partial(self._layout_setup, consts.FADER_LAYOUT_SYSEX_BYTE),
         self._device,
         device_layer_mode,
         device_nav_layer_mode,
         device_background_layer_mode,
         self._clip_actions_component,
         self._clip_delete_layer_mode,
         device_mode_scene_launch_translation,
         self._show_playing_clip,
         self._set_clip_actions_type], behaviour=ReenterBehaviour(self.toggle_detail_view))

    def _create_user_mode(self):
        self._modes.add_mode('user_mode', [
         partial(self._layout_setup, consts.USER_LAYOUT_SYSEX_BYTE)])

    def _create_record_arm_mode(self):
        arm_layer_mode = LayerMode(self._mixer, layer=Layer(arm_buttons=self._midimap['Mixer_Button_Matrix']))
        self._modes.add_mode('record_arm_mode', [
         partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_layer_mode,
         arm_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_track_select_mode(self):
        track_select_layer_mode = LayerMode(self._mixer, layer=Layer(track_select_buttons=self._midimap['Mixer_Button_Matrix']))
        self._modes.add_mode('track_select_mode', [
         partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_layer_mode,
         track_select_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_mute_mode(self):
        mute_layer_mode = LayerMode(self._mixer, layer=Layer(mute_buttons=self._midimap['Mixer_Button_Matrix']))
        self._modes.add_mode('mute_mode', [
         partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_layer_mode,
         mute_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_solo_mode(self):
        solo_layer_mode = LayerMode(self._mixer, layer=Layer(solo_buttons=self._midimap['Mixer_Button_Matrix']))
        self._modes.add_mode('solo_mode', [
         partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_layer_mode,
         solo_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_volume_mode(self):
        volume_mode_scene_launch_translation = self._create_translation('Volume_Mode_Scene_Launch_Translation', consts.VOLUME_MAP_CHANNEL, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix']))
        volume_layer_mode = LayerMode(self._mixer, layer=Layer(volume_controls=self._midimap['Slider_Button_Matrix']))
        self._modes.add_mode('volume_mode', [
         partial(self._layout_setup, consts.FADER_LAYOUT_SYSEX_BYTE),
         volume_layer_mode,
         self._action_button_background_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         volume_mode_scene_launch_translation,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_pan_mode(self):
        pan_mode_scene_launch_translation = self._create_translation('Pan_Mode_Scene_Launch_Translation', consts.PAN_MAP_CHANNEL, Layer(scene_launch_buttons=self._midimap['Scene_Launch_Button_Matrix']))
        pan_layer_mode = LayerMode(self._mixer, layer=Layer(pan_controls=self._midimap['Slider_Button_Matrix']))
        self._modes.add_mode('pan_mode', [
         partial(self._layout_setup, consts.FADER_LAYOUT_SYSEX_BYTE),
         pan_layer_mode,
         self._action_button_background_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         pan_mode_scene_launch_translation,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_sends_mode(self):
        send_layer_mode = LayerMode(self._mixer, layer=Layer(send_controls=self._midimap['Slider_Button_Matrix'], send_select_buttons=self._midimap['Scene_Launch_Button_Matrix']))
        self._modes.add_mode('sends_mode', [
         partial(self._layout_setup, consts.FADER_LAYOUT_SYSEX_BYTE),
         send_layer_mode,
         self._action_button_background_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def _create_stop_clips_mode(self):
        stop_layer_mode = AddLayerMode(self._session, Layer(stop_track_clip_buttons=self._midimap['Mixer_Button_Matrix'], stop_scene_clip_buttons=self._midimap['Scene_Stop_Button_Matrix'], stop_all_clips_button=self._midimap['Stop_All_Clips_Button']))
        self._modes.add_mode('stop_clip_mode', [
         partial(self._layout_setup, consts.SESSION_LAYOUT_SYSEX_BYTE),
         self._session_layer_mode,
         stop_layer_mode,
         self._session_zooming_manager,
         self._prioritized_session_zooming_button_layer_mode,
         self._session.update_navigation_buttons], behaviour=SpecialReenterBehaviour('session_mode'))

    def toggle_detail_view(self):
        view = self.application().view
        if view.is_view_visible('Detail'):
            if view.is_view_visible('Detail/DeviceChain'):
                view.show_view('Detail/Clip')
            else:
                view.show_view('Detail/DeviceChain')

    def _create_user(self):
        self._user_matrix_component = UserMatrixComponent(name='User_Matrix_Component', is_enabled=False, layer=Layer(user_button_matrix_ch_6=self._midimap['User_Button_Matrix_Ch_6'], user_button_matrix_ch_7=self._midimap['User_Button_Matrix_Ch_7'], user_button_matrix_ch_8=self._midimap['User_Button_Matrix_Ch_8'], user_button_matrix_ch_14=self._midimap['User_Button_Matrix_Ch_14'], user_button_matrix_ch_15=self._midimap['User_Button_Matrix_Ch_15'], user_button_matrix_ch_16=self._midimap['User_Button_Matrix_Ch_16'], user_left_side_button_matrix_ch_6=self._midimap['User_Left_Side_Button_Matrix_Ch_6'], user_left_side_button_matrix_ch_7=self._midimap['User_Left_Side_Button_Matrix_Ch_7'], user_left_side_button_matrix_ch_8=self._midimap['User_Left_Side_Button_Matrix_Ch_8'], user_left_side_button_matrix_ch_14=self._midimap['User_Left_Side_Button_Matrix_Ch_14'], user_left_side_button_matrix_ch_15=self._midimap['User_Left_Side_Button_Matrix_Ch_15'], user_left_side_button_matrix_ch_16=self._midimap['User_Left_Side_Button_Matrix_Ch_16'], user_right_side_button_matrix_ch_6=self._midimap['User_Right_Side_Button_Matrix_Ch_6'], user_right_side_button_matrix_ch_7=self._midimap['User_Right_Side_Button_Matrix_Ch_7'], user_right_side_button_matrix_ch_8=self._midimap['User_Right_Side_Button_Matrix_Ch_8'], user_right_side_button_matrix_ch_14=self._midimap['User_Right_Side_Button_Matrix_Ch_14'], user_right_side_button_matrix_ch_15=self._midimap['User_Right_Side_Button_Matrix_Ch_15'], user_right_side_button_matrix_ch_16=self._midimap['User_Right_Side_Button_Matrix_Ch_16'], user_bottom_button_matrix_ch_6=self._midimap['User_Bottom_Button_Matrix_Ch_6'], user_bottom_button_matrix_ch_7=self._midimap['User_Bottom_Button_Matrix_Ch_7'], user_bottom_button_matrix_ch_8=self._midimap['User_Bottom_Button_Matrix_Ch_8'], user_bottom_button_matrix_ch_14=self._midimap['User_Bottom_Button_Matrix_Ch_14'], user_bottom_button_matrix_ch_15=self._midimap['User_Bottom_Button_Matrix_Ch_15'], user_bottom_button_matrix_ch_16=self._midimap['User_Bottom_Button_Matrix_Ch_16'], user_arrow_button_matrix_ch_6=self._midimap['User_Arrow_Button_Matrix_Ch_6'], user_arrow_button_matrix_ch_7=self._midimap['User_Arrow_Button_Matrix_Ch_7'], user_arrow_button_matrix_ch_8=self._midimap['User_Arrow_Button_Matrix_Ch_8'], user_arrow_button_matrix_ch_14=self._midimap['User_Arrow_Button_Matrix_Ch_14'], user_arrow_button_matrix_ch_15=self._midimap['User_Arrow_Button_Matrix_Ch_15'], user_arrow_button_matrix_ch_16=self._midimap['User_Arrow_Button_Matrix_Ch_16']))
        self._user_matrix_component.set_enabled(True)

    @subject_slot('drum_group')
    def _on_drum_group_changed(self):
        if self._note_modes.selected_mode == 'drum_mode':
            self._drum_group.set_drum_group_device(self._drum_group_finder.drum_group)
        if self._modes.selected_mode == 'note_mode':
            self._select_note_mode()
        else:
            self.release_controlled_track()
        self._update_note_mode_button(self._drum_group_finder.drum_group is not None)
        return

    def _select_note_mode(self):
        u"""
        Selects which note mode to use depending on the kind of
        current target track and its device chain.  Will also
        select the target if specified.
        """
        track = self._target_track_component.target_track
        drum_device = self._drum_group_finder.drum_group
        if track is None or track.is_foldable or track in self.song().return_tracks or track == self.song().master_track or track.is_frozen or track.has_audio_input:
            self._note_modes.selected_mode = 'audio_mode'
        else:
            if drum_device:
                self._note_modes.selected_mode = 'drum_mode'
            else:
                self._note_modes.selected_mode = 'chromatic_mode'
        self._modes.update()
        if self._note_modes.selected_mode == 'audio_mode':
            self.release_controlled_track()
        else:
            self.set_controlled_track(self._target_track_component.target_track)
        return

    def _select_target_track(self):
        track = self._target_track_component.target_track
        if track != self.song().view.selected_track:
            self.song().view.selected_track = track

    def _update_note_mode_button(self, focused_track_is_drum_track):
        button = self._midimap['Note_Mode_Button']
        if focused_track_is_drum_track:
            button.default_states = {True: 'Mode.Drum.On', False: 'Mode.Drum.Off'}
        else:
            button.default_states = {True: 'Mode.Chromatic.On', False: 'Mode.Chromatic.Off'}
        button.reset_state()
        self._modes.update()

    def _show_playing_clip(self):
        track = None
        if self._use_sel_track():
            track = self.song().view.selected_track
        else:
            track = self._target_track_component.target_track
        if track in self.song().tracks:
            slot_index = track.fired_slot_index
            if slot_index < 0:
                slot_index = track.playing_slot_index
            if slot_index >= 0:
                clip_slot = track.clip_slots[slot_index]
                self.song().view.highlighted_clip_slot = clip_slot
        return

    def _set_clip_actions_type(self):
        self._clip_actions_component.use_selected_track(self._use_sel_track())
        self._clip_actions_component.update()

    def _use_sel_track(self):
        return self._modes.selected_mode == 'device_mode'

    def _should_arm_track(self):
        return self._modes.selected_mode == 'record_arm_mode'

    @subject_slot('selected_mode')
    def _on_layout_changed(self, mode):
        if mode == 'note_mode':
            self.set_controlled_track(self._target_track_component.target_track)
        else:
            self.release_controlled_track()
        self._session_record.set_enabled(mode != 'user_mode')

    @subject_slot('session_record')
    def _on_session_record_changed(self):
        status = self.song().session_record
        feedback_color = int(self._skin['Instrument.FeedbackRecord'] if status else self._skin['Instrument.Feedback'])
        self._c_instance.set_feedback_velocity(feedback_color)

    def _clear_send_cache(self):
        with self.component_guard():
            for control in self.controls:
                control.clear_send_cache()

    def _update_hardware(self):
        self._clear_send_cache()
        self.update()

    def _update_global_components(self):
        self._actions_component.update()
        self._session_record.update()
        self._modifier_background_component.update()

    def _layout_setup(self, mode):
        self._layout_switch(mode)
        self._clear_send_cache()
        self._update_global_components()

    def _layout_switch(self, mode):
        prefix = consts.SYSEX_STANDARD_PREFIX + consts.SYSEX_PARAM_BYTE_LAYOUT
        suffix = consts.SYSEX_STANDARD_SUFFIX
        self._send_midi(prefix + mode + suffix)
        self._last_sent_mode_byte = mode

    def port_settings_changed(self):
        self.set_highlighting_session_component(None)
        super(Launchpad_Pro, self).port_settings_changed()
        return

    def on_identified(self):
        self._send_challenge()

    def _send_challenge(self):
        challenge_bytes = []
        for index in range(4):
            challenge_bytes.append(self._challenge >> 8 * index & 127)

        challenge = consts.CHALLENGE_PREFIX + tuple(challenge_bytes) + (247, )
        self._send_midi(challenge)

    def _on_handshake_successful(self):
        self._do_send_midi(consts.LIVE_MODE_SWITCH_REQUEST)
        with self.component_guard():
            self._modes.set_enabled(True)
            self._actions_component.set_enabled(True)
            self._session_record.set_enabled(True)
            self._modifier_background_component.set_enabled(True)
            self._shifted_background.set_enabled(True)
            self.release_controlled_track()
            self.set_feedback_channels(consts.FEEDBACK_CHANNELS)
        if self._last_sent_mode_byte is not None:
            self._layout_setup(self._last_sent_mode_byte)
        self.set_highlighting_session_component(self._session)
        self.update()
        return

    def _is_challenge_response(self, midi_bytes):
        return len(midi_bytes) == 10 and midi_bytes[:7] == consts.SYSEX_STANDARD_PREFIX + consts.SYSEX_CHALLENGE_RESPONSE_BYTE

    def _is_response_valid(self, midi_bytes):
        response = long(midi_bytes[7])
        response += long(midi_bytes[8] << 8)
        return response == Live.Application.encrypt_challenge2(self._challenge)

    def handle_sysex(self, midi_bytes):
        if len(midi_bytes) < 7:
            pass
        else:
            if self._is_challenge_response(midi_bytes) and self._is_response_valid(midi_bytes):
                self._on_handshake_successful()
            else:
                if midi_bytes[6] == consts.SYSEX_STATUS_BYTE_LAYOUT and midi_bytes[7] == consts.NOTE_LAYOUT_SYSEX_BYTE[0]:
                    self._update_hardware()
                else:
                    if midi_bytes[6] in (consts.SYSEX_STATUS_BYTE_MODE,
                     consts.SYSEX_STATUS_BYTE_LAYOUT):
                        pass
                    else:
                        super(Launchpad_Pro, self).handle_sysex(midi_bytes)