carta1 1.1.10 → 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.
- package/bin/cli.js +8 -4
- package/codec/{coding/bitallocation.js → allocation/solve.js} +131 -97
- package/codec/allocation/source.js +69 -0
- package/codec/allocation/stage.js +38 -0
- package/codec/analysis/bands.js +49 -0
- package/codec/analysis/{transient.js → blocks.js} +90 -47
- package/codec/analysis/spectrum.js +160 -0
- package/codec/analysis/stage.js +32 -0
- package/codec/{browser/interface.js → boundary/browser.js} +1 -1
- package/codec/boundary/container.js +96 -0
- package/codec/boundary/pcm.js +193 -0
- package/codec/boundary/processor.js +208 -0
- package/codec/{io/readers.js → boundary/reader.js} +2 -1
- package/codec/boundary/stream.js +361 -0
- package/codec/{browser → boundary}/worker.js +45 -42
- package/codec/core/constants.js +9 -131
- package/codec/core/geometry.js +25 -0
- package/codec/core/tables.js +117 -0
- package/codec/dequantization.js +54 -0
- package/codec/index.js +10 -11
- package/codec/pipeline/decoder.js +10 -399
- package/codec/pipeline/encoder.js +12 -434
- package/codec/quantization/spectrum.js +67 -0
- package/codec/quantization/stage.js +45 -0
- package/codec/{transforms/qmf.js → signal/bands.js} +8 -5
- package/codec/{transforms → signal}/fft.js +1 -1
- package/codec/{transforms/mdct.js → signal/spectrum.js} +16 -1
- package/codec/{core/buffers.js → state.js} +46 -35
- package/codec/{io → syntax}/bitstream.js +27 -8
- package/codec/{io/serialization.js → syntax/frame.js} +5 -101
- package/codec/synthesis/bands.js +201 -0
- package/codec/synthesis/pcm.js +50 -0
- package/codec/synthesis/stage.js +17 -0
- package/codec/utils.js +9 -52
- package/dist/carta1-worker-interface.min.js.map +1 -1
- package/dist/carta1-worker.min.js +1 -1
- package/dist/carta1-worker.min.js.map +1 -1
- package/dist/carta1.min.js +1 -1
- package/dist/carta1.min.js.map +1 -1
- package/package.json +1 -1
- package/codec/coding/quantization.js +0 -149
- package/codec/io/processor.js +0 -671
|
@@ -1,450 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Carta1 Audio Codec - Encoding Pipeline
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
435
|
-
*
|
|
436
|
-
*
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
`
|
|
75
|
+
`synthesizeQmfPair: qmfWorkBuffers.delay[${outputLength}] is required`
|
|
73
76
|
)
|
|
74
77
|
workBuffer.set(delayLine)
|
|
75
78
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Carta1
|
|
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
|
+
}
|