UNPKG

3.73 kBPlain TextView Raw
1import { NormalRange } from "../core/type/Units.js";
2import { StereoEffect, StereoEffectOptions } from "./StereoEffect.js";
3import { optionsFromArguments } from "../core/util/Defaults.js";
4import { Scale } from "../signal/Scale.js";
5import { Signal } from "../signal/Signal.js";
6import { FeedbackCombFilter } from "../component/filter/FeedbackCombFilter.js";
7import { readOnly } from "../core/util/Interface.js";
8
9export interface JCReverbOptions extends StereoEffectOptions {
10 roomSize: NormalRange;
11}
12
13/**
14 * an array of the comb filter delay time values
15 */
16const combFilterDelayTimes = [
17 1687 / 25000,
18 1601 / 25000,
19 2053 / 25000,
20 2251 / 25000,
21];
22
23/**
24 * the resonances of each of the comb filters
25 */
26const combFilterResonances = [0.773, 0.802, 0.753, 0.733];
27
28/**
29 * the allpass filter frequencies
30 */
31const allpassFilterFreqs = [347, 113, 37];
32
33/**
34 * JCReverb is a simple [Schroeder Reverberator](https://ccrma.stanford.edu/~jos/pasp/Schroeder_Reverberators.html)
35 * tuned by John Chowning in 1970.
36 * It is made up of three allpass filters and four {@link FeedbackCombFilter}.
37 * JCReverb is now implemented with an AudioWorkletNode which may result on performance degradation on some platforms. Consider using {@link Reverb}.
38 * @example
39 * const reverb = new Tone.JCReverb(0.4).toDestination();
40 * const delay = new Tone.FeedbackDelay(0.5);
41 * // connecting the synth to reverb through delay
42 * const synth = new Tone.DuoSynth().chain(delay, reverb);
43 * synth.triggerAttackRelease("A4", "8n");
44 *
45 * @category Effect
46 */
47export class JCReverb extends StereoEffect<JCReverbOptions> {
48 readonly name: string = "JCReverb";
49
50 /**
51 * Room size control values.
52 */
53 readonly roomSize: Signal<"normalRange">;
54
55 /**
56 * Scale the room size
57 */
58 private _scaleRoomSize: Scale;
59
60 /**
61 * a series of allpass filters
62 */
63 private _allpassFilters: BiquadFilterNode[] = [];
64
65 /**
66 * parallel feedback comb filters
67 */
68 private _feedbackCombFilters: FeedbackCombFilter[] = [];
69
70 /**
71 * @param roomSize Correlated to the decay time.
72 */
73 constructor(roomSize?: NormalRange);
74 constructor(options?: Partial<JCReverbOptions>);
75 constructor() {
76 const options = optionsFromArguments(
77 JCReverb.getDefaults(),
78 arguments,
79 ["roomSize"]
80 );
81 super(options);
82
83 this.roomSize = new Signal({
84 context: this.context,
85 value: options.roomSize,
86 units: "normalRange",
87 });
88 this._scaleRoomSize = new Scale({
89 context: this.context,
90 min: -0.733,
91 max: 0.197,
92 });
93
94 // make the allpass filters
95 this._allpassFilters = allpassFilterFreqs.map((freq) => {
96 const allpass = this.context.createBiquadFilter();
97 allpass.type = "allpass";
98 allpass.frequency.value = freq;
99 return allpass;
100 });
101
102 // and the comb filters
103 this._feedbackCombFilters = combFilterDelayTimes.map(
104 (delayTime, index) => {
105 const fbcf = new FeedbackCombFilter({
106 context: this.context,
107 delayTime,
108 });
109 this._scaleRoomSize.connect(fbcf.resonance);
110 fbcf.resonance.value = combFilterResonances[index];
111 if (index < combFilterDelayTimes.length / 2) {
112 this.connectEffectLeft(...this._allpassFilters, fbcf);
113 } else {
114 this.connectEffectRight(...this._allpassFilters, fbcf);
115 }
116 return fbcf;
117 }
118 );
119
120 // chain the allpass filters together
121 this.roomSize.connect(this._scaleRoomSize);
122 readOnly(this, ["roomSize"]);
123 }
124
125 static getDefaults(): JCReverbOptions {
126 return Object.assign(StereoEffect.getDefaults(), {
127 roomSize: 0.5,
128 });
129 }
130
131 dispose(): this {
132 super.dispose();
133 this._allpassFilters.forEach((apf) => apf.disconnect());
134 this._feedbackCombFilters.forEach((fbcf) => fbcf.dispose());
135 this.roomSize.dispose();
136 this._scaleRoomSize.dispose();
137 return this;
138 }
139}