openwakeword-js 0.1.18 → 0.1.20

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/index.html CHANGED
@@ -5,6 +5,7 @@
5
5
  <meta charset="utf-8" />
6
6
  <meta content="width=device-width, initial-scale=1.0" name="viewport" />
7
7
  <title>AI Wake Word Detector | OpenWakeWord JS</title>
8
+
8
9
  <!-- Fonts -->
9
10
  <link href="https://fonts.googleapis.com" rel="preconnect" />
10
11
  <link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect" />
@@ -286,7 +287,7 @@
286
287
  class="h-44 overflow-y-auto rounded-xl bg-[#030406] p-4 font-mono text-[10px] text-slate-600 border border-white/5 custom-scrollbar leading-relaxed">
287
288
  <div
288
289
  class="opacity-40 animate-pulse font-bold text-blue-900 border border-blue-900/40 p-1 inline-block rounded mb-2">
289
- OPENWAKEWORD-JS V0.1.17 READY</div>
290
+ OPENWAKEWORD-JS V0.1.19 READY</div>
290
291
  </div>
291
292
  </div>
292
293
  </details>
@@ -305,9 +306,12 @@
305
306
  model: null,
306
307
  audioContext: null,
307
308
  processor: null,
308
- startTime: Date.now()
309
+ startTime: Date.now(),
310
+ lastActivationTime: 0,
311
+ cooldownSeconds: 2.0
309
312
  };
310
313
 
