@voqalize/avatar 0.2.2 โ 0.4.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/LICENSE-CC-BY-4.0 +396 -0
- package/README.md +191 -662
- package/assets/README.md +48 -0
- package/assets/tanya.glb +0 -0
- package/assets/tara.glb +0 -0
- package/assets/tushar.glb +0 -0
- package/client/Avatar.tsx +57 -0
- package/client/AvatarClient.ts +818 -0
- package/client/arjun.ts +26 -0
- package/client/createAvatar.ts +177 -0
- package/client/createCanvasAvatar.ts +72 -0
- package/client/index.ts +44 -0
- package/client/internal.ts +108 -0
- package/client/interviewer-female.ts +4 -0
- package/client/interviewer-male.ts +4 -0
- package/client/ishita.ts +26 -0
- package/client/kabir.ts +26 -0
- package/client/meera.ts +26 -0
- package/client/naina.ts +26 -0
- package/client/playout.ts +95 -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/supports.ts +28 -0
- package/client/three/assets.ts +24 -0
- package/client/three/budgets.ts +27 -0
- package/client/three/holds.ts +64 -0
- package/client/three/internal.ts +54 -0
- package/client/three/motion-limits.json +116 -0
- package/client/three/sequences.ts +281 -0
- package/client/three/tanya.ts +69 -0
- package/client/three/tara-rig.ts +1562 -0
- package/client/three/tara.ts +76 -0
- package/client/three/tushar.ts +65 -0
- package/client/types.ts +235 -0
- package/client/useAvatar.ts +74 -0
- package/client/vikram.ts +26 -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 +274 -0
- package/dist/AvatarClient.d.ts.map +1 -0
- package/dist/AvatarClient.js +712 -0
- package/dist/AvatarClient.js.map +1 -0
- package/dist/arjun.d.ts +7 -0
- package/dist/arjun.d.ts.map +1 -0
- package/dist/arjun.js +20 -0
- package/dist/arjun.js.map +1 -0
- package/dist/createAvatar.d.ts +135 -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 +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +34 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +41 -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 +7 -0
- package/dist/ishita.d.ts.map +1 -0
- package/dist/ishita.js +20 -0
- package/dist/ishita.js.map +1 -0
- package/dist/kabir.d.ts +7 -0
- package/dist/kabir.d.ts.map +1 -0
- package/dist/kabir.js +20 -0
- package/dist/kabir.js.map +1 -0
- package/dist/meera.d.ts +7 -0
- package/dist/meera.d.ts.map +1 -0
- package/dist/meera.js +20 -0
- package/dist/meera.js.map +1 -0
- package/dist/naina.d.ts +7 -0
- package/dist/naina.d.ts.map +1 -0
- package/dist/naina.js +20 -0
- package/dist/naina.js.map +1 -0
- package/dist/playout.d.ts +42 -0
- package/dist/playout.d.ts.map +1 -0
- package/dist/playout.js +85 -0
- package/dist/playout.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/supports.d.ts +20 -0
- package/dist/supports.d.ts.map +1 -0
- package/dist/supports.js +26 -0
- package/dist/supports.js.map +1 -0
- package/dist/three/assets.d.ts +24 -0
- package/dist/three/assets.d.ts.map +1 -0
- package/dist/three/assets.js +22 -0
- package/dist/three/assets.js.map +1 -0
- package/dist/three/budgets.d.ts +17 -0
- package/dist/three/budgets.d.ts.map +1 -0
- package/dist/three/budgets.js +21 -0
- package/dist/three/budgets.js.map +1 -0
- package/dist/three/holds.d.ts +33 -0
- package/dist/three/holds.d.ts.map +1 -0
- package/dist/three/holds.js +56 -0
- package/dist/three/holds.js.map +1 -0
- package/dist/three/internal.d.ts +52 -0
- package/dist/three/internal.d.ts.map +1 -0
- package/dist/three/internal.js +49 -0
- package/dist/three/internal.js.map +1 -0
- package/dist/three/motion-limits.json +116 -0
- package/dist/three/sequences.d.ts +71 -0
- package/dist/three/sequences.d.ts.map +1 -0
- package/dist/three/sequences.js +262 -0
- package/dist/three/sequences.js.map +1 -0
- package/dist/three/tanya.d.ts +35 -0
- package/dist/three/tanya.d.ts.map +1 -0
- package/dist/three/tanya.js +51 -0
- package/dist/three/tanya.js.map +1 -0
- package/dist/three/tara-rig.d.ts +403 -0
- package/dist/three/tara-rig.d.ts.map +1 -0
- package/dist/three/tara-rig.js +1500 -0
- package/dist/three/tara-rig.js.map +1 -0
- package/dist/three/tara.d.ts +35 -0
- package/dist/three/tara.d.ts.map +1 -0
- package/dist/three/tara.js +58 -0
- package/dist/three/tara.js.map +1 -0
- package/dist/three/tushar.d.ts +31 -0
- package/dist/three/tushar.d.ts.map +1 -0
- package/dist/three/tushar.js +47 -0
- package/dist/three/tushar.js.map +1 -0
- package/dist/types.d.ts +149 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +140 -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 +7 -0
- package/dist/vikram.d.ts.map +1 -0
- package/dist/vikram.js +20 -0
- package/dist/vikram.js.map +1 -0
- package/package.json +110 -25
- package/src/avatar.d.ts +218 -120
- package/src/avatar.js +950 -213
- package/src/behavior.d.ts +42 -0
- package/src/behavior.js +114 -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 +8 -5
- 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 +434 -58
- package/src/hand.js +120 -91
- package/src/head.js +134 -0
- package/src/idle.js +227 -119
- package/src/interjections.js +200 -31
- package/src/params.js +8 -4
- package/src/perform.js +7 -9
- package/src/prosody.js +647 -0
- package/src/rig.d.ts +24 -0
- package/src/rig.js +32 -0
- package/src/speech-timing.js +23 -0
- package/src/visemes.js +110 -24
- 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/interjections.js
CHANGED
|
@@ -26,9 +26,13 @@ const nod = (peak, at = 0.24, rebound = 0.56) => [
|
|
|
26
26
|
[0, 0], [at, peak], [rebound, -peak * 0.24], [1, 0],
|
|
27
27
|
];
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// This is the authoring library. It intentionally contains exploratory clips
|
|
30
|
+
// as well as production ones; only the small `ACTIONS` export below is
|
|
31
|
+
// part of the public action contract.
|
|
32
|
+
const CLIPS = {
|
|
30
33
|
// ---------------------------------------------------------------------
|
|
31
|
-
//
|
|
34
|
+
// Wordless acknowledgement clips. Every one is an explicit backend or
|
|
35
|
+
// application decision; the client never fires them from VAD or a timer.
|
|
32
36
|
// ---------------------------------------------------------------------
|
|
33
37
|
// The nod peaks below are pre-compensated for the head's 160ms time constant.
|
|
34
38
|
// Authored at their perceptual value (~0.3) they rendered at ~0.6x and read as
|
|
@@ -44,26 +48,27 @@ export const INTERJECTIONS = {
|
|
|
44
48
|
// drops further than the trend โ and everything stays under the 1.5 Hz line
|
|
45
49
|
// where sustained attention turns into impatience.
|
|
46
50
|
NOD_SMALL: {
|
|
47
|
-
id: 'NOD_SMALL', label: 'nod (continuer)', text: '', duration:
|
|
51
|
+
id: 'NOD_SMALL', label: 'nod (continuer)', text: '', duration: 900,
|
|
48
52
|
keys: {
|
|
49
|
-
// One
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
// One readable stroke, not an ambient bob. The peak is deliberately
|
|
54
|
+
// authored above its desired rendered travel because the common head
|
|
55
|
+
// smoothing absorbs a fast impulse before it reaches the SVG.
|
|
56
|
+
headPitch: [[0, 0], [0.25, 0.74], [0.38, 0.70], [0.68, -0.12], [1, 0]],
|
|
57
|
+
browRaiseL: [[0, 0], [0.34, 0.16], [1, 0]],
|
|
58
|
+
browRaiseR: [[0, 0], [0.34, 0.16], [1, 0]],
|
|
54
59
|
},
|
|
55
60
|
},
|
|
56
61
|
NOD_SLOW: {
|
|
57
|
-
id: 'NOD_SLOW', label: 'nod (assessment
|
|
62
|
+
id: 'NOD_SLOW', label: 'nod (assessment / receipt)', text: '', duration: 1200,
|
|
58
63
|
keys: {
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// A receipt needs an arrival. The former two fast peaks registered as
|
|
65
|
+
// idle bobbing at call-tile size, so this is one deep downstroke with a
|
|
66
|
+
// short dwell and a slower recovery. The tiny torso commitment keeps the
|
|
67
|
+
// head from looking like it is sliding independently of the body.
|
|
68
|
+
headPitch: [[0, 0], [0.18, 0.18], [0.39, 1.06], [0.52, 1.02], [0.78, -0.16], [1, 0]],
|
|
69
|
+
torsoLean: [[0, 0], [0.40, 0.09], [0.58, 0.07], [1, 0]],
|
|
70
|
+
browRaiseL: [[0, 0], [0.23, 0.12], [0.62, 0.08], [1, 0]],
|
|
71
|
+
browRaiseR: [[0, 0], [0.23, 0.10], [0.62, 0.07], [1, 0]],
|
|
67
72
|
},
|
|
68
73
|
},
|
|
69
74
|
// The "ah โ I see" nod. What separates it from agreement is the upswing:
|
|
@@ -88,13 +93,126 @@ export const INTERJECTIONS = {
|
|
|
88
93
|
keys: {
|
|
89
94
|
browRaiseL: [[0, 0], [0.24, 0.30], [0.7, 0.06], [1, 0]],
|
|
90
95
|
browRaiseR: [[0, 0], [0.24, 0.26], [0.7, 0.06], [1, 0]],
|
|
91
|
-
headPitch: [[0, 0], [0.3, 0.08], [1, 0]],
|
|
92
96
|
mouthCornerL: [[0, 0], [0.35, 0.14], [1, 0]],
|
|
93
97
|
mouthCornerR: [[0, 0], [0.35, 0.14], [1, 0]],
|
|
94
98
|
},
|
|
95
99
|
blinkAt: [0.22],
|
|
96
100
|
},
|
|
97
|
-
//
|
|
101
|
+
// ---------------------------------------------------------------------
|
|
102
|
+
// Understanding beats โ eye/face/body composites, deliberately separate
|
|
103
|
+
// from nods. They are the shared low-authoring-cost alternative when a
|
|
104
|
+
// portrait's pitch geometry is too limited to carry acknowledgement alone.
|
|
105
|
+
// ---------------------------------------------------------------------
|
|
106
|
+
ACK_CONTINUE: {
|
|
107
|
+
id: 'ACK_CONTINUE', label: 'acknowledge: continue', text: '', duration: 620,
|
|
108
|
+
keys: {
|
|
109
|
+
// Eyes acknowledge first; nothing here claims agreement or closes the
|
|
110
|
+
// conversational floor while the user is still talking.
|
|
111
|
+
browRaiseL: [[0, 0], [0.18, 0.14], [0.48, 0.06], [1, 0]],
|
|
112
|
+
browRaiseR: [[0, 0], [0.18, 0.12], [0.48, 0.05], [1, 0]],
|
|
113
|
+
lidL: [[0, 0], [0.20, 0.035], [0.58, 0.01], [1, 0]],
|
|
114
|
+
lidR: [[0, 0], [0.20, 0.035], [0.58, 0.01], [1, 0]],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
ACK_RECEIVE: {
|
|
118
|
+
id: 'ACK_RECEIVE', label: 'acknowledge: received', text: '', duration: 1120,
|
|
119
|
+
keys: {
|
|
120
|
+
// Recognition โ take it in โ settle. The face does not nod; it lands in
|
|
121
|
+
// a quiet, held receipt, leaving room for the user to see the response.
|
|
122
|
+
// Recognition is quick: everything arrives in the first ~150 ms and then
|
|
123
|
+
// holds. It used to build over the first third of the clip with the lids
|
|
124
|
+
// half-lowered and the trunk sinking, and a video reviewer read that as
|
|
125
|
+
// the head drifting down โ "looks like losing consciousness". The lids
|
|
126
|
+
// are a flicker now, not a droop, and the lean is a small forward set.
|
|
127
|
+
browRaiseL: [[0, 0], [0.09, 0.17], [0.30, 0.06], [1, 0]],
|
|
128
|
+
browRaiseR: [[0, 0], [0.09, 0.14], [0.30, 0.05], [1, 0]],
|
|
129
|
+
lidL: [[0, 0], [0.10, 0.09], [0.30, 0.04], [0.70, 0.02], [1, 0]],
|
|
130
|
+
lidR: [[0, 0], [0.10, 0.09], [0.30, 0.04], [0.70, 0.02], [1, 0]],
|
|
131
|
+
mouthPress: [[0, 0], [0.13, 0.20], [0.60, 0.15], [1, 0]],
|
|
132
|
+
mouthCornerL: [[0, 0], [0.18, 0.09], [0.80, 0.055], [1, 0]],
|
|
133
|
+
mouthCornerR: [[0, 0], [0.18, 0.09], [0.80, 0.055], [1, 0]],
|
|
134
|
+
torsoLean: [[0, 0], [0.14, 0.07], [0.70, 0.05], [1, 0]],
|
|
135
|
+
shoulderL: [[0, 0], [0.16, 0.060], [0.70, 0.035], [1, 0]],
|
|
136
|
+
shoulderR: [[0, 0], [0.16, 0.060], [0.70, 0.035], [1, 0]],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
ACK_REALIZE: {
|
|
140
|
+
id: 'ACK_REALIZE', label: 'acknowledge: realization', text: '', duration: 980,
|
|
141
|
+
keys: {
|
|
142
|
+
// The brows and widened lid lead: this means "the point connected", not
|
|
143
|
+
// the generic social smile which looks like agreement in a small tile.
|
|
144
|
+
browRaiseL: [[0, 0], [0.15, 0.36], [0.42, 0.20], [1, 0]],
|
|
145
|
+
browRaiseR: [[0, 0], [0.15, 0.32], [0.42, 0.18], [1, 0]],
|
|
146
|
+
lidL: [[0, 0], [0.16, -0.07], [0.48, -0.025], [1, 0]],
|
|
147
|
+
lidR: [[0, 0], [0.16, -0.07], [0.48, -0.025], [1, 0]],
|
|
148
|
+
mouthCornerL: [[0, 0], [0.35, 0.12], [0.80, 0.09], [1, 0]],
|
|
149
|
+
mouthCornerR: [[0, 0], [0.35, 0.12], [0.80, 0.09], [1, 0]],
|
|
150
|
+
torsoLean: [[0, 0], [0.38, 0.07], [0.72, 0.05], [1, 0]],
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
ACK_EMPATHIZE: {
|
|
154
|
+
id: 'ACK_EMPATHIZE', label: 'acknowledge: empathy', text: '', duration: 1180,
|
|
155
|
+
keys: {
|
|
156
|
+
// Inner brows and softened lids acknowledge the affect without signalling
|
|
157
|
+
// agreement. Keep the mouth nearly neutral for professional contexts.
|
|
158
|
+
browInnerL: [[0, 0], [0.22, 0.24], [0.68, 0.16], [1, 0]],
|
|
159
|
+
browInnerR: [[0, 0], [0.22, 0.20], [0.68, 0.13], [1, 0]],
|
|
160
|
+
browRaiseL: [[0, 0], [0.22, 0.07], [0.68, 0.05], [1, 0]],
|
|
161
|
+
browRaiseR: [[0, 0], [0.22, 0.06], [0.68, 0.04], [1, 0]],
|
|
162
|
+
lidL: [[0, 0], [0.25, 0.06], [0.72, 0.04], [1, 0]],
|
|
163
|
+
lidR: [[0, 0], [0.25, 0.06], [0.72, 0.04], [1, 0]],
|
|
164
|
+
mouthPress: [[0, 0], [0.34, 0.06], [0.72, 0.045], [1, 0]],
|
|
165
|
+
torsoLean: [[0, 0], [0.40, 0.065], [0.76, 0.045], [1, 0]],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
// A comparison nod, not the director's default. Two quick decaying strokes
|
|
169
|
+
// with a barely-there listening tilt reflect the common conversational form
|
|
170
|
+
// the current single deep receipt does not show. It stays comparison-only:
|
|
171
|
+
// the host can decide whether this faster cadence fits its conversation.
|
|
172
|
+
// **Slower than it was, and that is what made it land.** This nod ran two
|
|
173
|
+
// down-beats 381 ms apart โ 2.6 Hz, which is past the 1.5 Hz line CLAUDE.md
|
|
174
|
+
// and research-biomechanics.md ยง 3.4 both draw between "I'm with you" and
|
|
175
|
+
// "hurry up", and which the head's own 160 ms smoothing attenuates to about a
|
|
176
|
+
// third. Authoring it louder was the obvious fix and the wrong one: the
|
|
177
|
+
// gesture was not too small, it was too *fast* to survive the mass of the
|
|
178
|
+
// thing it was moving.
|
|
179
|
+
//
|
|
180
|
+
// At 780 ms between down-beats it is 1.28 Hz, attenuated to about 0.64
|
|
181
|
+
// instead of 0.36, and 1.5 s long โ which is where the research puts this
|
|
182
|
+
// kind of nod anyway. ยง 3.3 separates a `short` continuer (0.83 s, small)
|
|
183
|
+
// from a `long` assessment nod (1.42 s, large range), and an acknowledgement
|
|
184
|
+
// of what someone just said is the second. ยง 3.4's two structural laws hold:
|
|
185
|
+
// the first cycle is the biggest, and each one decays.
|
|
186
|
+
//
|
|
187
|
+
// **No opening up-beat, and the corpus is why.** An earlier pass gave this a
|
|
188
|
+
// small preparatory lift on the theory that a nod winds up before it starts.
|
|
189
|
+
// ยง 3.3 separates `long` (large range, *no* swing-up) from `long_p` (large
|
|
190
|
+
// range *with* one, reading as a cognitive shift), so a lift here was quietly
|
|
191
|
+
// making agreement look like realisation. It also broke the rate: the beat
|
|
192
|
+
// sat 330 ms before the first peak, a 1.52 Hz opening half-cycle in a clip
|
|
193
|
+
// whose two down-beats are a comfortable 1.28 Hz apart.
|
|
194
|
+
ACK_NOD: {
|
|
195
|
+
id: 'ACK_NOD', label: 'acknowledge: nod', text: '', duration: 1500,
|
|
196
|
+
keys: {
|
|
197
|
+
// Down, up through neutral, down again, settle. The trailing โ0.08 flourish
|
|
198
|
+
// that used to sit at 0.94 is gone: it put two *return* beats 570 ms apart,
|
|
199
|
+
// a 1.75 Hz oscillation inside a clip whose down-beats are a comfortable
|
|
200
|
+
// 1.28 Hz. A gesture's last move should be releasing, not one more beat.
|
|
201
|
+
// Each down is the stroke and each up the recovery, so the downs are
|
|
202
|
+
// fast and the ups take their time: a symmetric ramp on both sides read
|
|
203
|
+
// as "a very slow, linear dip" on video. The downs still sit 720 ms
|
|
204
|
+
// apart (1.39 Hz), and a short hold at the bottom is what gives the
|
|
205
|
+
// stroke its weight.
|
|
206
|
+
headPitch: [[0, 0], [0.16, 1.10], [0.24, 1.00], [0.52, -0.20], [0.64, 0.66], [0.72, 0.58], [1, 0]],
|
|
207
|
+
headRoll: [[0, 0], [0.18, -0.090], [0.70, -0.070], [1, 0]],
|
|
208
|
+
torsoLean: [[0, 0], [0.32, 0.130], [0.70, 0.085], [1, 0]],
|
|
209
|
+
shoulderL: [[0, 0], [0.44, 0.062], [0.76, 0.036], [1, 0]],
|
|
210
|
+
shoulderR: [[0, 0], [0.44, 0.062], [0.76, 0.036], [1, 0]],
|
|
211
|
+
lidL: [[0, 0], [0.30, 0.070], [0.68, 0.040], [1, 0]],
|
|
212
|
+
lidR: [[0, 0], [0.30, 0.070], [0.68, 0.040], [1, 0]],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
// The disagree family follows the same three laws as explicit nods (ยง3.4): the
|
|
98
216
|
// first swing is the biggest, every cycle decays, and the whole gesture
|
|
99
217
|
// stays at or under ~1.5 Hz. Both are SERVER-SENT ONLY โ an agent must
|
|
100
218
|
// never disagree autonomously, so neither is in the listening engine's
|
|
@@ -211,6 +329,24 @@ export const INTERJECTIONS = {
|
|
|
211
329
|
blinkAt: [0.1],
|
|
212
330
|
},
|
|
213
331
|
|
|
332
|
+
/** Server-confirmed cut-off after playout stopped. This is not a state: the
|
|
333
|
+
* clip explains the abrupt transition, then lands in whatever factual pose
|
|
334
|
+
* Pipecat has resolved underneath it. */
|
|
335
|
+
RESPONSE_INTERRUPTED: {
|
|
336
|
+
id: 'RESPONSE_INTERRUPTED', label: 'response: interrupted', text: '', duration: 1550,
|
|
337
|
+
keys: {
|
|
338
|
+
mouthOpen: [[0, 0], [0.06, 0.36], [0.80, 0.36], [1, 0]],
|
|
339
|
+
jaw: [[0, 0], [0.08, 0.20], [0.80, 0.20], [1, 0]],
|
|
340
|
+
browRaiseL: [[0, 0], [0.12, 0.46], [0.72, 0.34], [1, 0]],
|
|
341
|
+
browRaiseR: [[0, 0], [0.12, 0.40], [0.72, 0.30], [1, 0]],
|
|
342
|
+
lidL: [[0, 0], [0.12, -0.18], [0.72, -0.12], [1, 0]],
|
|
343
|
+
lidR: [[0, 0], [0.12, -0.16], [0.72, -0.10], [1, 0]],
|
|
344
|
+
torsoLean: [[0, 0], [0.18, -0.16], [0.72, -0.10], [1, 0]],
|
|
345
|
+
shoulderL: [[0, 0], [0.18, -0.12], [0.72, -0.08], [1, 0]],
|
|
346
|
+
shoulderR: [[0, 0], [0.18, -0.12], [0.72, -0.08], [1, 0]],
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
|
|
214
350
|
/**
|
|
215
351
|
* "May I come in." This used to be a raised palm, and it is now the same
|
|
216
352
|
* signal without one: the body claims a little space and the face asks. The
|
|
@@ -273,8 +409,8 @@ export const INTERJECTIONS = {
|
|
|
273
409
|
* oscillation left to fill the time; a held smile past about a second stops
|
|
274
410
|
* being a greeting and becomes an expression.
|
|
275
411
|
*/
|
|
276
|
-
|
|
277
|
-
id: '
|
|
412
|
+
GESTURE_GREET: {
|
|
413
|
+
id: 'GESTURE_GREET', label: 'gesture: greet', text: '', duration: 1300,
|
|
278
414
|
keys: {
|
|
279
415
|
browRaiseL: [[0, 0], [0.11, 0.85], [0.4, 0.22], [0.75, 0.12], [1, 0]],
|
|
280
416
|
browRaiseR: [[0, 0], [0.11, 0.80], [0.4, 0.20], [0.75, 0.10], [1, 0]],
|
|
@@ -304,8 +440,8 @@ export const INTERJECTIONS = {
|
|
|
304
440
|
* Deep enough to be unmistakable: peak 0.62 against NOD_SMALL's 0.52, over
|
|
305
441
|
* nearly three times the duration.
|
|
306
442
|
*/
|
|
307
|
-
|
|
308
|
-
id: '
|
|
443
|
+
GESTURE_APPROVE: {
|
|
444
|
+
id: 'GESTURE_APPROVE', label: 'gesture: approve', text: '', duration: 1500,
|
|
309
445
|
keys: {
|
|
310
446
|
headPitch: [[0, 0], [0.3, 0.62], [0.58, 0.20], [0.78, 0.34], [1, 0]],
|
|
311
447
|
mouthCornerL: [[0, 0], [0.36, 0.58], [0.8, 0.48], [1, 0]],
|
|
@@ -473,8 +609,8 @@ export const INTERJECTIONS = {
|
|
|
473
609
|
torsoLean: [[0, 0], [0.25, 0.10], [0.7, 0.08], [1, 0]],
|
|
474
610
|
},
|
|
475
611
|
},
|
|
476
|
-
|
|
477
|
-
id: '
|
|
612
|
+
GESTURE_WAIT: {
|
|
613
|
+
id: 'GESTURE_WAIT', label: 'gesture: wait', text: 'one moment', duration: 1350,
|
|
478
614
|
// w ยท ส ยท n ยท m ยท oส ยท m ยท ษ ยท n ยท t
|
|
479
615
|
mouthCues: [
|
|
480
616
|
{ t: 0, v: 'F' }, { t: 110, v: 'C' }, { t: 210, v: 'A' }, { t: 320, v: 'F' },
|
|
@@ -557,19 +693,52 @@ export const INTERJECTIONS = {
|
|
|
557
693
|
},
|
|
558
694
|
};
|
|
559
695
|
|
|
560
|
-
|
|
696
|
+
/** Full local authoring library. Not a server action vocabulary. */
|
|
697
|
+
export const INTERNAL_CLIPS = CLIPS;
|
|
561
698
|
|
|
562
|
-
/**
|
|
563
|
-
|
|
699
|
+
/**
|
|
700
|
+
* This renderer's own addressable actions โ the bundled SVG faces' catalogue,
|
|
701
|
+
* not the wire's vocabulary, which is open and has only two required ids
|
|
702
|
+
* (`docs/contract-wire.md` ยง Action).
|
|
703
|
+
*
|
|
704
|
+
* Every one of these is a name a server may send *once it knows this renderer
|
|
705
|
+
* is mounted*, and nothing here is a name it may send blind. Two of them are
|
|
706
|
+
* what `ACKNOWLEDGE` resolves to on the floor โ `ACK_NOD` while the user still
|
|
707
|
+
* has it, `ACK_RECEIVE` once they have stopped โ and `RESPONSE_INTERRUPTED` is
|
|
708
|
+
* required of every avatar, so it appears here as this renderer's shape for it
|
|
709
|
+
* rather than as its own idea. The four `GESTURE_*` are things this body does
|
|
710
|
+
* and no protocol asks for.
|
|
711
|
+
*
|
|
712
|
+
* Naming is `CATEGORY_INTENT`, and the list doubles as the conformance sweep's
|
|
713
|
+
* roster: `ACKNOWLEDGE` is absent on purpose, because sweeping both shapes it
|
|
714
|
+
* resolves to covers strictly more.
|
|
715
|
+
*/
|
|
716
|
+
export const ACTION_IDS = Object.freeze([
|
|
717
|
+
'ACK_RECEIVE', 'ACK_NOD',
|
|
718
|
+
'RESPONSE_INTERRUPTED',
|
|
719
|
+
'GESTURE_GREET', 'GESTURE_GOODBYE', 'GESTURE_APPROVE', 'GESTURE_WAIT',
|
|
720
|
+
]);
|
|
721
|
+
|
|
722
|
+
/** Face-capable subset of the action contract. */
|
|
723
|
+
export const ACTIONS = Object.freeze({
|
|
724
|
+
...Object.fromEntries(ACTION_IDS.filter((id) => CLIPS[id]).map((id) => [id, CLIPS[id]])),
|
|
725
|
+
// The goodbye face intentionally reuses the greeting brow-flash while its
|
|
726
|
+
// hand owns the distinct, longer farewell motion. It still needs metadata so
|
|
727
|
+
// a client can enumerate every public action without knowing that detail.
|
|
728
|
+
GESTURE_GOODBYE: {
|
|
729
|
+
...CLIPS.GESTURE_GREET,
|
|
730
|
+
id: 'GESTURE_GOODBYE', label: 'gesture: goodbye', duration: 1550,
|
|
731
|
+
},
|
|
732
|
+
});
|
|
564
733
|
|
|
565
734
|
/**
|
|
566
|
-
* Attach audio to
|
|
567
|
-
*
|
|
735
|
+
* Attach audio to an internal authoring clip. Production actions are silent
|
|
736
|
+
* sequences; response speech always belongs to Pipecat's audio/viseme track.
|
|
568
737
|
* The baked viseme track is then scheduled against that file's clock instead of
|
|
569
738
|
* the local timer, so any timing drift resolves in the audio's favour.
|
|
570
739
|
*/
|
|
571
740
|
export function attachAudio(id, url) {
|
|
572
|
-
const clip =
|
|
741
|
+
const clip = CLIPS[id];
|
|
573
742
|
if (!clip) throw new Error(`unknown interjection: ${id}`);
|
|
574
743
|
const el = new Audio(url);
|
|
575
744
|
el.preload = 'auto';
|
package/src/params.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* mouthCornerL/R + up (smile)
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { JAW_RESPONSE_TAU_S, MOUTH_RESPONSE_TAU_S } from './speech-timing.js';
|
|
18
|
+
|
|
17
19
|
export const REST = {
|
|
18
20
|
// --- mouth -------------------------------------------------------------
|
|
19
21
|
mouthOpen: 0.02, // vertical aperture, 0..1
|
|
@@ -21,8 +23,10 @@ export const REST = {
|
|
|
21
23
|
mouthRound: 0.1, // pucker / lip protrusion
|
|
22
24
|
mouthPress: 0.15, // lips thinned & pressed together
|
|
23
25
|
mouthTuck: 0.0, // lower lip drawn under upper teeth (F/V)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
// Availability is not a permanent smile. Warmth and encouragement are
|
|
27
|
+
// explicit behavioral choices layered above this neutral resting shape.
|
|
28
|
+
mouthCornerL: 0.0, // -1 frown .. +1 smile
|
|
29
|
+
mouthCornerR: 0.0,
|
|
26
30
|
teethUpper: 0.0, // how far the upper teeth show, 0..1
|
|
27
31
|
tongue: 0.0, // tongue raised into the aperture, 0..1
|
|
28
32
|
jaw: 0.0, // extra chin drop, follows mouthOpen but slower
|
|
@@ -118,7 +122,7 @@ export const GROUPS = {
|
|
|
118
122
|
export const TAU = (() => {
|
|
119
123
|
const t = {};
|
|
120
124
|
for (const c of CHANNELS) t[c] = 0.09;
|
|
121
|
-
for (const c of GROUPS.mouth) t[c] =
|
|
125
|
+
for (const c of GROUPS.mouth) t[c] = MOUTH_RESPONSE_TAU_S;
|
|
122
126
|
for (const c of GROUPS.smile) t[c] = 0.13;
|
|
123
127
|
t.lidL = t.lidR = 0.018; // blinks must be crisp
|
|
124
128
|
t.squintL = t.squintR = 0.12;
|
|
@@ -126,7 +130,7 @@ export const TAU = (() => {
|
|
|
126
130
|
for (const c of GROUPS.brows) t[c] = 0.08;
|
|
127
131
|
for (const c of GROUPS.head) t[c] = 0.16; // the head has real mass
|
|
128
132
|
t.breath = 0.25;
|
|
129
|
-
t.jaw =
|
|
133
|
+
t.jaw = JAW_RESPONSE_TAU_S; // the jaw lags the lips slightly
|
|
130
134
|
// The torso has more mass than the head and reads wrong when it hasn't.
|
|
131
135
|
t.shoulderL = t.shoulderR = 0.19;
|
|
132
136
|
t.torsoLean = 0.24;
|
package/src/perform.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A performance is the server's choreography: timed verbs `{t, do, ...}` fired
|
|
5
5
|
* against a clock, where every verb resolves to one of the widget's own enums โ
|
|
6
|
-
* states, emotions, gaze targets,
|
|
6
|
+
* states, emotions, gaze targets, and semantic actions. The vocabulary is deliberately
|
|
7
7
|
* closed: the backend sequences what the rig already does well, it cannot
|
|
8
8
|
* invent motion. That constraint is what makes the wire format assemblable by
|
|
9
9
|
* a dialogue manager and reviewable by a human.
|
|
@@ -14,25 +14,23 @@
|
|
|
14
14
|
* tab is backgrounded. One deliberate difference: beat times fire *verbatim*,
|
|
15
15
|
* with no LEAD_MS. Visemes lead the sound because phoneme sync is
|
|
16
16
|
* frame-critical; a gesture arrives through its channels' own smoothing lag,
|
|
17
|
-
* and any deliberate lead
|
|
18
|
-
*
|
|
17
|
+
* and any deliberate lead is authored into the times by the composer, not
|
|
18
|
+
* imposed here.
|
|
19
19
|
*
|
|
20
20
|
* Seeking the audio backward does not re-fire earlier actions: verbs have side
|
|
21
21
|
* effects, and replaying a nod is worse than missing one.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
const VERBS = new Set(['state', 'emotion', 'gaze', '
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
const ID_VERBS = new Set(['interject', 'gesture']);
|
|
24
|
+
const VERBS = new Set(['state', 'emotion', 'gaze', 'action']);
|
|
25
|
+
// `action` is addressed by id; the remaining verbs name an enum value.
|
|
26
|
+
const ID_VERBS = new Set(['action']);
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
29
|
* Shape hygiene for action arrays, in the spirit of normalizeCues: sort by
|
|
31
30
|
* time, drop what cannot possibly fire โ no finite `t`, an unknown verb, a
|
|
32
31
|
* missing `name`/`id` โ each with a console warning, never a throw.
|
|
33
32
|
*
|
|
34
|
-
* This checks *shape* only. Enum values
|
|
35
|
-
* an interjection?) are checked when the verb fires, by the dispatcher in
|
|
33
|
+
* This checks *shape* only. Enum values are checked when the verb fires, by the dispatcher in
|
|
36
34
|
* avatar.js โ deliberately, and not just because importing the enums here
|
|
37
35
|
* would be a dependency cycle: a track half-composed against a newer widget
|
|
38
36
|
* should lose the verbs the widget doesn't know, not the whole performance.
|