@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.263 → 1.0.265

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.
@@ -27,6 +27,10 @@ export declare class SpatialAudioChannel {
27
27
  private positionCache;
28
28
  private panSmoother;
29
29
  private isMasterMuted;
30
+ private lastGainValues;
31
+ private lastPanValues;
32
+ private readonly GAIN_CHANGE_THRESHOLD;
33
+ private readonly PAN_CHANGE_THRESHOLD;
30
34
  private mlNoiseSuppressor;
31
35
  private noiseSuppressionMode;
32
36
  constructor(config?: SpatialAudioConfig);
@@ -40,6 +44,7 @@ export declare class SpatialAudioChannel {
40
44
  setupParticipant(participantId: string, track: MediaStreamTrack, bypassSpatialization?: boolean): Promise<void>;
41
45
  /**
42
46
  * Update spatial audio for a participant
47
+ * OPTIMIZED: Only updates gain/pan when values change significantly
43
48
  */
44
49
  updateSpatialAudio(participantId: string, position: Position, direction?: {
45
50
  x: number;
@@ -29,6 +29,11 @@ class SpatialAudioChannel {
29
29
  initialized: false,
30
30
  };
31
31
  this.isMasterMuted = false;
32
+ // Gain/Pan caching to prevent unnecessary audio parameter updates
33
+ this.lastGainValues = new Map();
34
+ this.lastPanValues = new Map();
35
+ this.GAIN_CHANGE_THRESHOLD = 0.02; // 2% change required to update
36
+ this.PAN_CHANGE_THRESHOLD = 0.03; // 3% change required to update
32
37
  // ML Noise Suppression
33
38
  this.mlNoiseSuppressor = null;
34
39
  this.noiseSuppressionMode = 'none';
@@ -38,13 +43,13 @@ class SpatialAudioChannel {
38
43
  // Master gain
39
44
  this.masterGainNode = this.audioContext.createGain();
40
45
  this.masterGainNode.gain.value = 1.0;
41
- // Master compressor
46
+ // Master compressor - gentle settings for voice clarity
42
47
  this.compressor = this.audioContext.createDynamicsCompressor();
43
- this.compressor.threshold.value = -15;
44
- this.compressor.knee.value = 40;
45
- this.compressor.ratio.value = 2.5;
46
- this.compressor.attack.value = 0.02;
47
- this.compressor.release.value = 0.25;
48
+ this.compressor.threshold.value = -24; // Higher threshold - only compress loud peaks
49
+ this.compressor.knee.value = 30; // Soft knee for natural transition
50
+ this.compressor.ratio.value = 3; // Gentle ratio for voice
51
+ this.compressor.attack.value = 0.003; // 3ms attack - catch transients but not too fast
52
+ this.compressor.release.value = 0.25; // 250ms release - smooth recovery
48
53
  // Connect master chain
49
54
  this.masterGainNode.connect(this.compressor);
50
55
  this.compressor.connect(this.audioContext.destination);
@@ -124,9 +129,13 @@ class SpatialAudioChannel {
124
129
  dynamicLowpass,
125
130
  stream,
126
131
  });
132
+ // Initialize gain cache for this participant at full volume
133
+ this.lastGainValues.set(participantId, 1.0);
134
+ this.lastPanValues.set(participantId, 0);
127
135
  }
128
136
  /**
129
137
  * Update spatial audio for a participant
138
+ * OPTIMIZED: Only updates gain/pan when values change significantly
130
139
  */
131
140
  updateSpatialAudio(participantId, position, direction) {
132
141
  const nodes = this.participantNodes.get(participantId);
@@ -140,7 +149,11 @@ class SpatialAudioChannel {
140
149
  const distance = (0, distance_calc_1.getDistanceBetween)(listenerPos, speakerHead);
141
150
  // Hard cutoff at max distance
142
151
  if (distance >= this.distanceConfig.maxDistance) {
143
- (0, gain_smoothing_1.applyGainSmooth)(nodes.gain, 0, this.audioContext, 0.033);
152
+ const lastGain = this.lastGainValues.get(participantId) ?? 1.0;
153
+ if (lastGain > 0.01) {
154
+ (0, gain_smoothing_1.applyGainSmooth)(nodes.gain, 0, this.audioContext, 0.1);
155
+ this.lastGainValues.set(participantId, 0);
156
+ }
144
157
  return;
145
158
  }
146
159
  // Calculate pan
@@ -153,11 +166,21 @@ class SpatialAudioChannel {
153
166
  maxDistance: this.distanceConfig.maxDistance,
154
167
  });
155
168
  const gainValue = gainPercent / 100;
156
- // Apply stereo panning
169
+ // Calculate pan value
157
170
  const panValue = (panning.right - panning.left) / 100;
158
- (0, gain_smoothing_1.applyStereoPanSmooth)(nodes.stereoPanner, panValue, this.audioContext, 0.05);
159
- // Apply gain
160
- (0, gain_smoothing_1.applyGainSmooth)(nodes.gain, gainValue, this.audioContext, 0.1);
171
+ // OPTIMIZATION: Only apply pan if it changed significantly
172
+ const lastPan = this.lastPanValues.get(participantId) ?? 0;
173
+ if (Math.abs(panValue - lastPan) > this.PAN_CHANGE_THRESHOLD) {
174
+ (0, gain_smoothing_1.applyStereoPanSmooth)(nodes.stereoPanner, panValue, this.audioContext, 0.08);
175
+ this.lastPanValues.set(participantId, panValue);
176
+ }
177
+ // OPTIMIZATION: Only apply gain if it changed significantly
178
+ // This prevents continuous setTargetAtTime calls which cause audio instability
179
+ const lastGain = this.lastGainValues.get(participantId) ?? 1.0;
180
+ if (Math.abs(gainValue - lastGain) > this.GAIN_CHANGE_THRESHOLD) {
181
+ (0, gain_smoothing_1.applyGainSmooth)(nodes.gain, gainValue, this.audioContext, 0.15);
182
+ this.lastGainValues.set(participantId, gainValue);
183
+ }
161
184
  }
162
185
  /**
163
186
  * Set listener position from LSD data
@@ -316,6 +339,8 @@ class SpatialAudioChannel {
316
339
  this.participantNodes.delete(participantId);
317
340
  this.panSmoother.clear(participantId);
318
341
  this.positionCache.clear(participantId);
342
+ this.lastGainValues.delete(participantId);
343
+ this.lastPanValues.delete(participantId);
319
344
  }
320
345
  }
321
346
  /**
@@ -399,12 +424,13 @@ class SpatialAudioChannel {
399
424
  return { monoSplitter, monoGainL, monoGainR, monoMerger, stereoUpmixer };
400
425
  }
401
426
  createParticipantCompressor() {
427
+ // Per-participant compressor - light touch to preserve voice dynamics
402
428
  const compressor = this.audioContext.createDynamicsCompressor();
403
- compressor.threshold.value = -6;
404
- compressor.knee.value = 3;
405
- compressor.ratio.value = 20;
406
- compressor.attack.value = 0.001;
407
- compressor.release.value = 0.05;
429
+ compressor.threshold.value = -18; // Only compress loud speech
430
+ compressor.knee.value = 12; // Soft knee for natural sound
431
+ compressor.ratio.value = 4; // Moderate ratio (was 20:1 - way too aggressive)
432
+ compressor.attack.value = 0.003; // 3ms attack - catch transients naturally
433
+ compressor.release.value = 0.15; // 150ms release - smooth recovery, no pumping
408
434
  return compressor;
409
435
  }
410
436
  createFilters() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newgameplusinc/odyssey-audio-video-sdk-dev",
3
- "version": "1.0.263",
3
+ "version": "1.0.265",
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",