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.
@@ -1,9 +1,18 @@
1
1
  /**
2
- * Carta1 Audio Codec - Quantization
2
+ * Carta1 Audio Codec - Quantization Module
3
3
  *
4
- * This module implements quantization and dequantization of MDCT coefficients
5
- * for the ATRAC1 audio codec, organizing coefficients into Block Floating Units (BFUs)
6
- * and applying appropriate scale factors for optimal bit allocation.
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
- * Find the optimal scale factor index for a set of coefficients
20
- * @param {Float32Array} coefficients - MDCT coefficients to analyze
21
- * @returns {number} Scale factor index (0-63)
22
- */
23
- export function findScaleFactor(coefficients) {
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
- return new Int32Array(coefficients.length)
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 quantized = new Int32Array(coefficients.length)
47
+ const hi = quantRange
48
+ const lo = -quantRange - 1
65
49
 
66
- for (let i = 0; i < coefficients.length; i++) {
67
- const normalized = coefficients[i] * normFactor
68
- quantized[i] = Math.max(
69
- -quantRange - 1,
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 quantized
56
+ return out
75
57
  }
76
58
 
77
59
  /**
78
- * Dequantize integer coefficients back to floating-point values
79
- * @param {Int32Array} quantized - Quantized integer coefficients
80
- * @param {number} scaleFactorIndex - Index into SCALE_FACTORS table
81
- * @param {number} bitsPerSample - Number of bits per quantized sample
82
- * @returns {Float32Array} Dequantized floating-point coefficients
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 dequantized = new Float32Array(quantized.length)
94
-
75
+ const deq = new Float32Array(quantized.length)
95
76
  for (let i = 0; i < quantized.length; i++) {
96
- dequantized[i] = quantized[i] * denormFactor
77
+ deq[i] = quantized[i] * denormFactor
97
78
  }
98
-
99
- return dequantized
79
+ return deq
100
80
  }
101
81
 
102
82
  /**
103
- * Group MDCT coefficients into Block Floating Units (BFUs)
104
- * @param {Float32Array} coefficients - Input MDCT coefficients
105
- * @param {number[]} blockModes - Block mode for each band (0=long, 1=short)
106
- * @param {Array<Float32Array>} bfuBuffers - Optional pre-allocated BFU buffers
107
- * @returns {Object} Object containing bfuData, bfuSizes, and bfuCount
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, bfuBuffers = null) {
110
- const bfuData = bfuBuffers ?? []
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 = bfuBuffers ? bfuBuffers[bfuIndex] : new Float32Array(size)
128
- bfu.fill(0) // Clear the buffer
129
-
130
- if (startPos >= 0 && startPos < bandSize) {
131
- const endPos = Math.min(startPos + size, bandSize)
132
- const actualSize = endPos - startPos
133
-
134
- for (let i = 0; i < actualSize; i++) {
135
- bfu[i] = coefficients[bandStart + startPos + i]
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
- if (!bfuBuffers) {
140
- bfuData.push(bfu)
141
- }
142
+ bfuData.push(bfu)
142
143
  bfuSizes.push(size)
143
144
  bfuIndex++
144
145
  }
@@ -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
- // Tonal analysis flags
275
- export const PSYMODEL_NOT_EXAMINED = 0
276
- export const PSYMODEL_TONAL = 1
277
- export const PSYMODEL_NON_TONAL = 2
278
- export const PSYMODEL_IRRELEVANT = 3
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
- // Pre-computed conversion factors
281
- export const PSYMODEL_LOG10_FACTOR = 10 / Math.LN10
282
- export const PSYMODEL_POW10_FACTOR = Math.LN10 / 10
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
- // Critical band boundaries (0-24)
285
- export const PSYMODEL_CRITICAL_BANDS = new Uint8Array([
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
- // Threshold table: [frequency_index, bark_value, threshold_value]
291
- // prettier-ignore
292
- export const PSYMODEL_THRESHOLD_TABLE = [
293
- [1.0, 0.85, 13.87], [2.0, 1.694, 2.85], [3.0, 2.525, -1.28], [4.0, 3.337, -3.5],
294
- [5.0, 4.124, -4.9], [6.0, 4.882, -5.89], [7.0, 5.608, -6.63], [8.0, 6.301, -7.21],
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
- // Frequency to critical band mapping
326
- export const PSYMODEL_FREQ_TO_CB_MAP = new Uint8Array([
327
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
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
+ })()
@@ -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 psychoacoustic analysis and quantization
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. Performs psychoacoustic analysis to determine masking thresholds
336
- * 3. Allocates bits based on perceptual importance
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 a perceptual model to ensure quantization noise remains below
340
- * the masking threshold, maximizing quality within the bit budget.
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 or options are not provided in context
356
+ * @throws {Error} If bufferPool is not provided in context
347
357
  */
348
- export function quantizationStage(context) {
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 psychoacoustic analysis and quantization
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 psychoResults = psychoAnalysis(coefficients, options.normalizationDb)
377
- const { bfuCount: selectedBfuCount, allocation } = allocateBits(
378
- psychoResults,
379
- bfuData,
380
- bfuSizes,
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
  }