text: //https://github.com/davidchambers/Base64.js
  
  ;(function () {
    var object = typeof exports != 'undefined' ? exports : this; // #8: web workers
    var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  
    function InvalidCharacterError(message) {
      this.message = message;
    }
    InvalidCharacterError.prototype = new Error;
    InvalidCharacterError.prototype.name = 'InvalidCharacterError';
  
    // encoder
    // [https://gist.github.com/999166] by [https://github.com/nignag]
    object.btoa || (
    object.btoa = function (input) {
      for (
        // initialize result and counter
        var block, charCode, idx = 0, map = chars, output = '';
        // if the next input index does not exist:
        //   change the mapping table to "="
        //   check if d has no fractional digits
        input.charAt(idx | 0) || (map = '=', idx % 1);
        // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8
        output += map.charAt(63 & block >> 8 - idx % 1 * 8)
      ) {
        charCode = input.charCodeAt(idx += 3/4);
        if (charCode > 0xFF) {
          throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range.");
        }
        block = block << 8 | charCode;
      }
      return output;
    });
  
    // decoder
    // [https://gist.github.com/1020396] by [https://github.com/atk]
    object.atob || (
    object.atob = function (input) {
      input = input.replace(/=+$/, '')
      if (input.length % 4 == 1) {
        throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.");
      }
      for (
        // initialize result and counters
        var bc = 0, bs, buffer, idx = 0, output = '';
        // get next character
        buffer = input.charAt(idx++);
        // character found in table? initialize bit storage and add its ascii value;
        ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
          // and if not first of each 4 characters,
          // convert the first 8 bits to one ascii character
          bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
      ) {
        // try to find character in table (0-63, not found => -1)
        buffer = chars.indexOf(buffer);
      }
      return output;
    });
  
  }());
  
  /**
   * @license -------------------------------------------------------------------
   *   module: Base64Binary
   *      src: http://blog.danguer.com/2011/10/24/base64-binary-decoding-in-javascript/
   *  license: Simplified BSD License
   * -------------------------------------------------------------------
   * Copyright 2011, Daniel Guerrero. All rights reserved.
   * 
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *     - Redistributions of source code must retain the above copyright
   *       notice, this list of conditions and the following disclaimer.
   *     - Redistributions in binary form must reproduce the above copyright
   *       notice, this list of conditions and the following disclaimer in the
   *       documentation and/or other materials provided with the distribution.
   * 
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL DANIEL GUERRERO BE LIABLE FOR ANY
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  
  var Base64Binary = {
  	_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
  
  	/* will return a  Uint8Array type */
  	decodeArrayBuffer: function(input) {
  		var bytes = Math.ceil( (3*input.length) / 4.0);
  		var ab = new ArrayBuffer(bytes);
  		this.decode(input, ab);
  
  		return ab;
  	},
  
  	decode: function(input, arrayBuffer) {
  		//get last chars to see if are valid
  		var lkey1 = this._keyStr.indexOf(input.charAt(input.length-1));		 
  		var lkey2 = this._keyStr.indexOf(input.charAt(input.length-1));		 
  
  		var bytes = Math.ceil( (3*input.length) / 4.0);
  		if (lkey1 == 64) bytes--; //padding chars, so skip
  		if (lkey2 == 64) bytes--; //padding chars, so skip
  
  		var uarray;
  		var chr1, chr2, chr3;
  		var enc1, enc2, enc3, enc4;
  		var i = 0;
  		var j = 0;
  
  		if (arrayBuffer)
  			uarray = new Uint8Array(arrayBuffer);
  		else
  			uarray = new Uint8Array(bytes);
  
  		input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
  
  		for (i=0; i<bytes; i+=3) {	
  			//get the 3 octects in 4 ascii chars
  			enc1 = this._keyStr.indexOf(input.charAt(j++));
  			enc2 = this._keyStr.indexOf(input.charAt(j++));
  			enc3 = this._keyStr.indexOf(input.charAt(j++));
  			enc4 = this._keyStr.indexOf(input.charAt(j++));
  
  			chr1 = (enc1 << 2) | (enc2 >> 4);
  			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  			chr3 = ((enc3 & 3) << 6) | enc4;
  
  			uarray[i] = chr1;			
  			if (enc3 != 64) uarray[i+1] = chr2;
  			if (enc4 != 64) uarray[i+2] = chr3;
  		}
  
  		return uarray;	
  	}
  };
  
  /**
   * @license -------------------------------------------------------------------
   *   module: WebAudioShim - Fix naming issues for WebAudioAPI supports
   *      src: https://github.com/Dinahmoe/webaudioshim
   *   author: Dinahmoe AB
   * -------------------------------------------------------------------
   * Copyright (c) 2012 DinahMoe AB
   * 
   * Permission is hereby granted, free of charge, to any person
   * obtaining a copy of this software and associated documentation
   * files (the "Software"), to deal in the Software without
   * restriction, including without limitation the rights to use,
   * copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the
   * Software is furnished to do so, subject to the following
   * conditions:
   * 
   * The above copyright notice and this permission notice shall be
   * included in all copies or substantial portions of the Software.
   * 
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
   * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
   * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
   * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
   * OTHER DEALINGS IN THE SOFTWARE.
   */
  
  window.AudioContext = window.AudioContext || window.webkitAudioContext || null;
  window.OfflineAudioContext = window.OfflineAudioContext || window.webkitOfflineAudioContext || null;
  
  (function (Context) {
  	var isFunction = function (f) {
  		return Object.prototype.toString.call(f) === "[object Function]" ||
  			Object.prototype.toString.call(f) === "[object AudioContextConstructor]";
  	};
  	var contextMethods = [
  		["createGainNode", "createGain"],
  		["createDelayNode", "createDelay"],
  		["createJavaScriptNode", "createScriptProcessor"]
  	];
  	///
  	var proto;
  	var instance;
  	var sourceProto;
  	///
  	if (!isFunction(Context)) {
  		return;
  	}
  	instance = new Context();
  	if (!instance.destination || !instance.sampleRate) {
  		return;
  	}
  	proto = Context.prototype;
  	sourceProto = Object.getPrototypeOf(instance.createBufferSource());
  
  	if (!isFunction(sourceProto.start)) {
  		if (isFunction(sourceProto.noteOn)) {
  			sourceProto.start = function (when, offset, duration) {
  				switch (arguments.length) {
  					case 0:
  						throw new Error("Not enough arguments.");
  					case 1:
  						this.noteOn(when);
  						break;
  					case 2:
  						if (this.buffer) {
  							this.noteGrainOn(when, offset, this.buffer.duration - offset);
  						} else {
  							throw new Error("Missing AudioBuffer");
  						}
  						break;
  					case 3:
  						this.noteGrainOn(when, offset, duration);
  				}
  			};
  		}
  	}
  
  	if (!isFunction(sourceProto.noteOn)) {
  		sourceProto.noteOn = sourceProto.start;
  	}
  
  	if (!isFunction(sourceProto.noteGrainOn)) {
  		sourceProto.noteGrainOn = sourceProto.start;
  	}
  
  	if (!isFunction(sourceProto.stop)) {
  		sourceProto.stop = sourceProto.noteOff;
  	}
  
  	if (!isFunction(sourceProto.noteOff)) {
  		sourceProto.noteOff = sourceProto.stop;
  	}
  
  	contextMethods.forEach(function (names) {
  		var name1;
  		var name2;
  		while (names.length) {
  			name1 = names.pop();
  			if (isFunction(this[name1])) {
  				this[names.pop()] = this[name1];
  			} else {
  				name2 = names.pop();
  				this[name1] = this[name2];
  			}
  		}
  	}, proto);
  })(window.AudioContext);
  
  /* 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));
  
  