@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.
- package/dist/SpatialAudioManager.d.ts +1 -3
- package/dist/SpatialAudioManager.js +27 -68
- package/dist/index.js +2 -0
- package/package.json +1 -1
|
@@ -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
|
-
//
|
|
394
|
-
const listenerPos = this.
|
|
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
|
-
//
|
|
405
|
-
|
|
406
|
-
const
|
|
407
|
-
if (
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
this.
|
|
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
|
-
|
|
445
|
-
//
|
|
446
|
-
|
|
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.
|
|
482
|
-
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
1091
|
-
const minGain =
|
|
1092
|
-
const falloffRate = 0.12; // Controls how fast volume drops (
|
|
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