class Launchpad_X(InstrumentControlMixin, NovationBase): model_family_code = ids.LP_X_FAMILY_CODE element_class = Elements channel_strip_class = ChannelStripComponent session_recording_class = mixin(SessionRecordingMixin, SessionRecordingComponent) track_recording_class = mixin(SessionRecordingMixin, TrackRecordingComponent) target_track_class = ArmedTargetTrackComponent skin = skin def __init__(self, *a, **k): self._last_layout_byte = sysex.SESSION_LAYOUT_BYTE (super(Launchpad_X, self).__init__)(*a, **k) def on_identified(self, midi_bytes): self._elements.firmware_mode_switch.send_value(sysex.DAW_MODE_BYTE) self._elements.layout_switch.send_value(self._last_layout_byte) self._target_track_changed() self._drum_group_changed() self.set_feedback_channels( [DRUM_FEEDBACK_CHANNEL, SCALE_FEEDBACK_CHANNEL]) super(Launchpad_X, self).on_identified(midi_bytes) def _create_components(self): super(Launchpad_X, self)._create_components() self._background = BackgroundComponent(name='Background', add_nop_listeners=True) self._session_layout_mode = partial( self._elements.layout_switch.send_value, sysex.SESSION_LAYOUT_BYTE) self._create_recording_modes() self._create_mixer_modes() self._create_session_modes() self._create_note_modes() self._create_main_modes() self._mixer.set_send_controls = nop self._Launchpad_X__on_layout_switch_value.subject = self._elements.layout_switch def _create_mixer_modes(self): self._mixer_modes = ModesComponent( name='Mixer_Modes', is_enabled=False, enable_skinning=True, layer=Layer( volume_button=(self._elements.scene_launch_buttons_raw[0]), pan_button=(self._elements.scene_launch_buttons_raw[1]), send_a_button=(self._elements.scene_launch_buttons_raw[2]), send_b_button=(self._elements.scene_launch_buttons_raw[3]), stop_button=(self._elements.scene_launch_buttons_raw[4]), mute_button=(self._elements.scene_launch_buttons_raw[5]), solo_button=(self._elements.scene_launch_buttons_raw[6]), arm_button=(self._elements.scene_launch_buttons_raw[7]))) bottom_row = self._elements.clip_launch_matrix.submatrix[:, 7:8] select_none_mode = partial(setattr, self._mixer_modes, 'selected_mode', 'none') self._mixer_modes.add_mode('none', self._session_layout_mode) button_fader_layout_mode = partial( self._elements.layout_switch.send_value, sysex.FADERS_LAYOUT_BYTE) def add_fader_mode(name, color, is_pan=False): control_dict = {'{}_controls'.format(name): 'button_faders'} if is_pan: control_dict[ 'track_color_controls'] = 'button_fader_color_elements' else: control_dict[ 'static_color_controls'] = 'button_fader_color_elements' self._mixer_modes.add_mode( name, (partial( self._elements.button_fader_setup_element.send_value, sysex.FADER_HORIZONTAL_ORIENTATION if is_pan else sysex.FADER_VERTICAL_ORIENTATION, sysex.FADER_BIPOLAR if is_pan else sysex.FADER_UNIPOLAR), partial(self._mixer.set_static_color_value, color), self._clear_send_cache_of_button_fader_color_elements, AddLayerMode(self._mixer, Layer(**control_dict)), button_fader_layout_mode), behaviour=ReenterBehaviour(on_reenter=select_none_mode)) add_fader_mode('volume', Rgb.GREEN.midi_value) add_fader_mode('pan', 0, True) add_fader_mode('send_a', Rgb.VIOLET.midi_value) add_fader_mode('send_b', Rgb.DARK_BLUE.midi_value) self._mixer_modes.add_mode( 'stop', (self._session_layout_mode, AddLayerMode(self._session, Layer(stop_track_clip_buttons=bottom_row))), behaviour=ReenterBehaviour(on_reenter=select_none_mode)) self._mixer_modes.add_mode( 'mute', (self._session_layout_mode, AddLayerMode(self._mixer, Layer(mute_buttons=bottom_row))), behaviour=ReenterBehaviour(on_reenter=select_none_mode)) self._mixer_modes.add_mode( 'solo', (self._session_layout_mode, AddLayerMode(self._mixer, Layer(solo_buttons=bottom_row))), behaviour=ReenterBehaviour(on_reenter=select_none_mode)) self._mixer_modes.add_mode( 'arm', (self._session_layout_mode, AddLayerMode(self._mixer, Layer(arm_buttons=bottom_row))), behaviour=ReenterBehaviour(on_reenter=select_none_mode)) self._mixer_modes.selected_mode = 'none' def _clear_send_cache_of_button_fader_color_elements(self): for element in self._elements.button_fader_color_elements_raw: element.clear_send_cache() def _create_session_modes(self): self._session_overview = SessionOverviewComponent( name='Session_Overview', is_enabled=False, session_ring=(self._session_ring), enable_skinning=True, layer=Layer(button_matrix='clip_launch_matrix')) self._session_modes = SessionModesComponent( name='Session_Modes', is_enabled=False, layer=Layer( cycle_mode_button='session_mode_button', mode_button_color_control='session_button_color_element')) self._session_modes.add_mode( 'launch', AddLayerMode(self._session, Layer(scene_launch_buttons='scene_launch_buttons'))) self._session_modes.add_mode( 'mixer', DelayMode(self._mixer_modes, SESSION_MODES_SWITCH_DELAY)) (self._session_modes.add_mode( 'overview', (self._session_layout_mode, self._session_overview, AddLayerMode( self._session_navigation, Layer(page_up_button='up_button', page_down_button='down_button', page_left_button='left_button', page_right_button='right_button')), AddLayerMode( self._background, Layer(scene_launch_buttons='scene_launch_buttons')))), ) self._session_modes.selected_mode = 'launch' def _create_note_modes(self): self._drum_group = DrumGroupComponent( name='Drum_Group', is_enabled=False, translation_channel=DRUM_FEEDBACK_CHANNEL, layer=Layer(matrix='drum_pads', scroll_up_button='left_button', scroll_down_button='right_button', scroll_page_up_button='up_button', scroll_page_down_button='down_button')) self._scale_pad_translator = ConfigurablePlayableComponent( SCALE_FEEDBACK_CHANNEL, name='Scale_Pads', is_enabled=False, layer=Layer(matrix='scale_pads')) self._note_modes = ModesComponent(name='Note_Modes', is_enabled=False) self._note_modes.add_mode( 'scale', (self._scale_pad_translator, AddLayerMode((self._background), layer=Layer(up_button='up_button', down_button='down_button', left_button='left_button', right_button='right_button')))) self._note_modes.add_mode('drum', self._drum_group) self._note_modes.selected_mode = 'scale' self._Launchpad_X__on_note_mode_changed.subject = self._note_modes def _create_main_modes(self): self._main_modes = ModesComponent( name='Main_Modes', is_enabled=False, layer=Layer(session_button='session_mode_button', note_button='note_mode_button', custom_button='custom_mode_button')) self._main_modes.add_mode('session', (self._session_modes), behaviour=(ImmediateBehaviour())) self._main_modes.add_mode('note', (self._note_modes), behaviour=(ImmediateBehaviour())) self._main_modes.add_mode('custom', None, behaviour=(ImmediateBehaviour())) self._main_modes.selected_mode = 'session' self._main_modes.set_enabled(True) self._Launchpad_X__on_main_mode_changed.subject = self._main_modes @listens('selected_mode') def __on_main_mode_changed(self, mode): self._recording_modes.selected_mode = 'track' if mode == 'note' else 'session' if mode == 'session': self._session_modes.revert_to_main_mode() self._update_controlled_track() self._elements.layout_switch.enquire_value() @listens('selected_mode') def __on_note_mode_changed(self, mode): if self._note_modes.is_enabled(): self._update_controlled_track() @listens('value') def __on_layout_switch_value(self, value): self._last_layout_byte = value def _drum_group_changed(self): drum_group = self._drum_group_finder.drum_group drum_groud_valid = liveobj_valid(drum_group) self._drum_group.set_drum_group_device(drum_group) self._elements.note_layout_switch.send_value( sysex.DRUM_LAYOUT_BYTE if drum_groud_valid else sysex. SCALE_LAYOUT_BYTE) self._note_modes.selected_mode = 'drum' if drum_groud_valid else 'scale' def _is_instrument_mode(self): return self._main_modes.selected_mode == 'note' def _feedback_velocity_changed(self, feedback_velocity): self._elements.scale_feedback_switch.send_value(feedback_velocity)
class Launchpad(ControlSurface): u""" Script for Novation's Launchpad Controller """ def __init__(self, c_instance): ControlSurface.__init__(self, c_instance) is_momentary = True self._suppress_send_midi = False with self.component_guard(): self._skin = make_default_skin() #with inject(skin=const(self._skin)).everywhere(): # self._midimap = MidiMap() self._suggested_input_port = u'Launchpad' self._suggested_output_port = u'Launchpad' self._control_is_with_automap = False self._user_byte_write_button = ButtonElement(is_momentary, MIDI_CC_TYPE, 0, 16) self._user_byte_write_button.name = u'User_Byte_Button' self._user_byte_write_button.send_value(1) self._user_byte_write_button.add_value_listener(self._user_byte_value) self._wrote_user_byte = False self._challenge = Live.Application.get_random_int(0, 400000000) & 2139062143 self._matrix = ButtonMatrixElement() self._matrix.name = u'Button_Matrix' #with inject(skin=const(self._skin)).everywhere(): for row in range(8): button_row = [] for column in range(8): button = ConfigurableButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, row * 16 + column, skin = self._skin) button.name = str(column) + u'_Clip_' + str(row) + u'_Button' button_row.append(button) self._matrix.add_row(tuple(button_row)) self._top_buttons = [ ConfigurableButtonElement(is_momentary, MIDI_CC_TYPE, 0, 104 + index, skin = self._skin) for index in range(8) ] self._side_buttons = [ ConfigurableButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, SIDE_NOTES[index], skin = self._skin) for index in range(8) ] self._top_matrix = ButtonMatrixElement(name = u'Top_Button_Matrix', rows = [self._top_buttons]) self._side_matrix = ButtonMatrixElement(name = u'Side_Button_Matrix', rows = [self._side_buttons]) self._config_button = ButtonElement(is_momentary, MIDI_CC_TYPE, 0, 0, optimized_send_midi=False) self._config_button.add_value_listener(self._config_value) self._top_buttons[0].name = u'Bank_Select_Up_Button' self._top_buttons[1].name = u'Bank_Select_Down_Button' self._top_buttons[2].name = u'Bank_Select_Left_Button' self._top_buttons[3].name = u'Bank_Select_Right_Button' self._top_buttons[4].name = u'Session_Button' self._top_buttons[5].name = u'User1_Button' self._top_buttons[6].name = u'User2_Button' self._top_buttons[7].name = u'Mixer_Button' self._side_buttons[0].name = u'Vol_Button' self._side_buttons[1].name = u'Pan_Button' self._side_buttons[2].name = u'SndA_Button' self._side_buttons[3].name = u'SndB_Button' self._side_buttons[4].name = u'Stop_Button' self._side_buttons[5].name = u'Trk_On_Button' self._side_buttons[6].name = u'Solo_Button' self._side_buttons[7].name = u'Arm_Button' self._create_background() self._create_translations() self._create_session() self._create_mixer() self._create_modes() def _create_background(self): self._background = BackgroundComponent(name = 'Background') self._background.layer = Layer(priority = 5, matrix = self._matrix, side_buttons = self._side_matrix, top_buttons = self._top_matrix) self._background.set_enabled(True) def _create_translations(self): self._translations1 = TranslationComponent(name = 'User1Translation', translated_channel=2, should_enable=True, should_reset=True, is_enabled=False, layer=Layer(matrix = self._matrix, top_buttons = self._top_matrix.submatrix[:4,1], side_buttons = self._side_matrix)) self._translations2 = TranslationComponent(name = 'User2Translation', translated_channel=3, should_enable=True, should_reset=True, is_enabled=False, layer=Layer(matrix = self._matrix, top_buttons = self._top_matrix.submatrix[:4,1], side_buttons = self._side_matrix)) def _create_session(self): self._session_ring = SessionRingComponent(8, 8, name = u'Session_Ring') self._session = SpecialSessionComponent(session_ring = self._session_ring, name = u'Session', is_enabled = False, auto_name = True) self._session_navigation = SessionNavigationComponent(session_ring = self._session_ring, name = u'SessionNavigation', is_enabled = False) self._session_navigation._vertical_banking.scroll_up_button.color = 'Session.NavigationButtonOn' self._session_navigation._vertical_banking.scroll_up_button.disabled_color = 'Session.NavigationButtonOff' self._session_navigation._vertical_banking.scroll_down_button.color = 'Session.NavigationButtonOn' self._session_navigation._vertical_banking.scroll_down_button.disabled_color = 'Session.NavigationButtonOff' self._session_navigation._horizontal_banking.scroll_up_button.color = 'Session.NavigationButtonOn' self._session_navigation._horizontal_banking.scroll_up_button.disabled_color = 'Session.NavigationButtonOff' self._session_navigation._horizontal_banking.scroll_down_button.color = 'Session.NavigationButtonOn' self._session_navigation._horizontal_banking.scroll_down_button.disabled_color = 'Session.NavigationButtonOff' self._session_navigation._vertical_paginator.scroll_up_button.color = 'Session.PageNavigationButtonOn' self._session_navigation._vertical_paginator.scroll_up_button.disabled_color = 'Session.PageNavigationButtonOff' self._session_navigation._vertical_paginator.scroll_down_button.color = 'Session.PageNavigationButtonOn' self._session_navigation._vertical_paginator.scroll_down_button.disabled_color = 'Session.PageNavigationButtonOff' self._session_navigation._horizontal_paginator.scroll_up_button.color = 'Session.PageNavigationButtonOn' self._session_navigation._horizontal_paginator.scroll_up_button.disabled_color = 'Session.PageNavigationButtonOff' self._session_navigation._horizontal_paginator.scroll_down_button.color = 'Session.PageNavigationButtonOn' self._session_navigation._horizontal_paginator.scroll_down_button.disabled_color = 'Session.PageNavigationButtonOff' self._zooming = SessionOverviewComponent(session_ring = self._session_ring, enable_skinning = True, name = u'Session_Zooming', is_enabled = False) self._zooming.layer = Layer(button_matrix = self._matrix) self._session_navigation.nav_layer = AddLayerMode(self._session_navigation, Layer(up_button = self._top_buttons[0], down_button = self._top_buttons[1], left_button = self._top_buttons[2], right_button = self._top_buttons[3])) self._session_navigation.page_layer = AddLayerMode(self._session_navigation, Layer(page_up_button = self._top_buttons[0], page_down_button = self._top_buttons[1], page_left_button = self._top_buttons[2], page_right_button = self._top_buttons[3])) self._session.cliplaunch_layer = AddLayerMode(self._session, Layer(priority = 1, clip_launch_buttons = self._matrix, scene_launch_buttons = self._side_matrix)) self._session.clipstop_layer = AddLayerMode(self._session, Layer(priority = 1, stop_all_clips_button = self._side_buttons[4], stop_track_clip_buttons = self._matrix.submatrix[:, 4:5])) def _create_mixer(self): self._mixer = SpecialMixerComponent(tracks_provider = self._session_ring, auto_name=True, is_enabled=True, invert_mute_feedback=True) self._mixer.name = u'Mixer' self._mixer.master_strip().name = u'Master_Channel_strip' self._mixer.selected_strip().name = u'Selected_Channel_strip' self._sliders = [] self._slider_volume_modes = [] self._slider_pan_modes = [] self._slider_single_modes = [] for column in range(8): self._mixer.channel_strip(column).name = u'Channel_Strip_' + str(column) self._sliders.append(PreciseButtonSliderElement(buttons = tuple([ self._matrix._orig_buttons[7-row][column]for row in range(8) ]))) self._sliders[-1].name = u'Button_Slider_' + str(column) self._slider_matrix = ButtonMatrixElement(name = u'SliderMatrix', rows = [self._sliders]) self._mixer.overview_layer = AddLayerMode(self._mixer, Layer(priority = 1, default_volume_buttons = self._matrix.submatrix[:, :1], default_panning_buttons = self._matrix.submatrix[:, 1:2], default_send1_buttons = self._matrix.submatrix[:, 2:3], default_send2_buttons = self._matrix.submatrix[:, 3:4], mute_buttons = self._matrix.submatrix[:, 5:6], solo_buttons = self._matrix.submatrix[:, 6:7], arm_buttons = self._matrix.submatrix[:, 7:8], unarm_all_button = self._side_buttons[7], unsolo_all_button = self._side_buttons[6], unmute_all_button = self._side_buttons[5])) self._mixer.volume_layer = AddLayerMode(self._mixer, Layer(priority = 1, volume_controls = self._slider_matrix)) self._mixer.pan_layer = AddLayerMode(self._mixer, Layer(priority = 1, pan_controls = self._slider_matrix)) self._mixer.send1_layer = AddLayerMode(self._mixer, Layer(priority = 1, send_controls = self._slider_matrix)) self._mixer.send2_layer = AddLayerMode(self._mixer, Layer(priority = 1, send_controls = self._slider_matrix)) def _create_modes(self): self._session_modes = ModesComponent(name = u'SessionModes') self._session_modes.add_mode(u'Session', [self._session, self._session.cliplaunch_layer, self._session_navigation, self._session_navigation.nav_layer], cycle_mode_button_color = u'MainModes.SelectedOn') self._session_modes.add_mode(u'Zoom', [self._zooming, self._session_navigation, self._session_navigation.page_layer], cycle_mode_button_color = u'MainModes.SelectedPressed') self._session_modes.selected_mode = u'Session' self._session_modes.layer = Layer(priority = 1, cycle_mode_button = self._top_buttons[4]) self._session_modes.set_enabled(False) self._mixer_modes = ModesComponent(name = u'MixerModes') self._mixer_modes.add_mode(u'Overview', [self._session_navigation, self._session_navigation.nav_layer, self._session, self._session.clipstop_layer, self._mixer, self._mixer.overview_layer]) self._mixer_modes.add_mode(u'TrackVolume', [self._session_navigation, self._session_navigation.nav_layer, self._mixer, self._mixer.volume_layer, tuple([self.set_slider_volume_mode, self.set_slider_single_mode])]) self._mixer_modes.add_mode(u'TrackPan', [self._session_navigation, self._session_navigation.nav_layer, self._mixer, self._mixer.pan_layer, tuple([self.set_slider_pan_mode, self.set_slider_single_mode])]) self._mixer_modes.add_mode(u'TrackSend1', [self._session_navigation, self._session_navigation.nav_layer, self._mixer, self._mixer.send1_layer, SetAttributeMode(obj=self._mixer, attribute=u'send_index', value=0), tuple([self.set_slider_send_mode, self.set_slider_single_mode])]) self._mixer_modes.add_mode(u'TrackSend2', [self._session_navigation, self._session_navigation.nav_layer, self._mixer, self._mixer.send2_layer, SetAttributeMode(obj=self._mixer, attribute=u'send_index', value=1), tuple([self.set_slider_send_mode, self.set_slider_single_mode])]) for mode in self._mixer_modes._mode_list: if mode == 'Overview': self._mixer_modes.get_mode_button(mode).mode_selected_color = u'MainModes.SelectedOn' self._mixer_modes.get_mode_button(mode).mode_unselected_color = u'MainModes.SelectedOn' else: self._mixer_modes.get_mode_button(mode).mode_selected_color = u'SubModes.SelectedOn' self._mixer_modes.get_mode_button(mode).mode_unselected_color = u'SubModes.SelectedOff' self._mixer_modes.selected_mode = 'Overview' self._mixer_modes.layer = Layer(priority = 1, Overview_button = self._top_buttons[7], TrackVolume_button = self._side_buttons[0], TrackPan_button = self._side_buttons[1], TrackSend1_button = self._side_buttons[2], TrackSend2_button = self._side_buttons[3]) self._mixer_modes.set_enabled(False) self._selector = ModesComponent(name = u'Selector') self._selector.add_mode(u'disabled', [self._background]) self._selector.add_mode(u'Session', [self._session_modes]) self._selector.add_mode(u'User1', [self._translations1]) self._selector.add_mode(u'User2', [self._translations2]) self._selector.add_mode(u'Mixer', [tuple([self.enable_sliders, self.disable_sliders]), self._mixer_modes]) for mode in self._selector._mode_list: self._selector.get_mode_button(mode).mode_selected_color = u'MainModes.SelectedOn' self._selector.get_mode_button(mode).mode_unselected_color = u'MainModes.SelectedOff' self._selector.layer = Layer(priority = 1, Session_button = self._top_buttons[4], User1_button = self._top_buttons[5], User2_button = self._top_buttons[6], Mixer_button = self._top_buttons[7]) self._selector.set_enabled(True) self._selector.selected_mode = u'disabled' def enable_sliders(self): for slider in self._sliders: slider._disabled = False def disable_sliders(self): for slider in self._sliders: slider._disabled = True def set_slider_single_mode(self): for slider in self._sliders: slider.set_mode(0) def set_slider_volume_mode(self): for slider in self._sliders: slider.set_mode(1) def set_slider_pan_mode(self): for slider in self._sliders: slider.set_mode(2) def set_slider_send_mode(self): for slider in self._sliders: slider.set_mode(1) def disconnect(self): self._suppress_send_midi = True for control in self.controls: if isinstance(control, ConfigurableButtonElement): control.remove_value_listener(self._button_value) self._selector = None self._user_byte_write_button.remove_value_listener(self._user_byte_value) self._config_button.remove_value_listener(self._config_value) super(Launchpad, self).disconnect() self._suppress_send_midi = False self._config_button.send_value(32) self._config_button.send_value(0) self._config_button = None self._user_byte_write_button.send_value(0) self._user_byte_write_button = None def refresh_state(self): super(Launchpad, self).refresh_state() self.schedule_message(5, self._update_hardware) def process_midi_bytes(self, midi_bytes, midi_processor): #def handle_sysex(self, midi_bytes): if len(midi_bytes) == 8: if midi_bytes[1:5] == (0, 32, 41, 6): response = long(midi_bytes[5]) response += long(midi_bytes[6]) << 8 if response == Live.Application.encrypt_challenge2(self._challenge): self._on_handshake_successful() else: super(Launchpad, self).process_midi_bytes(midi_bytes, midi_processor) def _on_handshake_successful(self): debug('Launchpad._on_handshake_successful') self._suppress_send_midi = False self.set_enabled(True) self._selector.selected_mode = 'Session' self._selector.set_enabled(True) debug(self._selector.selected_mode) """ def build_midi_map(self, midi_map_handle): ControlSurface.build_midi_map(self, midi_map_handle) #if self._selector.mode_index == 1: # new_channel = self._selector.channel_for_current_mode() # for note in DRUM_NOTES: # self._translate_message(MIDI_NOTE_TYPE, note, 0, note, new_channel) """ """ def _send_midi(self, midi_bytes, optimized = None): sent_successfully = False if not self._suppress_send_midi: sent_successfully = ControlSurface._send_midi(self, midi_bytes, optimized=optimized) return sent_successfully """ def _update_hardware(self): self._suppress_send_midi = False self._wrote_user_byte = True self._user_byte_write_button.send_value(1) self._suppress_send_midi = True self.set_enabled(False) self._suppress_send_midi = False self._send_challenge() def _send_challenge(self): for index in range(4): challenge_byte = self._challenge >> 8 * index & 127 self._send_midi((176, 17 + index, challenge_byte)) def _user_byte_value(self, value): if not value in range(128): raise AssertionError enabled = self._wrote_user_byte or value == 1 self._control_is_with_automap = not enabled self._suppress_send_midi = self._control_is_with_automap if not self._control_is_with_automap: for control in self.controls: if isinstance(control, ConfigurableButtonElement): control.set_force_next_value() #self._selector.set_mode(0) self._selector.selected_mode = u'Session' self.set_enabled(enabled) self._suppress_send_midi = False else: self._wrote_user_byte = False def _button_value(self, value): debug('_button_value', value, self._selector.selected_mode, self._selector.is_enabled()) #self._top_buttons[0].send_value(127) #assert value in range(128) def _config_value(self, value): assert value in range(128) """