adaptorex
Version:
Connect all your live interactive storytelling devices and software
225 lines (191 loc) • 6.34 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