@omote/core 0.10.5 → 0.10.6
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/README.md +76 -34
- package/dist/chunk-3FILA2CD.mjs +785 -0
- package/dist/chunk-3FILA2CD.mjs.map +1 -0
- package/dist/chunk-5WIOGMJA.mjs +785 -0
- package/dist/chunk-5WIOGMJA.mjs.map +1 -0
- package/dist/chunk-NWZMIQK4.mjs +782 -0
- package/dist/chunk-NWZMIQK4.mjs.map +1 -0
- package/dist/chunk-WW4XAUJ3.mjs +208 -0
- package/dist/chunk-WW4XAUJ3.mjs.map +1 -0
- package/dist/index.d.mts +84 -79
- package/dist/index.d.ts +84 -79
- package/dist/index.js +514 -406
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +233 -199
- package/dist/index.mjs.map +1 -1
- package/dist/logging/index.js +5 -0
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs +1 -1
- package/dist/otlp-2BML6FIK.mjs +7 -0
- package/dist/otlp-2BML6FIK.mjs.map +1 -0
- package/package.json +1 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/audio/audioConvert.ts","../src/audio/audioUtils.ts","../src/audio/MicrophoneCapture.ts","../src/audio/RingBuffer.ts","../src/audio/AudioScheduler.ts","../src/audio/AudioChunkCoalescer.ts","../src/inference/BlendshapeSmoother.ts","../src/inference/A2EProcessor.ts","../src/telemetry/types.ts","../src/inference/blendshapeUtils.ts","../src/audio/expressionProfile.ts","../src/audio/PlaybackPipeline.ts","../src/audio/TTSPlayback.ts","../src/inference/defaultModelUrls.ts","../src/utils/runtime.ts","../src/inference/onnxLoader.ts","../src/inference/UnifiedInferenceWorker.ts","../src/inference/sharedLazyWorker.ts","../src/inference/ctcDecoder.ts","../src/inference/adapters/SenseVoiceUnifiedAdapter.ts","../src/inference/adapters/A2EUnifiedAdapter.ts","../src/inference/kokoroTokenizer.ts","../src/inference/kokoroPhonemizer.ts","../src/cache/ModelCache.ts","../src/inference/kokoroVoices.ts","../src/inference/KokoroTTSTypes.ts","../src/inference/adapters/KokoroTTSUnifiedAdapter.ts","../src/inference/adapters/SileroVADUnifiedAdapter.ts","../src/inference/createA2E.ts","../src/audio/TTSSpeaker.ts","../src/inference/createKokoroTTS.ts","../src/audio/createTTSPlayer.ts","../src/inference/createSenseVoice.ts","../src/inference/createSileroVAD.ts","../src/audio/SpeechListener.ts","../src/audio/InterruptionHandler.ts","../src/inference/SafariSpeechRecognition.ts","../src/inference/ElevenLabsTTSBackend.ts","../src/emotion/Emotion.ts","../src/animation/types.ts","../src/animation/AnimationGraph.ts","../src/animation/audioEnergy.ts","../src/animation/ProceduralLifeLayer.ts","../src/animation/bodyAnimation.ts","../src/face/FACSMapping.ts","../src/face/EmotionResolver.ts","../src/face/FaceCompositor.ts","../src/face/TextEmotionAnalyzer.ts","../src/face/EmotionTagParser.ts","../src/character/CharacterController.ts","../src/orchestration/MicLipSync.ts","../src/orchestration/VoiceOrchestrator.ts","../../types/src/protocol.ts"],"sourcesContent":["/**\r\n * Audio format conversion utilities\r\n *\r\n * Bridges the gap between TTS engines (Float32 at various sample rates)\r\n * and playback pipelines (Uint8Array PCM16 at 16kHz).\r\n *\r\n * @module audio/audioConvert\r\n */\r\n\r\n/**\r\n * Convert Float32 [-1,1] samples to PCM16 Uint8Array (little-endian).\r\n *\r\n * @param samples - Float32Array of normalized audio samples\r\n * @returns Uint8Array of PCM16 bytes (2 bytes per sample, little-endian)\r\n */\r\nexport function float32ToPcm16(samples: Float32Array): Uint8Array {\r\n const buffer = new ArrayBuffer(samples.length * 2);\r\n const view = new DataView(buffer);\r\n\r\n for (let i = 0; i < samples.length; i++) {\r\n // Clamp to [-1, 1] then scale to Int16 range\r\n const s = Math.max(-1, Math.min(1, samples[i]));\r\n const val = s < 0 ? s * 32768 : s * 32767;\r\n view.setInt16(i * 2, val, true); // little-endian\r\n }\r\n\r\n return new Uint8Array(buffer);\r\n}\r\n\r\n/**\r\n * Linear interpolation resampler.\r\n * Good enough for speech (no sinc filtering needed).\r\n *\r\n * @param samples - Input audio samples\r\n * @param fromRate - Source sample rate (e.g., 24000)\r\n * @param toRate - Target sample rate (e.g., 16000)\r\n * @returns Resampled Float32Array\r\n */\r\nexport function resampleLinear(\r\n samples: Float32Array,\r\n fromRate: number,\r\n toRate: number,\r\n): Float32Array {\r\n if (fromRate === toRate) return samples;\r\n if (samples.length === 0) return new Float32Array(0);\r\n\r\n const ratio = fromRate / toRate;\r\n const outputLength = Math.floor(samples.length / ratio);\r\n if (outputLength === 0) return new Float32Array(0);\r\n\r\n const output = new Float32Array(outputLength);\r\n\r\n for (let i = 0; i < outputLength; i++) {\r\n const srcPos = i * ratio;\r\n const srcIndex = Math.floor(srcPos);\r\n const frac = srcPos - srcIndex;\r\n\r\n if (srcIndex + 1 < samples.length) {\r\n output[i] = samples[srcIndex] * (1 - frac) + samples[srcIndex + 1] * frac;\r\n } else {\r\n output[i] = samples[Math.min(srcIndex, samples.length - 1)];\r\n }\r\n }\r\n\r\n return output;\r\n}\r\n\r\n/**\r\n * Convenience: resample + encode in one call.\r\n * Converts TTS output (Float32 at TTS rate) to pipeline format (PCM16 Uint8Array at 16kHz).\r\n *\r\n * @param audio - Float32Array from TTS engine\r\n * @param sourceRate - TTS engine's output sample rate (default: 24000)\r\n * @param targetRate - Pipeline's expected sample rate (default: 16000)\r\n * @returns Uint8Array PCM16 at target rate\r\n */\r\nexport function ttsToPlaybackFormat(\r\n audio: Float32Array,\r\n sourceRate: number = 24000,\r\n targetRate: number = 16000,\r\n): Uint8Array {\r\n const resampled = resampleLinear(audio, sourceRate, targetRate);\r\n return float32ToPcm16(resampled);\r\n}\r\n","/**\r\n * Shared audio utility functions\r\n *\r\n * @module audio\r\n */\r\n\r\n/**\r\n * Safely convert an ArrayBuffer of PCM16 bytes to Float32 samples.\r\n * Handles odd-length buffers by truncating to the nearest even byte boundary.\r\n */\r\nexport function pcm16ToFloat32(buffer: ArrayBuffer): Float32Array {\r\n // Int16Array requires even byte length — truncate if odd\r\n const byteLen = buffer.byteLength & ~1;\r\n const int16 = byteLen === buffer.byteLength\r\n ? new Int16Array(buffer)\r\n : new Int16Array(buffer, 0, byteLen / 2);\r\n const float32 = new Float32Array(int16.length);\r\n for (let i = 0; i < int16.length; i++) {\r\n float32[i] = int16[i] / 32768;\r\n }\r\n return float32;\r\n}\r\n\r\n/**\r\n * Convert Int16Array samples to Float32Array.\r\n * Each sample is divided by 32768 to normalize to [-1, 1] range.\r\n */\r\nexport function int16ToFloat32(int16: Int16Array): Float32Array {\r\n const float32 = new Float32Array(int16.length);\r\n for (let i = 0; i < int16.length; i++) {\r\n float32[i] = int16[i] / 32768;\r\n }\r\n return float32;\r\n}\r\n","/**\r\n * Microphone capture - renderer-agnostic audio input\r\n *\r\n * Captures audio from the microphone and emits PCM chunks.\r\n * Works in any JavaScript environment with Web Audio API.\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { EventEmitter, type OmoteEvents } from '../events';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\n\r\nconst logger = createLogger('MicrophoneCapture');\r\n\r\nexport interface MicrophoneCaptureConfig {\r\n /** Target sample rate (default: 16000 for speech processing) */\r\n sampleRate?: number;\r\n /** Chunk size in samples (default: 1600 = 100ms at 16kHz) */\r\n chunkSize?: number;\r\n}\r\n\r\nexport class MicrophoneCapture {\r\n private config: Required<MicrophoneCaptureConfig>;\r\n private stream: MediaStream | null = null;\r\n private context: AudioContext | null = null;\r\n private processor: ScriptProcessorNode | null = null;\r\n private buffer: Float32Array = new Float32Array(0);\r\n private _isRecording = false;\r\n private _loggedFirstChunk = false;\r\n /** Actual AudioContext sample rate (may differ from target on Firefox) */\r\n private _nativeSampleRate = 0;\r\n\r\n constructor(\r\n private events: EventEmitter<OmoteEvents>,\r\n config: MicrophoneCaptureConfig = {}\r\n ) {\r\n this.config = {\r\n sampleRate: config.sampleRate ?? 16000,\r\n chunkSize: config.chunkSize ?? 1600,\r\n };\r\n }\r\n\r\n get isRecording(): boolean {\r\n return this._isRecording;\r\n }\r\n\r\n get isSupported(): boolean {\r\n return typeof navigator !== 'undefined' && !!navigator.mediaDevices?.getUserMedia;\r\n }\r\n\r\n async start(): Promise<void> {\r\n if (!this.isSupported) {\r\n logger.error('Microphone not supported in this browser', {\r\n code: 'MICROPHONE_NOT_SUPPORTED',\r\n });\r\n this.events.emit('error', {\r\n code: 'MICROPHONE_NOT_SUPPORTED',\r\n message: 'Microphone not supported in this browser',\r\n });\r\n return;\r\n }\r\n\r\n if (this._isRecording) return;\r\n\r\n try {\r\n this.stream = await navigator.mediaDevices.getUserMedia({\r\n audio: {\r\n sampleRate: { ideal: this.config.sampleRate },\r\n channelCount: 1,\r\n echoCancellation: true,\r\n noiseSuppression: true,\r\n autoGainControl: true,\r\n },\r\n });\r\n\r\n // Create AudioContext and connect mic stream.\r\n // Firefox throws NotSupportedError when connecting a MediaStreamSource\r\n // to an AudioContext with a different sample rate than the stream's native\r\n // rate. Fall back to native rate + JS resampling when this happens.\r\n this.context = new AudioContext({ sampleRate: this.config.sampleRate });\r\n\r\n // Resume AudioContext if suspended (browser autoplay policy)\r\n if (this.context.state === 'suspended') {\r\n logger.debug('Resuming suspended AudioContext (autoplay policy)');\r\n await this.context.resume();\r\n }\r\n\r\n let source: MediaStreamAudioSourceNode;\r\n try {\r\n source = this.context.createMediaStreamSource(this.stream);\r\n this._nativeSampleRate = this.context.sampleRate;\r\n } catch (sourceErr) {\r\n // Firefox: \"Connecting AudioNodes from AudioContexts with different\r\n // sample-rate is currently not supported\"\r\n logger.warn('Cannot connect stream at target rate, falling back to native rate', {\r\n targetRate: this.config.sampleRate,\r\n error: (sourceErr as Error).message,\r\n });\r\n await this.context.close();\r\n this.context = new AudioContext(); // native rate (typically 48kHz)\r\n if (this.context.state === 'suspended') {\r\n logger.debug('Resuming suspended AudioContext after native rate fallback');\r\n await this.context.resume();\r\n }\r\n source = this.context.createMediaStreamSource(this.stream);\r\n this._nativeSampleRate = this.context.sampleRate;\r\n logger.debug('Using native rate with JS resampling', {\r\n nativeRate: this._nativeSampleRate,\r\n targetRate: this.config.sampleRate,\r\n });\r\n }\r\n\r\n // Use ScriptProcessor for broad compatibility\r\n this.processor = this.context.createScriptProcessor(4096, 1, 1);\r\n\r\n this.processor.onaudioprocess = (e) => {\r\n const raw = e.inputBuffer.getChannelData(0);\r\n\r\n // Resample if AudioContext is running at a different rate than target\r\n const input = this._nativeSampleRate !== this.config.sampleRate\r\n ? this.resample(raw, this._nativeSampleRate, this.config.sampleRate)\r\n : raw;\r\n\r\n // Calculate audio level\r\n let rms = 0;\r\n let peak = 0;\r\n for (let i = 0; i < input.length; i++) {\r\n const abs = Math.abs(input[i]);\r\n rms += input[i] * input[i];\r\n if (abs > peak) peak = abs;\r\n }\r\n rms = Math.sqrt(rms / input.length);\r\n\r\n this.events.emit('audio.level', { rms, peak });\r\n\r\n // Accumulate samples\r\n const newBuffer = new Float32Array(this.buffer.length + input.length);\r\n newBuffer.set(this.buffer);\r\n newBuffer.set(input, this.buffer.length);\r\n this.buffer = newBuffer;\r\n\r\n // Emit chunks\r\n let chunkCount = 0;\r\n while (this.buffer.length >= this.config.chunkSize) {\r\n const chunk = this.buffer.slice(0, this.config.chunkSize);\r\n this.buffer = this.buffer.slice(this.config.chunkSize);\r\n\r\n const pcm = this.floatToPCM16(chunk);\r\n this.events.emit('audio.chunk', {\r\n pcm,\r\n timestamp: getClock().now(),\r\n });\r\n chunkCount++;\r\n }\r\n // Log first emission for debugging\r\n if (chunkCount > 0 && !this._loggedFirstChunk) {\r\n logger.debug('First audio chunks emitted', { chunkCount });\r\n this._loggedFirstChunk = true;\r\n }\r\n };\r\n\r\n source.connect(this.processor);\r\n this.processor.connect(this.context.destination);\r\n\r\n this._isRecording = true;\r\n logger.info('Started recording', {\r\n contextState: this.context.state,\r\n sampleRate: this.config.sampleRate,\r\n nativeSampleRate: this._nativeSampleRate,\r\n chunkSize: this.config.chunkSize,\r\n });\r\n } catch (err) {\r\n const domErr = err as DOMException;\r\n const isPermissionDenied = domErr.name === 'NotAllowedError' || domErr.name === 'PermissionDeniedError';\r\n const code = isPermissionDenied ? 'MICROPHONE_PERMISSION_DENIED' : 'MICROPHONE_ERROR';\r\n\r\n logger.error('Failed to start microphone', {\r\n code,\r\n errorName: domErr.name,\r\n message: domErr.message,\r\n });\r\n\r\n this.events.emit('error', {\r\n code: 'MICROPHONE_ERROR',\r\n message: (err as Error).message,\r\n details: err,\r\n });\r\n }\r\n }\r\n\r\n stop(): void {\r\n if (this.processor) {\r\n this.processor.disconnect();\r\n this.processor = null;\r\n }\r\n\r\n if (this.context) {\r\n this.context.close();\r\n this.context = null;\r\n }\r\n\r\n if (this.stream) {\r\n this.stream.getTracks().forEach((t) => t.stop());\r\n this.stream = null;\r\n }\r\n\r\n this.buffer = new Float32Array(0);\r\n this._isRecording = false;\r\n logger.info('Stopped recording');\r\n }\r\n\r\n /**\r\n * Resample audio using linear interpolation.\r\n * Used when the AudioContext runs at the device's native rate (e.g. 48kHz)\r\n * and we need to downsample to the target rate (e.g. 16kHz).\r\n */\r\n private resample(input: Float32Array, fromRate: number, toRate: number): Float32Array {\r\n if (fromRate === toRate) return input;\r\n const ratio = fromRate / toRate;\r\n const outputLength = Math.floor(input.length / ratio);\r\n const output = new Float32Array(outputLength);\r\n for (let i = 0; i < outputLength; i++) {\r\n const srcIdx = i * ratio;\r\n const lo = Math.floor(srcIdx);\r\n const hi = Math.min(lo + 1, input.length - 1);\r\n const frac = srcIdx - lo;\r\n output[i] = input[lo] * (1 - frac) + input[hi] * frac;\r\n }\r\n return output;\r\n }\r\n\r\n private floatToPCM16(float32: Float32Array): Int16Array {\r\n const pcm = new Int16Array(float32.length);\r\n for (let i = 0; i < float32.length; i++) {\r\n const s = Math.max(-1, Math.min(1, float32[i]));\r\n pcm[i] = s < 0 ? s * 0x8000 : s * 0x7fff;\r\n }\r\n return pcm;\r\n }\r\n}\r\n","/**\n * Ring buffer for audio sample accumulation\n *\n * Efficiently accumulates audio samples and provides\n * contiguous buffers for inference without memory allocation churn.\n *\n * @category Audio\n */\n\nexport class RingBuffer {\n private buffer: Float32Array;\n private writeIndex = 0;\n private isFull = false;\n\n constructor(private readonly size: number) {\n this.buffer = new Float32Array(size);\n }\n\n /**\n * Write samples to the ring buffer\n * Converts Int16Array PCM to Float32\n */\n write(pcm: Int16Array): void {\n for (let i = 0; i < pcm.length; i++) {\n this.buffer[this.writeIndex] = pcm[i] / 32768.0;\n this.writeIndex = (this.writeIndex + 1) % this.size;\n\n if (this.writeIndex === 0) {\n this.isFull = true;\n }\n }\n }\n\n /**\n * Write float samples directly\n */\n writeFloat(samples: Float32Array): void {\n for (let i = 0; i < samples.length; i++) {\n this.buffer[this.writeIndex] = samples[i];\n this.writeIndex = (this.writeIndex + 1) % this.size;\n\n if (this.writeIndex === 0) {\n this.isFull = true;\n }\n }\n }\n\n /**\n * Get a contiguous copy of the buffer contents in chronological order\n * Returns null if buffer isn't full yet\n */\n read(): Float32Array | null {\n if (!this.isFull) return null;\n\n const output = new Float32Array(this.size);\n\n // Copy from writeIndex to end (oldest samples)\n const firstPart = this.buffer.subarray(this.writeIndex);\n output.set(firstPart, 0);\n\n // Copy from 0 to writeIndex (newest samples)\n const secondPart = this.buffer.subarray(0, this.writeIndex);\n output.set(secondPart, firstPart.length);\n\n return output;\n }\n\n /**\n * Check if buffer has enough samples\n */\n get hasData(): boolean {\n return this.isFull;\n }\n\n /**\n * Get current fill level (0-1)\n */\n get fillLevel(): number {\n if (this.isFull) return 1;\n return this.writeIndex / this.size;\n }\n\n /**\n * Reset the buffer\n */\n reset(): void {\n this.buffer.fill(0);\n this.writeIndex = 0;\n this.isFull = false;\n }\n}\n","/**\r\n * AudioScheduler - Enterprise-grade Web Audio API scheduling\r\n *\r\n * Implements the lookahead scheduling pattern from Chris Wilson's\r\n * \"A Tale of Two Clocks\" - the authoritative guide on Web Audio timing.\r\n *\r\n * Key Features:\r\n * - Uses AudioContext.currentTime (hardware clock) for sample-accurate timing\r\n * - Pre-schedules audio chunks for gapless playback\r\n * - Tracks scheduled sources for cleanup\r\n * - Provides playback state monitoring\r\n *\r\n * @see https://web.dev/articles/audio-scheduling\r\n * @category Audio\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { ErrorCodes } from '../logging/ErrorCodes';\r\n\r\nconst logger = createLogger('AudioScheduler');\r\n\r\nexport interface AudioSchedulerOptions {\r\n /** Sample rate in Hz (default: 16000 for speech) */\r\n sampleRate?: number\r\n /** Number of audio channels (default: 1 for mono) */\r\n channels?: number\r\n /**\r\n * Initial lookahead delay in seconds before first audio plays.\r\n * Gives LAM inference time to compute blendshapes before audio starts.\r\n * Default: 0.05 (50ms) for WebGPU, increase to 0.3-0.5 for WASM on iOS.\r\n */\r\n initialLookaheadSec?: number\r\n /** Error callback for critical scheduling issues */\r\n onError?: (error: Error) => void\r\n}\r\n\r\nexport class AudioScheduler {\r\n private context: AudioContext | null = null\r\n private nextPlayTime = 0\r\n private scheduledSources: Array<{ source: AudioBufferSourceNode; gainNode: GainNode }> = []\r\n private isPlaying = false\r\n\r\n constructor(private readonly options: AudioSchedulerOptions = {}) {}\r\n\r\n /** Configured sample rate (default: 16000). */\r\n get sampleRate(): number {\r\n return this.options.sampleRate ?? 16000\r\n }\r\n\r\n /**\r\n * Initialize AudioContext with specified sample rate\r\n *\r\n * Note: This is now a no-op. AudioContext is created lazily on first schedule()\r\n * to avoid browser autoplay policy issues (requires user gesture).\r\n */\r\n async initialize(): Promise<void> {\r\n // No-op - context will be created lazily in ensureContext()\r\n logger.debug('Ready for lazy initialization')\r\n }\r\n\r\n /**\r\n * Eagerly create and warm up the AudioContext\r\n *\r\n * Call this when a playback session starts (e.g., when AI response begins).\r\n * The AudioContext needs time to initialize the audio hardware — on Windows\r\n * this can take 50-100ms. By warming up early (before audio data arrives),\r\n * the context is fully ready when schedule() is first called.\r\n *\r\n * Must be called after a user gesture (click/tap) for autoplay policy.\r\n */\r\n async warmup(): Promise<void> {\r\n await this.ensureContext()\r\n }\r\n\r\n /**\r\n * Ensure AudioContext is created and ready\r\n * Called lazily on first schedule() - requires user gesture\r\n */\r\n private async ensureContext(): Promise<AudioContext> {\r\n if (this.context && this.context.state !== 'closed') {\r\n return this.context\r\n }\r\n\r\n const sampleRate = this.options.sampleRate ?? 16000\r\n this.context = new AudioContext({ sampleRate })\r\n\r\n // Resume if suspended (browser autoplay policy)\r\n if (this.context.state === 'suspended') {\r\n await this.context.resume()\r\n logger.debug('AudioContext resumed from suspended state')\r\n }\r\n\r\n logger.info('AudioContext initialized', { sampleRate, state: this.context.state })\r\n return this.context\r\n }\r\n\r\n /**\r\n * Schedule an audio chunk for playback\r\n *\r\n * Uses Web Audio's hardware-accurate clock for sample-perfect timing.\r\n * Chunks are scheduled immediately, not when they should play - this\r\n * ensures gapless playback even if main thread stalls.\r\n *\r\n * @param audioData - Float32Array of audio samples\r\n * @returns Scheduled playback time in AudioContext seconds\r\n */\r\n async schedule(audioData: Float32Array): Promise<number> {\r\n // Lazy initialization (requires user gesture)\r\n const ctx = await this.ensureContext()\r\n const channels = this.options.channels ?? 1\r\n\r\n // Initialize playback timing on first chunk\r\n // Lookahead gives AudioContext time to initialize and LAM time to pre-compute.\r\n // 50ms default is fine for WebGPU; WASM on iOS needs 300-500ms.\r\n if (!this.isPlaying) {\r\n const lookahead = this.options.initialLookaheadSec ?? 0.05\r\n this.nextPlayTime = ctx.currentTime + lookahead\r\n this.isPlaying = true\r\n logger.debug('First chunk scheduled', { lookaheadSec: lookahead, currentTime: ctx.currentTime })\r\n }\r\n\r\n // Create audio buffer\r\n const audioBuffer = ctx.createBuffer(channels, audioData.length, ctx.sampleRate)\r\n audioBuffer.getChannelData(0).set(audioData)\r\n\r\n // Create gain node for fade control\r\n const gainNode = ctx.createGain()\r\n gainNode.gain.value = 1.0\r\n gainNode.connect(ctx.destination)\r\n\r\n // Create and schedule source\r\n const source = ctx.createBufferSource()\r\n source.buffer = audioBuffer\r\n source.connect(gainNode) // Route through gain node for fade control\r\n\r\n // Schedule at precise time for gapless playback\r\n const scheduleTime = this.nextPlayTime\r\n\r\n // Warn if playback has fallen behind (audio gap)\r\n if (scheduleTime < ctx.currentTime) {\r\n const gap = ctx.currentTime - scheduleTime\r\n const gapMs = gap * 1000\r\n if (gap > 0.5) {\r\n logger.error('Critical audio scheduling gap', {\r\n code: ErrorCodes.AUD_SCHEDULE_GAP,\r\n scheduleTime,\r\n currentTime: ctx.currentTime,\r\n gapMs: Math.round(gapMs),\r\n })\r\n this.options.onError?.(new Error(`Audio scheduling gap: ${gap.toFixed(3)}s`))\r\n } else {\r\n logger.warn('Audio gap detected', {\r\n scheduleTime,\r\n currentTime: ctx.currentTime,\r\n gapMs: Math.round(gapMs),\r\n })\r\n }\r\n }\r\n\r\n source.start(scheduleTime)\r\n\r\n // Track scheduled source with its gain node\r\n const entry = { source, gainNode }\r\n this.scheduledSources.push(entry)\r\n\r\n // Clean up when playback completes naturally to prevent unbounded growth\r\n source.onended = () => {\r\n const idx = this.scheduledSources.indexOf(entry)\r\n if (idx !== -1) {\r\n this.scheduledSources.splice(idx, 1)\r\n }\r\n }\r\n\r\n // Update next play time\r\n const duration = audioData.length / ctx.sampleRate\r\n this.nextPlayTime = scheduleTime + duration\r\n\r\n logger.trace('Chunk scheduled', {\r\n scheduleTime,\r\n durationSec: duration,\r\n samples: audioData.length,\r\n pendingSources: this.scheduledSources.length,\r\n })\r\n\r\n return scheduleTime\r\n }\r\n\r\n /**\r\n * Get current audio clock time\r\n *\r\n * This is the hardware-accurate time, NOT JavaScript time.\r\n * Use this for synchronizing visual animations to audio.\r\n *\r\n * @returns Current time in AudioContext seconds\r\n */\r\n getCurrentTime(): number {\r\n if (!this.context) return 0\r\n return this.context.currentTime\r\n }\r\n\r\n /**\r\n * Get scheduled playback end time\r\n */\r\n getPlaybackEndTime(): number {\r\n return this.nextPlayTime\r\n }\r\n\r\n /**\r\n * Check if all scheduled audio has finished playing\r\n */\r\n isComplete(): boolean {\r\n if (!this.context || !this.isPlaying) return false\r\n return this.context.currentTime >= this.nextPlayTime\r\n }\r\n\r\n /**\r\n * Cancel all scheduled audio with smooth fade-out\r\n *\r\n * Applies a linear fade-out to all playing sources and stops them gracefully.\r\n * Prevents audio clicks/pops by ramping gain to zero before stopping.\r\n *\r\n * @param fadeOutMs - Fade-out duration in milliseconds (default: 50ms)\r\n * @returns Promise that resolves when fade-out completes\r\n */\r\n async cancelAll(fadeOutMs: number = 50): Promise<void> {\r\n if (!this.context || this.scheduledSources.length === 0) {\r\n return\r\n }\r\n\r\n logger.debug('cancelAll', { fadeOutMs, pendingSources: this.scheduledSources.length })\r\n\r\n const ctx = this.context\r\n const currentTime = ctx.currentTime\r\n const fadeOutSec = fadeOutMs / 1000\r\n\r\n // Apply fade-out to all scheduled sources\r\n for (const { source, gainNode } of this.scheduledSources) {\r\n try {\r\n // Ramp gain from current value to zero\r\n gainNode.gain.setValueAtTime(gainNode.gain.value, currentTime)\r\n gainNode.gain.linearRampToValueAtTime(0.0, currentTime + fadeOutSec)\r\n\r\n // Stop source after fade completes\r\n source.stop(currentTime + fadeOutSec)\r\n } catch (err) {\r\n // Source may have already stopped naturally - ignore error\r\n }\r\n }\r\n\r\n // Clear tracking arrays\r\n this.scheduledSources = []\r\n this.isPlaying = false\r\n this.nextPlayTime = 0\r\n\r\n // Wait for fade-out to complete\r\n await new Promise(resolve => setTimeout(resolve, fadeOutMs))\r\n }\r\n\r\n /**\r\n * Reset scheduler state for new playback session\r\n * Stops any orphaned sources that weren't cleaned up by cancelAll()\r\n */\r\n reset(): void {\r\n logger.debug('reset', { pendingSources: this.scheduledSources.length })\r\n // Stop any still-playing sources before clearing\r\n if (this.context) {\r\n const now = this.context.currentTime\r\n for (const { source, gainNode } of this.scheduledSources) {\r\n try {\r\n gainNode.gain.setValueAtTime(0, now)\r\n source.stop(now)\r\n } catch {\r\n // Already stopped\r\n }\r\n }\r\n }\r\n this.nextPlayTime = 0\r\n this.isPlaying = false\r\n this.scheduledSources = []\r\n }\r\n\r\n /**\r\n * Cleanup resources\r\n */\r\n dispose(): void {\r\n logger.debug('dispose')\r\n this.reset() // sync — stops all sources immediately\r\n if (this.context) {\r\n this.context.close()\r\n this.context = null\r\n }\r\n this.scheduledSources = []\r\n this.isPlaying = false\r\n }\r\n}\r\n","/**\r\n * AudioChunkCoalescer - Combine small network chunks into optimal buffers\r\n *\r\n * Network streaming often delivers audio in small chunks (e.g., 32ms from TTS APIs).\r\n * Creating an AudioBufferSourceNode for each tiny chunk is inefficient and can cause\r\n * overhead from object creation/GC.\r\n *\r\n * This class implements a double-buffering pattern: accumulate small chunks in a\r\n * temporary buffer, then flush to playback queue when threshold is reached.\r\n *\r\n * Benefits:\r\n * - Reduces AudioBufferSourceNode overhead (fewer nodes = less GC pressure)\r\n * - Configurable buffer size for optimal playback chunk duration\r\n * - Maintains sample-accurate timing despite buffering\r\n *\r\n * Based on patterns from HLS.js and production streaming implementations.\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('AudioChunkCoalescer');\r\n\r\nexport interface AudioChunkCoalescerOptions {\r\n /**\r\n * Target duration in milliseconds for combined chunks\r\n * Default: 200ms (balances latency vs overhead)\r\n *\r\n * Smaller values = lower latency, more overhead\r\n * Larger values = higher latency, less overhead\r\n */\r\n targetDurationMs?: number\r\n\r\n /**\r\n * Sample rate in Hz\r\n * Default: 16000 (speech quality)\r\n */\r\n sampleRate?: number\r\n}\r\n\r\nexport class AudioChunkCoalescer {\r\n private tempBuffer: Uint8Array[] = []\r\n private readonly targetBytes: number\r\n\r\n constructor(private readonly options: AudioChunkCoalescerOptions = {}) {\r\n const targetMs = options.targetDurationMs ?? 200\r\n const sampleRate = options.sampleRate ?? 16000\r\n\r\n // Calculate target bytes: (duration_s) * (samples/s) * (2 bytes per Int16 sample)\r\n this.targetBytes = (targetMs / 1000) * sampleRate * 2\r\n\r\n logger.debug('Constructed', { sampleRate, targetDurationMs: targetMs, targetBytes: this.targetBytes })\r\n }\r\n\r\n /**\r\n * Add a chunk to the temporary buffer\r\n *\r\n * @param chunk - Uint8Array containing Int16 PCM audio\r\n * @returns Combined buffer if threshold reached, null otherwise\r\n */\r\n add(chunk: Uint8Array): ArrayBuffer | null {\r\n // Add to temporary buffer\r\n this.tempBuffer.push(chunk)\r\n\r\n // Calculate total bytes buffered\r\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\r\n\r\n // If we've reached the threshold, combine and return\r\n if (totalBytes >= this.targetBytes) {\r\n logger.debug('Threshold flush', { bufferSize: totalBytes, targetSize: this.targetBytes, chunks: this.tempBuffer.length })\r\n return this.flush()\r\n }\r\n\r\n return null\r\n }\r\n\r\n /**\r\n * Flush remaining buffered data\r\n *\r\n * Call this when the stream ends to ensure all audio is processed,\r\n * even if it doesn't reach the target threshold.\r\n *\r\n * @returns Combined buffer, or null if buffer is empty\r\n */\r\n flush(): ArrayBuffer | null {\r\n if (this.tempBuffer.length === 0) {\r\n return null\r\n }\r\n\r\n // Calculate total size\r\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\r\n\r\n logger.debug('Explicit flush', { bufferSize: totalBytes, chunks: this.tempBuffer.length })\r\n\r\n // Combine all chunks into single buffer\r\n const combined = new Uint8Array(totalBytes)\r\n let offset = 0\r\n for (const chunk of this.tempBuffer) {\r\n combined.set(chunk, offset)\r\n offset += chunk.length\r\n }\r\n\r\n // Clear temp buffer\r\n this.tempBuffer = []\r\n\r\n return combined.buffer\r\n }\r\n\r\n /**\r\n * Get current buffer fill level (0-1)\r\n */\r\n get fillLevel(): number {\r\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\r\n return Math.min(1, totalBytes / this.targetBytes)\r\n }\r\n\r\n /**\r\n * Get current buffered duration in milliseconds\r\n */\r\n getBufferedDurationMs(): number {\r\n const sampleRate = this.options.sampleRate ?? 16000\r\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\r\n const samples = totalBytes / 2 // Int16 = 2 bytes per sample\r\n return (samples / sampleRate) * 1000\r\n }\r\n\r\n /**\r\n * Get number of chunks currently buffered\r\n */\r\n get chunkCount(): number {\r\n return this.tempBuffer.length\r\n }\r\n\r\n /**\r\n * Reset the coalescer\r\n */\r\n reset(): void {\r\n this.tempBuffer = []\r\n }\r\n}\r\n","/**\n * BlendshapeSmoother — Per-channel critically damped spring for 52 ARKit blendshapes\n *\n * Eliminates frame gaps between inference batches by smoothly interpolating\n * blendshape weights using critically damped springs (the game industry standard).\n *\n * Each of the 52 blendshape channels has its own spring with position + velocity\n * state. When a new inference frame arrives, spring targets are updated. Between\n * frames, springs continue converging toward the last target — no frozen face.\n *\n * When inference stalls, `decayToNeutral()` sets all targets to 0, and the\n * springs smoothly close the mouth / relax the face over the halflife period.\n *\n * Math from Daniel Holden's \"Spring-It-On\" (Epic Games):\n * https://theorangeduck.com/page/spring-roll-call\n *\n * @category Inference\n *\n * @example Basic usage\n * ```typescript\n * const smoother = new BlendshapeSmoother({ halflife: 0.06 });\n *\n * // In frame loop (60fps):\n * smoother.setTarget(inferenceFrame); // when new frame arrives\n * const smoothed = smoother.update(1/60); // every render frame\n * applyToAvatar(smoothed);\n * ```\n */\n\nconst NUM_BLENDSHAPES = 52;\n\nexport interface BlendshapeSmootherConfig {\n /**\n * Spring halflife in seconds — time for the distance to the target\n * to reduce by half. Lower = snappier, higher = smoother.\n *\n * - 0.04s (40ms): Very snappy, slight jitter on fast transitions\n * - 0.06s (60ms): Sweet spot for lip sync (default)\n * - 0.10s (100ms): Very smooth, slight lag on fast consonants\n * - 0: Bypass mode — passes through raw target values (no smoothing)\n *\n * Default: 0.06\n */\n halflife?: number;\n}\n\nexport class BlendshapeSmoother {\n private readonly halflife: number;\n\n /** Current smoothed blendshape values */\n private values: Float32Array;\n /** Per-channel spring velocities */\n private velocities: Float32Array;\n /** Current spring targets (from latest inference frame) */\n private targets: Float32Array;\n /** Whether any target has been set */\n private _hasTarget = false;\n\n constructor(config?: BlendshapeSmootherConfig) {\n this.halflife = config?.halflife ?? 0.06;\n this.values = new Float32Array(NUM_BLENDSHAPES);\n this.velocities = new Float32Array(NUM_BLENDSHAPES);\n this.targets = new Float32Array(NUM_BLENDSHAPES);\n }\n\n /** Whether a target frame has been set (false until first setTarget call) */\n get hasTarget(): boolean {\n return this._hasTarget;\n }\n\n /**\n * Set new target frame from inference output.\n * Springs will converge toward these values on subsequent update() calls.\n */\n setTarget(frame: Float32Array): void {\n this.targets.set(frame);\n this._hasTarget = true;\n }\n\n /**\n * Snap current position to a frame without triggering spring physics.\n * Zeroes velocities so the spring starts from rest at this position.\n *\n * Used by A2EProcessor to seed the smoother when entering gap decay —\n * positions the spring at the last real inference frame before decaying\n * toward neutral.\n */\n setPosition(frame: Float32Array): void {\n this.values.set(frame);\n this.velocities.fill(0);\n this._hasTarget = true;\n }\n\n /**\n * Advance all 52 springs by `dt` seconds and return the smoothed frame.\n *\n * Call this every render frame (e.g., inside requestAnimationFrame).\n * Returns the internal values buffer — do NOT mutate the returned array.\n *\n * @param dt - Time step in seconds (e.g., 1/60 for 60fps)\n * @returns Smoothed blendshape values (Float32Array of 52)\n */\n update(dt: number): Float32Array {\n if (!this._hasTarget) {\n return this.values;\n }\n\n // Bypass mode: no smoothing, snap to target\n if (this.halflife <= 0) {\n this.values.set(this.targets);\n this.velocities.fill(0);\n return this.values;\n }\n\n // Critically damped spring, exact solution per channel\n // From Daniel Holden's Spring-It-On (Epic Games):\n // https://theorangeduck.com/page/spring-roll-call\n const damping = Math.LN2 / this.halflife;\n const eydt = Math.exp(-damping * dt);\n\n for (let i = 0; i < NUM_BLENDSHAPES; i++) {\n const j0 = this.values[i] - this.targets[i];\n const j1 = this.velocities[i] + j0 * damping;\n\n this.values[i] = eydt * (j0 + j1 * dt) + this.targets[i];\n this.velocities[i] = eydt * (this.velocities[i] - j1 * damping * dt);\n\n // Clamp to valid blendshape range\n this.values[i] = Math.max(0, Math.min(1, this.values[i]));\n }\n\n return this.values;\n }\n\n /**\n * Decay all spring targets to neutral (0).\n *\n * Call when inference stalls (no new frames for threshold duration).\n * The springs will smoothly close the mouth / relax the face over\n * the halflife period rather than freezing.\n */\n decayToNeutral(): void {\n this.targets.fill(0);\n }\n\n /**\n * Reset all state (values, velocities, targets).\n * Call when starting a new playback session.\n */\n reset(): void {\n this.values.fill(0);\n this.velocities.fill(0);\n this.targets.fill(0);\n this._hasTarget = false;\n }\n}\n","/**\r\n * A2EProcessor — Engine-agnostic audio-to-expression processor\r\n *\r\n * The core inference primitive: audio samples in → blendshape frames out.\r\n * No mic capture, no audio playback, no Web Audio API.\r\n *\r\n * This is what Unity/Unreal/Godot/any engine would use directly.\r\n * Web-specific concerns (mic, AudioContext, scheduling) live in the\r\n * orchestrator and pipeline layers above.\r\n *\r\n * Two output modes:\r\n * - **Pull mode**: `pushAudio(samples, timestamp)` + `getFrameForTime(t)`\r\n * For TTS playback where frames are synced to AudioContext clock.\r\n * - **Push mode**: `pushAudio(samples)` + `startDrip()` + `latestFrame`\r\n * For live mic / game loop where frames are consumed at ~30fps.\r\n *\r\n * @category Inference\r\n *\r\n * @example Pull mode (TTS playback)\r\n * ```typescript\r\n * const processor = new A2EProcessor({ backend: a2e });\r\n * processor.pushAudio(samples, audioContext.currentTime + delay);\r\n * const frame = processor.getFrameForTime(audioContext.currentTime);\r\n * ```\r\n *\r\n * @example Push mode (live mic)\r\n * ```typescript\r\n * const processor = new A2EProcessor({\r\n * backend: a2e,\r\n * onFrame: (frame) => applyToAvatar(frame),\r\n * });\r\n * processor.startDrip();\r\n * processor.pushAudio(micSamples); // no timestamp → drip mode\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { ErrorCodes } from '../logging/ErrorCodes';\r\nimport type { A2EBackend } from './A2EBackend';\r\nimport { BlendshapeSmoother } from './BlendshapeSmoother';\r\n\r\nconst logger = createLogger('A2EProcessor');\r\n\r\nexport interface A2EProcessorConfig {\r\n /** Inference backend */\r\n backend: A2EBackend;\r\n /** Sample rate (default: 16000) */\r\n sampleRate?: number;\r\n /** Samples per inference chunk (default: 16000 = 1s) */\r\n chunkSize?: number;\r\n /**\r\n * Identity/style index for the A2E model (default: 0).\r\n *\r\n * The LAM model uses a one-hot identity vector (12 classes, indices 0-11) as\r\n * style conditioning alongside audio features. Different indices produce\r\n * different expression intensity across face regions (brows, eyes, cheeks).\r\n */\r\n identityIndex?: number;\r\n /** Callback fired with each blendshape frame (push mode) */\r\n onFrame?: (frame: Float32Array) => void;\r\n /** Error callback */\r\n onError?: (error: Error) => void;\r\n}\r\n\r\ninterface TimestampedFrame {\r\n frame: Float32Array;\r\n timestamp: number;\r\n}\r\n\r\nconst FRAME_RATE = 30;\r\nconst DRIP_INTERVAL_MS = 33; // ~30fps\r\n\r\n// Frame hold + spring decay: bridges gaps between inference chunks during cloud TTS streaming.\r\n// Cloud TTS sentence gaps are typically 200-500ms. A 400ms hold bridges virtually all\r\n// gaps without visible decay. After hold expires, spring decays to neutral (~0.8s to threshold).\r\n// Combined hold+decay ~1.2s total. End-of-speech handled by PlaybackPipeline's neutral transition.\r\nconst HOLD_DURATION_MS = 400; // hold last frame 400ms before decay starts\r\nconst GAP_DECAY_HALFLIFE_S = 0.08; // spring halflife for gap decay (~0.8s to null threshold)\r\n\r\nexport class A2EProcessor {\r\n private static readonly MAX_PENDING_CHUNKS = 10;\r\n\r\n private readonly backend: A2EBackend;\r\n private readonly sampleRate: number;\r\n private readonly chunkSize: number;\r\n private readonly identityIndex: number;\r\n private readonly onFrame?: (frame: Float32Array) => void;\r\n private readonly onError?: (error: Error) => void;\r\n\r\n // Audio buffer accumulation (pre-allocated, zero-alloc append)\r\n private bufferCapacity: number;\r\n private buffer: Float32Array;\r\n private writeOffset = 0;\r\n private bufferStartTime = 0;\r\n\r\n // Frame queues (timestamped for pull mode, plain for drip mode)\r\n private timestampedQueue: TimestampedFrame[] = [];\r\n private plainQueue: Float32Array[] = [];\r\n\r\n // Push mode state\r\n private _latestFrame: Float32Array | null = null;\r\n private dripInterval: ReturnType<typeof setInterval> | null = null;\r\n\r\n // Last-frame-hold for pull mode (prevents avatar freezing between frames)\r\n private lastPulledFrame: Float32Array | null = null;\r\n private lastDequeuedTime = 0;\r\n private decayBuffer: Float32Array | null = null;\r\n\r\n // Gap-only spring decay (replaces quadratic decay)\r\n private smoother: BlendshapeSmoother;\r\n private gapDecayStarted = false;\r\n private lastSmootherUpdate = 0;\r\n\r\n // Inference serialization\r\n private inferenceRunning = false;\r\n private pendingChunks: Array<{ chunk: Float32Array; timestamp?: number; actualSamples?: number }> = [];\r\n\r\n // Diagnostic: track getFrameForTime calls\r\n private getFrameCallCount = 0;\r\n\r\n private disposed = false;\r\n\r\n constructor(config: A2EProcessorConfig) {\r\n this.backend = config.backend;\r\n this.sampleRate = config.sampleRate ?? 16000;\r\n this.chunkSize = config.chunkSize ?? config.backend.chunkSize ?? 16000;\r\n this.identityIndex = config.identityIndex ?? 0;\r\n this.onFrame = config.onFrame;\r\n this.onError = config.onError;\r\n\r\n // Pre-allocate audio buffer (2x chunkSize avoids reallocation in normal use)\r\n this.bufferCapacity = this.chunkSize * 2;\r\n this.buffer = new Float32Array(this.bufferCapacity);\r\n\r\n // Spring smoother for gap decay only (not used during active playback)\r\n this.smoother = new BlendshapeSmoother({ halflife: GAP_DECAY_HALFLIFE_S });\r\n }\r\n\r\n // ═══════════════════════════════════════════════════════════════════════\r\n // Audio Input\r\n // ═══════════════════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Push audio samples for inference (any source: mic, TTS, file).\r\n *\r\n * - With `timestamp`: frames stored with timestamps (pull mode)\r\n * - Without `timestamp`: frames stored in plain queue (drip/push mode)\r\n *\r\n * Fire-and-forget: returns immediately, inference runs async.\r\n */\r\n pushAudio(samples: Float32Array, timestamp?: number): void {\r\n if (this.disposed) return;\r\n\r\n // Track buffer start time when empty (for timestamped mode)\r\n if (this.writeOffset === 0 && timestamp !== undefined) {\r\n this.bufferStartTime = timestamp;\r\n }\r\n\r\n // Grow capacity if needed (rare — only if push exceeds remaining space)\r\n if (this.writeOffset + samples.length > this.bufferCapacity) {\r\n this.bufferCapacity = (this.writeOffset + samples.length) * 2;\r\n const grown = new Float32Array(this.bufferCapacity);\r\n grown.set(this.buffer.subarray(0, this.writeOffset));\r\n this.buffer = grown;\r\n }\r\n\r\n // Append samples (O(1) — no copy of existing data)\r\n this.buffer.set(samples, this.writeOffset);\r\n this.writeOffset += samples.length;\r\n\r\n logger.debug('pushAudio', {\r\n samplesIn: samples.length,\r\n writeOffset: this.writeOffset,\r\n chunkSize: this.chunkSize,\r\n willExtract: this.writeOffset >= this.chunkSize,\r\n inferenceRunning: this.inferenceRunning,\r\n pendingChunks: this.pendingChunks.length,\r\n queuedFrames: this.timestampedQueue.length + this.plainQueue.length,\r\n });\r\n\r\n // Slice off complete chunks and queue them for inference\r\n while (this.writeOffset >= this.chunkSize) {\r\n const chunk = this.buffer.slice(0, this.chunkSize);\r\n // Shift remainder to front (in-place memmove, zero-alloc)\r\n this.buffer.copyWithin(0, this.chunkSize, this.writeOffset);\r\n this.writeOffset -= this.chunkSize;\r\n\r\n const chunkTimestamp = timestamp !== undefined ? this.bufferStartTime : undefined;\r\n this.pendingChunks.push({ chunk, timestamp: chunkTimestamp });\r\n\r\n while (this.pendingChunks.length > A2EProcessor.MAX_PENDING_CHUNKS) {\r\n this.pendingChunks.shift();\r\n logger.warn('A2E backpressure: dropped oldest pending chunk', { pending: this.pendingChunks.length });\r\n }\r\n\r\n logger.info('Chunk queued for inference', {\r\n chunkSize: chunk.length,\r\n chunkTimestamp,\r\n pendingChunks: this.pendingChunks.length,\r\n remainderOffset: this.writeOffset,\r\n });\r\n\r\n // Advance buffer start time for next chunk\r\n if (timestamp !== undefined) {\r\n this.bufferStartTime += this.chunkSize / this.sampleRate;\r\n }\r\n }\r\n\r\n // Warn on backpressure (surfaces iOS burst arrivals without changing behavior)\r\n if (this.pendingChunks.length > 3) {\r\n logger.warn('A2E inference backpressure', {\r\n pendingChunks: this.pendingChunks.length,\r\n backend: this.backend.backend,\r\n });\r\n }\r\n\r\n // Kick off inference if not already running\r\n this.drainPendingChunks();\r\n }\r\n\r\n /**\r\n * Flush remaining buffered audio (pads to chunkSize).\r\n * Call at end of stream to process final partial chunk.\r\n *\r\n * Routes through the serialized pendingChunks pipeline to maintain\r\n * correct frame ordering. Without this, flush() could push frames\r\n * with the latest timestamp to the queue before drainPendingChunks()\r\n * finishes pushing frames with earlier timestamps — causing\r\n * getFrameForTime() to see out-of-order timestamps and stall.\r\n */\r\n async flush(): Promise<void> {\r\n if (this.disposed || this.writeOffset === 0) return;\r\n\r\n // Capture actual sample count BEFORE zeroing writeOffset.\r\n // flush() pads to chunkSize with zeros, but only actualSamples\r\n // contain real audio. drainPendingChunks uses this to limit\r\n // queued frames to the real audio duration (not the padded 1s).\r\n const actualSamples = this.writeOffset;\r\n\r\n // Pad to chunkSize\r\n const padded = new Float32Array(this.chunkSize);\r\n padded.set(this.buffer.subarray(0, actualSamples), 0);\r\n\r\n const chunkTimestamp = this.bufferStartTime > 0 ? this.bufferStartTime : undefined;\r\n\r\n logger.info('flush: routing through drain pipeline', {\r\n actualSamples,\r\n chunkTimestamp: chunkTimestamp?.toFixed(3),\r\n pendingChunks: this.pendingChunks.length,\r\n inferenceRunning: this.inferenceRunning,\r\n });\r\n\r\n this.writeOffset = 0;\r\n this.bufferStartTime = 0;\r\n\r\n // Route through the serialized pending chunk pipeline.\r\n // This ensures the flush chunk is processed AFTER all earlier chunks,\r\n // maintaining correct timestamp ordering in the frame queue.\r\n this.pendingChunks.push({ chunk: padded, timestamp: chunkTimestamp, actualSamples });\r\n this.drainPendingChunks();\r\n }\r\n\r\n /**\r\n * Reset buffer and frame queues\r\n */\r\n reset(): void {\r\n this.writeOffset = 0;\r\n this.bufferStartTime = 0;\r\n this.timestampedQueue = [];\r\n this.plainQueue = [];\r\n this._latestFrame = null;\r\n this.lastPulledFrame = null;\r\n this.lastDequeuedTime = 0;\r\n this.pendingChunks = [];\r\n // Must reset so drainPendingChunks() processes new chunks after stop→start\r\n this.inferenceRunning = false;\r\n this.getFrameCallCount = 0;\r\n // Reset spring decay state\r\n this.smoother.reset();\r\n this.gapDecayStarted = false;\r\n this.lastSmootherUpdate = 0;\r\n }\r\n\r\n // ═══════════════════════════════════════════════════════════════════════\r\n // Frame Output — Pull Mode (TTS playback)\r\n // ═══════════════════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Get frame synced to external clock (e.g. AudioContext.currentTime).\r\n *\r\n * Discards frames that are too old, returns the current frame,\r\n * or holds last frame as fallback to prevent avatar freezing.\r\n *\r\n * @param currentTime - Current playback time (seconds)\r\n * @returns Blendshape frame, or null if no frames yet\r\n */\r\n getFrameForTime(currentTime: number): Float32Array | null {\r\n this.getFrameCallCount++;\r\n\r\n // Dynamic discard window based on backend\r\n const discardWindow = this.backend.backend === 'wasm' ? 1.5 : 0.5;\r\n\r\n // Remove frames that are too old\r\n let discardCount = 0;\r\n while (\r\n this.timestampedQueue.length > 0 &&\r\n this.timestampedQueue[0].timestamp < currentTime - discardWindow\r\n ) {\r\n this.timestampedQueue.shift();\r\n discardCount++;\r\n }\r\n\r\n if (discardCount > 0) {\r\n logger.warn('getFrameForTime DISCARDED stale frames', {\r\n discardCount,\r\n currentTime: currentTime.toFixed(3),\r\n discardWindow,\r\n remainingFrames: this.timestampedQueue.length,\r\n nextFrameTs: this.timestampedQueue.length > 0\r\n ? this.timestampedQueue[0].timestamp.toFixed(3) : 'none',\r\n });\r\n }\r\n\r\n // Return frame that should be playing now — raw passthrough, no smoother\r\n if (\r\n this.timestampedQueue.length > 0 &&\r\n this.timestampedQueue[0].timestamp <= currentTime\r\n ) {\r\n const { frame } = this.timestampedQueue.shift()!;\r\n this.lastPulledFrame = frame;\r\n this.lastDequeuedTime = getClock().now();\r\n this.gapDecayStarted = false; // reset decay state on new dequeue\r\n return frame;\r\n }\r\n\r\n // Diagnostic: frames queued but AudioContext time hasn't reached them yet\r\n // This is normal during playback startup (audioDelayMs gap) — not an error\r\n if (this.timestampedQueue.length > 0 && this.getFrameCallCount % 60 === 0) {\r\n logger.debug('getFrameForTime: waiting for playback time to reach queued frames', {\r\n queueLen: this.timestampedQueue.length,\r\n frontTimestamp: this.timestampedQueue[0].timestamp.toFixed(4),\r\n currentTime: currentTime.toFixed(4),\r\n delta: (this.timestampedQueue[0].timestamp - currentTime).toFixed(4),\r\n });\r\n }\r\n\r\n // Last-frame-hold with spring decay toward neutral.\r\n // Prevents mouth-stuck between sentences: TTS chunk boundaries don't align\r\n // with phoneme boundaries, so the last frame often has mouth open.\r\n if (this.lastPulledFrame) {\r\n const now = getClock().now();\r\n const elapsed = now - this.lastDequeuedTime;\r\n\r\n // Normal hold — bridges inter-frame gaps during active playback\r\n if (elapsed < HOLD_DURATION_MS) {\r\n return this.lastPulledFrame;\r\n }\r\n\r\n // Spring decay toward neutral (zero blendshapes)\r\n if (!this.gapDecayStarted) {\r\n this.smoother.setPosition(this.lastPulledFrame);\r\n this.smoother.decayToNeutral();\r\n this.gapDecayStarted = true;\r\n this.lastSmootherUpdate = now;\r\n }\r\n\r\n const dt = Math.min((now - this.lastSmootherUpdate) / 1000, 0.1);\r\n this.lastSmootherUpdate = now;\r\n\r\n const smoothed = this.smoother.update(dt);\r\n\r\n // Check if fully decayed (all values below threshold)\r\n let maxVal = 0;\r\n for (let i = 0; i < 52; i++) {\r\n if (smoothed[i] > maxVal) maxVal = smoothed[i];\r\n }\r\n if (maxVal < 0.001) {\r\n this.lastPulledFrame = null;\r\n return null;\r\n }\r\n\r\n // Copy into reusable decay buffer\r\n if (!this.decayBuffer) this.decayBuffer = new Float32Array(52);\r\n this.decayBuffer.set(smoothed);\r\n return this.decayBuffer;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n // ═══════════════════════════════════════════════════════════════════════\r\n // Frame Output — Push Mode (live mic, game loop)\r\n // ═══════════════════════════════════════════════════════════════════════\r\n\r\n /** Latest frame from drip-feed (live mic, game loop) */\r\n get latestFrame(): Float32Array | null {\r\n return this._latestFrame;\r\n }\r\n\r\n /** Start 30fps drip-feed timer (push mode) */\r\n startDrip(): void {\r\n if (this.dripInterval) return;\r\n this.dripInterval = setInterval(() => {\r\n const frame = this.plainQueue.shift();\r\n if (frame) {\r\n this._latestFrame = frame;\r\n this.onFrame?.(frame);\r\n }\r\n }, DRIP_INTERVAL_MS);\r\n }\r\n\r\n /** Stop drip-feed timer */\r\n stopDrip(): void {\r\n if (this.dripInterval) {\r\n clearInterval(this.dripInterval);\r\n this.dripInterval = null;\r\n }\r\n }\r\n\r\n // ═══════════════════════════════════════════════════════════════════════\r\n // State\r\n // ═══════════════════════════════════════════════════════════════════════\r\n\r\n /** Number of frames waiting in queue (both modes combined) */\r\n get queuedFrameCount(): number {\r\n return this.timestampedQueue.length + this.plainQueue.length;\r\n }\r\n\r\n /** Buffer fill level as fraction of chunkSize (0-1) */\r\n get fillLevel(): number {\r\n return Math.min(1, this.writeOffset / this.chunkSize);\r\n }\r\n\r\n /** Dispose resources */\r\n dispose(): void {\r\n if (this.disposed) return;\r\n logger.debug('Disposed');\r\n this.disposed = true;\r\n this.stopDrip();\r\n this.reset();\r\n }\r\n\r\n // ═══════════════════════════════════════════════════════════════════════\r\n // Private\r\n // ═══════════════════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Process pending chunks sequentially.\r\n * Fire-and-forget — called from pushAudio() without awaiting.\r\n */\r\n private drainPendingChunks(): void {\r\n if (this.inferenceRunning || this.pendingChunks.length === 0) {\r\n if (this.inferenceRunning && this.pendingChunks.length > 0) {\r\n logger.debug('drainPendingChunks skipped (inference running)', {\r\n pendingChunks: this.pendingChunks.length,\r\n });\r\n }\r\n return;\r\n }\r\n\r\n this.inferenceRunning = true;\r\n logger.info('drainPendingChunks starting', { pendingChunks: this.pendingChunks.length });\r\n\r\n const processNext = async () => {\r\n while (this.pendingChunks.length > 0 && !this.disposed) {\r\n const { chunk, timestamp, actualSamples } = this.pendingChunks.shift()!;\r\n\r\n try {\r\n const t0 = getClock().now();\r\n const result = await this.backend.infer(chunk, this.identityIndex);\r\n const inferMs = Math.round(getClock().now() - t0);\r\n\r\n // Only queue frames proportional to actual audio duration.\r\n // For flush chunks, actualSamples tracks real audio before zero-padding.\r\n // For normal chunks, actualSamples is undefined → use full chunk length.\r\n const effectiveSamples = actualSamples ?? chunk.length;\r\n const actualDuration = effectiveSamples / this.sampleRate;\r\n const actualFrameCount = Math.ceil(actualDuration * FRAME_RATE);\r\n const framesToQueue = Math.min(Math.max(1, actualFrameCount), result.blendshapes.length);\r\n\r\n logger.info('Inference complete', {\r\n inferMs,\r\n modelFrames: result.blendshapes.length,\r\n framesToQueue,\r\n timestamp,\r\n totalQueued: this.timestampedQueue.length + framesToQueue,\r\n remainingPending: this.pendingChunks.length,\r\n });\r\n\r\n for (let i = 0; i < framesToQueue; i++) {\r\n if (timestamp !== undefined) {\r\n this.timestampedQueue.push({\r\n frame: result.blendshapes[i],\r\n timestamp: timestamp + i / FRAME_RATE,\r\n });\r\n } else {\r\n this.plainQueue.push(result.blendshapes[i]);\r\n }\r\n }\r\n } catch (err) {\r\n this.handleError(err);\r\n }\r\n\r\n // Yield to main thread between batches to prevent blocking\r\n if (this.pendingChunks.length > 0) {\r\n await new Promise<void>(r => setTimeout(r, 0));\r\n }\r\n }\r\n };\r\n\r\n processNext()\r\n .catch(err => this.handleError(err))\r\n .finally(() => {\r\n this.inferenceRunning = false;\r\n // Check if more chunks arrived while we were processing\r\n if (this.pendingChunks.length > 0) {\r\n this.drainPendingChunks();\r\n }\r\n });\r\n }\r\n\r\n private handleError(err: unknown): void {\r\n const error = err instanceof Error ? err : new Error(String(err));\r\n const isOOM = typeof err === 'number' || (error.message && /out of memory|oom|alloc/i.test(error.message));\r\n const isTimeout = error.message?.includes('timed out');\r\n const code = isOOM ? ErrorCodes.INF_OOM\r\n : isTimeout ? ErrorCodes.INF_TIMEOUT\r\n : ErrorCodes.INF_LOAD_FAILED;\r\n logger.warn('A2EProcessor inference error', {\r\n error: error.message,\r\n code,\r\n });\r\n this.onError?.(error);\r\n }\r\n}\r\n","/**\r\n * Telemetry Types\r\n *\r\n * Configuration and type definitions for OpenTelemetry instrumentation.\r\n *\r\n * @category Telemetry\r\n */\r\n\r\n/**\r\n * Supported telemetry exporters\r\n */\r\nexport type TelemetryExporter = 'console' | 'otlp' | 'none';\r\n\r\n/**\r\n * Sampling configuration\r\n */\r\nexport interface SamplingConfig {\r\n /** Sampling ratio (0.0 - 1.0). Default: 1.0 (sample everything) */\r\n ratio?: number;\r\n /** Always sample errors regardless of ratio */\r\n alwaysSampleErrors?: boolean;\r\n}\r\n\r\n/**\r\n * OTLP exporter configuration\r\n */\r\nexport interface OTLPExporterConfig {\r\n /** OTLP endpoint URL (e.g., 'https://tempo.example.com/v1/traces') */\r\n endpoint: string;\r\n /** Optional headers for authentication */\r\n headers?: Record<string, string>;\r\n /** Request timeout in ms. Default: 10000 */\r\n timeoutMs?: number;\r\n}\r\n\r\n/**\r\n * Main telemetry configuration\r\n */\r\nexport interface TelemetryConfig {\r\n /** Enable/disable telemetry. Default: false */\r\n enabled?: boolean;\r\n /** Service name for spans. Default: 'omote-sdk' */\r\n serviceName?: string;\r\n /** Service version. Default: SDK version */\r\n serviceVersion?: string;\r\n /** Exporter type. Default: 'none' */\r\n exporter?: TelemetryExporter;\r\n /** OTLP exporter config (required if exporter is 'otlp') */\r\n exporterConfig?: OTLPExporterConfig;\r\n /** Sampling configuration */\r\n sampling?: SamplingConfig;\r\n /** Enable metrics collection. Default: true when telemetry enabled */\r\n metricsEnabled?: boolean;\r\n /** Metrics export interval in ms. Default: 60000 */\r\n metricsIntervalMs?: number;\r\n}\r\n\r\n/**\r\n * Span attributes for model operations\r\n */\r\nexport interface ModelSpanAttributes {\r\n /** Model URL or identifier */\r\n 'model.url'?: string;\r\n /** Model name (e.g., 'whisper', 'lam', 'silero-vad') */\r\n 'model.name'?: string;\r\n /** Inference backend used */\r\n 'model.backend'?: 'webgpu' | 'wasm';\r\n /** Whether model was loaded from cache */\r\n 'model.cached'?: boolean;\r\n /** Model size in bytes */\r\n 'model.size_bytes'?: number;\r\n}\r\n\r\n/**\r\n * Span attributes for inference operations\r\n */\r\nexport interface InferenceSpanAttributes extends ModelSpanAttributes {\r\n /** Number of input audio samples */\r\n 'inference.input_samples'?: number;\r\n /** Input duration in ms */\r\n 'inference.input_duration_ms'?: number;\r\n /** Number of output frames (for LAM) */\r\n 'inference.output_frames'?: number;\r\n /** Inference duration in ms */\r\n 'inference.duration_ms'?: number;\r\n /** Whether inference succeeded */\r\n 'inference.success'?: boolean;\r\n /** Error type if failed */\r\n 'inference.error_type'?: string;\r\n}\r\n\r\n/**\r\n * Span attributes for cache operations\r\n */\r\nexport interface CacheSpanAttributes {\r\n /** Cache key (URL) */\r\n 'cache.key'?: string;\r\n /** Whether it was a cache hit */\r\n 'cache.hit'?: boolean;\r\n /** Size of cached item in bytes */\r\n 'cache.size_bytes'?: number;\r\n /** Cache operation type */\r\n 'cache.operation'?: 'get' | 'set' | 'delete';\r\n}\r\n\r\n/**\r\n * Combined span attributes type\r\n */\r\nexport type SpanAttributes =\r\n | ModelSpanAttributes\r\n | InferenceSpanAttributes\r\n | CacheSpanAttributes\r\n | Record<string, string | number | boolean | undefined>;\r\n\r\n/**\r\n * Metric names used by the SDK\r\n */\r\nexport const MetricNames = {\r\n // --- Inference ---\r\n /** Histogram: Inference latency in ms */\r\n INFERENCE_LATENCY: 'omote.inference.latency',\r\n /** Histogram: Model load time in ms */\r\n MODEL_LOAD_TIME: 'omote.model.load_time',\r\n /** Counter: Total inference operations */\r\n INFERENCE_TOTAL: 'omote.inference.total',\r\n /** Counter: Total errors */\r\n ERRORS_TOTAL: 'omote.errors.total',\r\n /** Counter: Cache hits */\r\n CACHE_HITS: 'omote.cache.hits',\r\n /** Counter: Cache misses */\r\n CACHE_MISSES: 'omote.cache.misses',\r\n /** Counter: Cache stale (version/etag mismatch) */\r\n CACHE_STALE: 'omote.cache.stale',\r\n /** Counter: Cache quota warning (>90% used) */\r\n CACHE_QUOTA_WARNING: 'omote.cache.quota_warning',\r\n /** Counter: Cache eviction (LRU) */\r\n CACHE_EVICTION: 'omote.cache.eviction',\r\n\r\n // --- Pipeline ---\r\n /** Histogram: Voice turn latency (speech end → transcript ready, excludes playback) */\r\n VOICE_TURN_LATENCY: 'omote.voice.turn.latency',\r\n /** Histogram: ASR transcription latency in ms */\r\n VOICE_TRANSCRIPTION_LATENCY: 'omote.voice.transcription.latency',\r\n /** Histogram: Response handler latency in ms */\r\n VOICE_RESPONSE_LATENCY: 'omote.voice.response.latency',\r\n /** Counter: Total transcriptions */\r\n VOICE_TRANSCRIPTIONS: 'omote.voice.transcriptions',\r\n /** Counter: Total interruptions */\r\n VOICE_INTERRUPTIONS: 'omote.voice.interruptions',\r\n\r\n // --- Playback ---\r\n /** Histogram: PlaybackPipeline session duration in ms */\r\n PLAYBACK_SESSION_DURATION: 'omote.playback.session.duration',\r\n /** Histogram: Audio chunk processing latency in ms */\r\n PLAYBACK_CHUNK_LATENCY: 'omote.playback.chunk.latency',\r\n\r\n // --- TTS ---\r\n /** Histogram: TTSSpeaker.connect() latency in ms */\r\n TTS_CONNECT_LATENCY: 'omote.tts.connect.latency',\r\n /** Histogram: TTSSpeaker.speak() latency in ms */\r\n TTS_SPEAK_LATENCY: 'omote.tts.speak.latency',\r\n /** Counter: TTSSpeaker.stop() aborted speak calls */\r\n TTS_SPEAK_ABORTED: 'omote.tts.speak.aborted',\r\n\r\n // --- Mic ---\r\n /** Counter: MicLipSync sessions started */\r\n MIC_SESSIONS: 'omote.mic.sessions',\r\n\r\n // --- Frame budget ---\r\n /** Histogram: CharacterController.update() latency in µs */\r\n AVATAR_FRAME_LATENCY: 'omote.avatar.frame.latency_us',\r\n /** Histogram: FaceCompositor.compose() latency in µs */\r\n COMPOSITOR_COMPOSE_LATENCY: 'omote.compositor.compose.latency_us',\r\n /** Counter: Frames exceeding budget threshold */\r\n AVATAR_FRAME_DROPS: 'omote.avatar.frame.drops',\r\n} as const;\r\n\r\n/**\r\n * Centralized error type taxonomy for structured error reporting.\r\n */\r\nexport const ErrorTypes = {\r\n INFERENCE: 'inference_error',\r\n NETWORK: 'network_error',\r\n TIMEOUT: 'timeout',\r\n USER: 'user_error',\r\n RUNTIME: 'runtime_error',\r\n MEDIA: 'media_error',\r\n MODEL: 'model_error',\r\n} as const;\r\nexport type ErrorType = typeof ErrorTypes[keyof typeof ErrorTypes];\r\n\r\n/**\r\n * Histogram buckets for inference latency (ms)\r\n */\r\nexport const INFERENCE_LATENCY_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000];\r\n\r\n/**\r\n * Histogram buckets for model load time (ms)\r\n */\r\nexport const MODEL_LOAD_TIME_BUCKETS = [100, 500, 1000, 2500, 5000, 10000, 30000, 60000];\r\n","/**\r\n * Shared blendshape constants and utilities for lip sync inference\r\n *\r\n * Contains ARKIT_BLENDSHAPES (canonical 52-blendshape ordering), symmetrization,\r\n * and interpolation utilities used by A2EInference and all renderer adapters.\r\n *\r\n * This module is the single source of truth for blendshape ordering to\r\n * avoid circular dependencies between inference classes.\r\n *\r\n * @category Inference\r\n */\r\n\r\n/**\r\n * ARKit blendshape names in alphabetical order (52 total)\r\n * This is the canonical ordering used by all A2E models in the SDK.\r\n */\r\nexport const ARKIT_BLENDSHAPES = [\r\n 'browDownLeft', 'browDownRight', 'browInnerUp', 'browOuterUpLeft', 'browOuterUpRight',\r\n 'cheekPuff', 'cheekSquintLeft', 'cheekSquintRight',\r\n 'eyeBlinkLeft', 'eyeBlinkRight', 'eyeLookDownLeft', 'eyeLookDownRight',\r\n 'eyeLookInLeft', 'eyeLookInRight', 'eyeLookOutLeft', 'eyeLookOutRight',\r\n 'eyeLookUpLeft', 'eyeLookUpRight', 'eyeSquintLeft', 'eyeSquintRight',\r\n 'eyeWideLeft', 'eyeWideRight',\r\n 'jawForward', 'jawLeft', 'jawOpen', 'jawRight',\r\n 'mouthClose', 'mouthDimpleLeft', 'mouthDimpleRight', 'mouthFrownLeft', 'mouthFrownRight',\r\n 'mouthFunnel', 'mouthLeft', 'mouthLowerDownLeft', 'mouthLowerDownRight',\r\n 'mouthPressLeft', 'mouthPressRight', 'mouthPucker', 'mouthRight',\r\n 'mouthRollLower', 'mouthRollUpper', 'mouthShrugLower', 'mouthShrugUpper',\r\n 'mouthSmileLeft', 'mouthSmileRight', 'mouthStretchLeft', 'mouthStretchRight',\r\n 'mouthUpperUpLeft', 'mouthUpperUpRight',\r\n 'noseSneerLeft', 'noseSneerRight', 'tongueOut'\r\n] as const;\r\n\r\n/** @deprecated Use ARKIT_BLENDSHAPES instead */\r\nexport const LAM_BLENDSHAPES = ARKIT_BLENDSHAPES;\r\n\r\n/**\r\n * ARKit Left/Right symmetric pairs for blendshape symmetrization\r\n * From LAM official postprocessing (models/utils.py)\r\n */\r\nconst BLENDSHAPE_SYMMETRIC_PAIRS: [string, string][] = [\r\n ['jawLeft', 'jawRight'],\r\n ['mouthLeft', 'mouthRight'],\r\n ['mouthSmileLeft', 'mouthSmileRight'],\r\n ['mouthFrownLeft', 'mouthFrownRight'],\r\n ['mouthDimpleLeft', 'mouthDimpleRight'],\r\n ['mouthStretchLeft', 'mouthStretchRight'],\r\n ['mouthPressLeft', 'mouthPressRight'],\r\n ['mouthUpperUpLeft', 'mouthUpperUpRight'],\r\n ['mouthLowerDownLeft', 'mouthLowerDownRight'],\r\n ['noseSneerLeft', 'noseSneerRight'],\r\n ['cheekSquintLeft', 'cheekSquintRight'],\r\n ['browDownLeft', 'browDownRight'],\r\n ['browOuterUpLeft', 'browOuterUpRight'],\r\n ['eyeBlinkLeft', 'eyeBlinkRight'],\r\n ['eyeLookUpLeft', 'eyeLookUpRight'],\r\n ['eyeLookDownLeft', 'eyeLookDownRight'],\r\n ['eyeLookInLeft', 'eyeLookInRight'],\r\n ['eyeLookOutLeft', 'eyeLookOutRight'],\r\n ['eyeSquintLeft', 'eyeSquintRight'],\r\n ['eyeWideLeft', 'eyeWideRight'],\r\n];\r\n\r\n// Precompute index pairs for fast symmetrization\r\nconst SYMMETRIC_INDEX_PAIRS: [number, number][] = BLENDSHAPE_SYMMETRIC_PAIRS.map(([l, r]) => [\r\n ARKIT_BLENDSHAPES.indexOf(l as typeof ARKIT_BLENDSHAPES[number]),\r\n ARKIT_BLENDSHAPES.indexOf(r as typeof ARKIT_BLENDSHAPES[number]),\r\n]).filter(([l, r]) => l !== -1 && r !== -1) as [number, number][];\r\n\r\n/**\r\n * Symmetrize blendshapes by averaging left/right pairs\r\n * From LAM official postprocessing (models/utils.py)\r\n * This fixes asymmetric output from the raw model\r\n */\r\nexport function symmetrizeBlendshapes(frame: Float32Array): Float32Array {\r\n const result = new Float32Array(frame);\r\n for (const [lIdx, rIdx] of SYMMETRIC_INDEX_PAIRS) {\r\n const avg = (frame[lIdx] + frame[rIdx]) / 2;\r\n result[lIdx] = avg;\r\n result[rIdx] = avg;\r\n }\r\n return result;\r\n}\r\n\r\n/**\r\n * Linearly interpolate between two blendshape weight arrays.\r\n *\r\n * Pure math utility with zero renderer dependency — used by all renderer\r\n * adapters (@omote/three, @omote/babylon, @omote/r3f) for smooth frame\r\n * transitions.\r\n *\r\n * @param current - Current blendshape weights\r\n * @param target - Target blendshape weights\r\n * @param factor - Interpolation factor (0 = no change, 1 = snap to target). Default: 0.3\r\n * @returns Interpolated weights as number[]\r\n */\r\nexport function lerpBlendshapes(\r\n current: Float32Array | number[],\r\n target: Float32Array | number[],\r\n factor: number = 0.3,\r\n): number[] {\r\n const len = Math.max(current.length, target.length);\r\n const result = new Array(len);\r\n for (let i = 0; i < len; i++) {\r\n const c = current[i] ?? 0;\r\n const t = target[i] ?? 0;\r\n result[i] = c + (t - c) * factor;\r\n }\r\n return result;\r\n}\r\n","/**\n * ExpressionProfile - Per-character weight scaling for A2E blendshape output\n *\n * Maps blendshape groups (eyes, brows, jaw, mouth, cheeks, nose, tongue)\n * to weight scalers. Used by PlaybackPipeline, MicLipSync, and VoiceOrchestrator.\n *\n * @category Audio\n */\n\nimport { ARKIT_BLENDSHAPES } from '../inference/blendshapeUtils';\n\n// ---------------------------------------------------------------------------\n// ExpressionProfile types\n// ---------------------------------------------------------------------------\n\nexport type BlendshapeGroup = 'eyes' | 'brows' | 'jaw' | 'mouth' | 'cheeks' | 'nose' | 'tongue';\n\n/**\n * Per-character weight scaling for A2E blendshape output.\n *\n * Group scalers multiply all blendshapes in that group (default 1.0).\n * Per-blendshape overrides take priority over group scalers.\n * Final values are clamped to [0, 1].\n */\nexport interface ExpressionProfile {\n /** eyeBlink*, eyeLook*, eyeSquint*, eyeWide* (14 blendshapes) */\n eyes?: number;\n /** browDown*, browInnerUp, browOuterUp* (5 blendshapes) */\n brows?: number;\n /** jawForward, jawLeft, jawRight, jawOpen (4 blendshapes) */\n jaw?: number;\n /** mouth* (23 blendshapes) */\n mouth?: number;\n /** cheekPuff, cheekSquint* (3 blendshapes) */\n cheeks?: number;\n /** noseSneer* (2 blendshapes) */\n nose?: number;\n /** tongueOut (1 blendshape) */\n tongue?: number;\n /** Per-blendshape overrides (0-2). Takes priority over group scalers. */\n overrides?: Partial<Record<string, number>>;\n}\n\n// ---------------------------------------------------------------------------\n// Precomputed blendshape → group mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Map each ARKIT_BLENDSHAPES entry to its BlendshapeGroup.\n * Built once at module load from prefix matching.\n */\nexport const BLENDSHAPE_TO_GROUP = new Map<string, BlendshapeGroup>();\n\nfor (const name of ARKIT_BLENDSHAPES) {\n if (name.startsWith('eye')) {\n BLENDSHAPE_TO_GROUP.set(name, 'eyes');\n } else if (name.startsWith('brow')) {\n BLENDSHAPE_TO_GROUP.set(name, 'brows');\n } else if (name.startsWith('jaw')) {\n BLENDSHAPE_TO_GROUP.set(name, 'jaw');\n } else if (name.startsWith('mouth')) {\n BLENDSHAPE_TO_GROUP.set(name, 'mouth');\n } else if (name.startsWith('cheek')) {\n BLENDSHAPE_TO_GROUP.set(name, 'cheeks');\n } else if (name.startsWith('nose')) {\n BLENDSHAPE_TO_GROUP.set(name, 'nose');\n } else if (name.startsWith('tongue')) {\n BLENDSHAPE_TO_GROUP.set(name, 'tongue');\n }\n}\n\n// ---------------------------------------------------------------------------\n// applyProfile utility\n// ---------------------------------------------------------------------------\n\n/**\n * Apply ExpressionProfile scaling to raw A2E blendshapes.\n *\n * For each blendshape:\n * 1. If an override exists for the blendshape name, use override as scaler\n * 2. Otherwise, use the group scaler (default 1.0)\n * 3. Clamp result to [0, 1]\n */\nexport function applyProfile(raw: Float32Array, profile: ExpressionProfile, out?: Float32Array): Float32Array {\n const scaled = out ?? new Float32Array(52);\n\n for (let i = 0; i < 52; i++) {\n const name = ARKIT_BLENDSHAPES[i];\n let scaler: number;\n\n // Per-blendshape override takes priority\n if (profile.overrides && profile.overrides[name] !== undefined) {\n scaler = profile.overrides[name]!;\n } else {\n // Group scaler (default 1.0)\n const group = BLENDSHAPE_TO_GROUP.get(name);\n scaler = group ? (profile[group] ?? 1.0) : 1.0;\n }\n\n scaled[i] = Math.min(1, Math.max(0, raw[i] * scaler));\n }\n\n return scaled;\n}\n","/**\r\n * PlaybackPipeline - Audio playback + A2E lip sync with ExpressionProfile scaling\r\n *\r\n * Refactored superset of FullFacePipeline. Adds:\r\n * - Sync mode (`feedBuffer`) for pre-recorded audio\r\n * - State tracking (idle → playing → stopping)\r\n * - Opt-in neutral transition animation on playback complete\r\n * - Idempotent `start()` (no spurious playback:complete on restart)\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { AudioScheduler } from './AudioScheduler';\r\nimport { AudioChunkCoalescer } from './AudioChunkCoalescer';\r\nimport { A2EProcessor } from '../inference/A2EProcessor';\r\nimport { BlendshapeSmoother } from '../inference/BlendshapeSmoother';\r\nimport { EventEmitter } from '../events/EventEmitter';\r\nimport type { A2EBackend } from '../inference/A2EBackend';\r\nimport { ARKIT_BLENDSHAPES } from '../inference/blendshapeUtils';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\nimport { pcm16ToFloat32 } from './audioUtils';\r\nimport { applyProfile } from './expressionProfile';\r\nimport type { ExpressionProfile } from './expressionProfile';\r\n\r\nconst logger = createLogger('PlaybackPipeline');\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport type PlaybackState = 'idle' | 'playing' | 'stopping';\r\n\r\nexport interface PlaybackPipelineConfig {\r\n /** A2E inference backend (from createA2E) */\r\n lam: A2EBackend;\r\n /** Sample rate in Hz (default: 16000) */\r\n sampleRate?: number;\r\n /** Target chunk duration for coalescing in ms (default: 200) */\r\n chunkTargetMs?: number;\r\n /** Audio playback delay in ms (default: auto-detected from backend) */\r\n audioDelayMs?: number;\r\n /** A2E inference chunk size in samples (default: 16000) */\r\n chunkSize?: number;\r\n /** Identity/style index for A2E model (default: 0) */\r\n identityIndex?: number;\r\n /** Per-character expression weight scaling */\r\n profile?: ExpressionProfile;\r\n /** Enable neutral transition on playback complete (default: false) */\r\n neutralTransitionEnabled?: boolean;\r\n /** Duration of neutral fade-out in ms (default: 250). Only applies when neutralTransitionEnabled=true. */\r\n neutralTransitionMs?: number;\r\n /** Stale frame warning threshold in ms (default: 2000) */\r\n staleThresholdMs?: number;\r\n}\r\n\r\n/**\r\n * Full face frame with scaled blendshapes\r\n */\r\nexport interface FullFaceFrame {\r\n /** Scaled 52 ARKit blendshapes (ExpressionProfile applied) */\r\n blendshapes: Float32Array;\r\n /** Raw A2E output (52 blendshapes, before profile scaling) */\r\n rawBlendshapes: Float32Array;\r\n /** AudioContext timestamp for this frame */\r\n timestamp: number;\r\n /** Emotion label for this frame (from SenseVoice, text heuristics, or LLM tags) */\r\n emotion?: string;\r\n}\r\n\r\nexport interface PlaybackPipelineEvents {\r\n /** New frame ready for display (scaled by ExpressionProfile) */\r\n 'frame': FullFaceFrame;\r\n /** Raw A2E frame (before profile scaling) */\r\n 'frame:raw': Float32Array;\r\n /** Playback started (first audio scheduled) */\r\n 'playback:start': { time: number };\r\n /** Playback completed naturally */\r\n 'playback:complete': void;\r\n /** Playback stopped (user-initiated) */\r\n 'playback:stop': void;\r\n /** Error occurred */\r\n 'error': Error;\r\n /** State changed */\r\n 'state': PlaybackState;\r\n\r\n [key: string]: unknown;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// PlaybackPipeline\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class PlaybackPipeline extends EventEmitter<PlaybackPipelineEvents> {\r\n private scheduler: AudioScheduler;\r\n private coalescer: AudioChunkCoalescer;\r\n private processor: A2EProcessor;\r\n private readonly sampleRate: number;\r\n\r\n private _state: PlaybackState = 'idle';\r\n private playbackStarted = false;\r\n private monitorInterval: ReturnType<typeof setInterval> | null = null;\r\n private frameAnimationId: number | null = null;\r\n\r\n // Stale frame detection\r\n private lastNewFrameTime = 0;\r\n private lastKnownLamFrame: Float32Array | null = null;\r\n private staleWarningEmitted = false;\r\n private readonly staleThresholdMs: number;\r\n\r\n // Diagnostic counter\r\n private frameLoopCount = 0;\r\n\r\n // Telemetry\r\n private sessionStartTime = 0;\r\n\r\n // ExpressionProfile\r\n private profile: ExpressionProfile;\r\n\r\n // Neutral transition\r\n private readonly neutralTransitionEnabled: boolean;\r\n private readonly neutralTransitionMs: number;\r\n private neutralTransitionFrame: Float32Array | null = null;\r\n private neutralTransitionStart = 0;\r\n private neutralAnimationId: number | null = null;\r\n\r\n // Ramp-in smoother: smooths neutral → first inference frame at start-of-speech\r\n private static readonly RAMP_IN_HALFLIFE = 0.05; // 50ms — snappy but smooth\r\n private static readonly RAMP_IN_DURATION_MS = 150; // deactivate after ~3 halflives\r\n private rampInSmoother: BlendshapeSmoother;\r\n private rampInActive = false;\r\n private rampInLastTime = 0;\r\n private rampInStartTime = 0;\r\n private readonly _rampInBuffer = new Float32Array(52);\r\n\r\n // Current frame refs\r\n private _currentFrame: Float32Array | null = null;\r\n private _currentRawFrame: Float32Array | null = null;\r\n\r\n // Emotion label (set externally via setEmotion, included in emitted frames)\r\n private _emotion: string | null = null;\r\n\r\n // Pre-allocated buffer for applyProfile (avoids per-frame Float32Array allocation)\r\n private readonly _profileBuffer = new Float32Array(52);\r\n\r\n /** Current pipeline state */\r\n get state(): PlaybackState { return this._state; }\r\n /** Current scaled blendshapes (updated in-place for perf) */\r\n get currentFrame(): Float32Array | null { return this._currentFrame; }\r\n /** Raw A2E blendshapes (before profile scaling) */\r\n get currentRawFrame(): Float32Array | null { return this._currentRawFrame; }\r\n\r\n constructor(private readonly config: PlaybackPipelineConfig) {\r\n super();\r\n\r\n this.sampleRate = config.sampleRate ?? 16000;\r\n this.profile = config.profile ?? {};\r\n this.staleThresholdMs = config.staleThresholdMs ?? 2000;\r\n this.neutralTransitionEnabled = config.neutralTransitionEnabled ?? true;\r\n this.neutralTransitionMs = config.neutralTransitionMs ?? 250;\r\n\r\n const chunkSize = config.chunkSize ?? config.lam.chunkSize ?? 16000;\r\n\r\n // Auto-detect audio delay\r\n const chunkAccumulationMs = (chunkSize / this.sampleRate) * 1000;\r\n const inferenceEstimateMs = config.lam.backend === 'wasm' ? 150 : 50;\r\n const marginMs = 100;\r\n const autoDelay = Math.ceil(chunkAccumulationMs + inferenceEstimateMs + marginMs);\r\n const audioDelayMs = config.audioDelayMs ?? autoDelay;\r\n\r\n logger.info('PlaybackPipeline config', {\r\n chunkSize,\r\n audioDelayMs,\r\n autoDelay,\r\n backend: config.lam.backend,\r\n modelId: config.lam.modelId,\r\n neutralTransitionEnabled: this.neutralTransitionEnabled,\r\n });\r\n\r\n this.rampInSmoother = new BlendshapeSmoother({ halflife: PlaybackPipeline.RAMP_IN_HALFLIFE });\r\n\r\n this.scheduler = new AudioScheduler({\r\n sampleRate: this.sampleRate,\r\n initialLookaheadSec: audioDelayMs / 1000,\r\n });\r\n this.coalescer = new AudioChunkCoalescer({\r\n sampleRate: this.sampleRate,\r\n targetDurationMs: config.chunkTargetMs ?? 200,\r\n });\r\n this.processor = new A2EProcessor({\r\n backend: config.lam,\r\n sampleRate: this.sampleRate,\r\n chunkSize,\r\n identityIndex: config.identityIndex,\r\n onError: (error) => {\r\n logger.error('A2E inference error', { message: error.message, stack: error.stack });\r\n this.emit('error', error);\r\n },\r\n });\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Lifecycle\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Initialize AudioContext (lazy, call after user gesture) */\r\n async initialize(): Promise<void> {\r\n await this.scheduler.initialize();\r\n }\r\n\r\n /** Eagerly create AudioContext. Call from user gesture for iOS. */\r\n async warmup(): Promise<void> {\r\n await this.scheduler.warmup();\r\n }\r\n\r\n /** Update ExpressionProfile at runtime */\r\n setProfile(profile: ExpressionProfile): void {\r\n this.profile = profile;\r\n }\r\n\r\n /** Set the emotion label to include in emitted frames */\r\n setEmotion(emotion: string | null): void {\r\n this._emotion = emotion;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Async mode (streaming TTS)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Start a new playback session.\r\n * Idempotent — calling during playback resets cleanly without emitting\r\n * spurious playback:complete.\r\n */\r\n start(): void {\r\n logger.info('start');\r\n // Stop any active session first (prevents duplicate frame loops/monitors)\r\n this.stopInternal();\r\n\r\n this.scheduler.reset();\r\n this.coalescer.reset();\r\n this.processor.reset();\r\n this.playbackStarted = false;\r\n\r\n // Reset stale frame tracking\r\n this.lastNewFrameTime = 0;\r\n this.lastKnownLamFrame = null;\r\n this.staleWarningEmitted = false;\r\n this.frameLoopCount = 0;\r\n\r\n // Reset current frames\r\n this._currentFrame = null;\r\n this._currentRawFrame = null;\r\n\r\n // Activate ramp-in for smooth neutral → first inference frame\r\n this.rampInSmoother.reset();\r\n this.rampInActive = true;\r\n this.rampInLastTime = 0;\r\n this.rampInStartTime = 0;\r\n\r\n // Cancel any neutral transition in progress\r\n this.cancelNeutralTransition();\r\n\r\n // Warm up AudioContext\r\n this.scheduler.warmup();\r\n\r\n this.sessionStartTime = getClock().now();\r\n this.startFrameLoop();\r\n this.startMonitoring();\r\n this.setState('playing');\r\n }\r\n\r\n /** Feed a streaming audio chunk (PCM16 Uint8Array) */\r\n async onAudioChunk(chunk: Uint8Array): Promise<void> {\r\n const chunkStart = getClock().now();\r\n const combined = this.coalescer.add(chunk);\r\n if (!combined) return;\r\n\r\n const float32 = pcm16ToFloat32(combined);\r\n const scheduleTime = await this.scheduler.schedule(float32);\r\n\r\n if (!this.playbackStarted) {\r\n this.playbackStarted = true;\r\n this.emit('playback:start', { time: scheduleTime });\r\n }\r\n\r\n this.processor.pushAudio(float32, scheduleTime);\r\n getTelemetry()?.recordHistogram(MetricNames.PLAYBACK_CHUNK_LATENCY, getClock().now() - chunkStart);\r\n }\r\n\r\n /** Signal end of audio stream (flushes remaining audio) */\r\n async end(): Promise<void> {\r\n logger.debug('end — flushing remaining audio');\r\n const remaining = this.coalescer.flush();\r\n if (remaining) {\r\n const chunk = new Uint8Array(remaining);\r\n await this.onAudioChunk(chunk);\r\n }\r\n await this.processor.flush();\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Sync mode (full buffer)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Feed a complete audio buffer. Chunks into 200ms pieces, schedules each\r\n * for playback, runs A2E inference, then waits for completion.\r\n */\r\n async feedBuffer(audio: ArrayBuffer | Float32Array): Promise<void> {\r\n const float32 = audio instanceof Float32Array ? audio : pcm16ToFloat32(audio);\r\n logger.debug('feedBuffer', { samples: float32.length, durationMs: Math.round((float32.length / this.sampleRate) * 1000) });\r\n this.start();\r\n\r\n const chunkSamples = Math.floor(this.sampleRate * 0.2);\r\n for (let i = 0; i < float32.length; i += chunkSamples) {\r\n const chunk = float32.subarray(i, Math.min(i + chunkSamples, float32.length));\r\n const scheduleTime = await this.scheduler.schedule(chunk);\r\n this.processor.pushAudio(chunk, scheduleTime);\r\n\r\n if (!this.playbackStarted) {\r\n this.playbackStarted = true;\r\n this.emit('playback:start', { time: scheduleTime });\r\n }\r\n }\r\n await this.processor.flush();\r\n\r\n // Wait for natural playback completion or stop\r\n return new Promise<void>(resolve => {\r\n const cleanup = () => { unsubComplete(); unsubStop(); };\r\n const unsubComplete = this.on('playback:complete', () => { cleanup(); resolve(); });\r\n const unsubStop = this.on('playback:stop', () => { cleanup(); resolve(); });\r\n });\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Control\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Stop playback immediately with fade-out */\r\n async stop(fadeOutMs: number = 50): Promise<void> {\r\n logger.info('stop', { fadeOutMs });\r\n this.setState('stopping');\r\n this.stopInternal();\r\n await this.scheduler.cancelAll(fadeOutMs);\r\n\r\n this.coalescer.reset();\r\n this.processor.reset();\r\n this.playbackStarted = false;\r\n this._emotion = null;\r\n\r\n this.emit('playback:stop');\r\n\r\n // Smooth mouth close: cancel any in-progress transition, start fresh\r\n this.cancelNeutralTransition();\r\n if (this.neutralTransitionEnabled && this._currentFrame) {\r\n this.startNeutralTransition(this._currentFrame);\r\n } else {\r\n this._currentFrame = null;\r\n this._currentRawFrame = null;\r\n this.setState('idle');\r\n }\r\n }\r\n\r\n /** Cleanup all resources */\r\n dispose(): void {\r\n logger.debug('dispose');\r\n this.stopInternal();\r\n this.cancelNeutralTransition();\r\n this.scheduler.dispose();\r\n this.coalescer.reset();\r\n this.processor.dispose();\r\n this._state = 'idle';\r\n }\r\n\r\n /** Get pipeline debug state */\r\n getDebugState() {\r\n return {\r\n state: this._state,\r\n playbackStarted: this.playbackStarted,\r\n coalescerFill: this.coalescer.fillLevel,\r\n processorFill: this.processor.fillLevel,\r\n queuedFrames: this.processor.queuedFrameCount,\r\n currentTime: this.scheduler.getCurrentTime(),\r\n playbackEndTime: this.scheduler.getPlaybackEndTime(),\r\n };\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal: Frame loop\r\n // ---------------------------------------------------------------------------\r\n\r\n private startFrameLoop(): void {\r\n const updateFrame = () => {\r\n this.frameLoopCount++;\r\n const currentTime = this.scheduler.getCurrentTime();\r\n const lamFrame = this.processor.getFrameForTime(currentTime);\r\n\r\n // Track new inference frames\r\n if (lamFrame && lamFrame !== this.lastKnownLamFrame) {\r\n this.lastNewFrameTime = getClock().now();\r\n this.lastKnownLamFrame = lamFrame;\r\n this.staleWarningEmitted = false;\r\n }\r\n\r\n // Stale detection\r\n if (\r\n this.playbackStarted &&\r\n this.lastNewFrameTime > 0 &&\r\n getClock().now() - this.lastNewFrameTime > this.staleThresholdMs\r\n ) {\r\n if (!this.staleWarningEmitted) {\r\n this.staleWarningEmitted = true;\r\n logger.warn('A2E stalled — no new inference frames', {\r\n staleDurationMs: Math.round(getClock().now() - this.lastNewFrameTime),\r\n queuedFrames: this.processor.queuedFrameCount,\r\n });\r\n }\r\n }\r\n\r\n if (lamFrame) {\r\n let effectiveFrame = lamFrame;\r\n\r\n // Ramp-in: smooth neutral → first inference frame at start-of-speech\r\n if (this.rampInActive) {\r\n const now = getClock().now();\r\n if (this.rampInLastTime === 0) {\r\n this.rampInStartTime = now;\r\n this.rampInLastTime = now;\r\n }\r\n this.rampInSmoother.setTarget(lamFrame);\r\n const dt = (now - this.rampInLastTime) / 1000;\r\n this.rampInLastTime = now;\r\n\r\n if (dt > 0) {\r\n const smoothed = this.rampInSmoother.update(dt);\r\n this._rampInBuffer.set(smoothed);\r\n effectiveFrame = this._rampInBuffer;\r\n }\r\n\r\n if (now - this.rampInStartTime > PlaybackPipeline.RAMP_IN_DURATION_MS) {\r\n this.rampInActive = false;\r\n }\r\n }\r\n\r\n const scaled = applyProfile(effectiveFrame, this.profile, this._profileBuffer);\r\n this._currentFrame = scaled;\r\n this._currentRawFrame = effectiveFrame;\r\n\r\n const fullFrame: FullFaceFrame = {\r\n blendshapes: scaled,\r\n rawBlendshapes: effectiveFrame,\r\n timestamp: currentTime,\r\n emotion: this._emotion ?? undefined,\r\n };\r\n\r\n this.emit('frame', fullFrame);\r\n this.emit('frame:raw', effectiveFrame);\r\n }\r\n\r\n this.frameAnimationId = requestAnimationFrame(updateFrame);\r\n };\r\n\r\n this.frameAnimationId = requestAnimationFrame(updateFrame);\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal: Playback monitoring\r\n // ---------------------------------------------------------------------------\r\n\r\n private startMonitoring(): void {\r\n if (this.monitorInterval) {\r\n clearInterval(this.monitorInterval);\r\n }\r\n\r\n this.monitorInterval = setInterval(() => {\r\n if (this.scheduler.isComplete() && this.processor.queuedFrameCount === 0) {\r\n this.onPlaybackComplete();\r\n }\r\n }, 100);\r\n }\r\n\r\n private onPlaybackComplete(): void {\r\n logger.debug('playback complete');\r\n if (this.sessionStartTime > 0) {\r\n getTelemetry()?.recordHistogram(\r\n MetricNames.PLAYBACK_SESSION_DURATION,\r\n getClock().now() - this.sessionStartTime,\r\n );\r\n }\r\n this.stopInternal();\r\n this.playbackStarted = false;\r\n\r\n this.emit('playback:complete');\r\n\r\n this.cancelNeutralTransition();\r\n if (this.neutralTransitionEnabled && this._currentFrame) {\r\n this.startNeutralTransition(this._currentFrame);\r\n } else {\r\n this.setState('idle');\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal: Neutral transition (opt-in)\r\n // ---------------------------------------------------------------------------\r\n\r\n private startNeutralTransition(fromFrame: Float32Array): void {\r\n this.neutralTransitionFrame = new Float32Array(fromFrame);\r\n this.neutralTransitionStart = getClock().now();\r\n\r\n const animate = () => {\r\n const elapsed = getClock().now() - this.neutralTransitionStart;\r\n const t = Math.min(1, elapsed / this.neutralTransitionMs);\r\n // Ease-out cubic: 1 - (1-t)^3\r\n const eased = 1 - Math.pow(1 - t, 3);\r\n\r\n logger.trace('neutral transition', { t: Math.round(t * 1000) / 1000, eased: Math.round(eased * 1000) / 1000 });\r\n\r\n const blendshapes = new Float32Array(52);\r\n for (let i = 0; i < 52; i++) {\r\n blendshapes[i] = this.neutralTransitionFrame![i] * (1 - eased);\r\n }\r\n\r\n this._currentFrame = blendshapes;\r\n const frame: FullFaceFrame = {\r\n blendshapes,\r\n rawBlendshapes: blendshapes, // raw = scaled during transition\r\n timestamp: getClock().now() / 1000,\r\n emotion: this._emotion ?? undefined,\r\n };\r\n this.emit('frame', frame);\r\n\r\n if (t >= 1) {\r\n this.neutralTransitionFrame = null;\r\n this._currentFrame = null;\r\n this._currentRawFrame = null;\r\n this.setState('idle');\r\n return;\r\n }\r\n\r\n this.neutralAnimationId = requestAnimationFrame(animate);\r\n };\r\n\r\n this.neutralAnimationId = requestAnimationFrame(animate);\r\n }\r\n\r\n private cancelNeutralTransition(): void {\r\n if (this.neutralAnimationId) {\r\n cancelAnimationFrame(this.neutralAnimationId);\r\n this.neutralAnimationId = null;\r\n }\r\n this.neutralTransitionFrame = null;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal: Helpers\r\n // ---------------------------------------------------------------------------\r\n\r\n private stopInternal(): void {\r\n if (this.monitorInterval) {\r\n clearInterval(this.monitorInterval);\r\n this.monitorInterval = null;\r\n }\r\n if (this.frameAnimationId) {\r\n cancelAnimationFrame(this.frameAnimationId);\r\n this.frameAnimationId = null;\r\n }\r\n }\r\n\r\n private setState(state: PlaybackState): void {\r\n if (this._state === state) return;\r\n const prev = this._state;\r\n this._state = state;\r\n logger.debug('state transition', { from: prev, to: state });\r\n this.emit('state', state);\r\n }\r\n}\r\n","/**\r\n * TTSPlayback — Composes TTSBackend + PlaybackPipeline for text → lip sync.\r\n *\r\n * Handles format conversion (Float32 @ TTS rate → PCM16 @ 16kHz)\r\n * and sentence prefetch for gapless playback.\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { PlaybackPipeline } from './PlaybackPipeline';\r\nimport { ttsToPlaybackFormat } from './audioConvert';\r\nimport { EventEmitter } from '../events/EventEmitter';\r\nimport { createLogger } from '../logging';\r\nimport type { TTSBackend } from '../inference/TTSBackend';\r\nimport type { A2EBackend } from '../inference/A2EBackend';\r\nimport type { ExpressionProfile } from './expressionProfile';\r\nimport type { PlaybackPipelineConfig, PlaybackPipelineEvents, FullFaceFrame } from './PlaybackPipeline';\r\n\r\nconst logger = createLogger('TTSPlayback');\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface TTSPlaybackConfig {\r\n /** TTS backend (e.g., KokoroTTSInference) */\r\n tts: TTSBackend;\r\n /** A2E inference backend (from createA2E) */\r\n lam: A2EBackend;\r\n /** Per-character expression weight scaling */\r\n profile?: ExpressionProfile;\r\n /** Prefetch next sentence while current plays. Default: true */\r\n prefetch?: boolean;\r\n /** Identity/style index for A2E model (default: 0) */\r\n identityIndex?: number;\r\n /** Audio playback delay in ms */\r\n audioDelayMs?: number;\r\n /** Enable neutral transition on playback complete */\r\n neutralTransitionEnabled?: boolean;\r\n /** Duration of neutral fade-out in ms */\r\n neutralTransitionMs?: number;\r\n}\r\n\r\nexport interface TTSPlaybackEvents {\r\n /** New frame ready for display */\r\n 'frame': FullFaceFrame;\r\n /** Raw A2E frame */\r\n 'frame:raw': Float32Array;\r\n /** Playback started */\r\n 'playback:start': { time: number };\r\n /** Playback completed */\r\n 'playback:complete': void;\r\n /** Playback stopped (user-initiated) */\r\n 'playback:stop': void;\r\n /** Error */\r\n 'error': Error;\r\n [key: string]: unknown;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// TTSPlayback\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class TTSPlayback extends EventEmitter<TTSPlaybackEvents> {\r\n private readonly config: TTSPlaybackConfig;\r\n private _pipeline: PlaybackPipeline | null = null;\r\n private initialized = false;\r\n\r\n constructor(config: TTSPlaybackConfig) {\r\n super();\r\n this.config = config;\r\n }\r\n\r\n /** Access underlying PlaybackPipeline for event subscriptions. */\r\n get pipeline(): PlaybackPipeline | null {\r\n return this._pipeline;\r\n }\r\n\r\n /** Load TTS model + initialize PlaybackPipeline. */\r\n async initialize(): Promise<void> {\r\n if (this.initialized) return;\r\n\r\n // Load TTS model if not already loaded\r\n if (!this.config.tts.isLoaded) {\r\n logger.info('Loading TTS model...');\r\n await this.config.tts.load();\r\n }\r\n\r\n // Create PlaybackPipeline\r\n this._pipeline = new PlaybackPipeline({\r\n lam: this.config.lam,\r\n profile: this.config.profile,\r\n identityIndex: this.config.identityIndex,\r\n audioDelayMs: this.config.audioDelayMs,\r\n neutralTransitionEnabled: this.config.neutralTransitionEnabled ?? true,\r\n neutralTransitionMs: this.config.neutralTransitionMs,\r\n });\r\n await this._pipeline.initialize();\r\n\r\n // Forward events\r\n this._pipeline.on('frame', (f) => this.emit('frame', f));\r\n this._pipeline.on('frame:raw', (f) => this.emit('frame:raw', f));\r\n this._pipeline.on('playback:start', (t) => this.emit('playback:start', t));\r\n this._pipeline.on('playback:complete', () => this.emit('playback:complete'));\r\n this._pipeline.on('playback:stop', () => this.emit('playback:stop'));\r\n this._pipeline.on('error', (e) => this.emit('error', e));\r\n\r\n this.initialized = true;\r\n logger.info('TTSPlayback initialized');\r\n }\r\n\r\n /**\r\n * Synthesize text and play with lip sync.\r\n * Streams sentences with prefetch for minimal gaps.\r\n *\r\n * @returns Resolves when playback completes\r\n */\r\n async speak(text: string, options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string }): Promise<void> {\r\n if (!this._pipeline || !this.initialized) {\r\n throw new Error('Not initialized. Call initialize() first.');\r\n }\r\n\r\n const tts = this.config.tts;\r\n const pipeline = this._pipeline;\r\n const prefetch = this.config.prefetch ?? true;\r\n\r\n logger.info('speak', { textLength: text.length, voice: options?.voice, prefetch });\r\n\r\n pipeline.start();\r\n\r\n try {\r\n if (prefetch) {\r\n await this.speakWithPrefetch(text, options);\r\n } else {\r\n await this.speakSequential(text, options);\r\n }\r\n\r\n await pipeline.end();\r\n } catch (err) {\r\n if (options?.signal?.aborted) {\r\n logger.warn('speak aborted via AbortSignal');\r\n pipeline.stop();\r\n return;\r\n }\r\n logger.error('speak error', { message: (err as Error).message });\r\n throw err;\r\n }\r\n\r\n // Wait for playback to complete or stop\r\n await new Promise<void>((resolve) => {\r\n const cleanup = () => { unsubComplete(); unsubStop(); };\r\n const unsubComplete = pipeline.on('playback:complete', () => { cleanup(); resolve(); });\r\n const unsubStop = pipeline.on('playback:stop', () => { cleanup(); resolve(); });\r\n });\r\n\r\n logger.debug('speak complete', { textLength: text.length });\r\n }\r\n\r\n /** Eagerly create AudioContext. Call from user gesture for iOS. */\r\n async warmup(): Promise<void> {\r\n if (this._pipeline) await this._pipeline.warmup();\r\n }\r\n\r\n /** Dispose of all resources. */\r\n async dispose(): Promise<void> {\r\n logger.debug('dispose');\r\n this._pipeline?.dispose();\r\n this._pipeline = null;\r\n this.initialized = false;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal: Prefetch strategy\r\n // ---------------------------------------------------------------------------\r\n\r\n private async speakWithPrefetch(\r\n text: string,\r\n options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string },\r\n ): Promise<void> {\r\n const tts = this.config.tts;\r\n const pipeline = this._pipeline!;\r\n\r\n // Collect chunks into a queue, prefetching ahead\r\n const chunks: Array<{ audio: Float32Array; duration: number }> = [];\r\n const generator = tts.stream(text, {\r\n signal: options?.signal,\r\n voice: options?.voice,\r\n speed: options?.speed,\r\n language: options?.language,\r\n singleShot: true,\r\n });\r\n\r\n // Start fetching first chunk\r\n let nextPromise = generator.next();\r\n\r\n while (true) {\r\n if (options?.signal?.aborted) return;\r\n\r\n const result = await nextPromise;\r\n if (result.done) break;\r\n\r\n const chunk = result.value;\r\n logger.debug('TTS chunk arrived (prefetch)', { samples: chunk.audio.length, durationMs: Math.round(chunk.duration * 1000) });\r\n\r\n // Start prefetching next chunk immediately\r\n nextPromise = generator.next();\r\n\r\n // Convert and feed to pipeline\r\n const pcm16 = ttsToPlaybackFormat(chunk.audio, tts.sampleRate, 16000);\r\n await pipeline.onAudioChunk(pcm16);\r\n }\r\n }\r\n\r\n private async speakSequential(\r\n text: string,\r\n options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string },\r\n ): Promise<void> {\r\n const tts = this.config.tts;\r\n const pipeline = this._pipeline!;\r\n\r\n for await (const chunk of tts.stream(text, {\r\n signal: options?.signal,\r\n voice: options?.voice,\r\n speed: options?.speed,\r\n language: options?.language,\r\n singleShot: true,\r\n })) {\r\n if (options?.signal?.aborted) return;\r\n\r\n logger.debug('TTS chunk arrived (sequential)', { samples: chunk.audio.length, durationMs: Math.round(chunk.duration * 1000) });\r\n const pcm16 = ttsToPlaybackFormat(chunk.audio, tts.sampleRate, 16000);\r\n await pipeline.onAudioChunk(pcm16);\r\n }\r\n }\r\n}\r\n","/**\r\n * Default and user-configurable model URLs for all ONNX models\r\n *\r\n * Out of the box, models are served from HuggingFace CDN (`/resolve/main/`\r\n * endpoint with `Access-Control-Allow-Origin: *`). For production apps that\r\n * need faster or more reliable delivery, call {@link configureModelUrls} once\r\n * at startup to point any or all models at your own CDN.\r\n *\r\n * @category Inference\r\n *\r\n * @example Use HuggingFace defaults (zero-config)\r\n * ```typescript\r\n * import { createA2E } from '@omote/core';\r\n * const a2e = createA2E(); // fetches from HuggingFace CDN\r\n * ```\r\n *\r\n * @example Self-host on your own CDN\r\n * ```typescript\r\n * import { configureModelUrls, createA2E } from '@omote/core';\r\n *\r\n * configureModelUrls({\r\n * lam: 'https://cdn.example.com/models/model_fp16.onnx',\r\n * senseVoice: 'https://cdn.example.com/models/sensevoice.int8.onnx',\r\n * // omitted keys keep HuggingFace defaults\r\n * });\r\n *\r\n * const a2e = createA2E(); // now fetches from your CDN\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('ModelUrls');\r\n\r\n/**\r\n * Validate that a URL is safe to use for model loading.\r\n * Allows: relative paths, HTTPS URLs, and HTTP only for localhost.\r\n */\r\nfunction isAllowedUrl(url: string): boolean {\r\n if (url.startsWith('/') && !url.startsWith('//')) return true; // relative\r\n if (url.startsWith('./') || url.startsWith('../')) return true; // relative\r\n try {\r\n const parsed = new URL(url, 'https://placeholder.invalid');\r\n if (parsed.protocol === 'https:') return true;\r\n if (parsed.protocol === 'http:') {\r\n return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname === '[::1]';\r\n }\r\n return false;\r\n } catch { return false; }\r\n}\r\n\r\nconst HF = 'https://huggingface.co';\r\n\r\n/** Model URL keys that can be configured */\r\nexport type ModelUrlKey = 'lam' | 'senseVoice' | 'sileroVad' | 'kokoroTTS' | 'kokoroVoices';\r\n\r\n/** HuggingFace CDN fallback URLs (immutable) */\r\nconst HF_MODEL_URLS: Readonly<Record<ModelUrlKey, string>> = {\r\n /** LAM A2E model — fp16 external data (230KB graph + 192MB weights) — 52 ARKit blendshapes */\r\n lam: `${HF}/omote-ai/lam-a2e/resolve/main/lam_ios.onnx`,\r\n\r\n /** SenseVoice ASR model (228MB int8, WASM) — speech recognition + emotion + language */\r\n senseVoice: `${HF}/omote-ai/sensevoice-asr/resolve/main/model.int8.onnx`,\r\n\r\n /** Silero VAD model (~2MB, WASM) — voice activity detection */\r\n sileroVad: `${HF}/deepghs/silero-vad-onnx/resolve/main/silero_vad.onnx`,\r\n\r\n /** Kokoro TTS model (92MB q8, WASM/WebGPU) — text-to-speech */\r\n kokoroTTS: `${HF}/onnx-community/Kokoro-82M-v1.0-ONNX/resolve/main/onnx/model_quantized.onnx`,\r\n\r\n /** Kokoro voice files base URL (individual .bin files under /voices/) */\r\n kokoroVoices: `${HF}/onnx-community/Kokoro-82M-v1.0-ONNX/resolve/main/voices`,\r\n};\r\n\r\n/** User overrides (mutable, starts empty) */\r\nconst _overrides: Partial<Record<ModelUrlKey, string>> = {};\r\n\r\n/**\r\n * Resolved model URLs — user overrides take priority, HuggingFace CDN is fallback.\r\n *\r\n * All SDK factories (`createA2E`, `createSenseVoice`, `createSileroVAD`) and\r\n * orchestrators (`VoiceOrchestrator`) read from this object. Call\r\n * {@link configureModelUrls} before constructing any pipelines to point\r\n * models at your own CDN.\r\n */\r\nexport const DEFAULT_MODEL_URLS: Readonly<Record<ModelUrlKey, string>> = new Proxy(\r\n {} as Record<ModelUrlKey, string>,\r\n {\r\n get(_target, prop: string) {\r\n const key = prop as ModelUrlKey;\r\n return _overrides[key] ?? HF_MODEL_URLS[key];\r\n },\r\n ownKeys() {\r\n return Object.keys(HF_MODEL_URLS);\r\n },\r\n getOwnPropertyDescriptor(_target, prop) {\r\n if (prop in HF_MODEL_URLS) {\r\n return { configurable: true, enumerable: true, value: (this as any).get!(_target, prop, _target) };\r\n }\r\n return undefined;\r\n },\r\n },\r\n);\r\n\r\n/**\r\n * Configure custom model URLs. Overrides persist for the lifetime of the page.\r\n * Omitted keys keep their HuggingFace CDN defaults.\r\n *\r\n * Call this **once** at app startup, before constructing any pipelines.\r\n *\r\n * @example Self-host all models\r\n * ```typescript\r\n * configureModelUrls({\r\n * lam: 'https://cdn.example.com/models/lam.onnx',\r\n * senseVoice: 'https://cdn.example.com/models/sensevoice.int8.onnx',\r\n * sileroVad: 'https://cdn.example.com/models/silero-vad.onnx',\r\n * });\r\n * ```\r\n *\r\n * @example Override only one model\r\n * ```typescript\r\n * configureModelUrls({\r\n * lam: '/models/model_fp16.onnx', // self-hosted, same origin\r\n * });\r\n * ```\r\n */\r\nexport function configureModelUrls(urls: Partial<Record<ModelUrlKey, string>>): void {\r\n logger.info('Model URLs configured', { keys: Object.keys(urls) });\r\n for (const [key, url] of Object.entries(urls)) {\r\n if (key in HF_MODEL_URLS && typeof url === 'string') {\r\n if (!isAllowedUrl(url)) {\r\n throw new Error(`Invalid model URL for '${key}': must be HTTPS, relative path, or localhost HTTP`);\r\n }\r\n _overrides[key as ModelUrlKey] = url;\r\n } else {\r\n logger.warn('Unrecognized model URL key', { key });\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Reset all model URL overrides back to HuggingFace CDN defaults.\r\n * Mainly useful for testing.\r\n */\r\nexport function resetModelUrls(): void {\r\n logger.debug('Model URLs reset to defaults');\r\n for (const key of Object.keys(_overrides)) {\r\n delete _overrides[key as ModelUrlKey];\r\n }\r\n}\r\n\r\n/**\r\n * Get the immutable HuggingFace CDN URLs (ignoring any overrides).\r\n * Useful for documentation or fallback logic.\r\n */\r\nexport const HF_CDN_URLS = HF_MODEL_URLS;\r\n","/**\r\n * Runtime detection utilities for platform-specific inference configuration\r\n *\r\n * These utilities help determine the optimal backend (WebGPU vs WASM) based on\r\n * the current platform's capabilities and known limitations.\r\n *\r\n * Key considerations:\r\n * - iOS Safari: WebGPU crashes due to JSEP bugs (GitHub #22776, #26827)\r\n * - Android Chrome: WebGPU works well (Chrome 121+)\r\n * - Desktop: WebGPU preferred for performance\r\n *\r\n * @module utils/runtime\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('Runtime');\r\n\r\n/**\r\n * Supported inference backends\r\n */\r\nexport type RuntimeBackend = 'webgpu' | 'wasm';\r\n\r\n/**\r\n * User-configurable backend preference\r\n */\r\nexport type BackendPreference =\r\n | 'auto' // iOS→WASM, else→WebGPU with fallback\r\n | 'webgpu' // Prefer WebGPU, fallback to WASM on error\r\n | 'wasm' // Prefer WASM, no WebGPU attempt\r\n | 'webgpu-only' // Force WebGPU, throw on failure (for debugging)\r\n | 'wasm-only'; // Force WASM, never load WebGPU bundle (smallest bundle)\r\n\r\n/**\r\n * Detect iOS Safari browser\r\n *\r\n * iOS Safari has severe WebGPU issues:\r\n * - JSEP compilation bugs cause OOM during session creation\r\n * - Threading bugs require numThreads=1\r\n * - Proxy mode triggers memory leaks\r\n *\r\n * @returns true if running in iOS Safari\r\n */\r\nexport function isIOSSafari(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n // Must be on iOS device AND using Safari (not Chrome/CriOS/Firefox/Edge)\r\n return /iphone|ipad|ipod/.test(ua) && /safari/.test(ua) && !/chrome|crios|fxios|chromium|edg/.test(ua);\r\n}\r\n\r\n/**\r\n * Detect any iOS device (regardless of browser)\r\n *\r\n * On iOS, all browsers use WebKit, so Chrome/Firefox on iOS\r\n * have the same limitations as Safari.\r\n *\r\n * @returns true if running on any iOS device\r\n */\r\nexport function isIOS(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n // iPadOS 13+ reports macOS user agent — detect via maxTouchPoints\r\n return /iphone|ipad|ipod/.test(ua) ||\r\n (/macintosh/.test(ua) && navigator.maxTouchPoints > 1);\r\n}\r\n\r\n/**\r\n * Detect Android device\r\n *\r\n * Android Chrome 121+ has good WebGPU support with Qualcomm/ARM GPUs.\r\n *\r\n * @returns true if running on Android\r\n */\r\nexport function isAndroid(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n return /android/i.test(navigator.userAgent);\r\n}\r\n\r\n/**\r\n * Detect any mobile device (iOS or Android)\r\n *\r\n * Mobile devices have different performance characteristics:\r\n * - Lower memory limits\r\n * - Thermal throttling\r\n * - Different GPU architectures\r\n *\r\n * @returns true if running on mobile\r\n */\r\nexport function isMobile(): boolean {\r\n return isIOS() || isAndroid();\r\n}\r\n\r\n/**\r\n * Check if WebGPU API is available in the browser\r\n *\r\n * Note: This only checks if the API exists, not if it works reliably.\r\n * iOS has navigator.gpu but ONNX Runtime's WebGPU backend crashes.\r\n *\r\n * @returns true if navigator.gpu exists\r\n */\r\nexport function hasWebGPUApi(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n return 'gpu' in navigator && navigator.gpu !== undefined;\r\n}\r\n\r\n/**\r\n * Get the recommended backend for the current platform\r\n *\r\n * Decision tree:\r\n * 1. iOS (any browser): Force WASM (WebGPU crashes)\r\n * 2. Android: WebGPU preferred (works in Chrome 121+)\r\n * 3. Desktop: WebGPU preferred (best performance)\r\n *\r\n * @returns 'wasm' for iOS, 'webgpu' for everything else\r\n */\r\nexport function getRecommendedBackend(): RuntimeBackend {\r\n // Safari (all platforms): Always WASM - WebGPU crashes due to JSEP bugs\r\n // iOS: All browsers use WebKit, so all have the same issue\r\n // macOS Safari: Same multithreaded JSEP build bug\r\n if (isSafari() || isIOS()) {\r\n logger.debug('Recommended backend: wasm', { reason: 'Safari/iOS detected — WebGPU crashes due to JSEP bugs' });\r\n return 'wasm';\r\n }\r\n\r\n // Android/Desktop (non-Safari): WebGPU preferred\r\n logger.debug('Recommended backend: webgpu', { reason: 'Non-Safari/iOS platform — WebGPU preferred' });\r\n return 'webgpu';\r\n}\r\n\r\n/**\r\n * Resolve user preference to actual backend\r\n *\r\n * @param preference User's backend preference\r\n * @param webgpuAvailable Whether WebGPU is available and working\r\n * @returns The backend to use\r\n */\r\nexport function resolveBackend(\r\n preference: BackendPreference,\r\n webgpuAvailable: boolean\r\n): RuntimeBackend {\r\n let resolved: RuntimeBackend;\r\n switch (preference) {\r\n case 'wasm-only':\r\n resolved = 'wasm';\r\n break;\r\n\r\n case 'webgpu-only':\r\n if (!webgpuAvailable) {\r\n throw new Error(\r\n 'WebGPU requested but not available. Use \"webgpu\" or \"auto\" for fallback.'\r\n );\r\n }\r\n resolved = 'webgpu';\r\n break;\r\n\r\n case 'wasm':\r\n resolved = 'wasm';\r\n break;\r\n\r\n case 'webgpu':\r\n resolved = webgpuAvailable ? 'webgpu' : 'wasm';\r\n break;\r\n\r\n case 'auto':\r\n default: {\r\n // Auto: Use platform recommendation, with WebGPU availability check\r\n const recommended = getRecommendedBackend();\r\n if (recommended === 'webgpu' && !webgpuAvailable) {\r\n resolved = 'wasm';\r\n } else {\r\n resolved = recommended;\r\n }\r\n break;\r\n }\r\n }\r\n logger.debug('Resolved backend', { preference, webgpuAvailable, resolved });\r\n return resolved;\r\n}\r\n\r\n/**\r\n * Get optimal WASM thread count for current platform\r\n *\r\n * @returns Recommended number of WASM threads\r\n */\r\nexport function getOptimalWasmThreads(): number {\r\n if (isIOS()) {\r\n // iOS: Must be 1 to avoid shared memory bugs (GitHub #22086)\r\n logger.debug('Optimal WASM threads: 1', { platform: 'iOS' });\r\n return 1;\r\n }\r\n\r\n if (isAndroid()) {\r\n // Android: Conservative threading (2 threads)\r\n logger.debug('Optimal WASM threads: 2', { platform: 'Android' });\r\n return 2;\r\n }\r\n\r\n // Desktop: Full threading (4 threads)\r\n logger.debug('Optimal WASM threads: 4', { platform: 'Desktop' });\r\n return 4;\r\n}\r\n\r\n/**\r\n * Check if WASM proxy mode should be enabled\r\n *\r\n * Proxy mode offloads inference to a Web Worker, but has issues:\r\n * - iOS: Triggers Safari 26 JSEP memory leak\r\n * - Mobile: Generally unstable\r\n *\r\n * @returns true if proxy mode is safe to enable\r\n */\r\nexport function shouldEnableWasmProxy(): boolean {\r\n // Always disable proxy. When proxy=true, WASM initializes in a Web Worker.\r\n // The WebGPU EP needs WASM on the main thread for model parsing. If a\r\n // consumer Vite-aliases all ORT imports to the same module, proxy=true\r\n // on one session causes \"WebAssembly is not initialized yet\" for WebGPU.\r\n return false;\r\n}\r\n\r\n/**\r\n * Detect Safari browser on any platform (macOS + iOS)\r\n *\r\n * Safari WebKit has bugs with ONNX Runtime's WebGPU multithreaded JSEP build\r\n * that crash session creation. Both iOS and macOS Safari are affected.\r\n *\r\n * @returns true if running in Safari on any platform\r\n */\r\nexport function isSafari(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n // Safari: has \"safari\" but not Chrome, Chromium, CriOS, FxiOS, or Edge\r\n return /safari/.test(ua) && !/chrome|crios|fxios|chromium|edg/.test(ua);\r\n}\r\n\r\n/**\r\n * Check if Web Speech API is available in the browser\r\n *\r\n * The Web Speech API provides native speech recognition in Safari and Chrome.\r\n * On iOS Safari, this is significantly faster than Whisper WASM.\r\n *\r\n * @returns true if SpeechRecognition API is available\r\n */\r\nexport function isSpeechRecognitionAvailable(): boolean {\r\n if (typeof window === 'undefined') return false;\r\n return 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window;\r\n}\r\n\r\n/**\r\n * Recommend using native Safari Speech API over Whisper on iOS\r\n *\r\n * On iOS, Whisper ASR via WASM takes ~1.3s per inference (30% over target).\r\n * Safari's native Web Speech API is:\r\n * - Much faster (native implementation)\r\n * - Battery-efficient (no WASM overhead)\r\n * - No model download needed (saves 30-150MB)\r\n *\r\n * @returns true if on iOS or Safari with Speech API available\r\n */\r\nexport function shouldUseNativeASR(): boolean {\r\n const ios = isIOS();\r\n const safari = isSafari();\r\n const speechAvailable = isSpeechRecognitionAvailable();\r\n const result = (ios || safari) && speechAvailable;\r\n logger.debug('shouldUseNativeASR decision', { result, ios, safari, speechAvailable });\r\n return result;\r\n}\r\n\r\n/**\r\n * Recommend using server-side LAM over client-side on iOS\r\n *\r\n * On iOS, LAM A2E via WASM takes ~332ms per second of audio (3.3x over target).\r\n * Server-side inference with GPU can achieve ~50ms, providing:\r\n * - Real-time A2E (under 100ms target)\r\n * - Reduced iOS device thermal/battery impact\r\n * - Better user experience\r\n *\r\n * @returns true if on iOS (should use server-side A2E)\r\n */\r\nexport function shouldUseServerA2E(): boolean {\r\n return isIOS();\r\n}\r\n","/**\r\n * Lazy ONNX Runtime loader with conditional WebGPU/WASM bundle loading\r\n *\r\n * This module provides a way to dynamically load the appropriate ONNX Runtime bundle\r\n * based on the platform's capabilities. This is critical for iOS support because:\r\n *\r\n * 1. iOS Safari has WebGPU API but ONNX Runtime's WebGPU backend crashes\r\n * 2. Loading the WebGPU bundle wastes bandwidth and can cause issues\r\n * 3. WASM-only bundle is smaller and more reliable on iOS\r\n *\r\n * Usage:\r\n * ```typescript\r\n * const ort = await getOnnxRuntime('wasm'); // Load WASM-only bundle\r\n * const ort = await getOnnxRuntime('webgpu'); // Load WebGPU bundle (includes WASM)\r\n * ```\r\n *\r\n * @module inference/onnxLoader\r\n */\r\n\r\n// Type-only import for TypeScript (no runtime code loaded at import time)\r\n// At runtime, we dynamically import either 'onnxruntime-web' or 'onnxruntime-web/webgpu'\r\nimport type { InferenceSession, Tensor, Env } from 'onnxruntime-common';\r\n\r\n// Type alias for the ORT module (loaded dynamically)\r\nexport type OrtModule = {\r\n InferenceSession: typeof InferenceSession;\r\n Tensor: typeof Tensor;\r\n env: Env;\r\n};\r\n\r\n// Re-export session options type\r\nexport type SessionOptions = InferenceSession.SessionOptions;\r\nimport {\r\n RuntimeBackend,\r\n BackendPreference,\r\n isIOS,\r\n isIOSSafari,\r\n isSafari,\r\n isMobile,\r\n getOptimalWasmThreads,\r\n shouldEnableWasmProxy,\r\n resolveBackend,\r\n hasWebGPUApi,\r\n} from '../utils/runtime';\r\n\r\n// Re-export RuntimeBackend for consumers\r\nexport type { RuntimeBackend } from '../utils/runtime';\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('OnnxLoader');\r\n\r\n// Cached ONNX Runtime instance\r\nlet ortInstance: OrtModule | null = null;\r\nlet loadedBackend: RuntimeBackend | null = null;\r\n\r\n/** CDN path for ONNX Runtime WASM files — single source of truth */\r\nexport const WASM_CDN_PATH = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.2/dist/';\r\n\r\n/**\r\n * Check if WebGPU is available and likely to work\r\n *\r\n * This is more thorough than just checking navigator.gpu exists.\r\n * It actually requests an adapter to verify the GPU is accessible.\r\n *\r\n * @returns true if WebGPU is available and working\r\n */\r\nexport async function isWebGPUAvailable(): Promise<boolean> {\r\n // iOS 18+ exposes navigator.gpu, but the ORT WebGPU bundle uses the\r\n // asyncify WASM binary (ort-wasm-simd-threaded.asyncify.wasm) which\r\n // crashes WebKit's JIT on all iOS browsers. Return false early to\r\n // prevent probing the GPU adapter (which itself consumes memory).\r\n if (isIOS()) {\r\n logger.debug('WebGPU check: disabled on iOS (asyncify bundle crashes WebKit)');\r\n return false;\r\n }\r\n\r\n if (!hasWebGPUApi()) {\r\n logger.debug('WebGPU check: navigator.gpu not available', {\r\n isSecureContext: typeof window !== 'undefined' ? (window as any).isSecureContext : 'N/A',\r\n });\r\n return false;\r\n }\r\n\r\n try {\r\n const adapter = await navigator.gpu!.requestAdapter();\r\n if (!adapter) {\r\n logger.debug('WebGPU check: No adapter available');\r\n return false;\r\n }\r\n\r\n // Adapter probe is sufficient — requestDevice() allocates driver resources\r\n // that are immediately destroyed, wasting memory and potentially interfering\r\n // with the subsequent ORT session creation.\r\n logger.debug('WebGPU check: Available and working');\r\n return true;\r\n } catch (err) {\r\n logger.debug('WebGPU check: Error during availability check', { error: err });\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * iOS WASM memory patch\r\n *\r\n * ONNX Runtime's ort-wasm-simd-threaded.wasm creates\r\n * WebAssembly.Memory({initial:256, maximum:65536, shared:true}).\r\n * iOS Safari rejects this with \"RangeError: Out of memory\" because\r\n * shared WebAssembly.Memory with a 4GB maximum exceeds iOS limits.\r\n *\r\n * This patch intercepts WebAssembly.Memory construction on iOS to:\r\n * 1. Keep `shared` flag (required by ORT v1.23.2 threaded WASM)\r\n * 2. Cap `maximum` to 32768 pages (2GB)\r\n *\r\n * The 2GB cap balances memory headroom for large A2E models\r\n * (needs ~500MB WASM for weights + activations) while staying within\r\n * iOS WebKit's process limits on modern iPhones (6-8GB RAM).\r\n *\r\n * Applied once on first ORT load, stays active for the page lifetime.\r\n */\r\nlet iosWasmPatched = false;\r\n\r\nfunction applyIOSWasmMemoryPatch(): void {\r\n // Only patch on iOS Safari. iOS Chrome/Firefox (all use WebKit) may handle\r\n // the default 4GB maximum without the constructor wrapper interfering.\r\n // Safari specifically throws \"RangeError: Out of memory\" on 4GB shared memory.\r\n if (iosWasmPatched || !isIOSSafari()) return;\r\n iosWasmPatched = true;\r\n\r\n const OrigMemory = WebAssembly.Memory;\r\n const MAX_IOS_PAGES = 32768; // 32768 × 64KB = 2GB\r\n\r\n logger.info('Applying iOS WASM memory patch (max→2GB, shared preserved)');\r\n\r\n // Replace WebAssembly.Memory constructor to cap maximum pages.\r\n // ORT v1.23.2 threaded WASM binaries ALL declare shared:true in their\r\n // memory import. Setting shared:false causes a LinkError on instantiation.\r\n // Instead, keep shared:true and only cap maximum from 65536 (4GB) to\r\n // 32768 (2GB) to allow sufficient headroom for model weights + inference\r\n // activations. Log the actual descriptor for debugging.\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n (WebAssembly as any).Memory = function IOSPatchedMemory(\r\n descriptor: WebAssembly.MemoryDescriptor\r\n ): WebAssembly.Memory {\r\n const patched = { ...descriptor };\r\n\r\n if (patched.maximum !== undefined && patched.maximum > MAX_IOS_PAGES) {\r\n logger.info('iOS memory patch: capping maximum', {\r\n original: patched.maximum,\r\n capped: MAX_IOS_PAGES,\r\n shared: patched.shared,\r\n initial: patched.initial,\r\n });\r\n patched.maximum = MAX_IOS_PAGES;\r\n }\r\n\r\n return new OrigMemory(patched);\r\n };\r\n\r\n // Preserve prototype chain so instanceof checks still work\r\n (WebAssembly as any).Memory.prototype = OrigMemory.prototype;\r\n}\r\n\r\n/**\r\n * Configure WASM environment settings based on platform\r\n *\r\n * This must be called before creating any inference sessions.\r\n */\r\nfunction configureWasm(ort: OrtModule): void {\r\n // Set CDN path for WASM files\r\n ort.env.wasm.wasmPaths = WASM_CDN_PATH;\r\n\r\n // Platform-specific threading configuration\r\n const numThreads = getOptimalWasmThreads();\r\n const enableProxy = shouldEnableWasmProxy();\r\n\r\n ort.env.wasm.numThreads = numThreads;\r\n ort.env.wasm.simd = true; // SIMD always helps\r\n ort.env.wasm.proxy = enableProxy;\r\n\r\n logger.info('WASM configured', {\r\n numThreads,\r\n simd: true,\r\n proxy: enableProxy,\r\n platform: isIOS() ? 'iOS' : isMobile() ? 'Android' : 'Desktop',\r\n });\r\n}\r\n\r\n/**\r\n * Load ONNX Runtime with the specified backend\r\n *\r\n * This lazily loads the appropriate bundle:\r\n * - 'wasm': Loads onnxruntime-web (WASM-only, smaller)\r\n * - 'webgpu': Loads onnxruntime-web/webgpu (includes WebGPU + WASM fallback)\r\n *\r\n * Once loaded, the same instance is reused for all subsequent calls.\r\n * If you need to switch backends, you must reload the page.\r\n *\r\n * @param backend The backend to load ('webgpu' or 'wasm')\r\n * @returns The ONNX Runtime module\r\n */\r\nexport async function getOnnxRuntime(\r\n backend: RuntimeBackend\r\n): Promise<OrtModule> {\r\n // Return cached instance if same backend\r\n if (ortInstance && loadedBackend === backend) {\r\n return ortInstance;\r\n }\r\n\r\n // Warn if trying to switch backends (not supported without page reload)\r\n if (ortInstance && loadedBackend !== backend) {\r\n logger.warn(\r\n `ONNX Runtime already loaded with ${loadedBackend} backend. ` +\r\n `Cannot switch to ${backend}. Returning existing instance.`\r\n );\r\n return ortInstance;\r\n }\r\n\r\n logger.info(`Loading ONNX Runtime with ${backend} backend...`);\r\n\r\n // iOS: Patch WebAssembly.Memory before ORT loads its WASM binary.\r\n // Must be applied before any InferenceSession.create() call.\r\n applyIOSWasmMemoryPatch();\r\n\r\n try {\r\n if (backend === 'wasm' && (isIOS() || isSafari())) {\r\n // Safari (all platforms) + iOS (all browsers): Import via\r\n // 'onnxruntime-web/wasm' sub-path. This uses the plain WASM binaries\r\n // (ort-wasm-simd / ort-wasm-simd-threaded) WITHOUT JSEP/ASYNCIFY.\r\n //\r\n // The JSEP build (ort-wasm-simd-threaded.jsep) crashes WebKit's JIT\r\n // on iOS and macOS Safari due to ASYNCIFY code generation issues.\r\n // See: https://bugs.webkit.org/show_bug.cgi?id=262475\r\n //\r\n // With COEP/COOP headers removed for iOS (no SharedArrayBuffer),\r\n // ORT picks ort-wasm-simd.wasm (single-threaded, Case A = works).\r\n // On macOS Safari (COEP/COOP present), ORT picks\r\n // ort-wasm-simd-threaded.wasm (threaded but non-JSEP, Case B = works).\r\n const module = await import('onnxruntime-web/wasm');\r\n ortInstance = module.default || module;\r\n } else if (backend === 'wasm') {\r\n // Desktop WASM: Explicitly load /wasm sub-path to avoid Vite alias\r\n // that redirects 'onnxruntime-web' → 'onnxruntime-web/webgpu'.\r\n // This prevents double-loading when both WASM and WebGPU paths are requested.\r\n const module = await import('onnxruntime-web/wasm');\r\n ortInstance = module.default || module;\r\n } else {\r\n // Load WebGPU bundle (includes WASM fallback)\r\n const module = await import('onnxruntime-web/webgpu');\r\n ortInstance = module.default || module;\r\n }\r\n\r\n loadedBackend = backend;\r\n\r\n // Configure WASM settings (applies to both bundles)\r\n configureWasm(ortInstance);\r\n\r\n logger.info(`ONNX Runtime loaded successfully`, { backend });\r\n\r\n return ortInstance;\r\n } catch (err) {\r\n logger.error(`Failed to load ONNX Runtime with ${backend} backend`, {\r\n error: err,\r\n });\r\n throw new Error(\r\n `Failed to load ONNX Runtime: ${err instanceof Error ? err.message : String(err)}`\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Get the appropriate ONNX Runtime based on user preference\r\n *\r\n * This resolves the user's preference against platform capabilities\r\n * and loads the appropriate bundle.\r\n *\r\n * @param preference User's backend preference\r\n * @returns The ONNX Runtime module and the resolved backend\r\n */\r\nexport async function getOnnxRuntimeForPreference(\r\n preference: BackendPreference = 'auto'\r\n): Promise<{ ort: OrtModule; backend: RuntimeBackend }> {\r\n // Check WebGPU availability (skip for iOS)\r\n const webgpuAvailable = await isWebGPUAvailable();\r\n\r\n // Resolve preference to actual backend\r\n const backend = resolveBackend(preference, webgpuAvailable);\r\n\r\n logger.info('Resolved backend preference', {\r\n preference,\r\n webgpuAvailable,\r\n resolvedBackend: backend,\r\n });\r\n\r\n // Load the appropriate bundle\r\n const ort = await getOnnxRuntime(backend);\r\n\r\n return { ort, backend };\r\n}\r\n\r\n/**\r\n * Options to customize session creation beyond backend selection.\r\n */\r\nexport interface GetSessionOptionsConfig {\r\n /**\r\n * Disable graph optimization entirely on iOS.\r\n * Only needed for LAM (Wav2Vec2) whose complex transformer graph causes\r\n * ~750-950MB peak scratch memory during optimization, OOMing when other\r\n * models are already resident. Other models (SenseVoice, VAD) are fine\r\n * with 'basic' optimization.\r\n */\r\n iosDisableOptimization?: boolean;\r\n}\r\n\r\n/**\r\n * Get session options for creating an inference session\r\n *\r\n * This returns optimized session options based on the backend and platform.\r\n *\r\n * @param backend The backend being used\r\n * @param config Optional per-model overrides\r\n * @returns Session options for InferenceSession.create()\r\n */\r\nexport function getSessionOptions(\r\n backend: RuntimeBackend,\r\n config?: GetSessionOptionsConfig,\r\n): SessionOptions {\r\n if (backend === 'webgpu') {\r\n return {\r\n executionProviders: [\r\n {\r\n name: 'webgpu',\r\n preferredLayout: 'NHWC', // Reduces memory overhead for layout conversions\r\n } as const,\r\n ],\r\n graphOptimizationLevel: 'all',\r\n };\r\n }\r\n\r\n // WASM backend\r\n // iOS: reduce memory pressure during session creation.\r\n // 'basic' is safe for most models (SenseVoice, VAD).\r\n // LAM (Wav2Vec2) needs 'disabled' — its dual-head transformer graph\r\n // causes ~750-950MB peak scratch during even basic optimization,\r\n // OOMing when SenseVoice + VAD are already resident.\r\n // See: https://github.com/microsoft/onnxruntime/issues/13408\r\n if (isIOS()) {\r\n return {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: config?.iosDisableOptimization ? 'disabled' : 'basic',\r\n enableCpuMemArena: false,\r\n enableMemPattern: false,\r\n };\r\n }\r\n\r\n return {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: 'all',\r\n };\r\n}\r\n\r\n/**\r\n * Create an inference session with automatic fallback\r\n *\r\n * If WebGPU session creation fails, automatically falls back to WASM.\r\n *\r\n * @param modelBuffer The model data as ArrayBuffer\r\n * @param preferredBackend The preferred backend\r\n * @returns The created session and the backend used\r\n */\r\nexport async function createSessionWithFallback(\r\n modelBuffer: ArrayBuffer,\r\n preferredBackend: RuntimeBackend\r\n): Promise<{\r\n session: InferenceSession;\r\n backend: RuntimeBackend;\r\n}> {\r\n const ort = await getOnnxRuntime(preferredBackend);\r\n\r\n // Convert ArrayBuffer to Uint8Array for onnxruntime-common types\r\n const modelData = new Uint8Array(modelBuffer);\r\n\r\n if (preferredBackend === 'webgpu') {\r\n try {\r\n const options = getSessionOptions('webgpu');\r\n const session = await ort.InferenceSession.create(modelData, options);\r\n\r\n logger.info('Session created with WebGPU backend');\r\n return { session, backend: 'webgpu' };\r\n } catch (err) {\r\n logger.warn('WebGPU session creation failed, falling back to WASM', {\r\n error: err instanceof Error ? err.message : String(err),\r\n });\r\n // Fall through to WASM\r\n }\r\n }\r\n\r\n // WASM (primary or fallback)\r\n const options = getSessionOptions('wasm');\r\n const session = await ort.InferenceSession.create(modelData, options);\r\n\r\n logger.info('Session created with WASM backend');\r\n return { session, backend: 'wasm' };\r\n}\r\n\r\n/**\r\n * Race a promise against a timeout. Rejects with a descriptive error if the\r\n * timeout fires first. Used to guard iOS URL-pass-through model loads where\r\n * ORT manages the fetch internally and we can't retry.\r\n */\r\nexport function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {\r\n return new Promise<T>((resolve, reject) => {\r\n const timer = setTimeout(\r\n () => reject(new Error(`${label} timed out after ${ms}ms`)),\r\n ms,\r\n );\r\n promise.then(resolve, reject).finally(() => clearTimeout(timer));\r\n });\r\n}\r\n\r\n/**\r\n * Get the currently loaded backend (if any)\r\n */\r\nexport function getLoadedBackend(): RuntimeBackend | null {\r\n return loadedBackend;\r\n}\r\n\r\n/**\r\n * Check if ONNX Runtime has been loaded\r\n */\r\nexport function isOnnxRuntimeLoaded(): boolean {\r\n return ortInstance !== null;\r\n}\r\n\r\n/**\r\n * Preload ONNX Runtime and compile the WASM binary early\r\n *\r\n * Call this before loading heavy resources (Three.js, VRM models) to ensure\r\n * WASM memory is allocated in a clean JS heap, reducing iOS memory pressure.\r\n * Uses the singleton pattern — subsequent model loading reuses this instance.\r\n *\r\n * @param preference Backend preference (default: 'auto')\r\n * @returns The resolved backend that was loaded\r\n */\r\nexport async function preloadOnnxRuntime(\r\n preference: BackendPreference = 'auto'\r\n): Promise<RuntimeBackend> {\r\n if (ortInstance) {\r\n logger.info('ONNX Runtime already preloaded', { backend: loadedBackend });\r\n return loadedBackend!;\r\n }\r\n\r\n logger.info('Preloading ONNX Runtime...', { preference });\r\n const { backend } = await getOnnxRuntimeForPreference(preference);\r\n logger.info('ONNX Runtime preloaded', { backend });\r\n return backend;\r\n}\r\n","/**\r\n * Unified Inference Worker — single Web Worker hosting all ONNX models\r\n *\r\n * Runs all model loading and inference off the main thread, preventing\r\n * InferenceSession.create() from blocking the renderer (5-30s).\r\n *\r\n * Uses WebGPU when available (Chrome/Edge 113+), falls back to WASM.\r\n * On iOS, uses a single WASM instance to stay within the ~1-1.5GB tab limit.\r\n *\r\n * This worker hosts SenseVoice + A2E + Silero VAD + Kokoro TTS in a single\r\n * ORT instance. Same total model memory, but inference runs off-main-thread.\r\n *\r\n * Consumer usage:\r\n * ```typescript\r\n * const worker = new UnifiedInferenceWorker();\r\n * await worker.init();\r\n *\r\n * const asr = createSenseVoice({ modelUrl: '...', unifiedWorker: worker });\r\n * const lam = createA2E({ modelUrl: '...', unifiedWorker: worker });\r\n * const vad = createSileroVAD({ modelUrl: '...', unifiedWorker: worker });\r\n * ```\r\n *\r\n * @category Inference\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport { isIOS } from '../utils/runtime';\r\nimport type { SenseVoiceResult, SenseVoiceModelInfo } from './SenseVoiceTypes';\r\nimport type { A2EModelInfo } from './A2EBackend';\r\nimport type { VADWorkerModelInfo } from './SileroVADTypes';\r\nimport { WASM_CDN_PATH } from './onnxLoader';\r\nimport { ErrorCodes } from '../logging/ErrorCodes';\r\n\r\nconst logger = createLogger('UnifiedInferenceWorker');\r\n\r\n/** Health state of the unified worker */\r\nexport type WorkerHealthState = 'healthy' | 'unhealthy' | 'recovering';\r\n\r\n// Health check constants\r\nconst MAX_CONSECUTIVE_FAILURES = 3;\r\nconst HEALTH_CHECK_TIMEOUT_MS = 10_000; // 10s — must exceed longest inference (Kokoro ~2s)\r\n\r\n// Timeouts per operation\r\nconst INIT_TIMEOUT_MS = 60_000; // 1 min — ORT WASM script fetch on slow networks\r\nconst SV_LOAD_TIMEOUT_MS = 300_000; // 5 min — 239MB with retries (3 × 120s fetch) + ORT init\r\nconst SV_INFER_TIMEOUT_MS = 10_000;\r\nconst LAM_LOAD_TIMEOUT_MS = 300_000; // 5 min — 192MB fp16 (opset 18, 608 nodes) with retries + ORT init\r\nconst LAM_INFER_TIMEOUT_MS = 15_000; // 15s — first WebGPU inference triggers shader compilation (5-15s)\r\nconst KOKORO_LOAD_TIMEOUT_MS = 300_000; // 5 min — 92MB model + ORT init\r\nconst KOKORO_INFER_TIMEOUT_MS = 120_000; // 2 min — TTS can be slow for long text\r\nconst VAD_LOAD_TIMEOUT_MS = 120_000; // 2 min — 2MB model + ORT script fetch on slow networks\r\nconst VAD_INFER_TIMEOUT_MS = 1_000;\r\nconst DISPOSE_TIMEOUT_MS = 5_000;\r\n\r\n/** Resolve a potentially relative URL to absolute (blob workers have no base URL) */\r\nfunction resolveUrl(url: string): string {\r\n if (/^https?:\\/\\//i.test(url) || /^blob:/i.test(url)) return url;\r\n try {\r\n return new URL(url, globalThis.location?.origin ?? 'https://localhost').href;\r\n } catch {\r\n return url;\r\n }\r\n}\r\n\r\nlet requestCounter = 0;\r\nfunction nextRequestId(): string {\r\n return `req_${++requestCounter}_${Date.now()}`;\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Inline Worker Script\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nconst WORKER_SCRIPT = `\r\n// Unified Inference Worker Script\r\n// Hosts SenseVoice + A2E + Silero VAD in a single ORT instance\r\n\r\nvar ort = null;\r\n\r\nfunction fetchWithTimeout(url, timeoutMs) {\r\n var controller = new AbortController();\r\n var timer = setTimeout(function() { controller.abort(); }, timeoutMs);\r\n return fetch(url, { signal: controller.signal }).finally(function() { clearTimeout(timer); });\r\n}\r\n\r\n// SenseVoice state\r\nvar svSession = null;\r\nvar svTokenMap = null;\r\nvar svNegMean = null;\r\nvar svInvStddev = null;\r\nvar svLanguageId = 0;\r\nvar svTextNormId = 14;\r\nvar svVocabSize = 0;\r\n\r\n// LAM (A2E) state\r\nvar lamSession = null;\r\nvar lamNumIdentityClasses = 12;\r\n\r\n// Kokoro TTS state\r\nvar kokoroSession = null;\r\n\r\n// Silero VAD state\r\nvar vadSession = null;\r\nvar vadSampleRate = 16000;\r\nvar vadChunkSize = 512;\r\nvar vadContextSize = 64;\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// kaldiFbank.ts — inlined as plain JavaScript\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nfunction fft(re, im) {\r\n var n = re.length;\r\n for (var i = 1, j = 0; i < n; i++) {\r\n var bit = n >> 1;\r\n while (j & bit) { j ^= bit; bit >>= 1; }\r\n j ^= bit;\r\n if (i < j) {\r\n var tmp = re[i]; re[i] = re[j]; re[j] = tmp;\r\n tmp = im[i]; im[i] = im[j]; im[j] = tmp;\r\n }\r\n }\r\n for (var len = 2; len <= n; len *= 2) {\r\n var halfLen = len / 2;\r\n var angle = -2 * Math.PI / len;\r\n var wRe = Math.cos(angle);\r\n var wIm = Math.sin(angle);\r\n for (var i = 0; i < n; i += len) {\r\n var curRe = 1, curIm = 0;\r\n for (var j = 0; j < halfLen; j++) {\r\n var a = i + j, b = a + halfLen;\r\n var tRe = curRe * re[b] - curIm * im[b];\r\n var tIm = curRe * im[b] + curIm * re[b];\r\n re[b] = re[a] - tRe; im[b] = im[a] - tIm;\r\n re[a] += tRe; im[a] += tIm;\r\n var nextRe = curRe * wRe - curIm * wIm;\r\n curIm = curRe * wIm + curIm * wRe;\r\n curRe = nextRe;\r\n }\r\n }\r\n }\r\n}\r\n\r\nfunction htkMel(freq) { return 1127.0 * Math.log(1.0 + freq / 700.0); }\r\nfunction htkMelInverse(mel) { return 700.0 * (Math.exp(mel / 1127.0) - 1.0); }\r\n\r\nfunction buildMelFilterbank(numBins, fftSize, sampleRate, lowFreq, highFreq) {\r\n var numFftBins = fftSize / 2 + 1;\r\n var lowMel = htkMel(lowFreq);\r\n var highMel = htkMel(highFreq);\r\n var melPoints = new Float64Array(numBins + 2);\r\n for (var i = 0; i < numBins + 2; i++) {\r\n melPoints[i] = lowMel + (highMel - lowMel) * i / (numBins + 1);\r\n }\r\n var binFreqs = new Float64Array(numBins + 2);\r\n for (var i = 0; i < numBins + 2; i++) {\r\n binFreqs[i] = htkMelInverse(melPoints[i]) * fftSize / sampleRate;\r\n }\r\n var filters = [];\r\n for (var m = 0; m < numBins; m++) {\r\n var left = binFreqs[m], center = binFreqs[m + 1], right = binFreqs[m + 2];\r\n var startBin = Math.max(0, Math.ceil(left));\r\n var endBin = Math.min(numFftBins - 1, Math.floor(right));\r\n var weights = new Float32Array(endBin - startBin + 1);\r\n for (var k = startBin; k <= endBin; k++) {\r\n if (k <= center) {\r\n weights[k - startBin] = (center - left) > 0 ? (k - left) / (center - left) : 0;\r\n } else {\r\n weights[k - startBin] = (right - center) > 0 ? (right - k) / (right - center) : 0;\r\n }\r\n }\r\n filters.push({ startBin: startBin, weights: weights });\r\n }\r\n return filters;\r\n}\r\n\r\nfunction createHammingWindow(length) {\r\n var w = new Float32Array(length);\r\n for (var i = 0; i < length; i++) {\r\n w[i] = 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (length - 1));\r\n }\r\n return w;\r\n}\r\n\r\nfunction computeKaldiFbank(audio, sampleRate, numMelBins, opts) {\r\n var frameLengthMs = (opts && opts.frameLengthMs !== undefined) ? opts.frameLengthMs : 25;\r\n var frameShiftMs = (opts && opts.frameShiftMs !== undefined) ? opts.frameShiftMs : 10;\r\n var lowFreq = (opts && opts.lowFreq !== undefined) ? opts.lowFreq : 20;\r\n var highFreq = (opts && opts.highFreq !== undefined) ? opts.highFreq : (sampleRate / 2);\r\n var dither = (opts && opts.dither !== undefined) ? opts.dither : 0;\r\n var preemphasis = (opts && opts.preemphasis !== undefined) ? opts.preemphasis : 0.97;\r\n\r\n var frameLengthSamples = Math.round(sampleRate * frameLengthMs / 1000);\r\n var frameShiftSamples = Math.round(sampleRate * frameShiftMs / 1000);\r\n\r\n var scaled = new Float32Array(audio.length);\r\n for (var i = 0; i < audio.length; i++) { scaled[i] = audio[i] * 32768; }\r\n\r\n if (dither > 0) {\r\n for (var i = 0; i < scaled.length; i++) {\r\n var u1 = Math.random(), u2 = Math.random();\r\n scaled[i] += dither * Math.sqrt(-2 * Math.log(u1 + 1e-10)) * Math.cos(2 * Math.PI * u2);\r\n }\r\n }\r\n\r\n var numFrames = Math.max(0, Math.floor((scaled.length - frameLengthSamples) / frameShiftSamples) + 1);\r\n if (numFrames === 0) return new Float32Array(0);\r\n\r\n var fftSize = 1;\r\n while (fftSize < frameLengthSamples) fftSize *= 2;\r\n var numFftBins = fftSize / 2 + 1;\r\n\r\n var window = createHammingWindow(frameLengthSamples);\r\n var filters = buildMelFilterbank(numMelBins, fftSize, sampleRate, lowFreq, highFreq);\r\n var output = new Float32Array(numFrames * numMelBins);\r\n var fftRe = new Float64Array(fftSize);\r\n var fftIm = new Float64Array(fftSize);\r\n\r\n for (var f = 0; f < numFrames; f++) {\r\n var offset = f * frameShiftSamples;\r\n fftRe.fill(0); fftIm.fill(0);\r\n for (var i = 0; i < frameLengthSamples; i++) {\r\n var sample = scaled[offset + i];\r\n if (preemphasis > 0 && i > 0) {\r\n sample -= preemphasis * scaled[offset + i - 1];\r\n } else if (preemphasis > 0 && i === 0 && offset > 0) {\r\n sample -= preemphasis * scaled[offset - 1];\r\n }\r\n fftRe[i] = sample * window[i];\r\n }\r\n fft(fftRe, fftIm);\r\n var outOffset = f * numMelBins;\r\n for (var m = 0; m < numMelBins; m++) {\r\n var filter = filters[m];\r\n var energy = 0;\r\n for (var k = 0; k < filter.weights.length; k++) {\r\n var bin = filter.startBin + k;\r\n if (bin < numFftBins) {\r\n var powerSpec = fftRe[bin] * fftRe[bin] + fftIm[bin] * fftIm[bin];\r\n energy += filter.weights[k] * powerSpec;\r\n }\r\n }\r\n output[outOffset + m] = Math.log(Math.max(energy, 1e-10));\r\n }\r\n }\r\n return output;\r\n}\r\n\r\nfunction applyLFR(features, featureDim, lfrM, lfrN) {\r\n var numFrames = features.length / featureDim;\r\n if (numFrames === 0) return new Float32Array(0);\r\n var leftPad = Math.floor((lfrM - 1) / 2);\r\n var paddedLen = numFrames + leftPad;\r\n var numOutputFrames = Math.ceil(paddedLen / lfrN);\r\n var outputDim = featureDim * lfrM;\r\n var output = new Float32Array(numOutputFrames * outputDim);\r\n for (var i = 0; i < numOutputFrames; i++) {\r\n var startFrame = i * lfrN - leftPad;\r\n for (var j = 0; j < lfrM; j++) {\r\n var srcFrame = startFrame + j;\r\n if (srcFrame < 0) srcFrame = 0;\r\n if (srcFrame >= numFrames) srcFrame = numFrames - 1;\r\n var srcOffset = srcFrame * featureDim;\r\n var dstOffset = i * outputDim + j * featureDim;\r\n for (var k = 0; k < featureDim; k++) {\r\n output[dstOffset + k] = features[srcOffset + k];\r\n }\r\n }\r\n }\r\n return output;\r\n}\r\n\r\nfunction applyCMVN(features, dim, negMeanVec, invStddevVec) {\r\n for (var i = 0; i < features.length; i++) {\r\n var d = i % dim;\r\n features[i] = (features[i] + negMeanVec[d]) * invStddevVec[d];\r\n }\r\n return features;\r\n}\r\n\r\nfunction parseCMVNFromMetadata(negMeanStr, invStddevStr) {\r\n var negMeanArr = new Float32Array(\r\n negMeanStr.split(',').map(function(s) { return parseFloat(s.trim()); })\r\n );\r\n var invStddevArr = new Float32Array(\r\n invStddevStr.split(',').map(function(s) { return parseFloat(s.trim()); })\r\n );\r\n return { negMean: negMeanArr, invStddev: invStddevArr };\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// ctcDecoder.ts — inlined as plain JavaScript\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nvar LANGUAGE_IDS = { 0: 'auto', 3: 'zh', 4: 'en', 7: 'yue', 11: 'ja', 12: 'ko', 13: 'nospeech' };\r\nvar TEXT_NORM_IDS = { 14: 'with_itn', 15: 'without_itn' };\r\n\r\nfunction resolveLanguageIdW(language) {\r\n var map = { auto: 0, zh: 3, en: 4, yue: 7, ja: 11, ko: 12 };\r\n return map[language] !== undefined ? map[language] : 0;\r\n}\r\n\r\nfunction resolveTextNormIdW(textNorm) {\r\n return textNorm === 'without_itn' ? 15 : 14;\r\n}\r\n\r\nfunction parseTokensFile(content) {\r\n var map = new Map();\r\n var lines = content.split('\\\\n');\r\n for (var idx = 0; idx < lines.length; idx++) {\r\n var trimmed = lines[idx].trim();\r\n if (!trimmed) continue;\r\n var lastSpace = trimmed.lastIndexOf(' ');\r\n if (lastSpace === -1) continue;\r\n var token = trimmed.substring(0, lastSpace);\r\n var id = parseInt(trimmed.substring(lastSpace + 1), 10);\r\n if (!isNaN(id)) map.set(id, token);\r\n }\r\n return map;\r\n}\r\n\r\nfunction parseStructuredToken(token) {\r\n // NOTE: Double-escape required — this code lives inside a JS template literal.\r\n // \\\\| in source becomes \\| in the worker (literal pipe). Single \\| becomes | (alternation).\r\n var match = token.match(/^<\\\\|(.+)\\\\|>$/);\r\n if (!match) return null;\r\n var value = match[1];\r\n if (value === 'zh' || value === 'en' || value === 'ja' || value === 'ko' || value === 'yue' || value === 'nospeech') {\r\n return { type: 'language', value: value };\r\n }\r\n var emotions = ['HAPPY', 'SAD', 'ANGRY', 'NEUTRAL', 'FEARFUL', 'DISGUSTED', 'SURPRISED', 'EMO_UNKNOWN'];\r\n if (emotions.indexOf(value) !== -1) return { type: 'emotion', value: value };\r\n var events = ['Speech', 'BGM', 'Applause', 'Laughter', 'Crying', 'Coughing', 'Sneezing', 'EVENT_UNKNOWN'];\r\n if (events.indexOf(value) !== -1) return { type: 'event', value: value };\r\n if (value === 'withitn' || value === 'woitn' || value === 'with_itn' || value === 'without_itn') {\r\n return { type: 'textnorm', value: value };\r\n }\r\n return null;\r\n}\r\n\r\nfunction ctcGreedyDecode(logits, seqLen, vocabSz, tokenMapLocal) {\r\n var tokenIds = [];\r\n for (var t = 0; t < seqLen; t++) {\r\n var offset = t * vocabSz;\r\n var maxIdx = 0, maxVal = logits[offset];\r\n for (var v = 1; v < vocabSz; v++) {\r\n if (logits[offset + v] > maxVal) { maxVal = logits[offset + v]; maxIdx = v; }\r\n }\r\n tokenIds.push(maxIdx);\r\n }\r\n var collapsed = [], prev = -1;\r\n for (var idx = 0; idx < tokenIds.length; idx++) {\r\n var id = tokenIds[idx];\r\n if (id !== prev) { collapsed.push(id); prev = id; }\r\n }\r\n var filtered = collapsed.filter(function(id) { return id !== 0 && id !== 1 && id !== 2; });\r\n var language = undefined, emotion = undefined, event = undefined;\r\n var textTokens = [];\r\n for (var idx = 0; idx < filtered.length; idx++) {\r\n var id = filtered[idx];\r\n var token = tokenMapLocal.get(id);\r\n if (!token) continue;\r\n var structured = parseStructuredToken(token);\r\n if (structured) {\r\n if (structured.type === 'language') language = structured.value;\r\n else if (structured.type === 'emotion') emotion = structured.value;\r\n else if (structured.type === 'event') event = structured.value;\r\n } else {\r\n textTokens.push(token);\r\n }\r\n }\r\n var text = textTokens.join('');\r\n // NOTE: Double-escape required — template literal context. \\\\u2581 produces \\u2581 in worker.\r\n text = text.replace(/\\\\u2581/g, ' ').trim();\r\n return { text: text, language: language, emotion: emotion, event: event };\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// blendshapeUtils.ts — inlined\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nvar SYMMETRIC_INDEX_PAIRS = [\r\n [23, 25], [32, 38], [43, 44], [29, 30], [27, 28], [45, 46],\r\n [35, 36], [47, 48], [33, 34], [49, 50], [6, 7], [0, 1],\r\n [3, 4], [8, 9], [16, 17], [10, 11], [12, 13], [14, 15],\r\n [18, 19], [20, 21],\r\n];\r\n\r\nfunction symmetrizeBlendshapes(frame) {\r\n var result = new Float32Array(frame);\r\n for (var p = 0; p < SYMMETRIC_INDEX_PAIRS.length; p++) {\r\n var lIdx = SYMMETRIC_INDEX_PAIRS[p][0], rIdx = SYMMETRIC_INDEX_PAIRS[p][1];\r\n var avg = (frame[lIdx] + frame[rIdx]) / 2;\r\n result[lIdx] = avg;\r\n result[rIdx] = avg;\r\n }\r\n return result;\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Shared ORT loader (WebGPU detection + fallback to WASM)\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nvar workerBackend = 'wasm';\r\n\r\nasync function loadOrt(wasmPaths, isIOSDevice) {\r\n if (ort) return;\r\n\r\n // Detect WebGPU in worker (Chrome/Edge 113+, not iOS/Safari)\r\n // Safari (macOS + iOS) must use WASM — the JSEP/ASYNCIFY binary in\r\n // ort.webgpu.min.js crashes WebKit's JIT compiler.\r\n var isSafariWorker = typeof navigator !== 'undefined' && /safari/i.test(navigator.userAgent) && !/chrome|crios|fxios|chromium|edg/i.test(navigator.userAgent);\r\n var hasWebGPU = false;\r\n var webgpuReason = '';\r\n if (isIOSDevice) {\r\n webgpuReason = 'iOS device';\r\n } else if (isSafariWorker) {\r\n webgpuReason = 'Safari (JSEP/ASYNCIFY crash)';\r\n } else if (typeof navigator === 'undefined' || !navigator.gpu) {\r\n webgpuReason = 'navigator.gpu unavailable';\r\n } else {\r\n try {\r\n var adapter = await navigator.gpu.requestAdapter();\r\n if (adapter) {\r\n hasWebGPU = true;\r\n } else {\r\n webgpuReason = 'requestAdapter returned null';\r\n }\r\n } catch (e) {\r\n webgpuReason = 'requestAdapter failed: ' + String(e);\r\n }\r\n }\r\n if (!hasWebGPU && webgpuReason) {\r\n console.warn('[UnifiedWorker] WebGPU unavailable: ' + webgpuReason + ', falling back to WASM');\r\n }\r\n\r\n var ortUrl;\r\n if (hasWebGPU) {\r\n ortUrl = wasmPaths + 'ort.webgpu.min.js';\r\n workerBackend = 'webgpu';\r\n } else {\r\n ortUrl = wasmPaths + 'ort.wasm.min.js';\r\n workerBackend = 'wasm';\r\n }\r\n\r\n var response = await fetchWithTimeout(ortUrl, 30000);\r\n var scriptText = await response.text();\r\n // SEC-01: Validate CDN response is JavaScript, not an HTML error page\r\n var trimmed = scriptText.trimStart();\r\n if (trimmed.startsWith('<!') || trimmed.startsWith('<html') || trimmed.startsWith('<HTML')) {\r\n throw new Error('CDN returned HTML instead of JavaScript — check ORT CDN URL: ' + ortUrl);\r\n }\r\n var blob = new Blob([scriptText], { type: 'application/javascript' });\r\n var blobUrl = URL.createObjectURL(blob);\r\n importScripts(blobUrl);\r\n URL.revokeObjectURL(blobUrl);\r\n ort = self.ort;\r\n ort.env.wasm.wasmPaths = wasmPaths;\r\n ort.env.wasm.numThreads = isIOSDevice ? 1 : 4;\r\n ort.env.wasm.simd = true;\r\n ort.env.wasm.proxy = false;\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// SenseVoice handlers\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nasync function svLoad(msg) {\r\n var tokensResponse = await fetchWithTimeout(msg.tokensUrl, 30000);\r\n if (!tokensResponse.ok) throw new Error('Failed to fetch tokens.txt: ' + tokensResponse.status);\r\n var tokensText = await tokensResponse.text();\r\n svTokenMap = parseTokensFile(tokensText);\r\n svLanguageId = msg.language;\r\n svTextNormId = msg.textNorm;\r\n\r\n var sessionOptions = { executionProviders: ['wasm'], graphOptimizationLevel: 'all' };\r\n if (msg.isIOS) {\r\n svSession = await ort.InferenceSession.create(msg.modelUrl, sessionOptions);\r\n } else {\r\n var modelResponse = await fetchWithTimeout(msg.modelUrl, 120000);\r\n if (!modelResponse.ok) throw new Error('Failed to fetch model: ' + modelResponse.status);\r\n var modelBuffer = await modelResponse.arrayBuffer();\r\n svSession = await ort.InferenceSession.create(new Uint8Array(modelBuffer), sessionOptions);\r\n }\r\n\r\n try {\r\n var metadata = svSession.handler && svSession.handler.metadata;\r\n if (metadata && metadata.neg_mean && metadata.inv_stddev) {\r\n var cmvn = parseCMVNFromMetadata(metadata.neg_mean, metadata.inv_stddev);\r\n svNegMean = cmvn.negMean;\r\n svInvStddev = cmvn.invStddev;\r\n }\r\n } catch (e) { /* CMVN not available */ }\r\n\r\n svVocabSize = 0;\r\n svTokenMap.forEach(function(val, key) { if (key >= svVocabSize) svVocabSize = key + 1; });\r\n\r\n return {\r\n vocabSize: svVocabSize,\r\n inputNames: svSession.inputNames.slice(),\r\n outputNames: svSession.outputNames.slice(),\r\n };\r\n}\r\n\r\nasync function svTranscribe(audio) {\r\n var preprocessStart = performance.now();\r\n var fbank = computeKaldiFbank(audio, 16000, 80);\r\n var numFrames = fbank.length / 80;\r\n if (numFrames === 0) {\r\n return { text: '', inferenceTimeMs: performance.now() - preprocessStart, preprocessTimeMs: performance.now() - preprocessStart };\r\n }\r\n var lfrFeatures = applyLFR(fbank, 80, 7, 6);\r\n var numLfrFrames = lfrFeatures.length / 560;\r\n if (svNegMean && svInvStddev) applyCMVN(lfrFeatures, 560, svNegMean, svInvStddev);\r\n var preprocessTimeMs = performance.now() - preprocessStart;\r\n\r\n var feeds = {\r\n x: new ort.Tensor('float32', lfrFeatures, [1, numLfrFrames, 560]),\r\n x_length: new ort.Tensor('int32', new Int32Array([numLfrFrames]), [1]),\r\n language: new ort.Tensor('int32', new Int32Array([svLanguageId]), [1]),\r\n text_norm: new ort.Tensor('int32', new Int32Array([svTextNormId]), [1]),\r\n };\r\n var results = await svSession.run(feeds);\r\n var logitsOutput = results['logits'];\r\n if (!logitsOutput) throw new Error('Model output missing \"logits\" tensor');\r\n\r\n var decoded = ctcGreedyDecode(logitsOutput.data, logitsOutput.dims[1], logitsOutput.dims[2], svTokenMap);\r\n var totalTimeMs = performance.now() - preprocessStart;\r\n\r\n return {\r\n text: decoded.text, language: decoded.language, emotion: decoded.emotion, event: decoded.event,\r\n inferenceTimeMs: totalTimeMs, preprocessTimeMs: preprocessTimeMs,\r\n };\r\n}\r\n\r\nasync function svDispose() {\r\n if (svSession) { await svSession.release(); svSession = null; }\r\n svTokenMap = null; svNegMean = null; svInvStddev = null;\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// LAM (A2E) handlers\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nasync function lamLoad(msg) {\r\n var useWebGPU = workerBackend === 'webgpu';\r\n var sessionOptions;\r\n if (msg.isIOS) {\r\n sessionOptions = {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: 'basic',\r\n enableCpuMemArena: false,\r\n enableMemPattern: false,\r\n };\r\n } else {\r\n sessionOptions = {\r\n executionProviders: useWebGPU ? ['webgpu', 'wasm'] : ['wasm'],\r\n graphOptimizationLevel: 'all',\r\n };\r\n }\r\n var dataFilename = msg.externalDataUrl ? msg.externalDataUrl.split('/').pop() : null;\r\n\r\n if (msg.isIOS) {\r\n // iOS: pass URLs directly to ORT (avoids JS heap OOM for large external data)\r\n if (msg.externalDataUrl && dataFilename) {\r\n sessionOptions.externalData = [{ path: dataFilename, data: msg.externalDataUrl }];\r\n }\r\n lamSession = await ort.InferenceSession.create(msg.modelUrl, sessionOptions);\r\n } else if (msg.externalDataUrl && dataFilename) {\r\n // URL pass-through: ORT fetches 192MB weights directly into WASM/GPU memory.\r\n // Avoids ~384MB JS heap spike. Browser HTTP cache handles fast reloads.\r\n sessionOptions.externalData = [{ path: dataFilename, data: msg.externalDataUrl }];\r\n lamSession = await ort.InferenceSession.create(msg.modelUrl, sessionOptions);\r\n } else {\r\n var graphResponse = await fetchWithTimeout(msg.modelUrl, 120000);\r\n if (!graphResponse.ok) throw new Error('Failed to fetch LAM graph: ' + graphResponse.status);\r\n var graphBuffer = await graphResponse.arrayBuffer();\r\n lamSession = await ort.InferenceSession.create(new Uint8Array(graphBuffer), sessionOptions);\r\n }\r\n\r\n lamNumIdentityClasses = msg.numIdentityClasses || 12;\r\n\r\n // Warmup\r\n var warmupAudio = new Float32Array(16000);\r\n var identity = new Float32Array(lamNumIdentityClasses);\r\n identity[0] = 1.0;\r\n var audioTensor = new ort.Tensor('float32', warmupAudio, [1, 16000]);\r\n var identityTensor = new ort.Tensor('float32', identity, [1, lamNumIdentityClasses]);\r\n await lamSession.run({ audio: audioTensor, identity: identityTensor });\r\n\r\n return {\r\n inputNames: lamSession.inputNames.slice(),\r\n outputNames: lamSession.outputNames.slice(),\r\n backend: workerBackend,\r\n };\r\n}\r\n\r\nasync function lamInfer(audio, identityIndex) {\r\n var audioTensor = new ort.Tensor('float32', audio, [1, audio.length]);\r\n var identity = new Float32Array(lamNumIdentityClasses);\r\n identity[identityIndex || 0] = 1.0;\r\n var identityTensor = new ort.Tensor('float32', identity, [1, lamNumIdentityClasses]);\r\n\r\n var results = await lamSession.run({ audio: audioTensor, identity: identityTensor });\r\n var blendshapeOutput = results['blendshapes'];\r\n if (!blendshapeOutput) throw new Error('Missing blendshapes output from LAM model');\r\n\r\n var blendshapeData = blendshapeOutput.data;\r\n var numFrames = blendshapeOutput.dims[1];\r\n var numBlendshapes = blendshapeOutput.dims[2];\r\n\r\n var flatBuffer = new Float32Array(numFrames * numBlendshapes);\r\n for (var f = 0; f < numFrames; f++) {\r\n var offset = f * numBlendshapes;\r\n var rawFrame = blendshapeData.slice(offset, offset + numBlendshapes);\r\n var symmetrized = symmetrizeBlendshapes(rawFrame);\r\n flatBuffer.set(symmetrized, offset);\r\n }\r\n return { flatBuffer: flatBuffer, numFrames: numFrames, numBlendshapes: numBlendshapes };\r\n}\r\n\r\nasync function lamDispose() {\r\n if (lamSession) { await lamSession.release(); lamSession = null; }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Kokoro TTS handlers\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nasync function kokoroLoad(msg) {\r\n // Kokoro's mixed-precision transformer graph OOMs with 'all' optimization (~1000MB WASM peak).\r\n // 'basic' is sufficient — WebGPU inference speed comes from GPU parallelism, not graph fusion.\r\n var sessionOptions = { executionProviders: ['wasm'], graphOptimizationLevel: 'basic' };\r\n\r\n var url = msg.modelUrl;\r\n var externalDataUrl = msg.externalDataUrl || null;\r\n var dataFilename = externalDataUrl ? externalDataUrl.split('/').pop() : null;\r\n\r\n if (externalDataUrl && dataFilename) {\r\n // URL pass-through: ORT fetches weights directly into WASM/GPU memory.\r\n // Avoids ~312MB JS heap spike. Browser HTTP cache handles fast reloads.\r\n sessionOptions.externalData = [{ path: dataFilename, data: externalDataUrl }];\r\n kokoroSession = await ort.InferenceSession.create(url, sessionOptions);\r\n } else if (msg.isIOS) {\r\n kokoroSession = await ort.InferenceSession.create(url, sessionOptions);\r\n } else {\r\n var response = await fetchWithTimeout(url, 120000);\r\n if (!response.ok) throw new Error('Failed to fetch Kokoro model: ' + response.status);\r\n var modelBuffer = await response.arrayBuffer();\r\n kokoroSession = await ort.InferenceSession.create(new Uint8Array(modelBuffer), sessionOptions);\r\n }\r\n\r\n return { ok: true };\r\n}\r\n\r\nasync function kokoroInfer(tokens, style, speed) {\r\n var inputIds;\r\n try {\r\n inputIds = new BigInt64Array(tokens.length);\r\n for (var i = 0; i < tokens.length; i++) {\r\n inputIds[i] = BigInt(tokens[i]);\r\n }\r\n } catch (e) {\r\n inputIds = tokens.map(function(t) { return BigInt(t); });\r\n }\r\n\r\n var feeds = {\r\n input_ids: new ort.Tensor('int64', inputIds, [1, tokens.length]),\r\n style: new ort.Tensor('float32', new Float32Array(style), [1, style.length]),\r\n speed: new ort.Tensor('float32', new Float32Array([speed]), [1]),\r\n };\r\n\r\n var results = await kokoroSession.run(feeds);\r\n var outputNames = Object.keys(results);\r\n if (outputNames.length === 0) throw new Error('KokoroTTS model returned no outputs');\r\n\r\n var output = results[outputNames[0]];\r\n return new Float32Array(output.data);\r\n}\r\n\r\nasync function kokoroDispose() {\r\n if (kokoroSession) { await kokoroSession.release(); kokoroSession = null; }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Silero VAD handlers\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nasync function vadLoad(msg) {\r\n vadSampleRate = msg.sampleRate;\r\n vadChunkSize = vadSampleRate === 16000 ? 512 : 256;\r\n vadContextSize = vadSampleRate === 16000 ? 64 : 32;\r\n\r\n var response = await fetchWithTimeout(msg.modelUrl, 60000);\r\n if (!response.ok) throw new Error('Failed to fetch VAD model: ' + response.status);\r\n var modelBuffer = await response.arrayBuffer();\r\n vadSession = await ort.InferenceSession.create(new Uint8Array(modelBuffer), {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: 'all',\r\n });\r\n\r\n return {\r\n inputNames: vadSession.inputNames.slice(),\r\n outputNames: vadSession.outputNames.slice(),\r\n };\r\n}\r\n\r\nasync function vadProcess(audio, state, context) {\r\n var inputSize = vadContextSize + vadChunkSize;\r\n var inputBuffer = new Float32Array(inputSize);\r\n inputBuffer.set(context, 0);\r\n inputBuffer.set(audio, vadContextSize);\r\n\r\n var inputTensor = new ort.Tensor('float32', new Float32Array(inputBuffer), [1, inputSize]);\r\n var stateTensor = new ort.Tensor('float32', new Float32Array(state), [2, 1, 128]);\r\n var srTensor;\r\n try {\r\n srTensor = new ort.Tensor('int64', new BigInt64Array([BigInt(vadSampleRate)]), []);\r\n } catch (e) {\r\n srTensor = new ort.Tensor('int64', [BigInt(vadSampleRate)], []);\r\n }\r\n\r\n var feeds = { 'input': inputTensor, 'state': stateTensor, 'sr': srTensor };\r\n var results = await vadSession.run(feeds);\r\n var outputTensor = results['output'];\r\n var newStateTensor = results['stateN'] || results['state'];\r\n if (!outputTensor) throw new Error('Missing output tensor from VAD model');\r\n\r\n return { probability: outputTensor.data[0], newState: new Float32Array(newStateTensor.data) };\r\n}\r\n\r\nfunction vadCreateInitialState() {\r\n return new Float32Array(2 * 1 * 128);\r\n}\r\n\r\nasync function vadDispose() {\r\n if (vadSession) { await vadSession.release(); vadSession = null; }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Message handler — serialized queue prevents concurrent ORT operations\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nvar messageQueue = Promise.resolve();\r\n\r\nself.onmessage = function(e) {\r\n messageQueue = messageQueue.then(function() {\r\n return handleMessage(e);\r\n });\r\n};\r\n\r\nasync function handleMessage(e) {\r\n var msg = e.data;\r\n var requestId = msg.requestId;\r\n\r\n try {\r\n switch (msg.type) {\r\n case 'init': {\r\n const startTime = performance.now();\r\n await loadOrt(msg.wasmPaths, msg.isIOS);\r\n self.postMessage({ type: 'init:done', requestId: requestId, loadTimeMs: performance.now() - startTime, backend: workerBackend });\r\n break;\r\n }\r\n\r\n case 'sv:load': {\r\n const startTime = performance.now();\r\n var info = await svLoad(msg);\r\n self.postMessage({\r\n type: 'sv:loaded', requestId: requestId, vocabSize: info.vocabSize,\r\n inputNames: info.inputNames, outputNames: info.outputNames,\r\n loadTimeMs: performance.now() - startTime,\r\n });\r\n break;\r\n }\r\n\r\n case 'sv:transcribe': {\r\n var result = await svTranscribe(msg.audio);\r\n self.postMessage({\r\n type: 'sv:result', requestId: requestId,\r\n text: result.text, language: result.language, emotion: result.emotion, event: result.event,\r\n inferenceTimeMs: result.inferenceTimeMs, preprocessTimeMs: result.preprocessTimeMs,\r\n });\r\n break;\r\n }\r\n\r\n case 'sv:dispose': {\r\n await svDispose();\r\n self.postMessage({ type: 'sv:disposed', requestId: requestId });\r\n break;\r\n }\r\n\r\n case 'vad:load': {\r\n const startTime = performance.now();\r\n var info = await vadLoad(msg);\r\n self.postMessage({\r\n type: 'vad:loaded', requestId: requestId,\r\n inputNames: info.inputNames, outputNames: info.outputNames,\r\n loadTimeMs: performance.now() - startTime,\r\n });\r\n break;\r\n }\r\n\r\n case 'vad:process': {\r\n const startTime = performance.now();\r\n var result = await vadProcess(msg.audio, msg.state, msg.context);\r\n self.postMessage({\r\n type: 'vad:result', requestId: requestId,\r\n probability: result.probability, state: result.newState,\r\n inferenceTimeMs: performance.now() - startTime,\r\n });\r\n break;\r\n }\r\n\r\n case 'vad:reset': {\r\n var state = vadCreateInitialState();\r\n self.postMessage({ type: 'vad:reset', requestId: requestId, state: state });\r\n break;\r\n }\r\n\r\n case 'vad:dispose': {\r\n await vadDispose();\r\n self.postMessage({ type: 'vad:disposed', requestId: requestId });\r\n break;\r\n }\r\n\r\n case 'lam:load': {\r\n const startTime = performance.now();\r\n var info = await lamLoad(msg);\r\n self.postMessage({\r\n type: 'lam:loaded', requestId: requestId,\r\n inputNames: info.inputNames, outputNames: info.outputNames,\r\n backend: info.backend,\r\n loadTimeMs: performance.now() - startTime,\r\n });\r\n break;\r\n }\r\n\r\n case 'lam:infer': {\r\n const startTime = performance.now();\r\n var result = await lamInfer(msg.audio, msg.identityIndex);\r\n var inferenceTimeMs = performance.now() - startTime;\r\n self.postMessage({\r\n type: 'lam:result', requestId: requestId,\r\n blendshapes: result.flatBuffer, numFrames: result.numFrames,\r\n numBlendshapes: result.numBlendshapes, inferenceTimeMs: inferenceTimeMs,\r\n }, [result.flatBuffer.buffer]);\r\n break;\r\n }\r\n\r\n case 'lam:dispose': {\r\n await lamDispose();\r\n self.postMessage({ type: 'lam:disposed', requestId: requestId });\r\n break;\r\n }\r\n\r\n case 'kokoro:load': {\r\n const startTime = performance.now();\r\n await kokoroLoad(msg);\r\n self.postMessage({\r\n type: 'kokoro:loaded', requestId: requestId,\r\n loadTimeMs: performance.now() - startTime,\r\n });\r\n break;\r\n }\r\n\r\n case 'kokoro:infer': {\r\n const startTime = performance.now();\r\n var audio = await kokoroInfer(msg.tokens, msg.style, msg.speed);\r\n var inferenceTimeMs = performance.now() - startTime;\r\n self.postMessage({\r\n type: 'kokoro:result', requestId: requestId,\r\n audio: audio, inferenceTimeMs: inferenceTimeMs,\r\n }, [audio.buffer]);\r\n break;\r\n }\r\n\r\n case 'kokoro:dispose': {\r\n await kokoroDispose();\r\n self.postMessage({ type: 'kokoro:disposed', requestId: requestId });\r\n break;\r\n }\r\n\r\n case 'ping': {\r\n self.postMessage({ type: 'pong', requestId: requestId });\r\n break;\r\n }\r\n\r\n case 'dispose-all': {\r\n await svDispose();\r\n await lamDispose();\r\n await kokoroDispose();\r\n await vadDispose();\r\n ort = null;\r\n self.postMessage({ type: 'dispose-all:done', requestId: requestId });\r\n break;\r\n }\r\n\r\n default:\r\n self.postMessage({ type: 'error', requestId: requestId, error: 'Unknown message type: ' + msg.type });\r\n }\r\n } catch (err) {\r\n var errorMsg = err.message || String(err);\r\n if (typeof err === 'number') {\r\n errorMsg = 'Raw C++ exception pointer (0x' + err.toString(16) + '). Likely OOM in WASM.';\r\n }\r\n self.postMessage({ type: 'error', requestId: requestId, error: errorMsg });\r\n }\r\n};\r\n\r\nself.onerror = function(err) {\r\n self.postMessage({ type: 'error', requestId: null, error: 'Worker error: ' + (err.message || String(err)) });\r\n};\r\n`;\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Main Thread: UnifiedInferenceWorker\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\n/**\r\n * Unified Inference Worker — single Web Worker for all ONNX models\r\n *\r\n * Hosts SenseVoice, A2E (LAM), Kokoro TTS, and Silero VAD in one ORT instance.\r\n * Uses WebGPU on Chrome/Edge 113+, falls back to WASM on Safari/iOS/Firefox.\r\n * All model loading and inference runs off the main thread.\r\n */\r\nexport class UnifiedInferenceWorker {\r\n private worker: Worker | null = null;\r\n private pendingRequests = new Map<string, {\r\n resolve: (value: unknown) => void;\r\n reject: (error: Error) => void;\r\n timeout: ReturnType<typeof setTimeout>;\r\n }>();\r\n private initialized = false;\r\n private healthState: WorkerHealthState = 'healthy';\r\n private consecutiveFailures = 0;\r\n private _generation = 0;\r\n private recovering = false;\r\n private _workerBackend: 'wasm' | 'webgpu' = 'wasm';\r\n\r\n /**\r\n * Initialize the worker (load ORT WASM from CDN)\r\n */\r\n async init(): Promise<void> {\r\n if (this.initialized) return;\r\n\r\n const startTime = performance.now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('UnifiedInferenceWorker.init');\r\n\r\n try {\r\n logger.info('Creating unified inference worker...');\r\n this.worker = this.createWorker();\r\n\r\n const result = await this.sendMessage<{ loadTimeMs: number; backend: string }>(\r\n { type: 'init', wasmPaths: WASM_CDN_PATH, isIOS: isIOS() },\r\n 'init:done',\r\n INIT_TIMEOUT_MS,\r\n );\r\n\r\n this._workerBackend = result.backend === 'webgpu' ? 'webgpu' : 'wasm';\r\n this.initialized = true;\r\n const loadTimeMs = performance.now() - startTime;\r\n logger.info('Unified worker initialized', { loadTimeMs: Math.round(loadTimeMs), backend: this._workerBackend });\r\n\r\n span?.setAttributes({ 'worker.init_time_ms': loadTimeMs, 'worker.backend': this._workerBackend });\r\n span?.end();\r\n } catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n const isTimeout = err.message.includes('timed out');\r\n if (isTimeout) {\r\n logger.error('Worker init timed out', { code: ErrorCodes.INF_TIMEOUT, timeoutMs: INIT_TIMEOUT_MS });\r\n }\r\n span?.endWithError(err);\r\n this.cleanup();\r\n throw error;\r\n }\r\n }\r\n\r\n // ── SenseVoice ────────────────────────────────────────────────────────\r\n\r\n async loadSenseVoice(config: {\r\n modelUrl: string;\r\n tokensUrl: string;\r\n language: number;\r\n textNorm: number;\r\n }): Promise<SenseVoiceModelInfo> {\r\n this.assertReady();\r\n\r\n const startTime = performance.now();\r\n const result = await this.sendMessage<{\r\n vocabSize: number;\r\n inputNames: string[];\r\n outputNames: string[];\r\n loadTimeMs: number;\r\n }>(\r\n {\r\n type: 'sv:load',\r\n modelUrl: resolveUrl(config.modelUrl),\r\n tokensUrl: resolveUrl(config.tokensUrl),\r\n isIOS: isIOS(),\r\n language: config.language,\r\n textNorm: config.textNorm,\r\n },\r\n 'sv:loaded',\r\n SV_LOAD_TIMEOUT_MS,\r\n );\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n return {\r\n backend: 'wasm',\r\n loadTimeMs,\r\n inputNames: result.inputNames,\r\n outputNames: result.outputNames,\r\n vocabSize: result.vocabSize,\r\n };\r\n }\r\n\r\n async transcribe(audio: Float32Array): Promise<SenseVoiceResult> {\r\n this.assertReady();\r\n\r\n const result = await this.sendMessage<{\r\n text: string;\r\n language?: string;\r\n emotion?: string;\r\n event?: string;\r\n inferenceTimeMs: number;\r\n preprocessTimeMs: number;\r\n }>(\r\n { type: 'sv:transcribe', audio },\r\n 'sv:result',\r\n SV_INFER_TIMEOUT_MS,\r\n );\r\n\r\n return {\r\n text: result.text,\r\n language: result.language,\r\n emotion: result.emotion,\r\n event: result.event,\r\n inferenceTimeMs: result.inferenceTimeMs,\r\n preprocessTimeMs: result.preprocessTimeMs,\r\n };\r\n }\r\n\r\n async disposeSenseVoice(): Promise<void> {\r\n if (!this.worker) return;\r\n await this.sendMessage({ type: 'sv:dispose' }, 'sv:disposed', DISPOSE_TIMEOUT_MS);\r\n }\r\n\r\n // ── LAM (A2E) ──────────────────────────────────────────────────────\r\n\r\n async loadLAM(config: {\r\n modelUrl: string;\r\n externalDataUrl: string | null;\r\n numIdentityClasses?: number;\r\n }): Promise<A2EModelInfo> {\r\n this.assertReady();\r\n\r\n const startTime = performance.now();\r\n const result = await this.sendMessage<{\r\n inputNames: string[];\r\n outputNames: string[];\r\n backend: string;\r\n loadTimeMs: number;\r\n }>(\r\n {\r\n type: 'lam:load',\r\n modelUrl: resolveUrl(config.modelUrl),\r\n externalDataUrl: config.externalDataUrl ? resolveUrl(config.externalDataUrl) : null,\r\n isIOS: isIOS(),\r\n numIdentityClasses: config.numIdentityClasses ?? 12,\r\n },\r\n 'lam:loaded',\r\n LAM_LOAD_TIMEOUT_MS,\r\n );\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n return {\r\n backend: (result.backend === 'webgpu' ? 'webgpu' : 'wasm') as 'webgpu' | 'wasm',\r\n loadTimeMs,\r\n inputNames: result.inputNames,\r\n outputNames: result.outputNames,\r\n };\r\n }\r\n\r\n async inferLAM(audio: Float32Array, identityIndex?: number): Promise<{\r\n blendshapes: Float32Array;\r\n numFrames: number;\r\n numBlendshapes: number;\r\n inferenceTimeMs: number;\r\n }> {\r\n this.assertReady();\r\n\r\n return this.sendMessage(\r\n { type: 'lam:infer', audio, identityIndex: identityIndex ?? 0 },\r\n 'lam:result',\r\n LAM_INFER_TIMEOUT_MS,\r\n );\r\n }\r\n\r\n async disposeLAM(): Promise<void> {\r\n if (!this.worker) return;\r\n await this.sendMessage({ type: 'lam:dispose' }, 'lam:disposed', DISPOSE_TIMEOUT_MS);\r\n }\r\n\r\n // ── Kokoro TTS ──────────────────────────────────────────────────────\r\n\r\n async loadKokoro(config: {\r\n modelUrl: string;\r\n }): Promise<{ loadTimeMs: number }> {\r\n this.assertReady();\r\n\r\n const startTime = performance.now();\r\n const result = await this.sendMessage<{\r\n loadTimeMs: number;\r\n }>(\r\n {\r\n type: 'kokoro:load',\r\n modelUrl: resolveUrl(config.modelUrl),\r\n isIOS: isIOS(),\r\n },\r\n 'kokoro:loaded',\r\n KOKORO_LOAD_TIMEOUT_MS,\r\n );\r\n\r\n return { loadTimeMs: performance.now() - startTime };\r\n }\r\n\r\n async inferKokoro(tokens: number[], style: Float32Array, speed: number): Promise<{\r\n audio: Float32Array;\r\n inferenceTimeMs: number;\r\n }> {\r\n this.assertReady();\r\n\r\n return this.sendMessage(\r\n { type: 'kokoro:infer', tokens, style, speed },\r\n 'kokoro:result',\r\n KOKORO_INFER_TIMEOUT_MS,\r\n );\r\n }\r\n\r\n async disposeKokoro(): Promise<void> {\r\n if (!this.worker) return;\r\n await this.sendMessage({ type: 'kokoro:dispose' }, 'kokoro:disposed', DISPOSE_TIMEOUT_MS);\r\n }\r\n\r\n // ── Silero VAD ────────────────────────────────────────────────────────\r\n\r\n async loadVAD(config: {\r\n modelUrl: string;\r\n sampleRate: number;\r\n }): Promise<VADWorkerModelInfo> {\r\n this.assertReady();\r\n\r\n const startTime = performance.now();\r\n const chunkSize = config.sampleRate === 16000 ? 512 : 256;\r\n const result = await this.sendMessage<{\r\n inputNames: string[];\r\n outputNames: string[];\r\n loadTimeMs: number;\r\n }>(\r\n {\r\n type: 'vad:load',\r\n modelUrl: resolveUrl(config.modelUrl),\r\n sampleRate: config.sampleRate,\r\n },\r\n 'vad:loaded',\r\n VAD_LOAD_TIMEOUT_MS,\r\n );\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n return {\r\n backend: 'wasm',\r\n loadTimeMs,\r\n inputNames: result.inputNames,\r\n outputNames: result.outputNames,\r\n sampleRate: config.sampleRate,\r\n chunkSize,\r\n };\r\n }\r\n\r\n async processVAD(\r\n audio: Float32Array,\r\n state: Float32Array,\r\n context: Float32Array,\r\n ): Promise<{ probability: number; state: Float32Array; inferenceTimeMs: number }> {\r\n this.assertReady();\r\n\r\n return this.sendMessage(\r\n { type: 'vad:process', audio, state, context },\r\n 'vad:result',\r\n VAD_INFER_TIMEOUT_MS,\r\n );\r\n }\r\n\r\n async resetVAD(): Promise<Float32Array> {\r\n this.assertReady();\r\n\r\n const result = await this.sendMessage<{ state: Float32Array }>(\r\n { type: 'vad:reset' },\r\n 'vad:reset',\r\n VAD_INFER_TIMEOUT_MS,\r\n );\r\n return result.state;\r\n }\r\n\r\n async disposeVAD(): Promise<void> {\r\n if (!this.worker) return;\r\n await this.sendMessage({ type: 'vad:dispose' }, 'vad:disposed', DISPOSE_TIMEOUT_MS);\r\n }\r\n\r\n // ── Lifecycle ─────────────────────────────────────────────────────────\r\n\r\n async dispose(): Promise<void> {\r\n logger.debug('Disposed');\r\n if (this.worker) {\r\n try {\r\n await this.sendMessage({ type: 'dispose-all' }, 'dispose-all:done', DISPOSE_TIMEOUT_MS);\r\n } catch {\r\n // Ignore errors during dispose\r\n }\r\n this.worker.terminate();\r\n this.worker = null;\r\n }\r\n this.initialized = false;\r\n this.healthState = 'healthy';\r\n this.consecutiveFailures = 0;\r\n this.rejectAllPending('Worker disposed');\r\n this.pendingRequests.clear();\r\n }\r\n\r\n /** Check if the worker is initialized and healthy */\r\n get isReady(): boolean {\r\n return this.initialized && this.healthState === 'healthy' && this.worker !== null;\r\n }\r\n\r\n /** Current health state of the worker */\r\n get health(): WorkerHealthState {\r\n return this.healthState;\r\n }\r\n\r\n /** Generation counter — increments on worker recovery. Adapters compare to detect stale sessions. */\r\n get workerGeneration(): number {\r\n return this._generation;\r\n }\r\n\r\n /** The ORT backend the worker is using ('webgpu' on Chrome/Edge, 'wasm' on Safari/iOS/Firefox) */\r\n get backend(): 'wasm' | 'webgpu' {\r\n return this._workerBackend;\r\n }\r\n\r\n /** Check if Web Workers are supported */\r\n static isSupported(): boolean {\r\n return typeof Worker !== 'undefined';\r\n }\r\n\r\n // ── Private ───────────────────────────────────────────────────────────\r\n\r\n private assertReady(): void {\r\n if (!this.initialized || !this.worker) {\r\n throw new Error('UnifiedInferenceWorker not initialized. Call init() first.');\r\n }\r\n if (this.healthState === 'recovering') {\r\n throw new Error('UnifiedInferenceWorker is recovering — retry shortly');\r\n }\r\n if (this.healthState === 'unhealthy') {\r\n throw new Error('UnifiedInferenceWorker recovery failed — unavailable until page reload');\r\n }\r\n }\r\n\r\n private createWorker(): Worker {\r\n const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });\r\n const blobUrl = URL.createObjectURL(blob);\r\n const worker = new Worker(blobUrl);\r\n URL.revokeObjectURL(blobUrl);\r\n\r\n worker.onmessage = (event: MessageEvent) => {\r\n this.handleWorkerMessage(event.data);\r\n };\r\n\r\n worker.onerror = (error) => {\r\n logger.error('Unified worker error', { error: error.message });\r\n this.rejectAllPending(`Worker error: ${error.message}`);\r\n };\r\n\r\n return worker;\r\n }\r\n\r\n private handleWorkerMessage(data: Record<string, unknown>): void {\r\n const requestId = data.requestId as string | null;\r\n\r\n if (data.type === 'error') {\r\n if (requestId && this.pendingRequests.has(requestId)) {\r\n const pending = this.pendingRequests.get(requestId)!;\r\n clearTimeout(pending.timeout);\r\n this.pendingRequests.delete(requestId);\r\n pending.reject(new Error(data.error as string));\r\n } else {\r\n // Broadcast error — reject all pending\r\n logger.error('Worker broadcast error', { error: data.error });\r\n this.rejectAllPending(data.error as string);\r\n }\r\n return;\r\n }\r\n\r\n // Success — reset consecutive failure count\r\n this.consecutiveFailures = 0;\r\n\r\n if (requestId && this.pendingRequests.has(requestId)) {\r\n const pending = this.pendingRequests.get(requestId)!;\r\n clearTimeout(pending.timeout);\r\n this.pendingRequests.delete(requestId);\r\n pending.resolve(data);\r\n }\r\n }\r\n\r\n private sendMessage<T = unknown>(\r\n message: Record<string, unknown>,\r\n expectedType: string,\r\n timeoutMs: number,\r\n ): Promise<T> {\r\n return new Promise((resolve, reject) => {\r\n if (!this.worker) {\r\n reject(new Error('Worker not initialized'));\r\n return;\r\n }\r\n\r\n const requestId = nextRequestId();\r\n const timeout = setTimeout(() => {\r\n this.pendingRequests.delete(requestId);\r\n this.consecutiveFailures++;\r\n\r\n logger.warn('Worker operation timed out', {\r\n type: message.type,\r\n timeoutMs,\r\n consecutiveFailures: this.consecutiveFailures,\r\n });\r\n\r\n reject(new Error(`Worker operation '${message.type}' timed out after ${timeoutMs}ms`));\r\n\r\n // After MAX_CONSECUTIVE_FAILURES, run health check\r\n if (this.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES && !this.recovering) {\r\n this.runHealthCheck();\r\n }\r\n }, timeoutMs);\r\n\r\n this.pendingRequests.set(requestId, {\r\n resolve: resolve as (value: unknown) => void,\r\n reject,\r\n timeout,\r\n });\r\n\r\n this.worker.postMessage({ ...message, requestId });\r\n });\r\n }\r\n\r\n /**\r\n * Ping the worker to check if it's alive. If ping succeeds, worker was just\r\n * busy with long inference. If ping fails, worker is truly stuck — recover.\r\n */\r\n private async runHealthCheck(): Promise<void> {\r\n if (this.recovering) return;\r\n\r\n logger.info('Running health check after consecutive failures', {\r\n consecutiveFailures: this.consecutiveFailures,\r\n });\r\n\r\n try {\r\n await this.sendMessage({ type: 'ping' }, 'pong', HEALTH_CHECK_TIMEOUT_MS);\r\n // Ping succeeded — worker is alive, was just busy\r\n logger.info('Health check passed — worker is alive (was busy with long inference)');\r\n this.consecutiveFailures = 0;\r\n } catch {\r\n // Ping failed — worker is truly stuck, recover\r\n logger.error('Health check FAILED — worker is unresponsive, attempting recovery');\r\n await this.recoverWorker();\r\n }\r\n }\r\n\r\n /**\r\n * Terminate the stuck worker, create a new one, and re-initialize ORT.\r\n * Model sessions are lost — adapters must reload via generation check.\r\n */\r\n private async recoverWorker(): Promise<void> {\r\n if (this.recovering) return;\r\n this.recovering = true;\r\n this.healthState = 'recovering';\r\n\r\n if (this.worker) {\r\n this.worker.terminate();\r\n this.worker = null;\r\n }\r\n this.rejectAllPending('Worker recovery in progress');\r\n\r\n try {\r\n this.worker = this.createWorker();\r\n const result = await this.sendMessage<{ backend: string }>(\r\n { type: 'init', wasmPaths: WASM_CDN_PATH, isIOS: isIOS() },\r\n 'init:done',\r\n INIT_TIMEOUT_MS,\r\n );\r\n this._workerBackend = result.backend === 'webgpu' ? 'webgpu' : 'wasm';\r\n this.healthState = 'healthy';\r\n this.consecutiveFailures = 0;\r\n this._generation++;\r\n this.initialized = true;\r\n logger.info('Worker recovery successful — models must be reloaded', { generation: this._generation, backend: this._workerBackend });\r\n } catch (error) {\r\n logger.error('Worker recovery FAILED', { error: String(error) });\r\n this.healthState = 'unhealthy';\r\n this.cleanup();\r\n } finally {\r\n this.recovering = false;\r\n }\r\n }\r\n\r\n private rejectAllPending(reason: string): void {\r\n for (const [, pending] of this.pendingRequests) {\r\n clearTimeout(pending.timeout);\r\n pending.reject(new Error(reason));\r\n }\r\n this.pendingRequests.clear();\r\n }\r\n\r\n private cleanup(): void {\r\n if (this.worker) {\r\n this.worker.terminate();\r\n this.worker = null;\r\n }\r\n this.initialized = false;\r\n this.rejectAllPending('Worker cleanup');\r\n this.pendingRequests.clear();\r\n }\r\n}\r\n\r\n","/**\n * Shared singleton UnifiedInferenceWorker\n *\n * All inference in the SDK is sequential — there is no benefit to multiple\n * workers, and each extra Worker loads its own ORT WASM instance (~300-400MB).\n * On iOS this exceeds the ~1-1.5GB tab limit → OOM, but even on desktop it\n * wastes memory for no throughput gain.\n *\n * This module provides a ref-counted singleton so ALL callers (Lazy* wrappers,\n * TTSSpeaker, VoiceOrchestrator, SpeechListener, etc.) share one worker.\n */\n\nimport { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\n\nlet sharedWorker: UnifiedInferenceWorker | null = null;\nlet sharedWorkerRefCount = 0;\nlet initPromise: Promise<UnifiedInferenceWorker> | null = null;\n\nexport async function acquireSharedWorker(): Promise<UnifiedInferenceWorker> {\n if (!initPromise) {\n initPromise = (async () => {\n const worker = new UnifiedInferenceWorker();\n await worker.init();\n sharedWorker = worker;\n return worker;\n })();\n }\n const worker = await initPromise;\n sharedWorkerRefCount++;\n return worker;\n}\n\nexport async function releaseSharedWorker(): Promise<void> {\n sharedWorkerRefCount--;\n if (sharedWorkerRefCount <= 0 && sharedWorker) {\n await sharedWorker.dispose();\n sharedWorker = null;\n sharedWorkerRefCount = 0;\n initPromise = null;\n }\n}\n\n/** @deprecated Always returns true — kept for backward compatibility. */\nexport function shouldUseSharedWorker(): boolean {\n return true;\n}\n","/**\n * CTC greedy decoder for SenseVoice\n *\n * Decodes CTC logits into text with structured token parsing\n * for language, emotion, and audio event detection.\n *\n * @module inference/ctcDecoder\n */\n\nexport interface CTCDecodeResult {\n /** Decoded text (speech content only) */\n text: string;\n /** Detected language (e.g., 'zh', 'en', 'ja', 'ko', 'yue') */\n language?: string;\n /** Detected emotion (e.g., 'HAPPY', 'SAD', 'ANGRY', 'NEUTRAL') */\n emotion?: string;\n /** Detected audio event (e.g., 'Speech', 'BGM', 'Laughter') */\n event?: string;\n}\n\n/** SenseVoice language ID → string mapping */\nconst LANGUAGE_IDS: Record<number, string> = {\n 0: 'auto',\n 3: 'zh',\n 4: 'en',\n 7: 'yue',\n 11: 'ja',\n 12: 'ko',\n 13: 'nospeech',\n};\n\n/** SenseVoice text normalization ID → string mapping */\nconst TEXT_NORM_IDS: Record<number, string> = {\n 14: 'with_itn',\n 15: 'without_itn',\n};\n\n/** Resolve language string to SenseVoice language ID */\nexport function resolveLanguageId(language: string): number {\n const map: Record<string, number> = {\n auto: 0,\n zh: 3,\n en: 4,\n yue: 7,\n ja: 11,\n ko: 12,\n };\n return map[language] ?? 0;\n}\n\n/** Resolve text norm string to SenseVoice text norm ID */\nexport function resolveTextNormId(textNorm: string): number {\n return textNorm === 'without_itn' ? 15 : 14;\n}\n\n/**\n * Parse tokens.txt into a token ID → string map\n *\n * Format: each line is \"token_string token_id\"\n * e.g., \"<unk> 0\", \"▁the 3\", \"s 4\"\n */\nexport function parseTokensFile(content: string): Map<number, string> {\n const map = new Map<number, string>();\n const lines = content.split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n // Find the last space — token string may contain spaces\n const lastSpace = trimmed.lastIndexOf(' ');\n if (lastSpace === -1) continue;\n const token = trimmed.substring(0, lastSpace);\n const id = parseInt(trimmed.substring(lastSpace + 1), 10);\n if (!isNaN(id)) {\n map.set(id, token);\n }\n }\n return map;\n}\n\n/**\n * SenseVoice structured token pattern matching\n * Structured tokens appear in the first few decoded tokens as <|tag|>\n */\nfunction parseStructuredToken(token: string): { type: string; value: string } | null {\n const match = token.match(/^<\\|(.+)\\|>$/);\n if (!match) return null;\n\n const value = match[1];\n\n // Language tokens\n if (value === 'zh' || value === 'en' || value === 'ja' || value === 'ko' || value === 'yue' || value === 'nospeech') {\n return { type: 'language', value };\n }\n\n // Emotion tokens\n const emotions = ['HAPPY', 'SAD', 'ANGRY', 'NEUTRAL', 'FEARFUL', 'DISGUSTED', 'SURPRISED', 'EMO_UNKNOWN'];\n if (emotions.includes(value)) {\n return { type: 'emotion', value };\n }\n\n // Audio event tokens\n const events = ['Speech', 'BGM', 'Applause', 'Laughter', 'Crying', 'Coughing', 'Sneezing', 'EVENT_UNKNOWN'];\n if (events.includes(value)) {\n return { type: 'event', value };\n }\n\n // ITN tokens\n if (value === 'withitn' || value === 'woitn' || value === 'with_itn' || value === 'without_itn') {\n return { type: 'textnorm', value };\n }\n\n return null;\n}\n\n/**\n * CTC greedy decode\n *\n * @param logits Raw logits from model output, flattened [seqLen, vocabSize]\n * @param seqLen Sequence length (time steps)\n * @param vocabSize Vocabulary size\n * @param tokenMap Token ID → string map from tokens.txt\n * @returns Decoded text and structured metadata\n */\nexport function ctcGreedyDecode(\n logits: Float32Array,\n seqLen: number,\n vocabSize: number,\n tokenMap: Map<number, string>,\n): CTCDecodeResult {\n // Step 1: Argmax per time step\n const tokenIds: number[] = [];\n for (let t = 0; t < seqLen; t++) {\n const offset = t * vocabSize;\n let maxIdx = 0;\n let maxVal = logits[offset];\n for (let v = 1; v < vocabSize; v++) {\n if (logits[offset + v] > maxVal) {\n maxVal = logits[offset + v];\n maxIdx = v;\n }\n }\n tokenIds.push(maxIdx);\n }\n\n // Step 2: Collapse consecutive duplicates\n const collapsed: number[] = [];\n let prev = -1;\n for (const id of tokenIds) {\n if (id !== prev) {\n collapsed.push(id);\n prev = id;\n }\n }\n\n // Step 3: Remove blank tokens (ID 0) and special tokens (<s>=1, </s>=2)\n const filtered = collapsed.filter(id => id !== 0 && id !== 1 && id !== 2);\n\n // Step 4: Convert to token strings and parse structured tokens\n let language: string | undefined;\n let emotion: string | undefined;\n let event: string | undefined;\n const textTokens: string[] = [];\n\n for (const id of filtered) {\n const token = tokenMap.get(id);\n if (!token) continue;\n\n const structured = parseStructuredToken(token);\n if (structured) {\n if (structured.type === 'language') language = structured.value;\n else if (structured.type === 'emotion') emotion = structured.value;\n else if (structured.type === 'event') event = structured.value;\n // Skip textnorm tokens — not useful in output\n } else {\n textTokens.push(token);\n }\n }\n\n // Step 5: Join tokens, handle SentencePiece boundary marker\n let text = textTokens.join('');\n // Replace SentencePiece word boundary (▁ = U+2581) with space\n text = text.replace(/\\u2581/g, ' ').trim();\n\n return { text, language, emotion, event };\n}\n","/**\r\n * SenseVoice adapter backed by UnifiedInferenceWorker\r\n *\r\n * Implements SenseVoiceBackend, delegating all inference to the shared worker.\r\n */\r\n\r\nimport { createLogger } from '../../logging';\r\nimport { getClock } from '../../logging/Clock';\r\nimport { getTelemetry } from '../../telemetry';\r\nimport { resolveLanguageId, resolveTextNormId } from '../ctcDecoder';\r\nimport type { SenseVoiceResult, SenseVoiceModelInfo, SenseVoiceBackend, SenseVoiceWorkerConfig } from '../SenseVoiceTypes';\r\nimport type { UnifiedInferenceWorker } from '../UnifiedInferenceWorker';\r\n\r\nconst logger = createLogger('SenseVoiceUnifiedAdapter');\r\n\r\nexport class SenseVoiceUnifiedAdapter implements SenseVoiceBackend {\r\n private worker: UnifiedInferenceWorker;\r\n private config: Required<SenseVoiceWorkerConfig>;\r\n private _isLoaded = false;\r\n private loadedGeneration = 0;\r\n private languageId: number;\r\n private textNormId: number;\r\n /** Per-adapter inference queue — ensures sequential state updates. */\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n constructor(worker: UnifiedInferenceWorker, config: SenseVoiceWorkerConfig) {\r\n this.worker = worker;\r\n const modelDir = config.modelUrl.substring(0, config.modelUrl.lastIndexOf('/'));\r\n this.config = {\r\n modelUrl: config.modelUrl,\r\n tokensUrl: config.tokensUrl ?? `${modelDir}/tokens.txt`,\r\n language: config.language ?? 'auto',\r\n textNorm: config.textNorm ?? 'with_itn',\r\n };\r\n this.languageId = resolveLanguageId(this.config.language);\r\n this.textNormId = resolveTextNormId(this.config.textNorm);\r\n }\r\n\r\n get isLoaded(): boolean { return this._isLoaded; }\r\n get backend(): 'wasm' | null { return this._isLoaded ? 'wasm' : null; }\r\n\r\n async load(onProgress?: (loaded: number, total: number) => void): Promise<SenseVoiceModelInfo> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SenseVoiceUnifiedAdapter.load', {\r\n 'model.url': this.config.modelUrl,\r\n });\r\n\r\n try {\r\n const result = await this.worker.loadSenseVoice({\r\n modelUrl: this.config.modelUrl,\r\n tokensUrl: this.config.tokensUrl,\r\n language: this.languageId,\r\n textNorm: this.textNormId,\r\n });\r\n this._isLoaded = true;\r\n this.loadedGeneration = this.worker.workerGeneration;\r\n onProgress?.(1, 1);\r\n\r\n logger.info('SenseVoice loaded via unified worker', {\r\n backend: 'wasm',\r\n loadTimeMs: Math.round(result.loadTimeMs),\r\n vocabSize: result.vocabSize,\r\n });\r\n\r\n span?.setAttributes({ 'model.backend': 'wasm', 'model.load_time_ms': result.loadTimeMs });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.model.load_time', result.loadTimeMs, {\r\n model: 'sensevoice-unified',\r\n backend: 'wasm',\r\n });\r\n\r\n return result;\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n }\r\n\r\n async transcribe(audioSamples: Float32Array): Promise<SenseVoiceResult> {\r\n this.assertLoaded();\r\n\r\n const audio = new Float32Array(audioSamples);\r\n\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SenseVoiceUnifiedAdapter.transcribe', {\r\n 'inference.input_samples': audio.length,\r\n 'inference.backend': 'wasm',\r\n });\r\n const startTime = getClock().now();\r\n try {\r\n const result = await this.worker.transcribe(audio);\r\n const latencyMs = getClock().now() - startTime;\r\n telemetry?.recordHistogram('omote.inference.latency', latencyMs, {\r\n model: 'sensevoice-unified',\r\n backend: 'wasm',\r\n });\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'sensevoice-unified',\r\n backend: 'wasm',\r\n status: 'success',\r\n });\r\n span?.setAttributes({ 'inference.duration_ms': latencyMs });\r\n span?.end();\r\n resolve(result);\r\n } catch (err) {\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'sensevoice-unified',\r\n backend: 'wasm',\r\n status: 'error',\r\n });\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this._isLoaded) {\r\n await this.worker.disposeSenseVoice();\r\n this._isLoaded = false;\r\n }\r\n }\r\n\r\n private assertLoaded(): void {\r\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\r\n if (this.worker.workerGeneration !== this.loadedGeneration) {\r\n this._isLoaded = false;\r\n throw new Error('Worker was recovered — model session lost. Call load() again.');\r\n }\r\n }\r\n}\r\n","/**\r\n * A2E adapter backed by UnifiedInferenceWorker\r\n *\r\n * Implements A2EBackend, delegating all inference to the shared worker.\r\n * Used on iOS to run A2E inference off the main thread via the unified worker.\r\n */\r\n\r\nimport { createLogger } from '../../logging';\r\nimport { getClock } from '../../logging/Clock';\r\nimport { getTelemetry } from '../../telemetry';\r\nimport type { A2EBackend, A2EModelInfo, A2EResult } from '../A2EBackend';\r\nimport type { RuntimeBackend } from '../../utils/runtime';\r\nimport type { UnifiedInferenceWorker } from '../UnifiedInferenceWorker';\r\n\r\nconst logger = createLogger('A2EUnifiedAdapter');\r\n\r\nexport class A2EUnifiedAdapter implements A2EBackend {\r\n readonly modelId = 'a2e' as const;\r\n readonly chunkSize: number;\r\n\r\n private worker: UnifiedInferenceWorker;\r\n private modelUrl: string;\r\n private externalDataUrl: string | null;\r\n private numIdentityClasses: number;\r\n private _isLoaded = false;\r\n private _backend: RuntimeBackend | null = null;\r\n private loadedGeneration = 0;\r\n /** Per-adapter inference queue — ensures sequential state updates. */\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n constructor(worker: UnifiedInferenceWorker, config: {\r\n modelUrl: string;\r\n externalDataUrl?: string | false;\r\n numIdentityClasses?: number;\r\n chunkSize?: number;\r\n }) {\r\n this.worker = worker;\r\n this.modelUrl = config.modelUrl;\r\n this.externalDataUrl = config.externalDataUrl !== false\r\n ? (typeof config.externalDataUrl === 'string'\r\n ? config.externalDataUrl\r\n : `${config.modelUrl}.data`)\r\n : null;\r\n this.numIdentityClasses = config.numIdentityClasses ?? 12;\r\n this.chunkSize = config.chunkSize ?? 16000;\r\n }\r\n\r\n get isLoaded(): boolean { return this._isLoaded; }\r\n get backend(): RuntimeBackend | null { return this._backend; }\r\n\r\n async load(): Promise<A2EModelInfo> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('A2EUnifiedAdapter.load', {\r\n 'model.url': this.modelUrl,\r\n });\r\n\r\n try {\r\n const result = await this.worker.loadLAM({\r\n modelUrl: this.modelUrl,\r\n externalDataUrl: this.externalDataUrl,\r\n numIdentityClasses: this.numIdentityClasses,\r\n });\r\n this._isLoaded = true;\r\n this._backend = result.backend;\r\n this.loadedGeneration = this.worker.workerGeneration;\r\n\r\n logger.info('A2E loaded via unified worker', {\r\n backend: result.backend,\r\n loadTimeMs: Math.round(result.loadTimeMs),\r\n });\r\n\r\n span?.setAttributes({ 'model.backend': result.backend, 'model.load_time_ms': result.loadTimeMs });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.model.load_time', result.loadTimeMs, {\r\n model: 'a2e-unified',\r\n backend: result.backend,\r\n });\r\n\r\n return result;\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n }\r\n\r\n async infer(audioSamples: Float32Array, identityIndex?: number): Promise<A2EResult> {\r\n this.assertLoaded();\r\n\r\n // Ensure audio is exactly chunkSize samples\r\n let audio: Float32Array;\r\n if (audioSamples.length === this.chunkSize) {\r\n audio = new Float32Array(audioSamples);\r\n } else if (audioSamples.length < this.chunkSize) {\r\n audio = new Float32Array(this.chunkSize);\r\n audio.set(audioSamples, 0);\r\n } else {\r\n audio = audioSamples.slice(0, this.chunkSize);\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('A2EUnifiedAdapter.infer', {\r\n 'inference.input_samples': audio.length,\r\n });\r\n\r\n try {\r\n const startTime = getClock().now();\r\n const result = await this.worker.inferLAM(audio, identityIndex);\r\n const inferenceTimeMs = getClock().now() - startTime;\r\n\r\n // Reconstruct per-frame Float32Array[] from flat buffer\r\n const flatBuffer = result.blendshapes;\r\n const { numFrames, numBlendshapes } = result;\r\n const blendshapes: Float32Array[] = [];\r\n for (let f = 0; f < numFrames; f++) {\r\n blendshapes.push(flatBuffer.slice(f * numBlendshapes, (f + 1) * numBlendshapes));\r\n }\r\n\r\n span?.setAttributes({\r\n 'inference.duration_ms': inferenceTimeMs,\r\n 'inference.frames': numFrames,\r\n });\r\n span?.end();\r\n\r\n resolve({ blendshapes, numFrames, inferenceTimeMs });\r\n } catch (err) {\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this._isLoaded) {\r\n await this.worker.disposeLAM();\r\n this._isLoaded = false;\r\n }\r\n }\r\n\r\n private assertLoaded(): void {\r\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\r\n if (this.worker.workerGeneration !== this.loadedGeneration) {\r\n this._isLoaded = false;\r\n throw new Error('Worker was recovered — model session lost. Call load() again.');\r\n }\r\n }\r\n}\r\n","/**\n * Character-level tokenizer for Kokoro TTS\n *\n * Maps IPA phoneme characters to token IDs using Kokoro's 178-token vocabulary.\n * Pads with token 0 at start/end, max 512 tokens total.\n *\n * @category Inference\n * @module inference/kokoroTokenizer\n */\n\nimport { createLogger } from '../logging';\n\nconst logger = createLogger('KokoroTokenizer');\n\n/** Kokoro vocabulary: IPA phoneme character → token ID */\nconst VOCAB: Record<string, number> = {\n ';': 1, ':': 2, ',': 3, '.': 4, '!': 5, '?': 6, '\\u2014': 9, '\\u2026': 10,\n '\"': 11, '(': 12, ')': 13, '\\u201C': 14, '\\u201D': 15, ' ': 16, '\\u0303': 17,\n '\\u02A3': 18, '\\u02A5': 19, '\\u02A6': 20, '\\u02A8': 21, '\\u1D5D': 22, '\\uAB67': 23,\n 'A': 24, 'I': 25, 'O': 31, 'Q': 33, 'S': 35, 'T': 36, 'W': 39, 'Y': 41,\n '\\u1D4A': 42, 'a': 43, 'b': 44, 'c': 45, 'd': 46, 'e': 47, 'f': 48, 'h': 50,\n 'i': 51, 'j': 52, 'k': 53, 'l': 54, 'm': 55, 'n': 56, 'o': 57, 'p': 58,\n 'q': 59, 'r': 60, 's': 61, 't': 62, 'u': 63, 'v': 64, 'w': 65, 'x': 66,\n 'y': 67, 'z': 68, '\\u0251': 69, '\\u0250': 70, '\\u0252': 71, '\\u00E6': 72,\n '\\u03B2': 75, '\\u0254': 76, '\\u0255': 77, '\\u00E7': 78, '\\u0256': 80,\n '\\u00F0': 81, '\\u02A4': 82, '\\u0259': 83, '\\u025A': 85, '\\u025B': 86,\n '\\u025C': 87, '\\u025F': 90, '\\u0261': 92, '\\u0265': 99, '\\u0268': 101,\n '\\u026A': 102, '\\u029D': 103, '\\u026F': 110, '\\u0270': 111, '\\u014B': 112,\n '\\u0273': 113, '\\u0272': 114, '\\u0274': 115, '\\u00F8': 116, '\\u0278': 118,\n '\\u03B8': 119, '\\u0153': 120, '\\u0279': 123, '\\u027E': 125, '\\u027B': 126,\n '\\u0281': 128, '\\u027D': 129, '\\u0282': 130, '\\u0283': 131, '\\u0288': 132,\n '\\u02A7': 133, '\\u028A': 135, '\\u028B': 136, '\\u028C': 138, '\\u0263': 139,\n '\\u0264': 140, '\\u03C7': 142, '\\u028E': 143, '\\u0292': 147, '\\u0294': 148,\n '\\u02C8': 156, '\\u02CC': 157, '\\u02D0': 158, '\\u02B0': 162, '\\u02B2': 164,\n '\\u2193': 169, '\\u2192': 171, '\\u2197': 172, '\\u2198': 173, '\\u1D7B': 177,\n};\n\n/** Maximum token sequence length (including padding) */\nconst MAX_TOKENS = 512;\n\n/** Padding token ID */\nconst PAD_TOKEN = 0;\n\n/**\n * Tokenize a phoneme string into padded token IDs.\n *\n * Characters not in the vocabulary are silently skipped.\n * Result is padded with token 0 at start and end, capped at 512 total.\n *\n * @param phonemes - IPA phoneme string from eSpeak-NG\n * @returns Padded token ID array\n */\nexport function tokenize(phonemes: string): number[] {\n const tokens: number[] = [PAD_TOKEN];\n for (const char of phonemes) {\n const id = VOCAB[char];\n if (id !== undefined && tokens.length < MAX_TOKENS - 1) {\n tokens.push(id);\n }\n }\n if (tokens.length >= MAX_TOKENS - 1) {\n logger.warn('Token sequence truncated at 512 limit', {\n inputLength: phonemes.length,\n maxTokens: MAX_TOKENS,\n });\n }\n tokens.push(PAD_TOKEN);\n return tokens;\n}\n\n/**\n * Get the vocabulary size (for validation).\n */\nexport function getVocabSize(): number {\n return Object.keys(VOCAB).length;\n}\n\nexport { VOCAB as KOKORO_VOCAB, MAX_TOKENS as KOKORO_MAX_TOKENS, PAD_TOKEN as KOKORO_PAD_TOKEN };\n","/**\n * Text normalization and phonemization for Kokoro TTS\n *\n * Two-stage G2P pipeline:\n * 1. Primary: CMU Pronouncing Dictionary (134K words) → ARPABET → IPA mapping\n * (ported from ttstokenizer, validated for Kokoro TTS by NeuML)\n * 2. Fallback: `phonemize` npm package (rule-based G2P) for out-of-vocabulary words\n *\n * @category Inference\n * @module inference/kokoroPhonemizer\n */\n\nimport { createLogger } from '../logging';\n\nconst logger = createLogger('KokoroPhonemizer');\n\n// ─── Text Normalization (ported from kokoro.js/src/phonemize.js) ─────────────\n\nfunction splitNum(match: string): string {\n if (match.includes('.')) return match;\n if (match.includes(':')) {\n const [h, m] = match.split(':').map(Number);\n if (m === 0) return `${h} o'clock`;\n if (m < 10) return `${h} oh ${m}`;\n return `${h} ${m}`;\n }\n const year = parseInt(match.slice(0, 4), 10);\n if (year < 1100 || year % 1000 < 10) return match;\n const left = match.slice(0, 2);\n const right = parseInt(match.slice(2, 4), 10);\n const suffix = match.endsWith('s') ? 's' : '';\n if (year % 1000 >= 100 && year % 1000 <= 999) {\n if (right === 0) return `${left} hundred${suffix}`;\n if (right < 10) return `${left} oh ${right}${suffix}`;\n }\n return `${left} ${right}${suffix}`;\n}\n\nfunction flipMoney(match: string): string {\n const bill = match[0] === '$' ? 'dollar' : 'pound';\n if (isNaN(Number(match.slice(1)))) return `${match.slice(1)} ${bill}s`;\n if (!match.includes('.')) {\n const suffix = match.slice(1) === '1' ? '' : 's';\n return `${match.slice(1)} ${bill}${suffix}`;\n }\n const [b, c] = match.slice(1).split('.');\n const d = parseInt(c.padEnd(2, '0'), 10);\n const coins = match[0] === '$' ? (d === 1 ? 'cent' : 'cents') : d === 1 ? 'penny' : 'pence';\n return `${b} ${bill}${b === '1' ? '' : 's'} and ${d} ${coins}`;\n}\n\nfunction pointNum(match: string): string {\n const [a, b] = match.split('.');\n return `${a} point ${b.split('').join(' ')}`;\n}\n\n/**\n * Normalize text before phonemization.\n * Handles abbreviations, numbers, currency, punctuation, etc.\n */\nexport function normalizeText(text: string): string {\n return text\n .replace(/[\\u2018\\u2019]/g, \"'\")\n .replace(/\\u00AB/g, '\\u201C')\n .replace(/\\u00BB/g, '\\u201D')\n .replace(/[\\u201C\\u201D]/g, '\"')\n .replace(/\\(/g, '\\u00AB')\n .replace(/\\)/g, '\\u00BB')\n .replace(/、/g, ', ')\n .replace(/。/g, '. ')\n .replace(/!/g, '! ')\n .replace(/,/g, ', ')\n .replace(/:/g, ': ')\n .replace(/;/g, '; ')\n .replace(/?/g, '? ')\n .replace(/[^\\S \\n]/g, ' ')\n .replace(/ {2,}/, ' ')\n .replace(/(?<=\\n) +(?=\\n)/g, '')\n .replace(/\\bD[Rr]\\.(?= [A-Z])/g, 'Doctor')\n .replace(/\\b(?:Mr\\.|MR\\.(?= [A-Z]))/g, 'Mister')\n .replace(/\\b(?:Ms\\.|MS\\.(?= [A-Z]))/g, 'Miss')\n .replace(/\\b(?:Mrs\\.|MRS\\.(?= [A-Z]))/g, 'Mrs')\n .replace(/\\betc\\.(?! [A-Z])/g, 'etc')\n .replace(/\\b(y)eah?\\b/gi, '$1e\\'a')\n .replace(/\\d*\\.\\d+|\\b\\d{4}s?\\b|(?<!:)\\b(?:[1-9]|1[0-2]):[0-5]\\d\\b(?!:)/g, splitNum)\n .replace(/(?<=\\d),(?=\\d)/g, '')\n .replace(/[$£]\\d+(?:\\.\\d+)?(?: hundred| thousand| (?:[bm]|tr)illion)*\\b|[$£]\\d+\\.\\d\\d?\\b/gi, flipMoney)\n .replace(/\\d*\\.\\d+/g, pointNum)\n .replace(/(?<=\\d)-(?=\\d)/g, ' to ')\n .replace(/(?<=\\d)S/g, ' S')\n .replace(/(?<=[BCDFGHJ-NP-TV-Z])'?s\\b/g, \"'S\")\n .replace(/(?<=X')S\\b/g, 's')\n .replace(/(?:[A-Za-z]\\.){2,} [a-z]/g, (m) => m.replace(/\\./g, '-'))\n .replace(/(?<=[A-Z])\\.(?=[A-Z])/gi, '-')\n .trim();\n}\n\n// ─── Punctuation Splitting ──────────────────────────────────────────────────\n\nconst PUNCTUATION = ';:,.!?¡¿\\u2014\\u2026\"\\u00AB\\u00BB\\u201C\\u201D(){}[]';\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nconst PUNCTUATION_PATTERN = new RegExp(`(\\\\s*[${escapeRegExp(PUNCTUATION)}]+\\\\s*)+`, 'g');\n\ninterface SplitSegment {\n match: boolean;\n text: string;\n}\n\nfunction splitOnPunctuation(text: string): SplitSegment[] {\n const result: SplitSegment[] = [];\n let prev = 0;\n for (const m of text.matchAll(PUNCTUATION_PATTERN)) {\n if (prev < m.index!) {\n result.push({ match: false, text: text.slice(prev, m.index!) });\n }\n if (m[0].length > 0) {\n result.push({ match: true, text: m[0] });\n }\n prev = m.index! + m[0].length;\n }\n if (prev < text.length) {\n result.push({ match: false, text: text.slice(prev) });\n }\n return result;\n}\n\n// ─── ARPABET → IPA Mapping (ported from ttstokenizer) ───────────────────────\n\n/** ttstokenizer's ARPABET → IPA mapping table (Kokoro-compatible) */\nconst ARPABET_TO_IPA: Record<string, string> = {\n // Vowels with stress (0=no stress, 1=primary, 2=secondary)\n 'AA0': 'ɑ', 'AA1': 'ˈɑː', 'AA2': 'ˌɑ',\n 'AE0': 'æ', 'AE1': 'ˈæ', 'AE2': 'ˌæ',\n 'AH0': 'ə', 'AH1': 'ˈʌ', 'AH2': 'ˌʌ',\n 'AO0': 'ɔ', 'AO1': 'ˈɔː', 'AO2': 'ˌɔ',\n 'AW0': 'aʊ', 'AW1': 'ˈaʊ', 'AW2': 'ˌaʊ',\n 'AY0': 'aɪ', 'AY1': 'ˈaɪ', 'AY2': 'ˌaɪ',\n 'EH0': 'ɛ', 'EH1': 'ˈɛ', 'EH2': 'ˌɛ',\n 'ER0': 'ɚ', 'ER1': 'ˈɚ', 'ER2': 'ˌɚ',\n 'EY0': 'eɪ', 'EY1': 'ˈeɪ', 'EY2': 'ˌeɪ',\n 'IH0': 'ɪ', 'IH1': 'ˈɪ', 'IH2': 'ˌɪ',\n 'IY0': 'i', 'IY1': 'ˈiː', 'IY2': 'ˌi',\n 'OW0': 'oʊ', 'OW1': 'ˈoʊ', 'OW2': 'ˌoʊ',\n 'OY0': 'ɔɪ', 'OY1': 'ˈɔɪ', 'OY2': 'ˌɔɪ',\n 'UH0': 'ʊ', 'UH1': 'ˈʊ', 'UH2': 'ˌʊ',\n 'UW0': 'u', 'UW1': 'ˈuː', 'UW2': 'ˌu',\n // Consonants\n 'B': 'b', 'CH': 'tʃ', 'D': 'd', 'DH': 'ð',\n 'F': 'f', 'G': 'ɡ', 'HH': 'h', 'JH': 'dʒ',\n 'K': 'k', 'L': 'l', 'M': 'm', 'N': 'n',\n 'NG': 'ŋ', 'P': 'p', 'R': 'ɹ', 'S': 's',\n 'SH': 'ʃ', 'T': 't', 'TH': 'θ', 'V': 'v',\n 'W': 'w', 'Y': 'j', 'Z': 'z', 'ZH': 'ʒ',\n};\n\n/**\n * Convert ARPABET pronunciation to Kokoro-compatible IPA.\n * @param arpabet - Space-separated ARPABET phones (e.g., \"HH AH0 L OW1\")\n * @returns IPA string (e.g., \"həlˈoʊ\")\n */\nfunction arpabetToIPA(arpabet: string): string {\n return arpabet\n .split(' ')\n .map(phone => ARPABET_TO_IPA[phone] ?? '')\n .join('');\n}\n\n// ─── Function Word Stress Reduction ─────────────────────────────────────────\n\n/**\n * English function words that are typically unstressed in connected speech.\n * CMU dict gives citation-form stress (e.g., \"to\" → T UW1 → \"tˈuː\"),\n * but in natural speech these are reduced (e.g., \"to\" → T UW0 → \"tu\").\n * Without reduction, every word gets primary stress → monotone output.\n */\nconst FUNCTION_WORDS = new Set([\n // Articles\n 'a', 'an', 'the',\n // Prepositions\n 'at', 'by', 'for', 'from', 'in', 'of', 'on', 'to', 'with',\n 'into', 'onto', 'upon',\n // Conjunctions\n 'and', 'but', 'or', 'nor', 'so', 'yet', 'as', 'if', 'than',\n // Pronouns\n 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her', 'us', 'them',\n 'you', 'your', 'my', 'his', 'its', 'our', 'their',\n // Auxiliaries\n 'am', 'is', 'are', 'was', 'were', 'be', 'been',\n 'have', 'has', 'had',\n 'do', 'does', 'did',\n 'can', 'could', 'will', 'would', 'shall', 'should', 'may', 'might', 'must',\n // Others\n 'not', 'just', 'some', 'that', 'this', 'what',\n]);\n\n/**\n * Reduce primary stress to unstressed for function words.\n * Replaces stress level 1 → 0 in ARPABET phones.\n * e.g., \"T UW1\" → \"T UW0\" (\"tˈuː\" → \"tu\")\n */\nfunction reduceStress(arpabet: string): string {\n return arpabet.replace(/1/g, '0');\n}\n\n// ─── IPA Post-processing ────────────────────────────────────────────────────\n\nfunction postProcessIPA(ps: string, language: string): string {\n let processed = ps\n .replace(/kəkˈoːɹoʊ/g, 'kˈoʊkəɹoʊ')\n .replace(/kəkˈɔːɹəʊ/g, 'kˈəʊkəɹəʊ')\n .replace(/ʲ/g, 'j')\n .replace(/r/g, 'ɹ')\n .replace(/x/g, 'k')\n .replace(/ɬ/g, 'l')\n // phonemize (hans00) → eSpeak-style IPA normalization for Kokoro VOCAB:\n .replace(/ɫ/g, 'l') // dark L → plain L (VOCAB token 54)\n .replace(/ɝ/g, 'ɜɹ') // rhotacized open-mid → decomposed (eSpeak-style: ɜ + ɹ)\n .replace(/(?<=[a-zɹː])(?=hˈʌndɹɪd)/g, ' ')\n .replace(/ z(?=[;:,.!?¡¿\\u2014\\u2026\"\\u00AB\\u00BB\\u201C\\u201D ]|$)/g, 'z');\n\n if (language === 'a') {\n processed = processed.replace(/(?<=nˈaɪn)ti(?!ː)/g, 'di');\n }\n return processed.trim();\n}\n\n// ─── Public API ─────────────────────────────────────────────────────────────\n\n/** Lazily loaded CMU pronouncing dictionary (134K words) */\nlet _cmuDict: Record<string, string> | null = null;\nlet _cmuLoadFailed = false;\n\n/** Lazily loaded phonemize module (OOV fallback) */\nlet _phonemizeModule: { phonemize: (text: string) => string } | null = null;\nlet _phonemizeLoadFailed = false;\n\n/**\n * Load phonemization backends.\n * Primary: CMU dictionary (ARPABET → IPA, ttstokenizer-compatible).\n * Fallback: phonemize (hans00, rule-based G2P) for out-of-vocabulary words.\n * Safe to call multiple times.\n */\nexport async function loadPhonemizer(): Promise<void> {\n // Load CMU dictionary (primary G2P)\n if (!_cmuDict && !_cmuLoadFailed) {\n try {\n const mod = await import('cmu-pronouncing-dictionary');\n // Package exports named `dictionary` (ESM: export const dictionary = {...})\n const dict = (mod as any).dictionary ?? (mod as any).default ?? mod;\n if (typeof dict === 'object' && dict !== null && typeof dict.hello === 'string') {\n _cmuDict = dict as Record<string, string>;\n } else {\n throw new Error('Unexpected CMU dictionary format');\n }\n logger.info('CMU dictionary loaded', { words: Object.keys(_cmuDict).length });\n } catch {\n _cmuLoadFailed = true;\n logger.warn('CMU dictionary not available, using phonemize fallback');\n }\n }\n\n // Load phonemize (OOV fallback)\n if (!_phonemizeModule && !_phonemizeLoadFailed) {\n try {\n const mod = await import('phonemize');\n _phonemizeModule = mod;\n logger.info('phonemize loaded (OOV fallback)');\n } catch {\n _phonemizeLoadFailed = true;\n logger.warn('phonemize not available');\n }\n }\n\n if (!_cmuDict && !_phonemizeModule) {\n throw new Error('No phonemization backend available (install cmu-pronouncing-dictionary or phonemize)');\n }\n}\n\n/**\n * Phonemize a text segment (no punctuation) word by word.\n * Uses CMU dict as primary, phonemize as OOV fallback.\n */\nfunction phonemizeSegment(segment: string): string {\n const words = segment.split(/\\s+/).filter(w => w.length > 0);\n const ipaWords: string[] = [];\n\n for (const word of words) {\n // Primary: CMU dict lookup\n if (_cmuDict) {\n const key = word.toLowerCase();\n const entry = _cmuDict[key];\n if (entry) {\n // Reduce stress on function words for natural connected speech prosody\n const arpabet = FUNCTION_WORDS.has(key) ? reduceStress(entry) : entry;\n ipaWords.push(arpabetToIPA(arpabet));\n continue;\n }\n }\n\n // Fallback: phonemize (hans00) for OOV words\n if (_phonemizeModule) {\n ipaWords.push(_phonemizeModule.phonemize(word));\n } else {\n logger.warn('OOV word with no fallback', { word });\n }\n }\n\n return ipaWords.join(' ');\n}\n\n/** Languages where CMU dict + ARPABET pipeline applies (English only) */\nconst CMU_LANGUAGES = new Set(['a', 'b']);\n\n/**\n * Convert text to IPA phonemes for Kokoro TTS.\n *\n * English (lang 'a'/'b'): CMU dictionary → ARPABET → ttstokenizer IPA mapping.\n * Non-English: phonemize (hans00) rule-based G2P directly.\n * OOV fallback: phonemize for words not in CMU dict.\n *\n * @param text - Input text (will be normalized first)\n * @param language - Voice language code: 'a'=American, 'b'=British, 'e'=Spanish, 'f'=French, etc.\n * @param normalize - Whether to apply text normalization (default: true)\n * @returns IPA phoneme string ready for tokenization\n */\nexport async function phonemize(text: string, language: string = 'a', normalize: boolean = true): Promise<string> {\n if (!_cmuDict && !_phonemizeModule) {\n await loadPhonemizer();\n }\n\n if (normalize) {\n text = normalizeText(text);\n }\n\n const sections = splitOnPunctuation(text);\n const useCmu = CMU_LANGUAGES.has(language);\n\n const parts = sections.map(({ match, text: t }) => {\n if (match) return t;\n // CMU dict pipeline for English; phonemize directly for other languages\n if (useCmu) return phonemizeSegment(t);\n if (_phonemizeModule) return _phonemizeModule.phonemize(t);\n return phonemizeSegment(t); // last resort: try CMU anyway\n });\n\n const joined = parts.join('');\n return postProcessIPA(joined, language);\n}\n\n/**\n * Check if the phonemizer is loaded and ready.\n */\nexport function isPhonemizerLoaded(): boolean {\n return _cmuDict !== null || _phonemizeModule !== null;\n}\n\n/**\n * Split text into sentences for streaming synthesis.\n * Splits on sentence-ending punctuation while preserving the punctuation.\n */\nexport function splitSentences(text: string): string[] {\n const sentences: string[] = [];\n const parts = text.split(/(?<=[.!?])\\s+/);\n for (const part of parts) {\n const trimmed = part.trim();\n if (trimmed.length > 0) {\n sentences.push(trimmed);\n }\n }\n return sentences.length > 0 ? sentences : [text];\n}\n","/**\r\n * Model Cache\r\n *\r\n * Caches ONNX models in IndexedDB for faster subsequent loads.\r\n * IndexedDB can handle large files (100s of MBs) unlike localStorage.\r\n *\r\n * @category Cache\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\n\r\nconst logger = createLogger('ModelCache');\r\n\r\nconst DB_NAME = 'omote-model-cache';\r\nconst DB_VERSION = 2;\r\nconst STORE_NAME = 'models';\r\n\r\n/** Default cache size limit: 1GB */\r\nconst DEFAULT_MAX_SIZE_BYTES = 1024 * 1024 * 1024;\r\n\r\n/**\r\n * Configuration for cache size limits and eviction behavior\r\n */\r\nexport interface CacheConfig {\r\n /** Maximum total cache size in bytes (default: 1GB) */\r\n maxSizeBytes?: number;\r\n /** Maximum age in milliseconds before eviction (default: none) */\r\n maxAgeMs?: number;\r\n /** Callback when storage quota exceeds warning threshold */\r\n onQuotaWarning?: (info: QuotaInfo) => void;\r\n}\r\n\r\n/**\r\n * Storage quota information\r\n */\r\nexport interface QuotaInfo {\r\n /** Total bytes used across all origins */\r\n usedBytes: number;\r\n /** Total available quota in bytes */\r\n quotaBytes: number;\r\n /** Percentage of quota used (0-100) */\r\n percentUsed: number;\r\n /** Bytes used by omote cache specifically */\r\n cacheBytes: number;\r\n}\r\n\r\n/** Global cache configuration */\r\nlet globalCacheConfig: CacheConfig = {\r\n maxSizeBytes: DEFAULT_MAX_SIZE_BYTES,\r\n};\r\n\r\n/**\r\n * Configure cache size limits and eviction behavior\r\n *\r\n * @param config - Cache configuration options\r\n *\r\n * @example\r\n * ```typescript\r\n * import { configureCacheLimit } from '@omote/core';\r\n *\r\n * // Set 500MB limit with 24-hour max age\r\n * configureCacheLimit({\r\n * maxSizeBytes: 500 * 1024 * 1024,\r\n * maxAgeMs: 24 * 60 * 60 * 1000,\r\n * onQuotaWarning: (info) => {\r\n * console.warn(`Storage ${info.percentUsed.toFixed(1)}% used`);\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport function configureCacheLimit(config: CacheConfig): void {\r\n globalCacheConfig = {\r\n ...globalCacheConfig,\r\n ...config,\r\n };\r\n\r\n // Trigger immediate cleanup if over limit\r\n const cache = getModelCache();\r\n cache.enforceLimit().catch((err) => {\r\n logger.warn('Failed to enforce limit after config change', { error: String(err) });\r\n });\r\n}\r\n\r\n/**\r\n * Get current cache configuration\r\n */\r\nexport function getCacheConfig(): CacheConfig {\r\n return { ...globalCacheConfig };\r\n}\r\n\r\ninterface CachedModel {\r\n url: string;\r\n data: ArrayBuffer;\r\n size: number;\r\n cachedAt: number;\r\n /** Last time this model was accessed (for LRU eviction) */\r\n lastAccessedAt: number;\r\n etag?: string;\r\n version?: string;\r\n}\r\n\r\n/**\r\n * Result from getWithValidation() method\r\n */\r\nexport interface ValidationResult {\r\n /** The cached data, or null if not found */\r\n data: ArrayBuffer | null;\r\n /** True if the cached data is stale (etag mismatch) */\r\n stale: boolean;\r\n}\r\n\r\n/**\r\n * Generate a version-aware cache key\r\n *\r\n * @param url - The model URL\r\n * @param version - Optional version string\r\n * @returns The cache key (url#vX.X.X if version provided, url otherwise)\r\n *\r\n * @example\r\n * ```typescript\r\n * getCacheKey('http://example.com/model.onnx', '1.0.0')\r\n * // Returns: 'http://example.com/model.onnx#v1.0.0'\r\n *\r\n * getCacheKey('http://example.com/model.onnx')\r\n * // Returns: 'http://example.com/model.onnx'\r\n * ```\r\n */\r\nexport function getCacheKey(url: string, version?: string): string {\r\n if (version) {\r\n return `${url}#v${version}`;\r\n }\r\n return url;\r\n}\r\n\r\ninterface CacheStats {\r\n totalSize: number;\r\n modelCount: number;\r\n models: { url: string; size: number; cachedAt: Date }[];\r\n}\r\n\r\n/**\r\n * ModelCache - IndexedDB-based cache for ONNX models\r\n */\r\nexport class ModelCache {\r\n private db: IDBDatabase | null = null;\r\n private dbPromise: Promise<IDBDatabase> | null = null;\r\n\r\n /**\r\n * Initialize the cache database\r\n */\r\n private async getDB(): Promise<IDBDatabase> {\r\n if (this.db) return this.db;\r\n if (this.dbPromise) return this.dbPromise;\r\n\r\n // Request persistent storage for more generous quota on iOS/mobile browsers\r\n // This increases available storage from ~50MB to potentially GBs\r\n if (navigator.storage && navigator.storage.persist) {\r\n try {\r\n const isPersisted = await navigator.storage.persist();\r\n if (isPersisted) {\r\n logger.debug('Persistent storage granted - increased quota available');\r\n } else {\r\n logger.debug('Persistent storage denied - using default quota');\r\n }\r\n\r\n // Log current quota usage (helpful for debugging iOS limits)\r\n if (navigator.storage.estimate) {\r\n const estimate = await navigator.storage.estimate();\r\n const usedMB = ((estimate.usage || 0) / 1024 / 1024).toFixed(2);\r\n const quotaMB = ((estimate.quota || 0) / 1024 / 1024).toFixed(2);\r\n logger.debug('Storage quota', { usedMB, quotaMB });\r\n }\r\n } catch (err) {\r\n logger.warn('Failed to request persistent storage', { error: String(err) });\r\n }\r\n }\r\n\r\n const dbOpenStart = getClock().now();\r\n this.dbPromise = new Promise((resolve, reject) => {\r\n const request = indexedDB.open(DB_NAME, DB_VERSION);\r\n\r\n request.onerror = () => {\r\n logger.error('Failed to open IndexedDB', { error: String(request.error) });\r\n reject(request.error);\r\n };\r\n\r\n request.onsuccess = () => {\r\n this.db = request.result;\r\n logger.debug('IndexedDB opened', { durationMs: Math.round(getClock().now() - dbOpenStart) });\r\n resolve(this.db);\r\n };\r\n\r\n request.onupgradeneeded = (event) => {\r\n const db = (event.target as IDBOpenDBRequest).result;\r\n const oldVersion = (event as IDBVersionChangeEvent).oldVersion;\r\n const tx = (event.target as IDBOpenDBRequest).transaction;\r\n\r\n if (oldVersion < 1) {\r\n // Initial schema: create store with url as key\r\n const store = db.createObjectStore(STORE_NAME, { keyPath: 'url' });\r\n store.createIndex('lastAccessedAt', 'lastAccessedAt', { unique: false });\r\n } else if (oldVersion < 2 && tx) {\r\n // Migrate from v1 to v2: add lastAccessedAt index and backfill existing entries\r\n const store = tx.objectStore(STORE_NAME);\r\n\r\n // Create index if it doesn't exist\r\n if (!store.indexNames.contains('lastAccessedAt')) {\r\n store.createIndex('lastAccessedAt', 'lastAccessedAt', { unique: false });\r\n }\r\n\r\n // Migrate existing entries: set lastAccessedAt = cachedAt\r\n const cursorRequest = store.openCursor();\r\n cursorRequest.onsuccess = (cursorEvent) => {\r\n const cursor = (cursorEvent.target as IDBRequest<IDBCursorWithValue>).result;\r\n if (cursor) {\r\n const value = cursor.value;\r\n if (value.lastAccessedAt === undefined) {\r\n value.lastAccessedAt = value.cachedAt || Date.now();\r\n cursor.update(value);\r\n }\r\n cursor.continue();\r\n }\r\n };\r\n }\r\n };\r\n });\r\n\r\n return this.dbPromise;\r\n }\r\n\r\n /**\r\n * Check if a model is cached\r\n */\r\n async has(url: string): Promise<boolean> {\r\n try {\r\n const db = await this.getDB();\r\n return new Promise((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readonly');\r\n const store = tx.objectStore(STORE_NAME);\r\n const request = store.count(url);\r\n request.onsuccess = () => resolve(request.result > 0);\r\n request.onerror = () => resolve(false);\r\n });\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Get a cached model\r\n *\r\n * Updates lastAccessedAt timestamp for LRU tracking on cache hit.\r\n */\r\n async get(url: string): Promise<ArrayBuffer | null> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('ModelCache.get', { 'cache.url': url });\r\n try {\r\n const db = await this.getDB();\r\n return new Promise((resolve) => {\r\n // Use readwrite to update lastAccessedAt on hit\r\n const tx = db.transaction(STORE_NAME, 'readwrite');\r\n const store = tx.objectStore(STORE_NAME);\r\n const request = store.get(url);\r\n request.onsuccess = () => {\r\n const cached = request.result as CachedModel | undefined;\r\n const hit = cached?.data != null;\r\n span?.setAttributes({ 'cache.hit': hit });\r\n if (cached) {\r\n span?.setAttributes({ 'cache.size_bytes': cached.size });\r\n // Update lastAccessedAt for LRU tracking\r\n cached.lastAccessedAt = Date.now();\r\n store.put(cached);\r\n }\r\n span?.end();\r\n if (hit) {\r\n telemetry?.incrementCounter(MetricNames.CACHE_HITS, 1, {});\r\n } else {\r\n telemetry?.incrementCounter(MetricNames.CACHE_MISSES, 1, {});\r\n }\r\n resolve(cached?.data ?? null);\r\n };\r\n request.onerror = () => {\r\n span?.setAttributes({ 'cache.hit': false });\r\n span?.end();\r\n telemetry?.incrementCounter(MetricNames.CACHE_MISSES, 1, {});\r\n resolve(null);\r\n };\r\n });\r\n } catch {\r\n span?.endWithError(new Error('Cache get failed'));\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Get a cached model with ETag validation\r\n *\r\n * Validates the cached data against the server's current ETag.\r\n * If the cached ETag differs from the server's, the data is marked as stale.\r\n *\r\n * @param url - The cache key\r\n * @param originalUrl - The original URL for HEAD request (if different from cache key)\r\n * @returns ValidationResult with data and stale flag\r\n *\r\n * @example\r\n * ```typescript\r\n * const result = await cache.getWithValidation('http://example.com/model.onnx');\r\n * if (result.data && !result.stale) {\r\n * // Use cached data\r\n * } else if (result.stale) {\r\n * // Refetch and update cache\r\n * }\r\n * ```\r\n */\r\n async getWithValidation(url: string, originalUrl?: string): Promise<ValidationResult> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('ModelCache.getWithValidation', { 'cache.url': url });\r\n\r\n try {\r\n const db = await this.getDB();\r\n const cached = await new Promise<CachedModel | undefined>((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readonly');\r\n const store = tx.objectStore(STORE_NAME);\r\n const request = store.get(url);\r\n request.onsuccess = () => resolve(request.result as CachedModel | undefined);\r\n request.onerror = () => resolve(undefined);\r\n });\r\n\r\n // Cache miss\r\n if (!cached?.data) {\r\n span?.setAttributes({ 'cache.hit': false });\r\n span?.end();\r\n telemetry?.incrementCounter(MetricNames.CACHE_MISSES, 1, {});\r\n return { data: null, stale: false };\r\n }\r\n\r\n span?.setAttributes({ 'cache.hit': true, 'cache.size_bytes': cached.size });\r\n\r\n // No etag stored - can't validate, return as fresh\r\n if (!cached.etag) {\r\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\r\n span?.end();\r\n telemetry?.incrementCounter(MetricNames.CACHE_HITS, 1, {});\r\n return { data: cached.data, stale: false };\r\n }\r\n\r\n // Validate via HEAD request\r\n const fetchUrl = originalUrl || url;\r\n try {\r\n const response = await fetch(fetchUrl, { method: 'HEAD' });\r\n if (!response.ok) {\r\n // Server error - assume cache is still valid\r\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\r\n span?.end();\r\n telemetry?.incrementCounter(MetricNames.CACHE_HITS, 1, {});\r\n return { data: cached.data, stale: false };\r\n }\r\n\r\n const serverEtag = response.headers.get('etag');\r\n const isStale = serverEtag !== null && serverEtag !== cached.etag;\r\n\r\n span?.setAttributes({\r\n 'cache.validated': true,\r\n 'cache.stale': isStale,\r\n 'cache.server_etag': serverEtag || 'none',\r\n 'cache.cached_etag': cached.etag,\r\n });\r\n span?.end();\r\n\r\n if (isStale) {\r\n telemetry?.incrementCounter(MetricNames.CACHE_STALE, 1, {});\r\n logger.debug('Stale cache detected', { url });\r\n } else {\r\n telemetry?.incrementCounter(MetricNames.CACHE_HITS, 1, {});\r\n }\r\n\r\n return { data: cached.data, stale: isStale };\r\n } catch (fetchError) {\r\n // HEAD request failed (network error, CORS, etc.)\r\n // Return cached data as non-stale - better than failing completely\r\n logger.warn('HEAD validation failed, using cached data', { error: String(fetchError) });\r\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\r\n span?.end();\r\n telemetry?.incrementCounter(MetricNames.CACHE_HITS, 1, {});\r\n return { data: cached.data, stale: false };\r\n }\r\n } catch {\r\n span?.endWithError(new Error('Cache getWithValidation failed'));\r\n return { data: null, stale: false };\r\n }\r\n }\r\n\r\n /**\r\n * Store a model in cache\r\n *\r\n * After storing, triggers LRU eviction if cache exceeds size limit.\r\n *\r\n * @param url - The cache key (use getCacheKey() for versioned keys)\r\n * @param data - The model data\r\n * @param etag - Optional ETag for staleness validation\r\n * @param version - Optional version string for metadata\r\n */\r\n async set(url: string, data: ArrayBuffer, etag?: string, version?: string): Promise<void> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('ModelCache.set', {\r\n 'cache.url': url,\r\n 'cache.size_bytes': data.byteLength,\r\n ...(version && { 'cache.version': version }),\r\n });\r\n try {\r\n // Check quota before caching (best effort, don't block write)\r\n this.checkQuota().catch((err) => {\r\n logger.warn('Quota check failed', { error: String(err) });\r\n });\r\n\r\n const db = await this.getDB();\r\n await new Promise<void>((resolve, reject) => {\r\n const tx = db.transaction(STORE_NAME, 'readwrite');\r\n const store = tx.objectStore(STORE_NAME);\r\n const now = Date.now();\r\n const cached: CachedModel = {\r\n url,\r\n data,\r\n size: data.byteLength,\r\n cachedAt: now,\r\n lastAccessedAt: now,\r\n etag,\r\n version,\r\n };\r\n const request = store.put(cached);\r\n request.onsuccess = () => {\r\n span?.end();\r\n resolve();\r\n };\r\n request.onerror = () => {\r\n span?.endWithError(request.error || new Error('Cache set failed'));\r\n reject(request.error);\r\n };\r\n });\r\n\r\n // Log total cache size after write\r\n this.getStats().then((stats) => {\r\n logger.debug('Cache updated', { url, sizeBytes: data.byteLength, totalSizeBytes: stats.totalSize, modelCount: stats.modelCount });\r\n }).catch(() => { /* best effort */ });\r\n\r\n // Trigger LRU cleanup after write (don't block)\r\n this.enforceLimit().catch((err) => {\r\n logger.warn('Failed to enforce limit after set', { error: String(err) });\r\n });\r\n } catch (err) {\r\n logger.warn('Failed to cache model', { url, error: String(err) });\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n }\r\n }\r\n\r\n /**\r\n * Check storage quota and trigger warnings/cleanup as needed\r\n *\r\n * - Logs warning if quota > 90% used\r\n * - Triggers LRU cleanup if quota > 95% used\r\n * - Calls onQuotaWarning callback if configured\r\n */\r\n private async checkQuota(): Promise<void> {\r\n const quota = await this.getQuotaInfo();\r\n if (!quota) {\r\n return; // API unavailable\r\n }\r\n\r\n const config = globalCacheConfig;\r\n const telemetry = getTelemetry();\r\n\r\n if (quota.percentUsed > 90) {\r\n logger.warn('Storage quota warning', { percentUsed: quota.percentUsed.toFixed(1), used: formatBytes(quota.usedBytes), quota: formatBytes(quota.quotaBytes) });\r\n\r\n // Emit telemetry counter\r\n telemetry?.incrementCounter(MetricNames.CACHE_QUOTA_WARNING, 1, {\r\n percent_used: String(Math.round(quota.percentUsed)),\r\n });\r\n\r\n // Call user callback if configured\r\n if (config.onQuotaWarning) {\r\n try {\r\n config.onQuotaWarning(quota);\r\n } catch (err) {\r\n logger.warn('onQuotaWarning callback error', { error: String(err) });\r\n }\r\n }\r\n }\r\n\r\n if (quota.percentUsed > 95) {\r\n logger.warn('Storage quota critical (>95%), triggering LRU cleanup', { percentUsed: quota.percentUsed.toFixed(1) });\r\n // Free at least 10% of cache to make room\r\n const bytesToFree = Math.max(quota.cacheBytes * 0.1, 10 * 1024 * 1024);\r\n await this.evictOldest(bytesToFree);\r\n }\r\n }\r\n\r\n /**\r\n * Delete a cached model\r\n */\r\n async delete(url: string): Promise<void> {\r\n try {\r\n const db = await this.getDB();\r\n return new Promise((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readwrite');\r\n const store = tx.objectStore(STORE_NAME);\r\n store.delete(url);\r\n tx.oncomplete = () => resolve();\r\n });\r\n } catch {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n /**\r\n * Clear all cached models\r\n */\r\n async clear(): Promise<void> {\r\n try {\r\n const db = await this.getDB();\r\n return new Promise((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readwrite');\r\n const store = tx.objectStore(STORE_NAME);\r\n store.clear();\r\n tx.oncomplete = () => resolve();\r\n });\r\n } catch {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n /**\r\n * Get cache statistics\r\n */\r\n async getStats(): Promise<CacheStats> {\r\n try {\r\n const db = await this.getDB();\r\n return new Promise((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readonly');\r\n const store = tx.objectStore(STORE_NAME);\r\n const request = store.getAll();\r\n request.onsuccess = () => {\r\n const models = (request.result as CachedModel[]) || [];\r\n resolve({\r\n totalSize: models.reduce((sum, m) => sum + m.size, 0),\r\n modelCount: models.length,\r\n models: models.map((m) => ({\r\n url: m.url,\r\n size: m.size,\r\n cachedAt: new Date(m.cachedAt),\r\n })),\r\n });\r\n };\r\n request.onerror = () => resolve({ totalSize: 0, modelCount: 0, models: [] });\r\n });\r\n } catch {\r\n return { totalSize: 0, modelCount: 0, models: [] };\r\n }\r\n }\r\n\r\n /**\r\n * Enforce cache size limit by evicting oldest entries (LRU)\r\n *\r\n * Called automatically after each set() operation.\r\n * Can also be called manually to trigger cleanup.\r\n */\r\n async enforceLimit(): Promise<void> {\r\n const config = globalCacheConfig;\r\n const maxSize = config.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES;\r\n\r\n const stats = await this.getStats();\r\n if (stats.totalSize <= maxSize) {\r\n return; // Under limit, nothing to do\r\n }\r\n\r\n const bytesToFree = stats.totalSize - maxSize;\r\n const evictedUrls = await this.evictOldest(bytesToFree);\r\n\r\n if (evictedUrls.length > 0) {\r\n logger.info('LRU eviction complete', { modelsRemoved: evictedUrls.length, bytesFreed: formatBytes(bytesToFree) });\r\n }\r\n }\r\n\r\n /**\r\n * Evict oldest entries (by lastAccessedAt) to free space\r\n *\r\n * @param bytesToFree - Minimum bytes to free\r\n * @returns List of evicted URLs\r\n *\r\n * @example\r\n * ```typescript\r\n * const cache = getModelCache();\r\n * const evicted = await cache.evictOldest(100 * 1024 * 1024); // Free 100MB\r\n * console.log('Evicted:', evicted);\r\n * ```\r\n */\r\n async evictOldest(bytesToFree: number): Promise<string[]> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('ModelCache.evictOldest', {\r\n 'eviction.bytes_requested': bytesToFree,\r\n });\r\n\r\n try {\r\n const db = await this.getDB();\r\n\r\n // Get all models sorted by lastAccessedAt (oldest first)\r\n const models = await new Promise<CachedModel[]>((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readonly');\r\n const store = tx.objectStore(STORE_NAME);\r\n const request = store.getAll();\r\n request.onsuccess = () => {\r\n const all = (request.result as CachedModel[]) || [];\r\n // Sort by lastAccessedAt ascending (oldest first)\r\n all.sort((a, b) => (a.lastAccessedAt || a.cachedAt || 0) - (b.lastAccessedAt || b.cachedAt || 0));\r\n resolve(all);\r\n };\r\n request.onerror = () => resolve([]);\r\n });\r\n\r\n const evictedUrls: string[] = [];\r\n let freedBytes = 0;\r\n\r\n // Evict models until we've freed enough space\r\n for (const model of models) {\r\n if (freedBytes >= bytesToFree) {\r\n break;\r\n }\r\n\r\n await this.delete(model.url);\r\n evictedUrls.push(model.url);\r\n freedBytes += model.size;\r\n\r\n logger.debug('Evicted model', { url: model.url, sizeBytes: model.size });\r\n }\r\n\r\n span?.setAttributes({\r\n 'eviction.bytes_freed': freedBytes,\r\n 'eviction.models_evicted': evictedUrls.length,\r\n });\r\n span?.end();\r\n\r\n // Emit telemetry counter\r\n if (freedBytes > 0) {\r\n telemetry?.incrementCounter(MetricNames.CACHE_EVICTION, evictedUrls.length, {\r\n bytes_freed: String(freedBytes),\r\n });\r\n }\r\n\r\n return evictedUrls;\r\n } catch (err) {\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n logger.warn('Eviction failed', { error: String(err) });\r\n return [];\r\n }\r\n }\r\n\r\n /**\r\n * Get storage quota information\r\n *\r\n * Uses navigator.storage.estimate() to get quota details.\r\n * Returns null if the API is unavailable.\r\n *\r\n * @returns Quota info or null if unavailable\r\n *\r\n * @example\r\n * ```typescript\r\n * const cache = getModelCache();\r\n * const quota = await cache.getQuotaInfo();\r\n * if (quota) {\r\n * console.log(`Using ${quota.percentUsed.toFixed(1)}% of quota`);\r\n * }\r\n * ```\r\n */\r\n async getQuotaInfo(): Promise<QuotaInfo | null> {\r\n if (!navigator?.storage?.estimate) {\r\n return null;\r\n }\r\n\r\n try {\r\n const estimate = await navigator.storage.estimate();\r\n const usedBytes = estimate.usage || 0;\r\n const quotaBytes = estimate.quota || 0;\r\n const percentUsed = quotaBytes > 0 ? (usedBytes / quotaBytes) * 100 : 0;\r\n\r\n const stats = await this.getStats();\r\n\r\n return {\r\n usedBytes,\r\n quotaBytes,\r\n percentUsed,\r\n cacheBytes: stats.totalSize,\r\n };\r\n } catch {\r\n return null;\r\n }\r\n }\r\n}\r\n\r\n// Singleton instance\r\nlet cacheInstance: ModelCache | null = null;\r\n\r\n/**\r\n * Get the global ModelCache instance\r\n */\r\nexport function getModelCache(): ModelCache {\r\n if (!cacheInstance) {\r\n cacheInstance = new ModelCache();\r\n }\r\n return cacheInstance;\r\n}\r\n\r\n// Max size for IndexedDB caching\r\n// When storing ArrayBuffer in IndexedDB, browser does structured clone which\r\n// temporarily doubles memory usage. To avoid STATUS_BREAKPOINT crashes:\r\n// - Files < 500MB: Cache as ArrayBuffer (safe, fast retrieval)\r\n// - Files >= 500MB: Skip IndexedDB, rely on HTTP cache\r\n// See: https://bugs.chromium.org/p/chromium/issues/detail?id=170845\r\nconst MAX_CACHE_SIZE_BYTES = 500 * 1024 * 1024;\r\n\r\n/**\r\n * Options for fetchWithCache\r\n */\r\nexport interface FetchWithCacheOptions {\r\n /** Optional version string for versioned caching */\r\n version?: string;\r\n /** If true, validates cached data against server ETag and refetches if stale */\r\n validateStale?: boolean;\r\n /** Progress callback during download */\r\n onProgress?: (loaded: number, total: number) => void;\r\n /** Timeout per fetch attempt in ms. Default: 120_000 (2 min) */\r\n timeoutMs?: number;\r\n /** Max retry attempts on failure. Default: 2 (3 total attempts) */\r\n maxRetries?: number;\r\n /** AbortSignal to cancel the entire operation */\r\n signal?: AbortSignal;\r\n}\r\n\r\n/**\r\n * Fetch with an AbortController-based timeout.\r\n * Also respects an optional caller-provided AbortSignal.\r\n */\r\nfunction fetchWithTimeout(\r\n url: string,\r\n timeoutMs: number,\r\n signal?: AbortSignal,\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), timeoutMs);\r\n\r\n // If the caller provided a signal, abort our controller when it fires\r\n const onCallerAbort = () => controller.abort();\r\n signal?.addEventListener('abort', onCallerAbort, { once: true });\r\n\r\n return fetch(url, { signal: controller.signal }).finally(() => {\r\n clearTimeout(timer);\r\n signal?.removeEventListener('abort', onCallerAbort);\r\n });\r\n}\r\n\r\n/**\r\n * Fetch a model with caching\r\n * Uses IndexedDB cache with network fallback\r\n * Files larger than 500MB are not cached to IndexedDB to avoid memory pressure\r\n * (structured clone during IndexedDB write temporarily doubles memory usage)\r\n *\r\n * @param url - The URL to fetch\r\n * @param onProgress - Optional progress callback (legacy signature)\r\n * @returns The fetched ArrayBuffer\r\n *\r\n * @example\r\n * ```typescript\r\n * // Simple usage (backwards compatible)\r\n * const data = await fetchWithCache('http://example.com/model.onnx');\r\n *\r\n * // With progress callback (backwards compatible)\r\n * const data = await fetchWithCache('http://example.com/model.onnx', (loaded, total) => {\r\n * console.log(`${loaded}/${total} bytes`);\r\n * });\r\n *\r\n * // With options (new API)\r\n * const data = await fetchWithCache('http://example.com/model.onnx', {\r\n * version: '1.0.0',\r\n * validateStale: true,\r\n * onProgress: (loaded, total) => console.log(`${loaded}/${total}`)\r\n * });\r\n * ```\r\n */\r\nexport async function fetchWithCache(\r\n url: string,\r\n optionsOrProgress?: FetchWithCacheOptions | ((loaded: number, total: number) => void)\r\n): Promise<ArrayBuffer> {\r\n // Normalize arguments - support both old and new signatures\r\n let options: FetchWithCacheOptions = {};\r\n if (typeof optionsOrProgress === 'function') {\r\n // Legacy signature: fetchWithCache(url, onProgress)\r\n options = { onProgress: optionsOrProgress };\r\n } else if (optionsOrProgress) {\r\n // New signature: fetchWithCache(url, options)\r\n options = optionsOrProgress;\r\n }\r\n\r\n const { version, validateStale = false, onProgress } = options;\r\n\r\n const cache = getModelCache();\r\n const cacheKey = version ? getCacheKey(url, version) : url;\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('fetchWithCache', {\r\n 'fetch.url': url,\r\n ...(version && { 'fetch.version': version }),\r\n 'fetch.validate_stale': validateStale,\r\n });\r\n\r\n // Check cache with optional staleness validation\r\n if (validateStale) {\r\n const validation = await cache.getWithValidation(cacheKey, url);\r\n\r\n if (validation.data && !validation.stale) {\r\n logger.debug('Cache hit (validated)', { url, sizeBytes: validation.data.byteLength });\r\n onProgress?.(validation.data.byteLength, validation.data.byteLength);\r\n span?.setAttributes({\r\n 'fetch.cache_hit': true,\r\n 'fetch.cache_validated': true,\r\n 'fetch.cache_stale': false,\r\n 'fetch.size_bytes': validation.data.byteLength,\r\n });\r\n span?.end();\r\n return validation.data;\r\n }\r\n\r\n if (validation.stale) {\r\n logger.debug('Cache stale, refetching', { url });\r\n span?.setAttributes({\r\n 'fetch.cache_hit': true,\r\n 'fetch.cache_validated': true,\r\n 'fetch.cache_stale': true,\r\n });\r\n // Continue to fetch fresh data\r\n }\r\n // If data is null, continue to fetch\r\n } else {\r\n // Simple cache check without validation (backwards compatible behavior)\r\n const cached = await cache.get(cacheKey);\r\n if (cached) {\r\n logger.debug('Cache hit', { url, sizeBytes: cached.byteLength });\r\n onProgress?.(cached.byteLength, cached.byteLength);\r\n span?.setAttributes({\r\n 'fetch.cache_hit': true,\r\n 'fetch.size_bytes': cached.byteLength,\r\n });\r\n span?.end();\r\n return cached;\r\n }\r\n }\r\n\r\n span?.setAttributes({ 'fetch.cache_hit': false });\r\n logger.debug('Cache miss, fetching', { url });\r\n\r\n const timeout = options.timeoutMs ?? 120_000;\r\n const maxRetries = options.maxRetries ?? 2;\r\n let lastError: Error | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n if (options.signal?.aborted) {\r\n throw new Error(`Fetch aborted for ${url}`);\r\n }\r\n\r\n if (attempt > 0) {\r\n const backoff = Math.min(2000 * Math.pow(2, attempt - 1), 16_000);\r\n logger.debug('Retrying fetch', { attempt, maxRetries, backoffMs: backoff, url });\r\n await new Promise(r => setTimeout(r, backoff));\r\n }\r\n\r\n try {\r\n const response = await fetchWithTimeout(url, timeout, options.signal);\r\n if (!response.ok) {\r\n throw new Error(`Failed to fetch ${url}: ${response.status}`);\r\n }\r\n\r\n const contentLength = response.headers.get('content-length');\r\n const total = contentLength ? parseInt(contentLength, 10) : 0;\r\n const etag = response.headers.get('etag') ?? undefined;\r\n\r\n // Check if file is too large for IndexedDB (avoid memory pressure during structured clone)\r\n const tooLargeForCache = total > MAX_CACHE_SIZE_BYTES;\r\n if (tooLargeForCache) {\r\n logger.debug('File too large for IndexedDB, using HTTP cache only', { url, sizeBytes: total, limitBytes: MAX_CACHE_SIZE_BYTES });\r\n }\r\n\r\n if (!response.body) {\r\n const data = await response.arrayBuffer();\r\n if (!tooLargeForCache) {\r\n // Fire-and-forget: model is usable immediately, cache write happens in background\r\n cache.set(cacheKey, data, etag, version).catch(err => {\r\n logger.warn('Background cache write failed', { url, error: String(err) });\r\n });\r\n }\r\n span?.setAttributes({\r\n 'fetch.size_bytes': data.byteLength,\r\n 'fetch.cached_to_indexeddb': !tooLargeForCache,\r\n ...(attempt > 0 && { 'fetch.retry_count': attempt }),\r\n });\r\n span?.end();\r\n return data;\r\n }\r\n\r\n // Stream with progress\r\n const reader = response.body.getReader();\r\n const chunks: Uint8Array[] = [];\r\n let loaded = 0;\r\n\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n chunks.push(value);\r\n loaded += value.length;\r\n logger.trace('Download progress', { url, loadedBytes: loaded, totalBytes: total || loaded });\r\n onProgress?.(loaded, total || loaded);\r\n }\r\n\r\n // Combine chunks\r\n const data = new Uint8Array(loaded);\r\n let offset = 0;\r\n for (const chunk of chunks) {\r\n data.set(chunk, offset);\r\n offset += chunk.length;\r\n }\r\n\r\n const buffer = data.buffer;\r\n\r\n // Cache for next time (if not too large) — fire-and-forget\r\n if (!tooLargeForCache) {\r\n cache.set(cacheKey, buffer, etag, version).catch(err => {\r\n logger.warn('Background cache write failed', { url, error: String(err) });\r\n });\r\n logger.debug('Cached after download', { url, sizeBytes: buffer.byteLength });\r\n }\r\n\r\n span?.setAttributes({\r\n 'fetch.size_bytes': buffer.byteLength,\r\n 'fetch.cached_to_indexeddb': !tooLargeForCache,\r\n ...(attempt > 0 && { 'fetch.retry_count': attempt }),\r\n });\r\n span?.end();\r\n\r\n return buffer;\r\n } catch (error) {\r\n lastError = error instanceof Error ? error : new Error(String(error));\r\n if (options.signal?.aborted) {\r\n span?.endWithError(lastError);\r\n throw lastError;\r\n }\r\n if (attempt < maxRetries) {\r\n logger.warn('Fetch attempt failed', { attempt: attempt + 1, url, error: lastError.message });\r\n }\r\n }\r\n }\r\n\r\n span?.endWithError(lastError!);\r\n throw lastError;\r\n}\r\n\r\n/**\r\n * Preload models into cache without creating sessions\r\n */\r\nexport async function preloadModels(\r\n urls: string[],\r\n onProgress?: (current: number, total: number, url: string) => void\r\n): Promise<void> {\r\n const cache = getModelCache();\r\n\r\n for (let i = 0; i < urls.length; i++) {\r\n const url = urls[i];\r\n onProgress?.(i, urls.length, url);\r\n\r\n if (await cache.has(url)) {\r\n logger.debug('Already cached, skipping', { url });\r\n continue;\r\n }\r\n\r\n await fetchWithCache(url);\r\n }\r\n\r\n onProgress?.(urls.length, urls.length, 'done');\r\n}\r\n\r\n/**\r\n * Format bytes as human readable string\r\n */\r\nexport function formatBytes(bytes: number): string {\r\n if (bytes < 1024) return `${bytes} B`;\r\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\r\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\r\n return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;\r\n}\r\n","/**\r\n * Kokoro TTS voice style loader\r\n *\r\n * Fetches and parses .bin voice files from HuggingFace CDN.\r\n * Each voice file contains style vectors shaped (511, 1, 256) as Float32.\r\n * The style vector for a given token count is indexed by clamped position.\r\n *\r\n * @category Inference\r\n * @module inference/kokoroVoices\r\n */\r\n\r\nimport { fetchWithCache, formatBytes } from '../cache/ModelCache';\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('KokoroVoices');\r\n\r\n/** Number of style entries per voice file (tokens 0-510) */\r\nconst NUM_STYLE_ENTRIES = 511;\r\n/** Style vector dimensionality */\r\nconst STYLE_DIM = 256;\r\n/** Expected Float32 count per voice file */\r\nconst EXPECTED_FLOATS = NUM_STYLE_ENTRIES * 1 * STYLE_DIM; // 511 * 1 * 256 = 130816\r\n\r\n/** Available Kokoro v1.0 voices */\r\nexport const KOKORO_VOICES = {\r\n // American Female\r\n af_heart: 'af_heart', af_alloy: 'af_alloy', af_aoede: 'af_aoede',\r\n af_bella: 'af_bella', af_jessica: 'af_jessica', af_kore: 'af_kore',\r\n af_nicole: 'af_nicole', af_nova: 'af_nova', af_river: 'af_river',\r\n af_sarah: 'af_sarah', af_sky: 'af_sky',\r\n // American Male\r\n am_adam: 'am_adam', am_echo: 'am_echo', am_eric: 'am_eric',\r\n am_fenrir: 'am_fenrir', am_liam: 'am_liam', am_michael: 'am_michael',\r\n am_onyx: 'am_onyx', am_puck: 'am_puck', am_santa: 'am_santa',\r\n // British Female\r\n bf_alice: 'bf_alice', bf_emma: 'bf_emma', bf_isabella: 'bf_isabella', bf_lily: 'bf_lily',\r\n // British Male\r\n bm_daniel: 'bm_daniel', bm_fable: 'bm_fable', bm_george: 'bm_george', bm_lewis: 'bm_lewis',\r\n // Spanish Female\r\n ef_dora: 'ef_dora',\r\n // Spanish Male\r\n em_alex: 'em_alex', em_santa: 'em_santa',\r\n // French Female\r\n ff_siwis: 'ff_siwis',\r\n} as const;\r\n\r\nexport type KokoroVoiceName = keyof typeof KOKORO_VOICES;\r\n\r\n/** Cached voice data: voice name → Float32Array (511 * 256 floats) */\r\nconst _voiceCache = new Map<string, Float32Array>();\r\n\r\n/**\r\n * Load a voice style file from URL and cache it.\r\n *\r\n * @param voiceName - Voice identifier (e.g., 'af_heart')\r\n * @param voiceBaseUrl - Base URL for voice files (should end with `/voices/`)\r\n * @returns The raw Float32Array of style data\r\n */\r\nexport async function loadVoice(voiceName: string, voiceBaseUrl: string): Promise<Float32Array> {\r\n if (!/^[a-z][a-z0-9_]*$/.test(voiceName)) {\r\n throw new Error('Invalid voice name: ' + voiceName);\r\n }\r\n\r\n const cached = _voiceCache.get(voiceName);\r\n if (cached) return cached;\r\n\r\n const url = `${voiceBaseUrl.replace(/\\/$/, '')}/${voiceName}.bin`;\r\n logger.info('Loading voice', { voice: voiceName, url });\r\n\r\n const buffer = await fetchWithCache(url);\r\n const data = new Float32Array(buffer);\r\n\r\n if (data.length !== EXPECTED_FLOATS) {\r\n logger.warn('Voice file unexpected size', {\r\n voice: voiceName,\r\n expected: EXPECTED_FLOATS,\r\n actual: data.length,\r\n bytes: formatBytes(buffer.byteLength),\r\n });\r\n }\r\n\r\n _voiceCache.set(voiceName, data);\r\n logger.info('Voice loaded', { voice: voiceName, bytes: formatBytes(buffer.byteLength) });\r\n return data;\r\n}\r\n\r\n/**\r\n * Get the style vector for a given token count from a loaded voice.\r\n *\r\n * @param voiceData - Raw Float32Array from loadVoice()\r\n * @param numTokens - Number of tokens in the input (used to index into style entries)\r\n * @returns Float32Array of shape [1, 256] — the style vector for this token count\r\n */\r\nexport function getStyleForTokenCount(voiceData: Float32Array, numTokens: number): Float32Array {\r\n // Clamp index to valid range [0, 510]\r\n const idx = Math.min(Math.max(numTokens, 0), NUM_STYLE_ENTRIES - 1);\r\n const offset = idx * STYLE_DIM;\r\n return voiceData.slice(offset, offset + STYLE_DIM);\r\n}\r\n\r\n/**\r\n * List all available voice names.\r\n */\r\nexport function listVoices(): string[] {\r\n return Object.keys(KOKORO_VOICES);\r\n}\r\n\r\n/** Language code mapping: first character of voice name → language code */\r\nconst VOICE_LANG_MAP: Record<string, string> = {\r\n a: 'a', // American English\r\n b: 'b', // British English\r\n e: 'e', // Spanish\r\n f: 'f', // French\r\n h: 'h', // Hindi\r\n i: 'i', // Italian\r\n j: 'j', // Japanese\r\n p: 'p', // Brazilian Portuguese\r\n z: 'z', // Mandarin Chinese\r\n};\r\n\r\n/**\r\n * Get the language code for a voice (inferred from prefix).\r\n * First character encodes language: a=American, b=British, e=Spanish, f=French, etc.\r\n */\r\nexport function getVoiceLanguage(voiceName: string): string {\r\n return VOICE_LANG_MAP[voiceName[0]] ?? 'a';\r\n}\r\n\r\n/**\r\n * Clear the voice cache (for testing or memory management).\r\n */\r\nexport function clearVoiceCache(): void {\r\n _voiceCache.clear();\r\n}\r\n\r\nexport { NUM_STYLE_ENTRIES, STYLE_DIM };\r\n","/**\n * Kokoro TTS type definitions\n *\n * @category Inference\n */\n\nimport type { BackendPreference } from '../utils/runtime';\nimport { KOKORO_VOICES } from './kokoroVoices';\n\n// ─── Config ─────────────────────────────────────────────────────────────────\n\nexport interface KokoroTTSConfig {\n /** ONNX model URL (default: HF CDN q8, 92MB) */\n modelUrl?: string;\n /** Voice files base URL (default: HF CDN voices directory) */\n voiceBaseUrl?: string;\n /** Default voice (default: 'af_heart') */\n defaultVoice?: string;\n /** Backend preference (default: 'wasm' — WebGPU crashes on int64 input_ids) */\n backend?: BackendPreference;\n /** Speech speed multiplier (default: 1.0) */\n speed?: number;\n /** Eagerly load phonemizer + default voice during load() instead of first speak(). Default: true. */\n eagerLoad?: boolean;\n}\n\n// ─── Result Types ───────────────────────────────────────────────────────────\n\nexport interface KokoroTTSResult {\n /** Audio samples at 24kHz */\n audio: Float32Array;\n /** Duration in seconds */\n duration: number;\n /** Inference time in ms */\n inferenceTimeMs: number;\n}\n\nexport interface KokoroStreamChunk {\n /** Audio for this sentence */\n audio: Float32Array;\n /** Original text segment */\n text: string;\n /** Phonemes for this segment */\n phonemes: string;\n /** Duration in seconds */\n duration: number;\n}\n\nexport interface KokoroTTSModelInfo {\n /** Resolved backend */\n backend: string;\n /** Model load time in ms */\n loadTimeMs: number;\n /** Default voice */\n defaultVoice: string;\n}\n\n// ─── Synthesis Options ──────────────────────────────────────────────────────\n\nexport interface SynthesizeOptions {\n /** Voice to use (overrides defaultVoice) */\n voice?: string;\n /** Speed multiplier (overrides config speed) */\n speed?: number;\n}\n\n// ─── Input Validation ────────────────────────────────────────────────────────\n\nconst MIN_SPEED = 0.25;\nconst MAX_SPEED = 4.0;\n\n/**\n * Validate TTS input parameters at API boundaries.\n * Returns trimmed text on success, throws on invalid input.\n */\nexport function validateTTSInput(\n text: unknown,\n voiceName: string,\n speed: number,\n availableVoices?: string[],\n): string {\n if (text === null || text === undefined || typeof text !== 'string') {\n throw new Error('KokoroTTS: text must be a string');\n }\n const trimmed = text.trim();\n if (trimmed.length === 0) {\n throw new Error('KokoroTTS: text must not be empty');\n }\n if (!Number.isFinite(speed) || speed < MIN_SPEED || speed > MAX_SPEED) {\n throw new Error(`KokoroTTS: speed must be between ${MIN_SPEED} and ${MAX_SPEED}, got ${speed}`);\n }\n const voices = availableVoices ?? Object.keys(KOKORO_VOICES);\n if (!voices.includes(voiceName)) {\n throw new Error(`KokoroTTS: unknown voice \"${voiceName}\". Available: ${voices.slice(0, 5).join(', ')}...`);\n }\n return trimmed;\n}\n","/**\r\n * Kokoro TTS adapter backed by UnifiedInferenceWorker\r\n *\r\n * Implements TTSBackend, delegating ONNX inference to the shared worker.\r\n * Phonemization, tokenization, and voice loading stay on main thread (fast, <10ms).\r\n * Only the heavy `session.run()` (~1-2s per sentence) goes to the worker.\r\n */\r\n\r\nimport { createLogger } from '../../logging';\r\nimport { getClock } from '../../logging/Clock';\r\nimport { getTelemetry } from '../../telemetry';\r\nimport { tokenize } from '../kokoroTokenizer';\r\nimport { phonemize, loadPhonemizer, splitSentences } from '../kokoroPhonemizer';\r\nimport { loadVoice, getStyleForTokenCount, getVoiceLanguage } from '../kokoroVoices';\r\nimport { DEFAULT_MODEL_URLS } from '../defaultModelUrls';\r\nimport type { TTSBackend, TTSStreamOptions, TTSChunk } from '../TTSBackend';\r\nimport { validateTTSInput } from '../KokoroTTSTypes';\r\nimport type { KokoroTTSConfig, KokoroTTSModelInfo, KokoroStreamChunk, SynthesizeOptions } from '../KokoroTTSTypes';\r\nimport type { UnifiedInferenceWorker } from '../UnifiedInferenceWorker';\r\n\r\nconst logger = createLogger('KokoroTTSUnifiedAdapter');\r\n\r\nexport class KokoroTTSUnifiedAdapter implements TTSBackend {\r\n private worker: UnifiedInferenceWorker;\r\n private readonly config: Required<Pick<KokoroTTSConfig, 'defaultVoice' | 'speed'>> & KokoroTTSConfig;\r\n private readonly modelUrl: string;\r\n private readonly voiceBaseUrl: string;\r\n\r\n private _isLoaded = false;\r\n private _backend: 'wasm' | 'webgpu' = 'wasm';\r\n private loadedGeneration = 0;\r\n /** Per-adapter inference queue — ensures sequential state updates. */\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n private loadedVoices = new Map<string, Float32Array>();\r\n private phonemizerReady = false;\r\n private defaultVoiceLoaded = false;\r\n\r\n constructor(worker: UnifiedInferenceWorker, config: KokoroTTSConfig = {}) {\r\n this.worker = worker;\r\n this.config = {\r\n defaultVoice: 'af_heart',\r\n speed: 1.0,\r\n eagerLoad: true,\r\n ...config,\r\n };\r\n this.modelUrl = config.modelUrl ?? DEFAULT_MODEL_URLS.kokoroTTS;\r\n this.voiceBaseUrl = config.voiceBaseUrl ?? DEFAULT_MODEL_URLS.kokoroVoices;\r\n }\r\n\r\n get isLoaded(): boolean { return this._isLoaded; }\r\n get sampleRate(): number { return 24000; }\r\n\r\n async load(): Promise<KokoroTTSModelInfo> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('KokoroTTSUnifiedAdapter.load', {\r\n 'model.url': this.modelUrl,\r\n });\r\n\r\n try {\r\n const startTime = getClock().now();\r\n\r\n // Load ONNX model in worker — phonemizer + voice deferred to first stream() call\r\n await this.worker.loadKokoro({ modelUrl: this.modelUrl });\r\n\r\n this._isLoaded = true;\r\n this._backend = this.worker.backend;\r\n this.loadedGeneration = this.worker.workerGeneration;\r\n\r\n // Eager load: pre-warm phonemizer + default voice so first speak() has no latency spike\r\n if (this.config.eagerLoad) {\r\n await loadPhonemizer();\r\n this.phonemizerReady = true;\r\n const voiceData = await loadVoice(this.config.defaultVoice, this.voiceBaseUrl);\r\n this.loadedVoices.set(this.config.defaultVoice, voiceData);\r\n this.defaultVoiceLoaded = true;\r\n }\r\n const loadTimeMs = getClock().now() - startTime;\r\n\r\n logger.info('Kokoro TTS loaded via unified worker', {\r\n backend: this._backend,\r\n loadTimeMs: Math.round(loadTimeMs),\r\n defaultVoice: this.config.defaultVoice,\r\n });\r\n\r\n span?.setAttributes({ 'model.backend': this._backend, 'model.load_time_ms': loadTimeMs });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.model.load_time', loadTimeMs, {\r\n model: 'kokoro-tts-unified',\r\n backend: this._backend,\r\n });\r\n\r\n return { backend: this._backend, loadTimeMs, defaultVoice: this.config.defaultVoice };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n getTelemetry()?.incrementCounter('omote.errors.total', 1, {\r\n model: 'kokoro-tts-unified',\r\n error_type: 'load_failed',\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n async *stream(text: string, options?: SynthesizeOptions & TTSStreamOptions): AsyncGenerator<KokoroStreamChunk & TTSChunk> {\r\n this.assertLoaded();\r\n\r\n const voiceName = options?.voice ?? this.config.defaultVoice;\r\n const speed = options?.speed ?? this.config.speed;\r\n text = validateTTSInput(text, voiceName, speed);\r\n\r\n // Lazy-load phonemizer + default voice on first stream() call (avoids blocking load())\r\n if (!this.phonemizerReady) {\r\n await loadPhonemizer();\r\n this.phonemizerReady = true;\r\n }\r\n if (!this.defaultVoiceLoaded) {\r\n const voiceData = await loadVoice(this.config.defaultVoice, this.voiceBaseUrl);\r\n this.loadedVoices.set(this.config.defaultVoice, voiceData);\r\n this.defaultVoiceLoaded = true;\r\n }\r\n\r\n const sentences = options?.singleShot ? [text] : splitSentences(text);\r\n const language = options?.language ?? getVoiceLanguage(voiceName);\r\n\r\n for (const sentence of sentences) {\r\n if (options?.signal?.aborted) return;\r\n\r\n // Phonemize + tokenize on main thread (fast, <10ms)\r\n const phonemes = await phonemize(sentence, language);\r\n const tokens = tokenize(phonemes);\r\n const voiceData = await this.ensureVoice(voiceName);\r\n const style = getStyleForTokenCount(voiceData, tokens.length);\r\n\r\n if (options?.signal?.aborted) return;\r\n\r\n // Delegate heavy ONNX inference to unified worker\r\n const audio = await this.runWorkerInference(tokens, style, speed);\r\n const duration = audio.length / 24000;\r\n\r\n yield { audio, text: sentence, phonemes, duration };\r\n }\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this._isLoaded) {\r\n await this.worker.disposeKokoro();\r\n this._isLoaded = false;\r\n }\r\n this.loadedVoices.clear();\r\n }\r\n\r\n private async ensureVoice(voiceName: string): Promise<Float32Array> {\r\n let data = this.loadedVoices.get(voiceName);\r\n if (!data) {\r\n data = await loadVoice(voiceName, this.voiceBaseUrl);\r\n this.loadedVoices.set(voiceName, data);\r\n }\r\n return data;\r\n }\r\n\r\n private assertLoaded(): void {\r\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\r\n if (this.worker.workerGeneration !== this.loadedGeneration) {\r\n this._isLoaded = false;\r\n throw new Error('Worker was recovered — model session lost. Call load() again.');\r\n }\r\n }\r\n\r\n private runWorkerInference(tokens: number[], style: Float32Array, speed: number): Promise<Float32Array> {\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const startTime = getClock().now();\r\n const telemetry = getTelemetry();\r\n try {\r\n const result = await this.worker.inferKokoro(tokens, style, speed);\r\n const latencyMs = getClock().now() - startTime;\r\n telemetry?.recordHistogram('omote.inference.latency', latencyMs, {\r\n model: 'kokoro-tts-unified',\r\n backend: this._backend,\r\n });\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'kokoro-tts-unified',\r\n backend: this._backend,\r\n status: 'success',\r\n });\r\n resolve(result.audio);\r\n } catch (err) {\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'kokoro-tts-unified',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n}\r\n","/**\r\n * Silero VAD adapter backed by UnifiedInferenceWorker\r\n *\r\n * Implements SileroVADBackend, delegating all inference to the shared worker.\r\n */\r\n\r\nimport { createLogger } from '../../logging';\r\nimport { getClock } from '../../logging/Clock';\r\nimport { getTelemetry } from '../../telemetry';\r\nimport type { SileroVADConfig, VADResult, SileroVADBackend, VADWorkerModelInfo } from '../SileroVADTypes';\r\nimport type { RuntimeBackend } from '../../utils/runtime';\r\nimport type { UnifiedInferenceWorker } from '../UnifiedInferenceWorker';\r\n\r\nconst logger = createLogger('SileroVADUnifiedAdapter');\r\n\r\nexport class SileroVADUnifiedAdapter implements SileroVADBackend {\r\n private worker: UnifiedInferenceWorker;\r\n private config: Required<SileroVADConfig>;\r\n private _isLoaded = false;\r\n private loadedGeneration = 0;\r\n\r\n // LSTM state (kept in main thread, sent to worker per inference)\r\n private state: Float32Array;\r\n private context: Float32Array;\r\n private readonly chunkSize: number;\r\n private readonly contextSize: number;\r\n\r\n /**\r\n * Per-adapter inference queue — ensures sequential state updates.\r\n *\r\n * The unified worker processes messages serially (single thread), but this queue\r\n * guarantees per-adapter state consistency. Example: VAD LSTM state from call N\r\n * must be applied before call N+1 starts. Without the queue, two rapid process()\r\n * calls could both read the same stale state.\r\n */\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n // Pre-speech buffer\r\n private preSpeechBuffer: Float32Array[] = [];\r\n private wasSpeaking = false;\r\n\r\n constructor(worker: UnifiedInferenceWorker, config: SileroVADConfig) {\r\n this.worker = worker;\r\n const sr = config.sampleRate ?? 16000;\r\n this.config = {\r\n modelUrl: config.modelUrl,\r\n backend: config.backend ?? 'wasm',\r\n sampleRate: sr,\r\n threshold: config.threshold ?? 0.5,\r\n preSpeechBufferChunks: config.preSpeechBufferChunks ?? 10,\r\n };\r\n this.chunkSize = sr === 16000 ? 512 : 256;\r\n this.contextSize = sr === 16000 ? 64 : 32;\r\n this.state = new Float32Array(2 * 1 * 128);\r\n this.context = new Float32Array(this.contextSize);\r\n }\r\n\r\n get isLoaded(): boolean { return this._isLoaded; }\r\n get backend(): RuntimeBackend | null { return this._isLoaded ? 'wasm' : null; }\r\n get sampleRate(): number { return this.config.sampleRate; }\r\n get threshold(): number { return this.config.threshold; }\r\n\r\n getChunkSize(): number { return this.chunkSize; }\r\n getChunkDurationMs(): number { return (this.chunkSize / this.config.sampleRate) * 1000; }\r\n\r\n async load(): Promise<VADWorkerModelInfo> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SileroVADUnifiedAdapter.load', {\r\n 'model.url': this.config.modelUrl,\r\n });\r\n\r\n try {\r\n const result = await this.worker.loadVAD({\r\n modelUrl: this.config.modelUrl,\r\n sampleRate: this.config.sampleRate,\r\n });\r\n this._isLoaded = true;\r\n this.loadedGeneration = this.worker.workerGeneration;\r\n\r\n logger.info('SileroVAD loaded via unified worker', {\r\n backend: 'wasm',\r\n loadTimeMs: Math.round(result.loadTimeMs),\r\n sampleRate: this.config.sampleRate,\r\n chunkSize: this.chunkSize,\r\n });\r\n\r\n span?.setAttributes({ 'model.backend': 'wasm', 'model.load_time_ms': result.loadTimeMs });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.model.load_time', result.loadTimeMs, {\r\n model: 'silero-vad-unified',\r\n backend: 'wasm',\r\n });\r\n\r\n return result;\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n }\r\n\r\n async process(audioChunk: Float32Array): Promise<VADResult> {\r\n this.assertLoaded();\r\n\r\n if (audioChunk.length !== this.chunkSize) {\r\n throw new Error(\r\n `Audio chunk must be exactly ${this.chunkSize} samples (got ${audioChunk.length}). ` +\r\n `Use getChunkSize() to get required size.`\r\n );\r\n }\r\n\r\n const audioChunkCopy = new Float32Array(audioChunk);\r\n\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n try {\r\n const startTime = getClock().now();\r\n const result = await this.worker.processVAD(audioChunkCopy, this.state, this.context);\r\n\r\n // Update local state\r\n this.state = result.state;\r\n this.context = audioChunkCopy.slice(-this.contextSize);\r\n\r\n const inferenceTimeMs = getClock().now() - startTime;\r\n const isSpeech = result.probability > this.config.threshold;\r\n\r\n // Pre-speech buffer logic (same as SileroVADInference)\r\n let preSpeechChunks: Float32Array[] | undefined;\r\n\r\n if (isSpeech && !this.wasSpeaking) {\r\n preSpeechChunks = [...this.preSpeechBuffer];\r\n this.preSpeechBuffer = [];\r\n } else if (!isSpeech && !this.wasSpeaking) {\r\n this.preSpeechBuffer.push(new Float32Array(audioChunkCopy));\r\n if (this.preSpeechBuffer.length > this.config.preSpeechBufferChunks) {\r\n this.preSpeechBuffer.shift();\r\n }\r\n } else if (!isSpeech && this.wasSpeaking) {\r\n this.preSpeechBuffer = [];\r\n }\r\n\r\n this.wasSpeaking = isSpeech;\r\n\r\n resolve({\r\n probability: result.probability,\r\n isSpeech,\r\n inferenceTimeMs,\r\n preSpeechChunks,\r\n });\r\n } catch (err) {\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n async reset(): Promise<void> {\r\n this.assertLoaded();\r\n\r\n const newState = await this.worker.resetVAD();\r\n this.state = newState;\r\n this.context = new Float32Array(this.contextSize);\r\n this.preSpeechBuffer = [];\r\n this.wasSpeaking = false;\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this._isLoaded) {\r\n await this.worker.disposeVAD();\r\n this._isLoaded = false;\r\n }\r\n this.state = new Float32Array(2 * 1 * 128);\r\n this.context = new Float32Array(this.contextSize);\r\n this.preSpeechBuffer = [];\r\n this.wasSpeaking = false;\r\n }\r\n\r\n private assertLoaded(): void {\r\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\r\n if (this.worker.workerGeneration !== this.loadedGeneration) {\r\n this._isLoaded = false;\r\n throw new Error('Worker was recovered — model session lost. Call load() again.');\r\n }\r\n }\r\n}\r\n","/**\n * Factory function for A2E inference via UnifiedInferenceWorker\n *\n * Creates an A2EBackend instance with zero-config defaults (HuggingFace CDN).\n * Routes inference through the shared unified worker.\n *\n * @category Inference\n *\n * @example\n * ```typescript\n * import { createA2E, UnifiedInferenceWorker } from '@omote/core';\n *\n * const worker = new UnifiedInferenceWorker();\n * await worker.init();\n *\n * const a2e = createA2E({ unifiedWorker: worker });\n * await a2e.load();\n * const { blendshapes } = await a2e.infer(audioSamples);\n * ```\n */\n\nimport { createLogger } from '../logging';\nimport { DEFAULT_MODEL_URLS } from './defaultModelUrls';\nimport { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\nimport { acquireSharedWorker, releaseSharedWorker } from './sharedLazyWorker';\nimport type { A2EBackend, A2EModelInfo, A2EResult } from './A2EBackend';\nimport type { RuntimeBackend } from '../utils/runtime';\nimport type { InferenceFactoryConfig } from './factoryTypes';\nimport { A2EUnifiedAdapter } from './adapters';\n\nconst logger = createLogger('createA2E');\n\n/**\n * Configuration for the A2E factory\n */\nexport interface CreateA2EConfig extends InferenceFactoryConfig {\n /** URL for the ONNX model. Default: HuggingFace CDN */\n modelUrl?: string;\n /**\n * URL for external model data file (.onnx.data weights).\n * Default: `${modelUrl}.data`\n *\n * Set to `false` to skip external data loading (single-file models only).\n */\n externalDataUrl?: string | false;\n /** Number of identity classes (default: 12) */\n numIdentityClasses?: number;\n}\n\n/**\n * Thin wrapper that lazily creates a UnifiedInferenceWorker on first load().\n */\nclass LazyA2E implements A2EBackend {\n private inner: A2EUnifiedAdapter | null = null;\n private ownedWorker: UnifiedInferenceWorker | null = null;\n private usesSharedWorker = false;\n private readonly config: CreateA2EConfig;\n\n constructor(config: CreateA2EConfig) {\n this.config = config;\n }\n\n get modelId(): 'a2e' { return 'a2e'; }\n get isLoaded(): boolean { return this.inner?.isLoaded ?? false; }\n get backend(): RuntimeBackend | null { return this.inner?.backend ?? null; }\n get chunkSize(): number { return this.inner?.chunkSize ?? 16000; }\n\n async load(): Promise<A2EModelInfo> {\n if (this.inner?.isLoaded) return { backend: 'wasm', loadTimeMs: 0, inputNames: [], outputNames: [] };\n\n const worker = await acquireSharedWorker();\n this.usesSharedWorker = true;\n\n const modelUrl = this.config.modelUrl ?? DEFAULT_MODEL_URLS.lam;\n this.inner = new A2EUnifiedAdapter(worker, {\n modelUrl,\n externalDataUrl: this.config.externalDataUrl,\n numIdentityClasses: this.config.numIdentityClasses,\n });\n return this.inner.load();\n }\n\n async infer(audioSamples: Float32Array, identityIndex?: number): Promise<A2EResult> {\n if (!this.inner) throw new Error('Model not loaded. Call load() first.');\n return this.inner.infer(audioSamples, identityIndex);\n }\n\n async dispose(): Promise<void> {\n if (this.inner) {\n await this.inner.dispose();\n this.inner = null;\n }\n if (this.usesSharedWorker) {\n await releaseSharedWorker();\n this.usesSharedWorker = false;\n } else if (this.ownedWorker) {\n await this.ownedWorker.dispose();\n this.ownedWorker = null;\n }\n }\n}\n\n/**\n * Create an A2E instance via the unified worker.\n *\n * If no `unifiedWorker` is provided, a dedicated worker is created on load().\n *\n * @param config - Factory configuration\n * @returns An A2EBackend instance\n */\nexport function createA2E(config: CreateA2EConfig = {}): A2EBackend {\n const modelUrl = config.modelUrl ?? DEFAULT_MODEL_URLS.lam;\n\n if (config.unifiedWorker) {\n logger.info('Creating A2EUnifiedAdapter (via unified worker)', { modelUrl });\n return new A2EUnifiedAdapter(config.unifiedWorker, {\n modelUrl,\n externalDataUrl: config.externalDataUrl,\n numIdentityClasses: config.numIdentityClasses,\n });\n }\n\n logger.info('Creating A2EUnifiedAdapter (dedicated worker, lazy init)');\n return new LazyA2E(config);\n}\n","/**\r\n * TTSSpeaker — Shared helper for OmoteAvatar TTS integration.\r\n *\r\n * Encapsulates createA2E + TTSPlayback lifecycle so that renderer adapters\r\n * (Three.js, Babylon.js) and the R3F hook can delegate with ~15 lines each.\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { TTSPlayback } from './TTSPlayback';\r\nimport { AudioScheduler } from './AudioScheduler';\r\nimport { ttsToPlaybackFormat, resampleLinear } from './audioConvert';\r\nimport { createA2E } from '../inference/createA2E';\r\nimport { UnifiedInferenceWorker } from '../inference/UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from '../inference/sharedLazyWorker';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\nimport type { TTSBackend } from '../inference/TTSBackend';\r\nimport type { A2EBackend } from '../inference/A2EBackend';\r\nimport type { ExpressionProfile } from './expressionProfile';\r\nimport type { CreateA2EConfig } from '../inference/createA2E';\r\nimport type { FrameSource } from '../orchestration/types';\r\n\r\nconst logger = createLogger('TTSSpeaker');\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface TTSSpeakerConfig {\r\n /** Skip LAM download — audio playback only, no lip sync. Default: false. */\r\n audioOnly?: boolean;\r\n /** Per-character expression weight scaling */\r\n profile?: ExpressionProfile;\r\n /** Identity/style index for A2E model (default: 0) */\r\n identityIndex?: number;\r\n /** Audio playback delay in ms */\r\n audioDelayMs?: number;\r\n /** Enable neutral transition on playback complete */\r\n neutralTransitionEnabled?: boolean;\r\n /** Duration of neutral fade-out in ms */\r\n neutralTransitionMs?: number;\r\n /** Pre-built A2E backend (skip internal createA2E). */\r\n lam?: A2EBackend;\r\n /** LAM model config (only when lam not provided). unifiedWorker is supplied by TTSSpeaker. */\r\n models?: Omit<CreateA2EConfig, 'unifiedWorker'>;\r\n /** Shared unified worker (recommended for iOS) */\r\n unifiedWorker?: UnifiedInferenceWorker;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// TTSSpeaker\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Maximum text buffer length before force-flushing (prevents unbounded memory growth). */\r\nconst MAX_BUFFER_LENGTH = 50_000;\r\n/** Minimum character length before a sentence boundary triggers TTS. */\r\nconst MIN_SENTENCE_LENGTH = 20;\r\n/** Regex to detect sentence boundaries: period, exclamation, question mark, or newline. */\r\nconst SENTENCE_BOUNDARY_RE = /[.!?\\n]\\s*/;\r\n\r\nexport class TTSSpeaker {\r\n private ttsPlayback: TTSPlayback | null = null;\r\n private tts: TTSBackend | null = null;\r\n private ownedLam: A2EBackend | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n private currentAbort: AbortController | null = null;\r\n private _isSpeaking = false;\r\n\r\n // Audio-only mode (no LAM, no blendshapes)\r\n private _audioOnly = false;\r\n private scheduler: AudioScheduler | null = null;\r\n\r\n /** Whether the speaker is currently playing audio. */\r\n get isSpeaking(): boolean {\r\n return this._isSpeaking;\r\n }\r\n\r\n /** Whether this speaker is in audio-only mode (no lip sync). */\r\n get audioOnly(): boolean {\r\n return this._audioOnly;\r\n }\r\n\r\n /** The internal TTSPlayback (implements FrameSource). Null until connect() or in audio-only mode. */\r\n get frameSource(): FrameSource | null {\r\n return this.ttsPlayback?.pipeline ?? null;\r\n }\r\n\r\n /**\r\n * Connect a TTS backend.\r\n *\r\n * By default, the full lip sync pipeline is created (auto-downloads LAM).\r\n * Pass `audioOnly: true` for audio-only mode (no blendshapes, no LAM download).\r\n *\r\n * @param tts - TTS backend to use for speech synthesis\r\n * @param config - Optional configuration for A2E, expression profile, etc.\r\n */\r\n async connect(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\r\n logger.info('Connecting TTS...');\r\n const span = getTelemetry()?.startSpan('TTSSpeaker.connect');\r\n const connectStart = getClock().now();\r\n\r\n // Store TTS backend reference\r\n this.tts = tts;\r\n\r\n // Load TTS if not already loaded\r\n if (!tts.isLoaded) {\r\n await tts.load();\r\n }\r\n\r\n // Audio-only mode: explicit opt-in only\r\n if (config?.audioOnly) {\r\n // Audio-only path: no LAM, no blendshapes, just AudioScheduler\r\n this._audioOnly = true;\r\n this.scheduler = new AudioScheduler({ sampleRate: tts.sampleRate });\r\n getTelemetry()?.recordHistogram(MetricNames.TTS_CONNECT_LATENCY, getClock().now() - connectStart);\r\n span?.end();\r\n logger.info('TTS connected (audio-only mode)');\r\n return;\r\n }\r\n\r\n // Full lip sync path\r\n let lam: A2EBackend;\r\n if (config?.lam) {\r\n lam = config.lam;\r\n } else {\r\n let worker = config?.unifiedWorker;\r\n if (!worker) {\r\n worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n }\r\n\r\n lam = createA2E({\r\n ...config?.models,\r\n unifiedWorker: worker,\r\n });\r\n this.ownedLam = lam;\r\n }\r\n\r\n if (!lam.isLoaded) {\r\n await lam.load();\r\n }\r\n\r\n this.ttsPlayback = new TTSPlayback({\r\n tts,\r\n lam,\r\n profile: config?.profile,\r\n identityIndex: config?.identityIndex,\r\n audioDelayMs: config?.audioDelayMs,\r\n neutralTransitionEnabled: config?.neutralTransitionEnabled,\r\n neutralTransitionMs: config?.neutralTransitionMs,\r\n });\r\n await this.ttsPlayback.initialize();\r\n\r\n getTelemetry()?.recordHistogram(MetricNames.TTS_CONNECT_LATENCY, getClock().now() - connectStart);\r\n span?.end();\r\n logger.info('TTS connected (lip sync mode)');\r\n }\r\n\r\n /**\r\n * Synthesize and play text with lip sync.\r\n * Auto-aborts previous speak if still in progress.\r\n *\r\n * @param text - Text to synthesize and play\r\n * @param options - Optional voice override and abort signal\r\n */\r\n async speak(text: string, options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string }): Promise<void> {\r\n if (!this._audioOnly && !this.ttsPlayback) {\r\n throw new Error('No TTS connected. Call connect() first.');\r\n }\r\n if (this._audioOnly && !this.tts) {\r\n throw new Error('No TTS connected. Call connect() first.');\r\n }\r\n\r\n // Auto-abort previous speak\r\n if (this.currentAbort) {\r\n this.currentAbort.abort();\r\n this.currentAbort = null;\r\n }\r\n\r\n const abort = new AbortController();\r\n this.currentAbort = abort;\r\n\r\n // If caller provided a signal, forward abort\r\n if (options?.signal) {\r\n if (options.signal.aborted) {\r\n abort.abort();\r\n } else {\r\n options.signal.addEventListener('abort', () => abort.abort(), { once: true });\r\n }\r\n }\r\n\r\n this._isSpeaking = true;\r\n const span = getTelemetry()?.startSpan('TTSSpeaker.speak', {\r\n 'text.length': text.length,\r\n });\r\n const speakStart = getClock().now();\r\n try {\r\n if (this._audioOnly) {\r\n await this.speakAudioOnly(text, abort, options?.voice, options?.speed, options?.language);\r\n } else {\r\n await this.ttsPlayback!.speak(text, {\r\n signal: abort.signal,\r\n voice: options?.voice,\r\n speed: options?.speed,\r\n language: options?.language,\r\n });\r\n }\r\n getTelemetry()?.recordHistogram(MetricNames.TTS_SPEAK_LATENCY, getClock().now() - speakStart);\r\n span?.end();\r\n } catch (err) {\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n throw err;\r\n } finally {\r\n this._isSpeaking = false;\r\n if (this.currentAbort === abort) {\r\n this.currentAbort = null;\r\n }\r\n }\r\n }\r\n\r\n /** Audio-only speak: TTS → resample → AudioScheduler (no blendshapes). */\r\n private async speakAudioOnly(text: string, abort: AbortController, voice?: string, speed?: number, language?: string): Promise<void> {\r\n if (!this.tts || !this.scheduler) return;\r\n\r\n for await (const chunk of this.tts.stream(text, { signal: abort.signal, voice, speed, language, singleShot: true })) {\r\n if (abort.signal.aborted) return;\r\n // Resample from TTS rate to scheduler rate (usually same, but just in case)\r\n const samples = resampleLinear(chunk.audio, this.tts.sampleRate, this.scheduler.sampleRate);\r\n await this.scheduler.schedule(samples);\r\n }\r\n\r\n // Wait for playback to complete\r\n if (!abort.signal.aborted) {\r\n await this.waitForSchedulerComplete(abort.signal);\r\n }\r\n }\r\n\r\n /** Poll scheduler until all audio has played. */\r\n private waitForSchedulerComplete(signal: AbortSignal): Promise<void> {\r\n return new Promise(resolve => {\r\n const check = () => {\r\n if (signal.aborted || !this.scheduler) { resolve(); return; }\r\n if (this.scheduler.isComplete()) { resolve(); return; }\r\n setTimeout(check, 50);\r\n };\r\n check();\r\n });\r\n }\r\n\r\n /**\r\n * Stream text token-by-token with automatic sentence buffering.\r\n * Designed for LLM token-by-token output. Sentences are detected at\r\n * boundary characters (.!?\\n) with a minimum length threshold, then\r\n * synthesized and played with lip sync.\r\n *\r\n * Auto-aborts previous speak/streamText if still in progress.\r\n *\r\n * @param options - Optional voice override and abort signal\r\n * @returns Sink with push() and end() methods\r\n */\r\n async streamText(options: {\r\n signal?: AbortSignal;\r\n voice?: string;\r\n speed?: number;\r\n language?: string;\r\n }): Promise<{\r\n push: (token: string) => void;\r\n end: () => Promise<void>;\r\n }> {\r\n if (!this._audioOnly && (!this.ttsPlayback || !this.tts)) {\r\n throw new Error('No TTS connected. Call connect() first.');\r\n }\r\n if (this._audioOnly && !this.tts) {\r\n throw new Error('No TTS connected. Call connect() first.');\r\n }\r\n\r\n // Auto-abort previous speak/streamText\r\n if (this.currentAbort) {\r\n this.currentAbort.abort();\r\n this.currentAbort = null;\r\n }\r\n\r\n const abort = new AbortController();\r\n this.currentAbort = abort;\r\n\r\n // Forward external signal\r\n if (options?.signal) {\r\n if (options.signal.aborted) {\r\n abort.abort();\r\n } else {\r\n options.signal.addEventListener('abort', () => abort.abort(), { once: true });\r\n }\r\n }\r\n\r\n const tts = this.tts!;\r\n const voice = options?.voice;\r\n const speed = options?.speed;\r\n const language = options?.language;\r\n\r\n // Audio-only mode: route to scheduler directly\r\n if (this._audioOnly) {\r\n return this.streamTextAudioOnly(abort, tts, voice, speed, language);\r\n }\r\n\r\n const pipeline = this.ttsPlayback!.pipeline!;\r\n\r\n pipeline.start();\r\n\r\n let buffer = '';\r\n let ended = false;\r\n // Sequential promise chain for sentence processing\r\n let processChain = Promise.resolve();\r\n\r\n const enqueueSentence = (sentence: string) => {\r\n processChain = processChain.then(async () => {\r\n if (abort.signal.aborted) return;\r\n try {\r\n for await (const chunk of tts.stream(sentence, { signal: abort.signal, voice, speed, language })) {\r\n if (abort.signal.aborted) return;\r\n const pcm16 = ttsToPlaybackFormat(chunk.audio, tts.sampleRate, 16000);\r\n await pipeline.onAudioChunk(pcm16);\r\n }\r\n } catch (err) {\r\n if (!abort.signal.aborted) {\r\n logger.error('streamText sentence error', { message: (err as Error).message });\r\n }\r\n }\r\n });\r\n };\r\n\r\n const checkBuffer = () => {\r\n while (true) {\r\n const match = SENTENCE_BOUNDARY_RE.exec(buffer);\r\n if (!match || match.index === undefined) break;\r\n\r\n const cutPoint = match.index + match[0].length;\r\n const candidate = buffer.substring(0, cutPoint);\r\n\r\n if (candidate.trim().length < MIN_SENTENCE_LENGTH) break;\r\n\r\n buffer = buffer.substring(cutPoint);\r\n enqueueSentence(candidate.trim());\r\n }\r\n };\r\n\r\n return {\r\n push: (token: string) => {\r\n if (ended) throw new Error('Cannot push after end()');\r\n if (abort.signal.aborted) return; // no-op after abort\r\n if (!token) return; // empty string guard\r\n\r\n buffer += token;\r\n\r\n if (buffer.length >= MAX_BUFFER_LENGTH) {\r\n logger.warn('Text buffer exceeded max length, force-flushing');\r\n if (buffer.trim()) { enqueueSentence(buffer.trim()); buffer = ''; }\r\n }\r\n\r\n if (!this._isSpeaking) {\r\n this._isSpeaking = true;\r\n }\r\n\r\n checkBuffer();\r\n },\r\n\r\n end: async () => {\r\n if (ended) return;\r\n ended = true;\r\n\r\n const unsubs: Array<() => void> = [];\r\n try {\r\n if (abort.signal.aborted) {\r\n return;\r\n }\r\n\r\n // Flush remaining buffer\r\n if (buffer.trim()) {\r\n enqueueSentence(buffer.trim());\r\n buffer = '';\r\n }\r\n\r\n // Wait for all sentences to process\r\n await processChain;\r\n\r\n if (abort.signal.aborted) {\r\n return;\r\n }\r\n\r\n await pipeline.end();\r\n\r\n // Wait for playback to complete or stop, with abort support\r\n await new Promise<void>((resolve) => {\r\n let resolved = false;\r\n const done = () => {\r\n if (resolved) return;\r\n resolved = true;\r\n resolve();\r\n };\r\n if (abort.signal.aborted) { resolve(); return; }\r\n unsubs.push(pipeline.on('playback:complete', done));\r\n unsubs.push(pipeline.on('playback:stop', done));\r\n const onAbort = () => done();\r\n abort.signal.addEventListener('abort', onAbort);\r\n unsubs.push(() => abort.signal.removeEventListener('abort', onAbort));\r\n });\r\n } finally {\r\n unsubs.forEach(fn => fn());\r\n this._isSpeaking = false;\r\n if (this.currentAbort === abort) this.currentAbort = null;\r\n }\r\n },\r\n };\r\n }\r\n\r\n /** streamText in audio-only mode: TTS → AudioScheduler (no blendshapes). */\r\n private streamTextAudioOnly(\r\n abort: AbortController,\r\n tts: TTSBackend,\r\n voice?: string,\r\n speed?: number,\r\n language?: string,\r\n ): { push: (token: string) => void; end: () => Promise<void> } {\r\n let buffer = '';\r\n let ended = false;\r\n let processChain = Promise.resolve();\r\n\r\n const enqueueSentence = (sentence: string) => {\r\n processChain = processChain.then(async () => {\r\n if (abort.signal.aborted) return;\r\n try {\r\n for await (const chunk of tts.stream(sentence, { signal: abort.signal, voice, speed, language })) {\r\n if (abort.signal.aborted) return;\r\n const samples = resampleLinear(chunk.audio, tts.sampleRate, this.scheduler!.sampleRate);\r\n await this.scheduler!.schedule(samples);\r\n }\r\n } catch (err) {\r\n if (!abort.signal.aborted) {\r\n logger.error('streamText sentence error', { message: (err as Error).message });\r\n }\r\n }\r\n });\r\n };\r\n\r\n const checkBuffer = () => {\r\n while (true) {\r\n const match = SENTENCE_BOUNDARY_RE.exec(buffer);\r\n if (!match || match.index === undefined) break;\r\n const cutPoint = match.index + match[0].length;\r\n const candidate = buffer.substring(0, cutPoint);\r\n if (candidate.trim().length < MIN_SENTENCE_LENGTH) break;\r\n buffer = buffer.substring(cutPoint);\r\n enqueueSentence(candidate.trim());\r\n }\r\n };\r\n\r\n return {\r\n push: (token: string) => {\r\n if (ended) throw new Error('Cannot push after end()');\r\n if (abort.signal.aborted) return;\r\n if (!token) return;\r\n buffer += token;\r\n if (buffer.length >= MAX_BUFFER_LENGTH) {\r\n logger.warn('Text buffer exceeded max length, force-flushing');\r\n if (buffer.trim()) { enqueueSentence(buffer.trim()); buffer = ''; }\r\n }\r\n if (!this._isSpeaking) this._isSpeaking = true;\r\n checkBuffer();\r\n },\r\n end: async () => {\r\n if (ended) return;\r\n ended = true;\r\n if (abort.signal.aborted) {\r\n this._isSpeaking = false;\r\n if (this.currentAbort === abort) this.currentAbort = null;\r\n return;\r\n }\r\n if (buffer.trim()) {\r\n enqueueSentence(buffer.trim());\r\n buffer = '';\r\n }\r\n await processChain;\r\n if (!abort.signal.aborted) {\r\n await this.waitForSchedulerComplete(abort.signal);\r\n }\r\n this._isSpeaking = false;\r\n if (this.currentAbort === abort) this.currentAbort = null;\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Warm up AudioContext for iOS/Safari autoplay policy.\r\n * Call from a user gesture handler (click/tap) before speak().\r\n */\r\n async warmup(): Promise<void> {\r\n if (this.scheduler) await this.scheduler.warmup();\r\n if (this.ttsPlayback) await this.ttsPlayback.warmup();\r\n }\r\n\r\n /** Abort current speak if any. Triggers neutral transition on PlaybackPipeline. */\r\n stop(): void {\r\n if (this.currentAbort) {\r\n this.currentAbort.abort();\r\n this.currentAbort = null;\r\n getTelemetry()?.incrementCounter(MetricNames.TTS_SPEAK_ABORTED);\r\n }\r\n // Stop PlaybackPipeline to trigger neutral fade-out (mouth close)\r\n this.ttsPlayback?.pipeline?.stop();\r\n this.scheduler?.cancelAll();\r\n this._isSpeaking = false;\r\n }\r\n\r\n /** Clean teardown of all owned resources. */\r\n async dispose(): Promise<void> {\r\n logger.debug('Disposing TTSSpeaker');\r\n this.stop();\r\n if (this.ttsPlayback) {\r\n await this.ttsPlayback.dispose();\r\n this.ttsPlayback = null;\r\n }\r\n if (this.scheduler) {\r\n this.scheduler.dispose();\r\n this.scheduler = null;\r\n }\r\n this.tts = null;\r\n this._audioOnly = false;\r\n if (this.ownedLam) {\r\n await this.ownedLam.dispose();\r\n this.ownedLam = null;\r\n }\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n } else if (this.ownedWorker) {\r\n await this.ownedWorker.dispose();\r\n this.ownedWorker = null;\r\n }\r\n }\r\n}\r\n","/**\r\n * Factory function for Kokoro TTS via UnifiedInferenceWorker\r\n *\r\n * When called without a `unifiedWorker`, a dedicated worker is created\r\n * automatically on the first `load()` call. Pass a shared worker when using\r\n * VoiceOrchestrator or multiple models to avoid extra WASM instances.\r\n *\r\n * @category Inference\r\n *\r\n * @example Standalone (auto-creates worker)\r\n * ```typescript\r\n * import { createKokoroTTS } from '@omote/core';\r\n *\r\n * const tts = createKokoroTTS({ defaultVoice: 'af_heart' });\r\n * await tts.load();\r\n *\r\n * for await (const chunk of tts.stream(\"Hello world!\")) {\r\n * playbackPipeline.feedBuffer(chunk.audio);\r\n * }\r\n * ```\r\n *\r\n * @example With shared worker\r\n * ```typescript\r\n * const tts = createKokoroTTS({ defaultVoice: 'af_heart', unifiedWorker: worker });\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { KokoroTTSUnifiedAdapter } from './adapters';\r\nimport { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from './sharedLazyWorker';\r\nimport type { InferenceFactoryConfig } from './factoryTypes';\r\nimport type { TTSBackend, TTSStreamOptions, TTSChunk } from './TTSBackend';\r\nimport type { KokoroTTSConfig, KokoroTTSModelInfo } from './KokoroTTSTypes';\r\n\r\nconst logger = createLogger('createKokoroTTS');\r\n\r\n/**\r\n * Configuration for the Kokoro TTS factory\r\n */\r\nexport interface CreateKokoroTTSConfig extends KokoroTTSConfig, InferenceFactoryConfig {\r\n}\r\n\r\n/**\r\n * Thin wrapper that lazily creates a UnifiedInferenceWorker on first load().\r\n * Returned when no unifiedWorker is provided.\r\n */\r\nclass LazyKokoroTTS implements TTSBackend {\r\n private inner: KokoroTTSUnifiedAdapter | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n private readonly config: KokoroTTSConfig;\r\n\r\n constructor(config: KokoroTTSConfig) {\r\n this.config = config;\r\n }\r\n\r\n get isLoaded(): boolean { return this.inner?.isLoaded ?? false; }\r\n get sampleRate(): number { return 24000; }\r\n\r\n async load(): Promise<KokoroTTSModelInfo> {\r\n if (this.inner?.isLoaded) return { backend: 'wasm', loadTimeMs: 0, defaultVoice: this.config.defaultVoice ?? 'af_heart' };\r\n\r\n const worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n\r\n this.inner = new KokoroTTSUnifiedAdapter(worker, this.config);\r\n return this.inner.load();\r\n }\r\n\r\n async *stream(text: string, options?: TTSStreamOptions): AsyncGenerator<TTSChunk> {\r\n if (!this.inner) throw new Error('Model not loaded. Call load() first.');\r\n yield* this.inner.stream(text, options);\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this.inner) {\r\n await this.inner.dispose();\r\n this.inner = null;\r\n }\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n } else if (this.ownedWorker) {\r\n await this.ownedWorker.dispose();\r\n this.ownedWorker = null;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Create a Kokoro TTS instance via the unified worker.\r\n *\r\n * If no `unifiedWorker` is provided, a dedicated worker is created on load().\r\n *\r\n * @param config - Factory configuration\r\n * @returns A TTSBackend instance\r\n */\r\nexport function createKokoroTTS(config: CreateKokoroTTSConfig = {}): TTSBackend {\r\n if (config.unifiedWorker) {\r\n logger.info('Creating KokoroTTSUnifiedAdapter (unified worker)');\r\n return new KokoroTTSUnifiedAdapter(config.unifiedWorker, config);\r\n }\r\n\r\n logger.info('Creating KokoroTTSUnifiedAdapter (dedicated worker, lazy init)');\r\n return new LazyKokoroTTS(config);\r\n}\r\n","/**\n * createTTSPlayer — Zero-config TTS player for audio-only playback.\n *\n * Speaks text through speakers without an avatar. No LAM download, no lip sync.\n *\n * @example\n * ```typescript\n * import { createTTSPlayer } from '@omote/core';\n *\n * const player = createTTSPlayer();\n * await player.load();\n * await player.speak(\"Hello world!\");\n *\n * // Streaming:\n * const stream = await player.streamText({});\n * stream.push(\"Hello \");\n * stream.push(\"world!\");\n * await stream.end();\n * ```\n *\n * @category Audio\n */\n\nimport { TTSSpeaker } from './TTSSpeaker';\nimport { createKokoroTTS } from '../inference/createKokoroTTS';\nimport { UnifiedInferenceWorker } from '../inference/UnifiedInferenceWorker';\nimport { acquireSharedWorker, releaseSharedWorker } from '../inference/sharedLazyWorker';\nimport type { TTSBackend } from '../inference/TTSBackend';\n\nexport interface CreateTTSPlayerConfig {\n /** Voice to use (default: 'af_heart') */\n voice?: string;\n /** Model URL override */\n modelUrl?: string;\n /** Voice data base URL override */\n voiceBaseUrl?: string;\n /** Shared unified worker (created automatically if not provided) */\n unifiedWorker?: UnifiedInferenceWorker;\n}\n\n/**\n * Zero-config TTS player. Speak text through speakers without an avatar.\n *\n * Uses Kokoro TTS (82M q8, ~92MB) with automatic worker creation.\n * No LAM model is downloaded — audio plays directly through AudioScheduler.\n */\nexport function createTTSPlayer(config?: CreateTTSPlayerConfig): TTSPlayer {\n return new TTSPlayer(config);\n}\n\n/**\n * Thin wrapper: TTSSpeaker in audio-only mode + delegated load().\n */\nexport class TTSPlayer extends TTSSpeaker {\n private backend: TTSBackend | null = null;\n private ttsWorker: UnifiedInferenceWorker | null = null;\n private ttsPlayerUsesSharedWorker = false;\n private ttsConfig: CreateTTSPlayerConfig;\n\n constructor(config?: CreateTTSPlayerConfig) {\n super();\n this.ttsConfig = config ?? {};\n }\n\n /** Load TTS model and connect in audio-only mode. */\n async load(): Promise<void> {\n let worker = this.ttsConfig.unifiedWorker;\n if (!worker) {\n worker = await acquireSharedWorker();\n this.ttsPlayerUsesSharedWorker = true;\n }\n\n this.backend = createKokoroTTS({\n defaultVoice: this.ttsConfig.voice,\n modelUrl: this.ttsConfig.modelUrl,\n voiceBaseUrl: this.ttsConfig.voiceBaseUrl,\n unifiedWorker: worker,\n });\n\n await this.backend.load();\n await this.connect(this.backend, { audioOnly: true });\n }\n\n /** Whether the TTS model is loaded and ready. */\n get isLoaded(): boolean {\n return this.backend?.isLoaded ?? false;\n }\n\n async dispose(): Promise<void> {\n await super.dispose();\n if (this.ttsPlayerUsesSharedWorker) {\n await releaseSharedWorker();\n this.ttsPlayerUsesSharedWorker = false;\n } else if (this.ttsWorker) {\n await this.ttsWorker.dispose();\n this.ttsWorker = null;\n }\n }\n}\n","/**\r\n * Factory function for SenseVoice ASR via UnifiedInferenceWorker\r\n *\r\n * @category Inference\r\n *\r\n * @example\r\n * ```typescript\r\n * import { createSenseVoice, UnifiedInferenceWorker } from '@omote/core';\r\n *\r\n * const worker = new UnifiedInferenceWorker();\r\n * await worker.init();\r\n *\r\n * const asr = createSenseVoice({\r\n * modelUrl: '/models/sensevoice/model.int8.onnx',\r\n * unifiedWorker: worker,\r\n * });\r\n * await asr.load();\r\n * const { text, emotion } = await asr.transcribe(audioSamples);\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { DEFAULT_MODEL_URLS } from './defaultModelUrls';\r\nimport { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from './sharedLazyWorker';\r\nimport type { InferenceFactoryConfig } from './factoryTypes';\r\nimport type { SenseVoiceBackend, SenseVoiceLanguage, SenseVoiceModelInfo, SenseVoiceResult } from './SenseVoiceTypes';\r\nimport { SenseVoiceUnifiedAdapter } from './adapters';\r\n\r\nconst logger = createLogger('createSenseVoice');\r\n\r\n// Re-export SenseVoiceBackend for consumers\r\nexport type { SenseVoiceBackend } from './SenseVoiceTypes';\r\n\r\n/**\r\n * Configuration for the SenseVoice factory\r\n */\r\nexport interface CreateSenseVoiceConfig extends InferenceFactoryConfig {\r\n /** Path or URL to model.int8.onnx (239MB). Default: HuggingFace CDN */\r\n modelUrl?: string;\r\n /** Path or URL to tokens.txt vocabulary file (default: sibling of modelUrl) */\r\n tokensUrl?: string;\r\n /** Language hint (default: 'auto') */\r\n language?: SenseVoiceLanguage;\r\n /** Text normalization (default: 'with_itn') */\r\n textNorm?: 'with_itn' | 'without_itn';\r\n}\r\n\r\n/**\r\n * Thin wrapper that lazily creates a UnifiedInferenceWorker on first load().\r\n */\r\nclass LazySenseVoice implements SenseVoiceBackend {\r\n private inner: SenseVoiceUnifiedAdapter | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n private readonly config: CreateSenseVoiceConfig;\r\n\r\n constructor(config: CreateSenseVoiceConfig) {\r\n this.config = config;\r\n }\r\n\r\n get isLoaded(): boolean { return this.inner?.isLoaded ?? false; }\r\n get backend(): 'wasm' | 'webgpu' | null { return this.inner?.backend ?? null; }\r\n\r\n async load(onProgress?: (loaded: number, total: number) => void): Promise<SenseVoiceModelInfo> {\r\n if (this.inner?.isLoaded) return { backend: 'wasm', loadTimeMs: 0 } as SenseVoiceModelInfo;\r\n\r\n const worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n\r\n const modelUrl = this.config.modelUrl ?? DEFAULT_MODEL_URLS.senseVoice;\r\n this.inner = new SenseVoiceUnifiedAdapter(worker, {\r\n modelUrl,\r\n tokensUrl: this.config.tokensUrl,\r\n language: this.config.language,\r\n textNorm: this.config.textNorm,\r\n });\r\n return this.inner.load(onProgress);\r\n }\r\n\r\n async transcribe(audioSamples: Float32Array): Promise<SenseVoiceResult> {\r\n if (!this.inner) throw new Error('Model not loaded. Call load() first.');\r\n return this.inner.transcribe(audioSamples);\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this.inner) {\r\n await this.inner.dispose();\r\n this.inner = null;\r\n }\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n } else if (this.ownedWorker) {\r\n await this.ownedWorker.dispose();\r\n this.ownedWorker = null;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Create a SenseVoice ASR instance via the unified worker.\r\n *\r\n * If no `unifiedWorker` is provided, a dedicated worker is created on load().\r\n *\r\n * @param config - Factory configuration\r\n * @returns A SenseVoiceBackend instance\r\n */\r\nexport function createSenseVoice(config: CreateSenseVoiceConfig = {}): SenseVoiceBackend {\r\n const modelUrl = config.modelUrl ?? DEFAULT_MODEL_URLS.senseVoice;\r\n\r\n if (config.unifiedWorker) {\r\n logger.info('Creating SenseVoiceUnifiedAdapter (shared unified worker)');\r\n return new SenseVoiceUnifiedAdapter(config.unifiedWorker, {\r\n modelUrl,\r\n tokensUrl: config.tokensUrl,\r\n language: config.language,\r\n textNorm: config.textNorm,\r\n }) as SenseVoiceBackend;\r\n }\r\n\r\n logger.info('Creating SenseVoiceUnifiedAdapter (dedicated worker, lazy init)');\r\n return new LazySenseVoice(config);\r\n}\r\n","/**\r\n * Factory function for Silero VAD via UnifiedInferenceWorker\r\n *\r\n * @category Inference\r\n *\r\n * @example\r\n * ```typescript\r\n * import { createSileroVAD, UnifiedInferenceWorker } from '@omote/core';\r\n *\r\n * const worker = new UnifiedInferenceWorker();\r\n * await worker.init();\r\n *\r\n * const vad = createSileroVAD({\r\n * modelUrl: '/models/silero-vad.onnx',\r\n * unifiedWorker: worker,\r\n * });\r\n * await vad.load();\r\n * const result = await vad.process(audioChunk);\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { DEFAULT_MODEL_URLS } from './defaultModelUrls';\r\nimport { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from './sharedLazyWorker';\r\nimport type { InferenceFactoryConfig } from './factoryTypes';\r\nimport type { SileroVADConfig, SileroVADBackend, VADModelInfo, VADWorkerModelInfo, VADResult } from './SileroVADTypes';\r\nimport type { RuntimeBackend } from '../utils/runtime';\r\nimport { SileroVADUnifiedAdapter } from './adapters';\r\n\r\nconst logger = createLogger('createSileroVAD');\r\n\r\n// Re-export SileroVADBackend for consumers\r\nexport type { SileroVADBackend } from './SileroVADTypes';\r\n\r\n/**\r\n * Configuration for the Silero VAD factory\r\n */\r\nexport interface SileroVADFactoryConfig extends Omit<SileroVADConfig, 'modelUrl'>, InferenceFactoryConfig {\r\n /** Path or URL to the ONNX model. Default: HuggingFace CDN */\r\n modelUrl?: string;\r\n}\r\n\r\n/**\r\n * Thin wrapper that lazily creates a UnifiedInferenceWorker on first load().\r\n */\r\nclass LazySileroVAD implements SileroVADBackend {\r\n private inner: SileroVADUnifiedAdapter | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n private readonly config: SileroVADFactoryConfig;\r\n\r\n constructor(config: SileroVADFactoryConfig) {\r\n this.config = config;\r\n }\r\n\r\n get isLoaded(): boolean { return this.inner?.isLoaded ?? false; }\r\n get backend(): RuntimeBackend | null { return this.inner?.backend ?? null; }\r\n get sampleRate(): number { return this.config.sampleRate ?? 16000; }\r\n get threshold(): number { return this.config.threshold ?? 0.5; }\r\n\r\n async load(): Promise<VADModelInfo | VADWorkerModelInfo> {\r\n if (this.inner?.isLoaded) return { backend: 'wasm', loadTimeMs: 0, inputNames: [], outputNames: [], sampleRate: this.sampleRate, chunkSize: this.sampleRate === 16000 ? 512 : 256 };\r\n\r\n const worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n\r\n const modelUrl = this.config.modelUrl ?? DEFAULT_MODEL_URLS.sileroVad;\r\n const resolvedConfig: SileroVADConfig = { ...this.config, modelUrl };\r\n this.inner = new SileroVADUnifiedAdapter(worker, resolvedConfig);\r\n return this.inner.load();\r\n }\r\n\r\n async process(audioChunk: Float32Array): Promise<VADResult> {\r\n if (!this.inner) throw new Error('Model not loaded. Call load() first.');\r\n return this.inner.process(audioChunk);\r\n }\r\n\r\n reset(): void {\r\n if (this.inner) this.inner.reset();\r\n }\r\n\r\n getChunkSize(): number {\r\n return (this.config.sampleRate ?? 16000) === 16000 ? 512 : 256;\r\n }\r\n\r\n getChunkDurationMs(): number {\r\n const sr = this.config.sampleRate ?? 16000;\r\n return (this.getChunkSize() / sr) * 1000;\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this.inner) {\r\n await this.inner.dispose();\r\n this.inner = null;\r\n }\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n } else if (this.ownedWorker) {\r\n await this.ownedWorker.dispose();\r\n this.ownedWorker = null;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Create a Silero VAD instance via the unified worker.\r\n *\r\n * If no `unifiedWorker` is provided, a dedicated worker is created on load().\r\n *\r\n * @param config - Factory configuration\r\n * @returns A SileroVADBackend instance\r\n */\r\nexport function createSileroVAD(config: SileroVADFactoryConfig = {}): SileroVADBackend {\r\n const modelUrl = config.modelUrl ?? DEFAULT_MODEL_URLS.sileroVad;\r\n const resolvedConfig: SileroVADConfig = { ...config, modelUrl };\r\n\r\n if (config.unifiedWorker) {\r\n logger.info('Creating SileroVADUnifiedAdapter (shared unified worker)');\r\n return new SileroVADUnifiedAdapter(config.unifiedWorker, resolvedConfig) as SileroVADBackend;\r\n }\r\n\r\n logger.info('Creating SileroVADUnifiedAdapter (dedicated worker, lazy init)');\r\n return new LazySileroVAD(config);\r\n}\r\n","/**\r\n * SpeechListener — Standalone listening primitive.\r\n *\r\n * Composes: MicrophoneCapture → SileroVAD → SenseVoice ASR → transcript events.\r\n * Used independently or alongside TTSSpeaker and VoiceOrchestrator.\r\n *\r\n * Does NOT handle TTS, LAM, or response routing — those belong to TTSSpeaker\r\n * and VoiceOrchestrator respectively.\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { EventEmitter } from '../events/EventEmitter';\r\nimport { MicrophoneCapture } from './MicrophoneCapture';\r\nimport { int16ToFloat32 } from './audioUtils';\r\nimport { createSenseVoice } from '../inference/createSenseVoice';\r\nimport { createSileroVAD } from '../inference/createSileroVAD';\r\nimport { UnifiedInferenceWorker } from '../inference/UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from '../inference/sharedLazyWorker';\r\nimport { isIOS } from '../utils/runtime';\r\nimport { calculateRMS } from '../animation/audioEnergy';\r\nimport { DEFAULT_MODEL_URLS } from '../inference/defaultModelUrls';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { ErrorCodes } from '../logging/ErrorCodes';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\nimport type { OmoteEvents } from '../events';\r\nimport type { SenseVoiceBackend, SenseVoiceLanguage } from '../inference/SenseVoiceTypes';\r\nimport type { SileroVADBackend } from '../inference/SileroVADTypes';\r\nimport type { LoadingProgress, TranscriptResult } from '../orchestration/types';\r\n\r\nconst logger = createLogger('SpeechListener');\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface SpeechListenerConfig {\r\n /** Pre-built backends — skip internal factory creation. */\r\n backends?: {\r\n asr: SenseVoiceBackend;\r\n vad: SileroVADBackend;\r\n };\r\n /** External unified worker (reuse across pipelines). */\r\n unifiedWorker?: UnifiedInferenceWorker;\r\n /** URLs and options for model loading (when backends not provided). */\r\n models?: {\r\n senseVoice: { modelUrl: string; tokensUrl?: string; language?: string; textNorm?: 'with_itn' | 'without_itn' };\r\n vad: { modelUrl: string; threshold?: number; preSpeechBufferChunks?: number };\r\n };\r\n\r\n /** Base silence timeout in ms (default: 500) */\r\n silenceTimeoutMs?: number;\r\n /** Extended silence timeout for long utterances (default: 700) */\r\n silenceTimeoutExtendedMs?: number;\r\n /** Enable adaptive timeout based on speech duration (default: true) */\r\n adaptiveTimeout?: boolean;\r\n /** Minimum audio duration in seconds (default: 0.3) */\r\n minAudioDurationSec?: number;\r\n /** Minimum audio energy (default: 0.02) */\r\n minAudioEnergy?: number;\r\n /** Enable audio normalization for quiet audio (default: true) */\r\n normalizeAudio?: boolean;\r\n\r\n /** Progressive transcription interval — desktop (default: 500ms) */\r\n progressiveIntervalMs?: number;\r\n /** Progressive transcription interval — iOS (default: 800ms) */\r\n progressiveIntervalIosMs?: number;\r\n /** Coverage threshold to use progressive result (default: 0.8) */\r\n progressiveCoverageThreshold?: number;\r\n /** Minimum samples before progressive transcription starts (default: 8000) */\r\n progressiveMinSamples?: number;\r\n /** Timeout for individual transcribe() calls (default: 10000ms) */\r\n transcriptionTimeoutMs?: number;\r\n}\r\n\r\nexport type SpeechListenerState = 'idle' | 'loading' | 'ready' | 'listening' | 'processing' | 'paused';\r\n\r\nexport interface SpeechListenerEvents {\r\n 'state': SpeechListenerState;\r\n 'loading:progress': LoadingProgress;\r\n 'transcript': TranscriptResult;\r\n 'speech:start': void;\r\n 'speech:end': { durationMs: number };\r\n 'audio:level': { rms: number; peak: number };\r\n 'audio:chunk': Float32Array;\r\n 'error': Error;\r\n [key: string]: unknown;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// SpeechListener\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class SpeechListener extends EventEmitter<SpeechListenerEvents> {\r\n private readonly config: SpeechListenerConfig;\r\n\r\n // State\r\n private _state: SpeechListenerState = 'idle';\r\n private epoch = 0;\r\n\r\n // Models\r\n private asr: SenseVoiceBackend | null = null;\r\n private vad: SileroVADBackend | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n\r\n // Mic\r\n private mic: MicrophoneCapture | null = null;\r\n private omoteEvents = new EventEmitter<OmoteEvents>();\r\n\r\n // Listener cleanup\r\n private _unsubChunk: (() => void) | null = null;\r\n private _unsubLevel: (() => void) | null = null;\r\n\r\n // Audio accumulation\r\n private static readonly MAX_AUDIO_BUFFER_SAMPLES = 16000 * 30; // 30 seconds\r\n private audioBuffer: Float32Array[] = [];\r\n private audioBufferSamples = 0;\r\n private speechStartTime = 0;\r\n private silenceTimer: ReturnType<typeof setTimeout> | null = null;\r\n private isSpeechActive = false;\r\n\r\n // Progressive transcription\r\n private progressiveTimer: ReturnType<typeof setInterval> | null = null;\r\n private progressivePromise: Promise<void> | null = null;\r\n private lastProgressiveResult: TranscriptResult | null = null;\r\n private lastProgressiveSamples = 0;\r\n\r\n // ASR error recovery\r\n private asrErrorCount = 0;\r\n private progressiveErrorCount = 0;\r\n\r\n /** Current listener state */\r\n get state(): SpeechListenerState { return this._state; }\r\n\r\n constructor(config?: SpeechListenerConfig) {\r\n super();\r\n this.config = config ?? {};\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Model loading\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Load ASR + VAD models. Only loads speech recognition models,\r\n * NOT TTS or LAM (those belong to TTSSpeaker).\r\n */\r\n async loadModels(): Promise<void> {\r\n this.setState('loading');\r\n const span = getTelemetry()?.startSpan('SpeechListener.loadModels', {\r\n 'model.name': 'speech-listener',\r\n });\r\n\r\n try {\r\n if (this.config.backends) {\r\n // Use pre-built backends\r\n const { asr, vad } = this.config.backends;\r\n if (!asr.isLoaded) await asr.load();\r\n if (!vad.isLoaded) await vad.load();\r\n this.asr = asr;\r\n this.vad = vad;\r\n } else if (this.config.models) {\r\n // Create from factories\r\n let worker = this.config.unifiedWorker;\r\n if (!worker && UnifiedInferenceWorker.isSupported()) {\r\n worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n }\r\n if (!worker) {\r\n throw new Error('UnifiedInferenceWorker is required but could not be created');\r\n }\r\n this.ownedWorker = worker;\r\n\r\n this.emitProgress('Loading ASR', 0, 2, 0);\r\n\r\n const asr = createSenseVoice({\r\n modelUrl: this.config.models.senseVoice.modelUrl,\r\n tokensUrl: this.config.models.senseVoice.tokensUrl,\r\n language: this.config.models.senseVoice.language as SenseVoiceLanguage,\r\n textNorm: this.config.models.senseVoice.textNorm,\r\n unifiedWorker: worker,\r\n });\r\n const vad = createSileroVAD({\r\n modelUrl: this.config.models.vad.modelUrl,\r\n threshold: this.config.models.vad.threshold,\r\n unifiedWorker: worker,\r\n });\r\n\r\n const [asrResult, vadResult] = await Promise.allSettled([\r\n asr.load(),\r\n vad.load(),\r\n ]);\r\n\r\n if (asrResult.status === 'rejected') throw asrResult.reason;\r\n if (vadResult.status === 'rejected') throw vadResult.reason;\r\n\r\n this.asr = asr;\r\n this.vad = vad;\r\n\r\n this.emitProgress('Models loaded', 100, 2, 2);\r\n } else {\r\n // Zero-config: use DEFAULT_MODEL_URLS (HuggingFace CDN or user-configured)\r\n const models = {\r\n senseVoice: { modelUrl: DEFAULT_MODEL_URLS.senseVoice },\r\n vad: { modelUrl: DEFAULT_MODEL_URLS.sileroVad },\r\n };\r\n\r\n let worker = this.config.unifiedWorker;\r\n if (!worker && UnifiedInferenceWorker.isSupported()) {\r\n worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n }\r\n if (!worker) {\r\n throw new Error('UnifiedInferenceWorker is required but could not be created');\r\n }\r\n this.ownedWorker = worker;\r\n\r\n this.emitProgress('Loading ASR', 0, 2, 0);\r\n\r\n const asr = createSenseVoice({\r\n modelUrl: models.senseVoice.modelUrl,\r\n unifiedWorker: worker,\r\n });\r\n const vad = createSileroVAD({\r\n modelUrl: models.vad.modelUrl,\r\n unifiedWorker: worker,\r\n });\r\n\r\n const [asrResult, vadResult] = await Promise.allSettled([\r\n asr.load(),\r\n vad.load(),\r\n ]);\r\n\r\n if (asrResult.status === 'rejected') throw asrResult.reason;\r\n if (vadResult.status === 'rejected') throw vadResult.reason;\r\n\r\n this.asr = asr;\r\n this.vad = vad;\r\n\r\n this.emitProgress('Models loaded', 100, 2, 2);\r\n }\r\n\r\n span?.end();\r\n this.setState('ready');\r\n logger.info('SpeechListener models loaded');\r\n } catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n span?.endWithError(err);\r\n logger.error('Model loading failed', { message: err.message });\r\n this.emit('error', err);\r\n this.setState('idle');\r\n throw err;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Lifecycle\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Start listening — activates mic + VAD. */\r\n async start(): Promise<void> {\r\n if (!this.asr || !this.vad) {\r\n throw new Error('Models not loaded. Call loadModels() first.');\r\n }\r\n if (this._state === 'listening') return; // already listening\r\n\r\n this.epoch++;\r\n this.asrErrorCount = 0;\r\n this.audioBuffer = [];\r\n this.audioBufferSamples = 0;\r\n\r\n this.mic = new MicrophoneCapture(this.omoteEvents, {\r\n sampleRate: 16000,\r\n chunkSize: 512,\r\n });\r\n\r\n this._unsubChunk = this.omoteEvents.on('audio.chunk', ({ pcm }) => {\r\n const float32 = int16ToFloat32(pcm);\r\n this.emit('audio:chunk', float32);\r\n this.processAudioChunk(float32);\r\n });\r\n\r\n this._unsubLevel = this.omoteEvents.on('audio.level', (level) => {\r\n this.emit('audio:level', level);\r\n });\r\n\r\n await this.mic.start();\r\n this.setState('listening');\r\n logger.info('Listening started');\r\n }\r\n\r\n /** Stop listening — deactivates mic, clears buffers. */\r\n stop(): void {\r\n this.epoch++;\r\n this.clearSilenceTimer();\r\n this.stopProgressiveTranscription();\r\n\r\n // Clean up event listeners to prevent leaks\r\n this._unsubChunk?.();\r\n this._unsubChunk = null;\r\n this._unsubLevel?.();\r\n this._unsubLevel = null;\r\n\r\n this.vad?.reset();\r\n this.mic?.stop();\r\n this.mic = null;\r\n\r\n this.isSpeechActive = false;\r\n this.audioBuffer = [];\r\n this.audioBufferSamples = 0;\r\n\r\n if (this._state !== 'idle') {\r\n this.setState('ready');\r\n }\r\n logger.info('Listening stopped');\r\n }\r\n\r\n /** Pause VAD/ASR but keep mic active for audio:chunk events (for interruption detection). */\r\n pause(): void {\r\n if (this._state !== 'listening' && this._state !== 'processing') return;\r\n this.epoch++;\r\n this.clearSilenceTimer();\r\n this.stopProgressiveTranscription();\r\n this.isSpeechActive = false;\r\n this.audioBuffer = [];\r\n this.audioBufferSamples = 0;\r\n this.setState('paused');\r\n }\r\n\r\n /** Resume VAD/ASR from paused state. */\r\n resume(): void {\r\n if (this._state !== 'paused') return;\r\n this.vad?.reset();\r\n this.setState('listening');\r\n }\r\n\r\n /** Dispose all resources. */\r\n async dispose(): Promise<void> {\r\n logger.debug('Disposing SpeechListener');\r\n this.stop();\r\n this.epoch++;\r\n\r\n await Promise.allSettled([\r\n this.asr?.dispose(),\r\n this.vad?.dispose(),\r\n ].filter(Boolean));\r\n\r\n this.asr = null;\r\n this.vad = null;\r\n\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n }\r\n this.ownedWorker = null;\r\n\r\n this._state = 'idle';\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Audio processing\r\n // ---------------------------------------------------------------------------\r\n\r\n private async processAudioChunk(samples: Float32Array): Promise<void> {\r\n if (!this.vad || this._state !== 'listening') return;\r\n\r\n try {\r\n const capturedEpoch = this.epoch;\r\n const result = await this.vad.process(samples);\r\n\r\n if (this.epoch !== capturedEpoch) return;\r\n if (this._state !== 'listening') return;\r\n\r\n const wasSpeaking = this.isSpeechActive;\r\n\r\n if (result.isSpeech) {\r\n if (!wasSpeaking) {\r\n this.isSpeechActive = true;\r\n this.speechStartTime = getClock().now();\r\n this.audioBuffer = [];\r\n this.audioBufferSamples = 0;\r\n this.lastProgressiveResult = null;\r\n this.lastProgressiveSamples = 0;\r\n logger.debug('Speech start');\r\n this.emit('speech:start');\r\n this.startProgressiveTranscription();\r\n }\r\n\r\n this.audioBuffer.push(new Float32Array(samples));\r\n this.audioBufferSamples += samples.length;\r\n\r\n // Force-flush if buffer exceeds max (prevents unbounded memory growth)\r\n if (this.audioBufferSamples >= SpeechListener.MAX_AUDIO_BUFFER_SAMPLES) {\r\n logger.warn('Audio buffer exceeded max, forcing transcription flush');\r\n this.onSilenceDetected();\r\n return;\r\n }\r\n\r\n this.clearSilenceTimer();\r\n } else if (wasSpeaking) {\r\n // Continue accumulating during silence (gap tolerance)\r\n this.audioBuffer.push(new Float32Array(samples));\r\n this.audioBufferSamples += samples.length;\r\n\r\n if (!this.silenceTimer) {\r\n const timeoutMs = this.getSilenceTimeout();\r\n this.silenceTimer = setTimeout(() => {\r\n this.onSilenceDetected();\r\n }, timeoutMs);\r\n }\r\n }\r\n } catch (err) {\r\n logger.warn('VAD error', { error: String(err) });\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Silence detection\r\n // ---------------------------------------------------------------------------\r\n\r\n private getSilenceTimeout(): number {\r\n const base = this.config.silenceTimeoutMs ?? 500;\r\n const extended = this.config.silenceTimeoutExtendedMs ?? 700;\r\n const adaptive = this.config.adaptiveTimeout ?? true;\r\n if (!adaptive) return base;\r\n const speechDurationMs = getClock().now() - this.speechStartTime;\r\n return speechDurationMs > 3000 ? extended : base;\r\n }\r\n\r\n private onSilenceDetected(): void {\r\n const capturedEpoch = this.epoch;\r\n this.isSpeechActive = false;\r\n const durationMs = getClock().now() - this.speechStartTime;\r\n logger.debug('Speech end', { durationMs: Math.round(durationMs) });\r\n this.emit('speech:end', { durationMs });\r\n this.clearSilenceTimer();\r\n\r\n this.processEndOfSpeech(capturedEpoch).catch(err => {\r\n logger.error('End of speech processing failed', { error: String(err) });\r\n if (this.epoch === capturedEpoch) {\r\n this.emit('error', err instanceof Error ? err : new Error(String(err)));\r\n this.setState('listening');\r\n }\r\n });\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // End of speech → transcription\r\n // ---------------------------------------------------------------------------\r\n\r\n private async processEndOfSpeech(capturedEpoch: number): Promise<void> {\r\n // Wait for in-flight progressive promise\r\n if (this.progressivePromise) {\r\n try { await this.progressivePromise; } catch { /* ignore */ }\r\n }\r\n this.stopProgressiveTranscription();\r\n\r\n if (this.epoch !== capturedEpoch) return;\r\n\r\n // Merge audio buffer\r\n const totalSamples = this.audioBufferSamples;\r\n const fullAudio = new Float32Array(totalSamples);\r\n let offset = 0;\r\n for (const chunk of this.audioBuffer) {\r\n fullAudio.set(chunk, offset);\r\n offset += chunk.length;\r\n }\r\n this.audioBuffer = [];\r\n this.audioBufferSamples = 0;\r\n\r\n // Audio validation\r\n const minDuration = this.config.minAudioDurationSec ?? 0.3;\r\n const minEnergy = this.config.minAudioEnergy ?? 0.02;\r\n const durationSec = totalSamples / 16000;\r\n\r\n if (durationSec < minDuration) {\r\n logger.info('Audio too short, discarding', { durationSec });\r\n this.setState('listening');\r\n return;\r\n }\r\n\r\n let rms = 0;\r\n for (let i = 0; i < fullAudio.length; i++) {\r\n rms += fullAudio[i] * fullAudio[i];\r\n }\r\n rms = Math.sqrt(rms / fullAudio.length);\r\n\r\n if (rms < minEnergy) {\r\n logger.info('Audio too quiet, discarding', { rms });\r\n this.setState('listening');\r\n return;\r\n }\r\n\r\n const normalizedAudio = this.normalizeAudio(fullAudio);\r\n\r\n // Transcribe\r\n this.setState('processing');\r\n let transcript: TranscriptResult | null = null;\r\n\r\n const coverageThreshold = this.config.progressiveCoverageThreshold ?? 0.8;\r\n if (\r\n this.lastProgressiveResult &&\r\n this.lastProgressiveResult.text.trim().length > 0 &&\r\n this.lastProgressiveSamples >= totalSamples * coverageThreshold\r\n ) {\r\n transcript = { ...this.lastProgressiveResult, isFinal: true };\r\n } else {\r\n this.lastProgressiveResult = null;\r\n transcript = await this.transcribeWithTimeout(normalizedAudio);\r\n if (transcript) transcript.isFinal = true;\r\n }\r\n\r\n if (this.epoch !== capturedEpoch) return;\r\n\r\n if (!transcript || !transcript.text.trim()) {\r\n logger.info('No transcript, resuming listening');\r\n this.setState('listening');\r\n return;\r\n }\r\n\r\n // Emit final transcript — SpeechListener stops here.\r\n // Response handling is the consumer's responsibility.\r\n this.emit('transcript', transcript);\r\n this.setState('listening');\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Progressive transcription\r\n // ---------------------------------------------------------------------------\r\n\r\n private startProgressiveTranscription(): void {\r\n this.stopProgressiveTranscription();\r\n\r\n const intervalMs = isIOS()\r\n ? (this.config.progressiveIntervalIosMs ?? 800)\r\n : (this.config.progressiveIntervalMs ?? 500);\r\n const minSamples = this.config.progressiveMinSamples ?? 8000;\r\n\r\n this.progressiveTimer = setInterval(() => {\r\n if (this.audioBufferSamples < minSamples || !this.asr) return;\r\n\r\n const capturedEpoch = this.epoch;\r\n const snapshot = new Float32Array(this.audioBufferSamples);\r\n let offset = 0;\r\n for (const chunk of this.audioBuffer) {\r\n snapshot.set(chunk, offset);\r\n offset += chunk.length;\r\n }\r\n const snapshotSamples = this.audioBufferSamples;\r\n\r\n this.progressivePromise = (async () => {\r\n try {\r\n const result = await this.transcribeWithTimeout(snapshot);\r\n if (this.epoch !== capturedEpoch) return;\r\n\r\n if (result && result.text.trim()) {\r\n this.lastProgressiveResult = result;\r\n this.lastProgressiveSamples = snapshotSamples;\r\n this.emit('transcript', { ...result, isFinal: false });\r\n }\r\n } catch (err) {\r\n this.progressiveErrorCount = (this.progressiveErrorCount ?? 0) + 1;\r\n if (this.progressiveErrorCount % 10 === 1) {\r\n logger.warn('Progressive transcription error', {\r\n code: ErrorCodes.SPH_ASR_ERROR,\r\n error: String(err),\r\n count: this.progressiveErrorCount,\r\n });\r\n }\r\n }\r\n })();\r\n }, intervalMs);\r\n }\r\n\r\n private stopProgressiveTranscription(): void {\r\n if (this.progressiveTimer) {\r\n clearInterval(this.progressiveTimer);\r\n this.progressiveTimer = null;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Transcription with timeout + ASR error recovery\r\n // ---------------------------------------------------------------------------\r\n\r\n private async transcribeWithTimeout(audio: Float32Array): Promise<TranscriptResult | null> {\r\n if (!this.asr) return null;\r\n\r\n const timeoutMs = this.config.transcriptionTimeoutMs ?? 10_000;\r\n const startTime = getClock().now();\r\n const span = getTelemetry()?.startSpan('SpeechListener.transcribe', {\r\n 'inference.input_samples': audio.length,\r\n 'inference.input_duration_ms': (audio.length / 16000) * 1000,\r\n });\r\n\r\n try {\r\n let timeoutId: ReturnType<typeof setTimeout>;\r\n const result = await Promise.race([\r\n this.asr.transcribe(audio),\r\n new Promise<never>((_, reject) => {\r\n timeoutId = setTimeout(() => reject(new Error(`Transcription timed out after ${timeoutMs}ms`)), timeoutMs);\r\n }),\r\n ]);\r\n clearTimeout(timeoutId!);\r\n\r\n const latency = getClock().now() - startTime;\r\n this.asrErrorCount = 0;\r\n getTelemetry()?.recordHistogram(MetricNames.VOICE_TRANSCRIPTION_LATENCY, latency);\r\n getTelemetry()?.incrementCounter(MetricNames.VOICE_TRANSCRIPTIONS);\r\n span?.setAttributes({ 'inference.duration_ms': latency, 'inference.success': true });\r\n span?.end();\r\n return {\r\n text: result.text,\r\n emotion: result.emotion,\r\n language: result.language,\r\n isFinal: false,\r\n inferenceTimeMs: latency,\r\n };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n this.asrErrorCount++;\r\n logger.warn('Transcription failed', { attempt: this.asrErrorCount, error: String(error) });\r\n\r\n if (this.asrErrorCount >= 3 && this.config.models) {\r\n logger.warn('3 consecutive ASR errors, recreating session');\r\n try {\r\n await this.asr.dispose();\r\n this.asr = createSenseVoice({\r\n modelUrl: this.config.models.senseVoice.modelUrl,\r\n tokensUrl: this.config.models.senseVoice.tokensUrl,\r\n language: this.config.models.senseVoice.language as SenseVoiceLanguage,\r\n textNorm: this.config.models.senseVoice.textNorm,\r\n unifiedWorker: (this.config.unifiedWorker ?? this.ownedWorker)!,\r\n });\r\n await this.asr.load();\r\n this.asrErrorCount = 0;\r\n } catch (recreateErr) {\r\n logger.error('ASR session recreation failed', { error: String(recreateErr) });\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Audio normalization\r\n // ---------------------------------------------------------------------------\r\n\r\n private normalizeAudio(audio: Float32Array): Float32Array {\r\n if (!(this.config.normalizeAudio ?? true)) return audio;\r\n\r\n let maxAbs = 0;\r\n for (let i = 0; i < audio.length; i++) {\r\n const abs = Math.abs(audio[i]);\r\n if (abs > maxAbs) maxAbs = abs;\r\n }\r\n\r\n if (maxAbs >= 0.1 || maxAbs === 0) return audio;\r\n\r\n const gain = 0.5 / maxAbs;\r\n const normalized = new Float32Array(audio.length);\r\n for (let i = 0; i < audio.length; i++) {\r\n normalized[i] = audio[i] * gain;\r\n }\r\n return normalized;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Helpers\r\n // ---------------------------------------------------------------------------\r\n\r\n private setState(state: SpeechListenerState): void {\r\n if (this._state === state) return;\r\n logger.debug('State transition', { from: this._state, to: state });\r\n this._state = state;\r\n this.emit('state', state);\r\n }\r\n\r\n private emitProgress(currentModel: string, progress: number, totalModels: number, modelsLoaded: number): void {\r\n this.emit('loading:progress', { currentModel, progress, totalModels, modelsLoaded });\r\n }\r\n\r\n private clearSilenceTimer(): void {\r\n if (this.silenceTimer) {\r\n clearTimeout(this.silenceTimer);\r\n this.silenceTimer = null;\r\n }\r\n }\r\n}\r\n","/**\r\n * Interruption Handler\r\n *\r\n * VAD-based barge-in detection for AI conversations:\r\n * - Monitors VAD probability for user speech\r\n * - Detects when user interrupts AI response\r\n * - Triggers interruption callbacks\r\n */\r\n\r\nimport { EventEmitter } from '../events/EventEmitter';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\n\r\nconst logger = createLogger('InterruptionHandler');\r\n\r\nexport interface InterruptionEvents {\r\n [key: string]: unknown;\r\n 'speech.detected': { rms: number };\r\n 'speech.ended': { durationMs: number };\r\n 'interruption.triggered': { rms: number; durationMs: number };\r\n}\r\n\r\n/**\r\n * Interruption handler configuration\r\n *\r\n * Industry standards applied:\r\n * - vadThreshold: 0.5 (Silero VAD default)\r\n * - minSpeechDurationMs: 200ms (Google/Amazon barge-in standard)\r\n * - silenceTimeoutMs: 500ms (OpenAI Realtime API standard)\r\n */\r\nexport interface InterruptionConfig {\r\n /** VAD probability threshold for speech detection (default: 0.5, Silero standard) */\r\n vadThreshold?: number;\r\n /** Minimum speech duration to trigger interruption (default: 200ms, Google/Amazon standard) */\r\n minSpeechDurationMs?: number;\r\n /** Silence duration to end speech (default: 500ms, OpenAI standard) */\r\n silenceTimeoutMs?: number;\r\n /** Enable interruption detection (default: true) */\r\n enabled?: boolean;\r\n}\r\n\r\nexport class InterruptionHandler extends EventEmitter<InterruptionEvents> {\r\n private config: Required<InterruptionConfig>;\r\n private isSpeaking = false;\r\n private speechStartTime = 0;\r\n private lastSpeechTime = 0;\r\n private silenceTimer: ReturnType<typeof setTimeout> | null = null;\r\n private aiIsSpeaking = false;\r\n\r\n // Debouncing: only emit one interruption per speech session\r\n private interruptionTriggeredThisSession = false;\r\n\r\n constructor(config: InterruptionConfig = {}) {\r\n super();\r\n this.config = {\r\n vadThreshold: 0.5, // Silero VAD default\r\n minSpeechDurationMs: 200, // Google/Amazon barge-in standard\r\n silenceTimeoutMs: 500, // OpenAI Realtime API standard\r\n enabled: true,\r\n ...config,\r\n };\r\n logger.debug('Constructed with config', {\r\n vadThreshold: this.config.vadThreshold,\r\n minSpeechDurationMs: this.config.minSpeechDurationMs,\r\n silenceTimeoutMs: this.config.silenceTimeoutMs,\r\n enabled: this.config.enabled,\r\n });\r\n }\r\n\r\n /**\r\n * Process raw audio energy for interruption detection (no VAD required).\r\n * Used during speaking state when the unified worker is busy with TTS.\r\n * Echo-cancelled mic input means energy above threshold = user speech.\r\n *\r\n * @param rms - RMS energy of audio chunk (0-1)\r\n * @param energyThreshold - Minimum energy to consider speech (default: 0.02)\r\n */\r\n processAudioEnergy(rms: number, energyThreshold: number = 0.02): void {\r\n if (!this.config.enabled) return;\r\n if (rms > energyThreshold) {\r\n this.onSpeechDetected(rms);\r\n } else {\r\n this.onSilenceDetected();\r\n }\r\n }\r\n\r\n /**\r\n * Process VAD result for interruption detection\r\n * @param vadProbability - Speech probability from VAD (0-1)\r\n * @param audioEnergy - Optional RMS energy for logging (default: 0)\r\n */\r\n processVADResult(vadProbability: number, audioEnergy: number = 0): void {\r\n if (!this.config.enabled) return;\r\n\r\n if (this.aiIsSpeaking) {\r\n logger.trace('VAD during AI speech', {\r\n vadProbability,\r\n audioEnergy,\r\n threshold: this.config.vadThreshold,\r\n });\r\n }\r\n\r\n if (vadProbability > this.config.vadThreshold) {\r\n this.onSpeechDetected(audioEnergy || vadProbability);\r\n } else {\r\n this.onSilenceDetected();\r\n }\r\n }\r\n\r\n /** Notify that AI started/stopped speaking */\r\n setAISpeaking(speaking: boolean): void {\r\n logger.debug('AI speaking state changed', { speaking });\r\n this.aiIsSpeaking = speaking;\r\n }\r\n\r\n /** Enable/disable interruption detection */\r\n setEnabled(enabled: boolean): void {\r\n logger.debug('Enabled state changed', { enabled });\r\n this.config.enabled = enabled;\r\n if (!enabled) {\r\n this.reset();\r\n }\r\n }\r\n\r\n /** Update configuration */\r\n updateConfig(config: Partial<InterruptionConfig>): void {\r\n this.config = { ...this.config, ...config };\r\n }\r\n\r\n /** Reset state */\r\n reset(): void {\r\n this.isSpeaking = false;\r\n this.speechStartTime = 0;\r\n this.lastSpeechTime = 0;\r\n this.interruptionTriggeredThisSession = false;\r\n if (this.silenceTimer) {\r\n clearTimeout(this.silenceTimer);\r\n this.silenceTimer = null;\r\n }\r\n }\r\n\r\n /** Get current state */\r\n getState(): { isSpeaking: boolean; speechDurationMs: number } {\r\n return {\r\n isSpeaking: this.isSpeaking,\r\n speechDurationMs: this.isSpeaking ? getClock().now() - this.speechStartTime : 0,\r\n };\r\n }\r\n\r\n private onSpeechDetected(rms: number): void {\r\n const now = getClock().now();\r\n this.lastSpeechTime = now;\r\n\r\n if (this.silenceTimer) {\r\n clearTimeout(this.silenceTimer);\r\n this.silenceTimer = null;\r\n }\r\n\r\n if (!this.isSpeaking) {\r\n this.isSpeaking = true;\r\n this.speechStartTime = now;\r\n this.emit('speech.detected', { rms });\r\n }\r\n\r\n // Check for interruption (only emit ONCE per speech session)\r\n if (this.aiIsSpeaking && !this.interruptionTriggeredThisSession) {\r\n const speechDuration = now - this.speechStartTime;\r\n if (speechDuration >= this.config.minSpeechDurationMs) {\r\n this.interruptionTriggeredThisSession = true;\r\n logger.debug('Interruption triggered', { rms, durationMs: speechDuration });\r\n this.emit('interruption.triggered', { rms, durationMs: speechDuration });\r\n }\r\n }\r\n }\r\n\r\n private onSilenceDetected(): void {\r\n if (!this.isSpeaking) return;\r\n\r\n if (!this.silenceTimer) {\r\n this.silenceTimer = setTimeout(() => {\r\n const durationMs = this.lastSpeechTime - this.speechStartTime;\r\n this.isSpeaking = false;\r\n this.silenceTimer = null;\r\n this.interruptionTriggeredThisSession = false;\r\n this.emit('speech.ended', { durationMs });\r\n }, this.config.silenceTimeoutMs);\r\n }\r\n }\r\n}\r\n","/**\r\n * Safari Web Speech API wrapper for iOS speech recognition\r\n *\r\n * Provides a similar interface to WhisperInference for easy substitution on iOS.\r\n * Uses the native Web Speech API which is significantly faster than Whisper WASM on iOS.\r\n *\r\n * Key differences from WhisperInference:\r\n * - Real-time streaming (not batch processing)\r\n * - No audio buffer input (microphone handled by browser)\r\n * - transcribe() throws error (use start/stop pattern instead)\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { SafariSpeechRecognition, shouldUseNativeASR } from '@omote/core';\r\n *\r\n * // Use native ASR on iOS, Whisper elsewhere\r\n * if (shouldUseNativeASR()) {\r\n * const speech = new SafariSpeechRecognition({ language: 'en-US' });\r\n *\r\n * speech.onResult((result) => {\r\n * console.log('Transcript:', result.text);\r\n * });\r\n *\r\n * await speech.start();\r\n * // ... user speaks ...\r\n * const finalResult = await speech.stop();\r\n * }\r\n * ```\r\n *\r\n * @example Platform-aware initialization\r\n * ```typescript\r\n * const asr = shouldUseNativeASR()\r\n * ? new SafariSpeechRecognition({ language: 'en-US' })\r\n * : new WhisperInference({ model: 'tiny' });\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry';\r\nimport { isSpeechRecognitionAvailable } from '../utils/runtime';\r\n\r\nconst logger = createLogger('SafariSpeech');\r\n\r\n/**\r\n * Configuration for Safari Speech Recognition\r\n */\r\nexport interface SafariSpeechConfig {\r\n /** Language code (default: 'en-US') */\r\n language?: string;\r\n /** Continuous mode for ongoing conversation (default: true) */\r\n continuous?: boolean;\r\n /** Interim results before speech ends (default: true) */\r\n interimResults?: boolean;\r\n /** Max alternatives (default: 1) */\r\n maxAlternatives?: number;\r\n}\r\n\r\n/**\r\n * Result from speech recognition (matches WhisperInference TranscriptionResult)\r\n */\r\nexport interface SpeechRecognitionResult {\r\n /** Transcribed text */\r\n text: string;\r\n /** Detected/used language */\r\n language: string;\r\n /** Time since start in ms (not inference time - native API) */\r\n inferenceTimeMs: number;\r\n /** Whether this is a final result or interim */\r\n isFinal: boolean;\r\n /** Confidence score (0-1) if available */\r\n confidence?: number;\r\n}\r\n\r\n/**\r\n * Callback for receiving recognition results\r\n */\r\nexport type SpeechResultCallback = (result: SpeechRecognitionResult) => void;\r\n\r\n/**\r\n * Callback for receiving recognition errors\r\n */\r\nexport type SpeechErrorCallback = (error: Error) => void;\r\n\r\n// Type declarations for Web Speech API (not in lib.dom.d.ts by default)\r\ninterface SpeechRecognitionEvent extends Event {\r\n resultIndex: number;\r\n results: SpeechRecognitionResultList;\r\n}\r\n\r\ninterface SpeechRecognitionResultList {\r\n length: number;\r\n item(index: number): SpeechRecognitionResult;\r\n [index: number]: SpeechRecognitionResultItem;\r\n}\r\n\r\ninterface SpeechRecognitionResultItem {\r\n isFinal: boolean;\r\n length: number;\r\n item(index: number): SpeechRecognitionAlternative;\r\n [index: number]: SpeechRecognitionAlternative;\r\n}\r\n\r\ninterface SpeechRecognitionAlternative {\r\n transcript: string;\r\n confidence: number;\r\n}\r\n\r\ninterface SpeechRecognitionErrorEvent extends Event {\r\n error: string;\r\n message: string;\r\n}\r\n\r\ninterface SpeechRecognitionInterface extends EventTarget {\r\n continuous: boolean;\r\n interimResults: boolean;\r\n lang: string;\r\n maxAlternatives: number;\r\n start(): void;\r\n stop(): void;\r\n abort(): void;\r\n onresult: ((event: SpeechRecognitionEvent) => void) | null;\r\n onerror: ((event: SpeechRecognitionErrorEvent) => void) | null;\r\n onend: (() => void) | null;\r\n onstart: (() => void) | null;\r\n onaudiostart: (() => void) | null;\r\n onaudioend: (() => void) | null;\r\n onspeechstart: (() => void) | null;\r\n onspeechend: (() => void) | null;\r\n}\r\n\r\ndeclare global {\r\n interface Window {\r\n SpeechRecognition?: new () => SpeechRecognitionInterface;\r\n webkitSpeechRecognition?: new () => SpeechRecognitionInterface;\r\n }\r\n}\r\n\r\n/**\r\n * Safari Web Speech API wrapper\r\n *\r\n * Provides native speech recognition on iOS Safari.\r\n * Much faster than Whisper WASM and more battery-efficient.\r\n */\r\nexport class SafariSpeechRecognition {\r\n private config: Required<SafariSpeechConfig>;\r\n private recognition: SpeechRecognitionInterface | null = null;\r\n private isListening = false;\r\n private startTime = 0;\r\n private accumulatedText = '';\r\n\r\n // Callbacks\r\n private resultCallbacks: SpeechResultCallback[] = [];\r\n private errorCallbacks: SpeechErrorCallback[] = [];\r\n\r\n // Promise resolvers for stop()\r\n private stopResolver: ((result: SpeechRecognitionResult) => void) | null = null;\r\n private stopRejecter: ((error: Error) => void) | null = null;\r\n\r\n constructor(config: SafariSpeechConfig = {}) {\r\n this.config = {\r\n language: config.language ?? 'en-US',\r\n continuous: config.continuous ?? true,\r\n interimResults: config.interimResults ?? true,\r\n maxAlternatives: config.maxAlternatives ?? 1,\r\n };\r\n\r\n logger.debug('SafariSpeechRecognition created', {\r\n language: this.config.language,\r\n continuous: this.config.continuous,\r\n });\r\n }\r\n\r\n /**\r\n * Check if Web Speech API is available\r\n */\r\n static isAvailable(): boolean {\r\n return isSpeechRecognitionAvailable();\r\n }\r\n\r\n /**\r\n * Check if currently listening\r\n */\r\n get listening(): boolean {\r\n return this.isListening;\r\n }\r\n\r\n /**\r\n * Get the language being used\r\n */\r\n get language(): string {\r\n return this.config.language;\r\n }\r\n\r\n /**\r\n * Register a callback for receiving results\r\n */\r\n onResult(callback: SpeechResultCallback): void {\r\n this.resultCallbacks.push(callback);\r\n }\r\n\r\n /**\r\n * Register a callback for receiving errors\r\n */\r\n onError(callback: SpeechErrorCallback): void {\r\n this.errorCallbacks.push(callback);\r\n }\r\n\r\n /**\r\n * Remove a result callback\r\n */\r\n offResult(callback: SpeechResultCallback): void {\r\n const index = this.resultCallbacks.indexOf(callback);\r\n if (index !== -1) {\r\n this.resultCallbacks.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Remove an error callback\r\n */\r\n offError(callback: SpeechErrorCallback): void {\r\n const index = this.errorCallbacks.indexOf(callback);\r\n if (index !== -1) {\r\n this.errorCallbacks.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Start listening for speech\r\n *\r\n * On iOS Safari, this will trigger the microphone permission prompt\r\n * if not already granted.\r\n */\r\n async start(): Promise<void> {\r\n if (this.isListening) {\r\n logger.warn('Already listening');\r\n return;\r\n }\r\n\r\n if (!SafariSpeechRecognition.isAvailable()) {\r\n const error = new Error(\r\n 'Web Speech API not available. ' +\r\n 'This API is supported in Safari (iOS/macOS) and Chrome. ' +\r\n 'On iOS, use Safari for native speech recognition.'\r\n );\r\n this.emitError(error);\r\n throw error;\r\n }\r\n\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.start', {\r\n 'speech.language': this.config.language,\r\n 'speech.continuous': this.config.continuous,\r\n });\r\n\r\n try {\r\n // Create recognition instance\r\n const SpeechRecognitionClass = window.SpeechRecognition || window.webkitSpeechRecognition;\r\n if (!SpeechRecognitionClass) {\r\n throw new Error('SpeechRecognition constructor not found');\r\n }\r\n\r\n this.recognition = new SpeechRecognitionClass();\r\n this.recognition.continuous = this.config.continuous;\r\n this.recognition.interimResults = this.config.interimResults;\r\n this.recognition.lang = this.config.language;\r\n this.recognition.maxAlternatives = this.config.maxAlternatives;\r\n\r\n // Set up event handlers\r\n this.setupEventHandlers();\r\n\r\n // Start recognition\r\n this.recognition.start();\r\n this.isListening = true;\r\n this.startTime = getClock().now();\r\n this.accumulatedText = '';\r\n\r\n logger.info('Speech recognition started', {\r\n language: this.config.language,\r\n });\r\n\r\n span?.end();\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n this.emitError(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Stop listening and return the final transcript\r\n */\r\n async stop(): Promise<SpeechRecognitionResult> {\r\n if (!this.isListening || !this.recognition) {\r\n logger.warn('Not currently listening');\r\n return {\r\n text: this.accumulatedText,\r\n language: this.config.language,\r\n inferenceTimeMs: 0,\r\n isFinal: true,\r\n };\r\n }\r\n\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.stop');\r\n\r\n return new Promise((resolve, reject) => {\r\n this.stopResolver = resolve;\r\n this.stopRejecter = reject;\r\n\r\n try {\r\n this.recognition!.stop();\r\n // onend handler will resolve the promise\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n this.isListening = false;\r\n reject(error);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Abort recognition without waiting for final result\r\n */\r\n abort(): void {\r\n if (this.recognition && this.isListening) {\r\n this.recognition.abort();\r\n this.isListening = false;\r\n logger.info('Speech recognition aborted');\r\n }\r\n }\r\n\r\n /**\r\n * NOT SUPPORTED: Transcribe audio buffer\r\n *\r\n * Safari Speech API does not support transcribing pre-recorded audio.\r\n * It only works with live microphone input.\r\n *\r\n * For batch transcription on iOS, use server-side Whisper or a cloud ASR service.\r\n *\r\n * @throws Error always - this method is not supported\r\n */\r\n async transcribe(_audio: Float32Array): Promise<SpeechRecognitionResult> {\r\n throw new Error(\r\n 'SafariSpeechRecognition does not support transcribe() with audio buffers. ' +\r\n 'The Web Speech API only works with live microphone input. ' +\r\n 'Use start() and stop() for real-time recognition, or use WhisperInference/cloud ASR for batch transcription.'\r\n );\r\n }\r\n\r\n /**\r\n * Dispose of recognition resources\r\n */\r\n dispose(): void {\r\n logger.debug('Disposed');\r\n if (this.recognition) {\r\n if (this.isListening) {\r\n this.recognition.abort();\r\n }\r\n this.recognition = null;\r\n }\r\n this.isListening = false;\r\n this.resultCallbacks = [];\r\n this.errorCallbacks = [];\r\n logger.debug('SafariSpeechRecognition disposed');\r\n }\r\n\r\n /**\r\n * Set up event handlers for the recognition instance\r\n */\r\n private setupEventHandlers(): void {\r\n if (!this.recognition) return;\r\n\r\n this.recognition.onresult = (event: SpeechRecognitionEvent) => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.onresult');\r\n\r\n try {\r\n // Process all new results\r\n for (let i = event.resultIndex; i < event.results.length; i++) {\r\n const result = event.results[i];\r\n const alternative = result[0];\r\n\r\n if (alternative) {\r\n const text = alternative.transcript;\r\n const isFinal = result.isFinal;\r\n\r\n // Accumulate final text\r\n if (isFinal) {\r\n this.accumulatedText += text + ' ';\r\n }\r\n\r\n const speechResult: SpeechRecognitionResult = {\r\n text: isFinal ? this.accumulatedText.trim() : text,\r\n language: this.config.language,\r\n inferenceTimeMs: getClock().now() - this.startTime,\r\n isFinal,\r\n confidence: alternative.confidence,\r\n };\r\n\r\n // Emit to callbacks\r\n this.emitResult(speechResult);\r\n\r\n logger.trace('Speech result', {\r\n text: text.substring(0, 50),\r\n isFinal,\r\n confidence: alternative.confidence,\r\n });\r\n }\r\n }\r\n\r\n span?.end();\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n logger.error('Error processing speech result', { error });\r\n }\r\n };\r\n\r\n this.recognition.onerror = (event: SpeechRecognitionErrorEvent) => {\r\n const error = new Error(`Speech recognition error: ${event.error} - ${event.message}`);\r\n logger.error('Speech recognition error', { error: event.error, message: event.message });\r\n this.emitError(error);\r\n\r\n if (this.stopRejecter) {\r\n this.stopRejecter(error);\r\n this.stopResolver = null;\r\n this.stopRejecter = null;\r\n }\r\n };\r\n\r\n this.recognition.onend = () => {\r\n this.isListening = false;\r\n logger.info('Speech recognition ended', {\r\n totalText: this.accumulatedText.length,\r\n durationMs: getClock().now() - this.startTime,\r\n });\r\n\r\n // Resolve stop() promise if pending\r\n if (this.stopResolver) {\r\n const result: SpeechRecognitionResult = {\r\n text: this.accumulatedText.trim(),\r\n language: this.config.language,\r\n inferenceTimeMs: getClock().now() - this.startTime,\r\n isFinal: true,\r\n };\r\n this.stopResolver(result);\r\n this.stopResolver = null;\r\n this.stopRejecter = null;\r\n }\r\n };\r\n\r\n this.recognition.onstart = () => {\r\n logger.debug('Speech recognition started by browser');\r\n };\r\n\r\n this.recognition.onspeechstart = () => {\r\n logger.debug('Speech detected');\r\n };\r\n\r\n this.recognition.onspeechend = () => {\r\n logger.debug('Speech ended');\r\n };\r\n }\r\n\r\n /**\r\n * Emit result to all registered callbacks\r\n */\r\n private emitResult(result: SpeechRecognitionResult): void {\r\n for (const callback of this.resultCallbacks) {\r\n try {\r\n callback(result);\r\n } catch (error) {\r\n logger.error('Error in result callback', { error });\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Emit error to all registered callbacks\r\n */\r\n private emitError(error: Error): void {\r\n for (const callback of this.errorCallbacks) {\r\n try {\r\n callback(error);\r\n } catch (callbackError) {\r\n logger.error('Error in error callback', { error: callbackError });\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * ElevenLabs TTS Backend — Cloud text-to-speech via ElevenLabs REST API.\r\n *\r\n * Implements the TTSBackend interface so it can be used anywhere Kokoro TTS is used\r\n * (TTSPlayback, TTSSpeaker, VoiceOrchestrator, PlaybackPipeline, etc.)\r\n *\r\n * Zero external dependencies — uses fetch() directly.\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { ElevenLabsTTSBackend } from '@omote/core';\r\n *\r\n * const tts = new ElevenLabsTTSBackend({\r\n * apiKey: 'your-api-key',\r\n * voiceId: 'voice-id',\r\n * });\r\n * await tts.load();\r\n *\r\n * for await (const chunk of tts.stream(\"Hello world!\")) {\r\n * playbackPipeline.feedBuffer(chunk.audio);\r\n * }\r\n * ```\r\n *\r\n * @example With PlaybackPipeline\r\n * ```typescript\r\n * const speaker = new TTSSpeaker();\r\n * await speaker.connect(tts, { lam: createA2E() });\r\n * await speaker.speak(\"Hello!\");\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging/Logger';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry/index';\r\nimport { pcm16ToFloat32 } from '../audio/audioUtils';\r\nimport type { TTSBackend, TTSStreamOptions, TTSChunk } from './TTSBackend';\r\n\r\nconst logger = createLogger('ElevenLabsTTS');\r\n\r\n// ─── Config ─────────────────────────────────────────────────────────────────\r\n\r\nexport interface ElevenLabsConfig {\r\n /** ElevenLabs API key */\r\n apiKey: string;\r\n /** Voice ID to use */\r\n voiceId: string;\r\n /** Model ID (default: 'eleven_multilingual_v2') */\r\n model?: string;\r\n /**\r\n * Output format (default: 'pcm_16000').\r\n * Use 'pcm_16000' for lip sync compatibility (16kHz matches A2E input).\r\n * Other options: 'pcm_22050', 'pcm_24000', 'pcm_44100'\r\n */\r\n outputFormat?: string;\r\n /** Voice stability 0-1 (default: 0.5) */\r\n stability?: number;\r\n /** Voice similarity boost 0-1 (default: 0.75) */\r\n similarityBoost?: number;\r\n /** API base URL override (default: 'https://api.elevenlabs.io') */\r\n baseUrl?: string;\r\n}\r\n\r\n// ─── Constants ──────────────────────────────────────────────────────────────\r\n\r\nconst DEFAULT_MODEL = 'eleven_multilingual_v2';\r\nconst DEFAULT_OUTPUT_FORMAT = 'pcm_16000';\r\nconst DEFAULT_STABILITY = 0.5;\r\nconst DEFAULT_SIMILARITY_BOOST = 0.75;\r\nconst DEFAULT_BASE_URL = 'https://api.elevenlabs.io';\r\n\r\n/** Map output format strings to sample rates */\r\nconst FORMAT_TO_SAMPLE_RATE: Record<string, number> = {\r\n pcm_16000: 16000,\r\n pcm_22050: 22050,\r\n pcm_24000: 24000,\r\n pcm_44100: 44100,\r\n};\r\n\r\n// ─── Class ──────────────────────────────────────────────────────────────────\r\n\r\nexport class ElevenLabsTTSBackend implements TTSBackend {\r\n private readonly apiKey: string;\r\n private readonly voiceId: string;\r\n private readonly model: string;\r\n private readonly outputFormat: string;\r\n private readonly stability: number;\r\n private readonly similarityBoost: number;\r\n private readonly baseUrl: string;\r\n private readonly _sampleRate: number;\r\n private _isLoaded = false;\r\n\r\n constructor(config: ElevenLabsConfig) {\r\n if (!config.apiKey) throw new Error('ElevenLabsTTS: apiKey is required');\r\n if (!config.voiceId) throw new Error('ElevenLabsTTS: voiceId is required');\r\n\r\n this.apiKey = config.apiKey;\r\n this.voiceId = config.voiceId;\r\n this.model = config.model ?? DEFAULT_MODEL;\r\n this.outputFormat = config.outputFormat ?? DEFAULT_OUTPUT_FORMAT;\r\n this.stability = config.stability ?? DEFAULT_STABILITY;\r\n this.similarityBoost = config.similarityBoost ?? DEFAULT_SIMILARITY_BOOST;\r\n this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\r\n\r\n const rate = FORMAT_TO_SAMPLE_RATE[this.outputFormat];\r\n if (!rate) {\r\n throw new Error(\r\n `ElevenLabsTTS: unsupported outputFormat \"${this.outputFormat}\". ` +\r\n `Supported: ${Object.keys(FORMAT_TO_SAMPLE_RATE).join(', ')}`,\r\n );\r\n }\r\n this._sampleRate = rate;\r\n }\r\n\r\n get sampleRate(): number {\r\n return this._sampleRate;\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this._isLoaded;\r\n }\r\n\r\n // ─── Load ───────────────────────────────────────────────────────────────\r\n\r\n /**\r\n * No-op for cloud TTS (no model to load).\r\n * Marks backend as ready.\r\n */\r\n async load(): Promise<void> {\r\n this._isLoaded = true;\r\n logger.info('ElevenLabs TTS ready', { voiceId: this.voiceId, model: this.model });\r\n }\r\n\r\n // ─── Stream ─────────────────────────────────────────────────────────────\r\n\r\n /**\r\n * Stream audio from ElevenLabs for the given text.\r\n *\r\n * Uses the streaming endpoint. Yields a single chunk for non-streaming\r\n * or multiple chunks as response data arrives.\r\n */\r\n async *stream(text: string, options?: TTSStreamOptions): AsyncGenerator<TTSChunk> {\r\n if (!this._isLoaded) {\r\n throw new Error('ElevenLabsTTS: not loaded. Call load() first.');\r\n }\r\n\r\n const trimmed = text.trim();\r\n if (trimmed.length === 0) {\r\n throw new Error('ElevenLabsTTS: text must not be empty');\r\n }\r\n\r\n const startTime = getClock().now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('ElevenLabsTTS.stream', {\r\n 'tts.text_length': trimmed.length,\r\n 'tts.voice_id': this.voiceId,\r\n 'tts.model': this.model,\r\n });\r\n\r\n const url = `${this.baseUrl}/v1/text-to-speech/${this.voiceId}?output_format=${this.outputFormat}`;\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers: {\r\n 'xi-api-key': this.apiKey,\r\n 'Content-Type': 'application/json',\r\n Accept: 'audio/pcm',\r\n },\r\n body: JSON.stringify({\r\n text: trimmed,\r\n model_id: this.model,\r\n voice_settings: {\r\n stability: this.stability,\r\n similarity_boost: this.similarityBoost,\r\n },\r\n }),\r\n signal: options?.signal,\r\n });\r\n\r\n if (!response.ok) {\r\n const errorText = await response.text().catch(() => 'unknown');\r\n const msg = `ElevenLabsTTS: HTTP ${response.status} — ${this.getHttpErrorMessage(response.status, errorText)}`;\r\n logger.error(msg);\r\n throw new Error(msg);\r\n }\r\n\r\n if (!response.body) {\r\n // Non-streaming fallback: read entire response\r\n const buffer = await response.arrayBuffer();\r\n const audio = pcm16ToFloat32(buffer);\r\n const duration = audio.length / this._sampleRate;\r\n\r\n const latency = getClock().now() - startTime;\r\n span?.setAttributes({ 'tts.duration_s': duration, 'tts.latency_ms': latency });\r\n span?.end();\r\n telemetry?.recordHistogram('omote.inference.latency', latency, {\r\n model: 'elevenlabs-tts',\r\n backend: 'cloud',\r\n });\r\n\r\n yield { audio, duration, text: trimmed };\r\n return;\r\n }\r\n\r\n // Streaming: read chunks from ReadableStream\r\n const reader = response.body.getReader();\r\n let totalSamples = 0;\r\n\r\n try {\r\n while (true) {\r\n if (options?.signal?.aborted) {\r\n reader.cancel();\r\n logger.debug('Stream aborted by signal');\r\n return;\r\n }\r\n\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n\r\n if (value && value.byteLength > 0) {\r\n // Ensure even byte count for Int16 alignment\r\n const usableBytes = value.byteLength & ~1;\r\n if (usableBytes === 0) continue;\r\n\r\n const audio = pcm16ToFloat32(value.buffer.slice(value.byteOffset, value.byteOffset + usableBytes));\r\n const duration = audio.length / this._sampleRate;\r\n totalSamples += audio.length;\r\n\r\n yield { audio, duration, text: trimmed };\r\n }\r\n }\r\n } finally {\r\n reader.releaseLock();\r\n }\r\n\r\n const latency = getClock().now() - startTime;\r\n const totalDuration = totalSamples / this._sampleRate;\r\n\r\n logger.debug('Stream complete', {\r\n totalDuration: `${totalDuration.toFixed(2)}s`,\r\n latencyMs: Math.round(latency),\r\n totalSamples,\r\n });\r\n\r\n span?.setAttributes({ 'tts.duration_s': totalDuration, 'tts.latency_ms': latency });\r\n span?.end();\r\n\r\n telemetry?.recordHistogram('omote.inference.latency', latency, {\r\n model: 'elevenlabs-tts',\r\n backend: 'cloud',\r\n });\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'elevenlabs-tts',\r\n backend: 'cloud',\r\n status: 'success',\r\n });\r\n } catch (err) {\r\n if (err instanceof DOMException && err.name === 'AbortError') {\r\n logger.debug('Stream aborted');\r\n span?.end();\r\n return;\r\n }\r\n\r\n const errMsg = err instanceof Error ? err.message : String(err);\r\n logger.error('Stream failed', { error: errMsg });\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n\r\n telemetry?.incrementCounter('omote.inference.total', 1, {\r\n model: 'elevenlabs-tts',\r\n backend: 'cloud',\r\n status: 'error',\r\n });\r\n\r\n throw err;\r\n }\r\n }\r\n\r\n // ─── Dispose ────────────────────────────────────────────────────────────\r\n\r\n async dispose(): Promise<void> {\r\n this._isLoaded = false;\r\n logger.info('ElevenLabs TTS disposed');\r\n }\r\n\r\n // ─── Private ────────────────────────────────────────────────────────────\r\n\r\n private getHttpErrorMessage(status: number, body: string): string {\r\n switch (status) {\r\n case 401:\r\n return 'Unauthorized — check your API key';\r\n case 403:\r\n return 'Forbidden — API key lacks required permissions';\r\n case 429:\r\n return 'Rate limited — too many requests';\r\n case 400:\r\n return `Bad request — ${body}`;\r\n default:\r\n return body || `HTTP error ${status}`;\r\n }\r\n }\r\n}\r\n","/**\n * Emotion - Helper for creating emotion vectors for avatar animation\n *\n * Provides 10 explicit emotion channels that can be used to control\n * avatar expressions and emotional states.\n *\n * @category Emotion\n *\n * @example Creating emotion vectors\n * ```typescript\n * import { createEmotionVector, EmotionPresets } from '@omote/core';\n *\n * // Named weights\n * const happy = createEmotionVector({ joy: 0.8, amazement: 0.2 });\n *\n * // Use preset\n * const surprised = EmotionPresets.surprised;\n * ```\n *\n * @example Smooth transitions\n * ```typescript\n * import { EmotionController } from '@omote/core';\n *\n * const controller = new EmotionController();\n * controller.setPreset('happy');\n * controller.transitionTo({ sadness: 0.7 }, 500);\n *\n * // In animation loop\n * controller.update();\n * const emotion = controller.emotion;\n * ```\n */\n\nimport { createLogger } from '../logging';\nimport { getClock } from '../logging/Clock';\n\nconst logger = createLogger('EmotionController');\n\n/** The 10 explicit emotion channels */\nexport const EMOTION_NAMES = [\n 'amazement',\n 'anger',\n 'cheekiness',\n 'disgust',\n 'fear',\n 'grief',\n 'joy',\n 'outofbreath',\n 'pain',\n 'sadness',\n] as const;\n\nexport type EmotionName = typeof EMOTION_NAMES[number];\n\n/** Emotion weights by name */\nexport type EmotionWeights = Partial<Record<EmotionName, number>>;\n\n/** Total emotion vector size */\nexport const EMOTION_VECTOR_SIZE = 26;\n\n/** Number of explicit emotion channels */\nexport const EXPLICIT_EMOTION_COUNT = 10;\n\n/**\n * Create an emotion vector from named weights\n *\n * @param weights - Named emotion weights (0-1)\n * @returns Float32Array of emotion values\n *\n * @example\n * ```ts\n * const emotion = createEmotionVector({ joy: 0.8, amazement: 0.3 });\n * ```\n */\nexport function createEmotionVector(weights: EmotionWeights = {}): Float32Array {\n const vector = new Float32Array(EMOTION_VECTOR_SIZE);\n\n for (const [name, value] of Object.entries(weights)) {\n const idx = EMOTION_NAMES.indexOf(name as EmotionName);\n if (idx >= 0) {\n vector[idx] = Math.max(0, Math.min(1, value));\n } else {\n logger.warn(`Invalid emotion name in createEmotionVector: \"${name}\"`);\n }\n }\n\n return vector;\n}\n\n/**\n * Pre-built emotion presets for common expressions\n */\nexport const EmotionPresets = {\n /** Neutral/default - no emotional expression */\n neutral: createEmotionVector({}),\n\n /** Happy - joy with slight amazement */\n happy: createEmotionVector({ joy: 0.7, amazement: 0.2 }),\n\n /** Sad - grief and sadness */\n sad: createEmotionVector({ sadness: 0.7, grief: 0.4 }),\n\n /** Angry - anger with disgust */\n angry: createEmotionVector({ anger: 0.8, disgust: 0.3 }),\n\n /** Surprised - high amazement */\n surprised: createEmotionVector({ amazement: 0.9, fear: 0.2 }),\n\n /** Scared - fear with pain */\n scared: createEmotionVector({ fear: 0.8, pain: 0.3 }),\n\n /** Disgusted - disgust with anger */\n disgusted: createEmotionVector({ disgust: 0.8, anger: 0.2 }),\n\n /** Excited - joy with amazement and cheekiness */\n excited: createEmotionVector({ joy: 0.6, amazement: 0.5, cheekiness: 0.4 }),\n\n /** Tired - out of breath with sadness */\n tired: createEmotionVector({ outofbreath: 0.6, sadness: 0.3 }),\n\n /** Playful - cheekiness with joy */\n playful: createEmotionVector({ cheekiness: 0.7, joy: 0.5 }),\n\n /** Pained - pain with grief */\n pained: createEmotionVector({ pain: 0.8, grief: 0.4 }),\n\n /** Contemplative - slight sadness, calm */\n contemplative: createEmotionVector({ sadness: 0.2, grief: 0.1 }),\n} as const;\n\nexport type EmotionPresetName = keyof typeof EmotionPresets;\n\n/**\n * Get an emotion preset by name\n */\nexport function getEmotionPreset(name: EmotionPresetName): Float32Array {\n return EmotionPresets[name].slice();\n}\n\n/**\n * Blend multiple emotion vectors together\n *\n * @param emotions - Array of { vector, weight } pairs\n * @returns Blended emotion vector\n *\n * @example\n * ```ts\n * const blended = blendEmotions([\n * { vector: EmotionPresets.happy, weight: 0.7 },\n * { vector: EmotionPresets.surprised, weight: 0.3 },\n * ]);\n * ```\n */\nexport function blendEmotions(\n emotions: Array<{ vector: Float32Array; weight: number }>\n): Float32Array {\n const result = new Float32Array(EMOTION_VECTOR_SIZE);\n let totalWeight = 0;\n\n for (const { vector, weight } of emotions) {\n totalWeight += weight;\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] += (vector[i] || 0) * weight;\n }\n }\n\n // Normalize if total weight > 0\n if (totalWeight > 0) {\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] /= totalWeight;\n }\n }\n\n return result;\n}\n\n/**\n * Interpolate between two emotion vectors\n *\n * @param from - Starting emotion\n * @param to - Target emotion\n * @param t - Interpolation factor (0-1)\n * @returns Interpolated emotion vector\n */\nexport function lerpEmotion(\n from: Float32Array,\n to: Float32Array,\n t: number\n): Float32Array {\n const result = new Float32Array(EMOTION_VECTOR_SIZE);\n const clampedT = Math.max(0, Math.min(1, t));\n\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] = (from[i] || 0) * (1 - clampedT) + (to[i] || 0) * clampedT;\n }\n\n return result;\n}\n\n/**\n * EmotionController - Manages emotion state with smooth transitions\n */\nexport class EmotionController {\n private currentEmotion = new Float32Array(EMOTION_VECTOR_SIZE);\n private targetEmotion = new Float32Array(EMOTION_VECTOR_SIZE);\n private transitionProgress = 1.0;\n private transitionDuration = 0;\n private transitionStartTime = 0;\n\n /**\n * Get the current emotion vector\n */\n get emotion(): Float32Array {\n if (this.transitionProgress >= 1.0) {\n return this.targetEmotion;\n }\n\n // Interpolate during transition\n return lerpEmotion(this.currentEmotion, this.targetEmotion, this.transitionProgress);\n }\n\n /**\n * Set emotion immediately (no transition)\n */\n set(weights: EmotionWeights): void {\n const newEmotion = createEmotionVector(weights);\n this.targetEmotion.set(newEmotion);\n this.currentEmotion.set(newEmotion);\n this.transitionProgress = 1.0;\n logger.debug('set', { weights });\n }\n\n /**\n * Set emotion from preset immediately\n */\n setPreset(preset: EmotionPresetName): void {\n const newEmotion = getEmotionPreset(preset);\n this.targetEmotion.set(newEmotion);\n this.currentEmotion.set(newEmotion);\n this.transitionProgress = 1.0;\n logger.debug('setPreset', { preset });\n }\n\n /**\n * Transition to new emotion over time\n *\n * @param weights - Target emotion weights\n * @param durationMs - Transition duration in milliseconds\n */\n transitionTo(weights: EmotionWeights, durationMs: number): void {\n this.currentEmotion.set(this.emotion);\n this.targetEmotion.set(createEmotionVector(weights));\n this.transitionDuration = durationMs;\n this.transitionStartTime = getClock().now();\n this.transitionProgress = 0;\n logger.debug('transitionTo', { weights, durationMs });\n }\n\n /**\n * Transition to preset over time\n */\n transitionToPreset(preset: EmotionPresetName, durationMs: number): void {\n this.currentEmotion.set(this.emotion);\n this.targetEmotion.set(getEmotionPreset(preset));\n this.transitionDuration = durationMs;\n this.transitionStartTime = getClock().now();\n this.transitionProgress = 0;\n }\n\n /**\n * Update transition progress (call each frame)\n */\n update(): void {\n if (this.transitionProgress >= 1.0) return;\n\n const elapsed = getClock().now() - this.transitionStartTime;\n this.transitionProgress = Math.min(1.0, elapsed / this.transitionDuration);\n }\n\n /**\n * Check if currently transitioning\n */\n get isTransitioning(): boolean {\n return this.transitionProgress < 1.0;\n }\n\n /**\n * Reset to neutral\n */\n reset(): void {\n this.currentEmotion.fill(0);\n this.targetEmotion.fill(0);\n this.transitionProgress = 1.0;\n logger.debug('reset');\n }\n}\n","/**\n * Animation Graph Types\n *\n * Renderer-agnostic animation state machine with emotion and audio-driven blending.\n *\n * @module animation\n */\n\n/**\n * Emotion labels for animation blending\n * Note: These are the 8 emotion categories used for animation, separate from the\n * internal EmotionName type used by EmotionController.\n */\nexport type EmotionLabel =\n | 'angry'\n | 'calm'\n | 'disgust'\n | 'fearful'\n | 'happy'\n | 'neutral'\n | 'sad'\n | 'surprised';\n\n/**\n * High-level animation states\n */\nexport type AnimationStateName = 'idle' | 'listening' | 'thinking' | 'speaking';\n\n/**\n * Events that trigger state transitions\n */\nexport type AnimationTrigger =\n | 'user_speech_start'\n | 'user_speech_end'\n | 'transcript_ready'\n | 'ai_response_start'\n | 'ai_audio_start'\n | 'ai_response_end'\n | 'timeout'\n | 'interrupt';\n\n/**\n * Animation layer types for blending\n */\nexport type AnimationLayer = 'base' | 'emotion' | 'gesture' | 'additive';\n\n/**\n * A single animation clip reference\n */\nexport interface AnimationClip {\n /** Unique identifier for the clip */\n name: string;\n /** Animation layer this clip belongs to */\n layer: AnimationLayer;\n /** Whether this clip loops */\n loop: boolean;\n /** Default duration in seconds (can be overridden by actual clip) */\n duration?: number;\n}\n\n/**\n * Blend weight for an animation clip\n */\nexport interface BlendWeight {\n /** Clip name */\n clip: string;\n /** Weight 0-1 */\n weight: number;\n /** Playback speed multiplier */\n speed: number;\n /** Current time in the animation (0-1 normalized) */\n time: number;\n}\n\n/**\n * Animation state definition\n */\nexport interface AnimationState {\n /** State name */\n name: AnimationStateName;\n /** Base animation clips for this state */\n baseClips: string[];\n /** Blend weights for base clips */\n baseWeights: number[];\n /** Whether emotion overlay is enabled in this state */\n emotionBlendEnabled: boolean;\n /** Whether gesture layer is enabled in this state */\n gestureBlendEnabled: boolean;\n /** Timeout in ms to auto-transition (0 = no timeout) */\n timeout: number;\n /** State to transition to on timeout */\n timeoutTarget?: AnimationStateName;\n}\n\n/**\n * Transition between states\n */\nexport interface Transition {\n /** Source state */\n from: AnimationStateName;\n /** Target state */\n to: AnimationStateName;\n /** Event that triggers this transition */\n trigger: AnimationTrigger;\n /** Blend duration in ms */\n duration: number;\n /** Optional condition function */\n condition?: () => boolean;\n}\n\n/**\n * Emotion to animation mapping\n */\nexport interface EmotionAnimationMap {\n /** Emotion label */\n emotion: EmotionLabel;\n /** Animation clip to blend */\n clip: string;\n /** Maximum blend weight for this emotion */\n maxWeight: number;\n /** Blend speed (weight change per second) */\n blendSpeed: number;\n}\n\n/**\n * Configuration for AnimationGraph\n */\nexport interface AnimationGraphConfig {\n /** Available animation states */\n states: AnimationState[];\n /** Transitions between states */\n transitions: Transition[];\n /** Emotion to animation mappings */\n emotionMappings: EmotionAnimationMap[];\n /** Gesture clips for audio-driven animation */\n gestureClips: string[];\n /** Initial state */\n initialState: AnimationStateName;\n /** Global blend speed for state transitions (weight/sec) */\n transitionBlendSpeed: number;\n /** Minimum audio energy to trigger gestures (0-1) */\n gestureThreshold: number;\n /** Gesture intensity multiplier */\n gestureIntensity: number;\n}\n\n/**\n * Current output of the animation graph\n */\nexport interface AnimationOutput {\n /** Current state name */\n state: AnimationStateName;\n /** All blend weights to apply */\n blendWeights: BlendWeight[];\n /** Active emotion (if any) */\n activeEmotion: EmotionLabel | null;\n /** Current gesture intensity (0-1) */\n gestureIntensity: number;\n /** Whether currently transitioning between states */\n isTransitioning: boolean;\n /** Transition progress (0-1) if transitioning */\n transitionProgress: number;\n}\n\n/**\n * Events emitted by AnimationGraph\n */\nexport type AnimationGraphEvents = {\n /** State changed */\n 'state.change': {\n from: AnimationStateName;\n to: AnimationStateName;\n trigger: AnimationTrigger;\n };\n /** Transition started */\n 'transition.start': {\n from: AnimationStateName;\n to: AnimationStateName;\n duration: number;\n };\n /** Transition completed */\n 'transition.end': {\n state: AnimationStateName;\n };\n /** Emotion changed */\n 'emotion.change': {\n emotion: EmotionLabel | null;\n confidence: number;\n };\n /** Animation output updated (every frame) */\n 'output.update': AnimationOutput;\n /** Index signature for EventEmitter compatibility */\n [key: string]: unknown;\n};\n\n/**\n * Default animation graph configuration\n */\nexport const DEFAULT_ANIMATION_CONFIG: AnimationGraphConfig = {\n initialState: 'idle',\n transitionBlendSpeed: 4.0, // Full blend in 250ms\n gestureThreshold: 0.1,\n gestureIntensity: 1.0,\n\n states: [\n {\n name: 'idle',\n baseClips: ['idle_breathe'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: false,\n timeout: 0,\n },\n {\n name: 'listening',\n baseClips: ['idle_attentive'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: false,\n timeout: 10000, // 10s timeout back to idle\n timeoutTarget: 'idle',\n },\n {\n name: 'thinking',\n baseClips: ['thinking_look_up', 'thinking_hand_chin'],\n baseWeights: [0.6, 0.4],\n emotionBlendEnabled: false,\n gestureBlendEnabled: false,\n timeout: 5000, // 5s max thinking\n timeoutTarget: 'idle',\n },\n {\n name: 'speaking',\n baseClips: ['talking_idle'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: true,\n timeout: 0,\n },\n ],\n\n transitions: [\n // User starts speaking\n { from: 'idle', to: 'listening', trigger: 'user_speech_start', duration: 300 },\n { from: 'speaking', to: 'listening', trigger: 'user_speech_start', duration: 200 }, // Interrupt\n\n // User stops speaking, processing\n { from: 'listening', to: 'thinking', trigger: 'transcript_ready', duration: 400 },\n\n // AI starts responding\n { from: 'thinking', to: 'speaking', trigger: 'ai_audio_start', duration: 300 },\n { from: 'idle', to: 'speaking', trigger: 'ai_audio_start', duration: 400 },\n\n // AI done\n { from: 'speaking', to: 'idle', trigger: 'ai_response_end', duration: 500 },\n\n // Timeouts\n { from: 'listening', to: 'idle', trigger: 'timeout', duration: 600 },\n { from: 'thinking', to: 'idle', trigger: 'timeout', duration: 400 },\n\n // Interrupts\n { from: 'speaking', to: 'listening', trigger: 'interrupt', duration: 150 },\n ],\n\n emotionMappings: [\n { emotion: 'happy', clip: 'emotion_happy', maxWeight: 0.7, blendSpeed: 2.0 },\n { emotion: 'sad', clip: 'emotion_sad', maxWeight: 0.6, blendSpeed: 1.5 },\n { emotion: 'angry', clip: 'emotion_angry', maxWeight: 0.5, blendSpeed: 2.5 },\n { emotion: 'fearful', clip: 'emotion_fear', maxWeight: 0.5, blendSpeed: 2.0 },\n { emotion: 'surprised', clip: 'emotion_surprised', maxWeight: 0.6, blendSpeed: 2.5 },\n { emotion: 'calm', clip: 'emotion_calm', maxWeight: 0.3, blendSpeed: 1.0 },\n { emotion: 'disgust', clip: 'emotion_disgust', maxWeight: 0.4, blendSpeed: 2.0 },\n { emotion: 'neutral', clip: 'emotion_neutral', maxWeight: 0.0, blendSpeed: 1.0 },\n ],\n\n gestureClips: ['gesture_hand_small', 'gesture_hand_medium', 'gesture_hand_large'],\n};\n","/**\n * Animation Graph\n *\n * State machine for character animation with emotion and audio-driven blending.\n * Renderer-agnostic - outputs blend weights that any 3D engine can consume.\n *\n * @example\n * ```typescript\n * import { AnimationGraph, DEFAULT_ANIMATION_CONFIG } from '@omote/core';\n *\n * const graph = new AnimationGraph(DEFAULT_ANIMATION_CONFIG);\n *\n * // Connect to voice pipeline\n * graph.on('output.update', (output) => {\n * // Apply blend weights to your 3D character\n * for (const { clip, weight } of output.blendWeights) {\n * mixer.getAction(clip).setEffectiveWeight(weight);\n * }\n * });\n *\n * // Drive from voice state\n * voiceState.on('listening', () => graph.trigger('user_speech_start'));\n * voiceState.on('thinking', () => graph.trigger('transcript_ready'));\n * voiceState.on('speaking', () => graph.trigger('ai_audio_start'));\n *\n * // Drive from emotion detection\n * emotion.on('result', ({ emotion, confidence }) => {\n * graph.setEmotion(emotion, confidence);\n * });\n *\n * // Update every frame\n * function animate(deltaTime: number) {\n * graph.update(deltaTime);\n * }\n * ```\n *\n * @module animation\n */\n\nimport { EventEmitter } from '../events';\nimport type {\n EmotionLabel,\n AnimationGraphConfig,\n AnimationGraphEvents,\n AnimationTrigger,\n AnimationOutput,\n AnimationState,\n AnimationStateName,\n BlendWeight,\n Transition,\n} from './types';\nimport { DEFAULT_ANIMATION_CONFIG } from './types';\nimport { createLogger } from '../logging';\n\nconst logger = createLogger('AnimationGraph');\n\n/**\n * Animation state machine with smooth blending\n */\nexport class AnimationGraph extends EventEmitter<AnimationGraphEvents> {\n private config: AnimationGraphConfig;\n private currentState: AnimationState;\n private previousState: AnimationState | null = null;\n\n // Transition state\n private isTransitioning: boolean = false;\n private transitionProgress: number = 0;\n private transitionDuration: number = 0;\n private transitionStartTime: number = 0;\n\n // Emotion state\n private currentEmotion: EmotionLabel | null = null;\n private emotionConfidence: number = 0;\n private emotionBlendWeight: number = 0;\n private targetEmotionWeight: number = 0;\n\n // Gesture state (audio-driven)\n private audioEnergy: number = 0;\n private gestureWeight: number = 0;\n private currentGestureClip: number = 0;\n\n // Timing\n private stateEnterTime: number = 0;\n private lastUpdateTime: number = 0;\n\n // Blend weights cache\n private cachedOutput: AnimationOutput;\n\n constructor(config: Partial<AnimationGraphConfig> = {}) {\n super();\n this.config = { ...DEFAULT_ANIMATION_CONFIG, ...config };\n\n // Find initial state\n const initialState = this.config.states.find(\n (s) => s.name === this.config.initialState\n );\n if (!initialState) {\n throw new Error(`Initial state '${this.config.initialState}' not found`);\n }\n this.currentState = initialState;\n this.stateEnterTime = Date.now();\n this.lastUpdateTime = Date.now();\n\n // Initialize cached output\n this.cachedOutput = this.computeOutput();\n\n logger.info('constructor', {\n initialState: this.config.initialState,\n stateCount: this.config.states.length,\n transitionCount: this.config.transitions.length,\n });\n }\n\n /**\n * Get current state name\n */\n get state(): AnimationStateName {\n return this.currentState.name;\n }\n\n /**\n * Get current animation output\n */\n get output(): AnimationOutput {\n return this.cachedOutput;\n }\n\n /**\n * Trigger an animation event (may cause state transition)\n */\n trigger(event: AnimationTrigger): boolean {\n // Find matching transition\n const transition = this.config.transitions.find(\n (t) =>\n t.from === this.currentState.name &&\n t.trigger === event &&\n (!t.condition || t.condition())\n );\n\n if (!transition) {\n return false;\n }\n\n this.startTransition(transition, event);\n return true;\n }\n\n /**\n * Set current emotion (from DistilHuBERT or manual)\n */\n setEmotion(emotion: EmotionLabel, confidence: number): void {\n const prevEmotion = this.currentEmotion;\n\n this.currentEmotion = emotion;\n this.emotionConfidence = Math.max(0, Math.min(1, confidence));\n\n // Find emotion mapping\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === emotion\n );\n if (mapping && this.currentState.emotionBlendEnabled) {\n this.targetEmotionWeight = mapping.maxWeight * this.emotionConfidence;\n } else {\n this.targetEmotionWeight = 0;\n }\n\n if (prevEmotion !== emotion) {\n this.emit('emotion.change', { emotion, confidence });\n }\n }\n\n /**\n * Clear current emotion\n */\n clearEmotion(): void {\n this.currentEmotion = null;\n this.emotionConfidence = 0;\n this.targetEmotionWeight = 0;\n this.emit('emotion.change', { emotion: null, confidence: 0 });\n }\n\n /**\n * Set audio energy for gesture animation (0-1)\n */\n setAudioEnergy(energy: number): void {\n this.audioEnergy = Math.max(0, Math.min(1, energy));\n }\n\n /**\n * Force transition to a specific state\n */\n setState(stateName: AnimationStateName, blendDuration: number = 300): void {\n const targetState = this.config.states.find((s) => s.name === stateName);\n if (!targetState) {\n logger.warn(`State '${stateName}' not found`);\n return;\n }\n\n if (targetState.name === this.currentState.name && !this.isTransitioning) {\n return;\n }\n\n // Create a manual transition\n const manualTransition: Transition = {\n from: this.currentState.name,\n to: stateName,\n trigger: 'timeout', // Arbitrary, not used for manual\n duration: blendDuration,\n };\n\n this.startTransition(manualTransition, 'timeout');\n }\n\n /**\n * Update animation graph (call every frame)\n * @param deltaMs Time since last update in milliseconds\n */\n update(deltaMs?: number): AnimationOutput {\n const now = Date.now();\n const dt = deltaMs ?? now - this.lastUpdateTime;\n this.lastUpdateTime = now;\n\n const dtSeconds = dt / 1000;\n\n // Update transition\n if (this.isTransitioning) {\n this.updateTransition(dtSeconds);\n }\n\n // Check timeout\n this.checkTimeout(now);\n\n // Update emotion blend\n this.updateEmotionBlend(dtSeconds);\n\n // Update gesture\n this.updateGesture(dtSeconds);\n\n // Compute and cache output\n this.cachedOutput = this.computeOutput();\n this.emit('output.update', this.cachedOutput);\n\n return this.cachedOutput;\n }\n\n /**\n * Reset to initial state\n */\n reset(): void {\n const initialState = this.config.states.find(\n (s) => s.name === this.config.initialState\n );\n if (initialState) {\n this.currentState = initialState;\n this.previousState = null;\n this.isTransitioning = false;\n this.transitionProgress = 0;\n this.stateEnterTime = Date.now();\n this.emotionBlendWeight = 0;\n this.gestureWeight = 0;\n this.cachedOutput = this.computeOutput();\n }\n }\n\n /**\n * Get all clip names used by this graph\n */\n getRequiredClips(): string[] {\n const clips = new Set<string>();\n\n // Base clips from all states\n for (const state of this.config.states) {\n for (const clip of state.baseClips) {\n clips.add(clip);\n }\n }\n\n // Emotion clips\n for (const mapping of this.config.emotionMappings) {\n clips.add(mapping.clip);\n }\n\n // Gesture clips\n for (const clip of this.config.gestureClips) {\n clips.add(clip);\n }\n\n return Array.from(clips);\n }\n\n // ─────────────────────────────────────────────────────────────────\n // Private methods\n // ─────────────────────────────────────────────────────────────────\n\n private startTransition(transition: Transition, event: AnimationTrigger): void {\n const targetState = this.config.states.find(\n (s) => s.name === transition.to\n );\n if (!targetState) {\n logger.warn(`Target state '${transition.to}' not found`);\n return;\n }\n\n const fromState = this.currentState.name;\n\n this.previousState = this.currentState;\n this.currentState = targetState;\n this.isTransitioning = true;\n this.transitionProgress = 0;\n this.transitionDuration = transition.duration;\n this.transitionStartTime = Date.now();\n this.stateEnterTime = Date.now();\n\n // Update emotion target based on new state\n if (!this.currentState.emotionBlendEnabled) {\n this.targetEmotionWeight = 0;\n }\n\n logger.debug('state transition', {\n from: fromState,\n to: targetState.name,\n trigger: event,\n duration: transition.duration,\n });\n\n this.emit('state.change', {\n from: fromState,\n to: targetState.name,\n trigger: event,\n });\n\n this.emit('transition.start', {\n from: fromState,\n to: targetState.name,\n duration: transition.duration,\n });\n }\n\n private updateTransition(dtSeconds: number): void {\n if (!this.isTransitioning || this.transitionDuration <= 0) {\n this.isTransitioning = false;\n this.transitionProgress = 1;\n return;\n }\n\n // Linear progress based on time\n const elapsed = Date.now() - this.transitionStartTime;\n this.transitionProgress = Math.min(1, elapsed / this.transitionDuration);\n\n if (this.transitionProgress >= 1) {\n this.isTransitioning = false;\n this.transitionProgress = 1;\n this.previousState = null;\n this.emit('transition.end', { state: this.currentState.name });\n }\n }\n\n private checkTimeout(now: number): void {\n if (this.isTransitioning) return;\n if (this.currentState.timeout <= 0) return;\n\n const elapsed = now - this.stateEnterTime;\n if (elapsed >= this.currentState.timeout) {\n logger.debug('timeout transition', {\n state: this.currentState.name,\n elapsed,\n timeout: this.currentState.timeout,\n });\n this.trigger('timeout');\n }\n }\n\n private updateEmotionBlend(dtSeconds: number): void {\n if (!this.currentEmotion) {\n // Decay emotion weight\n this.emotionBlendWeight = Math.max(\n 0,\n this.emotionBlendWeight - dtSeconds * 2.0\n );\n return;\n }\n\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === this.currentEmotion\n );\n const blendSpeed = mapping?.blendSpeed ?? 2.0;\n\n // Smoothly interpolate to target\n const diff = this.targetEmotionWeight - this.emotionBlendWeight;\n const maxChange = blendSpeed * dtSeconds;\n\n if (Math.abs(diff) <= maxChange) {\n this.emotionBlendWeight = this.targetEmotionWeight;\n } else {\n this.emotionBlendWeight += Math.sign(diff) * maxChange;\n }\n }\n\n private updateGesture(dtSeconds: number): void {\n if (!this.currentState.gestureBlendEnabled) {\n this.gestureWeight = Math.max(0, this.gestureWeight - dtSeconds * 4.0);\n return;\n }\n\n // Map audio energy to gesture weight\n const targetGesture =\n this.audioEnergy > this.config.gestureThreshold\n ? this.audioEnergy * this.config.gestureIntensity\n : 0;\n\n // Smooth the gesture weight\n const diff = targetGesture - this.gestureWeight;\n const blendSpeed = 8.0; // Fast response\n const maxChange = blendSpeed * dtSeconds;\n\n if (Math.abs(diff) <= maxChange) {\n this.gestureWeight = targetGesture;\n } else {\n this.gestureWeight += Math.sign(diff) * maxChange;\n }\n\n // Select gesture clip based on intensity\n const clipCount = this.config.gestureClips.length;\n if (clipCount > 0) {\n this.currentGestureClip = Math.min(\n clipCount - 1,\n Math.floor(this.gestureWeight * clipCount)\n );\n }\n }\n\n private computeOutput(): AnimationOutput {\n const blendWeights: BlendWeight[] = [];\n\n // Smooth transition weight (ease in-out)\n const t = this.transitionProgress;\n const transitionWeight = t * t * (3 - 2 * t); // smoothstep\n\n // Previous state clips (fading out)\n if (this.previousState && this.isTransitioning) {\n const fadeOut = 1 - transitionWeight;\n for (let i = 0; i < this.previousState.baseClips.length; i++) {\n const clip = this.previousState.baseClips[i];\n const baseWeight = this.previousState.baseWeights[i] ?? 1.0;\n blendWeights.push({\n clip,\n weight: baseWeight * fadeOut,\n speed: 1.0,\n time: 0,\n });\n }\n }\n\n // Current state clips (fading in or full)\n const fadeIn = this.isTransitioning ? transitionWeight : 1.0;\n for (let i = 0; i < this.currentState.baseClips.length; i++) {\n const clip = this.currentState.baseClips[i];\n const baseWeight = this.currentState.baseWeights[i] ?? 1.0;\n blendWeights.push({\n clip,\n weight: baseWeight * fadeIn,\n speed: 1.0,\n time: 0,\n });\n }\n\n // Emotion overlay\n if (this.currentEmotion && this.emotionBlendWeight > 0.01) {\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === this.currentEmotion\n );\n if (mapping) {\n blendWeights.push({\n clip: mapping.clip,\n weight: this.emotionBlendWeight,\n speed: 1.0,\n time: 0,\n });\n }\n }\n\n // Gesture layer\n if (this.gestureWeight > 0.01 && this.config.gestureClips.length > 0) {\n const gestureClip = this.config.gestureClips[this.currentGestureClip];\n blendWeights.push({\n clip: gestureClip,\n weight: this.gestureWeight,\n speed: 1.0 + this.audioEnergy * 0.5, // Faster with more energy\n time: 0,\n });\n }\n\n return {\n state: this.currentState.name,\n blendWeights,\n activeEmotion: this.emotionBlendWeight > 0.01 ? this.currentEmotion : null,\n gestureIntensity: this.gestureWeight,\n isTransitioning: this.isTransitioning,\n transitionProgress: this.transitionProgress,\n };\n }\n}\n","/**\n * Audio Energy Analysis\n *\n * Utilities for extracting energy/loudness from audio for gesture animation.\n *\n * @module animation\n */\n\n/**\n * Calculate RMS (Root Mean Square) energy from audio samples\n * @param samples Audio samples (Float32Array, normalized -1 to 1)\n * @returns RMS energy value (0 to 1)\n */\nexport function calculateRMS(samples: Float32Array): number {\n if (samples.length === 0) return 0;\n\n let sumSquares = 0;\n for (let i = 0; i < samples.length; i++) {\n sumSquares += samples[i] * samples[i];\n }\n\n return Math.sqrt(sumSquares / samples.length);\n}\n\n/**\n * Calculate peak amplitude from audio samples\n * @param samples Audio samples (Float32Array, normalized -1 to 1)\n * @returns Peak amplitude (0 to 1)\n */\nexport function calculatePeak(samples: Float32Array): number {\n let peak = 0;\n for (let i = 0; i < samples.length; i++) {\n const abs = Math.abs(samples[i]);\n if (abs > peak) peak = abs;\n }\n return peak;\n}\n\n/**\n * Smoothed energy analyzer for gesture animation\n */\nexport class AudioEnergyAnalyzer {\n private smoothedRMS: number = 0;\n private smoothedPeak: number = 0;\n private readonly smoothingFactor: number;\n private readonly noiseFloor: number;\n\n /**\n * @param smoothingFactor How much to smooth (0 = no smoothing, 1 = infinite smoothing). Default 0.85\n * @param noiseFloor Minimum energy threshold to consider as signal. Default 0.01\n */\n constructor(smoothingFactor: number = 0.85, noiseFloor: number = 0.01) {\n this.smoothingFactor = Math.max(0, Math.min(0.99, smoothingFactor));\n this.noiseFloor = noiseFloor;\n }\n\n /**\n * Process audio samples and return smoothed energy values\n * @param samples Audio samples (Float32Array)\n * @returns Object with rms and peak values\n */\n process(samples: Float32Array): { rms: number; peak: number; energy: number } {\n const instantRMS = calculateRMS(samples);\n const instantPeak = calculatePeak(samples);\n\n // Apply noise gate\n const gatedRMS = instantRMS > this.noiseFloor ? instantRMS : 0;\n const gatedPeak = instantPeak > this.noiseFloor ? instantPeak : 0;\n\n // Smooth the values (exponential moving average)\n // Attack fast (when getting louder), release slower\n if (gatedRMS > this.smoothedRMS) {\n // Fast attack\n this.smoothedRMS =\n this.smoothedRMS * 0.5 + gatedRMS * 0.5;\n } else {\n // Slow release\n this.smoothedRMS =\n this.smoothedRMS * this.smoothingFactor +\n gatedRMS * (1 - this.smoothingFactor);\n }\n\n if (gatedPeak > this.smoothedPeak) {\n this.smoothedPeak = this.smoothedPeak * 0.3 + gatedPeak * 0.7;\n } else {\n this.smoothedPeak =\n this.smoothedPeak * this.smoothingFactor +\n gatedPeak * (1 - this.smoothingFactor);\n }\n\n // Combined energy (weighted average of RMS and peak)\n // RMS is more stable, peak catches transients\n const energy = this.smoothedRMS * 0.7 + this.smoothedPeak * 0.3;\n\n return {\n rms: this.smoothedRMS,\n peak: this.smoothedPeak,\n energy: Math.min(1, energy * 2), // Scale up and clamp\n };\n }\n\n /**\n * Reset analyzer state\n */\n reset(): void {\n this.smoothedRMS = 0;\n this.smoothedPeak = 0;\n }\n\n /**\n * Get current smoothed RMS value\n */\n get rms(): number {\n return this.smoothedRMS;\n }\n\n /**\n * Get current smoothed peak value\n */\n get peak(): number {\n return this.smoothedPeak;\n }\n}\n\n/**\n * Extract emphasis points from audio (for gesture timing)\n *\n * Detects sudden increases in energy that correspond to speech emphasis.\n */\nexport class EmphasisDetector {\n private energyHistory: number[] = [];\n private readonly historySize: number;\n private readonly emphasisThreshold: number;\n\n /**\n * @param historySize Number of frames to track. Default 10\n * @param emphasisThreshold Minimum energy increase to count as emphasis. Default 0.15\n */\n constructor(historySize: number = 10, emphasisThreshold: number = 0.15) {\n this.historySize = historySize;\n this.emphasisThreshold = emphasisThreshold;\n }\n\n /**\n * Process energy value and detect emphasis\n * @param energy Current energy value (0-1)\n * @returns Object with isEmphasis flag and emphasisStrength\n */\n process(energy: number): { isEmphasis: boolean; emphasisStrength: number } {\n this.energyHistory.push(energy);\n if (this.energyHistory.length > this.historySize) {\n this.energyHistory.shift();\n }\n\n if (this.energyHistory.length < 3) {\n return { isEmphasis: false, emphasisStrength: 0 };\n }\n\n // Calculate average of previous frames (excluding current)\n const prevFrames = this.energyHistory.slice(0, -1);\n const avgPrev = prevFrames.reduce((a, b) => a + b, 0) / prevFrames.length;\n\n // Compare current to average\n const increase = energy - avgPrev;\n const isEmphasis = increase > this.emphasisThreshold;\n\n return {\n isEmphasis,\n emphasisStrength: isEmphasis ? Math.min(1, increase / 0.3) : 0,\n };\n }\n\n /**\n * Reset detector state\n */\n reset(): void {\n this.energyHistory = [];\n }\n}\n","/**\r\n * ProceduralLifeLayer - Renderer-agnostic procedural animation system\r\n *\r\n * Outputs per-frame blendshape values and head deltas for organic life-like\r\n * animation. No Three.js, no React, no R3F — just math.\r\n *\r\n * Implements research-based eye behavior, blinks, gaze breaks, microsaccades,\r\n * breathing/postural sway, and simplex noise-driven brow drift.\r\n *\r\n * Research sources:\r\n * - Blink frequency: log-normal IBI (mean=5.97s, SD(log)=0.89), PMC3565584\r\n * - Blink shape: asymmetric (92ms close, 242ms open, 3:1 ratio), PMC4043155\r\n * - Saccade latency: ~200ms, duration 20-200ms\r\n * - Microsaccades: ~1/second, amplitude 0.02-0.05, Scholarpedia\r\n * - Fixation duration: 200-350ms, Nature Scientific Reports\r\n * - Conversational gaze: Kendon (1967), Argyle & Cook (1976)\r\n * - Brow noise: NVIDIA Audio2Face, Unreal MetaHuman layered procedural animation\r\n *\r\n * @category Animation\r\n *\r\n * @example\r\n * ```typescript\r\n * import { ProceduralLifeLayer } from '@omote/core';\r\n *\r\n * const lifeLayer = new ProceduralLifeLayer();\r\n *\r\n * // In animation loop:\r\n * const output = lifeLayer.update(delta, {\r\n * eyeTargetX: normalizedX, // -1..1 from camera math\r\n * eyeTargetY: normalizedY,\r\n * audioEnergy: energy, // 0-1 from AudioEnergyAnalyzer\r\n * isSpeaking: true,\r\n * state: 'speaking', // conversational state for gaze behavior\r\n * });\r\n *\r\n * // Apply blendshapes to mesh\r\n * for (const [name, value] of Object.entries(output.blendshapes)) {\r\n * const idx = mesh.morphTargetDictionary?.[name];\r\n * if (idx !== undefined) mesh.morphTargetInfluences![idx] = value;\r\n * }\r\n *\r\n * // Apply head delta to head bone\r\n * headBone.rotation.y += output.headDelta.yaw;\r\n * headBone.rotation.x += output.headDelta.pitch;\r\n * ```\r\n */\r\n\r\nimport { createNoise2D } from 'simplex-noise';\r\nimport { ARKIT_BLENDSHAPES } from '../inference/blendshapeUtils';\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('ProceduralLifeLayer');\r\n\r\nconst simplex2d = createNoise2D();\r\n\r\n/** Pre-computed blendshape name → ARKit index map */\r\nconst LIFE_BS_INDEX = new Map<string, number>();\r\nfor (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {\r\n LIFE_BS_INDEX.set(ARKIT_BLENDSHAPES[i], i);\r\n}\r\n\r\n/**\r\n * Configuration for ProceduralLifeLayer\r\n */\r\nexport interface LifeLayerConfig {\r\n /** Seconds between blinks [min, max]. Default: [2.5, 6] */\r\n blinkIntervalRange?: [number, number];\r\n /** Seconds between gaze breaks [min, max]. Default: [3, 8] */\r\n gazeBreakIntervalRange?: [number, number];\r\n /** Gaze break deviation range [min, max]. Default: [0.15, 0.4] */\r\n gazeBreakAmplitudeRange?: [number, number];\r\n /** Eye micro-motion noise amplitude (0 to disable). Default: 0.06 */\r\n eyeNoiseAmplitude?: number;\r\n /** Base simplex noise amplitude for brow drift. Default: 0.30 */\r\n browNoiseAmplitude?: number;\r\n /** Multiply brow noise when speaking. Default: 2.0 */\r\n browNoiseSpeechMultiplier?: number;\r\n /** Breathing rate in Hz (0.25 = 15 breaths/min). Default: 0.25 */\r\n breathingRate?: number;\r\n /** Postural sway amplitude in radians. Default: 0.002 */\r\n posturalSwayAmplitude?: number;\r\n /** Max eye movement from center (0-1). Default: 0.8 */\r\n eyeMaxDeviation?: number;\r\n /** Eye smoothing factor (higher = faster response). Default: 15 */\r\n eyeSmoothing?: number;\r\n}\r\n\r\n/** Conversational state for state-dependent gaze behavior */\r\nexport type ConversationalState = 'idle' | 'listening' | 'thinking' | 'speaking';\r\n\r\n/**\r\n * Per-frame input to the life layer\r\n */\r\nexport interface LifeLayerInput {\r\n /** Normalized eye target X: -1 (left) to 1 (right). Consumer computes from camera. */\r\n eyeTargetX?: number;\r\n /** Normalized eye target Y: -1 (down) to 1 (up). Consumer computes from camera. */\r\n eyeTargetY?: number;\r\n /** Audio energy 0-1 (from AudioEnergyAnalyzer). Drives brow noise amplitude. */\r\n audioEnergy?: number;\r\n /** Whether avatar is speaking. Multiplies brow noise amplitude. */\r\n isSpeaking?: boolean;\r\n /** Conversational state for gaze behavior (idle/listening/thinking/speaking) */\r\n state?: ConversationalState;\r\n}\r\n\r\n/**\r\n * Per-frame output from the life layer\r\n */\r\nexport interface LifeLayerOutput {\r\n /** Blendshape values to SET directly on mesh (eyes, brows, cheeks). */\r\n blendshapes: Record<string, number>;\r\n /** Head rotation deltas in radians. Consumer adds to head bone rotation. */\r\n headDelta: { yaw: number; pitch: number };\r\n}\r\n\r\n// Blink phase constants\r\nconst PHASE_OPEN = 0;\r\nconst PHASE_CLOSING = 1;\r\nconst PHASE_CLOSED = 2;\r\nconst PHASE_OPENING = 3;\r\n\r\n// Blink timing (PMC4043155: asymmetric 3:1 ratio)\r\nconst BLINK_CLOSE_DURATION = 0.092; // 92ms (fast close)\r\nconst BLINK_HOLD_DURATION = 0.04; // 40ms hold\r\nconst BLINK_OPEN_DURATION = 0.242; // 242ms (slow open)\r\nconst BLINK_ASYMMETRY_DELAY = 0.008; // 8ms offset between eyes\r\n\r\n// Log-normal blink interval parameters (PMC3565584)\r\nconst BLINK_IBI_MU = Math.log(5.97); // mean IBI = 5.97s\r\nconst BLINK_IBI_SIGMA = 0.89; // SD(log) = 0.89\r\n\r\n// Gaze break phase timing\r\nconst GAZE_BREAK_DURATION = 0.12; // 120ms to glance away\r\nconst GAZE_BREAK_HOLD_DURATION = 0.3; // 300ms hold\r\nconst GAZE_BREAK_RETURN_DURATION = 0.15; // 150ms to return\r\n\r\n/**\r\n * State-dependent conversational gaze parameters\r\n *\r\n * Based on Kendon (1967) and Argyle & Cook (1976):\r\n * - Listeners maintain high eye contact (~75%)\r\n * - Thinkers avert gaze frequently (~17% contact)\r\n * - Speakers maintain moderate contact (~45%)\r\n */\r\nconst GAZE_STATE_PARAMS: Record<ConversationalState, {\r\n interval: [number, number];\r\n amplitude: [number, number];\r\n}> = {\r\n idle: { interval: [2, 5], amplitude: [0.15, 0.4] },\r\n listening: { interval: [4, 10], amplitude: [0.1, 0.25] },\r\n thinking: { interval: [1, 3], amplitude: [0.2, 0.5] },\r\n speaking: { interval: [2, 6], amplitude: [0.15, 0.35] },\r\n};\r\n\r\n// Eye micro-motion noise frequencies (Hz)\r\nconst EYE_NOISE_X_FREQ = 0.8;\r\nconst EYE_NOISE_Y_FREQ = 0.6;\r\nconst EYE_NOISE_X_PHASE = 73.1;\r\nconst EYE_NOISE_Y_PHASE = 91.7;\r\n\r\n// Brow noise frequencies (Hz)\r\nconst BROW_INNER_UP_FREQ = 0.4;\r\nconst BROW_OUTER_LEFT_FREQ = 0.35;\r\nconst BROW_OUTER_RIGHT_FREQ = 0.38;\r\nconst BROW_DOWN_FREQ = 0.3;\r\n\r\n// Brow noise phase offsets\r\nconst BROW_INNER_UP_PHASE = 0;\r\nconst BROW_OUTER_LEFT_PHASE = 17.3;\r\nconst BROW_OUTER_RIGHT_PHASE = 31.7;\r\nconst BROW_DOWN_LEFT_PHASE = 47.1;\r\nconst BROW_DOWN_RIGHT_PHASE = 59.3;\r\n\r\n// Emphasis detection\r\nconst EMPHASIS_ENERGY_THRESHOLD = 0.3;\r\nconst EMPHASIS_DECAY_RATE = 4.0;\r\n\r\nfunction clamp(v: number, min: number, max: number): number {\r\n return v < min ? min : v > max ? max : v;\r\n}\r\n\r\nfunction randomRange(min: number, max: number): number {\r\n return min + Math.random() * (max - min);\r\n}\r\n\r\nfunction smoothStep(t: number): number {\r\n return t * t * (3 - 2 * t);\r\n}\r\n\r\nfunction softClamp(v: number, max: number): number {\r\n return Math.tanh(v / max) * max;\r\n}\r\n\r\n/**\r\n * Sample from a log-normal distribution using Box-Muller transform.\r\n * PMC3565584: mean IBI = 5.97s, log-space SD = 0.89\r\n */\r\nfunction sampleLogNormal(mu: number, sigma: number): number {\r\n const u1 = Math.random();\r\n const u2 = Math.random();\r\n const z = Math.sqrt(-2 * Math.log(u1 || 1e-10)) * Math.cos(2 * Math.PI * u2);\r\n return Math.exp(mu + sigma * z);\r\n}\r\n\r\n/**\r\n * ProceduralLifeLayer - Renderer-agnostic procedural animation\r\n *\r\n * Generates per-frame blendshape values and head rotation deltas\r\n * for natural eye behavior, blinks, brow movement, and breathing.\r\n */\r\nexport class ProceduralLifeLayer {\r\n // Config\r\n private blinkIntervalRange: [number, number];\r\n private useLogNormalBlinks: boolean;\r\n private gazeBreakIntervalRange: [number, number];\r\n private gazeBreakAmplitudeRange: [number, number];\r\n private eyeNoiseAmplitude: number;\r\n private browNoiseAmplitude: number;\r\n private browNoiseSpeechMultiplier: number;\r\n private breathingRate: number;\r\n private posturalSwayAmplitude: number;\r\n private eyeMaxDeviation: number;\r\n private eyeSmoothing: number;\r\n\r\n // Blink state\r\n private blinkTimer = 0;\r\n private blinkInterval: number;\r\n private blinkPhase = PHASE_OPEN;\r\n private blinkProgress = 0;\r\n private asymmetryRight = 0.97;\r\n private smoothedBlinkLeft = 0;\r\n private smoothedBlinkRight = 0;\r\n\r\n // Eye contact (smoothed)\r\n private smoothedEyeX = 0;\r\n private smoothedEyeY = 0;\r\n\r\n // Eye micro-motion\r\n private eyeNoiseTime = 0;\r\n\r\n // Gaze break state\r\n private gazeBreakTimer = 0;\r\n private gazeBreakInterval: number;\r\n private gazeBreakPhase = PHASE_OPEN;\r\n private gazeBreakProgress = 0;\r\n private gazeBreakTargetX = 0;\r\n private gazeBreakTargetY = 0;\r\n private gazeBreakCurrentX = 0;\r\n private gazeBreakCurrentY = 0;\r\n\r\n // Conversational state for gaze\r\n private currentState: ConversationalState | null = null;\r\n\r\n // Breathing / postural sway\r\n private microMotionTime = 0;\r\n private breathingPhase = 0;\r\n\r\n // Brow noise\r\n private noiseTime = 0;\r\n private previousEnergy = 0;\r\n private emphasisLevel = 0;\r\n\r\n // Pre-allocated output object (reused each frame to avoid GC pressure)\r\n private readonly _outputBlendshapes: Record<string, number>;\r\n\r\n constructor(config?: LifeLayerConfig) {\r\n this.blinkIntervalRange = config?.blinkIntervalRange ?? [2.5, 6];\r\n this.useLogNormalBlinks = !config?.blinkIntervalRange; // log-normal only when using defaults\r\n this.gazeBreakIntervalRange = config?.gazeBreakIntervalRange ?? [3, 8];\r\n this.gazeBreakAmplitudeRange = config?.gazeBreakAmplitudeRange ?? [0.15, 0.4];\r\n this.eyeNoiseAmplitude = config?.eyeNoiseAmplitude ?? 0.06;\r\n this.browNoiseAmplitude = config?.browNoiseAmplitude ?? 0.12;\r\n this.browNoiseSpeechMultiplier = config?.browNoiseSpeechMultiplier ?? 1.5;\r\n this.breathingRate = config?.breathingRate ?? 0.25;\r\n this.posturalSwayAmplitude = config?.posturalSwayAmplitude ?? 0.002;\r\n this.eyeMaxDeviation = config?.eyeMaxDeviation ?? 0.8;\r\n this.eyeSmoothing = config?.eyeSmoothing ?? 15;\r\n\r\n // Pre-allocate output blendshapes object with all 52 keys\r\n this._outputBlendshapes = {};\r\n for (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {\r\n this._outputBlendshapes[ARKIT_BLENDSHAPES[i]] = 0;\r\n }\r\n\r\n // Initialize with random intervals\r\n this.blinkInterval = this.nextBlinkInterval();\r\n this.gazeBreakInterval = randomRange(...this.gazeBreakIntervalRange);\r\n\r\n logger.debug('constructor', {\r\n blinkIntervalRange: this.blinkIntervalRange,\r\n useLogNormalBlinks: this.useLogNormalBlinks,\r\n gazeBreakIntervalRange: this.gazeBreakIntervalRange,\r\n eyeNoiseAmplitude: this.eyeNoiseAmplitude,\r\n browNoiseAmplitude: this.browNoiseAmplitude,\r\n breathingRate: this.breathingRate,\r\n });\r\n }\r\n\r\n /**\r\n * Update the life layer and produce output for this frame.\r\n *\r\n * @param delta - Time since last frame in seconds\r\n * @param input - Per-frame input (eye target, audio energy, speaking state)\r\n * @returns Blendshape values and head rotation deltas\r\n */\r\n update(delta: number, input?: LifeLayerInput): LifeLayerOutput {\r\n const eyeTargetX = input?.eyeTargetX ?? 0;\r\n const eyeTargetY = input?.eyeTargetY ?? 0;\r\n const audioEnergy = input?.audioEnergy ?? 0;\r\n const isSpeaking = input?.isSpeaking ?? false;\r\n\r\n // Update conversational state\r\n this.currentState = input?.state ?? null;\r\n\r\n // Cap delta to prevent instant snaps when returning from backgrounded tab\r\n const safeDelta = Math.min(delta, 0.1);\r\n\r\n // Reuse pre-allocated output object — zero all values first\r\n const blendshapes = this._outputBlendshapes;\r\n for (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {\r\n blendshapes[ARKIT_BLENDSHAPES[i]] = 0;\r\n }\r\n\r\n // =====================================================================\r\n // BLINKS\r\n // =====================================================================\r\n this.updateBlinks(delta);\r\n\r\n const blinkSmoothing = 45;\r\n const blinkValues = this.getBlinkValues();\r\n this.smoothedBlinkLeft += (blinkValues.left - this.smoothedBlinkLeft) * Math.min(1, safeDelta * blinkSmoothing);\r\n this.smoothedBlinkRight += (blinkValues.right - this.smoothedBlinkRight) * Math.min(1, safeDelta * blinkSmoothing);\r\n\r\n blendshapes['eyeBlinkLeft'] = this.smoothedBlinkLeft;\r\n blendshapes['eyeBlinkRight'] = this.smoothedBlinkRight;\r\n\r\n // =====================================================================\r\n // EYE CONTACT (smooth toward target)\r\n // =====================================================================\r\n this.smoothedEyeX += (eyeTargetX - this.smoothedEyeX) * Math.min(1, safeDelta * this.eyeSmoothing);\r\n this.smoothedEyeY += (eyeTargetY - this.smoothedEyeY) * Math.min(1, safeDelta * this.eyeSmoothing);\r\n\r\n // =====================================================================\r\n // EYE MICRO-MOTION (continuous simplex noise replaces discrete saccades)\r\n // =====================================================================\r\n this.eyeNoiseTime += delta;\r\n const microMotion = this.getEyeMicroMotion();\r\n\r\n // =====================================================================\r\n // GAZE BREAKS\r\n // =====================================================================\r\n this.updateGazeBreaks(delta);\r\n\r\n // =====================================================================\r\n // COMBINED EYE DIRECTION\r\n // =====================================================================\r\n const finalEyeX = this.smoothedEyeX + this.gazeBreakCurrentX + microMotion.x;\r\n const finalEyeY = this.smoothedEyeY + this.gazeBreakCurrentY + microMotion.y;\r\n\r\n // Soft clamp: tanh compression avoids hard boundary pop\r\n const clampedX = softClamp(finalEyeX, this.eyeMaxDeviation);\r\n const clampedY = softClamp(finalEyeY, this.eyeMaxDeviation);\r\n\r\n // Convert to blendshape values with smooth zero-crossing\r\n const deadZone = 0.02;\r\n const lookRight = clampedX > deadZone ? clampedX\r\n : clampedX > 0 ? clampedX * (clampedX / deadZone) : 0;\r\n const lookLeft = clampedX < -deadZone ? -clampedX\r\n : clampedX < 0 ? -clampedX * (-clampedX / deadZone) : 0;\r\n const lookUp = clampedY > deadZone ? clampedY\r\n : clampedY > 0 ? clampedY * (clampedY / deadZone) : 0;\r\n const lookDown = clampedY < -deadZone ? -clampedY\r\n : clampedY < 0 ? -clampedY * (-clampedY / deadZone) : 0;\r\n\r\n // Conjugate pairing: both eyes move together naturally\r\n blendshapes['eyeLookInLeft'] = lookRight;\r\n blendshapes['eyeLookOutLeft'] = lookLeft;\r\n blendshapes['eyeLookInRight'] = lookLeft;\r\n blendshapes['eyeLookOutRight'] = lookRight;\r\n blendshapes['eyeLookUpLeft'] = lookUp;\r\n blendshapes['eyeLookUpRight'] = lookUp;\r\n blendshapes['eyeLookDownLeft'] = lookDown;\r\n blendshapes['eyeLookDownRight'] = lookDown;\r\n\r\n // =====================================================================\r\n // BROW NOISE (Simplex-driven organic drift)\r\n // =====================================================================\r\n this.updateBrowNoise(delta, audioEnergy, isSpeaking, blendshapes);\r\n\r\n // =====================================================================\r\n // BREATHING + POSTURAL SWAY\r\n // =====================================================================\r\n this.microMotionTime += delta;\r\n this.breathingPhase += delta * this.breathingRate * Math.PI * 2;\r\n\r\n const breathingY = Math.sin(this.breathingPhase) * 0.003;\r\n const swayAmp = this.posturalSwayAmplitude;\r\n const swayX = Math.sin(this.microMotionTime * 0.7) * swayAmp +\r\n Math.sin(this.microMotionTime * 1.3) * swayAmp * 0.5;\r\n const swayY = Math.sin(this.microMotionTime * 0.5) * swayAmp * 0.75 +\r\n Math.sin(this.microMotionTime * 0.9) * swayAmp * 0.5;\r\n\r\n // =====================================================================\r\n // BREATHING BLENDSHAPES (subtle jaw/nose movement)\r\n // =====================================================================\r\n const breathVal = Math.sin(this.breathingPhase);\r\n if (breathVal > 0) {\r\n blendshapes['jawOpen'] = breathVal * 0.015;\r\n blendshapes['noseSneerLeft'] = breathVal * 0.008;\r\n blendshapes['noseSneerRight'] = breathVal * 0.008;\r\n }\r\n\r\n return {\r\n blendshapes,\r\n headDelta: {\r\n yaw: swayX,\r\n pitch: breathingY + swayY,\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Write life layer output directly to a Float32Array[52] in ARKIT_BLENDSHAPES order.\r\n *\r\n * Includes micro-jitter (0.4% amplitude simplex noise on all channels) to\r\n * break uncanny stillness on undriven channels.\r\n *\r\n * @param delta - Time since last frame in seconds\r\n * @param input - Per-frame input\r\n * @param out - Pre-allocated Float32Array(52) to write into\r\n */\r\n updateToArray(delta: number, input: LifeLayerInput, out: Float32Array): void {\r\n out.fill(0);\r\n const result = this.update(delta, input);\r\n\r\n // Map named blendshapes to ARKit indices\r\n for (const [name, value] of Object.entries(result.blendshapes)) {\r\n const idx = LIFE_BS_INDEX.get(name);\r\n if (idx !== undefined) {\r\n out[idx] = value;\r\n }\r\n }\r\n\r\n // Micro-jitter: tiny simplex noise on all 52 channels\r\n for (let i = 0; i < 52; i++) {\r\n out[i] += simplex2d(this.noiseTime * 0.3, i * 7.13) * 0.004;\r\n }\r\n }\r\n\r\n /**\r\n * Reset all internal state to initial values.\r\n */\r\n reset(): void {\r\n logger.debug('reset');\r\n // Blink\r\n this.blinkTimer = 0;\r\n this.blinkInterval = this.nextBlinkInterval();\r\n this.blinkPhase = PHASE_OPEN;\r\n this.blinkProgress = 0;\r\n this.asymmetryRight = 0.97;\r\n this.smoothedBlinkLeft = 0;\r\n this.smoothedBlinkRight = 0;\r\n\r\n // Eye contact\r\n this.smoothedEyeX = 0;\r\n this.smoothedEyeY = 0;\r\n\r\n // Eye micro-motion\r\n this.eyeNoiseTime = 0;\r\n\r\n // Gaze break\r\n this.gazeBreakTimer = 0;\r\n this.gazeBreakInterval = randomRange(...this.gazeBreakIntervalRange);\r\n this.gazeBreakPhase = PHASE_OPEN;\r\n this.gazeBreakProgress = 0;\r\n this.gazeBreakTargetX = 0;\r\n this.gazeBreakTargetY = 0;\r\n this.gazeBreakCurrentX = 0;\r\n this.gazeBreakCurrentY = 0;\r\n\r\n // State\r\n this.currentState = null;\r\n\r\n // Breathing / sway\r\n this.microMotionTime = 0;\r\n this.breathingPhase = 0;\r\n\r\n // Brow noise\r\n this.noiseTime = 0;\r\n this.previousEnergy = 0;\r\n this.emphasisLevel = 0;\r\n }\r\n\r\n // =====================================================================\r\n // PRIVATE: Blink interval sampling\r\n // =====================================================================\r\n\r\n /**\r\n * Sample next blink interval.\r\n * Uses log-normal distribution (PMC3565584) when using default config,\r\n * or uniform random when custom blinkIntervalRange is provided.\r\n */\r\n private nextBlinkInterval(): number {\r\n if (this.useLogNormalBlinks) {\r\n const sample = sampleLogNormal(BLINK_IBI_MU, BLINK_IBI_SIGMA);\r\n return clamp(sample, 1.5, 12);\r\n }\r\n return randomRange(...this.blinkIntervalRange);\r\n }\r\n\r\n // =====================================================================\r\n // PRIVATE: Blink system\r\n // =====================================================================\r\n\r\n private updateBlinks(delta: number): void {\r\n this.blinkTimer += delta;\r\n\r\n if (this.blinkTimer >= this.blinkInterval && this.blinkPhase === PHASE_OPEN) {\r\n this.blinkPhase = PHASE_CLOSING;\r\n this.blinkProgress = 0;\r\n this.blinkTimer = 0;\r\n this.blinkInterval = this.nextBlinkInterval();\r\n this.asymmetryRight = 0.95 + Math.random() * 0.08;\r\n logger.trace('blink', { nextInterval: this.blinkInterval });\r\n }\r\n\r\n if (this.blinkPhase > PHASE_OPEN) {\r\n this.blinkProgress += delta;\r\n\r\n if (this.blinkPhase === PHASE_CLOSING) {\r\n if (this.blinkProgress >= BLINK_CLOSE_DURATION) {\r\n this.blinkPhase = PHASE_CLOSED;\r\n this.blinkProgress = 0;\r\n }\r\n } else if (this.blinkPhase === PHASE_CLOSED) {\r\n if (this.blinkProgress >= BLINK_HOLD_DURATION) {\r\n this.blinkPhase = PHASE_OPENING;\r\n this.blinkProgress = 0;\r\n }\r\n } else if (this.blinkPhase === PHASE_OPENING) {\r\n if (this.blinkProgress >= BLINK_OPEN_DURATION) {\r\n this.blinkPhase = PHASE_OPEN;\r\n this.blinkProgress = 0;\r\n }\r\n }\r\n }\r\n }\r\n\r\n private getBlinkValues(): { left: number; right: number } {\r\n if (this.blinkPhase === PHASE_OPEN) {\r\n return { left: 0, right: 0 };\r\n }\r\n\r\n if (this.blinkPhase === PHASE_CLOSING) {\r\n const t = Math.min(1, this.blinkProgress / BLINK_CLOSE_DURATION);\r\n const eased = t * t * t; // Cubic ease-in for snappy close\r\n const tRight = Math.max(0, Math.min(1, (this.blinkProgress - BLINK_ASYMMETRY_DELAY) / BLINK_CLOSE_DURATION));\r\n return {\r\n left: eased,\r\n right: tRight * tRight * tRight * this.asymmetryRight,\r\n };\r\n }\r\n\r\n if (this.blinkPhase === PHASE_CLOSED) {\r\n return { left: 1, right: this.asymmetryRight };\r\n }\r\n\r\n // PHASE_OPENING\r\n const t = Math.min(1, this.blinkProgress / BLINK_OPEN_DURATION);\r\n const eased = smoothStep(t);\r\n return {\r\n left: 1 - eased,\r\n right: (1 - eased) * this.asymmetryRight,\r\n };\r\n }\r\n\r\n // =====================================================================\r\n // PRIVATE: Eye micro-motion (continuous simplex noise)\r\n // =====================================================================\r\n\r\n private getEyeMicroMotion(): { x: number; y: number } {\r\n const amp = this.eyeNoiseAmplitude;\r\n const x = simplex2d(this.eyeNoiseTime * EYE_NOISE_X_FREQ, EYE_NOISE_X_PHASE) * amp;\r\n const y = simplex2d(this.eyeNoiseTime * EYE_NOISE_Y_FREQ, EYE_NOISE_Y_PHASE) * amp * 0.7;\r\n return { x, y };\r\n }\r\n\r\n // =====================================================================\r\n // PRIVATE: Gaze breaks (state-dependent)\r\n // =====================================================================\r\n\r\n /**\r\n * Get active gaze parameters — uses state-dependent params when\r\n * conversational state is provided, otherwise falls back to config ranges.\r\n */\r\n private getActiveGazeParams(): { interval: [number, number]; amplitude: [number, number] } {\r\n if (this.currentState && GAZE_STATE_PARAMS[this.currentState]) {\r\n return GAZE_STATE_PARAMS[this.currentState];\r\n }\r\n return {\r\n interval: this.gazeBreakIntervalRange,\r\n amplitude: this.gazeBreakAmplitudeRange,\r\n };\r\n }\r\n\r\n private updateGazeBreaks(delta: number): void {\r\n this.gazeBreakTimer += delta;\r\n\r\n if (this.gazeBreakTimer >= this.gazeBreakInterval && this.gazeBreakPhase === PHASE_OPEN) {\r\n this.gazeBreakPhase = PHASE_CLOSING; // Reusing: 1 = breaking away\r\n this.gazeBreakProgress = 0;\r\n this.gazeBreakTimer = 0;\r\n\r\n const params = this.getActiveGazeParams();\r\n const amp = randomRange(...params.amplitude);\r\n this.gazeBreakTargetX = (Math.random() - 0.5) * 2 * amp;\r\n this.gazeBreakTargetY = (Math.random() - 0.5) * amp * 0.4;\r\n this.gazeBreakInterval = randomRange(...params.interval);\r\n logger.trace('gaze break', {\r\n targetX: this.gazeBreakTargetX.toFixed(3),\r\n targetY: this.gazeBreakTargetY.toFixed(3),\r\n nextInterval: this.gazeBreakInterval.toFixed(2),\r\n state: this.currentState,\r\n });\r\n }\r\n\r\n if (this.gazeBreakPhase > PHASE_OPEN) {\r\n this.gazeBreakProgress += delta;\r\n\r\n if (this.gazeBreakPhase === 1) {\r\n // Breaking away\r\n const t = Math.min(1, this.gazeBreakProgress / GAZE_BREAK_DURATION);\r\n const eased = smoothStep(t);\r\n this.gazeBreakCurrentX = this.gazeBreakTargetX * eased;\r\n this.gazeBreakCurrentY = this.gazeBreakTargetY * eased;\r\n if (this.gazeBreakProgress >= GAZE_BREAK_DURATION) {\r\n this.gazeBreakPhase = 2;\r\n this.gazeBreakProgress = 0;\r\n }\r\n } else if (this.gazeBreakPhase === 2) {\r\n // Holding glance\r\n this.gazeBreakCurrentX = this.gazeBreakTargetX;\r\n this.gazeBreakCurrentY = this.gazeBreakTargetY;\r\n if (this.gazeBreakProgress >= GAZE_BREAK_HOLD_DURATION) {\r\n this.gazeBreakPhase = 3;\r\n this.gazeBreakProgress = 0;\r\n }\r\n } else if (this.gazeBreakPhase === 3) {\r\n // Returning to focus\r\n const t = Math.min(1, this.gazeBreakProgress / GAZE_BREAK_RETURN_DURATION);\r\n const eased = smoothStep(t);\r\n this.gazeBreakCurrentX = this.gazeBreakTargetX * (1 - eased);\r\n this.gazeBreakCurrentY = this.gazeBreakTargetY * (1 - eased);\r\n if (this.gazeBreakProgress >= GAZE_BREAK_RETURN_DURATION) {\r\n this.gazeBreakPhase = PHASE_OPEN;\r\n this.gazeBreakProgress = 0;\r\n this.gazeBreakCurrentX = 0;\r\n this.gazeBreakCurrentY = 0;\r\n }\r\n }\r\n } else {\r\n this.gazeBreakCurrentX = 0;\r\n this.gazeBreakCurrentY = 0;\r\n }\r\n }\r\n\r\n // =====================================================================\r\n // PRIVATE: Brow noise (simplex-driven organic drift)\r\n // =====================================================================\r\n\r\n private updateBrowNoise(\r\n delta: number,\r\n audioEnergy: number,\r\n isSpeaking: boolean,\r\n blendshapes: Record<string, number>,\r\n ): void {\r\n this.noiseTime += delta;\r\n\r\n // Emphasis detection: spike on energy transients\r\n const energyDelta = audioEnergy - this.previousEnergy;\r\n if (energyDelta > EMPHASIS_ENERGY_THRESHOLD) {\r\n this.emphasisLevel = 1.0;\r\n }\r\n this.emphasisLevel = Math.max(0, this.emphasisLevel - delta * EMPHASIS_DECAY_RATE);\r\n this.previousEnergy = audioEnergy;\r\n\r\n // Speech multiplier\r\n const speechMul = (isSpeaking && audioEnergy > 0) ? this.browNoiseSpeechMultiplier : 1.0;\r\n const amp = this.browNoiseAmplitude * speechMul;\r\n\r\n // browInnerUp: slow drift + emphasis spike\r\n const innerUpNoise = simplex2d(this.noiseTime * BROW_INNER_UP_FREQ, BROW_INNER_UP_PHASE);\r\n const innerUpBase = (innerUpNoise * 0.5 + 0.5) * amp * 0.83;\r\n const innerUpEmphasis = this.emphasisLevel * 0.25;\r\n blendshapes['browInnerUp'] = clamp(innerUpBase + innerUpEmphasis, 0, 1);\r\n\r\n // browOuterUpLeft\r\n const outerLeftNoise = simplex2d(this.noiseTime * BROW_OUTER_LEFT_FREQ, BROW_OUTER_LEFT_PHASE);\r\n blendshapes['browOuterUpLeft'] = clamp((outerLeftNoise * 0.5 + 0.5) * amp * 0.5, 0, 1);\r\n\r\n // browOuterUpRight\r\n const outerRightNoise = simplex2d(this.noiseTime * BROW_OUTER_RIGHT_FREQ, BROW_OUTER_RIGHT_PHASE);\r\n blendshapes['browOuterUpRight'] = clamp((outerRightNoise * 0.5 + 0.5) * amp * 0.5, 0, 1);\r\n\r\n // browDownLeft (subtle furrow)\r\n const downLeftNoise = simplex2d(this.noiseTime * BROW_DOWN_FREQ, BROW_DOWN_LEFT_PHASE);\r\n blendshapes['browDownLeft'] = clamp((downLeftNoise * 0.5 + 0.5) * amp * 0.33, 0, 1);\r\n\r\n // browDownRight (subtle furrow)\r\n const downRightNoise = simplex2d(this.noiseTime * BROW_DOWN_FREQ, BROW_DOWN_RIGHT_PHASE);\r\n blendshapes['browDownRight'] = clamp((downRightNoise * 0.5 + 0.5) * amp * 0.33, 0, 1);\r\n }\r\n}\r\n","/**\n * Body Animation — Renderer-agnostic interfaces and utilities.\n *\n * Defines the contract for body animation controllers that each renderer\n * adapter (@omote/three, @omote/babylon, @omote/r3f) implements natively.\n *\n * Also provides the shared bone filtering logic used during animation\n * retargeting — stripping head/neck/eye tracks so body animations don't\n * conflict with the face pipeline (FaceCompositor, gaze, ProceduralLifeLayer).\n *\n * @module animation\n */\n\n// ---------------------------------------------------------------------------\n// AnimationController — renderer-agnostic interface\n// ---------------------------------------------------------------------------\n\n/**\n * Renderer-agnostic animation controller interface.\n *\n * Each renderer adapter implements this against its native animation system:\n * - @omote/three → THREE.AnimationMixer + AnimationAction\n * - @omote/babylon → Babylon.js AnimationGroup\n * - @omote/r3f → React hook wrapping the Three.js implementation\n *\n * Python/Node ports implement this against their own runtimes.\n */\nexport interface AnimationController {\n /** Play an animation by id. */\n play(id: string, options?: { fadeInDuration?: number }): void;\n /** Stop all playing animations. */\n stop(fadeOutDuration?: number): void;\n /** Crossfade from current animation to target. */\n crossfadeTo(id: string, duration?: number): void;\n /** Check if a specific animation is currently playing. */\n isPlaying(id: string): boolean;\n /** Check if an animation with this id is loaded. */\n hasAnimation(id: string): boolean;\n /** List of loaded animation ids. */\n readonly availableAnimations: string[];\n}\n\n// ---------------------------------------------------------------------------\n// AnimationSource — describes an animation to load\n// ---------------------------------------------------------------------------\n\n/**\n * Describes an external animation asset to load and configure.\n * Renderer-agnostic — loaders are adapter-specific.\n */\nexport interface AnimationSource {\n /** Unique identifier for this animation. */\n id: string;\n /** URL to the animation file (FBX, GLB, etc.). */\n url: string;\n /** Clip name within the file (if it contains multiple clips). */\n clipName?: string;\n /** Playback options. */\n options?: AnimationSourceOptions;\n}\n\nexport interface AnimationSourceOptions {\n loop?: boolean;\n timeScale?: number;\n fadeInDuration?: number;\n fadeOutDuration?: number;\n clampWhenFinished?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// BoneFilterConfig — defines which bones the face pipeline owns\n// ---------------------------------------------------------------------------\n\n/**\n * Configuration for filtering bone tracks from body animations.\n *\n * The face pipeline (FaceCompositor, gaze tracking, ProceduralLifeLayer) owns\n * certain bones (head, neck, eyes). Body animations must strip these tracks\n * to prevent conflicts.\n */\nexport interface BoneFilterConfig {\n /** Bone names owned by the face pipeline (e.g., ['Head', 'Neck', 'LeftEye', 'RightEye']). */\n proceduralBones: string[];\n /** Whether to strip .position tracks (keep only quaternion/rotation). */\n filterPositionTracks: boolean;\n /** Whether to strip morphTargetInfluences tracks. */\n filterMorphTargets: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Mixamo bone name prefix (stripped during retargeting). */\nexport const MIXAMO_PREFIX = 'mixamorig';\n\n/**\n * Bones that need position tracks preserved during retargeting.\n * Stripping finger/hand position tracks causes fingers to splay to bind pose.\n */\nexport const PRESERVE_POSITION_BONES = new Set([\n 'LeftHand', 'RightHand',\n 'LeftHandThumb1', 'LeftHandThumb2', 'LeftHandThumb3',\n 'LeftHandIndex1', 'LeftHandIndex2', 'LeftHandIndex3',\n 'LeftHandMiddle1', 'LeftHandMiddle2', 'LeftHandMiddle3',\n 'LeftHandRing1', 'LeftHandRing2', 'LeftHandRing3',\n 'LeftHandPinky1', 'LeftHandPinky2', 'LeftHandPinky3',\n 'RightHandThumb1', 'RightHandThumb2', 'RightHandThumb3',\n 'RightHandIndex1', 'RightHandIndex2', 'RightHandIndex3',\n 'RightHandMiddle1', 'RightHandMiddle2', 'RightHandMiddle3',\n 'RightHandRing1', 'RightHandRing2', 'RightHandRing3',\n 'RightHandPinky1', 'RightHandPinky2', 'RightHandPinky3',\n]);\n\n/** Default bone filter for RPM/Mixamo avatars. */\nexport const DEFAULT_BONE_FILTER: BoneFilterConfig = {\n proceduralBones: ['Head', 'Neck', 'LeftEye', 'RightEye'],\n filterPositionTracks: true,\n filterMorphTargets: true,\n};\n\n// ---------------------------------------------------------------------------\n// filterAnimationTracks — renderer-agnostic track filtering\n// ---------------------------------------------------------------------------\n\n/**\n * A generic animation track descriptor. Renderers map their native track\n * objects to this shape for filtering, then map back.\n */\nexport interface TrackDescriptor {\n /** Full track name, e.g. \"mixamorigHips.quaternion\" or \"Head.position\". */\n name: string;\n}\n\n/**\n * Filter animation tracks according to a BoneFilterConfig.\n *\n * This is the renderer-agnostic core of `retargetClip`. Renderer adapters\n * call this with their native track names and use the result to decide\n * which tracks to keep.\n *\n * @returns true if the track should be KEPT (not filtered out).\n */\nexport function shouldKeepTrack(\n trackName: string,\n config: BoneFilterConfig,\n): boolean {\n // Strip Mixamo prefix for bone name extraction\n const boneName = trackName.split('.')[0].split(MIXAMO_PREFIX).join('');\n const property = trackName.split('.').pop() ?? '';\n\n // Filter morph target tracks\n if (config.filterMorphTargets && trackName.includes('morphTargetInfluences')) {\n return false;\n }\n\n // Filter procedural bone tracks (face pipeline owns these)\n if (config.proceduralBones.includes(boneName)) {\n return false;\n }\n\n // Filter position tracks (except preserved bones like hands/fingers)\n if (config.filterPositionTracks && property === 'position') {\n return PRESERVE_POSITION_BONES.has(boneName);\n }\n\n return true;\n}\n\n/**\n * Strip Mixamo prefix from a track name.\n * \"mixamorigHips.quaternion\" → \"Hips.quaternion\"\n */\nexport function stripMixamoPrefix(trackName: string): string {\n return trackName.split(MIXAMO_PREFIX).join('');\n}\n","/**\n * FACS (Facial Action Coding System) to ARKit Blendshape Mapping\n *\n * Two static lookup tables that decompose emotions into FACS Action Units,\n * then map AUs to ARKit blendshapes. Based on Ekman's FACS research.\n *\n * @category Face\n */\n\nimport type { EmotionName } from '../emotion';\n\n/**\n * A single FACS Action Unit activation within an emotion\n */\nexport interface AUActivation {\n /** FACS Action Unit identifier (e.g. 'AU6', 'AU12') */\n au: string;\n /** Activation intensity 0-1 */\n intensity: number;\n /** Facial region: upper (brows/eyes/cheeks) or lower (mouth/jaw) */\n region: 'upper' | 'lower';\n}\n\n/**\n * Table 1: Emotion → FACS Action Units\n *\n * Maps each of the 10 SDK emotion channels to their FACS AU combinations\n * with intensity and upper/lower face region tags.\n *\n * Sources:\n * - Ekman & Friesen (1978) FACS Manual\n * - Ekman (2003) Emotions Revealed\n * - Lucey et al. (2010) Extended Cohn-Kanade dataset\n */\nexport const EMOTION_TO_AU: Record<EmotionName, AUActivation[]> = {\n joy: [\n { au: 'AU6', intensity: 0.7, region: 'upper' }, // cheek raise (Duchenne)\n { au: 'AU12', intensity: 0.8, region: 'lower' }, // lip corner pull (smile)\n ],\n anger: [\n { au: 'AU4', intensity: 0.8, region: 'upper' }, // brow lower\n { au: 'AU5', intensity: 0.4, region: 'upper' }, // upper lid raise\n { au: 'AU7', intensity: 0.3, region: 'upper' }, // lid tighten\n { au: 'AU23', intensity: 0.6, region: 'lower' }, // lip tighten\n ],\n sadness: [\n { au: 'AU1', intensity: 0.7, region: 'upper' }, // inner brow raise\n { au: 'AU4', intensity: 0.3, region: 'upper' }, // brow lower (furrow)\n { au: 'AU15', intensity: 0.5, region: 'lower' }, // lip corner depress\n ],\n fear: [\n { au: 'AU1', intensity: 0.6, region: 'upper' }, // inner brow raise\n { au: 'AU2', intensity: 0.5, region: 'upper' }, // outer brow raise\n { au: 'AU4', intensity: 0.3, region: 'upper' }, // brow lower\n { au: 'AU5', intensity: 0.5, region: 'upper' }, // upper lid raise\n { au: 'AU20', intensity: 0.4, region: 'lower' }, // lip stretch\n ],\n disgust: [\n { au: 'AU9', intensity: 0.7, region: 'upper' }, // nose wrinkle\n { au: 'AU10', intensity: 0.5, region: 'lower' }, // upper lip raise\n { au: 'AU15', intensity: 0.4, region: 'lower' }, // lip corner depress\n ],\n amazement: [\n { au: 'AU1', intensity: 0.6, region: 'upper' }, // inner brow raise\n { au: 'AU2', intensity: 0.7, region: 'upper' }, // outer brow raise\n { au: 'AU5', intensity: 0.6, region: 'upper' }, // upper lid raise\n { au: 'AU26', intensity: 0.4, region: 'lower' }, // jaw drop\n ],\n grief: [\n { au: 'AU1', intensity: 0.8, region: 'upper' }, // inner brow raise\n { au: 'AU4', intensity: 0.5, region: 'upper' }, // brow lower\n { au: 'AU6', intensity: 0.3, region: 'upper' }, // cheek raise (grief cry)\n { au: 'AU15', intensity: 0.6, region: 'lower' }, // lip corner depress\n ],\n cheekiness: [\n { au: 'AU2', intensity: 0.4, region: 'upper' }, // outer brow raise\n { au: 'AU6', intensity: 0.4, region: 'upper' }, // cheek raise\n { au: 'AU12', intensity: 0.6, region: 'lower' }, // lip corner pull (smirk)\n ],\n pain: [\n { au: 'AU4', intensity: 0.7, region: 'upper' }, // brow lower\n { au: 'AU6', intensity: 0.4, region: 'upper' }, // cheek raise (orbicularis)\n { au: 'AU7', intensity: 0.7, region: 'upper' }, // lid tighten (squint)\n { au: 'AU9', intensity: 0.5, region: 'upper' }, // nose wrinkle\n ],\n outofbreath: [\n { au: 'AU1', intensity: 0.3, region: 'upper' }, // inner brow raise\n { au: 'AU25', intensity: 0.3, region: 'lower' }, // lips part\n { au: 'AU26', intensity: 0.5, region: 'lower' }, // jaw drop\n ],\n};\n\n/**\n * Table 2: FACS Action Unit → ARKit Blendshapes\n *\n * Maps each AU to one or more ARKit blendshape channels with weight.\n *\n * Sources:\n * - Apple ARKit face tracking documentation\n * - Melinda Ozel's ARKit-to-FACS cheat sheet\n */\nexport const AU_TO_ARKIT: Record<string, { blendshape: string; weight: number }[]> = {\n 'AU1': [{ blendshape: 'browInnerUp', weight: 1.0 }],\n 'AU2': [{ blendshape: 'browOuterUpLeft', weight: 1.0 }, { blendshape: 'browOuterUpRight', weight: 1.0 }],\n 'AU4': [{ blendshape: 'browDownLeft', weight: 1.0 }, { blendshape: 'browDownRight', weight: 1.0 }],\n 'AU5': [{ blendshape: 'eyeWideLeft', weight: 1.0 }, { blendshape: 'eyeWideRight', weight: 1.0 }],\n 'AU6': [{ blendshape: 'cheekSquintLeft', weight: 1.0 }, { blendshape: 'cheekSquintRight', weight: 1.0 }],\n 'AU7': [{ blendshape: 'eyeSquintLeft', weight: 1.0 }, { blendshape: 'eyeSquintRight', weight: 1.0 }],\n 'AU9': [{ blendshape: 'noseSneerLeft', weight: 1.0 }, { blendshape: 'noseSneerRight', weight: 1.0 }],\n 'AU10': [{ blendshape: 'mouthUpperUpLeft', weight: 1.0 }, { blendshape: 'mouthUpperUpRight', weight: 1.0 }],\n 'AU12': [{ blendshape: 'mouthSmileLeft', weight: 1.0 }, { blendshape: 'mouthSmileRight', weight: 1.0 }],\n 'AU15': [{ blendshape: 'mouthFrownLeft', weight: 1.0 }, { blendshape: 'mouthFrownRight', weight: 1.0 }],\n 'AU20': [{ blendshape: 'mouthStretchLeft', weight: 1.0 }, { blendshape: 'mouthStretchRight', weight: 1.0 }],\n 'AU23': [{ blendshape: 'mouthPressLeft', weight: 1.0 }, { blendshape: 'mouthPressRight', weight: 1.0 }],\n 'AU25': [{ blendshape: 'jawOpen', weight: 0.3 }],\n 'AU26': [{ blendshape: 'jawOpen', weight: 1.0 }],\n};\n\n/**\n * All AU identifiers referenced by EMOTION_TO_AU (for validation)\n */\nexport const ALL_AUS: string[] = [...new Set(\n Object.values(EMOTION_TO_AU).flatMap(activations => activations.map(a => a.au))\n)];\n","/**\n * EmotionResolver — Resolves EmotionWeights → split upper/lower face Float32Array[52]\n *\n * Uses FACS decomposition (EMOTION_TO_AU → AU_TO_ARKIT) to produce\n * anatomically correct blendshape contributions, split by facial region\n * for the FaceCompositor's modulation strategy:\n * - Upper face: additive overlay (independent of speech)\n * - Lower face: modulates speech output\n *\n * @category Face\n */\n\nimport { EMOTION_NAMES, type EmotionName, type EmotionWeights } from '../emotion';\nimport { ARKIT_BLENDSHAPES } from '../inference/blendshapeUtils';\nimport { EMOTION_TO_AU, AU_TO_ARKIT } from './FACSMapping';\nimport { createLogger } from '../logging';\n\nconst logger = createLogger('EmotionResolver');\n\n/** Pre-computed blendshape name → ARKit index map */\nconst BS_INDEX = new Map<string, number>();\nfor (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {\n BS_INDEX.set(ARKIT_BLENDSHAPES[i], i);\n}\n\n/**\n * Resolved emotion split into upper and lower face contributions.\n *\n * WARNING: Buffers are owned by EmotionResolver and are overwritten\n * on the next resolve() call. Copy if you need to retain values.\n */\nexport interface ResolvedEmotion {\n /** 52 channels — only upper face non-zero. Valid until next resolve() call. */\n upper: Float32Array;\n /** 52 channels — only lower face non-zero. Valid until next resolve() call. */\n lower: Float32Array;\n}\n\n/**\n * Resolves EmotionWeights into upper/lower face blendshape arrays\n * using FACS Action Unit decomposition.\n */\nexport class EmotionResolver {\n private readonly upperBuffer = new Float32Array(52);\n private readonly lowerBuffer = new Float32Array(52);\n\n /**\n * Resolve emotion weights to upper/lower face blendshape contributions.\n *\n * @param weights - Emotion channel weights from EmotionController\n * @param intensity - Global intensity multiplier (0-2). Default: 1.0\n * @returns Upper and lower face blendshape arrays (52 channels each)\n */\n resolve(weights: EmotionWeights, intensity: number = 1.0): ResolvedEmotion {\n const upper = this.upperBuffer;\n const lower = this.lowerBuffer;\n upper.fill(0);\n lower.fill(0);\n\n for (const emotionName of EMOTION_NAMES) {\n const emotionWeight = weights[emotionName];\n if (!emotionWeight || emotionWeight < 0.01) continue;\n\n const auActivations = EMOTION_TO_AU[emotionName as EmotionName];\n if (!auActivations) {\n logger.warn(`Unknown emotion name with no AU mapping: \"${emotionName}\"`);\n continue;\n }\n\n for (const activation of auActivations) {\n const arkitMappings = AU_TO_ARKIT[activation.au];\n if (!arkitMappings) continue;\n\n const target = activation.region === 'upper' ? upper : lower;\n const scale = emotionWeight * activation.intensity * intensity;\n\n for (const mapping of arkitMappings) {\n const idx = BS_INDEX.get(mapping.blendshape);\n if (idx !== undefined) {\n target[idx] += mapping.weight * scale;\n }\n }\n }\n }\n\n // Clamp all values to [0, 1]\n for (let i = 0; i < 52; i++) {\n if (upper[i] > 1) upper[i] = 1;\n if (lower[i] > 1) lower[i] = 1;\n }\n\n // Return direct references — valid until next resolve() call.\n // FaceCompositor (the only caller) reads values immediately in the\n // same synchronous compose() call, so no copy is needed.\n return { upper, lower };\n }\n}\n","/**\r\n * FaceCompositor — 5-stage signal processing chain for facial animation\r\n *\r\n * Composes A2E lip sync, emotion modulation, procedural life, and character\r\n * profile into a single Float32Array[52] per frame.\r\n *\r\n * ```\r\n * BASE (A2E) → EMOTION MODULATION → PROCEDURAL LIFE → CHARACTER PROFILE → OUTPUT [0,1]\r\n * ```\r\n *\r\n * Replaces manual blendshape merging in consumer code with a single `compose()` call.\r\n *\r\n * @category Face\r\n */\r\n\r\nimport { getClock } from '../logging/Clock';\r\nimport { ARKIT_BLENDSHAPES } from '../inference/blendshapeUtils';\r\nimport type { EmotionWeights } from '../emotion';\r\nimport { ProceduralLifeLayer } from '../animation/ProceduralLifeLayer';\r\nimport type { LifeLayerInput } from '../animation/ProceduralLifeLayer';\r\nimport { EmotionResolver } from './EmotionResolver';\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\n\r\nconst logger = createLogger('FaceCompositor');\r\n\r\n/**\r\n * Output of FaceCompositor.compose()\r\n *\r\n * WARNING: When using the internal output buffer (no `target` param),\r\n * `blendshapes` is a shared reference that is overwritten on the next\r\n * compose() call. Copy with `new Float32Array(output.blendshapes)` if\r\n * you need to retain values across frames.\r\n */\r\nexport interface FaceCompositorOutput {\r\n /**\r\n * 52 ARKit blendshape values, clamped [0,1].\r\n *\r\n * This buffer is reused across calls when no `target` parameter is\r\n * provided to compose(). Valid until the next compose() call.\r\n */\r\n blendshapes: Float32Array;\r\n /** Head rotation deltas in radians (from ProceduralLifeLayer) */\r\n headDelta: { yaw: number; pitch: number };\r\n}\r\n\r\nfunction smoothstep(t: number): number {\r\n return t * t * (3 - 2 * t);\r\n}\r\n\r\n/** Pre-computed blendshape name → LAM index map */\r\nconst BS_INDEX = new Map<string, number>();\r\nfor (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {\r\n BS_INDEX.set(ARKIT_BLENDSHAPES[i], i);\r\n}\r\n\r\n/** Index of mouthClose in ARKIT_BLENDSHAPES (for bilabial suppression) */\r\nconst IDX_MOUTH_CLOSE = BS_INDEX.get('mouthClose')!;\r\n\r\n/**\r\n * Pre-computed eye channel flags.\r\n * Eye blink + look channels are SET (overridden) by ProceduralLifeLayer.\r\n * eyeSquint and eyeWide are NOT included — they're driven by emotion.\r\n */\r\nconst IS_EYE_CHANNEL: boolean[] = new Array(52).fill(false);\r\nfor (const name of ARKIT_BLENDSHAPES) {\r\n if (name.startsWith('eyeBlink') || name.startsWith('eyeLook')) {\r\n IS_EYE_CHANNEL[BS_INDEX.get(name)!] = true;\r\n }\r\n}\r\n\r\n/**\r\n * Per-blendshape character profile (multiplier + offset)\r\n *\r\n * Superset of ExpressionProfile — gives per-channel control instead of per-group.\r\n */\r\nexport interface CharacterProfile {\r\n /** Per-blendshape multiplier (default: all 1.0) */\r\n multiplier?: Partial<Record<string, number>>;\r\n /** Per-blendshape offset (default: all 0.0) */\r\n offset?: Partial<Record<string, number>>;\r\n}\r\n\r\n/**\r\n * Configuration for FaceCompositor\r\n */\r\nexport interface FaceCompositorConfig {\r\n /** ProceduralLifeLayer instance (compositor creates default if omitted) */\r\n lifeLayer?: ProceduralLifeLayer;\r\n /** Character profile: per-BS multiplier + offset */\r\n profile?: CharacterProfile;\r\n /** Emotion smoothing factor per frame (0-1). Default: 0.12 */\r\n emotionSmoothing?: number;\r\n}\r\n\r\n/**\r\n * Per-frame input to the compositor\r\n */\r\nexport interface FaceCompositorInput extends LifeLayerInput {\r\n /** Delta time in seconds */\r\n deltaTime: number;\r\n /** Current emotion weights (from EmotionController.emotion or manual) */\r\n emotion?: EmotionWeights;\r\n /** Emotion intensity multiplier (0-2). Default: 1.0 */\r\n emotionIntensity?: number;\r\n}\r\n\r\n/**\r\n * FaceCompositor — 5-stage facial animation signal chain.\r\n *\r\n * @example\r\n * ```typescript\r\n * import { FaceCompositor, createA2E } from '@omote/core';\r\n *\r\n * const compositor = new FaceCompositor();\r\n *\r\n * // In animation loop:\r\n * const output = compositor.compose(a2eFrame, {\r\n * deltaTime: 0.016,\r\n * emotion: { joy: 0.8 },\r\n * isSpeaking: true,\r\n * audioEnergy: 0.5,\r\n * });\r\n *\r\n * // Apply output.blendshapes[0..51] to avatar morphTargetInfluences\r\n * ```\r\n */\r\nexport class FaceCompositor {\r\n private readonly emotionResolver = new EmotionResolver();\r\n private readonly lifeLayer: ProceduralLifeLayer;\r\n private readonly emotionSmoothing: number;\r\n\r\n // Pre-allocated buffers\r\n private readonly outputBuffer = new Float32Array(52);\r\n private readonly smoothedUpper = new Float32Array(52);\r\n private readonly smoothedLower = new Float32Array(52);\r\n private readonly lifeBuffer = new Float32Array(52);\r\n\r\n // Profile arrays (pre-expanded to 52 channels)\r\n private readonly multiplier = new Float32Array(52).fill(1);\r\n private readonly offset = new Float32Array(52);\r\n\r\n // Persistent emotion for convenience API\r\n private stickyEmotion: EmotionWeights | undefined;\r\n\r\n constructor(config?: FaceCompositorConfig) {\r\n this.lifeLayer = config?.lifeLayer ?? new ProceduralLifeLayer();\r\n this.emotionSmoothing = config?.emotionSmoothing ?? 0.12;\r\n\r\n if (config?.profile) {\r\n this.applyProfileArrays(config.profile);\r\n }\r\n\r\n logger.debug('constructor', {\r\n emotionSmoothing: this.emotionSmoothing,\r\n hasProfile: !!config?.profile,\r\n hasLifeLayer: !!config?.lifeLayer,\r\n });\r\n }\r\n\r\n /**\r\n * Compose a single output frame from the 5-stage signal chain.\r\n *\r\n * @param base - A2E raw output (Float32Array[52], ARKIT_BLENDSHAPES order)\r\n * @param input - Per-frame input (deltaTime, emotion, life layer params)\r\n * @param target - Optional pre-allocated output buffer (avoids per-frame allocation).\r\n * When omitted, an internal buffer is used (valid until next compose() call).\r\n * @returns Blendshapes (Float32Array[52] clamped [0,1]) and head rotation deltas\r\n */\r\n compose(base: Float32Array, input: FaceCompositorInput, target?: Float32Array): FaceCompositorOutput {\r\n const composeStart = getClock().now();\r\n const out = target ?? this.outputBuffer;\r\n\r\n // ── Stage 1: BASE ─────────────────────────────────────────────────\r\n out.set(base);\r\n\r\n // ── Stage 2: EMOTION MODULATION ───────────────────────────────────\r\n const emotion = input.emotion ?? this.stickyEmotion;\r\n if (emotion) {\r\n const resolved = this.emotionResolver.resolve(\r\n emotion,\r\n input.emotionIntensity ?? 1.0,\r\n );\r\n\r\n // Smooth emotion transitions (EMA)\r\n const k = this.emotionSmoothing;\r\n for (let i = 0; i < 52; i++) {\r\n this.smoothedUpper[i] += (resolved.upper[i] - this.smoothedUpper[i]) * k;\r\n this.smoothedLower[i] += (resolved.lower[i] - this.smoothedLower[i]) * k;\r\n }\r\n\r\n // Bilabial suppression: during P/B/M, smoothly suppress lower-face emotion\r\n // Smooth ramp from 1.0 (no suppression) at mouthClose=0.3 to 0.1 (90% suppressed) at 0.7\r\n const mc = base[IDX_MOUTH_CLOSE];\r\n const bilabialSuppress = mc <= 0.3 ? 1.0\r\n : mc >= 0.7 ? 0.1\r\n : 1.0 - 0.9 * smoothstep((mc - 0.3) * 2.5); // 1/(0.7-0.3) = 2.5\r\n\r\n // Upper face: additive overlay\r\n for (let i = 0; i < 52; i++) {\r\n out[i] += this.smoothedUpper[i];\r\n }\r\n\r\n // Lower face: modulate speech — base * (1 + emotionBias)\r\n for (let i = 0; i < 52; i++) {\r\n out[i] *= (1 + this.smoothedLower[i] * bilabialSuppress);\r\n }\r\n }\r\n\r\n // ── Stage 3: PROCEDURAL LIFE ──────────────────────────────────────\r\n // Use update() to get both blendshapes and headDelta\r\n const lifeResult = this.lifeLayer.update(input.deltaTime, input);\r\n\r\n // Map named blendshapes → LAM indices\r\n this.lifeBuffer.fill(0);\r\n for (const [name, value] of Object.entries(lifeResult.blendshapes)) {\r\n const idx = BS_INDEX.get(name);\r\n if (idx !== undefined) {\r\n this.lifeBuffer[idx] = value;\r\n }\r\n }\r\n\r\n for (let i = 0; i < 52; i++) {\r\n if (IS_EYE_CHANNEL[i]) {\r\n out[i] = this.lifeBuffer[i]; // SET (override blinks/gaze)\r\n } else {\r\n out[i] += this.lifeBuffer[i]; // ADD (brows, breathing)\r\n }\r\n }\r\n\r\n // ── Stage 4: CHARACTER PROFILE ────────────────────────────────────\r\n for (let i = 0; i < 52; i++) {\r\n out[i] = out[i] * this.multiplier[i] + this.offset[i];\r\n }\r\n\r\n // ── Stage 5: OUTPUT — clamp [0, 1] ────────────────────────────────\r\n for (let i = 0; i < 52; i++) {\r\n if (out[i] < 0) out[i] = 0;\r\n else if (out[i] > 1) out[i] = 1;\r\n }\r\n\r\n getTelemetry()?.recordHistogram(\r\n MetricNames.COMPOSITOR_COMPOSE_LATENCY,\r\n (getClock().now() - composeStart) * 1000, // µs\r\n );\r\n\r\n return { blendshapes: out, headDelta: lifeResult.headDelta };\r\n }\r\n\r\n /**\r\n * Set sticky emotion (used when input.emotion is not provided).\r\n */\r\n setEmotion(weights: EmotionWeights): void {\r\n this.stickyEmotion = weights;\r\n logger.debug('setEmotion', { weights });\r\n }\r\n\r\n /**\r\n * Update character profile at runtime.\r\n */\r\n setProfile(profile: CharacterProfile): void {\r\n this.multiplier.fill(1);\r\n this.offset.fill(0);\r\n this.applyProfileArrays(profile);\r\n logger.debug('setProfile', {\r\n multiplierKeys: profile.multiplier ? Object.keys(profile.multiplier).length : 0,\r\n offsetKeys: profile.offset ? Object.keys(profile.offset).length : 0,\r\n });\r\n }\r\n\r\n /**\r\n * Reset all smoothing state and life layer.\r\n */\r\n reset(): void {\r\n this.smoothedUpper.fill(0);\r\n this.smoothedLower.fill(0);\r\n this.lifeBuffer.fill(0);\r\n this.stickyEmotion = undefined;\r\n this.lifeLayer.reset();\r\n logger.debug('reset');\r\n }\r\n\r\n /** Expand partial profile maps into dense Float32Arrays */\r\n private applyProfileArrays(profile: CharacterProfile): void {\r\n if (profile.multiplier) {\r\n for (const [name, value] of Object.entries(profile.multiplier)) {\r\n const idx = BS_INDEX.get(name);\r\n if (idx !== undefined && value !== undefined) {\r\n this.multiplier[idx] = value;\r\n }\r\n }\r\n }\r\n if (profile.offset) {\r\n for (const [name, value] of Object.entries(profile.offset)) {\r\n const idx = BS_INDEX.get(name);\r\n if (idx !== undefined && value !== undefined) {\r\n this.offset[idx] = value;\r\n }\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * TextEmotionAnalyzer — Lightweight keyword heuristic for mapping AI response\r\n * text to an emotion label.\r\n *\r\n * Returns null if no strong signal is detected (keeps current emotion).\r\n *\r\n * @category Face\r\n */\r\n\r\nconst PATTERNS: Array<{ emotion: string; keywords: RegExp }> = [\r\n { emotion: 'empathetic', keywords: /\\b(sorry|unfortunately|condolences|sympathize|sympathise|my heart goes out)\\b/i },\r\n { emotion: 'excited', keywords: /\\b(amazing|incredible|awesome|fantastic|wonderful|brilliant|thrilling)\\b/i },\r\n { emotion: 'happy', keywords: /\\b(glad|great news|happy to|pleased to|delighted|congrats|congratulations)\\b/i },\r\n { emotion: 'sad', keywords: /\\b(sadly|tragic|heartbreaking|devastating|loss|mourn)\\b/i },\r\n { emotion: 'amused', keywords: /\\b(haha|heh|lol|funny|hilarious|laughing|joke)\\b/i },\r\n { emotion: 'concerned', keywords: /\\b(worry|worried|concerning|alarming|careful|caution|danger)\\b/i },\r\n { emotion: 'curious', keywords: /\\b(interesting|fascinating|intriguing|wonder|curious)\\b/i },\r\n { emotion: 'grateful', keywords: /\\b(thank you|thanks|grateful|appreciate|thankful)\\b/i },\r\n { emotion: 'angry', keywords: /\\b(outrageous|unacceptable|furious|infuriating|disgraceful)\\b/i },\r\n { emotion: 'scared', keywords: /\\b(terrifying|frightening|scary|horrifying|dreadful)\\b/i },\r\n { emotion: 'thoughtful', keywords: /\\b(consider|perhaps|on the other hand|reflect|ponder)\\b/i },\r\n { emotion: 'surprised', keywords: /\\b(wow|whoa|oh my|unbelievable|shocked|astonishing)\\b/i },\r\n];\r\n\r\n/**\r\n * Analyze AI response text for emotional content.\r\n *\r\n * @param text - The AI response text to analyze\r\n * @returns An emotion label string, or null if no strong signal detected\r\n */\r\nexport function analyzeTextEmotion(text: string): string | null {\r\n if (!text || text.length < 3) return null;\r\n\r\n for (const { emotion, keywords } of PATTERNS) {\r\n if (keywords.test(text)) {\r\n return emotion;\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n","/**\r\n * EmotionTagParser — Strips `[tag]` emotion annotations from LLM response text.\r\n *\r\n * LLMs can self-annotate responses with emotion tags like `[excited]` or `[sad]`.\r\n * This parser extracts the first valid tag and returns clean display text.\r\n *\r\n * @category Face\r\n */\r\n\r\nconst TAG_RE = /\\[([a-z_]+)\\]/gi;\r\n\r\nconst VALID_TAGS = new Set([\r\n 'happy', 'sad', 'angry', 'surprised', 'fearful', 'disgusted', 'neutral',\r\n 'scared', 'excited', 'tired', 'playful', 'pained', 'contemplative',\r\n 'thoughtful', 'concerned', 'curious', 'grateful', 'empathetic', 'amused',\r\n]);\r\n\r\n/**\r\n * Parse emotion tags from LLM response text.\r\n *\r\n * @param text - Raw LLM response text, possibly containing `[emotion]` tags\r\n * @returns Object with clean display text and extracted emotion label (or null)\r\n */\r\nexport function parseEmotionTags(text: string): { cleanText: string; emotion: string | null } {\r\n let emotion: string | null = null;\r\n\r\n const cleanText = text.replace(TAG_RE, (match, tag: string) => {\r\n const lower = tag.toLowerCase();\r\n if (!emotion && VALID_TAGS.has(lower)) {\r\n emotion = lower;\r\n return '';\r\n }\r\n return match;\r\n }).trim();\r\n\r\n return { cleanText, emotion };\r\n}\r\n","/**\r\n * CharacterController — Renderer-agnostic avatar composition loop\r\n *\r\n * Extracted from r3f's useOmoteAvatar + useGazeTracking.\r\n * Owns FaceCompositor, emotion resolution, eye angle math, head smoothing.\r\n * Pure function: input → output. No renderer side effects.\r\n *\r\n * @category Character\r\n */\r\n\r\nimport { FaceCompositor } from '../face/FaceCompositor';\r\nimport type { FaceCompositorConfig, FaceCompositorInput, CharacterProfile } from '../face/FaceCompositor';\r\nimport type { EmotionWeights } from '../emotion';\r\nimport type { ConversationalState } from '../animation';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\n\r\nconst logger = createLogger('CharacterController');\r\n\r\n/** Frame budget threshold in microseconds (33ms = 30fps budget) */\r\nconst FRAME_BUDGET_US = 33_000;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Emotion resolution (extracted from r3f emotionMapping.ts)\r\n// ---------------------------------------------------------------------------\r\n\r\nconst EMOTION_MAP: Record<string, EmotionWeights> = {\r\n // Synced with EmotionPresets (packages/core/src/emotion/Emotion.ts)\r\n happy: { joy: 0.7, amazement: 0.2 },\r\n sad: { sadness: 0.7, grief: 0.4 },\r\n angry: { anger: 0.8, disgust: 0.3 },\r\n surprised: { amazement: 0.9, fear: 0.2 },\r\n fearful: { fear: 0.8 },\r\n disgusted: { disgust: 0.8, anger: 0.2 },\r\n neutral: {},\r\n // From EmotionPresets\r\n scared: { fear: 0.8, pain: 0.3 },\r\n excited: { joy: 0.6, amazement: 0.5, cheekiness: 0.4 },\r\n tired: { outofbreath: 0.6, sadness: 0.3 },\r\n playful: { cheekiness: 0.7, joy: 0.5 },\r\n pained: { pain: 0.8, grief: 0.4 },\r\n contemplative: { sadness: 0.2, grief: 0.1 },\r\n // Text heuristic / LLM tag labels\r\n thoughtful: { sadness: 0.15 },\r\n concerned: { fear: 0.3, sadness: 0.2 },\r\n curious: { amazement: 0.4 },\r\n grateful: { joy: 0.6 },\r\n empathetic: { sadness: 0.4, grief: 0.2 },\r\n amused: { joy: 0.4, cheekiness: 0.4 },\r\n // SenseVoice fallback\r\n emo_unknown: {},\r\n};\r\n\r\nconst _resolveCache = new Map<string, EmotionWeights | undefined>();\r\nfor (const key of Object.keys(EMOTION_MAP)) {\r\n _resolveCache.set(key, EMOTION_MAP[key]);\r\n}\r\n\r\n/**\r\n * Convert an emotion label string or EmotionWeights object to EmotionWeights.\r\n * Cached to avoid per-frame string allocation.\r\n */\r\nexport function resolveEmotion(\r\n emotion: string | EmotionWeights | null | undefined,\r\n): EmotionWeights | undefined {\r\n if (!emotion) return undefined;\r\n if (typeof emotion === 'string') {\r\n let result = _resolveCache.get(emotion);\r\n if (result === undefined && !_resolveCache.has(emotion)) {\r\n result = EMOTION_MAP[emotion.toLowerCase()];\r\n _resolveCache.set(emotion, result);\r\n }\r\n return result;\r\n }\r\n return emotion;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Vec3 type (renderer-agnostic)\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Simple 3D vector (renderer-agnostic) */\r\nexport interface Vec3 {\r\n x: number;\r\n y: number;\r\n z: number;\r\n}\r\n\r\n/** Quaternion (renderer-agnostic, for head rotation) */\r\nexport interface Quat {\r\n x: number;\r\n y: number;\r\n z: number;\r\n w: number;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Config & I/O types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface CharacterControllerConfig {\r\n /** FaceCompositor configuration */\r\n compositor?: FaceCompositorConfig;\r\n /** Gaze tracking config */\r\n gaze?: {\r\n enabled?: boolean;\r\n yawInfluence?: number;\r\n pitchInfluence?: number;\r\n smoothing?: number;\r\n };\r\n}\r\n\r\nexport interface CharacterUpdateInput {\r\n /** Time since last frame in seconds */\r\n deltaTime: number;\r\n /** Scaled blendshapes from pipeline frame (or null when no frame) */\r\n baseBlendshapes: Float32Array | null;\r\n /** Raw blendshapes before profile scaling (optional) */\r\n rawBlendshapes?: Float32Array | null;\r\n /** Current emotion (string preset or weights object) */\r\n emotion?: string | EmotionWeights | null;\r\n /** Whether the avatar is currently speaking */\r\n isSpeaking: boolean;\r\n /** Current conversational state */\r\n state: ConversationalState;\r\n /** Audio energy level (0-1, drives emphasis/gesture intensity) */\r\n audioEnergy?: number;\r\n /** Camera world position (renderer provides in its own coords) */\r\n cameraWorldPos?: Vec3;\r\n /** Head bone world position (renderer provides in its own coords) */\r\n headWorldPos?: Vec3;\r\n /** Head bone world quaternion (for eye gaze local-space transform) */\r\n headWorldQuat?: Quat;\r\n /** Current avatar Y rotation in radians (for gaze compensation) */\r\n avatarRotationY?: number;\r\n}\r\n\r\nexport interface CharacterUpdateOutput {\r\n /** 52 ARKit blendshape values, clamped [0,1] — apply to morph targets */\r\n blendshapes: Float32Array;\r\n /** Head rotation delta (radians) — apply to head bone */\r\n headDelta: { yaw: number; pitch: number };\r\n /** Normalized eye targets for eye blendshapes */\r\n eyeTargets: { x: number; y: number };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// CharacterController\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class CharacterController {\r\n private readonly _compositor: FaceCompositor;\r\n private readonly gazeEnabled: boolean;\r\n private readonly gazeYawInfluence: number;\r\n private readonly gazePitchInfluence: number;\r\n private readonly gazeSmoothing: number;\r\n\r\n // Frame budget tracking (rolling window)\r\n private readonly frameTimes: Float64Array = new Float64Array(120); // 2s @ 60fps\r\n private frameTimeIdx = 0;\r\n private frameTimeFill = 0;\r\n\r\n // Pre-allocated buffers (zero-alloc hot path)\r\n private readonly zeroBase = new Float32Array(52);\r\n private readonly outputBuffer = new Float32Array(52);\r\n private readonly compositorInput: FaceCompositorInput = {\r\n deltaTime: 0,\r\n emotion: undefined,\r\n eyeTargetX: 0,\r\n eyeTargetY: 0,\r\n isSpeaking: false,\r\n state: 'idle',\r\n };\r\n\r\n // Head smoothing state\r\n private gazeHeadYaw = 0;\r\n private gazeHeadPitch = -0.1;\r\n\r\n constructor(config?: CharacterControllerConfig) {\r\n this._compositor = new FaceCompositor(config?.compositor);\r\n this.gazeEnabled = config?.gaze?.enabled ?? true;\r\n this.gazeYawInfluence = config?.gaze?.yawInfluence ?? 0.4;\r\n this.gazePitchInfluence = config?.gaze?.pitchInfluence ?? 0.3;\r\n this.gazeSmoothing = config?.gaze?.smoothing ?? 5;\r\n\r\n logger.debug('constructor', {\r\n gazeEnabled: this.gazeEnabled,\r\n gazeYawInfluence: this.gazeYawInfluence,\r\n gazePitchInfluence: this.gazePitchInfluence,\r\n gazeSmoothing: this.gazeSmoothing,\r\n hasCompositorConfig: !!config?.compositor,\r\n });\r\n }\r\n\r\n /**\r\n * Call each frame. Pure function: input → output. No renderer side effects.\r\n *\r\n * Composes A2E blendshapes, emotion, procedural life, gaze tracking\r\n * into a single output frame.\r\n */\r\n update(input: CharacterUpdateInput): CharacterUpdateOutput {\r\n const frameStart = getClock().now();\r\n const base = input.baseBlendshapes ?? this.zeroBase;\r\n\r\n // Compute eye targets from camera + head positions + head rotation\r\n const eyeTargets = this.computeEyeTargets(\r\n input.cameraWorldPos,\r\n input.headWorldPos,\r\n input.headWorldQuat,\r\n );\r\n\r\n // Build compositor input (mutate pre-allocated object)\r\n const ci = this.compositorInput;\r\n ci.deltaTime = input.deltaTime;\r\n ci.emotion = resolveEmotion(input.emotion);\r\n ci.eyeTargetX = eyeTargets.x;\r\n ci.eyeTargetY = eyeTargets.y;\r\n ci.isSpeaking = input.isSpeaking;\r\n ci.state = input.state;\r\n ci.audioEnergy = input.audioEnergy;\r\n\r\n // Run 5-stage compositor\r\n const { blendshapes, headDelta: lifeHeadDelta } = this._compositor.compose(\r\n base, ci, this.outputBuffer,\r\n );\r\n\r\n // Smooth head rotation\r\n const headDelta = this.computeHeadGaze(\r\n input.deltaTime,\r\n input.cameraWorldPos,\r\n input.headWorldPos,\r\n lifeHeadDelta,\r\n input.avatarRotationY ?? 0,\r\n );\r\n\r\n // Frame budget monitoring (microseconds)\r\n const frameUs = (getClock().now() - frameStart) * 1000;\r\n\r\n // Rolling window for getPerformanceSnapshot()\r\n this.frameTimes[this.frameTimeIdx] = frameUs;\r\n this.frameTimeIdx = (this.frameTimeIdx + 1) % this.frameTimes.length;\r\n if (this.frameTimeFill < this.frameTimes.length) this.frameTimeFill++;\r\n\r\n const tel = getTelemetry();\r\n if (tel) {\r\n tel.recordHistogram(MetricNames.AVATAR_FRAME_LATENCY, frameUs);\r\n if (frameUs > FRAME_BUDGET_US) {\r\n tel.incrementCounter(MetricNames.AVATAR_FRAME_DROPS);\r\n }\r\n }\r\n\r\n return {\r\n blendshapes,\r\n headDelta,\r\n eyeTargets,\r\n };\r\n }\r\n\r\n /** Set emotion (string preset or weights object). */\r\n setEmotion(emotion: string | EmotionWeights): void {\r\n const resolved = resolveEmotion(emotion);\r\n if (resolved) {\r\n this._compositor.setEmotion(resolved);\r\n logger.debug('setEmotion', { emotion, resolved });\r\n }\r\n }\r\n\r\n /** Update character profile at runtime. */\r\n setProfile(profile: CharacterProfile): void {\r\n this._compositor.setProfile(profile);\r\n logger.debug('setProfile', {\r\n multiplierKeys: profile.multiplier ? Object.keys(profile.multiplier).length : 0,\r\n offsetKeys: profile.offset ? Object.keys(profile.offset).length : 0,\r\n });\r\n }\r\n\r\n /** Access underlying FaceCompositor for advanced use. */\r\n get compositor(): FaceCompositor {\r\n return this._compositor;\r\n }\r\n\r\n /**\r\n * Get a snapshot of frame budget performance (rolling 2-second window).\r\n * Useful for runtime diagnostics / dev overlays.\r\n */\r\n getPerformanceSnapshot(): {\r\n avgFrameUs: number;\r\n maxFrameUs: number;\r\n p95FrameUs: number;\r\n droppedFrames: number;\r\n totalFrames: number;\r\n } {\r\n if (this.frameTimeFill === 0) {\r\n return { avgFrameUs: 0, maxFrameUs: 0, p95FrameUs: 0, droppedFrames: 0, totalFrames: 0 };\r\n }\r\n\r\n const n = this.frameTimeFill;\r\n const times = Array.from(this.frameTimes.subarray(0, n));\r\n times.sort((a, b) => a - b);\r\n\r\n const sum = times.reduce((a, b) => a + b, 0);\r\n const p95Idx = Math.min(Math.floor(n * 0.95), n - 1);\r\n\r\n return {\r\n avgFrameUs: sum / n,\r\n maxFrameUs: times[n - 1],\r\n p95FrameUs: times[p95Idx],\r\n droppedFrames: times.filter(t => t > FRAME_BUDGET_US).length,\r\n totalFrames: n,\r\n };\r\n }\r\n\r\n /** Reset all state (smoothing, life layer, emotions). */\r\n reset(): void {\r\n this._compositor.reset();\r\n this.gazeHeadYaw = 0;\r\n this.gazeHeadPitch = -0.1;\r\n logger.debug('reset');\r\n }\r\n\r\n dispose(): void {\r\n this.reset();\r\n logger.debug('dispose');\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Eye angle math (extracted from r3f useGazeTracking.computeEyeTargets)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Compute normalized eye targets from camera and head positions.\r\n * Pure atan2/asin math — no renderer dependency.\r\n */\r\n private computeEyeTargets(\r\n cameraPos?: Vec3,\r\n headPos?: Vec3,\r\n headQuat?: Quat,\r\n ): { x: number; y: number } {\r\n if (!this.gazeEnabled || !cameraPos || !headPos) {\r\n return { x: 0, y: 0 };\r\n }\r\n\r\n // Direction from head (eye approximation) to camera\r\n const eyeY = headPos.y + 0.08;\r\n let dx = cameraPos.x - headPos.x;\r\n let dy = cameraPos.y - eyeY;\r\n let dz = cameraPos.z - headPos.z;\r\n\r\n // Normalize\r\n const len = Math.sqrt(dx * dx + dy * dy + dz * dz);\r\n if (len < 0.001) return { x: 0, y: 0 };\r\n\r\n dx /= len;\r\n dy /= len;\r\n dz /= len;\r\n\r\n // Transform world-space direction into head-local space via inverse quaternion\r\n if (headQuat) {\r\n // Invert quaternion: conjugate (negate xyz, keep w)\r\n const qx = -headQuat.x, qy = -headQuat.y, qz = -headQuat.z, qw = headQuat.w;\r\n // Apply quaternion rotation: q * v * q^-1 (optimized)\r\n const ix = qw * dx + qy * dz - qz * dy;\r\n const iy = qw * dy + qz * dx - qx * dz;\r\n const iz = qw * dz + qx * dy - qy * dx;\r\n const iw = -qx * dx - qy * dy - qz * dz;\r\n dx = ix * qw + iw * -qx + iy * -qz - iz * -qy;\r\n dy = iy * qw + iw * -qy + iz * -qx - ix * -qz;\r\n dz = iz * qw + iw * -qz + ix * -qy - iy * -qx;\r\n }\r\n\r\n // Eye yaw and pitch in head-local space\r\n const eyeYaw = Math.atan2(-dx, dz);\r\n const eyePitch = Math.asin(Math.max(-1, Math.min(1, dy)));\r\n const maxAngle = Math.PI / 6;\r\n\r\n return {\r\n x: Math.max(-1, Math.min(1, eyeYaw / maxAngle)),\r\n y: Math.max(-1, Math.min(1, eyePitch / maxAngle)),\r\n };\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Head gaze smoothing (extracted from r3f useGazeTracking.applyHeadGaze)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Compute smoothed head rotation. Returns target yaw/pitch values.\r\n * Renderer is responsible for applying these to the head bone.\r\n */\r\n private computeHeadGaze(\r\n deltaTime: number,\r\n cameraPos: Vec3 | undefined,\r\n headPos: Vec3 | undefined,\r\n lifeHeadDelta: { yaw: number; pitch: number },\r\n avatarRotationY: number,\r\n ): { yaw: number; pitch: number } {\r\n if (this.gazeEnabled && cameraPos && headPos) {\r\n // Direction from head to camera\r\n const dx = cameraPos.x - headPos.x;\r\n const dy = cameraPos.y - headPos.y;\r\n const dz = cameraPos.z - headPos.z;\r\n\r\n const len = Math.sqrt(dx * dx + dy * dy + dz * dz);\r\n if (len > 0.001) {\r\n // Compensate for avatar rotation\r\n const cosR = Math.cos(-avatarRotationY);\r\n const sinR = Math.sin(-avatarRotationY);\r\n const lx = dx * cosR - dz * sinR;\r\n const lz = dx * sinR + dz * cosR;\r\n\r\n const yaw = Math.atan2(lx, lz) * this.gazeYawInfluence;\r\n const pitchOffset = -0.18;\r\n const pitch = Math.asin(Math.max(-1, Math.min(1, dy / len)))\r\n * this.gazePitchInfluence + pitchOffset;\r\n\r\n const targetYaw = yaw + lifeHeadDelta.yaw;\r\n const targetPitch = pitch + lifeHeadDelta.pitch;\r\n const k = Math.min(1, deltaTime * this.gazeSmoothing);\r\n this.gazeHeadYaw += (targetYaw - this.gazeHeadYaw) * k;\r\n this.gazeHeadPitch += (targetPitch - this.gazeHeadPitch) * k;\r\n }\r\n } else {\r\n const targetYaw = lifeHeadDelta.yaw;\r\n const targetPitch = lifeHeadDelta.pitch - 0.1;\r\n const k = Math.min(1, deltaTime * 5);\r\n this.gazeHeadYaw += (targetYaw - this.gazeHeadYaw) * k;\r\n this.gazeHeadPitch += (targetPitch - this.gazeHeadPitch) * k;\r\n }\r\n\r\n return {\r\n yaw: this.gazeHeadYaw,\r\n pitch: this.gazeHeadPitch,\r\n };\r\n }\r\n}\r\n","/**\n * MicLipSync - Microphone → VAD → A2E → blendshapes\n *\n * Simple composition class for live mic lip sync (\"mirror mode\").\n * Replaces A2EOrchestrator with proper MicrophoneCapture integration.\n *\n * @category Orchestration\n */\n\nimport { EventEmitter, type OmoteEvents } from '../events';\nimport { MicrophoneCapture } from '../audio/MicrophoneCapture';\nimport { A2EProcessor } from '../inference/A2EProcessor';\nimport { int16ToFloat32 } from '../audio/audioUtils';\nimport { applyProfile } from '../audio/expressionProfile';\nimport { createLogger } from '../logging';\nimport { getClock } from '../logging/Clock';\nimport { ErrorCodes } from '../logging/ErrorCodes';\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\nimport { MetricNames } from '../telemetry/types';\nimport type { A2EBackend } from '../inference/A2EBackend';\nimport type { SileroVADBackend } from '../inference/createSileroVAD';\nimport type { ExpressionProfile } from '../audio/expressionProfile';\n\nconst logger = createLogger('MicLipSync');\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type MicLipSyncState = 'idle' | 'active' | 'paused';\n\nexport interface MicLipSyncConfig {\n /** A2E inference backend (from createA2E) — required */\n lam: A2EBackend;\n /** VAD backend for speech boundary detection (optional) */\n vad?: SileroVADBackend;\n /** Mic sample rate (default: 16000) */\n sampleRate?: number;\n /** Mic chunk size in samples (default: 512, required by Silero VAD) */\n micChunkSize?: number;\n /** Per-character expression weight scaling */\n profile?: ExpressionProfile;\n /** Identity/style index for A2E model (default: 0) */\n identityIndex?: number;\n}\n\nexport interface MicLipSyncFrame {\n blendshapes: Float32Array;\n rawBlendshapes: Float32Array;\n}\n\nexport interface MicLipSyncEvents {\n /** New blendshape frame ready */\n 'frame': MicLipSyncFrame;\n /** Speech started (VAD) */\n 'speech:start': void;\n /** Speech ended (VAD) */\n 'speech:end': { durationMs: number };\n /** Microphone started */\n 'mic:start': void;\n /** Microphone stopped */\n 'mic:stop': void;\n /** Audio level update */\n 'audio:level': { rms: number; peak: number };\n /** State changed */\n 'state': MicLipSyncState;\n /** Error occurred */\n 'error': Error;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// MicLipSync\n// ---------------------------------------------------------------------------\n\nexport class MicLipSync extends EventEmitter<MicLipSyncEvents> {\n private omoteEvents = new EventEmitter<OmoteEvents>();\n private mic: MicrophoneCapture;\n private processor: A2EProcessor;\n private vad?: SileroVADBackend;\n\n private _state: MicLipSyncState = 'idle';\n private _isSpeaking = false;\n private _currentFrame: Float32Array | null = null;\n private profile: ExpressionProfile;\n\n // Logging\n private _firstFrameEmitted = false;\n\n // Pre-allocated buffer for applyProfile (avoids per-frame Float32Array allocation)\n private readonly _profileBuffer = new Float32Array(52);\n\n // VAD serialization — processVAD is async but called from sync callback\n private vadQueue = Promise.resolve();\n\n // VAD state\n private speechStartTime = 0;\n private vadChunkSize = 0;\n private vadBuffer: Float32Array | null = null;\n private vadBufferOffset = 0;\n\n /** Current state */\n get state(): MicLipSyncState { return this._state; }\n /** Latest blendshape frame (null before first inference) */\n get currentFrame(): Float32Array | null { return this._currentFrame; }\n /** Whether speech is currently detected (requires VAD) */\n get isSpeaking(): boolean { return this._isSpeaking; }\n /** Current backend type */\n get backend(): string | null { return this.processor ? 'active' : null; }\n\n constructor(config: MicLipSyncConfig) {\n super();\n logger.info('MicLipSync created', {\n sampleRate: config.sampleRate ?? 16000,\n micChunkSize: config.micChunkSize ?? 512,\n hasVAD: !!config.vad,\n hasProfile: !!config.profile,\n identityIndex: config.identityIndex ?? 0,\n });\n this.profile = config.profile ?? {};\n this.vad = config.vad;\n\n // Create MicrophoneCapture with internal OmoteEvents bus\n this.mic = new MicrophoneCapture(this.omoteEvents, {\n sampleRate: config.sampleRate ?? 16000,\n chunkSize: config.micChunkSize ?? 512,\n });\n\n // Create A2EProcessor in push/drip mode\n this.processor = new A2EProcessor({\n backend: config.lam,\n sampleRate: config.sampleRate ?? 16000,\n identityIndex: config.identityIndex,\n onFrame: (raw) => {\n const scaled = applyProfile(raw, this.profile, this._profileBuffer);\n this._currentFrame = scaled;\n if (!this._firstFrameEmitted) {\n this._firstFrameEmitted = true;\n logger.trace('First blendshape frame emitted');\n }\n this.emit('frame', { blendshapes: scaled, rawBlendshapes: raw });\n },\n onError: (error) => {\n logger.error('A2E inference error', { message: error.message });\n this.emit('error', error);\n },\n });\n\n // Wire mic PCM to processor (Int16Array → Float32Array conversion)\n this.omoteEvents.on('audio.chunk', ({ pcm }) => {\n const float32 = int16ToFloat32(pcm);\n this.processor.pushAudio(float32); // No timestamp = push/drip mode\n\n // Feed VAD if present — serialize async calls to prevent concurrent access\n if (this.vad) {\n this.vadQueue = this.vadQueue\n .then(() => this.processVAD(float32))\n .catch((err) => {\n logger.warn('VAD processing error', { error: String(err), code: ErrorCodes.SPH_VAD_ERROR });\n this.emit('error', err instanceof Error ? err : new Error(String(err)));\n });\n }\n });\n\n // Forward audio levels\n this.omoteEvents.on('audio.level', (level) => {\n this.emit('audio:level', level);\n });\n\n // Set up VAD buffer\n if (this.vad) {\n this.vadChunkSize = this.vad.getChunkSize();\n this.vadBuffer = new Float32Array(this.vadChunkSize);\n this.vadBufferOffset = 0;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /** Start microphone capture and inference loop */\n async start(): Promise<void> {\n if (this._state === 'active') return;\n\n logger.info('Starting MicLipSync');\n getTelemetry()?.incrementCounter(MetricNames.MIC_SESSIONS);\n await this.mic.start();\n this.processor.startDrip();\n this.emit('mic:start');\n this.setState('active');\n }\n\n /** Stop microphone and inference */\n stop(): void {\n if (this._state === 'idle') return;\n\n logger.info('Stopping MicLipSync');\n this.processor.stopDrip();\n this.mic.stop();\n this._isSpeaking = false;\n this.emit('mic:stop');\n this.setState('idle');\n }\n\n /** Pause inference (mic stays open for faster resume) */\n pause(): void {\n if (this._state !== 'active') return;\n\n this.processor.stopDrip();\n this.setState('paused');\n }\n\n /** Resume inference after pause */\n resume(): void {\n if (this._state !== 'paused') return;\n\n this.processor.startDrip();\n this.setState('active');\n }\n\n /** Update ExpressionProfile at runtime */\n setProfile(profile: ExpressionProfile): void {\n this.profile = profile;\n }\n\n /** Dispose of all resources */\n async dispose(): Promise<void> {\n this.stop();\n this.processor.dispose();\n this.vad = undefined; // release reference, don't dispose (caller owns it)\n }\n\n // ---------------------------------------------------------------------------\n // Internal: VAD processing\n // ---------------------------------------------------------------------------\n\n private async processVAD(samples: Float32Array): Promise<void> {\n if (!this.vad || !this.vadBuffer) return;\n\n // Accumulate samples to VAD chunk size\n for (let i = 0; i < samples.length; i++) {\n this.vadBuffer[this.vadBufferOffset++] = samples[i];\n\n if (this.vadBufferOffset >= this.vadChunkSize) {\n try {\n const result = await this.vad!.process(this.vadBuffer);\n const wasSpeaking = this._isSpeaking;\n this._isSpeaking = result.isSpeech;\n\n if (!wasSpeaking && result.isSpeech) {\n this.speechStartTime = getClock().now();\n this.emit('speech:start');\n } else if (wasSpeaking && !result.isSpeech) {\n const durationMs = getClock().now() - this.speechStartTime;\n this.emit('speech:end', { durationMs });\n }\n } catch (err) {\n logger.warn('VAD process error', { error: String(err), code: ErrorCodes.SPH_VAD_ERROR });\n this.emit('error', err instanceof Error ? err : new Error(String(err)));\n }\n\n this.vadBufferOffset = 0;\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: State management\n // ---------------------------------------------------------------------------\n\n private setState(state: MicLipSyncState): void {\n if (this._state === state) return;\n this._state = state;\n this.emit('state', state);\n }\n}\n","/**\r\n * VoiceOrchestrator — Shared voice wiring for OmoteAvatar adapters.\r\n *\r\n * Composes TTSSpeaker (local mode) or PlaybackPipeline (cloud mode) with\r\n * SpeechListener and InterruptionHandler. Supports both local TTS and\r\n * cloud TTS via discriminated union config.\r\n *\r\n * Extracted from the ~70 identical lines duplicated across three/babylon/r3f\r\n * adapters into a single reusable class.\r\n *\r\n * @category Orchestration\r\n */\r\n\r\nimport { EventEmitter } from '../events/EventEmitter';\r\nimport { TTSSpeaker } from '../audio/TTSSpeaker';\r\nimport { SpeechListener } from '../audio/SpeechListener';\r\nimport { InterruptionHandler } from '../audio/InterruptionHandler';\r\nimport { PlaybackPipeline } from '../audio/PlaybackPipeline';\r\nimport { calculateRMS } from '../animation/audioEnergy';\r\nimport { createA2E } from '../inference/createA2E';\r\nimport { UnifiedInferenceWorker } from '../inference/UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from '../inference/sharedLazyWorker';\r\nimport { createLogger } from '../logging';\r\nimport type { TTSBackend } from '../inference/TTSBackend';\r\nimport type { TTSSpeakerConfig } from '../audio/TTSSpeaker';\r\nimport type { SpeechListenerConfig } from '../audio/SpeechListener';\r\nimport type { ExpressionProfile } from '../audio/expressionProfile';\r\nimport type { A2EBackend } from '../inference/A2EBackend';\r\nimport type { ConversationalState } from '../animation';\r\nimport type {\r\n TranscriptResult,\r\n ResponseHandler,\r\n FrameSource,\r\n LoadingProgress,\r\n} from './types';\r\n\r\nconst logger = createLogger('VoiceOrchestrator');\r\n\r\n// ---------------------------------------------------------------------------\r\n// Config (discriminated union)\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface VoiceOrchestratorBaseConfig {\r\n listener?: SpeechListenerConfig;\r\n interruptionEnabled?: boolean;\r\n profile?: ExpressionProfile;\r\n onStateChange?: (state: ConversationalState) => void;\r\n onLoadingProgress?: (progress: LoadingProgress) => void;\r\n onError?: (error: Error) => void;\r\n onTranscriptEvent?: (result: TranscriptResult) => void;\r\n onInterruption?: () => void;\r\n}\r\n\r\nexport interface VoiceOrchestratorLocalConfig extends VoiceOrchestratorBaseConfig {\r\n mode?: 'local';\r\n tts: TTSBackend;\r\n speaker?: TTSSpeakerConfig;\r\n onTranscript: (text: string, emotion?: string) => string | Promise<string> | AsyncGenerator<string>;\r\n}\r\n\r\nexport interface VoiceOrchestratorCloudConfig extends VoiceOrchestratorBaseConfig {\r\n mode: 'cloud';\r\n onResponse: ResponseHandler;\r\n lam?: { modelUrl?: string; externalDataUrl?: string | false; unifiedWorker?: UnifiedInferenceWorker };\r\n identityIndex?: number;\r\n neutralTransitionEnabled?: boolean;\r\n}\r\n\r\nexport type VoiceOrchestratorConfig = VoiceOrchestratorLocalConfig | VoiceOrchestratorCloudConfig;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Events\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface VoiceOrchestratorEvents {\r\n 'state': ConversationalState;\r\n 'transcript': TranscriptResult;\r\n 'interruption': void;\r\n 'loading:progress': LoadingProgress;\r\n 'error': Error;\r\n 'audio:level': { rms: number; peak: number };\r\n 'playback:complete': void;\r\n [key: string]: unknown;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// VoiceOrchestrator\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class VoiceOrchestrator extends EventEmitter<VoiceOrchestratorEvents> {\r\n // Shared primitives\r\n private speechListener: SpeechListener | null = null;\r\n private interruption: InterruptionHandler | null = null;\r\n\r\n // Local mode\r\n private ttsSpeaker: TTSSpeaker | null = null;\r\n\r\n // Cloud mode\r\n private playbackPipeline: PlaybackPipeline | null = null;\r\n private ownedLam: A2EBackend | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n\r\n // Wiring cleanup\r\n private transcriptUnsub: (() => void) | null = null;\r\n private audioChunkUnsub: (() => void) | null = null;\r\n private connectEpoch = 0;\r\n\r\n // Response abort (cloud mode)\r\n private responseAbortController: AbortController | null = null;\r\n\r\n // State\r\n private _state: ConversationalState = 'idle';\r\n private _isSpeaking = false;\r\n private _frameSource: FrameSource | null = null;\r\n private _mode: 'local' | 'cloud' = 'local';\r\n private _sessionId: string | null = null;\r\n\r\n get state(): ConversationalState { return this._state; }\r\n get isSpeaking(): boolean { return this._isSpeaking; }\r\n get frameSource(): FrameSource | null { return this._frameSource; }\r\n\r\n /** Access the internal SpeechListener. */\r\n get listener(): SpeechListener | null { return this.speechListener; }\r\n /** Access the internal TTSSpeaker (local mode only). */\r\n get speaker(): TTSSpeaker | null { return this.ttsSpeaker; }\r\n\r\n // -------------------------------------------------------------------------\r\n // Connect / Disconnect\r\n // -------------------------------------------------------------------------\r\n\r\n async connect(config: VoiceOrchestratorConfig): Promise<void> {\r\n await this.disconnect();\r\n const epoch = ++this.connectEpoch;\r\n this._mode = config.mode ?? 'local';\r\n this._sessionId = crypto.randomUUID();\r\n\r\n // Wire config callbacks before any loading (B1 — subscribe pre-load)\r\n if (config.onStateChange) this.on('state', config.onStateChange);\r\n if (config.onLoadingProgress) this.on('loading:progress', config.onLoadingProgress);\r\n if (config.onError) this.on('error', config.onError);\r\n if (config.onTranscriptEvent) this.on('transcript', config.onTranscriptEvent);\r\n if (config.onInterruption) this.on('interruption', config.onInterruption);\r\n\r\n logger.info('Connecting voice orchestrator', { mode: this._mode });\r\n\r\n try {\r\n // 1. Connect speaker (local) or PlaybackPipeline (cloud)\r\n if (this._mode === 'local') {\r\n const localCfg = config as VoiceOrchestratorLocalConfig;\r\n this.emit('loading:progress', { currentModel: 'Loading TTS', progress: 0, totalModels: 3, modelsLoaded: 0 });\r\n this.ttsSpeaker = new TTSSpeaker();\r\n await this.ttsSpeaker.connect(localCfg.tts, localCfg.speaker);\r\n if (this.connectEpoch !== epoch) return;\r\n this.emit('loading:progress', { currentModel: 'TTS + lip sync loaded', progress: 33, totalModels: 3, modelsLoaded: 1 });\r\n this._frameSource = this.ttsSpeaker.frameSource;\r\n } else {\r\n const cloudCfg = config as VoiceOrchestratorCloudConfig;\r\n // Create A2E + PlaybackPipeline for cloud lip sync\r\n this.emit('loading:progress', { currentModel: 'Loading inference worker', progress: 0, totalModels: 3, modelsLoaded: 0 });\r\n let worker = cloudCfg.lam?.unifiedWorker;\r\n if (!worker) {\r\n worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n }\r\n this.ownedWorker = worker;\r\n const lam = createA2E({\r\n ...cloudCfg.lam,\r\n unifiedWorker: worker,\r\n });\r\n this.ownedLam = lam;\r\n await lam.load();\r\n if (this.connectEpoch !== epoch) return;\r\n this.emit('loading:progress', { currentModel: 'Lip sync model loaded', progress: 33, totalModels: 3, modelsLoaded: 1 });\r\n\r\n this.playbackPipeline = new PlaybackPipeline({\r\n lam,\r\n profile: config.profile,\r\n identityIndex: cloudCfg.identityIndex,\r\n neutralTransitionEnabled: cloudCfg.neutralTransitionEnabled,\r\n });\r\n await this.playbackPipeline.initialize();\r\n if (this.connectEpoch !== epoch) return;\r\n\r\n // Wire playback:complete → transition back to listening\r\n this.playbackPipeline.on('playback:complete', () => {\r\n this._isSpeaking = false;\r\n this.interruption?.setAISpeaking(false);\r\n this.speechListener?.resume();\r\n this.setState('listening');\r\n this.emit('playback:complete');\r\n });\r\n\r\n this._frameSource = this.playbackPipeline;\r\n }\r\n\r\n // 2. Connect listener — share the same worker to avoid 2× ORT WASM on iOS\r\n const listenerConfig: SpeechListenerConfig = { ...config.listener };\r\n if (this.ownedWorker && !listenerConfig.unifiedWorker) {\r\n listenerConfig.unifiedWorker = this.ownedWorker;\r\n }\r\n this.speechListener = new SpeechListener(listenerConfig);\r\n // Remap SpeechListener progress (0-100 over 2 models) to 33-100 over 3 total models\r\n this.speechListener.on('loading:progress', (p) => {\r\n const remapped: LoadingProgress = {\r\n currentModel: p.currentModel,\r\n totalModels: 3,\r\n modelsLoaded: 1 + p.modelsLoaded,\r\n progress: 33 + Math.round((p.progress / 100) * 67),\r\n };\r\n this.emit('loading:progress', remapped);\r\n });\r\n this.speechListener.on('error', (e) => this.emit('error', e));\r\n this.speechListener.on('audio:level', (l) => this.emit('audio:level', l));\r\n await this.speechListener.loadModels();\r\n if (this.connectEpoch !== epoch) return;\r\n\r\n // 3. Interruption handler\r\n this.interruption = new InterruptionHandler({\r\n enabled: config.interruptionEnabled ?? true,\r\n });\r\n this.interruption.on('interruption.triggered', () => {\r\n this.handleInterruption();\r\n });\r\n\r\n // 4. Wire audio:chunk for interruption detection during speaking\r\n const audioChunkHandler = (samples: Float32Array) => {\r\n if (this._state === 'speaking' && this.interruption) {\r\n const rms = calculateRMS(samples);\r\n this.interruption.processAudioEnergy(rms);\r\n }\r\n };\r\n this.speechListener.on('audio:chunk', audioChunkHandler);\r\n this.audioChunkUnsub = () => this.speechListener?.off?.('audio:chunk', audioChunkHandler);\r\n\r\n // 5. Wire transcript handler\r\n if (this._mode === 'local') {\r\n this.wireLocalTranscript(config as VoiceOrchestratorLocalConfig);\r\n } else {\r\n this.wireCloudTranscript(config as VoiceOrchestratorCloudConfig);\r\n }\r\n\r\n logger.info('Voice orchestrator connected', { mode: this._mode });\r\n } catch (err) {\r\n logger.error('Voice orchestrator connect failed, cleaning up', { error: String(err) });\r\n await this.disconnect();\r\n throw err;\r\n }\r\n }\r\n\r\n async disconnect(): Promise<void> {\r\n this.connectEpoch++;\r\n this.responseAbortController?.abort();\r\n this.responseAbortController = null;\r\n\r\n // Remove config callback listeners to prevent accumulation on re-connect()\r\n this.removeAllListeners();\r\n\r\n this.transcriptUnsub?.();\r\n this.audioChunkUnsub?.();\r\n this.transcriptUnsub = null;\r\n this.audioChunkUnsub = null;\r\n this.interruption = null;\r\n\r\n if (this.ttsSpeaker) {\r\n await this.ttsSpeaker.dispose();\r\n this.ttsSpeaker = null;\r\n }\r\n\r\n if (this.playbackPipeline) {\r\n await this.playbackPipeline.dispose();\r\n this.playbackPipeline = null;\r\n }\r\n\r\n if (this.ownedLam) {\r\n await this.ownedLam.dispose();\r\n this.ownedLam = null;\r\n }\r\n\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n } else if (this.ownedWorker) {\r\n await this.ownedWorker.dispose();\r\n this.ownedWorker = null;\r\n }\r\n\r\n if (this.speechListener) {\r\n await this.speechListener.dispose();\r\n this.speechListener = null;\r\n }\r\n\r\n this._frameSource = null;\r\n this._isSpeaking = false;\r\n this._state = 'idle';\r\n this._sessionId = null;\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Listener control\r\n // -------------------------------------------------------------------------\r\n\r\n async startListening(): Promise<void> {\r\n if (!this.speechListener) {\r\n throw new Error('VoiceOrchestrator not connected. Call connect() first.');\r\n }\r\n this.setState('listening');\r\n await this.speechListener.start();\r\n }\r\n\r\n stopListening(): void {\r\n this.speechListener?.stop();\r\n if (this._state === 'listening') this.setState('idle');\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Speaker control (local mode only)\r\n // -------------------------------------------------------------------------\r\n\r\n async speak(text: string, options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string }): Promise<void> {\r\n if (!this.ttsSpeaker) {\r\n throw new Error('speak() requires local mode. Call connect() with mode: \"local\" first.');\r\n }\r\n this._isSpeaking = true;\r\n this.setState('speaking');\r\n try {\r\n await this.ttsSpeaker.speak(text, options);\r\n } finally {\r\n this._isSpeaking = false;\r\n if (this._state === 'speaking') this.setState('idle');\r\n }\r\n }\r\n\r\n async streamText(options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string }): Promise<{\r\n push: (token: string) => void;\r\n end: () => Promise<void>;\r\n }> {\r\n if (!this.ttsSpeaker) {\r\n throw new Error('streamText() requires local mode. Call connect() with mode: \"local\" first.');\r\n }\r\n this._isSpeaking = true;\r\n this.setState('speaking');\r\n const stream = await this.ttsSpeaker.streamText(options ?? {});\r\n return {\r\n push: stream.push,\r\n end: async () => {\r\n try { await stream.end(); }\r\n finally {\r\n this._isSpeaking = false;\r\n if (this._state === 'speaking') this.setState('idle');\r\n }\r\n },\r\n };\r\n }\r\n\r\n stopSpeaking(): void {\r\n if (this._mode === 'local') {\r\n this.ttsSpeaker?.stop();\r\n } else {\r\n this.responseAbortController?.abort();\r\n this.playbackPipeline?.stop();\r\n }\r\n this._isSpeaking = false;\r\n this.interruption?.setAISpeaking(false);\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Internal — transcript wiring\r\n // -------------------------------------------------------------------------\r\n\r\n private wireLocalTranscript(config: VoiceOrchestratorLocalConfig): void {\r\n const handler = async (result: TranscriptResult) => {\r\n this.emit('transcript', result); // All transcripts (interim + final)\r\n if (!result.isFinal || !result.text.trim()) return;\r\n this.setState('thinking');\r\n this.speechListener?.pause();\r\n this.interruption?.setAISpeaking(true);\r\n try {\r\n const response = config.onTranscript(result.text, result.emotion);\r\n if (isAsyncGenerator(response)) {\r\n const stream = await this.streamText();\r\n for await (const token of response) { stream.push(token); }\r\n await stream.end();\r\n } else {\r\n const text = await response;\r\n await this.speak(text);\r\n }\r\n } catch (e) {\r\n logger.error('Voice transcript handler error', { error: String(e) });\r\n } finally {\r\n this.interruption?.setAISpeaking(false);\r\n this.speechListener?.resume();\r\n this.setState('listening');\r\n }\r\n };\r\n this.speechListener!.on('transcript', handler);\r\n this.transcriptUnsub = () => this.speechListener?.off?.('transcript', handler);\r\n }\r\n\r\n private wireCloudTranscript(config: VoiceOrchestratorCloudConfig): void {\r\n const handler = async (result: TranscriptResult) => {\r\n this.emit('transcript', result); // All transcripts (interim + final)\r\n if (!result.isFinal || !result.text.trim()) return;\r\n this.setState('thinking');\r\n this.speechListener?.pause();\r\n this.interruption?.setAISpeaking(true);\r\n this._isSpeaking = true;\r\n\r\n const abortController = new AbortController();\r\n this.responseAbortController = abortController;\r\n\r\n try {\r\n this.setState('speaking');\r\n this.playbackPipeline!.start();\r\n\r\n await config.onResponse({\r\n text: result.text,\r\n emotion: result.emotion,\r\n event: result.event,\r\n setEmotion: (emotion: string) => this.playbackPipeline?.setEmotion(emotion),\r\n send: async (chunk: Uint8Array) => {\r\n if (abortController.signal.aborted) return;\r\n await this.playbackPipeline!.onAudioChunk(chunk);\r\n },\r\n done: async () => {\r\n if (abortController.signal.aborted) return;\r\n await this.playbackPipeline!.end();\r\n },\r\n signal: abortController.signal,\r\n sessionId: this._sessionId!,\r\n });\r\n } catch (e) {\r\n if (!abortController.signal.aborted) {\r\n logger.error('Cloud response handler error', { error: String(e) });\r\n }\r\n } finally {\r\n this.responseAbortController = null;\r\n // State transitions handled by playback:complete event (cloud)\r\n // or speak()/streamText() finally blocks (local)\r\n }\r\n };\r\n this.speechListener!.on('transcript', handler);\r\n this.transcriptUnsub = () => this.speechListener?.off?.('transcript', handler);\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Internal — interruption\r\n // -------------------------------------------------------------------------\r\n\r\n private handleInterruption(): void {\r\n if (this._state !== 'speaking') return;\r\n logger.info('Interruption triggered');\r\n\r\n this.stopSpeaking();\r\n this.emit('interruption');\r\n this.speechListener?.resume();\r\n this.setState('listening');\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Internal — state\r\n // -------------------------------------------------------------------------\r\n\r\n private setState(state: ConversationalState): void {\r\n if (this._state === state) return;\r\n this._state = state;\r\n this.emit('state', state);\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Helpers\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction isAsyncGenerator(value: unknown): value is AsyncGenerator<string> {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n Symbol.asyncIterator in (value as Record<symbol, unknown>)\r\n );\r\n}\r\n","/**\n * Versioned NDJSON streaming protocol for client-server communication.\n *\n * Each line in the NDJSON stream is a JSON object conforming to ProtocolEvent.\n * The `v` field enables backward-compatible protocol evolution.\n *\n * @category Protocol\n */\n\n/** Current protocol version. Increment on breaking changes. */\nexport const PROTOCOL_VERSION = 1;\n\n/** Base shape for all NDJSON protocol events */\ninterface ProtocolEventBase {\n /** Protocol version */\n v: number;\n /** Event type discriminator */\n type: string;\n /** ISO 8601 timestamp */\n ts: string;\n}\n\n// --- Response events (server -> client) ---\n\nexport interface ResponseStartEvent extends ProtocolEventBase {\n type: 'response_start';\n /** Initial emotion for the response */\n emotion?: string;\n}\n\nexport interface ResponseChunkEvent extends ProtocolEventBase {\n type: 'response_chunk';\n /** Text chunk (streamed token-by-token) */\n text: string;\n}\n\nexport interface AudioChunkEvent extends ProtocolEventBase {\n type: 'audio_chunk';\n /** Base64-encoded PCM16 audio at 16kHz */\n audio: string;\n /** Audio format identifier */\n format: 'pcm_16000';\n /** Whether this is the last audio chunk */\n isLast: boolean;\n}\n\nexport interface ResponseEndEvent extends ProtocolEventBase {\n type: 'response_end';\n /** Complete response text */\n fullText: string;\n /** Response generation time in ms */\n durationMs?: number;\n /** Token count for this response */\n tokenCount?: number;\n}\n\nexport interface ErrorEvent extends ProtocolEventBase {\n type: 'error';\n /** Error message */\n message: string;\n /** Error code for programmatic handling */\n code?: string;\n /** Whether the client should retry */\n recoverable?: boolean;\n}\n\n/**\n * Union of all protocol events.\n * Used for type-safe NDJSON parsing on the client.\n */\nexport type ProtocolEvent =\n | ResponseStartEvent\n | ResponseChunkEvent\n | AudioChunkEvent\n | ResponseEndEvent\n | ErrorEvent;\n\n/**\n * Type guard for protocol events.\n */\nexport function isProtocolEvent(obj: unknown): obj is ProtocolEvent {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'v' in obj &&\n 'type' in obj &&\n 'ts' in obj\n );\n}\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["logger","logger","logger","logger","logger","logger","logger","logger","logger","logger","worker","logger","logger","logger","logger","logger","data","logger","logger","logger","logger","logger","logger","logger","logger","logger","logger","logger","logger","latency","logger","logger","logger","t","eased","logger","logger","BS_INDEX","logger","logger","logger"]}
|
|
1
|
+
{"version":3,"sources":["../src/audio/audioConvert.ts","../src/audio/audioUtils.ts","../src/telemetry/types.ts","../src/audio/MicrophoneCapture.ts","../src/audio/RingBuffer.ts","../src/audio/AudioScheduler.ts","../src/audio/AudioChunkCoalescer.ts","../src/inference/BlendshapeSmoother.ts","../src/inference/A2EProcessor.ts","../src/inference/blendshapeUtils.ts","../src/audio/expressionProfile.ts","../src/audio/PlaybackPipeline.ts","../src/audio/TTSPlayback.ts","../src/inference/defaultModelUrls.ts","../src/utils/runtime.ts","../src/inference/onnxLoader.ts","../src/inference/UnifiedInferenceWorker.ts","../src/inference/sharedLazyWorker.ts","../src/inference/ctcDecoder.ts","../src/inference/adapters/SenseVoiceUnifiedAdapter.ts","../src/inference/adapters/A2EUnifiedAdapter.ts","../src/inference/kokoroTokenizer.ts","../src/inference/kokoroPhonemizer.ts","../src/cache/ModelCache.ts","../src/inference/kokoroVoices.ts","../src/inference/KokoroTTSTypes.ts","../src/inference/adapters/KokoroTTSUnifiedAdapter.ts","../src/inference/adapters/SileroVADUnifiedAdapter.ts","../src/inference/createA2E.ts","../src/audio/TTSSpeaker.ts","../src/inference/createKokoroTTS.ts","../src/audio/createTTSPlayer.ts","../src/inference/createSenseVoice.ts","../src/inference/createSileroVAD.ts","../src/audio/SpeechListener.ts","../src/audio/InterruptionHandler.ts","../src/inference/SafariSpeechRecognition.ts","../src/inference/ElevenLabsTTSBackend.ts","../src/emotion/Emotion.ts","../src/animation/types.ts","../src/animation/AnimationGraph.ts","../src/animation/audioEnergy.ts","../src/animation/ProceduralLifeLayer.ts","../src/animation/bodyAnimation.ts","../src/face/FACSMapping.ts","../src/face/EmotionResolver.ts","../src/face/FaceCompositor.ts","../src/face/TextEmotionAnalyzer.ts","../src/face/EmotionTagParser.ts","../src/character/CharacterController.ts","../src/orchestration/MicLipSync.ts","../src/orchestration/VoiceOrchestrator.ts","../../types/src/protocol.ts"],"sourcesContent":["/**\r\n * Audio format conversion utilities\r\n *\r\n * Bridges the gap between TTS engines (Float32 at various sample rates)\r\n * and playback pipelines (Uint8Array PCM16 at 16kHz).\r\n *\r\n * @module audio/audioConvert\r\n */\r\n\r\n/**\r\n * Convert Float32 [-1,1] samples to PCM16 Uint8Array (little-endian).\r\n *\r\n * @param samples - Float32Array of normalized audio samples\r\n * @returns Uint8Array of PCM16 bytes (2 bytes per sample, little-endian)\r\n */\r\nexport function float32ToPcm16(samples: Float32Array): Uint8Array {\r\n const buffer = new ArrayBuffer(samples.length * 2);\r\n const view = new DataView(buffer);\r\n\r\n for (let i = 0; i < samples.length; i++) {\r\n // Clamp to [-1, 1] then scale to Int16 range\r\n const s = Math.max(-1, Math.min(1, samples[i]));\r\n const val = s < 0 ? s * 32768 : s * 32767;\r\n view.setInt16(i * 2, val, true); // little-endian\r\n }\r\n\r\n return new Uint8Array(buffer);\r\n}\r\n\r\n/**\r\n * Linear interpolation resampler.\r\n * Good enough for speech (no sinc filtering needed).\r\n *\r\n * @param samples - Input audio samples\r\n * @param fromRate - Source sample rate (e.g., 24000)\r\n * @param toRate - Target sample rate (e.g., 16000)\r\n * @returns Resampled Float32Array\r\n */\r\nexport function resampleLinear(\r\n samples: Float32Array,\r\n fromRate: number,\r\n toRate: number,\r\n): Float32Array {\r\n if (fromRate === toRate) return samples;\r\n if (samples.length === 0) return new Float32Array(0);\r\n\r\n const ratio = fromRate / toRate;\r\n const outputLength = Math.floor(samples.length / ratio);\r\n if (outputLength === 0) return new Float32Array(0);\r\n\r\n const output = new Float32Array(outputLength);\r\n\r\n for (let i = 0; i < outputLength; i++) {\r\n const srcPos = i * ratio;\r\n const srcIndex = Math.floor(srcPos);\r\n const frac = srcPos - srcIndex;\r\n\r\n if (srcIndex + 1 < samples.length) {\r\n output[i] = samples[srcIndex] * (1 - frac) + samples[srcIndex + 1] * frac;\r\n } else {\r\n output[i] = samples[Math.min(srcIndex, samples.length - 1)];\r\n }\r\n }\r\n\r\n return output;\r\n}\r\n\r\n/**\r\n * Convenience: resample + encode in one call.\r\n * Converts TTS output (Float32 at TTS rate) to pipeline format (PCM16 Uint8Array at 16kHz).\r\n *\r\n * @param audio - Float32Array from TTS engine\r\n * @param sourceRate - TTS engine's output sample rate (default: 24000)\r\n * @param targetRate - Pipeline's expected sample rate (default: 16000)\r\n * @returns Uint8Array PCM16 at target rate\r\n */\r\nexport function ttsToPlaybackFormat(\r\n audio: Float32Array,\r\n sourceRate: number = 24000,\r\n targetRate: number = 16000,\r\n): Uint8Array {\r\n const resampled = resampleLinear(audio, sourceRate, targetRate);\r\n return float32ToPcm16(resampled);\r\n}\r\n","/**\r\n * Shared audio utility functions\r\n *\r\n * @module audio\r\n */\r\n\r\n/**\r\n * Safely convert an ArrayBuffer of PCM16 bytes to Float32 samples.\r\n * Handles odd-length buffers by truncating to the nearest even byte boundary.\r\n */\r\nexport function pcm16ToFloat32(buffer: ArrayBuffer): Float32Array {\r\n // Int16Array requires even byte length — truncate if odd\r\n const byteLen = buffer.byteLength & ~1;\r\n const int16 = byteLen === buffer.byteLength\r\n ? new Int16Array(buffer)\r\n : new Int16Array(buffer, 0, byteLen / 2);\r\n const float32 = new Float32Array(int16.length);\r\n for (let i = 0; i < int16.length; i++) {\r\n float32[i] = int16[i] / 32768;\r\n }\r\n return float32;\r\n}\r\n\r\n/**\r\n * Convert Int16Array samples to Float32Array.\r\n * Each sample is divided by 32768 to normalize to [-1, 1] range.\r\n */\r\nexport function int16ToFloat32(int16: Int16Array): Float32Array {\r\n const float32 = new Float32Array(int16.length);\r\n for (let i = 0; i < int16.length; i++) {\r\n float32[i] = int16[i] / 32768;\r\n }\r\n return float32;\r\n}\r\n","/**\r\n * Telemetry Types\r\n *\r\n * Configuration and type definitions for OpenTelemetry instrumentation.\r\n *\r\n * @category Telemetry\r\n */\r\n\r\n/**\r\n * Supported telemetry exporters\r\n */\r\nexport type TelemetryExporter = 'console' | 'otlp' | 'none';\r\n\r\n/**\r\n * Sampling configuration\r\n */\r\nexport interface SamplingConfig {\r\n /** Sampling ratio (0.0 - 1.0). Default: 1.0 (sample everything) */\r\n ratio?: number;\r\n /** Always sample errors regardless of ratio */\r\n alwaysSampleErrors?: boolean;\r\n}\r\n\r\n/**\r\n * OTLP exporter configuration\r\n */\r\nexport interface OTLPExporterConfig {\r\n /** OTLP endpoint URL (e.g., 'https://tempo.example.com/v1/traces') */\r\n endpoint: string;\r\n /** Optional headers for authentication */\r\n headers?: Record<string, string>;\r\n /** Request timeout in ms. Default: 10000 */\r\n timeoutMs?: number;\r\n}\r\n\r\n/**\r\n * Main telemetry configuration\r\n */\r\nexport interface TelemetryConfig {\r\n /** Enable/disable telemetry. Default: false */\r\n enabled?: boolean;\r\n /** Service name for spans. Default: 'omote-sdk' */\r\n serviceName?: string;\r\n /** Service version. Default: SDK version */\r\n serviceVersion?: string;\r\n /** Exporter type. Default: 'none' */\r\n exporter?: TelemetryExporter;\r\n /** OTLP exporter config (required if exporter is 'otlp') */\r\n exporterConfig?: OTLPExporterConfig;\r\n /** Sampling configuration */\r\n sampling?: SamplingConfig;\r\n /** Enable metrics collection. Default: true when telemetry enabled */\r\n metricsEnabled?: boolean;\r\n /** Metrics export interval in ms. Default: 60000 */\r\n metricsIntervalMs?: number;\r\n /** Custom exporter instance (overrides `exporter` when provided) */\r\n customExporter?: import('./exporters/console').TelemetryExporterInterface;\r\n}\r\n\r\n/**\r\n * Span attributes for model operations\r\n */\r\nexport interface ModelSpanAttributes {\r\n /** Model URL or identifier */\r\n 'model.url'?: string;\r\n /** Model name (e.g., 'whisper', 'lam', 'silero-vad') */\r\n 'model.name'?: string;\r\n /** Inference backend used */\r\n 'model.backend'?: 'webgpu' | 'wasm';\r\n /** Whether model was loaded from cache */\r\n 'model.cached'?: boolean;\r\n /** Model size in bytes */\r\n 'model.size_bytes'?: number;\r\n}\r\n\r\n/**\r\n * Span attributes for inference operations\r\n */\r\nexport interface InferenceSpanAttributes extends ModelSpanAttributes {\r\n /** Number of input audio samples */\r\n 'inference.input_samples'?: number;\r\n /** Input duration in ms */\r\n 'inference.input_duration_ms'?: number;\r\n /** Number of output frames (for LAM) */\r\n 'inference.output_frames'?: number;\r\n /** Inference duration in ms */\r\n 'inference.duration_ms'?: number;\r\n /** Whether inference succeeded */\r\n 'inference.success'?: boolean;\r\n /** Error type if failed */\r\n 'inference.error_type'?: string;\r\n}\r\n\r\n/**\r\n * Span attributes for cache operations\r\n */\r\nexport interface CacheSpanAttributes {\r\n /** Cache key (URL) */\r\n 'cache.key'?: string;\r\n /** Whether it was a cache hit */\r\n 'cache.hit'?: boolean;\r\n /** Size of cached item in bytes */\r\n 'cache.size_bytes'?: number;\r\n /** Cache operation type */\r\n 'cache.operation'?: 'get' | 'set' | 'delete';\r\n}\r\n\r\n/**\r\n * Combined span attributes type\r\n */\r\nexport type SpanAttributes =\r\n | ModelSpanAttributes\r\n | InferenceSpanAttributes\r\n | CacheSpanAttributes\r\n | Record<string, string | number | boolean | undefined>;\r\n\r\n/**\r\n * Metric names used by the SDK\r\n */\r\nexport const MetricNames = {\r\n // --- Inference ---\r\n /** Histogram: Inference latency in ms */\r\n INFERENCE_LATENCY: 'omote.inference.latency',\r\n /** Histogram: Model load time in ms */\r\n MODEL_LOAD_TIME: 'omote.model.load_time',\r\n /** Counter: Total inference operations */\r\n INFERENCE_TOTAL: 'omote.inference.total',\r\n /** Counter: Total errors */\r\n ERRORS_TOTAL: 'omote.errors.total',\r\n /** Counter: Cache hits */\r\n CACHE_HITS: 'omote.cache.hits',\r\n /** Counter: Cache misses */\r\n CACHE_MISSES: 'omote.cache.misses',\r\n /** Counter: Cache stale (version/etag mismatch) */\r\n CACHE_STALE: 'omote.cache.stale',\r\n /** Counter: Cache quota warning (>90% used) */\r\n CACHE_QUOTA_WARNING: 'omote.cache.quota_warning',\r\n /** Counter: Cache eviction (LRU) */\r\n CACHE_EVICTION: 'omote.cache.eviction',\r\n\r\n // --- Pipeline ---\r\n /** Histogram: Voice turn latency (speech end → transcript ready, excludes playback) */\r\n VOICE_TURN_LATENCY: 'omote.voice.turn.latency',\r\n /** Histogram: ASR transcription latency in ms */\r\n VOICE_TRANSCRIPTION_LATENCY: 'omote.voice.transcription.latency',\r\n /** Histogram: Response handler latency in ms */\r\n VOICE_RESPONSE_LATENCY: 'omote.voice.response.latency',\r\n /** Counter: Total transcriptions */\r\n VOICE_TRANSCRIPTIONS: 'omote.voice.transcriptions',\r\n /** Counter: Total interruptions */\r\n VOICE_INTERRUPTIONS: 'omote.voice.interruptions',\r\n\r\n // --- Playback ---\r\n /** Histogram: PlaybackPipeline session duration in ms */\r\n PLAYBACK_SESSION_DURATION: 'omote.playback.session.duration',\r\n /** Histogram: Audio chunk processing latency in ms */\r\n PLAYBACK_CHUNK_LATENCY: 'omote.playback.chunk.latency',\r\n\r\n // --- TTS ---\r\n /** Histogram: TTSSpeaker.connect() latency in ms */\r\n TTS_CONNECT_LATENCY: 'omote.tts.connect.latency',\r\n /** Histogram: TTSSpeaker.speak() latency in ms */\r\n TTS_SPEAK_LATENCY: 'omote.tts.speak.latency',\r\n /** Counter: TTSSpeaker.stop() aborted speak calls */\r\n TTS_SPEAK_ABORTED: 'omote.tts.speak.aborted',\r\n\r\n // --- Mic ---\r\n /** Counter: MicLipSync sessions started */\r\n MIC_SESSIONS: 'omote.mic.sessions',\r\n\r\n // --- Frame budget ---\r\n /** Histogram: CharacterController.update() latency in µs */\r\n AVATAR_FRAME_LATENCY: 'omote.avatar.frame.latency_us',\r\n /** Histogram: FaceCompositor.compose() latency in µs */\r\n COMPOSITOR_COMPOSE_LATENCY: 'omote.compositor.compose.latency_us',\r\n /** Counter: Frames exceeding budget threshold */\r\n AVATAR_FRAME_DROPS: 'omote.avatar.frame.drops',\r\n\r\n // --- Audio scheduling ---\r\n /** Counter: Audio scheduling gaps (playback fell behind) */\r\n AUDIO_SCHEDULE_GAP: 'omote.audio.schedule_gap',\r\n} as const;\r\n\r\n\r\n/**\r\n * Histogram buckets for inference latency (ms)\r\n */\r\nexport const INFERENCE_LATENCY_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000];\r\n\r\n/**\r\n * Histogram buckets for model load time (ms)\r\n */\r\nexport const MODEL_LOAD_TIME_BUCKETS = [100, 500, 1000, 2500, 5000, 10000, 30000, 60000];\r\n","/**\r\n * Microphone capture - renderer-agnostic audio input\r\n *\r\n * Captures audio from the microphone and emits PCM chunks.\r\n * Works in any JavaScript environment with Web Audio API.\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { EventEmitter, type OmoteEvents } from '../events';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\n\r\nconst logger = createLogger('MicrophoneCapture');\r\n\r\nexport interface MicrophoneCaptureConfig {\r\n /** Target sample rate (default: 16000 for speech processing) */\r\n sampleRate?: number;\r\n /** Chunk size in samples (default: 1600 = 100ms at 16kHz) */\r\n chunkSize?: number;\r\n}\r\n\r\nexport class MicrophoneCapture {\r\n private config: Required<MicrophoneCaptureConfig>;\r\n private stream: MediaStream | null = null;\r\n private context: AudioContext | null = null;\r\n private processor: ScriptProcessorNode | null = null;\r\n private buffer: Float32Array = new Float32Array(0);\r\n private _isRecording = false;\r\n private _loggedFirstChunk = false;\r\n /** Actual AudioContext sample rate (may differ from target on Firefox) */\r\n private _nativeSampleRate = 0;\r\n\r\n constructor(\r\n private events: EventEmitter<OmoteEvents>,\r\n config: MicrophoneCaptureConfig = {}\r\n ) {\r\n this.config = {\r\n sampleRate: config.sampleRate ?? 16000,\r\n chunkSize: config.chunkSize ?? 1600,\r\n };\r\n }\r\n\r\n get isRecording(): boolean {\r\n return this._isRecording;\r\n }\r\n\r\n get isSupported(): boolean {\r\n return typeof navigator !== 'undefined' && !!navigator.mediaDevices?.getUserMedia;\r\n }\r\n\r\n async start(): Promise<void> {\r\n if (!this.isSupported) {\r\n logger.error('Microphone not supported in this browser', {\r\n code: 'MICROPHONE_NOT_SUPPORTED',\r\n });\r\n this.events.emit('error', {\r\n code: 'MICROPHONE_NOT_SUPPORTED',\r\n message: 'Microphone not supported in this browser',\r\n });\r\n return;\r\n }\r\n\r\n if (this._isRecording) return;\r\n\r\n const span = getTelemetry()?.startSpan('MicrophoneCapture.start');\r\n\r\n try {\r\n this.stream = await navigator.mediaDevices.getUserMedia({\r\n audio: {\r\n sampleRate: { ideal: this.config.sampleRate },\r\n channelCount: 1,\r\n echoCancellation: true,\r\n noiseSuppression: true,\r\n autoGainControl: true,\r\n },\r\n });\r\n\r\n // Create AudioContext and connect mic stream.\r\n // Firefox throws NotSupportedError when connecting a MediaStreamSource\r\n // to an AudioContext with a different sample rate than the stream's native\r\n // rate. Fall back to native rate + JS resampling when this happens.\r\n this.context = new AudioContext({ sampleRate: this.config.sampleRate });\r\n\r\n // Resume AudioContext if suspended (browser autoplay policy)\r\n if (this.context.state === 'suspended') {\r\n logger.debug('Resuming suspended AudioContext (autoplay policy)');\r\n await this.context.resume();\r\n }\r\n\r\n let source: MediaStreamAudioSourceNode;\r\n try {\r\n source = this.context.createMediaStreamSource(this.stream);\r\n this._nativeSampleRate = this.context.sampleRate;\r\n } catch (sourceErr) {\r\n // Firefox: \"Connecting AudioNodes from AudioContexts with different\r\n // sample-rate is currently not supported\"\r\n logger.warn('Cannot connect stream at target rate, falling back to native rate', {\r\n targetRate: this.config.sampleRate,\r\n error: (sourceErr as Error).message,\r\n });\r\n await this.context.close();\r\n this.context = new AudioContext(); // native rate (typically 48kHz)\r\n if (this.context.state === 'suspended') {\r\n logger.debug('Resuming suspended AudioContext after native rate fallback');\r\n await this.context.resume();\r\n }\r\n source = this.context.createMediaStreamSource(this.stream);\r\n this._nativeSampleRate = this.context.sampleRate;\r\n logger.debug('Using native rate with JS resampling', {\r\n nativeRate: this._nativeSampleRate,\r\n targetRate: this.config.sampleRate,\r\n });\r\n }\r\n\r\n // Use ScriptProcessor for broad compatibility\r\n this.processor = this.context.createScriptProcessor(4096, 1, 1);\r\n\r\n this.processor.onaudioprocess = (e) => {\r\n const raw = e.inputBuffer.getChannelData(0);\r\n\r\n // Resample if AudioContext is running at a different rate than target\r\n const input = this._nativeSampleRate !== this.config.sampleRate\r\n ? this.resample(raw, this._nativeSampleRate, this.config.sampleRate)\r\n : raw;\r\n\r\n // Calculate audio level\r\n let rms = 0;\r\n let peak = 0;\r\n for (let i = 0; i < input.length; i++) {\r\n const abs = Math.abs(input[i]);\r\n rms += input[i] * input[i];\r\n if (abs > peak) peak = abs;\r\n }\r\n rms = Math.sqrt(rms / input.length);\r\n\r\n this.events.emit('audio.level', { rms, peak });\r\n\r\n // Accumulate samples\r\n const newBuffer = new Float32Array(this.buffer.length + input.length);\r\n newBuffer.set(this.buffer);\r\n newBuffer.set(input, this.buffer.length);\r\n this.buffer = newBuffer;\r\n\r\n // Emit chunks\r\n let chunkCount = 0;\r\n while (this.buffer.length >= this.config.chunkSize) {\r\n const chunk = this.buffer.slice(0, this.config.chunkSize);\r\n this.buffer = this.buffer.slice(this.config.chunkSize);\r\n\r\n const pcm = this.floatToPCM16(chunk);\r\n this.events.emit('audio.chunk', {\r\n pcm,\r\n timestamp: getClock().now(),\r\n });\r\n chunkCount++;\r\n }\r\n // Log first emission for debugging\r\n if (chunkCount > 0 && !this._loggedFirstChunk) {\r\n logger.debug('First audio chunks emitted', { chunkCount });\r\n this._loggedFirstChunk = true;\r\n }\r\n };\r\n\r\n source.connect(this.processor);\r\n this.processor.connect(this.context.destination);\r\n\r\n this._isRecording = true;\r\n getTelemetry()?.incrementCounter(MetricNames.MIC_SESSIONS);\r\n span?.end();\r\n logger.info('Started recording', {\r\n contextState: this.context.state,\r\n sampleRate: this.config.sampleRate,\r\n nativeSampleRate: this._nativeSampleRate,\r\n chunkSize: this.config.chunkSize,\r\n });\r\n } catch (err) {\r\n const domErr = err as DOMException;\r\n const isPermissionDenied = domErr.name === 'NotAllowedError' || domErr.name === 'PermissionDeniedError';\r\n const code = isPermissionDenied ? 'MICROPHONE_PERMISSION_DENIED' : 'MICROPHONE_ERROR';\r\n\r\n logger.error('Failed to start microphone', {\r\n code,\r\n errorName: domErr.name,\r\n message: domErr.message,\r\n });\r\n\r\n this.events.emit('error', {\r\n code: 'MICROPHONE_ERROR',\r\n message: (err as Error).message,\r\n details: err,\r\n });\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n }\r\n }\r\n\r\n stop(): void {\r\n if (this.processor) {\r\n this.processor.disconnect();\r\n this.processor = null;\r\n }\r\n\r\n if (this.context) {\r\n this.context.close();\r\n this.context = null;\r\n }\r\n\r\n if (this.stream) {\r\n this.stream.getTracks().forEach((t) => t.stop());\r\n this.stream = null;\r\n }\r\n\r\n this.buffer = new Float32Array(0);\r\n this._isRecording = false;\r\n logger.info('Stopped recording');\r\n }\r\n\r\n /**\r\n * Resample audio using linear interpolation.\r\n * Used when the AudioContext runs at the device's native rate (e.g. 48kHz)\r\n * and we need to downsample to the target rate (e.g. 16kHz).\r\n */\r\n private resample(input: Float32Array, fromRate: number, toRate: number): Float32Array {\r\n if (fromRate === toRate) return input;\r\n const ratio = fromRate / toRate;\r\n const outputLength = Math.floor(input.length / ratio);\r\n const output = new Float32Array(outputLength);\r\n for (let i = 0; i < outputLength; i++) {\r\n const srcIdx = i * ratio;\r\n const lo = Math.floor(srcIdx);\r\n const hi = Math.min(lo + 1, input.length - 1);\r\n const frac = srcIdx - lo;\r\n output[i] = input[lo] * (1 - frac) + input[hi] * frac;\r\n }\r\n return output;\r\n }\r\n\r\n private floatToPCM16(float32: Float32Array): Int16Array {\r\n const pcm = new Int16Array(float32.length);\r\n for (let i = 0; i < float32.length; i++) {\r\n const s = Math.max(-1, Math.min(1, float32[i]));\r\n pcm[i] = s < 0 ? s * 0x8000 : s * 0x7fff;\r\n }\r\n return pcm;\r\n }\r\n}\r\n","/**\n * Ring buffer for audio sample accumulation\n *\n * Efficiently accumulates audio samples and provides\n * contiguous buffers for inference without memory allocation churn.\n *\n * @category Audio\n */\n\nexport class RingBuffer {\n private buffer: Float32Array;\n private writeIndex = 0;\n private isFull = false;\n\n constructor(private readonly size: number) {\n this.buffer = new Float32Array(size);\n }\n\n /**\n * Write samples to the ring buffer\n * Converts Int16Array PCM to Float32\n */\n write(pcm: Int16Array): void {\n for (let i = 0; i < pcm.length; i++) {\n this.buffer[this.writeIndex] = pcm[i] / 32768.0;\n this.writeIndex = (this.writeIndex + 1) % this.size;\n\n if (this.writeIndex === 0) {\n this.isFull = true;\n }\n }\n }\n\n /**\n * Write float samples directly\n */\n writeFloat(samples: Float32Array): void {\n for (let i = 0; i < samples.length; i++) {\n this.buffer[this.writeIndex] = samples[i];\n this.writeIndex = (this.writeIndex + 1) % this.size;\n\n if (this.writeIndex === 0) {\n this.isFull = true;\n }\n }\n }\n\n /**\n * Get a contiguous copy of the buffer contents in chronological order\n * Returns null if buffer isn't full yet\n */\n read(): Float32Array | null {\n if (!this.isFull) return null;\n\n const output = new Float32Array(this.size);\n\n // Copy from writeIndex to end (oldest samples)\n const firstPart = this.buffer.subarray(this.writeIndex);\n output.set(firstPart, 0);\n\n // Copy from 0 to writeIndex (newest samples)\n const secondPart = this.buffer.subarray(0, this.writeIndex);\n output.set(secondPart, firstPart.length);\n\n return output;\n }\n\n /**\n * Check if buffer has enough samples\n */\n get hasData(): boolean {\n return this.isFull;\n }\n\n /**\n * Get current fill level (0-1)\n */\n get fillLevel(): number {\n if (this.isFull) return 1;\n return this.writeIndex / this.size;\n }\n\n /**\n * Reset the buffer\n */\n reset(): void {\n this.buffer.fill(0);\n this.writeIndex = 0;\n this.isFull = false;\n }\n}\n","/**\r\n * AudioScheduler - Enterprise-grade Web Audio API scheduling\r\n *\r\n * Implements the lookahead scheduling pattern from Chris Wilson's\r\n * \"A Tale of Two Clocks\" - the authoritative guide on Web Audio timing.\r\n *\r\n * Key Features:\r\n * - Uses AudioContext.currentTime (hardware clock) for sample-accurate timing\r\n * - Pre-schedules audio chunks for gapless playback\r\n * - Tracks scheduled sources for cleanup\r\n * - Provides playback state monitoring\r\n *\r\n * @see https://web.dev/articles/audio-scheduling\r\n * @category Audio\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { ErrorCodes } from '../logging/ErrorCodes';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\n\r\nconst logger = createLogger('AudioScheduler');\r\n\r\nexport interface AudioSchedulerOptions {\r\n /** Sample rate in Hz (default: 16000 for speech) */\r\n sampleRate?: number\r\n /** Number of audio channels (default: 1 for mono) */\r\n channels?: number\r\n /**\r\n * Initial lookahead delay in seconds before first audio plays.\r\n * Gives LAM inference time to compute blendshapes before audio starts.\r\n * Default: 0.05 (50ms) for WebGPU, increase to 0.3-0.5 for WASM on iOS.\r\n */\r\n initialLookaheadSec?: number\r\n /** Error callback for critical scheduling issues */\r\n onError?: (error: Error) => void\r\n}\r\n\r\nexport class AudioScheduler {\r\n private context: AudioContext | null = null\r\n private nextPlayTime = 0\r\n private scheduledSources: Array<{ source: AudioBufferSourceNode; gainNode: GainNode }> = []\r\n private isPlaying = false\r\n\r\n constructor(private readonly options: AudioSchedulerOptions = {}) {}\r\n\r\n /** Configured sample rate (default: 16000). */\r\n get sampleRate(): number {\r\n return this.options.sampleRate ?? 16000\r\n }\r\n\r\n /**\r\n * Initialize AudioContext with specified sample rate\r\n *\r\n * Note: This is now a no-op. AudioContext is created lazily on first schedule()\r\n * to avoid browser autoplay policy issues (requires user gesture).\r\n */\r\n async initialize(): Promise<void> {\r\n // No-op - context will be created lazily in ensureContext()\r\n logger.debug('Ready for lazy initialization')\r\n }\r\n\r\n /**\r\n * Eagerly create and warm up the AudioContext\r\n *\r\n * Call this when a playback session starts (e.g., when AI response begins).\r\n * The AudioContext needs time to initialize the audio hardware — on Windows\r\n * this can take 50-100ms. By warming up early (before audio data arrives),\r\n * the context is fully ready when schedule() is first called.\r\n *\r\n * Must be called after a user gesture (click/tap) for autoplay policy.\r\n */\r\n async warmup(): Promise<void> {\r\n await this.ensureContext()\r\n }\r\n\r\n /**\r\n * Ensure AudioContext is created and ready\r\n * Called lazily on first schedule() - requires user gesture\r\n */\r\n private async ensureContext(): Promise<AudioContext> {\r\n if (this.context && this.context.state !== 'closed') {\r\n return this.context\r\n }\r\n\r\n const sampleRate = this.options.sampleRate ?? 16000\r\n this.context = new AudioContext({ sampleRate })\r\n\r\n // Resume if suspended (browser autoplay policy)\r\n if (this.context.state === 'suspended') {\r\n await this.context.resume()\r\n logger.debug('AudioContext resumed from suspended state')\r\n }\r\n\r\n logger.info('AudioContext initialized', { sampleRate, state: this.context.state })\r\n return this.context\r\n }\r\n\r\n /**\r\n * Schedule an audio chunk for playback\r\n *\r\n * Uses Web Audio's hardware-accurate clock for sample-perfect timing.\r\n * Chunks are scheduled immediately, not when they should play - this\r\n * ensures gapless playback even if main thread stalls.\r\n *\r\n * @param audioData - Float32Array of audio samples\r\n * @returns Scheduled playback time in AudioContext seconds\r\n */\r\n async schedule(audioData: Float32Array): Promise<number> {\r\n // Lazy initialization (requires user gesture)\r\n const ctx = await this.ensureContext()\r\n const channels = this.options.channels ?? 1\r\n\r\n // Initialize playback timing on first chunk\r\n // Lookahead gives AudioContext time to initialize and LAM time to pre-compute.\r\n // 50ms default is fine for WebGPU; WASM on iOS needs 300-500ms.\r\n if (!this.isPlaying) {\r\n const lookahead = this.options.initialLookaheadSec ?? 0.05\r\n this.nextPlayTime = ctx.currentTime + lookahead\r\n this.isPlaying = true\r\n logger.debug('First chunk scheduled', { lookaheadSec: lookahead, currentTime: ctx.currentTime })\r\n }\r\n\r\n // Create audio buffer\r\n const audioBuffer = ctx.createBuffer(channels, audioData.length, ctx.sampleRate)\r\n audioBuffer.getChannelData(0).set(audioData)\r\n\r\n // Create gain node for fade control\r\n const gainNode = ctx.createGain()\r\n gainNode.gain.value = 1.0\r\n gainNode.connect(ctx.destination)\r\n\r\n // Create and schedule source\r\n const source = ctx.createBufferSource()\r\n source.buffer = audioBuffer\r\n source.connect(gainNode) // Route through gain node for fade control\r\n\r\n // Schedule at precise time for gapless playback\r\n const scheduleTime = this.nextPlayTime\r\n\r\n // Warn if playback has fallen behind (audio gap)\r\n if (scheduleTime < ctx.currentTime) {\r\n const gap = ctx.currentTime - scheduleTime\r\n const gapMs = gap * 1000\r\n getTelemetry()?.incrementCounter(MetricNames.AUDIO_SCHEDULE_GAP, 1, { gap_ms: Math.round(gapMs) })\r\n if (gap > 0.5) {\r\n logger.error('Critical audio scheduling gap', {\r\n code: ErrorCodes.AUD_SCHEDULE_GAP,\r\n scheduleTime,\r\n currentTime: ctx.currentTime,\r\n gapMs: Math.round(gapMs),\r\n })\r\n this.options.onError?.(new Error(`Audio scheduling gap: ${gap.toFixed(3)}s`))\r\n } else {\r\n logger.warn('Audio gap detected', {\r\n scheduleTime,\r\n currentTime: ctx.currentTime,\r\n gapMs: Math.round(gapMs),\r\n })\r\n }\r\n }\r\n\r\n source.start(scheduleTime)\r\n\r\n // Track scheduled source with its gain node\r\n const entry = { source, gainNode }\r\n this.scheduledSources.push(entry)\r\n\r\n // Clean up when playback completes naturally to prevent unbounded growth\r\n source.onended = () => {\r\n const idx = this.scheduledSources.indexOf(entry)\r\n if (idx !== -1) {\r\n this.scheduledSources.splice(idx, 1)\r\n }\r\n }\r\n\r\n // Update next play time\r\n const duration = audioData.length / ctx.sampleRate\r\n this.nextPlayTime = scheduleTime + duration\r\n\r\n logger.trace('Chunk scheduled', {\r\n scheduleTime,\r\n durationSec: duration,\r\n samples: audioData.length,\r\n pendingSources: this.scheduledSources.length,\r\n })\r\n\r\n return scheduleTime\r\n }\r\n\r\n /**\r\n * Get current audio clock time\r\n *\r\n * This is the hardware-accurate time, NOT JavaScript time.\r\n * Use this for synchronizing visual animations to audio.\r\n *\r\n * @returns Current time in AudioContext seconds\r\n */\r\n getCurrentTime(): number {\r\n if (!this.context) return 0\r\n return this.context.currentTime\r\n }\r\n\r\n /**\r\n * Get scheduled playback end time\r\n */\r\n getPlaybackEndTime(): number {\r\n return this.nextPlayTime\r\n }\r\n\r\n /**\r\n * Check if all scheduled audio has finished playing\r\n */\r\n isComplete(): boolean {\r\n if (!this.context || !this.isPlaying) return false\r\n return this.context.currentTime >= this.nextPlayTime\r\n }\r\n\r\n /**\r\n * Cancel all scheduled audio with smooth fade-out\r\n *\r\n * Applies a linear fade-out to all playing sources and stops them gracefully.\r\n * Prevents audio clicks/pops by ramping gain to zero before stopping.\r\n *\r\n * @param fadeOutMs - Fade-out duration in milliseconds (default: 50ms)\r\n * @returns Promise that resolves when fade-out completes\r\n */\r\n async cancelAll(fadeOutMs: number = 50): Promise<void> {\r\n if (!this.context || this.scheduledSources.length === 0) {\r\n return\r\n }\r\n\r\n logger.debug('cancelAll', { fadeOutMs, pendingSources: this.scheduledSources.length })\r\n\r\n const ctx = this.context\r\n const currentTime = ctx.currentTime\r\n const fadeOutSec = fadeOutMs / 1000\r\n\r\n // Apply fade-out to all scheduled sources\r\n for (const { source, gainNode } of this.scheduledSources) {\r\n try {\r\n // Ramp gain from current value to zero\r\n gainNode.gain.setValueAtTime(gainNode.gain.value, currentTime)\r\n gainNode.gain.linearRampToValueAtTime(0.0, currentTime + fadeOutSec)\r\n\r\n // Stop source after fade completes\r\n source.stop(currentTime + fadeOutSec)\r\n } catch (err) {\r\n // Source may have already stopped naturally - ignore error\r\n }\r\n }\r\n\r\n // Clear tracking arrays\r\n this.scheduledSources = []\r\n this.isPlaying = false\r\n this.nextPlayTime = 0\r\n\r\n // Wait for fade-out to complete\r\n await new Promise(resolve => setTimeout(resolve, fadeOutMs))\r\n }\r\n\r\n /**\r\n * Reset scheduler state for new playback session\r\n * Stops any orphaned sources that weren't cleaned up by cancelAll()\r\n */\r\n reset(): void {\r\n logger.debug('reset', { pendingSources: this.scheduledSources.length })\r\n // Stop any still-playing sources before clearing\r\n if (this.context) {\r\n const now = this.context.currentTime\r\n for (const { source, gainNode } of this.scheduledSources) {\r\n try {\r\n gainNode.gain.setValueAtTime(0, now)\r\n source.stop(now)\r\n } catch {\r\n // Already stopped\r\n }\r\n }\r\n }\r\n this.nextPlayTime = 0\r\n this.isPlaying = false\r\n this.scheduledSources = []\r\n }\r\n\r\n /**\r\n * Cleanup resources\r\n */\r\n dispose(): void {\r\n logger.debug('dispose')\r\n this.reset() // sync — stops all sources immediately\r\n if (this.context) {\r\n this.context.close()\r\n this.context = null\r\n }\r\n this.scheduledSources = []\r\n this.isPlaying = false\r\n }\r\n}\r\n","/**\r\n * AudioChunkCoalescer - Combine small network chunks into optimal buffers\r\n *\r\n * Network streaming often delivers audio in small chunks (e.g., 32ms from TTS APIs).\r\n * Creating an AudioBufferSourceNode for each tiny chunk is inefficient and can cause\r\n * overhead from object creation/GC.\r\n *\r\n * This class implements a double-buffering pattern: accumulate small chunks in a\r\n * temporary buffer, then flush to playback queue when threshold is reached.\r\n *\r\n * Benefits:\r\n * - Reduces AudioBufferSourceNode overhead (fewer nodes = less GC pressure)\r\n * - Configurable buffer size for optimal playback chunk duration\r\n * - Maintains sample-accurate timing despite buffering\r\n *\r\n * Based on patterns from HLS.js and production streaming implementations.\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('AudioChunkCoalescer');\r\n\r\nexport interface AudioChunkCoalescerOptions {\r\n /**\r\n * Target duration in milliseconds for combined chunks\r\n * Default: 200ms (balances latency vs overhead)\r\n *\r\n * Smaller values = lower latency, more overhead\r\n * Larger values = higher latency, less overhead\r\n */\r\n targetDurationMs?: number\r\n\r\n /**\r\n * Sample rate in Hz\r\n * Default: 16000 (speech quality)\r\n */\r\n sampleRate?: number\r\n}\r\n\r\nexport class AudioChunkCoalescer {\r\n private tempBuffer: Uint8Array[] = []\r\n private readonly targetBytes: number\r\n\r\n constructor(private readonly options: AudioChunkCoalescerOptions = {}) {\r\n const targetMs = options.targetDurationMs ?? 200\r\n const sampleRate = options.sampleRate ?? 16000\r\n\r\n // Calculate target bytes: (duration_s) * (samples/s) * (2 bytes per Int16 sample)\r\n this.targetBytes = (targetMs / 1000) * sampleRate * 2\r\n\r\n logger.debug('Constructed', { sampleRate, targetDurationMs: targetMs, targetBytes: this.targetBytes })\r\n }\r\n\r\n /**\r\n * Add a chunk to the temporary buffer\r\n *\r\n * @param chunk - Uint8Array containing Int16 PCM audio\r\n * @returns Combined buffer if threshold reached, null otherwise\r\n */\r\n add(chunk: Uint8Array): ArrayBuffer | null {\r\n // Add to temporary buffer\r\n this.tempBuffer.push(chunk)\r\n\r\n // Calculate total bytes buffered\r\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\r\n\r\n // If we've reached the threshold, combine and return\r\n if (totalBytes >= this.targetBytes) {\r\n logger.debug('Threshold flush', { bufferSize: totalBytes, targetSize: this.targetBytes, chunks: this.tempBuffer.length })\r\n return this.flush()\r\n }\r\n\r\n return null\r\n }\r\n\r\n /**\r\n * Flush remaining buffered data\r\n *\r\n * Call this when the stream ends to ensure all audio is processed,\r\n * even if it doesn't reach the target threshold.\r\n *\r\n * @returns Combined buffer, or null if buffer is empty\r\n */\r\n flush(): ArrayBuffer | null {\r\n if (this.tempBuffer.length === 0) {\r\n return null\r\n }\r\n\r\n // Calculate total size\r\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\r\n\r\n logger.debug('Explicit flush', { bufferSize: totalBytes, chunks: this.tempBuffer.length })\r\n\r\n // Combine all chunks into single buffer\r\n const combined = new Uint8Array(totalBytes)\r\n let offset = 0\r\n for (const chunk of this.tempBuffer) {\r\n combined.set(chunk, offset)\r\n offset += chunk.length\r\n }\r\n\r\n // Clear temp buffer\r\n this.tempBuffer = []\r\n\r\n return combined.buffer\r\n }\r\n\r\n /**\r\n * Get current buffer fill level (0-1)\r\n */\r\n get fillLevel(): number {\r\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\r\n return Math.min(1, totalBytes / this.targetBytes)\r\n }\r\n\r\n /**\r\n * Get current buffered duration in milliseconds\r\n */\r\n getBufferedDurationMs(): number {\r\n const sampleRate = this.options.sampleRate ?? 16000\r\n const totalBytes = this.tempBuffer.reduce((sum, c) => sum + c.length, 0)\r\n const samples = totalBytes / 2 // Int16 = 2 bytes per sample\r\n return (samples / sampleRate) * 1000\r\n }\r\n\r\n /**\r\n * Get number of chunks currently buffered\r\n */\r\n get chunkCount(): number {\r\n return this.tempBuffer.length\r\n }\r\n\r\n /**\r\n * Reset the coalescer\r\n */\r\n reset(): void {\r\n this.tempBuffer = []\r\n }\r\n}\r\n","/**\n * BlendshapeSmoother — Per-channel critically damped spring for 52 ARKit blendshapes\n *\n * Eliminates frame gaps between inference batches by smoothly interpolating\n * blendshape weights using critically damped springs (the game industry standard).\n *\n * Each of the 52 blendshape channels has its own spring with position + velocity\n * state. When a new inference frame arrives, spring targets are updated. Between\n * frames, springs continue converging toward the last target — no frozen face.\n *\n * When inference stalls, `decayToNeutral()` sets all targets to 0, and the\n * springs smoothly close the mouth / relax the face over the halflife period.\n *\n * Math from Daniel Holden's \"Spring-It-On\" (Epic Games):\n * https://theorangeduck.com/page/spring-roll-call\n *\n * @category Inference\n *\n * @example Basic usage\n * ```typescript\n * const smoother = new BlendshapeSmoother({ halflife: 0.06 });\n *\n * // In frame loop (60fps):\n * smoother.setTarget(inferenceFrame); // when new frame arrives\n * const smoothed = smoother.update(1/60); // every render frame\n * applyToAvatar(smoothed);\n * ```\n */\n\nconst NUM_BLENDSHAPES = 52;\n\nexport interface BlendshapeSmootherConfig {\n /**\n * Spring halflife in seconds — time for the distance to the target\n * to reduce by half. Lower = snappier, higher = smoother.\n *\n * - 0.04s (40ms): Very snappy, slight jitter on fast transitions\n * - 0.06s (60ms): Sweet spot for lip sync (default)\n * - 0.10s (100ms): Very smooth, slight lag on fast consonants\n * - 0: Bypass mode — passes through raw target values (no smoothing)\n *\n * Default: 0.06\n */\n halflife?: number;\n}\n\nexport class BlendshapeSmoother {\n private readonly halflife: number;\n\n /** Current smoothed blendshape values */\n private values: Float32Array;\n /** Per-channel spring velocities */\n private velocities: Float32Array;\n /** Current spring targets (from latest inference frame) */\n private targets: Float32Array;\n /** Whether any target has been set */\n private _hasTarget = false;\n\n constructor(config?: BlendshapeSmootherConfig) {\n this.halflife = config?.halflife ?? 0.06;\n this.values = new Float32Array(NUM_BLENDSHAPES);\n this.velocities = new Float32Array(NUM_BLENDSHAPES);\n this.targets = new Float32Array(NUM_BLENDSHAPES);\n }\n\n /** Whether a target frame has been set (false until first setTarget call) */\n get hasTarget(): boolean {\n return this._hasTarget;\n }\n\n /**\n * Set new target frame from inference output.\n * Springs will converge toward these values on subsequent update() calls.\n */\n setTarget(frame: Float32Array): void {\n this.targets.set(frame);\n this._hasTarget = true;\n }\n\n /**\n * Snap current position to a frame without triggering spring physics.\n * Zeroes velocities so the spring starts from rest at this position.\n *\n * Used by A2EProcessor to seed the smoother when entering gap decay —\n * positions the spring at the last real inference frame before decaying\n * toward neutral.\n */\n setPosition(frame: Float32Array): void {\n this.values.set(frame);\n this.velocities.fill(0);\n this._hasTarget = true;\n }\n\n /**\n * Advance all 52 springs by `dt` seconds and return the smoothed frame.\n *\n * Call this every render frame (e.g., inside requestAnimationFrame).\n * Returns the internal values buffer — do NOT mutate the returned array.\n *\n * @param dt - Time step in seconds (e.g., 1/60 for 60fps)\n * @returns Smoothed blendshape values (Float32Array of 52)\n */\n update(dt: number): Float32Array {\n if (!this._hasTarget) {\n return this.values;\n }\n\n // Bypass mode: no smoothing, snap to target\n if (this.halflife <= 0) {\n this.values.set(this.targets);\n this.velocities.fill(0);\n return this.values;\n }\n\n // Critically damped spring, exact solution per channel\n // From Daniel Holden's Spring-It-On (Epic Games):\n // https://theorangeduck.com/page/spring-roll-call\n const damping = Math.LN2 / this.halflife;\n const eydt = Math.exp(-damping * dt);\n\n for (let i = 0; i < NUM_BLENDSHAPES; i++) {\n const j0 = this.values[i] - this.targets[i];\n const j1 = this.velocities[i] + j0 * damping;\n\n this.values[i] = eydt * (j0 + j1 * dt) + this.targets[i];\n this.velocities[i] = eydt * (this.velocities[i] - j1 * damping * dt);\n\n // Clamp to valid blendshape range\n this.values[i] = Math.max(0, Math.min(1, this.values[i]));\n }\n\n return this.values;\n }\n\n /**\n * Decay all spring targets to neutral (0).\n *\n * Call when inference stalls (no new frames for threshold duration).\n * The springs will smoothly close the mouth / relax the face over\n * the halflife period rather than freezing.\n */\n decayToNeutral(): void {\n this.targets.fill(0);\n }\n\n /**\n * Reset all state (values, velocities, targets).\n * Call when starting a new playback session.\n */\n reset(): void {\n this.values.fill(0);\n this.velocities.fill(0);\n this.targets.fill(0);\n this._hasTarget = false;\n }\n}\n","/**\r\n * A2EProcessor — Engine-agnostic audio-to-expression processor\r\n *\r\n * The core inference primitive: audio samples in → blendshape frames out.\r\n * No mic capture, no audio playback, no Web Audio API.\r\n *\r\n * This is what Unity/Unreal/Godot/any engine would use directly.\r\n * Web-specific concerns (mic, AudioContext, scheduling) live in the\r\n * orchestrator and pipeline layers above.\r\n *\r\n * Two output modes:\r\n * - **Pull mode**: `pushAudio(samples, timestamp)` + `getFrameForTime(t)`\r\n * For TTS playback where frames are synced to AudioContext clock.\r\n * - **Push mode**: `pushAudio(samples)` + `startDrip()` + `latestFrame`\r\n * For live mic / game loop where frames are consumed at ~30fps.\r\n *\r\n * @category Inference\r\n *\r\n * @example Pull mode (TTS playback)\r\n * ```typescript\r\n * const processor = new A2EProcessor({ backend: a2e });\r\n * processor.pushAudio(samples, audioContext.currentTime + delay);\r\n * const frame = processor.getFrameForTime(audioContext.currentTime);\r\n * ```\r\n *\r\n * @example Push mode (live mic)\r\n * ```typescript\r\n * const processor = new A2EProcessor({\r\n * backend: a2e,\r\n * onFrame: (frame) => applyToAvatar(frame),\r\n * });\r\n * processor.startDrip();\r\n * processor.pushAudio(micSamples); // no timestamp → drip mode\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { ErrorCodes } from '../logging/ErrorCodes';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\nimport type { A2EBackend } from './A2EBackend';\r\nimport { BlendshapeSmoother } from './BlendshapeSmoother';\r\n\r\nconst logger = createLogger('A2EProcessor');\r\n\r\nexport interface A2EProcessorConfig {\r\n /** Inference backend */\r\n backend: A2EBackend;\r\n /** Sample rate (default: 16000) */\r\n sampleRate?: number;\r\n /** Samples per inference chunk (default: 16000 = 1s) */\r\n chunkSize?: number;\r\n /**\r\n * Identity/style index for the A2E model (default: 0).\r\n *\r\n * The LAM model uses a one-hot identity vector (12 classes, indices 0-11) as\r\n * style conditioning alongside audio features. Different indices produce\r\n * different expression intensity across face regions (brows, eyes, cheeks).\r\n */\r\n identityIndex?: number;\r\n /** Callback fired with each blendshape frame (push mode) */\r\n onFrame?: (frame: Float32Array) => void;\r\n /** Error callback */\r\n onError?: (error: Error) => void;\r\n}\r\n\r\ninterface TimestampedFrame {\r\n frame: Float32Array;\r\n timestamp: number;\r\n}\r\n\r\nconst FRAME_RATE = 30;\r\nconst DRIP_INTERVAL_MS = 33; // ~30fps\r\n\r\n// Frame hold + spring decay: bridges gaps between inference chunks during cloud TTS streaming.\r\n// Cloud TTS sentence gaps are typically 200-500ms. A 400ms hold bridges virtually all\r\n// gaps without visible decay. After hold expires, spring decays to neutral (~0.8s to threshold).\r\n// Combined hold+decay ~1.2s total. End-of-speech handled by PlaybackPipeline's neutral transition.\r\nconst HOLD_DURATION_MS = 400; // hold last frame 400ms before decay starts\r\nconst GAP_DECAY_HALFLIFE_S = 0.08; // spring halflife for gap decay (~0.8s to null threshold)\r\n\r\nexport class A2EProcessor {\r\n private static readonly MAX_PENDING_CHUNKS = 10;\r\n\r\n private readonly backend: A2EBackend;\r\n private readonly sampleRate: number;\r\n private readonly chunkSize: number;\r\n private readonly identityIndex: number;\r\n private readonly onFrame?: (frame: Float32Array) => void;\r\n private readonly onError?: (error: Error) => void;\r\n\r\n // Audio buffer accumulation (pre-allocated, zero-alloc append)\r\n private bufferCapacity: number;\r\n private buffer: Float32Array;\r\n private writeOffset = 0;\r\n private bufferStartTime = 0;\r\n\r\n // Frame queues (timestamped for pull mode, plain for drip mode)\r\n private timestampedQueue: TimestampedFrame[] = [];\r\n private plainQueue: Float32Array[] = [];\r\n\r\n // Push mode state\r\n private _latestFrame: Float32Array | null = null;\r\n private dripInterval: ReturnType<typeof setInterval> | null = null;\r\n\r\n // Last-frame-hold for pull mode (prevents avatar freezing between frames)\r\n private lastPulledFrame: Float32Array | null = null;\r\n private lastDequeuedTime = 0;\r\n private decayBuffer: Float32Array | null = null;\r\n\r\n // Gap-only spring decay (replaces quadratic decay)\r\n private smoother: BlendshapeSmoother;\r\n private gapDecayStarted = false;\r\n private lastSmootherUpdate = 0;\r\n\r\n // Inference serialization\r\n private inferenceRunning = false;\r\n private pendingChunks: Array<{ chunk: Float32Array; timestamp?: number; actualSamples?: number }> = [];\r\n\r\n // Diagnostic: track getFrameForTime calls\r\n private getFrameCallCount = 0;\r\n\r\n private disposed = false;\r\n\r\n constructor(config: A2EProcessorConfig) {\r\n this.backend = config.backend;\r\n this.sampleRate = config.sampleRate ?? 16000;\r\n this.chunkSize = config.chunkSize ?? config.backend.chunkSize ?? 16000;\r\n this.identityIndex = config.identityIndex ?? 0;\r\n this.onFrame = config.onFrame;\r\n this.onError = config.onError;\r\n\r\n // Pre-allocate audio buffer (2x chunkSize avoids reallocation in normal use)\r\n this.bufferCapacity = this.chunkSize * 2;\r\n this.buffer = new Float32Array(this.bufferCapacity);\r\n\r\n // Spring smoother for gap decay only (not used during active playback)\r\n this.smoother = new BlendshapeSmoother({ halflife: GAP_DECAY_HALFLIFE_S });\r\n }\r\n\r\n // ═══════════════════════════════════════════════════════════════════════\r\n // Audio Input\r\n // ═══════════════════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Push audio samples for inference (any source: mic, TTS, file).\r\n *\r\n * - With `timestamp`: frames stored with timestamps (pull mode)\r\n * - Without `timestamp`: frames stored in plain queue (drip/push mode)\r\n *\r\n * Fire-and-forget: returns immediately, inference runs async.\r\n */\r\n pushAudio(samples: Float32Array, timestamp?: number): void {\r\n if (this.disposed) return;\r\n\r\n // Track buffer start time when empty (for timestamped mode)\r\n if (this.writeOffset === 0 && timestamp !== undefined) {\r\n this.bufferStartTime = timestamp;\r\n }\r\n\r\n // Grow capacity if needed (rare — only if push exceeds remaining space)\r\n if (this.writeOffset + samples.length > this.bufferCapacity) {\r\n this.bufferCapacity = (this.writeOffset + samples.length) * 2;\r\n const grown = new Float32Array(this.bufferCapacity);\r\n grown.set(this.buffer.subarray(0, this.writeOffset));\r\n this.buffer = grown;\r\n }\r\n\r\n // Append samples (O(1) — no copy of existing data)\r\n this.buffer.set(samples, this.writeOffset);\r\n this.writeOffset += samples.length;\r\n\r\n logger.debug('pushAudio', {\r\n samplesIn: samples.length,\r\n writeOffset: this.writeOffset,\r\n chunkSize: this.chunkSize,\r\n willExtract: this.writeOffset >= this.chunkSize,\r\n inferenceRunning: this.inferenceRunning,\r\n pendingChunks: this.pendingChunks.length,\r\n queuedFrames: this.timestampedQueue.length + this.plainQueue.length,\r\n });\r\n\r\n // Slice off complete chunks and queue them for inference\r\n while (this.writeOffset >= this.chunkSize) {\r\n const chunk = this.buffer.slice(0, this.chunkSize);\r\n // Shift remainder to front (in-place memmove, zero-alloc)\r\n this.buffer.copyWithin(0, this.chunkSize, this.writeOffset);\r\n this.writeOffset -= this.chunkSize;\r\n\r\n const chunkTimestamp = timestamp !== undefined ? this.bufferStartTime : undefined;\r\n this.pendingChunks.push({ chunk, timestamp: chunkTimestamp });\r\n\r\n while (this.pendingChunks.length > A2EProcessor.MAX_PENDING_CHUNKS) {\r\n this.pendingChunks.shift();\r\n logger.warn('A2E backpressure: dropped oldest pending chunk', { pending: this.pendingChunks.length });\r\n }\r\n\r\n logger.info('Chunk queued for inference', {\r\n chunkSize: chunk.length,\r\n chunkTimestamp,\r\n pendingChunks: this.pendingChunks.length,\r\n remainderOffset: this.writeOffset,\r\n });\r\n\r\n // Advance buffer start time for next chunk\r\n if (timestamp !== undefined) {\r\n this.bufferStartTime += this.chunkSize / this.sampleRate;\r\n }\r\n }\r\n\r\n // Warn on backpressure (surfaces iOS burst arrivals without changing behavior)\r\n if (this.pendingChunks.length > 3) {\r\n logger.warn('A2E inference backpressure', {\r\n pendingChunks: this.pendingChunks.length,\r\n backend: this.backend.backend,\r\n });\r\n }\r\n\r\n // Kick off inference if not already running\r\n this.drainPendingChunks();\r\n }\r\n\r\n /**\r\n * Flush remaining buffered audio (pads to chunkSize).\r\n * Call at end of stream to process final partial chunk.\r\n *\r\n * Routes through the serialized pendingChunks pipeline to maintain\r\n * correct frame ordering. Without this, flush() could push frames\r\n * with the latest timestamp to the queue before drainPendingChunks()\r\n * finishes pushing frames with earlier timestamps — causing\r\n * getFrameForTime() to see out-of-order timestamps and stall.\r\n */\r\n async flush(): Promise<void> {\r\n if (this.disposed || this.writeOffset === 0) return;\r\n\r\n // Capture actual sample count BEFORE zeroing writeOffset.\r\n // flush() pads to chunkSize with zeros, but only actualSamples\r\n // contain real audio. drainPendingChunks uses this to limit\r\n // queued frames to the real audio duration (not the padded 1s).\r\n const actualSamples = this.writeOffset;\r\n\r\n // Pad to chunkSize\r\n const padded = new Float32Array(this.chunkSize);\r\n padded.set(this.buffer.subarray(0, actualSamples), 0);\r\n\r\n const chunkTimestamp = this.bufferStartTime > 0 ? this.bufferStartTime : undefined;\r\n\r\n logger.info('flush: routing through drain pipeline', {\r\n actualSamples,\r\n chunkTimestamp: chunkTimestamp?.toFixed(3),\r\n pendingChunks: this.pendingChunks.length,\r\n inferenceRunning: this.inferenceRunning,\r\n });\r\n\r\n this.writeOffset = 0;\r\n this.bufferStartTime = 0;\r\n\r\n // Route through the serialized pending chunk pipeline.\r\n // This ensures the flush chunk is processed AFTER all earlier chunks,\r\n // maintaining correct timestamp ordering in the frame queue.\r\n this.pendingChunks.push({ chunk: padded, timestamp: chunkTimestamp, actualSamples });\r\n this.drainPendingChunks();\r\n }\r\n\r\n /**\r\n * Reset buffer and frame queues\r\n */\r\n reset(): void {\r\n this.writeOffset = 0;\r\n this.bufferStartTime = 0;\r\n this.timestampedQueue = [];\r\n this.plainQueue = [];\r\n this._latestFrame = null;\r\n this.lastPulledFrame = null;\r\n this.lastDequeuedTime = 0;\r\n this.pendingChunks = [];\r\n // Must reset so drainPendingChunks() processes new chunks after stop→start\r\n this.inferenceRunning = false;\r\n this.getFrameCallCount = 0;\r\n // Reset spring decay state\r\n this.smoother.reset();\r\n this.gapDecayStarted = false;\r\n this.lastSmootherUpdate = 0;\r\n }\r\n\r\n // ═══════════════════════════════════════════════════════════════════════\r\n // Frame Output — Pull Mode (TTS playback)\r\n // ═══════════════════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Get frame synced to external clock (e.g. AudioContext.currentTime).\r\n *\r\n * Discards frames that are too old, returns the current frame,\r\n * or holds last frame as fallback to prevent avatar freezing.\r\n *\r\n * @param currentTime - Current playback time (seconds)\r\n * @returns Blendshape frame, or null if no frames yet\r\n */\r\n getFrameForTime(currentTime: number): Float32Array | null {\r\n this.getFrameCallCount++;\r\n\r\n // Dynamic discard window based on backend\r\n const discardWindow = this.backend.backend === 'wasm' ? 1.5 : 0.5;\r\n\r\n // Remove frames that are too old\r\n let discardCount = 0;\r\n while (\r\n this.timestampedQueue.length > 0 &&\r\n this.timestampedQueue[0].timestamp < currentTime - discardWindow\r\n ) {\r\n this.timestampedQueue.shift();\r\n discardCount++;\r\n }\r\n\r\n if (discardCount > 0) {\r\n logger.warn('getFrameForTime DISCARDED stale frames', {\r\n discardCount,\r\n currentTime: currentTime.toFixed(3),\r\n discardWindow,\r\n remainingFrames: this.timestampedQueue.length,\r\n nextFrameTs: this.timestampedQueue.length > 0\r\n ? this.timestampedQueue[0].timestamp.toFixed(3) : 'none',\r\n });\r\n }\r\n\r\n // Return frame that should be playing now — raw passthrough, no smoother\r\n if (\r\n this.timestampedQueue.length > 0 &&\r\n this.timestampedQueue[0].timestamp <= currentTime\r\n ) {\r\n const { frame } = this.timestampedQueue.shift()!;\r\n this.lastPulledFrame = frame;\r\n this.lastDequeuedTime = getClock().now();\r\n this.gapDecayStarted = false; // reset decay state on new dequeue\r\n return frame;\r\n }\r\n\r\n // Diagnostic: frames queued but AudioContext time hasn't reached them yet\r\n // This is normal during playback startup (audioDelayMs gap) — not an error\r\n if (this.timestampedQueue.length > 0 && this.getFrameCallCount % 60 === 0) {\r\n logger.debug('getFrameForTime: waiting for playback time to reach queued frames', {\r\n queueLen: this.timestampedQueue.length,\r\n frontTimestamp: this.timestampedQueue[0].timestamp.toFixed(4),\r\n currentTime: currentTime.toFixed(4),\r\n delta: (this.timestampedQueue[0].timestamp - currentTime).toFixed(4),\r\n });\r\n }\r\n\r\n // Last-frame-hold with spring decay toward neutral.\r\n // Prevents mouth-stuck between sentences: TTS chunk boundaries don't align\r\n // with phoneme boundaries, so the last frame often has mouth open.\r\n if (this.lastPulledFrame) {\r\n const now = getClock().now();\r\n const elapsed = now - this.lastDequeuedTime;\r\n\r\n // Normal hold — bridges inter-frame gaps during active playback\r\n if (elapsed < HOLD_DURATION_MS) {\r\n return this.lastPulledFrame;\r\n }\r\n\r\n // Spring decay toward neutral (zero blendshapes)\r\n if (!this.gapDecayStarted) {\r\n this.smoother.setPosition(this.lastPulledFrame);\r\n this.smoother.decayToNeutral();\r\n this.gapDecayStarted = true;\r\n this.lastSmootherUpdate = now;\r\n }\r\n\r\n const dt = Math.min((now - this.lastSmootherUpdate) / 1000, 0.1);\r\n this.lastSmootherUpdate = now;\r\n\r\n const smoothed = this.smoother.update(dt);\r\n\r\n // Check if fully decayed (all values below threshold)\r\n let maxVal = 0;\r\n for (let i = 0; i < 52; i++) {\r\n if (smoothed[i] > maxVal) maxVal = smoothed[i];\r\n }\r\n if (maxVal < 0.001) {\r\n this.lastPulledFrame = null;\r\n return null;\r\n }\r\n\r\n // Copy into reusable decay buffer\r\n if (!this.decayBuffer) this.decayBuffer = new Float32Array(52);\r\n this.decayBuffer.set(smoothed);\r\n return this.decayBuffer;\r\n }\r\n\r\n return null;\r\n }\r\n\r\n // ═══════════════════════════════════════════════════════════════════════\r\n // Frame Output — Push Mode (live mic, game loop)\r\n // ═══════════════════════════════════════════════════════════════════════\r\n\r\n /** Latest frame from drip-feed (live mic, game loop) */\r\n get latestFrame(): Float32Array | null {\r\n return this._latestFrame;\r\n }\r\n\r\n /** Start 30fps drip-feed timer (push mode) */\r\n startDrip(): void {\r\n if (this.dripInterval) return;\r\n this.dripInterval = setInterval(() => {\r\n const frame = this.plainQueue.shift();\r\n if (frame) {\r\n this._latestFrame = frame;\r\n this.onFrame?.(frame);\r\n }\r\n }, DRIP_INTERVAL_MS);\r\n }\r\n\r\n /** Stop drip-feed timer */\r\n stopDrip(): void {\r\n if (this.dripInterval) {\r\n clearInterval(this.dripInterval);\r\n this.dripInterval = null;\r\n }\r\n }\r\n\r\n // ═══════════════════════════════════════════════════════════════════════\r\n // State\r\n // ═══════════════════════════════════════════════════════════════════════\r\n\r\n /** Number of frames waiting in queue (both modes combined) */\r\n get queuedFrameCount(): number {\r\n return this.timestampedQueue.length + this.plainQueue.length;\r\n }\r\n\r\n /** Buffer fill level as fraction of chunkSize (0-1) */\r\n get fillLevel(): number {\r\n return Math.min(1, this.writeOffset / this.chunkSize);\r\n }\r\n\r\n /** Dispose resources */\r\n dispose(): void {\r\n if (this.disposed) return;\r\n logger.debug('Disposed');\r\n this.disposed = true;\r\n this.stopDrip();\r\n this.reset();\r\n }\r\n\r\n // ═══════════════════════════════════════════════════════════════════════\r\n // Private\r\n // ═══════════════════════════════════════════════════════════════════════\r\n\r\n /**\r\n * Process pending chunks sequentially.\r\n * Fire-and-forget — called from pushAudio() without awaiting.\r\n */\r\n private drainPendingChunks(): void {\r\n if (this.inferenceRunning || this.pendingChunks.length === 0) {\r\n if (this.inferenceRunning && this.pendingChunks.length > 0) {\r\n logger.debug('drainPendingChunks skipped (inference running)', {\r\n pendingChunks: this.pendingChunks.length,\r\n });\r\n }\r\n return;\r\n }\r\n\r\n this.inferenceRunning = true;\r\n logger.info('drainPendingChunks starting', { pendingChunks: this.pendingChunks.length });\r\n\r\n const processNext = async () => {\r\n while (this.pendingChunks.length > 0 && !this.disposed) {\r\n const { chunk, timestamp, actualSamples } = this.pendingChunks.shift()!;\r\n\r\n try {\r\n const t0 = getClock().now();\r\n const result = await this.backend.infer(chunk, this.identityIndex);\r\n const inferMs = Math.round(getClock().now() - t0);\r\n getTelemetry()?.recordHistogram(MetricNames.INFERENCE_LATENCY, inferMs);\r\n getTelemetry()?.incrementCounter(MetricNames.INFERENCE_TOTAL);\r\n\r\n // Only queue frames proportional to actual audio duration.\r\n // For flush chunks, actualSamples tracks real audio before zero-padding.\r\n // For normal chunks, actualSamples is undefined → use full chunk length.\r\n const effectiveSamples = actualSamples ?? chunk.length;\r\n const actualDuration = effectiveSamples / this.sampleRate;\r\n const actualFrameCount = Math.ceil(actualDuration * FRAME_RATE);\r\n const framesToQueue = Math.min(Math.max(1, actualFrameCount), result.blendshapes.length);\r\n\r\n logger.info('Inference complete', {\r\n inferMs,\r\n modelFrames: result.blendshapes.length,\r\n framesToQueue,\r\n timestamp,\r\n totalQueued: this.timestampedQueue.length + framesToQueue,\r\n remainingPending: this.pendingChunks.length,\r\n });\r\n\r\n for (let i = 0; i < framesToQueue; i++) {\r\n if (timestamp !== undefined) {\r\n this.timestampedQueue.push({\r\n frame: result.blendshapes[i],\r\n timestamp: timestamp + i / FRAME_RATE,\r\n });\r\n } else {\r\n this.plainQueue.push(result.blendshapes[i]);\r\n }\r\n }\r\n } catch (err) {\r\n this.handleError(err);\r\n }\r\n\r\n // Yield to main thread between batches to prevent blocking\r\n if (this.pendingChunks.length > 0) {\r\n await new Promise<void>(r => setTimeout(r, 0));\r\n }\r\n }\r\n };\r\n\r\n processNext()\r\n .catch(err => this.handleError(err))\r\n .finally(() => {\r\n this.inferenceRunning = false;\r\n // Check if more chunks arrived while we were processing\r\n if (this.pendingChunks.length > 0) {\r\n this.drainPendingChunks();\r\n }\r\n });\r\n }\r\n\r\n private handleError(err: unknown): void {\r\n const error = err instanceof Error ? err : new Error(String(err));\r\n const isOOM = typeof err === 'number' || (error.message && /out of memory|oom|alloc/i.test(error.message));\r\n const isTimeout = error.message?.includes('timed out');\r\n const code = isOOM ? ErrorCodes.INF_OOM\r\n : isTimeout ? ErrorCodes.INF_TIMEOUT\r\n : ErrorCodes.INF_LOAD_FAILED;\r\n logger.warn('A2EProcessor inference error', {\r\n error: error.message,\r\n code,\r\n });\r\n getTelemetry()?.incrementCounter(MetricNames.ERRORS_TOTAL, 1, { source: 'A2EProcessor', code });\r\n this.onError?.(error);\r\n }\r\n}\r\n","/**\r\n * Shared blendshape constants and utilities for lip sync inference\r\n *\r\n * Contains ARKIT_BLENDSHAPES (canonical 52-blendshape ordering), symmetrization,\r\n * and interpolation utilities used by A2EInference and all renderer adapters.\r\n *\r\n * This module is the single source of truth for blendshape ordering to\r\n * avoid circular dependencies between inference classes.\r\n *\r\n * @category Inference\r\n */\r\n\r\n/**\r\n * ARKit blendshape names in alphabetical order (52 total)\r\n * This is the canonical ordering used by all A2E models in the SDK.\r\n */\r\nexport const ARKIT_BLENDSHAPES = [\r\n 'browDownLeft', 'browDownRight', 'browInnerUp', 'browOuterUpLeft', 'browOuterUpRight',\r\n 'cheekPuff', 'cheekSquintLeft', 'cheekSquintRight',\r\n 'eyeBlinkLeft', 'eyeBlinkRight', 'eyeLookDownLeft', 'eyeLookDownRight',\r\n 'eyeLookInLeft', 'eyeLookInRight', 'eyeLookOutLeft', 'eyeLookOutRight',\r\n 'eyeLookUpLeft', 'eyeLookUpRight', 'eyeSquintLeft', 'eyeSquintRight',\r\n 'eyeWideLeft', 'eyeWideRight',\r\n 'jawForward', 'jawLeft', 'jawOpen', 'jawRight',\r\n 'mouthClose', 'mouthDimpleLeft', 'mouthDimpleRight', 'mouthFrownLeft', 'mouthFrownRight',\r\n 'mouthFunnel', 'mouthLeft', 'mouthLowerDownLeft', 'mouthLowerDownRight',\r\n 'mouthPressLeft', 'mouthPressRight', 'mouthPucker', 'mouthRight',\r\n 'mouthRollLower', 'mouthRollUpper', 'mouthShrugLower', 'mouthShrugUpper',\r\n 'mouthSmileLeft', 'mouthSmileRight', 'mouthStretchLeft', 'mouthStretchRight',\r\n 'mouthUpperUpLeft', 'mouthUpperUpRight',\r\n 'noseSneerLeft', 'noseSneerRight', 'tongueOut'\r\n] as const;\r\n\r\n/** @deprecated Use ARKIT_BLENDSHAPES instead */\r\nexport const LAM_BLENDSHAPES = ARKIT_BLENDSHAPES;\r\n\r\n/**\r\n * ARKit Left/Right symmetric pairs for blendshape symmetrization\r\n * From LAM official postprocessing (models/utils.py)\r\n */\r\nconst BLENDSHAPE_SYMMETRIC_PAIRS: [string, string][] = [\r\n ['jawLeft', 'jawRight'],\r\n ['mouthLeft', 'mouthRight'],\r\n ['mouthSmileLeft', 'mouthSmileRight'],\r\n ['mouthFrownLeft', 'mouthFrownRight'],\r\n ['mouthDimpleLeft', 'mouthDimpleRight'],\r\n ['mouthStretchLeft', 'mouthStretchRight'],\r\n ['mouthPressLeft', 'mouthPressRight'],\r\n ['mouthUpperUpLeft', 'mouthUpperUpRight'],\r\n ['mouthLowerDownLeft', 'mouthLowerDownRight'],\r\n ['noseSneerLeft', 'noseSneerRight'],\r\n ['cheekSquintLeft', 'cheekSquintRight'],\r\n ['browDownLeft', 'browDownRight'],\r\n ['browOuterUpLeft', 'browOuterUpRight'],\r\n ['eyeBlinkLeft', 'eyeBlinkRight'],\r\n ['eyeLookUpLeft', 'eyeLookUpRight'],\r\n ['eyeLookDownLeft', 'eyeLookDownRight'],\r\n ['eyeLookInLeft', 'eyeLookInRight'],\r\n ['eyeLookOutLeft', 'eyeLookOutRight'],\r\n ['eyeSquintLeft', 'eyeSquintRight'],\r\n ['eyeWideLeft', 'eyeWideRight'],\r\n];\r\n\r\n// Precompute index pairs for fast symmetrization\r\nconst SYMMETRIC_INDEX_PAIRS: [number, number][] = BLENDSHAPE_SYMMETRIC_PAIRS.map(([l, r]) => [\r\n ARKIT_BLENDSHAPES.indexOf(l as typeof ARKIT_BLENDSHAPES[number]),\r\n ARKIT_BLENDSHAPES.indexOf(r as typeof ARKIT_BLENDSHAPES[number]),\r\n]).filter(([l, r]) => l !== -1 && r !== -1) as [number, number][];\r\n\r\n/**\r\n * Symmetrize blendshapes by averaging left/right pairs\r\n * From LAM official postprocessing (models/utils.py)\r\n * This fixes asymmetric output from the raw model\r\n */\r\nexport function symmetrizeBlendshapes(frame: Float32Array): Float32Array {\r\n const result = new Float32Array(frame);\r\n for (const [lIdx, rIdx] of SYMMETRIC_INDEX_PAIRS) {\r\n const avg = (frame[lIdx] + frame[rIdx]) / 2;\r\n result[lIdx] = avg;\r\n result[rIdx] = avg;\r\n }\r\n return result;\r\n}\r\n\r\n/**\r\n * Linearly interpolate between two blendshape weight arrays.\r\n *\r\n * Pure math utility with zero renderer dependency — used by all renderer\r\n * adapters (@omote/three, @omote/babylon, @omote/r3f) for smooth frame\r\n * transitions.\r\n *\r\n * @param current - Current blendshape weights\r\n * @param target - Target blendshape weights\r\n * @param factor - Interpolation factor (0 = no change, 1 = snap to target). Default: 0.3\r\n * @returns Interpolated weights as number[]\r\n */\r\nexport function lerpBlendshapes(\r\n current: Float32Array | number[],\r\n target: Float32Array | number[],\r\n factor: number = 0.3,\r\n): number[] {\r\n const len = Math.max(current.length, target.length);\r\n const result = new Array(len);\r\n for (let i = 0; i < len; i++) {\r\n const c = current[i] ?? 0;\r\n const t = target[i] ?? 0;\r\n result[i] = c + (t - c) * factor;\r\n }\r\n return result;\r\n}\r\n","/**\n * ExpressionProfile - Per-character weight scaling for A2E blendshape output\n *\n * Maps blendshape groups (eyes, brows, jaw, mouth, cheeks, nose, tongue)\n * to weight scalers. Used by PlaybackPipeline, MicLipSync, and VoiceOrchestrator.\n *\n * @category Audio\n */\n\nimport { ARKIT_BLENDSHAPES } from '../inference/blendshapeUtils';\n\n// ---------------------------------------------------------------------------\n// ExpressionProfile types\n// ---------------------------------------------------------------------------\n\nexport type BlendshapeGroup = 'eyes' | 'brows' | 'jaw' | 'mouth' | 'cheeks' | 'nose' | 'tongue';\n\n/**\n * Per-character weight scaling for A2E blendshape output.\n *\n * Group scalers multiply all blendshapes in that group (default 1.0).\n * Per-blendshape overrides take priority over group scalers.\n * Final values are clamped to [0, 1].\n */\nexport interface ExpressionProfile {\n /** eyeBlink*, eyeLook*, eyeSquint*, eyeWide* (14 blendshapes) */\n eyes?: number;\n /** browDown*, browInnerUp, browOuterUp* (5 blendshapes) */\n brows?: number;\n /** jawForward, jawLeft, jawRight, jawOpen (4 blendshapes) */\n jaw?: number;\n /** mouth* (23 blendshapes) */\n mouth?: number;\n /** cheekPuff, cheekSquint* (3 blendshapes) */\n cheeks?: number;\n /** noseSneer* (2 blendshapes) */\n nose?: number;\n /** tongueOut (1 blendshape) */\n tongue?: number;\n /** Per-blendshape overrides (0-2). Takes priority over group scalers. */\n overrides?: Partial<Record<string, number>>;\n}\n\n// ---------------------------------------------------------------------------\n// Precomputed blendshape → group mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Map each ARKIT_BLENDSHAPES entry to its BlendshapeGroup.\n * Built once at module load from prefix matching.\n */\nexport const BLENDSHAPE_TO_GROUP = new Map<string, BlendshapeGroup>();\n\nfor (const name of ARKIT_BLENDSHAPES) {\n if (name.startsWith('eye')) {\n BLENDSHAPE_TO_GROUP.set(name, 'eyes');\n } else if (name.startsWith('brow')) {\n BLENDSHAPE_TO_GROUP.set(name, 'brows');\n } else if (name.startsWith('jaw')) {\n BLENDSHAPE_TO_GROUP.set(name, 'jaw');\n } else if (name.startsWith('mouth')) {\n BLENDSHAPE_TO_GROUP.set(name, 'mouth');\n } else if (name.startsWith('cheek')) {\n BLENDSHAPE_TO_GROUP.set(name, 'cheeks');\n } else if (name.startsWith('nose')) {\n BLENDSHAPE_TO_GROUP.set(name, 'nose');\n } else if (name.startsWith('tongue')) {\n BLENDSHAPE_TO_GROUP.set(name, 'tongue');\n }\n}\n\n// ---------------------------------------------------------------------------\n// applyProfile utility\n// ---------------------------------------------------------------------------\n\n/**\n * Apply ExpressionProfile scaling to raw A2E blendshapes.\n *\n * For each blendshape:\n * 1. If an override exists for the blendshape name, use override as scaler\n * 2. Otherwise, use the group scaler (default 1.0)\n * 3. Clamp result to [0, 1]\n */\nexport function applyProfile(raw: Float32Array, profile: ExpressionProfile, out?: Float32Array): Float32Array {\n const scaled = out ?? new Float32Array(52);\n\n for (let i = 0; i < 52; i++) {\n const name = ARKIT_BLENDSHAPES[i];\n let scaler: number;\n\n // Per-blendshape override takes priority\n if (profile.overrides && profile.overrides[name] !== undefined) {\n scaler = profile.overrides[name]!;\n } else {\n // Group scaler (default 1.0)\n const group = BLENDSHAPE_TO_GROUP.get(name);\n scaler = group ? (profile[group] ?? 1.0) : 1.0;\n }\n\n scaled[i] = Math.min(1, Math.max(0, raw[i] * scaler));\n }\n\n return scaled;\n}\n","/**\r\n * PlaybackPipeline - Audio playback + A2E lip sync with ExpressionProfile scaling\r\n *\r\n * Refactored superset of FullFacePipeline. Adds:\r\n * - Sync mode (`feedBuffer`) for pre-recorded audio\r\n * - State tracking (idle → playing → stopping)\r\n * - Opt-in neutral transition animation on playback complete\r\n * - Idempotent `start()` (no spurious playback:complete on restart)\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { AudioScheduler } from './AudioScheduler';\r\nimport { AudioChunkCoalescer } from './AudioChunkCoalescer';\r\nimport { A2EProcessor } from '../inference/A2EProcessor';\r\nimport { BlendshapeSmoother } from '../inference/BlendshapeSmoother';\r\nimport { EventEmitter } from '../events/EventEmitter';\r\nimport type { A2EBackend } from '../inference/A2EBackend';\r\nimport { ARKIT_BLENDSHAPES } from '../inference/blendshapeUtils';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\nimport { pcm16ToFloat32 } from './audioUtils';\r\nimport { applyProfile } from './expressionProfile';\r\nimport type { ExpressionProfile } from './expressionProfile';\r\n\r\nconst logger = createLogger('PlaybackPipeline');\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport type PlaybackState = 'idle' | 'playing' | 'stopping';\r\n\r\nexport interface PlaybackPipelineConfig {\r\n /** A2E inference backend (from createA2E) */\r\n lam: A2EBackend;\r\n /** Sample rate in Hz (default: 16000) */\r\n sampleRate?: number;\r\n /** Target chunk duration for coalescing in ms (default: 200) */\r\n chunkTargetMs?: number;\r\n /** Audio playback delay in ms (default: auto-detected from backend) */\r\n audioDelayMs?: number;\r\n /** A2E inference chunk size in samples (default: 16000) */\r\n chunkSize?: number;\r\n /** Identity/style index for A2E model (default: 0) */\r\n identityIndex?: number;\r\n /** Per-character expression weight scaling */\r\n profile?: ExpressionProfile;\r\n /** Enable neutral transition on playback complete (default: false) */\r\n neutralTransitionEnabled?: boolean;\r\n /** Duration of neutral fade-out in ms (default: 250). Only applies when neutralTransitionEnabled=true. */\r\n neutralTransitionMs?: number;\r\n /** Stale frame warning threshold in ms (default: 2000) */\r\n staleThresholdMs?: number;\r\n}\r\n\r\n/**\r\n * Full face frame with scaled blendshapes\r\n */\r\nexport interface FullFaceFrame {\r\n /** Scaled 52 ARKit blendshapes (ExpressionProfile applied) */\r\n blendshapes: Float32Array;\r\n /** Raw A2E output (52 blendshapes, before profile scaling) */\r\n rawBlendshapes: Float32Array;\r\n /** AudioContext timestamp for this frame */\r\n timestamp: number;\r\n /** Emotion label for this frame (from SenseVoice, text heuristics, or LLM tags) */\r\n emotion?: string;\r\n}\r\n\r\nexport interface PlaybackPipelineEvents {\r\n /** New frame ready for display (scaled by ExpressionProfile) */\r\n 'frame': FullFaceFrame;\r\n /** Raw A2E frame (before profile scaling) */\r\n 'frame:raw': Float32Array;\r\n /** Playback started (first audio scheduled) */\r\n 'playback:start': { time: number };\r\n /** Playback completed naturally */\r\n 'playback:complete': void;\r\n /** Playback stopped (user-initiated) */\r\n 'playback:stop': void;\r\n /** Error occurred */\r\n 'error': Error;\r\n /** State changed */\r\n 'state': PlaybackState;\r\n\r\n [key: string]: unknown;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// PlaybackPipeline\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class PlaybackPipeline extends EventEmitter<PlaybackPipelineEvents> {\r\n private scheduler: AudioScheduler;\r\n private coalescer: AudioChunkCoalescer;\r\n private processor: A2EProcessor;\r\n private readonly sampleRate: number;\r\n\r\n private _state: PlaybackState = 'idle';\r\n private playbackStarted = false;\r\n private monitorInterval: ReturnType<typeof setInterval> | null = null;\r\n private frameAnimationId: number | null = null;\r\n\r\n // Stale frame detection\r\n private lastNewFrameTime = 0;\r\n private lastKnownLamFrame: Float32Array | null = null;\r\n private staleWarningEmitted = false;\r\n private readonly staleThresholdMs: number;\r\n\r\n // Diagnostic counter\r\n private frameLoopCount = 0;\r\n\r\n // Telemetry\r\n private sessionStartTime = 0;\r\n\r\n // ExpressionProfile\r\n private profile: ExpressionProfile;\r\n\r\n // Neutral transition\r\n private readonly neutralTransitionEnabled: boolean;\r\n private readonly neutralTransitionMs: number;\r\n private neutralTransitionFrame: Float32Array | null = null;\r\n private neutralTransitionStart = 0;\r\n private neutralAnimationId: number | null = null;\r\n\r\n // Ramp-in smoother: smooths neutral → first inference frame at start-of-speech\r\n private static readonly RAMP_IN_HALFLIFE = 0.05; // 50ms — snappy but smooth\r\n private static readonly RAMP_IN_DURATION_MS = 150; // deactivate after ~3 halflives\r\n private rampInSmoother: BlendshapeSmoother;\r\n private rampInActive = false;\r\n private rampInLastTime = 0;\r\n private rampInStartTime = 0;\r\n private readonly _rampInBuffer = new Float32Array(52);\r\n\r\n // Current frame refs\r\n private _currentFrame: Float32Array | null = null;\r\n private _currentRawFrame: Float32Array | null = null;\r\n\r\n // Emotion label (set externally via setEmotion, included in emitted frames)\r\n private _emotion: string | null = null;\r\n\r\n // Pre-allocated buffer for applyProfile (avoids per-frame Float32Array allocation)\r\n private readonly _profileBuffer = new Float32Array(52);\r\n\r\n /** Current pipeline state */\r\n get state(): PlaybackState { return this._state; }\r\n /** Current scaled blendshapes (updated in-place for perf) */\r\n get currentFrame(): Float32Array | null { return this._currentFrame; }\r\n /** Raw A2E blendshapes (before profile scaling) */\r\n get currentRawFrame(): Float32Array | null { return this._currentRawFrame; }\r\n\r\n constructor(private readonly config: PlaybackPipelineConfig) {\r\n super();\r\n\r\n this.sampleRate = config.sampleRate ?? 16000;\r\n this.profile = config.profile ?? {};\r\n this.staleThresholdMs = config.staleThresholdMs ?? 2000;\r\n this.neutralTransitionEnabled = config.neutralTransitionEnabled ?? true;\r\n this.neutralTransitionMs = config.neutralTransitionMs ?? 250;\r\n\r\n const chunkSize = config.chunkSize ?? config.lam.chunkSize ?? 16000;\r\n\r\n // Auto-detect audio delay\r\n const chunkAccumulationMs = (chunkSize / this.sampleRate) * 1000;\r\n const inferenceEstimateMs = config.lam.backend === 'wasm' ? 150 : 50;\r\n const marginMs = 100;\r\n const autoDelay = Math.ceil(chunkAccumulationMs + inferenceEstimateMs + marginMs);\r\n const audioDelayMs = config.audioDelayMs ?? autoDelay;\r\n\r\n logger.info('PlaybackPipeline config', {\r\n chunkSize,\r\n audioDelayMs,\r\n autoDelay,\r\n backend: config.lam.backend,\r\n modelId: config.lam.modelId,\r\n neutralTransitionEnabled: this.neutralTransitionEnabled,\r\n });\r\n\r\n this.rampInSmoother = new BlendshapeSmoother({ halflife: PlaybackPipeline.RAMP_IN_HALFLIFE });\r\n\r\n this.scheduler = new AudioScheduler({\r\n sampleRate: this.sampleRate,\r\n initialLookaheadSec: audioDelayMs / 1000,\r\n });\r\n this.coalescer = new AudioChunkCoalescer({\r\n sampleRate: this.sampleRate,\r\n targetDurationMs: config.chunkTargetMs ?? 200,\r\n });\r\n this.processor = new A2EProcessor({\r\n backend: config.lam,\r\n sampleRate: this.sampleRate,\r\n chunkSize,\r\n identityIndex: config.identityIndex,\r\n onError: (error) => {\r\n logger.error('A2E inference error', { message: error.message, stack: error.stack });\r\n this.emit('error', error);\r\n },\r\n });\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Lifecycle\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Initialize AudioContext (lazy, call after user gesture) */\r\n async initialize(): Promise<void> {\r\n await this.scheduler.initialize();\r\n }\r\n\r\n /** Eagerly create AudioContext. Call from user gesture for iOS. */\r\n async warmup(): Promise<void> {\r\n await this.scheduler.warmup();\r\n }\r\n\r\n /** Update ExpressionProfile at runtime */\r\n setProfile(profile: ExpressionProfile): void {\r\n this.profile = profile;\r\n }\r\n\r\n /** Set the emotion label to include in emitted frames */\r\n setEmotion(emotion: string | null): void {\r\n this._emotion = emotion;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Async mode (streaming TTS)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Start a new playback session.\r\n * Idempotent — calling during playback resets cleanly without emitting\r\n * spurious playback:complete.\r\n */\r\n start(): void {\r\n logger.info('start');\r\n // Stop any active session first (prevents duplicate frame loops/monitors)\r\n this.stopInternal();\r\n\r\n this.scheduler.reset();\r\n this.coalescer.reset();\r\n this.processor.reset();\r\n this.playbackStarted = false;\r\n\r\n // Reset stale frame tracking\r\n this.lastNewFrameTime = 0;\r\n this.lastKnownLamFrame = null;\r\n this.staleWarningEmitted = false;\r\n this.frameLoopCount = 0;\r\n\r\n // Reset current frames\r\n this._currentFrame = null;\r\n this._currentRawFrame = null;\r\n\r\n // Activate ramp-in for smooth neutral → first inference frame\r\n this.rampInSmoother.reset();\r\n this.rampInActive = true;\r\n this.rampInLastTime = 0;\r\n this.rampInStartTime = 0;\r\n\r\n // Cancel any neutral transition in progress\r\n this.cancelNeutralTransition();\r\n\r\n // Warm up AudioContext\r\n this.scheduler.warmup();\r\n\r\n this.sessionStartTime = getClock().now();\r\n this.startFrameLoop();\r\n this.startMonitoring();\r\n this.setState('playing');\r\n }\r\n\r\n /** Feed a streaming audio chunk (PCM16 Uint8Array) */\r\n async onAudioChunk(chunk: Uint8Array): Promise<void> {\r\n const chunkStart = getClock().now();\r\n const combined = this.coalescer.add(chunk);\r\n if (!combined) return;\r\n\r\n const float32 = pcm16ToFloat32(combined);\r\n const scheduleTime = await this.scheduler.schedule(float32);\r\n\r\n if (!this.playbackStarted) {\r\n this.playbackStarted = true;\r\n this.emit('playback:start', { time: scheduleTime });\r\n }\r\n\r\n this.processor.pushAudio(float32, scheduleTime);\r\n getTelemetry()?.recordHistogram(MetricNames.PLAYBACK_CHUNK_LATENCY, getClock().now() - chunkStart);\r\n }\r\n\r\n /** Signal end of audio stream (flushes remaining audio) */\r\n async end(): Promise<void> {\r\n logger.debug('end — flushing remaining audio');\r\n const remaining = this.coalescer.flush();\r\n if (remaining) {\r\n const chunk = new Uint8Array(remaining);\r\n await this.onAudioChunk(chunk);\r\n }\r\n await this.processor.flush();\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Sync mode (full buffer)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Feed a complete audio buffer. Chunks into 200ms pieces, schedules each\r\n * for playback, runs A2E inference, then waits for completion.\r\n */\r\n async feedBuffer(audio: ArrayBuffer | Float32Array): Promise<void> {\r\n const float32 = audio instanceof Float32Array ? audio : pcm16ToFloat32(audio);\r\n logger.debug('feedBuffer', { samples: float32.length, durationMs: Math.round((float32.length / this.sampleRate) * 1000) });\r\n this.start();\r\n\r\n const chunkSamples = Math.floor(this.sampleRate * 0.2);\r\n for (let i = 0; i < float32.length; i += chunkSamples) {\r\n const chunk = float32.subarray(i, Math.min(i + chunkSamples, float32.length));\r\n const scheduleTime = await this.scheduler.schedule(chunk);\r\n this.processor.pushAudio(chunk, scheduleTime);\r\n\r\n if (!this.playbackStarted) {\r\n this.playbackStarted = true;\r\n this.emit('playback:start', { time: scheduleTime });\r\n }\r\n }\r\n await this.processor.flush();\r\n\r\n // Wait for natural playback completion or stop\r\n return new Promise<void>(resolve => {\r\n const cleanup = () => { unsubComplete(); unsubStop(); };\r\n const unsubComplete = this.on('playback:complete', () => { cleanup(); resolve(); });\r\n const unsubStop = this.on('playback:stop', () => { cleanup(); resolve(); });\r\n });\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Control\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Stop playback immediately with fade-out */\r\n async stop(fadeOutMs: number = 50): Promise<void> {\r\n logger.info('stop', { fadeOutMs });\r\n this.setState('stopping');\r\n this.stopInternal();\r\n await this.scheduler.cancelAll(fadeOutMs);\r\n\r\n this.coalescer.reset();\r\n this.processor.reset();\r\n this.playbackStarted = false;\r\n this._emotion = null;\r\n\r\n this.emit('playback:stop');\r\n\r\n // Smooth mouth close: cancel any in-progress transition, start fresh\r\n this.cancelNeutralTransition();\r\n if (this.neutralTransitionEnabled && this._currentFrame) {\r\n this.startNeutralTransition(this._currentFrame);\r\n } else {\r\n this._currentFrame = null;\r\n this._currentRawFrame = null;\r\n this.setState('idle');\r\n }\r\n }\r\n\r\n /** Cleanup all resources */\r\n dispose(): void {\r\n logger.debug('dispose');\r\n this.stopInternal();\r\n this.cancelNeutralTransition();\r\n this.scheduler.dispose();\r\n this.coalescer.reset();\r\n this.processor.dispose();\r\n this._state = 'idle';\r\n }\r\n\r\n /** Get pipeline debug state */\r\n getDebugState() {\r\n return {\r\n state: this._state,\r\n playbackStarted: this.playbackStarted,\r\n coalescerFill: this.coalescer.fillLevel,\r\n processorFill: this.processor.fillLevel,\r\n queuedFrames: this.processor.queuedFrameCount,\r\n currentTime: this.scheduler.getCurrentTime(),\r\n playbackEndTime: this.scheduler.getPlaybackEndTime(),\r\n };\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal: Frame loop\r\n // ---------------------------------------------------------------------------\r\n\r\n private startFrameLoop(): void {\r\n const updateFrame = () => {\r\n this.frameLoopCount++;\r\n const currentTime = this.scheduler.getCurrentTime();\r\n const lamFrame = this.processor.getFrameForTime(currentTime);\r\n\r\n // Track new inference frames\r\n if (lamFrame && lamFrame !== this.lastKnownLamFrame) {\r\n this.lastNewFrameTime = getClock().now();\r\n this.lastKnownLamFrame = lamFrame;\r\n this.staleWarningEmitted = false;\r\n }\r\n\r\n // Stale detection\r\n if (\r\n this.playbackStarted &&\r\n this.lastNewFrameTime > 0 &&\r\n getClock().now() - this.lastNewFrameTime > this.staleThresholdMs\r\n ) {\r\n if (!this.staleWarningEmitted) {\r\n this.staleWarningEmitted = true;\r\n logger.warn('A2E stalled — no new inference frames', {\r\n staleDurationMs: Math.round(getClock().now() - this.lastNewFrameTime),\r\n queuedFrames: this.processor.queuedFrameCount,\r\n });\r\n }\r\n }\r\n\r\n if (lamFrame) {\r\n let effectiveFrame = lamFrame;\r\n\r\n // Ramp-in: smooth neutral → first inference frame at start-of-speech\r\n if (this.rampInActive) {\r\n const now = getClock().now();\r\n if (this.rampInLastTime === 0) {\r\n this.rampInStartTime = now;\r\n this.rampInLastTime = now;\r\n }\r\n this.rampInSmoother.setTarget(lamFrame);\r\n const dt = (now - this.rampInLastTime) / 1000;\r\n this.rampInLastTime = now;\r\n\r\n if (dt > 0) {\r\n const smoothed = this.rampInSmoother.update(dt);\r\n this._rampInBuffer.set(smoothed);\r\n effectiveFrame = this._rampInBuffer;\r\n }\r\n\r\n if (now - this.rampInStartTime > PlaybackPipeline.RAMP_IN_DURATION_MS) {\r\n this.rampInActive = false;\r\n }\r\n }\r\n\r\n const scaled = applyProfile(effectiveFrame, this.profile, this._profileBuffer);\r\n this._currentFrame = scaled;\r\n this._currentRawFrame = effectiveFrame;\r\n\r\n const fullFrame: FullFaceFrame = {\r\n blendshapes: scaled,\r\n rawBlendshapes: effectiveFrame,\r\n timestamp: currentTime,\r\n emotion: this._emotion ?? undefined,\r\n };\r\n\r\n this.emit('frame', fullFrame);\r\n this.emit('frame:raw', effectiveFrame);\r\n }\r\n\r\n this.frameAnimationId = requestAnimationFrame(updateFrame);\r\n };\r\n\r\n this.frameAnimationId = requestAnimationFrame(updateFrame);\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal: Playback monitoring\r\n // ---------------------------------------------------------------------------\r\n\r\n private startMonitoring(): void {\r\n if (this.monitorInterval) {\r\n clearInterval(this.monitorInterval);\r\n }\r\n\r\n this.monitorInterval = setInterval(() => {\r\n if (this.scheduler.isComplete() && this.processor.queuedFrameCount === 0) {\r\n this.onPlaybackComplete();\r\n }\r\n }, 100);\r\n }\r\n\r\n private onPlaybackComplete(): void {\r\n logger.debug('playback complete');\r\n if (this.sessionStartTime > 0) {\r\n getTelemetry()?.recordHistogram(\r\n MetricNames.PLAYBACK_SESSION_DURATION,\r\n getClock().now() - this.sessionStartTime,\r\n );\r\n }\r\n this.stopInternal();\r\n this.playbackStarted = false;\r\n\r\n this.emit('playback:complete');\r\n\r\n this.cancelNeutralTransition();\r\n if (this.neutralTransitionEnabled && this._currentFrame) {\r\n this.startNeutralTransition(this._currentFrame);\r\n } else {\r\n this.setState('idle');\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal: Neutral transition (opt-in)\r\n // ---------------------------------------------------------------------------\r\n\r\n private startNeutralTransition(fromFrame: Float32Array): void {\r\n this.neutralTransitionFrame = new Float32Array(fromFrame);\r\n this.neutralTransitionStart = getClock().now();\r\n\r\n const animate = () => {\r\n const elapsed = getClock().now() - this.neutralTransitionStart;\r\n const t = Math.min(1, elapsed / this.neutralTransitionMs);\r\n // Ease-out cubic: 1 - (1-t)^3\r\n const eased = 1 - Math.pow(1 - t, 3);\r\n\r\n logger.trace('neutral transition', { t: Math.round(t * 1000) / 1000, eased: Math.round(eased * 1000) / 1000 });\r\n\r\n const blendshapes = new Float32Array(52);\r\n for (let i = 0; i < 52; i++) {\r\n blendshapes[i] = this.neutralTransitionFrame![i] * (1 - eased);\r\n }\r\n\r\n this._currentFrame = blendshapes;\r\n const frame: FullFaceFrame = {\r\n blendshapes,\r\n rawBlendshapes: blendshapes, // raw = scaled during transition\r\n timestamp: getClock().now() / 1000,\r\n emotion: this._emotion ?? undefined,\r\n };\r\n this.emit('frame', frame);\r\n\r\n if (t >= 1) {\r\n this.neutralTransitionFrame = null;\r\n this._currentFrame = null;\r\n this._currentRawFrame = null;\r\n this.setState('idle');\r\n return;\r\n }\r\n\r\n this.neutralAnimationId = requestAnimationFrame(animate);\r\n };\r\n\r\n this.neutralAnimationId = requestAnimationFrame(animate);\r\n }\r\n\r\n private cancelNeutralTransition(): void {\r\n if (this.neutralAnimationId) {\r\n cancelAnimationFrame(this.neutralAnimationId);\r\n this.neutralAnimationId = null;\r\n }\r\n this.neutralTransitionFrame = null;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal: Helpers\r\n // ---------------------------------------------------------------------------\r\n\r\n private stopInternal(): void {\r\n if (this.monitorInterval) {\r\n clearInterval(this.monitorInterval);\r\n this.monitorInterval = null;\r\n }\r\n if (this.frameAnimationId) {\r\n cancelAnimationFrame(this.frameAnimationId);\r\n this.frameAnimationId = null;\r\n }\r\n }\r\n\r\n private setState(state: PlaybackState): void {\r\n if (this._state === state) return;\r\n const prev = this._state;\r\n this._state = state;\r\n logger.debug('state transition', { from: prev, to: state });\r\n this.emit('state', state);\r\n }\r\n}\r\n","/**\r\n * TTSPlayback — Composes TTSBackend + PlaybackPipeline for text → lip sync.\r\n *\r\n * Handles format conversion (Float32 @ TTS rate → PCM16 @ 16kHz)\r\n * and sentence prefetch for gapless playback.\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { PlaybackPipeline } from './PlaybackPipeline';\r\nimport { ttsToPlaybackFormat } from './audioConvert';\r\nimport { EventEmitter } from '../events/EventEmitter';\r\nimport { createLogger } from '../logging';\r\nimport type { TTSBackend } from '../inference/TTSBackend';\r\nimport type { A2EBackend } from '../inference/A2EBackend';\r\nimport type { ExpressionProfile } from './expressionProfile';\r\nimport type { PlaybackPipelineConfig, PlaybackPipelineEvents, FullFaceFrame } from './PlaybackPipeline';\r\n\r\nconst logger = createLogger('TTSPlayback');\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface TTSPlaybackConfig {\r\n /** TTS backend (e.g., KokoroTTSInference) */\r\n tts: TTSBackend;\r\n /** A2E inference backend (from createA2E) */\r\n lam: A2EBackend;\r\n /** Per-character expression weight scaling */\r\n profile?: ExpressionProfile;\r\n /** Prefetch next sentence while current plays. Default: true */\r\n prefetch?: boolean;\r\n /** Identity/style index for A2E model (default: 0) */\r\n identityIndex?: number;\r\n /** Audio playback delay in ms */\r\n audioDelayMs?: number;\r\n /** Enable neutral transition on playback complete */\r\n neutralTransitionEnabled?: boolean;\r\n /** Duration of neutral fade-out in ms */\r\n neutralTransitionMs?: number;\r\n}\r\n\r\nexport interface TTSPlaybackEvents {\r\n /** New frame ready for display */\r\n 'frame': FullFaceFrame;\r\n /** Raw A2E frame */\r\n 'frame:raw': Float32Array;\r\n /** Playback started */\r\n 'playback:start': { time: number };\r\n /** Playback completed */\r\n 'playback:complete': void;\r\n /** Playback stopped (user-initiated) */\r\n 'playback:stop': void;\r\n /** Error */\r\n 'error': Error;\r\n [key: string]: unknown;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// TTSPlayback\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class TTSPlayback extends EventEmitter<TTSPlaybackEvents> {\r\n private readonly config: TTSPlaybackConfig;\r\n private _pipeline: PlaybackPipeline | null = null;\r\n private initialized = false;\r\n\r\n constructor(config: TTSPlaybackConfig) {\r\n super();\r\n this.config = config;\r\n }\r\n\r\n /** Access underlying PlaybackPipeline for event subscriptions. */\r\n get pipeline(): PlaybackPipeline | null {\r\n return this._pipeline;\r\n }\r\n\r\n /** Load TTS model + initialize PlaybackPipeline. */\r\n async initialize(): Promise<void> {\r\n if (this.initialized) return;\r\n\r\n // Load TTS model if not already loaded\r\n if (!this.config.tts.isLoaded) {\r\n logger.info('Loading TTS model...');\r\n await this.config.tts.load();\r\n }\r\n\r\n // Create PlaybackPipeline\r\n this._pipeline = new PlaybackPipeline({\r\n lam: this.config.lam,\r\n profile: this.config.profile,\r\n identityIndex: this.config.identityIndex,\r\n audioDelayMs: this.config.audioDelayMs,\r\n neutralTransitionEnabled: this.config.neutralTransitionEnabled ?? true,\r\n neutralTransitionMs: this.config.neutralTransitionMs,\r\n });\r\n await this._pipeline.initialize();\r\n\r\n // Forward events\r\n this._pipeline.on('frame', (f) => this.emit('frame', f));\r\n this._pipeline.on('frame:raw', (f) => this.emit('frame:raw', f));\r\n this._pipeline.on('playback:start', (t) => this.emit('playback:start', t));\r\n this._pipeline.on('playback:complete', () => this.emit('playback:complete'));\r\n this._pipeline.on('playback:stop', () => this.emit('playback:stop'));\r\n this._pipeline.on('error', (e) => this.emit('error', e));\r\n\r\n this.initialized = true;\r\n logger.info('TTSPlayback initialized');\r\n }\r\n\r\n /**\r\n * Synthesize text and play with lip sync.\r\n * Streams sentences with prefetch for minimal gaps.\r\n *\r\n * @returns Resolves when playback completes\r\n */\r\n async speak(text: string, options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string }): Promise<void> {\r\n if (!this._pipeline || !this.initialized) {\r\n throw new Error('Not initialized. Call initialize() first.');\r\n }\r\n\r\n const tts = this.config.tts;\r\n const pipeline = this._pipeline;\r\n const prefetch = this.config.prefetch ?? true;\r\n\r\n logger.info('speak', { textLength: text.length, voice: options?.voice, prefetch });\r\n\r\n pipeline.start();\r\n\r\n try {\r\n if (prefetch) {\r\n await this.speakWithPrefetch(text, options);\r\n } else {\r\n await this.speakSequential(text, options);\r\n }\r\n\r\n await pipeline.end();\r\n } catch (err) {\r\n if (options?.signal?.aborted) {\r\n logger.warn('speak aborted via AbortSignal');\r\n pipeline.stop();\r\n return;\r\n }\r\n logger.error('speak error', { message: (err as Error).message });\r\n throw err;\r\n }\r\n\r\n // Wait for playback to complete or stop\r\n await new Promise<void>((resolve) => {\r\n const cleanup = () => { unsubComplete(); unsubStop(); };\r\n const unsubComplete = pipeline.on('playback:complete', () => { cleanup(); resolve(); });\r\n const unsubStop = pipeline.on('playback:stop', () => { cleanup(); resolve(); });\r\n });\r\n\r\n logger.debug('speak complete', { textLength: text.length });\r\n }\r\n\r\n /** Eagerly create AudioContext. Call from user gesture for iOS. */\r\n async warmup(): Promise<void> {\r\n if (this._pipeline) await this._pipeline.warmup();\r\n }\r\n\r\n /** Dispose of all resources. */\r\n async dispose(): Promise<void> {\r\n logger.debug('dispose');\r\n this._pipeline?.dispose();\r\n this._pipeline = null;\r\n this.initialized = false;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal: Prefetch strategy\r\n // ---------------------------------------------------------------------------\r\n\r\n private async speakWithPrefetch(\r\n text: string,\r\n options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string },\r\n ): Promise<void> {\r\n const tts = this.config.tts;\r\n const pipeline = this._pipeline!;\r\n\r\n // Collect chunks into a queue, prefetching ahead\r\n const chunks: Array<{ audio: Float32Array; duration: number }> = [];\r\n const generator = tts.stream(text, {\r\n signal: options?.signal,\r\n voice: options?.voice,\r\n speed: options?.speed,\r\n language: options?.language,\r\n singleShot: true,\r\n });\r\n\r\n // Start fetching first chunk\r\n let nextPromise = generator.next();\r\n\r\n while (true) {\r\n if (options?.signal?.aborted) return;\r\n\r\n const result = await nextPromise;\r\n if (result.done) break;\r\n\r\n const chunk = result.value;\r\n logger.debug('TTS chunk arrived (prefetch)', { samples: chunk.audio.length, durationMs: Math.round(chunk.duration * 1000) });\r\n\r\n // Start prefetching next chunk immediately\r\n nextPromise = generator.next();\r\n\r\n // Convert and feed to pipeline\r\n const pcm16 = ttsToPlaybackFormat(chunk.audio, tts.sampleRate, 16000);\r\n await pipeline.onAudioChunk(pcm16);\r\n }\r\n }\r\n\r\n private async speakSequential(\r\n text: string,\r\n options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string },\r\n ): Promise<void> {\r\n const tts = this.config.tts;\r\n const pipeline = this._pipeline!;\r\n\r\n for await (const chunk of tts.stream(text, {\r\n signal: options?.signal,\r\n voice: options?.voice,\r\n speed: options?.speed,\r\n language: options?.language,\r\n singleShot: true,\r\n })) {\r\n if (options?.signal?.aborted) return;\r\n\r\n logger.debug('TTS chunk arrived (sequential)', { samples: chunk.audio.length, durationMs: Math.round(chunk.duration * 1000) });\r\n const pcm16 = ttsToPlaybackFormat(chunk.audio, tts.sampleRate, 16000);\r\n await pipeline.onAudioChunk(pcm16);\r\n }\r\n }\r\n}\r\n","/**\r\n * Default and user-configurable model URLs for all ONNX models\r\n *\r\n * Out of the box, models are served from HuggingFace CDN (`/resolve/main/`\r\n * endpoint with `Access-Control-Allow-Origin: *`). For production apps that\r\n * need faster or more reliable delivery, call {@link configureModelUrls} once\r\n * at startup to point any or all models at your own CDN.\r\n *\r\n * @category Inference\r\n *\r\n * @example Use HuggingFace defaults (zero-config)\r\n * ```typescript\r\n * import { createA2E } from '@omote/core';\r\n * const a2e = createA2E(); // fetches from HuggingFace CDN\r\n * ```\r\n *\r\n * @example Self-host on your own CDN\r\n * ```typescript\r\n * import { configureModelUrls, createA2E } from '@omote/core';\r\n *\r\n * configureModelUrls({\r\n * lam: 'https://cdn.example.com/models/model_fp16.onnx',\r\n * senseVoice: 'https://cdn.example.com/models/sensevoice.int8.onnx',\r\n * // omitted keys keep HuggingFace defaults\r\n * });\r\n *\r\n * const a2e = createA2E(); // now fetches from your CDN\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('ModelUrls');\r\n\r\n/**\r\n * Validate that a URL is safe to use for model loading.\r\n * Allows: relative paths, HTTPS URLs, and HTTP only for localhost.\r\n */\r\nfunction isAllowedUrl(url: string): boolean {\r\n if (url.startsWith('/') && !url.startsWith('//')) return true; // relative\r\n if (url.startsWith('./') || url.startsWith('../')) return true; // relative\r\n try {\r\n const parsed = new URL(url, 'https://placeholder.invalid');\r\n if (parsed.protocol === 'https:') return true;\r\n if (parsed.protocol === 'http:') {\r\n return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname === '[::1]';\r\n }\r\n return false;\r\n } catch { return false; }\r\n}\r\n\r\nconst HF = 'https://huggingface.co';\r\n\r\n/** Model URL keys that can be configured */\r\nexport type ModelUrlKey = 'lam' | 'senseVoice' | 'sileroVad' | 'kokoroTTS' | 'kokoroVoices';\r\n\r\n/** HuggingFace CDN fallback URLs (immutable) */\r\nconst HF_MODEL_URLS: Readonly<Record<ModelUrlKey, string>> = {\r\n /** LAM A2E model — fp16 external data (230KB graph + 192MB weights) — 52 ARKit blendshapes */\r\n lam: `${HF}/omote-ai/lam-a2e/resolve/main/lam_ios.onnx`,\r\n\r\n /** SenseVoice ASR model (228MB int8, WASM) — speech recognition + emotion + language */\r\n senseVoice: `${HF}/omote-ai/sensevoice-asr/resolve/main/model.int8.onnx`,\r\n\r\n /** Silero VAD model (~2MB, WASM) — voice activity detection */\r\n sileroVad: `${HF}/deepghs/silero-vad-onnx/resolve/main/silero_vad.onnx`,\r\n\r\n /** Kokoro TTS model (92MB q8, WASM/WebGPU) — text-to-speech */\r\n kokoroTTS: `${HF}/onnx-community/Kokoro-82M-v1.0-ONNX/resolve/main/onnx/model_quantized.onnx`,\r\n\r\n /** Kokoro voice files base URL (individual .bin files under /voices/) */\r\n kokoroVoices: `${HF}/onnx-community/Kokoro-82M-v1.0-ONNX/resolve/main/voices`,\r\n};\r\n\r\n/** User overrides (mutable, starts empty) */\r\nconst _overrides: Partial<Record<ModelUrlKey, string>> = {};\r\n\r\n/**\r\n * Resolved model URLs — user overrides take priority, HuggingFace CDN is fallback.\r\n *\r\n * All SDK factories (`createA2E`, `createSenseVoice`, `createSileroVAD`) and\r\n * orchestrators (`VoiceOrchestrator`) read from this object. Call\r\n * {@link configureModelUrls} before constructing any pipelines to point\r\n * models at your own CDN.\r\n */\r\nexport const DEFAULT_MODEL_URLS: Readonly<Record<ModelUrlKey, string>> = new Proxy(\r\n {} as Record<ModelUrlKey, string>,\r\n {\r\n get(_target, prop: string) {\r\n const key = prop as ModelUrlKey;\r\n return _overrides[key] ?? HF_MODEL_URLS[key];\r\n },\r\n ownKeys() {\r\n return Object.keys(HF_MODEL_URLS);\r\n },\r\n getOwnPropertyDescriptor(_target, prop) {\r\n if (prop in HF_MODEL_URLS) {\r\n return { configurable: true, enumerable: true, value: (this as any).get!(_target, prop, _target) };\r\n }\r\n return undefined;\r\n },\r\n },\r\n);\r\n\r\n/**\r\n * Configure custom model URLs. Overrides persist for the lifetime of the page.\r\n * Omitted keys keep their HuggingFace CDN defaults.\r\n *\r\n * Call this **once** at app startup, before constructing any pipelines.\r\n *\r\n * @example Self-host all models\r\n * ```typescript\r\n * configureModelUrls({\r\n * lam: 'https://cdn.example.com/models/lam.onnx',\r\n * senseVoice: 'https://cdn.example.com/models/sensevoice.int8.onnx',\r\n * sileroVad: 'https://cdn.example.com/models/silero-vad.onnx',\r\n * });\r\n * ```\r\n *\r\n * @example Override only one model\r\n * ```typescript\r\n * configureModelUrls({\r\n * lam: '/models/model_fp16.onnx', // self-hosted, same origin\r\n * });\r\n * ```\r\n */\r\nexport function configureModelUrls(urls: Partial<Record<ModelUrlKey, string>>): void {\r\n logger.info('Model URLs configured', { keys: Object.keys(urls) });\r\n for (const [key, url] of Object.entries(urls)) {\r\n if (key in HF_MODEL_URLS && typeof url === 'string') {\r\n if (!isAllowedUrl(url)) {\r\n throw new Error(`Invalid model URL for '${key}': must be HTTPS, relative path, or localhost HTTP`);\r\n }\r\n _overrides[key as ModelUrlKey] = url;\r\n } else {\r\n logger.warn('Unrecognized model URL key', { key });\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Reset all model URL overrides back to HuggingFace CDN defaults.\r\n * Mainly useful for testing.\r\n */\r\nexport function resetModelUrls(): void {\r\n logger.debug('Model URLs reset to defaults');\r\n for (const key of Object.keys(_overrides)) {\r\n delete _overrides[key as ModelUrlKey];\r\n }\r\n}\r\n\r\n/**\r\n * Get the immutable HuggingFace CDN URLs (ignoring any overrides).\r\n * Useful for documentation or fallback logic.\r\n */\r\nexport const HF_CDN_URLS = HF_MODEL_URLS;\r\n","/**\r\n * Runtime detection utilities for platform-specific inference configuration\r\n *\r\n * These utilities help determine the optimal backend (WebGPU vs WASM) based on\r\n * the current platform's capabilities and known limitations.\r\n *\r\n * Key considerations:\r\n * - iOS Safari: WebGPU crashes due to JSEP bugs (GitHub #22776, #26827)\r\n * - Android Chrome: WebGPU works well (Chrome 121+)\r\n * - Desktop: WebGPU preferred for performance\r\n *\r\n * @module utils/runtime\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('Runtime');\r\n\r\n/**\r\n * Supported inference backends\r\n */\r\nexport type RuntimeBackend = 'webgpu' | 'wasm';\r\n\r\n/**\r\n * User-configurable backend preference\r\n */\r\nexport type BackendPreference =\r\n | 'auto' // iOS→WASM, else→WebGPU with fallback\r\n | 'webgpu' // Prefer WebGPU, fallback to WASM on error\r\n | 'wasm' // Prefer WASM, no WebGPU attempt\r\n | 'webgpu-only' // Force WebGPU, throw on failure (for debugging)\r\n | 'wasm-only'; // Force WASM, never load WebGPU bundle (smallest bundle)\r\n\r\n/**\r\n * Detect iOS Safari browser\r\n *\r\n * iOS Safari has severe WebGPU issues:\r\n * - JSEP compilation bugs cause OOM during session creation\r\n * - Threading bugs require numThreads=1\r\n * - Proxy mode triggers memory leaks\r\n *\r\n * @returns true if running in iOS Safari\r\n */\r\nexport function isIOSSafari(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n // Must be on iOS device AND using Safari (not Chrome/CriOS/Firefox/Edge)\r\n return /iphone|ipad|ipod/.test(ua) && /safari/.test(ua) && !/chrome|crios|fxios|chromium|edg/.test(ua);\r\n}\r\n\r\n/**\r\n * Detect any iOS device (regardless of browser)\r\n *\r\n * On iOS, all browsers use WebKit, so Chrome/Firefox on iOS\r\n * have the same limitations as Safari.\r\n *\r\n * @returns true if running on any iOS device\r\n */\r\nexport function isIOS(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n // iPadOS 13+ reports macOS user agent — detect via maxTouchPoints\r\n return /iphone|ipad|ipod/.test(ua) ||\r\n (/macintosh/.test(ua) && navigator.maxTouchPoints > 1);\r\n}\r\n\r\n/**\r\n * Detect Android device\r\n *\r\n * Android Chrome 121+ has good WebGPU support with Qualcomm/ARM GPUs.\r\n *\r\n * @returns true if running on Android\r\n */\r\nexport function isAndroid(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n return /android/i.test(navigator.userAgent);\r\n}\r\n\r\n/**\r\n * Detect any mobile device (iOS or Android)\r\n *\r\n * Mobile devices have different performance characteristics:\r\n * - Lower memory limits\r\n * - Thermal throttling\r\n * - Different GPU architectures\r\n *\r\n * @returns true if running on mobile\r\n */\r\nexport function isMobile(): boolean {\r\n return isIOS() || isAndroid();\r\n}\r\n\r\n/**\r\n * Check if WebGPU API is available in the browser\r\n *\r\n * Note: This only checks if the API exists, not if it works reliably.\r\n * iOS has navigator.gpu but ONNX Runtime's WebGPU backend crashes.\r\n *\r\n * @returns true if navigator.gpu exists\r\n */\r\nexport function hasWebGPUApi(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n return 'gpu' in navigator && navigator.gpu !== undefined;\r\n}\r\n\r\n/**\r\n * Get the recommended backend for the current platform\r\n *\r\n * Decision tree:\r\n * 1. iOS (any browser): Force WASM (WebGPU crashes)\r\n * 2. Android: WebGPU preferred (works in Chrome 121+)\r\n * 3. Desktop: WebGPU preferred (best performance)\r\n *\r\n * @returns 'wasm' for iOS, 'webgpu' for everything else\r\n */\r\nexport function getRecommendedBackend(): RuntimeBackend {\r\n // Safari (all platforms): Always WASM - WebGPU crashes due to JSEP bugs\r\n // iOS: All browsers use WebKit, so all have the same issue\r\n // macOS Safari: Same multithreaded JSEP build bug\r\n if (isSafari() || isIOS()) {\r\n logger.debug('Recommended backend: wasm', { reason: 'Safari/iOS detected — WebGPU crashes due to JSEP bugs' });\r\n return 'wasm';\r\n }\r\n\r\n // Android/Desktop (non-Safari): WebGPU preferred\r\n logger.debug('Recommended backend: webgpu', { reason: 'Non-Safari/iOS platform — WebGPU preferred' });\r\n return 'webgpu';\r\n}\r\n\r\n/**\r\n * Resolve user preference to actual backend\r\n *\r\n * @param preference User's backend preference\r\n * @param webgpuAvailable Whether WebGPU is available and working\r\n * @returns The backend to use\r\n */\r\nexport function resolveBackend(\r\n preference: BackendPreference,\r\n webgpuAvailable: boolean\r\n): RuntimeBackend {\r\n let resolved: RuntimeBackend;\r\n switch (preference) {\r\n case 'wasm-only':\r\n resolved = 'wasm';\r\n break;\r\n\r\n case 'webgpu-only':\r\n if (!webgpuAvailable) {\r\n throw new Error(\r\n 'WebGPU requested but not available. Use \"webgpu\" or \"auto\" for fallback.'\r\n );\r\n }\r\n resolved = 'webgpu';\r\n break;\r\n\r\n case 'wasm':\r\n resolved = 'wasm';\r\n break;\r\n\r\n case 'webgpu':\r\n resolved = webgpuAvailable ? 'webgpu' : 'wasm';\r\n break;\r\n\r\n case 'auto':\r\n default: {\r\n // Auto: Use platform recommendation, with WebGPU availability check\r\n const recommended = getRecommendedBackend();\r\n if (recommended === 'webgpu' && !webgpuAvailable) {\r\n resolved = 'wasm';\r\n } else {\r\n resolved = recommended;\r\n }\r\n break;\r\n }\r\n }\r\n logger.debug('Resolved backend', { preference, webgpuAvailable, resolved });\r\n return resolved;\r\n}\r\n\r\n/**\r\n * Get optimal WASM thread count for current platform\r\n *\r\n * @returns Recommended number of WASM threads\r\n */\r\nexport function getOptimalWasmThreads(): number {\r\n if (isIOS()) {\r\n // iOS: Must be 1 to avoid shared memory bugs (GitHub #22086)\r\n logger.debug('Optimal WASM threads: 1', { platform: 'iOS' });\r\n return 1;\r\n }\r\n\r\n if (isAndroid()) {\r\n // Android: Conservative threading (2 threads)\r\n logger.debug('Optimal WASM threads: 2', { platform: 'Android' });\r\n return 2;\r\n }\r\n\r\n // Desktop: Full threading (4 threads)\r\n logger.debug('Optimal WASM threads: 4', { platform: 'Desktop' });\r\n return 4;\r\n}\r\n\r\n/**\r\n * Check if WASM proxy mode should be enabled\r\n *\r\n * Proxy mode offloads inference to a Web Worker, but has issues:\r\n * - iOS: Triggers Safari 26 JSEP memory leak\r\n * - Mobile: Generally unstable\r\n *\r\n * @returns true if proxy mode is safe to enable\r\n */\r\nexport function shouldEnableWasmProxy(): boolean {\r\n // Always disable proxy. When proxy=true, WASM initializes in a Web Worker.\r\n // The WebGPU EP needs WASM on the main thread for model parsing. If a\r\n // consumer Vite-aliases all ORT imports to the same module, proxy=true\r\n // on one session causes \"WebAssembly is not initialized yet\" for WebGPU.\r\n return false;\r\n}\r\n\r\n/**\r\n * Detect Safari browser on any platform (macOS + iOS)\r\n *\r\n * Safari WebKit has bugs with ONNX Runtime's WebGPU multithreaded JSEP build\r\n * that crash session creation. Both iOS and macOS Safari are affected.\r\n *\r\n * @returns true if running in Safari on any platform\r\n */\r\nexport function isSafari(): boolean {\r\n if (typeof navigator === 'undefined') return false;\r\n const ua = navigator.userAgent.toLowerCase();\r\n // Safari: has \"safari\" but not Chrome, Chromium, CriOS, FxiOS, or Edge\r\n return /safari/.test(ua) && !/chrome|crios|fxios|chromium|edg/.test(ua);\r\n}\r\n\r\n/**\r\n * Check if Web Speech API is available in the browser\r\n *\r\n * The Web Speech API provides native speech recognition in Safari and Chrome.\r\n * On iOS Safari, this is significantly faster than Whisper WASM.\r\n *\r\n * @returns true if SpeechRecognition API is available\r\n */\r\nexport function isSpeechRecognitionAvailable(): boolean {\r\n if (typeof window === 'undefined') return false;\r\n return 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window;\r\n}\r\n\r\n/**\r\n * Recommend using native Safari Speech API over Whisper on iOS\r\n *\r\n * On iOS, Whisper ASR via WASM takes ~1.3s per inference (30% over target).\r\n * Safari's native Web Speech API is:\r\n * - Much faster (native implementation)\r\n * - Battery-efficient (no WASM overhead)\r\n * - No model download needed (saves 30-150MB)\r\n *\r\n * @returns true if on iOS or Safari with Speech API available\r\n */\r\nexport function shouldUseNativeASR(): boolean {\r\n const ios = isIOS();\r\n const safari = isSafari();\r\n const speechAvailable = isSpeechRecognitionAvailable();\r\n const result = (ios || safari) && speechAvailable;\r\n logger.debug('shouldUseNativeASR decision', { result, ios, safari, speechAvailable });\r\n return result;\r\n}\r\n\r\n/**\r\n * Recommend using server-side LAM over client-side on iOS\r\n *\r\n * On iOS, LAM A2E via WASM takes ~332ms per second of audio (3.3x over target).\r\n * Server-side inference with GPU can achieve ~50ms, providing:\r\n * - Real-time A2E (under 100ms target)\r\n * - Reduced iOS device thermal/battery impact\r\n * - Better user experience\r\n *\r\n * @returns true if on iOS (should use server-side A2E)\r\n */\r\nexport function shouldUseServerA2E(): boolean {\r\n return isIOS();\r\n}\r\n","/**\r\n * Lazy ONNX Runtime loader with conditional WebGPU/WASM bundle loading\r\n *\r\n * This module provides a way to dynamically load the appropriate ONNX Runtime bundle\r\n * based on the platform's capabilities. This is critical for iOS support because:\r\n *\r\n * 1. iOS Safari has WebGPU API but ONNX Runtime's WebGPU backend crashes\r\n * 2. Loading the WebGPU bundle wastes bandwidth and can cause issues\r\n * 3. WASM-only bundle is smaller and more reliable on iOS\r\n *\r\n * Usage:\r\n * ```typescript\r\n * const ort = await getOnnxRuntime('wasm'); // Load WASM-only bundle\r\n * const ort = await getOnnxRuntime('webgpu'); // Load WebGPU bundle (includes WASM)\r\n * ```\r\n *\r\n * @module inference/onnxLoader\r\n */\r\n\r\n// Type-only import for TypeScript (no runtime code loaded at import time)\r\n// At runtime, we dynamically import either 'onnxruntime-web' or 'onnxruntime-web/webgpu'\r\nimport type { InferenceSession, Tensor, Env } from 'onnxruntime-common';\r\n\r\n// Type alias for the ORT module (loaded dynamically)\r\nexport type OrtModule = {\r\n InferenceSession: typeof InferenceSession;\r\n Tensor: typeof Tensor;\r\n env: Env;\r\n};\r\n\r\n// Re-export session options type\r\nexport type SessionOptions = InferenceSession.SessionOptions;\r\nimport {\r\n RuntimeBackend,\r\n BackendPreference,\r\n isIOS,\r\n isIOSSafari,\r\n isSafari,\r\n isMobile,\r\n getOptimalWasmThreads,\r\n shouldEnableWasmProxy,\r\n resolveBackend,\r\n hasWebGPUApi,\r\n} from '../utils/runtime';\r\n\r\n// Re-export RuntimeBackend for consumers\r\nexport type { RuntimeBackend } from '../utils/runtime';\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('OnnxLoader');\r\n\r\n// Cached ONNX Runtime instance\r\nlet ortInstance: OrtModule | null = null;\r\nlet loadedBackend: RuntimeBackend | null = null;\r\n\r\n/** CDN path for ONNX Runtime WASM files — single source of truth */\r\nexport const WASM_CDN_PATH = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.2/dist/';\r\n\r\n/**\r\n * Check if WebGPU is available and likely to work\r\n *\r\n * This is more thorough than just checking navigator.gpu exists.\r\n * It actually requests an adapter to verify the GPU is accessible.\r\n *\r\n * @returns true if WebGPU is available and working\r\n */\r\nexport async function isWebGPUAvailable(): Promise<boolean> {\r\n // iOS 18+ exposes navigator.gpu, but the ORT WebGPU bundle uses the\r\n // asyncify WASM binary (ort-wasm-simd-threaded.asyncify.wasm) which\r\n // crashes WebKit's JIT on all iOS browsers. Return false early to\r\n // prevent probing the GPU adapter (which itself consumes memory).\r\n if (isIOS()) {\r\n logger.debug('WebGPU check: disabled on iOS (asyncify bundle crashes WebKit)');\r\n return false;\r\n }\r\n\r\n if (!hasWebGPUApi()) {\r\n logger.debug('WebGPU check: navigator.gpu not available', {\r\n isSecureContext: typeof window !== 'undefined' ? (window as any).isSecureContext : 'N/A',\r\n });\r\n return false;\r\n }\r\n\r\n try {\r\n const adapter = await navigator.gpu!.requestAdapter();\r\n if (!adapter) {\r\n logger.debug('WebGPU check: No adapter available');\r\n return false;\r\n }\r\n\r\n // Adapter probe is sufficient — requestDevice() allocates driver resources\r\n // that are immediately destroyed, wasting memory and potentially interfering\r\n // with the subsequent ORT session creation.\r\n logger.debug('WebGPU check: Available and working');\r\n return true;\r\n } catch (err) {\r\n logger.debug('WebGPU check: Error during availability check', { error: err });\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * iOS WASM memory patch\r\n *\r\n * ONNX Runtime's ort-wasm-simd-threaded.wasm creates\r\n * WebAssembly.Memory({initial:256, maximum:65536, shared:true}).\r\n * iOS Safari rejects this with \"RangeError: Out of memory\" because\r\n * shared WebAssembly.Memory with a 4GB maximum exceeds iOS limits.\r\n *\r\n * This patch intercepts WebAssembly.Memory construction on iOS to:\r\n * 1. Keep `shared` flag (required by ORT v1.23.2 threaded WASM)\r\n * 2. Cap `maximum` to 32768 pages (2GB)\r\n *\r\n * The 2GB cap balances memory headroom for large A2E models\r\n * (needs ~500MB WASM for weights + activations) while staying within\r\n * iOS WebKit's process limits on modern iPhones (6-8GB RAM).\r\n *\r\n * Applied once on first ORT load, stays active for the page lifetime.\r\n */\r\nlet iosWasmPatched = false;\r\n\r\nfunction applyIOSWasmMemoryPatch(): void {\r\n // Only patch on iOS Safari. iOS Chrome/Firefox (all use WebKit) may handle\r\n // the default 4GB maximum without the constructor wrapper interfering.\r\n // Safari specifically throws \"RangeError: Out of memory\" on 4GB shared memory.\r\n if (iosWasmPatched || !isIOSSafari()) return;\r\n iosWasmPatched = true;\r\n\r\n const OrigMemory = WebAssembly.Memory;\r\n const MAX_IOS_PAGES = 32768; // 32768 × 64KB = 2GB\r\n\r\n logger.info('Applying iOS WASM memory patch (max→2GB, shared preserved)');\r\n\r\n // Replace WebAssembly.Memory constructor to cap maximum pages.\r\n // ORT v1.23.2 threaded WASM binaries ALL declare shared:true in their\r\n // memory import. Setting shared:false causes a LinkError on instantiation.\r\n // Instead, keep shared:true and only cap maximum from 65536 (4GB) to\r\n // 32768 (2GB) to allow sufficient headroom for model weights + inference\r\n // activations. Log the actual descriptor for debugging.\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n (WebAssembly as any).Memory = function IOSPatchedMemory(\r\n descriptor: WebAssembly.MemoryDescriptor\r\n ): WebAssembly.Memory {\r\n const patched = { ...descriptor };\r\n\r\n if (patched.maximum !== undefined && patched.maximum > MAX_IOS_PAGES) {\r\n logger.info('iOS memory patch: capping maximum', {\r\n original: patched.maximum,\r\n capped: MAX_IOS_PAGES,\r\n shared: patched.shared,\r\n initial: patched.initial,\r\n });\r\n patched.maximum = MAX_IOS_PAGES;\r\n }\r\n\r\n return new OrigMemory(patched);\r\n };\r\n\r\n // Preserve prototype chain so instanceof checks still work\r\n (WebAssembly as any).Memory.prototype = OrigMemory.prototype;\r\n}\r\n\r\n/**\r\n * Configure WASM environment settings based on platform\r\n *\r\n * This must be called before creating any inference sessions.\r\n */\r\nfunction configureWasm(ort: OrtModule): void {\r\n // Set CDN path for WASM files\r\n ort.env.wasm.wasmPaths = WASM_CDN_PATH;\r\n\r\n // Platform-specific threading configuration\r\n const numThreads = getOptimalWasmThreads();\r\n const enableProxy = shouldEnableWasmProxy();\r\n\r\n ort.env.wasm.numThreads = numThreads;\r\n ort.env.wasm.simd = true; // SIMD always helps\r\n ort.env.wasm.proxy = enableProxy;\r\n\r\n logger.info('WASM configured', {\r\n numThreads,\r\n simd: true,\r\n proxy: enableProxy,\r\n platform: isIOS() ? 'iOS' : isMobile() ? 'Android' : 'Desktop',\r\n });\r\n}\r\n\r\n/**\r\n * Load ONNX Runtime with the specified backend\r\n *\r\n * This lazily loads the appropriate bundle:\r\n * - 'wasm': Loads onnxruntime-web (WASM-only, smaller)\r\n * - 'webgpu': Loads onnxruntime-web/webgpu (includes WebGPU + WASM fallback)\r\n *\r\n * Once loaded, the same instance is reused for all subsequent calls.\r\n * If you need to switch backends, you must reload the page.\r\n *\r\n * @param backend The backend to load ('webgpu' or 'wasm')\r\n * @returns The ONNX Runtime module\r\n */\r\nexport async function getOnnxRuntime(\r\n backend: RuntimeBackend\r\n): Promise<OrtModule> {\r\n // Return cached instance if same backend\r\n if (ortInstance && loadedBackend === backend) {\r\n return ortInstance;\r\n }\r\n\r\n // Warn if trying to switch backends (not supported without page reload)\r\n if (ortInstance && loadedBackend !== backend) {\r\n logger.warn(\r\n `ONNX Runtime already loaded with ${loadedBackend} backend. ` +\r\n `Cannot switch to ${backend}. Returning existing instance.`\r\n );\r\n return ortInstance;\r\n }\r\n\r\n logger.info(`Loading ONNX Runtime with ${backend} backend...`);\r\n\r\n // iOS: Patch WebAssembly.Memory before ORT loads its WASM binary.\r\n // Must be applied before any InferenceSession.create() call.\r\n applyIOSWasmMemoryPatch();\r\n\r\n try {\r\n if (backend === 'wasm' && (isIOS() || isSafari())) {\r\n // Safari (all platforms) + iOS (all browsers): Import via\r\n // 'onnxruntime-web/wasm' sub-path. This uses the plain WASM binaries\r\n // (ort-wasm-simd / ort-wasm-simd-threaded) WITHOUT JSEP/ASYNCIFY.\r\n //\r\n // The JSEP build (ort-wasm-simd-threaded.jsep) crashes WebKit's JIT\r\n // on iOS and macOS Safari due to ASYNCIFY code generation issues.\r\n // See: https://bugs.webkit.org/show_bug.cgi?id=262475\r\n //\r\n // With COEP/COOP headers removed for iOS (no SharedArrayBuffer),\r\n // ORT picks ort-wasm-simd.wasm (single-threaded, Case A = works).\r\n // On macOS Safari (COEP/COOP present), ORT picks\r\n // ort-wasm-simd-threaded.wasm (threaded but non-JSEP, Case B = works).\r\n const module = await import('onnxruntime-web/wasm');\r\n ortInstance = module.default || module;\r\n } else if (backend === 'wasm') {\r\n // Desktop WASM: Explicitly load /wasm sub-path to avoid Vite alias\r\n // that redirects 'onnxruntime-web' → 'onnxruntime-web/webgpu'.\r\n // This prevents double-loading when both WASM and WebGPU paths are requested.\r\n const module = await import('onnxruntime-web/wasm');\r\n ortInstance = module.default || module;\r\n } else {\r\n // Load WebGPU bundle (includes WASM fallback)\r\n const module = await import('onnxruntime-web/webgpu');\r\n ortInstance = module.default || module;\r\n }\r\n\r\n loadedBackend = backend;\r\n\r\n // Configure WASM settings (applies to both bundles)\r\n configureWasm(ortInstance);\r\n\r\n logger.info(`ONNX Runtime loaded successfully`, { backend });\r\n\r\n return ortInstance;\r\n } catch (err) {\r\n logger.error(`Failed to load ONNX Runtime with ${backend} backend`, {\r\n error: err,\r\n });\r\n throw new Error(\r\n `Failed to load ONNX Runtime: ${err instanceof Error ? err.message : String(err)}`\r\n );\r\n }\r\n}\r\n\r\n/**\r\n * Get the appropriate ONNX Runtime based on user preference\r\n *\r\n * This resolves the user's preference against platform capabilities\r\n * and loads the appropriate bundle.\r\n *\r\n * @param preference User's backend preference\r\n * @returns The ONNX Runtime module and the resolved backend\r\n */\r\nexport async function getOnnxRuntimeForPreference(\r\n preference: BackendPreference = 'auto'\r\n): Promise<{ ort: OrtModule; backend: RuntimeBackend }> {\r\n // Check WebGPU availability (skip for iOS)\r\n const webgpuAvailable = await isWebGPUAvailable();\r\n\r\n // Resolve preference to actual backend\r\n const backend = resolveBackend(preference, webgpuAvailable);\r\n\r\n logger.info('Resolved backend preference', {\r\n preference,\r\n webgpuAvailable,\r\n resolvedBackend: backend,\r\n });\r\n\r\n // Load the appropriate bundle\r\n const ort = await getOnnxRuntime(backend);\r\n\r\n return { ort, backend };\r\n}\r\n\r\n/**\r\n * Options to customize session creation beyond backend selection.\r\n */\r\nexport interface GetSessionOptionsConfig {\r\n /**\r\n * Disable graph optimization entirely on iOS.\r\n * Only needed for LAM (Wav2Vec2) whose complex transformer graph causes\r\n * ~750-950MB peak scratch memory during optimization, OOMing when other\r\n * models are already resident. Other models (SenseVoice, VAD) are fine\r\n * with 'basic' optimization.\r\n */\r\n iosDisableOptimization?: boolean;\r\n}\r\n\r\n/**\r\n * Get session options for creating an inference session\r\n *\r\n * This returns optimized session options based on the backend and platform.\r\n *\r\n * @param backend The backend being used\r\n * @param config Optional per-model overrides\r\n * @returns Session options for InferenceSession.create()\r\n */\r\nexport function getSessionOptions(\r\n backend: RuntimeBackend,\r\n config?: GetSessionOptionsConfig,\r\n): SessionOptions {\r\n if (backend === 'webgpu') {\r\n return {\r\n executionProviders: [\r\n {\r\n name: 'webgpu',\r\n preferredLayout: 'NHWC', // Reduces memory overhead for layout conversions\r\n } as const,\r\n ],\r\n graphOptimizationLevel: 'all',\r\n };\r\n }\r\n\r\n // WASM backend\r\n // iOS: reduce memory pressure during session creation.\r\n // 'basic' is safe for most models (SenseVoice, VAD).\r\n // LAM (Wav2Vec2) needs 'disabled' — its dual-head transformer graph\r\n // causes ~750-950MB peak scratch during even basic optimization,\r\n // OOMing when SenseVoice + VAD are already resident.\r\n // See: https://github.com/microsoft/onnxruntime/issues/13408\r\n if (isIOS()) {\r\n return {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: config?.iosDisableOptimization ? 'disabled' : 'basic',\r\n enableCpuMemArena: false,\r\n enableMemPattern: false,\r\n };\r\n }\r\n\r\n return {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: 'all',\r\n };\r\n}\r\n\r\n/**\r\n * Create an inference session with automatic fallback\r\n *\r\n * If WebGPU session creation fails, automatically falls back to WASM.\r\n *\r\n * @param modelBuffer The model data as ArrayBuffer\r\n * @param preferredBackend The preferred backend\r\n * @returns The created session and the backend used\r\n */\r\nexport async function createSessionWithFallback(\r\n modelBuffer: ArrayBuffer,\r\n preferredBackend: RuntimeBackend\r\n): Promise<{\r\n session: InferenceSession;\r\n backend: RuntimeBackend;\r\n}> {\r\n const ort = await getOnnxRuntime(preferredBackend);\r\n\r\n // Convert ArrayBuffer to Uint8Array for onnxruntime-common types\r\n const modelData = new Uint8Array(modelBuffer);\r\n\r\n if (preferredBackend === 'webgpu') {\r\n try {\r\n const options = getSessionOptions('webgpu');\r\n const session = await ort.InferenceSession.create(modelData, options);\r\n\r\n logger.info('Session created with WebGPU backend');\r\n return { session, backend: 'webgpu' };\r\n } catch (err) {\r\n logger.warn('WebGPU session creation failed, falling back to WASM', {\r\n error: err instanceof Error ? err.message : String(err),\r\n });\r\n // Fall through to WASM\r\n }\r\n }\r\n\r\n // WASM (primary or fallback)\r\n const options = getSessionOptions('wasm');\r\n const session = await ort.InferenceSession.create(modelData, options);\r\n\r\n logger.info('Session created with WASM backend');\r\n return { session, backend: 'wasm' };\r\n}\r\n\r\n/**\r\n * Race a promise against a timeout. Rejects with a descriptive error if the\r\n * timeout fires first. Used to guard iOS URL-pass-through model loads where\r\n * ORT manages the fetch internally and we can't retry.\r\n */\r\nexport function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {\r\n return new Promise<T>((resolve, reject) => {\r\n const timer = setTimeout(\r\n () => reject(new Error(`${label} timed out after ${ms}ms`)),\r\n ms,\r\n );\r\n promise.then(resolve, reject).finally(() => clearTimeout(timer));\r\n });\r\n}\r\n\r\n/**\r\n * Get the currently loaded backend (if any)\r\n */\r\nexport function getLoadedBackend(): RuntimeBackend | null {\r\n return loadedBackend;\r\n}\r\n\r\n/**\r\n * Check if ONNX Runtime has been loaded\r\n */\r\nexport function isOnnxRuntimeLoaded(): boolean {\r\n return ortInstance !== null;\r\n}\r\n\r\n/**\r\n * Preload ONNX Runtime and compile the WASM binary early\r\n *\r\n * Call this before loading heavy resources (Three.js, VRM models) to ensure\r\n * WASM memory is allocated in a clean JS heap, reducing iOS memory pressure.\r\n * Uses the singleton pattern — subsequent model loading reuses this instance.\r\n *\r\n * @param preference Backend preference (default: 'auto')\r\n * @returns The resolved backend that was loaded\r\n */\r\nexport async function preloadOnnxRuntime(\r\n preference: BackendPreference = 'auto'\r\n): Promise<RuntimeBackend> {\r\n if (ortInstance) {\r\n logger.info('ONNX Runtime already preloaded', { backend: loadedBackend });\r\n return loadedBackend!;\r\n }\r\n\r\n logger.info('Preloading ONNX Runtime...', { preference });\r\n const { backend } = await getOnnxRuntimeForPreference(preference);\r\n logger.info('ONNX Runtime preloaded', { backend });\r\n return backend;\r\n}\r\n","/**\r\n * Unified Inference Worker — single Web Worker hosting all ONNX models\r\n *\r\n * Runs all model loading and inference off the main thread, preventing\r\n * InferenceSession.create() from blocking the renderer (5-30s).\r\n *\r\n * Uses WebGPU when available (Chrome/Edge 113+), falls back to WASM.\r\n * On iOS, uses a single WASM instance to stay within the ~1-1.5GB tab limit.\r\n *\r\n * This worker hosts SenseVoice + A2E + Silero VAD + Kokoro TTS in a single\r\n * ORT instance. Same total model memory, but inference runs off-main-thread.\r\n *\r\n * Consumer usage:\r\n * ```typescript\r\n * const worker = new UnifiedInferenceWorker();\r\n * await worker.init();\r\n *\r\n * const asr = createSenseVoice({ modelUrl: '...', unifiedWorker: worker });\r\n * const lam = createA2E({ modelUrl: '...', unifiedWorker: worker });\r\n * const vad = createSileroVAD({ modelUrl: '...', unifiedWorker: worker });\r\n * ```\r\n *\r\n * @category Inference\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry';\r\nimport { isIOS } from '../utils/runtime';\r\nimport type { SenseVoiceResult, SenseVoiceModelInfo } from './SenseVoiceTypes';\r\nimport type { A2EModelInfo } from './A2EBackend';\r\nimport type { VADWorkerModelInfo } from './SileroVADTypes';\r\nimport { WASM_CDN_PATH } from './onnxLoader';\r\nimport { ErrorCodes } from '../logging/ErrorCodes';\r\n\r\nconst logger = createLogger('UnifiedInferenceWorker');\r\n\r\n/** Health state of the unified worker */\r\nexport type WorkerHealthState = 'healthy' | 'unhealthy' | 'recovering';\r\n\r\n// Health check constants\r\nconst MAX_CONSECUTIVE_FAILURES = 3;\r\nconst HEALTH_CHECK_TIMEOUT_MS = 10_000; // 10s — must exceed longest inference (Kokoro ~2s)\r\n\r\n// Timeouts per operation\r\nconst INIT_TIMEOUT_MS = 60_000; // 1 min — ORT WASM script fetch on slow networks\r\nconst SV_LOAD_TIMEOUT_MS = 300_000; // 5 min — 239MB with retries (3 × 120s fetch) + ORT init\r\nconst SV_INFER_TIMEOUT_MS = 10_000;\r\nconst LAM_LOAD_TIMEOUT_MS = 300_000; // 5 min — 192MB fp16 (opset 18, 608 nodes) with retries + ORT init\r\nconst LAM_INFER_TIMEOUT_MS = 15_000; // 15s — first WebGPU inference triggers shader compilation (5-15s)\r\nconst KOKORO_LOAD_TIMEOUT_MS = 300_000; // 5 min — 92MB model + ORT init\r\nconst KOKORO_INFER_TIMEOUT_MS = 120_000; // 2 min — TTS can be slow for long text\r\nconst VAD_LOAD_TIMEOUT_MS = 120_000; // 2 min — 2MB model + ORT script fetch on slow networks\r\nconst VAD_INFER_TIMEOUT_MS = 1_000;\r\nconst DISPOSE_TIMEOUT_MS = 5_000;\r\n\r\n/** Resolve a potentially relative URL to absolute (blob workers have no base URL) */\r\nfunction resolveUrl(url: string): string {\r\n if (/^https?:\\/\\//i.test(url) || /^blob:/i.test(url)) return url;\r\n try {\r\n return new URL(url, globalThis.location?.origin ?? 'https://localhost').href;\r\n } catch {\r\n return url;\r\n }\r\n}\r\n\r\nlet requestCounter = 0;\r\nfunction nextRequestId(): string {\r\n return `req_${++requestCounter}_${Date.now()}`;\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Inline Worker Script\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nconst WORKER_SCRIPT = `\r\n// Unified Inference Worker Script\r\n// Hosts SenseVoice + A2E + Silero VAD in a single ORT instance\r\n\r\nvar ort = null;\r\n\r\nfunction fetchWithTimeout(url, timeoutMs) {\r\n var controller = new AbortController();\r\n var timer = setTimeout(function() { controller.abort(); }, timeoutMs);\r\n return fetch(url, { signal: controller.signal }).finally(function() { clearTimeout(timer); });\r\n}\r\n\r\n// SenseVoice state\r\nvar svSession = null;\r\nvar svTokenMap = null;\r\nvar svNegMean = null;\r\nvar svInvStddev = null;\r\nvar svLanguageId = 0;\r\nvar svTextNormId = 14;\r\nvar svVocabSize = 0;\r\n\r\n// LAM (A2E) state\r\nvar lamSession = null;\r\nvar lamNumIdentityClasses = 12;\r\n\r\n// Kokoro TTS state\r\nvar kokoroSession = null;\r\n\r\n// Silero VAD state\r\nvar vadSession = null;\r\nvar vadSampleRate = 16000;\r\nvar vadChunkSize = 512;\r\nvar vadContextSize = 64;\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// kaldiFbank.ts — inlined as plain JavaScript\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nfunction fft(re, im) {\r\n var n = re.length;\r\n for (var i = 1, j = 0; i < n; i++) {\r\n var bit = n >> 1;\r\n while (j & bit) { j ^= bit; bit >>= 1; }\r\n j ^= bit;\r\n if (i < j) {\r\n var tmp = re[i]; re[i] = re[j]; re[j] = tmp;\r\n tmp = im[i]; im[i] = im[j]; im[j] = tmp;\r\n }\r\n }\r\n for (var len = 2; len <= n; len *= 2) {\r\n var halfLen = len / 2;\r\n var angle = -2 * Math.PI / len;\r\n var wRe = Math.cos(angle);\r\n var wIm = Math.sin(angle);\r\n for (var i = 0; i < n; i += len) {\r\n var curRe = 1, curIm = 0;\r\n for (var j = 0; j < halfLen; j++) {\r\n var a = i + j, b = a + halfLen;\r\n var tRe = curRe * re[b] - curIm * im[b];\r\n var tIm = curRe * im[b] + curIm * re[b];\r\n re[b] = re[a] - tRe; im[b] = im[a] - tIm;\r\n re[a] += tRe; im[a] += tIm;\r\n var nextRe = curRe * wRe - curIm * wIm;\r\n curIm = curRe * wIm + curIm * wRe;\r\n curRe = nextRe;\r\n }\r\n }\r\n }\r\n}\r\n\r\nfunction htkMel(freq) { return 1127.0 * Math.log(1.0 + freq / 700.0); }\r\nfunction htkMelInverse(mel) { return 700.0 * (Math.exp(mel / 1127.0) - 1.0); }\r\n\r\nfunction buildMelFilterbank(numBins, fftSize, sampleRate, lowFreq, highFreq) {\r\n var numFftBins = fftSize / 2 + 1;\r\n var lowMel = htkMel(lowFreq);\r\n var highMel = htkMel(highFreq);\r\n var melPoints = new Float64Array(numBins + 2);\r\n for (var i = 0; i < numBins + 2; i++) {\r\n melPoints[i] = lowMel + (highMel - lowMel) * i / (numBins + 1);\r\n }\r\n var binFreqs = new Float64Array(numBins + 2);\r\n for (var i = 0; i < numBins + 2; i++) {\r\n binFreqs[i] = htkMelInverse(melPoints[i]) * fftSize / sampleRate;\r\n }\r\n var filters = [];\r\n for (var m = 0; m < numBins; m++) {\r\n var left = binFreqs[m], center = binFreqs[m + 1], right = binFreqs[m + 2];\r\n var startBin = Math.max(0, Math.ceil(left));\r\n var endBin = Math.min(numFftBins - 1, Math.floor(right));\r\n var weights = new Float32Array(endBin - startBin + 1);\r\n for (var k = startBin; k <= endBin; k++) {\r\n if (k <= center) {\r\n weights[k - startBin] = (center - left) > 0 ? (k - left) / (center - left) : 0;\r\n } else {\r\n weights[k - startBin] = (right - center) > 0 ? (right - k) / (right - center) : 0;\r\n }\r\n }\r\n filters.push({ startBin: startBin, weights: weights });\r\n }\r\n return filters;\r\n}\r\n\r\nfunction createHammingWindow(length) {\r\n var w = new Float32Array(length);\r\n for (var i = 0; i < length; i++) {\r\n w[i] = 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (length - 1));\r\n }\r\n return w;\r\n}\r\n\r\nfunction computeKaldiFbank(audio, sampleRate, numMelBins, opts) {\r\n var frameLengthMs = (opts && opts.frameLengthMs !== undefined) ? opts.frameLengthMs : 25;\r\n var frameShiftMs = (opts && opts.frameShiftMs !== undefined) ? opts.frameShiftMs : 10;\r\n var lowFreq = (opts && opts.lowFreq !== undefined) ? opts.lowFreq : 20;\r\n var highFreq = (opts && opts.highFreq !== undefined) ? opts.highFreq : (sampleRate / 2);\r\n var dither = (opts && opts.dither !== undefined) ? opts.dither : 0;\r\n var preemphasis = (opts && opts.preemphasis !== undefined) ? opts.preemphasis : 0.97;\r\n\r\n var frameLengthSamples = Math.round(sampleRate * frameLengthMs / 1000);\r\n var frameShiftSamples = Math.round(sampleRate * frameShiftMs / 1000);\r\n\r\n var scaled = new Float32Array(audio.length);\r\n for (var i = 0; i < audio.length; i++) { scaled[i] = audio[i] * 32768; }\r\n\r\n if (dither > 0) {\r\n for (var i = 0; i < scaled.length; i++) {\r\n var u1 = Math.random(), u2 = Math.random();\r\n scaled[i] += dither * Math.sqrt(-2 * Math.log(u1 + 1e-10)) * Math.cos(2 * Math.PI * u2);\r\n }\r\n }\r\n\r\n var numFrames = Math.max(0, Math.floor((scaled.length - frameLengthSamples) / frameShiftSamples) + 1);\r\n if (numFrames === 0) return new Float32Array(0);\r\n\r\n var fftSize = 1;\r\n while (fftSize < frameLengthSamples) fftSize *= 2;\r\n var numFftBins = fftSize / 2 + 1;\r\n\r\n var window = createHammingWindow(frameLengthSamples);\r\n var filters = buildMelFilterbank(numMelBins, fftSize, sampleRate, lowFreq, highFreq);\r\n var output = new Float32Array(numFrames * numMelBins);\r\n var fftRe = new Float64Array(fftSize);\r\n var fftIm = new Float64Array(fftSize);\r\n\r\n for (var f = 0; f < numFrames; f++) {\r\n var offset = f * frameShiftSamples;\r\n fftRe.fill(0); fftIm.fill(0);\r\n for (var i = 0; i < frameLengthSamples; i++) {\r\n var sample = scaled[offset + i];\r\n if (preemphasis > 0 && i > 0) {\r\n sample -= preemphasis * scaled[offset + i - 1];\r\n } else if (preemphasis > 0 && i === 0 && offset > 0) {\r\n sample -= preemphasis * scaled[offset - 1];\r\n }\r\n fftRe[i] = sample * window[i];\r\n }\r\n fft(fftRe, fftIm);\r\n var outOffset = f * numMelBins;\r\n for (var m = 0; m < numMelBins; m++) {\r\n var filter = filters[m];\r\n var energy = 0;\r\n for (var k = 0; k < filter.weights.length; k++) {\r\n var bin = filter.startBin + k;\r\n if (bin < numFftBins) {\r\n var powerSpec = fftRe[bin] * fftRe[bin] + fftIm[bin] * fftIm[bin];\r\n energy += filter.weights[k] * powerSpec;\r\n }\r\n }\r\n output[outOffset + m] = Math.log(Math.max(energy, 1e-10));\r\n }\r\n }\r\n return output;\r\n}\r\n\r\nfunction applyLFR(features, featureDim, lfrM, lfrN) {\r\n var numFrames = features.length / featureDim;\r\n if (numFrames === 0) return new Float32Array(0);\r\n var leftPad = Math.floor((lfrM - 1) / 2);\r\n var paddedLen = numFrames + leftPad;\r\n var numOutputFrames = Math.ceil(paddedLen / lfrN);\r\n var outputDim = featureDim * lfrM;\r\n var output = new Float32Array(numOutputFrames * outputDim);\r\n for (var i = 0; i < numOutputFrames; i++) {\r\n var startFrame = i * lfrN - leftPad;\r\n for (var j = 0; j < lfrM; j++) {\r\n var srcFrame = startFrame + j;\r\n if (srcFrame < 0) srcFrame = 0;\r\n if (srcFrame >= numFrames) srcFrame = numFrames - 1;\r\n var srcOffset = srcFrame * featureDim;\r\n var dstOffset = i * outputDim + j * featureDim;\r\n for (var k = 0; k < featureDim; k++) {\r\n output[dstOffset + k] = features[srcOffset + k];\r\n }\r\n }\r\n }\r\n return output;\r\n}\r\n\r\nfunction applyCMVN(features, dim, negMeanVec, invStddevVec) {\r\n for (var i = 0; i < features.length; i++) {\r\n var d = i % dim;\r\n features[i] = (features[i] + negMeanVec[d]) * invStddevVec[d];\r\n }\r\n return features;\r\n}\r\n\r\nfunction parseCMVNFromMetadata(negMeanStr, invStddevStr) {\r\n var negMeanArr = new Float32Array(\r\n negMeanStr.split(',').map(function(s) { return parseFloat(s.trim()); })\r\n );\r\n var invStddevArr = new Float32Array(\r\n invStddevStr.split(',').map(function(s) { return parseFloat(s.trim()); })\r\n );\r\n return { negMean: negMeanArr, invStddev: invStddevArr };\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// ctcDecoder.ts — inlined as plain JavaScript\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nvar LANGUAGE_IDS = { 0: 'auto', 3: 'zh', 4: 'en', 7: 'yue', 11: 'ja', 12: 'ko', 13: 'nospeech' };\r\nvar TEXT_NORM_IDS = { 14: 'with_itn', 15: 'without_itn' };\r\n\r\nfunction resolveLanguageIdW(language) {\r\n var map = { auto: 0, zh: 3, en: 4, yue: 7, ja: 11, ko: 12 };\r\n return map[language] !== undefined ? map[language] : 0;\r\n}\r\n\r\nfunction resolveTextNormIdW(textNorm) {\r\n return textNorm === 'without_itn' ? 15 : 14;\r\n}\r\n\r\nfunction parseTokensFile(content) {\r\n var map = new Map();\r\n var lines = content.split('\\\\n');\r\n for (var idx = 0; idx < lines.length; idx++) {\r\n var trimmed = lines[idx].trim();\r\n if (!trimmed) continue;\r\n var lastSpace = trimmed.lastIndexOf(' ');\r\n if (lastSpace === -1) continue;\r\n var token = trimmed.substring(0, lastSpace);\r\n var id = parseInt(trimmed.substring(lastSpace + 1), 10);\r\n if (!isNaN(id)) map.set(id, token);\r\n }\r\n return map;\r\n}\r\n\r\nfunction parseStructuredToken(token) {\r\n // NOTE: Double-escape required — this code lives inside a JS template literal.\r\n // \\\\| in source becomes \\| in the worker (literal pipe). Single \\| becomes | (alternation).\r\n var match = token.match(/^<\\\\|(.+)\\\\|>$/);\r\n if (!match) return null;\r\n var value = match[1];\r\n if (value === 'zh' || value === 'en' || value === 'ja' || value === 'ko' || value === 'yue' || value === 'nospeech') {\r\n return { type: 'language', value: value };\r\n }\r\n var emotions = ['HAPPY', 'SAD', 'ANGRY', 'NEUTRAL', 'FEARFUL', 'DISGUSTED', 'SURPRISED', 'EMO_UNKNOWN'];\r\n if (emotions.indexOf(value) !== -1) return { type: 'emotion', value: value };\r\n var events = ['Speech', 'BGM', 'Applause', 'Laughter', 'Crying', 'Coughing', 'Sneezing', 'EVENT_UNKNOWN'];\r\n if (events.indexOf(value) !== -1) return { type: 'event', value: value };\r\n if (value === 'withitn' || value === 'woitn' || value === 'with_itn' || value === 'without_itn') {\r\n return { type: 'textnorm', value: value };\r\n }\r\n return null;\r\n}\r\n\r\nfunction ctcGreedyDecode(logits, seqLen, vocabSz, tokenMapLocal) {\r\n var tokenIds = [];\r\n for (var t = 0; t < seqLen; t++) {\r\n var offset = t * vocabSz;\r\n var maxIdx = 0, maxVal = logits[offset];\r\n for (var v = 1; v < vocabSz; v++) {\r\n if (logits[offset + v] > maxVal) { maxVal = logits[offset + v]; maxIdx = v; }\r\n }\r\n tokenIds.push(maxIdx);\r\n }\r\n var collapsed = [], prev = -1;\r\n for (var idx = 0; idx < tokenIds.length; idx++) {\r\n var id = tokenIds[idx];\r\n if (id !== prev) { collapsed.push(id); prev = id; }\r\n }\r\n var filtered = collapsed.filter(function(id) { return id !== 0 && id !== 1 && id !== 2; });\r\n var language = undefined, emotion = undefined, event = undefined;\r\n var textTokens = [];\r\n for (var idx = 0; idx < filtered.length; idx++) {\r\n var id = filtered[idx];\r\n var token = tokenMapLocal.get(id);\r\n if (!token) continue;\r\n var structured = parseStructuredToken(token);\r\n if (structured) {\r\n if (structured.type === 'language') language = structured.value;\r\n else if (structured.type === 'emotion') emotion = structured.value;\r\n else if (structured.type === 'event') event = structured.value;\r\n } else {\r\n textTokens.push(token);\r\n }\r\n }\r\n var text = textTokens.join('');\r\n // NOTE: Double-escape required — template literal context. \\\\u2581 produces \\u2581 in worker.\r\n text = text.replace(/\\\\u2581/g, ' ').trim();\r\n return { text: text, language: language, emotion: emotion, event: event };\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// blendshapeUtils.ts — inlined\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nvar SYMMETRIC_INDEX_PAIRS = [\r\n [23, 25], [32, 38], [43, 44], [29, 30], [27, 28], [45, 46],\r\n [35, 36], [47, 48], [33, 34], [49, 50], [6, 7], [0, 1],\r\n [3, 4], [8, 9], [16, 17], [10, 11], [12, 13], [14, 15],\r\n [18, 19], [20, 21],\r\n];\r\n\r\nfunction symmetrizeBlendshapes(frame) {\r\n var result = new Float32Array(frame);\r\n for (var p = 0; p < SYMMETRIC_INDEX_PAIRS.length; p++) {\r\n var lIdx = SYMMETRIC_INDEX_PAIRS[p][0], rIdx = SYMMETRIC_INDEX_PAIRS[p][1];\r\n var avg = (frame[lIdx] + frame[rIdx]) / 2;\r\n result[lIdx] = avg;\r\n result[rIdx] = avg;\r\n }\r\n return result;\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Shared ORT loader (WebGPU detection + fallback to WASM)\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nvar workerBackend = 'wasm';\r\n\r\nasync function loadOrt(wasmPaths, isIOSDevice) {\r\n if (ort) return;\r\n\r\n // Detect WebGPU in worker (Chrome/Edge 113+, not iOS/Safari)\r\n // Safari (macOS + iOS) must use WASM — the JSEP/ASYNCIFY binary in\r\n // ort.webgpu.min.js crashes WebKit's JIT compiler.\r\n var isSafariWorker = typeof navigator !== 'undefined' && /safari/i.test(navigator.userAgent) && !/chrome|crios|fxios|chromium|edg/i.test(navigator.userAgent);\r\n var hasWebGPU = false;\r\n var webgpuReason = '';\r\n if (isIOSDevice) {\r\n webgpuReason = 'iOS device';\r\n } else if (isSafariWorker) {\r\n webgpuReason = 'Safari (JSEP/ASYNCIFY crash)';\r\n } else if (typeof navigator === 'undefined' || !navigator.gpu) {\r\n webgpuReason = 'navigator.gpu unavailable';\r\n } else {\r\n try {\r\n var adapter = await navigator.gpu.requestAdapter();\r\n if (adapter) {\r\n hasWebGPU = true;\r\n } else {\r\n webgpuReason = 'requestAdapter returned null';\r\n }\r\n } catch (e) {\r\n webgpuReason = 'requestAdapter failed: ' + String(e);\r\n }\r\n }\r\n if (!hasWebGPU && webgpuReason) {\r\n console.warn('[UnifiedWorker] WebGPU unavailable: ' + webgpuReason + ', falling back to WASM');\r\n }\r\n\r\n var ortUrl;\r\n if (hasWebGPU) {\r\n ortUrl = wasmPaths + 'ort.webgpu.min.js';\r\n workerBackend = 'webgpu';\r\n } else {\r\n ortUrl = wasmPaths + 'ort.wasm.min.js';\r\n workerBackend = 'wasm';\r\n }\r\n\r\n var response = await fetchWithTimeout(ortUrl, 30000);\r\n var scriptText = await response.text();\r\n // SEC-01: Validate CDN response is JavaScript, not an HTML error page\r\n var trimmed = scriptText.trimStart();\r\n if (trimmed.startsWith('<!') || trimmed.startsWith('<html') || trimmed.startsWith('<HTML')) {\r\n throw new Error('CDN returned HTML instead of JavaScript — check ORT CDN URL: ' + ortUrl);\r\n }\r\n var blob = new Blob([scriptText], { type: 'application/javascript' });\r\n var blobUrl = URL.createObjectURL(blob);\r\n importScripts(blobUrl);\r\n URL.revokeObjectURL(blobUrl);\r\n ort = self.ort;\r\n ort.env.wasm.wasmPaths = wasmPaths;\r\n ort.env.wasm.numThreads = isIOSDevice ? 1 : 4;\r\n ort.env.wasm.simd = true;\r\n ort.env.wasm.proxy = false;\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// SenseVoice handlers\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nasync function svLoad(msg) {\r\n var tokensResponse = await fetchWithTimeout(msg.tokensUrl, 30000);\r\n if (!tokensResponse.ok) throw new Error('Failed to fetch tokens.txt: ' + tokensResponse.status);\r\n var tokensText = await tokensResponse.text();\r\n svTokenMap = parseTokensFile(tokensText);\r\n svLanguageId = msg.language;\r\n svTextNormId = msg.textNorm;\r\n\r\n var sessionOptions = { executionProviders: ['wasm'], graphOptimizationLevel: 'all' };\r\n if (msg.isIOS) {\r\n svSession = await ort.InferenceSession.create(msg.modelUrl, sessionOptions);\r\n } else {\r\n var modelResponse = await fetchWithTimeout(msg.modelUrl, 120000);\r\n if (!modelResponse.ok) throw new Error('Failed to fetch model: ' + modelResponse.status);\r\n var modelBuffer = await modelResponse.arrayBuffer();\r\n svSession = await ort.InferenceSession.create(new Uint8Array(modelBuffer), sessionOptions);\r\n }\r\n\r\n try {\r\n var metadata = svSession.handler && svSession.handler.metadata;\r\n if (metadata && metadata.neg_mean && metadata.inv_stddev) {\r\n var cmvn = parseCMVNFromMetadata(metadata.neg_mean, metadata.inv_stddev);\r\n svNegMean = cmvn.negMean;\r\n svInvStddev = cmvn.invStddev;\r\n }\r\n } catch (e) { /* CMVN not available */ }\r\n\r\n svVocabSize = 0;\r\n svTokenMap.forEach(function(val, key) { if (key >= svVocabSize) svVocabSize = key + 1; });\r\n\r\n return {\r\n vocabSize: svVocabSize,\r\n inputNames: svSession.inputNames.slice(),\r\n outputNames: svSession.outputNames.slice(),\r\n };\r\n}\r\n\r\nasync function svTranscribe(audio) {\r\n var preprocessStart = performance.now();\r\n var fbank = computeKaldiFbank(audio, 16000, 80);\r\n var numFrames = fbank.length / 80;\r\n if (numFrames === 0) {\r\n return { text: '', inferenceTimeMs: performance.now() - preprocessStart, preprocessTimeMs: performance.now() - preprocessStart };\r\n }\r\n var lfrFeatures = applyLFR(fbank, 80, 7, 6);\r\n var numLfrFrames = lfrFeatures.length / 560;\r\n if (svNegMean && svInvStddev) applyCMVN(lfrFeatures, 560, svNegMean, svInvStddev);\r\n var preprocessTimeMs = performance.now() - preprocessStart;\r\n\r\n var feeds = {\r\n x: new ort.Tensor('float32', lfrFeatures, [1, numLfrFrames, 560]),\r\n x_length: new ort.Tensor('int32', new Int32Array([numLfrFrames]), [1]),\r\n language: new ort.Tensor('int32', new Int32Array([svLanguageId]), [1]),\r\n text_norm: new ort.Tensor('int32', new Int32Array([svTextNormId]), [1]),\r\n };\r\n var results = await svSession.run(feeds);\r\n var logitsOutput = results['logits'];\r\n if (!logitsOutput) throw new Error('Model output missing \"logits\" tensor');\r\n\r\n var decoded = ctcGreedyDecode(logitsOutput.data, logitsOutput.dims[1], logitsOutput.dims[2], svTokenMap);\r\n var totalTimeMs = performance.now() - preprocessStart;\r\n\r\n return {\r\n text: decoded.text, language: decoded.language, emotion: decoded.emotion, event: decoded.event,\r\n inferenceTimeMs: totalTimeMs, preprocessTimeMs: preprocessTimeMs,\r\n };\r\n}\r\n\r\nasync function svDispose() {\r\n if (svSession) { await svSession.release(); svSession = null; }\r\n svTokenMap = null; svNegMean = null; svInvStddev = null;\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// LAM (A2E) handlers\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nasync function lamLoad(msg) {\r\n var useWebGPU = workerBackend === 'webgpu';\r\n var sessionOptions;\r\n if (msg.isIOS) {\r\n sessionOptions = {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: 'basic',\r\n enableCpuMemArena: false,\r\n enableMemPattern: false,\r\n };\r\n } else {\r\n sessionOptions = {\r\n executionProviders: useWebGPU ? ['webgpu', 'wasm'] : ['wasm'],\r\n graphOptimizationLevel: 'all',\r\n };\r\n }\r\n var dataFilename = msg.externalDataUrl ? msg.externalDataUrl.split('/').pop() : null;\r\n\r\n if (msg.isIOS) {\r\n // iOS: pass URLs directly to ORT (avoids JS heap OOM for large external data)\r\n if (msg.externalDataUrl && dataFilename) {\r\n sessionOptions.externalData = [{ path: dataFilename, data: msg.externalDataUrl }];\r\n }\r\n lamSession = await ort.InferenceSession.create(msg.modelUrl, sessionOptions);\r\n } else if (msg.externalDataUrl && dataFilename) {\r\n // URL pass-through: ORT fetches 192MB weights directly into WASM/GPU memory.\r\n // Avoids ~384MB JS heap spike. Browser HTTP cache handles fast reloads.\r\n sessionOptions.externalData = [{ path: dataFilename, data: msg.externalDataUrl }];\r\n lamSession = await ort.InferenceSession.create(msg.modelUrl, sessionOptions);\r\n } else {\r\n var graphResponse = await fetchWithTimeout(msg.modelUrl, 120000);\r\n if (!graphResponse.ok) throw new Error('Failed to fetch LAM graph: ' + graphResponse.status);\r\n var graphBuffer = await graphResponse.arrayBuffer();\r\n lamSession = await ort.InferenceSession.create(new Uint8Array(graphBuffer), sessionOptions);\r\n }\r\n\r\n lamNumIdentityClasses = msg.numIdentityClasses || 12;\r\n\r\n // Warmup\r\n var warmupAudio = new Float32Array(16000);\r\n var identity = new Float32Array(lamNumIdentityClasses);\r\n identity[0] = 1.0;\r\n var audioTensor = new ort.Tensor('float32', warmupAudio, [1, 16000]);\r\n var identityTensor = new ort.Tensor('float32', identity, [1, lamNumIdentityClasses]);\r\n await lamSession.run({ audio: audioTensor, identity: identityTensor });\r\n\r\n return {\r\n inputNames: lamSession.inputNames.slice(),\r\n outputNames: lamSession.outputNames.slice(),\r\n backend: workerBackend,\r\n };\r\n}\r\n\r\nasync function lamInfer(audio, identityIndex) {\r\n var audioTensor = new ort.Tensor('float32', audio, [1, audio.length]);\r\n var identity = new Float32Array(lamNumIdentityClasses);\r\n identity[identityIndex || 0] = 1.0;\r\n var identityTensor = new ort.Tensor('float32', identity, [1, lamNumIdentityClasses]);\r\n\r\n var results = await lamSession.run({ audio: audioTensor, identity: identityTensor });\r\n var blendshapeOutput = results['blendshapes'];\r\n if (!blendshapeOutput) throw new Error('Missing blendshapes output from LAM model');\r\n\r\n var blendshapeData = blendshapeOutput.data;\r\n var numFrames = blendshapeOutput.dims[1];\r\n var numBlendshapes = blendshapeOutput.dims[2];\r\n\r\n var flatBuffer = new Float32Array(numFrames * numBlendshapes);\r\n for (var f = 0; f < numFrames; f++) {\r\n var offset = f * numBlendshapes;\r\n var rawFrame = blendshapeData.slice(offset, offset + numBlendshapes);\r\n var symmetrized = symmetrizeBlendshapes(rawFrame);\r\n flatBuffer.set(symmetrized, offset);\r\n }\r\n return { flatBuffer: flatBuffer, numFrames: numFrames, numBlendshapes: numBlendshapes };\r\n}\r\n\r\nasync function lamDispose() {\r\n if (lamSession) { await lamSession.release(); lamSession = null; }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Kokoro TTS handlers\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nasync function kokoroLoad(msg) {\r\n // Kokoro's mixed-precision transformer graph OOMs with 'all' optimization (~1000MB WASM peak).\r\n // 'basic' is sufficient — WebGPU inference speed comes from GPU parallelism, not graph fusion.\r\n var sessionOptions = { executionProviders: ['wasm'], graphOptimizationLevel: 'basic' };\r\n\r\n var url = msg.modelUrl;\r\n var externalDataUrl = msg.externalDataUrl || null;\r\n var dataFilename = externalDataUrl ? externalDataUrl.split('/').pop() : null;\r\n\r\n if (externalDataUrl && dataFilename) {\r\n // URL pass-through: ORT fetches weights directly into WASM/GPU memory.\r\n // Avoids ~312MB JS heap spike. Browser HTTP cache handles fast reloads.\r\n sessionOptions.externalData = [{ path: dataFilename, data: externalDataUrl }];\r\n kokoroSession = await ort.InferenceSession.create(url, sessionOptions);\r\n } else if (msg.isIOS) {\r\n kokoroSession = await ort.InferenceSession.create(url, sessionOptions);\r\n } else {\r\n var response = await fetchWithTimeout(url, 120000);\r\n if (!response.ok) throw new Error('Failed to fetch Kokoro model: ' + response.status);\r\n var modelBuffer = await response.arrayBuffer();\r\n kokoroSession = await ort.InferenceSession.create(new Uint8Array(modelBuffer), sessionOptions);\r\n }\r\n\r\n return { ok: true };\r\n}\r\n\r\nasync function kokoroInfer(tokens, style, speed) {\r\n var inputIds;\r\n try {\r\n inputIds = new BigInt64Array(tokens.length);\r\n for (var i = 0; i < tokens.length; i++) {\r\n inputIds[i] = BigInt(tokens[i]);\r\n }\r\n } catch (e) {\r\n inputIds = tokens.map(function(t) { return BigInt(t); });\r\n }\r\n\r\n var feeds = {\r\n input_ids: new ort.Tensor('int64', inputIds, [1, tokens.length]),\r\n style: new ort.Tensor('float32', new Float32Array(style), [1, style.length]),\r\n speed: new ort.Tensor('float32', new Float32Array([speed]), [1]),\r\n };\r\n\r\n var results = await kokoroSession.run(feeds);\r\n var outputNames = Object.keys(results);\r\n if (outputNames.length === 0) throw new Error('KokoroTTS model returned no outputs');\r\n\r\n var output = results[outputNames[0]];\r\n return new Float32Array(output.data);\r\n}\r\n\r\nasync function kokoroDispose() {\r\n if (kokoroSession) { await kokoroSession.release(); kokoroSession = null; }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Silero VAD handlers\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nasync function vadLoad(msg) {\r\n vadSampleRate = msg.sampleRate;\r\n vadChunkSize = vadSampleRate === 16000 ? 512 : 256;\r\n vadContextSize = vadSampleRate === 16000 ? 64 : 32;\r\n\r\n var response = await fetchWithTimeout(msg.modelUrl, 60000);\r\n if (!response.ok) throw new Error('Failed to fetch VAD model: ' + response.status);\r\n var modelBuffer = await response.arrayBuffer();\r\n vadSession = await ort.InferenceSession.create(new Uint8Array(modelBuffer), {\r\n executionProviders: ['wasm'],\r\n graphOptimizationLevel: 'all',\r\n });\r\n\r\n return {\r\n inputNames: vadSession.inputNames.slice(),\r\n outputNames: vadSession.outputNames.slice(),\r\n };\r\n}\r\n\r\nasync function vadProcess(audio, state, context) {\r\n var inputSize = vadContextSize + vadChunkSize;\r\n var inputBuffer = new Float32Array(inputSize);\r\n inputBuffer.set(context, 0);\r\n inputBuffer.set(audio, vadContextSize);\r\n\r\n var inputTensor = new ort.Tensor('float32', new Float32Array(inputBuffer), [1, inputSize]);\r\n var stateTensor = new ort.Tensor('float32', new Float32Array(state), [2, 1, 128]);\r\n var srTensor;\r\n try {\r\n srTensor = new ort.Tensor('int64', new BigInt64Array([BigInt(vadSampleRate)]), []);\r\n } catch (e) {\r\n srTensor = new ort.Tensor('int64', [BigInt(vadSampleRate)], []);\r\n }\r\n\r\n var feeds = { 'input': inputTensor, 'state': stateTensor, 'sr': srTensor };\r\n var results = await vadSession.run(feeds);\r\n var outputTensor = results['output'];\r\n var newStateTensor = results['stateN'] || results['state'];\r\n if (!outputTensor) throw new Error('Missing output tensor from VAD model');\r\n\r\n return { probability: outputTensor.data[0], newState: new Float32Array(newStateTensor.data) };\r\n}\r\n\r\nfunction vadCreateInitialState() {\r\n return new Float32Array(2 * 1 * 128);\r\n}\r\n\r\nasync function vadDispose() {\r\n if (vadSession) { await vadSession.release(); vadSession = null; }\r\n}\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Message handler — serialized queue prevents concurrent ORT operations\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\nvar messageQueue = Promise.resolve();\r\n\r\nself.onmessage = function(e) {\r\n messageQueue = messageQueue.then(function() {\r\n return handleMessage(e);\r\n });\r\n};\r\n\r\nasync function handleMessage(e) {\r\n var msg = e.data;\r\n var requestId = msg.requestId;\r\n\r\n try {\r\n switch (msg.type) {\r\n case 'init': {\r\n const startTime = performance.now();\r\n await loadOrt(msg.wasmPaths, msg.isIOS);\r\n self.postMessage({ type: 'init:done', requestId: requestId, loadTimeMs: performance.now() - startTime, backend: workerBackend });\r\n break;\r\n }\r\n\r\n case 'sv:load': {\r\n const startTime = performance.now();\r\n var info = await svLoad(msg);\r\n self.postMessage({\r\n type: 'sv:loaded', requestId: requestId, vocabSize: info.vocabSize,\r\n inputNames: info.inputNames, outputNames: info.outputNames,\r\n loadTimeMs: performance.now() - startTime,\r\n });\r\n break;\r\n }\r\n\r\n case 'sv:transcribe': {\r\n var result = await svTranscribe(msg.audio);\r\n self.postMessage({\r\n type: 'sv:result', requestId: requestId,\r\n text: result.text, language: result.language, emotion: result.emotion, event: result.event,\r\n inferenceTimeMs: result.inferenceTimeMs, preprocessTimeMs: result.preprocessTimeMs,\r\n });\r\n break;\r\n }\r\n\r\n case 'sv:dispose': {\r\n await svDispose();\r\n self.postMessage({ type: 'sv:disposed', requestId: requestId });\r\n break;\r\n }\r\n\r\n case 'vad:load': {\r\n const startTime = performance.now();\r\n var info = await vadLoad(msg);\r\n self.postMessage({\r\n type: 'vad:loaded', requestId: requestId,\r\n inputNames: info.inputNames, outputNames: info.outputNames,\r\n loadTimeMs: performance.now() - startTime,\r\n });\r\n break;\r\n }\r\n\r\n case 'vad:process': {\r\n const startTime = performance.now();\r\n var result = await vadProcess(msg.audio, msg.state, msg.context);\r\n self.postMessage({\r\n type: 'vad:result', requestId: requestId,\r\n probability: result.probability, state: result.newState,\r\n inferenceTimeMs: performance.now() - startTime,\r\n });\r\n break;\r\n }\r\n\r\n case 'vad:reset': {\r\n var state = vadCreateInitialState();\r\n self.postMessage({ type: 'vad:reset', requestId: requestId, state: state });\r\n break;\r\n }\r\n\r\n case 'vad:dispose': {\r\n await vadDispose();\r\n self.postMessage({ type: 'vad:disposed', requestId: requestId });\r\n break;\r\n }\r\n\r\n case 'lam:load': {\r\n const startTime = performance.now();\r\n var info = await lamLoad(msg);\r\n self.postMessage({\r\n type: 'lam:loaded', requestId: requestId,\r\n inputNames: info.inputNames, outputNames: info.outputNames,\r\n backend: info.backend,\r\n loadTimeMs: performance.now() - startTime,\r\n });\r\n break;\r\n }\r\n\r\n case 'lam:infer': {\r\n const startTime = performance.now();\r\n var result = await lamInfer(msg.audio, msg.identityIndex);\r\n var inferenceTimeMs = performance.now() - startTime;\r\n self.postMessage({\r\n type: 'lam:result', requestId: requestId,\r\n blendshapes: result.flatBuffer, numFrames: result.numFrames,\r\n numBlendshapes: result.numBlendshapes, inferenceTimeMs: inferenceTimeMs,\r\n }, [result.flatBuffer.buffer]);\r\n break;\r\n }\r\n\r\n case 'lam:dispose': {\r\n await lamDispose();\r\n self.postMessage({ type: 'lam:disposed', requestId: requestId });\r\n break;\r\n }\r\n\r\n case 'kokoro:load': {\r\n const startTime = performance.now();\r\n await kokoroLoad(msg);\r\n self.postMessage({\r\n type: 'kokoro:loaded', requestId: requestId,\r\n loadTimeMs: performance.now() - startTime,\r\n });\r\n break;\r\n }\r\n\r\n case 'kokoro:infer': {\r\n const startTime = performance.now();\r\n var audio = await kokoroInfer(msg.tokens, msg.style, msg.speed);\r\n var inferenceTimeMs = performance.now() - startTime;\r\n self.postMessage({\r\n type: 'kokoro:result', requestId: requestId,\r\n audio: audio, inferenceTimeMs: inferenceTimeMs,\r\n }, [audio.buffer]);\r\n break;\r\n }\r\n\r\n case 'kokoro:dispose': {\r\n await kokoroDispose();\r\n self.postMessage({ type: 'kokoro:disposed', requestId: requestId });\r\n break;\r\n }\r\n\r\n case 'ping': {\r\n self.postMessage({ type: 'pong', requestId: requestId });\r\n break;\r\n }\r\n\r\n case 'dispose-all': {\r\n await svDispose();\r\n await lamDispose();\r\n await kokoroDispose();\r\n await vadDispose();\r\n ort = null;\r\n self.postMessage({ type: 'dispose-all:done', requestId: requestId });\r\n break;\r\n }\r\n\r\n default:\r\n self.postMessage({ type: 'error', requestId: requestId, error: 'Unknown message type: ' + msg.type });\r\n }\r\n } catch (err) {\r\n var errorMsg = err.message || String(err);\r\n if (typeof err === 'number') {\r\n errorMsg = 'Raw C++ exception pointer (0x' + err.toString(16) + '). Likely OOM in WASM.';\r\n }\r\n self.postMessage({ type: 'error', requestId: requestId, error: errorMsg });\r\n }\r\n};\r\n\r\nself.onerror = function(err) {\r\n self.postMessage({ type: 'error', requestId: null, error: 'Worker error: ' + (err.message || String(err)) });\r\n};\r\n`;\r\n\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n// Main Thread: UnifiedInferenceWorker\r\n// ═══════════════════════════════════════════════════════════════════════════\r\n\r\n/**\r\n * Unified Inference Worker — single Web Worker for all ONNX models\r\n *\r\n * Hosts SenseVoice, A2E (LAM), Kokoro TTS, and Silero VAD in one ORT instance.\r\n * Uses WebGPU on Chrome/Edge 113+, falls back to WASM on Safari/iOS/Firefox.\r\n * All model loading and inference runs off the main thread.\r\n */\r\nexport class UnifiedInferenceWorker {\r\n private worker: Worker | null = null;\r\n private pendingRequests = new Map<string, {\r\n resolve: (value: unknown) => void;\r\n reject: (error: Error) => void;\r\n timeout: ReturnType<typeof setTimeout>;\r\n }>();\r\n private initialized = false;\r\n private healthState: WorkerHealthState = 'healthy';\r\n private consecutiveFailures = 0;\r\n private _generation = 0;\r\n private recovering = false;\r\n private _workerBackend: 'wasm' | 'webgpu' = 'wasm';\r\n\r\n /**\r\n * Initialize the worker (load ORT WASM from CDN)\r\n */\r\n async init(): Promise<void> {\r\n if (this.initialized) return;\r\n\r\n const startTime = performance.now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('UnifiedInferenceWorker.init');\r\n\r\n try {\r\n logger.info('Creating unified inference worker...');\r\n this.worker = this.createWorker();\r\n\r\n const result = await this.sendMessage<{ loadTimeMs: number; backend: string }>(\r\n { type: 'init', wasmPaths: WASM_CDN_PATH, isIOS: isIOS() },\r\n 'init:done',\r\n INIT_TIMEOUT_MS,\r\n );\r\n\r\n this._workerBackend = result.backend === 'webgpu' ? 'webgpu' : 'wasm';\r\n this.initialized = true;\r\n const loadTimeMs = performance.now() - startTime;\r\n logger.info('Unified worker initialized', { loadTimeMs: Math.round(loadTimeMs), backend: this._workerBackend });\r\n\r\n span?.setAttributes({ 'worker.init_time_ms': loadTimeMs, 'worker.backend': this._workerBackend });\r\n span?.end();\r\n } catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n const isTimeout = err.message.includes('timed out');\r\n if (isTimeout) {\r\n logger.error('Worker init timed out', { code: ErrorCodes.INF_TIMEOUT, timeoutMs: INIT_TIMEOUT_MS });\r\n }\r\n span?.endWithError(err);\r\n this.cleanup();\r\n throw error;\r\n }\r\n }\r\n\r\n // ── SenseVoice ────────────────────────────────────────────────────────\r\n\r\n async loadSenseVoice(config: {\r\n modelUrl: string;\r\n tokensUrl: string;\r\n language: number;\r\n textNorm: number;\r\n }): Promise<SenseVoiceModelInfo> {\r\n this.assertReady();\r\n\r\n const startTime = performance.now();\r\n const result = await this.sendMessage<{\r\n vocabSize: number;\r\n inputNames: string[];\r\n outputNames: string[];\r\n loadTimeMs: number;\r\n }>(\r\n {\r\n type: 'sv:load',\r\n modelUrl: resolveUrl(config.modelUrl),\r\n tokensUrl: resolveUrl(config.tokensUrl),\r\n isIOS: isIOS(),\r\n language: config.language,\r\n textNorm: config.textNorm,\r\n },\r\n 'sv:loaded',\r\n SV_LOAD_TIMEOUT_MS,\r\n );\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n return {\r\n backend: 'wasm',\r\n loadTimeMs,\r\n inputNames: result.inputNames,\r\n outputNames: result.outputNames,\r\n vocabSize: result.vocabSize,\r\n };\r\n }\r\n\r\n async transcribe(audio: Float32Array): Promise<SenseVoiceResult> {\r\n this.assertReady();\r\n\r\n const result = await this.sendMessage<{\r\n text: string;\r\n language?: string;\r\n emotion?: string;\r\n event?: string;\r\n inferenceTimeMs: number;\r\n preprocessTimeMs: number;\r\n }>(\r\n { type: 'sv:transcribe', audio },\r\n 'sv:result',\r\n SV_INFER_TIMEOUT_MS,\r\n );\r\n\r\n return {\r\n text: result.text,\r\n language: result.language,\r\n emotion: result.emotion,\r\n event: result.event,\r\n inferenceTimeMs: result.inferenceTimeMs,\r\n preprocessTimeMs: result.preprocessTimeMs,\r\n };\r\n }\r\n\r\n async disposeSenseVoice(): Promise<void> {\r\n if (!this.worker) return;\r\n await this.sendMessage({ type: 'sv:dispose' }, 'sv:disposed', DISPOSE_TIMEOUT_MS);\r\n }\r\n\r\n // ── LAM (A2E) ──────────────────────────────────────────────────────\r\n\r\n async loadLAM(config: {\r\n modelUrl: string;\r\n externalDataUrl: string | null;\r\n numIdentityClasses?: number;\r\n }): Promise<A2EModelInfo> {\r\n this.assertReady();\r\n\r\n const startTime = performance.now();\r\n const result = await this.sendMessage<{\r\n inputNames: string[];\r\n outputNames: string[];\r\n backend: string;\r\n loadTimeMs: number;\r\n }>(\r\n {\r\n type: 'lam:load',\r\n modelUrl: resolveUrl(config.modelUrl),\r\n externalDataUrl: config.externalDataUrl ? resolveUrl(config.externalDataUrl) : null,\r\n isIOS: isIOS(),\r\n numIdentityClasses: config.numIdentityClasses ?? 12,\r\n },\r\n 'lam:loaded',\r\n LAM_LOAD_TIMEOUT_MS,\r\n );\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n return {\r\n backend: (result.backend === 'webgpu' ? 'webgpu' : 'wasm') as 'webgpu' | 'wasm',\r\n loadTimeMs,\r\n inputNames: result.inputNames,\r\n outputNames: result.outputNames,\r\n };\r\n }\r\n\r\n async inferLAM(audio: Float32Array, identityIndex?: number): Promise<{\r\n blendshapes: Float32Array;\r\n numFrames: number;\r\n numBlendshapes: number;\r\n inferenceTimeMs: number;\r\n }> {\r\n this.assertReady();\r\n\r\n return this.sendMessage(\r\n { type: 'lam:infer', audio, identityIndex: identityIndex ?? 0 },\r\n 'lam:result',\r\n LAM_INFER_TIMEOUT_MS,\r\n );\r\n }\r\n\r\n async disposeLAM(): Promise<void> {\r\n if (!this.worker) return;\r\n await this.sendMessage({ type: 'lam:dispose' }, 'lam:disposed', DISPOSE_TIMEOUT_MS);\r\n }\r\n\r\n // ── Kokoro TTS ──────────────────────────────────────────────────────\r\n\r\n async loadKokoro(config: {\r\n modelUrl: string;\r\n }): Promise<{ loadTimeMs: number }> {\r\n this.assertReady();\r\n\r\n const startTime = performance.now();\r\n const result = await this.sendMessage<{\r\n loadTimeMs: number;\r\n }>(\r\n {\r\n type: 'kokoro:load',\r\n modelUrl: resolveUrl(config.modelUrl),\r\n isIOS: isIOS(),\r\n },\r\n 'kokoro:loaded',\r\n KOKORO_LOAD_TIMEOUT_MS,\r\n );\r\n\r\n return { loadTimeMs: performance.now() - startTime };\r\n }\r\n\r\n async inferKokoro(tokens: number[], style: Float32Array, speed: number): Promise<{\r\n audio: Float32Array;\r\n inferenceTimeMs: number;\r\n }> {\r\n this.assertReady();\r\n\r\n return this.sendMessage(\r\n { type: 'kokoro:infer', tokens, style, speed },\r\n 'kokoro:result',\r\n KOKORO_INFER_TIMEOUT_MS,\r\n );\r\n }\r\n\r\n async disposeKokoro(): Promise<void> {\r\n if (!this.worker) return;\r\n await this.sendMessage({ type: 'kokoro:dispose' }, 'kokoro:disposed', DISPOSE_TIMEOUT_MS);\r\n }\r\n\r\n // ── Silero VAD ────────────────────────────────────────────────────────\r\n\r\n async loadVAD(config: {\r\n modelUrl: string;\r\n sampleRate: number;\r\n }): Promise<VADWorkerModelInfo> {\r\n this.assertReady();\r\n\r\n const startTime = performance.now();\r\n const chunkSize = config.sampleRate === 16000 ? 512 : 256;\r\n const result = await this.sendMessage<{\r\n inputNames: string[];\r\n outputNames: string[];\r\n loadTimeMs: number;\r\n }>(\r\n {\r\n type: 'vad:load',\r\n modelUrl: resolveUrl(config.modelUrl),\r\n sampleRate: config.sampleRate,\r\n },\r\n 'vad:loaded',\r\n VAD_LOAD_TIMEOUT_MS,\r\n );\r\n\r\n const loadTimeMs = performance.now() - startTime;\r\n return {\r\n backend: 'wasm',\r\n loadTimeMs,\r\n inputNames: result.inputNames,\r\n outputNames: result.outputNames,\r\n sampleRate: config.sampleRate,\r\n chunkSize,\r\n };\r\n }\r\n\r\n async processVAD(\r\n audio: Float32Array,\r\n state: Float32Array,\r\n context: Float32Array,\r\n ): Promise<{ probability: number; state: Float32Array; inferenceTimeMs: number }> {\r\n this.assertReady();\r\n\r\n return this.sendMessage(\r\n { type: 'vad:process', audio, state, context },\r\n 'vad:result',\r\n VAD_INFER_TIMEOUT_MS,\r\n );\r\n }\r\n\r\n async resetVAD(): Promise<Float32Array> {\r\n this.assertReady();\r\n\r\n const result = await this.sendMessage<{ state: Float32Array }>(\r\n { type: 'vad:reset' },\r\n 'vad:reset',\r\n VAD_INFER_TIMEOUT_MS,\r\n );\r\n return result.state;\r\n }\r\n\r\n async disposeVAD(): Promise<void> {\r\n if (!this.worker) return;\r\n await this.sendMessage({ type: 'vad:dispose' }, 'vad:disposed', DISPOSE_TIMEOUT_MS);\r\n }\r\n\r\n // ── Lifecycle ─────────────────────────────────────────────────────────\r\n\r\n async dispose(): Promise<void> {\r\n logger.debug('Disposed');\r\n if (this.worker) {\r\n try {\r\n await this.sendMessage({ type: 'dispose-all' }, 'dispose-all:done', DISPOSE_TIMEOUT_MS);\r\n } catch {\r\n // Ignore errors during dispose\r\n }\r\n this.worker.terminate();\r\n this.worker = null;\r\n }\r\n this.initialized = false;\r\n this.healthState = 'healthy';\r\n this.consecutiveFailures = 0;\r\n this.rejectAllPending('Worker disposed');\r\n this.pendingRequests.clear();\r\n }\r\n\r\n /** Check if the worker is initialized and healthy */\r\n get isReady(): boolean {\r\n return this.initialized && this.healthState === 'healthy' && this.worker !== null;\r\n }\r\n\r\n /** Current health state of the worker */\r\n get health(): WorkerHealthState {\r\n return this.healthState;\r\n }\r\n\r\n /** Generation counter — increments on worker recovery. Adapters compare to detect stale sessions. */\r\n get workerGeneration(): number {\r\n return this._generation;\r\n }\r\n\r\n /** The ORT backend the worker is using ('webgpu' on Chrome/Edge, 'wasm' on Safari/iOS/Firefox) */\r\n get backend(): 'wasm' | 'webgpu' {\r\n return this._workerBackend;\r\n }\r\n\r\n /** Check if Web Workers are supported */\r\n static isSupported(): boolean {\r\n return typeof Worker !== 'undefined';\r\n }\r\n\r\n // ── Private ───────────────────────────────────────────────────────────\r\n\r\n private assertReady(): void {\r\n if (!this.initialized || !this.worker) {\r\n throw new Error('UnifiedInferenceWorker not initialized. Call init() first.');\r\n }\r\n if (this.healthState === 'recovering') {\r\n throw new Error('UnifiedInferenceWorker is recovering — retry shortly');\r\n }\r\n if (this.healthState === 'unhealthy') {\r\n throw new Error('UnifiedInferenceWorker recovery failed — unavailable until page reload');\r\n }\r\n }\r\n\r\n private createWorker(): Worker {\r\n const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });\r\n const blobUrl = URL.createObjectURL(blob);\r\n const worker = new Worker(blobUrl);\r\n URL.revokeObjectURL(blobUrl);\r\n\r\n worker.onmessage = (event: MessageEvent) => {\r\n this.handleWorkerMessage(event.data);\r\n };\r\n\r\n worker.onerror = (error) => {\r\n logger.error('Unified worker error', { error: error.message });\r\n this.rejectAllPending(`Worker error: ${error.message}`);\r\n };\r\n\r\n return worker;\r\n }\r\n\r\n private handleWorkerMessage(data: Record<string, unknown>): void {\r\n const requestId = data.requestId as string | null;\r\n\r\n if (data.type === 'error') {\r\n if (requestId && this.pendingRequests.has(requestId)) {\r\n const pending = this.pendingRequests.get(requestId)!;\r\n clearTimeout(pending.timeout);\r\n this.pendingRequests.delete(requestId);\r\n pending.reject(new Error(data.error as string));\r\n } else {\r\n // Broadcast error — reject all pending\r\n logger.error('Worker broadcast error', { error: data.error });\r\n this.rejectAllPending(data.error as string);\r\n }\r\n return;\r\n }\r\n\r\n // Success — reset consecutive failure count\r\n this.consecutiveFailures = 0;\r\n\r\n if (requestId && this.pendingRequests.has(requestId)) {\r\n const pending = this.pendingRequests.get(requestId)!;\r\n clearTimeout(pending.timeout);\r\n this.pendingRequests.delete(requestId);\r\n pending.resolve(data);\r\n }\r\n }\r\n\r\n private sendMessage<T = unknown>(\r\n message: Record<string, unknown>,\r\n expectedType: string,\r\n timeoutMs: number,\r\n ): Promise<T> {\r\n return new Promise((resolve, reject) => {\r\n if (!this.worker) {\r\n reject(new Error('Worker not initialized'));\r\n return;\r\n }\r\n\r\n const requestId = nextRequestId();\r\n const timeout = setTimeout(() => {\r\n this.pendingRequests.delete(requestId);\r\n this.consecutiveFailures++;\r\n\r\n logger.warn('Worker operation timed out', {\r\n type: message.type,\r\n timeoutMs,\r\n consecutiveFailures: this.consecutiveFailures,\r\n });\r\n\r\n reject(new Error(`Worker operation '${message.type}' timed out after ${timeoutMs}ms`));\r\n\r\n // After MAX_CONSECUTIVE_FAILURES, run health check\r\n if (this.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES && !this.recovering) {\r\n this.runHealthCheck();\r\n }\r\n }, timeoutMs);\r\n\r\n this.pendingRequests.set(requestId, {\r\n resolve: resolve as (value: unknown) => void,\r\n reject,\r\n timeout,\r\n });\r\n\r\n this.worker.postMessage({ ...message, requestId });\r\n });\r\n }\r\n\r\n /**\r\n * Ping the worker to check if it's alive. If ping succeeds, worker was just\r\n * busy with long inference. If ping fails, worker is truly stuck — recover.\r\n */\r\n private async runHealthCheck(): Promise<void> {\r\n if (this.recovering) return;\r\n\r\n logger.info('Running health check after consecutive failures', {\r\n consecutiveFailures: this.consecutiveFailures,\r\n });\r\n\r\n try {\r\n await this.sendMessage({ type: 'ping' }, 'pong', HEALTH_CHECK_TIMEOUT_MS);\r\n // Ping succeeded — worker is alive, was just busy\r\n logger.info('Health check passed — worker is alive (was busy with long inference)');\r\n this.consecutiveFailures = 0;\r\n } catch {\r\n // Ping failed — worker is truly stuck, recover\r\n logger.error('Health check FAILED — worker is unresponsive, attempting recovery');\r\n await this.recoverWorker();\r\n }\r\n }\r\n\r\n /**\r\n * Terminate the stuck worker, create a new one, and re-initialize ORT.\r\n * Model sessions are lost — adapters must reload via generation check.\r\n */\r\n private async recoverWorker(): Promise<void> {\r\n if (this.recovering) return;\r\n this.recovering = true;\r\n this.healthState = 'recovering';\r\n\r\n if (this.worker) {\r\n this.worker.terminate();\r\n this.worker = null;\r\n }\r\n this.rejectAllPending('Worker recovery in progress');\r\n\r\n try {\r\n this.worker = this.createWorker();\r\n const result = await this.sendMessage<{ backend: string }>(\r\n { type: 'init', wasmPaths: WASM_CDN_PATH, isIOS: isIOS() },\r\n 'init:done',\r\n INIT_TIMEOUT_MS,\r\n );\r\n this._workerBackend = result.backend === 'webgpu' ? 'webgpu' : 'wasm';\r\n this.healthState = 'healthy';\r\n this.consecutiveFailures = 0;\r\n this._generation++;\r\n this.initialized = true;\r\n logger.info('Worker recovery successful — models must be reloaded', { generation: this._generation, backend: this._workerBackend });\r\n } catch (error) {\r\n logger.error('Worker recovery FAILED', { error: String(error) });\r\n this.healthState = 'unhealthy';\r\n this.cleanup();\r\n } finally {\r\n this.recovering = false;\r\n }\r\n }\r\n\r\n private rejectAllPending(reason: string): void {\r\n for (const [, pending] of this.pendingRequests) {\r\n clearTimeout(pending.timeout);\r\n pending.reject(new Error(reason));\r\n }\r\n this.pendingRequests.clear();\r\n }\r\n\r\n private cleanup(): void {\r\n if (this.worker) {\r\n this.worker.terminate();\r\n this.worker = null;\r\n }\r\n this.initialized = false;\r\n this.rejectAllPending('Worker cleanup');\r\n this.pendingRequests.clear();\r\n }\r\n}\r\n\r\n","/**\n * Shared singleton UnifiedInferenceWorker\n *\n * All inference in the SDK is sequential — there is no benefit to multiple\n * workers, and each extra Worker loads its own ORT WASM instance (~300-400MB).\n * On iOS this exceeds the ~1-1.5GB tab limit → OOM, but even on desktop it\n * wastes memory for no throughput gain.\n *\n * This module provides a ref-counted singleton so ALL callers (Lazy* wrappers,\n * TTSSpeaker, VoiceOrchestrator, SpeechListener, etc.) share one worker.\n */\n\nimport { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\n\nlet sharedWorker: UnifiedInferenceWorker | null = null;\nlet sharedWorkerRefCount = 0;\nlet initPromise: Promise<UnifiedInferenceWorker> | null = null;\n\nexport async function acquireSharedWorker(): Promise<UnifiedInferenceWorker> {\n if (!initPromise) {\n initPromise = (async () => {\n const worker = new UnifiedInferenceWorker();\n await worker.init();\n sharedWorker = worker;\n return worker;\n })();\n }\n const worker = await initPromise;\n sharedWorkerRefCount++;\n return worker;\n}\n\nexport async function releaseSharedWorker(): Promise<void> {\n sharedWorkerRefCount--;\n if (sharedWorkerRefCount <= 0 && sharedWorker) {\n await sharedWorker.dispose();\n sharedWorker = null;\n sharedWorkerRefCount = 0;\n initPromise = null;\n }\n}\n\n/** @deprecated Always returns true — kept for backward compatibility. */\nexport function shouldUseSharedWorker(): boolean {\n return true;\n}\n","/**\n * CTC greedy decoder for SenseVoice\n *\n * Decodes CTC logits into text with structured token parsing\n * for language, emotion, and audio event detection.\n *\n * @module inference/ctcDecoder\n */\n\nexport interface CTCDecodeResult {\n /** Decoded text (speech content only) */\n text: string;\n /** Detected language (e.g., 'zh', 'en', 'ja', 'ko', 'yue') */\n language?: string;\n /** Detected emotion (e.g., 'HAPPY', 'SAD', 'ANGRY', 'NEUTRAL') */\n emotion?: string;\n /** Detected audio event (e.g., 'Speech', 'BGM', 'Laughter') */\n event?: string;\n}\n\n/** SenseVoice language ID → string mapping */\nconst LANGUAGE_IDS: Record<number, string> = {\n 0: 'auto',\n 3: 'zh',\n 4: 'en',\n 7: 'yue',\n 11: 'ja',\n 12: 'ko',\n 13: 'nospeech',\n};\n\n/** SenseVoice text normalization ID → string mapping */\nconst TEXT_NORM_IDS: Record<number, string> = {\n 14: 'with_itn',\n 15: 'without_itn',\n};\n\n/** Resolve language string to SenseVoice language ID */\nexport function resolveLanguageId(language: string): number {\n const map: Record<string, number> = {\n auto: 0,\n zh: 3,\n en: 4,\n yue: 7,\n ja: 11,\n ko: 12,\n };\n return map[language] ?? 0;\n}\n\n/** Resolve text norm string to SenseVoice text norm ID */\nexport function resolveTextNormId(textNorm: string): number {\n return textNorm === 'without_itn' ? 15 : 14;\n}\n\n/**\n * Parse tokens.txt into a token ID → string map\n *\n * Format: each line is \"token_string token_id\"\n * e.g., \"<unk> 0\", \"▁the 3\", \"s 4\"\n */\nexport function parseTokensFile(content: string): Map<number, string> {\n const map = new Map<number, string>();\n const lines = content.split('\\n');\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n // Find the last space — token string may contain spaces\n const lastSpace = trimmed.lastIndexOf(' ');\n if (lastSpace === -1) continue;\n const token = trimmed.substring(0, lastSpace);\n const id = parseInt(trimmed.substring(lastSpace + 1), 10);\n if (!isNaN(id)) {\n map.set(id, token);\n }\n }\n return map;\n}\n\n/**\n * SenseVoice structured token pattern matching\n * Structured tokens appear in the first few decoded tokens as <|tag|>\n */\nfunction parseStructuredToken(token: string): { type: string; value: string } | null {\n const match = token.match(/^<\\|(.+)\\|>$/);\n if (!match) return null;\n\n const value = match[1];\n\n // Language tokens\n if (value === 'zh' || value === 'en' || value === 'ja' || value === 'ko' || value === 'yue' || value === 'nospeech') {\n return { type: 'language', value };\n }\n\n // Emotion tokens\n const emotions = ['HAPPY', 'SAD', 'ANGRY', 'NEUTRAL', 'FEARFUL', 'DISGUSTED', 'SURPRISED', 'EMO_UNKNOWN'];\n if (emotions.includes(value)) {\n return { type: 'emotion', value };\n }\n\n // Audio event tokens\n const events = ['Speech', 'BGM', 'Applause', 'Laughter', 'Crying', 'Coughing', 'Sneezing', 'EVENT_UNKNOWN'];\n if (events.includes(value)) {\n return { type: 'event', value };\n }\n\n // ITN tokens\n if (value === 'withitn' || value === 'woitn' || value === 'with_itn' || value === 'without_itn') {\n return { type: 'textnorm', value };\n }\n\n return null;\n}\n\n/**\n * CTC greedy decode\n *\n * @param logits Raw logits from model output, flattened [seqLen, vocabSize]\n * @param seqLen Sequence length (time steps)\n * @param vocabSize Vocabulary size\n * @param tokenMap Token ID → string map from tokens.txt\n * @returns Decoded text and structured metadata\n */\nexport function ctcGreedyDecode(\n logits: Float32Array,\n seqLen: number,\n vocabSize: number,\n tokenMap: Map<number, string>,\n): CTCDecodeResult {\n // Step 1: Argmax per time step\n const tokenIds: number[] = [];\n for (let t = 0; t < seqLen; t++) {\n const offset = t * vocabSize;\n let maxIdx = 0;\n let maxVal = logits[offset];\n for (let v = 1; v < vocabSize; v++) {\n if (logits[offset + v] > maxVal) {\n maxVal = logits[offset + v];\n maxIdx = v;\n }\n }\n tokenIds.push(maxIdx);\n }\n\n // Step 2: Collapse consecutive duplicates\n const collapsed: number[] = [];\n let prev = -1;\n for (const id of tokenIds) {\n if (id !== prev) {\n collapsed.push(id);\n prev = id;\n }\n }\n\n // Step 3: Remove blank tokens (ID 0) and special tokens (<s>=1, </s>=2)\n const filtered = collapsed.filter(id => id !== 0 && id !== 1 && id !== 2);\n\n // Step 4: Convert to token strings and parse structured tokens\n let language: string | undefined;\n let emotion: string | undefined;\n let event: string | undefined;\n const textTokens: string[] = [];\n\n for (const id of filtered) {\n const token = tokenMap.get(id);\n if (!token) continue;\n\n const structured = parseStructuredToken(token);\n if (structured) {\n if (structured.type === 'language') language = structured.value;\n else if (structured.type === 'emotion') emotion = structured.value;\n else if (structured.type === 'event') event = structured.value;\n // Skip textnorm tokens — not useful in output\n } else {\n textTokens.push(token);\n }\n }\n\n // Step 5: Join tokens, handle SentencePiece boundary marker\n let text = textTokens.join('');\n // Replace SentencePiece word boundary (▁ = U+2581) with space\n text = text.replace(/\\u2581/g, ' ').trim();\n\n return { text, language, emotion, event };\n}\n","/**\r\n * SenseVoice adapter backed by UnifiedInferenceWorker\r\n *\r\n * Implements SenseVoiceBackend, delegating all inference to the shared worker.\r\n */\r\n\r\nimport { createLogger } from '../../logging';\r\nimport { getClock } from '../../logging/Clock';\r\nimport { getTelemetry } from '../../telemetry';\r\nimport { MetricNames } from '../../telemetry/types';\r\nimport { resolveLanguageId, resolveTextNormId } from '../ctcDecoder';\r\nimport type { SenseVoiceResult, SenseVoiceModelInfo, SenseVoiceBackend, SenseVoiceWorkerConfig } from '../SenseVoiceTypes';\r\nimport type { UnifiedInferenceWorker } from '../UnifiedInferenceWorker';\r\n\r\nconst logger = createLogger('SenseVoiceUnifiedAdapter');\r\n\r\nexport class SenseVoiceUnifiedAdapter implements SenseVoiceBackend {\r\n private worker: UnifiedInferenceWorker;\r\n private config: Required<SenseVoiceWorkerConfig>;\r\n private _isLoaded = false;\r\n private loadedGeneration = 0;\r\n private languageId: number;\r\n private textNormId: number;\r\n /** Per-adapter inference queue — ensures sequential state updates. */\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n constructor(worker: UnifiedInferenceWorker, config: SenseVoiceWorkerConfig) {\r\n this.worker = worker;\r\n const modelDir = config.modelUrl.substring(0, config.modelUrl.lastIndexOf('/'));\r\n this.config = {\r\n modelUrl: config.modelUrl,\r\n tokensUrl: config.tokensUrl ?? `${modelDir}/tokens.txt`,\r\n language: config.language ?? 'auto',\r\n textNorm: config.textNorm ?? 'with_itn',\r\n };\r\n this.languageId = resolveLanguageId(this.config.language);\r\n this.textNormId = resolveTextNormId(this.config.textNorm);\r\n }\r\n\r\n get isLoaded(): boolean { return this._isLoaded; }\r\n get backend(): 'wasm' | null { return this._isLoaded ? 'wasm' : null; }\r\n\r\n async load(onProgress?: (loaded: number, total: number) => void): Promise<SenseVoiceModelInfo> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SenseVoiceUnifiedAdapter.load', {\r\n 'model.url': this.config.modelUrl,\r\n });\r\n\r\n try {\r\n const result = await this.worker.loadSenseVoice({\r\n modelUrl: this.config.modelUrl,\r\n tokensUrl: this.config.tokensUrl,\r\n language: this.languageId,\r\n textNorm: this.textNormId,\r\n });\r\n this._isLoaded = true;\r\n this.loadedGeneration = this.worker.workerGeneration;\r\n onProgress?.(1, 1);\r\n\r\n logger.info('SenseVoice loaded via unified worker', {\r\n backend: 'wasm',\r\n loadTimeMs: Math.round(result.loadTimeMs),\r\n vocabSize: result.vocabSize,\r\n });\r\n\r\n span?.setAttributes({ 'model.backend': 'wasm', 'model.load_time_ms': result.loadTimeMs });\r\n span?.end();\r\n telemetry?.recordHistogram(MetricNames.MODEL_LOAD_TIME, result.loadTimeMs, {\r\n model: 'sensevoice-unified',\r\n backend: 'wasm',\r\n });\r\n\r\n return result;\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n }\r\n\r\n async transcribe(audioSamples: Float32Array): Promise<SenseVoiceResult> {\r\n this.assertLoaded();\r\n\r\n const audio = new Float32Array(audioSamples);\r\n\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SenseVoiceUnifiedAdapter.transcribe', {\r\n 'inference.input_samples': audio.length,\r\n 'inference.backend': 'wasm',\r\n });\r\n const startTime = getClock().now();\r\n try {\r\n const result = await this.worker.transcribe(audio);\r\n const latencyMs = getClock().now() - startTime;\r\n telemetry?.recordHistogram(MetricNames.INFERENCE_LATENCY, latencyMs, {\r\n model: 'sensevoice-unified',\r\n backend: 'wasm',\r\n });\r\n telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {\r\n model: 'sensevoice-unified',\r\n backend: 'wasm',\r\n status: 'success',\r\n });\r\n span?.setAttributes({ 'inference.duration_ms': latencyMs });\r\n span?.end();\r\n resolve(result);\r\n } catch (err) {\r\n telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {\r\n model: 'sensevoice-unified',\r\n backend: 'wasm',\r\n status: 'error',\r\n });\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this._isLoaded) {\r\n await this.worker.disposeSenseVoice();\r\n this._isLoaded = false;\r\n }\r\n }\r\n\r\n private assertLoaded(): void {\r\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\r\n if (this.worker.workerGeneration !== this.loadedGeneration) {\r\n this._isLoaded = false;\r\n throw new Error('Worker was recovered — model session lost. Call load() again.');\r\n }\r\n }\r\n}\r\n","/**\r\n * A2E adapter backed by UnifiedInferenceWorker\r\n *\r\n * Implements A2EBackend, delegating all inference to the shared worker.\r\n * Used on iOS to run A2E inference off the main thread via the unified worker.\r\n */\r\n\r\nimport { createLogger } from '../../logging';\r\nimport { getClock } from '../../logging/Clock';\r\nimport { getTelemetry } from '../../telemetry';\r\nimport { MetricNames } from '../../telemetry/types';\r\nimport type { A2EBackend, A2EModelInfo, A2EResult } from '../A2EBackend';\r\nimport type { RuntimeBackend } from '../../utils/runtime';\r\nimport type { UnifiedInferenceWorker } from '../UnifiedInferenceWorker';\r\n\r\nconst logger = createLogger('A2EUnifiedAdapter');\r\n\r\nexport class A2EUnifiedAdapter implements A2EBackend {\r\n readonly modelId = 'a2e' as const;\r\n readonly chunkSize: number;\r\n\r\n private worker: UnifiedInferenceWorker;\r\n private modelUrl: string;\r\n private externalDataUrl: string | null;\r\n private numIdentityClasses: number;\r\n private _isLoaded = false;\r\n private _backend: RuntimeBackend | null = null;\r\n private loadedGeneration = 0;\r\n /** Per-adapter inference queue — ensures sequential state updates. */\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n constructor(worker: UnifiedInferenceWorker, config: {\r\n modelUrl: string;\r\n externalDataUrl?: string | false;\r\n numIdentityClasses?: number;\r\n chunkSize?: number;\r\n }) {\r\n this.worker = worker;\r\n this.modelUrl = config.modelUrl;\r\n this.externalDataUrl = config.externalDataUrl !== false\r\n ? (typeof config.externalDataUrl === 'string'\r\n ? config.externalDataUrl\r\n : `${config.modelUrl}.data`)\r\n : null;\r\n this.numIdentityClasses = config.numIdentityClasses ?? 12;\r\n this.chunkSize = config.chunkSize ?? 16000;\r\n }\r\n\r\n get isLoaded(): boolean { return this._isLoaded; }\r\n get backend(): RuntimeBackend | null { return this._backend; }\r\n\r\n async load(): Promise<A2EModelInfo> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('A2EUnifiedAdapter.load', {\r\n 'model.url': this.modelUrl,\r\n });\r\n\r\n try {\r\n const result = await this.worker.loadLAM({\r\n modelUrl: this.modelUrl,\r\n externalDataUrl: this.externalDataUrl,\r\n numIdentityClasses: this.numIdentityClasses,\r\n });\r\n this._isLoaded = true;\r\n this._backend = result.backend;\r\n this.loadedGeneration = this.worker.workerGeneration;\r\n\r\n logger.info('A2E loaded via unified worker', {\r\n backend: result.backend,\r\n loadTimeMs: Math.round(result.loadTimeMs),\r\n });\r\n\r\n span?.setAttributes({ 'model.backend': result.backend, 'model.load_time_ms': result.loadTimeMs });\r\n span?.end();\r\n telemetry?.recordHistogram(MetricNames.MODEL_LOAD_TIME, result.loadTimeMs, {\r\n model: 'a2e-unified',\r\n backend: result.backend,\r\n });\r\n\r\n return result;\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n }\r\n\r\n async infer(audioSamples: Float32Array, identityIndex?: number): Promise<A2EResult> {\r\n this.assertLoaded();\r\n\r\n // Ensure audio is exactly chunkSize samples\r\n let audio: Float32Array;\r\n if (audioSamples.length === this.chunkSize) {\r\n audio = new Float32Array(audioSamples);\r\n } else if (audioSamples.length < this.chunkSize) {\r\n audio = new Float32Array(this.chunkSize);\r\n audio.set(audioSamples, 0);\r\n } else {\r\n audio = audioSamples.slice(0, this.chunkSize);\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('A2EUnifiedAdapter.infer', {\r\n 'inference.input_samples': audio.length,\r\n });\r\n\r\n try {\r\n const startTime = getClock().now();\r\n const result = await this.worker.inferLAM(audio, identityIndex);\r\n const inferenceTimeMs = getClock().now() - startTime;\r\n\r\n // Reconstruct per-frame Float32Array[] from flat buffer\r\n const flatBuffer = result.blendshapes;\r\n const { numFrames, numBlendshapes } = result;\r\n const blendshapes: Float32Array[] = [];\r\n for (let f = 0; f < numFrames; f++) {\r\n blendshapes.push(flatBuffer.slice(f * numBlendshapes, (f + 1) * numBlendshapes));\r\n }\r\n\r\n span?.setAttributes({\r\n 'inference.duration_ms': inferenceTimeMs,\r\n 'inference.frames': numFrames,\r\n });\r\n span?.end();\r\n\r\n resolve({ blendshapes, numFrames, inferenceTimeMs });\r\n } catch (err) {\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this._isLoaded) {\r\n await this.worker.disposeLAM();\r\n this._isLoaded = false;\r\n }\r\n }\r\n\r\n private assertLoaded(): void {\r\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\r\n if (this.worker.workerGeneration !== this.loadedGeneration) {\r\n this._isLoaded = false;\r\n throw new Error('Worker was recovered — model session lost. Call load() again.');\r\n }\r\n }\r\n}\r\n","/**\n * Character-level tokenizer for Kokoro TTS\n *\n * Maps IPA phoneme characters to token IDs using Kokoro's 178-token vocabulary.\n * Pads with token 0 at start/end, max 512 tokens total.\n *\n * @category Inference\n * @module inference/kokoroTokenizer\n */\n\nimport { createLogger } from '../logging';\n\nconst logger = createLogger('KokoroTokenizer');\n\n/** Kokoro vocabulary: IPA phoneme character → token ID */\nconst VOCAB: Record<string, number> = {\n ';': 1, ':': 2, ',': 3, '.': 4, '!': 5, '?': 6, '\\u2014': 9, '\\u2026': 10,\n '\"': 11, '(': 12, ')': 13, '\\u201C': 14, '\\u201D': 15, ' ': 16, '\\u0303': 17,\n '\\u02A3': 18, '\\u02A5': 19, '\\u02A6': 20, '\\u02A8': 21, '\\u1D5D': 22, '\\uAB67': 23,\n 'A': 24, 'I': 25, 'O': 31, 'Q': 33, 'S': 35, 'T': 36, 'W': 39, 'Y': 41,\n '\\u1D4A': 42, 'a': 43, 'b': 44, 'c': 45, 'd': 46, 'e': 47, 'f': 48, 'h': 50,\n 'i': 51, 'j': 52, 'k': 53, 'l': 54, 'm': 55, 'n': 56, 'o': 57, 'p': 58,\n 'q': 59, 'r': 60, 's': 61, 't': 62, 'u': 63, 'v': 64, 'w': 65, 'x': 66,\n 'y': 67, 'z': 68, '\\u0251': 69, '\\u0250': 70, '\\u0252': 71, '\\u00E6': 72,\n '\\u03B2': 75, '\\u0254': 76, '\\u0255': 77, '\\u00E7': 78, '\\u0256': 80,\n '\\u00F0': 81, '\\u02A4': 82, '\\u0259': 83, '\\u025A': 85, '\\u025B': 86,\n '\\u025C': 87, '\\u025F': 90, '\\u0261': 92, '\\u0265': 99, '\\u0268': 101,\n '\\u026A': 102, '\\u029D': 103, '\\u026F': 110, '\\u0270': 111, '\\u014B': 112,\n '\\u0273': 113, '\\u0272': 114, '\\u0274': 115, '\\u00F8': 116, '\\u0278': 118,\n '\\u03B8': 119, '\\u0153': 120, '\\u0279': 123, '\\u027E': 125, '\\u027B': 126,\n '\\u0281': 128, '\\u027D': 129, '\\u0282': 130, '\\u0283': 131, '\\u0288': 132,\n '\\u02A7': 133, '\\u028A': 135, '\\u028B': 136, '\\u028C': 138, '\\u0263': 139,\n '\\u0264': 140, '\\u03C7': 142, '\\u028E': 143, '\\u0292': 147, '\\u0294': 148,\n '\\u02C8': 156, '\\u02CC': 157, '\\u02D0': 158, '\\u02B0': 162, '\\u02B2': 164,\n '\\u2193': 169, '\\u2192': 171, '\\u2197': 172, '\\u2198': 173, '\\u1D7B': 177,\n};\n\n/** Maximum token sequence length (including padding) */\nconst MAX_TOKENS = 512;\n\n/** Padding token ID */\nconst PAD_TOKEN = 0;\n\n/**\n * Tokenize a phoneme string into padded token IDs.\n *\n * Characters not in the vocabulary are silently skipped.\n * Result is padded with token 0 at start and end, capped at 512 total.\n *\n * @param phonemes - IPA phoneme string from eSpeak-NG\n * @returns Padded token ID array\n */\nexport function tokenize(phonemes: string): number[] {\n const tokens: number[] = [PAD_TOKEN];\n for (const char of phonemes) {\n const id = VOCAB[char];\n if (id !== undefined && tokens.length < MAX_TOKENS - 1) {\n tokens.push(id);\n }\n }\n if (tokens.length >= MAX_TOKENS - 1) {\n logger.warn('Token sequence truncated at 512 limit', {\n inputLength: phonemes.length,\n maxTokens: MAX_TOKENS,\n });\n }\n tokens.push(PAD_TOKEN);\n return tokens;\n}\n\n/**\n * Get the vocabulary size (for validation).\n */\nexport function getVocabSize(): number {\n return Object.keys(VOCAB).length;\n}\n\nexport { VOCAB as KOKORO_VOCAB, MAX_TOKENS as KOKORO_MAX_TOKENS, PAD_TOKEN as KOKORO_PAD_TOKEN };\n","/**\n * Text normalization and phonemization for Kokoro TTS\n *\n * Two-stage G2P pipeline:\n * 1. Primary: CMU Pronouncing Dictionary (134K words) → ARPABET → IPA mapping\n * (ported from ttstokenizer, validated for Kokoro TTS by NeuML)\n * 2. Fallback: `phonemize` npm package (rule-based G2P) for out-of-vocabulary words\n *\n * @category Inference\n * @module inference/kokoroPhonemizer\n */\n\nimport { createLogger } from '../logging';\n\nconst logger = createLogger('KokoroPhonemizer');\n\n// ─── Text Normalization (ported from kokoro.js/src/phonemize.js) ─────────────\n\nfunction splitNum(match: string): string {\n if (match.includes('.')) return match;\n if (match.includes(':')) {\n const [h, m] = match.split(':').map(Number);\n if (m === 0) return `${h} o'clock`;\n if (m < 10) return `${h} oh ${m}`;\n return `${h} ${m}`;\n }\n const year = parseInt(match.slice(0, 4), 10);\n if (year < 1100 || year % 1000 < 10) return match;\n const left = match.slice(0, 2);\n const right = parseInt(match.slice(2, 4), 10);\n const suffix = match.endsWith('s') ? 's' : '';\n if (year % 1000 >= 100 && year % 1000 <= 999) {\n if (right === 0) return `${left} hundred${suffix}`;\n if (right < 10) return `${left} oh ${right}${suffix}`;\n }\n return `${left} ${right}${suffix}`;\n}\n\nfunction flipMoney(match: string): string {\n const bill = match[0] === '$' ? 'dollar' : 'pound';\n if (isNaN(Number(match.slice(1)))) return `${match.slice(1)} ${bill}s`;\n if (!match.includes('.')) {\n const suffix = match.slice(1) === '1' ? '' : 's';\n return `${match.slice(1)} ${bill}${suffix}`;\n }\n const [b, c] = match.slice(1).split('.');\n const d = parseInt(c.padEnd(2, '0'), 10);\n const coins = match[0] === '$' ? (d === 1 ? 'cent' : 'cents') : d === 1 ? 'penny' : 'pence';\n return `${b} ${bill}${b === '1' ? '' : 's'} and ${d} ${coins}`;\n}\n\nfunction pointNum(match: string): string {\n const [a, b] = match.split('.');\n return `${a} point ${b.split('').join(' ')}`;\n}\n\n/**\n * Normalize text before phonemization.\n * Handles abbreviations, numbers, currency, punctuation, etc.\n */\nexport function normalizeText(text: string): string {\n return text\n .replace(/[\\u2018\\u2019]/g, \"'\")\n .replace(/\\u00AB/g, '\\u201C')\n .replace(/\\u00BB/g, '\\u201D')\n .replace(/[\\u201C\\u201D]/g, '\"')\n .replace(/\\(/g, '\\u00AB')\n .replace(/\\)/g, '\\u00BB')\n .replace(/、/g, ', ')\n .replace(/。/g, '. ')\n .replace(/!/g, '! ')\n .replace(/,/g, ', ')\n .replace(/:/g, ': ')\n .replace(/;/g, '; ')\n .replace(/?/g, '? ')\n .replace(/[^\\S \\n]/g, ' ')\n .replace(/ {2,}/, ' ')\n .replace(/(?<=\\n) +(?=\\n)/g, '')\n .replace(/\\bD[Rr]\\.(?= [A-Z])/g, 'Doctor')\n .replace(/\\b(?:Mr\\.|MR\\.(?= [A-Z]))/g, 'Mister')\n .replace(/\\b(?:Ms\\.|MS\\.(?= [A-Z]))/g, 'Miss')\n .replace(/\\b(?:Mrs\\.|MRS\\.(?= [A-Z]))/g, 'Mrs')\n .replace(/\\betc\\.(?! [A-Z])/g, 'etc')\n .replace(/\\b(y)eah?\\b/gi, '$1e\\'a')\n .replace(/\\d*\\.\\d+|\\b\\d{4}s?\\b|(?<!:)\\b(?:[1-9]|1[0-2]):[0-5]\\d\\b(?!:)/g, splitNum)\n .replace(/(?<=\\d),(?=\\d)/g, '')\n .replace(/[$£]\\d+(?:\\.\\d+)?(?: hundred| thousand| (?:[bm]|tr)illion)*\\b|[$£]\\d+\\.\\d\\d?\\b/gi, flipMoney)\n .replace(/\\d*\\.\\d+/g, pointNum)\n .replace(/(?<=\\d)-(?=\\d)/g, ' to ')\n .replace(/(?<=\\d)S/g, ' S')\n .replace(/(?<=[BCDFGHJ-NP-TV-Z])'?s\\b/g, \"'S\")\n .replace(/(?<=X')S\\b/g, 's')\n .replace(/(?:[A-Za-z]\\.){2,} [a-z]/g, (m) => m.replace(/\\./g, '-'))\n .replace(/(?<=[A-Z])\\.(?=[A-Z])/gi, '-')\n .trim();\n}\n\n// ─── Punctuation Splitting ──────────────────────────────────────────────────\n\nconst PUNCTUATION = ';:,.!?¡¿\\u2014\\u2026\"\\u00AB\\u00BB\\u201C\\u201D(){}[]';\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nconst PUNCTUATION_PATTERN = new RegExp(`(\\\\s*[${escapeRegExp(PUNCTUATION)}]+\\\\s*)+`, 'g');\n\ninterface SplitSegment {\n match: boolean;\n text: string;\n}\n\nfunction splitOnPunctuation(text: string): SplitSegment[] {\n const result: SplitSegment[] = [];\n let prev = 0;\n for (const m of text.matchAll(PUNCTUATION_PATTERN)) {\n if (prev < m.index!) {\n result.push({ match: false, text: text.slice(prev, m.index!) });\n }\n if (m[0].length > 0) {\n result.push({ match: true, text: m[0] });\n }\n prev = m.index! + m[0].length;\n }\n if (prev < text.length) {\n result.push({ match: false, text: text.slice(prev) });\n }\n return result;\n}\n\n// ─── ARPABET → IPA Mapping (ported from ttstokenizer) ───────────────────────\n\n/** ttstokenizer's ARPABET → IPA mapping table (Kokoro-compatible) */\nconst ARPABET_TO_IPA: Record<string, string> = {\n // Vowels with stress (0=no stress, 1=primary, 2=secondary)\n 'AA0': 'ɑ', 'AA1': 'ˈɑː', 'AA2': 'ˌɑ',\n 'AE0': 'æ', 'AE1': 'ˈæ', 'AE2': 'ˌæ',\n 'AH0': 'ə', 'AH1': 'ˈʌ', 'AH2': 'ˌʌ',\n 'AO0': 'ɔ', 'AO1': 'ˈɔː', 'AO2': 'ˌɔ',\n 'AW0': 'aʊ', 'AW1': 'ˈaʊ', 'AW2': 'ˌaʊ',\n 'AY0': 'aɪ', 'AY1': 'ˈaɪ', 'AY2': 'ˌaɪ',\n 'EH0': 'ɛ', 'EH1': 'ˈɛ', 'EH2': 'ˌɛ',\n 'ER0': 'ɚ', 'ER1': 'ˈɚ', 'ER2': 'ˌɚ',\n 'EY0': 'eɪ', 'EY1': 'ˈeɪ', 'EY2': 'ˌeɪ',\n 'IH0': 'ɪ', 'IH1': 'ˈɪ', 'IH2': 'ˌɪ',\n 'IY0': 'i', 'IY1': 'ˈiː', 'IY2': 'ˌi',\n 'OW0': 'oʊ', 'OW1': 'ˈoʊ', 'OW2': 'ˌoʊ',\n 'OY0': 'ɔɪ', 'OY1': 'ˈɔɪ', 'OY2': 'ˌɔɪ',\n 'UH0': 'ʊ', 'UH1': 'ˈʊ', 'UH2': 'ˌʊ',\n 'UW0': 'u', 'UW1': 'ˈuː', 'UW2': 'ˌu',\n // Consonants\n 'B': 'b', 'CH': 'tʃ', 'D': 'd', 'DH': 'ð',\n 'F': 'f', 'G': 'ɡ', 'HH': 'h', 'JH': 'dʒ',\n 'K': 'k', 'L': 'l', 'M': 'm', 'N': 'n',\n 'NG': 'ŋ', 'P': 'p', 'R': 'ɹ', 'S': 's',\n 'SH': 'ʃ', 'T': 't', 'TH': 'θ', 'V': 'v',\n 'W': 'w', 'Y': 'j', 'Z': 'z', 'ZH': 'ʒ',\n};\n\n/**\n * Convert ARPABET pronunciation to Kokoro-compatible IPA.\n * @param arpabet - Space-separated ARPABET phones (e.g., \"HH AH0 L OW1\")\n * @returns IPA string (e.g., \"həlˈoʊ\")\n */\nfunction arpabetToIPA(arpabet: string): string {\n return arpabet\n .split(' ')\n .map(phone => ARPABET_TO_IPA[phone] ?? '')\n .join('');\n}\n\n// ─── Function Word Stress Reduction ─────────────────────────────────────────\n\n/**\n * English function words that are typically unstressed in connected speech.\n * CMU dict gives citation-form stress (e.g., \"to\" → T UW1 → \"tˈuː\"),\n * but in natural speech these are reduced (e.g., \"to\" → T UW0 → \"tu\").\n * Without reduction, every word gets primary stress → monotone output.\n */\nconst FUNCTION_WORDS = new Set([\n // Articles\n 'a', 'an', 'the',\n // Prepositions\n 'at', 'by', 'for', 'from', 'in', 'of', 'on', 'to', 'with',\n 'into', 'onto', 'upon',\n // Conjunctions\n 'and', 'but', 'or', 'nor', 'so', 'yet', 'as', 'if', 'than',\n // Pronouns\n 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her', 'us', 'them',\n 'you', 'your', 'my', 'his', 'its', 'our', 'their',\n // Auxiliaries\n 'am', 'is', 'are', 'was', 'were', 'be', 'been',\n 'have', 'has', 'had',\n 'do', 'does', 'did',\n 'can', 'could', 'will', 'would', 'shall', 'should', 'may', 'might', 'must',\n // Others\n 'not', 'just', 'some', 'that', 'this', 'what',\n]);\n\n/**\n * Reduce primary stress to unstressed for function words.\n * Replaces stress level 1 → 0 in ARPABET phones.\n * e.g., \"T UW1\" → \"T UW0\" (\"tˈuː\" → \"tu\")\n */\nfunction reduceStress(arpabet: string): string {\n return arpabet.replace(/1/g, '0');\n}\n\n// ─── IPA Post-processing ────────────────────────────────────────────────────\n\nfunction postProcessIPA(ps: string, language: string): string {\n let processed = ps\n .replace(/kəkˈoːɹoʊ/g, 'kˈoʊkəɹoʊ')\n .replace(/kəkˈɔːɹəʊ/g, 'kˈəʊkəɹəʊ')\n .replace(/ʲ/g, 'j')\n .replace(/r/g, 'ɹ')\n .replace(/x/g, 'k')\n .replace(/ɬ/g, 'l')\n // phonemize (hans00) → eSpeak-style IPA normalization for Kokoro VOCAB:\n .replace(/ɫ/g, 'l') // dark L → plain L (VOCAB token 54)\n .replace(/ɝ/g, 'ɜɹ') // rhotacized open-mid → decomposed (eSpeak-style: ɜ + ɹ)\n .replace(/(?<=[a-zɹː])(?=hˈʌndɹɪd)/g, ' ')\n .replace(/ z(?=[;:,.!?¡¿\\u2014\\u2026\"\\u00AB\\u00BB\\u201C\\u201D ]|$)/g, 'z');\n\n if (language === 'a') {\n processed = processed.replace(/(?<=nˈaɪn)ti(?!ː)/g, 'di');\n }\n return processed.trim();\n}\n\n// ─── Public API ─────────────────────────────────────────────────────────────\n\n/** Lazily loaded CMU pronouncing dictionary (134K words) */\nlet _cmuDict: Record<string, string> | null = null;\nlet _cmuLoadFailed = false;\n\n/** Lazily loaded phonemize module (OOV fallback) */\nlet _phonemizeModule: { phonemize: (text: string) => string } | null = null;\nlet _phonemizeLoadFailed = false;\n\n/**\n * Load phonemization backends.\n * Primary: CMU dictionary (ARPABET → IPA, ttstokenizer-compatible).\n * Fallback: phonemize (hans00, rule-based G2P) for out-of-vocabulary words.\n * Safe to call multiple times.\n */\nexport async function loadPhonemizer(): Promise<void> {\n // Load CMU dictionary (primary G2P)\n if (!_cmuDict && !_cmuLoadFailed) {\n try {\n const mod = await import('cmu-pronouncing-dictionary');\n // Package exports named `dictionary` (ESM: export const dictionary = {...})\n const dict = (mod as any).dictionary ?? (mod as any).default ?? mod;\n if (typeof dict === 'object' && dict !== null && typeof dict.hello === 'string') {\n _cmuDict = dict as Record<string, string>;\n } else {\n throw new Error('Unexpected CMU dictionary format');\n }\n logger.info('CMU dictionary loaded', { words: Object.keys(_cmuDict).length });\n } catch {\n _cmuLoadFailed = true;\n logger.warn('CMU dictionary not available, using phonemize fallback');\n }\n }\n\n // Load phonemize (OOV fallback)\n if (!_phonemizeModule && !_phonemizeLoadFailed) {\n try {\n const mod = await import('phonemize');\n _phonemizeModule = mod;\n logger.info('phonemize loaded (OOV fallback)');\n } catch {\n _phonemizeLoadFailed = true;\n logger.warn('phonemize not available');\n }\n }\n\n if (!_cmuDict && !_phonemizeModule) {\n throw new Error('No phonemization backend available (install cmu-pronouncing-dictionary or phonemize)');\n }\n}\n\n/**\n * Phonemize a text segment (no punctuation) word by word.\n * Uses CMU dict as primary, phonemize as OOV fallback.\n */\nfunction phonemizeSegment(segment: string): string {\n const words = segment.split(/\\s+/).filter(w => w.length > 0);\n const ipaWords: string[] = [];\n\n for (const word of words) {\n // Primary: CMU dict lookup\n if (_cmuDict) {\n const key = word.toLowerCase();\n const entry = _cmuDict[key];\n if (entry) {\n // Reduce stress on function words for natural connected speech prosody\n const arpabet = FUNCTION_WORDS.has(key) ? reduceStress(entry) : entry;\n ipaWords.push(arpabetToIPA(arpabet));\n continue;\n }\n }\n\n // Fallback: phonemize (hans00) for OOV words\n if (_phonemizeModule) {\n ipaWords.push(_phonemizeModule.phonemize(word));\n } else {\n logger.warn('OOV word with no fallback', { word });\n }\n }\n\n return ipaWords.join(' ');\n}\n\n/** Languages where CMU dict + ARPABET pipeline applies (English only) */\nconst CMU_LANGUAGES = new Set(['a', 'b']);\n\n/**\n * Convert text to IPA phonemes for Kokoro TTS.\n *\n * English (lang 'a'/'b'): CMU dictionary → ARPABET → ttstokenizer IPA mapping.\n * Non-English: phonemize (hans00) rule-based G2P directly.\n * OOV fallback: phonemize for words not in CMU dict.\n *\n * @param text - Input text (will be normalized first)\n * @param language - Voice language code: 'a'=American, 'b'=British, 'e'=Spanish, 'f'=French, etc.\n * @param normalize - Whether to apply text normalization (default: true)\n * @returns IPA phoneme string ready for tokenization\n */\nexport async function phonemize(text: string, language: string = 'a', normalize: boolean = true): Promise<string> {\n if (!_cmuDict && !_phonemizeModule) {\n await loadPhonemizer();\n }\n\n if (normalize) {\n text = normalizeText(text);\n }\n\n const sections = splitOnPunctuation(text);\n const useCmu = CMU_LANGUAGES.has(language);\n\n const parts = sections.map(({ match, text: t }) => {\n if (match) return t;\n // CMU dict pipeline for English; phonemize directly for other languages\n if (useCmu) return phonemizeSegment(t);\n if (_phonemizeModule) return _phonemizeModule.phonemize(t);\n return phonemizeSegment(t); // last resort: try CMU anyway\n });\n\n const joined = parts.join('');\n return postProcessIPA(joined, language);\n}\n\n/**\n * Check if the phonemizer is loaded and ready.\n */\nexport function isPhonemizerLoaded(): boolean {\n return _cmuDict !== null || _phonemizeModule !== null;\n}\n\n/**\n * Split text into sentences for streaming synthesis.\n * Splits on sentence-ending punctuation while preserving the punctuation.\n */\nexport function splitSentences(text: string): string[] {\n const sentences: string[] = [];\n const parts = text.split(/(?<=[.!?])\\s+/);\n for (const part of parts) {\n const trimmed = part.trim();\n if (trimmed.length > 0) {\n sentences.push(trimmed);\n }\n }\n return sentences.length > 0 ? sentences : [text];\n}\n","/**\r\n * Model Cache\r\n *\r\n * Caches ONNX models in IndexedDB for faster subsequent loads.\r\n * IndexedDB can handle large files (100s of MBs) unlike localStorage.\r\n *\r\n * @category Cache\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\n\r\nconst logger = createLogger('ModelCache');\r\n\r\nconst DB_NAME = 'omote-model-cache';\r\nconst DB_VERSION = 2;\r\nconst STORE_NAME = 'models';\r\n\r\n/** Default cache size limit: 1GB */\r\nconst DEFAULT_MAX_SIZE_BYTES = 1024 * 1024 * 1024;\r\n\r\n/**\r\n * Configuration for cache size limits and eviction behavior\r\n */\r\nexport interface CacheConfig {\r\n /** Maximum total cache size in bytes (default: 1GB) */\r\n maxSizeBytes?: number;\r\n /** Maximum age in milliseconds before eviction (default: none) */\r\n maxAgeMs?: number;\r\n /** Callback when storage quota exceeds warning threshold */\r\n onQuotaWarning?: (info: QuotaInfo) => void;\r\n}\r\n\r\n/**\r\n * Storage quota information\r\n */\r\nexport interface QuotaInfo {\r\n /** Total bytes used across all origins */\r\n usedBytes: number;\r\n /** Total available quota in bytes */\r\n quotaBytes: number;\r\n /** Percentage of quota used (0-100) */\r\n percentUsed: number;\r\n /** Bytes used by omote cache specifically */\r\n cacheBytes: number;\r\n}\r\n\r\n/** Global cache configuration */\r\nlet globalCacheConfig: CacheConfig = {\r\n maxSizeBytes: DEFAULT_MAX_SIZE_BYTES,\r\n};\r\n\r\n/**\r\n * Configure cache size limits and eviction behavior\r\n *\r\n * @param config - Cache configuration options\r\n *\r\n * @example\r\n * ```typescript\r\n * import { configureCacheLimit } from '@omote/core';\r\n *\r\n * // Set 500MB limit with 24-hour max age\r\n * configureCacheLimit({\r\n * maxSizeBytes: 500 * 1024 * 1024,\r\n * maxAgeMs: 24 * 60 * 60 * 1000,\r\n * onQuotaWarning: (info) => {\r\n * console.warn(`Storage ${info.percentUsed.toFixed(1)}% used`);\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport function configureCacheLimit(config: CacheConfig): void {\r\n globalCacheConfig = {\r\n ...globalCacheConfig,\r\n ...config,\r\n };\r\n\r\n // Trigger immediate cleanup if over limit\r\n const cache = getModelCache();\r\n cache.enforceLimit().catch((err) => {\r\n logger.warn('Failed to enforce limit after config change', { error: String(err) });\r\n });\r\n}\r\n\r\n/**\r\n * Get current cache configuration\r\n */\r\nexport function getCacheConfig(): CacheConfig {\r\n return { ...globalCacheConfig };\r\n}\r\n\r\ninterface CachedModel {\r\n url: string;\r\n data: ArrayBuffer;\r\n size: number;\r\n cachedAt: number;\r\n /** Last time this model was accessed (for LRU eviction) */\r\n lastAccessedAt: number;\r\n etag?: string;\r\n version?: string;\r\n}\r\n\r\n/**\r\n * Result from getWithValidation() method\r\n */\r\nexport interface ValidationResult {\r\n /** The cached data, or null if not found */\r\n data: ArrayBuffer | null;\r\n /** True if the cached data is stale (etag mismatch) */\r\n stale: boolean;\r\n}\r\n\r\n/**\r\n * Generate a version-aware cache key\r\n *\r\n * @param url - The model URL\r\n * @param version - Optional version string\r\n * @returns The cache key (url#vX.X.X if version provided, url otherwise)\r\n *\r\n * @example\r\n * ```typescript\r\n * getCacheKey('http://example.com/model.onnx', '1.0.0')\r\n * // Returns: 'http://example.com/model.onnx#v1.0.0'\r\n *\r\n * getCacheKey('http://example.com/model.onnx')\r\n * // Returns: 'http://example.com/model.onnx'\r\n * ```\r\n */\r\nexport function getCacheKey(url: string, version?: string): string {\r\n if (version) {\r\n return `${url}#v${version}`;\r\n }\r\n return url;\r\n}\r\n\r\ninterface CacheStats {\r\n totalSize: number;\r\n modelCount: number;\r\n models: { url: string; size: number; cachedAt: Date }[];\r\n}\r\n\r\n/**\r\n * ModelCache - IndexedDB-based cache for ONNX models\r\n */\r\nexport class ModelCache {\r\n private db: IDBDatabase | null = null;\r\n private dbPromise: Promise<IDBDatabase> | null = null;\r\n\r\n /**\r\n * Initialize the cache database\r\n */\r\n private async getDB(): Promise<IDBDatabase> {\r\n if (this.db) return this.db;\r\n if (this.dbPromise) return this.dbPromise;\r\n\r\n // Request persistent storage for more generous quota on iOS/mobile browsers\r\n // This increases available storage from ~50MB to potentially GBs\r\n if (navigator.storage && navigator.storage.persist) {\r\n try {\r\n const isPersisted = await navigator.storage.persist();\r\n if (isPersisted) {\r\n logger.debug('Persistent storage granted - increased quota available');\r\n } else {\r\n logger.debug('Persistent storage denied - using default quota');\r\n }\r\n\r\n // Log current quota usage (helpful for debugging iOS limits)\r\n if (navigator.storage.estimate) {\r\n const estimate = await navigator.storage.estimate();\r\n const usedMB = ((estimate.usage || 0) / 1024 / 1024).toFixed(2);\r\n const quotaMB = ((estimate.quota || 0) / 1024 / 1024).toFixed(2);\r\n logger.debug('Storage quota', { usedMB, quotaMB });\r\n }\r\n } catch (err) {\r\n logger.warn('Failed to request persistent storage', { error: String(err) });\r\n }\r\n }\r\n\r\n const dbOpenStart = getClock().now();\r\n this.dbPromise = new Promise((resolve, reject) => {\r\n const request = indexedDB.open(DB_NAME, DB_VERSION);\r\n\r\n request.onerror = () => {\r\n logger.error('Failed to open IndexedDB', { error: String(request.error) });\r\n reject(request.error);\r\n };\r\n\r\n request.onsuccess = () => {\r\n this.db = request.result;\r\n logger.debug('IndexedDB opened', { durationMs: Math.round(getClock().now() - dbOpenStart) });\r\n resolve(this.db);\r\n };\r\n\r\n request.onupgradeneeded = (event) => {\r\n const db = (event.target as IDBOpenDBRequest).result;\r\n const oldVersion = (event as IDBVersionChangeEvent).oldVersion;\r\n const tx = (event.target as IDBOpenDBRequest).transaction;\r\n\r\n if (oldVersion < 1) {\r\n // Initial schema: create store with url as key\r\n const store = db.createObjectStore(STORE_NAME, { keyPath: 'url' });\r\n store.createIndex('lastAccessedAt', 'lastAccessedAt', { unique: false });\r\n } else if (oldVersion < 2 && tx) {\r\n // Migrate from v1 to v2: add lastAccessedAt index and backfill existing entries\r\n const store = tx.objectStore(STORE_NAME);\r\n\r\n // Create index if it doesn't exist\r\n if (!store.indexNames.contains('lastAccessedAt')) {\r\n store.createIndex('lastAccessedAt', 'lastAccessedAt', { unique: false });\r\n }\r\n\r\n // Migrate existing entries: set lastAccessedAt = cachedAt\r\n const cursorRequest = store.openCursor();\r\n cursorRequest.onsuccess = (cursorEvent) => {\r\n const cursor = (cursorEvent.target as IDBRequest<IDBCursorWithValue>).result;\r\n if (cursor) {\r\n const value = cursor.value;\r\n if (value.lastAccessedAt === undefined) {\r\n value.lastAccessedAt = value.cachedAt || Date.now();\r\n cursor.update(value);\r\n }\r\n cursor.continue();\r\n }\r\n };\r\n }\r\n };\r\n });\r\n\r\n return this.dbPromise;\r\n }\r\n\r\n /**\r\n * Check if a model is cached\r\n */\r\n async has(url: string): Promise<boolean> {\r\n try {\r\n const db = await this.getDB();\r\n return new Promise((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readonly');\r\n const store = tx.objectStore(STORE_NAME);\r\n const request = store.count(url);\r\n request.onsuccess = () => resolve(request.result > 0);\r\n request.onerror = () => resolve(false);\r\n });\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n /**\r\n * Get a cached model\r\n *\r\n * Updates lastAccessedAt timestamp for LRU tracking on cache hit.\r\n */\r\n async get(url: string): Promise<ArrayBuffer | null> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('ModelCache.get', { 'cache.url': url });\r\n try {\r\n const db = await this.getDB();\r\n return new Promise((resolve) => {\r\n // Use readwrite to update lastAccessedAt on hit\r\n const tx = db.transaction(STORE_NAME, 'readwrite');\r\n const store = tx.objectStore(STORE_NAME);\r\n const request = store.get(url);\r\n request.onsuccess = () => {\r\n const cached = request.result as CachedModel | undefined;\r\n const hit = cached?.data != null;\r\n span?.setAttributes({ 'cache.hit': hit });\r\n if (cached) {\r\n span?.setAttributes({ 'cache.size_bytes': cached.size });\r\n // Update lastAccessedAt for LRU tracking\r\n cached.lastAccessedAt = Date.now();\r\n store.put(cached);\r\n }\r\n span?.end();\r\n if (hit) {\r\n telemetry?.incrementCounter(MetricNames.CACHE_HITS, 1, {});\r\n } else {\r\n telemetry?.incrementCounter(MetricNames.CACHE_MISSES, 1, {});\r\n }\r\n resolve(cached?.data ?? null);\r\n };\r\n request.onerror = () => {\r\n span?.setAttributes({ 'cache.hit': false });\r\n span?.end();\r\n telemetry?.incrementCounter(MetricNames.CACHE_MISSES, 1, {});\r\n resolve(null);\r\n };\r\n });\r\n } catch {\r\n span?.endWithError(new Error('Cache get failed'));\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Get a cached model with ETag validation\r\n *\r\n * Validates the cached data against the server's current ETag.\r\n * If the cached ETag differs from the server's, the data is marked as stale.\r\n *\r\n * @param url - The cache key\r\n * @param originalUrl - The original URL for HEAD request (if different from cache key)\r\n * @returns ValidationResult with data and stale flag\r\n *\r\n * @example\r\n * ```typescript\r\n * const result = await cache.getWithValidation('http://example.com/model.onnx');\r\n * if (result.data && !result.stale) {\r\n * // Use cached data\r\n * } else if (result.stale) {\r\n * // Refetch and update cache\r\n * }\r\n * ```\r\n */\r\n async getWithValidation(url: string, originalUrl?: string): Promise<ValidationResult> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('ModelCache.getWithValidation', { 'cache.url': url });\r\n\r\n try {\r\n const db = await this.getDB();\r\n const cached = await new Promise<CachedModel | undefined>((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readonly');\r\n const store = tx.objectStore(STORE_NAME);\r\n const request = store.get(url);\r\n request.onsuccess = () => resolve(request.result as CachedModel | undefined);\r\n request.onerror = () => resolve(undefined);\r\n });\r\n\r\n // Cache miss\r\n if (!cached?.data) {\r\n span?.setAttributes({ 'cache.hit': false });\r\n span?.end();\r\n telemetry?.incrementCounter(MetricNames.CACHE_MISSES, 1, {});\r\n return { data: null, stale: false };\r\n }\r\n\r\n span?.setAttributes({ 'cache.hit': true, 'cache.size_bytes': cached.size });\r\n\r\n // No etag stored - can't validate, return as fresh\r\n if (!cached.etag) {\r\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\r\n span?.end();\r\n telemetry?.incrementCounter(MetricNames.CACHE_HITS, 1, {});\r\n return { data: cached.data, stale: false };\r\n }\r\n\r\n // Validate via HEAD request\r\n const fetchUrl = originalUrl || url;\r\n try {\r\n const response = await fetch(fetchUrl, { method: 'HEAD' });\r\n if (!response.ok) {\r\n // Server error - assume cache is still valid\r\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\r\n span?.end();\r\n telemetry?.incrementCounter(MetricNames.CACHE_HITS, 1, {});\r\n return { data: cached.data, stale: false };\r\n }\r\n\r\n const serverEtag = response.headers.get('etag');\r\n const isStale = serverEtag !== null && serverEtag !== cached.etag;\r\n\r\n span?.setAttributes({\r\n 'cache.validated': true,\r\n 'cache.stale': isStale,\r\n 'cache.server_etag': serverEtag || 'none',\r\n 'cache.cached_etag': cached.etag,\r\n });\r\n span?.end();\r\n\r\n if (isStale) {\r\n telemetry?.incrementCounter(MetricNames.CACHE_STALE, 1, {});\r\n logger.debug('Stale cache detected', { url });\r\n } else {\r\n telemetry?.incrementCounter(MetricNames.CACHE_HITS, 1, {});\r\n }\r\n\r\n return { data: cached.data, stale: isStale };\r\n } catch (fetchError) {\r\n // HEAD request failed (network error, CORS, etc.)\r\n // Return cached data as non-stale - better than failing completely\r\n logger.warn('HEAD validation failed, using cached data', { error: String(fetchError) });\r\n span?.setAttributes({ 'cache.validated': false, 'cache.stale': false });\r\n span?.end();\r\n telemetry?.incrementCounter(MetricNames.CACHE_HITS, 1, {});\r\n return { data: cached.data, stale: false };\r\n }\r\n } catch {\r\n span?.endWithError(new Error('Cache getWithValidation failed'));\r\n return { data: null, stale: false };\r\n }\r\n }\r\n\r\n /**\r\n * Store a model in cache\r\n *\r\n * After storing, triggers LRU eviction if cache exceeds size limit.\r\n *\r\n * @param url - The cache key (use getCacheKey() for versioned keys)\r\n * @param data - The model data\r\n * @param etag - Optional ETag for staleness validation\r\n * @param version - Optional version string for metadata\r\n */\r\n async set(url: string, data: ArrayBuffer, etag?: string, version?: string): Promise<void> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('ModelCache.set', {\r\n 'cache.url': url,\r\n 'cache.size_bytes': data.byteLength,\r\n ...(version && { 'cache.version': version }),\r\n });\r\n try {\r\n // Check quota before caching (best effort, don't block write)\r\n this.checkQuota().catch((err) => {\r\n logger.warn('Quota check failed', { error: String(err) });\r\n });\r\n\r\n const db = await this.getDB();\r\n await new Promise<void>((resolve, reject) => {\r\n const tx = db.transaction(STORE_NAME, 'readwrite');\r\n const store = tx.objectStore(STORE_NAME);\r\n const now = Date.now();\r\n const cached: CachedModel = {\r\n url,\r\n data,\r\n size: data.byteLength,\r\n cachedAt: now,\r\n lastAccessedAt: now,\r\n etag,\r\n version,\r\n };\r\n const request = store.put(cached);\r\n request.onsuccess = () => {\r\n span?.end();\r\n resolve();\r\n };\r\n request.onerror = () => {\r\n span?.endWithError(request.error || new Error('Cache set failed'));\r\n reject(request.error);\r\n };\r\n });\r\n\r\n // Log total cache size after write\r\n this.getStats().then((stats) => {\r\n logger.debug('Cache updated', { url, sizeBytes: data.byteLength, totalSizeBytes: stats.totalSize, modelCount: stats.modelCount });\r\n }).catch(() => { /* best effort */ });\r\n\r\n // Trigger LRU cleanup after write (don't block)\r\n this.enforceLimit().catch((err) => {\r\n logger.warn('Failed to enforce limit after set', { error: String(err) });\r\n });\r\n } catch (err) {\r\n logger.warn('Failed to cache model', { url, error: String(err) });\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n }\r\n }\r\n\r\n /**\r\n * Check storage quota and trigger warnings/cleanup as needed\r\n *\r\n * - Logs warning if quota > 90% used\r\n * - Triggers LRU cleanup if quota > 95% used\r\n * - Calls onQuotaWarning callback if configured\r\n */\r\n private async checkQuota(): Promise<void> {\r\n const quota = await this.getQuotaInfo();\r\n if (!quota) {\r\n return; // API unavailable\r\n }\r\n\r\n const config = globalCacheConfig;\r\n const telemetry = getTelemetry();\r\n\r\n if (quota.percentUsed > 90) {\r\n logger.warn('Storage quota warning', { percentUsed: quota.percentUsed.toFixed(1), used: formatBytes(quota.usedBytes), quota: formatBytes(quota.quotaBytes) });\r\n\r\n // Emit telemetry counter\r\n telemetry?.incrementCounter(MetricNames.CACHE_QUOTA_WARNING, 1, {\r\n percent_used: String(Math.round(quota.percentUsed)),\r\n });\r\n\r\n // Call user callback if configured\r\n if (config.onQuotaWarning) {\r\n try {\r\n config.onQuotaWarning(quota);\r\n } catch (err) {\r\n logger.warn('onQuotaWarning callback error', { error: String(err) });\r\n }\r\n }\r\n }\r\n\r\n if (quota.percentUsed > 95) {\r\n logger.warn('Storage quota critical (>95%), triggering LRU cleanup', { percentUsed: quota.percentUsed.toFixed(1) });\r\n // Free at least 10% of cache to make room\r\n const bytesToFree = Math.max(quota.cacheBytes * 0.1, 10 * 1024 * 1024);\r\n await this.evictOldest(bytesToFree);\r\n }\r\n }\r\n\r\n /**\r\n * Delete a cached model\r\n */\r\n async delete(url: string): Promise<void> {\r\n try {\r\n const db = await this.getDB();\r\n return new Promise((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readwrite');\r\n const store = tx.objectStore(STORE_NAME);\r\n store.delete(url);\r\n tx.oncomplete = () => resolve();\r\n });\r\n } catch {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n /**\r\n * Clear all cached models\r\n */\r\n async clear(): Promise<void> {\r\n try {\r\n const db = await this.getDB();\r\n return new Promise((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readwrite');\r\n const store = tx.objectStore(STORE_NAME);\r\n store.clear();\r\n tx.oncomplete = () => resolve();\r\n });\r\n } catch {\r\n // Ignore errors\r\n }\r\n }\r\n\r\n /**\r\n * Get cache statistics\r\n */\r\n async getStats(): Promise<CacheStats> {\r\n try {\r\n const db = await this.getDB();\r\n return new Promise((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readonly');\r\n const store = tx.objectStore(STORE_NAME);\r\n const request = store.getAll();\r\n request.onsuccess = () => {\r\n const models = (request.result as CachedModel[]) || [];\r\n resolve({\r\n totalSize: models.reduce((sum, m) => sum + m.size, 0),\r\n modelCount: models.length,\r\n models: models.map((m) => ({\r\n url: m.url,\r\n size: m.size,\r\n cachedAt: new Date(m.cachedAt),\r\n })),\r\n });\r\n };\r\n request.onerror = () => resolve({ totalSize: 0, modelCount: 0, models: [] });\r\n });\r\n } catch {\r\n return { totalSize: 0, modelCount: 0, models: [] };\r\n }\r\n }\r\n\r\n /**\r\n * Enforce cache size limit by evicting oldest entries (LRU)\r\n *\r\n * Called automatically after each set() operation.\r\n * Can also be called manually to trigger cleanup.\r\n */\r\n async enforceLimit(): Promise<void> {\r\n const config = globalCacheConfig;\r\n const maxSize = config.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES;\r\n\r\n const stats = await this.getStats();\r\n if (stats.totalSize <= maxSize) {\r\n return; // Under limit, nothing to do\r\n }\r\n\r\n const bytesToFree = stats.totalSize - maxSize;\r\n const evictedUrls = await this.evictOldest(bytesToFree);\r\n\r\n if (evictedUrls.length > 0) {\r\n logger.info('LRU eviction complete', { modelsRemoved: evictedUrls.length, bytesFreed: formatBytes(bytesToFree) });\r\n }\r\n }\r\n\r\n /**\r\n * Evict oldest entries (by lastAccessedAt) to free space\r\n *\r\n * @param bytesToFree - Minimum bytes to free\r\n * @returns List of evicted URLs\r\n *\r\n * @example\r\n * ```typescript\r\n * const cache = getModelCache();\r\n * const evicted = await cache.evictOldest(100 * 1024 * 1024); // Free 100MB\r\n * console.log('Evicted:', evicted);\r\n * ```\r\n */\r\n async evictOldest(bytesToFree: number): Promise<string[]> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('ModelCache.evictOldest', {\r\n 'eviction.bytes_requested': bytesToFree,\r\n });\r\n\r\n try {\r\n const db = await this.getDB();\r\n\r\n // Get all models sorted by lastAccessedAt (oldest first)\r\n const models = await new Promise<CachedModel[]>((resolve) => {\r\n const tx = db.transaction(STORE_NAME, 'readonly');\r\n const store = tx.objectStore(STORE_NAME);\r\n const request = store.getAll();\r\n request.onsuccess = () => {\r\n const all = (request.result as CachedModel[]) || [];\r\n // Sort by lastAccessedAt ascending (oldest first)\r\n all.sort((a, b) => (a.lastAccessedAt || a.cachedAt || 0) - (b.lastAccessedAt || b.cachedAt || 0));\r\n resolve(all);\r\n };\r\n request.onerror = () => resolve([]);\r\n });\r\n\r\n const evictedUrls: string[] = [];\r\n let freedBytes = 0;\r\n\r\n // Evict models until we've freed enough space\r\n for (const model of models) {\r\n if (freedBytes >= bytesToFree) {\r\n break;\r\n }\r\n\r\n await this.delete(model.url);\r\n evictedUrls.push(model.url);\r\n freedBytes += model.size;\r\n\r\n logger.debug('Evicted model', { url: model.url, sizeBytes: model.size });\r\n }\r\n\r\n span?.setAttributes({\r\n 'eviction.bytes_freed': freedBytes,\r\n 'eviction.models_evicted': evictedUrls.length,\r\n });\r\n span?.end();\r\n\r\n // Emit telemetry counter\r\n if (freedBytes > 0) {\r\n telemetry?.incrementCounter(MetricNames.CACHE_EVICTION, evictedUrls.length, {\r\n bytes_freed: String(freedBytes),\r\n });\r\n }\r\n\r\n return evictedUrls;\r\n } catch (err) {\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n logger.warn('Eviction failed', { error: String(err) });\r\n return [];\r\n }\r\n }\r\n\r\n /**\r\n * Get storage quota information\r\n *\r\n * Uses navigator.storage.estimate() to get quota details.\r\n * Returns null if the API is unavailable.\r\n *\r\n * @returns Quota info or null if unavailable\r\n *\r\n * @example\r\n * ```typescript\r\n * const cache = getModelCache();\r\n * const quota = await cache.getQuotaInfo();\r\n * if (quota) {\r\n * console.log(`Using ${quota.percentUsed.toFixed(1)}% of quota`);\r\n * }\r\n * ```\r\n */\r\n async getQuotaInfo(): Promise<QuotaInfo | null> {\r\n if (!navigator?.storage?.estimate) {\r\n return null;\r\n }\r\n\r\n try {\r\n const estimate = await navigator.storage.estimate();\r\n const usedBytes = estimate.usage || 0;\r\n const quotaBytes = estimate.quota || 0;\r\n const percentUsed = quotaBytes > 0 ? (usedBytes / quotaBytes) * 100 : 0;\r\n\r\n const stats = await this.getStats();\r\n\r\n return {\r\n usedBytes,\r\n quotaBytes,\r\n percentUsed,\r\n cacheBytes: stats.totalSize,\r\n };\r\n } catch {\r\n return null;\r\n }\r\n }\r\n}\r\n\r\n// Singleton instance\r\nlet cacheInstance: ModelCache | null = null;\r\n\r\n/**\r\n * Get the global ModelCache instance\r\n */\r\nexport function getModelCache(): ModelCache {\r\n if (!cacheInstance) {\r\n cacheInstance = new ModelCache();\r\n }\r\n return cacheInstance;\r\n}\r\n\r\n// Max size for IndexedDB caching\r\n// When storing ArrayBuffer in IndexedDB, browser does structured clone which\r\n// temporarily doubles memory usage. To avoid STATUS_BREAKPOINT crashes:\r\n// - Files < 500MB: Cache as ArrayBuffer (safe, fast retrieval)\r\n// - Files >= 500MB: Skip IndexedDB, rely on HTTP cache\r\n// See: https://bugs.chromium.org/p/chromium/issues/detail?id=170845\r\nconst MAX_CACHE_SIZE_BYTES = 500 * 1024 * 1024;\r\n\r\n/**\r\n * Options for fetchWithCache\r\n */\r\nexport interface FetchWithCacheOptions {\r\n /** Optional version string for versioned caching */\r\n version?: string;\r\n /** If true, validates cached data against server ETag and refetches if stale */\r\n validateStale?: boolean;\r\n /** Progress callback during download */\r\n onProgress?: (loaded: number, total: number) => void;\r\n /** Timeout per fetch attempt in ms. Default: 120_000 (2 min) */\r\n timeoutMs?: number;\r\n /** Max retry attempts on failure. Default: 2 (3 total attempts) */\r\n maxRetries?: number;\r\n /** AbortSignal to cancel the entire operation */\r\n signal?: AbortSignal;\r\n}\r\n\r\n/**\r\n * Fetch with an AbortController-based timeout.\r\n * Also respects an optional caller-provided AbortSignal.\r\n */\r\nfunction fetchWithTimeout(\r\n url: string,\r\n timeoutMs: number,\r\n signal?: AbortSignal,\r\n): Promise<Response> {\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), timeoutMs);\r\n\r\n // If the caller provided a signal, abort our controller when it fires\r\n const onCallerAbort = () => controller.abort();\r\n signal?.addEventListener('abort', onCallerAbort, { once: true });\r\n\r\n return fetch(url, { signal: controller.signal }).finally(() => {\r\n clearTimeout(timer);\r\n signal?.removeEventListener('abort', onCallerAbort);\r\n });\r\n}\r\n\r\n/**\r\n * Fetch a model with caching\r\n * Uses IndexedDB cache with network fallback\r\n * Files larger than 500MB are not cached to IndexedDB to avoid memory pressure\r\n * (structured clone during IndexedDB write temporarily doubles memory usage)\r\n *\r\n * @param url - The URL to fetch\r\n * @param onProgress - Optional progress callback (legacy signature)\r\n * @returns The fetched ArrayBuffer\r\n *\r\n * @example\r\n * ```typescript\r\n * // Simple usage (backwards compatible)\r\n * const data = await fetchWithCache('http://example.com/model.onnx');\r\n *\r\n * // With progress callback (backwards compatible)\r\n * const data = await fetchWithCache('http://example.com/model.onnx', (loaded, total) => {\r\n * console.log(`${loaded}/${total} bytes`);\r\n * });\r\n *\r\n * // With options (new API)\r\n * const data = await fetchWithCache('http://example.com/model.onnx', {\r\n * version: '1.0.0',\r\n * validateStale: true,\r\n * onProgress: (loaded, total) => console.log(`${loaded}/${total}`)\r\n * });\r\n * ```\r\n */\r\nexport async function fetchWithCache(\r\n url: string,\r\n optionsOrProgress?: FetchWithCacheOptions | ((loaded: number, total: number) => void)\r\n): Promise<ArrayBuffer> {\r\n // Normalize arguments - support both old and new signatures\r\n let options: FetchWithCacheOptions = {};\r\n if (typeof optionsOrProgress === 'function') {\r\n // Legacy signature: fetchWithCache(url, onProgress)\r\n options = { onProgress: optionsOrProgress };\r\n } else if (optionsOrProgress) {\r\n // New signature: fetchWithCache(url, options)\r\n options = optionsOrProgress;\r\n }\r\n\r\n const { version, validateStale = false, onProgress } = options;\r\n\r\n const cache = getModelCache();\r\n const cacheKey = version ? getCacheKey(url, version) : url;\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('fetchWithCache', {\r\n 'fetch.url': url,\r\n ...(version && { 'fetch.version': version }),\r\n 'fetch.validate_stale': validateStale,\r\n });\r\n\r\n // Check cache with optional staleness validation\r\n if (validateStale) {\r\n const validation = await cache.getWithValidation(cacheKey, url);\r\n\r\n if (validation.data && !validation.stale) {\r\n logger.debug('Cache hit (validated)', { url, sizeBytes: validation.data.byteLength });\r\n onProgress?.(validation.data.byteLength, validation.data.byteLength);\r\n span?.setAttributes({\r\n 'fetch.cache_hit': true,\r\n 'fetch.cache_validated': true,\r\n 'fetch.cache_stale': false,\r\n 'fetch.size_bytes': validation.data.byteLength,\r\n });\r\n span?.end();\r\n return validation.data;\r\n }\r\n\r\n if (validation.stale) {\r\n logger.debug('Cache stale, refetching', { url });\r\n span?.setAttributes({\r\n 'fetch.cache_hit': true,\r\n 'fetch.cache_validated': true,\r\n 'fetch.cache_stale': true,\r\n });\r\n // Continue to fetch fresh data\r\n }\r\n // If data is null, continue to fetch\r\n } else {\r\n // Simple cache check without validation (backwards compatible behavior)\r\n const cached = await cache.get(cacheKey);\r\n if (cached) {\r\n logger.debug('Cache hit', { url, sizeBytes: cached.byteLength });\r\n onProgress?.(cached.byteLength, cached.byteLength);\r\n span?.setAttributes({\r\n 'fetch.cache_hit': true,\r\n 'fetch.size_bytes': cached.byteLength,\r\n });\r\n span?.end();\r\n return cached;\r\n }\r\n }\r\n\r\n span?.setAttributes({ 'fetch.cache_hit': false });\r\n logger.debug('Cache miss, fetching', { url });\r\n\r\n const timeout = options.timeoutMs ?? 120_000;\r\n const maxRetries = options.maxRetries ?? 2;\r\n let lastError: Error | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n if (options.signal?.aborted) {\r\n throw new Error(`Fetch aborted for ${url}`);\r\n }\r\n\r\n if (attempt > 0) {\r\n const backoff = Math.min(2000 * Math.pow(2, attempt - 1), 16_000);\r\n logger.debug('Retrying fetch', { attempt, maxRetries, backoffMs: backoff, url });\r\n await new Promise(r => setTimeout(r, backoff));\r\n }\r\n\r\n try {\r\n const response = await fetchWithTimeout(url, timeout, options.signal);\r\n if (!response.ok) {\r\n throw new Error(`Failed to fetch ${url}: ${response.status}`);\r\n }\r\n\r\n const contentLength = response.headers.get('content-length');\r\n const total = contentLength ? parseInt(contentLength, 10) : 0;\r\n const etag = response.headers.get('etag') ?? undefined;\r\n\r\n // Check if file is too large for IndexedDB (avoid memory pressure during structured clone)\r\n const tooLargeForCache = total > MAX_CACHE_SIZE_BYTES;\r\n if (tooLargeForCache) {\r\n logger.debug('File too large for IndexedDB, using HTTP cache only', { url, sizeBytes: total, limitBytes: MAX_CACHE_SIZE_BYTES });\r\n }\r\n\r\n if (!response.body) {\r\n const data = await response.arrayBuffer();\r\n if (!tooLargeForCache) {\r\n // Fire-and-forget: model is usable immediately, cache write happens in background\r\n cache.set(cacheKey, data, etag, version).catch(err => {\r\n logger.warn('Background cache write failed', { url, error: String(err) });\r\n });\r\n }\r\n span?.setAttributes({\r\n 'fetch.size_bytes': data.byteLength,\r\n 'fetch.cached_to_indexeddb': !tooLargeForCache,\r\n ...(attempt > 0 && { 'fetch.retry_count': attempt }),\r\n });\r\n span?.end();\r\n return data;\r\n }\r\n\r\n // Stream with progress\r\n const reader = response.body.getReader();\r\n const chunks: Uint8Array[] = [];\r\n let loaded = 0;\r\n\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n chunks.push(value);\r\n loaded += value.length;\r\n logger.trace('Download progress', { url, loadedBytes: loaded, totalBytes: total || loaded });\r\n onProgress?.(loaded, total || loaded);\r\n }\r\n\r\n // Combine chunks\r\n const data = new Uint8Array(loaded);\r\n let offset = 0;\r\n for (const chunk of chunks) {\r\n data.set(chunk, offset);\r\n offset += chunk.length;\r\n }\r\n\r\n const buffer = data.buffer;\r\n\r\n // Cache for next time (if not too large) — fire-and-forget\r\n if (!tooLargeForCache) {\r\n cache.set(cacheKey, buffer, etag, version).catch(err => {\r\n logger.warn('Background cache write failed', { url, error: String(err) });\r\n });\r\n logger.debug('Cached after download', { url, sizeBytes: buffer.byteLength });\r\n }\r\n\r\n span?.setAttributes({\r\n 'fetch.size_bytes': buffer.byteLength,\r\n 'fetch.cached_to_indexeddb': !tooLargeForCache,\r\n ...(attempt > 0 && { 'fetch.retry_count': attempt }),\r\n });\r\n span?.end();\r\n\r\n return buffer;\r\n } catch (error) {\r\n lastError = error instanceof Error ? error : new Error(String(error));\r\n if (options.signal?.aborted) {\r\n span?.endWithError(lastError);\r\n throw lastError;\r\n }\r\n if (attempt < maxRetries) {\r\n logger.warn('Fetch attempt failed', { attempt: attempt + 1, url, error: lastError.message });\r\n }\r\n }\r\n }\r\n\r\n span?.endWithError(lastError!);\r\n throw lastError;\r\n}\r\n\r\n/**\r\n * Preload models into cache without creating sessions\r\n */\r\nexport async function preloadModels(\r\n urls: string[],\r\n onProgress?: (current: number, total: number, url: string) => void\r\n): Promise<void> {\r\n const cache = getModelCache();\r\n\r\n for (let i = 0; i < urls.length; i++) {\r\n const url = urls[i];\r\n onProgress?.(i, urls.length, url);\r\n\r\n if (await cache.has(url)) {\r\n logger.debug('Already cached, skipping', { url });\r\n continue;\r\n }\r\n\r\n await fetchWithCache(url);\r\n }\r\n\r\n onProgress?.(urls.length, urls.length, 'done');\r\n}\r\n\r\n/**\r\n * Format bytes as human readable string\r\n */\r\nexport function formatBytes(bytes: number): string {\r\n if (bytes < 1024) return `${bytes} B`;\r\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\r\n if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\r\n return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;\r\n}\r\n","/**\r\n * Kokoro TTS voice style loader\r\n *\r\n * Fetches and parses .bin voice files from HuggingFace CDN.\r\n * Each voice file contains style vectors shaped (511, 1, 256) as Float32.\r\n * The style vector for a given token count is indexed by clamped position.\r\n *\r\n * @category Inference\r\n * @module inference/kokoroVoices\r\n */\r\n\r\nimport { fetchWithCache, formatBytes } from '../cache/ModelCache';\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('KokoroVoices');\r\n\r\n/** Number of style entries per voice file (tokens 0-510) */\r\nconst NUM_STYLE_ENTRIES = 511;\r\n/** Style vector dimensionality */\r\nconst STYLE_DIM = 256;\r\n/** Expected Float32 count per voice file */\r\nconst EXPECTED_FLOATS = NUM_STYLE_ENTRIES * 1 * STYLE_DIM; // 511 * 1 * 256 = 130816\r\n\r\n/** Available Kokoro v1.0 voices */\r\nexport const KOKORO_VOICES = {\r\n // American Female\r\n af_heart: 'af_heart', af_alloy: 'af_alloy', af_aoede: 'af_aoede',\r\n af_bella: 'af_bella', af_jessica: 'af_jessica', af_kore: 'af_kore',\r\n af_nicole: 'af_nicole', af_nova: 'af_nova', af_river: 'af_river',\r\n af_sarah: 'af_sarah', af_sky: 'af_sky',\r\n // American Male\r\n am_adam: 'am_adam', am_echo: 'am_echo', am_eric: 'am_eric',\r\n am_fenrir: 'am_fenrir', am_liam: 'am_liam', am_michael: 'am_michael',\r\n am_onyx: 'am_onyx', am_puck: 'am_puck', am_santa: 'am_santa',\r\n // British Female\r\n bf_alice: 'bf_alice', bf_emma: 'bf_emma', bf_isabella: 'bf_isabella', bf_lily: 'bf_lily',\r\n // British Male\r\n bm_daniel: 'bm_daniel', bm_fable: 'bm_fable', bm_george: 'bm_george', bm_lewis: 'bm_lewis',\r\n // Spanish Female\r\n ef_dora: 'ef_dora',\r\n // Spanish Male\r\n em_alex: 'em_alex', em_santa: 'em_santa',\r\n // French Female\r\n ff_siwis: 'ff_siwis',\r\n} as const;\r\n\r\nexport type KokoroVoiceName = keyof typeof KOKORO_VOICES;\r\n\r\n/** Cached voice data: voice name → Float32Array (511 * 256 floats) */\r\nconst _voiceCache = new Map<string, Float32Array>();\r\n\r\n/**\r\n * Load a voice style file from URL and cache it.\r\n *\r\n * @param voiceName - Voice identifier (e.g., 'af_heart')\r\n * @param voiceBaseUrl - Base URL for voice files (should end with `/voices/`)\r\n * @returns The raw Float32Array of style data\r\n */\r\nexport async function loadVoice(voiceName: string, voiceBaseUrl: string): Promise<Float32Array> {\r\n if (!/^[a-z][a-z0-9_]*$/.test(voiceName)) {\r\n throw new Error('Invalid voice name: ' + voiceName);\r\n }\r\n\r\n const cached = _voiceCache.get(voiceName);\r\n if (cached) return cached;\r\n\r\n const url = `${voiceBaseUrl.replace(/\\/$/, '')}/${voiceName}.bin`;\r\n logger.info('Loading voice', { voice: voiceName, url });\r\n\r\n const buffer = await fetchWithCache(url);\r\n const data = new Float32Array(buffer);\r\n\r\n if (data.length !== EXPECTED_FLOATS) {\r\n logger.warn('Voice file unexpected size', {\r\n voice: voiceName,\r\n expected: EXPECTED_FLOATS,\r\n actual: data.length,\r\n bytes: formatBytes(buffer.byteLength),\r\n });\r\n }\r\n\r\n _voiceCache.set(voiceName, data);\r\n logger.info('Voice loaded', { voice: voiceName, bytes: formatBytes(buffer.byteLength) });\r\n return data;\r\n}\r\n\r\n/**\r\n * Get the style vector for a given token count from a loaded voice.\r\n *\r\n * @param voiceData - Raw Float32Array from loadVoice()\r\n * @param numTokens - Number of tokens in the input (used to index into style entries)\r\n * @returns Float32Array of shape [1, 256] — the style vector for this token count\r\n */\r\nexport function getStyleForTokenCount(voiceData: Float32Array, numTokens: number): Float32Array {\r\n // Clamp index to valid range [0, 510]\r\n const idx = Math.min(Math.max(numTokens, 0), NUM_STYLE_ENTRIES - 1);\r\n const offset = idx * STYLE_DIM;\r\n return voiceData.slice(offset, offset + STYLE_DIM);\r\n}\r\n\r\n/**\r\n * List all available voice names.\r\n */\r\nexport function listVoices(): string[] {\r\n return Object.keys(KOKORO_VOICES);\r\n}\r\n\r\n/** Language code mapping: first character of voice name → language code */\r\nconst VOICE_LANG_MAP: Record<string, string> = {\r\n a: 'a', // American English\r\n b: 'b', // British English\r\n e: 'e', // Spanish\r\n f: 'f', // French\r\n h: 'h', // Hindi\r\n i: 'i', // Italian\r\n j: 'j', // Japanese\r\n p: 'p', // Brazilian Portuguese\r\n z: 'z', // Mandarin Chinese\r\n};\r\n\r\n/**\r\n * Get the language code for a voice (inferred from prefix).\r\n * First character encodes language: a=American, b=British, e=Spanish, f=French, etc.\r\n */\r\nexport function getVoiceLanguage(voiceName: string): string {\r\n return VOICE_LANG_MAP[voiceName[0]] ?? 'a';\r\n}\r\n\r\n/**\r\n * Clear the voice cache (for testing or memory management).\r\n */\r\nexport function clearVoiceCache(): void {\r\n _voiceCache.clear();\r\n}\r\n\r\nexport { NUM_STYLE_ENTRIES, STYLE_DIM };\r\n","/**\n * Kokoro TTS type definitions\n *\n * @category Inference\n */\n\nimport type { BackendPreference } from '../utils/runtime';\nimport { KOKORO_VOICES } from './kokoroVoices';\n\n// ─── Config ─────────────────────────────────────────────────────────────────\n\nexport interface KokoroTTSConfig {\n /** ONNX model URL (default: HF CDN q8, 92MB) */\n modelUrl?: string;\n /** Voice files base URL (default: HF CDN voices directory) */\n voiceBaseUrl?: string;\n /** Default voice (default: 'af_heart') */\n defaultVoice?: string;\n /** Backend preference (default: 'wasm' — WebGPU crashes on int64 input_ids) */\n backend?: BackendPreference;\n /** Speech speed multiplier (default: 1.0) */\n speed?: number;\n /** Eagerly load phonemizer + default voice during load() instead of first speak(). Default: true. */\n eagerLoad?: boolean;\n}\n\n// ─── Result Types ───────────────────────────────────────────────────────────\n\nexport interface KokoroTTSResult {\n /** Audio samples at 24kHz */\n audio: Float32Array;\n /** Duration in seconds */\n duration: number;\n /** Inference time in ms */\n inferenceTimeMs: number;\n}\n\nexport interface KokoroStreamChunk {\n /** Audio for this sentence */\n audio: Float32Array;\n /** Original text segment */\n text: string;\n /** Phonemes for this segment */\n phonemes: string;\n /** Duration in seconds */\n duration: number;\n}\n\nexport interface KokoroTTSModelInfo {\n /** Resolved backend */\n backend: string;\n /** Model load time in ms */\n loadTimeMs: number;\n /** Default voice */\n defaultVoice: string;\n}\n\n// ─── Synthesis Options ──────────────────────────────────────────────────────\n\nexport interface SynthesizeOptions {\n /** Voice to use (overrides defaultVoice) */\n voice?: string;\n /** Speed multiplier (overrides config speed) */\n speed?: number;\n}\n\n// ─── Input Validation ────────────────────────────────────────────────────────\n\nconst MIN_SPEED = 0.25;\nconst MAX_SPEED = 4.0;\n\n/**\n * Validate TTS input parameters at API boundaries.\n * Returns trimmed text on success, throws on invalid input.\n */\nexport function validateTTSInput(\n text: unknown,\n voiceName: string,\n speed: number,\n availableVoices?: string[],\n): string {\n if (text === null || text === undefined || typeof text !== 'string') {\n throw new Error('KokoroTTS: text must be a string');\n }\n const trimmed = text.trim();\n if (trimmed.length === 0) {\n throw new Error('KokoroTTS: text must not be empty');\n }\n if (!Number.isFinite(speed) || speed < MIN_SPEED || speed > MAX_SPEED) {\n throw new Error(`KokoroTTS: speed must be between ${MIN_SPEED} and ${MAX_SPEED}, got ${speed}`);\n }\n const voices = availableVoices ?? Object.keys(KOKORO_VOICES);\n if (!voices.includes(voiceName)) {\n throw new Error(`KokoroTTS: unknown voice \"${voiceName}\". Available: ${voices.slice(0, 5).join(', ')}...`);\n }\n return trimmed;\n}\n","/**\r\n * Kokoro TTS adapter backed by UnifiedInferenceWorker\r\n *\r\n * Implements TTSBackend, delegating ONNX inference to the shared worker.\r\n * Phonemization, tokenization, and voice loading stay on main thread (fast, <10ms).\r\n * Only the heavy `session.run()` (~1-2s per sentence) goes to the worker.\r\n */\r\n\r\nimport { createLogger } from '../../logging';\r\nimport { getClock } from '../../logging/Clock';\r\nimport { getTelemetry } from '../../telemetry';\r\nimport { MetricNames } from '../../telemetry/types';\r\nimport { tokenize } from '../kokoroTokenizer';\r\nimport { phonemize, loadPhonemizer, splitSentences } from '../kokoroPhonemizer';\r\nimport { loadVoice, getStyleForTokenCount, getVoiceLanguage } from '../kokoroVoices';\r\nimport { DEFAULT_MODEL_URLS } from '../defaultModelUrls';\r\nimport type { TTSBackend, TTSStreamOptions, TTSChunk } from '../TTSBackend';\r\nimport { validateTTSInput } from '../KokoroTTSTypes';\r\nimport type { KokoroTTSConfig, KokoroTTSModelInfo, KokoroStreamChunk, SynthesizeOptions } from '../KokoroTTSTypes';\r\nimport type { UnifiedInferenceWorker } from '../UnifiedInferenceWorker';\r\n\r\nconst logger = createLogger('KokoroTTSUnifiedAdapter');\r\n\r\nexport class KokoroTTSUnifiedAdapter implements TTSBackend {\r\n private worker: UnifiedInferenceWorker;\r\n private readonly config: Required<Pick<KokoroTTSConfig, 'defaultVoice' | 'speed'>> & KokoroTTSConfig;\r\n private readonly modelUrl: string;\r\n private readonly voiceBaseUrl: string;\r\n\r\n private _isLoaded = false;\r\n private _backend: 'wasm' | 'webgpu' = 'wasm';\r\n private loadedGeneration = 0;\r\n /** Per-adapter inference queue — ensures sequential state updates. */\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n private loadedVoices = new Map<string, Float32Array>();\r\n private phonemizerReady = false;\r\n private defaultVoiceLoaded = false;\r\n\r\n constructor(worker: UnifiedInferenceWorker, config: KokoroTTSConfig = {}) {\r\n this.worker = worker;\r\n this.config = {\r\n defaultVoice: 'af_heart',\r\n speed: 1.0,\r\n eagerLoad: true,\r\n ...config,\r\n };\r\n this.modelUrl = config.modelUrl ?? DEFAULT_MODEL_URLS.kokoroTTS;\r\n this.voiceBaseUrl = config.voiceBaseUrl ?? DEFAULT_MODEL_URLS.kokoroVoices;\r\n }\r\n\r\n get isLoaded(): boolean { return this._isLoaded; }\r\n get sampleRate(): number { return 24000; }\r\n\r\n async load(): Promise<KokoroTTSModelInfo> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('KokoroTTSUnifiedAdapter.load', {\r\n 'model.url': this.modelUrl,\r\n });\r\n\r\n try {\r\n const startTime = getClock().now();\r\n\r\n // Load ONNX model in worker — phonemizer + voice deferred to first stream() call\r\n await this.worker.loadKokoro({ modelUrl: this.modelUrl });\r\n\r\n this._isLoaded = true;\r\n this._backend = this.worker.backend;\r\n this.loadedGeneration = this.worker.workerGeneration;\r\n\r\n // Eager load: pre-warm phonemizer + default voice so first speak() has no latency spike\r\n if (this.config.eagerLoad) {\r\n await loadPhonemizer();\r\n this.phonemizerReady = true;\r\n const voiceData = await loadVoice(this.config.defaultVoice, this.voiceBaseUrl);\r\n this.loadedVoices.set(this.config.defaultVoice, voiceData);\r\n this.defaultVoiceLoaded = true;\r\n }\r\n const loadTimeMs = getClock().now() - startTime;\r\n\r\n logger.info('Kokoro TTS loaded via unified worker', {\r\n backend: this._backend,\r\n loadTimeMs: Math.round(loadTimeMs),\r\n defaultVoice: this.config.defaultVoice,\r\n });\r\n\r\n span?.setAttributes({ 'model.backend': this._backend, 'model.load_time_ms': loadTimeMs });\r\n span?.end();\r\n telemetry?.recordHistogram(MetricNames.MODEL_LOAD_TIME, loadTimeMs, {\r\n model: 'kokoro-tts-unified',\r\n backend: this._backend,\r\n });\r\n\r\n return { backend: this._backend, loadTimeMs, defaultVoice: this.config.defaultVoice };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n getTelemetry()?.incrementCounter(MetricNames.ERRORS_TOTAL, 1, {\r\n model: 'kokoro-tts-unified',\r\n error_type: 'load_failed',\r\n });\r\n throw error;\r\n }\r\n }\r\n\r\n async *stream(text: string, options?: SynthesizeOptions & TTSStreamOptions): AsyncGenerator<KokoroStreamChunk & TTSChunk> {\r\n this.assertLoaded();\r\n\r\n const voiceName = options?.voice ?? this.config.defaultVoice;\r\n const speed = options?.speed ?? this.config.speed;\r\n text = validateTTSInput(text, voiceName, speed);\r\n\r\n // Lazy-load phonemizer + default voice on first stream() call (avoids blocking load())\r\n if (!this.phonemizerReady) {\r\n await loadPhonemizer();\r\n this.phonemizerReady = true;\r\n }\r\n if (!this.defaultVoiceLoaded) {\r\n const voiceData = await loadVoice(this.config.defaultVoice, this.voiceBaseUrl);\r\n this.loadedVoices.set(this.config.defaultVoice, voiceData);\r\n this.defaultVoiceLoaded = true;\r\n }\r\n\r\n const sentences = options?.singleShot ? [text] : splitSentences(text);\r\n const language = options?.language ?? getVoiceLanguage(voiceName);\r\n\r\n for (const sentence of sentences) {\r\n if (options?.signal?.aborted) return;\r\n\r\n // Phonemize + tokenize on main thread (fast, <10ms)\r\n const phonemes = await phonemize(sentence, language);\r\n const tokens = tokenize(phonemes);\r\n const voiceData = await this.ensureVoice(voiceName);\r\n const style = getStyleForTokenCount(voiceData, tokens.length);\r\n\r\n if (options?.signal?.aborted) return;\r\n\r\n // Delegate heavy ONNX inference to unified worker\r\n const audio = await this.runWorkerInference(tokens, style, speed);\r\n const duration = audio.length / 24000;\r\n\r\n yield { audio, text: sentence, phonemes, duration };\r\n }\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this._isLoaded) {\r\n await this.worker.disposeKokoro();\r\n this._isLoaded = false;\r\n }\r\n this.loadedVoices.clear();\r\n }\r\n\r\n private async ensureVoice(voiceName: string): Promise<Float32Array> {\r\n let data = this.loadedVoices.get(voiceName);\r\n if (!data) {\r\n data = await loadVoice(voiceName, this.voiceBaseUrl);\r\n this.loadedVoices.set(voiceName, data);\r\n }\r\n return data;\r\n }\r\n\r\n private assertLoaded(): void {\r\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\r\n if (this.worker.workerGeneration !== this.loadedGeneration) {\r\n this._isLoaded = false;\r\n throw new Error('Worker was recovered — model session lost. Call load() again.');\r\n }\r\n }\r\n\r\n private runWorkerInference(tokens: number[], style: Float32Array, speed: number): Promise<Float32Array> {\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n const startTime = getClock().now();\r\n const telemetry = getTelemetry();\r\n try {\r\n const result = await this.worker.inferKokoro(tokens, style, speed);\r\n const latencyMs = getClock().now() - startTime;\r\n telemetry?.recordHistogram(MetricNames.INFERENCE_LATENCY, latencyMs, {\r\n model: 'kokoro-tts-unified',\r\n backend: this._backend,\r\n });\r\n telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {\r\n model: 'kokoro-tts-unified',\r\n backend: this._backend,\r\n status: 'success',\r\n });\r\n resolve(result.audio);\r\n } catch (err) {\r\n telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {\r\n model: 'kokoro-tts-unified',\r\n backend: this._backend,\r\n status: 'error',\r\n });\r\n const span = telemetry?.startSpan('KokoroTTSUnifiedAdapter.inferError', {\r\n 'model.name': 'kokoro-tts-unified',\r\n 'model.backend': this._backend,\r\n });\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n}\r\n","/**\r\n * Silero VAD adapter backed by UnifiedInferenceWorker\r\n *\r\n * Implements SileroVADBackend, delegating all inference to the shared worker.\r\n */\r\n\r\nimport { createLogger } from '../../logging';\r\nimport { getClock } from '../../logging/Clock';\r\nimport { getTelemetry } from '../../telemetry';\r\nimport { MetricNames } from '../../telemetry/types';\r\nimport type { SileroVADConfig, VADResult, SileroVADBackend, VADWorkerModelInfo } from '../SileroVADTypes';\r\nimport type { RuntimeBackend } from '../../utils/runtime';\r\nimport type { UnifiedInferenceWorker } from '../UnifiedInferenceWorker';\r\n\r\nconst logger = createLogger('SileroVADUnifiedAdapter');\r\n\r\nexport class SileroVADUnifiedAdapter implements SileroVADBackend {\r\n private worker: UnifiedInferenceWorker;\r\n private config: Required<SileroVADConfig>;\r\n private _isLoaded = false;\r\n private loadedGeneration = 0;\r\n\r\n // LSTM state (kept in main thread, sent to worker per inference)\r\n private state: Float32Array;\r\n private context: Float32Array;\r\n private readonly chunkSize: number;\r\n private readonly contextSize: number;\r\n\r\n /**\r\n * Per-adapter inference queue — ensures sequential state updates.\r\n *\r\n * The unified worker processes messages serially (single thread), but this queue\r\n * guarantees per-adapter state consistency. Example: VAD LSTM state from call N\r\n * must be applied before call N+1 starts. Without the queue, two rapid process()\r\n * calls could both read the same stale state.\r\n */\r\n private inferenceQueue: Promise<void> = Promise.resolve();\r\n\r\n // Pre-speech buffer\r\n private preSpeechBuffer: Float32Array[] = [];\r\n private wasSpeaking = false;\r\n\r\n constructor(worker: UnifiedInferenceWorker, config: SileroVADConfig) {\r\n this.worker = worker;\r\n const sr = config.sampleRate ?? 16000;\r\n this.config = {\r\n modelUrl: config.modelUrl,\r\n backend: config.backend ?? 'wasm',\r\n sampleRate: sr,\r\n threshold: config.threshold ?? 0.5,\r\n preSpeechBufferChunks: config.preSpeechBufferChunks ?? 10,\r\n };\r\n this.chunkSize = sr === 16000 ? 512 : 256;\r\n this.contextSize = sr === 16000 ? 64 : 32;\r\n this.state = new Float32Array(2 * 1 * 128);\r\n this.context = new Float32Array(this.contextSize);\r\n }\r\n\r\n get isLoaded(): boolean { return this._isLoaded; }\r\n get backend(): RuntimeBackend | null { return this._isLoaded ? 'wasm' : null; }\r\n get sampleRate(): number { return this.config.sampleRate; }\r\n get threshold(): number { return this.config.threshold; }\r\n\r\n getChunkSize(): number { return this.chunkSize; }\r\n getChunkDurationMs(): number { return (this.chunkSize / this.config.sampleRate) * 1000; }\r\n\r\n async load(): Promise<VADWorkerModelInfo> {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SileroVADUnifiedAdapter.load', {\r\n 'model.url': this.config.modelUrl,\r\n });\r\n\r\n try {\r\n const result = await this.worker.loadVAD({\r\n modelUrl: this.config.modelUrl,\r\n sampleRate: this.config.sampleRate,\r\n });\r\n this._isLoaded = true;\r\n this.loadedGeneration = this.worker.workerGeneration;\r\n\r\n logger.info('SileroVAD loaded via unified worker', {\r\n backend: 'wasm',\r\n loadTimeMs: Math.round(result.loadTimeMs),\r\n sampleRate: this.config.sampleRate,\r\n chunkSize: this.chunkSize,\r\n });\r\n\r\n span?.setAttributes({ 'model.backend': 'wasm', 'model.load_time_ms': result.loadTimeMs });\r\n span?.end();\r\n telemetry?.recordHistogram(MetricNames.MODEL_LOAD_TIME, result.loadTimeMs, {\r\n model: 'silero-vad-unified',\r\n backend: 'wasm',\r\n });\r\n\r\n return result;\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n }\r\n\r\n async process(audioChunk: Float32Array): Promise<VADResult> {\r\n this.assertLoaded();\r\n\r\n if (audioChunk.length !== this.chunkSize) {\r\n throw new Error(\r\n `Audio chunk must be exactly ${this.chunkSize} samples (got ${audioChunk.length}). ` +\r\n `Use getChunkSize() to get required size.`\r\n );\r\n }\r\n\r\n const audioChunkCopy = new Float32Array(audioChunk);\r\n\r\n return new Promise((resolve, reject) => {\r\n this.inferenceQueue = this.inferenceQueue.then(async () => {\r\n try {\r\n const startTime = getClock().now();\r\n const result = await this.worker.processVAD(audioChunkCopy, this.state, this.context);\r\n\r\n // Update local state\r\n this.state = result.state;\r\n this.context = audioChunkCopy.slice(-this.contextSize);\r\n\r\n const inferenceTimeMs = getClock().now() - startTime;\r\n const isSpeech = result.probability > this.config.threshold;\r\n\r\n // Pre-speech buffer logic (same as SileroVADInference)\r\n let preSpeechChunks: Float32Array[] | undefined;\r\n\r\n if (isSpeech && !this.wasSpeaking) {\r\n preSpeechChunks = [...this.preSpeechBuffer];\r\n this.preSpeechBuffer = [];\r\n } else if (!isSpeech && !this.wasSpeaking) {\r\n this.preSpeechBuffer.push(new Float32Array(audioChunkCopy));\r\n if (this.preSpeechBuffer.length > this.config.preSpeechBufferChunks) {\r\n this.preSpeechBuffer.shift();\r\n }\r\n } else if (!isSpeech && this.wasSpeaking) {\r\n this.preSpeechBuffer = [];\r\n }\r\n\r\n this.wasSpeaking = isSpeech;\r\n\r\n resolve({\r\n probability: result.probability,\r\n isSpeech,\r\n inferenceTimeMs,\r\n preSpeechChunks,\r\n });\r\n } catch (err) {\r\n reject(err);\r\n }\r\n });\r\n });\r\n }\r\n\r\n async reset(): Promise<void> {\r\n this.assertLoaded();\r\n\r\n const newState = await this.worker.resetVAD();\r\n this.state = newState;\r\n this.context = new Float32Array(this.contextSize);\r\n this.preSpeechBuffer = [];\r\n this.wasSpeaking = false;\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this._isLoaded) {\r\n await this.worker.disposeVAD();\r\n this._isLoaded = false;\r\n }\r\n this.state = new Float32Array(2 * 1 * 128);\r\n this.context = new Float32Array(this.contextSize);\r\n this.preSpeechBuffer = [];\r\n this.wasSpeaking = false;\r\n }\r\n\r\n private assertLoaded(): void {\r\n if (!this._isLoaded) throw new Error('Model not loaded. Call load() first.');\r\n if (this.worker.workerGeneration !== this.loadedGeneration) {\r\n this._isLoaded = false;\r\n throw new Error('Worker was recovered — model session lost. Call load() again.');\r\n }\r\n }\r\n}\r\n","/**\n * Factory function for A2E inference via UnifiedInferenceWorker\n *\n * Creates an A2EBackend instance with zero-config defaults (HuggingFace CDN).\n * Routes inference through the shared unified worker.\n *\n * @category Inference\n *\n * @example\n * ```typescript\n * import { createA2E, UnifiedInferenceWorker } from '@omote/core';\n *\n * const worker = new UnifiedInferenceWorker();\n * await worker.init();\n *\n * const a2e = createA2E({ unifiedWorker: worker });\n * await a2e.load();\n * const { blendshapes } = await a2e.infer(audioSamples);\n * ```\n */\n\nimport { createLogger } from '../logging';\nimport { DEFAULT_MODEL_URLS } from './defaultModelUrls';\nimport { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\nimport { acquireSharedWorker, releaseSharedWorker } from './sharedLazyWorker';\nimport type { A2EBackend, A2EModelInfo, A2EResult } from './A2EBackend';\nimport type { RuntimeBackend } from '../utils/runtime';\nimport type { InferenceFactoryConfig } from './factoryTypes';\nimport { A2EUnifiedAdapter } from './adapters';\n\nconst logger = createLogger('createA2E');\n\n/**\n * Configuration for the A2E factory\n */\nexport interface CreateA2EConfig extends InferenceFactoryConfig {\n /** URL for the ONNX model. Default: HuggingFace CDN */\n modelUrl?: string;\n /**\n * URL for external model data file (.onnx.data weights).\n * Default: `${modelUrl}.data`\n *\n * Set to `false` to skip external data loading (single-file models only).\n */\n externalDataUrl?: string | false;\n /** Number of identity classes (default: 12) */\n numIdentityClasses?: number;\n}\n\n/**\n * Thin wrapper that lazily creates a UnifiedInferenceWorker on first load().\n */\nclass LazyA2E implements A2EBackend {\n private inner: A2EUnifiedAdapter | null = null;\n private ownedWorker: UnifiedInferenceWorker | null = null;\n private usesSharedWorker = false;\n private readonly config: CreateA2EConfig;\n\n constructor(config: CreateA2EConfig) {\n this.config = config;\n }\n\n get modelId(): 'a2e' { return 'a2e'; }\n get isLoaded(): boolean { return this.inner?.isLoaded ?? false; }\n get backend(): RuntimeBackend | null { return this.inner?.backend ?? null; }\n get chunkSize(): number { return this.inner?.chunkSize ?? 16000; }\n\n async load(): Promise<A2EModelInfo> {\n if (this.inner?.isLoaded) return { backend: 'wasm', loadTimeMs: 0, inputNames: [], outputNames: [] };\n\n const worker = await acquireSharedWorker();\n this.usesSharedWorker = true;\n\n const modelUrl = this.config.modelUrl ?? DEFAULT_MODEL_URLS.lam;\n this.inner = new A2EUnifiedAdapter(worker, {\n modelUrl,\n externalDataUrl: this.config.externalDataUrl,\n numIdentityClasses: this.config.numIdentityClasses,\n });\n return this.inner.load();\n }\n\n async infer(audioSamples: Float32Array, identityIndex?: number): Promise<A2EResult> {\n if (!this.inner) throw new Error('Model not loaded. Call load() first.');\n return this.inner.infer(audioSamples, identityIndex);\n }\n\n async dispose(): Promise<void> {\n if (this.inner) {\n await this.inner.dispose();\n this.inner = null;\n }\n if (this.usesSharedWorker) {\n await releaseSharedWorker();\n this.usesSharedWorker = false;\n } else if (this.ownedWorker) {\n await this.ownedWorker.dispose();\n this.ownedWorker = null;\n }\n }\n}\n\n/**\n * Create an A2E instance via the unified worker.\n *\n * If no `unifiedWorker` is provided, a dedicated worker is created on load().\n *\n * @param config - Factory configuration\n * @returns An A2EBackend instance\n */\nexport function createA2E(config: CreateA2EConfig = {}): A2EBackend {\n const modelUrl = config.modelUrl ?? DEFAULT_MODEL_URLS.lam;\n\n if (config.unifiedWorker) {\n logger.info('Creating A2EUnifiedAdapter (via unified worker)', { modelUrl });\n return new A2EUnifiedAdapter(config.unifiedWorker, {\n modelUrl,\n externalDataUrl: config.externalDataUrl,\n numIdentityClasses: config.numIdentityClasses,\n });\n }\n\n logger.info('Creating A2EUnifiedAdapter (dedicated worker, lazy init)');\n return new LazyA2E(config);\n}\n","/**\r\n * TTSSpeaker — Shared helper for OmoteAvatar TTS integration.\r\n *\r\n * Encapsulates createA2E + TTSPlayback lifecycle so that renderer adapters\r\n * (Three.js, Babylon.js) and the R3F hook can delegate with ~15 lines each.\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { TTSPlayback } from './TTSPlayback';\r\nimport { AudioScheduler } from './AudioScheduler';\r\nimport { ttsToPlaybackFormat, resampleLinear } from './audioConvert';\r\nimport { createA2E } from '../inference/createA2E';\r\nimport { UnifiedInferenceWorker } from '../inference/UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from '../inference/sharedLazyWorker';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\nimport type { TTSBackend } from '../inference/TTSBackend';\r\nimport type { A2EBackend } from '../inference/A2EBackend';\r\nimport type { ExpressionProfile } from './expressionProfile';\r\nimport type { CreateA2EConfig } from '../inference/createA2E';\r\nimport type { FrameSource } from '../orchestration/types';\r\n\r\nconst logger = createLogger('TTSSpeaker');\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface TTSSpeakerConfig {\r\n /** Skip LAM download — audio playback only, no lip sync. Default: false. */\r\n audioOnly?: boolean;\r\n /** Per-character expression weight scaling */\r\n profile?: ExpressionProfile;\r\n /** Identity/style index for A2E model (default: 0) */\r\n identityIndex?: number;\r\n /** Audio playback delay in ms */\r\n audioDelayMs?: number;\r\n /** Enable neutral transition on playback complete */\r\n neutralTransitionEnabled?: boolean;\r\n /** Duration of neutral fade-out in ms */\r\n neutralTransitionMs?: number;\r\n /** Pre-built A2E backend (skip internal createA2E). */\r\n lam?: A2EBackend;\r\n /** LAM model config (only when lam not provided). unifiedWorker is supplied by TTSSpeaker. */\r\n models?: Omit<CreateA2EConfig, 'unifiedWorker'>;\r\n /** Shared unified worker (recommended for iOS) */\r\n unifiedWorker?: UnifiedInferenceWorker;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// TTSSpeaker\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Maximum text buffer length before force-flushing (prevents unbounded memory growth). */\r\nconst MAX_BUFFER_LENGTH = 50_000;\r\n/** Minimum character length before a sentence boundary triggers TTS. */\r\nconst MIN_SENTENCE_LENGTH = 20;\r\n/** Regex to detect sentence boundaries: period, exclamation, question mark, or newline. */\r\nconst SENTENCE_BOUNDARY_RE = /[.!?\\n]\\s*/;\r\n\r\nexport class TTSSpeaker {\r\n private ttsPlayback: TTSPlayback | null = null;\r\n private tts: TTSBackend | null = null;\r\n private ownedLam: A2EBackend | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n private currentAbort: AbortController | null = null;\r\n private _isSpeaking = false;\r\n\r\n // Audio-only mode (no LAM, no blendshapes)\r\n private _audioOnly = false;\r\n private scheduler: AudioScheduler | null = null;\r\n\r\n /** Whether the speaker is currently playing audio. */\r\n get isSpeaking(): boolean {\r\n return this._isSpeaking;\r\n }\r\n\r\n /** Whether this speaker is in audio-only mode (no lip sync). */\r\n get audioOnly(): boolean {\r\n return this._audioOnly;\r\n }\r\n\r\n /** The internal TTSPlayback (implements FrameSource). Null until connect() or in audio-only mode. */\r\n get frameSource(): FrameSource | null {\r\n return this.ttsPlayback?.pipeline ?? null;\r\n }\r\n\r\n /**\r\n * Connect a TTS backend.\r\n *\r\n * By default, the full lip sync pipeline is created (auto-downloads LAM).\r\n * Pass `audioOnly: true` for audio-only mode (no blendshapes, no LAM download).\r\n *\r\n * @param tts - TTS backend to use for speech synthesis\r\n * @param config - Optional configuration for A2E, expression profile, etc.\r\n */\r\n async connect(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\r\n logger.info('Connecting TTS...');\r\n const span = getTelemetry()?.startSpan('TTSSpeaker.connect');\r\n const connectStart = getClock().now();\r\n\r\n // Store TTS backend reference\r\n this.tts = tts;\r\n\r\n // Load TTS if not already loaded\r\n if (!tts.isLoaded) {\r\n await tts.load();\r\n }\r\n\r\n // Audio-only mode: explicit opt-in only\r\n if (config?.audioOnly) {\r\n // Audio-only path: no LAM, no blendshapes, just AudioScheduler\r\n this._audioOnly = true;\r\n this.scheduler = new AudioScheduler({ sampleRate: tts.sampleRate });\r\n getTelemetry()?.recordHistogram(MetricNames.TTS_CONNECT_LATENCY, getClock().now() - connectStart);\r\n span?.end();\r\n logger.info('TTS connected (audio-only mode)');\r\n return;\r\n }\r\n\r\n // Full lip sync path\r\n let lam: A2EBackend;\r\n if (config?.lam) {\r\n lam = config.lam;\r\n } else {\r\n let worker = config?.unifiedWorker;\r\n if (!worker) {\r\n worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n }\r\n\r\n lam = createA2E({\r\n ...config?.models,\r\n unifiedWorker: worker,\r\n });\r\n this.ownedLam = lam;\r\n }\r\n\r\n if (!lam.isLoaded) {\r\n await lam.load();\r\n }\r\n\r\n this.ttsPlayback = new TTSPlayback({\r\n tts,\r\n lam,\r\n profile: config?.profile,\r\n identityIndex: config?.identityIndex,\r\n audioDelayMs: config?.audioDelayMs,\r\n neutralTransitionEnabled: config?.neutralTransitionEnabled,\r\n neutralTransitionMs: config?.neutralTransitionMs,\r\n });\r\n await this.ttsPlayback.initialize();\r\n\r\n getTelemetry()?.recordHistogram(MetricNames.TTS_CONNECT_LATENCY, getClock().now() - connectStart);\r\n span?.end();\r\n logger.info('TTS connected (lip sync mode)');\r\n }\r\n\r\n /**\r\n * Synthesize and play text with lip sync.\r\n * Auto-aborts previous speak if still in progress.\r\n *\r\n * @param text - Text to synthesize and play\r\n * @param options - Optional voice override and abort signal\r\n */\r\n async speak(text: string, options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string }): Promise<void> {\r\n if (!this._audioOnly && !this.ttsPlayback) {\r\n throw new Error('No TTS connected. Call connect() first.');\r\n }\r\n if (this._audioOnly && !this.tts) {\r\n throw new Error('No TTS connected. Call connect() first.');\r\n }\r\n\r\n // Auto-abort previous speak\r\n if (this.currentAbort) {\r\n this.currentAbort.abort();\r\n this.currentAbort = null;\r\n }\r\n\r\n const abort = new AbortController();\r\n this.currentAbort = abort;\r\n\r\n // If caller provided a signal, forward abort\r\n if (options?.signal) {\r\n if (options.signal.aborted) {\r\n abort.abort();\r\n } else {\r\n options.signal.addEventListener('abort', () => abort.abort(), { once: true });\r\n }\r\n }\r\n\r\n this._isSpeaking = true;\r\n const span = getTelemetry()?.startSpan('TTSSpeaker.speak', {\r\n 'text.length': text.length,\r\n });\r\n const speakStart = getClock().now();\r\n try {\r\n if (this._audioOnly) {\r\n await this.speakAudioOnly(text, abort, options?.voice, options?.speed, options?.language);\r\n } else {\r\n await this.ttsPlayback!.speak(text, {\r\n signal: abort.signal,\r\n voice: options?.voice,\r\n speed: options?.speed,\r\n language: options?.language,\r\n });\r\n }\r\n getTelemetry()?.recordHistogram(MetricNames.TTS_SPEAK_LATENCY, getClock().now() - speakStart);\r\n span?.end();\r\n } catch (err) {\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n throw err;\r\n } finally {\r\n this._isSpeaking = false;\r\n if (this.currentAbort === abort) {\r\n this.currentAbort = null;\r\n }\r\n }\r\n }\r\n\r\n /** Audio-only speak: TTS → resample → AudioScheduler (no blendshapes). */\r\n private async speakAudioOnly(text: string, abort: AbortController, voice?: string, speed?: number, language?: string): Promise<void> {\r\n if (!this.tts || !this.scheduler) return;\r\n\r\n for await (const chunk of this.tts.stream(text, { signal: abort.signal, voice, speed, language, singleShot: true })) {\r\n if (abort.signal.aborted) return;\r\n // Resample from TTS rate to scheduler rate (usually same, but just in case)\r\n const samples = resampleLinear(chunk.audio, this.tts.sampleRate, this.scheduler.sampleRate);\r\n await this.scheduler.schedule(samples);\r\n }\r\n\r\n // Wait for playback to complete\r\n if (!abort.signal.aborted) {\r\n await this.waitForSchedulerComplete(abort.signal);\r\n }\r\n }\r\n\r\n /** Poll scheduler until all audio has played. */\r\n private waitForSchedulerComplete(signal: AbortSignal): Promise<void> {\r\n return new Promise(resolve => {\r\n const check = () => {\r\n if (signal.aborted || !this.scheduler) { resolve(); return; }\r\n if (this.scheduler.isComplete()) { resolve(); return; }\r\n setTimeout(check, 50);\r\n };\r\n check();\r\n });\r\n }\r\n\r\n /**\r\n * Stream text token-by-token with automatic sentence buffering.\r\n * Designed for LLM token-by-token output. Sentences are detected at\r\n * boundary characters (.!?\\n) with a minimum length threshold, then\r\n * synthesized and played with lip sync.\r\n *\r\n * Auto-aborts previous speak/streamText if still in progress.\r\n *\r\n * @param options - Optional voice override and abort signal\r\n * @returns Sink with push() and end() methods\r\n */\r\n async streamText(options: {\r\n signal?: AbortSignal;\r\n voice?: string;\r\n speed?: number;\r\n language?: string;\r\n }): Promise<{\r\n push: (token: string) => void;\r\n end: () => Promise<void>;\r\n }> {\r\n if (!this._audioOnly && (!this.ttsPlayback || !this.tts)) {\r\n throw new Error('No TTS connected. Call connect() first.');\r\n }\r\n if (this._audioOnly && !this.tts) {\r\n throw new Error('No TTS connected. Call connect() first.');\r\n }\r\n\r\n // Auto-abort previous speak/streamText\r\n if (this.currentAbort) {\r\n this.currentAbort.abort();\r\n this.currentAbort = null;\r\n }\r\n\r\n const abort = new AbortController();\r\n this.currentAbort = abort;\r\n\r\n // Forward external signal\r\n if (options?.signal) {\r\n if (options.signal.aborted) {\r\n abort.abort();\r\n } else {\r\n options.signal.addEventListener('abort', () => abort.abort(), { once: true });\r\n }\r\n }\r\n\r\n const tts = this.tts!;\r\n const voice = options?.voice;\r\n const speed = options?.speed;\r\n const language = options?.language;\r\n\r\n // Audio-only mode: route to scheduler directly\r\n if (this._audioOnly) {\r\n return this.streamTextAudioOnly(abort, tts, voice, speed, language);\r\n }\r\n\r\n const pipeline = this.ttsPlayback!.pipeline!;\r\n\r\n pipeline.start();\r\n\r\n let buffer = '';\r\n let ended = false;\r\n // Sequential promise chain for sentence processing\r\n let processChain = Promise.resolve();\r\n\r\n const enqueueSentence = (sentence: string) => {\r\n processChain = processChain.then(async () => {\r\n if (abort.signal.aborted) return;\r\n try {\r\n for await (const chunk of tts.stream(sentence, { signal: abort.signal, voice, speed, language })) {\r\n if (abort.signal.aborted) return;\r\n const pcm16 = ttsToPlaybackFormat(chunk.audio, tts.sampleRate, 16000);\r\n await pipeline.onAudioChunk(pcm16);\r\n }\r\n } catch (err) {\r\n if (!abort.signal.aborted) {\r\n logger.error('streamText sentence error', { message: (err as Error).message });\r\n }\r\n }\r\n });\r\n };\r\n\r\n const checkBuffer = () => {\r\n while (true) {\r\n const match = SENTENCE_BOUNDARY_RE.exec(buffer);\r\n if (!match || match.index === undefined) break;\r\n\r\n const cutPoint = match.index + match[0].length;\r\n const candidate = buffer.substring(0, cutPoint);\r\n\r\n if (candidate.trim().length < MIN_SENTENCE_LENGTH) break;\r\n\r\n buffer = buffer.substring(cutPoint);\r\n enqueueSentence(candidate.trim());\r\n }\r\n };\r\n\r\n return {\r\n push: (token: string) => {\r\n if (ended) throw new Error('Cannot push after end()');\r\n if (abort.signal.aborted) return; // no-op after abort\r\n if (!token) return; // empty string guard\r\n\r\n buffer += token;\r\n\r\n if (buffer.length >= MAX_BUFFER_LENGTH) {\r\n logger.warn('Text buffer exceeded max length, force-flushing');\r\n if (buffer.trim()) { enqueueSentence(buffer.trim()); buffer = ''; }\r\n }\r\n\r\n if (!this._isSpeaking) {\r\n this._isSpeaking = true;\r\n }\r\n\r\n checkBuffer();\r\n },\r\n\r\n end: async () => {\r\n if (ended) return;\r\n ended = true;\r\n\r\n const unsubs: Array<() => void> = [];\r\n try {\r\n if (abort.signal.aborted) {\r\n return;\r\n }\r\n\r\n // Flush remaining buffer\r\n if (buffer.trim()) {\r\n enqueueSentence(buffer.trim());\r\n buffer = '';\r\n }\r\n\r\n // Wait for all sentences to process\r\n await processChain;\r\n\r\n if (abort.signal.aborted) {\r\n return;\r\n }\r\n\r\n await pipeline.end();\r\n\r\n // Wait for playback to complete or stop, with abort support\r\n await new Promise<void>((resolve) => {\r\n let resolved = false;\r\n const done = () => {\r\n if (resolved) return;\r\n resolved = true;\r\n resolve();\r\n };\r\n if (abort.signal.aborted) { resolve(); return; }\r\n unsubs.push(pipeline.on('playback:complete', done));\r\n unsubs.push(pipeline.on('playback:stop', done));\r\n const onAbort = () => done();\r\n abort.signal.addEventListener('abort', onAbort);\r\n unsubs.push(() => abort.signal.removeEventListener('abort', onAbort));\r\n });\r\n } finally {\r\n unsubs.forEach(fn => fn());\r\n this._isSpeaking = false;\r\n if (this.currentAbort === abort) this.currentAbort = null;\r\n }\r\n },\r\n };\r\n }\r\n\r\n /** streamText in audio-only mode: TTS → AudioScheduler (no blendshapes). */\r\n private streamTextAudioOnly(\r\n abort: AbortController,\r\n tts: TTSBackend,\r\n voice?: string,\r\n speed?: number,\r\n language?: string,\r\n ): { push: (token: string) => void; end: () => Promise<void> } {\r\n let buffer = '';\r\n let ended = false;\r\n let processChain = Promise.resolve();\r\n\r\n const enqueueSentence = (sentence: string) => {\r\n processChain = processChain.then(async () => {\r\n if (abort.signal.aborted) return;\r\n try {\r\n for await (const chunk of tts.stream(sentence, { signal: abort.signal, voice, speed, language })) {\r\n if (abort.signal.aborted) return;\r\n const samples = resampleLinear(chunk.audio, tts.sampleRate, this.scheduler!.sampleRate);\r\n await this.scheduler!.schedule(samples);\r\n }\r\n } catch (err) {\r\n if (!abort.signal.aborted) {\r\n logger.error('streamText sentence error', { message: (err as Error).message });\r\n }\r\n }\r\n });\r\n };\r\n\r\n const checkBuffer = () => {\r\n while (true) {\r\n const match = SENTENCE_BOUNDARY_RE.exec(buffer);\r\n if (!match || match.index === undefined) break;\r\n const cutPoint = match.index + match[0].length;\r\n const candidate = buffer.substring(0, cutPoint);\r\n if (candidate.trim().length < MIN_SENTENCE_LENGTH) break;\r\n buffer = buffer.substring(cutPoint);\r\n enqueueSentence(candidate.trim());\r\n }\r\n };\r\n\r\n return {\r\n push: (token: string) => {\r\n if (ended) throw new Error('Cannot push after end()');\r\n if (abort.signal.aborted) return;\r\n if (!token) return;\r\n buffer += token;\r\n if (buffer.length >= MAX_BUFFER_LENGTH) {\r\n logger.warn('Text buffer exceeded max length, force-flushing');\r\n if (buffer.trim()) { enqueueSentence(buffer.trim()); buffer = ''; }\r\n }\r\n if (!this._isSpeaking) this._isSpeaking = true;\r\n checkBuffer();\r\n },\r\n end: async () => {\r\n if (ended) return;\r\n ended = true;\r\n if (abort.signal.aborted) {\r\n this._isSpeaking = false;\r\n if (this.currentAbort === abort) this.currentAbort = null;\r\n return;\r\n }\r\n if (buffer.trim()) {\r\n enqueueSentence(buffer.trim());\r\n buffer = '';\r\n }\r\n await processChain;\r\n if (!abort.signal.aborted) {\r\n await this.waitForSchedulerComplete(abort.signal);\r\n }\r\n this._isSpeaking = false;\r\n if (this.currentAbort === abort) this.currentAbort = null;\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Warm up AudioContext for iOS/Safari autoplay policy.\r\n * Call from a user gesture handler (click/tap) before speak().\r\n */\r\n async warmup(): Promise<void> {\r\n if (this.scheduler) await this.scheduler.warmup();\r\n if (this.ttsPlayback) await this.ttsPlayback.warmup();\r\n }\r\n\r\n /** Abort current speak if any. Triggers neutral transition on PlaybackPipeline. */\r\n stop(): void {\r\n if (this.currentAbort) {\r\n this.currentAbort.abort();\r\n this.currentAbort = null;\r\n getTelemetry()?.incrementCounter(MetricNames.TTS_SPEAK_ABORTED);\r\n }\r\n // Stop PlaybackPipeline to trigger neutral fade-out (mouth close)\r\n this.ttsPlayback?.pipeline?.stop();\r\n this.scheduler?.cancelAll();\r\n this._isSpeaking = false;\r\n }\r\n\r\n /** Clean teardown of all owned resources. */\r\n async dispose(): Promise<void> {\r\n logger.debug('Disposing TTSSpeaker');\r\n this.stop();\r\n if (this.ttsPlayback) {\r\n await this.ttsPlayback.dispose();\r\n this.ttsPlayback = null;\r\n }\r\n if (this.scheduler) {\r\n this.scheduler.dispose();\r\n this.scheduler = null;\r\n }\r\n this.tts = null;\r\n this._audioOnly = false;\r\n if (this.ownedLam) {\r\n await this.ownedLam.dispose();\r\n this.ownedLam = null;\r\n }\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n } else if (this.ownedWorker) {\r\n await this.ownedWorker.dispose();\r\n this.ownedWorker = null;\r\n }\r\n }\r\n}\r\n","/**\r\n * Factory function for Kokoro TTS via UnifiedInferenceWorker\r\n *\r\n * When called without a `unifiedWorker`, a dedicated worker is created\r\n * automatically on the first `load()` call. Pass a shared worker when using\r\n * VoiceOrchestrator or multiple models to avoid extra WASM instances.\r\n *\r\n * @category Inference\r\n *\r\n * @example Standalone (auto-creates worker)\r\n * ```typescript\r\n * import { createKokoroTTS } from '@omote/core';\r\n *\r\n * const tts = createKokoroTTS({ defaultVoice: 'af_heart' });\r\n * await tts.load();\r\n *\r\n * for await (const chunk of tts.stream(\"Hello world!\")) {\r\n * playbackPipeline.feedBuffer(chunk.audio);\r\n * }\r\n * ```\r\n *\r\n * @example With shared worker\r\n * ```typescript\r\n * const tts = createKokoroTTS({ defaultVoice: 'af_heart', unifiedWorker: worker });\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { KokoroTTSUnifiedAdapter } from './adapters';\r\nimport { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from './sharedLazyWorker';\r\nimport type { InferenceFactoryConfig } from './factoryTypes';\r\nimport type { TTSBackend, TTSStreamOptions, TTSChunk } from './TTSBackend';\r\nimport type { KokoroTTSConfig, KokoroTTSModelInfo } from './KokoroTTSTypes';\r\n\r\nconst logger = createLogger('createKokoroTTS');\r\n\r\n/**\r\n * Configuration for the Kokoro TTS factory\r\n */\r\nexport interface CreateKokoroTTSConfig extends KokoroTTSConfig, InferenceFactoryConfig {\r\n}\r\n\r\n/**\r\n * Thin wrapper that lazily creates a UnifiedInferenceWorker on first load().\r\n * Returned when no unifiedWorker is provided.\r\n */\r\nclass LazyKokoroTTS implements TTSBackend {\r\n private inner: KokoroTTSUnifiedAdapter | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n private readonly config: KokoroTTSConfig;\r\n\r\n constructor(config: KokoroTTSConfig) {\r\n this.config = config;\r\n }\r\n\r\n get isLoaded(): boolean { return this.inner?.isLoaded ?? false; }\r\n get sampleRate(): number { return 24000; }\r\n\r\n async load(): Promise<KokoroTTSModelInfo> {\r\n if (this.inner?.isLoaded) return { backend: 'wasm', loadTimeMs: 0, defaultVoice: this.config.defaultVoice ?? 'af_heart' };\r\n\r\n const worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n\r\n this.inner = new KokoroTTSUnifiedAdapter(worker, this.config);\r\n return this.inner.load();\r\n }\r\n\r\n async *stream(text: string, options?: TTSStreamOptions): AsyncGenerator<TTSChunk> {\r\n if (!this.inner) throw new Error('Model not loaded. Call load() first.');\r\n yield* this.inner.stream(text, options);\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this.inner) {\r\n await this.inner.dispose();\r\n this.inner = null;\r\n }\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n } else if (this.ownedWorker) {\r\n await this.ownedWorker.dispose();\r\n this.ownedWorker = null;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Create a Kokoro TTS instance via the unified worker.\r\n *\r\n * If no `unifiedWorker` is provided, a dedicated worker is created on load().\r\n *\r\n * @param config - Factory configuration\r\n * @returns A TTSBackend instance\r\n */\r\nexport function createKokoroTTS(config: CreateKokoroTTSConfig = {}): TTSBackend {\r\n if (config.unifiedWorker) {\r\n logger.info('Creating KokoroTTSUnifiedAdapter (unified worker)');\r\n return new KokoroTTSUnifiedAdapter(config.unifiedWorker, config);\r\n }\r\n\r\n logger.info('Creating KokoroTTSUnifiedAdapter (dedicated worker, lazy init)');\r\n return new LazyKokoroTTS(config);\r\n}\r\n","/**\n * createTTSPlayer — Zero-config TTS player for audio-only playback.\n *\n * Speaks text through speakers without an avatar. No LAM download, no lip sync.\n *\n * @example\n * ```typescript\n * import { createTTSPlayer } from '@omote/core';\n *\n * const player = createTTSPlayer();\n * await player.load();\n * await player.speak(\"Hello world!\");\n *\n * // Streaming:\n * const stream = await player.streamText({});\n * stream.push(\"Hello \");\n * stream.push(\"world!\");\n * await stream.end();\n * ```\n *\n * @category Audio\n */\n\nimport { TTSSpeaker } from './TTSSpeaker';\nimport { createKokoroTTS } from '../inference/createKokoroTTS';\nimport { UnifiedInferenceWorker } from '../inference/UnifiedInferenceWorker';\nimport { acquireSharedWorker, releaseSharedWorker } from '../inference/sharedLazyWorker';\nimport { createLogger } from '../logging';\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\nimport type { TTSBackend } from '../inference/TTSBackend';\n\nconst logger = createLogger('TTSPlayer');\n\nexport interface CreateTTSPlayerConfig {\n /** Voice to use (default: 'af_heart') */\n voice?: string;\n /** Model URL override */\n modelUrl?: string;\n /** Voice data base URL override */\n voiceBaseUrl?: string;\n /** Shared unified worker (created automatically if not provided) */\n unifiedWorker?: UnifiedInferenceWorker;\n}\n\n/**\n * Zero-config TTS player. Speak text through speakers without an avatar.\n *\n * Uses Kokoro TTS (82M q8, ~92MB) with automatic worker creation.\n * No LAM model is downloaded — audio plays directly through AudioScheduler.\n */\nexport function createTTSPlayer(config?: CreateTTSPlayerConfig): TTSPlayer {\n return new TTSPlayer(config);\n}\n\n/**\n * Thin wrapper: TTSSpeaker in audio-only mode + delegated load().\n */\nexport class TTSPlayer extends TTSSpeaker {\n private backend: TTSBackend | null = null;\n private ttsWorker: UnifiedInferenceWorker | null = null;\n private ttsPlayerUsesSharedWorker = false;\n private ttsConfig: CreateTTSPlayerConfig;\n\n constructor(config?: CreateTTSPlayerConfig) {\n super();\n this.ttsConfig = config ?? {};\n }\n\n /** Load TTS model and connect in audio-only mode. */\n async load(): Promise<void> {\n const span = getTelemetry()?.startSpan('TTSPlayer.load');\n try {\n let worker = this.ttsConfig.unifiedWorker;\n if (!worker) {\n worker = await acquireSharedWorker();\n this.ttsPlayerUsesSharedWorker = true;\n }\n\n this.backend = createKokoroTTS({\n defaultVoice: this.ttsConfig.voice,\n modelUrl: this.ttsConfig.modelUrl,\n voiceBaseUrl: this.ttsConfig.voiceBaseUrl,\n unifiedWorker: worker,\n });\n\n await this.backend.load();\n await this.connect(this.backend, { audioOnly: true });\n logger.info('TTSPlayer loaded');\n span?.end();\n } catch (err) {\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\n throw err;\n }\n }\n\n /** Whether the TTS model is loaded and ready. */\n get isLoaded(): boolean {\n return this.backend?.isLoaded ?? false;\n }\n\n async dispose(): Promise<void> {\n await super.dispose();\n if (this.ttsPlayerUsesSharedWorker) {\n await releaseSharedWorker();\n this.ttsPlayerUsesSharedWorker = false;\n } else if (this.ttsWorker) {\n await this.ttsWorker.dispose();\n this.ttsWorker = null;\n }\n }\n}\n","/**\r\n * Factory function for SenseVoice ASR via UnifiedInferenceWorker\r\n *\r\n * @category Inference\r\n *\r\n * @example\r\n * ```typescript\r\n * import { createSenseVoice, UnifiedInferenceWorker } from '@omote/core';\r\n *\r\n * const worker = new UnifiedInferenceWorker();\r\n * await worker.init();\r\n *\r\n * const asr = createSenseVoice({\r\n * modelUrl: '/models/sensevoice/model.int8.onnx',\r\n * unifiedWorker: worker,\r\n * });\r\n * await asr.load();\r\n * const { text, emotion } = await asr.transcribe(audioSamples);\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { DEFAULT_MODEL_URLS } from './defaultModelUrls';\r\nimport { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from './sharedLazyWorker';\r\nimport type { InferenceFactoryConfig } from './factoryTypes';\r\nimport type { SenseVoiceBackend, SenseVoiceLanguage, SenseVoiceModelInfo, SenseVoiceResult } from './SenseVoiceTypes';\r\nimport { SenseVoiceUnifiedAdapter } from './adapters';\r\n\r\nconst logger = createLogger('createSenseVoice');\r\n\r\n// Re-export SenseVoiceBackend for consumers\r\nexport type { SenseVoiceBackend } from './SenseVoiceTypes';\r\n\r\n/**\r\n * Configuration for the SenseVoice factory\r\n */\r\nexport interface CreateSenseVoiceConfig extends InferenceFactoryConfig {\r\n /** Path or URL to model.int8.onnx (239MB). Default: HuggingFace CDN */\r\n modelUrl?: string;\r\n /** Path or URL to tokens.txt vocabulary file (default: sibling of modelUrl) */\r\n tokensUrl?: string;\r\n /** Language hint (default: 'auto') */\r\n language?: SenseVoiceLanguage;\r\n /** Text normalization (default: 'with_itn') */\r\n textNorm?: 'with_itn' | 'without_itn';\r\n}\r\n\r\n/**\r\n * Thin wrapper that lazily creates a UnifiedInferenceWorker on first load().\r\n */\r\nclass LazySenseVoice implements SenseVoiceBackend {\r\n private inner: SenseVoiceUnifiedAdapter | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n private readonly config: CreateSenseVoiceConfig;\r\n\r\n constructor(config: CreateSenseVoiceConfig) {\r\n this.config = config;\r\n }\r\n\r\n get isLoaded(): boolean { return this.inner?.isLoaded ?? false; }\r\n get backend(): 'wasm' | 'webgpu' | null { return this.inner?.backend ?? null; }\r\n\r\n async load(onProgress?: (loaded: number, total: number) => void): Promise<SenseVoiceModelInfo> {\r\n if (this.inner?.isLoaded) return { backend: 'wasm', loadTimeMs: 0 } as SenseVoiceModelInfo;\r\n\r\n const worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n\r\n const modelUrl = this.config.modelUrl ?? DEFAULT_MODEL_URLS.senseVoice;\r\n this.inner = new SenseVoiceUnifiedAdapter(worker, {\r\n modelUrl,\r\n tokensUrl: this.config.tokensUrl,\r\n language: this.config.language,\r\n textNorm: this.config.textNorm,\r\n });\r\n return this.inner.load(onProgress);\r\n }\r\n\r\n async transcribe(audioSamples: Float32Array): Promise<SenseVoiceResult> {\r\n if (!this.inner) throw new Error('Model not loaded. Call load() first.');\r\n return this.inner.transcribe(audioSamples);\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this.inner) {\r\n await this.inner.dispose();\r\n this.inner = null;\r\n }\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n } else if (this.ownedWorker) {\r\n await this.ownedWorker.dispose();\r\n this.ownedWorker = null;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Create a SenseVoice ASR instance via the unified worker.\r\n *\r\n * If no `unifiedWorker` is provided, a dedicated worker is created on load().\r\n *\r\n * @param config - Factory configuration\r\n * @returns A SenseVoiceBackend instance\r\n */\r\nexport function createSenseVoice(config: CreateSenseVoiceConfig = {}): SenseVoiceBackend {\r\n const modelUrl = config.modelUrl ?? DEFAULT_MODEL_URLS.senseVoice;\r\n\r\n if (config.unifiedWorker) {\r\n logger.info('Creating SenseVoiceUnifiedAdapter (shared unified worker)');\r\n return new SenseVoiceUnifiedAdapter(config.unifiedWorker, {\r\n modelUrl,\r\n tokensUrl: config.tokensUrl,\r\n language: config.language,\r\n textNorm: config.textNorm,\r\n }) as SenseVoiceBackend;\r\n }\r\n\r\n logger.info('Creating SenseVoiceUnifiedAdapter (dedicated worker, lazy init)');\r\n return new LazySenseVoice(config);\r\n}\r\n","/**\r\n * Factory function for Silero VAD via UnifiedInferenceWorker\r\n *\r\n * @category Inference\r\n *\r\n * @example\r\n * ```typescript\r\n * import { createSileroVAD, UnifiedInferenceWorker } from '@omote/core';\r\n *\r\n * const worker = new UnifiedInferenceWorker();\r\n * await worker.init();\r\n *\r\n * const vad = createSileroVAD({\r\n * modelUrl: '/models/silero-vad.onnx',\r\n * unifiedWorker: worker,\r\n * });\r\n * await vad.load();\r\n * const result = await vad.process(audioChunk);\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { DEFAULT_MODEL_URLS } from './defaultModelUrls';\r\nimport { UnifiedInferenceWorker } from './UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from './sharedLazyWorker';\r\nimport type { InferenceFactoryConfig } from './factoryTypes';\r\nimport type { SileroVADConfig, SileroVADBackend, VADModelInfo, VADWorkerModelInfo, VADResult } from './SileroVADTypes';\r\nimport type { RuntimeBackend } from '../utils/runtime';\r\nimport { SileroVADUnifiedAdapter } from './adapters';\r\n\r\nconst logger = createLogger('createSileroVAD');\r\n\r\n// Re-export SileroVADBackend for consumers\r\nexport type { SileroVADBackend } from './SileroVADTypes';\r\n\r\n/**\r\n * Configuration for the Silero VAD factory\r\n */\r\nexport interface SileroVADFactoryConfig extends Omit<SileroVADConfig, 'modelUrl'>, InferenceFactoryConfig {\r\n /** Path or URL to the ONNX model. Default: HuggingFace CDN */\r\n modelUrl?: string;\r\n}\r\n\r\n/**\r\n * Thin wrapper that lazily creates a UnifiedInferenceWorker on first load().\r\n */\r\nclass LazySileroVAD implements SileroVADBackend {\r\n private inner: SileroVADUnifiedAdapter | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n private readonly config: SileroVADFactoryConfig;\r\n\r\n constructor(config: SileroVADFactoryConfig) {\r\n this.config = config;\r\n }\r\n\r\n get isLoaded(): boolean { return this.inner?.isLoaded ?? false; }\r\n get backend(): RuntimeBackend | null { return this.inner?.backend ?? null; }\r\n get sampleRate(): number { return this.config.sampleRate ?? 16000; }\r\n get threshold(): number { return this.config.threshold ?? 0.5; }\r\n\r\n async load(): Promise<VADModelInfo | VADWorkerModelInfo> {\r\n if (this.inner?.isLoaded) return { backend: 'wasm', loadTimeMs: 0, inputNames: [], outputNames: [], sampleRate: this.sampleRate, chunkSize: this.sampleRate === 16000 ? 512 : 256 };\r\n\r\n const worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n\r\n const modelUrl = this.config.modelUrl ?? DEFAULT_MODEL_URLS.sileroVad;\r\n const resolvedConfig: SileroVADConfig = { ...this.config, modelUrl };\r\n this.inner = new SileroVADUnifiedAdapter(worker, resolvedConfig);\r\n return this.inner.load();\r\n }\r\n\r\n async process(audioChunk: Float32Array): Promise<VADResult> {\r\n if (!this.inner) throw new Error('Model not loaded. Call load() first.');\r\n return this.inner.process(audioChunk);\r\n }\r\n\r\n reset(): void {\r\n if (this.inner) this.inner.reset();\r\n }\r\n\r\n getChunkSize(): number {\r\n return (this.config.sampleRate ?? 16000) === 16000 ? 512 : 256;\r\n }\r\n\r\n getChunkDurationMs(): number {\r\n const sr = this.config.sampleRate ?? 16000;\r\n return (this.getChunkSize() / sr) * 1000;\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n if (this.inner) {\r\n await this.inner.dispose();\r\n this.inner = null;\r\n }\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n } else if (this.ownedWorker) {\r\n await this.ownedWorker.dispose();\r\n this.ownedWorker = null;\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Create a Silero VAD instance via the unified worker.\r\n *\r\n * If no `unifiedWorker` is provided, a dedicated worker is created on load().\r\n *\r\n * @param config - Factory configuration\r\n * @returns A SileroVADBackend instance\r\n */\r\nexport function createSileroVAD(config: SileroVADFactoryConfig = {}): SileroVADBackend {\r\n const modelUrl = config.modelUrl ?? DEFAULT_MODEL_URLS.sileroVad;\r\n const resolvedConfig: SileroVADConfig = { ...config, modelUrl };\r\n\r\n if (config.unifiedWorker) {\r\n logger.info('Creating SileroVADUnifiedAdapter (shared unified worker)');\r\n return new SileroVADUnifiedAdapter(config.unifiedWorker, resolvedConfig) as SileroVADBackend;\r\n }\r\n\r\n logger.info('Creating SileroVADUnifiedAdapter (dedicated worker, lazy init)');\r\n return new LazySileroVAD(config);\r\n}\r\n","/**\r\n * SpeechListener — Standalone listening primitive.\r\n *\r\n * Composes: MicrophoneCapture → SileroVAD → SenseVoice ASR → transcript events.\r\n * Used independently or alongside TTSSpeaker and VoiceOrchestrator.\r\n *\r\n * Does NOT handle TTS, LAM, or response routing — those belong to TTSSpeaker\r\n * and VoiceOrchestrator respectively.\r\n *\r\n * @category Audio\r\n */\r\n\r\nimport { EventEmitter } from '../events/EventEmitter';\r\nimport { MicrophoneCapture } from './MicrophoneCapture';\r\nimport { int16ToFloat32 } from './audioUtils';\r\nimport { createSenseVoice } from '../inference/createSenseVoice';\r\nimport { createSileroVAD } from '../inference/createSileroVAD';\r\nimport { UnifiedInferenceWorker } from '../inference/UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from '../inference/sharedLazyWorker';\r\nimport { isIOS } from '../utils/runtime';\r\nimport { calculateRMS } from '../animation/audioEnergy';\r\nimport { DEFAULT_MODEL_URLS } from '../inference/defaultModelUrls';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { ErrorCodes } from '../logging/ErrorCodes';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\nimport type { OmoteEvents } from '../events';\r\nimport type { SenseVoiceBackend, SenseVoiceLanguage } from '../inference/SenseVoiceTypes';\r\nimport type { SileroVADBackend } from '../inference/SileroVADTypes';\r\nimport type { LoadingProgress, TranscriptResult } from '../orchestration/types';\r\n\r\nconst logger = createLogger('SpeechListener');\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface SpeechListenerConfig {\r\n /** Pre-built backends — skip internal factory creation. */\r\n backends?: {\r\n asr: SenseVoiceBackend;\r\n vad: SileroVADBackend;\r\n };\r\n /** External unified worker (reuse across pipelines). */\r\n unifiedWorker?: UnifiedInferenceWorker;\r\n /** URLs and options for model loading (when backends not provided). */\r\n models?: {\r\n senseVoice: { modelUrl: string; tokensUrl?: string; language?: string; textNorm?: 'with_itn' | 'without_itn' };\r\n vad: { modelUrl: string; threshold?: number; preSpeechBufferChunks?: number };\r\n };\r\n\r\n /** Base silence timeout in ms (default: 500) */\r\n silenceTimeoutMs?: number;\r\n /** Extended silence timeout for long utterances (default: 700) */\r\n silenceTimeoutExtendedMs?: number;\r\n /** Enable adaptive timeout based on speech duration (default: true) */\r\n adaptiveTimeout?: boolean;\r\n /** Minimum audio duration in seconds (default: 0.3) */\r\n minAudioDurationSec?: number;\r\n /** Minimum audio energy (default: 0.02) */\r\n minAudioEnergy?: number;\r\n /** Enable audio normalization for quiet audio (default: true) */\r\n normalizeAudio?: boolean;\r\n\r\n /** Progressive transcription interval — desktop (default: 500ms) */\r\n progressiveIntervalMs?: number;\r\n /** Progressive transcription interval — iOS (default: 800ms) */\r\n progressiveIntervalIosMs?: number;\r\n /** Coverage threshold to use progressive result (default: 0.8) */\r\n progressiveCoverageThreshold?: number;\r\n /** Minimum samples before progressive transcription starts (default: 8000) */\r\n progressiveMinSamples?: number;\r\n /** Timeout for individual transcribe() calls (default: 10000ms) */\r\n transcriptionTimeoutMs?: number;\r\n}\r\n\r\nexport type SpeechListenerState = 'idle' | 'loading' | 'ready' | 'listening' | 'processing' | 'paused';\r\n\r\nexport interface SpeechListenerEvents {\r\n 'state': SpeechListenerState;\r\n 'loading:progress': LoadingProgress;\r\n 'transcript': TranscriptResult;\r\n 'speech:start': void;\r\n 'speech:end': { durationMs: number };\r\n 'audio:level': { rms: number; peak: number };\r\n 'audio:chunk': Float32Array;\r\n 'error': Error;\r\n [key: string]: unknown;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// SpeechListener\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class SpeechListener extends EventEmitter<SpeechListenerEvents> {\r\n private readonly config: SpeechListenerConfig;\r\n\r\n // State\r\n private _state: SpeechListenerState = 'idle';\r\n private epoch = 0;\r\n\r\n // Models\r\n private asr: SenseVoiceBackend | null = null;\r\n private vad: SileroVADBackend | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n\r\n // Mic\r\n private mic: MicrophoneCapture | null = null;\r\n private omoteEvents = new EventEmitter<OmoteEvents>();\r\n\r\n // Listener cleanup\r\n private _unsubChunk: (() => void) | null = null;\r\n private _unsubLevel: (() => void) | null = null;\r\n\r\n // Audio accumulation\r\n private static readonly MAX_AUDIO_BUFFER_SAMPLES = 16000 * 30; // 30 seconds\r\n private audioBuffer: Float32Array[] = [];\r\n private audioBufferSamples = 0;\r\n private speechStartTime = 0;\r\n private silenceTimer: ReturnType<typeof setTimeout> | null = null;\r\n private isSpeechActive = false;\r\n\r\n // Progressive transcription\r\n private progressiveTimer: ReturnType<typeof setInterval> | null = null;\r\n private progressivePromise: Promise<void> | null = null;\r\n private lastProgressiveResult: TranscriptResult | null = null;\r\n private lastProgressiveSamples = 0;\r\n\r\n // ASR error recovery\r\n private asrErrorCount = 0;\r\n private progressiveErrorCount = 0;\r\n\r\n /** Current listener state */\r\n get state(): SpeechListenerState { return this._state; }\r\n\r\n constructor(config?: SpeechListenerConfig) {\r\n super();\r\n this.config = config ?? {};\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Model loading\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Load ASR + VAD models. Only loads speech recognition models,\r\n * NOT TTS or LAM (those belong to TTSSpeaker).\r\n */\r\n async loadModels(): Promise<void> {\r\n this.setState('loading');\r\n const span = getTelemetry()?.startSpan('SpeechListener.loadModels', {\r\n 'model.name': 'speech-listener',\r\n });\r\n\r\n try {\r\n if (this.config.backends) {\r\n // Use pre-built backends\r\n const { asr, vad } = this.config.backends;\r\n if (!asr.isLoaded) await asr.load();\r\n if (!vad.isLoaded) await vad.load();\r\n this.asr = asr;\r\n this.vad = vad;\r\n } else if (this.config.models) {\r\n // Create from factories\r\n let worker = this.config.unifiedWorker;\r\n if (!worker && UnifiedInferenceWorker.isSupported()) {\r\n worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n }\r\n if (!worker) {\r\n throw new Error('UnifiedInferenceWorker is required but could not be created');\r\n }\r\n this.ownedWorker = worker;\r\n\r\n this.emitProgress('Loading ASR', 0, 2, 0);\r\n\r\n const asr = createSenseVoice({\r\n modelUrl: this.config.models.senseVoice.modelUrl,\r\n tokensUrl: this.config.models.senseVoice.tokensUrl,\r\n language: this.config.models.senseVoice.language as SenseVoiceLanguage,\r\n textNorm: this.config.models.senseVoice.textNorm,\r\n unifiedWorker: worker,\r\n });\r\n const vad = createSileroVAD({\r\n modelUrl: this.config.models.vad.modelUrl,\r\n threshold: this.config.models.vad.threshold,\r\n unifiedWorker: worker,\r\n });\r\n\r\n const [asrResult, vadResult] = await Promise.allSettled([\r\n asr.load(),\r\n vad.load(),\r\n ]);\r\n\r\n if (asrResult.status === 'rejected') throw asrResult.reason;\r\n if (vadResult.status === 'rejected') throw vadResult.reason;\r\n\r\n this.asr = asr;\r\n this.vad = vad;\r\n\r\n this.emitProgress('Models loaded', 100, 2, 2);\r\n } else {\r\n // Zero-config: use DEFAULT_MODEL_URLS (HuggingFace CDN or user-configured)\r\n const models = {\r\n senseVoice: { modelUrl: DEFAULT_MODEL_URLS.senseVoice },\r\n vad: { modelUrl: DEFAULT_MODEL_URLS.sileroVad },\r\n };\r\n\r\n let worker = this.config.unifiedWorker;\r\n if (!worker && UnifiedInferenceWorker.isSupported()) {\r\n worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n }\r\n if (!worker) {\r\n throw new Error('UnifiedInferenceWorker is required but could not be created');\r\n }\r\n this.ownedWorker = worker;\r\n\r\n this.emitProgress('Loading ASR', 0, 2, 0);\r\n\r\n const asr = createSenseVoice({\r\n modelUrl: models.senseVoice.modelUrl,\r\n unifiedWorker: worker,\r\n });\r\n const vad = createSileroVAD({\r\n modelUrl: models.vad.modelUrl,\r\n unifiedWorker: worker,\r\n });\r\n\r\n const [asrResult, vadResult] = await Promise.allSettled([\r\n asr.load(),\r\n vad.load(),\r\n ]);\r\n\r\n if (asrResult.status === 'rejected') throw asrResult.reason;\r\n if (vadResult.status === 'rejected') throw vadResult.reason;\r\n\r\n this.asr = asr;\r\n this.vad = vad;\r\n\r\n this.emitProgress('Models loaded', 100, 2, 2);\r\n }\r\n\r\n span?.end();\r\n this.setState('ready');\r\n logger.info('SpeechListener models loaded');\r\n } catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n span?.endWithError(err);\r\n logger.error('Model loading failed', { message: err.message });\r\n this.emit('error', err);\r\n this.setState('idle');\r\n throw err;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Lifecycle\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Start listening — activates mic + VAD. */\r\n async start(): Promise<void> {\r\n if (!this.asr || !this.vad) {\r\n throw new Error('Models not loaded. Call loadModels() first.');\r\n }\r\n if (this._state === 'listening') return; // already listening\r\n\r\n this.epoch++;\r\n this.asrErrorCount = 0;\r\n this.audioBuffer = [];\r\n this.audioBufferSamples = 0;\r\n\r\n this.mic = new MicrophoneCapture(this.omoteEvents, {\r\n sampleRate: 16000,\r\n chunkSize: 512,\r\n });\r\n\r\n this._unsubChunk = this.omoteEvents.on('audio.chunk', ({ pcm }) => {\r\n const float32 = int16ToFloat32(pcm);\r\n this.emit('audio:chunk', float32);\r\n this.processAudioChunk(float32);\r\n });\r\n\r\n this._unsubLevel = this.omoteEvents.on('audio.level', (level) => {\r\n this.emit('audio:level', level);\r\n });\r\n\r\n await this.mic.start();\r\n this.setState('listening');\r\n logger.info('Listening started');\r\n }\r\n\r\n /** Stop listening — deactivates mic, clears buffers. */\r\n stop(): void {\r\n this.epoch++;\r\n this.clearSilenceTimer();\r\n this.stopProgressiveTranscription();\r\n\r\n // Clean up event listeners to prevent leaks\r\n this._unsubChunk?.();\r\n this._unsubChunk = null;\r\n this._unsubLevel?.();\r\n this._unsubLevel = null;\r\n\r\n this.vad?.reset();\r\n this.mic?.stop();\r\n this.mic = null;\r\n\r\n this.isSpeechActive = false;\r\n this.audioBuffer = [];\r\n this.audioBufferSamples = 0;\r\n\r\n if (this._state !== 'idle') {\r\n this.setState('ready');\r\n }\r\n logger.info('Listening stopped');\r\n }\r\n\r\n /** Pause VAD/ASR but keep mic active for audio:chunk events (for interruption detection). */\r\n pause(): void {\r\n if (this._state !== 'listening' && this._state !== 'processing') return;\r\n this.epoch++;\r\n this.clearSilenceTimer();\r\n this.stopProgressiveTranscription();\r\n this.isSpeechActive = false;\r\n this.audioBuffer = [];\r\n this.audioBufferSamples = 0;\r\n this.setState('paused');\r\n }\r\n\r\n /** Resume VAD/ASR from paused state. */\r\n resume(): void {\r\n if (this._state !== 'paused') return;\r\n this.vad?.reset();\r\n this.setState('listening');\r\n }\r\n\r\n /** Dispose all resources. */\r\n async dispose(): Promise<void> {\r\n logger.debug('Disposing SpeechListener');\r\n this.stop();\r\n this.epoch++;\r\n\r\n await Promise.allSettled([\r\n this.asr?.dispose(),\r\n this.vad?.dispose(),\r\n ].filter(Boolean));\r\n\r\n this.asr = null;\r\n this.vad = null;\r\n\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n }\r\n this.ownedWorker = null;\r\n\r\n this._state = 'idle';\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Audio processing\r\n // ---------------------------------------------------------------------------\r\n\r\n private async processAudioChunk(samples: Float32Array): Promise<void> {\r\n if (!this.vad || this._state !== 'listening') return;\r\n\r\n try {\r\n const capturedEpoch = this.epoch;\r\n const result = await this.vad.process(samples);\r\n\r\n if (this.epoch !== capturedEpoch) return;\r\n if (this._state !== 'listening') return;\r\n\r\n const wasSpeaking = this.isSpeechActive;\r\n\r\n if (result.isSpeech) {\r\n if (!wasSpeaking) {\r\n this.isSpeechActive = true;\r\n this.speechStartTime = getClock().now();\r\n this.audioBuffer = [];\r\n this.audioBufferSamples = 0;\r\n this.lastProgressiveResult = null;\r\n this.lastProgressiveSamples = 0;\r\n logger.debug('Speech start');\r\n this.emit('speech:start');\r\n this.startProgressiveTranscription();\r\n }\r\n\r\n this.audioBuffer.push(new Float32Array(samples));\r\n this.audioBufferSamples += samples.length;\r\n\r\n // Force-flush if buffer exceeds max (prevents unbounded memory growth)\r\n if (this.audioBufferSamples >= SpeechListener.MAX_AUDIO_BUFFER_SAMPLES) {\r\n logger.warn('Audio buffer exceeded max, forcing transcription flush');\r\n this.onSilenceDetected();\r\n return;\r\n }\r\n\r\n this.clearSilenceTimer();\r\n } else if (wasSpeaking) {\r\n // Continue accumulating during silence (gap tolerance)\r\n this.audioBuffer.push(new Float32Array(samples));\r\n this.audioBufferSamples += samples.length;\r\n\r\n if (!this.silenceTimer) {\r\n const timeoutMs = this.getSilenceTimeout();\r\n this.silenceTimer = setTimeout(() => {\r\n this.onSilenceDetected();\r\n }, timeoutMs);\r\n }\r\n }\r\n } catch (err) {\r\n logger.warn('VAD error', { error: String(err) });\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Silence detection\r\n // ---------------------------------------------------------------------------\r\n\r\n private getSilenceTimeout(): number {\r\n const base = this.config.silenceTimeoutMs ?? 500;\r\n const extended = this.config.silenceTimeoutExtendedMs ?? 700;\r\n const adaptive = this.config.adaptiveTimeout ?? true;\r\n if (!adaptive) return base;\r\n const speechDurationMs = getClock().now() - this.speechStartTime;\r\n return speechDurationMs > 3000 ? extended : base;\r\n }\r\n\r\n private onSilenceDetected(): void {\r\n const capturedEpoch = this.epoch;\r\n this.isSpeechActive = false;\r\n const durationMs = getClock().now() - this.speechStartTime;\r\n logger.debug('Speech end', { durationMs: Math.round(durationMs) });\r\n this.emit('speech:end', { durationMs });\r\n this.clearSilenceTimer();\r\n\r\n this.processEndOfSpeech(capturedEpoch).catch(err => {\r\n logger.error('End of speech processing failed', { error: String(err) });\r\n if (this.epoch === capturedEpoch) {\r\n this.emit('error', err instanceof Error ? err : new Error(String(err)));\r\n this.setState('listening');\r\n }\r\n });\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // End of speech → transcription\r\n // ---------------------------------------------------------------------------\r\n\r\n private async processEndOfSpeech(capturedEpoch: number): Promise<void> {\r\n // Wait for in-flight progressive promise\r\n if (this.progressivePromise) {\r\n try { await this.progressivePromise; } catch { /* ignore */ }\r\n }\r\n this.stopProgressiveTranscription();\r\n\r\n if (this.epoch !== capturedEpoch) return;\r\n\r\n // Merge audio buffer\r\n const totalSamples = this.audioBufferSamples;\r\n const fullAudio = new Float32Array(totalSamples);\r\n let offset = 0;\r\n for (const chunk of this.audioBuffer) {\r\n fullAudio.set(chunk, offset);\r\n offset += chunk.length;\r\n }\r\n this.audioBuffer = [];\r\n this.audioBufferSamples = 0;\r\n\r\n // Audio validation\r\n const minDuration = this.config.minAudioDurationSec ?? 0.3;\r\n const minEnergy = this.config.minAudioEnergy ?? 0.02;\r\n const durationSec = totalSamples / 16000;\r\n\r\n if (durationSec < minDuration) {\r\n logger.info('Audio too short, discarding', { durationSec });\r\n this.setState('listening');\r\n return;\r\n }\r\n\r\n let rms = 0;\r\n for (let i = 0; i < fullAudio.length; i++) {\r\n rms += fullAudio[i] * fullAudio[i];\r\n }\r\n rms = Math.sqrt(rms / fullAudio.length);\r\n\r\n if (rms < minEnergy) {\r\n logger.info('Audio too quiet, discarding', { rms });\r\n this.setState('listening');\r\n return;\r\n }\r\n\r\n const normalizedAudio = this.normalizeAudio(fullAudio);\r\n\r\n // Transcribe\r\n this.setState('processing');\r\n let transcript: TranscriptResult | null = null;\r\n\r\n const coverageThreshold = this.config.progressiveCoverageThreshold ?? 0.8;\r\n if (\r\n this.lastProgressiveResult &&\r\n this.lastProgressiveResult.text.trim().length > 0 &&\r\n this.lastProgressiveSamples >= totalSamples * coverageThreshold\r\n ) {\r\n transcript = { ...this.lastProgressiveResult, isFinal: true };\r\n } else {\r\n this.lastProgressiveResult = null;\r\n transcript = await this.transcribeWithTimeout(normalizedAudio);\r\n if (transcript) transcript.isFinal = true;\r\n }\r\n\r\n if (this.epoch !== capturedEpoch) return;\r\n\r\n if (!transcript || !transcript.text.trim()) {\r\n logger.info('No transcript, resuming listening');\r\n this.setState('listening');\r\n return;\r\n }\r\n\r\n // Emit final transcript — SpeechListener stops here.\r\n // Response handling is the consumer's responsibility.\r\n this.emit('transcript', transcript);\r\n this.setState('listening');\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Progressive transcription\r\n // ---------------------------------------------------------------------------\r\n\r\n private startProgressiveTranscription(): void {\r\n this.stopProgressiveTranscription();\r\n\r\n const intervalMs = isIOS()\r\n ? (this.config.progressiveIntervalIosMs ?? 800)\r\n : (this.config.progressiveIntervalMs ?? 500);\r\n const minSamples = this.config.progressiveMinSamples ?? 8000;\r\n\r\n this.progressiveTimer = setInterval(() => {\r\n if (this.audioBufferSamples < minSamples || !this.asr) return;\r\n\r\n const capturedEpoch = this.epoch;\r\n const snapshot = new Float32Array(this.audioBufferSamples);\r\n let offset = 0;\r\n for (const chunk of this.audioBuffer) {\r\n snapshot.set(chunk, offset);\r\n offset += chunk.length;\r\n }\r\n const snapshotSamples = this.audioBufferSamples;\r\n\r\n this.progressivePromise = (async () => {\r\n try {\r\n const result = await this.transcribeWithTimeout(snapshot);\r\n if (this.epoch !== capturedEpoch) return;\r\n\r\n if (result && result.text.trim()) {\r\n this.lastProgressiveResult = result;\r\n this.lastProgressiveSamples = snapshotSamples;\r\n this.emit('transcript', { ...result, isFinal: false });\r\n }\r\n } catch (err) {\r\n this.progressiveErrorCount = (this.progressiveErrorCount ?? 0) + 1;\r\n if (this.progressiveErrorCount % 10 === 1) {\r\n logger.warn('Progressive transcription error', {\r\n code: ErrorCodes.SPH_ASR_ERROR,\r\n error: String(err),\r\n count: this.progressiveErrorCount,\r\n });\r\n }\r\n }\r\n })();\r\n }, intervalMs);\r\n }\r\n\r\n private stopProgressiveTranscription(): void {\r\n if (this.progressiveTimer) {\r\n clearInterval(this.progressiveTimer);\r\n this.progressiveTimer = null;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Transcription with timeout + ASR error recovery\r\n // ---------------------------------------------------------------------------\r\n\r\n private async transcribeWithTimeout(audio: Float32Array): Promise<TranscriptResult | null> {\r\n if (!this.asr) return null;\r\n\r\n const timeoutMs = this.config.transcriptionTimeoutMs ?? 10_000;\r\n const startTime = getClock().now();\r\n const span = getTelemetry()?.startSpan('SpeechListener.transcribe', {\r\n 'inference.input_samples': audio.length,\r\n 'inference.input_duration_ms': (audio.length / 16000) * 1000,\r\n });\r\n\r\n try {\r\n let timeoutId: ReturnType<typeof setTimeout>;\r\n const result = await Promise.race([\r\n this.asr.transcribe(audio),\r\n new Promise<never>((_, reject) => {\r\n timeoutId = setTimeout(() => reject(new Error(`Transcription timed out after ${timeoutMs}ms`)), timeoutMs);\r\n }),\r\n ]);\r\n clearTimeout(timeoutId!);\r\n\r\n const latency = getClock().now() - startTime;\r\n this.asrErrorCount = 0;\r\n getTelemetry()?.recordHistogram(MetricNames.VOICE_TRANSCRIPTION_LATENCY, latency);\r\n getTelemetry()?.incrementCounter(MetricNames.VOICE_TRANSCRIPTIONS);\r\n span?.setAttributes({ 'inference.duration_ms': latency, 'inference.success': true });\r\n span?.end();\r\n return {\r\n text: result.text,\r\n emotion: result.emotion,\r\n language: result.language,\r\n isFinal: false,\r\n inferenceTimeMs: latency,\r\n };\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n this.asrErrorCount++;\r\n logger.warn('Transcription failed', { attempt: this.asrErrorCount, error: String(error) });\r\n\r\n if (this.asrErrorCount >= 3 && this.config.models) {\r\n logger.warn('3 consecutive ASR errors, recreating session');\r\n try {\r\n await this.asr.dispose();\r\n this.asr = createSenseVoice({\r\n modelUrl: this.config.models.senseVoice.modelUrl,\r\n tokensUrl: this.config.models.senseVoice.tokensUrl,\r\n language: this.config.models.senseVoice.language as SenseVoiceLanguage,\r\n textNorm: this.config.models.senseVoice.textNorm,\r\n unifiedWorker: (this.config.unifiedWorker ?? this.ownedWorker)!,\r\n });\r\n await this.asr.load();\r\n this.asrErrorCount = 0;\r\n } catch (recreateErr) {\r\n logger.error('ASR session recreation failed', { error: String(recreateErr) });\r\n }\r\n }\r\n\r\n return null;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Audio normalization\r\n // ---------------------------------------------------------------------------\r\n\r\n private normalizeAudio(audio: Float32Array): Float32Array {\r\n if (!(this.config.normalizeAudio ?? true)) return audio;\r\n\r\n let maxAbs = 0;\r\n for (let i = 0; i < audio.length; i++) {\r\n const abs = Math.abs(audio[i]);\r\n if (abs > maxAbs) maxAbs = abs;\r\n }\r\n\r\n if (maxAbs >= 0.1 || maxAbs === 0) return audio;\r\n\r\n const gain = 0.5 / maxAbs;\r\n const normalized = new Float32Array(audio.length);\r\n for (let i = 0; i < audio.length; i++) {\r\n normalized[i] = audio[i] * gain;\r\n }\r\n return normalized;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Helpers\r\n // ---------------------------------------------------------------------------\r\n\r\n private setState(state: SpeechListenerState): void {\r\n if (this._state === state) return;\r\n logger.debug('State transition', { from: this._state, to: state });\r\n this._state = state;\r\n this.emit('state', state);\r\n }\r\n\r\n private emitProgress(currentModel: string, progress: number, totalModels: number, modelsLoaded: number): void {\r\n this.emit('loading:progress', { currentModel, progress, totalModels, modelsLoaded });\r\n }\r\n\r\n private clearSilenceTimer(): void {\r\n if (this.silenceTimer) {\r\n clearTimeout(this.silenceTimer);\r\n this.silenceTimer = null;\r\n }\r\n }\r\n}\r\n","/**\r\n * Interruption Handler\r\n *\r\n * VAD-based barge-in detection for AI conversations:\r\n * - Monitors VAD probability for user speech\r\n * - Detects when user interrupts AI response\r\n * - Triggers interruption callbacks\r\n */\r\n\r\nimport { EventEmitter } from '../events/EventEmitter';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\n\r\nconst logger = createLogger('InterruptionHandler');\r\n\r\nexport interface InterruptionEvents {\r\n [key: string]: unknown;\r\n 'speech.detected': { rms: number };\r\n 'speech.ended': { durationMs: number };\r\n 'interruption.triggered': { rms: number; durationMs: number };\r\n}\r\n\r\n/**\r\n * Interruption handler configuration\r\n *\r\n * Industry standards applied:\r\n * - vadThreshold: 0.5 (Silero VAD default)\r\n * - minSpeechDurationMs: 200ms (Google/Amazon barge-in standard)\r\n * - silenceTimeoutMs: 500ms (OpenAI Realtime API standard)\r\n */\r\nexport interface InterruptionConfig {\r\n /** VAD probability threshold for speech detection (default: 0.5, Silero standard) */\r\n vadThreshold?: number;\r\n /** Minimum speech duration to trigger interruption (default: 200ms, Google/Amazon standard) */\r\n minSpeechDurationMs?: number;\r\n /** Silence duration to end speech (default: 500ms, OpenAI standard) */\r\n silenceTimeoutMs?: number;\r\n /** Enable interruption detection (default: true) */\r\n enabled?: boolean;\r\n}\r\n\r\nexport class InterruptionHandler extends EventEmitter<InterruptionEvents> {\r\n private config: Required<InterruptionConfig>;\r\n private isSpeaking = false;\r\n private speechStartTime = 0;\r\n private lastSpeechTime = 0;\r\n private silenceTimer: ReturnType<typeof setTimeout> | null = null;\r\n private aiIsSpeaking = false;\r\n\r\n // Debouncing: only emit one interruption per speech session\r\n private interruptionTriggeredThisSession = false;\r\n\r\n constructor(config: InterruptionConfig = {}) {\r\n super();\r\n this.config = {\r\n vadThreshold: 0.5, // Silero VAD default\r\n minSpeechDurationMs: 200, // Google/Amazon barge-in standard\r\n silenceTimeoutMs: 500, // OpenAI Realtime API standard\r\n enabled: true,\r\n ...config,\r\n };\r\n logger.debug('Constructed with config', {\r\n vadThreshold: this.config.vadThreshold,\r\n minSpeechDurationMs: this.config.minSpeechDurationMs,\r\n silenceTimeoutMs: this.config.silenceTimeoutMs,\r\n enabled: this.config.enabled,\r\n });\r\n }\r\n\r\n /**\r\n * Process raw audio energy for interruption detection (no VAD required).\r\n * Used during speaking state when the unified worker is busy with TTS.\r\n * Echo-cancelled mic input means energy above threshold = user speech.\r\n *\r\n * @param rms - RMS energy of audio chunk (0-1)\r\n * @param energyThreshold - Minimum energy to consider speech (default: 0.02)\r\n */\r\n processAudioEnergy(rms: number, energyThreshold: number = 0.02): void {\r\n if (!this.config.enabled) return;\r\n if (rms > energyThreshold) {\r\n this.onSpeechDetected(rms);\r\n } else {\r\n this.onSilenceDetected();\r\n }\r\n }\r\n\r\n /**\r\n * Process VAD result for interruption detection\r\n * @param vadProbability - Speech probability from VAD (0-1)\r\n * @param audioEnergy - Optional RMS energy for logging (default: 0)\r\n */\r\n processVADResult(vadProbability: number, audioEnergy: number = 0): void {\r\n if (!this.config.enabled) return;\r\n\r\n if (this.aiIsSpeaking) {\r\n logger.trace('VAD during AI speech', {\r\n vadProbability,\r\n audioEnergy,\r\n threshold: this.config.vadThreshold,\r\n });\r\n }\r\n\r\n if (vadProbability > this.config.vadThreshold) {\r\n this.onSpeechDetected(audioEnergy || vadProbability);\r\n } else {\r\n this.onSilenceDetected();\r\n }\r\n }\r\n\r\n /** Notify that AI started/stopped speaking */\r\n setAISpeaking(speaking: boolean): void {\r\n logger.debug('AI speaking state changed', { speaking });\r\n this.aiIsSpeaking = speaking;\r\n }\r\n\r\n /** Enable/disable interruption detection */\r\n setEnabled(enabled: boolean): void {\r\n logger.debug('Enabled state changed', { enabled });\r\n this.config.enabled = enabled;\r\n if (!enabled) {\r\n this.reset();\r\n }\r\n }\r\n\r\n /** Update configuration */\r\n updateConfig(config: Partial<InterruptionConfig>): void {\r\n this.config = { ...this.config, ...config };\r\n }\r\n\r\n /** Reset state */\r\n reset(): void {\r\n this.isSpeaking = false;\r\n this.speechStartTime = 0;\r\n this.lastSpeechTime = 0;\r\n this.interruptionTriggeredThisSession = false;\r\n if (this.silenceTimer) {\r\n clearTimeout(this.silenceTimer);\r\n this.silenceTimer = null;\r\n }\r\n }\r\n\r\n /** Get current state */\r\n getState(): { isSpeaking: boolean; speechDurationMs: number } {\r\n return {\r\n isSpeaking: this.isSpeaking,\r\n speechDurationMs: this.isSpeaking ? getClock().now() - this.speechStartTime : 0,\r\n };\r\n }\r\n\r\n private onSpeechDetected(rms: number): void {\r\n const now = getClock().now();\r\n this.lastSpeechTime = now;\r\n\r\n if (this.silenceTimer) {\r\n clearTimeout(this.silenceTimer);\r\n this.silenceTimer = null;\r\n }\r\n\r\n if (!this.isSpeaking) {\r\n this.isSpeaking = true;\r\n this.speechStartTime = now;\r\n this.emit('speech.detected', { rms });\r\n }\r\n\r\n // Check for interruption (only emit ONCE per speech session)\r\n if (this.aiIsSpeaking && !this.interruptionTriggeredThisSession) {\r\n const speechDuration = now - this.speechStartTime;\r\n if (speechDuration >= this.config.minSpeechDurationMs) {\r\n this.interruptionTriggeredThisSession = true;\r\n logger.debug('Interruption triggered', { rms, durationMs: speechDuration });\r\n getTelemetry()?.incrementCounter(MetricNames.VOICE_INTERRUPTIONS, 1, { source: 'detector' });\r\n this.emit('interruption.triggered', { rms, durationMs: speechDuration });\r\n }\r\n }\r\n }\r\n\r\n private onSilenceDetected(): void {\r\n if (!this.isSpeaking) return;\r\n\r\n if (!this.silenceTimer) {\r\n this.silenceTimer = setTimeout(() => {\r\n const durationMs = this.lastSpeechTime - this.speechStartTime;\r\n this.isSpeaking = false;\r\n this.silenceTimer = null;\r\n this.interruptionTriggeredThisSession = false;\r\n this.emit('speech.ended', { durationMs });\r\n }, this.config.silenceTimeoutMs);\r\n }\r\n }\r\n}\r\n","/**\r\n * Safari Web Speech API wrapper for iOS speech recognition\r\n *\r\n * Provides a similar interface to WhisperInference for easy substitution on iOS.\r\n * Uses the native Web Speech API which is significantly faster than Whisper WASM on iOS.\r\n *\r\n * Key differences from WhisperInference:\r\n * - Real-time streaming (not batch processing)\r\n * - No audio buffer input (microphone handled by browser)\r\n * - transcribe() throws error (use start/stop pattern instead)\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { SafariSpeechRecognition, shouldUseNativeASR } from '@omote/core';\r\n *\r\n * // Use native ASR on iOS, Whisper elsewhere\r\n * if (shouldUseNativeASR()) {\r\n * const speech = new SafariSpeechRecognition({ language: 'en-US' });\r\n *\r\n * speech.onResult((result) => {\r\n * console.log('Transcript:', result.text);\r\n * });\r\n *\r\n * await speech.start();\r\n * // ... user speaks ...\r\n * const finalResult = await speech.stop();\r\n * }\r\n * ```\r\n *\r\n * @example Platform-aware initialization\r\n * ```typescript\r\n * const asr = shouldUseNativeASR()\r\n * ? new SafariSpeechRecognition({ language: 'en-US' })\r\n * : new WhisperInference({ model: 'tiny' });\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry';\r\nimport { isSpeechRecognitionAvailable } from '../utils/runtime';\r\n\r\nconst logger = createLogger('SafariSpeech');\r\n\r\n/**\r\n * Configuration for Safari Speech Recognition\r\n */\r\nexport interface SafariSpeechConfig {\r\n /** Language code (default: 'en-US') */\r\n language?: string;\r\n /** Continuous mode for ongoing conversation (default: true) */\r\n continuous?: boolean;\r\n /** Interim results before speech ends (default: true) */\r\n interimResults?: boolean;\r\n /** Max alternatives (default: 1) */\r\n maxAlternatives?: number;\r\n}\r\n\r\n/**\r\n * Result from speech recognition (matches WhisperInference TranscriptionResult)\r\n */\r\nexport interface SpeechRecognitionResult {\r\n /** Transcribed text */\r\n text: string;\r\n /** Detected/used language */\r\n language: string;\r\n /** Time since start in ms (not inference time - native API) */\r\n inferenceTimeMs: number;\r\n /** Whether this is a final result or interim */\r\n isFinal: boolean;\r\n /** Confidence score (0-1) if available */\r\n confidence?: number;\r\n}\r\n\r\n/**\r\n * Callback for receiving recognition results\r\n */\r\nexport type SpeechResultCallback = (result: SpeechRecognitionResult) => void;\r\n\r\n/**\r\n * Callback for receiving recognition errors\r\n */\r\nexport type SpeechErrorCallback = (error: Error) => void;\r\n\r\n// Type declarations for Web Speech API (not in lib.dom.d.ts by default)\r\ninterface SpeechRecognitionEvent extends Event {\r\n resultIndex: number;\r\n results: SpeechRecognitionResultList;\r\n}\r\n\r\ninterface SpeechRecognitionResultList {\r\n length: number;\r\n item(index: number): SpeechRecognitionResult;\r\n [index: number]: SpeechRecognitionResultItem;\r\n}\r\n\r\ninterface SpeechRecognitionResultItem {\r\n isFinal: boolean;\r\n length: number;\r\n item(index: number): SpeechRecognitionAlternative;\r\n [index: number]: SpeechRecognitionAlternative;\r\n}\r\n\r\ninterface SpeechRecognitionAlternative {\r\n transcript: string;\r\n confidence: number;\r\n}\r\n\r\ninterface SpeechRecognitionErrorEvent extends Event {\r\n error: string;\r\n message: string;\r\n}\r\n\r\ninterface SpeechRecognitionInterface extends EventTarget {\r\n continuous: boolean;\r\n interimResults: boolean;\r\n lang: string;\r\n maxAlternatives: number;\r\n start(): void;\r\n stop(): void;\r\n abort(): void;\r\n onresult: ((event: SpeechRecognitionEvent) => void) | null;\r\n onerror: ((event: SpeechRecognitionErrorEvent) => void) | null;\r\n onend: (() => void) | null;\r\n onstart: (() => void) | null;\r\n onaudiostart: (() => void) | null;\r\n onaudioend: (() => void) | null;\r\n onspeechstart: (() => void) | null;\r\n onspeechend: (() => void) | null;\r\n}\r\n\r\ndeclare global {\r\n interface Window {\r\n SpeechRecognition?: new () => SpeechRecognitionInterface;\r\n webkitSpeechRecognition?: new () => SpeechRecognitionInterface;\r\n }\r\n}\r\n\r\n/**\r\n * Safari Web Speech API wrapper\r\n *\r\n * Provides native speech recognition on iOS Safari.\r\n * Much faster than Whisper WASM and more battery-efficient.\r\n */\r\nexport class SafariSpeechRecognition {\r\n private config: Required<SafariSpeechConfig>;\r\n private recognition: SpeechRecognitionInterface | null = null;\r\n private isListening = false;\r\n private startTime = 0;\r\n private accumulatedText = '';\r\n\r\n // Callbacks\r\n private resultCallbacks: SpeechResultCallback[] = [];\r\n private errorCallbacks: SpeechErrorCallback[] = [];\r\n\r\n // Promise resolvers for stop()\r\n private stopResolver: ((result: SpeechRecognitionResult) => void) | null = null;\r\n private stopRejecter: ((error: Error) => void) | null = null;\r\n\r\n constructor(config: SafariSpeechConfig = {}) {\r\n this.config = {\r\n language: config.language ?? 'en-US',\r\n continuous: config.continuous ?? true,\r\n interimResults: config.interimResults ?? true,\r\n maxAlternatives: config.maxAlternatives ?? 1,\r\n };\r\n\r\n logger.debug('SafariSpeechRecognition created', {\r\n language: this.config.language,\r\n continuous: this.config.continuous,\r\n });\r\n }\r\n\r\n /**\r\n * Check if Web Speech API is available\r\n */\r\n static isAvailable(): boolean {\r\n return isSpeechRecognitionAvailable();\r\n }\r\n\r\n /**\r\n * Check if currently listening\r\n */\r\n get listening(): boolean {\r\n return this.isListening;\r\n }\r\n\r\n /**\r\n * Get the language being used\r\n */\r\n get language(): string {\r\n return this.config.language;\r\n }\r\n\r\n /**\r\n * Register a callback for receiving results\r\n */\r\n onResult(callback: SpeechResultCallback): void {\r\n this.resultCallbacks.push(callback);\r\n }\r\n\r\n /**\r\n * Register a callback for receiving errors\r\n */\r\n onError(callback: SpeechErrorCallback): void {\r\n this.errorCallbacks.push(callback);\r\n }\r\n\r\n /**\r\n * Remove a result callback\r\n */\r\n offResult(callback: SpeechResultCallback): void {\r\n const index = this.resultCallbacks.indexOf(callback);\r\n if (index !== -1) {\r\n this.resultCallbacks.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Remove an error callback\r\n */\r\n offError(callback: SpeechErrorCallback): void {\r\n const index = this.errorCallbacks.indexOf(callback);\r\n if (index !== -1) {\r\n this.errorCallbacks.splice(index, 1);\r\n }\r\n }\r\n\r\n /**\r\n * Start listening for speech\r\n *\r\n * On iOS Safari, this will trigger the microphone permission prompt\r\n * if not already granted.\r\n */\r\n async start(): Promise<void> {\r\n if (this.isListening) {\r\n logger.warn('Already listening');\r\n return;\r\n }\r\n\r\n if (!SafariSpeechRecognition.isAvailable()) {\r\n const error = new Error(\r\n 'Web Speech API not available. ' +\r\n 'This API is supported in Safari (iOS/macOS) and Chrome. ' +\r\n 'On iOS, use Safari for native speech recognition.'\r\n );\r\n this.emitError(error);\r\n throw error;\r\n }\r\n\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.start', {\r\n 'speech.language': this.config.language,\r\n 'speech.continuous': this.config.continuous,\r\n });\r\n\r\n try {\r\n // Create recognition instance\r\n const SpeechRecognitionClass = window.SpeechRecognition || window.webkitSpeechRecognition;\r\n if (!SpeechRecognitionClass) {\r\n throw new Error('SpeechRecognition constructor not found');\r\n }\r\n\r\n this.recognition = new SpeechRecognitionClass();\r\n this.recognition.continuous = this.config.continuous;\r\n this.recognition.interimResults = this.config.interimResults;\r\n this.recognition.lang = this.config.language;\r\n this.recognition.maxAlternatives = this.config.maxAlternatives;\r\n\r\n // Set up event handlers\r\n this.setupEventHandlers();\r\n\r\n // Start recognition\r\n this.recognition.start();\r\n this.isListening = true;\r\n this.startTime = getClock().now();\r\n this.accumulatedText = '';\r\n\r\n logger.info('Speech recognition started', {\r\n language: this.config.language,\r\n });\r\n\r\n span?.end();\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n this.emitError(error instanceof Error ? error : new Error(String(error)));\r\n throw error;\r\n }\r\n }\r\n\r\n /**\r\n * Stop listening and return the final transcript\r\n */\r\n async stop(): Promise<SpeechRecognitionResult> {\r\n if (!this.isListening || !this.recognition) {\r\n logger.warn('Not currently listening');\r\n return {\r\n text: this.accumulatedText,\r\n language: this.config.language,\r\n inferenceTimeMs: 0,\r\n isFinal: true,\r\n };\r\n }\r\n\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.stop');\r\n\r\n return new Promise((resolve, reject) => {\r\n this.stopResolver = resolve;\r\n this.stopRejecter = reject;\r\n\r\n try {\r\n this.recognition!.stop();\r\n // onend handler will resolve the promise\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n this.isListening = false;\r\n reject(error);\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Abort recognition without waiting for final result\r\n */\r\n abort(): void {\r\n if (this.recognition && this.isListening) {\r\n this.recognition.abort();\r\n this.isListening = false;\r\n logger.info('Speech recognition aborted');\r\n }\r\n }\r\n\r\n /**\r\n * NOT SUPPORTED: Transcribe audio buffer\r\n *\r\n * Safari Speech API does not support transcribing pre-recorded audio.\r\n * It only works with live microphone input.\r\n *\r\n * For batch transcription on iOS, use server-side Whisper or a cloud ASR service.\r\n *\r\n * @throws Error always - this method is not supported\r\n */\r\n async transcribe(_audio: Float32Array): Promise<SpeechRecognitionResult> {\r\n throw new Error(\r\n 'SafariSpeechRecognition does not support transcribe() with audio buffers. ' +\r\n 'The Web Speech API only works with live microphone input. ' +\r\n 'Use start() and stop() for real-time recognition, or use WhisperInference/cloud ASR for batch transcription.'\r\n );\r\n }\r\n\r\n /**\r\n * Dispose of recognition resources\r\n */\r\n dispose(): void {\r\n logger.debug('Disposed');\r\n if (this.recognition) {\r\n if (this.isListening) {\r\n this.recognition.abort();\r\n }\r\n this.recognition = null;\r\n }\r\n this.isListening = false;\r\n this.resultCallbacks = [];\r\n this.errorCallbacks = [];\r\n logger.debug('SafariSpeechRecognition disposed');\r\n }\r\n\r\n /**\r\n * Set up event handlers for the recognition instance\r\n */\r\n private setupEventHandlers(): void {\r\n if (!this.recognition) return;\r\n\r\n this.recognition.onresult = (event: SpeechRecognitionEvent) => {\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('SafariSpeech.onresult');\r\n\r\n try {\r\n // Process all new results\r\n for (let i = event.resultIndex; i < event.results.length; i++) {\r\n const result = event.results[i];\r\n const alternative = result[0];\r\n\r\n if (alternative) {\r\n const text = alternative.transcript;\r\n const isFinal = result.isFinal;\r\n\r\n // Accumulate final text\r\n if (isFinal) {\r\n this.accumulatedText += text + ' ';\r\n }\r\n\r\n const speechResult: SpeechRecognitionResult = {\r\n text: isFinal ? this.accumulatedText.trim() : text,\r\n language: this.config.language,\r\n inferenceTimeMs: getClock().now() - this.startTime,\r\n isFinal,\r\n confidence: alternative.confidence,\r\n };\r\n\r\n // Emit to callbacks\r\n this.emitResult(speechResult);\r\n\r\n logger.trace('Speech result', {\r\n text: text.substring(0, 50),\r\n isFinal,\r\n confidence: alternative.confidence,\r\n });\r\n }\r\n }\r\n\r\n span?.end();\r\n } catch (error) {\r\n span?.endWithError(error instanceof Error ? error : new Error(String(error)));\r\n logger.error('Error processing speech result', { error });\r\n }\r\n };\r\n\r\n this.recognition.onerror = (event: SpeechRecognitionErrorEvent) => {\r\n const error = new Error(`Speech recognition error: ${event.error} - ${event.message}`);\r\n logger.error('Speech recognition error', { error: event.error, message: event.message });\r\n this.emitError(error);\r\n\r\n if (this.stopRejecter) {\r\n this.stopRejecter(error);\r\n this.stopResolver = null;\r\n this.stopRejecter = null;\r\n }\r\n };\r\n\r\n this.recognition.onend = () => {\r\n this.isListening = false;\r\n logger.info('Speech recognition ended', {\r\n totalText: this.accumulatedText.length,\r\n durationMs: getClock().now() - this.startTime,\r\n });\r\n\r\n // Resolve stop() promise if pending\r\n if (this.stopResolver) {\r\n const result: SpeechRecognitionResult = {\r\n text: this.accumulatedText.trim(),\r\n language: this.config.language,\r\n inferenceTimeMs: getClock().now() - this.startTime,\r\n isFinal: true,\r\n };\r\n this.stopResolver(result);\r\n this.stopResolver = null;\r\n this.stopRejecter = null;\r\n }\r\n };\r\n\r\n this.recognition.onstart = () => {\r\n logger.debug('Speech recognition started by browser');\r\n };\r\n\r\n this.recognition.onspeechstart = () => {\r\n logger.debug('Speech detected');\r\n };\r\n\r\n this.recognition.onspeechend = () => {\r\n logger.debug('Speech ended');\r\n };\r\n }\r\n\r\n /**\r\n * Emit result to all registered callbacks\r\n */\r\n private emitResult(result: SpeechRecognitionResult): void {\r\n for (const callback of this.resultCallbacks) {\r\n try {\r\n callback(result);\r\n } catch (error) {\r\n logger.error('Error in result callback', { error });\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Emit error to all registered callbacks\r\n */\r\n private emitError(error: Error): void {\r\n for (const callback of this.errorCallbacks) {\r\n try {\r\n callback(error);\r\n } catch (callbackError) {\r\n logger.error('Error in error callback', { error: callbackError });\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * ElevenLabs TTS Backend — Cloud text-to-speech via ElevenLabs REST API.\r\n *\r\n * Implements the TTSBackend interface so it can be used anywhere Kokoro TTS is used\r\n * (TTSPlayback, TTSSpeaker, VoiceOrchestrator, PlaybackPipeline, etc.)\r\n *\r\n * Zero external dependencies — uses fetch() directly.\r\n *\r\n * @category Inference\r\n *\r\n * @example Basic usage\r\n * ```typescript\r\n * import { ElevenLabsTTSBackend } from '@omote/core';\r\n *\r\n * const tts = new ElevenLabsTTSBackend({\r\n * apiKey: 'your-api-key',\r\n * voiceId: 'voice-id',\r\n * });\r\n * await tts.load();\r\n *\r\n * for await (const chunk of tts.stream(\"Hello world!\")) {\r\n * playbackPipeline.feedBuffer(chunk.audio);\r\n * }\r\n * ```\r\n *\r\n * @example With PlaybackPipeline\r\n * ```typescript\r\n * const speaker = new TTSSpeaker();\r\n * await speaker.connect(tts, { lam: createA2E() });\r\n * await speaker.speak(\"Hello!\");\r\n * ```\r\n */\r\n\r\nimport { createLogger } from '../logging/Logger';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry/index';\r\nimport { MetricNames } from '../telemetry/types';\r\nimport { pcm16ToFloat32 } from '../audio/audioUtils';\r\nimport type { TTSBackend, TTSStreamOptions, TTSChunk } from './TTSBackend';\r\n\r\nconst logger = createLogger('ElevenLabsTTS');\r\n\r\n// ─── Config ─────────────────────────────────────────────────────────────────\r\n\r\nexport interface ElevenLabsConfig {\r\n /** ElevenLabs API key */\r\n apiKey: string;\r\n /** Voice ID to use */\r\n voiceId: string;\r\n /** Model ID (default: 'eleven_multilingual_v2') */\r\n model?: string;\r\n /**\r\n * Output format (default: 'pcm_16000').\r\n * Use 'pcm_16000' for lip sync compatibility (16kHz matches A2E input).\r\n * Other options: 'pcm_22050', 'pcm_24000', 'pcm_44100'\r\n */\r\n outputFormat?: string;\r\n /** Voice stability 0-1 (default: 0.5) */\r\n stability?: number;\r\n /** Voice similarity boost 0-1 (default: 0.75) */\r\n similarityBoost?: number;\r\n /** API base URL override (default: 'https://api.elevenlabs.io') */\r\n baseUrl?: string;\r\n}\r\n\r\n// ─── Constants ──────────────────────────────────────────────────────────────\r\n\r\nconst DEFAULT_MODEL = 'eleven_multilingual_v2';\r\nconst DEFAULT_OUTPUT_FORMAT = 'pcm_16000';\r\nconst DEFAULT_STABILITY = 0.5;\r\nconst DEFAULT_SIMILARITY_BOOST = 0.75;\r\nconst DEFAULT_BASE_URL = 'https://api.elevenlabs.io';\r\n\r\n/** Map output format strings to sample rates */\r\nconst FORMAT_TO_SAMPLE_RATE: Record<string, number> = {\r\n pcm_16000: 16000,\r\n pcm_22050: 22050,\r\n pcm_24000: 24000,\r\n pcm_44100: 44100,\r\n};\r\n\r\n// ─── Class ──────────────────────────────────────────────────────────────────\r\n\r\nexport class ElevenLabsTTSBackend implements TTSBackend {\r\n private readonly apiKey: string;\r\n private readonly voiceId: string;\r\n private readonly model: string;\r\n private readonly outputFormat: string;\r\n private readonly stability: number;\r\n private readonly similarityBoost: number;\r\n private readonly baseUrl: string;\r\n private readonly _sampleRate: number;\r\n private _isLoaded = false;\r\n\r\n constructor(config: ElevenLabsConfig) {\r\n if (!config.apiKey) throw new Error('ElevenLabsTTS: apiKey is required');\r\n if (!config.voiceId) throw new Error('ElevenLabsTTS: voiceId is required');\r\n\r\n this.apiKey = config.apiKey;\r\n this.voiceId = config.voiceId;\r\n this.model = config.model ?? DEFAULT_MODEL;\r\n this.outputFormat = config.outputFormat ?? DEFAULT_OUTPUT_FORMAT;\r\n this.stability = config.stability ?? DEFAULT_STABILITY;\r\n this.similarityBoost = config.similarityBoost ?? DEFAULT_SIMILARITY_BOOST;\r\n this.baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\r\n\r\n const rate = FORMAT_TO_SAMPLE_RATE[this.outputFormat];\r\n if (!rate) {\r\n throw new Error(\r\n `ElevenLabsTTS: unsupported outputFormat \"${this.outputFormat}\". ` +\r\n `Supported: ${Object.keys(FORMAT_TO_SAMPLE_RATE).join(', ')}`,\r\n );\r\n }\r\n this._sampleRate = rate;\r\n }\r\n\r\n get sampleRate(): number {\r\n return this._sampleRate;\r\n }\r\n\r\n get isLoaded(): boolean {\r\n return this._isLoaded;\r\n }\r\n\r\n // ─── Load ───────────────────────────────────────────────────────────────\r\n\r\n /**\r\n * No-op for cloud TTS (no model to load).\r\n * Marks backend as ready.\r\n */\r\n async load(): Promise<void> {\r\n this._isLoaded = true;\r\n logger.info('ElevenLabs TTS ready', { voiceId: this.voiceId, model: this.model });\r\n }\r\n\r\n // ─── Stream ─────────────────────────────────────────────────────────────\r\n\r\n /**\r\n * Stream audio from ElevenLabs for the given text.\r\n *\r\n * Uses the streaming endpoint. Yields a single chunk for non-streaming\r\n * or multiple chunks as response data arrives.\r\n */\r\n async *stream(text: string, options?: TTSStreamOptions): AsyncGenerator<TTSChunk> {\r\n if (!this._isLoaded) {\r\n throw new Error('ElevenLabsTTS: not loaded. Call load() first.');\r\n }\r\n\r\n const trimmed = text.trim();\r\n if (trimmed.length === 0) {\r\n throw new Error('ElevenLabsTTS: text must not be empty');\r\n }\r\n\r\n const startTime = getClock().now();\r\n const telemetry = getTelemetry();\r\n const span = telemetry?.startSpan('ElevenLabsTTS.stream', {\r\n 'tts.text_length': trimmed.length,\r\n 'tts.voice_id': this.voiceId,\r\n 'tts.model': this.model,\r\n });\r\n\r\n const url = `${this.baseUrl}/v1/text-to-speech/${this.voiceId}?output_format=${this.outputFormat}`;\r\n\r\n try {\r\n const response = await fetch(url, {\r\n method: 'POST',\r\n headers: {\r\n 'xi-api-key': this.apiKey,\r\n 'Content-Type': 'application/json',\r\n Accept: 'audio/pcm',\r\n },\r\n body: JSON.stringify({\r\n text: trimmed,\r\n model_id: this.model,\r\n voice_settings: {\r\n stability: this.stability,\r\n similarity_boost: this.similarityBoost,\r\n },\r\n }),\r\n signal: options?.signal,\r\n });\r\n\r\n if (!response.ok) {\r\n const errorText = await response.text().catch(() => 'unknown');\r\n const msg = `ElevenLabsTTS: HTTP ${response.status} — ${this.getHttpErrorMessage(response.status, errorText)}`;\r\n logger.error(msg);\r\n throw new Error(msg);\r\n }\r\n\r\n if (!response.body) {\r\n // Non-streaming fallback: read entire response\r\n const buffer = await response.arrayBuffer();\r\n const audio = pcm16ToFloat32(buffer);\r\n const duration = audio.length / this._sampleRate;\r\n\r\n const latency = getClock().now() - startTime;\r\n span?.setAttributes({ 'tts.duration_s': duration, 'tts.latency_ms': latency });\r\n span?.end();\r\n telemetry?.recordHistogram(MetricNames.INFERENCE_LATENCY, latency, {\r\n model: 'elevenlabs-tts',\r\n backend: 'cloud',\r\n });\r\n\r\n yield { audio, duration, text: trimmed };\r\n return;\r\n }\r\n\r\n // Streaming: read chunks from ReadableStream\r\n const reader = response.body.getReader();\r\n let totalSamples = 0;\r\n\r\n try {\r\n while (true) {\r\n if (options?.signal?.aborted) {\r\n reader.cancel();\r\n logger.debug('Stream aborted by signal');\r\n return;\r\n }\r\n\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n\r\n if (value && value.byteLength > 0) {\r\n // Ensure even byte count for Int16 alignment\r\n const usableBytes = value.byteLength & ~1;\r\n if (usableBytes === 0) continue;\r\n\r\n const audio = pcm16ToFloat32(value.buffer.slice(value.byteOffset, value.byteOffset + usableBytes));\r\n const duration = audio.length / this._sampleRate;\r\n totalSamples += audio.length;\r\n\r\n yield { audio, duration, text: trimmed };\r\n }\r\n }\r\n } finally {\r\n reader.releaseLock();\r\n }\r\n\r\n const latency = getClock().now() - startTime;\r\n const totalDuration = totalSamples / this._sampleRate;\r\n\r\n logger.debug('Stream complete', {\r\n totalDuration: `${totalDuration.toFixed(2)}s`,\r\n latencyMs: Math.round(latency),\r\n totalSamples,\r\n });\r\n\r\n span?.setAttributes({ 'tts.duration_s': totalDuration, 'tts.latency_ms': latency });\r\n span?.end();\r\n\r\n telemetry?.recordHistogram(MetricNames.INFERENCE_LATENCY, latency, {\r\n model: 'elevenlabs-tts',\r\n backend: 'cloud',\r\n });\r\n telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {\r\n model: 'elevenlabs-tts',\r\n backend: 'cloud',\r\n status: 'success',\r\n });\r\n } catch (err) {\r\n if (err instanceof DOMException && err.name === 'AbortError') {\r\n logger.debug('Stream aborted');\r\n span?.end();\r\n return;\r\n }\r\n\r\n const errMsg = err instanceof Error ? err.message : String(err);\r\n logger.error('Stream failed', { error: errMsg });\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n\r\n telemetry?.incrementCounter(MetricNames.INFERENCE_TOTAL, 1, {\r\n model: 'elevenlabs-tts',\r\n backend: 'cloud',\r\n status: 'error',\r\n });\r\n\r\n throw err;\r\n }\r\n }\r\n\r\n // ─── Dispose ────────────────────────────────────────────────────────────\r\n\r\n async dispose(): Promise<void> {\r\n this._isLoaded = false;\r\n logger.info('ElevenLabs TTS disposed');\r\n }\r\n\r\n // ─── Private ────────────────────────────────────────────────────────────\r\n\r\n private getHttpErrorMessage(status: number, body: string): string {\r\n switch (status) {\r\n case 401:\r\n return 'Unauthorized — check your API key';\r\n case 403:\r\n return 'Forbidden — API key lacks required permissions';\r\n case 429:\r\n return 'Rate limited — too many requests';\r\n case 400:\r\n return `Bad request — ${body}`;\r\n default:\r\n return body || `HTTP error ${status}`;\r\n }\r\n }\r\n}\r\n","/**\n * Emotion - Helper for creating emotion vectors for avatar animation\n *\n * Provides 10 explicit emotion channels that can be used to control\n * avatar expressions and emotional states.\n *\n * @category Emotion\n *\n * @example Creating emotion vectors\n * ```typescript\n * import { createEmotionVector, EmotionPresets } from '@omote/core';\n *\n * // Named weights\n * const happy = createEmotionVector({ joy: 0.8, amazement: 0.2 });\n *\n * // Use preset\n * const surprised = EmotionPresets.surprised;\n * ```\n *\n * @example Smooth transitions\n * ```typescript\n * import { EmotionController } from '@omote/core';\n *\n * const controller = new EmotionController();\n * controller.setPreset('happy');\n * controller.transitionTo({ sadness: 0.7 }, 500);\n *\n * // In animation loop\n * controller.update();\n * const emotion = controller.emotion;\n * ```\n */\n\nimport { createLogger } from '../logging';\nimport { getClock } from '../logging/Clock';\n\nconst logger = createLogger('EmotionController');\n\n/** The 10 explicit emotion channels */\nexport const EMOTION_NAMES = [\n 'amazement',\n 'anger',\n 'cheekiness',\n 'disgust',\n 'fear',\n 'grief',\n 'joy',\n 'outofbreath',\n 'pain',\n 'sadness',\n] as const;\n\nexport type EmotionName = typeof EMOTION_NAMES[number];\n\n/** Emotion weights by name */\nexport type EmotionWeights = Partial<Record<EmotionName, number>>;\n\n/** Total emotion vector size */\nexport const EMOTION_VECTOR_SIZE = 26;\n\n/** Number of explicit emotion channels */\nexport const EXPLICIT_EMOTION_COUNT = 10;\n\n/**\n * Create an emotion vector from named weights\n *\n * @param weights - Named emotion weights (0-1)\n * @returns Float32Array of emotion values\n *\n * @example\n * ```ts\n * const emotion = createEmotionVector({ joy: 0.8, amazement: 0.3 });\n * ```\n */\nexport function createEmotionVector(weights: EmotionWeights = {}): Float32Array {\n const vector = new Float32Array(EMOTION_VECTOR_SIZE);\n\n for (const [name, value] of Object.entries(weights)) {\n const idx = EMOTION_NAMES.indexOf(name as EmotionName);\n if (idx >= 0) {\n vector[idx] = Math.max(0, Math.min(1, value));\n } else {\n logger.warn(`Invalid emotion name in createEmotionVector: \"${name}\"`);\n }\n }\n\n return vector;\n}\n\n/**\n * Pre-built emotion presets for common expressions\n */\nexport const EmotionPresets = {\n /** Neutral/default - no emotional expression */\n neutral: createEmotionVector({}),\n\n /** Happy - joy with slight amazement */\n happy: createEmotionVector({ joy: 0.7, amazement: 0.2 }),\n\n /** Sad - grief and sadness */\n sad: createEmotionVector({ sadness: 0.7, grief: 0.4 }),\n\n /** Angry - anger with disgust */\n angry: createEmotionVector({ anger: 0.8, disgust: 0.3 }),\n\n /** Surprised - high amazement */\n surprised: createEmotionVector({ amazement: 0.9, fear: 0.2 }),\n\n /** Scared - fear with pain */\n scared: createEmotionVector({ fear: 0.8, pain: 0.3 }),\n\n /** Disgusted - disgust with anger */\n disgusted: createEmotionVector({ disgust: 0.8, anger: 0.2 }),\n\n /** Excited - joy with amazement and cheekiness */\n excited: createEmotionVector({ joy: 0.6, amazement: 0.5, cheekiness: 0.4 }),\n\n /** Tired - out of breath with sadness */\n tired: createEmotionVector({ outofbreath: 0.6, sadness: 0.3 }),\n\n /** Playful - cheekiness with joy */\n playful: createEmotionVector({ cheekiness: 0.7, joy: 0.5 }),\n\n /** Pained - pain with grief */\n pained: createEmotionVector({ pain: 0.8, grief: 0.4 }),\n\n /** Contemplative - slight sadness, calm */\n contemplative: createEmotionVector({ sadness: 0.2, grief: 0.1 }),\n} as const;\n\nexport type EmotionPresetName = keyof typeof EmotionPresets;\n\n/**\n * Get an emotion preset by name\n */\nexport function getEmotionPreset(name: EmotionPresetName): Float32Array {\n return EmotionPresets[name].slice();\n}\n\n/**\n * Blend multiple emotion vectors together\n *\n * @param emotions - Array of { vector, weight } pairs\n * @returns Blended emotion vector\n *\n * @example\n * ```ts\n * const blended = blendEmotions([\n * { vector: EmotionPresets.happy, weight: 0.7 },\n * { vector: EmotionPresets.surprised, weight: 0.3 },\n * ]);\n * ```\n */\nexport function blendEmotions(\n emotions: Array<{ vector: Float32Array; weight: number }>\n): Float32Array {\n const result = new Float32Array(EMOTION_VECTOR_SIZE);\n let totalWeight = 0;\n\n for (const { vector, weight } of emotions) {\n totalWeight += weight;\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] += (vector[i] || 0) * weight;\n }\n }\n\n // Normalize if total weight > 0\n if (totalWeight > 0) {\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] /= totalWeight;\n }\n }\n\n return result;\n}\n\n/**\n * Interpolate between two emotion vectors\n *\n * @param from - Starting emotion\n * @param to - Target emotion\n * @param t - Interpolation factor (0-1)\n * @returns Interpolated emotion vector\n */\nexport function lerpEmotion(\n from: Float32Array,\n to: Float32Array,\n t: number\n): Float32Array {\n const result = new Float32Array(EMOTION_VECTOR_SIZE);\n const clampedT = Math.max(0, Math.min(1, t));\n\n for (let i = 0; i < EMOTION_VECTOR_SIZE; i++) {\n result[i] = (from[i] || 0) * (1 - clampedT) + (to[i] || 0) * clampedT;\n }\n\n return result;\n}\n\n/**\n * EmotionController - Manages emotion state with smooth transitions\n */\nexport class EmotionController {\n private currentEmotion = new Float32Array(EMOTION_VECTOR_SIZE);\n private targetEmotion = new Float32Array(EMOTION_VECTOR_SIZE);\n private transitionProgress = 1.0;\n private transitionDuration = 0;\n private transitionStartTime = 0;\n\n /**\n * Get the current emotion vector\n */\n get emotion(): Float32Array {\n if (this.transitionProgress >= 1.0) {\n return this.targetEmotion;\n }\n\n // Interpolate during transition\n return lerpEmotion(this.currentEmotion, this.targetEmotion, this.transitionProgress);\n }\n\n /**\n * Set emotion immediately (no transition)\n */\n set(weights: EmotionWeights): void {\n const newEmotion = createEmotionVector(weights);\n this.targetEmotion.set(newEmotion);\n this.currentEmotion.set(newEmotion);\n this.transitionProgress = 1.0;\n logger.debug('set', { weights });\n }\n\n /**\n * Set emotion from preset immediately\n */\n setPreset(preset: EmotionPresetName): void {\n const newEmotion = getEmotionPreset(preset);\n this.targetEmotion.set(newEmotion);\n this.currentEmotion.set(newEmotion);\n this.transitionProgress = 1.0;\n logger.debug('setPreset', { preset });\n }\n\n /**\n * Transition to new emotion over time\n *\n * @param weights - Target emotion weights\n * @param durationMs - Transition duration in milliseconds\n */\n transitionTo(weights: EmotionWeights, durationMs: number): void {\n this.currentEmotion.set(this.emotion);\n this.targetEmotion.set(createEmotionVector(weights));\n this.transitionDuration = durationMs;\n this.transitionStartTime = getClock().now();\n this.transitionProgress = 0;\n logger.debug('transitionTo', { weights, durationMs });\n }\n\n /**\n * Transition to preset over time\n */\n transitionToPreset(preset: EmotionPresetName, durationMs: number): void {\n this.currentEmotion.set(this.emotion);\n this.targetEmotion.set(getEmotionPreset(preset));\n this.transitionDuration = durationMs;\n this.transitionStartTime = getClock().now();\n this.transitionProgress = 0;\n }\n\n /**\n * Update transition progress (call each frame)\n */\n update(): void {\n if (this.transitionProgress >= 1.0) return;\n\n const elapsed = getClock().now() - this.transitionStartTime;\n this.transitionProgress = Math.min(1.0, elapsed / this.transitionDuration);\n }\n\n /**\n * Check if currently transitioning\n */\n get isTransitioning(): boolean {\n return this.transitionProgress < 1.0;\n }\n\n /**\n * Reset to neutral\n */\n reset(): void {\n this.currentEmotion.fill(0);\n this.targetEmotion.fill(0);\n this.transitionProgress = 1.0;\n logger.debug('reset');\n }\n}\n","/**\n * Animation Graph Types\n *\n * Renderer-agnostic animation state machine with emotion and audio-driven blending.\n *\n * @module animation\n */\n\n/**\n * Emotion labels for animation blending\n * Note: These are the 8 emotion categories used for animation, separate from the\n * internal EmotionName type used by EmotionController.\n */\nexport type EmotionLabel =\n | 'angry'\n | 'calm'\n | 'disgust'\n | 'fearful'\n | 'happy'\n | 'neutral'\n | 'sad'\n | 'surprised';\n\n/**\n * High-level animation states\n */\nexport type AnimationStateName = 'idle' | 'listening' | 'thinking' | 'speaking';\n\n/**\n * Events that trigger state transitions\n */\nexport type AnimationTrigger =\n | 'user_speech_start'\n | 'user_speech_end'\n | 'transcript_ready'\n | 'ai_response_start'\n | 'ai_audio_start'\n | 'ai_response_end'\n | 'timeout'\n | 'interrupt';\n\n/**\n * Animation layer types for blending\n */\nexport type AnimationLayer = 'base' | 'emotion' | 'gesture' | 'additive';\n\n/**\n * A single animation clip reference\n */\nexport interface AnimationClip {\n /** Unique identifier for the clip */\n name: string;\n /** Animation layer this clip belongs to */\n layer: AnimationLayer;\n /** Whether this clip loops */\n loop: boolean;\n /** Default duration in seconds (can be overridden by actual clip) */\n duration?: number;\n}\n\n/**\n * Blend weight for an animation clip\n */\nexport interface BlendWeight {\n /** Clip name */\n clip: string;\n /** Weight 0-1 */\n weight: number;\n /** Playback speed multiplier */\n speed: number;\n /** Current time in the animation (0-1 normalized) */\n time: number;\n}\n\n/**\n * Animation state definition\n */\nexport interface AnimationState {\n /** State name */\n name: AnimationStateName;\n /** Base animation clips for this state */\n baseClips: string[];\n /** Blend weights for base clips */\n baseWeights: number[];\n /** Whether emotion overlay is enabled in this state */\n emotionBlendEnabled: boolean;\n /** Whether gesture layer is enabled in this state */\n gestureBlendEnabled: boolean;\n /** Timeout in ms to auto-transition (0 = no timeout) */\n timeout: number;\n /** State to transition to on timeout */\n timeoutTarget?: AnimationStateName;\n}\n\n/**\n * Transition between states\n */\nexport interface Transition {\n /** Source state */\n from: AnimationStateName;\n /** Target state */\n to: AnimationStateName;\n /** Event that triggers this transition */\n trigger: AnimationTrigger;\n /** Blend duration in ms */\n duration: number;\n /** Optional condition function */\n condition?: () => boolean;\n}\n\n/**\n * Emotion to animation mapping\n */\nexport interface EmotionAnimationMap {\n /** Emotion label */\n emotion: EmotionLabel;\n /** Animation clip to blend */\n clip: string;\n /** Maximum blend weight for this emotion */\n maxWeight: number;\n /** Blend speed (weight change per second) */\n blendSpeed: number;\n}\n\n/**\n * Configuration for AnimationGraph\n */\nexport interface AnimationGraphConfig {\n /** Available animation states */\n states: AnimationState[];\n /** Transitions between states */\n transitions: Transition[];\n /** Emotion to animation mappings */\n emotionMappings: EmotionAnimationMap[];\n /** Gesture clips for audio-driven animation */\n gestureClips: string[];\n /** Initial state */\n initialState: AnimationStateName;\n /** Global blend speed for state transitions (weight/sec) */\n transitionBlendSpeed: number;\n /** Minimum audio energy to trigger gestures (0-1) */\n gestureThreshold: number;\n /** Gesture intensity multiplier */\n gestureIntensity: number;\n}\n\n/**\n * Current output of the animation graph\n */\nexport interface AnimationOutput {\n /** Current state name */\n state: AnimationStateName;\n /** All blend weights to apply */\n blendWeights: BlendWeight[];\n /** Active emotion (if any) */\n activeEmotion: EmotionLabel | null;\n /** Current gesture intensity (0-1) */\n gestureIntensity: number;\n /** Whether currently transitioning between states */\n isTransitioning: boolean;\n /** Transition progress (0-1) if transitioning */\n transitionProgress: number;\n}\n\n/**\n * Events emitted by AnimationGraph\n */\nexport type AnimationGraphEvents = {\n /** State changed */\n 'state.change': {\n from: AnimationStateName;\n to: AnimationStateName;\n trigger: AnimationTrigger;\n };\n /** Transition started */\n 'transition.start': {\n from: AnimationStateName;\n to: AnimationStateName;\n duration: number;\n };\n /** Transition completed */\n 'transition.end': {\n state: AnimationStateName;\n };\n /** Emotion changed */\n 'emotion.change': {\n emotion: EmotionLabel | null;\n confidence: number;\n };\n /** Animation output updated (every frame) */\n 'output.update': AnimationOutput;\n /** Index signature for EventEmitter compatibility */\n [key: string]: unknown;\n};\n\n/**\n * Default animation graph configuration\n */\nexport const DEFAULT_ANIMATION_CONFIG: AnimationGraphConfig = {\n initialState: 'idle',\n transitionBlendSpeed: 4.0, // Full blend in 250ms\n gestureThreshold: 0.1,\n gestureIntensity: 1.0,\n\n states: [\n {\n name: 'idle',\n baseClips: ['idle_breathe'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: false,\n timeout: 0,\n },\n {\n name: 'listening',\n baseClips: ['idle_attentive'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: false,\n timeout: 10000, // 10s timeout back to idle\n timeoutTarget: 'idle',\n },\n {\n name: 'thinking',\n baseClips: ['thinking_look_up', 'thinking_hand_chin'],\n baseWeights: [0.6, 0.4],\n emotionBlendEnabled: false,\n gestureBlendEnabled: false,\n timeout: 5000, // 5s max thinking\n timeoutTarget: 'idle',\n },\n {\n name: 'speaking',\n baseClips: ['talking_idle'],\n baseWeights: [1.0],\n emotionBlendEnabled: true,\n gestureBlendEnabled: true,\n timeout: 0,\n },\n ],\n\n transitions: [\n // User starts speaking\n { from: 'idle', to: 'listening', trigger: 'user_speech_start', duration: 300 },\n { from: 'speaking', to: 'listening', trigger: 'user_speech_start', duration: 200 }, // Interrupt\n\n // User stops speaking, processing\n { from: 'listening', to: 'thinking', trigger: 'transcript_ready', duration: 400 },\n\n // AI starts responding\n { from: 'thinking', to: 'speaking', trigger: 'ai_audio_start', duration: 300 },\n { from: 'idle', to: 'speaking', trigger: 'ai_audio_start', duration: 400 },\n\n // AI done\n { from: 'speaking', to: 'idle', trigger: 'ai_response_end', duration: 500 },\n\n // Timeouts\n { from: 'listening', to: 'idle', trigger: 'timeout', duration: 600 },\n { from: 'thinking', to: 'idle', trigger: 'timeout', duration: 400 },\n\n // Interrupts\n { from: 'speaking', to: 'listening', trigger: 'interrupt', duration: 150 },\n ],\n\n emotionMappings: [\n { emotion: 'happy', clip: 'emotion_happy', maxWeight: 0.7, blendSpeed: 2.0 },\n { emotion: 'sad', clip: 'emotion_sad', maxWeight: 0.6, blendSpeed: 1.5 },\n { emotion: 'angry', clip: 'emotion_angry', maxWeight: 0.5, blendSpeed: 2.5 },\n { emotion: 'fearful', clip: 'emotion_fear', maxWeight: 0.5, blendSpeed: 2.0 },\n { emotion: 'surprised', clip: 'emotion_surprised', maxWeight: 0.6, blendSpeed: 2.5 },\n { emotion: 'calm', clip: 'emotion_calm', maxWeight: 0.3, blendSpeed: 1.0 },\n { emotion: 'disgust', clip: 'emotion_disgust', maxWeight: 0.4, blendSpeed: 2.0 },\n { emotion: 'neutral', clip: 'emotion_neutral', maxWeight: 0.0, blendSpeed: 1.0 },\n ],\n\n gestureClips: ['gesture_hand_small', 'gesture_hand_medium', 'gesture_hand_large'],\n};\n","/**\n * Animation Graph\n *\n * State machine for character animation with emotion and audio-driven blending.\n * Renderer-agnostic - outputs blend weights that any 3D engine can consume.\n *\n * @example\n * ```typescript\n * import { AnimationGraph, DEFAULT_ANIMATION_CONFIG } from '@omote/core';\n *\n * const graph = new AnimationGraph(DEFAULT_ANIMATION_CONFIG);\n *\n * // Connect to voice pipeline\n * graph.on('output.update', (output) => {\n * // Apply blend weights to your 3D character\n * for (const { clip, weight } of output.blendWeights) {\n * mixer.getAction(clip).setEffectiveWeight(weight);\n * }\n * });\n *\n * // Drive from voice state\n * voiceState.on('listening', () => graph.trigger('user_speech_start'));\n * voiceState.on('thinking', () => graph.trigger('transcript_ready'));\n * voiceState.on('speaking', () => graph.trigger('ai_audio_start'));\n *\n * // Drive from emotion detection\n * emotion.on('result', ({ emotion, confidence }) => {\n * graph.setEmotion(emotion, confidence);\n * });\n *\n * // Update every frame\n * function animate(deltaTime: number) {\n * graph.update(deltaTime);\n * }\n * ```\n *\n * @module animation\n */\n\nimport { EventEmitter } from '../events';\nimport type {\n EmotionLabel,\n AnimationGraphConfig,\n AnimationGraphEvents,\n AnimationTrigger,\n AnimationOutput,\n AnimationState,\n AnimationStateName,\n BlendWeight,\n Transition,\n} from './types';\nimport { DEFAULT_ANIMATION_CONFIG } from './types';\nimport { createLogger } from '../logging';\n\nconst logger = createLogger('AnimationGraph');\n\n/**\n * Animation state machine with smooth blending\n */\nexport class AnimationGraph extends EventEmitter<AnimationGraphEvents> {\n private config: AnimationGraphConfig;\n private currentState: AnimationState;\n private previousState: AnimationState | null = null;\n\n // Transition state\n private isTransitioning: boolean = false;\n private transitionProgress: number = 0;\n private transitionDuration: number = 0;\n private transitionStartTime: number = 0;\n\n // Emotion state\n private currentEmotion: EmotionLabel | null = null;\n private emotionConfidence: number = 0;\n private emotionBlendWeight: number = 0;\n private targetEmotionWeight: number = 0;\n\n // Gesture state (audio-driven)\n private audioEnergy: number = 0;\n private gestureWeight: number = 0;\n private currentGestureClip: number = 0;\n\n // Timing\n private stateEnterTime: number = 0;\n private lastUpdateTime: number = 0;\n\n // Blend weights cache\n private cachedOutput: AnimationOutput;\n\n constructor(config: Partial<AnimationGraphConfig> = {}) {\n super();\n this.config = { ...DEFAULT_ANIMATION_CONFIG, ...config };\n\n // Find initial state\n const initialState = this.config.states.find(\n (s) => s.name === this.config.initialState\n );\n if (!initialState) {\n throw new Error(`Initial state '${this.config.initialState}' not found`);\n }\n this.currentState = initialState;\n this.stateEnterTime = Date.now();\n this.lastUpdateTime = Date.now();\n\n // Initialize cached output\n this.cachedOutput = this.computeOutput();\n\n logger.info('constructor', {\n initialState: this.config.initialState,\n stateCount: this.config.states.length,\n transitionCount: this.config.transitions.length,\n });\n }\n\n /**\n * Get current state name\n */\n get state(): AnimationStateName {\n return this.currentState.name;\n }\n\n /**\n * Get current animation output\n */\n get output(): AnimationOutput {\n return this.cachedOutput;\n }\n\n /**\n * Trigger an animation event (may cause state transition)\n */\n trigger(event: AnimationTrigger): boolean {\n // Find matching transition\n const transition = this.config.transitions.find(\n (t) =>\n t.from === this.currentState.name &&\n t.trigger === event &&\n (!t.condition || t.condition())\n );\n\n if (!transition) {\n return false;\n }\n\n this.startTransition(transition, event);\n return true;\n }\n\n /**\n * Set current emotion (from DistilHuBERT or manual)\n */\n setEmotion(emotion: EmotionLabel, confidence: number): void {\n const prevEmotion = this.currentEmotion;\n\n this.currentEmotion = emotion;\n this.emotionConfidence = Math.max(0, Math.min(1, confidence));\n\n // Find emotion mapping\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === emotion\n );\n if (mapping && this.currentState.emotionBlendEnabled) {\n this.targetEmotionWeight = mapping.maxWeight * this.emotionConfidence;\n } else {\n this.targetEmotionWeight = 0;\n }\n\n if (prevEmotion !== emotion) {\n this.emit('emotion.change', { emotion, confidence });\n }\n }\n\n /**\n * Clear current emotion\n */\n clearEmotion(): void {\n this.currentEmotion = null;\n this.emotionConfidence = 0;\n this.targetEmotionWeight = 0;\n this.emit('emotion.change', { emotion: null, confidence: 0 });\n }\n\n /**\n * Set audio energy for gesture animation (0-1)\n */\n setAudioEnergy(energy: number): void {\n this.audioEnergy = Math.max(0, Math.min(1, energy));\n }\n\n /**\n * Force transition to a specific state\n */\n setState(stateName: AnimationStateName, blendDuration: number = 300): void {\n const targetState = this.config.states.find((s) => s.name === stateName);\n if (!targetState) {\n logger.warn(`State '${stateName}' not found`);\n return;\n }\n\n if (targetState.name === this.currentState.name && !this.isTransitioning) {\n return;\n }\n\n // Create a manual transition\n const manualTransition: Transition = {\n from: this.currentState.name,\n to: stateName,\n trigger: 'timeout', // Arbitrary, not used for manual\n duration: blendDuration,\n };\n\n this.startTransition(manualTransition, 'timeout');\n }\n\n /**\n * Update animation graph (call every frame)\n * @param deltaMs Time since last update in milliseconds\n */\n update(deltaMs?: number): AnimationOutput {\n const now = Date.now();\n const dt = deltaMs ?? now - this.lastUpdateTime;\n this.lastUpdateTime = now;\n\n const dtSeconds = dt / 1000;\n\n // Update transition\n if (this.isTransitioning) {\n this.updateTransition(dtSeconds);\n }\n\n // Check timeout\n this.checkTimeout(now);\n\n // Update emotion blend\n this.updateEmotionBlend(dtSeconds);\n\n // Update gesture\n this.updateGesture(dtSeconds);\n\n // Compute and cache output\n this.cachedOutput = this.computeOutput();\n this.emit('output.update', this.cachedOutput);\n\n return this.cachedOutput;\n }\n\n /**\n * Reset to initial state\n */\n reset(): void {\n const initialState = this.config.states.find(\n (s) => s.name === this.config.initialState\n );\n if (initialState) {\n this.currentState = initialState;\n this.previousState = null;\n this.isTransitioning = false;\n this.transitionProgress = 0;\n this.stateEnterTime = Date.now();\n this.emotionBlendWeight = 0;\n this.gestureWeight = 0;\n this.cachedOutput = this.computeOutput();\n }\n }\n\n /**\n * Get all clip names used by this graph\n */\n getRequiredClips(): string[] {\n const clips = new Set<string>();\n\n // Base clips from all states\n for (const state of this.config.states) {\n for (const clip of state.baseClips) {\n clips.add(clip);\n }\n }\n\n // Emotion clips\n for (const mapping of this.config.emotionMappings) {\n clips.add(mapping.clip);\n }\n\n // Gesture clips\n for (const clip of this.config.gestureClips) {\n clips.add(clip);\n }\n\n return Array.from(clips);\n }\n\n // ─────────────────────────────────────────────────────────────────\n // Private methods\n // ─────────────────────────────────────────────────────────────────\n\n private startTransition(transition: Transition, event: AnimationTrigger): void {\n const targetState = this.config.states.find(\n (s) => s.name === transition.to\n );\n if (!targetState) {\n logger.warn(`Target state '${transition.to}' not found`);\n return;\n }\n\n const fromState = this.currentState.name;\n\n this.previousState = this.currentState;\n this.currentState = targetState;\n this.isTransitioning = true;\n this.transitionProgress = 0;\n this.transitionDuration = transition.duration;\n this.transitionStartTime = Date.now();\n this.stateEnterTime = Date.now();\n\n // Update emotion target based on new state\n if (!this.currentState.emotionBlendEnabled) {\n this.targetEmotionWeight = 0;\n }\n\n logger.debug('state transition', {\n from: fromState,\n to: targetState.name,\n trigger: event,\n duration: transition.duration,\n });\n\n this.emit('state.change', {\n from: fromState,\n to: targetState.name,\n trigger: event,\n });\n\n this.emit('transition.start', {\n from: fromState,\n to: targetState.name,\n duration: transition.duration,\n });\n }\n\n private updateTransition(dtSeconds: number): void {\n if (!this.isTransitioning || this.transitionDuration <= 0) {\n this.isTransitioning = false;\n this.transitionProgress = 1;\n return;\n }\n\n // Linear progress based on time\n const elapsed = Date.now() - this.transitionStartTime;\n this.transitionProgress = Math.min(1, elapsed / this.transitionDuration);\n\n if (this.transitionProgress >= 1) {\n this.isTransitioning = false;\n this.transitionProgress = 1;\n this.previousState = null;\n this.emit('transition.end', { state: this.currentState.name });\n }\n }\n\n private checkTimeout(now: number): void {\n if (this.isTransitioning) return;\n if (this.currentState.timeout <= 0) return;\n\n const elapsed = now - this.stateEnterTime;\n if (elapsed >= this.currentState.timeout) {\n logger.debug('timeout transition', {\n state: this.currentState.name,\n elapsed,\n timeout: this.currentState.timeout,\n });\n this.trigger('timeout');\n }\n }\n\n private updateEmotionBlend(dtSeconds: number): void {\n if (!this.currentEmotion) {\n // Decay emotion weight\n this.emotionBlendWeight = Math.max(\n 0,\n this.emotionBlendWeight - dtSeconds * 2.0\n );\n return;\n }\n\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === this.currentEmotion\n );\n const blendSpeed = mapping?.blendSpeed ?? 2.0;\n\n // Smoothly interpolate to target\n const diff = this.targetEmotionWeight - this.emotionBlendWeight;\n const maxChange = blendSpeed * dtSeconds;\n\n if (Math.abs(diff) <= maxChange) {\n this.emotionBlendWeight = this.targetEmotionWeight;\n } else {\n this.emotionBlendWeight += Math.sign(diff) * maxChange;\n }\n }\n\n private updateGesture(dtSeconds: number): void {\n if (!this.currentState.gestureBlendEnabled) {\n this.gestureWeight = Math.max(0, this.gestureWeight - dtSeconds * 4.0);\n return;\n }\n\n // Map audio energy to gesture weight\n const targetGesture =\n this.audioEnergy > this.config.gestureThreshold\n ? this.audioEnergy * this.config.gestureIntensity\n : 0;\n\n // Smooth the gesture weight\n const diff = targetGesture - this.gestureWeight;\n const blendSpeed = 8.0; // Fast response\n const maxChange = blendSpeed * dtSeconds;\n\n if (Math.abs(diff) <= maxChange) {\n this.gestureWeight = targetGesture;\n } else {\n this.gestureWeight += Math.sign(diff) * maxChange;\n }\n\n // Select gesture clip based on intensity\n const clipCount = this.config.gestureClips.length;\n if (clipCount > 0) {\n this.currentGestureClip = Math.min(\n clipCount - 1,\n Math.floor(this.gestureWeight * clipCount)\n );\n }\n }\n\n private computeOutput(): AnimationOutput {\n const blendWeights: BlendWeight[] = [];\n\n // Smooth transition weight (ease in-out)\n const t = this.transitionProgress;\n const transitionWeight = t * t * (3 - 2 * t); // smoothstep\n\n // Previous state clips (fading out)\n if (this.previousState && this.isTransitioning) {\n const fadeOut = 1 - transitionWeight;\n for (let i = 0; i < this.previousState.baseClips.length; i++) {\n const clip = this.previousState.baseClips[i];\n const baseWeight = this.previousState.baseWeights[i] ?? 1.0;\n blendWeights.push({\n clip,\n weight: baseWeight * fadeOut,\n speed: 1.0,\n time: 0,\n });\n }\n }\n\n // Current state clips (fading in or full)\n const fadeIn = this.isTransitioning ? transitionWeight : 1.0;\n for (let i = 0; i < this.currentState.baseClips.length; i++) {\n const clip = this.currentState.baseClips[i];\n const baseWeight = this.currentState.baseWeights[i] ?? 1.0;\n blendWeights.push({\n clip,\n weight: baseWeight * fadeIn,\n speed: 1.0,\n time: 0,\n });\n }\n\n // Emotion overlay\n if (this.currentEmotion && this.emotionBlendWeight > 0.01) {\n const mapping = this.config.emotionMappings.find(\n (m) => m.emotion === this.currentEmotion\n );\n if (mapping) {\n blendWeights.push({\n clip: mapping.clip,\n weight: this.emotionBlendWeight,\n speed: 1.0,\n time: 0,\n });\n }\n }\n\n // Gesture layer\n if (this.gestureWeight > 0.01 && this.config.gestureClips.length > 0) {\n const gestureClip = this.config.gestureClips[this.currentGestureClip];\n blendWeights.push({\n clip: gestureClip,\n weight: this.gestureWeight,\n speed: 1.0 + this.audioEnergy * 0.5, // Faster with more energy\n time: 0,\n });\n }\n\n return {\n state: this.currentState.name,\n blendWeights,\n activeEmotion: this.emotionBlendWeight > 0.01 ? this.currentEmotion : null,\n gestureIntensity: this.gestureWeight,\n isTransitioning: this.isTransitioning,\n transitionProgress: this.transitionProgress,\n };\n }\n}\n","/**\n * Audio Energy Analysis\n *\n * Utilities for extracting energy/loudness from audio for gesture animation.\n *\n * @module animation\n */\n\n/**\n * Calculate RMS (Root Mean Square) energy from audio samples\n * @param samples Audio samples (Float32Array, normalized -1 to 1)\n * @returns RMS energy value (0 to 1)\n */\nexport function calculateRMS(samples: Float32Array): number {\n if (samples.length === 0) return 0;\n\n let sumSquares = 0;\n for (let i = 0; i < samples.length; i++) {\n sumSquares += samples[i] * samples[i];\n }\n\n return Math.sqrt(sumSquares / samples.length);\n}\n\n/**\n * Calculate peak amplitude from audio samples\n * @param samples Audio samples (Float32Array, normalized -1 to 1)\n * @returns Peak amplitude (0 to 1)\n */\nexport function calculatePeak(samples: Float32Array): number {\n let peak = 0;\n for (let i = 0; i < samples.length; i++) {\n const abs = Math.abs(samples[i]);\n if (abs > peak) peak = abs;\n }\n return peak;\n}\n\n/**\n * Smoothed energy analyzer for gesture animation\n */\nexport class AudioEnergyAnalyzer {\n private smoothedRMS: number = 0;\n private smoothedPeak: number = 0;\n private readonly smoothingFactor: number;\n private readonly noiseFloor: number;\n\n /**\n * @param smoothingFactor How much to smooth (0 = no smoothing, 1 = infinite smoothing). Default 0.85\n * @param noiseFloor Minimum energy threshold to consider as signal. Default 0.01\n */\n constructor(smoothingFactor: number = 0.85, noiseFloor: number = 0.01) {\n this.smoothingFactor = Math.max(0, Math.min(0.99, smoothingFactor));\n this.noiseFloor = noiseFloor;\n }\n\n /**\n * Process audio samples and return smoothed energy values\n * @param samples Audio samples (Float32Array)\n * @returns Object with rms and peak values\n */\n process(samples: Float32Array): { rms: number; peak: number; energy: number } {\n const instantRMS = calculateRMS(samples);\n const instantPeak = calculatePeak(samples);\n\n // Apply noise gate\n const gatedRMS = instantRMS > this.noiseFloor ? instantRMS : 0;\n const gatedPeak = instantPeak > this.noiseFloor ? instantPeak : 0;\n\n // Smooth the values (exponential moving average)\n // Attack fast (when getting louder), release slower\n if (gatedRMS > this.smoothedRMS) {\n // Fast attack\n this.smoothedRMS =\n this.smoothedRMS * 0.5 + gatedRMS * 0.5;\n } else {\n // Slow release\n this.smoothedRMS =\n this.smoothedRMS * this.smoothingFactor +\n gatedRMS * (1 - this.smoothingFactor);\n }\n\n if (gatedPeak > this.smoothedPeak) {\n this.smoothedPeak = this.smoothedPeak * 0.3 + gatedPeak * 0.7;\n } else {\n this.smoothedPeak =\n this.smoothedPeak * this.smoothingFactor +\n gatedPeak * (1 - this.smoothingFactor);\n }\n\n // Combined energy (weighted average of RMS and peak)\n // RMS is more stable, peak catches transients\n const energy = this.smoothedRMS * 0.7 + this.smoothedPeak * 0.3;\n\n return {\n rms: this.smoothedRMS,\n peak: this.smoothedPeak,\n energy: Math.min(1, energy * 2), // Scale up and clamp\n };\n }\n\n /**\n * Reset analyzer state\n */\n reset(): void {\n this.smoothedRMS = 0;\n this.smoothedPeak = 0;\n }\n\n /**\n * Get current smoothed RMS value\n */\n get rms(): number {\n return this.smoothedRMS;\n }\n\n /**\n * Get current smoothed peak value\n */\n get peak(): number {\n return this.smoothedPeak;\n }\n}\n\n/**\n * Extract emphasis points from audio (for gesture timing)\n *\n * Detects sudden increases in energy that correspond to speech emphasis.\n */\nexport class EmphasisDetector {\n private energyHistory: number[] = [];\n private readonly historySize: number;\n private readonly emphasisThreshold: number;\n\n /**\n * @param historySize Number of frames to track. Default 10\n * @param emphasisThreshold Minimum energy increase to count as emphasis. Default 0.15\n */\n constructor(historySize: number = 10, emphasisThreshold: number = 0.15) {\n this.historySize = historySize;\n this.emphasisThreshold = emphasisThreshold;\n }\n\n /**\n * Process energy value and detect emphasis\n * @param energy Current energy value (0-1)\n * @returns Object with isEmphasis flag and emphasisStrength\n */\n process(energy: number): { isEmphasis: boolean; emphasisStrength: number } {\n this.energyHistory.push(energy);\n if (this.energyHistory.length > this.historySize) {\n this.energyHistory.shift();\n }\n\n if (this.energyHistory.length < 3) {\n return { isEmphasis: false, emphasisStrength: 0 };\n }\n\n // Calculate average of previous frames (excluding current)\n const prevFrames = this.energyHistory.slice(0, -1);\n const avgPrev = prevFrames.reduce((a, b) => a + b, 0) / prevFrames.length;\n\n // Compare current to average\n const increase = energy - avgPrev;\n const isEmphasis = increase > this.emphasisThreshold;\n\n return {\n isEmphasis,\n emphasisStrength: isEmphasis ? Math.min(1, increase / 0.3) : 0,\n };\n }\n\n /**\n * Reset detector state\n */\n reset(): void {\n this.energyHistory = [];\n }\n}\n","/**\r\n * ProceduralLifeLayer - Renderer-agnostic procedural animation system\r\n *\r\n * Outputs per-frame blendshape values and head deltas for organic life-like\r\n * animation. No Three.js, no React, no R3F — just math.\r\n *\r\n * Implements research-based eye behavior, blinks, gaze breaks, microsaccades,\r\n * breathing/postural sway, and simplex noise-driven brow drift.\r\n *\r\n * Research sources:\r\n * - Blink frequency: log-normal IBI (mean=5.97s, SD(log)=0.89), PMC3565584\r\n * - Blink shape: asymmetric (92ms close, 242ms open, 3:1 ratio), PMC4043155\r\n * - Saccade latency: ~200ms, duration 20-200ms\r\n * - Microsaccades: ~1/second, amplitude 0.02-0.05, Scholarpedia\r\n * - Fixation duration: 200-350ms, Nature Scientific Reports\r\n * - Conversational gaze: Kendon (1967), Argyle & Cook (1976)\r\n * - Brow noise: NVIDIA Audio2Face, Unreal MetaHuman layered procedural animation\r\n *\r\n * @category Animation\r\n *\r\n * @example\r\n * ```typescript\r\n * import { ProceduralLifeLayer } from '@omote/core';\r\n *\r\n * const lifeLayer = new ProceduralLifeLayer();\r\n *\r\n * // In animation loop:\r\n * const output = lifeLayer.update(delta, {\r\n * eyeTargetX: normalizedX, // -1..1 from camera math\r\n * eyeTargetY: normalizedY,\r\n * audioEnergy: energy, // 0-1 from AudioEnergyAnalyzer\r\n * isSpeaking: true,\r\n * state: 'speaking', // conversational state for gaze behavior\r\n * });\r\n *\r\n * // Apply blendshapes to mesh\r\n * for (const [name, value] of Object.entries(output.blendshapes)) {\r\n * const idx = mesh.morphTargetDictionary?.[name];\r\n * if (idx !== undefined) mesh.morphTargetInfluences![idx] = value;\r\n * }\r\n *\r\n * // Apply head delta to head bone\r\n * headBone.rotation.y += output.headDelta.yaw;\r\n * headBone.rotation.x += output.headDelta.pitch;\r\n * ```\r\n */\r\n\r\nimport { createNoise2D } from 'simplex-noise';\r\nimport { ARKIT_BLENDSHAPES } from '../inference/blendshapeUtils';\r\nimport { createLogger } from '../logging';\r\n\r\nconst logger = createLogger('ProceduralLifeLayer');\r\n\r\nconst simplex2d = createNoise2D();\r\n\r\n/** Pre-computed blendshape name → ARKit index map */\r\nconst LIFE_BS_INDEX = new Map<string, number>();\r\nfor (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {\r\n LIFE_BS_INDEX.set(ARKIT_BLENDSHAPES[i], i);\r\n}\r\n\r\n/**\r\n * Configuration for ProceduralLifeLayer\r\n */\r\nexport interface LifeLayerConfig {\r\n /** Seconds between blinks [min, max]. Default: [2.5, 6] */\r\n blinkIntervalRange?: [number, number];\r\n /** Seconds between gaze breaks [min, max]. Default: [3, 8] */\r\n gazeBreakIntervalRange?: [number, number];\r\n /** Gaze break deviation range [min, max]. Default: [0.15, 0.4] */\r\n gazeBreakAmplitudeRange?: [number, number];\r\n /** Eye micro-motion noise amplitude (0 to disable). Default: 0.06 */\r\n eyeNoiseAmplitude?: number;\r\n /** Base simplex noise amplitude for brow drift. Default: 0.30 */\r\n browNoiseAmplitude?: number;\r\n /** Multiply brow noise when speaking. Default: 2.0 */\r\n browNoiseSpeechMultiplier?: number;\r\n /** Breathing rate in Hz (0.25 = 15 breaths/min). Default: 0.25 */\r\n breathingRate?: number;\r\n /** Postural sway amplitude in radians. Default: 0.002 */\r\n posturalSwayAmplitude?: number;\r\n /** Max eye movement from center (0-1). Default: 0.8 */\r\n eyeMaxDeviation?: number;\r\n /** Eye smoothing factor (higher = faster response). Default: 15 */\r\n eyeSmoothing?: number;\r\n}\r\n\r\n/** Conversational state for state-dependent gaze behavior */\r\nexport type ConversationalState = 'idle' | 'listening' | 'thinking' | 'speaking';\r\n\r\n/**\r\n * Per-frame input to the life layer\r\n */\r\nexport interface LifeLayerInput {\r\n /** Normalized eye target X: -1 (left) to 1 (right). Consumer computes from camera. */\r\n eyeTargetX?: number;\r\n /** Normalized eye target Y: -1 (down) to 1 (up). Consumer computes from camera. */\r\n eyeTargetY?: number;\r\n /** Audio energy 0-1 (from AudioEnergyAnalyzer). Drives brow noise amplitude. */\r\n audioEnergy?: number;\r\n /** Whether avatar is speaking. Multiplies brow noise amplitude. */\r\n isSpeaking?: boolean;\r\n /** Conversational state for gaze behavior (idle/listening/thinking/speaking) */\r\n state?: ConversationalState;\r\n}\r\n\r\n/**\r\n * Per-frame output from the life layer\r\n */\r\nexport interface LifeLayerOutput {\r\n /** Blendshape values to SET directly on mesh (eyes, brows, cheeks). */\r\n blendshapes: Record<string, number>;\r\n /** Head rotation deltas in radians. Consumer adds to head bone rotation. */\r\n headDelta: { yaw: number; pitch: number };\r\n}\r\n\r\n// Blink phase constants\r\nconst PHASE_OPEN = 0;\r\nconst PHASE_CLOSING = 1;\r\nconst PHASE_CLOSED = 2;\r\nconst PHASE_OPENING = 3;\r\n\r\n// Blink timing (PMC4043155: asymmetric 3:1 ratio)\r\nconst BLINK_CLOSE_DURATION = 0.092; // 92ms (fast close)\r\nconst BLINK_HOLD_DURATION = 0.04; // 40ms hold\r\nconst BLINK_OPEN_DURATION = 0.242; // 242ms (slow open)\r\nconst BLINK_ASYMMETRY_DELAY = 0.008; // 8ms offset between eyes\r\n\r\n// Log-normal blink interval parameters (PMC3565584)\r\nconst BLINK_IBI_MU = Math.log(5.97); // mean IBI = 5.97s\r\nconst BLINK_IBI_SIGMA = 0.89; // SD(log) = 0.89\r\n\r\n// Gaze break phase timing\r\nconst GAZE_BREAK_DURATION = 0.12; // 120ms to glance away\r\nconst GAZE_BREAK_HOLD_DURATION = 0.3; // 300ms hold\r\nconst GAZE_BREAK_RETURN_DURATION = 0.15; // 150ms to return\r\n\r\n/**\r\n * State-dependent conversational gaze parameters\r\n *\r\n * Based on Kendon (1967) and Argyle & Cook (1976):\r\n * - Listeners maintain high eye contact (~75%)\r\n * - Thinkers avert gaze frequently (~17% contact)\r\n * - Speakers maintain moderate contact (~45%)\r\n */\r\nconst GAZE_STATE_PARAMS: Record<ConversationalState, {\r\n interval: [number, number];\r\n amplitude: [number, number];\r\n}> = {\r\n idle: { interval: [2, 5], amplitude: [0.15, 0.4] },\r\n listening: { interval: [4, 10], amplitude: [0.1, 0.25] },\r\n thinking: { interval: [1, 3], amplitude: [0.2, 0.5] },\r\n speaking: { interval: [2, 6], amplitude: [0.15, 0.35] },\r\n};\r\n\r\n// Eye micro-motion noise frequencies (Hz)\r\nconst EYE_NOISE_X_FREQ = 0.8;\r\nconst EYE_NOISE_Y_FREQ = 0.6;\r\nconst EYE_NOISE_X_PHASE = 73.1;\r\nconst EYE_NOISE_Y_PHASE = 91.7;\r\n\r\n// Brow noise frequencies (Hz)\r\nconst BROW_INNER_UP_FREQ = 0.4;\r\nconst BROW_OUTER_LEFT_FREQ = 0.35;\r\nconst BROW_OUTER_RIGHT_FREQ = 0.38;\r\nconst BROW_DOWN_FREQ = 0.3;\r\n\r\n// Brow noise phase offsets\r\nconst BROW_INNER_UP_PHASE = 0;\r\nconst BROW_OUTER_LEFT_PHASE = 17.3;\r\nconst BROW_OUTER_RIGHT_PHASE = 31.7;\r\nconst BROW_DOWN_LEFT_PHASE = 47.1;\r\nconst BROW_DOWN_RIGHT_PHASE = 59.3;\r\n\r\n// Emphasis detection\r\nconst EMPHASIS_ENERGY_THRESHOLD = 0.3;\r\nconst EMPHASIS_DECAY_RATE = 4.0;\r\n\r\nfunction clamp(v: number, min: number, max: number): number {\r\n return v < min ? min : v > max ? max : v;\r\n}\r\n\r\nfunction randomRange(min: number, max: number): number {\r\n return min + Math.random() * (max - min);\r\n}\r\n\r\nfunction smoothStep(t: number): number {\r\n return t * t * (3 - 2 * t);\r\n}\r\n\r\nfunction softClamp(v: number, max: number): number {\r\n return Math.tanh(v / max) * max;\r\n}\r\n\r\n/**\r\n * Sample from a log-normal distribution using Box-Muller transform.\r\n * PMC3565584: mean IBI = 5.97s, log-space SD = 0.89\r\n */\r\nfunction sampleLogNormal(mu: number, sigma: number): number {\r\n const u1 = Math.random();\r\n const u2 = Math.random();\r\n const z = Math.sqrt(-2 * Math.log(u1 || 1e-10)) * Math.cos(2 * Math.PI * u2);\r\n return Math.exp(mu + sigma * z);\r\n}\r\n\r\n/**\r\n * ProceduralLifeLayer - Renderer-agnostic procedural animation\r\n *\r\n * Generates per-frame blendshape values and head rotation deltas\r\n * for natural eye behavior, blinks, brow movement, and breathing.\r\n */\r\nexport class ProceduralLifeLayer {\r\n // Config\r\n private blinkIntervalRange: [number, number];\r\n private useLogNormalBlinks: boolean;\r\n private gazeBreakIntervalRange: [number, number];\r\n private gazeBreakAmplitudeRange: [number, number];\r\n private eyeNoiseAmplitude: number;\r\n private browNoiseAmplitude: number;\r\n private browNoiseSpeechMultiplier: number;\r\n private breathingRate: number;\r\n private posturalSwayAmplitude: number;\r\n private eyeMaxDeviation: number;\r\n private eyeSmoothing: number;\r\n\r\n // Blink state\r\n private blinkTimer = 0;\r\n private blinkInterval: number;\r\n private blinkPhase = PHASE_OPEN;\r\n private blinkProgress = 0;\r\n private asymmetryRight = 0.97;\r\n private smoothedBlinkLeft = 0;\r\n private smoothedBlinkRight = 0;\r\n\r\n // Eye contact (smoothed)\r\n private smoothedEyeX = 0;\r\n private smoothedEyeY = 0;\r\n\r\n // Eye micro-motion\r\n private eyeNoiseTime = 0;\r\n\r\n // Gaze break state\r\n private gazeBreakTimer = 0;\r\n private gazeBreakInterval: number;\r\n private gazeBreakPhase = PHASE_OPEN;\r\n private gazeBreakProgress = 0;\r\n private gazeBreakTargetX = 0;\r\n private gazeBreakTargetY = 0;\r\n private gazeBreakCurrentX = 0;\r\n private gazeBreakCurrentY = 0;\r\n\r\n // Conversational state for gaze\r\n private currentState: ConversationalState | null = null;\r\n\r\n // Breathing / postural sway\r\n private microMotionTime = 0;\r\n private breathingPhase = 0;\r\n\r\n // Brow noise\r\n private noiseTime = 0;\r\n private previousEnergy = 0;\r\n private emphasisLevel = 0;\r\n\r\n // Pre-allocated output object (reused each frame to avoid GC pressure)\r\n private readonly _outputBlendshapes: Record<string, number>;\r\n\r\n constructor(config?: LifeLayerConfig) {\r\n this.blinkIntervalRange = config?.blinkIntervalRange ?? [2.5, 6];\r\n this.useLogNormalBlinks = !config?.blinkIntervalRange; // log-normal only when using defaults\r\n this.gazeBreakIntervalRange = config?.gazeBreakIntervalRange ?? [3, 8];\r\n this.gazeBreakAmplitudeRange = config?.gazeBreakAmplitudeRange ?? [0.15, 0.4];\r\n this.eyeNoiseAmplitude = config?.eyeNoiseAmplitude ?? 0.06;\r\n this.browNoiseAmplitude = config?.browNoiseAmplitude ?? 0.12;\r\n this.browNoiseSpeechMultiplier = config?.browNoiseSpeechMultiplier ?? 1.5;\r\n this.breathingRate = config?.breathingRate ?? 0.25;\r\n this.posturalSwayAmplitude = config?.posturalSwayAmplitude ?? 0.002;\r\n this.eyeMaxDeviation = config?.eyeMaxDeviation ?? 0.8;\r\n this.eyeSmoothing = config?.eyeSmoothing ?? 15;\r\n\r\n // Pre-allocate output blendshapes object with all 52 keys\r\n this._outputBlendshapes = {};\r\n for (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {\r\n this._outputBlendshapes[ARKIT_BLENDSHAPES[i]] = 0;\r\n }\r\n\r\n // Initialize with random intervals\r\n this.blinkInterval = this.nextBlinkInterval();\r\n this.gazeBreakInterval = randomRange(...this.gazeBreakIntervalRange);\r\n\r\n logger.debug('constructor', {\r\n blinkIntervalRange: this.blinkIntervalRange,\r\n useLogNormalBlinks: this.useLogNormalBlinks,\r\n gazeBreakIntervalRange: this.gazeBreakIntervalRange,\r\n eyeNoiseAmplitude: this.eyeNoiseAmplitude,\r\n browNoiseAmplitude: this.browNoiseAmplitude,\r\n breathingRate: this.breathingRate,\r\n });\r\n }\r\n\r\n /**\r\n * Update the life layer and produce output for this frame.\r\n *\r\n * @param delta - Time since last frame in seconds\r\n * @param input - Per-frame input (eye target, audio energy, speaking state)\r\n * @returns Blendshape values and head rotation deltas\r\n */\r\n update(delta: number, input?: LifeLayerInput): LifeLayerOutput {\r\n const eyeTargetX = input?.eyeTargetX ?? 0;\r\n const eyeTargetY = input?.eyeTargetY ?? 0;\r\n const audioEnergy = input?.audioEnergy ?? 0;\r\n const isSpeaking = input?.isSpeaking ?? false;\r\n\r\n // Update conversational state\r\n this.currentState = input?.state ?? null;\r\n\r\n // Cap delta to prevent instant snaps when returning from backgrounded tab\r\n const safeDelta = Math.min(delta, 0.1);\r\n\r\n // Reuse pre-allocated output object — zero all values first\r\n const blendshapes = this._outputBlendshapes;\r\n for (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {\r\n blendshapes[ARKIT_BLENDSHAPES[i]] = 0;\r\n }\r\n\r\n // =====================================================================\r\n // BLINKS\r\n // =====================================================================\r\n this.updateBlinks(delta);\r\n\r\n const blinkSmoothing = 45;\r\n const blinkValues = this.getBlinkValues();\r\n this.smoothedBlinkLeft += (blinkValues.left - this.smoothedBlinkLeft) * Math.min(1, safeDelta * blinkSmoothing);\r\n this.smoothedBlinkRight += (blinkValues.right - this.smoothedBlinkRight) * Math.min(1, safeDelta * blinkSmoothing);\r\n\r\n blendshapes['eyeBlinkLeft'] = this.smoothedBlinkLeft;\r\n blendshapes['eyeBlinkRight'] = this.smoothedBlinkRight;\r\n\r\n // =====================================================================\r\n // EYE CONTACT (smooth toward target)\r\n // =====================================================================\r\n this.smoothedEyeX += (eyeTargetX - this.smoothedEyeX) * Math.min(1, safeDelta * this.eyeSmoothing);\r\n this.smoothedEyeY += (eyeTargetY - this.smoothedEyeY) * Math.min(1, safeDelta * this.eyeSmoothing);\r\n\r\n // =====================================================================\r\n // EYE MICRO-MOTION (continuous simplex noise replaces discrete saccades)\r\n // =====================================================================\r\n this.eyeNoiseTime += delta;\r\n const microMotion = this.getEyeMicroMotion();\r\n\r\n // =====================================================================\r\n // GAZE BREAKS\r\n // =====================================================================\r\n this.updateGazeBreaks(delta);\r\n\r\n // =====================================================================\r\n // COMBINED EYE DIRECTION\r\n // =====================================================================\r\n const finalEyeX = this.smoothedEyeX + this.gazeBreakCurrentX + microMotion.x;\r\n const finalEyeY = this.smoothedEyeY + this.gazeBreakCurrentY + microMotion.y;\r\n\r\n // Soft clamp: tanh compression avoids hard boundary pop\r\n const clampedX = softClamp(finalEyeX, this.eyeMaxDeviation);\r\n const clampedY = softClamp(finalEyeY, this.eyeMaxDeviation);\r\n\r\n // Convert to blendshape values with smooth zero-crossing\r\n const deadZone = 0.02;\r\n const lookRight = clampedX > deadZone ? clampedX\r\n : clampedX > 0 ? clampedX * (clampedX / deadZone) : 0;\r\n const lookLeft = clampedX < -deadZone ? -clampedX\r\n : clampedX < 0 ? -clampedX * (-clampedX / deadZone) : 0;\r\n const lookUp = clampedY > deadZone ? clampedY\r\n : clampedY > 0 ? clampedY * (clampedY / deadZone) : 0;\r\n const lookDown = clampedY < -deadZone ? -clampedY\r\n : clampedY < 0 ? -clampedY * (-clampedY / deadZone) : 0;\r\n\r\n // Conjugate pairing: both eyes move together naturally\r\n blendshapes['eyeLookInLeft'] = lookRight;\r\n blendshapes['eyeLookOutLeft'] = lookLeft;\r\n blendshapes['eyeLookInRight'] = lookLeft;\r\n blendshapes['eyeLookOutRight'] = lookRight;\r\n blendshapes['eyeLookUpLeft'] = lookUp;\r\n blendshapes['eyeLookUpRight'] = lookUp;\r\n blendshapes['eyeLookDownLeft'] = lookDown;\r\n blendshapes['eyeLookDownRight'] = lookDown;\r\n\r\n // =====================================================================\r\n // BROW NOISE (Simplex-driven organic drift)\r\n // =====================================================================\r\n this.updateBrowNoise(delta, audioEnergy, isSpeaking, blendshapes);\r\n\r\n // =====================================================================\r\n // BREATHING + POSTURAL SWAY\r\n // =====================================================================\r\n this.microMotionTime += delta;\r\n this.breathingPhase += delta * this.breathingRate * Math.PI * 2;\r\n\r\n const breathingY = Math.sin(this.breathingPhase) * 0.003;\r\n const swayAmp = this.posturalSwayAmplitude;\r\n const swayX = Math.sin(this.microMotionTime * 0.7) * swayAmp +\r\n Math.sin(this.microMotionTime * 1.3) * swayAmp * 0.5;\r\n const swayY = Math.sin(this.microMotionTime * 0.5) * swayAmp * 0.75 +\r\n Math.sin(this.microMotionTime * 0.9) * swayAmp * 0.5;\r\n\r\n // =====================================================================\r\n // BREATHING BLENDSHAPES (subtle jaw/nose movement)\r\n // =====================================================================\r\n const breathVal = Math.sin(this.breathingPhase);\r\n if (breathVal > 0) {\r\n blendshapes['jawOpen'] = breathVal * 0.015;\r\n blendshapes['noseSneerLeft'] = breathVal * 0.008;\r\n blendshapes['noseSneerRight'] = breathVal * 0.008;\r\n }\r\n\r\n return {\r\n blendshapes,\r\n headDelta: {\r\n yaw: swayX,\r\n pitch: breathingY + swayY,\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Write life layer output directly to a Float32Array[52] in ARKIT_BLENDSHAPES order.\r\n *\r\n * Includes micro-jitter (0.4% amplitude simplex noise on all channels) to\r\n * break uncanny stillness on undriven channels.\r\n *\r\n * @param delta - Time since last frame in seconds\r\n * @param input - Per-frame input\r\n * @param out - Pre-allocated Float32Array(52) to write into\r\n */\r\n updateToArray(delta: number, input: LifeLayerInput, out: Float32Array): void {\r\n out.fill(0);\r\n const result = this.update(delta, input);\r\n\r\n // Map named blendshapes to ARKit indices\r\n for (const [name, value] of Object.entries(result.blendshapes)) {\r\n const idx = LIFE_BS_INDEX.get(name);\r\n if (idx !== undefined) {\r\n out[idx] = value;\r\n }\r\n }\r\n\r\n // Micro-jitter: tiny simplex noise on all 52 channels\r\n for (let i = 0; i < 52; i++) {\r\n out[i] += simplex2d(this.noiseTime * 0.3, i * 7.13) * 0.004;\r\n }\r\n }\r\n\r\n /**\r\n * Reset all internal state to initial values.\r\n */\r\n reset(): void {\r\n logger.debug('reset');\r\n // Blink\r\n this.blinkTimer = 0;\r\n this.blinkInterval = this.nextBlinkInterval();\r\n this.blinkPhase = PHASE_OPEN;\r\n this.blinkProgress = 0;\r\n this.asymmetryRight = 0.97;\r\n this.smoothedBlinkLeft = 0;\r\n this.smoothedBlinkRight = 0;\r\n\r\n // Eye contact\r\n this.smoothedEyeX = 0;\r\n this.smoothedEyeY = 0;\r\n\r\n // Eye micro-motion\r\n this.eyeNoiseTime = 0;\r\n\r\n // Gaze break\r\n this.gazeBreakTimer = 0;\r\n this.gazeBreakInterval = randomRange(...this.gazeBreakIntervalRange);\r\n this.gazeBreakPhase = PHASE_OPEN;\r\n this.gazeBreakProgress = 0;\r\n this.gazeBreakTargetX = 0;\r\n this.gazeBreakTargetY = 0;\r\n this.gazeBreakCurrentX = 0;\r\n this.gazeBreakCurrentY = 0;\r\n\r\n // State\r\n this.currentState = null;\r\n\r\n // Breathing / sway\r\n this.microMotionTime = 0;\r\n this.breathingPhase = 0;\r\n\r\n // Brow noise\r\n this.noiseTime = 0;\r\n this.previousEnergy = 0;\r\n this.emphasisLevel = 0;\r\n }\r\n\r\n // =====================================================================\r\n // PRIVATE: Blink interval sampling\r\n // =====================================================================\r\n\r\n /**\r\n * Sample next blink interval.\r\n * Uses log-normal distribution (PMC3565584) when using default config,\r\n * or uniform random when custom blinkIntervalRange is provided.\r\n */\r\n private nextBlinkInterval(): number {\r\n if (this.useLogNormalBlinks) {\r\n const sample = sampleLogNormal(BLINK_IBI_MU, BLINK_IBI_SIGMA);\r\n return clamp(sample, 1.5, 12);\r\n }\r\n return randomRange(...this.blinkIntervalRange);\r\n }\r\n\r\n // =====================================================================\r\n // PRIVATE: Blink system\r\n // =====================================================================\r\n\r\n private updateBlinks(delta: number): void {\r\n this.blinkTimer += delta;\r\n\r\n if (this.blinkTimer >= this.blinkInterval && this.blinkPhase === PHASE_OPEN) {\r\n this.blinkPhase = PHASE_CLOSING;\r\n this.blinkProgress = 0;\r\n this.blinkTimer = 0;\r\n this.blinkInterval = this.nextBlinkInterval();\r\n this.asymmetryRight = 0.95 + Math.random() * 0.08;\r\n logger.trace('blink', { nextInterval: this.blinkInterval });\r\n }\r\n\r\n if (this.blinkPhase > PHASE_OPEN) {\r\n this.blinkProgress += delta;\r\n\r\n if (this.blinkPhase === PHASE_CLOSING) {\r\n if (this.blinkProgress >= BLINK_CLOSE_DURATION) {\r\n this.blinkPhase = PHASE_CLOSED;\r\n this.blinkProgress = 0;\r\n }\r\n } else if (this.blinkPhase === PHASE_CLOSED) {\r\n if (this.blinkProgress >= BLINK_HOLD_DURATION) {\r\n this.blinkPhase = PHASE_OPENING;\r\n this.blinkProgress = 0;\r\n }\r\n } else if (this.blinkPhase === PHASE_OPENING) {\r\n if (this.blinkProgress >= BLINK_OPEN_DURATION) {\r\n this.blinkPhase = PHASE_OPEN;\r\n this.blinkProgress = 0;\r\n }\r\n }\r\n }\r\n }\r\n\r\n private getBlinkValues(): { left: number; right: number } {\r\n if (this.blinkPhase === PHASE_OPEN) {\r\n return { left: 0, right: 0 };\r\n }\r\n\r\n if (this.blinkPhase === PHASE_CLOSING) {\r\n const t = Math.min(1, this.blinkProgress / BLINK_CLOSE_DURATION);\r\n const eased = t * t * t; // Cubic ease-in for snappy close\r\n const tRight = Math.max(0, Math.min(1, (this.blinkProgress - BLINK_ASYMMETRY_DELAY) / BLINK_CLOSE_DURATION));\r\n return {\r\n left: eased,\r\n right: tRight * tRight * tRight * this.asymmetryRight,\r\n };\r\n }\r\n\r\n if (this.blinkPhase === PHASE_CLOSED) {\r\n return { left: 1, right: this.asymmetryRight };\r\n }\r\n\r\n // PHASE_OPENING\r\n const t = Math.min(1, this.blinkProgress / BLINK_OPEN_DURATION);\r\n const eased = smoothStep(t);\r\n return {\r\n left: 1 - eased,\r\n right: (1 - eased) * this.asymmetryRight,\r\n };\r\n }\r\n\r\n // =====================================================================\r\n // PRIVATE: Eye micro-motion (continuous simplex noise)\r\n // =====================================================================\r\n\r\n private getEyeMicroMotion(): { x: number; y: number } {\r\n const amp = this.eyeNoiseAmplitude;\r\n const x = simplex2d(this.eyeNoiseTime * EYE_NOISE_X_FREQ, EYE_NOISE_X_PHASE) * amp;\r\n const y = simplex2d(this.eyeNoiseTime * EYE_NOISE_Y_FREQ, EYE_NOISE_Y_PHASE) * amp * 0.7;\r\n return { x, y };\r\n }\r\n\r\n // =====================================================================\r\n // PRIVATE: Gaze breaks (state-dependent)\r\n // =====================================================================\r\n\r\n /**\r\n * Get active gaze parameters — uses state-dependent params when\r\n * conversational state is provided, otherwise falls back to config ranges.\r\n */\r\n private getActiveGazeParams(): { interval: [number, number]; amplitude: [number, number] } {\r\n if (this.currentState && GAZE_STATE_PARAMS[this.currentState]) {\r\n return GAZE_STATE_PARAMS[this.currentState];\r\n }\r\n return {\r\n interval: this.gazeBreakIntervalRange,\r\n amplitude: this.gazeBreakAmplitudeRange,\r\n };\r\n }\r\n\r\n private updateGazeBreaks(delta: number): void {\r\n this.gazeBreakTimer += delta;\r\n\r\n if (this.gazeBreakTimer >= this.gazeBreakInterval && this.gazeBreakPhase === PHASE_OPEN) {\r\n this.gazeBreakPhase = PHASE_CLOSING; // Reusing: 1 = breaking away\r\n this.gazeBreakProgress = 0;\r\n this.gazeBreakTimer = 0;\r\n\r\n const params = this.getActiveGazeParams();\r\n const amp = randomRange(...params.amplitude);\r\n this.gazeBreakTargetX = (Math.random() - 0.5) * 2 * amp;\r\n this.gazeBreakTargetY = (Math.random() - 0.5) * amp * 0.4;\r\n this.gazeBreakInterval = randomRange(...params.interval);\r\n logger.trace('gaze break', {\r\n targetX: this.gazeBreakTargetX.toFixed(3),\r\n targetY: this.gazeBreakTargetY.toFixed(3),\r\n nextInterval: this.gazeBreakInterval.toFixed(2),\r\n state: this.currentState,\r\n });\r\n }\r\n\r\n if (this.gazeBreakPhase > PHASE_OPEN) {\r\n this.gazeBreakProgress += delta;\r\n\r\n if (this.gazeBreakPhase === 1) {\r\n // Breaking away\r\n const t = Math.min(1, this.gazeBreakProgress / GAZE_BREAK_DURATION);\r\n const eased = smoothStep(t);\r\n this.gazeBreakCurrentX = this.gazeBreakTargetX * eased;\r\n this.gazeBreakCurrentY = this.gazeBreakTargetY * eased;\r\n if (this.gazeBreakProgress >= GAZE_BREAK_DURATION) {\r\n this.gazeBreakPhase = 2;\r\n this.gazeBreakProgress = 0;\r\n }\r\n } else if (this.gazeBreakPhase === 2) {\r\n // Holding glance\r\n this.gazeBreakCurrentX = this.gazeBreakTargetX;\r\n this.gazeBreakCurrentY = this.gazeBreakTargetY;\r\n if (this.gazeBreakProgress >= GAZE_BREAK_HOLD_DURATION) {\r\n this.gazeBreakPhase = 3;\r\n this.gazeBreakProgress = 0;\r\n }\r\n } else if (this.gazeBreakPhase === 3) {\r\n // Returning to focus\r\n const t = Math.min(1, this.gazeBreakProgress / GAZE_BREAK_RETURN_DURATION);\r\n const eased = smoothStep(t);\r\n this.gazeBreakCurrentX = this.gazeBreakTargetX * (1 - eased);\r\n this.gazeBreakCurrentY = this.gazeBreakTargetY * (1 - eased);\r\n if (this.gazeBreakProgress >= GAZE_BREAK_RETURN_DURATION) {\r\n this.gazeBreakPhase = PHASE_OPEN;\r\n this.gazeBreakProgress = 0;\r\n this.gazeBreakCurrentX = 0;\r\n this.gazeBreakCurrentY = 0;\r\n }\r\n }\r\n } else {\r\n this.gazeBreakCurrentX = 0;\r\n this.gazeBreakCurrentY = 0;\r\n }\r\n }\r\n\r\n // =====================================================================\r\n // PRIVATE: Brow noise (simplex-driven organic drift)\r\n // =====================================================================\r\n\r\n private updateBrowNoise(\r\n delta: number,\r\n audioEnergy: number,\r\n isSpeaking: boolean,\r\n blendshapes: Record<string, number>,\r\n ): void {\r\n this.noiseTime += delta;\r\n\r\n // Emphasis detection: spike on energy transients\r\n const energyDelta = audioEnergy - this.previousEnergy;\r\n if (energyDelta > EMPHASIS_ENERGY_THRESHOLD) {\r\n this.emphasisLevel = 1.0;\r\n }\r\n this.emphasisLevel = Math.max(0, this.emphasisLevel - delta * EMPHASIS_DECAY_RATE);\r\n this.previousEnergy = audioEnergy;\r\n\r\n // Speech multiplier\r\n const speechMul = (isSpeaking && audioEnergy > 0) ? this.browNoiseSpeechMultiplier : 1.0;\r\n const amp = this.browNoiseAmplitude * speechMul;\r\n\r\n // browInnerUp: slow drift + emphasis spike\r\n const innerUpNoise = simplex2d(this.noiseTime * BROW_INNER_UP_FREQ, BROW_INNER_UP_PHASE);\r\n const innerUpBase = (innerUpNoise * 0.5 + 0.5) * amp * 0.83;\r\n const innerUpEmphasis = this.emphasisLevel * 0.25;\r\n blendshapes['browInnerUp'] = clamp(innerUpBase + innerUpEmphasis, 0, 1);\r\n\r\n // browOuterUpLeft\r\n const outerLeftNoise = simplex2d(this.noiseTime * BROW_OUTER_LEFT_FREQ, BROW_OUTER_LEFT_PHASE);\r\n blendshapes['browOuterUpLeft'] = clamp((outerLeftNoise * 0.5 + 0.5) * amp * 0.5, 0, 1);\r\n\r\n // browOuterUpRight\r\n const outerRightNoise = simplex2d(this.noiseTime * BROW_OUTER_RIGHT_FREQ, BROW_OUTER_RIGHT_PHASE);\r\n blendshapes['browOuterUpRight'] = clamp((outerRightNoise * 0.5 + 0.5) * amp * 0.5, 0, 1);\r\n\r\n // browDownLeft (subtle furrow)\r\n const downLeftNoise = simplex2d(this.noiseTime * BROW_DOWN_FREQ, BROW_DOWN_LEFT_PHASE);\r\n blendshapes['browDownLeft'] = clamp((downLeftNoise * 0.5 + 0.5) * amp * 0.33, 0, 1);\r\n\r\n // browDownRight (subtle furrow)\r\n const downRightNoise = simplex2d(this.noiseTime * BROW_DOWN_FREQ, BROW_DOWN_RIGHT_PHASE);\r\n blendshapes['browDownRight'] = clamp((downRightNoise * 0.5 + 0.5) * amp * 0.33, 0, 1);\r\n }\r\n}\r\n","/**\n * Body Animation — Renderer-agnostic interfaces and utilities.\n *\n * Defines the contract for body animation controllers that each renderer\n * adapter (@omote/three, @omote/babylon, @omote/r3f) implements natively.\n *\n * Also provides the shared bone filtering logic used during animation\n * retargeting — stripping head/neck/eye tracks so body animations don't\n * conflict with the face pipeline (FaceCompositor, gaze, ProceduralLifeLayer).\n *\n * @module animation\n */\n\n// ---------------------------------------------------------------------------\n// AnimationController — renderer-agnostic interface\n// ---------------------------------------------------------------------------\n\n/**\n * Renderer-agnostic animation controller interface.\n *\n * Each renderer adapter implements this against its native animation system:\n * - @omote/three → THREE.AnimationMixer + AnimationAction\n * - @omote/babylon → Babylon.js AnimationGroup\n * - @omote/r3f → React hook wrapping the Three.js implementation\n *\n * Python/Node ports implement this against their own runtimes.\n */\nexport interface AnimationController {\n /** Play an animation by id. */\n play(id: string, options?: { fadeInDuration?: number }): void;\n /** Stop all playing animations. */\n stop(fadeOutDuration?: number): void;\n /** Crossfade from current animation to target. */\n crossfadeTo(id: string, duration?: number): void;\n /** Check if a specific animation is currently playing. */\n isPlaying(id: string): boolean;\n /** Check if an animation with this id is loaded. */\n hasAnimation(id: string): boolean;\n /** List of loaded animation ids. */\n readonly availableAnimations: string[];\n}\n\n// ---------------------------------------------------------------------------\n// AnimationSource — describes an animation to load\n// ---------------------------------------------------------------------------\n\n/**\n * Describes an external animation asset to load and configure.\n * Renderer-agnostic — loaders are adapter-specific.\n */\nexport interface AnimationSource {\n /** Unique identifier for this animation. */\n id: string;\n /** URL to the animation file (FBX, GLB, etc.). */\n url: string;\n /** Clip name within the file (if it contains multiple clips). */\n clipName?: string;\n /** Playback options. */\n options?: AnimationSourceOptions;\n}\n\nexport interface AnimationSourceOptions {\n loop?: boolean;\n timeScale?: number;\n fadeInDuration?: number;\n fadeOutDuration?: number;\n clampWhenFinished?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// BoneFilterConfig — defines which bones the face pipeline owns\n// ---------------------------------------------------------------------------\n\n/**\n * Configuration for filtering bone tracks from body animations.\n *\n * The face pipeline (FaceCompositor, gaze tracking, ProceduralLifeLayer) owns\n * certain bones (head, neck, eyes). Body animations must strip these tracks\n * to prevent conflicts.\n */\nexport interface BoneFilterConfig {\n /** Bone names owned by the face pipeline (e.g., ['Head', 'Neck', 'LeftEye', 'RightEye']). */\n proceduralBones: string[];\n /** Whether to strip .position tracks (keep only quaternion/rotation). */\n filterPositionTracks: boolean;\n /** Whether to strip morphTargetInfluences tracks. */\n filterMorphTargets: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Mixamo bone name prefix (stripped during retargeting). */\nexport const MIXAMO_PREFIX = 'mixamorig';\n\n/**\n * Bones that need position tracks preserved during retargeting.\n * Stripping finger/hand position tracks causes fingers to splay to bind pose.\n */\nexport const PRESERVE_POSITION_BONES = new Set([\n 'LeftHand', 'RightHand',\n 'LeftHandThumb1', 'LeftHandThumb2', 'LeftHandThumb3',\n 'LeftHandIndex1', 'LeftHandIndex2', 'LeftHandIndex3',\n 'LeftHandMiddle1', 'LeftHandMiddle2', 'LeftHandMiddle3',\n 'LeftHandRing1', 'LeftHandRing2', 'LeftHandRing3',\n 'LeftHandPinky1', 'LeftHandPinky2', 'LeftHandPinky3',\n 'RightHandThumb1', 'RightHandThumb2', 'RightHandThumb3',\n 'RightHandIndex1', 'RightHandIndex2', 'RightHandIndex3',\n 'RightHandMiddle1', 'RightHandMiddle2', 'RightHandMiddle3',\n 'RightHandRing1', 'RightHandRing2', 'RightHandRing3',\n 'RightHandPinky1', 'RightHandPinky2', 'RightHandPinky3',\n]);\n\n/** Default bone filter for RPM/Mixamo avatars. */\nexport const DEFAULT_BONE_FILTER: BoneFilterConfig = {\n proceduralBones: ['Head', 'Neck', 'LeftEye', 'RightEye'],\n filterPositionTracks: true,\n filterMorphTargets: true,\n};\n\n// ---------------------------------------------------------------------------\n// filterAnimationTracks — renderer-agnostic track filtering\n// ---------------------------------------------------------------------------\n\n/**\n * A generic animation track descriptor. Renderers map their native track\n * objects to this shape for filtering, then map back.\n */\nexport interface TrackDescriptor {\n /** Full track name, e.g. \"mixamorigHips.quaternion\" or \"Head.position\". */\n name: string;\n}\n\n/**\n * Filter animation tracks according to a BoneFilterConfig.\n *\n * This is the renderer-agnostic core of `retargetClip`. Renderer adapters\n * call this with their native track names and use the result to decide\n * which tracks to keep.\n *\n * @returns true if the track should be KEPT (not filtered out).\n */\nexport function shouldKeepTrack(\n trackName: string,\n config: BoneFilterConfig,\n): boolean {\n // Strip Mixamo prefix for bone name extraction\n const boneName = trackName.split('.')[0].split(MIXAMO_PREFIX).join('');\n const property = trackName.split('.').pop() ?? '';\n\n // Filter morph target tracks\n if (config.filterMorphTargets && trackName.includes('morphTargetInfluences')) {\n return false;\n }\n\n // Filter procedural bone tracks (face pipeline owns these)\n if (config.proceduralBones.includes(boneName)) {\n return false;\n }\n\n // Filter position tracks (except preserved bones like hands/fingers)\n if (config.filterPositionTracks && property === 'position') {\n return PRESERVE_POSITION_BONES.has(boneName);\n }\n\n return true;\n}\n\n/**\n * Strip Mixamo prefix from a track name.\n * \"mixamorigHips.quaternion\" → \"Hips.quaternion\"\n */\nexport function stripMixamoPrefix(trackName: string): string {\n return trackName.split(MIXAMO_PREFIX).join('');\n}\n","/**\n * FACS (Facial Action Coding System) to ARKit Blendshape Mapping\n *\n * Two static lookup tables that decompose emotions into FACS Action Units,\n * then map AUs to ARKit blendshapes. Based on Ekman's FACS research.\n *\n * @category Face\n */\n\nimport type { EmotionName } from '../emotion';\n\n/**\n * A single FACS Action Unit activation within an emotion\n */\nexport interface AUActivation {\n /** FACS Action Unit identifier (e.g. 'AU6', 'AU12') */\n au: string;\n /** Activation intensity 0-1 */\n intensity: number;\n /** Facial region: upper (brows/eyes/cheeks) or lower (mouth/jaw) */\n region: 'upper' | 'lower';\n}\n\n/**\n * Table 1: Emotion → FACS Action Units\n *\n * Maps each of the 10 SDK emotion channels to their FACS AU combinations\n * with intensity and upper/lower face region tags.\n *\n * Sources:\n * - Ekman & Friesen (1978) FACS Manual\n * - Ekman (2003) Emotions Revealed\n * - Lucey et al. (2010) Extended Cohn-Kanade dataset\n */\nexport const EMOTION_TO_AU: Record<EmotionName, AUActivation[]> = {\n joy: [\n { au: 'AU6', intensity: 0.7, region: 'upper' }, // cheek raise (Duchenne)\n { au: 'AU12', intensity: 0.8, region: 'lower' }, // lip corner pull (smile)\n ],\n anger: [\n { au: 'AU4', intensity: 0.8, region: 'upper' }, // brow lower\n { au: 'AU5', intensity: 0.4, region: 'upper' }, // upper lid raise\n { au: 'AU7', intensity: 0.3, region: 'upper' }, // lid tighten\n { au: 'AU23', intensity: 0.6, region: 'lower' }, // lip tighten\n ],\n sadness: [\n { au: 'AU1', intensity: 0.7, region: 'upper' }, // inner brow raise\n { au: 'AU4', intensity: 0.3, region: 'upper' }, // brow lower (furrow)\n { au: 'AU15', intensity: 0.5, region: 'lower' }, // lip corner depress\n ],\n fear: [\n { au: 'AU1', intensity: 0.6, region: 'upper' }, // inner brow raise\n { au: 'AU2', intensity: 0.5, region: 'upper' }, // outer brow raise\n { au: 'AU4', intensity: 0.3, region: 'upper' }, // brow lower\n { au: 'AU5', intensity: 0.5, region: 'upper' }, // upper lid raise\n { au: 'AU20', intensity: 0.4, region: 'lower' }, // lip stretch\n ],\n disgust: [\n { au: 'AU9', intensity: 0.7, region: 'upper' }, // nose wrinkle\n { au: 'AU10', intensity: 0.5, region: 'lower' }, // upper lip raise\n { au: 'AU15', intensity: 0.4, region: 'lower' }, // lip corner depress\n ],\n amazement: [\n { au: 'AU1', intensity: 0.6, region: 'upper' }, // inner brow raise\n { au: 'AU2', intensity: 0.7, region: 'upper' }, // outer brow raise\n { au: 'AU5', intensity: 0.6, region: 'upper' }, // upper lid raise\n { au: 'AU26', intensity: 0.4, region: 'lower' }, // jaw drop\n ],\n grief: [\n { au: 'AU1', intensity: 0.8, region: 'upper' }, // inner brow raise\n { au: 'AU4', intensity: 0.5, region: 'upper' }, // brow lower\n { au: 'AU6', intensity: 0.3, region: 'upper' }, // cheek raise (grief cry)\n { au: 'AU15', intensity: 0.6, region: 'lower' }, // lip corner depress\n ],\n cheekiness: [\n { au: 'AU2', intensity: 0.4, region: 'upper' }, // outer brow raise\n { au: 'AU6', intensity: 0.4, region: 'upper' }, // cheek raise\n { au: 'AU12', intensity: 0.6, region: 'lower' }, // lip corner pull (smirk)\n ],\n pain: [\n { au: 'AU4', intensity: 0.7, region: 'upper' }, // brow lower\n { au: 'AU6', intensity: 0.4, region: 'upper' }, // cheek raise (orbicularis)\n { au: 'AU7', intensity: 0.7, region: 'upper' }, // lid tighten (squint)\n { au: 'AU9', intensity: 0.5, region: 'upper' }, // nose wrinkle\n ],\n outofbreath: [\n { au: 'AU1', intensity: 0.3, region: 'upper' }, // inner brow raise\n { au: 'AU25', intensity: 0.3, region: 'lower' }, // lips part\n { au: 'AU26', intensity: 0.5, region: 'lower' }, // jaw drop\n ],\n};\n\n/**\n * Table 2: FACS Action Unit → ARKit Blendshapes\n *\n * Maps each AU to one or more ARKit blendshape channels with weight.\n *\n * Sources:\n * - Apple ARKit face tracking documentation\n * - Melinda Ozel's ARKit-to-FACS cheat sheet\n */\nexport const AU_TO_ARKIT: Record<string, { blendshape: string; weight: number }[]> = {\n 'AU1': [{ blendshape: 'browInnerUp', weight: 1.0 }],\n 'AU2': [{ blendshape: 'browOuterUpLeft', weight: 1.0 }, { blendshape: 'browOuterUpRight', weight: 1.0 }],\n 'AU4': [{ blendshape: 'browDownLeft', weight: 1.0 }, { blendshape: 'browDownRight', weight: 1.0 }],\n 'AU5': [{ blendshape: 'eyeWideLeft', weight: 1.0 }, { blendshape: 'eyeWideRight', weight: 1.0 }],\n 'AU6': [{ blendshape: 'cheekSquintLeft', weight: 1.0 }, { blendshape: 'cheekSquintRight', weight: 1.0 }],\n 'AU7': [{ blendshape: 'eyeSquintLeft', weight: 1.0 }, { blendshape: 'eyeSquintRight', weight: 1.0 }],\n 'AU9': [{ blendshape: 'noseSneerLeft', weight: 1.0 }, { blendshape: 'noseSneerRight', weight: 1.0 }],\n 'AU10': [{ blendshape: 'mouthUpperUpLeft', weight: 1.0 }, { blendshape: 'mouthUpperUpRight', weight: 1.0 }],\n 'AU12': [{ blendshape: 'mouthSmileLeft', weight: 1.0 }, { blendshape: 'mouthSmileRight', weight: 1.0 }],\n 'AU15': [{ blendshape: 'mouthFrownLeft', weight: 1.0 }, { blendshape: 'mouthFrownRight', weight: 1.0 }],\n 'AU20': [{ blendshape: 'mouthStretchLeft', weight: 1.0 }, { blendshape: 'mouthStretchRight', weight: 1.0 }],\n 'AU23': [{ blendshape: 'mouthPressLeft', weight: 1.0 }, { blendshape: 'mouthPressRight', weight: 1.0 }],\n 'AU25': [{ blendshape: 'jawOpen', weight: 0.3 }],\n 'AU26': [{ blendshape: 'jawOpen', weight: 1.0 }],\n};\n\n/**\n * All AU identifiers referenced by EMOTION_TO_AU (for validation)\n */\nexport const ALL_AUS: string[] = [...new Set(\n Object.values(EMOTION_TO_AU).flatMap(activations => activations.map(a => a.au))\n)];\n","/**\n * EmotionResolver — Resolves EmotionWeights → split upper/lower face Float32Array[52]\n *\n * Uses FACS decomposition (EMOTION_TO_AU → AU_TO_ARKIT) to produce\n * anatomically correct blendshape contributions, split by facial region\n * for the FaceCompositor's modulation strategy:\n * - Upper face: additive overlay (independent of speech)\n * - Lower face: modulates speech output\n *\n * @category Face\n */\n\nimport { EMOTION_NAMES, type EmotionName, type EmotionWeights } from '../emotion';\nimport { ARKIT_BLENDSHAPES } from '../inference/blendshapeUtils';\nimport { EMOTION_TO_AU, AU_TO_ARKIT } from './FACSMapping';\nimport { createLogger } from '../logging';\n\nconst logger = createLogger('EmotionResolver');\n\n/** Pre-computed blendshape name → ARKit index map */\nconst BS_INDEX = new Map<string, number>();\nfor (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {\n BS_INDEX.set(ARKIT_BLENDSHAPES[i], i);\n}\n\n/**\n * Resolved emotion split into upper and lower face contributions.\n *\n * WARNING: Buffers are owned by EmotionResolver and are overwritten\n * on the next resolve() call. Copy if you need to retain values.\n */\nexport interface ResolvedEmotion {\n /** 52 channels — only upper face non-zero. Valid until next resolve() call. */\n upper: Float32Array;\n /** 52 channels — only lower face non-zero. Valid until next resolve() call. */\n lower: Float32Array;\n}\n\n/**\n * Resolves EmotionWeights into upper/lower face blendshape arrays\n * using FACS Action Unit decomposition.\n */\nexport class EmotionResolver {\n private readonly upperBuffer = new Float32Array(52);\n private readonly lowerBuffer = new Float32Array(52);\n\n /**\n * Resolve emotion weights to upper/lower face blendshape contributions.\n *\n * @param weights - Emotion channel weights from EmotionController\n * @param intensity - Global intensity multiplier (0-2). Default: 1.0\n * @returns Upper and lower face blendshape arrays (52 channels each)\n */\n resolve(weights: EmotionWeights, intensity: number = 1.0): ResolvedEmotion {\n const upper = this.upperBuffer;\n const lower = this.lowerBuffer;\n upper.fill(0);\n lower.fill(0);\n\n for (const emotionName of EMOTION_NAMES) {\n const emotionWeight = weights[emotionName];\n if (!emotionWeight || emotionWeight < 0.01) continue;\n\n const auActivations = EMOTION_TO_AU[emotionName as EmotionName];\n if (!auActivations) {\n logger.warn(`Unknown emotion name with no AU mapping: \"${emotionName}\"`);\n continue;\n }\n\n for (const activation of auActivations) {\n const arkitMappings = AU_TO_ARKIT[activation.au];\n if (!arkitMappings) continue;\n\n const target = activation.region === 'upper' ? upper : lower;\n const scale = emotionWeight * activation.intensity * intensity;\n\n for (const mapping of arkitMappings) {\n const idx = BS_INDEX.get(mapping.blendshape);\n if (idx !== undefined) {\n target[idx] += mapping.weight * scale;\n }\n }\n }\n }\n\n // Clamp all values to [0, 1]\n for (let i = 0; i < 52; i++) {\n if (upper[i] > 1) upper[i] = 1;\n if (lower[i] > 1) lower[i] = 1;\n }\n\n // Return direct references — valid until next resolve() call.\n // FaceCompositor (the only caller) reads values immediately in the\n // same synchronous compose() call, so no copy is needed.\n return { upper, lower };\n }\n}\n","/**\r\n * FaceCompositor — 5-stage signal processing chain for facial animation\r\n *\r\n * Composes A2E lip sync, emotion modulation, procedural life, and character\r\n * profile into a single Float32Array[52] per frame.\r\n *\r\n * ```\r\n * BASE (A2E) → EMOTION MODULATION → PROCEDURAL LIFE → CHARACTER PROFILE → OUTPUT [0,1]\r\n * ```\r\n *\r\n * Replaces manual blendshape merging in consumer code with a single `compose()` call.\r\n *\r\n * @category Face\r\n */\r\n\r\nimport { getClock } from '../logging/Clock';\r\nimport { ARKIT_BLENDSHAPES } from '../inference/blendshapeUtils';\r\nimport type { EmotionWeights } from '../emotion';\r\nimport { ProceduralLifeLayer } from '../animation/ProceduralLifeLayer';\r\nimport type { LifeLayerInput } from '../animation/ProceduralLifeLayer';\r\nimport { EmotionResolver } from './EmotionResolver';\r\nimport { createLogger } from '../logging';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\n\r\nconst logger = createLogger('FaceCompositor');\r\n\r\n/**\r\n * Output of FaceCompositor.compose()\r\n *\r\n * WARNING: When using the internal output buffer (no `target` param),\r\n * `blendshapes` is a shared reference that is overwritten on the next\r\n * compose() call. Copy with `new Float32Array(output.blendshapes)` if\r\n * you need to retain values across frames.\r\n */\r\nexport interface FaceCompositorOutput {\r\n /**\r\n * 52 ARKit blendshape values, clamped [0,1].\r\n *\r\n * This buffer is reused across calls when no `target` parameter is\r\n * provided to compose(). Valid until the next compose() call.\r\n */\r\n blendshapes: Float32Array;\r\n /** Head rotation deltas in radians (from ProceduralLifeLayer) */\r\n headDelta: { yaw: number; pitch: number };\r\n}\r\n\r\nfunction smoothstep(t: number): number {\r\n return t * t * (3 - 2 * t);\r\n}\r\n\r\n/** Pre-computed blendshape name → LAM index map */\r\nconst BS_INDEX = new Map<string, number>();\r\nfor (let i = 0; i < ARKIT_BLENDSHAPES.length; i++) {\r\n BS_INDEX.set(ARKIT_BLENDSHAPES[i], i);\r\n}\r\n\r\n/** Index of mouthClose in ARKIT_BLENDSHAPES (for bilabial suppression) */\r\nconst IDX_MOUTH_CLOSE = BS_INDEX.get('mouthClose')!;\r\n\r\n/**\r\n * Pre-computed eye channel flags.\r\n * Eye blink + look channels are SET (overridden) by ProceduralLifeLayer.\r\n * eyeSquint and eyeWide are NOT included — they're driven by emotion.\r\n */\r\nconst IS_EYE_CHANNEL: boolean[] = new Array(52).fill(false);\r\nfor (const name of ARKIT_BLENDSHAPES) {\r\n if (name.startsWith('eyeBlink') || name.startsWith('eyeLook')) {\r\n IS_EYE_CHANNEL[BS_INDEX.get(name)!] = true;\r\n }\r\n}\r\n\r\n/**\r\n * Per-blendshape character profile (multiplier + offset)\r\n *\r\n * Superset of ExpressionProfile — gives per-channel control instead of per-group.\r\n */\r\nexport interface CharacterProfile {\r\n /** Per-blendshape multiplier (default: all 1.0) */\r\n multiplier?: Partial<Record<string, number>>;\r\n /** Per-blendshape offset (default: all 0.0) */\r\n offset?: Partial<Record<string, number>>;\r\n}\r\n\r\n/**\r\n * Configuration for FaceCompositor\r\n */\r\nexport interface FaceCompositorConfig {\r\n /** ProceduralLifeLayer instance (compositor creates default if omitted) */\r\n lifeLayer?: ProceduralLifeLayer;\r\n /** Character profile: per-BS multiplier + offset */\r\n profile?: CharacterProfile;\r\n /** Emotion smoothing factor per frame (0-1). Default: 0.12 */\r\n emotionSmoothing?: number;\r\n}\r\n\r\n/**\r\n * Per-frame input to the compositor\r\n */\r\nexport interface FaceCompositorInput extends LifeLayerInput {\r\n /** Delta time in seconds */\r\n deltaTime: number;\r\n /** Current emotion weights (from EmotionController.emotion or manual) */\r\n emotion?: EmotionWeights;\r\n /** Emotion intensity multiplier (0-2). Default: 1.0 */\r\n emotionIntensity?: number;\r\n}\r\n\r\n/**\r\n * FaceCompositor — 5-stage facial animation signal chain.\r\n *\r\n * @example\r\n * ```typescript\r\n * import { FaceCompositor, createA2E } from '@omote/core';\r\n *\r\n * const compositor = new FaceCompositor();\r\n *\r\n * // In animation loop:\r\n * const output = compositor.compose(a2eFrame, {\r\n * deltaTime: 0.016,\r\n * emotion: { joy: 0.8 },\r\n * isSpeaking: true,\r\n * audioEnergy: 0.5,\r\n * });\r\n *\r\n * // Apply output.blendshapes[0..51] to avatar morphTargetInfluences\r\n * ```\r\n */\r\nexport class FaceCompositor {\r\n private readonly emotionResolver = new EmotionResolver();\r\n private readonly lifeLayer: ProceduralLifeLayer;\r\n private readonly emotionSmoothing: number;\r\n\r\n // Pre-allocated buffers\r\n private readonly outputBuffer = new Float32Array(52);\r\n private readonly smoothedUpper = new Float32Array(52);\r\n private readonly smoothedLower = new Float32Array(52);\r\n private readonly lifeBuffer = new Float32Array(52);\r\n\r\n // Profile arrays (pre-expanded to 52 channels)\r\n private readonly multiplier = new Float32Array(52).fill(1);\r\n private readonly offset = new Float32Array(52);\r\n\r\n // Persistent emotion for convenience API\r\n private stickyEmotion: EmotionWeights | undefined;\r\n\r\n constructor(config?: FaceCompositorConfig) {\r\n this.lifeLayer = config?.lifeLayer ?? new ProceduralLifeLayer();\r\n this.emotionSmoothing = config?.emotionSmoothing ?? 0.12;\r\n\r\n if (config?.profile) {\r\n this.applyProfileArrays(config.profile);\r\n }\r\n\r\n logger.debug('constructor', {\r\n emotionSmoothing: this.emotionSmoothing,\r\n hasProfile: !!config?.profile,\r\n hasLifeLayer: !!config?.lifeLayer,\r\n });\r\n }\r\n\r\n /**\r\n * Compose a single output frame from the 5-stage signal chain.\r\n *\r\n * @param base - A2E raw output (Float32Array[52], ARKIT_BLENDSHAPES order)\r\n * @param input - Per-frame input (deltaTime, emotion, life layer params)\r\n * @param target - Optional pre-allocated output buffer (avoids per-frame allocation).\r\n * When omitted, an internal buffer is used (valid until next compose() call).\r\n * @returns Blendshapes (Float32Array[52] clamped [0,1]) and head rotation deltas\r\n */\r\n compose(base: Float32Array, input: FaceCompositorInput, target?: Float32Array): FaceCompositorOutput {\r\n const composeStart = getClock().now();\r\n const out = target ?? this.outputBuffer;\r\n\r\n // ── Stage 1: BASE ─────────────────────────────────────────────────\r\n out.set(base);\r\n\r\n // ── Stage 2: EMOTION MODULATION ───────────────────────────────────\r\n const emotion = input.emotion ?? this.stickyEmotion;\r\n if (emotion) {\r\n const resolved = this.emotionResolver.resolve(\r\n emotion,\r\n input.emotionIntensity ?? 1.0,\r\n );\r\n\r\n // Smooth emotion transitions (EMA)\r\n const k = this.emotionSmoothing;\r\n for (let i = 0; i < 52; i++) {\r\n this.smoothedUpper[i] += (resolved.upper[i] - this.smoothedUpper[i]) * k;\r\n this.smoothedLower[i] += (resolved.lower[i] - this.smoothedLower[i]) * k;\r\n }\r\n\r\n // Bilabial suppression: during P/B/M, smoothly suppress lower-face emotion\r\n // Smooth ramp from 1.0 (no suppression) at mouthClose=0.3 to 0.1 (90% suppressed) at 0.7\r\n const mc = base[IDX_MOUTH_CLOSE];\r\n const bilabialSuppress = mc <= 0.3 ? 1.0\r\n : mc >= 0.7 ? 0.1\r\n : 1.0 - 0.9 * smoothstep((mc - 0.3) * 2.5); // 1/(0.7-0.3) = 2.5\r\n\r\n // Upper face: additive overlay\r\n for (let i = 0; i < 52; i++) {\r\n out[i] += this.smoothedUpper[i];\r\n }\r\n\r\n // Lower face: modulate speech — base * (1 + emotionBias)\r\n for (let i = 0; i < 52; i++) {\r\n out[i] *= (1 + this.smoothedLower[i] * bilabialSuppress);\r\n }\r\n }\r\n\r\n // ── Stage 3: PROCEDURAL LIFE ──────────────────────────────────────\r\n // Use update() to get both blendshapes and headDelta\r\n const lifeResult = this.lifeLayer.update(input.deltaTime, input);\r\n\r\n // Map named blendshapes → LAM indices\r\n this.lifeBuffer.fill(0);\r\n for (const [name, value] of Object.entries(lifeResult.blendshapes)) {\r\n const idx = BS_INDEX.get(name);\r\n if (idx !== undefined) {\r\n this.lifeBuffer[idx] = value;\r\n }\r\n }\r\n\r\n for (let i = 0; i < 52; i++) {\r\n if (IS_EYE_CHANNEL[i]) {\r\n out[i] = this.lifeBuffer[i]; // SET (override blinks/gaze)\r\n } else {\r\n out[i] += this.lifeBuffer[i]; // ADD (brows, breathing)\r\n }\r\n }\r\n\r\n // ── Stage 4: CHARACTER PROFILE ────────────────────────────────────\r\n for (let i = 0; i < 52; i++) {\r\n out[i] = out[i] * this.multiplier[i] + this.offset[i];\r\n }\r\n\r\n // ── Stage 5: OUTPUT — clamp [0, 1] ────────────────────────────────\r\n for (let i = 0; i < 52; i++) {\r\n if (out[i] < 0) out[i] = 0;\r\n else if (out[i] > 1) out[i] = 1;\r\n }\r\n\r\n getTelemetry()?.recordHistogram(\r\n MetricNames.COMPOSITOR_COMPOSE_LATENCY,\r\n (getClock().now() - composeStart) * 1000, // µs\r\n );\r\n\r\n return { blendshapes: out, headDelta: lifeResult.headDelta };\r\n }\r\n\r\n /**\r\n * Set sticky emotion (used when input.emotion is not provided).\r\n */\r\n setEmotion(weights: EmotionWeights): void {\r\n this.stickyEmotion = weights;\r\n logger.debug('setEmotion', { weights });\r\n }\r\n\r\n /**\r\n * Update character profile at runtime.\r\n */\r\n setProfile(profile: CharacterProfile): void {\r\n this.multiplier.fill(1);\r\n this.offset.fill(0);\r\n this.applyProfileArrays(profile);\r\n logger.debug('setProfile', {\r\n multiplierKeys: profile.multiplier ? Object.keys(profile.multiplier).length : 0,\r\n offsetKeys: profile.offset ? Object.keys(profile.offset).length : 0,\r\n });\r\n }\r\n\r\n /**\r\n * Reset all smoothing state and life layer.\r\n */\r\n reset(): void {\r\n this.smoothedUpper.fill(0);\r\n this.smoothedLower.fill(0);\r\n this.lifeBuffer.fill(0);\r\n this.stickyEmotion = undefined;\r\n this.lifeLayer.reset();\r\n logger.debug('reset');\r\n }\r\n\r\n /** Expand partial profile maps into dense Float32Arrays */\r\n private applyProfileArrays(profile: CharacterProfile): void {\r\n if (profile.multiplier) {\r\n for (const [name, value] of Object.entries(profile.multiplier)) {\r\n const idx = BS_INDEX.get(name);\r\n if (idx !== undefined && value !== undefined) {\r\n this.multiplier[idx] = value;\r\n }\r\n }\r\n }\r\n if (profile.offset) {\r\n for (const [name, value] of Object.entries(profile.offset)) {\r\n const idx = BS_INDEX.get(name);\r\n if (idx !== undefined && value !== undefined) {\r\n this.offset[idx] = value;\r\n }\r\n }\r\n }\r\n }\r\n}\r\n","/**\r\n * TextEmotionAnalyzer — Lightweight keyword heuristic for mapping AI response\r\n * text to an emotion label.\r\n *\r\n * Returns null if no strong signal is detected (keeps current emotion).\r\n *\r\n * @category Face\r\n */\r\n\r\nconst PATTERNS: Array<{ emotion: string; keywords: RegExp }> = [\r\n { emotion: 'empathetic', keywords: /\\b(sorry|unfortunately|condolences|sympathize|sympathise|my heart goes out)\\b/i },\r\n { emotion: 'excited', keywords: /\\b(amazing|incredible|awesome|fantastic|wonderful|brilliant|thrilling)\\b/i },\r\n { emotion: 'happy', keywords: /\\b(glad|great news|happy to|pleased to|delighted|congrats|congratulations)\\b/i },\r\n { emotion: 'sad', keywords: /\\b(sadly|tragic|heartbreaking|devastating|loss|mourn)\\b/i },\r\n { emotion: 'amused', keywords: /\\b(haha|heh|lol|funny|hilarious|laughing|joke)\\b/i },\r\n { emotion: 'concerned', keywords: /\\b(worry|worried|concerning|alarming|careful|caution|danger)\\b/i },\r\n { emotion: 'curious', keywords: /\\b(interesting|fascinating|intriguing|wonder|curious)\\b/i },\r\n { emotion: 'grateful', keywords: /\\b(thank you|thanks|grateful|appreciate|thankful)\\b/i },\r\n { emotion: 'angry', keywords: /\\b(outrageous|unacceptable|furious|infuriating|disgraceful)\\b/i },\r\n { emotion: 'scared', keywords: /\\b(terrifying|frightening|scary|horrifying|dreadful)\\b/i },\r\n { emotion: 'thoughtful', keywords: /\\b(consider|perhaps|on the other hand|reflect|ponder)\\b/i },\r\n { emotion: 'surprised', keywords: /\\b(wow|whoa|oh my|unbelievable|shocked|astonishing)\\b/i },\r\n];\r\n\r\n/**\r\n * Analyze AI response text for emotional content.\r\n *\r\n * @param text - The AI response text to analyze\r\n * @returns An emotion label string, or null if no strong signal detected\r\n */\r\nexport function analyzeTextEmotion(text: string): string | null {\r\n if (!text || text.length < 3) return null;\r\n\r\n for (const { emotion, keywords } of PATTERNS) {\r\n if (keywords.test(text)) {\r\n return emotion;\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n","/**\r\n * EmotionTagParser — Strips `[tag]` emotion annotations from LLM response text.\r\n *\r\n * LLMs can self-annotate responses with emotion tags like `[excited]` or `[sad]`.\r\n * This parser extracts the first valid tag and returns clean display text.\r\n *\r\n * @category Face\r\n */\r\n\r\nconst TAG_RE = /\\[([a-z_]+)\\]/gi;\r\n\r\nconst VALID_TAGS = new Set([\r\n 'happy', 'sad', 'angry', 'surprised', 'fearful', 'disgusted', 'neutral',\r\n 'scared', 'excited', 'tired', 'playful', 'pained', 'contemplative',\r\n 'thoughtful', 'concerned', 'curious', 'grateful', 'empathetic', 'amused',\r\n]);\r\n\r\n/**\r\n * Parse emotion tags from LLM response text.\r\n *\r\n * @param text - Raw LLM response text, possibly containing `[emotion]` tags\r\n * @returns Object with clean display text and extracted emotion label (or null)\r\n */\r\nexport function parseEmotionTags(text: string): { cleanText: string; emotion: string | null } {\r\n let emotion: string | null = null;\r\n\r\n const cleanText = text.replace(TAG_RE, (match, tag: string) => {\r\n const lower = tag.toLowerCase();\r\n if (!emotion && VALID_TAGS.has(lower)) {\r\n emotion = lower;\r\n return '';\r\n }\r\n return match;\r\n }).trim();\r\n\r\n return { cleanText, emotion };\r\n}\r\n","/**\r\n * CharacterController — Renderer-agnostic avatar composition loop\r\n *\r\n * Extracted from r3f's useOmoteAvatar + useGazeTracking.\r\n * Owns FaceCompositor, emotion resolution, eye angle math, head smoothing.\r\n * Pure function: input → output. No renderer side effects.\r\n *\r\n * @category Character\r\n */\r\n\r\nimport { FaceCompositor } from '../face/FaceCompositor';\r\nimport type { FaceCompositorConfig, FaceCompositorInput, CharacterProfile } from '../face/FaceCompositor';\r\nimport type { EmotionWeights } from '../emotion';\r\nimport type { ConversationalState } from '../animation';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\n\r\nconst logger = createLogger('CharacterController');\r\n\r\n/** Frame budget threshold in microseconds (33ms = 30fps budget) */\r\nconst FRAME_BUDGET_US = 33_000;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Emotion resolution (extracted from r3f emotionMapping.ts)\r\n// ---------------------------------------------------------------------------\r\n\r\nconst EMOTION_MAP: Record<string, EmotionWeights> = {\r\n // Synced with EmotionPresets (packages/core/src/emotion/Emotion.ts)\r\n happy: { joy: 0.7, amazement: 0.2 },\r\n sad: { sadness: 0.7, grief: 0.4 },\r\n angry: { anger: 0.8, disgust: 0.3 },\r\n surprised: { amazement: 0.9, fear: 0.2 },\r\n fearful: { fear: 0.8 },\r\n disgusted: { disgust: 0.8, anger: 0.2 },\r\n neutral: {},\r\n // From EmotionPresets\r\n scared: { fear: 0.8, pain: 0.3 },\r\n excited: { joy: 0.6, amazement: 0.5, cheekiness: 0.4 },\r\n tired: { outofbreath: 0.6, sadness: 0.3 },\r\n playful: { cheekiness: 0.7, joy: 0.5 },\r\n pained: { pain: 0.8, grief: 0.4 },\r\n contemplative: { sadness: 0.2, grief: 0.1 },\r\n // Text heuristic / LLM tag labels\r\n thoughtful: { sadness: 0.15 },\r\n concerned: { fear: 0.3, sadness: 0.2 },\r\n curious: { amazement: 0.4 },\r\n grateful: { joy: 0.6 },\r\n empathetic: { sadness: 0.4, grief: 0.2 },\r\n amused: { joy: 0.4, cheekiness: 0.4 },\r\n // SenseVoice fallback\r\n emo_unknown: {},\r\n};\r\n\r\nconst _resolveCache = new Map<string, EmotionWeights | undefined>();\r\nfor (const key of Object.keys(EMOTION_MAP)) {\r\n _resolveCache.set(key, EMOTION_MAP[key]);\r\n}\r\n\r\n/**\r\n * Convert an emotion label string or EmotionWeights object to EmotionWeights.\r\n * Cached to avoid per-frame string allocation.\r\n */\r\nexport function resolveEmotion(\r\n emotion: string | EmotionWeights | null | undefined,\r\n): EmotionWeights | undefined {\r\n if (!emotion) return undefined;\r\n if (typeof emotion === 'string') {\r\n let result = _resolveCache.get(emotion);\r\n if (result === undefined && !_resolveCache.has(emotion)) {\r\n result = EMOTION_MAP[emotion.toLowerCase()];\r\n _resolveCache.set(emotion, result);\r\n }\r\n return result;\r\n }\r\n return emotion;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Vec3 type (renderer-agnostic)\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Simple 3D vector (renderer-agnostic) */\r\nexport interface Vec3 {\r\n x: number;\r\n y: number;\r\n z: number;\r\n}\r\n\r\n/** Quaternion (renderer-agnostic, for head rotation) */\r\nexport interface Quat {\r\n x: number;\r\n y: number;\r\n z: number;\r\n w: number;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Config & I/O types\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface CharacterControllerConfig {\r\n /** FaceCompositor configuration */\r\n compositor?: FaceCompositorConfig;\r\n /** Gaze tracking config */\r\n gaze?: {\r\n enabled?: boolean;\r\n yawInfluence?: number;\r\n pitchInfluence?: number;\r\n smoothing?: number;\r\n };\r\n}\r\n\r\nexport interface CharacterUpdateInput {\r\n /** Time since last frame in seconds */\r\n deltaTime: number;\r\n /** Scaled blendshapes from pipeline frame (or null when no frame) */\r\n baseBlendshapes: Float32Array | null;\r\n /** Raw blendshapes before profile scaling (optional) */\r\n rawBlendshapes?: Float32Array | null;\r\n /** Current emotion (string preset or weights object) */\r\n emotion?: string | EmotionWeights | null;\r\n /** Whether the avatar is currently speaking */\r\n isSpeaking: boolean;\r\n /** Current conversational state */\r\n state: ConversationalState;\r\n /** Audio energy level (0-1, drives emphasis/gesture intensity) */\r\n audioEnergy?: number;\r\n /** Camera world position (renderer provides in its own coords) */\r\n cameraWorldPos?: Vec3;\r\n /** Head bone world position (renderer provides in its own coords) */\r\n headWorldPos?: Vec3;\r\n /** Head bone world quaternion (for eye gaze local-space transform) */\r\n headWorldQuat?: Quat;\r\n /** Current avatar Y rotation in radians (for gaze compensation) */\r\n avatarRotationY?: number;\r\n}\r\n\r\nexport interface CharacterUpdateOutput {\r\n /** 52 ARKit blendshape values, clamped [0,1] — apply to morph targets */\r\n blendshapes: Float32Array;\r\n /** Head rotation delta (radians) — apply to head bone */\r\n headDelta: { yaw: number; pitch: number };\r\n /** Normalized eye targets for eye blendshapes */\r\n eyeTargets: { x: number; y: number };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// CharacterController\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class CharacterController {\r\n private readonly _compositor: FaceCompositor;\r\n private readonly gazeEnabled: boolean;\r\n private readonly gazeYawInfluence: number;\r\n private readonly gazePitchInfluence: number;\r\n private readonly gazeSmoothing: number;\r\n\r\n // Frame budget tracking (rolling window)\r\n private readonly frameTimes: Float64Array = new Float64Array(120); // 2s @ 60fps\r\n private frameTimeIdx = 0;\r\n private frameTimeFill = 0;\r\n\r\n // Pre-allocated buffers (zero-alloc hot path)\r\n private readonly zeroBase = new Float32Array(52);\r\n private readonly outputBuffer = new Float32Array(52);\r\n private readonly compositorInput: FaceCompositorInput = {\r\n deltaTime: 0,\r\n emotion: undefined,\r\n eyeTargetX: 0,\r\n eyeTargetY: 0,\r\n isSpeaking: false,\r\n state: 'idle',\r\n };\r\n\r\n // Head smoothing state\r\n private gazeHeadYaw = 0;\r\n private gazeHeadPitch = -0.1;\r\n\r\n constructor(config?: CharacterControllerConfig) {\r\n this._compositor = new FaceCompositor(config?.compositor);\r\n this.gazeEnabled = config?.gaze?.enabled ?? true;\r\n this.gazeYawInfluence = config?.gaze?.yawInfluence ?? 0.4;\r\n this.gazePitchInfluence = config?.gaze?.pitchInfluence ?? 0.3;\r\n this.gazeSmoothing = config?.gaze?.smoothing ?? 5;\r\n\r\n logger.debug('constructor', {\r\n gazeEnabled: this.gazeEnabled,\r\n gazeYawInfluence: this.gazeYawInfluence,\r\n gazePitchInfluence: this.gazePitchInfluence,\r\n gazeSmoothing: this.gazeSmoothing,\r\n hasCompositorConfig: !!config?.compositor,\r\n });\r\n }\r\n\r\n /**\r\n * Call each frame. Pure function: input → output. No renderer side effects.\r\n *\r\n * Composes A2E blendshapes, emotion, procedural life, gaze tracking\r\n * into a single output frame.\r\n */\r\n update(input: CharacterUpdateInput): CharacterUpdateOutput {\r\n const frameStart = getClock().now();\r\n const base = input.baseBlendshapes ?? this.zeroBase;\r\n\r\n // Compute eye targets from camera + head positions + head rotation\r\n const eyeTargets = this.computeEyeTargets(\r\n input.cameraWorldPos,\r\n input.headWorldPos,\r\n input.headWorldQuat,\r\n );\r\n\r\n // Build compositor input (mutate pre-allocated object)\r\n const ci = this.compositorInput;\r\n ci.deltaTime = input.deltaTime;\r\n ci.emotion = resolveEmotion(input.emotion);\r\n ci.eyeTargetX = eyeTargets.x;\r\n ci.eyeTargetY = eyeTargets.y;\r\n ci.isSpeaking = input.isSpeaking;\r\n ci.state = input.state;\r\n ci.audioEnergy = input.audioEnergy;\r\n\r\n // Run 5-stage compositor\r\n const { blendshapes, headDelta: lifeHeadDelta } = this._compositor.compose(\r\n base, ci, this.outputBuffer,\r\n );\r\n\r\n // Smooth head rotation\r\n const headDelta = this.computeHeadGaze(\r\n input.deltaTime,\r\n input.cameraWorldPos,\r\n input.headWorldPos,\r\n lifeHeadDelta,\r\n input.avatarRotationY ?? 0,\r\n );\r\n\r\n // Frame budget monitoring (microseconds)\r\n const frameUs = (getClock().now() - frameStart) * 1000;\r\n\r\n // Rolling window for getPerformanceSnapshot()\r\n this.frameTimes[this.frameTimeIdx] = frameUs;\r\n this.frameTimeIdx = (this.frameTimeIdx + 1) % this.frameTimes.length;\r\n if (this.frameTimeFill < this.frameTimes.length) this.frameTimeFill++;\r\n\r\n const tel = getTelemetry();\r\n if (tel) {\r\n tel.recordHistogram(MetricNames.AVATAR_FRAME_LATENCY, frameUs);\r\n if (frameUs > FRAME_BUDGET_US) {\r\n tel.incrementCounter(MetricNames.AVATAR_FRAME_DROPS);\r\n }\r\n }\r\n\r\n return {\r\n blendshapes,\r\n headDelta,\r\n eyeTargets,\r\n };\r\n }\r\n\r\n /** Set emotion (string preset or weights object). */\r\n setEmotion(emotion: string | EmotionWeights): void {\r\n const resolved = resolveEmotion(emotion);\r\n if (resolved) {\r\n this._compositor.setEmotion(resolved);\r\n logger.debug('setEmotion', { emotion, resolved });\r\n }\r\n }\r\n\r\n /** Update character profile at runtime. */\r\n setProfile(profile: CharacterProfile): void {\r\n this._compositor.setProfile(profile);\r\n logger.debug('setProfile', {\r\n multiplierKeys: profile.multiplier ? Object.keys(profile.multiplier).length : 0,\r\n offsetKeys: profile.offset ? Object.keys(profile.offset).length : 0,\r\n });\r\n }\r\n\r\n /** Access underlying FaceCompositor for advanced use. */\r\n get compositor(): FaceCompositor {\r\n return this._compositor;\r\n }\r\n\r\n /**\r\n * Get a snapshot of frame budget performance (rolling 2-second window).\r\n * Useful for runtime diagnostics / dev overlays.\r\n */\r\n getPerformanceSnapshot(): {\r\n avgFrameUs: number;\r\n maxFrameUs: number;\r\n p95FrameUs: number;\r\n droppedFrames: number;\r\n totalFrames: number;\r\n } {\r\n if (this.frameTimeFill === 0) {\r\n return { avgFrameUs: 0, maxFrameUs: 0, p95FrameUs: 0, droppedFrames: 0, totalFrames: 0 };\r\n }\r\n\r\n const n = this.frameTimeFill;\r\n const times = Array.from(this.frameTimes.subarray(0, n));\r\n times.sort((a, b) => a - b);\r\n\r\n const sum = times.reduce((a, b) => a + b, 0);\r\n const p95Idx = Math.min(Math.floor(n * 0.95), n - 1);\r\n\r\n return {\r\n avgFrameUs: sum / n,\r\n maxFrameUs: times[n - 1],\r\n p95FrameUs: times[p95Idx],\r\n droppedFrames: times.filter(t => t > FRAME_BUDGET_US).length,\r\n totalFrames: n,\r\n };\r\n }\r\n\r\n /** Reset all state (smoothing, life layer, emotions). */\r\n reset(): void {\r\n this._compositor.reset();\r\n this.gazeHeadYaw = 0;\r\n this.gazeHeadPitch = -0.1;\r\n logger.debug('reset');\r\n }\r\n\r\n dispose(): void {\r\n this.reset();\r\n logger.debug('dispose');\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Eye angle math (extracted from r3f useGazeTracking.computeEyeTargets)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Compute normalized eye targets from camera and head positions.\r\n * Pure atan2/asin math — no renderer dependency.\r\n */\r\n private computeEyeTargets(\r\n cameraPos?: Vec3,\r\n headPos?: Vec3,\r\n headQuat?: Quat,\r\n ): { x: number; y: number } {\r\n if (!this.gazeEnabled || !cameraPos || !headPos) {\r\n return { x: 0, y: 0 };\r\n }\r\n\r\n // Direction from head (eye approximation) to camera\r\n const eyeY = headPos.y + 0.08;\r\n let dx = cameraPos.x - headPos.x;\r\n let dy = cameraPos.y - eyeY;\r\n let dz = cameraPos.z - headPos.z;\r\n\r\n // Normalize\r\n const len = Math.sqrt(dx * dx + dy * dy + dz * dz);\r\n if (len < 0.001) return { x: 0, y: 0 };\r\n\r\n dx /= len;\r\n dy /= len;\r\n dz /= len;\r\n\r\n // Transform world-space direction into head-local space via inverse quaternion\r\n if (headQuat) {\r\n // Invert quaternion: conjugate (negate xyz, keep w)\r\n const qx = -headQuat.x, qy = -headQuat.y, qz = -headQuat.z, qw = headQuat.w;\r\n // Apply quaternion rotation: q * v * q^-1 (optimized)\r\n const ix = qw * dx + qy * dz - qz * dy;\r\n const iy = qw * dy + qz * dx - qx * dz;\r\n const iz = qw * dz + qx * dy - qy * dx;\r\n const iw = -qx * dx - qy * dy - qz * dz;\r\n dx = ix * qw + iw * -qx + iy * -qz - iz * -qy;\r\n dy = iy * qw + iw * -qy + iz * -qx - ix * -qz;\r\n dz = iz * qw + iw * -qz + ix * -qy - iy * -qx;\r\n }\r\n\r\n // Eye yaw and pitch in head-local space\r\n const eyeYaw = Math.atan2(-dx, dz);\r\n const eyePitch = Math.asin(Math.max(-1, Math.min(1, dy)));\r\n const maxAngle = Math.PI / 6;\r\n\r\n return {\r\n x: Math.max(-1, Math.min(1, eyeYaw / maxAngle)),\r\n y: Math.max(-1, Math.min(1, eyePitch / maxAngle)),\r\n };\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Head gaze smoothing (extracted from r3f useGazeTracking.applyHeadGaze)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Compute smoothed head rotation. Returns target yaw/pitch values.\r\n * Renderer is responsible for applying these to the head bone.\r\n */\r\n private computeHeadGaze(\r\n deltaTime: number,\r\n cameraPos: Vec3 | undefined,\r\n headPos: Vec3 | undefined,\r\n lifeHeadDelta: { yaw: number; pitch: number },\r\n avatarRotationY: number,\r\n ): { yaw: number; pitch: number } {\r\n if (this.gazeEnabled && cameraPos && headPos) {\r\n // Direction from head to camera\r\n const dx = cameraPos.x - headPos.x;\r\n const dy = cameraPos.y - headPos.y;\r\n const dz = cameraPos.z - headPos.z;\r\n\r\n const len = Math.sqrt(dx * dx + dy * dy + dz * dz);\r\n if (len > 0.001) {\r\n // Compensate for avatar rotation\r\n const cosR = Math.cos(-avatarRotationY);\r\n const sinR = Math.sin(-avatarRotationY);\r\n const lx = dx * cosR - dz * sinR;\r\n const lz = dx * sinR + dz * cosR;\r\n\r\n const yaw = Math.atan2(lx, lz) * this.gazeYawInfluence;\r\n const pitchOffset = -0.18;\r\n const pitch = Math.asin(Math.max(-1, Math.min(1, dy / len)))\r\n * this.gazePitchInfluence + pitchOffset;\r\n\r\n const targetYaw = yaw + lifeHeadDelta.yaw;\r\n const targetPitch = pitch + lifeHeadDelta.pitch;\r\n const k = Math.min(1, deltaTime * this.gazeSmoothing);\r\n this.gazeHeadYaw += (targetYaw - this.gazeHeadYaw) * k;\r\n this.gazeHeadPitch += (targetPitch - this.gazeHeadPitch) * k;\r\n }\r\n } else {\r\n const targetYaw = lifeHeadDelta.yaw;\r\n const targetPitch = lifeHeadDelta.pitch - 0.1;\r\n const k = Math.min(1, deltaTime * 5);\r\n this.gazeHeadYaw += (targetYaw - this.gazeHeadYaw) * k;\r\n this.gazeHeadPitch += (targetPitch - this.gazeHeadPitch) * k;\r\n }\r\n\r\n return {\r\n yaw: this.gazeHeadYaw,\r\n pitch: this.gazeHeadPitch,\r\n };\r\n }\r\n}\r\n","/**\n * MicLipSync - Microphone → VAD → A2E → blendshapes\n *\n * Simple composition class for live mic lip sync (\"mirror mode\").\n * Replaces A2EOrchestrator with proper MicrophoneCapture integration.\n *\n * @category Orchestration\n */\n\nimport { EventEmitter, type OmoteEvents } from '../events';\nimport { MicrophoneCapture } from '../audio/MicrophoneCapture';\nimport { A2EProcessor } from '../inference/A2EProcessor';\nimport { int16ToFloat32 } from '../audio/audioUtils';\nimport { applyProfile } from '../audio/expressionProfile';\nimport { createLogger } from '../logging';\nimport { getClock } from '../logging/Clock';\nimport { ErrorCodes } from '../logging/ErrorCodes';\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\nimport { MetricNames } from '../telemetry/types';\nimport type { A2EBackend } from '../inference/A2EBackend';\nimport type { SileroVADBackend } from '../inference/createSileroVAD';\nimport type { ExpressionProfile } from '../audio/expressionProfile';\n\nconst logger = createLogger('MicLipSync');\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type MicLipSyncState = 'idle' | 'active' | 'paused';\n\nexport interface MicLipSyncConfig {\n /** A2E inference backend (from createA2E) — required */\n lam: A2EBackend;\n /** VAD backend for speech boundary detection (optional) */\n vad?: SileroVADBackend;\n /** Mic sample rate (default: 16000) */\n sampleRate?: number;\n /** Mic chunk size in samples (default: 512, required by Silero VAD) */\n micChunkSize?: number;\n /** Per-character expression weight scaling */\n profile?: ExpressionProfile;\n /** Identity/style index for A2E model (default: 0) */\n identityIndex?: number;\n}\n\nexport interface MicLipSyncFrame {\n blendshapes: Float32Array;\n rawBlendshapes: Float32Array;\n}\n\nexport interface MicLipSyncEvents {\n /** New blendshape frame ready */\n 'frame': MicLipSyncFrame;\n /** Speech started (VAD) */\n 'speech:start': void;\n /** Speech ended (VAD) */\n 'speech:end': { durationMs: number };\n /** Microphone started */\n 'mic:start': void;\n /** Microphone stopped */\n 'mic:stop': void;\n /** Audio level update */\n 'audio:level': { rms: number; peak: number };\n /** State changed */\n 'state': MicLipSyncState;\n /** Error occurred */\n 'error': Error;\n [key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// MicLipSync\n// ---------------------------------------------------------------------------\n\nexport class MicLipSync extends EventEmitter<MicLipSyncEvents> {\n private omoteEvents = new EventEmitter<OmoteEvents>();\n private mic: MicrophoneCapture;\n private processor: A2EProcessor;\n private vad?: SileroVADBackend;\n\n private _state: MicLipSyncState = 'idle';\n private _isSpeaking = false;\n private _currentFrame: Float32Array | null = null;\n private profile: ExpressionProfile;\n\n // Logging\n private _firstFrameEmitted = false;\n\n // Pre-allocated buffer for applyProfile (avoids per-frame Float32Array allocation)\n private readonly _profileBuffer = new Float32Array(52);\n\n // VAD serialization — processVAD is async but called from sync callback\n private vadQueue = Promise.resolve();\n\n // VAD state\n private speechStartTime = 0;\n private vadChunkSize = 0;\n private vadBuffer: Float32Array | null = null;\n private vadBufferOffset = 0;\n\n /** Current state */\n get state(): MicLipSyncState { return this._state; }\n /** Latest blendshape frame (null before first inference) */\n get currentFrame(): Float32Array | null { return this._currentFrame; }\n /** Whether speech is currently detected (requires VAD) */\n get isSpeaking(): boolean { return this._isSpeaking; }\n /** Current backend type */\n get backend(): string | null { return this.processor ? 'active' : null; }\n\n constructor(config: MicLipSyncConfig) {\n super();\n logger.info('MicLipSync created', {\n sampleRate: config.sampleRate ?? 16000,\n micChunkSize: config.micChunkSize ?? 512,\n hasVAD: !!config.vad,\n hasProfile: !!config.profile,\n identityIndex: config.identityIndex ?? 0,\n });\n this.profile = config.profile ?? {};\n this.vad = config.vad;\n\n // Create MicrophoneCapture with internal OmoteEvents bus\n this.mic = new MicrophoneCapture(this.omoteEvents, {\n sampleRate: config.sampleRate ?? 16000,\n chunkSize: config.micChunkSize ?? 512,\n });\n\n // Create A2EProcessor in push/drip mode\n this.processor = new A2EProcessor({\n backend: config.lam,\n sampleRate: config.sampleRate ?? 16000,\n identityIndex: config.identityIndex,\n onFrame: (raw) => {\n const scaled = applyProfile(raw, this.profile, this._profileBuffer);\n this._currentFrame = scaled;\n if (!this._firstFrameEmitted) {\n this._firstFrameEmitted = true;\n logger.trace('First blendshape frame emitted');\n }\n this.emit('frame', { blendshapes: scaled, rawBlendshapes: raw });\n },\n onError: (error) => {\n logger.error('A2E inference error', { message: error.message });\n this.emit('error', error);\n },\n });\n\n // Wire mic PCM to processor (Int16Array → Float32Array conversion)\n this.omoteEvents.on('audio.chunk', ({ pcm }) => {\n const float32 = int16ToFloat32(pcm);\n this.processor.pushAudio(float32); // No timestamp = push/drip mode\n\n // Feed VAD if present — serialize async calls to prevent concurrent access\n if (this.vad) {\n this.vadQueue = this.vadQueue\n .then(() => this.processVAD(float32))\n .catch((err) => {\n logger.warn('VAD processing error', { error: String(err), code: ErrorCodes.SPH_VAD_ERROR });\n this.emit('error', err instanceof Error ? err : new Error(String(err)));\n });\n }\n });\n\n // Forward audio levels\n this.omoteEvents.on('audio.level', (level) => {\n this.emit('audio:level', level);\n });\n\n // Set up VAD buffer\n if (this.vad) {\n this.vadChunkSize = this.vad.getChunkSize();\n this.vadBuffer = new Float32Array(this.vadChunkSize);\n this.vadBufferOffset = 0;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------------------\n\n /** Start microphone capture and inference loop */\n async start(): Promise<void> {\n if (this._state === 'active') return;\n\n logger.info('Starting MicLipSync');\n getTelemetry()?.incrementCounter(MetricNames.MIC_SESSIONS);\n await this.mic.start();\n this.processor.startDrip();\n this.emit('mic:start');\n this.setState('active');\n }\n\n /** Stop microphone and inference */\n stop(): void {\n if (this._state === 'idle') return;\n\n logger.info('Stopping MicLipSync');\n this.processor.stopDrip();\n this.mic.stop();\n this._isSpeaking = false;\n this.emit('mic:stop');\n this.setState('idle');\n }\n\n /** Pause inference (mic stays open for faster resume) */\n pause(): void {\n if (this._state !== 'active') return;\n\n this.processor.stopDrip();\n this.setState('paused');\n }\n\n /** Resume inference after pause */\n resume(): void {\n if (this._state !== 'paused') return;\n\n this.processor.startDrip();\n this.setState('active');\n }\n\n /** Update ExpressionProfile at runtime */\n setProfile(profile: ExpressionProfile): void {\n this.profile = profile;\n }\n\n /** Dispose of all resources */\n async dispose(): Promise<void> {\n this.stop();\n this.processor.dispose();\n this.vad = undefined; // release reference, don't dispose (caller owns it)\n }\n\n // ---------------------------------------------------------------------------\n // Internal: VAD processing\n // ---------------------------------------------------------------------------\n\n private async processVAD(samples: Float32Array): Promise<void> {\n if (!this.vad || !this.vadBuffer) return;\n\n // Accumulate samples to VAD chunk size\n for (let i = 0; i < samples.length; i++) {\n this.vadBuffer[this.vadBufferOffset++] = samples[i];\n\n if (this.vadBufferOffset >= this.vadChunkSize) {\n try {\n const result = await this.vad!.process(this.vadBuffer);\n const wasSpeaking = this._isSpeaking;\n this._isSpeaking = result.isSpeech;\n\n if (!wasSpeaking && result.isSpeech) {\n this.speechStartTime = getClock().now();\n this.emit('speech:start');\n } else if (wasSpeaking && !result.isSpeech) {\n const durationMs = getClock().now() - this.speechStartTime;\n this.emit('speech:end', { durationMs });\n }\n } catch (err) {\n logger.warn('VAD process error', { error: String(err), code: ErrorCodes.SPH_VAD_ERROR });\n this.emit('error', err instanceof Error ? err : new Error(String(err)));\n }\n\n this.vadBufferOffset = 0;\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal: State management\n // ---------------------------------------------------------------------------\n\n private setState(state: MicLipSyncState): void {\n if (this._state === state) return;\n this._state = state;\n this.emit('state', state);\n }\n}\n","/**\r\n * VoiceOrchestrator — Shared voice wiring for OmoteAvatar adapters.\r\n *\r\n * Composes TTSSpeaker (local mode) or PlaybackPipeline (cloud mode) with\r\n * SpeechListener and InterruptionHandler. Supports both local TTS and\r\n * cloud TTS via discriminated union config.\r\n *\r\n * Extracted from the ~70 identical lines duplicated across three/babylon/r3f\r\n * adapters into a single reusable class.\r\n *\r\n * @category Orchestration\r\n */\r\n\r\nimport { EventEmitter } from '../events/EventEmitter';\r\nimport { TTSSpeaker } from '../audio/TTSSpeaker';\r\nimport { SpeechListener } from '../audio/SpeechListener';\r\nimport { InterruptionHandler } from '../audio/InterruptionHandler';\r\nimport { PlaybackPipeline } from '../audio/PlaybackPipeline';\r\nimport { calculateRMS } from '../animation/audioEnergy';\r\nimport { createA2E } from '../inference/createA2E';\r\nimport { UnifiedInferenceWorker } from '../inference/UnifiedInferenceWorker';\r\nimport { acquireSharedWorker, releaseSharedWorker } from '../inference/sharedLazyWorker';\r\nimport { createLogger } from '../logging';\r\nimport { getClock } from '../logging/Clock';\r\nimport { getTelemetry } from '../telemetry/OmoteTelemetry';\r\nimport { MetricNames } from '../telemetry/types';\r\nimport type { TTSBackend } from '../inference/TTSBackend';\r\nimport type { TTSSpeakerConfig } from '../audio/TTSSpeaker';\r\nimport type { SpeechListenerConfig } from '../audio/SpeechListener';\r\nimport type { ExpressionProfile } from '../audio/expressionProfile';\r\nimport type { A2EBackend } from '../inference/A2EBackend';\r\nimport type { ConversationalState } from '../animation';\r\nimport type {\r\n TranscriptResult,\r\n ResponseHandler,\r\n FrameSource,\r\n LoadingProgress,\r\n} from './types';\r\n\r\nconst logger = createLogger('VoiceOrchestrator');\r\n\r\n// ---------------------------------------------------------------------------\r\n// Config (discriminated union)\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface VoiceOrchestratorBaseConfig {\r\n listener?: SpeechListenerConfig;\r\n interruptionEnabled?: boolean;\r\n profile?: ExpressionProfile;\r\n onStateChange?: (state: ConversationalState) => void;\r\n onLoadingProgress?: (progress: LoadingProgress) => void;\r\n onError?: (error: Error) => void;\r\n onTranscriptEvent?: (result: TranscriptResult) => void;\r\n onInterruption?: () => void;\r\n}\r\n\r\nexport interface VoiceOrchestratorLocalConfig extends VoiceOrchestratorBaseConfig {\r\n mode?: 'local';\r\n tts: TTSBackend;\r\n speaker?: TTSSpeakerConfig;\r\n onTranscript: (text: string, emotion?: string) => string | Promise<string> | AsyncGenerator<string>;\r\n}\r\n\r\nexport interface VoiceOrchestratorCloudConfig extends VoiceOrchestratorBaseConfig {\r\n mode: 'cloud';\r\n onResponse: ResponseHandler;\r\n lam?: { modelUrl?: string; externalDataUrl?: string | false; unifiedWorker?: UnifiedInferenceWorker };\r\n identityIndex?: number;\r\n neutralTransitionEnabled?: boolean;\r\n}\r\n\r\nexport type VoiceOrchestratorConfig = VoiceOrchestratorLocalConfig | VoiceOrchestratorCloudConfig;\r\n\r\n// ---------------------------------------------------------------------------\r\n// Events\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface VoiceOrchestratorEvents {\r\n 'state': ConversationalState;\r\n 'transcript': TranscriptResult;\r\n 'interruption': void;\r\n 'loading:progress': LoadingProgress;\r\n 'error': Error;\r\n 'audio:level': { rms: number; peak: number };\r\n 'playback:complete': void;\r\n [key: string]: unknown;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// VoiceOrchestrator\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class VoiceOrchestrator extends EventEmitter<VoiceOrchestratorEvents> {\r\n // Shared primitives\r\n private speechListener: SpeechListener | null = null;\r\n private interruption: InterruptionHandler | null = null;\r\n\r\n // Local mode\r\n private ttsSpeaker: TTSSpeaker | null = null;\r\n\r\n // Cloud mode\r\n private playbackPipeline: PlaybackPipeline | null = null;\r\n private ownedLam: A2EBackend | null = null;\r\n private ownedWorker: UnifiedInferenceWorker | null = null;\r\n private usesSharedWorker = false;\r\n\r\n // Wiring cleanup\r\n private transcriptUnsub: (() => void) | null = null;\r\n private audioChunkUnsub: (() => void) | null = null;\r\n private connectEpoch = 0;\r\n\r\n // Response abort (cloud mode)\r\n private responseAbortController: AbortController | null = null;\r\n\r\n // State\r\n private _state: ConversationalState = 'idle';\r\n private _isSpeaking = false;\r\n private _frameSource: FrameSource | null = null;\r\n private _mode: 'local' | 'cloud' = 'local';\r\n private _sessionId: string | null = null;\r\n\r\n get state(): ConversationalState { return this._state; }\r\n get isSpeaking(): boolean { return this._isSpeaking; }\r\n get frameSource(): FrameSource | null { return this._frameSource; }\r\n\r\n /** Access the internal SpeechListener. */\r\n get listener(): SpeechListener | null { return this.speechListener; }\r\n /** Access the internal TTSSpeaker (local mode only). */\r\n get speaker(): TTSSpeaker | null { return this.ttsSpeaker; }\r\n\r\n // -------------------------------------------------------------------------\r\n // Connect / Disconnect\r\n // -------------------------------------------------------------------------\r\n\r\n async connect(config: VoiceOrchestratorConfig): Promise<void> {\r\n await this.disconnect();\r\n const epoch = ++this.connectEpoch;\r\n this._mode = config.mode ?? 'local';\r\n this._sessionId = crypto.randomUUID();\r\n\r\n const span = getTelemetry()?.startSpan('VoiceOrchestrator.connect', {\r\n 'mode': this._mode,\r\n 'session.id': this._sessionId,\r\n });\r\n\r\n // Wire config callbacks before any loading (B1 — subscribe pre-load)\r\n if (config.onStateChange) this.on('state', config.onStateChange);\r\n if (config.onLoadingProgress) this.on('loading:progress', config.onLoadingProgress);\r\n if (config.onError) this.on('error', config.onError);\r\n if (config.onTranscriptEvent) this.on('transcript', config.onTranscriptEvent);\r\n if (config.onInterruption) this.on('interruption', config.onInterruption);\r\n\r\n logger.info('Connecting voice orchestrator', { mode: this._mode });\r\n\r\n try {\r\n // 1. Connect speaker (local) or PlaybackPipeline (cloud)\r\n if (this._mode === 'local') {\r\n const localCfg = config as VoiceOrchestratorLocalConfig;\r\n this.emit('loading:progress', { currentModel: 'Loading TTS', progress: 0, totalModels: 3, modelsLoaded: 0 });\r\n this.ttsSpeaker = new TTSSpeaker();\r\n await this.ttsSpeaker.connect(localCfg.tts, localCfg.speaker);\r\n if (this.connectEpoch !== epoch) return;\r\n this.emit('loading:progress', { currentModel: 'TTS + lip sync loaded', progress: 33, totalModels: 3, modelsLoaded: 1 });\r\n this._frameSource = this.ttsSpeaker.frameSource;\r\n } else {\r\n const cloudCfg = config as VoiceOrchestratorCloudConfig;\r\n // Create A2E + PlaybackPipeline for cloud lip sync\r\n this.emit('loading:progress', { currentModel: 'Loading inference worker', progress: 0, totalModels: 3, modelsLoaded: 0 });\r\n let worker = cloudCfg.lam?.unifiedWorker;\r\n if (!worker) {\r\n worker = await acquireSharedWorker();\r\n this.usesSharedWorker = true;\r\n }\r\n this.ownedWorker = worker;\r\n const lam = createA2E({\r\n ...cloudCfg.lam,\r\n unifiedWorker: worker,\r\n });\r\n this.ownedLam = lam;\r\n await lam.load();\r\n if (this.connectEpoch !== epoch) return;\r\n this.emit('loading:progress', { currentModel: 'Lip sync model loaded', progress: 33, totalModels: 3, modelsLoaded: 1 });\r\n\r\n this.playbackPipeline = new PlaybackPipeline({\r\n lam,\r\n profile: config.profile,\r\n identityIndex: cloudCfg.identityIndex,\r\n neutralTransitionEnabled: cloudCfg.neutralTransitionEnabled,\r\n });\r\n await this.playbackPipeline.initialize();\r\n if (this.connectEpoch !== epoch) return;\r\n\r\n // Wire playback:complete → transition back to listening\r\n this.playbackPipeline.on('playback:complete', () => {\r\n this._isSpeaking = false;\r\n this.interruption?.setAISpeaking(false);\r\n this.speechListener?.resume();\r\n this.setState('listening');\r\n this.emit('playback:complete');\r\n });\r\n\r\n this._frameSource = this.playbackPipeline;\r\n }\r\n\r\n // 2. Connect listener — share the same worker to avoid 2× ORT WASM on iOS\r\n const listenerConfig: SpeechListenerConfig = { ...config.listener };\r\n if (this.ownedWorker && !listenerConfig.unifiedWorker) {\r\n listenerConfig.unifiedWorker = this.ownedWorker;\r\n }\r\n this.speechListener = new SpeechListener(listenerConfig);\r\n // Remap SpeechListener progress (0-100 over 2 models) to 33-100 over 3 total models\r\n this.speechListener.on('loading:progress', (p) => {\r\n const remapped: LoadingProgress = {\r\n currentModel: p.currentModel,\r\n totalModels: 3,\r\n modelsLoaded: 1 + p.modelsLoaded,\r\n progress: 33 + Math.round((p.progress / 100) * 67),\r\n };\r\n this.emit('loading:progress', remapped);\r\n });\r\n this.speechListener.on('error', (e) => this.emit('error', e));\r\n this.speechListener.on('audio:level', (l) => this.emit('audio:level', l));\r\n await this.speechListener.loadModels();\r\n if (this.connectEpoch !== epoch) return;\r\n\r\n // 3. Interruption handler\r\n this.interruption = new InterruptionHandler({\r\n enabled: config.interruptionEnabled ?? true,\r\n });\r\n this.interruption.on('interruption.triggered', () => {\r\n this.handleInterruption();\r\n });\r\n\r\n // 4. Wire audio:chunk for interruption detection during speaking\r\n const audioChunkHandler = (samples: Float32Array) => {\r\n if (this._state === 'speaking' && this.interruption) {\r\n const rms = calculateRMS(samples);\r\n this.interruption.processAudioEnergy(rms);\r\n }\r\n };\r\n this.speechListener.on('audio:chunk', audioChunkHandler);\r\n this.audioChunkUnsub = () => this.speechListener?.off?.('audio:chunk', audioChunkHandler);\r\n\r\n // 5. Wire transcript handler\r\n if (this._mode === 'local') {\r\n this.wireLocalTranscript(config as VoiceOrchestratorLocalConfig);\r\n } else {\r\n this.wireCloudTranscript(config as VoiceOrchestratorCloudConfig);\r\n }\r\n\r\n logger.info('Voice orchestrator connected', { mode: this._mode });\r\n span?.end();\r\n } catch (err) {\r\n logger.error('Voice orchestrator connect failed, cleaning up', { error: String(err) });\r\n span?.endWithError(err instanceof Error ? err : new Error(String(err)));\r\n await this.disconnect();\r\n throw err;\r\n }\r\n }\r\n\r\n async disconnect(): Promise<void> {\r\n this.connectEpoch++;\r\n this.responseAbortController?.abort();\r\n this.responseAbortController = null;\r\n\r\n // Remove config callback listeners to prevent accumulation on re-connect()\r\n this.removeAllListeners();\r\n\r\n this.transcriptUnsub?.();\r\n this.audioChunkUnsub?.();\r\n this.transcriptUnsub = null;\r\n this.audioChunkUnsub = null;\r\n this.interruption = null;\r\n\r\n if (this.ttsSpeaker) {\r\n await this.ttsSpeaker.dispose();\r\n this.ttsSpeaker = null;\r\n }\r\n\r\n if (this.playbackPipeline) {\r\n await this.playbackPipeline.dispose();\r\n this.playbackPipeline = null;\r\n }\r\n\r\n if (this.ownedLam) {\r\n await this.ownedLam.dispose();\r\n this.ownedLam = null;\r\n }\r\n\r\n if (this.usesSharedWorker) {\r\n await releaseSharedWorker();\r\n this.usesSharedWorker = false;\r\n } else if (this.ownedWorker) {\r\n await this.ownedWorker.dispose();\r\n this.ownedWorker = null;\r\n }\r\n\r\n if (this.speechListener) {\r\n await this.speechListener.dispose();\r\n this.speechListener = null;\r\n }\r\n\r\n this._frameSource = null;\r\n this._isSpeaking = false;\r\n this._state = 'idle';\r\n this._sessionId = null;\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Listener control\r\n // -------------------------------------------------------------------------\r\n\r\n async startListening(): Promise<void> {\r\n if (!this.speechListener) {\r\n throw new Error('VoiceOrchestrator not connected. Call connect() first.');\r\n }\r\n this.setState('listening');\r\n await this.speechListener.start();\r\n }\r\n\r\n stopListening(): void {\r\n this.speechListener?.stop();\r\n if (this._state === 'listening') this.setState('idle');\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Speaker control (local mode only)\r\n // -------------------------------------------------------------------------\r\n\r\n async speak(text: string, options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string }): Promise<void> {\r\n if (!this.ttsSpeaker) {\r\n throw new Error('speak() requires local mode. Call connect() with mode: \"local\" first.');\r\n }\r\n this._isSpeaking = true;\r\n this.setState('speaking');\r\n try {\r\n await this.ttsSpeaker.speak(text, options);\r\n } finally {\r\n this._isSpeaking = false;\r\n if (this._state === 'speaking') this.setState('idle');\r\n }\r\n }\r\n\r\n async streamText(options?: { signal?: AbortSignal; voice?: string; speed?: number; language?: string }): Promise<{\r\n push: (token: string) => void;\r\n end: () => Promise<void>;\r\n }> {\r\n if (!this.ttsSpeaker) {\r\n throw new Error('streamText() requires local mode. Call connect() with mode: \"local\" first.');\r\n }\r\n this._isSpeaking = true;\r\n this.setState('speaking');\r\n const stream = await this.ttsSpeaker.streamText(options ?? {});\r\n return {\r\n push: stream.push,\r\n end: async () => {\r\n try { await stream.end(); }\r\n finally {\r\n this._isSpeaking = false;\r\n if (this._state === 'speaking') this.setState('idle');\r\n }\r\n },\r\n };\r\n }\r\n\r\n stopSpeaking(): void {\r\n if (this._mode === 'local') {\r\n this.ttsSpeaker?.stop();\r\n } else {\r\n this.responseAbortController?.abort();\r\n this.playbackPipeline?.stop();\r\n }\r\n this._isSpeaking = false;\r\n this.interruption?.setAISpeaking(false);\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Internal — transcript wiring\r\n // -------------------------------------------------------------------------\r\n\r\n private wireLocalTranscript(config: VoiceOrchestratorLocalConfig): void {\r\n const handler = async (result: TranscriptResult) => {\r\n this.emit('transcript', result); // All transcripts (interim + final)\r\n if (!result.isFinal || !result.text.trim()) return;\r\n const turnStart = getClock().now();\r\n this.setState('thinking');\r\n this.speechListener?.pause();\r\n this.interruption?.setAISpeaking(true);\r\n try {\r\n const response = config.onTranscript(result.text, result.emotion);\r\n if (isAsyncGenerator(response)) {\r\n const stream = await this.streamText();\r\n for await (const token of response) { stream.push(token); }\r\n await stream.end();\r\n } else {\r\n const text = await response;\r\n await this.speak(text);\r\n }\r\n } catch (e) {\r\n logger.error('Voice transcript handler error', { error: String(e) });\r\n } finally {\r\n this.interruption?.setAISpeaking(false);\r\n this.speechListener?.resume();\r\n getTelemetry()?.recordHistogram(MetricNames.VOICE_TURN_LATENCY, getClock().now() - turnStart);\r\n this.setState('listening');\r\n }\r\n };\r\n this.speechListener!.on('transcript', handler);\r\n this.transcriptUnsub = () => this.speechListener?.off?.('transcript', handler);\r\n }\r\n\r\n private wireCloudTranscript(config: VoiceOrchestratorCloudConfig): void {\r\n const handler = async (result: TranscriptResult) => {\r\n this.emit('transcript', result); // All transcripts (interim + final)\r\n if (!result.isFinal || !result.text.trim()) return;\r\n const turnStart = getClock().now();\r\n let firstChunkSent = false;\r\n this.setState('thinking');\r\n this.speechListener?.pause();\r\n this.interruption?.setAISpeaking(true);\r\n this._isSpeaking = true;\r\n\r\n const abortController = new AbortController();\r\n this.responseAbortController = abortController;\r\n\r\n try {\r\n this.setState('speaking');\r\n this.playbackPipeline!.start();\r\n\r\n await config.onResponse({\r\n text: result.text,\r\n emotion: result.emotion,\r\n event: result.event,\r\n setEmotion: (emotion: string) => this.playbackPipeline?.setEmotion(emotion),\r\n send: async (chunk: Uint8Array) => {\r\n if (abortController.signal.aborted) return;\r\n if (!firstChunkSent) {\r\n firstChunkSent = true;\r\n getTelemetry()?.recordHistogram(MetricNames.VOICE_RESPONSE_LATENCY, getClock().now() - turnStart);\r\n }\r\n await this.playbackPipeline!.onAudioChunk(chunk);\r\n },\r\n done: async () => {\r\n if (abortController.signal.aborted) return;\r\n getTelemetry()?.recordHistogram(MetricNames.VOICE_TURN_LATENCY, getClock().now() - turnStart);\r\n await this.playbackPipeline!.end();\r\n },\r\n signal: abortController.signal,\r\n sessionId: this._sessionId!,\r\n });\r\n } catch (e) {\r\n if (!abortController.signal.aborted) {\r\n logger.error('Cloud response handler error', { error: String(e) });\r\n }\r\n } finally {\r\n this.responseAbortController = null;\r\n // State transitions handled by playback:complete event (cloud)\r\n // or speak()/streamText() finally blocks (local)\r\n }\r\n };\r\n this.speechListener!.on('transcript', handler);\r\n this.transcriptUnsub = () => this.speechListener?.off?.('transcript', handler);\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Internal — interruption\r\n // -------------------------------------------------------------------------\r\n\r\n private handleInterruption(): void {\r\n if (this._state !== 'speaking') return;\r\n logger.info('Interruption triggered');\r\n\r\n this.stopSpeaking();\r\n this.emit('interruption');\r\n getTelemetry()?.incrementCounter(MetricNames.VOICE_INTERRUPTIONS, 1, { source: 'orchestrator' });\r\n this.speechListener?.resume();\r\n this.setState('listening');\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Internal — state\r\n // -------------------------------------------------------------------------\r\n\r\n private setState(state: ConversationalState): void {\r\n if (this._state === state) return;\r\n this._state = state;\r\n this.emit('state', state);\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Helpers\r\n// ---------------------------------------------------------------------------\r\n\r\nfunction isAsyncGenerator(value: unknown): value is AsyncGenerator<string> {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n Symbol.asyncIterator in (value as Record<symbol, unknown>)\r\n );\r\n}\r\n","/**\n * Versioned NDJSON streaming protocol for client-server communication.\n *\n * Each line in the NDJSON stream is a JSON object conforming to ProtocolEvent.\n * The `v` field enables backward-compatible protocol evolution.\n *\n * @category Protocol\n */\n\n/** Current protocol version. Increment on breaking changes. */\nexport const PROTOCOL_VERSION = 1;\n\n/** Base shape for all NDJSON protocol events */\ninterface ProtocolEventBase {\n /** Protocol version */\n v: number;\n /** Event type discriminator */\n type: string;\n /** ISO 8601 timestamp */\n ts: string;\n}\n\n// --- Response events (server -> client) ---\n\nexport interface ResponseStartEvent extends ProtocolEventBase {\n type: 'response_start';\n /** Initial emotion for the response */\n emotion?: string;\n}\n\nexport interface ResponseChunkEvent extends ProtocolEventBase {\n type: 'response_chunk';\n /** Text chunk (streamed token-by-token) */\n text: string;\n}\n\nexport interface AudioChunkEvent extends ProtocolEventBase {\n type: 'audio_chunk';\n /** Base64-encoded PCM16 audio at 16kHz */\n audio: string;\n /** Audio format identifier */\n format: 'pcm_16000';\n /** Whether this is the last audio chunk */\n isLast: boolean;\n}\n\nexport interface ResponseEndEvent extends ProtocolEventBase {\n type: 'response_end';\n /** Complete response text */\n fullText: string;\n /** Response generation time in ms */\n durationMs?: number;\n /** Token count for this response */\n tokenCount?: number;\n}\n\nexport interface ErrorEvent extends ProtocolEventBase {\n type: 'error';\n /** Error message */\n message: string;\n /** Error code for programmatic handling */\n code?: string;\n /** Whether the client should retry */\n recoverable?: boolean;\n}\n\n/**\n * Union of all protocol events.\n * Used for type-safe NDJSON parsing on the client.\n */\nexport type ProtocolEvent =\n | ResponseStartEvent\n | ResponseChunkEvent\n | AudioChunkEvent\n | ResponseEndEvent\n | ErrorEvent;\n\n/**\n * Type guard for protocol events.\n */\nexport function isProtocolEvent(obj: unknown): obj is ProtocolEvent {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'v' in obj &&\n 'type' in obj &&\n 'ts' in obj\n );\n}\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["logger","logger","logger","logger","logger","logger","logger","logger","logger","logger","worker","logger","logger","logger","logger","logger","data","logger","logger","logger","logger","logger","logger","logger","logger","logger","logger","logger","logger","logger","latency","logger","logger","logger","t","eased","logger","logger","BS_INDEX","logger","logger","logger"]}
|