carta1 1.1.9 → 1.2.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 (44) hide show
  1. package/README.md +149 -75
  2. package/bin/cli.js +8 -4
  3. package/codec/{coding/bitallocation.js → allocation/solve.js} +131 -97
  4. package/codec/allocation/source.js +69 -0
  5. package/codec/allocation/stage.js +38 -0
  6. package/codec/analysis/bands.js +49 -0
  7. package/codec/analysis/{transient.js → blocks.js} +90 -47
  8. package/codec/analysis/spectrum.js +160 -0
  9. package/codec/analysis/stage.js +32 -0
  10. package/codec/{browser/interface.js → boundary/browser.js} +1 -1
  11. package/codec/boundary/container.js +96 -0
  12. package/codec/boundary/pcm.js +193 -0
  13. package/codec/boundary/processor.js +208 -0
  14. package/codec/{io/readers.js → boundary/reader.js} +2 -1
  15. package/codec/boundary/stream.js +361 -0
  16. package/codec/{browser → boundary}/worker.js +45 -42
  17. package/codec/core/constants.js +9 -131
  18. package/codec/core/geometry.js +25 -0
  19. package/codec/core/tables.js +117 -0
  20. package/codec/dequantization.js +54 -0
  21. package/codec/index.js +12 -11
  22. package/codec/pipeline/decoder.js +10 -399
  23. package/codec/pipeline/encoder.js +12 -434
  24. package/codec/quantization/spectrum.js +67 -0
  25. package/codec/quantization/stage.js +45 -0
  26. package/codec/{transforms/qmf.js → signal/bands.js} +8 -5
  27. package/codec/{transforms → signal}/fft.js +1 -1
  28. package/codec/{transforms/mdct.js → signal/spectrum.js} +16 -1
  29. package/codec/{core/buffers.js → state.js} +46 -35
  30. package/codec/{io → syntax}/bitstream.js +28 -9
  31. package/codec/{io/serialization.js → syntax/frame.js} +5 -101
  32. package/codec/synthesis/bands.js +201 -0
  33. package/codec/synthesis/pcm.js +50 -0
  34. package/codec/synthesis/stage.js +17 -0
  35. package/codec/utils.js +9 -52
  36. package/dist/carta1-worker-interface.min.js +1 -1
  37. package/dist/carta1-worker-interface.min.js.map +1 -1
  38. package/dist/carta1-worker.min.js +1 -1
  39. package/dist/carta1-worker.min.js.map +1 -1
  40. package/dist/carta1.min.js +1 -1
  41. package/dist/carta1.min.js.map +1 -1
  42. package/package.json +16 -16
  43. package/codec/coding/quantization.js +0 -149
  44. package/codec/io/processor.js +0 -558
@@ -1,450 +1,28 @@
1
1
  /**
2
2
  * Carta1 Audio Codec - Encoding Pipeline
3
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.
4
+ * The frame pipeline reads as analysis, allocation, then quantization. Byte
5
+ * packing remains the responsibility of serializeFrame().
15
6
  */
16
7
 
17
- import {
18
- pipe,
19
- throwError,
20
- reverseSpectrum,
21
- calculateBandOffset,
22
- } from '../utils.js'
23
- import { qmfAnalysis } from '../transforms/qmf.js'
24
- import { mdct64, mdct256, mdct512 } from '../transforms/mdct.js'
25
- import { groupIntoBFUs, quantize } from '../coding/quantization.js'
26
- import { allocateBits } from '../coding/bitallocation.js'
27
- import { BufferPool } from '../core/buffers.js'
8
+ import { allocate } from '../allocation/stage.js'
9
+ import { analyze } from '../analysis/stage.js'
28
10
  import { EncoderOptions } from '../core/options.js'
