ableton-js

repository·master·Indexed 19 days ago

https://github.com/leolabs/ableton-js

A Node.js library for controlling Ableton Live via its MIDI Remote Script. It provides a TypeScript-friendly interface to interact with Ableton's internal state, functions, and UI views (Session, Arranger, Browser) using a UDP-based JSON communication protocol. The library includes namespaces for song, session, application, internal, and MIDI operations, as well as support for property listeners and ETag caching.

Tokens
10.7K
Snippets
34
Records
44
Agent score
67%

What's inside ableton-js

  1. Understand the Ableton.js communication protocol

    master

    Ableton.js communicates with the MIDI Script via UDP using JSON objects.

    Command Structure

    A command payload includes:

    • uuid: A unique command ID for associating requests and responses.
    • ns: The command namespace (e.g., song).
    • nsid: The namespace ID (e.g., to address a specific track).
    • name: The command name (e.g., get_prop).
    • args: Arguments for the command.
    • etag: An MD5 hash used for caching.
    • cache: Boolean indicating if the plugin should use ETag caching.

    Response Structure

    The script responds with:

    • data: The return value (JSON-compatible).
    • event: Either result or error.
    • uuid: The original command UUID.

    Caching Mechanism

    To reduce UDP bandwidth, the library uses an LRU cache and ETags. If the plugin determines the data hasn't changed, it returns a placeholder object: { "__cached": true }.

  2. Use the Ableton class to control Ableton Live

    master

    The Ableton class is the primary entry point for controlling Ableton. You instantiate it with an optional logger and call .start() to establish a connection. Once connected, you can interact with various namespaces (like song) to get or set properties and add event listeners.

    Key methods:

    • start(): Establishes the connection with Live.
    • namespace.get(prop): Retrieves the current value of a property.
    • namespace.set(prop, value): Sets a property value.
    • namespace.addListener(prop, callback): Attaches a listener to a property to react to changes.
    import { Ableton } from "ableton-js";
    
    // Log all messages to the console
    const ableton = new Ableton({ logger: console });
    
    const test = async () => {
      // Establishes a connection with Live
      await ableton.start();
    
      // Observe the current playback state and tempo
      ableton.song.addListener("is_playing", (p) => console.log("Playing:", p));
      ableton.song.addListener("tempo", (t) => console.log("Tempo:", t));
    
      // Get the current tempo
      const tempo = await ableton.song.get("tempo");
      console.log("Current tempo:", tempo);
    
      // Set the tempo
      await ableton.song.set("tempo", 85);
    };
    
    test();
  3. Install and activate the Ableton.js MIDI Remote Script

    master

    To use ableton-js, you must install a corresponding MIDI Remote Script into Ableton Live.

    1. Copy the midi-script folder from the ableton-js repository to your Ableton Remote Scripts folder.
    2. Rename the folder to AbletonJS.
    3. The Remote Scripts folder is typically located at: ~/Music/Ableton/User Library/Remote Scripts.
    4. Open Ableton Live and add AbletonJS to your list of available Control Surfaces in Preferences.

    If you are on macOS and have forked the repository, you can automate this using yarn commands:

    • yarn ableton10:start (for Ableton 10)
    • yarn ableton11:start (for Ableton 11)

    These commands will copy the folder, open Ableton, and stream logs to your terminal.

  4. Access TrackView properties

    master

    The TrackView namespace provides access to several properties that can be retrieved, observed, or set.

    Gettable/Observable Properties:

    • is_collapsed: A boolean indicating if the view is collapsed.
    • selected_device: Returns the RawDevice currently selected in the track view. This is automatically transformed into a Device instance.

    Settable Properties:

    • device_insert_mode: Set the insertion mode using DeviceInsertMode (Default, Left, or Right).
    • is_collapsed: Set whether the view is collapsed.
  5. Use the Ableton class namespaces

    master

    The Ableton instance provides several high-level namespaces to interact with different parts of Ableton Live. Instead of sending raw commands, use these specialized objects:

    • ableton.song: For interacting with the current song/project.
    • ableton.session: For session-related controls (e.g., session ring).
    • ableton.application: For application-level settings and state.
    • ableton.internal: For low-level or internal Ableton properties.
    • ableton.midi: For MIDI-related operations.
  6. Initialize and start the Ableton client

    master

    To use ableton-js, instantiate the Ableton class and call the start() method. This establishes a UDP connection to the Ableton Remote Script. You can optionally provide AbletonOptions to configure port files, heartbeats, timeouts, and caching.

    If start() is called while the client is already in a starting or started state, it will return the existing connection promise instead of attempting to restart.

    import { Ableton } from 'ableton-js';
    
    const ableton = new Ableton({
      heartbeatInterval: 2000,
      commandTimeoutMs: 2000,
      disableCache: false
    });
    
    try {
      await ableton.start();
      console.log('Connected to Ableton!');
    } catch (err) {
      console.error('Failed to connect:', err);
    }
  7. Listen to connection and protocol events

    master

    The Ableton instance emits several low-level events that provide insight into the connection state and the underlying UDP communication protocol:

    • connect: Emitted when a connection to Ableton is established.
    • disconnect: Emitted when the connection is lost (e.g., when loading a new project).
    • message: Emitted when a raw message is received from Ableton.
    • error: Emitted when a received message cannot be parsed.
    • ping: Emitted on every response, providing the latency in milliseconds.
    // A connection to Ableton is established
    ab.on("connect", (e) => console.log("Connect", e));
    
    // Connection to Ableton was lost, also happens when you load a new project
    ab.on("disconnect", (e) => console.log("Disconnect", e));
    
    // A raw message was received from Ableton
    ab.on("message", (m) => console.log("Message:", m));
    
    // A received message could not be parsed
    ab.on("error", (e) => console.error("Error:", e));
    
    // Fires on every response with the current ping
    ab.on("ping", (ping) => console.log("Ping:", ping, "ms"));
  8. Configure AbletonOptions

    master

    When instantiating the Ableton class, you can pass an AbletonOptions object to customize the connection behavior:

    OptionTypeDefaultDescription
    serverPortFilestringableton-js-server.portName of the file in the OS tmp directory containing the Remote Script's port.
    clientPortFilestringableton-js-client.portName of the file in the OS tmp directory containing the client's port.
    heartbeatIntervalnumber2000How often (in ms) to ping the Remote Script to check reachability.
    commandTimeoutMsnumber2000How long to wait for a response before throwing a TimeoutError.
    commandWarnMsnumber1000Threshold (in ms) after which a warning is logged for slow commands.
    cacheOptionsLruCache.OptionsundefinedConfiguration for the internal LRU response cache.
    disableCachebooleanfalseIf true, completely disables the response cache.
    loggerLoggerundefinedA custom logger instance. Set to console for standard output.
  9. Reference: Command and Response JSON schemas

    master

    The following JSON structures define the wire format for commands and responses used in the UDP protocol.

    // Command payload
    {
      "uuid": "a20f25a0-83e2-11e9-bbe1-bd3a580ef903",
      "ns": "song",
      "nsid": null,
      "name": "get_prop",
      "args": { "prop": "current_song_time" },
      "etag": "4e0794e44c7eb58bdbbbf7268e8237b4",
      "cache": true
    }
    
    // Standard response
    {
      "data": 0.0,
      "event": "result",
      "uuid": "a20f25a0-83e2-11e9-bbe1-bd3a580ef903"
    }
    
    // Cached response (data matches ETag)
    {
      "data": { "__cached": true },
      "event": "result",
      "uuid": "a20f25a0-83e2-11e9-bbe1-bd3a580ef903"
    }
    
    // Response with new data and ETag
    {
      "data": { "data": 0.0, "etag": "4e0794e44c7eb58bdbbbf7268e8237b4" },
      "event": "result",
      "uuid": "a20f25a0-83e2-11e9-bbe1-bd3a580ef903"
    }
  10. Control and query the Ableton application view

    master

    The ApplicationView class allows you to manipulate and inspect the visible parts of the Ableton Live interface, such as the Session/Arranger views, the Browser, and Detail views. You can show, hide, focus, or scroll these views using specific commands.

    import { Ableton } from "../index.js";
    import { ApplicationView, NavDirection } from "./ns/application-view.js";
    
    const ableton = new Ableton();
    const appView = new ApplicationView(ableton);
    
    // Example: Focus the Arranger view
    await appView.focusView("Arranger");
    
    // Example: Scroll the Session view up
    await appView.scrollView("Session", NavDirection.Up);
    
    // Example: Check if the Browser is visible
    const isBrowserVisible = await appView.isViewVisible("Browser");
  11. Parse MIDI messages with MidiMessage

    master

    The MidiMessage class is used to wrap RawMidiMessage data and provide structured access to MIDI commands and parameters. You can convert a generic MidiMessage into specific types like MidiCC or MidiNote using helper methods.

    • toCC(): Converts the message to a MidiCC object. Throws if the command is not MidiCommand.ControlChange.
    • toNote(): Converts the message to a MidiNote object. Throws if the command is not MidiCommand.NoteOn or MidiCommand.NoteOff.
    import { MidiMessage, MidiCommand } from 'ableton-js/ns/midi';
    
    // Example: Parsing a raw message (e.g., from an observable property)
    const raw = { bytes: [176, 7, 127] }; // CC, Controller 7, Value 127
    const msg = new MidiMessage(raw);
    
    if (msg.command === MidiCommand.ControlChange) {
      const cc = msg.toCC();
      console.log(cc.controller, cc.value);
    }
  12. Control Clip playback and scrubbing

    master

    Use the following methods to control how a clip plays or how you navigate through it:

    • fire(): Starts playing the clip.
    • stop(): Stops playing the clip.
    • setFireButtonState(state: boolean): Directly sets the fire button state (supports all launch modes).
    • movePlayingPos(amount: number): Jumps forward or backward by a relative number of beats (only works if the clip is playing).
    • scrub(position: number): Starts scrubbing from a specific beat position. Continue scrubbing until stopScrub() is called.
    • stopScrub(): Stops the current scrubbing operation.
    await clip.fire();
    await clip.movePlayingPos(2); // Jump forward 2 beats
    await clip.scrub(0);           // Start scrubbing from beat 0
    await clip.stopScrub();        // Stop scrubbing