This is a follow up. Now that I have it working, I am coming back with some how tos.
Huge disclaimer: Any of the below code or other text is provided without any warranties whatsoever. Use at your own risk.
Being able to control Live like this is incredible.
I have many years of programming experience, but I have no Python experience until now. I found it relatively easy to pick up.
I would encourage anyone who is both interested in Live and who is not yet a programmer, but who is interested in learning programming to try this out. It would be a great way to learn programming with something that you can relate to.
Basically, what my code does is MIDI-map buttons and encoders to Live devices, just like regular MIDI mapping.
Unlike Live's MIDI mapping, a huge advantage to my code is that it can map to any device with the same matching name. That is particularly useful for my scenario, because I have a lot of the same MIDI effects on tracks. Being able to control them all at the same time without tedious MIDI mappings is simply awesome. Also, I have tracks with dedicated Drum Racks, which send MIDI data to External Instrument devices. So, essentially, any Live track can be its own MIDI controller. Combine that with Push, and wow. It is hard to overstate how powerful that is.
My code also has listeners so that when devices are added and parameters are renamed, it updates itself automatically. So, if you are using the multi map thing I described above and you add a new device, it will include that.
I took my main code and attempted to reduce it down to some simple examples, and I posted it below. You will notice that the parameters are stored in lists. That is because of the multiple parameter mapping stuff I described above.
The below code requires the init script. See the first link above for how to set that up.
The loadParameter function is a big loop, which handles multiple levels of devices. You may wish to customize this for your needs.
Code: Select all
from ableton.v2.control_surface import IdentifiableControlSurface, MIDI_CC_TYPE
from ableton.v2.control_surface.elements import ButtonElement, EncoderElement
import Live
import logging
logger = logging.getLogger(__name__)
class MyControlSurface(IdentifiableControlSurface):
def __init__(self, *a, **k):
super(MyControlSurface, self).__init__( *a, **k)
with self.component_guard():
self.parameterListMain = []
self.parameterCh1CC1s = []
self.parameterCh1CC2s = []
self.parameterCh1CC6s = []
self.parameterCh1CC7s = []
buttonPitchDropOff = ButtonElement(False, 0, 0, 60).add_value_listener(self.buttonPressedPitchDropOff)
buttonPitchDropOn = ButtonElement(False, 0, 0, 61).add_value_listener(self.buttonPressedPitchDropOn)
encoderBusFX1Macro1 = EncoderElement(MIDI_CC_TYPE, 0, 6, Live.MidiMap.MapMode.absolute).add_value_listener(self.encoderTurnedBusFX1Macro1)
encoderBusFX1Macro2 = EncoderElement(MIDI_CC_TYPE, 0, 7, Live.MidiMap.MapMode.absolute).add_value_listener(self.encoderTurnedBusFX1Macro2)
self.song.add_tracks_listener(self.trackChanged)
for track in self.song.tracks:
track.add_devices_listener(self.deviceChanged)
for deviceLevel0 in track.devices:
deviceLevel0.add_name_listener(self.deviceLevel0NameChanged)
self.loadParameters()
logger.info("MyControlSurface loaded")
self.show_message("MyControlSurface loaded")
def buttonPressedPitchDropOff(self, value):
self.setParameter(self.parameterCh1CC1s, 0)
self.setParameter(self.parameterCh1CC2s, 0)
def buttonPressedPitchDropOn(self, value):
self.setParameter(self.parameterCh1CC1s, 1)
self.setParameter(self.parameterCh1CC2s, 127)
def encoderTurnedBusFX1Macro1(self, value):self.setParameter(self.parameterCh1CC6s, value)
def encoderTurnedBusFX1Macro2(self, value):self.setParameter(self.parameterCh1CC7s, value)
def loadParameter(self, deviceNameLevel0, chainNameLevel0, deviceNameLevel1, chainNameLevel1, deviceNameLevel2, parameterName, parameterList):
parameterList.clear()
for track in self.song.tracks:
for deviceLevel0 in track.devices:
if deviceLevel0.name.upper() == deviceNameLevel0.upper():
if chainNameLevel0 == None:
for parameter in deviceLevel0.parameters:
self.appendParameter(parameterList, parameterName, parameter)
else:
for chainLevel0 in deviceLevel0.chains:
if chainLevel0.name.upper() == chainNameLevel0.upper():
for deviceLevel1 in chainLevel0.devices:
if deviceLevel1.name.upper() == deviceNameLevel1.upper():
if chainNameLevel1 == None:
for parameter in deviceLevel1.parameters:
self.appendParameter(parameterList, parameterName, parameter)
else:
for chainLevel1 in deviceLevel1.chains:
if chainLevel1.name.upper() == chainNameLevel1.upper():
for deviceLevel2 in chainLevel1.devices:
if deviceLevel2.name.upper() == deviceNameLevel2.upper():
for parameter in deviceLevel2.parameters:
self.appendParameter(parameterList, parameterName, parameter)
def appendParameter(self, parameterList, parameterName, parameter):
if (parameterName.upper() == "DEVICE ON" and parameter.name.upper().startswith('DEVICE ON')) or parameter.name.upper() == parameterName.upper():
if not parameter.name_has_listener(self.parameterNameChanged):
parameter.add_name_listener(self.parameterNameChanged)
parameterList.append(parameter)
self.parameterListMain.append(parameter)
def setParameter(self, parameterList, value):
for parameter in parameterList:
parameter.value = value
def loadParameters(self):
self.parameterListMain.clear()
self.loadParameter("Control Bus FX 1", None, None, None, None, "Bus FX 1 Macro 1", self.parameterBusFXControlControlBusFX1Macro1s)
self.loadParameter("Control Bus FX 1", None, None, None, None, "Bus FX 1 Macro 2", self.parameterBusFXControlControlBusFX1Macro2s)
self.loadParameter("Pitch Drop", None, None, None, None, "Device On", self.parameterCh1CC1s)
self.loadParameter("Pitch Drop", None, None, None, None, "Drop Activate", self.parameterCh1CC2s)
def trackChanged(self):
for track in self.song.tracks:
if not track.devices_has_listener(self.deviceChanged):
track.add_devices_listener(self.deviceChanged)
def deviceChanged(self):
for track in self.song.tracks:
for deviceLevel0 in track.devices:
if not deviceLevel0.name_has_listener(self.deviceLevel0NameChanged):
deviceLevel0.add_name_listener(self.deviceLevel0NameChanged)
self.loadParameters()
def deviceLevel0NameChanged(self):
self.loadParameters()
def parameterNameChanged(self):
self.loadParameters()
def getDeviceLevel0(self, trackName, deviceLevel0Name):
for track in self.song.tracks:
if track.name.upper() == trackName.upper():
for deviceLevel0 in track.devices:
if deviceLevel0.name.upper() == deviceLevel0Name.upper():
return deviceLevel0
def removeListeners(self):
if self.song.tracks_has_listener(self.trackChanged):
self.song.remove_tracks_listener(self.trackChanged)
for track in self.song.tracks:
if track.devices_has_listener(self.deviceChanged):
track.remove_devices_listener(self.deviceChanged)
for deviceLevel0 in track.devices:
if deviceLevel0.name_has_listener(self.deviceLevel0NameChanged):
deviceLevel0.remove_name_listener(self.deviceLevel0NameChanged)
for parameter in self.parameterListMain:
if parameter.name_has_listener(self.parameterNameChanged):
parameter.remove_name_listener(self.parameterNameChanged)
def disconnect(self):
self.removeListeners()
self.parameterListMain = None
self.parameterCh1CC1s = None
self.parameterCh1CC2s = None
self.parameterCh1CC6s = None
self.parameterCh1CC7s = None
super(MyControlSurface, self).disconnect()
logger.info("MyControlSurface unloaded")