@voqalize/avatar 0.2.1 → 0.3.0
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/LICENSE +21 -661
- package/README.md +144 -663
- package/client/Avatar.tsx +57 -0
- package/client/AvatarClient.ts +523 -0
- package/client/arjun.ts +24 -0
- package/client/createAvatar.ts +139 -0
- package/client/createCanvasAvatar.ts +72 -0
- package/client/index.ts +39 -0
- package/client/internal.ts +80 -0
- package/client/interviewer-female.ts +4 -0
- package/client/interviewer-male.ts +4 -0
- package/client/ishita.ts +24 -0
- package/client/kabir.ts +24 -0
- package/client/meera.ts +24 -0
- package/client/naina.ts +24 -0
- package/client/professional-female-a.ts +4 -0
- package/client/professional-female-b.ts +4 -0
- package/client/professional-male-a.ts +4 -0
- package/client/professional-male-b.ts +4 -0
- package/client/react.ts +13 -0
- package/client/types.ts +146 -0
- package/client/useAvatar.ts +74 -0
- package/client/vikram.ts +24 -0
- package/dist/Avatar.d.ts +44 -0
- package/dist/Avatar.d.ts.map +1 -0
- package/{client/dist → dist}/Avatar.js +2 -2
- package/dist/Avatar.js.map +1 -0
- package/dist/AvatarClient.d.ts +196 -0
- package/dist/AvatarClient.d.ts.map +1 -0
- package/dist/AvatarClient.js +450 -0
- package/dist/AvatarClient.js.map +1 -0
- package/dist/arjun.d.ts +6 -0
- package/dist/arjun.d.ts.map +1 -0
- package/dist/arjun.js +18 -0
- package/dist/arjun.js.map +1 -0
- package/dist/createAvatar.d.ts +99 -0
- package/dist/createAvatar.d.ts.map +1 -0
- package/dist/createAvatar.js +75 -0
- package/dist/createAvatar.js.map +1 -0
- package/dist/createCanvasAvatar.d.ts +22 -0
- package/dist/createCanvasAvatar.d.ts.map +1 -0
- package/dist/createCanvasAvatar.js +47 -0
- package/dist/createCanvasAvatar.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +30 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +33 -0
- package/dist/internal.js.map +1 -0
- package/dist/interviewer-female.d.ts +4 -0
- package/dist/interviewer-female.d.ts.map +1 -0
- package/dist/interviewer-female.js +3 -0
- package/dist/interviewer-female.js.map +1 -0
- package/dist/interviewer-male.d.ts +4 -0
- package/dist/interviewer-male.d.ts.map +1 -0
- package/dist/interviewer-male.js +3 -0
- package/dist/interviewer-male.js.map +1 -0
- package/dist/ishita.d.ts +6 -0
- package/dist/ishita.d.ts.map +1 -0
- package/dist/ishita.js +18 -0
- package/dist/ishita.js.map +1 -0
- package/dist/kabir.d.ts +6 -0
- package/dist/kabir.d.ts.map +1 -0
- package/dist/kabir.js +18 -0
- package/dist/kabir.js.map +1 -0
- package/dist/meera.d.ts +6 -0
- package/dist/meera.d.ts.map +1 -0
- package/dist/meera.js +18 -0
- package/dist/meera.js.map +1 -0
- package/dist/naina.d.ts +6 -0
- package/dist/naina.d.ts.map +1 -0
- package/dist/naina.js +18 -0
- package/dist/naina.js.map +1 -0
- package/dist/professional-female-a.d.ts +4 -0
- package/dist/professional-female-a.d.ts.map +1 -0
- package/dist/professional-female-a.js +3 -0
- package/dist/professional-female-a.js.map +1 -0
- package/dist/professional-female-b.d.ts +4 -0
- package/dist/professional-female-b.d.ts.map +1 -0
- package/dist/professional-female-b.js +3 -0
- package/dist/professional-female-b.js.map +1 -0
- package/dist/professional-male-a.d.ts +4 -0
- package/dist/professional-male-a.d.ts.map +1 -0
- package/dist/professional-male-a.js +3 -0
- package/dist/professional-male-a.js.map +1 -0
- package/dist/professional-male-b.d.ts +4 -0
- package/dist/professional-male-b.d.ts.map +1 -0
- package/dist/professional-male-b.js +3 -0
- package/dist/professional-male-b.js.map +1 -0
- package/dist/react.d.ts +13 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +13 -0
- package/dist/react.js.map +1 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +93 -0
- package/dist/types.js.map +1 -0
- package/dist/useAvatar.d.ts +39 -0
- package/dist/useAvatar.d.ts.map +1 -0
- package/dist/useAvatar.js +35 -0
- package/dist/useAvatar.js.map +1 -0
- package/dist/vikram.d.ts +6 -0
- package/dist/vikram.d.ts.map +1 -0
- package/dist/vikram.js +18 -0
- package/dist/vikram.js.map +1 -0
- package/package.json +81 -24
- package/src/avatar.d.ts +161 -120
- package/src/avatar.js +234 -146
- package/src/behavior.d.ts +26 -0
- package/src/behavior.js +102 -0
- package/src/camera.js +29 -0
- package/src/canvas/author/parts/eye.mjs +722 -0
- package/src/canvas/author/parts/hand.mjs +1156 -0
- package/src/canvas/author/parts/mouth.mjs +741 -0
- package/src/canvas/author/parts/nose.mjs +100 -0
- package/src/canvas/author/parts/skin-detail.mjs +67 -0
- package/src/canvas/author/path.mjs +283 -0
- package/src/canvas/author/rig.mjs +405 -0
- package/src/canvas/avatars/round/face.d.mts +3 -0
- package/src/canvas/avatars/round/face.mjs +1307 -0
- package/src/canvas/create-rig.d.ts +15 -0
- package/src/canvas/create-rig.js +100 -0
- package/src/canvas/data/img/professional-female-a-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-female-a-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-female-a-top-body.webp +0 -0
- package/src/canvas/data/img/professional-female-b-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-female-b-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-female-b-top-body.webp +0 -0
- package/src/canvas/data/img/professional-male-a-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-male-a-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-male-a-top-body.webp +0 -0
- package/src/canvas/data/img/professional-male-b-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-male-b-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-male-b-top-body.webp +0 -0
- package/src/canvas/data/img/round-m3-hair-back.webp +0 -0
- package/src/canvas/data/img/round-m3-hair-front.webp +0 -0
- package/src/canvas/data/img/round-m3-top-body.webp +0 -0
- package/src/canvas/data/img/round-w1-hair-back.webp +0 -0
- package/src/canvas/data/img/round-w1-hair-front.webp +0 -0
- package/src/canvas/data/img/round-w1-top-body.webp +0 -0
- package/src/canvas/data/interviewer-female.rig.json +1 -0
- package/src/canvas/data/interviewer-male.rig.json +1 -0
- package/src/canvas/data/professional-female-a.rig.json +1 -0
- package/src/canvas/data/professional-female-b.rig.json +1 -0
- package/src/canvas/data/professional-male-a.rig.json +1 -0
- package/src/canvas/data/professional-male-b.rig.json +1 -0
- package/src/canvas/src/live.js +508 -0
- package/src/canvas/src/render2d.js +218 -0
- package/src/canvas/src/rig.js +297 -0
- package/src/canvas/src/vocab.js +96 -0
- package/src/clips.js +18 -7
- package/src/conformance.js +119 -0
- package/src/emotions.js +4 -3
- package/src/face-core.js +27 -1
- package/src/face-myna.d.ts +7 -0
- package/src/face-myna.js +192 -131
- package/src/face-peep-control-plane.js +167 -0
- package/src/face-peep.d.ts +7 -0
- package/src/face-peep.js +274 -116
- package/src/face-wren.d.ts +7 -0
- package/src/face-wren.js +17 -19
- package/src/faces.d.ts +12 -0
- package/src/faces.js +53 -0
- package/src/gaze.js +119 -4
- package/src/hand.js +120 -91
- package/src/idle.js +18 -101
- package/src/interjections.js +156 -31
- package/src/params.js +8 -4
- package/src/perform.js +7 -9
- package/src/rig.d.ts +24 -0
- package/src/rig.js +32 -0
- package/src/speech-timing.js +23 -0
- package/src/visemes.js +28 -12
- package/client/dist/Avatar.d.ts +0 -27
- package/client/dist/Avatar.d.ts.map +0 -1
- package/client/dist/Avatar.js.map +0 -1
- package/client/dist/AvatarClient.d.ts +0 -139
- package/client/dist/AvatarClient.d.ts.map +0 -1
- package/client/dist/AvatarClient.js +0 -223
- package/client/dist/AvatarClient.js.map +0 -1
- package/client/dist/index.d.ts +0 -22
- package/client/dist/index.d.ts.map +0 -1
- package/client/dist/index.js +0 -22
- package/client/dist/index.js.map +0 -1
- package/client/dist/types.d.ts +0 -86
- package/client/dist/types.d.ts.map +0 -1
- package/client/dist/types.js +0 -31
- package/client/dist/types.js.map +0 -1
- package/client/dist/useAvatar.d.ts +0 -42
- package/client/dist/useAvatar.d.ts.map +0 -1
- package/client/dist/useAvatar.js +0 -51
- package/client/dist/useAvatar.js.map +0 -1
- package/client/src/Avatar.tsx +0 -33
- package/client/src/AvatarClient.ts +0 -270
- package/client/src/index.ts +0 -22
- package/client/src/types.ts +0 -104
- package/client/src/useAvatar.ts +0 -85
- package/docs/contract-avatar.md +0 -371
- package/docs/contract-protocol.md +0 -461
package/src/avatar.d.ts
CHANGED
|
@@ -3,28 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The widget is dependency-free ES modules with no build step, so there is no
|
|
5
5
|
* compiler to derive these from; this file is written by hand against
|
|
6
|
-
* `docs/
|
|
6
|
+
* `docs/internal-mixer.md` (the mixer's driving API) and a
|
|
7
7
|
* reading of `avatar.js`. It lives here rather than in a consumer because it
|
|
8
8
|
* is only correct next to the code it describes — the previous copy lived in
|
|
9
9
|
* a vendored tree two repos away and went stale the first time an enum grew.
|
|
10
10
|
*
|
|
11
|
-
* It is deliberately not a conversion of the widget to TypeScript.
|
|
12
|
-
* enums (state / gaze / emotion /
|
|
13
|
-
*
|
|
14
|
-
* widget enforces
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* It is deliberately not a conversion of the widget to TypeScript. The
|
|
12
|
+
* string-keyed enums (state / gaze / emotion / semantic action ids) are closed
|
|
13
|
+
* literal unions, and there is no `| string` escape hatch on any setter: the
|
|
14
|
+
* widget enforces the same enums at runtime — unknown state and action ids
|
|
15
|
+
* throw, unknown emotion and gaze fall back silently — so a caller who wants
|
|
16
|
+
* one of these has a name that is in the union or a bug. Keeping the two in
|
|
17
|
+
* step is this file's whole job; a stale `.d.ts` must never claim to be
|
|
18
|
+
* stricter than the code it describes, so widen the runtime first.
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
|
-
/** `STATE_NAMES` — see docs/
|
|
21
|
+
/** `STATE_NAMES` — see docs/internal-mixer.md § States. */
|
|
20
22
|
export type AvatarStateName =
|
|
21
23
|
| "IDLE"
|
|
22
24
|
| "LISTENING"
|
|
23
25
|
| "THINKING"
|
|
24
26
|
| "SPEAKING"
|
|
27
|
+
| "WORKING"
|
|
25
28
|
| "REVIEWING_SCREEN"
|
|
26
29
|
| "WAITING_FOR_USER"
|
|
27
|
-
| "TYPING"
|
|
28
30
|
| "TYPING_CHAT"
|
|
29
31
|
| "DISTRACTED"
|
|
30
32
|
| "SEARCHING_SCREEN"
|
|
@@ -35,7 +37,7 @@ export type AvatarStateName =
|
|
|
35
37
|
| "DEGRADED"
|
|
36
38
|
| "OFFLINE";
|
|
37
39
|
|
|
38
|
-
/** `EMOTION_NAMES` — see docs/
|
|
40
|
+
/** `EMOTION_NAMES` — see docs/internal-mixer.md § Emotion. */
|
|
39
41
|
export type AvatarEmotionName =
|
|
40
42
|
| "neutral"
|
|
41
43
|
| "warm"
|
|
@@ -44,7 +46,7 @@ export type AvatarEmotionName =
|
|
|
44
46
|
| "encouraging"
|
|
45
47
|
| "thoughtful";
|
|
46
48
|
|
|
47
|
-
/** `GAZE_NAMES` — see docs/
|
|
49
|
+
/** `GAZE_NAMES` — see docs/internal-mixer.md § Gaze. `"CUSTOM"` is the escape hatch (any name + a `custom` point works). */
|
|
48
50
|
export type AvatarGazeName =
|
|
49
51
|
| "USER"
|
|
50
52
|
| "USER_EAR"
|
|
@@ -60,71 +62,80 @@ export type AvatarGazeName =
|
|
|
60
62
|
| "AWAY_DOWN"
|
|
61
63
|
| "CUSTOM";
|
|
62
64
|
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
| "
|
|
67
|
-
| "
|
|
68
|
-
| "
|
|
69
|
-
| "
|
|
70
|
-
| "
|
|
71
|
-
| "
|
|
72
|
-
| "
|
|
73
|
-
| "YIELD_FLOOR"
|
|
74
|
-
| "RAISE_HAND"
|
|
75
|
-
| "WAVE"
|
|
76
|
-
| "THUMBS_UP"
|
|
77
|
-
| "SHRUG"
|
|
78
|
-
| "GO_ON_ARM"
|
|
79
|
-
| "MM_HMM"
|
|
80
|
-
| "OKAY"
|
|
81
|
-
| "YES"
|
|
82
|
-
| "SURE"
|
|
83
|
-
| "I_SEE"
|
|
84
|
-
| "RIGHT"
|
|
85
|
-
| "GO_ON"
|
|
86
|
-
| "ONE_MOMENT"
|
|
87
|
-
| "SORRY"
|
|
88
|
-
| "HMM"
|
|
89
|
-
| "GOT_IT"
|
|
90
|
-
| "TAKE_YOUR_TIME";
|
|
65
|
+
/** The complete server-addressable action contract. Names are semantic, not
|
|
66
|
+
* anatomical: a future rig may implement `ACK_NOD` without a literal nod. */
|
|
67
|
+
export type AvatarActionId =
|
|
68
|
+
| "ACK_RECEIVE"
|
|
69
|
+
| "ACK_NOD"
|
|
70
|
+
| "RESPONSE_INTERRUPTED"
|
|
71
|
+
| "GESTURE_GREET"
|
|
72
|
+
| "GESTURE_GOODBYE"
|
|
73
|
+
| "GESTURE_APPROVE"
|
|
74
|
+
| "GESTURE_WAIT";
|
|
91
75
|
|
|
92
|
-
/**
|
|
93
|
-
* the frame edge plus the face half that makes it belong to somebody. Disjoint
|
|
94
|
-
* from the interjection ids on purpose: `interject("WAVE")` is still the face
|
|
95
|
-
* alone. */
|
|
96
|
-
export type AvatarHandGestureId = "HI" | "BYE" | "THUMBS_UP" | "ONE_MOMENT";
|
|
97
|
-
|
|
98
|
-
/** Rhubarb Lip Sync letter — see docs/contract-protocol.md § Speech. */
|
|
76
|
+
/** Rhubarb Lip Sync letter — see docs/internal-mixer.md § Speech. */
|
|
99
77
|
export type VisemeLetter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "X";
|
|
100
78
|
|
|
101
79
|
/** One viseme cue: `t` is a ms offset into the utterance, `i` is optional 0..1 loudness. */
|
|
102
80
|
export interface Cue {
|
|
103
81
|
t: number;
|
|
104
|
-
v:
|
|
82
|
+
v: VisemeLetter;
|
|
83
|
+
/** 0..1. Absent means full. */
|
|
105
84
|
i?: number;
|
|
106
85
|
}
|
|
107
86
|
|
|
87
|
+
/** A pose channel from `src/params.js` — see docs/internal-rig.md § The pose channels. */
|
|
88
|
+
export type PoseChannel =
|
|
89
|
+
| "mouthOpen" | "mouthWidth" | "mouthRound" | "mouthPress" | "mouthTuck"
|
|
90
|
+
| "mouthCornerL" | "mouthCornerR" | "teethUpper" | "tongue" | "jaw"
|
|
91
|
+
| "lidL" | "lidR" | "squintL" | "squintR" | "pupilX" | "pupilY"
|
|
92
|
+
| "browRaiseL" | "browRaiseR" | "browAngleL" | "browAngleR"
|
|
93
|
+
| "browInnerL" | "browInnerR"
|
|
94
|
+
| "headYaw" | "headPitch" | "headRoll"
|
|
95
|
+
| "breath" | "shoulderL" | "shoulderR" | "torsoLean" | "torsoTurn";
|
|
96
|
+
|
|
97
|
+
/** A partial pose. Channels are clamped to `RANGE`; absent channels keep the mix. */
|
|
98
|
+
export type PoseOverrides = Partial<Record<PoseChannel, number>>;
|
|
99
|
+
|
|
100
|
+
/** Which hand the character gestures with: `1` the viewer's right (the
|
|
101
|
+
* character's own left), `-1` the other. */
|
|
102
|
+
export type HandSide = 1 | -1;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* A motion scale. `1` is the amplitude the channel was authored at, `0` stills
|
|
106
|
+
* it, and `2` is the documented ceiling — past that the smoothing constants
|
|
107
|
+
* stop holding and clips overshoot their range clamp.
|
|
108
|
+
*
|
|
109
|
+
* TypeScript cannot say "0..2", so this is a named `number` and the check is at
|
|
110
|
+
* the door: the public `createAvatar` throws `RangeError`. The live setters
|
|
111
|
+
* below do not, because they exist for review sliders that already bound
|
|
112
|
+
* themselves and should not throw mid-drag.
|
|
113
|
+
*/
|
|
114
|
+
export type Gain = number;
|
|
115
|
+
|
|
108
116
|
/** Normalized-screen-coordinate escape hatch for `setGaze('CUSTOM', custom)`. */
|
|
109
117
|
export interface GazeCustom {
|
|
110
118
|
x: number;
|
|
111
119
|
y: number;
|
|
112
120
|
}
|
|
113
121
|
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
keepGaze?: boolean
|
|
122
|
-
}
|
|
122
|
+
/**
|
|
123
|
+
* A `perform()` timeline action — see docs/internal-mixer.md § Composing
|
|
124
|
+
* behavior. `t` is a ms offset into the performance. Discriminated on `do`
|
|
125
|
+
* because the verbs do not share their payload: only `action` is addressed by
|
|
126
|
+
* `id`, and only `state` reads `keepGaze`.
|
|
127
|
+
*/
|
|
128
|
+
export type AvatarAction =
|
|
129
|
+
| { t: number; do: "state"; name: AvatarStateName; keepGaze?: boolean }
|
|
130
|
+
| { t: number; do: "emotion"; name: AvatarEmotionName; i?: number }
|
|
131
|
+
| { t: number; do: "gaze"; name: AvatarGazeName }
|
|
132
|
+
| { t: number; do: "action"; id: AvatarActionId };
|
|
123
133
|
|
|
124
134
|
export interface SetStateOptions {
|
|
125
|
-
emotion?: AvatarEmotionName
|
|
135
|
+
emotion?: AvatarEmotionName;
|
|
136
|
+
/** 0..1 emotion strength. Default 1. */
|
|
126
137
|
intensity?: number;
|
|
127
|
-
gaze?: AvatarGazeName
|
|
138
|
+
gaze?: AvatarGazeName;
|
|
128
139
|
keepGaze?: boolean;
|
|
129
140
|
}
|
|
130
141
|
|
|
@@ -141,12 +152,11 @@ export interface PerformOptions {
|
|
|
141
152
|
}
|
|
142
153
|
|
|
143
154
|
export interface PerformHandle {
|
|
144
|
-
/** Cancels the *future* of this performance only — see docs/
|
|
155
|
+
/** Cancels the *future* of this performance only — see docs/internal-mixer.md. */
|
|
145
156
|
stop: () => void;
|
|
146
157
|
}
|
|
147
158
|
|
|
148
|
-
export type AvatarEventName =
|
|
149
|
-
| "state" | "speakEnd" | "clipEnd" | "backchannel" | "performEnd" | "gestureEnd";
|
|
159
|
+
export type AvatarEventName = "state" | "speakEnd" | "clipEnd" | "performEnd" | "gestureEnd";
|
|
150
160
|
|
|
151
161
|
/** What a host needs to frame an avatar it has never seen: the drawing's own
|
|
152
162
|
* window, and where the mouth is inside it. See CLAUDE.md § The two
|
|
@@ -158,114 +168,145 @@ export interface AvatarMeta {
|
|
|
158
168
|
|
|
159
169
|
/** The object `createAvatar()` returns — the whole server-facing surface. */
|
|
160
170
|
export interface AvatarApi {
|
|
161
|
-
setState(name: AvatarStateName
|
|
162
|
-
setEmotion(name: AvatarEmotionName
|
|
163
|
-
setGaze(name: AvatarGazeName
|
|
171
|
+
setState(name: AvatarStateName, o?: SetStateOptions): AvatarApi;
|
|
172
|
+
setEmotion(name: AvatarEmotionName, intensity?: number): AvatarApi;
|
|
173
|
+
setGaze(name: AvatarGazeName, custom?: GazeCustom): AvatarApi;
|
|
164
174
|
speak(o?: SpeakOptions): AvatarApi;
|
|
165
175
|
pushCues(cues: Cue[]): AvatarApi;
|
|
166
176
|
stopSpeaking(): AvatarApi;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
setHandSide(dir: number): AvatarApi;
|
|
177
|
+
/** Self-completing action: states resolve underneath while motion lands. */
|
|
178
|
+
action(id: AvatarActionId): AvatarApi;
|
|
179
|
+
setHandSide(dir: HandSide): AvatarApi;
|
|
171
180
|
perform(actions: AvatarAction[], o?: PerformOptions): PerformHandle;
|
|
172
181
|
setUserSpeaking(speaking: boolean | null): AvatarApi;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
readonly
|
|
177
|
-
|
|
178
|
-
readonly
|
|
182
|
+
/** Hold user gaze for a short interaction window; currently JS-level only. */
|
|
183
|
+
attend(ms?: number): AvatarApi;
|
|
184
|
+
setMouthGain(g: Gain): AvatarApi;
|
|
185
|
+
readonly mouthGain: Gain;
|
|
186
|
+
setGestureGain(g: Gain): AvatarApi;
|
|
187
|
+
readonly gestureGain: Gain;
|
|
188
|
+
setMotionGain(g: Gain): AvatarApi;
|
|
189
|
+
readonly motionGain: Gain;
|
|
179
190
|
blink(double?: boolean): AvatarApi;
|
|
180
191
|
/** Advance one frame by hand — only meaningful under `{ manual: true }`. */
|
|
181
192
|
step(dt: number): AvatarApi;
|
|
182
|
-
|
|
193
|
+
/** Pin channels to fixed values, above the whole mix. `null` releases. */
|
|
194
|
+
setOverrides(o: PoseOverrides | null): AvatarApi;
|
|
183
195
|
on(event: "state", fn: (name: AvatarStateName) => void): AvatarApi;
|
|
184
196
|
on(event: "speakEnd" | "performEnd", fn: () => void): AvatarApi;
|
|
185
|
-
on(event: "clipEnd" | "
|
|
197
|
+
on(event: "clipEnd" | "gestureEnd", fn: (id: string) => void): AvatarApi;
|
|
186
198
|
on(event: AvatarEventName, fn: (...args: unknown[]) => void): AvatarApi;
|
|
187
199
|
readonly state: AvatarStateName;
|
|
188
200
|
readonly emotion: AvatarEmotionName;
|
|
189
201
|
readonly gaze: AvatarGazeName;
|
|
190
202
|
readonly speaking: boolean;
|
|
191
203
|
readonly performing: boolean;
|
|
204
|
+
/** Internal clip id in flight. Not a contract — the clip catalog is private
|
|
205
|
+
* to this renderer, which is why this is the one open string here. */
|
|
192
206
|
readonly clip: string | null;
|
|
193
|
-
/**
|
|
194
|
-
readonly gesturing:
|
|
195
|
-
readonly params: Record<
|
|
207
|
+
/** Semantic hand action in flight, including for a non-SVG custom rig. */
|
|
208
|
+
readonly gesturing: AvatarActionId | null;
|
|
209
|
+
readonly params: Readonly<Record<PoseChannel, number>>;
|
|
196
210
|
readonly userSpeaking: boolean;
|
|
197
|
-
|
|
198
|
-
readonly
|
|
211
|
+
/** Legacy SVG inspection fields; null for a renderer-neutral AvatarRig. */
|
|
212
|
+
readonly svg: SVGSVGElement | null;
|
|
213
|
+
readonly meta: AvatarMeta | null;
|
|
199
214
|
destroy(): void;
|
|
200
215
|
}
|
|
201
216
|
|
|
202
217
|
/** A face module's factory — `createFace(mount, theme)`. See
|
|
203
|
-
* docs/
|
|
218
|
+
* docs/authoring-a-face.md § Adding a new avatar. */
|
|
204
219
|
export type FaceFactory = (
|
|
205
220
|
mount: Element,
|
|
206
|
-
theme?:
|
|
221
|
+
theme?: FaceTheme,
|
|
207
222
|
) => {
|
|
208
223
|
svg: SVGSVGElement;
|
|
209
|
-
apply: (params: Record<
|
|
210
|
-
theme:
|
|
224
|
+
apply: (params: Readonly<Record<PoseChannel, number>>) => void;
|
|
225
|
+
theme: FaceTheme;
|
|
211
226
|
destroy: () => void;
|
|
212
227
|
};
|
|
213
228
|
|
|
229
|
+
/** A face's palette: CSS colour strings by role. Keys are the face's own
|
|
230
|
+
* (`THEME` in its module) — a shared key set was tried and each drawing wanted
|
|
231
|
+
* different roles. See CLAUDE.md on why `peep` has no second palette. */
|
|
232
|
+
export type FaceTheme = Readonly<Record<string, string>>;
|
|
233
|
+
|
|
234
|
+
/** One drawn face: what to build, and how to frame it. Import one from
|
|
235
|
+
* `@voqalize/avatar/faces/<name>`; nothing resolves a face by string. */
|
|
236
|
+
export interface Face {
|
|
237
|
+
readonly create: FaceFactory;
|
|
238
|
+
readonly meta: AvatarMeta;
|
|
239
|
+
}
|
|
240
|
+
|
|
214
241
|
export interface CreateAvatarOptions {
|
|
215
242
|
/** Element, or CSS selector resolved via `document.querySelector`. */
|
|
216
243
|
mount: string | Element;
|
|
217
|
-
/**
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
*
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
244
|
+
/** The face to wear. Required unless `rig` replaces the renderer outright. */
|
|
245
|
+
face?: Face;
|
|
246
|
+
/** Renderer-neutral rig factory. It replaces the SVG face implementation;
|
|
247
|
+
* no SVG or metadata is required. See docs/internal-rig.md. */
|
|
248
|
+
rig?: import("./rig.js").AvatarRigFactory;
|
|
249
|
+
/** Passed to `rig` verbatim. Opaque here on purpose — it belongs to whoever
|
|
250
|
+
* wrote the rig, and this file has no way to know its shape. */
|
|
251
|
+
rigOptions?: unknown;
|
|
252
|
+
theme?: FaceTheme;
|
|
253
|
+
mouthGain?: Gain;
|
|
254
|
+
gestureGain?: Gain;
|
|
255
|
+
motionGain?: Gain;
|
|
256
|
+
/** Disable only the bundled SVG hand renderer. A custom rig still receives
|
|
257
|
+
* first-class `frame.hand` controls for every gesture action. Default true. */
|
|
229
258
|
hand?: boolean;
|
|
230
|
-
|
|
231
|
-
handSide?: number;
|
|
259
|
+
handSide?: HandSide;
|
|
232
260
|
/** Withhold the rAF loop so a tool can drive frames itself via `step(dt)`. */
|
|
233
261
|
manual?: boolean;
|
|
234
262
|
}
|
|
235
263
|
|
|
236
264
|
export function createAvatar(opts: CreateAvatarOptions): AvatarApi;
|
|
237
265
|
|
|
238
|
-
/** The registry: `{ create, meta }` per avatar. */
|
|
239
|
-
export const AVATARS: Record<string, { create: FaceFactory; meta: AvatarMeta }>;
|
|
240
|
-
export const AVATAR_NAMES: string[];
|
|
241
|
-
export const DEFAULT_AVATAR: string;
|
|
242
|
-
|
|
243
266
|
/** Per-state base pose + idle profile. Read-only in practice — the mixer owns it. */
|
|
244
|
-
export const STATES: Record<
|
|
245
|
-
export const STATE_NAMES: AvatarStateName[];
|
|
246
|
-
export const GAZE_NAMES: AvatarGazeName[];
|
|
247
|
-
export const GAZE_TARGETS: Record<
|
|
248
|
-
export const EMOTION_NAMES: AvatarEmotionName[];
|
|
249
|
-
export const
|
|
250
|
-
export const
|
|
251
|
-
export const HAND_GESTURES: Record<string, Record<string, unknown>>;
|
|
252
|
-
export const HAND_GESTURE_IDS: AvatarHandGestureId[];
|
|
267
|
+
export const STATES: Readonly<Record<AvatarStateName, Readonly<Record<string, unknown>>>>;
|
|
268
|
+
export const STATE_NAMES: readonly AvatarStateName[];
|
|
269
|
+
export const GAZE_NAMES: readonly AvatarGazeName[];
|
|
270
|
+
export const GAZE_TARGETS: Readonly<Record<AvatarGazeName, { x: number; y: number }>>;
|
|
271
|
+
export const EMOTION_NAMES: readonly AvatarEmotionName[];
|
|
272
|
+
export const ACTIONS: Readonly<Record<AvatarActionId, unknown>>;
|
|
273
|
+
export const ACTION_IDS: readonly AvatarActionId[];
|
|
253
274
|
/** Asserts the two framing rules against a face's own window. Throws on
|
|
254
|
-
* violation — `
|
|
275
|
+
* violation — `pnpm test` runs it for every registered avatar. */
|
|
255
276
|
export function checkHandFraming(meta: AvatarMeta): {
|
|
256
277
|
ok: true;
|
|
257
278
|
wristDrop: number;
|
|
258
279
|
outboardLimit: number;
|
|
259
280
|
worst: Record<string, number>;
|
|
260
281
|
};
|
|
261
|
-
|
|
262
|
-
export const
|
|
263
|
-
export const VISEME_LETTERS: VisemeLetter[];
|
|
264
|
-
export const VISEME_SHAPES: Record<string, Record<string, number>>;
|
|
282
|
+
export const VISEME_LETTERS: readonly VisemeLetter[];
|
|
283
|
+
export const VISEME_SHAPES: Readonly<Record<VisemeLetter, PoseOverrides>>;
|
|
265
284
|
/** Cues lead the audio by this many ms — perceptual tolerance is asymmetric. */
|
|
266
285
|
export const LEAD_MS: number;
|
|
267
|
-
export const ARPABET_TO_VISEME: Record<string,
|
|
268
|
-
export const AZURE_VISEME_TO_LETTER: Record<number,
|
|
286
|
+
export const ARPABET_TO_VISEME: Readonly<Record<string, VisemeLetter>>;
|
|
287
|
+
export const AZURE_VISEME_TO_LETTER: Readonly<Record<number, VisemeLetter>>;
|
|
288
|
+
|
|
289
|
+
/** The silent/rest letter, `"X"`. */
|
|
290
|
+
export const SILENT: VisemeLetter;
|
|
291
|
+
/** Pose channels for one letter at `intensity` (0..1), ready to merge into a frame. */
|
|
292
|
+
export function shapeFor(letter: VisemeLetter, intensity?: number): PoseOverrides;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The mouth clock. Someone has to turn a cue array plus a clock into "which
|
|
296
|
+
* letter is on screen right now"; every renderer needs exactly that and none
|
|
297
|
+
* should write it twice, so it is a class to construct rather than a contract
|
|
298
|
+
* to implement. `sample()` returns `null` when the track is done.
|
|
299
|
+
*/
|
|
300
|
+
export class VisemeTrack {
|
|
301
|
+
/** @param clock elapsed ms of the audio being played. */
|
|
302
|
+
start(cues: Cue[], clock: () => number): void;
|
|
303
|
+
/** Streaming top-up: append cues that arrive mid-utterance. */
|
|
304
|
+
push(cues: Cue[]): void;
|
|
305
|
+
stop(): void;
|
|
306
|
+
sample(): { letter: VisemeLetter; intensity: number } | null;
|
|
307
|
+
onEnd: (() => void) | null;
|
|
308
|
+
readonly playing: boolean;
|
|
309
|
+
}
|
|
269
310
|
|
|
270
311
|
export function attachAudio(id: string, url: string): void;
|
|
271
312
|
export function normalizeActions(actions: AvatarAction[]): AvatarAction[];
|