@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,485 @@
|
|
|
1
|
+
// THE TAP: one AudioWorkletNode hanging off a GainNode, measuring and never
|
|
2
|
+
// sounding.
|
|
3
|
+
//
|
|
4
|
+
// ── WHY A WORKLET AND NOT AN AnalyserNode ───────────────────────────────
|
|
5
|
+
//
|
|
6
|
+
// `AnalyserNode` answers on the MAIN thread, when asked. A meter built on one
|
|
7
|
+
// samples the signal at whatever rate rAF happens to run at, which means every
|
|
8
|
+
// transient between two frames is invisible and a stalled main thread reads as
|
|
9
|
+
// silence. The peak a mixer exists to show is exactly the thing that model
|
|
10
|
+
// drops. A worklet sees every block, so the attack is measured where the audio
|
|
11
|
+
// is and only the RESULT crosses the thread boundary.
|
|
12
|
+
//
|
|
13
|
+
// ── THE OUTPUT IS NEVER CONNECTED, AND THAT IS THE SAFETY RULE ──────────
|
|
14
|
+
//
|
|
15
|
+
// The node is connected FROM the gain node and its own output goes NOWHERE. A
|
|
16
|
+
// meter that joined the graph would sum a second copy of the signal into the
|
|
17
|
+
// destination: every metered channel 6 dB hot, and the export — which mixes
|
|
18
|
+
// offline and has no meters — would disagree with what the author heard. The
|
|
19
|
+
// processor writes nothing to `outputs` either, so even a mis-wiring downstream
|
|
20
|
+
// could only add silence.
|
|
21
|
+
//
|
|
22
|
+
// ── THE PROCESSOR'S SOURCE IS BUILT HERE, FROM meterMath.ts's ───────────
|
|
23
|
+
//
|
|
24
|
+
// A worklet is loaded by URL, and this chrome is served as one bundle with no
|
|
25
|
+
// second entry point to put a processor file at. So the source is assembled as
|
|
26
|
+
// a string and handed over as a Blob URL. The SMOOTHING inside it is not
|
|
27
|
+
// written here: `METER_STEP_SOURCE` is `meterMath.ts`'s own function,
|
|
28
|
+
// stringified, which is what makes the ballistics unit-testable with no
|
|
29
|
+
// AudioContext anywhere. Read that file's header for the constraint that keeps
|
|
30
|
+
// the function portable.
|
|
31
|
+
//
|
|
32
|
+
// THE SOURCE TEXT'S OWN NAME IS NEVER USED. The chrome is minified, so the
|
|
33
|
+
// text reads `function Xe(…)` in production, and the prelude binds each one to
|
|
34
|
+
// the name `process` calls (`const meterStep = (…);`). `audioMeter.test.ts`
|
|
35
|
+
// runs this processor built from a minified bundle; `sourceHygiene.test.ts`
|
|
36
|
+
// fails any evaluated text in the repo that calls a serialised function by a
|
|
37
|
+
// name it does not bind.
|
|
38
|
+
//
|
|
39
|
+
// `__name` IS DEFINED IN THE PRELUDE ON PURPOSE. esbuild's `keepNames` wraps
|
|
40
|
+
// named functions in a module-scope helper that does not survive being turned
|
|
41
|
+
// into text, and this repo has already shipped one page-injected function that
|
|
42
|
+
// threw `ReferenceError: __name is not defined` while every vitest run stayed
|
|
43
|
+
// green (vitest's transform omits `keepNames`). `meterStep` is written with no
|
|
44
|
+
// inner functions so it cannot need the helper today; the prelude is what stops
|
|
45
|
+
// a future edit reintroducing the failure in a place nothing tests.
|
|
46
|
+
import { useCallback, useEffect, useSyncExternalStore } from "react";
|
|
47
|
+
import { useShotEditor } from "../state/context.js";
|
|
48
|
+
import { recordRuntimeError, runtimeRealmRestarted } from "../state/runtimeErrors.js";
|
|
49
|
+
import {
|
|
50
|
+
METER_CHANNELS,
|
|
51
|
+
METER_DIFFERS_SOURCE,
|
|
52
|
+
METER_FIELDS,
|
|
53
|
+
METER_FLOOR,
|
|
54
|
+
METER_MESSAGE_LENGTH,
|
|
55
|
+
METER_POST_MS,
|
|
56
|
+
METER_STEP_SOURCE,
|
|
57
|
+
METER_TUNING,
|
|
58
|
+
METER_VISIBLE_RATIO,
|
|
59
|
+
meterVisiblyDiffers,
|
|
60
|
+
} from "./meterMath.js";
|
|
61
|
+
import { clearMeter, publishMeter, readMeter, subscribeMeter } from "./meterStore.js";
|
|
62
|
+
import type { MeterReading } from "./meterStore.js";
|
|
63
|
+
import type { MeterChannel } from "./meterMath.js";
|
|
64
|
+
|
|
65
|
+
/** The processor's registered name. One string, spelled once: the module text
|
|
66
|
+
* and the node constructor must agree or the construction throws. */
|
|
67
|
+
export const METER_PROCESSOR = "dailies-meter";
|
|
68
|
+
|
|
69
|
+
// --- minimal structural Web Audio surface (fakes stay trivial) ---------------
|
|
70
|
+
//
|
|
71
|
+
// The same posture `preview.ts` takes and for the same reason: no DOM lib types
|
|
72
|
+
// in the signatures, so the jsdom tests drive a hand-rolled fake rather than
|
|
73
|
+
// needing a real audio graph.
|
|
74
|
+
|
|
75
|
+
export interface MeterContextLike {
|
|
76
|
+
readonly sampleRate: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* What a tap hangs off. `disconnect` MUST take a destination: calling the
|
|
81
|
+
* no-argument form on a monitored gain node would tear down its real output
|
|
82
|
+
* too, which is the whole mix.
|
|
83
|
+
*/
|
|
84
|
+
export interface MeterSourceLike {
|
|
85
|
+
connect(destination: unknown): unknown;
|
|
86
|
+
disconnect(destination?: unknown): void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface MeterPortLike {
|
|
90
|
+
onmessage: ((event: { data: unknown }) => void) | null;
|
|
91
|
+
close?(): void;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface MeterNodeLike {
|
|
95
|
+
readonly port: MeterPortLike;
|
|
96
|
+
disconnect(): void;
|
|
97
|
+
/** Raised once when the processor throws: it is dead for good. */
|
|
98
|
+
onprocessorerror?: ((event: unknown) => void) | null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface MeterDeps {
|
|
102
|
+
/** Compile the processor into this context. Called at most once per context;
|
|
103
|
+
* a second call with the same URL is harmless but is not made. */
|
|
104
|
+
addModule(ctx: MeterContextLike, url: string): Promise<void>;
|
|
105
|
+
/** Construct the node. Separate from `addModule` because the construction is
|
|
106
|
+
* what throws when the module has not landed, and a test wants to drive the
|
|
107
|
+
* two independently. */
|
|
108
|
+
createNode(ctx: MeterContextLike): MeterNodeLike & MeterSourceLike;
|
|
109
|
+
/** The module URL. One per page in production. */
|
|
110
|
+
moduleUrl(): string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The whole processor, as text. */
|
|
114
|
+
export function meterWorkletSource(): string {
|
|
115
|
+
return [
|
|
116
|
+
// See the header: scoped to this module, never installed on anything else.
|
|
117
|
+
"const __name = (fn) => fn;",
|
|
118
|
+
// BOUND BY NAME HERE, never by the text's own name. The shipped chrome is
|
|
119
|
+
// minified, so `meterStep.toString()` there is `function Xe(…)`, and a bare
|
|
120
|
+
// declaration would leave `process` calling a `meterStep` that does not
|
|
121
|
+
// exist: `ReferenceError: meterStep is not defined` on every quantum. As a
|
|
122
|
+
// parenthesised expression the text's own name no longer matters.
|
|
123
|
+
`const meterStep = (${METER_STEP_SOURCE});`,
|
|
124
|
+
`const meterVisiblyDiffers = (${METER_DIFFERS_SOURCE});`,
|
|
125
|
+
`const TUNING = ${JSON.stringify(METER_TUNING)};`,
|
|
126
|
+
`const POST_MS = ${JSON.stringify(METER_POST_MS)};`,
|
|
127
|
+
`const FLOOR = ${JSON.stringify(METER_FLOOR)};`,
|
|
128
|
+
`const RATIO = ${JSON.stringify(METER_VISIBLE_RATIO)};`,
|
|
129
|
+
`const CHANNELS = ${JSON.stringify(METER_CHANNELS.length)};`,
|
|
130
|
+
`const FIELDS = ${JSON.stringify(METER_FIELDS)};`,
|
|
131
|
+
"class DailiesMeterProcessor extends AudioWorkletProcessor {",
|
|
132
|
+
" constructor() {",
|
|
133
|
+
" super();",
|
|
134
|
+
// ONE STATE PER CHANNEL, each through the same `meterStep`: L and R are
|
|
135
|
+
// two meters that happen to share a quantum, not one meter split.
|
|
136
|
+
" this.states = [];",
|
|
137
|
+
" for (let c = 0; c < CHANNELS; c++) this.states.push({ rms: 0, peak: 0, hold: 0, holdLeftMs: 0 });",
|
|
138
|
+
// What the MAIN THREAD currently believes, per channel, which is what a new
|
|
139
|
+
// post has to be measured against. Born at silence, which is what the
|
|
140
|
+
// store answers for a channel nobody has published to yet, so the first
|
|
141
|
+
// audible block posts and a silent start posts nothing at all. Allocated
|
|
142
|
+
// once; a post overwrites its entries, never the array.
|
|
143
|
+
" this.posted = this.states.slice();",
|
|
144
|
+
" this.sinceMs = 0;",
|
|
145
|
+
" }",
|
|
146
|
+
" process(inputs) {",
|
|
147
|
+
" const input = inputs[0];",
|
|
148
|
+
" const count = input ? input.length : 0;",
|
|
149
|
+
" let frames = 0;",
|
|
150
|
+
" for (let c = 0; c < count; c++) {",
|
|
151
|
+
" const ch = input[c];",
|
|
152
|
+
" if (ch && ch.length > frames) frames = ch.length;",
|
|
153
|
+
" }",
|
|
154
|
+
// A DISCONNECTED input reports no channels at all, which is not the same as
|
|
155
|
+
// a block of zeros: the needle must still decay over the time that passed,
|
|
156
|
+
// so the quantum falls back to the spec's 128 rather than to nothing.
|
|
157
|
+
" if (frames === 0) frames = 128;",
|
|
158
|
+
" const dtMs = (frames / sampleRate) * 1000;",
|
|
159
|
+
" for (let c = 0; c < CHANNELS; c++) {",
|
|
160
|
+
// ONE INPUT CHANNEL FEEDS BOTH. The node asks for two with the speakers
|
|
161
|
+
// rule, so the engine upmixes mono before this runs; an engine (or a
|
|
162
|
+
// test) that hands over one channel anyway still reads as equal L and R,
|
|
163
|
+
// never as a dead right side.
|
|
164
|
+
" const ch = count === 0 ? null : input[c < count ? c : count - 1];",
|
|
165
|
+
" let sum = 0;",
|
|
166
|
+
" let peak = 0;",
|
|
167
|
+
" const n = ch ? ch.length : 0;",
|
|
168
|
+
" for (let i = 0; i < n; i++) {",
|
|
169
|
+
" const v = ch[i];",
|
|
170
|
+
" sum += v * v;",
|
|
171
|
+
" const a = v < 0 ? -v : v;",
|
|
172
|
+
" if (a > peak) peak = a;",
|
|
173
|
+
" }",
|
|
174
|
+
" const rms = n > 0 ? Math.sqrt(sum / n) : 0;",
|
|
175
|
+
" this.states[c] = meterStep(this.states[c], rms, peak, dtMs, TUNING);",
|
|
176
|
+
" }",
|
|
177
|
+
" this.sinceMs += dtMs;",
|
|
178
|
+
" if (this.sinceMs < POST_MS) return true;",
|
|
179
|
+
// EITHER CHANNEL moving is a post, and the post carries both: the main
|
|
180
|
+
// thread gates each channel again on its own, so a still side costs no
|
|
181
|
+
// render there.
|
|
182
|
+
" let differs = false;",
|
|
183
|
+
" for (let c = 0; c < CHANNELS && !differs; c++) {",
|
|
184
|
+
" const s = this.states[c];",
|
|
185
|
+
" differs = meterVisiblyDiffers(this.posted[c], s.rms, s.peak, s.hold, FLOOR, RATIO);",
|
|
186
|
+
" }",
|
|
187
|
+
// ── THE QUIET PATH, AND WHY IT IS HERE AND NOT ON THE MAIN THREAD ──
|
|
188
|
+
//
|
|
189
|
+
// A filter in the `onmessage` handler saves the React commit and nothing
|
|
190
|
+
// else: the structured clone, the port hop and the main thread's wake-up
|
|
191
|
+
// have all already happened by the time it runs. This is where that cost
|
|
192
|
+
// is actually spent, so this is where a silent mix has to stop spending
|
|
193
|
+
// it. The tap outlives the transport, because the preview's AudioContext
|
|
194
|
+
// is built on the first play edge and is not suspended afterwards, so
|
|
195
|
+
// "silent" is the state a metered session sits in for most of its life.
|
|
196
|
+
//
|
|
197
|
+
// RE-ARMING IS INSTANT, and that is what `this.sinceMs = POST_MS` buys.
|
|
198
|
+
// The counter is left ARMED rather than reset, so the gate is re-asked
|
|
199
|
+
// every render quantum (2.7 ms at 48 kHz) instead of every 16 ms, and the
|
|
200
|
+
// first audible block posts from inside the very `process` call that saw
|
|
201
|
+
// it. A meter that misses the attack is worse than a meter that costs 62
|
|
202
|
+
// commits a second.
|
|
203
|
+
" if (!differs) {",
|
|
204
|
+
" this.sinceMs = POST_MS;",
|
|
205
|
+
" return true;",
|
|
206
|
+
" }",
|
|
207
|
+
" this.sinceMs = 0;",
|
|
208
|
+
// A FRESH array per post, copied by the structured clone. Transferring
|
|
209
|
+
// would detach the buffer and cost an allocation anyway; six floats at
|
|
210
|
+
// ~60 Hz is not worth a ring buffer and a SharedArrayBuffer's
|
|
211
|
+
// cross-origin-isolation requirement.
|
|
212
|
+
" const out = new Float32Array(CHANNELS * FIELDS);",
|
|
213
|
+
" for (let c = 0; c < CHANNELS; c++) {",
|
|
214
|
+
" const s = this.states[c];",
|
|
215
|
+
// THE MAIN THREAD'S GATE, MIRRORED. `onmessage` publishes only the
|
|
216
|
+
// channels that visibly differ from the store and drops the rest, so a
|
|
217
|
+
// channel moves its `posted` entry only when it passes the same test.
|
|
218
|
+
// Advancing a dropped channel's entry would let it creep, one sub-threshold
|
|
219
|
+
// post at a time, up to two thresholds from what the face shows, and then
|
|
220
|
+
// never post again.
|
|
221
|
+
" if (meterVisiblyDiffers(this.posted[c], s.rms, s.peak, s.hold, FLOOR, RATIO)) this.posted[c] = s;",
|
|
222
|
+
" out[c * FIELDS] = s.rms;",
|
|
223
|
+
" out[c * FIELDS + 1] = s.peak;",
|
|
224
|
+
" out[c * FIELDS + 2] = s.hold;",
|
|
225
|
+
" }",
|
|
226
|
+
" this.port.postMessage(out);",
|
|
227
|
+
// TRUE FOR EVER while the node lives. Returning false lets the engine
|
|
228
|
+
// collect the processor, and a meter that stopped measuring during a quiet
|
|
229
|
+
// passage would never come back. The quiet path above stops the POSTING,
|
|
230
|
+
// never the measuring: the needle has to keep falling while nobody is
|
|
231
|
+
// being told, or the level it re-arms from is the one it had before the
|
|
232
|
+
// silence.
|
|
233
|
+
" return true;",
|
|
234
|
+
" }",
|
|
235
|
+
"}",
|
|
236
|
+
`registerProcessor(${JSON.stringify(METER_PROCESSOR)}, DailiesMeterProcessor);`,
|
|
237
|
+
].join("\n");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* ONE Blob URL for the page.
|
|
242
|
+
*
|
|
243
|
+
* ponytail: created on first use and never revoked. It is one small object URL
|
|
244
|
+
* for the life of the document, and revoking it would break the next context's
|
|
245
|
+
* `addModule` — a second AudioContext is born whenever the preview is disposed
|
|
246
|
+
* and re-created. Revoke it from a page-teardown hook if a leak is ever
|
|
247
|
+
* measured; there is nothing else holding it.
|
|
248
|
+
*/
|
|
249
|
+
let moduleUrl: string | null = null;
|
|
250
|
+
function browserModuleUrl(): string {
|
|
251
|
+
if (moduleUrl === null) {
|
|
252
|
+
moduleUrl = URL.createObjectURL(new Blob([meterWorkletSource()], { type: "text/javascript" }));
|
|
253
|
+
}
|
|
254
|
+
return moduleUrl;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const browserDeps: MeterDeps = {
|
|
258
|
+
addModule: (ctx, url) => (ctx as unknown as BaseAudioContext).audioWorklet.addModule(url),
|
|
259
|
+
createNode: (ctx) => {
|
|
260
|
+
const node = new AudioWorkletNode(ctx as unknown as BaseAudioContext, METER_PROCESSOR, {
|
|
261
|
+
// ONE output, and it is never connected — see the header. Declaring zero
|
|
262
|
+
// outputs would also work in Chrome, but "the output exists and goes
|
|
263
|
+
// nowhere" is the statement the rule is written as, and it is the shape
|
|
264
|
+
// every other analysis worklet in the wild takes.
|
|
265
|
+
numberOfOutputs: 1,
|
|
266
|
+
outputChannelCount: [1],
|
|
267
|
+
// TWO INPUT CHANNELS, ALWAYS, by the speakers rule: the engine upmixes a
|
|
268
|
+
// mono master to L = R and downmixes anything wider, so `process` sees
|
|
269
|
+
// exactly the stereo pair the meter draws. The default ("max") would
|
|
270
|
+
// hand a mono source over as one channel.
|
|
271
|
+
channelCount: METER_CHANNELS.length,
|
|
272
|
+
channelCountMode: "explicit",
|
|
273
|
+
channelInterpretation: "speakers",
|
|
274
|
+
});
|
|
275
|
+
return node as unknown as MeterNodeLike & MeterSourceLike;
|
|
276
|
+
},
|
|
277
|
+
moduleUrl: browserModuleUrl,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
/** `addModule` is per CONTEXT and must not be repeated: a second compile of the
|
|
281
|
+
* same source is wasted work on the audio thread, and two attaches racing at
|
|
282
|
+
* the first play edge would both do it. Keyed on the context object, so a
|
|
283
|
+
* disposed context's promise is collected with it. */
|
|
284
|
+
const MODULES = new WeakMap<object, Promise<void>>();
|
|
285
|
+
|
|
286
|
+
function moduleFor(ctx: MeterContextLike, deps: MeterDeps): Promise<void> {
|
|
287
|
+
const existing = MODULES.get(ctx);
|
|
288
|
+
if (existing !== undefined) return existing;
|
|
289
|
+
const pending = deps.addModule(ctx, deps.moduleUrl());
|
|
290
|
+
MODULES.set(ctx, pending);
|
|
291
|
+
// A FAILED compile is dropped rather than latched, so the next mount tries
|
|
292
|
+
// again. Latching would silence every meter in the session over one transient
|
|
293
|
+
// failure, which is the posture the preview's buffer cache already rejects.
|
|
294
|
+
void pending.catch(() => {
|
|
295
|
+
if (MODULES.get(ctx) === pending) MODULES.delete(ctx);
|
|
296
|
+
});
|
|
297
|
+
return pending;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
interface Tap {
|
|
301
|
+
key: string;
|
|
302
|
+
/** How many mounted meters want this node measured. A channel can be on
|
|
303
|
+
* screen in two places (a strip and a master bar) and neither may tear the
|
|
304
|
+
* other's tap down. */
|
|
305
|
+
refs: number;
|
|
306
|
+
node: (MeterNodeLike & MeterSourceLike) | null;
|
|
307
|
+
/** Set by the last `release`, read by the async bring-up: a mount that
|
|
308
|
+
* unmounted while `addModule` was in flight must not leave a node wired to
|
|
309
|
+
* a graph nobody is looking at. */
|
|
310
|
+
dead: boolean;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** ONE tap per GainNode, and the map holds no strong reference to the graph. */
|
|
314
|
+
const TAPS = new WeakMap<MeterSourceLike, Tap>();
|
|
315
|
+
|
|
316
|
+
/** Meter nodes whose processor threw and is dead, until each is torn down:
|
|
317
|
+
* while any is, the worklet error stands in the stage health. */
|
|
318
|
+
const deadProcessors = new Set<object>();
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Measure `source` under `key` until the returned function is called. Each
|
|
322
|
+
* stereo channel publishes under `meterChannelKey(key, "L" | "R")`.
|
|
323
|
+
*
|
|
324
|
+
* Refcounted, so two faces over one bus share one worklet. Idempotent per
|
|
325
|
+
* (node, key): a second attach under a DIFFERENT key on the same node is
|
|
326
|
+
* refused rather than silently re-pointed, because one node cannot honestly
|
|
327
|
+
* publish as two buses and the quiet version of that bug is a master bar
|
|
328
|
+
* showing a clip's level.
|
|
329
|
+
*/
|
|
330
|
+
export function attachMeter(
|
|
331
|
+
key: string,
|
|
332
|
+
ctx: MeterContextLike,
|
|
333
|
+
source: MeterSourceLike,
|
|
334
|
+
deps: MeterDeps = browserDeps,
|
|
335
|
+
): () => void {
|
|
336
|
+
const existing = TAPS.get(source);
|
|
337
|
+
if (existing !== undefined) {
|
|
338
|
+
if (existing.key !== key) {
|
|
339
|
+
console.error(
|
|
340
|
+
`[audio] meter: this node is already published as ${JSON.stringify(existing.key)}; ` +
|
|
341
|
+
`refusing to also publish it as ${JSON.stringify(key)}`,
|
|
342
|
+
);
|
|
343
|
+
return () => {};
|
|
344
|
+
}
|
|
345
|
+
existing.refs += 1;
|
|
346
|
+
return () => release(source, existing);
|
|
347
|
+
}
|
|
348
|
+
const tap: Tap = { key, refs: 1, node: null, dead: false };
|
|
349
|
+
TAPS.set(source, tap);
|
|
350
|
+
void moduleFor(ctx, deps)
|
|
351
|
+
.then(() => {
|
|
352
|
+
if (tap.dead) return;
|
|
353
|
+
const node = deps.createNode(ctx);
|
|
354
|
+
// A processor that throws is dead for good and says so only in the
|
|
355
|
+
// console: kept for the stage health an agent reads (runtimeErrors.ts),
|
|
356
|
+
// STANDING until this node is torn down (`release`).
|
|
357
|
+
node.onprocessorerror = (event) => {
|
|
358
|
+
const message = (event as { message?: unknown } | null)?.message;
|
|
359
|
+
deadProcessors.add(node);
|
|
360
|
+
recordRuntimeError(
|
|
361
|
+
"audio-worklet",
|
|
362
|
+
`the level meter's audio worklet processor threw and stopped${typeof message === "string" && message !== "" ? `: ${message}` : " (the browser console has the error)"}`,
|
|
363
|
+
true,
|
|
364
|
+
);
|
|
365
|
+
};
|
|
366
|
+
// Spelled once per tap, not per post: the handler runs at the post rate.
|
|
367
|
+
const channels = METER_CHANNELS.map((channel) => meterChannelKey(key, channel));
|
|
368
|
+
node.port.onmessage = (event) => {
|
|
369
|
+
const data = event.data;
|
|
370
|
+
if (!(data instanceof Float32Array) || data.length < METER_MESSAGE_LENGTH) return;
|
|
371
|
+
for (let c = 0; c < channels.length; c++) {
|
|
372
|
+
const at = c * METER_FIELDS;
|
|
373
|
+
// THE GATE BEFORE THE ALLOCATION, PER CHANNEL, which is the half
|
|
374
|
+
// `publishMeter` cannot do for us: by the time the store had the
|
|
375
|
+
// object, the object would exist. Same question, same numbers,
|
|
376
|
+
// asked against what the store is already holding, so a post where
|
|
377
|
+
// only L moved leaves R's reading (and R's face) alone. See
|
|
378
|
+
// `meterStore.ts`'s header for why a reading's identity is the
|
|
379
|
+
// whole contract.
|
|
380
|
+
if (!meterVisiblyDiffers(readMeter(channels[c]!), data[at]!, data[at + 1]!, data[at + 2]!, METER_FLOOR, METER_VISIBLE_RATIO)) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
publishMeter(channels[c]!, reading(data, at));
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
// The tap's ONE connection, and it is inbound only.
|
|
387
|
+
source.connect(node);
|
|
388
|
+
tap.node = node;
|
|
389
|
+
})
|
|
390
|
+
.catch((e: unknown) => {
|
|
391
|
+
// Decoration, not the mix: a meter that cannot be built must leave the
|
|
392
|
+
// audio alone and say so once, rather than failing the play edge.
|
|
393
|
+
console.error("[audio] meter tap unavailable", e);
|
|
394
|
+
});
|
|
395
|
+
return () => release(source, tap);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function reading(data: Float32Array, at: number): MeterReading {
|
|
399
|
+
return { rms: data[at]!, peak: data[at + 1]!, hold: data[at + 2]!, holdLeftMs: 0 };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The store key one channel of a tap publishes under: `"master:L"`.
|
|
404
|
+
*
|
|
405
|
+
* A TAP IS STILL ONE KEY (`attachMeter`'s refusal is about the node, and one
|
|
406
|
+
* node is one stereo bus), and the store is still one reading per key, so
|
|
407
|
+
* neither contract had to change shape to carry two channels. Each channel is
|
|
408
|
+
* its own entry, gated on its own, so a still side never re-renders.
|
|
409
|
+
*/
|
|
410
|
+
export function meterChannelKey(key: string, channel: MeterChannel): string {
|
|
411
|
+
return `${key}:${channel}`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function release(source: MeterSourceLike, tap: Tap): void {
|
|
415
|
+
if (tap.dead) return;
|
|
416
|
+
tap.refs -= 1;
|
|
417
|
+
if (tap.refs > 0) return;
|
|
418
|
+
tap.dead = true;
|
|
419
|
+
TAPS.delete(source);
|
|
420
|
+
const node = tap.node;
|
|
421
|
+
tap.node = null;
|
|
422
|
+
if (node !== null) {
|
|
423
|
+
node.port.onmessage = null;
|
|
424
|
+
node.onprocessorerror = null;
|
|
425
|
+
// THE SELECTIVE FORM. `source.disconnect()` with no argument would drop the
|
|
426
|
+
// monitored node's real output as well, which is the mix.
|
|
427
|
+
try {
|
|
428
|
+
source.disconnect(node);
|
|
429
|
+
} catch {
|
|
430
|
+
// Already torn down with its context; nothing left to unwire.
|
|
431
|
+
}
|
|
432
|
+
node.disconnect();
|
|
433
|
+
node.port.close?.();
|
|
434
|
+
if (deadProcessors.delete(node) && deadProcessors.size === 0) runtimeRealmRestarted("audio-worklet");
|
|
435
|
+
}
|
|
436
|
+
// The face keeps drawing whatever it last saw unless it is told, so the
|
|
437
|
+
// channel falls to silence rather than freezing at -6 dB over nothing.
|
|
438
|
+
for (const channel of METER_CHANNELS) clearMeter(meterChannelKey(tap.key, channel));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* One channel's live reading, for a face.
|
|
443
|
+
*
|
|
444
|
+
* Subscribes to the lane-local store and NOT to the shot store or the hot
|
|
445
|
+
* channel — see `meterStore.ts`'s header for why neither may carry this.
|
|
446
|
+
*/
|
|
447
|
+
export function useMeterReading(key: string): MeterReading {
|
|
448
|
+
const subscribe = useCallback((onChange: () => void) => subscribeMeter(key, onChange), [key]);
|
|
449
|
+
const get = useCallback(() => readMeter(key), [key]);
|
|
450
|
+
return useSyncExternalStore(subscribe, get, get);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Tap a node while this component is mounted AND no export is running.
|
|
455
|
+
*
|
|
456
|
+
* ── BOTH HALVES OF THAT GATE ARE LOAD-BEARING ───────────────────────────
|
|
457
|
+
*
|
|
458
|
+
* MOUNTED, because a tap is per-frame work on the audio thread for a bar
|
|
459
|
+
* nobody is looking at. The effect's cleanup is the detach, so closing the
|
|
460
|
+
* mixer stops the measuring rather than merely hiding it.
|
|
461
|
+
*
|
|
462
|
+
* NO EXPORT LOCK, because an export is the one time this chrome's audio graph
|
|
463
|
+
* is not the author's monitor. A BROWSER export renders through the visible
|
|
464
|
+
* stage and a SERVER render has the machine busy; adding a worklet to either is
|
|
465
|
+
* work competing with the render for the audio thread, and the numbers it would
|
|
466
|
+
* post describe a mix nobody is listening to. `exportLock` is the store's own
|
|
467
|
+
* word for both (state/types.ts), and reading it here is the only thing this
|
|
468
|
+
* lane asks of the shot store.
|
|
469
|
+
*
|
|
470
|
+
* `source` is null until the graph exists — the preview builds its context
|
|
471
|
+
* lazily on the first play edge, which is the user gesture — so a face mounts
|
|
472
|
+
* over silence and starts metering when the node arrives.
|
|
473
|
+
*/
|
|
474
|
+
export function useMeterTap(
|
|
475
|
+
key: string,
|
|
476
|
+
ctx: MeterContextLike | null,
|
|
477
|
+
source: MeterSourceLike | null,
|
|
478
|
+
deps?: MeterDeps,
|
|
479
|
+
): void {
|
|
480
|
+
const exporting = useShotEditor((s) => s.exportLock !== null);
|
|
481
|
+
useEffect(() => {
|
|
482
|
+
if (ctx === null || source === null || exporting) return;
|
|
483
|
+
return attachMeter(key, ctx, source, deps);
|
|
484
|
+
}, [key, ctx, source, exporting, deps]);
|
|
485
|
+
}
|