Tone.js/Tone/effect/Reverb.ts

146 lines
3.9 KiB
TypeScript
Raw Normal View History

2019-08-02 20:29:09 +00:00
import { Merge } from "../component/channel/Merge";
import { Gain } from "../core/context/Gain";
import { Seconds, Time } from "../core/type/Units";
2019-08-02 20:29:09 +00:00
import { optionsFromArguments } from "../core/util/Defaults";
import { Noise } from "../source/Noise";
import { Effect, EffectOptions } from "./Effect";
import { OfflineContext } from "../core/context/OfflineContext";
import { noOp } from "../core/util/Interface";
2019-12-11 15:35:01 +00:00
import { assertRange } from "../core/util/Debug";
2019-08-02 20:29:09 +00:00
interface ReverbOptions extends EffectOptions {
decay: Seconds;
preDelay: Seconds;
}
/**
2019-09-14 20:39:18 +00:00
* Simple convolution created with decaying noise.
* Generates an Impulse Response Buffer
* with Tone.Offline then feeds the IR into ConvolverNode.
* The impulse response generation is async, so you have
2024-04-29 14:48:37 +00:00
* to wait until {@link ready} resolves before it will make a sound.
2019-08-02 20:29:09 +00:00
*
2019-09-14 20:39:18 +00:00
* Inspiration from [ReverbGen](https://github.com/adelespinasse/reverbGen).
* Copyright (c) 2014 Alan deLespinasse Apache 2.0 License.
2019-09-16 14:15:23 +00:00
*
* @category Effect
2019-08-02 20:29:09 +00:00
*/
export class Reverb extends Effect<ReverbOptions> {
2019-09-04 23:18:44 +00:00
readonly name: string = "Reverb";
2019-08-02 20:29:09 +00:00
/**
2019-09-14 20:39:18 +00:00
* Convolver node
2019-08-02 20:29:09 +00:00
*/
private _convolver: ConvolverNode = this.context.createConvolver();
/**
2019-09-29 14:19:56 +00:00
* The duration of the reverb.
2019-08-02 20:29:09 +00:00
*/
private _decay: Seconds;
2019-09-29 14:19:56 +00:00
2019-08-02 20:29:09 +00:00
/**
* The amount of time before the reverb is fully ramped in.
*/
private _preDelay: Seconds;
/**
2024-04-29 14:48:37 +00:00
* Resolves when the reverb buffer is generated. Whenever either {@link decay}
* or {@link preDelay} are set, you have to wait until {@link ready} resolves
* before the IR is generated with the latest values.
*/
ready: Promise<void> = Promise.resolve();
2019-08-02 20:29:09 +00:00
2019-08-27 16:00:59 +00:00
/**
* @param decay The amount of time it will reverberate for.
2019-08-27 16:00:59 +00:00
*/
2019-08-02 20:29:09 +00:00
constructor(decay?: Seconds);
constructor(options?: Partial<ReverbOptions>);
constructor() {
super(optionsFromArguments(Reverb.getDefaults(), arguments, ["decay"]));
const options = optionsFromArguments(Reverb.getDefaults(), arguments, ["decay"]);
this._decay = options.decay;
this._preDelay = options.preDelay;
this.generate();
2019-08-02 20:29:09 +00:00
this.connectEffect(this._convolver);
}
static getDefaults(): ReverbOptions {
return Object.assign(Effect.getDefaults(), {
2019-09-14 22:12:44 +00:00
decay: 1.5,
preDelay: 0.01,
2019-08-02 20:29:09 +00:00
});
}
/**
* The duration of the reverb.
*/
get decay(): Time {
return this._decay;
}
set decay(time) {
2019-12-11 15:13:52 +00:00
time = this.toSeconds(time);
2019-12-14 17:34:44 +00:00
assertRange(time, 0.001);
2019-12-11 15:13:52 +00:00
this._decay = time;
this.generate();
}
/**
* The amount of time before the reverb is fully ramped in.
*/
get preDelay(): Time {
return this._preDelay;
}
set preDelay(time) {
2019-12-11 15:13:52 +00:00
time = this.toSeconds(time);
assertRange(time, 0);
this._preDelay = time;
this.generate();
}
2019-08-02 20:29:09 +00:00
/**
* Generate the Impulse Response. Returns a promise while the IR is being generated.
* @return Promise which returns this object.
*/
async generate(): Promise<this> {
const previousReady = this.ready;
// create a noise burst which decays over the duration in each channel
const context = new OfflineContext(2, this._decay + this._preDelay, this.context.sampleRate);
const noiseL = new Noise({ context });
const noiseR = new Noise({ context });
const merge = new Merge({ context });
noiseL.connect(merge, 0, 0);
noiseR.connect(merge, 0, 1);
const gainNode = new Gain({ context }).toDestination();
merge.connect(gainNode);
noiseL.start(0);
noiseR.start(0);
// predelay
gainNode.gain.setValueAtTime(0, 0);
gainNode.gain.setValueAtTime(1, this._preDelay);
// decay
gainNode.gain.exponentialApproachValueAtTime(0, this._preDelay, this.decay);
// render the buffer
const renderPromise = context.render();
this.ready = renderPromise.then(noOp);
// wait for the previous `ready` to resolve
await previousReady;
// set the buffer
this._convolver.buffer = (await renderPromise).get() as AudioBuffer;
2019-08-02 20:29:09 +00:00
return this;
}
dispose(): this {
super.dispose();
this._convolver.disconnect();
return this;
}
}