422 lines
14 KiB
JavaScript
422 lines
14 KiB
JavaScript
/* Copyright 2013 Chris Wilson
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
// Initialize the MIDI library.
|
|
(function (global) {
|
|
'use strict';
|
|
var midiIO, _requestMIDIAccess, MIDIAccess, _onReady, MIDIPort, MIDIInput, MIDIOutput, _midiProc;
|
|
|
|
function Promise() {
|
|
|
|
}
|
|
|
|
Promise.prototype.then = function(accept, reject) {
|
|
this.accept = accept;
|
|
this.reject = reject;
|
|
}
|
|
|
|
Promise.prototype.succeed = function(access) {
|
|
if (this.accept)
|
|
this.accept(access);
|
|
}
|
|
|
|
Promise.prototype.fail = function(error) {
|
|
if (this.reject)
|
|
this.reject(error);
|
|
}
|
|
|
|
function _JazzInstance() {
|
|
this.inputInUse = false;
|
|
this.outputInUse = false;
|
|
|
|
// load the Jazz plugin
|
|
var o1 = document.createElement("object");
|
|
o1.id = "_Jazz" + Math.random() + "ie";
|
|
o1.classid = "CLSID:1ACE1618-1C7D-4561-AEE1-34842AA85E90";
|
|
|
|
this.activeX = o1;
|
|
|
|
var o2 = document.createElement("object");
|
|
o2.id = "_Jazz" + Math.random();
|
|
o2.type="audio/x-jazz";
|
|
o1.appendChild(o2);
|
|
|
|
this.objRef = o2;
|
|
|
|
var e = document.createElement("p");
|
|
e.appendChild(document.createTextNode("This page requires the "));
|
|
|
|
var a = document.createElement("a");
|
|
a.appendChild(document.createTextNode("Jazz plugin"));
|
|
a.href = "http://jazz-soft.net/";
|
|
|
|
e.appendChild(a);
|
|
e.appendChild(document.createTextNode("."));
|
|
o2.appendChild(e);
|
|
|
|
var insertionPoint = document.getElementById("MIDIPlugin");
|
|
if (!insertionPoint) {
|
|
// Create hidden element
|
|
var insertionPoint = document.createElement("div");
|
|
insertionPoint.id = "MIDIPlugin";
|
|
insertionPoint.style.position = "absolute";
|
|
insertionPoint.style.visibility = "hidden";
|
|
insertionPoint.style.left = "-9999px";
|
|
insertionPoint.style.top = "-9999px";
|
|
document.body.appendChild(insertionPoint);
|
|
}
|
|
insertionPoint.appendChild(o1);
|
|
|
|
if (this.objRef.isJazz)
|
|
this._Jazz = this.objRef;
|
|
else if (this.activeX.isJazz)
|
|
this._Jazz = this.activeX;
|
|
else
|
|
this._Jazz = null;
|
|
if (this._Jazz) {
|
|
this._Jazz._jazzTimeZero = this._Jazz.Time();
|
|
this._Jazz._perfTimeZero = window.performance.now();
|
|
}
|
|
}
|
|
|
|
_requestMIDIAccess = function _requestMIDIAccess() {
|
|
var access = new MIDIAccess();
|
|
return access._promise;
|
|
};
|
|
|
|
// API Methods
|
|
|
|
MIDIAccess = function() {
|
|
this._jazzInstances = new Array();
|
|
this._jazzInstances.push( new _JazzInstance() );
|
|
this._promise = new Promise;
|
|
|
|
if (this._jazzInstances[0]._Jazz) {
|
|
this._Jazz = this._jazzInstances[0]._Jazz;
|
|
window.setTimeout( _onReady.bind(this), 3 );
|
|
} else {
|
|
window.setTimeout( _onNotReady.bind(this), 3 );
|
|
}
|
|
};
|
|
|
|
_onReady = function _onReady() {
|
|
if (this._promise)
|
|
this._promise.succeed(this);
|
|
};
|
|
|
|
function _onNotReady() {
|
|
if (this._promise)
|
|
this._promise.fail( { code: 1 } );
|
|
};
|
|
|
|
MIDIAccess.prototype.inputs = function( ) {
|
|
if (!this._Jazz)
|
|
return null;
|
|
var list=this._Jazz.MidiInList();
|
|
var inputs = new Array( list.length );
|
|
|
|
for ( var i=0; i<list.length; i++ ) {
|
|
inputs[i] = new MIDIInput( this, list[i], i );
|
|
}
|
|
return inputs;
|
|
}
|
|
|
|
MIDIAccess.prototype.outputs = function( ) {
|
|
if (!this._Jazz)
|
|
return null;
|
|
var list=this._Jazz.MidiOutList();
|
|
var outputs = new Array( list.length );
|
|
|
|
for ( var i=0; i<list.length; i++ ) {
|
|
outputs[i] = new MIDIOutput( this, list[i], i );
|
|
}
|
|
return outputs;
|
|
};
|
|
|
|
MIDIInput = function MIDIInput( midiAccess, name, index ) {
|
|
this._listeners = [];
|
|
this._midiAccess = midiAccess;
|
|
this._index = index;
|
|
this._inLongSysexMessage = false;
|
|
this._sysexBuffer = new Uint8Array();
|
|
this.id = "" + index + "." + name;
|
|
this.manufacturer = "";
|
|
this.name = name;
|
|
this.type = "input";
|
|
this.version = "";
|
|
this.onmidimessage = null;
|
|
|
|
var inputInstance = null;
|
|
for (var i=0; (i<midiAccess._jazzInstances.length)&&(!inputInstance); i++) {
|
|
if (!midiAccess._jazzInstances[i].inputInUse)
|
|
inputInstance=midiAccess._jazzInstances[i];
|
|
}
|
|
if (!inputInstance) {
|
|
inputInstance = new _JazzInstance();
|
|
midiAccess._jazzInstances.push( inputInstance );
|
|
}
|
|
inputInstance.inputInUse = true;
|
|
|
|
this._jazzInstance = inputInstance._Jazz;
|
|
this._input = this._jazzInstance.MidiInOpen( this._index, _midiProc.bind(this) );
|
|
};
|
|
|
|
// Introduced in DOM Level 2:
|
|
MIDIInput.prototype.addEventListener = function (type, listener, useCapture ) {
|
|
if (type !== "midimessage")
|
|
return;
|
|
for (var i=0; i<this._listeners.length; i++)
|
|
if (this._listeners[i] == listener)
|
|
return;
|
|
this._listeners.push( listener );
|
|
};
|
|
|
|
MIDIInput.prototype.removeEventListener = function (type, listener, useCapture ) {
|
|
if (type !== "midimessage")
|
|
return;
|
|
for (var i=0; i<this._listeners.length; i++)
|
|
if (this._listeners[i] == listener) {
|
|
this._listeners.splice( i, 1 ); //remove it
|
|
return;
|
|
}
|
|
};
|
|
|
|
MIDIInput.prototype.preventDefault = function() {
|
|
this._pvtDef = true;
|
|
};
|
|
|
|
MIDIInput.prototype.dispatchEvent = function (evt) {
|
|
this._pvtDef = false;
|
|
|
|
// dispatch to listeners
|
|
for (var i=0; i<this._listeners.length; i++)
|
|
if (this._listeners[i].handleEvent)
|
|
this._listeners[i].handleEvent.bind(this)( evt );
|
|
else
|
|
this._listeners[i].bind(this)( evt );
|
|
|
|
if (this.onmidimessage)
|
|
this.onmidimessage( evt );
|
|
|
|
return this._pvtDef;
|
|
};
|
|
|
|
MIDIInput.prototype.appendToSysexBuffer = function ( data ) {
|
|
var oldLength = this._sysexBuffer.length;
|
|
var tmpBuffer = new Uint8Array( oldLength + data.length );
|
|
tmpBuffer.set( this._sysexBuffer );
|
|
tmpBuffer.set( data, oldLength );
|
|
this._sysexBuffer = tmpBuffer;
|
|
};
|
|
|
|
MIDIInput.prototype.bufferLongSysex = function ( data, initialOffset ) {
|
|
var j = initialOffset;
|
|
while (j<data.length) {
|
|
if (data[j] == 0xF7) {
|
|
// end of sysex!
|
|
j++;
|
|
this.appendToSysexBuffer( data.slice(initialOffset, j) );
|
|
return j;
|
|
}
|
|
j++;
|
|
}
|
|
// didn't reach the end; just tack it on.
|
|
this.appendToSysexBuffer( data.slice(initialOffset, j) );
|
|
this._inLongSysexMessage = true;
|
|
return j;
|
|
};
|
|
|
|
_midiProc = function _midiProc( timestamp, data ) {
|
|
// Have to use createEvent/initEvent because IE10 fails on new CustomEvent. Thanks, IE!
|
|
var length = 0;
|
|
var i,j;
|
|
var isSysexMessage = false;
|
|
|
|
// Jazz sometimes passes us multiple messages at once, so we need to parse them out
|
|
// and pass them one at a time.
|
|
|
|
for (i=0; i<data.length; i+=length) {
|
|
if (this._inLongSysexMessage) {
|
|
i = this.bufferLongSysex(data,i);
|
|
if ( data[i-1] != 0xf7 ) {
|
|
// ran off the end without hitting the end of the sysex message
|
|
return;
|
|
}
|
|
isSysexMessage = true;
|
|
} else {
|
|
isSysexMessage = false;
|
|
switch (data[i] & 0xF0) {
|
|
case 0x80: // note off
|
|
case 0x90: // note on
|
|
case 0xA0: // polyphonic aftertouch
|
|
case 0xB0: // control change
|
|
case 0xE0: // channel mode
|
|
length = 3;
|
|
break;
|
|
|
|
case 0xC0: // program change
|
|
case 0xD0: // channel aftertouch
|
|
length = 2;
|
|
break;
|
|
|
|
case 0xF0:
|
|
switch (data[i]) {
|
|
case 0xf0: // variable-length sysex.
|
|
i = this.bufferLongSysex(data,i);
|
|
if ( data[i-1] != 0xf7 ) {
|
|
// ran off the end without hitting the end of the sysex message
|
|
return;
|
|
}
|
|
isSysexMessage = true;
|
|
break;
|
|
|
|
case 0xF1: // MTC quarter frame
|
|
case 0xF3: // song select
|
|
length = 2;
|
|
break;
|
|
|
|
case 0xF2: // song position pointer
|
|
length = 3;
|
|
break;
|
|
|
|
default:
|
|
length = 1;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
var evt = document.createEvent( "Event" );
|
|
evt.initEvent( "midimessage", false, false );
|
|
evt.receivedTime = parseFloat( timestamp.toString()) + this._jazzInstance._perfTimeZero;
|
|
if (isSysexMessage || this._inLongSysexMessage) {
|
|
evt.data = new Uint8Array( this._sysexBuffer );
|
|
this._sysexBuffer = new Uint8Array(0);
|
|
this._inLongSysexMessage = false;
|
|
} else
|
|
evt.data = new Uint8Array(data.slice(i, length+i));
|
|
this.dispatchEvent( evt );
|
|
}
|
|
};
|
|
|
|
MIDIOutput = function MIDIOutput( midiAccess, name, index ) {
|
|
this._listeners = [];
|
|
this._midiAccess = midiAccess;
|
|
this._index = index;
|
|
this.id = "" + index + "." + name;
|
|
this.manufacturer = "";
|
|
this.name = name;
|
|
this.type = "output";
|
|
this.version = "";
|
|
|
|
var outputInstance = null;
|
|
for (var i=0; (i<midiAccess._jazzInstances.length)&&(!outputInstance); i++) {
|
|
if (!midiAccess._jazzInstances[i].outputInUse)
|
|
outputInstance=midiAccess._jazzInstances[i];
|
|
}
|
|
if (!outputInstance) {
|
|
outputInstance = new _JazzInstance();
|
|
midiAccess._jazzInstances.push( outputInstance );
|
|
}
|
|
outputInstance.outputInUse = true;
|
|
|
|
this._jazzInstance = outputInstance._Jazz;
|
|
this._jazzInstance.MidiOutOpen(this.name);
|
|
};
|
|
|
|
function _sendLater() {
|
|
this.jazz.MidiOutLong( this.data ); // handle send as sysex
|
|
}
|
|
|
|
MIDIOutput.prototype.send = function( data, timestamp ) {
|
|
var delayBeforeSend = 0;
|
|
if (data.length === 0)
|
|
return false;
|
|
|
|
if (timestamp)
|
|
delayBeforeSend = Math.floor( timestamp - window.performance.now() );
|
|
|
|
if (timestamp && (delayBeforeSend>1)) {
|
|
var sendObj = new Object();
|
|
sendObj.jazz = this._jazzInstance;
|
|
sendObj.data = data;
|
|
|
|
window.setTimeout( _sendLater.bind(sendObj), delayBeforeSend );
|
|
} else {
|
|
this._jazzInstance.MidiOutLong( data );
|
|
}
|
|
return true;
|
|
};
|
|
|
|
//init: create plugin
|
|
if (!window.navigator.requestMIDIAccess)
|
|
window.navigator.requestMIDIAccess = _requestMIDIAccess;
|
|
|
|
}(window));
|
|
|
|
// Polyfill window.performance.now() if necessary.
|
|
(function (exports) {
|
|
var perf = {}, props;
|
|
|
|
function findAlt() {
|
|
var prefix = ['moz', 'webkit', 'o', 'ms'],
|
|
i = prefix.length,
|
|
//worst case, we use Date.now()
|
|
props = {
|
|
value: (function (start) {
|
|
return function () {
|
|
return Date.now() - start;
|
|
};
|
|
}(Date.now()))
|
|
};
|
|
|
|
//seach for vendor prefixed version
|
|
for (; i >= 0; i--) {
|
|
if ((prefix[i] + "Now") in exports.performance) {
|
|
props.value = function (method) {
|
|
return function () {
|
|
exports.performance[method]();
|
|
}
|
|
}(prefix[i] + "Now");
|
|
return props;
|
|
}
|
|
}
|
|
|
|
//otherwise, try to use connectionStart
|
|
if ("timing" in exports.performance && "connectStart" in exports.performance.timing) {
|
|
//this pretty much approximates performance.now() to the millisecond
|
|
props.value = (function (start) {
|
|
return function() {
|
|
Date.now() - start;
|
|
};
|
|
}(exports.performance.timing.connectStart));
|
|
}
|
|
return props;
|
|
}
|
|
|
|
//if already defined, bail
|
|
if (("performance" in exports) && ("now" in exports.performance))
|
|
return;
|
|
if (!("performance" in exports))
|
|
Object.defineProperty(exports, "performance", {
|
|
get: function () {
|
|
return perf;
|
|
}});
|
|
//otherwise, performance is there, but not "now()"
|
|
|
|
props = findAlt();
|
|
Object.defineProperty(exports.performance, "now", props);
|
|
}(window));
|