talking-head-studio 0.4.10 → 0.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +299 -337
- package/dist/TalkingHead.d.ts +44 -28
- package/dist/TalkingHead.js +21 -2
- package/dist/TalkingHead.web.d.ts +37 -4
- package/dist/TalkingHead.web.js +28 -8
- package/dist/TalkingHeadVisualization.d.ts +22 -0
- package/dist/TalkingHeadVisualization.js +30 -10
- package/dist/api/studioApi.d.ts +12 -1
- package/dist/api/studioApi.js +41 -28
- package/dist/appearance/apply.js +2 -3
- package/dist/appearance/matchers.js +1 -2
- package/dist/appearance/schema.js +1 -2
- package/dist/contract.d.ts +14 -0
- package/dist/contract.js +30 -0
- package/dist/core/avatar/avatarCapabilities.d.ts +60 -0
- package/dist/core/avatar/avatarCapabilities.js +100 -0
- package/dist/core/avatar/backend.d.ts +130 -0
- package/dist/core/avatar/backend.js +4 -0
- package/dist/core/avatar/backends/gaussian.d.ts +49 -0
- package/dist/core/avatar/backends/gaussian.js +293 -0
- package/dist/core/avatar/backends/index.d.ts +3 -0
- package/dist/core/avatar/backends/index.js +7 -0
- package/dist/core/avatar/backends/morphTarget.d.ts +39 -0
- package/dist/core/avatar/backends/morphTarget.js +179 -0
- package/dist/core/avatar/faceControls.d.ts +40 -0
- package/dist/core/avatar/faceControls.js +138 -0
- package/dist/core/avatar/motion.d.ts +1713 -0
- package/dist/core/avatar/motion.js +550 -0
- package/dist/core/avatar/motionRuntime.d.ts +46 -0
- package/dist/core/avatar/motionRuntime.js +84 -0
- package/dist/core/avatar/schema.d.ts +78 -0
- package/dist/core/avatar/schema.js +134 -0
- package/dist/core/avatar/visemes.d.ts +47 -1
- package/dist/core/avatar/visemes.js +114 -1
- package/dist/editor/AvatarCanvas.js +93 -3
- package/dist/editor/AvatarEditor.native.js +19 -9
- package/dist/editor/AvatarModel.js +2 -2
- package/dist/editor/FaceSqueezeEditor.d.ts +3 -1
- package/dist/editor/FaceSqueezeEditor.js +195 -121
- package/dist/editor/FaceSqueezeEditor.web.d.ts +3 -1
- package/dist/editor/FaceSqueezeEditor.web.js +32 -30
- package/dist/editor/RigidAccessory.js +18 -4
- package/dist/editor/SkinnedClothing.js +19 -9
- package/dist/editor/boneLockedDrag.d.ts +11 -0
- package/dist/editor/boneLockedDrag.js +68 -0
- package/dist/editor/boneSnap.js +22 -12
- package/dist/editor/boneSnap.web.d.ts +27 -0
- package/dist/editor/boneSnap.web.js +99 -0
- package/dist/editor/index.web.d.ts +10 -0
- package/dist/editor/index.web.js +26 -0
- package/dist/editor/sounds/haha.wav +0 -0
- package/dist/editor/sounds/owie.wav +0 -0
- package/dist/editor/sounds/stop.wav +0 -0
- package/dist/editor/studioTheme.d.ts +14 -14
- package/dist/editor/studioTheme.js +19 -16
- package/dist/editor/types.d.ts +1 -0
- package/dist/html/accessories.d.ts +7 -0
- package/dist/html/accessories.js +149 -0
- package/dist/html/motion.d.ts +1 -0
- package/dist/html/motion.js +189 -0
- package/dist/html/visemes.d.ts +7 -0
- package/dist/html/visemes.js +348 -0
- package/dist/html.d.ts +1 -1
- package/dist/html.js +56 -734
- package/dist/index.d.ts +19 -1
- package/dist/index.js +44 -5
- package/dist/index.web.d.ts +18 -1
- package/dist/index.web.js +36 -3
- package/dist/platform/api/types.d.ts +10 -0
- package/dist/platform/api/types.js +2 -0
- package/dist/platform/marketplace/types.d.ts +32 -0
- package/dist/platform/marketplace/types.js +2 -0
- package/dist/platform/sdk/unity.d.ts +27 -0
- package/dist/platform/sdk/unity.js +2 -0
- package/dist/platform/sdk/unreal.d.ts +23 -0
- package/dist/platform/sdk/unreal.js +2 -0
- package/dist/platform/sdk/web.d.ts +16 -0
- package/dist/platform/sdk/web.js +2 -0
- package/dist/sketchfab/api.js +5 -5
- package/dist/sketchfab/glbInspect.d.ts +22 -0
- package/dist/sketchfab/glbInspect.js +58 -0
- package/dist/sketchfab/index.d.ts +3 -0
- package/dist/sketchfab/index.js +8 -1
- package/dist/sketchfab/inspectRemote.d.ts +13 -0
- package/dist/sketchfab/inspectRemote.js +77 -0
- package/dist/sketchfab/types.d.ts +10 -0
- package/dist/sketchfab/useSketchfabSearch.js +1 -2
- package/dist/studio/AccessoryBrowserScreen.d.ts +6 -0
- package/dist/studio/AccessoryBrowserScreen.js +626 -0
- package/dist/studio/AccessoryPanel.d.ts +10 -0
- package/dist/studio/AccessoryPanel.js +396 -0
- package/dist/studio/AppearancePanel.d.ts +9 -0
- package/dist/studio/AppearancePanel.js +77 -0
- package/dist/studio/AvatarCreatorScreen.d.ts +5 -0
- package/dist/studio/AvatarCreatorScreen.js +806 -0
- package/dist/studio/AvatarEditorScreen.d.ts +14 -0
- package/dist/studio/AvatarEditorScreen.js +510 -0
- package/dist/studio/AvatarGrid.d.ts +23 -0
- package/dist/studio/AvatarGrid.js +257 -0
- package/dist/studio/ColorSwatch.d.ts +8 -0
- package/dist/studio/ColorSwatch.js +100 -0
- package/dist/studio/CreateVoiceProfileSheet.d.ts +8 -0
- package/dist/studio/CreateVoiceProfileSheet.js +242 -0
- package/dist/studio/DetailsPanel.d.ts +15 -0
- package/dist/studio/DetailsPanel.js +239 -0
- package/dist/studio/FilamentEditor.d.ts +2 -0
- package/dist/studio/FilamentEditor.js +6 -0
- package/dist/studio/PrecisionPanel.d.ts +2 -0
- package/dist/studio/PrecisionPanel.js +7 -0
- package/dist/studio/PublicGalleryScreen.d.ts +5 -0
- package/dist/studio/PublicGalleryScreen.js +358 -0
- package/dist/studio/SketchfabModelCard.d.ts +20 -0
- package/dist/studio/SketchfabModelCard.js +104 -0
- package/dist/studio/StudioBrowseHeader.d.ts +9 -0
- package/dist/studio/StudioBrowseHeader.js +28 -0
- package/dist/studio/StudioEmptyState.d.ts +8 -0
- package/dist/studio/StudioEmptyState.js +29 -0
- package/dist/studio/StudioFloatingAction.d.ts +13 -0
- package/dist/studio/StudioFloatingAction.js +42 -0
- package/dist/studio/StudioSectionHeader.d.ts +7 -0
- package/dist/studio/StudioSectionHeader.js +27 -0
- package/dist/studio/StudioSurfaceCard.d.ts +8 -0
- package/dist/studio/StudioSurfaceCard.js +20 -0
- package/dist/studio/VoicePanel.d.ts +15 -0
- package/dist/studio/VoicePanel.js +305 -0
- package/dist/studio/constants.d.ts +3 -0
- package/dist/studio/constants.js +6 -0
- package/dist/studio/index.d.ts +29 -0
- package/dist/studio/index.js +54 -0
- package/dist/studio/useSketchfabCapabilities.d.ts +31 -0
- package/dist/studio/useSketchfabCapabilities.js +82 -0
- package/dist/tts/useDirectVisemeStream.d.ts +2 -6
- package/dist/tts/useDirectVisemeStream.js +16 -12
- package/dist/tts/useMotionMarkers.d.ts +0 -1
- package/dist/tts/useMotionMarkers.js +1 -2
- package/dist/utils/avatarUtils.js +94 -8
- package/dist/utils/faceLandmarkerToShapeWeights.js +21 -14
- package/dist/voice/convertToWav.js +1 -2
- package/dist/voice/index.d.ts +3 -0
- package/dist/voice/index.js +6 -1
- package/dist/voice/useAudioPlayer.js +18 -6
- package/dist/voice/useAudioRecording.js +1 -2
- package/dist/voice/useFaceControls.d.ts +14 -0
- package/dist/voice/useFaceControls.js +81 -0
- package/dist/voice/useVoicePreview.d.ts +7 -0
- package/dist/voice/useVoicePreview.js +83 -0
- package/dist/wardrobe/index.d.ts +3 -0
- package/dist/wardrobe/index.js +8 -1
- package/dist/wardrobe/useAccessoryGestures.d.ts +20 -0
- package/dist/wardrobe/useAccessoryGestures.js +94 -0
- package/dist/wardrobe/useAvatarWardrobeHydration.js +9 -4
- package/dist/wardrobe/useStudioAvatar.d.ts +29 -0
- package/dist/wardrobe/useStudioAvatar.js +186 -0
- package/dist/wardrobe/wardrobeStore.d.ts +2 -0
- package/dist/wardrobe/wardrobeStore.js +12 -2
- package/dist/wgpu/R3FWebGpuCanvas.d.ts +15 -0
- package/dist/wgpu/R3FWebGpuCanvas.js +176 -0
- package/dist/wgpu/WgpuAvatar.d.ts +26 -2
- package/dist/wgpu/WgpuAvatar.js +313 -46
- package/dist/wgpu/accessoryDefaults.d.ts +12 -0
- package/dist/wgpu/accessoryDefaults.js +19 -0
- package/dist/wgpu/blobShim.d.ts +2 -0
- package/dist/wgpu/blobShim.js +191 -0
- package/dist/wgpu/index.d.ts +1 -0
- package/dist/wgpu/index.js +4 -1
- package/dist/wgpu/loadGLTFFromUri.d.ts +2 -0
- package/dist/wgpu/loadGLTFFromUri.js +75 -0
- package/dist/wgpu/morphTables.js +21 -10
- package/dist/wgpu/motionState.d.ts +20 -0
- package/dist/wgpu/motionState.js +31 -0
- package/dist/wgpu/patchThreeForRN.d.ts +28 -0
- package/dist/wgpu/patchThreeForRN.js +292 -0
- package/dist/wgpu/scenePlacement.d.ts +5 -0
- package/dist/wgpu/scenePlacement.js +50 -0
- package/dist/wgpu/useAuthedModelUri.js +22 -11
- package/dist/wgpu/useNativeGLTF.d.ts +7 -0
- package/dist/wgpu/useNativeGLTF.js +36 -0
- package/package.json +102 -32
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# talking-head-studio
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Make any GLB model talk — on the web and on React Native — with phoneme-accurate, audio-aligned lip-sync. With or without blend shapes.**
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/talking-head-studio)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -8,56 +8,122 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## The point: lip-sync that's driven by the audio, not guessed
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- **Dynamic Wardrobe & Accessories:** Swap hair, skin, and eye colors on the fly. Attach hats, glasses, or backpacks to any bone at runtime.
|
|
13
|
+
Most avatar libraries flap a jaw open in proportion to audio loudness. That reads as
|
|
14
|
+
"mouth moving," not "speaking." talking-head-studio is built around a different model: a
|
|
15
|
+
**viseme schedule** — a timed list of mouth shapes derived from the actual synthesized
|
|
16
|
+
speech — drives morph targets on the model.
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
```
|
|
19
|
+
TTS server ──▶ AgentVisemePayload ──▶ scheduleVisemes() ──▶ morph drive
|
|
20
|
+
(word-aligned { cues: [{ viseme, startMs, (this library, (Three.js
|
|
21
|
+
phonemes) endMs }], durationMs } web + native) morph targets)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The wire format is `AgentVisemePayload`: per-phoneme cues using the 9-shape Rhubarb
|
|
25
|
+
vocabulary (`A`–`H`, `X`), each with a start/end time in milliseconds. The library maps
|
|
26
|
+
those onto Oculus viseme morphs and schedules them against the audio clock, so the mouth
|
|
27
|
+
hits each shape *when that sound is actually heard*.
|
|
28
|
+
|
|
29
|
+
This pairs directly with a TTS server that emits viseme timings from real word alignment
|
|
30
|
+
(we built [Qwen3-TTS](https://github.com/sitebay/Qwen3-TTS) for exactly this — it serves
|
|
31
|
+
`AgentVisemePayload` over an SSE endpoint). But the format is open: emit cues from any
|
|
32
|
+
source and the renderer consumes them identically.
|
|
33
|
+
|
|
34
|
+
### Four lip-sync tiers — every model works
|
|
35
|
+
|
|
36
|
+
The model decides the fidelity; you don't have to pre-process anything.
|
|
37
|
+
|
|
38
|
+
| Your model has… | Method | Quality |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| Oculus viseme morphs | Direct morph drive (`MorphTargetBackend`) | Excellent |
|
|
41
|
+
| ARKit blend shapes (52 AUs) | `remapArkitToOculus()` → morph drive | Good |
|
|
42
|
+
| Only `jawOpen` / `mouthOpen` | Amplitude fallback | Acceptable |
|
|
43
|
+
| No face rig at all | Gaussian splat backend *(roadmap — not yet built)* | Excellent |
|
|
20
44
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- [Installation](#installation)
|
|
24
|
-
- [Quick Start](#quick-start)
|
|
25
|
-
- [Subpath Exports](#subpath-exports)
|
|
26
|
-
- [Props](#props)
|
|
27
|
-
- [Ref API](#ref-api)
|
|
28
|
-
- [Accessories](#accessories)
|
|
29
|
-
- [Color Customization](#color-customization)
|
|
30
|
-
- [Voice Pipeline Integration](#voice-pipeline-integration)
|
|
31
|
-
- [GLB Compatibility](#glb-compatibility)
|
|
32
|
-
- [Plain React / Next.js](#plain-react--nextjs)
|
|
33
|
-
- [MotionEngine (Upcoming)](#motionengine-upcoming)
|
|
34
|
-
- [Contributing](#contributing)
|
|
35
|
-
- [Credits](#credits)
|
|
36
|
-
- [License](#license)
|
|
45
|
+
If a model has no viseme morphs, scheduled cues still fall back to the jaw/amplitude path
|
|
46
|
+
automatically — you never get a frozen face.
|
|
37
47
|
|
|
38
48
|
---
|
|
39
49
|
|
|
40
|
-
##
|
|
50
|
+
## Two renderers, one contract
|
|
41
51
|
|
|
42
|
-
|
|
52
|
+
The same `AgentVisemePayload` / `FaceControl` contract drives both render paths, so you
|
|
53
|
+
write your voice pipeline once:
|
|
54
|
+
|
|
55
|
+
- **Web** — an isolated `<iframe>` running [met4citizen TalkingHead](https://github.com/met4citizen/TalkingHead)
|
|
56
|
+
as the rig (`TalkingHead.web.tsx`). Drop it into any React / Next / Vite app.
|
|
57
|
+
- **React Native** — a native WebGPU renderer (`WgpuAvatar`, via `react-native-wgpu` +
|
|
58
|
+
react-three-fiber). No WebView, no postMessage latency, morphs driven on the GPU.
|
|
59
|
+
|
|
60
|
+
Capabilities differ slightly between the two — see the [capability matrix](#runtime-capability-matrix).
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Install
|
|
43
65
|
|
|
44
66
|
```bash
|
|
67
|
+
# React Native / Expo WebView path
|
|
45
68
|
npm install talking-head-studio react-native-webview
|
|
69
|
+
|
|
70
|
+
# React Native / Expo native WebGPU path
|
|
71
|
+
npx expo install react-native-wgpu @react-three/fiber three three-stdlib expo-asset
|
|
72
|
+
|
|
73
|
+
# Web (React, Next.js, Vite)
|
|
74
|
+
npm install talking-head-studio
|
|
46
75
|
```
|
|
47
76
|
|
|
48
|
-
`react-
|
|
77
|
+
`three`, `@react-three/fiber`, and the platform packages are peer dependencies — bring your
|
|
78
|
+
own versions. `react-native-webview` is only required for the WebView renderer. Native
|
|
79
|
+
WebGPU uses `react-native-wgpu` and must run in a native build, not Expo Go.
|
|
49
80
|
|
|
50
|
-
###
|
|
81
|
+
### React Native / Expo WebGPU setup
|
|
82
|
+
|
|
83
|
+
Native WebGPU needs the React Native new architecture and the WebGPU build of Three.js.
|
|
84
|
+
The example app in `example/` has the full working config; these are the important parts:
|
|
85
|
+
|
|
86
|
+
```jsonc
|
|
87
|
+
// app.json
|
|
88
|
+
{
|
|
89
|
+
"expo": {
|
|
90
|
+
"newArchEnabled": true,
|
|
91
|
+
"plugins": ["expo-asset"]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
// metro.config.js
|
|
98
|
+
const path = require('path');
|
|
99
|
+
const { getDefaultConfig } = require('expo/metro-config');
|
|
100
|
+
|
|
101
|
+
const config = getDefaultConfig(__dirname);
|
|
102
|
+
const nodeModules = path.resolve(__dirname, 'node_modules');
|
|
103
|
+
const threeWebgpu = path.resolve(nodeModules, 'three/build/three.webgpu.js');
|
|
104
|
+
|
|
105
|
+
config.resolver.assetExts.push('glb');
|
|
106
|
+
config.resolver.extraNodeModules = {
|
|
107
|
+
three: threeWebgpu,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
module.exports = config;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Build and launch a native app so `WebGPUModule` is linked:
|
|
51
114
|
|
|
52
115
|
```bash
|
|
53
|
-
|
|
116
|
+
npx expo prebuild --platform android --no-install
|
|
117
|
+
npx expo run:android
|
|
54
118
|
```
|
|
55
119
|
|
|
56
|
-
|
|
120
|
+
Expo Go cannot load the native WebGPU module.
|
|
57
121
|
|
|
58
122
|
---
|
|
59
123
|
|
|
60
|
-
## Quick
|
|
124
|
+
## Quick start
|
|
125
|
+
|
|
126
|
+
### Web / React Native component
|
|
61
127
|
|
|
62
128
|
```tsx
|
|
63
129
|
import { useRef } from 'react';
|
|
@@ -72,397 +138,293 @@ export default function Avatar() {
|
|
|
72
138
|
avatarUrl="https://example.com/your-model.glb"
|
|
73
139
|
mood="happy"
|
|
74
140
|
cameraView="upper"
|
|
75
|
-
hairColor="#1a1a2e"
|
|
76
|
-
skinColor="#e0a370"
|
|
77
|
-
accessories={[
|
|
78
|
-
{
|
|
79
|
-
id: 'sunglasses',
|
|
80
|
-
url: 'https://example.com/sunglasses.glb',
|
|
81
|
-
bone: 'Head',
|
|
82
|
-
position: [0, 0.08, 0.12],
|
|
83
|
-
rotation: [0, 0, 0],
|
|
84
|
-
scale: 1.0,
|
|
85
|
-
},
|
|
86
|
-
]}
|
|
87
141
|
style={{ width: 400, height: 600 }}
|
|
88
|
-
onReady={() =>
|
|
89
|
-
|
|
142
|
+
onReady={() => {
|
|
143
|
+
// Drive the mouth from a viseme schedule (e.g. from your TTS server)
|
|
144
|
+
ref.current?.scheduleVisemes({
|
|
145
|
+
cues: [
|
|
146
|
+
{ viseme: 'A', startMs: 0, endMs: 90 },
|
|
147
|
+
{ viseme: 'E', startMs: 90, endMs: 170 },
|
|
148
|
+
{ viseme: 'X', startMs: 170, endMs: 220 },
|
|
149
|
+
],
|
|
150
|
+
durationMs: 220,
|
|
151
|
+
audioStartedAtMs: Date.now(),
|
|
152
|
+
});
|
|
153
|
+
}}
|
|
90
154
|
/>
|
|
91
155
|
);
|
|
92
156
|
}
|
|
93
157
|
```
|
|
94
158
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
## Subpath Exports
|
|
98
|
-
|
|
99
|
-
The package ships five independent entry points. Import only what you need — each subpath has its own optional peer dependencies.
|
|
100
|
-
|
|
101
|
-
### `talking-head-studio` — Live talking avatar
|
|
102
|
-
```tsx
|
|
103
|
-
import { TalkingHead } from 'talking-head-studio';
|
|
104
|
-
// Peer deps: react
|
|
105
|
-
// Native-only peers: react-native (optional), react-native-webview (optional)
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### `talking-head-studio/editor` — 3D editor with gizmo (web)
|
|
109
|
-
R3F-based canvas with PivotControls gizmo for placing accessories on an avatar. Web only.
|
|
110
|
-
```tsx
|
|
111
|
-
import { AvatarCanvas } from 'talking-head-studio/editor';
|
|
112
|
-
// Peer deps: @react-three/fiber, @react-three/drei, three
|
|
113
|
-
```
|
|
159
|
+
### Native WebGPU (React Native, no WebView)
|
|
114
160
|
|
|
115
|
-
### `talking-head-studio/appearance` — Material color system
|
|
116
|
-
Apply skin/hair/eye colors to any GLB avatar. Works in both the live view and the 3D editor.
|
|
117
161
|
```tsx
|
|
118
|
-
import {
|
|
119
|
-
// No extra peer deps
|
|
120
|
-
```
|
|
162
|
+
import { WgpuAvatar, type WgpuAvatarRef } from 'talking-head-studio/wgpu';
|
|
121
163
|
|
|
122
|
-
|
|
123
|
-
Headless hooks for recording voice samples (WebM→WAV conversion included). Backend-agnostic — send audio wherever you want (Qwen3-TTS, ElevenLabs, Groq, etc).
|
|
124
|
-
```tsx
|
|
125
|
-
import { useAudioRecording, useAudioPlayer } from 'talking-head-studio/voice';
|
|
126
|
-
// No extra peer deps (browser APIs only)
|
|
127
|
-
```
|
|
164
|
+
const ref = useRef<WgpuAvatarRef>(null);
|
|
128
165
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
166
|
+
<WgpuAvatar
|
|
167
|
+
ref={ref}
|
|
168
|
+
avatarUrl="https://example.com/your-model.glb"
|
|
169
|
+
mood="neutral"
|
|
170
|
+
style={{ flex: 1 }}
|
|
171
|
+
/>;
|
|
172
|
+
// ref.current?.scheduleVisemes(payload) — same contract as the web component
|
|
134
173
|
```
|
|
135
174
|
|
|
136
175
|
---
|
|
137
176
|
|
|
138
|
-
##
|
|
177
|
+
## TalkingHead component — props & ref
|
|
178
|
+
|
|
179
|
+
### Props
|
|
139
180
|
|
|
140
181
|
| Prop | Type | Default | Description |
|
|
141
182
|
|------|------|---------|-------------|
|
|
142
|
-
| `avatarUrl` | `string` |
|
|
143
|
-
| `authToken` | `string \| null` | `null` | Bearer token
|
|
144
|
-
| `mood` | `TalkingHeadMood` | `'neutral'` |
|
|
145
|
-
| `cameraView` | `'head' \| 'upper' \| 'full'` | `'upper'` |
|
|
146
|
-
| `cameraDistance` | `number` | `-0.5` |
|
|
147
|
-
| `hairColor` | `string` |
|
|
148
|
-
| `skinColor` | `string` |
|
|
149
|
-
| `eyeColor` | `string` |
|
|
150
|
-
| `accessories` | `TalkingHeadAccessory[]` | `[]` |
|
|
151
|
-
| `onReady` | `() => void` |
|
|
152
|
-
| `onError` | `(
|
|
153
|
-
| `style` | `ViewStyle` |
|
|
154
|
-
|
|
155
|
-
###
|
|
156
|
-
|
|
157
|
-
The `mood` prop accepts one of:
|
|
158
|
-
|
|
159
|
-
```
|
|
160
|
-
neutral | happy | sad | angry | excited | thinking | concerned | surprised
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Mood can be changed at any time via props or the ref API. On rigged models, mood maps to blend shape expressions. On non-rigged models, mood is a no-op.
|
|
164
|
-
|
|
165
|
-
---
|
|
183
|
+
| `avatarUrl` | `string` | required | Any `.glb`. Rigged or not. |
|
|
184
|
+
| `authToken` | `string \| null` | `null` | Bearer token for authenticated GLB URLs. |
|
|
185
|
+
| `mood` | `TalkingHeadMood` | `'neutral'` | `neutral \| happy \| sad \| angry \| fear \| disgust \| love \| sleep \| excited \| thinking \| concerned \| surprised` |
|
|
186
|
+
| `cameraView` | `'head' \| 'upper' \| 'full'` | `'upper'` | Framing preset. |
|
|
187
|
+
| `cameraDistance` | `number` | `-0.5` | Zoom offset. Negative = closer. |
|
|
188
|
+
| `hairColor` | `string` | — | Hex color. Applied to materials named `hair`, `fur`. |
|
|
189
|
+
| `skinColor` | `string` | — | Applied to `skin`, `body`, `face`. |
|
|
190
|
+
| `eyeColor` | `string` | — | Applied to `eye`, `iris`. |
|
|
191
|
+
| `accessories` | `TalkingHeadAccessory[]` | `[]` | Bone-attached GLB items. |
|
|
192
|
+
| `onReady` | `() => void` | — | Fired when fully loaded. |
|
|
193
|
+
| `onError` | `(msg: string) => void` | — | Fired on load failure. |
|
|
194
|
+
| `style` | `ViewStyle / CSSProperties` | — | Container style. |
|
|
195
|
+
|
|
196
|
+
### Ref methods
|
|
166
197
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const ref = useRef<TalkingHeadRef>(null);
|
|
173
|
-
|
|
174
|
-
// Drive lip-sync from an audio amplitude value (0..1)
|
|
175
|
-
ref.current?.sendAmplitude(0.7);
|
|
198
|
+
```ts
|
|
199
|
+
// Lip-sync
|
|
200
|
+
ref.current?.scheduleVisemes(payload); // AgentVisemePayload → full timed lip-sync schedule
|
|
201
|
+
ref.current?.clearVisemes();
|
|
202
|
+
ref.current?.sendAmplitude(0.7); // amplitude 0..1 → jaw (fallback / no schedule)
|
|
176
203
|
|
|
177
|
-
//
|
|
204
|
+
// Expression & appearance
|
|
178
205
|
ref.current?.setMood('excited');
|
|
179
|
-
|
|
180
|
-
// Change colors at runtime
|
|
181
206
|
ref.current?.setHairColor('#ff0000');
|
|
182
207
|
ref.current?.setSkinColor('#8d5524');
|
|
183
208
|
ref.current?.setEyeColor('#2e86de');
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
scale: 0.8,
|
|
194
|
-
},
|
|
195
|
-
]);
|
|
209
|
+
ref.current?.setAccessories([...]);
|
|
210
|
+
|
|
211
|
+
// Body — procedural motions, gestures, poses, animation clips
|
|
212
|
+
ref.current?.dispatchMotion('groove'); // looping procedural motion
|
|
213
|
+
ref.current?.stopMotion();
|
|
214
|
+
ref.current?.playGesture('thumbup'); // upstream hand gesture
|
|
215
|
+
ref.current?.playPose('oneknee'); // upstream pose template
|
|
216
|
+
ref.current?.playAnimation('/animations/wave.glb', { dur: 2 });
|
|
217
|
+
ref.current?.lookAt(120, 80, 500); // turn toward viewport coords
|
|
196
218
|
```
|
|
197
219
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
220
|
+
The motion vocabulary (`groove`, `wave`, `nod`, `idle`, `attack`, `defend`, `celebrate`,
|
|
221
|
+
plus every upstream gesture/pose name) is exported as typed constants —
|
|
222
|
+
`MOTION_KEYS`, `TALKINGHEAD_GESTURES`, `TALKINGHEAD_POSES`, and the `isMotionKey()` guard —
|
|
223
|
+
from both the package root and `talking-head-studio/contract`.
|
|
224
|
+
|
|
225
|
+
### Runtime capability matrix
|
|
226
|
+
|
|
227
|
+
Both renderers share one API; where native can't match the WebView's upstream rig, it
|
|
228
|
+
falls back to a procedural approximation rather than failing. This table is the honest gap
|
|
229
|
+
list.
|
|
230
|
+
|
|
231
|
+
| Feature | Web (iframe) | Native (WGPU) | Notes |
|
|
232
|
+
|---|:---:|:---:|---|
|
|
233
|
+
| Viseme schedules (`scheduleVisemes`) | ✅ | ✅ | Both consume `AgentVisemePayload`. |
|
|
234
|
+
| Amplitude jaw fallback (`sendAmplitude`) | ✅ | ⚠️ | Web drives jaw from amplitude; native exposes the method for API parity. |
|
|
235
|
+
| Core procedural motions (`groove`, `attack`, `defend`) | ✅ | ✅ | Shared `MOTION_DEFS` source of truth. |
|
|
236
|
+
| Gesture names (`thumbup`, `shrug`, …) | ✅ | ⚠️ | Web delegates to TalkingHead; native uses procedural approximations. |
|
|
237
|
+
| Pose names (`oneknee`, `kneel`, `sitting`, …) | ✅ | ⚠️ | Web delegates to TalkingHead; native uses static procedural poses. |
|
|
238
|
+
| Full mood vocabulary | ✅ | ✅ | All 8 upstream moods + friendly aliases. |
|
|
239
|
+
| External animation clips (`playAnimation`) | ✅ | ⚠️ | Web delegates to TalkingHead; native plays GLB clips via `AnimationMixer`. |
|
|
240
|
+
| Gaze (`lookAt`) | ✅ | ❌ | Native eye/head-gaze bridge is future work. |
|
|
241
|
+
| Listening / mic-reactive mouth | ⚠️ | ❌ | Web can route host-provided audio; native bridge not implemented. |
|
|
208
242
|
|
|
209
243
|
---
|
|
210
244
|
|
|
211
|
-
##
|
|
212
|
-
|
|
213
|
-
Attach any GLB model to any bone on the avatar skeleton. The system handles loading, disposal, and transform updates.
|
|
245
|
+
## Self-hosting the runtime assets
|
|
214
246
|
|
|
215
|
-
|
|
247
|
+
By default the web iframe pulls the TalkingHead rig, three.js, and the HeadAudio model
|
|
248
|
+
from public CDNs (jsDelivr, gstatic). To run fully self-hosted — no external CDN — vendor
|
|
249
|
+
those files and point the renderer at your own origin:
|
|
216
250
|
|
|
217
251
|
```ts
|
|
218
|
-
|
|
219
|
-
id: string; // Unique identifier for diffing
|
|
220
|
-
url: string; // URL to a .glb file
|
|
221
|
-
bone: string; // Target bone name (e.g. "Head", "RightHand", "Spine")
|
|
222
|
-
position: [number, number, number]; // Offset from the bone origin
|
|
223
|
-
rotation: [number, number, number]; // Euler rotation in radians
|
|
224
|
-
scale: number; // Uniform scale factor
|
|
225
|
-
}
|
|
226
|
-
```
|
|
252
|
+
import { buildAvatarHtml } from 'talking-head-studio/html';
|
|
227
253
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
accessories={[
|
|
234
|
-
{
|
|
235
|
-
id: 'cowboy-hat',
|
|
236
|
-
url: '/models/cowboy-hat.glb',
|
|
237
|
-
bone: 'Head',
|
|
238
|
-
position: [0, 0.18, 0],
|
|
239
|
-
rotation: [0, 0, 0],
|
|
240
|
-
scale: 1.2,
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
id: 'aviators',
|
|
244
|
-
url: '/models/aviator-glasses.glb',
|
|
245
|
-
bone: 'Head',
|
|
246
|
-
position: [0, 0.06, 0.11],
|
|
247
|
-
rotation: [0, 0, 0],
|
|
248
|
-
scale: 1.0,
|
|
249
|
-
},
|
|
250
|
-
{
|
|
251
|
-
id: 'backpack',
|
|
252
|
-
url: '/models/backpack.glb',
|
|
253
|
-
bone: 'Spine1',
|
|
254
|
-
position: [0, 0, -0.15],
|
|
255
|
-
rotation: [0, Math.PI, 0],
|
|
256
|
-
scale: 0.9,
|
|
257
|
-
},
|
|
258
|
-
]}
|
|
259
|
-
/>
|
|
254
|
+
const html = buildAvatarHtml({
|
|
255
|
+
avatarUrl: 'https://your-cdn/model.glb',
|
|
256
|
+
vendorBaseUrl: 'https://your-cdn/vendor', // serves three.module.js, talkinghead.mjs, etc.
|
|
257
|
+
// ...
|
|
258
|
+
});
|
|
260
259
|
```
|
|
261
260
|
|
|
262
|
-
|
|
261
|
+
`vendorBaseUrl` replaces every CDN reference; `dracoDecoderUrl` overrides the DRACO decoder
|
|
262
|
+
location independently.
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
```
|
|
267
|
-
Head, Neck, Spine, Spine1, Spine2,
|
|
268
|
-
LeftShoulder, LeftArm, LeftForeArm, LeftHand,
|
|
269
|
-
RightShoulder, RightArm, RightForeArm, RightHand,
|
|
270
|
-
LeftUpLeg, LeftLeg, LeftFoot,
|
|
271
|
-
RightUpLeg, RightLeg, RightFoot
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
Bone matching is flexible -- if an exact match is not found, the component tries a prefix match (useful for Sketchfab exports like `Head_5`). If no bone matches, the accessory falls back to the scene root.
|
|
264
|
+
---
|
|
275
265
|
|
|
276
|
-
|
|
266
|
+
## FaceControl — the lower-level contract
|
|
277
267
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
ref.current?.setAccessories([]);
|
|
268
|
+
If you're writing a custom backend or a game-engine integration, `FaceControl` is the
|
|
269
|
+
single value that flows between a voice pipeline and any avatar backend.
|
|
281
270
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
271
|
+
```ts
|
|
272
|
+
import type { FaceControl, ExpressionState, HeadPose, EyeGaze } from 'talking-head-studio';
|
|
273
|
+
|
|
274
|
+
type HeadPose = { yaw: number; pitch: number; roll: number }; // each -1..1
|
|
275
|
+
type EyeGaze = { x: number; y: number }; // each -1..1
|
|
276
|
+
|
|
277
|
+
type ExpressionState = {
|
|
278
|
+
jawOpen: number; mouthSmile: number; mouthFunnel: number; mouthPucker: number;
|
|
279
|
+
mouthWide: number; upperLipRaise: number; lowerLipDepress: number; cheekRaise: number;
|
|
280
|
+
blinkLeft: number; blinkRight: number; browInnerUp: number;
|
|
281
|
+
browDownLeft: number; browDownRight: number;
|
|
282
|
+
eyeGazeLeft: EyeGaze; eyeGazeRight: EyeGaze;
|
|
283
|
+
}; // all weights 0..1 unless noted
|
|
286
284
|
```
|
|
287
285
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
---
|
|
291
|
-
|
|
292
|
-
## Color Customization
|
|
286
|
+
Drive it from a viseme schedule:
|
|
293
287
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
The system matches material names against known keywords:
|
|
288
|
+
```ts
|
|
289
|
+
import { useFaceControlsFromVisemes } from 'talking-head-studio';
|
|
297
290
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
| Hair | `hair`, `fur` |
|
|
301
|
-
| Skin | `skin`, `body`, `face` |
|
|
302
|
-
| Eyes | `eye`, `iris` |
|
|
291
|
+
const faceControl = useFaceControlsFromVisemes(schedule); // rAF-sampled FaceControl
|
|
292
|
+
```
|
|
303
293
|
|
|
304
|
-
|
|
305
|
-
// Via props
|
|
306
|
-
<TalkingHead hairColor="#2d1b00" skinColor="#f0c8a0" eyeColor="#3d6b4f" />
|
|
294
|
+
Or implement a backend against it:
|
|
307
295
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
296
|
+
```ts
|
|
297
|
+
import type { AvatarBackend, AvatarRenderTarget, FaceControl } from 'talking-head-studio';
|
|
298
|
+
|
|
299
|
+
class MyBackend implements AvatarBackend {
|
|
300
|
+
initialize() {}
|
|
301
|
+
attach(target: AvatarRenderTarget) {}
|
|
302
|
+
setControl(control: FaceControl) {}
|
|
303
|
+
renderFrame() {}
|
|
304
|
+
dispose() {}
|
|
305
|
+
}
|
|
312
306
|
```
|
|
313
307
|
|
|
314
|
-
|
|
308
|
+
### MorphTargetBackend — the built-in Three.js adapter
|
|
315
309
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
## Voice Pipeline Integration
|
|
310
|
+
The concrete `AvatarBackend` for GLB-with-morphs. Hand it a loaded scene; it discovers
|
|
311
|
+
morph targets, builds a lookup cache, and drives them from `FaceControl`.
|
|
319
312
|
|
|
320
|
-
|
|
313
|
+
```ts
|
|
314
|
+
import { MorphTargetBackend, createNeutralExpression } from 'talking-head-studio';
|
|
315
|
+
|
|
316
|
+
const backend = new MorphTargetBackend(gltf.scene, {
|
|
317
|
+
mood: 'neutral',
|
|
318
|
+
expressionScale: 1.0,
|
|
319
|
+
calibration: {
|
|
320
|
+
neutral: { pose: { yaw: 0, pitch: 0, roll: 0 }, expr: createNeutralExpression() },
|
|
321
|
+
ranges: { jawOpen: { min: 0, max: 0.85 } }, // clamp jaw for this model
|
|
322
|
+
gazeLimits: { x: { min: -0.6, max: 0.6 } },
|
|
323
|
+
},
|
|
324
|
+
});
|
|
321
325
|
|
|
322
|
-
|
|
326
|
+
backend.setControl(faceControl);
|
|
327
|
+
backend.renderFrame();
|
|
328
|
+
console.log(backend.availableChannels); // what this model actually supports
|
|
329
|
+
```
|
|
323
330
|
|
|
324
|
-
|
|
331
|
+
### ARKit → Oculus remap (no ML, no artist work)
|
|
325
332
|
|
|
326
|
-
|
|
333
|
+
```ts
|
|
334
|
+
import { remapArkitToOculus, getArkitWeightsForViseme } from 'talking-head-studio';
|
|
327
335
|
|
|
328
|
-
|
|
336
|
+
remapArkitToOculus({ jawOpen: 0.7, mouthLowerDownLeft: 0.4 }); // → { aa: 0.68, oh: 0.12, ... }
|
|
337
|
+
getArkitWeightsForViseme('ou'); // → { mouthPucker: 0.9, ... }
|
|
338
|
+
```
|
|
329
339
|
|
|
330
|
-
|
|
340
|
+
The full `ARKIT_TO_OCULUS` coefficient table is exported for building your own bake pipeline.
|
|
331
341
|
|
|
332
|
-
|
|
333
|
-
import { useDataChannel } from '@livekit/components-react';
|
|
342
|
+
---
|
|
334
343
|
|
|
335
|
-
|
|
336
|
-
const ref = useRef<TalkingHeadRef>(null);
|
|
344
|
+
## Accessories
|
|
337
345
|
|
|
338
|
-
|
|
339
|
-
if (data.amplitude !== undefined) {
|
|
340
|
-
ref.current?.sendAmplitude(data.amplitude);
|
|
341
|
-
}
|
|
342
|
-
});
|
|
346
|
+
Any GLB attached to any skeleton bone, placeable at runtime.
|
|
343
347
|
|
|
344
|
-
|
|
348
|
+
```ts
|
|
349
|
+
interface TalkingHeadAccessory {
|
|
350
|
+
id: string;
|
|
351
|
+
url: string;
|
|
352
|
+
bone: string; // 'Head' | 'Spine' | 'RightHand' | ...
|
|
353
|
+
position: [number, number, number];
|
|
354
|
+
rotation: [number, number, number]; // Euler, radians
|
|
355
|
+
scale: number;
|
|
345
356
|
}
|
|
346
357
|
```
|
|
347
358
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
```tsx
|
|
351
|
-
const audioCtx = new AudioContext();
|
|
352
|
-
const analyser = audioCtx.createAnalyser();
|
|
353
|
-
const buf = new Uint8Array(analyser.frequencyBinCount);
|
|
354
|
-
|
|
355
|
-
// Connect your audio source to the analyser
|
|
356
|
-
source.connect(analyser);
|
|
357
|
-
|
|
358
|
-
// Poll amplitude and feed the avatar
|
|
359
|
-
const interval = setInterval(() => {
|
|
360
|
-
analyser.getByteFrequencyData(buf);
|
|
361
|
-
const amplitude = buf.reduce((a, b) => a + b, 0) / buf.length / 255;
|
|
362
|
-
ref.current?.sendAmplitude(amplitude);
|
|
363
|
-
}, 50);
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### Any audio source
|
|
367
|
-
|
|
368
|
-
The only contract is a number between 0 and 1, called at roughly 20 Hz. This works with ElevenLabs, OpenAI Realtime, Deepgram, Whisper, or any other TTS/STT pipeline.
|
|
359
|
+
Common Mixamo bones: `Head, Neck, Spine, Spine1, Spine2, LeftHand, RightHand, LeftFoot, RightFoot, Hips`.
|
|
360
|
+
The 3D editor (`talking-head-studio/editor`, web only) provides a gizmo for live placement.
|
|
369
361
|
|
|
370
362
|
---
|
|
371
363
|
|
|
372
|
-
##
|
|
373
|
-
|
|
374
|
-
### Rigged models (full feature set)
|
|
375
|
-
|
|
376
|
-
For the complete experience -- phoneme lip-sync, expressions, moods, gestures -- your GLB should have:
|
|
377
|
-
|
|
378
|
-
- A **Mixamo-compatible armature** (the component expects standard bone names)
|
|
379
|
-
- **ARKit blend shapes** and/or **Oculus viseme blend shapes** for lip-sync
|
|
380
|
-
- Standard Three.js-compatible GLB format
|
|
381
|
-
|
|
382
|
-
Models from [Avaturn](https://avaturn.me/) or any Mixamo-rigged source work out of the box.
|
|
383
|
-
|
|
384
|
-
### Non-rigged models (static fallback)
|
|
385
|
-
|
|
386
|
-
Any valid GLB loads successfully. Non-rigged models get:
|
|
364
|
+
## Subpath exports
|
|
387
365
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
-
|
|
391
|
-
|
|
392
|
-
-
|
|
393
|
-
-
|
|
366
|
+
| Import | Description |
|
|
367
|
+
|------|-------------|
|
|
368
|
+
| `talking-head-studio` | Avatar component + `FaceControl` contracts + motion constants |
|
|
369
|
+
| `talking-head-studio/contract` | Stable type-only entrypoint — visemes, FaceControl, backends, motion |
|
|
370
|
+
| `talking-head-studio/html` | `buildAvatarHtml()` for self-hosted / custom iframe embedding |
|
|
371
|
+
| `talking-head-studio/wgpu` | React Native WebGPU renderer (`WgpuAvatar`) |
|
|
372
|
+
| `talking-head-studio/editor` | R3F 3D editor with placement gizmo (web only) |
|
|
373
|
+
| `talking-head-studio/appearance` | Material color system for any GLB |
|
|
374
|
+
| `talking-head-studio/voice` | Audio recording + WAV conversion hooks |
|
|
375
|
+
| `talking-head-studio/sketchfab` | Sketchfab search + download hooks |
|
|
376
|
+
| `talking-head-studio/api` | Studio API client (avatar CRUD, voice profiles) |
|
|
377
|
+
| `talking-head-studio/wardrobe` | Accessory + outfit state management |
|
|
394
378
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
For detailed model authoring guidance, see the [TalkingHead documentation](https://github.com/met4citizen/TalkingHead).
|
|
379
|
+
Workspace packages (`packages/avatar-creator`, `packages/agent-avatar`) ship an embeddable
|
|
380
|
+
creator widget and a LiveKit + MCP agent integration.
|
|
398
381
|
|
|
399
382
|
---
|
|
400
383
|
|
|
401
|
-
##
|
|
402
|
-
|
|
403
|
-
This works on the web without `react-native` or `react-native-webview` installed at runtime.
|
|
404
|
-
|
|
405
|
-
On web, the component renders an `<iframe>` with `srcdoc` containing the full Three.js scene. No WebView, no native modules, no build plugins.
|
|
406
|
-
|
|
407
|
-
```tsx
|
|
408
|
-
// Works in any React 18+ web app
|
|
409
|
-
import { TalkingHead } from 'talking-head-studio';
|
|
410
|
-
|
|
411
|
-
export default function Page() {
|
|
412
|
-
return (
|
|
413
|
-
<TalkingHead
|
|
414
|
-
avatarUrl="/models/avatar.glb"
|
|
415
|
-
mood="happy"
|
|
416
|
-
style={{ width: 600, height: 800 }}
|
|
417
|
-
/>
|
|
418
|
-
);
|
|
419
|
-
}
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
Metro and Expo use the native entry backed by `react-native-webview`. Standard web bundlers use the browser entry backed by a plain `<iframe>`. The API is identical.
|
|
384
|
+
## Roadmap
|
|
423
385
|
|
|
424
|
-
|
|
386
|
+
> **Status legend:** ✅ shipped · 🔜 in progress · 🧪 designed, not yet built
|
|
425
387
|
|
|
426
|
-
|
|
388
|
+
**Shipped today**
|
|
389
|
+
- ✅ `FaceControl` face-control space (pose + expression + gaze) and `AvatarBackend` interface
|
|
390
|
+
- ✅ `MorphTargetBackend` — GLB morph discovery + mood layering
|
|
391
|
+
- ✅ ARKit → Oculus analytical remap with full coefficient table
|
|
392
|
+
- ✅ `AgentVisemePayload` viseme schedule format + `scheduleVisemes` on both renderers
|
|
393
|
+
- ✅ Shared procedural motion engine (web + native WGPU), gestures, poses, animation clips
|
|
394
|
+
- ✅ Self-hosting via `buildAvatarHtml({ vendorBaseUrl })`
|
|
395
|
+
- ✅ `packages/avatar-creator`, `packages/agent-avatar`
|
|
427
396
|
|
|
428
|
-
|
|
397
|
+
**In progress**
|
|
398
|
+
- 🔜 Native (WGPU) gaze bridge (`lookAt`) and mic-reactive listening
|
|
399
|
+
- 🔜 GLB schema walker — report morph coverage, bones, LODs, viseme tier for any model
|
|
429
400
|
|
|
430
|
-
|
|
401
|
+
**Designed, not yet built**
|
|
402
|
+
- 🧪 `GaussianBackend` — Gaussian-splat renderer + FLAME per-viseme delta transfer, so a
|
|
403
|
+
model with *no* face rig still gets excellent lip-sync. This is the zero-prerequisite path.
|
|
404
|
+
- 🧪 FLAME viseme transfer pipeline (companion backend) — bake Oculus visemes into a GLB
|
|
405
|
+
that lacks them
|
|
406
|
+
- 🧪 Unity / Unreal SDKs implementing the same `AvatarBackend` contract
|
|
407
|
+
- 🧪 Avatar marketplace + RPM import tooling (`CatalogItem` / `AvatarAsset` types exist;
|
|
408
|
+
backend and store do not)
|
|
431
409
|
|
|
432
410
|
---
|
|
433
411
|
|
|
434
412
|
## Contributing
|
|
435
413
|
|
|
436
|
-
Contributions are welcome. Please open an issue to discuss your idea before submitting a pull request.
|
|
437
|
-
|
|
438
414
|
```bash
|
|
439
415
|
git clone https://github.com/sitebay/talking-head-studio.git
|
|
440
416
|
cd talking-head-studio
|
|
441
417
|
npm install
|
|
442
|
-
npm run typecheck
|
|
418
|
+
npm run typecheck # must be clean
|
|
443
419
|
npm test
|
|
444
420
|
```
|
|
445
421
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
## Credits
|
|
449
|
-
|
|
450
|
-
This project builds on excellent open-source work:
|
|
451
|
-
|
|
452
|
-
- [met4citizen/TalkingHead](https://github.com/met4citizen/TalkingHead) -- The 3D avatar engine powering model loading, rigging, and expression systems.
|
|
453
|
-
- [met4citizen/HeadAudio](https://github.com/met4citizen/HeadAudio) -- Phoneme-based lip-sync from audio streams using AudioWorklet.
|
|
454
|
-
- [lhupyn/motion-engine](https://github.com/lhupyn/motion-engine) -- Real-time body motion tracking (upcoming integration).
|
|
455
|
-
- [Three.js](https://threejs.org/) -- 3D rendering, loaded via CDN at runtime.
|
|
456
|
-
|
|
457
|
-
---
|
|
458
|
-
|
|
459
|
-
## License
|
|
460
|
-
|
|
461
|
-
MIT
|
|
462
|
-
at runtime.
|
|
422
|
+
Monorepo with `packages/*` as npm workspaces; the main library is the root package. The
|
|
423
|
+
publish gate (`prepublishOnly`) runs lint, typecheck, tests, and metadata checks.
|
|
463
424
|
|
|
464
425
|
---
|
|
465
426
|
|
|
466
|
-
##
|
|
427
|
+
## Credits & license
|
|
467
428
|
|
|
468
|
-
|
|
429
|
+
Built on [met4citizen/TalkingHead](https://github.com/met4citizen/TalkingHead) (rig +
|
|
430
|
+
gestures/poses on the web path) and [Three.js](https://threejs.org). MIT licensed.
|