jzz
Version:
MIDI library for Node.js and web-browsers
1,522 lines (1,486 loc) • 174 kB
JavaScript
(function(global, factory) {
/* istanbul ignore next */
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = factory();
}
else if (typeof define === 'function' && define.amd) {
define('JZZ', [], factory);
}
else {
if (!global) global = window;
if (global.JZZ && global.JZZ.MIDI) return;
global.JZZ = factory();
}
})(this, function() {
var _scope = typeof window === 'undefined' ? global : window;
var _version = '1.9.3';
var i, j, k, m, n;
/* istanbul ignore next */
var _time = Date.now || function () { return new Date().getTime(); };
var _startTime = _time();
/* istanbul ignore next */
var _now = typeof performance != 'undefined' && performance.now ?
function() { return performance.now(); } : function() { return _time() - _startTime; };
var _schedule = function(f) {
setTimeout(f, 0);
};
function _nop() {}
function _func(f) { return typeof f == 'function'; }
// _R: common root for all async objects
function _R() {
this._orig = this;
this._ready = false;
this._queue = [];
this._log = [];
}
_R.prototype._exec = function() {
while (this._ready && this._queue.length) {
var x = this._queue.shift();
x[0].apply(this, x[1]);
}
};
_R.prototype._push = function(func, arg) { this._queue.push([func, arg]); _R.prototype._exec.apply(this); };
_R.prototype._slip = function(func, arg) { this._queue.unshift([func, arg]); };
_R.prototype._pause = function() { this._ready = false; };
_R.prototype._resume = function() { this._ready = true; _R.prototype._exec.apply(this); };
_R.prototype._break = function(err) { this._orig._bad = true; this._orig._log.push(err || 'Unknown JZZ error'); };
_R.prototype._repair = function() { this._orig._bad = false; };
_R.prototype._crash = function(err) { this._break(err); this._resume(); };
_R.prototype._err = function() { return this._log[this._log.length - 1]; };
_R.prototype.log = function() { return _clone(this._log); };
_R.prototype._dup = function() {
var F = function() {};
F.prototype = this._orig;
var ret = new F();
ret._ready = false;
ret._queue = [];
return ret;
};
_R.prototype._image = function() { return this._dup(); };
_R.prototype._thenable = function() {
if (this.then) return this;
var self = this;
var F = function() {}; F.prototype = self;
var ret = new F();
ret.then = function(good, bad) { self._push(_then, [good, bad]); return this; };
return ret;
};
function _then(good, bad) {
if (this._bad) {
if (_func(bad)) bad.apply(this, [new Error(this._err())]);
}
else {
if (_func(good)) good.apply(this, [this]);
}
}
function _wait(obj, delay) {
if (this._bad) obj._crash(this._err());
else setTimeout(function() { obj._resume(); }, delay);
}
_R.prototype.wait = function(delay) {
if (!delay) return this;
var ret = this._image();
this._push(_wait, [ret, delay]);
return ret._thenable();
};
function _kick(obj) { if (this._bad) obj._break(this._err()); obj._resume(); }
function _rechain(self, obj, name) {
self[name] = function() {
var arg = arguments;
var ret = obj._image();
this._push(_kick, [ret]);
return ret[name].apply(ret, arg);
};
}
function _and(q) {
if (!this._bad) {
if (_func(q)) q.apply(this); else console.log(q);
}
}
_R.prototype.and = function(func) { this._push(_and, [func]); return this._thenable(); };
function _or(q) {
if (this._bad) {
if (_func(q)) q.apply(this); else console.log(q);
}
}
_R.prototype.or = function(func) { this._push(_or, [func]); return this._thenable(); };
_R.prototype._info = {};
_R.prototype.info = function() {
var info = _clone(this._orig._info);
if (typeof info.engine == 'undefined') info.engine = 'none';
if (typeof info.sysex == 'undefined') info.sysex = true;
return info;
};
_R.prototype.name = function() { return this.info().name; };
function _close(obj) {
if (this._bad) obj._crash(this._err());
else {
this._break('Closed');
obj._resume();
}
}
_R.prototype.close = function() {
var ret = new _R();
if (this._close) this._push(this._close, []);
this._push(_close, [ret]);
return ret._thenable();
};
function _tryAny(arr) {
if (!arr.length) {
this._break();
return;
}
var func = arr.shift();
if (arr.length) {
var self = this;
this._slip(_or, [ function() { _tryAny.apply(self, [arr]); } ]);
}
try {
this._repair();
func.apply(this);
}
catch (err) {
this._break(err.toString());
}
}
function _push(arr, obj) {
for (var i = 0; i < arr.length; i++) if (arr[i] === obj) return;
arr.push(obj);
}
function _pop(arr, obj) {
for (var i = 0; i < arr.length; i++) if (arr[i] === obj) {
arr.splice(i, 1);
return;
}
}
// _J: JZZ object
function _J() {
_R.apply(this);
}
_J.prototype = new _R();
function _for(x, f) {
for(var k in x) if (x.hasOwnProperty(k)) f.call(this, k);
}
function _clone(obj, key, val) {
if (typeof key == 'undefined') return _clone(obj, [], []);
if (obj instanceof Object) {
for (var i = 0; i < key.length; i++) if (key[i] === obj) return val[i];
var ret;
if (obj instanceof Array) ret = []; else ret = {};
key.push(obj); val.push(ret);
_for(obj, function(k) { ret[k] = _clone(obj[k], key, val); });
return ret;
}
return obj;
}
_J.prototype._info = { name: 'JZZ.js', ver: _version, version: _version, inputs: [], outputs: [] };
var _outs = [];
var _ins = [];
var _outsW = [];
var _insW = [];
var _outsM = {};
var _insM = {};
function _postRefresh() {
_jzz._info.engine = _engine._type;
_jzz._info.version = _engine._version;
_jzz._info.sysex = _engine._sysex;
_jzz._info.inputs = [];
_jzz._info.outputs = [];
_outs = [];
_ins = [];
_engine._allOuts = {};
_engine._allIns = {};
var i, x;
for (i = 0; i < _engine._outs.length; i++) {
x = _engine._outs[i];
if (_outsM[x.name]) continue;
x.engine = _engine;
_engine._allOuts[x.name] = x;
_jzz._info.outputs.push({
id: x.name,
name: x.name,
manufacturer: x.manufacturer,
version: x.version,
engine: _engine._type
});
_outs.push(x);
}
for (i = 0; i < _virtual._outs.length; i++) {
x = _virtual._outs[i];
if (_outsM[x.name]) continue;
_jzz._info.outputs.push({
id: x.name,
name: x.name,
manufacturer: x.manufacturer,
version: x.version,
engine: x.type
});
_outs.push(x);
}
for (i = 0; i < _engine._ins.length; i++) {
x = _engine._ins[i];
if (_insM[x.name]) continue;
x.engine = _engine;
_engine._allIns[x.name] = x;
_jzz._info.inputs.push({
id: x.name,
name: x.name,
manufacturer: x.manufacturer,
version: x.version,
engine: _engine._type
});
_ins.push(x);
}
for (i = 0; i < _virtual._ins.length; i++) {
x = _virtual._ins[i];
if (_insM[x.name]) continue;
_jzz._info.inputs.push({
id: x.name,
name: x.name,
manufacturer: x.manufacturer,
version: x.version,
engine: x.type
});
_ins.push(x);
}
if (_jzz._watcher && _jzz._watcher._handles.length) {
var diff = _diff(_insW, _outsW, _jzz._info.inputs, _jzz._info.outputs);
if (diff) {
for (j = 0; j < diff.inputs.removed.length; j++) {
x = _engine._inMap[diff.inputs.removed[j].name];
if (x) x._closeAll();
}
for (j = 0; j < diff.outputs.removed.length; j++) {
x = _engine._outMap[diff.outputs.removed[j].name];
if (x) x._closeAll();
}
_schedule(function() { _fireW(diff); });
}
}
_insW = _jzz._info.inputs;
_outsW = _jzz._info.outputs;
}
function _refresh() {
if (!this._bad) _engine._refresh(this);
}
_J.prototype.refresh = function() {
this._push(_refresh, []);
return this._thenable();
};
function _filterList(q, arr) {
var i, n;
if (_func(q)) q = q(arr);
if (!(q instanceof Array)) q = [q];
var before = [];
var after = [];
var etc = arr.slice();
var a = before;
for (i = 0; i < q.length; i++) {
if (typeof q[i] == 'undefined') a = after;
else if (q[i] instanceof RegExp) for (n = 0; n < etc.length; n++) {
if (q[i].test(etc[n].name)) {
a.push(etc[n]);
etc.splice(n, 1);
n--;
}
}
else {
for (n = 0; n < etc.length; n++) if (q[i] + '' === n + '' || q[i] === etc[n].name || (q[i] instanceof Object && q[i].name === etc[n].name)) {
a.push(etc[n]);
etc.splice(n, 1);
n--;
}
}
}
return a == before ? before : before.concat(etc).concat(after);
}
function _notFound(port, q) {
var msg;
if (q instanceof RegExp) msg = 'Port matching ' + q + ' not found';
else if (q instanceof Object || typeof q == 'undefined') msg = 'Port not found';
else msg = 'Port "' + q + '" not found';
port._crash(msg);
}
function _openMidiOut(port, arg) {
if (this._bad) port._crash(this._err());
else {
var arr = _filterList(arg, _outs);
if (!arr.length) { _notFound(port, arg); return; }
var pack = function(x) { return function() { x.engine._openOut(this, x.name); }; };
for (var i = 0; i < arr.length; i++) arr[i] = pack(arr[i]);
port._slip(_tryAny, [arr]);
port._resume();
}
}
_J.prototype.openMidiOut = function(arg) {
var port = new _M();
this._push(_refresh, []);
this._push(_openMidiOut, [port, arg]);
return port._thenable();
};
_J.prototype._openMidiOutNR = function(arg) {
var port = new _M();
this._push(_openMidiOut, [port, arg]);
return port._thenable();
};
function _openMidiIn(port, arg) {
if (this._bad) port._crash(this._err());
else {
var arr = _filterList(arg, _ins);
if (!arr.length) { _notFound(port, arg); return; }
var pack = function(x) { return function() { x.engine._openIn(this, x.name); }; };
for (var i = 0; i < arr.length; i++) arr[i] = pack(arr[i]);
port._slip(_tryAny, [arr]);
port._resume();
}
}
_J.prototype.openMidiIn = function(arg) {
var port = new _M();
this._push(_refresh, []);
this._push(_openMidiIn, [port, arg]);
return port._thenable();
};
_J.prototype._openMidiInNR = function(arg) {
var port = new _M();
this._push(_openMidiIn, [port, arg]);
return port._thenable();
};
function _onChange(watcher, arg) {
if (this._bad) watcher._crash();
else {
watcher._slip(_connectW, [arg]);
watcher._resume();
}
}
_J.prototype.onChange = function(arg) {
if (!this._orig._watcher) this._orig._watcher = new _W();
var watcher = this._orig._watcher._image();
this._push(_onChange, [watcher, arg]);
return watcher._thenable();
};
_J.prototype._close = function() {
_engine._close();
var a = _plugged.slice();
for (var i = 0; i < a.length; i++) if (a[i]) {
if (a[i]._close) a[i]._close();
else if (a[i].close) a[i].close();
}
};
// _M: MIDI-In/Out object
function _M() {
_R.apply(this);
this._handles = [];
this._outs = [];
}
_M.prototype = new _R();
_M.prototype._filter = function(msg) {
if (this._orig._mpe) {
var out;
var outs = 0;
if (this._handles && this._handles.length) {
outs = this._handles.length;
out = this._handles[0];
}
if (this._outs && this._outs.length) {
outs = this._outs.length;
out = this._outs[0];
}
if (outs == 1 && !out._mpe) {
msg = this._orig._mpe.filter(msg);
}
}
return msg;
};
_M.prototype._receive = function(msg) { this._emit(this._filter(msg)); };
function _receive(msg) { if (!this._bad) this._receive(msg); }
_M.prototype.send = function() {
this._push(_receive, [_midi.apply(null, arguments)]);
return this._thenable();
};
_M.prototype.note = function(c, n, v, t) {
this.noteOn(c, n, v);
if (typeof this._ch == 'undefined' && typeof this._master == 'undefined') {
if (t > 0) this.wait(t).noteOff(c, n);
}
else {
if (v > 0) this.wait(v).noteOff(c);
}
return this._thenable();
};
function _midi(msg) { return msg.isMidi2 ? new UMP(msg) : MIDI.apply(null, arguments); }
_M.prototype._emit = function(msg) {
var i, m;
for (i = 0; i < this._handles.length; i++) {
m = _midi(msg);
this._handles[i].apply(this, [m._stamp(this)]);
}
for (i = 0; i < this._outs.length; i++) {
m = _midi(msg);
if (!m._stamped(this._outs[i])) this._outs[i].send(m._stamp(this));
}
};
function _emit(msg) { this._emit(msg); }
_M.prototype.emit = function(msg) {
this._push(_emit, [msg]);
return this._thenable();
};
function _connect(arg) {
if (_func(arg)) _push(this._orig._handles, arg);
else _push(this._orig._outs, arg);
}
function _disconnect(arg) {
if (typeof arg == 'undefined') {
this._orig._handles = [];
this._orig._outs = [];
}
else if (_func(arg)) _pop(this._orig._handles, arg);
else _pop(this._orig._outs, arg);
}
_M.prototype.connect = function(arg) {
this._push(_connect, [arg]);
return this._thenable();
};
_M.prototype.disconnect = function(arg) {
this._push(_disconnect, [arg]);
return this._thenable();
};
_M.prototype.connected = function() {
return this._orig._handles.length + this._orig._outs.length;
};
_M.prototype._image = function() {
var dup = this._dup();
dup._gr = this._gr;
dup._ch = this._ch;
dup._sxid = this._sxid;
dup._master = this._master;
dup._band = this._band;
return dup;
};
_M.prototype._sxid = 0x7f;
_M.prototype.sxId = function(id) {
if (typeof id == 'undefined') id = _M.prototype._sxid;
if (id == this._sxid) return this._thenable();
id = _7b(id);
var img = this._image();
img._sxid = id;
this._push(_kick, [img]);
return img._thenable();
};
_M.prototype.ch = function(c) {
if (c == this._ch || typeof c == 'undefined' && typeof this._ch == 'undefined') return this._thenable();
var img = this._image();
if (typeof c != 'undefined') c = _ch(c);
img._ch = c;
img._master = undefined;
img._band = undefined;
this._push(_kick, [img]);
return img._thenable();
};
_M.prototype.MIDI1 = function() {
var img = this._image();
img._ch = undefined;
img._sxid = _M.prototype._sxid;
img._master = undefined;
img._band = undefined;
this._push(_kick, [img]);
return img._thenable();
};
_M.prototype.MIDI2 = function() {
var img = this._image();
img._ch = undefined;
img._sxid = _M.prototype._sxid;
img._master = undefined;
img._band = undefined;
var m2 = new _M2(img);
this._push(_kick, [m2]);
return m2._thenable();
};
function _mpe(m, n) {
if (!this._orig._mpe) this._orig._mpe = new MPE();
this._orig._mpe.setup(m, n);
}
_M.prototype.mpe = function(m, n) {
if (m == this._master && n == this._band || typeof m == 'undefined' && typeof this._master == 'undefined') return this._thenable();
if (typeof m != 'undefined') MPE.validate(m, n);
if (!n) return this.ch(m);
var img = this._image();
img._ch = undefined;
img._master = m;
img._band = n;
this._push(_mpe, [m, n]);
this._push(_kick, [img]);
return img._thenable();
};
function _validateChannel(c) {
if (c != parseInt(c) || c < 0 || c > 15)
throw RangeError('Bad channel value (must not be less than 0 or more than 15): ' + c);
}
// _M2: MIDI 2.0 adaptor
function _M2(sink) {
_R.apply(this);
this._sink = sink;
}
_M2.prototype = new _R();
_M2.prototype._sxid = _M.prototype._sxid;
_M2.prototype._receive = function(msg) { this._sink._receive(msg); };
function _midi2(msg) { return msg.isMidi && !msg.isMidi2 ? new MIDI(msg) : UMP.apply(null, arguments); }
_M2.prototype.send = function() {
this._push(_receive, [_midi2.apply(null, arguments)]);
return this._thenable();
};
_M2.prototype._image = _M.prototype._image;
_M2.prototype.connect = function(arg) {
this._sink.connect(arg);
this._push(_kick, [this._sink]);
return this._thenable();
};
_M2.prototype.disconnect = function(arg) {
this._sink.disconnect(arg);
this._push(_kick, [this._sink]);
return this._thenable();
};
_M2.prototype.connected = function() { return this._sink.connected(); };
_M2.prototype.sxId = _M.prototype.sxId;
_M2.prototype.ch = _M.prototype.ch;
_M2.prototype.gr = function(g) {
if (g == this._gr || typeof g == 'undefined' && typeof this._gr == 'undefined') return this._thenable();
var img = this._image();
if (typeof g != 'undefined') g = _7b(g);
img._gr = g;
this._push(_kick, [img]);
return img._thenable();
};
_M2.prototype.MIDI1 = function() {
var img = this._sink._image();
this._push(_kick, [img]);
return img._thenable();
};
_M2.prototype.MIDI2 = function() {
var img = this._image();
img._gr = undefined;
img._ch = undefined;
img._sxid = _M.prototype._sxid;
this._push(_kick, [img]);
return img._thenable();
};
// _W: Watcher object ~ MIDIAccess.onstatechange
function _W() {
_R.apply(this);
this._handles = [];
_rechain(this, _jzz, 'refresh');
_rechain(this, _jzz, 'openMidiOut');
_rechain(this, _jzz, 'openMidiIn');
_rechain(this, _jzz, 'onChange');
_rechain(this, _jzz, 'close');
}
_W.prototype = new _R();
function _connectW(arg) {
if (_func(arg)) {
if (!this._orig._handles.length) _engine._watch();
_push(this._orig._handles, arg);
}
}
function _disconnectW(arg) {
if (typeof arg == 'undefined') this._orig._handles = [];
else _pop(this._orig._handles, arg);
if (!this._orig._handles.length) _engine._unwatch();
}
_W.prototype.connect = function(arg) {
this._push(_connectW, [arg]);
return this._thenable();
};
_W.prototype.disconnect = function(arg) {
this._push(_disconnectW, [arg]);
return this._thenable();
};
function _changed(x0, y0, x1, y1) {
var i;
if (x0.length != x1.length || y0.length != y1.length) return true;
for (i = 0; i < x0.length; i++) if (x0[i].name != x1[i].name) return true;
for (i = 0; i < y0.length; i++) if (y0[i].name != y1[i].name) return true;
return false;
}
function _diff(x0, y0, x1, y1) {
if (!_changed(x0, y0, x1, y1)) return;
var ax = []; // added
var ay = [];
var rx = []; // removed
var ry = [];
var i;
var h = {};
for (i = 0; i < x0.length; i++) h[x0[i].name] = true;
for (i = 0; i < x1.length; i++) if (!h[x1[i].name]) ax.push(x1[i]);
h = {};
for (i = 0; i < x1.length; i++) h[x1[i].name] = true;
for (i = 0; i < x0.length; i++) if (!h[x0[i].name]) rx.push(x0[i]);
h = {};
for (i = 0; i < y0.length; i++) h[y0[i].name] = true;
for (i = 0; i < y1.length; i++) if (!h[y1[i].name]) ay.push(y1[i]);
h = {};
for (i = 0; i < y1.length; i++) h[y1[i].name] = true;
for (i = 0; i < y0.length; i++) if (!h[y0[i].name]) ry.push(y0[i]);
return { inputs: { added: ax, removed: rx }, outputs: { added: ay, removed: ry } };
}
function _fireW(arg) {
for (i = 0; i < _jzz._watcher._handles.length; i++) _jzz._watcher._handles[i].apply(_jzz, [arg]);
}
var _jzz;
var _engine = { _outs: [], _ins: [], _close: _nop };
var _virtual = { _outs: [], _ins: [] };
var _plugged = [];
// Node.js
function _tryNODE() {
if (typeof module != 'undefined' && module.exports) {
var jazzmidi = require('jazz-midi');
if (jazzmidi) {
_initNode(jazzmidi);
return;
}
}
this._break();
}
// Jazz-Plugin
function _tryJazzPlugin() {
var div = document.createElement('div');
div.style.visibility = 'hidden';
document.body.appendChild(div);
var obj = document.createElement('object');
obj.style.visibility = 'hidden';
obj.style.width = '0px'; obj.style.height = '0px';
obj.classid = 'CLSID:1ACE1618-1C7D-4561-AEE1-34842AA85E90';
obj.type = 'audio/x-jazz';
document.body.appendChild(obj);
/* istanbul ignore next */
if (obj.isJazz) {
_initJazzPlugin(obj);
return;
}
this._break();
}
// Web MIDI API
var _navigator;
var _requestMIDIAccess;
function _findMidiAccess() {
if (typeof navigator !== 'undefined' && navigator.requestMIDIAccess) {
_navigator = navigator;
_requestMIDIAccess = navigator.requestMIDIAccess;
try {
if (_requestMIDIAccess.toString().indexOf('JZZ(') != -1) _requestMIDIAccess = undefined;
}
catch (err) {}
}
}
function _tryWebMIDI() {
_findMidiAccess();
if (_requestMIDIAccess) {
var self = this;
var onGood = function(midi) {
_initWebMIDI(midi);
self._resume();
};
var onBad = function(msg) {
self._crash(msg);
};
var opt = {};
_requestMIDIAccess.call(_navigator, opt).then(onGood, onBad);
this._pause();
return;
}
this._break();
}
function _tryWebMIDIsysex() {
_findMidiAccess();
if (_requestMIDIAccess) {
var self = this;
var onGood = function(midi) {
_initWebMIDI(midi, true);
self._resume();
};
var onBad = function(msg) {
self._crash(msg);
};
var opt = { sysex:true };
_requestMIDIAccess.call(_navigator, opt).then(onGood, onBad);
this._pause();
return;
}
this._break();
}
// Web-extension
function _tryCRX() {
var self = this;
var inst;
var msg;
function eventHandle(evt) {
inst = true;
var a = evt.detail;
if (!a) {
if (!msg) msg = document.getElementById('jazz-midi-msg');
if (!msg) return;
try { a = JSON.parse(msg.innerText); } catch (err) {}
msg.innerText = '';
}
document.removeEventListener('jazz-midi-msg', eventHandle);
if (a[0] === 'version') {
_initCRX(msg, a[2]);
self._resume();
}
else {
self._crash();
}
}
this._pause();
try {
document.addEventListener('jazz-midi-msg', eventHandle);
document.dispatchEvent(new Event('jazz-midi'));
}
catch (err) {}
setTimeout(function() { if (!inst) self._crash(); }, 50);
}
/* istanbul ignore next */
function _zeroBreak() {
this._pause();
var self = this;
_schedule(function() { self._crash(); });
}
function _filterEngines(opt) {
var ret = [];
var arr = _filterEngineNames(opt);
for (var i = 0; i < arr.length; i++) {
if (arr[i] == 'webmidi') {
if (opt && opt.sysex === true) ret.push(_tryWebMIDIsysex);
if (!opt || opt.sysex !== true || opt.degrade === true) ret.push(_tryWebMIDI);
}
else if (arr[i] == 'node') { ret.push(_tryNODE); ret.push(_zeroBreak); }
else if (arr[i] == 'extension') ret.push(_tryCRX);
else if (arr[i] == 'plugin') ret.push(_tryJazzPlugin);
}
ret.push(_initNONE);
return ret;
}
function _filterEngineNames(opt) {
var web = ['node', 'extension', 'plugin', 'webmidi'];
if (!opt || !opt.engine) return web;
var arr = opt.engine instanceof Array ? opt.engine : [opt.engine];
var dup = {};
var none;
var etc;
var head = [];
var tail = [];
var i;
for (i = 0; i < arr.length; i++) {
var name = arr[i].toString().toLowerCase();
if (dup[name]) continue;
dup[name] = true;
if (name === 'none') none = true;
if (name === 'etc' || typeof name == 'undefined') etc = true;
if (etc) tail.push(name); else head.push(name);
_pop(web, name);
}
if (etc || head.length || tail.length) none = false;
return none ? [] : head.concat(etc ? web : tail);
}
function _initJZZ(opt) {
_jzz = new _J();
_jzz._options = opt;
_jzz._push(_tryAny, [_filterEngines(opt)]);
_jzz.refresh();
_jzz._resume();
}
function _initNONE() {
_engine._type = 'none';
_engine._version = _version;
_engine._sysex = true;
_engine._outs = [];
_engine._ins = [];
_engine._outMap = {};
_engine._inMap = {};
_engine._refresh = function() { _postRefresh(); };
_engine._watch = _nop;
_engine._unwatch = _nop;
_engine._close = _nop;
}
// common initialization for Jazz-Plugin and jazz-midi
function _initEngineJP() {
_engine._inArr = [];
_engine._outArr = [];
_engine._inMap = {};
_engine._outMap = {};
_engine._outsW = [];
_engine._insW = [];
_engine._version = _engine._main.version;
_engine._sysex = true;
var watcher;
function _closeAll() {
for (var i = 0; i < this.clients.length; i++) this._close(this.clients[i]);
}
_engine._refresh = function() {
_engine._outs = [];
_engine._ins = [];
var i, x;
for (i = 0; (x = _engine._main.MidiOutInfo(i)).length; i++) {
_engine._outs.push({ type: _engine._type, name: x[0], manufacturer: x[1], version: x[2] });
}
for (i = 0; (x = _engine._main.MidiInInfo(i)).length; i++) {
_engine._ins.push({ type: _engine._type, name: x[0], manufacturer: x[1], version: x[2] });
}
_postRefresh();
};
_engine._openOut = function(port, name) {
var impl = _engine._outMap[name];
if (!impl) {
if (_engine._pool.length <= _engine._outArr.length) _engine._pool.push(_engine._newPlugin());
impl = {
name: name,
clients: [],
info: {
name: name,
manufacturer: _engine._allOuts[name].manufacturer,
version: _engine._allOuts[name].version,
type: 'MIDI-out',
sysex: _engine._sysex,
engine: _engine._type
},
_close: function(port) { _engine._closeOut(port); },
_closeAll: _closeAll,
_receive: function(a) { if (a.length) this.plugin.MidiOutRaw(a.slice()); }
};
var plugin = _engine._pool[_engine._outArr.length];
impl.plugin = plugin;
_engine._outArr.push(impl);
_engine._outMap[name] = impl;
}
if (!impl.open) {
var s = impl.plugin.MidiOutOpen(name);
if (s !== name) {
if (s) impl.plugin.MidiOutClose();
port._break(); return;
}
impl.open = true;
}
port._orig._impl = impl;
_push(impl.clients, port._orig);
port._info = impl.info;
port._receive = function(arg) { impl._receive(arg); };
port._close = function() { impl._close(this); };
};
_engine._openIn = function(port, name) {
var impl = _engine._inMap[name];
if (!impl) {
if (_engine._pool.length <= _engine._inArr.length) _engine._pool.push(_engine._newPlugin());
impl = {
name: name,
clients: [],
info: {
name: name,
manufacturer: _engine._allIns[name].manufacturer,
version: _engine._allIns[name].version,
type: 'MIDI-in',
sysex: _engine._sysex,
engine: _engine._type
},
_close: function(port) { _engine._closeIn(port); },
_closeAll: _closeAll,
handle: function(t, a) {
for (var i = 0; i < this.clients.length; i++) {
var msg = MIDI(a);
this.clients[i]._emit(msg);
}
}
};
var makeHandle = function(x) { return function(t, a) { x.handle(t, a); }; };
impl.onmidi = makeHandle(impl);
var plugin = _engine._pool[_engine._inArr.length];
impl.plugin = plugin;
_engine._inArr.push(impl);
_engine._inMap[name] = impl;
}
if (!impl.open) {
var s = impl.plugin.MidiInOpen(name, impl.onmidi);
if (s !== name) {
if (s) impl.plugin.MidiInClose();
port._break(); return;
}
impl.open = true;
}
port._orig._impl = impl;
_push(impl.clients, port._orig);
port._info = impl.info;
port._close = function() { impl._close(this); };
};
_engine._closeOut = function(port) {
var impl = port._impl;
_pop(impl.clients, port._orig);
if (!impl.clients.length && impl.open) {
impl.open = false;
impl.plugin.MidiOutClose();
}
};
_engine._closeIn = function(port) {
var impl = port._impl;
_pop(impl.clients, port._orig);
if (!impl.clients.length && impl.open) {
impl.open = false;
impl.plugin.MidiInClose();
}
};
_engine._close = function() {
for (var i = 0; i < _engine._inArr.length; i++) if (_engine._inArr[i].open) _engine._inArr[i].plugin.MidiInClose();
_engine._unwatch();
};
_engine._watch = function() {
if (!watcher) watcher = setInterval(function() { _engine._refresh(); }, 250);
};
_engine._unwatch = function() {
if (watcher) clearInterval(watcher);
watcher = undefined;
};
}
function _initNode(obj) {
_engine._type = 'node';
_engine._main = obj;
_engine._pool = [];
_engine._newPlugin = function() { return new obj.MIDI(); };
_initEngineJP();
}
/* istanbul ignore next */
function _initJazzPlugin(obj) {
_engine._type = 'plugin';
_engine._main = obj;
_engine._pool = [obj];
_engine._newPlugin = function() {
var plg = document.createElement('object');
plg.style.visibility = 'hidden';
plg.style.width = '0px'; obj.style.height = '0px';
plg.classid = 'CLSID:1ACE1618-1C7D-4561-AEE1-34842AA85E90';
plg.type = 'audio/x-jazz';
document.body.appendChild(plg);
return plg.isJazz ? plg : undefined;
};
_initEngineJP();
}
function _initWebMIDI(access, sysex) {
_engine._type = 'webmidi';
_engine._version = 43;
_engine._sysex = !!sysex;
_engine._access = access;
_engine._inMap = {};
_engine._outMap = {};
_engine._outsW = [];
_engine._insW = [];
var watcher;
function _closeAll() {
for (var i = 0; i < this.clients.length; i++) this._close(this.clients[i]);
}
_engine._refresh = function() {
_engine._outs = [];
_engine._ins = [];
_engine._access.outputs.forEach(function(port) {
_engine._outs.push({type: _engine._type, name: port.name, manufacturer: port.manufacturer, version: port.version});
});
_engine._access.inputs.forEach(function(port) {
_engine._ins.push({type: _engine._type, name: port.name, manufacturer: port.manufacturer, version: port.version});
});
_postRefresh();
};
_engine._openOut = function(port, name) {
var impl = _engine._outMap[name];
if (!impl) {
impl = {
name: name,
clients: [],
info: {
name: name,
manufacturer: _engine._allOuts[name].manufacturer,
version: _engine._allOuts[name].version,
type: 'MIDI-out',
sysex: _engine._sysex,
engine: _engine._type
},
_close: function(port) { _engine._closeOut(port); },
_closeAll: _closeAll,
_receive: function(a) { if (impl.dev && a.length) this.dev.send(a.slice()); }
};
}
var found;
_engine._access.outputs.forEach(function(dev) {
if (dev.name === name) found = dev;
});
if (found) {
impl.dev = found;
_engine._outMap[name] = impl;
port._orig._impl = impl;
_push(impl.clients, port._orig);
port._info = impl.info;
port._receive = function(arg) { impl._receive(arg); };
port._close = function() { impl._close(this); };
if (impl.dev.open) {
port._pause();
impl.dev.open().then(function() {
port._resume();
}, function() {
port._crash();
});
}
}
else port._break();
};
_engine._openIn = function(port, name) {
var impl = _engine._inMap[name];
if (!impl) {
impl = {
name: name,
clients: [],
info: {
name: name,
manufacturer: _engine._allIns[name].manufacturer,
version: _engine._allIns[name].version,
type: 'MIDI-in',
sysex: _engine._sysex,
engine: _engine._type
},
_close: function(port) { _engine._closeIn(port); },
_closeAll: _closeAll,
handle: function(evt) {
for (var i = 0; i < this.clients.length; i++) {
var msg = MIDI([].slice.call(evt.data));
this.clients[i]._emit(msg);
}
}
};
}
var found;
_engine._access.inputs.forEach(function(dev) {
if (dev.name === name) found = dev;
});
if (found) {
impl.dev = found;
var makeHandle = function(x) { return function(evt) { x.handle(evt); }; };
impl.dev.onmidimessage = makeHandle(impl);
_engine._inMap[name] = impl;
port._orig._impl = impl;
_push(impl.clients, port._orig);
port._info = impl.info;
port._close = function() { impl._close(this); };
if (impl.dev.open) {
port._pause();
impl.dev.open().then(function() {
port._resume();
}, function() {
port._crash();
});
}
}
else port._break();
};
_engine._closeOut = function(port) {
var impl = port._impl;
_pop(impl.clients, port._orig);
if (!impl.clients.length) {
if (impl.dev && impl.dev.close) impl.dev.close();
impl.dev = undefined;
}
};
_engine._closeIn = function(port) {
var impl = port._impl;
_pop(impl.clients, port._orig);
if (!impl.clients.length) {
if (impl.dev) {
impl.dev.onmidimessage = null;
if (impl.dev.close) impl.dev.close();
}
impl.dev = undefined;
}
};
_engine._close = function() {
_engine._unwatch();
};
_engine._watch = function() {
_engine._access.onstatechange = function() {
watcher = true;
_schedule(function() {
if (watcher) {
_engine._refresh();
watcher = false;
}
});
};
};
_engine._unwatch = function() {
_engine._access.onstatechange = undefined;
};
}
function _initCRX(msg, ver) {
_engine._type = 'extension';
_engine._version = ver;
_engine._sysex = true;
_engine._pool = [];
_engine._outs = [];
_engine._ins = [];
_engine._inArr = [];
_engine._outArr = [];
_engine._inMap = {};
_engine._outMap = {};
_engine._outsW = [];
_engine._insW = [];
_engine.refreshClients = [];
_engine._msg = msg;
_engine._newPlugin = function() {
var plugin = { id: _engine._pool.length };
_engine._pool.push(plugin);
if (!plugin.id) plugin.ready = true;
else document.dispatchEvent(new CustomEvent('jazz-midi', { detail: ['new'] }));
};
_engine._newPlugin();
_engine._refresh = function(client) {
_engine.refreshClients.push(client);
client._pause();
_schedule(function() {
document.dispatchEvent(new CustomEvent('jazz-midi', { detail: ['refresh'] }));
});
};
function _closeAll() {
for (var i = 0; i < this.clients.length; i++) this._close(this.clients[i]);
}
_engine._openOut = function(port, name) {
var impl = _engine._outMap[name];
if (!impl) {
if (_engine._pool.length <= _engine._outArr.length) _engine._newPlugin();
var plugin = _engine._pool[_engine._outArr.length];
impl = {
name: name,
clients: [],
info: {
name: name,
manufacturer: _engine._allOuts[name].manufacturer,
version: _engine._allOuts[name].version,
type: 'MIDI-out',
sysex: _engine._sysex,
engine: _engine._type
},
_start: function() { document.dispatchEvent(new CustomEvent('jazz-midi', { detail: ['openout', plugin.id, name] })); },
_close: function(port) { _engine._closeOut(port); },
_closeAll: _closeAll,
_receive: function(a) { if (a.length) { var v = a.slice(); v.splice(0, 0, 'play', plugin.id); document.dispatchEvent(new CustomEvent('jazz-midi', {detail: v})); } }
};
impl.plugin = plugin;
plugin.output = impl;
_engine._outArr.push(impl);
_engine._outMap[name] = impl;
}
port._orig._impl = impl;
_push(impl.clients, port._orig);
port._info = impl.info;
port._receive = function(arg) { impl._receive(arg); };
port._close = function() { impl._close(this); };
if (!impl.open) {
port._pause();
if (impl.plugin.ready) impl._start();
}
};
_engine._openIn = function(port, name) {
var impl = _engine._inMap[name];
if (!impl) {
if (_engine._pool.length <= _engine._inArr.length) _engine._newPlugin();
var plugin = _engine._pool[_engine._inArr.length];
impl = {
name: name,
clients: [],
info: {
name: name,
manufacturer: _engine._allIns[name].manufacturer,
version: _engine._allIns[name].version,
type: 'MIDI-in',
sysex: _engine._sysex,
engine: _engine._type
},
_start: function() { document.dispatchEvent(new CustomEvent('jazz-midi', { detail: ['openin', plugin.id, name] })); },
_close: function(port) { _engine._closeIn(port); },
_closeAll: _closeAll
};
impl.plugin = plugin;
plugin.input = impl;
_engine._inArr.push(impl);
_engine._inMap[name] = impl;
}
port._orig._impl = impl;
_push(impl.clients, port._orig);
port._info = impl.info;
port._close = function() { impl._close(this); };
if (!impl.open) {
port._pause();
if (impl.plugin.ready) impl._start();
}
};
_engine._closeOut = function(port) {
var impl = port._impl;
_pop(impl.clients, port._orig);
if (!impl.clients.length && impl.open) {
impl.open = false;
document.dispatchEvent(new CustomEvent('jazz-midi', { detail: ['closeout', impl.plugin.id] }));
}
};
_engine._closeIn = function(port) {
var impl = port._impl;
_pop(impl.clients, port._orig);
if (!impl.clients.length && impl.open) {
impl.open = false;
document.dispatchEvent(new CustomEvent('jazz-midi', { detail: ['closein', impl.plugin.id] }));
}
};
_engine._close = function() {
_engine._unwatch();
};
var watcher;
_engine._watch = function() {
_engine._insW = _engine._ins;
_engine._outsW = _engine._outs;
watcher = setInterval(function() {
document.dispatchEvent(new CustomEvent('jazz-midi', {detail:['refresh']}));
}, 250);
};
_engine._unwatch = function() {
clearInterval(watcher);
watcher = undefined;
};
document.addEventListener('jazz-midi-msg', function(evt) {
var i, j, impl;
var v = evt.detail ? [ evt.detail ] : undefined;
if (!v) {
v = _engine._msg.innerText.split('\n');
_engine._msg.innerText = '';
for (i = 0; i < v.length; i++) try { v[i] = JSON.parse(v[i]); } catch (err) { v[i] = []; }
}
for (i = 0; i < v.length; i++) {
var a = v[i];
if (!a.length) continue;
if (a[0] === 'refresh') {
if (a[1].ins) {
for (j = 0; j < a[1].ins.length; j++) a[1].ins[j].type = _engine._type;
_engine._ins = a[1].ins;
}
if (a[1].outs) {
for (j = 0; j < a[1].outs.length; j++) a[1].outs[j].type = _engine._type;
_engine._outs = a[1].outs;
}
_postRefresh();
for (j = 0; j < _engine.refreshClients.length; j++) _engine.refreshClients[j]._resume();
_engine.refreshClients = [];
}
else if (a[0] === 'version') {
var plugin = _engine._pool[a[1]];
if (plugin) {
plugin.ready = true;
if (plugin.input) plugin.input._start();
if (plugin.output) plugin.output._start();
}
}
else if (a[0] === 'openout') {
impl = _engine._pool[a[1]].output;
if (impl) {
if (a[2] == impl.name) {
impl.open = true;
if (impl.clients) for (j = 0; j < impl.clients.length; j++) impl.clients[j]._resume();
}
else if (impl.clients) for (j = 0; j < impl.clients.length; j++) impl.clients[j]._crash();
}
}
else if (a[0] === 'openin') {
impl = _engine._pool[a[1]].input;
if (impl) {
if (a[2] == impl.name) {
impl.open = true;
if (impl.clients) for (j = 0; j < impl.clients.length; j++) impl.clients[j]._resume();
}
else if (impl.clients) for (j = 0; j < impl.clients.length; j++) impl.clients[j]._crash();
}
}
else if (a[0] === 'midi') {
impl = _engine._pool[a[1]].input;
if (impl && impl.clients) {
for (j = 0; j < impl.clients.length; j++) {
var msg = MIDI(a.slice(3));
impl.clients[j]._emit(msg);
}
}
}
}
});
}
var JZZ = function(opt) {
if (!_jzz) _initJZZ(opt);
return _jzz._thenable();
};
JZZ.JZZ = JZZ;
JZZ.version = _version;
JZZ.info = function() { return _J.prototype.info(); };
function Widget(arg) {
var self = new _M();
if (arg instanceof Object) _for(arg, function(k) { self[k] = arg[k]; });
self._resume();
return self;
}
JZZ.Widget = Widget;
_J.prototype.Widget = JZZ.Widget;
JZZ.addMidiIn = function(name, widget) {
var info = _clone(widget._info || {});
info.name = name;
info.type = info.type || 'javascript';
info.manufacturer = info.manufacturer || 'virtual';
info.version = info.version || '0.0';
var engine = {
_info: function() { return info; },
_openIn: function(port) {
port._pause();
port._info = _clone(info);
port._close = function() { widget.disconnect(port); };
widget.connect(port);
port._resume();
}
};
return JZZ.lib.registerMidiIn(name, engine);
};
_J.prototype.addMidiIn = JZZ.addMidiIn;
JZZ.addMidiOut = function(name, widget) {
var info = _clone(widget._info || {});
info.name = name;
info.type = info.type || 'javascript';
info.manufacturer = info.manufacturer || 'virtual';
info.version = info.version || '0.0';
var engine = {
_info: function() { return info; },
_openOut: function(port) {
port._pause();
port._info = _clone(info);
port._close = function() { port.disconnect(); };
_connect.apply(port, [widget]);
port._resume();
}
};
return JZZ.lib.registerMidiOut(name, engine);
};
_J.prototype.addMidiOut = JZZ.addMidiOut;
JZZ.removeMidiOut = function(name) { return JZZ.lib.unregisterMidiOut(name); };
_J.prototype.removeMidiOut = JZZ.removeMidiOut;
JZZ.removeMidiIn = function(name) { return JZZ.lib.unregisterMidiIn(name); };
_J.prototype.removeMidiIn = JZZ.removeMidiIn;
JZZ.maskMidiIn = function(name) { _insM[name] = true; };
_J.prototype.maskMidiIn = JZZ.maskMidiIn;
JZZ.unmaskMidiIn = function(name) { delete _insM[name]; };
_J.prototype.unmaskMidiIn = JZZ.unmaskMidiIn;
JZZ.maskMidiOut = function(name) { _outsM[name] = true; };
_J.prototype.maskMidiOut = JZZ.maskMidiOut;
JZZ.unmaskMidiOut = function(name) { delete _outsM[name]; };
_J.prototype.unmaskMidiOut = JZZ.unmaskMidiOut;
// JZZ.SMPTE
function SMPTE() {
var self = this instanceof SMPTE ? this : self = new SMPTE();
SMPTE.prototype.reset.apply(self, arguments);
return self;
}
SMPTE.prototype.reset = function(arg) {
if (arg instanceof SMPTE) {
this.setType(arg.getType());
this.setHour(arg.getHour());
this.setMinute(arg.getMinute());
this.setSecond(arg.getSecond());
this.setFrame(arg.getFrame());
this.setQuarter(arg.getQuarter());
return this;
}
var arr = arg instanceof Array ? arg : arguments;
this.setType(arr[0]);
this.setHour(arr[1]);
this.setMinute(arr[2]);
this.setSecond(arr[3]);
this.setFrame(arr[4]);
this.setQuarter(arr[5]);
return this;
};
function _fixDropFrame() { if (this.type == 29.97 && !this.second && this.frame < 2 && this.minute % 10) this.frame = 2; }
SMPTE.prototype.isFullFrame = function() { return this.quarter == 0 || this.quarter == 4; };
SMPTE.prototype.getType = function() { return this.type; };
SMPTE.prototype.getHour = function() { return this.hour; };
SMPTE.prototype.getMinute = function() { return this.minute; };
SMPTE.prototype.getSecond = function() { return this.second; };
SMPTE.prototype.getFrame = function() { return this.frame; };
SMPTE.prototype.getQuarter = function() { return this.quarter; };
SMPTE.prototype.setType = function(x) {
if (typeof x == 'undefined' || x == 24) this.type = 24;
else if (x == 25) this.type = 25;
else if (x == 29.97) { this.type = 29.97; _fixDropFrame.apply(this); }
else if (x == 30) this.type = 30;
else throw RangeError('Bad SMPTE frame rate: ' + x);
if (this.frame >= this.type) this.frame = this.type - 1; // could not be more than 29
return this;
};
SMPTE.prototype.setHour = function(x) {
if (typeof x == 'undefined') x = 0;
if (x != parseInt(x) || x < 0 || x >= 24) throw RangeError('Bad SMPTE hours value: ' + x);
this.hour = x;
return this;
};
SMPTE.prototype.setMinute = function(x) {
if (typeof x == 'undefined') x = 0;
if (x != parseInt(x) || x < 0 || x >= 60) throw RangeError('Bad SMPTE minutes value: ' + x);
this.minute = x;
_fixDropFrame.apply(this);
return this;
};
SMPTE.prototype.setSecond = function(x) {
if (typeof x == 'undefined') x = 0;
if (x != parseInt(x) || x < 0 || x >= 60) throw RangeError('Bad SMPTE seconds value: ' + x);
this.second = x;
_fixDropFrame.apply(this);
return this;
};
SMPTE.prototype.setFrame = function(x) {
if (typeof x == 'undefined') x = 0;
if (x != parseInt(x) || x < 0 || x >= this.type) throw RangeError('Bad SMPTE frame number: ' + x);
this.frame = x;
_fixDropFrame.apply(this);
return this;
};
SMPTE.prototype.setQuarter = function(x) {
if (typeof x == 'undefined') x = 0;
if (x != parseInt(x) || x < 0 || x >= 8) throw RangeError('Bad SMPTE quarter frame: ' + x);
this.quarter = x;
return this;
};
SMPTE.prototype.incrFrame = function() {
this.frame++;
if (this.frame >= this.type) {
this.frame = 0;
this.second++;
if (this.second >= 60) {
this.second = 0;
this.minute++;
if (this.minute >= 60) {
this.minute = 0;
this.hour = this.hour >= 23 ? 0 : this.hour + 1;
}
}
}
_fixDropFrame.apply(this);
return this;
};
SMPTE.prototype.decrFrame = function() {
if (!this.second && this.frame == 2 && this.type == 29.97 && this.minute % 10) this.frame = 0; // drop-frame
this.frame--;
if (this.frame < 0) {
this.frame = this.type == 29.97 ? 29 : this.type - 1;
this.second--;
if (this.second < 0) {
this.second = 59;
this.minute--;
if (this.minute < 0) {
this.minute = 59;
this.hour = this.hour ? this.hour - 1 : 23;
}
}
}
return this;
};
SMPTE.prototype.incrQF = function() {
this.backwards = false;
this.quarter = (this.quarter + 1)