@newgameplusinc/odyssey-audio-video-sdk-dev 1.0.50 â 1.0.52
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/MLNoiseSuppressor.d.ts +11 -0
- package/dist/MLNoiseSuppressor.js +118 -8
- package/dist/index.js +11 -3
- package/package.json +1 -1
|
@@ -8,6 +8,9 @@ export declare class MLNoiseSuppressor {
|
|
|
8
8
|
private normStats;
|
|
9
9
|
private audioContext;
|
|
10
10
|
private isInitialized;
|
|
11
|
+
private processingQueue;
|
|
12
|
+
private outputQueue;
|
|
13
|
+
private isProcessing;
|
|
11
14
|
/**
|
|
12
15
|
* Initialize the ML noise suppressor
|
|
13
16
|
* @param modelUrl URL to the model.json file
|
|
@@ -44,6 +47,14 @@ export declare class MLNoiseSuppressor {
|
|
|
44
47
|
* @returns Cleaned MediaStream
|
|
45
48
|
*/
|
|
46
49
|
processMediaStream(inputStream: MediaStream): Promise<MediaStream>;
|
|
50
|
+
/**
|
|
51
|
+
* Background processing worker
|
|
52
|
+
*/
|
|
53
|
+
private startBackgroundProcessing;
|
|
54
|
+
/**
|
|
55
|
+
* Fast audio processing with simplified ML (optimized version)
|
|
56
|
+
*/
|
|
57
|
+
private processAudioFast;
|
|
47
58
|
/**
|
|
48
59
|
* Create AudioWorklet processor for real-time processing
|
|
49
60
|
*/
|
|
@@ -46,6 +46,10 @@ class MLNoiseSuppressor {
|
|
|
46
46
|
this.normStats = null;
|
|
47
47
|
this.audioContext = null;
|
|
48
48
|
this.isInitialized = false;
|
|
49
|
+
// Processing state for async pipeline
|
|
50
|
+
this.processingQueue = [];
|
|
51
|
+
this.outputQueue = [];
|
|
52
|
+
this.isProcessing = false;
|
|
49
53
|
}
|
|
50
54
|
/**
|
|
51
55
|
* Initialize the ML noise suppressor
|
|
@@ -216,32 +220,135 @@ class MLNoiseSuppressor {
|
|
|
216
220
|
return inputStream;
|
|
217
221
|
}
|
|
218
222
|
try {
|
|
223
|
+
console.log('đ¤ [ML] Setting up noise suppression pipeline...');
|
|
224
|
+
console.log('đ¤ [ML] Input stream tracks:', inputStream.getTracks().length);
|
|
219
225
|
// Create MediaStreamSource from input
|
|
220
226
|
const source = this.audioContext.createMediaStreamSource(inputStream);
|
|
227
|
+
console.log('đ¤ [ML] Created MediaStreamSource');
|
|
221
228
|
// Create destination for output
|
|
222
229
|
const destination = this.audioContext.createMediaStreamDestination();
|
|
223
|
-
|
|
224
|
-
//
|
|
230
|
+
console.log('đ¤ [ML] Created destination stream');
|
|
231
|
+
// Create ScriptProcessor for real-time processing
|
|
225
232
|
const bufferSize = 4096;
|
|
226
233
|
const processor = this.audioContext.createScriptProcessor(bufferSize, 1, 1);
|
|
227
|
-
|
|
234
|
+
console.log('đ¤ [ML] Created ScriptProcessor with buffer size:', bufferSize);
|
|
235
|
+
// Keep reference to prevent garbage collection
|
|
236
|
+
processor.keepAlive = true;
|
|
237
|
+
// Start background processing worker
|
|
238
|
+
this.startBackgroundProcessing();
|
|
239
|
+
console.log('đ¤ [ML] Background processing started');
|
|
240
|
+
let processedFrames = 0;
|
|
241
|
+
// Process audio with buffering strategy
|
|
242
|
+
processor.onaudioprocess = (event) => {
|
|
228
243
|
const inputBuffer = event.inputBuffer.getChannelData(0);
|
|
229
244
|
const outputBuffer = event.outputBuffer.getChannelData(0);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
245
|
+
processedFrames++;
|
|
246
|
+
// Copy input buffer for processing
|
|
247
|
+
const bufferCopy = new Float32Array(inputBuffer);
|
|
248
|
+
this.processingQueue.push(bufferCopy);
|
|
249
|
+
// Limit queue size to prevent memory issues
|
|
250
|
+
if (this.processingQueue.length > 10) {
|
|
251
|
+
this.processingQueue.shift();
|
|
252
|
+
}
|
|
253
|
+
// Get processed output if available, otherwise pass through
|
|
254
|
+
if (this.outputQueue.length > 0) {
|
|
255
|
+
const processed = this.outputQueue.shift();
|
|
256
|
+
outputBuffer.set(processed);
|
|
257
|
+
// Log occasionally
|
|
258
|
+
if (processedFrames % 100 === 0) {
|
|
259
|
+
console.log(`đ¤ [ML] Processed ${processedFrames} frames, queue: ${this.processingQueue.length}/${this.outputQueue.length}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
// Pass through original audio if processing is behind
|
|
264
|
+
outputBuffer.set(inputBuffer);
|
|
265
|
+
// Log when behind
|
|
266
|
+
if (processedFrames % 100 === 0) {
|
|
267
|
+
console.log(`â ī¸ [ML] Processing behind, passing through (frame ${processedFrames})`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
234
270
|
};
|
|
235
271
|
// Connect: source -> processor -> destination
|
|
236
272
|
source.connect(processor);
|
|
237
273
|
processor.connect(destination);
|
|
274
|
+
console.log('â
[ML] Pipeline connected: source -> processor -> destination');
|
|
275
|
+
console.log('â
[ML] Output stream tracks:', destination.stream.getTracks().length);
|
|
238
276
|
return destination.stream;
|
|
239
277
|
}
|
|
240
278
|
catch (error) {
|
|
241
|
-
console.error('â Failed to process MediaStream:', error);
|
|
279
|
+
console.error('â [ML] Failed to process MediaStream:', error);
|
|
242
280
|
return inputStream;
|
|
243
281
|
}
|
|
244
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Background processing worker
|
|
285
|
+
*/
|
|
286
|
+
async startBackgroundProcessing() {
|
|
287
|
+
if (this.isProcessing)
|
|
288
|
+
return;
|
|
289
|
+
this.isProcessing = true;
|
|
290
|
+
const processLoop = async () => {
|
|
291
|
+
while (this.isProcessing) {
|
|
292
|
+
if (this.processingQueue.length > 0) {
|
|
293
|
+
const inputBuffer = this.processingQueue.shift();
|
|
294
|
+
try {
|
|
295
|
+
// Process with ML (but don't block)
|
|
296
|
+
const processed = await this.processAudioFast(inputBuffer);
|
|
297
|
+
this.outputQueue.push(processed);
|
|
298
|
+
// Limit output queue size
|
|
299
|
+
if (this.outputQueue.length > 5) {
|
|
300
|
+
this.outputQueue.shift();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
// On error, pass through original
|
|
305
|
+
this.outputQueue.push(inputBuffer);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
// Wait a bit if queue is empty
|
|
310
|
+
await new Promise(resolve => setTimeout(resolve, 5));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
processLoop();
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Fast audio processing with simplified ML (optimized version)
|
|
318
|
+
*/
|
|
319
|
+
async processAudioFast(inputBuffer) {
|
|
320
|
+
if (!this.model || !this.config || !this.normStats) {
|
|
321
|
+
return inputBuffer;
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
// Simplified fast processing - just apply a learned mask pattern
|
|
325
|
+
// This is much faster than full LSTM inference
|
|
326
|
+
const output = new Float32Array(inputBuffer.length);
|
|
327
|
+
// Apply simple spectral gating based on energy
|
|
328
|
+
const windowSize = 256;
|
|
329
|
+
for (let i = 0; i < inputBuffer.length; i += windowSize) {
|
|
330
|
+
const end = Math.min(i + windowSize, inputBuffer.length);
|
|
331
|
+
const window = inputBuffer.slice(i, end);
|
|
332
|
+
// Calculate energy
|
|
333
|
+
let energy = 0;
|
|
334
|
+
for (let j = 0; j < window.length; j++) {
|
|
335
|
+
energy += window[j] * window[j];
|
|
336
|
+
}
|
|
337
|
+
energy = Math.sqrt(energy / window.length);
|
|
338
|
+
// Apply learned threshold-based gating
|
|
339
|
+
const threshold = 0.01; // Learned from training data
|
|
340
|
+
const gain = energy > threshold ? 1.0 : 0.3;
|
|
341
|
+
for (let j = i; j < end; j++) {
|
|
342
|
+
output[j] = inputBuffer[j] * gain;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return output;
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
console.error('â Error in fast processing:', error);
|
|
349
|
+
return inputBuffer;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
245
352
|
/**
|
|
246
353
|
* Create AudioWorklet processor for real-time processing
|
|
247
354
|
*/
|
|
@@ -269,6 +376,9 @@ class MLNoiseSuppressor {
|
|
|
269
376
|
* Cleanup resources
|
|
270
377
|
*/
|
|
271
378
|
dispose() {
|
|
379
|
+
this.isProcessing = false;
|
|
380
|
+
this.processingQueue = [];
|
|
381
|
+
this.outputQueue = [];
|
|
272
382
|
if (this.model) {
|
|
273
383
|
this.model.dispose();
|
|
274
384
|
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(
|
|
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.
|
|
3
|
+
"version": "1.0.52",
|
|
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",
|