Writing Custom Control Surfaces for Ableton

Recently I’ve been really interested in what goes on in Ableton Live under the hood, since it’s a widely-used piece of proprietary software for making bangers. It turns out you can get pretty far knowing just a small amount of Python scripting!

For instance, I found that .als files (and .adv’s, and .adg’s, and probably more) are just gzipped XML and so therefore you can do some fun processing on them using any XML library, such as Python’s. So, remembering the week that it took me to convert all my Ableton DJ sets into Rekordbox, I wrote a script to automatically convert Ableton cues to Rekordbox and vice-versa. This could have definitely saved me about 10 hours of boring work time that could have been spent making bangers instead!

This week, I started looking into Ableton’s Python interface for control surfaces and you’ll never guess what happened next (a bunch of boring stuff).

What’s a control surface?

According to Ableton, “Control Surfaces are specially written scripts which allow controllers to interface with Live”.

Most people who use Ableton interact with it through a tactile controller, such as a MIDI keyboard or an Ableton Push or the APC40 controller. Control surfaces are scripts which act as a bridge between Ableton and the controller, telling Ableton what each button/knob/fader/etc. on the controller should do.

It turns out that these control surfaces are simply Python scripts.

Where does Ableton store its control surface scripts?

Ableton comes with a bunch of control surface scripts pre-installed for common controllers such as the Launchkey, Push, and APCs. For these controllers, you can just plug in the controller, open Ableton Preferences, and select the control surface for it via a drop-down list in the Link/MIDI tab.

These scripts are stored in /Applications/Ableton Live $VERSION.app/Contents/App Resources/MIDI Remote Scripts/ on Mac (where $VERSION is 10 Suite for instance if you have Live 10 Suite installed) and \ProgramData\Ableton\Live x.x\Resources\MIDI Remote Scripts\ on Windows.

For the rest of this post we will refer to this as the MIDI Remote Scripts directory.

Loading a custom control surface script

If you want to load your own custom control surface script for an existing MIDI controller, the steps are fairly straightforward:

  1. Find the existing control surface for the MIDI controller in the MIDI Remote Scripts directory. For instance, for the APC40, this would be in APC40/ as a bunch of .pyc files. You can either decompile the .pyc yourself or find the decompiled version in https://github.com/gluon/AbletonLive10.1_MIDIRemoteScripts.
  2. Copy these decompiled Python files to a new directory in the MIDI Remote Scripts directory.
  3. Customize them (more on this in the next section).
  4. Now open Ableton and go to the Link/MIDI tab of Preferences. Ableton should automatically compile the .py files to .pyc. If everything loaded without errors, you will see your new MIDI Remote Scripts directory show up as an option in the Control Surface dropdown.

Customizing control surfaces

In the MIDI Remote Scripts directory, there’s various directories that start with _. These are libraries which other control surfaces can make use of. One particularly useful one is _Framework, which includes definitions for useful classes like ButtonElement, which represents a button on the controller.

The best intro I’ve found to _Framework is this one by Hanz Petrov: https://livecontrol.q3f.org/ableton-liveapi/articles/introduction-to-the-framework-classes/.

Let’s say we want to take the Metronome button on an APC40MKII and instead map that to turn on looping for the currently active clip. Assume we’ve already created a ControlSurface subclass as a starting point.

  1. As a shortcut to calling ButtonElement directly, note that there is an _APC library already which provides a make_button utility method for APC controllers. Github link here.
  2. make_button takes an identifier as input. To figure out what we need to pass here for the metronome button, we can look at the APC control protocol documentation which conveniently lists identifiers for all the buttons on the APC40MKII. We see that 0x5a, or 90, is the identifier for the metronome button.
  3. Now we have to add a listener to the button using the add_value_listener method of ButtonElement that triggers when the button is pressed. Let’s call this handler start_loop.
  4. In start_loop, we can call the song() method of the ControlSurface superclass in order to get a reference to the Live.Song.Song object that we are currently controlling. For documentation on this and other Live objects that are available in Python, see here.
  5. song().view.highlighted_clip_slot.clip now gives us the active clip. This is a Live.Clip.Clip object according to the docs above. So we can simply set clip.looping to a truthy value like 1 to make the clip loop!

In reality, I’ve found the best way to figure these things out is to simply copy/paste code from other control surface scripts as needed. In particular, Will Marshall’s unsupported Ableton 8 control surface scripts are really helpful. Also the unofficial Live Python API documentation is crucial.

Debugging tips

  1. You can log to Ableton’s main log file using the ControlSurface log_message method defined here. On Mac, the log file is at /Users/[username]/Library/Preferences/Ableton/Live x.x.x/Log.txt and on Windows it’s \Users\[username]\AppData\Roaming\Ableton\Live x.x.x\Preferences\Log.txt. This file also contains errors emitted by Python.
  2. You can also reload the MIDI control surface without restarting Ableton: simply re-open the currently open session from File > Open Recent Set.
  3. If you find that Ableton doesn’t auto-compile Python, you can compile using python -m compileall .
  4. As mentioned previously, if a script throws an error immediately, it won’t show up in Ableton’s list of available control surfaces.


As an example, see this Youtube video and Github repo for adding CDJ-style looping buttons to the APC40MK2. The work for this was done originally for Ableton 8 by Will Marshall and I only made some minor edits to make it more CDJ-like and loadable in Live 10.

Note that you can accomplish much of this already without control surface scripts simply by using Ableton’s MIDI mapping mode. One exception is the ability to halve and double loop lengths.

I hope this helps to show that control surface scripts are a quite powerful and flexible way to get your Ableton controller to behave exactly the way you want. Happy hacking!