@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.62 → 1.0.63

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.
@@ -112,6 +112,13 @@ export declare class SpatialAudioManager extends EventManager {
112
112
  * @param muted True to mute, false to unmute
113
113
  */
114
114
  setParticipantMuted(participantId: string, muted: boolean): void;
115
+ /**
116
+ * Update listener position and orientation
117
+ * The \"listener\" is YOU - where your ears/head are positioned
118
+ *
119
+ * @param position Your HEAD position (camera position), not body position!
120
+ * @param orientation Which way your head is facing (forward and up vectors)
121
+ */
115
122
  setListenerPosition(position: Position, orientation: {
116
123
  forwardX: number;
117
124
  forwardY: number;
@@ -122,7 +129,10 @@ export declare class SpatialAudioManager extends EventManager {
122
129
  }): void;
123
130
  /**
124
131
  * Update listener orientation from LSD camera direction
125
- * @param cameraPos Camera position in world space
132
+ * IMPORTANT: Uses CAMERA position (head) as listener, not body position!
133
+ *
134
+ * @param listenerPos Player body position (for reference, not used as listener)
135
+ * @param cameraPos Camera/HEAD position - THIS is the actual listener position for audio
126
136
  * @param lookAtPos Look-at position (where camera is pointing)
127
137
  */
128
138
  setListenerFromLSD(listenerPos: Position, cameraPos: Position, lookAtPos: Position): void;
@@ -142,13 +142,14 @@ class SpatialAudioManager extends EventManager_1.EventManager {
142
142
  }
143
143
  // Create BiquadFilter nodes for static/noise reduction
144
144
  // Based on: https://tagdiwalaviral.medium.com/struggles-of-noise-reduction-in-rtc-part-2-2526f8179442
145
- // HIGHPASS FILTER: Remove low-frequency rumble (< 80Hz)
145
+ // HIGHPASS FILTER: Remove low-frequency rumble and plosives (< 80Hz)
146
146
  // Human voice fundamental: 80-300Hz, harmonics: 300Hz-8kHz
147
147
  // This cuts BELOW voice range while preserving full voice spectrum
148
+ // Lower Q for gentler slope = less phase distortion = fewer onset artifacts
148
149
  const highpassFilter = this.audioContext.createBiquadFilter();
149
150
  highpassFilter.type = "highpass";
150
151
  highpassFilter.frequency.value = 80; // Cut frequencies below 80Hz (removes rumble/pops)
151
- highpassFilter.Q.value = 1.0; // Quality factor
152
+ highpassFilter.Q.value = 0.707; // Butterworth response (gentler, reduces plosives)
152
153
  // LOWPASS FILTER: Remove high-frequency hiss (> 8000Hz)
153
154
  // Voice harmonics extend to ~8kHz - this preserves full voice richness
154
155
  // while removing digital artifacts and hiss ABOVE useful voice range
@@ -156,14 +157,14 @@ class SpatialAudioManager extends EventManager_1.EventManager {
156
157
  lowpassFilter.type = "lowpass";
157
158
  lowpassFilter.frequency.value = 8000; // Cut frequencies above 8kHz (preserves voice harmonics)
158
159
  lowpassFilter.Q.value = 1.0; // Quality factor
159
- // VOICE BAND EMPHASIS: Boost 80-300Hz fundamental range for clarity
160
+ // VOICE BAND EMPHASIS: Subtle boost 80-300Hz fundamental range for clarity
160
161
  // This emphasizes the base pitch without affecting harmonics
161
- // Helps reduce the "pop" when someone starts speaking
162
+ // Reduced gain to prevent onset artifacts ("mic pop" when speaking starts)
162
163
  const voiceBandFilter = this.audioContext.createBiquadFilter();
163
164
  voiceBandFilter.type = "peaking";
164
165
  voiceBandFilter.frequency.value = 180; // Center of voice fundamental (80-300Hz)
165
- voiceBandFilter.Q.value = 1.5; // Moderate width (~100-260Hz affected)
166
- voiceBandFilter.gain.value = 2; // +2dB boost for clarity
166
+ voiceBandFilter.Q.value = 0.8; // Wider, gentler curve (reduces artifacts)
167
+ voiceBandFilter.gain.value = 1; // +1dB subtle boost (was 2dB - too aggressive)
167
168
  const dynamicLowpass = this.audioContext.createBiquadFilter();
168
169
  dynamicLowpass.type = "lowpass";
169
170
  dynamicLowpass.frequency.value = 7500; // Fixed for all angles
@@ -181,13 +182,24 @@ class SpatialAudioManager extends EventManager_1.EventManager {
181
182
  panner.coneOuterGain = 0.3; // Some sound even outside cone
182
183
  // Configure gain for individual participant volume control
183
184
  gain.gain.value = 1.0;
185
+ // ADD COMPRESSOR: Prevents sudden peaks and "pops" when speaking starts
186
+ // This is KEY to eliminating onset artifacts
187
+ const participantCompressor = this.audioContext.createDynamicsCompressor();
188
+ participantCompressor.threshold.value = -24; // Start compressing at -24dB
189
+ participantCompressor.knee.value = 10; // Smooth knee for natural sound
190
+ participantCompressor.ratio.value = 3; // 3:1 compression ratio
191
+ participantCompressor.attack.value = 0.003; // 3ms fast attack for transients
192
+ participantCompressor.release.value = 0.15; // 150ms release for natural decay
184
193
  let currentNode = source;
194
+ // First apply compressor to tame initial transients (CRITICAL for preventing pops)
195
+ currentNode.connect(participantCompressor);
196
+ currentNode = participantCompressor;
185
197
  if (denoiseNode) {
186
198
  currentNode.connect(denoiseNode);
187
199
  currentNode = denoiseNode;
188
200
  }
189
201
  // Audio chain with voice optimization filters
190
- // Chain: source -> [denoise] -> highpass -> voiceBand -> lowpass -> dynamicLowpass -> proximityGain -> panner -> analyser -> gain -> masterGain
202
+ // Chain: source -> compressor -> [denoise] -> highpass -> voiceBand -> lowpass -> dynamicLowpass -> proximityGain -> panner -> analyser -> gain -> masterGain
191
203
  currentNode.connect(highpassFilter);
192
204
  highpassFilter.connect(voiceBandFilter);
193
205
  voiceBandFilter.connect(lowpassFilter);
@@ -211,6 +223,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
211
223
  analyser,
212
224
  gain,
213
225
  proximityGain,
226
+ compressor: participantCompressor,
214
227
  highpassFilter,
215
228
  lowpassFilter,
216
229
  voiceBandFilter,
@@ -301,10 +314,10 @@ class SpatialAudioManager extends EventManager_1.EventManager {
301
314
  const nodes = this.participantNodes.get(participantId);
302
315
  if (nodes?.panner) {
303
316
  const normalizedPosition = this.normalizePositionUnits(position);
304
- const listenerPos = this.listenerPosition;
305
- // Calculate distance (in meters)
317
+ const listenerPos = this.listenerPosition; // This is HEAD position (from setListenerFromLSD)
318
+ // Calculate distance (in meters) - from HEAD to source
306
319
  const distance = this.getDistanceBetween(listenerPos, normalizedPosition);
307
- // Calculate angle between listener and source
320
+ // Calculate angle between listener HEAD and source
308
321
  const angle = this.calculateAngle(listenerPos, normalizedPosition, this.listenerDirection.forward);
309
322
  // Calculate stereo panning based on angle
310
323
  const panning = this.calculatePanning(angle);
@@ -314,10 +327,14 @@ class SpatialAudioManager extends EventManager_1.EventManager {
314
327
  this.applyStereoPanning(participantId, panning);
315
328
  // Apply gain with smooth transition to reduce clicking/popping
316
329
  const gainValue = gain / 100; // Convert to 0-1 range
317
- nodes.gain.gain.setTargetAtTime(gainValue, this.audioContext.currentTime, 0.05 // Smooth transition over 50ms to reduce clicking
318
- );
319
- // Apply proximity gain for additional distance-based attenuation
320
- nodes.proximityGain.gain.setTargetAtTime(gainValue, this.audioContext.currentTime, 0.05);
330
+ // Use exponentialRampToValueAtTime for smoother, more natural transitions
331
+ // This prevents the "pop" when someone starts speaking
332
+ const currentTime = this.audioContext.currentTime;
333
+ const rampTime = 0.08; // 80ms smooth ramp (was 50ms - increased for gentler onset)
334
+ // Ensure we never ramp to exactly 0 (causes issues)
335
+ const targetGain = Math.max(0.001, gainValue);
336
+ nodes.gain.gain.setTargetAtTime(targetGain, currentTime, rampTime);
337
+ nodes.proximityGain.gain.setTargetAtTime(targetGain, currentTime, rampTime);
321
338
  // Update 3D position for PannerNode (still used for vertical positioning)
322
339
  nodes.panner.positionY.setValueAtTime(normalizedPosition.y, this.audioContext.currentTime);
323
340
  nodes.panner.positionZ.setValueAtTime(normalizedPosition.z, this.audioContext.currentTime);
@@ -348,17 +365,28 @@ class SpatialAudioManager extends EventManager_1.EventManager {
348
365
  nodes.gain.gain.setValueAtTime(muted ? 0 : 1, this.audioContext.currentTime);
349
366
  }
350
367
  }
368
+ /**
369
+ * Update listener position and orientation
370
+ * The \"listener\" is YOU - where your ears/head are positioned
371
+ *
372
+ * @param position Your HEAD position (camera position), not body position!
373
+ * @param orientation Which way your head is facing (forward and up vectors)
374
+ */
351
375
  setListenerPosition(position, orientation) {
352
376
  const normalizedPosition = this.normalizePositionUnits(position);
353
377
  this.applyListenerTransform(normalizedPosition, orientation);
354
378
  }
355
379
  /**
356
380
  * Update listener orientation from LSD camera direction
357
- * @param cameraPos Camera position in world space
381
+ * IMPORTANT: Uses CAMERA position (head) as listener, not body position!
382
+ *
383
+ * @param listenerPos Player body position (for reference, not used as listener)
384
+ * @param cameraPos Camera/HEAD position - THIS is the actual listener position for audio
358
385
  * @param lookAtPos Look-at position (where camera is pointing)
359
386
  */
360
387
  setListenerFromLSD(listenerPos, cameraPos, lookAtPos) {
361
- const normalizedListener = this.normalizePositionUnits(listenerPos);
388
+ // USE CAMERA POSITION AS LISTENER (head position, not body!)
389
+ const normalizedListener = this.normalizePositionUnits(cameraPos); // ✅ Changed from listenerPos
362
390
  const normalizedCamera = this.normalizePositionUnits(cameraPos);
363
391
  const normalizedLookAt = this.normalizePositionUnits(lookAtPos);
364
392
  // Calculate forward vector (from camera to look-at point)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newgameplusinc/odyssey-audio-video-sdk-dev",
3
- "version": "1.0.62",
3
+ "version": "1.0.63",
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",