@omote/three 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.js CHANGED
@@ -1,11 +1,9 @@
1
1
  // src/OmoteAvatar.ts
2
2
  import {
3
3
  CharacterController,
4
- TTSSpeaker,
5
- SpeechListener,
6
- VoiceOrchestrator,
7
4
  createLogger as createLogger2
8
5
  } from "@omote/core";
6
+ import { OmoteAvatarCore } from "@omote/avatar";
9
7
  import { Vector3, Quaternion } from "three";
10
8
 
11
9
  // src/SceneDiscovery.ts
@@ -101,25 +99,18 @@ function writeBlendshapes(blendshapes, morphEntries) {
101
99
  }
102
100
 
103
101
  // src/OmoteAvatar.ts
104
- var logger2 = createLogger2("OmoteAvatar");
102
+ var logger2 = createLogger2("OmoteAvatar.Three");
105
103
  var _headWorldPos = new Vector3();
106
104
  var _camWorldPos = new Vector3();
107
105
  var _headWorldQuat = new Quaternion();
108
106
  var OmoteAvatar = class {
109
107
  constructor(options) {
108
+ // External frame source (adapter-level, handles emotion extraction)
110
109
  this.frameSourceCallback = null;
111
110
  this.connectedSource = null;
112
- // TTS integration
113
- this.ttsSpeaker = null;
114
- // Speech listener
115
- this.speechListener = null;
116
- // Voice orchestrator
117
- this.voiceOrchestrator = null;
118
111
  // State
119
112
  this.currentBlendshapes = null;
120
113
  this._emotion = null;
121
- this._isSpeaking = false;
122
- this._state = "idle";
123
114
  this._audioEnergy = 0;
124
115
  this.discovery = discoverScene(options.target);
125
116
  const controllerConfig = {
@@ -127,6 +118,13 @@ var OmoteAvatar = class {
127
118
  gaze: options.gaze
128
119
  };
129
120
  this.controller = new CharacterController(controllerConfig);
121
+ this.core = new OmoteAvatarCore();
122
+ this.core.onFrame = (frame) => {
123
+ this.currentBlendshapes = frame.blendshapes;
124
+ if (frame.emotion !== void 0) {
125
+ this._emotion = frame.emotion;
126
+ }
127
+ };
130
128
  if (this.discovery.morphEntries.length === 0) {
131
129
  logger2.warn("No morph targets found \u2014 blendshape animation will have no effect");
132
130
  }
@@ -169,8 +167,8 @@ var OmoteAvatar = class {
169
167
  deltaTime: delta,
170
168
  baseBlendshapes: this.currentBlendshapes,
171
169
  emotion: this._emotion,
172
- isSpeaking: this._isSpeaking,
173
- state: this._state,
170
+ isSpeaking: this.core.isSpeaking,
171
+ state: this.core.state,
174
172
  audioEnergy: this._audioEnergy,
175
173
  cameraWorldPos,
176
174
  headWorldPos,
@@ -184,7 +182,7 @@ var OmoteAvatar = class {
184
182
  }
185
183
  }
186
184
  // -------------------------------------------------------------------------
187
- // Frame source connection
185
+ // Frame source connection (adapter-level, with emotion extraction)
188
186
  // -------------------------------------------------------------------------
189
187
  /**
190
188
  * Connect to any frame source (PlaybackPipeline, MicLipSync, etc.).
@@ -194,9 +192,6 @@ var OmoteAvatar = class {
194
192
  * disconnects the previous one.
195
193
  */
196
194
  connectFrameSource(source) {
197
- if (this.ttsSpeaker && source !== this.ttsSpeaker.frameSource) {
198
- this.ttsSpeaker.stop();
199
- }
200
195
  this.disconnectFrameSource();
201
196
  this.frameSourceCallback = (frame) => {
202
197
  this.currentBlendshapes = frame.blendshapes;
@@ -220,88 +215,26 @@ var OmoteAvatar = class {
220
215
  this.frameSourceCallback = null;
221
216
  }
222
217
  // -------------------------------------------------------------------------
223
- // Speaker (TTS → lip sync)
218
+ // Speaker (TTS → lip sync) — delegated to OmoteAvatarCore
224
219
  // -------------------------------------------------------------------------
225
- /**
226
- * Connect a TTS backend for speak() / streamText() support.
227
- * Loads LAM model and creates internal PlaybackPipeline.
228
- *
229
- * @param tts - TTS backend (e.g., KokoroTTSInference, ElevenLabs adapter)
230
- * @param config - A2E, expression profile, and playback configuration
231
- */
220
+ /** Warm up AudioContext for iOS/Safari autoplay policy. Call from user gesture. */
221
+ async warmup() {
222
+ return this.core.warmup();
223
+ }
232
224
  async connectSpeaker(tts, config) {
233
- await this.disconnectSpeaker();
234
- this.ttsSpeaker = new TTSSpeaker();
235
- await this.ttsSpeaker.connect(tts, config);
236
- this.connectFrameSource(this.ttsSpeaker.frameSource);
225
+ return this.core.connectSpeaker(tts, config);
237
226
  }
238
- /**
239
- * Synthesize text and play with lip sync.
240
- * Auto-aborts previous speak if still in progress.
241
- *
242
- * @param text - Text to synthesize
243
- * @param options - Optional voice override and abort signal
244
- */
245
227
  async speak(text, options) {
246
- if (this.voiceOrchestrator) {
247
- await this.voiceOrchestrator.speak(text, options);
248
- return;
249
- }
250
- if (!this.ttsSpeaker) {
251
- throw new Error("No speaker connected. Call connectSpeaker() first.");
252
- }
253
- this._isSpeaking = true;
254
- this._state = "speaking";
255
- try {
256
- await this.ttsSpeaker.speak(text, options);
257
- } finally {
258
- this._isSpeaking = false;
259
- if (this._state === "speaking") {
260
- this._state = "idle";
261
- }
262
- }
228
+ return this.core.speak(text, options);
263
229
  }
264
- /**
265
- * Stream LLM tokens with sentence-buffered TTS + lip sync.
266
- * Returns a sink: call push(token) for each token, end() when done.
267
- */
268
230
  async streamText(options) {
269
- if (this.voiceOrchestrator) {
270
- return this.voiceOrchestrator.streamText(options);
271
- }
272
- if (!this.ttsSpeaker) {
273
- throw new Error("No speaker connected. Call connectSpeaker() first.");
274
- }
275
- this._isSpeaking = true;
276
- this._state = "speaking";
277
- const stream = await this.ttsSpeaker.streamText(options ?? {});
278
- return {
279
- push: stream.push,
280
- end: async () => {
281
- try {
282
- await stream.end();
283
- } finally {
284
- this._isSpeaking = false;
285
- if (this._state === "speaking") this._state = "idle";
286
- }
287
- }
288
- };
231
+ return this.core.streamText(options);
289
232
  }
290
- /** Stop current TTS playback. */
291
233
  stopSpeaking() {
292
- if (this.voiceOrchestrator) {
293
- this.voiceOrchestrator.stopSpeaking();
294
- return;
295
- }
296
- this.ttsSpeaker?.stop();
234
+ this.core.stopSpeaking();
297
235
  }
298
- /** Disconnect speaker and dispose its resources. */
299
236
  async disconnectSpeaker() {
300
- if (this.ttsSpeaker) {
301
- this.disconnectFrameSource();
302
- await this.ttsSpeaker.dispose();
303
- this.ttsSpeaker = null;
304
- }
237
+ return this.core.disconnectSpeaker();
305
238
  }
306
239
  /** @deprecated Use connectSpeaker(). Will be removed in v1.0. */
307
240
  async connectTTS(tts, config) {
@@ -312,88 +245,49 @@ var OmoteAvatar = class {
312
245
  return this.disconnectSpeaker();
313
246
  }
314
247
  // -------------------------------------------------------------------------
315
- // Listener (mic → VAD → ASR → transcript)
248
+ // Listener (mic → VAD → ASR → transcript) — delegated to OmoteAvatarCore
316
249
  // -------------------------------------------------------------------------
317
- /**
318
- * Connect a speech listener for startListening() / onTranscript() support.
319
- * Loads ASR + VAD models.
320
- */
321
250
  async connectListener(config) {
322
- await this.disconnectListener();
323
- this.speechListener = new SpeechListener(config);
324
- await this.speechListener.loadModels();
251
+ return this.core.connectListener(config);
325
252
  }
326
- /** Start listening for user speech. Requires connectListener() or connectVoice() first. */
327
253
  async startListening() {
328
- if (this.voiceOrchestrator) {
329
- await this.voiceOrchestrator.startListening();
330
- return;
331
- }
332
- if (!this.speechListener) {
333
- throw new Error("No listener connected. Call connectListener() first.");
334
- }
335
- this._state = "listening";
336
- await this.speechListener.start();
254
+ return this.core.startListening();
337
255
  }
338
- /** Stop listening. */
339
256
  stopListening() {
340
- if (this.voiceOrchestrator) {
341
- this.voiceOrchestrator.stopListening();
342
- return;
343
- }
344
- this.speechListener?.stop();
345
- if (this._state === "listening") this._state = "idle";
257
+ this.core.stopListening();
346
258
  }
347
- /**
348
- * Subscribe to transcript events. Returns an unsubscribe function.
349
- * Requires connectListener() first.
350
- */
351
259
  onTranscript(callback) {
352
- const listener = this.speechListener ?? this.voiceOrchestrator?.listener;
353
- if (!listener) {
354
- throw new Error("No listener connected. Call connectListener() or connectVoice() first.");
355
- }
356
- listener.on("transcript", callback);
357
- return () => {
358
- listener.off?.("transcript", callback);
359
- };
260
+ return this.core.onTranscript(callback);
360
261
  }
361
- /** Disconnect listener and dispose its resources. */
362
262
  async disconnectListener() {
363
- if (this.speechListener) {
364
- await this.speechListener.dispose();
365
- this.speechListener = null;
366
- }
263
+ return this.core.disconnectListener();
367
264
  }
368
265
  // -------------------------------------------------------------------------
369
- // Voice (combined speaker + listener + interruption)
266
+ // Voice (combined speaker + listener + interruption) — delegated
370
267
  // -------------------------------------------------------------------------
371
- /**
372
- * Connect voice with automatic speaker + listener + interruption wiring.
373
- * Supports both local TTS (mode: 'local') and cloud TTS (mode: 'cloud').
374
- * Does NOT auto-start listening — call startListening() when ready.
375
- *
376
- * Backward compatible: `mode` defaults to 'local' when not specified.
377
- */
378
268
  async connectVoice(config) {
379
- await this.disconnectVoice();
380
- this.voiceOrchestrator = new VoiceOrchestrator();
381
- await this.voiceOrchestrator.connect(config);
382
- if (this.voiceOrchestrator.frameSource) {
383
- this.connectFrameSource(this.voiceOrchestrator.frameSource);
384
- }
385
- this.voiceOrchestrator.on("state", (state) => {
386
- this._state = state;
387
- this._isSpeaking = state === "speaking";
388
- });
269
+ return this.core.connectVoice(config);
389
270
  }
390
- /** Disconnect voice (speaker + listener + interruption). */
391
271
  async disconnectVoice() {
392
- if (this.voiceOrchestrator) {
393
- this.disconnectFrameSource();
394
- await this.voiceOrchestrator.disconnect();
395
- this.voiceOrchestrator = null;
396
- }
272
+ return this.core.disconnectVoice();
273
+ }
274
+ // -------------------------------------------------------------------------
275
+ // Event subscriptions — delegated to OmoteAvatarCore
276
+ // -------------------------------------------------------------------------
277
+ onTranscriptEvent(callback) {
278
+ return this.core.onTranscriptEvent(callback);
279
+ }
280
+ onVoiceStateChange(callback) {
281
+ return this.core.onVoiceStateChange(callback);
282
+ }
283
+ onLoadingProgress(callback) {
284
+ return this.core.onLoadingProgress(callback);
285
+ }
286
+ onError(callback) {
287
+ return this.core.onError(callback);
288
+ }
289
+ onAudioLevel(callback) {
290
+ return this.core.onAudioLevel(callback);
397
291
  }
398
292
  // -------------------------------------------------------------------------
399
293
  // State setters
@@ -408,11 +302,11 @@ var OmoteAvatar = class {
408
302
  }
409
303
  /** Set whether the avatar is currently speaking (drives mouth animation intensity). */
410
304
  setSpeaking(speaking) {
411
- this._isSpeaking = speaking;
305
+ this.core.setSpeaking(speaking);
412
306
  }
413
307
  /** Set the conversational state (idle, listening, thinking, speaking). */
414
308
  setState(state) {
415
- this._state = state;
309
+ this.core.setState(state);
416
310
  }
417
311
  /** Set audio energy level (0-1, drives emphasis/gesture intensity). */
418
312
  setAudioEnergy(energy) {
@@ -443,23 +337,23 @@ var OmoteAvatar = class {
443
337
  }
444
338
  /** Whether the avatar is currently speaking via TTS. */
445
339
  get isSpeaking() {
446
- return this._isSpeaking;
340
+ return this.core.isSpeaking;
447
341
  }
448
342
  /** Whether the avatar is currently listening for speech. */
449
343
  get isListening() {
450
- return this._state === "listening";
344
+ return this.core.state === "listening";
451
345
  }
452
346
  /** Current conversational state. */
453
347
  get conversationalState() {
454
- return this._state;
348
+ return this.core.state;
455
349
  }
456
350
  /** Access the internal TTSSpeaker (null if not connected). */
457
351
  get speaker() {
458
- return this.ttsSpeaker ?? this.voiceOrchestrator?.speaker ?? null;
352
+ return this.core.speaker;
459
353
  }
460
354
  /** Access the internal SpeechListener (null if not connected). */
461
355
  get listener() {
462
- return this.speechListener ?? this.voiceOrchestrator?.listener ?? null;
356
+ return this.core.listener;
463
357
  }
464
358
  // -------------------------------------------------------------------------
465
359
  // Lifecycle
@@ -468,22 +362,88 @@ var OmoteAvatar = class {
468
362
  reset() {
469
363
  this.currentBlendshapes = null;
470
364
  this._emotion = null;
471
- this._isSpeaking = false;
472
- this._state = "idle";
473
365
  this._audioEnergy = 0;
366
+ this.core.reset();
474
367
  this.controller.reset();
475
368
  }
476
369
  /** Disconnect all voice resources, frame sources, and dispose the controller. */
477
370
  async dispose() {
478
- await this.disconnectVoice();
479
- await this.disconnectSpeaker();
480
- await this.disconnectListener();
371
+ await this.core.dispose();
481
372
  this.disconnectFrameSource();
482
373
  this.controller.dispose();
483
374
  logger2.debug("Disposed");
484
375
  }
485
376
  };
486
377
 
378
+ // src/createAvatar.ts
379
+ import * as THREE from "three";
380
+ import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
381
+ import { OrbitControls } from "three/addons/controls/OrbitControls.js";
382
+ async function createAvatar(config) {
383
+ const container = typeof config.container === "string" ? document.querySelector(config.container) : config.container;
384
+ if (!container) throw new Error(`Container not found: ${config.container}`);
385
+ const { clientWidth: w, clientHeight: h } = container;
386
+ const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
387
+ renderer.setSize(w, h);
388
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
389
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
390
+ container.appendChild(renderer.domElement);
391
+ const scene = new THREE.Scene();
392
+ const camera = new THREE.PerspectiveCamera(config.fov ?? 35, w / h, 0.1, 100);
393
+ camera.position.set(0, 1.5, 0.8);
394
+ scene.add(new THREE.AmbientLight(16777215, 0.5));
395
+ const dirLight = new THREE.DirectionalLight(16777215, 1);
396
+ dirLight.position.set(2, 3, 2);
397
+ scene.add(dirLight);
398
+ let controls = null;
399
+ if (config.controls !== false) {
400
+ controls = new OrbitControls(camera, renderer.domElement);
401
+ controls.target.set(0, 1.5, 0);
402
+ controls.enableDamping = true;
403
+ controls.update();
404
+ }
405
+ const gltf = await new GLTFLoader().loadAsync(config.src);
406
+ scene.add(gltf.scene);
407
+ const avatar = new OmoteAvatar({
408
+ target: gltf.scene,
409
+ gaze: { enabled: true, smoothing: 0.08 }
410
+ });
411
+ const clock = new THREE.Clock();
412
+ let animId = 0;
413
+ function animate() {
414
+ animId = requestAnimationFrame(animate);
415
+ avatar.update(clock.getDelta(), camera);
416
+ controls?.update();
417
+ renderer.render(scene, camera);
418
+ }
419
+ animate();
420
+ const ro = new ResizeObserver(() => {
421
+ const { clientWidth: rw, clientHeight: rh } = container;
422
+ camera.aspect = rw / rh;
423
+ camera.updateProjectionMatrix();
424
+ renderer.setSize(rw, rh);
425
+ });
426
+ ro.observe(container);
427
+ return {
428
+ avatar,
429
+ scene,
430
+ camera,
431
+ renderer,
432
+ controls,
433
+ animations: gltf.animations ?? [],
434
+ dispose() {
435
+ cancelAnimationFrame(animId);
436
+ ro.disconnect();
437
+ controls?.dispose();
438
+ avatar.dispose();
439
+ renderer.dispose();
440
+ if (renderer.domElement.parentElement) {
441
+ renderer.domElement.parentElement.removeChild(renderer.domElement);
442
+ }
443
+ }
444
+ };
445
+ }
446
+
487
447
  // src/BlendshapeController.ts
488
448
  import { LAM_BLENDSHAPES as LAM_BLENDSHAPES2, lerpBlendshapes } from "@omote/core";
489
449
  var BlendshapeController = class {
@@ -548,6 +508,7 @@ var BlendshapeController = class {
548
508
  export {
549
509
  BlendshapeController,
550
510
  OmoteAvatar,
511
+ createAvatar,
551
512
  discoverScene,
552
513
  writeBlendshapes
553
514
  };
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 Three.js avatar class.\n *\n * Wraps CharacterController from @omote/core with Three.js scene discovery,\n * blendshape writing, and gaze bone rotation. Drop-in avatar for any\n * Three.js app that has a render loop.\n *\n * @example\n * ```ts\n * import { OmoteAvatar } from '@omote/three';\n * import { createA2E, PlaybackPipeline } from '@omote/core';\n *\n * const avatar = new OmoteAvatar({ target: avatarModel });\n * const lam = createA2E();\n * await lam.load();\n * const pipeline = new PlaybackPipeline({ lam, sampleRate: 16000 });\n * avatar.connectFrameSource(pipeline);\n *\n * // In render loop:\n * avatar.update(delta, camera);\n * ```\n *\n * @example Speaker integration\n * ```ts\n * const avatar = new OmoteAvatar({ target: avatarModel });\n * await avatar.connectSpeaker(myTTSBackend, { profile: { mouth: 1.2 } });\n * await avatar.speak(\"Hello world!\"); // lip-syncs automatically\n * ```\n *\n * @category Three\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';\nimport { Vector3, Quaternion } from 'three';\nimport type { Camera, Object3D } from 'three';\nimport { discoverScene } from './SceneDiscovery';\nimport type { SceneDiscoveryResult } from './SceneDiscovery';\nimport { writeBlendshapes } from './BlendshapeWriter';\n\nconst logger = createLogger('OmoteAvatar');\n\n// Reusable scratch vectors — avoids per-frame allocation\nconst _headWorldPos = new Vector3();\nconst _camWorldPos = new Vector3();\nconst _headWorldQuat = new Quaternion();\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 /** Three.js Object3D (loaded GLB scene, Group, etc.) to traverse for meshes and bones. */\n target: Object3D;\n /** FaceCompositor configuration (profile, emotion, life layer). */\n compositor?: FaceCompositorConfig;\n /** Gaze tracking configuration. */\n gaze?: CharacterControllerConfig['gaze'];\n}\n\n// ---------------------------------------------------------------------------\n// OmoteAvatar\n// ---------------------------------------------------------------------------\n\nexport class OmoteAvatar {\n private readonly controller: CharacterController;\n private readonly discovery: SceneDiscoveryResult;\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 // 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\n constructor(options: OmoteAvatarOptions) {\n this.discovery = discoverScene(options.target);\n\n const controllerConfig: CharacterControllerConfig = {\n compositor: options.compositor,\n gaze: options.gaze,\n };\n this.controller = new CharacterController(controllerConfig);\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\n // -------------------------------------------------------------------------\n // Per-frame update\n // -------------------------------------------------------------------------\n\n /**\n * Call each frame in your render loop.\n *\n * Runs CharacterController (compositor + gaze + life layer), writes\n * blendshapes to morph targets, and applies head bone rotation.\n *\n * @param delta - Time since last frame in seconds\n * @param camera - The active Three.js camera (used for gaze direction)\n * @param avatarRotationY - Optional avatar Y rotation in radians for gaze compensation\n */\n update(delta: number, camera: Camera, avatarRotationY?: number): void {\n // Compute head world position + quaternion from bone\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?.getWorldPosition) {\n this.discovery.headBone.getWorldPosition(_headWorldPos);\n headWorldPos = { x: _headWorldPos.x, y: _headWorldPos.y, z: _headWorldPos.z };\n this.discovery.headBone.getWorldQuaternion(_headWorldQuat);\n headWorldQuat = { x: _headWorldQuat.x, y: _headWorldQuat.y, z: _headWorldQuat.z, w: _headWorldQuat.w };\n }\n\n // Camera world position (use getWorldPosition for parented cameras)\n camera.getWorldPosition(_camWorldPos);\n const cameraWorldPos = {\n x: _camWorldPos.x,\n y: _camWorldPos.y,\n z: _camWorldPos.z,\n };\n\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 from gaze + life layer\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 to any frame source (PlaybackPipeline, MicLipSync, etc.).\n * Listens for 'frame' events and updates current blendshapes automatically.\n *\n * Only one source can be connected at a time. Connecting a new source\n * disconnects the previous one.\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 // Disconnect existing source first\n this.disconnectFrameSource();\n\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 currently connected frame source. */\n disconnectFrameSource(): void {\n if (this.connectedSource && this.frameSourceCallback) {\n if (this.connectedSource.off) {\n this.connectedSource.off('frame', this.frameSourceCallback);\n }\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 raw blendshapes directly (alternative to connectFrameSource). */\n setFrame(blendshapes: Float32Array): void {\n this.currentBlendshapes = blendshapes;\n }\n\n /** Set the current emotion (string preset name 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 animation intensity). */\n setSpeaking(speaking: boolean): void {\n this._isSpeaking = speaking;\n }\n\n /** Set the 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 // Accessors\n // -------------------------------------------------------------------------\n\n /** Access the underlying FaceCompositor for advanced configuration. */\n get compositor() {\n return this.controller.compositor;\n }\n\n /** Access discovered scene parts (meshes, bones). */\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, and dispose the controller. */\n async dispose(): Promise<void> {\n await this.disconnectVoice();\n await this.disconnectSpeaker();\n await this.disconnectListener();\n this.disconnectFrameSource();\n this.controller.dispose();\n logger.debug('Disposed');\n }\n}\n\n","/**\n * SceneDiscovery — Traverse a Three.js scene graph to discover bones and\n * morph targets. Pure function, no React or side-effect dependencies.\n *\n * This is the CANONICAL implementation. @omote/r3f imports from here.\n *\n * Public interfaces use duck types (no direct Three.js type imports) so\n * consumers with different @types/three versions avoid type-incompatibility\n * errors in monorepo setups.\n *\n * @category Three\n */\n\nimport { LAM_BLENDSHAPES, createLogger } from '@omote/core';\n\nconst logger = createLogger('SceneDiscovery');\n\n// ---------------------------------------------------------------------------\n// Duck-typed interfaces — structurally compatible with Three.js but\n// importable without pulling in @types/three.\n// ---------------------------------------------------------------------------\n\n/** Any object with `traverse` (structurally compatible with THREE.Object3D). */\nexport interface SceneObject {\n traverse(callback: (child: any) => void): void;\n}\n\n/** Minimal shape of a skinned mesh. */\nexport interface DiscoveredMesh {\n name: string;\n isSkinnedMesh?: boolean;\n morphTargetDictionary?: Record<string, number>;\n morphTargetInfluences?: number[];\n [key: string]: any;\n}\n\n/** Minimal shape of a bone. */\nexport interface DiscoveredBone {\n name: string;\n isBone?: boolean;\n rotation: { x: number; y: number; z: number };\n getWorldPosition?(target: any): any;\n [key: string]: any;\n}\n\n/** Pre-computed morph target index array for a single mesh */\nexport interface MorphIndexEntry {\n mesh: DiscoveredMesh;\n /** indices[lamIndex] = morphTargetIndex (or -1 if not found) */\n indices: Int16Array;\n}\n\nexport interface SceneDiscoveryResult {\n meshes: DiscoveredMesh[];\n headBone: DiscoveredBone | null;\n neckBone: DiscoveredBone | null;\n leftEyeBone: DiscoveredBone | null;\n rightEyeBone: DiscoveredBone | null;\n /** Pre-computed morph index arrays per mesh (for zero-lookup hot path) */\n morphEntries: MorphIndexEntry[];\n /** Primary face mesh (prefer 'Head_Mesh', fallback to first mesh with morph targets) */\n faceMesh: DiscoveredMesh | null;\n /** Number of successfully mapped ARKit blendshapes */\n mappedBlendshapeCount: number;\n}\n\n/**\n * Traverse a Three.js scene to discover bones and morph targets.\n * Pure function — no React, no side effects.\n *\n * Finds all SkinnedMesh nodes and named bones (Head, Neck, LeftEye, RightEye).\n * Pre-computes morph index arrays using LAM_BLENDSHAPES for zero-lookup\n * hot-path blendshape writing.\n */\nexport function discoverScene(scene: SceneObject): SceneDiscoveryResult {\n const meshes: DiscoveredMesh[] = [];\n let headBone: DiscoveredBone | null = null;\n let neckBone: DiscoveredBone | null = null;\n let leftEyeBone: DiscoveredBone | null = null;\n let rightEyeBone: DiscoveredBone | null = null;\n\n scene.traverse((child: any) => {\n if (child.isSkinnedMesh) {\n meshes.push(child as DiscoveredMesh);\n }\n if (child.isBone) {\n switch (child.name) {\n case 'Head': headBone = child as DiscoveredBone; break;\n case 'Neck': neckBone = child as DiscoveredBone; break;\n case 'LeftEye': leftEyeBone = child as DiscoveredBone; break;\n case 'RightEye': rightEyeBone = child as DiscoveredBone; break;\n }\n }\n });\n\n // Pre-compute morph target index arrays (hot path optimization)\n const morphEntries: MorphIndexEntry[] = [];\n let mappedCount = 0;\n\n for (const mesh of meshes) {\n if (!mesh.morphTargetDictionary || !mesh.morphTargetInfluences) continue;\n\n const indices = new Int16Array(LAM_BLENDSHAPES.length).fill(-1);\n let meshMapped = 0;\n\n for (let i = 0; i < LAM_BLENDSHAPES.length; i++) {\n const morphIdx = mesh.morphTargetDictionary[LAM_BLENDSHAPES[i]];\n if (morphIdx !== undefined) {\n indices[i] = morphIdx;\n meshMapped++;\n }\n }\n\n if (meshMapped > 0) {\n morphEntries.push({ mesh, indices });\n mappedCount = Math.max(mappedCount, meshMapped);\n }\n\n logger.debug(`Mesh \"${mesh.name}\": ${meshMapped}/${LAM_BLENDSHAPES.length} blendshapes mapped`);\n }\n\n const faceMesh = meshes.find(m => m.name === 'Head_Mesh' && m.morphTargetDictionary)\n ?? meshes.find(m => m.morphTargetDictionary)\n ?? null;\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), ${mappedCount} mapped blendshapes, bones: [${boneNames.join(', ')}]`,\n );\n\n return {\n meshes, headBone, neckBone, leftEyeBone, rightEyeBone,\n morphEntries, faceMesh, mappedBlendshapeCount: mappedCount,\n };\n}\n","/**\n * BlendshapeWriter — Write Float32Array[52] blendshapes to morph target\n * influences using pre-computed index arrays.\n *\n * Zero-lookup hot path: iterates a flat Int16Array per mesh instead\n * of doing string-keyed dictionary lookups every frame.\n *\n * @category Three\n */\n\nimport type { MorphIndexEntry } from './SceneDiscovery';\n\n/**\n * Write 52 ARKit blendshapes to morph target influences.\n * Uses pre-computed index arrays for zero-lookup hot path.\n *\n * @param blendshapes - Float32Array of 52 ARKit blendshape weights\n * @param morphEntries - Pre-computed morph index entries 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 influences = mesh.morphTargetInfluences;\n if (!influences) continue;\n for (let i = 0; i < 52; i++) {\n const morphIdx = indices[i];\n if (morphIdx >= 0) {\n influences[morphIdx] = blendshapes[i];\n }\n }\n }\n}\n","import { LAM_BLENDSHAPES, lerpBlendshapes } from '@omote/core';\nimport type { Object3D, SkinnedMesh } from 'three';\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 SkinnedMesh children automatically. Default: true */\n autoFind?: boolean;\n /** Called when meshes with morph targets are found */\n onMeshesFound?: (meshes: SkinnedMesh[]) => void;\n}\n\nexport class BlendshapeController {\n private _meshes: SkinnedMesh[] = [];\n private nameToIndex: Map<string, number>[] = [];\n private currentWeights: number[] = [];\n private blendshapeNames: readonly string[];\n private smoothing: number;\n private onMeshesFound?: (meshes: SkinnedMesh[]) => void;\n\n constructor(target: Object3D, options?: BlendshapeControllerOptions) {\n this.blendshapeNames = options?.names ?? LAM_BLENDSHAPES;\n this.smoothing = options?.smoothing ?? 0.7;\n this.onMeshesFound = options?.onMeshesFound;\n if (options?.autoFind !== false) {\n this.setTarget(target);\n }\n }\n\n get meshes(): SkinnedMesh[] {\n return this._meshes;\n }\n\n setTarget(target: Object3D): void {\n this._meshes = [];\n this.nameToIndex = [];\n\n target.traverse((child) => {\n const mesh = child as SkinnedMesh;\n if (mesh.morphTargetInfluences && mesh.morphTargetDictionary) {\n this._meshes.push(mesh);\n\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 = mesh.morphTargetDictionary[name];\n if (idx !== undefined) {\n map.set(name, idx);\n }\n }\n this.nameToIndex.push(map);\n }\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 if (!mesh.morphTargetInfluences || !map) continue;\n\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const dictIdx = map.get(name);\n if (dictIdx !== undefined) {\n mesh.morphTargetInfluences[dictIdx] = this.currentWeights[i];\n }\n }\n }\n }\n\n dispose(): void {\n this._meshes = [];\n this.nameToIndex = [];\n this.currentWeights = [];\n }\n}\n"],"mappings":";AAgCA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAAA;AAAA,OACK;AAcP,SAAS,SAAS,kBAAkB;;;ACvCpC,SAAS,iBAAiB,oBAAoB;AAE9C,IAAM,SAAS,aAAa,gBAAgB;AA2DrC,SAAS,cAAc,OAA0C;AACtE,QAAM,SAA2B,CAAC;AAClC,MAAI,WAAkC;AACtC,MAAI,WAAkC;AACtC,MAAI,cAAqC;AACzC,MAAI,eAAsC;AAE1C,QAAM,SAAS,CAAC,UAAe;AAC7B,QAAI,MAAM,eAAe;AACvB,aAAO,KAAK,KAAuB;AAAA,IACrC;AACA,QAAI,MAAM,QAAQ;AAChB,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AAAQ,qBAAW;AAAyB;AAAA,QACjD,KAAK;AAAQ,qBAAW;AAAyB;AAAA,QACjD,KAAK;AAAW,wBAAc;AAAyB;AAAA,QACvD,KAAK;AAAY,yBAAe;AAAyB;AAAA,MAC3D;AAAA,IACF;AAAA,EACF,CAAC;AAGD,QAAM,eAAkC,CAAC;AACzC,MAAI,cAAc;AAElB,aAAW,QAAQ,QAAQ;AACzB,QAAI,CAAC,KAAK,yBAAyB,CAAC,KAAK,sBAAuB;AAEhE,UAAM,UAAU,IAAI,WAAW,gBAAgB,MAAM,EAAE,KAAK,EAAE;AAC9D,QAAI,aAAa;AAEjB,aAAS,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;AAC/C,YAAM,WAAW,KAAK,sBAAsB,gBAAgB,CAAC,CAAC;AAC9D,UAAI,aAAa,QAAW;AAC1B,gBAAQ,CAAC,IAAI;AACb;AAAA,MACF;AAAA,IACF;AAEA,QAAI,aAAa,GAAG;AAClB,mBAAa,KAAK,EAAE,MAAM,QAAQ,CAAC;AACnC,oBAAc,KAAK,IAAI,aAAa,UAAU;AAAA,IAChD;AAEA,WAAO,MAAM,SAAS,KAAK,IAAI,MAAM,UAAU,IAAI,gBAAgB,MAAM,qBAAqB;AAAA,EAChG;AAEA,QAAM,WAAW,OAAO,KAAK,OAAK,EAAE,SAAS,eAAe,EAAE,qBAAqB,KAC9E,OAAO,KAAK,OAAK,EAAE,qBAAqB,KACxC;AAEL,MAAI,aAAa,WAAW,GAAG;AAC7B,WAAO,KAAK,iCAAiC;AAAA,EAC/C;AACA,MAAI,CAAC,UAAU;AACb,WAAO,KAAK,8BAA8B;AAAA,EAC5C;AAEA,QAAM,YAAY;AAAA,IAChB,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,gBAAgB;AAAA,EAClB,EAAE,OAAO,OAAO;AAEhB,SAAO;AAAA,IACL,uBAAuB,OAAO,MAAM,cAAc,WAAW,gCAAgC,UAAU,KAAK,IAAI,CAAC;AAAA,EACnH;AAEA,SAAO;AAAA,IACL;AAAA,IAAQ;AAAA,IAAU;AAAA,IAAU;AAAA,IAAa;AAAA,IACzC;AAAA,IAAc;AAAA,IAAU,uBAAuB;AAAA,EACjD;AACF;;;AChIO,SAAS,iBACd,aACA,cACM;AACN,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,UAAM,EAAE,MAAM,QAAQ,IAAI,aAAa,CAAC;AACxC,UAAM,aAAa,KAAK;AACxB,QAAI,CAAC,WAAY;AACjB,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,YAAM,WAAW,QAAQ,CAAC;AAC1B,UAAI,YAAY,GAAG;AACjB,mBAAW,QAAQ,IAAI,YAAY,CAAC;AAAA,MACtC;AAAA,IACF;AAAA,EACF;AACF;;;AFwBA,IAAMC,UAASC,cAAa,aAAa;AAGzC,IAAM,gBAAgB,IAAI,QAAQ;AAClC,IAAM,eAAe,IAAI,QAAQ;AACjC,IAAM,iBAAiB,IAAI,WAAW;AAsB/B,IAAM,cAAN,MAAkB;AAAA,EAsBvB,YAAY,SAA6B;AAnBzC,SAAQ,sBAA+E;AACvF,SAAQ,kBAAsC;AAG9C;AAAA,SAAQ,aAAgC;AAGxC;AAAA,SAAQ,iBAAwC;AAGhD;AAAA,SAAQ,oBAA8C;AAGtD;AAAA,SAAQ,qBAA0C;AAClD,SAAQ,WAA2C;AACnD,SAAQ,cAAc;AACtB,SAAQ,SAA8B;AACtC,SAAQ,eAAe;AAGrB,SAAK,YAAY,cAAc,QAAQ,MAAM;AAE7C,UAAM,mBAA8C;AAAA,MAClD,YAAY,QAAQ;AAAA,MACpB,MAAM,QAAQ;AAAA,IAChB;AACA,SAAK,aAAa,IAAI,oBAAoB,gBAAgB;AAE1D,QAAI,KAAK,UAAU,aAAa,WAAW,GAAG;AAC5C,MAAAD,QAAO,KAAK,wEAAmE;AAAA,IACjF;AACA,QAAI,CAAC,KAAK,UAAU,UAAU;AAC5B,MAAAA,QAAO,KAAK,2DAAsD;AAAA,IACpE;AACA,IAAAA,QAAO;AAAA,MACL,gBAAgB,KAAK,UAAU,OAAO,MAAM,cAAc,KAAK,UAAU,qBAAqB,iCAAiC,CAAC,CAAC,KAAK,UAAU,QAAQ;AAAA,IAC1J;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,OAAO,OAAe,QAAgB,iBAAgC;AAEpE,QAAI;AACJ,QAAI;AACJ,QAAI,KAAK,UAAU,UAAU,kBAAkB;AAC7C,WAAK,UAAU,SAAS,iBAAiB,aAAa;AACtD,qBAAe,EAAE,GAAG,cAAc,GAAG,GAAG,cAAc,GAAG,GAAG,cAAc,EAAE;AAC5E,WAAK,UAAU,SAAS,mBAAmB,cAAc;AACzD,sBAAgB,EAAE,GAAG,eAAe,GAAG,GAAG,eAAe,GAAG,GAAG,eAAe,GAAG,GAAG,eAAe,EAAE;AAAA,IACvG;AAGA,WAAO,iBAAiB,YAAY;AACpC,UAAM,iBAAiB;AAAA,MACrB,GAAG,aAAa;AAAA,MAChB,GAAG,aAAa;AAAA,MAChB,GAAG,aAAa;AAAA,IAClB;AAEA,UAAM,SAAS,KAAK,WAAW,OAAO;AAAA,MACpC,WAAW;AAAA,MACX,iBAAiB,KAAK;AAAA,MACtB,SAAS,KAAK;AAAA,MACd,YAAY,KAAK;AAAA,MACjB,OAAO,KAAK;AAAA,MACZ,aAAa,KAAK;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA,iBAAiB,mBAAmB;AAAA,IACtC,CAAC;AAGD,qBAAiB,OAAO,aAAa,KAAK,UAAU,YAAY;AAGhE,QAAI,KAAK,UAAU,UAAU;AAC3B,WAAK,UAAU,SAAS,SAAS,IAAI,OAAO,UAAU;AACtD,WAAK,UAAU,SAAS,SAAS,IAAI,OAAO,UAAU;AAAA,IACxD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,mBAAmB,QAA2B;AAE5C,QAAI,KAAK,cAAc,WAAW,KAAK,WAAW,aAAa;AAC7D,WAAK,WAAW,KAAK;AAAA,IACvB;AAGA,SAAK,sBAAsB;AAE3B,SAAK,sBAAsB,CAAC,UAA2D;AACrF,WAAK,qBAAqB,MAAM;AAChC,UAAI,MAAM,YAAY,QAAW;AAC/B,aAAK,WAAW,MAAM;AAAA,MACxB;AAAA,IACF;AACA,WAAO,GAAG,SAAS,KAAK,mBAAmB;AAC3C,SAAK,kBAAkB;AACvB,IAAAA,QAAO,MAAM,wBAAwB;AAAA,EACvC;AAAA;AAAA,EAGA,wBAA8B;AAC5B,QAAI,KAAK,mBAAmB,KAAK,qBAAqB;AACpD,UAAI,KAAK,gBAAgB,KAAK;AAC5B,aAAK,gBAAgB,IAAI,SAAS,KAAK,mBAAmB;AAAA,MAC5D;AACA,MAAAA,QAAO,MAAM,2BAA2B;AAAA,IAC1C;AACA,SAAK,kBAAkB;AACvB,SAAK,sBAAsB;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,eAAe,KAAiB,QAA0C;AAC9E,UAAM,KAAK,kBAAkB;AAC7B,SAAK,aAAa,IAAI,WAAW;AACjC,UAAM,KAAK,WAAW,QAAQ,KAAK,MAAM;AACzC,SAAK,mBAAmB,KAAK,WAAW,WAAY;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,MAAM,MAAc,SAAmE;AAC3F,QAAI,KAAK,mBAAmB;AAC1B,YAAM,KAAK,kBAAkB,MAAM,MAAM,OAAO;AAChD;AAAA,IACF;AACA,QAAI,CAAC,KAAK,YAAY;AACpB,YAAM,IAAI,MAAM,oDAAoD;AAAA,IACtE;AACA,SAAK,cAAc;AACnB,SAAK,SAAS;AACd,QAAI;AACF,YAAM,KAAK,WAAW,MAAM,MAAM,OAAO;AAAA,IAC3C,UAAE;AACA,WAAK,cAAc;AACnB,UAAI,KAAK,WAAW,YAAY;AAC9B,aAAK,SAAS;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WAAW,SAGd;AACD,QAAI,KAAK,mBAAmB;AAC1B,aAAO,KAAK,kBAAkB,WAAW,OAAO;AAAA,IAClD;AACA,QAAI,CAAC,KAAK,YAAY;AACpB,YAAM,IAAI,MAAM,oDAAoD;AAAA,IACtE;AACA,SAAK,cAAc;AACnB,SAAK,SAAS;AACd,UAAM,SAAS,MAAM,KAAK,WAAW,WAAW,WAAW,CAAC,CAAC;AAC7D,WAAO;AAAA,MACL,MAAM,OAAO;AAAA,MACb,KAAK,YAAY;AACf,YAAI;AAAE,gBAAM,OAAO,IAAI;AAAA,QAAG,UAC1B;AAAU,eAAK,cAAc;AAAO,cAAI,KAAK,WAAW,WAAY,MAAK,SAAS;AAAA,QAAQ;AAAA,MAC5F;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGA,eAAqB;AACnB,QAAI,KAAK,mBAAmB;AAC1B,WAAK,kBAAkB,aAAa;AACpC;AAAA,IACF;AACA,SAAK,YAAY,KAAK;AAAA,EACxB;AAAA;AAAA,EAGA,MAAM,oBAAmC;AACvC,QAAI,KAAK,YAAY;AACnB,WAAK,sBAAsB;AAC3B,YAAM,KAAK,WAAW,QAAQ;AAC9B,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,WAAW,KAAiB,QAA0C;AAC1E,WAAO,KAAK,eAAe,KAAK,MAAM;AAAA,EACxC;AAAA;AAAA,EAGA,MAAM,gBAA+B;AACnC,WAAO,KAAK,kBAAkB;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,gBAAgB,QAA8C;AAClE,UAAM,KAAK,mBAAmB;AAC9B,SAAK,iBAAiB,IAAI,eAAe,MAAM;AAC/C,UAAM,KAAK,eAAe,WAAW;AAAA,EACvC;AAAA;AAAA,EAGA,MAAM,iBAAgC;AACpC,QAAI,KAAK,mBAAmB;AAC1B,YAAM,KAAK,kBAAkB,eAAe;AAC5C;AAAA,IACF;AACA,QAAI,CAAC,KAAK,gBAAgB;AACxB,YAAM,IAAI,MAAM,sDAAsD;AAAA,IACxE;AACA,SAAK,SAAS;AACd,UAAM,KAAK,eAAe,MAAM;AAAA,EAClC;AAAA;AAAA,EAGA,gBAAsB;AACpB,QAAI,KAAK,mBAAmB;AAC1B,WAAK,kBAAkB,cAAc;AACrC;AAAA,IACF;AACA,SAAK,gBAAgB,KAAK;AAC1B,QAAI,KAAK,WAAW,YAAa,MAAK,SAAS;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,UAA0D;AACrE,UAAM,WAAW,KAAK,kBAAkB,KAAK,mBAAmB;AAChE,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,wEAAwE;AAAA,IAC1F;AACA,aAAS,GAAG,cAAc,QAAQ;AAClC,WAAO,MAAM;AAAE,eAAS,MAAM,cAAc,QAAQ;AAAA,IAAG;AAAA,EACzD;AAAA;AAAA,EAGA,MAAM,qBAAoC;AACxC,QAAI,KAAK,gBAAgB;AACvB,YAAM,KAAK,eAAe,QAAQ;AAClC,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,aAAa,QAAgD;AACjE,UAAM,KAAK,gBAAgB;AAC3B,SAAK,oBAAoB,IAAI,kBAAkB;AAC/C,UAAM,KAAK,kBAAkB,QAAQ,MAAM;AAG3C,QAAI,KAAK,kBAAkB,aAAa;AACtC,WAAK,mBAAmB,KAAK,kBAAkB,WAAW;AAAA,IAC5D;AAGA,SAAK,kBAAkB,GAAG,SAAS,CAAC,UAAU;AAC5C,WAAK,SAAS;AACd,WAAK,cAAc,UAAU;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,kBAAiC;AACrC,QAAI,KAAK,mBAAmB;AAC1B,WAAK,sBAAsB;AAC3B,YAAM,KAAK,kBAAkB,WAAW;AACxC,WAAK,oBAAoB;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,aAAiC;AACxC,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA,EAGA,WAAW,SAAwC;AACjD,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGA,YAAY,UAAyB;AACnC,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA,EAGA,SAAS,OAAkC;AACzC,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,eAAe,QAAsB;AACnC,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA,EAGA,WAAW,SAAiC;AAC1C,SAAK,WAAW,WAAW,OAAO;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,aAAa;AACf,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA,EAGA,IAAI,QAA8B;AAChC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,kBAA2B;AAC7B,WAAO,KAAK,UAAU,aAAa,SAAS;AAAA,EAC9C;AAAA;AAAA,EAGA,IAAI,wBAAgC;AAClC,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA;AAAA,EAGA,IAAI,aAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,cAAuB;AACzB,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA;AAAA,EAGA,IAAI,sBAA2C;AAC7C,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,IAAI,UAA6B;AAC/B,WAAO,KAAK,cAAc,KAAK,mBAAmB,WAAW;AAAA,EAC/D;AAAA;AAAA,EAGA,IAAI,WAAkC;AACpC,WAAO,KAAK,kBAAkB,KAAK,mBAAmB,YAAY;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,QAAc;AACZ,SAAK,qBAAqB;AAC1B,SAAK,WAAW;AAChB,SAAK,cAAc;AACnB,SAAK,SAAS;AACd,SAAK,eAAe;AACpB,SAAK,WAAW,MAAM;AAAA,EACxB;AAAA;AAAA,EAGA,MAAM,UAAyB;AAC7B,UAAM,KAAK,gBAAgB;AAC3B,UAAM,KAAK,kBAAkB;AAC7B,UAAM,KAAK,mBAAmB;AAC9B,SAAK,sBAAsB;AAC3B,SAAK,WAAW,QAAQ;AACxB,IAAAA,QAAO,MAAM,UAAU;AAAA,EACzB;AACF;;;AG9gBA,SAAS,mBAAAE,kBAAiB,uBAAuB;AAc1C,IAAM,uBAAN,MAA2B;AAAA,EAQhC,YAAY,QAAkB,SAAuC;AAPrE,SAAQ,UAAyB,CAAC;AAClC,SAAQ,cAAqC,CAAC;AAC9C,SAAQ,iBAA2B,CAAC;AAMlC,SAAK,kBAAkB,SAAS,SAASA;AACzC,SAAK,YAAY,SAAS,aAAa;AACvC,SAAK,gBAAgB,SAAS;AAC9B,QAAI,SAAS,aAAa,OAAO;AAC/B,WAAK,UAAU,MAAM;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,IAAI,SAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAU,QAAwB;AAChC,SAAK,UAAU,CAAC;AAChB,SAAK,cAAc,CAAC;AAEpB,WAAO,SAAS,CAAC,UAAU;AACzB,YAAM,OAAO;AACb,UAAI,KAAK,yBAAyB,KAAK,uBAAuB;AAC5D,aAAK,QAAQ,KAAK,IAAI;AAEtB,cAAM,MAAM,oBAAI,IAAoB;AACpC,iBAAS,IAAI,GAAG,IAAI,KAAK,gBAAgB,QAAQ,KAAK;AACpD,gBAAM,OAAO,KAAK,gBAAgB,CAAC;AACnC,gBAAM,MAAM,KAAK,sBAAsB,IAAI;AAC3C,cAAI,QAAQ,QAAW;AACrB,gBAAI,IAAI,MAAM,GAAG;AAAA,UACnB;AAAA,QACF;AACA,aAAK,YAAY,KAAK,GAAG;AAAA,MAC3B;AAAA,IACF,CAAC;AAED,SAAK,iBAAiB,IAAI,MAAM,KAAK,gBAAgB,MAAM,EAAE,KAAK,CAAC;AAEnE,QAAI,KAAK,QAAQ,SAAS,KAAK,KAAK,eAAe;AACjD,WAAK,cAAc,KAAK,OAAO;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,OAAO,SAAwC;AAC7C,SAAK,iBAAiB,gBAAgB,KAAK,gBAAgB,SAAS,KAAK,SAAS;AAElF,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,QAAQ,KAAK;AAC5C,YAAM,OAAO,KAAK,QAAQ,CAAC;AAC3B,YAAM,MAAM,KAAK,YAAY,CAAC;AAC9B,UAAI,CAAC,KAAK,yBAAyB,CAAC,IAAK;AAEzC,eAAS,IAAI,GAAG,IAAI,KAAK,gBAAgB,QAAQ,KAAK;AACpD,cAAM,OAAO,KAAK,gBAAgB,CAAC;AACnC,cAAM,UAAU,IAAI,IAAI,IAAI;AAC5B,YAAI,YAAY,QAAW;AACzB,eAAK,sBAAsB,OAAO,IAAI,KAAK,eAAe,CAAC;AAAA,QAC7D;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAgB;AACd,SAAK,UAAU,CAAC;AAChB,SAAK,cAAc,CAAC;AACpB,SAAK,iBAAiB,CAAC;AAAA,EACzB;AACF;","names":["createLogger","logger","createLogger","LAM_BLENDSHAPES"]}
1
+ {"version":3,"sources":["../src/OmoteAvatar.ts","../src/SceneDiscovery.ts","../src/BlendshapeWriter.ts","../src/createAvatar.ts","../src/BlendshapeController.ts"],"sourcesContent":["/**\r\n * OmoteAvatar — Full-featured Three.js avatar class.\r\n *\r\n * Wraps CharacterController from @omote/core with Three.js scene discovery,\r\n * blendshape writing, and gaze bone rotation. Drop-in avatar for any\r\n * Three.js app that has a render loop.\r\n *\r\n * Voice methods (speak, streamText, connectSpeaker, connectVoice, etc.) are\r\n * delegated to OmoteAvatarCore from @omote/avatar, eliminating duplication\r\n * across Three.js, Babylon, and R3F adapters.\r\n *\r\n * @example\r\n * ```ts\r\n * import { OmoteAvatar } from '@omote/three';\r\n * import { createA2E, PlaybackPipeline } from '@omote/core';\r\n *\r\n * const avatar = new OmoteAvatar({ target: avatarModel });\r\n * const lam = createA2E();\r\n * await lam.load();\r\n * const pipeline = new PlaybackPipeline({ lam, sampleRate: 16000 });\r\n * avatar.connectFrameSource(pipeline);\r\n *\r\n * // In render loop:\r\n * avatar.update(delta, camera);\r\n * ```\r\n *\r\n * @example Speaker integration\r\n * ```ts\r\n * const avatar = new OmoteAvatar({ target: avatarModel });\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 Three\r\n */\r\n\r\nimport {\r\n CharacterController,\r\n createLogger,\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 LoadingProgress,\r\n VoiceOrchestratorConfig,\r\n} from '@omote/core';\r\nimport { OmoteAvatarCore } from '@omote/avatar';\r\nimport type { SpeakOptions, StreamTextSink } from '@omote/avatar';\r\nimport { Vector3, Quaternion } from 'three';\r\nimport type { Camera, Object3D } from 'three';\r\nimport { discoverScene } from './SceneDiscovery';\r\nimport type { SceneDiscoveryResult } from './SceneDiscovery';\r\nimport { writeBlendshapes } from './BlendshapeWriter';\r\n\r\nconst logger = createLogger('OmoteAvatar.Three');\r\n\r\n// Reusable scratch vectors — avoids per-frame allocation\r\nconst _headWorldPos = new Vector3();\r\nconst _camWorldPos = new Vector3();\r\nconst _headWorldQuat = new Quaternion();\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 /** Three.js Object3D (loaded GLB scene, Group, etc.) to traverse for meshes and bones. */\r\n target: Object3D;\r\n /** FaceCompositor configuration (profile, emotion, life layer). */\r\n compositor?: FaceCompositorConfig;\r\n /** Gaze tracking configuration. */\r\n gaze?: CharacterControllerConfig['gaze'];\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 core: OmoteAvatarCore;\r\n\r\n // External frame source (adapter-level, handles emotion extraction)\r\n private frameSourceCallback: ((frame: { blendshapes: Float32Array; emotion?: string }) => void) | null = null;\r\n private connectedSource: FrameSource | null = null;\r\n\r\n // State\r\n private currentBlendshapes: Float32Array | null = null;\r\n private _emotion: string | EmotionWeights | null = null;\r\n private _audioEnergy = 0;\r\n\r\n constructor(options: OmoteAvatarOptions) {\r\n this.discovery = discoverScene(options.target);\r\n\r\n const controllerConfig: CharacterControllerConfig = {\r\n compositor: options.compositor,\r\n gaze: options.gaze,\r\n };\r\n this.controller = new CharacterController(controllerConfig);\r\n\r\n // Voice composition core — handles speaker, listener, orchestrator\r\n this.core = new OmoteAvatarCore();\r\n this.core.onFrame = (frame) => {\r\n this.currentBlendshapes = frame.blendshapes;\r\n if (frame.emotion !== undefined) {\r\n this._emotion = frame.emotion;\r\n }\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\r\n // -------------------------------------------------------------------------\r\n // Per-frame update\r\n // -------------------------------------------------------------------------\r\n\r\n /**\r\n * Call each frame in your render loop.\r\n *\r\n * Runs CharacterController (compositor + gaze + life layer), writes\r\n * blendshapes to morph targets, and applies head bone rotation.\r\n *\r\n * @param delta - Time since last frame in seconds\r\n * @param camera - The active Three.js camera (used for gaze direction)\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 // Compute head world position + quaternion from bone\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?.getWorldPosition) {\r\n this.discovery.headBone.getWorldPosition(_headWorldPos);\r\n headWorldPos = { x: _headWorldPos.x, y: _headWorldPos.y, z: _headWorldPos.z };\r\n this.discovery.headBone.getWorldQuaternion(_headWorldQuat);\r\n headWorldQuat = { x: _headWorldQuat.x, y: _headWorldQuat.y, z: _headWorldQuat.z, w: _headWorldQuat.w };\r\n }\r\n\r\n // Camera world position (use getWorldPosition for parented cameras)\r\n camera.getWorldPosition(_camWorldPos);\r\n const cameraWorldPos = {\r\n x: _camWorldPos.x,\r\n y: _camWorldPos.y,\r\n z: _camWorldPos.z,\r\n };\r\n\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.core.isSpeaking,\r\n state: this.core.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 from gaze + life layer\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 (adapter-level, with emotion extraction)\r\n // -------------------------------------------------------------------------\r\n\r\n /**\r\n * Connect to any frame source (PlaybackPipeline, MicLipSync, etc.).\r\n * Listens for 'frame' events and updates current blendshapes automatically.\r\n *\r\n * Only one source can be connected at a time. Connecting a new source\r\n * disconnects the previous one.\r\n */\r\n connectFrameSource(source: FrameSource): void {\r\n // Disconnect existing source first\r\n this.disconnectFrameSource();\r\n\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 currently connected frame source. */\r\n disconnectFrameSource(): void {\r\n if (this.connectedSource && this.frameSourceCallback) {\r\n if (this.connectedSource.off) {\r\n this.connectedSource.off('frame', this.frameSourceCallback);\r\n }\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) — delegated to OmoteAvatarCore\r\n // -------------------------------------------------------------------------\r\n\r\n /** Warm up AudioContext for iOS/Safari autoplay policy. Call from user gesture. */\r\n async warmup(): Promise<void> {\r\n return this.core.warmup();\r\n }\r\n\r\n async connectSpeaker(tts: TTSBackend, config?: TTSSpeakerConfig): Promise<void> {\r\n return this.core.connectSpeaker(tts, config);\r\n }\r\n\r\n async speak(text: string, options?: SpeakOptions): Promise<void> {\r\n return this.core.speak(text, options);\r\n }\r\n\r\n async streamText(options?: SpeakOptions): Promise<StreamTextSink> {\r\n return this.core.streamText(options);\r\n }\r\n\r\n stopSpeaking(): void {\r\n this.core.stopSpeaking();\r\n }\r\n\r\n async disconnectSpeaker(): Promise<void> {\r\n return this.core.disconnectSpeaker();\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) — delegated to OmoteAvatarCore\r\n // -------------------------------------------------------------------------\r\n\r\n async connectListener(config?: SpeechListenerConfig): Promise<void> {\r\n return this.core.connectListener(config);\r\n }\r\n\r\n async startListening(): Promise<void> {\r\n return this.core.startListening();\r\n }\r\n\r\n stopListening(): void {\r\n this.core.stopListening();\r\n }\r\n\r\n onTranscript(callback: (result: TranscriptResult) => void): () => void {\r\n return this.core.onTranscript(callback);\r\n }\r\n\r\n async disconnectListener(): Promise<void> {\r\n return this.core.disconnectListener();\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Voice (combined speaker + listener + interruption) — delegated\r\n // -------------------------------------------------------------------------\r\n\r\n async connectVoice(config: VoiceOrchestratorConfig): Promise<void> {\r\n return this.core.connectVoice(config);\r\n }\r\n\r\n async disconnectVoice(): Promise<void> {\r\n return this.core.disconnectVoice();\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // Event subscriptions — delegated to OmoteAvatarCore\r\n // -------------------------------------------------------------------------\r\n\r\n onTranscriptEvent(callback: (result: TranscriptResult) => void): () => void {\r\n return this.core.onTranscriptEvent(callback);\r\n }\r\n\r\n onVoiceStateChange(callback: (state: ConversationalState) => void): () => void {\r\n return this.core.onVoiceStateChange(callback);\r\n }\r\n\r\n onLoadingProgress(callback: (progress: LoadingProgress) => void): () => void {\r\n return this.core.onLoadingProgress(callback);\r\n }\r\n\r\n onError(callback: (error: Error) => void): () => void {\r\n return this.core.onError(callback);\r\n }\r\n\r\n onAudioLevel(callback: (level: { rms: number; peak: number }) => void): () => void {\r\n return this.core.onAudioLevel(callback);\r\n }\r\n\r\n // -------------------------------------------------------------------------\r\n // State setters\r\n // -------------------------------------------------------------------------\r\n\r\n /** Set raw blendshapes directly (alternative to connectFrameSource). */\r\n setFrame(blendshapes: Float32Array): void {\r\n this.currentBlendshapes = blendshapes;\r\n }\r\n\r\n /** Set the current emotion (string preset name 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 animation intensity). */\r\n setSpeaking(speaking: boolean): void {\r\n this.core.setSpeaking(speaking);\r\n }\r\n\r\n /** Set the conversational state (idle, listening, thinking, speaking). */\r\n setState(state: ConversationalState): void {\r\n this.core.setState(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 // Accessors\r\n // -------------------------------------------------------------------------\r\n\r\n /** Access the underlying FaceCompositor for advanced configuration. */\r\n get compositor() {\r\n return this.controller.compositor;\r\n }\r\n\r\n /** Access discovered scene parts (meshes, bones). */\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.core.isSpeaking;\r\n }\r\n\r\n /** Whether the avatar is currently listening for speech. */\r\n get isListening(): boolean {\r\n return this.core.state === 'listening';\r\n }\r\n\r\n /** Current conversational state. */\r\n get conversationalState(): ConversationalState {\r\n return this.core.state;\r\n }\r\n\r\n /** Access the internal TTSSpeaker (null if not connected). */\r\n get speaker() {\r\n return this.core.speaker;\r\n }\r\n\r\n /** Access the internal SpeechListener (null if not connected). */\r\n get listener() {\r\n return this.core.listener;\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._audioEnergy = 0;\r\n this.core.reset();\r\n this.controller.reset();\r\n }\r\n\r\n /** Disconnect all voice resources, frame sources, and dispose the controller. */\r\n async dispose(): Promise<void> {\r\n await this.core.dispose();\r\n this.disconnectFrameSource();\r\n this.controller.dispose();\r\n logger.debug('Disposed');\r\n }\r\n}\r\n","/**\n * SceneDiscovery — Traverse a Three.js scene graph to discover bones and\n * morph targets. Pure function, no React or side-effect dependencies.\n *\n * This is the CANONICAL implementation. @omote/r3f imports from here.\n *\n * Public interfaces use duck types (no direct Three.js type imports) so\n * consumers with different @types/three versions avoid type-incompatibility\n * errors in monorepo setups.\n *\n * @category Three\n */\n\nimport { LAM_BLENDSHAPES, createLogger } from '@omote/core';\n\nconst logger = createLogger('SceneDiscovery');\n\n// ---------------------------------------------------------------------------\n// Duck-typed interfaces — structurally compatible with Three.js but\n// importable without pulling in @types/three.\n// ---------------------------------------------------------------------------\n\n/** Any object with `traverse` (structurally compatible with THREE.Object3D). */\nexport interface SceneObject {\n traverse(callback: (child: any) => void): void;\n}\n\n/** Minimal shape of a skinned mesh. */\nexport interface DiscoveredMesh {\n name: string;\n isSkinnedMesh?: boolean;\n morphTargetDictionary?: Record<string, number>;\n morphTargetInfluences?: number[];\n [key: string]: any;\n}\n\n/** Minimal shape of a bone. */\nexport interface DiscoveredBone {\n name: string;\n isBone?: boolean;\n rotation: { x: number; y: number; z: number };\n getWorldPosition?(target: any): any;\n [key: string]: any;\n}\n\n/** Pre-computed morph target index array for a single mesh */\nexport interface MorphIndexEntry {\n mesh: DiscoveredMesh;\n /** indices[lamIndex] = morphTargetIndex (or -1 if not found) */\n indices: Int16Array;\n}\n\nexport interface SceneDiscoveryResult {\n meshes: DiscoveredMesh[];\n headBone: DiscoveredBone | null;\n neckBone: DiscoveredBone | null;\n leftEyeBone: DiscoveredBone | null;\n rightEyeBone: DiscoveredBone | null;\n /** Pre-computed morph index arrays per mesh (for zero-lookup hot path) */\n morphEntries: MorphIndexEntry[];\n /** Primary face mesh (prefer 'Head_Mesh', fallback to first mesh with morph targets) */\n faceMesh: DiscoveredMesh | null;\n /** Number of successfully mapped ARKit blendshapes */\n mappedBlendshapeCount: number;\n}\n\n/**\n * Traverse a Three.js scene to discover bones and morph targets.\n * Pure function — no React, no side effects.\n *\n * Finds all SkinnedMesh nodes and named bones (Head, Neck, LeftEye, RightEye).\n * Pre-computes morph index arrays using LAM_BLENDSHAPES for zero-lookup\n * hot-path blendshape writing.\n */\nexport function discoverScene(scene: SceneObject): SceneDiscoveryResult {\n const meshes: DiscoveredMesh[] = [];\n let headBone: DiscoveredBone | null = null;\n let neckBone: DiscoveredBone | null = null;\n let leftEyeBone: DiscoveredBone | null = null;\n let rightEyeBone: DiscoveredBone | null = null;\n\n scene.traverse((child: any) => {\n if (child.isSkinnedMesh) {\n meshes.push(child as DiscoveredMesh);\n }\n if (child.isBone) {\n switch (child.name) {\n case 'Head': headBone = child as DiscoveredBone; break;\n case 'Neck': neckBone = child as DiscoveredBone; break;\n case 'LeftEye': leftEyeBone = child as DiscoveredBone; break;\n case 'RightEye': rightEyeBone = child as DiscoveredBone; break;\n }\n }\n });\n\n // Pre-compute morph target index arrays (hot path optimization)\n const morphEntries: MorphIndexEntry[] = [];\n let mappedCount = 0;\n\n for (const mesh of meshes) {\n if (!mesh.morphTargetDictionary || !mesh.morphTargetInfluences) continue;\n\n const indices = new Int16Array(LAM_BLENDSHAPES.length).fill(-1);\n let meshMapped = 0;\n\n for (let i = 0; i < LAM_BLENDSHAPES.length; i++) {\n const morphIdx = mesh.morphTargetDictionary[LAM_BLENDSHAPES[i]];\n if (morphIdx !== undefined) {\n indices[i] = morphIdx;\n meshMapped++;\n }\n }\n\n if (meshMapped > 0) {\n morphEntries.push({ mesh, indices });\n mappedCount = Math.max(mappedCount, meshMapped);\n }\n\n logger.debug(`Mesh \"${mesh.name}\": ${meshMapped}/${LAM_BLENDSHAPES.length} blendshapes mapped`);\n }\n\n const faceMesh = meshes.find(m => m.name === 'Head_Mesh' && m.morphTargetDictionary)\n ?? meshes.find(m => m.morphTargetDictionary)\n ?? null;\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), ${mappedCount} mapped blendshapes, bones: [${boneNames.join(', ')}]`,\n );\n\n return {\n meshes, headBone, neckBone, leftEyeBone, rightEyeBone,\n morphEntries, faceMesh, mappedBlendshapeCount: mappedCount,\n };\n}\n","/**\n * BlendshapeWriter — Write Float32Array[52] blendshapes to morph target\n * influences using pre-computed index arrays.\n *\n * Zero-lookup hot path: iterates a flat Int16Array per mesh instead\n * of doing string-keyed dictionary lookups every frame.\n *\n * @category Three\n */\n\nimport type { MorphIndexEntry } from './SceneDiscovery';\n\n/**\n * Write 52 ARKit blendshapes to morph target influences.\n * Uses pre-computed index arrays for zero-lookup hot path.\n *\n * @param blendshapes - Float32Array of 52 ARKit blendshape weights\n * @param morphEntries - Pre-computed morph index entries 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 influences = mesh.morphTargetInfluences;\n if (!influences) continue;\n for (let i = 0; i < 52; i++) {\n const morphIdx = indices[i];\n if (morphIdx >= 0) {\n influences[morphIdx] = blendshapes[i];\n }\n }\n }\n}\n","/**\r\n * createAvatar — Async factory for a complete Three.js avatar scene.\r\n *\r\n * Sets up renderer, scene, camera, lighting, GLTF loading, OmoteAvatar,\r\n * render loop, and resize handling. Returns a handle with all sub-objects.\r\n *\r\n * @example\r\n * ```typescript\r\n * import { createAvatar } from '@omote/three';\r\n *\r\n * const { avatar, dispose } = await createAvatar({\r\n * src: '/avatar.glb',\r\n * container: '#avatar-container',\r\n * });\r\n * avatar.connectFrameSource(pipeline);\r\n * ```\r\n *\r\n * @category Three\r\n */\r\n\r\nimport * as THREE from 'three';\r\nimport { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';\r\nimport { OrbitControls } from 'three/addons/controls/OrbitControls.js';\r\nimport { OmoteAvatar } from './OmoteAvatar';\r\n\r\nexport interface CreateAvatarConfig {\r\n /** URL to GLB/GLTF avatar model */\r\n src: string;\r\n /** Container element or CSS selector */\r\n container: HTMLElement | string;\r\n /** Camera FOV (default: 35) */\r\n fov?: number;\r\n /** Enable orbit controls (default: true) */\r\n controls?: boolean;\r\n}\r\n\r\nexport interface AvatarHandle {\r\n avatar: OmoteAvatar;\r\n scene: THREE.Scene;\r\n camera: THREE.PerspectiveCamera;\r\n renderer: THREE.WebGLRenderer;\r\n controls: OrbitControls | null;\r\n /** Animation clips embedded in the GLTF/GLB file. Pass to avatar.connectAnimations(). */\r\n animations: THREE.AnimationClip[];\r\n dispose(): void;\r\n}\r\n\r\nexport async function createAvatar(config: CreateAvatarConfig): Promise<AvatarHandle> {\r\n const container = typeof config.container === 'string'\r\n ? document.querySelector<HTMLElement>(config.container)\r\n : config.container;\r\n if (!container) throw new Error(`Container not found: ${config.container}`);\r\n\r\n const { clientWidth: w, clientHeight: h } = container;\r\n\r\n // Renderer\r\n const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });\r\n renderer.setSize(w, h);\r\n renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\r\n renderer.toneMapping = THREE.ACESFilmicToneMapping;\r\n container.appendChild(renderer.domElement);\r\n\r\n // Scene + Camera\r\n const scene = new THREE.Scene();\r\n const camera = new THREE.PerspectiveCamera(config.fov ?? 35, w / h, 0.1, 100);\r\n camera.position.set(0, 1.5, 0.8);\r\n\r\n // Lighting\r\n scene.add(new THREE.AmbientLight(0xffffff, 0.5));\r\n const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);\r\n dirLight.position.set(2, 3, 2);\r\n scene.add(dirLight);\r\n\r\n // Controls\r\n let controls: OrbitControls | null = null;\r\n if (config.controls !== false) {\r\n controls = new OrbitControls(camera, renderer.domElement);\r\n controls.target.set(0, 1.5, 0);\r\n controls.enableDamping = true;\r\n controls.update();\r\n }\r\n\r\n // Load GLTF\r\n const gltf = await new GLTFLoader().loadAsync(config.src);\r\n scene.add(gltf.scene);\r\n\r\n // OmoteAvatar\r\n const avatar = new OmoteAvatar({\r\n target: gltf.scene,\r\n gaze: { enabled: true, smoothing: 0.08 },\r\n });\r\n\r\n // Render loop\r\n const clock = new THREE.Clock();\r\n let animId = 0;\r\n function animate() {\r\n animId = requestAnimationFrame(animate);\r\n avatar.update(clock.getDelta(), camera);\r\n controls?.update();\r\n renderer.render(scene, camera);\r\n }\r\n animate();\r\n\r\n // Resize\r\n const ro = new ResizeObserver(() => {\r\n const { clientWidth: rw, clientHeight: rh } = container;\r\n camera.aspect = rw / rh;\r\n camera.updateProjectionMatrix();\r\n renderer.setSize(rw, rh);\r\n });\r\n ro.observe(container);\r\n\r\n return {\r\n avatar, scene, camera, renderer, controls,\r\n animations: gltf.animations ?? [],\r\n dispose() {\r\n cancelAnimationFrame(animId);\r\n ro.disconnect();\r\n controls?.dispose();\r\n avatar.dispose();\r\n renderer.dispose();\r\n if (renderer.domElement.parentElement) {\r\n renderer.domElement.parentElement.removeChild(renderer.domElement);\r\n }\r\n },\r\n };\r\n}\r\n","import { LAM_BLENDSHAPES, lerpBlendshapes } from '@omote/core';\nimport type { Object3D, SkinnedMesh } from 'three';\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 SkinnedMesh children automatically. Default: true */\n autoFind?: boolean;\n /** Called when meshes with morph targets are found */\n onMeshesFound?: (meshes: SkinnedMesh[]) => void;\n}\n\nexport class BlendshapeController {\n private _meshes: SkinnedMesh[] = [];\n private nameToIndex: Map<string, number>[] = [];\n private currentWeights: number[] = [];\n private blendshapeNames: readonly string[];\n private smoothing: number;\n private onMeshesFound?: (meshes: SkinnedMesh[]) => void;\n\n constructor(target: Object3D, options?: BlendshapeControllerOptions) {\n this.blendshapeNames = options?.names ?? LAM_BLENDSHAPES;\n this.smoothing = options?.smoothing ?? 0.7;\n this.onMeshesFound = options?.onMeshesFound;\n if (options?.autoFind !== false) {\n this.setTarget(target);\n }\n }\n\n get meshes(): SkinnedMesh[] {\n return this._meshes;\n }\n\n setTarget(target: Object3D): void {\n this._meshes = [];\n this.nameToIndex = [];\n\n target.traverse((child) => {\n const mesh = child as SkinnedMesh;\n if (mesh.morphTargetInfluences && mesh.morphTargetDictionary) {\n this._meshes.push(mesh);\n\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 = mesh.morphTargetDictionary[name];\n if (idx !== undefined) {\n map.set(name, idx);\n }\n }\n this.nameToIndex.push(map);\n }\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 if (!mesh.morphTargetInfluences || !map) continue;\n\n for (let i = 0; i < this.blendshapeNames.length; i++) {\n const name = this.blendshapeNames[i];\n const dictIdx = map.get(name);\n if (dictIdx !== undefined) {\n mesh.morphTargetInfluences[dictIdx] = this.currentWeights[i];\n }\n }\n }\n }\n\n dispose(): void {\n this._meshes = [];\n this.nameToIndex = [];\n this.currentWeights = [];\n }\n}\n"],"mappings":"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","names":["createLogger","logger","createLogger","LAM_BLENDSHAPES"]}