follower uses a single smoothing value

This commit is contained in:
tambien 2018-06-05 22:14:46 -04:00
parent b1ae21930f
commit eb003d8b6c
3 changed files with 87 additions and 131 deletions

View file

@ -7,6 +7,7 @@
* Tone.Meter uses RMS instead of peak (thanks [@Idicious](https://github.com/Idicious))
* Tone.Sampler supports polyphonic syntax (thanks [@zfan40](https://github.com/zfan40))
* Building files with [webpack](https://webpack.js.org/)
* Follower uses a single "smoothing" value instead of separate attacks and releases
### r12

View file

@ -1,26 +1,22 @@
define(["Tone/core/Tone", "Tone/signal/Abs", "Tone/signal/Subtract", "Tone/signal/Multiply",
"Tone/signal/Signal", "Tone/signal/WaveShaper", "Tone/type/Type", "Tone/core/Delay", "Tone/core/AudioNode"], function(Tone){
define(["Tone/core/Tone", "Tone/signal/Abs", "Tone/signal/Subtract",
"Tone/signal/Signal", "Tone/type/Type", "Tone/core/Delay", "Tone/core/AudioNode"], function(Tone){
"use strict";
/**
* @class Tone.Follower is a crude envelope follower which will follow
* the amplitude of an incoming signal.
* Take care with small (< 0.02) attack or decay values
* as follower has some ripple which is exaggerated
* at these values. Read more about envelope followers (also known
* the amplitude of an incoming signal. Read more about envelope followers (also known
* as envelope detectors) on [Wikipedia](https://en.wikipedia.org/wiki/Envelope_detector).
*
* @constructor
* @extends {Tone.AudioNode}
* @param {Time|Object} [attack] The rate at which the follower rises.
* @param {Time=} release The rate at which the folower falls.
* @param {Time} [smoothing=0.05] The rate of change of the follower.
* @example
* var follower = new Tone.Follower(0.2, 0.4);
* var follower = new Tone.Follower(0.3);
*/
Tone.Follower = function(){
var options = Tone.defaults(arguments, ["attack", "release"], Tone.Follower);
var options = Tone.defaults(arguments, ["smoothing"], Tone.Follower);
Tone.AudioNode.call(this);
this.createInsOuts(1, 1);
@ -38,13 +34,7 @@ define(["Tone/core/Tone", "Tone/signal/Abs", "Tone/signal/Subtract", "Tone/signa
this._filter = this.context.createBiquadFilter();
this._filter.type = "lowpass";
this._filter.frequency.value = 0;
this._filter.Q.value = -100;
/**
* @type {WaveShaperNode}
* @private
*/
this._frequencyValues = new Tone.WaveShaper();
this._filter.Q.value = 0;
/**
* @type {Tone.Subtract}
@ -53,39 +43,25 @@ define(["Tone/core/Tone", "Tone/signal/Abs", "Tone/signal/Subtract", "Tone/signa
this._sub = new Tone.Subtract();
/**
* delay node to compare change over time
* @type {Tone.Delay}
* @private
*/
this._delay = new Tone.Delay(this.blockTime);
/**
* this keeps it far from 0, even for very small differences
* @type {Tone.Multiply}
* the smoothing value
* @private
* @type {Number}
*/
this._mult = new Tone.Multiply(10000);
this._smoothing = options.smoothing;
/**
* @private
* @type {number}
*/
this._attack = options.attack;
this.input.connect(this._delay, this._sub);
this.input.connect(this._sub, 0, 1);
this._sub.chain(this._filter, this._abs, this.output);
/**
* @private
* @type {number}
*/
this._release = options.release;
//the smoothed signal to get the values
this.input.chain(this._abs, this._filter, this.output);
//the difference path
this._abs.connect(this._sub, 0, 1);
this._filter.chain(this._delay, this._sub);
//threshold the difference and use the thresh to set the frequency
this._sub.chain(this._mult, this._frequencyValues, this._filter.frequency);
//set the attack and release values in the table
this._setAttackRelease(this._attack, this._release);
//set the smoothing initially
this.smoothing = options.smoothing;
};
Tone.extend(Tone.Follower, Tone.AudioNode);
@ -95,60 +71,22 @@ define(["Tone/core/Tone", "Tone/signal/Abs", "Tone/signal/Subtract", "Tone/signa
* @type {Object}
*/
Tone.Follower.defaults = {
"attack" : 0.05,
"release" : 0.5
};
/**
* sets the attack and release times in the wave shaper
* @param {Time} attack
* @param {Time} release
* @private
*/
Tone.Follower.prototype._setAttackRelease = function(attack, release){
var minTime = this.blockTime;
attack = Tone.Time(attack).toFrequency();
release = Tone.Time(release).toFrequency();
attack = Math.max(attack, minTime);
release = Math.max(release, minTime);
this._frequencyValues.setMap(function(val){
if (val <= 0){
return attack;
} else {
return release;
}
});
"smoothing" : 0.05,
};
/**
* The attack time.
* @memberOf Tone.Follower#
* @type {Time}
* @name attack
* @name smoothing
*/
Object.defineProperty(Tone.Follower.prototype, "attack", {
Object.defineProperty(Tone.Follower.prototype, "smoothing", {
get : function(){
return this._attack;
return this._smoothing;
},
set : function(attack){
this._attack = attack;
this._setAttackRelease(this._attack, this._release);
}
});
/**
* The release time.
* @memberOf Tone.Follower#
* @type {Time}
* @name release
*/
Object.defineProperty(Tone.Follower.prototype, "release", {
get : function(){
return this._release;
},
set : function(release){
this._release = release;
this._setAttackRelease(this._attack, this._release);
set : function(smoothing){
this._smoothing = smoothing;
this._filter.frequency.value = Tone.Time(smoothing).toFrequency() * 0.5;
}
});
@ -167,17 +105,12 @@ define(["Tone/core/Tone", "Tone/signal/Abs", "Tone/signal/Subtract", "Tone/signa
Tone.AudioNode.prototype.dispose.call(this);
this._filter.disconnect();
this._filter = null;
this._frequencyValues.disconnect();
this._frequencyValues = null;
this._delay.dispose();
this._delay = null;
this._sub.disconnect();
this._sub = null;
this._abs.dispose();
this._abs = null;
this._mult.dispose();
this._mult = null;
this._curve = null;
return this;
};

View file

@ -17,69 +17,91 @@ function(Follower, Basic, Offline, Test, Signal, PassAudio, PassAudioStereo){
it("handles getter/setter as Object", function(){
var foll = new Follower();
var values = {
"attack" : 0.2,
"release" : 0.4
"smoothing" : 0.2,
};
foll.set(values);
expect(foll.get()).to.have.keys(["attack", "release"]);
expect(foll.get().attack).to.be.closeTo(0.2, 0.001);
expect(foll.get().release).to.be.closeTo(0.4, 0.001);
expect(foll.get()).to.have.keys(["smoothing"]);
expect(foll.get().smoothing).to.be.closeTo(0.2, 0.001);
foll.dispose();
});
it("can be constructed with an object", function(){
var follower = new Follower({
"attack" : 0.5,
"release" : 0.3
"smoothing" : 0.5,
});
expect(follower.attack).to.be.closeTo(0.5, 0.001);
expect(follower.release).to.be.closeTo(0.3, 0.001);
expect(follower.smoothing).to.be.closeTo(0.5, 0.001);
follower.dispose();
});
it("smoothes the incoming signal", function(){
it("smoothes the incoming signal at 0.1", function(){
return Offline(function(){
var foll = new Follower(0.1, 0.5).toMaster();
var foll = new Follower(0.1).toMaster();
var sig = new Signal(0);
sig.connect(foll);
sig.setValueAtTime(1, 0.1);
}, 0.1).then(function(buffer){
expect(buffer.max()).to.lessThan(1);
sig.setValueAtTime(0, 0.3);
}, 0.41).then(function(buffer){
expect(buffer.getValueAtTime(0)).to.be.closeTo(0, 0.01);
expect(buffer.getValueAtTime(0.1)).to.be.closeTo(0.0, 0.01);
expect(buffer.getValueAtTime(0.15)).to.be.closeTo(0.5, 0.2);
expect(buffer.getValueAtTime(0.2)).to.be.closeTo(1, 0.2);
expect(buffer.getValueAtTime(0.3)).to.be.closeTo(1, 0.2);
expect(buffer.getValueAtTime(0.35)).to.be.closeTo(0.5, 0.2);
expect(buffer.getValueAtTime(0.4)).to.be.closeTo(0, 0.2);
});
});
/*it("smoothing follows attack and release", function(done){
var foll, sig;
var offline = new Offline(1);
offline.before(function(dest){
foll = new Follower(0.1, 0.5);
sig = new Signal(0);
it("smoothes the incoming signal at 0.05", function(){
return Offline(function(){
var foll = new Follower(0.05).toMaster();
var sig = new Signal(0);
sig.connect(foll);
foll.connect(dest);
sig.setValueAtTime(1, 0);
sig.setValueAtTime(0, 0.4);
sig.setValueAtTime(1, 0.1);
sig.setValueAtTime(0, 0.3);
}, 0.41).then(function(buffer){
expect(buffer.getValueAtTime(0)).to.be.closeTo(0, 0.01);
expect(buffer.getValueAtTime(0.1)).to.be.closeTo(0.0, 0.01);
expect(buffer.getValueAtTime(0.125)).to.be.closeTo(0.5, 0.2);
expect(buffer.getValueAtTime(0.15)).to.be.closeTo(1, 0.2);
expect(buffer.getValueAtTime(0.3)).to.be.closeTo(1, 0.2);
expect(buffer.getValueAtTime(0.325)).to.be.closeTo(0.5, 0.2);
expect(buffer.getValueAtTime(0.35)).to.be.closeTo(0, 0.2);
});
var delta = 0.15;
offline.test(function(sample, time){
if (time < 0.1){
expect(sample).to.be.within(0 - delta, 1 + delta);
} else if (time < 0.4){
expect(sample).to.be.closeTo(1, delta);
} else if (time < 0.65){
expect(sample).to.be.above(0);
} else if (time < 0.9){
expect(sample).to.be.within(0 - delta, 1 + delta);
} else {
expect(sample).to.be.closeTo(0, delta);
}
});
it("smoothes the incoming signal at 0.2", function(){
return Offline(function(){
var foll = new Follower(0.2).toMaster();
var sig = new Signal(0);
sig.connect(foll);
sig.setValueAtTime(1, 0.1);
sig.setValueAtTime(0, 0.3);
}, 0.51).then(function(buffer){
expect(buffer.getValueAtTime(0)).to.be.closeTo(0, 0.01);
expect(buffer.getValueAtTime(0.1)).to.be.closeTo(0.0, 0.01);
expect(buffer.getValueAtTime(0.2)).to.be.closeTo(0.5, 0.2);
expect(buffer.getValueAtTime(0.3)).to.be.closeTo(1, 0.2);
expect(buffer.getValueAtTime(0.4)).to.be.closeTo(0.5, 0.2);
expect(buffer.getValueAtTime(0.5)).to.be.closeTo(0, 0.2);
});
offline.after(function(){
foll.dispose();
sig.dispose();
done();
});
it("smoothes the incoming signal at 0.5", function(){
return Offline(function(){
var foll = new Follower(0.5).toMaster();
var sig = new Signal(0);
sig.connect(foll);
sig.setValueAtTime(1, 0.1);
sig.setValueAtTime(0, 0.6);
}, 1.11).then(function(buffer){
expect(buffer.getValueAtTime(0)).to.be.closeTo(0, 0.01);
expect(buffer.getValueAtTime(0.1)).to.be.closeTo(0.0, 0.01);
expect(buffer.getValueAtTime(0.35)).to.be.closeTo(0.5, 0.2);
expect(buffer.getValueAtTime(0.6)).to.be.closeTo(1, 0.2);
expect(buffer.getValueAtTime(0.85)).to.be.closeTo(0.5, 0.2);
expect(buffer.getValueAtTime(1.1)).to.be.closeTo(0, 0.2);
});
offline.run();
});*/
});
it("passes the incoming signal through", function(){
var follower;