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/
+