@tekyzinc/stt-component 0.3.0 → 0.3.2

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/index.cjs CHANGED
@@ -295,8 +295,10 @@ var WorkerManager = class extends TypedEventEmitter {
295
295
  };
296
296
 
297
297
  // src/correction-orchestrator.ts
298
+ var INITIAL_CORRECTION_DELAY_MS = 1e3;
298
299
  var CorrectionOrchestrator = class {
299
300
  forcedTimer = null;
301
+ initialTimer = null;
300
302
  lastCorrectionTime = 0;
301
303
  correctionFn = null;
302
304
  config;
@@ -308,14 +310,25 @@ var CorrectionOrchestrator = class {
308
310
  setCorrectionFn(fn) {
309
311
  this.correctionFn = fn;
310
312
  }
311
- /** Start the correction orchestrator (begin forced interval timer). */
313
+ /** Start the correction orchestrator.
314
+ * Fires a quick initial correction after 1s for early feedback, then
315
+ * switches to the regular forcedInterval cadence from that point. */
312
316
  start() {
313
317
  if (!this.config.enabled) return;
314
318
  this.lastCorrectionTime = Date.now();
315
- this.startForcedTimer();
319
+ this.initialTimer = setTimeout(() => {
320
+ this.initialTimer = null;
321
+ this.correctionFn?.();
322
+ this.lastCorrectionTime = Date.now();
323
+ this.startForcedTimer();
324
+ }, INITIAL_CORRECTION_DELAY_MS);
316
325
  }
317
326
  /** Stop the orchestrator (clear all timers). */
318
327
  stop() {
328
+ if (this.initialTimer) {
329
+ clearTimeout(this.initialTimer);
330
+ this.initialTimer = null;
331
+ }
319
332
  this.stopForcedTimer();
320
333
  }
321
334
  /** Called when a speech pause is detected. Triggers correction if cooldown elapsed. */
@@ -424,12 +437,22 @@ function toBCP47(language) {
424
437
  return WHISPER_TO_BCP47[language.toLowerCase()] ?? language;
425
438
  }
426
439
  var NO_RESULT_TIMEOUT_MS = 5e3;
440
+ var IDLE_TIMEOUT_MS = 3e4;
427
441
  var SpeechStreamingManager = class {
428
442
  recognition = null;
429
443
  accumulated = "";
430
444
  active = false;
445
+ // user is recording — emit results, trigger onPause on end
446
+ keepingWarm = false;
447
+ // recognition running silently between user sessions
448
+ currentLang = "";
449
+ // BCP-47 of running recognition (for fast-path check)
431
450
  receivedResult = false;
451
+ lastFinalIndex = -1;
452
+ // instance fields so warm-restart resets them cleanly
453
+ lastFinalText = "";
432
454
  noResultTimer = null;
455
+ idleTimer = null;
433
456
  onTranscript = null;
434
457
  onPause = null;
435
458
  onError = null;
@@ -459,14 +482,25 @@ var SpeechStreamingManager = class {
459
482
  console.warn(message);
460
483
  }
461
484
  /**
462
- * Start streaming recognition. Returns a Promise that resolves once
463
- * SpeechRecognition has claimed the microphone (onaudiostart) or after
464
- * a 300ms fallback whichever comes first. The engine should await
465
- * this before calling getUserMedia to avoid dual-mic conflicts.
466
- *
467
- * When skipMicWait is true (warm restart — mic already active), returns
468
- * immediately after calling recognition.start() without waiting for
469
- * onaudiostart.
485
+ * Pre-warm: start recognition in muted mode so it's ready before the user
486
+ * clicks. Call after engine.init() completes. Eliminates startup latency on
487
+ * first click by keeping the Google Speech session alive.
488
+ */
489
+ preWarm(language) {
490
+ const SR = getSpeechRecognition();
491
+ if (!SR) return;
492
+ const bcp47 = toBCP47(language);
493
+ if (this.recognition && this.currentLang === bcp47) return;
494
+ this.log(`[SSM] preWarm() \u2014 lang: "${language}" \u2192 "${bcp47}"`);
495
+ this.keepingWarm = true;
496
+ this.active = false;
497
+ this.clearIdleTimer();
498
+ this.spawnRecognition(language);
499
+ }
500
+ /**
501
+ * Start streaming recognition. If recognition is already warm (session
502
+ * running from preWarm or a previous session within the idle window),
503
+ * activates instantly — no Google handshake. Otherwise cold-starts.
470
504
  */
471
505
  start(language, skipMicWait = false) {
472
506
  const SR = getSpeechRecognition();
@@ -477,14 +511,49 @@ var SpeechStreamingManager = class {
477
511
  const bcp47 = toBCP47(language);
478
512
  this.log(`[SSM] start() \u2014 lang: "${language}" \u2192 "${bcp47}"`);
479
513
  this.accumulated = "";
480
- this.active = true;
481
514
  this.receivedResult = false;
515
+ this.lastFinalIndex = -1;
516
+ this.lastFinalText = "";
517
+ this.clearIdleTimer();
518
+ if (this.recognition && this.currentLang === bcp47) {
519
+ this.log("[SSM] start() \u2014 warm session, activating immediately");
520
+ this.keepingWarm = false;
521
+ this.active = true;
522
+ this.clearNoResultTimer();
523
+ this.noResultTimer = setTimeout(() => {
524
+ if (this.active && !this.receivedResult) {
525
+ this.log("[SSM] no-result timeout fired \u2014 no onresult in 5s");
526
+ this.onError?.(
527
+ "Speech streaming started but received no results. Mic may be blocked by another audio capture."
528
+ );
529
+ }
530
+ }, NO_RESULT_TIMEOUT_MS);
531
+ return Promise.resolve();
532
+ }
533
+ this.keepingWarm = false;
534
+ this.active = true;
535
+ return this.spawnRecognition(language, skipMicWait);
536
+ }
537
+ /**
538
+ * Create and start a new SpeechRecognition instance.
539
+ * Used by both preWarm() (active=false) and start() cold path (active=true).
540
+ */
541
+ spawnRecognition(language, skipMicWait = false) {
542
+ const SR = getSpeechRecognition();
543
+ const bcp47 = toBCP47(language);
544
+ if (this.recognition) {
545
+ const old = this.recognition;
546
+ this.recognition = null;
547
+ try {
548
+ old.stop();
549
+ } catch {
550
+ }
551
+ }
552
+ this.currentLang = bcp47;
482
553
  const recognition = new SR();
483
554
  recognition.continuous = true;
484
555
  recognition.interimResults = true;
485
556
  recognition.lang = bcp47;
486
- let lastFinalIndex = -1;
487
- let lastFinalText = "";
488
557
  let micReady = false;
489
558
  const micClaimPromise = new Promise((resolve) => {
490
559
  recognition.onaudiostart = () => {
@@ -504,26 +573,29 @@ var SpeechStreamingManager = class {
504
573
  }, 300);
505
574
  });
506
575
  this.clearNoResultTimer();
507
- this.noResultTimer = setTimeout(() => {
508
- if (this.active && !this.receivedResult) {
509
- this.log("[SSM] no-result timeout fired \u2014 no onresult in 5s");
510
- this.onError?.(
511
- "Speech streaming started but received no results. Mic may be blocked by another audio capture."
512
- );
513
- }
514
- }, NO_RESULT_TIMEOUT_MS);
576
+ if (this.active) {
577
+ this.noResultTimer = setTimeout(() => {
578
+ if (this.active && !this.receivedResult) {
579
+ this.log("[SSM] no-result timeout fired \u2014 no onresult in 5s");
580
+ this.onError?.(
581
+ "Speech streaming started but received no results. Mic may be blocked by another audio capture."
582
+ );
583
+ }
584
+ }, NO_RESULT_TIMEOUT_MS);
585
+ }
515
586
  recognition.onresult = (e) => {
516
587
  if (this.recognition !== recognition) return;
517
588
  this.receivedResult = true;
518
589
  this.clearNoResultTimer();
590
+ if (!this.active) return;
519
591
  let final_ = "";
520
592
  let interim = "";
521
593
  for (let i = e.resultIndex; i < e.results.length; i++) {
522
594
  const t = e.results[i][0].transcript;
523
595
  if (e.results[i].isFinal) {
524
- if (i > lastFinalIndex) {
596
+ if (i > this.lastFinalIndex) {
525
597
  final_ += t;
526
- lastFinalIndex = i;
598
+ this.lastFinalIndex = i;
527
599
  }
528
600
  } else {
529
601
  interim += t;
@@ -532,8 +604,8 @@ var SpeechStreamingManager = class {
532
604
  this.log(
533
605
  `[SSM] onresult \u2014 finals: "${final_}", interim: "${interim}", accumulated: "${this.accumulated}"`
534
606
  );
535
- if (final_ && final_.trim() !== lastFinalText) {
536
- lastFinalText = final_.trim();
607
+ if (final_ && final_.trim() !== this.lastFinalText) {
608
+ this.lastFinalText = final_.trim();
537
609
  this.accumulated = this.accumulated ? this.accumulated + " " + final_.trim() : final_.trim();
538
610
  this.onTranscript?.(this.accumulated);
539
611
  } else if (interim) {
@@ -545,11 +617,13 @@ var SpeechStreamingManager = class {
545
617
  recognition.onerror = (e) => {
546
618
  if (this.recognition !== recognition) return;
547
619
  this.log(`[SSM] onerror \u2014 ${e.error}`);
548
- this.onError?.(e.error);
620
+ if (this.active) this.onError?.(e.error);
549
621
  };
550
622
  recognition.onend = () => {
551
623
  if (this.recognition !== recognition) return;
552
- this.log(`[SSM] onend \u2014 active: ${this.active}, receivedResult: ${this.receivedResult}`);
624
+ this.log(
625
+ `[SSM] onend \u2014 active: ${this.active}, keepingWarm: ${this.keepingWarm}, receivedResult: ${this.receivedResult}`
626
+ );
553
627
  if (this.active) {
554
628
  this.onPause?.();
555
629
  try {
@@ -558,10 +632,22 @@ var SpeechStreamingManager = class {
558
632
  } catch (err) {
559
633
  this.log(`[SSM] restart THREW: ${err}`);
560
634
  this.recognition = null;
635
+ this.currentLang = "";
561
636
  this.onError?.("Speech recognition failed to restart after pause.");
562
637
  }
638
+ } else if (this.keepingWarm) {
639
+ try {
640
+ recognition.start();
641
+ this.log("[SSM] warm restart");
642
+ } catch (err) {
643
+ this.log(`[SSM] warm restart THREW: ${err}`);
644
+ this.recognition = null;
645
+ this.keepingWarm = false;
646
+ this.currentLang = "";
647
+ }
563
648
  } else {
564
649
  this.recognition = null;
650
+ this.currentLang = "";
565
651
  }
566
652
  };
567
653
  this.recognition = recognition;
@@ -571,15 +657,20 @@ var SpeechStreamingManager = class {
571
657
  } catch (err) {
572
658
  this.log(`[SSM] recognition.start() THREW: ${err}`);
573
659
  this.recognition = null;
660
+ this.currentLang = "";
661
+ const wasActive = this.active;
574
662
  this.active = false;
663
+ this.keepingWarm = false;
575
664
  this.clearNoResultTimer();
576
- this.onError?.(
577
- `Speech recognition failed to start: ${err instanceof Error ? err.message : String(err)}`
578
- );
665
+ if (wasActive) {
666
+ this.onError?.(
667
+ `Speech recognition failed to start: ${err instanceof Error ? err.message : String(err)}`
668
+ );
669
+ }
579
670
  return Promise.resolve();
580
671
  }
581
- if (skipMicWait) {
582
- this.log("[SSM] skipMicWait \u2014 warm restart, returning immediately");
672
+ if (!this.active || skipMicWait) {
673
+ if (skipMicWait) this.log("[SSM] skipMicWait \u2014 warm restart, returning immediately");
583
674
  return Promise.resolve();
584
675
  }
585
676
  return micClaimPromise;
@@ -590,26 +681,50 @@ var SpeechStreamingManager = class {
590
681
  this.noResultTimer = null;
591
682
  }
592
683
  }
593
- /** Stop streaming recognition and return accumulated text. */
684
+ clearIdleTimer() {
685
+ if (this.idleTimer) {
686
+ clearTimeout(this.idleTimer);
687
+ this.idleTimer = null;
688
+ }
689
+ }
690
+ startIdleTimer() {
691
+ this.clearIdleTimer();
692
+ this.idleTimer = setTimeout(() => {
693
+ this.idleTimer = null;
694
+ this.keepingWarm = false;
695
+ this.log("[SSM] idle timeout \u2014 stopping recognition");
696
+ if (this.recognition) {
697
+ const rec = this.recognition;
698
+ this.recognition = null;
699
+ this.currentLang = "";
700
+ try {
701
+ rec.stop();
702
+ } catch {
703
+ }
704
+ }
705
+ }, IDLE_TIMEOUT_MS);
706
+ }
707
+ /** Stop streaming recognition and return accumulated text.
708
+ * Keeps the recognition session alive (muted) for instant restart. */
594
709
  stop() {
595
710
  this.active = false;
711
+ this.keepingWarm = true;
596
712
  this.clearNoResultTimer();
597
- if (this.recognition) {
598
- const rec = this.recognition;
599
- this.recognition = null;
600
- rec.stop();
601
- }
602
713
  const result = this.accumulated;
603
714
  this.accumulated = "";
715
+ this.startIdleTimer();
604
716
  return result;
605
717
  }
606
- /** Abort immediately without returning text. */
718
+ /** Abort immediately and release all resources. */
607
719
  destroy() {
608
720
  this.active = false;
721
+ this.keepingWarm = false;
609
722
  this.clearNoResultTimer();
723
+ this.clearIdleTimer();
610
724
  if (this.recognition) {
611
725
  const rec = this.recognition;
612
726
  this.recognition = null;
727
+ this.currentLang = "";
613
728
  rec.abort();
614
729
  }
615
730
  this.accumulated = "";
@@ -664,6 +779,9 @@ var STTEngine = class extends TypedEventEmitter {
664
779
  await this.workerManager.loadModel(this.config);
665
780
  this.state.isModelLoaded = true;
666
781
  this.updateStatus("ready");
782
+ if (this.config.streaming.enabled) {
783
+ this.speechStreaming.preWarm(this.config.language);
784
+ }
667
785
  } catch (err) {
668
786
  this.emitError("MODEL_LOAD_FAILED", err instanceof Error ? err.message : String(err));
669
787
  this.updateStatus("idle");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/event-emitter.ts","../src/audio-capture.ts","../src/worker-manager.ts","../src/correction-orchestrator.ts","../src/speech-streaming.ts","../src/stt-engine.ts"],"sourcesContent":["// Public types\r\nexport type {\r\n STTModelSize,\r\n STTBackend,\r\n STTStatus,\r\n STTCorrectionProvider,\r\n STTStreamingProvider,\r\n STTCorrectionConfig,\r\n STTStreamingConfig,\r\n STTChunkingConfig,\r\n STTConfig,\r\n ResolvedSTTConfig,\r\n STTState,\r\n STTError,\r\n STTEvents,\r\n AudioCaptureHandle,\r\n} from './types.js';\r\n\r\n// Public values\r\nexport { DEFAULT_STT_CONFIG, resolveConfig } from './types.js';\r\n\r\n// Event emitter\r\nexport { TypedEventEmitter } from './event-emitter.js';\r\n\r\n// Audio capture\r\nexport {\r\n startCapture,\r\n pauseCapture,\r\n resumeCapture,\r\n snapshotAudio,\r\n resampleAudio,\r\n stopCapture,\r\n} from './audio-capture.js';\r\n\r\n// Worker manager\r\nexport { WorkerManager } from './worker-manager.js';\r\nexport type { WorkerManagerEvents } from './worker-manager.js';\r\n\r\n// Correction orchestrator\r\nexport { CorrectionOrchestrator } from './correction-orchestrator.js';\r\n\r\n// Speech streaming\r\nexport { SpeechStreamingManager } from './speech-streaming.js';\r\n\r\n// STT Engine (main public API)\r\nexport { STTEngine } from './stt-engine.js';\r\n","/** Supported Whisper model sizes. */\r\nexport type STTModelSize = 'tiny' | 'base' | 'small' | 'medium';\r\n\r\n/** Supported compute backends. */\r\nexport type STTBackend = 'webgpu' | 'wasm' | 'auto';\r\n\r\n/** Engine lifecycle states. */\r\nexport type STTStatus = 'idle' | 'loading' | 'ready' | 'recording' | 'processing';\r\n\r\n/** Supported correction engine providers. */\r\nexport type STTCorrectionProvider = 'whisper';\r\n\r\n/** Supported real-time streaming providers. */\r\nexport type STTStreamingProvider = 'web-speech-api';\r\n\r\n/** Correction engine configuration. */\r\nexport interface STTCorrectionConfig {\r\n /** Enable mid-recording correction. Default: true */\r\n enabled?: boolean;\r\n /** Correction engine provider. Default: 'whisper' */\r\n provider?: STTCorrectionProvider;\r\n /** Silence duration (ms) before triggering correction. Default: 1000 */\r\n pauseThreshold?: number;\r\n /** Maximum interval (ms) between forced corrections. Default: 3000 */\r\n forcedInterval?: number;\r\n}\r\n\r\n/** Real-time streaming preview configuration. */\r\nexport interface STTStreamingConfig {\r\n /** Enable real-time streaming transcript. Default: true */\r\n enabled?: boolean;\r\n /** Streaming provider. Default: 'web-speech-api' */\r\n provider?: STTStreamingProvider;\r\n}\r\n\r\n/** Audio chunking configuration for long-form audio. */\r\nexport interface STTChunkingConfig {\r\n /** Chunk length in seconds for Whisper processing. Default: 30 */\r\n chunkLengthS?: number;\r\n /** Stride length in seconds for overlapping chunks. Default: 5 */\r\n strideLengthS?: number;\r\n}\r\n\r\n/** Full engine configuration. All fields optional — sensible defaults applied. */\r\nexport interface STTConfig {\r\n /** Whisper model size. Default: 'tiny' */\r\n model?: STTModelSize;\r\n /** Compute backend preference. Default: 'auto' (WebGPU with WASM fallback) */\r\n backend?: STTBackend;\r\n /** Transcription language. Default: 'en' */\r\n language?: string;\r\n /** Model quantization dtype. Default: 'q4' */\r\n dtype?: string;\r\n /** Mid-recording correction settings. */\r\n correction?: STTCorrectionConfig;\r\n /** Audio chunking settings for long-form audio. */\r\n chunking?: STTChunkingConfig;\r\n /** Web Speech API streaming preview settings. */\r\n streaming?: STTStreamingConfig;\r\n}\r\n\r\n/** Resolved configuration with all defaults applied. */\r\nexport interface ResolvedSTTConfig {\r\n model: STTModelSize;\r\n backend: STTBackend;\r\n language: string;\r\n dtype: string;\r\n correction: Required<STTCorrectionConfig>;\r\n chunking: Required<STTChunkingConfig>;\r\n streaming: Required<STTStreamingConfig>;\r\n}\r\n\r\n/** Engine state exposed to consumers via status events. */\r\nexport interface STTState {\r\n status: STTStatus;\r\n isModelLoaded: boolean;\r\n /** Model download progress (0–100). */\r\n loadProgress: number;\r\n /** Active compute backend, or null if not yet determined. */\r\n backend: 'webgpu' | 'wasm' | null;\r\n error: string | null;\r\n}\r\n\r\n/** Structured error emitted via the 'error' event. */\r\nexport interface STTError {\r\n code: string;\r\n message: string;\r\n}\r\n\r\n/** Event map for the typed event emitter. */\r\nexport type STTEvents = {\r\n /** Streaming interim text during recording. */\r\n transcript: (text: string) => void;\r\n /** Whisper-corrected text replacing interim text. */\r\n correction: (text: string) => void;\r\n /** Actionable error (mic denied, model fail, transcription fail). */\r\n error: (error: STTError) => void;\r\n /** Engine state change. */\r\n status: (state: STTState) => void;\r\n /** Diagnostic log for debugging (subscribe to capture all internal events). */\r\n debug: (message: string) => void;\r\n};\r\n\r\n/** Handle returned by audio capture — used internally. */\r\nexport interface AudioCaptureHandle {\r\n audioCtx: AudioContext;\r\n stream: MediaStream;\r\n samples: Float32Array[];\r\n /** Retain reference to prevent GC from stopping audio processing. */\r\n _processor: ScriptProcessorNode;\r\n /** Source node for disconnect/reconnect on pause/resume. */\r\n _source: MediaStreamAudioSourceNode;\r\n /** Gain node (silent) to prevent mic playback. */\r\n _silencer: GainNode;\r\n}\r\n\r\n/** Message sent from main thread to Whisper worker. */\r\nexport interface WorkerMessage {\r\n type: 'load' | 'transcribe' | 'cancel';\r\n audio?: Float32Array;\r\n config?: {\r\n model: string;\r\n backend: STTBackend;\r\n language: string;\r\n dtype: string;\r\n chunkLengthS: number;\r\n strideLengthS: number;\r\n };\r\n}\r\n\r\n/** Response sent from Whisper worker to main thread. */\r\nexport interface WorkerResponse {\r\n type: 'progress' | 'ready' | 'result' | 'error';\r\n data?: unknown;\r\n}\r\n\r\n/** Default configuration values. */\r\nexport const DEFAULT_STT_CONFIG: ResolvedSTTConfig = {\r\n model: 'tiny',\r\n backend: 'auto',\r\n language: 'en',\r\n dtype: 'q4',\r\n correction: {\r\n enabled: true,\r\n provider: 'whisper',\r\n pauseThreshold: 1_000,\r\n forcedInterval: 3_000,\r\n },\r\n chunking: {\r\n chunkLengthS: 30,\r\n strideLengthS: 5,\r\n },\r\n streaming: {\r\n enabled: true,\r\n provider: 'web-speech-api',\r\n },\r\n};\r\n\r\n/** Merge user config with defaults to produce resolved config. */\r\nexport function resolveConfig(config?: STTConfig): ResolvedSTTConfig {\r\n return {\r\n model: config?.model ?? DEFAULT_STT_CONFIG.model,\r\n backend: config?.backend ?? DEFAULT_STT_CONFIG.backend,\r\n language: config?.language ?? DEFAULT_STT_CONFIG.language,\r\n dtype: config?.dtype ?? DEFAULT_STT_CONFIG.dtype,\r\n correction: {\r\n enabled: config?.correction?.enabled ?? DEFAULT_STT_CONFIG.correction.enabled,\r\n provider: config?.correction?.provider ?? DEFAULT_STT_CONFIG.correction.provider,\r\n pauseThreshold:\r\n config?.correction?.pauseThreshold ?? DEFAULT_STT_CONFIG.correction.pauseThreshold,\r\n forcedInterval:\r\n config?.correction?.forcedInterval ?? DEFAULT_STT_CONFIG.correction.forcedInterval,\r\n },\r\n chunking: {\r\n chunkLengthS: config?.chunking?.chunkLengthS ?? DEFAULT_STT_CONFIG.chunking.chunkLengthS,\r\n strideLengthS: config?.chunking?.strideLengthS ?? DEFAULT_STT_CONFIG.chunking.strideLengthS,\r\n },\r\n streaming: {\r\n enabled: config?.streaming?.enabled ?? DEFAULT_STT_CONFIG.streaming.enabled,\r\n provider: config?.streaming?.provider ?? DEFAULT_STT_CONFIG.streaming.provider,\r\n },\r\n };\r\n}\r\n","/**\n * A generic, typed event emitter.\n *\n * Type parameter `T` is a map of event names to listener signatures,\n * giving consumers compile-time safety on event names and callback args.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class TypedEventEmitter<T extends Record<string, (...args: any[]) => void>> {\n private listeners = new Map<keyof T, Set<T[keyof T]>>();\n\n /** Subscribe to an event. */\n on<K extends keyof T>(event: K, listener: T[K]): void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(listener as T[keyof T]);\n }\n\n /** Unsubscribe a specific listener. No-op if not registered. */\n off<K extends keyof T>(event: K, listener: T[K]): void {\n this.listeners.get(event)?.delete(listener as T[keyof T]);\n }\n\n /** Emit an event, calling all registered listeners in insertion order. */\n emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>): void {\n const set = this.listeners.get(event);\n if (!set) return;\n for (const listener of set) {\n (listener as (...a: Parameters<T[K]>) => void)(...args);\n }\n }\n\n /** Remove all listeners, optionally for a single event. */\n removeAllListeners(event?: keyof T): void {\n if (event !== undefined) {\n this.listeners.delete(event);\n } else {\n this.listeners.clear();\n }\n }\n}\n","import type { AudioCaptureHandle } from './types.js';\r\n\r\nconst TARGET_SAMPLE_RATE = 16_000;\r\n\r\n/**\r\n * Start capturing raw PCM audio from the microphone.\r\n * Uses ScriptProcessorNode to collect Float32Array samples directly.\r\n */\r\nexport async function startCapture(): Promise<AudioCaptureHandle> {\r\n const stream = await navigator.mediaDevices.getUserMedia({\r\n audio: { channelCount: 1 },\r\n });\r\n const audioCtx = new AudioContext();\r\n\r\n // Chrome may suspend AudioContext — must resume within user gesture\r\n if (audioCtx.state === 'suspended') {\r\n await audioCtx.resume();\r\n }\r\n\r\n const source = audioCtx.createMediaStreamSource(stream);\r\n const samples: Float32Array[] = [];\r\n\r\n const processor = audioCtx.createScriptProcessor(4096, 1, 1);\r\n processor.onaudioprocess = (e: AudioProcessingEvent) => {\r\n samples.push(new Float32Array(e.inputBuffer.getChannelData(0)));\r\n };\r\n\r\n // Connect through a silent gain node so mic audio doesn't play back\r\n const silencer = audioCtx.createGain();\r\n silencer.gain.value = 0;\r\n source.connect(processor);\r\n processor.connect(silencer);\r\n silencer.connect(audioCtx.destination);\r\n\r\n return { audioCtx, stream, samples, _processor: processor, _source: source, _silencer: silencer };\r\n}\r\n\r\n/**\r\n * Pause capture without releasing mic or AudioContext.\r\n * Disconnects the audio source so no new samples are collected.\r\n * Returns resampled audio from the recording period.\r\n * Call resumeCapture() to start collecting again.\r\n */\r\nexport async function pauseCapture(capture: AudioCaptureHandle): Promise<Float32Array> {\r\n capture._source.disconnect();\r\n const currentSamples = [...capture.samples];\r\n capture.samples.length = 0;\r\n return resampleAudio(currentSamples, capture.audioCtx.sampleRate);\r\n}\r\n\r\n/**\r\n * Resume a paused capture. Reconnects the audio source to the processor.\r\n * AudioContext is resumed if suspended.\r\n */\r\nexport async function resumeCapture(capture: AudioCaptureHandle): Promise<void> {\r\n if (capture.audioCtx.state === 'suspended') {\r\n await capture.audioCtx.resume();\r\n }\r\n capture._source.connect(capture._processor);\r\n}\r\n\r\n/**\r\n * Copy current audio buffer without stopping capture.\r\n * Returns a shallow copy of the samples array (each chunk is shared, not cloned).\r\n */\r\nexport function snapshotAudio(capture: AudioCaptureHandle): Float32Array[] {\r\n return [...capture.samples];\r\n}\r\n\r\n/**\r\n * Concatenate sample chunks and resample to 16kHz for Whisper.\r\n */\r\nexport async function resampleAudio(\r\n samples: Float32Array[],\r\n nativeSr: number,\r\n): Promise<Float32Array> {\r\n const totalLength = samples.reduce((sum, s) => sum + s.length, 0);\r\n if (totalLength === 0) return new Float32Array(0);\r\n\r\n const fullAudio = new Float32Array(totalLength);\r\n let offset = 0;\r\n for (const s of samples) {\r\n fullAudio.set(s, offset);\r\n offset += s.length;\r\n }\r\n\r\n if (nativeSr === TARGET_SAMPLE_RATE) return fullAudio;\r\n\r\n const duration = fullAudio.length / nativeSr;\r\n const outLength = Math.round(duration * TARGET_SAMPLE_RATE);\r\n const offline = new OfflineAudioContext(1, outLength, TARGET_SAMPLE_RATE);\r\n const buffer = offline.createBuffer(1, fullAudio.length, nativeSr);\r\n buffer.getChannelData(0).set(fullAudio);\r\n const src = offline.createBufferSource();\r\n src.buffer = buffer;\r\n src.connect(offline.destination);\r\n src.start(0);\r\n const resampled = await offline.startRendering();\r\n return resampled.getChannelData(0);\r\n}\r\n\r\n/**\r\n * Stop capturing and return resampled audio at 16kHz.\r\n */\r\nexport async function stopCapture(capture: AudioCaptureHandle): Promise<Float32Array> {\r\n const { audioCtx, stream, samples, _processor } = capture;\r\n\r\n // Disconnect processor to stop capturing\r\n try {\r\n _processor.disconnect();\r\n } catch {\r\n /* already disconnected */\r\n }\r\n\r\n // Stop microphone tracks\r\n for (const track of stream.getTracks()) {\r\n track.stop();\r\n }\r\n\r\n const nativeSr = audioCtx.sampleRate;\r\n await audioCtx.close();\r\n\r\n return resampleAudio(samples, nativeSr);\r\n}\r\n","import type { ResolvedSTTConfig, WorkerResponse } from './types.js';\nimport { TypedEventEmitter } from './event-emitter.js';\n\n/** Events emitted by the WorkerManager. */\nexport type WorkerManagerEvents = {\n progress: (percent: number) => void;\n ready: () => void;\n result: (text: string) => void;\n error: (message: string) => void;\n};\n\n/**\n * Manages the Whisper Web Worker lifecycle.\n * Provides typed message passing and a promise-based transcription API.\n */\nexport class WorkerManager extends TypedEventEmitter<WorkerManagerEvents> {\n private worker: Worker | null = null;\n private transcribeResolve: ((text: string) => void) | null = null;\n private currentTranscribePromise: Promise<string> | null = null;\n private modelReadyResolve: (() => void) | null = null;\n private modelReadyReject: ((err: Error) => void) | null = null;\n\n /** True while a transcription job is running in the worker. */\n get isTranscribing(): boolean {\n return this.transcribeResolve !== null;\n }\n\n /** Await the current in-flight transcription without starting a new one. */\n awaitCurrentTranscription(): Promise<string> {\n return this.currentTranscribePromise ?? Promise.resolve('');\n }\n\n /** Spawn the Web Worker. Must be called before loadModel/transcribe. */\n spawn(workerUrl?: URL): void {\n if (this.worker) return;\n\n const url = workerUrl ?? new URL('./whisper-worker.js', import.meta.url);\n\n this.worker = new Worker(url, { type: 'module' });\n this.worker.onmessage = (e: MessageEvent<WorkerResponse>) => {\n this.handleMessage(e.data);\n };\n this.worker.onerror = (e: ErrorEvent) => {\n this.emit('error', e.message ?? 'Worker error');\n };\n }\n\n /** Load the Whisper model in the worker. Resolves when ready. */\n async loadModel(config: ResolvedSTTConfig): Promise<void> {\n if (!this.worker) throw new Error('Worker not spawned');\n\n return new Promise<void>((resolve, reject) => {\n this.modelReadyResolve = resolve;\n this.modelReadyReject = reject;\n this.worker!.postMessage({\n type: 'load',\n config: {\n model: config.model,\n backend: config.backend,\n language: config.language,\n dtype: config.dtype,\n chunkLengthS: config.chunking.chunkLengthS,\n strideLengthS: config.chunking.strideLengthS,\n },\n });\n });\n }\n\n /** Send audio to the worker for transcription. Resolves with text. */\n async transcribe(audio: Float32Array): Promise<string> {\n if (!this.worker) throw new Error('Worker not spawned');\n if (audio.length === 0) return '';\n\n this.currentTranscribePromise = new Promise<string>((resolve) => {\n this.transcribeResolve = resolve;\n this.worker!.postMessage({ type: 'transcribe', audio }, [audio.buffer]);\n });\n return this.currentTranscribePromise;\n }\n\n /** Cancel any in-flight transcription. */\n cancel(): void {\n this.worker?.postMessage({ type: 'cancel' });\n if (this.transcribeResolve) {\n this.transcribeResolve('');\n this.transcribeResolve = null;\n }\n }\n\n /** Terminate the worker and release resources. */\n destroy(): void {\n this.cancel();\n this.worker?.terminate();\n this.worker = null;\n this.removeAllListeners();\n }\n\n private handleMessage(msg: WorkerResponse): void {\n switch (msg.type) {\n case 'progress':\n this.emit('progress', msg.data as number);\n break;\n case 'ready':\n this.emit('ready');\n this.modelReadyResolve?.();\n this.modelReadyResolve = null;\n this.modelReadyReject = null;\n break;\n case 'result':\n this.emit('result', msg.data as string);\n this.transcribeResolve?.(msg.data as string);\n this.transcribeResolve = null;\n break;\n case 'error': {\n const errMsg = msg.data as string;\n this.emit('error', errMsg);\n // Reject model load if still pending\n if (this.modelReadyReject) {\n this.modelReadyReject(new Error(errMsg));\n this.modelReadyResolve = null;\n this.modelReadyReject = null;\n }\n // Resolve transcribe with empty string on error\n if (this.transcribeResolve) {\n this.transcribeResolve('');\n this.transcribeResolve = null;\n }\n break;\n }\n }\n }\n}\n","import type { ResolvedSTTConfig } from './types.js';\n\n/**\n * Manages mid-recording correction timing.\n * Two triggers: pause detection and forced interval.\n */\nexport class CorrectionOrchestrator {\n private forcedTimer: ReturnType<typeof setInterval> | null = null;\n private lastCorrectionTime = 0;\n private correctionFn: (() => void) | null = null;\n private config: ResolvedSTTConfig['correction'];\n\n /** Create a new correction orchestrator with the given timing config. */\n constructor(config: ResolvedSTTConfig['correction']) {\n this.config = config;\n }\n\n /** Set the function to call when a correction is triggered. */\n setCorrectionFn(fn: () => void): void {\n this.correctionFn = fn;\n }\n\n /** Start the correction orchestrator (begin forced interval timer). */\n start(): void {\n if (!this.config.enabled) return;\n\n this.lastCorrectionTime = Date.now();\n this.startForcedTimer();\n }\n\n /** Stop the orchestrator (clear all timers). */\n stop(): void {\n this.stopForcedTimer();\n }\n\n /** Called when a speech pause is detected. Triggers correction if cooldown elapsed. */\n onPauseDetected(): void {\n if (!this.config.enabled) return;\n\n const now = Date.now();\n if (now - this.lastCorrectionTime < this.config.pauseThreshold) return;\n\n this.triggerCorrection();\n }\n\n /** Force a correction now (resets timer). */\n forceCorrection(): void {\n this.triggerCorrection();\n }\n\n private triggerCorrection(): void {\n this.lastCorrectionTime = Date.now();\n this.correctionFn?.();\n // Reset forced timer after any correction\n this.restartForcedTimer();\n }\n\n private startForcedTimer(): void {\n this.stopForcedTimer();\n this.forcedTimer = setInterval(() => {\n this.triggerCorrection();\n }, this.config.forcedInterval);\n }\n\n private stopForcedTimer(): void {\n if (this.forcedTimer) {\n clearInterval(this.forcedTimer);\n this.forcedTimer = null;\n }\n }\n\n private restartForcedTimer(): void {\n if (this.forcedTimer) {\n this.startForcedTimer();\n }\n }\n}\n","/* ─── Web Speech API types ──────────────────────────────── */\r\n\r\ninterface SpeechRecognitionEvent {\r\n results: SpeechRecognitionResultList;\r\n resultIndex: number;\r\n}\r\n\r\ninterface SpeechRecognitionErrorEvent {\r\n error: string;\r\n}\r\n\r\ninterface SpeechRecognitionInstance {\r\n continuous: boolean;\r\n interimResults: boolean;\r\n lang: string;\r\n onaudiostart: (() => void) | null;\r\n onresult: ((e: SpeechRecognitionEvent) => void) | null;\r\n onerror: ((e: SpeechRecognitionErrorEvent) => void) | null;\r\n onend: (() => void) | null;\r\n start: () => void;\r\n stop: () => void;\r\n abort: () => void;\r\n}\r\n\r\ntype SpeechRecognitionCtor = new () => SpeechRecognitionInstance;\r\n\r\nfunction getSpeechRecognition(): SpeechRecognitionCtor | null {\r\n if (typeof globalThis === 'undefined') return null;\r\n const w = globalThis as unknown as Record<string, unknown>;\r\n return (w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null) as SpeechRecognitionCtor | null;\r\n}\r\n\r\n/* ─── Language mapping ──────────────────────────────────── */\r\n\r\n/** Map Whisper language codes to BCP-47 locale tags for the Speech API. */\r\nconst WHISPER_TO_BCP47: Record<string, string> = {\r\n en: 'en-US',\r\n english: 'en-US',\r\n zh: 'zh-CN',\r\n chinese: 'zh-CN',\r\n de: 'de-DE',\r\n german: 'de-DE',\r\n es: 'es-ES',\r\n spanish: 'es-ES',\r\n ru: 'ru-RU',\r\n russian: 'ru-RU',\r\n ko: 'ko-KR',\r\n korean: 'ko-KR',\r\n fr: 'fr-FR',\r\n french: 'fr-FR',\r\n ja: 'ja-JP',\r\n japanese: 'ja-JP',\r\n pt: 'pt-BR',\r\n portuguese: 'pt-BR',\r\n tr: 'tr-TR',\r\n turkish: 'tr-TR',\r\n pl: 'pl-PL',\r\n polish: 'pl-PL',\r\n nl: 'nl-NL',\r\n dutch: 'nl-NL',\r\n ar: 'ar-SA',\r\n arabic: 'ar-SA',\r\n sv: 'sv-SE',\r\n swedish: 'sv-SE',\r\n it: 'it-IT',\r\n italian: 'it-IT',\r\n id: 'id-ID',\r\n indonesian: 'id-ID',\r\n hi: 'hi-IN',\r\n hindi: 'hi-IN',\r\n fi: 'fi-FI',\r\n finnish: 'fi-FI',\r\n vi: 'vi-VN',\r\n vietnamese: 'vi-VN',\r\n he: 'he-IL',\r\n hebrew: 'he-IL',\r\n uk: 'uk-UA',\r\n ukrainian: 'uk-UA',\r\n el: 'el-GR',\r\n greek: 'el-GR',\r\n ms: 'ms-MY',\r\n malay: 'ms-MY',\r\n cs: 'cs-CZ',\r\n czech: 'cs-CZ',\r\n ro: 'ro-RO',\r\n romanian: 'ro-RO',\r\n da: 'da-DK',\r\n danish: 'da-DK',\r\n hu: 'hu-HU',\r\n hungarian: 'hu-HU',\r\n no: 'nb-NO',\r\n norwegian: 'nb-NO',\r\n th: 'th-TH',\r\n thai: 'th-TH',\r\n};\r\n\r\n/**\r\n * Convert a Whisper language code to a BCP-47 locale tag for the Speech API.\r\n * Already-BCP-47 codes (containing '-') pass through unchanged.\r\n */\r\nfunction toBCP47(language: string): string {\r\n if (language.includes('-')) return language;\r\n return WHISPER_TO_BCP47[language.toLowerCase()] ?? language;\r\n}\r\n\r\n/* ─── SpeechStreamingManager ────────────────────────────── */\r\n\r\n/**\r\n * Manages Web Speech API for real-time streaming transcript preview.\r\n * Provides word-by-word interim text while Whisper handles corrections.\r\n */\r\nconst NO_RESULT_TIMEOUT_MS = 5_000;\r\n\r\nexport class SpeechStreamingManager {\r\n private recognition: SpeechRecognitionInstance | null = null;\r\n private accumulated = '';\r\n private active = false;\r\n private receivedResult = false;\r\n private noResultTimer: ReturnType<typeof setTimeout> | null = null;\r\n private onTranscript: ((text: string) => void) | null = null;\r\n private onPause: (() => void) | null = null;\r\n private onError: ((message: string) => void) | null = null;\r\n private onDebug: ((message: string) => void) | null = null;\r\n\r\n /** Check if the Web Speech API is available in this environment. */\r\n static isSupported(): boolean {\r\n return getSpeechRecognition() !== null;\r\n }\r\n\r\n /** Set callback for streaming transcript updates (interim + final text). */\r\n setOnTranscript(fn: (text: string) => void): void {\r\n this.onTranscript = fn;\r\n }\r\n\r\n /** Set callback for speech pause detection (Speech API onend). */\r\n setOnPause(fn: () => void): void {\r\n this.onPause = fn;\r\n }\r\n\r\n /** Set callback for errors. */\r\n setOnError(fn: (message: string) => void): void {\r\n this.onError = fn;\r\n }\r\n\r\n /** Set callback for diagnostic debug messages. */\r\n setOnDebug(fn: (message: string) => void): void {\r\n this.onDebug = fn;\r\n }\r\n\r\n private log(message: string): void {\r\n this.onDebug?.(message);\r\n console.warn(message);\r\n }\r\n\r\n /**\r\n * Start streaming recognition. Returns a Promise that resolves once\r\n * SpeechRecognition has claimed the microphone (onaudiostart) or after\r\n * a 300ms fallback — whichever comes first. The engine should await\r\n * this before calling getUserMedia to avoid dual-mic conflicts.\r\n *\r\n * When skipMicWait is true (warm restart — mic already active), returns\r\n * immediately after calling recognition.start() without waiting for\r\n * onaudiostart.\r\n */\r\n start(language: string, skipMicWait = false): Promise<void> {\r\n const SR = getSpeechRecognition();\r\n if (!SR) {\r\n this.log('[SSM] SpeechRecognition not available in this environment');\r\n return Promise.resolve();\r\n }\r\n\r\n const bcp47 = toBCP47(language);\r\n this.log(`[SSM] start() — lang: \"${language}\" → \"${bcp47}\"`);\r\n\r\n this.accumulated = '';\r\n this.active = true;\r\n this.receivedResult = false;\r\n\r\n const recognition = new SR();\r\n recognition.continuous = true;\r\n recognition.interimResults = true;\r\n recognition.lang = bcp47;\r\n\r\n let lastFinalIndex = -1;\r\n let lastFinalText = '';\r\n\r\n // Promise resolves when SR claims mic (onaudiostart) or after fallback.\r\n // This ensures getUserMedia doesn't compete for the mic.\r\n let micReady = false;\r\n const micClaimPromise = new Promise<void>((resolve) => {\r\n recognition.onaudiostart = () => {\r\n if (this.recognition !== recognition) return;\r\n this.log('[SSM] onaudiostart — mic acquired by Speech API');\r\n if (!micReady) {\r\n micReady = true;\r\n resolve();\r\n }\r\n };\r\n // Fallback: resolve after 300ms even if onaudiostart never fires\r\n setTimeout(() => {\r\n if (!micReady) {\r\n micReady = true;\r\n this.log('[SSM] mic-claim fallback — proceeding after 300ms');\r\n resolve();\r\n }\r\n }, 300);\r\n });\r\n\r\n // Detect silent failure: if no onresult fires within timeout, emit error\r\n this.clearNoResultTimer();\r\n this.noResultTimer = setTimeout(() => {\r\n if (this.active && !this.receivedResult) {\r\n this.log('[SSM] no-result timeout fired — no onresult in 5s');\r\n this.onError?.(\r\n 'Speech streaming started but received no results. ' +\r\n 'Mic may be blocked by another audio capture.',\r\n );\r\n }\r\n }, NO_RESULT_TIMEOUT_MS);\r\n\r\n recognition.onresult = (e: SpeechRecognitionEvent) => {\r\n if (this.recognition !== recognition) return;\r\n this.receivedResult = true;\r\n this.clearNoResultTimer();\r\n\r\n let final_ = '';\r\n let interim = '';\r\n for (let i = e.resultIndex; i < e.results.length; i++) {\r\n const t = e.results[i][0].transcript;\r\n if (e.results[i].isFinal) {\r\n if (i > lastFinalIndex) {\r\n final_ += t;\r\n lastFinalIndex = i;\r\n }\r\n } else {\r\n interim += t;\r\n }\r\n }\r\n\r\n this.log(\r\n `[SSM] onresult — finals: \"${final_}\", interim: \"${interim}\", accumulated: \"${this.accumulated}\"`,\r\n );\r\n\r\n if (final_ && final_.trim() !== lastFinalText) {\r\n lastFinalText = final_.trim();\r\n this.accumulated = this.accumulated\r\n ? this.accumulated + ' ' + final_.trim()\r\n : final_.trim();\r\n this.onTranscript?.(this.accumulated);\r\n } else if (interim) {\r\n const trimmed = interim.trimStart();\r\n const full = this.accumulated ? this.accumulated + ' ' + trimmed : trimmed;\r\n this.onTranscript?.(full);\r\n }\r\n };\r\n\r\n recognition.onerror = (e: SpeechRecognitionErrorEvent) => {\r\n if (this.recognition !== recognition) return;\r\n this.log(`[SSM] onerror — ${e.error}`);\r\n this.onError?.(e.error);\r\n };\r\n\r\n recognition.onend = () => {\r\n if (this.recognition !== recognition) return;\r\n this.log(`[SSM] onend — active: ${this.active}, receivedResult: ${this.receivedResult}`);\r\n\r\n if (this.active) {\r\n // Speech API paused — trigger correction\r\n this.onPause?.();\r\n // Restart for continued streaming\r\n try {\r\n recognition.start();\r\n this.log('[SSM] restarted after pause');\r\n } catch (err) {\r\n this.log(`[SSM] restart THREW: ${err}`);\r\n this.recognition = null;\r\n this.onError?.('Speech recognition failed to restart after pause.');\r\n }\r\n } else {\r\n this.recognition = null;\r\n }\r\n };\r\n\r\n this.recognition = recognition;\r\n try {\r\n recognition.start();\r\n this.log('[SSM] recognition.start() succeeded');\r\n } catch (err) {\r\n this.log(`[SSM] recognition.start() THREW: ${err}`);\r\n this.recognition = null;\r\n this.active = false;\r\n this.clearNoResultTimer();\r\n this.onError?.(\r\n `Speech recognition failed to start: ${err instanceof Error ? err.message : String(err)}`,\r\n );\r\n return Promise.resolve(); // Resolve so engine can proceed to getUserMedia\r\n }\r\n\r\n if (skipMicWait) {\r\n this.log('[SSM] skipMicWait — warm restart, returning immediately');\r\n return Promise.resolve();\r\n }\r\n\r\n return micClaimPromise;\r\n }\r\n\r\n private clearNoResultTimer(): void {\r\n if (this.noResultTimer) {\r\n clearTimeout(this.noResultTimer);\r\n this.noResultTimer = null;\r\n }\r\n }\r\n\r\n /** Stop streaming recognition and return accumulated text. */\r\n stop(): string {\r\n this.active = false;\r\n this.clearNoResultTimer();\r\n if (this.recognition) {\r\n const rec = this.recognition;\r\n this.recognition = null;\r\n rec.stop();\r\n }\r\n const result = this.accumulated;\r\n this.accumulated = '';\r\n return result;\r\n }\r\n\r\n /** Abort immediately without returning text. */\r\n destroy(): void {\r\n this.active = false;\r\n this.clearNoResultTimer();\r\n if (this.recognition) {\r\n const rec = this.recognition;\r\n this.recognition = null;\r\n rec.abort();\r\n }\r\n this.accumulated = '';\r\n this.onTranscript = null;\r\n this.onPause = null;\r\n this.onError = null;\r\n this.onDebug = null;\r\n }\r\n}\r\n","import type {\r\n STTConfig,\r\n STTState,\r\n STTEvents,\r\n STTStatus,\r\n ResolvedSTTConfig,\r\n AudioCaptureHandle,\r\n} from './types.js';\r\nimport { resolveConfig } from './types.js';\r\nimport { TypedEventEmitter } from './event-emitter.js';\r\nimport { startCapture, pauseCapture, resumeCapture, snapshotAudio, resampleAudio } from './audio-capture.js';\r\nimport { WorkerManager } from './worker-manager.js';\r\nimport { CorrectionOrchestrator } from './correction-orchestrator.js';\r\nimport { SpeechStreamingManager } from './speech-streaming.js';\r\n\r\n/**\r\n * Main STT engine — the public API for speech-to-text with Whisper correction.\r\n *\r\n * Usage:\r\n * ```typescript\r\n * const engine = new STTEngine({ model: 'tiny' });\r\n * engine.on('transcript', (text) => console.log(text));\r\n * engine.on('correction', (text) => console.log('corrected:', text));\r\n * await engine.init();\r\n * await engine.start();\r\n * const finalText = await engine.stop();\r\n * ```\r\n */\r\nexport class STTEngine extends TypedEventEmitter<STTEvents> {\r\n private config: ResolvedSTTConfig;\r\n private workerManager: WorkerManager;\r\n private correctionOrchestrator: CorrectionOrchestrator;\r\n private speechStreaming: SpeechStreamingManager;\r\n private capture: AudioCaptureHandle | null = null;\r\n private state: STTState;\r\n private workerUrl?: URL;\r\n /** Prevents performCorrection from emitting while stop() is consuming the in-flight result. */\r\n private _stopping = false;\r\n\r\n /**\r\n * Create a new STT engine instance.\r\n * @param config - Optional configuration overrides (model, backend, language, etc.).\r\n * @param workerUrl - Optional custom URL for the Whisper Web Worker script.\r\n */\r\n constructor(config?: STTConfig, workerUrl?: URL) {\r\n super();\r\n this.config = resolveConfig(config);\r\n this.workerManager = new WorkerManager();\r\n this.correctionOrchestrator = new CorrectionOrchestrator(this.config.correction);\r\n this.speechStreaming = new SpeechStreamingManager();\r\n this.workerUrl = workerUrl;\r\n\r\n this.state = {\r\n status: 'idle',\r\n isModelLoaded: false,\r\n loadProgress: 0,\r\n backend: null,\r\n error: null,\r\n };\r\n\r\n this.correctionOrchestrator.setCorrectionFn(() => {\r\n this.performCorrection();\r\n });\r\n\r\n this.setupWorkerListeners();\r\n this.setupStreamingCallbacks();\r\n }\r\n\r\n /** Initialize the engine: spawn worker and load model. */\r\n async init(): Promise<void> {\r\n this.updateStatus('loading');\r\n this.workerManager.spawn(this.workerUrl);\r\n\r\n try {\r\n await this.workerManager.loadModel(this.config);\r\n this.state.isModelLoaded = true;\r\n this.updateStatus('ready');\r\n } catch (err) {\r\n this.emitError('MODEL_LOAD_FAILED', err instanceof Error ? err.message : String(err));\r\n this.updateStatus('idle');\r\n throw err;\r\n }\r\n }\r\n\r\n /** Start recording audio and enable correction cycles. */\r\n async start(): Promise<void> {\r\n if (this.state.status !== 'ready') {\r\n throw new Error(`Cannot start: engine is \"${this.state.status}\", expected \"ready\"`);\r\n }\r\n\r\n try {\r\n // Check if mic is already warm from a previous recording session.\r\n // When warm, skip getUserMedia and the 300ms SR mic-claim wait.\r\n const warmCapture =\r\n this.capture &&\r\n this.capture.stream.getTracks().every((t) => t.readyState === 'live');\r\n\r\n this.emitDebug(\r\n `[STT] start() — streaming: ${this.config.streaming.enabled}, lang: \"${this.config.language}\", warm: ${!!warmCapture}`,\r\n );\r\n\r\n if (this.config.streaming.enabled) {\r\n // On warm restart, skip the mic-claim wait — no getUserMedia race.\r\n await this.speechStreaming.start(this.config.language, !!warmCapture);\r\n if (!warmCapture) {\r\n this.emitDebug('[STT] Speech API mic claim complete — starting getUserMedia');\r\n }\r\n }\r\n\r\n if (warmCapture) {\r\n await resumeCapture(this.capture!);\r\n this.emitDebug('[STT] warm mic resumed — skipped getUserMedia');\r\n } else {\r\n // First start or stale capture: full mic init with getUserMedia.\r\n this.capture = await startCapture();\r\n }\r\n\r\n this.updateStatus('recording');\r\n this.correctionOrchestrator.start();\r\n } catch (err) {\r\n this.emitError(\r\n 'MIC_DENIED',\r\n err instanceof Error ? err.message : 'Microphone access denied. Check browser permissions.',\r\n );\r\n }\r\n }\r\n\r\n /** Stop recording, run final transcription, return text.\r\n * Mic and AudioContext stay alive for fast restart — call destroy() to fully release. */\r\n async stop(): Promise<string> {\r\n if (!this.capture) return '';\r\n\r\n // Prevent any in-flight performCorrection from emitting — stop() will own the final emit.\r\n this._stopping = true;\r\n this.correctionOrchestrator.stop();\r\n this.speechStreaming.stop();\r\n\r\n this.updateStatus('processing');\r\n\r\n // If a correction job is already running, reuse it: Whisper can't be interrupted mid-inference\r\n // anyway, so cancelling just wastes the work and causes a second full run.\r\n if (this.workerManager.isTranscribing) {\r\n try {\r\n // Resample audio and await the in-flight result in parallel.\r\n const [audio, inFlightText] = await Promise.all([\r\n pauseCapture(this.capture),\r\n this.workerManager.awaitCurrentTranscription(),\r\n ]);\r\n this._stopping = false;\r\n\r\n const text = inFlightText.trim();\r\n if (text) {\r\n this.emit('correction', text);\r\n this.updateStatus('ready');\r\n return text;\r\n }\r\n\r\n // In-flight returned empty (cancelled or error) — fall through to fresh transcription.\r\n if (audio.length > 0) {\r\n const freshText = await this.workerManager.transcribe(audio);\r\n this.emit('correction', freshText);\r\n this.updateStatus('ready');\r\n return freshText;\r\n }\r\n\r\n this.updateStatus('ready');\r\n return '';\r\n } catch (err) {\r\n this._stopping = false;\r\n this.emitError(\r\n 'TRANSCRIPTION_FAILED',\r\n err instanceof Error ? err.message : 'Final transcription failed.',\r\n );\r\n this.updateStatus('ready');\r\n return '';\r\n }\r\n }\r\n\r\n // No job in-flight — cancel is a no-op, run fresh transcription.\r\n this.workerManager.cancel();\r\n this._stopping = false;\r\n\r\n try {\r\n // Soft pause — keeps stream and AudioContext alive for fast restart.\r\n const audio = await pauseCapture(this.capture);\r\n\r\n if (audio.length === 0) {\r\n this.updateStatus('ready');\r\n return '';\r\n }\r\n\r\n const text = await this.workerManager.transcribe(audio);\r\n this.emit('correction', text);\r\n this.updateStatus('ready');\r\n return text;\r\n } catch (err) {\r\n this.emitError(\r\n 'TRANSCRIPTION_FAILED',\r\n err instanceof Error ? err.message : 'Final transcription failed.',\r\n );\r\n this.updateStatus('ready');\r\n return '';\r\n }\r\n }\r\n\r\n /** Destroy the engine: terminate worker, release all resources. */\r\n destroy(): void {\r\n this.correctionOrchestrator.stop();\r\n this.speechStreaming.destroy();\r\n\r\n if (this.capture) {\r\n try {\r\n this.capture._processor.disconnect();\r\n } catch {\r\n /* already disconnected */\r\n }\r\n for (const track of this.capture.stream.getTracks()) {\r\n track.stop();\r\n }\r\n this.capture.audioCtx.close().catch(() => {});\r\n this.capture = null;\r\n }\r\n\r\n this.workerManager.destroy();\r\n this.updateStatus('idle');\r\n this.removeAllListeners();\r\n }\r\n\r\n /** Get current engine state. */\r\n getState(): Readonly<STTState> {\r\n return { ...this.state };\r\n }\r\n\r\n /** Notify the correction orchestrator of a speech pause. */\r\n notifyPause(): void {\r\n this.correctionOrchestrator.onPauseDetected();\r\n }\r\n\r\n private async performCorrection(): Promise<void> {\r\n if (!this.capture || !this.state.isModelLoaded) return;\r\n\r\n this.workerManager.cancel();\r\n\r\n try {\r\n const samples = snapshotAudio(this.capture);\r\n const nativeSr = this.capture.audioCtx.sampleRate;\r\n const audio = await resampleAudio(samples, nativeSr);\r\n\r\n if (audio.length === 0) return;\r\n\r\n const text = await this.workerManager.transcribe(audio);\r\n if (text.trim() && this.capture && !this._stopping) {\r\n this.emit('correction', text);\r\n }\r\n } catch (err) {\r\n this.emitError(\r\n 'TRANSCRIPTION_FAILED',\r\n err instanceof Error ? err.message : 'Correction transcription failed.',\r\n );\r\n // Recording continues — error is non-fatal\r\n }\r\n }\r\n\r\n private setupStreamingCallbacks(): void {\r\n this.speechStreaming.setOnDebug((message) => {\r\n this.emit('debug', message);\r\n });\r\n\r\n this.speechStreaming.setOnTranscript((text) => {\r\n this.emitDebug(`[STT] transcript callback — \"${text}\"`);\r\n this.emit('transcript', text);\r\n });\r\n\r\n this.speechStreaming.setOnPause(() => {\r\n this.emitDebug('[STT] pause callback — triggering correction');\r\n this.correctionOrchestrator.onPauseDetected();\r\n });\r\n\r\n this.speechStreaming.setOnError((message) => {\r\n this.emitDebug(`[STT] streaming error — \"${message}\"`);\r\n this.emitError('STREAMING_ERROR', message);\r\n });\r\n }\r\n\r\n private setupWorkerListeners(): void {\r\n this.workerManager.on('progress', (percent) => {\r\n this.state.loadProgress = percent;\r\n this.emit('status', { ...this.state });\r\n });\r\n\r\n this.workerManager.on('error', (message) => {\r\n this.emitError('WORKER_ERROR', message);\r\n });\r\n }\r\n\r\n private updateStatus(status: STTStatus): void {\r\n this.state.status = status;\r\n this.state.error = null;\r\n this.emit('status', { ...this.state });\r\n }\r\n\r\n private emitError(code: string, message: string): void {\r\n this.state.error = message;\r\n this.emit('error', { code, message });\r\n }\r\n\r\n private emitDebug(message: string): void {\r\n console.warn(message);\r\n this.emit('debug', message);\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACyIO,IAAM,qBAAwC;AAAA,EACnD,OAAO;AAAA,EACP,SAAS;AAAA,EACT,UAAU;AAAA,EACV,OAAO;AAAA,EACP,YAAY;AAAA,IACV,SAAS;AAAA,IACT,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,EAClB;AAAA,EACA,UAAU;AAAA,IACR,cAAc;AAAA,IACd,eAAe;AAAA,EACjB;AAAA,EACA,WAAW;AAAA,IACT,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AACF;AAGO,SAAS,cAAc,QAAuC;AACnE,SAAO;AAAA,IACL,OAAO,QAAQ,SAAS,mBAAmB;AAAA,IAC3C,SAAS,QAAQ,WAAW,mBAAmB;AAAA,IAC/C,UAAU,QAAQ,YAAY,mBAAmB;AAAA,IACjD,OAAO,QAAQ,SAAS,mBAAmB;AAAA,IAC3C,YAAY;AAAA,MACV,SAAS,QAAQ,YAAY,WAAW,mBAAmB,WAAW;AAAA,MACtE,UAAU,QAAQ,YAAY,YAAY,mBAAmB,WAAW;AAAA,MACxE,gBACE,QAAQ,YAAY,kBAAkB,mBAAmB,WAAW;AAAA,MACtE,gBACE,QAAQ,YAAY,kBAAkB,mBAAmB,WAAW;AAAA,IACxE;AAAA,IACA,UAAU;AAAA,MACR,cAAc,QAAQ,UAAU,gBAAgB,mBAAmB,SAAS;AAAA,MAC5E,eAAe,QAAQ,UAAU,iBAAiB,mBAAmB,SAAS;AAAA,IAChF;AAAA,IACA,WAAW;AAAA,MACT,SAAS,QAAQ,WAAW,WAAW,mBAAmB,UAAU;AAAA,MACpE,UAAU,QAAQ,WAAW,YAAY,mBAAmB,UAAU;AAAA,IACxE;AAAA,EACF;AACF;;;AC/KO,IAAM,oBAAN,MAA4E;AAAA,EACzE,YAAY,oBAAI,IAA8B;AAAA;AAAA,EAGtD,GAAsB,OAAU,UAAsB;AACpD,QAAI,MAAM,KAAK,UAAU,IAAI,KAAK;AAClC,QAAI,CAAC,KAAK;AACR,YAAM,oBAAI,IAAI;AACd,WAAK,UAAU,IAAI,OAAO,GAAG;AAAA,IAC/B;AACA,QAAI,IAAI,QAAsB;AAAA,EAChC;AAAA;AAAA,EAGA,IAAuB,OAAU,UAAsB;AACrD,SAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAsB;AAAA,EAC1D;AAAA;AAAA,EAGA,KAAwB,UAAa,MAA8B;AACjE,UAAM,MAAM,KAAK,UAAU,IAAI,KAAK;AACpC,QAAI,CAAC,IAAK;AACV,eAAW,YAAY,KAAK;AAC1B,MAAC,SAA8C,GAAG,IAAI;AAAA,IACxD;AAAA,EACF;AAAA;AAAA,EAGA,mBAAmB,OAAuB;AACxC,QAAI,UAAU,QAAW;AACvB,WAAK,UAAU,OAAO,KAAK;AAAA,IAC7B,OAAO;AACL,WAAK,UAAU,MAAM;AAAA,IACvB;AAAA,EACF;AACF;;;ACxCA,IAAM,qBAAqB;AAM3B,eAAsB,eAA4C;AAChE,QAAM,SAAS,MAAM,UAAU,aAAa,aAAa;AAAA,IACvD,OAAO,EAAE,cAAc,EAAE;AAAA,EAC3B,CAAC;AACD,QAAM,WAAW,IAAI,aAAa;AAGlC,MAAI,SAAS,UAAU,aAAa;AAClC,UAAM,SAAS,OAAO;AAAA,EACxB;AAEA,QAAM,SAAS,SAAS,wBAAwB,MAAM;AACtD,QAAM,UAA0B,CAAC;AAEjC,QAAM,YAAY,SAAS,sBAAsB,MAAM,GAAG,CAAC;AAC3D,YAAU,iBAAiB,CAAC,MAA4B;AACtD,YAAQ,KAAK,IAAI,aAAa,EAAE,YAAY,eAAe,CAAC,CAAC,CAAC;AAAA,EAChE;AAGA,QAAM,WAAW,SAAS,WAAW;AACrC,WAAS,KAAK,QAAQ;AACtB,SAAO,QAAQ,SAAS;AACxB,YAAU,QAAQ,QAAQ;AAC1B,WAAS,QAAQ,SAAS,WAAW;AAErC,SAAO,EAAE,UAAU,QAAQ,SAAS,YAAY,WAAW,SAAS,QAAQ,WAAW,SAAS;AAClG;AAQA,eAAsB,aAAa,SAAoD;AACrF,UAAQ,QAAQ,WAAW;AAC3B,QAAM,iBAAiB,CAAC,GAAG,QAAQ,OAAO;AAC1C,UAAQ,QAAQ,SAAS;AACzB,SAAO,cAAc,gBAAgB,QAAQ,SAAS,UAAU;AAClE;AAMA,eAAsB,cAAc,SAA4C;AAC9E,MAAI,QAAQ,SAAS,UAAU,aAAa;AAC1C,UAAM,QAAQ,SAAS,OAAO;AAAA,EAChC;AACA,UAAQ,QAAQ,QAAQ,QAAQ,UAAU;AAC5C;AAMO,SAAS,cAAc,SAA6C;AACzE,SAAO,CAAC,GAAG,QAAQ,OAAO;AAC5B;AAKA,eAAsB,cACpB,SACA,UACuB;AACvB,QAAM,cAAc,QAAQ,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AAChE,MAAI,gBAAgB,EAAG,QAAO,IAAI,aAAa,CAAC;AAEhD,QAAM,YAAY,IAAI,aAAa,WAAW;AAC9C,MAAI,SAAS;AACb,aAAW,KAAK,SAAS;AACvB,cAAU,IAAI,GAAG,MAAM;AACvB,cAAU,EAAE;AAAA,EACd;AAEA,MAAI,aAAa,mBAAoB,QAAO;AAE5C,QAAM,WAAW,UAAU,SAAS;AACpC,QAAM,YAAY,KAAK,MAAM,WAAW,kBAAkB;AAC1D,QAAM,UAAU,IAAI,oBAAoB,GAAG,WAAW,kBAAkB;AACxE,QAAM,SAAS,QAAQ,aAAa,GAAG,UAAU,QAAQ,QAAQ;AACjE,SAAO,eAAe,CAAC,EAAE,IAAI,SAAS;AACtC,QAAM,MAAM,QAAQ,mBAAmB;AACvC,MAAI,SAAS;AACb,MAAI,QAAQ,QAAQ,WAAW;AAC/B,MAAI,MAAM,CAAC;AACX,QAAM,YAAY,MAAM,QAAQ,eAAe;AAC/C,SAAO,UAAU,eAAe,CAAC;AACnC;AAKA,eAAsB,YAAY,SAAoD;AACpF,QAAM,EAAE,UAAU,QAAQ,SAAS,WAAW,IAAI;AAGlD,MAAI;AACF,eAAW,WAAW;AAAA,EACxB,QAAQ;AAAA,EAER;AAGA,aAAW,SAAS,OAAO,UAAU,GAAG;AACtC,UAAM,KAAK;AAAA,EACb;AAEA,QAAM,WAAW,SAAS;AAC1B,QAAM,SAAS,MAAM;AAErB,SAAO,cAAc,SAAS,QAAQ;AACxC;;;AC3HA;AAeO,IAAM,gBAAN,cAA4B,kBAAuC;AAAA,EAChE,SAAwB;AAAA,EACxB,oBAAqD;AAAA,EACrD,2BAAmD;AAAA,EACnD,oBAAyC;AAAA,EACzC,mBAAkD;AAAA;AAAA,EAG1D,IAAI,iBAA0B;AAC5B,WAAO,KAAK,sBAAsB;AAAA,EACpC;AAAA;AAAA,EAGA,4BAA6C;AAC3C,WAAO,KAAK,4BAA4B,QAAQ,QAAQ,EAAE;AAAA,EAC5D;AAAA;AAAA,EAGA,MAAM,WAAuB;AAC3B,QAAI,KAAK,OAAQ;AAEjB,UAAM,MAAM,aAAa,IAAI,IAAI,uBAAuB,YAAY,GAAG;AAEvE,SAAK,SAAS,IAAI,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAChD,SAAK,OAAO,YAAY,CAAC,MAAoC;AAC3D,WAAK,cAAc,EAAE,IAAI;AAAA,IAC3B;AACA,SAAK,OAAO,UAAU,CAAC,MAAkB;AACvC,WAAK,KAAK,SAAS,EAAE,WAAW,cAAc;AAAA,IAChD;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,UAAU,QAA0C;AACxD,QAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,oBAAoB;AAEtD,WAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,WAAK,oBAAoB;AACzB,WAAK,mBAAmB;AACxB,WAAK,OAAQ,YAAY;AAAA,QACvB,MAAM;AAAA,QACN,QAAQ;AAAA,UACN,OAAO,OAAO;AAAA,UACd,SAAS,OAAO;AAAA,UAChB,UAAU,OAAO;AAAA,UACjB,OAAO,OAAO;AAAA,UACd,cAAc,OAAO,SAAS;AAAA,UAC9B,eAAe,OAAO,SAAS;AAAA,QACjC;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,WAAW,OAAsC;AACrD,QAAI,CAAC,KAAK,OAAQ,OAAM,IAAI,MAAM,oBAAoB;AACtD,QAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SAAK,2BAA2B,IAAI,QAAgB,CAAC,YAAY;AAC/D,WAAK,oBAAoB;AACzB,WAAK,OAAQ,YAAY,EAAE,MAAM,cAAc,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC;AAAA,IACxE,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,SAAe;AACb,SAAK,QAAQ,YAAY,EAAE,MAAM,SAAS,CAAC;AAC3C,QAAI,KAAK,mBAAmB;AAC1B,WAAK,kBAAkB,EAAE;AACzB,WAAK,oBAAoB;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA,EAGA,UAAgB;AACd,SAAK,OAAO;AACZ,SAAK,QAAQ,UAAU;AACvB,SAAK,SAAS;AACd,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEQ,cAAc,KAA2B;AAC/C,YAAQ,IAAI,MAAM;AAAA,MAChB,KAAK;AACH,aAAK,KAAK,YAAY,IAAI,IAAc;AACxC;AAAA,MACF,KAAK;AACH,aAAK,KAAK,OAAO;AACjB,aAAK,oBAAoB;AACzB,aAAK,oBAAoB;AACzB,aAAK,mBAAmB;AACxB;AAAA,MACF,KAAK;AACH,aAAK,KAAK,UAAU,IAAI,IAAc;AACtC,aAAK,oBAAoB,IAAI,IAAc;AAC3C,aAAK,oBAAoB;AACzB;AAAA,MACF,KAAK,SAAS;AACZ,cAAM,SAAS,IAAI;AACnB,aAAK,KAAK,SAAS,MAAM;AAEzB,YAAI,KAAK,kBAAkB;AACzB,eAAK,iBAAiB,IAAI,MAAM,MAAM,CAAC;AACvC,eAAK,oBAAoB;AACzB,eAAK,mBAAmB;AAAA,QAC1B;AAEA,YAAI,KAAK,mBAAmB;AAC1B,eAAK,kBAAkB,EAAE;AACzB,eAAK,oBAAoB;AAAA,QAC3B;AACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC7HO,IAAM,yBAAN,MAA6B;AAAA,EAC1B,cAAqD;AAAA,EACrD,qBAAqB;AAAA,EACrB,eAAoC;AAAA,EACpC;AAAA;AAAA,EAGR,YAAY,QAAyC;AACnD,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,gBAAgB,IAAsB;AACpC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,QAAc;AACZ,QAAI,CAAC,KAAK,OAAO,QAAS;AAE1B,SAAK,qBAAqB,KAAK,IAAI;AACnC,SAAK,iBAAiB;AAAA,EACxB;AAAA;AAAA,EAGA,OAAa;AACX,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA,EAGA,kBAAwB;AACtB,QAAI,CAAC,KAAK,OAAO,QAAS;AAE1B,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,MAAM,KAAK,qBAAqB,KAAK,OAAO,eAAgB;AAEhE,SAAK,kBAAkB;AAAA,EACzB;AAAA;AAAA,EAGA,kBAAwB;AACtB,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEQ,oBAA0B;AAChC,SAAK,qBAAqB,KAAK,IAAI;AACnC,SAAK,eAAe;AAEpB,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEQ,mBAAyB;AAC/B,SAAK,gBAAgB;AACrB,SAAK,cAAc,YAAY,MAAM;AACnC,WAAK,kBAAkB;AAAA,IACzB,GAAG,KAAK,OAAO,cAAc;AAAA,EAC/B;AAAA,EAEQ,kBAAwB;AAC9B,QAAI,KAAK,aAAa;AACpB,oBAAc,KAAK,WAAW;AAC9B,WAAK,cAAc;AAAA,IACrB;AAAA,EACF;AAAA,EAEQ,qBAA2B;AACjC,QAAI,KAAK,aAAa;AACpB,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AACF;;;AClDA,SAAS,uBAAqD;AAC5D,MAAI,OAAO,eAAe,YAAa,QAAO;AAC9C,QAAM,IAAI;AACV,SAAQ,EAAE,qBAAqB,EAAE,2BAA2B;AAC9D;AAKA,IAAM,mBAA2C;AAAA,EAC/C,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,IAAI;AAAA,EACJ,WAAW;AAAA,EACX,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,IAAI;AAAA,EACJ,QAAQ;AAAA,EACR,IAAI;AAAA,EACJ,WAAW;AAAA,EACX,IAAI;AAAA,EACJ,WAAW;AAAA,EACX,IAAI;AAAA,EACJ,MAAM;AACR;AAMA,SAAS,QAAQ,UAA0B;AACzC,MAAI,SAAS,SAAS,GAAG,EAAG,QAAO;AACnC,SAAO,iBAAiB,SAAS,YAAY,CAAC,KAAK;AACrD;AAQA,IAAM,uBAAuB;AAEtB,IAAM,yBAAN,MAA6B;AAAA,EAC1B,cAAgD;AAAA,EAChD,cAAc;AAAA,EACd,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,gBAAsD;AAAA,EACtD,eAAgD;AAAA,EAChD,UAA+B;AAAA,EAC/B,UAA8C;AAAA,EAC9C,UAA8C;AAAA;AAAA,EAGtD,OAAO,cAAuB;AAC5B,WAAO,qBAAqB,MAAM;AAAA,EACpC;AAAA;AAAA,EAGA,gBAAgB,IAAkC;AAChD,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,WAAW,IAAsB;AAC/B,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA,EAGA,WAAW,IAAqC;AAC9C,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA,EAGA,WAAW,IAAqC;AAC9C,SAAK,UAAU;AAAA,EACjB;AAAA,EAEQ,IAAI,SAAuB;AACjC,SAAK,UAAU,OAAO;AACtB,YAAQ,KAAK,OAAO;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAM,UAAkB,cAAc,OAAsB;AAC1D,UAAM,KAAK,qBAAqB;AAChC,QAAI,CAAC,IAAI;AACP,WAAK,IAAI,2DAA2D;AACpE,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAEA,UAAM,QAAQ,QAAQ,QAAQ;AAC9B,SAAK,IAAI,+BAA0B,QAAQ,aAAQ,KAAK,GAAG;AAE3D,SAAK,cAAc;AACnB,SAAK,SAAS;AACd,SAAK,iBAAiB;AAEtB,UAAM,cAAc,IAAI,GAAG;AAC3B,gBAAY,aAAa;AACzB,gBAAY,iBAAiB;AAC7B,gBAAY,OAAO;AAEnB,QAAI,iBAAiB;AACrB,QAAI,gBAAgB;AAIpB,QAAI,WAAW;AACf,UAAM,kBAAkB,IAAI,QAAc,CAAC,YAAY;AACrD,kBAAY,eAAe,MAAM;AAC/B,YAAI,KAAK,gBAAgB,YAAa;AACtC,aAAK,IAAI,sDAAiD;AAC1D,YAAI,CAAC,UAAU;AACb,qBAAW;AACX,kBAAQ;AAAA,QACV;AAAA,MACF;AAEA,iBAAW,MAAM;AACf,YAAI,CAAC,UAAU;AACb,qBAAW;AACX,eAAK,IAAI,wDAAmD;AAC5D,kBAAQ;AAAA,QACV;AAAA,MACF,GAAG,GAAG;AAAA,IACR,CAAC;AAGD,SAAK,mBAAmB;AACxB,SAAK,gBAAgB,WAAW,MAAM;AACpC,UAAI,KAAK,UAAU,CAAC,KAAK,gBAAgB;AACvC,aAAK,IAAI,wDAAmD;AAC5D,aAAK;AAAA,UACH;AAAA,QAEF;AAAA,MACF;AAAA,IACF,GAAG,oBAAoB;AAEvB,gBAAY,WAAW,CAAC,MAA8B;AACpD,UAAI,KAAK,gBAAgB,YAAa;AACtC,WAAK,iBAAiB;AACtB,WAAK,mBAAmB;AAExB,UAAI,SAAS;AACb,UAAI,UAAU;AACd,eAAS,IAAI,EAAE,aAAa,IAAI,EAAE,QAAQ,QAAQ,KAAK;AACrD,cAAM,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE;AAC1B,YAAI,EAAE,QAAQ,CAAC,EAAE,SAAS;AACxB,cAAI,IAAI,gBAAgB;AACtB,sBAAU;AACV,6BAAiB;AAAA,UACnB;AAAA,QACF,OAAO;AACL,qBAAW;AAAA,QACb;AAAA,MACF;AAEA,WAAK;AAAA,QACH,kCAA6B,MAAM,gBAAgB,OAAO,oBAAoB,KAAK,WAAW;AAAA,MAChG;AAEA,UAAI,UAAU,OAAO,KAAK,MAAM,eAAe;AAC7C,wBAAgB,OAAO,KAAK;AAC5B,aAAK,cAAc,KAAK,cACpB,KAAK,cAAc,MAAM,OAAO,KAAK,IACrC,OAAO,KAAK;AAChB,aAAK,eAAe,KAAK,WAAW;AAAA,MACtC,WAAW,SAAS;AAClB,cAAM,UAAU,QAAQ,UAAU;AAClC,cAAM,OAAO,KAAK,cAAc,KAAK,cAAc,MAAM,UAAU;AACnE,aAAK,eAAe,IAAI;AAAA,MAC1B;AAAA,IACF;AAEA,gBAAY,UAAU,CAAC,MAAmC;AACxD,UAAI,KAAK,gBAAgB,YAAa;AACtC,WAAK,IAAI,wBAAmB,EAAE,KAAK,EAAE;AACrC,WAAK,UAAU,EAAE,KAAK;AAAA,IACxB;AAEA,gBAAY,QAAQ,MAAM;AACxB,UAAI,KAAK,gBAAgB,YAAa;AACtC,WAAK,IAAI,8BAAyB,KAAK,MAAM,qBAAqB,KAAK,cAAc,EAAE;AAEvF,UAAI,KAAK,QAAQ;AAEf,aAAK,UAAU;AAEf,YAAI;AACF,sBAAY,MAAM;AAClB,eAAK,IAAI,6BAA6B;AAAA,QACxC,SAAS,KAAK;AACZ,eAAK,IAAI,wBAAwB,GAAG,EAAE;AACtC,eAAK,cAAc;AACnB,eAAK,UAAU,mDAAmD;AAAA,QACpE;AAAA,MACF,OAAO;AACL,aAAK,cAAc;AAAA,MACrB;AAAA,IACF;AAEA,SAAK,cAAc;AACnB,QAAI;AACF,kBAAY,MAAM;AAClB,WAAK,IAAI,qCAAqC;AAAA,IAChD,SAAS,KAAK;AACZ,WAAK,IAAI,oCAAoC,GAAG,EAAE;AAClD,WAAK,cAAc;AACnB,WAAK,SAAS;AACd,WAAK,mBAAmB;AACxB,WAAK;AAAA,QACH,uCAAuC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACzF;AACA,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAEA,QAAI,aAAa;AACf,WAAK,IAAI,8DAAyD;AAClE,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,qBAA2B;AACjC,QAAI,KAAK,eAAe;AACtB,mBAAa,KAAK,aAAa;AAC/B,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AAAA;AAAA,EAGA,OAAe;AACb,SAAK,SAAS;AACd,SAAK,mBAAmB;AACxB,QAAI,KAAK,aAAa;AACpB,YAAM,MAAM,KAAK;AACjB,WAAK,cAAc;AACnB,UAAI,KAAK;AAAA,IACX;AACA,UAAM,SAAS,KAAK;AACpB,SAAK,cAAc;AACnB,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,UAAgB;AACd,SAAK,SAAS;AACd,SAAK,mBAAmB;AACxB,QAAI,KAAK,aAAa;AACpB,YAAM,MAAM,KAAK;AACjB,WAAK,cAAc;AACnB,UAAI,MAAM;AAAA,IACZ;AACA,SAAK,cAAc;AACnB,SAAK,eAAe;AACpB,SAAK,UAAU;AACf,SAAK,UAAU;AACf,SAAK,UAAU;AAAA,EACjB;AACF;;;AC1TO,IAAM,YAAN,cAAwB,kBAA6B;AAAA,EAClD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAqC;AAAA,EACrC;AAAA,EACA;AAAA;AAAA,EAEA,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOpB,YAAY,QAAoB,WAAiB;AAC/C,UAAM;AACN,SAAK,SAAS,cAAc,MAAM;AAClC,SAAK,gBAAgB,IAAI,cAAc;AACvC,SAAK,yBAAyB,IAAI,uBAAuB,KAAK,OAAO,UAAU;AAC/E,SAAK,kBAAkB,IAAI,uBAAuB;AAClD,SAAK,YAAY;AAEjB,SAAK,QAAQ;AAAA,MACX,QAAQ;AAAA,MACR,eAAe;AAAA,MACf,cAAc;AAAA,MACd,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAEA,SAAK,uBAAuB,gBAAgB,MAAM;AAChD,WAAK,kBAAkB;AAAA,IACzB,CAAC;AAED,SAAK,qBAAqB;AAC1B,SAAK,wBAAwB;AAAA,EAC/B;AAAA;AAAA,EAGA,MAAM,OAAsB;AAC1B,SAAK,aAAa,SAAS;AAC3B,SAAK,cAAc,MAAM,KAAK,SAAS;AAEvC,QAAI;AACF,YAAM,KAAK,cAAc,UAAU,KAAK,MAAM;AAC9C,WAAK,MAAM,gBAAgB;AAC3B,WAAK,aAAa,OAAO;AAAA,IAC3B,SAAS,KAAK;AACZ,WAAK,UAAU,qBAAqB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AACpF,WAAK,aAAa,MAAM;AACxB,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,QAAI,KAAK,MAAM,WAAW,SAAS;AACjC,YAAM,IAAI,MAAM,4BAA4B,KAAK,MAAM,MAAM,qBAAqB;AAAA,IACpF;AAEA,QAAI;AAGF,YAAM,cACJ,KAAK,WACL,KAAK,QAAQ,OAAO,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,MAAM;AAEtE,WAAK;AAAA,QACH,mCAA8B,KAAK,OAAO,UAAU,OAAO,YAAY,KAAK,OAAO,QAAQ,YAAY,CAAC,CAAC,WAAW;AAAA,MACtH;AAEA,UAAI,KAAK,OAAO,UAAU,SAAS;AAEjC,cAAM,KAAK,gBAAgB,MAAM,KAAK,OAAO,UAAU,CAAC,CAAC,WAAW;AACpE,YAAI,CAAC,aAAa;AAChB,eAAK,UAAU,kEAA6D;AAAA,QAC9E;AAAA,MACF;AAEA,UAAI,aAAa;AACf,cAAM,cAAc,KAAK,OAAQ;AACjC,aAAK,UAAU,oDAA+C;AAAA,MAChE,OAAO;AAEL,aAAK,UAAU,MAAM,aAAa;AAAA,MACpC;AAEA,WAAK,aAAa,WAAW;AAC7B,WAAK,uBAAuB,MAAM;AAAA,IACpC,SAAS,KAAK;AACZ,WAAK;AAAA,QACH;AAAA,QACA,eAAe,QAAQ,IAAI,UAAU;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,MAAM,OAAwB;AAC5B,QAAI,CAAC,KAAK,QAAS,QAAO;AAG1B,SAAK,YAAY;AACjB,SAAK,uBAAuB,KAAK;AACjC,SAAK,gBAAgB,KAAK;AAE1B,SAAK,aAAa,YAAY;AAI9B,QAAI,KAAK,cAAc,gBAAgB;AACrC,UAAI;AAEF,cAAM,CAAC,OAAO,YAAY,IAAI,MAAM,QAAQ,IAAI;AAAA,UAC9C,aAAa,KAAK,OAAO;AAAA,UACzB,KAAK,cAAc,0BAA0B;AAAA,QAC/C,CAAC;AACD,aAAK,YAAY;AAEjB,cAAM,OAAO,aAAa,KAAK;AAC/B,YAAI,MAAM;AACR,eAAK,KAAK,cAAc,IAAI;AAC5B,eAAK,aAAa,OAAO;AACzB,iBAAO;AAAA,QACT;AAGA,YAAI,MAAM,SAAS,GAAG;AACpB,gBAAM,YAAY,MAAM,KAAK,cAAc,WAAW,KAAK;AAC3D,eAAK,KAAK,cAAc,SAAS;AACjC,eAAK,aAAa,OAAO;AACzB,iBAAO;AAAA,QACT;AAEA,aAAK,aAAa,OAAO;AACzB,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,aAAK,YAAY;AACjB,aAAK;AAAA,UACH;AAAA,UACA,eAAe,QAAQ,IAAI,UAAU;AAAA,QACvC;AACA,aAAK,aAAa,OAAO;AACzB,eAAO;AAAA,MACT;AAAA,IACF;AAGA,SAAK,cAAc,OAAO;AAC1B,SAAK,YAAY;AAEjB,QAAI;AAEF,YAAM,QAAQ,MAAM,aAAa,KAAK,OAAO;AAE7C,UAAI,MAAM,WAAW,GAAG;AACtB,aAAK,aAAa,OAAO;AACzB,eAAO;AAAA,MACT;AAEA,YAAM,OAAO,MAAM,KAAK,cAAc,WAAW,KAAK;AACtD,WAAK,KAAK,cAAc,IAAI;AAC5B,WAAK,aAAa,OAAO;AACzB,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,WAAK;AAAA,QACH;AAAA,QACA,eAAe,QAAQ,IAAI,UAAU;AAAA,MACvC;AACA,WAAK,aAAa,OAAO;AACzB,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA,EAGA,UAAgB;AACd,SAAK,uBAAuB,KAAK;AACjC,SAAK,gBAAgB,QAAQ;AAE7B,QAAI,KAAK,SAAS;AAChB,UAAI;AACF,aAAK,QAAQ,WAAW,WAAW;AAAA,MACrC,QAAQ;AAAA,MAER;AACA,iBAAW,SAAS,KAAK,QAAQ,OAAO,UAAU,GAAG;AACnD,cAAM,KAAK;AAAA,MACb;AACA,WAAK,QAAQ,SAAS,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAC5C,WAAK,UAAU;AAAA,IACjB;AAEA,SAAK,cAAc,QAAQ;AAC3B,SAAK,aAAa,MAAM;AACxB,SAAK,mBAAmB;AAAA,EAC1B;AAAA;AAAA,EAGA,WAA+B;AAC7B,WAAO,EAAE,GAAG,KAAK,MAAM;AAAA,EACzB;AAAA;AAAA,EAGA,cAAoB;AAClB,SAAK,uBAAuB,gBAAgB;AAAA,EAC9C;AAAA,EAEA,MAAc,oBAAmC;AAC/C,QAAI,CAAC,KAAK,WAAW,CAAC,KAAK,MAAM,cAAe;AAEhD,SAAK,cAAc,OAAO;AAE1B,QAAI;AACF,YAAM,UAAU,cAAc,KAAK,OAAO;AAC1C,YAAM,WAAW,KAAK,QAAQ,SAAS;AACvC,YAAM,QAAQ,MAAM,cAAc,SAAS,QAAQ;AAEnD,UAAI,MAAM,WAAW,EAAG;AAExB,YAAM,OAAO,MAAM,KAAK,cAAc,WAAW,KAAK;AACtD,UAAI,KAAK,KAAK,KAAK,KAAK,WAAW,CAAC,KAAK,WAAW;AAClD,aAAK,KAAK,cAAc,IAAI;AAAA,MAC9B;AAAA,IACF,SAAS,KAAK;AACZ,WAAK;AAAA,QACH;AAAA,QACA,eAAe,QAAQ,IAAI,UAAU;AAAA,MACvC;AAAA,IAEF;AAAA,EACF;AAAA,EAEQ,0BAAgC;AACtC,SAAK,gBAAgB,WAAW,CAAC,YAAY;AAC3C,WAAK,KAAK,SAAS,OAAO;AAAA,IAC5B,CAAC;AAED,SAAK,gBAAgB,gBAAgB,CAAC,SAAS;AAC7C,WAAK,UAAU,qCAAgC,IAAI,GAAG;AACtD,WAAK,KAAK,cAAc,IAAI;AAAA,IAC9B,CAAC;AAED,SAAK,gBAAgB,WAAW,MAAM;AACpC,WAAK,UAAU,mDAA8C;AAC7D,WAAK,uBAAuB,gBAAgB;AAAA,IAC9C,CAAC;AAED,SAAK,gBAAgB,WAAW,CAAC,YAAY;AAC3C,WAAK,UAAU,iCAA4B,OAAO,GAAG;AACrD,WAAK,UAAU,mBAAmB,OAAO;AAAA,IAC3C,CAAC;AAAA,EACH;AAAA,EAEQ,uBAA6B;AACnC,SAAK,cAAc,GAAG,YAAY,CAAC,YAAY;AAC7C,WAAK,MAAM,eAAe;AAC1B,WAAK,KAAK,UAAU,EAAE,GAAG,KAAK,MAAM,CAAC;AAAA,IACvC,CAAC;AAED,SAAK,cAAc,GAAG,SAAS,CAAC,YAAY;AAC1C,WAAK,UAAU,gBAAgB,OAAO;AAAA,IACxC,CAAC;AAAA,EACH;AAAA,EAEQ,aAAa,QAAyB;AAC5C,SAAK,MAAM,SAAS;AACpB,SAAK,MAAM,QAAQ;AACnB,SAAK,KAAK,UAAU,EAAE,GAAG,KAAK,MAAM,CAAC;AAAA,EACvC;AAAA,EAEQ,UAAU,MAAc,SAAuB;AACrD,SAAK,MAAM,QAAQ;AACnB,SAAK,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAAA,EACtC;AAAA,EAEQ,UAAU,SAAuB;AACvC,YAAQ,KAAK,OAAO;AACpB,SAAK,KAAK,SAAS,OAAO;AAAA,EAC5B;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types.ts","../src/event-emitter.ts","../src/audio-capture.ts","../src/worker-manager.ts","../src/correction-orchestrator.ts","../src/speech-streaming.ts","../src/stt-engine.ts"],"sourcesContent":["// Public types\r\nexport type {\r\n STTModelSize,\r\n STTBackend,\r\n STTStatus,\r\n STTCorrectionProvider,\r\n STTStreamingProvider,\r\n STTCorrectionConfig,\r\n STTStreamingConfig,\r\n STTChunkingConfig,\r\n STTConfig,\r\n ResolvedSTTConfig,\r\n STTState,\r\n STTError,\r\n STTEvents,\r\n AudioCaptureHandle,\r\n} from './types.js';\r\n\r\n// Public values\r\nexport { DEFAULT_STT_CONFIG, resolveConfig } from './types.js';\r\n\r\n// Event emitter\r\nexport { TypedEventEmitter } from './event-emitter.js';\r\n\r\n// Audio capture\r\nexport {\r\n startCapture,\r\n pauseCapture,\r\n resumeCapture,\r\n snapshotAudio,\r\n resampleAudio,\r\n stopCapture,\r\n} from './audio-capture.js';\r\n\r\n// Worker manager\r\nexport { WorkerManager } from './worker-manager.js';\r\nexport type { WorkerManagerEvents } from './worker-manager.js';\r\n\r\n// Correction orchestrator\r\nexport { CorrectionOrchestrator } from './correction-orchestrator.js';\r\n\r\n// Speech streaming\r\nexport { SpeechStreamingManager } from './speech-streaming.js';\r\n\r\n// STT Engine (main public API)\r\nexport { STTEngine } from './stt-engine.js';\r\n","/** Supported Whisper model sizes. */\r\nexport type STTModelSize = 'tiny' | 'base' | 'small' | 'medium';\r\n\r\n/** Supported compute backends. */\r\nexport type STTBackend = 'webgpu' | 'wasm' | 'auto';\r\n\r\n/** Engine lifecycle states. */\r\nexport type STTStatus = 'idle' | 'loading' | 'ready' | 'recording' | 'processing';\r\n\r\n/** Supported correction engine providers. */\r\nexport type STTCorrectionProvider = 'whisper';\r\n\r\n/** Supported real-time streaming providers. */\r\nexport type STTStreamingProvider = 'web-speech-api';\r\n\r\n/** Correction engine configuration. */\r\nexport interface STTCorrectionConfig {\r\n /** Enable mid-recording correction. Default: true */\r\n enabled?: boolean;\r\n /** Correction engine provider. Default: 'whisper' */\r\n provider?: STTCorrectionProvider;\r\n /** Silence duration (ms) before triggering correction. Default: 1000 */\r\n pauseThreshold?: number;\r\n /** Maximum interval (ms) between forced corrections. Default: 3000 */\r\n forcedInterval?: number;\r\n}\r\n\r\n/** Real-time streaming preview configuration. */\r\nexport interface STTStreamingConfig {\r\n /** Enable real-time streaming transcript. Default: true */\r\n enabled?: boolean;\r\n /** Streaming provider. Default: 'web-speech-api' */\r\n provider?: STTStreamingProvider;\r\n}\r\n\r\n/** Audio chunking configuration for long-form audio. */\r\nexport interface STTChunkingConfig {\r\n /** Chunk length in seconds for Whisper processing. Default: 30 */\r\n chunkLengthS?: number;\r\n /** Stride length in seconds for overlapping chunks. Default: 5 */\r\n strideLengthS?: number;\r\n}\r\n\r\n/** Full engine configuration. All fields optional — sensible defaults applied. */\r\nexport interface STTConfig {\r\n /** Whisper model size. Default: 'tiny' */\r\n model?: STTModelSize;\r\n /** Compute backend preference. Default: 'auto' (WebGPU with WASM fallback) */\r\n backend?: STTBackend;\r\n /** Transcription language. Default: 'en' */\r\n language?: string;\r\n /** Model quantization dtype. Default: 'q4' */\r\n dtype?: string;\r\n /** Mid-recording correction settings. */\r\n correction?: STTCorrectionConfig;\r\n /** Audio chunking settings for long-form audio. */\r\n chunking?: STTChunkingConfig;\r\n /** Web Speech API streaming preview settings. */\r\n streaming?: STTStreamingConfig;\r\n}\r\n\r\n/** Resolved configuration with all defaults applied. */\r\nexport interface ResolvedSTTConfig {\r\n model: STTModelSize;\r\n backend: STTBackend;\r\n language: string;\r\n dtype: string;\r\n correction: Required<STTCorrectionConfig>;\r\n chunking: Required<STTChunkingConfig>;\r\n streaming: Required<STTStreamingConfig>;\r\n}\r\n\r\n/** Engine state exposed to consumers via status events. */\r\nexport interface STTState {\r\n status: STTStatus;\r\n isModelLoaded: boolean;\r\n /** Model download progress (0–100). */\r\n loadProgress: number;\r\n /** Active compute backend, or null if not yet determined. */\r\n backend: 'webgpu' | 'wasm' | null;\r\n error: string | null;\r\n}\r\n\r\n/** Structured error emitted via the 'error' event. */\r\nexport interface STTError {\r\n code: string;\r\n message: string;\r\n}\r\n\r\n/** Event map for the typed event emitter. */\r\nexport type STTEvents = {\r\n /** Streaming interim text during recording. */\r\n transcript: (text: string) => void;\r\n /** Whisper-corrected text replacing interim text. */\r\n correction: (text: string) => void;\r\n /** Actionable error (mic denied, model fail, transcription fail). */\r\n error: (error: STTError) => void;\r\n /** Engine state change. */\r\n status: (state: STTState) => void;\r\n /** Diagnostic log for debugging (subscribe to capture all internal events). */\r\n debug: (message: string) => void;\r\n};\r\n\r\n/** Handle returned by audio capture — used internally. */\r\nexport interface AudioCaptureHandle {\r\n audioCtx: AudioContext;\r\n stream: MediaStream;\r\n samples: Float32Array[];\r\n /** Retain reference to prevent GC from stopping audio processing. */\r\n _processor: ScriptProcessorNode;\r\n /** Source node for disconnect/reconnect on pause/resume. */\r\n _source: MediaStreamAudioSourceNode;\r\n /** Gain node (silent) to prevent mic playback. */\r\n _silencer: GainNode;\r\n}\r\n\r\n/** Message sent from main thread to Whisper worker. */\r\nexport interface WorkerMessage {\r\n type: 'load' | 'transcribe' | 'cancel';\r\n audio?: Float32Array;\r\n config?: {\r\n model: string;\r\n backend: STTBackend;\r\n language: string;\r\n dtype: string;\r\n chunkLengthS: number;\r\n strideLengthS: number;\r\n };\r\n}\r\n\r\n/** Response sent from Whisper worker to main thread. */\r\nexport interface WorkerResponse {\r\n type: 'progress' | 'ready' | 'result' | 'error';\r\n data?: unknown;\r\n}\r\n\r\n/** Default configuration values. */\r\nexport const DEFAULT_STT_CONFIG: ResolvedSTTConfig = {\r\n model: 'tiny',\r\n backend: 'auto',\r\n language: 'en',\r\n dtype: 'q4',\r\n correction: {\r\n enabled: true,\r\n provider: 'whisper',\r\n pauseThreshold: 1_000,\r\n forcedInterval: 3_000,\r\n },\r\n chunking: {\r\n chunkLengthS: 30,\r\n strideLengthS: 5,\r\n },\r\n streaming: {\r\n enabled: true,\r\n provider: 'web-speech-api',\r\n },\r\n};\r\n\r\n/** Merge user config with defaults to produce resolved config. */\r\nexport function resolveConfig(config?: STTConfig): ResolvedSTTConfig {\r\n return {\r\n model: config?.model ?? DEFAULT_STT_CONFIG.model,\r\n backend: config?.backend ?? DEFAULT_STT_CONFIG.backend,\r\n language: config?.language ?? DEFAULT_STT_CONFIG.language,\r\n dtype: config?.dtype ?? DEFAULT_STT_CONFIG.dtype,\r\n correction: {\r\n enabled: config?.correction?.enabled ?? DEFAULT_STT_CONFIG.correction.enabled,\r\n provider: config?.correction?.provider ?? DEFAULT_STT_CONFIG.correction.provider,\r\n pauseThreshold:\r\n config?.correction?.pauseThreshold ?? DEFAULT_STT_CONFIG.correction.pauseThreshold,\r\n forcedInterval:\r\n config?.correction?.forcedInterval ?? DEFAULT_STT_CONFIG.correction.forcedInterval,\r\n },\r\n chunking: {\r\n chunkLengthS: config?.chunking?.chunkLengthS ?? DEFAULT_STT_CONFIG.chunking.chunkLengthS,\r\n strideLengthS: config?.chunking?.strideLengthS ?? DEFAULT_STT_CONFIG.chunking.strideLengthS,\r\n },\r\n streaming: {\r\n enabled: config?.streaming?.enabled ?? DEFAULT_STT_CONFIG.streaming.enabled,\r\n provider: config?.streaming?.provider ?? DEFAULT_STT_CONFIG.streaming.provider,\r\n },\r\n };\r\n}\r\n","/**\n * A generic, typed event emitter.\n *\n * Type parameter `T` is a map of event names to listener signatures,\n * giving consumers compile-time safety on event names and callback args.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class TypedEventEmitter<T extends Record<string, (...args: any[]) => void>> {\n private listeners = new Map<keyof T, Set<T[keyof T]>>();\n\n /** Subscribe to an event. */\n on<K extends keyof T>(event: K, listener: T[K]): void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(listener as T[keyof T]);\n }\n\n /** Unsubscribe a specific listener. No-op if not registered. */\n off<K extends keyof T>(event: K, listener: T[K]): void {\n this.listeners.get(event)?.delete(listener as T[keyof T]);\n }\n\n /** Emit an event, calling all registered listeners in insertion order. */\n emit<K extends keyof T>(event: K, ...args: Parameters<T[K]>): void {\n const set = this.listeners.get(event);\n if (!set) return;\n for (const listener of set) {\n (listener as (...a: Parameters<T[K]>) => void)(...args);\n }\n }\n\n /** Remove all listeners, optionally for a single event. */\n removeAllListeners(event?: keyof T): void {\n if (event !== undefined) {\n this.listeners.delete(event);\n } else {\n this.listeners.clear();\n }\n }\n}\n","import type { AudioCaptureHandle } from './types.js';\r\n\r\nconst TARGET_SAMPLE_RATE = 16_000;\r\n\r\n/**\r\n * Start capturing raw PCM audio from the microphone.\r\n * Uses ScriptProcessorNode to collect Float32Array samples directly.\r\n */\r\nexport async function startCapture(): Promise<AudioCaptureHandle> {\r\n const stream = await navigator.mediaDevices.getUserMedia({\r\n audio: { channelCount: 1 },\r\n });\r\n const audioCtx = new AudioContext();\r\n\r\n // Chrome may suspend AudioContext — must resume within user gesture\r\n if (audioCtx.state === 'suspended') {\r\n await audioCtx.resume();\r\n }\r\n\r\n const source = audioCtx.createMediaStreamSource(stream);\r\n const samples: Float32Array[] = [];\r\n\r\n const processor = audioCtx.createScriptProcessor(4096, 1, 1);\r\n processor.onaudioprocess = (e: AudioProcessingEvent) => {\r\n samples.push(new Float32Array(e.inputBuffer.getChannelData(0)));\r\n };\r\n\r\n // Connect through a silent gain node so mic audio doesn't play back\r\n const silencer = audioCtx.createGain();\r\n silencer.gain.value = 0;\r\n source.connect(processor);\r\n processor.connect(silencer);\r\n silencer.connect(audioCtx.destination);\r\n\r\n return { audioCtx, stream, samples, _processor: processor, _source: source, _silencer: silencer };\r\n}\r\n\r\n/**\r\n * Pause capture without releasing mic or AudioContext.\r\n * Disconnects the audio source so no new samples are collected.\r\n * Returns resampled audio from the recording period.\r\n * Call resumeCapture() to start collecting again.\r\n */\r\nexport async function pauseCapture(capture: AudioCaptureHandle): Promise<Float32Array> {\r\n capture._source.disconnect();\r\n const currentSamples = [...capture.samples];\r\n capture.samples.length = 0;\r\n return resampleAudio(currentSamples, capture.audioCtx.sampleRate);\r\n}\r\n\r\n/**\r\n * Resume a paused capture. Reconnects the audio source to the processor.\r\n * AudioContext is resumed if suspended.\r\n */\r\nexport async function resumeCapture(capture: AudioCaptureHandle): Promise<void> {\r\n if (capture.audioCtx.state === 'suspended') {\r\n await capture.audioCtx.resume();\r\n }\r\n capture._source.connect(capture._processor);\r\n}\r\n\r\n/**\r\n * Copy current audio buffer without stopping capture.\r\n * Returns a shallow copy of the samples array (each chunk is shared, not cloned).\r\n */\r\nexport function snapshotAudio(capture: AudioCaptureHandle): Float32Array[] {\r\n return [...capture.samples];\r\n}\r\n\r\n/**\r\n * Concatenate sample chunks and resample to 16kHz for Whisper.\r\n */\r\nexport async function resampleAudio(\r\n samples: Float32Array[],\r\n nativeSr: number,\r\n): Promise<Float32Array> {\r\n const totalLength = samples.reduce((sum, s) => sum + s.length, 0);\r\n if (totalLength === 0) return new Float32Array(0);\r\n\r\n const fullAudio = new Float32Array(totalLength);\r\n let offset = 0;\r\n for (const s of samples) {\r\n fullAudio.set(s, offset);\r\n offset += s.length;\r\n }\r\n\r\n if (nativeSr === TARGET_SAMPLE_RATE) return fullAudio;\r\n\r\n const duration = fullAudio.length / nativeSr;\r\n const outLength = Math.round(duration * TARGET_SAMPLE_RATE);\r\n const offline = new OfflineAudioContext(1, outLength, TARGET_SAMPLE_RATE);\r\n const buffer = offline.createBuffer(1, fullAudio.length, nativeSr);\r\n buffer.getChannelData(0).set(fullAudio);\r\n const src = offline.createBufferSource();\r\n src.buffer = buffer;\r\n src.connect(offline.destination);\r\n src.start(0);\r\n const resampled = await offline.startRendering();\r\n return resampled.getChannelData(0);\r\n}\r\n\r\n/**\r\n * Stop capturing and return resampled audio at 16kHz.\r\n */\r\nexport async function stopCapture(capture: AudioCaptureHandle): Promise<Float32Array> {\r\n const { audioCtx, stream, samples, _processor } = capture;\r\n\r\n // Disconnect processor to stop capturing\r\n try {\r\n _processor.disconnect();\r\n } catch {\r\n /* already disconnected */\r\n }\r\n\r\n // Stop microphone tracks\r\n for (const track of stream.getTracks()) {\r\n track.stop();\r\n }\r\n\r\n const nativeSr = audioCtx.sampleRate;\r\n await audioCtx.close();\r\n\r\n return resampleAudio(samples, nativeSr);\r\n}\r\n","import type { ResolvedSTTConfig, WorkerResponse } from './types.js';\nimport { TypedEventEmitter } from './event-emitter.js';\n\n/** Events emitted by the WorkerManager. */\nexport type WorkerManagerEvents = {\n progress: (percent: number) => void;\n ready: () => void;\n result: (text: string) => void;\n error: (message: string) => void;\n};\n\n/**\n * Manages the Whisper Web Worker lifecycle.\n * Provides typed message passing and a promise-based transcription API.\n */\nexport class WorkerManager extends TypedEventEmitter<WorkerManagerEvents> {\n private worker: Worker | null = null;\n private transcribeResolve: ((text: string) => void) | null = null;\n private currentTranscribePromise: Promise<string> | null = null;\n private modelReadyResolve: (() => void) | null = null;\n private modelReadyReject: ((err: Error) => void) | null = null;\n\n /** True while a transcription job is running in the worker. */\n get isTranscribing(): boolean {\n return this.transcribeResolve !== null;\n }\n\n /** Await the current in-flight transcription without starting a new one. */\n awaitCurrentTranscription(): Promise<string> {\n return this.currentTranscribePromise ?? Promise.resolve('');\n }\n\n /** Spawn the Web Worker. Must be called before loadModel/transcribe. */\n spawn(workerUrl?: URL): void {\n if (this.worker) return;\n\n const url = workerUrl ?? new URL('./whisper-worker.js', import.meta.url);\n\n this.worker = new Worker(url, { type: 'module' });\n this.worker.onmessage = (e: MessageEvent<WorkerResponse>) => {\n this.handleMessage(e.data);\n };\n this.worker.onerror = (e: ErrorEvent) => {\n this.emit('error', e.message ?? 'Worker error');\n };\n }\n\n /** Load the Whisper model in the worker. Resolves when ready. */\n async loadModel(config: ResolvedSTTConfig): Promise<void> {\n if (!this.worker) throw new Error('Worker not spawned');\n\n return new Promise<void>((resolve, reject) => {\n this.modelReadyResolve = resolve;\n this.modelReadyReject = reject;\n this.worker!.postMessage({\n type: 'load',\n config: {\n model: config.model,\n backend: config.backend,\n language: config.language,\n dtype: config.dtype,\n chunkLengthS: config.chunking.chunkLengthS,\n strideLengthS: config.chunking.strideLengthS,\n },\n });\n });\n }\n\n /** Send audio to the worker for transcription. Resolves with text. */\n async transcribe(audio: Float32Array): Promise<string> {\n if (!this.worker) throw new Error('Worker not spawned');\n if (audio.length === 0) return '';\n\n this.currentTranscribePromise = new Promise<string>((resolve) => {\n this.transcribeResolve = resolve;\n this.worker!.postMessage({ type: 'transcribe', audio }, [audio.buffer]);\n });\n return this.currentTranscribePromise;\n }\n\n /** Cancel any in-flight transcription. */\n cancel(): void {\n this.worker?.postMessage({ type: 'cancel' });\n if (this.transcribeResolve) {\n this.transcribeResolve('');\n this.transcribeResolve = null;\n }\n }\n\n /** Terminate the worker and release resources. */\n destroy(): void {\n this.cancel();\n this.worker?.terminate();\n this.worker = null;\n this.removeAllListeners();\n }\n\n private handleMessage(msg: WorkerResponse): void {\n switch (msg.type) {\n case 'progress':\n this.emit('progress', msg.data as number);\n break;\n case 'ready':\n this.emit('ready');\n this.modelReadyResolve?.();\n this.modelReadyResolve = null;\n this.modelReadyReject = null;\n break;\n case 'result':\n this.emit('result', msg.data as string);\n this.transcribeResolve?.(msg.data as string);\n this.transcribeResolve = null;\n break;\n case 'error': {\n const errMsg = msg.data as string;\n this.emit('error', errMsg);\n // Reject model load if still pending\n if (this.modelReadyReject) {\n this.modelReadyReject(new Error(errMsg));\n this.modelReadyResolve = null;\n this.modelReadyReject = null;\n }\n // Resolve transcribe with empty string on error\n if (this.transcribeResolve) {\n this.transcribeResolve('');\n this.transcribeResolve = null;\n }\n break;\n }\n }\n }\n}\n","import type { ResolvedSTTConfig } from './types.js';\n\n/**\n * Manages mid-recording correction timing.\n * Two triggers: pause detection and forced interval.\n */\n/** Delay (ms) before the first correction fires after recording starts. */\nconst INITIAL_CORRECTION_DELAY_MS = 1_000;\n\nexport class CorrectionOrchestrator {\n private forcedTimer: ReturnType<typeof setInterval> | null = null;\n private initialTimer: ReturnType<typeof setTimeout> | null = null;\n private lastCorrectionTime = 0;\n private correctionFn: (() => void) | null = null;\n private config: ResolvedSTTConfig['correction'];\n\n /** Create a new correction orchestrator with the given timing config. */\n constructor(config: ResolvedSTTConfig['correction']) {\n this.config = config;\n }\n\n /** Set the function to call when a correction is triggered. */\n setCorrectionFn(fn: () => void): void {\n this.correctionFn = fn;\n }\n\n /** Start the correction orchestrator.\n * Fires a quick initial correction after 1s for early feedback, then\n * switches to the regular forcedInterval cadence from that point. */\n start(): void {\n if (!this.config.enabled) return;\n\n this.lastCorrectionTime = Date.now();\n // One-shot early correction — gives the user immediate Whisper feedback\n // before the Web Speech API has produced its first interim result (~1-2s).\n this.initialTimer = setTimeout(() => {\n this.initialTimer = null;\n this.correctionFn?.();\n this.lastCorrectionTime = Date.now();\n this.startForcedTimer();\n }, INITIAL_CORRECTION_DELAY_MS);\n }\n\n /** Stop the orchestrator (clear all timers). */\n stop(): void {\n if (this.initialTimer) {\n clearTimeout(this.initialTimer);\n this.initialTimer = null;\n }\n this.stopForcedTimer();\n }\n\n /** Called when a speech pause is detected. Triggers correction if cooldown elapsed. */\n onPauseDetected(): void {\n if (!this.config.enabled) return;\n\n const now = Date.now();\n if (now - this.lastCorrectionTime < this.config.pauseThreshold) return;\n\n this.triggerCorrection();\n }\n\n /** Force a correction now (resets timer). */\n forceCorrection(): void {\n this.triggerCorrection();\n }\n\n private triggerCorrection(): void {\n this.lastCorrectionTime = Date.now();\n this.correctionFn?.();\n // Reset forced timer after any correction\n this.restartForcedTimer();\n }\n\n private startForcedTimer(): void {\n this.stopForcedTimer();\n this.forcedTimer = setInterval(() => {\n this.triggerCorrection();\n }, this.config.forcedInterval);\n }\n\n private stopForcedTimer(): void {\n if (this.forcedTimer) {\n clearInterval(this.forcedTimer);\n this.forcedTimer = null;\n }\n }\n\n private restartForcedTimer(): void {\n if (this.forcedTimer) {\n this.startForcedTimer();\n }\n }\n}\n","/* ─── Web Speech API types ──────────────────────────────── */\r\n\r\ninterface SpeechRecognitionEvent {\r\n results: SpeechRecognitionResultList;\r\n resultIndex: number;\r\n}\r\n\r\ninterface SpeechRecognitionErrorEvent {\r\n error: string;\r\n}\r\n\r\ninterface SpeechRecognitionInstance {\r\n continuous: boolean;\r\n interimResults: boolean;\r\n lang: string;\r\n onaudiostart: (() => void) | null;\r\n onresult: ((e: SpeechRecognitionEvent) => void) | null;\r\n onerror: ((e: SpeechRecognitionErrorEvent) => void) | null;\r\n onend: (() => void) | null;\r\n start: () => void;\r\n stop: () => void;\r\n abort: () => void;\r\n}\r\n\r\ntype SpeechRecognitionCtor = new () => SpeechRecognitionInstance;\r\n\r\nfunction getSpeechRecognition(): SpeechRecognitionCtor | null {\r\n if (typeof globalThis === 'undefined') return null;\r\n const w = globalThis as unknown as Record<string, unknown>;\r\n return (w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null) as SpeechRecognitionCtor | null;\r\n}\r\n\r\n/* ─── Language mapping ──────────────────────────────────── */\r\n\r\n/** Map Whisper language codes to BCP-47 locale tags for the Speech API. */\r\nconst WHISPER_TO_BCP47: Record<string, string> = {\r\n en: 'en-US',\r\n english: 'en-US',\r\n zh: 'zh-CN',\r\n chinese: 'zh-CN',\r\n de: 'de-DE',\r\n german: 'de-DE',\r\n es: 'es-ES',\r\n spanish: 'es-ES',\r\n ru: 'ru-RU',\r\n russian: 'ru-RU',\r\n ko: 'ko-KR',\r\n korean: 'ko-KR',\r\n fr: 'fr-FR',\r\n french: 'fr-FR',\r\n ja: 'ja-JP',\r\n japanese: 'ja-JP',\r\n pt: 'pt-BR',\r\n portuguese: 'pt-BR',\r\n tr: 'tr-TR',\r\n turkish: 'tr-TR',\r\n pl: 'pl-PL',\r\n polish: 'pl-PL',\r\n nl: 'nl-NL',\r\n dutch: 'nl-NL',\r\n ar: 'ar-SA',\r\n arabic: 'ar-SA',\r\n sv: 'sv-SE',\r\n swedish: 'sv-SE',\r\n it: 'it-IT',\r\n italian: 'it-IT',\r\n id: 'id-ID',\r\n indonesian: 'id-ID',\r\n hi: 'hi-IN',\r\n hindi: 'hi-IN',\r\n fi: 'fi-FI',\r\n finnish: 'fi-FI',\r\n vi: 'vi-VN',\r\n vietnamese: 'vi-VN',\r\n he: 'he-IL',\r\n hebrew: 'he-IL',\r\n uk: 'uk-UA',\r\n ukrainian: 'uk-UA',\r\n el: 'el-GR',\r\n greek: 'el-GR',\r\n ms: 'ms-MY',\r\n malay: 'ms-MY',\r\n cs: 'cs-CZ',\r\n czech: 'cs-CZ',\r\n ro: 'ro-RO',\r\n romanian: 'ro-RO',\r\n da: 'da-DK',\r\n danish: 'da-DK',\r\n hu: 'hu-HU',\r\n hungarian: 'hu-HU',\r\n no: 'nb-NO',\r\n norwegian: 'nb-NO',\r\n th: 'th-TH',\r\n thai: 'th-TH',\r\n};\r\n\r\n/**\r\n * Convert a Whisper language code to a BCP-47 locale tag for the Speech API.\r\n * Already-BCP-47 codes (containing '-') pass through unchanged.\r\n */\r\nfunction toBCP47(language: string): string {\r\n if (language.includes('-')) return language;\r\n return WHISPER_TO_BCP47[language.toLowerCase()] ?? language;\r\n}\r\n\r\n/* ─── SpeechStreamingManager ────────────────────────────── */\r\n\r\n/**\r\n * Manages Web Speech API for real-time streaming transcript preview.\r\n * Provides word-by-word interim text while Whisper handles corrections.\r\n *\r\n * Keep-alive design: the recognition session is never stopped between user\r\n * sessions (only muted). This eliminates the 1-2s Google server handshake\r\n * on every click — start() resolves instantly when the session is warm.\r\n */\r\nconst NO_RESULT_TIMEOUT_MS = 5_000;\r\n/** How long to keep recognition running silently after stop() before truly releasing it. */\r\nconst IDLE_TIMEOUT_MS = 30_000;\r\n\r\nexport class SpeechStreamingManager {\r\n private recognition: SpeechRecognitionInstance | null = null;\r\n private accumulated = '';\r\n private active = false; // user is recording — emit results, trigger onPause on end\r\n private keepingWarm = false; // recognition running silently between user sessions\r\n private currentLang = ''; // BCP-47 of running recognition (for fast-path check)\r\n private receivedResult = false;\r\n private lastFinalIndex = -1; // instance fields so warm-restart resets them cleanly\r\n private lastFinalText = '';\r\n private noResultTimer: ReturnType<typeof setTimeout> | null = null;\r\n private idleTimer: ReturnType<typeof setTimeout> | null = null;\r\n private onTranscript: ((text: string) => void) | null = null;\r\n private onPause: (() => void) | null = null;\r\n private onError: ((message: string) => void) | null = null;\r\n private onDebug: ((message: string) => void) | null = null;\r\n\r\n /** Check if the Web Speech API is available in this environment. */\r\n static isSupported(): boolean {\r\n return getSpeechRecognition() !== null;\r\n }\r\n\r\n /** Set callback for streaming transcript updates (interim + final text). */\r\n setOnTranscript(fn: (text: string) => void): void {\r\n this.onTranscript = fn;\r\n }\r\n\r\n /** Set callback for speech pause detection (Speech API onend). */\r\n setOnPause(fn: () => void): void {\r\n this.onPause = fn;\r\n }\r\n\r\n /** Set callback for errors. */\r\n setOnError(fn: (message: string) => void): void {\r\n this.onError = fn;\r\n }\r\n\r\n /** Set callback for diagnostic debug messages. */\r\n setOnDebug(fn: (message: string) => void): void {\r\n this.onDebug = fn;\r\n }\r\n\r\n private log(message: string): void {\r\n this.onDebug?.(message);\r\n console.warn(message);\r\n }\r\n\r\n /**\r\n * Pre-warm: start recognition in muted mode so it's ready before the user\r\n * clicks. Call after engine.init() completes. Eliminates startup latency on\r\n * first click by keeping the Google Speech session alive.\r\n */\r\n preWarm(language: string): void {\r\n const SR = getSpeechRecognition();\r\n if (!SR) return;\r\n const bcp47 = toBCP47(language);\r\n if (this.recognition && this.currentLang === bcp47) return; // already warm\r\n this.log(`[SSM] preWarm() — lang: \"${language}\" → \"${bcp47}\"`);\r\n this.keepingWarm = true;\r\n this.active = false;\r\n this.clearIdleTimer();\r\n this.spawnRecognition(language);\r\n }\r\n\r\n /**\r\n * Start streaming recognition. If recognition is already warm (session\r\n * running from preWarm or a previous session within the idle window),\r\n * activates instantly — no Google handshake. Otherwise cold-starts.\r\n */\r\n start(language: string, skipMicWait = false): Promise<void> {\r\n const SR = getSpeechRecognition();\r\n if (!SR) {\r\n this.log('[SSM] SpeechRecognition not available in this environment');\r\n return Promise.resolve();\r\n }\r\n\r\n const bcp47 = toBCP47(language);\r\n this.log(`[SSM] start() — lang: \"${language}\" → \"${bcp47}\"`);\r\n\r\n this.accumulated = '';\r\n this.receivedResult = false;\r\n this.lastFinalIndex = -1;\r\n this.lastFinalText = '';\r\n this.clearIdleTimer();\r\n\r\n // ── Fast path: session already running with the right language ──────────\r\n if (this.recognition && this.currentLang === bcp47) {\r\n this.log('[SSM] start() — warm session, activating immediately');\r\n this.keepingWarm = false;\r\n this.active = true;\r\n // Arm the no-result watchdog for silent-failure detection\r\n this.clearNoResultTimer();\r\n this.noResultTimer = setTimeout(() => {\r\n if (this.active && !this.receivedResult) {\r\n this.log('[SSM] no-result timeout fired — no onresult in 5s');\r\n this.onError?.(\r\n 'Speech streaming started but received no results. ' +\r\n 'Mic may be blocked by another audio capture.',\r\n );\r\n }\r\n }, NO_RESULT_TIMEOUT_MS);\r\n return Promise.resolve();\r\n }\r\n\r\n // ── Cold start: spin up a fresh recognition session ─────────────────────\r\n this.keepingWarm = false;\r\n this.active = true;\r\n return this.spawnRecognition(language, skipMicWait);\r\n }\r\n\r\n /**\r\n * Create and start a new SpeechRecognition instance.\r\n * Used by both preWarm() (active=false) and start() cold path (active=true).\r\n */\r\n private spawnRecognition(language: string, skipMicWait = false): Promise<void> {\r\n const SR = getSpeechRecognition()!;\r\n const bcp47 = toBCP47(language);\r\n\r\n // Tear down any stale session first\r\n if (this.recognition) {\r\n const old = this.recognition;\r\n this.recognition = null;\r\n try { old.stop(); } catch { /* already stopped */ }\r\n }\r\n\r\n this.currentLang = bcp47;\r\n\r\n const recognition = new SR();\r\n recognition.continuous = true;\r\n recognition.interimResults = true;\r\n recognition.lang = bcp47;\r\n\r\n // Mic-claim promise (used on cold active starts only)\r\n let micReady = false;\r\n const micClaimPromise = new Promise<void>((resolve) => {\r\n recognition.onaudiostart = () => {\r\n if (this.recognition !== recognition) return;\r\n this.log('[SSM] onaudiostart — mic acquired by Speech API');\r\n if (!micReady) { micReady = true; resolve(); }\r\n };\r\n setTimeout(() => {\r\n if (!micReady) {\r\n micReady = true;\r\n this.log('[SSM] mic-claim fallback — proceeding after 300ms');\r\n resolve();\r\n }\r\n }, 300);\r\n });\r\n\r\n // No-result watchdog — only when actively recording\r\n this.clearNoResultTimer();\r\n if (this.active) {\r\n this.noResultTimer = setTimeout(() => {\r\n if (this.active && !this.receivedResult) {\r\n this.log('[SSM] no-result timeout fired — no onresult in 5s');\r\n this.onError?.(\r\n 'Speech streaming started but received no results. ' +\r\n 'Mic may be blocked by another audio capture.',\r\n );\r\n }\r\n }, NO_RESULT_TIMEOUT_MS);\r\n }\r\n\r\n recognition.onresult = (e: SpeechRecognitionEvent) => {\r\n if (this.recognition !== recognition) return;\r\n this.receivedResult = true;\r\n this.clearNoResultTimer();\r\n\r\n // Discard results while muted (keeping warm between user sessions)\r\n if (!this.active) return;\r\n\r\n let final_ = '';\r\n let interim = '';\r\n for (let i = e.resultIndex; i < e.results.length; i++) {\r\n const t = e.results[i][0].transcript;\r\n if (e.results[i].isFinal) {\r\n if (i > this.lastFinalIndex) {\r\n final_ += t;\r\n this.lastFinalIndex = i;\r\n }\r\n } else {\r\n interim += t;\r\n }\r\n }\r\n\r\n this.log(\r\n `[SSM] onresult — finals: \"${final_}\", interim: \"${interim}\", accumulated: \"${this.accumulated}\"`,\r\n );\r\n\r\n if (final_ && final_.trim() !== this.lastFinalText) {\r\n this.lastFinalText = final_.trim();\r\n this.accumulated = this.accumulated\r\n ? this.accumulated + ' ' + final_.trim()\r\n : final_.trim();\r\n this.onTranscript?.(this.accumulated);\r\n } else if (interim) {\r\n const trimmed = interim.trimStart();\r\n const full = this.accumulated ? this.accumulated + ' ' + trimmed : trimmed;\r\n this.onTranscript?.(full);\r\n }\r\n };\r\n\r\n recognition.onerror = (e: SpeechRecognitionErrorEvent) => {\r\n if (this.recognition !== recognition) return;\r\n this.log(`[SSM] onerror — ${e.error}`);\r\n // Suppress errors while muted — they're not relevant to the user\r\n if (this.active) this.onError?.(e.error);\r\n };\r\n\r\n recognition.onend = () => {\r\n if (this.recognition !== recognition) return;\r\n this.log(\r\n `[SSM] onend — active: ${this.active}, keepingWarm: ${this.keepingWarm}, receivedResult: ${this.receivedResult}`,\r\n );\r\n\r\n if (this.active) {\r\n // User is recording — trigger correction and restart for continued streaming\r\n this.onPause?.();\r\n try {\r\n recognition.start();\r\n this.log('[SSM] restarted after pause');\r\n } catch (err) {\r\n this.log(`[SSM] restart THREW: ${err}`);\r\n this.recognition = null;\r\n this.currentLang = '';\r\n this.onError?.('Speech recognition failed to restart after pause.');\r\n }\r\n } else if (this.keepingWarm) {\r\n // Between user sessions — restart silently to keep the session alive\r\n try {\r\n recognition.start();\r\n this.log('[SSM] warm restart');\r\n } catch (err) {\r\n this.log(`[SSM] warm restart THREW: ${err}`);\r\n this.recognition = null;\r\n this.keepingWarm = false;\r\n this.currentLang = '';\r\n }\r\n } else {\r\n this.recognition = null;\r\n this.currentLang = '';\r\n }\r\n };\r\n\r\n this.recognition = recognition;\r\n try {\r\n recognition.start();\r\n this.log('[SSM] recognition.start() succeeded');\r\n } catch (err) {\r\n this.log(`[SSM] recognition.start() THREW: ${err}`);\r\n this.recognition = null;\r\n this.currentLang = '';\r\n const wasActive = this.active;\r\n this.active = false;\r\n this.keepingWarm = false;\r\n this.clearNoResultTimer();\r\n if (wasActive) {\r\n this.onError?.(\r\n `Speech recognition failed to start: ${err instanceof Error ? err.message : String(err)}`,\r\n );\r\n }\r\n return Promise.resolve();\r\n }\r\n\r\n // preWarm path or skipMicWait — no need to wait for mic claim\r\n if (!this.active || skipMicWait) {\r\n if (skipMicWait) this.log('[SSM] skipMicWait — warm restart, returning immediately');\r\n return Promise.resolve();\r\n }\r\n\r\n return micClaimPromise;\r\n }\r\n\r\n private clearNoResultTimer(): void {\r\n if (this.noResultTimer) {\r\n clearTimeout(this.noResultTimer);\r\n this.noResultTimer = null;\r\n }\r\n }\r\n\r\n private clearIdleTimer(): void {\r\n if (this.idleTimer) {\r\n clearTimeout(this.idleTimer);\r\n this.idleTimer = null;\r\n }\r\n }\r\n\r\n private startIdleTimer(): void {\r\n this.clearIdleTimer();\r\n this.idleTimer = setTimeout(() => {\r\n this.idleTimer = null;\r\n this.keepingWarm = false;\r\n this.log('[SSM] idle timeout — stopping recognition');\r\n if (this.recognition) {\r\n const rec = this.recognition;\r\n this.recognition = null;\r\n this.currentLang = '';\r\n try { rec.stop(); } catch { /* already stopped */ }\r\n }\r\n }, IDLE_TIMEOUT_MS);\r\n }\r\n\r\n /** Stop streaming recognition and return accumulated text.\r\n * Keeps the recognition session alive (muted) for instant restart. */\r\n stop(): string {\r\n this.active = false;\r\n this.keepingWarm = true;\r\n this.clearNoResultTimer();\r\n const result = this.accumulated;\r\n this.accumulated = '';\r\n // Keep recognition running silently; release after idle timeout\r\n this.startIdleTimer();\r\n return result;\r\n }\r\n\r\n /** Abort immediately and release all resources. */\r\n destroy(): void {\r\n this.active = false;\r\n this.keepingWarm = false;\r\n this.clearNoResultTimer();\r\n this.clearIdleTimer();\r\n if (this.recognition) {\r\n const rec = this.recognition;\r\n this.recognition = null;\r\n this.currentLang = '';\r\n rec.abort();\r\n }\r\n this.accumulated = '';\r\n this.onTranscript = null;\r\n this.onPause = null;\r\n this.onError = null;\r\n this.onDebug = null;\r\n }\r\n}\r\n","import type {\r\n STTConfig,\r\n STTState,\r\n STTEvents,\r\n STTStatus,\r\n ResolvedSTTConfig,\r\n AudioCaptureHandle,\r\n} from './types.js';\r\nimport { resolveConfig } from './types.js';\r\nimport { TypedEventEmitter } from './event-emitter.js';\r\nimport { startCapture, pauseCapture, resumeCapture, snapshotAudio, resampleAudio } from './audio-capture.js';\r\nimport { WorkerManager } from './worker-manager.js';\r\nimport { CorrectionOrchestrator } from './correction-orchestrator.js';\r\nimport { SpeechStreamingManager } from './speech-streaming.js';\r\n\r\n/**\r\n * Main STT engine — the public API for speech-to-text with Whisper correction.\r\n *\r\n * Usage:\r\n * ```typescript\r\n * const engine = new STTEngine({ model: 'tiny' });\r\n * engine.on('transcript', (text) => console.log(text));\r\n * engine.on('correction', (text) => console.log('corrected:', text));\r\n * await engine.init();\r\n * await engine.start();\r\n * const finalText = await engine.stop();\r\n * ```\r\n */\r\nexport class STTEngine extends TypedEventEmitter<STTEvents> {\r\n private config: ResolvedSTTConfig;\r\n private workerManager: WorkerManager;\r\n private correctionOrchestrator: CorrectionOrchestrator;\r\n private speechStreaming: SpeechStreamingManager;\r\n private capture: AudioCaptureHandle | null = null;\r\n private state: STTState;\r\n private workerUrl?: URL;\r\n /** Prevents performCorrection from emitting while stop() is consuming the in-flight result. */\r\n private _stopping = false;\r\n\r\n /**\r\n * Create a new STT engine instance.\r\n * @param config - Optional configuration overrides (model, backend, language, etc.).\r\n * @param workerUrl - Optional custom URL for the Whisper Web Worker script.\r\n */\r\n constructor(config?: STTConfig, workerUrl?: URL) {\r\n super();\r\n this.config = resolveConfig(config);\r\n this.workerManager = new WorkerManager();\r\n this.correctionOrchestrator = new CorrectionOrchestrator(this.config.correction);\r\n this.speechStreaming = new SpeechStreamingManager();\r\n this.workerUrl = workerUrl;\r\n\r\n this.state = {\r\n status: 'idle',\r\n isModelLoaded: false,\r\n loadProgress: 0,\r\n backend: null,\r\n error: null,\r\n };\r\n\r\n this.correctionOrchestrator.setCorrectionFn(() => {\r\n this.performCorrection();\r\n });\r\n\r\n this.setupWorkerListeners();\r\n this.setupStreamingCallbacks();\r\n }\r\n\r\n /** Initialize the engine: spawn worker and load model. */\r\n async init(): Promise<void> {\r\n this.updateStatus('loading');\r\n this.workerManager.spawn(this.workerUrl);\r\n\r\n try {\r\n await this.workerManager.loadModel(this.config);\r\n this.state.isModelLoaded = true;\r\n this.updateStatus('ready');\r\n // Pre-warm recognition so the first click is instant (no Google handshake delay)\r\n if (this.config.streaming.enabled) {\r\n this.speechStreaming.preWarm(this.config.language);\r\n }\r\n } catch (err) {\r\n this.emitError('MODEL_LOAD_FAILED', err instanceof Error ? err.message : String(err));\r\n this.updateStatus('idle');\r\n throw err;\r\n }\r\n }\r\n\r\n /** Start recording audio and enable correction cycles. */\r\n async start(): Promise<void> {\r\n if (this.state.status !== 'ready') {\r\n throw new Error(`Cannot start: engine is \"${this.state.status}\", expected \"ready\"`);\r\n }\r\n\r\n try {\r\n // Check if mic is already warm from a previous recording session.\r\n // When warm, skip getUserMedia and the 300ms SR mic-claim wait.\r\n const warmCapture =\r\n this.capture &&\r\n this.capture.stream.getTracks().every((t) => t.readyState === 'live');\r\n\r\n this.emitDebug(\r\n `[STT] start() — streaming: ${this.config.streaming.enabled}, lang: \"${this.config.language}\", warm: ${!!warmCapture}`,\r\n );\r\n\r\n if (this.config.streaming.enabled) {\r\n // On warm restart, skip the mic-claim wait — no getUserMedia race.\r\n await this.speechStreaming.start(this.config.language, !!warmCapture);\r\n if (!warmCapture) {\r\n this.emitDebug('[STT] Speech API mic claim complete — starting getUserMedia');\r\n }\r\n }\r\n\r\n if (warmCapture) {\r\n await resumeCapture(this.capture!);\r\n this.emitDebug('[STT] warm mic resumed — skipped getUserMedia');\r\n } else {\r\n // First start or stale capture: full mic init with getUserMedia.\r\n this.capture = await startCapture();\r\n }\r\n\r\n this.updateStatus('recording');\r\n this.correctionOrchestrator.start();\r\n } catch (err) {\r\n this.emitError(\r\n 'MIC_DENIED',\r\n err instanceof Error ? err.message : 'Microphone access denied. Check browser permissions.',\r\n );\r\n }\r\n }\r\n\r\n /** Stop recording, run final transcription, return text.\r\n * Mic and AudioContext stay alive for fast restart — call destroy() to fully release. */\r\n async stop(): Promise<string> {\r\n if (!this.capture) return '';\r\n\r\n // Prevent any in-flight performCorrection from emitting — stop() will own the final emit.\r\n this._stopping = true;\r\n this.correctionOrchestrator.stop();\r\n this.speechStreaming.stop();\r\n\r\n this.updateStatus('processing');\r\n\r\n // If a correction job is already running, reuse it: Whisper can't be interrupted mid-inference\r\n // anyway, so cancelling just wastes the work and causes a second full run.\r\n if (this.workerManager.isTranscribing) {\r\n try {\r\n // Resample audio and await the in-flight result in parallel.\r\n const [audio, inFlightText] = await Promise.all([\r\n pauseCapture(this.capture),\r\n this.workerManager.awaitCurrentTranscription(),\r\n ]);\r\n this._stopping = false;\r\n\r\n const text = inFlightText.trim();\r\n if (text) {\r\n this.emit('correction', text);\r\n this.updateStatus('ready');\r\n return text;\r\n }\r\n\r\n // In-flight returned empty (cancelled or error) — fall through to fresh transcription.\r\n if (audio.length > 0) {\r\n const freshText = await this.workerManager.transcribe(audio);\r\n this.emit('correction', freshText);\r\n this.updateStatus('ready');\r\n return freshText;\r\n }\r\n\r\n this.updateStatus('ready');\r\n return '';\r\n } catch (err) {\r\n this._stopping = false;\r\n this.emitError(\r\n 'TRANSCRIPTION_FAILED',\r\n err instanceof Error ? err.message : 'Final transcription failed.',\r\n );\r\n this.updateStatus('ready');\r\n return '';\r\n }\r\n }\r\n\r\n // No job in-flight — cancel is a no-op, run fresh transcription.\r\n this.workerManager.cancel();\r\n this._stopping = false;\r\n\r\n try {\r\n // Soft pause — keeps stream and AudioContext alive for fast restart.\r\n const audio = await pauseCapture(this.capture);\r\n\r\n if (audio.length === 0) {\r\n this.updateStatus('ready');\r\n return '';\r\n }\r\n\r\n const text = await this.workerManager.transcribe(audio);\r\n this.emit('correction', text);\r\n this.updateStatus('ready');\r\n return text;\r\n } catch (err) {\r\n this.emitError(\r\n 'TRANSCRIPTION_FAILED',\r\n err instanceof Error ? err.message : 'Final transcription failed.',\r\n );\r\n this.updateStatus('ready');\r\n return '';\r\n }\r\n }\r\n\r\n /** Destroy the engine: terminate worker, release all resources. */\r\n destroy(): void {\r\n this.correctionOrchestrator.stop();\r\n this.speechStreaming.destroy();\r\n\r\n if (this.capture) {\r\n try {\r\n this.capture._processor.disconnect();\r\n } catch {\r\n /* already disconnected */\r\n }\r\n for (const track of this.capture.stream.getTracks()) {\r\n track.stop();\r\n }\r\n this.capture.audioCtx.close().catch(() => {});\r\n this.capture = null;\r\n }\r\n\r\n this.workerManager.destroy();\r\n this.updateStatus('idle');\r\n this.removeAllListeners();\r\n }\r\n\r\n /** Get current engine state. */\r\n getState(): Readonly<STTState> {\r\n return { ...this.state };\r\n }\r\n\r\n /** Notify the correction orchestrator of a speech pause. */\r\n notifyPause(): void {\r\n this.correctionOrchestrator.onPauseDetected();\r\n }\r\n\r\n private async performCorrection(): Promise<void> {\r\n if (!this.capture || !this.state.isModelLoaded) return;\r\n\r\n this.workerManager.cancel();\r\n\r\n try {\r\n const samples = snapshotAudio(this.capture);\r\n const nativeSr = this.capture.audioCtx.sampleRate;\r\n const audio = await resampleAudio(samples, nativeSr);\r\n\r\n if (audio.length === 0) return;\r\n\r\n const text = await this.workerManager.transcribe(audio);\r\n if (text.trim() && this.capture && !this._stopping) {\r\n this.emit('correction', text);\r\n }\r\n } catch (err) {\r\n this.emitError(\r\n 'TRANSCRIPTION_FAILED',\r\n err instanceof Error ? err.message : 'Correction transcription failed.',\r\n );\r\n // Recording continues — error is non-fatal\r\n }\r\n }\r\n\r\n private setupStreamingCallbacks(): void {\r\n this.speechStreaming.setOnDebug((message) => {\r\n this.emit('debug', message);\r\n });\r\n\r\n this.speechStreaming.setOnTranscript((text) => {\r\n this.emitDebug(`[STT] transcript callback — \"${text}\"`);\r\n this.emit('transcript', text);\r\n });\r\n\r\n this.speechStreaming.setOnPause(() => {\r\n this.emitDebug('[STT] pause callback — triggering correction');\r\n this.correctionOrchestrator.onPauseDetected();\r\n });\r\n\r\n this.speechStreaming.setOnError((message) => {\r\n this.emitDebug(`[STT] streaming error — \"${message}\"`);\r\n this.emitError('STREAMING_ERROR', message);\r\n });\r\n }\r\n\r\n private setupWorkerListeners(): void {\r\n this.workerManager.on('progress', (percent) => {\r\n this.state.loadProgress = percent;\r\n this.emit('status', { ...this.state });\r\n });\r\n\r\n this.workerManager.on('error', (message) => {\r\n this.emitError('WORKER_ERROR', message);\r\n });\r\n }\r\n\r\n private updateStatus(status: STTStatus): void {\r\n this.state.status = status;\r\n this.state.error = null;\r\n this.emit('status', { ...this.state });\r\n }\r\n\r\n private emitError(code: string, message: string): void {\r\n this.state.error = message;\r\n this.emit('error', { code, message });\r\n }\r\n\r\n private emitDebug(message: string): void {\r\n console.warn(message);\r\n this.emit('debug', message);\r\n }\r\n}\r\n"],"mappings":"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","names":[]}