@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.9 → 1.0.11

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.
@@ -1,13 +1,35 @@
1
1
  import { EventManager } from "./EventManager";
2
2
  import { Position } from "./types";
3
+ type SpatialAudioDistanceConfig = {
4
+ refDistance?: number;
5
+ maxDistance?: number;
6
+ rolloffFactor?: number;
7
+ unit?: "auto" | "meters" | "centimeters";
8
+ };
9
+ type DenoiserOptions = {
10
+ enabled?: boolean;
11
+ threshold?: number;
12
+ noiseFloor?: number;
13
+ release?: number;
14
+ };
15
+ type SpatialAudioOptions = {
16
+ distance?: SpatialAudioDistanceConfig;
17
+ denoiser?: DenoiserOptions;
18
+ };
3
19
  export declare class SpatialAudioManager extends EventManager {
4
20
  private audioContext;
5
21
  private participantNodes;
6
22
  private masterGainNode;
7
23
  private monitoringIntervals;
8
24
  private compressor;
25
+ private options;
26
+ private denoiseWorkletReady;
27
+ private denoiseWorkletUrl?;
28
+ private denoiserWasmBytes?;
29
+ private listenerPosition;
30
+ private listenerInitialized;
9
31
  private listenerDirection;
10
- constructor();
32
+ constructor(options?: SpatialAudioOptions);
11
33
  getAudioContext(): AudioContext;
12
34
  /**
13
35
  * Setup spatial audio for a participant
@@ -24,7 +46,7 @@ export declare class SpatialAudioManager extends EventManager {
24
46
  * @param track Audio track from MediaSoup consumer
25
47
  * @param bypassSpatialization For testing - bypasses 3D positioning
26
48
  */
27
- setupSpatialAudioForParticipant(participantId: string, track: MediaStreamTrack, bypassSpatialization?: boolean): void;
49
+ setupSpatialAudioForParticipant(participantId: string, track: MediaStreamTrack, bypassSpatialization?: boolean): Promise<void>;
28
50
  private startMonitoring;
29
51
  /**
30
52
  * Update spatial audio position and orientation for a participant
@@ -65,7 +87,17 @@ export declare class SpatialAudioManager extends EventManager {
65
87
  * @param lookAtPos Look-at position (where camera is pointing)
66
88
  */
67
89
  setListenerFromLSD(listenerPos: Position, cameraPos: Position, lookAtPos: Position): void;
90
+ private applyListenerTransform;
68
91
  removeParticipant(participantId: string): void;
69
92
  resumeAudioContext(): Promise<void>;
70
93
  getAudioContextState(): AudioContextState;
94
+ private getDistanceConfig;
95
+ private applySpatialBoostIfNeeded;
96
+ private getDistanceBetween;
97
+ private calculateDistanceGain;
98
+ private normalizePositionUnits;
99
+ private isDenoiserEnabled;
100
+ private ensureDenoiseWorklet;
101
+ private resolveOptions;
71
102
  }
103
+ export {};
@@ -3,14 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SpatialAudioManager = void 0;
4
4
  const EventManager_1 = require("./EventManager");
