trackmonitorn.jsMax for Live · js
// Runs inside Ableton through Max for Live.
// Watches every track and reports which clip just started.
var observers = [];
// Runs once when the device loads.
function bang() {
// Clear watchers from any earlier run, so nothing
// gets reported twice after a reload.
for (var i = 0; i < observers.length; i++) {
observers[i].property = "";
}
observers = [];
// Count the tracks in the open set.
var liveSet = new LiveAPI("live_set");
var count = liveSet.getcount("tracks");
post("Watching " + count + " tracks\n");
// Put a watcher on every track. Ableton calls it
// whenever the playing clip on that track changes.
for (var i = 0; i < count; i++) {
var api = new LiveAPI(makeCallback(i), "live_set tracks " + i);
api.property = "playing_slot_index";
observers.push(api);
}
}
// Builds the function that runs when a track changes.
function makeCallback(trackIndex) {
return function(args) {
// args[1] is the slot number. -1 means the track
// stopped, so that case is ignored.
if (args[0] === "playing_slot_index" && args[1] >= 0) {
var slotIndex = args[1];
// Find the clip in that slot and read its name.
var clip = new LiveAPI("live_set tracks " + trackIndex + " clip_slots " + slotIndex + " clip");
var name = clip.get("name");
post("Now playing: " + name + "\n");
// Hand the name to webhook.js.
outlet(0, "clip", name);
}
};
}
webhook.jsMax for Live · node.script
// Runs in Node inside the same device. Gets a clip name
// from trackmonitorn.js. If it's a lights clip, it sends
// a web request to IFTTT, and IFTTT sets the bulbs.
const Max = require('max-api');
const https = require('https');
// One IFTTT address per color. The key is my IFTTT account.
const KEY = "<ifttt-maker-key>";
const red = "https://maker.ifttt.com/trigger/RED/with/key/" + KEY;
const blue = "https://maker.ifttt.com/trigger/BLUE/with/key/" + KEY;
const green = "https://maker.ifttt.com/trigger/GREEN/with/key/" + KEY;
const off = "https://maker.ifttt.com/trigger/OFF/with/key/" + KEY;
const on = "https://maker.ifttt.com/trigger/WHITE/with/key/" + KEY;
const normal = "https://maker.ifttt.com/trigger/NORMAL/with/key/" + KEY;
const rain = "https://maker.ifttt.com/trigger/rain/with/key/" + KEY;
const purple = "https://maker.ifttt.com/trigger/pink/with/key/" + KEY;
// Which clip names mean what. Any name not in this
// list is ignored. Adding a color is one new line here
// and one new rule in IFTTT.
const clipMap = {
"Start game": { url: normal, color: "normal" },
"green": { url: green, color: "green" },
"red": { url: red, color: "red" },
"blue": { url: blue, color: "blue" },
"normal": { url: normal, color: "normal" },
"off": { url: off, color: "off" },
"white": { url: on, color: "white" },
"purple": { url: purple, color: "purple" },
"rain": { url: rain, color: "rain" }
};
// Send the request. If it fails, wait and try again,
// up to four times.
function sendWebhook(entry, attempt) {
attempt = attempt || 1;
// agent: false opens a fresh connection every time
// instead of reusing an old one.
https.get(entry.url, { agent: false }, (res) => {
res.resume(); // read the reply so the connection closes cleanly
Max.post("Sent" + (attempt > 1 ? " (attempt " + attempt + ")" : "") + " > " + entry.color);
}).on('error', (err) => {
if (attempt < 4) {
// Wait a bit longer each time: 0.5 s, 1 s, 1.5 s.
setTimeout(() => sendWebhook(entry, attempt + 1), attempt * 500);
} else {
Max.post("FAILED after 4 tries: " + entry.color + " (" + err.message + ")");
}
});
}
// Runs every time trackmonitorn.js reports a clip.
Max.addHandler("clip", (clipName) => {
const entry = clipMap[clipName];
if (entry) {
sendWebhook(entry);
} else {
// A music or sound clip. Nothing to send.
Max.post("No webhook mapped for: " + clipName);
}
});