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

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.
@@ -50,7 +50,6 @@ export declare class SpatialAudioManager extends EventManager {
50
50
  private localParticipantId;
51
51
  private isMasterMuted;
52
52
  private smoothedPanValues;
53
- private lastGainValue;
54
53
  private lastPanValue;
55
54
  private lastSpatialUpdateTime;
56
55
  private _spatialDebugTimes;
@@ -60,6 +59,9 @@ export declare class SpatialAudioManager extends EventManager {
60
59
  private cachedSpeakerPositions;
61
60
  private cachedListenerPosition;
62
61
  private positionSnapThreshold;
62
+ private cachedGainValues;
63
+ private gainChangeThreshold;
64
+ private minimumStableGain;
63
65
  private _listenerDebugLogTime?;
64
66
  private mlSuppressor;
65
67
  private mlModelReady;
@@ -23,9 +23,6 @@ class SpatialAudioManager extends EventManager_1.EventManager {
23
23
  // PAN SMOOTHING: Prevents random left/right jumping
24
24
  // Stores the previous smoothed pan value for each participant
25
25
  this.smoothedPanValues = new Map();
26
- // GAIN SMOOTHING: Prevents audio clicks/pops from rapid gain changes
27
- // Tracks last applied gain value per participant for smooth ramping
28
- this.lastGainValue = new Map();
29
26
  // PAN SMOOTHING: Tracks last applied pan value to skip micro-changes
30
27
  this.lastPanValue = new Map();
31
28
  // UPDATE THROTTLE: Tracks last spatial update time to prevent too-frequent updates
@@ -49,6 +46,15 @@ class SpatialAudioManager extends EventManager_1.EventManager {
49
46
  // 0.30m = 30cm - ignores pixel streaming jitter, physics wobble, breathing
50
47
  // ALIGNED with server-side 100cm filter - this catches smaller jitter
51
48
  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
52
58
  // NOTE: Rate limiting variables removed - setTargetAtTime provides sufficient smoothing
53
59
  // The smoothPanValue() and position snapping handle jitter reduction
54
60
  // ML Noise Suppressor (TensorFlow.js-based)
@@ -401,7 +407,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
401
407
  if (distance >= maxDistance) {
402
408
  // Smooth fade to zero - prevents click (100ms fade)
403
409
  nodes.gain.gain.setTargetAtTime(0, this.audioContext.currentTime, 0.033);
404
- this.lastGainValue.set(participantId, 0);
410
+ this.cachedGainValues.set(participantId, 0);
405
411
  return; // Skip all other processing
406
412
  }
407
413
  // Step 3: Calculate relative vector (speaker relative to listener)
@@ -433,23 +439,52 @@ class SpatialAudioManager extends EventManager_1.EventManager {
433
439
  // SMOOTH THE PAN VALUE to prevent random left/right jumping
434
440
  const smoothedPanValue = this.smoothPanValue(participantId, rawPanValue);
435
441
  const panning = this.panningFromPanValue(smoothedPanValue, dxLocal);
436
- // Calculate gain DIRECTLY based on distance - NO complex smoothing
437
- // The gain formula: sqrt curve from 100% (0.5m) to 20% (15m)
438
- const gain = this.calculateLogarithmicGain(distance);
442
+ // Calculate gain based on distance
443
+ 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
+ }
439
475
  // Apply panning
440
476
  this.applyStereoPanning(participantId, panning);
441
477
  // Apply gain with Web Audio's built-in smoothing (setTargetAtTime)
442
- // This is the ONLY smoothing needed - no additional rate limiting
443
- const gainValue = gain / 100; // Convert to 0-1 range
444
478
  const currentTime = this.audioContext.currentTime;
445
479
  try {
446
480
  // setTargetAtTime provides smooth exponential interpolation
447
- // Time constant 0.1 = ~300ms to settle (prevents clicks)
448
- nodes.gain.gain.setTargetAtTime(gainValue, currentTime, 0.1);
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);
449
484
  }
450
485
  catch (err) {
451
486
  // Fallback: If scheduling fails, set value directly (rare edge case)
452
- nodes.gain.gain.value = gainValue;
487
+ nodes.gain.gain.value = finalGainValue;
453
488
  }
454
489
  }
455
490
  }
@@ -469,7 +504,8 @@ class SpatialAudioManager extends EventManager_1.EventManager {
469
504
  // Smooth ramp to 0 (muted) or 1 (unmuted) - prevents click
470
505
  // Time constant 0.05 = ~150ms to reach target (3 time constants)
471
506
  nodes.gain.gain.setTargetAtTime(muted ? 0 : 1, this.audioContext.currentTime, 0.05);
472
- this.lastGainValue.set(participantId, muted ? 0 : 1);
507
+ // Update cached gain value when muting/unmuting
508
+ this.cachedGainValues.set(participantId, muted ? 0 : 1);
473
509
  }
474
510
  }
475
511
  /**
@@ -665,6 +701,10 @@ class SpatialAudioManager extends EventManager_1.EventManager {
665
701
  this.participantNodes.delete(participantId);
666
702
  // Clean up smoothed pan value tracking
667
703
  this.smoothedPanValues.delete(participantId);
704
+ // Clean up cached gain values
705
+ this.cachedGainValues.delete(participantId);
706
+ // Clean up cached speaker position
707
+ this.cachedSpeakerPositions.delete(participantId);
668
708
  }
669
709
  }
670
710
  async resumeAudioContext() {
@@ -1047,9 +1087,9 @@ class SpatialAudioManager extends EventManager_1.EventManager {
1047
1087
  * - 10m+ → 5% (minimum, barely audible)
1048
1088
  */
1049
1089
  calculateLogarithmicGain(distance) {
1050
- const minDistance = 1.0; // Full volume at 1m or closer
1051
- const minGain = 5; // Minimum 5% at far distances (barely audible)
1052
- const falloffRate = 0.15; // Controls how fast volume drops (higher = faster)
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)
1053
1093
  // Full volume within minimum distance
1054
1094
  if (distance <= minDistance)
1055
1095
  return 100;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newgameplusinc/odyssey-audio-video-sdk-dev",
3
- "version": "1.0.255",
3
+ "version": "1.0.256",
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",