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.
@@ -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 size = real.length
16
- if (size === 1) return
17
-
18
- const bits = Math.log2(size)
19
-
20
- // Bit reversal
21
- for (let i = 0; i < size; i++) {
22
- let reversed = 0
23
- let temp = i
24
- for (let b = 0; b < bits; b++) {
25
- reversed = (reversed << 1) | (temp & 1)
26
- temp >>= 1
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
- if (reversed > i) {
29
- ;[real[i], real[reversed]] = [real[reversed], real[i]]
30
- ;[imag[i], imag[reversed]] = [imag[reversed], imag[i]]
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 decimation-in-time
35
- for (let stride = 2; stride <= size; stride <<= 1) {
36
- const halfStride = stride >> 1
37
- const angle = (-2 * Math.PI) / stride
38
- const wReal = Math.cos(angle)
39
- const wImag = Math.sin(angle)
40
-
41
- for (let start = 0; start < size; start += stride) {
42
- let twiddleReal = 1
43
- let twiddleImag = 0
44
-
45
- for (let k = 0; k < halfStride; k++) {
46
- const evenIndex = start + k
47
- const oddIndex = evenIndex + halfStride
48
-
49
- const evenReal = real[evenIndex]
50
- const evenImag = imag[evenIndex]
51
- const oddReal = real[oddIndex]
52
- const oddImag = imag[oddIndex]
53
-
54
- const tReal = oddReal * twiddleReal - oddImag * twiddleImag
55
- const tImag = oddReal * twiddleImag + oddImag * twiddleReal
56
-
57
- real[evenIndex] = evenReal + tReal
58
- imag[evenIndex] = evenImag + tImag
59
- real[oddIndex] = evenReal - tReal
60
- imag[oddIndex] = evenImag - tImag
61
-
62
- const nextReal = twiddleReal * wReal - twiddleImag * wImag
63
- twiddleImag = twiddleReal * wImag + twiddleImag * wReal
64
- twiddleReal = nextReal
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
  }