carta1 1.0.17 → 1.0.19
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/codec/core/buffers.js +5 -5
- package/codec/core/constants.js +11 -0
- package/codec/pipeline/decoder.js +184 -71
- package/codec/pipeline/encoder.js +157 -83
- package/codec/utils.js +40 -0
- 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/core/buffers.js
CHANGED
|
@@ -40,11 +40,11 @@ export class BufferPool {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// Transient detector
|
|
43
|
-
this.transientDetection =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
this.transientDetection = [
|
|
44
|
+
new Float32Array(64),
|
|
45
|
+
new Float32Array(64),
|
|
46
|
+
new Float32Array(128),
|
|
47
|
+
]
|
|
48
48
|
|
|
49
49
|
// MDCT/IMDCT work buffers
|
|
50
50
|
this.mdctBuffers = {
|
package/codec/core/constants.js
CHANGED
|
@@ -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 {
|
|
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
|
-
*
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
174
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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'
|
|
@@ -122,39 +130,21 @@ export function blockSelectorStage(context) {
|
|
|
122
130
|
*/
|
|
123
131
|
return (input) => {
|
|
124
132
|
const { bands } = input
|
|
125
|
-
const [
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
bufferPool.transientDetection
|
|
134
|
-
|
|
135
|
-
)
|
|
136
|
-
const midTransient = detectTransient(
|
|
137
|
-
midCoeffs,
|
|
138
|
-
bufferPool.transientDetection.prevMidCoeffs,
|
|
139
|
-
options.transientThresholdMid
|
|
140
|
-
)
|
|
141
|
-
const highTransient = detectTransient(
|
|
142
|
-
highCoeffs,
|
|
143
|
-
bufferPool.transientDetection.prevHighCoeffs,
|
|
144
|
-
options.transientThresholdHigh
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
bufferPool.transientDetection.prevLowCoeffs = lowCoeffs
|
|
148
|
-
bufferPool.transientDetection.prevMidCoeffs = midCoeffs
|
|
149
|
-
bufferPool.transientDetection.prevHighCoeffs = highCoeffs
|
|
133
|
+
const fftSizes = [FFT_SIZE_LOW, FFT_SIZE_MID, FFT_SIZE_HIGH]
|
|
134
|
+
const blockModes = bands.map((bandSamples, bandIndex) => {
|
|
135
|
+
const coeffs = performFFT(bandSamples, fftSizes[bandIndex])
|
|
136
|
+
const transient = detectTransient(
|
|
137
|
+
coeffs,
|
|
138
|
+
bufferPool.transientDetection[bandIndex],
|
|
139
|
+
options.transientThresholdLow
|
|
140
|
+
)
|
|
141
|
+
bufferPool.transientDetection[bandIndex] = coeffs
|
|
142
|
+
return transient * Math.max(bandIndex + 1, 2)
|
|
143
|
+
})
|
|
150
144
|
|
|
151
145
|
return {
|
|
152
146
|
bands,
|
|
153
|
-
blockModes
|
|
154
|
-
lowTransient ? 2 : 0,
|
|
155
|
-
midTransient ? 2 : 0,
|
|
156
|
-
highTransient ? 3 : 0,
|
|
157
|
-
],
|
|
147
|
+
blockModes,
|
|
158
148
|
}
|
|
159
149
|
}
|
|
160
150
|
}
|
|
@@ -180,6 +170,130 @@ export function mdctStage(context) {
|
|
|
180
170
|
context?.bufferPool ?? throwError('mdctStage: bufferPool is required')
|
|
181
171
|
const overlapBuffers = bufferPool.mdctOverlap
|
|
182
172
|
|
|
173
|
+
// Transform function mapping for each band
|
|
174
|
+
const TRANSFORM_FUNCS = [mdct256, mdct256, mdct512]
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Transform a single frequency band using MDCT
|
|
178
|
+
*/
|
|
179
|
+
function transformBand(
|
|
180
|
+
samples,
|
|
181
|
+
bandIndex,
|
|
182
|
+
blockMode,
|
|
183
|
+
overlapBuffer,
|
|
184
|
+
bufferPool
|
|
185
|
+
) {
|
|
186
|
+
const config = MDCT_BAND_CONFIGS[bandIndex]
|
|
187
|
+
const transformFunc = TRANSFORM_FUNCS[bandIndex]
|
|
188
|
+
const isLongBlock = blockMode === 0
|
|
189
|
+
|
|
190
|
+
if (isLongBlock) {
|
|
191
|
+
return transformLongBlock(
|
|
192
|
+
samples,
|
|
193
|
+
bandIndex,
|
|
194
|
+
config,
|
|
195
|
+
transformFunc,
|
|
196
|
+
overlapBuffer,
|
|
197
|
+
bufferPool
|
|
198
|
+
)
|
|
199
|
+
} else {
|
|
200
|
+
return transformShortBlocks(
|
|
201
|
+
samples,
|
|
202
|
+
bandIndex,
|
|
203
|
+
config,
|
|
204
|
+
overlapBuffer,
|
|
205
|
+
bufferPool
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Transform using long block (better frequency resolution)
|
|
212
|
+
*/
|
|
213
|
+
function transformLongBlock(
|
|
214
|
+
samples,
|
|
215
|
+
bandIndex,
|
|
216
|
+
config,
|
|
217
|
+
transformFunc,
|
|
218
|
+
overlapBuffer,
|
|
219
|
+
bufferPool
|
|
220
|
+
) {
|
|
221
|
+
const mdctSize = bandIndex === 2 ? MDCT_SIZE_LONG : MDCT_SIZE_MID
|
|
222
|
+
const mdctInput = bufferPool.transformBuffers[mdctSize]
|
|
223
|
+
mdctInput.fill(0)
|
|
224
|
+
|
|
225
|
+
// Apply overlap from previous frame
|
|
226
|
+
mdctInput.set(overlapBuffer, config.windowStart)
|
|
227
|
+
|
|
228
|
+
// Window and save tail for next frame
|
|
229
|
+
applyTailWindowing(samples, overlapBuffer, config.size)
|
|
230
|
+
|
|
231
|
+
// Add current frame samples
|
|
232
|
+
mdctInput.set(samples, config.windowStart + MDCT_OVERLAP_SIZE)
|
|
233
|
+
|
|
234
|
+
// Transform
|
|
235
|
+
let spectrum = transformFunc.transform(mdctInput, bufferPool.mdctBuffers)
|
|
236
|
+
|
|
237
|
+
// Apply spectral reversal for mid/high bands
|
|
238
|
+
if (bandIndex > 0) {
|
|
239
|
+
spectrum = reverseSpectrum(spectrum, bufferPool.reversalBuffers)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return spectrum
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Transform using short blocks (better time resolution for transients)
|
|
247
|
+
*/
|
|
248
|
+
function transformShortBlocks(
|
|
249
|
+
samples,
|
|
250
|
+
bandIndex,
|
|
251
|
+
config,
|
|
252
|
+
overlapBuffer,
|
|
253
|
+
bufferPool
|
|
254
|
+
) {
|
|
255
|
+
const numBlocks = 1 << (config.size === 256 ? 3 : 2) // 8 for band 2, 4 for bands 0-1
|
|
256
|
+
const output = new Float32Array(config.size)
|
|
257
|
+
|
|
258
|
+
for (let block = 0; block < numBlocks; block++) {
|
|
259
|
+
const blockStart = block * MDCT_SHORT_BLOCK_SIZE
|
|
260
|
+
const blockSamples = samples.subarray(
|
|
261
|
+
blockStart,
|
|
262
|
+
blockStart + MDCT_SHORT_BLOCK_SIZE
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
// Prepare MDCT input
|
|
266
|
+
const mdctInput = bufferPool.transformBuffers[MDCT_SIZE_SHORT]
|
|
267
|
+
mdctInput.fill(0)
|
|
268
|
+
mdctInput.set(overlapBuffer, 0)
|
|
269
|
+
|
|
270
|
+
// Window and save tail
|
|
271
|
+
applyTailWindowing(blockSamples, overlapBuffer, MDCT_SHORT_BLOCK_SIZE)
|
|
272
|
+
mdctInput.set(blockSamples, MDCT_OVERLAP_SIZE)
|
|
273
|
+
|
|
274
|
+
// Transform
|
|
275
|
+
let spectrum = mdct64.transform(mdctInput, bufferPool.mdctBuffers)
|
|
276
|
+
|
|
277
|
+
// Apply spectral reversal for mid/high bands
|
|
278
|
+
if (bandIndex > 0) {
|
|
279
|
+
spectrum = reverseSpectrum(spectrum, bufferPool.reversalBuffers)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
output.set(spectrum, blockStart)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return output
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function applyTailWindowing(samples, overlapBuffer, blockSize) {
|
|
289
|
+
const tailStart = blockSize - MDCT_OVERLAP_SIZE
|
|
290
|
+
for (let i = 0; i < MDCT_OVERLAP_SIZE; i++) {
|
|
291
|
+
const tailValue = samples[tailStart + i]
|
|
292
|
+
overlapBuffer[i] = WINDOW_SHORT[i] * tailValue
|
|
293
|
+
samples[tailStart + i] = tailValue * WINDOW_SHORT[31 - i]
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
183
297
|
/**
|
|
184
298
|
* Transform frequency bands using MDCT
|
|
185
299
|
* @param {Object} input - Block selection results
|
|
@@ -194,60 +308,20 @@ export function mdctStage(context) {
|
|
|
194
308
|
*/
|
|
195
309
|
return (input) => {
|
|
196
310
|
const { bands, blockModes, originalFrame } = input
|
|
197
|
-
|
|
198
311
|
const coefficients = new Float32Array(512)
|
|
199
312
|
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
}
|
|
313
|
+
bands.forEach((bandSamples, bandIndex) => {
|
|
314
|
+
const transformed = transformBand(
|
|
315
|
+
bandSamples,
|
|
316
|
+
bandIndex,
|
|
317
|
+
blockModes[bandIndex],
|
|
318
|
+
overlapBuffers[bandIndex],
|
|
319
|
+
bufferPool
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
const offset = calculateBandOffset(bandIndex)
|
|
323
|
+
coefficients.set(transformed, offset)
|
|
324
|
+
})
|
|
251
325
|
|
|
252
326
|
return { bands, coefficients, blockModes, originalFrame }
|
|
253
327
|
}
|
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)
|