carta1 1.0.16 → 1.0.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.
@@ -1,21 +1,14 @@
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
5
  export class FFT {
8
- /**
9
- * @private
10
- * Caches pre-computed tables for different FFT sizes.
11
- * @type {Map<number, { bitRev: Uint32Array, cos: Float32Array, sin: Float32Array }>}
12
- */
13
6
  static _cache = new Map()
14
7
 
15
8
  /**
16
9
  * Perform an in-place forward FFT on complex data.
17
- * @param {Float32Array} real - Real part of the input/output data. The array is modified in-place.
18
- * @param {Float32Array} imag - Imaginary part of the input/output data. The array is modified in-place.
10
+ * @param {Float32Array} real - Real part of the input/output data
11
+ * @param {Float32Array} imag - Imaginary part of the input/output data
19
12
  */
20
13
  static fft(real, imag) {
21
14
  const n = real.length
@@ -29,110 +22,203 @@ export class FFT {
29
22
 
30
23
  let cache = FFT._cache.get(n)
31
24
  if (!cache) {
32
- const log2n = Math.log2(n)
33
-
34
- const bitRev = new Uint32Array(n)
35
- for (let i = 1; i < n; i++) {
36
- bitRev[i] = (bitRev[i >>> 1] >>> 1) | ((i & 1) << (log2n - 1))
37
- }
38
-
39
- const cosTbl = new Float32Array(n / 2)
40
- const sinTbl = new Float32Array(n / 2)
41
- for (let i = 0; i < n / 2; i++) {
42
- const angle = (-2 * Math.PI * i) / n
43
- cosTbl[i] = Math.cos(angle)
44
- sinTbl[i] = Math.sin(angle)
45
- }
46
- cache = { bitRev, cos: cosTbl, sin: sinTbl }
25
+ cache = FFT._buildCache(n)
47
26
  FFT._cache.set(n, cache)
48
27
  }
49
28
 
50
29
  const { bitRev, cos: cosTbl, sin: sinTbl } = cache
51
30
 
31
+ // Bit-reversal permutation with improved memory access pattern
52
32
  for (let i = 0; i < n; i++) {
53
33
  const j = bitRev[i]
54
34
  if (j > i) {
55
- let temp
56
- temp = real[i]
35
+ // Use temporary variables to reduce array access
36
+ const tempR = real[i]
37
+ const tempI = imag[i]
57
38
  real[i] = real[j]
58
- real[j] = temp
59
- temp = imag[i]
60
39
  imag[i] = imag[j]
61
- imag[j] = temp
40
+ real[j] = tempR
41
+ imag[j] = tempI
62
42
  }
63
43
  }
64
44
 
45
+ // Optimized butterfly computations for small strides
46
+ // Stride 2 - no twiddle factors needed
65
47
  for (let i = 0; i < n; i += 2) {
66
- const i1 = i + 1
67
- const tReal = real[i1]
68
- const tImag = imag[i1]
69
-
70
- const evenReal = real[i]
71
- const evenImag = imag[i]
72
-
73
- real[i] = evenReal + tReal
74
- imag[i] = evenImag + tImag
75
- real[i1] = evenReal - tReal
76
- imag[i1] = evenImag - tImag
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
77
57
  }
78
58
 
59
+ // Stride 4 - simple twiddle factors
79
60
  if (n >= 4) {
80
61
  for (let i = 0; i < n; i += 4) {
81
- const evenIndex1 = i
82
- const oddIndex1 = i + 2
83
- const evenReal1 = real[evenIndex1]
84
- const evenImag1 = imag[evenIndex1]
85
- const tReal1 = real[oddIndex1]
86
- const tImag1 = imag[oddIndex1]
87
-
88
- real[evenIndex1] = evenReal1 + tReal1
89
- imag[evenIndex1] = evenImag1 + tImag1
90
- real[oddIndex1] = evenReal1 - tReal1
91
- imag[oddIndex1] = evenImag1 - tImag1
92
-
93
- const evenIndex2 = i + 1
94
- const oddIndex2 = i + 3
95
-
96
- const tReal2 = imag[oddIndex2]
97
- const tImag2 = -real[oddIndex2]
98
-
99
- const evenReal2 = real[evenIndex2]
100
- const evenImag2 = imag[evenIndex2]
101
-
102
- real[evenIndex2] = evenReal2 + tReal2
103
- imag[evenIndex2] = evenImag2 + tImag2
104
- real[oddIndex2] = evenReal2 - tReal2
105
- imag[oddIndex2] = evenImag2 - tImag2
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
86
+ }
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
106
146
  }
107
147
  }
108
148
 
109
- for (let stride = 8; stride <= n; stride <<= 1) {
149
+ // General Cooley-Tukey butterflies with optimized inner loop
150
+ for (let stride = 16; stride <= n; stride <<= 1) {
110
151
  const halfStride = stride >>> 1
111
152
  const step = n / stride
112
- for (let i = 0; i < n; i += stride) {
113
- for (
114
- let k = 0, twiddleIndex = 0;
115
- k < halfStride;
116
- k++, twiddleIndex += step
117
- ) {
118
- const evenIndex = i + k
119
- const oddIndex = evenIndex + halfStride
120
-
121
- const cos_t = cosTbl[twiddleIndex]
122
- const sin_t = sinTbl[twiddleIndex]
123
-
124
- const tReal = real[oddIndex] * cos_t - imag[oddIndex] * sin_t
125
- const tImag = real[oddIndex] * sin_t + imag[oddIndex] * cos_t
126
-
127
- const evenReal = real[evenIndex]
128
- const evenImag = imag[evenIndex]
129
-
130
- real[evenIndex] = evenReal + tReal
131
- imag[evenIndex] = evenImag + tImag
132
- real[oddIndex] = evenReal - tReal
133
- imag[oddIndex] = evenImag - tImag
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
134
191
  }
135
192
  }
136
193
  }
137
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
+ }
138
224
  }
package/codec/utils.js CHANGED
@@ -146,6 +146,46 @@ export function withStereo(monoTransform, ...args) {
146
146
  }
147
147
  }