29
- import {
30
- WORD_LENGTH_BITS,
31
- WINDOW_SHORT,
32
- FFT_SIZE_LOW,
33
- FFT_SIZE_MID,
34
- FFT_SIZE_HIGH,
35
- MDCT_SIZE_SHORT,
36
- MDCT_SIZE_MID,
37
- MDCT_SIZE_LONG,
38
- MDCT_BAND_CONFIGS,
39
- MDCT_SHORT_BLOCK_SIZE,
40
- MDCT_OVERLAP_SIZE,
41
- } from '../core/constants.js'
42
- import { performFFT, detectTransient } from '../analysis/transient.js'
43
-
44
- /**
45
- * QMF Analysis Stage - Splits audio into frequency bands
46
- *
47
- * Performs two-stage QMF analysis to decompose the input signal into three bands:
48
- * - Low band (0-5.5kHz): processed at full resolution
49
- * - Mid band (5.5-11kHz): processed at half resolution
50
- * - High band (11-22kHz): processed at quarter resolution with delay compensation
51
- *
52
- * @param {Object} context - Pipeline context containing bufferPool
53
- * @param {BufferPool} context.bufferPool - Shared buffer pool for efficient memory management
54
- * @returns {Function} Stage function that processes PCM samples
55
- * @throws {Error} If bufferPool is not provided in context
56
- */
57
- export function qmfAnalysisStage(context) {
58
- const bufferPool =
59
- context?.bufferPool ??
60
- throwError('qmfAnalysisStage: bufferPool is required')
61
- const delays = bufferPool.qmfDelays
62
-
63
- /**
64
- * Process PCM samples through QMF analysis
65
- * @param {Float32Array} pcmSamples - Input PCM samples
66
- * @returns {Object} Analysis result containing frequency bands
67
- * @returns {Array<Float32Array>} returns.bands - Three frequency bands [low, mid, high]
68
- */
69
- return (pcmSamples) => {
70
- const stage1 = qmfAnalysis(
71
- pcmSamples,
72
- delays.lowBand,
73
- bufferPool.qmfWorkBuffers
74
- )
75
- delays.lowBand = stage1.newDelay
76
-
77
- const stage2 = qmfAnalysis(
78
- stage1.lowBand,
79
- delays.midBand,
80
- bufferPool.qmfWorkBuffers
81
- )
82
- delays.midBand = stage2.newDelay
83
-
84
- const delayedHigh =
85
- bufferPool.qmfWorkBuffers.highBandDelay[stage1.highBand.length]
86
- delayedHigh.set(delays.highBand)
87
- delayedHigh.set(stage1.highBand, delays.highBand.length)
88
-
89
- const highBand = delayedHigh.slice(0, stage1.highBand.length)
90
- delays.highBand = delayedHigh.slice(stage1.highBand.length)
91
-
92
- return {
93
- bands: [stage2.lowBand, stage2.highBand, highBand],
94
- }
95
- }
96
- }
97
-
98
- /**
99
- * Block Selection Stage - Detects transients to choose transform block sizes
100
- *
101
- * Analyzes frequency bands using FFT and transient detection to determine
102
- * appropriate MDCT block sizes. Short blocks are used for transient signals
103
- * to prevent pre-echo artifacts, while long blocks provide better frequency
104
- * resolution for steady-state signals.
105
- *
106
- * @param {Object} context - Pipeline context containing bufferPool
107
- * @param {BufferPool} context.bufferPool - Shared buffer pool for efficient memory management
108
- * @returns {Function} Stage function that processes QMF analysis results
109
- * @throws {Error} If bufferPool is not provided in context
110
- */
111
- export function blockSelectorStage(context) {
112
- const bufferPool =
113
- context?.bufferPool ??
114
- throwError('blockSelectorStage: bufferPool is required')
115
- const options =
116
- context?.options ?? throwError('blockSelectorStage: options is required')
117
-
118
- /**
119
- * Analyze frequency bands and determine block modes
120
- * @param {Object} input - QMF analysis results
121
- * @param {Array<Float32Array>} input.bands - Three frequency bands [low, mid, high]
122
- * @returns {Object} Block selection results
123
- * @returns {Array<Float32Array>} returns.bands - Original frequency bands
124
- * @returns {Array<number>} returns.blockModes - Block modes for each band
125
- */
126
- return (input) => {
127
- const { bands } = input
128
-
129
- // Use fixed block modes if provided, otherwise do transient detection
130
- let blockModes
131
- if (options.fixedBlockModes) {
132
- blockModes = options.fixedBlockModes
133
- } else {
134
- const fftSizes = [FFT_SIZE_LOW, FFT_SIZE_MID, FFT_SIZE_HIGH]
135
- blockModes = bands.map((bandSamples, bandIndex) => {
136
- const coeffs = performFFT(bandSamples, fftSizes[bandIndex])
137
- const transient = detectTransient(
138
- coeffs,
139
- bufferPool.transientDetection[bandIndex],
140
- options.transientThresholdLow
141
- )
142
- bufferPool.transientDetection[bandIndex] = coeffs
143
- return transient * Math.max(bandIndex + 1, 2)
144
- })
145
- }
146
-
147
- return {
148
- bands,
149
- blockModes,
150
- }
151
- }
152
- }
153
-
154
- /**
155
- * MDCT Transform Stage - Applies Modified Discrete Cosine Transform
156
- *
157
- * Transforms time-domain samples to frequency-domain coefficients using MDCT.
158
- * The transform size depends on the block mode determined by transient detection:
159
- * - Long blocks: 512/256 samples for better frequency resolution
160
- * - Short blocks: 64 samples for better time resolution
161
- *
162
- * Applies windowing and overlap-add processing to prevent blocking artifacts.
163
- * Mid and high bands are spectrally reversed to match ATRAC1 format.
164
- *
165
- * @param {Object} context - Pipeline context containing bufferPool
166
- * @param {BufferPool} context.bufferPool - Shared buffer pool for efficient memory management
167
- * @returns {Function} Stage function that processes block selection results
168
- * @throws {Error} If bufferPool is not provided in context
169
- */
170
- export function mdctStage(context) {
171
- const bufferPool =
172
- context?.bufferPool ?? throwError('mdctStage: bufferPool is required')
173
- const overlapBuffers = bufferPool.mdctOverlap
174
-
175
- // Transform function mapping for each band
176
- const TRANSFORM_FUNCS = [mdct256, mdct256, mdct512]
177
-
178
- /**
179
- * Transform a single frequency band using MDCT.
180
- * @param {Float32Array} samples
181
- * @param {number} bandIndex
182
- * @param {number} blockMode
183
- * @param {Float32Array} overlapBuffer
184
- * @param {Object} bufferPool
185
- * @returns {Float32Array}
186
- */
187
- function transformBand(
188
- samples,
189
- bandIndex,
190
- blockMode,
191
- overlapBuffer,
192
- bufferPool
193
- ) {
194
- const config = MDCT_BAND_CONFIGS[bandIndex]
195
- const transformFunc = TRANSFORM_FUNCS[bandIndex]
196
- const isLongBlock = blockMode === 0
197
-
198
- if (isLongBlock) {
199
- return transformLongBlock(
200
- samples,
201
- bandIndex,
202
- config,
203
- transformFunc,
204
- overlapBuffer,
205
- bufferPool
206
- )
207
- } else {
208
- return transformShortBlocks(
209
- samples,
210
- bandIndex,
211
- config,
212
- overlapBuffer,
213
- bufferPool
214
- )
215
- }
216
- }
217
-
218
- /**
219
- * Transform using long block (better frequency resolution).
220
- * @param {Float32Array} samples
221
- * @param {number} bandIndex
222
- * @param {Object} config
223
- * @param {Object} transformFunc
224
- * @param {Float32Array} overlapBuffer
225
- * @param {Object} bufferPool
226
- * @returns {Float32Array}
227
- */
228
- function transformLongBlock(
229
- samples,
230
- bandIndex,
231
- config,
232
- transformFunc,
233
- overlapBuffer,
234
- bufferPool
235
- ) {
236
- const mdctSize = bandIndex === 2 ? MDCT_SIZE_LONG : MDCT_SIZE_MID
237
- const mdctInput = bufferPool.transformBuffers[mdctSize]
238
- mdctInput.fill(0)
239
-
240
- // Apply overlap from previous frame
241
- mdctInput.set(overlapBuffer, config.windowStart)
242
-
243
- // Window and save tail for next frame
244
- applyTailWindowing(samples, overlapBuffer, config.size)
245
-
246
- // Add current frame samples
247
- mdctInput.set(samples, config.windowStart + MDCT_OVERLAP_SIZE)
248
-
249
- // Transform
250
- let spectrum = transformFunc.transform(mdctInput, bufferPool.mdctBuffers)
251
-
252
- // Apply spectral reversal for mid/high bands
253
- if (bandIndex > 0) {
254
- spectrum = reverseSpectrum(spectrum, bufferPool.reversalBuffers)
255
- }
256
-
257
- return spectrum
258
- }
259
-
260
- /**
261
- * Transform using short blocks (better time resolution for transients).
262
- * @param {Float32Array} samples
263
- * @param {number} bandIndex
264
- * @param {Object} config
265
- * @param {Float32Array} overlapBuffer
266
- * @param {Object} bufferPool
267
- * @returns {Float32Array}
268
- */
269
- function transformShortBlocks(
270
- samples,
271
- bandIndex,
272
- config,
273
- overlapBuffer,
274
- bufferPool
275
- ) {
276
- const numBlocks = 1 << (config.size === 256 ? 3 : 2) // 8 for band 2, 4 for bands 0-1
277
- const output = new Float32Array(config.size)
278
-
279
- for (let block = 0; block < numBlocks; block++) {
280
- const blockStart = block * MDCT_SHORT_BLOCK_SIZE
281
- const blockSamples = samples.subarray(
282
- blockStart,
283
- blockStart + MDCT_SHORT_BLOCK_SIZE
284
- )
285
-
286
- // Prepare MDCT input
287
- const mdctInput = bufferPool.transformBuffers[MDCT_SIZE_SHORT]
288
- mdctInput.fill(0)
289
- mdctInput.set(overlapBuffer, 0)
290
-
291
- // Window and save tail
292
- applyTailWindowing(blockSamples, overlapBuffer, MDCT_SHORT_BLOCK_SIZE)
293
- mdctInput.set(blockSamples, MDCT_OVERLAP_SIZE)
294
-
295
- // Transform
296
- let spectrum = mdct64.transform(mdctInput, bufferPool.mdctBuffers)
297
-
298
- // Apply spectral reversal for mid/high bands
299
- if (bandIndex > 0) {
300
- spectrum = reverseSpectrum(spectrum, bufferPool.reversalBuffers)
301
- }
302
-
303
- output.set(spectrum, blockStart)
304
- }
305
-
306
- return output
307
- }
308
-
309
- function applyTailWindowing(samples, overlapBuffer, blockSize) {
310
- const tailStart = blockSize - MDCT_OVERLAP_SIZE
311
- for (let i = 0; i < MDCT_OVERLAP_SIZE; i++) {
312
- const tailValue = samples[tailStart + i]
313
- overlapBuffer[i] = WINDOW_SHORT[i] * tailValue
314
- samples[tailStart + i] = tailValue * WINDOW_SHORT[31 - i]
315
- }
316
- }
317
-
318
- /**
319
- * Transform frequency bands using MDCT
320
- * @param {Object} input - Block selection results
321
- * @param {Array<Float32Array>} input.bands - Three frequency bands
322
- * @param {Array<number>} input.blockModes - Block modes for each band
323
- * @param {any} input.originalFrame - Original frame data (passed through)
324
- * @returns {Object} MDCT transform results
325
- * @returns {Array<Float32Array>} returns.bands - Original frequency bands
326
- * @returns {Float32Array} returns.coefficients - MDCT coefficients (512 samples)
327
- * @returns {Array<number>} returns.blockModes - Block modes
328
- * @returns {any} returns.originalFrame - Original frame data
329
- */
330
- return (input) => {
331
- const { bands, blockModes, originalFrame } = input
332
- const coefficients = new Float32Array(512)
333
-
334
- bands.forEach((bandSamples, bandIndex) => {
335
- const transformed = transformBand(
336
- bandSamples,
337
- bandIndex,
338
- blockModes[bandIndex],
339
- overlapBuffers[bandIndex],
340
- bufferPool
341
- )
342
-
343
- const offset = calculateBandOffset(bandIndex)
344
- coefficients.set(transformed, offset)
345
- })
346
-
347
- return { bands, coefficients, blockModes, originalFrame }
348
- }
349
- }
11
+ import { quantize } from '../quantization/stage.js'
12
+ import { BufferPool } from '../state.js'
13
+ import { pipe } from '../utils.js'
350
14
 
