diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fed0bf8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +################################################################################ +# This .gitignore file was automatically created by Microsoft(R) Visual Studio. +################################################################################ + +/.vs/AbletonRemoteScripts/v14/.suo diff --git a/.vs/AbletonRemoteScripts/v14/.suo b/.vs/AbletonRemoteScripts/v14/.suo new file mode 100644 index 00000000..2a87edd9 Binary files /dev/null and b/.vs/AbletonRemoteScripts/v14/.suo differ diff --git a/.vs/AbletonRemoteScripts/v16/.suo b/.vs/AbletonRemoteScripts/v16/.suo new file mode 100644 index 00000000..f9879a19 Binary files /dev/null and b/.vs/AbletonRemoteScripts/v16/.suo differ diff --git a/AbletonRemoteScripts.pyproj b/AbletonRemoteScripts.pyproj new file mode 100644 index 00000000..d7d18c23 --- /dev/null +++ b/AbletonRemoteScripts.pyproj @@ -0,0 +1,142 @@ + + + + Debug + 2.0 + {ed5fbfbf-4be8-482d-b574-58db3cd35247} + + Novation_Impulse2\__init__.py + + . + . + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + AbletonRemoteScripts + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AbletonRemoteScripts.pyproj.user b/AbletonRemoteScripts.pyproj.user new file mode 100644 index 00000000..76fe5a55 --- /dev/null +++ b/AbletonRemoteScripts.pyproj.user @@ -0,0 +1,6 @@ + + + + ProjectFiles + + \ No newline at end of file diff --git a/AbletonRemoteScripts.sln b/AbletonRemoteScripts.sln new file mode 100644 index 00000000..ce577c9f --- /dev/null +++ b/AbletonRemoteScripts.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29201.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "AbletonRemoteScripts", "AbletonRemoteScripts.pyproj", "{ED5FBFBF-4BE8-482D-B574-58DB3CD35247}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {043C1B30-6E87-4123-8E53-DA1C1379F802} + EndGlobalSection +EndGlobal diff --git a/Backup/AbletonRemoteScripts.sln b/Backup/AbletonRemoteScripts.sln new file mode 100644 index 00000000..2173f144 --- /dev/null +++ b/Backup/AbletonRemoteScripts.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "AbletonRemoteScripts", "AbletonRemoteScripts.pyproj", "{ED5FBFBF-4BE8-482D-B574-58DB3CD35247}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Backup1/AbletonRemoteScripts.sln b/Backup1/AbletonRemoteScripts.sln new file mode 100644 index 00000000..7177b599 --- /dev/null +++ b/Backup1/AbletonRemoteScripts.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "AbletonRemoteScripts", "AbletonRemoteScripts.pyproj", "{ED5FBFBF-4BE8-482D-B574-58DB3CD35247}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED5FBFBF-4BE8-482D-B574-58DB3CD35247}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Nocturn2/._.DS_Store b/Nocturn2/._.DS_Store new file mode 100644 index 00000000..c7f79192 Binary files /dev/null and b/Nocturn2/._.DS_Store differ diff --git a/Nocturn2/._MixerController.py b/Nocturn2/._MixerController.py new file mode 100644 index 00000000..269384c4 Binary files /dev/null and b/Nocturn2/._MixerController.py differ diff --git a/Nocturn2/._Nocturn.py b/Nocturn2/._Nocturn.py new file mode 100644 index 00000000..eed6c1d6 Binary files /dev/null and b/Nocturn2/._Nocturn.py differ diff --git a/Nocturn2/._NocturnComponent.py b/Nocturn2/._NocturnComponent.py new file mode 100644 index 00000000..269384c4 Binary files /dev/null and b/Nocturn2/._NocturnComponent.py differ diff --git a/Nocturn2/._PlayingController.py b/Nocturn2/._PlayingController.py new file mode 100644 index 00000000..269384c4 Binary files /dev/null and b/Nocturn2/._PlayingController.py differ diff --git a/Nocturn2/._SceneController.py b/Nocturn2/._SceneController.py new file mode 100644 index 00000000..269384c4 Binary files /dev/null and b/Nocturn2/._SceneController.py differ diff --git a/Nocturn2/.___init__.py b/Nocturn2/.___init__.py new file mode 100644 index 00000000..eed6c1d6 Binary files /dev/null and b/Nocturn2/.___init__.py differ diff --git a/Nocturn2/.___init__.py2.py b/Nocturn2/.___init__.py2.py new file mode 100644 index 00000000..cd271bc3 Binary files /dev/null and b/Nocturn2/.___init__.py2.py differ diff --git a/Nocturn2/._apihelper.py b/Nocturn2/._apihelper.py new file mode 100644 index 00000000..eed6c1d6 Binary files /dev/null and b/Nocturn2/._apihelper.py differ diff --git a/Nocturn2/._consts.py b/Nocturn2/._consts.py new file mode 100644 index 00000000..eed6c1d6 Binary files /dev/null and b/Nocturn2/._consts.py differ diff --git a/Nocturn2/Automap/._.DS_Store b/Nocturn2/Automap/._.DS_Store new file mode 100644 index 00000000..c7f79192 Binary files /dev/null and b/Nocturn2/Automap/._.DS_Store differ diff --git a/Nocturn2/Automap/._User.automap b/Nocturn2/Automap/._User.automap new file mode 100644 index 00000000..6d0d8d28 Binary files /dev/null and b/Nocturn2/Automap/._User.automap differ diff --git a/Nocturn2/Automap/Fx.automap b/Nocturn2/Automap/Fx.automap new file mode 100644 index 00000000..9c8a58c7 --- /dev/null +++ b/Nocturn2/Automap/Fx.automap @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Nocturn2/Automap/Inst.automap b/Nocturn2/Automap/Inst.automap new file mode 100644 index 00000000..6c0a372b --- /dev/null +++ b/Nocturn2/Automap/Inst.automap @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Nocturn2/Automap/Mixer.automap b/Nocturn2/Automap/Mixer.automap new file mode 100644 index 00000000..b6c95411 --- /dev/null +++ b/Nocturn2/Automap/Mixer.automap @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Nocturn2/Automap/User.automap b/Nocturn2/Automap/User.automap new file mode 100644 index 00000000..daec8d1b --- /dev/null +++ b/Nocturn2/Automap/User.automap @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Nocturn2/readme.txt b/Nocturn2/readme.txt new file mode 100644 index 00000000..f6016d6c --- /dev/null +++ b/Nocturn2/readme.txt @@ -0,0 +1,3 @@ +This is so-called "Gulie Script" +all relevant files and instruction can be found here +http://www.macableton.com/hardware-reviews-tutorials/guille-script-for-automapno.html diff --git a/Novation_Impulse2/EncoderModeSelector.py b/Novation_Impulse2/EncoderModeSelector.py index 15f933bc..6bd98afb 100644 --- a/Novation_Impulse2/EncoderModeSelector.py +++ b/Novation_Impulse2/EncoderModeSelector.py @@ -43,8 +43,10 @@ def set_device_mixer_buttons(self, device_button, mixer_button): self._device_button = device_button self._mixer_button = mixer_button #raise self._device_button != None and (self._mixer_button != None or AssertionError) - self._device_button.add_value_listener(self._device_value) - self._mixer_button.add_value_listener(self._mixer_value) + if self._device_button != None: + self._device_button.add_value_listener(self._device_value) + if self._mixer_button!= None: + self._mixer_button.add_value_listener(self._mixer_value) def set_provide_volume_mode(self, provide_volume_mode): self._number_of_modes = 6 if provide_volume_mode else 5 diff --git a/Novation_Impulse2/Novation_Impulse2.py b/Novation_Impulse2/Novation_Impulse2.py index e0f34eea..5b948193 100644 --- a/Novation_Impulse2/Novation_Impulse2.py +++ b/Novation_Impulse2/Novation_Impulse2.py @@ -14,8 +14,9 @@ from ShiftableTransportComponent import ShiftableTransportComponent from PeekableEncoderElement import PeekableEncoderElement from EncoderModeSelector import EncoderModeSelector -INITIAL_DISPLAY_DELAY = 30 -STANDARD_DISPLAY_DELAY = 20 +INITIAL_DISPLAY_DELAY = 20 +STANDARD_DISPLAY_DELAY = 15 +SHORT_DISPLAY_DELAY = 15 IS_MOMENTARY = True SYSEX_START = (240, 0, 32, 41, 103) PAD_TRANSLATIONS = ((0, 3, 60, 0), @@ -48,6 +49,10 @@ def __init__(self, c_instance): self._has_sliders = True self._current_midi_map = None self._display_reset_delay = -1 + self._string_to_display = None + self.shift_pressed = False + # special alternative buttons mode. for now only mixer buttons become record buttons. later we will add something more + self.alternative_buttons_mode = False self._shift_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 39) self._preview_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 41) self._master_slider = SliderElement(MIDI_CC_TYPE, 0, 8) @@ -66,10 +71,26 @@ def __init__(self, c_instance): mixer_button.name = 'Encoder_Mixer_Mode' self._encoder_modes = EncoderModeSelector(self._device_component, self._mixer, self._next_bank_button, self._prev_bank_button, self._encoders) self._encoder_modes.set_device_mixer_buttons(device_button, mixer_button) - self._string_to_display = None + self._shift_button.add_value_listener(self._shift_button_handler) + for component in self.components: component.set_enabled(False) + # attributes + def alternative_buttons_mode(self): + return self.alternative_buttons_mode + + def alternative_buttons_mode(self,value): + self.log ('alternative_buttons_mode_value ' + str(value)) + self.alternative_buttons_mode = value + + def shift_pressed(self): + return self.shift_pressed + + def shift_pressed(self,value): + self.log ('shift_pressed value ' + str(value)) + self.shift_pressed = value + def refresh_state(self): ControlSurface.refresh_state(self) self.schedule_message(3, self._send_midi, SYSEX_START + (6, 1, 1, 1, 247)) @@ -102,6 +123,7 @@ def handle_sysex(self, midi_bytes): self.request_rebuild_midi_map() def disconnect(self): + self.log('starting disconnect 1') self._name_display_data_source.set_display_string(' ') for encoder in self._encoders: encoder.remove_value_listener(self._encoder_value) @@ -115,19 +137,27 @@ def disconnect(self): button.remove_value_listener(self._mixer_button_value) self._preview_button.remove_value_listener(self._preview_value) + self.log('starting disconnect 3') ControlSurface.disconnect(self) + self.log('starting disconnect 3') self._encoders = None self._sliders = None self._strip_buttons = None self._master_slider = None self._current_midi_map = None - self._shift_button = None self._name_display = None self._prev_bank_button = None self._next_bank_button = None self._encoder_modes = None self._transport_view_modes = None + self.log('starting disconnect 4') self._send_midi(SYSEX_START + (6, 0, 0, 0, 247)) + self.log('starting disconnect 5') + + if self._shift_button != None: + self._shift_button.remove_value_listener(self._shift_button_handler) + self._shift_button = None + self.log('starting disconnect 6') def build_midi_map(self, midi_map_handle): self._current_midi_map = midi_map_handle @@ -144,6 +174,7 @@ def update_display(self): self._show_current_track_name() def _setup_mixer(self): + self.log('setup mixer') mute_solo_flip_button = ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 0, 34) self._next_nav_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 37) self._prev_nav_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 38) @@ -151,7 +182,7 @@ def _setup_mixer(self): mute_solo_flip_button.name = 'Mute_Solo_Flip_Button' self._next_nav_button.name = 'Next_Track_Button' self._prev_nav_button.name = 'Prev_Track_Button' - self._mixer = SpecialMixerComponent(8, self.c_instance) + self._mixer = SpecialMixerComponent(self, 8, self.c_instance) self._mixer.name = 'Mixer' self._mixer.set_select_buttons(self._next_nav_button, self._prev_nav_button) self._mixer.selected_strip().name = 'Selected_Channel_Strip' @@ -173,10 +204,10 @@ def _setup_mixer(self): self._mixer.master_strip().set_mute_button(ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 1, 17)) self._mixer.set_strip_mute_solo_buttons(tuple(self._strip_buttons), mute_solo_flip_button) - self._mixer.set_shift_button(self._shift_button) + #self._mixer.set_shift_button(self._shift_button) + self._mixer.updateMixerButtons() self._button9 = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 9 + 8) - self._mixer.set_selected_mute_solo_button(self._button9) def _setup_session(self): num_pads = len(PAD_TRANSLATIONS) @@ -184,8 +215,15 @@ def _setup_session(self): self._session.name = 'Session_Control' self._session.selected_scene().name = 'Selected_Scene' self._session.set_mixer(self._mixer) - self._session.set_track_banking_increment(num_pads) - self._session.set_track_bank_buttons(ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 0, 35), ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 0, 36)) + # for ableton 9.1.1 and lower + #self._session.set_track_banking_increment(num_pads) + #self._session.set_track_bank_buttons(ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 0, 35), ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 0, 36)) + # for ableton 9.1.1 and higher + self._track_left_button = ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 0, 36) + self._track_right_button = ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 0, 35) + self._session.set_page_left_button(self._track_left_button) + self._session.set_page_right_button(self._track_right_button) + pads = [] for index in range(num_pads): pads.append(ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 60 + index)) @@ -212,13 +250,14 @@ def _setup_transport(self): play_button.name = 'Play_Button' stop_button.name = 'Stop_Button' rec_button.name = 'Record_Button' - transport = ShiftableTransportComponent(self.c_instance) - transport.name = 'Transport' - transport.set_stop_button(stop_button) - transport.set_play_button(play_button) - transport.set_record_buttonOnInit(rec_button) - transport.set_shift_button(self._shift_button) - self._transport_view_modes = TransportViewModeSelector(transport, self._session, ffwd_button, rwd_button, loop_button) + self._transport = ShiftableTransportComponent(self.c_instance,self._session, self, ffwd_button, rwd_button) + self._transport.name = 'Transport' + self._transport.set_stop_buttonOnInit(stop_button) + self._transport.set_play_button(play_button) + self._transport.set_record_buttonOnInit(rec_button) +# self._transport.set_shift_button(self._shift_button) + self._transport.set_mixer9_button(self._button9) + self._transport_view_modes = TransportViewModeSelector(self,self.c_instance,self._transport, self._session, ffwd_button, rwd_button, loop_button) self._transport_view_modes.name = 'Transport_View_Modes' def _setup_device(self): @@ -266,6 +305,7 @@ def _encoder_value(self, value, sender): self._set_string_to_display(display_string) def _slider_value(self, value, sender): + self.log ('_slider_value ' + str(value) + ' ' +str(sender)) if not sender in tuple(self._sliders) + (self._master_slider,): raise AssertionError if not value in range(128): @@ -277,12 +317,22 @@ def _slider_value(self, value, sender): returns = self.song().return_tracks track = None if sender.mapped_parameter() != None: + self.log ('1') if sender == self._master_slider: - track = self._has_sliders and master + self.log ('2') +# track = self._has_sliders and master + if self._has_sliders: + track = master + else: + self.log ('2.1') + track = self.song().view.selected_track else: + self.log ('3') track = self._mixer.channel_strip(self._sliders.index(sender))._track else: + self.log ('4') track = self.song().view.selected_track + self.log('track='+str(track)) if track == master: display_string = 'Master' elif track in tracks: @@ -296,6 +346,7 @@ def _slider_value(self, value, sender): self._set_string_to_display(display_string) def _mixer_button_value(self, value, sender): + self.log ('__mixer_button_value ' + str(value) + ' ' +str(sender)) if not value in range(128): raise AssertionError #if self._mixer.is_enabled() and value > 0: @@ -307,6 +358,30 @@ def _mixer_button_value(self, value, sender): self._display_reset_delay = STANDARD_DISPLAY_DELAY else: self._set_string_to_display(' - ') + # if shift_pressed XOR alternative_mode + if self.shift_pressed <> self.alternative_buttons_mode: + self.log("_mixer_button_value") + self.log(str(value)) + if (value == 0): + self.select_armed_track_if_only_one() + + def select_armed_track_if_only_one(self): + self.log("select_armed_track_if_only_one") + song = self.song() + armed_tracks = [] + tracks = song.tracks + self.log("select_armed_track_if_only_one 2") + for track in tracks: + if track.can_be_armed and track.arm: + armed_tracks.append(track) + self.log(str(len(armed_tracks))) + if (len(armed_tracks) == 1): + self.log("selecting the track") + sel_track = armed_tracks[0] + self.song().view.selected_track = sel_track + self._mixer._selected_tracks = [] + self._mixer._selected_tracks.append(sel_track) + self._mixer.on_selected_track_changed() def _preview_value(self, value): if not value in range(128): @@ -332,19 +407,63 @@ def _set_string_to_display(self, string_to_display): self._display_reset_delay = STANDARD_DISPLAY_DELAY def _on_selected_track_changed(self): + self.log('_on_selected_track_changed') ControlSurface._on_selected_track_changed(self) self._show_current_track_name() #all_tracks = self._has_sliders or self._session.tracks_to_use() all_tracks2 = self._session.tracks_to_use() selected_track = self.song().view.selected_track num_strips = self._session.width() - for selected_track in all_tracks2: + if selected_track in all_tracks2: track_index = list(all_tracks2).index(selected_track) + self.log('track_index '+ str(track_index)) new_offset = track_index - track_index % num_strips + self.log('new_offset '+ str(new_offset)) if not new_offset / num_strips == int(new_offset / num_strips): raise AssertionError self._session.set_offsets(new_offset, self._session.scene_offset()) + + def _shift_button_handler(self, value): + self.log("root shift handler : "+ str(value)) + if not self._shift_button != None: + raise AssertionError + if not value in range(128): + raise AssertionError + self.log("root shift handler 2") + self.shift_pressed = value > 0 +# calling other handlers + self._mixer._shift_button_handler(value) + self._transport._shift_button_handler(value) + self._transport_view_modes._shift_button_handler(value) + +#clip stop + self.log("root shift handler 3") + num_pads = len(PAD_TRANSLATIONS) + pads = [] + for index in range(num_pads): + pads.append(ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 60 + index)) + pads[-1].name = 'Pad_' + str(index) + clip_slot = self._session.selected_scene().clip_slot(index) + if self.shift_pressed: + clip_slot.set_launch_button(None) + else: + clip_slot.set_launch_button(pads[index]) + if self.shift_pressed: + self._session.set_stop_track_clip_buttons(tuple(pads)) + else: + self._session.set_stop_track_clip_buttons(None) + + self.log("root shift handler 4") + + def flipAlternativeButtonMode(self): + self.alternative_buttons_mode = not self.alternative_buttons_mode + self.updateAlternativeButtonMode() + + def updateAlternativeButtonMode(self): + self._mixer.updateMixerButtons() + self._transport_view_modes.update() + def log(self, message): pass # self.c_instance.log_message(message) diff --git a/Novation_Impulse2/ShiftableTransportComponent.py b/Novation_Impulse2/ShiftableTransportComponent.py index bdeb0862..12555f2f 100644 --- a/Novation_Impulse2/ShiftableTransportComponent.py +++ b/Novation_Impulse2/ShiftableTransportComponent.py @@ -1,91 +1,149 @@ #Embedded file name: /Users/versonator/Hudson/live/Projects/AppLive/Resources/MIDI Remote Scripts/Novation_Impulse/ShiftableTransportComponent.py import Live +NavDirection = Live.Application.Application.View.NavDirection from _Framework.ButtonElement import ButtonElement from _Framework.TransportComponent import TransportComponent +from _Framework.ToggleComponent import ToggleComponent class ShiftableTransportComponent(TransportComponent): """ Special transport class handling the seek buttons differently based on a shift button""" - def __init__(self, c_instance): - self.c_instance = c_instance - self._shift_button = None - self._shift_pressed = False + def __init__(self, c_instance, session, parent, ffwd_button, rwd_button): TransportComponent.__init__(self) + self.c_instance = c_instance + self._mixer9_button = None + self._play_button = None + self._record_button = None + self._session = session + self._parent = parent + self._ffwd_button = ffwd_button + self._rwd_button = rwd_button + song = self.song() +# self._automation_toggle= self.register_component(ToggleComponent('session_automation_record', song)) + self._automation_toggle, self._re_enable_automation_toggle, self._delete_automation = self.register_components(ToggleComponent('session_automation_record', song), ToggleComponent('re_enable_automation_enabled', song, read_only=True), ToggleComponent('has_envelopes', None, read_only=True)) + + def disconnect(self): - if self._shift_button != None: - self._shift_button.remove_value_listener(self._shift_value) - self._shift_button = None + if self._play_button != None: + self._play_button.remove_value_listener(self._play_pressed) + self._play_button = None + TransportComponent.disconnect(self) - def set_shift_button(self, button): - self.log("set_shift_button (transport)") - if not (button == None or isinstance(button, ButtonElement) and button.is_momentary()): - raise AssertionError - self.log("set_shift_button 2 (transport)") - if self._shift_button != button: - if self._shift_button != None: - self._shift_button.remove_value_listener(self._shift_value) - self._shift_pressed = False - self._shift_button = button - self.log("set_shift_button 3 (transport)") - self._shift_button != None and self._shift_button.add_value_listener(self._shift_value) - self.log("set_shift_button 4 (transport)") + def set_stop_buttonOnInit(self, button): + self.log("set_stop_buttonOnInit 1") + self._stop_button = button + self.set_stop_button(self._stop_button) + self.log("set_stopbuttonOnInit 2") + def set_record_buttonOnInit(self, button): self.log("set_record_buttonOnInit 1") - self.record_button = button - self.set_record_button(self.record_button) + self._record_button = button + self.set_record_button(self._record_button) self.log("set_record_buttonOnInit 2") - def _shift_value(self, value): - self.log("shift handler") - if not self._shift_button != None: + def set_mixer9_button(self, button): + self.log("set_mixer9_button 1") + self._mixer9_button = button + self.set_overdub_button(self._mixer9_button) + #self._automation_toggle.set_toggle_button(self._mixer9_button) + self.log("set_mixer9_button 2") + + + def set_play_button(self, button): + self._play_button = button + self._play_button.add_value_listener(self._play_pressed) + self._play_toggle.set_toggle_button(button) + + def _play_pressed(self, value): + self.log("_play_pressed " + str(value)) + if not value in range(128): raise AssertionError + if self._parent.shift_pressed: + if value != 0: + if self.song().can_undo: + #todo: add message + self.song().undo() + self.log("undoing") + self._parent._set_string_to_display('undoing') + + else: + #todo: add message + self._parent._set_string_to_display('cannot undo') + + + def _shift_button_handler(self, value): + self.log("shift handler transport component : " + str(value)) if not value in range(128): raise AssertionError - self.log("shift handler 2") - self._shift_pressed = self.is_enabled() and value > 0 - self.log("shift handler 3") - if self._shift_pressed: - self.log("shift handler pressed") - self.set_overdub_button(self.record_button) + self.log("shift handler transport component 2") + if self._parent.shift_pressed: + self._play_toggle.set_toggle_button(None) + self._session.set_stop_all_clips_button(self._stop_button) + self.set_stop_button(None) + self.set_overdub_button(None) + self._automation_toggle.set_toggle_button(self._mixer9_button) + self.set_metronome_button(self._record_button) self.set_record_button(None) + else: - self.log("shift handler unpressed") - self.set_overdub_button(None) - self.set_record_button(self.record_button) + self._play_toggle.set_toggle_button(self._play_button) + self._session.set_stop_all_clips_button(None) + self.set_stop_button(self._stop_button) + self.set_overdub_button(self._mixer9_button) + self._automation_toggle.set_toggle_button(None) + self.set_metronome_button(None) + self.set_record_button(self._record_button) + self.log("shift handler transport component 3") def _ffwd_value(self, value): - self.log("ffwd handler main") + self.log("ffwd handler main" + str(value)) if not self._ffwd_button != None: raise AssertionError if not value in range(128): raise AssertionError else: - if self._shift_pressed: + if self._parent.shift_pressed: self.log("ffwd shifted handler") - self.song().current_song_time = self._shift_pressed and self.song().last_event_time +# self.song().current_song_time = self.song().last_event_time + if value == 1: + self._scroll_device_chain(NavDirection.right) else: self.log("ffwd normal handler") TransportComponent._ffwd_value(self, value) def _rwd_value(self, value): - self.log("rwd handler main") + self.log("rwd handler main" + str(value)) if not self._rwd_button != None: raise AssertionError if not value in range(128): raise AssertionError else: - if self._shift_pressed: - self.song().current_song_time = self._shift_pressed and 0.0 + if self._parent.shift_pressed: self.log("rwd shifted handler") +# self.song().current_song_time = 0.0 + if value == 1: + self._scroll_device_chain(NavDirection.left) else: self.log("rwd normal handler") TransportComponent._rwd_value(self, value) + + def _scroll_device_chain(self, direction): + self.log("_scroll_device_chain 1") + view = self._parent.application().view + self.log("_scroll_device_chain 2") + if not view.is_view_visible('Detail') or not view.is_view_visible('Detail/DeviceChain'): + view.show_view('Detail') + view.show_view('Detail/DeviceChain') + else: + view.scroll_view(direction, 'Detail/DeviceChain', False) + def log(self, message): pass -# self.c_instance.log_message(message) +# self.c_instance.log_message(message) + diff --git a/Novation_Impulse2/SpecialMixerComponent.py b/Novation_Impulse2/SpecialMixerComponent.py index 3f3b6a3b..487c1808 100644 --- a/Novation_Impulse2/SpecialMixerComponent.py +++ b/Novation_Impulse2/SpecialMixerComponent.py @@ -5,16 +5,16 @@ class SpecialMixerComponent(MixerComponent): """ Special mixer class that reassigns buttons to mute or solo based on a toggle """ - def __init__(self, num_tracks, c_instance): + def __init__(self, parent, num_tracks, c_instance): + self.parent = parent self.c_instance = c_instance - self._shift_button = None + self.log("mixer.init") self._selected_mute_solo_button = None self._strip_mute_solo_buttons = None self._mute_solo_flip_button = None MixerComponent.__init__(self, num_tracks) self._selected_tracks = [] self._register_timer_callback(self._on_timer) - self._shift_pressed = False self._mute_solo_raw_value = 127 @@ -22,35 +22,12 @@ def disconnect(self): self._unregister_timer_callback(self._on_timer) self._selected_tracks = None MixerComponent.disconnect(self) - if self._shift_button != None: - self._shift_button.remove_value_listener(self._shift_value) - self._shift_button = None if self._mute_solo_flip_button != None: self._mute_solo_flip_button.remove_value_listener(self._mute_solo_flip_value) self._mute_solo_flip_button = None self._selected_mute_solo_button = None self._strip_mute_solo_buttons = None - def set_shift_button(self, shift_button): - self.log("set_shift_button - mixer") - if not (shift_button == None or shift_button.is_momentary()): - raise AssertionError - if self._shift_button != None: - self._shift_button.remove_value_listener(self._shift_value) - self._shift_button = shift_button - self._shift_button != None and self._shift_button.add_value_listener(self._shift_value) - - def set_selected_mute_solo_button(self, button): - if not isinstance(button, (type(None), ButtonElement)): - raise AssertionError - self._selected_mute_solo_button = button - #self.selected_strip().set_mute_button(self._selected_mute_solo_button) - #self.selected_strip().set_solo_button(None) - #self.selected_strip().set_arm_button(None) - self.selected_strip().set_mute_button(None) - self.selected_strip().set_solo_button(None) - self.selected_strip().set_arm_button(self._selected_mute_solo_button) - def set_strip_mute_solo_buttons(self, buttons, flip_button): self.log("set_strip_mute_solo_buttons") if not (buttons is None or isinstance(buttons, tuple) and len(buttons) == len(self._channel_strips)): @@ -74,30 +51,14 @@ def set_strip_mute_solo_buttons(self, buttons, flip_button): def tracks_to_use(self): return tuple(self.song().visible_tracks) + tuple(self.song().return_tracks) - def _shift_value(self, value): - if (value > 0): - self._shift_pressed = True - else: - self._shift_pressed = False + def _shift_button_handler(self, value): + self.log("calling mixer shift value " + str(value)) self.updateMixerButtons() pass return - self.log("_shift_value " + str(value)) - if not self._shift_button != None: - raise AssertionError - if not value in range(128): - raise AssertionError - if value > 0: - self.selected_strip().set_mute_button(None) -# self.selected_strip().set_solo_button(self._selected_mute_solo_button) - self.selected_strip().set_arm_button(self._selected_mute_solo_button) - else: -# self.selected_strip().set_solo_button(None) - self.selected_strip().set_arm_button(None) - self.selected_strip().set_mute_button(self._selected_mute_solo_button) def _mute_solo_flip_value(self, value): - #self.log(value) + self.log("_mute_solo_flip_value" + str(value)) if not self._mute_solo_flip_button != None: raise AssertionError if not value in range(128): @@ -107,24 +68,31 @@ def _mute_solo_flip_value(self, value): def updateMixerButtons(self): + parent_shift_pressed = self.parent.shift_pressed + parent_alternative_buttons_mode = self.parent.alternative_buttons_mode + self.log("updateMixerButtons " + str(parent_shift_pressed) + " " +str(parent_alternative_buttons_mode)) if self._strip_mute_solo_buttons != None: for index in range(len(self._strip_mute_solo_buttons)): strip = self.channel_strip(index) self.log("setting strip") - if self._shift_pressed: + if self.parent.shift_pressed or self.parent.alternative_buttons_mode: + self.log("setting strip to arm") strip.set_mute_button(None) strip.set_solo_button(None) strip.set_arm_button(self._strip_mute_solo_buttons[index]) else: if self._mute_solo_raw_value == 0: + self.log("setting strip to solo") strip.set_mute_button(None) strip.set_solo_button(self._strip_mute_solo_buttons[index]) strip.set_arm_button(None) else: + self.log("setting strip to mute") strip.set_solo_button(None) strip.set_mute_button(self._strip_mute_solo_buttons[index]) strip.set_arm_button(None) + def _on_timer(self): sel_track = None while len(self._selected_tracks) > 0: @@ -138,22 +106,22 @@ def _on_timer(self): found_recording_clip = False song = self.song() tracks = song.tracks - if song.is_playing: - check_arrangement = song.record_mode - for track in tracks: - if track.can_be_armed and track.arm: - if check_arrangement: - found_recording_clip = True - break - else: - playing_slot_index = track.playing_slot_index - if playing_slot_index in range(len(track.clip_slots)): - slot = track.clip_slots[playing_slot_index] - if slot.has_clip and slot.clip.is_recording: - found_recording_clip = True - break - - if found_recording_clip or song.exclusive_arm: + check_arrangement = song.is_playing and song.record_mode + for track in tracks: + if track.can_be_armed and track.arm: + if check_arrangement: + found_recording_clip = True + break + else: + playing_slot_index = track.playing_slot_index + if playing_slot_index in range(len(track.clip_slots)): + slot = track.clip_slots[playing_slot_index] + if slot.has_clip and slot.clip.is_recording: + found_recording_clip = True + break + + if not found_recording_clip: + if song.exclusive_arm: for track in tracks: if track.can_be_armed and track.arm and track != sel_track: track.arm = False @@ -161,14 +129,15 @@ def _on_timer(self): sel_track.arm = True sel_track.view.select_instrument() self._selected_tracks = [] - self.updateMixerButtons() def _next_track_value(self, value): + self.log("_next_track_value "+ str(value)) MixerComponent._next_track_value(self, value) self._selected_tracks.append(self.song().view.selected_track) self.updateMixerButtons() def _prev_track_value(self, value): + self.log("_prev_track_value " + str(value)) MixerComponent._prev_track_value(self, value) self._selected_tracks.append(self.song().view.selected_track) self.updateMixerButtons() diff --git a/Novation_Impulse2/TransportViewModeSelector.py b/Novation_Impulse2/TransportViewModeSelector.py index 63db9307..ee4b6172 100644 --- a/Novation_Impulse2/TransportViewModeSelector.py +++ b/Novation_Impulse2/TransportViewModeSelector.py @@ -8,24 +8,28 @@ class TransportViewModeSelector(ModeSelectorComponent): """ Class that reassigns specific buttons based on the views visible in Live """ - def __init__(self, transport, session, ffwd_button, rwd_button, loop_button): + def __init__(self, parent, c_instance, transport, session, ffwd_button, rwd_button, loop_button): if not isinstance(transport, TransportComponent): raise AssertionError if not isinstance(session, SessionComponent): raise AssertionError if not isinstance(ffwd_button, ButtonElement): raise AssertionError - if not isinstance(rwd_button, ButtonElement): + if not isinstance(rwd_button, ButtonElement): raise AssertionError - if not isinstance(loop_button, ButtonElement): + if not isinstance(loop_button, ButtonElement): raise AssertionError ModeSelectorComponent.__init__(self) + self._parent = parent + self.c_instance = c_instance self._transport = transport self._session = session self._ffwd_button = ffwd_button self._rwd_button = rwd_button self._loop_button = loop_button + self._shift_pressed = False self.application().view.add_is_view_visible_listener('Session', self._on_view_changed) + self._loop_button.add_value_listener(self._loop_pressed) self.update() def disconnect(self): @@ -34,25 +38,57 @@ def disconnect(self): self._session = None self._ffwd_button = None self._rwd_button = None + self._loop_button.remove_value_listener(self._loop_pressed) self._loop_button = None self.application().view.remove_is_view_visible_listener('Session', self._on_view_changed) def update(self): if self.is_enabled(): - if self._mode_index == 0: + self.log("transportviewselctor_update ") + if self._shift_pressed: + #shift plus loop will make an alternative control mode for everything + self._session.selected_scene().set_launch_button(None) + self._transport.set_loop_button(None) + else: + if self._mode_index == 0: + self._transport.set_loop_button(self._loop_button) + self._session.selected_scene().set_launch_button(None) + else: + self._transport.set_loop_button(None) + self._session.selected_scene().set_launch_button(self._loop_button) + # hack as we have nadler for fwd that changes devices + if self._mode_index == 0 or self._shift_pressed: self._transport.set_seek_buttons(self._ffwd_button, self._rwd_button) - self._transport.set_loop_button(self._loop_button) self._session.set_select_buttons(None, None) - self._session.selected_scene().set_launch_button(None) else: self._transport.set_seek_buttons(None, None) - self._transport.set_loop_button(None) self._session.set_select_buttons(self._ffwd_button, self._rwd_button) - self._session.selected_scene().set_launch_button(self._loop_button) def _on_view_changed(self): if self.application().view.is_view_visible('Session'): self._mode_index = 1 else: self._mode_index = 0 - self.update() \ No newline at end of file + self.update() + + + def _shift_button_handler(self, value): + self.log("shift handler transport component " + str(value)) + if not value in range(128): + raise AssertionError + self.log("shift handler 2") + self._shift_pressed = self.is_enabled() and self._parent.shift_pressed + self.update() + self.log("shift handler 3") + + def _loop_pressed(self, value): + self.log("loop handler transport component " + str(value)) + + if (value == 1) and (self._shift_pressed): + self._parent.flipAlternativeButtonMode() + + + def log(self, message): + pass +# self.c_instance.log_message(message) + diff --git a/Novation_Impulse2/debug.bat b/Novation_Impulse2/debug.bat new file mode 100644 index 00000000..8b9035f0 --- /dev/null +++ b/Novation_Impulse2/debug.bat @@ -0,0 +1,6 @@ +rem C:\Documents and Settings\%username%\Application Data\Ableton\Live 8.x.x\Preferences\Log.txt +rem cd "C:\Documents and Settings\%username%\Application Data\Ableton\Live 9.1\Preferences\" + +cd "C:\Documents and Settings\mbakirov\Application Data\Ableton\Live 9.5\Preferences" + +rem http://remotescripts.blogspot.com.au/2010_03_01_archive.html \ No newline at end of file diff --git a/Novation_Impulse2/deploy.bat b/Novation_Impulse2/deploy.bat new file mode 100644 index 00000000..39e6a2ab --- /dev/null +++ b/Novation_Impulse2/deploy.bat @@ -0,0 +1,4 @@ +del /Q "C:\ProgramData\Ableton\Live 9 Suite\Resources\MIDI Remote Scripts\Novation_Impulse2\*.*" +xcopy *.py "C:\ProgramData\Ableton\Live 9 Suite\Resources\MIDI Remote Scripts\Novation_Impulse2\*.*" +del /Q "C:\ProgramData\Ableton\Live 9 Suite 64\Resources\MIDI Remote Scripts\Novation_Impulse2\*.*" +xcopy *.py "C:\ProgramData\Ableton\Live 9 Suite 64\Resources\MIDI Remote Scripts\Novation_Impulse2\*.*" \ No newline at end of file diff --git a/Novation_Impulse2/readme.txt b/Novation_Impulse2/readme.txt index 800d551b..20ca11ae 100644 --- a/Novation_Impulse2/readme.txt +++ b/Novation_Impulse2/readme.txt @@ -1,15 +1,82 @@ +Created by Marat Bakirov 2013-2017 +Marat branch is here: +https://github.com/maratbakirov/AbletonLive9_RemoteScripts + +Discussion +https://forum.ableton.com/viewtopic.php?f=1&t=197828 + +IMPORTANT! + +since Ableton 9.1.2 some API has been changed + +the new version is for Ableton 9.1.2 and higher + +for old version please use version from a folder Novation_Impulse2_9_1 + Features -Shift + Rec = Overdub +Mixer button 9 = OverDub +Shift + Mixer button 9 = Automation record on/off -Mixer button 9 - arm selected track +Shift + Rec = Metronome on/off +Shift + Play = Undo +Shift + Stop = Stop all clips +Shift + Ffwd/Back - next/previous device -Shift changes mixer buttons to be arm the specific track button. +Shift + Loop now enters "alternative assignments" mode. +Differences: + is that mixer buttons are permanently armed (like the shift is pressed) + if you press Shift and arm the track, it will NOT me selected automatically -Known issues -1) After moving track with Shift+Track buttons are not returned to inital condition. - Also happens sometimes in other cases (like bank switching with Shift). +It is very good for perfomance when you need to quickly switch instruments and only have one left hand or right hand available as the other is ready to play + +Shift + Clip buttons are now clip stop. + + + +Known issues that cannot be fixed +1) After moving track with Shift+Track buttons are not returned to inital condition. + Also happens sometimes in other cases (like bank switching with Shift). Workaround - press and release Shit again. + update : seems to work fine with Nocation Firmware 695 and higher 2) Pressin shift + (mutes/solos) flip button leds to unpredictable results. Workaround - do not do that, if you have done that, just press and release shift. +The reason for these problems is that when you use shift+track or shift+bank, the message "shift off" is not sent. +This is out of the scope of what I can do. The bug report is sent to Novation. + +Known issues and todo: +FIXED: 1) impulse does not disconnect properly and does not send disconnection message. +FIXED 1.5) refactor shift - move it to main class. +FIXED: 2)when track goes down, the bank is not changed. +3) on initialize mute/solo buttons do not light + workaround - press shift button. +FIXED: 4) when arming a single track make the track selected + + +Future things to implement. + +DONE 1) mixer9 - overdub +DONE 3) Shift + Stop - stop all clips. + + +FIXED:4) Shift+Mixer9 - Automation record on/off +4.5) Shift + Rec = metronome +5) Shift + Loop switch session/arrangement (as loop is the button that also changes in this case) + replanned as shift+loop now behave totally differently +FIXED: 6) Shift + play Undo (as play is like backspace but turned opposite) + +DONE: another parallel feature - Shift + drum pad in clip mode - stop the selected clip. + +think of re-mapping drum pads for another notes. + +7) display messages, when + metronome on/off + undo + switch session/arrangement + overdub + automation arm + +2) Shift + Mixer 9 - automation arm (seems to be rather hard - not sure there is API for that) + + diff --git a/Novation_Impulse2_9_1/EncoderModeSelector.py b/Novation_Impulse2_9_1/EncoderModeSelector.py new file mode 100644 index 00000000..6bd98afb --- /dev/null +++ b/Novation_Impulse2_9_1/EncoderModeSelector.py @@ -0,0 +1,119 @@ +#Embedded file name: /Users/versonator/Hudson/live/Projects/AppLive/Resources/MIDI Remote Scripts/Novation_Impulse/EncoderModeSelector.py +import Live +from _Framework.ModeSelectorComponent import ModeSelectorComponent + +class EncoderModeSelector(ModeSelectorComponent): + """ Class that reassigns the given encoders to either device- or mixer control """ + + def __init__(self, device, mixer, bank_up_button, bank_down_button, encoders): + ModeSelectorComponent.__init__(self) + self._mixer = mixer + self._device = device + self._bank_up_button = bank_up_button + self._bank_down_button = bank_down_button + self._encoders = encoders + self._device_button = None + self._mixer_button = None + self._device_mode = True + self._mode_index = 0 + self._number_of_modes = 5 + self._bank_down_button.add_value_listener(self._bank_down_value) + self._bank_up_button.add_value_listener(self._bank_up_value) + self.update() + + def disconnect(self): + self._bank_down_button.remove_value_listener(self._bank_down_value) + self._bank_up_button.remove_value_listener(self._bank_up_value) + self.set_device_mixer_buttons(None, None) + ModeSelectorComponent.disconnect(self) + self._mixer = None + self._device = None + self._bank_up_button = None + self._bank_down_button = None + self._encoders = None + + def set_device_mixer_buttons(self, device_button, mixer_button): + #if not self._device_button != None: + # if not self._mixer_button != None: + # raise AssertionError + if self._device_button != None: + self._device_button.remove_value_listener(self._device_value) + if self._mixer_button!= None: + self._mixer_button.remove_value_listener(self._mixer_value) + self._device_button = device_button + self._mixer_button = mixer_button + #raise self._device_button != None and (self._mixer_button != None or AssertionError) + if self._device_button != None: + self._device_button.add_value_listener(self._device_value) + if self._mixer_button!= None: + self._mixer_button.add_value_listener(self._mixer_value) + + def set_provide_volume_mode(self, provide_volume_mode): + self._number_of_modes = 6 if provide_volume_mode else 5 + + def number_of_modes(self): + return self._number_of_modes + + def update(self): + if not self._mode_index in range(self.number_of_modes()): + raise AssertionError + if self.is_enabled(): + self._device.set_allow_update(False) + self._mixer.set_allow_update(False) + self._device.set_bank_nav_buttons(None, None) + self._device.set_parameter_controls(()) + for index in range(len(self._encoders)): + strip = self._mixer.channel_strip(index) + strip.set_pan_control(None) + strip.set_send_controls(None) + if self.number_of_modes() > 5: + strip.set_volume_control(None) + + if self._device_mode: + self._device.set_bank_nav_buttons(self._bank_down_button, self._bank_up_button) + self._device.set_parameter_controls(self._encoders) + else: + for index in range(len(self._encoders)): + strip = self._mixer.channel_strip(index) + if self._mode_index == 0: + strip.set_pan_control(self._encoders[index]) + elif self._mode_index < 5: + sends = [None, + None, + None, + None] + sends[self._mode_index - 1] = self._encoders[index] + strip.set_send_controls(tuple(sends)) + else: + strip.set_volume_control(self._encoders[index]) + + self._device.set_allow_update(True) + self._mixer.set_allow_update(True) + + def _bank_down_value(self, value): + if not value in range(128): + raise AssertionError + if self.is_enabled() and not self._device_mode: + new_mode = (value > 0 or not self._bank_down_button.is_momentary()) and max(self._mode_index - 1, 0) + self.set_mode(new_mode) + + def _bank_up_value(self, value): + if not value in range(128): + raise AssertionError + if self.is_enabled() and not self._device_mode: + new_mode = (value > 0 or not self._bank_up_button.is_momentary()) and min(self._mode_index + 1, self.number_of_modes() - 1) + self.set_mode(new_mode) + + def _device_value(self, value): + if not value in range(128): + raise AssertionError + if self.is_enabled() and not self._device_mode: + self._device_mode = (value > 0 or not self._device_button.is_momentary()) and True + self.update() + + def _mixer_value(self, value): + if not value in range(128): + raise AssertionError + if self.is_enabled() and self._device_mode: + self._device_mode = (value > 0 or not self._mixer_button.is_momentary()) and False + self.update() \ No newline at end of file diff --git a/Novation_Impulse2_9_1/Novation_Impulse2.py b/Novation_Impulse2_9_1/Novation_Impulse2.py new file mode 100644 index 00000000..393e2249 --- /dev/null +++ b/Novation_Impulse2_9_1/Novation_Impulse2.py @@ -0,0 +1,410 @@ +#Embedded file name: /Users/versonator/Hudson/live/Projects/AppLive/Resources/MIDI Remote Scripts/Novation_Impulse/Novation_Impulse.py +from __future__ import with_statement +import Live +from _Framework.ControlSurface import ControlSurface +from _Framework.InputControlElement import * +from _Framework.SliderElement import SliderElement +from _Framework.ButtonElement import ButtonElement +from _Framework.PhysicalDisplayElement import PhysicalDisplayElement +from _Framework.DisplayDataSource import DisplayDataSource +from _Framework.SessionComponent import SessionComponent +from _Framework.DeviceComponent import DeviceComponent +from TransportViewModeSelector import TransportViewModeSelector +from SpecialMixerComponent import SpecialMixerComponent +from ShiftableTransportComponent import ShiftableTransportComponent +from PeekableEncoderElement import PeekableEncoderElement +from EncoderModeSelector import EncoderModeSelector +INITIAL_DISPLAY_DELAY = 20 +STANDARD_DISPLAY_DELAY = 15 +IS_MOMENTARY = True +SYSEX_START = (240, 0, 32, 41, 103) +PAD_TRANSLATIONS = ((0, 3, 60, 0), + (1, 3, 62, 0), + (2, 3, 64, 0), + (3, 3, 65, 0), + (0, 2, 67, 0), + (1, 2, 69, 0), + (2, 2, 71, 0), + (3, 2, 72, 0)) +LED_OFF = 4 +RED_FULL = 7 +RED_BLINK = 11 +GREEN_FULL = 52 +GREEN_BLINK = 56 +AMBER_FULL = RED_FULL + GREEN_FULL - 4 +AMBER_BLINK = AMBER_FULL - 4 + 8 + +class Novation_Impulse2(ControlSurface): + """ Script for Novation's Impulse keyboards """ + + def __init__(self, c_instance): + ControlSurface.__init__(self, c_instance) + self.c_instance = c_instance + with self.component_guard(): + self.set_pad_translations(PAD_TRANSLATIONS) + self._device_selection_follows_track_selection = True + self._suggested_input_port = 'Impulse' + self._suggested_output_port = 'Impulse' + self._has_sliders = True + self._current_midi_map = None + self._display_reset_delay = -1 + self._shift_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 39) + self._preview_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 41) + self._master_slider = SliderElement(MIDI_CC_TYPE, 0, 8) + self._shift_button.name = 'Shift_Button' + self._master_slider.name = 'Master_Volume_Control' + self._master_slider.add_value_listener(self._slider_value, identify_sender=True) + self._preview_button.add_value_listener(self._preview_value) + self._setup_mixer() + self._setup_session() + self._setup_transport() + self._setup_device() + self._setup_name_display() + device_button = ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 1, 10) + mixer_button = ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 1, 9) + device_button.name = 'Encoder_Device_Mode' + mixer_button.name = 'Encoder_Mixer_Mode' + self._encoder_modes = EncoderModeSelector(self._device_component, self._mixer, self._next_bank_button, self._prev_bank_button, self._encoders) + self._encoder_modes.set_device_mixer_buttons(device_button, mixer_button) + self._string_to_display = None + self._shift_pressed = False + self._shift_button.add_value_listener(self._shift_value) + + + for component in self.components: + component.set_enabled(False) + + def refresh_state(self): + ControlSurface.refresh_state(self) + self.schedule_message(3, self._send_midi, SYSEX_START + (6, 1, 1, 1, 247)) + + def handle_sysex(self, midi_bytes): + if midi_bytes[0:-2] == SYSEX_START + (7,) and midi_bytes[-2] != 0: + self._has_sliders = midi_bytes[-2] != 25 + self.schedule_message(1, self._show_startup_message) + for control in self.controls: + if isinstance(control, InputControlElement): + control.clear_send_cache() + + for component in self.components: + component.set_enabled(True) + + if self._has_sliders: + self._mixer.master_strip().set_volume_control(self._master_slider) + self._mixer.update() + else: + self._mixer.master_strip().set_volume_control(None) + self._mixer.selected_strip().set_volume_control(self._master_slider) + for index in range(len(self._sliders)): + self._mixer.channel_strip(index).set_volume_control(None) + slider = self._sliders[index] + slider.release_parameter() + if slider.value_has_listener(self._slider_value): + slider.remove_value_listener(self._slider_value) + + self._encoder_modes.set_provide_volume_mode(not self._has_sliders) + self.request_rebuild_midi_map() + + def disconnect(self): + self.log('starting disconnect 1') + self._name_display_data_source.set_display_string(' ') + for encoder in self._encoders: + encoder.remove_value_listener(self._encoder_value) + + self._master_slider.remove_value_listener(self._slider_value) + if self._has_sliders: + for slider in tuple(self._sliders): + slider.remove_value_listener(self._slider_value) + + for button in self._strip_buttons: + button.remove_value_listener(self._mixer_button_value) + + self._preview_button.remove_value_listener(self._preview_value) + self.log('starting disconnect 3') + ControlSurface.disconnect(self) + self.log('starting disconnect 3') + self._encoders = None + self._sliders = None + self._strip_buttons = None + self._master_slider = None + self._current_midi_map = None + self._name_display = None + self._prev_bank_button = None + self._next_bank_button = None + self._encoder_modes = None + self._transport_view_modes = None + self.log('starting disconnect 4') + self._send_midi(SYSEX_START + (6, 0, 0, 0, 247)) + self.log('starting disconnect 5') + + if self._shift_button != None: + self._shift_button.remove_value_listener(self._shift_value) + self._shift_button = None + self.log('starting disconnect 6') + + def build_midi_map(self, midi_map_handle): + self._current_midi_map = midi_map_handle + ControlSurface.build_midi_map(self, midi_map_handle) + + def update_display(self): + ControlSurface.update_display(self) + if self._string_to_display != None: + self._name_display_data_source.set_display_string(self._string_to_display) + self._string_to_display = None + if self._display_reset_delay >= 0: + self._display_reset_delay -= 1 + if self._display_reset_delay == -1: + self._show_current_track_name() + + def _setup_mixer(self): + mute_solo_flip_button = ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 0, 34) + self._next_nav_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 37) + self._prev_nav_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 38) + self._strip_buttons = [] + mute_solo_flip_button.name = 'Mute_Solo_Flip_Button' + self._next_nav_button.name = 'Next_Track_Button' + self._prev_nav_button.name = 'Prev_Track_Button' + self._mixer = SpecialMixerComponent(8, self.c_instance) + self._mixer.name = 'Mixer' + self._mixer.set_select_buttons(self._next_nav_button, self._prev_nav_button) + self._mixer.selected_strip().name = 'Selected_Channel_Strip' + self._mixer.master_strip().name = 'Master_Channel_Strip' + self._mixer.master_strip().set_volume_control(self._master_slider) + self._sliders = [] + for index in range(8): + strip = self._mixer.channel_strip(index) + strip.name = 'Channel_Strip_' + str(index) + strip.set_invert_mute_feedback(True) + self._sliders.append(SliderElement(MIDI_CC_TYPE, 0, index)) + self._sliders[-1].name = str(index) + '_Volume_Control' + self._sliders[-1].set_feedback_delay(-1) + self._sliders[-1].add_value_listener(self._slider_value, identify_sender=True) + strip.set_volume_control(self._sliders[-1]) + self._strip_buttons.append(ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 9 + index)) + self._strip_buttons[-1].name = str(index) + '_Mute_Button' + self._strip_buttons[-1].add_value_listener(self._mixer_button_value, identify_sender=True) + + self._mixer.master_strip().set_mute_button(ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 1, 17)) + self._mixer.set_strip_mute_solo_buttons(tuple(self._strip_buttons), mute_solo_flip_button) + #self._mixer.set_shift_button(self._shift_button) + self._mixer.updateMixerButtons() + + self._button9 = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 9 + 8) + + def _setup_session(self): + num_pads = len(PAD_TRANSLATIONS) + self._session = SessionComponent(8, 0) + self._session.name = 'Session_Control' + self._session.selected_scene().name = 'Selected_Scene' + self._session.set_mixer(self._mixer) + self._session.set_track_banking_increment(num_pads) + self._session.set_track_bank_buttons(ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 0, 35), ButtonElement(not IS_MOMENTARY, MIDI_CC_TYPE, 0, 36)) + pads = [] + for index in range(num_pads): + pads.append(ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 60 + index)) + pads[-1].name = 'Pad_' + str(index) + clip_slot = self._session.selected_scene().clip_slot(index) + clip_slot.set_triggered_to_play_value(GREEN_BLINK) + clip_slot.set_triggered_to_record_value(RED_BLINK) + clip_slot.set_stopped_value(AMBER_FULL) + clip_slot.set_started_value(GREEN_FULL) + clip_slot.set_recording_value(RED_FULL) + clip_slot.set_launch_button(pads[-1]) + clip_slot.name = str(index) + '_Selected_Clip_Slot' + + def _setup_transport(self): + rwd_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 27) + ffwd_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 28) + stop_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 29) + play_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 30) + loop_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 31) + rec_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 32) + ffwd_button.name = 'FFwd_Button' + rwd_button.name = 'Rwd_Button' + loop_button.name = 'Loop_Button' + play_button.name = 'Play_Button' + stop_button.name = 'Stop_Button' + rec_button.name = 'Record_Button' + self._transport = ShiftableTransportComponent(self.c_instance,self._session, self) + self._transport.name = 'Transport' + self._transport.set_stop_buttonOnInit(stop_button) + self._transport.set_play_button(play_button) + self._transport.set_record_buttonOnInit(rec_button) +# self._transport.set_shift_button(self._shift_button) + self._transport.set_mixer9_button(self._button9) + self._transport_view_modes = TransportViewModeSelector(self._transport, self._session, ffwd_button, rwd_button, loop_button) + self._transport_view_modes.name = 'Transport_View_Modes' + + def _setup_device(self): + encoders = [] + for index in range(8): + encoders.append(PeekableEncoderElement(MIDI_CC_TYPE, 1, index, Live.MidiMap.MapMode.relative_binary_offset)) + encoders[-1].set_feedback_delay(-1) + encoders[-1].add_value_listener(self._encoder_value, identify_sender=True) + encoders[-1].name = 'Device_Control_' + str(index) + + self._encoders = tuple(encoders) + self._prev_bank_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 1, 12) + self._next_bank_button = ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 1, 11) + self._prev_bank_button.name = 'Device_Bank_Down_Button' + self._next_bank_button.name = 'Device_Bank_Up_Button' + device = DeviceComponent() + device.name = 'Device_Component' + self.set_device_component(device) + device.set_parameter_controls(self._encoders) + device.set_bank_nav_buttons(self._prev_bank_button, self._next_bank_button) + + def _setup_name_display(self): + self._name_display = PhysicalDisplayElement(16, 1) + self._name_display.name = 'Display' + self._name_display.set_message_parts(SYSEX_START + (8,), (247,)) + self._name_display_data_source = DisplayDataSource() + self._name_display.segment(0).set_data_source(self._name_display_data_source) + + def _encoder_value(self, value, sender): + if not sender in self._encoders: + raise AssertionError + if not value in range(128): + raise AssertionError +# display_string = self._device_component.is_enabled() and ' - ' +# display_string = sender.mapped_parameter() != None and sender.mapped_parameter().name + display_string = '' + if self._device_component.is_enabled(): +# display_string = sender.name +# track = self.song().view.selected_track +# display_string = str(list(tracks).index(track) + 1) + pass + if (sender.mapped_parameter() != None): +# display_string = display_string + '-' + display_string = display_string + sender.mapped_parameter().name + self._set_string_to_display(display_string) + + def _slider_value(self, value, sender): + self.log ('_slider_value ' + str(value) + ' ' +str(sender)) + if not sender in tuple(self._sliders) + (self._master_slider,): + raise AssertionError + if not value in range(128): + raise AssertionError + if self._mixer.is_enabled(): + display_string = ' - ' + master = self.song().master_track + tracks = self.song().tracks + returns = self.song().return_tracks + track = None + if sender.mapped_parameter() != None: + self.log ('1') + if sender == self._master_slider: + self.log ('2') +# track = self._has_sliders and master + if self._has_sliders: + track = master + else: + self.log ('2.1') + track = self.song().view.selected_track + else: + self.log ('3') + track = self._mixer.channel_strip(self._sliders.index(sender))._track + else: + self.log ('4') + track = self.song().view.selected_track + self.log('track='+str(track)) + if track == master: + display_string = 'Master' + elif track in tracks: + display_string = str(list(tracks).index(track) + 1) + elif track in returns: + display_string = str(chr(ord('A') + list(returns).index(track))) + else: +# raise False or AssertionError + raise AssertionError + display_string += ' Volume' + self._set_string_to_display(display_string) + + def _mixer_button_value(self, value, sender): + if not value in range(128): + raise AssertionError + #if self._mixer.is_enabled() and value > 0: + if self._mixer.is_enabled(): + strip = self._mixer.channel_strip(self._strip_buttons.index(sender)) + #self._string_to_display = strip != None and None + self._name_display.segment(0).set_data_source(strip.track_name_data_source()) + self._name_display.update() + self._display_reset_delay = STANDARD_DISPLAY_DELAY + else: + self._set_string_to_display(' - ') + + def _preview_value(self, value): + if not value in range(128): + raise AssertionError + for encoder in self._encoders: + encoder.set_peek_mode(value > 0) + + def _show_current_track_name(self): + if self._name_display != None and self._mixer != None: + self._string_to_display = None + self._name_display.segment(0).set_data_source(self._mixer.selected_strip().track_name_data_source()) + self._name_display.update() + + def _show_startup_message(self): + self._name_display.display_message('LIVE') + self._display_reset_delay = INITIAL_DISPLAY_DELAY + + def _set_string_to_display(self, string_to_display): + if not isinstance(string_to_display, (str, unicode)): + raise AssertionError + self._name_display.segment(0).set_data_source(self._name_display_data_source) + self._string_to_display = string_to_display + self._display_reset_delay = STANDARD_DISPLAY_DELAY + + def _on_selected_track_changed(self): + self.log('_on_selected_track_changed') + ControlSurface._on_selected_track_changed(self) + self._show_current_track_name() + #all_tracks = self._has_sliders or self._session.tracks_to_use() + all_tracks2 = self._session.tracks_to_use() + selected_track = self.song().view.selected_track + num_strips = self._session.width() + if selected_track in all_tracks2: + track_index = list(all_tracks2).index(selected_track) + self.log('track_index '+ str(track_index)) + new_offset = track_index - track_index % num_strips + self.log('new_offset '+ str(new_offset)) + if not new_offset / num_strips == int(new_offset / num_strips): + raise AssertionError + self._session.set_offsets(new_offset, self._session.scene_offset()) + + + def _shift_value(self, value): + self.log("root shift handler") + if not self._shift_button != None: + raise AssertionError + if not value in range(128): + raise AssertionError + self.log("root shift handler 2") + self._shift_pressed = value > 0 +# calling other handlers + self._mixer._shift_value(value) + self._transport._shift_value(value) + +#clip stop + self.log("root shift handler 3") + num_pads = len(PAD_TRANSLATIONS) + pads = [] + for index in range(num_pads): + pads.append(ButtonElement(IS_MOMENTARY, MIDI_CC_TYPE, 0, 60 + index)) + pads[-1].name = 'Pad_' + str(index) + clip_slot = self._session.selected_scene().clip_slot(index) + if self._shift_pressed: + clip_slot.set_launch_button(None) + else: + clip_slot.set_launch_button(pads[index]) + if self._shift_pressed: + self._session.set_stop_track_clip_buttons(tuple(pads)) + else: + self._session.set_stop_track_clip_buttons(None) + + self.log("root shift handler 4") + + def log(self, message): + pass +# self.c_instance.log_message(message) diff --git a/Novation_Impulse2_9_1/PeekableEncoderElement.py b/Novation_Impulse2_9_1/PeekableEncoderElement.py new file mode 100644 index 00000000..14569cf1 --- /dev/null +++ b/Novation_Impulse2_9_1/PeekableEncoderElement.py @@ -0,0 +1,27 @@ +#Embedded file name: /Users/versonator/Hudson/live/Projects/AppLive/Resources/MIDI Remote Scripts/Novation_Impulse/PeekableEncoderElement.py +import Live +from _Framework.EncoderElement import EncoderElement +from _Framework.InputControlElement import * + +class PeekableEncoderElement(EncoderElement): + """ Encoder that can be connected and disconnected to a specific parameter """ + + def __init__(self, msg_type, channel, identifier, map_mode): + EncoderElement.__init__(self, msg_type, channel, identifier, map_mode) + self._peek_mode = False + + def set_peek_mode(self, peek_mode): + if not isinstance(peek_mode, type(False)): + raise AssertionError + self._peek_mode = self._peek_mode != peek_mode and peek_mode + self._request_rebuild() + + def get_peek_mode(self): + return self._peek_mode + + def install_connections(self, install_translation_callback, install_mapping_callback, install_forwarding_callback): + current_parameter = self._parameter_to_map_to + if self._peek_mode: + self._parameter_to_map_to = None + InputControlElement.install_connections(self, install_translation_callback, install_mapping_callback, install_forwarding_callback) + self._parameter_to_map_to = current_parameter \ No newline at end of file diff --git a/Novation_Impulse2_9_1/ShiftableTransportComponent.py b/Novation_Impulse2_9_1/ShiftableTransportComponent.py new file mode 100644 index 00000000..c12bd282 --- /dev/null +++ b/Novation_Impulse2_9_1/ShiftableTransportComponent.py @@ -0,0 +1,133 @@ +#Embedded file name: /Users/versonator/Hudson/live/Projects/AppLive/Resources/MIDI Remote Scripts/Novation_Impulse/ShiftableTransportComponent.py +import Live +from _Framework.ButtonElement import ButtonElement +from _Framework.TransportComponent import TransportComponent +from _Framework.ToggleComponent import ToggleComponent + +class ShiftableTransportComponent(TransportComponent): + """ Special transport class handling the seek buttons differently based on a shift button""" + + def __init__(self, c_instance, session, parent): + TransportComponent.__init__(self) + self.c_instance = c_instance + self._shift_pressed = False + self._mixer9_button = None + self._play_button = None + self._record_button = None + self._session = session + self._parent = parent + song = self.song() +# self._automation_toggle= self.register_component(ToggleComponent('session_automation_record', song)) + self._automation_toggle, self._re_enable_automation_toggle, self._delete_automation = self.register_components(ToggleComponent('session_automation_record', song), ToggleComponent('re_enable_automation_enabled', song, read_only=True), ToggleComponent('has_envelopes', None, read_only=True)) + + + + def disconnect(self): + if self._play_button != None: + self._play_button.remove_value_listener(self._play_pressed) + self._play_button = None + + TransportComponent.disconnect(self) + + def set_stop_buttonOnInit(self, button): + self.log("set_stop_buttonOnInit 1") + self._stop_button = button + self.set_stop_button(self._stop_button) + self.log("set_stopbuttonOnInit 2") + + + def set_record_buttonOnInit(self, button): + self.log("set_record_buttonOnInit 1") + self._record_button = button + self.set_record_button(self._record_button) + self.log("set_record_buttonOnInit 2") + + def set_mixer9_button(self, button): + self.log("set_mixer9_button 1") + self._mixer9_button = button + self.set_overdub_button(self._mixer9_button) + #self._automation_toggle.set_toggle_button(self._mixer9_button) + self.log("set_mixer9_button 2") + + + def set_play_button(self, button): + self._play_button = button + self._play_button.add_value_listener(self._play_pressed) + self._play_toggle.set_toggle_button(button) + + def _play_pressed(self, value): + self.log("_play_pressed " + str(value)) + if not value in range(128): + raise AssertionError + if self._shift_pressed: + if value != 0: + if self.song().can_undo: + #todo: add message + self.song().undo() + self.log("undoing") + self._parent._set_string_to_display('undoing') + + else: + #todo: add message + self._parent._set_string_to_display('cannot undo') + + + def _shift_value(self, value): + self.log("shift handler transport component " + str(value)) + if not value in range(128): + raise AssertionError + self.log("shift handler 2") + self._shift_pressed = self.is_enabled() and value > 0 + self.log("shift handler 3") + if self._shift_pressed: + self._play_toggle.set_toggle_button(None) + self._session.set_stop_all_clips_button(self._stop_button) + self.set_stop_button(None) + self.set_overdub_button(None) + self._automation_toggle.set_toggle_button(self._mixer9_button) + self.set_metronome_button(self._record_button) + self.set_record_button(None) + else: + self._play_toggle.set_toggle_button(self._play_button) + self._session.set_stop_all_clips_button(None) + self.set_stop_button(self._stop_button) + self.set_overdub_button(self._mixer9_button) + self._automation_toggle.set_toggle_button(None) + self.set_metronome_button(None) + self.set_record_button(self._record_button) + self.log("shift handler 4") + + + + def _ffwd_value(self, value): + self.log("ffwd handler main") + if not self._ffwd_button != None: + raise AssertionError + if not value in range(128): + raise AssertionError + else: + if self._shift_pressed: + self.log("ffwd shifted handler") + self.song().current_song_time = self._shift_pressed and self.song().last_event_time + else: + self.log("ffwd normal handler") + TransportComponent._ffwd_value(self, value) + + def _rwd_value(self, value): + self.log("rwd handler main") + if not self._rwd_button != None: + raise AssertionError + if not value in range(128): + raise AssertionError + else: + if self._shift_pressed: + self.song().current_song_time = self._shift_pressed and 0.0 + self.log("rwd shifted handler") + else: + self.log("rwd normal handler") + TransportComponent._rwd_value(self, value) + + def log(self, message): + pass +# self.c_instance.log_message(message) + diff --git a/Novation_Impulse2_9_1/SpecialMixerComponent.py b/Novation_Impulse2_9_1/SpecialMixerComponent.py new file mode 100644 index 00000000..5364732b --- /dev/null +++ b/Novation_Impulse2_9_1/SpecialMixerComponent.py @@ -0,0 +1,149 @@ +#Embedded file name: /Users/versonator/Hudson/live/Projects/AppLive/Resources/MIDI Remote Scripts/Novation_Impulse/SpecialMixerComponent.py +from _Framework.MixerComponent import MixerComponent +from _Framework.ButtonElement import ButtonElement + +class SpecialMixerComponent(MixerComponent): + """ Special mixer class that reassigns buttons to mute or solo based on a toggle """ + + def __init__(self, num_tracks, c_instance): + self.c_instance = c_instance + self._selected_mute_solo_button = None + self._strip_mute_solo_buttons = None + self._mute_solo_flip_button = None + MixerComponent.__init__(self, num_tracks) + self._selected_tracks = [] + self._register_timer_callback(self._on_timer) + self._shift_pressed = False + self._mute_solo_raw_value = 127 + + + def disconnect(self): + self._unregister_timer_callback(self._on_timer) + self._selected_tracks = None + MixerComponent.disconnect(self) + #if self._shift_button != None: + # self._shift_button.remove_value_listener(self._shift_value) + # self._shift_button = None + if self._mute_solo_flip_button != None: + self._mute_solo_flip_button.remove_value_listener(self._mute_solo_flip_value) + self._mute_solo_flip_button = None + self._selected_mute_solo_button = None + self._strip_mute_solo_buttons = None + + def set_strip_mute_solo_buttons(self, buttons, flip_button): + self.log("set_strip_mute_solo_buttons") + if not (buttons is None or isinstance(buttons, tuple) and len(buttons) == len(self._channel_strips)): + raise AssertionError + if not isinstance(flip_button, (type(None), ButtonElement)): + raise AssertionError + self._mute_solo_flip_button = flip_button + self._strip_mute_solo_buttons = buttons + if self._mute_solo_flip_button != None: + self._mute_solo_flip_button.remove_value_listener(self._mute_solo_flip_value) + if self._mute_solo_flip_button != None: + self._mute_solo_flip_button.add_value_listener(self._mute_solo_flip_value) + for index in range(len(self._channel_strips)): + strip = self.channel_strip(index) + button = None + if self._strip_mute_solo_buttons != None: + button = self._strip_mute_solo_buttons[index] + strip.set_mute_button(button) + strip.set_solo_button(None) + + def tracks_to_use(self): + return tuple(self.song().visible_tracks) + tuple(self.song().return_tracks) + + def _shift_value(self, value): + self.log("calling mixer shift value " + str(value)) + if (value > 0): + self._shift_pressed = True + else: + self._shift_pressed = False + self.updateMixerButtons() + pass + return + + def _mute_solo_flip_value(self, value): + #self.log(value) + if not self._mute_solo_flip_button != None: + raise AssertionError + if not value in range(128): + raise AssertionError + self._mute_solo_raw_value = value + self.updateMixerButtons() + + + def updateMixerButtons(self): + self.log("updateMixerButtons") + if self._strip_mute_solo_buttons != None: + for index in range(len(self._strip_mute_solo_buttons)): + strip = self.channel_strip(index) + self.log("setting strip") + if self._shift_pressed: + strip.set_mute_button(None) + strip.set_solo_button(None) + strip.set_arm_button(self._strip_mute_solo_buttons[index]) + else: + if self._mute_solo_raw_value == 0: + strip.set_mute_button(None) + strip.set_solo_button(self._strip_mute_solo_buttons[index]) + strip.set_arm_button(None) + else: + strip.set_solo_button(None) + strip.set_mute_button(self._strip_mute_solo_buttons[index]) + strip.set_arm_button(None) + + def _on_timer(self): +# self.log("_on_timer") + sel_track = None + while len(self._selected_tracks) > 0: + track = self._selected_tracks[-1] + if track != None and track.has_midi_input and track.can_be_armed and not track.arm: + sel_track = track + break + del self._selected_tracks[-1] + + if sel_track != None: + found_recording_clip = False + song = self.song() + tracks = song.tracks + if song.is_playing: + check_arrangement = song.record_mode + for track in tracks: + if track.can_be_armed and track.arm: + if check_arrangement: + found_recording_clip = True + break + else: + playing_slot_index = track.playing_slot_index + if playing_slot_index in range(len(track.clip_slots)): + slot = track.clip_slots[playing_slot_index] + if slot.has_clip and slot.clip.is_recording: + found_recording_clip = True + break + + if found_recording_clip or song.exclusive_arm: + for track in tracks: + if track.can_be_armed and track.arm and track != sel_track: + track.arm = False + + sel_track.arm = True + sel_track.view.select_instrument() + self._selected_tracks = [] +# self.updateMixerButtons() + + def _next_track_value(self, value): + self.log("_next_track_value "+ str(value)) + MixerComponent._next_track_value(self, value) + self._selected_tracks.append(self.song().view.selected_track) + self.updateMixerButtons() + + def _prev_track_value(self, value): + self.log("_prev_track_value " + str(value)) + MixerComponent._prev_track_value(self, value) + self._selected_tracks.append(self.song().view.selected_track) + self.updateMixerButtons() + + def log(self, message): + pass +# self.c_instance.log_message(message) diff --git a/Novation_Impulse2_9_1/TransportViewModeSelector.py b/Novation_Impulse2_9_1/TransportViewModeSelector.py new file mode 100644 index 00000000..63db9307 --- /dev/null +++ b/Novation_Impulse2_9_1/TransportViewModeSelector.py @@ -0,0 +1,58 @@ +#Embedded file name: /Users/versonator/Hudson/live/Projects/AppLive/Resources/MIDI Remote Scripts/Novation_Impulse/TransportViewModeSelector.py +import Live +from _Framework.ModeSelectorComponent import ModeSelectorComponent +from _Framework.ButtonElement import ButtonElement +from _Framework.TransportComponent import TransportComponent +from _Framework.SessionComponent import SessionComponent + +class TransportViewModeSelector(ModeSelectorComponent): + """ Class that reassigns specific buttons based on the views visible in Live """ + + def __init__(self, transport, session, ffwd_button, rwd_button, loop_button): + if not isinstance(transport, TransportComponent): + raise AssertionError + if not isinstance(session, SessionComponent): + raise AssertionError + if not isinstance(ffwd_button, ButtonElement): + raise AssertionError + if not isinstance(rwd_button, ButtonElement): + raise AssertionError + if not isinstance(loop_button, ButtonElement): + raise AssertionError + ModeSelectorComponent.__init__(self) + self._transport = transport + self._session = session + self._ffwd_button = ffwd_button + self._rwd_button = rwd_button + self._loop_button = loop_button + self.application().view.add_is_view_visible_listener('Session', self._on_view_changed) + self.update() + + def disconnect(self): + ModeSelectorComponent.disconnect(self) + self._transport = None + self._session = None + self._ffwd_button = None + self._rwd_button = None + self._loop_button = None + self.application().view.remove_is_view_visible_listener('Session', self._on_view_changed) + + def update(self): + if self.is_enabled(): + if self._mode_index == 0: + self._transport.set_seek_buttons(self._ffwd_button, self._rwd_button) + self._transport.set_loop_button(self._loop_button) + self._session.set_select_buttons(None, None) + self._session.selected_scene().set_launch_button(None) + else: + self._transport.set_seek_buttons(None, None) + self._transport.set_loop_button(None) + self._session.set_select_buttons(self._ffwd_button, self._rwd_button) + self._session.selected_scene().set_launch_button(self._loop_button) + + def _on_view_changed(self): + if self.application().view.is_view_visible('Session'): + self._mode_index = 1 + else: + self._mode_index = 0 + self.update() \ No newline at end of file diff --git a/Novation_Impulse2_9_1/__init__.py b/Novation_Impulse2_9_1/__init__.py new file mode 100644 index 00000000..2ff28270 --- /dev/null +++ b/Novation_Impulse2_9_1/__init__.py @@ -0,0 +1,12 @@ +#Embedded file name: /Users/versonator/Hudson/live/Projects/AppLive/Resources/MIDI Remote Scripts/Novation_Impulse/__init__.py +from Novation_Impulse2 import Novation_Impulse2 + +def create_instance(c_instance): + return Novation_Impulse2(c_instance) + + +from _Framework.Capabilities import * + +def get_capabilities(): + return {CONTROLLER_ID_KEY: controller_id(vendor_id=4661, product_ids=[25], model_name='Impulse 25'), + PORTS_KEY: [inport(props=[NOTES_CC, REMOTE, SCRIPT]), inport(props=[NOTES_CC, REMOTE]), outport(props=[NOTES_CC, REMOTE, SCRIPT])]} \ No newline at end of file diff --git a/Novation_Impulse2_9_1/debug.bat b/Novation_Impulse2_9_1/debug.bat new file mode 100644 index 00000000..c95dcf34 --- /dev/null +++ b/Novation_Impulse2_9_1/debug.bat @@ -0,0 +1,7 @@ +rem C:\Documents and Settings\%username%\Application Data\Ableton\Live 8.x.x\Preferences\Log.txt +rem cd "C:\Documents and Settings\%username%\Application Data\Ableton\Live 9.1\Preferences\" + +cd "C:\Documents and Settings\mbakirov\Application Data\Ableton\Live 9.1.2\Preferences\" + + +rem http://remotescripts.blogspot.com.au/2010_03_01_archive.html \ No newline at end of file diff --git a/Novation_Impulse2_9_1/deploy.bat b/Novation_Impulse2_9_1/deploy.bat new file mode 100644 index 00000000..9041931a --- /dev/null +++ b/Novation_Impulse2_9_1/deploy.bat @@ -0,0 +1,2 @@ +del /Q "D:\ProgramData\Ableton\Live 9 Suite\Resources\MIDI Remote Scripts\Novation_Impulse2\*.*" +xcopy *.py "D:\ProgramData\Ableton\Live 9 Suite\Resources\MIDI Remote Scripts\Novation_Impulse2\*.*" \ No newline at end of file diff --git a/Novation_Impulse2_9_1/readme.txt b/Novation_Impulse2_9_1/readme.txt new file mode 100644 index 00000000..6a1c9ae6 --- /dev/null +++ b/Novation_Impulse2_9_1/readme.txt @@ -0,0 +1,59 @@ +Features + +Mixer button 9 = OverDub +Shift + Mixer button 9 = Automation record on/off +Shift + Rec = Metronome on/off + +Shift + PLay = Undo +Shift + Stop = Stop all clips + +Shift + track mixer button = rec this track.. + +Shift + Clip buttons are now clip stop. + + + +Known issues that cannot be fixed +1) After moving track with Shift+Track buttons are not returned to inital condition. + Also happens sometimes in other cases (like bank switching with Shift). + Workaround - press and release Shit again. +2) Pressin shift + (mutes/solos) flip button leds to unpredictable results. + Workaround - do not do that, if you have done that, just press and release shift. + +The reason for these problems is that when you use shift+track or shift+bank, the message "shift off" is not sent. +This is out of the scope of what I can do. The bug report is sent to Novation. + +Known issues and todo: +FIXED: 1) impulse does not disconnect properly and does not send disconnection message. +FIXED 1.5) refactor shift - move it to main class. +FIXED: 2)when track goes down, the bank is not changed. +3) on initialize mute/solo buttons do not light + workaround - press shift button. +4) when arming a single track make the track selected + + +Future things to implement. + +DONE 1) mixer9 - overdub +DONE 3) Shift + Stop - stop all clips. + + +FIXED:4) Shift+Mixer9 - Automation record on/off +4.5) Shift + Rec = metronome +5) Shift + Loop switch session/arrangement (as loop is the button that also changes in this case) +FIXED: 6) Shift + play Undo (as play is like backspace but turned opposite) + +DONE: another parallel feature - Shift + drum pad in clip mode - stop the selected clip. + +think of re-mapping drum pads for another notes. + +7) display messages, when + metronome on/off + undo + switch session/arrangement + overdub + automation arm + +2) Shift + Mixer 9 - automation arm (seems to be rather hard - not sure there is API for that) + + diff --git a/README.md b/README.md index a78c7006..bc71668a 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,31 @@ -Ableton Live 9.6 Remote Scripts -============================= +please download from the folder -This is the unofficial repository of Ableton Live 9.6 MIDI Remote Scripts. +https://github.com/maratbakirov/AbletonLive9_RemoteScripts/releases/ -You can find more informations on -[this page](http://julienbayle.net/ableton-live-9-midi-remote-scripts "this page") +Novation_Impulse2.zip + main release for ableton version higher than 9.1 -**UPDATED on 31th March 2016 with Lemur Live Control v2.1.31 (just in case)** +Novation_Impulse2_9_1.zip + for ableton before 9.1 -(all files marked with older references didn't have changed since previous versions) +extract the zip to the -===> NO support given, ONLY source files ! +"C:\ProgramData\Ableton\Live 9 [here it could have said Suite 64]\Resources\MIDI Remote Scripts" + + + + +# All related projects: +https://github.com/maratbakirov/AbletonLive9_RemoteScripts + +https://github.com/maratbakirov/AbletonLive9_RemoteScripts/releases + +https://github.com/maratbakirov/AbletonLive10_MIDIRemoteScripts +https://github.com/maratbakirov/AbletonLive10_MIDIRemoteScripts/releases + +https://github.com/maratbakirov/AbletonLive11_MIDIRemoteScripts +https://github.com/maratbakirov/AbletonLive11_MIDIRemoteScripts/releases -Please, enjoy. diff --git a/UpgradeLog.htm b/UpgradeLog.htm new file mode 100644 index 00000000..712e88b5 Binary files /dev/null and b/UpgradeLog.htm differ diff --git a/UpgradeLog2.htm b/UpgradeLog2.htm new file mode 100644 index 00000000..ae87587e Binary files /dev/null and b/UpgradeLog2.htm differ diff --git a/_releases/Novation_Impulse2.zip b/_releases/Novation_Impulse2.zip new file mode 100644 index 00000000..61e535c7 Binary files /dev/null and b/_releases/Novation_Impulse2.zip differ diff --git a/_releases/Novation_Impulse2_9_1.zip b/_releases/Novation_Impulse2_9_1.zip new file mode 100644 index 00000000..b2818b59 Binary files /dev/null and b/_releases/Novation_Impulse2_9_1.zip differ diff --git a/_releases/README.md b/_releases/README.md new file mode 100644 index 00000000..7688fac2 --- /dev/null +++ b/_releases/README.md @@ -0,0 +1,4 @@ +please download from the releases + +https://github.com/maratbakirov/AbletonLive9_RemoteScripts/releases/ +