2019-11-04 03:17:00 +00:00
|
|
|
import { StereoEffect, StereoEffectOptions } from "./StereoEffect";
|
|
|
|
import { Frequency, Hertz, Positive } from "../core/type/Units";
|
|
|
|
import { optionsFromArguments } from "../core/util/Defaults";
|
|
|
|
import { LFO } from "../source/oscillator/LFO";
|
|
|
|
import { Signal } from "../signal/Signal";
|
|
|
|
import { readOnly } from "../core/util/Interface";
|
|
|
|
|
|
|
|
export interface PhaserOptions extends StereoEffectOptions {
|
|
|
|
frequency: Frequency;
|
|
|
|
octaves: Positive;
|
|
|
|
stages: Positive;
|
|
|
|
Q: Positive;
|
2020-04-17 02:24:18 +00:00
|
|
|
baseFrequency: Frequency;
|
2019-11-04 03:17:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Phaser is a phaser effect. Phasers work by changing the phase
|
|
|
|
* of different frequency components of an incoming signal. Read more on
|
|
|
|
* [Wikipedia](https://en.wikipedia.org/wiki/Phaser_(effect)).
|
|
|
|
* Inspiration for this phaser comes from [Tuna.js](https://github.com/Dinahmoe/tuna/).
|
|
|
|
* @example
|
2020-04-17 02:24:18 +00:00
|
|
|
* const phaser = new Tone.Phaser({
|
2019-11-04 03:17:00 +00:00
|
|
|
* frequency: 15,
|
|
|
|
* octaves: 5,
|
|
|
|
* baseFrequency: 1000
|
|
|
|
* }).toDestination();
|
2020-04-17 02:24:18 +00:00
|
|
|
* const synth = new Tone.FMSynth().connect(phaser);
|
2019-11-04 03:17:00 +00:00
|
|
|
* synth.triggerAttackRelease("E3", "2n");
|
2019-11-05 03:29:14 +00:00
|
|
|
* @category Effect
|
2019-11-04 03:17:00 +00:00
|
|
|
*/
|
|
|
|
export class Phaser extends StereoEffect<PhaserOptions> {
|
|
|
|
|
|
|
|
readonly name: string = "Phaser";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* the lfo which controls the frequency on the left side
|
|
|
|
*/
|
|
|
|
private _lfoL: LFO;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* the lfo which controls the frequency on the right side
|
|
|
|
*/
|
|
|
|
private _lfoR: LFO;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* the base modulation frequency
|
|
|
|
*/
|
|
|
|
private _baseFrequency: Hertz;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* the octaves of the phasing
|
|
|
|
*/
|
|
|
|
private _octaves: Positive;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The quality factor of the filters
|
|
|
|
*/
|
|
|
|
readonly Q: Signal<"positive">;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* the array of filters for the left side
|
|
|
|
*/
|
|
|
|
private _filtersL: BiquadFilterNode[];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* the array of filters for the left side
|
|
|
|
*/
|
|
|
|
private _filtersR: BiquadFilterNode[];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* the frequency of the effect
|
|
|
|
*/
|
|
|
|
readonly frequency: Signal<"frequency">;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param frequency The speed of the phasing.
|
|
|
|
* @param octaves The octaves of the effect.
|
|
|
|
* @param baseFrequency The base frequency of the filters.
|
|
|
|
*/
|
|
|
|
constructor(frequency?: Frequency, octaves?: Positive, baseFrequency?: Frequency);
|
|
|
|
constructor(options?: Partial<PhaserOptions>);
|
|
|
|
constructor() {
|
|
|
|
|
|
|
|
super(optionsFromArguments(Phaser.getDefaults(), arguments, ["frequency", "octaves", "baseFrequency"]));
|
|
|
|
const options = optionsFromArguments(Phaser.getDefaults(), arguments, ["frequency", "octaves", "baseFrequency"]);
|
|
|
|
|
|
|
|
this._lfoL = new LFO({
|
|
|
|
context: this.context,
|
2020-04-17 02:24:18 +00:00
|
|
|
frequency: options.frequency,
|
2019-11-04 03:17:00 +00:00
|
|
|
min: 0,
|
|
|
|
max: 1
|
|
|
|
});
|
|
|
|
this._lfoR = new LFO({
|
|
|
|
context: this.context,
|
2020-04-17 02:24:18 +00:00
|
|
|
frequency: options.frequency,
|
2019-11-04 03:17:00 +00:00
|
|
|
min: 0,
|
|
|
|
max: 1,
|
|
|
|
phase: 180,
|
|
|
|
});
|
|
|
|
this._baseFrequency = this.toFrequency(options.baseFrequency);
|
|
|
|
this._octaves = options.octaves;
|
|
|
|
this.Q = new Signal({
|
|
|
|
context: this.context,
|
|
|
|
value: options.Q,
|
|
|
|
units: "positive",
|
|
|
|
});
|
|
|
|
this._filtersL = this._makeFilters(options.stages, this._lfoL);
|
|
|
|
this._filtersR = this._makeFilters(options.stages, this._lfoR);
|
|
|
|
|
|
|
|
this.frequency = this._lfoL.frequency;
|
|
|
|
this.frequency.value = options.frequency;
|
|
|
|
|
|
|
|
// connect them up
|
|
|
|
this.connectEffectLeft(...this._filtersL);
|
|
|
|
this.connectEffectRight(...this._filtersR);
|
|
|
|
// control the frequency with one LFO
|
|
|
|
this._lfoL.frequency.connect(this._lfoR.frequency);
|
|
|
|
// set the options
|
|
|
|
this.baseFrequency = options.baseFrequency;
|
|
|
|
this.octaves = options.octaves;
|
|
|
|
// start the lfo
|
|
|
|
this._lfoL.start();
|
|
|
|
this._lfoR.start();
|
|
|
|
readOnly(this, ["frequency", "Q"]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static getDefaults(): PhaserOptions {
|
|
|
|
return Object.assign(StereoEffect.getDefaults(), {
|
|
|
|
frequency: 0.5,
|
|
|
|
octaves: 3,
|
|
|
|
stages: 10,
|
|
|
|
Q: 10,
|
|
|
|
baseFrequency: 350,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private _makeFilters(stages: number, connectToFreq: LFO): BiquadFilterNode[] {
|
|
|
|
const filters: BiquadFilterNode[] = [];
|
|
|
|
// make all the filters
|
|
|
|
for (let i = 0; i < stages; i++) {
|
2019-11-17 18:09:19 +00:00
|
|
|
const filter = this.context.createBiquadFilter();
|
2019-11-04 03:17:00 +00:00
|
|
|
filter.type = "allpass";
|
|
|
|
this.Q.connect(filter.Q);
|
|
|
|
connectToFreq.connect(filter.frequency);
|
|
|
|
filters.push(filter);
|
|
|
|
}
|
|
|
|
return filters;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The number of octaves the phase goes above the baseFrequency
|
|
|
|
*/
|
|
|
|
get octaves() {
|
|
|
|
return this._octaves;
|
|
|
|
}
|
|
|
|
set octaves(octaves) {
|
|
|
|
this._octaves = octaves;
|
2019-11-17 18:09:19 +00:00
|
|
|
const max = this._baseFrequency * Math.pow(2, octaves);
|
2019-11-04 03:17:00 +00:00
|
|
|
this._lfoL.max = max;
|
|
|
|
this._lfoR.max = max;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The the base frequency of the filters.
|
|
|
|
*/
|
|
|
|
get baseFrequency(): Frequency {
|
|
|
|
return this._baseFrequency;
|
|
|
|
}
|
|
|
|
set baseFrequency(freq) {
|
|
|
|
this._baseFrequency = this.toFrequency(freq);
|
|
|
|
this._lfoL.min = this._baseFrequency;
|
|
|
|
this._lfoR.min = this._baseFrequency;
|
|
|
|
this.octaves = this._octaves;
|
|
|
|
}
|
2020-04-17 02:24:18 +00:00
|
|
|
|
|
|
|
dispose(): this {
|
2019-11-04 03:17:00 +00:00
|
|
|
super.dispose();
|
|
|
|
this.Q.dispose();
|
|
|
|
this._lfoL.dispose();
|
|
|
|
this._lfoR.dispose();
|
|
|
|
this._filtersL.forEach(f => f.disconnect());
|
|
|
|
this._filtersR.forEach(f => f.disconnect());
|
|
|
|
this.frequency.dispose();
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|