'use strict';
const debug = require('debug')('Cantabile');
const EndPoint = require('./EndPoint');
const EventEmitter = require('events');
/**
* Represents a monitored controller
* Returned from the {{#crossLink "OnscreenKeyboard/watch:method"}}{{/crossLink}} method.
*
* @class ControllerWatcher
* @extends EventEmitter
*/
class ControllerWatcher extends EventEmitter
{
constructor(owner, channel, kind, controller, listener)
{
super();
this.owner = owner;
this._channel = channel;
this._kind = kind;
this._controller = controller;
this._value = null;
this._listener = listener;
}
/**
* Returns the MIDI channel number of controller being watched
*
* @property channel
* @type {Number}
*/
get channel() { return this._channel; }
/**
* Returns the kind of controller being watched
*
* @property kind
* @type {String}
*/
get kind() { return this._kind; }
/**
* Returns the number of the controller being watched
*
* @property controller
* @type {Number}
*/
get controller() { return this._controller; }
/**
* Returns the current value of the controller
*
* @property value
* @type {Number}
*/
get value() { return this._value; }
_start()
{
this.owner.post("/watchController", {
channel: this._channel,
kind: this._kind,
controller: this._controller,
}).then(r => {
if (r.data.id)
{
this.owner._registerWatcher(r.data.id, this);
this._id = r.data.id;
}
this._value = r.data.value;
this._fireChanged();
});
}
_stop()
{
if (this.owner._epid && this._id)
{
this.owner.send("POST", "/unwatch", { id: this._id})
this.owner._revokeWatcher(this._id);
this._id = 0;
this._value = null;
this._fireChanged();
}
}
/**
* Stops monitoring this controller for changes
*
* @method unwatch
*/
unwatch()
{
this._stop();
this.owner._revokeWatcher(this);
}
_update(data)
{
this._value = data.value;
this._fireChanged();
}
_fireChanged()
{
// Function listener?
if (this._listener)
this._listener(this._value, this);
/**
* Fired after a new show note has been added
*
* @event controllerChanged
* @param {Number} value The new value of the controller
* @param {ControllerWatcher} source This object
*/
this.emit('controllerChanged', this._value, this);
}
}
/**
* Provides access to controllers managed by Cantabile's on-screen keyboard device
*
* Access this object via the {{#crossLink "Cantabile/onscreenKeyboard:property"}}{{/crossLink}} property.
*
* @class OnscreenKeyboard
* @extends EndPoint
*/
class OnscreenKeyboard extends EndPoint
{
constructor(owner)
{
super(owner, "/api/onscreenKeyboard");
this.watchers = [];
this.ids = {};
}
/**
* Queries the on-screen keyboard for the current value of a controller
*
* @example
*
* // Get the value of cc 64 on channel 1
* let C = new CantabileApi();
* console.log(await C.onscreenKeyboard.queryController(1, "controller", 64));
*
* @example
*
* let C = new CantabileApi();
* C.onscreenKeyboard.queryController(1, "controller", 64).then(r => console.log(r)));
*
* @method queryController
* @param {Number} channel The MIDI channel number of the controller
* @param {String} kind The MIDI controller kind
* @param {Number} controller The number of the controller
* @return {Promise|String} A promise to provide the controller value
*/
async queryController(channel, kind, controller)
{
await this.owner.untilConnected();
return (await this.post("/queryController", {
channel: channel,
kind: kind,
controller: controller || 0,
})).data.value;
}
_onOpen()
{
for (let i=0; i<this.watchers.length; i++)
{
this.watchers[i]._start();
}
}
_onClose()
{
for (let i=0; i<this.watchers.length; i++)
{
this.watchers[i]._stop();
}
}
/**
* Starts watching a controller for changes
*
* @example
*
* Using a callback function:
*
* let C = new CantabileApi();
*
* // Watch a controller using a callback function
* C.onscreenKeyboard.watchController(1, "controller", 64, function(value) {
* console.log(value);
* })
*
* // The "onscreenKeyboard" end point must be opened before callbacks will happen
* C.onscreenKeyboard.open();
*
* @example
*
* Using the ControllerWatcher class and events:
*
* let C = new CantabileApi();
* let watcher = C.onscreenKeyboard.watchController(1, "controller", 64);
* watcher.on('changed', function(value) {
* console.log(value);
* });
*
* // The "onscreenKeyboard" end point must be opened before callbacks will happen
* C.onscreenKeyboard.open();
*
* /// later, stop listening
* watcher.unwatch();
*
* @method watch
* @param {Number} channel The MIDI channel number of the controller
* @param {String} kind The MIDI controller kind
* @param {Number} controller The number of the controller
* @param {Function} [callback] Optional callback function to be called when the controller value changes.
*
* The callback function has the form function(value, source) where value is the controller value and source
* is the ControllerWatcher instance.
*
* @return {ControllerWatcher}
*/
watch(channel, kind, controller, listener)
{
let w = new ControllerWatcher(this, channel, kind, controller, listener);
this.watchers.push(w);
if (this.watchers.length == 1)
this.open();
if (this.isOpen)
w._start();
return w;
}
/**
* Inject MIDI from the on-screen keyboard device
*
* @method injectMidi
* @param {object} data An array of bytes or a MidiControllerEvent
*
* @example
*
* Using a callback function:
*
* // Send a note on event
* C.onscreenKeyboard.inject([0x90, 64, 64]);
*
* @example
*
* Using the MidiControllerEvent
*
* // Send Midi CC 23 = 127
* let watcher = C.onscreenKeyboard.inject({
* channel: 0,
* kind: "controller",
* controller: 23,
* value: 127,
* });
*
*/
injectMidi(data)
{
this.post("/injectMidi", {
value: data
})
}
_registerWatcher(id, watcher)
{
this.ids[id] = watcher;
}
_revokeWatcher(id)
{
delete this.ids[id];
}
_revokeWatcher(w)
{
this.watchers = this.watchers.filter(x=>x != w);
if (this.watchers.length == 0)
this.close();
}
_onEvent_controllerChanged(data)
{
// Get the watcher
let w = this.ids[data.id];
if (w)
{
w._update(data);
}
}
}
module.exports = OnscreenKeyboard;