carta1 1.1.10 → 1.3.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.
Files changed (46) hide show
  1. package/bin/cli.js +8 -4
  2. package/codec/{coding/bitallocation.js → allocation/solve.js} +131 -97
  3. package/codec/allocation/source.js +69 -0
  4. package/codec/allocation/stage.js +38 -0
  5. package/codec/analysis/bands.js +68 -0
  6. package/codec/analysis/{transient.js → blocks.js} +90 -47
  7. package/codec/analysis/spectrum.js +160 -0
  8. package/codec/analysis/stage.js +32 -0
  9. package/codec/{browser/interface.js → boundary/browser.js} +1 -1
  10. package/codec/boundary/container.js +96 -0
  11. package/codec/boundary/pcm.js +193 -0
  12. package/codec/boundary/processor.js +208 -0
  13. package/codec/{io/readers.js → boundary/reader.js} +2 -1
  14. package/codec/boundary/stream.js +361 -0
  15. package/codec/{browser → boundary}/worker.js +45 -42
  16. package/codec/core/constants.js +19 -132
  17. package/codec/core/geometry.js +25 -0
  18. package/codec/core/tables.js +215 -0
  19. package/codec/dequantization.js +54 -0
  20. package/codec/index.js +12 -13
  21. package/codec/pipeline/decoder.js +10 -399
  22. package/codec/pipeline/encoder.js +12 -434
  23. package/codec/quantization/spectrum.js +67 -0
  24. package/codec/quantization/stage.js +49 -0
  25. package/codec/signal/fft.js +68 -0
  26. package/codec/{transforms → signal}/mdct.js +37 -65
  27. package/codec/signal/order.js +16 -0
  28. package/codec/signal/qmf.js +539 -0
  29. package/codec/state.js +112 -0
  30. package/codec/{io → syntax}/bitstream.js +27 -8
  31. package/codec/{io/serialization.js → syntax/frame.js} +5 -101
  32. package/codec/synthesis/bands.js +228 -0
  33. package/codec/synthesis/pcm.js +64 -0
  34. package/codec/synthesis/stage.js +17 -0
  35. package/codec/utils.js +9 -52
  36. package/dist/carta1-worker-interface.min.js.map +1 -1
  37. package/dist/carta1-worker.min.js +1 -1
  38. package/dist/carta1-worker.min.js.map +1 -1
  39. package/dist/carta1.min.js +1 -1
  40. package/dist/carta1.min.js.map +1 -1
  41. package/package.json +1 -1
  42. package/codec/coding/quantization.js +0 -149
  43. package/codec/core/buffers.js +0 -81
  44. package/codec/io/processor.js +0 -671
  45. package/codec/transforms/fft.js +0 -69
  46. package/codec/transforms/qmf.js +0 -105
