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.
@@ -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
- // Calculate signal energy for each BFU
37
- const signalEnergyDb = calculateBfuEnergy(bfuData, bfuSizes, maxBfuCount)
38
-
39
- // Interpolate critical band thresholds to BFUs
40
- const maskThresholdDb = interpolateCriticalBandsToBfus(
41
- psychoResults.criticalBandThresholds
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 data
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 energyPerBfu = new Float32Array(bfuCount)
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 data = bfuData[i]
84
- const size = bfuSizes[i]
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
- // Calculate mean square energy
92
- let energy = 0
93
- for (let j = 0; j < size; j++) {
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 critical band thresholds directly to BFU frequencies
107
- * @param {Float32Array} criticalBandThresholds - Critical band thresholds
108
- * @returns {Float32Array} Masking threshold per BFU
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 interpolateCriticalBandsToBfus(criticalBandThresholds) {
111
- const maskThresholdPerBfu = new Float32Array(NUM_BFUS)
112
- const freqPerBin = SAMPLE_RATE / PSYMODEL_FFT_SIZE
113
-
114
- for (let i = 0; i < NUM_BFUS; i++) {
115
- const bfuFreq = BFU_FREQUENCIES[i]
116
- const fftBinIndex = Math.round(bfuFreq / freqPerBin)
117
-
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
- }
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 maskThresholdPerBfu
100
+ return maskValues
155
101
  }
156
102
 
157
103
  /**
158
- * Calculate Signal-to-Mask Ratio for each BFU
159
- * @param {Float32Array} signalEnergyDb - Signal energy per BFU
160
- * @param {Float32Array} maskThresholdDb - Masking threshold per BFU
161
- * @param {number} maxBfuCount - Maximum number of BFUs
162
- * @returns {Float32Array} SMR values in dB
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(signalEnergyDb, maskThresholdDb, maxBfuCount) {
165
- const smrValues = new Float32Array(maxBfuCount)
111
+ function calculateSMR(energyDb, maskDb, bfuCount) {
112
+ const smrValues = new Float32Array(bfuCount)
166
113
 
167
- for (let bfu = 0; bfu < maxBfuCount; bfu++) {
168
- smrValues[bfu] =
169
- signalEnergyDb[bfu] <= PSYMODEL_MIN_POWER_DB
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
- : signalEnergyDb[bfu] - maskThresholdDb[bfu]
119
+ : energyValue - maskDb[bfuIndex]
172
120
  }
173
121
 
174
122
  return smrValues
175
123
  }
176
124
 
177
125
  /**
178
- * Select optimal number of BFUs to use based on SMR distribution
179
- * @param {Float32Array} smrValues - Signal-to-Mask ratios
180
- * @param {Float32Array} signalEnergyDb - Signal energy per BFU
181
- * @param {number} maxBfuCount - Maximum available BFUs
182
- * @returns {number} Optimal BFU count
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, signalEnergyDb, maxBfuCount) {
185
- // Check if we have any audible content (positive SMR)
186
- const hasAudibleContent = smrValues.some((smr) => smr > 0)
187
-
188
- if (hasAudibleContent) {
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
- }
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
- contributions.sort((a, b) => b.smr - a.smr)
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
- 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
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
- // Find smallest BFU count that includes all signal
261
- for (const candidateCount of BFU_AMOUNTS) {
262
- if (candidateCount > highestSignalBfu) {
263
- return candidateCount
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[BFU_AMOUNTS_COUNT - 1]
185
+ return BFU_AMOUNTS[BFU_AMOUNTS.length - 1]
269
186
  }
270
187
 
271
188
  /**
272
- * Allocate available bits among BFUs using a greedy algorithm
273
- * @param {number} actualBfuCount - Number of BFUs to allocate
274
- * @param {Float32Array} smrValues - Signal-to-Mask ratios
275
- * @param {Array} bfuSizes - Size of each BFU
276
- * @param {number} availableBits - Total bits available for allocation
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 performBitAllocation(
280
- actualBfuCount,
197
+ function distributeBitsGreedy(
198
+ activeBfuCount,
281
199
  smrValues,
282
200
  bfuSizes,
283
- availableBits
201
+ remainingBits
284
202
  ) {
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] })
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 heap
296
- for (let i = Math.floor(heap.length / 2) - 1; i >= 0; i--) {
297
- heapifyDown(heap, i)
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
- // Greedy allocation
301
- while (heap.length > 0 && availableBits > 0) {
302
- const bestBfu = heap[0]
303
- const currentWl = allocation[bestBfu.bfu]
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
- if (currentWl >= MAX_WORD_LENGTH_INDEX) {
306
- heap[0] = heap[heap.length - 1]
307
- heap.pop()
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
- const upgradeCost = calculateUpgradeCost(bestBfu.bfu, currentWl, bfuSizes)
313
-
314
- if (upgradeCost <= availableBits) {
315
- availableBits -= upgradeCost
316
- allocation[bestBfu.bfu]++
317
- bestBfu.priority += DISTORTION_DELTA_DB[currentWl]
318
- heapifyDown(heap, 0)
319
- } else {
320
- heap[0] = heap[heap.length - 1]
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 allocation
253
+ return wordLengthAllocation
327
254
  }
328
255
 
329
256
  /**
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
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 calculateUpgradeCost(bfuIndex, currentWl, bfuSizes) {
337
- const currentBits = WORD_LENGTH_BITS[currentWl] * bfuSizes[bfuIndex]
338
- const nextBits = WORD_LENGTH_BITS[currentWl + 1] * bfuSizes[bfuIndex]
339
- return nextBits - currentBits
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
- * Maintain max heap property by moving element down
344
- * @param {Array} heap - Heap array
345
- * @param {number} startIdx - Starting index
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 heapifyDown(heap, startIdx) {
348
- const length = heap.length
349
- const element = heap[startIdx]
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 leftChild = 2 * startIdx + 1
353
- const rightChild = 2 * startIdx + 2
354
- let largest = startIdx
355
- let largestPriority = element.priority
356
-
357
- if (leftChild < length && heap[leftChild].priority > largestPriority) {
358
- largest = leftChild
359
- largestPriority = heap[leftChild].priority
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 (rightChild < length && heap[rightChild].priority > largestPriority) {
363
- largest = rightChild
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
- if (largest === startIdx) break
305
+ // If current position is correct, we're done
306
+ if (maxIndex === currentIndex) break
367
307
 
368
- heap[startIdx] = heap[largest]
369
- startIdx = largest
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
- heap[startIdx] = element
314
+ // Place original element in its final position
315
+ heapIndices[currentIndex] = originalIndexValue
316
+ heapPriorities[currentIndex] = originalPriorityValue
373
317
  }
@@ -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
  }