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
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Carta1 Audio Codec - MDCT-based Psychoacoustic Analysis
|
|
2
|
+
* Carta1 Audio Codec - MDCT-based Psychoacoustic Analysis (Optimized)
|
|
3
3
|
*
|
|
4
4
|
* This module implements the psychoacoustic model for ATRAC1 compression,
|
|
5
5
|
* analyzing audio signals to determine masking thresholds for perceptual coding.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
7
|
import {
|
|
9
8
|
PSYMODEL_MIN_POWER_DB,
|
|
10
9
|
PSYMODEL_NOT_EXAMINED,
|
|
@@ -20,12 +19,17 @@ import {
|
|
|
20
19
|
PSYMODEL_PSD_SOURCE_IDX0,
|
|
21
20
|
PSYMODEL_PSD_INTERP_WEIGHT,
|
|
22
21
|
PSYMODEL_PSD_SOURCE_IDX1,
|
|
22
|
+
PSYMODEL_TONAL_OFFSETS_LOW_FREQ,
|
|
23
|
+
PSYMODEL_TONAL_OFFSETS_MID_FREQ,
|
|
24
|
+
PSYMODEL_TONAL_OFFSETS_HIGH_FREQ,
|
|
25
|
+
PSYMODEL_HALF_FFT_SIZE,
|
|
26
|
+
PSYMODEL_NUM_BINS,
|
|
23
27
|
} from '../core/constants.js'
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
30
|
* Convert linear power value to decibels
|
|
27
31
|
* @param {number} value - Linear power value
|
|
28
|
-
* @returns {number} Power in decibels
|
|
32
|
+
* @returns {number} Power in decibels, or minimum threshold if value <= 0
|
|
29
33
|
*/
|
|
30
34
|
export function toDb(value) {
|
|
31
35
|
return value > 0
|
|
@@ -44,160 +48,287 @@ export function fromDb(db) {
|
|
|
44
48
|
|
|
45
49
|
/**
|
|
46
50
|
* Perform complete psychoacoustic analysis on MDCT coefficients
|
|
51
|
+
*
|
|
52
|
+
* This function implements the ISO/IEC 11172-3 psychoacoustic model to:
|
|
53
|
+
* 1. Calculate power spectral density from MDCT coefficients
|
|
54
|
+
* 2. Detect tonal and non-tonal maskers in the frequency domain
|
|
55
|
+
* 3. Calculate critical band masking thresholds for bit allocation
|
|
56
|
+
*
|
|
47
57
|
* @param {Float32Array} mdctCoeffs - MDCT coefficients (512 samples)
|
|
48
|
-
* @param {number} normalizationDb - Target maximum power level in dB
|
|
49
|
-
* @
|
|
58
|
+
* @param {number} normalizationDb - Target maximum power level in dB for normalization
|
|
59
|
+
* @param {Object} psychoBuffers - Psychoacoustics working buffers from buffer poool
|
|
60
|
+
* @returns {Object} Analysis results with PSD, maskers, and critical band thresholds
|
|
50
61
|
*/
|
|
51
|
-
export function psychoAnalysis(mdctCoeffs, normalizationDb) {
|
|
62
|
+
export function psychoAnalysis(mdctCoeffs, normalizationDb, psychoBuffers) {
|
|
52
63
|
const fftScale = PSYMODEL_FFT_SIZE / 512
|
|
53
64
|
|
|
54
|
-
//
|
|
55
|
-
const
|
|
65
|
+
// Create local index arrays
|
|
66
|
+
const flags = new Int8Array(PSYMODEL_HALF_FFT_SIZE + 1)
|
|
67
|
+
const tonalIdx = new Int32Array(PSYMODEL_HALF_FFT_SIZE)
|
|
68
|
+
const nonTonalIdx = new Int32Array(PSYMODEL_CRITICAL_BANDS.length)
|
|
69
|
+
const maskIsTonal = new Int8Array(PSYMODEL_HALF_FFT_SIZE)
|
|
70
|
+
|
|
71
|
+
// Calculate PSD (dB), store raw linear power and normalization scale
|
|
72
|
+
const { offsetDb } = calculatePSDFromMDCT(
|
|
73
|
+
mdctCoeffs,
|
|
74
|
+
normalizationDb,
|
|
75
|
+
psychoBuffers
|
|
76
|
+
)
|
|
56
77
|
|
|
57
78
|
// Find tonal and non-tonal maskers
|
|
58
|
-
const {
|
|
79
|
+
const { tonalCount, nonTonalCount } = findAllMaskers(
|
|
80
|
+
psychoBuffers,
|
|
81
|
+
flags,
|
|
82
|
+
tonalIdx,
|
|
83
|
+
nonTonalIdx,
|
|
84
|
+
offsetDb,
|
|
85
|
+
fftScale
|
|
86
|
+
)
|
|
59
87
|
|
|
60
88
|
// Calculate critical band masking thresholds
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
89
|
+
calculateCriticalBandThresholds(
|
|
90
|
+
psychoBuffers,
|
|
91
|
+
tonalIdx,
|
|
92
|
+
nonTonalIdx,
|
|
93
|
+
maskIsTonal,
|
|
94
|
+
tonalCount,
|
|
95
|
+
nonTonalCount
|
|
65
96
|
)
|
|
66
97
|
|
|
98
|
+
// Materialize objects only at the boundary (keep internal typed arrays)
|
|
99
|
+
const tonalMaskers = new Array(tonalCount)
|
|
100
|
+
for (let i = 0; i < tonalCount; i++) {
|
|
101
|
+
tonalMaskers[i] = { index: tonalIdx[i], spl: psychoBuffers.tonalSPL[i] }
|
|
102
|
+
}
|
|
103
|
+
const nonTonalMaskers = new Array(nonTonalCount)
|
|
104
|
+
for (let i = 0; i < nonTonalCount; i++) {
|
|
105
|
+
nonTonalMaskers[i] = {
|
|
106
|
+
index: nonTonalIdx[i],
|
|
107
|
+
spl: psychoBuffers.nonTonalSPL[i],
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
67
111
|
return {
|
|
68
|
-
psd,
|
|
112
|
+
psd: psychoBuffers.psdDb,
|
|
69
113
|
tonalMaskers,
|
|
70
|
-
tonalMaskersCount:
|
|
114
|
+
tonalMaskersCount: tonalCount,
|
|
71
115
|
nonTonalMaskers,
|
|
72
|
-
nonTonalMaskersCount:
|
|
73
|
-
criticalBandThresholds,
|
|
116
|
+
nonTonalMaskersCount: nonTonalCount,
|
|
117
|
+
criticalBandThresholds: psychoBuffers.criticalBandThresholds,
|
|
74
118
|
flags,
|
|
75
119
|
}
|
|
76
120
|
}
|
|
77
121
|
|
|
78
122
|
/**
|
|
79
123
|
* Calculate Power Spectral Density from MDCT coefficients
|
|
80
|
-
*
|
|
81
|
-
*
|
|
124
|
+
*
|
|
125
|
+
* Transforms MDCT coefficients to power spectral density using interpolation
|
|
126
|
+
* between adjacent MDCT bins to match the psychoacoustic model's FFT resolution.
|
|
127
|
+
* The output is normalized so the maximum power reaches the target level.
|
|
128
|
+
*
|
|
129
|
+
* @param {Float32Array} mdctCoeffs - Input MDCT coefficients (512 samples)
|
|
130
|
+
* @param {number} normalizationDb - Target maximum power level in dB
|
|
131
|
+
* @param {Object} psychoBuffers - Pre-allocated working buffers
|
|
132
|
+
* @returns {Object} Normalization parameters {offsetDb, linScale}
|
|
82
133
|
*/
|
|
83
|
-
function calculatePSDFromMDCT(mdctCoeffs, normalizationDb) {
|
|
84
|
-
const
|
|
85
|
-
const psd = new Float32Array(half + 1)
|
|
86
|
-
const linearPower = new Float32Array(half + 1)
|
|
134
|
+
function calculatePSDFromMDCT(mdctCoeffs, normalizationDb, psychoBuffers) {
|
|
135
|
+
const linPower = psychoBuffers.linPower
|
|
87
136
|
|
|
88
137
|
let maxLinearPower = 0
|
|
89
|
-
for (let i = 0; i <=
|
|
138
|
+
for (let i = 0; i <= PSYMODEL_HALF_FFT_SIZE; i++) {
|
|
90
139
|
const a = mdctCoeffs[PSYMODEL_PSD_SOURCE_IDX0[i]]
|
|
91
140
|
const b = mdctCoeffs[PSYMODEL_PSD_SOURCE_IDX1[i]]
|
|
141
|
+
const w = PSYMODEL_PSD_INTERP_WEIGHT[i]
|
|
92
142
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (power > maxLinearPower) {
|
|
99
|
-
maxLinearPower = power
|
|
100
|
-
}
|
|
143
|
+
// Calculate interpolated power between adjacent MDCT coefficients
|
|
144
|
+
const interpolatedPower = a * a * (1 - w) + b * b * w
|
|
145
|
+
linPower[i] = interpolatedPower
|
|
146
|
+
if (interpolatedPower > maxLinearPower) maxLinearPower = interpolatedPower
|
|
101
147
|
}
|
|
102
148
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
149
|
+
// dB offset ensures max reaches normalizationDb
|
|
150
|
+
const offsetDb =
|
|
151
|
+
normalizationDb -
|
|
152
|
+
(maxLinearPower > 0 ? PSYMODEL_LOG10_FACTOR * Math.log(maxLinearPower) : 0)
|
|
153
|
+
|
|
154
|
+
// Calculate linear scale for later fast conversions
|
|
155
|
+
const linScale = Math.exp(offsetDb * PSYMODEL_POW10_FACTOR)
|
|
156
|
+
|
|
157
|
+
// Fill PSD in dB (normalized)
|
|
158
|
+
const psdDb = psychoBuffers.psdDb
|
|
159
|
+
if (maxLinearPower > 0) {
|
|
160
|
+
for (let i = 0; i <= PSYMODEL_HALF_FFT_SIZE; i++) {
|
|
161
|
+
const p = linPower[i]
|
|
162
|
+
psdDb[i] =
|
|
163
|
+
p > 0
|
|
164
|
+
? PSYMODEL_LOG10_FACTOR * Math.log(p) + offsetDb
|
|
165
|
+
: PSYMODEL_MIN_POWER_DB
|
|
112
166
|
}
|
|
167
|
+
} else {
|
|
168
|
+
psdDb.fill(PSYMODEL_MIN_POWER_DB, 0, PSYMODEL_NUM_BINS)
|
|
113
169
|
}
|
|
114
170
|
|
|
115
|
-
return
|
|
171
|
+
return { offsetDb, linScale }
|
|
116
172
|
}
|
|
117
173
|
|
|
118
174
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
175
|
+
* Orchestrate the complete masker detection process
|
|
176
|
+
*
|
|
177
|
+
* Coordinates the detection of both tonal and non-tonal maskers, then filters
|
|
178
|
+
* out maskers that fall below the absolute hearing threshold.
|
|
179
|
+
*
|
|
180
|
+
* @param {Object} psychoBuffers - Pre-allocated working buffers
|
|
181
|
+
* @param {Int8Array} flags - Classification flags for each frequency bin
|
|
182
|
+
* @param {Int32Array} tonalIdx - Output array for tonal masker indices
|
|
183
|
+
* @param {Int32Array} nonTonalIdx - Output array for non-tonal masker indices
|
|
184
|
+
* @param {number} offsetDb - dB offset for power normalization
|
|
185
|
+
* @param {number} fftScale - Scaling factor for FFT bin mapping
|
|
186
|
+
* @returns {Object} Count of detected maskers after filtering
|
|
123
187
|
*/
|
|
124
|
-
function findAllMaskers(
|
|
125
|
-
|
|
126
|
-
|
|
188
|
+
function findAllMaskers(
|
|
189
|
+
psychoBuffers,
|
|
190
|
+
flags,
|
|
191
|
+
tonalIdx,
|
|
192
|
+
nonTonalIdx,
|
|
193
|
+
offsetDb,
|
|
194
|
+
fftScale
|
|
195
|
+
) {
|
|
127
196
|
flags.fill(PSYMODEL_NOT_EXAMINED)
|
|
128
197
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
198
|
+
const tonalCount = findTonalMaskers(
|
|
199
|
+
psychoBuffers,
|
|
200
|
+
flags,
|
|
201
|
+
tonalIdx,
|
|
202
|
+
offsetDb,
|
|
203
|
+
fftScale
|
|
204
|
+
)
|
|
205
|
+
const nonTonalCount = findNonTonalMaskers(
|
|
206
|
+
psychoBuffers,
|
|
207
|
+
flags,
|
|
208
|
+
nonTonalIdx,
|
|
209
|
+
offsetDb
|
|
210
|
+
)
|
|
134
211
|
|
|
135
|
-
|
|
136
|
-
|
|
212
|
+
const { tonal: keptTonal, nonTonal: keptNonTonal } = decimateMaskers(
|
|
213
|
+
psychoBuffers,
|
|
214
|
+
flags,
|
|
215
|
+
tonalIdx,
|
|
216
|
+
nonTonalIdx,
|
|
217
|
+
tonalCount,
|
|
218
|
+
nonTonalCount
|
|
219
|
+
)
|
|
137
220
|
|
|
138
221
|
return {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
nonTonalMaskers: decimated.nonTonalList,
|
|
222
|
+
tonalCount: keptTonal,
|
|
223
|
+
nonTonalCount: keptNonTonal,
|
|
142
224
|
}
|
|
143
225
|
}
|
|
144
226
|
|
|
145
227
|
/**
|
|
146
|
-
* Detect tonal maskers
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
228
|
+
* Detect tonal maskers using linear power sums and a global dB offset
|
|
229
|
+
*
|
|
230
|
+
* Identifies frequency bins that represent tonal components by finding local peaks
|
|
231
|
+
* that exceed neighboring bins by a sufficient margin. Uses different neighbor
|
|
232
|
+
* patterns based on frequency range for optimal detection.
|
|
233
|
+
*
|
|
234
|
+
* @param {Object} psychoBuffers - Pre-allocated working buffers
|
|
235
|
+
* @param {Int8Array} flags - Classification flags for each frequency bin
|
|
236
|
+
* @param {Int32Array} tonalIdx - Output array for tonal masker indices
|
|
237
|
+
* @param {number} offsetDb - dB offset for power normalization
|
|
238
|
+
* @param {number} fftScale - Scaling factor for FFT bin mapping
|
|
239
|
+
* @returns {number} Number of tonal maskers detected
|
|
151
240
|
*/
|
|
152
|
-
function findTonalMaskers(
|
|
153
|
-
const
|
|
241
|
+
function findTonalMaskers(psychoBuffers, flags, tonalIdx, offsetDb, fftScale) {
|
|
242
|
+
const psdDb = psychoBuffers.psdDb
|
|
243
|
+
const linPower = psychoBuffers.linPower
|
|
244
|
+
|
|
245
|
+
let count = 0
|
|
154
246
|
const maxTonalBin = Math.floor(249 * fftScale)
|
|
155
247
|
|
|
156
|
-
// Frequency range boundaries
|
|
157
248
|
const RANGE_LOW = Math.floor(63 * fftScale)
|
|
158
249
|
const RANGE_MID = Math.floor(127 * fftScale)
|
|
159
250
|
const RANGE_HIGH = Math.floor(150 * fftScale)
|
|
160
251
|
|
|
161
|
-
for (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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 {
|
|
252
|
+
for (
|
|
253
|
+
let binIndex = 1, len = psdDb.length - 1;
|
|
254
|
+
binIndex < len && binIndex <= maxTonalBin;
|
|
255
|
+
binIndex++
|
|
256
|
+
) {
|
|
257
|
+
// Check for local peak in power spectral density
|
|
258
|
+
const binPower = psdDb[binIndex]
|
|
259
|
+
if (binPower <= psdDb[binIndex - 1] || binPower < psdDb[binIndex + 1])
|
|
174
260
|
continue
|
|
175
|
-
}
|
|
176
261
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
262
|
+
// Select neighbor pattern based on frequency range
|
|
263
|
+
let neighborOffsets = null
|
|
264
|
+
if (binIndex > 2 && binIndex < RANGE_LOW)
|
|
265
|
+
neighborOffsets = PSYMODEL_TONAL_OFFSETS_LOW_FREQ
|
|
266
|
+
else if (binIndex >= RANGE_LOW && binIndex < RANGE_MID)
|
|
267
|
+
neighborOffsets = PSYMODEL_TONAL_OFFSETS_MID_FREQ
|
|
268
|
+
else if (binIndex >= RANGE_MID && binIndex < RANGE_HIGH)
|
|
269
|
+
neighborOffsets = PSYMODEL_TONAL_OFFSETS_HIGH_FREQ
|
|
270
|
+
else continue
|
|
271
|
+
|
|
272
|
+
if (isTonalWithOffsets(psdDb, binIndex, neighborOffsets)) {
|
|
273
|
+
// Calculate SPL using adjacent bins: offsetDb + 10*log10(sum of linear powers)
|
|
274
|
+
const adjacentPowerSum =
|
|
275
|
+
linPower[binIndex - 1] + linPower[binIndex] + linPower[binIndex + 1]
|
|
276
|
+
const soundPressureLevel =
|
|
277
|
+
adjacentPowerSum > 0
|
|
278
|
+
? offsetDb + PSYMODEL_LOG10_FACTOR * Math.log(adjacentPowerSum)
|
|
279
|
+
: PSYMODEL_MIN_POWER_DB
|
|
280
|
+
|
|
281
|
+
tonalIdx[count] = binIndex
|
|
282
|
+
psychoBuffers.tonalSPL[count] = soundPressureLevel
|
|
283
|
+
count++
|
|
284
|
+
|
|
285
|
+
// Mark this bin as tonal and mark neighbors as irrelevant
|
|
286
|
+
flags[binIndex] = PSYMODEL_TONAL
|
|
287
|
+
|
|
288
|
+
// Mark pattern-specific neighbors as irrelevant
|
|
289
|
+
for (let i = 0; i < neighborOffsets.length; i++) {
|
|
290
|
+
const neighborIdx = binIndex + neighborOffsets[i]
|
|
291
|
+
if (
|
|
292
|
+
neighborIdx >= 0 &&
|
|
293
|
+
neighborIdx < flags.length &&
|
|
294
|
+
neighborIdx !== binIndex
|
|
295
|
+
) {
|
|
296
|
+
flags[neighborIdx] = PSYMODEL_IRRELEVANT
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Mark immediate neighbors as irrelevant
|
|
301
|
+
if (binIndex - 1 >= 0) flags[binIndex - 1] = PSYMODEL_IRRELEVANT
|
|
302
|
+
if (binIndex + 1 < flags.length) flags[binIndex + 1] = PSYMODEL_IRRELEVANT
|
|
183
303
|
}
|
|
184
304
|
}
|
|
185
305
|
|
|
186
|
-
return
|
|
306
|
+
return count
|
|
187
307
|
}
|
|
188
308
|
|
|
189
309
|
/**
|
|
190
|
-
* Check if a
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
310
|
+
* Check if a frequency bin qualifies as tonal based on neighbor comparison
|
|
311
|
+
*
|
|
312
|
+
* A bin is considered tonal if it exceeds all specified neighbor bins
|
|
313
|
+
* by at least the tonal threshold (7 dB).
|
|
314
|
+
*
|
|
315
|
+
* @param {Float32Array} psdDb - Power spectral density in dB
|
|
316
|
+
* @param {number} binIndex - Index of the bin to test
|
|
317
|
+
* @param {Int8Array} offsets - Neighbor offset pattern to check
|
|
318
|
+
* @returns {boolean} True if the bin qualifies as tonal
|
|
195
319
|
*/
|
|
196
|
-
function
|
|
320
|
+
function isTonalWithOffsets(psdDb, binIndex, offsets) {
|
|
197
321
|
const TONAL_THRESHOLD = 7.0
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
322
|
+
const binPower = psdDb[binIndex]
|
|
323
|
+
const arrayLength = psdDb.length
|
|
324
|
+
|
|
325
|
+
for (let i = 0; i < offsets.length; i++) {
|
|
326
|
+
const neighborIndex = binIndex + offsets[i]
|
|
327
|
+
if (
|
|
328
|
+
neighborIndex >= 0 &&
|
|
329
|
+
neighborIndex < arrayLength &&
|
|
330
|
+
binPower - psdDb[neighborIndex] < TONAL_THRESHOLD
|
|
331
|
+
) {
|
|
201
332
|
return false
|
|
202
333
|
}
|
|
203
334
|
}
|
|
@@ -205,249 +336,296 @@ function isTonal(psd, k, offsets) {
|
|
|
205
336
|
}
|
|
206
337
|
|
|
207
338
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Detect non-tonal maskers (noise-like components)
|
|
225
|
-
* @param {Uint8Array} flags - Component classification flags
|
|
226
|
-
* @param {Float32Array} psd - Power spectral density
|
|
227
|
-
* @returns {Object} Updated flags and non-tonal masker list
|
|
339
|
+
* Detect non-tonal maskers per critical band using linear power aggregation
|
|
340
|
+
*
|
|
341
|
+
* Groups unexamined frequency bins by critical band and calculates the energy
|
|
342
|
+
* centroid for each band. Creates non-tonal maskers at the centroid positions
|
|
343
|
+
* to represent the noise-like spectral content.
|
|
344
|
+
*
|
|
345
|
+
* @param {Object} psychoBuffers - Pre-allocated working buffers
|
|
346
|
+
* @param {Int8Array} flags - Classification flags for each frequency bin
|
|
347
|
+
* @param {Int32Array} nonTonalIdx - Output array for non-tonal masker indices
|
|
348
|
+
* @param {number} offsetDb - dB offset for power normalization
|
|
349
|
+
* @returns {number} Number of non-tonal maskers detected
|
|
228
350
|
*/
|
|
229
|
-
function findNonTonalMaskers(flags,
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
351
|
+
function findNonTonalMaskers(psychoBuffers, flags, nonTonalIdx, offsetDb) {
|
|
352
|
+
const linPower = psychoBuffers.linPower
|
|
353
|
+
|
|
354
|
+
let count = 0
|
|
355
|
+
const cbList = PSYMODEL_CRITICAL_BANDS
|
|
356
|
+
const mapLen = PSYMODEL_FREQ_TO_CB_MAP.length
|
|
357
|
+
const flagsLen = flags.length
|
|
358
|
+
|
|
359
|
+
for (let bandIndex = 0; bandIndex < cbList.length - 1; bandIndex++) {
|
|
360
|
+
const currentBandIdx = cbList[bandIndex]
|
|
361
|
+
const bandStartBin = PSYMODEL_THRESHOLD_TABLE[currentBandIdx][0] - 1
|
|
362
|
+
const bandEndBin = PSYMODEL_THRESHOLD_TABLE[cbList[bandIndex + 1]][0] - 1
|
|
363
|
+
|
|
364
|
+
// Analyze critical band in linear power domain
|
|
365
|
+
let totalLinearPower = 0
|
|
366
|
+
let weightedLinearPower = 0
|
|
367
|
+
const baseBarkValue = PSYMODEL_THRESHOLD_TABLE[currentBandIdx][1]
|
|
368
|
+
|
|
369
|
+
const endBin = Math.min(bandEndBin, flagsLen)
|
|
370
|
+
for (let binIndex = bandStartBin; binIndex < endBin; binIndex++) {
|
|
371
|
+
if (flags[binIndex] === PSYMODEL_NOT_EXAMINED) {
|
|
372
|
+
const binPower = linPower[binIndex]
|
|
373
|
+
totalLinearPower += binPower
|
|
374
|
+
if (binIndex < mapLen) {
|
|
375
|
+
const barkValue =
|
|
376
|
+
PSYMODEL_THRESHOLD_TABLE[PSYMODEL_FREQ_TO_CB_MAP[binIndex]][1]
|
|
377
|
+
weightedLinearPower += binPower * (barkValue - baseBarkValue)
|
|
378
|
+
}
|
|
379
|
+
flags[binIndex] = PSYMODEL_IRRELEVANT
|
|
253
380
|
}
|
|
254
|
-
|
|
255
|
-
nonTonalList.push({ index, spl: bandAnalysis.powerDb })
|
|
256
|
-
flags[index] = PSYMODEL_NON_TONAL
|
|
257
381
|
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return nonTonalList
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
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
|
|
271
|
-
*/
|
|
272
|
-
function analyzeCriticalBand(psd, flags, startBin, endBin, bandIndex) {
|
|
273
|
-
let totalPower = 0
|
|
274
|
-
let weightedPower = 0
|
|
275
|
-
const baseBark = PSYMODEL_THRESHOLD_TABLE[bandIndex][1]
|
|
276
|
-
|
|
277
|
-
for (let k = startBin; k < endBin && k < flags.length; k++) {
|
|
278
|
-
if (flags[k] === PSYMODEL_NOT_EXAMINED) {
|
|
279
|
-
const power = fromDb(psd[k])
|
|
280
|
-
totalPower += power
|
|
281
|
-
|
|
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
|
-
}
|
|
287
382
|
|
|
288
|
-
|
|
383
|
+
if (totalLinearPower > 0) {
|
|
384
|
+
// Calculate energy centroid offset within the critical band
|
|
385
|
+
const centroidOffset =
|
|
386
|
+
(weightedLinearPower / totalLinearPower) * (bandEndBin - bandStartBin)
|
|
387
|
+
|
|
388
|
+
// Convert total power to dB: offsetDb + 10*log10(totalLinearPower)
|
|
389
|
+
const maskerPowerDb =
|
|
390
|
+
offsetDb + PSYMODEL_LOG10_FACTOR * Math.log(totalLinearPower)
|
|
391
|
+
|
|
392
|
+
// Place non-tonal masker near energy centroid (avoid tonal slots)
|
|
393
|
+
let maskerIndex = Math.round(bandStartBin + centroidOffset)
|
|
394
|
+
maskerIndex =
|
|
395
|
+
maskerIndex < 0
|
|
396
|
+
? 0
|
|
397
|
+
: maskerIndex >= flagsLen
|
|
398
|
+
? flagsLen - 1
|
|
399
|
+
: maskerIndex
|
|
400
|
+
if (flags[maskerIndex] === PSYMODEL_TONAL && maskerIndex + 1 < flagsLen)
|
|
401
|
+
maskerIndex++
|
|
402
|
+
|
|
403
|
+
nonTonalIdx[count] = maskerIndex
|
|
404
|
+
psychoBuffers.nonTonalSPL[count] = maskerPowerDb
|
|
405
|
+
flags[maskerIndex] = PSYMODEL_NON_TONAL
|
|
406
|
+
count++
|
|
289
407
|
}
|
|
290
408
|
}
|
|
291
409
|
|
|
292
|
-
return
|
|
293
|
-
powerDb: toDb(totalPower),
|
|
294
|
-
centroidOffset:
|
|
295
|
-
totalPower > 0 ? (weightedPower / totalPower) * (endBin - startBin) : 0,
|
|
296
|
-
}
|
|
410
|
+
return count
|
|
297
411
|
}
|
|
298
412
|
|
|
299
413
|
/**
|
|
300
|
-
* Remove weak maskers below absolute threshold
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
414
|
+
* Remove weak maskers below absolute hearing threshold
|
|
415
|
+
*
|
|
416
|
+
* Filters out maskers that fall below the absolute threshold of hearing
|
|
417
|
+
* using the psychoacoustic model's threshold table. Compacts the arrays
|
|
418
|
+
* in-place to maintain efficiency.
|
|
419
|
+
*
|
|
420
|
+
* @param {Object} psychoBuffers - Pre-allocated working buffers
|
|
421
|
+
* @param {Int8Array} flags - Classification flags for each frequency bin
|
|
422
|
+
* @param {Int32Array} tonalIdx - Array of tonal masker indices
|
|
423
|
+
* @param {Int32Array} nonTonalIdx - Array of non-tonal masker indices
|
|
424
|
+
* @param {number} tonalCount - Number of detected tonal maskers
|
|
425
|
+
* @param {number} nonTonalCount - Number of detected non-tonal maskers
|
|
426
|
+
* @returns {Object} Counts of audible maskers after filtering
|
|
305
427
|
*/
|
|
306
|
-
function decimateMaskers(
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
428
|
+
function decimateMaskers(
|
|
429
|
+
psychoBuffers,
|
|
430
|
+
flags,
|
|
431
|
+
tonalIdx,
|
|
432
|
+
nonTonalIdx,
|
|
433
|
+
tonalCount,
|
|
434
|
+
nonTonalCount
|
|
435
|
+
) {
|
|
436
|
+
const isAudible = (idx, spl) => {
|
|
437
|
+
if (idx >= PSYMODEL_FREQ_TO_CB_MAP.length) return false
|
|
438
|
+
const cb = PSYMODEL_FREQ_TO_CB_MAP[idx]
|
|
439
|
+
return spl >= PSYMODEL_THRESHOLD_TABLE[cb][2]
|
|
319
440
|
}
|
|
320
441
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
442
|
+
let keptTonal = 0
|
|
443
|
+
for (let i = 0; i < tonalCount; i++) {
|
|
444
|
+
const idx = tonalIdx[i]
|
|
445
|
+
const spl = psychoBuffers.tonalSPL[i]
|
|
446
|
+
if (isAudible(idx, spl)) {
|
|
447
|
+
// compact in-place
|
|
448
|
+
if (keptTonal !== i) {
|
|
449
|
+
tonalIdx[keptTonal] = idx
|
|
450
|
+
psychoBuffers.tonalSPL[keptTonal] = spl
|
|
451
|
+
}
|
|
452
|
+
keptTonal++
|
|
453
|
+
} else if (idx < flags.length) {
|
|
454
|
+
flags[idx] = PSYMODEL_IRRELEVANT
|
|
328
455
|
}
|
|
329
456
|
}
|
|
330
457
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
458
|
+
let keptNonTonal = 0
|
|
459
|
+
for (let i = 0; i < nonTonalCount; i++) {
|
|
460
|
+
const idx = nonTonalIdx[i]
|
|
461
|
+
const spl = psychoBuffers.nonTonalSPL[i]
|
|
462
|
+
if (isAudible(idx, spl)) {
|
|
463
|
+
if (keptNonTonal !== i) {
|
|
464
|
+
nonTonalIdx[keptNonTonal] = idx
|
|
465
|
+
psychoBuffers.nonTonalSPL[keptNonTonal] = spl
|
|
466
|
+
}
|
|
467
|
+
keptNonTonal++
|
|
468
|
+
} else if (idx < flags.length) {
|
|
469
|
+
flags[idx] = PSYMODEL_IRRELEVANT
|
|
338
470
|
}
|
|
339
471
|
}
|
|
340
472
|
|
|
341
|
-
return {
|
|
342
|
-
tonalList: audibleTonal,
|
|
343
|
-
nonTonalList: audibleNonTonal,
|
|
344
|
-
}
|
|
473
|
+
return { tonal: keptTonal, nonTonal: keptNonTonal }
|
|
345
474
|
}
|
|
346
475
|
|
|
347
476
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
477
|
+
* Precompute masker features used in threshold summation
|
|
478
|
+
*
|
|
479
|
+
* Collects all audible maskers and precomputes their bark scale values,
|
|
480
|
+
* tonal offsets, and masking function coefficients to optimize the
|
|
481
|
+
* critical band threshold calculation.
|
|
482
|
+
*
|
|
483
|
+
* @param {Object} psychoBuffers - Pre-allocated working buffers
|
|
484
|
+
* @param {Int32Array} tonalIdx - Array of tonal masker indices
|
|
485
|
+
* @param {Int32Array} nonTonalIdx - Array of non-tonal masker indices
|
|
486
|
+
* @param {Int8Array} maskIsTonal - Array indicating masker type (1=tonal, 0=non-tonal)
|
|
487
|
+
* @param {number} tonalCount - Number of audible tonal maskers
|
|
488
|
+
* @param {number} nonTonalCount - Number of audible non-tonal maskers
|
|
489
|
+
* @returns {number} Total number of maskers collected
|
|
353
490
|
*/
|
|
354
|
-
function
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
491
|
+
function collectMaskers(
|
|
492
|
+
psychoBuffers,
|
|
493
|
+
tonalIdx,
|
|
494
|
+
nonTonalIdx,
|
|
495
|
+
maskIsTonal,
|
|
496
|
+
tonalCount,
|
|
497
|
+
nonTonalCount
|
|
498
|
+
) {
|
|
499
|
+
const psd = psychoBuffers.psdDb
|
|
500
|
+
const maskSpl = psychoBuffers.maskSpl
|
|
501
|
+
const maskPsd = psychoBuffers.maskPsd
|
|
502
|
+
const maskBark = psychoBuffers.maskBark
|
|
503
|
+
const maskTonalOffset = psychoBuffers.maskTonalOffset
|
|
504
|
+
const maskPsdF1 = psychoBuffers.maskPsdF1
|
|
505
|
+
const maskPsdF2 = psychoBuffers.maskPsdF2
|
|
506
|
+
|
|
507
|
+
let maskerIndex = 0
|
|
508
|
+
|
|
509
|
+
// Process tonal maskers
|
|
510
|
+
for (let i = 0; i < tonalCount; i++) {
|
|
511
|
+
const binIndex = tonalIdx[i]
|
|
512
|
+
if (binIndex < PSYMODEL_FREQ_TO_CB_MAP.length) {
|
|
513
|
+
const barkValue =
|
|
514
|
+
PSYMODEL_THRESHOLD_TABLE[PSYMODEL_FREQ_TO_CB_MAP[binIndex]][1]
|
|
515
|
+
const soundPressureLevel = psychoBuffers.tonalSPL[i]
|
|
516
|
+
const psdValue = psd[binIndex]
|
|
517
|
+
|
|
518
|
+
maskSpl[maskerIndex] = soundPressureLevel
|
|
519
|
+
maskPsd[maskerIndex] = psdValue
|
|
520
|
+
maskBark[maskerIndex] = barkValue
|
|
521
|
+
maskIsTonal[maskerIndex] = 1
|
|
522
|
+
maskTonalOffset[maskerIndex] = -1.525 - 0.275 * barkValue - 4.5 // Tonal offset formula
|
|
523
|
+
maskPsdF1[maskerIndex] = 0.4 * psdValue + 6.0
|
|
524
|
+
maskPsdF2[maskerIndex] = 0.15 * psdValue
|
|
525
|
+
maskerIndex++
|
|
371
526
|
}
|
|
372
527
|
}
|
|
373
528
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
529
|
+
// Process non-tonal maskers
|
|
530
|
+
for (let i = 0; i < nonTonalCount; i++) {
|
|
531
|
+
const binIndex = nonTonalIdx[i]
|
|
532
|
+
if (binIndex < PSYMODEL_FREQ_TO_CB_MAP.length) {
|
|
533
|
+
const barkValue =
|
|
534
|
+
PSYMODEL_THRESHOLD_TABLE[PSYMODEL_FREQ_TO_CB_MAP[binIndex]][1]
|
|
535
|
+
const soundPressureLevel = psychoBuffers.nonTonalSPL[i]
|
|
536
|
+
const psdValue = psd[binIndex]
|
|
537
|
+
|
|
538
|
+
maskSpl[maskerIndex] = soundPressureLevel
|
|
539
|
+
maskPsd[maskerIndex] = psdValue
|
|
540
|
+
maskBark[maskerIndex] = barkValue
|
|
541
|
+
maskIsTonal[maskerIndex] = 0
|
|
542
|
+
maskTonalOffset[maskerIndex] = -1.525 - 0.175 * barkValue - 0.5 // Non-tonal offset formula
|
|
543
|
+
maskPsdF1[maskerIndex] = 0.4 * psdValue + 6.0
|
|
544
|
+
maskPsdF2[maskerIndex] = 0.15 * psdValue
|
|
545
|
+
maskerIndex++
|
|
383
546
|
}
|
|
384
547
|
}
|
|
385
548
|
|
|
386
|
-
|
|
387
|
-
for (let i = 0; i < PSYMODEL_CRITICAL_BANDS.length; i++) {
|
|
388
|
-
const bandData = PSYMODEL_THRESHOLD_TABLE[PSYMODEL_CRITICAL_BANDS[i]]
|
|
389
|
-
const maskedBark = bandData[1]
|
|
390
|
-
const quietThreshold = bandData[2]
|
|
391
|
-
|
|
392
|
-
// Start with threshold in quiet
|
|
393
|
-
let summedEnergy = fromDb(quietThreshold)
|
|
394
|
-
|
|
395
|
-
// Add contributions from all maskers
|
|
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
|
-
}
|
|
411
|
-
|
|
412
|
-
criticalBandThresholds[i] = toDb(summedEnergy)
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
return criticalBandThresholds
|
|
549
|
+
return maskerIndex
|
|
416
550
|
}
|
|
417
551
|
|
|
418
552
|
/**
|
|
419
|
-
* Calculate
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
* @
|
|
553
|
+
* Calculate critical band masking thresholds using precomputed masker features
|
|
554
|
+
*
|
|
555
|
+
* Computes the masking threshold for each critical band by summing the
|
|
556
|
+
* contributions from all maskers, taking into account the masking function
|
|
557
|
+
* shape and bark scale distances.
|
|
558
|
+
*
|
|
559
|
+
* @param {Object} psychoBuffers - Pre-allocated working buffers
|
|
560
|
+
* @param {Int32Array} tonalIdx - Array of tonal masker indices
|
|
561
|
+
* @param {Int32Array} nonTonalIdx - Array of non-tonal masker indices
|
|
562
|
+
* @param {Int8Array} maskIsTonal - Array indicating masker type
|
|
563
|
+
* @param {number} tonalCount - Number of audible tonal maskers
|
|
564
|
+
* @param {number} nonTonalCount - Number of audible non-tonal maskers
|
|
426
565
|
*/
|
|
427
|
-
function
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
566
|
+
function calculateCriticalBandThresholds(
|
|
567
|
+
psychoBuffers,
|
|
568
|
+
tonalIdx,
|
|
569
|
+
nonTonalIdx,
|
|
570
|
+
maskIsTonal,
|
|
571
|
+
tonalCount,
|
|
572
|
+
nonTonalCount
|
|
433
573
|
) {
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
574
|
+
const cbList = PSYMODEL_CRITICAL_BANDS
|
|
575
|
+
const thresholds = psychoBuffers.criticalBandThresholds
|
|
576
|
+
|
|
577
|
+
const maskersCount = collectMaskers(
|
|
578
|
+
psychoBuffers,
|
|
579
|
+
tonalIdx,
|
|
580
|
+
nonTonalIdx,
|
|
581
|
+
maskIsTonal,
|
|
582
|
+
tonalCount,
|
|
583
|
+
nonTonalCount
|
|
584
|
+
)
|
|
585
|
+
const maskSpl = psychoBuffers.maskSpl
|
|
586
|
+
const maskBark = psychoBuffers.maskBark
|
|
587
|
+
const maskTonalOffset = psychoBuffers.maskTonalOffset
|
|
588
|
+
const maskPsdF1 = psychoBuffers.maskPsdF1
|
|
589
|
+
const maskPsdF2 = psychoBuffers.maskPsdF2
|
|
590
|
+
|
|
591
|
+
// Calculate masking threshold for each critical band
|
|
592
|
+
for (let bandIndex = 0; bandIndex < cbList.length; bandIndex++) {
|
|
593
|
+
const bandData = PSYMODEL_THRESHOLD_TABLE[cbList[bandIndex]]
|
|
594
|
+
const maskedBarkValue = bandData[1]
|
|
595
|
+
const absoluteThreshold = bandData[2]
|
|
596
|
+
|
|
597
|
+
// Start with absolute threshold of hearing (quiet threshold)
|
|
598
|
+
let totalLinearEnergy = fromDb(absoluteThreshold)
|
|
599
|
+
|
|
600
|
+
// Sum contributions from all maskers
|
|
601
|
+
for (let maskerIdx = 0; maskerIdx < maskersCount; maskerIdx++) {
|
|
602
|
+
const barkDistance = maskedBarkValue - maskBark[maskerIdx]
|
|
603
|
+
|
|
604
|
+
// Check if masker is within effective masking range
|
|
605
|
+
if (barkDistance >= -3 && barkDistance < 8) {
|
|
606
|
+
// Calculate piecewise masking function using precomputed terms
|
|
607
|
+
const f1 = maskPsdF1[maskerIdx]
|
|
608
|
+
const f2 = maskPsdF2[maskerIdx]
|
|
609
|
+
let maskingFunction
|
|
610
|
+
|
|
611
|
+
if (barkDistance < -1.0) {
|
|
612
|
+
maskingFunction = 17.0 * (barkDistance + 1.0) - f1
|
|
613
|
+
} else if (barkDistance < 0.0) {
|
|
614
|
+
maskingFunction = f1 * barkDistance
|
|
615
|
+
} else if (barkDistance < 1.0) {
|
|
616
|
+
maskingFunction = -17.0 * barkDistance
|
|
617
|
+
} else {
|
|
618
|
+
maskingFunction = -(barkDistance - 1.0) * (17.0 - f2) - 17.0
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Calculate final masked threshold and add to total
|
|
622
|
+
const maskedThresholdDb =
|
|
623
|
+
maskSpl[maskerIdx] + maskTonalOffset[maskerIdx] + maskingFunction
|
|
624
|
+
totalLinearEnergy += fromDb(maskedThresholdDb)
|
|
625
|
+
}
|
|
626
|
+
}
|
|
451
627
|
|
|
452
|
-
|
|
628
|
+
// Convert back to dB and store final threshold
|
|
629
|
+
thresholds[bandIndex] = toDb(totalLinearEnergy)
|
|
630
|
+
}
|
|
453
631
|
}
|