1047 lines
28 KiB
JavaScript
1047 lines
28 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 = '0.4.0';
|
|
Dancer.adapters = {};
|
|
|
|
Dancer.prototype = {
|
|
|
|
load : function ( source, micBoost, useMic ) {
|
|
// Loading an Audio element
|
|
if ( source instanceof HTMLElement ) {
|
|
this.source = source;
|
|
if ( Dancer.isSupported() === 'flash' ) {
|
|
this.source = { src: Dancer._getMP3SrcFromAudio( 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 if ( FlashDetect.versionAtLeast( 9 ) ) {
|
|
return 'flash';
|
|
} else {
|
|
return '';
|
|
}
|
|
};
|
|
|
|
Dancer.canPlay = function ( type ) {
|
|
var canPlay = audioEl.canPlayType;
|
|
return !!(
|
|
Dancer.isSupported() === 'flash' ?
|
|
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 );
|
|
case 'flash':
|
|
return new Dancer.adapters.flash( 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, 10 ];
|
|
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;
|
|
|
|
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 );
|
|
} else {
|
|
this.offKick && this.offKick.call( this.dancer, magnitude );
|
|
this.currentThreshold -= this.decay;
|
|
}
|
|
},
|
|
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 = new AudioContext(), filter = context.createBiquadFilter();
|
|
|
|
filter.type = "lowpass";
|
|
filter.frequency.value = 440;
|
|
|
|
this.dancer = dancer;
|
|
this.audio = new Audio();
|
|
this.context = context;
|
|
this.filter = filter;
|
|
};
|
|
|
|
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.source.connect( this.filter );
|
|
this.gain.connect(this.context.destination);
|
|
this.proc.connect(this.context.destination);
|
|
//this.filter.connect( this.context.destination );
|
|
|
|
this.isLoaded = true;
|
|
this.progress = 1;
|
|
this.dancer.trigger( 'loaded' );
|
|
}
|
|
|
|
Dancer.adapters.webaudio = adapter;
|
|
|
|
})();
|
|
|
|
(function() {
|
|
var
|
|
SAMPLE_SIZE = 1024,
|
|
SAMPLE_RATE = 44100,
|
|
smLoaded = false,
|
|
smLoading = false,
|
|
CONVERSION_COEFFICIENT = 0.93;
|
|
|
|
var adapter = function ( dancer ) {
|
|
this.dancer = dancer;
|
|
this.wave_L = [];
|
|
this.wave_R = [];
|
|
this.spectrum = [];
|
|
window.SM2_DEFER = true;
|
|
};
|
|
|
|
adapter.prototype = {
|
|
// `source` can be either an Audio element, if supported, or an object
|
|
// either way, the path is stored in the `src` property
|
|
load : function ( source ) {
|
|
var _this = this;
|
|
this.path = source ? source.src : this.path;
|
|
|
|
this.isLoaded = false;
|
|
this.progress = 0;
|
|
|
|
!window.soundManager && !smLoading && loadSM.call( this );
|
|
|
|
if ( window.soundManager ) {
|
|
this.audio = soundManager.createSound({
|
|
id : 'dancer' + Math.random() + '',
|
|
url : this.path,
|
|
stream : true,
|
|
autoPlay : false,
|
|
autoLoad : true,
|
|
whileplaying : function () {
|
|
_this.update();
|
|
},
|
|
whileloading : function () {
|
|
_this.progress = this.bytesLoaded / this.bytesTotal;
|
|
},
|
|
onload : function () {
|
|
_this.fft = new FFT( SAMPLE_SIZE, SAMPLE_RATE );
|
|
_this.signal = new Float32Array( SAMPLE_SIZE );
|
|
_this.waveform = new Float32Array( SAMPLE_SIZE );
|
|
_this.isLoaded = true;
|
|
_this.progress = 1;
|
|
_this.dancer.trigger( 'loaded' );
|
|
}
|
|
});
|
|
this.dancer.audio = this.audio;
|
|
}
|
|
|
|
// Returns audio if SM already loaded -- otherwise,
|
|
// sets dancer instance's audio property after load
|
|
return this.audio;
|
|
},
|
|
|
|
play : function () {
|
|
this.audio.play();
|
|
this.isPlaying = true;
|
|
},
|
|
|
|
pause : function () {
|
|
this.audio.pause();
|
|
this.isPlaying = false;
|
|
},
|
|
|
|
setVolume : function ( volume ) {
|
|
this.audio.setVolume( volume * 100 );
|
|
},
|
|
|
|
getVolume : function () {
|
|
return this.audio.volume / 100;
|
|
},
|
|
|
|
getProgress : function () {
|
|
return this.progress;
|
|
},
|
|
|
|
getWaveform : function () {
|
|
return this.waveform;
|
|
},
|
|
|
|
getSpectrum : function () {
|
|
return this.fft.spectrum;
|
|
},
|
|
|
|
getTime : function () {
|
|
return this.audio.position / 1000;
|
|
},
|
|
|
|
update : function () {
|
|
if ( !this.isPlaying && !this.isLoaded ) return;
|
|
this.wave_L = this.audio.waveformData.left;
|
|
this.wave_R = this.audio.waveformData.right;
|
|
var avg;
|
|
for ( var i = 0, j = this.wave_L.length; i < j; i++ ) {
|
|
avg = parseFloat(this.wave_L[ i ]) + parseFloat(this.wave_R[ i ]);
|
|
this.waveform[ 2 * i ] = avg / 2;
|
|
this.waveform[ i * 2 + 1 ] = avg / 2;
|
|
this.signal[ 2 * i ] = avg * CONVERSION_COEFFICIENT;
|
|
this.signal[ i * 2 + 1 ] = avg * CONVERSION_COEFFICIENT;
|
|
}
|
|
|
|
this.fft.forward( this.signal );
|
|
this.dancer.trigger( 'update' );
|
|
}
|
|
};
|
|
|
|
function loadSM () {
|
|
var adapter = this;
|
|
smLoading = true;
|
|
loadScript( Dancer.options.flashJS, function () {
|
|
soundManager = new SoundManager();
|
|
soundManager.flashVersion = 9;
|
|
soundManager.flash9Options.useWaveformData = true;
|
|
soundManager.useWaveformData = true;
|
|
soundManager.useHighPerformance = true;
|
|
soundManager.useFastPolling = true;
|
|
soundManager.multiShot = false;
|
|
soundManager.debugMode = false;
|
|
soundManager.debugFlash = false;
|
|
soundManager.url = Dancer.options.flashSWF;
|
|
soundManager.onready(function () {
|
|
smLoaded = true;
|
|
adapter.load();
|
|
});
|
|
soundManager.ontimeout(function(){
|
|
console.error( 'Error loading SoundManager2.swf' );
|
|
});
|
|
soundManager.beginDelayedInit();
|
|
});
|
|
}
|
|
|
|
function loadScript ( url, callback ) {
|
|
var
|
|
script = document.createElement( 'script' ),
|
|
appender = document.getElementsByTagName( 'script' )[0];
|
|
script.type = 'text/javascript';
|
|
script.src = url;
|
|
script.onload = callback;
|
|
appender.parentNode.insertBefore( script, appender );
|
|
}
|
|
|
|
Dancer.adapters.flash = 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();
|
|
};
|
|
|
|
|
|
/*
|
|
Copyright (c) Copyright (c) 2007, Carl S. Yestrau All rights reserved.
|
|
Code licensed under the BSD License: http://www.featureblend.com/license.txt
|
|
Version: 1.0.4
|
|
*/
|
|
var FlashDetect = new function(){
|
|
var self = this;
|
|
self.installed = false;
|
|
self.raw = "";
|
|
self.major = -1;
|
|
self.minor = -1;
|
|
self.revision = -1;
|
|
self.revisionStr = "";
|
|
var activeXDetectRules = [
|
|
{
|
|
"name":"ShockwaveFlash.ShockwaveFlash.7",
|
|
"version":function(obj){
|
|
return getActiveXVersion(obj);
|
|
}
|
|
},
|
|
{
|
|
"name":"ShockwaveFlash.ShockwaveFlash.6",
|
|
"version":function(obj){
|
|
var version = "6,0,21";
|
|
try{
|
|
obj.AllowScriptAccess = "always";
|
|
version = getActiveXVersion(obj);
|
|
}catch(err){}
|
|
return version;
|
|
}
|
|
},
|
|
{
|
|
"name":"ShockwaveFlash.ShockwaveFlash",
|
|
"version":function(obj){
|
|
return getActiveXVersion(obj);
|
|
}
|
|
}
|
|
];
|
|
/**
|
|
* Extract the ActiveX version of the plugin.
|
|
*
|
|
* @param {Object} The flash ActiveX object.
|
|
* @type String
|
|
*/
|
|
var getActiveXVersion = function(activeXObj){
|
|
var version = -1;
|
|
try{
|
|
version = activeXObj.GetVariable("$version");
|
|
}catch(err){}
|
|
return version;
|
|
};
|
|
/**
|
|
* Try and retrieve an ActiveX object having a specified name.
|
|
*
|
|
* @param {String} name The ActiveX object name lookup.
|
|
* @return One of ActiveX object or a simple object having an attribute of activeXError with a value of true.
|
|
* @type Object
|
|
*/
|
|
var getActiveXObject = function(name){
|
|
var obj = -1;
|
|
try{
|
|
obj = new ActiveXObject(name);
|
|
}catch(err){
|
|
obj = {activeXError:true};
|
|
}
|
|
return obj;
|
|
};
|
|
/**
|
|
* Parse an ActiveX $version string into an object.
|
|
*
|
|
* @param {String} str The ActiveX Object GetVariable($version) return value.
|
|
* @return An object having raw, major, minor, revision and revisionStr attributes.
|
|
* @type Object
|
|
*/
|
|
var parseActiveXVersion = function(str){
|
|
var versionArray = str.split(",");//replace with regex
|
|
return {
|
|
"raw":str,
|
|
"major":parseInt(versionArray[0].split(" ")[1], 10),
|
|
"minor":parseInt(versionArray[1], 10),
|
|
"revision":parseInt(versionArray[2], 10),
|
|
"revisionStr":versionArray[2]
|
|
};
|
|
};
|
|
/**
|
|
* Parse a standard enabledPlugin.description into an object.
|
|
*
|
|
* @param {String} str The enabledPlugin.description value.
|
|
* @return An object having raw, major, minor, revision and revisionStr attributes.
|
|
* @type Object
|
|
*/
|
|
var parseStandardVersion = function(str){
|
|
var descParts = str.split(/ +/);
|
|
var majorMinor = descParts[2].split(/\./);
|
|
var revisionStr = descParts[3];
|
|
return {
|
|
"raw":str,
|
|
"major":parseInt(majorMinor[0], 10),
|
|
"minor":parseInt(majorMinor[1], 10),
|
|
"revisionStr":revisionStr,
|
|
"revision":parseRevisionStrToInt(revisionStr)
|
|
};
|
|
};
|
|
/**
|
|
* Parse the plugin revision string into an integer.
|
|
*
|
|
* @param {String} The revision in string format.
|
|
* @type Number
|
|
*/
|
|
var parseRevisionStrToInt = function(str){
|
|
return parseInt(str.replace(/[a-zA-Z]/g, ""), 10) || self.revision;
|
|
};
|
|
/**
|
|
* Is the major version greater than or equal to a specified version.
|
|
*
|
|
* @param {Number} version The minimum required major version.
|
|
* @type Boolean
|
|
*/
|
|
self.majorAtLeast = function(version){
|
|
return self.major >= version;
|
|
};
|
|
/**
|
|
* Is the minor version greater than or equal to a specified version.
|
|
*
|
|
* @param {Number} version The minimum required minor version.
|
|
* @type Boolean
|
|
*/
|
|
self.minorAtLeast = function(version){
|
|
return self.minor >= version;
|
|
};
|
|
/**
|
|
* Is the revision version greater than or equal to a specified version.
|
|
*
|
|
* @param {Number} version The minimum required revision version.
|
|
* @type Boolean
|
|
*/
|
|
self.revisionAtLeast = function(version){
|
|
return self.revision >= version;
|
|
};
|
|
/**
|
|
* Is the version greater than or equal to a specified major, minor and revision.
|
|
*
|
|
* @param {Number} major The minimum required major version.
|
|
* @param {Number} (Optional) minor The minimum required minor version.
|
|
* @param {Number} (Optional) revision The minimum required revision version.
|
|
* @type Boolean
|
|
*/
|
|
self.versionAtLeast = function(major){
|
|
var properties = [self.major, self.minor, self.revision];
|
|
var len = Math.min(properties.length, arguments.length);
|
|
for(i=0; i<len; i++){
|
|
if(properties[i]>=arguments[i]){
|
|
if(i+1<len && properties[i]==arguments[i]){
|
|
continue;
|
|
}else{
|
|
return true;
|
|
}
|
|
}else{
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Constructor, sets raw, major, minor, revisionStr, revision and installed public properties.
|
|
*/
|
|
self.FlashDetect = function(){
|
|
if(navigator.plugins && navigator.plugins.length>0){
|
|
var type = 'application/x-shockwave-flash';
|
|
var mimeTypes = navigator.mimeTypes;
|
|
if(mimeTypes && mimeTypes[type] && mimeTypes[type].enabledPlugin && mimeTypes[type].enabledPlugin.description){
|
|
var version = mimeTypes[type].enabledPlugin.description;
|
|
var versionObj = parseStandardVersion(version);
|
|
self.raw = versionObj.raw;
|
|
self.major = versionObj.major;
|
|
self.minor = versionObj.minor;
|
|
self.revisionStr = versionObj.revisionStr;
|
|
self.revision = versionObj.revision;
|
|
self.installed = true;
|
|
}
|
|
}else if(navigator.appVersion.indexOf("Mac")==-1 && window.execScript){
|
|
var version = -1;
|
|
for(var i=0; i<activeXDetectRules.length && version==-1; i++){
|
|
var obj = getActiveXObject(activeXDetectRules[i].name);
|
|
if(!obj.activeXError){
|
|
self.installed = true;
|
|
version = activeXDetectRules[i].version(obj);
|
|
if(version!=-1){
|
|
var versionObj = parseActiveXVersion(version);
|
|
self.raw = versionObj.raw;
|
|
self.major = versionObj.major;
|
|
self.minor = versionObj.minor;
|
|
self.revision = versionObj.revision;
|
|
self.revisionStr = versionObj.revisionStr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}();
|
|
};
|
|
FlashDetect.JS_RELEASE = "1.0.4";
|