@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.256 → 1.0.258

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.
@@ -58,10 +58,8 @@ export declare class SpatialAudioManager extends EventManager {
58
58
  private panCenterDeadZone;
59
59
  private cachedSpeakerPositions;
60
60
  private cachedListenerPosition;
61
+ private listenerPositionInitialized;
61
62
  private positionSnapThreshold;
62
- private cachedGainValues;
63
- private gainChangeThreshold;
64
- private minimumStableGain;
65
63
  private _listenerDebugLogTime?;
66
64
  private mlSuppressor;
67
65
  private mlModelReady;
@@ -42,19 +42,11 @@ class SpatialAudioManager extends EventManager_1.EventManager {
42
42
  this.cachedSpeakerPositions = new Map();
43
43
  // Cache for last snapped listener position
44
44
  this.cachedListenerPosition = { x: 0, y: 0, z: 0 };
45
+ // Track if listener position has been set at least once
46
+ this.listenerPositionInitialized = false;
45
47
  // Minimum position change (in meters) to trigger recalculation
46
48
  // 0.30m = 30cm - ignores pixel streaming jitter, physics wobble, breathing
47
- // ALIGNED with server-side 100cm filter - this catches smaller jitter
48
49
  this.positionSnapThreshold = 0.30;
49
- // GAIN STABILIZATION: Prevents gain fluctuations when distance is stable
50
- // Caches last calculated gain value for each participant
51
- this.cachedGainValues = new Map();
52
- // Minimum gain change (0-1 scale) to trigger update
53
- // 0.08 = 8% change required - filters out tiny distance jitter
54
- this.gainChangeThreshold = 0.08;
55
- // Minimum stable gain floor - never go below this when participant is audible
56
- // This ensures audio is always clearly audible when within max distance
57
- this.minimumStableGain = 0.15; // 15% minimum when within range
58
50
  // NOTE: Rate limiting variables removed - setTargetAtTime provides sufficient smoothing
59
51
  // The smoothPanValue() and position snapping handle jitter reduction
60
52
  // ML Noise Suppressor (TensorFlow.js-based)
@@ -390,25 +382,20 @@ class SpatialAudioManager extends EventManager_1.EventManager {
390
382
  // SNAP: Reduce jitter by ignoring micro-movements (<15cm)
391
383
  const snappedSpeakerPos = this.snapPosition(normalizedBodyPosition, participantId);
392
384
  const speakerHeadPosition = this.computeHeadPosition(snappedSpeakerPos);
393
- // SNAP: Use cached listener position to reduce jitter
394
- const listenerPos = this.cachedListenerPosition.x !== 0 || this.cachedListenerPosition.z !== 0
385
+ // Use cached listener position if initialized, otherwise use current position
386
+ const listenerPos = this.listenerPositionInitialized
395
387
  ? this.cachedListenerPosition
396
388
  : this.listenerPosition;
397
- // DEBUG: Log positions for troubleshooting distance issues
398
- const distX = speakerHeadPosition.x - listenerPos.x;
399
- const distY = speakerHeadPosition.y - listenerPos.y;
400
- const distZ = speakerHeadPosition.z - listenerPos.z;
401
389
  // Step 2: Calculate 3D distance (Euclidean distance from datum-based positions)
402
390
  // distance = √(Δx² + Δy² + Δz²)
403
391
  const distance = this.getDistanceBetween(listenerPos, speakerHeadPosition);
404
- // HARD CUTOFF: Fade out completely if beyond max distance (15m)
405
- // This prevents any audio processing for distant participants
406
- const maxDistance = 15.0;
407
- if (distance >= maxDistance) {
408
- // Smooth fade to zero - prevents click (100ms fade)
409
- nodes.gain.gain.setTargetAtTime(0, this.audioContext.currentTime, 0.033);
410
- this.cachedGainValues.set(participantId, 0);
411
- return; // Skip all other processing
392
+ // DEBUG: Log distance and gain every 2 seconds
393
+ const now = Date.now();
394
+ const lastLog = this._spatialDebugTimes.get(participantId) || 0;
395
+ if (now - lastLog > 2000) {
396
+ this._spatialDebugTimes.set(participantId, now);
397
+ const calculatedGainDebug = this.calculateLogarithmicGain(distance);
398
+ console.log(`🔊 SPATIAL DEBUG [${participantId.substring(0, 8)}]: dist=${distance.toFixed(2)}m, gain=${calculatedGainDebug}%, listenerInit=${this.listenerPositionInitialized}, listener=(${listenerPos.x.toFixed(1)},${listenerPos.y.toFixed(1)},${listenerPos.z.toFixed(1)}), speaker=(${speakerHeadPosition.x.toFixed(1)},${speakerHeadPosition.y.toFixed(1)},${speakerHeadPosition.z.toFixed(1)})`);
412
399
  }
413
400
  // Step 3: Calculate relative vector (speaker relative to listener)
414
401
  // vecToSource = speaker.pos - listener.pos
@@ -441,50 +428,22 @@ class SpatialAudioManager extends EventManager_1.EventManager {
441
428
  const panning = this.panningFromPanValue(smoothedPanValue, dxLocal);
442
429
  // Calculate gain based on distance
443
430
  const calculatedGain = this.calculateLogarithmicGain(distance);
444
- let newGainValue = calculatedGain / 100; // Convert to 0-1 range
445
- // Apply minimum stable gain floor - ensures audio is always clearly audible
446
- // when within max distance (15m). This prevents "too quiet" audio.
447
- newGainValue = Math.max(newGainValue, this.minimumStableGain);
448
- // GAIN STABILIZATION: Only update gain if change exceeds threshold
449
- // This prevents audio fluctuations when users are stationary
450
- const cachedGain = this.cachedGainValues.get(participantId);
451
- let finalGainValue = newGainValue;
452
- if (cachedGain !== undefined && cachedGain > 0) {
453
- const gainChange = Math.abs(newGainValue - cachedGain);
454
- // HYSTERESIS: Use different thresholds for increasing vs decreasing gain
455
- // This prevents oscillation around threshold boundaries
456
- const isIncreasing = newGainValue > cachedGain;
457
- const effectiveThreshold = isIncreasing
458
- ? this.gainChangeThreshold * 0.8 // Easier to increase (respond to getting closer)
459
- : this.gainChangeThreshold * 1.2; // Harder to decrease (resist getting quieter)
460
- if (gainChange < effectiveThreshold) {
461
- // Change too small - keep cached value for stability
462
- finalGainValue = cachedGain;
463
- }
464
- else {
465
- // Significant change - use weighted average for smooth transition
466
- // Blend 70% new value with 30% cached value
467
- finalGainValue = newGainValue * 0.7 + cachedGain * 0.3;
468
- this.cachedGainValues.set(participantId, finalGainValue);
469
- }
470
- }
471
- else {
472
- // First time or was muted - cache the value
473
- this.cachedGainValues.set(participantId, newGainValue);
474
- }
431
+ const gainValue = calculatedGain / 100; // Convert to 0-1 range
432
+ // DEBUG: Log RSD processing in SDK with ALL data for matching with Vue
433
+ console.log(`[SDK-RSD] id=${participantId.substring(0, 8)} | speakerPos=(${speakerHeadPosition.x.toFixed(1)},${speakerHeadPosition.y.toFixed(1)},${speakerHeadPosition.z.toFixed(1)}) | listenerPos=(${listenerPos.x.toFixed(1)},${listenerPos.y.toFixed(1)},${listenerPos.z.toFixed(1)}) | dist=${distance.toFixed(2)}m | gain=${(gainValue * 100).toFixed(0)}% | pan=${smoothedPanValue.toFixed(2)} (L:${panning.left.toFixed(0)}% R:${panning.right.toFixed(0)}%)`);
475
434
  // Apply panning
476
435
  this.applyStereoPanning(participantId, panning);
477
436
  // Apply gain with Web Audio's built-in smoothing (setTargetAtTime)
437
+ // This provides all the smoothing needed - no additional caching required
478
438
  const currentTime = this.audioContext.currentTime;
479
439
  try {
480
440
  // setTargetAtTime provides smooth exponential interpolation
481
- // Time constant 0.25 = ~750ms to settle (very smooth transitions)
482
- // Longer time constant prevents audible gain jumps
483
- nodes.gain.gain.setTargetAtTime(finalGainValue, currentTime, 0.25);
441
+ // Time constant 0.1 = ~300ms to settle
442
+ nodes.gain.gain.setTargetAtTime(gainValue, currentTime, 0.1);
484
443
  }
485
444
  catch (err) {
486
445
  // Fallback: If scheduling fails, set value directly (rare edge case)
487
- nodes.gain.gain.value = finalGainValue;
446
+ nodes.gain.gain.value = gainValue;
488
447
  }
489
448
  }
490
449
  }
@@ -504,8 +463,6 @@ class SpatialAudioManager extends EventManager_1.EventManager {
504
463
  // Smooth ramp to 0 (muted) or 1 (unmuted) - prevents click
505
464
  // Time constant 0.05 = ~150ms to reach target (3 time constants)
506
465
  nodes.gain.gain.setTargetAtTime(muted ? 0 : 1, this.audioContext.currentTime, 0.05);
507
- // Update cached gain value when muting/unmuting
508
- this.cachedGainValues.set(participantId, muted ? 0 : 1);
509
466
  }
510
467
  }
511
468
  /**
@@ -548,6 +505,8 @@ class SpatialAudioManager extends EventManager_1.EventManager {
548
505
  * @param rot Rotation data (pitch, yaw, roll) - stored but not used for panning
549
506
  */
550
507
  setListenerFromLSD(listenerPos, cameraPos, lookAtPos, rot, localParticipantId) {
508
+ // DEBUG: Log LSD data received in SDK
509
+ console.log(`[SDK-LSD] cam=(${cameraPos.x.toFixed(1)},${cameraPos.y.toFixed(1)},${cameraPos.z.toFixed(1)}) rot=(${rot?.x?.toFixed(1)},${rot?.y?.toFixed(1)},${rot?.z?.toFixed(1)})`);
551
510
  // Store local participant ID for logging
552
511
  if (localParticipantId && !this.localParticipantId) {
553
512
  this.localParticipantId = localParticipantId;
@@ -701,8 +660,6 @@ class SpatialAudioManager extends EventManager_1.EventManager {
701
660
  this.participantNodes.delete(participantId);
702
661
  // Clean up smoothed pan value tracking
703
662
  this.smoothedPanValues.delete(participantId);
704
- // Clean up cached gain values
705
- this.cachedGainValues.delete(participantId);
706
663
  // Clean up cached speaker position
707
664
  this.cachedSpeakerPositions.delete(participantId);
708
665
  }
@@ -796,10 +753,12 @@ class SpatialAudioManager extends EventManager_1.EventManager {
796
753
  const cached = isListener
797
754
  ? this.cachedListenerPosition
798
755
  : this.cachedSpeakerPositions.get(participantId);
799
- // If no cached position, use this one as the baseline
800
- if (!cached || (cached.x === 0 && cached.y === 0 && cached.z === 0)) {
756
+ // If no cached position or first time, use this one as the baseline
757
+ const isFirstTime = !cached || (!this.listenerPositionInitialized && isListener);
758
+ if (isFirstTime) {
801
759
  if (isListener) {
802
760
  this.cachedListenerPosition = { ...position };
761
+ this.listenerPositionInitialized = true;
803
762
  }
804
763
  else {
805
764
  this.cachedSpeakerPositions.set(participantId, { ...position });
@@ -1087,9 +1046,9 @@ class SpatialAudioManager extends EventManager_1.EventManager {
1087
1046
  * - 10m+ → 5% (minimum, barely audible)
1088
1047
  */
1089
1048
  calculateLogarithmicGain(distance) {
1090
- const minDistance = 1.5; // Full volume at 1.5m or closer (conversation distance)
1091
- const minGain = 20; // Minimum 20% at far distances (still clearly audible)
1092
- const falloffRate = 0.12; // Controls how fast volume drops (lower = gentler)
1049
+ const minDistance = 1.0; // Full volume at 1m or closer
1050
+ const minGain = 15; // Minimum 15% at far distances (still audible)
1051
+ const falloffRate = 0.12; // Controls how fast volume drops (gentler)
1093
1052
  // Full volume within minimum distance
1094
1053
  if (distance <= minDistance)
1095
1054
  return 100;
package/dist/index.js CHANGED
@@ -436,6 +436,8 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
436
436
  }
437
437
  });
438
438
  this.socket.on("participant-position-updated", (data) => {
439
+ // DEBUG: Log position update received from socket
440
+ console.log(`[SDK-SOCKET] id=${data.participantId.substring(0, 8)} pos=(${data.position.x.toFixed(1)},${data.position.y.toFixed(1)},${data.position.z.toFixed(1)}) rot=(${data.rot?.x?.toFixed(1)},${data.rot?.y?.toFixed(1)},${data.rot?.z?.toFixed(1)})`);
439
441
  const participant = this.room?.participants.get(data.participantId);
440
442
  if (participant) {
441
443
  participant.position = data.position;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newgameplusinc/odyssey-audio-video-sdk-dev",
3
- "version": "1.0.256",
3
+ "version": "1.0.258",
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",