From f251999de6d855ca0c7e8cda7bd33719e598c687 Mon Sep 17 00:00:00 2001 From: Tyler G Date: Mon, 28 Mar 2016 14:30:21 -0400 Subject: [PATCH 1/5] add a filterNode to the audioNode chain, so the order to master output is now filterNode=>panNode=>gainNode. Added properties to store the filter's frequency and q factor. These properties can be directly set or get from the sound instance, in the same manner as pan --- src/soundjs/AbstractSoundInstance.js | 78 +++++++++++++++++++ src/soundjs/webaudio/WebAudioSoundInstance.js | 18 ++++- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/soundjs/AbstractSoundInstance.js b/src/soundjs/AbstractSoundInstance.js index b38311d2..d8c1a816 100644 --- a/src/soundjs/AbstractSoundInstance.js +++ b/src/soundjs/AbstractSoundInstance.js @@ -152,6 +152,32 @@ this.createjs = this.createjs || {}; set: this.setPan }); + /** + * The filter frequency of the sound, between 0 and half the sample rate. Note that filter is not supported by HTML Audio. + * + * @property filterFrequency + * @type {Number} + * @default 22050 + */ + this._filterFrequency = 22050; + Object.defineProperty(this, "filterFrequency", { + get: this.getFilterFrequency, + set: this.setFilterFrequency + }); + + /** + * The filter quality factor of the sound, between 0.0001 and 1000. Note that filter is not supported by HTML Audio. + * + * @property filterFrequency + * @type {Number} + * @default 1 + */ + this._filterQ = 1; + Object.defineProperty(this, "filterQ", { + get: this.getFilterQ, + set: this.setFilterQ + }); + /** * Audio sprite property used to determine the starting offset. * @property startTime @@ -529,6 +555,58 @@ this.createjs = this.createjs || {}; return this._pan; }; + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterFrequency:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setFilterFrequency + * @param {Number} value The filter frequency value, between 0 and half the sample rate. + * @return {AbstractSoundInstance} Returns reference to itself for chaining calls + */ + p.setFilterFrequency = function (value) { + if(value == this._filterFrequency) { return this; } + this._filterFrequency = value; + this._updateFilter(); + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterFrequency:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getFilterFrequency + * @return {Number} The value of the filter frequency, between 0 and half the sample rate. + */ + p.getFilterFrequency = function () { + return this._filterFrequency; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterQ:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setFilterQ + * @param {Number} value The filter quality factor, between 0.0001 and 1000. + * @return {AbstractSoundInstance} Returns reference to itself for chaining calls + */ + p.setFilterQ = function (value) { + if(value == this._filterQ) { return this; } + this._filterQ = value; + this._updateFilter(); + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterQ:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getFilterQ + * @return {Number} The value of the filter frequency, between 0.0001 and 1000. + */ + p.getFilterQ = function () { + return this._filterQ; + }; + /** * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} directly as a property * diff --git a/src/soundjs/webaudio/WebAudioSoundInstance.js b/src/soundjs/webaudio/WebAudioSoundInstance.js index 888564b2..cf6acef9 100644 --- a/src/soundjs/webaudio/WebAudioSoundInstance.js +++ b/src/soundjs/webaudio/WebAudioSoundInstance.js @@ -78,6 +78,16 @@ this.createjs = this.createjs || {}; this.panNode.connect(this.gainNode); this._updatePan(); + /** + * NOTE this is only intended for use by advanced users. + *
A filterNode allowing frequency filtering. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/panNode:property"}}{{/crossLink}}. + * @property filterNode + * @type {BiquadFilterNode} + * @since 0.6.? + */ + this.filterNode = s.context.createBiquadFilter(); + this.filterNode.connect(this.panNode); //filter node => pan node => gain node + /** * NOTE this is only intended for use by advanced users. *
sourceNode is the audio source. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/panNode:property"}}{{/crossLink}}. @@ -196,6 +206,11 @@ this.createjs = this.createjs || {}; // z need to be -0.5 otherwise the sound only plays in left, right, or center }; + p._updateFilter = function() { + this.filterNode.frequency.value = this._filterFrequency; + this.filterNode.Q.value = this._filterQ; + }; + p._removeLooping = function(value) { this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); }; @@ -271,7 +286,8 @@ this.createjs = this.createjs || {}; p._createAndPlayAudioNode = function(startTime, offset) { var audioNode = s.context.createBufferSource(); audioNode.buffer = this.playbackResource; - audioNode.connect(this.panNode); + //audioNode.connect(this.panNode); + audioNode.connect(this.filterNode); var dur = this._duration * 0.001; audioNode.startTime = startTime + dur; audioNode.start(audioNode.startTime, offset+(this._startTime*0.001), dur - offset); From 18d6accefa0c397c5adda6c05c791032d580789e Mon Sep 17 00:00:00 2001 From: Tyler G Date: Fri, 1 Apr 2016 14:09:54 -0400 Subject: [PATCH 2/5] add ability to change filterType on the filterNode. Defaults to lowpass. --- src/soundjs/AbstractSoundInstance.js | 48 +++++++++++++++++++ src/soundjs/webaudio/WebAudioSoundInstance.js | 1 + 2 files changed, 49 insertions(+) diff --git a/src/soundjs/AbstractSoundInstance.js b/src/soundjs/AbstractSoundInstance.js index d8c1a816..37266af7 100644 --- a/src/soundjs/AbstractSoundInstance.js +++ b/src/soundjs/AbstractSoundInstance.js @@ -178,6 +178,19 @@ this.createjs = this.createjs || {}; set: this.setFilterQ }); + /** + * The filter type, one of the following: "lowpass" "highpass" bandpass". Note that filter is not supported by HTML Audio. + * + * @property filterType + * @type {String} + * @default "lowpass" + */ + this._filterType = "lowpass"; + Object.defineProperty(this, "filterType", { + get: this.getFilterType, + set: this.setFilterType + }); + /** * Audio sprite property used to determine the starting offset. * @property startTime @@ -607,6 +620,41 @@ this.createjs = this.createjs || {}; return this._filterQ; }; + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterType:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setFilterType + * @param {String} The filter type, one of the following: "lowpass" "highpass" bandpass". + * @return {AbstractSoundInstance} Returns reference to itself for chaining calls + */ + p.setFilterType = function (value) { + if(value == this._filterType) { return this; } + + var acceptedTypes = { + "lowpass" : true, + "highpass" : true, + "bandpass" : true + }; + + if (typeof acceptedTypes[value] === 'undefined') { return false; } + + this._filterType = value; + this._updateFilter(); + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterType:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getFilterType + * @return {String} The filter type, one of the following: "lowpass" "highpass" bandpass". + */ + p.getFilterType = function () { + return this._filterType; + }; + /** * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} directly as a property * diff --git a/src/soundjs/webaudio/WebAudioSoundInstance.js b/src/soundjs/webaudio/WebAudioSoundInstance.js index cf6acef9..83cf41d8 100644 --- a/src/soundjs/webaudio/WebAudioSoundInstance.js +++ b/src/soundjs/webaudio/WebAudioSoundInstance.js @@ -209,6 +209,7 @@ this.createjs = this.createjs || {}; p._updateFilter = function() { this.filterNode.frequency.value = this._filterFrequency; this.filterNode.Q.value = this._filterQ; + this.filterNode.type = this._filterType; }; p._removeLooping = function(value) { From a0adca2840f02d21f8d2dd900c93f5b80dbbb00f Mon Sep 17 00:00:00 2001 From: Tyler G Date: Fri, 1 Apr 2016 14:27:02 -0400 Subject: [PATCH 3/5] add ability to change the filterNode's detune property --- src/soundjs/AbstractSoundInstance.js | 42 ++++++++++++++++++- src/soundjs/webaudio/WebAudioSoundInstance.js | 1 + 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/soundjs/AbstractSoundInstance.js b/src/soundjs/AbstractSoundInstance.js index 37266af7..136ef940 100644 --- a/src/soundjs/AbstractSoundInstance.js +++ b/src/soundjs/AbstractSoundInstance.js @@ -191,6 +191,19 @@ this.createjs = this.createjs || {}; set: this.setFilterType }); + /** + * The filter detune value in cents. Note that filter is not supported by HTML Audio. + * + * @property filterDetune + * @type {Number} + * @default 0 + */ + this._filterDetune = 0; + Object.defineProperty(this, "filterDetune", { + get: this.getFilterDetune, + set: this.setFilterDetune + }); + /** * Audio sprite property used to determine the starting offset. * @property startTime @@ -638,12 +651,39 @@ this.createjs = this.createjs || {}; }; if (typeof acceptedTypes[value] === 'undefined') { return false; } - + this._filterType = value; this._updateFilter(); return this; }; + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterDetune:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getFilterDetune + * @return {Number} The filter detune value in cents. + */ + p.getFilterDetune= function () { + return this._filterDetune; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterDetune:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setFilterDetune + * @param {Number} The filter detune value in cents. + * @return {AbstractSoundInstance} Returns reference to itself for chaining calls + */ + p.setFilterDetune = function (value) { + if(value == this._filterDetune) { return this; } + + this._filterDetune= value; + this._updateFilter(); + return this; + }; + /** * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterType:property"}}{{/crossLink}} directly as a property * diff --git a/src/soundjs/webaudio/WebAudioSoundInstance.js b/src/soundjs/webaudio/WebAudioSoundInstance.js index 83cf41d8..89d1fb73 100644 --- a/src/soundjs/webaudio/WebAudioSoundInstance.js +++ b/src/soundjs/webaudio/WebAudioSoundInstance.js @@ -210,6 +210,7 @@ this.createjs = this.createjs || {}; this.filterNode.frequency.value = this._filterFrequency; this.filterNode.Q.value = this._filterQ; this.filterNode.type = this._filterType; + this.filterNode.detune.value = this._filterDetune; }; p._removeLooping = function(value) { From 164eb1bdc89daef88af97d3ea9cdb40a79e57420 Mon Sep 17 00:00:00 2001 From: Tyler G Date: Fri, 1 Apr 2016 14:48:30 -0400 Subject: [PATCH 4/5] add a distortionNode to the chain. This comes before the filterNode so the order is now: distortionNode => filterNode => panNode => gainNode. The distortion amount can be changed by distortionAmount public property on the sound instance. Oversampling is hardcoded to 4x currently. --- src/soundjs/AbstractSoundInstance.js | 60 +++++++++++++++---- src/soundjs/webaudio/WebAudioSoundInstance.js | 33 +++++++++- 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/src/soundjs/AbstractSoundInstance.js b/src/soundjs/AbstractSoundInstance.js index 136ef940..a89bc95f 100644 --- a/src/soundjs/AbstractSoundInstance.js +++ b/src/soundjs/AbstractSoundInstance.js @@ -204,6 +204,19 @@ this.createjs = this.createjs || {}; set: this.setFilterDetune }); + /** + * The distortion value in amount. Note that distortion is not supported by HTML Audio. + * + * @property distortionAmount + * @type {Number} + * @default 0 + */ + this._distortionAmount = 0; + Object.defineProperty(this, "distortionAmount", { + get: this.getDistortionAmount, + set: this.setDistortionAmount + }); + /** * Audio sprite property used to determine the starting offset. * @property startTime @@ -658,14 +671,14 @@ this.createjs = this.createjs || {}; }; /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterDetune:property"}}{{/crossLink}} directly as a property + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterType:property"}}{{/crossLink}} directly as a property * * @deprecated - * @method getFilterDetune - * @return {Number} The filter detune value in cents. + * @method getFilterType + * @return {String} The filter type, one of the following: "lowpass" "highpass" bandpass". */ - p.getFilterDetune= function () { - return this._filterDetune; + p.getFilterType = function () { + return this._filterType; }; /** @@ -685,14 +698,41 @@ this.createjs = this.createjs || {}; }; /** - * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterType:property"}}{{/crossLink}} directly as a property + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/filterDetune:property"}}{{/crossLink}} directly as a property * * @deprecated - * @method getFilterType - * @return {String} The filter type, one of the following: "lowpass" "highpass" bandpass". + * @method getFilterDetune + * @return {Number} The filter detune value in cents. */ - p.getFilterType = function () { - return this._filterType; + p.getFilterDetune = function () { + return this._filterDetune; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/distortionAmount:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setDistortionAmount + * @param {Number} The distortion value in amount. + * @return {AbstractSoundInstance} Returns reference to itself for chaining calls + */ + p.setDistortionAmount = function (value) { + if(value == this._distortionAmount) { return this; } + + this._distortionAmount = value; + this._updateDistortion(); + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/distortionAmount:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getDistortionAmount + * @return {Number} The distortion value in amount. + */ + p.getDistortionAmount = function () { + return this._distortionAmount; }; /** diff --git a/src/soundjs/webaudio/WebAudioSoundInstance.js b/src/soundjs/webaudio/WebAudioSoundInstance.js index 89d1fb73..70b23ce1 100644 --- a/src/soundjs/webaudio/WebAudioSoundInstance.js +++ b/src/soundjs/webaudio/WebAudioSoundInstance.js @@ -88,6 +88,16 @@ this.createjs = this.createjs || {}; this.filterNode = s.context.createBiquadFilter(); this.filterNode.connect(this.panNode); //filter node => pan node => gain node + /** + * NOTE this is only intended for use by advanced users. + *
A waveShaperNode allowing application of disortion curves. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/filterNode:property"}}{{/crossLink}}. + * @property distortionNode + * @type {WaveShaperNode} + * @since 0.6.? + */ + this.distortionNode = s.context.createWaveShaper(); + this.distortionNode.connect(this.filterNode); //distortion node => filter node => pan node => gain node + /** * NOTE this is only intended for use by advanced users. *
sourceNode is the audio source. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/panNode:property"}}{{/crossLink}}. @@ -213,6 +223,27 @@ this.createjs = this.createjs || {}; this.filterNode.detune.value = this._filterDetune; }; + // distortion curve for the waveshaper, thanks to Kevin Ennis + // http://stackoverflow.com/questions/22312841/waveshaper-node-in-webaudio-how-to-emulate-distortion + p._makeDistortionCurve = function(amount) { + var k = typeof amount === 'number' ? amount : 50; + var n_samples = 44100; + var curve = new Float32Array(n_samples); + var deg = Math.PI / 180; + var i = 0; + var x; + for ( ; i < n_samples; ++i ) { + x = i * 2 / n_samples - 1; + curve[i] = ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) ); + } + return curve; + }; + + p._updateDistortion = function() { + this.distortionNode.oversample = '4x'; //pull this out into a config. Can be 'none', '2x' or '4x' + this.distortionNode.curve = this._makeDistortionCurve(this._distortionAmount); + }; + p._removeLooping = function(value) { this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); }; @@ -289,7 +320,7 @@ this.createjs = this.createjs || {}; var audioNode = s.context.createBufferSource(); audioNode.buffer = this.playbackResource; //audioNode.connect(this.panNode); - audioNode.connect(this.filterNode); + audioNode.connect(this.distortionNode); var dur = this._duration * 0.001; audioNode.startTime = startTime + dur; audioNode.start(audioNode.startTime, offset+(this._startTime*0.001), dur - offset); From 2d69b96af4a5cacb76d5f2846a951a33d5e9dd03 Mon Sep 17 00:00:00 2001 From: Tyler G Date: Mon, 4 Apr 2016 18:22:27 -0400 Subject: [PATCH 5/5] add a convolverNode to the chain, which is now distortion node => filter node => convolver node => pan node => gain node. Added ability to set/get the buffer of the convolverNode. This can be set with an AudioBuffer directly, or with a filepath to an impulse response. In the latter case, soundjs will read and decode the file automatically, and set the convolver buffer. Note currently there seems to be a bug where only stereo impulse responses will work. Mono impulse responses (or null buffer on the convolver node) will case the convolver node to output no audio. May need to find a way to bypass the convolver node completely if buffer is null or not set properly. Otherwise chain will to destination will break without proper care --- src/soundjs/AbstractSoundInstance.js | 52 +++++++++++++++++++ src/soundjs/webaudio/WebAudioSoundInstance.js | 43 +++++++++++++-- 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/soundjs/AbstractSoundInstance.js b/src/soundjs/AbstractSoundInstance.js index a89bc95f..b402b2e2 100644 --- a/src/soundjs/AbstractSoundInstance.js +++ b/src/soundjs/AbstractSoundInstance.js @@ -217,6 +217,20 @@ this.createjs = this.createjs || {}; set: this.setDistortionAmount }); + /** + * The convolver buffer to use on the convolver node. This can be an audioBuffer or a filepath, and will be handled appropriately depending on which. + * Note that convolver is not supported by HTML Audio. + * + * @property convolverBuffer + * @type {String} || {AudioBuffer} + * @default null + */ + this._convolverBuffer = null; + Object.defineProperty(this, "convolverBuffer", { + get: this.getConvolverBuffer, + set: this.setConvolverBuffer + }); + /** * Audio sprite property used to determine the starting offset. * @property startTime @@ -735,6 +749,44 @@ this.createjs = this.createjs || {}; return this._distortionAmount; }; + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/convolverBuffer:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method setConvolverBuffer + * @param {String} The filepath to an impulse response WAV || {AudioBuffer} The audio buffer to use as the convoler's buffer. + * @return {AbstractSoundInstance} Returns reference to itself for chaining calls + */ + p.setConvolverBuffer = function (buffer) { + if (typeof buffer === 'string') { + //if a filepath is passed, must import it as an arraybuffer and decode + this._getConvolverBufferFromFilepath(buffer); + } + else { + if (typeof AudioBuffer !== 'undefined' && buffer instanceof AudioBuffer) { + //audio buffer is passed, set it directly + this._convolverBuffer = buffer; + this._updateConvolver(); + } + else { + return false; + } + } + return this; + }; + + /** + * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/convolverBuffer:property"}}{{/crossLink}} directly as a property + * + * @deprecated + * @method getConvolverBuffer + * @return {AudioBuffer} The audio buffer being used as the convolver's buffer. + */ + p.getConvolverBuffer = function () { + return this._convolverBuffer; + }; + + /** * DEPRECATED, please use {{#crossLink "AbstractSoundInstance/position:property"}}{{/crossLink}} directly as a property * diff --git a/src/soundjs/webaudio/WebAudioSoundInstance.js b/src/soundjs/webaudio/WebAudioSoundInstance.js index 70b23ce1..042e6657 100644 --- a/src/soundjs/webaudio/WebAudioSoundInstance.js +++ b/src/soundjs/webaudio/WebAudioSoundInstance.js @@ -80,13 +80,24 @@ this.createjs = this.createjs || {}; /** * NOTE this is only intended for use by advanced users. - *
A filterNode allowing frequency filtering. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/panNode:property"}}{{/crossLink}}. + *
A ConvolverNode allowing application of convolver buffers (e.g. reverb) Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/panNode:property"}}{{/crossLink}}. + * @property convolverNode + * @type { ConvolverNode} + * @since 0.6.? + */ + this.convolverNode = s.context.createConvolver(); + this.convolverNode.buffer = this.testBuffer; + this.convolverNode.connect(this.panNode); //convolver node => pan node => gain node + + /** + * NOTE this is only intended for use by advanced users. + *
A filterNode allowing frequency filtering. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/convolverNode:property"}}{{/crossLink}}. * @property filterNode * @type {BiquadFilterNode} * @since 0.6.? */ this.filterNode = s.context.createBiquadFilter(); - this.filterNode.connect(this.panNode); //filter node => pan node => gain node + this.filterNode.connect(this.convolverNode); //filter node => convolver node => pan node => gain node /** * NOTE this is only intended for use by advanced users. @@ -96,7 +107,7 @@ this.createjs = this.createjs || {}; * @since 0.6.? */ this.distortionNode = s.context.createWaveShaper(); - this.distortionNode.connect(this.filterNode); //distortion node => filter node => pan node => gain node + this.distortionNode.connect(this.filterNode); //distortion node => filter node => convolver node => pan node => gain node /** * NOTE this is only intended for use by advanced users. @@ -244,6 +255,27 @@ this.createjs = this.createjs || {}; this.distortionNode.curve = this._makeDistortionCurve(this._distortionAmount); }; + p._updateConvolver = function() { + this.convolverNode.buffer = this._convolverBuffer; + }; + + p._getConvolverBufferFromFilepath = function(filepath) { + // grab audio track via XHR for convolver node + var req = new XMLHttpRequest(); + req.open('GET', filepath, true); + req.responseType = 'arraybuffer'; + + var self = this; //save this reference + req.onload = function() { + var audioData = req.response; + s.context.decodeAudioData(audioData, function(buffer) { + self._convolverBuffer = buffer; + self._updateConvolver(); + }, function(e){"Error with decoding audio data" + e.err}); + } + req.send(); + }; + p._removeLooping = function(value) { this._sourceNodeNext = this._cleanUpAudioNode(this._sourceNodeNext); }; @@ -317,10 +349,13 @@ this.createjs = this.createjs || {}; * @since 0.4.1 */ p._createAndPlayAudioNode = function(startTime, offset) { + console.log('createandplayaudionode'); var audioNode = s.context.createBufferSource(); audioNode.buffer = this.playbackResource; - //audioNode.connect(this.panNode); audioNode.connect(this.distortionNode); + //audioNode.connect(this.convolverNode); + console.log('connected to convolverNode'); + var dur = this._duration * 0.001; audioNode.startTime = startTime + dur; audioNode.start(audioNode.startTime, offset+(this._startTime*0.001), dur - offset);