@lovelace_lol/polymer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +226 -0
  2. package/dist/index.d.ts +2603 -0
  3. package/dist/index.js +1031 -0
  4. package/dist/test.cjs +1519 -0
  5. package/dist/worker/agency-worker.js +972 -0
  6. package/dist/worker/manifest.edn +1 -0
  7. package/package.json +57 -0
  8. package/shadow-cljs.edn +173 -0
  9. package/src/polymer/animation/agency.cljs +84 -0
  10. package/src/polymer/animation/domain.cljs +110 -0
  11. package/src/polymer/animation/planner.cljs +88 -0
  12. package/src/polymer/animation/runtime.cljs +137 -0
  13. package/src/polymer/animation/scheduler.cljs +178 -0
  14. package/src/polymer/animation/service.cljs +981 -0
  15. package/src/polymer/animation/state.cljs +112 -0
  16. package/src/polymer/blink/agency.cljs +131 -0
  17. package/src/polymer/blink/planner.cljs +115 -0
  18. package/src/polymer/blink/scheduler.cljs +90 -0
  19. package/src/polymer/blink/snippet.cljs +50 -0
  20. package/src/polymer/blink/state.cljs +102 -0
  21. package/src/polymer/camera_context/agency.cljs +132 -0
  22. package/src/polymer/camera_context/domain.cljs +186 -0
  23. package/src/polymer/camera_context/planner.cljs +50 -0
  24. package/src/polymer/camera_context/scheduler.cljs +113 -0
  25. package/src/polymer/camera_context/state.cljs +121 -0
  26. package/src/polymer/character.cljs +471 -0
  27. package/src/polymer/conversation/agency.cljs +83 -0
  28. package/src/polymer/conversation/domain.cljs +35 -0
  29. package/src/polymer/conversation/effector.cljs +130 -0
  30. package/src/polymer/conversation/planner.cljs +178 -0
  31. package/src/polymer/conversation/scheduler.cljs +202 -0
  32. package/src/polymer/conversation/service.cljs +329 -0
  33. package/src/polymer/conversation/state.cljs +186 -0
  34. package/src/polymer/core.cljs +172 -0
  35. package/src/polymer/eye_head/agency.cljs +75 -0
  36. package/src/polymer/eye_head/domain.cljs +133 -0
  37. package/src/polymer/eye_head/goap.cljs +106 -0
  38. package/src/polymer/eye_head/scheduler.cljs +176 -0
  39. package/src/polymer/eye_head/service.cljs +259 -0
  40. package/src/polymer/eye_head/snippet.cljs +92 -0
  41. package/src/polymer/eye_head/state.cljs +167 -0
  42. package/src/polymer/gaze/agency.cljs +85 -0
  43. package/src/polymer/gaze/domain.cljs +211 -0
  44. package/src/polymer/gaze/goap.cljs +208 -0
  45. package/src/polymer/gaze/scheduler.cljs +184 -0
  46. package/src/polymer/gaze/state.cljs +202 -0
  47. package/src/polymer/gesture/agency.cljs +185 -0
  48. package/src/polymer/gesture/goap.cljs +461 -0
  49. package/src/polymer/gesture/scheduler.cljs +122 -0
  50. package/src/polymer/gesture/snippet.cljs +198 -0
  51. package/src/polymer/gesture/state.cljs +273 -0
  52. package/src/polymer/lipsync/agency.cljs +593 -0
  53. package/src/polymer/lipsync/articulation/jaw.cljs +258 -0
  54. package/src/polymer/lipsync/articulation/snippet.cljs +605 -0
  55. package/src/polymer/lipsync/articulation/tongue.cljs +418 -0
  56. package/src/polymer/lipsync/articulation/visemes.cljs +429 -0
  57. package/src/polymer/lipsync/goap.cljs +132 -0
  58. package/src/polymer/lipsync/pronunciation/effector.cljs +51 -0
  59. package/src/polymer/lipsync/scheduler.cljs +302 -0
  60. package/src/polymer/lipsync/state.cljs +202 -0
  61. package/src/polymer/nlp/phonemes.cljs +164 -0
  62. package/src/polymer/prosodic/agency.cljs +196 -0
  63. package/src/polymer/prosodic/goap.cljs +122 -0
  64. package/src/polymer/prosodic/scheduler.cljs +99 -0
  65. package/src/polymer/prosodic/snippet.cljs +106 -0
  66. package/src/polymer/prosodic/state.cljs +139 -0
  67. package/src/polymer/runtime/embody.cljs +117 -0
  68. package/src/polymer/runtime/embody_wasm.cljs +177 -0
  69. package/src/polymer/runtime/rust_embody_host.cljs +645 -0
  70. package/src/polymer/society.cljs +239 -0
  71. package/src/polymer/stream.cljs +130 -0
  72. package/src/polymer/transcription/agency.cljs +69 -0
  73. package/src/polymer/transcription/domain.cljs +68 -0
  74. package/src/polymer/transcription/effector.cljs +611 -0
  75. package/src/polymer/transcription/planner.cljs +203 -0
  76. package/src/polymer/transcription/scheduler.cljs +225 -0
  77. package/src/polymer/transcription/service.cljs +209 -0
  78. package/src/polymer/transcription/state.cljs +239 -0
  79. package/src/polymer/tts/agency.cljs +503 -0
  80. package/src/polymer/tts/azure.cljs +290 -0
  81. package/src/polymer/tts/planner.cljs +178 -0
  82. package/src/polymer/tts/runtime.cljs +624 -0
  83. package/src/polymer/tts/scheduler.cljs +162 -0
  84. package/src/polymer/tts/state.cljs +191 -0
  85. package/src/polymer/worker.cljs +70 -0
