carta1 1.0.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.
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Carta1 Audio Codec - Encoding Pipeline
3
+ *
4
+ * The encoding pipeline transforms PCM audio samples into ATRAC1 compressed format
5
+ * through a series of processing stages:
6
+ *
7
+ * 1. QMF Analysis: Splits audio into frequency bands using quadrature mirror filters
8
+ * 2. Block Selection: Detects transients to choose appropriate transform block sizes
9
+ * 3. MDCT Transform: Applies Modified Discrete Cosine Transform to frequency bands
10
+ * 4. Quantization: Performs psychoacoustic analysis, bit allocation, and quantization
11
+ *
12
+ * Each stage is implemented as a functional pipeline component that can be composed
13
+ * using the pipe utility. The pipeline maintains state through a shared context
14
+ * containing buffer pools and encoding options.
15
+ */
16
+
17
+ import { pipe, throwError } from '../utils.js'
18
+ import { qmfAnalysis } from '../transforms/qmf.js'
19
+ import { mdct64, mdct256, mdct512 } from '../transforms/mdct.js'
20
+ import {
21
+ groupIntoBFUs,
22
+ findScaleFactor,
23
+ quantize,
24
+ } from '../coding/quantization.js'
25
+ import { allocateBits } from '../coding/bitallocation.js'
26
+ import { BufferPool } from '../core/buffers.js'
27
+ import { EncoderOptions } from '../core/options.js'
28
+ import {
29
+ WORD_LENGTH_BITS,
30
+ WINDOW_SHORT,
31
+ FFT_SIZE_LOW,
32
+ FFT_SIZE_MID,
33
+ FFT_SIZE_HIGH,
34
+ PSYMODEL_FFT_SIZE,
35
+ MDCT_SIZE_SHORT,
36
+ MDCT_SIZE_MID,
37
+ MDCT_SIZE_LONG,
38
+ } from '../core/constants.js'
39
+ import { performFFT, detectTransient } from '../analysis/transient.js'
40
+ import { psychoAnalysis } from '../analysis/psychoacoustics.js'
41
+
42
+ /**
43
+ * QMF Analysis Stage - Splits audio into frequency bands
44
+ *
45
+ * Performs two-stage QMF analysis to decompose the input signal into three bands:
46
+ * - Low band (0-5.5kHz): processed at full resolution
47
+ * - Mid band (5.5-11kHz): processed at half resolution
48
+ * - High band (11-22kHz): processed at quarter resolution with delay compensation
49
+ *
50
+ * @param {Object} context - Pipeline context containing bufferPool
51
+ * @param {BufferPool} context.bufferPool - Shared buffer pool for efficient memory management
52
+ * @returns {Function} Stage function that processes PCM samples
53
+ * @throws {Error} If bufferPool is not provided in context
54
+ */
55
+ function qmfAnalysisStage(context) {
56
+ const bufferPool =
57
+ context?.bufferPool ??
58
+ throwError('qmfAnalysisStage: bufferPool is required')
59
+ const delays = bufferPool.qmfDelays
60
+
61
+ /**
62
+ * Process PCM samples through QMF analysis
63
+ * @param {Float32Array} pcmSamples - Input PCM samples
64
+ * @returns {Object} Analysis result containing frequency bands
65
+ * @returns {Array<Float32Array>} returns.bands - Three frequency bands [low, mid, high]
66
+ */
67
+ return (pcmSamples) => {
68
+ const stage1 = qmfAnalysis(
69
+ pcmSamples,
70
+ delays.lowBand,
71
+ bufferPool.qmfWorkBuffers
72
+ )
73
+ delays.lowBand = stage1.newDelay
74
+
75
+ const stage2 = qmfAnalysis(
76
+ stage1.lowBand,
77
+ delays.midBand,
78
+ bufferPool.qmfWorkBuffers
79
+ )
80
+ delays.midBand = stage2.newDelay
81
+
82
+ const delayedHigh =
83
+ bufferPool.qmfWorkBuffers.highBandDelay[stage1.highBand.length]
84
+ delayedHigh.set(delays.highBand)
85
+ delayedHigh.set(stage1.highBand, delays.highBand.length)
86
+
87
+ const highBand = delayedHigh.slice(0, stage1.highBand.length)
88
+ delays.highBand = delayedHigh.slice(stage1.highBand.length)
89
+
90
+ return {
91
+ bands: [stage2.lowBand, stage2.highBand, highBand],
92
+ }
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Block Selection Stage - Detects transients to choose transform block sizes
98
+ *
99
+ * Analyzes frequency bands using FFT and transient detection to determine
100
+ * appropriate MDCT block sizes. Short blocks are used for transient signals
101
+ * to prevent pre-echo artifacts, while long blocks provide better frequency
102
+ * resolution for steady-state signals.
103
+ *
104
+ * @param {Object} context - Pipeline context containing bufferPool
105
+ * @param {BufferPool} context.bufferPool - Shared buffer pool for efficient memory management
106
+ * @returns {Function} Stage function that processes QMF analysis results
107
+ * @throws {Error} If bufferPool is not provided in context
108
+ */
109
+ function blockSelectorStage(context) {
110
+ const bufferPool =
111
+ context?.bufferPool ??
112
+ throwError('blockSelectorStage: bufferPool is required')
113
+ const options =
114
+ context?.options ?? throwError('blockSelectorStage: options is required')
115
+
116
+ /**
117
+ * Analyze frequency bands and determine block modes
118
+ * @param {Object} input - QMF analysis results
119
+ * @param {Array<Float32Array>} input.bands - Three frequency bands [low, mid, high]
120
+ * @returns {Object} Block selection results
121
+ * @returns {Array<Float32Array>} returns.bands - Original frequency bands
122
+ * @returns {Array<number>} returns.blockModes - Block mode indices for each band
123
+ */
124
+ return (input) => {
125
+ const { bands } = input
126
+ const [lowBand, midBand, highBand] = bands
127
+
128
+ const lowCoeffs = performFFT(lowBand, FFT_SIZE_LOW)
129
+ const midCoeffs = performFFT(midBand, FFT_SIZE_MID)
130
+ const highCoeffs = performFFT(highBand, FFT_SIZE_HIGH)
131
+
132
+ const lowTransient = detectTransient(
133
+ lowCoeffs,
134
+ bufferPool.transientDetection.prevLowCoeffs,
135
+ options.transientThresholdLow
136
+ )
137
+ const midTransient = detectTransient(
138
+ midCoeffs,
139
+ bufferPool.transientDetection.prevMidCoeffs,
140
+ options.transientThresholdMid
141
+ )
142
+ const highTransient = detectTransient(
143
+ highCoeffs,
144
+ bufferPool.transientDetection.prevHighCoeffs,
145
+ options.transientThresholdHigh
146
+ )
147
+
148
+ bufferPool.transientDetection.prevLowCoeffs = lowCoeffs
149
+ bufferPool.transientDetection.prevMidCoeffs = midCoeffs
150
+ bufferPool.transientDetection.prevHighCoeffs = highCoeffs
151
+
152
+ return {
153
+ bands,
154
+ blockModes: [
155
+ lowTransient ? 2 : 0,
156
+ midTransient ? 2 : 0,
157
+ highTransient ? 3 : 0,
158
+ ],
159
+ }
160
+ }
161
+ }
162
+
163
+ /**
164
+ * MDCT Transform Stage - Applies Modified Discrete Cosine Transform
165
+ *
166
+ * Transforms time-domain samples to frequency-domain coefficients using MDCT.
167
+ * The transform size depends on the block mode determined by transient detection:
168
+ * - Long blocks: 512/256 samples for better frequency resolution
169
+ * - Short blocks: 64 samples for better time resolution
170
+ *
171
+ * Applies windowing and overlap-add processing to prevent blocking artifacts.
172
+ * Mid and high bands are spectrally reversed to match ATRAC1 format.
173
+ *
174
+ * @param {Object} context - Pipeline context containing bufferPool
175
+ * @param {BufferPool} context.bufferPool - Shared buffer pool for efficient memory management
176
+ * @returns {Function} Stage function that processes block selection results
177
+ * @throws {Error} If bufferPool is not provided in context
178
+ */
179
+ function mdctStage(context) {
180
+ const bufferPool =
181
+ context?.bufferPool ?? throwError('mdctStage: bufferPool is required')
182
+ const overlapBuffers = bufferPool.mdctOverlap
183
+
184
+ /**
185
+ * Transform frequency bands using MDCT
186
+ * @param {Object} input - Block selection results
187
+ * @param {Array<Float32Array>} input.bands - Three frequency bands
188
+ * @param {Array<number>} input.blockModes - Block mode indices for each band
189
+ * @param {any} input.originalFrame - Original frame data (passed through)
190
+ * @returns {Object} MDCT transform results
191
+ * @returns {Array<Float32Array>} returns.bands - Original frequency bands
192
+ * @returns {Float32Array} returns.coefficients - MDCT coefficients (512 samples)
193
+ * @returns {Array<number>} returns.blockModes - Block mode indices
194
+ * @returns {any} returns.originalFrame - Original frame data
195
+ */
196
+ return (input) => {
197
+ const { bands, blockModes, originalFrame } = input
198
+
199
+ const coefficients = new Float32Array(512)
200
+
201
+ for (let band = 0; band < 3; band++) {
202
+ const samples = bands[band]
203
+ const bandSize = band === 2 ? 256 : 128
204
+ const numBlocks = 1 << blockModes[band]
205
+ const blockSize = numBlocks === 1 ? bandSize : 32
206
+
207
+ const mdctFunc =
208
+ numBlocks === 1 ? (band === 2 ? mdct512 : mdct256) : mdct64
209
+ const mdctSize =
210
+ numBlocks === 1
211
+ ? band === 2
212
+ ? MDCT_SIZE_LONG
213
+ : MDCT_SIZE_MID
214
+ : MDCT_SIZE_SHORT
215
+ const windowStart = numBlocks === 1 ? (band === 2 ? 112 : 48) : 0
216
+
217
+ const offset = band === 0 ? 0 : band === 1 ? 128 : 256
218
+
219
+ for (let block = 0; block < numBlocks; block++) {
220
+ const blockStart = block * blockSize
221
+ const mdctInput = bufferPool.transformBuffers[mdctSize]
222
+ mdctInput.fill(0)
223
+
224
+ mdctInput.set(overlapBuffers[band], windowStart)
225
+
226
+ for (let i = 0; i < 32; i++) {
227
+ const tailValue = samples[blockStart + blockSize - 32 + i]
228
+ overlapBuffers[band][i] = WINDOW_SHORT[i] * tailValue
229
+ samples[blockStart + blockSize - 32 + i] =
230
+ tailValue * WINDOW_SHORT[31 - i]
231
+ }
232
+
233
+ mdctInput.set(
234
+ samples.subarray(blockStart, blockStart + blockSize),
235
+ windowStart + 32
236
+ )
237
+
238
+ let spectrum = mdctFunc.transform(mdctInput, bufferPool.mdctBuffers)
239
+
240
+ if (band > 0) {
241
+ const reversed = bufferPool.reversalBuffers[spectrum.length]
242
+ reversed.fill(0)
243
+ for (let i = 0; i < spectrum.length; i++) {
244
+ reversed[i] = spectrum[spectrum.length - 1 - i]
245
+ }
246
+ spectrum = reversed
247
+ }
248
+
249
+ coefficients.set(spectrum, offset + blockStart)
250
+ }
251
+ }
252
+
253
+ return { bands, coefficients, blockModes, originalFrame }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Quantization Stage - Performs psychoacoustic analysis and quantization
259
+ *
260
+ * The final encoding stage that:
261
+ * 1. Groups coefficients into Block Floating Units (BFUs)
262
+ * 2. Performs psychoacoustic analysis to determine masking thresholds
263
+ * 3. Allocates bits based on perceptual importance
264
+ * 4. Quantizes coefficients using scale factors and word lengths
265
+ *
266
+ * Uses a perceptual model to ensure quantization noise remains below
267
+ * the masking threshold, maximizing quality within the bit budget.
268
+ *
269
+ * @param {Object} context - Pipeline context containing bufferPool and options
270
+ * @param {BufferPool} context.bufferPool - Shared buffer pool for efficient memory management
271
+ * @param {EncoderOptions} context.options - Encoding configuration options
272
+ * @returns {Function} Stage function that processes MDCT results
273
+ * @throws {Error} If bufferPool or options are not provided in context
274
+ */
275
+ function quantizationStage(context) {
276
+ const bufferPool =
277
+ context?.bufferPool ??
278
+ throwError('quantizationStage: bufferPool is required')
279
+ const options =
280
+ context?.options ?? throwError('quantizationStage: options is required')
281
+
282
+ /**
283
+ * Perform psychoacoustic analysis and quantization
284
+ * @param {Object} input - MDCT transform results
285
+ * @param {Float32Array} input.coefficients - MDCT coefficients
286
+ * @param {Array<number>} input.blockModes - Block mode indices
287
+ * @returns {Object} Quantization results ready for bitstream encoding
288
+ * @returns {number} returns.nBfu - Number of active BFUs
289
+ * @returns {Int32Array} returns.scaleFactorIndices - Scale factor indices for each BFU
290
+ * @returns {Int32Array} returns.wordLengthIndices - Word length indices for each BFU
291
+ * @returns {Array<Int32Array>} returns.quantizedCoefficients - Quantized coefficient data
292
+ * @returns {Array<number>} returns.blockSizeMode - Block size mode for each band
293
+ */
294
+ return (input) => {
295
+ const { coefficients, blockModes } = input
296
+
297
+ const { bfuData, bfuSizes, bfuCount } = groupIntoBFUs(
298
+ coefficients,
299
+ blockModes,
300
+ bufferPool.bfuData
301
+ )
302
+
303
+ const psychoResults = psychoAnalysis(
304
+ coefficients,
305
+ PSYMODEL_FFT_SIZE,
306
+ bufferPool.psychoAnalysis,
307
+ options.normalizationDb
308
+ )
309
+ const { bfuCount: selectedBfuCount, allocation } = allocateBits(
310
+ psychoResults,
311
+ bfuData,
312
+ bfuSizes,
313
+ bfuCount
314
+ )
315
+
316
+ const scaleFactorIndices = new Int32Array(selectedBfuCount)
317
+ const quantizedCoefficients = []
318
+
319
+ for (let bfu = 0; bfu < selectedBfuCount; bfu++) {
320
+ const data = bfuData[bfu].subarray(0, bfuSizes[bfu])
321
+ const wordLength = allocation[bfu]
322
+ const bitsPerSample = WORD_LENGTH_BITS[wordLength]
323
+
324
+ scaleFactorIndices[bfu] = findScaleFactor(data)
325
+ const quantized = quantize(data, scaleFactorIndices[bfu], bitsPerSample)
326
+ quantizedCoefficients.push(quantized)
327
+ }
328
+
329
+ return {
330
+ nBfu: selectedBfuCount,
331
+ scaleFactorIndices,
332
+ wordLengthIndices: allocation.slice(0, selectedBfuCount),
333
+ quantizedCoefficients,
334
+ blockSizeMode: blockModes,
335
+ }
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Create ATRAC1 encoding pipeline
341
+ *
342
+ * Constructs a complete encoding pipeline that transforms PCM audio samples
343
+ * into ATRAC1 compressed format. The pipeline processes audio through QMF
344
+ * analysis, transient detection, MDCT transform, and psychoacoustic quantization.
345
+ *
346
+ * The returned function can be called repeatedly to encode audio frames,
347
+ * maintaining state through the shared buffer pool for efficient processing.
348
+ *
349
+ * @param {EncoderOptions} [options=new EncoderOptions()] - Encoding configuration
350
+ * @param {BufferPool} [bufferPool=new BufferPool()] - Shared buffer pool for state
351
+ * @returns {Function} Encoding pipeline function that processes PCM samples
352
+ *
353
+ * @example
354
+ * const encoder = encode(new EncoderOptions(), new BufferPool())
355
+ * const result = encoder(pcmSamples) // Returns quantized ATRAC1 frame data
356
+ */
357
+ export function encode(
358
+ options = new EncoderOptions(),
359
+ bufferPool = new BufferPool()
360
+ ) {
361
+ const context = { options, bufferPool }
362
+ return pipe(
363
+ context,
364
+ qmfAnalysisStage,
365
+ blockSelectorStage,
366
+ mdctStage,
367
+ quantizationStage
368
+ )
369
+ }
@@ -0,0 +1,69 @@
1
+ /**
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.
6
+ */
7
+
8
+ export class FFT {
9
+ /**
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
13
+ */
14
+ 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
27
+ }
28
+ if (reversed > i) {
29
+ ;[real[i], real[reversed]] = [real[reversed], real[i]]
30
+ ;[imag[i], imag[reversed]] = [imag[reversed], imag[i]]
31
+ }
32
+ }
33
+
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
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Carta1 Audio Codec - Modified Discrete Cosine Transform
3
+ *
4
+ * This module implements forward and inverse MDCT transforms for the ATRAC1 codec.
5
+ * The MDCT provides time-frequency analysis with perfect reconstruction and
6
+ * 50% overlap between adjacent blocks to avoid blocking artifacts.
7
+ */
8
+
9
+ import { FFT } from './fft.js'
10
+ import {
11
+ MDCT_SIZE_SHORT,
12
+ MDCT_SIZE_MID,
13
+ MDCT_SIZE_LONG,
14
+ } from '../core/constants.js'
15
+ import { throwError } from '../utils.js'
16
+
17
+ /**
18
+ * Base class for MDCT and IMDCT transforms
19
+ */
20
+ class MDCTBase {
21
+ constructor(size, scale) {
22
+ this.size = size
23
+ this.halfSize = size >> 1
24
+ this.quarterSize = size >> 2
25
+ this.fftSize = this.halfSize >> 1
26
+
27
+ const alpha = (2.0 * Math.PI) / (8.0 * size)
28
+ const omega = (2.0 * Math.PI) / size
29
+ const scaleRoot = Math.sqrt(scale / size)
30
+
31
+ this.sinCosTable = new Float32Array(this.halfSize)
32
+ for (let i = 0; i < this.quarterSize; i++) {
33
+ const angle = omega * i + alpha
34
+ this.sinCosTable[i * 2] = scaleRoot * Math.cos(angle)
35
+ this.sinCosTable[i * 2 + 1] = scaleRoot * Math.sin(angle)
36
+ }
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Forward MDCT transform
42
+ */
43
+ export class MDCT extends MDCTBase {
44
+ constructor(size, scale) {
45
+ super(size, scale)
46
+ }
47
+
48
+ /**
49
+ * Perform forward MDCT transform
50
+ * @param {Float32Array} input - Time-domain input samples
51
+ * @param {Object} mdctBuffers - Optional work buffers for FFT
52
+ * @returns {Float32Array} Frequency-domain MDCT coefficients
53
+ */
54
+ transform(input, mdctBuffers = null) {
55
+ const buffers = mdctBuffers?.[this.fftSize]
56
+ const real = buffers?.real ?? throwError('mdct: real is required')
57
+ const imag = buffers?.imag ?? throwError('mdct: imag is required')
58
+
59
+ const n4 = this.quarterSize
60
+ const n34 = 3 * n4
61
+
62
+ real.fill(0)
63
+ imag.fill(0)
64
+
65
+ // Pre-FFT butterfly
66
+ for (let i = 0; i < n4; i += 2) {
67
+ const i1 = n34 - 1 - i
68
+ const i2 = n34 + i
69
+ const i3 = n4 + i
70
+ const i4 = n4 - 1 - i
71
+
72
+ const r = input[i1] + input[i2]
73
+ const im = input[i3] - input[i4]
74
+ const c = this.sinCosTable[i]
75
+ const s = this.sinCosTable[i + 1]
76
+
77
+ real[i >> 1] = r * c + im * s
78
+ imag[i >> 1] = im * c - r * s
79
+ }
80
+
81
+ for (let i = n4; i < this.halfSize; i += 2) {
82
+ const idx = i >> 1
83
+ const i1 = n34 - 1 - i
84
+ const i2 = i - n4
85
+ const i3 = n4 + i
86
+ const i4 = 5 * n4 - 1 - i
87
+
88
+ const r = input[i1] - input[i2]
89
+ const im = input[i3] + input[i4]
90
+ const c = this.sinCosTable[i]
91
+ const s = this.sinCosTable[i + 1]
92
+
93
+ real[idx] = r * c + im * s
94
+ imag[idx] = im * c - r * s
95
+ }
96
+
97
+ FFT.fft(real, imag)
98
+
99
+ // Post-FFT processing
100
+ const output = new Float32Array(this.halfSize)
101
+ for (let i = 0; i < this.fftSize; i++) {
102
+ const c = this.sinCosTable[i * 2]
103
+ const s = this.sinCosTable[i * 2 + 1]
104
+ const re = real[i]
105
+ const im = imag[i]
106
+
107
+ output[i * 2] = -re * c - im * s
108
+ output[this.halfSize - 1 - i * 2] = -re * s + im * c
109
+ }
110
+
111
+ return output
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Inverse MDCT transform
117
+ */
118
+ export class IMDCT extends MDCTBase {
119
+ constructor(size, scale = null) {
120
+ super(size, scale || size)
121
+ }
122
+
123
+ /**
124
+ * Perform inverse MDCT transform
125
+ * @param {Float32Array} input - Frequency-domain MDCT coefficients
126
+ * @param {Object} mdctBuffers - Optional work buffers for FFT
127
+ * @returns {Float32Array} Time-domain output samples
128
+ */
129
+ transform(input, mdctBuffers = null) {
130
+ const buffers = mdctBuffers?.[this.fftSize]
131
+ const real = buffers?.real ?? throwError('imdct: real is required')
132
+ const imag = buffers?.imag ?? throwError('imdct: imag is required')
133
+
134
+ const n4 = this.quarterSize
135
+ const n34 = 3 * n4
136
+
137
+ real.fill(0)
138
+ imag.fill(0)
139
+
140
+ // Pre-FFT processing
141
+ for (let i = 0; i < this.fftSize; i++) {
142
+ const i2 = i * 2
143
+ const r = input[i2]
144
+ const im = input[this.halfSize - 1 - i2]
145
+ const c = this.sinCosTable[i2]
146
+ const s = this.sinCosTable[i2 + 1]
147
+
148
+ real[i] = -2.0 * (im * s + r * c)
149
+ imag[i] = -2.0 * (im * c - r * s)
150
+ }
151
+
152
+ FFT.fft(real, imag)
153
+
154
+ // Post-FFT butterfly
155
+ const output = new Float32Array(this.size)
156
+
157
+ for (let i = 0; i < this.fftSize / 2; i++) {
158
+ const i2 = i * 2
159
+ const c = this.sinCosTable[i2]
160
+ const s = this.sinCosTable[i2 + 1]
161
+ const re = real[i]
162
+ const im = imag[i]
163
+
164
+ const r1 = re * c + im * s
165
+ const i1 = re * s - im * c
166
+
167
+ output[n34 - 1 - i2] = r1
168
+ output[n34 + i2] = r1
169
+ output[n4 + i2] = i1
170
+ output[n4 - 1 - i2] = -i1
171
+ }
172
+
173
+ for (let i = this.fftSize / 2; i < this.fftSize; i++) {
174
+ const idx = (i - this.fftSize / 2) * 2 + n4
175
+ const i2 = i * 2
176
+ const c = this.sinCosTable[i2]
177
+ const s = this.sinCosTable[i2 + 1]
178
+ const re = real[i]
179
+ const im = imag[i]
180
+
181
+ const r1 = re * c + im * s
182
+ const i1 = re * s - im * c
183
+
184
+ output[n34 - 1 - idx] = r1
185
+ output[idx - n4] = -r1
186
+ output[n4 + idx] = i1
187
+ output[5 * n4 - 1 - idx] = i1
188
+ }
189
+
190
+ return output
191
+ }
192
+ }
193
+
194
+ // Pre-instantiated transforms
195
+ export const mdct64 = new MDCT(MDCT_SIZE_SHORT, 0.5)
196
+ export const mdct256 = new MDCT(MDCT_SIZE_MID, 0.5)
197
+ export const mdct512 = new MDCT(MDCT_SIZE_LONG, 1.0)
198
+
199
+ export const imdct64 = new IMDCT(MDCT_SIZE_SHORT, MDCT_SIZE_SHORT * 4)
200
+ export const imdct256 = new IMDCT(MDCT_SIZE_MID, MDCT_SIZE_MID * 4)
201
+ export const imdct512 = new IMDCT(MDCT_SIZE_LONG, MDCT_SIZE_LONG * 2)
202
+
203
+ /**
204
+ * Perform overlap-add operation for MDCT reconstruction
205
+ * @param {Float32Array} prev - Previous block samples
206
+ * @param {Float32Array} curr - Current block samples
207
+ * @param {Float32Array} window - Window function coefficients
208
+ * @returns {Float32Array} Overlap-added output samples
209
+ */
210
+ export function overlapAdd(prev, curr, window) {
211
+ const size = prev.length
212
+ const output = new Float32Array(size * 2)
213
+
214
+ for (let i = 0; i < size; i++) {
215
+ const w1 = window[i]
216
+ const w2 = window[2 * size - 1 - i]
217
+ const p = prev[i]
218
+ const c = curr[size - 1 - i]
219
+
220
+ output[i] = p * w2 - c * w1
221
+ output[2 * size - 1 - i] = p * w1 + c * w2
222
+ }
223
+
224
+ return output
225
+ }