@dailies-studio/shot-editor 0.1.0 → 0.2.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 (432) hide show
  1. package/dist/.vite/manifest.json +382 -0
  2. package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
  3. package/dist/assets/AgentPanel-DJEphah0.css +1 -0
  4. package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
  5. package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
  6. package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
  7. package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
  8. package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
  9. package/dist/assets/CurvePane-BjFVabKX.css +1 -0
  10. package/dist/assets/CurvePane-R22WzllJ.js +1 -0
  11. package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
  12. package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
  13. package/dist/assets/FontPicker-v0IOIArM.css +1 -0
  14. package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
  15. package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
  16. package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
  17. package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
  18. package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
  19. package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
  20. package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
  21. package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
  22. package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
  23. package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
  24. package/dist/assets/SoundMode-BmcZru3i.js +3 -0
  25. package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
  26. package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
  27. package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
  28. package/dist/assets/board-B88EPuLO.css +1 -0
  29. package/dist/assets/board-CL-he7SL.js +3 -0
  30. package/dist/assets/components-0oOSPN0l.js +1 -0
  31. package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
  32. package/dist/assets/googleFonts-BNon6M7X.js +1 -0
  33. package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
  34. package/dist/assets/index-4F5NQmI8.css +1 -0
  35. package/dist/assets/index-BGHQGV5z.js +117 -0
  36. package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  37. package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  38. package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  39. package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  40. package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  41. package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  42. package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  43. package/dist/assets/libraryModel-CpIwfFha.js +1 -0
  44. package/dist/assets/lucide-6iK64tTB.js +9319 -0
  45. package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
  46. package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
  47. package/dist/assets/webmcp-BsB9gxWC.js +2 -0
  48. package/dist/index.html +34 -3
  49. package/package.json +12 -9
  50. package/src/addFamilies.ts +66 -0
  51. package/src/api.ts +1631 -55
  52. package/src/app/App.tsx +977 -198
  53. package/src/app/lazyPanel.tsx +137 -0
  54. package/src/app/lazySurfaces.tsx +203 -0
  55. package/src/audio/index.tsx +41 -10
  56. package/src/audio/meter.ts +485 -0
  57. package/src/audio/meterMath.ts +273 -0
  58. package/src/audio/meterStore.ts +155 -0
  59. package/src/audio/offlineMix.ts +289 -0
  60. package/src/audio/preview.ts +928 -118
  61. package/src/bridge.ts +15 -5
  62. package/src/bridgeTypes.ts +1611 -36
  63. package/src/channels.ts +17 -32
  64. package/src/fonts/googleFonts.ts +4 -0
  65. package/src/hostApi.ts +174 -0
  66. package/src/icons.tsx +770 -0
  67. package/src/layerWords.ts +61 -0
  68. package/src/main.tsx +56 -4
  69. package/src/panels/ResizeHandle.tsx +33 -43
  70. package/src/panels/agent/AgentDots.tsx +36 -0
  71. package/src/panels/agent/AgentPanel.tsx +119 -43
  72. package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
  73. package/src/panels/agent/agent.css +113 -97
  74. package/src/panels/agent/agentGate.ts +35 -0
  75. package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
  76. package/src/panels/code/CodeStudio.tsx +373 -0
  77. package/src/panels/code/FileTree.tsx +265 -0
  78. package/src/panels/code/codeBufferGuard.ts +52 -0
  79. package/src/panels/code/codeParts.tsx +321 -0
  80. package/src/panels/comments/CommentComposer.tsx +239 -0
  81. package/src/panels/comments/CommentThread.tsx +346 -0
  82. package/src/panels/comments/CommentsPanel.tsx +336 -0
  83. package/src/panels/comments/InboxButton.tsx +131 -0
  84. package/src/panels/comments/MentionMenu.tsx +98 -0
  85. package/src/panels/comments/parts.tsx +171 -0
  86. package/src/panels/export/ExportDialog.tsx +919 -96
  87. package/src/panels/export/ExportScrim.tsx +57 -0
  88. package/src/panels/files/AssetsTab.tsx +656 -0
  89. package/src/panels/files/FilePanel.tsx +1466 -216
  90. package/src/panels/files/LayerMarks.tsx +40 -0
  91. package/src/panels/files/LeftColumn.tsx +37 -0
  92. package/src/panels/files/McpSection.tsx +690 -47
  93. package/src/panels/files/MissingAssetsNotice.tsx +60 -0
  94. package/src/panels/files/assetDrag.ts +79 -0
  95. package/src/panels/files/resourceParts.tsx +255 -0
  96. package/src/panels/fonts/FontPicker.tsx +762 -0
  97. package/src/panels/fonts/faceCache.ts +207 -0
  98. package/src/panels/fonts/fontPicker.css +271 -0
  99. package/src/panels/fonts/pickerModel.ts +265 -0
  100. package/src/panels/fonts/pickerRequest.ts +46 -0
  101. package/src/panels/header/AccountAvatar.tsx +76 -0
  102. package/src/panels/header/AgentChip.tsx +77 -0
  103. package/src/panels/header/AppMenu.tsx +213 -0
  104. package/src/panels/header/Header.tsx +564 -84
  105. package/src/panels/header/HistoryControls.tsx +31 -5
  106. package/src/panels/header/PresenceStack.tsx +329 -0
  107. package/src/panels/header/ProjectTitle.tsx +232 -0
  108. package/src/panels/header/ShareButton.tsx +59 -0
  109. package/src/panels/header/StatusBadges.tsx +358 -27
  110. package/src/panels/header/SyncDeletedDialog.tsx +88 -0
  111. package/src/panels/header/ZoomControls.tsx +46 -15
  112. package/src/panels/header/agentHandoff.ts +52 -0
  113. package/src/panels/header/leave.ts +26 -0
  114. package/src/panels/help/HelpSection.tsx +189 -163
  115. package/src/panels/insert/InsertDrawer.tsx +723 -0
  116. package/src/panels/insert/iconSet.ts +109 -0
  117. package/src/panels/inspector/ExposeDot.tsx +117 -0
  118. package/src/panels/inspector/FramingRows.tsx +314 -44
  119. package/src/panels/inspector/InspectorFooter.tsx +91 -0
  120. package/src/panels/inspector/InspectorHead.tsx +425 -0
  121. package/src/panels/inspector/InspectorPanel.tsx +408 -124
  122. package/src/panels/inspector/KeyframeDot.tsx +373 -0
  123. package/src/panels/inspector/KeyframeSection.tsx +257 -0
  124. package/src/panels/inspector/MarkerSection.tsx +55 -11
  125. package/src/panels/inspector/ParamRow.tsx +84 -53
  126. package/src/panels/inspector/RightColumn.tsx +152 -0
  127. package/src/panels/inspector/TimeSection.tsx +866 -0
  128. package/src/panels/inspector/animate/EaseControl.tsx +244 -0
  129. package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
  130. package/src/panels/inspector/animate/ShotSection.tsx +562 -48
  131. package/src/panels/inspector/design/AlignRow.tsx +95 -31
  132. package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
  133. package/src/panels/inspector/design/ClonerSection.tsx +323 -62
  134. package/src/panels/inspector/design/ComponentSection.tsx +50 -43
  135. package/src/panels/inspector/design/CornerSection.tsx +45 -14
  136. package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
  137. package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
  138. package/src/panels/inspector/design/DeviceSection.tsx +210 -0
  139. package/src/panels/inspector/design/FillSection.tsx +83 -51
  140. package/src/panels/inspector/design/FontField.tsx +196 -0
  141. package/src/panels/inspector/design/HtmlSection.tsx +23 -12
  142. package/src/panels/inspector/design/InstanceSection.tsx +373 -154
  143. package/src/panels/inspector/design/MaskSection.tsx +84 -0
  144. package/src/panels/inspector/design/MaterialSection.tsx +177 -0
  145. package/src/panels/inspector/design/MediaSection.tsx +273 -120
  146. package/src/panels/inspector/design/MeshSection.tsx +910 -82
  147. package/src/panels/inspector/design/PaintRows.tsx +550 -0
  148. package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
  149. package/src/panels/inspector/design/ShadowSection.tsx +221 -48
  150. package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
  151. package/src/panels/inspector/design/StrokeSection.tsx +228 -43
  152. package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
  153. package/src/panels/inspector/design/TransformSection.tsx +244 -50
  154. package/src/panels/inspector/design/TypographySection.tsx +679 -110
  155. package/src/panels/inspector/layout.ts +295 -0
  156. package/src/panels/inspector/propValue.ts +199 -0
  157. package/src/panels/inspector/scene/CameraSection.tsx +232 -22
  158. package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
  159. package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
  160. package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
  161. package/src/panels/inspector/scene/SceneSection.tsx +219 -44
  162. package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
  163. package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
  164. package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
  165. package/src/panels/inspector/timeModel.ts +190 -0
  166. package/src/panels/inspector/useDocWrite.ts +438 -146
  167. package/src/panels/library/LibraryBrowser.tsx +435 -0
  168. package/src/panels/library/LibraryGrid.tsx +190 -0
  169. package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
  170. package/src/panels/library/LibraryRail.tsx +84 -0
  171. package/src/panels/library/LibrarySoundPanel.tsx +293 -0
  172. package/src/panels/library/SoundHistory.tsx +245 -0
  173. package/src/panels/library/StockSounds.tsx +390 -0
  174. package/src/panels/library/libraryModel.ts +147 -0
  175. package/src/panels/library/stockSearch.ts +77 -0
  176. package/src/panels/maskMarks.tsx +117 -0
  177. package/src/panels/material/MaterialCommentNode.tsx +54 -0
  178. package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
  179. package/src/panels/material/MaterialGraphMenu.tsx +45 -0
  180. package/src/panels/material/MaterialGroupNode.tsx +56 -0
  181. package/src/panels/material/MaterialLibrary.tsx +253 -0
  182. package/src/panels/material/MaterialNode.tsx +453 -0
  183. package/src/panels/material/MaterialNodePalette.tsx +349 -0
  184. package/src/panels/material/MaterialParameterControls.tsx +525 -0
  185. package/src/panels/material/MaterialRerouteNode.tsx +42 -0
  186. package/src/panels/material/MaterialWorkspace.tsx +501 -0
  187. package/src/panels/material/NodePreview.tsx +70 -0
  188. package/src/panels/material/flowModel.ts +441 -0
  189. package/src/panels/material/parameterValues.ts +231 -0
  190. package/src/panels/material/previewScheduler.ts +235 -0
  191. package/src/panels/material/reactFlow.css +6 -0
  192. package/src/panels/material/socketGlyph.ts +85 -0
  193. package/src/panels/material/viewportMemory.ts +58 -0
  194. package/src/panels/preferences/PreferencesDialog.tsx +215 -0
  195. package/src/panels/share/ShareComponentDialog.tsx +247 -0
  196. package/src/panels/stage/StageMenu.tsx +35 -5
  197. package/src/panels/stage/StagePanel.tsx +338 -23
  198. package/src/panels/stage/cellSlots.tsx +70 -0
  199. package/src/panels/storyboard/BoardCanvas.tsx +739 -0
  200. package/src/panels/storyboard/BoardCursors.tsx +158 -0
  201. package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
  202. package/src/panels/storyboard/BoardPlan.tsx +42 -0
  203. package/src/panels/storyboard/ComponentSheet.tsx +187 -0
  204. package/src/panels/storyboard/ComponentStage.tsx +235 -0
  205. package/src/panels/storyboard/MiniSketch.tsx +173 -0
  206. package/src/panels/storyboard/PartPicker.tsx +64 -0
  207. package/src/panels/storyboard/PlanPanel.tsx +617 -0
  208. package/src/panels/storyboard/ReferencePreview.tsx +217 -0
  209. package/src/panels/storyboard/ShotBrief.tsx +360 -0
  210. package/src/panels/storyboard/ShotCard.tsx +251 -0
  211. package/src/panels/storyboard/StagePreview.tsx +348 -0
  212. package/src/panels/storyboard/StillThumbs.tsx +162 -0
  213. package/src/panels/storyboard/StoryboardView.tsx +79 -0
  214. package/src/panels/storyboard/board.css +1065 -0
  215. package/src/panels/storyboard/board.ts +12 -0
  216. package/src/panels/storyboard/boardClipboard.ts +111 -0
  217. package/src/panels/storyboard/boardOrder.ts +68 -0
  218. package/src/panels/storyboard/boardRows.tsx +425 -0
  219. package/src/panels/storyboard/liveCap.ts +90 -0
  220. package/src/panels/storyboard/plan.css +295 -0
  221. package/src/panels/storyboard/posterCache.ts +133 -0
  222. package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
  223. package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
  224. package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
  225. package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
  226. package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
  227. package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
  228. package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
  229. package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
  230. package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
  231. package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
  232. package/src/panels/storyboard/shotboard/sketch.css +1392 -0
  233. package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
  234. package/src/panels/storyboard/storyboardDrop.ts +164 -0
  235. package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
  236. package/src/panels/studio/ComponentSidePanel.tsx +355 -0
  237. package/src/panels/studio/ComponentStudio.tsx +243 -0
  238. package/src/panels/studio/ComponentsHome.tsx +318 -0
  239. package/src/panels/studio/ComponentsPreview.tsx +179 -0
  240. package/src/panels/studio/EveryUse.tsx +244 -0
  241. package/src/panels/studio/ThisUsePanel.tsx +679 -0
  242. package/src/panels/studio/componentParts.tsx +341 -0
  243. package/src/panels/studio/components.ts +6 -0
  244. package/src/panels/studio/newCodeComponent.ts +66 -0
  245. package/src/panels/timeline/AudioFxButton.tsx +290 -0
  246. package/src/panels/timeline/AudioLane.tsx +674 -85
  247. package/src/panels/timeline/BlockRow.tsx +154 -85
  248. package/src/panels/timeline/ChannelRow.tsx +378 -129
  249. package/src/panels/timeline/CurvePane.tsx +508 -0
  250. package/src/panels/timeline/Filmstrip.tsx +361 -0
  251. package/src/panels/timeline/GenerateDrawer.tsx +407 -0
  252. package/src/panels/timeline/KeyStretch.tsx +115 -0
  253. package/src/panels/timeline/LifespanRow.tsx +730 -47
  254. package/src/panels/timeline/MarkerLane.tsx +46 -8
  255. package/src/panels/timeline/PlayButton.tsx +91 -0
  256. package/src/panels/timeline/PropertyPicker.tsx +254 -0
  257. package/src/panels/timeline/ShotRow.tsx +400 -0
  258. package/src/panels/timeline/SoundMode.tsx +1406 -0
  259. package/src/panels/timeline/TimelineDock.tsx +2338 -272
  260. package/src/panels/timeline/TransportVolume.tsx +163 -0
  261. package/src/panels/timeline/beatRuler.ts +74 -0
  262. package/src/panels/timeline/curveModel.ts +500 -0
  263. package/src/panels/timeline/duckUnderVoice.ts +68 -0
  264. package/src/panels/timeline/keyframeMenu.ts +138 -0
  265. package/src/panels/timeline/labelWidth.ts +49 -0
  266. package/src/panels/timeline/marquee.ts +155 -0
  267. package/src/panels/timeline/model.ts +1860 -199
  268. package/src/panels/timeline/snap.ts +125 -43
  269. package/src/panels/timeline/staggerPreview.tsx +92 -0
  270. package/src/panels/timeline/takeMeasure.ts +72 -0
  271. package/src/panels/timeline/useLive.ts +48 -0
  272. package/src/panels/timeline/viewport.ts +234 -0
  273. package/src/panels/toolbar/BackChip.tsx +116 -0
  274. package/src/panels/toolbar/CameraViewBar.tsx +118 -49
  275. package/src/panels/toolbar/CanvasBar.tsx +117 -0
  276. package/src/panels/toolbar/InsertButton.tsx +38 -0
  277. package/src/panels/toolbar/IsolationTrail.tsx +156 -0
  278. package/src/panels/toolbar/PanelToggles.tsx +86 -0
  279. package/src/panels/toolbar/ShadingBar.tsx +119 -26
  280. package/src/panels/toolbar/ToolDockBar.tsx +329 -80
  281. package/src/panels/toolbar/ToolFooter.tsx +102 -0
  282. package/src/panels/toolbar/ViewModeBar.tsx +247 -38
  283. package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
  284. package/src/panels/toolbar/useAddRows.tsx +326 -0
  285. package/src/panels/toolbar/useDismiss.ts +122 -0
  286. package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
  287. package/src/panels/versions/versionModel.ts +103 -0
  288. package/src/panels/virtualWindow.ts +271 -0
  289. package/src/shotTokens.css +151 -60
  290. package/src/stage/FrustumGizmo.tsx +504 -84
  291. package/src/stage/GridPlane.tsx +225 -16
  292. package/src/stage/NodePreviews.tsx +628 -0
  293. package/src/stage/PerfHud.tsx +437 -47
  294. package/src/stage/SplitRenderer.tsx +816 -169
  295. package/src/stage/StageApp.tsx +4274 -486
  296. package/src/stage/StageRootBoundary.tsx +114 -0
  297. package/src/stage/StageTools.tsx +651 -151
  298. package/src/stage/TransformGizmo.tsx +696 -0
  299. package/src/stage/adaptivePresentation.ts +145 -0
  300. package/src/stage/bridgeClient.ts +2522 -41
  301. package/src/stage/exportWorker.ts +9 -0
  302. package/src/stage/feed.ts +305 -14
  303. package/src/stage/fontPreview.ts +176 -0
  304. package/src/stage/frustumGeometry.ts +319 -1
  305. package/src/stage/furnitureTokens.ts +33 -0
  306. package/src/stage/gizmoMath.ts +508 -16
  307. package/src/stage/gpuTimer.ts +276 -0
  308. package/src/stage/instanceModules.ts +194 -33
  309. package/src/stage/interactions/move.ts +24 -5
  310. package/src/stage/isolation.ts +291 -33
  311. package/src/stage/jsSelfProfiler.ts +325 -0
  312. package/src/stage/main.tsx +116 -16
  313. package/src/stage/modalState.ts +222 -23
  314. package/src/stage/moduleEpoch.ts +60 -0
  315. package/src/stage/motionPathStage.ts +279 -0
  316. package/src/stage/motionPreview.ts +74 -0
  317. package/src/stage/overlays/Overlays.tsx +1112 -159
  318. package/src/stage/overlays/Rulers.tsx +495 -0
  319. package/src/stage/panes.ts +159 -10
  320. package/src/stage/project.ts +68 -3
  321. package/src/stage/renderScaleController.ts +730 -0
  322. package/src/stage/rulers.ts +129 -0
  323. package/src/stage/sceneExtent.ts +75 -0
  324. package/src/stage/shading.ts +20 -0
  325. package/src/stage/splineStage.ts +632 -0
  326. package/src/stage/stagePerformanceTelemetry.ts +717 -0
  327. package/src/stage/stageTheme.ts +36 -0
  328. package/src/stage/stageWarmup.ts +11 -0
  329. package/src/stage/textAutoSize.ts +124 -0
  330. package/src/stage/tier1Export.ts +641 -0
  331. package/src/stage/tokenColor.ts +14 -9
  332. package/src/stage/view.ts +56 -9
  333. package/src/stage/workingView.ts +277 -33
  334. package/src/state/accountPrefs.ts +261 -0
  335. package/src/state/agentActions.ts +173 -15
  336. package/src/state/agentActivity.ts +198 -0
  337. package/src/state/agentQuestion.ts +239 -0
  338. package/src/state/bootOpen.ts +42 -0
  339. package/src/state/cameraVerbs.ts +139 -0
  340. package/src/state/clientId.ts +6 -1
  341. package/src/state/comments.ts +1099 -0
  342. package/src/state/context.tsx +19 -1
  343. package/src/state/editorPrefs.ts +220 -0
  344. package/src/state/errorReports.ts +121 -0
  345. package/src/state/fieldWrite.ts +161 -0
  346. package/src/state/fontRecents.ts +70 -0
  347. package/src/state/fonts.ts +656 -0
  348. package/src/state/graphClipboard.ts +414 -0
  349. package/src/state/imageThumbs.ts +216 -0
  350. package/src/state/insert.ts +444 -67
  351. package/src/state/isolationTrail.ts +92 -0
  352. package/src/state/keyMoves.ts +134 -0
  353. package/src/state/keyframeClipboard.ts +177 -0
  354. package/src/state/keymap.ts +1153 -117
  355. package/src/state/layoutPrefs.ts +272 -0
  356. package/src/state/layouts.ts +205 -0
  357. package/src/state/measureMedia.ts +167 -0
  358. package/src/state/menu.ts +2341 -28
  359. package/src/state/motionStamp.ts +7 -0
  360. package/src/state/panelSize.ts +375 -5
  361. package/src/state/pendingChanges.ts +119 -0
  362. package/src/state/playHold.ts +78 -0
  363. package/src/state/playbackPreparation.ts +562 -0
  364. package/src/state/posterCapture.ts +242 -0
  365. package/src/state/presence.ts +254 -0
  366. package/src/state/projectComponents.ts +160 -0
  367. package/src/state/renderEvents.ts +298 -18
  368. package/src/state/runtimeErrors.ts +46 -0
  369. package/src/state/selection.ts +12 -0
  370. package/src/state/shortcuts.ts +1606 -0
  371. package/src/state/sketchGuide.ts +51 -0
  372. package/src/state/splineRefs.ts +87 -0
  373. package/src/state/stageHealth.ts +259 -0
  374. package/src/state/stageWatchdog.ts +47 -0
  375. package/src/state/stillStage.ts +330 -0
  376. package/src/state/stillThumbs.ts +474 -0
  377. package/src/state/store.ts +23591 -2982
  378. package/src/state/storyboardModel.ts +528 -0
  379. package/src/state/sync.ts +1257 -0
  380. package/src/state/syncCache.ts +159 -0
  381. package/src/state/triangles.ts +13 -0
  382. package/src/state/types.ts +1698 -55
  383. package/src/state/unsavedGuard.ts +49 -0
  384. package/src/state/viewMode.ts +200 -0
  385. package/src/state/webmcp.ts +1128 -0
  386. package/src/state/workspace.ts +1147 -0
  387. package/src/styles/assets-tab.css +304 -0
  388. package/src/styles/canvas.css +545 -0
  389. package/src/styles/comments.css +1435 -0
  390. package/src/styles/controls.css +162 -0
  391. package/src/styles/dialogs.css +390 -0
  392. package/src/styles/insert-drawer.css +586 -0
  393. package/src/styles/inspector-board.css +207 -0
  394. package/src/styles/inspector-object.css +486 -0
  395. package/src/styles/inspector-shot.css +252 -0
  396. package/src/styles/inspector.css +360 -0
  397. package/src/styles/left-column.css +1628 -0
  398. package/src/styles/library-preview.css +575 -0
  399. package/src/styles/library.css +512 -0
  400. package/src/styles/modes/code.css +569 -0
  401. package/src/styles/modes/component.css +1561 -0
  402. package/src/styles/modes/material.css +1201 -0
  403. package/src/styles/modes/storyboard.css +518 -0
  404. package/src/styles/share-component.css +49 -0
  405. package/src/styles/shell.css +653 -0
  406. package/src/styles/shot-brief.css +360 -0
  407. package/src/styles/states.css +76 -0
  408. package/src/styles/sync.css +380 -0
  409. package/src/styles/timeline-graph.css +384 -0
  410. package/src/styles/timeline-meters.css +89 -0
  411. package/src/styles/timeline-rows.css +1959 -0
  412. package/src/styles/timeline-sound.css +1003 -0
  413. package/src/styles/timeline.css +754 -0
  414. package/src/styles/tool-footer.css +525 -0
  415. package/src/styles/topbar.css +886 -0
  416. package/src/styles/versions.css +262 -0
  417. package/src/styles.css +72 -2921
  418. package/src/tools.ts +108 -23
  419. package/dist/assets/index-B7notge7.css +0 -1
  420. package/dist/assets/index-BBE6Ta9L.js +0 -52
  421. package/src/panels/files/icons.tsx +0 -118
  422. package/src/panels/header/DocSwitcher.tsx +0 -85
  423. package/src/panels/header/TransportBar.tsx +0 -86
  424. package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
  425. package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
  426. package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
  427. package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
  428. package/src/panels/inspector/animate/useBlocks.ts +0 -43
  429. package/src/panels/material/MaterialPicker.tsx +0 -172
  430. package/src/panels/timeline/SceneStrip.tsx +0 -145
  431. package/src/panels/toolbar/AddMenu.tsx +0 -230
  432. package/src/stage/MoveGizmo.tsx +0 -411
