UNPKG

3.94 kBPlain TextView Raw
1import { Merge } from "../component/channel/Merge.js";
2import { Gain } from "../core/context/Gain.js";
3import { Seconds, Time } from "../core/type/Units.js";
4import { optionsFromArguments } from "../core/util/Defaults.js";
5import { Noise } from "../source/Noise.js";
6import { Effect, EffectOptions } from "./Effect.js";
7import { OfflineContext } from "../core/context/OfflineContext.js";
8import { noOp } from "../core/util/Interface.js";
9import { assertRange } from "../core/util/Debug.js";
10
11interface ReverbOptions extends EffectOptions {
12 decay: Seconds;
13 preDelay: Seconds;
14}
15
16/**
17 * Simple convolution created with decaying noise.
18 * Generates an Impulse Response Buffer
19 * with Tone.Offline then feeds the IR into ConvolverNode.
20 * The impulse response generation is async, so you have
21 * to wait until {@link ready} resolves before it will make a sound.
22 *
23 * Inspiration from [ReverbGen](https://github.com/adelespinasse/reverbGen).
24 * Copyright (c) 2014 Alan deLespinasse Apache 2.0 License.
25 *
26 * @category Effect
27 */
28export class Reverb extends Effect<ReverbOptions> {
29 readonly name: string = "Reverb";
30
31 /**
32 * Convolver node
33 */
34 private _convolver: ConvolverNode = this.context.createConvolver();
35
36 /**
37 * The duration of the reverb.
38 */
39 private _decay: Seconds;
40
41 /**
42 * The amount of time before the reverb is fully ramped in.
43 */
44 private _preDelay: Seconds;
45
46 /**
47 * Resolves when the reverb buffer is generated. Whenever either {@link decay}
48 * or {@link preDelay} are set, you have to wait until {@link ready} resolves
49 * before the IR is generated with the latest values.
50 */
51 ready: Promise<void> = Promise.resolve();
52
53 /**
54 * @param decay The amount of time it will reverberate for.
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 * The amount of time before the reverb is fully ramped in.
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 * Generate the Impulse Response. Returns a promise while the IR is being generated.
106 * @return Promise which returns this object.
107 */
108 async generate(): Promise<this> {
109 const previousReady = this.ready;
110
111 // create a noise burst which decays over the duration in each channel
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 // predelay
127 gainNode.gain.setValueAtTime(0, 0);
128 gainNode.gain.setValueAtTime(1, this._preDelay);
129 // decay
130 gainNode.gain.exponentialApproachValueAtTime(
131 0,
132 this._preDelay,
133 this.decay
134 );
135
136 // render the buffer
137 const renderPromise = context.render();
138 this.ready = renderPromise.then(noOp);
139
140 // wait for the previous `ready` to resolve
141 await previousReady;
142 // set the buffer
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}