UNPKG

5.51 kBPlain TextView Raw
1import { Effect, EffectOptions } from "./Effect.js";
2import { Filter } from "../component/filter/Filter.js";
3import { Follower } from "../component/analysis/Follower.js";
4import {
5 Decibels,
6 Frequency,
7 GainFactor,
8 Hertz,
9 Positive,
10 Time,
11} from "../core/type/Units.js";
12import { optionsFromArguments } from "../core/util/Defaults.js";
13import { Gain } from "../core/context/Gain.js";
14import { dbToGain, gainToDb } from "../core/type/Conversions.js";
15import { ScaleExp } from "../signal/ScaleExp.js";
16import { Signal } from "../signal/Signal.js";
17import { readOnly } from "../core/util/Interface.js";
18
19export interface AutoWahOptions extends EffectOptions {
20 baseFrequency: Frequency;
21 octaves: Positive;
22 sensitivity: Decibels;
23 Q: Positive;
24 gain: GainFactor;
25 follower: Time;
26}
27
28/**
29 * AutoWah connects a {@link Follower} to a {@link Filter}.
30 * The frequency of the filter, follows the input amplitude curve.
31 * Inspiration from [Tuna.js](https://github.com/Dinahmoe/tuna).
32 *
33 * @example
34 * const autoWah = new Tone.AutoWah(50, 6, -30).toDestination();
35 * // initialize the synth and connect to autowah
36 * const synth = new Tone.Synth().connect(autoWah);
37 * // Q value influences the effect of the wah - default is 2
38 * autoWah.Q.value = 6;
39 * // more audible on higher notes
40 * synth.triggerAttackRelease("C4", "8n");
41 * @category Effect
42 */
43export class AutoWah extends Effect<AutoWahOptions> {
44 readonly name: string = "AutoWah";
45
46 /**
47 * The envelope follower. Set the attack/release
48 * timing to adjust how the envelope is followed.
49 */
50 private _follower: Follower;
51
52 /**
53 * scales the follower value to the frequency domain
54 */
55 private _sweepRange: ScaleExp;
56
57 /**
58 * Hold the base frequency value
59 */
60 private _baseFrequency: Hertz;
61
62 /**
63 * Private holder for the octave count
64 */
65 private _octaves: Positive;
66
67 /**
68 * the input gain to adjust the sensitivity
69 */
70 private _inputBoost: Gain;
71
72 /**
73 * Private holder for the filter
74 */
75 private _bandpass: Filter;
76
77 /**
78 * The peaking fitler
79 */
80 private _peaking: Filter;
81
82 /**
83 * The gain of the filter.
84 */
85 readonly gain: Signal<"decibels">;
86
87 /**
88 * The quality of the filter.
89 */
90 readonly Q: Signal<"positive">;
91
92 /**
93 * @param baseFrequency The frequency the filter is set to at the low point of the wah
94 * @param octaves The number of octaves above the baseFrequency the filter will sweep to when fully open.
95 * @param sensitivity The decibel threshold sensitivity for the incoming signal. Normal range of -40 to 0.
96 */
97 constructor(
98 baseFrequency?: Frequency,
99 octaves?: Positive,
100 sensitivity?: Decibels
101 );
102 constructor(options?: Partial<AutoWahOptions>);
103 constructor() {
104 const options = optionsFromArguments(AutoWah.getDefaults(), arguments, [
105 "baseFrequency",
106 "octaves",
107 "sensitivity",
108 ]);
109 super(options);
110
111 this._follower = new Follower({
112 context: this.context,
113 smoothing: options.follower,
114 });
115 this._sweepRange = new ScaleExp({
116 context: this.context,
117 min: 0,
118 max: 1,
119 exponent: 0.5,
120 });
121 this._baseFrequency = this.toFrequency(options.baseFrequency);
122 this._octaves = options.octaves;
123 this._inputBoost = new Gain({ context: this.context });
124 this._bandpass = new Filter({
125 context: this.context,
126 rolloff: -48,
127 frequency: 0,
128 Q: options.Q,
129 });
130 this._peaking = new Filter({
131 context: this.context,
132 type: "peaking",
133 });
134 this._peaking.gain.value = options.gain;
135 this.gain = this._peaking.gain;
136 this.Q = this._bandpass.Q;
137
138 // the control signal path
139 this.effectSend.chain(
140 this._inputBoost,
141 this._follower,
142 this._sweepRange
143 );
144 this._sweepRange.connect(this._bandpass.frequency);
145 this._sweepRange.connect(this._peaking.frequency);
146 // the filtered path
147 this.effectSend.chain(this._bandpass, this._peaking, this.effectReturn);
148 // set the initial value
149 this._setSweepRange();
150 this.sensitivity = options.sensitivity;
151
152 readOnly(this, ["gain", "Q"]);
153 }
154
155 static getDefaults(): AutoWahOptions {
156 return Object.assign(Effect.getDefaults(), {
157 baseFrequency: 100,
158 octaves: 6,
159 sensitivity: 0,
160 Q: 2,
161 gain: 2,
162 follower: 0.2,
163 });
164 }
165
166 /**
167 * The number of octaves that the filter will sweep above the baseFrequency.
168 */
169 get octaves() {
170 return this._octaves;
171 }
172 set octaves(octaves) {
173 this._octaves = octaves;
174 this._setSweepRange();
175 }
176
177 /**
178 * The follower's smoothing time
179 */
180 get follower(): Time {
181 return this._follower.smoothing;
182 }
183 set follower(follower) {
184 this._follower.smoothing = follower;
185 }
186
187 /**
188 * The base frequency from which the sweep will start from.
189 */
190 get baseFrequency(): Frequency {
191 return this._baseFrequency;
192 }
193 set baseFrequency(baseFreq) {
194 this._baseFrequency = this.toFrequency(baseFreq);
195 this._setSweepRange();
196 }
197
198 /**
199 * The sensitivity to control how responsive to the input signal the filter is.
200 */
201 get sensitivity(): Decibels {
202 return gainToDb(1 / this._inputBoost.gain.value);
203 }
204 set sensitivity(sensitivity) {
205 this._inputBoost.gain.value = 1 / dbToGain(sensitivity);
206 }
207
208 /**
209 * sets the sweep range of the scaler
210 */
211 private _setSweepRange() {
212 this._sweepRange.min = this._baseFrequency;
213 this._sweepRange.max = Math.min(
214 this._baseFrequency * Math.pow(2, this._octaves),
215 this.context.sampleRate / 2
216 );
217 }
218
219 dispose(): this {
220 super.dispose();
221 this._follower.dispose();
222 this._sweepRange.dispose();
223 this._bandpass.dispose();
224 this._peaking.dispose();
225 this._inputBoost.dispose();
226 return this;
227 }
228}