This repository has been archived on 2026-04-30. You can view files and clone it, but cannot push or open issues or pull requests.
huegasm/web/vendor/dancer.js
2017-02-06 23:52:04 -08:00

709 lines
18 KiB
JavaScript

/*
* dancer - v0.4.0 - 2014-02-01
* https://github.com/jsantell/dancer.js
* Copyright (c) 2014 Jordan Santell
* Licensed MIT
*/
(function() {
var Dancer = function () {
this.audioAdapter = Dancer._getAdapter( this );
this.events = {};
this.sections = [];
this.bind( 'update', update );
};
Dancer.version = 'X.X.X';
Dancer.adapters = {};
Dancer.prototype = {
load : function ( source, micBoost, useMic ) {
// Loading an Audio element
if ( source instanceof HTMLElement ) {
this.source = source;
// Loading an object with src, [codecs]
} else if(source instanceof EventTarget){
this.source = source;
} else {
this.source = window.Audio ? new Audio() : {};
this.source.src = Dancer._makeSupportedPath( source.src, source.codecs );
}
this.useMic = useMic === true;
this.boost = micBoost ? micBoost : 1;
this.audio = this.audioAdapter.load(this.source, this.useMic, this.boost);
return this;
},
/* Controls */
play : function () {
this.audioAdapter.play();
return this;
},
pause : function () {
this.audioAdapter.pause();
return this;
},
setVolume : function ( volume ) {
this.audioAdapter.setVolume( volume );
return this;
},
setBoost : function ( boost ) {
this.audioAdapter.setBoost( boost );
return this;
},
/* Actions */
createKick : function ( options ) {
return new Dancer.Kick( this, options );
},
bind : function ( name, callback ) {
if ( !this.events[ name ] ) {
this.events[ name ] = [];
}
this.events[ name ].push( callback );
return this;
},
unbind : function ( name ) {
if ( this.events[ name ] ) {
delete this.events[ name ];
}
return this;
},
trigger : function ( name ) {
var _this = this;
if ( this.events[ name ] ) {
this.events[ name ].forEach(function( callback ) {
callback.call( _this );
});
}
return this;
},
/* Getters */
getVolume : function () {
return this.audioAdapter.getVolume();
},
getProgress : function () {
return this.audioAdapter.getProgress();
},
getTime : function () {
return this.audioAdapter.getTime();
},
// Returns the magnitude of a frequency or average over a range of frequencies
getFrequency : function ( freq, endFreq ) {
var sum = 0;
if ( endFreq !== undefined ) {
for ( var i = freq; i <= endFreq; i++ ) {
sum += this.getSpectrum()[ i ];
}
return sum / ( endFreq - freq + 1 );
} else {
return this.getSpectrum()[ freq ];
}
},
getWaveform : function () {
return this.audioAdapter.getWaveform();
},
getSpectrum : function () {
return this.audioAdapter.getSpectrum();
},
isLoaded : function () {
return this.audioAdapter.isLoaded;
},
isPlaying : function () {
return this.audioAdapter.isPlaying;
},
/* Sections */
after : function ( time, callback ) {
var _this = this;
this.sections.push({
condition : function () {
return _this.getTime() > time;
},
callback : callback
});
return this;
},
before : function ( time, callback ) {
var _this = this;
this.sections.push({
condition : function () {
return _this.getTime() < time;
},
callback : callback
});
return this;
},
between : function ( startTime, endTime, callback ) {
var _this = this;
this.sections.push({
condition : function () {
return _this.getTime() > startTime && _this.getTime() < endTime;
},
callback : callback
});
return this;
},
onceAt : function ( time, callback ) {
var
_this = this,
thisSection = null;
this.sections.push({
condition : function () {
return _this.getTime() > time && !this.called;
},
callback : function () {
callback.call( this );
thisSection.called = true;
},
called : false
});
// Baking the section in the closure due to callback's this being the dancer instance
thisSection = this.sections[ this.sections.length - 1 ];
return this;
}
};
function update () {
for (var i in this.sections) {
if (this.sections[i].condition && this.sections[i].condition() )
this.sections[i].callback.call(this);
}
}
window.Dancer = Dancer;
})();
(function ( Dancer ) {
var CODECS = {
'mp3' : 'audio/mpeg;',
'ogg' : 'audio/ogg; codecs="vorbis"',
'wav' : 'audio/wav; codecs="1"',
'aac' : 'audio/mp4; codecs="mp4a.40.2"'
},
audioEl = document.createElement( 'audio' );
Dancer.options = {};
Dancer.setOptions = function ( o ) {
for ( var option in o ) {
if ( o.hasOwnProperty( option ) ) {
Dancer.options[ option ] = o[ option ];
}
}
};
Dancer.isSupported = function () {
if ( !window.Float32Array || !window.Uint32Array ) {
return null;
} else if ( !isUnsupportedSafari() && ( window.AudioContext || window.webkitAudioContext )) {
return 'webaudio';
} else {
return '';
}
};
Dancer.canPlay = function ( type ) {
var canPlay = audioEl.canPlayType;
return !!(
type.toLowerCase() === 'mp3' ||
audioEl.canPlayType &&
audioEl.canPlayType( CODECS[ type.toLowerCase() ] ).replace( /no/, ''));
};
Dancer.addPlugin = function ( name, fn ) {
if ( Dancer.prototype[ name ] === undefined ) {
Dancer.prototype[ name ] = fn;
}
};
Dancer._makeSupportedPath = function ( source, codecs ) {
if ( !codecs ) { return source; }
for ( var i = 0; i < codecs.length; i++ ) {
if ( Dancer.canPlay( codecs[ i ] ) ) {
return source + '.' + codecs[ i ];
}
}
return source;
};
Dancer._getAdapter = function ( instance ) {
switch ( Dancer.isSupported() ) {
case 'webaudio':
return new Dancer.adapters.webaudio( instance );
default:
return null;
}
};
Dancer._getMP3SrcFromAudio = function ( audioEl ) {
var sources = audioEl.children;
if ( audioEl.src ) { return audioEl.src; }
for ( var i = sources.length; i--; ) {
if (( sources[ i ].type || '' ).match( /audio\/mpeg/ )) return sources[ i ].src;
}
return null;
};
// Browser detection is lame, but Safari 6 has Web Audio API,
// but does not support processing audio from a Media Element Source
// https://gist.github.com/3265344
function isUnsupportedSafari () {
var
isApple = !!( navigator.vendor || '' ).match( /Apple/ ),
version = navigator.userAgent.match( /Version\/([^ ]*)/ );
version = version ? parseFloat( version[ 1 ] ) : 0;
return isApple && version <= 6;
}
})( window.Dancer );
(function ( undefined ) {
var Kick = function ( dancer, o ) {
o = o || {};
this.dancer = dancer;
this.frequency = o.frequency !== undefined ? o.frequency : [ 0, 5 ];
this.threshold = o.threshold !== undefined ? o.threshold : 0.3;
this.decay = o.decay !== undefined ? o.decay : 0.02;
this.onKick = o.onKick;
this.offKick = o.offKick;
this.isOn = false;
this.currentThreshold = this.threshold;
this.previousMag = 0;
this.canUseRatio = true;
this.canUseRatioHandle = null;
var _this = this;
this.dancer.bind( 'update', function () {
_this.onUpdate();
});
};
Kick.prototype = {
on : function () {
this.isOn = true;
return this;
},
off : function () {
this.isOn = false;
return this;
},
set : function ( o ) {
o = o || {};
this.frequency = o.frequency !== undefined ? o.frequency : this.frequency;
this.threshold = o.threshold !== undefined ? o.threshold : this.threshold;
this.decay = o.decay !== undefined ? o.decay : this.decay;
this.onKick = o.onKick || this.onKick;
this.offKick = o.offKick || this.offKick;
},
onUpdate : function () {
if ( !this.isOn ) { return; }
var magnitude = this.maxAmplitude(this.frequency);
if (magnitude >= this.currentThreshold && magnitude >= this.threshold) {
this.currentThreshold = magnitude;
this.onKick && this.onKick.call(this.dancer, magnitude);
this.canUseRatio = false;
if(this.canUseRatioHandle) {
clearTimeout(this.canUseRatioHandle);
this.canUseRatioHandle = null;
}
var self = this;
this.canUseRatioHandle = setTimeout(function(){
self.canUseRatio = true;
}, 5000);
} else {
if(magnitude/this.previousMag > this.threshold*5 && magnitude>0.1 && this.canUseRatio) {
this.onKick && this.onKick.call(this.dancer, magnitude, magnitude/this.previousMag);
} else {
this.offKick && this.offKick.call(this.dancer, magnitude);
}
this.currentThreshold -= this.decay;
this.previousMag = (magnitude > 0) ? magnitude : 0.0001;
}
},
maxAmplitude : function ( frequency ) {
var max = 0, fft = this.dancer.getSpectrum();
// Sloppy array check
if ( !frequency.length ) {
return frequency < fft.length ?
fft[ ~~frequency ] :
null;
}
for ( var i = frequency[ 0 ], l = frequency[ 1 ]; i <= l; i++ ) {
if ( fft[ i ] > max ) { max = fft[ i ]; }
}
return max;
}
};
window.Dancer.Kick = Kick;
})();
(function() {
var
SAMPLE_SIZE = 2048,
SAMPLE_RATE = 44100;
var adapter = function ( dancer ) {
var context;
if('AudioContext' in window) {
context = new AudioContext();
} else {
context = new webkitAudioContext();
}
this.dancer = dancer;
this.audio = new Audio();
this.context = context;
};
adapter.prototype = {
load : function (_source, useMic, boost) {
var _this = this;
this.audio = _source;
this.useMic = useMic;
this.boost = boost;
this.isLoaded = false;
this.progress = 0;
if(this.proc){
this.proc.onaudioprocess = null;
delete this.proc;
}
this.proc = this.context.createScriptProcessor( SAMPLE_SIZE / 2, 1, 1 );
this.proc.onaudioprocess = function ( e ) {
_this.update.call( _this, e );
};
this.gain = this.context.createGain();
this.fft = new FFT( SAMPLE_SIZE / 2, SAMPLE_RATE, this.boost );
this.signal = new Float32Array( SAMPLE_SIZE / 2 );
if ( this.audio.readyState < 3 ) {
this.audio.addEventListener( 'canplay', function () {
connectContext.call( _this );
});
} else {
connectContext.call( _this );
}
this.audio.addEventListener( 'progress', function ( e ) {
if ( e.currentTarget.duration && e.currentTarget.duration !== Infinity ) {
_this.progress = e.currentTarget.seekable.end( 0 ) / e.currentTarget.duration;
}
});
return this.audio;
},
play : function () {
this.audio.play();
this.isPlaying = true;
},
pause : function () {
this.audio.pause();
this.isPlaying = false;
},
setVolume : function ( volume ) {
this.gain.gain.value = volume;
},
setBoost : function( boost ){
if(this.fft){
this.fft.setBoost(boost);
}
this.boost = boost;
},
getVolume : function () {
return this.gain.gain.value;
},
getProgress : function() {
return this.progress;
},
getWaveform : function () {
return this.signal;
},
getSpectrum : function () {
return this.fft.spectrum;
},
getTime : function () {
return this.audio.currentTime;
},
update : function ( e ) {
if ((!this.isPlaying || !this.isLoaded) && this.useMic !== true ) return;
var
buffers = [],
channels = e.inputBuffer.numberOfChannels,
resolution = SAMPLE_SIZE / channels,
sum = function ( prev, curr ) {
return prev[ i ] + curr[ i ];
}, i;
for ( i = channels; i--; ) {
buffers.push( e.inputBuffer.getChannelData( i ) );
}
for ( i = 0; i < resolution; i++ ) {
this.signal[ i ] = channels > 1 ?
buffers.reduce( sum ) / channels :
buffers[ 0 ][ i ];
}
this.fft.forward( this.signal );
this.dancer.trigger( 'update' );
}
};
function connectContext () {
try {
if(this.useMic){
this.source = this.context.createMediaStreamSource(this.audio);
} else {
this.source = this.context.createMediaElementSource(this.audio);
}
} catch (err) {
console.info('Dancer: '+ err);
return;
}
this.source.connect(this.proc);
this.source.connect(this.gain);
this.gain.connect(this.context.destination);
this.proc.connect(this.context.destination);
this.isLoaded = true;
this.progress = 1;
this.dancer.trigger( 'loaded' );
}
Dancer.adapters.webaudio = adapter;
})();
/*
* DSP.js - a comprehensive digital signal processing library for javascript
*
* Created by Corban Brook <corbanbrook@gmail.com> on 2010-01-01.
* Copyright 2010 Corban Brook. All rights reserved.
*
*/
// Fourier Transform Module used by DFT, FFT, RFFT
function FourierTransform(bufferSize, sampleRate, boost) {
this.bufferSize = bufferSize;
this.sampleRate = sampleRate;
this.bandwidth = 2 / bufferSize * sampleRate / 2;
this.boost = boost ? boost : 1;
this.spectrum = new Float32Array(bufferSize/2);
this.real = new Float32Array(bufferSize);
this.imag = new Float32Array(bufferSize);
this.peakBand = 0;
this.peak = 0;
/**
* Calculates the *middle* frequency of an FFT band.
*
* @param {Number} index The index of the FFT band.
*
* @returns The middle frequency in Hz.
*/
this.getBandFrequency = function(index) {
return this.bandwidth * index + this.bandwidth / 2;
};
this.setBoost = function(boost){
this.boost = boost;
};
this.calculateSpectrum = function() {
var spectrum = this.spectrum,
real = this.real,
imag = this.imag,
boost = this.boost,
bSi = 2 / this.bufferSize,
sqrt = Math.sqrt,
rval,
ival,
mag;
for (var i = 0, N = bufferSize/2; i < N; i++) {
rval = real[i];
ival = imag[i];
mag = bSi * sqrt(rval * rval + ival * ival);
if (mag > this.peak) {
this.peakBand = i;
this.peak = mag;
}
spectrum[i] = mag * boost;
}
};
}
/**
* FFT is a class for calculating the Discrete Fourier Transform of a signal
* with the Fast Fourier Transform algorithm.
*
* @param {Number} bufferSize The size of the sample buffer to be computed. Must be power of 2
* @param {Number} sampleRate The sampleRate of the buffer (eg. 44100)
* @param {Number} boost The coefficient
*
* @constructor
*/
function FFT(bufferSize, sampleRate, boost) {
FourierTransform.call(this, bufferSize, sampleRate, boost);
this.reverseTable = new Uint32Array(bufferSize);
var limit = 1;
var bit = bufferSize >> 1;
var i;
while (limit < bufferSize) {
for (i = 0; i < limit; i++) {
this.reverseTable[i + limit] = this.reverseTable[i] + bit;
}
limit = limit << 1;
bit = bit >> 1;
}
this.sinTable = new Float32Array(bufferSize);
this.cosTable = new Float32Array(bufferSize);
for (i = 0; i < bufferSize; i++) {
this.sinTable[i] = Math.sin(-Math.PI/i);
this.cosTable[i] = Math.cos(-Math.PI/i);
}
}
/**
* Performs a forward transform on the sample buffer.
* Converts a time domain signal to frequency domain spectra.
*
* @param {Array} buffer The sample buffer. Buffer Length must be power of 2
*
* @returns The frequency spectrum array
*/
FFT.prototype.forward = function(buffer) {
// Locally scope variables for speed up
var bufferSize = this.bufferSize,
cosTable = this.cosTable,
sinTable = this.sinTable,
reverseTable = this.reverseTable,
real = this.real,
imag = this.imag,
spectrum = this.spectrum;
var k = Math.floor(Math.log(bufferSize) / Math.LN2);
if (Math.pow(2, k) !== bufferSize) { throw "Invalid buffer size, must be a power of 2."; }
if (bufferSize !== buffer.length) { throw "Supplied buffer is not the same size as defined FFT. FFT Size: " + bufferSize + " Buffer Size: " + buffer.length; }
var halfSize = 1,
phaseShiftStepReal,
phaseShiftStepImag,
currentPhaseShiftReal,
currentPhaseShiftImag,
off,
tr,
ti,
tmpReal,
i;
for (i = 0; i < bufferSize; i++) {
real[i] = buffer[reverseTable[i]];
imag[i] = 0;
}
while (halfSize < bufferSize) {
//phaseShiftStepReal = Math.cos(-Math.PI/halfSize);
//phaseShiftStepImag = Math.sin(-Math.PI/halfSize);
phaseShiftStepReal = cosTable[halfSize];
phaseShiftStepImag = sinTable[halfSize];
currentPhaseShiftReal = 1;
currentPhaseShiftImag = 0;
for (var fftStep = 0; fftStep < halfSize; fftStep++) {
i = fftStep;
while (i < bufferSize) {
off = i + halfSize;
tr = (currentPhaseShiftReal * real[off]) - (currentPhaseShiftImag * imag[off]);
ti = (currentPhaseShiftReal * imag[off]) + (currentPhaseShiftImag * real[off]);
real[off] = real[i] - tr;
imag[off] = imag[i] - ti;
real[i] += tr;
imag[i] += ti;
i += halfSize << 1;
}
tmpReal = currentPhaseShiftReal;
currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag);
currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal);
}
halfSize = halfSize << 1;
}
return this.calculateSpectrum();
};