@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
- lowpassFilter.connect(analyser);
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
- lowpassFilter.connect(panner);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newgameplusinc/odyssey-audio-video-sdk-dev",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "Odyssey Spatial Audio & Video SDK using MediaSoup for real-time communication",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",