Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Discuss music production with Ableton Live.
jbone1313
Posts: 578
Joined: Wed Dec 12, 2007 2:44 am

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by jbone1313 » Mon Mar 01, 2021 2:40 pm

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

wayfinder
Posts: 176
Joined: Tue Jul 28, 2009 4:01 pm

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by wayfinder » Mon Mar 01, 2021 3:12 pm

jbone1313 wrote:
Mon Mar 01, 2021 2:40 pm
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.
That's fantastic! Good luck :)
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
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...
Last edited by wayfinder on Mon Mar 01, 2021 5:15 pm, edited 1 time in total.

jbone1313
Posts: 578
Joined: Wed Dec 12, 2007 2:44 am

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by jbone1313 » Mon Mar 01, 2021 3:27 pm

wayfinder wrote:
Mon Mar 01, 2021 3:12 pm
I looked that 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...
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

wayfinder
Posts: 176
Joined: Tue Jul 28, 2009 4:01 pm

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by wayfinder » Mon Mar 01, 2021 4:50 pm

Ah, thank you! Looks like that works in Live 10 already, I just changed it and made my script COMPLETELY FUTURE-PROOF

jbone1313
Posts: 578
Joined: Wed Dec 12, 2007 2:44 am

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by jbone1313 » Mon Mar 01, 2021 5:52 pm

...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! 8O 8) :)

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                            


wayfinder
Posts: 176
Joined: Tue Jul 28, 2009 4:01 pm

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by wayfinder » Tue Mar 02, 2021 12:45 am

Image

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
Next, in my K1 object, I attach a listener function to the song time:

Code: Select all

            self.song().add_current_song_time_listener(self.beat_listener)
And then I created the callback function like so:

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()
Here's what the code of the updater looks like:

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)
Here it is in action: https://i.imgur.com/WBxXlG3.mp4. Sorry for the low volume, it was pretty late in the evening already :) I also just noticed that in the video, amber and red LEDs look almost identical. They're a little easier to tell apart than that.

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.

wayfinder
Posts: 176
Joined: Tue Jul 28, 2009 4:01 pm

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by wayfinder » Tue Mar 02, 2021 11:59 am

Image

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)
Since I'm handling the LED updates in my own LED updater function, that should hopefully not break anything. As far as I can tell, it was only necessary for the initial row the red box starts on, so I'll keep an eye out for whether that one makes any specific trouble the others don't, but for the moment I consider the issue closed.

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

jbone1313
Posts: 578
Joined: Wed Dec 12, 2007 2:44 am

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by jbone1313 » Tue Mar 02, 2021 4:19 pm

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!

wayfinder
Posts: 176
Joined: Tue Jul 28, 2009 4:01 pm

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by wayfinder » Tue Mar 02, 2021 5:03 pm

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.

jbone1313
Posts: 578
Joined: Wed Dec 12, 2007 2:44 am

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by jbone1313 » Tue Mar 02, 2021 5:30 pm

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. :)

jbone1313
Posts: 578
Joined: Wed Dec 12, 2007 2:44 am

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by jbone1313 » Tue Mar 02, 2021 7:05 pm

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.

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
   

jbone1313
Posts: 578
Joined: Wed Dec 12, 2007 2:44 am

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by jbone1313 » Thu Mar 04, 2021 2:46 pm

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

jbone1313
Posts: 578
Joined: Wed Dec 12, 2007 2:44 am

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by jbone1313 » Fri Mar 05, 2021 12:24 am

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

jbone1313
Posts: 578
Joined: Wed Dec 12, 2007 2:44 am

Re: Project thread: Building an ultra-portable performance set, and my experience with Remote Scripting in Live 10

Post by jbone1313 » Mon Mar 08, 2021 4:11 pm

jbone1313 wrote:
Fri Mar 05, 2021 12:24 am
Have you thought much about what to do upon disconnection?
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)
In my main script:
  • 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()
The full list of imports I am using is below.

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__)

Post Reply