UNPKG

adaptorex

Version:

Connect all your live interactive storytelling devices and software

225 lines (191 loc) 6.34 kB
/** * 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