1 | import { Effect, EffectOptions } from "./Effect.js";
|
2 | import { Filter } from "../component/filter/Filter.js";
|
3 | import { Follower } from "../component/analysis/Follower.js";
|
4 | import {
|
5 | Decibels,
|
6 | Frequency,
|
7 | GainFactor,
|
8 | Hertz,
|
9 | Positive,
|
10 | Time,
|
11 | } from "../core/type/Units.js";
|
12 | import { optionsFromArguments } from "../core/util/Defaults.js";
|
13 | import { Gain } from "../core/context/Gain.js";
|
14 | import { dbToGain, gainToDb } from "../core/type/Conversions.js";
|
15 | import { ScaleExp } from "../signal/ScaleExp.js";
|
16 | import { Signal } from "../signal/Signal.js";
|
17 | import { readOnly } from "../core/util/Interface.js";
|
18 |
|
19 | export 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 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | export class AutoWah extends Effect<AutoWahOptions> {
|
44 | readonly name: string = "AutoWah";
|
45 |
|
46 | |
47 |
|
48 |
|
49 |
|
50 | private _follower: Follower;
|
51 |
|
52 | |
53 |
|
54 |
|
55 | private _sweepRange: ScaleExp;
|
56 |
|
57 | |
58 |
|
59 |
|
60 | private _baseFrequency: Hertz;
|
61 |
|
62 | |
63 |
|
64 |
|
65 | private _octaves: Positive;
|
66 |
|
67 | |
68 |
|
69 |
|
70 | private _inputBoost: Gain;
|
71 |
|
72 | |
73 |
|
74 |
|
75 | private _bandpass: Filter;
|
76 |
|
77 | |
78 |
|
79 |
|
80 | private _peaking: Filter;
|
81 |
|
82 | |
83 |
|
84 |
|
85 | readonly gain: Signal<"decibels">;
|
86 |
|
87 | |
88 |
|
89 |
|
90 | readonly Q: Signal<"positive">;
|
91 |
|
92 | |
93 |
|
94 |
|
95 |
|
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 |
|
179 |
|
180 | get follower(): Time {
|
181 | return this._follower.smoothing;
|
182 | }
|
183 | set follower(follower) {
|
184 | this._follower.smoothing = follower;
|
185 | }
|
186 |
|
187 | |
188 |
|
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 |
|
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 |
|
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 | }
|