From eb003d8b6ccc2fae3e30651ccfc2fde8827c2321 Mon Sep 17 00:00:00 2001 From: tambien Date: Tue, 5 Jun 2018 22:14:46 -0400 Subject: [PATCH] follower uses a single smoothing value --- CHANGELOG.md | 1 + Tone/component/Follower.js | 113 ++++++++----------------------------- test/component/Follower.js | 104 ++++++++++++++++++++-------------- 3 files changed, 87 insertions(+), 131 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa61bd72..0ab6974e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Tone/component/Follower.js b/Tone/component/Follower.js index bcce1e9d..83861c97 100644 --- a/Tone/component/Follower.js +++ b/Tone/component/Follower.js @@ -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; }; diff --git a/test/component/Follower.js b/test/component/Follower.js index def42ad5..6f4ae765 100644 --- a/test/component/Follower.js +++ b/test/component/Follower.js @@ -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;