@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.
- package/dist/.vite/manifest.json +382 -0
- package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
- package/dist/assets/AgentPanel-DJEphah0.css +1 -0
- package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
- package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
- package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
- package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
- package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
- package/dist/assets/CurvePane-BjFVabKX.css +1 -0
- package/dist/assets/CurvePane-R22WzllJ.js +1 -0
- package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
- package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
- package/dist/assets/FontPicker-v0IOIArM.css +1 -0
- package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
- package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
- package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
- package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
- package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
- package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
- package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
- package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
- package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
- package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
- package/dist/assets/SoundMode-BmcZru3i.js +3 -0
- package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
- package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
- package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
- package/dist/assets/board-B88EPuLO.css +1 -0
- package/dist/assets/board-CL-he7SL.js +3 -0
- package/dist/assets/components-0oOSPN0l.js +1 -0
- package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
- package/dist/assets/googleFonts-BNon6M7X.js +1 -0
- package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
- package/dist/assets/index-4F5NQmI8.css +1 -0
- package/dist/assets/index-BGHQGV5z.js +117 -0
- package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/assets/libraryModel-CpIwfFha.js +1 -0
- package/dist/assets/lucide-6iK64tTB.js +9319 -0
- package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
- package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
- package/dist/assets/webmcp-BsB9gxWC.js +2 -0
- package/dist/index.html +34 -3
- package/package.json +12 -9
- package/src/addFamilies.ts +66 -0
- package/src/api.ts +1631 -55
- package/src/app/App.tsx +977 -198
- package/src/app/lazyPanel.tsx +137 -0
- package/src/app/lazySurfaces.tsx +203 -0
- package/src/audio/index.tsx +41 -10
- package/src/audio/meter.ts +485 -0
- package/src/audio/meterMath.ts +273 -0
- package/src/audio/meterStore.ts +155 -0
- package/src/audio/offlineMix.ts +289 -0
- package/src/audio/preview.ts +928 -118
- package/src/bridge.ts +15 -5
- package/src/bridgeTypes.ts +1611 -36
- package/src/channels.ts +17 -32
- package/src/fonts/googleFonts.ts +4 -0
- package/src/hostApi.ts +174 -0
- package/src/icons.tsx +770 -0
- package/src/layerWords.ts +61 -0
- package/src/main.tsx +56 -4
- package/src/panels/ResizeHandle.tsx +33 -43
- package/src/panels/agent/AgentDots.tsx +36 -0
- package/src/panels/agent/AgentPanel.tsx +119 -43
- package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
- package/src/panels/agent/agent.css +113 -97
- package/src/panels/agent/agentGate.ts +35 -0
- package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
- package/src/panels/code/CodeStudio.tsx +373 -0
- package/src/panels/code/FileTree.tsx +265 -0
- package/src/panels/code/codeBufferGuard.ts +52 -0
- package/src/panels/code/codeParts.tsx +321 -0
- package/src/panels/comments/CommentComposer.tsx +239 -0
- package/src/panels/comments/CommentThread.tsx +346 -0
- package/src/panels/comments/CommentsPanel.tsx +336 -0
- package/src/panels/comments/InboxButton.tsx +131 -0
- package/src/panels/comments/MentionMenu.tsx +98 -0
- package/src/panels/comments/parts.tsx +171 -0
- package/src/panels/export/ExportDialog.tsx +919 -96
- package/src/panels/export/ExportScrim.tsx +57 -0
- package/src/panels/files/AssetsTab.tsx +656 -0
- package/src/panels/files/FilePanel.tsx +1466 -216
- package/src/panels/files/LayerMarks.tsx +40 -0
- package/src/panels/files/LeftColumn.tsx +37 -0
- package/src/panels/files/McpSection.tsx +690 -47
- package/src/panels/files/MissingAssetsNotice.tsx +60 -0
- package/src/panels/files/assetDrag.ts +79 -0
- package/src/panels/files/resourceParts.tsx +255 -0
- package/src/panels/fonts/FontPicker.tsx +762 -0
- package/src/panels/fonts/faceCache.ts +207 -0
- package/src/panels/fonts/fontPicker.css +271 -0
- package/src/panels/fonts/pickerModel.ts +265 -0
- package/src/panels/fonts/pickerRequest.ts +46 -0
- package/src/panels/header/AccountAvatar.tsx +76 -0
- package/src/panels/header/AgentChip.tsx +77 -0
- package/src/panels/header/AppMenu.tsx +213 -0
- package/src/panels/header/Header.tsx +564 -84
- package/src/panels/header/HistoryControls.tsx +31 -5
- package/src/panels/header/PresenceStack.tsx +329 -0
- package/src/panels/header/ProjectTitle.tsx +232 -0
- package/src/panels/header/ShareButton.tsx +59 -0
- package/src/panels/header/StatusBadges.tsx +358 -27
- package/src/panels/header/SyncDeletedDialog.tsx +88 -0
- package/src/panels/header/ZoomControls.tsx +46 -15
- package/src/panels/header/agentHandoff.ts +52 -0
- package/src/panels/header/leave.ts +26 -0
- package/src/panels/help/HelpSection.tsx +189 -163
- package/src/panels/insert/InsertDrawer.tsx +723 -0
- package/src/panels/insert/iconSet.ts +109 -0
- package/src/panels/inspector/ExposeDot.tsx +117 -0
- package/src/panels/inspector/FramingRows.tsx +314 -44
- package/src/panels/inspector/InspectorFooter.tsx +91 -0
- package/src/panels/inspector/InspectorHead.tsx +425 -0
- package/src/panels/inspector/InspectorPanel.tsx +408 -124
- package/src/panels/inspector/KeyframeDot.tsx +373 -0
- package/src/panels/inspector/KeyframeSection.tsx +257 -0
- package/src/panels/inspector/MarkerSection.tsx +55 -11
- package/src/panels/inspector/ParamRow.tsx +84 -53
- package/src/panels/inspector/RightColumn.tsx +152 -0
- package/src/panels/inspector/TimeSection.tsx +866 -0
- package/src/panels/inspector/animate/EaseControl.tsx +244 -0
- package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
- package/src/panels/inspector/animate/ShotSection.tsx +562 -48
- package/src/panels/inspector/design/AlignRow.tsx +95 -31
- package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
- package/src/panels/inspector/design/ClonerSection.tsx +323 -62
- package/src/panels/inspector/design/ComponentSection.tsx +50 -43
- package/src/panels/inspector/design/CornerSection.tsx +45 -14
- package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
- package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
- package/src/panels/inspector/design/DeviceSection.tsx +210 -0
- package/src/panels/inspector/design/FillSection.tsx +83 -51
- package/src/panels/inspector/design/FontField.tsx +196 -0
- package/src/panels/inspector/design/HtmlSection.tsx +23 -12
- package/src/panels/inspector/design/InstanceSection.tsx +373 -154
- package/src/panels/inspector/design/MaskSection.tsx +84 -0
- package/src/panels/inspector/design/MaterialSection.tsx +177 -0
- package/src/panels/inspector/design/MediaSection.tsx +273 -120
- package/src/panels/inspector/design/MeshSection.tsx +910 -82
- package/src/panels/inspector/design/PaintRows.tsx +550 -0
- package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
- package/src/panels/inspector/design/ShadowSection.tsx +221 -48
- package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
- package/src/panels/inspector/design/StrokeSection.tsx +228 -43
- package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
- package/src/panels/inspector/design/TransformSection.tsx +244 -50
- package/src/panels/inspector/design/TypographySection.tsx +679 -110
- package/src/panels/inspector/layout.ts +295 -0
- package/src/panels/inspector/propValue.ts +199 -0
- package/src/panels/inspector/scene/CameraSection.tsx +232 -22
- package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
- package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
- package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
- package/src/panels/inspector/scene/SceneSection.tsx +219 -44
- package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
- package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
- package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
- package/src/panels/inspector/timeModel.ts +190 -0
- package/src/panels/inspector/useDocWrite.ts +438 -146
- package/src/panels/library/LibraryBrowser.tsx +435 -0
- package/src/panels/library/LibraryGrid.tsx +190 -0
- package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
- package/src/panels/library/LibraryRail.tsx +84 -0
- package/src/panels/library/LibrarySoundPanel.tsx +293 -0
- package/src/panels/library/SoundHistory.tsx +245 -0
- package/src/panels/library/StockSounds.tsx +390 -0
- package/src/panels/library/libraryModel.ts +147 -0
- package/src/panels/library/stockSearch.ts +77 -0
- package/src/panels/maskMarks.tsx +117 -0
- package/src/panels/material/MaterialCommentNode.tsx +54 -0
- package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
- package/src/panels/material/MaterialGraphMenu.tsx +45 -0
- package/src/panels/material/MaterialGroupNode.tsx +56 -0
- package/src/panels/material/MaterialLibrary.tsx +253 -0
- package/src/panels/material/MaterialNode.tsx +453 -0
- package/src/panels/material/MaterialNodePalette.tsx +349 -0
- package/src/panels/material/MaterialParameterControls.tsx +525 -0
- package/src/panels/material/MaterialRerouteNode.tsx +42 -0
- package/src/panels/material/MaterialWorkspace.tsx +501 -0
- package/src/panels/material/NodePreview.tsx +70 -0
- package/src/panels/material/flowModel.ts +441 -0
- package/src/panels/material/parameterValues.ts +231 -0
- package/src/panels/material/previewScheduler.ts +235 -0
- package/src/panels/material/reactFlow.css +6 -0
- package/src/panels/material/socketGlyph.ts +85 -0
- package/src/panels/material/viewportMemory.ts +58 -0
- package/src/panels/preferences/PreferencesDialog.tsx +215 -0
- package/src/panels/share/ShareComponentDialog.tsx +247 -0
- package/src/panels/stage/StageMenu.tsx +35 -5
- package/src/panels/stage/StagePanel.tsx +338 -23
- package/src/panels/stage/cellSlots.tsx +70 -0
- package/src/panels/storyboard/BoardCanvas.tsx +739 -0
- package/src/panels/storyboard/BoardCursors.tsx +158 -0
- package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
- package/src/panels/storyboard/BoardPlan.tsx +42 -0
- package/src/panels/storyboard/ComponentSheet.tsx +187 -0
- package/src/panels/storyboard/ComponentStage.tsx +235 -0
- package/src/panels/storyboard/MiniSketch.tsx +173 -0
- package/src/panels/storyboard/PartPicker.tsx +64 -0
- package/src/panels/storyboard/PlanPanel.tsx +617 -0
- package/src/panels/storyboard/ReferencePreview.tsx +217 -0
- package/src/panels/storyboard/ShotBrief.tsx +360 -0
- package/src/panels/storyboard/ShotCard.tsx +251 -0
- package/src/panels/storyboard/StagePreview.tsx +348 -0
- package/src/panels/storyboard/StillThumbs.tsx +162 -0
- package/src/panels/storyboard/StoryboardView.tsx +79 -0
- package/src/panels/storyboard/board.css +1065 -0
- package/src/panels/storyboard/board.ts +12 -0
- package/src/panels/storyboard/boardClipboard.ts +111 -0
- package/src/panels/storyboard/boardOrder.ts +68 -0
- package/src/panels/storyboard/boardRows.tsx +425 -0
- package/src/panels/storyboard/liveCap.ts +90 -0
- package/src/panels/storyboard/plan.css +295 -0
- package/src/panels/storyboard/posterCache.ts +133 -0
- package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
- package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
- package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
- package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
- package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
- package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
- package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
- package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
- package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
- package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
- package/src/panels/storyboard/shotboard/sketch.css +1392 -0
- package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
- package/src/panels/storyboard/storyboardDrop.ts +164 -0
- package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
- package/src/panels/studio/ComponentSidePanel.tsx +355 -0
- package/src/panels/studio/ComponentStudio.tsx +243 -0
- package/src/panels/studio/ComponentsHome.tsx +318 -0
- package/src/panels/studio/ComponentsPreview.tsx +179 -0
- package/src/panels/studio/EveryUse.tsx +244 -0
- package/src/panels/studio/ThisUsePanel.tsx +679 -0
- package/src/panels/studio/componentParts.tsx +341 -0
- package/src/panels/studio/components.ts +6 -0
- package/src/panels/studio/newCodeComponent.ts +66 -0
- package/src/panels/timeline/AudioFxButton.tsx +290 -0
- package/src/panels/timeline/AudioLane.tsx +674 -85
- package/src/panels/timeline/BlockRow.tsx +154 -85
- package/src/panels/timeline/ChannelRow.tsx +378 -129
- package/src/panels/timeline/CurvePane.tsx +508 -0
- package/src/panels/timeline/Filmstrip.tsx +361 -0
- package/src/panels/timeline/GenerateDrawer.tsx +407 -0
- package/src/panels/timeline/KeyStretch.tsx +115 -0
- package/src/panels/timeline/LifespanRow.tsx +730 -47
- package/src/panels/timeline/MarkerLane.tsx +46 -8
- package/src/panels/timeline/PlayButton.tsx +91 -0
- package/src/panels/timeline/PropertyPicker.tsx +254 -0
- package/src/panels/timeline/ShotRow.tsx +400 -0
- package/src/panels/timeline/SoundMode.tsx +1406 -0
- package/src/panels/timeline/TimelineDock.tsx +2338 -272
- package/src/panels/timeline/TransportVolume.tsx +163 -0
- package/src/panels/timeline/beatRuler.ts +74 -0
- package/src/panels/timeline/curveModel.ts +500 -0
- package/src/panels/timeline/duckUnderVoice.ts +68 -0
- package/src/panels/timeline/keyframeMenu.ts +138 -0
- package/src/panels/timeline/labelWidth.ts +49 -0
- package/src/panels/timeline/marquee.ts +155 -0
- package/src/panels/timeline/model.ts +1860 -199
- package/src/panels/timeline/snap.ts +125 -43
- package/src/panels/timeline/staggerPreview.tsx +92 -0
- package/src/panels/timeline/takeMeasure.ts +72 -0
- package/src/panels/timeline/useLive.ts +48 -0
- package/src/panels/timeline/viewport.ts +234 -0
- package/src/panels/toolbar/BackChip.tsx +116 -0
- package/src/panels/toolbar/CameraViewBar.tsx +118 -49
- package/src/panels/toolbar/CanvasBar.tsx +117 -0
- package/src/panels/toolbar/InsertButton.tsx +38 -0
- package/src/panels/toolbar/IsolationTrail.tsx +156 -0
- package/src/panels/toolbar/PanelToggles.tsx +86 -0
- package/src/panels/toolbar/ShadingBar.tsx +119 -26
- package/src/panels/toolbar/ToolDockBar.tsx +329 -80
- package/src/panels/toolbar/ToolFooter.tsx +102 -0
- package/src/panels/toolbar/ViewModeBar.tsx +247 -38
- package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
- package/src/panels/toolbar/useAddRows.tsx +326 -0
- package/src/panels/toolbar/useDismiss.ts +122 -0
- package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
- package/src/panels/versions/versionModel.ts +103 -0
- package/src/panels/virtualWindow.ts +271 -0
- package/src/shotTokens.css +151 -60
- package/src/stage/FrustumGizmo.tsx +504 -84
- package/src/stage/GridPlane.tsx +225 -16
- package/src/stage/NodePreviews.tsx +628 -0
- package/src/stage/PerfHud.tsx +437 -47
- package/src/stage/SplitRenderer.tsx +816 -169
- package/src/stage/StageApp.tsx +4274 -486
- package/src/stage/StageRootBoundary.tsx +114 -0
- package/src/stage/StageTools.tsx +651 -151
- package/src/stage/TransformGizmo.tsx +696 -0
- package/src/stage/adaptivePresentation.ts +145 -0
- package/src/stage/bridgeClient.ts +2522 -41
- package/src/stage/exportWorker.ts +9 -0
- package/src/stage/feed.ts +305 -14
- package/src/stage/fontPreview.ts +176 -0
- package/src/stage/frustumGeometry.ts +319 -1
- package/src/stage/furnitureTokens.ts +33 -0
- package/src/stage/gizmoMath.ts +508 -16
- package/src/stage/gpuTimer.ts +276 -0
- package/src/stage/instanceModules.ts +194 -33
- package/src/stage/interactions/move.ts +24 -5
- package/src/stage/isolation.ts +291 -33
- package/src/stage/jsSelfProfiler.ts +325 -0
- package/src/stage/main.tsx +116 -16
- package/src/stage/modalState.ts +222 -23
- package/src/stage/moduleEpoch.ts +60 -0
- package/src/stage/motionPathStage.ts +279 -0
- package/src/stage/motionPreview.ts +74 -0
- package/src/stage/overlays/Overlays.tsx +1112 -159
- package/src/stage/overlays/Rulers.tsx +495 -0
- package/src/stage/panes.ts +159 -10
- package/src/stage/project.ts +68 -3
- package/src/stage/renderScaleController.ts +730 -0
- package/src/stage/rulers.ts +129 -0
- package/src/stage/sceneExtent.ts +75 -0
- package/src/stage/shading.ts +20 -0
- package/src/stage/splineStage.ts +632 -0
- package/src/stage/stagePerformanceTelemetry.ts +717 -0
- package/src/stage/stageTheme.ts +36 -0
- package/src/stage/stageWarmup.ts +11 -0
- package/src/stage/textAutoSize.ts +124 -0
- package/src/stage/tier1Export.ts +641 -0
- package/src/stage/tokenColor.ts +14 -9
- package/src/stage/view.ts +56 -9
- package/src/stage/workingView.ts +277 -33
- package/src/state/accountPrefs.ts +261 -0
- package/src/state/agentActions.ts +173 -15
- package/src/state/agentActivity.ts +198 -0
- package/src/state/agentQuestion.ts +239 -0
- package/src/state/bootOpen.ts +42 -0
- package/src/state/cameraVerbs.ts +139 -0
- package/src/state/clientId.ts +6 -1
- package/src/state/comments.ts +1099 -0
- package/src/state/context.tsx +19 -1
- package/src/state/editorPrefs.ts +220 -0
- package/src/state/errorReports.ts +121 -0
- package/src/state/fieldWrite.ts +161 -0
- package/src/state/fontRecents.ts +70 -0
- package/src/state/fonts.ts +656 -0
- package/src/state/graphClipboard.ts +414 -0
- package/src/state/imageThumbs.ts +216 -0
- package/src/state/insert.ts +444 -67
- package/src/state/isolationTrail.ts +92 -0
- package/src/state/keyMoves.ts +134 -0
- package/src/state/keyframeClipboard.ts +177 -0
- package/src/state/keymap.ts +1153 -117
- package/src/state/layoutPrefs.ts +272 -0
- package/src/state/layouts.ts +205 -0
- package/src/state/measureMedia.ts +167 -0
- package/src/state/menu.ts +2341 -28
- package/src/state/motionStamp.ts +7 -0
- package/src/state/panelSize.ts +375 -5
- package/src/state/pendingChanges.ts +119 -0
- package/src/state/playHold.ts +78 -0
- package/src/state/playbackPreparation.ts +562 -0
- package/src/state/posterCapture.ts +242 -0
- package/src/state/presence.ts +254 -0
- package/src/state/projectComponents.ts +160 -0
- package/src/state/renderEvents.ts +298 -18
- package/src/state/runtimeErrors.ts +46 -0
- package/src/state/selection.ts +12 -0
- package/src/state/shortcuts.ts +1606 -0
- package/src/state/sketchGuide.ts +51 -0
- package/src/state/splineRefs.ts +87 -0
- package/src/state/stageHealth.ts +259 -0
- package/src/state/stageWatchdog.ts +47 -0
- package/src/state/stillStage.ts +330 -0
- package/src/state/stillThumbs.ts +474 -0
- package/src/state/store.ts +23591 -2982
- package/src/state/storyboardModel.ts +528 -0
- package/src/state/sync.ts +1257 -0
- package/src/state/syncCache.ts +159 -0
- package/src/state/triangles.ts +13 -0
- package/src/state/types.ts +1698 -55
- package/src/state/unsavedGuard.ts +49 -0
- package/src/state/viewMode.ts +200 -0
- package/src/state/webmcp.ts +1128 -0
- package/src/state/workspace.ts +1147 -0
- package/src/styles/assets-tab.css +304 -0
- package/src/styles/canvas.css +545 -0
- package/src/styles/comments.css +1435 -0
- package/src/styles/controls.css +162 -0
- package/src/styles/dialogs.css +390 -0
- package/src/styles/insert-drawer.css +586 -0
- package/src/styles/inspector-board.css +207 -0
- package/src/styles/inspector-object.css +486 -0
- package/src/styles/inspector-shot.css +252 -0
- package/src/styles/inspector.css +360 -0
- package/src/styles/left-column.css +1628 -0
- package/src/styles/library-preview.css +575 -0
- package/src/styles/library.css +512 -0
- package/src/styles/modes/code.css +569 -0
- package/src/styles/modes/component.css +1561 -0
- package/src/styles/modes/material.css +1201 -0
- package/src/styles/modes/storyboard.css +518 -0
- package/src/styles/share-component.css +49 -0
- package/src/styles/shell.css +653 -0
- package/src/styles/shot-brief.css +360 -0
- package/src/styles/states.css +76 -0
- package/src/styles/sync.css +380 -0
- package/src/styles/timeline-graph.css +384 -0
- package/src/styles/timeline-meters.css +89 -0
- package/src/styles/timeline-rows.css +1959 -0
- package/src/styles/timeline-sound.css +1003 -0
- package/src/styles/timeline.css +754 -0
- package/src/styles/tool-footer.css +525 -0
- package/src/styles/topbar.css +886 -0
- package/src/styles/versions.css +262 -0
- package/src/styles.css +72 -2921
- package/src/tools.ts +108 -23
- package/dist/assets/index-B7notge7.css +0 -1
- package/dist/assets/index-BBE6Ta9L.js +0 -52
- package/src/panels/files/icons.tsx +0 -118
- package/src/panels/header/DocSwitcher.tsx +0 -85
- package/src/panels/header/TransportBar.tsx +0 -86
- package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
- package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
- package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
- package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
- package/src/panels/inspector/animate/useBlocks.ts +0 -43
- package/src/panels/material/MaterialPicker.tsx +0 -172
- package/src/panels/timeline/SceneStrip.tsx +0 -145
- package/src/panels/toolbar/AddMenu.tsx +0 -230
- 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
|
+
}
|