@czap/core 0.1.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.
Files changed (372) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/dist/addressed-digest.d.ts +15 -0
  4. package/dist/addressed-digest.d.ts.map +1 -0
  5. package/dist/addressed-digest.js +35 -0
  6. package/dist/addressed-digest.js.map +1 -0
  7. package/dist/animation.d.ts +46 -0
  8. package/dist/animation.d.ts.map +1 -0
  9. package/dist/animation.js +70 -0
  10. package/dist/animation.js.map +1 -0
  11. package/dist/assembly.d.ts +25 -0
  12. package/dist/assembly.d.ts.map +1 -0
  13. package/dist/assembly.js +58 -0
  14. package/dist/assembly.js.map +1 -0
  15. package/dist/av-bridge.d.ts +74 -0
  16. package/dist/av-bridge.d.ts.map +1 -0
  17. package/dist/av-bridge.js +107 -0
  18. package/dist/av-bridge.js.map +1 -0
  19. package/dist/av-renderer.d.ts +56 -0
  20. package/dist/av-renderer.d.ts.map +1 -0
  21. package/dist/av-renderer.js +65 -0
  22. package/dist/av-renderer.js.map +1 -0
  23. package/dist/blend.d.ts +61 -0
  24. package/dist/blend.d.ts.map +1 -0
  25. package/dist/blend.js +100 -0
  26. package/dist/blend.js.map +1 -0
  27. package/dist/boundary.d.ts +154 -0
  28. package/dist/boundary.d.ts.map +1 -0
  29. package/dist/boundary.js +269 -0
  30. package/dist/boundary.js.map +1 -0
  31. package/dist/brands.d.ts +63 -0
  32. package/dist/brands.d.ts.map +1 -0
  33. package/dist/brands.js +31 -0
  34. package/dist/brands.js.map +1 -0
  35. package/dist/caps.d.ts +49 -0
  36. package/dist/caps.d.ts.map +1 -0
  37. package/dist/caps.js +73 -0
  38. package/dist/caps.js.map +1 -0
  39. package/dist/capsule.d.ts +77 -0
  40. package/dist/capsule.d.ts.map +1 -0
  41. package/dist/capsule.js +18 -0
  42. package/dist/capsule.js.map +1 -0
  43. package/dist/capsules/boundary-evaluate.d.ts +28 -0
  44. package/dist/capsules/boundary-evaluate.d.ts.map +1 -0
  45. package/dist/capsules/boundary-evaluate.js +117 -0
  46. package/dist/capsules/boundary-evaluate.js.map +1 -0
  47. package/dist/capsules/canonical-cbor.d.ts +13 -0
  48. package/dist/capsules/canonical-cbor.d.ts.map +1 -0
  49. package/dist/capsules/canonical-cbor.js +60 -0
  50. package/dist/capsules/canonical-cbor.js.map +1 -0
  51. package/dist/capsules/token-buffer.d.ts +24 -0
  52. package/dist/capsules/token-buffer.d.ts.map +1 -0
  53. package/dist/capsules/token-buffer.js +53 -0
  54. package/dist/capsules/token-buffer.js.map +1 -0
  55. package/dist/capture.d.ts +40 -0
  56. package/dist/capture.d.ts.map +1 -0
  57. package/dist/capture.js +10 -0
  58. package/dist/capture.js.map +1 -0
  59. package/dist/cbor.d.ts +33 -0
  60. package/dist/cbor.d.ts.map +1 -0
  61. package/dist/cbor.js +179 -0
  62. package/dist/cbor.js.map +1 -0
  63. package/dist/cell.d.ts +53 -0
  64. package/dist/cell.d.ts.map +1 -0
  65. package/dist/cell.js +83 -0
  66. package/dist/cell.js.map +1 -0
  67. package/dist/codec.d.ts +30 -0
  68. package/dist/codec.d.ts.map +1 -0
  69. package/dist/codec.js +25 -0
  70. package/dist/codec.js.map +1 -0
  71. package/dist/component.d.ts +52 -0
  72. package/dist/component.d.ts.map +1 -0
  73. package/dist/component.js +44 -0
  74. package/dist/component.js.map +1 -0
  75. package/dist/composable.d.ts +76 -0
  76. package/dist/composable.d.ts.map +1 -0
  77. package/dist/composable.js +221 -0
  78. package/dist/composable.js.map +1 -0
  79. package/dist/compositor-pool.d.ts +74 -0
  80. package/dist/compositor-pool.d.ts.map +1 -0
  81. package/dist/compositor-pool.js +119 -0
  82. package/dist/compositor-pool.js.map +1 -0
  83. package/dist/compositor.d.ts +90 -0
  84. package/dist/compositor.d.ts.map +1 -0
  85. package/dist/compositor.js +278 -0
  86. package/dist/compositor.js.map +1 -0
  87. package/dist/config.d.ts +72 -0
  88. package/dist/config.d.ts.map +1 -0
  89. package/dist/config.js +97 -0
  90. package/dist/config.js.map +1 -0
  91. package/dist/dag.d.ts +251 -0
  92. package/dist/dag.d.ts.map +1 -0
  93. package/dist/dag.js +450 -0
  94. package/dist/dag.js.map +1 -0
  95. package/dist/defaults.d.ts +45 -0
  96. package/dist/defaults.d.ts.map +1 -0
  97. package/dist/defaults.js +45 -0
  98. package/dist/defaults.js.map +1 -0
  99. package/dist/derived.d.ts +34 -0
  100. package/dist/derived.d.ts.map +1 -0
  101. package/dist/derived.js +101 -0
  102. package/dist/derived.js.map +1 -0
  103. package/dist/diagnostics.d.ts +77 -0
  104. package/dist/diagnostics.d.ts.map +1 -0
  105. package/dist/diagnostics.js +122 -0
  106. package/dist/diagnostics.js.map +1 -0
  107. package/dist/dirty.d.ts +55 -0
  108. package/dist/dirty.d.ts.map +1 -0
  109. package/dist/dirty.js +80 -0
  110. package/dist/dirty.js.map +1 -0
  111. package/dist/easing.d.ts +55 -0
  112. package/dist/easing.d.ts.map +1 -0
  113. package/dist/easing.js +291 -0
  114. package/dist/easing.js.map +1 -0
  115. package/dist/ecs.d.ts +105 -0
  116. package/dist/ecs.d.ts.map +1 -0
  117. package/dist/ecs.js +245 -0
  118. package/dist/ecs.js.map +1 -0
  119. package/dist/fnv.d.ts +14 -0
  120. package/dist/fnv.d.ts.map +1 -0
  121. package/dist/fnv.js +28 -0
  122. package/dist/fnv.js.map +1 -0
  123. package/dist/frame-budget.d.ts +73 -0
  124. package/dist/frame-budget.d.ts.map +1 -0
  125. package/dist/frame-budget.js +114 -0
  126. package/dist/frame-budget.js.map +1 -0
  127. package/dist/gen-frame.d.ts +102 -0
  128. package/dist/gen-frame.d.ts.map +1 -0
  129. package/dist/gen-frame.js +121 -0
  130. package/dist/gen-frame.js.map +1 -0
  131. package/dist/harness/arbitrary-from-schema.d.ts +28 -0
  132. package/dist/harness/arbitrary-from-schema.d.ts.map +1 -0
  133. package/dist/harness/arbitrary-from-schema.js +262 -0
  134. package/dist/harness/arbitrary-from-schema.js.map +1 -0
  135. package/dist/harness/cached-projection.d.ts +19 -0
  136. package/dist/harness/cached-projection.d.ts.map +1 -0
  137. package/dist/harness/cached-projection.js +39 -0
  138. package/dist/harness/cached-projection.js.map +1 -0
  139. package/dist/harness/index.d.ts +16 -0
  140. package/dist/harness/index.d.ts.map +1 -0
  141. package/dist/harness/index.js +15 -0
  142. package/dist/harness/index.js.map +1 -0
  143. package/dist/harness/policy-gate.d.ts +18 -0
  144. package/dist/harness/policy-gate.d.ts.map +1 -0
  145. package/dist/harness/policy-gate.js +46 -0
  146. package/dist/harness/policy-gate.js.map +1 -0
  147. package/dist/harness/pure-transform.d.ts +42 -0
  148. package/dist/harness/pure-transform.d.ts.map +1 -0
  149. package/dist/harness/pure-transform.js +76 -0
  150. package/dist/harness/pure-transform.js.map +1 -0
  151. package/dist/harness/receipted-mutation.d.ts +23 -0
  152. package/dist/harness/receipted-mutation.d.ts.map +1 -0
  153. package/dist/harness/receipted-mutation.js +52 -0
  154. package/dist/harness/receipted-mutation.js.map +1 -0
  155. package/dist/harness/scene-composition.d.ts +19 -0
  156. package/dist/harness/scene-composition.d.ts.map +1 -0
  157. package/dist/harness/scene-composition.js +47 -0
  158. package/dist/harness/scene-composition.js.map +1 -0
  159. package/dist/harness/site-adapter.d.ts +18 -0
  160. package/dist/harness/site-adapter.d.ts.map +1 -0
  161. package/dist/harness/site-adapter.js +38 -0
  162. package/dist/harness/site-adapter.js.map +1 -0
  163. package/dist/harness/state-machine.d.ts +19 -0
  164. package/dist/harness/state-machine.d.ts.map +1 -0
  165. package/dist/harness/state-machine.js +44 -0
  166. package/dist/harness/state-machine.js.map +1 -0
  167. package/dist/hlc.d.ts +99 -0
  168. package/dist/hlc.d.ts.map +1 -0
  169. package/dist/hlc.js +219 -0
  170. package/dist/hlc.js.map +1 -0
  171. package/dist/index.d.ts +104 -0
  172. package/dist/index.d.ts.map +1 -0
  173. package/dist/index.js +137 -0
  174. package/dist/index.js.map +1 -0
  175. package/dist/interpolate.d.ts +14 -0
  176. package/dist/interpolate.d.ts.map +1 -0
  177. package/dist/interpolate.js +31 -0
  178. package/dist/interpolate.js.map +1 -0
  179. package/dist/live-cell.d.ts +46 -0
  180. package/dist/live-cell.d.ts.map +1 -0
  181. package/dist/live-cell.js +154 -0
  182. package/dist/live-cell.js.map +1 -0
  183. package/dist/op.d.ts +58 -0
  184. package/dist/op.d.ts.map +1 -0
  185. package/dist/op.js +171 -0
  186. package/dist/op.js.map +1 -0
  187. package/dist/plan.d.ts +195 -0
  188. package/dist/plan.d.ts.map +1 -0
  189. package/dist/plan.js +211 -0
  190. package/dist/plan.js.map +1 -0
  191. package/dist/protocol.d.ts +33 -0
  192. package/dist/protocol.d.ts.map +1 -0
  193. package/dist/protocol.js +10 -0
  194. package/dist/protocol.js.map +1 -0
  195. package/dist/quantizer-types.d.ts +28 -0
  196. package/dist/quantizer-types.d.ts.map +1 -0
  197. package/dist/quantizer-types.js +9 -0
  198. package/dist/quantizer-types.js.map +1 -0
  199. package/dist/receipt.d.ts +294 -0
  200. package/dist/receipt.d.ts.map +1 -0
  201. package/dist/receipt.js +352 -0
  202. package/dist/receipt.js.map +1 -0
  203. package/dist/runtime-coordinator.d.ts +75 -0
  204. package/dist/runtime-coordinator.d.ts.map +1 -0
  205. package/dist/runtime-coordinator.js +149 -0
  206. package/dist/runtime-coordinator.js.map +1 -0
  207. package/dist/scheduler.d.ts +58 -0
  208. package/dist/scheduler.d.ts.map +1 -0
  209. package/dist/scheduler.js +109 -0
  210. package/dist/scheduler.js.map +1 -0
  211. package/dist/ship-capsule.d.ts +54 -0
  212. package/dist/ship-capsule.d.ts.map +1 -0
  213. package/dist/ship-capsule.js +142 -0
  214. package/dist/ship-capsule.js.map +1 -0
  215. package/dist/ship-manifest.d.ts +45 -0
  216. package/dist/ship-manifest.d.ts.map +1 -0
  217. package/dist/ship-manifest.js +175 -0
  218. package/dist/ship-manifest.js.map +1 -0
  219. package/dist/signal.d.ts +149 -0
  220. package/dist/signal.d.ts.map +1 -0
  221. package/dist/signal.js +277 -0
  222. package/dist/signal.js.map +1 -0
  223. package/dist/speculative.d.ts +67 -0
  224. package/dist/speculative.d.ts.map +1 -0
  225. package/dist/speculative.js +139 -0
  226. package/dist/speculative.js.map +1 -0
  227. package/dist/store.d.ts +39 -0
  228. package/dist/store.d.ts.map +1 -0
  229. package/dist/store.js +42 -0
  230. package/dist/store.js.map +1 -0
  231. package/dist/style.d.ts +119 -0
  232. package/dist/style.d.ts.map +1 -0
  233. package/dist/style.js +168 -0
  234. package/dist/style.js.map +1 -0
  235. package/dist/testing.d.ts +14 -0
  236. package/dist/testing.d.ts.map +1 -0
  237. package/dist/testing.js +14 -0
  238. package/dist/testing.js.map +1 -0
  239. package/dist/theme.d.ts +78 -0
  240. package/dist/theme.d.ts.map +1 -0
  241. package/dist/theme.js +109 -0
  242. package/dist/theme.js.map +1 -0
  243. package/dist/timeline.d.ts +45 -0
  244. package/dist/timeline.d.ts.map +1 -0
  245. package/dist/timeline.js +101 -0
  246. package/dist/timeline.js.map +1 -0
  247. package/dist/token-buffer.d.ts +43 -0
  248. package/dist/token-buffer.d.ts.map +1 -0
  249. package/dist/token-buffer.js +112 -0
  250. package/dist/token-buffer.js.map +1 -0
  251. package/dist/token.d.ts +107 -0
  252. package/dist/token.d.ts.map +1 -0
  253. package/dist/token.js +143 -0
  254. package/dist/token.js.map +1 -0
  255. package/dist/tuple.d.ts +16 -0
  256. package/dist/tuple.d.ts.map +1 -0
  257. package/dist/tuple.js +16 -0
  258. package/dist/tuple.js.map +1 -0
  259. package/dist/type-utils.d.ts +41 -0
  260. package/dist/type-utils.d.ts.map +1 -0
  261. package/dist/type-utils.js +10 -0
  262. package/dist/type-utils.js.map +1 -0
  263. package/dist/typed-ref.d.ts +50 -0
  264. package/dist/typed-ref.d.ts.map +1 -0
  265. package/dist/typed-ref.js +59 -0
  266. package/dist/typed-ref.js.map +1 -0
  267. package/dist/ui-quality.d.ts +50 -0
  268. package/dist/ui-quality.d.ts.map +1 -0
  269. package/dist/ui-quality.js +64 -0
  270. package/dist/ui-quality.js.map +1 -0
  271. package/dist/validation-error.d.ts +25 -0
  272. package/dist/validation-error.d.ts.map +1 -0
  273. package/dist/validation-error.js +32 -0
  274. package/dist/validation-error.js.map +1 -0
  275. package/dist/vector-clock.d.ts +46 -0
  276. package/dist/vector-clock.d.ts.map +1 -0
  277. package/dist/vector-clock.js +91 -0
  278. package/dist/vector-clock.js.map +1 -0
  279. package/dist/video.d.ts +62 -0
  280. package/dist/video.d.ts.map +1 -0
  281. package/dist/video.js +59 -0
  282. package/dist/video.js.map +1 -0
  283. package/dist/wasm-dispatch.d.ts +52 -0
  284. package/dist/wasm-dispatch.d.ts.map +1 -0
  285. package/dist/wasm-dispatch.js +204 -0
  286. package/dist/wasm-dispatch.js.map +1 -0
  287. package/dist/wasm-fallback.d.ts +19 -0
  288. package/dist/wasm-fallback.d.ts.map +1 -0
  289. package/dist/wasm-fallback.js +93 -0
  290. package/dist/wasm-fallback.js.map +1 -0
  291. package/dist/wire.d.ts +49 -0
  292. package/dist/wire.d.ts.map +1 -0
  293. package/dist/wire.js +201 -0
  294. package/dist/wire.js.map +1 -0
  295. package/dist/zap.d.ts +42 -0
  296. package/dist/zap.d.ts.map +1 -0
  297. package/dist/zap.js +172 -0
  298. package/dist/zap.js.map +1 -0
  299. package/package.json +71 -0
  300. package/src/addressed-digest.ts +48 -0
  301. package/src/animation.ts +103 -0
  302. package/src/assembly.ts +76 -0
  303. package/src/av-bridge.ts +161 -0
  304. package/src/av-renderer.ts +118 -0
  305. package/src/blend.ts +135 -0
  306. package/src/boundary.ts +363 -0
  307. package/src/brands.ts +86 -0
  308. package/src/caps.ts +100 -0
  309. package/src/capsule.ts +95 -0
  310. package/src/capsules/boundary-evaluate.ts +128 -0
  311. package/src/capsules/canonical-cbor.ts +60 -0
  312. package/src/capsules/token-buffer.ts +57 -0
  313. package/src/capture.ts +48 -0
  314. package/src/cbor.ts +199 -0
  315. package/src/cell.ts +130 -0
  316. package/src/codec.ts +39 -0
  317. package/src/component.ts +102 -0
  318. package/src/composable.ts +328 -0
  319. package/src/compositor-pool.ts +162 -0
  320. package/src/compositor.ts +387 -0
  321. package/src/config.ts +157 -0
  322. package/src/dag.ts +527 -0
  323. package/src/defaults.ts +60 -0
  324. package/src/derived.ts +164 -0
  325. package/src/diagnostics.ts +186 -0
  326. package/src/dirty.ts +101 -0
  327. package/src/easing.ts +334 -0
  328. package/src/ecs.ts +382 -0
  329. package/src/fnv.ts +31 -0
  330. package/src/frame-budget.ts +149 -0
  331. package/src/gen-frame.ts +229 -0
  332. package/src/harness/arbitrary-from-schema.ts +270 -0
  333. package/src/harness/cached-projection.ts +46 -0
  334. package/src/harness/index.ts +16 -0
  335. package/src/harness/policy-gate.ts +51 -0
  336. package/src/harness/pure-transform.ts +121 -0
  337. package/src/harness/receipted-mutation.ts +59 -0
  338. package/src/harness/scene-composition.ts +54 -0
  339. package/src/harness/site-adapter.ts +43 -0
  340. package/src/harness/state-machine.ts +49 -0
  341. package/src/hlc.ts +238 -0
  342. package/src/index.ts +274 -0
  343. package/src/interpolate.ts +37 -0
  344. package/src/live-cell.ts +199 -0
  345. package/src/op.ts +233 -0
  346. package/src/plan.ts +317 -0
  347. package/src/protocol.ts +49 -0
  348. package/src/quantizer-types.ts +29 -0
  349. package/src/receipt.ts +444 -0
  350. package/src/runtime-coordinator.ts +230 -0
  351. package/src/scheduler.ts +161 -0
  352. package/src/ship-capsule.ts +191 -0
  353. package/src/signal.ts +345 -0
  354. package/src/speculative.ts +186 -0
  355. package/src/store.ts +77 -0
  356. package/src/style.ts +249 -0
  357. package/src/testing.ts +14 -0
  358. package/src/theme.ts +153 -0
  359. package/src/timeline.ts +146 -0
  360. package/src/token-buffer.ts +151 -0
  361. package/src/token.ts +197 -0
  362. package/src/tuple.ts +19 -0
  363. package/src/type-utils.ts +48 -0
  364. package/src/typed-ref.ts +79 -0
  365. package/src/ui-quality.ts +105 -0
  366. package/src/validation-error.ts +34 -0
  367. package/src/vector-clock.ts +111 -0
  368. package/src/video.ts +106 -0
  369. package/src/wasm-dispatch.ts +300 -0
  370. package/src/wasm-fallback.ts +102 -0
  371. package/src/wire.ts +274 -0
  372. package/src/zap.ts +241 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * AVBridge -- SharedArrayBuffer timeline bridge for A/V convergence.
