Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
Awesome! You’ve inspired me to do this. Got VS Code setup last night and made a ton of progress. You are helping me so much. Gonna upgrade my log game now.
Things I have learned:
-I spent an hour trying to get Hello World working. I was doing it just like you, but it just would not work. I kept getting errors with the basic import of my main class. Then I realized I was using Live 11 and you are using Live 10. So, I looked at the decompiled Live 11 remote scripts and realized the import syntax is a little different in Live 11, presumably due to the new version of Python used in Live 11. So, whenever you get there, keep that in mind
-Even though my scripts are in the Ableton folder, not user folder, reloading my script by switching back and forth in Live’s MIDI setup does not always work. I have to restart Live often
Things I have learned:
-I spent an hour trying to get Hello World working. I was doing it just like you, but it just would not work. I kept getting errors with the basic import of my main class. Then I realized I was using Live 11 and you are using Live 10. So, I looked at the decompiled Live 11 remote scripts and realized the import syntax is a little different in Live 11, presumably due to the new version of Python used in Live 11. So, whenever you get there, keep that in mind
-Even though my scripts are in the Ableton folder, not user folder, reloading my script by switching back and forth in Live’s MIDI setup does not always work. I have to restart Live often
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
That's fantastic! Good luck
I looked at the 11 scripts very briefly and didn't find the difference, could you tell me what it is? I'm not sure I'll get Live 11, but it would be good to know regardless...jbone1313 wrote: ↑Mon Mar 01, 2021 2:40 pm-I spent an hour trying to get Hello World working. I was doing it just like you, but it just would not work. I kept getting errors with the basic import of my main class. Then I realized I was using Live 11 and you are using Live 10. So, I looked at the decompiled Live 11 remote scripts and realized the import syntax is a little different in Live 11, presumably due to the new version of Python used in Live 11. So, whenever you get there, keep that in mind
Last edited by wayfinder on Mon Mar 01, 2021 5:15 pm, edited 1 time in total.
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
Of course, sorry. At the time, I could not think of a way to describe it. But, it is easy. In your case, I would have had to change this:
From: import wfK1
To: from .wfK1 import K1
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
Ah, thank you! Looks like that works in Live 10 already, I just changed it and made my script COMPLETELY FUTURE-PROOF
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
...and MY SCRIPT IS DONE. I am beside myself with how awesome this is. I feel like a beast!
Wayfinder, thank you so much again!
In case anyone is interested, I pasted the script below.
The only thing I am a little unsure of is that every time I press a button, it loops through all tracks and all devices and updates the list. Originally, I did that one time, when the script loads. But, doing it on each button press means that any devices I add after load will be included, without needing to restart Live. If I notice any performance issues, then I might look for a way to handle it better. Perhaps there is a way I can listen for some kind of "on device added" event or something.
Usual disclaimer: This code is distributed WITHOUT ANY WARRANTY WHATSOEVER. Use at your own risk.
This code does what I was describing earlier in the thread. For any device named "REPEATERMASTER" etc., it changes the dial values based on a Push button press. The awesomist part is that I am using a Drum Rack, which sends the notes out of External Instrument device to an internal MIDI port. No MIDI mapping needed.
SICK!
Wayfinder, thank you so much again!
In case anyone is interested, I pasted the script below.
The only thing I am a little unsure of is that every time I press a button, it loops through all tracks and all devices and updates the list. Originally, I did that one time, when the script loads. But, doing it on each button press means that any devices I add after load will be included, without needing to restart Live. If I notice any performance issues, then I might look for a way to handle it better. Perhaps there is a way I can listen for some kind of "on device added" event or something.
Usual disclaimer: This code is distributed WITHOUT ANY WARRANTY WHATSOEVER. Use at your own risk.
This code does what I was describing earlier in the thread. For any device named "REPEATERMASTER" etc., it changes the dial values based on a Push button press. The awesomist part is that I am using a Drum Rack, which sends the notes out of External Instrument device to an internal MIDI port. No MIDI mapping needed.
SICK!
Code: Select all
from _Framework.ControlSurface import ControlSurface
from _Framework.ButtonElement import ButtonElement
class MyControlSurface(ControlSurface):
def __init__(self, instance):
super(MyControlSurface, self).__init__(instance, False)
with self.component_guard():
self.show_message("MyControlSurface loaded")
#self.log_message("MyControlSurface loaded")
self._repeaterNames = ["REPEATERMASTER", "REPEATERDRUMS", "REPEATEROTHER"]
buttonRepeaterMasterOff = ButtonElement(False, 0, 0, 36)
buttonRepeaterMaster1_2 = ButtonElement(False, 0, 0, 37)
buttonRepeaterMaster1_4 = ButtonElement(False, 0, 0, 38)
buttonRepeaterMaster1_8 = ButtonElement(False, 0, 0, 39)
buttonRepeaterMaster1_16 = ButtonElement(False, 0, 0, 40)
buttonRepeaterMaster1_32 = ButtonElement(False, 0, 0, 41)
buttonRepeaterMaster1_6 = ButtonElement(False, 0, 0, 42)
buttonRepeaterMaster1_3 = ButtonElement(False, 0, 0, 43)
buttonRepeaterMasterOff.add_value_listener(self.buttonRepeaterMasterOffPressed)
buttonRepeaterMaster1_2.add_value_listener(self.buttonRepeaterMaster1_2Pressed)
buttonRepeaterMaster1_4.add_value_listener(self.buttonRepeaterMaster1_4Pressed)
buttonRepeaterMaster1_8.add_value_listener(self.buttonRepeaterMaster1_8Pressed)
buttonRepeaterMaster1_16.add_value_listener(self.buttonRepeaterMaster1_16Pressed)
buttonRepeaterMaster1_32.add_value_listener(self.buttonRepeaterMaster1_32Pressed)
buttonRepeaterMaster1_6.add_value_listener(self.buttonRepeaterMaster1_6Pressed)
buttonRepeaterMaster1_3.add_value_listener(self.buttonRepeaterMaster1_3Pressed)
buttonRepeaterDrumsOff = ButtonElement(False, 0, 0, 44)
buttonRepeaterDrums1_2 = ButtonElement(False, 0, 0, 45)
buttonRepeaterDrums1_4 = ButtonElement(False, 0, 0, 46)
buttonRepeaterDrums1_8 = ButtonElement(False, 0, 0, 47)
buttonRepeaterDrums1_16 = ButtonElement(False, 0, 0, 48)
buttonRepeaterDrums1_32 = ButtonElement(False, 0, 0, 49)
buttonRepeaterDrums1_6 = ButtonElement(False, 0, 0, 50)
buttonRepeaterDrums1_3 = ButtonElement(False, 0, 0, 51)
buttonRepeaterDrumsOff.add_value_listener(self.buttonRepeaterDrumsOffPressed)
buttonRepeaterDrums1_2.add_value_listener(self.buttonRepeaterDrums1_2Pressed)
buttonRepeaterDrums1_4.add_value_listener(self.buttonRepeaterDrums1_4Pressed)
buttonRepeaterDrums1_8.add_value_listener(self.buttonRepeaterDrums1_8Pressed)
buttonRepeaterDrums1_16.add_value_listener(self.buttonRepeaterDrums1_16Pressed)
buttonRepeaterDrums1_32.add_value_listener(self.buttonRepeaterDrums1_32Pressed)
buttonRepeaterDrums1_6.add_value_listener(self.buttonRepeaterDrums1_6Pressed)
buttonRepeaterDrums1_3.add_value_listener(self.buttonRepeaterDrums1_3Pressed)
buttonRepeaterOtherOff = ButtonElement(False, 0, 0, 52)
buttonRepeaterOther1_2 = ButtonElement(False, 0, 0, 53)
buttonRepeaterOther1_4 = ButtonElement(False, 0, 0, 54)
buttonRepeaterOther1_8 = ButtonElement(False, 0, 0, 55)
buttonRepeaterOther1_16 = ButtonElement(False, 0, 0, 56)
buttonRepeaterOther1_32 = ButtonElement(False, 0, 0, 57)
buttonRepeaterOther1_6 = ButtonElement(False, 0, 0, 58)
buttonRepeaterOther1_3 = ButtonElement(False, 0, 0, 59)
buttonRepeaterOtherOff.add_value_listener(self.buttonRepeaterOtherOffPressed)
buttonRepeaterOther1_2.add_value_listener(self.buttonRepeaterOther1_2Pressed)
buttonRepeaterOther1_4.add_value_listener(self.buttonRepeaterOther1_4Pressed)
buttonRepeaterOther1_8.add_value_listener(self.buttonRepeaterOther1_8Pressed)
buttonRepeaterOther1_16.add_value_listener(self.buttonRepeaterOther1_16Pressed)
buttonRepeaterOther1_32.add_value_listener(self.buttonRepeaterOther1_32Pressed)
buttonRepeaterOther1_6.add_value_listener(self.buttonRepeaterOther1_6Pressed)
buttonRepeaterOther1_3.add_value_listener(self.buttonRepeaterOther1_3Pressed)
def buttonRepeaterMasterOffPressed(self, value):
self.setButtonRepeater("REPEATERMASTER",0,0)
def buttonRepeaterMaster1_2Pressed(self, value):
self.setButtonRepeater("REPEATERMASTER",127,107)
def buttonRepeaterMaster1_4Pressed(self, value):
self.setButtonRepeater("REPEATERMASTER",127,96)
def buttonRepeaterMaster1_8Pressed(self, value):
self.setButtonRepeater("REPEATERMASTER",127,78)
def buttonRepeaterMaster1_16Pressed(self, value):
self.setButtonRepeater("REPEATERMASTER",127,61)
def buttonRepeaterMaster1_32Pressed(self, value):
self.setButtonRepeater("REPEATERMASTER",127,44)
def buttonRepeaterMaster1_6Pressed(self, value):
self.setButtonRepeater("REPEATERMASTER",127,84)
def buttonRepeaterMaster1_3Pressed(self, value):
self.setButtonRepeater("REPEATERMASTER",127,100)
def buttonRepeaterDrumsOffPressed(self, value):
self.setButtonRepeater("REPEATERDRUMS",0,0)
def buttonRepeaterDrums1_2Pressed(self, value):
self.setButtonRepeater("REPEATERDRUMS",127,107)
def buttonRepeaterDrums1_4Pressed(self, value):
self.setButtonRepeater("REPEATERDRUMS",127,96)
def buttonRepeaterDrums1_8Pressed(self, value):
self.setButtonRepeater("REPEATERDRUMS",127,78)
def buttonRepeaterDrums1_16Pressed(self, value):
self.setButtonRepeater("REPEATERDRUMS",127,61)
def buttonRepeaterDrums1_32Pressed(self, value):
self.setButtonRepeater("REPEATERDRUMS",127,44)
def buttonRepeaterDrums1_6Pressed(self, value):
self.setButtonRepeater("REPEATERDRUMS",127,84)
def buttonRepeaterDrums1_3Pressed(self, value):
self.setButtonRepeater("REPEATERDRUMS",127,100)
def buttonRepeaterOtherOffPressed(self, value):
self.setButtonRepeater("REPEATEROTHER",0,0)
def buttonRepeaterOther1_2Pressed(self, value):
self.setButtonRepeater("REPEATEROTHER",127,107)
def buttonRepeaterOther1_4Pressed(self, value):
self.setButtonRepeater("REPEATEROTHER",127,96)
def buttonRepeaterOther1_8Pressed(self, value):
self.setButtonRepeater("REPEATEROTHER",127,78)
def buttonRepeaterOther1_16Pressed(self, value):
self.setButtonRepeater("REPEATEROTHER",127,61)
def buttonRepeaterOther1_32Pressed(self, value):
self.setButtonRepeater("REPEATEROTHER",127,44)
def buttonRepeaterOther1_6Pressed(self, value):
self.setButtonRepeater("REPEATEROTHER",127,84)
def buttonRepeaterOther1_3Pressed(self, value):
self.setButtonRepeater("REPEATEROTHER",127,100)
def setButtonRepeater(self, deviceName, repeatValue, gridValue):
self._repeaters = []
for track in self.song().tracks:
for device in track.devices:
if device.name.upper() in self._repeaterNames:
self._repeaters.append(device)
for device in self._repeaters:
for parameter in device.parameters:
if device.name.upper() == deviceName:
if parameter.name.upper() == "REPEAT":
parameter.value = repeatValue
if parameter.name.upper() == "GRID":
parameter.value = gridValue
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
Implementing the Advanced LED Behavior
I'm not sure that the method I'm using is ideal, but I'm still pretty proud I figured it out. There was not much in the way of examples to rely on with this - I could not find any scripts that had LEDs blinking in time to the beat (I think some controllers have that built in, but it didn't appear to be in any of the remote scripts), so I had to come up with my own approach here. The basic concept is that I attach a listener to the song position and use that to figure out a blinking pattern in time to the music. When the pattern switches between on and off, I go through all the LEDs in my red box and set their color according to their associated clip slot's content: Off for an empty slot, the button's color for a slot with a clip in it, and blinking in that color when that clip is currently playing. I keep track of the beat phase with a global variable:
Code: Select all
beat_phase = 127 # this will be a velocity value, 0 is off and anything else is on
Code: Select all
self.song().add_current_song_time_listener(self.beat_listener)
Code: Select all
def beat_listener(self):
global beat_phase
# this saves the previous value of the beat phase for comparison
bp = beat_phase
# the sub_division value counts from 1 to 4 over a single beat
value = self.song().get_current_beats_song_time().sub_division
# I use the first half of the beat for on, and the second half for off
if value is 1 or value is 2:
beat_phase = 127
else:
beat_phase = 0
# now I compare the new phase value to the previous one, and
# only update the LEDs when it has changed. this is necessary
# because the listener that triggers this function fires like ten times
# per second, and it would probably not be super great to update
# 16 LEDs every time.
if bp is not beat_phase:
self.LED_updater()
Code: Select all
def LED_updater(self):
global beat_phase
for column in range(16):
# accessing the clip slot with the horizontal position determined by the iterator
# and the vertical position by where the red box is
slot = self.song().tracks[column].clip_slots[self.session._scene_offset]
# here's the logic for which clip gets which behavior. the first if catches filled
# slots and groups with filled slots (ON!), then out of those the second if catches
# the ones that are playing (on for the first half of the beat, off for the second)
if slot.has_clip or slot.controls_other_clips:
bp = 127
if slot.is_playing and self.song().is_playing:
bp = beat_phase
else:
bp = 0
# and here's the command to the ButtonElement in that position (track number
# column, row number 0 (remember, I only have the one scene)
self.matrix.send_value(column, 0, bp)
One slight inconvenience is that it only works while the song is playing, because the updater is called by the song position listener callback. So I added calls to LED_updater to both the init code and the function for moving the red box up and down, and now it also works when playback is stopped. One bug I haven't found yet is that after the first LED update in the init function, the LEDs that it correctly turns on will turn off again after like half a second, and I haven't yet figured out why. There's always something, isn't there?
The next thing I want to try and do is implement control modifiers, like shift keys to temporarily give elements a different function.
Last edited by wayfinder on Tue Mar 02, 2021 12:29 pm, edited 1 time in total.
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
Debugging Upgrade and Fixing a Bug
I tried to fix that annoying little bug where the very first LEDs that were set to a color when the set wasn't playing were being turned off after a short time. I wasn't sure where that signal was being fired, so I turned to stackoverflow to find a diagnostic function to determine the caller of a function. I found this nifty one that let me step through the call stack to even see which function called the one that called the one, and so on, until I found, after a few steps, that ClipSlotComponent.update was responsible - the natural behavior for that is to turn off the lights for slots that have a clip but are not playing! So what I needed was a way to turn that off, and thankfully, it was as easy as specifying this during the ButtonMatrix assembly:
Code: Select all
slot.set_allow_update(False)
edit: I made the corniest of banners for my project update posts. too much? probably. let me know in the comments, and don't forget to like and subscribe and hit the bell button
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
Hi wayfinder,
I am asking this question as a last resort. I have searched and researched until I am blue in the face.
My next attempt is to simply map an encoder to a specific device on a specific track.
The reason I am trying do to encoders now is decoupling my MIDI mappings from specific Live sets is a tantalizing prospect. I have complex mappings, and it bothers me that they are so tightly coupled to Live sets. Yes, I use template sets, but there would still many advantages to managing MIDI mappings outside of specific sets.
Sounds easy. Given how easy it was to map buttons to parameters, as my script above does, I thought that would be easy, but it is not. From what I can tell, _Framework DeviceComponent is geared towards blue hand mapping, not specific MIDI mapping.
In theory, this is possible. The Remotify people have done something like this: https://remotify.io/docs/advanced-ablet ... ce-control
On your journey, have you come across anything that would help?
I might make a new thread about this one, or rather, a dedicated thread about using MIDI remote scripts to replace Live's MIDI mapping.
Thanks in advance. Keep up the great work!
I am asking this question as a last resort. I have searched and researched until I am blue in the face.
My next attempt is to simply map an encoder to a specific device on a specific track.
The reason I am trying do to encoders now is decoupling my MIDI mappings from specific Live sets is a tantalizing prospect. I have complex mappings, and it bothers me that they are so tightly coupled to Live sets. Yes, I use template sets, but there would still many advantages to managing MIDI mappings outside of specific sets.
Sounds easy. Given how easy it was to map buttons to parameters, as my script above does, I thought that would be easy, but it is not. From what I can tell, _Framework DeviceComponent is geared towards blue hand mapping, not specific MIDI mapping.
In theory, this is possible. The Remotify people have done something like this: https://remotify.io/docs/advanced-ablet ... ce-control
On your journey, have you come across anything that would help?
I might make a new thread about this one, or rather, a dedicated thread about using MIDI remote scripts to replace Live's MIDI mapping.
Thanks in advance. Keep up the great work!
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
I'm not sure I understand completely what you want to do... But I did come across a way to access devices - you can get at them through the song().tracks[your_track].devices[position_in_the_chain] infrastructure.
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
In short, at this point, I simply want to map an encoder to a specific device dial via a remote script.
I am aware of accessing devices that way. But, the problem is I do not see a way to bind the encoder to the device dial.
Binding encoders to mixer controls is easy, as you have shown. But, binding to specific devices does not seem straightforward.
Thanks.
I am aware of accessing devices that way. But, the problem is I do not see a way to bind the encoder to the device dial.
Binding encoders to mixer controls is easy, as you have shown. But, binding to specific devices does not seem straightforward.
Thanks.
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
Well, here is the magic! A little POC of an encoder mapped to a specific device dial!
I don't know why I did not put two and two together. Wayfinder already has an example of an Encoder listener. I may need to do some normalizing/converting of MIDI values to parameter values, but that won't be hard.
Usual disclaimer: This code is distributed WITHOUT ANY WARRANTY WHATSOEVER. Use at your own risk.
I don't know why I did not put two and two together. Wayfinder already has an example of an Encoder listener. I may need to do some normalizing/converting of MIDI values to parameter values, but that won't be hard.
Usual disclaimer: This code is distributed WITHOUT ANY WARRANTY WHATSOEVER. Use at your own risk.
Code: Select all
from _Framework.ControlSurface import ControlSurface
from _Framework.ButtonElement import ButtonElement
from _Framework.EncoderElement import EncoderElement
from _Framework.InputControlElement import *
import Live
class MyControlSurface(ControlSurface):
def __init__(self, instance):
super(MyControlSurface, self).__init__(instance, False)
with self.component_guard():
#self.log_message("MyControlSurface loaded")
self.show_message("MyControlSurface loaded")
encoder = EncoderElement(MIDI_CC_TYPE, 0, 1, Live.MidiMap.MapMode.relative_smooth_two_compliment)
encoder.add_value_listener(self.encoderTurned)
def encoderTurned(self, value):
for track in self.song().tracks:
for device in track.devices:
if device.name.upper() == "MyTEST":
for parameter in device.parameters:
if parameter.name.upper() == "TEST1":
parameter.value = value
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
Hi wayfinder, congrats on your continued progress.
Just curious if you installed Python and/or the debug symbols?
VSCode keeps nagging me to do that. I am wondering if there are any advantages. E.g., would that give us intellisense on the Live classes? That would be nice, but I do not want to install Python unless it is really worth it.
On my end, I am almost done with my mapping script. When I get done, I am going to update my thread on that with a nice concise example.
One thing that tripped me up is parameter names. There might be some Live bug or something. Basically, when I mapped a device parameter to a Live macro and used the auto-generated name in my search, it was not finding the parameter. After I printed the parameter names in my log and did some thinking, I realized that naming the parameter BEFORE mapping would make the parameter name get seen by the Live API.
It is weird, because the API has two properties for parameter names: an original name and then simply “name.” Oddly, it was behaving as if I was using the original name property when in fact I was not.
Anyhow, I wanted share that with you so that you are aware.
Cheers
Just curious if you installed Python and/or the debug symbols?
VSCode keeps nagging me to do that. I am wondering if there are any advantages. E.g., would that give us intellisense on the Live classes? That would be nice, but I do not want to install Python unless it is really worth it.
On my end, I am almost done with my mapping script. When I get done, I am going to update my thread on that with a nice concise example.
One thing that tripped me up is parameter names. There might be some Live bug or something. Basically, when I mapped a device parameter to a Live macro and used the auto-generated name in my search, it was not finding the parameter. After I printed the parameter names in my log and did some thinking, I realized that naming the parameter BEFORE mapping would make the parameter name get seen by the Live API.
It is weird, because the API has two properties for parameter names: an original name and then simply “name.” Oddly, it was behaving as if I was using the original name property when in fact I was not.
Anyhow, I wanted share that with you so that you are aware.
Cheers
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
Hi wayfinder,
Sorry, one more thing. Now that I have things working, I am starting to think about cleaning things up.
Have you thought much about what to do upon disconnection?
Some, but not all, of the examples I have seen do various cleanups, like, e.g., setting variables to null, etc.
I wonder what truly needs to be cleaned up. Like, when the script disconnects, after Live is closed or a new set is loaded, it SEEMS like everything is closed down, so that there would be no "leaking," etc. But, I am not sure. From a cursory google search, it seems like Python does some garbage collection, so memory management might not be a big issue.
I am guessing that the main disconnection function is really for doing cleanups on the controller side, for example, turning off indicators and lights, etc.
Any thoughts or observations from your side would be welcome.
Cheers
Sorry, one more thing. Now that I have things working, I am starting to think about cleaning things up.
Have you thought much about what to do upon disconnection?
Some, but not all, of the examples I have seen do various cleanups, like, e.g., setting variables to null, etc.
I wonder what truly needs to be cleaned up. Like, when the script disconnects, after Live is closed or a new set is loaded, it SEEMS like everything is closed down, so that there would be no "leaking," etc. But, I am not sure. From a cursory google search, it seems like Python does some garbage collection, so memory management might not be a big issue.
I am guessing that the main disconnection function is really for doing cleanups on the controller side, for example, turning off indicators and lights, etc.
Any thoughts or observations from your side would be welcome.
Cheers
Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10
From reading the ableton.v2 code, it *seems* the only things we need to manually disconnect are listeners, created using the Live API and any cyclic dependencies. I *assume* cyclic dependencies would be such things we do in our code like, e.g., creating classes that reference each other. Since I am not doing that, I am not worrying about it.
After I got my script all finished, I decided to do one last thing: see if I could replace _Framework with ableton.v2. I was able to do that successfully! Now, I feel great about forward compatibility knowing I am using the latest bits!
The main things I had to change are listed below. Hope this helps. I will update my script in my dedicated post.
In init:
- return MyControlSurface(c_instance) was changed to return MyControlSurface(c_instance=c_instance)
- Replace song() with song
- In the main constructor of my script, I replaced ControlSurface with IdentifiableControlSurface
- def __init__(self, instance) was changed to def __init__(self, *a, **k):
- class MyControlSurface(IdentifiableControlSurface) was changed to super(MyControlSurface, self).__init__( *a, **k)
- self.log_message("…") was changed to logger.info("…")
- ControlSurface.disconnect(self) was changed to super(MyControlSurface, self).disconnect()
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__)