bitwig-nks-preview-generator
Version:
Streaming convert NKSF files to preview audio with using Bitwig Studio.
220 lines (207 loc) • 5.71 kB
JavaScript
const WebSocket = require('rpc-websockets').Client,
log = require('./logger')('bitwig-studio-remote')
const wait = msec => {
return new Promise(resolve => setTimeout(resolve, msec))
}
/**
* A basic remote client class for Bitwig Studio
* @class
*/
module.exports = class BitwigStudio extends WebSocket {
/**
* default options for connect().
* @static
* @type {Object}
*/
static get defaultOptions() {
return {
url: 'ws://localhost:8887',
timeout: 5000,
Client: BitwigStudio
}
}
/**
* connect() options descriptions
* @static
* @type {Object}
*/
static get optionsDescriptions() {
return {
url: 'Bitwig Studio WebSockets URL.',
timeout: 'timeout msec for connect Bitwig Studio.',
Client: 'Inherited Client class.'
}
}
/**
* Connect a Bitwig Studio application.
* @static
* @async
* @method
* @param {Object} config - requirements configuration for RPC modules.
* @param {class} client - Inherit class of BitwigStudio
* @param {Object} options
* @return {BitwigStudio} - client instance.
*/
static async connect(config, options) {
// default options
const opts = Object.assign({}, this.defaultOptions, options)
let bitwig
try {
bitwig = new opts.Client(opts.url, {
autoconnect: true,
reconnect: true,
max_reconnects: 0
})
// wait for connect
await bitwig.promise('open', undefined, true, opts.timeout)
// requierments configuration
await bitwig.mergeConfig(Object.assign({}, config, {
useAbbreviatedMethodNames: false,
useApplication: true
}))
bitwig.reconnect = false
return bitwig
} catch (err) {
if (bitwig) {
bitwig.close()
}
throw err
}
}
/**
* Closes a WebSocket connection gracefully.
* @method
* @override
* @param {Number} code - socket close code
* @param {String} data - optional data to be sent before closing
* @return {Undefined}
*/
close(code, data) {
this.reconnect = false
super.close(code, data)
this.socket.terminate()
}
/**
* Get a current RPC configuration.
* @property
* @return {Object} - current RPC configuration.
*/
get config() {
return this._config
}
/**
* Merge requierments configuration into current.
* @async
* @method
* @param {Object} config - requirements configuration.
* @return {BitwigStudio} - this instance.
*/
async mergeConfig(config) {
const currentConfig = await this.call('rpc.config')
const fulfilled = Object.keys(config).every(key => {
return config[key] === currentConfig[key]
})
if (fulfilled) {
this._config = currentConfig
} else {
this._config = Object.assign(currentConfig, config)
await this.notify('rpc.config', this._config)
// wait for restart rpc extension.
await this.promise('close')
// wait for reconnect
await this.promise('open')
}
return this
}
/**
* Quit Bitwig Studio application.
* @async
* @method
* @param {Number} sec - shutdown warning seconds.
* @return - this instance
*/
async quit(sec = 5) {
this.shutdown = true
try {
if (sec > 0) {
let remains = sec
while (remains) {
await this.msg(`Automatically shutdown Bitwig Studio after ${remains} seconds.`)
await wait(1000)
remains--
}
}
await this.action('Quit')
await wait(1000)
await this.action('Dialog: No')
this.close()
await this.promise('close')
} finally {
this.close()
}
return this
}
/**
* Invoke a action.
* @method
* @param {String} id - action id.
* @return {Promise}
*/
action(id) {
log.debug('action:', id)
return this.notify('application.getAction.invoke', [id])
}
/**
* Show popup message.
* @method
* @param {String} msg - message.
* @return {Promise}
*/
msg(msg) {
log.debug('msg:', msg)
return this.notify('host.showPopupNotification', [msg])
};
/**
* Promise a future event.
* @method
* @param {String} event - expect event name.
* @param {Array|String|Number|boolean} conditions - expect event params.
* @param {boolean} once
* @param {Number} timeout - timeout msec, default = 3000.
* @return {Promise} - resolve {Array} event params.
*/
promise(event, conditions, once, timeout = 3000) {
return new Promise((resolve, reject) => {
let timerId, handler
const conds = typeof conditions !== 'undefined' && !Array.isArray(conditions)
? [conditions] : conditions
log.debug('promise()', `expect event:${event} params:[${conds}] timeout:${timeout}`)
const cleanup = () => {
clearTimeout(timerId)
this.removeListener(event, handler)
}
timerId = setTimeout(() => {
cleanup()
reject(new Error(`promise() operation timeout. expect event:${event} params:[${conds}]`))
}, timeout)
handler = function() {
const args = Array.prototype.slice.call(arguments)
const ok = typeof conds === 'undefined' ||
conds.every((e, i) => { return e === args[i] })
log.debug(`promise() got event:${event} params:[${args}]`)
if (ok) {
cleanup()
resolve(args)
} else if (once) {
cleanup()
reject(new Error(`promise() event params are unexpeced. event:${event} expect:[${conds}] detect:[${args}]`))
}
}
if (once) {
this.once(event, handler)
} else {
this.on(event, handler)
}
})
}
}