Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposed new features: filtering, distortion and convolution (reverb) #238

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 258 additions & 0 deletions src/soundjs/AbstractSoundInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,85 @@ 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
});

/**
* 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
});

/**
* 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
});

/**
* 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
});

/**
* 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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<script src="https://gist.github.com/millsymel/fd110ff692f85b30acd2364bc56c4aab.js"></script>

*/
this._convolverBuffer = null;
Object.defineProperty(this, "convolverBuffer", {
get: this.getConvolverBuffer,
set: this.setConvolverBuffer
});

/**
* Audio sprite property used to determine the starting offset.
* @property startTime
Expand Down Expand Up @@ -529,6 +608,185 @@ 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/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/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/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/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;
};

/**
* 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
*
Expand Down
86 changes: 85 additions & 1 deletion src/soundjs/webaudio/WebAudioSoundInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,37 @@ this.createjs = this.createjs || {};
this.panNode.connect(this.gainNode);
this._updatePan();

/**
* NOTE this is only intended for use by advanced users.
* <br />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.
* <br />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.convolverNode); //filter node => convolver node => pan node => gain node

/**
* NOTE this is only intended for use by advanced users.
* <br />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 => convolver node => pan node => gain node

/**
* NOTE this is only intended for use by advanced users.
* <br />sourceNode is the audio source. Connected to WebAudioSoundInstance {{#crossLink "WebAudioSoundInstance/panNode:property"}}{{/crossLink}}.
Expand Down Expand Up @@ -196,6 +227,55 @@ 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;
this.filterNode.type = this._filterType;
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._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);
};
Expand Down Expand Up @@ -269,9 +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);
Expand Down