@omote/babylon 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -145,7 +145,7 @@ function writeBlendshapes(blendshapes, morphEntries) {
145
145
  }
146
146
 
147
147
  // src/OmoteAvatar.ts
148
- var logger2 = (0, import_core2.createLogger)("OmoteAvatar");
148
+ var logger2 = (0, import_core2.createLogger)("OmoteAvatar.Babylon");
149
149
  var OmoteAvatar = class {
150
150
  constructor(options) {
151
151
  // State
@@ -275,6 +275,10 @@ var OmoteAvatar = class {
275
275
  * @param tts - TTS backend (e.g., KokoroTTSInference, ElevenLabs adapter)
276
276
  * @param config - A2E, expression profile, and playback configuration
277
277
  */
278
+ /** Warm up AudioContext for iOS/Safari autoplay policy. Call from user gesture. */
279
+ async warmup() {
280
+ if (this.ttsSpeaker) await this.ttsSpeaker.warmup();
281
+ }
278
282
  async connectSpeaker(tts, config) {
279
283
  await this.disconnectSpeaker();
280
284
  this.ttsSpeaker = new import_core2.TTSSpeaker();
@@ -543,9 +547,9 @@ var OmoteAvatar = class {
543
547
  // Internal
544
548
  // ---------------------------------------------------------------------------
545
549
  registerAutoUpdate() {
546
- this.lastTime = performance.now();
550
+ this.lastTime = (0, import_core2.getClock)().now();
547
551
  this.renderCallback = () => {
548
- const now = performance.now();
552
+ const now = (0, import_core2.getClock)().now();
549
553
  const delta = (now - this.lastTime) / 1e3;
550
554
  this.lastTime = now;
551
555
  if (this._camera) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/OmoteAvatar.ts","../src/SceneDiscovery.ts","../src/BlendshapeWriter.ts","../src/BlendshapeController.ts"],"sourcesContent":["// High-level\nexport { OmoteAvatar } from './OmoteAvatar';\nexport type { OmoteAvatarOptions, FrameSource } from './OmoteAvatar';\n\n// Re-export TTSSpeakerConfig as TTSConfig for convenience\nexport type { TTSSpeakerConfig as TTSConfig } from '@omote/core';\n\n// Scene utilities\nexport { discoverScene } from './SceneDiscovery';\nexport type { SceneDiscoveryResult, MorphIndexEntry } from './SceneDiscovery';\nexport { writeBlendshapes } from './BlendshapeWriter';\n\n// Low-level escape hatch\nexport { BlendshapeController } from './BlendshapeController';\nexport type { BlendshapeControllerOptions } from './BlendshapeController';\n\n","/**\n * OmoteAvatar — Full-featured Babylon.js avatar controller.\n *\n * Owns CharacterController, SceneDiscovery, and BlendshapeWriter.\n * Mirrors the Three.js OmoteAvatar API for consistency across adapters.\n *\n * Usage:\n * ```typescript\n * import { OmoteAvatar } from '@omote/babylon';\n * import { createA2E, PlaybackPipeline } from '@omote/core';\n *\n * const avatar = new OmoteAvatar({ target: mesh, scene });\n * avatar.setCamera(camera);\n *\n * // Connect a frame source (PlaybackPipeline, MicLipSync, etc.)\n * const pipeline = new PlaybackPipeline({ lam: createA2E() });\n * avatar.connectFrameSource(pipeline);\n *\n * // Option A: manual update in render loop\n * scene.registerBeforeRender(() => avatar.update(engine.getDeltaTime() / 1000, camera));\n *\n * // Option B: autoUpdate (registers scene.registerBeforeRender internally)\n * const avatar = new OmoteAvatar({ target: mesh, scene, autoUpdate: true });\n * avatar.setCamera(camera);\n * ```\n *\n * @example Speaker integration\n * ```typescript\n * const avatar = new OmoteAvatar({ target: mesh, scene });\n * await avatar.connectSpeaker(myTTSBackend, { profile: { mouth: 1.2 } });\n * await avatar.speak(\"Hello world!\"); // lip-syncs automatically\n * ```\n *\n * @category Babylon\n */\n\nimport {\n CharacterController,\n TTSSpeaker,\n SpeechListener,\n VoiceOrchestrator,\n createLogger,\n} from '@omote/core';\nimport type {\n CharacterControllerConfig,\n CharacterProfile,\n EmotionWeights,\n ConversationalState,\n FaceCompositorConfig,\n FrameSource,\n TTSSpeakerConfig,\n TTSBackend,\n SpeechListenerConfig,\n TranscriptResult,\n VoiceOrchestratorConfig,\n} from '@omote/core';\n\nconst logger = createLogger('OmoteAvatar');\nimport type { AbstractMesh, Scene, Camera } from '@babylonjs/core';\n\nimport { discoverScene, type SceneDiscoveryResult } from './SceneDiscovery';\nimport { writeBlendshapes } from './BlendshapeWriter';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n// Re-export FrameSource from @omote/core for backward compatibility\nexport type { FrameSource } from '@omote/core';\n\nexport interface OmoteAvatarOptions {\n /** Root mesh of the avatar (typically loaded via SceneLoader) */\n target: AbstractMesh;\n /** Babylon.js scene */\n scene: Scene;\n /** FaceCompositor configuration */\n compositor?: FaceCompositorConfig;\n /** Gaze tracking configuration */\n gaze?: CharacterControllerConfig['gaze'];\n /**\n * Register scene.registerBeforeRender() for automatic update.\n * Requires setCamera() to be called before the first frame.\n * Default: false\n */\n autoUpdate?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// OmoteAvatar\n// ---------------------------------------------------------------------------\n\nexport class OmoteAvatar {\n private readonly controller: CharacterController;\n private readonly discovery: SceneDiscoveryResult;\n private readonly scene: Scene;\n\n // State\n private currentBlendshapes: Float32Array | null = null;\n private _emotion: string | EmotionWeights | null = null;\n private _isSpeaking = false;\n private _state: ConversationalState = 'idle';\n private _audioEnergy = 0;\n private _camera: Camera | null = null;\n\n // Frame source connection\n private frameSourceCallback: ((frame: { blendshapes: Float32Array }) => void) | null = null;\n private connectedSource: FrameSource | null = null;\n\n // TTS integration\n private ttsSpeaker: TTSSpeaker | null = null;\n\n // Speech listener\n private speechListener: SpeechListener | null = null;\n\n // Voice orchestrator\n private voiceOrchestrator: VoiceOrchestrator | null = null;\n\n // Auto-update\n private renderCallback: (() => void) | null = null;\n private lastTime = 0;\n\n constructor(options: OmoteAvatarOptions) {\n this.scene = options.scene;\n this.discovery = discoverScene(options.target);\n this.controller = new CharacterController({\n compositor: options.compositor,\n gaze: options.gaze,\n });\n\n if (this.discovery.morphEntries.length === 0) {\n logger.warn('No morph targets found — blendshape animation will have no effect');\n }\n if (!this.discovery.headBone) {\n logger.warn('Head bone not found — gaze tracking will be disabled');\n }\n logger.info(\n `Initialized: ${this.discovery.meshes.length} mesh(es), ${this.discovery.mappedBlendshapeCount} mapped blendshapes, headBone=${!!this.discovery.headBone}`,\n );\n\n if (options.autoUpdate) {\n this.registerAutoUpdate();\n }\n }\n\n // ---------------------------------------------------------------------------\n // Frame update\n // ---------------------------------------------------------------------------\n\n /**\n * Call each frame with delta time and camera.\n *\n * Runs CharacterController (compositor, gaze, procedural life) then writes\n * blendshapes to morph targets and applies head rotation.\n *\n * If using autoUpdate, this is called automatically via scene.registerBeforeRender().\n *\n * @param delta - Time since last frame in seconds\n * @param camera - Active Babylon.js camera (for gaze tracking)\n * @param avatarRotationY - Optional avatar Y rotation in radians for gaze compensation\n */\n update(delta: number, camera: Camera, avatarRotationY?: number): void {\n // Get camera world position (globalPosition handles parented cameras)\n const camPos = camera.globalPosition;\n const cameraWorldPos = { x: camPos.x, y: camPos.y, z: camPos.z };\n\n // Get head bone world position + quaternion (if available)\n let headWorldPos: { x: number; y: number; z: number } | undefined;\n let headWorldQuat: { x: number; y: number; z: number; w: number } | undefined;\n if (this.discovery.headBone) {\n const wp = this.discovery.headBone.getAbsolutePosition();\n headWorldPos = { x: wp.x, y: wp.y, z: wp.z };\n const rq = this.discovery.headBone.absoluteRotationQuaternion;\n if (rq) {\n headWorldQuat = { x: rq.x, y: rq.y, z: rq.z, w: rq.w };\n }\n }\n\n // Run CharacterController (compositor + gaze + life layer)\n const output = this.controller.update({\n deltaTime: delta,\n baseBlendshapes: this.currentBlendshapes,\n emotion: this._emotion,\n isSpeaking: this._isSpeaking,\n state: this._state,\n audioEnergy: this._audioEnergy,\n cameraWorldPos,\n headWorldPos,\n headWorldQuat,\n avatarRotationY: avatarRotationY ?? 0,\n });\n\n // Write blendshapes to morph targets\n writeBlendshapes(output.blendshapes, this.discovery.morphEntries);\n\n // Apply head rotation delta\n if (this.discovery.headBone) {\n this.discovery.headBone.rotation.y = output.headDelta.yaw;\n this.discovery.headBone.rotation.x = output.headDelta.pitch;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Frame source connection\n // ---------------------------------------------------------------------------\n\n /**\n * Connect a frame source (PlaybackPipeline, MicLipSync, etc.).\n *\n * Automatically listens for 'frame' events and stores the latest blendshapes.\n * Only one source can be connected at a time; calling again disconnects the previous.\n */\n connectFrameSource(source: FrameSource): void {\n // If connecting a different source while TTS is active, stop current speak\n if (this.ttsSpeaker && source !== this.ttsSpeaker.frameSource) {\n this.ttsSpeaker.stop();\n }\n\n this.disconnectFrameSource();\n this.frameSourceCallback = (frame: { blendshapes: Float32Array; emotion?: string }) => {\n this.currentBlendshapes = frame.blendshapes;\n if (frame.emotion !== undefined) {\n this._emotion = frame.emotion;\n }\n };\n source.on('frame', this.frameSourceCallback);\n this.connectedSource = source;\n logger.debug('Frame source connected');\n }\n\n /** Disconnect the current frame source (if any). */\n disconnectFrameSource(): void {\n if (this.connectedSource && this.frameSourceCallback) {\n this.connectedSource.off?.('frame', this.frameSourceCallback);\n logger.debug('Frame source disconnected');\n }\n this.connectedSource = null;\n this.frameSourceCallback = null;\n }\n\n // ---------------------------------------------------------------------------\n // Speaker (TTS → lip sync)\n // ---------------------------------------------------------------------------\n\n /**\n * Connect a TTS backend for speak() / streamText() support.\n * Loads LAM model and creates internal PlaybackPipeline.\n *\n * @param tts - TTS backend (e.g., KokoroTTSInference, ElevenLabs adapter)\n * @param config - A2E, expression profile, and playback configuration\n */\n async connectSpeaker(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\n await this.disconnectSpeaker();\n this.ttsSpeaker = new TTSSpeaker();\n await this.ttsSpeaker.connect(tts, config);\n this.connectFrameSource(this.ttsSpeaker.frameSource!);\n }\n\n /**\n * Synthesize text and play with lip sync.\n * Auto-aborts previous speak if still in progress.\n *\n * @param text - Text to synthesize\n * @param options - Optional voice override and abort signal\n */\n async speak(text: string, options?: { signal?: AbortSignal; voice?: string }): Promise<void> {\n if (this.voiceOrchestrator) {\n await this.voiceOrchestrator.speak(text, options);\n return;\n }\n if (!this.ttsSpeaker) {\n throw new Error('No speaker connected. Call connectSpeaker() first.');\n }\n this._isSpeaking = true;\n this._state = 'speaking';\n try {\n await this.ttsSpeaker.speak(text, options);\n } finally {\n this._isSpeaking = false;\n if (this._state === 'speaking') {\n this._state = 'idle';\n }\n }\n }\n\n /**\n * Stream LLM tokens with sentence-buffered TTS + lip sync.\n * Returns a sink: call push(token) for each token, end() when done.\n */\n async streamText(options?: { signal?: AbortSignal; voice?: string }): Promise<{\n push: (token: string) => void;\n end: () => Promise<void>;\n }> {\n if (this.voiceOrchestrator) {\n return this.voiceOrchestrator.streamText(options);\n }\n if (!this.ttsSpeaker) {\n throw new Error('No speaker connected. Call connectSpeaker() first.');\n }\n this._isSpeaking = true;\n this._state = 'speaking';\n const stream = await this.ttsSpeaker.streamText(options ?? {});\n return {\n push: stream.push,\n end: async () => {\n try { await stream.end(); }\n finally { this._isSpeaking = false; if (this._state === 'speaking') this._state = 'idle'; }\n },\n };\n }\n\n /** Stop current TTS playback. */\n stopSpeaking(): void {\n if (this.voiceOrchestrator) {\n this.voiceOrchestrator.stopSpeaking();\n return;\n }\n this.ttsSpeaker?.stop();\n }\n\n /** Disconnect speaker and dispose its resources. */\n async disconnectSpeaker(): Promise<void> {\n if (this.ttsSpeaker) {\n this.disconnectFrameSource();\n await this.ttsSpeaker.dispose();\n this.ttsSpeaker = null;\n }\n }\n\n /** @deprecated Use connectSpeaker(). Will be removed in v1.0. */\n async connectTTS(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\n return this.connectSpeaker(tts, config);\n }\n\n /** @deprecated Use disconnectSpeaker(). Will be removed in v1.0. */\n async disconnectTTS(): Promise<void> {\n return this.disconnectSpeaker();\n }\n\n // ---------------------------------------------------------------------------\n // Listener (mic → VAD → ASR → transcript)\n // ---------------------------------------------------------------------------\n\n /**\n * Connect a speech listener for startListening() / onTranscript() support.\n * Loads ASR + VAD models.\n */\n async connectListener(config?: SpeechListenerConfig): Promise<void> {\n await this.disconnectListener();\n this.speechListener = new SpeechListener(config);\n await this.speechListener.loadModels();\n }\n\n /** Start listening for user speech. Requires connectListener() or connectVoice() first. */\n async startListening(): Promise<void> {\n if (this.voiceOrchestrator) {\n await this.voiceOrchestrator.startListening();\n return;\n }\n if (!this.speechListener) {\n throw new Error('No listener connected. Call connectListener() first.');\n }\n this._state = 'listening';\n await this.speechListener.start();\n }\n\n /** Stop listening. */\n stopListening(): void {\n if (this.voiceOrchestrator) {\n this.voiceOrchestrator.stopListening();\n return;\n }\n this.speechListener?.stop();\n if (this._state === 'listening') this._state = 'idle';\n }\n\n /**\n * Subscribe to transcript events. Returns an unsubscribe function.\n * Requires connectListener() first.\n */\n onTranscript(callback: (result: TranscriptResult) => void): () => void {\n const listener = this.speechListener ?? this.voiceOrchestrator?.listener;\n if (!listener) {\n throw new Error('No listener connected. Call connectListener() or connectVoice() first.');\n }\n listener.on('transcript', callback);\n return () => { listener.off?.('transcript', callback); };\n }\n\n /** Disconnect listener and dispose its resources. */\n async disconnectListener(): Promise<void> {\n if (this.speechListener) {\n await this.speechListener.dispose();\n this.speechListener = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Voice (combined speaker + listener + interruption)\n // ---------------------------------------------------------------------------\n\n /**\n * Connect voice with automatic speaker + listener + interruption wiring.\n * Supports both local TTS (mode: 'local') and cloud TTS (mode: 'cloud').\n * Does NOT auto-start listening — call startListening() when ready.\n *\n * Backward compatible: `mode` defaults to 'local' when not specified.\n */\n async connectVoice(config: VoiceOrchestratorConfig): Promise<void> {\n await this.disconnectVoice();\n this.voiceOrchestrator = new VoiceOrchestrator();\n await this.voiceOrchestrator.connect(config);\n\n // Connect frame source from orchestrator\n if (this.voiceOrchestrator.frameSource) {\n this.connectFrameSource(this.voiceOrchestrator.frameSource);\n }\n\n // Sync state from orchestrator → avatar\n this.voiceOrchestrator.on('state', (state) => {\n this._state = state;\n this._isSpeaking = state === 'speaking';\n });\n }\n\n /** Disconnect voice (speaker + listener + interruption). */\n async disconnectVoice(): Promise<void> {\n if (this.voiceOrchestrator) {\n this.disconnectFrameSource();\n await this.voiceOrchestrator.disconnect();\n this.voiceOrchestrator = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // State setters\n // ---------------------------------------------------------------------------\n\n /** Set blendshapes directly (alternative to connectFrameSource). */\n setFrame(blendshapes: Float32Array): void {\n this.currentBlendshapes = blendshapes;\n }\n\n /** Set emotion (string preset like 'happy' or EmotionWeights object). */\n setEmotion(emotion: string | EmotionWeights): void {\n this._emotion = emotion;\n }\n\n /** Set whether the avatar is currently speaking (drives mouth emphasis). */\n setSpeaking(speaking: boolean): void {\n this._isSpeaking = speaking;\n }\n\n /** Set conversational state (idle, listening, thinking, speaking). */\n setState(state: ConversationalState): void {\n this._state = state;\n }\n\n /** Set audio energy level (0-1, drives emphasis/gesture intensity). */\n setAudioEnergy(energy: number): void {\n this._audioEnergy = energy;\n }\n\n /** Update character expression profile at runtime. */\n setProfile(profile: CharacterProfile): void {\n this.controller.setProfile(profile);\n }\n\n /**\n * Set the active camera for gaze tracking.\n * Required when using autoUpdate. Can also be passed directly to update().\n */\n setCamera(camera: Camera): void {\n this._camera = camera;\n }\n\n // ---------------------------------------------------------------------------\n // Accessors\n // ---------------------------------------------------------------------------\n\n /** Access underlying FaceCompositor for advanced use. */\n get compositor() {\n return this.controller.compositor;\n }\n\n /** Access SceneDiscoveryResult (meshes, bones, morph entries). */\n get parts(): SceneDiscoveryResult {\n return this.discovery;\n }\n\n /** Whether the scene has any mapped morph targets. */\n get hasMorphTargets(): boolean {\n return this.discovery.morphEntries.length > 0;\n }\n\n /** Number of successfully mapped ARKit blendshapes. */\n get mappedBlendshapeCount(): number {\n return this.discovery.mappedBlendshapeCount;\n }\n\n /** Whether the avatar is currently speaking via TTS. */\n get isSpeaking(): boolean {\n return this._isSpeaking;\n }\n\n /** Whether the avatar is currently listening for speech. */\n get isListening(): boolean {\n return this._state === 'listening';\n }\n\n /** Current conversational state. */\n get conversationalState(): ConversationalState {\n return this._state;\n }\n\n /** Access the internal TTSSpeaker (null if not connected). */\n get speaker(): TTSSpeaker | null {\n return this.ttsSpeaker ?? this.voiceOrchestrator?.speaker ?? null;\n }\n\n /** Access the internal SpeechListener (null if not connected). */\n get listener(): SpeechListener | null {\n return this.speechListener ?? this.voiceOrchestrator?.listener ?? null;\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n /** Reset all state (smoothing, life layer, emotions). */\n reset(): void {\n this.currentBlendshapes = null;\n this._emotion = null;\n this._isSpeaking = false;\n this._state = 'idle';\n this._audioEnergy = 0;\n this.controller.reset();\n }\n\n /** Disconnect all voice resources, frame sources, unregister render loop, dispose controller. */\n async dispose(): Promise<void> {\n await this.disconnectVoice();\n await this.disconnectSpeaker();\n await this.disconnectListener();\n this.disconnectFrameSource();\n if (this.renderCallback) {\n this.scene.unregisterBeforeRender(this.renderCallback);\n this.renderCallback = null;\n }\n this.controller.dispose();\n logger.debug('Disposed');\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private registerAutoUpdate(): void {\n this.lastTime = performance.now();\n this.renderCallback = () => {\n const now = performance.now();\n const delta = (now - this.lastTime) / 1000;\n this.lastTime = now;\n if (this._camera) {\n this.update(delta, this._camera);\n }\n };\n this.scene.registerBeforeRender(this.renderCallback);\n }\n}\n\n","/**\n * SceneDiscovery — Discover avatar structure from a Babylon.js scene graph.\n *\n * Finds morph targets, head/neck/eye bones, and pre-computes a fast index\n * mapping from LAM_BLENDSHAPES order to per-mesh morph target indices.\n *\n * @category Babylon\n */\n\nimport { LAM_BLENDSHAPES, createLogger } from '@omote/core';\n\nconst logger = createLogger('SceneDiscovery');\nimport type { AbstractMesh, TransformNode } from '@babylonjs/core';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MorphIndexEntry {\n mesh: AbstractMesh;\n /** indices[lamIndex] = morphTargetIndex (or -1 if not found) */\n indices: Int16Array;\n}\n\nexport interface SceneDiscoveryResult {\n /** All meshes under the root that have a MorphTargetManager */\n meshes: AbstractMesh[];\n /** Head bone (TransformNode named 'Head') or null */\n headBone: TransformNode | null;\n /** Neck bone (TransformNode named 'Neck') or null */\n neckBone: TransformNode | null;\n /** Left eye bone (TransformNode named 'LeftEye') or null */\n leftEyeBone: TransformNode | null;\n /** Right eye bone (TransformNode named 'RightEye') or null */\n rightEyeBone: TransformNode | null;\n /** Pre-computed morph index mapping per mesh */\n morphEntries: MorphIndexEntry[];\n /** Primary face mesh (prefers 'Head_Mesh', falls back to first with morph targets) */\n faceMesh: AbstractMesh | null;\n /** Total number of ARKit blendshapes successfully mapped across all meshes */\n mappedBlendshapeCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Normalize morph target name by stripping common prefixes */\nfunction normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n}\n\n/** Case-insensitive bone name matching */\nconst BONE_PATTERNS: Record<string, RegExp> = {\n head: /^head$/i,\n neck: /^neck$/i,\n leftEye: /^lefteye$/i,\n rightEye: /^righteye$/i,\n};\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Discover avatar structure from a Babylon.js root node.\n *\n * Traverses the scene graph under `root` to find:\n * - Meshes with MorphTargetManagers\n * - Head, neck, and eye bones\n * - Pre-computed LAM_BLENDSHAPES -> morph target index mapping\n *\n * @param root - The root AbstractMesh (typically loaded via SceneLoader)\n * @returns SceneDiscoveryResult with all discovered components\n */\nexport function discoverScene(root: AbstractMesh): SceneDiscoveryResult {\n const meshes: AbstractMesh[] = [];\n const morphEntries: MorphIndexEntry[] = [];\n let faceMesh: AbstractMesh | null = null;\n let mappedBlendshapeCount = 0;\n\n // ---- Morph target discovery ----\n\n // Collect all candidate meshes (root + children)\n const candidates: AbstractMesh[] = [root];\n if (root.getChildMeshes) {\n candidates.push(...root.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n meshes.push(mesh);\n\n // Prefer mesh named 'Head_Mesh' as the face mesh\n if (!faceMesh || mesh.name === 'Head_Mesh') {\n faceMesh = mesh;\n }\n\n // Build normalized name -> morph target index lookup for this mesh\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalized = normalizeName(morphTarget.name);\n morphLookup.set(normalized, t);\n }\n\n // Pre-compute Int16Array mapping: lamIndex -> morphTargetIndex (or -1)\n const indices = new Int16Array(LAM_BLENDSHAPES.length);\n let meshMapped = 0;\n for (let i = 0; i < LAM_BLENDSHAPES.length; i++) {\n const morphIdx = morphLookup.get(LAM_BLENDSHAPES[i]);\n if (morphIdx !== undefined) {\n indices[i] = morphIdx;\n meshMapped++;\n } else {\n indices[i] = -1;\n }\n }\n\n if (meshMapped > 0) {\n morphEntries.push({ mesh, indices });\n }\n mappedBlendshapeCount = Math.max(mappedBlendshapeCount, meshMapped);\n\n logger.debug(`Mesh \"${mesh.name}\": ${meshMapped}/${LAM_BLENDSHAPES.length} blendshapes mapped`);\n }\n\n // ---- Bone discovery ----\n\n let headBone: TransformNode | null = null;\n let neckBone: TransformNode | null = null;\n let leftEyeBone: TransformNode | null = null;\n let rightEyeBone: TransformNode | null = null;\n\n // getChildTransformNodes is on TransformNode (AbstractMesh extends TransformNode)\n const transformNodes = root.getChildTransformNodes(false);\n\n for (const node of transformNodes) {\n if (!headBone && BONE_PATTERNS.head.test(node.name)) {\n headBone = node;\n } else if (!neckBone && BONE_PATTERNS.neck.test(node.name)) {\n neckBone = node;\n } else if (!leftEyeBone && BONE_PATTERNS.leftEye.test(node.name)) {\n leftEyeBone = node;\n } else if (!rightEyeBone && BONE_PATTERNS.rightEye.test(node.name)) {\n rightEyeBone = node;\n }\n\n // Early exit if all bones found\n if (headBone && neckBone && leftEyeBone && rightEyeBone) break;\n }\n\n if (morphEntries.length === 0) {\n logger.warn('No morph targets found in scene');\n }\n if (!headBone) {\n logger.warn('Head bone not found in scene');\n }\n\n const boneNames = [\n headBone && 'Head',\n neckBone && 'Neck',\n leftEyeBone && 'LeftEye',\n rightEyeBone && 'RightEye',\n ].filter(Boolean);\n\n logger.info(\n `Discovery complete: ${meshes.length} mesh(es), ${mappedBlendshapeCount} mapped blendshapes, bones: [${boneNames.join(', ')}]`,\n );\n\n return {\n meshes,\n headBone,\n neckBone,\n leftEyeBone,\n rightEyeBone,\n morphEntries,\n faceMesh,\n mappedBlendshapeCount,\n };\n}\n","/**\n * BlendshapeWriter — Zero-lookup hot-path blendshape application.\n *\n * Uses pre-computed Int16Array indices from SceneDiscovery for O(1) lookups\n * per blendshape per mesh. No Map.get(), no string comparison on the hot path.\n *\n * @category Babylon\n */\n\nimport type { MorphIndexEntry } from './SceneDiscovery';\n\n/**\n * Write 52 ARKit blendshapes to Babylon.js morph targets.\n *\n * Uses pre-computed indices for zero-lookup hot path.\n * Call this every frame after CharacterController.update().\n *\n * @param blendshapes - 52-element Float32Array in LAM_BLENDSHAPES order\n * @param morphEntries - Pre-computed index mappings from discoverScene()\n */\nexport function writeBlendshapes(\n blendshapes: Float32Array,\n morphEntries: MorphIndexEntry[],\n): void {\n for (let e = 0; e < morphEntries.length; e++) {\n const { mesh, indices } = morphEntries[e];\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n for (let i = 0; i < 52; i++) {\n const morphIdx = indices[i];\n if (morphIdx >= 0) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = blendshapes[i];\n }\n }\n }\n }\n}\n","import { LAM_BLENDSHAPES, lerpBlendshapes } from '@omote/core';\nimport type { AbstractMesh, Scene } from '@babylonjs/core';\n\nexport interface BlendshapeControllerOptions {\n /** Blendshape names in order (default: LAM_BLENDSHAPES, 52 ARKit) */\n names?: readonly string[];\n /** Smoothing factor 0-1 (0 = no change, 1 = snap to target). Default: 0.7 */\n smoothing?: number;\n /** Traverse target for child meshes with MorphTargetManager automatically. Default: true */\n autoFind?: boolean;\n /** Register scene.registerBeforeRender() callback automatically. Default: false */\n autoRegister?: boolean;\n /** Called when meshes with morph targets are found */\n onMeshesFound?: (meshes: AbstractMesh[]) => void;\n}\n\nexport class BlendshapeController {\n private _meshes: AbstractMesh[] = [];\n private nameToIndex: Map<string, number>[] = [];\n private currentWeights: number[] = [];\n private blendshapeNames: readonly string[];\n private smoothing: number;\n private scene: Scene | null;\n private renderObserver: (() => void) | null = null;\n private pendingWeights: Float32Array | number[] | null = null;\n private onMeshesFound?: (meshes: AbstractMesh[]) => void;\n\n constructor(target: AbstractMesh, scene: Scene, options?: BlendshapeControllerOptions) {\n this.blendshapeNames = options?.names ?? LAM_BLENDSHAPES;\n this.smoothing = options?.smoothing ?? 0.7;\n this.scene = scene;\n this.onMeshesFound = options?.onMeshesFound;\n\n if (options?.autoFind !== false) {\n this.setTarget(target);\n }\n\n if (options?.autoRegister) {\n this.registerRenderLoop();\n }\n }\n\n get meshes(): AbstractMesh[] {\n return this._meshes;\n }\n\n /** Normalize Babylon morph target names (strip common prefixes) */\n private normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n }\n\n setTarget(target: AbstractMesh): void {\n this._meshes = [];\n this.nameToIndex = [];\n\n // Collect meshes with MorphTargetManager\n const candidates: AbstractMesh[] = [target];\n if (target.getChildMeshes) {\n candidates.push(...target.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n this._meshes.push(mesh);\n\n // Build normalized name -> morph target index lookup\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalizedName = this.normalizeName(morphTarget.name);\n morphLookup.set(normalizedName, t);\n }\n\n // Map blendshape names to morph target indices\n const map = new Map<string, number>();\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const idx = morphLookup.get(name);\n if (idx !== undefined) {\n map.set(name, idx);\n }\n }\n\n this.nameToIndex.push(map);\n }\n\n this.currentWeights = new Array(this.blendshapeNames.length).fill(0);\n\n if (this._meshes.length > 0 && this.onMeshesFound) {\n this.onMeshesFound(this._meshes);\n }\n }\n\n update(weights: Float32Array | number[]): void {\n this.currentWeights = lerpBlendshapes(this.currentWeights, weights, this.smoothing);\n\n for (let m = 0; m < this._meshes.length; m++) {\n const mesh = this._meshes[m];\n const map = this.nameToIndex[m];\n const manager = mesh.morphTargetManager;\n if (!manager || !map) continue;\n\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const morphIdx = map.get(name);\n if (morphIdx !== undefined) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = this.currentWeights[i];\n }\n }\n }\n }\n }\n\n /** Buffer weights for the next render loop tick (used with autoRegister) */\n setWeightsForNextFrame(weights: Float32Array | number[]): void {\n this.pendingWeights = weights;\n }\n\n private registerRenderLoop(): void {\n if (!this.scene) return;\n const callback = () => {\n if (this.pendingWeights) {\n this.update(this.pendingWeights);\n this.pendingWeights = null;\n }\n };\n this.scene.registerBeforeRender(callback);\n this.renderObserver = () => {\n this.scene?.unregisterBeforeRender(callback);\n };\n }\n\n dispose(): void {\n if (this.renderObserver) {\n this.renderObserver();\n this.renderObserver = null;\n }\n this._meshes = [];\n this.nameToIndex = [];\n this.currentWeights = [];\n this.scene = null;\n }\n}\n"],"mappings":"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","names":["import_core","logger","import_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/OmoteAvatar.ts","../src/SceneDiscovery.ts","../src/BlendshapeWriter.ts","../src/BlendshapeController.ts"],"sourcesContent":["// High-level\nexport { OmoteAvatar } from './OmoteAvatar';\nexport type { OmoteAvatarOptions, FrameSource } from './OmoteAvatar';\n\n// Re-export TTSSpeakerConfig as TTSConfig for convenience\nexport type { TTSSpeakerConfig as TTSConfig } from '@omote/core';\n\n// Scene utilities\nexport { discoverScene } from './SceneDiscovery';\nexport type { SceneDiscoveryResult, MorphIndexEntry } from './SceneDiscovery';\nexport { writeBlendshapes } from './BlendshapeWriter';\n\n// Low-level escape hatch\nexport { BlendshapeController } from './BlendshapeController';\nexport type { BlendshapeControllerOptions } from './BlendshapeController';\n\n","/**\r\n * OmoteAvatar — Full-featured Babylon.js avatar controller.\r\n *\r\n * Owns CharacterController, SceneDiscovery, and BlendshapeWriter.\r\n * Mirrors the Three.js OmoteAvatar API for consistency across adapters.\r\n *\r\n * Usage:\r\n * ```typescript\r\n * import { OmoteAvatar } from '@omote/babylon';\r\n * import { createA2E, PlaybackPipeline } from '@omote/core';\r\n *\r\n * const avatar = new OmoteAvatar({ target: mesh, scene });\r\n * avatar.setCamera(camera);\r\n *\r\n * // Connect a frame source (PlaybackPipeline, MicLipSync, etc.)\r\n * const pipeline = new PlaybackPipeline({ lam: createA2E() });\r\n * avatar.connectFrameSource(pipeline);\r\n *\r\n * // Option A: manual update in render loop\r\n * scene.registerBeforeRender(() => avatar.update(engine.getDeltaTime() / 1000, camera));\r\n *\r\n * // Option B: autoUpdate (registers scene.registerBeforeRender internally)\r\n * const avatar = new OmoteAvatar({ target: mesh, scene, autoUpdate: true });\r\n * avatar.setCamera(camera);\r\n * ```\r\n *\r\n * @example Speaker integration\r\n * ```typescript\r\n * const avatar = new OmoteAvatar({ target: mesh, scene });\r\n * await avatar.connectSpeaker(myTTSBackend, { profile: { mouth: 1.2 } });\r\n * await avatar.speak(\"Hello world!\"); // lip-syncs automatically\r\n * ```\r\n *\r\n * @category Babylon\r\n */\r\n\r\nimport {\r\n CharacterController,\r\n TTSSpeaker,\r\n SpeechListener,\r\n VoiceOrchestrator,\r\n createLogger,\r\n getClock,\r\n} from '@omote/core';\r\nimport type {\r\n CharacterControllerConfig,\r\n CharacterProfile,\r\n EmotionWeights,\r\n ConversationalState,\r\n FaceCompositorConfig,\r\n FrameSource,\r\n TTSSpeakerConfig,\r\n TTSBackend,\r\n SpeechListenerConfig,\r\n TranscriptResult,\r\n VoiceOrchestratorConfig,\r\n} from '@omote/core';\r\n\r\nconst logger = createLogger('OmoteAvatar.Babylon');\r\nimport type { AbstractMesh, Scene, Camera } from '@babylonjs/core';\r\n\r\nimport { discoverScene, type SceneDiscoveryResult } from './SceneDiscovery';\r\nimport { writeBlendshapes } from './BlendshapeWriter';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\n// Re-export FrameSource from @omote/core for backward compatibility\r\nexport type { FrameSource } from '@omote/core';\r\n\r\nexport interface OmoteAvatarOptions {\r\n /** Root mesh of the avatar (typically loaded via SceneLoader) */\r\n target: AbstractMesh;\r\n /** Babylon.js scene */\r\n scene: Scene;\r\n /** FaceCompositor configuration */\r\n compositor?: FaceCompositorConfig;\r\n /** Gaze tracking configuration */\r\n gaze?: CharacterControllerConfig['gaze'];\r\n /**\r\n * Register scene.registerBeforeRender() for automatic update.\r\n * Requires setCamera() to be called before the first frame.\r\n * Default: false\r\n */\r\n autoUpdate?: boolean;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// OmoteAvatar\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class OmoteAvatar {\r\n private readonly controller: CharacterController;\r\n private readonly discovery: SceneDiscoveryResult;\r\n private readonly scene: Scene;\r\n\r\n // State\r\n private currentBlendshapes: Float32Array | null = null;\r\n private _emotion: string | EmotionWeights | null = null;\r\n private _isSpeaking = false;\r\n private _state: ConversationalState = 'idle';\r\n private _audioEnergy = 0;\r\n private _camera: Camera | null = null;\r\n\r\n // Frame source connection\r\n private frameSourceCallback: ((frame: { blendshapes: Float32Array }) => void) | null = null;\r\n private connectedSource: FrameSource | null = null;\r\n\r\n // TTS integration\r\n private ttsSpeaker: TTSSpeaker | null = null;\r\n\r\n // Speech listener\r\n private speechListener: SpeechListener | null = null;\r\n\r\n // Voice orchestrator\r\n private voiceOrchestrator: VoiceOrchestrator | null = null;\r\n\r\n // Auto-update\r\n private renderCallback: (() => void) | null = null;\r\n private lastTime = 0;\r\n\r\n constructor(options: OmoteAvatarOptions) {\r\n this.scene = options.scene;\r\n this.discovery = discoverScene(options.target);\r\n this.controller = new CharacterController({\r\n compositor: options.compositor,\r\n gaze: options.gaze,\r\n });\r\n\r\n if (this.discovery.morphEntries.length === 0) {\r\n logger.warn('No morph targets found — blendshape animation will have no effect');\r\n }\r\n if (!this.discovery.headBone) {\r\n logger.warn('Head bone not found — gaze tracking will be disabled');\r\n }\r\n logger.info(\r\n `Initialized: ${this.discovery.meshes.length} mesh(es), ${this.discovery.mappedBlendshapeCount} mapped blendshapes, headBone=${!!this.discovery.headBone}`,\r\n );\r\n\r\n if (options.autoUpdate) {\r\n this.registerAutoUpdate();\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Frame update\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Call each frame with delta time and camera.\r\n *\r\n * Runs CharacterController (compositor, gaze, procedural life) then writes\r\n * blendshapes to morph targets and applies head rotation.\r\n *\r\n * If using autoUpdate, this is called automatically via scene.registerBeforeRender().\r\n *\r\n * @param delta - Time since last frame in seconds\r\n * @param camera - Active Babylon.js camera (for gaze tracking)\r\n * @param avatarRotationY - Optional avatar Y rotation in radians for gaze compensation\r\n */\r\n update(delta: number, camera: Camera, avatarRotationY?: number): void {\r\n // Get camera world position (globalPosition handles parented cameras)\r\n const camPos = camera.globalPosition;\r\n const cameraWorldPos = { x: camPos.x, y: camPos.y, z: camPos.z };\r\n\r\n // Get head bone world position + quaternion (if available)\r\n let headWorldPos: { x: number; y: number; z: number } | undefined;\r\n let headWorldQuat: { x: number; y: number; z: number; w: number } | undefined;\r\n if (this.discovery.headBone) {\r\n const wp = this.discovery.headBone.getAbsolutePosition();\r\n headWorldPos = { x: wp.x, y: wp.y, z: wp.z };\r\n const rq = this.discovery.headBone.absoluteRotationQuaternion;\r\n if (rq) {\r\n headWorldQuat = { x: rq.x, y: rq.y, z: rq.z, w: rq.w };\r\n }\r\n }\r\n\r\n // Run CharacterController (compositor + gaze + life layer)\r\n const output = this.controller.update({\r\n deltaTime: delta,\r\n baseBlendshapes: this.currentBlendshapes,\r\n emotion: this._emotion,\r\n isSpeaking: this._isSpeaking,\r\n state: this._state,\r\n audioEnergy: this._audioEnergy,\r\n cameraWorldPos,\r\n headWorldPos,\r\n headWorldQuat,\r\n avatarRotationY: avatarRotationY ?? 0,\r\n });\r\n\r\n // Write blendshapes to morph targets\r\n writeBlendshapes(output.blendshapes, this.discovery.morphEntries);\r\n\r\n // Apply head rotation delta\r\n if (this.discovery.headBone) {\r\n this.discovery.headBone.rotation.y = output.headDelta.yaw;\r\n this.discovery.headBone.rotation.x = output.headDelta.pitch;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Frame source connection\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect a frame source (PlaybackPipeline, MicLipSync, etc.).\r\n *\r\n * Automatically listens for 'frame' events and stores the latest blendshapes.\r\n * Only one source can be connected at a time; calling again disconnects the previous.\r\n */\r\n connectFrameSource(source: FrameSource): void {\r\n // If connecting a different source while TTS is active, stop current speak\r\n if (this.ttsSpeaker && source !== this.ttsSpeaker.frameSource) {\r\n this.ttsSpeaker.stop();\r\n }\r\n\r\n this.disconnectFrameSource();\r\n this.frameSourceCallback = (frame: { blendshapes: Float32Array; emotion?: string }) => {\r\n this.currentBlendshapes = frame.blendshapes;\r\n if (frame.emotion !== undefined) {\r\n this._emotion = frame.emotion;\r\n }\r\n };\r\n source.on('frame', this.frameSourceCallback);\r\n this.connectedSource = source;\r\n logger.debug('Frame source connected');\r\n }\r\n\r\n /** Disconnect the current frame source (if any). */\r\n disconnectFrameSource(): void {\r\n if (this.connectedSource && this.frameSourceCallback) {\r\n this.connectedSource.off?.('frame', this.frameSourceCallback);\r\n logger.debug('Frame source disconnected');\r\n }\r\n this.connectedSource = null;\r\n this.frameSourceCallback = null;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Speaker (TTS → lip sync)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect a TTS backend for speak() / streamText() support.\r\n * Loads LAM model and creates internal PlaybackPipeline.\r\n *\r\n * @param tts - TTS backend (e.g., KokoroTTSInference, ElevenLabs adapter)\r\n * @param config - A2E, expression profile, and playback configuration\r\n */\r\n /** Warm up AudioContext for iOS/Safari autoplay policy. Call from user gesture. */\r\n async warmup(): Promise<void> {\r\n if (this.ttsSpeaker) await this.ttsSpeaker.warmup();\r\n }\r\n\r\n async connectSpeaker(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\r\n await this.disconnectSpeaker();\r\n this.ttsSpeaker = new TTSSpeaker();\r\n await this.ttsSpeaker.connect(tts, config);\r\n this.connectFrameSource(this.ttsSpeaker.frameSource!);\r\n }\r\n\r\n /**\r\n * Synthesize text and play with lip sync.\r\n * Auto-aborts previous speak if still in progress.\r\n *\r\n * @param text - Text to synthesize\r\n * @param options - Optional voice override and abort signal\r\n */\r\n async speak(text: string, options?: { signal?: AbortSignal; voice?: string }): Promise<void> {\r\n if (this.voiceOrchestrator) {\r\n await this.voiceOrchestrator.speak(text, options);\r\n return;\r\n }\r\n if (!this.ttsSpeaker) {\r\n throw new Error('No speaker connected. Call connectSpeaker() first.');\r\n }\r\n this._isSpeaking = true;\r\n this._state = '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') {\r\n this._state = 'idle';\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Stream LLM tokens with sentence-buffered TTS + lip sync.\r\n * Returns a sink: call push(token) for each token, end() when done.\r\n */\r\n async streamText(options?: { signal?: AbortSignal; voice?: string }): Promise<{\r\n push: (token: string) => void;\r\n end: () => Promise<void>;\r\n }> {\r\n if (this.voiceOrchestrator) {\r\n return this.voiceOrchestrator.streamText(options);\r\n }\r\n if (!this.ttsSpeaker) {\r\n throw new Error('No speaker connected. Call connectSpeaker() first.');\r\n }\r\n this._isSpeaking = true;\r\n this._state = '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 { this._isSpeaking = false; if (this._state === 'speaking') this._state = 'idle'; }\r\n },\r\n };\r\n }\r\n\r\n /** Stop current TTS playback. */\r\n stopSpeaking(): void {\r\n if (this.voiceOrchestrator) {\r\n this.voiceOrchestrator.stopSpeaking();\r\n return;\r\n }\r\n this.ttsSpeaker?.stop();\r\n }\r\n\r\n /** Disconnect speaker and dispose its resources. */\r\n async disconnectSpeaker(): Promise<void> {\r\n if (this.ttsSpeaker) {\r\n this.disconnectFrameSource();\r\n await this.ttsSpeaker.dispose();\r\n this.ttsSpeaker = null;\r\n }\r\n }\r\n\r\n /** @deprecated Use connectSpeaker(). Will be removed in v1.0. */\r\n async connectTTS(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\r\n return this.connectSpeaker(tts, config);\r\n }\r\n\r\n /** @deprecated Use disconnectSpeaker(). Will be removed in v1.0. */\r\n async disconnectTTS(): Promise<void> {\r\n return this.disconnectSpeaker();\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Listener (mic → VAD → ASR → transcript)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect a speech listener for startListening() / onTranscript() support.\r\n * Loads ASR + VAD models.\r\n */\r\n async connectListener(config?: SpeechListenerConfig): Promise<void> {\r\n await this.disconnectListener();\r\n this.speechListener = new SpeechListener(config);\r\n await this.speechListener.loadModels();\r\n }\r\n\r\n /** Start listening for user speech. Requires connectListener() or connectVoice() first. */\r\n async startListening(): Promise<void> {\r\n if (this.voiceOrchestrator) {\r\n await this.voiceOrchestrator.startListening();\r\n return;\r\n }\r\n if (!this.speechListener) {\r\n throw new Error('No listener connected. Call connectListener() first.');\r\n }\r\n this._state = 'listening';\r\n await this.speechListener.start();\r\n }\r\n\r\n /** Stop listening. */\r\n stopListening(): void {\r\n if (this.voiceOrchestrator) {\r\n this.voiceOrchestrator.stopListening();\r\n return;\r\n }\r\n this.speechListener?.stop();\r\n if (this._state === 'listening') this._state = 'idle';\r\n }\r\n\r\n /**\r\n * Subscribe to transcript events. Returns an unsubscribe function.\r\n * Requires connectListener() first.\r\n */\r\n onTranscript(callback: (result: TranscriptResult) => void): () => void {\r\n const listener = this.speechListener ?? this.voiceOrchestrator?.listener;\r\n if (!listener) {\r\n throw new Error('No listener connected. Call connectListener() or connectVoice() first.');\r\n }\r\n listener.on('transcript', callback);\r\n return () => { listener.off?.('transcript', callback); };\r\n }\r\n\r\n /** Disconnect listener and dispose its resources. */\r\n async disconnectListener(): Promise<void> {\r\n if (this.speechListener) {\r\n await this.speechListener.dispose();\r\n this.speechListener = null;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Voice (combined speaker + listener + interruption)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect voice with automatic speaker + listener + interruption wiring.\r\n * Supports both local TTS (mode: 'local') and cloud TTS (mode: 'cloud').\r\n * Does NOT auto-start listening — call startListening() when ready.\r\n *\r\n * Backward compatible: `mode` defaults to 'local' when not specified.\r\n */\r\n async connectVoice(config: VoiceOrchestratorConfig): Promise<void> {\r\n await this.disconnectVoice();\r\n this.voiceOrchestrator = new VoiceOrchestrator();\r\n await this.voiceOrchestrator.connect(config);\r\n\r\n // Connect frame source from orchestrator\r\n if (this.voiceOrchestrator.frameSource) {\r\n this.connectFrameSource(this.voiceOrchestrator.frameSource);\r\n }\r\n\r\n // Sync state from orchestrator → avatar\r\n this.voiceOrchestrator.on('state', (state) => {\r\n this._state = state;\r\n this._isSpeaking = state === 'speaking';\r\n });\r\n }\r\n\r\n /** Disconnect voice (speaker + listener + interruption). */\r\n async disconnectVoice(): Promise<void> {\r\n if (this.voiceOrchestrator) {\r\n this.disconnectFrameSource();\r\n await this.voiceOrchestrator.disconnect();\r\n this.voiceOrchestrator = null;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // State setters\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Set blendshapes directly (alternative to connectFrameSource). */\r\n setFrame(blendshapes: Float32Array): void {\r\n this.currentBlendshapes = blendshapes;\r\n }\r\n\r\n /** Set emotion (string preset like 'happy' or EmotionWeights object). */\r\n setEmotion(emotion: string | EmotionWeights): void {\r\n this._emotion = emotion;\r\n }\r\n\r\n /** Set whether the avatar is currently speaking (drives mouth emphasis). */\r\n setSpeaking(speaking: boolean): void {\r\n this._isSpeaking = speaking;\r\n }\r\n\r\n /** Set conversational state (idle, listening, thinking, speaking). */\r\n setState(state: ConversationalState): void {\r\n this._state = state;\r\n }\r\n\r\n /** Set audio energy level (0-1, drives emphasis/gesture intensity). */\r\n setAudioEnergy(energy: number): void {\r\n this._audioEnergy = energy;\r\n }\r\n\r\n /** Update character expression profile at runtime. */\r\n setProfile(profile: CharacterProfile): void {\r\n this.controller.setProfile(profile);\r\n }\r\n\r\n /**\r\n * Set the active camera for gaze tracking.\r\n * Required when using autoUpdate. Can also be passed directly to update().\r\n */\r\n setCamera(camera: Camera): void {\r\n this._camera = camera;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Accessors\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Access underlying FaceCompositor for advanced use. */\r\n get compositor() {\r\n return this.controller.compositor;\r\n }\r\n\r\n /** Access SceneDiscoveryResult (meshes, bones, morph entries). */\r\n get parts(): SceneDiscoveryResult {\r\n return this.discovery;\r\n }\r\n\r\n /** Whether the scene has any mapped morph targets. */\r\n get hasMorphTargets(): boolean {\r\n return this.discovery.morphEntries.length > 0;\r\n }\r\n\r\n /** Number of successfully mapped ARKit blendshapes. */\r\n get mappedBlendshapeCount(): number {\r\n return this.discovery.mappedBlendshapeCount;\r\n }\r\n\r\n /** Whether the avatar is currently speaking via TTS. */\r\n get isSpeaking(): boolean {\r\n return this._isSpeaking;\r\n }\r\n\r\n /** Whether the avatar is currently listening for speech. */\r\n get isListening(): boolean {\r\n return this._state === 'listening';\r\n }\r\n\r\n /** Current conversational state. */\r\n get conversationalState(): ConversationalState {\r\n return this._state;\r\n }\r\n\r\n /** Access the internal TTSSpeaker (null if not connected). */\r\n get speaker(): TTSSpeaker | null {\r\n return this.ttsSpeaker ?? this.voiceOrchestrator?.speaker ?? null;\r\n }\r\n\r\n /** Access the internal SpeechListener (null if not connected). */\r\n get listener(): SpeechListener | null {\r\n return this.speechListener ?? this.voiceOrchestrator?.listener ?? null;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Lifecycle\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Reset all state (smoothing, life layer, emotions). */\r\n reset(): void {\r\n this.currentBlendshapes = null;\r\n this._emotion = null;\r\n this._isSpeaking = false;\r\n this._state = 'idle';\r\n this._audioEnergy = 0;\r\n this.controller.reset();\r\n }\r\n\r\n /** Disconnect all voice resources, frame sources, unregister render loop, dispose controller. */\r\n async dispose(): Promise<void> {\r\n await this.disconnectVoice();\r\n await this.disconnectSpeaker();\r\n await this.disconnectListener();\r\n this.disconnectFrameSource();\r\n if (this.renderCallback) {\r\n this.scene.unregisterBeforeRender(this.renderCallback);\r\n this.renderCallback = null;\r\n }\r\n this.controller.dispose();\r\n logger.debug('Disposed');\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal\r\n // ---------------------------------------------------------------------------\r\n\r\n private registerAutoUpdate(): void {\r\n this.lastTime = getClock().now();\r\n this.renderCallback = () => {\r\n const now = getClock().now();\r\n const delta = (now - this.lastTime) / 1000;\r\n this.lastTime = now;\r\n if (this._camera) {\r\n this.update(delta, this._camera);\r\n }\r\n };\r\n this.scene.registerBeforeRender(this.renderCallback);\r\n }\r\n}\r\n\r\n","/**\n * SceneDiscovery — Discover avatar structure from a Babylon.js scene graph.\n *\n * Finds morph targets, head/neck/eye bones, and pre-computes a fast index\n * mapping from LAM_BLENDSHAPES order to per-mesh morph target indices.\n *\n * @category Babylon\n */\n\nimport { LAM_BLENDSHAPES, createLogger } from '@omote/core';\n\nconst logger = createLogger('SceneDiscovery');\nimport type { AbstractMesh, TransformNode } from '@babylonjs/core';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MorphIndexEntry {\n mesh: AbstractMesh;\n /** indices[lamIndex] = morphTargetIndex (or -1 if not found) */\n indices: Int16Array;\n}\n\nexport interface SceneDiscoveryResult {\n /** All meshes under the root that have a MorphTargetManager */\n meshes: AbstractMesh[];\n /** Head bone (TransformNode named 'Head') or null */\n headBone: TransformNode | null;\n /** Neck bone (TransformNode named 'Neck') or null */\n neckBone: TransformNode | null;\n /** Left eye bone (TransformNode named 'LeftEye') or null */\n leftEyeBone: TransformNode | null;\n /** Right eye bone (TransformNode named 'RightEye') or null */\n rightEyeBone: TransformNode | null;\n /** Pre-computed morph index mapping per mesh */\n morphEntries: MorphIndexEntry[];\n /** Primary face mesh (prefers 'Head_Mesh', falls back to first with morph targets) */\n faceMesh: AbstractMesh | null;\n /** Total number of ARKit blendshapes successfully mapped across all meshes */\n mappedBlendshapeCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Normalize morph target name by stripping common prefixes */\nfunction normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n}\n\n/** Case-insensitive bone name matching */\nconst BONE_PATTERNS: Record<string, RegExp> = {\n head: /^head$/i,\n neck: /^neck$/i,\n leftEye: /^lefteye$/i,\n rightEye: /^righteye$/i,\n};\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Discover avatar structure from a Babylon.js root node.\n *\n * Traverses the scene graph under `root` to find:\n * - Meshes with MorphTargetManagers\n * - Head, neck, and eye bones\n * - Pre-computed LAM_BLENDSHAPES -> morph target index mapping\n *\n * @param root - The root AbstractMesh (typically loaded via SceneLoader)\n * @returns SceneDiscoveryResult with all discovered components\n */\nexport function discoverScene(root: AbstractMesh): SceneDiscoveryResult {\n const meshes: AbstractMesh[] = [];\n const morphEntries: MorphIndexEntry[] = [];\n let faceMesh: AbstractMesh | null = null;\n let mappedBlendshapeCount = 0;\n\n // ---- Morph target discovery ----\n\n // Collect all candidate meshes (root + children)\n const candidates: AbstractMesh[] = [root];\n if (root.getChildMeshes) {\n candidates.push(...root.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n meshes.push(mesh);\n\n // Prefer mesh named 'Head_Mesh' as the face mesh\n if (!faceMesh || mesh.name === 'Head_Mesh') {\n faceMesh = mesh;\n }\n\n // Build normalized name -> morph target index lookup for this mesh\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalized = normalizeName(morphTarget.name);\n morphLookup.set(normalized, t);\n }\n\n // Pre-compute Int16Array mapping: lamIndex -> morphTargetIndex (or -1)\n const indices = new Int16Array(LAM_BLENDSHAPES.length);\n let meshMapped = 0;\n for (let i = 0; i < LAM_BLENDSHAPES.length; i++) {\n const morphIdx = morphLookup.get(LAM_BLENDSHAPES[i]);\n if (morphIdx !== undefined) {\n indices[i] = morphIdx;\n meshMapped++;\n } else {\n indices[i] = -1;\n }\n }\n\n if (meshMapped > 0) {\n morphEntries.push({ mesh, indices });\n }\n mappedBlendshapeCount = Math.max(mappedBlendshapeCount, meshMapped);\n\n logger.debug(`Mesh \"${mesh.name}\": ${meshMapped}/${LAM_BLENDSHAPES.length} blendshapes mapped`);\n }\n\n // ---- Bone discovery ----\n\n let headBone: TransformNode | null = null;\n let neckBone: TransformNode | null = null;\n let leftEyeBone: TransformNode | null = null;\n let rightEyeBone: TransformNode | null = null;\n\n // getChildTransformNodes is on TransformNode (AbstractMesh extends TransformNode)\n const transformNodes = root.getChildTransformNodes(false);\n\n for (const node of transformNodes) {\n if (!headBone && BONE_PATTERNS.head.test(node.name)) {\n headBone = node;\n } else if (!neckBone && BONE_PATTERNS.neck.test(node.name)) {\n neckBone = node;\n } else if (!leftEyeBone && BONE_PATTERNS.leftEye.test(node.name)) {\n leftEyeBone = node;\n } else if (!rightEyeBone && BONE_PATTERNS.rightEye.test(node.name)) {\n rightEyeBone = node;\n }\n\n // Early exit if all bones found\n if (headBone && neckBone && leftEyeBone && rightEyeBone) break;\n }\n\n if (morphEntries.length === 0) {\n logger.warn('No morph targets found in scene');\n }\n if (!headBone) {\n logger.warn('Head bone not found in scene');\n }\n\n const boneNames = [\n headBone && 'Head',\n neckBone && 'Neck',\n leftEyeBone && 'LeftEye',\n rightEyeBone && 'RightEye',\n ].filter(Boolean);\n\n logger.info(\n `Discovery complete: ${meshes.length} mesh(es), ${mappedBlendshapeCount} mapped blendshapes, bones: [${boneNames.join(', ')}]`,\n );\n\n return {\n meshes,\n headBone,\n neckBone,\n leftEyeBone,\n rightEyeBone,\n morphEntries,\n faceMesh,\n mappedBlendshapeCount,\n };\n}\n","/**\n * BlendshapeWriter — Zero-lookup hot-path blendshape application.\n *\n * Uses pre-computed Int16Array indices from SceneDiscovery for O(1) lookups\n * per blendshape per mesh. No Map.get(), no string comparison on the hot path.\n *\n * @category Babylon\n */\n\nimport type { MorphIndexEntry } from './SceneDiscovery';\n\n/**\n * Write 52 ARKit blendshapes to Babylon.js morph targets.\n *\n * Uses pre-computed indices for zero-lookup hot path.\n * Call this every frame after CharacterController.update().\n *\n * @param blendshapes - 52-element Float32Array in LAM_BLENDSHAPES order\n * @param morphEntries - Pre-computed index mappings from discoverScene()\n */\nexport function writeBlendshapes(\n blendshapes: Float32Array,\n morphEntries: MorphIndexEntry[],\n): void {\n for (let e = 0; e < morphEntries.length; e++) {\n const { mesh, indices } = morphEntries[e];\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n for (let i = 0; i < 52; i++) {\n const morphIdx = indices[i];\n if (morphIdx >= 0) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = blendshapes[i];\n }\n }\n }\n }\n}\n","import { LAM_BLENDSHAPES, lerpBlendshapes } from '@omote/core';\nimport type { AbstractMesh, Scene } from '@babylonjs/core';\n\nexport interface BlendshapeControllerOptions {\n /** Blendshape names in order (default: LAM_BLENDSHAPES, 52 ARKit) */\n names?: readonly string[];\n /** Smoothing factor 0-1 (0 = no change, 1 = snap to target). Default: 0.7 */\n smoothing?: number;\n /** Traverse target for child meshes with MorphTargetManager automatically. Default: true */\n autoFind?: boolean;\n /** Register scene.registerBeforeRender() callback automatically. Default: false */\n autoRegister?: boolean;\n /** Called when meshes with morph targets are found */\n onMeshesFound?: (meshes: AbstractMesh[]) => void;\n}\n\nexport class BlendshapeController {\n private _meshes: AbstractMesh[] = [];\n private nameToIndex: Map<string, number>[] = [];\n private currentWeights: number[] = [];\n private blendshapeNames: readonly string[];\n private smoothing: number;\n private scene: Scene | null;\n private renderObserver: (() => void) | null = null;\n private pendingWeights: Float32Array | number[] | null = null;\n private onMeshesFound?: (meshes: AbstractMesh[]) => void;\n\n constructor(target: AbstractMesh, scene: Scene, options?: BlendshapeControllerOptions) {\n this.blendshapeNames = options?.names ?? LAM_BLENDSHAPES;\n this.smoothing = options?.smoothing ?? 0.7;\n this.scene = scene;\n this.onMeshesFound = options?.onMeshesFound;\n\n if (options?.autoFind !== false) {\n this.setTarget(target);\n }\n\n if (options?.autoRegister) {\n this.registerRenderLoop();\n }\n }\n\n get meshes(): AbstractMesh[] {\n return this._meshes;\n }\n\n /** Normalize Babylon morph target names (strip common prefixes) */\n private normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n }\n\n setTarget(target: AbstractMesh): void {\n this._meshes = [];\n this.nameToIndex = [];\n\n // Collect meshes with MorphTargetManager\n const candidates: AbstractMesh[] = [target];\n if (target.getChildMeshes) {\n candidates.push(...target.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n this._meshes.push(mesh);\n\n // Build normalized name -> morph target index lookup\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalizedName = this.normalizeName(morphTarget.name);\n morphLookup.set(normalizedName, t);\n }\n\n // Map blendshape names to morph target indices\n const map = new Map<string, number>();\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const idx = morphLookup.get(name);\n if (idx !== undefined) {\n map.set(name, idx);\n }\n }\n\n this.nameToIndex.push(map);\n }\n\n this.currentWeights = new Array(this.blendshapeNames.length).fill(0);\n\n if (this._meshes.length > 0 && this.onMeshesFound) {\n this.onMeshesFound(this._meshes);\n }\n }\n\n update(weights: Float32Array | number[]): void {\n this.currentWeights = lerpBlendshapes(this.currentWeights, weights, this.smoothing);\n\n for (let m = 0; m < this._meshes.length; m++) {\n const mesh = this._meshes[m];\n const map = this.nameToIndex[m];\n const manager = mesh.morphTargetManager;\n if (!manager || !map) continue;\n\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const morphIdx = map.get(name);\n if (morphIdx !== undefined) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = this.currentWeights[i];\n }\n }\n }\n }\n }\n\n /** Buffer weights for the next render loop tick (used with autoRegister) */\n setWeightsForNextFrame(weights: Float32Array | number[]): void {\n this.pendingWeights = weights;\n }\n\n private registerRenderLoop(): void {\n if (!this.scene) return;\n const callback = () => {\n if (this.pendingWeights) {\n this.update(this.pendingWeights);\n this.pendingWeights = null;\n }\n };\n this.scene.registerBeforeRender(callback);\n this.renderObserver = () => {\n this.scene?.unregisterBeforeRender(callback);\n };\n }\n\n dispose(): void {\n if (this.renderObserver) {\n this.renderObserver();\n this.renderObserver = null;\n }\n this._meshes = [];\n this.nameToIndex = [];\n this.currentWeights = [];\n this.scene = null;\n }\n}\n"],"mappings":"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","names":["import_core","logger","import_core"]}
package/dist/index.d.cts CHANGED
@@ -111,6 +111,8 @@ declare class OmoteAvatar {
111
111
  * @param tts - TTS backend (e.g., KokoroTTSInference, ElevenLabs adapter)
112
112
  * @param config - A2E, expression profile, and playback configuration
113
113
  */
114
+ /** Warm up AudioContext for iOS/Safari autoplay policy. Call from user gesture. */
115
+ warmup(): Promise<void>;
114
116
  connectSpeaker(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void>;
115
117
  /**
116
118
  * Synthesize text and play with lip sync.
package/dist/index.d.ts CHANGED
@@ -111,6 +111,8 @@ declare class OmoteAvatar {
111
111
  * @param tts - TTS backend (e.g., KokoroTTSInference, ElevenLabs adapter)
112
112
  * @param config - A2E, expression profile, and playback configuration
113
113
  */
114
+ /** Warm up AudioContext for iOS/Safari autoplay policy. Call from user gesture. */
115
+ warmup(): Promise<void>;
114
116
  connectSpeaker(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void>;
115
117
  /**
116
118
  * Synthesize text and play with lip sync.
package/dist/index.js CHANGED
@@ -4,7 +4,8 @@ import {
4
4
  TTSSpeaker,
5
5
  SpeechListener,
6
6
  VoiceOrchestrator,
7
- createLogger as createLogger2
7
+ createLogger as createLogger2,
8
+ getClock
8
9
  } from "@omote/core";
9
10
 
10
11
  // src/SceneDiscovery.ts
@@ -122,7 +123,7 @@ function writeBlendshapes(blendshapes, morphEntries) {
122
123
  }
123
124
 
124
125
  // src/OmoteAvatar.ts
125
- var logger2 = createLogger2("OmoteAvatar");
126
+ var logger2 = createLogger2("OmoteAvatar.Babylon");
126
127
  var OmoteAvatar = class {
127
128
  constructor(options) {
128
129
  // State
@@ -252,6 +253,10 @@ var OmoteAvatar = class {
252
253
  * @param tts - TTS backend (e.g., KokoroTTSInference, ElevenLabs adapter)
253
254
  * @param config - A2E, expression profile, and playback configuration
254
255
  */
256
+ /** Warm up AudioContext for iOS/Safari autoplay policy. Call from user gesture. */
257
+ async warmup() {
258
+ if (this.ttsSpeaker) await this.ttsSpeaker.warmup();
259
+ }
255
260
  async connectSpeaker(tts, config) {
256
261
  await this.disconnectSpeaker();
257
262
  this.ttsSpeaker = new TTSSpeaker();
@@ -520,9 +525,9 @@ var OmoteAvatar = class {
520
525
  // Internal
521
526
  // ---------------------------------------------------------------------------
522
527
  registerAutoUpdate() {
523
- this.lastTime = performance.now();
528
+ this.lastTime = getClock().now();
524
529
  this.renderCallback = () => {
525
- const now = performance.now();
530
+ const now = getClock().now();
526
531
  const delta = (now - this.lastTime) / 1e3;
527
532
  this.lastTime = now;
528
533
  if (this._camera) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/OmoteAvatar.ts","../src/SceneDiscovery.ts","../src/BlendshapeWriter.ts","../src/BlendshapeController.ts"],"sourcesContent":["/**\n * OmoteAvatar — Full-featured Babylon.js avatar controller.\n *\n * Owns CharacterController, SceneDiscovery, and BlendshapeWriter.\n * Mirrors the Three.js OmoteAvatar API for consistency across adapters.\n *\n * Usage:\n * ```typescript\n * import { OmoteAvatar } from '@omote/babylon';\n * import { createA2E, PlaybackPipeline } from '@omote/core';\n *\n * const avatar = new OmoteAvatar({ target: mesh, scene });\n * avatar.setCamera(camera);\n *\n * // Connect a frame source (PlaybackPipeline, MicLipSync, etc.)\n * const pipeline = new PlaybackPipeline({ lam: createA2E() });\n * avatar.connectFrameSource(pipeline);\n *\n * // Option A: manual update in render loop\n * scene.registerBeforeRender(() => avatar.update(engine.getDeltaTime() / 1000, camera));\n *\n * // Option B: autoUpdate (registers scene.registerBeforeRender internally)\n * const avatar = new OmoteAvatar({ target: mesh, scene, autoUpdate: true });\n * avatar.setCamera(camera);\n * ```\n *\n * @example Speaker integration\n * ```typescript\n * const avatar = new OmoteAvatar({ target: mesh, scene });\n * await avatar.connectSpeaker(myTTSBackend, { profile: { mouth: 1.2 } });\n * await avatar.speak(\"Hello world!\"); // lip-syncs automatically\n * ```\n *\n * @category Babylon\n */\n\nimport {\n CharacterController,\n TTSSpeaker,\n SpeechListener,\n VoiceOrchestrator,\n createLogger,\n} from '@omote/core';\nimport type {\n CharacterControllerConfig,\n CharacterProfile,\n EmotionWeights,\n ConversationalState,\n FaceCompositorConfig,\n FrameSource,\n TTSSpeakerConfig,\n TTSBackend,\n SpeechListenerConfig,\n TranscriptResult,\n VoiceOrchestratorConfig,\n} from '@omote/core';\n\nconst logger = createLogger('OmoteAvatar');\nimport type { AbstractMesh, Scene, Camera } from '@babylonjs/core';\n\nimport { discoverScene, type SceneDiscoveryResult } from './SceneDiscovery';\nimport { writeBlendshapes } from './BlendshapeWriter';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n// Re-export FrameSource from @omote/core for backward compatibility\nexport type { FrameSource } from '@omote/core';\n\nexport interface OmoteAvatarOptions {\n /** Root mesh of the avatar (typically loaded via SceneLoader) */\n target: AbstractMesh;\n /** Babylon.js scene */\n scene: Scene;\n /** FaceCompositor configuration */\n compositor?: FaceCompositorConfig;\n /** Gaze tracking configuration */\n gaze?: CharacterControllerConfig['gaze'];\n /**\n * Register scene.registerBeforeRender() for automatic update.\n * Requires setCamera() to be called before the first frame.\n * Default: false\n */\n autoUpdate?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// OmoteAvatar\n// ---------------------------------------------------------------------------\n\nexport class OmoteAvatar {\n private readonly controller: CharacterController;\n private readonly discovery: SceneDiscoveryResult;\n private readonly scene: Scene;\n\n // State\n private currentBlendshapes: Float32Array | null = null;\n private _emotion: string | EmotionWeights | null = null;\n private _isSpeaking = false;\n private _state: ConversationalState = 'idle';\n private _audioEnergy = 0;\n private _camera: Camera | null = null;\n\n // Frame source connection\n private frameSourceCallback: ((frame: { blendshapes: Float32Array }) => void) | null = null;\n private connectedSource: FrameSource | null = null;\n\n // TTS integration\n private ttsSpeaker: TTSSpeaker | null = null;\n\n // Speech listener\n private speechListener: SpeechListener | null = null;\n\n // Voice orchestrator\n private voiceOrchestrator: VoiceOrchestrator | null = null;\n\n // Auto-update\n private renderCallback: (() => void) | null = null;\n private lastTime = 0;\n\n constructor(options: OmoteAvatarOptions) {\n this.scene = options.scene;\n this.discovery = discoverScene(options.target);\n this.controller = new CharacterController({\n compositor: options.compositor,\n gaze: options.gaze,\n });\n\n if (this.discovery.morphEntries.length === 0) {\n logger.warn('No morph targets found — blendshape animation will have no effect');\n }\n if (!this.discovery.headBone) {\n logger.warn('Head bone not found — gaze tracking will be disabled');\n }\n logger.info(\n `Initialized: ${this.discovery.meshes.length} mesh(es), ${this.discovery.mappedBlendshapeCount} mapped blendshapes, headBone=${!!this.discovery.headBone}`,\n );\n\n if (options.autoUpdate) {\n this.registerAutoUpdate();\n }\n }\n\n // ---------------------------------------------------------------------------\n // Frame update\n // ---------------------------------------------------------------------------\n\n /**\n * Call each frame with delta time and camera.\n *\n * Runs CharacterController (compositor, gaze, procedural life) then writes\n * blendshapes to morph targets and applies head rotation.\n *\n * If using autoUpdate, this is called automatically via scene.registerBeforeRender().\n *\n * @param delta - Time since last frame in seconds\n * @param camera - Active Babylon.js camera (for gaze tracking)\n * @param avatarRotationY - Optional avatar Y rotation in radians for gaze compensation\n */\n update(delta: number, camera: Camera, avatarRotationY?: number): void {\n // Get camera world position (globalPosition handles parented cameras)\n const camPos = camera.globalPosition;\n const cameraWorldPos = { x: camPos.x, y: camPos.y, z: camPos.z };\n\n // Get head bone world position + quaternion (if available)\n let headWorldPos: { x: number; y: number; z: number } | undefined;\n let headWorldQuat: { x: number; y: number; z: number; w: number } | undefined;\n if (this.discovery.headBone) {\n const wp = this.discovery.headBone.getAbsolutePosition();\n headWorldPos = { x: wp.x, y: wp.y, z: wp.z };\n const rq = this.discovery.headBone.absoluteRotationQuaternion;\n if (rq) {\n headWorldQuat = { x: rq.x, y: rq.y, z: rq.z, w: rq.w };\n }\n }\n\n // Run CharacterController (compositor + gaze + life layer)\n const output = this.controller.update({\n deltaTime: delta,\n baseBlendshapes: this.currentBlendshapes,\n emotion: this._emotion,\n isSpeaking: this._isSpeaking,\n state: this._state,\n audioEnergy: this._audioEnergy,\n cameraWorldPos,\n headWorldPos,\n headWorldQuat,\n avatarRotationY: avatarRotationY ?? 0,\n });\n\n // Write blendshapes to morph targets\n writeBlendshapes(output.blendshapes, this.discovery.morphEntries);\n\n // Apply head rotation delta\n if (this.discovery.headBone) {\n this.discovery.headBone.rotation.y = output.headDelta.yaw;\n this.discovery.headBone.rotation.x = output.headDelta.pitch;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Frame source connection\n // ---------------------------------------------------------------------------\n\n /**\n * Connect a frame source (PlaybackPipeline, MicLipSync, etc.).\n *\n * Automatically listens for 'frame' events and stores the latest blendshapes.\n * Only one source can be connected at a time; calling again disconnects the previous.\n */\n connectFrameSource(source: FrameSource): void {\n // If connecting a different source while TTS is active, stop current speak\n if (this.ttsSpeaker && source !== this.ttsSpeaker.frameSource) {\n this.ttsSpeaker.stop();\n }\n\n this.disconnectFrameSource();\n this.frameSourceCallback = (frame: { blendshapes: Float32Array; emotion?: string }) => {\n this.currentBlendshapes = frame.blendshapes;\n if (frame.emotion !== undefined) {\n this._emotion = frame.emotion;\n }\n };\n source.on('frame', this.frameSourceCallback);\n this.connectedSource = source;\n logger.debug('Frame source connected');\n }\n\n /** Disconnect the current frame source (if any). */\n disconnectFrameSource(): void {\n if (this.connectedSource && this.frameSourceCallback) {\n this.connectedSource.off?.('frame', this.frameSourceCallback);\n logger.debug('Frame source disconnected');\n }\n this.connectedSource = null;\n this.frameSourceCallback = null;\n }\n\n // ---------------------------------------------------------------------------\n // Speaker (TTS → lip sync)\n // ---------------------------------------------------------------------------\n\n /**\n * Connect a TTS backend for speak() / streamText() support.\n * Loads LAM model and creates internal PlaybackPipeline.\n *\n * @param tts - TTS backend (e.g., KokoroTTSInference, ElevenLabs adapter)\n * @param config - A2E, expression profile, and playback configuration\n */\n async connectSpeaker(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\n await this.disconnectSpeaker();\n this.ttsSpeaker = new TTSSpeaker();\n await this.ttsSpeaker.connect(tts, config);\n this.connectFrameSource(this.ttsSpeaker.frameSource!);\n }\n\n /**\n * Synthesize text and play with lip sync.\n * Auto-aborts previous speak if still in progress.\n *\n * @param text - Text to synthesize\n * @param options - Optional voice override and abort signal\n */\n async speak(text: string, options?: { signal?: AbortSignal; voice?: string }): Promise<void> {\n if (this.voiceOrchestrator) {\n await this.voiceOrchestrator.speak(text, options);\n return;\n }\n if (!this.ttsSpeaker) {\n throw new Error('No speaker connected. Call connectSpeaker() first.');\n }\n this._isSpeaking = true;\n this._state = 'speaking';\n try {\n await this.ttsSpeaker.speak(text, options);\n } finally {\n this._isSpeaking = false;\n if (this._state === 'speaking') {\n this._state = 'idle';\n }\n }\n }\n\n /**\n * Stream LLM tokens with sentence-buffered TTS + lip sync.\n * Returns a sink: call push(token) for each token, end() when done.\n */\n async streamText(options?: { signal?: AbortSignal; voice?: string }): Promise<{\n push: (token: string) => void;\n end: () => Promise<void>;\n }> {\n if (this.voiceOrchestrator) {\n return this.voiceOrchestrator.streamText(options);\n }\n if (!this.ttsSpeaker) {\n throw new Error('No speaker connected. Call connectSpeaker() first.');\n }\n this._isSpeaking = true;\n this._state = 'speaking';\n const stream = await this.ttsSpeaker.streamText(options ?? {});\n return {\n push: stream.push,\n end: async () => {\n try { await stream.end(); }\n finally { this._isSpeaking = false; if (this._state === 'speaking') this._state = 'idle'; }\n },\n };\n }\n\n /** Stop current TTS playback. */\n stopSpeaking(): void {\n if (this.voiceOrchestrator) {\n this.voiceOrchestrator.stopSpeaking();\n return;\n }\n this.ttsSpeaker?.stop();\n }\n\n /** Disconnect speaker and dispose its resources. */\n async disconnectSpeaker(): Promise<void> {\n if (this.ttsSpeaker) {\n this.disconnectFrameSource();\n await this.ttsSpeaker.dispose();\n this.ttsSpeaker = null;\n }\n }\n\n /** @deprecated Use connectSpeaker(). Will be removed in v1.0. */\n async connectTTS(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\n return this.connectSpeaker(tts, config);\n }\n\n /** @deprecated Use disconnectSpeaker(). Will be removed in v1.0. */\n async disconnectTTS(): Promise<void> {\n return this.disconnectSpeaker();\n }\n\n // ---------------------------------------------------------------------------\n // Listener (mic → VAD → ASR → transcript)\n // ---------------------------------------------------------------------------\n\n /**\n * Connect a speech listener for startListening() / onTranscript() support.\n * Loads ASR + VAD models.\n */\n async connectListener(config?: SpeechListenerConfig): Promise<void> {\n await this.disconnectListener();\n this.speechListener = new SpeechListener(config);\n await this.speechListener.loadModels();\n }\n\n /** Start listening for user speech. Requires connectListener() or connectVoice() first. */\n async startListening(): Promise<void> {\n if (this.voiceOrchestrator) {\n await this.voiceOrchestrator.startListening();\n return;\n }\n if (!this.speechListener) {\n throw new Error('No listener connected. Call connectListener() first.');\n }\n this._state = 'listening';\n await this.speechListener.start();\n }\n\n /** Stop listening. */\n stopListening(): void {\n if (this.voiceOrchestrator) {\n this.voiceOrchestrator.stopListening();\n return;\n }\n this.speechListener?.stop();\n if (this._state === 'listening') this._state = 'idle';\n }\n\n /**\n * Subscribe to transcript events. Returns an unsubscribe function.\n * Requires connectListener() first.\n */\n onTranscript(callback: (result: TranscriptResult) => void): () => void {\n const listener = this.speechListener ?? this.voiceOrchestrator?.listener;\n if (!listener) {\n throw new Error('No listener connected. Call connectListener() or connectVoice() first.');\n }\n listener.on('transcript', callback);\n return () => { listener.off?.('transcript', callback); };\n }\n\n /** Disconnect listener and dispose its resources. */\n async disconnectListener(): Promise<void> {\n if (this.speechListener) {\n await this.speechListener.dispose();\n this.speechListener = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Voice (combined speaker + listener + interruption)\n // ---------------------------------------------------------------------------\n\n /**\n * Connect voice with automatic speaker + listener + interruption wiring.\n * Supports both local TTS (mode: 'local') and cloud TTS (mode: 'cloud').\n * Does NOT auto-start listening — call startListening() when ready.\n *\n * Backward compatible: `mode` defaults to 'local' when not specified.\n */\n async connectVoice(config: VoiceOrchestratorConfig): Promise<void> {\n await this.disconnectVoice();\n this.voiceOrchestrator = new VoiceOrchestrator();\n await this.voiceOrchestrator.connect(config);\n\n // Connect frame source from orchestrator\n if (this.voiceOrchestrator.frameSource) {\n this.connectFrameSource(this.voiceOrchestrator.frameSource);\n }\n\n // Sync state from orchestrator → avatar\n this.voiceOrchestrator.on('state', (state) => {\n this._state = state;\n this._isSpeaking = state === 'speaking';\n });\n }\n\n /** Disconnect voice (speaker + listener + interruption). */\n async disconnectVoice(): Promise<void> {\n if (this.voiceOrchestrator) {\n this.disconnectFrameSource();\n await this.voiceOrchestrator.disconnect();\n this.voiceOrchestrator = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // State setters\n // ---------------------------------------------------------------------------\n\n /** Set blendshapes directly (alternative to connectFrameSource). */\n setFrame(blendshapes: Float32Array): void {\n this.currentBlendshapes = blendshapes;\n }\n\n /** Set emotion (string preset like 'happy' or EmotionWeights object). */\n setEmotion(emotion: string | EmotionWeights): void {\n this._emotion = emotion;\n }\n\n /** Set whether the avatar is currently speaking (drives mouth emphasis). */\n setSpeaking(speaking: boolean): void {\n this._isSpeaking = speaking;\n }\n\n /** Set conversational state (idle, listening, thinking, speaking). */\n setState(state: ConversationalState): void {\n this._state = state;\n }\n\n /** Set audio energy level (0-1, drives emphasis/gesture intensity). */\n setAudioEnergy(energy: number): void {\n this._audioEnergy = energy;\n }\n\n /** Update character expression profile at runtime. */\n setProfile(profile: CharacterProfile): void {\n this.controller.setProfile(profile);\n }\n\n /**\n * Set the active camera for gaze tracking.\n * Required when using autoUpdate. Can also be passed directly to update().\n */\n setCamera(camera: Camera): void {\n this._camera = camera;\n }\n\n // ---------------------------------------------------------------------------\n // Accessors\n // ---------------------------------------------------------------------------\n\n /** Access underlying FaceCompositor for advanced use. */\n get compositor() {\n return this.controller.compositor;\n }\n\n /** Access SceneDiscoveryResult (meshes, bones, morph entries). */\n get parts(): SceneDiscoveryResult {\n return this.discovery;\n }\n\n /** Whether the scene has any mapped morph targets. */\n get hasMorphTargets(): boolean {\n return this.discovery.morphEntries.length > 0;\n }\n\n /** Number of successfully mapped ARKit blendshapes. */\n get mappedBlendshapeCount(): number {\n return this.discovery.mappedBlendshapeCount;\n }\n\n /** Whether the avatar is currently speaking via TTS. */\n get isSpeaking(): boolean {\n return this._isSpeaking;\n }\n\n /** Whether the avatar is currently listening for speech. */\n get isListening(): boolean {\n return this._state === 'listening';\n }\n\n /** Current conversational state. */\n get conversationalState(): ConversationalState {\n return this._state;\n }\n\n /** Access the internal TTSSpeaker (null if not connected). */\n get speaker(): TTSSpeaker | null {\n return this.ttsSpeaker ?? this.voiceOrchestrator?.speaker ?? null;\n }\n\n /** Access the internal SpeechListener (null if not connected). */\n get listener(): SpeechListener | null {\n return this.speechListener ?? this.voiceOrchestrator?.listener ?? null;\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n /** Reset all state (smoothing, life layer, emotions). */\n reset(): void {\n this.currentBlendshapes = null;\n this._emotion = null;\n this._isSpeaking = false;\n this._state = 'idle';\n this._audioEnergy = 0;\n this.controller.reset();\n }\n\n /** Disconnect all voice resources, frame sources, unregister render loop, dispose controller. */\n async dispose(): Promise<void> {\n await this.disconnectVoice();\n await this.disconnectSpeaker();\n await this.disconnectListener();\n this.disconnectFrameSource();\n if (this.renderCallback) {\n this.scene.unregisterBeforeRender(this.renderCallback);\n this.renderCallback = null;\n }\n this.controller.dispose();\n logger.debug('Disposed');\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private registerAutoUpdate(): void {\n this.lastTime = performance.now();\n this.renderCallback = () => {\n const now = performance.now();\n const delta = (now - this.lastTime) / 1000;\n this.lastTime = now;\n if (this._camera) {\n this.update(delta, this._camera);\n }\n };\n this.scene.registerBeforeRender(this.renderCallback);\n }\n}\n\n","/**\n * SceneDiscovery — Discover avatar structure from a Babylon.js scene graph.\n *\n * Finds morph targets, head/neck/eye bones, and pre-computes a fast index\n * mapping from LAM_BLENDSHAPES order to per-mesh morph target indices.\n *\n * @category Babylon\n */\n\nimport { LAM_BLENDSHAPES, createLogger } from '@omote/core';\n\nconst logger = createLogger('SceneDiscovery');\nimport type { AbstractMesh, TransformNode } from '@babylonjs/core';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MorphIndexEntry {\n mesh: AbstractMesh;\n /** indices[lamIndex] = morphTargetIndex (or -1 if not found) */\n indices: Int16Array;\n}\n\nexport interface SceneDiscoveryResult {\n /** All meshes under the root that have a MorphTargetManager */\n meshes: AbstractMesh[];\n /** Head bone (TransformNode named 'Head') or null */\n headBone: TransformNode | null;\n /** Neck bone (TransformNode named 'Neck') or null */\n neckBone: TransformNode | null;\n /** Left eye bone (TransformNode named 'LeftEye') or null */\n leftEyeBone: TransformNode | null;\n /** Right eye bone (TransformNode named 'RightEye') or null */\n rightEyeBone: TransformNode | null;\n /** Pre-computed morph index mapping per mesh */\n morphEntries: MorphIndexEntry[];\n /** Primary face mesh (prefers 'Head_Mesh', falls back to first with morph targets) */\n faceMesh: AbstractMesh | null;\n /** Total number of ARKit blendshapes successfully mapped across all meshes */\n mappedBlendshapeCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Normalize morph target name by stripping common prefixes */\nfunction normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n}\n\n/** Case-insensitive bone name matching */\nconst BONE_PATTERNS: Record<string, RegExp> = {\n head: /^head$/i,\n neck: /^neck$/i,\n leftEye: /^lefteye$/i,\n rightEye: /^righteye$/i,\n};\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Discover avatar structure from a Babylon.js root node.\n *\n * Traverses the scene graph under `root` to find:\n * - Meshes with MorphTargetManagers\n * - Head, neck, and eye bones\n * - Pre-computed LAM_BLENDSHAPES -> morph target index mapping\n *\n * @param root - The root AbstractMesh (typically loaded via SceneLoader)\n * @returns SceneDiscoveryResult with all discovered components\n */\nexport function discoverScene(root: AbstractMesh): SceneDiscoveryResult {\n const meshes: AbstractMesh[] = [];\n const morphEntries: MorphIndexEntry[] = [];\n let faceMesh: AbstractMesh | null = null;\n let mappedBlendshapeCount = 0;\n\n // ---- Morph target discovery ----\n\n // Collect all candidate meshes (root + children)\n const candidates: AbstractMesh[] = [root];\n if (root.getChildMeshes) {\n candidates.push(...root.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n meshes.push(mesh);\n\n // Prefer mesh named 'Head_Mesh' as the face mesh\n if (!faceMesh || mesh.name === 'Head_Mesh') {\n faceMesh = mesh;\n }\n\n // Build normalized name -> morph target index lookup for this mesh\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalized = normalizeName(morphTarget.name);\n morphLookup.set(normalized, t);\n }\n\n // Pre-compute Int16Array mapping: lamIndex -> morphTargetIndex (or -1)\n const indices = new Int16Array(LAM_BLENDSHAPES.length);\n let meshMapped = 0;\n for (let i = 0; i < LAM_BLENDSHAPES.length; i++) {\n const morphIdx = morphLookup.get(LAM_BLENDSHAPES[i]);\n if (morphIdx !== undefined) {\n indices[i] = morphIdx;\n meshMapped++;\n } else {\n indices[i] = -1;\n }\n }\n\n if (meshMapped > 0) {\n morphEntries.push({ mesh, indices });\n }\n mappedBlendshapeCount = Math.max(mappedBlendshapeCount, meshMapped);\n\n logger.debug(`Mesh \"${mesh.name}\": ${meshMapped}/${LAM_BLENDSHAPES.length} blendshapes mapped`);\n }\n\n // ---- Bone discovery ----\n\n let headBone: TransformNode | null = null;\n let neckBone: TransformNode | null = null;\n let leftEyeBone: TransformNode | null = null;\n let rightEyeBone: TransformNode | null = null;\n\n // getChildTransformNodes is on TransformNode (AbstractMesh extends TransformNode)\n const transformNodes = root.getChildTransformNodes(false);\n\n for (const node of transformNodes) {\n if (!headBone && BONE_PATTERNS.head.test(node.name)) {\n headBone = node;\n } else if (!neckBone && BONE_PATTERNS.neck.test(node.name)) {\n neckBone = node;\n } else if (!leftEyeBone && BONE_PATTERNS.leftEye.test(node.name)) {\n leftEyeBone = node;\n } else if (!rightEyeBone && BONE_PATTERNS.rightEye.test(node.name)) {\n rightEyeBone = node;\n }\n\n // Early exit if all bones found\n if (headBone && neckBone && leftEyeBone && rightEyeBone) break;\n }\n\n if (morphEntries.length === 0) {\n logger.warn('No morph targets found in scene');\n }\n if (!headBone) {\n logger.warn('Head bone not found in scene');\n }\n\n const boneNames = [\n headBone && 'Head',\n neckBone && 'Neck',\n leftEyeBone && 'LeftEye',\n rightEyeBone && 'RightEye',\n ].filter(Boolean);\n\n logger.info(\n `Discovery complete: ${meshes.length} mesh(es), ${mappedBlendshapeCount} mapped blendshapes, bones: [${boneNames.join(', ')}]`,\n );\n\n return {\n meshes,\n headBone,\n neckBone,\n leftEyeBone,\n rightEyeBone,\n morphEntries,\n faceMesh,\n mappedBlendshapeCount,\n };\n}\n","/**\n * BlendshapeWriter — Zero-lookup hot-path blendshape application.\n *\n * Uses pre-computed Int16Array indices from SceneDiscovery for O(1) lookups\n * per blendshape per mesh. No Map.get(), no string comparison on the hot path.\n *\n * @category Babylon\n */\n\nimport type { MorphIndexEntry } from './SceneDiscovery';\n\n/**\n * Write 52 ARKit blendshapes to Babylon.js morph targets.\n *\n * Uses pre-computed indices for zero-lookup hot path.\n * Call this every frame after CharacterController.update().\n *\n * @param blendshapes - 52-element Float32Array in LAM_BLENDSHAPES order\n * @param morphEntries - Pre-computed index mappings from discoverScene()\n */\nexport function writeBlendshapes(\n blendshapes: Float32Array,\n morphEntries: MorphIndexEntry[],\n): void {\n for (let e = 0; e < morphEntries.length; e++) {\n const { mesh, indices } = morphEntries[e];\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n for (let i = 0; i < 52; i++) {\n const morphIdx = indices[i];\n if (morphIdx >= 0) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = blendshapes[i];\n }\n }\n }\n }\n}\n","import { LAM_BLENDSHAPES, lerpBlendshapes } from '@omote/core';\nimport type { AbstractMesh, Scene } from '@babylonjs/core';\n\nexport interface BlendshapeControllerOptions {\n /** Blendshape names in order (default: LAM_BLENDSHAPES, 52 ARKit) */\n names?: readonly string[];\n /** Smoothing factor 0-1 (0 = no change, 1 = snap to target). Default: 0.7 */\n smoothing?: number;\n /** Traverse target for child meshes with MorphTargetManager automatically. Default: true */\n autoFind?: boolean;\n /** Register scene.registerBeforeRender() callback automatically. Default: false */\n autoRegister?: boolean;\n /** Called when meshes with morph targets are found */\n onMeshesFound?: (meshes: AbstractMesh[]) => void;\n}\n\nexport class BlendshapeController {\n private _meshes: AbstractMesh[] = [];\n private nameToIndex: Map<string, number>[] = [];\n private currentWeights: number[] = [];\n private blendshapeNames: readonly string[];\n private smoothing: number;\n private scene: Scene | null;\n private renderObserver: (() => void) | null = null;\n private pendingWeights: Float32Array | number[] | null = null;\n private onMeshesFound?: (meshes: AbstractMesh[]) => void;\n\n constructor(target: AbstractMesh, scene: Scene, options?: BlendshapeControllerOptions) {\n this.blendshapeNames = options?.names ?? LAM_BLENDSHAPES;\n this.smoothing = options?.smoothing ?? 0.7;\n this.scene = scene;\n this.onMeshesFound = options?.onMeshesFound;\n\n if (options?.autoFind !== false) {\n this.setTarget(target);\n }\n\n if (options?.autoRegister) {\n this.registerRenderLoop();\n }\n }\n\n get meshes(): AbstractMesh[] {\n return this._meshes;\n }\n\n /** Normalize Babylon morph target names (strip common prefixes) */\n private normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n }\n\n setTarget(target: AbstractMesh): void {\n this._meshes = [];\n this.nameToIndex = [];\n\n // Collect meshes with MorphTargetManager\n const candidates: AbstractMesh[] = [target];\n if (target.getChildMeshes) {\n candidates.push(...target.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n this._meshes.push(mesh);\n\n // Build normalized name -> morph target index lookup\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalizedName = this.normalizeName(morphTarget.name);\n morphLookup.set(normalizedName, t);\n }\n\n // Map blendshape names to morph target indices\n const map = new Map<string, number>();\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const idx = morphLookup.get(name);\n if (idx !== undefined) {\n map.set(name, idx);\n }\n }\n\n this.nameToIndex.push(map);\n }\n\n this.currentWeights = new Array(this.blendshapeNames.length).fill(0);\n\n if (this._meshes.length > 0 && this.onMeshesFound) {\n this.onMeshesFound(this._meshes);\n }\n }\n\n update(weights: Float32Array | number[]): void {\n this.currentWeights = lerpBlendshapes(this.currentWeights, weights, this.smoothing);\n\n for (let m = 0; m < this._meshes.length; m++) {\n const mesh = this._meshes[m];\n const map = this.nameToIndex[m];\n const manager = mesh.morphTargetManager;\n if (!manager || !map) continue;\n\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const morphIdx = map.get(name);\n if (morphIdx !== undefined) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = this.currentWeights[i];\n }\n }\n }\n }\n }\n\n /** Buffer weights for the next render loop tick (used with autoRegister) */\n setWeightsForNextFrame(weights: Float32Array | number[]): void {\n this.pendingWeights = weights;\n }\n\n private registerRenderLoop(): void {\n if (!this.scene) return;\n const callback = () => {\n if (this.pendingWeights) {\n this.update(this.pendingWeights);\n this.pendingWeights = null;\n }\n };\n this.scene.registerBeforeRender(callback);\n this.renderObserver = () => {\n this.scene?.unregisterBeforeRender(callback);\n };\n }\n\n dispose(): void {\n if (this.renderObserver) {\n this.renderObserver();\n this.renderObserver = null;\n }\n this._meshes = [];\n this.nameToIndex = [];\n this.currentWeights = [];\n this.scene = null;\n }\n}\n"],"mappings":"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","names":["createLogger","logger","createLogger","LAM_BLENDSHAPES"]}
1
+ {"version":3,"sources":["../src/OmoteAvatar.ts","../src/SceneDiscovery.ts","../src/BlendshapeWriter.ts","../src/BlendshapeController.ts"],"sourcesContent":["/**\r\n * OmoteAvatar — Full-featured Babylon.js avatar controller.\r\n *\r\n * Owns CharacterController, SceneDiscovery, and BlendshapeWriter.\r\n * Mirrors the Three.js OmoteAvatar API for consistency across adapters.\r\n *\r\n * Usage:\r\n * ```typescript\r\n * import { OmoteAvatar } from '@omote/babylon';\r\n * import { createA2E, PlaybackPipeline } from '@omote/core';\r\n *\r\n * const avatar = new OmoteAvatar({ target: mesh, scene });\r\n * avatar.setCamera(camera);\r\n *\r\n * // Connect a frame source (PlaybackPipeline, MicLipSync, etc.)\r\n * const pipeline = new PlaybackPipeline({ lam: createA2E() });\r\n * avatar.connectFrameSource(pipeline);\r\n *\r\n * // Option A: manual update in render loop\r\n * scene.registerBeforeRender(() => avatar.update(engine.getDeltaTime() / 1000, camera));\r\n *\r\n * // Option B: autoUpdate (registers scene.registerBeforeRender internally)\r\n * const avatar = new OmoteAvatar({ target: mesh, scene, autoUpdate: true });\r\n * avatar.setCamera(camera);\r\n * ```\r\n *\r\n * @example Speaker integration\r\n * ```typescript\r\n * const avatar = new OmoteAvatar({ target: mesh, scene });\r\n * await avatar.connectSpeaker(myTTSBackend, { profile: { mouth: 1.2 } });\r\n * await avatar.speak(\"Hello world!\"); // lip-syncs automatically\r\n * ```\r\n *\r\n * @category Babylon\r\n */\r\n\r\nimport {\r\n CharacterController,\r\n TTSSpeaker,\r\n SpeechListener,\r\n VoiceOrchestrator,\r\n createLogger,\r\n getClock,\r\n} from '@omote/core';\r\nimport type {\r\n CharacterControllerConfig,\r\n CharacterProfile,\r\n EmotionWeights,\r\n ConversationalState,\r\n FaceCompositorConfig,\r\n FrameSource,\r\n TTSSpeakerConfig,\r\n TTSBackend,\r\n SpeechListenerConfig,\r\n TranscriptResult,\r\n VoiceOrchestratorConfig,\r\n} from '@omote/core';\r\n\r\nconst logger = createLogger('OmoteAvatar.Babylon');\r\nimport type { AbstractMesh, Scene, Camera } from '@babylonjs/core';\r\n\r\nimport { discoverScene, type SceneDiscoveryResult } from './SceneDiscovery';\r\nimport { writeBlendshapes } from './BlendshapeWriter';\r\n\r\n// ---------------------------------------------------------------------------\r\n// Types\r\n// ---------------------------------------------------------------------------\r\n\r\n// Re-export FrameSource from @omote/core for backward compatibility\r\nexport type { FrameSource } from '@omote/core';\r\n\r\nexport interface OmoteAvatarOptions {\r\n /** Root mesh of the avatar (typically loaded via SceneLoader) */\r\n target: AbstractMesh;\r\n /** Babylon.js scene */\r\n scene: Scene;\r\n /** FaceCompositor configuration */\r\n compositor?: FaceCompositorConfig;\r\n /** Gaze tracking configuration */\r\n gaze?: CharacterControllerConfig['gaze'];\r\n /**\r\n * Register scene.registerBeforeRender() for automatic update.\r\n * Requires setCamera() to be called before the first frame.\r\n * Default: false\r\n */\r\n autoUpdate?: boolean;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// OmoteAvatar\r\n// ---------------------------------------------------------------------------\r\n\r\nexport class OmoteAvatar {\r\n private readonly controller: CharacterController;\r\n private readonly discovery: SceneDiscoveryResult;\r\n private readonly scene: Scene;\r\n\r\n // State\r\n private currentBlendshapes: Float32Array | null = null;\r\n private _emotion: string | EmotionWeights | null = null;\r\n private _isSpeaking = false;\r\n private _state: ConversationalState = 'idle';\r\n private _audioEnergy = 0;\r\n private _camera: Camera | null = null;\r\n\r\n // Frame source connection\r\n private frameSourceCallback: ((frame: { blendshapes: Float32Array }) => void) | null = null;\r\n private connectedSource: FrameSource | null = null;\r\n\r\n // TTS integration\r\n private ttsSpeaker: TTSSpeaker | null = null;\r\n\r\n // Speech listener\r\n private speechListener: SpeechListener | null = null;\r\n\r\n // Voice orchestrator\r\n private voiceOrchestrator: VoiceOrchestrator | null = null;\r\n\r\n // Auto-update\r\n private renderCallback: (() => void) | null = null;\r\n private lastTime = 0;\r\n\r\n constructor(options: OmoteAvatarOptions) {\r\n this.scene = options.scene;\r\n this.discovery = discoverScene(options.target);\r\n this.controller = new CharacterController({\r\n compositor: options.compositor,\r\n gaze: options.gaze,\r\n });\r\n\r\n if (this.discovery.morphEntries.length === 0) {\r\n logger.warn('No morph targets found — blendshape animation will have no effect');\r\n }\r\n if (!this.discovery.headBone) {\r\n logger.warn('Head bone not found — gaze tracking will be disabled');\r\n }\r\n logger.info(\r\n `Initialized: ${this.discovery.meshes.length} mesh(es), ${this.discovery.mappedBlendshapeCount} mapped blendshapes, headBone=${!!this.discovery.headBone}`,\r\n );\r\n\r\n if (options.autoUpdate) {\r\n this.registerAutoUpdate();\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Frame update\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Call each frame with delta time and camera.\r\n *\r\n * Runs CharacterController (compositor, gaze, procedural life) then writes\r\n * blendshapes to morph targets and applies head rotation.\r\n *\r\n * If using autoUpdate, this is called automatically via scene.registerBeforeRender().\r\n *\r\n * @param delta - Time since last frame in seconds\r\n * @param camera - Active Babylon.js camera (for gaze tracking)\r\n * @param avatarRotationY - Optional avatar Y rotation in radians for gaze compensation\r\n */\r\n update(delta: number, camera: Camera, avatarRotationY?: number): void {\r\n // Get camera world position (globalPosition handles parented cameras)\r\n const camPos = camera.globalPosition;\r\n const cameraWorldPos = { x: camPos.x, y: camPos.y, z: camPos.z };\r\n\r\n // Get head bone world position + quaternion (if available)\r\n let headWorldPos: { x: number; y: number; z: number } | undefined;\r\n let headWorldQuat: { x: number; y: number; z: number; w: number } | undefined;\r\n if (this.discovery.headBone) {\r\n const wp = this.discovery.headBone.getAbsolutePosition();\r\n headWorldPos = { x: wp.x, y: wp.y, z: wp.z };\r\n const rq = this.discovery.headBone.absoluteRotationQuaternion;\r\n if (rq) {\r\n headWorldQuat = { x: rq.x, y: rq.y, z: rq.z, w: rq.w };\r\n }\r\n }\r\n\r\n // Run CharacterController (compositor + gaze + life layer)\r\n const output = this.controller.update({\r\n deltaTime: delta,\r\n baseBlendshapes: this.currentBlendshapes,\r\n emotion: this._emotion,\r\n isSpeaking: this._isSpeaking,\r\n state: this._state,\r\n audioEnergy: this._audioEnergy,\r\n cameraWorldPos,\r\n headWorldPos,\r\n headWorldQuat,\r\n avatarRotationY: avatarRotationY ?? 0,\r\n });\r\n\r\n // Write blendshapes to morph targets\r\n writeBlendshapes(output.blendshapes, this.discovery.morphEntries);\r\n\r\n // Apply head rotation delta\r\n if (this.discovery.headBone) {\r\n this.discovery.headBone.rotation.y = output.headDelta.yaw;\r\n this.discovery.headBone.rotation.x = output.headDelta.pitch;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Frame source connection\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect a frame source (PlaybackPipeline, MicLipSync, etc.).\r\n *\r\n * Automatically listens for 'frame' events and stores the latest blendshapes.\r\n * Only one source can be connected at a time; calling again disconnects the previous.\r\n */\r\n connectFrameSource(source: FrameSource): void {\r\n // If connecting a different source while TTS is active, stop current speak\r\n if (this.ttsSpeaker && source !== this.ttsSpeaker.frameSource) {\r\n this.ttsSpeaker.stop();\r\n }\r\n\r\n this.disconnectFrameSource();\r\n this.frameSourceCallback = (frame: { blendshapes: Float32Array; emotion?: string }) => {\r\n this.currentBlendshapes = frame.blendshapes;\r\n if (frame.emotion !== undefined) {\r\n this._emotion = frame.emotion;\r\n }\r\n };\r\n source.on('frame', this.frameSourceCallback);\r\n this.connectedSource = source;\r\n logger.debug('Frame source connected');\r\n }\r\n\r\n /** Disconnect the current frame source (if any). */\r\n disconnectFrameSource(): void {\r\n if (this.connectedSource && this.frameSourceCallback) {\r\n this.connectedSource.off?.('frame', this.frameSourceCallback);\r\n logger.debug('Frame source disconnected');\r\n }\r\n this.connectedSource = null;\r\n this.frameSourceCallback = null;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Speaker (TTS → lip sync)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect a TTS backend for speak() / streamText() support.\r\n * Loads LAM model and creates internal PlaybackPipeline.\r\n *\r\n * @param tts - TTS backend (e.g., KokoroTTSInference, ElevenLabs adapter)\r\n * @param config - A2E, expression profile, and playback configuration\r\n */\r\n /** Warm up AudioContext for iOS/Safari autoplay policy. Call from user gesture. */\r\n async warmup(): Promise<void> {\r\n if (this.ttsSpeaker) await this.ttsSpeaker.warmup();\r\n }\r\n\r\n async connectSpeaker(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\r\n await this.disconnectSpeaker();\r\n this.ttsSpeaker = new TTSSpeaker();\r\n await this.ttsSpeaker.connect(tts, config);\r\n this.connectFrameSource(this.ttsSpeaker.frameSource!);\r\n }\r\n\r\n /**\r\n * Synthesize text and play with lip sync.\r\n * Auto-aborts previous speak if still in progress.\r\n *\r\n * @param text - Text to synthesize\r\n * @param options - Optional voice override and abort signal\r\n */\r\n async speak(text: string, options?: { signal?: AbortSignal; voice?: string }): Promise<void> {\r\n if (this.voiceOrchestrator) {\r\n await this.voiceOrchestrator.speak(text, options);\r\n return;\r\n }\r\n if (!this.ttsSpeaker) {\r\n throw new Error('No speaker connected. Call connectSpeaker() first.');\r\n }\r\n this._isSpeaking = true;\r\n this._state = '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') {\r\n this._state = 'idle';\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Stream LLM tokens with sentence-buffered TTS + lip sync.\r\n * Returns a sink: call push(token) for each token, end() when done.\r\n */\r\n async streamText(options?: { signal?: AbortSignal; voice?: string }): Promise<{\r\n push: (token: string) => void;\r\n end: () => Promise<void>;\r\n }> {\r\n if (this.voiceOrchestrator) {\r\n return this.voiceOrchestrator.streamText(options);\r\n }\r\n if (!this.ttsSpeaker) {\r\n throw new Error('No speaker connected. Call connectSpeaker() first.');\r\n }\r\n this._isSpeaking = true;\r\n this._state = '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 { this._isSpeaking = false; if (this._state === 'speaking') this._state = 'idle'; }\r\n },\r\n };\r\n }\r\n\r\n /** Stop current TTS playback. */\r\n stopSpeaking(): void {\r\n if (this.voiceOrchestrator) {\r\n this.voiceOrchestrator.stopSpeaking();\r\n return;\r\n }\r\n this.ttsSpeaker?.stop();\r\n }\r\n\r\n /** Disconnect speaker and dispose its resources. */\r\n async disconnectSpeaker(): Promise<void> {\r\n if (this.ttsSpeaker) {\r\n this.disconnectFrameSource();\r\n await this.ttsSpeaker.dispose();\r\n this.ttsSpeaker = null;\r\n }\r\n }\r\n\r\n /** @deprecated Use connectSpeaker(). Will be removed in v1.0. */\r\n async connectTTS(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\r\n return this.connectSpeaker(tts, config);\r\n }\r\n\r\n /** @deprecated Use disconnectSpeaker(). Will be removed in v1.0. */\r\n async disconnectTTS(): Promise<void> {\r\n return this.disconnectSpeaker();\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Listener (mic → VAD → ASR → transcript)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect a speech listener for startListening() / onTranscript() support.\r\n * Loads ASR + VAD models.\r\n */\r\n async connectListener(config?: SpeechListenerConfig): Promise<void> {\r\n await this.disconnectListener();\r\n this.speechListener = new SpeechListener(config);\r\n await this.speechListener.loadModels();\r\n }\r\n\r\n /** Start listening for user speech. Requires connectListener() or connectVoice() first. */\r\n async startListening(): Promise<void> {\r\n if (this.voiceOrchestrator) {\r\n await this.voiceOrchestrator.startListening();\r\n return;\r\n }\r\n if (!this.speechListener) {\r\n throw new Error('No listener connected. Call connectListener() first.');\r\n }\r\n this._state = 'listening';\r\n await this.speechListener.start();\r\n }\r\n\r\n /** Stop listening. */\r\n stopListening(): void {\r\n if (this.voiceOrchestrator) {\r\n this.voiceOrchestrator.stopListening();\r\n return;\r\n }\r\n this.speechListener?.stop();\r\n if (this._state === 'listening') this._state = 'idle';\r\n }\r\n\r\n /**\r\n * Subscribe to transcript events. Returns an unsubscribe function.\r\n * Requires connectListener() first.\r\n */\r\n onTranscript(callback: (result: TranscriptResult) => void): () => void {\r\n const listener = this.speechListener ?? this.voiceOrchestrator?.listener;\r\n if (!listener) {\r\n throw new Error('No listener connected. Call connectListener() or connectVoice() first.');\r\n }\r\n listener.on('transcript', callback);\r\n return () => { listener.off?.('transcript', callback); };\r\n }\r\n\r\n /** Disconnect listener and dispose its resources. */\r\n async disconnectListener(): Promise<void> {\r\n if (this.speechListener) {\r\n await this.speechListener.dispose();\r\n this.speechListener = null;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Voice (combined speaker + listener + interruption)\r\n // ---------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect voice with automatic speaker + listener + interruption wiring.\r\n * Supports both local TTS (mode: 'local') and cloud TTS (mode: 'cloud').\r\n * Does NOT auto-start listening — call startListening() when ready.\r\n *\r\n * Backward compatible: `mode` defaults to 'local' when not specified.\r\n */\r\n async connectVoice(config: VoiceOrchestratorConfig): Promise<void> {\r\n await this.disconnectVoice();\r\n this.voiceOrchestrator = new VoiceOrchestrator();\r\n await this.voiceOrchestrator.connect(config);\r\n\r\n // Connect frame source from orchestrator\r\n if (this.voiceOrchestrator.frameSource) {\r\n this.connectFrameSource(this.voiceOrchestrator.frameSource);\r\n }\r\n\r\n // Sync state from orchestrator → avatar\r\n this.voiceOrchestrator.on('state', (state) => {\r\n this._state = state;\r\n this._isSpeaking = state === 'speaking';\r\n });\r\n }\r\n\r\n /** Disconnect voice (speaker + listener + interruption). */\r\n async disconnectVoice(): Promise<void> {\r\n if (this.voiceOrchestrator) {\r\n this.disconnectFrameSource();\r\n await this.voiceOrchestrator.disconnect();\r\n this.voiceOrchestrator = null;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // State setters\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Set blendshapes directly (alternative to connectFrameSource). */\r\n setFrame(blendshapes: Float32Array): void {\r\n this.currentBlendshapes = blendshapes;\r\n }\r\n\r\n /** Set emotion (string preset like 'happy' or EmotionWeights object). */\r\n setEmotion(emotion: string | EmotionWeights): void {\r\n this._emotion = emotion;\r\n }\r\n\r\n /** Set whether the avatar is currently speaking (drives mouth emphasis). */\r\n setSpeaking(speaking: boolean): void {\r\n this._isSpeaking = speaking;\r\n }\r\n\r\n /** Set conversational state (idle, listening, thinking, speaking). */\r\n setState(state: ConversationalState): void {\r\n this._state = state;\r\n }\r\n\r\n /** Set audio energy level (0-1, drives emphasis/gesture intensity). */\r\n setAudioEnergy(energy: number): void {\r\n this._audioEnergy = energy;\r\n }\r\n\r\n /** Update character expression profile at runtime. */\r\n setProfile(profile: CharacterProfile): void {\r\n this.controller.setProfile(profile);\r\n }\r\n\r\n /**\r\n * Set the active camera for gaze tracking.\r\n * Required when using autoUpdate. Can also be passed directly to update().\r\n */\r\n setCamera(camera: Camera): void {\r\n this._camera = camera;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Accessors\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Access underlying FaceCompositor for advanced use. */\r\n get compositor() {\r\n return this.controller.compositor;\r\n }\r\n\r\n /** Access SceneDiscoveryResult (meshes, bones, morph entries). */\r\n get parts(): SceneDiscoveryResult {\r\n return this.discovery;\r\n }\r\n\r\n /** Whether the scene has any mapped morph targets. */\r\n get hasMorphTargets(): boolean {\r\n return this.discovery.morphEntries.length > 0;\r\n }\r\n\r\n /** Number of successfully mapped ARKit blendshapes. */\r\n get mappedBlendshapeCount(): number {\r\n return this.discovery.mappedBlendshapeCount;\r\n }\r\n\r\n /** Whether the avatar is currently speaking via TTS. */\r\n get isSpeaking(): boolean {\r\n return this._isSpeaking;\r\n }\r\n\r\n /** Whether the avatar is currently listening for speech. */\r\n get isListening(): boolean {\r\n return this._state === 'listening';\r\n }\r\n\r\n /** Current conversational state. */\r\n get conversationalState(): ConversationalState {\r\n return this._state;\r\n }\r\n\r\n /** Access the internal TTSSpeaker (null if not connected). */\r\n get speaker(): TTSSpeaker | null {\r\n return this.ttsSpeaker ?? this.voiceOrchestrator?.speaker ?? null;\r\n }\r\n\r\n /** Access the internal SpeechListener (null if not connected). */\r\n get listener(): SpeechListener | null {\r\n return this.speechListener ?? this.voiceOrchestrator?.listener ?? null;\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Lifecycle\r\n // ---------------------------------------------------------------------------\r\n\r\n /** Reset all state (smoothing, life layer, emotions). */\r\n reset(): void {\r\n this.currentBlendshapes = null;\r\n this._emotion = null;\r\n this._isSpeaking = false;\r\n this._state = 'idle';\r\n this._audioEnergy = 0;\r\n this.controller.reset();\r\n }\r\n\r\n /** Disconnect all voice resources, frame sources, unregister render loop, dispose controller. */\r\n async dispose(): Promise<void> {\r\n await this.disconnectVoice();\r\n await this.disconnectSpeaker();\r\n await this.disconnectListener();\r\n this.disconnectFrameSource();\r\n if (this.renderCallback) {\r\n this.scene.unregisterBeforeRender(this.renderCallback);\r\n this.renderCallback = null;\r\n }\r\n this.controller.dispose();\r\n logger.debug('Disposed');\r\n }\r\n\r\n // ---------------------------------------------------------------------------\r\n // Internal\r\n // ---------------------------------------------------------------------------\r\n\r\n private registerAutoUpdate(): void {\r\n this.lastTime = getClock().now();\r\n this.renderCallback = () => {\r\n const now = getClock().now();\r\n const delta = (now - this.lastTime) / 1000;\r\n this.lastTime = now;\r\n if (this._camera) {\r\n this.update(delta, this._camera);\r\n }\r\n };\r\n this.scene.registerBeforeRender(this.renderCallback);\r\n }\r\n}\r\n\r\n","/**\n * SceneDiscovery — Discover avatar structure from a Babylon.js scene graph.\n *\n * Finds morph targets, head/neck/eye bones, and pre-computes a fast index\n * mapping from LAM_BLENDSHAPES order to per-mesh morph target indices.\n *\n * @category Babylon\n */\n\nimport { LAM_BLENDSHAPES, createLogger } from '@omote/core';\n\nconst logger = createLogger('SceneDiscovery');\nimport type { AbstractMesh, TransformNode } from '@babylonjs/core';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MorphIndexEntry {\n mesh: AbstractMesh;\n /** indices[lamIndex] = morphTargetIndex (or -1 if not found) */\n indices: Int16Array;\n}\n\nexport interface SceneDiscoveryResult {\n /** All meshes under the root that have a MorphTargetManager */\n meshes: AbstractMesh[];\n /** Head bone (TransformNode named 'Head') or null */\n headBone: TransformNode | null;\n /** Neck bone (TransformNode named 'Neck') or null */\n neckBone: TransformNode | null;\n /** Left eye bone (TransformNode named 'LeftEye') or null */\n leftEyeBone: TransformNode | null;\n /** Right eye bone (TransformNode named 'RightEye') or null */\n rightEyeBone: TransformNode | null;\n /** Pre-computed morph index mapping per mesh */\n morphEntries: MorphIndexEntry[];\n /** Primary face mesh (prefers 'Head_Mesh', falls back to first with morph targets) */\n faceMesh: AbstractMesh | null;\n /** Total number of ARKit blendshapes successfully mapped across all meshes */\n mappedBlendshapeCount: number;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Normalize morph target name by stripping common prefixes */\nfunction normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n}\n\n/** Case-insensitive bone name matching */\nconst BONE_PATTERNS: Record<string, RegExp> = {\n head: /^head$/i,\n neck: /^neck$/i,\n leftEye: /^lefteye$/i,\n rightEye: /^righteye$/i,\n};\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Discover avatar structure from a Babylon.js root node.\n *\n * Traverses the scene graph under `root` to find:\n * - Meshes with MorphTargetManagers\n * - Head, neck, and eye bones\n * - Pre-computed LAM_BLENDSHAPES -> morph target index mapping\n *\n * @param root - The root AbstractMesh (typically loaded via SceneLoader)\n * @returns SceneDiscoveryResult with all discovered components\n */\nexport function discoverScene(root: AbstractMesh): SceneDiscoveryResult {\n const meshes: AbstractMesh[] = [];\n const morphEntries: MorphIndexEntry[] = [];\n let faceMesh: AbstractMesh | null = null;\n let mappedBlendshapeCount = 0;\n\n // ---- Morph target discovery ----\n\n // Collect all candidate meshes (root + children)\n const candidates: AbstractMesh[] = [root];\n if (root.getChildMeshes) {\n candidates.push(...root.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n meshes.push(mesh);\n\n // Prefer mesh named 'Head_Mesh' as the face mesh\n if (!faceMesh || mesh.name === 'Head_Mesh') {\n faceMesh = mesh;\n }\n\n // Build normalized name -> morph target index lookup for this mesh\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalized = normalizeName(morphTarget.name);\n morphLookup.set(normalized, t);\n }\n\n // Pre-compute Int16Array mapping: lamIndex -> morphTargetIndex (or -1)\n const indices = new Int16Array(LAM_BLENDSHAPES.length);\n let meshMapped = 0;\n for (let i = 0; i < LAM_BLENDSHAPES.length; i++) {\n const morphIdx = morphLookup.get(LAM_BLENDSHAPES[i]);\n if (morphIdx !== undefined) {\n indices[i] = morphIdx;\n meshMapped++;\n } else {\n indices[i] = -1;\n }\n }\n\n if (meshMapped > 0) {\n morphEntries.push({ mesh, indices });\n }\n mappedBlendshapeCount = Math.max(mappedBlendshapeCount, meshMapped);\n\n logger.debug(`Mesh \"${mesh.name}\": ${meshMapped}/${LAM_BLENDSHAPES.length} blendshapes mapped`);\n }\n\n // ---- Bone discovery ----\n\n let headBone: TransformNode | null = null;\n let neckBone: TransformNode | null = null;\n let leftEyeBone: TransformNode | null = null;\n let rightEyeBone: TransformNode | null = null;\n\n // getChildTransformNodes is on TransformNode (AbstractMesh extends TransformNode)\n const transformNodes = root.getChildTransformNodes(false);\n\n for (const node of transformNodes) {\n if (!headBone && BONE_PATTERNS.head.test(node.name)) {\n headBone = node;\n } else if (!neckBone && BONE_PATTERNS.neck.test(node.name)) {\n neckBone = node;\n } else if (!leftEyeBone && BONE_PATTERNS.leftEye.test(node.name)) {\n leftEyeBone = node;\n } else if (!rightEyeBone && BONE_PATTERNS.rightEye.test(node.name)) {\n rightEyeBone = node;\n }\n\n // Early exit if all bones found\n if (headBone && neckBone && leftEyeBone && rightEyeBone) break;\n }\n\n if (morphEntries.length === 0) {\n logger.warn('No morph targets found in scene');\n }\n if (!headBone) {\n logger.warn('Head bone not found in scene');\n }\n\n const boneNames = [\n headBone && 'Head',\n neckBone && 'Neck',\n leftEyeBone && 'LeftEye',\n rightEyeBone && 'RightEye',\n ].filter(Boolean);\n\n logger.info(\n `Discovery complete: ${meshes.length} mesh(es), ${mappedBlendshapeCount} mapped blendshapes, bones: [${boneNames.join(', ')}]`,\n );\n\n return {\n meshes,\n headBone,\n neckBone,\n leftEyeBone,\n rightEyeBone,\n morphEntries,\n faceMesh,\n mappedBlendshapeCount,\n };\n}\n","/**\n * BlendshapeWriter — Zero-lookup hot-path blendshape application.\n *\n * Uses pre-computed Int16Array indices from SceneDiscovery for O(1) lookups\n * per blendshape per mesh. No Map.get(), no string comparison on the hot path.\n *\n * @category Babylon\n */\n\nimport type { MorphIndexEntry } from './SceneDiscovery';\n\n/**\n * Write 52 ARKit blendshapes to Babylon.js morph targets.\n *\n * Uses pre-computed indices for zero-lookup hot path.\n * Call this every frame after CharacterController.update().\n *\n * @param blendshapes - 52-element Float32Array in LAM_BLENDSHAPES order\n * @param morphEntries - Pre-computed index mappings from discoverScene()\n */\nexport function writeBlendshapes(\n blendshapes: Float32Array,\n morphEntries: MorphIndexEntry[],\n): void {\n for (let e = 0; e < morphEntries.length; e++) {\n const { mesh, indices } = morphEntries[e];\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n for (let i = 0; i < 52; i++) {\n const morphIdx = indices[i];\n if (morphIdx >= 0) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = blendshapes[i];\n }\n }\n }\n }\n}\n","import { LAM_BLENDSHAPES, lerpBlendshapes } from '@omote/core';\nimport type { AbstractMesh, Scene } from '@babylonjs/core';\n\nexport interface BlendshapeControllerOptions {\n /** Blendshape names in order (default: LAM_BLENDSHAPES, 52 ARKit) */\n names?: readonly string[];\n /** Smoothing factor 0-1 (0 = no change, 1 = snap to target). Default: 0.7 */\n smoothing?: number;\n /** Traverse target for child meshes with MorphTargetManager automatically. Default: true */\n autoFind?: boolean;\n /** Register scene.registerBeforeRender() callback automatically. Default: false */\n autoRegister?: boolean;\n /** Called when meshes with morph targets are found */\n onMeshesFound?: (meshes: AbstractMesh[]) => void;\n}\n\nexport class BlendshapeController {\n private _meshes: AbstractMesh[] = [];\n private nameToIndex: Map<string, number>[] = [];\n private currentWeights: number[] = [];\n private blendshapeNames: readonly string[];\n private smoothing: number;\n private scene: Scene | null;\n private renderObserver: (() => void) | null = null;\n private pendingWeights: Float32Array | number[] | null = null;\n private onMeshesFound?: (meshes: AbstractMesh[]) => void;\n\n constructor(target: AbstractMesh, scene: Scene, options?: BlendshapeControllerOptions) {\n this.blendshapeNames = options?.names ?? LAM_BLENDSHAPES;\n this.smoothing = options?.smoothing ?? 0.7;\n this.scene = scene;\n this.onMeshesFound = options?.onMeshesFound;\n\n if (options?.autoFind !== false) {\n this.setTarget(target);\n }\n\n if (options?.autoRegister) {\n this.registerRenderLoop();\n }\n }\n\n get meshes(): AbstractMesh[] {\n return this._meshes;\n }\n\n /** Normalize Babylon morph target names (strip common prefixes) */\n private normalizeName(name: string): string {\n return name.replace(/^morph_/i, '').replace(/^blendshape\\./i, '');\n }\n\n setTarget(target: AbstractMesh): void {\n this._meshes = [];\n this.nameToIndex = [];\n\n // Collect meshes with MorphTargetManager\n const candidates: AbstractMesh[] = [target];\n if (target.getChildMeshes) {\n candidates.push(...target.getChildMeshes(false));\n }\n\n for (const mesh of candidates) {\n const manager = mesh.morphTargetManager;\n if (!manager) continue;\n\n this._meshes.push(mesh);\n\n // Build normalized name -> morph target index lookup\n const morphLookup = new Map<string, number>();\n const numTargets = manager.numTargets;\n for (let t = 0; t < numTargets; t++) {\n const morphTarget = manager.getTarget(t);\n const normalizedName = this.normalizeName(morphTarget.name);\n morphLookup.set(normalizedName, t);\n }\n\n // Map blendshape names to morph target indices\n const map = new Map<string, number>();\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const idx = morphLookup.get(name);\n if (idx !== undefined) {\n map.set(name, idx);\n }\n }\n\n this.nameToIndex.push(map);\n }\n\n this.currentWeights = new Array(this.blendshapeNames.length).fill(0);\n\n if (this._meshes.length > 0 && this.onMeshesFound) {\n this.onMeshesFound(this._meshes);\n }\n }\n\n update(weights: Float32Array | number[]): void {\n this.currentWeights = lerpBlendshapes(this.currentWeights, weights, this.smoothing);\n\n for (let m = 0; m < this._meshes.length; m++) {\n const mesh = this._meshes[m];\n const map = this.nameToIndex[m];\n const manager = mesh.morphTargetManager;\n if (!manager || !map) continue;\n\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const morphIdx = map.get(name);\n if (morphIdx !== undefined) {\n const target = manager.getTarget(morphIdx);\n if (target) {\n target.influence = this.currentWeights[i];\n }\n }\n }\n }\n }\n\n /** Buffer weights for the next render loop tick (used with autoRegister) */\n setWeightsForNextFrame(weights: Float32Array | number[]): void {\n this.pendingWeights = weights;\n }\n\n private registerRenderLoop(): void {\n if (!this.scene) return;\n const callback = () => {\n if (this.pendingWeights) {\n this.update(this.pendingWeights);\n this.pendingWeights = null;\n }\n };\n this.scene.registerBeforeRender(callback);\n this.renderObserver = () => {\n this.scene?.unregisterBeforeRender(callback);\n };\n }\n\n dispose(): void {\n if (this.renderObserver) {\n this.renderObserver();\n this.renderObserver = null;\n }\n this._meshes = [];\n this.nameToIndex = [];\n this.currentWeights = [];\n this.scene = null;\n }\n}\n"],"mappings":"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","names":["createLogger","logger","createLogger","LAM_BLENDSHAPES"]}
package/package.json CHANGED
@@ -1,51 +1,51 @@
1
- {
2
- "name": "@omote/babylon",
3
- "version": "0.3.1",
4
- "description": "Babylon.js adapter for Omote AI character SDK",
5
- "type": "module",
6
- "main": "./dist/index.cjs",
7
- "module": "./dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js",
13
- "require": "./dist/index.cjs"
14
- }
15
- },
16
- "publishConfig": {
17
- "access": "public"
18
- },
19
- "scripts": {
20
- "build": "tsup",
21
- "typecheck": "tsc --noEmit",
22
- "test": "vitest run",
23
- "test:watch": "vitest"
24
- },
25
- "peerDependencies": {
26
- "@omote/core": ">=0.9.0",
27
- "@babylonjs/core": ">=6.0.0"
28
- },
29
- "peerDependenciesMeta": {
30
- "@babylonjs/loaders": {
31
- "optional": true
32
- }
33
- },
34
- "devDependencies": {
35
- "@omote/core": "file:../core",
36
- "@babylonjs/core": "^8.0.0",
37
- "@babylonjs/loaders": "^8.0.0",
38
- "tsup": "^8.0.0",
39
- "typescript": "^5.3.0",
40
- "vitest": "^4.0.16"
41
- },
42
- "files": [
43
- "dist"
44
- ],
45
- "license": "MIT",
46
- "repository": {
47
- "type": "git",
48
- "url": "https://github.com/omoteai/omote.git",
49
- "directory": "packages/babylon"
50
- }
51
- }
1
+ {
2
+ "name": "@omote/babylon",
3
+ "version": "0.3.2",
4
+ "description": "Babylon.js adapter for Omote AI character SDK",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest"
24
+ },
25
+ "peerDependencies": {
26
+ "@omote/core": ">=0.9.0",
27
+ "@babylonjs/core": ">=6.0.0"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "@babylonjs/loaders": {
31
+ "optional": true
32
+ }
33
+ },
34
+ "devDependencies": {
35
+ "@omote/core": "file:../core",
36
+ "@babylonjs/core": "^8.0.0",
37
+ "@babylonjs/loaders": "^8.0.0",
38
+ "tsup": "^8.0.0",
39
+ "typescript": "^5.3.0",
40
+ "vitest": "^4.0.16"
41
+ },
42
+ "files": [
43
+ "dist"
44
+ ],
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/omoteai/omote.git",
49
+ "directory": "packages/babylon"
50
+ }
51
+ }