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.
@@ -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 algorithm for ATRAC1 compression,
5
- * distributing available bits among Block Floating Units (BFUs) based on
6
- * psychoacoustic masking and signal-to-mask ratios.
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
- DISTORTION_DELTA_DB,
16
- PSYMODEL_MIN_POWER_DB,
17
- INTERPOLATION_COMPENSATION_FACTOR,
18
- BFU_MASK_INTERPOLATION_TABLE,
19
- BFU_FREQUENCIES,
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 based on psychoacoustic analysis
27
- * @param {Object} psychoResults - Psychoacoustic analysis results
28
- * @param {Array} bfuData - BFU coefficient data
29
- * @param {Array} bfuSizes - Size of each BFU
30
- * @param {number} maxBfuCount - Maximum number of BFUs to use
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(psychoResults, bfuData, bfuSizes, maxBfuCount) {
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)
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
- * Calculate energy in dB for each BFU
46
- * @param {Array} bfuData - BFU coefficient bfuData
47
- * @param {Array} bfuSizes - Size of each BFU
48
- * @param {number} bfuCount - Number of BFUs to process
49
- * @returns {Float32Array} Energy values in dB
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
- * 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
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 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
86
+ function distributeBitsRDO(activeBfuCount, bfuData, bfuSizes, remainingBits) {
87
+ const scaleFactorTable = new Int32Array(NUM_BFUS)
88
+ const wordLengths = new Int32Array(activeBfuCount)
92
89
 
93
- // Add compensation factor based on threshold difference
94
- maskValues[bfuIndex] =
95
- interpolatedValue +
96
- Math.abs(nextBandThreshold - currentBandThreshold) *
97
- INTERPOLATION_COMPENSATION_FACTOR
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
- return maskValues
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
- * 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)
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
- for (let bfuIndex = 0; bfuIndex < bfuCount; bfuIndex++) {
115
- const energyValue = energyDb[bfuIndex]
116
- smrValues[bfuIndex] =
117
- energyValue <= PSYMODEL_MIN_POWER_DB
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
- return smrValues
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
- * 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
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
- // 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]
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
- // Sort BFUs by SMR value (highest first)
159
- positiveSMRBfus.sort((a, b) => b.smrValue - a.smrValue)
136
+ // Heapify
137
+ for (let i = (heapIndices.length >> 1) - 1; i >= 0; i--)
138
+ siftDown(heapIndices, heapPriorities, i)
160
139
 
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
- }
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
- // 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
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
- 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)
150
+ if (cost > remainingBits || cost <= 0) {
151
+ popRoot(heapIndices, heapPriorities)
152
+ continue
153
+ }
180
154
 
181
- // If excluded BFUs contribute less than 10% of included average, use this count
182
- if (averageExcludedSmr < averageIncludedSmr * 0.1) return candidateCount
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 BFU_AMOUNTS[BFU_AMOUNTS.length - 1]
172
+ return { wordLengths, scaleFactorIndices: scaleFactorTable }
186
173
  }
187
174
 
188
175
  /**
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
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 distributeBitsGreedy(
198
- activeBfuCount,
199
- smrValues,
200
- bfuSizes,
201
- remainingBits
202
- ) {
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])
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
- // Build max-heap structure
216
- for (
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
- // Greedily allocate bits to highest priority BFUs
225
- while (heapBfuIndices.length && remainingBits > 0) {
226
- const currentBfu = heapBfuIndices[0]
227
- const currentWordLength = wordLengthAllocation[currentBfu]
228
-
229
- // Skip BFUs that have reached maximum quantization precision
230
- if (currentWordLength >= MAX_WORD_LENGTH_INDEX) {
231
- popRoot(heapBfuIndices, heapPriorities)
232
- continue
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
- * 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
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 lastIndex = heapIndices.length - 1
263
- heapIndices[0] = heapIndices[lastIndex]
264
- heapPriorities[0] = heapPriorities[lastIndex]
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
- * 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
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 currentIndex = startIndex
279
- const originalIndexValue = heapIndices[currentIndex]
280
- const originalPriorityValue = heapPriorities[currentIndex]
249
+ let i = startIndex
250
+ const idxVal = heapIndices[i]
251
+ const prVal = heapPriorities[i]
281
252
 
282
253
  while (true) {
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]
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
- // Check if right child has higher priority
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
- // If current position is correct, we're done
306
- if (maxIndex === currentIndex) break
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
- // Place original element in its final position
315
- heapIndices[currentIndex] = originalIndexValue
316
- heapPriorities[currentIndex] = originalPriorityValue
273
+ heapIndices[i] = idxVal
274
+ heapPriorities[i] = prVal
317
275
  }