adaptorex
Version:
Connect all your live interactive storytelling devices and software
243 lines (210 loc) • 6.52 kB
JavaScript
/**
* Connect adaptor:ex to Ableton Live using the adaptor:ex max for live API
*
* @requires device
*
* @module ableton/ableton
* @copyright Lasse Marburg 2022
* @license MIT
*/
const device = require("../devices/device.js")
const { Plugin } = require("../plugin.js")
const schema = require("./schema.json")
/** @typedef {import('../../types').AdaptorAction} Action */
/** @typedef {import('../plugin_items').PluginItemsTopic} PluginItemsTopic */
class Ableton extends Plugin {
/** @type {PluginItemsTopic} */
livesets
constructor() {
super(schema)
}
/**
* Merge clips, tracks and scenes from stored schema
*/
async setup(config, game) {
await super.setup(config, game, { livesets: Liveset })
this.addTemplate("trackMix", (payload, action) => {
const title = `ableton ${payload.liveset?.replace("ableton.livesets.", "")} ${action.name}`
const subtitle = `Set mix for ${payload.track}`
const body = []
for (let mix in payload) {
if (["liveset", "track"].includes(mix)) {
continue
}
if (mix == "sends") {
for (let send in payload.sends) {
body.push({ text: `send ${send}: ${payload.sends[send]}` })
}
continue
}
body.push({ text: `${mix}: ${payload[mix]}` })
}
return { title, subtitle, body }
})
if (config.schema) {
this.schema.definitions.live_clip = config.schema.definitions.live_clip
this.schema.definitions.live_track = config.schema.definitions.live_track
this.schema.definitions.live_scene = config.schema.definitions.live_scene
}
return this.schema
}
async load(setup) {
setup.schema = this.schema
return setup
}
/**
* Play a clip
*
* @type {Action}
*/
async playClip(data, session) {
/** @type {Liveset} */
let liveset = this.livesets.getItem(data.liveset)
await liveset.send({ play: { clip: data.clip } })
}
/**
* Play a scene. Starts all clips in the scene.
*
* @type {Action}
*/
async playScene(data, session) {
/** @type {Liveset} */
let liveset = this.livesets.getItem(data.liveset)
await liveset.send({ play: { scene: data.scene } })
}
/**
* Stop a clip
*
* @type {Action}
*/
async stopClip(data, session) {
/** @type {Liveset} */
let liveset = this.livesets.getItem(data.liveset)
await liveset.send({ stop: { clip: data.clip } })
}
/**
* Stop a track. Stops all clips in the track.
*
* @type {Action}
*/
async stopTrack(data, session) {
/** @type {Liveset} */
let liveset = this.livesets.getItem(data.liveset)
await liveset.send({ stop: { track: data.track } })
}
/**
* Change Track Mix values volume, panning or sends or toggle track activator.
*
* @type {Action}
*/
async trackMix(data, session) {
/** @type {Liveset} */
let liveset = this.livesets.getItem(data.liveset)
delete data.liveset
let track = data.track
delete data.track
for (let device in data) {
if (device == "sends") {
for (let send in data.sends) {
await liveset.send({
mix: '"' + track + '" ' + send + " " + data.sends[send]
})
}
continue
}
await liveset.send({
mix: '"' + track + '" ' + device + " " + data[device]
})
}
}
}
/**
* OSC connection to Ableton Live via adaptor maxforlive plugin
*
* Registers mapping from live set and adds clips, tracks and scenes to schema definitions
*/
class Liveset extends device.OSCdevice {
/**
* @param {Object} config - device config data. see device.OSCdevice
*/
constructor(config, collection, plugin, game) {
super(config, collection, plugin, game)
}
/**
* route changes in liveset
* check for clips, tracks and scenes and add them to schema definitions
*/
async incomingMessage(message) {
if (message.hasOwnProperty("liveset")) {
try {
var liveset = JSON.parse(message.liveset)
} catch (err) {
log.error(
this.name,
"Error when parsing liveset mapping string to json."
)
log.error(this.name, err)
return
}
await this.setSchemaDefinitions(
liveset.clips,
liveset.tracks,
liveset.scenes
)
} else if (message.hasOwnProperty("event")) {
await super.incomingMessage(message.event)
} else {
await super.incomingMessage(message)
}
}
/**
* Add clips, tracks and scenes to schema definitions.
*
* @todo latest added liveset will overwrite definitions of previous livesets! See https://gitlab.com/machina_ex/adaptor_ex/adaptor_ex_server/-/issues/501 for a possible fix
*
* @param {array<string>} clips - List of clip names in liveset
* @param {array<string>} tracks - List of track names in liveset
* @param {array<string>} scenes - List of scene names in liveset
*/
async setSchemaDefinitions(clips, tracks, scenes) {
let index = this.plugin.schema.definitions.live_clip.anyOf.findIndex(
(elem) => elem.title == this.name
)
if (Array.isArray(tracks)) {
tracks.unshift("master")
}
if (index < 0) {
this.insertDefinition("live_clip", clips)
this.insertDefinition("live_track", tracks)
this.insertDefinition("live_scene", scenes)
} else {
this.plugin.schema.definitions.live_clip.anyOf[index].enum = clips || []
this.plugin.schema.definitions.live_track.anyOf[index].enum = tracks || []
this.plugin.schema.definitions.live_track.anyOf[index].enum.unshift(
"master"
)
this.plugin.schema.definitions.live_scene.anyOf[index].enum = scenes || []
}
await this.plugin.schemaUpdate(this.plugin.schema)
}
insertDefinition(type, elements) {
let next_to_last = this.plugin.schema.definitions[type].anyOf.length - 2
this.plugin.schema.definitions[type].anyOf.splice(next_to_last, 0, {
title: this.name,
type: "string",
enum: elements || []
})
}
close() {
super.close()
let index = this.plugin.schema.definitions.live_clip.anyOf.findIndex(
(elem) => elem.title == this.name
)
if (index >= 0) {
this.plugin.schema.definitions.live_clip.anyOf.splice(index, 1)
this.plugin.schema.definitions.live_track.anyOf.splice(index, 1)
this.plugin.schema.definitions.live_scene.anyOf.splice(index, 1)
}
}
}
module.exports.Plugin = Ableton