carta1 1.0.17 → 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.
@@ -244,6 +244,17 @@ export const FFT_SIZE_LOW = 128
244
244
  export const FFT_SIZE_MID = 128
245
245
  export const FFT_SIZE_HIGH = 256
246
246
 
247
+ // MDCT/IMDCT transform configuration
248
+ export const MDCT_BAND_CONFIGS = [
249
+ { size: 128, windowStart: 48 }, // Low band (0-5.5kHz)
250
+ { size: 128, windowStart: 48 }, // Mid band (5.5-11kHz)
251
+ { size: 256, windowStart: 112 }, // High band (11-22kHz)
252
+ ]
253
+
254
+ export const MDCT_SHORT_BLOCK_SIZE = 32
255
+ export const MDCT_OVERLAP_SIZE = 32
256
+ export const MDCT_TAIL_WINDOW_SIZE = 16
257
+
247
258
  // Quantization
248
259
  export const QUANTIZATION_SIGN_BIT_SHIFT = 1
249
260
 
@@ -13,7 +13,12 @@
13
13
  * containing buffer pools and decoding options.
14
14
  */
15
15
 
16
- import { pipe, throwError } from '../utils.js'
16
+ import {
17
+ pipe,
18
+ throwError,
19
+ reverseSpectrum,
20
+ extractBandCoefficients,
21
+ } from '../utils.js'
17
22
  import { qmfSynthesis } from '../transforms/qmf.js'
18
23
  import { imdct64, imdct256, imdct512, overlapAdd } from '../transforms/mdct.js'
19
24
  import { dequantize } from '../coding/quantization.js'
@@ -25,6 +30,9 @@ import {
25
30
  WORD_LENGTH_BITS,
26
31
  WINDOW_SHORT,
27
32
  MDCT_SIZE_LONG,
33
+ MDCT_BAND_CONFIGS,
34
+ MDCT_SHORT_BLOCK_SIZE,
35
+ MDCT_TAIL_WINDOW_SIZE,
28
36
  } from '../core/constants.js'
29
37
 
30
38
  /**
@@ -110,89 +118,194 @@ export function imdctStage(context) {
110
118
  context?.bufferPool ?? throwError('imdctStage: bufferPool is required')
111
119
  const overlapBuffers = bufferPool.imdctOverlap
112
120
 
121
+ // Transform function mapping for each band
122
+ const TRANSFORM_FUNCS = [imdct256, imdct256, imdct512]
123
+
113
124
  /**
114
- * Transform coefficients back to time domain using inverse MDCT
115
- * @param {Array} input - Dequantization results
116
- * @param {Float32Array} input[0] - MDCT coefficients (512 samples)
117
- * @param {Array<number>} input[1] - Block mode indices for each band
118
- * @returns {Array<Float32Array>} Three reconstructed frequency bands [low, mid, high]
125
+ * Inverse transform a single frequency band
119
126
  */
