@hraness/slopcamera 3.2.4

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 (441) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +60 -0
  3. package/PRIVACY.md +46 -0
  4. package/README.md +311 -0
  5. package/SECURITY.md +21 -0
  6. package/apps/desktop/README.md +966 -0
  7. package/apps/desktop/analysis/protocol.ts +223 -0
  8. package/apps/desktop/application/capability-binding.ts +594 -0
  9. package/apps/desktop/application/context.ts +97 -0
  10. package/apps/desktop/application/creative-iteration.ts +1385 -0
  11. package/apps/desktop/application/default-registry.ts +146 -0
  12. package/apps/desktop/application/directing-contract.ts +218 -0
  13. package/apps/desktop/application/directing-plan.ts +178 -0
  14. package/apps/desktop/application/errors.ts +18 -0
  15. package/apps/desktop/application/gateway-port.ts +867 -0
  16. package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
  17. package/apps/desktop/application/html-overlay-integrity.ts +337 -0
  18. package/apps/desktop/application/html-overlay-renderer.ts +45 -0
  19. package/apps/desktop/application/index.ts +13 -0
  20. package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
  21. package/apps/desktop/application/operation-effects.ts +94 -0
  22. package/apps/desktop/application/operation.ts +224 -0
  23. package/apps/desktop/application/operations/analysis/faces.ts +244 -0
  24. package/apps/desktop/application/operations/analysis/music.ts +248 -0
  25. package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
  26. package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
  27. package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
  28. package/apps/desktop/application/operations/analysis/shared.ts +202 -0
  29. package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
  30. package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
  31. package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
  32. package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
  33. package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
  34. package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
  35. package/apps/desktop/application/operations/gateway/image.ts +41 -0
  36. package/apps/desktop/application/operations/gateway/index.ts +17 -0
  37. package/apps/desktop/application/operations/gateway/shared.ts +122 -0
  38. package/apps/desktop/application/operations/gateway/speech.ts +41 -0
  39. package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
  40. package/apps/desktop/application/operations/gateway/video.ts +41 -0
  41. package/apps/desktop/application/operations/index.ts +31 -0
  42. package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
  43. package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
  44. package/apps/desktop/application/operations/iteration/select.ts +155 -0
  45. package/apps/desktop/application/operations/iteration/shared.ts +250 -0
  46. package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
  47. package/apps/desktop/application/operations/media/binding.ts +79 -0
  48. package/apps/desktop/application/operations/media/capabilities.ts +112 -0
  49. package/apps/desktop/application/operations/media/color-grade.ts +343 -0
  50. package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
  51. package/apps/desktop/application/operations/media/index.ts +8 -0
  52. package/apps/desktop/application/operations/media/ingest.ts +303 -0
  53. package/apps/desktop/application/operations/media/overlay.ts +729 -0
  54. package/apps/desktop/application/operations/media/shared.ts +845 -0
  55. package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
  56. package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
  57. package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
  58. package/apps/desktop/application/operations/project/snapshot.ts +95 -0
  59. package/apps/desktop/application/operations/recording/index.ts +5 -0
  60. package/apps/desktop/application/operations/recording/pause.ts +34 -0
  61. package/apps/desktop/application/operations/recording/resume.ts +34 -0
  62. package/apps/desktop/application/operations/recording/shared.ts +199 -0
  63. package/apps/desktop/application/operations/recording/start.ts +169 -0
  64. package/apps/desktop/application/operations/recording/stop.ts +34 -0
  65. package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
  66. package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
  67. package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
  68. package/apps/desktop/application/operations/render/project-plan.ts +939 -0
  69. package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
  70. package/apps/desktop/application/operations/render/project.ts +1922 -0
  71. package/apps/desktop/application/operations/shared.ts +67 -0
  72. package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
  73. package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
  74. package/apps/desktop/application/operations/spatial-project.ts +209 -0
  75. package/apps/desktop/application/operations/spatial-render.ts +415 -0
  76. package/apps/desktop/application/operations/spatial-scene.ts +97 -0
  77. package/apps/desktop/application/operations/studio.ts +143 -0
  78. package/apps/desktop/application/output-publication-lease.ts +163 -0
  79. package/apps/desktop/application/project-publication-lease.ts +233 -0
  80. package/apps/desktop/application/project-store.ts +196 -0
  81. package/apps/desktop/application/receipts.ts +533 -0
  82. package/apps/desktop/application/registry.ts +182 -0
  83. package/apps/desktop/application/spatial-assets.ts +675 -0
  84. package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
  85. package/apps/desktop/application/spatial-project-authority.ts +35 -0
  86. package/apps/desktop/application/spatial-project-lease.ts +126 -0
  87. package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
  88. package/apps/desktop/application/spatial-project-store.ts +286 -0
  89. package/apps/desktop/application/spatial-render-projection.ts +201 -0
  90. package/apps/desktop/application/spatial-render.ts +677 -0
  91. package/apps/desktop/application/spatial-spz.ts +44 -0
  92. package/apps/desktop/application/spatial-world-import.ts +135 -0
  93. package/apps/desktop/application/spatial-world-provenance.ts +46 -0
  94. package/apps/desktop/application/studio-port.ts +58 -0
  95. package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
  96. package/apps/desktop/capture/protocol.ts +689 -0
  97. package/apps/desktop/cli/analyzer.ts +406 -0
  98. package/apps/desktop/cli/args.ts +3181 -0
  99. package/apps/desktop/cli/asset-ingest.ts +747 -0
  100. package/apps/desktop/cli/atomic-render-effects.ts +210 -0
  101. package/apps/desktop/cli/atomic-render-platform.ts +205 -0
  102. package/apps/desktop/cli/atomic-render.ts +50 -0
  103. package/apps/desktop/cli/audio-analysis.ts +252 -0
  104. package/apps/desktop/cli/bounded-map.ts +39 -0
  105. package/apps/desktop/cli/bundle-service.ts +256 -0
  106. package/apps/desktop/cli/capabilities.ts +212 -0
  107. package/apps/desktop/cli/capture-bundle.ts +1117 -0
  108. package/apps/desktop/cli/command-host-resources.ts +251 -0
  109. package/apps/desktop/cli/commands.ts +6836 -0
  110. package/apps/desktop/cli/directing-blob.ts +291 -0
  111. package/apps/desktop/cli/directing-media.ts +517 -0
  112. package/apps/desktop/cli/directing-quote.ts +89 -0
  113. package/apps/desktop/cli/directing-service.ts +288 -0
  114. package/apps/desktop/cli/emoji-assets.ts +538 -0
  115. package/apps/desktop/cli/errors.ts +29 -0
  116. package/apps/desktop/cli/face-analysis-service.ts +940 -0
  117. package/apps/desktop/cli/filter-script.ts +76 -0
  118. package/apps/desktop/cli/gateway-application-port.ts +1142 -0
  119. package/apps/desktop/cli/gateway-credential.ts +81 -0
  120. package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
  121. package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
  122. package/apps/desktop/cli/gateway-media-service.ts +3106 -0
  123. package/apps/desktop/cli/gateway-media-signature.ts +122 -0
  124. package/apps/desktop/cli/gateway-provider-options.ts +562 -0
  125. package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
  126. package/apps/desktop/cli/help.ts +501 -0
  127. package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
  128. package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
  129. package/apps/desktop/cli/io.ts +290 -0
  130. package/apps/desktop/cli/main.ts +123 -0
  131. package/apps/desktop/cli/media-effects-service.ts +885 -0
  132. package/apps/desktop/cli/media-ingest-model.ts +364 -0
  133. package/apps/desktop/cli/media-ingest-platform.ts +227 -0
  134. package/apps/desktop/cli/media-ingest-program.ts +200 -0
  135. package/apps/desktop/cli/media-ingest.ts +30 -0
  136. package/apps/desktop/cli/music-analysis-service.ts +434 -0
  137. package/apps/desktop/cli/mutation-lock.ts +530 -0
  138. package/apps/desktop/cli/native-assets.d.ts +12 -0
  139. package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
  140. package/apps/desktop/cli/overlay-playback.ts +177 -0
  141. package/apps/desktop/cli/paths.ts +265 -0
  142. package/apps/desktop/cli/portable-surface.ts +242 -0
  143. package/apps/desktop/cli/project-camera-receipt.ts +112 -0
  144. package/apps/desktop/cli/project-face-camera.ts +394 -0
  145. package/apps/desktop/cli/project-inactivity-service.ts +552 -0
  146. package/apps/desktop/cli/project-media-integrity.ts +121 -0
  147. package/apps/desktop/cli/project-renderer.ts +1526 -0
  148. package/apps/desktop/cli/project-service.ts +519 -0
  149. package/apps/desktop/cli/project-state-transaction.ts +520 -0
  150. package/apps/desktop/cli/query-limits.ts +1 -0
  151. package/apps/desktop/cli/recording-controller.ts +1567 -0
  152. package/apps/desktop/cli/recording-daemon.ts +931 -0
  153. package/apps/desktop/cli/recording-ref.ts +56 -0
  154. package/apps/desktop/cli/renamed-environment.ts +6 -0
  155. package/apps/desktop/cli/renderer.ts +1439 -0
  156. package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
  157. package/apps/desktop/cli/scene-analysis-service.ts +879 -0
  158. package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
  159. package/apps/desktop/cli/spatial-project-service.ts +90 -0
  160. package/apps/desktop/cli/spatial-scene-service.ts +105 -0
  161. package/apps/desktop/cli/spatial-world-service.ts +39 -0
  162. package/apps/desktop/cli/speech-analysis-service.ts +682 -0
  163. package/apps/desktop/cli/studio-assemble.ts +58 -0
  164. package/apps/desktop/cli/studio-command.ts +60 -0
  165. package/apps/desktop/cli/studio-custody.ts +60 -0
  166. package/apps/desktop/cli/studio-encode.ts +216 -0
  167. package/apps/desktop/cli/studio-exr.ts +52 -0
  168. package/apps/desktop/cli/studio-files.ts +179 -0
  169. package/apps/desktop/cli/studio-output-validation.ts +162 -0
  170. package/apps/desktop/cli/studio-process.ts +145 -0
  171. package/apps/desktop/cli/studio-runtime.ts +77 -0
  172. package/apps/desktop/cli/studio-scaffold.ts +70 -0
  173. package/apps/desktop/cli/studio-service.ts +301 -0
  174. package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
  175. package/apps/desktop/cli/studio-template-names.ts +3 -0
  176. package/apps/desktop/cli/studio-workflow.ts +19 -0
  177. package/apps/desktop/cli/time.ts +25 -0
  178. package/apps/desktop/cli/workflow-code.ts +584 -0
  179. package/apps/desktop/cli/workflow-runs.ts +576 -0
  180. package/apps/desktop/code/advanced.ts +139 -0
  181. package/apps/desktop/code/application-node-planner.ts +1288 -0
  182. package/apps/desktop/code/compiler.ts +329 -0
  183. package/apps/desktop/code/contracts.ts +13 -0
  184. package/apps/desktop/code/creative-recipes.ts +94 -0
  185. package/apps/desktop/code/define-workflow.ts +115 -0
  186. package/apps/desktop/code/editing.ts +284 -0
  187. package/apps/desktop/code/file-candidate-provenance.ts +420 -0
  188. package/apps/desktop/code/file-candidate.ts +69 -0
  189. package/apps/desktop/code/graph-builder.ts +10 -0
  190. package/apps/desktop/code/host-resource-policy.ts +55 -0
  191. package/apps/desktop/code/plan-contracts.ts +159 -0
  192. package/apps/desktop/code/planning.ts +361 -0
  193. package/apps/desktop/code/public.ts +134 -0
  194. package/apps/desktop/code/reference-projection.ts +62 -0
  195. package/apps/desktop/code/run-contracts.ts +466 -0
  196. package/apps/desktop/code/run-store.ts +2478 -0
  197. package/apps/desktop/code/runtime-identity.ts +328 -0
  198. package/apps/desktop/code/scheduler.ts +3067 -0
  199. package/apps/desktop/code/semantic-builder.ts +1436 -0
  200. package/apps/desktop/code/source-bundle.ts +1641 -0
  201. package/apps/desktop/code/source-typecheck.ts +211 -0
  202. package/apps/desktop/code/testing.ts +79 -0
  203. package/apps/desktop/code/worker-client.ts +1675 -0
  204. package/apps/desktop/code/worker-effects.ts +141 -0
  205. package/apps/desktop/code/worker-entry.ts +435 -0
  206. package/apps/desktop/code/worker-lease-guardian.ts +179 -0
  207. package/apps/desktop/code/worker-process-identity.ts +484 -0
  208. package/apps/desktop/code/worker-protocol.ts +211 -0
  209. package/apps/desktop/code/workflow-effects.ts +132 -0
  210. package/apps/desktop/contracts/analysis.ts +949 -0
  211. package/apps/desktop/contracts/edit.ts +576 -0
  212. package/apps/desktop/contracts/index.ts +9 -0
  213. package/apps/desktop/contracts/media-effects.ts +139 -0
  214. package/apps/desktop/contracts/project-render.ts +644 -0
  215. package/apps/desktop/contracts/project.ts +740 -0
  216. package/apps/desktop/contracts/recording.ts +1434 -0
  217. package/apps/desktop/contracts/render.ts +334 -0
  218. package/apps/desktop/contracts/runtime.ts +155 -0
  219. package/apps/desktop/contracts/spatial-compositor.ts +58 -0
  220. package/apps/desktop/contracts/spatial-project.ts +159 -0
  221. package/apps/desktop/contracts/spatial-world.ts +48 -0
  222. package/apps/desktop/contracts/video-effects.ts +120 -0
  223. package/apps/desktop/core/alignment-apply.ts +400 -0
  224. package/apps/desktop/core/audio-alignment.ts +466 -0
  225. package/apps/desktop/core/auto-zoom.ts +209 -0
  226. package/apps/desktop/core/canonical-json.ts +14 -0
  227. package/apps/desktop/core/emoji.ts +210 -0
  228. package/apps/desktop/core/events.ts +409 -0
  229. package/apps/desktop/core/face-framing.ts +533 -0
  230. package/apps/desktop/core/face-tracks.ts +326 -0
  231. package/apps/desktop/core/inactivity.ts +137 -0
  232. package/apps/desktop/core/index.ts +30 -0
  233. package/apps/desktop/core/intervals.ts +123 -0
  234. package/apps/desktop/core/montage.ts +236 -0
  235. package/apps/desktop/core/music-analysis.ts +518 -0
  236. package/apps/desktop/core/plan.ts +244 -0
  237. package/apps/desktop/core/project-auto-zoom.ts +75 -0
  238. package/apps/desktop/core/project-camera-spatial.ts +676 -0
  239. package/apps/desktop/core/project-camera.ts +403 -0
  240. package/apps/desktop/core/project-captions.ts +888 -0
  241. package/apps/desktop/core/project-inactivity.ts +125 -0
  242. package/apps/desktop/core/project-metadata.ts +927 -0
  243. package/apps/desktop/core/project-plan.ts +375 -0
  244. package/apps/desktop/core/project-render-plan.ts +303 -0
  245. package/apps/desktop/core/project-time.ts +469 -0
  246. package/apps/desktop/core/render-plan.ts +442 -0
  247. package/apps/desktop/core/scene-cache.ts +174 -0
  248. package/apps/desktop/core/scene-sampling.ts +448 -0
  249. package/apps/desktop/core/spatial-compositor.ts +22 -0
  250. package/apps/desktop/core/spatial-durability.ts +99 -0
  251. package/apps/desktop/core/spatial-project.ts +169 -0
  252. package/apps/desktop/core/spatial-shot-clock.ts +32 -0
  253. package/apps/desktop/core/speech-analysis.ts +333 -0
  254. package/apps/desktop/core/storage.ts +970 -0
  255. package/apps/desktop/core/time-map.ts +91 -0
  256. package/apps/desktop/core/time.ts +61 -0
  257. package/apps/desktop/core/video-effects.ts +660 -0
  258. package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
  259. package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  260. package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  261. package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
  262. package/apps/desktop/dist/cli/main.js +5086 -0
  263. package/apps/desktop/html-overlay/catalog.ts +171 -0
  264. package/apps/desktop/html-overlay/contracts.ts +372 -0
  265. package/apps/desktop/html-overlay/execution-profile.ts +77 -0
  266. package/apps/desktop/html-overlay/index.ts +98 -0
  267. package/apps/desktop/html-overlay/libraries.ts +263 -0
  268. package/apps/desktop/html-overlay/random.ts +49 -0
  269. package/apps/desktop/html-overlay/runtime.ts +813 -0
  270. package/apps/desktop/html-overlay/scaffolds.ts +792 -0
  271. package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
  272. package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
  273. package/apps/desktop/html-overlay/spatial.ts +720 -0
  274. package/apps/desktop/studio/assets/README.md +39 -0
  275. package/apps/desktop/studio/assets/contracts.ts +129 -0
  276. package/apps/desktop/studio/assets/network.ts +64 -0
  277. package/apps/desktop/studio/assets/poly-haven.ts +148 -0
  278. package/apps/desktop/studio/drivers/blender_driver.py +466 -0
  279. package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
  280. package/apps/desktop/studio/education/driver.py +308 -0
  281. package/apps/desktop/studio/education/lesson.py +180 -0
  282. package/apps/desktop/studio/education/toolkit.py +249 -0
  283. package/apps/desktop/workflows/chaptered-demo.ts +48 -0
  284. package/apps/desktop/workflows/creative-iteration.ts +107 -0
  285. package/apps/desktop/workflows/creative-selection.ts +92 -0
  286. package/apps/desktop/workflows/fragments.ts +486 -0
  287. package/apps/desktop/workflows/index.ts +93 -0
  288. package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
  289. package/apps/desktop/workflows/scene-direction.ts +12 -0
  290. package/apps/desktop/workflows/social-variants.ts +75 -0
  291. package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
  292. package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
  293. package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  294. package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  295. package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
  296. package/dist/cli.js +479 -0
  297. package/dist/code/advanced.js +270 -0
  298. package/dist/code/index.js +2240 -0
  299. package/dist/generate.js +24 -0
  300. package/dist/host-resources.js +26 -0
  301. package/dist/index-42zsesc1.js +2257 -0
  302. package/dist/index-7308egqr.js +48 -0
  303. package/dist/index-8txs6fkn.js +712 -0
  304. package/dist/index-mcy8z0br.js +192 -0
  305. package/dist/index-p63wavx0.js +2029 -0
  306. package/dist/index-r7gdhmsp.js +424 -0
  307. package/dist/index-sh6xbav6.js +1219 -0
  308. package/dist/index-z1w83f81.js +4 -0
  309. package/dist/index-z7239b4h.js +2363 -0
  310. package/dist/index-zfeg9hmw.js +2013 -0
  311. package/dist/index.js +241 -0
  312. package/dist/operations.js +29 -0
  313. package/dist/skill-install-dh0ntqba.js +10 -0
  314. package/dist/vectorize/worker.js +142 -0
  315. package/dist/workflow.js +16 -0
  316. package/docs/README.md +38 -0
  317. package/docs/studio.md +215 -0
  318. package/examples/studio/blender/README.md +26 -0
  319. package/examples/studio/blender/character.py +152 -0
  320. package/examples/studio/blender/cloth.py +101 -0
  321. package/examples/studio/blender/color_chart.py +29 -0
  322. package/examples/studio/blender/fluid.py +78 -0
  323. package/examples/studio/blender/import_model.py +74 -0
  324. package/examples/studio/blender/product.py +53 -0
  325. package/examples/studio/blender/shaded_street.py +481 -0
  326. package/examples/studio/blender/studio_scene.py +147 -0
  327. package/examples/studio/cadquery/README.md +13 -0
  328. package/examples/studio/cadquery/bracket.py +36 -0
  329. package/examples/studio/cadquery/import_step.py +12 -0
  330. package/examples/studio/education/README.md +46 -0
  331. package/examples/studio/education/lesson.json +99 -0
  332. package/examples/studio/education/scene.py +7 -0
  333. package/examples/studio/hybrid-scene.ts +96 -0
  334. package/examples/studio/native-workflow.ts +14 -0
  335. package/examples/studio/vgpu/README.md +64 -0
  336. package/examples/studio/vgpu/heat-field.mjs +290 -0
  337. package/package.json +304 -0
  338. package/packages/scene/src/index.ts +1 -0
  339. package/packages/scene/src/scene.ts +215 -0
  340. package/schema/diagram.schema.json +334 -0
  341. package/skills/slopcamera/SKILL.md +45 -0
  342. package/skills/slopcamera/agents/openai.yaml +4 -0
  343. package/skills/slopcamera/references/customization.md +77 -0
  344. package/skills/slopcamera/references/diagrams.md +153 -0
  345. package/skills/slopcamera/references/directed-scenes.md +42 -0
  346. package/skills/slopcamera/references/directing-video.md +18 -0
  347. package/skills/slopcamera/references/educational-video.md +26 -0
  348. package/skills/slopcamera/references/gateway-media.md +134 -0
  349. package/skills/slopcamera/references/install.md +62 -0
  350. package/skills/slopcamera/references/native-studio.md +31 -0
  351. package/skills/slopcamera/references/reference-led-3d.md +140 -0
  352. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
  353. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
  354. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
  355. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
  356. package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
  357. package/skills/slopcamera/references/vectorization.md +29 -0
  358. package/skills/slopcamera/references/video-projects.md +187 -0
  359. package/skills/slopcamera/references/visual-communication.md +120 -0
  360. package/skills/slopcamera/references/workflows-sdk.md +45 -0
  361. package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
  362. package/src/artifacts.ts +106 -0
  363. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  364. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
  365. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
  366. package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
  367. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  368. package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
  369. package/src/assets.d.ts +9 -0
  370. package/src/cli.ts +601 -0
  371. package/src/cloud-errors.ts +26 -0
  372. package/src/code/advanced.ts +10 -0
  373. package/src/code/boundary.ts +21 -0
  374. package/src/code/canonical-json.ts +129 -0
  375. package/src/code/compiler.ts +953 -0
  376. package/src/code/contracts.ts +576 -0
  377. package/src/code/define-workflow.ts +334 -0
  378. package/src/code/errors.ts +41 -0
  379. package/src/code/graph-builder.ts +945 -0
  380. package/src/code/index.ts +121 -0
  381. package/src/code/json-snapshot.ts +899 -0
  382. package/src/code/json-utf8.ts +88 -0
  383. package/src/code/portable-builder.ts +136 -0
  384. package/src/code/projection.ts +378 -0
  385. package/src/code/public-operations.ts +450 -0
  386. package/src/code/runtime.ts +580 -0
  387. package/src/code/sha256.ts +36 -0
  388. package/src/config.ts +180 -0
  389. package/src/desktop.ts +253 -0
  390. package/src/fs.ts +10 -0
  391. package/src/generate.ts +704 -0
  392. package/src/host-resource-posix.ts +161 -0
  393. package/src/host-resources.ts +1749 -0
  394. package/src/icons.ts +58 -0
  395. package/src/index.ts +200 -0
  396. package/src/label-layout.ts +130 -0
  397. package/src/layout.ts +413 -0
  398. package/src/lint.ts +127 -0
  399. package/src/mcp/boundary.ts +354 -0
  400. package/src/mcp/index.ts +33 -0
  401. package/src/mcp/server.ts +272 -0
  402. package/src/mcp/tools.ts +963 -0
  403. package/src/mcp/types.ts +71 -0
  404. package/src/operations.ts +775 -0
  405. package/src/parse.ts +861 -0
  406. package/src/process-environment.ts +17 -0
  407. package/src/render.ts +437 -0
  408. package/src/skill-install.ts +49 -0
  409. package/src/spatial-scene/authoring.ts +27 -0
  410. package/src/spatial-scene/camera-track.ts +83 -0
  411. package/src/spatial-scene/contracts.ts +277 -0
  412. package/src/spatial-scene/evaluate.ts +145 -0
  413. package/src/spatial-scene/gltf.ts +558 -0
  414. package/src/spatial-scene/identity.ts +260 -0
  415. package/src/spatial-scene/index.ts +10 -0
  416. package/src/spatial-scene/inspect.ts +68 -0
  417. package/src/spatial-scene/math.ts +280 -0
  418. package/src/spatial-scene/patch.ts +131 -0
  419. package/src/spatial-scene/time.ts +53 -0
  420. package/src/studio/contracts.ts +129 -0
  421. package/src/studio/index.ts +2 -0
  422. package/src/studio/plan.ts +156 -0
  423. package/src/studio/shared.ts +41 -0
  424. package/src/theme.ts +68 -0
  425. package/src/tldr.ts +474 -0
  426. package/src/types.ts +212 -0
  427. package/src/vectorize/archive.ts +101 -0
  428. package/src/vectorize/command.ts +797 -0
  429. package/src/vectorize/index.ts +27 -0
  430. package/src/vectorize/limits.ts +61 -0
  431. package/src/vectorize/metrics.ts +351 -0
  432. package/src/vectorize/pixels.ts +323 -0
  433. package/src/vectorize/supervisor.ts +339 -0
  434. package/src/vectorize/svg.ts +188 -0
  435. package/src/vectorize/tool.ts +542 -0
  436. package/src/vectorize/types.ts +113 -0
  437. package/src/vectorize/vectorize.ts +634 -0
  438. package/src/vectorize/worker-protocol.ts +40 -0
  439. package/src/vectorize/worker.ts +209 -0
  440. package/src/version.ts +1 -0
  441. package/src/workflow.ts +409 -0
