@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.51 → 1.0.53

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.
@@ -11,6 +11,7 @@ export declare class MLNoiseSuppressor {
11
11
  private processingQueue;
12
12
  private outputQueue;
13
13
  private isProcessing;
14
+ private highPassFilter;
14
15
  /**
15
16
  * Initialize the ML noise suppressor
16
17
  * @param modelUrl URL to the model.json file
@@ -52,7 +53,8 @@ export declare class MLNoiseSuppressor {
52
53
  */
53
54
  private startBackgroundProcessing;
54
55
  /**
55
- * Fast audio processing with simplified ML (optimized version)
56
+ * Fast audio processing optimized for voice quality
57
+ * Preserves voice fundamentals (80-250Hz) while reducing noise
56
58
  */
57
59
  private processAudioFast;
58
60
  /**
@@ -50,6 +50,8 @@ class MLNoiseSuppressor {
50
50
  this.processingQueue = [];
51
51
  this.outputQueue = [];
52
52
  this.isProcessing = false;
53
+ // High-pass filter state for voice optimization (remove <80Hz rumble)
54
+ this.highPassFilter = null;
53
55
  }
54
56
  /**
55
57
  * Initialize the ML noise suppressor
@@ -220,22 +222,36 @@ class MLNoiseSuppressor {
220
222
  return inputStream;
221
223
  }
222
224
  try {
223
- console.log('🎤 Setting up ML noise suppression pipeline...');
225
+ console.log('🎤 [ML] Setting up noise suppression pipeline...');
226
+ console.log('🎤 [ML] Input stream tracks:', inputStream.getTracks().length);
224
227
  // Create MediaStreamSource from input
225
228
  const source = this.audioContext.createMediaStreamSource(inputStream);
229
+ console.log('🎤 [ML] Created MediaStreamSource');
230
+ // Create high-pass filter to remove rumble (<80Hz)
231
+ // This improves voice clarity and matches Google Meet quality
232
+ this.highPassFilter = this.audioContext.createBiquadFilter();
233
+ this.highPassFilter.type = 'highpass';
234
+ this.highPassFilter.frequency.value = 80; // Remove frequencies below 80Hz
235
+ this.highPassFilter.Q.value = 0.7; // Gentle slope
236
+ console.log('🎤 [ML] Created high-pass filter (80Hz cutoff)');
226
237
  // Create destination for output
227
238
  const destination = this.audioContext.createMediaStreamDestination();
239
+ console.log('🎤 [ML] Created destination stream');
228
240
  // Create ScriptProcessor for real-time processing
229
241
  const bufferSize = 4096;
230
242
  const processor = this.audioContext.createScriptProcessor(bufferSize, 1, 1);
243
+ console.log('🎤 [ML] Created ScriptProcessor with buffer size:', bufferSize);
231
244
  // Keep reference to prevent garbage collection
232
245
  processor.keepAlive = true;
233
246
  // Start background processing worker
234
247
  this.startBackgroundProcessing();
248
+ console.log('🎤 [ML] Background processing started');
249
+ let processedFrames = 0;
235
250
  // Process audio with buffering strategy
236
251
  processor.onaudioprocess = (event) => {
237
252
  const inputBuffer = event.inputBuffer.getChannelData(0);
238
253
  const outputBuffer = event.outputBuffer.getChannelData(0);
254
+ processedFrames++;
239
255
  // Copy input buffer for processing
240
256
  const bufferCopy = new Float32Array(inputBuffer);
241
257
  this.processingQueue.push(bufferCopy);
@@ -247,20 +263,30 @@ class MLNoiseSuppressor {
247
263
  if (this.outputQueue.length > 0) {
248
264
  const processed = this.outputQueue.shift();
249
265
  outputBuffer.set(processed);
266
+ // Log occasionally
267
+ if (processedFrames % 100 === 0) {
268
+ console.log(`🎤 [ML] Processed ${processedFrames} frames, queue: ${this.processingQueue.length}/${this.outputQueue.length}`);
269
+ }
250
270
  }
251
271
  else {
252
272
  // Pass through original audio if processing is behind
253
273
  outputBuffer.set(inputBuffer);
274
+ // Log when behind
275
+ if (processedFrames % 100 === 0) {
276
+ console.log(`âš ī¸ [ML] Processing behind, passing through (frame ${processedFrames})`);
277
+ }
254
278
  }
255
279
  };
256
- // Connect: source -> processor -> destination
257
- source.connect(processor);
280
+ // Connect: source -> highpass -> processor -> destination
281
+ source.connect(this.highPassFilter);
282
+ this.highPassFilter.connect(processor);
258
283
  processor.connect(destination);
259
- console.log('✅ ML noise suppression pipeline connected with buffering');
284
+ console.log('✅ [ML] Pipeline connected: source -> highpass(80Hz) -> processor -> destination');
285
+ console.log('✅ [ML] Output stream tracks:', destination.stream.getTracks().length);
260
286
  return destination.stream;
261
287
  }
262
288
  catch (error) {
263
- console.error('❌ Failed to process MediaStream:', error);
289
+ console.error('❌ [ML] Failed to process MediaStream:', error);
264
290
  return inputStream;
265
291
  }
266
292
  }
@@ -283,9 +309,6 @@ class MLNoiseSuppressor {
283
309
  if (this.outputQueue.length > 5) {
284
310
  this.outputQueue.shift();
285
311
  }
286
- this.isProcessing = false;
287
- this.processingQueue = [];
288
- this.outputQueue = [];
289
312
  }
290
313
  catch (error) {
291
314
  // On error, pass through original
@@ -301,32 +324,56 @@ class MLNoiseSuppressor {
301
324
  processLoop();
302
325
  }
303
326
  /**
304
- * Fast audio processing with simplified ML (optimized version)
327
+ * Fast audio processing optimized for voice quality
328
+ * Preserves voice fundamentals (80-250Hz) while reducing noise
305
329
  */
306
330
  async processAudioFast(inputBuffer) {
307
331
  if (!this.model || !this.config || !this.normStats) {
308
332
  return inputBuffer;
309
333
  }
310
334
  try {
311
- // Simplified fast processing - just apply a learned mask pattern
312
- // This is much faster than full LSTM inference
313
335
  const output = new Float32Array(inputBuffer.length);
314
- // Apply simple spectral gating based on energy
315
- const windowSize = 256;
316
- for (let i = 0; i < inputBuffer.length; i += windowSize) {
336
+ // Use smaller windows for better voice quality
337
+ const windowSize = 128;
338
+ const overlapFactor = 0.5;
339
+ const hopSize = Math.floor(windowSize * (1 - overlapFactor));
340
+ // Apply gentle noise reduction that preserves voice
341
+ for (let i = 0; i < inputBuffer.length; i += hopSize) {
317
342
  const end = Math.min(i + windowSize, inputBuffer.length);
318
343
  const window = inputBuffer.slice(i, end);
319
- // Calculate energy
344
+ // Calculate RMS energy
320
345
  let energy = 0;
321
346
  for (let j = 0; j < window.length; j++) {
322
347
  energy += window[j] * window[j];
323
348
  }
324
- energy = Math.sqrt(energy / window.length);
325
- // Apply learned threshold-based gating
326
- const threshold = 0.01; // Learned from training data
327
- const gain = energy > threshold ? 1.0 : 0.3;
328
- for (let j = i; j < end; j++) {
329
- output[j] = inputBuffer[j] * gain;
349
+ const rms = Math.sqrt(energy / window.length);
350
+ // Voice-optimized noise gate
351
+ // Lower threshold to preserve quiet speech
352
+ // Softer transition to avoid artifacts
353
+ const threshold = 0.005; // More sensitive for voice
354
+ const ratio = 0.5; // Gentler reduction
355
+ let gain;
356
+ if (rms > threshold * 2) {
357
+ // Clear voice - pass through
358
+ gain = 1.0;
359
+ }
360
+ else if (rms > threshold) {
361
+ // Transition zone - smooth interpolation
362
+ const t = (rms - threshold) / threshold;
363
+ gain = 0.7 + (0.3 * t); // 0.7 to 1.0
364
+ }
365
+ else {
366
+ // Likely noise - reduce gently
367
+ gain = 0.7; // Much less aggressive than before (was 0.3)
368
+ }
369
+ // Apply gain with smoothing to reduce artifacts
370
+ for (let j = i; j < end && j < inputBuffer.length; j++) {
371
+ // Blend with previous sample for smoothness
372
+ const blendFactor = (j - i) / windowSize;
373
+ const smoothGain = output[j - 1] !== undefined
374
+ ? gain * blendFactor + (1 - blendFactor) * (output[j - 1] / (inputBuffer[j - 1] || 1))
375
+ : gain;
376
+ output[j] = inputBuffer[j] * smoothGain;
330
377
  }
331
378
  }
332
379
  return output;
@@ -363,6 +410,13 @@ class MLNoiseSuppressor {
363
410
  * Cleanup resources
364
411
  */
365
412
  dispose() {
413
+ this.isProcessing = false;
414
+ this.processingQueue = [];
415
+ this.outputQueue = [];
416
+ if (this.highPassFilter) {
417
+ this.highPassFilter.disconnect();
418
+ this.highPassFilter = null;
419
+ }
366
420
  if (this.model) {
367
421
  this.model.dispose();
368
422
  this.model = null;
package/dist/index.js CHANGED
@@ -145,22 +145,30 @@ class OdysseySpatialComms extends EventManager_1.EventManager {
145
145
  return this.mlNoiseSuppressionEnabled && this.mlNoiseSuppressor !== null;
146
146
  }
147
147
  async produceTrack(track, appData) {
148
+ console.log(`đŸŽŦ [SDK] produceTrack called - kind: ${track.kind}, enabled: ${track.enabled}, readyState: ${track.readyState}`);
148
149
  let processedTrack = track;
149
150
  // Apply ML noise suppression to audio BEFORE sending to MediaSoup
150
151
  if (track.kind === 'audio' && this.mlNoiseSuppressionEnabled && this.mlNoiseSuppressor) {
151
152
  try {
152
- console.log('🎤 Applying ML noise suppression to audio...');
153
+ console.log('🎤 [SDK] Applying ML noise suppression to audio...');
153
154
  const inputStream = new MediaStream([track]);
155
+ console.log('🎤 [SDK] Created input stream with track');
154
156
  const cleanedStream = await this.mlNoiseSuppressor.processMediaStream(inputStream);
157
+ console.log('🎤 [SDK] Got cleaned stream from ML');
155
158
  processedTrack = cleanedStream.getAudioTracks()[0];
156
- console.log('✅ ML noise suppression applied');
159
+ console.log(`✅ [SDK] ML noise suppression applied - processed track state: ${processedTrack.readyState}`);
157
160
  }
158
161
  catch (error) {
159
- console.error('❌ ML noise suppression failed, using original track:', error);
162
+ console.error('❌ [SDK] ML noise suppression failed, using original track:', error);
160
163
  processedTrack = track; // Fallback to original track
161
164
  }
162
165
  }
166
+ else {
167
+ console.log(`â„šī¸ [SDK] Skipping ML - kind: ${track.kind}, ML enabled: ${this.mlNoiseSuppressionEnabled}`);
168
+ }
169
+ console.log(`📤 [SDK] Producing track to MediaSoup - kind: ${processedTrack.kind}, state: ${processedTrack.readyState}`);
163
170
  const producer = await this.mediasoupManager.produce(processedTrack, appData);
171
+ console.log(`✅ [SDK] Producer created - id: ${producer.id}, kind: ${producer.kind}`);
164
172
  if (this.localParticipant) {
165
173
  const isFirstProducer = this.localParticipant.producers.size === 0;
166
174
  this.localParticipant.producers.set(producer.id, producer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newgameplusinc/odyssey-audio-video-sdk-dev",
3
- "version": "1.0.51",
3
+ "version": "1.0.53",
4
4
  "description": "Odyssey Spatial Audio & Video SDK using MediaSoup for real-time communication with AI-powered noise suppression",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",