@@ -0,0 +1,273 @@
1
+ // THE METER'S BALLISTICS, AS A PLAIN FUNCTION, BECAUSE THE AUDIO THREAD IS THE
2
+ // ONE PLACE A UNIT TEST CANNOT GO.
3
+ //
4
+ // An AudioWorkletProcessor runs inside `AudioWorkletGlobalScope`: no DOM, no
5
+ // module graph a test can reach into, and no way to observe it except by
6
+ // building a real AudioContext and listening to what it posts. So the smoothing
7
+ // — which is the only part with an opinion in it — lives out here as an
8
+ // ordinary exported function, and the worklet gets its SOURCE TEXT rather than
9
+ // a second copy of the arithmetic. One implementation, and the tests drive it
10
+ // directly with no AudioContext at all.
11
+ //
12
+ // ── THE FUNCTION MUST BE SELF-CONTAINED, AND THAT IS A REAL CONSTRAINT ──
13
+ //
14
+ // `meterStep.toString()` is what crosses into the worklet. Anything the body
15
+ // references that is not a JavaScript builtin or one of its own parameters does
16
+ // not exist on the other side, and the failure is a `ReferenceError` inside an
17
+ // audio thread whose console output is easy to miss. So:
18
+ //
19
+ // NO module-level constants in the body. The tuning is a PARAMETER, and the
20
+ // worklet's source interpolates `METER_TUNING` as JSON beside the function.
21
+ // NO nested function declarations or arrow consts. esbuild's `keepNames`
22
+ // wraps every named function — nested ones included — in a module-scope
23
+ // `__name` helper, which does not survive serialisation. This repo has been
24
+ // bitten by exactly that once already, in the page-injected collector that
25
+ // threw `ReferenceError: __name is not defined` inside the page while every
26
+ // vitest run stayed green (vitest's transform omits `keepNames`). The body
27
+ // below therefore has no inner functions at all, and `meter.ts` defines a
28
+ // scoped `__name` in the worklet prelude anyway, so the class cannot come
29
+ // back through a future edit.
30
+ //
31
+ // NO RELIANCE ON THE NAME. The shipped chrome is minified, so this text
32
+ // reads `function Xe(…)` there. The worklet binds it by its own
33
+ // `const meterStep = (…)`, and a consumer must never call it by the name
34
+ // written below.
35
+ //
36
+ // `test/audioMeter.test.ts` proves the constraint rather than asserting it in
37
+ // prose: it rebuilds the function from `METER_STEP_SOURCE` alone, in a scope
38
+ // with nothing in it, and checks it answers what the imported one answers. It
39
+ // also builds the worklet from a minified bundle and runs `process` there.
40
+
41
+ /** What a meter's needle does, in milliseconds. */
42
+ export interface MeterTuning {
43
+ /** Rising. FAST, because a transient the eye never sees is a transient the
44
+ * author cannot mix against: a meter that misses the peak of a consonant
45
+ * reads 6 dB low on speech. */
46
+ attackMs: number;
47
+ /** Falling. SLOW, because a needle that tracks the waveform down is a blur.
48
+ * This is the number that makes a meter readable at all. */
49
+ releaseMs: number;
50
+ /** How long the peak marker STAYS where the loudest sample put it. */
51
+ holdMs: number;
52
+ /** How fast it falls once the hold expires. Slower than `releaseMs`, so the
53
+ * marker is still visibly above the bar when it starts to move. */
54
+ holdReleaseMs: number;
55
+ }
56
+
57
+ /**
58
+ * The house ballistics.
59
+ *
60
+ * ponytail: one set of numbers, no per-meter override. They are roughly a
61
+ * digital peak-programme meter's, which is the face every NLE mixer wears, and
62
+ * nothing in this chrome has asked for a second calibration. Add a parameter if
63
+ * a second meter is ever measured needing one.
64
+ */
65
+ export const METER_TUNING: MeterTuning = {
66
+ attackMs: 5,
67
+ releaseMs: 300,
68
+ holdMs: 1200,
69
+ holdReleaseMs: 900,
70
+ };
71
+
72
+ /** Linear amplitude, 0..1-ish. Not dB: the conversion is a FACE decision and a
73
+ * meter drawn in dB and a meter drawn on a linear bar want the same state. */
74
+ export interface MeterState {
75
+ rms: number;
76
+ peak: number;
77
+ /** The peak marker: the highest recent peak, held then released. */
78
+ hold: number;
79
+ /** Milliseconds of hold left before the marker starts falling. */
80
+ holdLeftMs: number;
81
+ }
82
+
83
+ /** Frozen, so every idle meter shares one snapshot identity —
84
+ * `useSyncExternalStore` compares by identity and a fresh object per read is
85
+ * an infinite render loop. */
86
+ export const METER_SILENT: MeterState = Object.freeze({ rms: 0, peak: 0, hold: 0, holdLeftMs: 0 });
87
+
88
+ /**
89
+ * One step of the meter, over `dtMs` of audio.
90
+ *
91
+ * SELF-CONTAINED by contract — see this file's header. Builtins only, no inner
92
+ * functions, and every tuning number arrives as a parameter.
93
+ *
94
+ * The smoothing is a one-pole filter whose coefficient is `exp(-dt / tau)`,
95
+ * which is the fraction of the OLD value still present after `dt`. Written that
96
+ * way rather than as a fixed per-block factor because the render quantum's
97
+ * duration depends on the context's sample rate: a coefficient tuned at 48 kHz
98
+ * would make the needle 8% faster on a 44.1 kHz device, and the author would be
99
+ * mixing against a different meter on a different machine.
100
+ *
101
+ * ATTACK AND RELEASE ARE THE SAME FILTER with different taus, chosen by
102
+ * direction. That is what a peak-programme meter is, and it is why a bar that
103
+ * simply averaged looked dead on speech.
104
+ *
105
+ * NON-FINITE IN IS ZERO, not propagated. A NaN sample (a source that has
106
+ * glitched, an uninitialised buffer) would otherwise poison the state for the
107
+ * life of the tap: every later `Math.exp` blend of a NaN is a NaN, the bar goes
108
+ * blank, and nothing says why.
109
+ */
110
+ export function meterStep(
111
+ state: MeterState,
112
+ rms: number,
113
+ peak: number,
114
+ dtMs: number,
115
+ tuning: MeterTuning,
116
+ ): MeterState {
117
+ const dt = Number.isFinite(dtMs) && dtMs > 0 ? dtMs : 0;
118
+ const inRms = Number.isFinite(rms) && rms > 0 ? rms : 0;
119
+ const inPeak = Number.isFinite(peak) && peak > 0 ? peak : 0;
120
+ const prevRms = Number.isFinite(state.rms) && state.rms > 0 ? state.rms : 0;
121
+ const prevPeak = Number.isFinite(state.peak) && state.peak > 0 ? state.peak : 0;
122
+ const attack = Math.exp(-dt / (tuning.attackMs > 0 ? tuning.attackMs : 1e-3));
123
+ const release = Math.exp(-dt / (tuning.releaseMs > 0 ? tuning.releaseMs : 1e-3));
124
+ const nextRms = inRms + (prevRms - inRms) * (inRms > prevRms ? attack : release);
125
+ const nextPeak = inPeak + (prevPeak - inPeak) * (inPeak > prevPeak ? attack : release);
126
+
127
+ // ── THE PEAK MARKER ──────────────────────────────────────────────────
128
+ //
129
+ // Against the RAW peak of this block and not against the smoothed one: the
130
+ // marker exists to record the loudest sample that actually happened, and
131
+ // reading it off a needle that is still climbing would under-report every
132
+ // transient by exactly the attack it just applied.
133
+ let hold = Number.isFinite(state.hold) && state.hold > 0 ? state.hold : 0;
134
+ let holdLeftMs = Number.isFinite(state.holdLeftMs) && state.holdLeftMs > 0 ? state.holdLeftMs : 0;
135
+ if (inPeak >= hold) {
136
+ hold = inPeak;
137
+ holdLeftMs = tuning.holdMs > 0 ? tuning.holdMs : 0;
138
+ } else if (holdLeftMs > dt) {
139
+ holdLeftMs = holdLeftMs - dt;
140
+ } else {
141
+ holdLeftMs = 0;
142
+ hold = hold * Math.exp(-dt / (tuning.holdReleaseMs > 0 ? tuning.holdReleaseMs : 1e-3));
143
+ // The marker may never fall BELOW the needle it marks: a hold that sank
144
+ // under the bar reads as a second, quieter peak that never happened.
145
+ if (hold < nextPeak) hold = nextPeak;
146
+ }
147
+ return { rms: nextRms, peak: nextPeak, hold, holdLeftMs };
148
+ }
149
+
150
+ /**
151
+ * The bytes the worklet gets.
152
+ *
153
+ * A module-level `toString()` and not a call per attach: the string is fixed
154
+ * for the life of the page, and reading it once is what lets `meter.ts` build
155
+ * ONE Blob URL for every context.
156
+ */
157
+ export const METER_STEP_SOURCE: string = meterStep.toString();
158
+
159
+ // ── WHEN A CHANGE IS WORTH TELLING ANYONE ABOUT ─────────────────────────
160
+ //
161
+ // The tap measures whether or not anything is sounding, and it measures for as
162
+ // long as a face is mounted. Without a gate that costs a structured clone
163
+ // across the thread boundary and a React commit per post, for ever, over a
164
+ // silent mix on a paused transport. The gate is the SAME question on both
165
+ // sides of the port ("could a face tell these two apart?"), so it lives here
166
+ // beside the ballistics and crosses into the worklet as text, exactly as
167
+ // `meterStep` does.
168
+
169
+ /**
170
+ * The bottom of the scale, in dB, and the ONE place it is spelled.
171
+ *
172
+ * It began as a face decision in the meter's component, and it is still drawn
173
+ * there (`meterPercent`, panels/timeline/SoundMode.tsx). It moved here because
174
+ * the publisher's "indistinguishable"
175
+ * test is defined against the scale the face draws: two levels below the floor
176
+ * are the same picture, and a second -60 in a second file is a drift that would
177
+ * show up as a meter that either dithered or went deaf.
178
+ */
179
+ export const METER_FLOOR_DB = -60;
180
+
181
+ /** The floor as LINEAR amplitude, which is the unit the state is kept in. */
182
+ export const METER_FLOOR = 10 ** (METER_FLOOR_DB / 20);
183
+
184
+ /**
185
+ * How far a level must move before anybody is told, in dB.
186
+ *
187
+ * PICKED OFF THE PIXELS, for the 72px header meter this was first drawn in:
188
+ * one DEVICE pixel there was 60 / (72 * dpr) dB, 0.21 dB even at dpr 4, so
189
+ * nothing suppressed could move it. The meters now live in the Sound mode's
190
+ * mixer (SoundMode.tsx), a continuous vertical bar per channel no taller than
191
+ * the strip, so the same arithmetic holds there. What it can also move is the
192
+ * readout's one decimal, by a tenth or two, and only for a level sitting
193
+ * nearly still: a moving one crosses the threshold on every post (see below).
194
+ *
195
+ * NOT COARSER, because the release is 300 ms and a 16 ms post is therefore
196
+ * 0.47 dB of fall: a 1 dB threshold would hold three posts back and then jump
197
+ * the needle three pixels, which is the stair-step a meter exists to not have.
198
+ * Not finer, because below a device pixel there is nothing left to show.
199
+ */
200
+ export const METER_VISIBLE_DB = 0.2;
201
+
202
+ /** The same threshold as an amplitude RATIO, so the test is two multiplies and
203
+ * no logarithm on the audio thread. */
204
+ export const METER_VISIBLE_RATIO = 10 ** (METER_VISIBLE_DB / 20);
205
+
206
+ /**
207
+ * Could a face tell these two apart? The publisher's gate, and the worklet's.
208
+ *
209
+ * SELF-CONTAINED by the same contract as `meterStep` (see this file's header):
210
+ * it crosses into the worklet as text, so the floor and the ratio arrive as
211
+ * parameters and there are no inner functions in the body.
212
+ *
213
+ * The candidate arrives as three LOOSE FLOATS rather than as a state object
214
+ * because both callers hold them loose (the port hands the main thread a
215
+ * `Float32Array`, the worklet holds its own fields), and building an object to
216
+ * ask the question would be the allocation the question exists to avoid.
217
+ *
218
+ * BELOW THE FLOOR EVERYTHING IS ONE LEVEL. The face draws 0% for all of it, and
219
+ * a release that keeps halving toward a denormal would otherwise notify for
220
+ * ever without ever changing the picture. Above the floor the test is a RATIO
221
+ * and not a difference, because the bar is logarithmic: 0.001 of amplitude is
222
+ * 9 dB of bar near the floor and a thousandth of a pixel at the top, so a
223
+ * linear epsilon would either dither down there or go deaf up here.
224
+ *
225
+ * `holdLeftMs` IS DELIBERATELY NOT COMPARED. Nothing draws it: it is the
226
+ * marker's own countdown, and the marker's POSITION is `hold`, which is
227
+ * compared. Notifying on it alone would be the 62 Hz churn back again with no
228
+ * pixel to show for it.
229
+ */
230
+ export function meterVisiblyDiffers(
231
+ prev: MeterState,
232
+ rms: number,
233
+ peak: number,
234
+ hold: number,
235
+ floor: number,
236
+ ratio: number,
237
+ ): boolean {
238
+ // Written out three times, with two reused locals and no inner helper: a
239
+ // nested function does not survive the trip into the worklet.
240
+ let a = prev.rms > floor ? prev.rms : 0;
241
+ let b = rms > floor ? rms : 0;
242
+ if ((a > b ? a : b) > (a < b ? a : b) * ratio) return true;
243
+ a = prev.peak > floor ? prev.peak : 0;
244
+ b = peak > floor ? peak : 0;
245
+ if ((a > b ? a : b) > (a < b ? a : b) * ratio) return true;
246
+ a = prev.hold > floor ? prev.hold : 0;
247
+ b = hold > floor ? hold : 0;
248
+ return (a > b ? a : b) > (a < b ? a : b) * ratio;
249
+ }
250
+
251
+ /** The gate's bytes, for the worklet. Same reason as `METER_STEP_SOURCE`. */
252
+ export const METER_DIFFERS_SOURCE: string = meterVisiblyDiffers.toString();
253
+
254
+ /** How often the processor posts AT MOST, in milliseconds. About one animation
255
+ * frame: faster is invisible and costs a structured clone per post, slower is
256
+ * a meter that stutters against the picture. A post is only made when
257
+ * `meterVisiblyDiffers` says the picture changed, so this is a ceiling and not
258
+ * a cadence. */
259
+ export const METER_POST_MS = 16;
260
+
261
+ /** The channels a tap measures, in post order: the master is STEREO, and a
262
+ * mono source is upmixed to both by the node's own channel rules (`meter.ts`,
263
+ * `createNode`), so it reads as equal L and R. */
264
+ export const METER_CHANNELS = ["L", "R"] as const;
265
+ export type MeterChannel = (typeof METER_CHANNELS)[number];
266
+
267
+ /** The numbers ONE channel carries, in order: rms, peak, hold. */
268
+ export const METER_FIELDS = 3;
269
+
270
+ /** What a post carries: each channel's three numbers, L first. A
271
+ * `Float32Array` of exactly this length, so the reader indexes rather than
272
+ * parsing. The worklet and the `onmessage` guard both read it from here. */
273
+ export const METER_MESSAGE_LENGTH = METER_FIELDS * METER_CHANNELS.length;
@@ -0,0 +1,155 @@
1
+ // WHERE A METER'S NUMBERS LIVE, AND IT IS NOT THE SHOT STORE.
2
+ //
3
+ // A tap posts about sixty times a second, for as long as anything is sounding.
4
+ // There are exactly two channels in this chrome and neither of them may carry
5
+ // that:
6
+ //
7
+ // THE SHOT STORE is what every panel in the shell subscribes to, and its
8
+ // notifications are what re-derive the inspector, the outline and the dock.
9
+ // A level is not document state, does not survive a reload, and must never
10
+ // enter the undo history — putting it there would make a meter tick an edit.
11
+ // THE HOT CHANNEL (`useShotEditorHot`) is THE FRAME. Its contract is "the
12
+ // smallest leaf that must repaint every tick", and a level is neither the
13
+ // frame nor on the frame's schedule: a paused transport still meters a
14
+ // monitored input, and a playing one posts on the AUDIO clock.
15
+ //
16
+ // So this is a lane-local store on the same terms `panels/timeline/viewport.ts`
17
+ // is one, and its header states the rule both obey.
18
+ //
19
+ // ── IDENTITY IS THE WHOLE CONTRACT ──────────────────────────────────────
20
+ //
21
+ // `useSyncExternalStore` compares snapshots with `Object.is` and calls its
22
+ // getter during render, so `read` must return the SAME object until the value
23
+ // actually changes and must never allocate. An unknown key answers one frozen
24
+ // silent reading rather than a fresh zero object, which is what stops a mounted
25
+ // meter over a channel nobody is tapping from re-rendering for ever.
26
+ import {
27
+ METER_FLOOR,
28
+ METER_SILENT,
29
+ METER_VISIBLE_RATIO,
30
+ meterVisiblyDiffers,
31
+ } from "./meterMath.js";
32
+ import type { MeterState } from "./meterMath.js";
33
+
34
+ /** What a face reads. The full `MeterState` — `holdLeftMs` included — because
35
+ * the tap owns the ballistics and a second decay out here would be a second
36
+ * set of ballistics that disagreed with the first. */
37
+ export type MeterReading = MeterState;
38
+
39
+ export { METER_SILENT };
40
+
41
+ interface Entry {
42
+ reading: MeterReading;
43
+ listeners: Set<() => void>;
44
+ }
45
+
46
+ /**
47
+ * Keyed by a STRING, not by the GainNode.
48
+ *
49
+ * The node is the tap's business (`meter.ts` keys its WeakMap on it), and a
50
+ * face that had to hold a live audio node in order to draw a bar would be a
51
+ * React component pinning a Web Audio graph across every re-render. A key is a
52
+ * name the chrome chooses — "master", a clip id — so a face can subscribe
53
+ * before the tap exists and keep drawing silence until it does.
54
+ */
55
+ const ENTRIES = new Map<string, Entry>();
56
+
57
+ /** The current reading. Stable by identity between publishes, and it never
58
+ * creates an entry: a getter with a side effect is a subscription attached
59
+ * from a render React may throw away. */
60
+ export function readMeter(key: string): MeterReading {
61
+ return ENTRIES.get(key)?.reading ?? METER_SILENT;
62
+ }
63
+
64
+ /** Subscribe to one channel. Returns the unsubscribe, which drops the entry
65
+ * with the last listener AND with no reading left to serve, so a meter that
66
+ * unmounts leaves nothing behind in the map. */
67
+ export function subscribeMeter(key: string, listener: () => void): () => void {
68
+ let entry = ENTRIES.get(key);
69
+ if (entry === undefined) {
70
+ entry = { reading: METER_SILENT, listeners: new Set() };
71
+ ENTRIES.set(key, entry);
72
+ }
73
+ entry.listeners.add(listener);
74
+ const held = entry;
75
+ return () => {
76
+ held.listeners.delete(listener);
77
+ if (held.listeners.size === 0 && held.reading === METER_SILENT) ENTRIES.delete(key);
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Publish one channel's reading. Called from a `MessagePort` handler at the
83
+ * post rate, so it does exactly three things: decide whether anybody could
84
+ * SEE the difference, replace the object, and notify.
85
+ *
86
+ * ── THE GATE IS THE CONTRACT AT THE TOP OF THIS FILE, ENFORCED ──────────
87
+ *
88
+ * "`read` must return the SAME object until the value actually changes" is a
89
+ * promise the READER cannot keep on its own. A publisher that swapped the
90
+ * object on every post kept the letter of it and broke the sense: a mounted
91
+ * meter re-rendered at the worklet's post rate for ever, playing or not,
92
+ * silent or not, because every post was a new identity. So the publisher is
93
+ * what decides that the value actually changed, and `meterVisiblyDiffers`
94
+ * (meterMath.ts) is the definition of "actually": a move the face could not
95
+ * draw is not a change, and the same question is asked on the audio thread
96
+ * before the post is even made.
97
+ *
98
+ * SILENCE LANDS ON THE FROZEN SINGLETON, exactly, rather than on a small
99
+ * number near it. A release is asymptotic and never reaches zero, so without
100
+ * this the lane's rest state would be whatever amplitude the decay happened to
101
+ * be at when the gate closed: `readMeter` would hand back a private object
102
+ * instead of `METER_SILENT`, and `subscribeMeter`'s unsubscribe, which drops
103
+ * an entry only when its reading IS `METER_SILENT`, would leak the entry.
104
+ *
105
+ * A listener that THROWS must not stop the others hearing about it. This lane
106
+ * is decoration: a meter face that blew up may not take the mixer's other bars
107
+ * down with it, which is the posture the filmstrip loader's notify already
108
+ * takes for the same reason.
109
+ */
110
+ export function publishMeter(key: string, reading: MeterReading): void {
111
+ const entry = ENTRIES.get(key);
112
+ const prev = entry === undefined ? METER_SILENT : entry.reading;
113
+ if (!meterVisiblyDiffers(prev, reading.rms, reading.peak, reading.hold, METER_FLOOR, METER_VISIBLE_RATIO)) {
114
+ return;
115
+ }
116
+ const next =
117
+ reading.rms <= METER_FLOOR && reading.peak <= METER_FLOOR && reading.hold <= METER_FLOOR
118
+ ? METER_SILENT
119
+ : reading;
120
+ if (entry === undefined) {
121
+ ENTRIES.set(key, { reading: next, listeners: new Set() });
122
+ return;
123
+ }
124
+ entry.reading = next;
125
+ for (const listener of [...entry.listeners]) {
126
+ try {
127
+ listener();
128
+ } catch {
129
+ // See above.
130
+ }
131
+ }
132
+ }
133
+
134
+ /**
135
+ * The channel has stopped being tapped: fall to silence and say so.
136
+ *
137
+ * NOT a delete when someone is listening. A face subscribed to a channel whose
138
+ * tap just detached has to be told, or it keeps the last level it was handed
139
+ * painted on screen for ever — a meter frozen at -6 dB over a silent mix, which
140
+ * reads as a signal rather than as a missing one.
141
+ */
142
+ export function clearMeter(key: string): void {
143
+ const entry = ENTRIES.get(key);
144
+ if (entry === undefined) return;
145
+ if (entry.listeners.size === 0) {
146
+ ENTRIES.delete(key);
147
+ return;
148
+ }
149
+ publishMeter(key, METER_SILENT);
150
+ }
151
+
152
+ /** Test hook, and the one way to empty the map without a subscriber churn. */
153
+ export function resetMeters(): void {
154
+ ENTRIES.clear();
155
+ }