carta1 1.0.11 → 1.0.13
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 +33 -141
- package/codec/coding/bitallocation.js +46 -13
- package/codec/core/buffers.js +0 -7
- package/codec/core/constants.js +3 -0
- package/codec/index.js +2 -0
- package/codec/pipeline/encoder.js +1 -7
- 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
|
@@ -9,12 +9,12 @@ 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
20
|
} from '../core/constants.js'
|
|
@@ -42,45 +42,33 @@ export function fromDb(db) {
|
|
|
42
42
|
/**
|
|
43
43
|
* Perform complete psychoacoustic analysis on MDCT coefficients
|
|
44
44
|
* @param {Float32Array} mdctCoeffs - MDCT coefficients (512 samples)
|
|
45
|
-
* @param {number}
|
|
46
|
-
* @param {Object} psychoBuffers - Reusable buffers for performance
|
|
45
|
+
* @param {number} normalizationDb - Target maximum power level in dB
|
|
47
46
|
* @returns {Object} Analysis results including PSD, maskers, and thresholds
|
|
48
47
|
*/
|
|
49
|
-
export function psychoAnalysis(
|
|
50
|
-
|
|
51
|
-
targetSize,
|
|
52
|
-
psychoBuffers,
|
|
53
|
-
normalizationDb
|
|
54
|
-
) {
|
|
55
|
-
const fftScale = targetSize / 512
|
|
48
|
+
export function psychoAnalysis(mdctCoeffs, normalizationDb) {
|
|
49
|
+
const fftScale = PSYMODEL_FFT_SIZE / 512
|
|
56
50
|
|
|
57
51
|
// Apply frequency band reversal correction
|
|
58
52
|
const correctedCoeffs = applyMdctCorrection(mdctCoeffs)
|
|
59
53
|
|
|
60
54
|
// Calculate power spectral density
|
|
61
|
-
const psd = calculatePSDFromMDCT(correctedCoeffs,
|
|
55
|
+
const psd = calculatePSDFromMDCT(correctedCoeffs, 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
|
|
71
|
-
const
|
|
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,
|
|
82
70
|
nonTonalMaskers,
|
|
83
|
-
|
|
71
|
+
criticalBandThresholds,
|
|
84
72
|
flags,
|
|
85
73
|
}
|
|
86
74
|
}
|
|
@@ -112,11 +100,10 @@ function applyMdctCorrection(mdctCoeffs) {
|
|
|
112
100
|
/**
|
|
113
101
|
* Calculate Power Spectral Density from MDCT coefficients
|
|
114
102
|
* @param {Float32Array} mdctCoeffs - MDCT coefficients
|
|
115
|
-
* @param {number} targetSize - Target FFT size
|
|
116
103
|
* @returns {Float32Array} Power spectral density in dB
|
|
117
104
|
*/
|
|
118
|
-
function calculatePSDFromMDCT(mdctCoeffs,
|
|
119
|
-
const halfTargetSize =
|
|
105
|
+
function calculatePSDFromMDCT(mdctCoeffs, normalizationDb) {
|
|
106
|
+
const halfTargetSize = PSYMODEL_FFT_SIZE / 2
|
|
120
107
|
const psd = new Float32Array(halfTargetSize + 1)
|
|
121
108
|
|
|
122
109
|
// Calculate power values and find maximum
|
|
@@ -132,36 +119,23 @@ function calculatePSDFromMDCT(mdctCoeffs, targetSize, normalizationDb) {
|
|
|
132
119
|
const normFactor = 1 / (maxPower || 1)
|
|
133
120
|
let maxPsdValue = -Infinity
|
|
134
121
|
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
122
|
+
// Interpolate for different sizes
|
|
123
|
+
const scaleFactor = 512 / PSYMODEL_FFT_SIZE
|
|
148
124
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
125
|
+
for (let i = 0; i <= halfTargetSize; i++) {
|
|
126
|
+
const srcIdx = i * scaleFactor
|
|
127
|
+
const srcIdxInt = Math.floor(srcIdx)
|
|
128
|
+
const frac = srcIdx - srcIdxInt
|
|
153
129
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
130
|
+
const power =
|
|
131
|
+
srcIdxInt < 511
|
|
132
|
+
? powers[srcIdxInt] * (1 - frac) + powers[srcIdxInt + 1] * frac
|
|
133
|
+
: powers[511]
|
|
158
134
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
normalizedPower > 0 ? toDb(normalizedPower) : PSYMODEL_MIN_POWER_DB
|
|
135
|
+
const normalizedPower = power * normFactor
|
|
136
|
+
psd[i] = normalizedPower > 0 ? toDb(normalizedPower) : PSYMODEL_MIN_POWER_DB
|
|
162
137
|
|
|
163
|
-
|
|
164
|
-
}
|
|
138
|
+
maxPsdValue = Math.max(maxPsdValue, psd[i])
|
|
165
139
|
}
|
|
166
140
|
|
|
167
141
|
// Apply normalization to target maximum
|
|
@@ -179,13 +153,12 @@ function calculatePSDFromMDCT(mdctCoeffs, targetSize, normalizationDb) {
|
|
|
179
153
|
/**
|
|
180
154
|
* Find all maskers (tonal and non-tonal) in the spectrum
|
|
181
155
|
* @param {Float32Array} psd - Power spectral density
|
|
182
|
-
* @param {Object} psychoBuffers - Reusable buffers
|
|
183
156
|
* @param {number} fftScale - FFT scaling factor
|
|
184
157
|
* @returns {Object} Flags array and lists of maskers
|
|
185
158
|
*/
|
|
186
|
-
function findAllMaskers(psd,
|
|
159
|
+
function findAllMaskers(psd, fftScale) {
|
|
187
160
|
// Initialize flags
|
|
188
|
-
const flags =
|
|
161
|
+
const flags = new Uint8Array(PSYMODEL_FFT_SIZE / 2)
|
|
189
162
|
flags.fill(PSYMODEL_NOT_EXAMINED)
|
|
190
163
|
|
|
191
164
|
// Find tonal maskers first
|
|
@@ -401,52 +374,18 @@ function decimateMaskers(flags, tonalList, nonTonalList) {
|
|
|
401
374
|
}
|
|
402
375
|
|
|
403
376
|
/**
|
|
404
|
-
* Calculate
|
|
377
|
+
* Calculate critical band masking thresholds
|
|
405
378
|
* @param {Float32Array} psd - Power spectral density
|
|
406
379
|
* @param {Array} tonalMaskers - List of tonal maskers
|
|
407
380
|
* @param {Array} nonTonalMaskers - List of non-tonal maskers
|
|
408
|
-
* @
|
|
409
|
-
* @param {number} fftSize - FFT size
|
|
410
|
-
* @returns {Float32Array} Global masking threshold
|
|
381
|
+
* @returns {Float32Array} Critical band thresholds
|
|
411
382
|
*/
|
|
412
|
-
function
|
|
413
|
-
psd,
|
|
414
|
-
tonalMaskers,
|
|
415
|
-
nonTonalMaskers,
|
|
416
|
-
thresholdBuffer,
|
|
417
|
-
fftSize
|
|
418
|
-
) {
|
|
383
|
+
function calculateCriticalBandThresholds(psd, tonalMaskers, nonTonalMaskers) {
|
|
419
384
|
const criticalBandThresholds = new Float32Array(
|
|
420
385
|
PSYMODEL_CRITICAL_BANDS.length
|
|
421
386
|
)
|
|
422
387
|
|
|
423
388
|
// Calculate threshold for each critical band
|
|
424
|
-
calculateCriticalBandThresholds(
|
|
425
|
-
psd,
|
|
426
|
-
tonalMaskers,
|
|
427
|
-
nonTonalMaskers,
|
|
428
|
-
criticalBandThresholds
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
// Interpolate to full spectrum with compensation
|
|
432
|
-
interpolateThresholdCompensated(
|
|
433
|
-
criticalBandThresholds,
|
|
434
|
-
thresholdBuffer,
|
|
435
|
-
fftSize
|
|
436
|
-
)
|
|
437
|
-
|
|
438
|
-
return thresholdBuffer
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Calculate masking threshold for each critical band
|
|
443
|
-
*/
|
|
444
|
-
function calculateCriticalBandThresholds(
|
|
445
|
-
psd,
|
|
446
|
-
tonalMaskers,
|
|
447
|
-
nonTonalMaskers,
|
|
448
|
-
output
|
|
449
|
-
) {
|
|
450
389
|
for (let i = 0; i < PSYMODEL_CRITICAL_BANDS.length; i++) {
|
|
451
390
|
const bandData = PSYMODEL_THRESHOLD_TABLE[PSYMODEL_CRITICAL_BANDS[i]]
|
|
452
391
|
const maskedBark = bandData[1]
|
|
@@ -470,8 +409,10 @@ function calculateCriticalBandThresholds(
|
|
|
470
409
|
false // isTonal
|
|
471
410
|
)
|
|
472
411
|
|
|
473
|
-
|
|
412
|
+
criticalBandThresholds[i] = toDb(summedEnergy)
|
|
474
413
|
}
|
|
414
|
+
|
|
415
|
+
return criticalBandThresholds
|
|
475
416
|
}
|
|
476
417
|
|
|
477
418
|
/**
|
|
@@ -538,52 +479,3 @@ function calculateMaskingLevel(
|
|
|
538
479
|
|
|
539
480
|
return maskerSpl + tonalOffset + maskingFunction
|
|
540
481
|
}
|
|
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
|
-
}
|
|
@@ -18,6 +18,9 @@ import {
|
|
|
18
18
|
SAMPLE_RATE,
|
|
19
19
|
BFU_FREQUENCIES,
|
|
20
20
|
PSYMODEL_MIN_POWER_DB,
|
|
21
|
+
PSYMODEL_CB_FREQ_INDICES,
|
|
22
|
+
INTERPOLATION_COMPENSATION_FACTOR,
|
|
23
|
+
PSYMODEL_FFT_SIZE,
|
|
21
24
|
} from '../core/constants.js'
|
|
22
25
|
|
|
23
26
|
/**
|
|
@@ -32,10 +35,10 @@ export function allocateBits(psychoResults, bfuData, bfuSizes, maxBfuCount) {
|
|
|
32
35
|
// Calculate signal energy for each BFU
|
|
33
36
|
const signalEnergyDb = calculateBfuEnergy(bfuData, bfuSizes, maxBfuCount)
|
|
34
37
|
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
psychoResults.
|
|
38
|
-
|
|
38
|
+
// Interpolate critical band thresholds to BFUs
|
|
39
|
+
const maskThresholdDb = interpolateCriticalBandsToBfus(
|
|
40
|
+
psychoResults.criticalBandThresholds
|
|
41
|
+
)
|
|
39
42
|
|
|
40
43
|
// Calculate Signal-to-Mask Ratios
|
|
41
44
|
const smrValues = calculateSMR(signalEnergyDb, maskThresholdDb, maxBfuCount)
|
|
@@ -99,25 +102,55 @@ function calculateBfuEnergy(bfuData, bfuSizes, bfuCount) {
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
/**
|
|
102
|
-
*
|
|
103
|
-
* @param {Float32Array}
|
|
105
|
+
* Interpolate critical band thresholds directly to BFU frequencies
|
|
106
|
+
* @param {Float32Array} criticalBandThresholds - Critical band thresholds
|
|
104
107
|
* @returns {Float32Array} Masking threshold per BFU
|
|
105
108
|
*/
|
|
106
|
-
function
|
|
109
|
+
function interpolateCriticalBandsToBfus(criticalBandThresholds) {
|
|
107
110
|
const maskThresholdPerBfu = new Float32Array(NUM_BFUS)
|
|
108
111
|
maskThresholdPerBfu.fill(PSYMODEL_MIN_POWER_DB)
|
|
109
112
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
const freqPerBin = SAMPLE_RATE / PSYMODEL_FFT_SIZE
|
|
114
|
+
|
|
115
|
+
// Get frequency indices for critical bands (from constants)
|
|
116
|
+
const freqIndices = PSYMODEL_CB_FREQ_INDICES
|
|
113
117
|
|
|
114
|
-
// Map each BFU's center frequency to nearest FFT bin
|
|
115
118
|
for (let i = 0; i < NUM_BFUS; i++) {
|
|
116
119
|
const bfuFreq = BFU_FREQUENCIES[i]
|
|
120
|
+
// Convert BFU frequency to FFT bin index (rounded like current implementation)
|
|
117
121
|
const fftBinIndex = Math.round(bfuFreq / freqPerBin)
|
|
118
122
|
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
// Find which critical bands surround this FFT bin
|
|
124
|
+
let bandIndex = 0
|
|
125
|
+
while (
|
|
126
|
+
bandIndex < freqIndices.length - 1 &&
|
|
127
|
+
freqIndices[bandIndex + 1] < fftBinIndex
|
|
128
|
+
) {
|
|
129
|
+
bandIndex++
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Apply same interpolation logic as interpolateThresholdCompensated
|
|
133
|
+
if (fftBinIndex <= freqIndices[0]) {
|
|
134
|
+
maskThresholdPerBfu[i] = criticalBandThresholds[0]
|
|
135
|
+
} else if (fftBinIndex >= freqIndices[freqIndices.length - 1]) {
|
|
136
|
+
maskThresholdPerBfu[i] =
|
|
137
|
+
criticalBandThresholds[criticalBandThresholds.length - 1]
|
|
138
|
+
} else {
|
|
139
|
+
const x0 = freqIndices[bandIndex]
|
|
140
|
+
const x1 = freqIndices[bandIndex + 1]
|
|
141
|
+
const y0 = criticalBandThresholds[bandIndex]
|
|
142
|
+
const y1 = criticalBandThresholds[bandIndex + 1]
|
|
143
|
+
const bandWidth = x1 - x0
|
|
144
|
+
|
|
145
|
+
if (bandWidth > 0) {
|
|
146
|
+
const t = (fftBinIndex - x0) / bandWidth
|
|
147
|
+
const interpolated = y0 + (y1 - y0) * t
|
|
148
|
+
const compensation =
|
|
149
|
+
Math.abs(y1 - y0) * INTERPOLATION_COMPENSATION_FACTOR
|
|
150
|
+
maskThresholdPerBfu[i] = interpolated + compensation
|
|
151
|
+
} else {
|
|
152
|
+
maskThresholdPerBfu[i] = y0
|
|
153
|
+
}
|
|
121
154
|
}
|
|
122
155
|
}
|
|
123
156
|
|
package/codec/core/buffers.js
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
MAX_BFU_SIZE,
|
|
8
8
|
QMF_DELAY,
|
|
9
9
|
QMF_HIGH_BAND_DELAY,
|
|
10
|
-
PSYMODEL_FFT_SIZE,
|
|
11
10
|
} from './constants.js'
|
|
12
11
|
|
|
13
12
|
export class BufferPool {
|
|
@@ -84,12 +83,6 @@ export class BufferPool {
|
|
|
84
83
|
256: new Float32Array(256),
|
|
85
84
|
}
|
|
86
85
|
|
|
87
|
-
// Psychoacoustic model buffers
|
|
88
|
-
this.psychoAnalysis = {
|
|
89
|
-
flags: new Uint8Array(PSYMODEL_FFT_SIZE / 2),
|
|
90
|
-
threshold: new Float32Array(PSYMODEL_FFT_SIZE / 2 + 1),
|
|
91
|
-
}
|
|
92
|
-
|
|
93
86
|
// Bit allocation buffers
|
|
94
87
|
this.bfuData = new Array(NUM_BFUS)
|
|
95
88
|
for (let i = 0; i < NUM_BFUS; i++) {
|
package/codec/core/constants.js
CHANGED
|
@@ -116,6 +116,9 @@ export const QMF_ODD = (() => {
|
|
|
116
116
|
return odd
|
|
117
117
|
})()
|
|
118
118
|
|
|
119
|
+
// Bit allocation
|
|
120
|
+
export const INTERPOLATION_COMPENSATION_FACTOR = 0.25
|
|
121
|
+
|
|
119
122
|
// Quantization
|
|
120
123
|
export const MAX_WORD_LENGTH_INDEX = 15
|
|
121
124
|
export const WORD_LENGTH_BITS = new Int32Array([
|
package/codec/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import { FFT } from './transforms/fft.js'
|
|
|
19
19
|
import {
|
|
20
20
|
WORD_LENGTH_BITS,
|
|
21
21
|
SPECS_PER_BFU,
|
|
22
|
+
SCALE_FACTORS,
|
|
22
23
|
BFU_START_LONG,
|
|
23
24
|
} from './core/constants.js'
|
|
24
25
|
|
|
@@ -39,5 +40,6 @@ export {
|
|
|
39
40
|
FFT,
|
|
40
41
|
WORD_LENGTH_BITS,
|
|
41
42
|
SPECS_PER_BFU,
|
|
43
|
+
SCALE_FACTORS,
|
|
42
44
|
BFU_START_LONG,
|
|
43
45
|
}
|
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
FFT_SIZE_LOW,
|
|
32
32
|
FFT_SIZE_MID,
|
|
33
33
|
FFT_SIZE_HIGH,
|
|
34
|
-
PSYMODEL_FFT_SIZE,
|
|
35
34
|
MDCT_SIZE_SHORT,
|
|
36
35
|
MDCT_SIZE_MID,
|
|
37
36
|
MDCT_SIZE_LONG,
|
|
@@ -300,12 +299,7 @@ export function quantizationStage(context) {
|
|
|
300
299
|
bufferPool.bfuData
|
|
301
300
|
)
|
|
302
301
|
|
|
303
|
-
const psychoResults = psychoAnalysis(
|
|
304
|
-
coefficients,
|
|
305
|
-
PSYMODEL_FFT_SIZE,
|
|
306
|
-
bufferPool.psychoAnalysis,
|
|
307
|
-
options.normalizationDb
|
|
308
|
-
)
|
|
302
|
+
const psychoResults = psychoAnalysis(coefficients, options.normalizationDb)
|
|
309
303
|
const { bfuCount: selectedBfuCount, allocation } = allocateBits(
|
|
310
304
|
psychoResults,
|
|
311
305
|
bfuData,
|