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.
@@ -9,14 +9,17 @@ import {
9
9
  PSYMODEL_MIN_POWER_DB,
10
10
  PSYMODEL_NOT_EXAMINED,
11
11
  PSYMODEL_TONAL,
12
+ PSYMODEL_FFT_SIZE,
12
13
  PSYMODEL_NON_TONAL,
13
14
  PSYMODEL_IRRELEVANT,
14
15
  PSYMODEL_LOG10_FACTOR,
15
16
  PSYMODEL_POW10_FACTOR,
16
17
  PSYMODEL_CRITICAL_BANDS,
17
- PSYMODEL_CB_FREQ_INDICES,
18
18
  PSYMODEL_THRESHOLD_TABLE,
19
19
  PSYMODEL_FREQ_TO_CB_MAP,
20
+ PSYMODEL_PSD_SOURCE_IDX0,
21
+ PSYMODEL_PSD_INTERP_WEIGHT,
22
+ PSYMODEL_PSD_SOURCE_IDX1,
20
23
  } from '../core/constants.js'
21
24
 
22
25
  /**
@@ -42,134 +45,70 @@ export function fromDb(db) {
42
45
  /**
43
46
  * Perform complete psychoacoustic analysis on MDCT coefficients
44
47
  * @param {Float32Array} mdctCoeffs - MDCT coefficients (512 samples)
45
- * @param {number} targetSize - Target FFT size for analysis
46
- * @param {Object} psychoBuffers - Reusable buffers for performance
48
+ * @param {number} normalizationDb - Target maximum power level in dB
47
49
  * @returns {Object} Analysis results including PSD, maskers, and thresholds
48
50
  */