120
- return (input) => {
121
- const [coefficients, blockModes] = input
122
- const bands = []
123
-
124
- const bandCoeffs = [
125
- coefficients.subarray(0, 128),
126
- coefficients.subarray(128, 256),
127
- coefficients.subarray(256, 512),
128
- ]
129
-
130
- for (let band = 0; band < 3; band++) {
131
- const bandSize = band === 2 ? 256 : 128
132
- const numBlocks = 1 << blockModes[band]
133
- const blockSize = numBlocks === 1 ? bandSize : 32
134
-
135
- const imdctFunc =
136
- numBlocks === 1 ? (band === 2 ? imdct512 : imdct256) : imdct64
137
-
138
- const invBuf = bufferPool.transformBuffers[MDCT_SIZE_LONG]
139
- invBuf.fill(0)
140
- const dstBuf = overlapBuffers[band]
141
- const prevBuf = dstBuf.slice(bandSize * 2 - 16, bandSize * 2)
142
-
143
- let start = 0
144
- let pos = 0
145
-
146
- for (let block = 0; block < numBlocks; block++) {
147
- let blockSpecs = bandCoeffs[band].slice(pos, pos + blockSize)
148
-
149
- // Reverse spectral reversal for mid and high bands
150
- if (band > 0) {
151
- const reversed = bufferPool.reversalBuffers[blockSpecs.length]
152
- for (let i = 0; i < blockSpecs.length; i++) {
153
- reversed[i] = blockSpecs[blockSpecs.length - 1 - i]
154
- }
155
- blockSpecs = reversed
156
- }
157
-
158
- const inv = imdctFunc.transform(blockSpecs, bufferPool.mdctBuffers)
159
- const invStart = inv.length / 4
160
-
161
- for (let i = 0; i < blockSize; i++) {
162
- invBuf[start + i] = inv[invStart + i]
163
- }
164
-
165
- const dstPart = overlapAdd(
166
- prevBuf.slice(0, 16),
167
- invBuf.slice(start, start + 16),
168
- WINDOW_SHORT
169
- )
127
+ function transformBand(
128
+ coeffs,
129
+ bandIndex,
130
+ blockMode,
131
+ overlapBuffer,
132
+ bufferPool,
133
+ config
134
+ ) {
135
+ const isLongBlock = blockMode === 0
136
+ const transformFunc = TRANSFORM_FUNCS[bandIndex]
137
+
138
+ if (isLongBlock) {
139
+ return inverseLongBlock(
140
+ coeffs,
141
+ bandIndex,
142
+ overlapBuffer,
143
+ bufferPool,
144
+ config,
145
+ transformFunc
146
+ )
147
+ } else {
148
+ return inverseShortBlocks(
149
+ coeffs,
150
+ bandIndex,
151
+ overlapBuffer,
152
+ bufferPool,
153
+ config
154
+ )
155
+ }
156
+ }
170
157
 
171
- dstBuf.set(dstPart, start)
158
+ /**
159
+ * Inverse transform using long block
160
+ */
161
+ function inverseLongBlock(
162
+ coeffs,
163
+ bandIndex,
164
+ overlapBuffer,
165
+ bufferPool,
166
+ config,
167
+ transformFunc
168
+ ) {
169
+ // Unreverse spectral reversal for mid/high bands
170
+ let blockSpecs = coeffs
171
+ if (bandIndex > 0) {
172
+ blockSpecs = reverseSpectrum(coeffs, bufferPool.reversalBuffers)
173
+ }
174
+
175
+ // Apply inverse transform
176
+ const inv = transformFunc.transform(blockSpecs, bufferPool.mdctBuffers)
177
+ const invStart = inv.length / 4
172
178
 
173
- for (let i = 0; i < 16; i++) {
174
- prevBuf[i] = invBuf[start + 16 + i]
175
- }
179
+ // Prepare output buffer
180
+ const invBuf = bufferPool.transformBuffers[MDCT_SIZE_LONG]
181
+ invBuf.fill(0)
182
+
183
+ // Copy transformed samples
184
+ for (let i = 0; i < config.size; i++) {
185
+ invBuf[i] = inv[invStart + i]
186
+ }
187
+
188
+ // Handle overlap-add with previous frame
189
+ const prevBuf = overlapBuffer.slice(
190
+ config.size * 2 - MDCT_TAIL_WINDOW_SIZE,
191
+ config.size * 2
192
+ )
193
+ const dstPart = overlapAdd(
194
+ prevBuf.slice(0, MDCT_TAIL_WINDOW_SIZE),
195
+ invBuf.slice(0, MDCT_TAIL_WINDOW_SIZE),
196
+ WINDOW_SHORT
197
+ )
176
198
 
177
- start += blockSize
178
- pos += blockSize
199
+ overlapBuffer.set(dstPart, 0)
200
+
201
+ // Save tail for next frame
202
+ for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
203
+ prevBuf[i] = invBuf[MDCT_TAIL_WINDOW_SIZE + i]
204
+ }
205
+
206
+ // Copy main samples
207
+ const copyLen = bandIndex === 2 ? 240 : 112
208
+ for (let i = 0; i < copyLen; i++) {
209
+ overlapBuffer[32 + i] = invBuf[MDCT_TAIL_WINDOW_SIZE + i]
210
+ }
211
+
212
+ // Copy final samples
213
+ for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
214
+ overlapBuffer[config.size * 2 - MDCT_TAIL_WINDOW_SIZE + i] =
215
+ invBuf[config.size - MDCT_TAIL_WINDOW_SIZE + i]
216
+ }
217
+
218
+ return overlapBuffer.slice(0, config.size)
219
+ }
220
+
221
+ /**
222
+ * Inverse transform using short blocks
223
+ */
224
+ function inverseShortBlocks(
225
+ coeffs,
226
+ bandIndex,
227
+ overlapBuffer,
228
+ bufferPool,
229
+ config
230
+ ) {
231
+ const numBlocks = 1 << (config.size === 256 ? 3 : 2)
232
+ const invBuf = bufferPool.transformBuffers[MDCT_SIZE_LONG]
233
+ invBuf.fill(0)
234
+
235
+ const prevBuf = overlapBuffer.slice(
236
+ config.size * 2 - MDCT_TAIL_WINDOW_SIZE,
237
+ config.size * 2
238
+ )
239
+
240
+ let start = 0
241
+ let pos = 0
242
+
243
+ for (let block = 0; block < numBlocks; block++) {
244
+ // Extract block coefficients
245
+ let blockSpecs = coeffs.slice(pos, pos + MDCT_SHORT_BLOCK_SIZE)
246
+
247
+ // Unreverse spectral reversal for mid/high bands
248
+ if (bandIndex > 0) {
249
+ blockSpecs = reverseSpectrum(blockSpecs, bufferPool.reversalBuffers)
179
250
  }
180
251
 
181
- if (numBlocks === 1) {
182
- const copyLen = band === 2 ? 240 : 112
183
- for (let i = 0; i < copyLen; i++) {
184
- dstBuf[32 + i] = invBuf[16 + i]
185
- }
252
+ // Apply inverse transform
253
+ const inv = imdct64.transform(blockSpecs, bufferPool.mdctBuffers)
254
+ const invStart = inv.length / 4
255
+
256
+ // Copy to intermediate buffer
257
+ for (let i = 0; i < MDCT_SHORT_BLOCK_SIZE; i++) {
258
+ invBuf[start + i] = inv[invStart + i]
186
259
  }
187
260
 
188
- for (let i = 0; i < 16; i++) {
189
- dstBuf[bandSize * 2 - 16 + i] = invBuf[bandSize - 16 + i]
261
+ // Overlap-add with previous block
262
+ const dstPart = overlapAdd(
263
+ prevBuf.slice(0, MDCT_TAIL_WINDOW_SIZE),
264
+ invBuf.slice(start, start + MDCT_TAIL_WINDOW_SIZE),
265
+ WINDOW_SHORT
266
+ )
267
+
268
+ overlapBuffer.set(dstPart, start)
269
+
270
+ // Save tail for next block
271
+ for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
272
+ prevBuf[i] = invBuf[start + MDCT_TAIL_WINDOW_SIZE + i]
190
273
  }
191
274
 
192
- bands.push(dstBuf.slice(0, bandSize))
275
+ start += MDCT_SHORT_BLOCK_SIZE
276
+ pos += MDCT_SHORT_BLOCK_SIZE
277
+ }
278
+
279
+ // Copy final tail samples
280
+ for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
281
+ overlapBuffer[config.size * 2 - MDCT_TAIL_WINDOW_SIZE + i] =
282
+ invBuf[config.size - MDCT_TAIL_WINDOW_SIZE + i]
193
283
  }
194
284
 
195
- return bands
285
+ return overlapBuffer.slice(0, config.size)
286
+ }
287
+
288
+ /**
289
+ * Transform coefficients back to time domain using inverse MDCT
290
+ * @param {Array} input - Dequantization results
291
+ * @param {Float32Array} input[0] - MDCT coefficients (512 samples)
292
+ * @param {Array<number>} input[1] - Block mode indices for each band
293
+ * @returns {Array<Float32Array>} Three reconstructed frequency bands [low, mid, high]
294
+ */
295
+ return (input) => {
296
+ const [coefficients, blockModes] = input
297
+
298
+ return MDCT_BAND_CONFIGS.map((config, bandIndex) => {
299
+ const bandCoeffs = extractBandCoefficients(coefficients, bandIndex)
300
+ return transformBand(
301
+ bandCoeffs,
302
+ bandIndex,
303
+ blockModes[bandIndex],
304
+ overlapBuffers[bandIndex],
305
+ bufferPool,
306
+ config
307
+ )
308
+ })
196
309
  }
197
310
  }
