carta1 1.0.21 → 1.1.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/codec/browser/interface.js +0 -1
- package/codec/coding/bitallocation.js +205 -303
- package/codec/coding/quantization.js +79 -78
- package/codec/core/constants.js +53 -153
- package/codec/core/options.js +0 -13
- package/codec/pipeline/decoder.js +23 -3
- package/codec/pipeline/encoder.js +37 -39
- 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/analysis/psychoacoustics.js +0 -453
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Carta1 Audio Codec - Quantization
|
|
2
|
+
* Carta1 Audio Codec - Quantization Module
|
|
3
3
|
*
|
|
4
|
-
* This module
|
|
5
|
-
* for the
|
|
6
|
-
*
|
|
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
|
+
* Key functions:
|
|
9
|
+
* - Quantization of coefficients using scale factors and bit depths
|
|
10
|
+
* - Dequantization for reconstruction during decoding
|
|
11
|
+
* - BFU (Band Frequency Unit) organization for psychoacoustic processing
|
|
12
|
+
*
|
|
13
|
+
* The quantization process uses configurable scale factors to maintain audio quality
|
|
14
|
+
* while achieving the target compression ratio. The module supports both uniform
|
|
15
|
+
* and non-uniform quantization schemes based on perceptual importance.
|
|
7
16
|
*/
|
|
8
17
|
|
|
9
18
|
import {
|
|
@@ -16,70 +25,43 @@ import {
|
|
|
16
25
|
} from '../core/constants.js'
|
|
17
26
|
|
|
18
27
|
/**
|
|
19
|
-
*
|
|
20
|
-
* @param {Float32Array} coefficients
|
|
21
|
-
* @
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
let maxAmplitude = 0
|
|
25
|
-
|
|
26
|
-
for (let i = 0; i < coefficients.length; i++) {
|
|
27
|
-
maxAmplitude = Math.max(maxAmplitude, Math.abs(coefficients[i]))
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (maxAmplitude === 0) return 0
|
|
31
|
-
|
|
32
|
-
// Binary search for optimal scale factor
|
|
33
|
-
let low = 0
|
|
34
|
-
let high = SCALE_FACTORS.length - 1
|
|
35
|
-
|
|
36
|
-
while (low < high) {
|
|
37
|
-
const mid = (low + high) >> 1
|
|
38
|
-
if (maxAmplitude <= SCALE_FACTORS[mid]) {
|
|
39
|
-
high = mid
|
|
40
|
-
} else {
|
|
41
|
-
low = mid + 1
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return low
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Quantize floating-point coefficients to integer values
|
|
50
|
-
* @param {Float32Array} coefficients - Input coefficients to quantize
|
|
51
|
-
* @param {number} scaleFactorIndex - Index into SCALE_FACTORS table
|
|
52
|
-
* @param {number} bitsPerSample - Number of bits per quantized sample
|
|
53
|
-
* @returns {Int32Array} Quantized integer coefficients
|
|
28
|
+
* Quantize coefficients.
|
|
29
|
+
* @param {Float32Array} coefficients
|
|
30
|
+
* @param {number} scaleFactorIndex
|
|
31
|
+
* @param {number} bitsPerSample
|
|
32
|
+
* @returns {Int32Array} out
|
|
54
33
|
*/
|
|
55
34
|
export function quantize(coefficients, scaleFactorIndex, bitsPerSample) {
|
|
35
|
+
const length = coefficients.length
|
|
36
|
+
const out = new Int32Array(length)
|
|
56
37
|
if (bitsPerSample === 0 || scaleFactorIndex === 0) {
|
|
57
|
-
|
|
38
|
+
// Zero fast path
|
|
39
|
+
out.fill(0, 0, length)
|
|
40
|
+
return out
|
|
58
41
|
}
|
|
59
42
|
|
|
60
43
|
const scaleFactor = SCALE_FACTORS[scaleFactorIndex]
|
|
61
44
|
const quantRange = (1 << (bitsPerSample - QUANTIZATION_SIGN_BIT_SHIFT)) - 1
|
|
62
45
|
const normFactor = quantRange / scaleFactor
|
|
63
46
|
|
|
64
|
-
const
|
|
47
|
+
const hi = quantRange
|
|
48
|
+
const lo = -quantRange - 1
|
|
65
49
|
|
|
66
|
-
for (let i = 0; i <
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Math.min(quantRange, Math.round(normalized))
|
|
71
|
-
)
|
|
50
|
+
for (let i = 0; i < length; i++) {
|
|
51
|
+
const x = coefficients[i] * normFactor
|
|
52
|
+
const y = (x + (x >= 0 ? 0.5 : -0.5)) | 0
|
|
53
|
+
out[i] = y > hi ? hi : y < lo ? lo : y
|
|
72
54
|
}
|
|
73
55
|
|
|
74
|
-
return
|
|
56
|
+
return out
|
|
75
57
|
}
|
|
76
58
|
|
|
77
59
|
/**
|
|
78
|
-
* Dequantize
|
|
79
|
-
* @param {Int32Array} quantized
|
|
80
|
-
* @param {number} scaleFactorIndex
|
|
81
|
-
* @param {number} bitsPerSample
|
|
82
|
-
* @returns {Float32Array}
|
|
60
|
+
* Dequantize coefficients.
|
|
61
|
+
* @param {Int32Array} quantized
|
|
62
|
+
* @param {number} scaleFactorIndex
|
|
63
|
+
* @param {number} bitsPerSample
|
|
64
|
+
* @returns {Float32Array}
|
|
83
65
|
*/
|
|
84
66
|
export function dequantize(quantized, scaleFactorIndex, bitsPerSample) {
|
|
85
67
|
if (bitsPerSample === 0 || scaleFactorIndex === 0) {
|
|
@@ -90,24 +72,41 @@ export function dequantize(quantized, scaleFactorIndex, bitsPerSample) {
|
|
|
90
72
|
const quantRange = (1 << (bitsPerSample - QUANTIZATION_SIGN_BIT_SHIFT)) - 1
|
|
91
73
|
const denormFactor = scaleFactor / quantRange
|
|
92
74
|
|
|
93
|
-
const
|
|
94
|
-
|
|
75
|
+
const deq = new Float32Array(quantized.length)
|
|
95
76
|
for (let i = 0; i < quantized.length; i++) {
|
|
96
|
-
|
|
77
|
+
deq[i] = quantized[i] * denormFactor
|
|
97
78
|
}
|
|
98
|
-
|
|
99
|
-
return dequantized
|
|
79
|
+
return deq
|
|
100
80
|
}
|
|
101
81
|
|
|
102
82
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
83
|
+
* Groups MDCT coefficients into Band Frequency Units (BFUs) for bit allocation.
|
|
84
|
+
*
|
|
85
|
+
* This function organizes frequency-domain coefficients into perceptually meaningful
|
|
86
|
+
* frequency bands (BFUs) based on the block modes for each of the three frequency bands.
|
|
87
|
+
* It handles both long and short blocks, with different frequency resolutions and
|
|
88
|
+
* BFU start positions for each block type.
|
|
89
|
+
*
|
|
90
|
+
* The function implements copy-avoidance optimization: when a BFU slice is fully
|
|
91
|
+
* contained within a frequency band, it directly copies the data without buffer
|
|
92
|
+
* filling. For BFUs that span band boundaries or extend beyond the available
|
|
93
|
+
* coefficients, it handles partial copying and zero-padding appropriately.
|
|
94
|
+
*
|
|
95
|
+
* Frequency Band Structure:
|
|
96
|
+
* - Band 0: 0-128 coefficients (low frequencies)
|
|
97
|
+
* - Band 1: 128-256 coefficients (mid frequencies)
|
|
98
|
+
* - Band 2: 256-512 coefficients (high frequencies)
|
|
99
|
+
*
|
|
100
|
+
* Block Types:
|
|
101
|
+
* - Long blocks (mode 0): Higher frequency resolution, fewer temporal segments
|
|
102
|
+
* - Short blocks (mode 1): Lower frequency resolution, more temporal segments
|
|
103
|
+
*
|
|
104
|
+
* @param {Float32Array} coefficients
|
|
105
|
+
* @param {Array<number>} blockModes
|
|
106
|
+
* @returns {{bfuData: Array<Float32Array>, bfuSizes: Array<number>, bfuCount: number}}
|
|
108
107
|
*/
|
|
109
|
-
export function groupIntoBFUs(coefficients, blockModes
|
|
110
|
-
const bfuData =
|
|
108
|
+
export function groupIntoBFUs(coefficients, blockModes) {
|
|
109
|
+
const bfuData = []
|
|
111
110
|
const bfuSizes = []
|
|
112
111
|
|
|
113
112
|
let coeffIndex = 0
|
|
@@ -123,22 +122,24 @@ export function groupIntoBFUs(coefficients, blockModes, bfuBuffers = null) {
|
|
|
123
122
|
while (bfuIndex < bandEnd) {
|
|
124
123
|
const size = SPECS_PER_BFU[bfuIndex]
|
|
125
124
|
const startPos = startPositions[bfuIndex] - bandStart
|
|
126
|
-
|
|
127
|
-
const bfu =
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
125
|
+
const endPos = startPos + size
|
|
126
|
+
const bfu = new Float32Array(size)
|
|
127
|
+
|
|
128
|
+
if (startPos >= 0 && endPos <= bandSize) {
|
|
129
|
+
bfu.set(coefficients.subarray(bandStart + startPos, bandStart + endPos))
|
|
130
|
+
} else {
|
|
131
|
+
bfu.fill(0)
|
|
132
|
+
if (startPos < bandSize && endPos > 0) {
|
|
133
|
+
const srcStart = Math.max(0, startPos)
|
|
134
|
+
const srcEnd = Math.min(bandSize, endPos)
|
|
135
|
+
bfu.set(
|
|
136
|
+
coefficients.subarray(bandStart + srcStart, bandStart + srcEnd),
|
|
137
|
+
Math.max(0, -startPos)
|
|
138
|
+
)
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
|
|
139
|
-
|
|
140
|
-
bfuData.push(bfu)
|
|
141
|
-
}
|
|
142
|
+
bfuData.push(bfu)
|
|
142
143
|
bfuSizes.push(size)
|
|
143
144
|
bfuIndex++
|
|
144
145
|
}
|
package/codec/core/constants.js
CHANGED
|
@@ -117,43 +117,6 @@ export const QMF_ODD = (() => {
|
|
|
117
117
|
return odd
|
|
118
118
|
})()
|
|
119
119
|
|
|
120
|
-
// Bit allocation
|
|
121
|
-
export const INTERPOLATION_COMPENSATION_FACTOR = 0.25
|
|
122
|
-
|
|
123
|
-
// Quantization
|
|
124
|
-
export const MAX_WORD_LENGTH_INDEX = 15
|
|
125
|
-
export const WORD_LENGTH_BITS = new Int32Array([
|
|
126
|
-
0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
|
127
|
-
])
|
|
128
|
-
export const QUANT_DISTORTION_TABLE = (() => {
|
|
129
|
-
const table = new Float32Array(MAX_WORD_LENGTH_INDEX + 1)
|
|
130
|
-
table[0] = 1.0
|
|
131
|
-
for (let wl = 1; wl <= MAX_WORD_LENGTH_INDEX; wl++) {
|
|
132
|
-
const bitsPerSample = WORD_LENGTH_BITS[wl]
|
|
133
|
-
const quantRange = (1 << (bitsPerSample - 1)) - 1
|
|
134
|
-
table[wl] = 1.0 / (12.0 * quantRange * quantRange)
|
|
135
|
-
}
|
|
136
|
-
return table
|
|
137
|
-
})()
|
|
138
|
-
export const DISTORTION_DELTA_DB = (() => {
|
|
139
|
-
const table = new Float32Array(MAX_WORD_LENGTH_INDEX)
|
|
140
|
-
for (let i = 0; i < MAX_WORD_LENGTH_INDEX; i++) {
|
|
141
|
-
table[i] =
|
|
142
|
-
10 *
|
|
143
|
-
Math.log10(
|
|
144
|
-
QUANT_DISTORTION_TABLE[i + 1] / (QUANT_DISTORTION_TABLE[i] || 1.0)
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
return table
|
|
148
|
-
})()
|
|
149
|
-
export const SCALE_FACTORS = (() => {
|
|
150
|
-
const table = new Float32Array(64)
|
|
151
|
-
for (let i = 0; i < 64; i++) {
|
|
152
|
-
table[i] = Math.pow(2.0, i / 3.0 - 21)
|
|
153
|
-
}
|
|
154
|
-
return table
|
|
155
|
-
})()
|
|
156
|
-
|
|
157
120
|
// Psychoacoustic model
|
|
158
121
|
export const BARK_SCALE = (() => {
|
|
159
122
|
const table = new Float32Array(NUM_BFUS)
|
|
@@ -188,57 +151,6 @@ export const SPREADING_MATRIX = (() => {
|
|
|
188
151
|
return matrix
|
|
189
152
|
})()
|
|
190
153
|
|
|
191
|
-
// ISO/IEC 11172-3:1993 Psychoacoustic Model Constants
|
|
192
|
-
export const PSYMODEL_MIN_POWER_DB = -200
|
|
193
|
-
export const PSYMODEL_FFT_SIZE = 2048
|
|
194
|
-
|
|
195
|
-
// Pre-computes lookup tables for resampling the MDCT power spectrum into a PSD.
|
|
196
|
-
// For each PSD bin, these tables provide the two source MDCT indices and the
|
|
197
|
-
// linear interpolation weight needed to calculate the final power value.
|
|
198
|
-
export const [
|
|
199
|
-
PSYMODEL_PSD_SOURCE_IDX0,
|
|
200
|
-
PSYMODEL_PSD_SOURCE_IDX1,
|
|
201
|
-
PSYMODEL_PSD_INTERP_WEIGHT,
|
|
202
|
-
] = (() => {
|
|
203
|
-
const half = PSYMODEL_FFT_SIZE >>> 1
|
|
204
|
-
const sizeMinus1 = PSYMODEL_FFT_SIZE - 1
|
|
205
|
-
const halfDiv2 = half / 2
|
|
206
|
-
const scale = sizeMinus1 / half
|
|
207
|
-
|
|
208
|
-
const idx0 = new Uint16Array(half + 1)
|
|
209
|
-
const idx1 = new Uint16Array(half + 1)
|
|
210
|
-
const w1 = new Float32Array(half + 1)
|
|
211
|
-
|
|
212
|
-
for (let i = 0; i <= half; i++) {
|
|
213
|
-
const src = i * scale
|
|
214
|
-
const base = src | 0
|
|
215
|
-
|
|
216
|
-
const j0 = base
|
|
217
|
-
idx0[i] =
|
|
218
|
-
j0 < half
|
|
219
|
-
? j0 < halfDiv2
|
|
220
|
-
? j0
|
|
221
|
-
: half - 1 - (j0 - halfDiv2)
|
|
222
|
-
: sizeMinus1 - (j0 - half)
|
|
223
|
-
|
|
224
|
-
const j1 = base + 1
|
|
225
|
-
if (j1 >= PSYMODEL_FFT_SIZE) {
|
|
226
|
-
idx1[i] = idx0[i]
|
|
227
|
-
} else {
|
|
228
|
-
idx1[i] =
|
|
229
|
-
j1 < half
|
|
230
|
-
? j1 < halfDiv2
|
|
231
|
-
? j1
|
|
232
|
-
: half - 1 - (j1 - halfDiv2)
|
|
233
|
-
: sizeMinus1 - (j1 - half)
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
w1[i] = src - base
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return [idx0, idx1, w1]
|
|
240
|
-
})()
|
|
241
|
-
|
|
242
154
|
// FFT sizes for transient detection
|
|
243
155
|
export const FFT_SIZE_LOW = 128
|
|
244
156
|
export const FFT_SIZE_MID = 128
|
|
@@ -271,72 +183,60 @@ export const FRAME_HEADER_BITS = 16
|
|
|
271
183
|
export const FRAME_WORD_LENGTH_BITS = 4
|
|
272
184
|
export const FRAME_SCALE_FACTOR_BITS = 6
|
|
273
185
|
|
|
274
|
-
//
|
|
275
|
-
export const
|
|
276
|
-
export const
|
|
277
|
-
|
|
278
|
-
|
|
186
|
+
// Quantization
|
|
187
|
+
export const MAX_WORD_LENGTH_INDEX = 15
|
|
188
|
+
export const WORD_LENGTH_BITS = new Int32Array([
|
|
189
|
+
0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
|
190
|
+
])
|
|
191
|
+
export const QUANT_DISTORTION_TABLE = (() => {
|
|
192
|
+
const table = new Float32Array(MAX_WORD_LENGTH_INDEX + 1)
|
|
193
|
+
table[0] = 1.0
|
|
194
|
+
for (let wl = 1; wl <= MAX_WORD_LENGTH_INDEX; wl++) {
|
|
195
|
+
const bitsPerSample = WORD_LENGTH_BITS[wl]
|
|
196
|
+
const quantRange = (1 << (bitsPerSample - 1)) - 1
|
|
197
|
+
table[wl] = 1.0 / (12.0 * quantRange * quantRange)
|
|
198
|
+
}
|
|
199
|
+
return table
|
|
200
|
+
})()
|
|
201
|
+
export const DISTORTION_DELTA_DB = (() => {
|
|
202
|
+
const table = new Float32Array(MAX_WORD_LENGTH_INDEX)
|
|
203
|
+
for (let i = 0; i < MAX_WORD_LENGTH_INDEX; i++) {
|
|
204
|
+
table[i] =
|
|
205
|
+
10 *
|
|
206
|
+
Math.log10(
|
|
207
|
+
QUANT_DISTORTION_TABLE[i + 1] / (QUANT_DISTORTION_TABLE[i] || 1.0)
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
return table
|
|
211
|
+
})()
|
|
212
|
+
export const SCALE_FACTORS = (() => {
|
|
213
|
+
const table = new Float32Array(64)
|
|
214
|
+
for (let i = 0; i < 64; i++) {
|
|
215
|
+
table[i] = Math.pow(2.0, i / 3.0 - 21)
|
|
216
|
+
}
|
|
217
|
+
return table
|
|
218
|
+
})()
|
|
279
219
|
|
|
280
|
-
//
|
|
281
|
-
export const
|
|
282
|
-
export const
|
|
220
|
+
// RDO bit allocation
|
|
221
|
+
export const LP_DISTORTION_P = 4
|
|
222
|
+
export const LP_DISTORTION_CONSTANT = 1 / 80 // For L4 (p=4), it's 1/80.
|
|
223
|
+
export const { POW2_NEG_P, POW2_POS_P, SF_POW_P } = (() => {
|
|
224
|
+
// Calculate the 2^{-p*b} table
|
|
225
|
+
const maxBits = WORD_LENGTH_BITS[MAX_WORD_LENGTH_INDEX]
|
|
226
|
+
const pow2_neg_p = new Float64Array(maxBits + 1)
|
|
227
|
+
for (let b = 0; b <= maxBits; b++) {
|
|
228
|
+
pow2_neg_p[b] = Math.pow(2, -LP_DISTORTION_P * b)
|
|
229
|
+
}
|
|
283
230
|
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
0, 1, 2, 4, 5, 7, 8, 10, 12, 14, 16, 19, 22, 26, 31, 36, 44, 49, 54, 60, 67,
|
|
287
|
-
74, 80, 92, 105,
|
|
288
|
-
])
|
|
231
|
+
// Calculate the 2^{p} constant for the D(0) case
|
|
232
|
+
const pow2_pos_p = Math.pow(2, LP_DISTORTION_P)
|
|
289
233
|
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
[9.0, 6.959, -7.68], [10.0, 7.581, -8.08], [11.0, 8.169, -8.43], [12.0, 8.723, -8.75],
|
|
296
|
-
[13.0, 9.244, -9.05], [14.0, 9.734, -9.33], [15.0, 10.195, -9.61], [16.0, 10.629, -9.89],
|
|
297
|
-
[17.0, 11.037, -10.17], [18.0, 11.421, -10.47], [19.0, 11.783, -10.77], [20.0, 12.125, -11.1],
|
|
298
|
-
[21.0, 12.448, -11.44], [22.0, 12.753, -11.79], [23.0, 13.042, -12.17], [24.0, 13.317, -12.56],
|
|
299
|
-
[25.0, 13.577, -12.96], [26.0, 13.825, -13.38], [27.0, 14.062, -13.79], [28.0, 14.288, -14.21],
|
|
300
|
-
[29.0, 14.504, -14.63], [30.0, 14.711, -15.03], [31.0, 14.909, -15.41], [32.0, 15.1, -15.77],
|
|
301
|
-
[33.0, 15.283, -16.09], [34.0, 15.46, -16.37], [35.0, 15.631, -16.6], [36.0, 15.795, -16.78],
|
|
302
|
-
[37.0, 15.955, -16.91], [38.0, 16.11, -16.97], [39.0, 16.26, -16.98], [40.0, 16.405, -16.92],
|
|
303
|
-
[41.0, 16.547, -16.81], [42.0, 16.685, -16.65], [43.0, 16.82, -16.43], [44.0, 16.951, -16.17],
|
|
304
|
-
[45.0, 17.079, -15.87], [46.0, 17.204, -15.54], [47.0, 17.327, -15.19], [48.0, 17.447, -14.82],
|
|
305
|
-
[50.0, 17.68, -14.06], [52.0, 17.904, -13.32], [54.0, 18.121, -12.64], [56.0, 18.331, -12.04],
|
|
306
|
-
[58.0, 18.534, -11.53], [60.0, 18.73, -11.11], [62.0, 18.922, -10.77], [64.0, 19.108, -10.49],
|
|
307
|
-
[66.0, 19.288, -10.26], [68.0, 19.464, -10.07], [70.0, 19.635, -9.89], [72.0, 19.801, -9.72],
|
|
308
|
-
[74.0, 19.963, -9.54], [76.0, 20.12, -9.37], [78.0, 20.273, -9.18], [80.0, 20.421, -8.97],
|
|
309
|
-
[82.0, 20.565, -8.75], [84.0, 20.705, -8.51], [86.0, 20.84, -8.26], [88.0, 20.971, -7.98],
|
|
310
|
-
[90.0, 21.099, -7.68], [92.0, 21.222, -7.36], [94.0, 21.341, -7.02], [96.0, 21.457, -6.65],
|
|
311
|
-
[100.0, 21.676, -5.85], [104.0, 21.882, -4.93], [108.0, 22.074, -3.9], [112.0, 22.253, -2.75],
|
|
312
|
-
[116.0, 22.42, -1.46], [120.0, 22.575, -0.03], [124.0, 22.721, 1.56], [128.0, 22.857, 3.31],
|
|
313
|
-
[132.0, 22.984, 5.23], [136.0, 23.102, 7.34], [140.0, 23.213, 9.64], [144.0, 23.317, 12.15],
|
|
314
|
-
[148.0, 23.414, 14.88], [152.0, 23.506, 17.84], [156.0, 23.592, 21.05], [160.0, 23.673, 24.52],
|
|
315
|
-
[164.0, 23.749, 28.25], [168.0, 23.821, 32.27], [172.0, 23.888, 36.59], [176.0, 23.952, 41.22],
|
|
316
|
-
[180.0, 24.013, 46.18], [184.0, 24.07, 51.49], [188.0, 24.124, 56.0], [192.0, 24.176, 56.0],
|
|
317
|
-
[196.0, 24.225, 56.0], [200.0, 24.271, 56.0], [204.0, 24.316, 56.0], [208.0, 24.358, 56.0],
|
|
318
|
-
[212.0, 24.398, 56.0], [216.0, 24.436, 56.0], [220.0, 24.473, 56.0], [224.0, 24.508, 56.0],
|
|
319
|
-
[228.0, 24.541, 56.0], [232.0, 24.573, 56.0]
|
|
320
|
-
]
|
|
321
|
-
export const PSYMODEL_CB_FREQ_INDICES = PSYMODEL_CRITICAL_BANDS.map(
|
|
322
|
-
(cbIdx) => PSYMODEL_THRESHOLD_TABLE[cbIdx][0] - 1
|
|
323
|
-
)
|
|
234
|
+
// Calculate the (scale_factor)^p table
|
|
235
|
+
const sfPowP = new Float64Array(SCALE_FACTORS.length)
|
|
236
|
+
for (let i = 0; i < SCALE_FACTORS.length; i++) {
|
|
237
|
+
sfPowP[i] = Math.pow(SCALE_FACTORS[i], LP_DISTORTION_P)
|
|
238
|
+
}
|
|
324
239
|
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
|
329
|
-
41, 42, 43, 44, 45, 46, 47, 47, 48, 48, 49, 49, 50, 50, 51, 51, 52, 52, 53,
|
|
330
|
-
53, 54, 54, 55, 55, 56, 56, 57, 57, 58, 58, 59, 59, 60, 60, 61, 61, 62, 62,
|
|
331
|
-
63, 63, 64, 64, 65, 65, 66, 66, 67, 67, 68, 68, 69, 69, 70, 70, 71, 71, 71,
|
|
332
|
-
71, 72, 72, 72, 72, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76,
|
|
333
|
-
76, 76, 77, 77, 77, 77, 78, 78, 78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 81,
|
|
334
|
-
81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, 84, 85, 85, 85, 85,
|
|
335
|
-
86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90,
|
|
336
|
-
90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95,
|
|
337
|
-
95, 95, 96, 96, 96, 96, 97, 97, 97, 97, 98, 98, 98, 98, 99, 99, 99, 99, 100,
|
|
338
|
-
100, 100, 100, 101, 101, 101, 101, 102, 102, 102, 102, 103, 103, 103, 103,
|
|
339
|
-
104, 104, 104, 104, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105,
|
|
340
|
-
105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105,
|
|
341
|
-
105,
|
|
342
|
-
])
|
|
240
|
+
// Return all computed tables in a single object
|
|
241
|
+
return { POW2_NEG_P: pow2_neg_p, POW2_POS_P: pow2_pos_p, SF_POW_P: sfPowP }
|
|
242
|
+
})()
|
package/codec/core/options.js
CHANGED
|
@@ -18,7 +18,6 @@ class EncoderOptions {
|
|
|
18
18
|
transientThresholdLow: 1.0,
|
|
19
19
|
transientThresholdMid: 1.5,
|
|
20
20
|
transientThresholdHigh: 2.0,
|
|
21
|
-
normalizationDb: 68,
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
this.metadata = {
|
|
@@ -46,14 +45,6 @@ class EncoderOptions {
|
|
|
46
45
|
range: [0.01, 4],
|
|
47
46
|
step: 0.01,
|
|
48
47
|
},
|
|
49
|
-
normalizationDb: {
|
|
50
|
-
default: this.values.normalizationDb,
|
|
51
|
-
name: 'Normalization Target (dB)',
|
|
52
|
-
description:
|
|
53
|
-
'Sets the target maximum level in decibels for psychoacoustic model normalization. This affects the dynamic range allocation and quantization precision. Higher values may increase bitrate but preserve more detail.',
|
|
54
|
-
range: [60, 80],
|
|
55
|
-
step: 1,
|
|
56
|
-
},
|
|
57
48
|
}
|
|
58
49
|
|
|
59
50
|
// Apply user-provided options
|
|
@@ -122,10 +113,6 @@ class EncoderOptions {
|
|
|
122
113
|
return this.values.transientThresholdHigh
|
|
123
114
|
}
|
|
124
115
|
|
|
125
|
-
get normalizationDb() {
|
|
126
|
-
return this.values.normalizationDb
|
|
127
|
-
}
|
|
128
|
-
|
|
129
116
|
getMetadata(key) {
|
|
130
117
|
return this.metadata[key]
|
|
131
118
|
}
|
|
@@ -122,7 +122,14 @@ export function imdctStage(context) {
|
|
|
122
122
|
const TRANSFORM_FUNCS = [imdct256, imdct256, imdct512]
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
* Inverse transform a single frequency band
|
|
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}
|
|
126
133
|
*/
|
|
127
134
|
function transformBand(
|
|
128
135
|
coeffs,
|
|
@@ -156,7 +163,14 @@ export function imdctStage(context) {
|
|
|
156
163
|
}
|
|
157
164
|
|
|
158
165
|
/**
|
|
159
|
-
* Inverse transform using long block
|
|
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}
|
|
160
174
|
*/
|
|
161
175
|
function inverseLongBlock(
|
|
162
176
|
coeffs,
|
|
@@ -219,7 +233,13 @@ export function imdctStage(context) {
|
|
|
219
233
|
}
|
|
220
234
|
|
|
221
235
|
/**
|
|
222
|
-
* Inverse transform using short blocks
|
|
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}
|
|
223
243
|
*/
|
|
224
244
|
function inverseShortBlocks(
|
|
225
245
|
coeffs,
|
|
@@ -22,11 +22,7 @@ import {
|
|
|
22
22
|
} from '../utils.js'
|
|
23
23
|
import { qmfAnalysis } from '../transforms/qmf.js'
|
|
24
24
|
import { mdct64, mdct256, mdct512 } from '../transforms/mdct.js'
|
|
25
|
-
import {
|
|
26
|
-
groupIntoBFUs,
|
|
27
|
-
findScaleFactor,
|
|
28
|
-
quantize,
|
|
29
|
-
} from '../coding/quantization.js'
|
|
25
|
+
import { groupIntoBFUs, quantize } from '../coding/quantization.js'
|
|
30
26
|
import { allocateBits } from '../coding/bitallocation.js'
|
|
31
27
|
import { BufferPool } from '../core/buffers.js'
|
|
32
28
|
import { EncoderOptions } from '../core/options.js'
|
|
@@ -44,7 +40,6 @@ import {
|
|
|
44
40
|
MDCT_OVERLAP_SIZE,
|
|
45
41
|
} from '../core/constants.js'
|
|
46
42
|
import { performFFT, detectTransient } from '../analysis/transient.js'
|
|
47
|
-
import { psychoAnalysis } from '../analysis/psychoacoustics.js'
|
|
48
43
|
|
|
49
44
|
/**
|
|
50
45
|
* QMF Analysis Stage - Splits audio into frequency bands
|
|
@@ -174,7 +169,13 @@ export function mdctStage(context) {
|
|
|
174
169
|
const TRANSFORM_FUNCS = [mdct256, mdct256, mdct512]
|
|
175
170
|
|
|
176
171
|
/**
|
|
177
|
-
* Transform a single frequency band using MDCT
|
|
172
|
+
* Transform a single frequency band using MDCT.
|
|
173
|
+
* @param {Float32Array} samples
|
|
174
|
+
* @param {number} bandIndex
|
|
175
|
+
* @param {number} blockMode
|
|
176
|
+
* @param {Float32Array} overlapBuffer
|
|
177
|
+
* @param {Object} bufferPool
|
|
178
|
+
* @returns {Float32Array}
|
|
178
179
|
*/
|
|
179
180
|
function transformBand(
|
|
180
181
|
samples,
|
|
@@ -208,7 +209,14 @@ export function mdctStage(context) {
|
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
/**
|
|
211
|
-
* Transform using long block (better frequency resolution)
|
|
212
|
+
* Transform using long block (better frequency resolution).
|
|
213
|
+
* @param {Float32Array} samples
|
|
214
|
+
* @param {number} bandIndex
|
|
215
|
+
* @param {Object} config
|
|
216
|
+
* @param {Object} transformFunc
|
|
217
|
+
* @param {Float32Array} overlapBuffer
|
|
218
|
+
* @param {Object} bufferPool
|
|
219
|
+
* @returns {Float32Array}
|
|
212
220
|
*/
|
|
213
221
|
function transformLongBlock(
|
|
214
222
|
samples,
|
|
@@ -243,7 +251,13 @@ export function mdctStage(context) {
|
|
|
243
251
|
}
|
|
244
252
|
|
|
245
253
|
/**
|
|
246
|
-
* Transform using short blocks (better time resolution for transients)
|
|
254
|
+
* Transform using short blocks (better time resolution for transients).
|
|
255
|
+
* @param {Float32Array} samples
|
|
256
|
+
* @param {number} bandIndex
|
|
257
|
+
* @param {Object} config
|
|
258
|
+
* @param {Float32Array} overlapBuffer
|
|
259
|
+
* @param {Object} bufferPool
|
|
260
|
+
* @returns {Float32Array}
|
|
247
261
|
*/
|
|
248
262
|
function transformShortBlocks(
|
|
249
263
|
samples,
|
|
@@ -328,32 +342,22 @@ export function mdctStage(context) {
|
|
|
328
342
|
}
|
|
329
343
|
|
|
330
344
|
/**
|
|
331
|
-
* Quantization Stage - Performs
|
|
345
|
+
* Quantization Stage - Performs RDO bit allocation and quantization
|
|
332
346
|
*
|
|
333
347
|
* The final encoding stage that:
|
|
334
348
|
* 1. Groups coefficients into Block Floating Units (BFUs)
|
|
335
|
-
* 2.
|
|
336
|
-
* 3.
|
|
337
|
-
* 4. Quantizes coefficients using scale factors and word lengths
|
|
349
|
+
* 2. Allocates bits using Rate-Distortion Optimization (RDO)
|
|
350
|
+
* 3. Quantizes coefficients using scale factors and word lengths
|
|
338
351
|
*
|
|
339
|
-
* Uses
|
|
340
|
-
*
|
|
352
|
+
* Uses RDO to optimize the allocation of bits for maximum quality
|
|
353
|
+
* within the available bit budget.
|
|
341
354
|
*
|
|
342
|
-
* @param {Object} context - Pipeline context containing bufferPool and options
|
|
343
|
-
* @param {BufferPool} context.bufferPool - Shared buffer pool for efficient memory management
|
|
344
|
-
* @param {EncoderOptions} context.options - Encoding configuration options
|
|
345
355
|
* @returns {Function} Stage function that processes MDCT results
|
|
346
|
-
* @throws {Error} If bufferPool
|
|
356
|
+
* @throws {Error} If bufferPool is not provided in context
|
|
347
357
|
*/
|
|
348
|
-
export function quantizationStage(
|
|
349
|
-
const bufferPool =
|
|
350
|
-
context?.bufferPool ??
|
|
351
|
-
throwError('quantizationStage: bufferPool is required')
|
|
352
|
-
const options =
|
|
353
|
-
context?.options ?? throwError('quantizationStage: options is required')
|
|
354
|
-
|
|
358
|
+
export function quantizationStage() {
|
|
355
359
|
/**
|
|
356
|
-
* Perform
|
|
360
|
+
* Perform RDO bit allocation and quantization
|
|
357
361
|
* @param {Object} input - MDCT transform results
|
|
358
362
|
* @param {Float32Array} input.coefficients - MDCT coefficients
|
|
359
363
|
* @param {Array<number>} input.blockModes - Block modes
|
|
@@ -369,27 +373,21 @@ export function quantizationStage(context) {
|
|
|
369
373
|
|
|
370
374
|
const { bfuData, bfuSizes, bfuCount } = groupIntoBFUs(
|
|
371
375
|
coefficients,
|
|
372
|
-
blockModes
|
|
373
|
-
bufferPool.bfuData
|
|
376
|
+
blockModes
|
|
374
377
|
)
|
|
375
378
|
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
bfuCount
|
|
382
|
-
)
|
|
379
|
+
const {
|
|
380
|
+
bfuCount: selectedBfuCount,
|
|
381
|
+
allocation,
|
|
382
|
+
scaleFactorIndices,
|
|
383
|
+
} = allocateBits(bfuData, bfuSizes, bfuCount)
|
|
383
384
|
|
|
384
|
-
const scaleFactorIndices = new Int32Array(selectedBfuCount)
|
|
385
385
|
const quantizedCoefficients = []
|
|
386
386
|
|
|
387
387
|
for (let bfu = 0; bfu < selectedBfuCount; bfu++) {
|
|
388
388
|
const data = bfuData[bfu].subarray(0, bfuSizes[bfu])
|
|
389
389
|
const wordLength = allocation[bfu]
|
|
390
390
|
const bitsPerSample = WORD_LENGTH_BITS[wordLength]
|
|
391
|
-
|
|
392
|
-
scaleFactorIndices[bfu] = findScaleFactor(data)
|
|
393
391
|
const quantized = quantize(data, scaleFactorIndices[bfu], bitsPerSample)
|
|
394
392
|
quantizedCoefficients.push(quantized)
|
|
395
393
|
}
|