1 | import { StereoEffect, StereoEffectOptions } from "./StereoEffect.js";
|
2 | import { Frequency, Hertz, Positive } from "../core/type/Units.js";
|
3 | import { optionsFromArguments } from "../core/util/Defaults.js";
|
4 | import { LFO } from "../source/oscillator/LFO.js";
|
5 | import { Signal } from "../signal/Signal.js";
|
6 | import { readOnly } from "../core/util/Interface.js";
|
7 |
|
8 | export interface PhaserOptions extends StereoEffectOptions {
|
9 | frequency: Frequency;
|
10 | octaves: Positive;
|
11 | stages: Positive;
|
12 | Q: Positive;
|
13 | baseFrequency: Frequency;
|
14 | }
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 | export class Phaser extends StereoEffect<PhaserOptions> {
|
32 | readonly name: string = "Phaser";
|
33 |
|
34 | |
35 |
|
36 |
|
37 | private _lfoL: LFO;
|
38 |
|
39 | |
40 |
|
41 |
|
42 | private _lfoR: LFO;
|
43 |
|
44 | |
45 |
|
46 |
|
47 | private _baseFrequency: Hertz;
|
48 |
|
49 | |
50 |
|
51 |
|
52 | private _octaves: Positive;
|
53 |
|
54 | |
55 |
|
56 |
|
57 | readonly Q: Signal<"positive">;
|
58 |
|
59 | |
60 |
|
61 |
|
62 | private _filtersL: BiquadFilterNode[];
|
63 |
|
64 | |
65 |
|
66 |
|
67 | private _filtersR: BiquadFilterNode[];
|
68 |
|
69 | |
70 |
|
71 |
|
72 | readonly frequency: Signal<"frequency">;
|
73 |
|
74 | |
75 |
|
76 |
|
77 |
|
78 |
|
79 | constructor(
|
80 | frequency?: Frequency,
|
81 | octaves?: Positive,
|
82 | baseFrequency?: Frequency
|
83 | );
|
84 | constructor(options?: Partial<PhaserOptions>);
|
85 | constructor() {
|
86 | const options = optionsFromArguments(Phaser.getDefaults(), arguments, [
|
87 | "frequency",
|
88 | "octaves",
|
89 | "baseFrequency",
|
90 | ]);
|
91 | super(options);
|
92 |
|
93 | this._lfoL = new LFO({
|
94 | context: this.context,
|
95 | frequency: options.frequency,
|
96 | min: 0,
|
97 | max: 1,
|
98 | });
|
99 | this._lfoR = new LFO({
|
100 | context: this.context,
|
101 | frequency: options.frequency,
|
102 | min: 0,
|
103 | max: 1,
|
104 | phase: 180,
|
105 | });
|
106 | this._baseFrequency = this.toFrequency(options.baseFrequency);
|
107 | this._octaves = options.octaves;
|
108 | this.Q = new Signal({
|
109 | context: this.context,
|
110 | value: options.Q,
|
111 | units: "positive",
|
112 | });
|
113 | this._filtersL = this._makeFilters(options.stages, this._lfoL);
|
114 | this._filtersR = this._makeFilters(options.stages, this._lfoR);
|
115 |
|
116 | this.frequency = this._lfoL.frequency;
|
117 | this.frequency.value = options.frequency;
|
118 |
|
119 | // connect them up
|
120 | this.connectEffectLeft(...this._filtersL);
|
121 | this.connectEffectRight(...this._filtersR);
|
122 | // control the frequency with one LFO
|
123 | this._lfoL.frequency.connect(this._lfoR.frequency);
|
124 | // set the options
|
125 | this.baseFrequency = options.baseFrequency;
|
126 | this.octaves = options.octaves;
|
127 | // start the lfo
|
128 | this._lfoL.start();
|
129 | this._lfoR.start();
|
130 | readOnly(this, ["frequency", "Q"]);
|
131 | }
|
132 |
|
133 | static getDefaults(): PhaserOptions {
|
134 | return Object.assign(StereoEffect.getDefaults(), {
|
135 | frequency: 0.5,
|
136 | octaves: 3,
|
137 | stages: 10,
|
138 | Q: 10,
|
139 | baseFrequency: 350,
|
140 | });
|
141 | }
|
142 |
|
143 | private _makeFilters(
|
144 | stages: number,
|
145 | connectToFreq: LFO
|
146 | ): BiquadFilterNode[] {
|
147 | const filters: BiquadFilterNode[] = [];
|
148 |
|
149 | for (let i = 0; i < stages; i++) {
|
150 | const filter = this.context.createBiquadFilter();
|
151 | filter.type = "allpass";
|
152 | this.Q.connect(filter.Q);
|
153 | connectToFreq.connect(filter.frequency);
|
154 | filters.push(filter);
|
155 | }
|
156 | return filters;
|
157 | }
|
158 |
|
159 | |
160 |
|
161 |
|
162 | get octaves() {
|
163 | return this._octaves;
|
164 | }
|
165 | set octaves(octaves) {
|
166 | this._octaves = octaves;
|
167 | const max = this._baseFrequency * Math.pow(2, octaves);
|
168 | this._lfoL.max = max;
|
169 | this._lfoR.max = max;
|
170 | }
|
171 |
|
172 | |
173 |
|
174 |
|
175 | get baseFrequency(): Frequency {
|
176 | return this._baseFrequency;
|
177 | }
|
178 | set baseFrequency(freq) {
|
179 | this._baseFrequency = this.toFrequency(freq);
|
180 | this._lfoL.min = this._baseFrequency;
|
181 | this._lfoR.min = this._baseFrequency;
|
182 | this.octaves = this._octaves;
|
183 | }
|
184 |
|
185 | dispose(): this {
|
186 | super.dispose();
|
187 | this.Q.dispose();
|
188 | this._lfoL.dispose();
|
189 | this._lfoR.dispose();
|
190 | this._filtersL.forEach((f) => f.disconnect());
|
191 | this._filtersR.forEach((f) => f.disconnect());
|
192 | this.frequency.dispose();
|
193 | return this;
|
194 | }
|
195 | }
|