├── LICENSE
├── README.md
├── YourControllerName - Live 11
├── MIDI_Map.py
├── SpecialChannelStripComponent.py
├── SpecialMixerComponent.py
├── SpecialSessionComponent.py
├── SpecialTransportComponent.py
├── SpecialViewControllerComponent.py
├── SpecialZoomingComponent.py
├── YourControllerName.py
└── __init__.py
└── YourControllerName
├── MIDI_Map.py
├── SpecialChannelStripComponent.py
├── SpecialMixerComponent.py
├── SpecialSessionComponent.py
├── SpecialTransportComponent.py
├── SpecialViewControllerComponent.py
├── SpecialZoomingComponent.py
├── YourControllerName.py
└── __init__.py
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Laidlaw
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ableton Live Custom MIDI Remote Scripts
2 |
3 | Scripts for creating custom MIDI Remote Scripts for Ableton Live.
4 | Known to be working with Live 9 and 10. I've added a new directory for Live 11 which updates the code to Python 3.
5 | Untested in Live 12.
6 |
7 | ---
8 | ### Background
9 | This is an [in-depth tutorial](https://youtu.be/IgKwcCJsoz4) on how to use these scripts.
10 | When I made the video 5 years ago, I had next to no programming knowledge, and I created it with the intent
11 | that non-programmers would be able to make their own MIDI scripts to interface with Live. 30k views later... it probably
12 | deserves an update. The video has more details about what to modify to get things working, but I'll add more comments
13 | to the code as I figure out what each function does.
14 |
15 | - Most of the code is from these [Behringer FCB1020 scripts](http://remotescripts.blogspot.com) by Hanz Petrov, if I
16 | can find their git or another source to credit them appropriately then I will do so. Let me know here if the site is gone, I have archived it for
17 | posterity.
18 | - For the Push 2, user [jzgdev](https://github.com/jzgdev/Push2UserModeScript) has a working Push 2 User Mode.
19 | - [More information](https://structure-void.com/ableton-live-midi-remote-scripts/) on MIDI remote scripts, the Live API
20 | documentation, Push scripts, and [decompiled scripts](https://github.com/gluon) for more current versions of Live by
21 | Julien Bayle.
22 |
23 | ---
24 | ### Instructions
25 |
26 | - Connect your MIDI device to your computer
27 | - Open Live and navigate to `Preferences → Link MIDI → MIDI`
28 | - Under either the Input or Output press the dropdown menu, note the exact name of your controller (if spaces are present in name, use underscores (e.g. "Arturia_Beatstep"
29 | instead of "Arturia Beatstep").
30 | - Close Live.
31 | - Find and replace all instances in the scripts of `YourControllerName` folder with your controller's name.
32 | - There are three in `__init__.py`
33 | - and nine in `YourControllerName.py`
34 | - Save everything
35 | - Rename the YourControllerName folder and the `YourControllerName.py` with the same name.
36 | - Open `MIDI_Map.py` and inspect what you want to control. Unless stated otherwise, `-1` is the idle/unused value for
37 | each parameter.
38 | - You can use a third-party tool (see: [Downloads](#downloads)), or your device's user manual to figure out what MIDI
39 | channel, note or CC values each control on your device sends.
40 | I'd do this with pen and paper or [draw.io](https://draw.io).
41 | - Once you know your layout, assign the values to the respective parameter in `MIDI_Map.py` and ensure you set
42 | `BUTTONCHANNEL` and `SLIDERCHANNEL` to the same MIDI channel as your buttons and pots/faders.
43 | - Important: you must use the value for decimal number when assigning MIDI notes/buttons, DO NOT
44 | use note values like these C3, D4, A1 etc., they won't work. There are tables to work it out from the note, but the
45 | software is much more efficient and accurate.
46 | - Save and close everything and make a backup of your edited version, Live 9 compiles your scripts on launch, so you
47 | won't be able to make edits after it has done that. You've been warned. Don't let that effort be for nothing.
48 |
49 | MIDI Remote Scripts folder:
50 | - For Windows users: `\ProgramData\Ableton\Live x.x\Resources\MIDI Remote Scripts\`
51 | - For macOS users:
52 | - Locate the Live application in Finder (typically `/Applications/`),
53 | - right click on it and select "Show Package Contents" in the context menu,
54 | - navigate to: `/Contents/App-Resources/MIDI Remote Scripts/`
55 | - If you have multiple versions of Live installed, you will have to copy your scripts to each version individually.
56 | - If you are replacing a pre-existing script with the same device name then make a copy of the original.
57 | Otherwise, you will have to reinstall Live to get the default scripts back.
58 | - Launch Live and head back to `Preferences → Link MIDI → MIDI`
59 | - Under the `Control Surface` section, choose an empty slot and assign your newly created script.
60 | - Set the Input and Output to your MIDI device.
61 | - Under `MIDI Ports` check that `Remote` and `Track' are set to `On` for both the Input and the Output, or to
62 | your preference if you know what you're doing.
63 | - Test it out.
64 | - There is going to be a lot of trial and error here but it's pretty straightforward once you get an input working.
65 |
66 |
67 | ---
68 | ### Troubleshooting:
69 | - Make sure `YourControllerName` is changed everywhere, and you saved the script before placing it in the
70 | `MIDI Remote Scripts` directory.
71 | - Set the `BUTTONCHANNEL` and the `SLIDERCHANNEL` correctly. It won't work until you do that.
72 | - If you have multiple versions of Live installed, you will have to copy your scripts to each version individually.
73 | - Assign one control message (CC/Note) per parameter.
74 | - Before you add to `MIDI Remote Scripts` folder just make sure you have the correct MIDI channels and renamed
75 | YourControllerName in `__init__.py` and `YourControllerName.py`. Don't forget to change the folder and the file names
76 | as well.
77 |
78 |
79 | ---
80 | ### Updates
81 | - Change the size of the track selection box (coloured box in Session view).
82 | - There are now two variables in MIDI_Map.py to modify the size of the box:
83 | - `TSB_X` is the horizontal count i.e. the number of tracks
84 | - `TSB_Y` is the vertical count which represents the number of scenes you want to control.
85 | - For this to work properly you must manually add or remove rows/columns for `SCENELAUNCH` and `CLIPNOTEMAP`
86 | sections in [MIDI_Map.py](https://github.com/laidlaw42/Ableton-Live-MIDI-Remote-Scripts/blob/YourControllerName/YourControllerName/MIDI_Map.py).
87 |
88 |
89 | ---
90 | ### Downloads
91 | - [MIDI Monitor](https://www.snoize.com/midimonitor/) for macOS (preferred).
92 | - [MIDI Tools](https://mountainutilities.eu/miditools) for Windows and macOS.
93 | - [BCRManager](http://mountainutilities.eu/bcmanager) a GUI software solution for editing the Behringer BCR2000.
94 | - [Atom](https://atom.io) the code editor I mention in the video.
95 | - [PyCharm](https://www.jetbrains.com/pycharm/) probably a better editor to use if you're going to get into Python.
96 |
--------------------------------------------------------------------------------
/YourControllerName - Live 11/MIDI_Map.py:
--------------------------------------------------------------------------------
1 | # Avoid using tabs for indentation, use spaces.
2 | # Don't use floats, they might break things.
3 |
4 |
5 | # Combination Mode offsets
6 | # ------------------------
7 |
8 | TRACK_OFFSET = -1 # offset from the left of linked session origin; set to -1 for auto-joining of multiple instances
9 | SCENE_OFFSET = 0 # offset from the top of linked session origin (no auto-join)
10 |
11 | # Buttons / Pads
12 | # -------------
13 | # Valid Note/CC assignments are 0 to 127, or -1 for NONE
14 | # Duplicate assignments are permitted
15 |
16 | BUTTONCHANNEL = 0 # Channel assignment for all mapped buttons/pads; valid range is 0 to 15 ; 0=1, 1=2 etc.
17 | MESSAGETYPE = 0 # Message type for buttons/pads; set to 0 for MIDI Notes, 1 for CCs.
18 | # When using CCs for buttons/pads, set BUTTONCHANNEL and SLIDERCHANNEL to different values.
19 |
20 |
21 | # Track selection box (aka that coloured box for scene/track launching)
22 | TSB_X = 8 # Controls the horizontal value for the track selection box. Default value is 8
23 | TSB_Y = 8 # Controls the horizontal value for the track selection box. Default value is 8
24 |
25 | # General
26 | PLAY = -1 # Global play
27 | STOP = -1 # Global stop
28 | REC = -1 # Global record
29 | TAPTEMPO = -1 # Tap tempo
30 | NUDGEUP = -1 # Tempo Nudge Up
31 | NUDGEDOWN = -1 # Tempo Nudge Down
32 | UNDO = -1 # Undo
33 | REDO = -1 # Redo
34 | LOOP = -1 # Loop on/off
35 | PUNCHIN = -1 # Punch in
36 | PUNCHOUT = -1 # Punch out
37 | OVERDUB = -1 # Overdub on/off
38 | METRONOME = -1 # Metronome on/off
39 | RECQUANT = -1 # Record quantization on/off
40 | DETAILVIEW = -1 # Detail view switch
41 | CLIPTRACKVIEW = -1 # Clip/Track view switch
42 |
43 | # Device Control
44 | DEVICELOCK = -1 # Device Lock (lock "blue hand")
45 | DEVICEONOFF = -1 # Device on/off
46 | DEVICENAVLEFT = -1 # Device nav left
47 | DEVICENAVRIGHT = -1 # Device nav right
48 | DEVICEBANKNAVLEFT = -1 # Device bank nav left
49 | DEVICEBANKNAVRIGHT = -1 # Device bank nav right
50 | DEVICEBANK = (-1, # Bank 1 #All 8 banks must be assigned to positive values in order for bank selection to work
51 | -1, # Bank 2
52 | -1, # Bank 3
53 | -1, # Bank 4
54 | -1, # Bank 5
55 | -1, # Bank 6
56 | -1, # Bank 7
57 | -1, # Bank 8
58 | )
59 |
60 | # Arrangement View Controls
61 | SEEKFWD = -1 # Seek forward
62 | SEEKRWD = -1 # Seek rewind
63 |
64 | # Session Navigation (aka "red box")
65 | SESSIONLEFT = -1 # Session left
66 | SESSIONRIGHT = -1 # Session right
67 | SESSIONUP = -1 # Session up
68 | SESSIONDOWN = -1 # Session down
69 | ZOOMUP = -1 # Session Zoom up
70 | ZOOMDOWN = -1 # Session Zoom down
71 | ZOOMLEFT = -1 # Session Zoom left
72 | ZOOMRIGHT = -1 # Session Zoom right
73 |
74 | # Track Navigation
75 | TRACKLEFT = -1 # Track left
76 | TRACKRIGHT = -1 # Track right
77 |
78 | # Scene Navigation
79 | SCENEUP = -1 # Scene down
80 | SCENEDN = -1 # Scene up
81 |
82 | # Scene Launch
83 | SELSCENELAUNCH = -1 # Selected scene launch
84 | SCENELAUNCH = (-1, # Scene 1 Launch
85 | -1, # Scene 2
86 | -1, # Scene 3
87 | -1, # Scene 4
88 | -1, # Scene 5
89 | -1, # Scene 6
90 | -1, # Scene 7
91 | -1, # Scene 8
92 | )
93 |
94 | # Clip Launch / Stop
95 | SELCLIPLAUNCH = -1 # Selected clip launch
96 | STOPALLCLIPS = -1 # Stop all clips
97 |
98 | # 8x8 Matrix note assignments
99 | # Track no.: 1 2 3 4 5 6 7 8
100 | CLIPNOTEMAP = ((-1, -1, -1, -1, -1, -1, -1, -1), # Row 1
101 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 2
102 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 3
103 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 4
104 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 5
105 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 6
106 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 7
107 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 8
108 | )
109 |
110 | # Track Control
111 | MASTERSEL = -1 # Master track select
112 | SELTRACKREC = -1 # Arm Selected Track
113 | SELTRACKSOLO = -1 # Solo Selected Track
114 | SELTRACKMUTE = -1 # Mute Selected Track
115 |
116 | TRACKSTOP = (-1, # Track 1 Clip Stop
117 | -1, # Track 2
118 | -1, # Track 3
119 | -1, # Track 4
120 | -1, # Track 5
121 | -1, # Track 6
122 | -1, # Track 7
123 | -1, # Track 8
124 | )
125 |
126 | TRACKSEL = (-1, # Track 1 Select
127 | -1, # Track 2
128 | -1, # Track 3
129 | -1, # Track 4
130 | -1, # Track 5
131 | -1, # Track 6
132 | -1, # Track 7
133 | -1, # Track 8
134 | )
135 |
136 | TRACKMUTE = (-1, # Track 1 On/Off
137 | -1, # Track 2
138 | -1, # Track 3
139 | -1, # Track 4
140 | -1, # Track 5
141 | -1, # Track 6
142 | -1, # Track 7
143 | -1, # Track 8
144 | )
145 |
146 | TRACKSOLO = (-1, # Track 1 Solo
147 | -1, # Track 2
148 | -1, # Track 3
149 | -1, # Track 4
150 | -1, # Track 5
151 | -1, # Track 6
152 | -1, # Track 7
153 | -1, # Track 8
154 | )
155 |
156 | TRACKREC = (-1, # Track 1 Record
157 | -1, # Track 2
158 | -1, # Track 3
159 | -1, # Track 4
160 | -1, # Track 5
161 | -1, # Track 6
162 | -1, # Track 7
163 | -1, # Track 8
164 | )
165 |
166 |
167 | # Pad Translations for Drum Rack
168 | PADCHANNEL = 0 # MIDI channel for Drum Rack notes
169 | DRUM_PADS = (-1, -1, -1, -1, # MIDI note numbers for 4 x 4 Drum Rack
170 | -1, -1, -1, -1, # Mapping will be disabled if any notes are set to -1
171 | -1, -1, -1, -1, # Notes will be "swallowed" if already mapped elsewhere
172 | -1, -1, -1, -1,
173 | )
174 |
175 |
176 | # Sliders / Knobs
177 | # ---------------
178 | # Valid CC assignments are 0 to 127, or -1 for NONE
179 | # Duplicate assignments will be ignored
180 | SLIDERCHANNEL = 0 # Channel assignment for all mapped CCs; valid range is 0 to 15
181 | TEMPO_TOP = 180.0 # Upper limit of tempo control in BPM (max is 999)
182 | TEMPO_BOTTOM = 100.0 # Lower limit of tempo control in BPM (min is 0)
183 |
184 | TEMPOCONTROL = -1 # Tempo control CC assignment; control range is set above
185 | MASTERVOLUME = -1 # Master track volume
186 | CUELEVEL = -1 # Cue level control
187 | CROSSFADER = -1 # Crossfader control
188 |
189 | TRACKVOL = (-1, # Track 1 Volume
190 | -1, # Track 2
191 | -1, # Track 3
192 | -1, # Track 4
193 | -1, # Track 5
194 | -1, # Track 6
195 | -1, # Track 7
196 | -1, # Track 8
197 | )
198 |
199 | TRACKPAN = (-1, # Track 1 Pan
200 | -1, # Track 2
201 | -1, # Track 3
202 | -1, # Track 4
203 | -1, # Track 5
204 | -1, # Track 6
205 | -1, # Track 7
206 | -1, # Track 8
207 | )
208 |
209 | TRACKSENDA = (-1, # Track 1 Send A
210 | -1, # Track 2
211 | -1, # Track 3
212 | -1, # Track 4
213 | -1, # Track 5
214 | -1, # Track 6
215 | -1, # Track 7
216 | -1, # Track 8
217 | )
218 |
219 | TRACKSENDB = (-1, # Track 1 Send B
220 | -1, # Track 2
221 | -1, # Track 3
222 | -1, # Track 4
223 | -1, # Track 5
224 | -1, # Track 6
225 | -1, # Track 7
226 | -1, # Track 8
227 | )
228 |
229 | TRACKSENDC = (-1, # Track 1 Send C
230 | -1, # Track 2
231 | -1, # Track 3
232 | -1, # Track 4
233 | -1, # Track 5
234 | -1, # Track 6
235 | -1, # Track 7
236 | -1, # Track 8
237 | )
238 |
239 | PARAMCONTROL = (-1, # Param 1 #All 8 params must be assigned to positive values in order for param control to work
240 | -1, # Param 2
241 | -1, # Param 3
242 | -1, # Param 4
243 | -1, # Param 5
244 | -1, # Param 6
245 | -1, # Param 7
246 | -1, # Param 8
247 | )
248 |
249 |
250 |
--------------------------------------------------------------------------------
/YourControllerName - Live 11/SpecialChannelStripComponent.py:
--------------------------------------------------------------------------------
1 | # emacs-mode: -*- python-*-
2 | # -*- coding: utf-8 -*-
3 |
4 | from _Framework.ChannelStripComponent import ChannelStripComponent
5 | TRACK_FOLD_DELAY = 5
6 |
7 |
8 | class SpecialChannelStripComponent(ChannelStripComponent):
9 | """ Subclass of channel strip component using select button for (un)folding tracks """
10 | __module__ = __name__
11 |
12 | def __init__(self):
13 | ChannelStripComponent.__init__(self)
14 | self._toggle_fold_ticks_delay = -1
15 | self._register_timer_callback(self._on_timer)
16 |
17 | def disconnect(self):
18 | self._unregister_timer_callback(self._on_timer)
19 | ChannelStripComponent.disconnect(self)
20 |
21 | def _select_value(self, value):
22 | ChannelStripComponent._select_value(self, value)
23 | if (self.is_enabled() and (self._track != None)):
24 | if (self._track.is_foldable and (self._select_button.is_momentary() and (value != 0))):
25 | self._toggle_fold_ticks_delay = TRACK_FOLD_DELAY
26 | else:
27 | self._toggle_fold_ticks_delay = -1
28 |
29 | def _on_timer(self):
30 | if (self.is_enabled() and (self._track != None)):
31 | if (self._toggle_fold_ticks_delay > -1):
32 | assert self._track.is_foldable
33 | if (self._toggle_fold_ticks_delay == 0):
34 | self._track.fold_state = (not self._track.fold_state)
35 | self._toggle_fold_ticks_delay -= 1
36 |
37 |
38 | # local variables:
39 | # tab-width: 4
40 |
--------------------------------------------------------------------------------
/YourControllerName - Live 11/SpecialMixerComponent.py:
--------------------------------------------------------------------------------
1 | from _Framework.MixerComponent import MixerComponent
2 | from .SpecialChannelStripComponent import SpecialChannelStripComponent
3 |
4 |
5 | class SpecialMixerComponent(MixerComponent):
6 | """ Special mixer class that uses return tracks alongside midi and audio tracks """
7 | __module__ = __name__
8 |
9 | def __init__(self, num_tracks):
10 | MixerComponent.__init__(self, num_tracks)
11 |
12 | def tracks_to_use(self):
13 | return tuple(self.song().visible_tracks) + tuple(self.song().return_tracks)
14 |
15 | def _create_strip(self):
16 | return SpecialChannelStripComponent()
17 |
--------------------------------------------------------------------------------
/YourControllerName - Live 11/SpecialSessionComponent.py:
--------------------------------------------------------------------------------
1 | # emacs-mode: -*- python-*-
2 | # -*- coding: utf-8 -*-
3 |
4 | import Live
5 | from _Framework.SessionComponent import SessionComponent
6 | from _Framework.ButtonElement import ButtonElement
7 | class SpecialSessionComponent(SessionComponent):
8 | " Special SessionComponent for APC combination mode and button to fire selected clip slot "
9 | __module__ = __name__
10 |
11 | def __init__(self, num_tracks, num_scenes):
12 | SessionComponent.__init__(self, num_tracks, num_scenes)
13 | self._slot_launch_button = None
14 |
15 | def disconnect(self):
16 | SessionComponent.disconnect(self)
17 | if (self._slot_launch_button != None):
18 | self._slot_launch_button.remove_value_listener(self._slot_launch_value)
19 | self._slot_launch_button = None
20 |
21 | def link_with_track_offset(self, track_offset, scene_offset):
22 | assert (track_offset >= 0)
23 | assert (scene_offset >= 0)
24 | if self._is_linked():
25 | self._unlink()
26 | self.set_offsets(track_offset, scene_offset)
27 | self._link()
28 |
29 | def unlink(self):
30 | if self._is_linked():
31 | self._unlink()
32 |
33 | def set_slot_launch_button(self, button):
34 | assert ((button == None) or isinstance(button, ButtonElement))
35 | if (self._slot_launch_button != button):
36 | if (self._slot_launch_button != None):
37 | self._slot_launch_button.remove_value_listener(self._slot_launch_value)
38 | self._slot_launch_button = button
39 | if (self._slot_launch_button != None):
40 | self._slot_launch_button.add_value_listener(self._slot_launch_value)
41 |
42 | self.update()
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 |
--------------------------------------------------------------------------------
/YourControllerName - Live 11/SpecialTransportComponent.py:
--------------------------------------------------------------------------------
1 | # There are lot of things to explore that you can use to add extra functionality to your scipt.
2 | # You'd essentially enable it here by uncommenting then add it YourControllerName.py and MIDI_Map.
3 | # I experiment more and add myself in the future.
4 |
5 | import Live
6 | from _Framework.TransportComponent import TransportComponent
7 | from _Framework.ButtonElement import ButtonElement
8 | from _Framework.EncoderElement import EncoderElement #added
9 | from _Framework.SubjectSlot import subject_slot #added
10 | #TEMPO_TOP = 300.0
11 | #TEMPO_BOTTOM = 40.0
12 | from .MIDI_Map import TEMPO_TOP
13 | from .MIDI_Map import TEMPO_BOTTOM
14 | class SpecialTransportComponent(TransportComponent):
15 | __doc__ = ' TransportComponent that only uses certain buttons if a shift button is pressed '
16 | def __init__(self):
17 | TransportComponent.__init__(self)
18 | #self._shift_button = None
19 | self._quant_toggle_button = None
20 | #self._shift_pressed = False
21 | self._last_quant_value = Live.Song.RecordingQuantization.rec_q_eight
22 | self.song().add_midi_recording_quantization_listener(self._on_quantisation_changed)
23 | self._on_quantisation_changed()
24 | self._undo_button = None #added from OpenLabs SpecialTransportComponent script
25 | self._redo_button = None #added from OpenLabs SpecialTransportComponent script
26 | #self._bts_button = None #added from OpenLabs SpecialTransportComponent script
27 | self._tempo_encoder_control = None #new addition
28 | return None
29 |
30 | def disconnect(self):
31 | TransportComponent.disconnect(self)
32 | #if self._shift_button != None:
33 | #self._shift_button.remove_value_listener(self._shift_value)
34 | #self._shift_button = None
35 | if self._quant_toggle_button != None:
36 | self._quant_toggle_button.remove_value_listener(self._quant_toggle_value)
37 | self._quant_toggle_button = None
38 | self.song().remove_midi_recording_quantization_listener(self._on_quantisation_changed)
39 | if (self._undo_button != None): #added from OpenLabs SpecialTransportComponent script
40 | self._undo_button.remove_value_listener(self._undo_value)
41 | self._undo_button = None
42 | if (self._redo_button != None): #added from OpenLabs SpecialTransportComponent script
43 | self._redo_button.remove_value_listener(self._redo_value)
44 | self._redo_button = None
45 | #if (self._bts_button != None): #added from OpenLabs SpecialTransportComponent script
46 | #self._bts_button.remove_value_listener(self._bts_value)
47 | #self._bts_button = None
48 | if (self._tempo_encoder_control != None): #new addition
49 | self._tempo_encoder_control.remove_value_listener(self._tempo_encoder_value)
50 | self._tempo_encoder_control = None
51 | return None
52 |
53 | #def set_shift_button(self, button):
54 | #if not(button == None or isinstance(button, ButtonElement) and button.is_momentary()):
55 | #isinstance(button, ButtonElement)
56 | #raise AssertionError
57 | #if self._shift_button != button:
58 | #if self._shift_button != None:
59 | #self._shift_button.remove_value_listener(self._shift_value)
60 | #self._shift_button = button
61 | #if self._shift_button != None:
62 | #self._shift_button.add_value_listener(self._shift_value)
63 | #
64 | #self.update()
65 | #return None
66 |
67 | def set_quant_toggle_button(self, button):
68 | if not(button == None or isinstance(button, ButtonElement) and button.is_momentary()):
69 | isinstance(button, ButtonElement)
70 | raise AssertionError
71 | if self._quant_toggle_button != button:
72 | if self._quant_toggle_button != None:
73 | self._quant_toggle_button.remove_value_listener(self._quant_toggle_value)
74 | self._quant_toggle_button = button
75 | if self._quant_toggle_button != None:
76 | self._quant_toggle_button.add_value_listener(self._quant_toggle_value)
77 |
78 | self.update()
79 | return None
80 |
81 | #def update(self):
82 | #self._on_metronome_changed()
83 | #self._on_overdub_changed()
84 | #self._on_quantisation_changed()
85 | #self._on_nudge_up_changed() #added
86 | #self._on_nudge_down_changed #added
87 |
88 | #def _shift_value(self, value):
89 | #if not self._shift_button != None:
90 | #raise AssertionError
91 | #if not value in range(128):
92 | #raise AssertionError
93 | #self._shift_pressed = value != 0
94 | #if self.is_enabled():
95 | #self.is_enabled()
96 | #self.update()
97 | #else:
98 | #self.is_enabled()
99 | #return None
100 |
101 | #def _metronome_value(self, value):
102 | #if not self._shift_pressed:
103 | ###if self._shift_pressed:
104 | #TransportComponent._metronome_value(self, value)
105 |
106 |
107 | #def _overdub_value(self, value):
108 | #if not self._shift_pressed:
109 | #TransportComponent._overdub_value(self, value)
110 |
111 |
112 | #def _nudge_up_value(self, value): #added
113 | #if not self._shift_pressed:
114 | #TransportComponent._nudge_up_value(self, value)
115 |
116 |
117 | #def _nudge_down_value(self, value): #added
118 | #if not self._shift_pressed:
119 | #TransportComponent._nudge_down_value(self, value)
120 |
121 |
122 | #def _tap_tempo_value(self, value): # Added as Shift + Tap Tempo
123 | #if not self._shift_pressed:
124 | ##if self._shift_pressed:
125 | #TransportComponent._tap_tempo_value(self, value)
126 |
127 |
128 | def _quant_toggle_value(self, value):
129 | assert (self._quant_toggle_button != None)
130 | assert (value in range(128))
131 | assert (self._last_quant_value != Live.Song.RecordingQuantization.rec_q_no_q)
132 | if self.is_enabled(): # and (not self._shift_pressed):
133 | if ((value != 0) or (not self._quant_toggle_button.is_momentary())):
134 | quant_value = self.song().midi_recording_quantization
135 | if (quant_value != Live.Song.RecordingQuantization.rec_q_no_q):
136 | self._last_quant_value = quant_value
137 | self.song().midi_recording_quantization = Live.Song.RecordingQuantization.rec_q_no_q
138 | else:
139 | self.song().midi_recording_quantization = self._last_quant_value
140 |
141 |
142 | #def _on_metronome_changed(self):
143 | #if not self._shift_pressed:
144 | ##if self._shift_pressed:
145 | #TransportComponent._on_metronome_changed(self)
146 |
147 |
148 | #def _on_overdub_changed(self):
149 | #if not self._shift_pressed:
150 | #TransportComponent._on_overdub_changed(self)
151 |
152 |
153 | #def _on_nudge_up_changed(self): #added
154 | #if not self._shift_pressed:
155 | #TransportComponent._on_nudge_up_changed(self)
156 |
157 |
158 | #def _on_nudge_down_changed(self): #added
159 | #if not self._shift_pressed:
160 | #TransportComponent._on_nudge_down_changed(self)
161 |
162 |
163 | def _on_quantisation_changed(self):
164 | if self.is_enabled():
165 | quant_value = self.song().midi_recording_quantization
166 | quant_on = (quant_value != Live.Song.RecordingQuantization.rec_q_no_q)
167 | if quant_on:
168 | self._last_quant_value = quant_value
169 | if self._quant_toggle_button != None: #((not self._shift_pressed) and (self._quant_toggle_button != None)):
170 | if quant_on:
171 | self._quant_toggle_button.turn_on()
172 | else:
173 | self._quant_toggle_button.turn_off()
174 |
175 | """ from OpenLabs module SpecialTransportComponent """
176 |
177 | def set_undo_button(self, undo_button):
178 | assert isinstance(undo_button, (ButtonElement,
179 | type(None)))
180 | if (undo_button != self._undo_button):
181 | if (self._undo_button != None):
182 | self._undo_button.remove_value_listener(self._undo_value)
183 | self._undo_button = undo_button
184 | if (self._undo_button != None):
185 | self._undo_button.add_value_listener(self._undo_value)
186 | self.update()
187 |
188 |
189 |
190 | def set_redo_button(self, redo_button):
191 | assert isinstance(redo_button, (ButtonElement,
192 | type(None)))
193 | if (redo_button != self._redo_button):
194 | if (self._redo_button != None):
195 | self._redo_button.remove_value_listener(self._redo_value)
196 | self._redo_button = redo_button
197 | if (self._redo_button != None):
198 | self._redo_button.add_value_listener(self._redo_value)
199 | self.update()
200 |
201 |
202 | #def set_bts_button(self, bts_button): #"back to start" button
203 | #assert isinstance(bts_button, (ButtonElement,
204 | #type(None)))
205 | #if (bts_button != self._bts_button):
206 | #if (self._bts_button != None):
207 | #self._bts_button.remove_value_listener(self._bts_value)
208 | #self._bts_button = bts_button
209 | #if (self._bts_button != None):
210 | #self._bts_button.add_value_listener(self._bts_value)
211 | #self.update()
212 |
213 |
214 | def _undo_value(self, value):
215 | #if self._shift_pressed: #added
216 | assert (self._undo_button != None)
217 | assert (value in range(128))
218 | if self.is_enabled():
219 | if ((value != 0) or (not self._undo_button.is_momentary())):
220 | if self.song().can_undo:
221 | self.song().undo()
222 |
223 |
224 | def _redo_value(self, value):
225 | #if self._shift_pressed: #added
226 | assert (self._redo_button != None)
227 | assert (value in range(128))
228 | if self.is_enabled():
229 | if ((value != 0) or (not self._redo_button.is_momentary())):
230 | if self.song().can_redo:
231 | self.song().redo()
232 |
233 |
234 | #def _bts_value(self, value):
235 | #assert (self._bts_button != None)
236 | #assert (value in range(128))
237 | #if self.is_enabled():
238 | #if ((value != 0) or (not self._bts_button.is_momentary())):
239 | #self.song().current_song_time = 0.0
240 |
241 |
242 | def _tempo_encoder_value(self, value):
243 | ##if not self._shift_pressed:
244 | #if self._shift_pressed:
245 | assert (self._tempo_encoder_control != None)
246 | assert (value in range(128))
247 | backwards = (value >= 64)
248 | step = 0.1 #step = 1.0 #reduce this for finer control; 1.0 is 1 bpm
249 | if backwards:
250 | amount = (value - 128)
251 | else:
252 | amount = value
253 | tempo = max(20, min(999, (self.song().tempo + (amount * step))))
254 | self.song().tempo = tempo
255 |
256 |
257 | def set_tempo_encoder(self, control):
258 | assert ((control == None) or (isinstance(control, EncoderElement) and (control.message_map_mode() is Live.MidiMap.MapMode.relative_two_compliment)))
259 | if (self._tempo_encoder_control != None):
260 | self._tempo_encoder_control.remove_value_listener(self._tempo_encoder_value)
261 | self._tempo_encoder_control = control
262 | if (self._tempo_encoder_control != None):
263 | self._tempo_encoder_control.add_value_listener(self._tempo_encoder_value)
264 | self.update()
265 |
266 | @subject_slot('value')
267 | def _tempo_value(self, value): #Override to pull tempo range from MIDI_Maps.py
268 | assert (self._tempo_control != None)
269 | assert (value in range(128))
270 | if self.is_enabled():
271 | fraction = ((TEMPO_TOP - TEMPO_BOTTOM) / 127.0)
272 | self.song().tempo = ((fraction * value) + TEMPO_BOTTOM)
273 |
274 |
--------------------------------------------------------------------------------
/YourControllerName - Live 11/SpecialViewControllerComponent.py:
--------------------------------------------------------------------------------
1 | # There are lot of things to explore that you can use to add extra functionality to your scipt.
2 | # You'd essentially enable it here by uncommenting then add it YourControllerName.py and MIDI_Map.
3 | # I experiment more and add myself in the future.
4 |
5 | # Partial --== Decompile ==-- with fixes
6 | import Live
7 | from _Framework.ControlSurfaceComponent import ControlSurfaceComponent
8 | from _Framework.ButtonElement import ButtonElement
9 | SHOW_PLAYING_CLIP_DELAY = 5
10 | class DetailViewControllerComponent(ControlSurfaceComponent):
11 | __module__ = __name__
12 | __doc__ = ' Component that can toggle the device chain- and clip view of the selected track '
13 |
14 | def __init__(self):
15 | ControlSurfaceComponent.__init__(self)
16 | self._device_clip_toggle_button = None
17 | self._detail_toggle_button = None
18 | self._left_button = None
19 | self._right_button = None
20 | #self._shift_button = None
21 | #self._shift_pressed = False
22 | self._show_playing_clip_ticks_delay = -1
23 | self.application().view.add_is_view_visible_listener('Detail', self._detail_view_visibility_changed)
24 | self._register_timer_callback(self._on_timer)
25 | return None
26 |
27 | def disconnect(self):
28 | self._unregister_timer_callback(self._on_timer)
29 | self.application().view.remove_is_view_visible_listener('Detail', self._detail_view_visibility_changed)
30 | if self._device_clip_toggle_button != None:
31 | self._device_clip_toggle_button.remove_value_listener(self._device_clip_toggle_value)
32 | self._device_clip_toggle_button = None
33 | if self._detail_toggle_button != None:
34 | self._detail_toggle_button.remove_value_listener(self._detail_toggle_value)
35 | self._detail_toggle_button = None
36 | if self._left_button != None:
37 | self._left_button.remove_value_listener(self._nav_value)
38 | self._left_button = None
39 | if self._right_button != None:
40 | self._right_button.remove_value_listener(self._nav_value)
41 | self._right_button = None
42 | #if self._shift_button != None:
43 | #self._shift_button.remove_value_listener(self._shift_value)
44 | #self._shift_button = None
45 | return None
46 |
47 | def set_device_clip_toggle_button(self, button):
48 | if not(button == None or isinstance(button, ButtonElement)):
49 | isinstance(button, ButtonElement)
50 | raise AssertionError
51 | if self._device_clip_toggle_button != button:
52 | if self._device_clip_toggle_button != None:
53 | self._device_clip_toggle_button.remove_value_listener(self._device_clip_toggle_value)
54 | self._device_clip_toggle_button = button
55 | if self._device_clip_toggle_button != None:
56 | self._device_clip_toggle_button.add_value_listener(self._device_clip_toggle_value)
57 |
58 | self.update()
59 | return None
60 |
61 | def set_detail_toggle_button(self, button):
62 | if not(button == None or isinstance(button, ButtonElement)):
63 | isinstance(button, ButtonElement)
64 | raise AssertionError
65 | if self._detail_toggle_button != button:
66 | if self._detail_toggle_button != None:
67 | self._detail_toggle_button.remove_value_listener(self._detail_toggle_value)
68 | self._detail_toggle_button = button
69 | if self._detail_toggle_button != None:
70 | self._detail_toggle_button.add_value_listener(self._detail_toggle_value)
71 |
72 | self.update()
73 | return None
74 |
75 | def set_device_nav_buttons(self, left_button, right_button):
76 | if not(left_button == None or isinstance(left_button, ButtonElement)):
77 | isinstance(left_button, ButtonElement)
78 | raise AssertionError
79 | if not(right_button == None or isinstance(right_button, ButtonElement)):
80 | isinstance(right_button, ButtonElement)
81 | raise AssertionError
82 | identify_sender = True
83 | if self._left_button != None:
84 | self._left_button.remove_value_listener(self._nav_value)
85 | self._left_button = left_button
86 | if self._left_button != None:
87 | self._left_button.add_value_listener(self._nav_value, identify_sender)
88 | if self._right_button != None:
89 | self._right_button.remove_value_listener(self._nav_value)
90 | self._right_button = right_button
91 | if self._right_button != None:
92 | self._right_button.add_value_listener(self._nav_value, identify_sender)
93 |
94 | self.update()
95 | return None
96 |
97 | #def set_shift_button(self, button):
98 | #if not(button == None or isinstance(button, ButtonElement) and button.is_momentary()):
99 | #isinstance(button, ButtonElement)
100 | #raise AssertionError
101 | #if self._shift_button != button:
102 | #if self._shift_button != None:
103 | #self._shift_button.remove_value_listener(self._shift_value)
104 | #self._shift_button = button
105 | #if self._shift_button != None:
106 | #self._shift_button.add_value_listener(self._shift_value)
107 | #
108 | #self.update()
109 | #return None
110 |
111 | def on_enabled_changed(self):
112 | self.update()
113 |
114 | def update(self):
115 | pass
116 | #if self.is_enabled():
117 | #self.is_enabled()
118 | #if not self._shift_pressed:
119 | #self._shift_pressed
120 | #if self._left_button != None:
121 | #self._left_button.turn_off()
122 | #if self._right_button != None:
123 | #self._right_button.turn_off()
124 | #if self._device_clip_toggle_button != None:
125 | #self._device_clip_toggle_button.turn_off()
126 | #self._detail_view_visibility_changed()
127 | #else:
128 | #self._shift_pressed
129 | #else:
130 | #self.is_enabled()
131 | return None
132 |
133 | def _detail_view_visibility_changed(self):
134 | if self.is_enabled() and self._detail_toggle_button != None: #not self._shift_pressed and self._detail_toggle_button != None:
135 | if self.application().view.is_view_visible('Detail'):
136 | self.application().view.is_view_visible('Detail')
137 | self._detail_toggle_button.turn_on()
138 | else:
139 | self.application().view.is_view_visible('Detail')
140 | self._detail_toggle_button.turn_off()
141 | else:
142 | self.is_enabled()
143 | return None
144 |
145 | def _device_clip_toggle_value(self, value):
146 | if not self._device_clip_toggle_button != None:
147 | raise AssertionError
148 | if not value in range(128):
149 | raise AssertionError
150 | if self.is_enabled(): #and not self._shift_pressed:
151 | button_is_momentary = self._device_clip_toggle_button.is_momentary()
152 | if not button_is_momentary or value != 0:
153 | not button_is_momentary
154 | if not self.application().view.is_view_visible('Detail'):
155 | self.application().view.is_view_visible('Detail')
156 | self.application().view.show_view('Detail')
157 | else:
158 | self.application().view.is_view_visible('Detail')
159 | if not self.application().view.is_view_visible('Detail/DeviceChain'):
160 | self.application().view.is_view_visible('Detail/DeviceChain')
161 | self.application().view.show_view('Detail/DeviceChain')
162 | else:
163 | self.application().view.is_view_visible('Detail/DeviceChain')
164 | self.application().view.show_view('Detail/Clip')
165 | if button_is_momentary and value != 0:
166 | self._show_playing_clip_ticks_delay = SHOW_PLAYING_CLIP_DELAY
167 | else:
168 | button_is_momentary
169 | self._show_playing_clip_ticks_delay = -1
170 | else:
171 | self.is_enabled()
172 | return None
173 |
174 |
175 | def _detail_toggle_value(self, value):
176 | assert (self._detail_toggle_button != None)
177 | assert (value in range(128))
178 | if self.is_enabled(): # and (not self._shift_pressed):
179 | if ((not self._detail_toggle_button.is_momentary()) or (value != 0)):
180 | if (not self.application().view.is_view_visible('Detail')):
181 | self.application().view.show_view('Detail')
182 | else:
183 | self.application().view.hide_view('Detail')
184 |
185 |
186 | #def _shift_value(self, value):
187 | #if not self._shift_button != None:
188 | #raise AssertionError
189 | #if not value in range(128):
190 | #raise AssertionError
191 | #self._shift_pressed = value != 0
192 | #self.update()
193 | #return None
194 |
195 | def _nav_value(self, value, sender):
196 | assert ((sender != None) and (sender in (self._left_button,
197 | self._right_button)))
198 | if self.is_enabled(): # and (not self._shift_pressed):
199 | if ((not sender.is_momentary()) or (value != 0)):
200 | modifier_pressed = True
201 | if ((not self.application().view.is_view_visible('Detail')) or (not self.application().view.is_view_visible('Detail/DeviceChain'))):
202 | self.application().view.show_view('Detail')
203 | self.application().view.show_view('Detail/DeviceChain')
204 | else:
205 | direction = Live.Application.Application.View.NavDirection.left
206 | if (sender == self._right_button):
207 | direction = Live.Application.Application.View.NavDirection.right
208 | self.application().view.scroll_view(direction, 'Detail/DeviceChain', (not modifier_pressed))
209 |
210 | def _on_timer(self):
211 | if self.is_enabled(): # and (not self._shift_pressed):
212 | if (self._show_playing_clip_ticks_delay > -1):
213 | if (self._show_playing_clip_ticks_delay == 0):
214 | song = self.song()
215 | playing_slot_index = song.view.selected_track.playing_slot_index
216 | if (playing_slot_index > -1):
217 | song.view.selected_scene = song.scenes[playing_slot_index]
218 | if song.view.highlighted_clip_slot.has_clip:
219 | self.application().view.show_view('Detail/Clip')
220 | self._show_playing_clip_ticks_delay -= 1
221 |
--------------------------------------------------------------------------------
/YourControllerName - Live 11/SpecialZoomingComponent.py:
--------------------------------------------------------------------------------
1 | # emacs-mode: -*- python-*-
2 | # -*- coding: utf-8 -*-
3 |
4 | import Live
5 | from _Framework.SessionZoomingComponent import SessionZoomingComponent
6 | from _Framework.ButtonElement import ButtonElement
7 | class SpecialZoomingComponent(SessionZoomingComponent):
8 | ' Special ZoomingComponent that uses clip stop buttons for stop all when zoomed '
9 | __module__ = __name__
10 |
11 | def __init__(self, session):
12 | SessionZoomingComponent.__init__(self, session)
13 |
14 |
15 | def _scroll_up(self):
16 | #if self._is_zoomed_out:
17 | height = self._session.height()
18 | track_offset = self._session.track_offset()
19 | scene_offset = self._session.scene_offset()
20 |
21 | if scene_offset > 0:
22 | new_scene_offset = scene_offset
23 | if scene_offset % height > 0:
24 | new_scene_offset -= (scene_offset % height)
25 | else:
26 | new_scene_offset = max(0, scene_offset - height)
27 | self._session.set_offsets(track_offset, new_scene_offset)
28 |
29 | def _scroll_down(self):
30 | #if self._is_zoomed_out:
31 | height = self._session.height()
32 | track_offset = self._session.track_offset()
33 | scene_offset = self._session.scene_offset()
34 | new_scene_offset = scene_offset + height - (scene_offset % height)
35 | self._session.set_offsets(track_offset, new_scene_offset)
36 |
37 | def _scroll_left(self):
38 | #if self._is_zoomed_out:
39 | width = self._session.width()
40 | track_offset = self._session.track_offset()
41 | scene_offset = self._session.scene_offset()
42 | if track_offset > 0:
43 | new_track_offset = track_offset
44 | if track_offset % width > 0:
45 | new_track_offset -= (track_offset % width)
46 | else:
47 | new_track_offset = max(0, track_offset - width)
48 | self._session.set_offsets(new_track_offset, scene_offset)
49 |
50 | def _scroll_right(self):
51 | #if self._is_zoomed_out:
52 | width = self._session.width()
53 | track_offset = self._session.track_offset()
54 | scene_offset = self._session.scene_offset()
55 | new_track_offset = track_offset + width - (track_offset % width)
56 | self._session.set_offsets(new_track_offset, scene_offset)
57 |
--------------------------------------------------------------------------------
/YourControllerName - Live 11/YourControllerName.py:
--------------------------------------------------------------------------------
1 | from __future__ import with_statement
2 |
3 | import Live
4 | from _Framework.ControlSurface import ControlSurface
5 | from _Framework.InputControlElement import *
6 | from _Framework.SliderElement import SliderElement
7 | from _Framework.ButtonElement import ButtonElement
8 | from _Framework.ButtonMatrixElement import ButtonMatrixElement
9 | from _Framework.ChannelStripComponent import ChannelStripComponent
10 | from _Framework.DeviceComponent import DeviceComponent
11 | from _Framework.ControlSurfaceComponent import ControlSurfaceComponent
12 | from _Framework.SessionZoomingComponent import SessionZoomingComponent
13 | from .SpecialMixerComponent import SpecialMixerComponent
14 | from .SpecialTransportComponent import SpecialTransportComponent
15 | from .SpecialSessionComponent import SpecialSessionComponent
16 | from .SpecialZoomingComponent import SpecialZoomingComponent
17 | from .SpecialViewControllerComponent import DetailViewControllerComponent
18 | from .MIDI_Map import *
19 |
20 |
21 | # MIDI_NOTE_TYPE = 0
22 | # MIDI_CC_TYPE = 1
23 | # MIDI_PB_TYPE = 2
24 |
25 |
26 | class YourControllerName(ControlSurface): # Make sure you update the name
27 | __doc__ = " Script for YourControllerName in APC emulation mode " # Make sure you update the name
28 |
29 | _active_instances = []
30 |
31 | def _combine_active_instances():
32 | track_offset = 0
33 | scene_offset = 0
34 | for instance in YourControllerName._active_instances: # Make sure you update the name
35 | instance._activate_combination_mode(track_offset, scene_offset)
36 | track_offset += instance._session.width()
37 | _combine_active_instances = staticmethod(_combine_active_instances)
38 |
39 | def __init__(self, c_instance):
40 | ControlSurface.__init__(self, c_instance)
41 | # self.set_suppress_rebuild_requests(True)
42 | with self.component_guard():
43 | self._note_map = []
44 | self._ctrl_map = []
45 | self._load_MIDI_map()
46 | self._session = None
47 | self._session_zoom = None
48 | self._mixer = None
49 | self._setup_session_control()
50 | self._setup_mixer_control()
51 | self._session.set_mixer(self._mixer)
52 | self._setup_device_and_transport_control()
53 | self.set_highlighting_session_component(self._session)
54 | # self.set_suppress_rebuild_requests(False)
55 | self._pads = []
56 | self._load_pad_translations()
57 | self._do_combine()
58 |
59 | def disconnect(self):
60 | self._note_map = None
61 | self._ctrl_map = None
62 | self._pads = None
63 | self._do_uncombine()
64 | self._shift_button = None
65 | self._session = None
66 | self._session_zoom = None
67 | self._mixer = None
68 | ControlSurface.disconnect(self)
69 |
70 | def _do_combine(self):
71 | if self not in YourControllerName._active_instances: # Make sure you update the name
72 | YourControllerName._active_instances.append(self) # Make sure you update the name
73 | YourControllerName._combine_active_instances() # Make sure you update the name
74 |
75 | def _do_uncombine(self):
76 | if (self in YourControllerName._active_instances) and YourControllerName._active_instances.remove(self): # Make sure you update the name
77 | self._session.unlink()
78 | YourControllerName._combine_active_instances() # Make sure you update the name
79 |
80 | def _activate_combination_mode(self, track_offset, scene_offset):
81 | if TRACK_OFFSET != -1:
82 | track_offset = TRACK_OFFSET
83 | if SCENE_OFFSET != -1:
84 | scene_offset = SCENE_OFFSET
85 | self._session.link_with_track_offset(track_offset, scene_offset)
86 |
87 | def _setup_session_control(self):
88 | is_momentary = True
89 | self._session = SpecialSessionComponent(TSB_X, TSB_Y) # Track selection box size (X,Y) (horizontal, vertical).
90 | self._session.name = 'Session_Control'
91 | self._session.set_track_bank_buttons(self._note_map[SESSIONRIGHT], self._note_map[SESSIONLEFT])
92 | self._session.set_scene_bank_buttons(self._note_map[SESSIONDOWN], self._note_map[SESSIONUP])
93 | self._session.set_select_buttons(self._note_map[SCENEDN], self._note_map[SCENEUP])
94 | # range(tsb_x) is the horizontal count for the track selection box
95 | self._scene_launch_buttons = [self._note_map[SCENELAUNCH[index]] for index in range(TSB_X)]
96 | # range(tsb_y) Range value is the track selection
97 | self._track_stop_buttons = [self._note_map[TRACKSTOP[index]] for index in range(TSB_Y)]
98 | self._session.set_stop_all_clips_button(self._note_map[STOPALLCLIPS])
99 | self._session.set_stop_track_clip_buttons(tuple(self._track_stop_buttons))
100 | self._session.selected_scene().name = 'Selected_Scene'
101 | self._session.selected_scene().set_launch_button(self._note_map[SELSCENELAUNCH])
102 | self._session.set_slot_launch_button(self._note_map[SELCLIPLAUNCH])
103 | for scene_index in range(TSB_Y): # Change range() value to set the vertical count for track selection box
104 | scene = self._session.scene(scene_index)
105 | scene.name = 'Scene_' + str(scene_index)
106 | button_row = []
107 | scene.set_launch_button(self._scene_launch_buttons[scene_index])
108 | scene.set_triggered_value(2)
109 | for track_index in range(TSB_X): # Change range() value to set the horizontal count for track selection box
110 | button = self._note_map[CLIPNOTEMAP[scene_index][track_index]]
111 | button_row.append(button)
112 | clip_slot = scene.clip_slot(track_index)
113 | clip_slot.name = str(track_index) + '_Clip_Slot_' + str(scene_index)
114 | clip_slot.set_launch_button(button)
115 | self._session_zoom = SpecialZoomingComponent(self._session)
116 | self._session_zoom.name = 'Session_Overview'
117 | self._session_zoom.set_nav_buttons(self._note_map[ZOOMUP], self._note_map[ZOOMDOWN], self._note_map[ZOOMLEFT], self._note_map[ZOOMRIGHT])
118 |
119 | def _setup_mixer_control(self):
120 |
121 | is_momentary = True
122 | self._mixer = SpecialMixerComponent(8)
123 | self._mixer.name = 'Mixer'
124 | self._mixer.master_strip().name = 'Master_Channel_Strip'
125 | self._mixer.master_strip().set_select_button(self._note_map[MASTERSEL])
126 | self._mixer.selected_strip().name = 'Selected_Channel_Strip'
127 | self._mixer.set_select_buttons(self._note_map[TRACKRIGHT], self._note_map[TRACKLEFT])
128 | self._mixer.set_crossfader_control(self._ctrl_map[CROSSFADER])
129 | self._mixer.set_prehear_volume_control(self._ctrl_map[CUELEVEL])
130 | self._mixer.master_strip().set_volume_control(self._ctrl_map[MASTERVOLUME])
131 | self._mixer.selected_strip().set_arm_button(self._note_map[SELTRACKREC])
132 | self._mixer.selected_strip().set_solo_button(self._note_map[SELTRACKSOLO])
133 | self._mixer.selected_strip().set_mute_button(self._note_map[SELTRACKMUTE])
134 | for track in range(8):
135 | # My guess is that altering the range here will allow you to alter the range of mixer tracks
136 | # So if you had a 16 fader mixer, this would come in handy.
137 | strip = self._mixer.channel_strip(track)
138 | strip.name = 'Channel_Strip_' + str(track)
139 | strip.set_arm_button(self._note_map[TRACKREC[track]])
140 | strip.set_solo_button(self._note_map[TRACKSOLO[track]])
141 | strip.set_mute_button(self._note_map[TRACKMUTE[track]])
142 | strip.set_select_button(self._note_map[TRACKSEL[track]])
143 | strip.set_volume_control(self._ctrl_map[TRACKVOL[track]])
144 | strip.set_pan_control(self._ctrl_map[TRACKPAN[track]])
145 | strip.set_send_controls((self._ctrl_map[TRACKSENDA[track]], self._ctrl_map[TRACKSENDB[track]], self._ctrl_map[TRACKSENDC[track]]))
146 | strip.set_invert_mute_feedback(True)
147 |
148 | def _setup_device_and_transport_control(self):
149 | is_momentary = True
150 | self._device = DeviceComponent()
151 | self._device.name = 'Device_Component'
152 | device_bank_buttons = []
153 | device_param_controls = []
154 | for index in range(8):
155 | device_param_controls.append(self._ctrl_map[PARAMCONTROL[index]])
156 | device_bank_buttons.append(self._note_map[DEVICEBANK[index]])
157 | if None not in device_bank_buttons:
158 | self._device.set_bank_buttons(tuple(device_bank_buttons))
159 | if None not in device_param_controls:
160 | self._device.set_parameter_controls(tuple(device_param_controls))
161 | self._device.set_on_off_button(self._note_map[DEVICEONOFF])
162 | self._device.set_bank_nav_buttons(self._note_map[DEVICEBANKNAVLEFT], self._note_map[DEVICEBANKNAVRIGHT])
163 | self._device.set_lock_button(self._note_map[DEVICELOCK])
164 | self.set_device_component(self._device)
165 |
166 | detail_view_toggler = DetailViewControllerComponent()
167 | detail_view_toggler.name = 'Detail_View_Control'
168 | detail_view_toggler.set_device_clip_toggle_button(self._note_map[CLIPTRACKVIEW])
169 | detail_view_toggler.set_detail_toggle_button(self._note_map[DETAILVIEW])
170 | detail_view_toggler.set_device_nav_buttons(self._note_map[DEVICENAVLEFT], self._note_map[DEVICENAVRIGHT])
171 |
172 | transport = SpecialTransportComponent()
173 | transport.name = 'Transport'
174 | transport.set_play_button(self._note_map[PLAY])
175 | transport.set_stop_button(self._note_map[STOP])
176 | transport.set_record_button(self._note_map[REC])
177 | transport.set_nudge_buttons(self._note_map[NUDGEUP], self._note_map[NUDGEDOWN])
178 | transport.set_undo_button(self._note_map[UNDO])
179 | transport.set_redo_button(self._note_map[REDO])
180 | transport.set_tap_tempo_button(self._note_map[TAPTEMPO])
181 | transport.set_quant_toggle_button(self._note_map[RECQUANT])
182 | transport.set_overdub_button(self._note_map[OVERDUB])
183 | transport.set_metronome_button(self._note_map[METRONOME])
184 | transport.set_tempo_control(self._ctrl_map[TEMPOCONTROL])
185 | transport.set_loop_button(self._note_map[LOOP])
186 | transport.set_seek_buttons(self._note_map[SEEKFWD], self._note_map[SEEKRWD])
187 | transport.set_punch_buttons(self._note_map[PUNCHIN], self._note_map[PUNCHOUT])
188 | # transport.set_song_position_control(self._ctrl_map[SONGPOSITION]) #still not implemented as of Live 8.1.6
189 |
190 | def _on_selected_track_changed(self):
191 | ControlSurface._on_selected_track_changed(self)
192 | track = self.song().view.selected_track
193 | device_to_select = track.view.selected_device
194 | if device_to_select is None and len(track.devices) > 0:
195 | device_to_select = track.devices[0]
196 | if device_to_select is not None:
197 | self.song().view.select_device(device_to_select)
198 | self._device_component.set_device(device_to_select)
199 |
200 | def _load_pad_translations(self):
201 | if -1 not in DRUM_PADS:
202 | pad = []
203 | for row in range(4):
204 | for col in range(4):
205 | pad = (col, row, DRUM_PADS[row*4 + col], PADCHANNEL,)
206 | self._pads.append(pad)
207 | self.set_pad_translations(tuple(self._pads))
208 |
209 | def _load_MIDI_map(self):
210 | is_momentary = True
211 | for note in range(128):
212 | button = ButtonElement(is_momentary, MESSAGETYPE, BUTTONCHANNEL, note)
213 | button.name = 'Note_' + str(note)
214 | self._note_map.append(button)
215 | self._note_map.append(None) # add None to the end of the list, selectable with [-1]
216 | if MESSAGETYPE == MIDI_CC_TYPE and BUTTONCHANNEL == SLIDERCHANNEL:
217 | for ctrl in range(128):
218 | self._ctrl_map.append(None)
219 | else:
220 | for ctrl in range(128):
221 | control = SliderElement(MIDI_CC_TYPE, SLIDERCHANNEL, ctrl)
222 | control.name = 'Ctrl_' + str(ctrl)
223 | self._ctrl_map.append(control)
224 | self._ctrl_map.append(None)
225 |
--------------------------------------------------------------------------------
/YourControllerName - Live 11/__init__.py:
--------------------------------------------------------------------------------
1 | import Live
2 | from .YourControllerName import YourControllerName
3 |
4 |
5 | def create_instance(c_instance):
6 | """ Creates and returns the APC20 script """
7 | return YourControllerName(c_instance)
8 |
9 | # local variables:
10 | # tab-width: 4
11 |
--------------------------------------------------------------------------------
/YourControllerName/MIDI_Map.py:
--------------------------------------------------------------------------------
1 | # Avoid using tabs for indentation, use spaces.
2 | # Don't use floats, they might break things.
3 |
4 |
5 | # Combination Mode offsets
6 | # ------------------------
7 |
8 | TRACK_OFFSET = -1 # offset from the left of linked session origin; set to -1 for auto-joining of multiple instances
9 | SCENE_OFFSET = 0 # offset from the top of linked session origin (no auto-join)
10 |
11 | # Buttons / Pads
12 | # -------------
13 | # Valid Note/CC assignments are 0 to 127, or -1 for NONE
14 | # Duplicate assignments are permitted
15 |
16 | BUTTONCHANNEL = 0 # Channel assignment for all mapped buttons/pads; valid range is 0 to 15 ; 0=1, 1=2 etc.
17 | MESSAGETYPE = 0 # Message type for buttons/pads; set to 0 for MIDI Notes, 1 for CCs.
18 | # When using CCs for buttons/pads, set BUTTONCHANNEL and SLIDERCHANNEL to different values.
19 |
20 |
21 | # Track selection box (aka that coloured box for scene/track launching)
22 | TSB_X = 8 # Controls the horizontal value for the track selection box. Default value is 8
23 | TSB_Y = 8 # Controls the horizontal value for the track selection box. Default value is 8
24 |
25 | # General
26 | PLAY = -1 # Global play
27 | STOP = -1 # Global stop
28 | REC = -1 # Global record
29 | TAPTEMPO = -1 # Tap tempo
30 | NUDGEUP = -1 # Tempo Nudge Up
31 | NUDGEDOWN = -1 # Tempo Nudge Down
32 | UNDO = -1 # Undo
33 | REDO = -1 # Redo
34 | LOOP = -1 # Loop on/off
35 | PUNCHIN = -1 # Punch in
36 | PUNCHOUT = -1 # Punch out
37 | OVERDUB = -1 # Overdub on/off
38 | METRONOME = -1 # Metronome on/off
39 | RECQUANT = -1 # Record quantization on/off
40 | DETAILVIEW = -1 # Detail view switch
41 | CLIPTRACKVIEW = -1 # Clip/Track view switch
42 |
43 | # Device Control
44 | DEVICELOCK = -1 # Device Lock (lock "blue hand")
45 | DEVICEONOFF = -1 # Device on/off
46 | DEVICENAVLEFT = -1 # Device nav left
47 | DEVICENAVRIGHT = -1 # Device nav right
48 | DEVICEBANKNAVLEFT = -1 # Device bank nav left
49 | DEVICEBANKNAVRIGHT = -1 # Device bank nav right
50 | DEVICEBANK = (-1, # Bank 1 #All 8 banks must be assigned to positive values in order for bank selection to work
51 | -1, # Bank 2
52 | -1, # Bank 3
53 | -1, # Bank 4
54 | -1, # Bank 5
55 | -1, # Bank 6
56 | -1, # Bank 7
57 | -1, # Bank 8
58 | )
59 |
60 | # Arrangement View Controls
61 | SEEKFWD = -1 # Seek forward
62 | SEEKRWD = -1 # Seek rewind
63 |
64 | # Session Navigation (aka "red box")
65 | SESSIONLEFT = -1 # Session left
66 | SESSIONRIGHT = -1 # Session right
67 | SESSIONUP = -1 # Session up
68 | SESSIONDOWN = -1 # Session down
69 | ZOOMUP = -1 # Session Zoom up
70 | ZOOMDOWN = -1 # Session Zoom down
71 | ZOOMLEFT = -1 # Session Zoom left
72 | ZOOMRIGHT = -1 # Session Zoom right
73 |
74 | # Track Navigation
75 | TRACKLEFT = -1 # Track left
76 | TRACKRIGHT = -1 # Track right
77 |
78 | # Scene Navigation
79 | SCENEUP = -1 # Scene down
80 | SCENEDN = -1 # Scene up
81 |
82 | # Scene Launch
83 | SELSCENELAUNCH = -1 # Selected scene launch
84 | SCENELAUNCH = (-1, # Scene 1 Launch
85 | -1, # Scene 2
86 | -1, # Scene 3
87 | -1, # Scene 4
88 | -1, # Scene 5
89 | -1, # Scene 6
90 | -1, # Scene 7
91 | -1, # Scene 8
92 | )
93 |
94 | # Clip Launch / Stop
95 | SELCLIPLAUNCH = -1 # Selected clip launch
96 | STOPALLCLIPS = -1 # Stop all clips
97 |
98 | # 8x8 Matrix note assignments
99 | # Track no.: 1 2 3 4 5 6 7 8
100 | CLIPNOTEMAP = ((-1, -1, -1, -1, -1, -1, -1, -1), # Row 1
101 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 2
102 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 3
103 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 4
104 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 5
105 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 6
106 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 7
107 | (-1, -1, -1, -1, -1, -1, -1, -1), # Row 8
108 | )
109 |
110 | # Track Control
111 | MASTERSEL = -1 # Master track select
112 | SELTRACKREC = -1 # Arm Selected Track
113 | SELTRACKSOLO = -1 # Solo Selected Track
114 | SELTRACKMUTE = -1 # Mute Selected Track
115 |
116 | TRACKSTOP = (-1, # Track 1 Clip Stop
117 | -1, # Track 2
118 | -1, # Track 3
119 | -1, # Track 4
120 | -1, # Track 5
121 | -1, # Track 6
122 | -1, # Track 7
123 | -1, # Track 8
124 | )
125 |
126 | TRACKSEL = (-1, # Track 1 Select
127 | -1, # Track 2
128 | -1, # Track 3
129 | -1, # Track 4
130 | -1, # Track 5
131 | -1, # Track 6
132 | -1, # Track 7
133 | -1, # Track 8
134 | )
135 |
136 | TRACKMUTE = (-1, # Track 1 On/Off
137 | -1, # Track 2
138 | -1, # Track 3
139 | -1, # Track 4
140 | -1, # Track 5
141 | -1, # Track 6
142 | -1, # Track 7
143 | -1, # Track 8
144 | )
145 |
146 | TRACKSOLO = (-1, # Track 1 Solo
147 | -1, # Track 2
148 | -1, # Track 3
149 | -1, # Track 4
150 | -1, # Track 5
151 | -1, # Track 6
152 | -1, # Track 7
153 | -1, # Track 8
154 | )
155 |
156 | TRACKREC = (-1, # Track 1 Record
157 | -1, # Track 2
158 | -1, # Track 3
159 | -1, # Track 4
160 | -1, # Track 5
161 | -1, # Track 6
162 | -1, # Track 7
163 | -1, # Track 8
164 | )
165 |
166 |
167 | # Pad Translations for Drum Rack
168 | PADCHANNEL = 0 # MIDI channel for Drum Rack notes
169 | DRUM_PADS = (-1, -1, -1, -1, # MIDI note numbers for 4 x 4 Drum Rack
170 | -1, -1, -1, -1, # Mapping will be disabled if any notes are set to -1
171 | -1, -1, -1, -1, # Notes will be "swallowed" if already mapped elsewhere
172 | -1, -1, -1, -1,
173 | )
174 |
175 |
176 | # Sliders / Knobs
177 | # ---------------
178 | # Valid CC assignments are 0 to 127, or -1 for NONE
179 | # Duplicate assignments will be ignored
180 | SLIDERCHANNEL = 0 # Channel assignment for all mapped CCs; valid range is 0 to 15
181 | TEMPO_TOP = 180.0 # Upper limit of tempo control in BPM (max is 999)
182 | TEMPO_BOTTOM = 100.0 # Lower limit of tempo control in BPM (min is 0)
183 |
184 | TEMPOCONTROL = -1 # Tempo control CC assignment; control range is set above
185 | MASTERVOLUME = -1 # Master track volume
186 | CUELEVEL = -1 # Cue level control
187 | CROSSFADER = -1 # Crossfader control
188 |
189 | TRACKVOL = (-1, # Track 1 Volume
190 | -1, # Track 2
191 | -1, # Track 3
192 | -1, # Track 4
193 | -1, # Track 5
194 | -1, # Track 6
195 | -1, # Track 7
196 | -1, # Track 8
197 | )
198 |
199 | TRACKPAN = (-1, # Track 1 Pan
200 | -1, # Track 2
201 | -1, # Track 3
202 | -1, # Track 4
203 | -1, # Track 5
204 | -1, # Track 6
205 | -1, # Track 7
206 | -1, # Track 8
207 | )
208 |
209 | TRACKSENDA = (-1, # Track 1 Send A
210 | -1, # Track 2
211 | -1, # Track 3
212 | -1, # Track 4
213 | -1, # Track 5
214 | -1, # Track 6
215 | -1, # Track 7
216 | -1, # Track 8
217 | )
218 |
219 | TRACKSENDB = (-1, # Track 1 Send B
220 | -1, # Track 2
221 | -1, # Track 3
222 | -1, # Track 4
223 | -1, # Track 5
224 | -1, # Track 6
225 | -1, # Track 7
226 | -1, # Track 8
227 | )
228 |
229 | TRACKSENDC = (-1, # Track 1 Send C
230 | -1, # Track 2
231 | -1, # Track 3
232 | -1, # Track 4
233 | -1, # Track 5
234 | -1, # Track 6
235 | -1, # Track 7
236 | -1, # Track 8
237 | )
238 |
239 | PARAMCONTROL = (-1, # Param 1 #All 8 params must be assigned to positive values in order for param control to work
240 | -1, # Param 2
241 | -1, # Param 3
242 | -1, # Param 4
243 | -1, # Param 5
244 | -1, # Param 6
245 | -1, # Param 7
246 | -1, # Param 8
247 | )
248 |
249 |
250 |
--------------------------------------------------------------------------------
/YourControllerName/SpecialChannelStripComponent.py:
--------------------------------------------------------------------------------
1 | # emacs-mode: -*- python-*-
2 | # -*- coding: utf-8 -*-
3 |
4 | from _Framework.ChannelStripComponent import ChannelStripComponent
5 | TRACK_FOLD_DELAY = 5
6 |
7 |
8 | class SpecialChannelStripComponent(ChannelStripComponent):
9 | """ Subclass of channel strip component using select button for (un)folding tracks """
10 | __module__ = __name__
11 |
12 | def __init__(self):
13 | ChannelStripComponent.__init__(self)
14 | self._toggle_fold_ticks_delay = -1
15 | self._register_timer_callback(self._on_timer)
16 |
17 | def disconnect(self):
18 | self._unregister_timer_callback(self._on_timer)
19 | ChannelStripComponent.disconnect(self)
20 |
21 | def _select_value(self, value):
22 | ChannelStripComponent._select_value(self, value)
23 | if (self.is_enabled() and (self._track != None)):
24 | if (self._track.is_foldable and (self._select_button.is_momentary() and (value != 0))):
25 | self._toggle_fold_ticks_delay = TRACK_FOLD_DELAY
26 | else:
27 | self._toggle_fold_ticks_delay = -1
28 |
29 | def _on_timer(self):
30 | if (self.is_enabled() and (self._track != None)):
31 | if (self._toggle_fold_ticks_delay > -1):
32 | assert self._track.is_foldable
33 | if (self._toggle_fold_ticks_delay == 0):
34 | self._track.fold_state = (not self._track.fold_state)
35 | self._toggle_fold_ticks_delay -= 1
36 |
37 |
38 | # local variables:
39 | # tab-width: 4
40 |
--------------------------------------------------------------------------------
/YourControllerName/SpecialMixerComponent.py:
--------------------------------------------------------------------------------
1 | from _Framework.MixerComponent import MixerComponent
2 | from SpecialChannelStripComponent import SpecialChannelStripComponent
3 |
4 |
5 | class SpecialMixerComponent(MixerComponent):
6 | """ Special mixer class that uses return tracks alongside midi and audio tracks """
7 | __module__ = __name__
8 |
9 | def __init__(self, num_tracks):
10 | MixerComponent.__init__(self, num_tracks)
11 |
12 | def tracks_to_use(self):
13 | return tuple(self.song().visible_tracks) + tuple(self.song().return_tracks)
14 |
15 | def _create_strip(self):
16 | return SpecialChannelStripComponent()
17 |
18 |
--------------------------------------------------------------------------------
/YourControllerName/SpecialSessionComponent.py:
--------------------------------------------------------------------------------
1 | # emacs-mode: -*- python-*-
2 | # -*- coding: utf-8 -*-
3 |
4 | import Live
5 | from _Framework.SessionComponent import SessionComponent
6 | from _Framework.ButtonElement import ButtonElement
7 | class SpecialSessionComponent(SessionComponent):
8 | " Special SessionComponent for APC combination mode and button to fire selected clip slot "
9 | __module__ = __name__
10 |
11 | def __init__(self, num_tracks, num_scenes):
12 | SessionComponent.__init__(self, num_tracks, num_scenes)
13 | self._slot_launch_button = None
14 |
15 | def disconnect(self):
16 | SessionComponent.disconnect(self)
17 | if (self._slot_launch_button != None):
18 | self._slot_launch_button.remove_value_listener(self._slot_launch_value)
19 | self._slot_launch_button = None
20 |
21 | def link_with_track_offset(self, track_offset, scene_offset):
22 | assert (track_offset >= 0)
23 | assert (scene_offset >= 0)
24 | if self._is_linked():
25 | self._unlink()
26 | self.set_offsets(track_offset, scene_offset)
27 | self._link()
28 |
29 | def unlink(self):
30 | if self._is_linked():
31 | self._unlink()
32 |
33 | def set_slot_launch_button(self, button):
34 | assert ((button == None) or isinstance(button, ButtonElement))
35 | if (self._slot_launch_button != button):
36 | if (self._slot_launch_button != None):
37 | self._slot_launch_button.remove_value_listener(self._slot_launch_value)
38 | self._slot_launch_button = button
39 | if (self._slot_launch_button != None):
40 | self._slot_launch_button.add_value_listener(self._slot_launch_value)
41 |
42 | self.update()
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 |
--------------------------------------------------------------------------------
/YourControllerName/SpecialTransportComponent.py:
--------------------------------------------------------------------------------
1 | # There are lot of things to explore that you can use to add extra functionality to your scipt.
2 | # You'd essentially enable it here by uncommenting then add it YourControllerName.py and MIDI_Map.
3 | # I experiment more and add myself in the future.
4 |
5 | import Live
6 | from _Framework.TransportComponent import TransportComponent
7 | from _Framework.ButtonElement import ButtonElement
8 | from _Framework.EncoderElement import EncoderElement #added
9 | from _Framework.SubjectSlot import subject_slot #added
10 | #TEMPO_TOP = 300.0
11 | #TEMPO_BOTTOM = 40.0
12 | from MIDI_Map import TEMPO_TOP
13 | from MIDI_Map import TEMPO_BOTTOM
14 | class SpecialTransportComponent(TransportComponent):
15 | __doc__ = ' TransportComponent that only uses certain buttons if a shift button is pressed '
16 | def __init__(self):
17 | TransportComponent.__init__(self)
18 | #self._shift_button = None
19 | self._quant_toggle_button = None
20 | #self._shift_pressed = False
21 | self._last_quant_value = Live.Song.RecordingQuantization.rec_q_eight
22 | self.song().add_midi_recording_quantization_listener(self._on_quantisation_changed)
23 | self._on_quantisation_changed()
24 | self._undo_button = None #added from OpenLabs SpecialTransportComponent script
25 | self._redo_button = None #added from OpenLabs SpecialTransportComponent script
26 | #self._bts_button = None #added from OpenLabs SpecialTransportComponent script
27 | self._tempo_encoder_control = None #new addition
28 | return None
29 |
30 | def disconnect(self):
31 | TransportComponent.disconnect(self)
32 | #if self._shift_button != None:
33 | #self._shift_button.remove_value_listener(self._shift_value)
34 | #self._shift_button = None
35 | if self._quant_toggle_button != None:
36 | self._quant_toggle_button.remove_value_listener(self._quant_toggle_value)
37 | self._quant_toggle_button = None
38 | self.song().remove_midi_recording_quantization_listener(self._on_quantisation_changed)
39 | if (self._undo_button != None): #added from OpenLabs SpecialTransportComponent script
40 | self._undo_button.remove_value_listener(self._undo_value)
41 | self._undo_button = None
42 | if (self._redo_button != None): #added from OpenLabs SpecialTransportComponent script
43 | self._redo_button.remove_value_listener(self._redo_value)
44 | self._redo_button = None
45 | #if (self._bts_button != None): #added from OpenLabs SpecialTransportComponent script
46 | #self._bts_button.remove_value_listener(self._bts_value)
47 | #self._bts_button = None
48 | if (self._tempo_encoder_control != None): #new addition
49 | self._tempo_encoder_control.remove_value_listener(self._tempo_encoder_value)
50 | self._tempo_encoder_control = None
51 | return None
52 |
53 | #def set_shift_button(self, button):
54 | #if not(button == None or isinstance(button, ButtonElement) and button.is_momentary()):
55 | #isinstance(button, ButtonElement)
56 | #raise AssertionError
57 | #if self._shift_button != button:
58 | #if self._shift_button != None:
59 | #self._shift_button.remove_value_listener(self._shift_value)
60 | #self._shift_button = button
61 | #if self._shift_button != None:
62 | #self._shift_button.add_value_listener(self._shift_value)
63 | #
64 | #self.update()
65 | #return None
66 |
67 | def set_quant_toggle_button(self, button):
68 | if not(button == None or isinstance(button, ButtonElement) and button.is_momentary()):
69 | isinstance(button, ButtonElement)
70 | raise AssertionError
71 | if self._quant_toggle_button != button:
72 | if self._quant_toggle_button != None:
73 | self._quant_toggle_button.remove_value_listener(self._quant_toggle_value)
74 | self._quant_toggle_button = button
75 | if self._quant_toggle_button != None:
76 | self._quant_toggle_button.add_value_listener(self._quant_toggle_value)
77 |
78 | self.update()
79 | return None
80 |
81 | #def update(self):
82 | #self._on_metronome_changed()
83 | #self._on_overdub_changed()
84 | #self._on_quantisation_changed()
85 | #self._on_nudge_up_changed() #added
86 | #self._on_nudge_down_changed #added
87 |
88 | #def _shift_value(self, value):
89 | #if not self._shift_button != None:
90 | #raise AssertionError
91 | #if not value in range(128):
92 | #raise AssertionError
93 | #self._shift_pressed = value != 0
94 | #if self.is_enabled():
95 | #self.is_enabled()
96 | #self.update()
97 | #else:
98 | #self.is_enabled()
99 | #return None
100 |
101 | #def _metronome_value(self, value):
102 | #if not self._shift_pressed:
103 | ###if self._shift_pressed:
104 | #TransportComponent._metronome_value(self, value)
105 |
106 |
107 | #def _overdub_value(self, value):
108 | #if not self._shift_pressed:
109 | #TransportComponent._overdub_value(self, value)
110 |
111 |
112 | #def _nudge_up_value(self, value): #added
113 | #if not self._shift_pressed:
114 | #TransportComponent._nudge_up_value(self, value)
115 |
116 |
117 | #def _nudge_down_value(self, value): #added
118 | #if not self._shift_pressed:
119 | #TransportComponent._nudge_down_value(self, value)
120 |
121 |
122 | #def _tap_tempo_value(self, value): # Added as Shift + Tap Tempo
123 | #if not self._shift_pressed:
124 | ##if self._shift_pressed:
125 | #TransportComponent._tap_tempo_value(self, value)
126 |
127 |
128 | def _quant_toggle_value(self, value):
129 | assert (self._quant_toggle_button != None)
130 | assert (value in range(128))
131 | assert (self._last_quant_value != Live.Song.RecordingQuantization.rec_q_no_q)
132 | if self.is_enabled(): # and (not self._shift_pressed):
133 | if ((value != 0) or (not self._quant_toggle_button.is_momentary())):
134 | quant_value = self.song().midi_recording_quantization
135 | if (quant_value != Live.Song.RecordingQuantization.rec_q_no_q):
136 | self._last_quant_value = quant_value
137 | self.song().midi_recording_quantization = Live.Song.RecordingQuantization.rec_q_no_q
138 | else:
139 | self.song().midi_recording_quantization = self._last_quant_value
140 |
141 |
142 | #def _on_metronome_changed(self):
143 | #if not self._shift_pressed:
144 | ##if self._shift_pressed:
145 | #TransportComponent._on_metronome_changed(self)
146 |
147 |
148 | #def _on_overdub_changed(self):
149 | #if not self._shift_pressed:
150 | #TransportComponent._on_overdub_changed(self)
151 |
152 |
153 | #def _on_nudge_up_changed(self): #added
154 | #if not self._shift_pressed:
155 | #TransportComponent._on_nudge_up_changed(self)
156 |
157 |
158 | #def _on_nudge_down_changed(self): #added
159 | #if not self._shift_pressed:
160 | #TransportComponent._on_nudge_down_changed(self)
161 |
162 |
163 | def _on_quantisation_changed(self):
164 | if self.is_enabled():
165 | quant_value = self.song().midi_recording_quantization
166 | quant_on = (quant_value != Live.Song.RecordingQuantization.rec_q_no_q)
167 | if quant_on:
168 | self._last_quant_value = quant_value
169 | if self._quant_toggle_button != None: #((not self._shift_pressed) and (self._quant_toggle_button != None)):
170 | if quant_on:
171 | self._quant_toggle_button.turn_on()
172 | else:
173 | self._quant_toggle_button.turn_off()
174 |
175 | """ from OpenLabs module SpecialTransportComponent """
176 |
177 | def set_undo_button(self, undo_button):
178 | assert isinstance(undo_button, (ButtonElement,
179 | type(None)))
180 | if (undo_button != self._undo_button):
181 | if (self._undo_button != None):
182 | self._undo_button.remove_value_listener(self._undo_value)
183 | self._undo_button = undo_button
184 | if (self._undo_button != None):
185 | self._undo_button.add_value_listener(self._undo_value)
186 | self.update()
187 |
188 |
189 |
190 | def set_redo_button(self, redo_button):
191 | assert isinstance(redo_button, (ButtonElement,
192 | type(None)))
193 | if (redo_button != self._redo_button):
194 | if (self._redo_button != None):
195 | self._redo_button.remove_value_listener(self._redo_value)
196 | self._redo_button = redo_button
197 | if (self._redo_button != None):
198 | self._redo_button.add_value_listener(self._redo_value)
199 | self.update()
200 |
201 |
202 | #def set_bts_button(self, bts_button): #"back to start" button
203 | #assert isinstance(bts_button, (ButtonElement,
204 | #type(None)))
205 | #if (bts_button != self._bts_button):
206 | #if (self._bts_button != None):
207 | #self._bts_button.remove_value_listener(self._bts_value)
208 | #self._bts_button = bts_button
209 | #if (self._bts_button != None):
210 | #self._bts_button.add_value_listener(self._bts_value)
211 | #self.update()
212 |
213 |
214 | def _undo_value(self, value):
215 | #if self._shift_pressed: #added
216 | assert (self._undo_button != None)
217 | assert (value in range(128))
218 | if self.is_enabled():
219 | if ((value != 0) or (not self._undo_button.is_momentary())):
220 | if self.song().can_undo:
221 | self.song().undo()
222 |
223 |
224 | def _redo_value(self, value):
225 | #if self._shift_pressed: #added
226 | assert (self._redo_button != None)
227 | assert (value in range(128))
228 | if self.is_enabled():
229 | if ((value != 0) or (not self._redo_button.is_momentary())):
230 | if self.song().can_redo:
231 | self.song().redo()
232 |
233 |
234 | #def _bts_value(self, value):
235 | #assert (self._bts_button != None)
236 | #assert (value in range(128))
237 | #if self.is_enabled():
238 | #if ((value != 0) or (not self._bts_button.is_momentary())):
239 | #self.song().current_song_time = 0.0
240 |
241 |
242 | def _tempo_encoder_value(self, value):
243 | ##if not self._shift_pressed:
244 | #if self._shift_pressed:
245 | assert (self._tempo_encoder_control != None)
246 | assert (value in range(128))
247 | backwards = (value >= 64)
248 | step = 0.1 #step = 1.0 #reduce this for finer control; 1.0 is 1 bpm
249 | if backwards:
250 | amount = (value - 128)
251 | else:
252 | amount = value
253 | tempo = max(20, min(999, (self.song().tempo + (amount * step))))
254 | self.song().tempo = tempo
255 |
256 |
257 | def set_tempo_encoder(self, control):
258 | assert ((control == None) or (isinstance(control, EncoderElement) and (control.message_map_mode() is Live.MidiMap.MapMode.relative_two_compliment)))
259 | if (self._tempo_encoder_control != None):
260 | self._tempo_encoder_control.remove_value_listener(self._tempo_encoder_value)
261 | self._tempo_encoder_control = control
262 | if (self._tempo_encoder_control != None):
263 | self._tempo_encoder_control.add_value_listener(self._tempo_encoder_value)
264 | self.update()
265 |
266 | @subject_slot('value')
267 | def _tempo_value(self, value): #Override to pull tempo range from MIDI_Maps.py
268 | assert (self._tempo_control != None)
269 | assert (value in range(128))
270 | if self.is_enabled():
271 | fraction = ((TEMPO_TOP - TEMPO_BOTTOM) / 127.0)
272 | self.song().tempo = ((fraction * value) + TEMPO_BOTTOM)
273 |
274 |
--------------------------------------------------------------------------------
/YourControllerName/SpecialViewControllerComponent.py:
--------------------------------------------------------------------------------
1 | # There are lot of things to explore that you can use to add extra functionality to your scipt.
2 | # You'd essentially enable it here by uncommenting then add it YourControllerName.py and MIDI_Map.
3 | # I experiment more and add myself in the future.
4 |
5 | # Partial --== Decompile ==-- with fixes
6 | import Live
7 | from _Framework.ControlSurfaceComponent import ControlSurfaceComponent
8 | from _Framework.ButtonElement import ButtonElement
9 | SHOW_PLAYING_CLIP_DELAY = 5
10 | class DetailViewControllerComponent(ControlSurfaceComponent):
11 | __module__ = __name__
12 | __doc__ = ' Component that can toggle the device chain- and clip view of the selected track '
13 |
14 | def __init__(self):
15 | ControlSurfaceComponent.__init__(self)
16 | self._device_clip_toggle_button = None
17 | self._detail_toggle_button = None
18 | self._left_button = None
19 | self._right_button = None
20 | #self._shift_button = None
21 | #self._shift_pressed = False
22 | self._show_playing_clip_ticks_delay = -1
23 | self.application().view.add_is_view_visible_listener('Detail', self._detail_view_visibility_changed)
24 | self._register_timer_callback(self._on_timer)
25 | return None
26 |
27 | def disconnect(self):
28 | self._unregister_timer_callback(self._on_timer)
29 | self.application().view.remove_is_view_visible_listener('Detail', self._detail_view_visibility_changed)
30 | if self._device_clip_toggle_button != None:
31 | self._device_clip_toggle_button.remove_value_listener(self._device_clip_toggle_value)
32 | self._device_clip_toggle_button = None
33 | if self._detail_toggle_button != None:
34 | self._detail_toggle_button.remove_value_listener(self._detail_toggle_value)
35 | self._detail_toggle_button = None
36 | if self._left_button != None:
37 | self._left_button.remove_value_listener(self._nav_value)
38 | self._left_button = None
39 | if self._right_button != None:
40 | self._right_button.remove_value_listener(self._nav_value)
41 | self._right_button = None
42 | #if self._shift_button != None:
43 | #self._shift_button.remove_value_listener(self._shift_value)
44 | #self._shift_button = None
45 | return None
46 |
47 | def set_device_clip_toggle_button(self, button):
48 | if not(button == None or isinstance(button, ButtonElement)):
49 | isinstance(button, ButtonElement)
50 | raise AssertionError
51 | if self._device_clip_toggle_button != button:
52 | if self._device_clip_toggle_button != None:
53 | self._device_clip_toggle_button.remove_value_listener(self._device_clip_toggle_value)
54 | self._device_clip_toggle_button = button
55 | if self._device_clip_toggle_button != None:
56 | self._device_clip_toggle_button.add_value_listener(self._device_clip_toggle_value)
57 |
58 | self.update()
59 | return None
60 |
61 | def set_detail_toggle_button(self, button):
62 | if not(button == None or isinstance(button, ButtonElement)):
63 | isinstance(button, ButtonElement)
64 | raise AssertionError
65 | if self._detail_toggle_button != button:
66 | if self._detail_toggle_button != None:
67 | self._detail_toggle_button.remove_value_listener(self._detail_toggle_value)
68 | self._detail_toggle_button = button
69 | if self._detail_toggle_button != None:
70 | self._detail_toggle_button.add_value_listener(self._detail_toggle_value)
71 |
72 | self.update()
73 | return None
74 |
75 | def set_device_nav_buttons(self, left_button, right_button):
76 | if not(left_button == None or isinstance(left_button, ButtonElement)):
77 | isinstance(left_button, ButtonElement)
78 | raise AssertionError
79 | if not(right_button == None or isinstance(right_button, ButtonElement)):
80 | isinstance(right_button, ButtonElement)
81 | raise AssertionError
82 | identify_sender = True
83 | if self._left_button != None:
84 | self._left_button.remove_value_listener(self._nav_value)
85 | self._left_button = left_button
86 | if self._left_button != None:
87 | self._left_button.add_value_listener(self._nav_value, identify_sender)
88 | if self._right_button != None:
89 | self._right_button.remove_value_listener(self._nav_value)
90 | self._right_button = right_button
91 | if self._right_button != None:
92 | self._right_button.add_value_listener(self._nav_value, identify_sender)
93 |
94 | self.update()
95 | return None
96 |
97 | #def set_shift_button(self, button):
98 | #if not(button == None or isinstance(button, ButtonElement) and button.is_momentary()):
99 | #isinstance(button, ButtonElement)
100 | #raise AssertionError
101 | #if self._shift_button != button:
102 | #if self._shift_button != None:
103 | #self._shift_button.remove_value_listener(self._shift_value)
104 | #self._shift_button = button
105 | #if self._shift_button != None:
106 | #self._shift_button.add_value_listener(self._shift_value)
107 | #
108 | #self.update()
109 | #return None
110 |
111 | def on_enabled_changed(self):
112 | self.update()
113 |
114 | def update(self):
115 | pass
116 | #if self.is_enabled():
117 | #self.is_enabled()
118 | #if not self._shift_pressed:
119 | #self._shift_pressed
120 | #if self._left_button != None:
121 | #self._left_button.turn_off()
122 | #if self._right_button != None:
123 | #self._right_button.turn_off()
124 | #if self._device_clip_toggle_button != None:
125 | #self._device_clip_toggle_button.turn_off()
126 | #self._detail_view_visibility_changed()
127 | #else:
128 | #self._shift_pressed
129 | #else:
130 | #self.is_enabled()
131 | return None
132 |
133 | def _detail_view_visibility_changed(self):
134 | if self.is_enabled() and self._detail_toggle_button != None: #not self._shift_pressed and self._detail_toggle_button != None:
135 | if self.application().view.is_view_visible('Detail'):
136 | self.application().view.is_view_visible('Detail')
137 | self._detail_toggle_button.turn_on()
138 | else:
139 | self.application().view.is_view_visible('Detail')
140 | self._detail_toggle_button.turn_off()
141 | else:
142 | self.is_enabled()
143 | return None
144 |
145 | def _device_clip_toggle_value(self, value):
146 | if not self._device_clip_toggle_button != None:
147 | raise AssertionError
148 | if not value in range(128):
149 | raise AssertionError
150 | if self.is_enabled(): #and not self._shift_pressed:
151 | button_is_momentary = self._device_clip_toggle_button.is_momentary()
152 | if not button_is_momentary or value != 0:
153 | not button_is_momentary
154 | if not self.application().view.is_view_visible('Detail'):
155 | self.application().view.is_view_visible('Detail')
156 | self.application().view.show_view('Detail')
157 | else:
158 | self.application().view.is_view_visible('Detail')
159 | if not self.application().view.is_view_visible('Detail/DeviceChain'):
160 | self.application().view.is_view_visible('Detail/DeviceChain')
161 | self.application().view.show_view('Detail/DeviceChain')
162 | else:
163 | self.application().view.is_view_visible('Detail/DeviceChain')
164 | self.application().view.show_view('Detail/Clip')
165 | if button_is_momentary and value != 0:
166 | self._show_playing_clip_ticks_delay = SHOW_PLAYING_CLIP_DELAY
167 | else:
168 | button_is_momentary
169 | self._show_playing_clip_ticks_delay = -1
170 | else:
171 | self.is_enabled()
172 | return None
173 |
174 |
175 | def _detail_toggle_value(self, value):
176 | assert (self._detail_toggle_button != None)
177 | assert (value in range(128))
178 | if self.is_enabled(): # and (not self._shift_pressed):
179 | if ((not self._detail_toggle_button.is_momentary()) or (value != 0)):
180 | if (not self.application().view.is_view_visible('Detail')):
181 | self.application().view.show_view('Detail')
182 | else:
183 | self.application().view.hide_view('Detail')
184 |
185 |
186 | #def _shift_value(self, value):
187 | #if not self._shift_button != None:
188 | #raise AssertionError
189 | #if not value in range(128):
190 | #raise AssertionError
191 | #self._shift_pressed = value != 0
192 | #self.update()
193 | #return None
194 |
195 | def _nav_value(self, value, sender):
196 | assert ((sender != None) and (sender in (self._left_button,
197 | self._right_button)))
198 | if self.is_enabled(): # and (not self._shift_pressed):
199 | if ((not sender.is_momentary()) or (value != 0)):
200 | modifier_pressed = True
201 | if ((not self.application().view.is_view_visible('Detail')) or (not self.application().view.is_view_visible('Detail/DeviceChain'))):
202 | self.application().view.show_view('Detail')
203 | self.application().view.show_view('Detail/DeviceChain')
204 | else:
205 | direction = Live.Application.Application.View.NavDirection.left
206 | if (sender == self._right_button):
207 | direction = Live.Application.Application.View.NavDirection.right
208 | self.application().view.scroll_view(direction, 'Detail/DeviceChain', (not modifier_pressed))
209 |
210 | def _on_timer(self):
211 | if self.is_enabled(): # and (not self._shift_pressed):
212 | if (self._show_playing_clip_ticks_delay > -1):
213 | if (self._show_playing_clip_ticks_delay == 0):
214 | song = self.song()
215 | playing_slot_index = song.view.selected_track.playing_slot_index
216 | if (playing_slot_index > -1):
217 | song.view.selected_scene = song.scenes[playing_slot_index]
218 | if song.view.highlighted_clip_slot.has_clip:
219 | self.application().view.show_view('Detail/Clip')
220 | self._show_playing_clip_ticks_delay -= 1
221 |
--------------------------------------------------------------------------------
/YourControllerName/SpecialZoomingComponent.py:
--------------------------------------------------------------------------------
1 | # emacs-mode: -*- python-*-
2 | # -*- coding: utf-8 -*-
3 |
4 | import Live
5 | from _Framework.SessionZoomingComponent import SessionZoomingComponent
6 | from _Framework.ButtonElement import ButtonElement
7 | class SpecialZoomingComponent(SessionZoomingComponent):
8 | ' Special ZoomingComponent that uses clip stop buttons for stop all when zoomed '
9 | __module__ = __name__
10 |
11 | def __init__(self, session):
12 | SessionZoomingComponent.__init__(self, session)
13 |
14 |
15 | def _scroll_up(self):
16 | #if self._is_zoomed_out:
17 | height = self._session.height()
18 | track_offset = self._session.track_offset()
19 | scene_offset = self._session.scene_offset()
20 |
21 | if scene_offset > 0:
22 | new_scene_offset = scene_offset
23 | if scene_offset % height > 0:
24 | new_scene_offset -= (scene_offset % height)
25 | else:
26 | new_scene_offset = max(0, scene_offset - height)
27 | self._session.set_offsets(track_offset, new_scene_offset)
28 |
29 | def _scroll_down(self):
30 | #if self._is_zoomed_out:
31 | height = self._session.height()
32 | track_offset = self._session.track_offset()
33 | scene_offset = self._session.scene_offset()
34 | new_scene_offset = scene_offset + height - (scene_offset % height)
35 | self._session.set_offsets(track_offset, new_scene_offset)
36 |
37 | def _scroll_left(self):
38 | #if self._is_zoomed_out:
39 | width = self._session.width()
40 | track_offset = self._session.track_offset()
41 | scene_offset = self._session.scene_offset()
42 | if track_offset > 0:
43 | new_track_offset = track_offset
44 | if track_offset % width > 0:
45 | new_track_offset -= (track_offset % width)
46 | else:
47 | new_track_offset = max(0, track_offset - width)
48 | self._session.set_offsets(new_track_offset, scene_offset)
49 |
50 | def _scroll_right(self):
51 | #if self._is_zoomed_out:
52 | width = self._session.width()
53 | track_offset = self._session.track_offset()
54 | scene_offset = self._session.scene_offset()
55 | new_track_offset = track_offset + width - (track_offset % width)
56 | self._session.set_offsets(new_track_offset, scene_offset)
57 |
--------------------------------------------------------------------------------
/YourControllerName/YourControllerName.py:
--------------------------------------------------------------------------------
1 | from __future__ import with_statement
2 |
3 | import Live
4 | from _Framework.ControlSurface import ControlSurface
5 | from _Framework.InputControlElement import *
6 | from _Framework.SliderElement import SliderElement
7 | from _Framework.ButtonElement import ButtonElement
8 | from _Framework.ButtonMatrixElement import ButtonMatrixElement
9 | from _Framework.ChannelStripComponent import ChannelStripComponent
10 | from _Framework.DeviceComponent import DeviceComponent
11 | from _Framework.ControlSurfaceComponent import ControlSurfaceComponent
12 | from _Framework.SessionZoomingComponent import SessionZoomingComponent
13 | from SpecialMixerComponent import SpecialMixerComponent
14 | from SpecialTransportComponent import SpecialTransportComponent
15 | from SpecialSessionComponent import SpecialSessionComponent
16 | from SpecialZoomingComponent import SpecialZoomingComponent
17 | from SpecialViewControllerComponent import DetailViewControllerComponent
18 | from MIDI_Map import *
19 |
20 |
21 | # MIDI_NOTE_TYPE = 0
22 | # MIDI_CC_TYPE = 1
23 | # MIDI_PB_TYPE = 2
24 |
25 |
26 | class YourControllerName(ControlSurface): # Make sure you update the name
27 | __doc__ = " Script for YourControllerName in APC emulation mode " # Make sure you update the name
28 |
29 | _active_instances = []
30 |
31 | def _combine_active_instances():
32 | track_offset = 0
33 | scene_offset = 0
34 | for instance in YourControllerName._active_instances: # Make sure you update the name
35 | instance._activate_combination_mode(track_offset, scene_offset)
36 | track_offset += instance._session.width()
37 | _combine_active_instances = staticmethod(_combine_active_instances)
38 |
39 | def __init__(self, c_instance):
40 | ControlSurface.__init__(self, c_instance)
41 | # self.set_suppress_rebuild_requests(True)
42 | with self.component_guard():
43 | self._note_map = []
44 | self._ctrl_map = []
45 | self._load_MIDI_map()
46 | self._session = None
47 | self._session_zoom = None
48 | self._mixer = None
49 | self._setup_session_control()
50 | self._setup_mixer_control()
51 | self._session.set_mixer(self._mixer)
52 | self._setup_device_and_transport_control()
53 | self.set_highlighting_session_component(self._session)
54 | # self.set_suppress_rebuild_requests(False)
55 | self._pads = []
56 | self._load_pad_translations()
57 | self._do_combine()
58 |
59 | def disconnect(self):
60 | self._note_map = None
61 | self._ctrl_map = None
62 | self._pads = None
63 | self._do_uncombine()
64 | self._shift_button = None
65 | self._session = None
66 | self._session_zoom = None
67 | self._mixer = None
68 | ControlSurface.disconnect(self)
69 |
70 | def _do_combine(self):
71 | if self not in YourControllerName._active_instances: # Make sure you update the name
72 | YourControllerName._active_instances.append(self) # Make sure you update the name
73 | YourControllerName._combine_active_instances() # Make sure you update the name
74 |
75 | def _do_uncombine(self):
76 | if (self in YourControllerName._active_instances) and YourControllerName._active_instances.remove(self): # Make sure you update the name
77 | self._session.unlink()
78 | YourControllerName._combine_active_instances() # Make sure you update the name
79 |
80 | def _activate_combination_mode(self, track_offset, scene_offset):
81 | if TRACK_OFFSET != -1:
82 | track_offset = TRACK_OFFSET
83 | if SCENE_OFFSET != -1:
84 | scene_offset = SCENE_OFFSET
85 | self._session.link_with_track_offset(track_offset, scene_offset)
86 |
87 | def _setup_session_control(self):
88 | is_momentary = True
89 | self._session = SpecialSessionComponent(TSB_X, TSB_Y) # Track selection box size (X,Y) (horizontal, vertical).
90 | self._session.name = 'Session_Control'
91 | self._session.set_track_bank_buttons(self._note_map[SESSIONRIGHT], self._note_map[SESSIONLEFT])
92 | self._session.set_scene_bank_buttons(self._note_map[SESSIONDOWN], self._note_map[SESSIONUP])
93 | self._session.set_select_buttons(self._note_map[SCENEDN], self._note_map[SCENEUP])
94 | # range(tsb_x) is the horizontal count for the track selection box
95 | self._scene_launch_buttons = [self._note_map[SCENELAUNCH[index]] for index in range(TSB_X)]
96 | # range(tsb_y) Range value is the track selection
97 | self._track_stop_buttons = [self._note_map[TRACKSTOP[index]] for index in range(TSB_Y)]
98 | self._session.set_stop_all_clips_button(self._note_map[STOPALLCLIPS])
99 | self._session.set_stop_track_clip_buttons(tuple(self._track_stop_buttons))
100 | self._session.selected_scene().name = 'Selected_Scene'
101 | self._session.selected_scene().set_launch_button(self._note_map[SELSCENELAUNCH])
102 | self._session.set_slot_launch_button(self._note_map[SELCLIPLAUNCH])
103 | for scene_index in range(TSB_Y): # Change range() value to set the vertical count for track selection box
104 | scene = self._session.scene(scene_index)
105 | scene.name = 'Scene_' + str(scene_index)
106 | button_row = []
107 | scene.set_launch_button(self._scene_launch_buttons[scene_index])
108 | scene.set_triggered_value(2)
109 | for track_index in range(TSB_X): # Change range() value to set the horizontal count for track selection box
110 | button = self._note_map[CLIPNOTEMAP[scene_index][track_index]]
111 | button_row.append(button)
112 | clip_slot = scene.clip_slot(track_index)
113 | clip_slot.name = str(track_index) + '_Clip_Slot_' + str(scene_index)
114 | clip_slot.set_launch_button(button)
115 | self._session_zoom = SpecialZoomingComponent(self._session)
116 | self._session_zoom.name = 'Session_Overview'
117 | self._session_zoom.set_nav_buttons(self._note_map[ZOOMUP], self._note_map[ZOOMDOWN], self._note_map[ZOOMLEFT], self._note_map[ZOOMRIGHT])
118 |
119 | def _setup_mixer_control(self):
120 |
121 | is_momentary = True
122 | self._mixer = SpecialMixerComponent(8)
123 | self._mixer.name = 'Mixer'
124 | self._mixer.master_strip().name = 'Master_Channel_Strip'
125 | self._mixer.master_strip().set_select_button(self._note_map[MASTERSEL])
126 | self._mixer.selected_strip().name = 'Selected_Channel_Strip'
127 | self._mixer.set_select_buttons(self._note_map[TRACKRIGHT], self._note_map[TRACKLEFT])
128 | self._mixer.set_crossfader_control(self._ctrl_map[CROSSFADER])
129 | self._mixer.set_prehear_volume_control(self._ctrl_map[CUELEVEL])
130 | self._mixer.master_strip().set_volume_control(self._ctrl_map[MASTERVOLUME])
131 | self._mixer.selected_strip().set_arm_button(self._note_map[SELTRACKREC])
132 | self._mixer.selected_strip().set_solo_button(self._note_map[SELTRACKSOLO])
133 | self._mixer.selected_strip().set_mute_button(self._note_map[SELTRACKMUTE])
134 | for track in range(8):
135 | # My guess is that altering the range here will allow you to alter the range of mixer tracks
136 | # So if you had a 16 fader mixer, this would come in handy.
137 | strip = self._mixer.channel_strip(track)
138 | strip.name = 'Channel_Strip_' + str(track)
139 | strip.set_arm_button(self._note_map[TRACKREC[track]])
140 | strip.set_solo_button(self._note_map[TRACKSOLO[track]])
141 | strip.set_mute_button(self._note_map[TRACKMUTE[track]])
142 | strip.set_select_button(self._note_map[TRACKSEL[track]])
143 | strip.set_volume_control(self._ctrl_map[TRACKVOL[track]])
144 | strip.set_pan_control(self._ctrl_map[TRACKPAN[track]])
145 | strip.set_send_controls((self._ctrl_map[TRACKSENDA[track]], self._ctrl_map[TRACKSENDB[track]], self._ctrl_map[TRACKSENDC[track]]))
146 | strip.set_invert_mute_feedback(True)
147 |
148 | def _setup_device_and_transport_control(self):
149 | is_momentary = True
150 | self._device = DeviceComponent()
151 | self._device.name = 'Device_Component'
152 | device_bank_buttons = []
153 | device_param_controls = []
154 | for index in range(8):
155 | device_param_controls.append(self._ctrl_map[PARAMCONTROL[index]])
156 | device_bank_buttons.append(self._note_map[DEVICEBANK[index]])
157 | if None not in device_bank_buttons:
158 | self._device.set_bank_buttons(tuple(device_bank_buttons))
159 | if None not in device_param_controls:
160 | self._device.set_parameter_controls(tuple(device_param_controls))
161 | self._device.set_on_off_button(self._note_map[DEVICEONOFF])
162 | self._device.set_bank_nav_buttons(self._note_map[DEVICEBANKNAVLEFT], self._note_map[DEVICEBANKNAVRIGHT])
163 | self._device.set_lock_button(self._note_map[DEVICELOCK])
164 | self.set_device_component(self._device)
165 |
166 | detail_view_toggler = DetailViewControllerComponent()
167 | detail_view_toggler.name = 'Detail_View_Control'
168 | detail_view_toggler.set_device_clip_toggle_button(self._note_map[CLIPTRACKVIEW])
169 | detail_view_toggler.set_detail_toggle_button(self._note_map[DETAILVIEW])
170 | detail_view_toggler.set_device_nav_buttons(self._note_map[DEVICENAVLEFT], self._note_map[DEVICENAVRIGHT])
171 |
172 | transport = SpecialTransportComponent()
173 | transport.name = 'Transport'
174 | transport.set_play_button(self._note_map[PLAY])
175 | transport.set_stop_button(self._note_map[STOP])
176 | transport.set_record_button(self._note_map[REC])
177 | transport.set_nudge_buttons(self._note_map[NUDGEUP], self._note_map[NUDGEDOWN])
178 | transport.set_undo_button(self._note_map[UNDO])
179 | transport.set_redo_button(self._note_map[REDO])
180 | transport.set_tap_tempo_button(self._note_map[TAPTEMPO])
181 | transport.set_quant_toggle_button(self._note_map[RECQUANT])
182 | transport.set_overdub_button(self._note_map[OVERDUB])
183 | transport.set_metronome_button(self._note_map[METRONOME])
184 | transport.set_tempo_control(self._ctrl_map[TEMPOCONTROL])
185 | transport.set_loop_button(self._note_map[LOOP])
186 | transport.set_seek_buttons(self._note_map[SEEKFWD], self._note_map[SEEKRWD])
187 | transport.set_punch_buttons(self._note_map[PUNCHIN], self._note_map[PUNCHOUT])
188 | # transport.set_song_position_control(self._ctrl_map[SONGPOSITION]) #still not implemented as of Live 8.1.6
189 |
190 | def _on_selected_track_changed(self):
191 | ControlSurface._on_selected_track_changed(self)
192 | track = self.song().view.selected_track
193 | device_to_select = track.view.selected_device
194 | if device_to_select is None and len(track.devices) > 0:
195 | device_to_select = track.devices[0]
196 | if device_to_select is not None:
197 | self.song().view.select_device(device_to_select)
198 | self._device_component.set_device(device_to_select)
199 |
200 | def _load_pad_translations(self):
201 | if -1 not in DRUM_PADS:
202 | pad = []
203 | for row in range(4):
204 | for col in range(4):
205 | pad = (col, row, DRUM_PADS[row*4 + col], PADCHANNEL,)
206 | self._pads.append(pad)
207 | self.set_pad_translations(tuple(self._pads))
208 |
209 | def _load_MIDI_map(self):
210 | is_momentary = True
211 | for note in range(128):
212 | button = ButtonElement(is_momentary, MESSAGETYPE, BUTTONCHANNEL, note)
213 | button.name = 'Note_' + str(note)
214 | self._note_map.append(button)
215 | self._note_map.append(None) # add None to the end of the list, selectable with [-1]
216 | if MESSAGETYPE == MIDI_CC_TYPE and BUTTONCHANNEL == SLIDERCHANNEL:
217 | for ctrl in range(128):
218 | self._ctrl_map.append(None)
219 | else:
220 | for ctrl in range(128):
221 | control = SliderElement(MIDI_CC_TYPE, SLIDERCHANNEL, ctrl)
222 | control.name = 'Ctrl_' + str(ctrl)
223 | self._ctrl_map.append(control)
224 | self._ctrl_map.append(None)
225 |
--------------------------------------------------------------------------------
/YourControllerName/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | import Live
3 | from YourControllerName import YourControllerName
4 |
5 | def create_instance(c_instance):
6 | ' Creates and returns the APC20 script '
7 | return YourControllerName(c_instance)
8 |
9 |
10 | # local variables:
11 | # tab-width: 4
12 |
--------------------------------------------------------------------------------