carta1 1.0.21 → 1.0.22
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 +485 -307
- package/codec/coding/bitallocation.js +210 -266
- package/codec/core/buffers.js +18 -0
- package/codec/core/constants.js +93 -37
- package/codec/pipeline/encoder.js +5 -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
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* distributing available bits among Block Floating Units (BFUs) based on
|
|
6
6
|
* psychoacoustic masking and signal-to-mask ratios.
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
8
|
import {
|
|
10
9
|
FRAME_BITS,
|
|
11
10
|
FRAME_OVERHEAD_BITS,
|
|
@@ -14,13 +13,12 @@ import {
|
|
|
14
13
|
MAX_WORD_LENGTH_INDEX,
|
|
15
14
|
WORD_LENGTH_BITS,
|
|
16
15
|
DISTORTION_DELTA_DB,
|
|
17
|
-
NUM_BFUS,
|
|
18
|
-
SAMPLE_RATE,
|
|
19
|
-
BFU_FREQUENCIES,
|
|
20
|
-
BFU_AMOUNTS_COUNT,
|
|
21
16
|
PSYMODEL_MIN_POWER_DB,
|
|
22
|
-
PSYMODEL_CB_FREQ_INDICES,
|
|
23
17
|
INTERPOLATION_COMPENSATION_FACTOR,
|
|
18
|
+
BFU_MASK_INTERPOLATION_TABLE,
|
|
19
|
+
BFU_FREQUENCIES,
|
|
20
|
+
NUM_BFUS,
|
|
21
|
+
SAMPLE_RATE,
|
|
24
22
|
PSYMODEL_FFT_SIZE,
|
|
25
23
|
} from '../core/constants.js'
|
|
26
24
|
|
|
@@ -33,341 +31,287 @@ import {
|
|
|
33
31
|
* @returns {Object} Allocation results with BFU count and bit distribution
|
|
34
32
|
*/
|
|
35
33
|
export function allocateBits(psychoResults, bfuData, bfuSizes, maxBfuCount) {
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
// Calculate Signal-to-Mask Ratios
|
|
45
|
-
const smrValues = calculateSMR(signalEnergyDb, maskThresholdDb, maxBfuCount)
|
|
46
|
-
|
|
47
|
-
// Determine optimal number of BFUs to use
|
|
48
|
-
const actualBfuCount = selectOptimalBfuCount(
|
|
49
|
-
smrValues,
|
|
50
|
-
signalEnergyDb,
|
|
51
|
-
maxBfuCount
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
// Calculate available bits for allocation
|
|
55
|
-
const availableBits =
|
|
56
|
-
FRAME_BITS - FRAME_OVERHEAD_BITS - actualBfuCount * BITS_PER_BFU_METADATA
|
|
57
|
-
|
|
58
|
-
// Perform actual bit allocation
|
|
59
|
-
const allocation = performBitAllocation(
|
|
60
|
-
actualBfuCount,
|
|
61
|
-
smrValues,
|
|
62
|
-
bfuSizes,
|
|
63
|
-
availableBits
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
bfuCount: actualBfuCount,
|
|
68
|
-
allocation,
|
|
69
|
-
}
|
|
34
|
+
const energyDb = calculateBfuEnergy(bfuData, bfuSizes, maxBfuCount)
|
|
35
|
+
const maskDb = interpolateMask(psychoResults.criticalBandThresholds)
|
|
36
|
+
const smr = calculateSMR(energyDb, maskDb, maxBfuCount)
|
|
37
|
+
const usedBFU = selectOptimalBfuCount(smr, energyDb, maxBfuCount)
|
|
38
|
+
const avail =
|
|
39
|
+
FRAME_BITS - FRAME_OVERHEAD_BITS - usedBFU * BITS_PER_BFU_METADATA
|
|
40
|
+
const allocation = distributeBitsGreedy(usedBFU, smr, bfuSizes, avail)
|
|
41
|
+
return { bfuCount: usedBFU, allocation }
|
|
70
42
|
}
|
|
71
43
|
|
|
72
44
|
/**
|
|
73
45
|
* Calculate energy in dB for each BFU
|
|
74
|
-
* @param {Array} bfuData - BFU coefficient
|
|
46
|
+
* @param {Array} bfuData - BFU coefficient bfuData
|
|
75
47
|
* @param {Array} bfuSizes - Size of each BFU
|
|
76
48
|
* @param {number} bfuCount - Number of BFUs to process
|
|
77
49
|
* @returns {Float32Array} Energy values in dB
|
|
78
50
|
*/
|
|
79
51
|
function calculateBfuEnergy(bfuData, bfuSizes, bfuCount) {
|
|
80
|
-
const
|
|
81
|
-
|
|
52
|
+
const out = new Float32Array(bfuCount)
|
|
53
|
+
const minDb = PSYMODEL_MIN_POWER_DB
|
|
82
54
|
for (let i = 0; i < bfuCount; i++) {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (size === 0) {
|
|
87
|
-
energyPerBfu[i] = PSYMODEL_MIN_POWER_DB
|
|
55
|
+
const sz = bfuSizes[i]
|
|
56
|
+
if (!sz) {
|
|
57
|
+
out[i] = minDb
|
|
88
58
|
continue
|
|
89
59
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
let
|
|
93
|
-
|
|
94
|
-
energy += data[j] * data[j]
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Convert to dB correctly using log10
|
|
98
|
-
energyPerBfu[i] =
|
|
99
|
-
energy > 0 ? 10 * Math.log10(energy / size) : PSYMODEL_MIN_POWER_DB
|
|
60
|
+
let sum = 0,
|
|
61
|
+
buf = bfuData[i]
|
|
62
|
+
for (let j = 0; j < sz; j++) sum += buf[j] * buf[j]
|
|
63
|
+
out[i] = sum > 0 ? 10 * Math.log10(sum / sz) : minDb
|
|
100
64
|
}
|
|
101
|
-
|
|
102
|
-
return energyPerBfu
|
|
65
|
+
return out
|
|
103
66
|
}
|
|
104
67
|
|
|
105
68
|
/**
|
|
106
|
-
* Interpolate
|
|
107
|
-
* @param {Float32Array} criticalBandThresholds -
|
|
108
|
-
* @returns {Float32Array}
|
|
69
|
+
* Interpolate psychoacoustic masking thresholds from critical bands to BFU frequencies
|
|
70
|
+
* @param {Float32Array} criticalBandThresholds - Masking thresholds for each critical band
|
|
71
|
+
* @returns {Float32Array} Interpolated masking thresholds for each BFU
|
|
109
72
|
*/
|
|
110
|
-
function
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
}
|
|
151
|
-
}
|
|
73
|
+
function interpolateMask(criticalBandThresholds) {
|
|
74
|
+
const maskValues = new Float32Array(NUM_BFUS)
|
|
75
|
+
|
|
76
|
+
for (let bfuIndex = 0; bfuIndex < NUM_BFUS; bfuIndex++) {
|
|
77
|
+
const interpolationEntry = BFU_MASK_INTERPOLATION_TABLE[bfuIndex]
|
|
78
|
+
const currentBandThreshold = criticalBandThresholds[interpolationEntry.band]
|
|
79
|
+
const nextBandThreshold = criticalBandThresholds[interpolationEntry.next]
|
|
80
|
+
|
|
81
|
+
// Calculate interpolation parameter t based on BFU frequency position
|
|
82
|
+
const normalizedFreqPosition =
|
|
83
|
+
(Math.round(
|
|
84
|
+
BFU_FREQUENCIES[bfuIndex] / (SAMPLE_RATE / PSYMODEL_FFT_SIZE)
|
|
85
|
+
) -
|
|
86
|
+
interpolationEntry.x0) *
|
|
87
|
+
interpolationEntry.tInv
|
|
88
|
+
|
|
89
|
+
const interpolatedValue =
|
|
90
|
+
currentBandThreshold +
|
|
91
|
+
(nextBandThreshold - currentBandThreshold) * normalizedFreqPosition
|
|
92
|
+
|
|
93
|
+
// Add compensation factor based on threshold difference
|
|
94
|
+
maskValues[bfuIndex] =
|
|
95
|
+
interpolatedValue +
|
|
96
|
+
Math.abs(nextBandThreshold - currentBandThreshold) *
|
|
97
|
+
INTERPOLATION_COMPENSATION_FACTOR
|
|
152
98
|
}
|
|
153
99
|
|
|
154
|
-
return
|
|
100
|
+
return maskValues
|
|
155
101
|
}
|
|
156
102
|
|
|
157
103
|
/**
|
|
158
|
-
* Calculate Signal-to-Mask
|
|
159
|
-
*
|
|
160
|
-
* @param {Float32Array}
|
|
161
|
-
* @param {
|
|
162
|
-
* @
|
|
104
|
+
* Calculate Signal-to-Mask Ratios (SMR) for each BFU
|
|
105
|
+
* SMR determines how much energy exceeds the masking threshold
|
|
106
|
+
* @param {Float32Array} energyDb - Energy values in dB for each BFU
|
|
107
|
+
* @param {Float32Array} maskDb - Masking threshold values in dB for each BFU
|
|
108
|
+
* @param {number} bfuCount - Number of BFUs to process
|
|
109
|
+
* @returns {Float32Array} SMR values for each BFU (-Infinity for inaudible BFUs)
|
|
163
110
|
*/
|
|
164
|
-
function calculateSMR(
|
|
165
|
-
const smrValues = new Float32Array(
|
|
111
|
+
function calculateSMR(energyDb, maskDb, bfuCount) {
|
|
112
|
+
const smrValues = new Float32Array(bfuCount)
|
|
166
113
|
|
|
167
|
-
for (let
|
|
168
|
-
|
|
169
|
-
|
|
114
|
+
for (let bfuIndex = 0; bfuIndex < bfuCount; bfuIndex++) {
|
|
115
|
+
const energyValue = energyDb[bfuIndex]
|
|
116
|
+
smrValues[bfuIndex] =
|
|
117
|
+
energyValue <= PSYMODEL_MIN_POWER_DB
|
|
170
118
|
? -Infinity
|
|
171
|
-
:
|
|
119
|
+
: energyValue - maskDb[bfuIndex]
|
|
172
120
|
}
|
|
173
121
|
|
|
174
122
|
return smrValues
|
|
175
123
|
}
|
|
176
124
|
|
|
177
125
|
/**
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* @param {Float32Array}
|
|
181
|
-
* @param {
|
|
182
|
-
* @
|
|
126
|
+
* Determine optimal number of BFUs to use based on SMR analysis
|
|
127
|
+
* Uses cumulative SMR analysis to find the point of diminishing returns
|
|
128
|
+
* @param {Float32Array} smrValues - Signal-to-Mask ratios for each BFU
|
|
129
|
+
* @param {Float32Array} energyValues - Energy values for each BFU
|
|
130
|
+
* @param {number} maxBfuCount - Maximum BFUs available
|
|
131
|
+
* @returns {number} Optimal number of BFUs to use for encoding
|
|
183
132
|
*/
|
|
184
|
-
function selectOptimalBfuCount(smrValues,
|
|
185
|
-
//
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
contributions.push({ index: i, smr: smrValues[i] })
|
|
194
|
-
}
|
|
133
|
+
function selectOptimalBfuCount(smrValues, energyValues, maxBfuCount) {
|
|
134
|
+
// Collect BFUs with positive SMR (above masking threshold)
|
|
135
|
+
const positiveSMRBfus = []
|
|
136
|
+
for (let bfuIndex = 0; bfuIndex < maxBfuCount; bfuIndex++) {
|
|
137
|
+
if (smrValues[bfuIndex] > 0) {
|
|
138
|
+
positiveSMRBfus.push({
|
|
139
|
+
index: bfuIndex,
|
|
140
|
+
smrValue: smrValues[bfuIndex],
|
|
141
|
+
})
|
|
195
142
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (contributions.length === 0) {
|
|
199
|
-
return BFU_AMOUNTS[BFU_AMOUNTS.length - 1]
|
|
200
|
-
}
|
|
201
|
-
|
|
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
|
-
}
|
|
209
|
-
|
|
210
|
-
// Find where the gradient becomes shallow (diminishing returns)
|
|
211
|
-
for (const candidateCount of BFU_AMOUNTS) {
|
|
212
|
-
if (candidateCount > maxBfuCount) continue
|
|
213
|
-
|
|
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)
|
|
218
|
-
|
|
219
|
-
const maxRequiredIndex = Math.max(...requiredBfus, 0)
|
|
143
|
+
}
|
|
220
144
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
145
|
+
// If no BFUs have positive SMR, fall back to energy-based selection
|
|
146
|
+
if (!positiveSMRBfus.length) {
|
|
147
|
+
for (let bfuIndex = maxBfuCount - 1; bfuIndex >= 0; bfuIndex--) {
|
|
148
|
+
if (energyValues[bfuIndex] > PSYMODEL_MIN_POWER_DB) {
|
|
149
|
+
return (
|
|
150
|
+
BFU_AMOUNTS.find((count) => count > bfuIndex) ||
|
|
151
|
+
BFU_AMOUNTS[BFU_AMOUNTS.length - 1]
|
|
226
152
|
)
|
|
227
|
-
if (capturedIndex >= contributions.length - 1) {
|
|
228
|
-
return candidateCount
|
|
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
|
-
}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
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
153
|
}
|
|
258
154
|
}
|
|
155
|
+
return BFU_AMOUNTS[0]
|
|
156
|
+
}
|
|
259
157
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
158
|
+
// Sort BFUs by SMR value (highest first)
|
|
159
|
+
positiveSMRBfus.sort((a, b) => b.smrValue - a.smrValue)
|
|
160
|
+
|
|
161
|
+
// Calculate cumulative SMR values
|
|
162
|
+
const cumulativeSmr = new Float32Array(positiveSMRBfus.length)
|
|
163
|
+
cumulativeSmr[0] = positiveSMRBfus[0].smrValue
|
|
164
|
+
for (let k = 1; k < positiveSMRBfus.length; k++) {
|
|
165
|
+
cumulativeSmr[k] = cumulativeSmr[k - 1] + positiveSMRBfus[k].smrValue
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Find optimal cutoff point using diminishing returns analysis
|
|
169
|
+
for (const candidateCount of BFU_AMOUNTS) {
|
|
170
|
+
if (candidateCount > maxBfuCount) continue
|
|
171
|
+
if (positiveSMRBfus.length <= candidateCount) return candidateCount
|
|
172
|
+
|
|
173
|
+
const lastIncludedIndex = candidateCount - 1
|
|
174
|
+
const averageIncludedSmr =
|
|
175
|
+
cumulativeSmr[lastIncludedIndex] / (lastIncludedIndex + 1)
|
|
176
|
+
const averageExcludedSmr =
|
|
177
|
+
(cumulativeSmr[positiveSMRBfus.length - 1] -
|
|
178
|
+
cumulativeSmr[lastIncludedIndex]) /
|
|
179
|
+
(positiveSMRBfus.length - lastIncludedIndex - 1)
|
|
180
|
+
|
|
181
|
+
// If excluded BFUs contribute less than 10% of included average, use this count
|
|
182
|
+
if (averageExcludedSmr < averageIncludedSmr * 0.1) return candidateCount
|
|
266
183
|
}
|
|
267
184
|
|
|
268
|
-
return BFU_AMOUNTS[
|
|
185
|
+
return BFU_AMOUNTS[BFU_AMOUNTS.length - 1]
|
|
269
186
|
}
|
|
270
187
|
|
|
271
188
|
/**
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
* @param {
|
|
275
|
-
* @param {
|
|
276
|
-
* @param {
|
|
189
|
+
* Distribute available bits among BFUs using a greedy heap-based algorithm
|
|
190
|
+
* Iteratively upgrades quantization precision for the BFU with highest SMR
|
|
191
|
+
* @param {number} activeBfuCount - Number of BFUs to distribute bits to
|
|
192
|
+
* @param {Float32Array} smrValues - Signal-to-Mask ratios for prioritization
|
|
193
|
+
* @param {Array} bfuSizes - Size (coefficient count) of each BFU
|
|
194
|
+
* @param {number} remainingBits - Total bits available for distribution
|
|
277
195
|
* @returns {Int32Array} Word length index allocation per BFU
|
|
278
196
|
*/
|
|
279
|
-
function
|
|
280
|
-
|
|
197
|
+
function distributeBitsGreedy(
|
|
198
|
+
activeBfuCount,
|
|
281
199
|
smrValues,
|
|
282
200
|
bfuSizes,
|
|
283
|
-
|
|
201
|
+
remainingBits
|
|
284
202
|
) {
|
|
285
|
-
const
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
203
|
+
const wordLengthAllocation = new Int32Array(activeBfuCount)
|
|
204
|
+
const heapBfuIndices = []
|
|
205
|
+
const heapPriorities = []
|
|
206
|
+
|
|
207
|
+
// Initialize heap with BFUs that have finite SMR values
|
|
208
|
+
for (let bfuIndex = 0; bfuIndex < activeBfuCount; bfuIndex++) {
|
|
209
|
+
if (smrValues[bfuIndex] > -Infinity) {
|
|
210
|
+
heapBfuIndices.push(bfuIndex)
|
|
211
|
+
heapPriorities.push(smrValues[bfuIndex])
|
|
292
212
|
}
|
|
293
213
|
}
|
|
294
214
|
|
|
295
|
-
// Build max
|
|
296
|
-
for (
|
|
297
|
-
|
|
215
|
+
// Build max-heap structure
|
|
216
|
+
for (
|
|
217
|
+
let nodeIndex = (heapBfuIndices.length >> 1) - 1;
|
|
218
|
+
nodeIndex >= 0;
|
|
219
|
+
nodeIndex--
|
|
220
|
+
) {
|
|
221
|
+
siftDown(heapBfuIndices, heapPriorities, nodeIndex)
|
|
298
222
|
}
|
|
299
223
|
|
|
300
|
-
//
|
|
301
|
-
while (
|
|
302
|
-
const
|
|
303
|
-
const
|
|
224
|
+
// Greedily allocate bits to highest priority BFUs
|
|
225
|
+
while (heapBfuIndices.length && remainingBits > 0) {
|
|
226
|
+
const currentBfu = heapBfuIndices[0]
|
|
227
|
+
const currentWordLength = wordLengthAllocation[currentBfu]
|
|
304
228
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (heap.length > 0) heapifyDown(heap, 0)
|
|
229
|
+
// Skip BFUs that have reached maximum quantization precision
|
|
230
|
+
if (currentWordLength >= MAX_WORD_LENGTH_INDEX) {
|
|
231
|
+
popRoot(heapBfuIndices, heapPriorities)
|
|
309
232
|
continue
|
|
310
233
|
}
|
|
311
234
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
heap.pop()
|
|
322
|
-
if (heap.length > 0) heapifyDown(heap, 0)
|
|
235
|
+
// Calculate bit cost for upgrading this BFU's quantization
|
|
236
|
+
const bfuSize = bfuSizes[currentBfu]
|
|
237
|
+
const upgradeCost =
|
|
238
|
+
WORD_LENGTH_BITS[currentWordLength + 1] * bfuSize -
|
|
239
|
+
WORD_LENGTH_BITS[currentWordLength] * bfuSize
|
|
240
|
+
|
|
241
|
+
if (upgradeCost > remainingBits) {
|
|
242
|
+
popRoot(heapBfuIndices, heapPriorities)
|
|
243
|
+
continue
|
|
323
244
|
}
|
|
245
|
+
|
|
246
|
+
// Perform the upgrade
|
|
247
|
+
remainingBits -= upgradeCost
|
|
248
|
+
wordLengthAllocation[currentBfu] = currentWordLength + 1
|
|
249
|
+
heapPriorities[0] += DISTORTION_DELTA_DB[currentWordLength]
|
|
250
|
+
siftDown(heapBfuIndices, heapPriorities, 0)
|
|
324
251
|
}
|
|
325
252
|
|
|
326
|
-
return
|
|
253
|
+
return wordLengthAllocation
|
|
327
254
|
}
|
|
328
255
|
|
|
329
256
|
/**
|
|
330
|
-
*
|
|
331
|
-
* @param {
|
|
332
|
-
* @param {
|
|
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
|
|
257
|
+
* Remove the root element from a max-heap and restore heap property
|
|
258
|
+
* @param {Array} heapIndices - Array of BFU indices forming the heap
|
|
259
|
+
* @param {Array} heapPriorities - Array of priority values corresponding to indices
|
|
335
260
|
*/
|
|
336
|
-
function
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
261
|
+
function popRoot(heapIndices, heapPriorities) {
|
|
262
|
+
const lastIndex = heapIndices.length - 1
|
|
263
|
+
heapIndices[0] = heapIndices[lastIndex]
|
|
264
|
+
heapPriorities[0] = heapPriorities[lastIndex]
|
|
265
|
+
heapIndices.pop()
|
|
266
|
+
heapPriorities.pop()
|
|
267
|
+
if (heapIndices.length) siftDown(heapIndices, heapPriorities, 0)
|
|
340
268
|
}
|
|
341
269
|
|
|
342
270
|
/**
|
|
343
|
-
*
|
|
344
|
-
* @param {Array}
|
|
345
|
-
* @param {
|
|
271
|
+
* Restore max-heap property by sifting an element down to its correct position
|
|
272
|
+
* @param {Array} heapIndices - Array of BFU indices forming the heap
|
|
273
|
+
* @param {Array} heapPriorities - Array of priority values corresponding to indices
|
|
274
|
+
* @param {number} startIndex - Index of element to sift down
|
|
346
275
|
*/
|
|
347
|
-
function
|
|
348
|
-
const
|
|
349
|
-
|
|
276
|
+
function siftDown(heapIndices, heapPriorities, startIndex) {
|
|
277
|
+
const heapSize = heapIndices.length
|
|
278
|
+
let currentIndex = startIndex
|
|
279
|
+
const originalIndexValue = heapIndices[currentIndex]
|
|
280
|
+
const originalPriorityValue = heapPriorities[currentIndex]
|
|
350
281
|
|
|
351
282
|
while (true) {
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
let
|
|
355
|
-
let
|
|
356
|
-
|
|
357
|
-
if
|
|
358
|
-
|
|
359
|
-
|
|
283
|
+
const leftChildIndex = (currentIndex << 1) + 1
|
|
284
|
+
const rightChildIndex = leftChildIndex + 1
|
|
285
|
+
let maxIndex = currentIndex
|
|
286
|
+
let maxPriority = originalPriorityValue
|
|
287
|
+
|
|
288
|
+
// Check if left child has higher priority
|
|
289
|
+
if (
|
|
290
|
+
leftChildIndex < heapSize &&
|
|
291
|
+
heapPriorities[leftChildIndex] > maxPriority
|
|
292
|
+
) {
|
|
293
|
+
maxIndex = leftChildIndex
|
|
294
|
+
maxPriority = heapPriorities[leftChildIndex]
|
|
360
295
|
}
|
|
361
296
|
|
|
362
|
-
if
|
|
363
|
-
|
|
297
|
+
// Check if right child has higher priority
|
|
298
|
+
if (
|
|
299
|
+
rightChildIndex < heapSize &&
|
|
300
|
+
heapPriorities[rightChildIndex] > maxPriority
|
|
301
|
+
) {
|
|
302
|
+
maxIndex = rightChildIndex
|
|
364
303
|
}
|
|
365
304
|
|
|
366
|
-
|
|
305
|
+
// If current position is correct, we're done
|
|
306
|
+
if (maxIndex === currentIndex) break
|
|
367
307
|
|
|
368
|
-
|
|
369
|
-
|
|
308
|
+
// Move the higher priority child up
|
|
309
|
+
heapIndices[currentIndex] = heapIndices[maxIndex]
|
|
310
|
+
heapPriorities[currentIndex] = heapPriorities[maxIndex]
|
|
311
|
+
currentIndex = maxIndex
|
|
370
312
|
}
|
|
371
313
|
|
|
372
|
-
|
|
314
|
+
// Place original element in its final position
|
|
315
|
+
heapIndices[currentIndex] = originalIndexValue
|
|
316
|
+
heapPriorities[currentIndex] = originalPriorityValue
|
|
373
317
|
}
|
package/codec/core/buffers.js
CHANGED
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
MAX_BFU_SIZE,
|
|
8
8
|
QMF_DELAY,
|
|
9
9
|
QMF_HIGH_BAND_DELAY,
|
|
10
|
+
PSYMODEL_CRITICAL_BANDS,
|
|
11
|
+
PSYMODEL_HALF_FFT_SIZE,
|
|
12
|
+
PSYMODEL_NUM_BINS,
|
|
10
13
|
} from './constants.js'
|
|
11
14
|
|
|
12
15
|
export class BufferPool {
|
|
@@ -88,5 +91,20 @@ export class BufferPool {
|
|
|
88
91
|
for (let i = 0; i < NUM_BFUS; i++) {
|
|
89
92
|
this.bfuData[i] = new Float32Array(MAX_BFU_SIZE)
|
|
90
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
|
+
}
|
|
91
109
|
}
|
|
92
110
|
}
|