UNPKG

adaptorex

Version:

Connect all your live interactive storytelling devices and software

243 lines (210 loc) 6.52 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