3
+ *
4
+ * Provides a single sample counter shared between an AudioWorklet
5
+ * (which advances it) and the visual compositor (which reads it).
6
+ * Works in both real-time (browser) and offline (deterministic) modes.
7
+ *
8
+ * Memory layout (SharedArrayBuffer, 24 bytes):
9
+ * Int32[0] -- sample counter (atomic increment by audio, atomic read by video)
10
+ * Int32[1] -- audio running flag (1 = playing, 0 = paused)
11
+ * Float64[1] -- audio start timestamp in ms (bytes 8-15, for drift calc)
12
+ *
13
+ * @module
14
+ */
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Types
18
+ // ---------------------------------------------------------------------------
19
+
20
+ interface AVBridgeShape {
21
+ readonly buffer: SharedArrayBuffer;
22
+ readonly sampleRate: number;
23
+ readonly fps: number;
24
+
25
+ advanceSamples(count: number): void;
26
+ getCurrentSample(): number;
27
+ setRunning(running: boolean): void;
28
+ isRunning(): boolean;
29
+
30
+ getCurrentFrame(): number;
31
+ sampleToTime(sample: number): number;
32
+ timeToSample(time: number): number;
33
+
34
+ isAudioAhead(): boolean;
35
+ drift(): number;
36
+ reset(): void;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Constants
41
+ // ---------------------------------------------------------------------------
42
+
43
+ const BUFFER_BYTE_LENGTH = 24;
44
+ const SAMPLE_COUNTER_IDX = 0;
45
+ const RUNNING_FLAG_IDX = 1;
46
+ const START_TIMESTAMP_F64_IDX = 1;
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Factory
50
+ // ---------------------------------------------------------------------------
51
+
52
+ interface AVBridgeConfig {
53
+ readonly sampleRate: number;
54
+ readonly fps: number;
55
+ readonly buffer?: SharedArrayBuffer;
56
+ }
57
+
58
+ /**
59
+ * Creates an AVBridge backed by a SharedArrayBuffer for lock-free
60
+ * audio/video timeline synchronization between threads.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const bridge = AVBridge.make({ sampleRate: 48000, fps: 60 });
65
+ * bridge.setRunning(true);
66
+ * bridge.advanceSamples(800); // AudioWorklet advances by 800 samples
67
+ * const frame = bridge.getCurrentFrame(); // current video frame number
68
+ * const drift = bridge.drift(); // fractional frame offset
69
+ * bridge.reset(); // zero out counters
70
+ * ```
71
+ */
72
+ function _make(config: AVBridgeConfig): AVBridgeShape {
73
+ const { sampleRate, fps } = config;
74
+ if (sampleRate <= 0 || !Number.isFinite(sampleRate)) {
75
+ throw new RangeError(`AVBridge.make: sampleRate must be a positive finite number, got ${sampleRate}`);
76
+ }
77
+ if (fps <= 0 || !Number.isFinite(fps)) {
78
+ throw new RangeError(`AVBridge.make: fps must be a positive finite number, got ${fps}`);
79
+ }
80
+ const buffer = config.buffer ?? new SharedArrayBuffer(BUFFER_BYTE_LENGTH);
81
+ const i32 = new Int32Array(buffer);
82
+ const f64 = new Float64Array(buffer);
83
+
84
+ return {
85
+ buffer,
86
+ sampleRate,
87
+ fps,
88
+
89
+ advanceSamples(count: number): void {
90
+ Atomics.add(i32, SAMPLE_COUNTER_IDX, count);
91
+ },
92
+
93
+ getCurrentSample(): number {
94
+ return Atomics.load(i32, SAMPLE_COUNTER_IDX);
95
+ },
96
+
97
+ setRunning(running: boolean): void {
98
+ Atomics.store(i32, RUNNING_FLAG_IDX, running ? 1 : 0);
99
+ },
100
+
101
+ isRunning(): boolean {
102
+ return Atomics.load(i32, RUNNING_FLAG_IDX) === 1;
103
+ },
104
+
105
+ getCurrentFrame(): number {
106
+ const sample = Atomics.load(i32, SAMPLE_COUNTER_IDX);
107
+ return Math.floor((sample / sampleRate) * fps);
108
+ },
109
+
110
+ sampleToTime(sample: number): number {
111
+ return sample / sampleRate;
112
+ },
113
+
114
+ timeToSample(time: number): number {
115
+ return Math.round(time * sampleRate);
116
+ },
117
+
118
+ isAudioAhead(): boolean {
119
+ const sample = Atomics.load(i32, SAMPLE_COUNTER_IDX);
120
+ const frame = Math.floor((sample / sampleRate) * fps);
121
+ const samplesPerFrame = sampleRate / fps;
122
+ return sample % samplesPerFrame > 0 || sample > (frame + 1) * samplesPerFrame;
123
+ },
124
+
125
+ drift(): number {
126
+ const sample = Atomics.load(i32, SAMPLE_COUNTER_IDX);
127
+ const exactFrame = (sample / sampleRate) * fps;
128
+ const currentFrame = Math.floor(exactFrame);
129
+ return exactFrame - currentFrame;
130
+ },
131
+
132
+ reset(): void {
133
+ Atomics.store(i32, SAMPLE_COUNTER_IDX, 0);
134
+ Atomics.store(i32, RUNNING_FLAG_IDX, 0);
135
+ f64[START_TIMESTAMP_F64_IDX] = 0;
136
+ },
137
+ };
138
+ }
139
+
140
+ /**
141
+ * AVBridge -- SharedArrayBuffer-based timeline bridge for audio/video convergence.
142
+ * Provides atomic sample counting shared between AudioWorklet and visual compositor.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const bridge = AVBridge.make({ sampleRate: 44100, fps: 30 });
147
+ * bridge.setRunning(true);
148
+ * bridge.advanceSamples(1470); // advance by one video frame worth of samples
149
+ * bridge.getCurrentFrame(); // 1
150
+ * bridge.sampleToTime(44100); // 1.0 (seconds)
151
+ * bridge.timeToSample(0.5); // 22050
152
+ * ```
153
+ */
154
+ export const AVBridge = { make: _make };
155
+
156
+ export declare namespace AVBridge {
157
+ /** Structural shape of an AVBridge instance — sample counters, time conversions, reset. */
158
+ export type Shape = AVBridgeShape;
159
+ /** Configuration accepted by {@link AVBridge.make}: sample rate and fps. */
160
+ export type Config = AVBridgeConfig;
161
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * AVRenderer -- deterministic offline A/V renderer.
3
+ *
4
+ * Steps through audio samples and visual frames in lockstep.
5
+ * Each video frame knows its exact audio position. No wall-clock
6
+ * dependency -- fully deterministic.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ import type { CompositeState } from './compositor.js';
12
+ import type { Compositor } from './compositor.js';
13
+ import { AVBridge } from './av-bridge.js';
14
+ import type { Millis } from './brands.js';
15
+ import { Effect } from 'effect';
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Types
19
+ // ---------------------------------------------------------------------------
20
+
21
+ interface AVRenderConfig {
22
+ readonly sampleRate: number;
23
+ readonly fps: number;
24
+ readonly durationMs: Millis;
25
+ }
26
+
27
+ interface AVFrameOutput {
28
+ readonly frame: number;
29
+ readonly timestamp: number;
30
+ readonly sample: number;
31
+ readonly sampleCount: number;
32
+ readonly state: CompositeState;
33
+ }
34
+
35
+ interface AVRendererShape {
36
+ readonly config: AVRenderConfig;
37
+ readonly bridge: AVBridge.Shape;
38
+ readonly totalFrames: number;
39
+ frames(options?: {
40
+ onAudioFrame?: (sample: number, sampleCount: number) => void;
41
+ onVideoFrame?: (frame: number, timestamp: number, state: CompositeState) => void;
42
+ }): AsyncGenerator<AVFrameOutput>;
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Factory
47
+ // ---------------------------------------------------------------------------
48
+
49
+ function _make(config: AVRenderConfig, compositor: Compositor.Shape, existingBridge?: AVBridge.Shape): AVRendererShape {
50
+ const { sampleRate, fps, durationMs } = config;
51
+ const totalFrames = Math.ceil((durationMs / 1000) * fps);
52
+ const samplesPerFrame = Math.round(sampleRate / fps);
53
+
54
+ const bridge = existingBridge ?? AVBridge.make({ sampleRate, fps });
55
+ bridge.reset();
56
+
57
+ return {
58
+ config,
59
+ bridge,
60
+ totalFrames,
61
+
62
+ async *frames(options) {
63
+ const { onAudioFrame, onVideoFrame } = options ?? {};
64
+
65
+ for (let i = 0; i < totalFrames; i++) {
66
+ const targetSample = (i + 1) * samplesPerFrame;
67
+ const currentSample = bridge.getCurrentSample();
68
+ const advance = targetSample - currentSample;
69
+
70
+ if (advance > 0) {
71
+ bridge.advanceSamples(advance);
72
+ }
73
+
74
+ const frameSample = i * samplesPerFrame;
75
+ const timestamp = (i * 1000) / fps;
76
+
77
+ if (onAudioFrame) {
78
+ onAudioFrame(frameSample, samplesPerFrame);
79
+ }
80
+
81
+ const state = await Effect.runPromise(compositor.compute());
82
+
83
+ if (onVideoFrame) {
84
+ onVideoFrame(i, timestamp, state);
85
+ }
86
+
87
+ yield {
88
+ frame: i,
89
+ timestamp,
90
+ sample: frameSample,
91
+ sampleCount: samplesPerFrame,
92
+ state,
93
+ };
94
+ }
95
+ },
96
+ };
97
+ }
98
+
99
+ /**
100
+ * AVRenderer — deterministic offline audio+video renderer.
101
+ *
102
+ * Steps an {@link AVBridge} in lockstep with a {@link Compositor} so every
103
+ * video frame carries the exact sample offset it corresponds to. Pure clock
104
+ * math — no wall-clock input, reproducible across runs.
105
+ */
106
+ export const AVRenderer = {
107
+ /** Create a renderer bound to a compositor, optionally reusing an existing {@link AVBridge}. */
108
+ make: _make,
109
+ };
110
+
111
+ export declare namespace AVRenderer {
112
+ /** Structural shape of a renderer instance returned by {@link AVRenderer.make}. */
113
+ export type Shape = AVRendererShape;
114
+ /** Configuration accepted by {@link AVRenderer.make}. */
115
+ export type Config = AVRenderConfig;
116
+ /** Per-frame output yielded by the async iterator. */
117
+ export type FrameOutput = AVFrameOutput;
118
+ }
package/src/blend.ts ADDED
@@ -0,0 +1,135 @@
1
+ /**
2
+ * BlendTree -- weighted multi-state blending.
3
+ *
4
+ * A blend tree holds named numeric-record values with weights.
5
+ * `compute()` returns the weighted average of all values.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { Scope } from 'effect';
11
+ import { Effect, Stream, PubSub } from 'effect';
12
+
13
+ interface BlendNodeShape<T> {
14
+ readonly value: T;
15
+ readonly weight: number;
16
+ }
17
+
18
+ interface BlendTreeShape<T extends Record<string, number>> {
19
+ add(name: string, value: T, weight: number): void;
20
+ remove(name: string): void;
21
+ setWeight(name: string, weight: number): void;
22
+ compute(): T;
23
+ readonly changes: Stream.Stream<T>;
24
+ }
25
+
26
+ /**
27
+ * Creates a new BlendTree for weighted multi-state blending of numeric records.
28
+ * Requires a Scope for lifecycle management of the change stream.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * const program = Effect.scoped(Effect.gen(function* () {
33
+ * const tree = yield* BlendTree.make<{ x: number; y: number }>();
34
+ * tree.add('idle', { x: 0, y: 0 }, 0.3);
35
+ * tree.add('active', { x: 100, y: 50 }, 0.7);
36
+ * const blended = tree.compute(); // { x: 70, y: 35 }
37
+ * }));
38
+ * ```
39
+ */
40
+ function _make<T extends Record<string, number>>(): Effect.Effect<BlendTreeShape<T>, never, Scope.Scope> {
41
+ return Effect.gen(function* () {
42
+ const nodes = new Map<string, BlendNodeShape<T>>();
43
+ const pubsub = yield* PubSub.unbounded<T>();
44
+
45
+ // The computed result is a Record<string, number> whose keys match T's keys by
46
+ // construction (we only write keys copied from node.value, which is T). TS can't
47
+ // track that structural promise, so we contain one cast in a named helper.
48
+ const finalizeBlend = (record: Record<string, number>): T => record as unknown as T;
49
+
50
+ function computeBlend(): T {
51
+ const result: Record<string, number> = {};
52
+ let totalWeight = 0;
53
+
54
+ for (const node of nodes.values()) {
55
+ if (node.weight > 0) totalWeight += node.weight;
56
+ }
57
+
58
+ if (totalWeight === 0 || nodes.size === 0) {
59
+ return finalizeBlend(result);
60
+ }
61
+
62
+ let initialized = false;
63
+ for (const node of nodes.values()) {
64
+ const w = node.weight > 0 ? node.weight / totalWeight : 0;
65
+ for (const key in node.value) {
66
+ if (Object.prototype.hasOwnProperty.call(node.value, key)) {
67
+ if (!initialized || !(key in result)) {
68
+ result[key] = 0;
69
+ }
70
+ result[key]! += node.value[key]! * w;
71
+ }
72
+ }
73
+ initialized = true;
74
+ }
75
+
76
+ return finalizeBlend(result);
77
+ }
78
+
79
+ function notifyChange(): void {
80
+ const blended = computeBlend();
81
+ Effect.runSync(PubSub.publish(pubsub, blended));
82
+ }
83
+
84
+ const tree: BlendTreeShape<T> = {
85
+ add(name: string, value: T, weight: number): void {
86
+ nodes.set(name, { value, weight });
87
+ notifyChange();
88
+ },
89
+
90
+ remove(name: string): void {
91
+ nodes.delete(name);
92
+ notifyChange();
93
+ },
94
+
95
+ setWeight(name: string, weight: number): void {
96
+ const node = nodes.get(name);
97
+ if (node) {
98
+ nodes.set(name, { ...node, weight });
99
+ notifyChange();
100
+ }
101
+ },
102
+
103
+ compute(): T {
104
+ return computeBlend();
105
+ },
106
+
107
+ changes: Stream.fromPubSub(pubsub),
108
+ };
109
+
110
+ return tree;
111
+ });
112
+ }
113
+
114
+ /**
115
+ * BlendTree -- weighted multi-state blending for numeric records.
116
+ * Add named nodes with values and weights, then compute the weighted average.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * const program = Effect.scoped(Effect.gen(function* () {
121
+ * const tree = yield* BlendTree.make<{ opacity: number }>();
122
+ * tree.add('fadeIn', { opacity: 1 }, 0.8);
123
+ * tree.add('fadeOut', { opacity: 0 }, 0.2);
124
+ * const result = tree.compute(); // { opacity: 0.8 }
125
+ * }));
126
+ * ```
127
+ */
128
+ export const BlendTree = { make: _make };
129
+
130
+ export declare namespace BlendTree {
131
+ /** Structural shape of a blend-tree instance: `sample(weights)` over a `Record<string, number>` space. */
132
+ export type Shape<T extends Record<string, number>> = BlendTreeShape<T>;
133
+ /** Individual leaf/intermediate node in a blend tree. */
134
+ export type Node<T> = BlendNodeShape<T>;
135
+ }