351
15
  /**
352
- * Quantization Stage - Performs RDO bit allocation and quantization
353
- *
354
- * The final encoding stage that:
355
- * 1. Groups coefficients into Block Floating Units (BFUs)
356
- * 2. Allocates bits using Rate-Distortion Optimization (RDO)
357
- * 3. Quantizes coefficients using scale factors and word lengths
358
- *
359
- * Uses RDO to optimize the allocation of bits for maximum quality
360
- * within the available bit budget.
361
- *
362
- * @returns {Function} Stage function that processes MDCT results
363
- * @throws {Error} If bufferPool is not provided in context
364
- */
365
- export function quantizationStage(context) {
366
- const options =
367
- context?.options ?? throwError('quantizationStage: options is required')
368
-
369
- /**
370
- * Perform RDO bit allocation and quantization
371
- * @param {Object} input - MDCT transform results
372
- * @param {Float32Array} input.coefficients - MDCT coefficients
373
- * @param {Array<number>} input.blockModes - Block modes
374
- * @returns {Object} Quantization results ready for bitstream encoding
375
- * @returns {number} returns.nBfu - Number of active BFUs
376
- * @returns {Int32Array} returns.scaleFactorIndices - Scale factor indices for each BFU
377
- * @returns {Int32Array} returns.wordLengthIndices - Word length indices for each BFU
378
- * @returns {Array<Int32Array>} returns.quantizedCoefficients - Quantized coefficient data
379
- * @returns {Array<number>} returns.blockModes - Block modes for each band
380
- */
381
- return (input) => {
382
- const { coefficients, blockModes } = input
383
-
384
- const { bfuData, bfuSizes, bfuCount } = groupIntoBFUs(
385
- coefficients,
386
- blockModes
387
- )
388
-
389
- const {
390
- bfuCount: selectedBfuCount,
391
- allocation,
392
- scaleFactorIndices,
393
- } = allocateBits(bfuData, bfuSizes, bfuCount, options.allocationBias)
394
-
395
- const slicedWordLengthIndices = allocation.slice(0, selectedBfuCount)
396
- const slicedScaleFactorIndices = scaleFactorIndices.slice(
397
- 0,
398
- selectedBfuCount
399
- )
400
- const quantizedCoefficients = []
401
-
402
- for (let bfu = 0; bfu < selectedBfuCount; bfu++) {
403
- const data = bfuData[bfu].subarray(0, bfuSizes[bfu])
404
- const wordLength = slicedWordLengthIndices[bfu]
405
- const bitsPerSample = WORD_LENGTH_BITS[wordLength]
406
- const quantized = quantize(data, scaleFactorIndices[bfu], bitsPerSample)
407
- quantizedCoefficients.push(quantized)
408
- }
409
-
410
- return {
411
- nBfu: selectedBfuCount,
412
- scaleFactorIndices: slicedScaleFactorIndices,
413
- wordLengthIndices: slicedWordLengthIndices,
414
- quantizedCoefficients,
415
- blockModes,
416
- }
417
- }
418
- }
419
-
420
- /**
421
- * Create ATRAC1 encoding pipeline
422
- *
423
- * Constructs a complete encoding pipeline that transforms PCM audio samples
424
- * into ATRAC1 compressed format. The pipeline processes audio through QMF
425
- * analysis, transient detection, MDCT transform, and psychoacoustic quantization.
426
- *
427
- * The returned function can be called repeatedly to encode audio frames,
428
- * maintaining state through the shared buffer pool for efficient processing.
429
- *
430
- * @param {EncoderOptions} [options=new EncoderOptions()] - Encoding configuration
431
- * @param {BufferPool} [bufferPool=new BufferPool()] - Shared buffer pool for state
432
- * @returns {Function} Encoding pipeline function that processes PCM samples
16
+ * Create an ATRAC1 encoder.
433
17
  *
434
- * @example
435
- * const encoder = encode(new EncoderOptions(), new BufferPool())
436
- * const result = encoder(pcmSamples) // Returns quantized ATRAC1 frame data
18
+ * @param {EncoderOptions} [options=new EncoderOptions()]
19
+ * @param {BufferPool} [bufferPool=new BufferPool()]
20
+ * @returns {Function}
437
21
  */