5
5
  class SpatialAudioManager extends EventManager_1.EventManager {
6
- constructor() {
6
+ constructor(options) {
7
7
  super();
8
8
  this.participantNodes = new Map();
9
9
  this.monitoringIntervals = new Map();
10
+ this.denoiseWorkletReady = null;
11
+ this.listenerPosition = { x: 0, y: 0, z: 0 };
12
+ this.listenerInitialized = false;
10
13
  this.listenerDirection = {
11
14
  forward: { x: 0, y: 1, z: 0 },
12
15
  up: { x: 0, y: 0, z: 1 },
13
16
  };
17
+ this.options = this.resolveOptions(options);
14
18
  // Use high sample rate for best audio quality
15
19
  this.audioContext = new AudioContext({ sampleRate: 48000 });
16
20
  // Master gain
@@ -54,10 +58,10 @@ class SpatialAudioManager extends EventManager_1.EventManager {
54
58
  * @param track Audio track from MediaSoup consumer
55
59
  * @param bypassSpatialization For testing - bypasses 3D positioning
56
60
  */
57
- setupSpatialAudioForParticipant(participantId, track, bypassSpatialization = false // Default to false
61
+ async setupSpatialAudioForParticipant(participantId, track, bypassSpatialization = false // Default to false
58
62
  ) {
59
63
  if (this.audioContext.state === "suspended") {
60
- this.audioContext.resume();
64
+ await this.audioContext.resume();
61
65
  }
62
66
  // Create stream with noise suppression constraints
63
67
  const stream = new MediaStream([track]);
@@ -65,6 +69,29 @@ class SpatialAudioManager extends EventManager_1.EventManager {
65
69
  const panner = this.audioContext.createPanner();
66
70
  const analyser = this.audioContext.createAnalyser();
67
71
  const gain = this.audioContext.createGain();
72
+ let denoiseNode;
73
+ if (this.isDenoiserEnabled() && typeof this.audioContext.audioWorklet !== "undefined") {
74
+ try {
75
+ await this.ensureDenoiseWorklet();
76
+ denoiseNode = new AudioWorkletNode(this.audioContext, "odyssey-denoise", {
77
+ numberOfInputs: 1,
78
+ numberOfOutputs: 1,
79
+ processorOptions: {
80
+ enabled: this.options.denoiser?.enabled !== false,
81
+ threshold: this.options.denoiser?.threshold,
82
+ noiseFloor: this.options.denoiser?.noiseFloor,
83
+ release: this.options.denoiser?.release,
84
+ wasmBytes: this.denoiserWasmBytes
85
+ ? this.denoiserWasmBytes.slice(0)
86
+ : null,
87
+ },
88
+ });
89
+ }
90
+ catch (error) {
91
+ console.warn("⚠️ Failed to initialize denoiser worklet. Falling back to raw audio.", error);
92
+ denoiseNode = undefined;
93
+ }
94
+ }
68
95
  // Create BiquadFilter nodes for static/noise reduction
69
96
  // Based on: https://tagdiwalaviral.medium.com/struggles-of-noise-reduction-in-rtc-part-2-2526f8179442
70
97
  const highpassFilter = this.audioContext.createBiquadFilter();
@@ -76,28 +103,32 @@ class SpatialAudioManager extends EventManager_1.EventManager {
76
103
  lowpassFilter.frequency.value = 7500; // Below 8kHz to avoid flat/muffled sound
77
104
  lowpassFilter.Q.value = 1.0; // Quality factor
78
105
  // Configure Panner for realistic 3D spatial audio
106
+ const distanceConfig = this.getDistanceConfig();
79
107
  panner.panningModel = "HRTF"; // Head-Related Transfer Function for realistic 3D
80
108
  panner.distanceModel = "inverse"; // Natural distance falloff
81
- panner.refDistance = 100; // Distance at which volume = 1.0 (1 meter in Unreal = 100cm)
82
- panner.maxDistance = 1500; // Maximum audible distance (15 meters)
83
- panner.rolloffFactor = 1.5; // How quickly sound fades with distance
109
+ panner.refDistance = distanceConfig.refDistance ?? 1.2;
110
+ panner.maxDistance = distanceConfig.maxDistance ?? 30;
111
+ panner.rolloffFactor = distanceConfig.rolloffFactor ?? 1.35; // How quickly sound fades with distance
84
112
  panner.coneInnerAngle = 360; // Omnidirectional sound source
85
113
  panner.coneOuterAngle = 360;
86
114
  panner.coneOuterGain = 0.3; // Some sound even outside cone
87
115
  // Configure gain for individual participant volume control
88
116
  gain.gain.value = 1.0;
117
+ let currentNode = source;
118
+ if (denoiseNode) {
119
+ currentNode.connect(denoiseNode);
120
+ currentNode = denoiseNode;
121
+ }
122
+ currentNode.connect(highpassFilter);
123
+ highpassFilter.connect(lowpassFilter);
89
124
  if (bypassSpatialization) {
90
125
  console.log(`🔊 TESTING: Connecting audio directly to destination (bypassing spatial audio) for ${participantId}`);
91
- source.connect(highpassFilter);
92
- highpassFilter.connect(lowpassFilter);
93
126
  lowpassFilter.connect(analyser);
94
127
  analyser.connect(this.masterGainNode);
95
128
  }
96
129
  else {
97
130
  // Standard spatialized path with full audio chain
98
131
  // Audio Chain: source -> filters -> panner -> analyser -> gain -> masterGain -> compressor -> destination
99
- source.connect(highpassFilter);
100
- highpassFilter.connect(lowpassFilter);
101
132
  lowpassFilter.connect(panner);
102
133
  panner.connect(analyser);
103
134
  analyser.connect(gain);
@@ -110,6 +141,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
110
141
  gain,
111
142
  highpassFilter,
112
143
  lowpassFilter,
144
+ denoiseNode,
113
145
  stream,
114
146
  });
115
147
  console.log(`🎧 Spatial audio setup complete for ${participantId}:`, {
@@ -197,10 +229,13 @@ class SpatialAudioManager extends EventManager_1.EventManager {
197
229
  updateSpatialAudio(participantId, position, direction) {
198
230
  const nodes = this.participantNodes.get(participantId);
199
231
  if (nodes?.panner) {
232
+ const distanceConfig = this.getDistanceConfig();
233
+ const normalizedPosition = this.normalizePositionUnits(position);
234
+ const targetPosition = this.applySpatialBoostIfNeeded(normalizedPosition);
200
235
  // Update position (where the sound is coming from)
201
- nodes.panner.positionX.setValueAtTime(position.x, this.audioContext.currentTime);
202
- nodes.panner.positionY.setValueAtTime(position.y, this.audioContext.currentTime);
203
- nodes.panner.positionZ.setValueAtTime(position.z, this.audioContext.currentTime);
236
+ nodes.panner.positionX.setValueAtTime(targetPosition.x, this.audioContext.currentTime);
237
+ nodes.panner.positionY.setValueAtTime(targetPosition.y, this.audioContext.currentTime);
238
+ nodes.panner.positionZ.setValueAtTime(targetPosition.z, this.audioContext.currentTime);
204
239
  // Update orientation (where the participant is facing)
205
240
  // This makes the audio source directional based on participant's direction
206
241
  if (direction) {
@@ -217,66 +252,36 @@ class SpatialAudioManager extends EventManager_1.EventManager {
217
252
  nodes.panner.orientationZ.setValueAtTime(normZ, this.audioContext.currentTime);
218
253
  }
219
254
  }
220
- }
221
- }
222
- setListenerPosition(position, orientation) {
223
- const { listener } = this.audioContext;
224
- if (listener) {
225
- // Store listener direction for reference
226
- this.listenerDirection = {
227
- forward: {
228
- x: orientation.forwardX,
229
- y: orientation.forwardY,
230
- z: orientation.forwardZ,
231
- },
232
- up: {
233
- x: orientation.upX,
234
- y: orientation.upY,
235
- z: orientation.upZ,
236
- },
237
- };
238
- // Use setPosition and setOrientation for atomic updates if available
239
- if (listener.positionX) {
240
- listener.positionX.setValueAtTime(position.x, this.audioContext.currentTime);
241
- listener.positionY.setValueAtTime(position.y, this.audioContext.currentTime);
242
- listener.positionZ.setValueAtTime(position.z, this.audioContext.currentTime);
243
- }
244
- if (listener.forwardX) {
245
- listener.forwardX.setValueAtTime(orientation.forwardX, this.audioContext.currentTime);
246
- listener.forwardY.setValueAtTime(orientation.forwardY, this.audioContext.currentTime);
247
- listener.forwardZ.setValueAtTime(orientation.forwardZ, this.audioContext.currentTime);
248
- listener.upX.setValueAtTime(orientation.upX, this.audioContext.currentTime);
249
- listener.upY.setValueAtTime(orientation.upY, this.audioContext.currentTime);
250
- listener.upZ.setValueAtTime(orientation.upZ, this.audioContext.currentTime);
251
- }
252
- // Log spatial audio updates occasionally
253
- if (Math.random() < 0.01) {
254
- console.log(`🎧 [Spatial Audio] Listener updated:`, {
255
- position: { x: position.x.toFixed(1), y: position.y.toFixed(1), z: position.z.toFixed(1) },
256
- forward: {
257
- x: orientation.forwardX.toFixed(2),
258
- y: orientation.forwardY.toFixed(2),
259
- z: orientation.forwardZ.toFixed(2),
260
- },
261
- up: {
262
- x: orientation.upX.toFixed(2),
263
- y: orientation.upY.toFixed(2),
264
- z: orientation.upZ.toFixed(2),
265
- },
255
+ const listenerPos = this.listenerPosition;
256
+ const distance = this.getDistanceBetween(listenerPos, targetPosition);
257
+ const distanceGain = this.calculateDistanceGain(distanceConfig, distance);
258
+ nodes.gain.gain.setTargetAtTime(distanceGain, this.audioContext.currentTime, 0.05);
259
+ if (Math.random() < 0.02) {
260
+ console.log("🎚️ [Spatial Audio] Distance gain", {
261
+ participantId,
262
+ distance: distance.toFixed(2),
263
+ gain: distanceGain.toFixed(2),
266
264
  });
267
265
  }
268
266
  }
269
267
  }
268
+ setListenerPosition(position, orientation) {
269
+ const normalizedPosition = this.normalizePositionUnits(position);
270
+ this.applyListenerTransform(normalizedPosition, orientation);
271
+ }
270
272
  /**
271
273
  * Update listener orientation from LSD camera direction
272
274
  * @param cameraPos Camera position in world space
273
275
  * @param lookAtPos Look-at position (where camera is pointing)
274
276
  */
275
277
  setListenerFromLSD(listenerPos, cameraPos, lookAtPos) {
278
+ const normalizedListener = this.normalizePositionUnits(listenerPos);
279
+ const normalizedCamera = this.normalizePositionUnits(cameraPos);
280
+ const normalizedLookAt = this.normalizePositionUnits(lookAtPos);
276
281
  // Calculate forward vector (from camera to look-at point)
277
- const forwardX = lookAtPos.x - cameraPos.x;
278
- const forwardY = lookAtPos.y - cameraPos.y;
279
- const forwardZ = lookAtPos.z - cameraPos.z;
282
+ const forwardX = normalizedLookAt.x - normalizedCamera.x;
283
+ const forwardY = normalizedLookAt.y - normalizedCamera.y;
284
+ const forwardZ = normalizedLookAt.z - normalizedCamera.z;
280
285
  // Normalize forward vector
281
286
  const forwardLen = Math.sqrt(forwardX * forwardX + forwardY * forwardY + forwardZ * forwardZ);
282
287
  if (forwardLen < 0.001) {
@@ -294,7 +299,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
294
299
  const rightLen = Math.sqrt(rightX * rightX + rightY * rightY + rightZ * rightZ);
295
300
  if (rightLen < 0.001) {
296
301
  // Forward is parallel to world up, use fallback
297
- this.setListenerPosition(listenerPos, {
302
+ this.applyListenerTransform(normalizedListener, {
298
303
  forwardX: fwdX,
299
304
  forwardY: fwdY,
300
305
  forwardZ: fwdZ,
@@ -311,7 +316,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
311
316
  const upX = fwdY * rZ - fwdZ * rY;
312
317
  const upY = fwdZ * rX - fwdX * rZ;
313
318
  const upZ = fwdX * rY - fwdY * rX;
314
- this.setListenerPosition(listenerPos, {
319
+ this.applyListenerTransform(normalizedListener, {
315
320
  forwardX: fwdX,
316
321
  forwardY: fwdY,
317
322
  forwardZ: fwdZ,
@@ -320,6 +325,58 @@ class SpatialAudioManager extends EventManager_1.EventManager {
320
325
  upZ,
321
326
  });
322
327
  }
328
+ applyListenerTransform(normalizedPosition, orientation) {
329
+ const { listener } = this.audioContext;
330
+ if (!listener) {
331
+ return;
332
+ }
333
+ this.listenerPosition = { ...normalizedPosition };
334
+ this.listenerInitialized = true;
335
+ this.listenerDirection = {
336
+ forward: {
337
+ x: orientation.forwardX,
338
+ y: orientation.forwardY,
339
+ z: orientation.forwardZ,
340
+ },
341
+ up: {
342
+ x: orientation.upX,
343
+ y: orientation.upY,
344
+ z: orientation.upZ,
345
+ },
346
+ };
347
+ if (listener.positionX) {
348
+ listener.positionX.setValueAtTime(normalizedPosition.x, this.audioContext.currentTime);
349
+ listener.positionY.setValueAtTime(normalizedPosition.y, this.audioContext.currentTime);
350
+ listener.positionZ.setValueAtTime(normalizedPosition.z, this.audioContext.currentTime);
351
+ }
352
+ if (listener.forwardX) {
353
+ listener.forwardX.setValueAtTime(orientation.forwardX, this.audioContext.currentTime);
354
+ listener.forwardY.setValueAtTime(orientation.forwardY, this.audioContext.currentTime);
355
+ listener.forwardZ.setValueAtTime(orientation.forwardZ, this.audioContext.currentTime);
356
+ listener.upX.setValueAtTime(orientation.upX, this.audioContext.currentTime);
357
+ listener.upY.setValueAtTime(orientation.upY, this.audioContext.currentTime);
358
+ listener.upZ.setValueAtTime(orientation.upZ, this.audioContext.currentTime);
359
+ }
360
+ if (Math.random() < 0.01) {
361
+ console.log(`🎧 [Spatial Audio] Listener updated:`, {
362
+ position: {
363
+ x: normalizedPosition.x.toFixed(2),
364
+ y: normalizedPosition.y.toFixed(2),
365
+ z: normalizedPosition.z.toFixed(2),
366
+ },
367
+ forward: {
368
+ x: orientation.forwardX.toFixed(2),
369
+ y: orientation.forwardY.toFixed(2),
370
+ z: orientation.forwardZ.toFixed(2),
371
+ },
372
+ up: {
373
+ x: orientation.upX.toFixed(2),
374
+ y: orientation.upY.toFixed(2),
375
+ z: orientation.upZ.toFixed(2),
376
+ },
377
+ });
378
+ }
379
+ }
323
380
  removeParticipant(participantId) {
324
381
  // Stop monitoring
325
382
  if (this.monitoringIntervals.has(participantId)) {
@@ -332,6 +389,9 @@ class SpatialAudioManager extends EventManager_1.EventManager {
332
389
  nodes.panner.disconnect();
333
390
  nodes.analyser.disconnect();
334
391
  nodes.gain.disconnect();
392
+ if (nodes.denoiseNode) {
393
+ nodes.denoiseNode.disconnect();
394
+ }
335
395
  nodes.stream.getTracks().forEach((track) => track.stop());
336
396
  this.participantNodes.delete(participantId);
337
397
  console.log(`🗑️ Removed participant ${participantId} from spatial audio.`);
@@ -346,5 +406,189 @@ class SpatialAudioManager extends EventManager_1.EventManager {
346
406
  getAudioContextState() {
347
407
  return this.audioContext.state;
348
408
  }
409
+ getDistanceConfig() {
410
+ return {
411
+ refDistance: this.options.distance?.refDistance ?? 1.2,
412
+ maxDistance: this.options.distance?.maxDistance ?? 30,
413
+ rolloffFactor: this.options.distance?.rolloffFactor ?? 1.35,
414
+ unit: this.options.distance?.unit ?? "auto",
415
+ };
416
+ }
417
+ applySpatialBoostIfNeeded(position) {
418
+ if (!this.listenerInitialized) {
419
+ return position;
420
+ }
421
+ const boost = (this.options.distance?.rolloffFactor || 1) * 0.85;
422
+ if (!isFinite(boost) || boost <= 1.01) {
423
+ return position;
424
+ }
425
+ const listener = this.listenerPosition;
426
+ return {
427
+ x: listener.x + (position.x - listener.x) * boost,
428
+ y: listener.y + (position.y - listener.y) * Math.min(boost, 1.2),
429
+ z: listener.z + (position.z - listener.z) * boost,
430
+ };
431
+ }
432
+ getDistanceBetween(a, b) {
433
+ const dx = b.x - a.x;
434
+ const dy = b.y - a.y;
435
+ const dz = b.z - a.z;
436
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
437
+ }
438
+ calculateDistanceGain(config, distance) {
439
+ if (!this.listenerInitialized) {
440
+ return 1;
441
+ }
442
+ if (distance <= config.refDistance) {
443
+ return 1;
444
+ }
445
+ if (distance >= config.maxDistance) {
446
+ return 0;
447
+ }
448
+ const normalized = (distance - config.refDistance) /
449
+ Math.max(config.maxDistance - config.refDistance, 0.001);
450
+ const shaped = Math.pow(Math.max(0, 1 - normalized), Math.max(1.2, config.rolloffFactor * 1.05));
451
+ return Math.min(1, Math.max(0.01, shaped));
452
+ }
453
+ normalizePositionUnits(position) {
454
+ const distanceConfig = this.getDistanceConfig();
455
+ if (distanceConfig.unit === "meters") {
456
+ return { ...position };
457
+ }
458
+ if (distanceConfig.unit === "centimeters") {
459
+ return {
460
+ x: position.x / 100,
461
+ y: position.y / 100,
462
+ z: position.z / 100,
463
+ };
464
+ }
465
+ const maxAxis = Math.max(Math.abs(position.x), Math.abs(position.y), Math.abs(position.z));
466
+ if (maxAxis > 50) {
467
+ // Likely centimeters coming from server
468
+ return {
469
+ x: position.x / 100,
470
+ y: position.y / 100,
471
+ z: position.z / 100,
472
+ };
473
+ }
474
+ return { ...position };
475
+ }
476
+ isDenoiserEnabled() {
477
+ return this.options.denoiser?.enabled !== false;
478
+ }
479
+ async ensureDenoiseWorklet() {
480
+ if (!this.isDenoiserEnabled()) {
481
+ return;
482
+ }
483
+ if (!("audioWorklet" in this.audioContext)) {
484
+ console.warn("⚠️ AudioWorklet not supported in this browser. Disabling denoiser.");
485
+ this.options.denoiser = {
486
+ ...(this.options.denoiser || {}),
487
+ enabled: false,
488
+ };
489
+ return;
490
+ }
491
+ if (this.denoiseWorkletReady) {
492
+ return this.denoiseWorkletReady;
493
+ }
494
+ const processorSource = `class OdysseyDenoiseProcessor extends AudioWorkletProcessor {
495
+ constructor(options) {
496
+ super();
497
+ const cfg = (options && options.processorOptions) || {};
498
+ this.enabled = cfg.enabled !== false;
499
+ this.threshold = typeof cfg.threshold === 'number' ? cfg.threshold : 0.012;
500
+ this.noiseFloor = typeof cfg.noiseFloor === 'number' ? cfg.noiseFloor : 0.004;
501
+ this.release = typeof cfg.release === 'number' ? cfg.release : 0.18;
502
+ this.smoothedLevel = this.noiseFloor;
503
+ }
504
+
505
+ process(inputs, outputs) {
506
+ const input = inputs[0];
507
+ const output = outputs[0];
508
+ if (!input || input.length === 0 || !output || output.length === 0) {
509
+ return true;
510
+ }
511
+
512
+ for (let channel = 0; channel < input.length; channel++) {
513
+ const inChannel = input[channel];
514
+ const outChannel = output[channel];
515
+ if (!inChannel || !outChannel) {
516
+ continue;
517
+ }
518
+
519
+ let sum = 0;
520
+ for (let i = 0; i < inChannel.length; i++) {
521
+ const sample = inChannel[i];
522
+ sum += sample * sample;
523
+ }
524
+
525
+ const rms = Math.sqrt(sum / inChannel.length);
526
+ this.smoothedLevel += (rms - this.smoothedLevel) * this.release;
527
+ const dynamicThreshold = Math.max(
528
+ this.noiseFloor,
529
+ this.threshold * 0.6 + this.smoothedLevel * 0.4
530
+ );
531
+
532
+ if (!this.enabled || rms >= dynamicThreshold) {
533
+ for (let i = 0; i < inChannel.length; i++) {
534
+ outChannel[i] = inChannel[i];
535
+ }
536
+ } else {
537
+ for (let i = 0; i < inChannel.length; i++) {
538
+ outChannel[i] = 0;
539
+ }
540
+ }
541
+ }
542
+
543
+ return true;
544
+ }
545
+ }
546
+
547
+ registerProcessor('odyssey-denoise', OdysseyDenoiseProcessor);
548
+ `;
549
+ const blob = new Blob([processorSource], {
550
+ type: "application/javascript",
551
+ });
552
+ this.denoiseWorkletUrl = URL.createObjectURL(blob);
553
+ this.denoiseWorkletReady = this.audioContext.audioWorklet
554
+ .addModule(this.denoiseWorkletUrl)
555
+ .catch((error) => {
556
+ console.error("❌ Failed to register denoise worklet", error);
557
+ this.options.denoiser = {
558
+ ...(this.options.denoiser || {}),
559
+ enabled: false,
560
+ };
561
+ throw error;
562
+ });
563
+ return this.denoiseWorkletReady;
564
+ }
565
+ resolveOptions(options) {
566
+ const distanceDefaults = {
567
+ refDistance: 1.2,
568
+ maxDistance: 30,
569
+ rolloffFactor: 1.35,
570
+ unit: "auto",
571
+ };
572
+ const denoiserDefaults = {
573
+ enabled: true,
574
+ threshold: 0.012,
575
+ noiseFloor: 0.004,
576
+ release: 0.18,
577
+ };
578
+ return {
579
+ distance: {
580
+ refDistance: options?.distance?.refDistance ?? distanceDefaults.refDistance,
581
+ maxDistance: options?.distance?.maxDistance ?? distanceDefaults.maxDistance,
582
+ rolloffFactor: options?.distance?.rolloffFactor ?? distanceDefaults.rolloffFactor,
583
+ unit: options?.distance?.unit ?? distanceDefaults.unit,
584
+ },
585
+ denoiser: {
586
+ enabled: options?.denoiser?.enabled ?? denoiserDefaults.enabled,
587
+ threshold: options?.denoiser?.threshold ?? denoiserDefaults.threshold,
588
+ noiseFloor: options?.denoiser?.noiseFloor ?? denoiserDefaults.noiseFloor,
589
+ release: options?.denoiser?.release ?? denoiserDefaults.release,
590
+ },
591
+ };
592
+ }
349
593
  }
350
594
  exports.SpatialAudioManager = SpatialAudioManager;
package/dist/index.js CHANGED
@@ -425,7 +425,7 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
425
425
  else {
426
426
  console.log("🎧 SDK: Setting up spatial audio for REMOTE participant", participant.participantId);
427
427
  // Setup spatial audio with full 3D positioning
428
- this.spatialAudioManager.setupSpatialAudioForParticipant(participant.participantId, track, false // Enable spatial audio
428
+ await this.spatialAudioManager.setupSpatialAudioForParticipant(participant.participantId, track, false // Enable spatial audio
429
429
  );
430
430
  // Update spatial audio position for this participant
431
431
  this.spatialAudioManager.updateSpatialAudio(participant.participantId, data.position);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newgameplusinc/odyssey-audio-video-sdk-dev",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
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",