@@ -1,411 +1,22 @@
1
1
  /**
2
2
  * Carta1 Audio Codec - Decoding Pipeline
3
3
  *
4
- * The decoding pipeline transforms ATRAC1 compressed format back into PCM audio samples
5
- * through a series of processing stages:
6
- *
7
- * 1. Dequantization: Reconstructs MDCT coefficients from quantized data using scale factors
8
- * 2. Inverse MDCT: Applies Inverse Modified Discrete Cosine Transform to frequency bands
9
- * 3. QMF Synthesis: Reconstructs full-spectrum audio using quadrature mirror filters
10
- *
11
- * Each stage is implemented as a functional pipeline component that can be composed
12
- * using the pipe utility. The pipeline maintains state through a shared context
13
- * containing buffer pools and decoding options.
14
- */
15
-
16
- import {
17
- pipe,
18
- throwError,
19
- reverseSpectrum,
20
- extractBandCoefficients,
21
- } from '../utils.js'
22
- import { qmfSynthesis } from '../transforms/qmf.js'
23
- import { imdct64, imdct256, imdct512, overlapAdd } from '../transforms/mdct.js'
24
- import { dequantize } from '../coding/quantization.js'
25
- import { BufferPool } from '../core/buffers.js'
26
- import {
27
- BFU_BAND_BOUNDARIES,
28
- BFU_START_LONG,
29
- BFU_START_SHORT,
30
- WORD_LENGTH_BITS,
31
- WINDOW_SHORT,
32
- MDCT_SIZE_LONG,
33
- MDCT_BAND_CONFIGS,
34
- MDCT_SHORT_BLOCK_SIZE,
35
- MDCT_TAIL_WINDOW_SIZE,
36
- } from '../core/constants.js'
37
-
38
- /**
39
- * Dequantization Stage - Reconstructs MDCT coefficients from quantized data
40
- *
41
- * Reverses the quantization process by:
42
- * - Reconstructing coefficient values using scale factors and word lengths
43
- * - Organizing coefficients back into Block Floating Units (BFUs)
44
- * - Preparing coefficient array for inverse MDCT transform
45
- *
46
- * Uses the same BFU organization as the encoder but reconstructs the original
47
- * coefficient values from the quantized representation.
48
- *
49
- * @returns {Function} Stage function that processes quantized frame data
50
- * @throws {Error} If bufferPool is not provided in context
51
- */
52
- export function dequantizationStage() {
53
- /**
54
- * Reconstruct MDCT coefficients from quantized data
55
- * @param {Object} frameData - Quantized frame data
56
- * @param {number} frameData.nBfu - Number of active BFUs
57
- * @param {Int32Array} frameData.scaleFactorIndices - Scale factor indices for each BFU
58
- * @param {Int32Array} frameData.wordLengthIndices - Word length indices for each BFU
59
- * @param {Array<Int32Array>} frameData.quantizedCoefficients - Quantized coefficient data
60
- * @param {Array<number>} frameData.blockModes - Block modes for each band
61
- * @returns {Object} Dequantization results
62
- * @returns {Float32Array} returns.coefficients - Reconstructed MDCT coefficients (512 samples)
63
- * @returns {Array<number>} returns.blockModes - Block modes for each band
64
- */
65
- return (frameData) => {
66
- const coefficients = new Float32Array(512)
67
- const {
68
- nBfu,
69
- scaleFactorIndices,
70
- wordLengthIndices,
71
- quantizedCoefficients,
72
- blockModes,
73
- } = frameData
74
-
75
- for (let bfu = 0; bfu < nBfu; bfu++) {
76
- const bitsPerSample = WORD_LENGTH_BITS[wordLengthIndices[bfu]]
77
-
78
- let band = 0
79
- if (bfu >= BFU_BAND_BOUNDARIES[0]) band = 1
80
- if (bfu >= BFU_BAND_BOUNDARIES[1]) band = 2
81
-
82
- const isLongBlock = blockModes[band] === 0
83
- const position = isLongBlock ? BFU_START_LONG[bfu] : BFU_START_SHORT[bfu]
84
-
85
- if (bitsPerSample > 0) {
86
- const quantized = quantizedCoefficients[bfu]
87
- const dequantized = dequantize(
88
- quantized,
89
- scaleFactorIndices[bfu],
90
- bitsPerSample
91
- )
92
- coefficients.set(dequantized, position)
93
- }
94
- }
95
-
96
- return { coefficients, blockModes }
97
- }
98
- }
99
-
100
- /**
101
- * Inverse MDCT Stage - Transforms frequency coefficients back to time domain
102
- *
103
- * Applies Inverse Modified Discrete Cosine Transform to reconstruct time-domain
104
- * samples from frequency coefficients. The transform size depends on block modes:
105
- * - Long blocks: 512/256 samples for steady-state signals
106
- * - Short blocks: 64 samples for transient signals
107
- *
108
- * Applies windowing and overlap-add processing to prevent blocking artifacts.
109
- * Mid and high bands are spectrally unreversed from ATRAC1 format.
110
- *
111
- * @param {Object} context - Pipeline context containing bufferPool
112
- * @param {BufferPool} context.bufferPool - Shared buffer pool for efficient memory management
113
- * @returns {Function} Stage function that processes dequantization results
114
- * @throws {Error} If bufferPool is not provided in context
115
- */
116
- export function imdctStage(context) {
117
- const bufferPool =
118
- context?.bufferPool ?? throwError('imdctStage: bufferPool is required')
119
- const overlapBuffers = bufferPool.imdctOverlap
120
-
121
- // Transform function mapping for each band
122
- const TRANSFORM_FUNCS = [imdct256, imdct256, imdct512]
123
-
124
- /**
125
- * Inverse transform a single frequency band.
126
- * @param {Float32Array} coeffs
127
- * @param {number} bandIndex
128
- * @param {number} blockMode
129
- * @param {Float32Array} overlapBuffer
130
- * @param {Object} bufferPool
131
- * @param {Object} config
132
- * @returns {Float32Array}
133
- */
134
- function transformBand(
135
- coeffs,
136
- bandIndex,
137
- blockMode,
138
- overlapBuffer,
139
- bufferPool,
140
- config
141
- ) {
142
- const isLongBlock = blockMode === 0
143
- const transformFunc = TRANSFORM_FUNCS[bandIndex]
144
-
145
- if (isLongBlock) {
146
- return inverseLongBlock(
147
- coeffs,
148
- bandIndex,
149
- overlapBuffer,
150
- bufferPool,
151
- config,
152
- transformFunc
153
- )
154
- } else {
155
- return inverseShortBlocks(
156
- coeffs,
157
- bandIndex,
158
- overlapBuffer,
159
- bufferPool,
160
- config
161
- )
162
- }
163
- }
164
-
165
- /**
166
- * Inverse transform using long block.
167
- * @param {Float32Array} coeffs
168
- * @param {number} bandIndex
169
- * @param {Float32Array} overlapBuffer
170
- * @param {Object} bufferPool
171
- * @param {Object} config
172
- * @param {Object} transformFunc
173
- * @returns {Float32Array}
174
- */
175
- function inverseLongBlock(
176
- coeffs,
177
- bandIndex,
178
- overlapBuffer,
179
- bufferPool,
180
- config,
181
- transformFunc
182
- ) {
183
- // Unreverse spectral reversal for mid/high bands
184
- let blockSpecs = coeffs
185
- if (bandIndex > 0) {
186
- blockSpecs = reverseSpectrum(coeffs, bufferPool.reversalBuffers)
187
- }
188
-
189
- // Apply inverse transform
190
- const inv = transformFunc.transform(blockSpecs, bufferPool.mdctBuffers)
191
- const invStart = inv.length / 4
192
-
193
- // Prepare output buffer
194
- const invBuf = bufferPool.transformBuffers[MDCT_SIZE_LONG]
195
- invBuf.fill(0)
196
-
197
- // Copy transformed samples
198
- for (let i = 0; i < config.size; i++) {
199
- invBuf[i] = inv[invStart + i]
200
- }
201
-
202
- // Handle overlap-add with previous frame
203
- const prevBuf = overlapBuffer.slice(
204
- config.size * 2 - MDCT_TAIL_WINDOW_SIZE,
205
- config.size * 2
206
- )
207
- const dstPart = overlapAdd(
208
- prevBuf.slice(0, MDCT_TAIL_WINDOW_SIZE),
209
- invBuf.slice(0, MDCT_TAIL_WINDOW_SIZE),
210
- WINDOW_SHORT
211
- )
212
-
213
- overlapBuffer.set(dstPart, 0)
214
-
215
- // Save tail for next frame
216
- for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
217
- prevBuf[i] = invBuf[MDCT_TAIL_WINDOW_SIZE + i]
218
- }
219
-
220
- // Copy main samples
221
- const copyLen = bandIndex === 2 ? 240 : 112
222
- for (let i = 0; i < copyLen; i++) {
223
- overlapBuffer[32 + i] = invBuf[MDCT_TAIL_WINDOW_SIZE + i]
224
- }
225
-
226
- // Copy final samples
227
- for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
228
- overlapBuffer[config.size * 2 - MDCT_TAIL_WINDOW_SIZE + i] =
229
- invBuf[config.size - MDCT_TAIL_WINDOW_SIZE + i]
230
- }
231
-
232
- return overlapBuffer.slice(0, config.size)
233
- }
234
-
235
- /**
236
- * Inverse transform using short blocks.
237
- * @param {Float32Array} coeffs
238
- * @param {number} bandIndex
239
- * @param {Float32Array} overlapBuffer
240
- * @param {Object} bufferPool
241
- * @param {Object} config
242
- * @returns {Float32Array}
243
- */
244
- function inverseShortBlocks(
245
- coeffs,
246
- bandIndex,
247
- overlapBuffer,
248
- bufferPool,
249
- config
250
- ) {
251
- const numBlocks = 1 << (config.size === 256 ? 3 : 2)
252
- const invBuf = bufferPool.transformBuffers[MDCT_SIZE_LONG]
253
- invBuf.fill(0)
254
-
255
- const prevBuf = overlapBuffer.slice(
256
- config.size * 2 - MDCT_TAIL_WINDOW_SIZE,
257
- config.size * 2
258
- )
259
-
260
- let start = 0
261
- let pos = 0
262
-
263
- for (let block = 0; block < numBlocks; block++) {
264
- // Extract block coefficients
265
- let blockSpecs = coeffs.slice(pos, pos + MDCT_SHORT_BLOCK_SIZE)
266
-
267
- // Unreverse spectral reversal for mid/high bands
268
- if (bandIndex > 0) {
269
- blockSpecs = reverseSpectrum(blockSpecs, bufferPool.reversalBuffers)
270
- }
271
-
272
- // Apply inverse transform
273
- const inv = imdct64.transform(blockSpecs, bufferPool.mdctBuffers)
274
- const invStart = inv.length / 4
275
-
276
- // Copy to intermediate buffer
277
- for (let i = 0; i < MDCT_SHORT_BLOCK_SIZE; i++) {
278
- invBuf[start + i] = inv[invStart + i]
279
- }
280
-
281
- // Overlap-add with previous block
282
- const dstPart = overlapAdd(
283
- prevBuf.slice(0, MDCT_TAIL_WINDOW_SIZE),
284
- invBuf.slice(start, start + MDCT_TAIL_WINDOW_SIZE),
285
- WINDOW_SHORT
286
- )
287
-
288
- overlapBuffer.set(dstPart, start)
289
-
290
- // Save tail for next block
291
- for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
292
- prevBuf[i] = invBuf[start + MDCT_TAIL_WINDOW_SIZE + i]
293
- }
294
-
295
- start += MDCT_SHORT_BLOCK_SIZE
296
- pos += MDCT_SHORT_BLOCK_SIZE
297
- }
298
-
299
- // Copy final tail samples
300
- for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
301
- overlapBuffer[config.size * 2 - MDCT_TAIL_WINDOW_SIZE + i] =
302
- invBuf[config.size - MDCT_TAIL_WINDOW_SIZE + i]
303
- }
304
-
305
- return overlapBuffer.slice(0, config.size)
306
- }
307
-
308
- /**
309
- * Transform coefficients back to time domain using inverse MDCT
310
- * @param {Object} input - Dequantization results
311
- * @param {Float32Array} input.coefficients - MDCT coefficients (512 samples)
312
- * @param {Array<number>} input.blockModes - Block modes for each band
313
- * @returns {Array<Float32Array>} Three reconstructed frequency bands [low, mid, high]
314
- */
315
- return (input) => {
316
- const { coefficients, blockModes } = input
317
-
318
- return MDCT_BAND_CONFIGS.map((config, bandIndex) => {
319
- const bandCoeffs = extractBandCoefficients(coefficients, bandIndex)
320
- return transformBand(
321
- bandCoeffs,
322
- bandIndex,
323
- blockModes[bandIndex],
324
- overlapBuffers[bandIndex],
325
- bufferPool,
326
- config
327
- )
328
- })
329
- }
330
- }
331
-
332
- /**
333
- * QMF Synthesis Stage - Reconstructs full-spectrum audio from frequency bands
334
- *
335
- * Performs two-stage QMF synthesis to reconstruct the full-spectrum signal
336
- * from three frequency bands:
337
- * - Low band (0-5.5kHz): processed at full resolution
338
- * - Mid band (5.5-11kHz): processed at half resolution
339
- * - High band (11-22kHz): processed at quarter resolution with delay compensation
340
- *
341
- * This is the inverse operation of the QMF analysis performed during encoding,
342
- * combining the separate frequency bands back into a single wideband signal.
343
- *
344
- * @param {Object} context - Pipeline context containing bufferPool
345
- * @param {BufferPool} context.bufferPool - Shared buffer pool for efficient memory management
346
- * @returns {Function} Stage function that processes inverse MDCT results
347
- * @throws {Error} If bufferPool is not provided in context
4
+ * The frame pipeline reads as dequantization, then synthesis. Byte unpacking
5
+ * remains the responsibility of deserializeFrame().
348
6
  */
