/**
* A module for creating oscillating tones ( periodic wave forms ) along with music theory utilities
* @module BB.AudioTone
* @extends BB.AudioBase
*/
define(['./BB', './BB.AudioBase' ],
function( BB, AudioBase ) {
'use strict';
/**
* A module for creating oscillating tones ( periodic wave forms ) along with music theory utilities
* @class BB.AudioTone
* @constructor
* @extends BB.AudioBase
*
* @param {Object} config A config object to initialize the Tone,
* can contain the following:<br><br>
* <code class="code prettyprint">
* {<br>
* context: BB.Audio.context[2], // choose specific context <br>
* connect: fft, // overide default destination <br>
* volume: 0.5, // technically master "gain" (expolential multiplier)<br>
* type: "custom", // "sine", "square", "sawtooth", "triangle", "custom"<br>
* wave: [ 0, 1, 0, 0.5, 0, 0.25, 0, 0.125 ] // only for "custom" type<Br>
* }
* </code>
* <br>
* see the <a href="../../examples/editor/?file=audio-waveshaper" target="_blank">waveshaper</a> for an example of how the wave property works. the "wave" property abstracts the WebAudio API's <a href="https://developer.mozilla.org/en-US/docs/Web/API/PeriodicWave" target="_blank">createPeriodicWave</a> method a bit. the Array passed above starts with 0 followed by the fundamental's amplitude value, followed by how ever many subsequent harmonics you choose. Alternatively, you can also pass an object with a "real" and "imag" (imaginary) Float32Array, for example:
* <br><br>
* <code class="code prettyprint">
* {<br>
* context: BB.Audio.context[2], // choose specific context <br>
* type: "custom", <br>
* wave: {<br>
* imag: new Float32Array([0,1,0,0.5,0,0.25,0,0.125]),<br>
* real: new Float32Array(8)<br>
* }<br>
* }
* </code>
* <br>
* @example
* in the example below instantiating the BB.AudioTone creates a <a href="https://developer.mozilla.org/en-US/docs/Web/API/GainNode" target="_blank">GainNode</a> ( essentially the Tone's output ) connected to the default BB.Audio.context ( ie. AudioDestination )
* <br> <img src="../assets/images/audiosampler1.png"/>
* <br> everytime an individual tone is played, for example: <code> O.makeNote("C#")</code>, a corresponding <a href="https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode/type" target="_blank">OscillatorNode</a> with it's own GainNode is created and connected to the Tone's master GainNode ( the image below is an example of the graph when two notes are played )
* <br> <img src="../assets/images/audiotone1.png"/> <br>
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var O = new BB.AudioTone({<br>
* volume: 0.75,<br>
* type: "square"<br>
* });<br>
* <br>
* O.makeNote( 440 );<br>
* O.makeNote( 880 );<br>
* </code>
*
* view basic <a href="../../examples/editor/?file=audio-tone" target="_blank">BB.AudioTone</a> example
*/
BB.AudioTone = function( config ) {
BB.AudioBase.call(this, config);
if( !config ) config = {}; // instead of below? ...bad practice?
// if( !config ) throw new Error('BB.AudioTone: requires a config object');
/**
* the type of wave
* @type {String}
* @property type
*/
this.type = null;
this._wave = null;
var oscTypes = [ "sine", "square", "sawtooth", "triangle", "custom" ];
if( typeof config.type !== "undefined"){
if( oscTypes.indexOf(config.type) < 0 ){
throw new Error('BB.AudioTone: type should be either "sine", "square", "sawtooth", "triangle" or "custom ');
} else if( config.type === "custom" ){
if( typeof config.wave !== "undefined" ){
if(config.wave instanceof Array){
this.type = config.type;
this._wave = config.wave;
var imag = new Float32Array( config.wave ); // sine
var real = new Float32Array( imag.length ); // cos
this.wavetable = this.ctx.createPeriodicWave( real, imag );
} else if(config.wave instanceof Object){
if(typeof config.wave.real === "undefined" || typeof config.wave.imag === "undefined"){
throw new Error('BB.AudioTone: wave object should contain a "real" and an "imag" (Float32Array) properties');
} else if( !(config.wave.real instanceof Float32Array) || !(config.wave.imag instanceof Float32Array) ) {
throw new Error('BB.AudioTone: real and imag properties should be an instanceof Float32Array');
} else {
this.type = config.type;
this._wave = config.wave;
this.wavetable = this.ctx.createPeriodicWave( config.wave.real, config.wave.imag );
}
} else {
throw new Error('BB.AudioTone: wave should be instanceof Object or instanceof Array');
}
} else {
throw new Error('BB.AudioTone: additional wave property is required for "custom" type');
}
} else { this.type = config.type; }
} else {
this.type = "sine";
}
this.polynotes = {}; // keep track of current polysymphonic notes
this.gainInterval = null; // for elegently adjusting global gain
this.scales = {
"major" : [],
"naturalminor" : [],
"harmonicminor" : [],
"melodicminor" : [],
"majorpentatonic" : [],
"minorpentatonic" : [],
"blues" : [],
"minorblues" : [],
"majorblues" : [],
"augmented" : [],
"diminished" : [],
"phrygiandominant" : [],
"dorian" : [],
"phrygian" : [],
"lydian" : [],
"mixolydian" : [],
"locrian" : [],
"jazzmelodicminor" : [],
"dorianb2" : [],
"lydianaugmented" : [],
"lydianb7" : [],
"mixolydianb13" : [],
"locrian#2" : [],
"superlocrian" : [],
"wholehalfdiminished" : [],
"halwholediminished" : [],
"enigmatic" : [],
"doubleharmonic" : [],
"hungarianminor" : [],
"persian" : [],
"arabian" : [],
"japanese" : [],
"egyptian" : [],
"hirajoshi" : [],
"nickfunk1" : [],
"nickfunk2" : []
};
this.chords = {
"maj" : [], // major
"min" : [], // minor
"dim" : [], // diminished
"7" : [], // 7th
"min7" : [], // minor 7th
"maj7" : [], // major 7th
"sus4" : [],
"7sus4" : [],
"6" : [],
"min6" : [],
"aug" : [],
"7-5" : [],
"7+5" : [],
"min7-5" : [],
"min/maj7" : [],
"maj7+5" : [],
"maj7-5" : [],
"9" : [],
"min9" : [],
"maj9" : [],
"7+9" : [],
"7-9" : [],
"7+9-5" : [],
"6/9" : [],
"9+5" : [],
"9-5" : [],
"min9-5" : [],
"11" : [],
"min11" : [],
"11-9" : [],
"13" : [],
"min13" : [],
"maj13" : [],
"add9" : [],
"minadd9" : [],
"sus2" : [],
"5" : []
};
};
BB.AudioTone.prototype = Object.create(BB.AudioBase.prototype);
BB.AudioTone.prototype.constructor = BB.AudioTone;
/**
* the wave Array/Object
* @property wave
* @type Object
* @default null
*/
Object.defineProperty(BB.AudioTone.prototype, "wave", {
get: function() {
return this._wave;
},
set: function(wave) {
if( typeof wave !== "undefined" ){
if(wave instanceof Array){
this.type = "custom";
this._wave = wave;
var imag = new Float32Array( wave ); // sine
var real = new Float32Array( imag.length ); // cos
this.wavetable = this.ctx.createPeriodicWave( real, imag );
} else if(wave instanceof Object){
if(typeof wave.real === "undefined" || typeof wave.imag === "undefined"){
throw new Error('BB.AudioTone.wave: wave object should contain a "real" and an "imag" (Float32Array) properties');
} else if( !(wave.real instanceof Float32Array) || !(wave.imag instanceof Float32Array) ) {
throw new Error('BB.AudioTone.wave: real and imag properties should be an instanceof Float32Array');
} else {
this.type = "custom";
this._wave = wave;
this.wavetable = this.ctx.createPeriodicWave( wave.real, wave.imag );
}
} else {
throw new Error('BB.AudioTone.wave: wave should be instanceof Object or instanceof Array');
}
} else {
throw new Error('BB.AudioTone.wave: expecting a wave argument ( Array or Object )');
}
}
});
// ... public utils .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ....
/**
* utility method which takes a root frequency and a number of semitones and returns the frequency of the "note" the specified number of semitones away from the root frequency
* @method freq
* @param {Number} root the root frequency ( in Hz )
* @param {Number} semitones the number of semitones away from the root you need to know the frequency of ( negative values are lower pitches then root, positive values are higher pitches )
* @param {String} [tuning] the kind of temperment to base the transposition on ( eitehr "equal" for equatempered tuning or "just" for pure/harmoniic tuning )
* @return {Number} a frequency value in Hz
* @example
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var O = new BB.AudioTone();<br>
* <br>
* O.makeNote( O.freq( 440, 5 ) ); // plays a D,587.33Hz ( 5 semitones up from A,440Hz )
* </code>
*/
BB.AudioTone.prototype.freq = function( root, n, temp ){
if(typeof temp === "undefined") temp = "equal";
// see https://www.youtube.com/watch?v=Y5RNrvcQ7q0 for details on different temperments
var rnd = null;
if(temp === "just"){
var ratios = [
// just intonation ( aka "chromatic scale" aka "pure intonnation" aka "harmonic tuning" )
1, // unison ( 1/1 ) // C
25/24, // minor second // C#
9/8, // major second // D
6/5, // minor third // D#
5/4, // major third // E
4/3, // fourth // F
45/32, // diminished fifth // F#
3/2, // fifth // G
8/5, // minor sixth // G#
5/3, // major sixth // A
9/5, // minor seventh // A#
15/8, // major seventh // B
// 2 // octave ( 2/1 ) // C
];
if(typeof root !== "number") throw new Error('BB.AudioTone.freq: expecting a frequency in Hz');
rnd = root * ratios[n%12];
if(n>=12) rnd = rnd * (Math.floor(n/12)+1);
return Math.round(rnd*100)/100;
}
else if(temp === "equal") {
if(typeof root !== "number") throw new Error('BB.AudioTone.freq: expecting a frequency in Hz');
if(typeof n === "undefined") n = 0;
// formula via: http://www.phy.mtu.edu/~suits/NoteFreqCalcs.html
var tr2 = Math.pow(2, 1/12); // the twelth root of 2
rnd = root * Math.pow(tr2,n);
return Math.round(rnd*100)/100;
} else {
throw new Error('not a scale');
}
};
/**
* utility method which takes a root note (String) and a number of semitones and returns the frequency of the "note" the specified number of semitones away from the root note
* @method note
* @param {Number} root the root note ( "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" where "A" is 440 )
* @param {Number} octave the number of octaves away from the root you need to know the frequency of ( negative values are lower pitches then root, positive values are higher pitches )
* @param {Number} [frequency] alternative "A" frequency ( overrides default of 440 )
* @return {Number} a frequency value in Hz
* @example
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var O = new BB.AudioTone();<br>
* <br>
* O.makeNote( O.note( "A", 1 ) ); // plays an A,880Hz ( 1 octave up from A,440Hz )
* </code>
*/
BB.AudioTone.prototype.note = function( note, transpose, root ){
var nStr = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
var idx = nStr.indexOf(note);
if( idx < 0 ) throw new Error('BB.AudioTone.note: expecting "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#" or "B"');
var RT = (typeof root !== "number" ) ? 440 : root;
var oct = (typeof transpose !== "number") ? 0 : transpose;
note = idx + ( (oct+5)*12);
return RT * Math.pow(2,(note-69)/12);
};
/**
* generates a musical scale ( array of related frequency values ) from a
* root note.
*
* the notes are stored in an array in the <code>.scales</code> property (
* object ) and can be accessed by querying the key ( name ) of the scale type
* you generated like so: <code> .scales.major </code>, which
* will return an array of notes ( frequency number values in Hz )
*
* @method createScale
*
* @param {String} scale name of the scale type you want to generate.
* can be either major, naturalminor, harmonicminor, melodicminor, majorpentatonic,
* minorpentatonic, blues, minorblues, majorblues, augmented, diminished, phrygiandominant,
* dorian, phrygian, lydian, mixolydian, locrian, jazzmelodicminor, dorianb2, lydianaugmented,
* lydianb7, mixolydianb13, locrian#2, superlocrian, wholehalfdiminished, halwholediminished,
* enigmatic, doubleharmonic, hungarianminor, persian, arabian, japanese, egyptian, hirajoshi,
* nickfunk1, nickfunk2
*
* @param {Number} root the fundamental/root note of the scale ( the "A" in an "A major" )
* can be either a number (frequency in Hz) or a note string ( "A", "A#", "B", etc )
*
* @param {String} [tuning] the kind of tuning (temperment) you would like to use to derive the scale with,
* can be either "equal" (default) or "just"
*
* @return {Array} the newly created array
*
* @example
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var O = new BB.AudioTone({ volume: 0.5 });<br>
* <br>
* function scheduler( freq, tuning, timeout ) { <br>
* return function() { <br>
* setTimeout(function(){ <br>
* O.makeNote({ <br>
* type: "sine", <br>
* tuning: tuning, <br>
* frequency: freq, <br>
* duration: 0.25,<br>
* attack: 0.1,<br>
* release:0.5<br>
* });<br>
* },timeout);<br>
* }<br>
* } <br>
* <br>
* function playScale( scale, root, tuning ){<br>
* O.createScale( scale, root ); // this line generates the scale <br>
* <br>
* for (var i = 0; i < O.scales[scale].length; i++) {<br>
* var freq = O.scales[scale][i];<br>
* scheduler( freq, tuning, i*500 )();<br>
* };<br>
* }<br>
* <br>
* playScale( "major", 220, "just" );<br>
* </code>
*/
BB.AudioTone.prototype.createScale = function( scale, root, temp ){
if( !(scale in this.scales) ) {
throw new Error("BB.AudioTone.createScale: '"+scale+"' is not a valid scale name, choose from: "+Object.keys(this.scales) );
} else {
var tuning = "equal";
if(typeof temp !== "undefined"){
if( temp === "equal" || temp === "just" ){
tuning = temp;
} else {
throw new Error('BB.AudioTone.createScale: temperment should be either "just" or "equal"');
}
}
if(typeof root === "string") root = this.note(root);
else if(typeof root === "number") root = root;
else throw new Error('BB.AudioTone.createScale: expecting a frequency (number) or a note ("A","A#",etc.) as second paramter');
// via >> http://www.cs.cmu.edu/~scottd/chords_and_scales/music.html
var steps;
if(scale=="major") steps = [2, 2, 1, 2, 2, 2, 1];
else if(scale=="naturalminor") steps = [2, 1, 2, 2, 1, 2, 2];
else if(scale=="harmonicminor") steps = [2, 1, 2, 2, 1, 3, 1];
else if(scale=="melodicminor") steps = [2, 1, 2, 2, 2, 2, 1];
else if(scale=="majorpentatonic") steps = [2, 2, 3, 2, 3];
else if(scale=="minorpentatonic") steps = [3, 2, 2, 3, 2];
else if(scale=="blues") steps = [3, 2, 1, 1, 3, 2];
else if(scale=="minorblues") steps = [2, 1, 2, 1, 1, 1, 2, 2];
else if(scale=="majorblues") steps = [2, 1, 1, 1, 1, 1, 2, 1, 2];
else if(scale=="augmented") steps = [2, 2, 2, 2, 2, 2];
else if(scale=="diminished") steps = [2, 1, 2, 1, 2, 1, 2, 1];
else if(scale=="phrygiandominant") steps = [1, 3, 1, 2, 1, 2, 2];
else if(scale=="dorian") steps = [2, 1, 2, 2, 2, 1, 2];
else if(scale=="phrygian") steps = [1, 2, 2, 2, 1, 2, 2];
else if(scale=="lydian") steps = [2, 2, 2, 1, 2, 2, 1];
else if(scale=="mixolydian") steps = [2, 2, 1, 2, 2, 1, 2];
else if(scale=="locrian") steps = [1, 2, 2, 1, 2, 2, 2];
else if(scale=="jazzmelodicminor") steps = [2, 1, 2, 2, 2, 2, 1];
else if(scale=="dorianb2") steps = [1, 2, 2, 2, 2, 1, 2];
else if(scale=="lydianaugmented") steps = [2, 2, 2, 2, 1, 2, 1];
else if(scale=="lydianb7") steps = [2, 2, 2, 1, 2, 1, 2];
else if(scale=="mixolydianb13") steps = [2, 2, 1, 2, 1, 2, 2];
else if(scale=="locrian#2") steps = [2, 1, 2, 1, 2, 2, 2];
else if(scale=="superlocrian") steps = [1, 2, 1, 2, 2, 2, 2];
else if(scale=="wholehalfdiminished") steps = [2, 1, 2, 1, 2, 1, 2, 1];
else if(scale=="halwholediminished") steps = [1, 2, 1, 2, 1, 2, 1, 2];
else if(scale=="enigmatic") steps = [1, 3, 2, 2, 2, 1, 1];
else if(scale=="doubleharmonic") steps = [1, 3, 1, 2, 1, 3, 1];
else if(scale=="hungarianminor") steps = [2, 1, 3, 1, 1, 3, 1];
else if(scale=="persian") steps = [1, 3, 1, 1, 2, 3, 1];
else if(scale=="arabian") steps = [2, 2, 1, 1, 2, 2, 2];
else if(scale=="japanese") steps = [1, 4, 2, 1, 4];
else if(scale=="egyptian") steps = [2, 3, 2, 3, 2];
else if(scale=="hirajoshi") steps = [2, 1, 4, 1, 4];
else if(scale=="nickfunk1") steps = [3, 2, 2, 3, 2, 3, 2];
else if(scale=="nickfunk2") steps = [3, 2, 1, 1, 3, 2, 3, 2, 1];
this.scales[scale] = [];
var incSteps = [0];
for (var s = 1; s < steps.length+1; s++) {
var inc = 0;
for (var j = 0; j < s; j++) inc += steps[j];
incSteps.push( inc );
}
for (var i = 0; i < incSteps.length; i++) {
this.scales[scale].push( this.freq( root, incSteps[i], tuning ) );
}
return this.scales[scale];
}
};
// ... private utils .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ....
// OVERRIDE the BB.AudioBase._globalGainUpdate()
//
BB.AudioTone.prototype._globalGainUpdate = function( gradually, fromNoteOff ){
if(typeof gradually === "undefined") gradually = 0;
if(fromNoteOff){
if(typeof this.gainInterval!=="undefined"){
clearInterval( this.gainInterval );
this.gainInterval = undefined;
}
var self = this;
this.gainInterval = setInterval(function(){
var count = 0;
var decs = [];
for (var k in self.polynotes) {
if (self.polynotes[k].down) count++;
else decs.push( self.polynotes[k].gain.gain.value );
}
if( decs.length > 0 ){
var sum = decs.reduce(function(a, b) { return a + b; }); // via : http://stackoverflow.com/a/10624256
var polylength = count + sum;
if(polylength<1) polylength = 1;
if(BB.Detect.browserInfo.name=="Firefox"){
self.gain.gain.setTargetAtTimePoly( self.volume/polylength, 0, 0 );
} else {
self.gain.gain.setTargetAtTime( self.volume/polylength, 0, 0 );
}
} else {
clearInterval( self.gainInterval );
self.gainInterval = undefined;
}
},1000/60);
} else {
var count = 0;
for (var k in this.polynotes) {
if (this.polynotes[k].down) count++;
}
var polylength = (count===0) ? 1 : count;
// this.gain.gain.value = this._volume / polylength;
if(BB.Detect.browserInfo.name=="Firefox"){
this.gain.gain.setTargetAtTimePoly( this._volume/polylength, 0, gradually);
} else {
this.gain.gain.setTargetAtTime( this._volume/polylength, 0, this._sec2tc(gradually));
}
}
};
BB.AudioTone.prototype._addPolyNote = function( freq, oscNode, gainNode ){
this.polynotes[freq] = { down:true, osc:oscNode, gain:gainNode };
this._globalGainUpdate( 0.25 );
};
BB.AudioTone.prototype._removePolyNote = function( freq ){
if(typeof this.polynotes[freq]!=="undefined"){
if(typeof this.polynotes[freq].timeout!=="undefined")
clearTimeout( this.polynotes[freq].timeout );
this.polynotes[freq].osc.stop(0);
this.polynotes[freq].gain.disconnect();
delete this.polynotes[freq];
this._globalGainUpdate( 0.25 );
}
};
BB.AudioTone.prototype._returnSteps = function( type ){
// via >> http://www.phy.mtu.edu/~suits/chords.html
// && >> http://www.cs.cmu.edu/~scottd/chords_and_scales/music.html
var steps;
if(type=="maj") steps = [4, 3];
else if(type=="min") steps = [3, 4];
else if(type=="dim") steps = [3, 3];
else if(type=="7") steps = [4, 3, 3];
else if(type=="min7") steps = [3, 4, 3 ];
else if(type=="maj7") steps = [4, 3, 4];
else if(type=="sus4") steps = [5, 2];
else if(type=="7sus4") steps = [5, 2, 3];
else if(type=="6") steps = [4, 3, 2];
else if(type=="min6") steps = [3, 4, 2];
else if(type=="aug") steps = [4, 4];
else if(type=="7-5") steps = [4, 2, 4];
else if(type=="7+5") steps = [4, 4, 2];
else if(type=="min7-5") steps = [3, 3, 4];
else if(type=="min/maj7")steps = [3, 4, 4];
else if(type=="maj7+5") steps = [4, 4, 3];
else if(type=="maj7-5") steps = [4, 2, 5];
else if(type=="9") steps = [4, 3, 3, -8];
else if(type=="min9") steps = [3, 4, 3, -8];
else if(type=="maj9") steps = [4, 3, 4, -9];
else if(type=="7+9") steps = [4, 3, 3, -7];
else if(type=="7-9") steps = [4, 3, 3, -9];
else if(type=="7+9-5") steps = [4, 2, 4, -7];
else if(type=="6/9") steps = [4, 3, 2, -7];
else if(type=="9+5") steps = [4, 4, 2, -8];
else if(type=="9-5") steps = [4, 2, 4, -8];
else if(type=="min9-5") steps = [3, 3, 4, -8];
else if(type=="11") steps = [4, 3, 3, -8, 3];
else if(type=="min11") steps = [3, 4, 3, -8, 3];
else if(type=="11-9") steps = [4, 3, 3, -9, 4];
else if(type=="13") steps = [4, 3, 3, -8, 3, 4];
else if(type=="min13") steps = [3, 4, 3, -8, 3, 4];
else if(type=="maj13") steps = [4, 3, 4, -9, 3, 4];
else if(type=="add9") steps = [4, 3, -5];
else if(type=="minadd9") steps = [3, 4, -5];
else if(type=="sus2") steps = [2, 5];
else if(type=="5") steps = [7];
return steps;
};
// ... make sounds!!!! .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ....
/**
* starts playing a tone
*
* @method noteOn
* @param {number} frequency a value in Hz of the frequency you want to play
* @param {number} [velocity] the volume/gain relative to the master volume/gain of this particular note ( default 1 )
* @param {number} [attack] the time it takes for the note to fade in ( in seconds, default 0.05 )
* @param {number} [schedule] if you want to schedule the note for later ( rather than play immediately ), in seconds ( relative to BB.Audio.context.currentTime )
*
* @example
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var O = new BB.AudioTone({ volume: 0.5 });<br>
* <br>
* O.ntoeOn( 440, 1, 0.5 ); // will stay on, until noteOff(440) is executed
* </code>
*/
BB.AudioTone.prototype.noteOn = function( freq, velocity, attack, schedule ){
if(typeof freq === "undefined") throw new Error('need frequency');
// if(attack == 0) attack = 0.1; // fix firefox bug
if( typeof this.polynotes[freq] !== "undefined" ){
// console.log(this.polynotes[freq].gain.gain.value);
if( this.polynotes[freq].gain.gain.value < 0.99 ){
// note currently fading out
this._removePolyNote( freq );
} else {
// already being pressed, so do nothing
// console.warn('BB.AudioTone.noteOn: this note is already on'); // <<< MAKE A NOTE OF THIS
return;
}
}
var gainNode = this.ctx.createGain();
gainNode.connect( this.gain );
gainNode.gain.value = 0.0;
gainNode.gain.name = "note";
var osc = this.ctx.createOscillator();
osc.connect( gainNode );
osc.frequency.value = freq;
if(this.type === "custom"){
osc.setPeriodicWave( this.wavetable );
} else {
osc.type = this.type;
}
this._addPolyNote( freq, osc, gainNode );
var now = (typeof schedule !== "undefined") ? schedule : this.ctx.currentTime;
var vel = (typeof velocity !== "undefined") ? velocity : 1.0;
var ack = (typeof attack !== "undefined" ) ? attack : 0.05;
osc.start(now);
// this._fadePolyFill( gainNode.gain, vel, now, ack );
if(BB.Detect.browserInfo.name=="Firefox"){
gainNode.gain.setTargetAtTimePoly( vel, 0, ack); // maybe edit back to "now" instead of 0
} else {
gainNode.gain.setTargetAtTime( vel, 0, this._sec2tc(ack)); // maybe edit back to "now" instead of 0
}
};
BB.AudioTone.prototype.chordOn = function( config ){
var nStr = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
if(typeof config !== "object") throw new Error('BB.AudioTone.chordOn: expecting config object');
var root;
if(typeof config.frequency === "number") root = config.frequency;
else if(typeof config.frequency === "string" && nStr.indexOf(config.frequency) >= 0) root = this.note(config.frequency);
else throw new Error('BB.AudioTone.chordOn: frequency paramter should either be a number or a note string ("A","A#", etc.)');
var vel = (typeof config.velocity !== "undefined") ? config.velocity : 1;
var ack = (typeof config.attack !== "undefined") ? config.attack : 0;
var typ = (typeof config.type !== "undefined") ? config.type : "maj";
var tun = (typeof config.tuning !== "undefined") ? config.tuning : "equal";
var sch = (typeof config.schedule !== "undefined") ? config.schedule : 0;
var steps = this._returnSteps( typ );
var incSteps = [0];
for (var s = 1; s < steps.length+1; s++) {
var inc = 0;
for (var j = 0; j < s; j++) inc += steps[j];
incSteps.push( inc );
}
for (var i = 0; i < incSteps.length; i++) {
this.noteOn( O.freq(root, incSteps[i], tun), vel, ack, sch );
}
};
/**
* stops playing a tone
*
* @method noteOff
* @param {number} frequency a value in Hz of the note you want to stop playing
* @param {number} [release] the time it takes for the note to fade out ( in seconds, default 0 )
*
* @example
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var O = new BB.AudioTone({ volume: 0.5 });<br>
* <br>
* O.noteOn( 440, 1, 0.5 ); <br>
* setTimeout(function(){ O.noteOff(440); },1000);
* </code>
*/
BB.AudioTone.prototype.noteOff = function( freq, release ){
if(typeof freq === "undefined") throw new Error('need frequency');
if(typeof this.polynotes[freq] !== "undefined"){
this.polynotes[freq].down = false;
var self= this;
var now = this.ctx.currentTime;
var dec = (typeof release !== "undefined") ? release : 0;
// var decSec = (dec/20.0); // best guess of dec to seconds, also tried /10.0 ( dec should be 0 - 100 )
// var release = now + decSec;
release = now + dec;
this.polynotes[freq].gain.gain.cancelScheduledValues( now );
if(BB.Detect.browserInfo.name=="Firefox"){
this.polynotes[freq].gain.gain.setTargetAtTimePoly( 0.0, 0, dec); // maybe edit back to "now" instead of 0
} else {
this.polynotes[freq].gain.gain.setTargetAtTime( 0.0, 0, this._sec2tc(dec)); // maybe edit back to "now" instead of 0
}
// this._fadePolyFill( this.polynotes[freq].gain.gain, 0.0, now, dec );
this.polynotes[freq].osc.stop( release );
this._globalGainUpdate( this._sec2tc(dec), true );
if(typeof this.polynotes[freq].timeout !== "undefined" )
clearTimeout( this.polynotes[freq].timeout );
this.polynotes[freq].timeout = setTimeout(function(){
self._removePolyNote( freq );
},dec*1000);
}
};
BB.AudioTone.prototype.chordOff = function( config ){
var nStr = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
if(typeof config !== "object") throw new Error('BB.AudioTone.chordOff: expecting config object');
var root;
if(typeof config.frequency === "number") root = config.frequency;
else if(typeof config.frequency === "string" && nStr.indexOf(config.frequency) >= 0) root = this.note(config.frequency);
else throw new Error('BB.AudioTone.chordOff: frequency paramter should either be a number or a note string ("A","A#", etc.)');
if(typeof config.type !== "string") throw new Error('BB.AudioTone.chordOff: expecting a type property (chord) as a string');
var dec = (typeof config.release !== "undefined") ? config.release : 0;
var tun = (typeof config.tuning !== "undefined") ? config.tuning : "equal";
var typ = config.type;
var steps = this._returnSteps( typ );
var incSteps = [0];
for (var s = 1; s < steps.length+1; s++) {
var inc = 0;
for (var j = 0; j < s; j++) inc += steps[j];
incSteps.push( inc );
}
for (var i = 0; i < incSteps.length; i++) {
this.noteOff( O.freq(root, incSteps[i], tun), dec );
}
};
/**
* plays (starts and stops) a tone
* @method makeNote
* @param {object} config the first value can either be a config object or a note ( in either Hz or string format )
* @param {Number} velocity the gain value ( relative to the Tone's master valume ) of this particular note
* @param {Number} sustain the amount of time ( in seconds ) to sustain ( play ) the note
*
* @example
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var O = new BB.AudioTone({ volume: 0.5 });<br>
* <br>
* O.makeNote("A"); // plays a 440 Hz sine wave<br>
* O.makeNote( 444, 0.5, 3 ); // plays a 444 Hz sine wave at half volume for three seconds<br>
* O.makeNote({<br>
* frequency: 220,<br>
* velocity: 0.75, // 75% of 0.5 ( ie. O's master gain )<br>
* attack: 1, // fade in for 1 second<br>
* sustain: 2, // hold note for 2 more seconds<br>
* release: 2, // fade out for 2 more seconds<br>
* schedule: BB.Audio.context.currentTime + 5, // play 5 seconds from now<br>
* });<br>
* </code>
*/
BB.AudioTone.prototype.makeNote = function( config, velocity, sustain ){
var nStr = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
if( !config ) config = {};// instead of below? ...bad practice?
var freq, vel, dur, ack, dec, sch;
if ( typeof config === "object" ){
if(typeof config.frequency !== "undefined"){
freq = (typeof config.frequency === "number") ? config.frequency : this.note(config.frequency);
} else { freq = 440; }
vel = (typeof config.velocity !== "undefined") ? config.velocity : 1;
ack = (typeof config.attack !== "undefined") ? config.attack : 0;
dec = (typeof config.release !== "undefined") ? config.release : 0;
dur = (typeof config.sustain !== "undefined") ? config.sustain*1000 : 250;
sch = (typeof config.schedule !== "undefined") ? config.schedule : 0;
}
else if( typeof config === "number" || typeof config === "string" && nStr.indexOf(config) >= 0 ){
if(typeof config === "number") freq = config;
else freq = this.note(config);
vel = (typeof velocity === "number") ? velocity : 1;
dur = (typeof sustain === "number") ? sustain*1000 : 250;
ack = 0;
dec = 0;
sch = 0;
if(typeof velocity !=="undefined" && typeof velocity !== "number")
throw new Error('BB.AudioTone.makeNote: velocity (second param) should be a number');
if(typeof sustain !=="undefined" && typeof sustain !== "number")
throw new Error('BB.AudioTone.makeNote: sustain (third param) should be a number');
}
else {
throw new Error('BB.AudioTone.makeNote: first argument should either be a config object or a frequency (number) or a note, "A", "A#", etc.');
}
if( typeof this.polynotes[freq] !== "undefined")
if(typeof this.polynotes[freq].durTimeout !== "undefined")
clearTimeout( this.polynotes[freq].durTimeout );
this.noteOn( freq, vel, ack, true, sch );
var self = this;
this.polynotes[freq].durTimeout = setTimeout(function(){
self.noteOff(freq, dec);
}, dur );
};
/**
* plays (starts and stops) a chord ( based on "just" or "equal" temperments ) from any of the following chord types: maj,
* min, dim, 7, min7, maj7, sus4, 7sus4, 6, min6, aug, 7-5, 7+5, min7-5, min/maj7, maj7+5,
* maj7-5, 9, min9, maj9, 7+9, 7-9, 7+9-5, 6/9, 9+5, 9-5, min9-5, 11, min11, 11-9, 13, min13,
* maj13, add9, minadd9, sus2, 5,
*
* @method makeChord
* @param {object} config object with any of the optional properties listed in the example below
*
* @example
* <code class="code prettyprint">
* BB.Audio.init();<br>
* <br>
* var O = new BB.AudioTone({ volume: 0.5 });<br>
* <br>
* O.makeChord({<br>
* frequency: 220,<br>
* type: "min7", // plays a minor 7th chord<br>
* tuning: "just", // ...based on "just" (pure/harmonic) tuning rather the default eqaul tempered tuning<br>
* velocity: 0.75, // 75% of 0.5 ( ie. O's master gain )<br>
* attack: 1, // fade in for 1 second<br>
* sustain: 2, // hold note for 2 more seconds<br>
* release: 2, // fade out for 2 more seconds<br>
* schedule: BB.Audio.context.currentTime + 5, // play 5 seconds from now<br>
* });<br>
* </code>
*/
BB.AudioTone.prototype.makeChord = function( config ){
var nStr = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
if(typeof config !=="object") throw new Error('BB.AudioTone.makeChord: expecting a config object');
var root;
if(typeof config.frequency === "number") root = config.frequency;
else if(typeof config.frequency === "string" && nStr.indexOf(config.frequency) >= 0) root = this.note(config.frequency);
else throw new Error('BB.AudioTone.makeChord: frequency should either be a number or a note("A","A#",etc.)');
var vel = (typeof config.velocity !== "undefined") ? config.velocity : 1;
var ack = (typeof config.attack !== "undefined") ? config.attack : 0;
var dec = (typeof config.release !== "undefined") ? config.release : 0;
var dur = (typeof config.sustain !== "undefined") ? config.sustain : 0.25;
var typ = (typeof config.type !== "undefined") ? config.type : "maj";
var tun = (typeof config.tuning !== "undefined") ? config.tuning : "equal";
var sch = (typeof config.schedule !== "undefined") ? config.schedule : 0;
var steps = this._returnSteps( typ );
var incSteps = [0];
for (var s = 1; s < steps.length+1; s++) {
var inc = 0;
for (var j = 0; j < s; j++) inc += steps[j];
incSteps.push( inc );
}
for (var i = 0; i < incSteps.length; i++) {
this.makeNote({
frequency: O.freq(root, incSteps[i], tun),
velocity: vel,
attack: ack,
release: dec,
sustain: dur,
schedule: sch
});
}
};
return BB.AudioTone;
});