1 | import { NormalRange } from "../core/type/Units.js";
|
2 | import { StereoEffect, StereoEffectOptions } from "./StereoEffect.js";
|
3 | import { optionsFromArguments } from "../core/util/Defaults.js";
|
4 | import { Scale } from "../signal/Scale.js";
|
5 | import { Signal } from "../signal/Signal.js";
|
6 | import { FeedbackCombFilter } from "../component/filter/FeedbackCombFilter.js";
|
7 | import { readOnly } from "../core/util/Interface.js";
|
8 |
|
9 | export interface JCReverbOptions extends StereoEffectOptions {
|
10 | roomSize: NormalRange;
|
11 | }
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | const combFilterDelayTimes = [
|
17 | 1687 / 25000,
|
18 | 1601 / 25000,
|
19 | 2053 / 25000,
|
20 | 2251 / 25000,
|
21 | ];
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | const combFilterResonances = [0.773, 0.802, 0.753, 0.733];
|
27 |
|
28 |
|
29 |
|
30 |
|
31 | const allpassFilterFreqs = [347, 113, 37];
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | export class JCReverb extends StereoEffect<JCReverbOptions> {
|
48 | readonly name: string = "JCReverb";
|
49 |
|
50 | |
51 |
|
52 |
|
53 | readonly roomSize: Signal<"normalRange">;
|
54 |
|
55 | |
56 |
|
57 |
|
58 | private _scaleRoomSize: Scale;
|
59 |
|
60 | |
61 |
|
62 |
|
63 | private _allpassFilters: BiquadFilterNode[] = [];
|
64 |
|
65 | |
66 |
|
67 |
|
68 | private _feedbackCombFilters: FeedbackCombFilter[] = [];
|
69 |
|
70 | |
71 |
|
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 | }
|