Tone.js/Tone/effect/JCReverb.ts

144 lines
3.7 KiB
TypeScript
Raw Normal View History

2024-05-03 14:10:40 +00:00
import { NormalRange } from "../core/type/Units.js";
import { StereoEffect, StereoEffectOptions } from "./StereoEffect.js";
import { optionsFromArguments } from "../core/util/Defaults.js";
import { Scale } from "../signal/Scale.js";
import { Signal } from "../signal/Signal.js";
import { FeedbackCombFilter } from "../component/filter/FeedbackCombFilter.js";
import { readOnly } from "../core/util/Interface.js";
2019-11-04 01:33:46 +00:00
export interface JCReverbOptions extends StereoEffectOptions {
roomSize: NormalRange;
}
/**
* an array of the comb filter delay time values
*/
2024-05-03 15:09:28 +00:00
const combFilterDelayTimes = [
1687 / 25000,
1601 / 25000,
2053 / 25000,
2251 / 25000,
];
2019-11-04 01:33:46 +00:00
/**
* the resonances of each of the comb filters
*/
const combFilterResonances = [0.773, 0.802, 0.753, 0.733];
/**
* the allpass filter frequencies
*/
const allpassFilterFreqs = [347, 113, 37];
/**
* JCReverb is a simple [Schroeder Reverberator](https://ccrma.stanford.edu/~jos/pasp/Schroeder_Reverberators.html)
* tuned by John Chowning in 1970.
2024-04-29 14:48:37 +00:00
* It is made up of three allpass filters and four {@link FeedbackCombFilter}.
* JCReverb is now implemented with an AudioWorkletNode which may result on performance degradation on some platforms. Consider using {@link Reverb}.
2019-11-04 01:33:46 +00:00
* @example
* const reverb = new Tone.JCReverb(0.4).toDestination();
* const delay = new Tone.FeedbackDelay(0.5);
2019-11-04 01:33:46 +00:00
* // connecting the synth to reverb through delay
* const synth = new Tone.DuoSynth().chain(delay, reverb);
2019-11-04 01:33:46 +00:00
* synth.triggerAttackRelease("A4", "8n");
2024-05-03 15:09:28 +00:00
*
2019-11-05 03:29:14 +00:00
* @category Effect
2019-11-04 01:33:46 +00:00
*/
export class JCReverb extends StereoEffect<JCReverbOptions> {
2019-11-04 02:43:01 +00:00
readonly name: string = "JCReverb";
2019-11-04 01:33:46 +00:00
/**
2024-05-03 15:09:28 +00:00
* Room size control values.
2019-11-04 01:33:46 +00:00
*/
2024-05-01 19:55:52 +00:00
readonly roomSize: Signal<"normalRange">;
2019-11-04 01:33:46 +00:00
/**
* Scale the room size
*/
private _scaleRoomSize: Scale;
/**
* a series of allpass filters
*/
private _allpassFilters: BiquadFilterNode[] = [];
/**
* parallel feedback comb filters
*/
private _feedbackCombFilters: FeedbackCombFilter[] = [];
/**
* @param roomSize Correlated to the decay time.
*/
constructor(roomSize?: NormalRange);
constructor(options?: Partial<JCReverbOptions>);
constructor() {
2024-05-03 15:09:28 +00:00
super(
optionsFromArguments(JCReverb.getDefaults(), arguments, [
"roomSize",
])
);
const options = optionsFromArguments(
JCReverb.getDefaults(),
arguments,
["roomSize"]
);
2019-11-04 01:33:46 +00:00
this.roomSize = new Signal({
context: this.context,
value: options.roomSize,
units: "normalRange",
});
this._scaleRoomSize = new Scale({
context: this.context,
min: -0.733,
2019-11-04 01:33:46 +00:00
max: 0.197,
});
// make the allpass filters
2024-05-03 15:09:28 +00:00
this._allpassFilters = allpassFilterFreqs.map((freq) => {
2019-11-04 01:33:46 +00:00
const allpass = this.context.createBiquadFilter();
allpass.type = "allpass";
allpass.frequency.value = freq;
return allpass;
});
2019-11-04 01:33:46 +00:00
// and the comb filters
2024-05-03 15:09:28 +00:00
this._feedbackCombFilters = combFilterDelayTimes.map(
(delayTime, index) => {
const fbcf = new FeedbackCombFilter({
context: this.context,
delayTime,
});
this._scaleRoomSize.connect(fbcf.resonance);
fbcf.resonance.value = combFilterResonances[index];
if (index < combFilterDelayTimes.length / 2) {
this.connectEffectLeft(...this._allpassFilters, fbcf);
} else {
this.connectEffectRight(...this._allpassFilters, fbcf);
}
return fbcf;
2019-11-04 01:33:46 +00:00
}
2024-05-03 15:09:28 +00:00
);
2019-11-04 01:33:46 +00:00
// chain the allpass filters together
this.roomSize.connect(this._scaleRoomSize);
readOnly(this, ["roomSize"]);
}
static getDefaults(): JCReverbOptions {
return Object.assign(StereoEffect.getDefaults(), {
roomSize: 0.5,
});
}
dispose(): this {
super.dispose();
2024-05-03 15:09:28 +00:00
this._allpassFilters.forEach((apf) => apf.disconnect());
this._feedbackCombFilters.forEach((fbcf) => fbcf.dispose());
2019-11-04 01:33:46 +00:00
this.roomSize.dispose();
this._scaleRoomSize.dispose();
return this;
}
}