49
- export function psychoAnalysis(
50
- mdctCoeffs,
51
- targetSize,
52
- psychoBuffers,
53
- normalizationDb
54
- ) {
55
- const fftScale = targetSize / 512
56
-
57
- // Apply frequency band reversal correction
58
- const correctedCoeffs = applyMdctCorrection(mdctCoeffs)
51
+ export function psychoAnalysis(mdctCoeffs, normalizationDb) {
52
+ const fftScale = PSYMODEL_FFT_SIZE / 512
59
53
 
60
54
  // Calculate power spectral density
61
- const psd = calculatePSDFromMDCT(correctedCoeffs, targetSize, normalizationDb)
55
+ const psd = calculatePSDFromMDCT(mdctCoeffs, normalizationDb)
62
56
 
63
57
  // Find tonal and non-tonal maskers
64
- const { flags, tonalMaskers, nonTonalMaskers } = findAllMaskers(
65
- psd,
66
- psychoBuffers,
67
- fftScale
68
- )
58
+ const { flags, tonalMaskers, nonTonalMaskers } = findAllMaskers(psd, fftScale)
69
59
 
70
- // Calculate global masking threshold
71
- const globalThreshold = calculateGlobalThreshold(
60
+ // Calculate critical band masking thresholds
61
+ const criticalBandThresholds = calculateCriticalBandThresholds(
72
62
  psd,
73
63
  tonalMaskers,
74
- nonTonalMaskers,
75
- psychoBuffers.threshold,
76
- targetSize
64
+ nonTonalMaskers
77
65
  )
78
66
 
79
67
  return {
80
68
  psd,
81
69
  tonalMaskers,
70
+ tonalMaskersCount: tonalMaskers.length,
82
71
  nonTonalMaskers,
83
- globalThreshold,
72
+ nonTonalMaskersCount: nonTonalMaskers.length,
73
+ criticalBandThresholds,
84
74
  flags,
85
75
  }
86
76
  }
87
77
 
88
- /**
89
- * Apply frequency band reversal correction to MDCT coefficients
90
- * @param {Float32Array} mdctCoeffs - Original MDCT coefficients
91
- * @returns {Float32Array} Corrected coefficients
92
- */
93
- function applyMdctCorrection(mdctCoeffs) {
94
- const corrected = new Float32Array(512)
95
-
96
- // Low band: direct copy
97
- corrected.set(mdctCoeffs.subarray(0, 128), 0)
98
-
99
- // Mid band: reversed
100
- for (let i = 0; i < 128; i++) {
101
- corrected[128 + i] = mdctCoeffs[255 - i]
102
- }
103
-
104
- // High band: reversed
105
- for (let i = 0; i < 256; i++) {
106
- corrected[256 + i] = mdctCoeffs[511 - i]
107
- }
108
-
109
- return corrected
110
- }
111
-
112
78
  /**
113
79
  * Calculate Power Spectral Density from MDCT coefficients
114
80
  * @param {Float32Array} mdctCoeffs - MDCT coefficients
115
- * @param {number} targetSize - Target FFT size
116
81
  * @returns {Float32Array} Power spectral density in dB
117
82
  */
118
- function calculatePSDFromMDCT(mdctCoeffs, targetSize, normalizationDb) {
119
- const halfTargetSize = targetSize / 2
120
- const psd = new Float32Array(halfTargetSize + 1)
121
-
122
- // Calculate power values and find maximum
123
- const powers = new Float32Array(512)
124
- let maxPower = 0
125
-
126
- for (let i = 0; i < 512; i++) {
127
- const power = mdctCoeffs[i] * mdctCoeffs[i]
128
- powers[i] = power
129
- maxPower = Math.max(maxPower, power)
130
- }
131
-
132
- const normFactor = 1 / (maxPower || 1)
133
- let maxPsdValue = -Infinity
134
-
135
- if (targetSize === 512) {
136
- // Direct averaging for same size
137
- for (let i = 0; i < 256; i++) {
138
- const avgPower = (powers[i * 2] + powers[i * 2 + 1]) * 0.5 * normFactor
139
- psd[i] = avgPower > 0 ? toDb(avgPower) : PSYMODEL_MIN_POWER_DB
140
- maxPsdValue = Math.max(maxPsdValue, psd[i])
141
- }
142
- psd[256] = psd[255]
143
- // Check if last value is the max
144
- maxPsdValue = Math.max(maxPsdValue, psd[256])
145
- } else {
146
- // Interpolate for different sizes
147
- const scaleFactor = 512 / targetSize
148
-
149
- for (let i = 0; i <= halfTargetSize; i++) {
150
- const srcIdx = i * scaleFactor
151
- const srcIdxInt = Math.floor(srcIdx)
152
- const frac = srcIdx - srcIdxInt
153
-
154
- const power =
155
- srcIdxInt < 511
156
- ? powers[srcIdxInt] * (1 - frac) + powers[srcIdxInt + 1] * frac
157
- : powers[511]
158
-
159
- const normalizedPower = power * normFactor
160
- psd[i] =
161
- normalizedPower > 0 ? toDb(normalizedPower) : PSYMODEL_MIN_POWER_DB
162
-
163
- maxPsdValue = Math.max(maxPsdValue, psd[i])
83
+ function calculatePSDFromMDCT(mdctCoeffs, normalizationDb) {
84
+ const half = PSYMODEL_FFT_SIZE >>> 1
85
+ const psd = new Float32Array(half + 1)
86
+ const linearPower = new Float32Array(half + 1)
87
+
88
+ let maxLinearPower = 0
89
+ for (let i = 0; i <= half; i++) {
90
+ const a = mdctCoeffs[PSYMODEL_PSD_SOURCE_IDX0[i]]
91
+ const b = mdctCoeffs[PSYMODEL_PSD_SOURCE_IDX1[i]]
92
+
93
+ const power =
94
+ a * a * (1 - PSYMODEL_PSD_INTERP_WEIGHT[i]) +
95
+ b * b * PSYMODEL_PSD_INTERP_WEIGHT[i]
96
+ linearPower[i] = power
97
+
98
+ if (power > maxLinearPower) {
99
+ maxLinearPower = power
164
100
  }
165
101
  }
166
102
 
167
- // Apply normalization to target maximum
168
- const normalizationDelta = normalizationDb - maxPsdValue
103
+ const offset =
104
+ normalizationDb - PSYMODEL_LOG10_FACTOR * Math.log(maxLinearPower || 1)
169
105
 
170
- for (let i = 0; i < psd.length; i++) {
171
- if (psd[i] > PSYMODEL_MIN_POWER_DB) {
172
- psd[i] += normalizationDelta
106
+ for (let i = 0; i <= half; i++) {
107
+ const power = linearPower[i]
108
+ if (power > 0) {
109
+ psd[i] = PSYMODEL_LOG10_FACTOR * Math.log(power) + offset
110
+ } else {
111
+ psd[i] = PSYMODEL_MIN_POWER_DB
173
112
  }
174
113
  }
175
114
 
@@ -179,20 +118,19 @@ function calculatePSDFromMDCT(mdctCoeffs, targetSize, normalizationDb) {
179
118
  /**
180
119
  * Find all maskers (tonal and non-tonal) in the spectrum
181
120
  * @param {Float32Array} psd - Power spectral density
182
- * @param {Object} psychoBuffers - Reusable buffers
183
121
  * @param {number} fftScale - FFT scaling factor
184
122
  * @returns {Object} Flags array and lists of maskers
185
123
  */
186
- function findAllMaskers(psd, psychoBuffers, fftScale) {
124
+ function findAllMaskers(psd, fftScale) {
187
125
  // Initialize flags
188
- const flags = psychoBuffers.flags
126
+ const flags = new Uint8Array(PSYMODEL_FFT_SIZE / 2)
189
127
  flags.fill(PSYMODEL_NOT_EXAMINED)
190
128
 
191
129
  // Find tonal maskers first
192
- let { tonalList } = findTonalMaskers(psd, flags, fftScale)
130
+ const tonalList = findTonalMaskers(psd, flags, fftScale)
193
131
 
194
132
  // Find non-tonal maskers
195
- let { nonTonalList } = findNonTonalMaskers(flags, psd)
133
+ const nonTonalList = findNonTonalMaskers(flags, psd)
196
134
 
197
135
  // Apply decimation to remove weak maskers
198
136
  const decimated = decimateMaskers(flags, tonalList, nonTonalList)
@@ -214,68 +152,67 @@ function findAllMaskers(psd, psychoBuffers, fftScale) {
214
152
  function findTonalMaskers(psd, flags, fftScale) {
215
153
  const tonalList = []
216
154
  const maxTonalBin = Math.floor(249 * fftScale)
217
- const fftSize = (psd.length - 1) * 2
218
155
 
219
156
  // Frequency range boundaries
220
157
  const RANGE_LOW = Math.floor(63 * fftScale)
221
158
  const RANGE_MID = Math.floor(127 * fftScale)
222
159
  const RANGE_HIGH = Math.floor(150 * fftScale)
223
160
 
224
- for (let k = 1; k < fftSize / 2 - 1 && k <= maxTonalBin; k++) {
161
+ for (let k = 1; k < psd.length - 1 && k <= maxTonalBin; k++) {
225
162
  // Check for local maximum
226
163
  if (psd[k] <= psd[k - 1] || psd[k] < psd[k + 1]) continue
227
164
 
228
165
  // Determine search range based on frequency
229
- const searchOffsets = getSearchOffsets(k, RANGE_LOW, RANGE_MID, RANGE_HIGH)
230
- if (!searchOffsets) continue
166
+ let searchOffsets
167
+ if (k > 2 && k < RANGE_LOW) {
168
+ searchOffsets = [-2, 2]
169
+ } else if (k >= RANGE_LOW && k < RANGE_MID) {
170
+ searchOffsets = [-3, -2, 2, 3]
171
+ } else if (k >= RANGE_MID && k < RANGE_HIGH) {
172
+ searchOffsets = [-6, -5, -4, -3, -2, 2, 3, 4, 5, 6]
173
+ } else {
174
+ continue
175
+ }
231
176
 
232
177
  // Check tonality criteria
233
178
  if (isTonal(psd, k, searchOffsets)) {
234
179
  // Calculate combined SPL
235
180
  const spl = toDb(fromDb(psd[k - 1]) + fromDb(psd[k]) + fromDb(psd[k + 1]))
236
-
237
181
  tonalList.push({ index: k, spl })
238
182
  markTonalComponent(flags, k, searchOffsets)
239
183
  }
240
184
  }
241
185
 
242
- return { flags, tonalList }
243
- }
244
-
245
- /**
246
- * Get search offsets for tonality check based on frequency
247
- */
248
- function getSearchOffsets(k, rangeLow, rangeMid, rangeHigh) {
249
- if (k > 2 && k < rangeLow) return [-2, 2]
250
- if (k >= rangeLow && k < rangeMid) return [-3, -2, 2, 3]
251
- if (k >= rangeMid && k < rangeHigh) return [-6, -5, -4, -3, -2, 2, 3, 4, 5, 6]
252
- return null
186
+ return tonalList
253
187
  }
254
188
 
255
189
  /**
256
190
  * Check if a peak is tonal based on surrounding bins
191
+ * @param {Float32Array} psd - Power spectral density array
192
+ * @param {number} k - Index of the peak to test
193
+ * @param {number[]} offsets - Array of offset indices to check around the peak
194
+ * @returns {boolean} True if the peak qualifies as tonal
257
195
  */
258
196
  function isTonal(psd, k, offsets) {
259
- const TONAL_THRESHOLD = 7 // dB
260
-
197
+ const TONAL_THRESHOLD = 7.0
261
198
  for (const offset of offsets) {
262
199
  const idx = k + offset
263
- if (idx >= 0 && idx < psd.length) {
264
- if (psd[k] - psd[idx] < TONAL_THRESHOLD) return false
200
+ if (idx >= 0 && idx < psd.length && psd[k] - psd[idx] < TONAL_THRESHOLD) {
201
+ return false
265
202
  }
266
203
  }
267
-
268
204
  return true
269
205
  }
270
206
 
271
207
  /**
272
208
  * Mark component and neighbors as tonal/irrelevant
209
+ * @param {Uint8Array} flags - Component classification flags array
210
+ * @param {number} k - Index of the tonal component
211
+ * @param {number[]} searchOffsets - Array of offset indices used for tonal detection
273
212
  */
274
213
  function markTonalComponent(flags, k, searchOffsets) {
275
214
  flags[k] = PSYMODEL_TONAL
276
-
277
- const allOffsets = [...searchOffsets, -1, 1]
278
- for (const offset of allOffsets) {
215
+ for (const offset of [...searchOffsets, -1, 1]) {
279
216
  const idx = k + offset
280
217
  if (idx >= 0 && idx < flags.length && idx !== k) {
281
218
  flags[idx] = PSYMODEL_IRRELEVANT
@@ -297,7 +234,6 @@ function findNonTonalMaskers(flags, psd) {
297
234
  const cbEnd =
298
235
  PSYMODEL_THRESHOLD_TABLE[PSYMODEL_CRITICAL_BANDS[i + 1]][0] - 1
299
236
 
300
- // Calculate band energy and centroid
301
237
  const bandAnalysis = analyzeCriticalBand(
302
238
  psd,
303
239
  flags,
@@ -321,11 +257,17 @@ function findNonTonalMaskers(flags, psd) {
321
257
  }
322
258
  }
323
259
 
324
- return { flags, nonTonalList }
260
+ return nonTonalList
325
261
  }
326
262
 
327
263
  /**
328
264
  * Analyze a critical band for non-tonal masking
265
+ * @param {Float32Array} psd - Power spectral density array
266
+ * @param {Uint8Array} flags - Component classification flags array
267
+ * @param {number} startBin - Starting frequency bin of the critical band
268
+ * @param {number} endBin - Ending frequency bin of the critical band
269
+ * @param {number} bandIndex - Critical band index for threshold table lookup
270
+ * @returns {Object} Analysis results with power and centroid offset
329
271
  */
330
272
  function analyzeCriticalBand(psd, flags, startBin, endBin, bandIndex) {
331
273
  let totalPower = 0
@@ -337,9 +279,11 @@ function analyzeCriticalBand(psd, flags, startBin, endBin, bandIndex) {
337
279
  const power = fromDb(psd[k])
338
280
  totalPower += power
339
281
 
340
- const barkDiff =
341
- PSYMODEL_THRESHOLD_TABLE[PSYMODEL_FREQ_TO_CB_MAP[k]][1] - baseBark
342
- weightedPower += power * barkDiff
282
+ if (k < PSYMODEL_FREQ_TO_CB_MAP.length) {
283
+ const barkDiff =
284
+ PSYMODEL_THRESHOLD_TABLE[PSYMODEL_FREQ_TO_CB_MAP[k]][1] - baseBark
285
+ weightedPower += power * barkDiff
286
+ }
343
287
 
344
288
  flags[k] = PSYMODEL_IRRELEVANT
345
289
  }
@@ -401,52 +345,45 @@ function decimateMaskers(flags, tonalList, nonTonalList) {
401
345
  }
402
346
 
403
347
  /**
404
- * Calculate global masking threshold
348
+ * Calculate critical band masking thresholds
405
349
  * @param {Float32Array} psd - Power spectral density
406
350
  * @param {Array} tonalMaskers - List of tonal maskers
407
351
  * @param {Array} nonTonalMaskers - List of non-tonal maskers
408
- * @param {Float32Array} thresholdBuffer - Output buffer for threshold
409
- * @param {number} fftSize - FFT size
410
- * @returns {Float32Array} Global masking threshold
352
+ * @returns {Float32Array} Critical band thresholds
411
353
  */
412
- function calculateGlobalThreshold(
413
- psd,
414
- tonalMaskers,
415
- nonTonalMaskers,
416
- thresholdBuffer,
417
- fftSize
418
- ) {
354
+ function calculateCriticalBandThresholds(psd, tonalMaskers, nonTonalMaskers) {
419
355
  const criticalBandThresholds = new Float32Array(
420
356
  PSYMODEL_CRITICAL_BANDS.length
421
357
  )
422
358
 
423
- // Calculate threshold for each critical band
424
- calculateCriticalBandThresholds(
425
- psd,
426
- tonalMaskers,
427
- nonTonalMaskers,
428
- criticalBandThresholds
429
- )
359
+ // Pre-collect all maskers with their properties
360
+ const maskers = []
430
361
 
431
- // Interpolate to full spectrum with compensation
432
- interpolateThresholdCompensated(
433
- criticalBandThresholds,
434
- thresholdBuffer,
435
- fftSize
436
- )
362
+ for (const masker of tonalMaskers) {
363
+ const j = masker.index
364
+ if (j < PSYMODEL_FREQ_TO_CB_MAP.length) {
365
+ maskers.push({
366
+ spl: masker.spl,
367
+ psd: psd[j],
368
+ bark: PSYMODEL_THRESHOLD_TABLE[PSYMODEL_FREQ_TO_CB_MAP[j]][1],
369
+ isTonal: true,
370
+ })
371
+ }
372
+ }
437
373
 
438
- return thresholdBuffer
439
- }
374
+ for (const masker of nonTonalMaskers) {
375
+ const j = masker.index
376
+ if (j < PSYMODEL_FREQ_TO_CB_MAP.length) {
377
+ maskers.push({
378
+ spl: masker.spl,
379
+ psd: psd[j],
380
+ bark: PSYMODEL_THRESHOLD_TABLE[PSYMODEL_FREQ_TO_CB_MAP[j]][1],
381
+ isTonal: false,
382
+ })
383
+ }
384
+ }
440
385
 
441
- /**
442
- * Calculate masking threshold for each critical band
443
- */
444
- function calculateCriticalBandThresholds(
445
- psd,
446
- tonalMaskers,
447
- nonTonalMaskers,
448
- output
449
- ) {
386
+ // Calculate threshold for each critical band
450
387
  for (let i = 0; i < PSYMODEL_CRITICAL_BANDS.length; i++) {
451
388
  const bandData = PSYMODEL_THRESHOLD_TABLE[PSYMODEL_CRITICAL_BANDS[i]]
452
389
  const maskedBark = bandData[1]
@@ -456,57 +393,36 @@ function calculateCriticalBandThresholds(
456
393
  let summedEnergy = fromDb(quietThreshold)
457
394
 
458
395
  // Add contributions from all maskers
459
- summedEnergy += calculateMaskerContributions(
460
- psd,
461
- tonalMaskers,
462
- maskedBark,
463
- true // isTonal
464
- )
465
-
466
- summedEnergy += calculateMaskerContributions(
467
- psd,
468
- nonTonalMaskers,
469
- maskedBark,
470
- false // isTonal
471
- )
472
-
473
- output[i] = toDb(summedEnergy)
474
- }
475
- }
476
-
477
- /**
478
- * Calculate energy contributions from a set of maskers
479
- */
480
- function calculateMaskerContributions(psd, maskers, maskedBark, isTonal) {
481
- let totalEnergy = 0
482
-
483
- for (const masker of maskers) {
484
- const j = masker.index
485
- if (j >= PSYMODEL_FREQ_TO_CB_MAP.length) continue
486
-
487
- const maskerBark = PSYMODEL_THRESHOLD_TABLE[PSYMODEL_FREQ_TO_CB_MAP[j]][1]
488
- const barkDistance = maskedBark - maskerBark
489
-
490
- // Check if within masking range
491
- if (barkDistance < -3 || barkDistance >= 8) continue
492
-
493
- // Calculate masking level
494
- const maskedDb = calculateMaskingLevel(
495
- masker.spl,
496
- psd[j],
497
- maskerBark,
498
- barkDistance,
499
- isTonal
500
- )
396
+ for (const masker of maskers) {
397
+ const barkDistance = maskedBark - masker.bark
398
+
399
+ // Check if within masking range
400
+ if (barkDistance >= -3 && barkDistance < 8) {
401
+ const maskedDb = calculateMaskingLevel(
402
+ masker.spl,
403
+ masker.psd,
404
+ masker.bark,
405
+ barkDistance,
406
+ masker.isTonal
407
+ )
408
+ summedEnergy += fromDb(maskedDb)
409
+ }
410
+ }
501
411
 
502
- totalEnergy += fromDb(maskedDb)
412
+ criticalBandThresholds[i] = toDb(summedEnergy)
503
413
  }
504
414
 
505
- return totalEnergy
415
+ return criticalBandThresholds
506
416
  }
507
417
 
508
418
  /**
509
- * Calculate masking level based on masker properties
419
+ * Calculate energy contributions from a set of maskers
420
+ * @param {number} maskerSpl - Sound pressure level of the masker in dB
421
+ * @param {number} maskerPsd - Power spectral density of the masker in dB
422
+ * @param {number} maskerBark - Bark frequency of the masker
423
+ * @param {number} barkDistance - Distance in Bark scale from masker to maskee
424
+ * @param {boolean} isTonal - Whether the masker is tonal or non-tonal
425
+ * @returns {number} Masking threshold contribution in dB
510
426
  */
511
427
  function calculateMaskingLevel(
512
428
  maskerSpl,
@@ -515,75 +431,23 @@ function calculateMaskingLevel(
515
431
  barkDistance,
516
432
  isTonal
517
433
  ) {
518
- // Masking index
519
434
  const tonalOffset = isTonal
520
435
  ? -1.525 - 0.275 * maskerBark - 4.5
521
436
  : -1.525 - 0.175 * maskerBark - 0.5
522
437
 
523
- // PSD-dependent factors
524
- const psdFactor1 = 0.4 * maskerPsd + 6
438
+ const psdFactor1 = 0.4 * maskerPsd + 6.0
525
439
  const psdFactor2 = 0.15 * maskerPsd
526
440
 
527
- // Calculate masking function
528
441
  let maskingFunction
529
- if (barkDistance < -1) {
530
- maskingFunction = 17 * (barkDistance + 1) - psdFactor1
531
- } else if (barkDistance < 0) {
442
+ if (barkDistance < -1.0) {
443
+ maskingFunction = 17.0 * (barkDistance + 1.0) - psdFactor1
444
+ } else if (barkDistance < 0.0) {
532
445
  maskingFunction = psdFactor1 * barkDistance
533
- } else if (barkDistance < 1) {
534
- maskingFunction = -17 * barkDistance
446
+ } else if (barkDistance < 1.0) {
447
+ maskingFunction = -17.0 * barkDistance
535
448
  } else {
536
- maskingFunction = -(barkDistance - 1) * (17 - psdFactor2) - 17
449
+ maskingFunction = -(barkDistance - 1.0) * (17.0 - psdFactor2) - 17.0
537
450
  }
538
451
 
539
452
  return maskerSpl + tonalOffset + maskingFunction
540
453
  }
541
-
542
- /**
543
- * Interpolate critical band thresholds to full spectrum
544
- * @param {Float32Array} cbThresholds - Critical band thresholds
545
- * @param {Float32Array} output - Output buffer
546
- * @param {number} fftSize - FFT size
547
- */
548
- function interpolateThresholdCompensated(cbThresholds, output, fftSize) {
549
- const COMPENSATION_FACTOR = 0.25
550
- const halfSize = fftSize / 2
551
-
552
- // Get frequency indices for critical bands
553
- const freqIndices = PSYMODEL_CB_FREQ_INDICES
554
-
555
- let bandIndex = 0
556
- for (let i = 0; i <= halfSize; i++) {
557
- if (i <= freqIndices[0]) {
558
- // Before first critical band
559
- output[i] = cbThresholds[0]
560
- } else if (i >= freqIndices[freqIndices.length - 1]) {
561
- // After last critical band
562
- output[i] = cbThresholds[cbThresholds.length - 1]
563
- } else {
564
- // Find current band
565
- while (
566
- bandIndex < freqIndices.length - 1 &&
567
- freqIndices[bandIndex + 1] < i
568
- ) {
569
- bandIndex++
570
- }
571
-
572
- // Interpolate with compensation
573
- const x0 = freqIndices[bandIndex]
574
- const x1 = freqIndices[bandIndex + 1]
575
- const y0 = cbThresholds[bandIndex]
576
- const y1 = cbThresholds[bandIndex + 1]
577
- const bandWidth = x1 - x0
578
-
579
- if (bandWidth > 0) {
580
- const t = (i - x0) / bandWidth
581
- const interpolated = y0 + (y1 - y0) * t
582
- const compensation = Math.abs(y1 - y0) * COMPENSATION_FACTOR
583
- output[i] = interpolated + compensation
584
- } else {
585
- output[i] = y0
586
- }
587
- }
588
- }
589
- }