198
311
 
@@ -14,7 +14,12 @@
14
14
  * containing buffer pools and encoding options.
15
15
  */
16
16
 
17
- import { pipe, throwError } from '../utils.js'
17
+ import {
18
+ pipe,
19
+ throwError,
20
+ reverseSpectrum,
21
+ calculateBandOffset,
22
+ } from '../utils.js'
18
23
  import { qmfAnalysis } from '../transforms/qmf.js'
19
24
  import { mdct64, mdct256, mdct512 } from '../transforms/mdct.js'
20
25
  import {
@@ -34,6 +39,9 @@ import {
34
39
  MDCT_SIZE_SHORT,
35
40
  MDCT_SIZE_MID,
36
41
  MDCT_SIZE_LONG,
42
+ MDCT_BAND_CONFIGS,
43
+ MDCT_SHORT_BLOCK_SIZE,
44
+ MDCT_OVERLAP_SIZE,
37
45
  } from '../core/constants.js'
38
46
  import { performFFT, detectTransient } from '../analysis/transient.js'
39
47
  import { psychoAnalysis } from '../analysis/psychoacoustics.js'
@@ -180,6 +188,130 @@ export function mdctStage(context) {
180
188
  context?.bufferPool ?? throwError('mdctStage: bufferPool is required')
181
189
  const overlapBuffers = bufferPool.mdctOverlap
182
190
 
191
+ // Transform function mapping for each band
192
+ const TRANSFORM_FUNCS = [mdct256, mdct256, mdct512]
193
+
194
+ /**
195
+ * Transform a single frequency band using MDCT
196
+ */
197
+ function transformBand(
198
+ samples,
199
+ bandIndex,
200
+ blockMode,
201
+ overlapBuffer,
202
+ bufferPool
203
+ ) {
204
+ const config = MDCT_BAND_CONFIGS[bandIndex]
205
+ const transformFunc = TRANSFORM_FUNCS[bandIndex]
206
+ const isLongBlock = blockMode === 0
207
+
208
+ if (isLongBlock) {
209
+ return transformLongBlock(
210
+ samples,
211
+ bandIndex,
212
+ config,
213
+ transformFunc,
214
+ overlapBuffer,
215
+ bufferPool
216
+ )
217
+ } else {
218
+ return transformShortBlocks(
219
+ samples,
220
+ bandIndex,
221
+ config,
222
+ overlapBuffer,
223
+ bufferPool
224
+ )
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Transform using long block (better frequency resolution)
230
+ */
231
+ function transformLongBlock(
232
+ samples,
233
+ bandIndex,
234
+ config,
235
+ transformFunc,
236
+ overlapBuffer,
237
+ bufferPool
238
+ ) {
239
+ const mdctSize = bandIndex === 2 ? MDCT_SIZE_LONG : MDCT_SIZE_MID
240
+ const mdctInput = bufferPool.transformBuffers[mdctSize]
241
+ mdctInput.fill(0)
242
+
243
+ // Apply overlap from previous frame
244
+ mdctInput.set(overlapBuffer, config.windowStart)
245
+
246
+ // Window and save tail for next frame
247
+ applyTailWindowing(samples, overlapBuffer, config.size)
248
+
249
+ // Add current frame samples
250
+ mdctInput.set(samples, config.windowStart + MDCT_OVERLAP_SIZE)
251
+
252
+ // Transform
253
+ let spectrum = transformFunc.transform(mdctInput, bufferPool.mdctBuffers)
254
+
255
+ // Apply spectral reversal for mid/high bands
256
+ if (bandIndex > 0) {
257
+ spectrum = reverseSpectrum(spectrum, bufferPool.reversalBuffers)
258
+ }
259
+
260
+ return spectrum
261
+ }
262
+
263
+ /**
264
+ * Transform using short blocks (better time resolution for transients)
265
+ */
266
+ function transformShortBlocks(
267
+ samples,
268
+ bandIndex,
269
+ config,
270
+ overlapBuffer,
271
+ bufferPool
272
+ ) {
273
+ const numBlocks = 1 << (config.size === 256 ? 3 : 2) // 8 for band 2, 4 for bands 0-1
274
+ const output = new Float32Array(config.size)
275
+
276
+ for (let block = 0; block < numBlocks; block++) {
277
+ const blockStart = block * MDCT_SHORT_BLOCK_SIZE
278
+ const blockSamples = samples.subarray(
279
+ blockStart,
280
+ blockStart + MDCT_SHORT_BLOCK_SIZE
281
+ )
282
+
283
+ // Prepare MDCT input
284
+ const mdctInput = bufferPool.transformBuffers[MDCT_SIZE_SHORT]
285
+ mdctInput.fill(0)
286
+ mdctInput.set(overlapBuffer, 0)
287
+
288
+ // Window and save tail
289
+ applyTailWindowing(blockSamples, overlapBuffer, MDCT_SHORT_BLOCK_SIZE)
290
+ mdctInput.set(blockSamples, MDCT_OVERLAP_SIZE)
291
+
292
+ // Transform
293
+ let spectrum = mdct64.transform(mdctInput, bufferPool.mdctBuffers)
294
+
295
+ // Apply spectral reversal for mid/high bands
296
+ if (bandIndex > 0) {
297
+ spectrum = reverseSpectrum(spectrum, bufferPool.reversalBuffers)
298
+ }
299
+
300
+ output.set(spectrum, blockStart)
301
+ }
302
+
303
+ return output
304
+ }
305
+
306
+ function applyTailWindowing(samples, overlapBuffer, blockSize) {
307
+ const tailStart = blockSize - MDCT_OVERLAP_SIZE
308
+ for (let i = 0; i < MDCT_OVERLAP_SIZE; i++) {
309
+ const tailValue = samples[tailStart + i]
310
+ overlapBuffer[i] = WINDOW_SHORT[i] * tailValue
311
+ samples[tailStart + i] = tailValue * WINDOW_SHORT[31 - i]
312
+ }
313
+ }
314
+
183
315
  /**
184
316
  * Transform frequency bands using MDCT
185
317
  * @param {Object} input - Block selection results
@@ -194,60 +326,20 @@ export function mdctStage(context) {
194
326
  */
195
327
  return (input) => {
196
328
  const { bands, blockModes, originalFrame } = input
197
-
198
329
  const coefficients = new Float32Array(512)
199
330
 
200
- for (let band = 0; band < 3; band++) {
201
- const samples = bands[band]
202
- const bandSize = band === 2 ? 256 : 128
203
- const numBlocks = 1 << blockModes[band]
204
- const blockSize = numBlocks === 1 ? bandSize : 32
205
-
206
- const mdctFunc =
207
- numBlocks === 1 ? (band === 2 ? mdct512 : mdct256) : mdct64
208
- const mdctSize =
209
- numBlocks === 1
210
- ? band === 2
211
- ? MDCT_SIZE_LONG
212
- : MDCT_SIZE_MID
213
- : MDCT_SIZE_SHORT
214
- const windowStart = numBlocks === 1 ? (band === 2 ? 112 : 48) : 0
215
-
216
- const offset = band === 0 ? 0 : band === 1 ? 128 : 256
217
-
218
- for (let block = 0; block < numBlocks; block++) {
219
- const blockStart = block * blockSize
220
- const mdctInput = bufferPool.transformBuffers[mdctSize]
221
- mdctInput.fill(0)
222
-
223
- mdctInput.set(overlapBuffers[band], windowStart)
224
-
225
- for (let i = 0; i < 32; i++) {
226
- const tailValue = samples[blockStart + blockSize - 32 + i]
227
- overlapBuffers[band][i] = WINDOW_SHORT[i] * tailValue
228
- samples[blockStart + blockSize - 32 + i] =
229
- tailValue * WINDOW_SHORT[31 - i]
230
- }
231
-
232
- mdctInput.set(
233
- samples.subarray(blockStart, blockStart + blockSize),
234
- windowStart + 32
235
- )
236
-
237
- let spectrum = mdctFunc.transform(mdctInput, bufferPool.mdctBuffers)
238
-
239
- if (band > 0) {
240
- const reversed = bufferPool.reversalBuffers[spectrum.length]
241
- reversed.fill(0)
242
- for (let i = 0; i < spectrum.length; i++) {
243
- reversed[i] = spectrum[spectrum.length - 1 - i]
244
- }
245
- spectrum = reversed
246
- }
247
-
248
- coefficients.set(spectrum, offset + blockStart)
249
- }
250
- }
331
+ bands.forEach((bandSamples, bandIndex) => {
332
+ const transformed = transformBand(
333
+ bandSamples,
334
+ bandIndex,
335
+ blockModes[bandIndex],
336
+ overlapBuffers[bandIndex],
337
+ bufferPool
338
+ )
339
+
340
+ const offset = calculateBandOffset(bandIndex)
341
+ coefficients.set(transformed, offset)
342
+ })
251
343
 
252
344
  return { bands, coefficients, blockModes, originalFrame }
253
345
  }
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)