@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.
- package/dist/Reverb.es.js +296 -12
- package/dist/Reverb.iife.js +575 -287
- package/dist/Reverb.umd.js +576 -288
- package/package.json +13 -11
- package/dist/src/Meta.d.ts +0 -3
- package/dist/src/NoiseType.d.ts +0 -6
- package/dist/src/interfaces/MetaInterface.d.ts +0 -7
- package/dist/src/interfaces/OptionInterface.d.ts +0 -39
- /package/dist/{src/Reverb.d.ts → Reverb.d.ts} +0 -0
package/dist/Reverb.iife.js
CHANGED
|
@@ -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.
|
|
8
|
+
* @version 1.3.0
|
|
9
9
|
* @see {@link https://github.com/logue/Reverb.js}
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
var Reverb = (function (
|
|
13
|
-
|
|
12
|
+
var Reverb = (function () {
|
|
13
|
+
'use strict';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
*
|
|
128
|
+
* High-pass filtered noise. Opposite of {@link red}.
|
|
116
129
|
*
|
|
117
|
-
* @param
|
|
130
|
+
* @param opts -
|
|
118
131
|
*/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
*
|
|
150
|
+
* Band-pass filtered noise (interleaved blue noise). Opposite of
|
|
151
|
+
* {@link violet}.
|
|
129
152
|
*
|
|
130
|
-
* @param
|
|
153
|
+
* @param opts -
|
|
131
154
|
*/
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
*
|
|
158
|
+
* Returns number of 1 bits in `x`.
|
|
142
159
|
*
|
|
143
|
-
* @param
|
|
160
|
+
* @param x -
|
|
144
161
|
*/
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
*
|
|
175
|
+
* Exponential decay (1/f) noise, based on Voss-McCarthy algorithm.
|
|
156
176
|
*
|
|
157
|
-
* @
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
186
|
+
* @param opts -
|
|
195
187
|
*/
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
*
|
|
206
|
+
* Low-pass filtered noise (same as brown noise). Opposite of {@link blue}.
|
|
201
207
|
*
|
|
202
|
-
* @param
|
|
208
|
+
* @param opts -
|
|
203
209
|
*/
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
*
|
|
226
|
+
* Band-stop filtered noise (interleaved red noise). Opposite of {@link green}.
|
|
215
227
|
*
|
|
216
|
-
* @param
|
|
228
|
+
* @param opts -
|
|
217
229
|
*/
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
*
|
|
233
|
+
* Unfiltered noise w/ uniform distribution. Merely yields samples from
|
|
234
|
+
* given PRNG.
|
|
229
235
|
*
|
|
230
|
-
* @param
|
|
236
|
+
* @param opts -
|
|
231
237
|
*/
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
238
|
+
function* white(opts) {
|
|
239
|
+
const { scale, rnd } = { ...DEFAULT_OPTS, ...opts };
|
|
240
|
+
while (true) {
|
|
241
|
+
yield rnd.norm(scale);
|
|
242
|
+
}
|
|
235
243
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
*
|
|
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
|
|
269
|
+
* @param init - init step of reducer
|
|
270
|
+
* @param rfn - reduction step of reducer
|
|
249
271
|
*/
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
*
|
|
281
|
+
* Takes a transducer and input iterable. Returns iterator of
|
|
282
|
+
* transformed results.
|
|
256
283
|
*
|
|
257
|
-
* @param
|
|
284
|
+
* @param xform -
|
|
285
|
+
* @param xs -
|
|
258
286
|
*/
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
*
|
|
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
|
-
* @
|
|
296
|
-
*
|
|
297
|
-
*
|
|
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
|
-
|
|
300
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
322
|
-
|
|
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
|
-
|
|
638
|
+
return Reverb;
|
|
351
639
|
|
|
352
|
-
})(
|
|
640
|
+
})();
|