@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.17 → 1.0.18
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.
|
@@ -96,6 +96,13 @@ export declare class SpatialAudioManager extends EventManager {
|
|
|
96
96
|
private getDistanceBetween;
|
|
97
97
|
private calculateDistanceGain;
|
|
98
98
|
private normalizePositionUnits;
|
|
99
|
+
private getVectorFromListener;
|
|
100
|
+
private applyDirectionalSuppression;
|
|
101
|
+
private calculateClarityScore;
|
|
102
|
+
private calculateProximityWeight;
|
|
103
|
+
private calculateDirectionFocus;
|
|
104
|
+
private normalizeVector;
|
|
105
|
+
private clamp;
|
|
99
106
|
private isDenoiserEnabled;
|
|
100
107
|
private ensureDenoiseWorklet;
|
|
101
108
|
private resolveOptions;
|
|
@@ -69,6 +69,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
69
69
|
const panner = this.audioContext.createPanner();
|
|
70
70
|
const analyser = this.audioContext.createAnalyser();
|
|
71
71
|
const gain = this.audioContext.createGain();
|
|
72
|
+
const proximityGain = this.audioContext.createGain();
|
|
72
73
|
let denoiseNode;
|
|
73
74
|
if (this.isDenoiserEnabled() && typeof this.audioContext.audioWorklet !== "undefined") {
|
|
74
75
|
try {
|
|
@@ -102,6 +103,11 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
102
103
|
lowpassFilter.type = "lowpass";
|
|
103
104
|
lowpassFilter.frequency.value = 7500; // Below 8kHz to avoid flat/muffled sound
|
|
104
105
|
lowpassFilter.Q.value = 1.0; // Quality factor
|
|
106
|
+
const dynamicLowpass = this.audioContext.createBiquadFilter();
|
|
107
|
+
dynamicLowpass.type = "lowpass";
|
|
108
|
+
dynamicLowpass.frequency.value = 7600;
|
|
109
|
+
dynamicLowpass.Q.value = 0.8;
|
|
110
|
+
proximityGain.gain.value = 1.0;
|
|
105
111
|
// Configure Panner for realistic 3D spatial audio
|
|
106
112
|
const distanceConfig = this.getDistanceConfig();
|
|
107
113
|
panner.panningModel = "HRTF"; // Head-Related Transfer Function for realistic 3D
|
|
@@ -121,15 +127,17 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
121
127
|
}
|
|
122
128
|
currentNode.connect(highpassFilter);
|
|
123
129
|
highpassFilter.connect(lowpassFilter);
|
|
130
|
+
lowpassFilter.connect(dynamicLowpass);
|
|
131
|
+
dynamicLowpass.connect(proximityGain);
|
|
124
132
|
if (bypassSpatialization) {
|
|
125
133
|
console.log(`🔊 TESTING: Connecting audio directly to destination (bypassing spatial audio) for ${participantId}`);
|
|
126
|
-
|
|
134
|
+
proximityGain.connect(analyser);
|
|
127
135
|
analyser.connect(this.masterGainNode);
|
|
128
136
|
}
|
|
129
137
|
else {
|
|
130
138
|
// Standard spatialized path with full audio chain
|
|
131
139
|
// Audio Chain: source -> filters -> panner -> analyser -> gain -> masterGain -> compressor -> destination
|
|
132
|
-
|
|
140
|
+
proximityGain.connect(panner);
|
|
133
141
|
panner.connect(analyser);
|
|
134
142
|
analyser.connect(gain);
|
|
135
143
|
gain.connect(this.masterGainNode);
|
|
@@ -139,8 +147,10 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
139
147
|
panner,
|
|
140
148
|
analyser,
|
|
141
149
|
gain,
|
|
150
|
+
proximityGain,
|
|
142
151
|
highpassFilter,
|
|
143
152
|
lowpassFilter,
|
|
153
|
+
dynamicLowpass,
|
|
144
154
|
denoiseNode,
|
|
145
155
|
stream,
|
|
146
156
|
});
|
|
@@ -253,7 +263,9 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
253
263
|
}
|
|
254
264
|
}
|
|
255
265
|
const listenerPos = this.listenerPosition;
|
|
266
|
+
const vectorToSource = this.getVectorFromListener(targetPosition);
|
|
256
267
|
const distance = this.getDistanceBetween(listenerPos, targetPosition);
|
|
268
|
+
this.applyDirectionalSuppression(participantId, distance, vectorToSource);
|
|
257
269
|
const distanceGain = this.calculateDistanceGain(distanceConfig, distance);
|
|
258
270
|
nodes.gain.gain.setTargetAtTime(distanceGain, this.audioContext.currentTime, 0.05);
|
|
259
271
|
if (Math.random() < 0.02) {
|
|
@@ -473,6 +485,75 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
473
485
|
}
|
|
474
486
|
return { ...position };
|
|
475
487
|
}
|
|
488
|
+
getVectorFromListener(targetPosition) {
|
|
489
|
+
if (!this.listenerInitialized) {
|
|
490
|
+
return { ...targetPosition };
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
x: targetPosition.x - this.listenerPosition.x,
|
|
494
|
+
y: targetPosition.y - this.listenerPosition.y,
|
|
495
|
+
z: targetPosition.z - this.listenerPosition.z,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
applyDirectionalSuppression(participantId, distance, vectorToSource) {
|
|
499
|
+
const nodes = this.participantNodes.get(participantId);
|
|
500
|
+
if (!nodes) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const clarityScore = this.calculateClarityScore(distance, vectorToSource);
|
|
504
|
+
const targetGain = 0.55 + clarityScore * 0.6; // 0.55 → 1.15
|
|
505
|
+
const targetLowpass = 3200 + clarityScore * 4200; // 3.2kHz → ~7.4kHz
|
|
506
|
+
nodes.proximityGain.gain.setTargetAtTime(targetGain, this.audioContext.currentTime, 0.08);
|
|
507
|
+
nodes.dynamicLowpass.frequency.setTargetAtTime(targetLowpass, this.audioContext.currentTime, 0.12);
|
|
508
|
+
if (Math.random() < 0.005) {
|
|
509
|
+
console.log("🎚️ [Spatial Audio] Directional tuning", {
|
|
510
|
+
participantId,
|
|
511
|
+
distance: distance.toFixed(2),
|
|
512
|
+
clarityScore: clarityScore.toFixed(2),
|
|
513
|
+
targetGain: targetGain.toFixed(2),
|
|
514
|
+
lowpassHz: targetLowpass.toFixed(0),
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
calculateClarityScore(distance, vectorToSource) {
|
|
519
|
+
const proximityWeight = this.calculateProximityWeight(distance);
|
|
520
|
+
const focusWeight = this.calculateDirectionFocus(vectorToSource);
|
|
521
|
+
return this.clamp(0.2 + proximityWeight * 0.6 + focusWeight * 0.2, 0, 1);
|
|
522
|
+
}
|
|
523
|
+
calculateProximityWeight(distance) {
|
|
524
|
+
const closeRange = 1.2;
|
|
525
|
+
const fadeRange = 12;
|
|
526
|
+
if (distance <= closeRange) {
|
|
527
|
+
return 1;
|
|
528
|
+
}
|
|
529
|
+
if (distance >= fadeRange) {
|
|
530
|
+
return 0;
|
|
531
|
+
}
|
|
532
|
+
return 1 - (distance - closeRange) / (fadeRange - closeRange);
|
|
533
|
+
}
|
|
534
|
+
calculateDirectionFocus(vectorToSource) {
|
|
535
|
+
if (!this.listenerInitialized) {
|
|
536
|
+
return 0.5;
|
|
537
|
+
}
|
|
538
|
+
const forward = this.normalizeVector(this.listenerDirection.forward);
|
|
539
|
+
const source = this.normalizeVector(vectorToSource, { x: 0, y: 0, z: 1 });
|
|
540
|
+
const dot = forward.x * source.x + forward.y * source.y + forward.z * source.z;
|
|
541
|
+
return this.clamp((dot + 1) / 2, 0, 1);
|
|
542
|
+
}
|
|
543
|
+
normalizeVector(vector, fallback = { x: 0, y: 0, z: 1 }) {
|
|
544
|
+
const length = Math.hypot(vector.x, vector.y, vector.z);
|
|
545
|
+
if (length < 1e-4) {
|
|
546
|
+
return { ...fallback };
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
x: vector.x / length,
|
|
550
|
+
y: vector.y / length,
|
|
551
|
+
z: vector.z / length,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
clamp(value, min, max) {
|
|
555
|
+
return Math.min(max, Math.max(min, value));
|
|
556
|
+
}
|
|
476
557
|
isDenoiserEnabled() {
|
|
477
558
|
return this.options.denoiser?.enabled !== false;
|
|
478
559
|
}
|
package/package.json
CHANGED