carta1 1.0.12 → 1.0.14
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/analysis/psychoacoustics.js +138 -274
- package/codec/coding/bitallocation.js +145 -179
- package/codec/core/buffers.js +0 -7
- package/codec/core/constants.js +51 -0
- package/codec/pipeline/encoder.js +1 -7
- 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
|
@@ -17,7 +17,11 @@ import {
|
|
|
17
17
|
NUM_BFUS,
|
|
18
18
|
SAMPLE_RATE,
|
|
19
19
|
BFU_FREQUENCIES,
|
|
20
|
+
BFU_AMOUNTS_COUNT,
|
|
20
21
|
PSYMODEL_MIN_POWER_DB,
|
|
22
|
+
PSYMODEL_CB_FREQ_INDICES,
|
|
23
|
+
INTERPOLATION_COMPENSATION_FACTOR,
|
|
24
|
+
PSYMODEL_FFT_SIZE,
|
|
21
25
|
} from '../core/constants.js'
|
|
22
26
|
|
|
23
27
|
/**
|
|
@@ -32,10 +36,10 @@ export function allocateBits(psychoResults, bfuData, bfuSizes, maxBfuCount) {
|
|
|
32
36
|
// Calculate signal energy for each BFU
|
|
33
37
|
const signalEnergyDb = calculateBfuEnergy(bfuData, bfuSizes, maxBfuCount)
|
|
34
38
|
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
psychoResults.
|
|
38
|
-
|
|
39
|
+
// Interpolate critical band thresholds to BFUs
|
|
40
|
+
const maskThresholdDb = interpolateCriticalBandsToBfus(
|
|
41
|
+
psychoResults.criticalBandThresholds
|
|
42
|
+
)
|
|
39
43
|
|
|
40
44
|
// Calculate Signal-to-Mask Ratios
|
|
41
45
|
const smrValues = calculateSMR(signalEnergyDb, maskThresholdDb, maxBfuCount)
|
|
@@ -90,7 +94,7 @@ function calculateBfuEnergy(bfuData, bfuSizes, bfuCount) {
|
|
|
90
94
|
energy += data[j] * data[j]
|
|
91
95
|
}
|
|
92
96
|
|
|
93
|
-
// Convert to dB
|
|
97
|
+
// Convert to dB correctly using log10
|
|
94
98
|
energyPerBfu[i] =
|
|
95
99
|
energy > 0 ? 10 * Math.log10(energy / size) : PSYMODEL_MIN_POWER_DB
|
|
96
100
|
}
|
|
@@ -99,25 +103,51 @@ function calculateBfuEnergy(bfuData, bfuSizes, bfuCount) {
|
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
/**
|
|
102
|
-
*
|
|
103
|
-
* @param {Float32Array}
|
|
106
|
+
* Interpolate critical band thresholds directly to BFU frequencies
|
|
107
|
+
* @param {Float32Array} criticalBandThresholds - Critical band thresholds
|
|
104
108
|
* @returns {Float32Array} Masking threshold per BFU
|
|
105
109
|
*/
|
|
106
|
-
function
|
|
110
|
+
function interpolateCriticalBandsToBfus(criticalBandThresholds) {
|
|
107
111
|
const maskThresholdPerBfu = new Float32Array(NUM_BFUS)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Calculate frequency resolution
|
|
111
|
-
const fftSize = (globalThreshold.length - 1) * 2
|
|
112
|
-
const freqPerBin = SAMPLE_RATE / fftSize
|
|
112
|
+
const freqPerBin = SAMPLE_RATE / PSYMODEL_FFT_SIZE
|
|
113
113
|
|
|
114
|
-
// Map each BFU's center frequency to nearest FFT bin
|
|
115
114
|
for (let i = 0; i < NUM_BFUS; i++) {
|
|
116
115
|
const bfuFreq = BFU_FREQUENCIES[i]
|
|
117
116
|
const fftBinIndex = Math.round(bfuFreq / freqPerBin)
|
|
118
117
|
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
// Find which critical bands surround this FFT bin
|
|
119
|
+
let bandIndex = 0
|
|
120
|
+
while (
|
|
121
|
+
bandIndex < PSYMODEL_CB_FREQ_INDICES.length - 1 &&
|
|
122
|
+
PSYMODEL_CB_FREQ_INDICES[bandIndex + 1] < fftBinIndex
|
|
123
|
+
) {
|
|
124
|
+
bandIndex++
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (fftBinIndex <= PSYMODEL_CB_FREQ_INDICES[0]) {
|
|
128
|
+
maskThresholdPerBfu[i] = criticalBandThresholds[0]
|
|
129
|
+
} else if (
|
|
130
|
+
fftBinIndex >=
|
|
131
|
+
PSYMODEL_CB_FREQ_INDICES[PSYMODEL_CB_FREQ_INDICES.length - 1]
|
|
132
|
+
) {
|
|
133
|
+
maskThresholdPerBfu[i] =
|
|
134
|
+
criticalBandThresholds[criticalBandThresholds.length - 1]
|
|
135
|
+
} else {
|
|
136
|
+
const x0 = PSYMODEL_CB_FREQ_INDICES[bandIndex]
|
|
137
|
+
const x1 = PSYMODEL_CB_FREQ_INDICES[bandIndex + 1]
|
|
138
|
+
const y0 = criticalBandThresholds[bandIndex]
|
|
139
|
+
const y1 = criticalBandThresholds[bandIndex + 1]
|
|
140
|
+
const bandWidth = x1 - x0
|
|
141
|
+
|
|
142
|
+
if (bandWidth > 0) {
|
|
143
|
+
const t = (fftBinIndex - x0) / bandWidth
|
|
144
|
+
const interpolated = y0 + (y1 - y0) * t
|
|
145
|
+
const compensation =
|
|
146
|
+
Math.abs(y1 - y0) * INTERPOLATION_COMPENSATION_FACTOR
|
|
147
|
+
maskThresholdPerBfu[i] = interpolated + compensation
|
|
148
|
+
} else {
|
|
149
|
+
maskThresholdPerBfu[i] = y0
|
|
150
|
+
}
|
|
121
151
|
}
|
|
122
152
|
}
|
|
123
153
|
|
|
@@ -135,7 +165,6 @@ function calculateSMR(signalEnergyDb, maskThresholdDb, maxBfuCount) {
|
|
|
135
165
|
const smrValues = new Float32Array(maxBfuCount)
|
|
136
166
|
|
|
137
167
|
for (let bfu = 0; bfu < maxBfuCount; bfu++) {
|
|
138
|
-
// BFUs with no signal have undefined SMR
|
|
139
168
|
smrValues[bfu] =
|
|
140
169
|
signalEnergyDb[bfu] <= PSYMODEL_MIN_POWER_DB
|
|
141
170
|
? -Infinity
|
|
@@ -157,111 +186,86 @@ function selectOptimalBfuCount(smrValues, signalEnergyDb, maxBfuCount) {
|
|
|
157
186
|
const hasAudibleContent = smrValues.some((smr) => smr > 0)
|
|
158
187
|
|
|
159
188
|
if (hasAudibleContent) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
smrValues
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Select BFU count when content is above masking threshold
|
|
172
|
-
* Uses diminishing returns approach - find where adding more BFUs
|
|
173
|
-
* provides minimal benefit
|
|
174
|
-
*/
|
|
175
|
-
function selectBfuCountForAudibleContent(smrValues, maxBfuCount) {
|
|
176
|
-
// Create sorted list of BFUs by their contribution (positive SMR only)
|
|
177
|
-
const contributions = []
|
|
178
|
-
for (let i = 0; i < maxBfuCount; i++) {
|
|
179
|
-
if (smrValues[i] > 0) {
|
|
180
|
-
contributions.push({ index: i, smr: smrValues[i] })
|
|
189
|
+
// Create sorted list of BFUs by their contribution (positive SMR only)
|
|
190
|
+
const contributions = []
|
|
191
|
+
for (let i = 0; i < maxBfuCount; i++) {
|
|
192
|
+
if (smrValues[i] > 0) {
|
|
193
|
+
contributions.push({ index: i, smr: smrValues[i] })
|
|
194
|
+
}
|
|
181
195
|
}
|
|
182
|
-
|
|
183
|
-
contributions.sort((a, b) => b.smr - a.smr)
|
|
184
|
-
|
|
185
|
-
if (contributions.length === 0) {
|
|
186
|
-
return BFU_AMOUNTS[BFU_AMOUNTS.length - 1]
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Find the natural cutoff using gradient of cumulative SMR
|
|
190
|
-
const cumulativeSMR = []
|
|
191
|
-
let sum = 0
|
|
192
|
-
for (let i = 0; i < contributions.length; i++) {
|
|
193
|
-
sum += contributions[i].smr
|
|
194
|
-
cumulativeSMR.push(sum)
|
|
195
|
-
}
|
|
196
|
+
contributions.sort((a, b) => b.smr - a.smr)
|
|
196
197
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// Check if all significant contributions fit within this count
|
|
202
|
-
const requiredBfus = contributions
|
|
203
|
-
.slice(0, Math.min(contributions.length, candidateCount))
|
|
204
|
-
.map((c) => c.index)
|
|
198
|
+
if (contributions.length === 0) {
|
|
199
|
+
return BFU_AMOUNTS[BFU_AMOUNTS.length - 1]
|
|
200
|
+
}
|
|
205
201
|
|
|
206
|
-
|
|
202
|
+
// Find the natural cutoff using gradient of cumulative SMR
|
|
203
|
+
const cumulativeSMR = []
|
|
204
|
+
let sum = 0
|
|
205
|
+
for (let i = 0; i < contributions.length; i++) {
|
|
206
|
+
sum += contributions[i].smr
|
|
207
|
+
cumulativeSMR.push(sum)
|
|
208
|
+
}
|
|
207
209
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
candidateCount - 1,
|
|
212
|
-
contributions.length - 1
|
|
213
|
-
)
|
|
214
|
-
if (capturedIndex >= contributions.length - 1) {
|
|
215
|
-
return candidateCount
|
|
216
|
-
}
|
|
210
|
+
// Find where the gradient becomes shallow (diminishing returns)
|
|
211
|
+
for (const candidateCount of BFU_AMOUNTS) {
|
|
212
|
+
if (candidateCount > maxBfuCount) continue
|
|
217
213
|
|
|
218
|
-
//
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
214
|
+
// Check if all significant contributions fit within this count
|
|
215
|
+
const requiredBfus = contributions
|
|
216
|
+
.slice(0, Math.min(contributions.length, candidateCount))
|
|
217
|
+
.map((c) => c.index)
|
|
222
218
|
|
|
223
|
-
|
|
224
|
-
const averageRemaining = (totalValue - currentValue) / remainingIndices
|
|
225
|
-
const averageCaptured = currentValue / (capturedIndex + 1)
|
|
219
|
+
const maxRequiredIndex = Math.max(...requiredBfus, 0)
|
|
226
220
|
|
|
227
|
-
|
|
228
|
-
if
|
|
221
|
+
if (maxRequiredIndex < candidateCount) {
|
|
222
|
+
// Check if we've captured the bulk of the contribution
|
|
223
|
+
const capturedIndex = Math.min(
|
|
224
|
+
candidateCount - 1,
|
|
225
|
+
contributions.length - 1
|
|
226
|
+
)
|
|
227
|
+
if (capturedIndex >= contributions.length - 1) {
|
|
229
228
|
return candidateCount
|
|
230
229
|
}
|
|
230
|
+
|
|
231
|
+
// Use gradient to detect diminishing returns
|
|
232
|
+
const remainingIndices = contributions.length - capturedIndex - 1
|
|
233
|
+
if (remainingIndices > 0) {
|
|
234
|
+
const avgRemaining =
|
|
235
|
+
(cumulativeSMR[cumulativeSMR.length - 1] -
|
|
236
|
+
cumulativeSMR[capturedIndex]) /
|
|
237
|
+
remainingIndices
|
|
238
|
+
const avgCaptured = cumulativeSMR[capturedIndex] / (capturedIndex + 1)
|
|
239
|
+
|
|
240
|
+
// If remaining BFUs contribute much less on average, we've found our cutoff
|
|
241
|
+
if (avgRemaining < avgCaptured * 0.1) {
|
|
242
|
+
return candidateCount
|
|
243
|
+
}
|
|
244
|
+
}
|
|
231
245
|
}
|
|
232
246
|
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
signalEnergyDb,
|
|
245
|
-
maxBfuCount
|
|
246
|
-
) {
|
|
247
|
-
// Find the highest BFU index that contains actual signal
|
|
248
|
-
let highestSignalBfu = -1
|
|
249
|
-
|
|
250
|
-
for (let i = maxBfuCount - 1; i >= 0; i--) {
|
|
251
|
-
if (signalEnergyDb[i] > PSYMODEL_MIN_POWER_DB && smrValues[i] > -Infinity) {
|
|
252
|
-
highestSignalBfu = i
|
|
253
|
-
break
|
|
247
|
+
} else {
|
|
248
|
+
// Find the highest BFU index that contains actual signal
|
|
249
|
+
let highestSignalBfu = -1
|
|
250
|
+
for (let i = maxBfuCount - 1; i >= 0; i--) {
|
|
251
|
+
if (
|
|
252
|
+
signalEnergyDb[i] > PSYMODEL_MIN_POWER_DB &&
|
|
253
|
+
smrValues[i] > -Infinity
|
|
254
|
+
) {
|
|
255
|
+
highestSignalBfu = i
|
|
256
|
+
break
|
|
257
|
+
}
|
|
254
258
|
}
|
|
255
|
-
}
|
|
256
259
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
// Find smallest BFU count that includes all signal
|
|
261
|
+
for (const candidateCount of BFU_AMOUNTS) {
|
|
262
|
+
if (candidateCount > highestSignalBfu) {
|
|
263
|
+
return candidateCount
|
|
264
|
+
}
|
|
261
265
|
}
|
|
262
266
|
}
|
|
263
267
|
|
|
264
|
-
return BFU_AMOUNTS[
|
|
268
|
+
return BFU_AMOUNTS[BFU_AMOUNTS_COUNT - 1]
|
|
265
269
|
}
|
|
266
270
|
|
|
267
271
|
/**
|
|
@@ -278,36 +282,44 @@ function performBitAllocation(
|
|
|
278
282
|
bfuSizes,
|
|
279
283
|
availableBits
|
|
280
284
|
) {
|
|
281
|
-
const allocation = new Int32Array(actualBfuCount)
|
|
285
|
+
const allocation = new Int32Array(actualBfuCount)
|
|
286
|
+
const heap = []
|
|
287
|
+
|
|
288
|
+
// Initialize heap with BFUs that have signal
|
|
289
|
+
for (let i = 0; i < actualBfuCount; i++) {
|
|
290
|
+
if (smrValues[i] > -Infinity) {
|
|
291
|
+
heap.push({ bfu: i, priority: smrValues[i] })
|
|
292
|
+
}
|
|
293
|
+
}
|
|
282
294
|
|
|
283
|
-
//
|
|
284
|
-
|
|
295
|
+
// Build max heap
|
|
296
|
+
for (let i = Math.floor(heap.length / 2) - 1; i >= 0; i--) {
|
|
297
|
+
heapifyDown(heap, i)
|
|
298
|
+
}
|
|
285
299
|
|
|
286
|
-
// Greedy allocation
|
|
287
|
-
while (
|
|
288
|
-
const bestBfu =
|
|
300
|
+
// Greedy allocation
|
|
301
|
+
while (heap.length > 0 && availableBits > 0) {
|
|
302
|
+
const bestBfu = heap[0]
|
|
289
303
|
const currentWl = allocation[bestBfu.bfu]
|
|
290
304
|
|
|
291
|
-
// Check if BFU is already at maximum word length
|
|
292
305
|
if (currentWl >= MAX_WORD_LENGTH_INDEX) {
|
|
293
|
-
|
|
306
|
+
heap[0] = heap[heap.length - 1]
|
|
307
|
+
heap.pop()
|
|
308
|
+
if (heap.length > 0) heapifyDown(heap, 0)
|
|
294
309
|
continue
|
|
295
310
|
}
|
|
296
311
|
|
|
297
|
-
// Calculate cost to upgrade this BFU
|
|
298
312
|
const upgradeCost = calculateUpgradeCost(bestBfu.bfu, currentWl, bfuSizes)
|
|
299
313
|
|
|
300
314
|
if (upgradeCost <= availableBits) {
|
|
301
|
-
// Perform upgrade
|
|
302
315
|
availableBits -= upgradeCost
|
|
303
316
|
allocation[bestBfu.bfu]++
|
|
304
|
-
|
|
305
|
-
// Update priority based on distortion reduction
|
|
306
317
|
bestBfu.priority += DISTORTION_DELTA_DB[currentWl]
|
|
307
|
-
|
|
318
|
+
heapifyDown(heap, 0)
|
|
308
319
|
} else {
|
|
309
|
-
|
|
310
|
-
|
|
320
|
+
heap[0] = heap[heap.length - 1]
|
|
321
|
+
heap.pop()
|
|
322
|
+
if (heap.length > 0) heapifyDown(heap, 0)
|
|
311
323
|
}
|
|
312
324
|
}
|
|
313
325
|
|
|
@@ -316,6 +328,10 @@ function performBitAllocation(
|
|
|
316
328
|
|
|
317
329
|
/**
|
|
318
330
|
* Calculate bit cost to upgrade a BFU's word length
|
|
331
|
+
* @param {number} bfuIndex - Index of the BFU to upgrade
|
|
332
|
+
* @param {number} currentWl - Current word length of the BFU
|
|
333
|
+
* @param {number[]} bfuSizes - Array of BFU sizes (number of coefficients per BFU)
|
|
334
|
+
* @returns {number} Additional bits required to upgrade word length by 1
|
|
319
335
|
*/
|
|
320
336
|
function calculateUpgradeCost(bfuIndex, currentWl, bfuSizes) {
|
|
321
337
|
const currentBits = WORD_LENGTH_BITS[currentWl] * bfuSizes[bfuIndex]
|
|
@@ -323,52 +339,21 @@ function calculateUpgradeCost(bfuIndex, currentWl, bfuSizes) {
|
|
|
323
339
|
return nextBits - currentBits
|
|
324
340
|
}
|
|
325
341
|
|
|
326
|
-
/**
|
|
327
|
-
* Create a priority queue for BFU allocation
|
|
328
|
-
* @param {Float32Array} smrValues - Initial priorities (SMR values)
|
|
329
|
-
* @param {number} bfuCount - Number of BFUs
|
|
330
|
-
* @returns {Object} Priority queue interface
|
|
331
|
-
*/
|
|
332
|
-
function createPriorityQueue(smrValues, bfuCount) {
|
|
333
|
-
const heap = []
|
|
334
|
-
|
|
335
|
-
// Initialize with BFUs that have signal
|
|
336
|
-
for (let i = 0; i < bfuCount; i++) {
|
|
337
|
-
if (smrValues[i] > -Infinity) {
|
|
338
|
-
heap.push({ bfu: i, priority: smrValues[i] })
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Build max heap
|
|
343
|
-
for (let i = Math.floor(heap.length / 2) - 1; i >= 0; i--) {
|
|
344
|
-
heapifyDown(heap, i)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return {
|
|
348
|
-
isEmpty: () => heap.length === 0,
|
|
349
|
-
peek: () => heap[0],
|
|
350
|
-
pop: () => popMax(heap),
|
|
351
|
-
updateTop: () => heapifyDown(heap, 0),
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
342
|
/**
|
|
356
343
|
* Maintain max heap property by moving element down
|
|
357
344
|
* @param {Array} heap - Heap array
|
|
358
345
|
* @param {number} startIdx - Starting index
|
|
359
346
|
*/
|
|
360
347
|
function heapifyDown(heap, startIdx) {
|
|
361
|
-
let idx = startIdx
|
|
362
|
-
const element = heap[idx]
|
|
363
348
|
const length = heap.length
|
|
349
|
+
const element = heap[startIdx]
|
|
364
350
|
|
|
365
351
|
while (true) {
|
|
366
|
-
const leftChild = 2 *
|
|
367
|
-
const rightChild = 2 *
|
|
368
|
-
let largest =
|
|
352
|
+
const leftChild = 2 * startIdx + 1
|
|
353
|
+
const rightChild = 2 * startIdx + 2
|
|
354
|
+
let largest = startIdx
|
|
369
355
|
let largestPriority = element.priority
|
|
370
356
|
|
|
371
|
-
// Find largest among parent and children
|
|
372
357
|
if (leftChild < length && heap[leftChild].priority > largestPriority) {
|
|
373
358
|
largest = leftChild
|
|
374
359
|
largestPriority = heap[leftChild].priority
|
|
@@ -378,30 +363,11 @@ function heapifyDown(heap, startIdx) {
|
|
|
378
363
|
largest = rightChild
|
|
379
364
|
}
|
|
380
365
|
|
|
381
|
-
|
|
382
|
-
if (largest === idx) break
|
|
383
|
-
|
|
384
|
-
// Swap and continue
|
|
385
|
-
heap[idx] = heap[largest]
|
|
386
|
-
idx = largest
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
heap[idx] = element
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* Remove and return the maximum element from heap
|
|
394
|
-
* @param {Array} heap - Heap array
|
|
395
|
-
* @returns {Object} Maximum element
|
|
396
|
-
*/
|
|
397
|
-
function popMax(heap) {
|
|
398
|
-
const max = heap[0]
|
|
399
|
-
const last = heap.pop()
|
|
366
|
+
if (largest === startIdx) break
|
|
400
367
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
heapifyDown(heap, 0)
|
|
368
|
+
heap[startIdx] = heap[largest]
|
|
369
|
+
startIdx = largest
|
|
404
370
|
}
|
|
405
371
|
|
|
406
|
-
|
|
372
|
+
heap[startIdx] = element
|
|
407
373
|
}
|
package/codec/core/buffers.js
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
MAX_BFU_SIZE,
|
|
8
8
|
QMF_DELAY,
|
|
9
9
|
QMF_HIGH_BAND_DELAY,
|
|
10
|
-
PSYMODEL_FFT_SIZE,
|
|
11
10
|
} from './constants.js'
|
|
12
11
|
|
|
13
12
|
export class BufferPool {
|
|
@@ -84,12 +83,6 @@ export class BufferPool {
|
|
|
84
83
|
256: new Float32Array(256),
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
// Psychoacoustic model buffers
|
|
88
|
-
this.psychoAnalysis = {
|
|
89
|
-
flags: new Uint8Array(PSYMODEL_FFT_SIZE / 2),
|
|
90
|
-
threshold: new Float32Array(PSYMODEL_FFT_SIZE / 2 + 1),
|
|
91
|
-
}
|
|
92
|
-
|
|
93
86
|
// Bit allocation buffers
|
|
94
87
|
this.bfuData = new Array(NUM_BFUS)
|
|
95
88
|
for (let i = 0; i < NUM_BFUS; i++) {
|
package/codec/core/constants.js
CHANGED
|
@@ -33,6 +33,7 @@ export const SPECS_PER_BFU = new Int32Array([
|
|
|
33
33
|
20, 20, 20, 20, 20,
|
|
34
34
|
])
|
|
35
35
|
|
|
36
|
+
export const BFU_AMOUNTS_COUNT = 8
|
|
36
37
|
export const BFU_AMOUNTS = new Int32Array([20, 28, 32, 36, 40, 44, 48, 52])
|
|
37
38
|
export const BFU_BAND_BOUNDARIES = new Int32Array([20, 36, 52])
|
|
38
39
|
|
|
@@ -116,6 +117,9 @@ export const QMF_ODD = (() => {
|
|
|
116
117
|
return odd
|
|
117
118
|
})()
|
|
118
119
|
|
|
120
|
+
// Bit allocation
|
|
121
|
+
export const INTERPOLATION_COMPENSATION_FACTOR = 0.25
|
|
122
|
+
|
|
119
123
|
// Quantization
|
|
120
124
|
export const MAX_WORD_LENGTH_INDEX = 15
|
|
121
125
|
export const WORD_LENGTH_BITS = new Int32Array([
|
|
@@ -188,6 +192,53 @@ export const SPREADING_MATRIX = (() => {
|
|
|
188
192
|
export const PSYMODEL_MIN_POWER_DB = -200
|
|
189
193
|
export const PSYMODEL_FFT_SIZE = 2048
|
|
190
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
|
+
|
|
191
242
|
// FFT sizes for transient detection
|
|
192
243
|
export const FFT_SIZE_LOW = 256
|
|
193
244
|
export const FFT_SIZE_MID = 256
|
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
FFT_SIZE_LOW,
|
|
32
32
|
FFT_SIZE_MID,
|
|
33
33
|
FFT_SIZE_HIGH,
|
|
34
|
-
PSYMODEL_FFT_SIZE,
|
|
35
34
|
MDCT_SIZE_SHORT,
|
|
36
35
|
MDCT_SIZE_MID,
|
|
37
36
|
MDCT_SIZE_LONG,
|
|
@@ -300,12 +299,7 @@ export function quantizationStage(context) {
|
|
|
300
299
|
bufferPool.bfuData
|
|
301
300
|
)
|
|
302
301
|
|
|
303
|
-
const psychoResults = psychoAnalysis(
|
|
304
|
-
coefficients,
|
|
305
|
-
PSYMODEL_FFT_SIZE,
|
|
306
|
-
bufferPool.psychoAnalysis,
|
|
307
|
-
options.normalizationDb
|
|
308
|
-
)
|
|
302
|
+
const psychoResults = psychoAnalysis(coefficients, options.normalizationDb)
|
|
309
303
|
const { bfuCount: selectedBfuCount, allocation } = allocateBits(
|
|
310
304
|
psychoResults,
|
|
311
305
|
bfuData,
|