carta1 1.0.22 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/codec/browser/interface.js +0 -1
- package/codec/coding/bitallocation.js +206 -248
- package/codec/coding/quantization.js +79 -78
- package/codec/core/buffers.js +0 -18
- package/codec/core/constants.js +24 -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
|
@@ -71,7 +71,6 @@ class Carta1Worker {
|
|
|
71
71
|
* @param {number} [options.transientThresholdLow] - Low band transient threshold
|
|
72
72
|
* @param {number} [options.transientThresholdMid] - Mid band transient threshold
|
|
73
73
|
* @param {number} [options.transientThresholdHigh] - High band transient threshold
|
|
74
|
-
* @param {number} [options.normalizationDb] - Normalization level in dB
|
|
75
74
|
* @returns {Promise<Object>} Encoding result
|
|
76
75
|
* @returns {Blob} returns.aeaBlob - ATRAC1 encoded audio blob
|
|
77
76
|
* @returns {Object} returns.shortBlockData - Short block frame indices
|
|
@@ -1,317 +1,275 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Carta1 Audio Codec - Bit Allocation Module
|
|
3
3
|
*
|
|
4
|
-
* This module implements the bit allocation
|
|
5
|
-
* distributing available bits
|
|
6
|
-
*
|
|
4
|
+
* This module implements the bit allocation system for the Carta1 audio codec,
|
|
5
|
+
* responsible for distributing available bits across frequency bands to minimize
|
|
6
|
+
* perceptual distortion while maintaining the target bitrate.
|
|
7
|
+
*
|
|
8
|
+
* The module uses Rate-Distortion Optimization (RDO) with a greedy algorithm
|
|
9
|
+
* that allocates bits to Band Frequency Units (BFUs) based on the perceptual
|
|
10
|
+
* benefit per bit spent. It employs psychoacoustic modeling to determine
|
|
11
|
+
* scale factors and uses a priority queue to efficiently find the optimal
|
|
12
|
+
* bit distribution.
|
|
13
|
+
*
|
|
14
|
+
* Key components:
|
|
15
|
+
* - RDO-based bit allocation using exponential distortion modeling
|
|
16
|
+
* - Scale factor computation for each frequency band
|
|
17
|
+
* - Max-heap priority queue for greedy optimization
|
|
18
|
+
* - Support for variable word lengths per BFU
|
|
19
|
+
*
|
|
20
|
+
* The algorithm ensures that bits are allocated where they provide the
|
|
21
|
+
* greatest reduction in audible distortion, resulting in high-quality
|
|
22
|
+
* audio compression at the target bitrate.
|
|
7
23
|
*/
|
|
24
|
+
|
|
8
25
|
import {
|
|
26
|
+
NUM_BFUS,
|
|
9
27
|
FRAME_BITS,
|
|
10
28
|
FRAME_OVERHEAD_BITS,
|
|
11
29
|
BITS_PER_BFU_METADATA,
|
|
12
|
-
BFU_AMOUNTS,
|
|
13
30
|
MAX_WORD_LENGTH_INDEX,
|
|
14
31
|
WORD_LENGTH_BITS,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
NUM_BFUS,
|
|
21
|
-
SAMPLE_RATE,
|
|
22
|
-
PSYMODEL_FFT_SIZE,
|
|
32
|
+
LP_DISTORTION_CONSTANT,
|
|
33
|
+
SF_POW_P,
|
|
34
|
+
POW2_POS_P,
|
|
35
|
+
POW2_NEG_P,
|
|
36
|
+
SCALE_FACTORS,
|
|
23
37
|
} from '../core/constants.js'
|
|
24
38
|
|
|
25
39
|
/**
|
|
26
|
-
* Allocate bits to BFUs
|
|
27
|
-
* @param {
|
|
28
|
-
* @param {
|
|
29
|
-
* @param {
|
|
30
|
-
* @
|
|
31
|
-
* @returns {Object} Allocation results with BFU count and bit distribution
|
|
40
|
+
* Allocate bits to BFUs using optimized greedy Lp-RDO
|
|
41
|
+
* @param {Array<Float32Array>} bfuData
|
|
42
|
+
* @param {Int32Array} bfuSizes
|
|
43
|
+
* @param {number} maxBfuCount
|
|
44
|
+
* @returns {{bfuCount:number, allocation:Int32Array, scaleFactorIndices:Int32Array}}
|
|
32
45
|
*/
|
|
33
|
-
export function allocateBits(
|
|
34
|
-
const
|
|
35
|
-
const maskDb = interpolateMask(psychoResults.criticalBandThresholds)
|
|
36
|
-
const smr = calculateSMR(energyDb, maskDb, maxBfuCount)
|
|
37
|
-
const usedBFU = selectOptimalBfuCount(smr, energyDb, maxBfuCount)
|
|
46
|
+
export function allocateBits(bfuData, bfuSizes, maxBfuCount) {
|
|
47
|
+
const usedBFU = maxBfuCount
|
|
38
48
|
const avail =
|
|
39
49
|
FRAME_BITS - FRAME_OVERHEAD_BITS - usedBFU * BITS_PER_BFU_METADATA
|
|
40
|
-
const allocation = distributeBitsGreedy(usedBFU, smr, bfuSizes, avail)
|
|
41
|
-
return { bfuCount: usedBFU, allocation }
|
|
42
|
-
}
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*/
|
|
51
|
-
function calculateBfuEnergy(bfuData, bfuSizes, bfuCount) {
|
|
52
|
-
const out = new Float32Array(bfuCount)
|
|
53
|
-
const minDb = PSYMODEL_MIN_POWER_DB
|
|
54
|
-
for (let i = 0; i < bfuCount; i++) {
|
|
55
|
-
const sz = bfuSizes[i]
|
|
56
|
-
if (!sz) {
|
|
57
|
-
out[i] = minDb
|
|
58
|
-
continue
|
|
59
|
-
}
|
|
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
|
|
51
|
+
const rdoResult = distributeBitsRDO(usedBFU, bfuData, bfuSizes, avail)
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
bfuCount: usedBFU,
|
|
55
|
+
allocation: rdoResult.wordLengths,
|
|
56
|
+
scaleFactorIndices: rdoResult.scaleFactorIndices,
|
|
64
57
|
}
|
|
65
|
-
return out
|
|
66
58
|
}
|
|
67
59
|
|
|
68
60
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
61
|
+
* Distributes available bits across Band Frequency Units (BFUs) using Rate-Distortion Optimization.
|
|
62
|
+
*
|
|
63
|
+
* This function implements an optimized greedy RDO allocator that minimizes perceptual distortion
|
|
64
|
+
* while staying within the bit budget. It uses a priority queue (max-heap) to greedily allocate
|
|
65
|
+
* bits to the BFUs that provide the greatest distortion reduction per bit spent.
|
|
66
|
+
*
|
|
67
|
+
* Mathematical Model:
|
|
68
|
+
* Distortion for b bits is modeled as:
|
|
69
|
+
* D(b) = C * 2^{-p * b} for b >= 1
|
|
70
|
+
* D(0) = C * 2^{ p } (finite large step for "mute" case)
|
|
71
|
+
* where C = N * (sf^p) * k, with N = BFU length, sf = scale factor, p and k constants.
|
|
72
|
+
*
|
|
73
|
+
* Hence, for an upgrade from b1 -> b2 (b2 > b1), ΔD = D(b1) - D(b2) can be computed with 2 table lookups.
|
|
74
|
+
*
|
|
75
|
+
* Precomputed tables:
|
|
76
|
+
* POW2_NEG_P[b] = 2^{-p*b} for b=0..maxBits
|
|
77
|
+
* POW2_POS_P = 2^{ p}
|
|
78
|
+
* SF_POW_P[sfIndex] = (SCALE_FACTORS[sfIndex])^p
|
|
79
|
+
*
|
|
80
|
+
* @param {number} activeBfuCount
|
|
81
|
+
* @param {Array<Float32Array>} bfuData
|
|
82
|
+
* @param {Int32Array} bfuSizes
|
|
83
|
+
* @param {number} remainingBits
|
|
84
|
+
* @returns {{wordLengths: Int32Array, scaleFactorIndices: Int32Array}}
|
|
72
85
|
*/
|
|
73
|
-
function
|
|
74
|
-
const
|
|
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
|
|
86
|
+
function distributeBitsRDO(activeBfuCount, bfuData, bfuSizes, remainingBits) {
|
|
87
|
+
const scaleFactorTable = new Int32Array(NUM_BFUS)
|
|
88
|
+
const wordLengths = new Int32Array(activeBfuCount)
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
const baseWeight = new Float64Array(activeBfuCount) // C above
|
|
91
|
+
for (let bfuIndex = 0; bfuIndex < activeBfuCount; bfuIndex++) {
|
|
92
|
+
const sz = bfuSizes[bfuIndex] | 0
|
|
93
|
+
if (sz > 0) {
|
|
94
|
+
const sfi = findScaleFactor(bfuData[bfuIndex].subarray(0, sz))
|
|
95
|
+
scaleFactorTable[bfuIndex] = sfi
|
|
96
|
+
baseWeight[bfuIndex] = sz * SF_POW_P[sfi] * LP_DISTORTION_CONSTANT
|
|
97
|
+
} else {
|
|
98
|
+
scaleFactorTable[bfuIndex] = 0
|
|
99
|
+
baseWeight[bfuIndex] = 0
|
|
100
|
+
}
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
const heapIndices = []
|
|
104
|
+
const heapPriorities = []
|
|
105
|
+
const deltaDist = (bfuIndex, currentWl, nextWl) => {
|
|
106
|
+
const base = baseWeight[bfuIndex]
|
|
107
|
+
if (base === 0) return 0
|
|
102
108
|
|
|
103
|
-
|
|
104
|
-
|
|
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)
|
|
110
|
-
*/
|
|
111
|
-
function calculateSMR(energyDb, maskDb, bfuCount) {
|
|
112
|
-
const smrValues = new Float32Array(bfuCount)
|
|
109
|
+
const bits1 = WORD_LENGTH_BITS[currentWl] | 0
|
|
110
|
+
const bits2 = WORD_LENGTH_BITS[nextWl] | 0
|
|
113
111
|
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
? -Infinity
|
|
119
|
-
: energyValue - maskDb[bfuIndex]
|
|
112
|
+
const f1 = bits1 === 0 ? POW2_POS_P : POW2_NEG_P[bits1]
|
|
113
|
+
const f2 = POW2_NEG_P[bits2]
|
|
114
|
+
|
|
115
|
+
return base * (f1 - f2)
|
|
120
116
|
}
|
|
121
117
|
|
|
122
|
-
|
|
123
|
-
|
|
118
|
+
for (let bfuIndex = 0; bfuIndex < activeBfuCount; bfuIndex++) {
|
|
119
|
+
const sz = bfuSizes[bfuIndex] | 0
|
|
120
|
+
if (sz === 0) continue
|
|
121
|
+
if (scaleFactorTable[bfuIndex] === 0) continue
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
* @param {number} maxBfuCount - Maximum BFUs available
|
|
131
|
-
* @returns {number} Optimal number of BFUs to use for encoding
|
|
132
|
-
*/
|
|
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
|
-
})
|
|
142
|
-
}
|
|
143
|
-
}
|
|
123
|
+
const currentWl = 0
|
|
124
|
+
const nextWl = 1
|
|
125
|
+
const deltaBits =
|
|
126
|
+
(WORD_LENGTH_BITS[nextWl] - WORD_LENGTH_BITS[currentWl]) | 0
|
|
127
|
+
if (deltaBits <= 0) continue
|
|
144
128
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
BFU_AMOUNTS.find((count) => count > bfuIndex) ||
|
|
151
|
-
BFU_AMOUNTS[BFU_AMOUNTS.length - 1]
|
|
152
|
-
)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return BFU_AMOUNTS[0]
|
|
129
|
+
const deltaCost = deltaBits * sz
|
|
130
|
+
const dd = deltaDist(bfuIndex, currentWl, nextWl)
|
|
131
|
+
|
|
132
|
+
heapIndices.push(bfuIndex)
|
|
133
|
+
heapPriorities.push(dd / deltaCost) // priority = ΔD / ΔR
|
|
156
134
|
}
|
|
157
135
|
|
|
158
|
-
//
|
|
159
|
-
|
|
136
|
+
// Heapify
|
|
137
|
+
for (let i = (heapIndices.length >> 1) - 1; i >= 0; i--)
|
|
138
|
+
siftDown(heapIndices, heapPriorities, i)
|
|
160
139
|
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
140
|
+
// Greedy spending
|
|
141
|
+
while (remainingBits > 0 && heapIndices.length > 0) {
|
|
142
|
+
const bfu = heapIndices[0]
|
|
143
|
+
const cur = wordLengths[bfu] | 0
|
|
144
|
+
const nxt = cur + 1
|
|
167
145
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (positiveSMRBfus.length <= candidateCount) return candidateCount
|
|
146
|
+
const sz = bfuSizes[bfu] | 0
|
|
147
|
+
const deltaBits = (WORD_LENGTH_BITS[nxt] - WORD_LENGTH_BITS[cur]) | 0
|
|
148
|
+
const cost = deltaBits * sz
|
|
172
149
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
(cumulativeSmr[positiveSMRBfus.length - 1] -
|
|
178
|
-
cumulativeSmr[lastIncludedIndex]) /
|
|
179
|
-
(positiveSMRBfus.length - lastIncludedIndex - 1)
|
|
150
|
+
if (cost > remainingBits || cost <= 0) {
|
|
151
|
+
popRoot(heapIndices, heapPriorities)
|
|
152
|
+
continue
|
|
153
|
+
}
|
|
180
154
|
|
|
181
|
-
|
|
182
|
-
|
|
155
|
+
remainingBits -= cost
|
|
156
|
+
wordLengths[bfu] = nxt
|
|
157
|
+
|
|
158
|
+
if (nxt < MAX_WORD_LENGTH_INDEX) {
|
|
159
|
+
const nxt2 = nxt + 1
|
|
160
|
+
const deltaBits2 = (WORD_LENGTH_BITS[nxt2] - WORD_LENGTH_BITS[nxt]) | 0
|
|
161
|
+
if (deltaBits2 > 0) {
|
|
162
|
+
heapPriorities[0] = deltaDist(bfu, nxt, nxt2) / (deltaBits2 * sz)
|
|
163
|
+
siftDown(heapIndices, heapPriorities, 0)
|
|
164
|
+
} else {
|
|
165
|
+
popRoot(heapIndices, heapPriorities)
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
popRoot(heapIndices, heapPriorities)
|
|
169
|
+
}
|
|
183
170
|
}
|
|
184
171
|
|
|
185
|
-
return
|
|
172
|
+
return { wordLengths, scaleFactorIndices: scaleFactorTable }
|
|
186
173
|
}
|
|
187
174
|
|
|
188
175
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* @param {
|
|
192
|
-
* @
|
|
193
|
-
* @param {Array} bfuSizes - Size (coefficient count) of each BFU
|
|
194
|
-
* @param {number} remainingBits - Total bits available for distribution
|
|
195
|
-
* @returns {Int32Array} Word length index allocation per BFU
|
|
176
|
+
* Find the optimal scale factor index for a set of coefficients.
|
|
177
|
+
* Same semantics as the original.
|
|
178
|
+
* @param {Float32Array} coefficients
|
|
179
|
+
* @returns {number}
|
|
196
180
|
*/
|
|
197
|
-
function
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
heapPriorities.push(smrValues[bfuIndex])
|
|
212
|
-
}
|
|
181
|
+
export function findScaleFactor(coefficients) {
|
|
182
|
+
let maxAmplitude = 0.0
|
|
183
|
+
const n = coefficients.length | 0
|
|
184
|
+
let i = 0
|
|
185
|
+
const n4 = n & ~3
|
|
186
|
+
for (; i < n4; i += 4) {
|
|
187
|
+
const a0 = Math.abs(coefficients[i])
|
|
188
|
+
const a1 = Math.abs(coefficients[i + 1])
|
|
189
|
+
const a2 = Math.abs(coefficients[i + 2])
|
|
190
|
+
const a3 = Math.abs(coefficients[i + 3])
|
|
191
|
+
if (a0 > maxAmplitude) maxAmplitude = a0
|
|
192
|
+
if (a1 > maxAmplitude) maxAmplitude = a1
|
|
193
|
+
if (a2 > maxAmplitude) maxAmplitude = a2
|
|
194
|
+
if (a3 > maxAmplitude) maxAmplitude = a3
|
|
213
195
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
let nodeIndex = (heapBfuIndices.length >> 1) - 1;
|
|
218
|
-
nodeIndex >= 0;
|
|
219
|
-
nodeIndex--
|
|
220
|
-
) {
|
|
221
|
-
siftDown(heapBfuIndices, heapPriorities, nodeIndex)
|
|
196
|
+
for (; i < n; i++) {
|
|
197
|
+
const a = Math.abs(coefficients[i])
|
|
198
|
+
if (a > maxAmplitude) maxAmplitude = a
|
|
222
199
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
200
|
+
if (maxAmplitude === 0) return 0
|
|
201
|
+
|
|
202
|
+
// Binary search
|
|
203
|
+
let low = 0
|
|
204
|
+
let high = SCALE_FACTORS.length - 1
|
|
205
|
+
while (low < high) {
|
|
206
|
+
const mid = (low + high) >> 1
|
|
207
|
+
if (maxAmplitude <= SCALE_FACTORS[mid]) {
|
|
208
|
+
high = mid
|
|
209
|
+
} else {
|
|
210
|
+
low = mid + 1
|
|
244
211
|
}
|
|
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)
|
|
251
212
|
}
|
|
252
|
-
|
|
253
|
-
return wordLengthAllocation
|
|
213
|
+
return low
|
|
254
214
|
}
|
|
255
215
|
|
|
256
216
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
217
|
+
* Removes the root element from a max-heap and maintains heap property.
|
|
218
|
+
*
|
|
219
|
+
* This function efficiently removes the maximum priority element from the heap
|
|
220
|
+
* by replacing it with the last element and then restoring the heap property
|
|
221
|
+
* through sifting down.
|
|
222
|
+
*
|
|
223
|
+
* @param {Array<number>} heapIndices
|
|
224
|
+
* @param {Array<number>} heapPriorities
|
|
260
225
|
*/
|
|
261
226
|
function popRoot(heapIndices, heapPriorities) {
|
|
262
|
-
const
|
|
263
|
-
heapIndices[0] = heapIndices[
|
|
264
|
-
heapPriorities[0] = heapPriorities[
|
|
227
|
+
const last = heapIndices.length - 1
|
|
228
|
+
heapIndices[0] = heapIndices[last]
|
|
229
|
+
heapPriorities[0] = heapPriorities[last]
|
|
265
230
|
heapIndices.pop()
|
|
266
231
|
heapPriorities.pop()
|
|
267
232
|
if (heapIndices.length) siftDown(heapIndices, heapPriorities, 0)
|
|
268
233
|
}
|
|
269
234
|
|
|
270
235
|
/**
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
236
|
+
* Restores max-heap property by sifting an element down from a given position.
|
|
237
|
+
*
|
|
238
|
+
* This function maintains the heap invariant where parent nodes have higher
|
|
239
|
+
* priority values than their children. It compares the element at startIndex
|
|
240
|
+
* with its children and swaps with the larger child until the heap property
|
|
241
|
+
* is satisfied, or it reaches a leaf position.
|
|
242
|
+
*
|
|
243
|
+
* @param {Array<number>} heapIndices
|
|
244
|
+
* @param {Array<number>} heapPriorities
|
|
245
|
+
* @param {number} startIndex
|
|
275
246
|
*/
|
|
276
247
|
function siftDown(heapIndices, heapPriorities, startIndex) {
|
|
277
248
|
const heapSize = heapIndices.length
|
|
278
|
-
let
|
|
279
|
-
const
|
|
280
|
-
const
|
|
249
|
+
let i = startIndex
|
|
250
|
+
const idxVal = heapIndices[i]
|
|
251
|
+
const prVal = heapPriorities[i]
|
|
281
252
|
|
|
282
253
|
while (true) {
|
|
283
|
-
const
|
|
284
|
-
const
|
|
285
|
-
let
|
|
286
|
-
let
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
heapPriorities[leftChildIndex] > maxPriority
|
|
292
|
-
) {
|
|
293
|
-
maxIndex = leftChildIndex
|
|
294
|
-
maxPriority = heapPriorities[leftChildIndex]
|
|
254
|
+
const l = (i << 1) + 1
|
|
255
|
+
const r = l + 1
|
|
256
|
+
let maxI = i
|
|
257
|
+
let maxP = prVal
|
|
258
|
+
|
|
259
|
+
if (l < heapSize && heapPriorities[l] > maxP) {
|
|
260
|
+
maxI = l
|
|
261
|
+
maxP = heapPriorities[l]
|
|
295
262
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (
|
|
299
|
-
rightChildIndex < heapSize &&
|
|
300
|
-
heapPriorities[rightChildIndex] > maxPriority
|
|
301
|
-
) {
|
|
302
|
-
maxIndex = rightChildIndex
|
|
263
|
+
if (r < heapSize && heapPriorities[r] > maxP) {
|
|
264
|
+
maxI = r
|
|
303
265
|
}
|
|
266
|
+
if (maxI === i) break
|
|
304
267
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
// Move the higher priority child up
|
|
309
|
-
heapIndices[currentIndex] = heapIndices[maxIndex]
|
|
310
|
-
heapPriorities[currentIndex] = heapPriorities[maxIndex]
|
|
311
|
-
currentIndex = maxIndex
|
|
268
|
+
heapIndices[i] = heapIndices[maxI]
|
|
269
|
+
heapPriorities[i] = heapPriorities[maxI]
|
|
270
|
+
i = maxI
|
|
312
271
|
}
|
|
313
272
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
heapPriorities[currentIndex] = originalPriorityValue
|
|
273
|
+
heapIndices[i] = idxVal
|
|
274
|
+
heapPriorities[i] = prVal
|
|
317
275
|
}
|