@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.9 → 1.0.10
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 +28 -2
- package/dist/SpatialAudioManager.js +207 -21
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -1,13 +1,33 @@
|
|
|
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?;
|
|
9
29
|
private listenerDirection;
|
|
10
|
-
constructor();
|
|
30
|
+
constructor(options?: SpatialAudioOptions);
|
|
11
31
|
getAudioContext(): AudioContext;
|
|
12
32
|
/**
|
|
13
33
|
* Setup spatial audio for a participant
|
|
@@ -24,7 +44,7 @@ export declare class SpatialAudioManager extends EventManager {
|
|
|
24
44
|
* @param track Audio track from MediaSoup consumer
|
|
25
45
|
* @param bypassSpatialization For testing - bypasses 3D positioning
|
|
26
46
|
*/
|
|
27
|
-
setupSpatialAudioForParticipant(participantId: string, track: MediaStreamTrack, bypassSpatialization?: boolean): void
|
|
47
|
+
setupSpatialAudioForParticipant(participantId: string, track: MediaStreamTrack, bypassSpatialization?: boolean): Promise<void>;
|
|
28
48
|
private startMonitoring;
|
|
29
49
|
/**
|
|
30
50
|
* Update spatial audio position and orientation for a participant
|
|
@@ -68,4 +88,10 @@ export declare class SpatialAudioManager extends EventManager {
|
|
|
68
88
|
removeParticipant(participantId: string): void;
|
|
69
89
|
resumeAudioContext(): Promise<void>;
|
|
70
90
|
getAudioContextState(): AudioContextState;
|
|
91
|
+
private getDistanceConfig;
|
|
92
|
+
private normalizePositionUnits;
|
|
93
|
+
private isDenoiserEnabled;
|
|
94
|
+
private ensureDenoiseWorklet;
|
|
95
|
+
private resolveOptions;
|
|
71
96
|
}
|
|
97
|
+
export {};
|
|
@@ -3,14 +3,16 @@ 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;
|
|
10
11
|
this.listenerDirection = {
|
|
11
12
|
forward: { x: 0, y: 1, z: 0 },
|
|
12
13
|
up: { x: 0, y: 0, z: 1 },
|
|
13
14
|
};
|
|
15
|
+
this.options = this.resolveOptions(options);
|
|
14
16
|
// Use high sample rate for best audio quality
|
|
15
17
|
this.audioContext = new AudioContext({ sampleRate: 48000 });
|
|
16
18
|
// Master gain
|
|
@@ -54,10 +56,10 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
54
56
|
* @param track Audio track from MediaSoup consumer
|
|
55
57
|
* @param bypassSpatialization For testing - bypasses 3D positioning
|
|
56
58
|
*/
|
|
57
|
-
setupSpatialAudioForParticipant(participantId, track, bypassSpatialization = false // Default to false
|
|
59
|
+
async setupSpatialAudioForParticipant(participantId, track, bypassSpatialization = false // Default to false
|
|
58
60
|
) {
|
|
59
61
|
if (this.audioContext.state === "suspended") {
|
|
60
|
-
this.audioContext.resume();
|
|
62
|
+
await this.audioContext.resume();
|
|
61
63
|
}
|
|
62
64
|
// Create stream with noise suppression constraints
|
|
63
65
|
const stream = new MediaStream([track]);
|
|
@@ -65,6 +67,29 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
65
67
|
const panner = this.audioContext.createPanner();
|
|
66
68
|
const analyser = this.audioContext.createAnalyser();
|
|
67
69
|
const gain = this.audioContext.createGain();
|
|
70
|
+
let denoiseNode;
|
|
71
|
+
if (this.isDenoiserEnabled() && typeof this.audioContext.audioWorklet !== "undefined") {
|
|
72
|
+
try {
|
|
73
|
+
await this.ensureDenoiseWorklet();
|
|
74
|
+
denoiseNode = new AudioWorkletNode(this.audioContext, "odyssey-denoise", {
|
|
75
|
+
numberOfInputs: 1,
|
|
76
|
+
numberOfOutputs: 1,
|
|
77
|
+
processorOptions: {
|
|
78
|
+
enabled: this.options.denoiser?.enabled !== false,
|
|
79
|
+
threshold: this.options.denoiser?.threshold,
|
|
80
|
+
noiseFloor: this.options.denoiser?.noiseFloor,
|
|
81
|
+
release: this.options.denoiser?.release,
|
|
82
|
+
wasmBytes: this.denoiserWasmBytes
|
|
83
|
+
? this.denoiserWasmBytes.slice(0)
|
|
84
|
+
: null,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.warn("⚠️ Failed to initialize denoiser worklet. Falling back to raw audio.", error);
|
|
90
|
+
denoiseNode = undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
68
93
|
// Create BiquadFilter nodes for static/noise reduction
|
|
69
94
|
// Based on: https://tagdiwalaviral.medium.com/struggles-of-noise-reduction-in-rtc-part-2-2526f8179442
|
|
70
95
|
const highpassFilter = this.audioContext.createBiquadFilter();
|
|
@@ -76,28 +101,32 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
76
101
|
lowpassFilter.frequency.value = 7500; // Below 8kHz to avoid flat/muffled sound
|
|
77
102
|
lowpassFilter.Q.value = 1.0; // Quality factor
|
|
78
103
|
// Configure Panner for realistic 3D spatial audio
|
|
104
|
+
const distanceConfig = this.getDistanceConfig();
|
|
79
105
|
panner.panningModel = "HRTF"; // Head-Related Transfer Function for realistic 3D
|
|
80
106
|
panner.distanceModel = "inverse"; // Natural distance falloff
|
|
81
|
-
panner.refDistance =
|
|
82
|
-
panner.maxDistance =
|
|
83
|
-
panner.rolloffFactor = 1.
|
|
107
|
+
panner.refDistance = distanceConfig.refDistance ?? 1.2;
|
|
108
|
+
panner.maxDistance = distanceConfig.maxDistance ?? 30;
|
|
109
|
+
panner.rolloffFactor = distanceConfig.rolloffFactor ?? 1.35; // How quickly sound fades with distance
|
|
84
110
|
panner.coneInnerAngle = 360; // Omnidirectional sound source
|
|
85
111
|
panner.coneOuterAngle = 360;
|
|
86
112
|
panner.coneOuterGain = 0.3; // Some sound even outside cone
|
|
87
113
|
// Configure gain for individual participant volume control
|
|
88
114
|
gain.gain.value = 1.0;
|
|
115
|
+
let currentNode = source;
|
|
116
|
+
if (denoiseNode) {
|
|
117
|
+
currentNode.connect(denoiseNode);
|
|
118
|
+
currentNode = denoiseNode;
|
|
119
|
+
}
|
|
120
|
+
currentNode.connect(highpassFilter);
|
|
121
|
+
highpassFilter.connect(lowpassFilter);
|
|
89
122
|
if (bypassSpatialization) {
|
|
90
123
|
console.log(`🔊 TESTING: Connecting audio directly to destination (bypassing spatial audio) for ${participantId}`);
|
|
91
|
-
source.connect(highpassFilter);
|
|
92
|
-
highpassFilter.connect(lowpassFilter);
|
|
93
124
|
lowpassFilter.connect(analyser);
|
|
94
125
|
analyser.connect(this.masterGainNode);
|
|
95
126
|
}
|
|
96
127
|
else {
|
|
97
128
|
// Standard spatialized path with full audio chain
|
|
98
129
|
// Audio Chain: source -> filters -> panner -> analyser -> gain -> masterGain -> compressor -> destination
|
|
99
|
-
source.connect(highpassFilter);
|
|
100
|
-
highpassFilter.connect(lowpassFilter);
|
|
101
130
|
lowpassFilter.connect(panner);
|
|
102
131
|
panner.connect(analyser);
|
|
103
132
|
analyser.connect(gain);
|
|
@@ -110,6 +139,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
110
139
|
gain,
|
|
111
140
|
highpassFilter,
|
|
112
141
|
lowpassFilter,
|
|
142
|
+
denoiseNode,
|
|
113
143
|
stream,
|
|
114
144
|
});
|
|
115
145
|
console.log(`🎧 Spatial audio setup complete for ${participantId}:`, {
|
|
@@ -197,10 +227,11 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
197
227
|
updateSpatialAudio(participantId, position, direction) {
|
|
198
228
|
const nodes = this.participantNodes.get(participantId);
|
|
199
229
|
if (nodes?.panner) {
|
|
230
|
+
const normalizedPosition = this.normalizePositionUnits(position);
|
|
200
231
|
// Update position (where the sound is coming from)
|
|
201
|
-
nodes.panner.positionX.setValueAtTime(
|
|
202
|
-
nodes.panner.positionY.setValueAtTime(
|
|
203
|
-
nodes.panner.positionZ.setValueAtTime(
|
|
232
|
+
nodes.panner.positionX.setValueAtTime(normalizedPosition.x, this.audioContext.currentTime);
|
|
233
|
+
nodes.panner.positionY.setValueAtTime(normalizedPosition.y, this.audioContext.currentTime);
|
|
234
|
+
nodes.panner.positionZ.setValueAtTime(normalizedPosition.z, this.audioContext.currentTime);
|
|
204
235
|
// Update orientation (where the participant is facing)
|
|
205
236
|
// This makes the audio source directional based on participant's direction
|
|
206
237
|
if (direction) {
|
|
@@ -222,6 +253,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
222
253
|
setListenerPosition(position, orientation) {
|
|
223
254
|
const { listener } = this.audioContext;
|
|
224
255
|
if (listener) {
|
|
256
|
+
const normalizedPosition = this.normalizePositionUnits(position);
|
|
225
257
|
// Store listener direction for reference
|
|
226
258
|
this.listenerDirection = {
|
|
227
259
|
forward: {
|
|
@@ -237,9 +269,9 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
237
269
|
};
|
|
238
270
|
// Use setPosition and setOrientation for atomic updates if available
|
|
239
271
|
if (listener.positionX) {
|
|
240
|
-
listener.positionX.setValueAtTime(
|
|
241
|
-
listener.positionY.setValueAtTime(
|
|
242
|
-
listener.positionZ.setValueAtTime(
|
|
272
|
+
listener.positionX.setValueAtTime(normalizedPosition.x, this.audioContext.currentTime);
|
|
273
|
+
listener.positionY.setValueAtTime(normalizedPosition.y, this.audioContext.currentTime);
|
|
274
|
+
listener.positionZ.setValueAtTime(normalizedPosition.z, this.audioContext.currentTime);
|
|
243
275
|
}
|
|
244
276
|
if (listener.forwardX) {
|
|
245
277
|
listener.forwardX.setValueAtTime(orientation.forwardX, this.audioContext.currentTime);
|
|
@@ -273,10 +305,13 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
273
305
|
* @param lookAtPos Look-at position (where camera is pointing)
|
|
274
306
|
*/
|
|
275
307
|
setListenerFromLSD(listenerPos, cameraPos, lookAtPos) {
|
|
308
|
+
const normalizedListener = this.normalizePositionUnits(listenerPos);
|
|
309
|
+
const normalizedCamera = this.normalizePositionUnits(cameraPos);
|
|
310
|
+
const normalizedLookAt = this.normalizePositionUnits(lookAtPos);
|
|
276
311
|
// Calculate forward vector (from camera to look-at point)
|
|
277
|
-
const forwardX =
|
|
278
|
-
const forwardY =
|
|
279
|
-
const forwardZ =
|
|
312
|
+
const forwardX = normalizedLookAt.x - normalizedCamera.x;
|
|
313
|
+
const forwardY = normalizedLookAt.y - normalizedCamera.y;
|
|
314
|
+
const forwardZ = normalizedLookAt.z - normalizedCamera.z;
|
|
280
315
|
// Normalize forward vector
|
|
281
316
|
const forwardLen = Math.sqrt(forwardX * forwardX + forwardY * forwardY + forwardZ * forwardZ);
|
|
282
317
|
if (forwardLen < 0.001) {
|
|
@@ -294,7 +329,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
294
329
|
const rightLen = Math.sqrt(rightX * rightX + rightY * rightY + rightZ * rightZ);
|
|
295
330
|
if (rightLen < 0.001) {
|
|
296
331
|
// Forward is parallel to world up, use fallback
|
|
297
|
-
this.setListenerPosition(
|
|
332
|
+
this.setListenerPosition(normalizedListener, {
|
|
298
333
|
forwardX: fwdX,
|
|
299
334
|
forwardY: fwdY,
|
|
300
335
|
forwardZ: fwdZ,
|
|
@@ -311,7 +346,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
311
346
|
const upX = fwdY * rZ - fwdZ * rY;
|
|
312
347
|
const upY = fwdZ * rX - fwdX * rZ;
|
|
313
348
|
const upZ = fwdX * rY - fwdY * rX;
|
|
314
|
-
this.setListenerPosition(
|
|
349
|
+
this.setListenerPosition(normalizedListener, {
|
|
315
350
|
forwardX: fwdX,
|
|
316
351
|
forwardY: fwdY,
|
|
317
352
|
forwardZ: fwdZ,
|
|
@@ -332,6 +367,9 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
332
367
|
nodes.panner.disconnect();
|
|
333
368
|
nodes.analyser.disconnect();
|
|
334
369
|
nodes.gain.disconnect();
|
|
370
|
+
if (nodes.denoiseNode) {
|
|
371
|
+
nodes.denoiseNode.disconnect();
|
|
372
|
+
}
|
|
335
373
|
nodes.stream.getTracks().forEach((track) => track.stop());
|
|
336
374
|
this.participantNodes.delete(participantId);
|
|
337
375
|
console.log(`🗑️ Removed participant ${participantId} from spatial audio.`);
|
|
@@ -346,5 +384,153 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
346
384
|
getAudioContextState() {
|
|
347
385
|
return this.audioContext.state;
|
|
348
386
|
}
|
|
387
|
+
getDistanceConfig() {
|
|
388
|
+
return {
|
|
389
|
+
refDistance: this.options.distance?.refDistance ?? 1.2,
|
|
390
|
+
maxDistance: this.options.distance?.maxDistance ?? 30,
|
|
391
|
+
rolloffFactor: this.options.distance?.rolloffFactor ?? 1.35,
|
|
392
|
+
unit: this.options.distance?.unit ?? "auto",
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
normalizePositionUnits(position) {
|
|
396
|
+
const distanceConfig = this.getDistanceConfig();
|
|
397
|
+
if (distanceConfig.unit === "meters") {
|
|
398
|
+
return { ...position };
|
|
399
|
+
}
|
|
400
|
+
if (distanceConfig.unit === "centimeters") {
|
|
401
|
+
return {
|
|
402
|
+
x: position.x / 100,
|
|
403
|
+
y: position.y / 100,
|
|
404
|
+
z: position.z / 100,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const maxAxis = Math.max(Math.abs(position.x), Math.abs(position.y), Math.abs(position.z));
|
|
408
|
+
if (maxAxis > 50) {
|
|
409
|
+
// Likely centimeters coming from server
|
|
410
|
+
return {
|
|
411
|
+
x: position.x / 100,
|
|
412
|
+
y: position.y / 100,
|
|
413
|
+
z: position.z / 100,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
return { ...position };
|
|
417
|
+
}
|
|
418
|
+
isDenoiserEnabled() {
|
|
419
|
+
return this.options.denoiser?.enabled !== false;
|
|
420
|
+
}
|
|
421
|
+
async ensureDenoiseWorklet() {
|
|
422
|
+
if (!this.isDenoiserEnabled()) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (!("audioWorklet" in this.audioContext)) {
|
|
426
|
+
console.warn("⚠️ AudioWorklet not supported in this browser. Disabling denoiser.");
|
|
427
|
+
this.options.denoiser = {
|
|
428
|
+
...(this.options.denoiser || {}),
|
|
429
|
+
enabled: false,
|
|
430
|
+
};
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (this.denoiseWorkletReady) {
|
|
434
|
+
return this.denoiseWorkletReady;
|
|
435
|
+
}
|
|
436
|
+
const processorSource = `class OdysseyDenoiseProcessor extends AudioWorkletProcessor {
|
|
437
|
+
constructor(options) {
|
|
438
|
+
super();
|
|
439
|
+
const cfg = (options && options.processorOptions) || {};
|
|
440
|
+
this.enabled = cfg.enabled !== false;
|
|
441
|
+
this.threshold = typeof cfg.threshold === 'number' ? cfg.threshold : 0.012;
|
|
442
|
+
this.noiseFloor = typeof cfg.noiseFloor === 'number' ? cfg.noiseFloor : 0.004;
|
|
443
|
+
this.release = typeof cfg.release === 'number' ? cfg.release : 0.18;
|
|
444
|
+
this.smoothedLevel = this.noiseFloor;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
process(inputs, outputs) {
|
|
448
|
+
const input = inputs[0];
|
|
449
|
+
const output = outputs[0];
|
|
450
|
+
if (!input || input.length === 0 || !output || output.length === 0) {
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
for (let channel = 0; channel < input.length; channel++) {
|
|
455
|
+
const inChannel = input[channel];
|
|
456
|
+
const outChannel = output[channel];
|
|
457
|
+
if (!inChannel || !outChannel) {
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
let sum = 0;
|
|
462
|
+
for (let i = 0; i < inChannel.length; i++) {
|
|
463
|
+
const sample = inChannel[i];
|
|
464
|
+
sum += sample * sample;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const rms = Math.sqrt(sum / inChannel.length);
|
|
468
|
+
this.smoothedLevel += (rms - this.smoothedLevel) * this.release;
|
|
469
|
+
const dynamicThreshold = Math.max(
|
|
470
|
+
this.noiseFloor,
|
|
471
|
+
this.threshold * 0.6 + this.smoothedLevel * 0.4
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
if (!this.enabled || rms >= dynamicThreshold) {
|
|
475
|
+
for (let i = 0; i < inChannel.length; i++) {
|
|
476
|
+
outChannel[i] = inChannel[i];
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
for (let i = 0; i < inChannel.length; i++) {
|
|
480
|
+
outChannel[i] = 0;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
registerProcessor('odyssey-denoise', OdysseyDenoiseProcessor);
|
|
490
|
+
`;
|
|
491
|
+
const blob = new Blob([processorSource], {
|
|
492
|
+
type: "application/javascript",
|
|
493
|
+
});
|
|
494
|
+
this.denoiseWorkletUrl = URL.createObjectURL(blob);
|
|
495
|
+
this.denoiseWorkletReady = this.audioContext.audioWorklet
|
|
496
|
+
.addModule(this.denoiseWorkletUrl)
|
|
497
|
+
.catch((error) => {
|
|
498
|
+
console.error("❌ Failed to register denoise worklet", error);
|
|
499
|
+
this.options.denoiser = {
|
|
500
|
+
...(this.options.denoiser || {}),
|
|
501
|
+
enabled: false,
|
|
502
|
+
};
|
|
503
|
+
throw error;
|
|
504
|
+
});
|
|
505
|
+
return this.denoiseWorkletReady;
|
|
506
|
+
}
|
|
507
|
+
resolveOptions(options) {
|
|
508
|
+
const distanceDefaults = {
|
|
509
|
+
refDistance: 1.2,
|
|
510
|
+
maxDistance: 30,
|
|
511
|
+
rolloffFactor: 1.35,
|
|
512
|
+
unit: "auto",
|
|
513
|
+
};
|
|
514
|
+
const denoiserDefaults = {
|
|
515
|
+
enabled: true,
|
|
516
|
+
threshold: 0.012,
|
|
517
|
+
noiseFloor: 0.004,
|
|
518
|
+
release: 0.18,
|
|
519
|
+
};
|
|
520
|
+
return {
|
|
521
|
+
distance: {
|
|
522
|
+
refDistance: options?.distance?.refDistance ?? distanceDefaults.refDistance,
|
|
523
|
+
maxDistance: options?.distance?.maxDistance ?? distanceDefaults.maxDistance,
|
|
524
|
+
rolloffFactor: options?.distance?.rolloffFactor ?? distanceDefaults.rolloffFactor,
|
|
525
|
+
unit: options?.distance?.unit ?? distanceDefaults.unit,
|
|
526
|
+
},
|
|
527
|
+
denoiser: {
|
|
528
|
+
enabled: options?.denoiser?.enabled ?? denoiserDefaults.enabled,
|
|
529
|
+
threshold: options?.denoiser?.threshold ?? denoiserDefaults.threshold,
|
|
530
|
+
noiseFloor: options?.denoiser?.noiseFloor ?? denoiserDefaults.noiseFloor,
|
|
531
|
+
release: options?.denoiser?.release ?? denoiserDefaults.release,
|
|
532
|
+
},
|
|
533
|
+
};
|
|
534
|
+
}
|
|
349
535
|
}
|
|
350
536
|
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