438
22
  export function encode(
439
23
  options = new EncoderOptions(),
440
24
  bufferPool = new BufferPool()
441
25
  ) {
442
26
  const context = { options, bufferPool }
443
- return pipe(
444
- context,
445
- qmfAnalysisStage,
446
- blockSelectorStage,
447
- mdctStage,
448
- quantizationStage
449
- )
27
+ return pipe(context, analyze, allocate, quantize)
450
28
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Carta1 Audio Codec - Spectrum Quantization
3
+ *
4
+ * This module handles the quantization and dequantization of MDCT frequency coefficients
5
+ * for the Carta1 audio codec. It provides functions to convert floating-point frequency
6
+ * domain data into integer representations suitable for bitstream encoding.
7
+ *
8
+ * The quantization process uses configurable scale factors to maintain audio quality
9
+ * while achieving the target compression ratio. The module supports both uniform
10
+ * and non-uniform quantization schemes based on perceptual importance.
11
+ */
12
+
13
+ import { QUANTIZATION_SIGN_BIT_SHIFT } from '../core/constants.js'
14
+ import { SCALE_FACTORS } from '../core/tables.js'
15
+
16
+ /**
17
+ * Quantize coefficients.
18
+ * @param {Float32Array} coefficients
19
+ * @param {number} scaleFactorIndex
20
+ * @param {number} bitsPerSample
21
+ * @returns {Int32Array} out
22
+ */
23
+ export function quantize(coefficients, scaleFactorIndex, bitsPerSample) {
24
+ const length = coefficients.length
25
+ const out = new Int32Array(length)
26
+ if (bitsPerSample === 0 || scaleFactorIndex === 0) {
27
+ out.fill(0, 0, length)
28
+ return out
29
+ }
30
+
31
+ const scaleFactor = SCALE_FACTORS[scaleFactorIndex]
32
+ const quantRange = (1 << (bitsPerSample - QUANTIZATION_SIGN_BIT_SHIFT)) - 1
33
+ const normFactor = quantRange / scaleFactor
34
+
35
+ const hi = quantRange
36
+ const lo = -quantRange
37
+
38
+ for (let i = 0; i < length; i++) {
39
+ const x = coefficients[i] * normFactor
40
+ const y = (x + (x >= 0 ? 0.5 : -0.5)) | 0
41
+ out[i] = y > hi ? hi : y < lo ? lo : y
42
+ }
43
+
44
+ return out
45
+ }
46
+
47
+ /**
48
+ * Dequantize coefficients.
49
+ * @param {Int32Array} quantized
50
+ * @param {number} scaleFactorIndex
51
+ * @param {number} bitsPerSample
52
+ * @returns {Float32Array}
53
+ */
54
+ export function dequantize(quantized, scaleFactorIndex, bitsPerSample) {
55
+ if (bitsPerSample === 0 || scaleFactorIndex === 0) {
56
+ return new Float32Array(quantized.length)
57
+ }
58
+
59
+ const scaleFactor = SCALE_FACTORS[scaleFactorIndex]
60
+ const quantRange = (1 << (bitsPerSample - QUANTIZATION_SIGN_BIT_SHIFT)) - 1
61
+
62
+ const deq = new Float32Array(quantized.length)
63
+ for (let i = 0; i < quantized.length; i++) {
64
+ deq[i] = (quantized[i] * scaleFactor) / quantRange
65
+ }
66
+ return deq
67
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Carta1 quantization stage.
3
+ */
4
+
5
+ import { WORD_LENGTH_BITS } from '../core/tables.js'
6
+ import { quantize as quantizeCoefficient } from './spectrum.js'
7
+
8
+ /**
9
+ * Materialize retained allocation as quantized ATRAC1 symbols.
10
+ *
11
+ * @returns {QuantizeFrame}
12
+ */
13
+ export function quantize() {
14
+ /**
15
+ * @param {object} input
16
+ * @returns {object}
17
+ */
18
+ return (input) => {
19
+ const {
20
+ bfuData,
21
+ bfuSizes,
22
+ nBfu,
23
+ scaleFactorIndices,
24
+ wordLengthIndices,
25
+ blockModes,
26
+ } = input
27
+ const quantizedCoefficients = []
28
+
29
+ for (let bfu = 0; bfu < nBfu; bfu++) {
30
+ const data = bfuData[bfu].subarray(0, bfuSizes[bfu])
31
+ const bitsPerSample = WORD_LENGTH_BITS[wordLengthIndices[bfu]]
32
+ quantizedCoefficients.push(
33
+ quantizeCoefficient(data, scaleFactorIndices[bfu], bitsPerSample)
34
+ )
35
+ }
36
+
37
+ return {
38
+ nBfu,
39
+ scaleFactorIndices,
40
+ wordLengthIndices,
41
+ quantizedCoefficients,
42
+ blockModes,
43
+ }
44
+ }
45
+ }
@@ -6,7 +6,8 @@
6
6
  * bands for independent processing and coding.
7
7
  */
8
8
 
9
- import { QMF_DELAY, QMF_EVEN, QMF_ODD } from '../core/constants.js'
9
+ import { QMF_DELAY } from '../core/constants.js'
10
+ import { QMF_EVEN, QMF_ODD } from '../core/tables.js'
10
11
  import { throwError } from '../utils.js'
11
12
 
12
13
  /**
@@ -16,13 +17,15 @@ import { throwError } from '../utils.js'
16
17
  * @param {Object} qmfWorkBuffers - Optional work buffers for processing
17
18
  * @returns {Object} Object containing lowBand, highBand, and newDelay
18
19
  */
19
- export function qmfAnalysis(input, delayLine, qmfWorkBuffers = null) {
20
+ export function analyzeQmfPair(input, delayLine, qmfWorkBuffers = null) {
20
21
  const inputLength = input.length
21
22
  const outputLength = inputLength >> 1
22
23
 
23
24
  const workBuffer =
24
25
  qmfWorkBuffers?.delay?.[inputLength] ??
25
- throwError(`qmfAnalysis: qmfWorkBuffers.delay[${inputLength}] is required`)
26
+ throwError(
27
+ `analyzeQmfPair: qmfWorkBuffers.delay[${inputLength}] is required`
28
+ )
26
29
  workBuffer.set(delayLine)
27
30
  workBuffer.set(input, delayLine.length)
28
31
 
@@ -57,7 +60,7 @@ export function qmfAnalysis(input, delayLine, qmfWorkBuffers = null) {
57
60
  * @param {Object} qmfWorkBuffers - Optional work buffers for processing
58
61
  * @returns {Object} Object containing output and newDelay
59
62
  */
60
- export function qmfSynthesis(
63
+ export function synthesizeQmfPair(
61
64
  lowBand,
62
65
  highBand,
63
66
  delayLine,
@@ -69,7 +72,7 @@ export function qmfSynthesis(
69
72
  const workBuffer =
70
73
  qmfWorkBuffers?.delay?.[outputLength] ??
71
74
  throwError(
72
- `qmfSynthesis: qmfWorkBuffers.delay[${outputLength}] is required`
75
+ `synthesizeQmfPair: qmfWorkBuffers.delay[${outputLength}] is required`
73
76
  )
74
77
  workBuffer.set(delayLine)
75
78
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Carta1 Audio Codec - Fast Fourier Transform
2
+ * Carta1 Fast Fourier Transform operator.
3
3
  *
4
4
  * This module implements a radix-2 Cooley-Tukey FFT algorithm for frequency domain
5
5
  * analysis used in psychoacoustic modeling and transient detection.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Carta1 Audio Codec - Modified Discrete Cosine Transform
2
+ * Carta1 spectrum signal operators.
3
3
  *
4
4
  * This module implements forward and inverse MDCT transforms for the ATRAC1 codec.
5
5
  * The MDCT provides time-frequency analysis with perfect reconstruction and
@@ -243,3 +243,18 @@ export function overlapAdd(prev, curr, window) {
243
243
 
244
244
  return output
245
245
  }
246
+
247
+ /**
248
+ * Reverse spectral order for ATRAC1 mid and high bands.
249
+ *
250
+ * @param {Float32Array} spectrum
251
+ * @param {Object} reversalBuffers
252
+ * @returns {Float32Array}
253
+ */
254
+ export function reverseSpectrum(spectrum, reversalBuffers) {
255
+ const reversed = reversalBuffers[spectrum.length]
256
+ for (let i = 0; i < spectrum.length; i++) {
257
+ reversed[i] = spectrum[spectrum.length - 1 - i]
258
+ }
259
+ return reversed
260
+ }