carta1 1.0.22 → 1.1.1
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 +159 -250
- package/codec/coding/quantization.js +79 -78
- package/codec/core/buffers.js +1 -30
- package/codec/core/constants.js +10 -180
- package/codec/core/options.js +0 -13
- package/codec/pipeline/decoder.js +23 -3
- package/codec/pipeline/encoder.js +37 -43
- 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 -631
|
@@ -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/buffers.js
CHANGED
|
@@ -2,15 +2,7 @@
|
|
|
2
2
|
* Carta1 Audio Codec - Buffer Management
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
NUM_BFUS,
|
|
7
|
-
MAX_BFU_SIZE,
|
|
8
|
-
QMF_DELAY,
|
|
9
|
-
QMF_HIGH_BAND_DELAY,
|
|
10
|
-
PSYMODEL_CRITICAL_BANDS,
|
|
11
|
-
PSYMODEL_HALF_FFT_SIZE,
|
|
12
|
-
PSYMODEL_NUM_BINS,
|
|
13
|
-
} from './constants.js'
|
|
5
|
+
import { QMF_DELAY, QMF_HIGH_BAND_DELAY } from './constants.js'
|
|
14
6
|
|
|
15
7
|
export class BufferPool {
|
|
16
8
|
constructor() {
|
|
@@ -85,26 +77,5 @@ export class BufferPool {
|
|
|
85
77
|
128: new Float32Array(128),
|
|
86
78
|
256: new Float32Array(256),
|
|
87
79
|
}
|
|
88
|
-
|
|
89
|
-
// Bit allocation buffers
|
|
90
|
-
this.bfuData = new Array(NUM_BFUS)
|
|
91
|
-
for (let i = 0; i < NUM_BFUS; i++) {
|
|
92
|
-
this.bfuData[i] = new Float32Array(MAX_BFU_SIZE)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Psychoacoustic model buffers (large Float32Arrays)
|
|
96
|
-
this.psychoBuffers = {
|
|
97
|
-
psdDb: new Float32Array(PSYMODEL_NUM_BINS),
|
|
98
|
-
linPower: new Float32Array(PSYMODEL_NUM_BINS),
|
|
99
|
-
tonalSPL: new Float32Array(PSYMODEL_HALF_FFT_SIZE),
|
|
100
|
-
nonTonalSPL: new Float32Array(PSYMODEL_CRITICAL_BANDS.length),
|
|
101
|
-
maskSpl: new Float32Array(PSYMODEL_HALF_FFT_SIZE),
|
|
102
|
-
maskPsd: new Float32Array(PSYMODEL_HALF_FFT_SIZE),
|
|
103
|
-
maskBark: new Float32Array(PSYMODEL_HALF_FFT_SIZE),
|
|
104
|
-
maskTonalOffset: new Float32Array(PSYMODEL_HALF_FFT_SIZE),
|
|
105
|
-
maskPsdF1: new Float32Array(PSYMODEL_HALF_FFT_SIZE),
|
|
106
|
-
maskPsdF2: new Float32Array(PSYMODEL_HALF_FFT_SIZE),
|
|
107
|
-
criticalBandThresholds: new Float32Array(PSYMODEL_CRITICAL_BANDS.length),
|
|
108
|
-
}
|
|
109
80
|
}
|
|
110
81
|
}
|
package/codec/core/constants.js
CHANGED
|
@@ -151,59 +151,6 @@ export const SPREADING_MATRIX = (() => {
|
|
|
151
151
|
return matrix
|
|
152
152
|
})()
|
|
153
153
|
|
|
154
|
-
// ISO/IEC 11172-3:1993 Psychoacoustic Model Constants
|
|
155
|
-
export const PSYMODEL_MIN_POWER_DB = -200
|
|
156
|
-
export const PSYMODEL_FFT_SIZE = 2048
|
|
157
|
-
export const PSYMODEL_HALF_FFT_SIZE = PSYMODEL_FFT_SIZE >>> 1
|
|
158
|
-
export const PSYMODEL_NUM_BINS = PSYMODEL_HALF_FFT_SIZE + 1
|
|
159
|
-
|
|
160
|
-
// Pre-computes lookup tables for resampling the MDCT power spectrum into a PSD.
|
|
161
|
-
// For each PSD bin, these tables provide the two source MDCT indices and the
|
|
162
|
-
// linear interpolation weight needed to calculate the final power value.
|
|
163
|
-
export const [
|
|
164
|
-
PSYMODEL_PSD_SOURCE_IDX0,
|
|
165
|
-
PSYMODEL_PSD_SOURCE_IDX1,
|
|
166
|
-
PSYMODEL_PSD_INTERP_WEIGHT,
|
|
167
|
-
] = (() => {
|
|
168
|
-
const half = PSYMODEL_FFT_SIZE >>> 1
|
|
169
|
-
const sizeMinus1 = PSYMODEL_FFT_SIZE - 1
|
|
170
|
-
const halfDiv2 = half / 2
|
|
171
|
-
const scale = sizeMinus1 / half
|
|
172
|
-
|
|
173
|
-
const idx0 = new Uint16Array(half + 1)
|
|
174
|
-
const idx1 = new Uint16Array(half + 1)
|
|
175
|
-
const w1 = new Float32Array(half + 1)
|
|
176
|
-
|
|
177
|
-
for (let i = 0; i <= half; i++) {
|
|
178
|
-
const src = i * scale
|
|
179
|
-
const base = src | 0
|
|
180
|
-
|
|
181
|
-
const j0 = base
|
|
182
|
-
idx0[i] =
|
|
183
|
-
j0 < half
|
|
184
|
-
? j0 < halfDiv2
|
|
185
|
-
? j0
|
|
186
|
-
: half - 1 - (j0 - halfDiv2)
|
|
187
|
-
: sizeMinus1 - (j0 - half)
|
|
188
|
-
|
|
189
|
-
const j1 = base + 1
|
|
190
|
-
if (j1 >= PSYMODEL_FFT_SIZE) {
|
|
191
|
-
idx1[i] = idx0[i]
|
|
192
|
-
} else {
|
|
193
|
-
idx1[i] =
|
|
194
|
-
j1 < half
|
|
195
|
-
? j1 < halfDiv2
|
|
196
|
-
? j1
|
|
197
|
-
: half - 1 - (j1 - halfDiv2)
|
|
198
|
-
: sizeMinus1 - (j1 - half)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
w1[i] = src - base
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return [idx0, idx1, w1]
|
|
205
|
-
})()
|
|
206
|
-
|
|
207
154
|
// FFT sizes for transient detection
|
|
208
155
|
export const FFT_SIZE_LOW = 128
|
|
209
156
|
export const FFT_SIZE_MID = 128
|
|
@@ -236,133 +183,6 @@ export const FRAME_HEADER_BITS = 16
|
|
|
236
183
|
export const FRAME_WORD_LENGTH_BITS = 4
|
|
237
184
|
export const FRAME_SCALE_FACTOR_BITS = 6
|
|
238
185
|
|
|
239
|
-
// Tonal analysis flags
|
|
240
|
-
export const PSYMODEL_NOT_EXAMINED = 0
|
|
241
|
-
export const PSYMODEL_TONAL = 1
|
|
242
|
-
export const PSYMODEL_NON_TONAL = 2
|
|
243
|
-
export const PSYMODEL_IRRELEVANT = 3
|
|
244
|
-
|
|
245
|
-
// Pre-computed conversion factors
|
|
246
|
-
export const PSYMODEL_LOG10_FACTOR = 10 / Math.LN10
|
|
247
|
-
export const PSYMODEL_POW10_FACTOR = Math.LN10 / 10
|
|
248
|
-
|
|
249
|
-
// Critical band boundaries (0-24)
|
|
250
|
-
export const PSYMODEL_CRITICAL_BANDS = new Uint8Array([
|
|
251
|
-
0, 1, 2, 4, 5, 7, 8, 10, 12, 14, 16, 19, 22, 26, 31, 36, 44, 49, 54, 60, 67,
|
|
252
|
-
74, 80, 92, 105,
|
|
253
|
-
])
|
|
254
|
-
|
|
255
|
-
// Threshold table: [frequency_index, bark_value, threshold_value]
|
|
256
|
-
// prettier-ignore
|
|
257
|
-
export const PSYMODEL_THRESHOLD_TABLE = [
|
|
258
|
-
[1.0, 0.85, 13.87], [2.0, 1.694, 2.85], [3.0, 2.525, -1.28], [4.0, 3.337, -3.5],
|
|
259
|
-
[5.0, 4.124, -4.9], [6.0, 4.882, -5.89], [7.0, 5.608, -6.63], [8.0, 6.301, -7.21],
|
|
260
|
-
[9.0, 6.959, -7.68], [10.0, 7.581, -8.08], [11.0, 8.169, -8.43], [12.0, 8.723, -8.75],
|
|
261
|
-
[13.0, 9.244, -9.05], [14.0, 9.734, -9.33], [15.0, 10.195, -9.61], [16.0, 10.629, -9.89],
|
|
262
|
-
[17.0, 11.037, -10.17], [18.0, 11.421, -10.47], [19.0, 11.783, -10.77], [20.0, 12.125, -11.1],
|
|
263
|
-
[21.0, 12.448, -11.44], [22.0, 12.753, -11.79], [23.0, 13.042, -12.17], [24.0, 13.317, -12.56],
|
|
264
|
-
[25.0, 13.577, -12.96], [26.0, 13.825, -13.38], [27.0, 14.062, -13.79], [28.0, 14.288, -14.21],
|
|
265
|
-
[29.0, 14.504, -14.63], [30.0, 14.711, -15.03], [31.0, 14.909, -15.41], [32.0, 15.1, -15.77],
|
|
266
|
-
[33.0, 15.283, -16.09], [34.0, 15.46, -16.37], [35.0, 15.631, -16.6], [36.0, 15.795, -16.78],
|
|
267
|
-
[37.0, 15.955, -16.91], [38.0, 16.11, -16.97], [39.0, 16.26, -16.98], [40.0, 16.405, -16.92],
|
|
268
|
-
[41.0, 16.547, -16.81], [42.0, 16.685, -16.65], [43.0, 16.82, -16.43], [44.0, 16.951, -16.17],
|
|
269
|
-
[45.0, 17.079, -15.87], [46.0, 17.204, -15.54], [47.0, 17.327, -15.19], [48.0, 17.447, -14.82],
|
|
270
|
-
[50.0, 17.68, -14.06], [52.0, 17.904, -13.32], [54.0, 18.121, -12.64], [56.0, 18.331, -12.04],
|
|
271
|
-
[58.0, 18.534, -11.53], [60.0, 18.73, -11.11], [62.0, 18.922, -10.77], [64.0, 19.108, -10.49],
|
|
272
|
-
[66.0, 19.288, -10.26], [68.0, 19.464, -10.07], [70.0, 19.635, -9.89], [72.0, 19.801, -9.72],
|
|
273
|
-
[74.0, 19.963, -9.54], [76.0, 20.12, -9.37], [78.0, 20.273, -9.18], [80.0, 20.421, -8.97],
|
|
274
|
-
[82.0, 20.565, -8.75], [84.0, 20.705, -8.51], [86.0, 20.84, -8.26], [88.0, 20.971, -7.98],
|
|
275
|
-
[90.0, 21.099, -7.68], [92.0, 21.222, -7.36], [94.0, 21.341, -7.02], [96.0, 21.457, -6.65],
|
|
276
|
-
[100.0, 21.676, -5.85], [104.0, 21.882, -4.93], [108.0, 22.074, -3.9], [112.0, 22.253, -2.75],
|
|
277
|
-
[116.0, 22.42, -1.46], [120.0, 22.575, -0.03], [124.0, 22.721, 1.56], [128.0, 22.857, 3.31],
|
|
278
|
-
[132.0, 22.984, 5.23], [136.0, 23.102, 7.34], [140.0, 23.213, 9.64], [144.0, 23.317, 12.15],
|
|
279
|
-
[148.0, 23.414, 14.88], [152.0, 23.506, 17.84], [156.0, 23.592, 21.05], [160.0, 23.673, 24.52],
|
|
280
|
-
[164.0, 23.749, 28.25], [168.0, 23.821, 32.27], [172.0, 23.888, 36.59], [176.0, 23.952, 41.22],
|
|
281
|
-
[180.0, 24.013, 46.18], [184.0, 24.07, 51.49], [188.0, 24.124, 56.0], [192.0, 24.176, 56.0],
|
|
282
|
-
[196.0, 24.225, 56.0], [200.0, 24.271, 56.0], [204.0, 24.316, 56.0], [208.0, 24.358, 56.0],
|
|
283
|
-
[212.0, 24.398, 56.0], [216.0, 24.436, 56.0], [220.0, 24.473, 56.0], [224.0, 24.508, 56.0],
|
|
284
|
-
[228.0, 24.541, 56.0], [232.0, 24.573, 56.0]
|
|
285
|
-
]
|
|
286
|
-
export const PSYMODEL_CB_FREQ_INDICES = PSYMODEL_CRITICAL_BANDS.map(
|
|
287
|
-
(cbIdx) => PSYMODEL_THRESHOLD_TABLE[cbIdx][0] - 1
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
// Frequency to critical band mapping
|
|
291
|
-
export const PSYMODEL_FREQ_TO_CB_MAP = new Uint8Array([
|
|
292
|
-
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
|
|
293
|
-
22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
|
294
|
-
41, 42, 43, 44, 45, 46, 47, 47, 48, 48, 49, 49, 50, 50, 51, 51, 52, 52, 53,
|
|
295
|
-
53, 54, 54, 55, 55, 56, 56, 57, 57, 58, 58, 59, 59, 60, 60, 61, 61, 62, 62,
|
|
296
|
-
63, 63, 64, 64, 65, 65, 66, 66, 67, 67, 68, 68, 69, 69, 70, 70, 71, 71, 71,
|
|
297
|
-
71, 72, 72, 72, 72, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76,
|
|
298
|
-
76, 76, 77, 77, 77, 77, 78, 78, 78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 81,
|
|
299
|
-
81, 81, 81, 82, 82, 82, 82, 83, 83, 83, 83, 84, 84, 84, 84, 85, 85, 85, 85,
|
|
300
|
-
86, 86, 86, 86, 87, 87, 87, 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90,
|
|
301
|
-
90, 91, 91, 91, 91, 92, 92, 92, 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95,
|
|
302
|
-
95, 95, 96, 96, 96, 96, 97, 97, 97, 97, 98, 98, 98, 98, 99, 99, 99, 99, 100,
|
|
303
|
-
100, 100, 100, 101, 101, 101, 101, 102, 102, 102, 102, 103, 103, 103, 103,
|
|
304
|
-
104, 104, 104, 104, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105,
|
|
305
|
-
105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, 105,
|
|
306
|
-
105,
|
|
307
|
-
])
|
|
308
|
-
|
|
309
|
-
// Tonal detection neighbor offset patterns for different frequency ranges
|
|
310
|
-
// These avoid per-iteration array allocations during tonal masker detection
|
|
311
|
-
export const PSYMODEL_TONAL_OFFSETS_LOW_FREQ = Int8Array.of(-2, 2)
|
|
312
|
-
export const PSYMODEL_TONAL_OFFSETS_MID_FREQ = Int8Array.of(-3, -2, 2, 3)
|
|
313
|
-
export const PSYMODEL_TONAL_OFFSETS_HIGH_FREQ = Int8Array.of(-6, -5, -4, -3, -2 , 2, 3, 4, 5, 6) // prettier-ignore
|
|
314
|
-
|
|
315
|
-
// Bit allocation
|
|
316
|
-
export const INTERPOLATION_COMPENSATION_FACTOR = 0.25
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Precomputed interpolation data for psychoacoustic mask interpolation
|
|
320
|
-
* Creates lookup table for interpolating psychoacoustic mask values across BFUs
|
|
321
|
-
*/
|
|
322
|
-
export const BFU_MASK_INTERPOLATION_TABLE = (() => {
|
|
323
|
-
const interpolationEntries = new Array(NUM_BFUS)
|
|
324
|
-
const frequencyPerBin = SAMPLE_RATE / PSYMODEL_FFT_SIZE
|
|
325
|
-
const criticalBandIndices = PSYMODEL_CB_FREQ_INDICES
|
|
326
|
-
|
|
327
|
-
for (let bfuIndex = 0; bfuIndex < NUM_BFUS; bfuIndex++) {
|
|
328
|
-
const fftBinIndex = Math.round(BFU_FREQUENCIES[bfuIndex] / frequencyPerBin)
|
|
329
|
-
|
|
330
|
-
// Find critical band index using binary search
|
|
331
|
-
let lowIndex = 0
|
|
332
|
-
let highIndex = criticalBandIndices.length - 1
|
|
333
|
-
|
|
334
|
-
while (lowIndex < highIndex) {
|
|
335
|
-
const midIndex = (lowIndex + highIndex + 1) >> 1
|
|
336
|
-
if (criticalBandIndices[midIndex] <= fftBinIndex) {
|
|
337
|
-
lowIndex = midIndex
|
|
338
|
-
} else {
|
|
339
|
-
highIndex = midIndex - 1
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const currentBandIndex = lowIndex
|
|
344
|
-
const nextBandIndex = Math.min(
|
|
345
|
-
currentBandIndex + 1,
|
|
346
|
-
criticalBandIndices.length - 1
|
|
347
|
-
)
|
|
348
|
-
const currentBandFreq = criticalBandIndices[currentBandIndex]
|
|
349
|
-
const nextBandFreq = criticalBandIndices[nextBandIndex]
|
|
350
|
-
const bandWidth = nextBandFreq - currentBandFreq || 1
|
|
351
|
-
const inverseBandWidth = 1 / bandWidth
|
|
352
|
-
|
|
353
|
-
interpolationEntries[bfuIndex] = {
|
|
354
|
-
band: currentBandIndex,
|
|
355
|
-
next: nextBandIndex,
|
|
356
|
-
x0: currentBandFreq,
|
|
357
|
-
y0: currentBandIndex,
|
|
358
|
-
y1: nextBandIndex,
|
|
359
|
-
tInv: inverseBandWidth,
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return interpolationEntries
|
|
364
|
-
})()
|
|
365
|
-
|
|
366
186
|
// Quantization
|
|
367
187
|
export const MAX_WORD_LENGTH_INDEX = 15
|
|
368
188
|
export const WORD_LENGTH_BITS = new Int32Array([
|
|
@@ -396,3 +216,13 @@ export const SCALE_FACTORS = (() => {
|
|
|
396
216
|
}
|
|
397
217
|
return table
|
|
398
218
|
})()
|
|
219
|
+
|
|
220
|
+
// RDO bit allocation
|
|
221
|
+
export const INV_POWER_OF_TWO = (() => {
|
|
222
|
+
const maxBits = WORD_LENGTH_BITS[MAX_WORD_LENGTH_INDEX]
|
|
223
|
+
const table = new Float64Array(maxBits + 1)
|
|
224
|
+
for (let b = 0; b <= maxBits; b++) {
|
|
225
|
+
table[b] = Math.pow(2, -b)
|
|
226
|
+
}
|
|
227
|
+
return table
|
|
228
|
+
})()
|
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,31 +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
|
-
const { bfuCount: selectedBfuCount, allocation } = allocateBits(
|
|
382
|
-
psychoResults,
|
|
383
|
-
bfuData,
|
|
384
|
-
bfuSizes,
|
|
385
|
-
bfuCount
|
|
386
|
-
)
|
|
379
|
+
const {
|
|
380
|
+
bfuCount: selectedBfuCount,
|
|
381
|
+
allocation,
|
|
382
|
+
scaleFactorIndices,
|
|
383
|
+
} = allocateBits(bfuData, bfuSizes, bfuCount)
|
|
387
384
|
|
|
388
|
-
const scaleFactorIndices = new Int32Array(selectedBfuCount)
|
|
389
385
|
const quantizedCoefficients = []
|
|
390
386
|
|
|
391
387
|
for (let bfu = 0; bfu < selectedBfuCount; bfu++) {
|
|
392
388
|
const data = bfuData[bfu].subarray(0, bfuSizes[bfu])
|
|
393
389
|
const wordLength = allocation[bfu]
|
|
394
390
|
const bitsPerSample = WORD_LENGTH_BITS[wordLength]
|
|
395
|
-
|
|
396
|
-
scaleFactorIndices[bfu] = findScaleFactor(data)
|
|
397
391
|
const quantized = quantize(data, scaleFactorIndices[bfu], bitsPerSample)
|
|
398
392
|
quantizedCoefficients.push(quantized)
|
|
399
393
|
}
|