@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,198 @@
1
+ (ns polymer.gesture.snippet
2
+ (:require [polymer.gesture.state :as state]))
3
+
4
+ ;; Gesture snippet construction is pure. It turns LoomLarge-authored arm/hand
5
+ ;; snapshots into typed Animation snippets; Animation owns the eventual Embody
6
+ ;; runtime side effect.
7
+
8
+ (def eps 0.000001)
9
+ (def rotation-channels ["rx" "ry" "rz"])
10
+ (def position-channels ["tx" "ty" "tz"])
11
+
12
+ (defn abs-value [value]
13
+ (js/Math.abs value))
14
+
15
+ (defn radians->degrees [value]
16
+ (* value (/ 180 js/Math.PI)))
17
+
18
+ (defn degrees->radians [value]
19
+ (* value (/ js/Math.PI 180)))
20
+
21
+ (defn quaternion->euler [[x y z w]]
22
+ (let [sinr-cosp (* 2 (+ (* w x) (* y z)))
23
+ cosr-cosp (- 1 (* 2 (+ (* x x) (* y y))))
24
+ roll (js/Math.atan2 sinr-cosp cosr-cosp)
25
+ sinp (* 2 (- (* w y) (* z x)))
26
+ pitch (if (>= (abs-value sinp) 1)
27
+ (* (if (neg? sinp) -1 1) (/ js/Math.PI 2))
28
+ (js/Math.asin sinp))
29
+ siny-cosp (* 2 (+ (* w z) (* x y)))
30
+ cosy-cosp (- 1 (* 2 (+ (* y y) (* z z))))
31
+ yaw (js/Math.atan2 siny-cosp cosy-cosp)]
32
+ [roll pitch yaw]))
33
+
34
+ (defn authored-euler->channels [rotation-euler]
35
+ ;; LoomLarge stores captured gesture rotations as local-axis Euler degrees.
36
+ ;; Animation snippets also address local rx/ry/rz channels, so preserve those
37
+ ;; authored axes directly instead of round-tripping through a quaternion.
38
+ (let [pitch (:pitch rotation-euler)
39
+ yaw (:yaw rotation-euler)
40
+ roll (:roll rotation-euler)]
41
+ (when (every? state/finite-number? [pitch yaw roll])
42
+ (zipmap rotation-channels
43
+ (map degrees->radians [pitch yaw roll])))))
44
+
45
+ (defn finite-values [values expected-count]
46
+ (let [items (vec values)]
47
+ (when (and (= expected-count (count items))
48
+ (every? state/finite-number? items))
49
+ items)))
50
+
51
+ (defn transform-values [transform]
52
+ (let [rotation (or (authored-euler->channels (:rotationEuler transform))
53
+ (when-let [q (finite-values (:rotation transform) 4)]
54
+ (zipmap rotation-channels (quaternion->euler q))))
55
+ position (when-let [p (finite-values (:position transform) 3)]
56
+ (zipmap position-channels p))]
57
+ (merge rotation position)))
58
+
59
+ (defn channel-kind [channel]
60
+ (if (#{"rx" "ry" "rz"} channel) :rotation :position))
61
+
62
+ (defn target-value->channel [bone-id channel value curve]
63
+ (let [rotation? (= :rotation (channel-kind channel))
64
+ metric (if rotation? (abs-value (radians->degrees value)) (abs-value value))
65
+ target (cond->
66
+ {:type "bone"
67
+ :id bone-id
68
+ :channel channel
69
+ :scale (if (neg? value) -1 1)}
70
+ rotation? (assoc :maxDegrees metric)
71
+ (not rotation?) (assoc :maxUnits metric))]
72
+ {:target target
73
+ :keyframes curve}))
74
+
75
+ (defn static-curve [duration-sec ramp-ratio hold-ratio return?]
76
+ (let [ramp-end (min duration-sec (max 0.01 (* duration-sec ramp-ratio)))
77
+ hold-end (min duration-sec (+ ramp-end (* duration-sec hold-ratio)))]
78
+ (cond-> [{:time 0 :intensity 0}
79
+ {:time ramp-end :intensity 1}
80
+ {:time hold-end :intensity 1}]
81
+ return? (conj {:time duration-sec :intensity 0})
82
+ (not return?) (conj {:time duration-sec :intensity 1}))))
83
+
84
+ (defn static-bone-channels [gesture config]
85
+ (let [duration-sec (/ (state/number-or (:durationMs gesture) (:defaultDurationMs config)) 1000)
86
+ return? (if (contains? gesture :returnToBase)
87
+ (not (false? (:returnToBase gesture)))
88
+ (:returnToBase config))
89
+ curve (static-curve duration-sec
90
+ (:rampRatio config)
91
+ (:holdRatio config)
92
+ return?)]
93
+ (->> (:bones gesture)
94
+ (mapcat (fn [[bone-id transform]]
95
+ (->> (transform-values transform)
96
+ (keep (fn [[channel value]]
97
+ (when (> (abs-value value) eps)
98
+ (target-value->channel bone-id channel value curve)))))))
99
+ vec)))
100
+
101
+ (defn keyframe-values [gesture]
102
+ (->> (:keyframes gesture)
103
+ (mapcat (fn [keyframe]
104
+ (let [time-sec (/ (state/number-or (:timeMs keyframe) 0) 1000)]
105
+ (mapcat
106
+ (fn [[bone-id transform]]
107
+ (map (fn [[channel value]]
108
+ {:boneId bone-id
109
+ :channel channel
110
+ :time time-sec
111
+ :value value})
112
+ (transform-values transform)))
113
+ (:bones keyframe)))))
114
+ (remove #(<= (abs-value (:value %)) eps))
115
+ vec))
116
+
117
+ (defn group-keyframe-values [values]
118
+ (reduce (fn [acc value]
119
+ (update acc [(:boneId value) (:channel value)] conj value))
120
+ {}
121
+ values))
122
+
123
+ (defn dynamic-channel [[bone-id channel] values duration-sec return?]
124
+ (let [pivot (apply max-key #(abs-value (:value %)) values)
125
+ pivot-value (:value pivot)
126
+ max-value (abs-value pivot-value)
127
+ rotation? (= :rotation (channel-kind channel))
128
+ target (cond->
129
+ {:type "bone"
130
+ :id bone-id
131
+ :channel channel
132
+ :scale (if (neg? pivot-value) -1 1)}
133
+ rotation? (assoc :maxDegrees (abs-value (radians->degrees pivot-value)))
134
+ (not rotation?) (assoc :maxUnits max-value))
135
+ signed-scale (:scale target)
136
+ curve (->> values
137
+ (map (fn [value]
138
+ {:time (:time value)
139
+ :intensity (/ (* signed-scale (:value value)) max-value)}))
140
+ (sort-by :time)
141
+ vec)
142
+ with-start (if (zero? (:time (first curve)))
143
+ curve
144
+ (into [{:time 0 :intensity 0}] curve))
145
+ with-end (if return?
146
+ (conj with-start {:time duration-sec :intensity 0})
147
+ with-start)]
148
+ {:target target
149
+ :keyframes with-end}))
150
+
151
+ (defn dynamic-bone-channels [gesture config]
152
+ (let [duration-sec (/ (state/number-or (:durationMs gesture) (:defaultDurationMs config)) 1000)
153
+ return? (if (contains? gesture :returnToBase)
154
+ (not (false? (:returnToBase gesture)))
155
+ (:returnToBase config))
156
+ values (keyframe-values gesture)]
157
+ (->> (group-keyframe-values values)
158
+ (map (fn [[key values]]
159
+ (dynamic-channel key values duration-sec return?)))
160
+ vec)))
161
+
162
+ (defn gesture-name [gesture now]
163
+ (str "polymer:gesture:" (:id gesture) ":" now))
164
+
165
+ (defn build-gesture-snippet [gesture config context]
166
+ (let [now (state/now-ms)
167
+ channels (if (seq (:keyframes gesture))
168
+ (dynamic-bone-channels gesture config)
169
+ (static-bone-channels gesture config))
170
+ duration-ms (state/number-or (:durationMs gesture) (:defaultDurationMs config))
171
+ duration-sec (/ duration-ms 1000)]
172
+ (when (seq channels)
173
+ {:name (or (:name context) (gesture-name gesture now))
174
+ :channels channels
175
+ :maxTime duration-sec
176
+ :loop (boolean (:loop gesture))
177
+ :snippetPriority (int (state/number-or (:priority gesture) (:priority config)))
178
+ :snippetPlaybackRate 1
179
+ :snippetIntensityScale (state/number-or (:intensity context) (:intensity config))
180
+ :metadata {:agency "gesture"
181
+ :gestureId (:id gesture)
182
+ :gestureName (:name gesture)
183
+ :description (:description gesture)
184
+ :emoji (:emoji gesture)
185
+ :trigger (:trigger context)
186
+ :intent (:intent context)
187
+ :goal (:goal context)
188
+ :selection (:selection context)
189
+ :scope (:scope gesture)
190
+ :captureSource (:captureSource gesture)
191
+ :sourceText (:sourceText gesture)
192
+ :textRepresentation (:textRepresentation gesture)
193
+ :createdAt (:createdAt gesture)
194
+ :updatedAt (:updatedAt gesture)
195
+ :durationMs duration-ms
196
+ :tags (:tags gesture)
197
+ :affectedBones (:affectedBones gesture)
198
+ :affectedAUs (:affectedAUs gesture)}})))
@@ -0,0 +1,273 @@
1
+ (ns polymer.gesture.state
2
+ (:require [clojure.string :as str]))
3
+
4
+ ;; Gesture state is the local ledger for authored arm/hand gesticulations. The
5
+ ;; saved gesture library may come from a host, but Polymer keeps only data here:
6
+ ;; no scene handles, storage, host UI state, or animation runtime objects.
7
+
8
+ (def default-config
9
+ {:enabled true
10
+ :intensity 1
11
+ :priority 45
12
+ :defaultDurationMs 700
13
+ :cooldownMs 0
14
+ :rampRatio 0.25
15
+ :holdRatio 0.55
16
+ :returnToBase true
17
+ :replaceActive true
18
+ :maxActive 1})
19
+
20
+ (def default-state
21
+ {:agency "gesture"
22
+ :gestures {}
23
+ :emojiMappings {}
24
+ :activeSnippets {}
25
+ :lastPlayedByGesture {}
26
+ :scheduledCount 0
27
+ :removedCount 0
28
+ :config default-config
29
+ :lastEvent nil})
30
+
31
+ (defn now-ms []
32
+ (.now js/Date))
33
+
34
+ (defn finite-number? [value]
35
+ (and (number? value) (js/isFinite value)))
36
+
37
+ (defn clamp [lo hi value]
38
+ (-> value (max lo) (min hi)))
39
+
40
+ (defn number-or [value fallback]
41
+ (if (finite-number? value) value fallback))
42
+
43
+ (defn js-data [value]
44
+ (cond
45
+ (nil? value) nil
46
+ (or (map? value) (sequential? value)) value
47
+ :else (js->clj value :keywordize-keys true)))
48
+
49
+ (defn string-key [value]
50
+ (cond
51
+ (nil? value) nil
52
+ (keyword? value) (name value)
53
+ :else (str value)))
54
+
55
+ (defn clean-string [value]
56
+ (when-let [key (string-key value)]
57
+ (let [trimmed (str/trim key)]
58
+ (when (pos? (count trimmed)) trimmed))))
59
+
60
+ (defn clean-strings [values]
61
+ (->> (or values [])
62
+ (keep clean-string)
63
+ distinct
64
+ vec))
65
+
66
+ (defn sanitize-config [config]
67
+ (let [merged (merge default-config (or config {}))]
68
+ {:enabled (not (false? (:enabled merged)))
69
+ :intensity (clamp 0 2 (number-or (:intensity merged) (:intensity default-config)))
70
+ :priority (int (clamp -1000 1000 (number-or (:priority merged) (:priority default-config))))
71
+ :defaultDurationMs (clamp 80 10000 (number-or (:defaultDurationMs merged)
72
+ (:defaultDurationMs default-config)))
73
+ :cooldownMs (clamp 0 10000 (number-or (:cooldownMs merged) (:cooldownMs default-config)))
74
+ :rampRatio (clamp 0.05 0.8 (number-or (:rampRatio merged) (:rampRatio default-config)))
75
+ :holdRatio (clamp 0 0.9 (number-or (:holdRatio merged) (:holdRatio default-config)))
76
+ :returnToBase (not (false? (:returnToBase merged)))
77
+ :replaceActive (not (false? (:replaceActive merged)))
78
+ :maxActive (int (clamp 1 12 (number-or (:maxActive merged) (:maxActive default-config))))}))
79
+
80
+ (defn normalize-bones [bones]
81
+ (into {}
82
+ (keep (fn [[bone-id transform]]
83
+ (when-let [id (clean-string bone-id)]
84
+ [id transform])))
85
+ (or bones {})))
86
+
87
+ (defn normalize-keyframe [keyframe]
88
+ (let [time-ms (number-or (:timeMs keyframe) 0)
89
+ bones (normalize-bones (:bones keyframe))]
90
+ (when (seq bones)
91
+ {:timeMs (max 0 time-ms)
92
+ :bones bones})))
93
+
94
+ (defn normalize-keyframes [keyframes]
95
+ (->> (or keyframes [])
96
+ (keep normalize-keyframe)
97
+ (sort-by :timeMs)
98
+ vec))
99
+
100
+ (defn keyframe-bone-names [keyframes]
101
+ (->> keyframes
102
+ (mapcat #(keys (:bones %)))
103
+ (keep clean-string)
104
+ distinct
105
+ vec))
106
+
107
+ (defn normalize-gesture [fallback-id gesture]
108
+ (let [input (or gesture {})
109
+ id (clean-string (or (:id input) fallback-id))
110
+ bones (normalize-bones (:bones input))
111
+ keyframes (normalize-keyframes (:keyframes input))
112
+ duration-ms (when (contains? input :durationMs)
113
+ (clamp 80 10000
114
+ (number-or (:durationMs input)
115
+ (:defaultDurationMs default-config))))
116
+ priority (when (contains? input :priority)
117
+ (int (clamp -1000 1000
118
+ (number-or (:priority input)
119
+ (:priority default-config)))))
120
+ affected-bones (or (seq (clean-strings (:affectedBones input)))
121
+ (seq (keys bones))
122
+ (seq (keyframe-bone-names keyframes)))]
123
+ (when id
124
+ (-> input
125
+ (assoc :id id
126
+ :name (or (clean-string (:name input)) id)
127
+ :affectedBones (vec affected-bones)
128
+ :affectedAUs (clean-strings (:affectedAUs input))
129
+ :bones bones)
130
+ (cond->
131
+ duration-ms (assoc :durationMs duration-ms)
132
+ priority (assoc :priority priority)
133
+ (seq keyframes) (assoc :keyframes keyframes)
134
+ (clean-string (:description input)) (assoc :description (clean-string (:description input)))
135
+ (clean-string (:captureSource input)) (assoc :captureSource (clean-string (:captureSource input)))
136
+ (clean-string (:sourceText input)) (assoc :sourceText (clean-string (:sourceText input)))
137
+ (clean-string (:textRepresentation input)) (assoc :textRepresentation (clean-string (:textRepresentation input)))
138
+ (clean-string (:emoji input)) (assoc :emoji (clean-string (:emoji input)))
139
+ (seq (:tags input)) (assoc :tags (clean-strings (:tags input))))))))
140
+
141
+ (defn normalize-gesture-library [gestures]
142
+ (into {}
143
+ (keep (fn [[gesture-id gesture]]
144
+ (when-let [normalized (normalize-gesture gesture-id gesture)]
145
+ [(:id normalized) normalized])))
146
+ (or gestures {})))
147
+
148
+ (defn gesture-emoji-mappings [gestures]
149
+ (into {}
150
+ (keep (fn [[gesture-id gesture]]
151
+ (when-let [emoji (clean-string (:emoji gesture))]
152
+ [emoji gesture-id])))
153
+ gestures))
154
+
155
+ (defn normalize-emoji-mappings [emoji-mappings gestures]
156
+ (merge
157
+ (gesture-emoji-mappings gestures)
158
+ (into {}
159
+ (keep (fn [[emoji gesture-id]]
160
+ (when-let [clean-emoji (clean-string emoji)]
161
+ (when-let [clean-id (clean-string gesture-id)]
162
+ [clean-emoji clean-id]))))
163
+ (or emoji-mappings {}))))
164
+
165
+ (defn library-from-config [config]
166
+ (normalize-gesture-library (or (:gestures config)
167
+ (:characterGestures config)
168
+ (:gestureLibrary config))))
169
+
170
+ (defn emoji-mappings-from-config [config gestures]
171
+ (normalize-emoji-mappings (or (:emojiMappings config)
172
+ (:gestureEmojiMappings config))
173
+ gestures))
174
+
175
+ (defn config->state [config]
176
+ (let [data (or (js-data config) {})
177
+ gestures (library-from-config data)]
178
+ (assoc default-state
179
+ :gestures gestures
180
+ :emojiMappings (emoji-mappings-from-config data gestures)
181
+ :config (sanitize-config data))))
182
+
183
+ (defn configure [state config]
184
+ (let [data (or (js-data config) {})
185
+ configured (update state :config #(sanitize-config (merge % data)))]
186
+ (if (or (contains? data :gestures)
187
+ (contains? data :characterGestures)
188
+ (contains? data :gestureLibrary)
189
+ (contains? data :emojiMappings)
190
+ (contains? data :gestureEmojiMappings))
191
+ (let [gestures (if (or (contains? data :gestures)
192
+ (contains? data :characterGestures)
193
+ (contains? data :gestureLibrary))
194
+ (library-from-config data)
195
+ (:gestures configured))
196
+ mapping-input (if (or (contains? data :gestures)
197
+ (contains? data :characterGestures)
198
+ (contains? data :gestureLibrary))
199
+ (or (:emojiMappings data) (:gestureEmojiMappings data))
200
+ (or (:emojiMappings data)
201
+ (:gestureEmojiMappings data)
202
+ (:emojiMappings configured)))]
203
+ (assoc configured
204
+ :gestures gestures
205
+ :emojiMappings (normalize-emoji-mappings mapping-input gestures)))
206
+ configured)))
207
+
208
+ (defn load-gestures [state gestures emoji-mappings loaded-at]
209
+ (let [library (normalize-gesture-library (or (js-data gestures) {}))]
210
+ (-> state
211
+ (assoc :gestures library
212
+ :emojiMappings (normalize-emoji-mappings (js-data emoji-mappings) library))
213
+ (assoc :lastEvent {:type "gestureLibraryUpdated"
214
+ :gestureCount (count library)
215
+ :emojiCount (count (normalize-emoji-mappings (js-data emoji-mappings) library))
216
+ :at loaded-at}))))
217
+
218
+ (defn gesture-ready? [state gesture-id now]
219
+ (let [cooldown-ms (get-in state [:config :cooldownMs])
220
+ last-played (get-in state [:lastPlayedByGesture gesture-id] 0)]
221
+ (or (zero? cooldown-ms)
222
+ (>= (- now last-played) cooldown-ms))))
223
+
224
+ (defn active-names [state]
225
+ (vec (keys (:activeSnippets state))))
226
+
227
+ (defn active-names-for-gesture [state gesture-id]
228
+ (->> (:activeSnippets state)
229
+ (keep (fn [[name active]]
230
+ (when (= gesture-id (:gestureId active)) name)))
231
+ vec))
232
+
233
+ (defn record-schedule [state gesture snippet scheduled-at]
234
+ (let [name (:name snippet)
235
+ gesture-id (:id gesture)]
236
+ (-> state
237
+ (assoc-in [:activeSnippets name]
238
+ {:name name
239
+ :gestureId gesture-id
240
+ :gestureName (:name gesture)
241
+ :emoji (:emoji gesture)
242
+ :scope (:scope gesture)
243
+ :affectedBones (:affectedBones gesture)
244
+ :maxTime (:maxTime snippet)
245
+ :loop (:loop snippet)
246
+ :priority (:snippetPriority snippet)
247
+ :trigger (get-in snippet [:metadata :trigger])
248
+ :intent (get-in snippet [:metadata :intent])
249
+ :scheduledAt scheduled-at})
250
+ (assoc-in [:lastPlayedByGesture gesture-id] scheduled-at)
251
+ (update :scheduledCount inc)
252
+ (assoc :lastEvent {:type "gestureScheduled"
253
+ :name name
254
+ :gestureId gesture-id
255
+ :at scheduled-at}))))
256
+
257
+ (defn record-remove [state name reason removed-at]
258
+ (-> state
259
+ (update :activeSnippets dissoc name)
260
+ (update :removedCount inc)
261
+ (assoc :lastEvent {:type "gestureRemoved"
262
+ :name name
263
+ :reason reason
264
+ :at removed-at})))
265
+
266
+ (defn visible-state-map [state]
267
+ (assoc state
268
+ :gestureCount (count (:gestures state))
269
+ :emojiCount (count (:emojiMappings state))
270
+ :gestures (vec (keys (:gestures state)))))
271
+
272
+ (defn visible-state [state]
273
+ (clj->js (visible-state-map state)))