314
+
311
315
  const ui = {
312
316
  btn: document.getElementById('toggleBtn'),
313
317
  btnText: document.getElementById('btnText'),
@@ -345,12 +349,13 @@
345
349
  log('Neural buffers allocating locally...', 'SYSTEM');
346
350
  try {
347
351
  state.model = new Model({
348
- wakewordModels: ['./models/hello_deepa.onnx', './models/namaste_deepa.onnx'],
352
+ wakewordModels: ['./models/hello_deepa.onnx', './models/namaste_deepa.onnx', './models/hello_deepa_old.onnx'],
349
353
  melspectrogramModelPath: './models/melspectrogram.onnx',
350
354
  embeddingModelPath: './models/embedding_model.onnx',
351
- vadModelPath: './models/silero_vad.onnx',
352
355
  inferenceFramework: 'onnx',
353
356
  wasmPaths: './models/'
357
+
358
+
354
359
  });
355
360
  await state.model.init();
356
361
  log('Neural intelligence hub connected.', 'SYSTEM');
@@ -363,16 +368,33 @@
363
368
 
364
369
  function pushDetection(name, score) {
365
370
  if (ui.empty) ui.empty.style.display = 'none';
371
+ const isHello = name.includes('hello_deepa');
372
+ const isNamaste = name.includes('namaste');
373
+
374
+ let displayText = name.replace(/_/g, ' ');
375
+ let langLabel = "Neural Activation Triggered";
376
+ let langColor = "text-slate-500";
377
+
378
+ if (isHello) {
379
+ displayText = "Hello Deepa Activated..";
380
+ langLabel = "Language: English";
381
+ langColor = "text-blue-400";
382
+ } else if (isNamaste) {
383
+ displayText = "Namaste Deepa Activated..";
384
+ langLabel = "Language: Nepali";
385
+ langColor = "text-red-400";
386
+ }
387
+
366
388
  const card = document.createElement('div');
367
- card.className = "detection-card flex items-center justify-between p-5 bg-white/5 backdrop-blur-md rounded-[1.5rem] border border-white/10 shadow-lg";
389
+ card.className = "group flex flex-row items-center justify-between p-5 bg-white/[0.03] border border-white/5 rounded-2xl hover:bg-white/[0.05] hover:border-white/10 transition-all duration-300 transform animate-in fade-in slide-in-from-right-4";
368
390
  card.innerHTML = `
369
391
  <div class="flex items-center gap-4">
370
- <div class="size-12 rounded-full bg-emerald-500/10 flex items-center justify-center text-emerald-400 border border-emerald-500/20 shadow-inner">
392
+ <div class="size-12 rounded-full overflow-hidden flex items-center justify-center bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 shadow-[0_0_20px_rgba(16,185,129,0.1)]">
371
393
  <svg class="size-7" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round"/></svg>
372
394
  </div>
373
395
  <div class="flex flex-col">
374
- <span class="text-white font-bold capitalize text-base tracking-tight">${name.replace(/_/g, ' ')}</span>
375
- <span class="text-[9px] text-slate-500 font-black uppercase tracking-[0.1em]">Neural Activation Triggered</span>
396
+ <span class="text-white font-bold capitalize text-base tracking-tight">${displayText}</span>
397
+ <span class="text-[9px] ${langColor} font-black uppercase tracking-[0.1em]">${langLabel}</span>
376
398
  </div>
377
399
  </div>
378
400
  <div class="flex flex-col items-end gap-1">
@@ -384,6 +406,7 @@
384
406
  if (ui.stack.children.length > 5) ui.stack.lastElementChild.remove();
385
407
  }
386
408
 
409
+
387
410
  async function toggle() {
388
411
  if (state.isListening) {
389
412
  state.isListening = false;
@@ -406,26 +429,62 @@
406
429
  state.audioContext = new AudioContext({ sampleRate: 16000 });
407
430
  const source = state.audioContext.createMediaStreamSource(stream);
408
431
 
409
- // FIXED: Power of two buffer
410
- state.processor = state.audioContext.createScriptProcessor(1024, 1, 1);
432
+ // Robust processing queue to prevent re-entrancy and state corruption
433
+ const audioQueue = [];
434
+ let isProcessing = false;
435
+
436
+ async function processQueue() {
437
+ if (isProcessing || audioQueue.length === 0) return;
438
+ isProcessing = true;
411
439
 
440
+ while (audioQueue.length > 0) {
441
+ const data = audioQueue.shift();
442
+
443
+ // Python-style Cooldown Check
444
+ const currentTime = Date.now();
445
+ if (currentTime - state.lastActivationTime < state.cooldownSeconds * 1000) {
446
+ continue;
447
+ }
448
+
449
+ try {
450
+ const results = await state.model.predict(data);
451
+ for (const [n, s] of Object.entries(results)) {
452
+ if (s > state.threshold) {
453
+ const clean = n.split('/').pop().replace('.onnx', '');
454
+
455
+ // Detection Triggered
456
+ state.lastActivationTime = Date.now();
457
+ pushDetection(clean, s);
458
+ log(`Match Found: ${clean} (${s.toFixed(2)})`, 'MATCH');
459
+
460
+ // Python-style Reset & Clear Queue
461
+ if (state.model.reset) state.model.reset();
462
+ audioQueue.length = 0; // Clear the queue
463
+ break; // Only trigger one wake word at a time
464
+ }
465
+ }
466
+ } catch (err) {
467
+ log(`Inference Error: ${err.message}`, 'ERROR');
468
+ }
469
+ }
470
+
471
+ isProcessing = false;
472
+ }
473
+
474
+
475
+ state.processor = state.audioContext.createScriptProcessor(2048, 1, 1);
412
476
  source.connect(state.processor);
413
477
  state.processor.connect(state.audioContext.destination);
414
478
 
415
- state.processor.onaudioprocess = async (e) => {
479
+ state.processor.onaudioprocess = (e) => {
416
480
  if (!state.isListening) return;
417
- const data = e.inputBuffer.getChannelData(0);
418
- const results = await state.model.predict(data);
419
- for (const [n, s] of Object.entries(results)) {
420
- if (s > state.threshold) {
421
- const clean = n.split('/').pop().replace('.onnx', '');
422
- pushDetection(clean, s);
423
- log(`Match Found: ${clean} (${s.toFixed(2)})`, 'MATCH');
424
- }
425
- }
481
+ const data = new Float32Array(e.inputBuffer.getChannelData(0));
482
+ audioQueue.push(data);
483
+ processQueue();
426
484
  };
427
485
 
428
486
  state.isListening = true;
487
+
429
488
  ui.orbRing.classList.add('animate-pulse-ring');
430
489
  ui.orbIcon.classList.remove('opacity-40');
431
490
  ui.orbLabel.textContent = "listening";
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openwakeword-js",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Port of openWakeWord to JavaScript/TypeScript using ONNX Runtime",
5
5
  "bin": {
6
6
  "openwakeword-js-setup": "scripts/download_models.js"
@@ -8,14 +8,14 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
8
  const packageRoot = path.join(__dirname, '..');
9
9
  const MODELS_DIR = path.join(process.cwd(), 'models');
10
10
 
11
- const MODELS = {
11
+ const EXTERNAL_MODELS = {
12
12
  'melspectrogram.onnx': 'https://github.com/dscripka/openWakeWord/releases/download/v0.5.1/melspectrogram.onnx',
13
13
  'embedding_model.onnx': 'https://github.com/dscripka/openWakeWord/releases/download/v0.5.1/embedding_model.onnx',
14
- 'silero_vad.onnx': 'https://github.com/dscripka/openWakeWord/releases/download/v0.5.1/silero_vad.onnx',
15
- 'hello_deepa.onnx': 'https://github.com/Firojpaudel/OpenWakeWord_npm_porting/raw/main/models/hello_deepa.onnx',
16
- 'namaste_deepa.onnx': 'https://github.com/Firojpaudel/OpenWakeWord_npm_porting/raw/main/models/namaste_deepa.onnx'
14
+ 'silero_vad.onnx': 'https://github.com/dscripka/openWakeWord/releases/download/v0.5.1/silero_vad.onnx'
17
15
  };
18
16
 
17
+ const SAMPLE_MODELS = ['hello_deepa.onnx', 'hello_deepa_old.onnx', 'namaste_deepa.onnx'];
18
+
19
19
  async function downloadFile(url, dest) {
20
20
  return new Promise((resolve, reject) => {
21
21
  https.get(url, (response) => {
@@ -55,8 +55,8 @@ async function main() {
55
55
  console.log(`Created directory: ${MODELS_DIR}`);
56
56
  }
57
57
 
58
- console.log('Downloading neural model binaries...');
59
- for (const [name, url] of Object.entries(MODELS)) {
58
+ console.log('Deploying base neural model binaries...');
59
+ for (const [name, url] of Object.entries(EXTERNAL_MODELS)) {
60
60
  const dest = path.join(MODELS_DIR, name);
61
61
  if (fs.existsSync(dest) && fs.statSync(dest).size > 1000000) {
62
62
  console.log(`- ${name} already exists and validated, skipping.`);
@@ -70,34 +70,37 @@ async function main() {
70
70
  console.log(`Failed: ${err.message}`);
71
71
  }
72
72
  }
73
- console.log('\nAI Model deployment complete.');
73
+
74
+ console.log('\nDeploying sample wake-word models from package...');
75
+ for (const name of SAMPLE_MODELS) {
76
+ const src = path.join(packageRoot, 'models', name);
77
+ const dest = path.join(MODELS_DIR, name);
78
+ copyIfExists(src, dest, 'Sample Model');
79
+ }
74
80
 
75
81
  console.log('\nDeploying ONNX Runtime WebAssembly environment...');
76
82
  const nodeModulesPath = path.join(process.cwd(), 'node_modules', 'onnxruntime-web', 'dist');
77
83
 
78
84
  if (fs.existsSync(nodeModulesPath)) {
79
- // Copy EVERYTHING starting with ort-wasm to ensure all loaders/workers are present
80
85
  const runtimeFiles = fs.readdirSync(nodeModulesPath).filter(f =>
81
86
  f.startsWith('ort-wasm') && (f.endsWith('.wasm') || f.endsWith('.mjs') || f.endsWith('.js'))
82
87
  );
83
88
  for (const file of runtimeFiles) {
84
89
  copyIfExists(path.join(nodeModulesPath, file), path.join(MODELS_DIR, file), 'RUNTIME');
85
90
  }
86
- } else {
87
- console.log('Warning: onnxruntime-web not found in node_modules. Standard inference may fail.');
91
+ // Also copy the main entry point if not already there
92
+ copyIfExists(path.join(nodeModulesPath, 'ort.mjs'), path.join(MODELS_DIR, 'ort.mjs'), 'RUNTIME_ENTRY');
88
93
  }
89
94
 
90
95
  console.log('\nDeploying optimized AI Listening Interface...');
91
- const exampleHtml = path.join(packageRoot, 'index.html');
92
- const destHtml = path.join(process.cwd(), 'index.html');
93
- copyIfExists(exampleHtml, destHtml, 'UI');
96
+ copyIfExists(path.join(packageRoot, 'index.html'), path.join(process.cwd(), 'index.html'), 'UI');
97
+ copyIfExists(path.join(packageRoot, 'openwakeword.mjs'), path.join(process.cwd(), 'openwakeword.mjs'), 'Library');
94
98
 
95
- const libSrc = path.join(packageRoot, 'dist', 'index.js');
96
- const libDest = path.join(process.cwd(), 'openwakeword.mjs');
97
- copyIfExists(libSrc, libDest, 'Library');
99
+ // Copy test.html too
100
+ copyIfExists(path.join(packageRoot, 'models', 'test.html'), path.join(MODELS_DIR, 'test.html'), 'Debug UI');
98
101
 
99
102
  console.log('\n----------------------------------------------------');
100
- console.log('SETUP COMPLETE (v0.1.15)');
103
+ console.log('SETUP COMPLETE (v0.1.19)');
101
104
  console.log('----------------------------------------------------');
102
105
  console.log('Your precision AI wake word interface is ready.');
103
106
  console.log('\nTo start the demo:');