Skip to content

Architecture overview

This page explains how a MIDI Remote Script is put together and how it talks to Live. It is the conceptual map for everything in the API Reference: once you understand the layers below, the reference stops looking like 1300 disconnected modules and starts looking like one system.

What a Remote Script actually is

A MIDI Remote Script (Ableton also calls it a Control Surface) is a small Python program that runs inside Live's own embedded Python interpreter. Its job is to sit between two worlds and translate continuously in both directions:

HARDWAREbuttons · encoders · pads · faders · LEDsMIDINote On/Off · CC · SysExELEMENTSButtonElement · EncoderElement · ButtonMatrixCOMPONENTSSession · Mixer · Transport · DeviceLIVE OBJECT MODELSong · Track · Clip · Device · ParameterLIVE ENGINEC++ coreYOUR SCRIPT · ControlSurfaceINPUTFEEDBACK

A Remote Script sits between two worlds and translates continuously in both directions: the input path (left) turns a knob turn or pad hit into a Live Object Model call, and the feedback path (right) observes Live and pushes values back out to light an LED or move a fader. The middle two layers, Elements and Components, are your script (the ControlSurface); the bottom two are Live itself.

  • Downstream (hardware -> Live): a knob turn or pad hit arrives as a MIDI message, the script decides what it means, and calls the Live Object Model to change a parameter, fire a clip, arm a track, etc.
  • Upstream (Live -> hardware): the script observes Live (clip added, track armed, parameter changed) and sends MIDI back to light up an LED, move a motor fader, or update a display.

The script is not polled in a loop. It is event-driven: it registers listeners on the LOM and reacts. This is the single most important idea in the whole framework.

The two halves: the LOM and the framework

Everything splits into two stacks that should never be confused.

1. The Live Object Model (the Live module)

The LOM is the object tree that represents the Live set itself -- Application, Song, Track, ClipSlot, Clip, Device, DeviceParameter, MixerDevice, and so on. It is implemented in C++ and exposed to Python as the built-in Live module. You don't import it from disk; Live injects it.

Three operations matter:

  • read a property -- song.tempo, track.name, clip.is_playing
  • call a function -- song.create_audio_track(-1), clip_slot.fire()
  • observe a property -- add a listener that fires whenever a value changes (song.add_tempo_listener(callback)). Observation is what makes feedback possible.

Because the LOM is C, it has no .pyc to extract -- but it is richly documented by Ableton with real docstrings, reachable only at runtime. That is exactly what dump_runtime_api.py captures. The LOM reference is the part of these docs with genuine prose descriptions.

2. The control-surface framework (the Python you can read)

This is the reusable machinery that turns hardware into LOM operations. It exists in three generations, all present in a modern Live install:

Layer Lineage Style
_Framework Live 9 / 10 / 11 the original, imperative
ableton.v2.control_surface Live 10+ rewrite control / element / component model
ableton.v3.control_surface Live 11 / 12 devices declarative, specification-driven

A given device script is built on one of these (newer devices on v3, older ones on _Framework or v2). They share the same core vocabulary, which the next section defines.

The core vocabulary

These five concepts appear in every generation under slightly different names. Learn them once.

ControlSurface -- the root object

The ControlSurface is the top of your script. Live instantiates exactly one per enabled controller. It owns the MIDI ports, builds the components, and manages the global lifecycle (see the ControlSurface lifecycle guide). Your device class subclasses it.

Element -- one physical control

An Element wraps a single physical control and its MIDI identity: ButtonElement, EncoderElement, SliderElement, plus the grid wrapper ButtonMatrixElement. An element knows its message type (MIDI_NOTE_TYPE vs MIDI_CC_TYPE), its channel and number, and how to send a value back to the hardware (LED feedback). Elements are pure I/O -- they carry no musical meaning on their own.

Component -- one behaviour

A Component is a unit of behaviour: a SessionComponent (the clip grid), a MixerComponent, a TransportComponent, a DeviceComponent, a step sequencer. A component talks to the LOM and exposes named controls (e.g. "play_button", "stop_button") that get wired to physical elements. Components can be enabled/disabled and nested. In v3, Ableton's ready-made components live in ableton.v3.control_surface.components.

Layer -- the wiring between elements and a component

A Layer maps physical elements onto a component's named controls: Layer(play_button=button_1, stop_button=button_2). Separating behaviour (component) from wiring (layer) is what lets the same component be driven by different hardware, and lets a control be re-bound when modes change.

Mode -- swapping wirings at runtime

A Mode (via ModesComponent) swaps which layers/components are active. "Shift held -> the grid means scene launch instead of clip launch" is a mode change: same buttons, a different layer applied. Modes are how one piece of hardware does ten jobs. See Components, Layers & Modes.

Why a device script looks almost empty

Open any modern device page in the reference -- say a recent keyboard or pad controller -- and you will often find only a Specification class, a setup() method, and a couple of handlers. This is correct, not a gap in the docs. In the v3 model the device script only declares what it is:

  • a ControlSurfaceSpecification subclass -- a declarative bag of settings: how many tracks/scenes, which element class, which skin/colours, which component map;
  • a thin ControlSurface subclass whose setup() wires a few device-specific extras.

Everything else -- the session grid, the mixer, transport, device control, MIDI map building, mode handling -- is inherited from ableton.v3.control_surface.ControlSurface. The behaviour you're looking for is on the base class, not the device. So to understand what a controller can do, read its Specification (what it turns on) and then the v3 base classes (how those things work). The richness of this documentation lives in ableton.v2 / ableton.v3, not in the per-device pages.

How a message flows (end to end)

A concrete round-trip, to tie it together:

  1. You press pad 5. The hardware sends a MIDI note.
  2. Live routes it to your script's input port; the framework dispatches it to the ButtonElement mapped to that note.
  3. The element fires its value to whatever control it's bound to in the current layer.
  4. The bound component (say SessionComponent) interprets it -- "launch clip slot (1, 5)" -- and calls the LOM: clip_slot.fire().
  5. The clip starts. Live's ClipSlot.is_playing changes.
  6. The listener the component registered on that slot fires.
  7. The component pushes a new colour to the element, which sends MIDI out.
  8. The pad lights up green.

Steps 1-4 are the downstream path; 5-8 are the upstream/feedback path. The MIDI message flow guide details the dispatch and the MIDI map.

Why clean decompilation stopped working

A short, honest note, because it shapes how this documentation is built:

  • Live <= 10 (Python 2): .pyc decompiled trivially.
  • Live 11 (Python 3.7): uncompyle6 / decompyle3 still work, with manual fixups. Live 11 is therefore the readable reference -- where a Live 12 symbol is unchanged, the Live 11 source explains it.
  • Live 12 (Python 3.11): zero-cost exception tables and the adaptive interpreter changed the bytecode so deeply that no decompiler reconstructs source. Clean full decompilation is no longer possible.

But documentation never needed the source -- only the API surface (class hierarchy, signatures, attributes, docstrings), which the compiled code object preserves intact. That is what the toolchain extracts. The missing piece -- the meaning -- is what these guides supply.