@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.
- package/dist/SpatialAudioManager.d.ts +11 -1
- package/dist/SpatialAudioManager.js +44 -16
- package/package.json +1 -1
|
@@ -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
|
-
*
|
|
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 =
|
|
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:
|
|
160
|
+
// VOICE BAND EMPHASIS: Subtle boost 80-300Hz fundamental range for clarity
|
|
160
161
|
// This emphasizes the base pitch without affecting harmonics
|
|
161
|
-
//
|
|
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 =
|
|
166
|
-
voiceBandFilter.gain.value =
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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