UNPKG

jzz

Version:

MIDI library for Node.js and web-browsers

1,522 lines (1,486 loc) 174 kB
(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)