/**
* A base audio module class, extended by BB.AudioNoise, BB.AudioSampler, BB.AudioFX, etc.
* @module BB.AudioBase
*/
define(['./BB', './BB.Audio', './BB.Detect' ],
function( BB, Audio, Detect ){
'use strict';
/**
* A base audio module class, extended by BB.AudioNoise, BB.AudioSampler, BB.AudioFX, etc.
* @class BB.AudioBase
* @constructor
*
* @example
* in the example below instantiating the BB.AudioBase creates a <a href="https://developer.mozilla.org/en-US/docs/Web/API/GainNode" target="_blank">GainNode</a> ( essentially the modules's output ) connected to the default BB.Audio.context ( ie. AudioDestination )
* <br> <img src="../assets/images/audiosampler1.png"/>
* <br>
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var node = new BB.AudioBase();<br>
* <br>
* // or optional config property<br>
* var node = new BB.AudioBase({<br>
* context: BB.Audio.context[2],<br>
* connect: fft,<br>
* volume: 0.5<br>
* });<br>
* <br>
* </code>
*/
BB.AudioBase = function( config ) {
if(typeof config === "undefined") config = {};
/**
* the Audio Context this derived from
* @property ctx
* @type AudioContext
* @default BB.Audio.context
*/
if( typeof BB.Audio.context === "undefined" )
throw new Error('BB Audio Modules require that you first create an AudioContext: BB.Audio.init()');
if( BB.Audio.context instanceof Array ){
if( typeof config === "undefined" || typeof config.context === "undefined" )
throw new Error('BB.AudioBase: BB.Audio.context is an Array, specify which { context:BB.Audio.context[?] }');
else
this.ctx = config.context;
} else {
this.ctx = BB.Audio.context;
}
/**
* the "output" gain node ( use .volume, .setGain() to interface with this )
* @property gain
* @type GainNode
* @private
*/
this.gain = this.ctx.createGain();
// default destination is context destination
// unless otherwise specified in { connect:AudioNode }
if( typeof config.connect !== 'undefined' ){
if( config.connect instanceof BB.AudioAnalyser )
this.gain.connect( config.connect.node );
else if( config.connect instanceof BB.AudioFX || config.connect instanceof BB.AFX )
this.gain.connect( config.connect.input );
else if( config.connect instanceof BB.AudioBase )
this.gain.connect( config.connect.gain );
else if( config.connect instanceof AudioDestinationNode || config.connect instanceof AudioNode )
this.gain.connect( config.connect );
else
throw new Error('BB.AudioBase: connect property expecting an AudioNode');
} else {
this.gain.connect( this.ctx.destination );
}
this._volume = (typeof config.volume !== "undefined") ? config.volume : 1; // MASTER NOISE VOLUME ESSENTIALY
this.gain.gain.value = this._volume;
this.gain.gain.name = "master";
};
/**
* the master volume (of output gain node)
* @property volume
* @type Number
* @default 1
*/
Object.defineProperty(BB.AudioBase.prototype, "volume", {
get: function() {
return this._volume;
},
set: function(v) {
if( typeof v !== 'number'){
throw new Error("BB.AudioBase.volume: expecing a number");
} else {
this.setGain( v );
}
}
});
/**
* connects the Noise to a particular AudioNode or AudioDestinationNode
* @method connect
* @param {AudioNode} destination the AudioNode or AudioDestinationNode to connect to
* @param {Number} output which output of the the Noise do you want to connect to the destination
* @param {Number} input which input of the destination you want to connect the Noise to
* @example
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var node = new BB.AudioBase({<br>
* volume: 0.75,<br>
* });<br>
* <br>
* node.connect( exampleNode );<br>
* // connected to both default BB.Audio.context && exampleNode<br>
* // so if exampleNode is also connected to BB.Audio.context by default,<br>
* // ...then you've got node connected to BB.Audio.context twice<br>
* </code>
* <br>
* ...which looks like this ( where the first Gain is the Noise and the second is the exampleNode )<br>
* <img src="../assets/images/audiosampler3.png">
*/
BB.AudioBase.prototype.connect = function( destination, output, input ){
if( destination instanceof BB.AudioAnalyser)
destination = destination.node;
else if( destination instanceof BB.AudioFX || destination instanceof BB.AFX )
destination = destination.input;
else if( destination instanceof BB.AudioBase )
destination = destination.gain;
if( !(destination instanceof AudioDestinationNode || destination instanceof AudioNode) )
throw new Error('BB.AudioBase.connect: destination should be an instanceof AudioDestinationNode or AudioNode');
if( typeof output !== "undefined" && typeof output !== "number" )
throw new Error('BB.AudioBase.connect: output should be a number');
if( typeof intput !== "undefined" && typeof input !== "number" )
throw new Error('BB.AudioBase.connect: input should be a number');
if( typeof intput !== "undefined" ) this.gain.connect( destination, output, input );
else if( typeof output !== "undefined" ) this.gain.connect( destination, output );
else this.gain.connect( destination );
};
/**
* diconnects the Noise from the node it's connected to
* @method disconnect
* @param {AudioNode} destination what it's connected to
* @param {Number} output the particular output number
* @param {Number} input the particular input number
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var node = new BB.AudioBase({<br>
* volume: 0.75,<br>
* });<br>
* <br>
* node.disconnect(); // disconnected from default BB.Audio.context<br>
* node.connect( exampleNode ); // connected to exampleNode only<br>
* </code>
* <br>
* ...which looks like this ( where the first Gain is the node and the second is the exampleNode )<br>
* <img src="../assets/images/audiosampler4.png">
*/
BB.AudioBase.prototype.disconnect = function(destination, output, input ){
if( destination instanceof BB.AudioAnalyser )
destination = destination.node;
else if( destination instanceof BB.AudioFX || destination instanceof BB.AFX )
destination = destination.input;
else if( destination instanceof BB.AudioBase )
destination = destination.gain;
if( typeof destination !== "undefined" &&
!(destination instanceof AudioDestinationNode || destination instanceof AudioNode) )
throw new Error('BB.AudioBase.disconnect: destination should be an instanceof AudioDestinationNode or AudioNode');
if( typeof output !== "undefined" && typeof output !== "number" )
throw new Error('BB.AudioBase.disconnect: output should be a number');
if( typeof input !== "undefined" && typeof input !== "number" )
throw new Error('BB.AudioBase.disconnect: input should be a number');
if( typeof input !== "undefined" ) this.gain.disconnect( destination, output, input );
else if( typeof output !== "undefined" ) this.gain.disconnect( destination, output );
else if( typeof destination !== "undefined" ) this.gain.disconnect( destination );
else this.gain.disconnect();
};
/**
* sets the gain level of the node ( in a sense, master volume control )
* @method setGain
* @param {Number} num a float value, 1 being the default volume, below 1 decreses the volume, above one pushes the gain
* @param {Number} ramp value in seconds for how quickly/slowly to ramp to the new value (num) specified
*
* @example
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var node = new BB.AudioBase({<br>
* volume: 0.75<br>
* });<br>
* <br>
* node.setGain( 0.25, 2 ); // lower's volume from 0.75 to 0.25 in 2 seconds <br>
* // if no ramp value is needed, you could alternatively do<br>
* node.volume = 0.5; // immediately jumps from 0.25 to 0.5 <br>
* </code>
*/
BB.AudioBase.prototype.setGain = function( num, gradually ){
if( typeof num !== "number" )
throw new Error('BB.AudioBase.setGain: first argument expecting a number');
this._volume = num;
if(typeof gradually !== "undefined"){
if( typeof num !== "number" )
throw new Error('BB.AudioBase.setGain: second argument expecting a number');
else
this._globalGainUpdate( gradually );
}
else { this._globalGainUpdate(0); }
};
BB.AudioBase.prototype._sec2tc = function( sec ){
return ( sec * 2 ) / 10;
};
BB.AudioBase.prototype._globalGainUpdate = function( gradually ){
if(typeof gradually === "undefined") gradually = 0;
if(BB.Detect.browserInfo.name=="Firefox"){
this.gain.gain.setTargetAtTimePoly( this._volume, 0, gradually);
} else {
this.gain.gain.setTargetAtTime( this._volume, 0, this._sec2tc(gradually));
}
};
// ... polyfills .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ....
if(BB.Detect.browserInfo.name=="Firefox"){
// !!! *** !!! *** !!! *** !!! *** !!! *** !!! *** !!!
// !!! THIS POLY FILL IS STILL UNDERCONSTRUCTION !!!
// !!! *** !!! *** !!! *** !!! *** !!! *** !!! *** !!!
//
AudioParam.prototype.setTargetAtTimePoly = function( target, startTime, seconds ){
// this.value = target;
var polyTimeout = null;
var polyInterval = null;
startTime = startTime * 1000;
var self = this;
// if(typeof polyTimeout !== "undefined"){ clearTimeout( polyTimeout ); polyTimeout = undefined; }
polyTimeout = setTimeout(function(){
if(seconds===0){
self.value = target;
// console.log(self.name,'jump to '+target+", now "+self.value)
} else {
// if(typeof polyInterval !== "undefined"){ clearInterval( polyInterval ); polyInterval = undefined; }
var delta = target - self.value;
var inc = delta / (seconds*60);
var dir = (delta>=0) ? "up" : "down";
polyInterval = setInterval(function(){
self.value += inc*2; // why 2? well b/c for some f'n reason it goes too slow otherwise
if(dir=="up"){
if( self.value >= target){clearInterval( polyInterval ); }
} else {
if( self.value <= target){clearInterval( polyInterval ); }
}
}, 1000/60 );
}
}, startTime );
};
}
return BB.AudioBase;
});