├── DJ Template Project ├── Icon ├── Samples │ ├── empty.wav │ ├── 1 2-Audio.wav │ ├── empty.wav.asd │ ├── 1 2-Audio.wav.asd │ └── Deadmau5 │ │ └── Kicks │ │ └── Analog Kicks │ │ ├── XF_Kick_A_002.wav │ │ └── XF_Kick_A_002.wav.asd ├── DJ Template v5.als └── Ableton Project Info │ └── Project8_1.cfg ├── Dark Theme.ask ├── APCAdvanced ├── APC.pyc ├── VUMeters.pyc ├── __init__.pyc ├── apc40_22.jpg ├── APC40plus22.pyc ├── Matrix_Maps.pyc ├── LooperComponent.pyc ├── RepeatComponent.pyc ├── EncoderEQComponent.pyc ├── APCSessionComponent.pyc ├── MatrixModesComponent.pyc ├── RingedEncoderElement.pyc ├── SliderModesComponent.pyc ├── SpecialMixerComponent.pyc ├── EncoderDeviceComponent.pyc ├── PedaledSessionComponent.pyc ├── ConfigurableButtonElement.pyc ├── EncoderUserModesComponent.pyc ├── ShiftableDeviceComponent.pyc ├── ShiftableSelectorComponent.pyc ├── ShiftableTranslatorComponent.pyc ├── ShiftableTransportComponent.pyc ├── SpecialChannelStripComponent.pyc ├── DetailViewControllerComponent.pyc ├── EncoderMixerModeSelectorComponent.pyc ├── ShiftableEncoderSelectorComponent.pyc ├── __init__.py ├── SpecialChannelStripComponent.py ├── RepeatComponent.py ├── ShiftableTranslatorComponent.py ├── SliderModesComponent.py ├── ConfigurableButtonElement.py ├── RingedEncoderElement.py ├── ShiftableEncoderSelectorComponent.py ├── ShiftableDeviceComponent.py ├── PedaledSessionComponent.py ├── ShiftableSelectorComponent.py ├── APCSessionComponent.py ├── EncoderDeviceComponent.py ├── SpecialMixerComponent.py ├── ReadMe.txt ├── EncoderUserModesComponent.py ├── EncoderMixerModeSelectorComponent.py ├── EncoderEQComponent.py ├── LooperComponent.py ├── Matrix_Maps.py ├── APC.py ├── MatrixModesComponent.py ├── VUMeters.py ├── DetailViewControllerComponent.py ├── ShiftableTransportComponent.py └── APC40plus22.py ├── README └── Installer.pmdoc ├── 02dj-contents.xml ├── 03dark.xml ├── 02dj.xml └── index.xml /DJ Template Project/Icon : -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Dark Theme.ask: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/Dark Theme.ask -------------------------------------------------------------------------------- /APCAdvanced/APC.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/APC.pyc -------------------------------------------------------------------------------- /APCAdvanced/VUMeters.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/VUMeters.pyc -------------------------------------------------------------------------------- /APCAdvanced/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/__init__.pyc -------------------------------------------------------------------------------- /APCAdvanced/apc40_22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/apc40_22.jpg -------------------------------------------------------------------------------- /APCAdvanced/APC40plus22.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/APC40plus22.pyc -------------------------------------------------------------------------------- /APCAdvanced/Matrix_Maps.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/Matrix_Maps.pyc -------------------------------------------------------------------------------- /APCAdvanced/LooperComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/LooperComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/RepeatComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/RepeatComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/EncoderEQComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/EncoderEQComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/APCSessionComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/APCSessionComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/MatrixModesComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/MatrixModesComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/RingedEncoderElement.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/RingedEncoderElement.pyc -------------------------------------------------------------------------------- /APCAdvanced/SliderModesComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/SliderModesComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/SpecialMixerComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/SpecialMixerComponent.pyc -------------------------------------------------------------------------------- /DJ Template Project/Samples/empty.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/DJ Template Project/Samples/empty.wav -------------------------------------------------------------------------------- /APCAdvanced/EncoderDeviceComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/EncoderDeviceComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/PedaledSessionComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/PedaledSessionComponent.pyc -------------------------------------------------------------------------------- /DJ Template Project/DJ Template v5.als: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/DJ Template Project/DJ Template v5.als -------------------------------------------------------------------------------- /APCAdvanced/ConfigurableButtonElement.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/ConfigurableButtonElement.pyc -------------------------------------------------------------------------------- /APCAdvanced/EncoderUserModesComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/EncoderUserModesComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/ShiftableDeviceComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/ShiftableDeviceComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/ShiftableSelectorComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/ShiftableSelectorComponent.pyc -------------------------------------------------------------------------------- /DJ Template Project/Samples/1 2-Audio.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/DJ Template Project/Samples/1 2-Audio.wav -------------------------------------------------------------------------------- /DJ Template Project/Samples/empty.wav.asd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/DJ Template Project/Samples/empty.wav.asd -------------------------------------------------------------------------------- /APCAdvanced/ShiftableTranslatorComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/ShiftableTranslatorComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/ShiftableTransportComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/ShiftableTransportComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/SpecialChannelStripComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/SpecialChannelStripComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/DetailViewControllerComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/DetailViewControllerComponent.pyc -------------------------------------------------------------------------------- /DJ Template Project/Samples/1 2-Audio.wav.asd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/DJ Template Project/Samples/1 2-Audio.wav.asd -------------------------------------------------------------------------------- /APCAdvanced/EncoderMixerModeSelectorComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/EncoderMixerModeSelectorComponent.pyc -------------------------------------------------------------------------------- /APCAdvanced/ShiftableEncoderSelectorComponent.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/APCAdvanced/ShiftableEncoderSelectorComponent.pyc -------------------------------------------------------------------------------- /DJ Template Project/Ableton Project Info/Project8_1.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/DJ Template Project/Ableton Project Info/Project8_1.cfg -------------------------------------------------------------------------------- /DJ Template Project/Samples/Deadmau5/Kicks/Analog Kicks/XF_Kick_A_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/DJ Template Project/Samples/Deadmau5/Kicks/Analog Kicks/XF_Kick_A_002.wav -------------------------------------------------------------------------------- /DJ Template Project/Samples/Deadmau5/Kicks/Analog Kicks/XF_Kick_A_002.wav.asd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willrjmarshall/AbletonDJTemplateUnsupported/HEAD/DJ Template Project/Samples/Deadmau5/Kicks/Analog Kicks/XF_Kick_A_002.wav.asd -------------------------------------------------------------------------------- /APCAdvanced/__init__.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # -*- coding: utf-8 -*- 3 | 4 | import Live 5 | from APC40plus22 import APC40plus22 6 | 7 | def create_instance(c_instance): 8 | ' Creates and returns the APC40_22 script ' 9 | return APC40plus22(c_instance) 10 | 11 | # local variables: 12 | # tab-width: 4 13 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is an open-source template for DJing with Ableton Live 8 with an Akai APC40. You are welcome to do whatever you want with it: perform, distribute, modify, etc. 2 | 3 | IMPORTANT: A video explaining how this template works and how it can be used can be found at http://www.vimeo.com/6993740 4 | 5 | Watch it before doing anything else! 6 | 7 | This template requires the full version of Ableton Live 8. It is best to upgrade to the latest version (currently 8.2.2), as there are stability issues with previous versions. 8 | 9 | If you don't own an APC, you could readily map this template to whatever controller you have. 10 | 11 | If you have any issues getting this working, feel free to drop me a message. 12 | -------------------------------------------------------------------------------- /Installer.pmdoc/02dj-contents.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Installer.pmdoc/03dark.xml: -------------------------------------------------------------------------------- 1 | com.willmarshall.me.willMarshallsAbletonDjTemplate.DarkTheme.pkg1.0Dark Theme.ask/tmp/installTo.pathinstallFrom.isRelativeTypescripts.postflight.isRelativeTypeparentinstallTotheme_transfer03dark-contents.xml/CVS$/\.svn$/\.cvsignore$/\.cvspass$/\.DS_Store$ -------------------------------------------------------------------------------- /Installer.pmdoc/02dj.xml: -------------------------------------------------------------------------------- 1 | com.willmarshall.me.willMarshallsAbletonDjTemplate.DJTemplateProject.pkg1.0DJ Template Project/tmp/WillMarshallDJTemplateinstallTo.pathinstallFrom.isRelativeTypescripts.postflight.isRelativeTypeparentrequireAuthorizationinstallTodj_template_transfer02dj-contents.xml/CVS$/\.svn$/\.cvsignore$/\.cvspass$/\.DS_Store$ -------------------------------------------------------------------------------- /APCAdvanced/SpecialChannelStripComponent.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # -*- coding: utf-8 -*- 3 | 4 | #import Live #added 5 | from _Framework.ChannelStripComponent import ChannelStripComponent 6 | TRACK_FOLD_DELAY = 5 7 | class SpecialChannelStripComponent(ChannelStripComponent): 8 | ' Subclass of channel strip component using select button for (un)folding tracks ' 9 | __module__ = __name__ 10 | 11 | def __init__(self): 12 | ChannelStripComponent.__init__(self) 13 | self._toggle_fold_ticks_delay = -1 14 | self._register_timer_callback(self._on_timer) 15 | 16 | 17 | def disconnect(self): 18 | self._unregister_timer_callback(self._on_timer) 19 | ChannelStripComponent.disconnect(self) 20 | 21 | 22 | def _select_value(self, value): 23 | ChannelStripComponent._select_value(self, value) 24 | if (self.is_enabled() and (self._track != None)): 25 | if (self._track.is_foldable and (self._select_button.is_momentary() and (value != 0))): 26 | self._toggle_fold_ticks_delay = TRACK_FOLD_DELAY 27 | else: 28 | self._toggle_fold_ticks_delay = -1 29 | 30 | 31 | def _on_timer(self): 32 | if (self.is_enabled() and (self._track != None)): 33 | if (self._toggle_fold_ticks_delay > -1): 34 | assert self._track.is_foldable 35 | if (self._toggle_fold_ticks_delay == 0): 36 | self._track.fold_state = (not self._track.fold_state) 37 | self._toggle_fold_ticks_delay -= 1 38 | 39 | 40 | # local variables: 41 | # tab-width: 4 -------------------------------------------------------------------------------- /APCAdvanced/RepeatComponent.py: -------------------------------------------------------------------------------- 1 | from _Framework.ButtonElement import ButtonElement #added 2 | from _Framework.EncoderElement import EncoderElement #added 3 | 4 | 5 | class RepeatComponent(): 6 | 'Handles beat repeat controls' 7 | __module__ = __name__ 8 | 9 | 10 | def __init__(self, parent): 11 | self._shift_button = None 12 | self._shift_pressed = False 13 | self._rack = None 14 | 15 | for device in parent.song().master_track.devices: 16 | if device.name == "Repeats": 17 | self._rack = device 18 | break 19 | 20 | if self._rack: 21 | for scene_index in range(5): 22 | scene = parent._session.scene(scene_index) 23 | button = scene._launch_button 24 | scene.set_launch_button(None) 25 | 26 | parent._device_buttons.append(button) 27 | button.add_value_listener(self._device_toggle, True) 28 | 29 | def _device_toggle(self, value, sender): 30 | if not self._shift_pressed: 31 | id = sender.message_identifier() - 82 32 | self._rack.parameters[id + 1].value = (value * 127) 33 | 34 | 35 | 36 | 37 | 38 | def set_shift_button(self, button): #added 39 | assert ((button == None) or (isinstance(button, ButtonElement) and button.is_momentary())) 40 | if (self._shift_button != button): 41 | if (self._shift_button != None): 42 | self._shift_button.remove_value_listener(self._shift_value) 43 | self._shift_button = button 44 | if (self._shift_button != None): 45 | self._shift_button.add_value_listener(self._shift_value) 46 | 47 | def _shift_value(self, value): #added 48 | assert (self._shift_button != None) 49 | assert (value in range(128)) 50 | self._shift_pressed = (value != 0) 51 | -------------------------------------------------------------------------------- /APCAdvanced/ShiftableTranslatorComponent.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # -*- coding: utf-8 -*- 3 | 4 | from _Framework.ChannelTranslationSelector import ChannelTranslationSelector 5 | from _Framework.ButtonElement import ButtonElement 6 | from _Framework.MixerComponent import MixerComponent 7 | class ShiftableTranslatorComponent(ChannelTranslationSelector): 8 | ' Class that translates the channel of some buttons as long as a shift button is held ' 9 | __module__ = __name__ 10 | 11 | def __init__(self): 12 | ChannelTranslationSelector.__init__(self) 13 | self._shift_button = None 14 | self._shift_pressed = False 15 | 16 | 17 | 18 | def disconnect(self): 19 | if (self._shift_button != None): 20 | self._shift_button.remove_value_listener(self._shift_value) 21 | self._shift_button = None 22 | ChannelTranslationSelector.disconnect(self) 23 | 24 | 25 | 26 | def set_shift_button(self, button): 27 | assert ((button == None) or (isinstance(button, ButtonElement) and button.is_momentary())) 28 | if (self._shift_button != None): 29 | self._shift_button.remove_value_listener(self._shift_value) 30 | self._shift_button = button 31 | if (self._shift_button != None): 32 | self._shift_button.add_value_listener(self._shift_value) 33 | self.set_mode(0) 34 | 35 | 36 | 37 | def on_enabled_changed(self): 38 | if self.is_enabled(): 39 | self.set_mode(int(self._shift_pressed)) 40 | 41 | 42 | 43 | def number_of_modes(self): 44 | return 2 45 | 46 | 47 | 48 | def _shift_value(self, value): 49 | assert (self._shift_button != None) 50 | assert (value in range(128)) 51 | self._shift_pressed = (value != 0) 52 | if self.is_enabled(): 53 | self.set_mode(int(self._shift_pressed)) 54 | 55 | 56 | 57 | # local variables: 58 | # tab-width: 4 59 | -------------------------------------------------------------------------------- /APCAdvanced/SliderModesComponent.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | 3 | import Live 4 | from _Framework.ModeSelectorComponent import ModeSelectorComponent 5 | from _Framework.ButtonElement import ButtonElement 6 | class SliderModesComponent(ModeSelectorComponent): 7 | ' SelectorComponent that assigns sliders to different functions ' 8 | __module__ = __name__ 9 | 10 | def __init__(self, mixer, sliders): 11 | assert (len(sliders) == 8) 12 | ModeSelectorComponent.__init__(self) 13 | self._mixer = mixer 14 | self._sliders = sliders 15 | self._mode_index = 0 16 | 17 | 18 | def disconnect(self): 19 | ModeSelectorComponent.disconnect(self) 20 | self._mixer = None 21 | self._sliders = None 22 | 23 | 24 | def set_mode_buttons(self, buttons): 25 | assert isinstance(buttons, (tuple, 26 | type(None))) 27 | for button in self._modes_buttons: 28 | button.remove_value_listener(self._mode_value) 29 | 30 | self._modes_buttons = [] 31 | if (buttons != None): 32 | for button in buttons: 33 | assert isinstance(button, ButtonElement) 34 | identify_sender = True 35 | button.add_value_listener(self._mode_value, identify_sender) 36 | self._modes_buttons.append(button) 37 | 38 | self.update() 39 | 40 | 41 | def number_of_modes(self): 42 | return 8 43 | 44 | 45 | def update(self): 46 | if self.is_enabled(): 47 | assert (self._mode_index in range(self.number_of_modes())) 48 | for index in range(len(self._modes_buttons)): 49 | if (index == self._mode_index): 50 | self._modes_buttons[index].turn_on() 51 | else: 52 | self._modes_buttons[index].turn_off() 53 | 54 | for index in range(len(self._sliders)): 55 | strip = self._mixer.channel_strip(index) 56 | slider = self._sliders[index] 57 | slider.use_default_message() 58 | slider.set_identifier((slider.message_identifier() - self._mode_index)) 59 | strip.set_volume_control(None) 60 | strip.set_pan_control(None) 61 | strip.set_send_controls((None, None, None)) 62 | slider.release_parameter() 63 | if (self._mode_index == 0): 64 | strip.set_volume_control(slider) 65 | elif (self._mode_index == 1): 66 | strip.set_pan_control(slider) 67 | elif (self._mode_index < 5): 68 | send_controls = [None, 69 | None, 70 | None] 71 | send_controls[(self._mode_index - 2)] = slider 72 | strip.set_send_controls(tuple(send_controls)) 73 | #self._rebuild_callback() 74 | 75 | 76 | # local variables: 77 | # tab-width: 4 78 | -------------------------------------------------------------------------------- /APCAdvanced/ConfigurableButtonElement.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | #From Launchpad scripts 3 | 4 | import Live 5 | from _Framework.ButtonElement import * 6 | class ConfigurableButtonElement(ButtonElement): 7 | __module__ = __name__ 8 | __doc__ = ' Special button class that can be configured with custom on- and off-values ' 9 | 10 | def __init__(self, is_momentary, msg_type, channel, identifier): 11 | ButtonElement.__init__(self, is_momentary, msg_type, channel, identifier) 12 | self._on_value = 1 #127 for Launchpad #0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green 13 | self._off_value = 0 #4 for Launchpad, 0 for APC40/20 14 | self._is_enabled = True 15 | self._is_notifying = False 16 | self._force_next_value = False 17 | self._pending_listeners = [] 18 | 19 | 20 | 21 | def set_on_off_values(self, on_value, off_value): 22 | assert (on_value in range(128)) 23 | assert (off_value in range(128)) 24 | self._last_sent_value = -1 25 | self._on_value = on_value 26 | self._off_value = off_value 27 | 28 | 29 | 30 | def set_force_next_value(self): 31 | self._force_next_value = True 32 | 33 | 34 | 35 | def set_enabled(self, enabled): 36 | self._is_enabled = enabled 37 | 38 | 39 | 40 | def turn_on(self): 41 | self.send_value(self._on_value) 42 | 43 | 44 | 45 | def turn_off(self): 46 | self.send_value(self._off_value) 47 | 48 | 49 | 50 | def reset(self): 51 | self.send_value(0) #4 for Launchpad, 0 for APC40/20 52 | 53 | 54 | 55 | def add_value_listener(self, callback, identify_sender = False): 56 | if (not self._is_notifying): 57 | ButtonElement.add_value_listener(self, callback, identify_sender) 58 | else: 59 | self._pending_listeners.append((callback, 60 | identify_sender)) 61 | 62 | 63 | 64 | def receive_value(self, value): 65 | self._is_notifying = True 66 | ButtonElement.receive_value(self, value) 67 | self._is_notifying = False 68 | for listener in self._pending_listeners: 69 | self.add_value_listener(listener[0], listener[1]) 70 | 71 | self._pending_listeners = [] 72 | 73 | 74 | 75 | def send_value(self, value, force = False): 76 | ButtonElement.send_value(self, value, (force or self._force_next_value)) 77 | self._force_next_value = False 78 | 79 | 80 | 81 | def install_connections(self): 82 | if self._is_enabled: 83 | ButtonElement.install_connections(self) 84 | elif ((self._msg_channel != self._original_channel) or (self._msg_identifier != self._original_identifier)): 85 | self._install_translation(self._msg_type, self._original_identifier, self._original_channel, self._msg_identifier, self._msg_channel) 86 | 87 | 88 | 89 | 90 | # local variables: 91 | # tab-width: 4 92 | -------------------------------------------------------------------------------- /APCAdvanced/RingedEncoderElement.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # -*- coding: utf-8 -*- 3 | 4 | from _Framework.EncoderElement import EncoderElement 5 | from _Framework.ButtonElement import ButtonElement 6 | RING_OFF_VALUE = 0 7 | RING_SIN_VALUE = 1 8 | RING_VOL_VALUE = 2 9 | RING_PAN_VALUE = 3 10 | class RingedEncoderElement(EncoderElement): 11 | ' Class representing a continuous control on the controller enclosed with an LED ring ' 12 | __module__ = __name__ 13 | 14 | def __init__(self, msg_type, channel, identifier, map_mode): 15 | EncoderElement.__init__(self, msg_type, channel, identifier, map_mode) 16 | self._ring_mode_button = None 17 | self.set_needs_takeover(False) 18 | 19 | 20 | 21 | def set_ring_mode_button(self, button): 22 | assert ((button == None) or isinstance(button, ButtonElement)) 23 | if (self._ring_mode_button != None): 24 | force_send = True 25 | self._ring_mode_button.send_value(RING_OFF_VALUE, force_send) 26 | self._ring_mode_button = button 27 | self._update_ring_mode() 28 | 29 | 30 | 31 | def connect_to(self, parameter): 32 | if ((parameter != self._parameter_to_map_to) and (not self.is_mapped_manually())): 33 | force_send = True 34 | self._ring_mode_button.send_value(RING_OFF_VALUE, force_send) 35 | EncoderElement.connect_to(self, parameter) 36 | 37 | 38 | 39 | def release_parameter(self): 40 | EncoderElement.release_parameter(self) 41 | self._update_ring_mode() 42 | 43 | 44 | 45 | def install_connections(self): 46 | EncoderElement.install_connections(self) 47 | if ((not self._is_mapped) and (len(self._value_notifications) == 0)): 48 | self._is_being_forwarded = self._install_forwarding(self) 49 | self._update_ring_mode() 50 | 51 | 52 | 53 | def is_mapped_manually(self): 54 | return ((not self._is_mapped) and (not self._is_being_forwarded)) 55 | 56 | 57 | 58 | def _update_ring_mode(self): 59 | if (self._ring_mode_button != None): 60 | force_send = True 61 | if self.is_mapped_manually(): 62 | self._ring_mode_button.send_value(RING_SIN_VALUE, force_send) 63 | elif (self._parameter_to_map_to != None): 64 | param = self._parameter_to_map_to 65 | p_range = (param.max - param.min) 66 | value = (((param.value - param.min) / p_range) * 127) 67 | self.send_value(int(value), force_send) 68 | if (self._parameter_to_map_to.min == (-1 * self._parameter_to_map_to.max)): 69 | self._ring_mode_button.send_value(RING_PAN_VALUE, force_send) 70 | elif self._parameter_to_map_to.is_quantized: 71 | self._ring_mode_button.send_value(RING_SIN_VALUE, force_send) 72 | else: 73 | self._ring_mode_button.send_value(RING_VOL_VALUE, force_send) 74 | else: 75 | self._ring_mode_button.send_value(RING_OFF_VALUE, force_send) 76 | 77 | 78 | 79 | # local variables: 80 | # tab-width: 4 -------------------------------------------------------------------------------- /Installer.pmdoc/index.xml: -------------------------------------------------------------------------------- 1 | Will Marshall's Ableton DJ Template/Users/willmarshall/Projects/Will Marshall's Ableton DJ Template.mpkgcom.willmarshall.meThis will install the APC40 DJ Template, along with the APCAdvanced remote script. 2 | 3 | Installing will close Ableton!/tmp/APCAdvanced/tmp/WillMarshallDJTemplate/tmp//Users/willmarshall/Desktop/Ableton_logo_screen.gifcom.ableton.liveLive01apcadvanced.xml02dj.xml03dark.xmldescriptionproperties.titleproperties.userDomainproperties.customizeOptionpreinstallActions.actionsproperties.systemDomainproperties.anywhereDomain -------------------------------------------------------------------------------- /APCAdvanced/ShiftableEncoderSelectorComponent.py: -------------------------------------------------------------------------------- 1 | 2 | import Live 3 | from _Framework.ModeSelectorComponent import ModeSelectorComponent 4 | from _Framework.ButtonElement import ButtonElement 5 | from _Framework.DeviceComponent import DeviceComponent 6 | 7 | class ShiftableEncoderSelectorComponent(ModeSelectorComponent): 8 | __doc__ = ' SelectorComponent that assigns encoders to functions based on the shift button ' 9 | 10 | def __init__(self, parent, bank_buttons, encoder_user_modes, encoder_modes, encoder_eq_modes, encoder_device_modes):#, select_buttons, master_button, arm_buttons, matrix, session, zooming, mixer, slider_modes, matrix_modes): #, mode_callback): 11 | if not len(bank_buttons) == 4: 12 | raise AssertionError 13 | ModeSelectorComponent.__init__(self) 14 | self._toggle_pressed = False 15 | self._invert_assignment = False 16 | self._parent = parent 17 | self._bank_buttons = bank_buttons 18 | self._encoder_user_modes = encoder_user_modes 19 | self._encoder_modes = encoder_modes 20 | self._encoder_eq_modes = encoder_eq_modes 21 | self._encoder_device_modes = encoder_device_modes 22 | 23 | def disconnect(self): 24 | ModeSelectorComponent.disconnect(self) 25 | self._parent = None #added 26 | self._bank_buttons = None #added 27 | self._encoder_modes = None 28 | self._encoder_user_modes = None 29 | self._encoder_eq_modes = None 30 | self._encoder_device_modes = None 31 | return None 32 | 33 | def set_mode_toggle(self, button): 34 | ModeSelectorComponent.set_mode_toggle(self, button) #called from parent: self._shift_modes.set_mode_toggle(self._shift_button) 35 | self.set_mode(0) 36 | 37 | def invert_assignment(self): 38 | self._invert_assignment = True 39 | self._recalculate_mode() 40 | 41 | def number_of_modes(self): 42 | return 2 43 | 44 | def update(self): 45 | if self.is_enabled(): 46 | if self._mode_index == int(self._invert_assignment): 47 | self._encoder_user_modes.set_mode_buttons(None) 48 | self._encoder_modes.set_modes_buttons(self._bank_buttons) 49 | else: 50 | self._encoder_modes.set_modes_buttons(None) 51 | self._encoder_user_modes.set_mode_buttons(self._bank_buttons) 52 | return None 53 | 54 | 55 | def _toggle_value(self, value): #"toggle" is shift button 56 | if not self._mode_toggle != None: 57 | raise AssertionError 58 | if not value in range(128): 59 | raise AssertionError 60 | self._toggle_pressed = value > 0 61 | self._recalculate_mode() 62 | if value > 0: 63 | self._encoder_eq_modes._ignore_buttons = True 64 | if self._encoder_eq_modes._track_eq != None: 65 | self._encoder_eq_modes._track_eq._ignore_cut_buttons = True 66 | self._encoder_device_modes._ignore_buttons = True 67 | for button in self._encoder_user_modes._modes_buttons: 68 | button.use_default_message() 69 | else: 70 | self._encoder_eq_modes._ignore_buttons = False 71 | if self._encoder_eq_modes._track_eq != None: 72 | self._encoder_eq_modes._track_eq._ignore_cut_buttons = False 73 | self._encoder_device_modes._ignore_buttons = False 74 | if self._encoder_user_modes._mode_index == 3: 75 | for control in self._encoder_user_modes._param_controls: 76 | control.set_channel(9 + self._encoder_user_modes._mode_index) 77 | if self._encoder_user_modes._user_buttons != None: 78 | for button in self._encoder_user_modes._user_buttons: 79 | button.turn_off() 80 | for button in self._encoder_user_modes._user_buttons: 81 | button.set_channel(9 + self._encoder_user_modes._mode_index) 82 | 83 | return None 84 | 85 | 86 | def _recalculate_mode(self): #called if toggle (i.e. shift) is pressed 87 | self.set_mode((int(self._toggle_pressed) + int(self._invert_assignment)) % self.number_of_modes()) 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /APCAdvanced/ShiftableDeviceComponent.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # -*- coding: utf-8 -*- 3 | 4 | import Live 5 | from _Generic.Devices import * 6 | from _Framework.DeviceComponent import DeviceComponent 7 | from _Framework.ChannelTranslationSelector import ChannelTranslationSelector 8 | from _Framework.ButtonElement import ButtonElement 9 | class ShiftableDeviceComponent(DeviceComponent): 10 | ' DeviceComponent that only uses bank buttons if a shift button is pressed ' 11 | __module__ = __name__ 12 | 13 | def __init__(self): 14 | DeviceComponent.__init__(self) 15 | self._shift_button = None 16 | self._shift_pressed = False 17 | self._control_translation_selector = ChannelTranslationSelector(8) 18 | 19 | 20 | def disconnect(self): 21 | DeviceComponent.disconnect(self) 22 | self._control_translation_selector.disconnect() 23 | if (self._shift_button != None): 24 | self._shift_button.remove_value_listener(self._shift_value) 25 | self._shift_button = None 26 | 27 | 28 | def set_parameter_controls(self, controls): 29 | DeviceComponent.set_parameter_controls(self, controls) 30 | self._control_translation_selector.set_controls_to_translate(controls) 31 | self._control_translation_selector.set_mode(self._bank_index) 32 | 33 | 34 | 35 | def set_device(self, device): 36 | DeviceComponent.set_device(self, device) 37 | self._control_translation_selector.set_mode(self._bank_index) 38 | 39 | 40 | 41 | def set_shift_button(self, button): 42 | assert ((button == None) or (isinstance(button, ButtonElement) and button.is_momentary())) 43 | if (self._shift_button != button): 44 | if (self._shift_button != None): 45 | self._shift_button.remove_value_listener(self._shift_value) 46 | self._shift_button = button 47 | if (self._shift_button != None): 48 | self._shift_button.add_value_listener(self._shift_value) 49 | self.update() 50 | 51 | 52 | 53 | def update(self): 54 | if (self._parameter_controls != None): 55 | for control in self._parameter_controls: 56 | control.release_parameter() 57 | 58 | if (self.is_enabled() and (self._device != None)): 59 | self._device_bank_registry[self._device] = self._bank_index 60 | if ((self._parameter_controls != None) and (self._bank_index < number_of_parameter_banks(self._device))): 61 | old_bank_name = self._bank_name 62 | self._assign_parameters() 63 | if (self._bank_name != old_bank_name): 64 | self._show_msg_callback(((self._device.name + ' Bank: ') + self._bank_name)) 65 | 66 | if (not self._shift_pressed): 67 | self._on_on_off_changed() 68 | 69 | elif (self._bank_buttons != None): 70 | for index in range(len(self._bank_buttons)): 71 | if (index == self._bank_index): 72 | self._bank_buttons[index].turn_on() 73 | else: 74 | self._bank_buttons[index].turn_off() 75 | 76 | #self._rebuild_callback() 77 | 78 | 79 | 80 | def _shift_value(self, value): 81 | assert (self._shift_button != None) 82 | assert (value in range(128)) 83 | self._shift_pressed = (value != 0) 84 | self.update() 85 | 86 | 87 | 88 | def _bank_value(self, value, sender): 89 | assert ((sender != None) and (sender in self._bank_buttons)) 90 | if (self._shift_pressed and self.is_enabled()): 91 | if ((value != 0) or (not sender.is_momentary())): 92 | self._bank_name = '' 93 | self._bank_index = list(self._bank_buttons).index(sender) 94 | self._control_translation_selector.set_mode(self._bank_index) 95 | self.update() 96 | 97 | 98 | 99 | def _on_off_value(self, value): 100 | if not self._shift_pressed: 101 | DeviceComponent._on_off_value(self, value) 102 | 103 | 104 | 105 | def _on_on_off_changed(self): 106 | if not self._shift_pressed: 107 | DeviceComponent._on_on_off_changed(self) 108 | 109 | 110 | 111 | def _lock_value(self, value): #added 112 | if self._shift_pressed: 113 | DeviceComponent._lock_value(self, value) 114 | 115 | 116 | # local variables: 117 | # tab-width: 4 118 | -------------------------------------------------------------------------------- /APCAdvanced/PedaledSessionComponent.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # -*- coding: utf-8 -*- 3 | 4 | import Live 5 | from APCSessionComponent import APCSessionComponent 6 | from _Framework.ButtonElement import ButtonElement 7 | from ConfigurableButtonElement import ConfigurableButtonElement #added 8 | class PedaledSessionComponent(APCSessionComponent): 9 | ' Special SessionComponent with a button (pedal) to fire the selected clip slot ' 10 | __module__ = __name__ 11 | 12 | def __init__(self, num_tracks, num_scenes): 13 | APCSessionComponent.__init__(self, num_tracks, num_scenes) 14 | self._slot_launch_button = None 15 | 16 | 17 | 18 | def disconnect(self): 19 | #for index in range(len(self._tracks_and_listeners)): #added from launchpad 20 | #track = self._tracks_and_listeners[index][0] 21 | #listener = self._tracks_and_listeners[index][2] 22 | #if ((track != None) and track.playing_slot_index_has_listener(listener)): 23 | #track.remove_playing_slot_index_listener(listener) 24 | APCSessionComponent.disconnect(self) 25 | if (self._slot_launch_button != None): 26 | self._slot_launch_button.remove_value_listener(self._slot_launch_value) 27 | self._slot_launch_button = None 28 | 29 | 30 | 31 | def set_slot_launch_button(self, button): 32 | assert ((button == None) or isinstance(button, ButtonElement)) 33 | if (self._slot_launch_button != button): 34 | if (self._slot_launch_button != None): 35 | self._slot_launch_button.remove_value_listener(self._slot_launch_value) 36 | self._slot_launch_button = button 37 | if (self._slot_launch_button != None): 38 | self._slot_launch_button.add_value_listener(self._slot_launch_value) 39 | #self._rebuild_callback() 40 | self.update() 41 | 42 | 43 | 44 | def _slot_launch_value(self, value): 45 | assert (value in range(128)) 46 | assert (self._slot_launch_button != None) 47 | if self.is_enabled(): 48 | if ((value != 0) or (not self._slot_launch_button.is_momentary())): 49 | if (self.song().view.highlighted_clip_slot != None): 50 | self.song().view.highlighted_clip_slot.fire() 51 | 52 | 53 | 54 | # local variables: 55 | # tab-width: 4 56 | 57 | #def _reassign_tracks(self): 58 | #for index in range(len(self._tracks_and_listeners)): 59 | #track = self._tracks_and_listeners[index][0] 60 | #fire_listener = self._tracks_and_listeners[index][1] 61 | #playing_listener = self._tracks_and_listeners[index][2] 62 | #if (track != None): 63 | #if track.fired_slot_index_has_listener(fire_listener): 64 | #track.remove_fired_slot_index_listener(fire_listener) 65 | #if track.playing_slot_index_has_listener(playing_listener): 66 | #track.remove_playing_slot_index_listener(playing_listener) 67 | 68 | #self._tracks_and_listeners = [] 69 | #tracks_to_use = self.tracks_to_use() 70 | #for index in range(self._num_tracks): 71 | #fire_listener = lambda index = index:self._on_fired_slot_index_changed(index) 72 | 73 | #playing_listener = lambda index = index:self._on_playing_slot_index_changed(index) 74 | 75 | #track = None 76 | #if ((self._track_offset + index) < len(tracks_to_use)): 77 | #track = tracks_to_use[(self._track_offset + index)] 78 | #if (track != None): 79 | #self._tracks_and_listeners.append((track, 80 | #fire_listener, 81 | #playing_listener)) 82 | #track.add_fired_slot_index_listener(fire_listener) 83 | #track.add_playing_slot_index_listener(playing_listener) 84 | #self._update_stop_clips_led(index) 85 | 86 | 87 | 88 | 89 | #def _on_fired_slot_index_changed(self, index): 90 | #self._update_stop_clips_led(index) 91 | 92 | 93 | 94 | #def _on_playing_slot_index_changed(self, index): 95 | #self._update_stop_clips_led(index) 96 | 97 | 98 | 99 | #def _update_stop_clips_led(self, index): 100 | #if (self.is_enabled() and (self._stop_track_clip_buttons != None)): 101 | #button = self._stop_track_clip_buttons[index] 102 | #if (index in range(len(self._tracks_and_listeners))): 103 | #track = self._tracks_and_listeners[index][0] 104 | #if (track.fired_slot_index == -2): 105 | #button.send_value(self._stop_track_clip_value) 106 | #elif (track.playing_slot_index >= 0): 107 | #button.send_value(1) 108 | #else: 109 | #button.turn_off() 110 | #else: 111 | #button.send_value(0) 112 | 113 | -------------------------------------------------------------------------------- /APCAdvanced/ShiftableSelectorComponent.py: -------------------------------------------------------------------------------- 1 | 2 | import Live 3 | from _Framework.ModeSelectorComponent import ModeSelectorComponent 4 | from _Framework.ButtonElement import ButtonElement 5 | from _Framework.DeviceComponent import DeviceComponent 6 | from EncoderUserModesComponent import EncoderUserModesComponent #added 7 | from PedaledSessionComponent import PedaledSessionComponent #added 8 | from _Framework.SessionZoomingComponent import SessionZoomingComponent #added 9 | #from consts import * #see below (not used) 10 | #MANUFACTURER_ID = 71 11 | #ABLETON_MODE = 65 12 | #NOTE_MODE = 65 #67 = APC20 Note Mode; 65 = APC40 Ableton Mode 1 13 | 14 | class ShiftableSelectorComponent(ModeSelectorComponent): 15 | __doc__ = ' SelectorComponent that assigns buttons to functions based on the shift button ' 16 | #def __init__(self, select_buttons, master_button, arm_buttons, matrix, session, zooming, mixer, transport, slider_modes, mode_callback): 17 | def __init__(self, parent, select_buttons, master_button, arm_buttons, matrix, session, zooming, mixer, slider_modes, matrix_modes): 18 | if not len(select_buttons) == 8: 19 | raise AssertionError 20 | if not len(arm_buttons) == 8: 21 | raise AssertionError 22 | ModeSelectorComponent.__init__(self) 23 | self._toggle_pressed = False 24 | self._note_mode_active = False 25 | self._invert_assignment = False 26 | self._select_buttons = select_buttons 27 | self._master_button = master_button 28 | self._slider_modes = slider_modes 29 | self._matrix_modes = matrix_modes #added new 30 | self._arm_buttons = arm_buttons 31 | #self._transport = transport 32 | self._session = session 33 | self._zooming = zooming 34 | self._matrix = matrix 35 | self._mixer = mixer 36 | #self._master_button.add_value_listener(self._master_value) 37 | self._parent = parent #use this to call methods of parent class (APC40plus21) 38 | 39 | 40 | def disconnect(self): 41 | ModeSelectorComponent.disconnect(self) 42 | #self._master_button.remove_value_listener(self._master_value) 43 | self._select_buttons = None 44 | self._master_button = None 45 | self._slider_modes = None 46 | self._matrix_modes = None #added 47 | self._arm_buttons = None 48 | #self._transport = None 49 | self._session = None 50 | self._zooming = None 51 | self._matrix = None 52 | self._mixer = None 53 | self._parent = None #added 54 | return None 55 | 56 | def set_mode_toggle(self, button): 57 | ModeSelectorComponent.set_mode_toggle(self, button) #called from APC40_22: self._shift_modes.set_mode_toggle(self._shift_button) 58 | self.set_mode(0) 59 | 60 | def invert_assignment(self): 61 | self._invert_assignment = True 62 | self._recalculate_mode() 63 | 64 | def number_of_modes(self): 65 | return 2 66 | 67 | def update(self): 68 | if self.is_enabled(): 69 | if self._mode_index == int(self._invert_assignment): 70 | self._slider_modes.set_mode_buttons(None) 71 | self._matrix_modes.set_mode_buttons(None) 72 | for index in range(len(self._arm_buttons)): #was: for index in range(len(self._select_buttons)): 73 | self._mixer.channel_strip(index).set_arm_button(self._arm_buttons[index]) 74 | self._mixer.channel_strip(index).set_select_button(self._select_buttons[index]) 75 | else: 76 | for index in range(len(self._arm_buttons)): #was: for index in range(len(self._select_buttons)): 77 | self._mixer.channel_strip(index).set_arm_button(None) 78 | self._mixer.channel_strip(index).set_select_button(None) 79 | self._slider_modes.set_mode_buttons(self._arm_buttons) 80 | self._matrix_modes.set_mode_buttons(self._select_buttons) 81 | return None 82 | 83 | def _partial_refresh(self, value): 84 | #for control in self._parent.controls: 85 | #control.clear_send_cache() 86 | for component in self._parent.components: 87 | if isinstance(component, PedaledSessionComponent) or isinstance(component, SessionZoomingComponent): 88 | component.update() 89 | 90 | 91 | def _toggle_value(self, value): #"toggle" is shift button 92 | if not self._mode_toggle != None: 93 | raise AssertionError 94 | if not value in range(128): 95 | raise AssertionError 96 | self._toggle_pressed = value > 0 97 | self._recalculate_mode() 98 | if value < 1 and self._matrix_modes._last_mode > 1: #refresh on Shift button release, and if previous mode was Note Mode 99 | self._parent.schedule_message(2, self._partial_refresh, value) 100 | return None 101 | 102 | 103 | def _recalculate_mode(self): #called if toggle (i.e. shift) is pressed 104 | self.set_mode((int(self._toggle_pressed) + int(self._invert_assignment)) % self.number_of_modes()) 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /APCAdvanced/APCSessionComponent.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # -*- coding: utf-8 -*- 3 | 4 | import Live 5 | from _Framework.SessionComponent import SessionComponent 6 | from _Framework.CompoundComponent import CompoundComponent #added 7 | #from SpecialClipSlotComponent import SpecialClipSlotComponent #added 8 | from _Framework.SceneComponent import SceneComponent #added 9 | from _Framework.ClipSlotComponent import ClipSlotComponent #added 10 | 11 | class APCSessionComponent(SessionComponent): 12 | " Special SessionComponent for the APC controllers' combination mode " 13 | __module__ = __name__ 14 | 15 | def __init__(self, num_tracks, num_scenes): 16 | SessionComponent.__init__(self, num_tracks, num_scenes) 17 | #def __init__(self, num_tracks, num_scenes): 18 | #if not SessionComponent._session_highlighting_callback != None: 19 | #raise AssertionError 20 | #if not isinstance(num_tracks, int): 21 | #isinstance(num_tracks, int) 22 | #raise AssertionError 23 | #isinstance(num_tracks, int) 24 | #if not num_tracks >= 0: 25 | #raise AssertionError 26 | #if not isinstance(num_scenes, int): 27 | #isinstance(num_scenes, int) 28 | #raise AssertionError 29 | #isinstance(num_scenes, int) 30 | #if not num_scenes >= 0: 31 | #raise AssertionError 32 | #CompoundComponent.__init__(self) 33 | #self._track_offset = -1 34 | #self._scene_offset = -1 35 | #self._num_tracks = num_tracks 36 | #self._bank_up_button = None 37 | #self._bank_down_button = None 38 | #self._bank_right_button = None 39 | #self._bank_left_button = None 40 | #self._stop_all_button = None 41 | #self._next_scene_button = None 42 | #self._prev_scene_button = None 43 | #self._stop_track_clip_buttons = None 44 | #self._scroll_up_ticks_delay = -1 45 | #self._scroll_down_ticks_delay = -1 46 | #self._scroll_right_ticks_delay = -1 47 | #self._scroll_left_ticks_delay = -1 48 | #self._stop_track_clip_value = 127 49 | #self._offset_callback = None 50 | #self._highlighting_callback = SessionComponent._session_highlighting_callback 51 | #if num_tracks > 0: 52 | #pass 53 | #self._show_highlight = num_tracks > 0 54 | #self._mixer = None 55 | #self._selected_scene = SpecialSceneComponent(self._num_tracks, self.tracks_to_use) 56 | #self.on_selected_scene_changed() 57 | #self.register_components(self._selected_scene) 58 | #self._scenes = [] 59 | #self._tracks_and_listeners = [] 60 | #for index in range(num_scenes): 61 | #self._scenes.append(self._create_scene(self._num_tracks)) 62 | #self.register_components(self._scenes[index]) 63 | #self.set_offsets(0, 0) 64 | #self._register_timer_callback(self._on_timer) 65 | #return None 66 | 67 | #def _create_scene(self, num_tracks): 68 | #return SpecialSceneComponent(self.tracks_to_use) 69 | 70 | def link_with_track_offset(self, track_offset): 71 | assert (track_offset >= 0) 72 | if self._is_linked(): 73 | self._unlink() 74 | self._change_offsets(track_offset, 0) 75 | self._link() 76 | 77 | 78 | # local variables: 79 | # tab-width: 4 80 | 81 | #class SpecialSceneComponent(SceneComponent): 82 | 83 | #def __init__(self, num_slots, tracks_to_use_callback): 84 | #SceneComponent.__init__(self, num_slots, tracks_to_use_callback) 85 | 86 | #def _create_clip_slot(self): 87 | #return ClipSlotComponent() 88 | 89 | 90 | #class SpecialClipSlotComponent(ClipSlotComponent): 91 | 92 | #def __init__(self): 93 | #ClipSlotComponent.__init__(self) 94 | 95 | 96 | #def update(self): 97 | #self._has_fired_slot = False 98 | #if (self.is_enabled() and (self._launch_button != None)): 99 | #self._launch_button.turn_off() 100 | #value_to_send = -1 101 | #if (self._clip_slot != None): 102 | #if self.has_clip(): 103 | #value_to_send = self._stopped_value 104 | #if self._clip_slot.clip.is_triggered: 105 | #if self._clip_slot.clip.will_record_on_start: 106 | #value_to_send = self._triggered_to_record_value 107 | #else: 108 | #value_to_send = self._triggered_to_play_value 109 | #elif self._clip_slot.clip.is_playing: 110 | #if self._clip_slot.clip.is_recording: 111 | #value_to_send = self._recording_value 112 | #else: 113 | #value_to_send = self._started_value 114 | #elif self._clip_slot.is_triggered: 115 | #if self._clip_slot.will_record_on_start: 116 | #value_to_send = self._triggered_to_record_value 117 | #else: 118 | #value_to_send = self._triggered_to_play_value 119 | #if (value_to_send in range(128)): 120 | #self._launch_button.send_value(value_to_send, True) -------------------------------------------------------------------------------- /APCAdvanced/EncoderDeviceComponent.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # http://remotescripts.blogspot.com 3 | 4 | import Live 5 | from _Framework.ControlSurfaceComponent import ControlSurfaceComponent 6 | from _Framework.ButtonElement import ButtonElement 7 | from _Framework.EncoderElement import EncoderElement 8 | from _Framework.MixerComponent import MixerComponent 9 | from _Framework.DeviceComponent import DeviceComponent 10 | 11 | class EncoderDeviceComponent(ControlSurfaceComponent): 12 | __module__ = __name__ 13 | __doc__ = " Class representing encoder Device component " 14 | 15 | def __init__(self, mixer, device, parent): 16 | ControlSurfaceComponent.__init__(self) 17 | assert isinstance(mixer, MixerComponent) 18 | self._param_controls = None 19 | self._mixer = mixer 20 | self._buttons = [] 21 | self._lock_button = None 22 | self._last_mode = 0 23 | self._is_locked = False 24 | self._ignore_buttons = False 25 | self._track = None 26 | self._strip = None 27 | self._parent = parent 28 | self._device = device 29 | self._alt_device = DeviceComponent() 30 | self._alt_device.name = 'Alt_Device_Component' 31 | self.song().add_appointed_device_listener(self._on_device_changed) 32 | 33 | def disconnect(self): 34 | self.song().remove_appointed_device_listener(self._on_device_changed) 35 | self._param_controls = None 36 | self._mixer = None 37 | self._buttons = None 38 | self._lock_button = None 39 | self._track = None 40 | self._strip = None 41 | self._parent = None 42 | self._device = None 43 | self._alt_device = None 44 | 45 | def update(self): 46 | pass 47 | #self._show_msg_callback("EncoderDeviceComponent update called") 48 | 49 | 50 | def set_controls_and_buttons(self, controls, buttons): 51 | assert ((controls == None) or (isinstance(controls, tuple) and (len(controls) == 8))) 52 | self._param_controls = controls 53 | assert ((buttons == None) or (isinstance(buttons, tuple)) or (len(buttons) == 4)) 54 | self._buttons = buttons 55 | self.set_lock_button(self._buttons[0]) 56 | 57 | if self._is_locked == True: 58 | self._alt_device.set_parameter_controls(self._param_controls) 59 | self._alt_device.set_bank_nav_buttons(self._buttons[2], self._buttons[3]) 60 | self._alt_device.set_on_off_button(self._buttons[1]) 61 | else: 62 | self.on_selected_track_changed() 63 | 64 | 65 | def _on_device_changed(self): 66 | if self.is_enabled(): 67 | if self._is_locked != True: 68 | selected_device= self.song().appointed_device 69 | self._alt_device.set_device(selected_device) 70 | self._setup_controls_and_buttons() 71 | 72 | 73 | def on_selected_track_changed(self): 74 | if self.is_enabled(): 75 | if self._is_locked != True: 76 | track = self.song().view.selected_track 77 | selected_device = track.view.selected_device 78 | self._alt_device.set_device(selected_device) 79 | self._setup_controls_and_buttons() 80 | 81 | 82 | def _setup_controls_and_buttons(self): 83 | if self._buttons != None and self._param_controls != None: 84 | if self._alt_device != None: 85 | self._alt_device.set_parameter_controls(self._param_controls) 86 | self._alt_device.set_bank_nav_buttons(self._buttons[2], self._buttons[3]) 87 | self._alt_device.set_on_off_button(self._buttons[1]) 88 | self._alt_device._on_on_off_changed() 89 | 90 | #self._rebuild_callback() 91 | 92 | 93 | def on_enabled_changed(self): 94 | self.update() 95 | 96 | 97 | def set_lock_button(self, button): 98 | assert ((button == None) or isinstance(button, ButtonElement)) 99 | if (self._lock_button != None): 100 | self._lock_button.remove_value_listener(self._lock_value) 101 | self._lock_button = None 102 | self._lock_button = button 103 | if (self._lock_button != None): 104 | self._lock_button.add_value_listener(self._lock_value) 105 | if self._is_locked: 106 | self._lock_button.turn_on() 107 | else: 108 | self._lock_button.turn_off() 109 | 110 | 111 | def _lock_value(self, value): 112 | assert (self._lock_button != None) 113 | assert (value != None) 114 | assert isinstance(value, int) 115 | if ((not self._lock_button.is_momentary()) or (value is not 0)): 116 | if self._ignore_buttons == False: 117 | if self._is_locked: 118 | self._is_locked = False 119 | self._lock_button.turn_off() 120 | self.on_selected_track_changed() 121 | else: 122 | self._is_locked = True 123 | self._lock_button.turn_on() 124 | 125 | 126 | 127 | # local variables: 128 | # tab-width: 4 129 | -------------------------------------------------------------------------------- /APCAdvanced/SpecialMixerComponent.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # -*- coding: utf-8 -*- 3 | 4 | from _Framework.MixerComponent import MixerComponent 5 | from SpecialChannelStripComponent import SpecialChannelStripComponent 6 | from _Framework.ButtonElement import ButtonElement #added 7 | from _Framework.EncoderElement import EncoderElement #added 8 | 9 | class SpecialMixerComponent(MixerComponent): 10 | ' Special mixer class that uses return tracks alongside midi and audio tracks, and only maps prehear when not shifted ' 11 | __module__ = __name__ 12 | 13 | def __init__(self, parent, num_tracks): 14 | self._is_locked = False #added 15 | self._parent = parent #added 16 | MixerComponent.__init__(self, num_tracks) 17 | self._shift_button = None #added 18 | self._pedal = None 19 | self._shift_pressed = False #added 20 | self._pedal_pressed = False #added 21 | 22 | 23 | 24 | def disconnect(self): #added 25 | MixerComponent.disconnect(self) 26 | if (self._shift_button != None): 27 | self._shift_button.remove_value_listener(self._shift_value) 28 | self._shift_button = None 29 | if (self._pedal != None): 30 | self._pedal.remove_value_listener(self._pedal_value) 31 | self._pedal = None 32 | 33 | 34 | def set_shift_button(self, button): #added 35 | assert ((button == None) or (isinstance(button, ButtonElement) and button.is_momentary())) 36 | if (self._shift_button != button): 37 | if (self._shift_button != None): 38 | self._shift_button.remove_value_listener(self._shift_value) 39 | self._shift_button = button 40 | if (self._shift_button != None): 41 | self._shift_button.add_value_listener(self._shift_value) 42 | self.update() 43 | 44 | def set_pedal(self, pedal): 45 | assert ((pedal == None) or (isinstance(pedal, ButtonElement) and pedal.is_momentary())) 46 | if (self._pedal != pedal): 47 | if (self._pedal != None): 48 | self._pedal.remove_value_listener(self._pedal_value) 49 | self._pedal = pedal 50 | if (self._pedal != None): 51 | self._pedal.add_value_listener(self._pedal_value) 52 | self.update() 53 | 54 | 55 | def _shift_value(self, value): #added 56 | assert (self._shift_button != None) 57 | assert (value in range(128)) 58 | self._shift_pressed = (value != 0) 59 | self.update() 60 | 61 | def _pedal_value(self, value): #added 62 | assert (self._pedal != None) 63 | assert (value in range(128)) 64 | self._pedal_pressed = (value == 0) 65 | self.update() 66 | 67 | 68 | def on_selected_track_changed(self): #added override 69 | selected_track = self.song().view.selected_track 70 | if (self._selected_strip != None): 71 | if self._is_locked == False: #added 72 | self._selected_strip.set_track(selected_track) 73 | if self.is_enabled(): 74 | if (self._next_track_button != None): 75 | if (selected_track != self.song().master_track): 76 | 77 | self._next_track_button.turn_on() 78 | else: 79 | self._next_track_button.turn_off() 80 | if (self._prev_track_button != None): 81 | if (selected_track != self.song().tracks[0]): 82 | self._prev_track_button.turn_on() 83 | else: 84 | self._prev_track_button.turn_off() 85 | 86 | 87 | 88 | def update(self): #added override 89 | if self._allow_updates: 90 | master_track = self.song().master_track 91 | if self.is_enabled(): 92 | if (self._prehear_volume_control != None): 93 | #if self._shift_pressed: #added 94 | if not self._shift_pressed and not self._pedal_pressed: #added 95 | self._prehear_volume_control.connect_to(master_track.mixer_device.cue_volume) 96 | else: 97 | self._prehear_volume_control.release_parameter() #added 98 | if (self._crossfader_control != None): 99 | self._crossfader_control.connect_to(master_track.mixer_device.crossfader) 100 | else: 101 | if (self._prehear_volume_control != None): 102 | self._prehear_volume_control.release_parameter() 103 | if (self._crossfader_control != None): 104 | self._crossfader_control.release_parameter() 105 | if (self._bank_up_button != None): 106 | self._bank_up_button.turn_off() 107 | if (self._bank_down_button != None): 108 | self._bank_down_button.turn_off() 109 | if (self._next_track_button != None): 110 | self._next_track_button.turn_off() 111 | if (self._prev_track_button != None): 112 | self._prev_track_button.turn_off() 113 | #self._rebuild_callback() 114 | else: 115 | self._update_requests += 1 116 | 117 | 118 | def tracks_to_use(self): 119 | return (self.song().visible_tracks + self.song().return_tracks) 120 | 121 | 122 | 123 | def _create_strip(self): 124 | return SpecialChannelStripComponent() 125 | 126 | 127 | def set_track_offset(self, new_offset): #added override 128 | MixerComponent.set_track_offset(self, new_offset) 129 | if self._parent._slider_modes != None: 130 | self._parent._slider_modes.update() 131 | if self._parent._encoder_modes != None: 132 | self._parent._encoder_modes.update() 133 | 134 | 135 | # local variables: 136 | # tab-width: 4 137 | -------------------------------------------------------------------------------- /APCAdvanced/ReadMe.txt: -------------------------------------------------------------------------------- 1 | 2 | http://remotescripts.blogspot.com 3 | 4 | ------------------- 5 | APS40_22 revision 3 6 | ------------------- 7 | 8 | This set of Python scripts adds APC20/Launchpad-style User Mode/Note Mode functionality to the APC40, with additional shifted control mappings. Compatible with Live 8.1 & higher, on PC or mac. 9 | 10 | To use, drop the APC40_22 folder into Ableton's MIDI Remote Scripts directory, and select APC40_22 from the MIDI Preferences dialog. Switch Track Input to "On" for Note Mode, and switch Remote to "On" for User Modes. Consider switching Takeover Mode to "Value Scaling" when using sliders or encoders in User mode. 11 | 12 | 13 | Modifications and customizations include the following (refer also to APC40_22.jpg for custom control map example): 14 | 15 | ------ 16 | Matrix 17 | ------ 18 | 19 | * Shift + Track Select buttons = Matrix Modes selection. There are 8 Matrix Modes available. The default setup of the Matrix Modes is as follows: 20 | 21 | * Shift + Track Select 1 = Clip Launch (default) 22 | * Shift + Track Select 2 = Session Overview 23 | * Shift + Track Select 3 = User 1 -> 8 x 6 Note Mode grid on Channel 10, Drum Rack mapping (4 notes wide) 24 | * Shift + Track Select 4 = User 2 -> 8 x 6 Note Mode grid on Channel 11, notes laid out left to right (8 notes wide) 25 | * Shift + Track Select 5 = User 3 -> 8 x 6 Note Mode grid on Channel 12, same as previous, but with sharps turned off 26 | * Shift + Track Select 6 = User 4 -> 8 x 5 Note Mode grid on Channel 13, double grid 4 notes wide; does not use Clip Stop buttons 27 | * Shift + Track Select 7 = User 5 -> 8 x 6 Note Mode grid on Channel 14, notes ascend in vertical columns 28 | * Shift + Track Select 8 = User 6 -> 8 x 6 user Mode grid on Channel 15, no MIDI notes sent 29 | 30 | For each of the User Matrix Modes, the following options are available for customization (by editing the Matrix_Maps.py file): 31 | * LED colour assignment for each button 32 | * MIDI note assignment for each button 33 | * Channel assignment for entire grid 34 | * Stop Track buttons included as part of grid 35 | * Note Mode or User Mode setting (Note Mode sends MIDI notes, except where buttons are mapped; User Mode does not send MIDI notes). 36 | 37 | The Drum Rack selection box is automapped to MIDI notes 36 through 72 on Channel 10 (User Mode 1 by default). This is the lower left 4 x 4 grid of User Mode 1, also coloured solid green by default. 38 | 39 | ------- 40 | Sliders 41 | ------- 42 | 43 | * Shift + Arm buttons = Slider Reassignment. 8 Slider Modes are available (left to right): 44 | 45 | * Shift + Arm button 1 = Volume (default) 46 | * Shift + Arm button 2 = Pan 47 | * Shift + Arm button 3 = Send A 48 | * Shift + Arm button 4 = Send B 49 | * Shift + Arm button 5 = Send C 50 | * Shift + Arm button 6 = User 1 51 | * Shift + Arm button 7 = User 2 52 | * Shift + Arm button 8 = User 3 53 | 54 | -------------- 55 | Track Encoders 56 | -------------- 57 | * Shift + Track Control buttons = Encoder Modes selection. There are 4 Encoder Modes available: 58 | 59 | * Shift + Pan = Default (Pan / Send A / Send B / Send C) 60 | * Shift + Send A = Alternate Device Mode. Encoders mirror the currently selected Device. In this mode, the track control buttons operate as follows (left to right): 61 | * Device Lock/Load - selecting locks the current Device, so that it won't change when switching tracks; deselecting unlocks then loads the new current Device 62 | * Device On/Off 63 | * Device Bank Left - select to scroll Left (lights up when there are additional banks available for scroll left) 64 | * Device Bank Right - select to scroll right (lights up when there are addtional banks available for scroll right) 65 | * Shift + Send B = EQ/Filter Mode. In this mode, Encoders 1 & 5 will map to current track's Device Filter & Cutoff (if track has device with Filter/Cutoff controls); Encoders 2, 3 & 4 will map to track's Send A, Send B & Send C; Encoders 6, 7 & 8 will map to track's first three EQ controls (Low, Mid & High, if track has EQ3). In this mode, the track control buttons operate as follows (left to right): 66 | *EQ Lock/Load - selecting locks to the current track's EQ/Filter/Pans; deselecting unlocks then loads the new current track's EQ/Filter/Pans 67 | *EQ Low Cut (lights up when cut is active) 68 | *EQ Mid Cut (lights up when cut is active) 69 | *EQ High Cut (lights up when cut is active) 70 | * Shift + Send C = User Mode. Encoders and track buttons can be user-mapped. 71 | 72 | * In Default Mode, pressing and holding the Pan button switches between Pan control and Volume control for the 8 encoders. 73 | 74 | ----- 75 | Other 76 | ----- 77 | 78 | * Shift + Tap Tempo = Device Lock. 79 | * Shift + Nudge- = Undo. 80 | * Shift + Nudge+ = Redo. 81 | * Shift + Cue Volume = Tempo Control. 82 | 83 | ---------------- 84 | Revision History 85 | ---------------- 86 | 87 | 2010-08-10 88 | * APC40_22 revision 3 - Added Press & Hold feature for Pan button (to switch between Pan and Volume control modes); fixed bug where manual MIDI-mapping of Pan encoders would prevent Send modes from operating; fixed bug where either sliders or encoders (but not both) would follow session highlight (aka "red box") when mapped to same control. 89 | 90 | 2010-07-15 91 | * APC40_22 revision 2 - Fixed bug: Encoder LEDs now update without lag, when device parameters are being controlled by another element (such as sliders). 92 | 93 | 2010-06-29 94 | * APC40_22 revision 1 - Fixed bug: Track Encoders in EQ/Filter Mode (Shift + Send B) would sometimes would not fully release parameters. 95 | 96 | 2010-06-28 97 | * Initial APC40_22 release, built on APC40_21 script rev.0 98 | 99 | Visit http://remotescripts.blogspot.com for the latest updates and more information 100 | 101 | 102 | ---------- 103 | DISCLAIMER 104 | ---------- 105 | 106 | THESE FILES ARE PROVIDED AS-IS, WITHOUT ANY WARRANTY, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO FITNESS FOR ANY PARTICULAR PURPOSE. -------------------------------------------------------------------------------- /APCAdvanced/EncoderUserModesComponent.py: -------------------------------------------------------------------------------- 1 | 2 | import Live 3 | from _Framework.ModeSelectorComponent import ModeSelectorComponent 4 | from _Framework.ButtonElement import ButtonElement 5 | from _Framework.DeviceComponent import DeviceComponent 6 | 7 | class EncoderUserModesComponent(ModeSelectorComponent): 8 | ' SelectorComponent that assigns encoders to different user functions ' 9 | __module__ = __name__ 10 | 11 | def __init__(self, parent, encoder_modes, param_controls, bank_buttons, mixer, device, encoder_device_modes, encoder_eq_modes): #, mixer, sliders): 12 | assert (len(bank_buttons) == 4) 13 | ModeSelectorComponent.__init__(self) 14 | self._parent = parent 15 | self._encoder_modes = encoder_modes 16 | self._param_controls = param_controls 17 | self._bank_buttons = bank_buttons 18 | self._mixer = mixer 19 | self._device = device 20 | self._encoder_device_modes = encoder_device_modes 21 | self._encoder_eq_modes = encoder_eq_modes 22 | self._mode_index = 0 23 | self._modes_buttons = [] 24 | self._user_buttons = [] 25 | self._last_mode = 0 26 | 27 | 28 | def disconnect(self): 29 | ModeSelectorComponent.disconnect(self) 30 | self._parent = None 31 | self._encoder_modes = None 32 | self._param_controls = None 33 | self._bank_buttons = None 34 | self._mixer = None 35 | self._device = None 36 | self._encoder_device_modes = None 37 | self._encoder_eq_modes = None 38 | self._modes_buttons = None 39 | self._user_buttons = None 40 | 41 | def on_enabled_changed(self): 42 | pass 43 | 44 | def set_mode(self, mode): 45 | assert isinstance(mode, int) 46 | assert (mode in range(self.number_of_modes())) 47 | if (self._mode_index != mode): 48 | self._last_mode = self._mode_index # keep track of previous mode, to allow conditional actions 49 | self._mode_index = mode 50 | self._set_modes() 51 | 52 | 53 | def set_mode_buttons(self, buttons): 54 | assert isinstance(buttons, (tuple, 55 | type(None))) 56 | for button in self._modes_buttons: 57 | button.remove_value_listener(self._mode_value) 58 | 59 | self._modes_buttons = [] 60 | if (buttons != None): 61 | for button in buttons: 62 | assert isinstance(button, ButtonElement) 63 | identify_sender = True 64 | button.add_value_listener(self._mode_value, identify_sender) 65 | self._modes_buttons.append(button) 66 | assert (self._mode_index in range(self.number_of_modes())) 67 | 68 | 69 | def number_of_modes(self): 70 | return 4 71 | 72 | 73 | def update(self): 74 | pass 75 | 76 | 77 | def _mode_value(self, value, sender): 78 | assert (len(self._modes_buttons) > 0) 79 | assert isinstance(value, int) 80 | assert isinstance(sender, ButtonElement) 81 | assert (self._modes_buttons.count(sender) == 1) 82 | if ((value is not 0) or (not sender.is_momentary())): 83 | self.set_mode(self._modes_buttons.index(sender)) 84 | 85 | 86 | def _set_modes(self): 87 | if self.is_enabled(): 88 | assert (self._mode_index in range(self.number_of_modes())) 89 | for index in range(len(self._modes_buttons)): 90 | if (index <= self._mode_index): 91 | self._modes_buttons[index].turn_on() 92 | else: 93 | self._modes_buttons[index].turn_off() 94 | for button in self._modes_buttons: 95 | button.release_parameter() 96 | button.use_default_message() 97 | for control in self._param_controls: 98 | control.release_parameter() 99 | control.use_default_message() 100 | #control.set_needs_takeover(False) 101 | self._encoder_modes.set_enabled(False) 102 | 103 | self._encoder_device_modes.set_lock_button(None) 104 | self._encoder_device_modes._alt_device.set_bank_nav_buttons(None, None) 105 | self._encoder_device_modes._alt_device.set_on_off_button(None) 106 | if self._encoder_device_modes._alt_device._parameter_controls != None: 107 | for control in self._encoder_device_modes._alt_device._parameter_controls: 108 | control.release_parameter() 109 | self._encoder_device_modes.set_enabled(False) 110 | 111 | self._encoder_eq_modes.set_enabled(False) 112 | self._encoder_eq_modes.set_lock_button(None) 113 | if self._encoder_eq_modes._track_eq != None: 114 | self._encoder_eq_modes._track_eq.set_cut_buttons(None) 115 | if self._encoder_eq_modes._track_eq._gain_controls != None: 116 | for control in self._encoder_eq_modes._track_eq._gain_controls: 117 | control.release_parameter() 118 | if self._encoder_eq_modes._strip != None: 119 | self._encoder_eq_modes._strip.set_send_controls(None) 120 | 121 | self._user_buttons = [] 122 | 123 | if (self._mode_index == 0): 124 | self._encoder_modes.set_enabled(True) 125 | 126 | elif (self._mode_index == 1): 127 | self._encoder_device_modes.set_enabled(True) 128 | self._encoder_device_modes.set_controls_and_buttons(self._param_controls, self._modes_buttons) 129 | 130 | elif (self._mode_index == 2): 131 | self._encoder_eq_modes.set_enabled(True) 132 | self._encoder_eq_modes.set_controls_and_buttons(self._param_controls, self._modes_buttons) 133 | 134 | 135 | elif (self._mode_index == 3): 136 | self._encoder_eq_modes._ignore_buttons = True 137 | if self._encoder_eq_modes._track_eq != None: 138 | self._encoder_eq_modes._track_eq._ignore_cut_buttons = True 139 | self._encoder_device_modes._ignore_buttons = True 140 | for button in self._modes_buttons: 141 | self._user_buttons.append(button) 142 | for control in self._param_controls: 143 | control.set_identifier((control.message_identifier() - 9)) 144 | control._ring_mode_button.send_value(0) 145 | else: 146 | pass 147 | #self._rebuild_callback() 148 | 149 | 150 | 151 | # local variables: 152 | # tab-width: 4 153 | -------------------------------------------------------------------------------- /APCAdvanced/EncoderMixerModeSelectorComponent.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # -*- coding: utf-8 -*- 3 | 4 | from _Framework.ModeSelectorComponent import ModeSelectorComponent 5 | from _Framework.ButtonElement import ButtonElement 6 | from _Framework.MixerComponent import MixerComponent 7 | PAN_TO_VOL_DELAY = 5 #added delay value for _on_timer Pan/Vol Mode selection 8 | 9 | class EncoderMixerModeSelectorComponent(ModeSelectorComponent): 10 | ' Class that reassigns encoders on the AxiomPro to different mixer functions ' 11 | __module__ = __name__ 12 | 13 | def __init__(self, mixer): 14 | assert isinstance(mixer, MixerComponent) 15 | ModeSelectorComponent.__init__(self) 16 | self._controls = None 17 | self._mixer = mixer 18 | self.set_mode(0) #moved here 19 | self._pan_to_vol_ticks_delay = -1 #added 20 | self._mode_is_pan = True #new 21 | self._register_timer_callback(self._on_timer) #added 22 | 23 | 24 | def disconnect(self): 25 | for button in self._modes_buttons: 26 | button.remove_value_listener(self._mode_value) 27 | 28 | self._controls = None 29 | self._mixer = None 30 | self._unregister_timer_callback(self._on_timer) #added 31 | ModeSelectorComponent.disconnect(self) 32 | 33 | 34 | def set_modes_buttons(self, buttons): 35 | assert ((buttons == None) or (isinstance(buttons, tuple) or (len(buttons) == self.number_of_modes()))) 36 | identify_sender = True 37 | for button in self._modes_buttons: 38 | button.remove_value_listener(self._mode_value) 39 | 40 | self._modes_buttons = [] 41 | if (buttons != None): 42 | for button in buttons: 43 | assert isinstance(button, ButtonElement) 44 | self._modes_buttons.append(button) 45 | button.add_value_listener(self._mode_value, identify_sender) 46 | self.update() 47 | 48 | 49 | def set_controls(self, controls): 50 | assert ((controls == None) or (isinstance(controls, tuple) and (len(controls) == 8))) 51 | self._controls = controls 52 | self.update() 53 | 54 | 55 | def number_of_modes(self): 56 | return 4 57 | 58 | 59 | def _mode_value(self, value, sender): 60 | if self.is_enabled(): #added to ignore mode buttons when not enabled 61 | assert (len(self._modes_buttons) > 0) 62 | assert isinstance(value, int) 63 | assert isinstance(sender, ButtonElement) 64 | assert (self._modes_buttons.count(sender) == 1) 65 | if ((value is not 0) or (not sender.is_momentary())): 66 | self.set_mode(self._modes_buttons.index(sender)) 67 | if self._modes_buttons.index(sender) == 0 and sender.is_momentary() and (value != 0): #added check for Pan button 68 | self._pan_to_vol_ticks_delay = PAN_TO_VOL_DELAY 69 | else: 70 | self._pan_to_vol_ticks_delay = -1 71 | 72 | def update(self): 73 | assert (self._modes_buttons != None) 74 | if self.is_enabled(): 75 | if (self._modes_buttons != None): 76 | for button in self._modes_buttons: 77 | if (self._modes_buttons.index(button) == self._mode_index): 78 | button.turn_on() 79 | else: 80 | button.turn_off() 81 | 82 | if (self._controls != None): 83 | for index in range(len(self._controls)): 84 | if (self._mode_index == 0): 85 | if self._mode_is_pan == True: #added 86 | self._mixer.channel_strip(index).set_volume_control(None) 87 | self._mixer.channel_strip(index).set_pan_control(self._controls[index]) 88 | else: 89 | self._mixer.channel_strip(index).set_pan_control(None) 90 | self._mixer.channel_strip(index).set_volume_control(self._controls[index]) 91 | self._mixer.channel_strip(index).set_send_controls((None, None, None)) 92 | elif (self._mode_index == 1): 93 | self._mixer.channel_strip(index).set_volume_control(None) #added 94 | self._mixer.channel_strip(index).set_pan_control(None) 95 | self._mixer.channel_strip(index).set_send_controls((self._controls[index], 96 | None, 97 | None)) 98 | elif (self._mode_index == 2): 99 | self._mixer.channel_strip(index).set_volume_control(None) #added 100 | self._mixer.channel_strip(index).set_pan_control(None) 101 | self._mixer.channel_strip(index).set_send_controls((None, 102 | self._controls[index], 103 | None)) 104 | elif (self._mode_index == 3): 105 | self._mixer.channel_strip(index).set_volume_control(None) #added 106 | self._mixer.channel_strip(index).set_pan_control(None) 107 | self._mixer.channel_strip(index).set_send_controls((None, 108 | None, 109 | self._controls[index])) 110 | else: 111 | pass 112 | #print 'Invalid mode index' 113 | #assert False 114 | else: 115 | for index in range(8): 116 | self._mixer.channel_strip(index).set_pan_control(None) 117 | self._mixer.channel_strip(index).set_send_controls((None, None, None)) 118 | #self._rebuild_callback() 119 | 120 | 121 | def _on_timer(self): #added to allow press & hold for Pan/Vol Mode selection 122 | if (self.is_enabled()): 123 | if (self._pan_to_vol_ticks_delay > -1): 124 | if (self._pan_to_vol_ticks_delay == 0): 125 | self._mode_is_pan = not self._mode_is_pan 126 | if self._mode_is_pan == True: 127 | self._show_msg_callback("Set to Pan Mode") 128 | else: 129 | self._show_msg_callback("Set to Volume Mode") 130 | self.update() 131 | self._pan_to_vol_ticks_delay -= 1 132 | 133 | # local variables: 134 | # tab-width: 4 135 | -------------------------------------------------------------------------------- /APCAdvanced/EncoderEQComponent.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # http://remotescripts.blogspot.com 3 | 4 | import Live 5 | from _Framework.ControlSurfaceComponent import ControlSurfaceComponent 6 | from _Framework.ButtonElement import ButtonElement 7 | from _Framework.EncoderElement import EncoderElement 8 | from _Framework.MixerComponent import MixerComponent 9 | from _Framework.TrackEQComponent import TrackEQComponent 10 | from _Framework.TrackFilterComponent import TrackFilterComponent 11 | 12 | from _Generic.Devices import * 13 | EQ_DEVICES = {'Eq8': {'Gains': [ ('%i Gain A' % (index + 1)) for index in range(8) ]}, 14 | 'FilterEQ3': {'Gains': ['GainLo', 15 | 'GainMid', 16 | 'GainHi'], 17 | 'Cuts': ['LowOn', 18 | 'MidOn', 19 | 'HighOn']}} 20 | class SpecialTrackEQComponent(TrackEQComponent): #added to override _cut_value 21 | 22 | def __init__(self): 23 | TrackEQComponent.__init__(self) 24 | self._ignore_cut_buttons = False 25 | 26 | def _cut_value(self, value, sender): 27 | assert (sender in self._cut_buttons) 28 | assert (value in range(128)) 29 | if self._ignore_cut_buttons == False: #added 30 | if (self.is_enabled() and (self._device != None)): 31 | if ((not sender.is_momentary()) or (value is not 0)): 32 | device_dict = EQ_DEVICES[self._device.class_name] 33 | if ('Cuts' in device_dict.keys()): 34 | cut_names = device_dict['Cuts'] 35 | index = list(self._cut_buttons).index(sender) 36 | if (index in range(len(cut_names))): 37 | parameter = get_parameter_by_name(self._device, cut_names[index]) 38 | if (parameter != None): 39 | parameter.value = float((int((parameter.value + 1)) % 2)) 40 | 41 | 42 | class EncoderEQComponent(ControlSurfaceComponent): 43 | __module__ = __name__ 44 | __doc__ = " Class representing encoder EQ component " 45 | 46 | def __init__(self, mixer, parent): 47 | ControlSurfaceComponent.__init__(self) 48 | assert isinstance(mixer, MixerComponent) 49 | self._param_controls = None 50 | self._mixer = mixer 51 | self._buttons = [] 52 | self._param_controls = None 53 | self._lock_button = None 54 | self._last_mode = 0 55 | self._is_locked = False 56 | self._ignore_buttons = False 57 | self._track = None 58 | self._strip = None 59 | self._parent = parent 60 | self._track_eq = SpecialTrackEQComponent() 61 | self._track_filter = TrackFilterComponent() 62 | 63 | def disconnect(self): 64 | self._param_controls = None 65 | self._mixer = None 66 | self._buttons = None 67 | self._param_controls = None 68 | self._lock_button = None 69 | self._track = None 70 | self._strip = None 71 | self._parent = None 72 | self._track_eq = None 73 | self._track_filter = None 74 | 75 | def update(self): 76 | pass 77 | 78 | 79 | def set_controls_and_buttons(self, controls, buttons): 80 | assert ((controls == None) or (isinstance(controls, tuple) and (len(controls) == 8))) 81 | self._param_controls = controls 82 | assert ((buttons == None) or (isinstance(buttons, tuple)) or (len(buttons) == 4)) 83 | self._buttons = buttons 84 | self.set_lock_button(self._buttons[0]) 85 | self._update_controls_and_buttons() 86 | 87 | 88 | def _update_controls_and_buttons(self): 89 | #if self.is_enabled(): 90 | if self._param_controls != None and self._buttons != None: 91 | if self._is_locked != True: 92 | self._track = self.song().view.selected_track 93 | self._track_eq.set_track(self._track) 94 | cut_buttons = [self._buttons[1], self._buttons[2], self._buttons[3]] 95 | self._track_eq.set_cut_buttons(tuple(cut_buttons)) 96 | self._track_eq.set_gain_controls(tuple([self._param_controls[5], self._param_controls[6], self._param_controls[7]])) 97 | self._track_filter.set_track(self._track) 98 | self._track_filter.set_filter_controls(self._param_controls[0], self._param_controls[4]) 99 | self._strip = self._mixer._selected_strip 100 | self._strip.set_send_controls(tuple([self._param_controls[1], self._param_controls[2], self._param_controls[3]])) 101 | 102 | else: 103 | self._track_eq.set_track(self._track) 104 | cut_buttons = [self._buttons[1], self._buttons[2], self._buttons[3]] 105 | self._track_eq.set_cut_buttons(tuple(cut_buttons)) 106 | self._track_eq.set_gain_controls(tuple([self._param_controls[5], self._param_controls[6], self._param_controls[7]])) 107 | self._track_filter.set_track(self._track) 108 | self._track_filter.set_filter_controls(self._param_controls[0], self._param_controls[4]) 109 | ##self._strip = self._mixer._selected_strip 110 | self._strip.set_send_controls(tuple([self._param_controls[1], self._param_controls[2], self._param_controls[3]])) 111 | ##pass 112 | 113 | #self._rebuild_callback() 114 | 115 | 116 | def on_track_list_changed(self): 117 | self.on_selected_track_changed() 118 | 119 | 120 | def on_selected_track_changed(self): 121 | if self.is_enabled(): 122 | if self._is_locked != True: 123 | self._update_controls_and_buttons() 124 | 125 | 126 | def on_enabled_changed(self): 127 | self.update() 128 | 129 | def set_lock_button(self, button): 130 | assert ((button == None) or isinstance(button, ButtonElement)) 131 | if (self._lock_button != None): 132 | self._lock_button.remove_value_listener(self._lock_value) 133 | self._lock_button = None 134 | self._lock_button = button 135 | if (self._lock_button != None): 136 | self._lock_button.add_value_listener(self._lock_value) 137 | if self._is_locked: 138 | self._lock_button.turn_on() 139 | else: 140 | self._lock_button.turn_off() 141 | 142 | 143 | def _lock_value(self, value): 144 | assert (self._lock_button != None) 145 | assert (value != None) 146 | assert isinstance(value, int) 147 | if ((not self._lock_button.is_momentary()) or (value is not 0)): 148 | #if (value is not 0): 149 | if self._ignore_buttons == False: 150 | if self._is_locked: 151 | self._is_locked = False 152 | self._mixer._is_locked = False 153 | self._lock_button.turn_off() 154 | self._mixer.on_selected_track_changed() 155 | self.on_selected_track_changed() 156 | else: 157 | self._is_locked = True 158 | self._mixer._is_locked = True 159 | self._lock_button.turn_on() 160 | 161 | 162 | 163 | # local variables: 164 | # tab-width: 4 165 | -------------------------------------------------------------------------------- /APCAdvanced/LooperComponent.py: -------------------------------------------------------------------------------- 1 | from _Framework.ButtonElement import ButtonElement #added 2 | from _Framework.EncoderElement import EncoderElement #added 3 | 4 | class LooperComponent(): 5 | 'Handles looping controls' 6 | __module__ = __name__ 7 | 8 | 9 | def __init__(self, parent): 10 | self._parent = parent 11 | self._loop_toggle_button = None 12 | self._loop_start_button = None 13 | self._loop_double_button = None 14 | self._loop_halve_button = None 15 | self._loop_length = 64 16 | self._loop_start = 0 17 | self._clip_length = 0 18 | self._shift_button = None 19 | self._current_clip = None 20 | self._shift_pressed = False 21 | 22 | def set_loop_toggle_button(self, button): 23 | assert ((button == None) or (isinstance(button, ButtonElement) and button.is_momentary())) 24 | if self._loop_toggle_button != button: 25 | if self._loop_toggle_button != None: 26 | self._loop_toggle_button.remove_value_listener(self.toggle_loop) 27 | self._loop_toggle_button = button 28 | if (self._loop_toggle_button != None): 29 | self._loop_toggle_button.add_value_listener(self.toggle_loop) 30 | 31 | 32 | def toggle_loop(self, value): 33 | if value == 1: 34 | self.get_current_clip() 35 | if self._current_clip != None: 36 | current_clip = self._current_clip 37 | if not self._shift_pressed: 38 | if current_clip.looping == 1: 39 | current_clip.looping = 0 40 | else: 41 | self._clip_length = current_clip.length 42 | current_clip.looping = 1 43 | else: 44 | was_playing = current_clip.looping 45 | current_clip.looping = 1 46 | if current_clip.loop_start >= 32.0: 47 | current_clip.loop_end = current_clip.loop_end - 32.0 48 | current_clip.loop_start = current_clip.loop_start - 32.0 49 | else: 50 | current_clip.loop_end = 0.0 + self._loop_length 51 | current_clip.loop_start = 0.0 52 | if was_playing == 0: 53 | current_clip.looping = 0 54 | 55 | 56 | def set_loop_start_button(self, button): 57 | assert ((button == None) or (isinstance(button, ButtonElement) and button.is_momentary())) 58 | if self._loop_start_button != button: 59 | if self._loop_start_button != None: 60 | self._loop_start_button.remove_value_listener(self.move_loop_start) 61 | self._loop_start_button = button 62 | if (self._loop_start_button != None): 63 | self._loop_start_button.add_value_listener(self.move_loop_start) 64 | 65 | def move_loop_start(self, value): 66 | if value == 1: 67 | self.get_current_clip() 68 | if self._current_clip != None: 69 | current_clip = self._current_clip 70 | if not self._shift_pressed: 71 | self._loop_start = round(current_clip.playing_position / 4.0) * 4 72 | was_playing = current_clip.looping 73 | current_clip.looping = 1 74 | current_clip.loop_end = self._loop_start + self._loop_length 75 | current_clip.loop_start = self._loop_start 76 | # Twice to fix a weird bug 77 | current_clip.loop_end = self._loop_start + self._loop_length 78 | if was_playing == 0: 79 | current_clip.looping = 0 80 | else: 81 | was_playing = current_clip.looping 82 | current_clip.looping = 1 83 | current_clip.loop_end = current_clip.loop_end + 32.0 84 | current_clip.loop_start = current_clip.loop_start + 32.0 85 | if was_playing == 0: 86 | current_clip.looping = 0 87 | 88 | def set_loop_double_button(self, button): 89 | assert ((button == None) or (isinstance(button, ButtonElement) and button.is_momentary())) 90 | if self._loop_double_button != button: 91 | if self._loop_double_button != None: 92 | self._loop_double_button.remove_value_listener(self.increase_loop) 93 | self._loop_double_button = button 94 | if (self._loop_double_button != None): 95 | self._loop_double_button.add_value_listener(self.increase_loop) 96 | 97 | # Doubles loop without shift 98 | # Moves loop one bar right with shift 99 | def increase_loop(self, value): 100 | if value == 1: 101 | self.get_current_clip() 102 | if self._current_clip != None: 103 | current_clip = self._current_clip 104 | was_playing = current_clip.looping 105 | current_clip.looping = 1 106 | if not self._shift_pressed: 107 | if self._loop_length <= 128: 108 | self._loop_length = self._loop_length * 2.0 109 | else: 110 | self._loop_length = self._loop_length + 16 111 | current_clip.loop_end = current_clip.loop_start + self._loop_length 112 | else: 113 | current_clip.loop_end = current_clip.loop_end + 4.0 114 | current_clip.loop_start = current_clip.loop_start + 4.0 115 | if was_playing == 0: 116 | current_clip.looping = 0 117 | 118 | 119 | def set_loop_halve_button(self, button): 120 | assert ((button == None) or (isinstance(button, ButtonElement) and button.is_momentary())) 121 | if self._loop_halve_button != button: 122 | if self._loop_halve_button != None: 123 | self._loop_halve_button.remove_value_listener(self.decrease_loop) 124 | self._loop_halve_button = button 125 | if (self._loop_halve_button != None): 126 | self._loop_halve_button.add_value_listener(self.decrease_loop) 127 | 128 | # halves loop without shift 129 | # left loop one bar right with shift 130 | def decrease_loop(self, value): 131 | if value == 1: 132 | self.get_current_clip() 133 | if self._current_clip != None: 134 | current_clip = self._current_clip 135 | was_playing = current_clip.looping 136 | current_clip.looping = 1 137 | if not self._shift_pressed: 138 | if self._loop_length <= 128: 139 | self._loop_length = self._loop_length / 2.0 140 | else: 141 | self._loop_length = self._loop_length - 16 142 | current_clip.loop_end = current_clip.loop_start + self._loop_length 143 | else: 144 | if current_clip.loop_start >= 4.0: 145 | current_clip.loop_end = current_clip.loop_end - 4.0 146 | current_clip.loop_start = current_clip.loop_start - 4.0 147 | else: 148 | current_clip.loop_end = 0.0 + self._loop_length 149 | current_clip.loop_start = 0.0 150 | if was_playing == 0: 151 | current_clip.looping = 0 152 | 153 | 154 | def get_current_clip(self): 155 | if (self._parent.song().view.highlighted_clip_slot != None): 156 | clip_slot = self._parent.song().view.highlighted_clip_slot 157 | if clip_slot.has_clip: 158 | self._current_clip = clip_slot.clip 159 | else: 160 | self._current_clip = None 161 | else: 162 | self._current_clip = None 163 | 164 | 165 | def set_shift_button(self, button): #added 166 | assert ((button == None) or (isinstance(button, ButtonElement) and button.is_momentary())) 167 | if (self._shift_button != button): 168 | if (self._shift_button != None): 169 | self._shift_button.remove_value_listener(self._shift_value) 170 | self._shift_button = button 171 | if (self._shift_button != None): 172 | self._shift_button.add_value_listener(self._shift_value) 173 | 174 | def _shift_value(self, value): #added 175 | assert (self._shift_button != None) 176 | assert (value in range(128)) 177 | self._shift_pressed = (value != 0) 178 | -------------------------------------------------------------------------------- /APCAdvanced/Matrix_Maps.py: -------------------------------------------------------------------------------- 1 | # http://remotescripts.blogspot.com 2 | # Mappings for APC40_21 USER MODE/NOTE MODE are defined in this file 3 | # Values may be edited with any text editor, but avoid using tabs for indentation 4 | 5 | #---------- Page 1 is Clip Launch 6 | 7 | #---------- Page 2 is Session Overview 8 | 9 | #---------- Page 3 is User Mode 1 10 | 11 | #set USE_STOP_MODE to True in order to use Track Stop buttons as part of Note Mode/User Mode grid, otherwise set to False. 12 | USE_STOP_ROW_1 = True 13 | 14 | #set IS_NOTE_MODE to True for Note Mode (sends MIDI notes), or set to False for User Mode (does not send MIDI notes) 15 | IS_NOTE_MODE_1 = True 16 | 17 | # The PATTERN array represents the colour values for each button in the grid; there are 6 rows and 8 columns 18 | # The LED colour values are: 0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green 19 | # The last row represents the Track Stop buttons; these values will be ignored unless USE_STOP_ROW is set to True 20 | # The Track Stop buttons can be set to 0=off or 1-127=green 21 | PATTERN_1 = ((3, 3, 3, 3, 5, 5, 5, 5), #Row 1 22 | (3, 3, 3, 3, 5, 5, 5, 5), #Row 2 23 | (1, 1, 1, 1, 1, 1, 1, 1), #Row 3 24 | (1, 1, 1, 1, 3, 3, 3, 3), #Row 4 25 | (1, 1, 1, 1, 3, 3, 3, 3), #Row 5 26 | (1, 1, 1, 1, 1, 1, 1, 1), #Clip Stop Row 27 | ) #0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green 28 | 29 | # The CHANNEL value sets the MIDI Channel for the entire grid. 30 | # Values 0 through 15 correspond to MIDI channels 1 through 16. 31 | # Channels 0 through 8 should be avoided, to prevent conflict with the APC40's native mappings 32 | CHANNEL_1 = 9 33 | 34 | # The NOTEMAP array represents the MIDI note values for each button in the grid; there are 6 rows and 8 columns 35 | # Valid note values are 0 through 127 36 | # The last row represents the Track Stop buttons; these values will be ignored unless USE_STOP_ROW is set to True 37 | NOTEMAP_1 = ((56, 57, 58, 59, 80, 81, 82, 83), #Row 1 38 | (52, 53, 54, 55, 76, 77, 78, 79), #Row 2 39 | (48, 49, 50, 51, 72, 73, 74, 75), #Row 3 40 | (44, 45, 46, 47, 68, 69, 70, 71), #Row 4 41 | (40, 41, 42, 43, 64, 65, 66, 67), #Row 5 42 | (36, 37, 38, 39, 60, 61, 62, 63), #Clip Stop Row 43 | ) 44 | 45 | #---------- Page 4 is User Mode 2 46 | 47 | USE_STOP_ROW_2 = True 48 | IS_NOTE_MODE_2 = True 49 | 50 | PATTERN_2 = ((5, 5, 5, 5, 5, 5, 5, 5), #Row 1 51 | (1, 1, 1, 1, 5, 5, 5, 5), #Row 2 52 | (1, 1, 1, 1, 1, 1, 1, 1), #Row 3 53 | (3, 3, 3, 3, 3, 3, 3, 3), #Row 4 54 | (1, 1, 1, 1, 3, 3, 3, 3), #Row 5 55 | (1, 1, 1, 1, 1, 1, 1, 1), #Clip Stop Row 56 | ) #0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green 57 | 58 | CHANNEL_2 = 10 59 | 60 | NOTEMAP_2 = ((76, 77, 78, 79, 80, 81, 82, 83), #Row 1 61 | (68, 69, 70, 71, 72, 73, 74, 75), #Row 2 62 | (60, 61, 62, 63, 64, 65, 66, 67), #Row 3 63 | (52, 53, 54, 55, 56, 57, 58, 59), #Row 4 64 | (44, 45, 46, 47, 48, 49, 50, 51), #Row 5 65 | (36, 37, 38, 39, 40, 41, 42, 43), #Clip Stop Row 66 | ) 67 | 68 | #---------- Page 5 is User Mode 3 69 | 70 | USE_STOP_ROW_3 = True 71 | IS_NOTE_MODE_3 = True 72 | 73 | PATTERN_3 = ((5, 5, 0, 5, 0, 5, 0, 5), #Row 1 74 | (0, 1, 0, 1, 5, 0, 5, 0), #Row 2 75 | (1, 0, 1, 0, 1, 1, 0, 1), #Row 3 76 | (3, 3, 0, 3, 0, 3, 0, 3), #Row 4 77 | (0, 1, 0, 1, 3, 0, 3, 0), #Row 5 78 | (1, 0, 1, 0, 1, 1, 0, 1), #Clip Stop Row 79 | ) #0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green 80 | 81 | CHANNEL_3 = 11 82 | 83 | NOTEMAP_3 = ((76, 77, 78, 79, 80, 81, 82, 83), #Row 1 84 | (68, 69, 70, 71, 72, 73, 74, 75), #Row 2 85 | (60, 61, 62, 63, 64, 65, 66, 67), #Row 3 86 | (52, 53, 54, 55, 56, 57, 58, 59), #Row 4 87 | (44, 45, 46, 47, 48, 49, 50, 51), #Row 5 88 | (36, 37, 38, 39, 40, 41, 42, 43), #Clip Stop Row 89 | ) 90 | 91 | #---------- Page 6 is User Mode 4 92 | 93 | USE_STOP_ROW_4 = False 94 | IS_NOTE_MODE_4 = True 95 | 96 | PATTERN_4 = ((5, 1, 5, 1, 3, 1, 3, 1), #Row 1 97 | (1, 5, 1, 5, 1, 3, 1, 3), #Row 2 98 | (5, 1, 5, 1, 3, 1, 3, 1), #Row 3 99 | (1, 5, 1, 5, 1, 3, 1, 3), #Row 4 100 | (5, 1, 5, 1, 3, 1, 3, 1), #Row 5 101 | (1, 1, 1, 1, 1, 1, 1, 1), #Clip Stop Row 102 | ) #0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green 103 | 104 | CHANNEL_4 = 12 105 | 106 | NOTEMAP_4 = ((56, 57, 58, 59, 80, 81, 82, 83), #Row 1 107 | (52, 53, 54, 55, 76, 77, 78, 79), #Row 2 108 | (48, 49, 50, 51, 72, 73, 74, 75), #Row 3 109 | (44, 45, 46, 47, 68, 69, 70, 71), #Row 4 110 | (40, 41, 42, 43, 64, 65, 66, 67), #Row 5 111 | (36, 37, 38, 39, 60, 61, 62, 63), #Clip Stop Row 112 | ) 113 | 114 | #---------- Page 7 is User Mode 5 115 | 116 | USE_STOP_ROW_5 = True 117 | IS_NOTE_MODE_5 = True 118 | 119 | PATTERN_5 = ((1, 5, 3, 1, 5, 3, 1, 5), #Row 1 120 | (1, 5, 3, 1, 5, 3, 1, 5), #Row 2 121 | (1, 5, 3, 1, 5, 3, 1, 5), #Row 3 122 | (1, 5, 3, 1, 5, 3, 1, 5), #Row 4 123 | (1, 5, 3, 1, 5, 3, 1, 5), #Row 5 124 | (1, 1, 1, 1, 1, 1, 1, 5), #Clip Stop Row 125 | ) #0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green 126 | 127 | CHANNEL_5 = 13 128 | 129 | NOTEMAP_5 = ((41, 47, 53, 59, 65, 71, 77, 83), #Row 1 130 | (40, 46, 52, 58, 64, 70, 76, 82), #Row 2 131 | (39, 45, 51, 57, 63, 69, 75, 81), #Row 3 132 | (38, 44, 50, 56, 62, 68, 74, 80), #Row 4 133 | (37, 43, 49, 55, 61, 67, 73, 79), #Row 5 134 | (36, 42, 48, 54, 60, 66, 72, 78), #Clip Stop Row 135 | ) 136 | 137 | #---------- Page 8 is User Mode 6 138 | 139 | USE_STOP_ROW_6 = True 140 | IS_NOTE_MODE_6 = False 141 | 142 | PATTERN_6 = ((3, 3, 3, 3, 3, 3, 3, 3), #Row 1 143 | (5, 5, 5, 5, 5, 5, 5, 5), #Row 2 144 | (1, 1, 1, 1, 1, 1, 1, 1), #Row 3 145 | (3, 3, 3, 3, 3, 3, 3, 3), #Row 4 146 | (5, 5, 5, 5, 5, 5, 5, 5), #Row 5 147 | (1, 1, 1, 1, 1, 1, 1, 1), #Clip Stop Row 148 | ) #0=off, 1=green, 2=green blink, 3=red, 4=red blink, 5=yellow, 6=yellow blink, 7-127=green 149 | 150 | CHANNEL_6 = 14 151 | 152 | NOTEMAP_6 = ((56, 57, 58, 59, 80, 81, 82, 83), #Row 1 153 | (52, 53, 54, 55, 76, 77, 78, 79), #Row 2 154 | (48, 49, 50, 51, 72, 73, 74, 75), #Row 3 155 | (44, 45, 46, 47, 68, 69, 70, 71), #Row 4 156 | (40, 41, 42, 43, 64, 65, 66, 67), #Row 5 157 | (36, 37, 38, 39, 60, 61, 62, 63), #Clip Stop Row 158 | ) 159 | 160 | #---------- Pad Translations for Drum Rack 161 | 162 | # The PAD_TRANSLATIONS array represents a 4 x 4 Drum Rack 163 | # Each slot in the rack is identified using X,Y coordinates, and mapped to a MIDI note and MIDI channel: 164 | # (pad_x, pad_y, note, channel) 165 | # Only one drum rack can be used at a time; maximum grid size is 4 x 4 (LiveAPI limitation) 166 | PAD_TRANSLATIONS = ((0, 0, 48, 9), (1, 0, 49, 9), (2, 0, 50, 9), (3, 0, 51, 9), 167 | (0, 1, 44, 9), (1, 1, 45, 9), (2, 1, 46, 9), (3, 1, 47, 9), 168 | (0, 2, 40, 9), (1, 2, 41, 9), (2, 2, 42, 9), (3, 2, 43, 9), 169 | (0, 3, 36, 9), (1, 3, 37, 9), (2, 3, 38, 9), (3, 3, 39, 9), 170 | ) #(pad_x, pad_y, note, channel) -------------------------------------------------------------------------------- /APCAdvanced/APC.py: -------------------------------------------------------------------------------- 1 | # --== Decompile ==-- 2 | 3 | import Live 4 | from _Framework.ControlSurface import ControlSurface 5 | MANUFACTURER_ID = 71 6 | ABLETON_MODE = 65 #65 = 0x41 = Ableton Live Mode 1; 66 = 0x42 = Ableton Mode 2; 67 = 0x43 = Ableton Mode 3 (APC20 only) 7 | #PRODUCT_MODEL_ID = 115 # 0x73 Product Model ID (APC40) 8 | 9 | class APC(ControlSurface): 10 | __doc__ = " Script for Akai's line of APC Controllers " 11 | _active_instances = [] 12 | def _combine_active_instances(): 13 | if not len(APC._active_instances) > 0: 14 | raise AssertionError 15 | if len(APC._active_instances) > 1: 16 | support_devices = False 17 | for instance in APC._active_instances: 18 | support_devices |= instance._device_component != None 19 | track_offset = 0 20 | for instance in APC._active_instances: 21 | instance._activate_combination_mode(track_offset, support_devices) 22 | track_offset += instance._session.width() 23 | return None 24 | 25 | _combine_active_instances = staticmethod(_combine_active_instances) 26 | def __init__(self, c_instance): 27 | ControlSurface.__init__(self, c_instance) 28 | self.set_suppress_rebuild_requests(True) 29 | self._suppress_session_highlight = True 30 | self._suppress_send_midi = True 31 | self._suggested_input_port = 'Akai ' + self.__class__.__name__ 32 | self._suggested_output_port = 'Akai ' + self.__class__.__name__ 33 | self._shift_button = None 34 | self._matrix = None 35 | self._session = None 36 | self._session_zoom = None 37 | self._mixer = None 38 | self._setup_session_control() 39 | self._setup_mixer_control() 40 | self._session.set_mixer(self._mixer) 41 | self._shift_button.name = 'Shift_Button' 42 | self._setup_custom_components() 43 | for component in self.components: 44 | component.set_enabled(False) 45 | self.set_suppress_rebuild_requests(False) 46 | self._device_id = 0 47 | self._common_channel = 0 48 | self._dongle_challenge = (Live.Application.get_random_int(0, 2000000), Live.Application.get_random_int(2000001, 4000000)) 49 | return None 50 | 51 | def disconnect(self): 52 | self._shift_button = None 53 | self._matrix = None 54 | self._session = None 55 | self._session_zoom = None 56 | self._mixer = None 57 | ControlSurface.disconnect(self) 58 | return None 59 | 60 | def connect_script_instances(self, instanciated_scripts): 61 | if len(APC._active_instances) > 0 and self == APC._active_instances[0]: 62 | APC._combine_active_instances() 63 | 64 | def refresh_state(self): 65 | ControlSurface.refresh_state(self) 66 | self.schedule_message(5, self._update_hardware) 67 | 68 | def handle_sysex(self, midi_bytes): 69 | self._suppress_send_midi = False 70 | if ((midi_bytes[3] == 6) and (midi_bytes[4] == 2)): 71 | assert (midi_bytes[5] == MANUFACTURER_ID) 72 | assert (midi_bytes[6] == self._product_model_id_byte()) # PRODUCT_MODEL_ID 73 | version_bytes = midi_bytes[9:13] 74 | self._device_id = midi_bytes[13] 75 | self._send_introduction_message() # instead of _send_midi, below: 76 | #self._send_midi((240, 77 | #MANUFACTURER_ID, 78 | #self._device_id, 79 | #PRODUCT_MODEL_ID, 80 | #96, 81 | #0, 82 | #4, 83 | #APPLICTION_ID, 84 | #self.application().get_major_version(), 85 | #self.application().get_minor_version(), 86 | #self.application().get_bugfix_version(), 87 | #247)) 88 | challenge1 = [0, 89 | 0, 90 | 0, 91 | 0, 92 | 0, 93 | 0, 94 | 0, 95 | 0] 96 | challenge2 = [0, 97 | 0, 98 | 0, 99 | 0, 100 | 0, 101 | 0, 102 | 0, 103 | 0] 104 | for index in range(8): 105 | challenge1[index] = ((self._dongle_challenge[0] >> (4 * (7 - index))) & 15) 106 | challenge2[index] = ((self._dongle_challenge[1] >> (4 * (7 - index))) & 15) 107 | 108 | dongle_message = ((((240, 109 | MANUFACTURER_ID, 110 | self._device_id, 111 | self._product_model_id_byte(), 112 | 80, 113 | 0, 114 | 16) + tuple(challenge1)) + tuple(challenge2)) + (247,)) 115 | self._send_midi(dongle_message) 116 | message = (((self.__class__.__name__ + ': Got response from controller, version ' + str(((version_bytes[0] << 4) + version_bytes[1]))) + '.') + str(((version_bytes[2] << 4) + version_bytes[3]))) 117 | self.log_message(message) 118 | elif (midi_bytes[4] == 81): 119 | assert (midi_bytes[1] == MANUFACTURER_ID) 120 | assert (midi_bytes[2] == self._device_id) 121 | assert (midi_bytes[3] == self._product_model_id_byte()) # PRODUCT_MODEL_ID) 122 | assert (midi_bytes[5] == 0) 123 | assert (midi_bytes[6] == 16) 124 | response = [long(0), 125 | long(0)] 126 | for index in range(8): 127 | response[0] += (long((midi_bytes[(7 + index)] & 15)) << (4 * (7 - index))) 128 | response[1] += (long((midi_bytes[(15 + index)] & 15)) << (4 * (7 - index))) 129 | 130 | expected_response = Live.Application.encrypt_challenge(self._dongle_challenge[0], self._dongle_challenge[1]) 131 | if ((long(expected_response[0]) == response[0]) and (long(expected_response[1]) == response[1])): 132 | self._suppress_session_highlight = False 133 | for component in self.components: 134 | component.set_enabled(True) 135 | 136 | self._on_selected_track_changed() 137 | 138 | def _update_hardware(self): 139 | self._suppress_send_midi = True 140 | self._suppress_session_highlight = True 141 | self.set_suppress_rebuild_requests(True) 142 | for component in self.components: 143 | component.set_enabled(False) 144 | self.set_suppress_rebuild_requests(False) 145 | self._suppress_send_midi = False 146 | self._send_midi((240, 126, 0, 6, 1, 247)) #(0xF0, 0x7E, 0x00, 0x06, 0x01, 0xF7) = Standard MMC Device Enquiry 147 | 148 | def _set_session_highlight(self, track_offset, scene_offset, width, height, include_return_tracks): 149 | if not self._suppress_session_highlight: 150 | self._suppress_session_highlight 151 | ControlSurface._set_session_highlight(self, track_offset, scene_offset, width, height, include_return_tracks) 152 | else: 153 | self._suppress_session_highlight 154 | 155 | def _send_midi(self, midi_bytes): 156 | if not self._suppress_send_midi: 157 | self._suppress_send_midi 158 | ControlSurface._send_midi(self, midi_bytes) 159 | else: 160 | self._suppress_send_midi 161 | 162 | def _send_introduction_message(self, mode_byte=ABLETON_MODE): 163 | self._send_midi((240, MANUFACTURER_ID, self._device_id, self._product_model_id_byte(), 96, 0, 4, mode_byte, self.application().get_major_version(), self.application().get_minor_version(), self.application().get_bugfix_version(), 247)) 164 | 165 | def _activate_combination_mode(self, track_offset, support_devices): 166 | self._session.link_with_track_offset(track_offset) 167 | 168 | def _setup_session_control(self): 169 | raise AssertionError, 'Function _setup_session_control must be overridden by subclass' 170 | 171 | def _setup_mixer_control(self): 172 | raise AssertionError, 'Function _setup_mixer_control must be overridden by subclass' 173 | 174 | def _setup_custom_components(self): 175 | raise AssertionError, 'Function _setup_custom_components must be overridden by subclass' 176 | 177 | def _product_model_id_byte(self): 178 | raise AssertionError, 'Function _product_model_id_byte must be overridden by subclass' 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /APCAdvanced/MatrixModesComponent.py: -------------------------------------------------------------------------------- 1 | # emacs-mode: -*- python-*- 2 | # -*- coding: utf-8 -*- 3 | 4 | from _Framework.ModeSelectorComponent import ModeSelectorComponent 5 | from _Framework.ButtonElement import ButtonElement 6 | from _Framework.MixerComponent import MixerComponent 7 | from _Framework.ButtonMatrixElement import ButtonMatrixElement 8 | from _Framework.ControlSurface import ControlSurface 9 | from Matrix_Maps import * 10 | 11 | class MatrixModesComponent(ModeSelectorComponent): 12 | ' SelectorComponent that assigns matrix to different functions ' 13 | __module__ = __name__ 14 | 15 | def __init__(self, matrix, session, zooming, stop_buttons, parent): 16 | assert isinstance(matrix, ButtonMatrixElement) 17 | ModeSelectorComponent.__init__(self) 18 | self._controls = None 19 | self._session = session 20 | self._session_zoom = zooming 21 | self._matrix = matrix 22 | self._track_stop_buttons = stop_buttons 23 | self._stop_button_matrix = ButtonMatrixElement() #new dummy matrix for stop buttons, to allow note mode/user mode switching 24 | button_row = [] 25 | for track_index in range(8): 26 | button = self._track_stop_buttons[track_index] 27 | button_row.append(button) 28 | self._stop_button_matrix.add_row(tuple(button_row)) 29 | self._mode_index = 0 30 | self._last_mode = 0 31 | self._parent = parent 32 | self._parent.set_pad_translations(PAD_TRANSLATIONS) #comment out to remove Drum Rack mapping 33 | 34 | 35 | def disconnect(self): 36 | for button in self._modes_buttons: 37 | button.remove_value_listener(self._mode_value) 38 | self._controls = None 39 | self._session = None 40 | self._session_zoom = None 41 | self._matrix = None 42 | self._track_stop_buttons = None 43 | self._stop_button_matrix = None 44 | ModeSelectorComponent.disconnect(self) 45 | 46 | 47 | def set_mode(self, mode): #override ModeSelectorComponent set_mode, to avoid flickers 48 | assert isinstance(mode, int) 49 | assert (mode in range(self.number_of_modes())) 50 | if (self._mode_index != mode): 51 | self._last_mode = self._mode_index # keep track of previous mode, to allow refresh after Note Mode only 52 | self._mode_index = mode 53 | self._set_modes() 54 | 55 | 56 | def set_mode_buttons(self, buttons): 57 | assert isinstance(buttons, (tuple, 58 | type(None))) 59 | for button in self._modes_buttons: 60 | button.remove_value_listener(self._mode_value) 61 | 62 | self._modes_buttons = [] 63 | if (buttons != None): 64 | for button in buttons: 65 | assert isinstance(button, ButtonElement) 66 | identify_sender = True 67 | button.add_value_listener(self._mode_value, identify_sender) 68 | self._modes_buttons.append(button) 69 | for index in range(len(self._modes_buttons)): 70 | if (index == self._mode_index): 71 | self._modes_buttons[index].turn_on() 72 | else: 73 | self._modes_buttons[index].turn_off() 74 | 75 | 76 | def number_of_modes(self): 77 | return 8 78 | 79 | 80 | def update(self): 81 | pass 82 | 83 | 84 | def _set_modes(self): 85 | if self.is_enabled(): 86 | self._session.set_allow_update(False) 87 | self._session_zoom.set_allow_update(False) 88 | assert (self._mode_index in range(self.number_of_modes())) 89 | for index in range(len(self._modes_buttons)): 90 | if (index == self._mode_index): 91 | self._modes_buttons[index].turn_on() 92 | else: 93 | self._modes_buttons[index].turn_off() 94 | self._session.set_stop_track_clip_buttons(tuple(self._track_stop_buttons)) 95 | for track_index in range(8): 96 | button = self._track_stop_buttons[track_index] 97 | button.use_default_message() 98 | button.set_enabled(True) 99 | button.set_force_next_value() 100 | button.send_value(0) 101 | self._session_zoom.set_enabled(True) 102 | self._session.set_enabled(True) 103 | self._session.set_show_highlight(True) 104 | self._session_zoom.set_zoom_button(self._parent._shift_button) 105 | for scene_index in range(5): 106 | scene = self._session.scene(scene_index) 107 | for track_index in range(8): 108 | button = self._matrix.get_button(track_index, scene_index) 109 | button.use_default_message() 110 | clip_slot = scene.clip_slot(track_index) 111 | clip_slot.set_launch_button(button) 112 | button.set_enabled(True) 113 | 114 | if (self._mode_index == 0): #Clip Launch 115 | self._session_zoom._zoom_value(1) #zoom out 116 | pass 117 | 118 | elif (self._mode_index == 1): #Session Overview 119 | self._session_zoom.set_enabled(True) 120 | self._session_zoom._is_zoomed_out = True 121 | self._session_zoom._scene_bank_index = int(((self._session_zoom._session.scene_offset() / self._session_zoom._session.height()) / self._session_zoom._buttons.height())) 122 | self._session.set_enabled(False) 123 | self._session_zoom.update() 124 | self._session_zoom.set_zoom_button(None) 125 | 126 | elif (self._mode_index == 2): 127 | self._set_note_mode(PATTERN_1, CHANNEL_1, NOTEMAP_1, USE_STOP_ROW_1, IS_NOTE_MODE_1) 128 | elif (self._mode_index == 3): 129 | self._set_note_mode(PATTERN_2, CHANNEL_2, NOTEMAP_2, USE_STOP_ROW_2, IS_NOTE_MODE_2) 130 | elif (self._mode_index == 4): 131 | self._set_note_mode(PATTERN_3, CHANNEL_3, NOTEMAP_3, USE_STOP_ROW_3, IS_NOTE_MODE_3) 132 | elif (self._mode_index == 5): 133 | self._set_note_mode(PATTERN_4, CHANNEL_4, NOTEMAP_4, USE_STOP_ROW_4, IS_NOTE_MODE_4) 134 | elif (self._mode_index == 6): 135 | self._set_note_mode(PATTERN_5, CHANNEL_5, NOTEMAP_5, USE_STOP_ROW_5, IS_NOTE_MODE_5) 136 | elif (self._mode_index == 7): 137 | self._set_note_mode(PATTERN_6, CHANNEL_6, NOTEMAP_6, USE_STOP_ROW_6, IS_NOTE_MODE_6) 138 | else: 139 | pass #assert False 140 | self._session.set_allow_update(True) 141 | self._session_zoom.set_allow_update(True) 142 | #self._rebuild_callback() 143 | 144 | 145 | def _set_note_mode(self, pattern, channel, notemap, use_stop_row = False, is_note_mode = True): 146 | self._session_zoom.set_zoom_button(None) 147 | self._session_zoom.set_enabled(False) 148 | for scene_index in range(5): 149 | scene = self._session.scene(scene_index) 150 | for track_index in range(8): 151 | clip_slot = scene.clip_slot(track_index) 152 | button = self._matrix.get_button(track_index, scene_index) 153 | clip_slot.set_launch_button(None) 154 | button.set_channel(channel) #remap all Note Mode notes to new channel 155 | button.set_identifier(notemap[scene_index][track_index]) 156 | #button.send_value(pattern[scene_index][track_index], True) 157 | button.set_on_off_values(pattern[scene_index][track_index], 0) 158 | button.set_force_next_value() 159 | button.turn_on() 160 | #button.turn_off() 161 | if is_note_mode == True: 162 | button.set_enabled(False) 163 | if use_stop_row == True: 164 | self._session.set_stop_track_clip_buttons(None) 165 | for track_index in range(8): 166 | button = self._stop_button_matrix.get_button(track_index, 0) 167 | button.set_channel(channel) #remap all Note Mode notes to new channel 168 | button.set_identifier(notemap[5][track_index]) 169 | button.set_force_next_value() 170 | button.send_value(pattern[5][track_index]) 171 | #button.receive_value(pattern[5][track_index]) #TODO - feedback? 172 | if is_note_mode == True: 173 | button.set_enabled(False) 174 | else: 175 | #self._session.set_enabled(True) 176 | for track_index in range(8): 177 | button = self._stop_button_matrix.get_button(track_index, 0) 178 | button.send_value(0, True) 179 | self._session.set_enabled(True) 180 | self._session.set_show_highlight(True) 181 | 182 | # local variables: 183 | # tab-width: 4 184 | -------------------------------------------------------------------------------- /APCAdvanced/VUMeters.py: -------------------------------------------------------------------------------- 1 | import Live 2 | from _Framework.ControlSurfaceComponent import ControlSurfaceComponent 3 | from _Framework.ButtonElement import ButtonElement 4 | from _Framework.SessionComponent import SessionComponent 5 | import math 6 | 7 | 8 | # Constants. Tweaking these would let us work with different grid sizes or different templates 9 | 10 | #Index of the columns used for VU display 11 | A_COL = [4] 12 | B_COL = [5] 13 | C_COL = [6] 14 | D_COL = [7] 15 | # Which channels we are monitoring for RMS 16 | A_SOURCE = 0 17 | B_SOURCE = 1 18 | C_SOURCE = 2 19 | D_SOURCE = 3 20 | 21 | # Grid size 22 | CLIP_GRID_X = 8 23 | CLIP_GRID_Y = 5 24 | 25 | # Velocity values for clip colours. Different on some devices 26 | LED_RED = 3 27 | LED_ON = 127 28 | LED_OFF = 0 29 | LED_ORANGE = 5 30 | 31 | # Scaling constants. Narrows the db range we display to 0db-21db or thereabouts 32 | CHANNEL_SCALE_MAX = 0.92 33 | CHANNEL_SCALE_MIN = 0.52 34 | CHANNEL_SCALE_INCREMENTS = 10 35 | 36 | MASTER_SCALE_MAX = 0.92 37 | MASTER_SCALE_MIN = 0.52 38 | MASTER_SCALE_INCREMENTS = 5 39 | 40 | RMS_FRAMES = 2 41 | USE_RMS = True 42 | 43 | class VUMeter(): 44 | 'represents a single VU to store RMS values etc in' 45 | def __init__(self, parent, track, top, bottom, 46 | increments, vu_set, master = False): 47 | 48 | self.frames = [0.0] * RMS_FRAMES 49 | self.parent = parent 50 | self.track = track 51 | self.top = top 52 | self.bottom = bottom 53 | self.multiplier = self.calculate_multiplier(top, bottom, increments) 54 | self.current_level = 0 55 | self.matrix = self.setup_matrix(vu_set, master) 56 | self.master = master 57 | 58 | def observe(self): 59 | new_frame = self.mean_peak() 60 | self.store_frame(new_frame) 61 | if self.master and new_frame >= 0.92: 62 | self.parent._clipping = True 63 | self.parent.clip_warning() 64 | else: 65 | 66 | if self.master and self.parent._clipping: 67 | self.parent._parent._session._change_offsets(0, 1) 68 | self.parent._parent._session._change_offsets(0, -1) 69 | 70 | 71 | self.parent._clipping = False 72 | 73 | if not self.parent._clipping: 74 | if USE_RMS: 75 | level = self.scale(self.rms(self.frames)) 76 | else: 77 | level = self.scale(new_frame) 78 | if level != self.current_level: 79 | self.current_level = level 80 | if self.master: 81 | self.parent.set_master_leds(level) 82 | else: 83 | self.parent.set_leds(self.matrix, level) 84 | 85 | def store_frame(self, frame): 86 | self.frames.pop(0) 87 | self.frames.append(frame) 88 | 89 | def rms(self, frames): 90 | return math.sqrt(sum(frame*frame for frame in frames)/len(frames)) 91 | 92 | # return the mean of the L and R peak values 93 | def mean_peak(self): 94 | return (self.track.output_meter_left + self.track.output_meter_right) / 2 95 | 96 | 97 | # Perform the scaling as per params. We reduce the range, then round it out to integers 98 | def scale(self, value): 99 | if (value > self.top): 100 | value = self.top 101 | elif (value < self.bottom): 102 | value = self.bottom 103 | value = value - self.bottom 104 | value = value * self.multiplier #float, scale 0-10 105 | return int(round(value)) 106 | 107 | def calculate_multiplier(self, top, bottom, increments): 108 | return (increments / (top - bottom)) 109 | 110 | 111 | # Goes from top to bottom: so clip grid, then stop, then select, then activator/solo/arm 112 | def setup_matrix(self, vu_set, master): 113 | matrix = [] 114 | if master: 115 | for scene in self.parent._parent._session._scenes: 116 | matrix.append(scene._launch_button) 117 | else: 118 | for index, column_index in enumerate(vu_set): 119 | matrix.append([]) 120 | column = matrix[index] 121 | for row_index in range(CLIP_GRID_Y): 122 | column.append(self.parent._parent._button_rows[row_index][column_index]) 123 | if master != True: 124 | strip = self.parent._parent._mixer.channel_strip(column_index) 125 | column.append(self.parent._parent._track_stop_buttons[column_index]) 126 | column.extend([strip._select_button, strip._mute_button, strip._solo_button, strip._arm_button]) 127 | return matrix 128 | 129 | 130 | class VUMeters(ControlSurfaceComponent): 131 | 'standalone class used to handle VU meters' 132 | 133 | def __init__(self, parent): 134 | # Boilerplate 135 | ControlSurfaceComponent.__init__(self) 136 | self._parent = parent 137 | 138 | # Default the L/R/Master levels to 0 139 | self._meter_level = 0 140 | self._a_level = 0 141 | self._b_level = 0 142 | self._c_level = 0 143 | self._d_level = 0 144 | 145 | # We don't start clipping 146 | self._clipping = False 147 | 148 | # The tracks we'll be pulling L and R RMS from 149 | self._a_track = self.song().tracks[A_SOURCE] 150 | self._b_track = self.song().tracks[B_SOURCE] 151 | self._c_track = self.song().tracks[C_SOURCE] 152 | self._d_track = self.song().tracks[D_SOURCE] 153 | 154 | #setup classes 155 | self.a_meter = VUMeter(self, self._a_track, 156 | CHANNEL_SCALE_MAX, 157 | CHANNEL_SCALE_MIN, CHANNEL_SCALE_INCREMENTS, 158 | A_COL) 159 | self.b_meter = VUMeter(self, self._b_track, 160 | CHANNEL_SCALE_MAX, 161 | CHANNEL_SCALE_MIN, CHANNEL_SCALE_INCREMENTS, 162 | B_COL) 163 | self.c_meter = VUMeter(self, self._c_track, 164 | CHANNEL_SCALE_MAX, 165 | CHANNEL_SCALE_MIN, CHANNEL_SCALE_INCREMENTS, 166 | C_COL) 167 | self.d_meter = VUMeter(self, self._d_track, 168 | CHANNEL_SCALE_MAX, 169 | CHANNEL_SCALE_MIN, CHANNEL_SCALE_INCREMENTS, 170 | D_COL) 171 | self.master_meter = VUMeter(self, self.song().master_track, 172 | MASTER_SCALE_MAX, 173 | MASTER_SCALE_MIN, MASTER_SCALE_INCREMENTS, 174 | None, True) 175 | # Listeners! 176 | self._a_track.add_output_meter_left_listener(self.a_meter.observe) 177 | self._b_track.add_output_meter_left_listener(self.b_meter.observe) 178 | self._c_track.add_output_meter_left_listener(self.c_meter.observe) 179 | self._d_track.add_output_meter_left_listener(self.d_meter.observe) 180 | 181 | self.song().master_track.add_output_meter_left_listener(self.master_meter.observe) 182 | 183 | # If you fail to kill the listeners on shutdown, Ableton stores them in memory and punches you in the face 184 | def disconnect(self): 185 | self._a_track.remove_output_meter_left_listener(self.a_meter.observe) 186 | self._b_track.remove_output_meter_left_listener(self.b_meter.observe) 187 | self._c_track.remove_output_meter_left_listener(self.c_meter.observe) 188 | self._d_track.remove_output_meter_left_listener(self.d_meter.observe) 189 | 190 | self.song().master_track.remove_output_meter_left_listener(self.master_meter.observe) 191 | 192 | # Called when the Master clips. Makes the entire clip grid BRIGHT RED 193 | def clip_warning(self): 194 | for row_index in range(CLIP_GRID_Y): 195 | row = self._parent._button_rows[row_index] 196 | for button_index in range(CLIP_GRID_X): 197 | button = row[button_index] 198 | # Passing True to send_value forces it to happen even when the button in question is MIDI mapped 199 | button.send_value(LED_RED, True) 200 | 201 | def set_master_leds(self, level): 202 | for scene_index in range(CLIP_GRID_Y): 203 | scene = self._parent._session.scene(scene_index) 204 | if scene_index >= (CLIP_GRID_Y - level): 205 | scene._launch_button.send_value(LED_ON, True) 206 | else: 207 | scene._launch_button.send_value(LED_OFF, True) 208 | 209 | 210 | # Iterate through every column in the matrix, light up the LEDs based on the level 211 | # Level for channels is scaled to 10 cos we have 10 LEDs 212 | # Top two LEDs are red, the next is orange 213 | def set_leds(self, matrix, level): 214 | for column in matrix: 215 | for index in range(10): 216 | button = column[index] 217 | if index >= (10 - level): 218 | if index < 1: 219 | button.send_value(LED_RED, True) 220 | elif index < 2: 221 | button.send_value(LED_ORANGE, True) 222 | else: 223 | button.send_value(LED_ON, True) 224 | else: 225 | button.send_value(LED_OFF, True) 226 | 227 | # boilerplate 228 | def update(self): 229 | pass 230 | 231 | def on_enabled_changed(self): 232 | self.update() 233 | 234 | def on_selected_track_changed(self): 235 | self.update() 236 | 237 | def on_track_list_changed(self): 238 | self.update() 239 | 240 | def on_selected_scene_changed(self): 241 | self.update() 242 | 243 | def on_scene_list_changed(self): 244 | 245 | self.update() 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | -------------------------------------------------------------------------------- /APCAdvanced/DetailViewControllerComponent.py: -------------------------------------------------------------------------------- 1 | # Partial --== Decompile ==-- with fixes 2 | import Live 3 | from _Framework.ControlSurfaceComponent import ControlSurfaceComponent 4 | from _Framework.ButtonElement import ButtonElement 5 | SHOW_PLAYING_CLIP_DELAY = 5 6 | class DetailViewControllerComponent(ControlSurfaceComponent): 7 | __module__ = __name__ 8 | __doc__ = ' Component that can toggle the device chain- and clip view of the selected track ' 9 | 10 | def __init__(self): 11 | ControlSurfaceComponent.__init__(self) 12 | self._device_clip_toggle_button = None 13 | self._detail_toggle_button = None 14 | self._left_button = None 15 | self._right_button = None 16 | self._shift_button = None 17 | self._shift_pressed = False 18 | self._show_playing_clip_ticks_delay = -1 19 | self.application().view.add_is_view_visible_listener('Detail', self._detail_view_visibility_changed) 20 | self._register_timer_callback(self._on_timer) 21 | return None 22 | 23 | def disconnect(self): 24 | self._unregister_timer_callback(self._on_timer) 25 | self.application().view.remove_is_view_visible_listener('Detail', self._detail_view_visibility_changed) 26 | if self._device_clip_toggle_button != None: 27 | self._device_clip_toggle_button.remove_value_listener(self._device_clip_toggle_value) 28 | self._device_clip_toggle_button = None 29 | if self._detail_toggle_button != None: 30 | self._detail_toggle_button.remove_value_listener(self._detail_toggle_value) 31 | self._detail_toggle_button = None 32 | if self._left_button != None: 33 | self._left_button.remove_value_listener(self._nav_value) 34 | self._left_button = None 35 | if self._right_button != None: 36 | self._right_button.remove_value_listener(self._nav_value) 37 | self._right_button = None 38 | if self._shift_button != None: 39 | self._shift_button.remove_value_listener(self._shift_value) 40 | self._shift_button = None 41 | return None 42 | 43 | def set_device_clip_toggle_button(self, button): 44 | if not(button == None or isinstance(button, ButtonElement)): 45 | isinstance(button, ButtonElement) 46 | raise AssertionError 47 | if self._device_clip_toggle_button != button: 48 | if self._device_clip_toggle_button != None: 49 | self._device_clip_toggle_button.remove_value_listener(self._device_clip_toggle_value) 50 | self._device_clip_toggle_button = button 51 | if self._device_clip_toggle_button != None: 52 | self._device_clip_toggle_button.add_value_listener(self._device_clip_toggle_value) 53 | #self._rebuild_callback() 54 | self.update() 55 | return None 56 | 57 | def set_detail_toggle_button(self, button): 58 | if not(button == None or isinstance(button, ButtonElement)): 59 | isinstance(button, ButtonElement) 60 | raise AssertionError 61 | if self._detail_toggle_button != button: 62 | if self._detail_toggle_button != None: 63 | self._detail_toggle_button.remove_value_listener(self._detail_toggle_value) 64 | self._detail_toggle_button = button 65 | if self._detail_toggle_button != None: 66 | self._detail_toggle_button.add_value_listener(self._detail_toggle_value) 67 | #self._rebuild_callback() 68 | self.update() 69 | return None 70 | 71 | def set_device_nav_buttons(self, left_button, right_button): 72 | if not(left_button == None or isinstance(left_button, ButtonElement)): 73 | isinstance(left_button, ButtonElement) 74 | raise AssertionError 75 | if not(right_button == None or isinstance(right_button, ButtonElement)): 76 | isinstance(right_button, ButtonElement) 77 | raise AssertionError 78 | identify_sender = True 79 | if self._left_button != None: 80 | self._left_button.remove_value_listener(self._nav_value) 81 | self._left_button = left_button 82 | if self._left_button != None: 83 | self._left_button.add_value_listener(self._nav_value, identify_sender) 84 | if self._right_button != None: 85 | self._right_button.remove_value_listener(self._nav_value) 86 | self._right_button = right_button 87 | if self._right_button != None: 88 | self._right_button.add_value_listener(self._nav_value, identify_sender) 89 | #self._rebuild_callback() 90 | self.update() 91 | return None 92 | 93 | def set_shift_button(self, button): 94 | if not(button == None or isinstance(button, ButtonElement) and button.is_momentary()): 95 | isinstance(button, ButtonElement) 96 | raise AssertionError 97 | if self._shift_button != button: 98 | if self._shift_button != None: 99 | self._shift_button.remove_value_listener(self._shift_value) 100 | self._shift_button = button 101 | if self._shift_button != None: 102 | self._shift_button.add_value_listener(self._shift_value) 103 | #self._rebuild_callback() 104 | self.update() 105 | return None 106 | 107 | def on_enabled_changed(self): 108 | self.update() 109 | 110 | def update(self): 111 | if self.is_enabled(): 112 | self.is_enabled() 113 | if not self._shift_pressed: 114 | self._shift_pressed 115 | if self._left_button != None: 116 | self._left_button.turn_off() 117 | if self._right_button != None: 118 | self._right_button.turn_off() 119 | if self._device_clip_toggle_button != None: 120 | self._device_clip_toggle_button.turn_off() 121 | self._detail_view_visibility_changed() 122 | else: 123 | self._shift_pressed 124 | else: 125 | self.is_enabled() 126 | return None 127 | 128 | def _detail_view_visibility_changed(self): 129 | if self.is_enabled() and not self._shift_pressed and self._detail_toggle_button != None: 130 | if self.application().view.is_view_visible('Detail'): 131 | self.application().view.is_view_visible('Detail') 132 | self._detail_toggle_button.turn_on() 133 | else: 134 | self.application().view.is_view_visible('Detail') 135 | self._detail_toggle_button.turn_off() 136 | else: 137 | self.is_enabled() 138 | return None 139 | 140 | def _device_clip_toggle_value(self, value): 141 | if not self._device_clip_toggle_button != None: 142 | raise AssertionError 143 | if not value in range(128): 144 | raise AssertionError 145 | if self.is_enabled() and not self._shift_pressed: 146 | not self._shift_pressed 147 | button_is_momentary = self._device_clip_toggle_button.is_momentary() 148 | if not button_is_momentary or value != 0: 149 | not button_is_momentary 150 | if not self.application().view.is_view_visible('Detail'): 151 | self.application().view.is_view_visible('Detail') 152 | self.application().view.show_view('Detail') 153 | else: 154 | self.application().view.is_view_visible('Detail') 155 | if not self.application().view.is_view_visible('Detail/DeviceChain'): 156 | self.application().view.is_view_visible('Detail/DeviceChain') 157 | self.application().view.show_view('Detail/DeviceChain') 158 | else: 159 | self.application().view.is_view_visible('Detail/DeviceChain') 160 | self.application().view.show_view('Detail/Clip') 161 | if button_is_momentary and value != 0: 162 | self._show_playing_clip_ticks_delay = SHOW_PLAYING_CLIP_DELAY 163 | else: 164 | button_is_momentary 165 | self._show_playing_clip_ticks_delay = -1 166 | else: 167 | self.is_enabled() 168 | return None 169 | 170 | 171 | def _detail_toggle_value(self, value): 172 | assert (self._detail_toggle_button != None) 173 | assert (value in range(128)) 174 | if (self.is_enabled() and (not self._shift_pressed)): 175 | if ((not self._detail_toggle_button.is_momentary()) or (value != 0)): 176 | if (not self.application().view.is_view_visible('Detail')): 177 | self.application().view.show_view('Detail') 178 | else: 179 | self.application().view.hide_view('Detail') 180 | 181 | 182 | def _shift_value(self, value): 183 | if not self._shift_button != None: 184 | raise AssertionError 185 | if not value in range(128): 186 | raise AssertionError 187 | self._shift_pressed = value != 0 188 | self.update() 189 | return None 190 | 191 | def _nav_value(self, value, sender): 192 | assert ((sender != None) and (sender in (self._left_button, 193 | self._right_button))) 194 | if (self.is_enabled() and (not self._shift_pressed)): 195 | if ((not sender.is_momentary()) or (value != 0)): 196 | modifier_pressed = True 197 | if ((not self.application().view.is_view_visible('Detail')) or (not self.application().view.is_view_visible('Detail/DeviceChain'))): 198 | self.application().view.show_view('Detail') 199 | self.application().view.show_view('Detail/DeviceChain') 200 | else: 201 | direction = Live.Application.Application.View.NavDirection.left 202 | if (sender == self._right_button): 203 | direction = Live.Application.Application.View.NavDirection.right 204 | self.application().view.scroll_view(direction, 'Detail/DeviceChain', (not modifier_pressed)) 205 | 206 | def _on_timer(self): 207 | if (self.is_enabled() and (not self._shift_pressed)): 208 | if (self._show_playing_clip_ticks_delay > -1): 209 | if (self._show_playing_clip_ticks_delay == 0): 210 | song = self.song() 211 | playing_slot_index = song.view.selected_track.playing_slot_index 212 | if (playing_slot_index > -1): 213 | song.view.selected_scene = song.scenes[playing_slot_index] 214 | if song.view.highlighted_clip_slot.has_clip: 215 | self.application().view.show_view('Detail/Clip') 216 | self._show_playing_clip_ticks_delay -= 1 -------------------------------------------------------------------------------- /APCAdvanced/ShiftableTransportComponent.py: -------------------------------------------------------------------------------- 1 | # partial --== Decompile ==-- with fixes 2 | import Live 3 | from _Framework.TransportComponent import TransportComponent 4 | from _Framework.ButtonElement import ButtonElement 5 | from _Framework.EncoderElement import EncoderElement #added 6 | 7 | class ShiftableTransportComponent(TransportComponent): 8 | __doc__ = ' TransportComponent that only uses certain buttons if a shift button is pressed ' 9 | def __init__(self): 10 | TransportComponent.__init__(self) 11 | self._shift_button = None 12 | self._pedal = None 13 | self._shift_pressed = False 14 | self._pedal_pressed = False #added 15 | self._quant_toggle_button = None 16 | self._last_quant_value = Live.Song.RecordingQuantization.rec_q_eight 17 | self.song().add_midi_recording_quantization_listener(self._on_quantisation_changed) 18 | self._on_quantisation_changed() 19 | self._undo_button = None #added from OpenLabs SpecialTransportComponent script 20 | self._redo_button = None #added from OpenLabs SpecialTransportComponent script 21 | self._bts_button = None #added from OpenLabs SpecialTransportComponent script 22 | self._tempo_encoder_control = None #new addition 23 | return None 24 | 25 | def disconnect(self): 26 | TransportComponent.disconnect(self) 27 | if self._shift_button != None: 28 | self._shift_button.remove_value_listener(self._shift_value) 29 | self._shift_button = None 30 | if self._quant_toggle_button != None: 31 | self._quant_toggle_button.remove_value_listener(self._quant_toggle_value) 32 | self._quant_toggle_button = None 33 | self.song().remove_midi_recording_quantization_listener(self._on_quantisation_changed) 34 | if (self._undo_button != None): #added from OpenLabs SpecialTransportComponent script 35 | self._undo_button.remove_value_listener(self._undo_value) 36 | self._undo_button = None 37 | if (self._redo_button != None): #added from OpenLabs SpecialTransportComponent script 38 | self._redo_button.remove_value_listener(self._redo_value) 39 | self._redo_button = None 40 | if (self._bts_button != None): #added from OpenLabs SpecialTransportComponent script 41 | self._bts_button.remove_value_listener(self._bts_value) 42 | self._bts_button = None 43 | if (self._tempo_encoder_control != None): #new addition 44 | self._tempo_encoder_control.remove_value_listener(self._tempo_encoder_value) 45 | self._tempo_encoder_control = None 46 | return None 47 | 48 | def set_shift_button(self, button): 49 | if not(button == None or isinstance(button, ButtonElement) and button.is_momentary()): 50 | isinstance(button, ButtonElement) 51 | raise AssertionError 52 | if self._shift_button != button: 53 | if self._shift_button != None: 54 | self._shift_button.remove_value_listener(self._shift_value) 55 | self._shift_button = button 56 | if self._shift_button != None: 57 | self._shift_button.add_value_listener(self._shift_value) 58 | #self._rebuild_callback() 59 | self.update() 60 | return None 61 | 62 | 63 | def set_pedal(self, pedal): 64 | if not(pedal == None or isinstance(pedal, ButtonElement) and pedal.is_momentary()): 65 | isinstance(pedal, ButtonElement) 66 | raise AssertionError 67 | if self._pedal != pedal: 68 | if self._pedal != None: 69 | self._pedal.remove_value_listener(self._pedal_value) 70 | self._pedal = pedal 71 | if self._pedal != None: 72 | self._pedal.add_value_listener(self._pedal_value) 73 | #self._rebuild_callback() 74 | self.update() 75 | return None 76 | 77 | def set_quant_toggle_button(self, button): 78 | if not(button == None or isinstance(button, ButtonElement) and button.is_momentary()): 79 | isinstance(button, ButtonElement) 80 | raise AssertionError 81 | if self._quant_toggle_button != button: 82 | if self._quant_toggle_button != None: 83 | self._quant_toggle_button.remove_value_listener(self._quant_toggle_value) 84 | self._quant_toggle_button = button 85 | if self._quant_toggle_button != None: 86 | self._quant_toggle_button.add_value_listener(self._quant_toggle_value) 87 | #self._rebuild_callback() 88 | self.update() 89 | return None 90 | 91 | def update(self): 92 | self._on_metronome_changed() 93 | self._on_overdub_changed() 94 | self._on_quantisation_changed() 95 | self._on_nudge_up_changed() #added 96 | self._on_nudge_down_changed #added 97 | 98 | def _shift_value(self, value): 99 | if not self._shift_button != None: 100 | raise AssertionError 101 | if not value in range(128): 102 | raise AssertionError 103 | self._shift_pressed = value != 0 104 | if self.is_enabled(): 105 | self.is_enabled() 106 | self.update() 107 | else: 108 | self.is_enabled() 109 | return None 110 | 111 | def _pedal_value(self, value): 112 | if not self._pedal != None: 113 | raise AssertionError 114 | if not value in range(128): 115 | raise AssertionError 116 | 117 | if value == 127: 118 | self._pedal_pressed = False 119 | elif value == 0: 120 | self._pedal_pressed = True 121 | 122 | if self.is_enabled(): 123 | self.is_enabled() 124 | self.update() 125 | else: 126 | self.is_enabled() 127 | return None 128 | 129 | def _metronome_value(self, value): 130 | if not self._shift_pressed: 131 | ##if self._shift_pressed: 132 | TransportComponent._metronome_value(self, value) 133 | 134 | 135 | def _overdub_value(self, value): 136 | if not self._shift_pressed: 137 | TransportComponent._overdub_value(self, value) 138 | 139 | 140 | def _nudge_up_value(self, value): #added 141 | if not self._shift_pressed: 142 | TransportComponent._nudge_up_value(self, value) 143 | 144 | 145 | def _nudge_down_value(self, value): #added 146 | if not self._shift_pressed: 147 | TransportComponent._nudge_down_value(self, value) 148 | 149 | 150 | def _tap_tempo_value(self, value): # Added as Shift + Tap Tempo 151 | if not self._shift_pressed: 152 | #if self._shift_pressed: 153 | TransportComponent._tap_tempo_value(self, value) 154 | 155 | 156 | def _quant_toggle_value(self, value): 157 | assert (self._quant_toggle_button != None) 158 | assert (value in range(128)) 159 | assert (self._last_quant_value != Live.Song.RecordingQuantization.rec_q_no_q) 160 | if (self.is_enabled() and (not self._shift_pressed)): 161 | if ((value != 0) or (not self._quant_toggle_button.is_momentary())): 162 | quant_value = self.song().midi_recording_quantization 163 | if (quant_value != Live.Song.RecordingQuantization.rec_q_no_q): 164 | self._last_quant_value = quant_value 165 | self.song().midi_recording_quantization = Live.Song.RecordingQuantization.rec_q_no_q 166 | else: 167 | self.song().midi_recording_quantization = self._last_quant_value 168 | 169 | 170 | def _on_metronome_changed(self): 171 | if not self._shift_pressed: 172 | #if self._shift_pressed: 173 | TransportComponent._on_metronome_changed(self) 174 | 175 | 176 | def _on_overdub_changed(self): 177 | if not self._shift_pressed: 178 | TransportComponent._on_overdub_changed(self) 179 | 180 | 181 | def _on_nudge_up_changed(self): #added 182 | if not self._shift_pressed: 183 | TransportComponent._on_nudge_up_changed(self) 184 | 185 | 186 | def _on_nudge_down_changed(self): #added 187 | if not self._shift_pressed: 188 | TransportComponent._on_nudge_down_changed(self) 189 | 190 | 191 | def _on_quantisation_changed(self): 192 | if self.is_enabled(): 193 | quant_value = self.song().midi_recording_quantization 194 | quant_on = (quant_value != Live.Song.RecordingQuantization.rec_q_no_q) 195 | if quant_on: 196 | self._last_quant_value = quant_value 197 | if ((not self._shift_pressed) and (self._quant_toggle_button != None)): 198 | if quant_on: 199 | self._quant_toggle_button.turn_on() 200 | else: 201 | self._quant_toggle_button.turn_off() 202 | 203 | """ from OpenLabs module SpecialTransportComponent """ 204 | 205 | def set_undo_button(self, undo_button): 206 | assert isinstance(undo_button, (ButtonElement, 207 | type(None))) 208 | if (undo_button != self._undo_button): 209 | if (self._undo_button != None): 210 | self._undo_button.remove_value_listener(self._undo_value) 211 | self._undo_button = undo_button 212 | if (self._undo_button != None): 213 | self._undo_button.add_value_listener(self._undo_value) 214 | self.update() 215 | 216 | 217 | 218 | def set_redo_button(self, redo_button): 219 | assert isinstance(redo_button, (ButtonElement, 220 | type(None))) 221 | if (redo_button != self._redo_button): 222 | if (self._redo_button != None): 223 | self._redo_button.remove_value_listener(self._redo_value) 224 | self._redo_button = redo_button 225 | if (self._redo_button != None): 226 | self._redo_button.add_value_listener(self._redo_value) 227 | self.update() 228 | 229 | 230 | def set_bts_button(self, bts_button): #"back to start" button 231 | assert isinstance(bts_button, (ButtonElement, 232 | type(None))) 233 | if (bts_button != self._bts_button): 234 | if (self._bts_button != None): 235 | self._bts_button.remove_value_listener(self._bts_value) 236 | self._bts_button = bts_button 237 | if (self._bts_button != None): 238 | self._bts_button.add_value_listener(self._bts_value) 239 | self.update() 240 | 241 | 242 | def _undo_value(self, value): 243 | if self._shift_pressed: #added 244 | assert (self._undo_button != None) 245 | assert (value in range(128)) 246 | if self.is_enabled(): 247 | if ((value != 0) or (not self._undo_button.is_momentary())): 248 | if self.song().can_undo: 249 | self.song().undo() 250 | 251 | 252 | def _redo_value(self, value): 253 | if self._shift_pressed: #added 254 | assert (self._redo_button != None) 255 | assert (value in range(128)) 256 | if self.is_enabled(): 257 | if ((value != 0) or (not self._redo_button.is_momentary())): 258 | if self.song().can_redo: 259 | self.song().redo() 260 | 261 | 262 | def _bts_value(self, value): 263 | assert (self._bts_button != None) 264 | assert (value in range(128)) 265 | if self.is_enabled(): 266 | if ((value != 0) or (not self._bts_button.is_momentary())): 267 | self.song().current_song_time = 0.0 268 | 269 | 270 | def _tempo_encoder_value(self, value): 271 | ##if not self._shift_pressed: 272 | if self._shift_pressed and not (self._pedal_pressed == True): 273 | assert (self._tempo_encoder_control != None) 274 | assert (value in range(128)) 275 | backwards = (value >= 64) 276 | step = 0.1 #step = 1.0 #reduce this for finer control; 1.0 is 1 bpm 277 | if backwards: 278 | amount = (value - 128) 279 | else: 280 | amount = value 281 | tempo = max(20, min(999, (self.song().tempo + (amount * step)))) 282 | self.song().tempo = tempo 283 | 284 | 285 | 286 | def set_tempo_encoder(self, control): 287 | assert ((control == None) or (isinstance(control, EncoderElement) and (control.message_map_mode() is Live.MidiMap.MapMode.relative_two_compliment))) 288 | if (self._tempo_encoder_control != None): 289 | self._tempo_encoder_control.remove_value_listener(self._tempo_encoder_value) 290 | self._tempo_encoder_control = control 291 | if (self._tempo_encoder_control != None): 292 | self._tempo_encoder_control.add_value_listener(self._tempo_encoder_value) 293 | self.update() 294 | -------------------------------------------------------------------------------- /APCAdvanced/APC40plus22.py: -------------------------------------------------------------------------------- 1 | # http://remotescripts.blogspot.com 2 | 3 | import Live 4 | from APC import APC 5 | from _Framework.ControlSurface import ControlSurface 6 | from _Framework.InputControlElement import * 7 | from _Framework.SliderElement import SliderElement 8 | from _Framework.ButtonElement import ButtonElement 9 | from _Framework.EncoderElement import EncoderElement 10 | from _Framework.ButtonMatrixElement import ButtonMatrixElement 11 | from _Framework.MixerComponent import MixerComponent 12 | from _Framework.ClipSlotComponent import ClipSlotComponent 13 | from _Framework.ChannelStripComponent import ChannelStripComponent 14 | from _Framework.SceneComponent import SceneComponent 15 | from _Framework.SessionZoomingComponent import SessionZoomingComponent 16 | from _Framework.ChannelTranslationSelector import ChannelTranslationSelector 17 | from EncoderMixerModeSelectorComponent import EncoderMixerModeSelectorComponent 18 | from RingedEncoderElement import RingedEncoderElement 19 | from DetailViewControllerComponent import DetailViewControllerComponent 20 | from ShiftableDeviceComponent import ShiftableDeviceComponent 21 | from ShiftableTransportComponent import ShiftableTransportComponent 22 | from ShiftableTranslatorComponent import ShiftableTranslatorComponent 23 | from PedaledSessionComponent import PedaledSessionComponent 24 | from SpecialMixerComponent import SpecialMixerComponent 25 | 26 | # Additional imports from APC20.py: 27 | from ShiftableSelectorComponent import ShiftableSelectorComponent 28 | from SliderModesComponent import SliderModesComponent 29 | 30 | # Import added from Launchpad scripts - needed for Note Mode: 31 | from ConfigurableButtonElement import ConfigurableButtonElement 32 | 33 | # New components 34 | from MatrixModesComponent import MatrixModesComponent 35 | from EncoderUserModesComponent import EncoderUserModesComponent 36 | from ShiftableEncoderSelectorComponent import ShiftableEncoderSelectorComponent 37 | from EncoderEQComponent import EncoderEQComponent 38 | from EncoderDeviceComponent import EncoderDeviceComponent 39 | 40 | #from ShiftableLooperComponent import ShiftableLooperComponent 41 | from LooperComponent import LooperComponent 42 | from RepeatComponent import RepeatComponent 43 | from VUMeters import VUMeters 44 | 45 | 46 | class APC40plus22(APC): 47 | __doc__ = " Script for Akai's APC40 Controller with extra features added " 48 | def __init__(self, c_instance): 49 | self._c_instance = c_instance 50 | self._shift_modes = None #added from APC20 script 51 | self._encoder_modes = None #added 52 | self._slider_modes = None #added 53 | APC.__init__(self, c_instance) 54 | self.show_message("APC40_22 script loaded") 55 | 56 | # Disabling the scene launch buttons and assigning them to the first 5 repeats on Master 57 | self._device_buttons = [] 58 | self.setup_device_buttons() 59 | 60 | def disconnect(self): #this is from the APC20 script 61 | for button in self._device_buttons: 62 | button.remove_value_listener(self._device_toggle) 63 | self._device_buttons = None 64 | self._shift_modes = None 65 | self._encoder_modes = None 66 | self._slider_modes = None 67 | APC.disconnect(self) 68 | 69 | def setup_device_buttons(self): 70 | repeat = RepeatComponent(self) 71 | repeat.set_shift_button(self._shift_button) 72 | 73 | def _setup_session_control(self): 74 | is_momentary = True 75 | self._shift_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 98) 76 | right_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 96) 77 | left_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 97) 78 | up_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 94) 79 | down_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 95) 80 | right_button.name = 'Bank_Select_Right_Button' 81 | left_button.name = 'Bank_Select_Left_Button' 82 | up_button.name = 'Bank_Select_Up_Button' 83 | down_button.name = 'Bank_Select_Down_Button' 84 | self._session = PedaledSessionComponent(8, 5) 85 | self._session.name = 'Session_Control' 86 | self._session.set_track_bank_buttons(right_button, left_button) 87 | self._session.set_scene_bank_buttons(down_button, up_button) 88 | self._matrix = ButtonMatrixElement() #was: matrix = ButtonMatrixElement() 89 | self._matrix.name = 'Button_Matrix' #was: matrix.name = 'Button_Matrix' 90 | scene_launch_buttons = [ ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, (index + 82)) for index in range(5) ] 91 | #self._track_stop_buttons = [ ButtonElement(is_momentary, MIDI_NOTE_TYPE, index, 52) for index in range(8) ] 92 | self._track_stop_buttons = [ ConfigurableButtonElement(is_momentary, MIDI_NOTE_TYPE, index, 52) for index in range(8) ] 93 | for index in range(len(scene_launch_buttons)): 94 | scene_launch_buttons[index].name = 'Scene_'+ str(index) + '_Launch_Button' 95 | for index in range(len(self._track_stop_buttons)): 96 | self._track_stop_buttons[index].name = 'Track_' + str(index) + '_Stop_Button' 97 | stop_all_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 81) 98 | stop_all_button.name = 'Stop_All_Clips_Button' 99 | self._session.set_stop_all_clips_button(stop_all_button) 100 | self._session.set_stop_track_clip_buttons(tuple(self._track_stop_buttons)) 101 | self._session.set_stop_track_clip_value(2) 102 | 103 | self._button_rows = [] 104 | for scene_index in range(5): 105 | scene = self._session.scene(scene_index) 106 | scene.name = 'Scene_' + str(scene_index) 107 | button_row = [] 108 | scene.set_launch_button(scene_launch_buttons[scene_index]) 109 | scene.set_triggered_value(2) 110 | for track_index in range(8): 111 | #button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, track_index, (scene_index + 53)) 112 | button = ConfigurableButtonElement(is_momentary, MIDI_NOTE_TYPE, track_index, (scene_index + 53)) #use Launchpad configurable button instead 113 | button.name = str(track_index) + '_Clip_' + str(scene_index) + '_Button' 114 | button_row.append(button) 115 | clip_slot = scene.clip_slot(track_index) 116 | clip_slot.name = str(track_index) + '_Clip_Slot_' + str(scene_index) 117 | clip_slot.set_triggered_to_play_value(2) 118 | clip_slot.set_triggered_to_record_value(4) 119 | clip_slot.set_stopped_value(3) 120 | clip_slot.set_started_value(1) 121 | clip_slot.set_recording_value(5) 122 | clip_slot.set_launch_button(button) 123 | self._matrix.add_row(tuple(button_row)) #matrix.add_row(tuple(button_row)) 124 | self._button_rows.append(button_row) 125 | 126 | # Removing the launch selected clip footpedal option 127 | #self._session.set_slot_launch_button(ButtonElement(is_momentary, MIDI_CC_TYPE, 0, 67)) 128 | 129 | 130 | self._session.selected_scene().name = 'Selected_Scene' 131 | self._session.selected_scene().set_launch_button(ButtonElement(is_momentary, MIDI_CC_TYPE, 0, 64)) 132 | self._session_zoom = SessionZoomingComponent(self._session) #use APC20 Zooming instead 133 | self._session_zoom.name = 'Session_Overview' 134 | self._session_zoom.set_button_matrix(self._matrix) #was: self._session_zoom.set_button_matrix(matrix) 135 | self._session_zoom.set_zoom_button(self._shift_button) #set in MatrixModesComponent instead 136 | self._session_zoom.set_nav_buttons(up_button, down_button, left_button, right_button) 137 | self._session_zoom.set_scene_bank_buttons(tuple(scene_launch_buttons)) 138 | self._session_zoom.set_stopped_value(3) 139 | self._session_zoom.set_selected_value(5) 140 | return None 141 | 142 | def _setup_mixer_control(self): 143 | is_momentary = True 144 | self._mixer = SpecialMixerComponent(self, 8) #added self for parent 145 | self._mixer.name = 'Mixer' 146 | self._mixer.master_strip().name = 'Master_Channel_Strip' 147 | master_select_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 80) 148 | master_select_button.name = 'Master_Select_Button' 149 | self._mixer.master_strip().set_select_button(master_select_button) #set in ShiftableSelectorComponent instead if used for Note Mode 150 | self._mixer.selected_strip().name = 'Selected_Channel_Strip' 151 | select_buttons = [] #added 152 | arm_buttons = [] #added 153 | sliders = [] #added 154 | for track in range(8): 155 | strip = self._mixer.channel_strip(track) 156 | strip.name = 'Channel_Strip_' + str(track) 157 | #volume_control = SliderElement(MIDI_CC_TYPE, track, 7) #set in ShiftableSelectorComponent instead 158 | #arm_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, track, 48) #see below 159 | solo_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, track, 49) 160 | solo_button.name = str(track) + '_Solo_Button' 161 | strip.set_solo_button(solo_button) 162 | 163 | if track < 4: 164 | mute_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, track, 50) 165 | mute_button.name = str(track) + '_Mute_Button' 166 | strip.set_mute_button(mute_button) 167 | strip.set_invert_mute_feedback(True) 168 | 169 | strip.set_shift_button(self._shift_button) 170 | select_buttons.append(ButtonElement(is_momentary, MIDI_NOTE_TYPE, track, 51)) #added 171 | select_buttons[-1].name = str(track) + '_Select_Button' #added 172 | #strip.set_select_button(select_buttons[-1]) #added 173 | arm_buttons.append(ButtonElement(is_momentary, MIDI_NOTE_TYPE, track, 48)) #added 174 | arm_buttons[-1].name = str(track) + '_Arm_Button' #added 175 | sliders.append(SliderElement(MIDI_CC_TYPE, track, 7)) #added 176 | sliders[-1].name = str(track) + '_Volume_Control' #added 177 | 178 | self._crossfader = SliderElement(MIDI_CC_TYPE, 0, 15) 179 | master_volume_control = SliderElement(MIDI_CC_TYPE, 0, 14) 180 | self._prehear_control = EncoderElement(MIDI_CC_TYPE, 0, 47, Live.MidiMap.MapMode.relative_two_compliment) 181 | self._crossfader.name = 'Crossfader' #not used in APC20 182 | master_volume_control.name = 'Master_Volume_Control' 183 | self._prehear_control.name = 'Prehear_Volume_Control' 184 | self._mixer.set_shift_button(self._shift_button) #added for shifting prehear 185 | self._mixer.set_crossfader_control(self._crossfader) #not used in APC20 186 | self._mixer.set_prehear_volume_control(self._prehear_control) #functionality overridden in SpecialMixerComponent 187 | self._mixer.master_strip().set_volume_control(master_volume_control) 188 | self._slider_modes = SliderModesComponent(self._mixer, tuple(sliders)) #added from APC20 script 189 | self._slider_modes.name = 'Slider_Modes' #added from APC20 script 190 | matrix_modes = MatrixModesComponent(self._matrix, self._session, self._session_zoom, tuple(self._track_stop_buttons), self) #added new 191 | matrix_modes.name = 'Matrix_Modes' #added new 192 | # Original method args for ShiftableSelectorComponent: (self, select_buttons, master_button, arm_buttons, matrix, session, zooming, mixer, transport, slider_modes, mode_callback) 193 | #self._shift_modes = ShiftableSelectorComponent(tuple(select_buttons), master_select_button, tuple(arm_buttons), self._matrix, self._session, self._session_zoom, self._mixer, transport, slider_modes, self._send_introduction_message) 194 | self._shift_modes = ShiftableSelectorComponent(self, tuple(select_buttons), master_select_button, tuple(arm_buttons), self._matrix, self._session, self._session_zoom, self._mixer, self._slider_modes, matrix_modes) #, self._send_introduction_message) #also added self for _parent 195 | self._shift_modes.name = 'Shift_Modes' 196 | self._shift_modes.set_mode_toggle(self._shift_button) 197 | 198 | def _setup_custom_components(self): 199 | self._setup_looper_control() 200 | self._setup_device_and_transport_control() 201 | self._setup_global_control() 202 | 203 | def _setup_looper_control(self): 204 | is_momentary = True 205 | #pedal = ButtonElement(is_momentary, MIDI_CC_TYPE, 0, 67) 206 | loop_on = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 4, 50) 207 | loop_start = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 5, 50) 208 | halve = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 6, 50) 209 | double = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 7, 50) 210 | looper = LooperComponent(self) 211 | looper.set_shift_button(self._shift_button) 212 | looper.set_loop_toggle_button(loop_on) 213 | looper.set_loop_start_button(loop_start) 214 | looper.set_loop_double_button(double) 215 | looper.set_loop_halve_button(halve) 216 | 217 | def _setup_device_and_transport_control(self): 218 | is_momentary = True 219 | device_bank_buttons = [] 220 | device_param_controls = [] 221 | bank_button_labels = ('Clip_Track_Button', 'Device_On_Off_Button', 'Previous_Device_Button', 'Next_Device_Button', 'Detail_View_Button', 'Rec_Quantization_Button', 'Midi_Overdub_Button', 'Device_Lock_Button', 'Metronome_Button') 222 | for index in range(8): 223 | device_bank_buttons.append(ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 58 + index)) 224 | device_bank_buttons[-1].name = bank_button_labels[index] 225 | ring_mode_button = ButtonElement(not is_momentary, MIDI_CC_TYPE, 0, 24 + index) 226 | ringed_encoder = RingedEncoderElement(MIDI_CC_TYPE, 0, 16 + index, Live.MidiMap.MapMode.absolute) 227 | ringed_encoder.set_ring_mode_button(ring_mode_button) 228 | ringed_encoder.set_feedback_delay(-1) #added from Axiom DirectLink example 229 | ringed_encoder.name = 'Device_Control_' + str(index) 230 | ring_mode_button.name = ringed_encoder.name + '_Ring_Mode_Button' 231 | device_param_controls.append(ringed_encoder) 232 | self._device = ShiftableDeviceComponent() 233 | self._device.name = 'Device_Component' 234 | self._device.set_bank_buttons(tuple(device_bank_buttons)) 235 | self._device.set_shift_button(self._shift_button) 236 | self._device.set_parameter_controls(tuple(device_param_controls)) 237 | self._device.set_on_off_button(device_bank_buttons[1]) 238 | self.set_device_component(self._device) 239 | detail_view_toggler = DetailViewControllerComponent() 240 | detail_view_toggler.name = 'Detail_View_Control' 241 | detail_view_toggler.set_shift_button(self._shift_button) 242 | detail_view_toggler.set_device_clip_toggle_button(device_bank_buttons[0]) 243 | detail_view_toggler.set_detail_toggle_button(device_bank_buttons[4]) 244 | detail_view_toggler.set_device_nav_buttons(device_bank_buttons[2], device_bank_buttons[3]) 245 | 246 | 247 | # VU Meters 248 | vu = VUMeters(self) 249 | 250 | transport = ShiftableTransportComponent() 251 | transport.name = 'Transport' 252 | play_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 91) 253 | stop_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 92) 254 | record_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 93) 255 | nudge_up_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 100) 256 | nudge_down_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 101) 257 | tap_tempo_button = ButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 99) 258 | play_button.name = 'Play_Button' 259 | stop_button.name = 'Stop_Button' 260 | record_button.name = 'Record_Button' 261 | nudge_up_button.name = 'Nudge_Up_Button' 262 | nudge_down_button.name = 'Nudge_Down_Button' 263 | tap_tempo_button.name = 'Tap_Tempo_Button' 264 | transport.set_shift_button(self._shift_button) 265 | transport.set_play_button(play_button) 266 | transport.set_stop_button(stop_button) 267 | transport.set_record_button(record_button) 268 | transport.set_nudge_buttons(nudge_up_button, nudge_down_button) 269 | transport.set_undo_button(nudge_down_button) #shifted nudge 270 | transport.set_redo_button(nudge_up_button) #shifted nudge 271 | transport.set_tap_tempo_button(tap_tempo_button) 272 | self._device.set_lock_button(tap_tempo_button) #shifted tap tempo 273 | transport.set_quant_toggle_button(device_bank_buttons[5]) 274 | transport.set_overdub_button(device_bank_buttons[6]) 275 | transport.set_metronome_button(device_bank_buttons[7]) 276 | transport.set_tempo_encoder(self._prehear_control) #shifted prehear 277 | bank_button_translator = ShiftableTranslatorComponent() 278 | bank_button_translator.set_controls_to_translate(tuple(device_bank_buttons)) 279 | bank_button_translator.set_shift_button(self._shift_button) 280 | 281 | 282 | def _setup_global_control(self): 283 | is_momentary = True 284 | self._global_bank_buttons = [] 285 | self._global_param_controls = [] 286 | for index in range(8): 287 | ring_button = ButtonElement(not is_momentary, MIDI_CC_TYPE, 0, 56 + index) 288 | ringed_encoder = RingedEncoderElement(MIDI_CC_TYPE, 0, 48 + index, Live.MidiMap.MapMode.absolute) 289 | ringed_encoder.name = 'Track_Control_' + str(index) 290 | ringed_encoder.set_feedback_delay(-1) 291 | ring_button.name = ringed_encoder.name + '_Ring_Mode_Button' 292 | ringed_encoder.set_ring_mode_button(ring_button) 293 | self._global_param_controls.append(ringed_encoder) 294 | self._global_bank_buttons = [] 295 | global_bank_labels = ('Pan_Button', 'Send_A_Button', 'Send_B_Button', 'Send_C_Button') 296 | for index in range(4): 297 | self._global_bank_buttons.append(ConfigurableButtonElement(is_momentary, MIDI_NOTE_TYPE, 0, 87 + index))#(not is_momentary, MIDI_NOTE_TYPE, 0, 87 + index)) 298 | self._global_bank_buttons[-1].name = global_bank_labels[index] 299 | self._encoder_modes = EncoderMixerModeSelectorComponent(self._mixer) 300 | self._encoder_modes.name = 'Track_Control_Modes' 301 | #self._encoder_modes.set_modes_buttons(self._global_bank_buttons) # set in ShiftableEncoderSelectorComponent 302 | self._encoder_modes.set_controls(tuple(self._global_param_controls)) 303 | #self._encoder_device_modes = EncoderDeviceModeSelectorComponent(self._mixer, self._device) #new 304 | self._encoder_device_modes = EncoderDeviceComponent(self._mixer, self._device, self) 305 | self._encoder_device_modes.name = 'Alt_Device_Control_Modes' #new 306 | self._encoder_eq_modes = EncoderEQComponent(self._mixer, self)#EncoderEQModeSelectorComponent(self._mixer) #new 307 | self._encoder_eq_modes.name = 'EQ_Control_Modes' #new 308 | global_translation_selector = ChannelTranslationSelector() #translate track encoders to channels 1 through 4, based on button selection (pan = 1, send A = 2, send B = 3, send C = 4) 309 | global_translation_selector.name = 'Global_Translations' 310 | global_translation_selector.set_controls_to_translate(tuple(self._global_param_controls)) 311 | global_translation_selector.set_mode_buttons(tuple(self._global_bank_buttons)) 312 | encoder_user_modes = EncoderUserModesComponent(self, self._encoder_modes, tuple(self._global_param_controls), tuple(self._global_bank_buttons), self._mixer, self._device, self._encoder_device_modes, self._encoder_eq_modes) #self._mixer, tuple(sliders)) #added 313 | encoder_user_modes.name = 'Encoder_User_Modes' #added 314 | self._encoder_shift_modes = ShiftableEncoderSelectorComponent(self, tuple(self._global_bank_buttons), encoder_user_modes, self._encoder_modes, self._encoder_eq_modes, self._encoder_device_modes) #tuple(select_buttons), master_select_button, tuple(arm_buttons), self._matrix, self._session, self._session_zoom, self._mixer, slider_modes, matrix_modes) #, self._send_introduction_message) #also added self for _parent 315 | self._encoder_shift_modes.name = 'Encoder_Shift_Modes' 316 | self._encoder_shift_modes.set_mode_toggle(self._shift_button) 317 | 318 | 319 | def _on_selected_track_changed(self): 320 | ControlSurface._on_selected_track_changed(self) 321 | #self._slider_modes.update() #added to update alternate slider assignments 322 | track = self.song().view.selected_track 323 | device_to_select = track.view.selected_device 324 | if device_to_select == None and len(track.devices) > 0: 325 | device_to_select = track.devices[0] 326 | if device_to_select != None: 327 | self.song().view.select_device(device_to_select) 328 | self._device_component.set_device(device_to_select) 329 | return None 330 | 331 | def _product_model_id_byte(self): 332 | return 115 333 | --------------------------------------------------------------------------------