UNPKG

4.81 kBPlain TextView Raw
1import { StereoEffect, StereoEffectOptions } from "./StereoEffect.js";
2import { Frequency, Hertz, Positive } from "../core/type/Units.js";
3import { optionsFromArguments } from "../core/util/Defaults.js";
4import { LFO } from "../source/oscillator/LFO.js";
5import { Signal } from "../signal/Signal.js";
6import { readOnly } from "../core/util/Interface.js";
7
8export interface PhaserOptions extends StereoEffectOptions {
9 frequency: Frequency;
10 octaves: Positive;
11 stages: Positive;
12 Q: Positive;
13 baseFrequency: Frequency;
14}
15
16/**
17 * Phaser is a phaser effect. Phasers work by changing the phase
18 * of different frequency components of an incoming signal. Read more on
19 * [Wikipedia](https://en.wikipedia.org/wiki/Phaser_(effect)).
20 * Inspiration for this phaser comes from [Tuna.js](https://github.com/Dinahmoe/tuna/).
21 * @example
22 * const phaser = new Tone.Phaser({
23 * frequency: 15,
24 * octaves: 5,
25 * baseFrequency: 1000
26 * }).toDestination();
27 * const synth = new Tone.FMSynth().connect(phaser);
28 * synth.triggerAttackRelease("E3", "2n");
29 * @category Effect
30 */
31export class Phaser extends StereoEffect<PhaserOptions> {
32 readonly name: string = "Phaser";
33
34 /**
35 * the lfo which controls the frequency on the left side
36 */
37 private _lfoL: LFO;
38
39 /**
40 * the lfo which controls the frequency on the right side
41 */
42 private _lfoR: LFO;
43
44 /**
45 * the base modulation frequency
46 */
47 private _baseFrequency: Hertz;
48
49 /**
50 * the octaves of the phasing
51 */
52 private _octaves: Positive;
53
54 /**
55 * The quality factor of the filters
56 */
57 readonly Q: Signal<"positive">;
58
59 /**
60 * the array of filters for the left side
61 */
62 private _filtersL: BiquadFilterNode[];
63
64 /**
65 * the array of filters for the left side
66 */
67 private _filtersR: BiquadFilterNode[];
68
69 /**
70 * the frequency of the effect
71 */
72 readonly frequency: Signal<"frequency">;
73
74 /**
75 * @param frequency The speed of the phasing.
76 * @param octaves The octaves of the effect.
77 * @param baseFrequency The base frequency of the filters.
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 // make all the filters
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 * The number of octaves the phase goes above the baseFrequency
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 * The the base frequency of the filters.
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}