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:
parent
5296ed141f
commit
ae4df127ad
|
@ -55,11 +55,6 @@ WebsocketsDispatcher::dispatch (Client client, const NodeStateMessage& msg)
|
|||
void
|
||||
WebsocketsDispatcher::update_all_nodes (Client client)
|
||||
{
|
||||
update (client, Node::tempo, globals ().tempo ());
|
||||
update (client, Node::position_time, globals ().position_time ());
|
||||
update (client, Node::transport_roll, globals ().transport_roll ());
|
||||
update (client, Node::record_state, globals ().record_state ());
|
||||
|
||||
for (uint32_t strip_n = 0; strip_n < strips ().strip_count (); ++strip_n) {
|
||||
boost::shared_ptr<Stripable> strip = strips ().nth_strip (strip_n);
|
||||
|
||||
|
@ -140,6 +135,11 @@ WebsocketsDispatcher::update_all_nodes (Client client)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
update (client, Node::tempo, globals ().tempo ());
|
||||
update (client, Node::position_time, globals ().position_time ());
|
||||
update (client, Node::transport_roll, globals ().transport_roll ());
|
||||
update (client, Node::record_state, globals ().record_state ());
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -16,10 +16,6 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
// This example does not call the API methods in control.js,
|
||||
// instead it couples the widgets directly to the message stream
|
||||
|
||||
import { ANode, Message } from '/shared/message.js';
|
||||
import { ArdourClient } from '/shared/ardour.js';
|
||||
|
||||
import { Switch, DiscreteSlider, ContinuousSlider, LogarithmicSlider,
|
||||
|
@ -29,108 +25,127 @@ import { Switch, DiscreteSlider, ContinuousSlider, LogarithmicSlider,
|
|||
|
||||
const MAX_LOG_LINES = 1000;
|
||||
|
||||
const ardour = new ArdourClient(location.host);
|
||||
const widgets = {};
|
||||
|
||||
main();
|
||||
const ardour = new ArdourClient();
|
||||
|
||||
function main () {
|
||||
ardour.handlers = {
|
||||
onConnected: (connected) => {
|
||||
if (connected) {
|
||||
log('Client connected', 'info');
|
||||
} else {
|
||||
log('Client disconnected', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
onMessage: (message, inbound) => {
|
||||
if (inbound) {
|
||||
log(`↙ ${message}`, 'message-in');
|
||||
} else {
|
||||
log(`↗ ${message}`, 'message-out');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ardour.getSurfaceManifest().then((manifest) => {
|
||||
const div = document.getElementById('manifest');
|
||||
div.innerHTML = `${manifest.name.toUpperCase()} v${manifest.version} — ${manifest.description}`;
|
||||
div.innerHTML = manifest.name.toUpperCase()
|
||||
+ ' v' + manifest.version + ' — ' + manifest.description;
|
||||
});
|
||||
|
||||
ardour.addCallbacks({
|
||||
onConnected: (error) => { log('Client connected', 'info'); },
|
||||
onDisconnected: (error) => { log('Client disconnected', 'error'); },
|
||||
onMessage: processMessage,
|
||||
onStripDescription: createStrip,
|
||||
onStripPluginDescription: createStripPlugin,
|
||||
onStripPluginParamDescription: createStripPluginParam
|
||||
ardour.mixer.on('ready', () => {
|
||||
const div = document.getElementById('strips');
|
||||
for (const strip of ardour.mixer.strips) {
|
||||
createStrip(strip, div);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ardour.connect();
|
||||
}
|
||||
|
||||
function createStrip (stripId, name, isVca) {
|
||||
const domId = `strip-${stripId}`;
|
||||
function createStrip (strip, parentDiv) {
|
||||
const domId = `strip-${strip.addrId}`;
|
||||
if (document.getElementById(domId) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const strips = document.getElementById('strips');
|
||||
const div = createElem(`<div class="strip" id="${domId}"></div>`, strips);
|
||||
createElem(`<label class="comp-name" for="${domId}">∿  ${name}</label>`, div);
|
||||
const div = createElem(`<div class="strip" id="${domId}"></div>`, parentDiv);
|
||||
createElem(`<label class="comp-name" for="${domId}">∿  ${strip.name}</label>`, div);
|
||||
|
||||
// meter
|
||||
const meter = new StripMeter();
|
||||
meter.el.classList.add('slider-meter');
|
||||
meter.appendTo(div);
|
||||
connectWidget(meter, ANode.STRIP_METER, stripId);
|
||||
bind(strip, 'meter', meter);
|
||||
|
||||
// gain
|
||||
let holder = createElem(`<div class="strip-slider"></div>`, div);
|
||||
createElem(`<label>Gain</label>`, holder);
|
||||
const gain = new StripGainSlider();
|
||||
gain.appendTo(holder);
|
||||
connectWidget(gain, ANode.STRIP_GAIN, stripId);
|
||||
bind(strip, 'gain', gain);
|
||||
|
||||
if (!isVca) {
|
||||
if (!strip.isVca) {
|
||||
// pan
|
||||
holder = createElem(`<div class="strip-slider"></div>`, div);
|
||||
createElem(`<label>Pan</label>`, holder);
|
||||
const pan = new StripPanSlider();
|
||||
pan.appendTo(holder);
|
||||
connectWidget(pan, ANode.STRIP_PAN, stripId);
|
||||
bind(strip, 'pan', pan);
|
||||
}
|
||||
|
||||
for (const plugin of strip.plugins) {
|
||||
createStripPlugin(plugin, div);
|
||||
}
|
||||
}
|
||||
|
||||
function createStripPlugin (stripId, pluginId, name) {
|
||||
const domId = `plugin-${stripId}-${pluginId}`;
|
||||
function createStripPlugin (plugin, parentDiv) {
|
||||
const domId = `plugin-${plugin.addrId}`;
|
||||
if (document.getElementById(domId) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const strip = document.getElementById(`strip-${stripId}`);
|
||||
const div = createElem(`<div class="plugin" id="${domId}"></div>`, strip);
|
||||
const div = createElem(`<div class="plugin" id="${domId}"></div>`, parentDiv);
|
||||
createElem(`<label class="comp-name">⨍  ${name}</label>`, div);
|
||||
|
||||
const enable = new Switch();
|
||||
enable.el.classList.add('plugin-enable');
|
||||
enable.appendTo(div);
|
||||
connectWidget(enable, ANode.STRIP_PLUGIN_ENABLE, stripId, pluginId);
|
||||
bind(plugin, 'enable', enable);
|
||||
|
||||
for (const param of plugin.parameters) {
|
||||
createStripPluginParam(param, div);
|
||||
}
|
||||
}
|
||||
|
||||
function createStripPluginParam (stripId, pluginId, paramId, name, valueType, min, max, isLog) {
|
||||
const domId = `param-${stripId}-${pluginId}-${paramId}`;
|
||||
function createStripPluginParam (param, parentDiv) {
|
||||
const domId = `param-${param.addrId}`;
|
||||
if (document.getElementById(domId) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let param, cssClass;
|
||||
let widget, cssClass;
|
||||
|
||||
if (valueType == 'b') {
|
||||
if (param.valueType.isBoolean) {
|
||||
cssClass = 'boolean';
|
||||
param = new Switch();
|
||||
} else if (valueType == 'i') {
|
||||
widget = new Switch();
|
||||
} else if (param.valueType.isInteger) {
|
||||
cssClass = 'discrete';
|
||||
param = new DiscreteSlider(min, max);
|
||||
} else if (valueType == 'd') {
|
||||
widget = new DiscreteSlider(param.min, param.max);
|
||||
} else if (param.valueType.isDouble) {
|
||||
cssClass = 'continuous';
|
||||
if (isLog) {
|
||||
param = new LogarithmicSlider(min, max);
|
||||
if (param.isLog) {
|
||||
widget = new LogarithmicSlider(param.min, param.max);
|
||||
} else {
|
||||
param = new ContinuousSlider(min, max);
|
||||
widget = new ContinuousSlider(param.min, param.max);
|
||||
}
|
||||
}
|
||||
|
||||
const plugin = document.getElementById(`plugin-${stripId}-${pluginId}`);
|
||||
const div = createElem(`<div class="plugin-param ${cssClass}" id="${domId}"></div>`, plugin);
|
||||
createElem(`<label for="${domId}">${name}</label>`, div);
|
||||
const div = createElem(`<div class="plugin-param ${cssClass}" id="${domId}"></div>`, parentDiv);
|
||||
createElem(`<label for="${domId}">${param.name}</label>`, div);
|
||||
|
||||
param.el.name = domId;
|
||||
param.appendTo(div);
|
||||
connectWidget(param, ANode.STRIP_PLUGIN_PARAM_VALUE, stripId, pluginId, paramId);
|
||||
widget.el.name = domId;
|
||||
widget.appendTo(div);
|
||||
bind(param, 'value', widget);
|
||||
}
|
||||
|
||||
function createElem (html, parent) {
|
||||
|
@ -146,24 +161,12 @@ import { Switch, DiscreteSlider, ContinuousSlider, LogarithmicSlider,
|
|||
return elem;
|
||||
}
|
||||
|
||||
function connectWidget (widget, node, ...addr) {
|
||||
const nodeAddrId = Message.nodeAddrId(node, addr);
|
||||
|
||||
widgets[nodeAddrId] = widget;
|
||||
|
||||
widget.callback = (val) => {
|
||||
const msg = new Message(node, addr, [val]);
|
||||
log(`↗ ${msg}`, 'message-out');
|
||||
ardour.send(msg);
|
||||
};
|
||||
}
|
||||
|
||||
function processMessage (msg) {
|
||||
log(`↙ ${msg}`, 'message-in');
|
||||
|
||||
if (widgets[msg.nodeAddrId]) {
|
||||
widgets[msg.nodeAddrId].value = msg.val[0];
|
||||
}
|
||||
function bind (component, property, widget) {
|
||||
// ardour → ui
|
||||
widget.value = component[property];
|
||||
component.on(property, (value) => widget.value = value);
|
||||
// ui → ardour
|
||||
widget.callback = (value) => component[property] = value;
|
||||
}
|
||||
|
||||
function log (message, className) {
|
||||
|
@ -181,4 +184,6 @@ import { Switch, DiscreteSlider, ContinuousSlider, LogarithmicSlider,
|
|||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
})();
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
<WebSurface>
|
||||
<Name value="Mixer Demo"/>
|
||||
<Description value="Mixer control capabilities demo aimed at developers"/>
|
||||
<Version value="0.1.0"/>
|
||||
<Version value="0.1.1"/>
|
||||
</WebSurface>
|
||||
|
|
|
@ -21,11 +21,11 @@ import { ArdourClient } from '/shared/ardour.js';
|
|||
(() => {
|
||||
|
||||
const dom = {
|
||||
main: document.getElementById('main'),
|
||||
time: document.getElementById('time'),
|
||||
roll: document.getElementById('roll'),
|
||||
record: document.getElementById('record'),
|
||||
fullscreen: document.getElementById('fullscreen')
|
||||
main : document.getElementById('main'),
|
||||
time : document.getElementById('time'),
|
||||
roll : document.getElementById('roll'),
|
||||
record : document.getElementById('record'),
|
||||
fullscreen : document.getElementById('fullscreen')
|
||||
};
|
||||
|
||||
const ardour = new ArdourClient();
|
||||
|
@ -36,12 +36,9 @@ import { ArdourClient } from '/shared/ardour.js';
|
|||
function main () {
|
||||
addDomEventListeners();
|
||||
|
||||
ardour.addCallbacks({
|
||||
onError: console.log,
|
||||
onPositionTime: setPosition,
|
||||
onTransportRoll: setRolling,
|
||||
onRecordState: setRecord
|
||||
});
|
||||
ardour.transport.on('time', setPosition);
|
||||
ardour.transport.on('roll', setRolling);
|
||||
ardour.transport.on('record', setRecord);
|
||||
|
||||
ardour.connect();
|
||||
}
|
||||
|
@ -52,14 +49,14 @@ import { ArdourClient } from '/shared/ardour.js';
|
|||
|
||||
const roll = () => {
|
||||
setRolling(!_rolling);
|
||||
ardour.setTransportRoll(_rolling);
|
||||
ardour.transport.roll = _rolling;
|
||||
};
|
||||
|
||||
dom.roll.addEventListener(touchOrClick, roll);
|
||||
|
||||
const record = () => {
|
||||
setRecord(!_record);
|
||||
ardour.setRecordState(_record);
|
||||
ardour.transport.record = _record;
|
||||
};
|
||||
|
||||
dom.record.addEventListener(touchOrClick, record);
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
<WebSurface>
|
||||
<Name value="Transport"/>
|
||||
<Description value="Provides basic transport control"/>
|
||||
<Version value="0.1.0"/>
|
||||
<Version value="0.1.1"/>
|
||||
</WebSurface>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
|
||||
}
|
93
share/web_surfaces/shared/base/component.js
Normal file
93
share/web_surfaces/shared/base/component.js
Normal 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);
|
||||
}
|
||||
|
||||
}
|
64
share/web_surfaces/shared/base/observable.js
Normal file
64
share/web_surfaces/shared/base/observable.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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',
|
|
@ -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) {}
|
||||
|
||||
}
|
87
share/web_surfaces/shared/components/mixer.js
Normal file
87
share/web_surfaces/shared/components/mixer.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
95
share/web_surfaces/shared/components/parameter.js
Normal file
95
share/web_surfaces/shared/components/parameter.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
72
share/web_surfaces/shared/components/plugin.js
Normal file
72
share/web_surfaces/shared/components/plugin.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
116
share/web_surfaces/shared/components/strip.js
Normal file
116
share/web_surfaces/shared/components/strip.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
81
share/web_surfaces/shared/components/transport.js
Normal file
81
share/web_surfaces/shared/components/transport.js
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user