talking-head-studio 0.4.9 → 0.4.11

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.
Files changed (69) hide show
  1. package/README.md +227 -351
  2. package/dist/TalkingHead.d.ts +16 -25
  3. package/dist/TalkingHead.web.d.ts +6 -0
  4. package/dist/TalkingHead.web.js +18 -8
  5. package/dist/api/studioApi.js +25 -26
  6. package/dist/appearance/apply.js +2 -3
  7. package/dist/appearance/matchers.js +1 -2
  8. package/dist/appearance/schema.js +1 -2
  9. package/dist/core/avatar/backend.d.ts +130 -0
  10. package/dist/core/avatar/backend.js +4 -0
  11. package/dist/core/avatar/backends/gaussian.d.ts +49 -0
  12. package/dist/core/avatar/backends/gaussian.js +291 -0
  13. package/dist/core/avatar/backends/index.d.ts +3 -0
  14. package/dist/core/avatar/backends/index.js +7 -0
  15. package/dist/core/avatar/backends/morphTarget.d.ts +39 -0
  16. package/dist/core/avatar/backends/morphTarget.js +179 -0
  17. package/dist/core/avatar/faceControls.d.ts +40 -0
  18. package/dist/core/avatar/faceControls.js +138 -0
  19. package/dist/core/avatar/schema.d.ts +50 -0
  20. package/dist/core/avatar/schema.js +134 -0
  21. package/dist/core/avatar/visemes.d.ts +64 -0
  22. package/dist/core/avatar/visemes.js +72 -0
  23. package/dist/editor/AvatarCanvas.js +1 -2
  24. package/dist/editor/AvatarEditor.native.js +18 -9
  25. package/dist/editor/AvatarModel.js +1 -2
  26. package/dist/editor/FaceSqueezeEditor.js +19 -9
  27. package/dist/editor/FaceSqueezeEditor.web.js +2 -2
  28. package/dist/editor/RigidAccessory.js +1 -2
  29. package/dist/editor/SkinnedClothing.js +18 -9
  30. package/dist/editor/boneSnap.js +22 -12
  31. package/dist/editor/studioTheme.js +2 -2
  32. package/dist/html.js +1 -2
  33. package/dist/index.d.ts +15 -1
  34. package/dist/index.js +28 -5
  35. package/dist/platform/api/types.d.ts +10 -0
  36. package/dist/platform/api/types.js +2 -0
  37. package/dist/platform/marketplace/types.d.ts +32 -0
  38. package/dist/platform/marketplace/types.js +2 -0
  39. package/dist/platform/sdk/unity.d.ts +27 -0
  40. package/dist/platform/sdk/unity.js +2 -0
  41. package/dist/platform/sdk/unreal.d.ts +23 -0
  42. package/dist/platform/sdk/unreal.js +2 -0
  43. package/dist/platform/sdk/web.d.ts +16 -0
  44. package/dist/platform/sdk/web.js +2 -0
  45. package/dist/sketchfab/api.js +4 -5
  46. package/dist/sketchfab/useSketchfabSearch.js +1 -2
  47. package/dist/tts/useDirectVisemeStream.d.ts +2 -6
  48. package/dist/tts/useDirectVisemeStream.js +1 -2
  49. package/dist/tts/useMotionMarkers.d.ts +0 -1
  50. package/dist/tts/useMotionMarkers.js +1 -2
  51. package/dist/utils/avatarUtils.js +2 -3
  52. package/dist/utils/faceLandmarkerToShapeWeights.js +19 -10
  53. package/dist/voice/convertToWav.js +1 -2
  54. package/dist/voice/index.d.ts +3 -0
  55. package/dist/voice/index.js +6 -1
  56. package/dist/voice/useAudioPlayer.js +1 -2
  57. package/dist/voice/useAudioRecording.js +1 -2
  58. package/dist/voice/useFaceControls.d.ts +14 -0
  59. package/dist/voice/useFaceControls.js +81 -0
  60. package/dist/voice/useVoicePreview.d.ts +7 -0
  61. package/dist/voice/useVoicePreview.js +81 -0
  62. package/dist/wardrobe/index.d.ts +2 -0
  63. package/dist/wardrobe/index.js +3 -1
  64. package/dist/wardrobe/useAvatarWardrobeHydration.js +1 -2
  65. package/dist/wardrobe/useStudioAvatar.d.ts +29 -0
  66. package/dist/wardrobe/useStudioAvatar.js +177 -0
  67. package/dist/wgpu/WgpuAvatar.js +17 -7
  68. package/dist/wgpu/useAuthedModelUri.js +18 -9
  69. package/package.json +8 -4
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { type StyleProp, type ViewStyle } from 'react-native';
3
3
  export type TalkingHeadMood = 'neutral' | 'happy' | 'sad' | 'angry' | 'excited' | 'thinking' | 'concerned' | 'surprised';
