carta1 1.0.21 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/codec/browser/interface.js +0 -1
- package/codec/coding/bitallocation.js +205 -303
- package/codec/coding/quantization.js +79 -78
- package/codec/core/constants.js +53 -153
- package/codec/core/options.js +0 -13
- package/codec/pipeline/decoder.js +23 -3
- package/codec/pipeline/encoder.js +37 -39
- package/dist/carta1-worker-interface.min.js.map +1 -1
- package/dist/carta1-worker.min.js +1 -1
- package/dist/carta1-worker.min.js.map +1 -1
- package/dist/carta1.min.js +1 -1
- package/dist/carta1.min.js.map +1 -1
- package/package.json +1 -1
- package/codec/analysis/psychoacoustics.js +0 -453
package/package.json
CHANGED
|
@@ -1,453 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Carta1 Audio Codec - MDCT-based Psychoacoustic Analysis
|
|
3
|
-
*
|
|
4
|
-
* This module implements the psychoacoustic model for ATRAC1 compression,
|
|
5
|
-
* analyzing audio signals to determine masking thresholds for perceptual coding.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
PSYMODEL_MIN_POWER_DB,
|
|
10
|
-
PSYMODEL_NOT_EXAMINED,
|
|
11
|
-
PSYMODEL_TONAL,
|
|
12
|
-
PSYMODEL_FFT_SIZE,
|
|
13
|
-
PSYMODEL_NON_TONAL,
|
|
14
|
-
PSYMODEL_IRRELEVANT,
|
|
15
|
-
PSYMODEL_LOG10_FACTOR,
|
|
16
|
-
PSYMODEL_POW10_FACTOR,
|
|
17
|
-
PSYMODEL_CRITICAL_BANDS,
|
|
18
|
-
PSYMODEL_THRESHOLD_TABLE,
|
|
19
|
-
PSYMODEL_FREQ_TO_CB_MAP,
|
|
20
|
-
PSYMODEL_PSD_SOURCE_IDX0,
|
|
21
|
-
PSYMODEL_PSD_INTERP_WEIGHT,
|
|
22
|
-
PSYMODEL_PSD_SOURCE_IDX1,
|
|
23
|
-
} from '../core/constants.js'
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Convert linear power value to decibels
|
|
27
|
-
* @param {number} value - Linear power value
|
|
28
|
-
* @returns {number} Power in decibels
|
|
29
|
-
*/
|
|
30
|
-
export function toDb(value) {
|
|
31
|
-
return value > 0
|
|
32
|
-
? PSYMODEL_LOG10_FACTOR * Math.log(value)
|
|
33
|
-
: PSYMODEL_MIN_POWER_DB
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Convert decibels to linear power value
|
|
38
|
-
* @param {number} db - Power in decibels
|
|
39
|
-
* @returns {number} Linear power value
|
|
40
|
-
*/
|
|
41
|
-
export function fromDb(db) {
|
|
42
|
-
return Math.exp(db * PSYMODEL_POW10_FACTOR)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Perform complete psychoacoustic analysis on MDCT coefficients
|
|
47
|
-
* @param {Float32Array} mdctCoeffs - MDCT coefficients (512 samples)
|
|
48
|
-
* @param {number} normalizationDb - Target maximum power level in dB
|
|
49
|
-
* @returns {Object} Analysis results including PSD, maskers, and thresholds
|
|
50
|
-
*/
|
|
51
|
-
export function psychoAnalysis(mdctCoeffs, normalizationDb) {
|
|
52
|
-
const fftScale = PSYMODEL_FFT_SIZE / 512
|
|
53
|
-
|
|
54
|
-
// Calculate power spectral density
|
|
55
|
-
const psd = calculatePSDFromMDCT(mdctCoeffs, normalizationDb)
|
|
56
|
-
|
|
57
|
-
// Find tonal and non-tonal maskers
|
|
58
|
-
const { flags, tonalMaskers, nonTonalMaskers } = findAllMaskers(psd, fftScale)
|
|
59
|
-
|
|
60
|
-
// Calculate critical band masking thresholds
|
|
61
|
-
const criticalBandThresholds = calculateCriticalBandThresholds(
|
|
62
|
-
psd,
|
|
63
|
-
tonalMaskers,
|
|
64
|
-
nonTonalMaskers
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
psd,
|
|
69
|
-
tonalMaskers,
|
|
70
|
-
tonalMaskersCount: tonalMaskers.length,
|
|
71
|
-
nonTonalMaskers,
|
|
72
|
-
nonTonalMaskersCount: nonTonalMaskers.length,
|
|
73
|
-
criticalBandThresholds,
|
|
74
|
-
flags,
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Calculate Power Spectral Density from MDCT coefficients
|
|
80
|
-
* @param {Float32Array} mdctCoeffs - MDCT coefficients
|
|
81
|
-
* @returns {Float32Array} Power spectral density in dB
|
|
82
|
-
*/
|
|
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
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const offset =
|
|
104
|
-
normalizationDb - PSYMODEL_LOG10_FACTOR * Math.log(maxLinearPower || 1)
|
|
105
|
-
|
|
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
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return psd
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Find all maskers (tonal and non-tonal) in the spectrum
|
|
120
|
-
* @param {Float32Array} psd - Power spectral density
|
|
121
|
-
* @param {number} fftScale - FFT scaling factor
|
|
122
|
-
* @returns {Object} Flags array and lists of maskers
|
|
123
|
-
*/
|
|
124
|
-
function findAllMaskers(psd, fftScale) {
|
|
125
|
-
// Initialize flags
|
|
126
|
-
const flags = new Uint8Array(PSYMODEL_FFT_SIZE / 2)
|
|
127
|
-
flags.fill(PSYMODEL_NOT_EXAMINED)
|
|
128
|
-
|
|
129
|
-
// Find tonal maskers first
|
|
130
|
-
const tonalList = findTonalMaskers(psd, flags, fftScale)
|
|
131
|
-
|
|
132
|
-
// Find non-tonal maskers
|
|
133
|
-
const nonTonalList = findNonTonalMaskers(flags, psd)
|
|
134
|
-
|
|
135
|
-
// Apply decimation to remove weak maskers
|
|
136
|
-
const decimated = decimateMaskers(flags, tonalList, nonTonalList)
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
flags,
|
|
140
|
-
tonalMaskers: decimated.tonalList,
|
|
141
|
-
nonTonalMaskers: decimated.nonTonalList,
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Detect tonal maskers (peaks in spectrum)
|
|
147
|
-
* @param {Float32Array} psd - Power spectral density
|
|
148
|
-
* @param {Uint8Array} flags - Component classification flags
|
|
149
|
-
* @param {number} fftScale - FFT scaling factor
|
|
150
|
-
* @returns {Object} Updated flags and tonal masker list
|
|
151
|
-
*/
|
|
152
|
-
function findTonalMaskers(psd, flags, fftScale) {
|
|
153
|
-
const tonalList = []
|
|
154
|
-
const maxTonalBin = Math.floor(249 * fftScale)
|
|
155
|
-
|
|
156
|
-
// Frequency range boundaries
|
|
157
|
-
const RANGE_LOW = Math.floor(63 * fftScale)
|
|
158
|
-
const RANGE_MID = Math.floor(127 * fftScale)
|
|
159
|
-
const RANGE_HIGH = Math.floor(150 * fftScale)
|
|
160
|
-
|
|
161
|
-
for (let k = 1; k < psd.length - 1 && k <= maxTonalBin; k++) {
|
|
162
|
-
// Check for local maximum
|
|
163
|
-
if (psd[k] <= psd[k - 1] || psd[k] < psd[k + 1]) continue
|
|
164
|
-
|
|
165
|
-
// Determine search range based on frequency
|
|
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
|
-
}
|
|
176
|
-
|
|
177
|
-
// Check tonality criteria
|
|
178
|
-
if (isTonal(psd, k, searchOffsets)) {
|
|
179
|
-
// Calculate combined SPL
|
|
180
|
-
const spl = toDb(fromDb(psd[k - 1]) + fromDb(psd[k]) + fromDb(psd[k + 1]))
|
|
181
|
-
tonalList.push({ index: k, spl })
|
|
182
|
-
markTonalComponent(flags, k, searchOffsets)
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return tonalList
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
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
|
|
195
|
-
*/
|
|
196
|
-
function isTonal(psd, k, offsets) {
|
|
197
|
-
const TONAL_THRESHOLD = 7.0
|
|
198
|
-
for (const offset of offsets) {
|
|
199
|
-
const idx = k + offset
|
|
200
|
-
if (idx >= 0 && idx < psd.length && psd[k] - psd[idx] < TONAL_THRESHOLD) {
|
|
201
|
-
return false
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return true
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
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
|
|
212
|
-
*/
|
|
213
|
-
function markTonalComponent(flags, k, searchOffsets) {
|
|
214
|
-
flags[k] = PSYMODEL_TONAL
|
|
215
|
-
for (const offset of [...searchOffsets, -1, 1]) {
|
|
216
|
-
const idx = k + offset
|
|
217
|
-
if (idx >= 0 && idx < flags.length && idx !== k) {
|
|
218
|
-
flags[idx] = PSYMODEL_IRRELEVANT
|
|
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
|
|
228
|
-
*/
|
|
229
|
-
function findNonTonalMaskers(flags, psd) {
|
|
230
|
-
const nonTonalList = []
|
|
231
|
-
|
|
232
|
-
for (let i = 0; i < PSYMODEL_CRITICAL_BANDS.length - 1; i++) {
|
|
233
|
-
const cbStart = PSYMODEL_THRESHOLD_TABLE[PSYMODEL_CRITICAL_BANDS[i]][0] - 1
|
|
234
|
-
const cbEnd =
|
|
235
|
-
PSYMODEL_THRESHOLD_TABLE[PSYMODEL_CRITICAL_BANDS[i + 1]][0] - 1
|
|
236
|
-
|
|
237
|
-
const bandAnalysis = analyzeCriticalBand(
|
|
238
|
-
psd,
|
|
239
|
-
flags,
|
|
240
|
-
cbStart,
|
|
241
|
-
cbEnd,
|
|
242
|
-
PSYMODEL_CRITICAL_BANDS[i]
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
if (bandAnalysis.powerDb > PSYMODEL_MIN_POWER_DB) {
|
|
246
|
-
// Place masker at energy centroid
|
|
247
|
-
let index = Math.round(cbStart + bandAnalysis.centroidOffset)
|
|
248
|
-
index = Math.max(0, Math.min(index, flags.length - 1))
|
|
249
|
-
|
|
250
|
-
// Avoid conflict with tonal maskers
|
|
251
|
-
if (flags[index] === PSYMODEL_TONAL) {
|
|
252
|
-
index = Math.min(index + 1, flags.length - 1)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
nonTonalList.push({ index, spl: bandAnalysis.powerDb })
|
|
256
|
-
flags[index] = PSYMODEL_NON_TONAL
|
|
257
|
-
}
|
|
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
|
-
|
|
288
|
-
flags[k] = PSYMODEL_IRRELEVANT
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return {
|
|
293
|
-
powerDb: toDb(totalPower),
|
|
294
|
-
centroidOffset:
|
|
295
|
-
totalPower > 0 ? (weightedPower / totalPower) * (endBin - startBin) : 0,
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Remove weak maskers below absolute threshold
|
|
301
|
-
* @param {Uint8Array} flags - Component classification flags
|
|
302
|
-
* @param {Array} tonalList - List of tonal maskers
|
|
303
|
-
* @param {Array} nonTonalList - List of non-tonal maskers
|
|
304
|
-
* @returns {Object} Filtered masker lists
|
|
305
|
-
*/
|
|
306
|
-
function decimateMaskers(flags, tonalList, nonTonalList) {
|
|
307
|
-
const isAudible = (masker) => {
|
|
308
|
-
const k = masker.index
|
|
309
|
-
return (
|
|
310
|
-
k < PSYMODEL_FREQ_TO_CB_MAP.length &&
|
|
311
|
-
masker.spl >= PSYMODEL_THRESHOLD_TABLE[PSYMODEL_FREQ_TO_CB_MAP[k]][2]
|
|
312
|
-
)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const markInaudible = (masker) => {
|
|
316
|
-
if (masker.index < flags.length) {
|
|
317
|
-
flags[masker.index] = PSYMODEL_IRRELEVANT
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Filter tonal maskers
|
|
322
|
-
const audibleTonal = []
|
|
323
|
-
for (const tonal of tonalList) {
|
|
324
|
-
if (isAudible(tonal)) {
|
|
325
|
-
audibleTonal.push(tonal)
|
|
326
|
-
} else {
|
|
327
|
-
markInaudible(tonal)
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Filter non-tonal maskers
|
|
332
|
-
const audibleNonTonal = []
|
|
333
|
-
for (const nonTonal of nonTonalList) {
|
|
334
|
-
if (isAudible(nonTonal)) {
|
|
335
|
-
audibleNonTonal.push(nonTonal)
|
|
336
|
-
} else {
|
|
337
|
-
markInaudible(nonTonal)
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return {
|
|
342
|
-
tonalList: audibleTonal,
|
|
343
|
-
nonTonalList: audibleNonTonal,
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Calculate critical band masking thresholds
|
|
349
|
-
* @param {Float32Array} psd - Power spectral density
|
|
350
|
-
* @param {Array} tonalMaskers - List of tonal maskers
|
|
351
|
-
* @param {Array} nonTonalMaskers - List of non-tonal maskers
|
|
352
|
-
* @returns {Float32Array} Critical band thresholds
|
|
353
|
-
*/
|
|
354
|
-
function calculateCriticalBandThresholds(psd, tonalMaskers, nonTonalMaskers) {
|
|
355
|
-
const criticalBandThresholds = new Float32Array(
|
|
356
|
-
PSYMODEL_CRITICAL_BANDS.length
|
|
357
|
-
)
|
|
358
|
-
|
|
359
|
-
// Pre-collect all maskers with their properties
|
|
360
|
-
const maskers = []
|
|
361
|
-
|
|
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
|
-
}
|
|
373
|
-
|
|
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
|
-
}
|
|
385
|
-
|
|
386
|
-
// Calculate threshold for each critical band
|
|
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
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
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
|
|
426
|
-
*/
|
|
427
|
-
function calculateMaskingLevel(
|
|
428
|
-
maskerSpl,
|
|
429
|
-
maskerPsd,
|
|
430
|
-
maskerBark,
|
|
431
|
-
barkDistance,
|
|
432
|
-
isTonal
|
|
433
|
-
) {
|
|
434
|
-
const tonalOffset = isTonal
|
|
435
|
-
? -1.525 - 0.275 * maskerBark - 4.5
|
|
436
|
-
: -1.525 - 0.175 * maskerBark - 0.5
|
|
437
|
-
|
|
438
|
-
const psdFactor1 = 0.4 * maskerPsd + 6.0
|
|
439
|
-
const psdFactor2 = 0.15 * maskerPsd
|
|
440
|
-
|
|
441
|
-
let maskingFunction
|
|
442
|
-
if (barkDistance < -1.0) {
|
|
443
|
-
maskingFunction = 17.0 * (barkDistance + 1.0) - psdFactor1
|
|
444
|
-
} else if (barkDistance < 0.0) {
|
|
445
|
-
maskingFunction = psdFactor1 * barkDistance
|
|
446
|
-
} else if (barkDistance < 1.0) {
|
|
447
|
-
maskingFunction = -17.0 * barkDistance
|
|
448
|
-
} else {
|
|
449
|
-
maskingFunction = -(barkDistance - 1.0) * (17.0 - psdFactor2) - 17.0
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
return maskerSpl + tonalOffset + maskingFunction
|
|
453
|
-
}
|