You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
421 lines
14 KiB
421 lines
14 KiB
/* 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 |
|
|
|
function MIDIAccess() { |
|
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)); |