4
+ import type { AgentVisemePayload, OculusViseme, VisemeCue } from './core/avatar/visemes';
4
5
  export type TalkingHeadLoadingStage = 'booting' | 'fetching_model' | 'loading_avatar' | 'loading_fallback' | 'ready';
5
6
  export interface TalkingHeadLoadingState {
6
7
  stage: TalkingHeadLoadingStage;
@@ -9,36 +10,20 @@ export interface TalkingHeadLoadingState {
9
10
  /**
10
11
  * Standard viseme keys supported by the avatar.
11
12
  * Use with sendViseme() from your TTS viseme callbacks.
13
+ * @deprecated Use OculusViseme from @yourplatform/avatar/core/avatar/visemes
12
14
  */
13
- export type TalkingHeadViseme = 'sil' | 'PP' | 'FF' | 'TH' | 'DD' | 'kk' | 'CH' | 'SS' | 'nn' | 'RR' | 'aa' | 'ee' | 'ih' | 'oh' | 'ou';
14
- /** Rhubarb mouth shape cue (Preston Blair set: A-H, X) */
15
- export interface TalkingHeadVisemeCue {
16
- startMs: number;
17
- endMs: number;
18
- viseme: 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'X';
19
- }
15
+ export type TalkingHeadViseme = OculusViseme;
16
+ /**
17
+ * Rhubarb mouth shape cue (Preston Blair set: A-H, X)
18
+ * @deprecated Use VisemeCue from @yourplatform/avatar/core/avatar/visemes
19
+ */
20
+ export type TalkingHeadVisemeCue = VisemeCue;
20
21
  /**
21
22
  * A full viseme schedule from the Rhubarb sidecar endpoint.
22
23
  * Pass to scheduleVisemes() when agent_visemes arrives on the data channel.
24
+ * @deprecated Use AgentVisemePayload from @yourplatform/avatar/core/avatar/visemes
23
25
  */
24
- export interface TalkingHeadVisemeSchedule {
25
- /** Matches X-TTS-Request-Id / agent_visemes.requestId */
26
- requestId?: string;
27
- /**
28
- * Wall-clock ms at which the TTS request was fired (agent side).
29
- * Used as the scheduling anchor plus AUDIO_PIPELINE_DELAY_MS.
30
- */
31
- startedAtMs?: number;
32
- /**
33
- * Wall-clock ms at which audio actually began playing in the speaker.
34
- * When present, used directly as the scheduling anchor with no additional
35
- * pipeline offset — more accurate than startedAtMs on fast connections.
36
- * Stamp this from the LiveKit onAudioPlaybackStarted callback if available.
37
- */
38
- audioStartedAtMs?: number;
39
- durationMs?: number;
40
- cues: TalkingHeadVisemeCue[];
41
- }
26
+ export type TalkingHeadVisemeSchedule = AgentVisemePayload;
42
27
  export interface TalkingHeadAccessory {
43
28
  id: string;
44
29
  url: string;
@@ -66,6 +51,9 @@ export interface TalkingHeadProps {
66
51
  /** Base URL for vendored assets. When set, replaces all cdn.jsdelivr.net references. */
67
52
  vendorBaseUrl?: string | null;
68
53
  }
54
+ /** @deprecated Use AvatarPlayerProps */
55
+ export type TalkingHeadPropsAlias = TalkingHeadProps;
56
+ export type AvatarPlayerProps = TalkingHeadProps;
69
57
  export interface TalkingHeadRef {
70
58
  sendAmplitude: (amplitude: number) => void;
71
59
  /**
@@ -103,4 +91,7 @@ export interface TalkingHeadRef {
103
91
  /** Dispatch a named motion/gesture to the avatar (e.g. 'wave_right', 'dance_idle'). */
104
92
  dispatchMotion: (name: string) => void;
105
93
  }
94
+ /** @deprecated Use AvatarPlayerRef */
95
+ export type TalkingHeadRefAlias = TalkingHeadRef;
96
+ export type AvatarPlayerRef = TalkingHeadRef;
106
97
  export declare const TalkingHead: React.ForwardRefExoticComponent<TalkingHeadProps & React.RefAttributes<TalkingHeadRef>>;
@@ -26,6 +26,9 @@ export interface TalkingHeadProps {
26
26
  onAvatarState?: (state: string) => void;
27
27
  style?: React.CSSProperties;
28
28
  }
29
+ /** @deprecated Use AvatarPlayerProps */
30
+ export type TalkingHeadPropsAlias = TalkingHeadProps;
31
+ export type AvatarPlayerProps = TalkingHeadProps;
29
32
  export interface TalkingHeadRef {
30
33
  sendAmplitude: (amplitude: number) => void;
31
34
  scheduleVisemes: (schedule: TalkingHeadVisemeSchedule) => void;
@@ -37,4 +40,7 @@ export interface TalkingHeadRef {
37
40
  setAccessories: (accessories: TalkingHeadAccessory[]) => void;
38
41
  dispatchMotion: (name: string) => void;
39
42
  }
43
+ /** @deprecated Use AvatarPlayerRef */
44
+ export type TalkingHeadRefAlias = TalkingHeadRef;
45
+ export type AvatarPlayerRef = TalkingHeadRef;
40
46
  export declare const TalkingHead: React.ForwardRefExoticComponent<TalkingHeadProps & React.RefAttributes<TalkingHeadRef>>;
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
36
  exports.TalkingHead = void 0;
27
37
  const jsx_runtime_1 = require("react/jsx-runtime");
@@ -203,6 +213,6 @@ exports.TalkingHead = (0, react_1.forwardRef)(({ avatarUrl, authToken, mood = 'n
203
213
  window.addEventListener('message', onMessage);
204
214
  return () => window.removeEventListener('message', onMessage);
205
215
  }, [post]);
206
- return ((0, jsx_runtime_1.jsx)("div", { style: { ...containerStyle, ...style }, children: (0, jsx_runtime_1.jsx)("iframe", { ref: iframeRef, srcDoc: srcdoc, style: iframeStyle, sandbox: "allow-scripts allow-same-origin", title: "TalkingHead Avatar" }) }));
216
+ return ((0, jsx_runtime_1.jsx)("div", { style: { ...containerStyle, ...style }, children: (0, jsx_runtime_1.jsx)("iframe", { ref: iframeRef, srcDoc: srcdoc, style: iframeStyle, title: "TalkingHead Avatar" }) }));
207
217
  });
208
218
  exports.TalkingHead.displayName = 'TalkingHead';
@@ -1,6 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uploadVoiceSample = exports.createVoiceProfile = exports.assetFileUrl = exports.suggestPlacement = exports.deleteAsset = exports.uploadAsset = exports.getAsset = exports.listAssets = exports.thumbnailHeaders = exports.avatarAnimatedThumbnailUrl = exports.avatarThumbnailUrl = exports.avatarFileUrl = exports.removeDefaultVoice = exports.setDefaultVoice = exports.getVoiceProfiles = exports.getVoiceProfileSamples = exports.getPublicAvatars = exports.createAvatar = exports.deleteAvatar = exports.updateAvatar = exports.getAvatar = exports.getMyAvatars = exports.studioFetch = exports.getToken = exports.configureAvatarApi = void 0;
3
+ exports.configureAvatarApi = configureAvatarApi;
4
+ exports.getToken = getToken;
5
+ exports.studioFetch = studioFetch;
6
+ exports.getMyAvatars = getMyAvatars;
7
+ exports.getAvatar = getAvatar;
8
+ exports.updateAvatar = updateAvatar;
9
+ exports.deleteAvatar = deleteAvatar;
10
+ exports.createAvatar = createAvatar;
11
+ exports.getPublicAvatars = getPublicAvatars;
12
+ exports.getVoiceProfileSamples = getVoiceProfileSamples;
13
+ exports.getVoiceProfiles = getVoiceProfiles;
14
+ exports.setDefaultVoice = setDefaultVoice;
15
+ exports.removeDefaultVoice = removeDefaultVoice;
16
+ exports.avatarFileUrl = avatarFileUrl;
17
+ exports.avatarThumbnailUrl = avatarThumbnailUrl;
18
+ exports.avatarAnimatedThumbnailUrl = avatarAnimatedThumbnailUrl;
19
+ exports.thumbnailHeaders = thumbnailHeaders;
20
+ exports.listAssets = listAssets;
21
+ exports.getAsset = getAsset;
22
+ exports.uploadAsset = uploadAsset;
23
+ exports.deleteAsset = deleteAsset;
24
+ exports.suggestPlacement = suggestPlacement;
25
+ exports.assetFileUrl = assetFileUrl;
26
+ exports.createVoiceProfile = createVoiceProfile;
27
+ exports.uploadVoiceSample = uploadVoiceSample;
4
28
  // ---------------------------------------------------------------------------
5
29
  // Injectable configuration
6
30
  // ---------------------------------------------------------------------------
@@ -11,11 +35,9 @@ function configureAvatarApi(opts) {
11
35
  _baseUrl = opts.baseUrl;
12
36
  _getToken = opts.getToken;
13
37
  }
14
- exports.configureAvatarApi = configureAvatarApi;
15
38
  async function getToken() {
16
39
  return _getToken ? _getToken() : null;
17
40
  }
18
- exports.getToken = getToken;
19
41
  // ---------------------------------------------------------------------------
20
42
  // Core fetch wrappers
21
43
  // ---------------------------------------------------------------------------
@@ -48,7 +70,6 @@ async function studioFetch(path, options) {
48
70
  }
49
71
  return res.json();
50
72
  }
51
- exports.studioFetch = studioFetch;
52
73
  /** POST/PUT with FormData, sharing auth token logic. */
53
74
  async function studioFetchForm(path, method, formData) {
54
75
  const token = await getToken();
@@ -76,11 +97,9 @@ async function studioFetchForm(path, method, formData) {
76
97
  function getMyAvatars() {
77
98
  return studioFetch('/v1/avatars');
78
99
  }
79
- exports.getMyAvatars = getMyAvatars;
80
100
  function getAvatar(id) {
81
101
  return studioFetch(`/v1/avatars/${id}`);
82
102
  }
83
- exports.getAvatar = getAvatar;
84
103
  function updateAvatar(id, data) {
85
104
  return studioFetch(`/v1/avatars/${id}`, {
86
105
  method: 'PATCH',
@@ -88,11 +107,9 @@ function updateAvatar(id, data) {
88
107
  body: JSON.stringify(data),
89
108
  });
90
109
  }
91
- exports.updateAvatar = updateAvatar;
92
110
  function deleteAvatar(id) {
93
111
  return studioFetch(`/v1/avatars/${id}`, { method: 'DELETE' });
94
112
  }
95
- exports.deleteAvatar = deleteAvatar;
96
113
  async function createAvatar(fileUri, name, description) {
97
114
  const formData = new FormData();
98
115
  formData.append('file', {
@@ -106,22 +123,18 @@ async function createAvatar(fileUri, name, description) {
106
123
  }
107
124
  return studioFetchForm('/v1/avatars', 'POST', formData);
108
125
  }
109
- exports.createAvatar = createAvatar;
110
126
  function getPublicAvatars() {
111
127
  return studioFetch('/v1/avatars/public');
112
128
  }
113
- exports.getPublicAvatars = getPublicAvatars;
114
129
  // ---------------------------------------------------------------------------
115
130
  // Voice profile endpoints
116
131
  // ---------------------------------------------------------------------------
117
132
  function getVoiceProfileSamples(profileId) {
118
133
  return studioFetch(`/profiles/${profileId}/samples`);
119
134
  }
120
- exports.getVoiceProfileSamples = getVoiceProfileSamples;
121
135
  function getVoiceProfiles() {
122
136
  return studioFetch('/profiles');
123
137
  }
124
- exports.getVoiceProfiles = getVoiceProfiles;
125
138
  function setDefaultVoice(avatarId, profileId) {
126
139
  return studioFetch(`/v1/avatars/${avatarId}/default-voice`, {
127
140
  method: 'PUT',
@@ -129,13 +142,11 @@ function setDefaultVoice(avatarId, profileId) {
129
142
  body: JSON.stringify({ profile_id: profileId }),
130
143
  });
131
144
  }
132
- exports.setDefaultVoice = setDefaultVoice;
133
145
  function removeDefaultVoice(avatarId) {
134
146
  return studioFetch(`/v1/avatars/${avatarId}/default-voice`, {
135
147
  method: 'DELETE',
136
148
  });
137
149
  }
138
- exports.removeDefaultVoice = removeDefaultVoice;
139
150
  // ---------------------------------------------------------------------------
140
151
  // URL helpers
141
152
  // ---------------------------------------------------------------------------
@@ -143,27 +154,23 @@ function avatarFileUrl(avatar) {
143
154
  const base = `${_baseUrl}${avatar.url}`;
144
155
  return avatar.updated_at ? `${base}?v=${encodeURIComponent(avatar.updated_at)}` : base;
145
156
  }
146
- exports.avatarFileUrl = avatarFileUrl;
147
157
  function avatarThumbnailUrl(avatar) {
148
158
  const url = avatar.thumbnail_url || avatar.animated_thumbnail_url;
149
159
  if (!url)
150
160
  return null;
151
161
  return `${_baseUrl}${url}`;
152
162
  }
153
- exports.avatarThumbnailUrl = avatarThumbnailUrl;
154
163
  function avatarAnimatedThumbnailUrl(avatar) {
155
164
  if (!avatar.animated_thumbnail_url)
156
165
  return null;
157
166
  return `${_baseUrl}${avatar.animated_thumbnail_url}`;
158
167
  }
159
- exports.avatarAnimatedThumbnailUrl = avatarAnimatedThumbnailUrl;
160
168
  async function thumbnailHeaders() {
161
169
  const token = await getToken();
162
170
  if (!token)
163
171
  return {};
164
172
  return { Authorization: `Bearer ${token}` };
165
173
  }
166
- exports.thumbnailHeaders = thumbnailHeaders;
167
174
  // ---------------------------------------------------------------------------
168
175
  // Wearable Asset endpoints
169
176
  // ---------------------------------------------------------------------------
@@ -171,11 +178,9 @@ function listAssets(category) {
171
178
  const query = category ? `?category=${encodeURIComponent(category)}` : '';
172
179
  return studioFetch(`/v1/assets${query}`);
173
180
  }
174
- exports.listAssets = listAssets;
175
181
  function getAsset(id) {
176
182
  return studioFetch(`/v1/assets/${id}`);
177
183
  }
178
- exports.getAsset = getAsset;
179
184
  async function uploadAsset(fileUri, meta) {
180
185
  const formData = new FormData();
181
186
  formData.append('file', {
@@ -198,20 +203,16 @@ async function uploadAsset(fileUri, meta) {
198
203
  formData.append('hides_body_parts', JSON.stringify(meta.hides_body_parts));
199
204
  return studioFetchForm('/v1/assets', 'POST', formData);
200
205
  }
201
- exports.uploadAsset = uploadAsset;
202
206
  function deleteAsset(id) {
203
207
  return studioFetch(`/v1/assets/${id}`, { method: 'DELETE' });
204
208
  }
205
- exports.deleteAsset = deleteAsset;
206
209
  function suggestPlacement(assetId, avatarId) {
207
210
  return studioFetch(`/v1/assets/${assetId}/suggest-placement?avatar_id=${encodeURIComponent(avatarId)}`, { method: 'POST' });
208
211
  }
209
- exports.suggestPlacement = suggestPlacement;
210
212
  function assetFileUrl(asset) {
211
213
  const base = `${_baseUrl}${asset.url}`;
212
214
  return asset.updated_at ? `${base}?v=${encodeURIComponent(asset.updated_at)}` : base;
213
215
  }
214
- exports.assetFileUrl = assetFileUrl;
215
216
  async function createVoiceProfile(name, language = 'en') {
216
217
  return studioFetch('/profiles', {
217
218
  method: 'POST',
@@ -221,7 +222,6 @@ async function createVoiceProfile(name, language = 'en') {
221
222
  body: JSON.stringify({ name, language }),
222
223
  });
223
224
  }
224
- exports.createVoiceProfile = createVoiceProfile;
225
225
  async function uploadVoiceSample(profileId, fileUri, fileName, referenceText) {
226
226
  const formData = new FormData();
227
227
  formData.append('reference_text', referenceText);
@@ -233,4 +233,3 @@ async function uploadVoiceSample(profileId, fileUri, fileName, referenceText) {
233
233
  });
234
234
  return studioFetchForm(`/profiles/${profileId}/samples`, 'POST', formData);
235
235
  }
236
- exports.uploadVoiceSample = uploadVoiceSample;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.applyAppearanceToObject3D = exports.invalidateMorphCache = void 0;
3
+ exports.invalidateMorphCache = invalidateMorphCache;
4
+ exports.applyAppearanceToObject3D = applyAppearanceToObject3D;
4
5
  const matchers_1 = require("./matchers");
5
6
  const schema_1 = require("./schema");
6
7
  function asMaterialArray(material) {
@@ -60,7 +61,6 @@ function getMorphCache(object3d) {
60
61
  function invalidateMorphCache(object3d) {
61
62
  morphCacheByScene.delete(object3d);
62
63
  }
63
- exports.invalidateMorphCache = invalidateMorphCache;
64
64
  // ---------------------------------------------------------------------------
65
65
  // Main apply function
66
66
  // ---------------------------------------------------------------------------
@@ -121,4 +121,3 @@ function applyAppearanceToObject3D(object3d, appearance) {
121
121
  }
122
122
  }
123
123
  }
124
- exports.applyAppearanceToObject3D = applyAppearanceToObject3D;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pickTargetForMaterialName = void 0;
3
+ exports.pickTargetForMaterialName = pickTargetForMaterialName;
4
4
  function tokenizeMaterialName(name) {
5
5
  return name
6
6
  .replace(/([a-z\d])([A-Z])/g, '$1 $2')
@@ -33,4 +33,3 @@ function pickTargetForMaterialName(name) {
33
33
  }
34
34
  return null;
35
35
  }
36
- exports.pickTargetForMaterialName = pickTargetForMaterialName;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.normalizeAppearance = void 0;
3
+ exports.normalizeAppearance = normalizeAppearance;
4
4
  const HEX_COLOR_PATTERN = /^#(?:[\dA-Fa-f]{3}|[\dA-Fa-f]{6})$/;
5
5
  function normalizeHexColor(value) {
6
6
  if (!HEX_COLOR_PATTERN.test(value)) {
@@ -22,4 +22,3 @@ function normalizeAppearance(input) {
22
22
  faceShapeWeights: input.faceShapeWeights,
23
23
  };
24
24
  }
25
- exports.normalizeAppearance = normalizeAppearance;
@@ -0,0 +1,130 @@
1
+ import type { FaceControl } from './faceControls';
2
+ /**
3
+ * A render target is intentionally abstract here; concrete backends can
4
+ * accept whatever they need (WebGL/WebGPU context, React Native surface,
5
+ * WebView, etc.) via their own config layer.
6
+ */
7
+ export type AvatarRenderTarget = unknown;
8
+ /**
9
+ * Canonical interface that all avatar renderers implement.
10
+ *
11
+ * The idea is:
12
+ * - talking-head-studio produces a stream of FaceControl values
13
+ * (pose + ExpressionState) from TTS visemes, emotions, and motions.
14
+ * - Each AvatarBackend implementation adapts those controls to its
15
+ * own rendering mechanism (morph targets, FLAME coefficients,
16
+ * Gaussian splats, 2D sprites, etc.).
17
+ */
18
+ export interface AvatarBackend {
19
+ /** Optional one-time initialization step (e.g. load meshes, shaders, splats). */
20
+ initialize?(): Promise<void> | void;
21
+ /** Configure the backend with a render target (canvas, surface, WebView, etc.). */
22
+ attach(target: AvatarRenderTarget): Promise<void> | void;
23
+ /**
24
+ * Update the current pose + expression state.
25
+ * This should be a cheap, per-frame call.
26
+ */
27
+ setControl(control: FaceControl): void;
28
+ /** Render the current control state into the attached target. */
29
+ renderFrame(): void;
30
+ /** Clean up graphics resources when the avatar is no longer needed. */
31
+ dispose?(): void;
32
+ }
33
+ /**
34
+ * Per-avatar calibration profile, allowing each asset to respond to the
35
+ * canonical control space in its own way.
36
+ *
37
+ * This is intentionally lightweight and focused on scalar ranges and
38
+ * asymmetries instead of model-specific knobs. Backends can use these
39
+ * values to remap 0..1 sliders into whatever ranges their rigs expect.
40
+ */
41
+ export type CalibrationProfile = {
42
+ /** Neutral pose + expression for this avatar. */
43
+ neutral: FaceControl;
44
+ /** Per-channel scalar ranges (0..1 logical → model space). */
45
+ ranges?: {
46
+ jawOpen?: {
47
+ min: number;
48
+ max: number;
49
+ };
50
+ mouthSmile?: {
51
+ min: number;
52
+ max: number;
53
+ };
54
+ mouthFunnel?: {
55
+ min: number;
56
+ max: number;
57
+ };
58
+ mouthPucker?: {
59
+ min: number;
60
+ max: number;
61
+ };
62
+ mouthWide?: {
63
+ min: number;
64
+ max: number;
65
+ };
66
+ upperLipRaise?: {
67
+ min: number;
68
+ max: number;
69
+ };
70
+ lowerLipDepress?: {
71
+ min: number;
72
+ max: number;
73
+ };
74
+ cheekRaise?: {
75
+ min: number;
76
+ max: number;
77
+ };
78
+ blinkLeft?: {
79
+ min: number;
80
+ max: number;
81
+ };
82
+ blinkRight?: {
83
+ min: number;
84
+ max: number;
85
+ };
86
+ browInnerUp?: {
87
+ min: number;
88
+ max: number;
89
+ };
90
+ browDownLeft?: {
91
+ min: number;
92
+ max: number;
93
+ };
94
+ browDownRight?: {
95
+ min: number;
96
+ max: number;
97
+ };
98
+ };
99
+ /** Simple asymmetry controls (e.g. left/right smile strength). */
100
+ asymmetry?: {
101
+ smileLeftBias?: number;
102
+ blinkLeftBias?: number;
103
+ };
104
+ /** Eye gaze range limits (default: full -1..1 on both axes). */
105
+ gazeLimits?: {
106
+ x?: {
107
+ min: number;
108
+ max: number;
109
+ };
110
+ y?: {
111
+ min: number;
112
+ max: number;
113
+ };
114
+ };
115
+ /** Optional head pose limits to keep motion comfortable. */
116
+ poseLimits?: {
117
+ yaw?: {
118
+ min: number;
119
+ max: number;
120
+ };
121
+ pitch?: {
122
+ min: number;
123
+ max: number;
124
+ };
125
+ roll?: {
126
+ min: number;
127
+ max: number;
128
+ };
129
+ };
130
+ };
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ // src/core/avatar/backend.ts
3
+ // Backend-agnostic avatar driver contracts built on top of FaceControl.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,49 @@
1
+ import type * as THREE from 'three';
2
+ import type { AvatarBackend, AvatarRenderTarget, CalibrationProfile } from '../backend';
3
+ import type { FaceControl } from '../faceControls';
4
+ import type { AvatarSchemaReport } from '../schema';
5
+ export interface GaussianBackendConfig {
6
+ /** Three.js scene root (gltf.scene). */
7
+ scene: THREE.Object3D;
8
+ /** Schema report from walkAvatarSchema — used to log coverage and skip morphs. */
9
+ schemaReport: AvatarSchemaReport;
10
+ /** Optional calibration profile for gaze limits and expression scaling. */
11
+ calibration?: CalibrationProfile;
12
+ /**
13
+ * 'fast' — only jaw bone driven (best for real-time on low-end devices).
14
+ * 'full' — all FLAME bone coefficients applied (default).
15
+ */
16
+ patchQuality?: 'fast' | 'full';
17
+ }
18
+ export declare class GaussianBackend implements AvatarBackend {
19
+ private scene;
20
+ private schemaReport;
21
+ private calibration;
22
+ private patchQuality;
23
+ /** Resolved skeleton bone map: canonical name → Three.js Bone */
24
+ private boneMap;
25
+ /** Pre-computed deformation patches, one per Oculus viseme. */
26
+ private patches;
27
+ /** Snapshot of bone rotations at attach() — restored on dispose(). */
28
+ private basePose;
29
+ /** Last control applied — used in renderFrame() to avoid redundant writes. */
30
+ private lastControl;
31
+ constructor(config: GaussianBackendConfig);
32
+ initialize(): void;
33
+ attach(_target: AvatarRenderTarget): void;
34
+ setControl(control: FaceControl): void;
35
+ renderFrame(): void;
36
+ dispose(): void;
37
+ private _buildBoneMap;
38
+ private _captureBasePose;
39
+ private _restoreBasePose;
40
+ private _buildPatches;
41
+ private _applyControl;
42
+ private _applyGaze;
43
+ /** Canonical bones resolved from the skeleton. Useful for debugging rig coverage. */
44
+ get resolvedBones(): string[];
45
+ /** Number of viseme patches with at least one resolved bone. */
46
+ get patchCoverage(): number;
47
+ /** The schema report this backend was initialized with. */
48
+ get schema(): AvatarSchemaReport;
49
+ }