@@ -0,0 +1,418 @@
1
+ (ns polymer.lipsync.articulation.tongue
2
+ (:require [clojure.string :as str]
3
+ [polymer.lipsync.state :as state]
4
+ [polymer.lipsync.articulation.visemes :as visemes]))
5
+
6
+ ;; Tongue planning is an independent LipSync articulator beside lips and jaw.
7
+ ;; The input is the same normalized phoneme/viseme timeline as the rest of
8
+ ;; LipSync. The output is still ordinary typed AU channels, so Embody receives
9
+ ;; the same snippet data shape as lips, jaw, blink, and future agencies.
10
+
11
+ (def tongue-up-au "37")
12
+ (def tongue-down-au "38")
13
+ (def tongue-left-au "39")
14
+ (def tongue-right-au "40")
15
+ (def tongue-tilt-left-au "41")
16
+ (def tongue-tilt-right-au "42")
17
+ (def tongue-narrow-au "73")
18
+ (def tongue-wide-au "74")
19
+ (def tongue-tip-up-au "76")
20
+ (def tongue-tip-down-au "77")
21
+
22
+ (def tongue-au-keys
23
+ ;; Keep the public list near the constants so snippet metadata and tests can
24
+ ;; count tongue channels without hard-coding the planner internals.
25
+ [tongue-up-au
26
+ tongue-down-au
27
+ tongue-left-au
28
+ tongue-right-au
29
+ tongue-tilt-left-au
30
+ tongue-tilt-right-au
31
+ tongue-narrow-au
32
+ tongue-wide-au
33
+ tongue-tip-up-au
34
+ tongue-tip-down-au])
35
+
36
+ (def tongue-au-caps
37
+ ;; Per-AU caps keep the planner from over-driving optional tongue controls.
38
+ ;; Tip/up cues can be more visible; width, tilt, and yaw cues stay subtle.
39
+ {tongue-up-au 0.68
40
+ tongue-down-au 0.48
41
+ tongue-left-au 0.16
42
+ tongue-right-au 0.16
43
+ tongue-tilt-left-au 0.18
44
+ tongue-tilt-right-au 0.18
45
+ tongue-narrow-au 0.50
46
+ tongue-wide-au 0.34
47
+ tongue-tip-up-au 0.62
48
+ tongue-tip-down-au 0.48})
49
+
50
+ (def intensity-eps 0.001)
51
+ (def tongue-group-gap-ms 86)
52
+ (def tongue-lead-ms 36)
53
+ (def tongue-release-ms 88)
54
+ (def tongue-attack-ms 48)
55
+ (def tongue-min-hold-ms 34)
56
+
57
+ (def tongue-visemes
58
+ #{(:Ch_J visemes/canonical-visemes)
59
+ (:K_G_H_NG visemes/canonical-visemes)
60
+ (:T_L_D_N visemes/canonical-visemes)
61
+ (:Th visemes/canonical-visemes)
62
+ (:R visemes/canonical-visemes)
63
+ (:S_Z visemes/canonical-visemes)})
64
+
65
+ (defn finite-number? [value]
66
+ (and (number? value) (js/isFinite value)))
67
+
68
+ (defn class-name [value]
69
+ (cond
70
+ (keyword? value) (name value)
71
+ (string? value) (str/lower-case value)
72
+ (nil? value) nil
73
+ :else (str/lower-case (str value))))
74
+
75
+ (defn event-classes [event]
76
+ ;; Provider events sometimes arrive with only a viseme id. Fall back to the
77
+ ;; canonical viseme class so Azure-style timelines and Web Speech fallback
78
+ ;; timelines go through the same tongue rules.
79
+ (let [from-event (into []
80
+ (keep class-name)
81
+ (concat (or (:phonemeClasses event) [])
82
+ [(:phonemeClass event)]))
83
+ fallback (into [] (keep class-name) (visemes/viseme-classes (:visemeId event)))]
84
+ (set (if (seq from-event) from-event fallback))))
85
+
86
+ (defn has-class? [event class]
87
+ (contains? (event-classes event) class))
88
+
89
+ (defn normalized-phoneme [event]
90
+ (visemes/normalize-phoneme (:phoneme event)))
91
+
92
+ (defn start-ms [event]
93
+ (state/number-or (:offsetMs event) 0))
94
+
95
+ (defn end-ms [event]
96
+ (+ (start-ms event) (max 1 (state/number-or (:durationMs event) 1))))
97
+
98
+ (defn excluded-lip-only? [event]
99
+ ;; Bilabials and labiodentals are lip/teeth actions. Keeping them out of the
100
+ ;; tongue planner prevents visible tongue motion on P/B/M/F/V closures.
101
+ (or (has-class? event "pause")
102
+ (has-class? event "bilabial")
103
+ (has-class? event "labiodental")
104
+ (has-class? event "vowel")
105
+ (has-class? event "diphthong")
106
+ (has-class? event "glide")))
107
+
108
+ (defn clean-targets [targets]
109
+ ;; Target maps are easier to reason about than a single scalar. Each phoneme
110
+ ;; can request a different mix of existing Embody tongue AUs while the snippet
111
+ ;; channel format stays unchanged.
112
+ (into {}
113
+ (keep (fn [[au target]]
114
+ (let [value (state/clamp 0 1 (state/number-or target 0))]
115
+ (when (> value intensity-eps)
116
+ [au value]))))
117
+ targets))
118
+
119
+ (defn tongue-au-targets [event]
120
+ ;; These values are performance cues, not phonetic simulation. The goal is to
121
+ ;; distinguish dental, alveolar, velar, and sibilant gestures with the AU
122
+ ;; controls Embody already exposes, while staying subtle enough for speech.
123
+ (let [phoneme (normalized-phoneme event)
124
+ viseme-id (:visemeId event)]
125
+ (clean-targets
126
+ (cond
127
+ (excluded-lip-only? event) {}
128
+
129
+ (contains? #{"TH" "DH"} phoneme)
130
+ {tongue-up-au 0.44
131
+ tongue-wide-au 0.22
132
+ tongue-tip-up-au 0.58}
133
+
134
+ (contains? #{"T" "D"} phoneme)
135
+ {tongue-up-au 0.46
136
+ tongue-tip-up-au 0.50}
137
+
138
+ (= "L" phoneme)
139
+ {tongue-up-au 0.48
140
+ tongue-tip-up-au 0.54
141
+ tongue-wide-au 0.10}
142
+
143
+ (= "N" phoneme)
144
+ {tongue-up-au 0.38
145
+ tongue-tip-up-au 0.36}
146
+
147
+ (contains? #{"K" "G" "NG"} phoneme)
148
+ {tongue-down-au 0.30
149
+ tongue-narrow-au 0.22
150
+ tongue-tip-down-au 0.24}
151
+
152
+ (contains? #{"S" "Z"} phoneme)
153
+ {tongue-up-au 0.16
154
+ tongue-narrow-au 0.42
155
+ tongue-tip-up-au 0.22}
156
+
157
+ (contains? #{"SH" "ZH"} phoneme)
158
+ {tongue-down-au 0.18
159
+ tongue-narrow-au 0.44
160
+ tongue-tip-down-au 0.22
161
+ tongue-tilt-left-au 0.08}
162
+
163
+ (contains? #{"CH" "JH"} phoneme)
164
+ {tongue-up-au 0.24
165
+ tongue-narrow-au 0.34
166
+ tongue-tip-up-au 0.28
167
+ tongue-tilt-right-au 0.08}
168
+
169
+ (= "R" phoneme)
170
+ {tongue-narrow-au 0.18
171
+ tongue-tip-down-au 0.12
172
+ tongue-tilt-left-au 0.08}
173
+
174
+ (= viseme-id (:Th visemes/canonical-visemes))
175
+ {tongue-up-au 0.40
176
+ tongue-wide-au 0.18
177
+ tongue-tip-up-au 0.52}
178
+
179
+ (= viseme-id (:T_L_D_N visemes/canonical-visemes))
180
+ {tongue-up-au 0.42
181
+ tongue-tip-up-au 0.44}
182
+
183
+ (= viseme-id (:Ch_J visemes/canonical-visemes))
184
+ {tongue-up-au 0.22
185
+ tongue-narrow-au 0.30
186
+ tongue-tip-up-au 0.24}
187
+
188
+ (= viseme-id (:K_G_H_NG visemes/canonical-visemes))
189
+ {tongue-down-au 0.26
190
+ tongue-narrow-au 0.20
191
+ tongue-tip-down-au 0.20}
192
+
193
+ (= viseme-id (:S_Z visemes/canonical-visemes))
194
+ {tongue-up-au 0.12
195
+ tongue-narrow-au 0.36
196
+ tongue-tip-up-au 0.18}
197
+
198
+ (= viseme-id (:R visemes/canonical-visemes))
199
+ {tongue-narrow-au 0.16
200
+ tongue-tip-down-au 0.10}
201
+
202
+ (has-class? event "dental")
203
+ {tongue-up-au 0.40
204
+ tongue-tip-up-au 0.48}
205
+
206
+ (has-class? event "sibilant")
207
+ {tongue-narrow-au 0.34
208
+ tongue-tip-up-au 0.16}
209
+
210
+ (has-class? event "tongue")
211
+ {tongue-up-au 0.30
212
+ tongue-tip-up-au 0.26}
213
+
214
+ (has-class? event "liquid")
215
+ {tongue-narrow-au 0.14
216
+ tongue-tip-down-au 0.10}
217
+
218
+ (contains? tongue-visemes viseme-id)
219
+ {tongue-up-au 0.16}
220
+
221
+ :else {}))))
222
+
223
+ (defn tongue-event [event]
224
+ (let [targets (tongue-au-targets event)]
225
+ (when (seq targets)
226
+ (assoc event :tongueTargets targets))))
227
+
228
+ (defn target-active? [targets au]
229
+ (> (get targets au 0) intensity-eps))
230
+
231
+ (defn opposing-targets? [current next-event]
232
+ ;; Do not collapse coronal and velar tongue gestures into one pose. A T/D/L/N
233
+ ;; target wants tongue-up/tip-up; K/G/NG wants tongue-down/tip-down. Merging
234
+ ;; those into one group drives opposing AUs at the same time and reads as a
235
+ ;; snap. Compatible stacks, such as repeated sibilants, still group.
236
+ (let [current-targets (:tongueTargets current)
237
+ next-targets (:tongueTargets next-event)]
238
+ (or (and (or (target-active? current-targets tongue-up-au)
239
+ (target-active? current-targets tongue-tip-up-au))
240
+ (or (target-active? next-targets tongue-down-au)
241
+ (target-active? next-targets tongue-tip-down-au)))
242
+ (and (or (target-active? current-targets tongue-down-au)
243
+ (target-active? current-targets tongue-tip-down-au))
244
+ (or (target-active? next-targets tongue-up-au)
245
+ (target-active? next-targets tongue-tip-up-au))))))
246
+
247
+ (defn same-tongue-group? [current next-event]
248
+ (let [gap (- (start-ms next-event) (end-ms current))]
249
+ (and (<= gap tongue-group-gap-ms)
250
+ (>= gap (- tongue-group-gap-ms))
251
+ (not (opposing-targets? current next-event)))))
252
+
253
+ (defn raw-groups [events]
254
+ ;; The grouping step makes the planner depend on a sequence of visemes rather
255
+ ;; than individual phonemes. "texts" and "strengths" should become one coda
256
+ ;; tongue gesture rather than K, T, TH, and S all firing separately.
257
+ (let [ordered (->> (into [] (keep tongue-event) events)
258
+ (sort-by :offsetMs)
259
+ vec)]
260
+ (loop [remaining ordered
261
+ current []
262
+ groups []]
263
+ (cond
264
+ (empty? remaining)
265
+ (if (seq current) (conj groups current) groups)
266
+
267
+ (empty? current)
268
+ (recur (rest remaining) [(first remaining)] groups)
269
+
270
+ (same-tongue-group? (last current) (first remaining))
271
+ (recur (rest remaining) (conj current (first remaining)) groups)
272
+
273
+ :else
274
+ (recur (rest remaining) [(first remaining)] (conj groups current))))))
275
+
276
+ (defn boosted-targets [events]
277
+ ;; Clusters with several tongue consonants get a small boost, but each AU is
278
+ ;; capped independently so stacked consonants do not create a stiff pose.
279
+ (let [raw (reduce (fn [merged event]
280
+ (merge-with max merged (:tongueTargets event)))
281
+ {}
282
+ events)
283
+ boost (min 1.12 (+ 1 (* 0.04 (dec (count events)))))]
284
+ (into {}
285
+ (keep (fn [[au target]]
286
+ (let [cap (get tongue-au-caps au 0.68)
287
+ value (state/clamp 0 cap (* target boost))]
288
+ (when (> value intensity-eps)
289
+ [au value]))))
290
+ raw)))
291
+
292
+ (defn make-group [events]
293
+ (let [start (apply min (map start-ms events))
294
+ end (apply max (map end-ms events))]
295
+ {:startMs start
296
+ :endMs end
297
+ :durationMs (max 1 (- end start))
298
+ :targets (boosted-targets events)
299
+ :events (vec events)}))
300
+
301
+ (defn tongue-groups [events]
302
+ (mapv make-group (raw-groups events)))
303
+
304
+ (defn push-frame [curve time-sec intensity]
305
+ (if (or (not (finite-number? time-sec))
306
+ (not (finite-number? intensity)))
307
+ curve
308
+ (let [frame {:time (max 0 time-sec)
309
+ :intensity (state/clamp 0 1 intensity)}
310
+ previous (last curve)]
311
+ (if (and previous (< (js/Math.abs (- (:time previous) (:time frame))) 0.001))
312
+ (assoc-in curve [(dec (count curve)) :intensity] (:intensity frame))
313
+ (conj curve frame)))))
314
+
315
+ (defn deduplicate-curve [curve]
316
+ (let [frames (vec curve)]
317
+ (if (<= (count frames) 1)
318
+ frames
319
+ (loop [remaining (rest frames)
320
+ result [(first frames)]]
321
+ (if (empty? remaining)
322
+ (vec result)
323
+ (let [curr (first remaining)
324
+ prev (last result)
325
+ intensity-changed? (> (js/Math.abs (- (:intensity curr) (:intensity prev))) intensity-eps)
326
+ time-changed? (> (js/Math.abs (- (:time curr) (:time prev))) 0.001)]
327
+ (recur (rest remaining)
328
+ (if (or intensity-changed? time-changed?)
329
+ (conj result curr)
330
+ result))))))))
331
+
332
+ (defn scaled-target [group au scale]
333
+ ;; Keep the per-AU cap after the user scale is applied. A tongueScale above
334
+ ;; 1 should make tongue motion easier to see, but it should not turn subtle
335
+ ;; width/tilt/yaw controls into full-strength snapped poses.
336
+ (let [cap (get tongue-au-caps au 0.68)]
337
+ (state/clamp 0 cap (* scale (get-in group [:targets au] 0)))))
338
+
339
+ (defn build-tongue-curve-for-au [groups au tongue-scale]
340
+ ;; Iterate every tongue group, including groups where this AU's target is 0.
341
+ ;; That gives the planner enough context to ramp an active AU down before the
342
+ ;; next opposing tongue gesture begins. Filtering to active groups only made
343
+ ;; an AU release on its own stale end time, which could leave tongue-up active
344
+ ;; while the next group was already driving tongue-down/narrow.
345
+ (let [scale (state/clamp 0 2 (state/number-or tongue-scale 1))
346
+ groups (vec groups)
347
+ has-active? (some #(> (scaled-target % au scale) intensity-eps) groups)]
348
+ (if (or (<= scale intensity-eps) (not has-active?))
349
+ []
350
+ (loop [index 0
351
+ curve []]
352
+ (if (>= index (count groups))
353
+ (->> curve (sort-by :time) deduplicate-curve vec)
354
+ (let [group (get groups index)
355
+ previous (get groups (dec index))
356
+ next-group (get groups (inc index))
357
+ previous-target (if previous (scaled-target previous au scale) 0)
358
+ next-target (if next-group (scaled-target next-group au scale) 0)
359
+ start-sec (/ (max 0 (- (:startMs group) tongue-lead-ms)) 1000)
360
+ group-start-sec (/ (:startMs group) 1000)
361
+ end-sec (/ (+ (:endMs group) tongue-release-ms) 1000)
362
+ target (scaled-target group au scale)
363
+ attack-sec (/ (min tongue-attack-ms (max 24 (* (:durationMs group) 0.35))) 1000)
364
+ hold-end-sec (/ (max (+ (:startMs group) tongue-min-hold-ms)
365
+ (- (:endMs group) (* tongue-release-ms 0.4)))
366
+ 1000)
367
+ previous-end-sec (if previous (/ (:endMs previous) 1000) 0)
368
+ next-start-sec (when next-group (/ (:startMs next-group) 1000))
369
+ starts-after-gap? (or (nil? previous)
370
+ (> (- start-sec previous-end-sec) (/ tongue-group-gap-ms 1000))
371
+ (<= previous-target intensity-eps))
372
+ curve-a (if (and (> target intensity-eps) starts-after-gap?)
373
+ (push-frame curve start-sec 0)
374
+ curve)
375
+ curve-b (if (> target intensity-eps)
376
+ (push-frame curve-a (+ group-start-sec attack-sec) target)
377
+ (if (> previous-target intensity-eps)
378
+ (push-frame curve-a group-start-sec 0)
379
+ curve-a))
380
+ connected? (and next-group
381
+ (<= (- (:startMs next-group) (:endMs group)) tongue-group-gap-ms))
382
+ release-end-sec (if (and connected?
383
+ (<= next-target intensity-eps)
384
+ next-start-sec)
385
+ (max hold-end-sec next-start-sec)
386
+ end-sec)]
387
+ (recur (inc index)
388
+ (cond
389
+ (<= target intensity-eps)
390
+ curve-b
391
+
392
+ (and connected? (> next-target intensity-eps))
393
+ (push-frame curve-b (max hold-end-sec
394
+ (/ (- (:startMs next-group) tongue-lead-ms) 1000))
395
+ target)
396
+
397
+ :else
398
+ (-> curve-b
399
+ (push-frame hold-end-sec target)
400
+ (push-frame release-end-sec 0))))))))))
401
+
402
+ (defn build-tongue-curve [events tongue-scale]
403
+ ;; Backward-compatible helper for tests and callers that only care about the
404
+ ;; original tongue-up channel.
405
+ (build-tongue-curve-for-au (tongue-groups events) tongue-up-au tongue-scale))
406
+
407
+ (defn build-tongue-curves [events tongue-scale]
408
+ (let [groups (tongue-groups events)
409
+ active-aus (->> groups
410
+ (mapcat #(keys (:targets %)))
411
+ set
412
+ (sort-by #(js/parseInt % 10)))]
413
+ (into {}
414
+ (keep (fn [au]
415
+ (let [curve (build-tongue-curve-for-au groups au tongue-scale)]
416
+ (when (seq curve)
417
+ [au curve]))))
418
+ active-aus)))