WebSockets: implement a JavaScript object-oriented client API

Replace previous callback based basic client with an easier
to use object-oriented API that further abstracts the low level
details of the WebSockets Server surface messaging protocol.

All built-in web surface demos were updated to use the new API.
This commit is contained in:
Luciano Iam
2020-05-29 11:37:34 +02:00
committed by Robin Gareus
parent 5296ed141f
commit ae4df127ad
18 changed files with 812 additions and 361 deletions

View File

@@ -16,40 +16,54 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import { ControlMixin } from './control.js';
import { MetadataMixin } from './metadata.js';
import { Message } from './message.js';
import { MessageChannel } from './channel.js';
import { MessageChannel } from './base/channel.js';
import { StateNode } from './base/protocol.js';
import { Mixer } from './components/mixer.js';
import { Transport } from './components/transport.js';
// See ControlMixin and MetadataMixin for available APIs
// See ArdourCallback for an example callback implementation
export class ArdourClient {
class BaseArdourClient {
constructor (host) {
this._callbacks = [];
constructor (handlers, options) {
this._options = options || {};
this._components = [];
this._connected = false;
this._pendingRequest = null;
this._channel = new MessageChannel(host || location.host);
this._channel.onError = (error) => {
this._fireCallbacks('error', error);
this._channel = new MessageChannel(this._options['host'] || location.host);
this._channel.onMessage = (msg, inbound) => {
this._handleMessage(msg, inbound);
};
this._channel.onMessage = (msg) => {
this._onChannelMessage(msg);
};
if (!('components' in this._options) || this._options['components']) {
this._mixer = new Mixer(this._channel);
this._transport = new Transport(this._channel);
this._components.push(this._mixer, this._transport);
}
this.handlers = handlers;
}
addCallbacks (callbacks) {
this._callbacks.push(callbacks);
set handlers (handlers) {
this._handlers = handlers || {};
this._channel.onError = this._handlers['onError'] || console.log;
}
// Access to the object-oriented API (enabled by default)
get mixer () {
return this._mixer;
}
get transport () {
return this._transport;
}
// Low level control messages flow through a WebSocket
async connect (autoReconnect) {
this._channel.onClose = async () => {
if (this._connected) {
this._fireCallbacks('disconnected');
this._connected = false;
this._setConnected(false);
}
if ((autoReconnect == null) || autoReconnect) {
@@ -71,50 +85,69 @@ class BaseArdourClient {
this._channel.send(msg);
}
// Private methods
async _connect () {
await this._channel.open();
this._connected = true;
this._fireCallbacks('connected');
async sendAndReceive (msg) {
return await this._channel.sendAndReceive(msg);
}
_send (node, addr, val) {
const msg = new Message(node, addr, val);
this.send(msg);
return msg;
}
// Surface metadata API goes over HTTP
async _sendAndReceive (node, addr, val) {
return new Promise((resolve, reject) => {
const nodeAddrId = this._send(node, addr, val).nodeAddrId;
this._pendingRequest = {resolve: resolve, nodeAddrId: nodeAddrId};
});
}
async _sendRecvSingle (node, addr, val) {
return (await this._sendAndReceive (node, addr, val))[0];
}
_onChannelMessage (msg) {
if (this._pendingRequest && (this._pendingRequest.nodeAddrId == msg.nodeAddrId)) {
this._pendingRequest.resolve(msg.val);
this._pendingRequest = null;
async getAvailableSurfaces () {
const response = await fetch('/surfaces.json');
if (response.status == 200) {
return await response.json();
} else {
this._fireCallbacks('message', msg);
this._fireCallbacks(msg.node, ...msg.addr, ...msg.val);
throw this._fetchResponseStatusError(response.status);
}
}
_fireCallbacks (name, ...args) {
// name_with_underscores -> onNameWithUnderscores
const method = 'on' + name.split('_').map((s) => {
return s[0].toUpperCase() + s.slice(1).toLowerCase();
}).join('');
async getSurfaceManifest () {
const response = await fetch('manifest.xml');
for (const callbacks of this._callbacks) {
if (method in callbacks) {
callbacks[method](...args)
if (response.status == 200) {
const manifest = {};
const xmlText = await response.text();
const xmlDoc = new DOMParser().parseFromString(xmlText, 'text/xml');
for (const child of xmlDoc.children[0].children) {
manifest[child.tagName.toLowerCase()] = child.getAttribute('value');
}
return manifest;
} else {
throw this._fetchResponseStatusError(response.status);
}
}
// Private methods
async _sleep (t) {
return new Promise(resolve => setTimeout(resolve, t));
}
async _connect () {
await this._channel.open();
this._setConnected(true);
}
_setConnected (connected) {
this._connected = connected;
if (this._handlers['onConnected']) {
this._handlers['onConnected'](this._connected);
}
}
_handleMessage (msg, inbound) {
if (this._handlers['onMessage']) {
this._handlers['onMessage'](msg, inbound);
}
if (inbound) {
for (const component of this._components) {
if (component.handleMessage(msg)) {
break;
}
}
}
}
@@ -123,21 +156,4 @@ class BaseArdourClient {
return new Error(`HTTP response status ${status}`);
}
async _sleep (t) {
return new Promise(resolve => setTimeout(resolve, 1000));
}
}
export class ArdourClient extends mixin(BaseArdourClient, ControlMixin, MetadataMixin) {}
function mixin (dstClass, ...classes) {
for (const srcClass of classes) {
for (const propName of Object.getOwnPropertyNames(srcClass.prototype)) {
if (propName != 'constructor') {
dstClass.prototype[propName] = srcClass.prototype[propName];
}
}
}
return dstClass;
}

View File

@@ -16,13 +16,14 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import { ANode, Message } from './message.js';
import { Message } from './protocol.js';
export class MessageChannel {
constructor (host) {
// https://developer.mozilla.org/en-US/docs/Web/API/URL/host
this._host = host;
this._pending = null;
}
async open () {
@@ -34,7 +35,14 @@ export class MessageChannel {
this._socket.onerror = (error) => this.onError(error);
this._socket.onmessage = (event) => {
this.onMessage (Message.fromJsonText(event.data));
const msg = Message.fromJsonText(event.data);
if (this._pending && (this._pending.nodeAddrId == msg.nodeAddrId)) {
this._pending.resolve(msg);
this._pending = null;
} else {
this.onMessage(msg, true);
}
};
this._socket.onopen = resolve;
@@ -43,18 +51,31 @@ export class MessageChannel {
close () {
this._socket.close();
if (this._pending) {
this._pending.reject(Error('MessageChannel: socket closed awaiting response'));
this._pending = null;
}
}
send (msg) {
if (this._socket) {
this._socket.send(msg.toJsonText());
this.onMessage(msg, false);
} else {
throw Error('MessageChannel: cannot call send() before open()');
this.onError(Error('MessageChannel: cannot call send() before open()'));
}
}
async sendAndReceive (msg) {
return new Promise((resolve, reject) => {
this._pending = {resolve: resolve, reject: reject, nodeAddrId: msg.nodeAddrId};
this.send(msg);
});
}
onClose () {}
onError (error) {}
onMessage (msg) {}
onMessage (msg, inbound) {}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright © 2020 Luciano Iam <lucianito@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import { Message } from './protocol.js';
import { Observable } from './observable.js';
export class Component extends Observable {
constructor (parent) {
super();
this._parent = parent;
}
get channel () {
return this._parent.channel;
}
on (property, callback) {
this.addObserver(property, (self) => callback(self[property]));
}
send (node, addr, val) {
this.channel.send(new Message(node, addr, val));
}
handle (node, addr, val) {
return false;
}
handleMessage (msg) {
return this.handle(msg.node, msg.addr, msg.val);
}
updateLocal (property, value) {
this['_' + property] = value;
this.notifyObservers(property);
}
updateRemote (property, value, node, addr) {
this['_' + property] = value;
this.send(node, addr || [], [value]);
}
}
export class RootComponent extends Component {
constructor (channel) {
super(null);
this._channel = channel;
}
get channel () {
return this._channel;
}
}
export class AddressableComponent extends Component {
constructor (parent, addr) {
super(parent);
this._addr = addr;
}
get addr () {
return this._addr;
}
get addrId () {
return this._addr.join('-');
}
updateRemote (property, value, node) {
super.updateRemote(property, value, node, this.addr);
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright © 2020 Luciano Iam <lucianito@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
export class Observable {
constructor () {
this._observers = {};
}
addObserver (property, observer) {
// property=undefined means the caller is interested in observing all properties
if (!(property in this._observers)) {
this._observers[property] = [];
}
this._observers[property].push(observer);
}
removeObserver (property, observer) {
// property=undefined means the caller is not interested in any property anymore
if (typeof(property) == 'undefined') {
for (const property in this._observers) {
this.removeObserver(property, observer);
}
} else {
const index = this._observers[property].indexOf(observer);
if (index > -1) {
this._observers[property].splice(index, 1);
}
}
}
notifyObservers (property) {
// always notify observers that observe all properties
if (undefined in this._observers) {
for (const observer of this._observers[undefined]) {
observer(this, property);
}
}
if (property in this._observers) {
for (const observer of this._observers[property]) {
observer(this);
}
}
}
}

View File

@@ -18,7 +18,7 @@
export const JSON_INF = 1.0e+128;
export const ANode = Object.freeze({
export const StateNode = Object.freeze({
TEMPO: 'tempo',
POSITION_TIME: 'position_time',
TRANSPORT_ROLL: 'transport_roll',

View File

@@ -1,56 +0,0 @@
/*
* Copyright © 2020 Luciano Iam <lucianito@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
// Example empty callback
export class ArdourCallback {
// Connection status
onConnected () {}
onDisconnected () {}
// All messages and errors
onMessage (msg) {}
onError (error) {}
// Globals
onTempo (bpm) {}
onPositionTime (seconds) {}
onTransportRoll (value) {}
onRecordState (value) {}
// Strips
onStripDescription (stripId, name, isVca) {}
onStripMeter (stripId, db) {}
onStripGain (stripId, db) {}
onStripPan (stripId, value) {}
onStripMute (stripId, value) {}
// Strip plugins
onStripPluginDescription (stripId, pluginId, name) {}
onStripPluginEnable (stripId, pluginId, value) {}
// Strip plugin parameters
// valueType
// 'b' : boolean
// 'i' : integer
// 'd' : double
onStripPluginParamDescription (stripId, pluginId, paramId, name, valueType, min, max, isLog) {}
onStripPluginParamValue (stripId, pluginId, paramId, value) {}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright © 2020 Luciano Iam <lucianito@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import { RootComponent } from '../base/component.js';
import { StateNode } from '../base/protocol.js';
import { Strip } from './strip.js';
export class Mixer extends RootComponent {
constructor (channel) {
super(channel);
this._strips = {};
this._ready = false;
}
get ready () {
return this._ready;
}
get strips () {
return Object.values(this._strips);
}
getStripByName (name) {
name = name.trim().toLowerCase();
return this.strips.find(strip => strip.name.trim().toLowerCase() == name);
}
handle (node, addr, val) {
if (node.startsWith('strip')) {
if (node == StateNode.STRIP_DESCRIPTION) {
this._strips[addr] = new Strip(this, addr, val);
this.notifyObservers('strips');
} else {
const stripAddr = [addr[0]];
if (stripAddr in this._strips) {
this._strips[stripAddr].handle(node, addr, val);
} else {
return false;
}
}
return true;
}
/*
RECORD_STATE signals all mixer initial state has been sent because
it is the last message to arrive immediately after client connection,
see WebsocketsDispatcher::update_all_nodes() in dispatcher.cc
For this to work the mixer component needs to receive incoming
messages before the transport component, otherwise the latter would
consume RECORD_STATE.
Some ideas for a better implementation of mixer readiness detection:
- Implement message bundles like OSC to pack all initial state
updates into a single unit
- Move *_DESCRIPTION messages to single message with val={JSON data},
currently val only supports primitive data types
- Append a termination or mixer ready message in update_all_nodes(),
easiest but the least elegant
*/
if (!this._ready && (node == StateNode.RECORD_STATE)) {
this.updateLocal('ready', true);
}
return false;
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright © 2020 Luciano Iam <lucianito@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import { AddressableComponent } from '../base/component.js';
import { StateNode } from '../base/protocol.js';
class ValueType {
constructor (rawType) {
this._rawType = rawType;
}
get isBoolean () {
return this._rawType == 'b';
}
get isInteger () {
return this._rawType == 'i';
}
get isDouble () {
return this._rawType == 'd';
}
}
export class Parameter extends AddressableComponent {
constructor (parent, addr, desc) {
super(parent, addr);
this._name = desc[0];
this._valueType = new ValueType(desc[1]);
this._min = desc[2];
this._max = desc[3];
this._isLog = desc[4];
this._value = 0;
}
get plugin () {
return this._parent;
}
get name () {
return this._name;
}
get valueType () {
return this._valueType;
}
get min () {
return this._min;
}
get max () {
return this._max;
}
get isLog () {
return this._isLog;
}
get value () {
return this._value;
}
set value (value) {
this.updateRemote('value', value, StateNode.STRIP_PLUGIN_PARAM_VALUE);
}
handle (node, addr, val) {
if (node == StateNode.STRIP_PLUGIN_PARAM_VALUE) {
this.updateLocal('value', val[0]);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright © 2020 Luciano Iam <lucianito@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import { AddressableComponent } from '../base/component.js';
import { Parameter } from './parameter.js';
import { StateNode } from '../base/protocol.js';
export class Plugin extends AddressableComponent {
constructor (parent, addr, desc) {
super(parent, addr);
this._parameters = {};
this._name = desc[0];
this._enable = false;
}
get strip () {
return this._parent;
}
get parameters () {
return Object.values(this._parameters);
}
get name () {
return this._name;
}
get enable () {
return this._enable;
}
set enable (value) {
this.updateRemote('enable', value, StateNode.STRIP_PLUGIN_ENABLE);
}
handle (node, addr, val) {
if (node.startsWith('strip_plugin_param')) {
if (node == StateNode.STRIP_PLUGIN_PARAM_DESCRIPTION) {
this._parameters[addr] = new Parameter(this, addr, val);
this.notifyObservers('parameters');
} else {
if (addr in this._parameters) {
this._parameters[addr].handle(node, addr, val);
}
}
return true;
} else if (node == StateNode.STRIP_PLUGIN_ENABLE) {
this.updateLocal('enable', val[0]);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright © 2020 Luciano Iam <lucianito@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import { AddressableComponent } from '../base/component.js';
import { Plugin } from './plugin.js';
import { StateNode } from '../base/protocol.js';
export class Strip extends AddressableComponent {
constructor (parent, addr, desc) {
super(parent, addr);
this._plugins = {};
this._name = desc[0];
this._isVca = desc[1];
this._meter = 0;
this._gain = 0;
this._pan = 0;
this._mute = false;
}
get plugins () {
return Object.values(this._plugins);
}
get name () {
return this._name;
}
get isVca () {
return this._isVca;
}
get meter () {
return this._meter;
}
get gain () {
return this._gain;
}
set gain (db) {
this.updateRemote('gain', db, StateNode.STRIP_GAIN);
}
get pan () {
return this._pan;
}
set pan (value) {
this.updateRemote('pan', value, StateNode.STRIP_PAN);
}
get mute () {
return this._mute;
}
set mute (value) {
this.updateRemote('mute', value, StateNode.STRIP_MUTE);
}
handle (node, addr, val) {
if (node.startsWith('strip_plugin')) {
if (node == StateNode.STRIP_PLUGIN_DESCRIPTION) {
this._plugins[addr] = new Plugin(this, addr, val);
this.notifyObservers('plugins');
} else {
const pluginAddr = [addr[0], addr[1]];
if (pluginAddr in this._plugins) {
this._plugins[pluginAddr].handle(node, addr, val);
} else {
return false;
}
}
return true;
} else {
switch (node) {
case StateNode.STRIP_METER:
this.updateLocal('meter', val[0]);
break;
case StateNode.STRIP_GAIN:
this.updateLocal('gain', val[0]);
break;
case StateNode.STRIP_PAN:
this.updateLocal('pan', val[0]);
break;
case StateNode.STRIP_MUTE:
this.updateLocal('mute', val[0]);
break;
default:
return false;
}
return true;
}
return false;
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright © 2020 Luciano Iam <lucianito@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import { RootComponent } from '../base/component.js';
import { StateNode } from '../base/protocol.js';
export class Transport extends RootComponent {
constructor (channel) {
super(channel);
this._time = 0;
this._tempo = 0;
this._roll = false;
this._record = false;
}
get time () {
return this._time;
}
get tempo () {
return this._tempo;
}
set tempo (bpm) {
this.updateRemote('tempo', bpm, StateNode.TEMPO);
}
get roll () {
return this._roll;
}
set roll (value) {
this.updateRemote('roll', value, StateNode.TRANSPORT_ROLL);
}
get record () {
return this._record;
}
set record (value) {
this.updateRemote('record', value, StateNode.RECORD_STATE);
}
handle (node, addr, val) {
switch (node) {
case StateNode.TEMPO:
this.updateLocal('tempo', val[0]);
break;
case StateNode.POSITION_TIME:
this.updateLocal('time', val[0]);
break;
case StateNode.TRANSPORT_ROLL:
this.updateLocal('roll', val[0]);
break;
case StateNode.RECORD_STATE:
this.updateLocal('record', val[0]);
break;
default:
return false;
}
return true;
}
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright © 2020 Luciano Iam <lucianito@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import { ANode } from './message.js';
// Surface control API over WebSockets
export class ControlMixin {
async getTempo () {
return await this._sendRecvSingle(ANode.TEMPO);
}
async getTransportRoll () {
return await this._sendRecvSingle(ANode.TRANSPORT_ROLL);
}
async getRecordState () {
return await this._sendRecvSingle(ANode.RECORD_STATE);
}
async getStripGain (stripId) {
return await this._sendRecvSingle(ANode.STRIP_GAIN, [stripId]);
}
async getStripPan (stripId) {
return await this._sendRecvSingle(ANode.STRIP_PAN, [stripId]);
}
async getStripMute (stripId) {
return await this._sendRecvSingle(ANode.STRIP_MUTE, [stripId]);
}
async getStripPluginEnable (stripId, pluginId) {
return await this._sendRecvSingle(ANode.STRIP_PLUGIN_ENABLE, [stripId, pluginId]);
}
async getStripPluginParamValue (stripId, pluginId, paramId) {
return await this._sendRecvSingle(ANode.STRIP_PLUGIN_PARAM_VALUE, [stripId, pluginId, paramId]);
}
setTempo (bpm) {
this._send(ANode.TEMPO, [], [bpm]);
}
setTransportRoll (value) {
this._send(ANode.TRANSPORT_ROLL, [], [value]);
}
setRecordState (value) {
this._send(ANode.RECORD_STATE, [], [value]);
}
setStripGain (stripId, db) {
this._send(ANode.STRIP_GAIN, [stripId], [db]);
}
setStripPan (stripId, value) {
this._send(ANode.STRIP_PAN, [stripId], [value]);
}
setStripMute (stripId, value) {
this._send(ANode.STRIP_MUTE, [stripId], [value]);
}
setStripPluginEnable (stripId, pluginId, value) {
this._send(ANode.STRIP_PLUGIN_ENABLE, [stripId, pluginId], [value]);
}
setStripPluginParamValue (stripId, pluginId, paramId, value) {
this._send(ANode.STRIP_PLUGIN_PARAM_VALUE, [stripId, pluginId, paramId], [value]);
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright © 2020 Luciano Iam <lucianito@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
// Surface metadata API over HTTP
export class MetadataMixin {
async getAvailableSurfaces () {
const response = await fetch('/surfaces.json');
if (response.status == 200) {
return await response.json();
} else {
throw this._fetchResponseStatusError(response.status);
}
}
async getSurfaceManifest () {
const response = await fetch('manifest.xml');
if (response.status == 200) {
const manifest = {};
const xmlText = await response.text();
const xmlDoc = new DOMParser().parseFromString(xmlText, 'text/xml');
for (const child of xmlDoc.children[0].children) {
manifest[child.tagName.toLowerCase()] = child.getAttribute('value');
}
return manifest;
} else {
throw this._fetchResponseStatusError(response.status);
}
}
}