148
148
 
149
+ /**
150
+ * Reverses the spectral order of coefficients for mid/high band processing
151
+ *
152
+ * Used in both MDCT and IMDCT stages to handle spectral reversal required
153
+ * by the ATRAC1 format for mid and high frequency bands.
154
+ *
155
+ * @param {Float32Array} spectrum - Input spectrum coefficients
156
+ * @param {Object} reversalBuffers - Pre-allocated buffers for different spectrum sizes
157
+ * @returns {Float32Array} Spectrum with reversed coefficient order
158
+ */
159
+ export function reverseSpectrum(spectrum, reversalBuffers) {
160
+ const reversed = reversalBuffers[spectrum.length]
161
+ for (let i = 0; i < spectrum.length; i++) {
162
+ reversed[i] = spectrum[spectrum.length - 1 - i]
163
+ }
164
+ return reversed
165
+ }
166
+
167
+ /**
168
+ * Calculates the starting offset for a frequency band in the coefficient array
169
+ *
170
+ * @param {number} bandIndex - Band index (0=low, 1=mid, 2=high)
171
+ * @returns {number} Starting offset in the 512-sample coefficient array
172
+ */
173
+ export function calculateBandOffset(bandIndex) {
174
+ return bandIndex === 0 ? 0 : bandIndex === 1 ? 128 : 256
175
+ }
176
+
177
+ /**
178
+ * Extracts coefficients for a specific frequency band from the full coefficient array
179
+ *
180
+ * @param {Float32Array} coefficients - Full 512-sample coefficient array
181
+ * @param {number} bandIndex - Band index (0=low, 1=mid, 2=high)
182
+ * @returns {Float32Array} Subarray containing coefficients for the specified band
183
+ */
184
+ export function extractBandCoefficients(coefficients, bandIndex) {
185
+ const offsets = [0, 128, 256, 512]
186
+ return coefficients.subarray(offsets[bandIndex], offsets[bandIndex + 1])
187
+ }
188
+
149
189
  // Stereo-adapted utility functions
150
190
  export const withStereoFlushSamples = withStereo(withFlushSamples)
151
191
  export const withStereoDelayCompensation = withStereo(withDelayCompensation)