carta1 1.0.15 → 1.0.17
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/codec/transforms/fft.js
CHANGED
|
@@ -1,69 +1,224 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Carta1 Audio Codec - Fast Fourier Transform
|
|
3
|
-
*
|
|
4
|
-
* This module implements a radix-2 Cooley-Tukey FFT algorithm for frequency domain
|
|
5
|
-
* analysis used in psychoacoustic modeling and transient detection.
|
|
3
|
+
* Optimized radix-2 Cooley-Tukey FFT implementation
|
|
6
4
|
*/
|
|
7
|
-
|
|
8
5
|
export class FFT {
|
|
6
|
+
static _cache = new Map()
|
|
7
|
+
|
|
9
8
|
/**
|
|
10
|
-
* Perform in-place FFT on complex data
|
|
11
|
-
* @param {Float32Array} real - Real part of input/output data
|
|
12
|
-
* @param {Float32Array} imag - Imaginary part of input/output data
|
|
9
|
+
* Perform an in-place forward FFT on complex data.
|
|
10
|
+
* @param {Float32Array} real - Real part of the input/output data
|
|
11
|
+
* @param {Float32Array} imag - Imaginary part of the input/output data
|
|
13
12
|
*/
|
|
14
13
|
static fft(real, imag) {
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
const n = real.length
|
|
15
|
+
if (n !== imag.length) {
|
|
16
|
+
throw new Error('Real and imaginary arrays must have the same length.')
|
|
17
|
+
}
|
|
18
|
+
if ((n & (n - 1)) !== 0) {
|
|
19
|
+
throw new Error('Input size must be a power of 2.')
|
|
20
|
+
}
|
|
21
|
+
if (n <= 1) return
|
|
22
|
+
|
|
23
|
+
let cache = FFT._cache.get(n)
|
|
24
|
+
if (!cache) {
|
|
25
|
+
cache = FFT._buildCache(n)
|
|
26
|
+
FFT._cache.set(n, cache)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { bitRev, cos: cosTbl, sin: sinTbl } = cache
|
|
30
|
+
|
|
31
|
+
// Bit-reversal permutation with improved memory access pattern
|
|
32
|
+
for (let i = 0; i < n; i++) {
|
|
33
|
+
const j = bitRev[i]
|
|
34
|
+
if (j > i) {
|
|
35
|
+
// Use temporary variables to reduce array access
|
|
36
|
+
const tempR = real[i]
|
|
37
|
+
const tempI = imag[i]
|
|
38
|
+
real[i] = real[j]
|
|
39
|
+
imag[i] = imag[j]
|
|
40
|
+
real[j] = tempR
|
|
41
|
+
imag[j] = tempI
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Optimized butterfly computations for small strides
|
|
46
|
+
// Stride 2 - no twiddle factors needed
|
|
47
|
+
for (let i = 0; i < n; i += 2) {
|
|
48
|
+
const evenR = real[i]
|
|
49
|
+
const evenI = imag[i]
|
|
50
|
+
const oddR = real[i + 1]
|
|
51
|
+
const oddI = imag[i + 1]
|
|
52
|
+
|
|
53
|
+
real[i] = evenR + oddR
|
|
54
|
+
imag[i] = evenI + oddI
|
|
55
|
+
real[i + 1] = evenR - oddR
|
|
56
|
+
imag[i + 1] = evenI - oddI
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Stride 4 - simple twiddle factors
|
|
60
|
+
if (n >= 4) {
|
|
61
|
+
for (let i = 0; i < n; i += 4) {
|
|
62
|
+
// First pair (W^0 = 1)
|
|
63
|
+
let evenR = real[i]
|
|
64
|
+
let evenI = imag[i]
|
|
65
|
+
let oddR = real[i + 2]
|
|
66
|
+
let oddI = imag[i + 2]
|
|
67
|
+
|
|
68
|
+
real[i] = evenR + oddR
|
|
69
|
+
imag[i] = evenI + oddI
|
|
70
|
+
real[i + 2] = evenR - oddR
|
|
71
|
+
imag[i + 2] = evenI - oddI
|
|
72
|
+
|
|
73
|
+
// Second pair (W^1 = -i)
|
|
74
|
+
evenR = real[i + 1]
|
|
75
|
+
evenI = imag[i + 1]
|
|
76
|
+
oddR = real[i + 3]
|
|
77
|
+
oddI = imag[i + 3]
|
|
78
|
+
|
|
79
|
+
const tR = oddI // Multiplication by -i
|
|
80
|
+
const tI = -oddR
|
|
81
|
+
|
|
82
|
+
real[i + 1] = evenR + tR
|
|
83
|
+
imag[i + 1] = evenI + tI
|
|
84
|
+
real[i + 3] = evenR - tR
|
|
85
|
+
imag[i + 3] = evenI - tI
|
|
27
86
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Stride 8 - unrolled for better performance
|
|
90
|
+
if (n >= 8) {
|
|
91
|
+
for (let i = 0; i < n; i += 8) {
|
|
92
|
+
// W^0 = 1
|
|
93
|
+
let idx0 = i
|
|
94
|
+
let idx4 = i + 4
|
|
95
|
+
let r0 = real[idx0],
|
|
96
|
+
i0 = imag[idx0]
|
|
97
|
+
let r4 = real[idx4],
|
|
98
|
+
i4 = imag[idx4]
|
|
99
|
+
real[idx0] = r0 + r4
|
|
100
|
+
imag[idx0] = i0 + i4
|
|
101
|
+
real[idx4] = r0 - r4
|
|
102
|
+
imag[idx4] = i0 - i4
|
|
103
|
+
|
|
104
|
+
// W^1 = cos(-π/4) - i*sin(-π/4)
|
|
105
|
+
let idx1 = i + 1
|
|
106
|
+
let idx5 = i + 5
|
|
107
|
+
r0 = real[idx1]
|
|
108
|
+
i0 = imag[idx1]
|
|
109
|
+
r4 = real[idx5]
|
|
110
|
+
i4 = imag[idx5]
|
|
111
|
+
const sqrt2_2 = 0.7071067811865476
|
|
112
|
+
let tR = sqrt2_2 * (r4 + i4)
|
|
113
|
+
let tI = sqrt2_2 * (i4 - r4)
|
|
114
|
+
real[idx1] = r0 + tR
|
|
115
|
+
imag[idx1] = i0 + tI
|
|
116
|
+
real[idx5] = r0 - tR
|
|
117
|
+
imag[idx5] = i0 - tI
|
|
118
|
+
|
|
119
|
+
// W^2 = -i
|
|
120
|
+
let idx2 = i + 2
|
|
121
|
+
let idx6 = i + 6
|
|
122
|
+
r0 = real[idx2]
|
|
123
|
+
i0 = imag[idx2]
|
|
124
|
+
r4 = real[idx6]
|
|
125
|
+
i4 = imag[idx6]
|
|
126
|
+
tR = i4
|
|
127
|
+
tI = -r4
|
|
128
|
+
real[idx2] = r0 + tR
|
|
129
|
+
imag[idx2] = i0 + tI
|
|
130
|
+
real[idx6] = r0 - tR
|
|
131
|
+
imag[idx6] = i0 - tI
|
|
132
|
+
|
|
133
|
+
// W^3 = cos(-3π/4) - i*sin(-3π/4)
|
|
134
|
+
let idx3 = i + 3
|
|
135
|
+
let idx7 = i + 7
|
|
136
|
+
r0 = real[idx3]
|
|
137
|
+
i0 = imag[idx3]
|
|
138
|
+
r4 = real[idx7]
|
|
139
|
+
i4 = imag[idx7]
|
|
140
|
+
tR = sqrt2_2 * (-r4 + i4)
|
|
141
|
+
tI = sqrt2_2 * (-r4 - i4)
|
|
142
|
+
real[idx3] = r0 + tR
|
|
143
|
+
imag[idx3] = i0 + tI
|
|
144
|
+
real[idx7] = r0 - tR
|
|
145
|
+
imag[idx7] = i0 - tI
|
|
31
146
|
}
|
|
32
147
|
}
|
|
33
148
|
|
|
34
|
-
// Cooley-Tukey
|
|
35
|
-
for (let stride =
|
|
36
|
-
const halfStride = stride
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let
|
|
43
|
-
let
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
149
|
+
// General Cooley-Tukey butterflies with optimized inner loop
|
|
150
|
+
for (let stride = 16; stride <= n; stride <<= 1) {
|
|
151
|
+
const halfStride = stride >>> 1
|
|
152
|
+
const step = n / stride
|
|
153
|
+
|
|
154
|
+
// Process each group
|
|
155
|
+
for (let grp = 0; grp < n; grp += stride) {
|
|
156
|
+
// First butterfly (W^0 = 1) - no multiplication needed
|
|
157
|
+
let evenIdx = grp
|
|
158
|
+
let oddIdx = grp + halfStride
|
|
159
|
+
const r0 = real[evenIdx]
|
|
160
|
+
const i0 = imag[evenIdx]
|
|
161
|
+
const r1 = real[oddIdx]
|
|
162
|
+
const i1 = imag[oddIdx]
|
|
163
|
+
real[evenIdx] = r0 + r1
|
|
164
|
+
imag[evenIdx] = i0 + i1
|
|
165
|
+
real[oddIdx] = r0 - r1
|
|
166
|
+
imag[oddIdx] = i0 - i1
|
|
167
|
+
|
|
168
|
+
// Remaining butterflies with twiddle factors
|
|
169
|
+
let twiddleIdx = step
|
|
170
|
+
for (let k = 1; k < halfStride; k++) {
|
|
171
|
+
evenIdx = grp + k
|
|
172
|
+
oddIdx = evenIdx + halfStride
|
|
173
|
+
|
|
174
|
+
const cosW = cosTbl[twiddleIdx]
|
|
175
|
+
const sinW = sinTbl[twiddleIdx]
|
|
176
|
+
|
|
177
|
+
const oddR = real[oddIdx]
|
|
178
|
+
const oddI = imag[oddIdx]
|
|
179
|
+
const tR = oddR * cosW - oddI * sinW
|
|
180
|
+
const tI = oddR * sinW + oddI * cosW
|
|
181
|
+
|
|
182
|
+
const evenR = real[evenIdx]
|
|
183
|
+
const evenI = imag[evenIdx]
|
|
184
|
+
|
|
185
|
+
real[evenIdx] = evenR + tR
|
|
186
|
+
imag[evenIdx] = evenI + tI
|
|
187
|
+
real[oddIdx] = evenR - tR
|
|
188
|
+
imag[oddIdx] = evenI - tI
|
|
189
|
+
|
|
190
|
+
twiddleIdx += step
|
|
65
191
|
}
|
|
66
192
|
}
|
|
67
193
|
}
|
|
68
194
|
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Build cache tables for FFT of size n
|
|
198
|
+
* @private
|
|
199
|
+
*/
|
|
200
|
+
static _buildCache(n) {
|
|
201
|
+
const log2n = Math.log2(n) | 0
|
|
202
|
+
|
|
203
|
+
// Optimized bit-reversal using iterative approach
|
|
204
|
+
const bitRev = new Uint32Array(n)
|
|
205
|
+
for (let i = 1; i < n; i++) {
|
|
206
|
+
bitRev[i] = (bitRev[i >>> 1] >>> 1) | ((i & 1) << (log2n - 1))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Pre-compute twiddle factors with better precision
|
|
210
|
+
const halfN = n >>> 1
|
|
211
|
+
const cosTbl = new Float32Array(halfN)
|
|
212
|
+
const sinTbl = new Float32Array(halfN)
|
|
213
|
+
|
|
214
|
+
// Use higher precision calculation then cast to Float32
|
|
215
|
+
const angleStep = (-2 * Math.PI) / n
|
|
216
|
+
for (let i = 0; i < halfN; i++) {
|
|
217
|
+
const angle = angleStep * i
|
|
218
|
+
cosTbl[i] = Math.fround(Math.cos(angle))
|
|
219
|
+
sinTbl[i] = Math.fround(Math.sin(angle))
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return { bitRev, cos: cosTbl, sin: sinTbl }
|
|
223
|
+
}
|
|
69
224
|
}
|