@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.
- package/README.md +226 -0
- package/dist/index.d.ts +2603 -0
- package/dist/index.js +1031 -0
- package/dist/test.cjs +1519 -0
- package/dist/worker/agency-worker.js +972 -0
- package/dist/worker/manifest.edn +1 -0
- package/package.json +57 -0
- package/shadow-cljs.edn +173 -0
- package/src/polymer/animation/agency.cljs +84 -0
- package/src/polymer/animation/domain.cljs +110 -0
- package/src/polymer/animation/planner.cljs +88 -0
- package/src/polymer/animation/runtime.cljs +137 -0
- package/src/polymer/animation/scheduler.cljs +178 -0
- package/src/polymer/animation/service.cljs +981 -0
- package/src/polymer/animation/state.cljs +112 -0
- package/src/polymer/blink/agency.cljs +131 -0
- package/src/polymer/blink/planner.cljs +115 -0
- package/src/polymer/blink/scheduler.cljs +90 -0
- package/src/polymer/blink/snippet.cljs +50 -0
- package/src/polymer/blink/state.cljs +102 -0
- package/src/polymer/camera_context/agency.cljs +132 -0
- package/src/polymer/camera_context/domain.cljs +186 -0
- package/src/polymer/camera_context/planner.cljs +50 -0
- package/src/polymer/camera_context/scheduler.cljs +113 -0
- package/src/polymer/camera_context/state.cljs +121 -0
- package/src/polymer/character.cljs +471 -0
- package/src/polymer/conversation/agency.cljs +83 -0
- package/src/polymer/conversation/domain.cljs +35 -0
- package/src/polymer/conversation/effector.cljs +130 -0
- package/src/polymer/conversation/planner.cljs +178 -0
- package/src/polymer/conversation/scheduler.cljs +202 -0
- package/src/polymer/conversation/service.cljs +329 -0
- package/src/polymer/conversation/state.cljs +186 -0
- package/src/polymer/core.cljs +172 -0
- package/src/polymer/eye_head/agency.cljs +75 -0
- package/src/polymer/eye_head/domain.cljs +133 -0
- package/src/polymer/eye_head/goap.cljs +106 -0
- package/src/polymer/eye_head/scheduler.cljs +176 -0
- package/src/polymer/eye_head/service.cljs +259 -0
- package/src/polymer/eye_head/snippet.cljs +92 -0
- package/src/polymer/eye_head/state.cljs +167 -0
- package/src/polymer/gaze/agency.cljs +85 -0
- package/src/polymer/gaze/domain.cljs +211 -0
- package/src/polymer/gaze/goap.cljs +208 -0
- package/src/polymer/gaze/scheduler.cljs +184 -0
- package/src/polymer/gaze/state.cljs +202 -0
- package/src/polymer/gesture/agency.cljs +185 -0
- package/src/polymer/gesture/goap.cljs +461 -0
- package/src/polymer/gesture/scheduler.cljs +122 -0
- package/src/polymer/gesture/snippet.cljs +198 -0
- package/src/polymer/gesture/state.cljs +273 -0
- package/src/polymer/lipsync/agency.cljs +593 -0
- package/src/polymer/lipsync/articulation/jaw.cljs +258 -0
- package/src/polymer/lipsync/articulation/snippet.cljs +605 -0
- package/src/polymer/lipsync/articulation/tongue.cljs +418 -0
- package/src/polymer/lipsync/articulation/visemes.cljs +429 -0
- package/src/polymer/lipsync/goap.cljs +132 -0
- package/src/polymer/lipsync/pronunciation/effector.cljs +51 -0
- package/src/polymer/lipsync/scheduler.cljs +302 -0
- package/src/polymer/lipsync/state.cljs +202 -0
- package/src/polymer/nlp/phonemes.cljs +164 -0
- package/src/polymer/prosodic/agency.cljs +196 -0
- package/src/polymer/prosodic/goap.cljs +122 -0
- package/src/polymer/prosodic/scheduler.cljs +99 -0
- package/src/polymer/prosodic/snippet.cljs +106 -0
- package/src/polymer/prosodic/state.cljs +139 -0
- package/src/polymer/runtime/embody.cljs +117 -0
- package/src/polymer/runtime/embody_wasm.cljs +177 -0
- package/src/polymer/runtime/rust_embody_host.cljs +645 -0
- package/src/polymer/society.cljs +239 -0
- package/src/polymer/stream.cljs +130 -0
- package/src/polymer/transcription/agency.cljs +69 -0
- package/src/polymer/transcription/domain.cljs +68 -0
- package/src/polymer/transcription/effector.cljs +611 -0
- package/src/polymer/transcription/planner.cljs +203 -0
- package/src/polymer/transcription/scheduler.cljs +225 -0
- package/src/polymer/transcription/service.cljs +209 -0
- package/src/polymer/transcription/state.cljs +239 -0
- package/src/polymer/tts/agency.cljs +503 -0
- package/src/polymer/tts/azure.cljs +290 -0
- package/src/polymer/tts/planner.cljs +178 -0
- package/src/polymer/tts/runtime.cljs +624 -0
- package/src/polymer/tts/scheduler.cljs +162 -0
- package/src/polymer/tts/state.cljs +191 -0
- 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)))
|