@logue/reverb 1.2.18 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,351 +5,639 @@
5
5
  * @author Logue <logue@hotmail.co.jp>
6
6
  * @copyright 2019-2023 By Masashi Yoshikawa All rights reserved.
7
7
  * @license MIT
8
- * @version 1.2.18
8
+ * @version 1.3.0
9
9
  * @see {@link https://github.com/logue/Reverb.js}
10
10
  */
11
11
 
12
12
  (function (global, factory) {
13
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@thi.ng/random'), require('@thi.ng/colored-noise'), require('@thi.ng/transducers')) :
14
- typeof define === 'function' && define.amd ? define(['@thi.ng/random', '@thi.ng/colored-noise', '@thi.ng/transducers'], factory) :
15
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Reverb = factory(global.thiNgrandom, global.thiNgcoloredNoise, global.thiNgtransducers));
16
- })(this, (function (random, coloredNoise, transducers) { 'use strict';
13
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
14
+ typeof define === 'function' && define.amd ? define(factory) :
15
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Reverb = factory());
16
+ })(this, (function () { 'use strict';
17
17
 
18
- const defaults = {
19
- noise: "white",
20
- scale: 1,
21
- peaks: 2,
22
- randomAlgorithm: random.SYSTEM,
23
- decay: 2,
24
- delay: 0,
25
- reverse: false,
26
- time: 2,
27
- filterType: "allpass",
28
- filterFreq: 2200,
29
- filterQ: 1,
30
- mix: 0.5,
31
- once: false
32
- };
33
-
34
- const meta = {
35
- version: "1.2.18",
36
- date: "2023-08-08T00:54:37.984Z"
37
- };
38
-
39
- const Noise = {
40
- /** Blue noise */
41
- blue: "blue",
42
- /** Brown noise (same as red noise) */
43
- brown: "red",
44
- /** Green noise */
45
- green: "green",
46
- /** Pink noise */
47
- pink: "pink",
48
- /** Red noise */
49
- red: "red",
50
- /** Violet noise */
51
- violet: "violet",
52
- /** White noise */
53
- white: "white"
54
- };
18
+ const INV_MAX = 1 / 2 ** 32;
19
+ class ARandom {
20
+ float(norm = 1) {
21
+ return this.int() * INV_MAX * norm;
22
+ }
23
+ probability(p) {
24
+ return this.float() < p;
25
+ }
26
+ norm(norm = 1) {
27
+ return (this.int() * INV_MAX - 0.5) * 2 * norm;
28
+ }
29
+ normMinMax(min, max) {
30
+ const x = this.minmax(min, max);
31
+ return this.float() < 0.5 ? x : -x;
32
+ }
33
+ minmax(min, max) {
34
+ return this.float() * (max - min) + min;
35
+ }
36
+ minmaxInt(min, max) {
37
+ min |= 0;
38
+ return min + (this.int() % ((max | 0) - min));
39
+ }
40
+ minmaxUint(min, max) {
41
+ min >>>= 0;
42
+ return min + (this.int() % ((max >>> 0) - min));
43
+ }
44
+ }
55
45
 
56
- class Reverb {
57
- /** Version strings */
58
- static version = meta.version;
59
- /** Build date */
60
- static build = meta.date;
61
- /** AudioContext */
62
- ctx;
63
- /** Wet Level (Reverberated node) */
64
- wetGainNode;
65
- /** Dry Level (Original sound node) */
66
- dryGainNode;
67
- /** Impulse response filter */
68
- filterNode;
69
- /** Convolution node for applying impulse response */
70
- convolverNode;
71
- /** Output gain node */
72
- outputNode;
73
- /** Option */
74
- options;
75
- /** Connected flag */
76
- isConnected;
77
- /** Noise Generator */
78
- noise = coloredNoise.white;
46
+ const random = Math.random;
79
47
  /**
80
- * Constructor
81
- *
82
- * @param ctx - Root AudioContext
83
- * @param options - Configure
48
+ * A `Math.random()` based {@link IRandom} implementation. Also @see
49
+ * {@link SYSTEM}.
84
50
  */
85
- constructor(ctx, options) {
86
- this.ctx = ctx;
87
- this.options = Object.assign(defaults, options);
88
- this.wetGainNode = this.ctx.createGain();
89
- this.dryGainNode = this.ctx.createGain();
90
- this.filterNode = this.ctx.createBiquadFilter();
91
- this.convolverNode = this.ctx.createConvolver();
92
- this.outputNode = this.ctx.createGain();
93
- this.isConnected = false;
94
- this.filterType(this.options.filterType);
95
- this.setNoise(this.options.noise);
96
- this.buildImpulse();
97
- this.mix(this.options.mix);
51
+ class SystemRandom extends ARandom {
52
+ int() {
53
+ return (random() * 4294967296) /* 2**32 */ >>> 0;
54
+ }
55
+ float(norm = 1) {
56
+ return random() * norm;
57
+ }
58
+ norm(norm = 1) {
59
+ return (random() - 0.5) * 2 * norm;
60
+ }
98
61
  }
99
62
  /**
100
- * Connect the node for the reverb effect to the original sound node.
101
- *
102
- * @param sourceNode - Input source node
63
+ * Used as default PRNG throughout most other thi.ng projects, though usually is
64
+ * configurable.
103
65
  */
104
- connect(sourceNode) {
105
- if (this.isConnected && this.options.once) {
106
- this.isConnected = false;
107
- return this.outputNode;
108
- }
109
- this.convolverNode.connect(this.filterNode);
110
- this.filterNode.connect(this.wetGainNode);
111
- sourceNode.connect(this.convolverNode);
112
- sourceNode.connect(this.dryGainNode).connect(this.outputNode);
113
- sourceNode.connect(this.wetGainNode).connect(this.outputNode);
114
- this.isConnected = true;
115
- return this.outputNode;
66
+ const SYSTEM = new SystemRandom();
67
+
68
+ const defaults = {
69
+ noise: "white",
70
+ scale: 1,
71
+ peaks: 2,
72
+ randomAlgorithm: SYSTEM,
73
+ decay: 2,
74
+ delay: 0,
75
+ reverse: false,
76
+ time: 2,
77
+ filterType: "allpass",
78
+ filterFreq: 2200,
79
+ filterQ: 1,
80
+ mix: 0.5,
81
+ once: false
82
+ };
83
+
84
+ const Meta = {
85
+ version: "1.3.0",
86
+ date: "2023-08-16T23:12:28.774Z"
87
+ };
88
+
89
+ const Noise = {
90
+ /** Blue noise */
91
+ blue: "blue",
92
+ /** Brown noise (same as red noise) */
93
+ brown: "red",
94
+ /** Green noise */
95
+ green: "green",
96
+ /** Pink noise */
97
+ pink: "pink",
98
+ /** Red noise */
99
+ red: "red",
100
+ /** Violet noise */
101
+ violet: "violet",
102
+ /** White noise */
103
+ white: "white"
104
+ };
105
+
106
+ const DEFAULT_OPTS = {
107
+ bins: 2,
108
+ scale: 1,
109
+ rnd: SYSTEM,
110
+ };
111
+
112
+ const preseed = (n, scale, rnd) => {
113
+ const state = new Array(n);
114
+ for (let i = 0; i < n; i++) {
115
+ state[i] = rnd.norm(scale);
116
+ }
117
+ return state;
118
+ };
119
+ const sum = (src) => src.reduce((sum, x) => sum + x, 0);
120
+ function* interleave(a, b) {
121
+ const src = [a[Symbol.iterator](), b[Symbol.iterator]()];
122
+ for (let i = 0; true; i ^= 1) {
123
+ const next = src[i].next();
124
+ if (next.done)
125
+ return;
126
+ yield next.value;
127
+ }
116
128
  }
129
+
117
130
  /**
118
- * Disconnect the reverb node
131
+ * High-pass filtered noise. Opposite of {@link red}.
119
132
  *
120
- * @param sourceNode - Input source node
133
+ * @param opts -
121
134
  */
122
- disconnect(sourceNode) {
123
- if (this.isConnected) {
124
- this.convolverNode.disconnect(this.filterNode);
125
- this.filterNode.disconnect(this.wetGainNode);
126
- }
127
- this.isConnected = false;
128
- return sourceNode;
135
+ function* blue(opts) {
136
+ const { bins, scale, rnd } = {
137
+ ...DEFAULT_OPTS,
138
+ ...opts,
139
+ };
140
+ const state = preseed(bins, scale, rnd);
141
+ state.forEach((x, i) => (state[i] = i & 1 ? x : -x));
142
+ const invN = 1 / bins;
143
+ let acc = sum(state);
144
+ for (let i = 0, sign = -1; true; ++i >= bins && (i = 0)) {
145
+ acc -= state[i];
146
+ acc += state[i] = sign * rnd.norm(scale);
147
+ sign ^= 0xfffffffe;
148
+ yield sign * acc * invN;
149
+ }
129
150
  }
151
+
130
152
  /**
131
- * Dry/Wet ratio
153
+ * Band-pass filtered noise (interleaved blue noise). Opposite of
154
+ * {@link violet}.
132
155
  *
133
- * @param mix - Ratio (0~1)
156
+ * @param opts -
134
157
  */
135
- mix(mix) {
136
- if (!Reverb.inRange(mix, 0, 1)) {
137
- throw new RangeError("[Reverb.js] Dry/Wet ratio must be between 0 to 1.");
138
- }
139
- this.options.mix = mix;
140
- this.dryGainNode.gain.value = 1 - this.options.mix;
141
- this.wetGainNode.gain.value = this.options.mix;
142
- }
158
+ const green = (opts) => interleave(blue(opts), blue(opts));
159
+
143
160
  /**
144
- * Set Impulse Response time length (second)
161
+ * Returns number of 1 bits in `x`.
145
162
  *
146
- * @param value - IR length
163
+ * @param x -
147
164
  */
148
- time(value) {
149
- if (!Reverb.inRange(value, 1, 50)) {
150
- throw new RangeError(
151
- "[Reverb.js] Time length of inpulse response must be less than 50sec."
152
- );
153
- }
154
- this.options.time = value;
155
- this.buildImpulse();
156
- }
165
+ const ctz32 = (x) => {
166
+ let c = 32;
167
+ x &= -x;
168
+ x && c--;
169
+ x & 0x0000ffff && (c -= 16);
170
+ x & 0x00ff00ff && (c -= 8);
171
+ x & 0x0f0f0f0f && (c -= 4);
172
+ x & 0x33333333 && (c -= 2);
173
+ x & 0x55555555 && (c -= 1);
174
+ return c;
175
+ };
176
+
157
177
  /**
158
- * Impulse response decay rate.
178
+ * Exponential decay (1/f) noise, based on Voss-McCarthy algorithm.
159
179
  *
160
- * @param value - Decay value
161
- */
162
- decay(value) {
163
- if (!Reverb.inRange(value, 0, 100)) {
164
- throw new RangeError(
165
- "[Reverb.js] Inpulse Response decay level must be less than 100."
166
- );
167
- }
168
- this.options.decay = value;
169
- this.buildImpulse();
170
- }
171
- /**
172
- * Delay before reverberation starts
180
+ * @remarks
181
+ * The number of internal states should be in the [4..32] range (default: 8).
182
+ * Due to JS integer limitations, `n` > 32 are meaningless.
173
183
  *
174
- * @param value - Time[ms]
175
- */
176
- delay(value) {
177
- if (!Reverb.inRange(value, 0, 100)) {
178
- throw new RangeError(
179
- "[Reverb.js] Inpulse Response delay time must be less than 100."
180
- );
181
- }
182
- this.options.delay = value;
183
- this.buildImpulse();
184
- }
185
- /**
186
- * Reverse the impulse response.
184
+ * References:
187
185
  *
188
- * @param reverse - Reverse IR
189
- */
190
- reverse(reverse) {
191
- this.options.reverse = reverse;
192
- this.buildImpulse();
193
- }
194
- /**
195
- * Filter for impulse response
186
+ * - https://www.dsprelated.com/showarticle/908.php
187
+ * - https://www.firstpr.com.au/dsp/pink-noise/#Voss-McCartney
196
188
  *
197
- * @param type - Filiter Type
189
+ * @param opts -
198
190
  */
199
- filterType(type = "allpass") {
200
- this.filterNode.type = this.options.filterType = type;
191
+ function* pink(opts) {
192
+ const { bins, scale, rnd } = {
193
+ ...DEFAULT_OPTS,
194
+ bins: 8,
195
+ ...opts,
196
+ };
197
+ const state = preseed(bins, scale, rnd);
198
+ const invN = 1 / bins;
199
+ let acc = sum(state);
200
+ for (let i = 0; true; i = (i + 1) >>> 0) {
201
+ const id = ctz32(i) % bins;
202
+ acc -= state[id];
203
+ acc += state[id] = rnd.norm(scale);
204
+ yield acc * invN;
205
+ }
201
206
  }
207
+
202
208
  /**
203
- * Filter frequency applied to impulse response
209
+ * Low-pass filtered noise (same as brown noise). Opposite of {@link blue}.
204
210
  *
205
- * @param freq - Frequency
211
+ * @param opts -
206
212
  */
207
- filterFreq(freq) {
208
- if (!Reverb.inRange(freq, 20, 2e4)) {
209
- throw new RangeError(
210
- "[Reverb.js] Filter frequrncy must be between 20 and 20000."
211
- );
212
- }
213
- this.options.filterFreq = freq;
214
- this.filterNode.frequency.value = this.options.filterFreq;
213
+ function* red(opts) {
214
+ const { bins, scale, rnd } = {
215
+ ...DEFAULT_OPTS,
216
+ ...opts,
217
+ };
218
+ const state = preseed(bins, scale, rnd);
219
+ const invN = 1 / bins;
220
+ let acc = sum(state);
221
+ for (let i = 0; true; ++i >= bins && (i = 0)) {
222
+ acc -= state[i];
223
+ acc += state[i] = rnd.norm(scale);
224
+ yield acc * invN;
225
+ }
215
226
  }
227
+
216
228
  /**
217
- * Filter quality.
229
+ * Band-stop filtered noise (interleaved red noise). Opposite of {@link green}.
218
230
  *
219
- * @param q - Quality
231
+ * @param opts -
220
232
  */
221
- filterQ(q) {
222
- if (!Reverb.inRange(q, 0, 10)) {
223
- throw new RangeError(
224
- "[Reverb.js] Filter quality value must be between 0 and 10."
225
- );
226
- }
227
- this.options.filterQ = q;
228
- this.filterNode.Q.value = this.options.filterQ;
229
- }
233
+ const violet = (opts) => interleave(red(opts), red(opts));
234
+
230
235
  /**
231
- * set IR source noise peaks
236
+ * Unfiltered noise w/ uniform distribution. Merely yields samples from
237
+ * given PRNG.
232
238
  *
233
- * @param p - Peaks
239
+ * @param opts -
234
240
  */
235
- peaks(p) {
236
- this.options.peaks = p;
237
- this.buildImpulse();
241
+ function* white(opts) {
242
+ const { scale, rnd } = { ...DEFAULT_OPTS, ...opts };
243
+ while (true) {
244
+ yield rnd.norm(scale);
245
+ }
238
246
  }
239
- /**
240
- * set IR source noise scale.
241
- *
242
- * @param s - Scale
243
- */
244
- scale(s) {
245
- this.options.scale = s;
246
- this.buildImpulse();
247
+
248
+ const implementsFunction = (x, fn) => x != null && typeof x[fn] === "function";
249
+
250
+ const ensureTransducer = (x) => implementsFunction(x, "xform") ? x.xform() : x;
251
+
252
+ const isIterable = (x) => x != null && typeof x[Symbol.iterator] === "function";
253
+
254
+ class Reduced {
255
+ constructor(val) {
256
+ this.value = val;
257
+ }
258
+ deref() {
259
+ return this.value;
260
+ }
247
261
  }
262
+ const reduced = (x) => new Reduced(x);
263
+ const isReduced = (x) => x instanceof Reduced;
264
+ const ensureReduced = (x) => x instanceof Reduced ? x : new Reduced(x);
265
+ const unreduced = (x) => (x instanceof Reduced ? x.deref() : x);
266
+
248
267
  /**
249
- * set IR source noise generator.
268
+ * Convenience helper for building a full {@link Reducer} using the identity
269
+ * function (i.e. `(x) => x`) as completion step (true for 90% of all
270
+ * bundled transducers).
250
271
  *
251
- * @param a - Algorithm
272
+ * @param init - init step of reducer
273
+ * @param rfn - reduction step of reducer
252
274
  */
253
- randomAlgorithm(a) {
254
- this.options.randomAlgorithm = a;
255
- this.buildImpulse();
275
+ const reducer = (init, rfn) => [init, (acc) => acc, rfn];
276
+
277
+ function push(xs) {
278
+ return xs
279
+ ? [...xs]
280
+ : reducer(() => [], (acc, x) => (acc.push(x), acc));
256
281
  }
282
+
257
283
  /**
258
- * Inpulse Response Noise algorithm.
284
+ * Takes a transducer and input iterable. Returns iterator of
285
+ * transformed results.
259
286
  *
260
- * @param type - IR noise algorithm type.
287
+ * @param xform -
288
+ * @param xs -
261
289
  */
262
- setNoise(type) {
263
- this.options.noise = type;
264
- switch (type) {
265
- case Noise.blue:
266
- this.noise = coloredNoise.blue;
267
- break;
268
- case Noise.green:
269
- this.noise = coloredNoise.green;
270
- break;
271
- case Noise.pink:
272
- this.noise = coloredNoise.pink;
273
- break;
274
- case Noise.red:
275
- case Noise.brown:
276
- this.noise = coloredNoise.red;
277
- break;
278
- case Noise.violet:
279
- this.noise = coloredNoise.violet;
280
- break;
281
- default:
282
- this.noise = coloredNoise.white;
283
- }
284
- this.buildImpulse();
290
+ function* iterator(xform, xs) {
291
+ const rfn = ensureTransducer(xform)(push());
292
+ const complete = rfn[1];
293
+ const reduce = rfn[2];
294
+ for (let x of xs) {
295
+ const y = reduce([], x);
296
+ if (isReduced(y)) {
297
+ yield* unreduced(complete(y.deref()));
298
+ return;
299
+ }
300
+ if (y.length) {
301
+ yield* y;
302
+ }
303
+ }
304
+ yield* unreduced(complete([]));
285
305
  }
306
+
286
307
  /**
287
- * Set Random Algorythm
308
+ * Reducer composition helper, internally used by various transducers
309
+ * during initialization. Takes existing reducer `rfn` (a 3-tuple) and a
310
+ * reducing function `fn`. Returns a new reducer tuple.
288
311
  *
289
- * @param algorithm - Algorythm
290
- */
291
- setRandomAlgorythm(algorithm) {
292
- this.options.randomAlgorithm = algorithm;
293
- this.buildImpulse();
294
- }
295
- /**
296
- * Return true if in range, otherwise false
312
+ * @remarks
313
+ * `rfn[2]` reduces values of type `B` into an accumulator of type `A`.
314
+ * `fn` accepts values of type `C` and produces interim results of type
315
+ * `B`, which are then (possibly) passed to the "inner" `rfn[2]`
316
+ * function. Therefore the resulting reducer takes inputs of `C` and an
317
+ * accumulator of type `A`.
318
+ *
319
+ * It is assumed that `fn` internally calls `rfn[2]` to pass its own
320
+ * results for further processing by the nested reducer `rfn`.
297
321
  *
298
- * @param x - Target value
299
- * @param min - Minimum value
300
- * @param max - Maximum value
322
+ * @example
323
+ * ```ts
324
+ * compR(rfn, fn)
325
+ * // [rfn[0], rfn[1], fn]
326
+ * ```
327
+ *
328
+ * @param rfn -
329
+ * @param fn -
301
330
  */
302
- static inRange(x, min, max) {
303
- return (x - min) * (x - max) <= 0;
331
+ const compR = (rfn, fn) => [rfn[0], rfn[1], fn];
332
+
333
+ function take(n, src) {
334
+ return isIterable(src)
335
+ ? iterator(take(n), src)
336
+ : (rfn) => {
337
+ const r = rfn[2];
338
+ let m = n;
339
+ return compR(rfn, (acc, x) => --m > 0
340
+ ? r(acc, x)
341
+ : m === 0
342
+ ? ensureReduced(r(acc, x))
343
+ : reduced(acc));
344
+ };
304
345
  }
305
- /** Utility function for building an impulse response from the module parameters. */
306
- buildImpulse() {
307
- const rate = this.ctx.sampleRate;
308
- const duration = Math.max(rate * this.options.time, 1);
309
- const delayDuration = rate * this.options.delay;
310
- const impulse = this.ctx.createBuffer(2, duration, rate);
311
- const impulseL = new Float32Array(duration);
312
- const impulseR = new Float32Array(duration);
313
- const noiseL = this.getNoise(duration);
314
- const noiseR = this.getNoise(duration);
315
- for (let i = 0; i < duration; i++) {
316
- let n = 0;
317
- if (i < delayDuration) {
318
- impulseL[i] = 0;
319
- impulseR[i] = 0;
320
- n = this.options.reverse ?? false ? duration - (i - delayDuration) : i - delayDuration;
321
- } else {
322
- n = this.options.reverse ?? false ? duration - i : i;
346
+
347
+ class Reverb {
348
+ /** Version strings */
349
+ static version = Meta.version;
350
+ /** Build date */
351
+ static build = Meta.date;
352
+ /** AudioContext */
353
+ ctx;
354
+ /** Wet Level (Reverberated node) */
355
+ wetGainNode;
356
+ /** Dry Level (Original sound node) */
357
+ dryGainNode;
358
+ /** Impulse response filter */
359
+ filterNode;
360
+ /** Convolution node for applying impulse response */
361
+ convolverNode;
362
+ /** Output gain node */
363
+ outputNode;
364
+ /** Option */
365
+ options;
366
+ /** Connected flag */
367
+ isConnected;
368
+ /** Noise Generator */
369
+ noise = white;
370
+ /**
371
+ * Constructor
372
+ *
373
+ * @param ctx - Root AudioContext
374
+ * @param options - Configure
375
+ */
376
+ constructor(ctx, options) {
377
+ this.ctx = ctx;
378
+ this.options = Object.assign(defaults, options);
379
+ this.wetGainNode = this.ctx.createGain();
380
+ this.dryGainNode = this.ctx.createGain();
381
+ this.filterNode = this.ctx.createBiquadFilter();
382
+ this.convolverNode = this.ctx.createConvolver();
383
+ this.outputNode = this.ctx.createGain();
384
+ this.isConnected = false;
385
+ this.filterType(this.options.filterType);
386
+ this.setNoise(this.options.noise);
387
+ this.buildImpulse();
388
+ this.mix(this.options.mix);
389
+ }
390
+ /**
391
+ * Connect the node for the reverb effect to the original sound node.
392
+ *
393
+ * @param sourceNode - Input source node
394
+ */
395
+ connect(sourceNode) {
396
+ if (this.isConnected && this.options.once) {
397
+ this.isConnected = false;
398
+ return this.outputNode;
323
399
  }
324
- impulseL[i] = (noiseL[i] ?? 0) * (1 - n / duration) ** this.options.decay;
325
- impulseR[i] = (noiseR[i] ?? 0) * (1 - n / duration) ** this.options.decay;
400
+ this.convolverNode.connect(this.filterNode);
401
+ this.filterNode.connect(this.wetGainNode);
402
+ sourceNode.connect(this.convolverNode);
403
+ sourceNode.connect(this.dryGainNode).connect(this.outputNode);
404
+ sourceNode.connect(this.wetGainNode).connect(this.outputNode);
405
+ this.isConnected = true;
406
+ return this.outputNode;
407
+ }
408
+ /**
409
+ * Disconnect the reverb node
410
+ *
411
+ * @param sourceNode - Input source node
412
+ */
413
+ disconnect(sourceNode) {
414
+ if (this.isConnected) {
415
+ this.convolverNode.disconnect(this.filterNode);
416
+ this.filterNode.disconnect(this.wetGainNode);
417
+ }
418
+ this.isConnected = false;
419
+ return sourceNode;
420
+ }
421
+ /**
422
+ * Dry/Wet ratio
423
+ *
424
+ * @param mix - Ratio (0~1)
425
+ */
426
+ mix(mix) {
427
+ if (!Reverb.inRange(mix, 0, 1)) {
428
+ throw new RangeError("[Reverb.js] Dry/Wet ratio must be between 0 to 1.");
429
+ }
430
+ this.options.mix = mix;
431
+ this.dryGainNode.gain.value = 1 - this.options.mix;
432
+ this.wetGainNode.gain.value = this.options.mix;
433
+ }
434
+ /**
435
+ * Set Impulse Response time length (second)
436
+ *
437
+ * @param value - IR length
438
+ */
439
+ time(value) {
440
+ if (!Reverb.inRange(value, 1, 50)) {
441
+ throw new RangeError(
442
+ "[Reverb.js] Time length of inpulse response must be less than 50sec."
443
+ );
444
+ }
445
+ this.options.time = value;
446
+ this.buildImpulse();
447
+ }
448
+ /**
449
+ * Impulse response decay rate.
450
+ *
451
+ * @param value - Decay value
452
+ */
453
+ decay(value) {
454
+ if (!Reverb.inRange(value, 0, 100)) {
455
+ throw new RangeError(
456
+ "[Reverb.js] Inpulse Response decay level must be less than 100."
457
+ );
458
+ }
459
+ this.options.decay = value;
460
+ this.buildImpulse();
461
+ }
462
+ /**
463
+ * Delay before reverberation starts
464
+ *
465
+ * @param value - Time[ms]
466
+ */
467
+ delay(value) {
468
+ if (!Reverb.inRange(value, 0, 100)) {
469
+ throw new RangeError(
470
+ "[Reverb.js] Inpulse Response delay time must be less than 100."
471
+ );
472
+ }
473
+ this.options.delay = value;
474
+ this.buildImpulse();
475
+ }
476
+ /**
477
+ * Reverse the impulse response.
478
+ *
479
+ * @param reverse - Reverse IR
480
+ */
481
+ reverse(reverse) {
482
+ this.options.reverse = reverse;
483
+ this.buildImpulse();
484
+ }
485
+ /**
486
+ * Filter for impulse response
487
+ *
488
+ * @param type - Filiter Type
489
+ */
490
+ filterType(type = "allpass") {
491
+ this.filterNode.type = this.options.filterType = type;
492
+ }
493
+ /**
494
+ * Filter frequency applied to impulse response
495
+ *
496
+ * @param freq - Frequency
497
+ */
498
+ filterFreq(freq) {
499
+ if (!Reverb.inRange(freq, 20, 2e4)) {
500
+ throw new RangeError(
501
+ "[Reverb.js] Filter frequrncy must be between 20 and 20000."
502
+ );
503
+ }
504
+ this.options.filterFreq = freq;
505
+ this.filterNode.frequency.value = this.options.filterFreq;
506
+ }
507
+ /**
508
+ * Filter quality.
509
+ *
510
+ * @param q - Quality
511
+ */
512
+ filterQ(q) {
513
+ if (!Reverb.inRange(q, 0, 10)) {
514
+ throw new RangeError(
515
+ "[Reverb.js] Filter quality value must be between 0 and 10."
516
+ );
517
+ }
518
+ this.options.filterQ = q;
519
+ this.filterNode.Q.value = this.options.filterQ;
520
+ }
521
+ /**
522
+ * set IR source noise peaks
523
+ *
524
+ * @param p - Peaks
525
+ */
526
+ peaks(p) {
527
+ this.options.peaks = p;
528
+ this.buildImpulse();
529
+ }
530
+ /**
531
+ * set IR source noise scale.
532
+ *
533
+ * @param s - Scale
534
+ */
535
+ scale(s) {
536
+ this.options.scale = s;
537
+ this.buildImpulse();
538
+ }
539
+ /**
540
+ * set IR source noise generator.
541
+ *
542
+ * @param a - Algorithm
543
+ */
544
+ randomAlgorithm(a) {
545
+ this.options.randomAlgorithm = a;
546
+ this.buildImpulse();
547
+ }
548
+ /**
549
+ * Inpulse Response Noise algorithm.
550
+ *
551
+ * @param type - IR noise algorithm type.
552
+ */
553
+ setNoise(type) {
554
+ this.options.noise = type;
555
+ switch (type) {
556
+ case Noise.blue:
557
+ this.noise = blue;
558
+ break;
559
+ case Noise.green:
560
+ this.noise = green;
561
+ break;
562
+ case Noise.pink:
563
+ this.noise = pink;
564
+ break;
565
+ case Noise.red:
566
+ case Noise.brown:
567
+ this.noise = red;
568
+ break;
569
+ case Noise.violet:
570
+ this.noise = violet;
571
+ break;
572
+ default:
573
+ this.noise = white;
574
+ }
575
+ this.buildImpulse();
576
+ }
577
+ /**
578
+ * Set Random Algorythm
579
+ *
580
+ * @param algorithm - Algorythm
581
+ */
582
+ setRandomAlgorythm(algorithm) {
583
+ this.options.randomAlgorithm = algorithm;
584
+ this.buildImpulse();
585
+ }
586
+ /**
587
+ * Return true if in range, otherwise false
588
+ *
589
+ * @param x - Target value
590
+ * @param min - Minimum value
591
+ * @param max - Maximum value
592
+ */
593
+ static inRange(x, min, max) {
594
+ return (x - min) * (x - max) <= 0;
595
+ }
596
+ /** Utility function for building an impulse response from the module parameters. */
597
+ buildImpulse() {
598
+ const rate = this.ctx.sampleRate;
599
+ const duration = Math.max(rate * this.options.time, 1);
600
+ const delayDuration = rate * this.options.delay;
601
+ const impulse = this.ctx.createBuffer(2, duration, rate);
602
+ const impulseL = new Float32Array(duration);
603
+ const impulseR = new Float32Array(duration);
604
+ const noiseL = this.getNoise(duration);
605
+ const noiseR = this.getNoise(duration);
606
+ for (let i = 0; i < duration; i++) {
607
+ let n = 0;
608
+ if (i < delayDuration) {
609
+ impulseL[i] = 0;
610
+ impulseR[i] = 0;
611
+ n = this.options.reverse ?? false ? duration - (i - delayDuration) : i - delayDuration;
612
+ } else {
613
+ n = this.options.reverse ?? false ? duration - i : i;
614
+ }
615
+ impulseL[i] = (noiseL[i] ?? 0) * (1 - n / duration) ** this.options.decay;
616
+ impulseR[i] = (noiseR[i] ?? 0) * (1 - n / duration) ** this.options.decay;
617
+ }
618
+ impulse.getChannelData(0).set(impulseL);
619
+ impulse.getChannelData(1).set(impulseR);
620
+ this.convolverNode.buffer = impulse;
621
+ }
622
+ /**
623
+ * Noise source
624
+ *
625
+ * @param duration - length of IR.
626
+ */
627
+ getNoise(duration) {
628
+ return [
629
+ ...take(
630
+ duration,
631
+ this.noise({
632
+ bins: this.options.peaks,
633
+ scale: this.options.scale,
634
+ rnd: this.options.randomAlgorithm
635
+ })
636
+ )
637
+ ];
326
638
  }
327
- impulse.getChannelData(0).set(impulseL);
328
- impulse.getChannelData(1).set(impulseR);
329
- this.convolverNode.buffer = impulse;
330
- }
331
- /**
332
- * Noise source
333
- *
334
- * @param duration - length of IR.
335
- */
336
- getNoise(duration) {
337
- return [
338
- ...transducers.take(
339
- duration,
340
- this.noise({
341
- bins: this.options.peaks,
342
- scale: this.options.scale,
343
- rnd: this.options.randomAlgorithm
344
- })
345
- )
346
- ];
347
639
  }
348
- }
349
- if (!window.Reverb) {
350
- window.Reverb = Reverb;
351
- }
352
640
 
353
- return Reverb;
641
+ return Reverb;
354
642
 
355
643
  }));