349
- export function qmfSynthesisStage(context) {
350
- const bufferPool =
351
- context?.bufferPool ??
352
- throwError('qmfSynthesisStage: bufferPool is required')
353
- const delays = bufferPool.qmfDelays
354
7
 
355
- /**
356
- * Reconstruct full-spectrum PCM samples from frequency bands
357
- * @param {Array<Float32Array>} bands - Three frequency bands [low, mid, high]
358
- * @returns {Float32Array} Reconstructed PCM samples
359
- */
360
- return (bands) => {
361
- // Apply high band delay compensation
362
- const delayedHigh = bufferPool.qmfWorkBuffers.highBandDelay[bands[2].length]
363
- delayedHigh.set(delays.highBand)
364
- delayedHigh.set(bands[2], delays.highBand.length)
365
-
366
- const highBand = delayedHigh.slice(0, bands[0].length * 2)
367
- delays.highBand = delayedHigh.slice(bands[0].length * 2)
368
-
369
- // Second stage synthesis: combine low and mid bands
370
- const stage2 = qmfSynthesis(
371
- bands[0],
372
- bands[1],
373
- delays.midBand,
374
- bufferPool.qmfWorkBuffers
375
- )
376
- delays.midBand = stage2.newDelay
377
-
378
- // First stage synthesis: combine result with high band
379
- const stage1 = qmfSynthesis(
380
- stage2.output,
381
- highBand,
382
- delays.lowBand,
383
- bufferPool.qmfWorkBuffers
384
- )
385
- delays.lowBand = stage1.newDelay
386
-
387
- return stage1.output
388
- }
389
- }
8
+ import { dequantize } from '../dequantization.js'
9
+ import { BufferPool } from '../state.js'
10
+ import { synthesize } from '../synthesis/stage.js'
11
+ import { pipe } from '../utils.js'
390
12
 
391
13
  /**
392
- * Create ATRAC1 decoding pipeline
393
- *
394
- * Constructs a complete decoding pipeline that transforms ATRAC1 compressed format
395
- * back into PCM audio samples. The pipeline processes quantized data through
396
- * dequantization, inverse MDCT transform, and QMF synthesis.
397
- *
398
- * The returned function can be called repeatedly to decode audio frames,
399
- * maintaining state through the shared buffer pool for efficient processing.
400
- *
401
- * @param {BufferPool} [bufferPool=new BufferPool()] - Shared buffer pool for state
402
- * @returns {Function} Decoding pipeline function that processes quantized frame data
14
+ * Create an ATRAC1 decoder.
403
15
  *
404
- * @example
405
- * const decoder = decode(new BufferPool())
406
- * const result = decoder(quantizedFrame) // Returns reconstructed PCM samples
16
+ * @param {BufferPool} [bufferPool=new BufferPool()]
17
+ * @returns {Function}
407
18
  */
408
19
  export function decode(bufferPool = new BufferPool()) {
409
20
  const context = { bufferPool }
410
- return pipe(context, dequantizationStage, imdctStage, qmfSynthesisStage)
21
+ return pipe(context, dequantize, synthesize)
411
22
  }