@thi.ng/dsp 4.6.16 → 4.6.18
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/CHANGELOG.md +1 -1
- package/README.md +1 -1
- package/add.js +24 -38
- package/addg.js +4 -19
- package/adsr.js +148 -156
- package/agen.js +17 -21
- package/allpass.js +37 -38
- package/alt.js +26 -22
- package/anti-alias.js +8 -37
- package/api.js +0 -1
- package/aproc.js +24 -30
- package/biquad.js +188 -184
- package/bounce.js +15 -16
- package/complex.js +4 -1
- package/const.js +12 -13
- package/convert.js +18 -64
- package/cosine.js +43 -48
- package/curve.js +12 -41
- package/dcblock.js +9 -10
- package/delay.js +100 -111
- package/feedback-delay.js +23 -30
- package/fft.js +188 -334
- package/filter-delay.js +23 -27
- package/filter-response.js +22 -33
- package/foldback.js +26 -35
- package/impulse-train.js +35 -37
- package/impulse.js +27 -42
- package/integrator.js +27 -32
- package/internal/ensure.js +4 -1
- package/internal/take.js +8 -5
- package/iterable.js +32 -43
- package/line.js +10 -25
- package/madd.js +25 -40
- package/mapg.js +77 -69
- package/merge.js +18 -20
- package/mix.js +20 -16
- package/mul.js +24 -38
- package/multiplex.js +15 -11
- package/onepole.js +41 -42
- package/osc-additive.js +25 -53
- package/osc-cos.js +4 -1
- package/osc-dsf.js +15 -52
- package/osc-mix.js +6 -20
- package/osc-parabolic.js +4 -13
- package/osc-rect.js +6 -8
- package/osc-saw.js +4 -1
- package/osc-sin.js +4 -1
- package/osc-tri.js +4 -1
- package/osc-wavetable.js +12 -9
- package/osc.js +40 -74
- package/package.json +13 -10
- package/pan.js +23 -29
- package/pink-noise.js +35 -57
- package/pipe.js +6 -4
- package/power.js +40 -96
- package/product.js +5 -4
- package/reciprocal.js +20 -22
- package/ref.js +24 -20
- package/serial.js +59 -60
- package/sincos.js +45 -61
- package/sum.js +5 -4
- package/svf.js +74 -78
- package/sweep.js +4 -27
- package/waveshaper.js +41 -60
- package/white-noise.js +17 -23
- package/window.js +67 -52
package/fft.js
CHANGED
|
@@ -2,355 +2,209 @@ import { isComplex } from "./complex.js";
|
|
|
2
2
|
import { magDb } from "./convert.js";
|
|
3
3
|
import { invPowerScale, powerScale } from "./power.js";
|
|
4
4
|
import { applyWindow } from "./window.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* @param n -
|
|
9
|
-
*/
|
|
10
|
-
export const complexArray = (n) => [
|
|
11
|
-
new Float64Array(n),
|
|
12
|
-
new Float64Array(n),
|
|
5
|
+
const complexArray = (n) => [
|
|
6
|
+
new Float64Array(n),
|
|
7
|
+
new Float64Array(n)
|
|
13
8
|
];
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* @param complex -
|
|
18
|
-
*/
|
|
19
|
-
export const copyComplex = (complex) => [
|
|
20
|
-
complex[0].slice(),
|
|
21
|
-
complex[1].slice(),
|
|
9
|
+
const copyComplex = (complex) => [
|
|
10
|
+
complex[0].slice(),
|
|
11
|
+
complex[1].slice()
|
|
22
12
|
];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
return res;
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
const n = src.length;
|
|
39
|
-
const dest = new Float64Array(n * 2);
|
|
40
|
-
dest.set(src);
|
|
41
|
-
for (let i = 1, j = n * 2 - 1; i < n; i++, j--) {
|
|
42
|
-
dest[j] = isImg ? -src[i] : src[i];
|
|
43
|
-
}
|
|
44
|
-
return dest;
|
|
13
|
+
function conjugate(src, isImg = true) {
|
|
14
|
+
if (isComplex(src)) {
|
|
15
|
+
const n = src[0].length;
|
|
16
|
+
const res = complexArray(n * 2);
|
|
17
|
+
const [sreal, simg] = src;
|
|
18
|
+
const [dreal, dimg] = res;
|
|
19
|
+
dreal.set(sreal);
|
|
20
|
+
dimg.set(simg);
|
|
21
|
+
for (let i = 1, j = n * 2 - 1; i < n; i++, j--) {
|
|
22
|
+
dreal[j] = sreal[i];
|
|
23
|
+
dimg[j] = -simg[i];
|
|
45
24
|
}
|
|
25
|
+
return res;
|
|
26
|
+
} else {
|
|
27
|
+
const n = src.length;
|
|
28
|
+
const dest = new Float64Array(n * 2);
|
|
29
|
+
dest.set(src);
|
|
30
|
+
for (let i = 1, j = n * 2 - 1; i < n; i++, j--) {
|
|
31
|
+
dest[j] = isImg ? -src[i] : src[i];
|
|
32
|
+
}
|
|
33
|
+
return dest;
|
|
34
|
+
}
|
|
46
35
|
}
|
|
47
36
|
const swapR = (real, n) => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
37
|
+
const n2 = n >> 1;
|
|
38
|
+
let ii;
|
|
39
|
+
let jj;
|
|
40
|
+
let k;
|
|
41
|
+
let t;
|
|
42
|
+
for (let i = 1, j = 1; i < n; i++) {
|
|
43
|
+
if (i < j) {
|
|
44
|
+
ii = i - 1;
|
|
45
|
+
jj = j - 1;
|
|
46
|
+
t = real[jj];
|
|
47
|
+
real[jj] = real[ii];
|
|
48
|
+
real[ii] = t;
|
|
49
|
+
}
|
|
50
|
+
k = n2;
|
|
51
|
+
while (k < j) {
|
|
52
|
+
j -= k;
|
|
53
|
+
k >>= 1;
|
|
54
|
+
}
|
|
55
|
+
j += k;
|
|
56
|
+
}
|
|
68
57
|
};
|
|
69
58
|
const swapRI = (real, img, n) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
59
|
+
const n2 = n >> 1;
|
|
60
|
+
let ii;
|
|
61
|
+
let jj;
|
|
62
|
+
let k;
|
|
63
|
+
let t;
|
|
64
|
+
for (let i = 1, j = 1; i < n; i++) {
|
|
65
|
+
if (i < j) {
|
|
66
|
+
ii = i - 1;
|
|
67
|
+
jj = j - 1;
|
|
68
|
+
t = real[jj];
|
|
69
|
+
real[jj] = real[ii];
|
|
70
|
+
real[ii] = t;
|
|
71
|
+
t = img[jj];
|
|
72
|
+
img[jj] = img[ii];
|
|
73
|
+
img[ii] = t;
|
|
74
|
+
}
|
|
75
|
+
k = n2;
|
|
76
|
+
while (k < j) {
|
|
77
|
+
j -= k;
|
|
78
|
+
k >>= 1;
|
|
79
|
+
}
|
|
80
|
+
j += k;
|
|
81
|
+
}
|
|
93
82
|
};
|
|
94
83
|
const transform = (real, img, n) => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
84
|
+
let step = 1;
|
|
85
|
+
let prevStep;
|
|
86
|
+
let i, j, ii, ip;
|
|
87
|
+
let tr, ti;
|
|
88
|
+
let ur, ui;
|
|
89
|
+
let wr, wi;
|
|
90
|
+
let t;
|
|
91
|
+
for (let b = Math.ceil(Math.log2(n)); b-- > 0; ) {
|
|
92
|
+
prevStep = step;
|
|
93
|
+
step <<= 1;
|
|
94
|
+
ur = 1;
|
|
95
|
+
ui = 0;
|
|
96
|
+
t = Math.PI / prevStep;
|
|
97
|
+
wr = Math.cos(t);
|
|
98
|
+
wi = -Math.sin(t);
|
|
99
|
+
for (j = 1; j <= prevStep; j++) {
|
|
100
|
+
for (i = j; i <= n; i += step) {
|
|
101
|
+
ip = i + prevStep - 1;
|
|
102
|
+
ii = i - 1;
|
|
103
|
+
tr = real[ip] * ur - img[ip] * ui;
|
|
104
|
+
ti = real[ip] * ui + img[ip] * ur;
|
|
105
|
+
real[ip] = real[ii] - tr;
|
|
106
|
+
img[ip] = img[ii] - ti;
|
|
107
|
+
real[ii] += tr;
|
|
108
|
+
img[ii] += ti;
|
|
109
|
+
}
|
|
110
|
+
t = ur;
|
|
111
|
+
ur = t * wr - ui * wi;
|
|
112
|
+
ui = t * wi + ui * wr;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
126
115
|
};
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
let real, img;
|
|
148
|
-
if (isComplex(complex)) {
|
|
149
|
-
real = complex[0];
|
|
150
|
-
img = complex[1];
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
real = complex;
|
|
154
|
-
}
|
|
155
|
-
if (window) {
|
|
156
|
-
applyWindow(real, window);
|
|
157
|
-
}
|
|
158
|
-
const n = real.length;
|
|
159
|
-
if (img) {
|
|
160
|
-
swapRI(real, img, n);
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
swapR(real, n);
|
|
164
|
-
img = new Float64Array(n);
|
|
165
|
-
}
|
|
166
|
-
transform(real, img, n);
|
|
167
|
-
return [real, img];
|
|
116
|
+
const fft = (complex, window) => {
|
|
117
|
+
let real, img;
|
|
118
|
+
if (isComplex(complex)) {
|
|
119
|
+
real = complex[0];
|
|
120
|
+
img = complex[1];
|
|
121
|
+
} else {
|
|
122
|
+
real = complex;
|
|
123
|
+
}
|
|
124
|
+
if (window) {
|
|
125
|
+
applyWindow(real, window);
|
|
126
|
+
}
|
|
127
|
+
const n = real.length;
|
|
128
|
+
if (img) {
|
|
129
|
+
swapRI(real, img, n);
|
|
130
|
+
} else {
|
|
131
|
+
swapR(real, n);
|
|
132
|
+
img = new Float64Array(n);
|
|
133
|
+
}
|
|
134
|
+
transform(real, img, n);
|
|
135
|
+
return [real, img];
|
|
168
136
|
};
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
*
|
|
174
|
-
* @remarks
|
|
175
|
-
*
|
|
176
|
-
* - https://www.dsprelated.com/showarticle/800.php (method #3)
|
|
177
|
-
*
|
|
178
|
-
* @param complex -
|
|
179
|
-
*/
|
|
180
|
-
export const ifft = (src) => {
|
|
181
|
-
let complex = isComplex(src)
|
|
182
|
-
? src
|
|
183
|
-
: [new Float64Array(src.length), src];
|
|
184
|
-
fft([complex[1], complex[0]]);
|
|
185
|
-
return scaleFFT(complex, 1 / complex[0].length);
|
|
137
|
+
const ifft = (src) => {
|
|
138
|
+
let complex = isComplex(src) ? src : [new Float64Array(src.length), src];
|
|
139
|
+
fft([complex[1], complex[0]]);
|
|
140
|
+
return scaleFFT(complex, 1 / complex[0].length);
|
|
186
141
|
};
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
142
|
+
const scaleFFT = (complex, scale) => {
|
|
143
|
+
const [real, img] = complex;
|
|
144
|
+
const n = real.length;
|
|
145
|
+
for (let i = 0; i < n; i++) {
|
|
146
|
+
real[i] *= scale;
|
|
147
|
+
img[i] *= scale;
|
|
148
|
+
}
|
|
149
|
+
return [real, img];
|
|
195
150
|
};
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
* - https://holometer.fnal.gov/GH_FFT.pdf
|
|
207
|
-
*
|
|
208
|
-
* @param complex -
|
|
209
|
-
* @param window -
|
|
210
|
-
*/
|
|
211
|
-
export const normalizeFFT = (complex, window = 2 / complex[0].length) => scaleFFT(complex, powerScale(window, 2));
|
|
212
|
-
/**
|
|
213
|
-
* Inverse operation of {@link normalizeFFT}. De-normalizes the complex FFT
|
|
214
|
-
* array by scaling each complex bin value with given scale factor (or, if given
|
|
215
|
-
* as array, the scale factor derived from these window function samples).
|
|
216
|
-
*
|
|
217
|
-
* @remarks
|
|
218
|
-
* By default assumes a rectangular window and the resulting scale factor of N /
|
|
219
|
-
* 2.
|
|
220
|
-
*
|
|
221
|
-
* References:
|
|
222
|
-
* - https://holometer.fnal.gov/GH_FFT.pdf
|
|
223
|
-
*
|
|
224
|
-
* @param complex -
|
|
225
|
-
* @param window -
|
|
226
|
-
*/
|
|
227
|
-
export const denormalizeFFT = (complex, window = complex[0].length / 2) => scaleFFT(complex, invPowerScale(window, 2));
|
|
228
|
-
/**
|
|
229
|
-
* Computes the magnitude of each FFT bin and if less than given `eps`
|
|
230
|
-
* threshold, sets that bin to zero. Returns input FFT array.
|
|
231
|
-
*
|
|
232
|
-
* @remarks
|
|
233
|
-
* It's recommended to apply this function prior computing
|
|
234
|
-
* {@link spectrumPhase}. The `eps` value might have to be adjusted and should
|
|
235
|
-
* be approx. `max(spectrumMag(fft))/10000`.
|
|
236
|
-
*
|
|
237
|
-
* References:
|
|
238
|
-
* - https://www.gaussianwaves.com/2015/11/interpreting-fft-results-obtaining-magnitude-and-phase-information/
|
|
239
|
-
*
|
|
240
|
-
* @param complex -
|
|
241
|
-
* @param eps -
|
|
242
|
-
*/
|
|
243
|
-
export const thresholdFFT = (complex, eps = 1e-12) => {
|
|
244
|
-
const [real, img] = complex;
|
|
245
|
-
for (let i = 0, n = real.length; i < n; i++) {
|
|
246
|
-
if (Math.hypot(real[i], img[i]) < eps) {
|
|
247
|
-
real[i] = img[i] = 0;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return complex;
|
|
151
|
+
const normalizeFFT = (complex, window = 2 / complex[0].length) => scaleFFT(complex, powerScale(window, 2));
|
|
152
|
+
const denormalizeFFT = (complex, window = complex[0].length / 2) => scaleFFT(complex, invPowerScale(window, 2));
|
|
153
|
+
const thresholdFFT = (complex, eps = 1e-12) => {
|
|
154
|
+
const [real, img] = complex;
|
|
155
|
+
for (let i = 0, n = real.length; i < n; i++) {
|
|
156
|
+
if (Math.hypot(real[i], img[i]) < eps) {
|
|
157
|
+
real[i] = img[i] = 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return complex;
|
|
251
161
|
};
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
* @param out - result array
|
|
259
|
-
*/
|
|
260
|
-
export const spectrumMag = (complex, n = complex[0].length / 2, out = []) => {
|
|
261
|
-
const [real, img] = complex;
|
|
262
|
-
for (let i = 0; i < n; i++) {
|
|
263
|
-
out[i] = Math.hypot(real[i], img[i]);
|
|
264
|
-
}
|
|
265
|
-
return out;
|
|
162
|
+
const spectrumMag = (complex, n = complex[0].length / 2, out = []) => {
|
|
163
|
+
const [real, img] = complex;
|
|
164
|
+
for (let i = 0; i < n; i++) {
|
|
165
|
+
out[i] = Math.hypot(real[i], img[i]);
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
266
168
|
};
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
* no-op). If windowing was used to compute the FFT, the same should be provided
|
|
276
|
-
* to this function for correct results.
|
|
277
|
-
*
|
|
278
|
-
* **IMPORTANT:** If the FFT result has already been normalized using
|
|
279
|
-
* {@link normalizeFFT}, the scaling factor (`window` arg) MUST be set 1.0.
|
|
280
|
-
*
|
|
281
|
-
* By default only the first N/2 values are returned. If `db` is true, the
|
|
282
|
-
* spectrum values are converted to dBFS.
|
|
283
|
-
*
|
|
284
|
-
* - https://holometer.fnal.gov/GH_FFT.pdf
|
|
285
|
-
* - https://dsp.stackexchange.com/a/32080
|
|
286
|
-
* - https://dsp.stackexchange.com/a/14935
|
|
287
|
-
* - https://www.kvraudio.com/forum/viewtopic.php?t=276092
|
|
288
|
-
*
|
|
289
|
-
* @param complex -
|
|
290
|
-
* @param db -
|
|
291
|
-
* @param window -
|
|
292
|
-
* @param n -
|
|
293
|
-
* @param out -
|
|
294
|
-
*/
|
|
295
|
-
export const spectrumPow = (complex, db = false, window = 2 / complex[0].length, n = complex[0].length / 2, out = []) => {
|
|
296
|
-
const [real, img] = complex;
|
|
297
|
-
const scale = powerScale(window, 2);
|
|
298
|
-
for (let i = 0; i < n; i++) {
|
|
299
|
-
const p = real[i] ** 2 + img[i] ** 2;
|
|
300
|
-
out[i] = db ? magDb(Math.sqrt(p) * scale) : p * scale;
|
|
301
|
-
}
|
|
302
|
-
return out;
|
|
169
|
+
const spectrumPow = (complex, db = false, window = 2 / complex[0].length, n = complex[0].length / 2, out = []) => {
|
|
170
|
+
const [real, img] = complex;
|
|
171
|
+
const scale = powerScale(window, 2);
|
|
172
|
+
for (let i = 0; i < n; i++) {
|
|
173
|
+
const p = real[i] ** 2 + img[i] ** 2;
|
|
174
|
+
out[i] = db ? magDb(Math.sqrt(p) * scale) : p * scale;
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
303
177
|
};
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
* to avoid exploding floating point error magnitudes.
|
|
311
|
-
*
|
|
312
|
-
* @param complex - FFT result
|
|
313
|
-
* @param n - bin count
|
|
314
|
-
* @param out - result array
|
|
315
|
-
*/
|
|
316
|
-
export const spectrumPhase = (complex, n = complex[0].length / 2, out = []) => {
|
|
317
|
-
const [real, img] = complex;
|
|
318
|
-
for (let i = 0; i < n; i++) {
|
|
319
|
-
out[i] = Math.atan2(img[i], real[i]);
|
|
320
|
-
}
|
|
321
|
-
return out;
|
|
178
|
+
const spectrumPhase = (complex, n = complex[0].length / 2, out = []) => {
|
|
179
|
+
const [real, img] = complex;
|
|
180
|
+
for (let i = 0; i < n; i++) {
|
|
181
|
+
out[i] = Math.atan2(img[i], real[i]);
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
322
184
|
};
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
* @param m - number of result values
|
|
349
|
-
*/
|
|
350
|
-
export const fftFreq = (n, fs, m = n / 2) => {
|
|
351
|
-
const res = new Float64Array(m);
|
|
352
|
-
for (let i = 0; i <= m; i++) {
|
|
353
|
-
res[i] = binFreq(i, fs, n);
|
|
354
|
-
}
|
|
355
|
-
return res;
|
|
185
|
+
const freqBin = (f, fs, n) => f * n / fs | 0;
|
|
186
|
+
const binFreq = (bin, fs, n) => bin * fs / n;
|
|
187
|
+
const fftFreq = (n, fs, m = n / 2) => {
|
|
188
|
+
const res = new Float64Array(m);
|
|
189
|
+
for (let i = 0; i <= m; i++) {
|
|
190
|
+
res[i] = binFreq(i, fs, n);
|
|
191
|
+
}
|
|
192
|
+
return res;
|
|
193
|
+
};
|
|
194
|
+
export {
|
|
195
|
+
binFreq,
|
|
196
|
+
complexArray,
|
|
197
|
+
conjugate,
|
|
198
|
+
copyComplex,
|
|
199
|
+
denormalizeFFT,
|
|
200
|
+
fft,
|
|
201
|
+
fftFreq,
|
|
202
|
+
freqBin,
|
|
203
|
+
ifft,
|
|
204
|
+
normalizeFFT,
|
|
205
|
+
scaleFFT,
|
|
206
|
+
spectrumMag,
|
|
207
|
+
spectrumPhase,
|
|
208
|
+
spectrumPow,
|
|
209
|
+
thresholdFFT
|
|
356
210
|
};
|
package/filter-delay.js
CHANGED
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
import { clamp01 } from "@thi.ng/math/interval";
|
|
2
2
|
import { Delay } from "./delay.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return super.next(x + this.filter.next(this._buf[this._rpos] * this._feedback));
|
|
23
|
-
}
|
|
24
|
-
feedback() {
|
|
25
|
-
return this._feedback;
|
|
26
|
-
}
|
|
27
|
-
setFeedback(feedback) {
|
|
28
|
-
this._feedback = clamp01(feedback);
|
|
29
|
-
}
|
|
3
|
+
const filterFeedbackDelay = (n, filter, feedback) => new FilterFeedbackDelay(n, filter, feedback);
|
|
4
|
+
class FilterFeedbackDelay extends Delay {
|
|
5
|
+
constructor(n, filter, _feedback = 0.5) {
|
|
6
|
+
super(n, 0);
|
|
7
|
+
this.filter = filter;
|
|
8
|
+
this._feedback = _feedback;
|
|
9
|
+
this.setFeedback(_feedback);
|
|
10
|
+
}
|
|
11
|
+
next(x) {
|
|
12
|
+
return super.next(
|
|
13
|
+
x + this.filter.next(this._buf[this._rpos] * this._feedback)
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
feedback() {
|
|
17
|
+
return this._feedback;
|
|
18
|
+
}
|
|
19
|
+
setFeedback(feedback) {
|
|
20
|
+
this._feedback = clamp01(feedback);
|
|
21
|
+
}
|
|
30
22
|
}
|
|
23
|
+
export {
|
|
24
|
+
FilterFeedbackDelay,
|
|
25
|
+
filterFeedbackDelay
|
|
26
|
+
};
|
package/filter-response.js
CHANGED
|
@@ -2,39 +2,28 @@ import { cossin } from "@thi.ng/math/angle";
|
|
|
2
2
|
import { TAU } from "@thi.ng/math/api";
|
|
3
3
|
import { magDb } from "./convert.js";
|
|
4
4
|
import { line } from "./line.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* - https://www.earlevel.com/main/2016/12/01/evaluating-filter-frequency-response/
|
|
13
|
-
* - https://www.earlevel.com/main/2016/12/08/filter-frequency-response-grapher/
|
|
14
|
-
* - https://github.com/mohayonao/freqr
|
|
15
|
-
*
|
|
16
|
-
* @param zeroes -
|
|
17
|
-
* @param poles -
|
|
18
|
-
* @param freq -
|
|
19
|
-
* @param db -
|
|
20
|
-
*/
|
|
21
|
-
export const filterResponseRaw = (zeroes, poles, freq, db = true) => {
|
|
22
|
-
const w0 = TAU * freq;
|
|
23
|
-
const [cp, sp] = convolve(poles, w0);
|
|
24
|
-
const [cz, sz] = convolve(zeroes, w0);
|
|
25
|
-
const mag = Math.sqrt((cz * cz + sz * sz) / (cp * cp + sp * sp));
|
|
26
|
-
const phase = Math.atan2(sp, cp) - Math.atan2(sz, cz);
|
|
27
|
-
return { freq, phase, mag: db ? magDb(mag) : mag };
|
|
5
|
+
const filterResponseRaw = (zeroes, poles, freq, db = true) => {
|
|
6
|
+
const w0 = TAU * freq;
|
|
7
|
+
const [cp, sp] = convolve(poles, w0);
|
|
8
|
+
const [cz, sz] = convolve(zeroes, w0);
|
|
9
|
+
const mag = Math.sqrt((cz * cz + sz * sz) / (cp * cp + sp * sp));
|
|
10
|
+
const phase = Math.atan2(sp, cp) - Math.atan2(sz, cz);
|
|
11
|
+
return { freq, phase, mag: db ? magDb(mag) : mag };
|
|
28
12
|
};
|
|
29
|
-
|
|
30
|
-
|
|
13
|
+
const filterResponse = (coeffs, freq, db) => filterResponseRaw(coeffs.zeroes, coeffs.poles, freq, db);
|
|
14
|
+
const freqRange = (fstart, fend, num) => line(fstart, fend, num - 1).take(num);
|
|
31
15
|
const convolve = (coeffs, w0) => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
16
|
+
let c = 0;
|
|
17
|
+
let s = 0;
|
|
18
|
+
for (let i = coeffs.length; i-- > 0; ) {
|
|
19
|
+
const k = cossin(w0 * i, coeffs[i]);
|
|
20
|
+
c += k[0];
|
|
21
|
+
s += k[1];
|
|
22
|
+
}
|
|
23
|
+
return [c, s];
|
|
24
|
+
};
|
|
25
|
+
export {
|
|
26
|
+
filterResponse,
|
|
27
|
+
filterResponseRaw,
|
|
28
|
+
freqRange
|
|
40
29
|
};
|