@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,162 @@
1
+ (ns polymer.tts.scheduler)
2
+
3
+ ;; TTS owns provider and audio side effects, but session callbacks should still
4
+ ;; be scheduled in one place. This scheduler queues Web Speech fallback-start
5
+ ;; callbacks and Azure audio-clock word-boundary polling. It does not synthesize
6
+ ;; speech, play audio, or call LipSync directly; the TTS agency supplies
7
+ ;; callbacks that translate scheduled facts into ordinary TTS/LipSync events.
8
+
9
+ (defn window* []
10
+ (when (exists? js/window)
11
+ js/window))
12
+
13
+ (defn now-ms []
14
+ (.now js/Date))
15
+
16
+ (defn clear-timeout! [timer]
17
+ (when timer
18
+ (when-let [window (window*)]
19
+ (.clearTimeout window timer))))
20
+
21
+ (defn clear-frame! [frame]
22
+ (when frame
23
+ (when-let [window (window*)]
24
+ (.cancelAnimationFrame window frame))))
25
+
26
+ (defn create-scheduler []
27
+ (let [start-fallbacks (atom {})
28
+ boundary-frames (atom {})
29
+ active-session (atom nil)
30
+ queue (atom [])
31
+ disposed? (atom false)]
32
+ (letfn [(enqueue! [effect]
33
+ (let [entry (assoc effect
34
+ :queueIndex (count @queue)
35
+ :queuedAt (now-ms))]
36
+ (swap! queue conj entry)
37
+ entry))
38
+
39
+ (cancel-start-fallback! [session-id]
40
+ (when-let [timer (get @start-fallbacks session-id)]
41
+ (clear-timeout! timer)
42
+ (swap! start-fallbacks dissoc session-id)))
43
+
44
+ (cancel-boundaries! [session-id]
45
+ (when-let [frame (get @boundary-frames session-id)]
46
+ (clear-frame! frame)
47
+ (swap! boundary-frames dissoc session-id)))
48
+
49
+ (request-boundary-frame! [session-id tick]
50
+ (when-let [window (window*)]
51
+ (swap! boundary-frames assoc session-id (.requestAnimationFrame window tick))))
52
+
53
+ (stop-session! [session-id]
54
+ (cancel-start-fallback! session-id)
55
+ (cancel-boundaries! session-id))]
56
+ {:start-session
57
+ (fn [session-id engine snippet-name]
58
+ (when-not @disposed?
59
+ ;; Provider callbacks can arrive late, but the scheduler records the
60
+ ;; accepted session before browser/backend work begins. Stop/reset can
61
+ ;; then cancel the active scheduled work from one place.
62
+ (reset! active-session {:sessionId session-id
63
+ :engine engine
64
+ :snippetName snippet-name})
65
+ (enqueue! {:type "ttsSessionStarted"
66
+ :agency "tts"
67
+ :sessionId session-id
68
+ :engine engine
69
+ :snippetName snippet-name})))
70
+
71
+ :schedule-start-fallback
72
+ (fn [session-id delay-ms callback]
73
+ (when-not @disposed?
74
+ (cancel-start-fallback! session-id)
75
+ (enqueue! {:type "webSpeechStartFallback"
76
+ :agency "tts"
77
+ :sessionId session-id
78
+ :delayMs delay-ms})
79
+ (when-let [window (window*)]
80
+ (swap! start-fallbacks
81
+ assoc
82
+ session-id
83
+ (.setTimeout window
84
+ (fn []
85
+ (swap! start-fallbacks dissoc session-id)
86
+ (when-not @disposed?
87
+ (callback)))
88
+ delay-ms)))))
89
+
90
+ :cancel-start-fallback
91
+ (fn [session-id]
92
+ (cancel-start-fallback! session-id))
93
+
94
+ :schedule-boundaries
95
+ (fn [session-id active-session? clock word-timings on-boundary]
96
+ (when-not @disposed?
97
+ (cancel-boundaries! session-id)
98
+ (enqueue! {:type "audioBoundaryPolling"
99
+ :agency "tts"
100
+ :sessionId session-id
101
+ :wordCount (count word-timings)})
102
+ (let [index (atom 0)
103
+ tick (atom nil)]
104
+ (reset! tick
105
+ (fn []
106
+ (if (and (not @disposed?)
107
+ (active-session? session-id)
108
+ ((:shouldContinue clock)))
109
+ (do
110
+ (let [current-time ((:currentTime clock))]
111
+ (while (and (< @index (count word-timings))
112
+ (<= (:startSec (nth word-timings @index)) (+ current-time 0.02)))
113
+ (let [boundary (nth word-timings @index)]
114
+ (on-boundary {:word (:word boundary)
115
+ :observedElapsedSec current-time})
116
+ (swap! index inc))))
117
+ (request-boundary-frame! session-id @tick))
118
+ (cancel-boundaries! session-id))))
119
+ (request-boundary-frame! session-id @tick))))
120
+
121
+ :stop-session
122
+ (fn [session-id]
123
+ (stop-session! session-id))
124
+
125
+ :stop-active-session
126
+ (fn [reason]
127
+ (when-not @disposed?
128
+ (let [session @active-session
129
+ session-id (:sessionId session)]
130
+ (when session-id
131
+ (stop-session! session-id)
132
+ (enqueue! {:type "ttsSessionStopped"
133
+ :agency "tts"
134
+ :sessionId session-id
135
+ :engine (:engine session)
136
+ :snippetName (:snippetName session)
137
+ :reason reason}))
138
+ (reset! active-session nil))))
139
+
140
+ :stop-all
141
+ (fn []
142
+ (doseq [session-id (keys @start-fallbacks)]
143
+ (cancel-start-fallback! session-id))
144
+ (doseq [session-id (keys @boundary-frames)]
145
+ (cancel-boundaries! session-id))
146
+ (reset! active-session nil))
147
+
148
+ :queue
149
+ (fn []
150
+ @queue)
151
+
152
+ :dispose
153
+ (fn []
154
+ (reset! disposed? true)
155
+ (doseq [timer (vals @start-fallbacks)]
156
+ (clear-timeout! timer))
157
+ (doseq [frame (vals @boundary-frames)]
158
+ (clear-frame! frame))
159
+ (reset! active-session nil)
160
+ (reset! start-fallbacks {})
161
+ (reset! boundary-frames {})
162
+ (reset! queue []))})))
@@ -0,0 +1,191 @@
1
+ (ns polymer.tts.state)
2
+
3
+ ;; The TTS agency state is intentionally separate from LipSync state.
4
+ ;; TTS owns provider/session facts; LipSync owns mouth planning.
5
+
6
+ (defn finite-number?
7
+ "Return true only for real numeric values that can safely enter timing math."
8
+ [value]
9
+ (and (number? value)
10
+ (not (js/isNaN value))
11
+ (js/isFinite value)))
12
+
13
+ (defn clamp
14
+ "Clamp a number into an inclusive range, using fallback when input is absent."
15
+ [minimum maximum value fallback]
16
+ (let [candidate (if (finite-number? value) value fallback)]
17
+ (min maximum (max minimum candidate))))
18
+
19
+ (defn string-or
20
+ "Keep non-blank string configuration values; treat nil/blank as missing."
21
+ [value fallback]
22
+ (if (and (string? value) (pos? (count (.trim value))))
23
+ value
24
+ fallback))
25
+
26
+ (defn bool-or
27
+ "Normalize optional booleans without treating false as missing."
28
+ [value fallback]
29
+ (if (nil? value) fallback (boolean value)))
30
+
31
+ (def default-config
32
+ ;; Defaults mirror the old Latticework TTS defaults and the current UI defaults.
33
+ {:engine "webSpeech"
34
+ :backendUrl ""
35
+ :voiceName ""
36
+ :azureVoiceName "en-US-JennyNeural"
37
+ :azureStyle ""
38
+ :azureStyleDegree nil
39
+ :rate 1
40
+ :pitch 1
41
+ :volume 1
42
+ :visualLeadMs 35
43
+ :lipsyncIntensity 1
44
+ :jawScale 1
45
+ :tongueScale 1
46
+ ;; Web Speech does not expose provider viseme timings. Polymer builds an
47
+ ;; utterance-level fallback timeline from text, then receives coarse browser
48
+ ;; word-boundary callbacks. A tight correction threshold makes that fallback
49
+ ;; clip jump at every boundary, which reads as choppy lip sync. Keep Azure
50
+ ;; tighter because Azure sends real viseme timings; keep Web Speech loose so
51
+ ;; boundaries correct only material drift.
52
+ :webSpeechDriftThresholdSec 0.35
53
+ :azureDriftThresholdSec 0.04
54
+ :azureCacheLimit 8
55
+ :debug false
56
+ ;; none | displayMedia — experimental Web Speech barge-in reference capture.
57
+ :webSpeechReferenceMode "none"
58
+ :providers nil})
59
+
60
+ (defn normalize-config
61
+ "Create the public, clamped TTS config map used by all agency decisions."
62
+ [config]
63
+ (let [input (merge default-config (or config {}))
64
+ reference-mode (string-or (:webSpeechReferenceMode input)
65
+ (:webSpeechReferenceMode default-config))]
66
+ {:engine (string-or (:engine input) (:engine default-config))
67
+ :backendUrl (string-or (:backendUrl input) (:backendUrl default-config))
68
+ :voiceName (string-or (:voiceName input) (:voiceName default-config))
69
+ :azureVoiceName (string-or (:azureVoiceName input) (:azureVoiceName default-config))
70
+ :azureStyle (string-or (:azureStyle input) (:azureStyle default-config))
71
+ :azureStyleDegree (:azureStyleDegree input)
72
+ :rate (clamp 0.5 2.0 (:rate input) (:rate default-config))
73
+ :pitch (clamp 0.5 2.0 (:pitch input) (:pitch default-config))
74
+ :volume (clamp 0 1 (:volume input) (:volume default-config))
75
+ :visualLeadMs (clamp 0 250 (:visualLeadMs input) (:visualLeadMs default-config))
76
+ :lipsyncIntensity (clamp 0 2 (:lipsyncIntensity input) (:lipsyncIntensity default-config))
77
+ :jawScale (clamp 0 2 (:jawScale input) (:jawScale default-config))
78
+ :tongueScale (clamp 0 2 (:tongueScale input) (:tongueScale default-config))
79
+ :webSpeechDriftThresholdSec (clamp 0 1 (:webSpeechDriftThresholdSec input) (:webSpeechDriftThresholdSec default-config))
80
+ :azureDriftThresholdSec (clamp 0 1 (:azureDriftThresholdSec input) (:azureDriftThresholdSec default-config))
81
+ :azureCacheLimit (int (clamp 0 64 (:azureCacheLimit input) (:azureCacheLimit default-config)))
82
+ :debug (bool-or (:debug input) false)
83
+ :webSpeechReferenceMode (if (#{"none" "displayMedia"} reference-mode)
84
+ reference-mode
85
+ "none")
86
+ :providers (:providers input)}))
87
+
88
+ (defn default-state
89
+ "Build a fresh state ledger for one TTS agency instance."
90
+ [config]
91
+ (let [normalized-config (normalize-config config)]
92
+ {:agency "tts"
93
+ :status "idle"
94
+ :engine (:engine normalized-config)
95
+ :speaking false
96
+ :currentText nil
97
+ :snippetName nil
98
+ :sessionId 0
99
+ :startedAt nil
100
+ :endedAt nil
101
+ :wordIndex 0
102
+ :webSpeechVoices []
103
+ :azureVoices []
104
+ :azureStatus "checking"
105
+ :azureStatusMessage "Azure voices have not been checked yet."
106
+ :lastPlan nil
107
+ :lastError nil
108
+ :config normalized-config}))
109
+
110
+ (defn visible-state
111
+ "Expose serializable state without private JS handles or provider functions."
112
+ [state]
113
+ (update-in state [:config] dissoc :providers))
114
+
115
+ (defn configure
116
+ "Merge public config into state and keep status/session fields untouched."
117
+ [state config]
118
+ (let [next-config (normalize-config (merge (:config state) (or config {})))]
119
+ (assoc state
120
+ :engine (:engine next-config)
121
+ :config next-config)))
122
+
123
+ (defn next-session
124
+ "Advance the session token so late provider callbacks can be ignored."
125
+ [state]
126
+ (update state :sessionId inc))
127
+
128
+ (defn record-plan
129
+ "Store provider-plan data that explains how this speech command will execute."
130
+ [state plan]
131
+ (assoc state :lastPlan plan))
132
+
133
+ (defn record-loading
134
+ "Mark speech synthesis/playback setup as active."
135
+ [state engine text snippet-name]
136
+ (assoc state
137
+ :status "loading"
138
+ :engine engine
139
+ :currentText text
140
+ :snippetName snippet-name
141
+ :speaking false
142
+ :startedAt nil
143
+ :endedAt nil
144
+ :wordIndex 0
145
+ :lastError nil))
146
+
147
+ (defn record-speaking
148
+ "Mark real audio playback as started."
149
+ [state started-at]
150
+ (assoc state
151
+ :status "speaking"
152
+ :speaking true
153
+ :startedAt started-at
154
+ :endedAt nil
155
+ :lastError nil))
156
+
157
+ (defn record-word-boundary
158
+ "Remember the latest provider word boundary without making host code poll state."
159
+ [state word-index]
160
+ (assoc state :wordIndex (inc (max 0 (int word-index)))))
161
+
162
+ (defn record-stopped
163
+ "Return the agency to idle after stop, end, or replacement."
164
+ [state ended-at]
165
+ (assoc state
166
+ :status "idle"
167
+ :speaking false
168
+ :currentText nil
169
+ :snippetName nil
170
+ :endedAt ended-at))
171
+
172
+ (defn record-error
173
+ "Move the agency into an error state while keeping the last useful context."
174
+ [state message]
175
+ (assoc state
176
+ :status "error"
177
+ :speaking false
178
+ :lastError message))
179
+
180
+ (defn record-web-speech-voices
181
+ "Store normalized browser voice choices for UI consumers."
182
+ [state voices]
183
+ (assoc state :webSpeechVoices (vec voices)))
184
+
185
+ (defn record-azure-status
186
+ "Store backend Azure availability separately from the active speech status."
187
+ [state status message voices]
188
+ (cond-> (assoc state
189
+ :azureStatus status
190
+ :azureStatusMessage message)
191
+ voices (assoc :azureVoices (vec voices))))
@@ -0,0 +1,70 @@
1
+ (ns polymer.worker
2
+ (:require [polymer.core :as polymer]))
3
+
4
+ ;; This namespace is an opt-in transport around the existing character API.
5
+ ;; It proves that Polymer can compile for a real Worker and exchange plain-data
6
+ ;; messages across that boundary. The character network still owns all
7
+ ;; cross-agency routing, planning, scheduling, and private effectors.
8
+
9
+ (defonce systems (atom {}))
10
+
11
+ (defn- post! [message]
12
+ (.postMessage js/self (clj->js message)))
13
+
14
+ (defn- post-error! [id message]
15
+ (post! {:id id
16
+ :stream "events"
17
+ :event {:type "error"
18
+ :agency "character"
19
+ :message message}}))
20
+
21
+ (defn- dispose-system! [id]
22
+ (when-let [system (get @systems id)]
23
+ (.dispose ^js system)
24
+ (swap! systems dissoc id)
25
+ true))
26
+
27
+ (defn- create-system! [id config]
28
+ (dispose-system! id)
29
+ (let [system (polymer/createCharacterAgencies (clj->js (or config {})))]
30
+ ;; Mirror only character-level events. The worker must not become a second
31
+ ;; message bus between individual agencies.
32
+ (.subscribeEvents
33
+ ^js system
34
+ #(post! {:id id
35
+ :stream "events"
36
+ :event (js->clj % :keywordize-keys true)}))
37
+ (swap! systems assoc id system)
38
+ (post! {:id id
39
+ :stream "events"
40
+ :event {:type "ready"
41
+ :agency "character"}})))
42
+
43
+ (defn- dispatch! [id message]
44
+ (if-let [system (get @systems id)]
45
+ (.dispatch ^js system (clj->js message))
46
+ (post-error! id "Unknown Polymer agency system id")))
47
+
48
+ (defn- snapshot! [id]
49
+ (if-let [system (get @systems id)]
50
+ (post! {:id id
51
+ :stream "snapshot"
52
+ :snapshot (js->clj (.snapshot ^js system) :keywordize-keys true)})
53
+ (post-error! id "Unknown Polymer agency system id")))
54
+
55
+ (defn- handle-message [message]
56
+ (let [payload (js->clj (.-data message) :keywordize-keys true)
57
+ id (or (:id payload) "character")]
58
+ (case (:type payload)
59
+ "createCharacterAgencies" (create-system! id (:config payload))
60
+ "dispatch" (dispatch! id (:message payload))
61
+ "snapshot" (snapshot! id)
62
+ "dispose" (do
63
+ (dispose-system! id)
64
+ (post! {:id id
65
+ :stream "lifecycle"
66
+ :event {:type "disposed"}}))
67
+ (post-error! id (str "Unknown worker message: " (:type payload))))))
68
+
69
+ (defn init []
70
+ (.addEventListener js/self "message" handle-message))