@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.10 → 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.
- package/dist/SpatialAudioManager.d.ts +6 -0
- package/dist/SpatialAudioManager.js +110 -52
- package/package.json +1 -1
|
@@ -26,6 +26,8 @@ export declare class SpatialAudioManager extends EventManager {
|
|
|
26
26
|
private denoiseWorkletReady;
|
|
27
27
|
private denoiseWorkletUrl?;
|
|
28
28
|
private denoiserWasmBytes?;
|
|
29
|
+
private listenerPosition;
|
|
30
|
+
private listenerInitialized;
|
|
29
31
|
private listenerDirection;
|
|
30
32
|
constructor(options?: SpatialAudioOptions);
|
|
31
33
|
getAudioContext(): AudioContext;
|
|
@@ -85,10 +87,14 @@ export declare class SpatialAudioManager extends EventManager {
|
|
|
85
87
|
* @param lookAtPos Look-at position (where camera is pointing)
|
|
86
88
|
*/
|
|
87
89
|
setListenerFromLSD(listenerPos: Position, cameraPos: Position, lookAtPos: Position): void;
|
|
90
|
+
private applyListenerTransform;
|
|
88
91
|
removeParticipant(participantId: string): void;
|
|
89
92
|
resumeAudioContext(): Promise<void>;
|
|
90
93
|
getAudioContextState(): AudioContextState;
|
|
91
94
|
private getDistanceConfig;
|
|
95
|
+
private applySpatialBoostIfNeeded;
|
|
96
|
+
private getDistanceBetween;
|
|
97
|
+
private calculateDistanceGain;
|
|
92
98
|
private normalizePositionUnits;
|
|
93
99
|
private isDenoiserEnabled;
|
|
94
100
|
private ensureDenoiseWorklet;
|
|
@@ -8,6 +8,8 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
8
8
|
this.participantNodes = new Map();
|
|
9
9
|
this.monitoringIntervals = new Map();
|
|
10
10
|
this.denoiseWorkletReady = null;
|
|
11
|
+
this.listenerPosition = { x: 0, y: 0, z: 0 };
|
|
12
|
+
this.listenerInitialized = false;
|
|
11
13
|
this.listenerDirection = {
|
|
12
14
|
forward: { x: 0, y: 1, z: 0 },
|
|
13
15
|
up: { x: 0, y: 0, z: 1 },
|
|
@@ -227,11 +229,13 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
227
229
|
updateSpatialAudio(participantId, position, direction) {
|
|
228
230
|
const nodes = this.participantNodes.get(participantId);
|
|
229
231
|
if (nodes?.panner) {
|
|
232
|
+
const distanceConfig = this.getDistanceConfig();
|
|
230
233
|
const normalizedPosition = this.normalizePositionUnits(position);
|
|
234
|
+
const targetPosition = this.applySpatialBoostIfNeeded(normalizedPosition);
|
|
231
235
|
// Update position (where the sound is coming from)
|
|
232
|
-
nodes.panner.positionX.setValueAtTime(
|
|
233
|
-
nodes.panner.positionY.setValueAtTime(
|
|
234
|
-
nodes.panner.positionZ.setValueAtTime(
|
|
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);
|
|
235
239
|
// Update orientation (where the participant is facing)
|
|
236
240
|
// This makes the audio source directional based on participant's direction
|
|
237
241
|
if (direction) {
|
|
@@ -248,57 +252,23 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
248
252
|
nodes.panner.orientationZ.setValueAtTime(normZ, this.audioContext.currentTime);
|
|
249
253
|
}
|
|
250
254
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
x: orientation.forwardX,
|
|
261
|
-
y: orientation.forwardY,
|
|
262
|
-
z: orientation.forwardZ,
|
|
263
|
-
},
|
|
264
|
-
up: {
|
|
265
|
-
x: orientation.upX,
|
|
266
|
-
y: orientation.upY,
|
|
267
|
-
z: orientation.upZ,
|
|
268
|
-
},
|
|
269
|
-
};
|
|
270
|
-
// Use setPosition and setOrientation for atomic updates if available
|
|
271
|
-
if (listener.positionX) {
|
|
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);
|
|
275
|
-
}
|
|
276
|
-
if (listener.forwardX) {
|
|
277
|
-
listener.forwardX.setValueAtTime(orientation.forwardX, this.audioContext.currentTime);
|
|
278
|
-
listener.forwardY.setValueAtTime(orientation.forwardY, this.audioContext.currentTime);
|
|
279
|
-
listener.forwardZ.setValueAtTime(orientation.forwardZ, this.audioContext.currentTime);
|
|
280
|
-
listener.upX.setValueAtTime(orientation.upX, this.audioContext.currentTime);
|
|
281
|
-
listener.upY.setValueAtTime(orientation.upY, this.audioContext.currentTime);
|
|
282
|
-
listener.upZ.setValueAtTime(orientation.upZ, this.audioContext.currentTime);
|
|
283
|
-
}
|
|
284
|
-
// Log spatial audio updates occasionally
|
|
285
|
-
if (Math.random() < 0.01) {
|
|
286
|
-
console.log(`🎧 [Spatial Audio] Listener updated:`, {
|
|
287
|
-
position: { x: position.x.toFixed(1), y: position.y.toFixed(1), z: position.z.toFixed(1) },
|
|
288
|
-
forward: {
|
|
289
|
-
x: orientation.forwardX.toFixed(2),
|
|
290
|
-
y: orientation.forwardY.toFixed(2),
|
|
291
|
-
z: orientation.forwardZ.toFixed(2),
|
|
292
|
-
},
|
|
293
|
-
up: {
|
|
294
|
-
x: orientation.upX.toFixed(2),
|
|
295
|
-
y: orientation.upY.toFixed(2),
|
|
296
|
-
z: orientation.upZ.toFixed(2),
|
|
297
|
-
},
|
|
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),
|
|
298
264
|
});
|
|
299
265
|
}
|
|
300
266
|
}
|
|
301
267
|
}
|
|
268
|
+
setListenerPosition(position, orientation) {
|
|
269
|
+
const normalizedPosition = this.normalizePositionUnits(position);
|
|
270
|
+
this.applyListenerTransform(normalizedPosition, orientation);
|
|
271
|
+
}
|
|
302
272
|
/**
|
|
303
273
|
* Update listener orientation from LSD camera direction
|
|
304
274
|
* @param cameraPos Camera position in world space
|
|
@@ -329,7 +299,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
329
299
|
const rightLen = Math.sqrt(rightX * rightX + rightY * rightY + rightZ * rightZ);
|
|
330
300
|
if (rightLen < 0.001) {
|
|
331
301
|
// Forward is parallel to world up, use fallback
|
|
332
|
-
this.
|
|
302
|
+
this.applyListenerTransform(normalizedListener, {
|
|
333
303
|
forwardX: fwdX,
|
|
334
304
|
forwardY: fwdY,
|
|
335
305
|
forwardZ: fwdZ,
|
|
@@ -346,7 +316,7 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
346
316
|
const upX = fwdY * rZ - fwdZ * rY;
|
|
347
317
|
const upY = fwdZ * rX - fwdX * rZ;
|
|
348
318
|
const upZ = fwdX * rY - fwdY * rX;
|
|
349
|
-
this.
|
|
319
|
+
this.applyListenerTransform(normalizedListener, {
|
|
350
320
|
forwardX: fwdX,
|
|
351
321
|
forwardY: fwdY,
|
|
352
322
|
forwardZ: fwdZ,
|
|
@@ -355,6 +325,58 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
355
325
|
upZ,
|
|
356
326
|
});
|
|
357
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
|
+
}
|
|
358
380
|
removeParticipant(participantId) {
|
|
359
381
|
// Stop monitoring
|
|
360
382
|
if (this.monitoringIntervals.has(participantId)) {
|
|
@@ -392,6 +414,42 @@ class SpatialAudioManager extends EventManager_1.EventManager {
|
|
|
392
414
|
unit: this.options.distance?.unit ?? "auto",
|
|
393
415
|
};
|
|
394
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
|
+
}
|
|
395
453
|
normalizePositionUnits(position) {
|
|
396
454
|
const distanceConfig = this.getDistanceConfig();
|
|
397
455
|
if (distanceConfig.unit === "meters") {
|
package/package.json
CHANGED