1 | import { Merge } from "../component/channel/Merge.js";
|
2 | import { Gain } from "../core/context/Gain.js";
|
3 | import { Seconds, Time } from "../core/type/Units.js";
|
4 | import { optionsFromArguments } from "../core/util/Defaults.js";
|
5 | import { Noise } from "../source/Noise.js";
|
6 | import { Effect, EffectOptions } from "./Effect.js";
|
7 | import { OfflineContext } from "../core/context/OfflineContext.js";
|
8 | import { noOp } from "../core/util/Interface.js";
|
9 | import { assertRange } from "../core/util/Debug.js";
|
10 |
|
11 | interface ReverbOptions extends EffectOptions {
|
12 | decay: Seconds;
|
13 | preDelay: Seconds;
|
14 | }
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | export class Reverb extends Effect<ReverbOptions> {
|
29 | readonly name: string = "Reverb";
|
30 |
|
31 | |
32 |
|
33 |
|
34 | private _convolver: ConvolverNode = this.context.createConvolver();
|
35 |
|
36 | |
37 |
|
38 |
|
39 | private _decay: Seconds;
|
40 |
|
41 | |
42 |
|
43 |
|
44 | private _preDelay: Seconds;
|
45 |
|
46 | |
47 |
|
48 |
|
49 |
|
50 |
|
51 | ready: Promise<void> = Promise.resolve();
|
52 |
|
53 | |
54 |
|
55 |
|
56 | constructor(decay?: Seconds);
|
57 | constructor(options?: Partial<ReverbOptions>);
|
58 | constructor() {
|
59 | const options = optionsFromArguments(Reverb.getDefaults(), arguments, [
|
60 | "decay",
|
61 | ]);
|
62 | super(options);
|
63 |
|
64 | this._decay = options.decay;
|
65 | this._preDelay = options.preDelay;
|
66 | this.generate();
|
67 |
|
68 | this.connectEffect(this._convolver);
|
69 | }
|
70 |
|
71 | static getDefaults(): ReverbOptions {
|
72 | return Object.assign(Effect.getDefaults(), {
|
73 | decay: 1.5,
|
74 | preDelay: 0.01,
|
75 | });
|
76 | }
|
77 |
|
78 | /**
|
79 | * The duration of the reverb.
|
80 | */
|
81 | get decay(): Time {
|
82 | return this._decay;
|
83 | }
|
84 | set decay(time) {
|
85 | time = this.toSeconds(time);
|
86 | assertRange(time, 0.001);
|
87 | this._decay = time;
|
88 | this.generate();
|
89 | }
|
90 |
|
91 | |
92 |
|
93 |
|
94 | get preDelay(): Time {
|
95 | return this._preDelay;
|
96 | }
|
97 | set preDelay(time) {
|
98 | time = this.toSeconds(time);
|
99 | assertRange(time, 0);
|
100 | this._preDelay = time;
|
101 | this.generate();
|
102 | }
|
103 |
|
104 | |
105 |
|
106 |
|
107 |
|
108 | async generate(): Promise<this> {
|
109 | const previousReady = this.ready;
|
110 |
|
111 |
|
112 | const context = new OfflineContext(
|
113 | 2,
|
114 | this._decay + this._preDelay,
|
115 | this.context.sampleRate
|
116 | );
|
117 | const noiseL = new Noise({ context });
|
118 | const noiseR = new Noise({ context });
|
119 | const merge = new Merge({ context });
|
120 | noiseL.connect(merge, 0, 0);
|
121 | noiseR.connect(merge, 0, 1);
|
122 | const gainNode = new Gain({ context }).toDestination();
|
123 | merge.connect(gainNode);
|
124 | noiseL.start(0);
|
125 | noiseR.start(0);
|
126 |
|
127 | gainNode.gain.setValueAtTime(0, 0);
|
128 | gainNode.gain.setValueAtTime(1, this._preDelay);
|
129 |
|
130 | gainNode.gain.exponentialApproachValueAtTime(
|
131 | 0,
|
132 | this._preDelay,
|
133 | this.decay
|
134 | );
|
135 |
|
136 |
|
137 | const renderPromise = context.render();
|
138 | this.ready = renderPromise.then(noOp);
|
139 |
|
140 |
|
141 | await previousReady;
|
142 |
|
143 | this._convolver.buffer = (await renderPromise).get() as AudioBuffer;
|
144 |
|
145 | return this;
|
146 | }
|
147 |
|
148 | dispose(): this {
|
149 | super.dispose();
|
150 | this._convolver.disconnect();
|
151 | return this;
|
152 | }
|
153 | }
|