@@ -0,0 +1,518 @@
1
+ import {
2
+ MusicAnalysisV1Schema,
3
+ type AnalysisSubject,
4
+ type MusicAnalysisV1,
5
+ } from "../contracts/analysis";
6
+
7
+ const EPSILON = 1e-12;
8
+ const MICROSECONDS_PER_SECOND = 1_000_000;
9
+ const MINIMUM_ONSET_SEPARATION_US = 120_000;
10
+ const MAJOR_PROFILE = [6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88] as const;
11
+ const MINOR_PROFILE = [6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17] as const;
12
+
13
+ type SourceRange = { readonly endUs: number; readonly startUs: number };
14
+ type MusicalKey = MusicAnalysisV1["keyRegions"][number]["key"];
15
+ type KeyRegion = MusicAnalysisV1["keyRegions"][number];
16
+ type MusicRegion = MusicAnalysisV1["musicRegions"][number];
17
+ type TempoRegion = MusicAnalysisV1["tempoRegions"][number];
18
+
19
+ export interface MonoPcm {
20
+ readonly sampleRateHz: number;
21
+ readonly samples: readonly number[];
22
+ }
23
+
24
+ export interface PcmFrameFeatures {
25
+ readonly chroma: readonly number[];
26
+ readonly endSample: number;
27
+ readonly endUs: number;
28
+ readonly onsetStrength: number;
29
+ readonly peak: number;
30
+ readonly rms: number;
31
+ readonly startSample: number;
32
+ readonly startUs: number;
33
+ readonly tonality: number;
34
+ readonly zeroCrossingRate: number;
35
+ }
36
+
37
+ export interface PcmFeatureConfig {
38
+ readonly hopSize: number;
39
+ readonly windowSize: number;
40
+ }
41
+
42
+ export interface KeyClassification {
43
+ readonly alternate: { readonly confidence: number; readonly key: MusicalKey } | null;
44
+ readonly confidence: number;
45
+ readonly key: MusicalKey;
46
+ }
47
+
48
+ export interface TempoEstimate {
49
+ readonly alternatives: readonly { readonly bpm: number; readonly confidence: number }[];
50
+ readonly beatTimesUs: readonly number[];
51
+ readonly bpm: number;
52
+ readonly confidence: number;
53
+ readonly meter: TempoRegion["meter"];
54
+ }
55
+
56
+ export interface AnalyzeMusicInput {
57
+ readonly analysisId: string;
58
+ readonly config: MusicAnalysisV1["config"];
59
+ readonly createdAt: string;
60
+ readonly inputDigest: string;
61
+ readonly pcm: MonoPcm;
62
+ readonly subject: AnalysisSubject;
63
+ readonly tool: MusicAnalysisV1["tool"];
64
+ }
65
+
66
+ function clamp(value: number, minimum = 0, maximum = 1): number {
67
+ return Math.min(maximum, Math.max(minimum, value));
68
+ }
69
+
70
+ function median(values: readonly number[]): number {
71
+ if (values.length === 0) return 0;
72
+ const ordered = [...values].sort((left, right) => left - right);
73
+ const middle = Math.floor(ordered.length / 2);
74
+ return ordered.length % 2 === 0
75
+ ? ((ordered[middle - 1] ?? 0) + (ordered[middle] ?? 0)) / 2
76
+ : ordered[middle] ?? 0;
77
+ }
78
+
79
+ function sampleToUs(sample: number, sampleRateHz: number): number {
80
+ return Math.round(sample * MICROSECONDS_PER_SECOND / sampleRateHz);
81
+ }
82
+
83
+ function validatePcm(pcm: MonoPcm): void {
84
+ if (!Number.isSafeInteger(pcm.sampleRateHz) || pcm.sampleRateHz < 8_000 || pcm.sampleRateHz > 192_000) {
85
+ throw new TypeError("PCM sampleRateHz must be a safe integer from 8,000 through 192,000.");
86
+ }
87
+ if (pcm.samples.some(sample => !Number.isFinite(sample))) {
88
+ throw new TypeError("PCM samples must be finite numbers.");
89
+ }
90
+ }
91
+
92
+ function fftPowerSpectrum(samples: readonly number[]): Float64Array {
93
+ let size = 1;
94
+ while (size < samples.length) size *= 2;
95
+ const real = new Float64Array(size);
96
+ const imaginary = new Float64Array(size);
97
+ for (let index = 0; index < samples.length; index += 1) {
98
+ const window = samples.length === 1
99
+ ? 1
100
+ : 0.5 - 0.5 * Math.cos(2 * Math.PI * index / (samples.length - 1));
101
+ real[index] = (samples[index] ?? 0) * window;
102
+ }
103
+ for (let index = 1, reversed = 0; index < size; index += 1) {
104
+ let bit = size >> 1;
105
+ while ((reversed & bit) !== 0) {
106
+ reversed ^= bit;
107
+ bit >>= 1;
108
+ }
109
+ reversed ^= bit;
110
+ if (index < reversed) {
111
+ const temporary = real[index]!;
112
+ real[index] = real[reversed]!;
113
+ real[reversed] = temporary;
114
+ }
115
+ }
116
+ for (let length = 2; length <= size; length *= 2) {
117
+ const angle = -2 * Math.PI / length;
118
+ const stepReal = Math.cos(angle);
119
+ const stepImaginary = Math.sin(angle);
120
+ for (let block = 0; block < size; block += length) {
121
+ let twiddleReal = 1;
122
+ let twiddleImaginary = 0;
123
+ for (let offset = 0; offset < length / 2; offset += 1) {
124
+ const even = block + offset;
125
+ const odd = even + length / 2;
126
+ const oddReal = real[odd]! * twiddleReal - imaginary[odd]! * twiddleImaginary;
127
+ const oddImaginary = real[odd]! * twiddleImaginary + imaginary[odd]! * twiddleReal;
128
+ const evenReal = real[even]!;
129
+ const evenImaginary = imaginary[even]!;
130
+ real[even] = evenReal + oddReal;
131
+ imaginary[even] = evenImaginary + oddImaginary;
132
+ real[odd] = evenReal - oddReal;
133
+ imaginary[odd] = evenImaginary - oddImaginary;
134
+ const nextReal = twiddleReal * stepReal - twiddleImaginary * stepImaginary;
135
+ twiddleImaginary = twiddleReal * stepImaginary + twiddleImaginary * stepReal;
136
+ twiddleReal = nextReal;
137
+ }
138
+ }
139
+ }
140
+ const power = new Float64Array(size / 2 + 1);
141
+ const normalization = Math.max(1, samples.length * samples.length);
142
+ for (let index = 0; index < power.length; index += 1) {
143
+ power[index] = (real[index]! ** 2 + imaginary[index]! ** 2) / normalization;
144
+ }
145
+ return power;
146
+ }
147
+
148
+ /** Fold pitched energy from C2 through B6 into twelve pitch classes. */
149
+ export function computeChroma(frame: readonly number[], sampleRateHz: number): readonly number[] {
150
+ if (!Number.isSafeInteger(sampleRateHz) || sampleRateHz < 8_000 || sampleRateHz > 192_000) {
151
+ throw new TypeError("Chroma sampleRateHz must be a safe integer from 8,000 through 192,000.");
152
+ }
153
+ if (frame.some(sample => !Number.isFinite(sample))) throw new TypeError("Chroma samples must be finite.");
154
+ if (frame.length === 0) return Array.from({ length: 12 }, () => 0);
155
+ const spectrum = fftPowerSpectrum(frame);
156
+ const fftSize = (spectrum.length - 1) * 2;
157
+ const chroma = Array.from({ length: 12 }, () => 0);
158
+ for (let bin = 1; bin < spectrum.length; bin += 1) {
159
+ const frequencyHz = bin * sampleRateHz / fftSize;
160
+ if (frequencyHz < 65.406 || frequencyHz > Math.min(sampleRateHz * 0.475, 1_975.53)) continue;
161
+ const midi = Math.round(69 + 12 * Math.log2(frequencyHz / 440));
162
+ if (midi < 36 || midi > 95) continue;
163
+ const pitchClass = ((midi % 12) + 12) % 12;
164
+ chroma[pitchClass] = (chroma[pitchClass] ?? 0) + spectrum[bin]!;
165
+ }
166
+ return chroma;
167
+ }
168
+
169
+ function chromaTonality(chroma: readonly number[]): number {
170
+ const total = chroma.reduce((sum, value) => sum + Math.max(0, value), 0);
171
+ if (total <= EPSILON) return 0;
172
+ let entropy = 0;
173
+ for (const value of chroma) {
174
+ const probability = Math.max(0, value) / total;
175
+ if (probability > EPSILON) entropy -= probability * Math.log(probability);
176
+ }
177
+ return clamp(1 - entropy / Math.log(12));
178
+ }
179
+
180
+ /** Extract deterministic features from already-decoded mono PCM. */
181
+ export function extractPcmFrameFeatures(
182
+ pcm: MonoPcm,
183
+ config: PcmFeatureConfig,
184
+ ): readonly PcmFrameFeatures[] {
185
+ validatePcm(pcm);
186
+ if (!Number.isSafeInteger(config.windowSize) || config.windowSize <= 0) {
187
+ throw new TypeError("Feature windowSize must be a positive safe integer.");
188
+ }
189
+ if (!Number.isSafeInteger(config.hopSize) || config.hopSize <= 0) {
190
+ throw new TypeError("Feature hopSize must be a positive safe integer.");
191
+ }
192
+
193
+ const frames: PcmFrameFeatures[] = [];
194
+ let priorRms = 0;
195
+ let priorCrest = 0;
196
+ let priorNormalizedChroma = Array.from({ length: 12 }, () => 0);
197
+ for (let startSample = 0; startSample < pcm.samples.length; startSample += config.hopSize) {
198
+ const endSample = Math.min(pcm.samples.length, startSample + config.windowSize);
199
+ const frame = pcm.samples.slice(startSample, endSample);
200
+ if (frame.length === 0) break;
201
+ let squareSum = 0;
202
+ let peak = 0;
203
+ let crossings = 0;
204
+ for (let index = 0; index < frame.length; index += 1) {
205
+ const sample = frame[index] ?? 0;
206
+ squareSum += sample * sample;
207
+ peak = Math.max(peak, Math.abs(sample));
208
+ if (index > 0 && ((frame[index - 1] ?? 0) >= 0) !== (sample >= 0)) crossings += 1;
209
+ }
210
+ const rms = Math.sqrt(squareSum / frame.length);
211
+ const crest = rms <= EPSILON ? 0 : peak / rms;
212
+ const chroma = computeChroma(frame, pcm.sampleRateHz);
213
+ const chromaTotal = chroma.reduce((sum, value) => sum + value, 0);
214
+ const normalizedChroma = chroma.map(value => chromaTotal <= EPSILON ? 0 : value / chromaTotal);
215
+ const spectralIncrease = normalizedChroma.reduce(
216
+ (sum, value, index) => sum + Math.max(0, value - (priorNormalizedChroma[index] ?? 0)),
217
+ 0,
218
+ );
219
+ const onsetStrength = Math.max(0, rms - priorRms)
220
+ + rms * spectralIncrease
221
+ + Math.max(0, crest - priorCrest) * rms * 0.25;
222
+ frames.push({
223
+ chroma,
224
+ endSample,
225
+ endUs: sampleToUs(endSample, pcm.sampleRateHz),
226
+ onsetStrength,
227
+ peak,
228
+ rms,
229
+ startSample,
230
+ startUs: sampleToUs(startSample, pcm.sampleRateHz),
231
+ tonality: chromaTonality(chroma),
232
+ zeroCrossingRate: frame.length <= 1 ? 0 : crossings / (frame.length - 1),
233
+ });
234
+ priorNormalizedChroma = normalizedChroma;
235
+ priorRms = rms;
236
+ priorCrest = crest;
237
+ }
238
+ return frames;
239
+ }
240
+
241
+ interface ScoredRange extends SourceRange {
242
+ readonly confidence: number;
243
+ }
244
+
245
+ /** Turn frame energy and tonal concentration into ordered music-presence regions. */
246
+ export function detectMusicPresenceRegions(
247
+ frames: readonly PcmFrameFeatures[],
248
+ minimumMusicUs: number,
249
+ threshold = 0.45,
250
+ ): readonly MusicRegion[] {
251
+ if (!Number.isSafeInteger(minimumMusicUs) || minimumMusicUs < 0) {
252
+ throw new TypeError("minimumMusicUs must be a nonnegative safe integer.");
253
+ }
254
+ if (!Number.isFinite(threshold) || threshold < 0 || threshold > 1) {
255
+ throw new TypeError("Music presence threshold must be between zero and one.");
256
+ }
257
+ const active: ScoredRange[] = [];
258
+ for (const frame of frames) {
259
+ const energy = clamp((frame.rms - 0.0015) / 0.025);
260
+ const confidence = energy * (0.4 + 0.6 * frame.tonality);
261
+ if (confidence >= threshold) active.push({ confidence, endUs: frame.endUs, startUs: frame.startUs });
262
+ }
263
+ const merged: { confidenceSum: number; count: number; endUs: number; startUs: number }[] = [];
264
+ for (const range of active) {
265
+ const prior = merged.at(-1);
266
+ if (prior !== undefined && range.startUs <= prior.endUs) {
267
+ prior.endUs = Math.max(prior.endUs, range.endUs);
268
+ prior.confidenceSum += range.confidence;
269
+ prior.count += 1;
270
+ } else {
271
+ merged.push({ confidenceSum: range.confidence, count: 1, endUs: range.endUs, startUs: range.startUs });
272
+ }
273
+ }
274
+ return merged
275
+ .filter(region => region.endUs - region.startUs >= minimumMusicUs)
276
+ .map(region => ({
277
+ confidence: clamp(region.confidenceSum / region.count),
278
+ range: { endUs: region.endUs, startUs: region.startUs },
279
+ }));
280
+ }
281
+
282
+ function rangesOverlap(left: SourceRange, right: SourceRange): boolean {
283
+ return left.startUs < right.endUs && right.startUs < left.endUs;
284
+ }
285
+
286
+ function onsetTimes(frames: readonly PcmFrameFeatures[], range: SourceRange): readonly number[] {
287
+ const candidates = frames.filter(frame => {
288
+ const centerUs = Math.round((frame.startUs + frame.endUs) / 2);
289
+ return centerUs >= range.startUs && centerUs < range.endUs;
290
+ });
291
+ if (candidates.length < 3) return [];
292
+ const strengths = candidates.map(frame => frame.onsetStrength);
293
+ const center = median(strengths);
294
+ const deviation = median(strengths.map(value => Math.abs(value - center)));
295
+ const threshold = Math.max(1e-5, center + 2.5 * deviation);
296
+ const peaks: { strength: number; timeUs: number }[] = [];
297
+ for (let index = 0; index < candidates.length; index += 1) {
298
+ const frame = candidates[index]!;
299
+ if (
300
+ frame.onsetStrength < threshold
301
+ || frame.onsetStrength < (candidates[index - 1]?.onsetStrength ?? -1)
302
+ || frame.onsetStrength <= (candidates[index + 1]?.onsetStrength ?? -1)
303
+ ) continue;
304
+ const peak = { strength: frame.onsetStrength, timeUs: Math.round((frame.startUs + frame.endUs) / 2) };
305
+ const prior = peaks.at(-1);
306
+ if (prior !== undefined && peak.timeUs - prior.timeUs < MINIMUM_ONSET_SEPARATION_US) {
307
+ if (peak.strength > prior.strength) peaks[peaks.length - 1] = peak;
308
+ } else {
309
+ peaks.push(peak);
310
+ }
311
+ }
312
+ return peaks.map(peak => peak.timeUs);
313
+ }
314
+
315
+ /** Estimate tempo from a frame-onset sequence inside one source interval. */
316
+ export function estimateTempo(
317
+ frames: readonly PcmFrameFeatures[],
318
+ range: SourceRange,
319
+ ): TempoEstimate | null {
320
+ const beats = onsetTimes(frames, range);
321
+ const intervals = beats.slice(1).map((timeUs, index) => timeUs - (beats[index] ?? timeUs))
322
+ .filter(intervalUs => intervalUs >= 150_000 && intervalUs <= 3_000_000);
323
+ if (intervals.length < 2) return null;
324
+ const medianIntervalUs = median(intervals);
325
+ let bpm = 60 * MICROSECONDS_PER_SECOND / medianIntervalUs;
326
+ while (bpm > 220) bpm /= 2;
327
+ while (bpm < 40) bpm *= 2;
328
+ const deviation = median(intervals.map(interval => Math.abs(interval - medianIntervalUs)));
329
+ const regularity = clamp(1 - deviation / Math.max(1, medianIntervalUs * 0.2));
330
+ const confidence = clamp(regularity * Math.min(1, intervals.length / 4));
331
+ const alternatives = [bpm / 2, bpm * 2]
332
+ .filter(alternative => alternative >= 20 && alternative <= 400)
333
+ .map((alternative, index) => ({ bpm: alternative, confidence: confidence * (index === 0 ? 0.55 : 0.45) }));
334
+ return {
335
+ alternatives,
336
+ beatTimesUs: beats.filter(timeUs => timeUs >= range.startUs && timeUs < range.endUs),
337
+ bpm,
338
+ confidence,
339
+ meter: "unknown",
340
+ };
341
+ }
342
+
343
+ function profileCorrelation(chroma: readonly number[], profile: readonly number[], tonic: number): number {
344
+ const leftMean = chroma.reduce((sum, value) => sum + value, 0) / 12;
345
+ const rotated = Array.from({ length: 12 }, (_, pitchClass) => profile[(pitchClass - tonic + 12) % 12] ?? 0);
346
+ const rightMean = rotated.reduce((sum, value) => sum + value, 0) / 12;
347
+ let covariance = 0;
348
+ let leftEnergy = 0;
349
+ let rightEnergy = 0;
350
+ for (let pitchClass = 0; pitchClass < 12; pitchClass += 1) {
351
+ const left = (chroma[pitchClass] ?? 0) - leftMean;
352
+ const right = (rotated[pitchClass] ?? 0) - rightMean;
353
+ covariance += left * right;
354
+ leftEnergy += left * left;
355
+ rightEnergy += right * right;
356
+ }
357
+ const denominator = Math.sqrt(leftEnergy * rightEnergy);
358
+ return denominator <= EPSILON ? -1 : clamp(covariance / denominator, -1, 1);
359
+ }
360
+
361
+ /** Classify an aggregate twelve-bin chromagram using major/minor key profiles. */
362
+ export function classifyChroma(chroma: readonly number[]): KeyClassification {
363
+ if (chroma.length !== 12 || chroma.some(value => !Number.isFinite(value) || value < 0)) {
364
+ throw new TypeError("A chromagram must contain twelve finite nonnegative bins.");
365
+ }
366
+ if (chroma.reduce((sum, value) => sum + value, 0) <= EPSILON) {
367
+ return { alternate: null, confidence: 0, key: { kind: "unknown" } };
368
+ }
369
+ const candidates: { key: MusicalKey; score: number }[] = [];
370
+ for (let tonic = 0; tonic < 12; tonic += 1) {
371
+ candidates.push(
372
+ { key: { kind: "key", mode: "major", pitchClass: tonic }, score: profileCorrelation(chroma, MAJOR_PROFILE, tonic) },
373
+ { key: { kind: "key", mode: "minor", pitchClass: tonic }, score: profileCorrelation(chroma, MINOR_PROFILE, tonic) },
374
+ );
375
+ }
376
+ candidates.sort((left, right) => right.score - left.score
377
+ || (left.key.kind === "key" ? left.key.pitchClass : 0) - (right.key.kind === "key" ? right.key.pitchClass : 0)
378
+ || String(left.key.kind === "key" ? left.key.mode : "").localeCompare(String(right.key.kind === "key" ? right.key.mode : "")));
379
+ const best = candidates[0]!;
380
+ const alternate = candidates[1]!;
381
+ const absolute = clamp((best.score + 1) / 2);
382
+ const separation = clamp((best.score - alternate.score) / 0.35);
383
+ const confidence = clamp(0.65 * absolute + 0.35 * separation);
384
+ return {
385
+ alternate: { confidence: clamp((alternate.score + 1) / 2), key: alternate.key },
386
+ confidence,
387
+ key: best.score < 0.05 ? { kind: "unknown" } : best.key,
388
+ };
389
+ }
390
+
391
+ function aggregateChroma(frames: readonly PcmFrameFeatures[], range: SourceRange): readonly number[] {
392
+ const aggregate = Array.from({ length: 12 }, () => 0);
393
+ for (const frame of frames) {
394
+ if (!rangesOverlap(range, { endUs: frame.endUs, startUs: frame.startUs })) continue;
395
+ for (let pitchClass = 0; pitchClass < 12; pitchClass += 1) {
396
+ aggregate[pitchClass] = (aggregate[pitchClass] ?? 0) + (frame.chroma[pitchClass] ?? 0);
397
+ }
398
+ }
399
+ return aggregate;
400
+ }
401
+
402
+ function sameKey(left: MusicalKey, right: MusicalKey): boolean {
403
+ return left.kind === "unknown"
404
+ ? right.kind === "unknown"
405
+ : right.kind === "key" && left.mode === right.mode && left.pitchClass === right.pitchClass;
406
+ }
407
+
408
+ export function detectTempoRegions(
409
+ features: readonly PcmFrameFeatures[],
410
+ musicRegions: readonly MusicRegion[],
411
+ durationUs: number,
412
+ windowUs: number,
413
+ ): readonly TempoRegion[] {
414
+ if (!Number.isSafeInteger(windowUs) || windowUs <= 0) {
415
+ throw new TypeError("Tempo windowUs must be a positive safe integer.");
416
+ }
417
+ const raw: TempoRegion[] = [];
418
+ for (let startUs = 0; startUs < durationUs; startUs += windowUs) {
419
+ const range = { endUs: Math.min(durationUs, startUs + windowUs), startUs };
420
+ if (!musicRegions.some(region => rangesOverlap(range, region.range))) continue;
421
+ const estimate = estimateTempo(features, range);
422
+ if (estimate === null) continue;
423
+ raw.push({ ...estimate, changeFromPrevious: null, range });
424
+ }
425
+ const merged: TempoRegion[] = [];
426
+ for (const region of raw) {
427
+ const prior = merged.at(-1);
428
+ if (
429
+ prior !== undefined
430
+ && prior.range.endUs === region.range.startUs
431
+ && Math.abs(prior.bpm - region.bpm) <= Math.max(3, prior.bpm * 0.04)
432
+ ) {
433
+ const priorDuration = prior.range.endUs - prior.range.startUs;
434
+ const duration = region.range.endUs - region.range.startUs;
435
+ const totalDuration = priorDuration + duration;
436
+ merged[merged.length - 1] = {
437
+ ...prior,
438
+ beatTimesUs: [...new Set([...prior.beatTimesUs, ...region.beatTimesUs])].sort((left, right) => left - right),
439
+ bpm: (prior.bpm * priorDuration + region.bpm * duration) / totalDuration,
440
+ confidence: (prior.confidence * priorDuration + region.confidence * duration) / totalDuration,
441
+ range: { endUs: region.range.endUs, startUs: prior.range.startUs },
442
+ };
443
+ } else {
444
+ merged.push(region);
445
+ }
446
+ }
447
+ return merged.map((region, index) => {
448
+ const prior = merged[index - 1];
449
+ return {
450
+ ...region,
451
+ changeFromPrevious: prior === undefined ? null : {
452
+ confidence: clamp(Math.min(prior.confidence, region.confidence)
453
+ * Math.abs(region.bpm - prior.bpm) / Math.max(4, prior.bpm * 0.08)),
454
+ deltaBpm: region.bpm - prior.bpm,
455
+ },
456
+ };
457
+ });
458
+ }
459
+
460
+ export function detectKeyRegions(
461
+ features: readonly PcmFrameFeatures[],
462
+ musicRegions: readonly MusicRegion[],
463
+ windowUs: number,
464
+ ): readonly KeyRegion[] {
465
+ if (!Number.isSafeInteger(windowUs) || windowUs <= 0) {
466
+ throw new TypeError("Key windowUs must be a positive safe integer.");
467
+ }
468
+ const raw: KeyRegion[] = [];
469
+ for (const music of musicRegions) {
470
+ for (let startUs = music.range.startUs; startUs < music.range.endUs; startUs += windowUs) {
471
+ const range = { endUs: Math.min(music.range.endUs, startUs + windowUs), startUs };
472
+ const classification = classifyChroma(aggregateChroma(features, range));
473
+ raw.push({ ...classification, changeConfidence: null, range });
474
+ }
475
+ }
476
+ const merged: KeyRegion[] = [];
477
+ for (const region of raw) {
478
+ const prior = merged.at(-1);
479
+ if (prior !== undefined && prior.range.endUs === region.range.startUs && sameKey(prior.key, region.key)) {
480
+ merged[merged.length - 1] = {
481
+ ...prior,
482
+ confidence: (prior.confidence + region.confidence) / 2,
483
+ range: { endUs: region.range.endUs, startUs: prior.range.startUs },
484
+ };
485
+ } else {
486
+ merged.push(region);
487
+ }
488
+ }
489
+ return merged.map((region, index) => ({
490
+ ...region,
491
+ changeConfidence: index === 0 ? null : clamp(Math.min(region.confidence, merged[index - 1]?.confidence ?? 0)),
492
+ }));
493
+ }
494
+
495
+ /** Analyze decoded PCM into the persisted, provenance-bearing music contract. */
496
+ export function analyzeMusic(input: AnalyzeMusicInput): MusicAnalysisV1 {
497
+ validatePcm(input.pcm);
498
+ if (input.pcm.sampleRateHz !== input.config.sampleRateHz) {
499
+ throw new TypeError("PCM and analysis config sample rates must match.");
500
+ }
501
+ const features = extractPcmFrameFeatures(input.pcm, input.config);
502
+ const durationUs = sampleToUs(input.pcm.samples.length, input.pcm.sampleRateHz);
503
+ const musicRegions = detectMusicPresenceRegions(features, input.config.minimumMusicUs);
504
+ return MusicAnalysisV1Schema.parse({
505
+ analysisId: input.analysisId,
506
+ config: input.config,
507
+ createdAt: input.createdAt,
508
+ durationUs,
509
+ inputDigest: input.inputDigest,
510
+ keyRegions: detectKeyRegions(features, musicRegions, input.config.tempoWindowUs),
511
+ kind: "slopcamera.music-analysis",
512
+ musicRegions,
513
+ schemaVersion: 1,
514
+ subject: input.subject,
515
+ tempoRegions: detectTempoRegions(features, musicRegions, durationUs, input.config.tempoWindowUs),
516
+ tool: input.tool,
517
+ });
518
+ }