
When building music production tools and AI-powered analysis pipelines, one of the first challenges is getting structured data out of your DAW. Today, we'll walk through creating a Max for Live device that exports arrangement locators from Ableton Live to a clean JSON format.
This article documents a real proof-of-concept we built — a simple "EXPORT JSON" button that extracts:
The result is a portable JSON file that can feed into visualization tools, AI models, or cross-DAW workflows.
We want a one-click solution inside Ableton Live that produces output like this:
{
"project": "my_track",
"bpm": 103.34,
"sections": [
{ "label": "INTRO", "time_seconds": 0 },
{ "label": "VERSE", "time_seconds": 16 },
{ "label": "BRIDGE", "time_seconds": 80 },
{ "label": "CHORUS", "time_seconds": 112 },
{ "label": "OUTRO", "time_seconds": 224 }
]
}
This JSON can then be:
To follow along, you'll need:
| Requirement | Notes |
|---|---|
| Ableton Live 12 Suite | Max for Live is included in Suite |
| Basic Max/MSP knowledge | We'll keep it simple |
| Text editor | For the JavaScript file |
Our device consists of three components:
┌─────────────────┐ ┌──────────────────────┐ ┌────────────────┐
│ textbutton │────▶│ js marker_export.js │────▶│ textedit │
│ "EXPORT JSON" │ │ (LiveAPI magic) │ │ (JSON preview)│
└─────────────────┘ └──────────────────────┘ └────────────────┘
Open Max for Live in Ableton (create a new Max MIDI Effect), then add these objects:
This is where things get tricky. The textbutton object has three outlets:
| Outlet | Output |
|---|---|
| 1st (left) | Button text |
| 2nd (middle) | Mouse events |
| 3rd (right) | Integer (1 on click) |
Connect the third outlet of textbutton to the inlet of js marker_export.js, then connect the outlet of the JS object to textedit.
For the device to be usable in Live's UI:
textbutton and texteditHere's the complete script that powers our device:
autowatch = 1;
outlets = 1;
function msg_int(v) {
if (v === 1) bang();
}
function bang() {
try {
var song = new LiveAPI("live_set");
// --- PROJECT NAME ---
var name = song.get("name");
name = Array.isArray(name) ? name[0] : name;
if (!name || name === "") name = "untitled";
name = name.replace(/[^a-z0-9_\-]/gi, "_");
// --- BPM ---
var bpm = song.get("tempo");
bpm = Array.isArray(bpm) ? bpm[0] : bpm;
// --- LOCATORS ---
var locatorIds = song.get("cue_points");
var sections = [];
if (Array.isArray(locatorIds)) {
for (var i = 0; i < locatorIds.length; i++) {
if (typeof locatorIds[i] !== "number") continue;
var l = new LiveAPI("id " + locatorIds[i]);
var label = l.get("name");
var time = l.get("time");
sections.push({
label: Array.isArray(label) ? label[0] : label,
time_seconds: Array.isArray(time) ? time[0] : time
});
}
}
// --- BUILD JSON ---
var data = {
project: name,
bpm: bpm,
sections: sections
};
var json = JSON.stringify(data, null, 2);
// Send to UI (textedit)
outlet(0, "set", json);
// Log to Max Console
post("EXPORT SUCCESS\\n");
} catch (e) {
post("EXPORT ERROR:", e.toString(), "\\n");
}
}
autowatch = 1 — Tells Max to reload the script automatically when the file changes. Essential during development.
LiveAPI — The bridge between JavaScript and Ableton's Live Object Model. We use it to access:
live_set — The current projecttempo — Current BPMcue_points — Array of locator IDsArray handling — LiveAPI often returns single values wrapped in arrays, so we consistently check with Array.isArray().
outlet(0, "set", json) — The "set" message tells textedit to display the text without triggering its output.
The JSON output appears in the Max Console window:
Max can't locate your JavaScript file.
Solution:
js object in Max.amxd filejs marker_export.js (no paths)The button sends an integer, but your script doesn't have a handler for it.
Solution: Add this function to your script:
function msg_int(v) {
if (v === 1) bang();
}
The LiveAPI path varies between Ableton versions.
Solution: Use cue_points instead:
var locatorIds = song.get("cue_points");
Usually caused by:
Solution: Filter IDs properly:
if (typeof locatorIds[i] !== "number") continue;
To save the JSON to disk, extend the script:
// --- FILE SAVE ---
var folder = "~/Documents/AbletonExports/";
var f = new File(folder + name + ".json", "write");
if (f.isopen) {
f.writestring(json);
f.close();
post("Saved to:", folder + name + ".json", "\\n");
} else {
post("EXPORT ERROR: cannot write file\\n");
}
Note: The ~ path expansion can be inconsistent in Max/JS. For reliability, either:
Create a folder with both files:
MTL_LocatorsToJSON/
├── MTL_LocatorsToJSON.amxd
├── marker_export.js
└── README.txt
Compress to ZIP and share. Recipients should:
~/Music/Ableton/User Library/Presets/MIDI Effects/Max MIDI Effect/
The .alp format is not a simple ZIP with a different extension. Creating valid Packs requires Ableton's specific export workflow, which varies between Live versions. For quick distribution, a ZIP folder is more reliable.
This JSON format is designed to be compatible with our song structure analysis pipeline. You can:
This MVP opens several possibilities:
| Enhancement | Description |
|---|---|
| Bidirectional sync | Import JSON to create locators |
| Audio Effect version | Support audio tracks, not just MIDI |
| Real-time export | Auto-export on locator changes |
| Cloud sync | Push to API endpoint directly |
| Time signature support | Include meter changes |
The final working script:
autowatch = 1;
outlets = 1;
function msg_int(v) {
if (v === 1) bang();
}
function bang() {
try {
var song = new LiveAPI("live_set");
// PROJECT NAME
var name = song.get("name");
name = Array.isArray(name) ? name[0] : name;
if (!name || name === "") name = "untitled";
name = name.replace(/[^a-z0-9_\-]/gi, "_");
// BPM
var bpm = song.get("tempo");
bpm = Array.isArray(bpm) ? bpm[0] : bpm;
// LOCATORS
var locatorIds = song.get("cue_points");
var sections = [];
if (Array.isArray(locatorIds)) {
for (var i = 0; i < locatorIds.length; i++) {
if (typeof locatorIds[i] !== "number") continue;
var l = new LiveAPI("id " + locatorIds[i]);
var label = l.get("name");
var time = l.get("time");
sections.push({
label: Array.isArray(label) ? label[0] : label,
time_seconds: Array.isArray(time) ? time[0] : time
});
}
}
// JSON OUTPUT
var data = {
project: name,
bpm: bpm,
sections: sections
};
var json = JSON.stringify(data, null, 2);
outlet(0, "set", json);
// OPTIONAL: File save
var folder = "~/Documents/AbletonExports/";
var f = new File(folder + name + ".json", "write");
if (f.isopen) {
f.writestring(json);
f.close();
}
} catch (e) {
post("EXPORT ERROR:", e.toString(), "\\n");
}
}
With about 50 lines of JavaScript and a simple Max for Live patcher, we've built a bridge between Ableton Live's arrangement markers and the wider world of data-driven music tools.
This approach demonstrates a key principle: start with the simplest thing that works. A button, a script, and a text display. No complex UI, no elaborate state management. Just structured data flowing from your DAW to wherever it needs to go.
The JSON format we've chosen is intentionally minimal — project name, BPM, and an array of labeled timestamps. This makes it trivial to parse in any language and integrate with any system.
At Music Tech Lab, we build tools at the intersection of music production and software engineering. Have a Max for Live challenge or want to discuss DAW integration strategies? Get in touch.
Building something similar or facing technical challenges? We've been there.
Let's talk — no sales pitch, just honest engineering advice.
Establishing cooperation between Netlify and Bravelab
The partnership has become a great deal for the companies, we believe that it is the thing also for Bravelab.io.
Factors that contribute to the success or failure of an IT outsourcing project
Several factors have an impact on the success or failure of software development outsourcing projects. The knowledge of these factors improves the outsourcing strategy.