In this document I will describe some fun that you might have with the ableton python api. It can use some better documentation and the goal is to help other people get started. It is written to be an initial reference just for myself but feel free to play with it if you find it useful.

Getting Started

I'm assuming that you have a mac and that you have some version of Ableton installed. Go to applications select the ableton app, right click and then select Show Package Content. From here we will be going to navigate to /Contents/App-Resources/MIDI Remote Scripts/ and create a folder for our python scripts.

You'll notice that this folder contains all the *.pyc files needed for your favorite hardware. We're going to create a python script that mimics a hardware controller which is why it needs to be placed here. In my case I have called my folder foobar. In this folder I've created an __init__.py file with the following code in it:

from __future__ import print_function
from ableton.v2.control_surface import ControlSurface
import random

class EuropythonSurface(ControlSurface):

    def __init__(self, *a, **k):
        super(EuropythonSurface, self).__init__(*a, **k)
        self.song.create_midi_track(-1)
        for i in range(8):
            self.fill_track_randomly(i)

    def random_note(self, time):
        # pitch (0-127), time (float), duration (float), velocity (0-127), and mute state (bool)
        pitch = random.randint(36, 48)
        time = time
        duration = random.choice([0.25, 0.5, 1, 2.])
        velocity = random.randint(70, 100)
        mute_state = False
        return (pitch, time, duration, velocity, mute_state)

    def fill_track_randomly(self, slot_num):
        track = self.song.tracks[-1]
        first_clip_slot = track.clip_slots[slot_num]

        number_bars = 4
        # Length in beat-time: 1.0 == 1 beat, 0.25 == 1/16th etc.
        first_clip_slot.create_clip(number_bars * 4.0)
        clip = first_clip_slot.clip

        notes = [] 
        acc = 0.0
        for _ in range(20):
            notes.append(self.random_note(acc))
            acc += notes[-1][2]

        clip.set_notes(tuple(notes))
        clip.fire()

def create_instance(c_instance):
    return EuropythonSurface(c_instance=c_instance)

This script will append a midi track and adds a stream of random notes. Now, if you have this code in place and start ableton you may notice that this .py file will have a .pyc file as well. If you want this script to have effect in ableton you still need to select the hardware that we've just created.

Once that this is connected, you should see some midi tracks created with some random notes in it.

This is a cool starter script. But with the power of python we are already able to do quite a bit more.

Moar Tools

You'll notice that you won't have access to a console from ableton. If you want to debug you'll have to cat the log file that ableton keeps track of. You should be able to find such logfile at this path: /Users/<username>/Library/Preferences/Ableton/Live 9.6.<version>. You will need this logfile if you want to do any kind of debugging.

You'll probably feel like you'll be hacking more than you're coding when you are diving into this. Ableton currently does not offer support for the internal ableton api. There is some documentation out there but it is sparse.

Better sounds

One of the things you can do to make the random sounds sound better is to alter the distribution that you sample from. Let's ensure that all the notes are from the same key and add a bit more of a markovian property.

from __future__ import print_function
from ableton.v2.control_surface import ControlSurface
import random
import math

class EuropythonSurface(ControlSurface):

    def __init__(self, *a, **k):
        super(EuropythonSurface, self).__init__(*a, **k)
        self.song.create_midi_track(-1)
        for i in range(8):
            self.fill_track_randomly(i)

    def random_note(self, time, pitches):
        # pitch (0-127), time (float), duration (float), velocity (0-127), and mute state (bool)
        pitch = random.choice(pitches)
        time = time
        duration = random.choice([0.25, 0.5, 1, 2.])
        velocity = random.randint(70, 100)
        mute_state = False
        return (pitch, time, duration, velocity, mute_state)

    def fill_track_randomly(self, slot_num):
        track = self.song.tracks[-1]
        first_clip_slot = track.clip_slots[slot_num]

        number_bars = 4
        # Length in beat-time: 1.0 == 1 beat, 0.25 == 1/16th etc.
        first_clip_slot.create_clip(number_bars * 4.0)
        clip = first_clip_slot.clip

        notes = [] 
        prev_pitch = 36
        acc = 0.0
        markov = {
            36: [40, 41, 43, 45],
            38: [36, 41, 43, 45],
            40: [36, 38, 43, 45],
            41: [36, 38, 40, 45],
            43: [36, 41, 43, 45],
            45: [36, 38, 40, 45]
        }
        for _ in range(20):
            notes.append(self.random_note(time=acc, pitches=markov[prev_pitch]))
            acc += notes[-1][2]
            prev_pitch = notes[-1][0]

        clip.set_notes(tuple(notes))
        clip.fire()

def create_instance(c_instance):
    return EuropythonSurface(c_instance=c_instance)

It is not something I'd listen to, but it is better than random. We can go a step further though by also including different tracks.

Future

So I've been meaning to have a go at generative algorithms.