├── 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.liveLive- 01apcadvanced.xml
- 02dj.xml
- 03dark.xml
descriptionproperties.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 |
--------------------------------------------------------------------------------