@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.
|
|
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
|
|
437
|
-
|
|
438
|
-
|
|
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.
|
|
448
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
1051
|
-
const minGain =
|
|
1052
|
-
const falloffRate = 0.
|
|
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