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