UNPKG

4.2 kBPlain TextView Raw
1import { StereoEffect, StereoEffectOptions } from "./StereoEffect.js";
2import { Frequency, NormalRange } from "../core/type/Units.js";
3import { optionsFromArguments } from "../core/util/Defaults.js";
4import { readOnly } from "../core/util/Interface.js";
5import { Signal } from "../signal/Signal.js";
6import { LowpassCombFilter } from "../component/filter/LowpassCombFilter.js";
7
8export interface FreeverbOptions extends StereoEffectOptions {
9 dampening: Frequency;
10 roomSize: NormalRange;
11}
12
13/**
14 * An array of comb filter delay values from Freeverb implementation
15 */
16const combFilterTunings = [
17 1557 / 44100,
18 1617 / 44100,
19 1491 / 44100,
20 1422 / 44100,
21 1277 / 44100,
22 1356 / 44100,
23 1188 / 44100,
24 1116 / 44100,
25];
26
27/**
28 * An array of allpass filter frequency values from Freeverb implementation
29 */
30const allpassFilterFrequencies = [225, 556, 441, 341];
31
32/**
33 * Freeverb is a reverb based on [Freeverb](https://ccrma.stanford.edu/~jos/pasp/Freeverb.html).
34 * Read more on reverb on [Sound On Sound](https://web.archive.org/web/20160404083902/http://www.soundonsound.com:80/sos/feb01/articles/synthsecrets.asp).
35 * Freeverb is now implemented with an AudioWorkletNode which may result on performance degradation on some platforms. Consider using {@link Reverb}.
36 * @example
37 * const freeverb = new Tone.Freeverb().toDestination();
38 * freeverb.dampening = 1000;
39 * // routing synth through the reverb
40 * const synth = new Tone.NoiseSynth().connect(freeverb);
41 * synth.triggerAttackRelease(0.05);
42 * @category Effect
43 */
44export class Freeverb extends StereoEffect<FreeverbOptions> {
45 readonly name: string = "Freeverb";
46
47 /**
48 * The roomSize value between 0 and 1. A larger roomSize will result in a longer decay.
49 */
50 readonly roomSize: Signal<"normalRange">;
51
52 /**
53 * the comb filters
54 */
55 private _combFilters: LowpassCombFilter[] = [];
56
57 /**
58 * the allpass filters on the left
59 */
60 private _allpassFiltersL: BiquadFilterNode[] = [];
61
62 /**
63 * the allpass filters on the right
64 */
65 private _allpassFiltersR: BiquadFilterNode[] = [];
66
67 /**
68 * @param roomSize Correlated to the decay time.
69 * @param dampening The cutoff frequency of a lowpass filter as part of the reverb.
70 */
71 constructor(roomSize?: NormalRange, dampening?: Frequency);
72 constructor(options?: Partial<FreeverbOptions>);
73 constructor() {
74 const options = optionsFromArguments(
75 Freeverb.getDefaults(),
76 arguments,
77 ["roomSize", "dampening"]
78 );
79 super(options);
80
81 this.roomSize = new Signal({
82 context: this.context,
83 value: options.roomSize,
84 units: "normalRange",
85 });
86
87 // make the allpass filters on the right
88 this._allpassFiltersL = allpassFilterFrequencies.map((freq) => {
89 const allpassL = this.context.createBiquadFilter();
90 allpassL.type = "allpass";
91 allpassL.frequency.value = freq;
92 return allpassL;
93 });
94
95 // make the allpass filters on the left
96 this._allpassFiltersR = allpassFilterFrequencies.map((freq) => {
97 const allpassR = this.context.createBiquadFilter();
98 allpassR.type = "allpass";
99 allpassR.frequency.value = freq;
100 return allpassR;
101 });
102
103 // make the comb filters
104 this._combFilters = combFilterTunings.map((delayTime, index) => {
105 const lfpf = new LowpassCombFilter({
106 context: this.context,
107 dampening: options.dampening,
108 delayTime,
109 });
110 if (index < combFilterTunings.length / 2) {
111 this.connectEffectLeft(lfpf, ...this._allpassFiltersL);
112 } else {
113 this.connectEffectRight(lfpf, ...this._allpassFiltersR);
114 }
115 this.roomSize.connect(lfpf.resonance);
116 return lfpf;
117 });
118
119 readOnly(this, ["roomSize"]);
120 }
121
122 static getDefaults(): FreeverbOptions {
123 return Object.assign(StereoEffect.getDefaults(), {
124 roomSize: 0.7,
125 dampening: 3000,
126 });
127 }
128
129 /**
130 * The amount of dampening of the reverberant signal.
131 */
132
133 get dampening(): Frequency {
134 return this._combFilters[0].dampening;
135 }
136 set dampening(d) {
137 this._combFilters.forEach((c) => (c.dampening = d));
138 }
139
140 dispose(): this {
141 super.dispose();
142 this._allpassFiltersL.forEach((al) => al.disconnect());
143 this._allpassFiltersR.forEach((ar) => ar.disconnect());
144 this._combFilters.forEach((cf) => cf.dispose());
145 this.roomSize.dispose();
146 return this;
147 }
148}