hyperframes 0.8.144 → 0.8.146
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/README.md +3 -2
- package/dist/{add-CWRTVGDB.js → add-6J4TLUDW.js} +15 -15
- package/dist/{animatedGifPrep-JTOHIWI4.js → animatedGifPrep-TPYA45ZZ.js} +10 -10
- package/dist/{asset-KU2Z4UFM.js → asset-B4C6ZU4V.js} +2 -2
- package/dist/{assetCataloger-3CS2P7PA.js → assetCataloger-GX4B55VB.js} +4 -4
- package/dist/{auth-IDSOIQWG.js → auth-T2V3URGO.js} +2 -2
- package/dist/{autoUpdate-373B4BA2.js → autoUpdate-O4R7IOYP.js} +2 -2
- package/dist/{backgroundChecks-53Q3NAMG.js → backgroundChecks-SQHGZCSH.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{batchRender-UKZA6BME.js → batchRender-T7S2CGJE.js} +5 -5
- package/dist/{beats-N5JGEOFR.js → beats-RNEXDM7U.js} +16 -16
- package/dist/{benchmark-CUN3LN4V.js → benchmark-IVK6FCEU.js} +12 -12
- package/dist/{browser-NCZT5AP6.js → browser-WUE6VK2N.js} +5 -5
- package/dist/{bundleWithLocalizedFonts-FWC7PE2D.js → bundleWithLocalizedFonts-EEIRL7FT.js} +2 -2
- package/dist/{capture-YLS32UHT.js → capture-K3EONGGU.js} +2 -2
- package/dist/{capture-QBLG3VJR.js → capture-LEKIGJ4G.js} +13 -13
- package/dist/{catalog-HIWNAFHW.js → catalog-IXYQ2KXW.js} +15 -15
- package/dist/{catch-up-HGS7KIAC.js → catch-up-IIF7HKR6.js} +6 -6
- package/dist/{check-FH6UYYLO.js → check-W7CDOXNP.js} +38 -38
- package/dist/{checkBrowser-RNWNOMCZ.js → checkBrowser-5VSXEHBZ.js} +118 -61
- package/dist/{chunk-XU5GGV6R.js → chunk-2NAUEDA6.js} +2 -2
- package/dist/{chunk-GZM2EFOE.js → chunk-3HSWLZ6S.js} +1 -1
- package/dist/{chunk-YUAXFWSQ.js → chunk-4RUILXS6.js} +5 -5
- package/dist/{chunk-MXNDKDFT.js → chunk-54UXJKFF.js} +1 -1
- package/dist/{chunk-DIRJ322I.js → chunk-66SAATRI.js} +3 -3
- package/dist/{chunk-RI53TS2Y.js → chunk-6TTAXMDG.js} +2 -2
- package/dist/{chunk-YJXHMIZA.js → chunk-A72OAGNB.js} +6 -6
- package/dist/{chunk-G75ENCEZ.js → chunk-AIJS74LG.js} +3 -3
- package/dist/{chunk-GEWKFRFI.js → chunk-AJ5R4GM6.js} +1 -1
- package/dist/{chunk-3VYNNWVA.js → chunk-BAKPN2D2.js} +101 -94
- package/dist/{chunk-3XTEXWNN.js → chunk-CVPWZ5LY.js} +3 -3
- package/dist/{chunk-AUBOF2PN.js → chunk-DDNS2674.js} +1 -1
- package/dist/{chunk-QFSCCQIR.js → chunk-DMKI64EZ.js} +1 -1
- package/dist/{chunk-NOPBBSLA.js → chunk-DUU77E2S.js} +69 -64
- package/dist/{chunk-RVFEDAUV.js → chunk-EGO6Z24R.js} +3 -3
- package/dist/{chunk-KC5XXX3X.js → chunk-EOOEC32M.js} +1 -1
- package/dist/{chunk-2DA2BBNY.js → chunk-ETNQF37F.js} +10 -10
- package/dist/{chunk-54QISXOC.js → chunk-FDL4HV44.js} +2 -2
- package/dist/{chunk-VYSUZEJG.js → chunk-FJLHVAPO.js} +6 -6
- package/dist/{chunk-QZ3AZTXI.js → chunk-FJWEYVEM.js} +42 -16
- package/dist/{chunk-EX6C672Y.js → chunk-FKLYMV3U.js} +1 -1
- package/dist/{chunk-PUGIRFPX.js → chunk-FMNVTKX2.js} +4 -4
- package/dist/{chunk-L5TUCXWR.js → chunk-HBGJYBGE.js} +2 -2
- package/dist/{chunk-SLCFKB3Y.js → chunk-HTM4CIFS.js} +11 -8
- package/dist/{chunk-T42U2W2R.js → chunk-IXLUTE6N.js} +1 -1
- package/dist/{chunk-OUSLVRO4.js → chunk-JBQJKC23.js} +76 -6
- package/dist/{chunk-IEIJRMLZ.js → chunk-JDB65PPT.js} +62 -63
- package/dist/{chunk-USDEMXXL.js → chunk-JYVGYKXG.js} +1 -1
- package/dist/{chunk-P6WH6QTV.js → chunk-K7ES5RSK.js} +1 -1
- package/dist/{chunk-LC3RQURJ.js → chunk-KFZUUXUN.js} +3 -3
- package/dist/{chunk-OIDN7G42.js → chunk-KG265HQ7.js} +2 -2
- package/dist/{chunk-7RAYSCYT.js → chunk-KPJMBC4K.js} +2 -2
- package/dist/{chunk-MAYCCGMQ.js → chunk-KVGDFUVO.js} +1 -1
- package/dist/{chunk-JEHFZQYT.js → chunk-LE7T4ML7.js} +1 -1
- package/dist/{chunk-MQZVBHEY.js → chunk-LFNQDTSH.js} +1 -1
- package/dist/{chunk-ZOA2SC4P.js → chunk-MOL32UKR.js} +45 -11
- package/dist/{chunk-RG3QWWIE.js → chunk-QSSPIATO.js} +2 -2
- package/dist/{chunk-F2SMS24Q.js → chunk-R6R74HNZ.js} +117 -139
- package/dist/{chunk-7SDCIWHQ.js → chunk-SYMSKGXI.js} +2 -2
- package/dist/{chunk-ZEUONDQG.js → chunk-SYUGVPYH.js} +1 -1
- package/dist/{chunk-4OC7MQNB.js → chunk-TBOMPYG6.js} +2 -2
- package/dist/{chunk-KFKZ3ISO.js → chunk-TH4OXAWY.js} +1 -1
- package/dist/{chunk-4LAR447G.js → chunk-UOBYRLDD.js} +1 -1
- package/dist/{chunk-NV4RXIQ7.js → chunk-UZUUVYAO.js} +3 -3
- package/dist/{chunk-PJ3HDUCY.js → chunk-VAS6HQX3.js} +39 -9
- package/dist/{chunk-LFX2HEYP.js → chunk-VT563F4O.js} +7 -7
- package/dist/{chunk-5VMR4WBS.js → chunk-WU2B74TL.js} +363 -103
- package/dist/{chunk-5HJ343GH.js → chunk-WUZ6TMAO.js} +1 -1
- package/dist/{chunk-GQJZORQP.js → chunk-XBSPQ52S.js} +1 -1
- package/dist/{chunk-5WADG7PR.js → chunk-XJUZEB44.js} +1 -1
- package/dist/{chunk-I5KY7EZP.js → chunk-XVIYZU3D.js} +1 -1
- package/dist/{chunk-XGFHYAYN.js → chunk-Z5C4LEAD.js} +33 -28
- package/dist/chunk-Z6FLHIDV.js +3795 -0
- package/dist/{chunk-DDIVWXN7.js → chunk-ZFN6GQU4.js} +4 -4
- package/dist/{chunk-Y6UPYWJS.js → chunk-ZVZSFDW3.js} +2 -2
- package/dist/{chunk-A53CBD24.js → chunk-ZZ7H4IJ6.js} +9 -9
- package/dist/{clean-KPX27IKJ.js → clean-2GDD2B4F.js} +23 -23
- package/dist/cli.js +52 -52
- package/dist/{cloud-P7DAOISN.js → cloud-LUKGDTTE.js} +4 -4
- package/dist/{cloudrun-XM56TBOH.js → cloudrun-NQF5KFQI.js} +6 -6
- package/dist/{compare-F6YAFZZM.js → compare-4P6O4ONG.js} +32 -32
- package/dist/{compiler-JZ4GNFMG.js → compiler-ACCPOZMQ.js} +10 -6
- package/dist/{component-3CFOGIQA.js → component-SDDOLNJD.js} +3 -3
- package/dist/{compositions-CPCMWF24.js → compositions-P2L6HUVQ.js} +15 -15
- package/dist/{delete-GYVKOKB6.js → delete-Q6S4BJ3F.js} +2 -2
- package/dist/{deterministicFonts-U2BIMGPG.js → deterministicFonts-C5JQLCTS.js} +2 -2
- package/dist/{dist-YPWQDWJ4.js → dist-GPDOV3IQ.js} +4 -4
- package/dist/{dist-KDJTFJHA.js → dist-HUMFCJ6T.js} +3 -3
- package/dist/{doctor-GWI3B6JT.js → doctor-NL73UKHV.js} +15 -15
- package/dist/{events-BRRUDPPT.js → events-AVOPOKO3.js} +5 -5
- package/dist/{events-V2QFN4YM.js → events-X7KPU7LG.js} +4 -4
- package/dist/{feedback-CXMD7FWL.js → feedback-GJZ5NXSH.js} +10 -10
- package/dist/{ffmpeg-T5SH2EFJ.js → ffmpeg-5V4FP3R6.js} +2 -2
- package/dist/{figma-YABDWD66.js → figma-V7SVQQI5.js} +3 -3
- package/dist/fontLocalizeCli.js +14 -14
- package/dist/{get-L2SOQKHX.js → get-FO3YZYEU.js} +2 -2
- package/dist/{gpuPolicy-NBJNWHYS.js → gpuPolicy-QGS4TFWP.js} +10 -10
- package/dist/{grade-compare-STXQPKYQ.js → grade-compare-7ICSN7KE.js} +33 -33
- package/dist/{help-4H6BSFLB.js → help-Y5LYZG7K.js} +2 -2
- package/dist/{history-4IQOLTHJ.js → history-RKGBRNLD.js} +49 -22
- package/dist/hyperframe-runtime.js +214 -68
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +214 -68
- package/dist/hyperframes-player.global.js +4 -5
- package/dist/hyperframes-slideshow.global.js +3 -4
- package/dist/{info-2CZOTNBC.js → info-GGUTL2NR.js} +11 -11
- package/dist/{init-6BXK42VS.js → init-PG7NA5SQ.js} +31 -31
- package/dist/{inspect-VKU47TBH.js → inspect-FCXUB6DE.js} +33 -33
- package/dist/{installer-T2BSSUDY.js → installer-LZ4X5TZZ.js} +6 -6
- package/dist/{keyframes-IEYVHRPI.js → keyframes-R3K6WJ3N.js} +8 -8
- package/dist/{lambda-TIKG25CK.js → lambda-3GAKHMC3.js} +7 -7
- package/dist/{layout-XOI4PT7A.js → layout-XNIZIGA2.js} +33 -33
- package/dist/{lint-DWCMVIX5.js → lint-4KDS2F4O.js} +11 -11
- package/dist/{lintProject-UBZ3TDHU.js → lintProject-SSEXWCH6.js} +3 -3
- package/dist/{list-7GXKG5QM.js → list-OVPURFPK.js} +2 -2
- package/dist/{login-BQ5L2UHX.js → login-EE2AX2PE.js} +4 -4
- package/dist/{manager-YKMHR6LN.js → manager-5ORDVWJB.js} +3 -3
- package/dist/{media-treatment-IFB7OSZ3.js → media-treatment-UDUEOP67.js} +86 -55
- package/dist/{models-TY4PJ545.js → models-LPK2O4B2.js} +14 -14
- package/dist/{motionShot-WXMFHICW.js → motionShot-G76XBOYI.js} +35 -29
- package/dist/{normalize-audio-RFD776HQ.js → normalize-audio-TUKWZ3OR.js} +10 -10
- package/dist/{open-7IXXGO2E.js → open-7XB2TGVB.js} +6 -6
- package/dist/{parakeetRunner-IHLYYMTZ.js → parakeetRunner-DL556HXL.js} +14 -14
- package/dist/{pipeline-TXKPLL2U.js → pipeline-34N5DLWU.js} +11 -11
- package/dist/{play-J2KP64UK.js → play-DDA2UUQF.js} +26 -26
- package/dist/{preflight-B4AFK3RG.js → preflight-ONF5AJNO.js} +5 -5
- package/dist/{present-MHHNRVRA.js → present-LLNRKXF5.js} +23 -23
- package/dist/{preview-CONUOFFQ.js → preview-H3ZZS46F.js} +57 -26
- package/dist/{producer-ZI46K7HH.js → producer-IFQOZK2G.js} +1 -1
- package/dist/{projectConfig-FGGVG5MV.js → projectConfig-SBRMQZGG.js} +8 -8
- package/dist/{publication-TXFBMW6B.js → publication-5BQICRZO.js} +4 -4
- package/dist/{publish-YZSPBFRD.js → publish-XDUZA3E3.js} +27 -26
- package/dist/{remove-background-4ATVLHLG.js → remove-background-GNKRMFQS.js} +12 -12
- package/dist/{render-MH4IUPWB.js → render-JM6KNRIA.js} +27 -27
- package/dist/{render-6FBYLYGJ.js → render-KTTC3EE5.js} +12 -12
- package/dist/{render-GFW5BPES.js → render-PCFO45RK.js} +5 -5
- package/dist/{render-batch-XUCZ2PUJ.js → render-batch-7R2NRNVJ.js} +5 -5
- package/dist/{renderCancellation-XF7M7IUP.js → renderCancellation-GP3XD2FS.js} +10 -10
- package/dist/renderSetupWorker.js +3 -3
- package/dist/{resolver-W7AYCQGS.js → resolver-C4FPX72I.js} +6 -6
- package/dist/{sherpa-LDUB5NLY.js → sherpa-MZP3MV2X.js} +14 -14
- package/dist/skills/hyperframes/scripts/lib/frame-packets-core.mjs +2 -20
- package/dist/skills/hyperframes/scripts/lib/main-module.mjs +13 -0
- package/dist/skills/hyperframes/scripts/lib/main-module.test.mjs +51 -0
- package/dist/skills/hyperframes/scripts/plugin-cli.mjs +4 -3
- package/dist/skills/hyperframes/scripts/plugin-cli.test.mjs +5 -1
- package/dist/skills/hyperframes-cli/SKILL.md +2 -0
- package/dist/skills/media-use/audio/scripts/heygen-voice.mjs +2 -2
- package/dist/skills/media-use/audio/scripts/lib/main-module.mjs +13 -0
- package/dist/skills/media-use/references/media-treatments.md +5 -1
- package/dist/skills/media-use/scripts/lib/cube-validate.mjs +2 -3
- package/dist/skills/media-use/scripts/lib/main-module.mjs +13 -0
- package/dist/{skills-VENERS5X.js → skills-OET4XSHR.js} +7 -7
- package/dist/{skillsUpdateCheck-UXFYTACG.js → skillsUpdateCheck-IPFSKKJD.js} +3 -3
- package/dist/{slideshow-4OJEPWNZ.js → slideshow-YTWATJRT.js} +1 -1
- package/dist/{snapshot-C6T74HTI.js → snapshot-EC4N75MS.js} +34 -34
- package/dist/{src-VUJ2FUFK.js → src-LIERUCFW.js} +13 -9
- package/dist/{src-FS5RSVEI.js → src-ZZQJOKO6.js} +13 -13
- package/dist/{staticProjectServer-V2RZ4DWD.js → staticProjectServer-YH2RHYKW.js} +20 -20
- package/dist/{status-LXPGSSKY.js → status-PTKNQD2E.js} +2 -2
- package/dist/studio/assets/{hyperframes-player-Bw_jIaSX.js → hyperframes-player-tkxyWf5H.js} +3 -3
- package/dist/studio/assets/{index-Cj7W_c7p.js → index-BrdqOkqP.js} +1 -1
- package/dist/studio/assets/{index-BVM9NuWl.js → index-Cg48BxAo.js} +145 -145
- package/dist/studio/assets/{index-DyH9dT7b.js → index-Dllxg4aU.js} +1 -1
- package/dist/studio/index.html +1 -1
- package/dist/{studio-api-TQE7KWKO.js → studio-api-T2TS2CHL.js} +16 -14
- package/dist/{studioServer-CYKSQK7L.js → studioServer-GJVVJUEL.js} +54 -52
- package/dist/{system-OQLOGGPJ.js → system-OXRG57F2.js} +2 -2
- package/dist/{telemetry-A23PPRD6.js → telemetry-ZQ5XVE6Z.js} +5 -5
- package/dist/{timeline-T3QSGT3A.js → timeline-I2T5WIPD.js} +34 -25
- package/dist/{tokens-RT5HWA6C.js → tokens-ONUWOXEX.js} +2 -2
- package/dist/{transcribe-FOLNOC5G.js → transcribe-BCQMA537.js} +12 -12
- package/dist/{transcribe-HCF64LQK.js → transcribe-ZLJF7V5Y.js} +13 -13
- package/dist/{updateCheck-27PIP3AA.js → updateCheck-ZWHDXF73.js} +2 -2
- package/dist/{upgrade-TVGXBUPJ.js → upgrade-32JGFZWS.js} +2 -2
- package/dist/{urlDownloader-KBLBJKRL.js → urlDownloader-EVDP43GM.js} +9 -9
- package/dist/{validate-ZMYGK4QI.js → validate-G4PUGQCI.js} +27 -27
- package/dist/{video-AVE5GIGM.js → video-TILEY3VJ.js} +10 -10
- package/package.json +3 -2
- package/dist/chunk-WJB7WI4J.js +0 -3753
- package/dist/studio/chunk-3GHTHPZD.js +0 -1105
- package/dist/studio/chunk-3GHTHPZD.js.map +0 -1
- package/dist/studio/chunk-EEXSYY5F.js +0 -190
- package/dist/studio/chunk-EEXSYY5F.js.map +0 -1
- package/dist/studio/chunk-G3EDKRZH.js +0 -1442
- package/dist/studio/chunk-G3EDKRZH.js.map +0 -1
- package/dist/studio/chunk-PEAKCOUW.js +0 -942
- package/dist/studio/chunk-PEAKCOUW.js.map +0 -1
- package/dist/studio/chunk-PZ5AY32C.js +0 -10
- package/dist/studio/chunk-PZ5AY32C.js.map +0 -1
- package/dist/studio/chunk-Y2OHHNM2.js +0 -970
- package/dist/studio/chunk-Y2OHHNM2.js.map +0 -1
- package/dist/studio/domEditingLayers-PYST4G45.js +0 -47
- package/dist/studio/domEditingLayers-PYST4G45.js.map +0 -1
- package/dist/studio/index.css +0 -165
- package/dist/studio/index.css.map +0 -1
- package/dist/studio/index.d.ts +0 -4315
- package/dist/studio/index.js +0 -100971
- package/dist/studio/index.js.map +0 -1
- package/dist/studio/player.d.ts +0 -30
- package/dist/studio/player.js +0 -10
- package/dist/studio/player.js.map +0 -1
- package/dist/studio/styles/tailwind-preset.d.ts +0 -39
- package/dist/studio/styles/tailwind-preset.js +0 -50
- package/dist/studio/styles/tailwind-preset.js.map +0 -1
- package/dist/studio/styles.css +0 -1
- package/dist/studio/ui.d.ts +0 -297
- package/dist/studio/ui.js +0 -66
- package/dist/studio/ui.js.map +0 -1
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
LOW_MEMORY_TOTAL_MB_THRESHOLD,
|
|
9
9
|
getSystemTotalMb,
|
|
10
10
|
isLowMemorySystem
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-JYVGYKXG.js";
|
|
12
12
|
import {
|
|
13
13
|
MEDIA_VISUAL_STYLE_PROPERTIES,
|
|
14
14
|
RENDER_FRAME_ID_PREFIX,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
renderFrameIdForRenderId,
|
|
27
27
|
resolveAuthoredTimingWindow,
|
|
28
28
|
toFps
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-BAKPN2D2.js";
|
|
30
30
|
import {
|
|
31
31
|
AUDIO_GROUP_RENDER_ID_ATTR,
|
|
32
32
|
COLOR_GRADING_SOURCE_HIDDEN_ATTR,
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
shiftRateLane,
|
|
56
56
|
sourceTimeAt,
|
|
57
57
|
timeAtSourceTime
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-Z6FLHIDV.js";
|
|
59
59
|
import {
|
|
60
60
|
clampAudioGain,
|
|
61
61
|
isAudibleVideoElement
|
|
@@ -75,11 +75,14 @@ import {
|
|
|
75
75
|
FFMPEG_PATH_ENV,
|
|
76
76
|
FFPROBE_PATH_ENV,
|
|
77
77
|
findFfBinary
|
|
78
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-VAS6HQX3.js";
|
|
79
79
|
import {
|
|
80
80
|
parseNumeric,
|
|
81
81
|
parseStartExpression
|
|
82
82
|
} from "./chunk-3RM2ETQT.js";
|
|
83
|
+
import {
|
|
84
|
+
isPathInside
|
|
85
|
+
} from "./chunk-BSQ7XANM.js";
|
|
83
86
|
import {
|
|
84
87
|
parseHTML
|
|
85
88
|
} from "./chunk-SDYDNHEE.js";
|
|
@@ -529,7 +532,7 @@ function resolveConfig(overrides) {
|
|
|
529
532
|
merged.enableStreamingEncode = false;
|
|
530
533
|
merged.streamingEncodeAutoDisabledOnWin32Compound = true;
|
|
531
534
|
console.error(
|
|
532
|
-
"[hyperframes] Windows compound-workaround auto-detected
|
|
535
|
+
"[hyperframes] Windows compound-workaround auto-detected, disabling streaming-encode (platform=win32, software-GPU forced, workers=1). Field signal ts=1784131903. Override: PRODUCER_ENABLE_STREAMING_ENCODE=true."
|
|
533
536
|
);
|
|
534
537
|
}
|
|
535
538
|
if (merged.useDrawElement && merged.enablePageSideCompositing) {
|
|
@@ -1231,10 +1234,10 @@ function resolveBrowserGpuMode(mode, options = {}) {
|
|
|
1231
1234
|
var _unverifiedHardwareGpuWarned = false;
|
|
1232
1235
|
function buildUnverifiedHardwareGpuWarning(platform, cause) {
|
|
1233
1236
|
if (cause === "probe-error") {
|
|
1234
|
-
return "[hyperframes] browserGpuMode=hardware was requested, but the GPU probe could not run, so hardware acceleration is UNVERIFIED
|
|
1237
|
+
return "[hyperframes] browserGpuMode=hardware was requested, but the GPU probe could not run, so hardware acceleration is UNVERIFIED. If Chrome falls back to software WebGL the capture will run at CPU speed. Honouring the explicit request anyway.\n This is a probe failure, not evidence of a missing GPU: see the `browserGpuMode probe \u2192 software (probe failed ...)` line above for the underlying error, which usually means Chrome could not launch (bad HYPERFRAMES_BROWSER_PATH, missing shared libraries, or a denied sandbox) rather than a GPU problem.\n Run `hyperframes doctor` to check the Chrome install.";
|
|
1235
1238
|
}
|
|
1236
1239
|
const remediation = platform === "linux" ? "Inside Docker, the container needs GPU passthrough: `--gpus all` with the NVIDIA Container Toolkit installed, or `--device /dev/dri` for Mesa/AMD/Intel. The image also needs the matching userspace driver + libEGL. Verify with `hyperframes render --browser-gpu` and watch for this warning disappearing." : "Check that the host exposes a GPU to this process and that the graphics drivers are installed.";
|
|
1237
|
-
return `[hyperframes] browserGpuMode=hardware was requested, but the WebGL probe found no hardware GPU
|
|
1240
|
+
return `[hyperframes] browserGpuMode=hardware was requested, but the WebGL probe found no hardware GPU, so Chrome will silently fall back to software WebGL and the capture will run at CPU speed. Honouring the explicit request anyway.
|
|
1238
1241
|
${remediation}
|
|
1239
1242
|
Pass --no-browser-gpu to select deterministic SwiftShader instead of waiting on a hardware path that is not there.`;
|
|
1240
1243
|
}
|
|
@@ -2459,7 +2462,7 @@ async function sendBeginFrame(client, params) {
|
|
|
2459
2462
|
}
|
|
2460
2463
|
if (isPending) {
|
|
2461
2464
|
throw new Error(
|
|
2462
|
-
`[BeginFrame] Frame still pending after ${PENDING_FRAME_RETRIES} retries
|
|
2465
|
+
`[BeginFrame] Frame still pending after ${PENDING_FRAME_RETRIES} retries: CPU overloaded by parallel renders. Reduce concurrent renders or use --docker for isolation.`
|
|
2463
2466
|
);
|
|
2464
2467
|
}
|
|
2465
2468
|
throw err;
|
|
@@ -2776,6 +2779,13 @@ async function injectVideoFramesBatch(page, updates) {
|
|
|
2776
2779
|
const computedStyle = window.getComputedStyle(video);
|
|
2777
2780
|
const opacityParsed = parseFloat(computedStyle.opacity);
|
|
2778
2781
|
const computedOpacity = video.hasAttribute(colorGradingSourceHiddenAttr) ? 1 : Number.isNaN(opacityParsed) ? 1 : opacityParsed;
|
|
2782
|
+
const videoRect = video.getBoundingClientRect();
|
|
2783
|
+
const videoBox = {
|
|
2784
|
+
left: Number.isFinite(video.offsetLeft) ? video.offsetLeft : 0,
|
|
2785
|
+
top: Number.isFinite(video.offsetTop) ? video.offsetTop : 0,
|
|
2786
|
+
width: video.offsetWidth > 0 ? video.offsetWidth : videoRect.width,
|
|
2787
|
+
height: video.offsetHeight > 0 ? video.offsetHeight : videoRect.height
|
|
2788
|
+
};
|
|
2779
2789
|
if (isNewImage) {
|
|
2780
2790
|
img = document.createElement("img");
|
|
2781
2791
|
img.classList.add("__render_frame__");
|
|
@@ -2794,21 +2804,15 @@ async function injectVideoFramesBatch(page, updates) {
|
|
|
2794
2804
|
img.style.setProperty(property, value);
|
|
2795
2805
|
}
|
|
2796
2806
|
}
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
img.style.top = `${offsetTop}px`;
|
|
2807
|
-
img.style.right = "auto";
|
|
2808
|
-
img.style.bottom = "auto";
|
|
2809
|
-
img.style.width = `${offsetWidth}px`;
|
|
2810
|
-
img.style.height = `${offsetHeight}px`;
|
|
2811
|
-
}
|
|
2807
|
+
img.style.position = "absolute";
|
|
2808
|
+
img.style.inset = "auto";
|
|
2809
|
+
img.style.left = `${videoBox.left}px`;
|
|
2810
|
+
img.style.top = `${videoBox.top}px`;
|
|
2811
|
+
img.style.right = "auto";
|
|
2812
|
+
img.style.bottom = "auto";
|
|
2813
|
+
img.style.width = `${videoBox.width}px`;
|
|
2814
|
+
img.style.height = `${videoBox.height}px`;
|
|
2815
|
+
img.style.boxSizing = "border-box";
|
|
2812
2816
|
img.style.objectFit = computedStyle.objectFit;
|
|
2813
2817
|
img.style.objectPosition = computedStyle.objectPosition;
|
|
2814
2818
|
img.style.zIndex = computedStyle.zIndex;
|
|
@@ -3167,7 +3171,7 @@ async function initDrawElementWorkerEncode(page) {
|
|
|
3167
3171
|
}
|
|
3168
3172
|
const workerSrc = `
|
|
3169
3173
|
// Reuse one OffscreenCanvas across frames (dimensions are constant for a
|
|
3170
|
-
// render)
|
|
3174
|
+
// render): a fresh canvas per frame churns ~w*h*4 bytes of backing store
|
|
3171
3175
|
// every frame and pressures GC on the encode hot path.
|
|
3172
3176
|
let oc = null, c = null;
|
|
3173
3177
|
self.onmessage = async (e) => {
|
|
@@ -3943,7 +3947,7 @@ async function initThreeDProjectionInPage() {
|
|
|
3943
3947
|
ok: false,
|
|
3944
3948
|
groups: 0,
|
|
3945
3949
|
quads: 0,
|
|
3946
|
-
reason: "degenerate 3D geometry (zero-size or inline-box quad)
|
|
3950
|
+
reason: "degenerate 3D geometry (zero-size or inline-box quad): quad projection cannot reproduce Blink's lenient rendering"
|
|
3947
3951
|
};
|
|
3948
3952
|
}
|
|
3949
3953
|
if (!desc.shell && hasAnimatedStrictDescendant(desc.el)) {
|
|
@@ -3951,7 +3955,7 @@ async function initThreeDProjectionInPage() {
|
|
|
3951
3955
|
ok: false,
|
|
3952
3956
|
groups: 0,
|
|
3953
3957
|
quads: 0,
|
|
3954
|
-
reason: "3D quad contains GSAP-animated descendants
|
|
3958
|
+
reason: "3D quad contains GSAP-animated descendants, a static texture would freeze them at init state"
|
|
3955
3959
|
};
|
|
3956
3960
|
}
|
|
3957
3961
|
}
|
|
@@ -4963,7 +4967,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
4963
4967
|
session.deGateReason = "render_mode_hint";
|
|
4964
4968
|
session.deFallbackTrigger = "render_mode_hint";
|
|
4965
4969
|
console.log(
|
|
4966
|
-
"[engine] fast capture
|
|
4970
|
+
"[engine] fast capture falls back to screenshot: render-mode compatibility hint forced screenshot capture (e.g. raw requestAnimationFrame composition)."
|
|
4967
4971
|
);
|
|
4968
4972
|
}
|
|
4969
4973
|
if (useDrawElement) {
|
|
@@ -4989,7 +4993,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
4989
4993
|
session.deGateReason = "unsupported_chrome";
|
|
4990
4994
|
session.deFallbackTrigger = "unsupported_chrome";
|
|
4991
4995
|
console.log(
|
|
4992
|
-
`[engine] fast capture
|
|
4996
|
+
`[engine] fast capture falls back to ${session.launchCaptureMode} capture: this Chrome build does not implement canvas.drawElementImage (Dev/Canary-only feature); run \`hyperframes browser ensure --force\` to fetch a supported build, or set HYPERFRAMES_BROWSER_PATH to one.`
|
|
4993
4997
|
);
|
|
4994
4998
|
await routeToFallback();
|
|
4995
4999
|
return;
|
|
@@ -4998,7 +5002,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
4998
5002
|
session.deGateReason = "ffmpeg_no_psnr_filter";
|
|
4999
5003
|
session.deFallbackTrigger = "ffmpeg_no_psnr_filter";
|
|
5000
5004
|
console.warn(
|
|
5001
|
-
`[engine] fast capture
|
|
5005
|
+
`[engine] fast capture falls back to ${session.launchCaptureMode} capture: host ffmpeg is missing or was built without the \`psnr\` filter (libpostproc), so drawElement self-verification cannot run. Install an ffmpeg build that includes libpostproc (or set HYPERFRAMES_FFMPEG_PATH to one) to re-enable fast capture.`
|
|
5002
5006
|
);
|
|
5003
5007
|
await routeToFallback();
|
|
5004
5008
|
return;
|
|
@@ -5008,7 +5012,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
5008
5012
|
session.deGateReason = "swiftshader";
|
|
5009
5013
|
session.deFallbackTrigger = "swiftshader";
|
|
5010
5014
|
console.log(
|
|
5011
|
-
`[engine] fast capture
|
|
5015
|
+
`[engine] fast capture falls back to ${session.launchCaptureMode} capture: SwiftShader (software rasterizer, no GPU egress to skip, drawElement is parity-or-slower; see fast-capture-limitations.md)`
|
|
5012
5016
|
);
|
|
5013
5017
|
await routeToFallback();
|
|
5014
5018
|
} else {
|
|
@@ -5018,7 +5022,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
5018
5022
|
session.deGateReason = `css_effect:${(cssFx.split(":")[0] ?? "").replace(/[^a-z-]/gi, "")}`;
|
|
5019
5023
|
session.deFallbackTrigger = cssFx;
|
|
5020
5024
|
console.log(
|
|
5021
|
-
`[engine] fast capture
|
|
5025
|
+
`[engine] fast capture falls back to ${session.launchCaptureMode} capture: ${cssFx} detected (drawElementImage cannot reproduce it; see fast-capture-limitations.md)`
|
|
5022
5026
|
);
|
|
5023
5027
|
await routeToFallback();
|
|
5024
5028
|
return;
|
|
@@ -5036,7 +5040,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
5036
5040
|
session.deGateReason = "at_risk_timeline";
|
|
5037
5041
|
session.deFallbackTrigger = "at_risk_timeline";
|
|
5038
5042
|
console.log(
|
|
5039
|
-
`[engine] fast capture
|
|
5043
|
+
`[engine] fast capture falls back to ${session.launchCaptureMode} capture: ${atRisk.size}/${totalFrames} frames animate a compositor-incompatible prop (blend/3D/clip/mask); drawElementImage drops these mid-animation (deterministic timeline gate; see fast-capture-limitations.md Lim 7)`
|
|
5040
5044
|
);
|
|
5041
5045
|
await routeToFallback();
|
|
5042
5046
|
return;
|
|
@@ -5047,7 +5051,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
5047
5051
|
session.deGateReason = "3d_init_failed";
|
|
5048
5052
|
session.deFallbackTrigger = "3d_init_failed";
|
|
5049
5053
|
console.log(
|
|
5050
|
-
`[engine] fast capture
|
|
5054
|
+
`[engine] fast capture falls back to ${session.launchCaptureMode} capture: 3D projection init failed (${threeD.reason ?? "unknown"})`
|
|
5051
5055
|
);
|
|
5052
5056
|
await routeToFallback();
|
|
5053
5057
|
return;
|
|
@@ -5121,7 +5125,7 @@ async function createCaptureSession(serverUrl, outputDir, options, onBeforeCaptu
|
|
|
5121
5125
|
const drawElementTransparent = useDrawElement && options.format === "png";
|
|
5122
5126
|
if (useDrawElement && (config?.enablePageSideCompositing ?? DEFAULT_CONFIG.enablePageSideCompositing)) {
|
|
5123
5127
|
console.warn(
|
|
5124
|
-
"[engine] useDrawElement is incompatible with page-side shader compositing
|
|
5128
|
+
"[engine] useDrawElement is incompatible with page-side shader compositing, ignoring enablePageSideCompositing for this render. Prefer resolveConfig, which disables page-side compositing automatically for fast-capture renders."
|
|
5125
5129
|
);
|
|
5126
5130
|
}
|
|
5127
5131
|
const supersampling = (options.deviceScaleFactor ?? 1) > 1;
|
|
@@ -5220,7 +5224,7 @@ async function constructCaptureSession(input) {
|
|
|
5220
5224
|
}
|
|
5221
5225
|
if (process.env.HF_FAST_CAPTURE_AUTOALPHA !== void 0) {
|
|
5222
5226
|
console.warn(
|
|
5223
|
-
"[engine] HF_FAST_CAPTURE_AUTOALPHA is retired and ignored
|
|
5227
|
+
"[engine] HF_FAST_CAPTURE_AUTOALPHA is retired and ignored: the requestPaint paint contract captures animated opacity natively (see drawElementService.ts)."
|
|
5224
5228
|
);
|
|
5225
5229
|
}
|
|
5226
5230
|
if (useDrawElement && process.env.HF_FAST_CAPTURE_ROOT_PROPS !== "false") {
|
|
@@ -5338,7 +5342,7 @@ var HF_READY_DIAGNOSTIC_EXPR = `(async function() {
|
|
|
5338
5342
|
await new Promise(function(r) { setTimeout(r, 0); });
|
|
5339
5343
|
}
|
|
5340
5344
|
// A thenable resolving via its own setTimeout(0) can still misreport as
|
|
5341
|
-
// pending here (macrotask registration order, not resolution order)
|
|
5345
|
+
// pending here (macrotask registration order, not resolution order), a
|
|
5342
5346
|
// narrow case that self-corrects on the next ~1s diagnostic tick.
|
|
5343
5347
|
var pendingBuildReadyKeys = keys.filter(function(key) { return !settled[key]; });
|
|
5344
5348
|
var rejectedBuildReadyKeys = keys.filter(function(key) { return settled[key] === "rejected"; });
|
|
@@ -5358,20 +5362,20 @@ function buildZeroDurationDiagnostic(diag) {
|
|
|
5358
5362
|
const hints = [];
|
|
5359
5363
|
if (diag.pendingBuildReadyKeys.length > 0) {
|
|
5360
5364
|
hints.push(
|
|
5361
|
-
`window.__hf.buildReady never resolved for: ${diag.pendingBuildReadyKeys.join(", ")}. The runtime holds render-ready until every registered buildReady promise settles
|
|
5365
|
+
`window.__hf.buildReady never resolved for: ${diag.pendingBuildReadyKeys.join(", ")}. The runtime holds render-ready until every registered buildReady promise settles. Find where the composition registers that key and confirm it actually resolves.`
|
|
5362
5366
|
);
|
|
5363
5367
|
}
|
|
5364
5368
|
if (diag.rejectedBuildReadyKeys.length > 0) {
|
|
5365
5369
|
hints.push(
|
|
5366
|
-
`window.__hf.buildReady rejected for: ${diag.rejectedBuildReadyKeys.join(", ")}. That key's build promise failed rather than hanging
|
|
5370
|
+
`window.__hf.buildReady rejected for: ${diag.rejectedBuildReadyKeys.join(", ")}. That key's build promise failed rather than hanging. Find why it rejects.`
|
|
5367
5371
|
);
|
|
5368
5372
|
}
|
|
5369
5373
|
if (!diag.hasPlayer) {
|
|
5370
|
-
hints.push("window.__player was never set
|
|
5374
|
+
hints.push("window.__player was never set: the HyperFrames runtime did not initialize.");
|
|
5371
5375
|
}
|
|
5372
5376
|
if (!diag.hasTimeline) {
|
|
5373
5377
|
hints.push(
|
|
5374
|
-
"No GSAP timeline registered (window.__timelines is empty). CSS/WAAPI/Lottie animations are usually auto-detected (the runtime infers duration from the longest running animation)
|
|
5378
|
+
"No GSAP timeline registered (window.__timelines is empty). CSS/WAAPI/Lottie animations are usually auto-detected (the runtime infers duration from the longest running animation), but this composition's duration could not be inferred, which usually means an infinite/unbounded animation (e.g. animation-iteration-count: infinite, repeat: -1 WAAPI, or a looping Lottie clip) or a Three.js scene with no discoverable AnimationClip."
|
|
5375
5379
|
);
|
|
5376
5380
|
}
|
|
5377
5381
|
if (diag.declaredDuration <= 0 && !diag.hasTimeline) {
|
|
@@ -5380,7 +5384,7 @@ function buildZeroDurationDiagnostic(diag) {
|
|
|
5380
5384
|
);
|
|
5381
5385
|
}
|
|
5382
5386
|
if (diag.hasSeek && diag.duration === 0 && diag.renderReady) {
|
|
5383
|
-
hints.push("The runtime finished initializing but reported zero duration
|
|
5387
|
+
hints.push("The runtime finished initializing but reported zero duration. This is permanent.");
|
|
5384
5388
|
}
|
|
5385
5389
|
return `[FrameCapture] Composition has zero duration.
|
|
5386
5390
|
Runtime ready: ${diag.renderReady}, __player: ${diag.hasPlayer}, __hf.seek: ${diag.hasSeek}, GSAP timeline: ${diag.hasTimeline}, data-duration: ${diag.declaredDuration > 0 ? diag.declaredDuration + "s" : "not set"}
|
|
@@ -5396,8 +5400,11 @@ async function pollHfReady(page, timeoutMs, intervalMs = 100) {
|
|
|
5396
5400
|
const deadline = Date.now() + timeoutMs;
|
|
5397
5401
|
let lastDiagnosticAt = 0;
|
|
5398
5402
|
while (Date.now() < deadline) {
|
|
5399
|
-
const
|
|
5400
|
-
|
|
5403
|
+
const state = await page.evaluate(
|
|
5404
|
+
`typeof window.__hfStartupError === "string" ? window.__hfStartupError : ${readyExpr}`
|
|
5405
|
+
);
|
|
5406
|
+
if (typeof state === "string") throw new Error(state);
|
|
5407
|
+
if (state) return;
|
|
5401
5408
|
const elapsed = timeoutMs - (deadline - Date.now());
|
|
5402
5409
|
if (elapsed >= FAST_FAIL_AFTER_MS) {
|
|
5403
5410
|
const now = Date.now();
|
|
@@ -5483,7 +5490,7 @@ async function pollSubCompositionTimelines(page, timeoutMs, {
|
|
|
5483
5490
|
if (failures.length > 0 && now - start >= scriptFailureGraceMs) {
|
|
5484
5491
|
scriptFailureBail = true;
|
|
5485
5492
|
console.warn(
|
|
5486
|
-
`[FrameCapture] Sub-composition timeline wait cut short after ${now - start}ms: script resource(s) failed to load (${failures.join(", ")})
|
|
5493
|
+
`[FrameCapture] Sub-composition timeline wait cut short after ${now - start}ms: script resource(s) failed to load (${failures.join(", ")}), so the timeline registration they carry can never arrive. Fix the script reference; the render proceeds without those animations.`
|
|
5487
5494
|
);
|
|
5488
5495
|
break;
|
|
5489
5496
|
}
|
|
@@ -5642,7 +5649,7 @@ var LIVE_MAP_MARKERS = [
|
|
|
5642
5649
|
function buildLiveMapWarning(libraries) {
|
|
5643
5650
|
return {
|
|
5644
5651
|
code: "live_map_detected",
|
|
5645
|
-
message: `Live map viewport(s) detected in the composition (${libraries.join(", ")}). Map tiles load over the network at render time, which the deterministic-render contract forbids
|
|
5652
|
+
message: `Live map viewport(s) detected in the composition (${libraries.join(", ")}). Map tiles load over the network at render time, which the deterministic-render contract forbids: frames captured before tiles arrive ship a blank or partial map with no error. Bake the map to a video first (see the motion-graphics maps skill / bake-basemap.mjs) and use the baked file as the imagery layer.`,
|
|
5646
5653
|
details: { sources: [...libraries] }
|
|
5647
5654
|
};
|
|
5648
5655
|
}
|
|
@@ -5890,7 +5897,7 @@ async function initializeSession(session) {
|
|
|
5890
5897
|
}).map((img) => img.src || img.getAttribute("src") || "(no src)").join(", ");
|
|
5891
5898
|
});
|
|
5892
5899
|
console.warn(
|
|
5893
|
-
`[FrameCapture] Some image elements did not load within ${pageReadyTimeout}ms: ${failedImages}. Continuing render
|
|
5900
|
+
`[FrameCapture] Some image elements did not load within ${pageReadyTimeout}ms: ${failedImages}. Continuing render. Affected images may appear blank/missing in early frames.`
|
|
5894
5901
|
);
|
|
5895
5902
|
}
|
|
5896
5903
|
await decodeAllImages(page);
|
|
@@ -5906,7 +5913,7 @@ async function initializeSession(session) {
|
|
|
5906
5913
|
return Array.from(document.querySelectorAll("video")).filter((v) => !skip.has(v.id)).filter((v) => v.readyState < 2 && !v.error).map((v) => v.src || v.getAttribute("src") || "(no src)").join(", ");
|
|
5907
5914
|
}, skipVideoIds);
|
|
5908
5915
|
console.warn(
|
|
5909
|
-
`[FrameCapture] Some video elements did not decode within ${pageReadyTimeout}ms: ${failedVideos}. Continuing render
|
|
5916
|
+
`[FrameCapture] Some video elements did not decode within ${pageReadyTimeout}ms: ${failedVideos}. Continuing render. Affected videos will appear as blank/black frames.`
|
|
5910
5917
|
);
|
|
5911
5918
|
}
|
|
5912
5919
|
recordCaptureWarnings(
|
|
@@ -5996,7 +6003,7 @@ async function initializeSession(session) {
|
|
|
5996
6003
|
}).map((img) => img.src || img.getAttribute("src") || "(no src)").join(", ");
|
|
5997
6004
|
});
|
|
5998
6005
|
console.warn(
|
|
5999
|
-
`[FrameCapture] Some image elements did not load within ${pageReadyTimeout}ms: ${failedImages}. Continuing render
|
|
6006
|
+
`[FrameCapture] Some image elements did not load within ${pageReadyTimeout}ms: ${failedImages}. Continuing render. Affected images may appear blank/missing in early frames.`
|
|
6000
6007
|
);
|
|
6001
6008
|
}
|
|
6002
6009
|
await decodeAllImages(page);
|
|
@@ -6012,7 +6019,7 @@ async function initializeSession(session) {
|
|
|
6012
6019
|
return Array.from(document.querySelectorAll("video")).filter((v) => !skip.has(v.id)).filter((v) => v.readyState < 2 && !v.error).map((v) => v.src || v.getAttribute("src") || "(no src)").join(", ");
|
|
6013
6020
|
}, bfSkipVideoIds);
|
|
6014
6021
|
console.warn(
|
|
6015
|
-
`[FrameCapture] Some video elements did not decode within ${pageReadyTimeout}ms: ${failedVideos}. Continuing render
|
|
6022
|
+
`[FrameCapture] Some video elements did not decode within ${pageReadyTimeout}ms: ${failedVideos}. Continuing render. Affected videos will appear as blank/black frames.`
|
|
6016
6023
|
);
|
|
6017
6024
|
}
|
|
6018
6025
|
recordCaptureWarnings(
|
|
@@ -6978,7 +6985,7 @@ async function captureFrameToBuffer(session, frameIndex, time) {
|
|
|
6978
6985
|
() => {
|
|
6979
6986
|
session.deFrameTimeouts = (session.deFrameTimeouts ?? 0) + 1;
|
|
6980
6987
|
console.log(
|
|
6981
|
-
`[engine] fast capture
|
|
6988
|
+
`[engine] fast capture frame ${frameIndex}: capture exceeded ${DE_FRAME_TIMEOUT_MS}ms; renderer stalled after drawElementImage (PRINFRA-488). Failing the drawElement attempt so the whole render retries via screenshot.`
|
|
6982
6989
|
);
|
|
6983
6990
|
}
|
|
6984
6991
|
) : await captureFrameCore(session, frameIndex, time);
|
|
@@ -7751,7 +7758,7 @@ function findHdrAutoPromotion(sources) {
|
|
|
7751
7758
|
} : null;
|
|
7752
7759
|
}
|
|
7753
7760
|
function formatHdrAutoPromotionWarning(promotion) {
|
|
7754
|
-
return `[Render] HDR auto-promotion triggered by "${promotion.triggeringAsset}"
|
|
7761
|
+
return `[Render] HDR auto-promotion triggered by "${promotion.triggeringAsset}", output: ${promotion.output.colorSpace} / ${promotion.output.codec}`;
|
|
7755
7762
|
}
|
|
7756
7763
|
function analyzeCompositionHdr(colorSpaces) {
|
|
7757
7764
|
let hasPq = false;
|
|
@@ -7834,6 +7841,11 @@ ${tail}` : windowsMessage;
|
|
|
7834
7841
|
ffmpeg stderr (tail):
|
|
7835
7842
|
${tail}` : `FFmpeg exited with code ${exitCode}`;
|
|
7836
7843
|
}
|
|
7844
|
+
function describeFfmpegFailure(result, timeoutMs) {
|
|
7845
|
+
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
7846
|
+
if (result.terminationReason !== "deadline") return error;
|
|
7847
|
+
return `FFmpeg timed out after ${timeoutMs} ms (ffmpegProcessTimeout; long renders can raise FFMPEG_PROCESS_TIMEOUT_MS). ${error}`;
|
|
7848
|
+
}
|
|
7837
7849
|
function spawnFfmpeg(args) {
|
|
7838
7850
|
const ffmpeg = spawn2(getFfmpegBinary(), args, { windowsHide: true });
|
|
7839
7851
|
trackChildProcess(ffmpeg);
|
|
@@ -7857,7 +7869,16 @@ function toResult(outcome, stderr = outcome.stderr) {
|
|
|
7857
7869
|
}
|
|
7858
7870
|
return result;
|
|
7859
7871
|
}
|
|
7872
|
+
var ABORTED_BEFORE_START = {
|
|
7873
|
+
success: false,
|
|
7874
|
+
exitCode: null,
|
|
7875
|
+
signal: null,
|
|
7876
|
+
stderr: "",
|
|
7877
|
+
durationMs: 0,
|
|
7878
|
+
terminationReason: "abort"
|
|
7879
|
+
};
|
|
7860
7880
|
async function runFfmpeg(args, opts) {
|
|
7881
|
+
if (opts?.signal?.aborted) return { ...ABORTED_BEFORE_START };
|
|
7861
7882
|
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
|
|
7862
7883
|
const managed = new ManagedChildProcess(spawnFfmpeg(args), {
|
|
7863
7884
|
signal: opts?.signal,
|
|
@@ -7867,6 +7888,7 @@ async function runFfmpeg(args, opts) {
|
|
|
7867
7888
|
return toResult(await managed.wait());
|
|
7868
7889
|
}
|
|
7869
7890
|
async function runFfmpegPipeline(producerArgs, consumerArgs, opts) {
|
|
7891
|
+
if (opts?.signal?.aborted) return { ...ABORTED_BEFORE_START };
|
|
7870
7892
|
const deadlineAtMs = Date.now() + (opts?.timeout ?? DEFAULT_TIMEOUT);
|
|
7871
7893
|
const producer = spawnFfmpeg(producerArgs);
|
|
7872
7894
|
const consumer = spawnFfmpeg(consumerArgs);
|
|
@@ -8002,6 +8024,7 @@ var mediaProbeOutputSignalCaches = /* @__PURE__ */ new WeakMap();
|
|
|
8002
8024
|
var MEDIA_PROBE_OUTPUT_CACHE_MAX_ENTRIES = 128;
|
|
8003
8025
|
var AUDIO_DURATION_PROBE_MARGIN_SECONDS = 0.05;
|
|
8004
8026
|
var AUDIO_DURATION_PROBE_TAIL_SECONDS = 1;
|
|
8027
|
+
var FFPROBE_READ_INTERVAL_EXPLICIT_FAR_END = "+2147483647";
|
|
8005
8028
|
var STILL_IMAGE_DEMUXERS = /* @__PURE__ */ new Set([
|
|
8006
8029
|
"apng",
|
|
8007
8030
|
"bmp_pipe",
|
|
@@ -8485,7 +8508,7 @@ async function probeDecodedAudioDuration(filePath, sampleRate, startSeconds, con
|
|
|
8485
8508
|
startSeconds,
|
|
8486
8509
|
startSeconds + containerDurationSeconds - AUDIO_DURATION_PROBE_TAIL_SECONDS
|
|
8487
8510
|
);
|
|
8488
|
-
const lastFrame = await probe(`${tailStart}
|
|
8511
|
+
const lastFrame = await probe(`${tailStart}%${FFPROBE_READ_INTERVAL_EXPLICIT_FAR_END}`) ?? await probe();
|
|
8489
8512
|
if (!lastFrame) return null;
|
|
8490
8513
|
return lastFrame.timestamp + lastFrame.nbSamples / sampleRate - startSeconds;
|
|
8491
8514
|
}
|
|
@@ -9124,7 +9147,7 @@ async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, confi
|
|
|
9124
9147
|
success: result.success,
|
|
9125
9148
|
outputPath,
|
|
9126
9149
|
durationMs: result.durationMs,
|
|
9127
|
-
error: !result.success ?
|
|
9150
|
+
error: !result.success ? describeFfmpegFailure(result, processTimeout) : void 0,
|
|
9128
9151
|
failureReason: result.failureReason
|
|
9129
9152
|
};
|
|
9130
9153
|
}
|
|
@@ -9216,7 +9239,7 @@ async function packageHls(videoPath, audioPath, outputDir, options) {
|
|
|
9216
9239
|
success: result.success,
|
|
9217
9240
|
outputPath: outputDir,
|
|
9218
9241
|
durationMs: result.durationMs,
|
|
9219
|
-
error: !result.success ?
|
|
9242
|
+
error: !result.success ? describeFfmpegFailure(result, processTimeout) : void 0,
|
|
9220
9243
|
failureReason: result.failureReason
|
|
9221
9244
|
};
|
|
9222
9245
|
}
|
|
@@ -9249,7 +9272,7 @@ async function applyFaststart(inputPath, outputPath, signal, config, fps, onSeco
|
|
|
9249
9272
|
success: result.success,
|
|
9250
9273
|
outputPath,
|
|
9251
9274
|
durationMs: result.durationMs,
|
|
9252
|
-
error: !result.success ?
|
|
9275
|
+
error: !result.success ? describeFfmpegFailure(result, processTimeout) : void 0,
|
|
9253
9276
|
failureReason: result.failureReason
|
|
9254
9277
|
};
|
|
9255
9278
|
}
|
|
@@ -9765,6 +9788,36 @@ import { copyFileSync as copyFileSync2, existsSync as existsSync8, linkSync as l
|
|
|
9765
9788
|
import { join as join10 } from "path";
|
|
9766
9789
|
import { cpus } from "os";
|
|
9767
9790
|
|
|
9791
|
+
// ../engine/src/utils/concurrencyLimit.ts
|
|
9792
|
+
function createConcurrencyLimit(limit) {
|
|
9793
|
+
let active = 0;
|
|
9794
|
+
const waiting = [];
|
|
9795
|
+
const waitForSlot = (signal) => new Promise((resolve) => {
|
|
9796
|
+
const granted = () => {
|
|
9797
|
+
signal?.removeEventListener("abort", aborted);
|
|
9798
|
+
resolve(true);
|
|
9799
|
+
};
|
|
9800
|
+
const aborted = () => {
|
|
9801
|
+
waiting.splice(waiting.indexOf(granted), 1);
|
|
9802
|
+
resolve(false);
|
|
9803
|
+
};
|
|
9804
|
+
waiting.push(granted);
|
|
9805
|
+
signal?.addEventListener("abort", aborted, { once: true });
|
|
9806
|
+
});
|
|
9807
|
+
return async (task, signal) => {
|
|
9808
|
+
if (signal?.aborted) return task();
|
|
9809
|
+
if (active < limit) active++;
|
|
9810
|
+
else if (!await waitForSlot(signal)) return task();
|
|
9811
|
+
try {
|
|
9812
|
+
return await task();
|
|
9813
|
+
} finally {
|
|
9814
|
+
const next = waiting.shift();
|
|
9815
|
+
if (next) next();
|
|
9816
|
+
else active--;
|
|
9817
|
+
}
|
|
9818
|
+
};
|
|
9819
|
+
}
|
|
9820
|
+
|
|
9768
9821
|
// ../engine/src/services/referenceResolver.ts
|
|
9769
9822
|
function findReferenceTargetEl(doc, refId) {
|
|
9770
9823
|
return doc.getElementById(refId) ?? doc.querySelector(`[data-composition-id="${refId}"]`);
|
|
@@ -10905,6 +10958,7 @@ function unwrapTemplate(html) {
|
|
|
10905
10958
|
// ../engine/src/services/extractionCache.ts
|
|
10906
10959
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
10907
10960
|
import {
|
|
10961
|
+
createReadStream as createReadStream3,
|
|
10908
10962
|
existsSync as existsSync7,
|
|
10909
10963
|
lstatSync as lstatSync2,
|
|
10910
10964
|
mkdirSync as mkdirSync5,
|
|
@@ -10970,6 +11024,15 @@ var COMPLETE_SENTINEL = ".hf-complete";
|
|
|
10970
11024
|
var GC_MARKER = ".hf-last-gc";
|
|
10971
11025
|
var SCHEMA_PREFIX = "hfcache-v6-";
|
|
10972
11026
|
var KEY_HEX_CHARS = 16;
|
|
11027
|
+
async function readContentSha256(path, signal) {
|
|
11028
|
+
try {
|
|
11029
|
+
const hash = createHash2("sha256");
|
|
11030
|
+
for await (const chunk of createReadStream3(path, { signal })) hash.update(chunk);
|
|
11031
|
+
return hash.digest("hex");
|
|
11032
|
+
} catch {
|
|
11033
|
+
return null;
|
|
11034
|
+
}
|
|
11035
|
+
}
|
|
10973
11036
|
function readKeyStat(videoPath) {
|
|
10974
11037
|
try {
|
|
10975
11038
|
const stat = statSync6(videoPath);
|
|
@@ -10980,9 +11043,9 @@ function readKeyStat(videoPath) {
|
|
|
10980
11043
|
}
|
|
10981
11044
|
function canonicalKeyBlob(input) {
|
|
10982
11045
|
const durationForKey = Number.isFinite(input.duration) ? input.duration : -1;
|
|
11046
|
+
const identity = input.contentSha256 !== void 0 ? { c: input.contentSha256 } : { p: input.videoPath, m: input.mtimeMs };
|
|
10983
11047
|
const blob = {
|
|
10984
|
-
|
|
10985
|
-
m: input.mtimeMs,
|
|
11048
|
+
...identity,
|
|
10986
11049
|
s: input.size,
|
|
10987
11050
|
ms: input.mediaStart,
|
|
10988
11051
|
d: durationForKey,
|
|
@@ -11228,6 +11291,7 @@ function extractionFrameCountForDuration(durationSeconds, fps, isVFR) {
|
|
|
11228
11291
|
const frames = Number(frameCount);
|
|
11229
11292
|
return Math.max(1, Number.isSafeInteger(frames) ? frames : Number.MAX_SAFE_INTEGER);
|
|
11230
11293
|
}
|
|
11294
|
+
var DOWNLOAD_SUBDIR = "_downloads";
|
|
11231
11295
|
var EXTRACT_CACHE_MIN_AGE_MS = 60 * 60 * 1e3;
|
|
11232
11296
|
var GC_STALENESS_MS = 24 * 60 * 60 * 1e3;
|
|
11233
11297
|
var SDR_TO_HDR_COLORSPACE_FILTER = "colorspace=all=bt2020:iall=bt709:range=tv";
|
|
@@ -11623,6 +11687,8 @@ function restoreSourceColourFilter(metadata) {
|
|
|
11623
11687
|
return tags.length > 0 ? [`setparams=${tags.map(([key, value]) => `${key}=${value}`).join(":")}`] : [];
|
|
11624
11688
|
}
|
|
11625
11689
|
var EXTRACTION_SEGMENT_SECONDS = 120;
|
|
11690
|
+
var EXTRACTION_SLOTS = Math.max(1, Math.floor(cpus().length / 2));
|
|
11691
|
+
var inExtractionSlot = createConcurrencyLimit(EXTRACTION_SLOTS);
|
|
11626
11692
|
function splitFrameRanges(ranges, segmentFrames) {
|
|
11627
11693
|
const segments = [];
|
|
11628
11694
|
for (const range of ranges) {
|
|
@@ -11646,30 +11712,33 @@ async function runSegmentedExtraction(job) {
|
|
|
11646
11712
|
while (next < segmentCount && !signal.aborted) {
|
|
11647
11713
|
const index = next++;
|
|
11648
11714
|
const { firstFrame, frames } = segments[index];
|
|
11649
|
-
const result = await
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11653
|
-
|
|
11654
|
-
|
|
11655
|
-
|
|
11656
|
-
|
|
11657
|
-
|
|
11658
|
-
|
|
11659
|
-
|
|
11660
|
-
|
|
11661
|
-
|
|
11662
|
-
|
|
11663
|
-
|
|
11664
|
-
|
|
11665
|
-
|
|
11666
|
-
|
|
11715
|
+
const result = await inExtractionSlot(
|
|
11716
|
+
() => runFfmpeg(
|
|
11717
|
+
[
|
|
11718
|
+
...decodeArgs,
|
|
11719
|
+
"-noaccurate_seek",
|
|
11720
|
+
"-ss",
|
|
11721
|
+
String(startTime + firstFrame / fps),
|
|
11722
|
+
"-i",
|
|
11723
|
+
videoPath,
|
|
11724
|
+
// Read one frame past the segment and keep exactly its own frames.
|
|
11725
|
+
"-t",
|
|
11726
|
+
String((frames + 1) / fps),
|
|
11727
|
+
"-frames:v",
|
|
11728
|
+
String(frames),
|
|
11729
|
+
"-start_number",
|
|
11730
|
+
String(firstFrame + 1),
|
|
11731
|
+
...filterAndEncodeArgs
|
|
11732
|
+
],
|
|
11733
|
+
{ ...job.runOptions, signal }
|
|
11734
|
+
),
|
|
11735
|
+
signal
|
|
11667
11736
|
);
|
|
11668
11737
|
results[index] = result;
|
|
11669
11738
|
if (!result.success) failed.abort();
|
|
11670
11739
|
}
|
|
11671
11740
|
};
|
|
11672
|
-
const workers = Math.min(segmentCount,
|
|
11741
|
+
const workers = Math.min(segmentCount, EXTRACTION_SLOTS);
|
|
11673
11742
|
await Promise.all(Array.from({ length: workers }, worker));
|
|
11674
11743
|
const ran = results.filter(Boolean);
|
|
11675
11744
|
const failure = ran.find((r) => !r.success && r.terminationReason !== "abort") ?? ran.find((r) => !r.success);
|
|
@@ -11774,20 +11843,23 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
11774
11843
|
if (resampleVfrToCfr) args.push("-fps_mode", "cfr", "-r", ffmpegFps);
|
|
11775
11844
|
let processResult;
|
|
11776
11845
|
if (resampleVfrToCfr && vfFilters.length > 0 && !isHdr && NUT_RAW_PIXEL_FORMATS.has(metadata.pixelFormat ?? "")) {
|
|
11777
|
-
processResult = await
|
|
11778
|
-
|
|
11779
|
-
|
|
11780
|
-
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
|
|
11788
|
-
|
|
11789
|
-
|
|
11790
|
-
|
|
11846
|
+
processResult = await inExtractionSlot(
|
|
11847
|
+
() => runFfmpegPipeline(
|
|
11848
|
+
[...args, "-an", "-sn", "-dn", "-c:v", "rawvideo", "-f", "nut", "pipe:1"],
|
|
11849
|
+
[
|
|
11850
|
+
"-f",
|
|
11851
|
+
"nut",
|
|
11852
|
+
"-i",
|
|
11853
|
+
"pipe:0",
|
|
11854
|
+
"-vf",
|
|
11855
|
+
[...restoreSourceColourFilter(metadata), ...vfFilters].join(","),
|
|
11856
|
+
"-fps_mode",
|
|
11857
|
+
"passthrough",
|
|
11858
|
+
...encodeArgs
|
|
11859
|
+
],
|
|
11860
|
+
runOptions
|
|
11861
|
+
),
|
|
11862
|
+
signal
|
|
11791
11863
|
);
|
|
11792
11864
|
} else {
|
|
11793
11865
|
const filterArgs = vfFilters.length > 0 ? ["-vf", vfFilters.join(",")] : [];
|
|
@@ -11804,7 +11876,10 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
11804
11876
|
segmentFrames
|
|
11805
11877
|
),
|
|
11806
11878
|
runOptions
|
|
11807
|
-
}) : await
|
|
11879
|
+
}) : await inExtractionSlot(
|
|
11880
|
+
() => runFfmpeg([...args, ...filterArgs, ...encodeArgs], runOptions),
|
|
11881
|
+
signal
|
|
11882
|
+
);
|
|
11808
11883
|
}
|
|
11809
11884
|
if (processResult.failureReason === "external_interruption") {
|
|
11810
11885
|
throw new VideoSourceExtractionError(
|
|
@@ -11905,6 +11980,9 @@ function canHoldFinalFramePastEof(video) {
|
|
|
11905
11980
|
return !video.loop && Number.isFinite(timelineDuration) && timelineDuration > 0;
|
|
11906
11981
|
}
|
|
11907
11982
|
var FINAL_FRAME_LOGICAL_DURATION_SECONDS = 1e-6;
|
|
11983
|
+
function isVideoPastTimelineEnd(video, timelineEnd) {
|
|
11984
|
+
return video.start >= timelineEnd;
|
|
11985
|
+
}
|
|
11908
11986
|
function rebaseVideoToWindow(video, window2) {
|
|
11909
11987
|
if (window2.preserveTimelinePhase) return;
|
|
11910
11988
|
const start = Math.abs(window2.compositionStart) < 1e-9 ? 0 : window2.compositionStart;
|
|
@@ -12248,14 +12326,16 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12248
12326
|
const warnedSrcs = /* @__PURE__ */ new Set();
|
|
12249
12327
|
for (const video of videos) {
|
|
12250
12328
|
if (signal?.aborted) break;
|
|
12251
|
-
if (options.timelineEnd !== void 0 && video
|
|
12329
|
+
if (options.timelineEnd !== void 0 && isVideoPastTimelineEnd(video, options.timelineEnd)) {
|
|
12330
|
+
continue;
|
|
12331
|
+
}
|
|
12252
12332
|
try {
|
|
12253
12333
|
let videoPath = video.src;
|
|
12254
12334
|
if (!isHttpUrl(videoPath)) {
|
|
12255
12335
|
videoPath = resolveProjectRelativeSrc(video.src, baseDir, compiledDir);
|
|
12256
12336
|
}
|
|
12257
12337
|
if (isHttpUrl(videoPath)) {
|
|
12258
|
-
const downloadDir = join10(options.outputDir,
|
|
12338
|
+
const downloadDir = join10(options.outputDir, DOWNLOAD_SUBDIR);
|
|
12259
12339
|
mkdirSync6(downloadDir, { recursive: true });
|
|
12260
12340
|
videoPath = await downloadToTemp(
|
|
12261
12341
|
videoPath,
|
|
@@ -12295,6 +12375,10 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12295
12375
|
}
|
|
12296
12376
|
}
|
|
12297
12377
|
breakdown.resolveMs = Date.now() - phase1Start;
|
|
12378
|
+
const contentKeyedDirs = [
|
|
12379
|
+
join10(options.outputDir, DOWNLOAD_SUBDIR),
|
|
12380
|
+
...options.contentKeyedDirs ?? []
|
|
12381
|
+
];
|
|
12298
12382
|
const cacheKeyInputs = resolvedVideos.map(({ video, videoPath }) => {
|
|
12299
12383
|
const stat = readKeyStat(videoPath);
|
|
12300
12384
|
if (!stat) return null;
|
|
@@ -12302,7 +12386,8 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12302
12386
|
videoPath,
|
|
12303
12387
|
mtimeMs: stat.mtimeMs,
|
|
12304
12388
|
size: stat.size,
|
|
12305
|
-
mediaStart: video.mediaStart
|
|
12389
|
+
mediaStart: video.mediaStart,
|
|
12390
|
+
contentKeyed: contentKeyedDirs.some((dir) => isPathInside(videoPath, dir))
|
|
12306
12391
|
};
|
|
12307
12392
|
});
|
|
12308
12393
|
const phase2ProbeStart = Date.now();
|
|
@@ -12448,10 +12533,26 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12448
12533
|
});
|
|
12449
12534
|
return { ...rehydrated, ownedByLookup: true };
|
|
12450
12535
|
}
|
|
12536
|
+
const contentSha256ByPath = /* @__PURE__ */ new Map();
|
|
12537
|
+
async function hashContentKeyedSources(works) {
|
|
12538
|
+
if (!cacheRootDir) return;
|
|
12539
|
+
const paths = /* @__PURE__ */ new Set();
|
|
12540
|
+
for (const work of works) {
|
|
12541
|
+
const keyInput = cacheKeyInputs[work.index];
|
|
12542
|
+
if (keyInput?.contentKeyed) paths.add(keyInput.videoPath);
|
|
12543
|
+
}
|
|
12544
|
+
await Promise.all(
|
|
12545
|
+
[...paths].map(
|
|
12546
|
+
async (path) => contentSha256ByPath.set(path, await readContentSha256(path, signal))
|
|
12547
|
+
)
|
|
12548
|
+
);
|
|
12549
|
+
}
|
|
12451
12550
|
function lookupCacheFor(work) {
|
|
12452
12551
|
if (!cacheRootDir) return { work };
|
|
12453
12552
|
const keyInput = cacheKeyInputs[work.index];
|
|
12454
12553
|
if (!keyInput) return { work };
|
|
12554
|
+
const contentSha256 = keyInput.contentKeyed ? contentSha256ByPath.get(keyInput.videoPath) ?? null : void 0;
|
|
12555
|
+
if (contentSha256 === null) return { work };
|
|
12455
12556
|
const transformParts = [
|
|
12456
12557
|
work.sdrToHdrTransfer ? sdrToHdrTransformKey(work.sdrToHdrTransfer) : void 0,
|
|
12457
12558
|
work.hdrToSdrTransformKey,
|
|
@@ -12463,6 +12564,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12463
12564
|
mtimeMs: keyInput.mtimeMs,
|
|
12464
12565
|
size: keyInput.size,
|
|
12465
12566
|
mediaStart: keyInput.mediaStart,
|
|
12567
|
+
contentSha256,
|
|
12466
12568
|
duration: work.videoDuration,
|
|
12467
12569
|
fps: fpsKey,
|
|
12468
12570
|
format: work.format,
|
|
@@ -12719,8 +12821,11 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12719
12821
|
});
|
|
12720
12822
|
}
|
|
12721
12823
|
const cacheMisses = [];
|
|
12722
|
-
|
|
12723
|
-
|
|
12824
|
+
const worksToLookUp = [...uniqueWorks.values()].filter(
|
|
12825
|
+
(work) => !uniqueOutcomes.has(work.dedupeKey)
|
|
12826
|
+
);
|
|
12827
|
+
await hashContentKeyedSources(worksToLookUp);
|
|
12828
|
+
for (const work of worksToLookUp) {
|
|
12724
12829
|
const lookup = lookupCacheFor(work);
|
|
12725
12830
|
if ("work" in lookup) {
|
|
12726
12831
|
cacheMisses.push(lookup);
|
|
@@ -13659,6 +13764,159 @@ function applyVolumeEnvelopeToWav(wavPath, keyframes, trackStart, baseVolume) {
|
|
|
13659
13764
|
}
|
|
13660
13765
|
}
|
|
13661
13766
|
|
|
13767
|
+
// ../core/dist/audio/audioFxTruePeak.js
|
|
13768
|
+
var TRUE_PEAK_OVERSAMPLE = 4;
|
|
13769
|
+
var TRUE_PEAK_DETECTOR_DELAY = 16;
|
|
13770
|
+
var KERNEL_HALF = TRUE_PEAK_OVERSAMPLE * TRUE_PEAK_DETECTOR_DELAY;
|
|
13771
|
+
var BRANCH_TAPS = 2 * TRUE_PEAK_DETECTOR_DELAY + 1;
|
|
13772
|
+
var TRUE_PEAK_WORKLET_SOURCE = `
|
|
13773
|
+
const TP_OS = ${TRUE_PEAK_OVERSAMPLE};
|
|
13774
|
+
const TP_HALF = ${KERNEL_HALF};
|
|
13775
|
+
const TP_BRANCH = ${BRANCH_TAPS};
|
|
13776
|
+
const TP_DELAY = ${TRUE_PEAK_DETECTOR_DELAY};
|
|
13777
|
+
|
|
13778
|
+
function besselI0(x) {
|
|
13779
|
+
let sum = 1, term = 1;
|
|
13780
|
+
for (let k = 1; k < 40; k++) {
|
|
13781
|
+
term *= (x / (2 * k)) * (x / (2 * k));
|
|
13782
|
+
sum += term;
|
|
13783
|
+
}
|
|
13784
|
+
return sum;
|
|
13785
|
+
}
|
|
13786
|
+
|
|
13787
|
+
/** Kaiser-windowed sinc, one array per polyphase branch, newest tap first. */
|
|
13788
|
+
function tpBranches() {
|
|
13789
|
+
const beta = 6;
|
|
13790
|
+
const norm = besselI0(beta);
|
|
13791
|
+
const branches = [];
|
|
13792
|
+
for (let p = 0; p < TP_OS; p++) {
|
|
13793
|
+
const taps = new Float64Array(TP_BRANCH);
|
|
13794
|
+
let sum = 0;
|
|
13795
|
+
for (let j = 0; j < TP_BRANCH; j++) {
|
|
13796
|
+
const k = p + TP_OS * j;
|
|
13797
|
+
if (k > 2 * TP_HALF) continue;
|
|
13798
|
+
const t = (k - TP_HALF) / TP_OS;
|
|
13799
|
+
const sinc = t === 0 ? 1 : Math.sin(Math.PI * t) / (Math.PI * t);
|
|
13800
|
+
const r = (k - TP_HALF) / TP_HALF;
|
|
13801
|
+
taps[j] = sinc * (besselI0(beta * Math.sqrt(Math.max(0, 1 - r * r))) / norm);
|
|
13802
|
+
sum += taps[j];
|
|
13803
|
+
}
|
|
13804
|
+
// Unity gain at DC on every branch, so a steady level reads as itself.
|
|
13805
|
+
for (let j = 0; j < TP_BRANCH; j++) taps[j] /= sum;
|
|
13806
|
+
branches.push(taps);
|
|
13807
|
+
}
|
|
13808
|
+
return branches;
|
|
13809
|
+
}
|
|
13810
|
+
|
|
13811
|
+
class HfTruePeak extends AudioWorkletProcessor {
|
|
13812
|
+
constructor(o) {
|
|
13813
|
+
super();
|
|
13814
|
+
this.p = o.processorOptions || {};
|
|
13815
|
+
this.look = Math.max(1, Math.round((this.p.lookahead ?? 3) * sampleRate / 1000));
|
|
13816
|
+
this.delayLen = this.look + TP_DELAY;
|
|
13817
|
+
this.branches = tpBranches();
|
|
13818
|
+
this.setLevels();
|
|
13819
|
+
this.clear();
|
|
13820
|
+
this.port.onmessage = (e) => {
|
|
13821
|
+
if (e.data && e.data.__hfDispose) { this.dead = true; return; }
|
|
13822
|
+
if (e.data && e.data.__hfReset) { this.clear(); return; }
|
|
13823
|
+
this.p = { ...this.p, ...e.data };
|
|
13824
|
+
this.setLevels();
|
|
13825
|
+
};
|
|
13826
|
+
}
|
|
13827
|
+
/** Back to the state of a fresh processor: no audio in the delay line, no gain reduction. */
|
|
13828
|
+
clear() {
|
|
13829
|
+
this.ch = [];
|
|
13830
|
+
this.rel = 1;
|
|
13831
|
+
this.dqIdx = new Int32Array(this.look + 2);
|
|
13832
|
+
this.dqVal = new Float64Array(this.look + 2);
|
|
13833
|
+
this.dqHead = 0;
|
|
13834
|
+
this.dqTail = 0;
|
|
13835
|
+
this.t = 0;
|
|
13836
|
+
this.avgRing = new Float64Array(this.look + 1).fill(1);
|
|
13837
|
+
this.avgPos = 0;
|
|
13838
|
+
this.avgSum = this.look + 1;
|
|
13839
|
+
}
|
|
13840
|
+
setLevels() {
|
|
13841
|
+
this.ceiling = Math.pow(10, (this.p.ceiling ?? -1) / 20);
|
|
13842
|
+
this.relCoef = 1 - Math.exp(-1 / (sampleRate * Math.max(1e-4, (this.p.release ?? 80) / 1000)));
|
|
13843
|
+
}
|
|
13844
|
+
channel(c) {
|
|
13845
|
+
if (!this.ch[c]) {
|
|
13846
|
+
this.ch[c] = {
|
|
13847
|
+
delay: new Float32Array(this.delayLen),
|
|
13848
|
+
w: 0,
|
|
13849
|
+
hist: new Float32Array(TP_BRANCH * 2),
|
|
13850
|
+
pos: 0,
|
|
13851
|
+
};
|
|
13852
|
+
}
|
|
13853
|
+
return this.ch[c];
|
|
13854
|
+
}
|
|
13855
|
+
/** Largest interpolated magnitude across channels for the newest sample. */
|
|
13856
|
+
peak(chans, inputs, n) {
|
|
13857
|
+
let peak = 0;
|
|
13858
|
+
for (let c = 0; c < chans; c++) {
|
|
13859
|
+
const s = this.channel(c);
|
|
13860
|
+
const x = inputs[c][n];
|
|
13861
|
+
s.pos = (s.pos + 1) % TP_BRANCH;
|
|
13862
|
+
s.hist[s.pos] = x;
|
|
13863
|
+
s.hist[s.pos + TP_BRANCH] = x;
|
|
13864
|
+
const base = s.pos + TP_BRANCH;
|
|
13865
|
+
for (let p = 0; p < TP_OS; p++) {
|
|
13866
|
+
const taps = this.branches[p];
|
|
13867
|
+
let acc = 0;
|
|
13868
|
+
for (let j = 0; j < TP_BRANCH; j++) acc += taps[j] * s.hist[base - j];
|
|
13869
|
+
const a = acc < 0 ? -acc : acc;
|
|
13870
|
+
if (a > peak) peak = a;
|
|
13871
|
+
}
|
|
13872
|
+
}
|
|
13873
|
+
return peak;
|
|
13874
|
+
}
|
|
13875
|
+
/** Windowed minimum of the required gain over the last look + 1 values. */
|
|
13876
|
+
windowMin(v) {
|
|
13877
|
+
const cap = this.dqIdx.length;
|
|
13878
|
+
while (this.dqTail !== this.dqHead && this.dqVal[(this.dqTail + cap - 1) % cap] >= v) {
|
|
13879
|
+
this.dqTail = (this.dqTail + cap - 1) % cap;
|
|
13880
|
+
}
|
|
13881
|
+
this.dqIdx[this.dqTail] = this.t;
|
|
13882
|
+
this.dqVal[this.dqTail] = v;
|
|
13883
|
+
this.dqTail = (this.dqTail + 1) % cap;
|
|
13884
|
+
while (this.dqIdx[this.dqHead] <= this.t - (this.look + 1)) {
|
|
13885
|
+
this.dqHead = (this.dqHead + 1) % cap;
|
|
13886
|
+
}
|
|
13887
|
+
this.t++;
|
|
13888
|
+
return this.dqVal[this.dqHead];
|
|
13889
|
+
}
|
|
13890
|
+
process(inputs, outputs) {
|
|
13891
|
+
if (this.dead) return false;
|
|
13892
|
+
const i = inputs[0], o = outputs[0];
|
|
13893
|
+
if (!i || !i.length) return true;
|
|
13894
|
+
const chans = i.length;
|
|
13895
|
+
const frames = i[0].length;
|
|
13896
|
+
for (let n = 0; n < frames; n++) {
|
|
13897
|
+
const peak = this.peak(chans, i, n);
|
|
13898
|
+
const need = peak > this.ceiling ? this.ceiling / peak : 1;
|
|
13899
|
+
const m = this.windowMin(need);
|
|
13900
|
+
this.avgSum += m - this.avgRing[this.avgPos];
|
|
13901
|
+
this.avgRing[this.avgPos] = m;
|
|
13902
|
+
this.avgPos = (this.avgPos + 1) % this.avgRing.length;
|
|
13903
|
+
const target = Math.min(1, this.avgSum / this.avgRing.length);
|
|
13904
|
+
const released = this.rel + (1 - this.rel) * this.relCoef;
|
|
13905
|
+
this.rel = released < target ? released : target;
|
|
13906
|
+
if (target >= 1 && this.rel > 0.9999999) this.rel = 1;
|
|
13907
|
+
for (let c = 0; c < chans; c++) {
|
|
13908
|
+
const s = this.channel(c);
|
|
13909
|
+
o[c][n] = s.delay[s.w] * this.rel;
|
|
13910
|
+
s.delay[s.w] = i[c][n];
|
|
13911
|
+
s.w = (s.w + 1) % this.delayLen;
|
|
13912
|
+
}
|
|
13913
|
+
}
|
|
13914
|
+
return true;
|
|
13915
|
+
}
|
|
13916
|
+
}
|
|
13917
|
+
registerProcessor("hf-truepeak", HfTruePeak);
|
|
13918
|
+
`;
|
|
13919
|
+
|
|
13662
13920
|
// ../core/dist/audio/audioFxTail.js
|
|
13663
13921
|
var MAX_FX_TAIL_SECONDS = 5;
|
|
13664
13922
|
var TAIL_FLOOR = 1e-3;
|
|
@@ -13720,7 +13978,7 @@ import { join as join12 } from "path";
|
|
|
13720
13978
|
import { pathToFileURL } from "url";
|
|
13721
13979
|
|
|
13722
13980
|
// ../core/dist/generated/audio-fx-runtime-inline.js
|
|
13723
|
-
var AUDIO_FX_RUNTIME_IIFE = '"use strict";(()=>{var Ae="data-fx-chain",yt=Ae.slice(5),j=1,M=(e="frequency",t="Frequency",n=1e3,o=20,a=2e4)=>({kind:"number",key:e,label:t,unit:"Hz",min:o,max:a,step:1,default:n,scale:"log",automatable:!0}),S=(e=.707,t="Bandwidth \\u2014 higher is narrower.")=>({kind:"number",key:"q",label:"Q",unit:"",min:.1,max:20,step:.01,default:e,scale:"log",automatable:!0,hint:t}),R=(e=-40,t=40,n=0)=>({kind:"number",key:"gain",label:"Gain",unit:"dB",min:e,max:t,step:.1,default:n,automatable:!0}),z={kind:"enum",key:"poles",label:"Slope",options:[{value:"1",label:"6 dB/oct"},{value:"2",label:"12 dB/oct"}],default:"2",hint:"Two poles is the usual biquad; one pole is gentler."},K=[{id:"gain",label:"Gain",group:"dynamics",description:"Raise or lower the whole signal. Automate it to duck under something else.",params:[R(-60,12,0)],web:"gain-node"},{id:"peaking",label:"Peaking EQ",group:"filter",description:"Boost or cut a band, leaving everything either side alone.",params:[M("frequency","Frequency",1e3),R(-40,40,0),S(1)],web:"biquad-peaking"},{id:"lowshelf",label:"Low Shelf",group:"filter",description:"Lift or drop everything below the corner frequency.",params:[M("frequency","Frequency",200,20,2e3),R(-40,40,0)],web:"biquad-lowshelf"},{id:"highshelf",label:"High Shelf",group:"filter",description:"Lift or drop everything above the corner frequency.",params:[M("frequency","Frequency",4e3,500,2e4),R(-40,40,0)],web:"biquad-highshelf"},{id:"highpass",label:"High-pass",group:"filter",description:"Remove low frequencies \\u2014 the usual fix for rumble on a voice.",params:[M("frequency","Cutoff",300,20,2e4),S(.707),z],web:"biquad-highpass"},{id:"lowpass",label:"Low-pass",group:"filter",description:"Remove high frequencies \\u2014 darkens or muffles a track.",params:[M("frequency","Cutoff",8e3,100,2e4),S(.707),z],web:"biquad-lowpass"},{id:"compressor",label:"Compressor",group:"dynamics",description:"Pull loud parts down so the quiet ones can come up.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-60,max:0,step:.5,default:-24,hint:"Level above which the compressor starts working."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:4},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:2e3,step:.1,default:20,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:250,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83,hint:"1 is a hard corner; higher eases into it."},{kind:"number",key:"makeup",label:"Makeup",unit:"dB",min:0,max:36,step:.1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1,hint:"Below 1 blends the dry signal back in."}],web:"worklet-compressor"},{id:"limiter",label:"Limiter",group:"dynamics",description:"Hard ceiling \\u2014 nothing gets past the limit.",params:[{kind:"number",key:"limit",label:"Ceiling",unit:"dB",min:-24,max:0,step:.1,default:-1},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.1,max:80,step:.1,default:5},{kind:"number",key:"release",label:"Release",unit:"ms",min:1,max:8e3,step:1,default:50,scale:"log"},{kind:"number",key:"level_out",label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0}],web:"worklet-limiter"},{id:"gate",label:"Noise Gate",group:"dynamics",description:"Silence the track when it drops below the threshold.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-80,max:0,step:.5,default:-35},{kind:"number",key:"range",label:"Range",unit:"dB",min:-80,max:0,step:.5,default:-24,hint:"How far down the gate pulls when closed."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:10},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:9e3,step:.1,default:1,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:100,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83}],web:"worklet-gate"},{id:"saturate",label:"Saturation",group:"nonlinear",description:"Soft-clip the waveform for warmth or outright distortion.",params:[{kind:"enum",key:"type",label:"Curve",options:[{value:"tanh",label:"Tanh"},{value:"atan",label:"Arctan"},{value:"cubic",label:"Cubic"},{value:"exp",label:"Exponential"},{value:"alg",label:"Algebraic"},{value:"quintic",label:"Quintic"},{value:"sin",label:"Sine"},{value:"erf",label:"Error function"},{value:"hard",label:"Hard clip"}],default:"tanh"},{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-40,max:0,step:.1,default:-6},{kind:"number",key:"output",automatable:!0,label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0},{kind:"number",key:"oversample",label:"Oversample",unit:"x",min:1,max:8,step:1,default:4,hint:"Higher costs more but keeps aliasing down."}],web:"waveshaper"},{id:"bitcrush",label:"Bitcrush",group:"nonlinear",description:"Drop bit depth and sample rate for a lo-fi, digital sound.",params:[{kind:"number",key:"bits",label:"Bit depth",unit:"bit",min:1,max:32,step:.1,default:8},{kind:"number",key:"samples",label:"Sample hold",unit:"x",min:1,max:250,step:1,default:1,hint:"Repeats each sample N times \\u2014 a crude downsample."},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-bitcrush"},{id:"pitchshift",label:"Pitch shift",group:"time",description:"Shifts pitch up or down without changing playback speed. Adds ~50 ms of latency.",params:[{kind:"number",key:"semitones",label:"Semitones",unit:"st",min:-12,max:12,step:1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-pitchshift"},{id:"delay",label:"Delay",group:"time",description:"Repeating echoes behind the dry signal.",params:[{kind:"number",key:"time",automatable:!0,label:"Time",unit:"ms",min:1,max:5e3,step:1,default:250,scale:"log"},{kind:"number",key:"feedback",automatable:!0,label:"Feedback",unit:"",min:.01,max:.95,step:.01,default:.35},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.4}],web:"delay-feedback"},{id:"chorus",label:"Chorus",group:"time",description:"Detuned copies of the signal for width and thickness.",params:[{kind:"number",key:"delay",automatable:!0,label:"Delay",unit:"ms",min:1,max:100,step:.1,default:7},{kind:"number",key:"depth",automatable:!0,label:"Depth",unit:"ms",min:0,max:10,step:.01,default:2},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.01,max:10,step:.01,default:1},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.5}],web:"chorus-lfo"},{id:"phaser",label:"Phaser",group:"time",description:"Sweeping notches moving through the spectrum.",params:[{kind:"number",key:"in_gain",automatable:!0,label:"Input",unit:"",min:0,max:1,step:.01,default:.4},{kind:"number",key:"out_gain",automatable:!0,label:"Output",unit:"",min:0,max:2,step:.01,default:.74},{kind:"number",key:"delay",label:"Delay",unit:"ms",min:.1,max:5,step:.1,default:3},{kind:"number",key:"decay",label:"Decay",unit:"",min:0,max:.99,step:.01,default:.4},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.1,max:2,step:.01,default:.5},{kind:"enum",key:"type",label:"Waveform",options:[{value:"0",label:"Triangular"},{value:"1",label:"Sinusoidal"}],default:"0"}],web:"allpass-phaser"},{id:"reverb",label:"Reverb",group:"time",description:"Room tail. Both ends convolve the same generated impulse, so preview matches render.",params:[{kind:"number",key:"size",label:"Room size",unit:"",min:.05,max:1,step:.01,default:.7},{kind:"number",key:"damping",label:"Damping",unit:"",min:0,max:1,step:.01,default:.5,hint:"Higher rolls the top off the tail faster."},{kind:"number",key:"wet",automatable:!0,label:"Wet",unit:"",min:0,max:1,step:.01,default:.35},{kind:"number",key:"dry",automatable:!0,label:"Dry",unit:"",min:0,max:1,step:.01,default:.7}],web:"convolver"}],E=new Map(K.map(e=>[e.id,e]));function F(e){return E.get(e)}var kt=K.map(e=>e.id);function k(e,t){let n=E.get(e);if(!n)return{};let o={};for(let a of n.params){let r=t?.[a.key];if(a.kind==="enum"){let s=a.options.some(l=>l.value===r);o[a.key]=s?r:a.default;continue}let i=typeof r=="number"?r:typeof r=="string"&&r.trim()!==""?Number(r):Number.NaN;o[a.key]=Number.isFinite(i)?Math.min(a.max,Math.max(a.min,i)):a.default}return o}var A=class extends Error{constructor(t){super(t),this.name="AudioFxChainError"}};function J(e){let t;try{t=JSON.parse(e)}catch(a){throw new A(`Chain file is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new A("Chain file must be a JSON object.");let n=t;if(n.version!==j)throw new A(`Unsupported chain version: ${String(n.version)}`);if(!Array.isArray(n.nodes))throw new A("Chain file is missing a `nodes` array.");let o=n.nodes.map(ye);return{version:j,nodes:o}}function H(e){return e.nodes.filter(t=>t.enabled!==!1)}function ye(e,t){if(typeof e!="object"||e===null)throw new A(`Node ${t} is not an object.`);let n=e;if(typeof n.type!="string"||!E.has(n.type))throw new A(`Node ${t} has unknown effect type: ${String(n.type)}`);return we({type:n.type,id:D(n.id),fromCarve:Y(n.fromCarve),fromPreset:D(n.fromPreset),label:D(n.label),fromEq:D(n.fromEq),fromLeveller:Y(n.fromLeveller),presetAmount:ke(n.presetAmount),enabled:n.enabled!==!1,params:k(n.type,n.params??void 0)})}function D(e){return typeof e=="string"&&e?e:void 0}function Y(e){return e===!0?!0:void 0}function ke(e){if(!(typeof e!="number"||!Number.isFinite(e)))return Math.min(1,Math.max(0,e))}function we(e){for(let t of Object.keys(e))e[t]===void 0&&delete e[t];return e}var ve=`\nconst dbToLin = (db) => Math.pow(10, db / 20);\n\n/**\n * One-pole envelope followers, one per channel.\n *\n * Per channel matters: the followers advance once per sample, so a single shared\n * follower stepped once per channel per sample. On stereo that ran a 20 ms attack\n * as 10 ms, and gave the right channel a gain computed from an envelope that had\n * already traversed the left \\u2014 so the two channels ducked by different amounts\n * from the same input and the image pumped.\n */\nclass EnvBank {\n constructor(attackMs, releaseMs) { this.set(attackMs, releaseMs); this.values = []; }\n set(attackMs, releaseMs) {\n this.a = Math.exp(-1 / (sampleRate * Math.max(1e-5, attackMs / 1000)));\n this.r = Math.exp(-1 / (sampleRate * Math.max(1e-5, releaseMs / 1000)));\n }\n push(ch, x) {\n const prev = this.values[ch] ?? 0;\n const m = Math.abs(x);\n const c = m > prev ? this.a : this.r;\n const next = m + c * (prev - m);\n this.values[ch] = next;\n return next;\n }\n}\n\n/** Soft-knee gain computer in dB, matching acompressor\'s shape. */\nfunction kneeGain(envDb, thresholdDb, ratio, kneeDb) {\n const over = envDb - thresholdDb;\n if (kneeDb > 0 && over > -kneeDb && over < kneeDb) {\n const t = (over + kneeDb) / (2 * kneeDb);\n return -((1 - 1 / ratio) * kneeDb * t * t);\n }\n return over > 0 ? -(over * (1 - 1 / ratio)) : 0;\n}\n\n// log10/exp per sample is the single most expensive thing a dynamics processor\n// can do on the audio thread, and every sample below the knee needs neither:\n// its gain is exactly unity. Comparing envelopes in the linear domain lets the\n// quiet majority of samples skip the transcendentals entirely.\nconst LN10_OVER_20 = Math.LN10 / 20;\nconst dbToLinFast = (db) => Math.exp(db * LN10_OVER_20);\n\nclass HfCompressor extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 20, this.p.release ?? 250);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 20, this.p.release ?? 250);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const makeup = dbToLin(p.makeup ?? 0);\n const mix = p.mix ?? 1;\n // Knee is expressed as a ratio in FFmpeg; convert to dB width.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const thresholdDb = p.threshold ?? -24;\n const ratio = p.ratio ?? 4;\n // Below this the gain computer returns unity, so the sample needs no\n // logarithm at all.\n const kneeStartLin = dbToLin(thresholdDb - kneeDb);\n const dry = 1 - mix;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n // Per-channel follower, and the sub-knee shortcut: below the knee the\n // gain is exactly unity, so the sample needs no logarithm at all.\n const env = this.env.push(ch, x);\n if (env <= kneeStartLin) {\n out[n] = x * makeup * mix + x * dry;\n continue;\n }\n const envDb = 20 * Math.log10(env);\n const g = dbToLinFast(kneeGain(envDb, thresholdDb, ratio, kneeDb));\n out[n] = x * g * makeup * mix + x * dry;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-compressor", HfCompressor);\n\nclass HfLimiter extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 5, this.p.release ?? 50);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 5, this.p.release ?? 50);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const ceiling = dbToLin(this.p.limit ?? -1);\n const outGain = dbToLin(this.p.level_out ?? 0);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n // Only ever attenuate: a limiter that can raise level is a compressor.\n const g = env > ceiling ? ceiling / env : 1;\n out[n] = x * g * outGain;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-limiter", HfLimiter);\n\nclass HfGate extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 1, this.p.release ?? 100);\n this.gains = [];\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 1, this.p.release ?? 100);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const threshold = dbToLin(p.threshold ?? -35);\n const floor = dbToLin(p.range ?? -24);\n const ratio = p.ratio ?? 10;\n // Knee is declared in the registry as a ratio, like the compressor\'s; a\n // hard threshold ignored it and chattered on material sitting right at the\n // gate point.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const kneeLin = dbToLin((p.threshold ?? -35) + kneeDb);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n let target = 1;\n // Above the threshold the gate is fully open; skip the pow entirely.\n if (env < threshold) {\n const under = env > 1e-9 ? threshold / env : 1e9;\n target = Math.max(floor, 1 / Math.pow(under, ratio - 1));\n if (!isFinite(target)) target = floor;\n }\n // Smooth toward the target so the gate does not click on every sample.\n const held = this.gains[ch] ?? 1;\n const c = target < held ? this.env.r : this.env.a;\n const smoothed = target + c * (held - target);\n this.gains[ch] = smoothed;\n out[n] = x * smoothed;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-gate", HfGate);\n\nclass HfBitcrush extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.holds = [];\n this.held = [];\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const levels = Math.pow(2, (p.bits ?? 8) - 1);\n const step = Math.max(1, Math.round(p.samples ?? 1));\n const mix = p.mix ?? 1;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n if (this.held[ch] === undefined) this.held[ch] = 0;\n if (this.holds[ch] === undefined) this.holds[ch] = 0;\n for (let n = 0; n < inp.length; n++) {\n // Per channel: advancing one shared counter only on the last channel\n // left every earlier channel either unheld or frozen for a whole\n // 128-sample quantum, clicking once a block.\n if (this.holds[ch] === 0) this.held[ch] = Math.round(inp[n] * levels) / levels;\n out[n] = this.held[ch] * mix + inp[n] * (1 - mix);\n this.holds[ch] = (this.holds[ch] + 1) % step;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-bitcrush", HfBitcrush);\n\n/** Linear-interpolated read, \\`delaySamples\\` behind the write head. */\nfunction readTap(ring, write, delaySamples) {\n const len = ring.length;\n const pos = (write - 1 - delaySamples + len) % len;\n const i0 = Math.floor(pos);\n const frac = pos - i0;\n const i1 = (i0 + 1) % len;\n return ring[i0] * (1 - frac) + ring[i1] * frac;\n}\n\n/** Equal-power-ish crossfade, zero at a tap\'s reset point \\u2014 hides the splice. */\nfunction xfade(phase) {\n return Math.sin(Math.PI * phase);\n}\n\n/**\n * Dual-tap granular delay line: two read taps 180\\xB0 apart in a 100 ms grain,\n * each sweeping at a speed relative to the write head that shifts pitch\n * without changing duration. One tap is always fading in as the other fades\n * out, which hides the splice each tap makes when it wraps.\n *\n * write/phase are block-level state, advanced once per SAMPLE across all\n * channels together (not once per channel) \\u2014 advancing them inside the\n * per-channel loop would move the tap 2x/4x too fast on a stereo/quad input.\n */\nclass HfPitchshift extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.grain = Math.round(sampleRate * 0.1);\n this.buf = [];\n this.write = 0;\n this.phase = 0;\n // Samples written so far, capped at one grain. The taps read up to a grain\n // behind the write head, so until this fills they would read the ring\'s\n // zeros \\u2014 the head of every clip came out attenuated or silent.\n this.filled = 0;\n // How much of the wet (pitch-shifted) path is currently in the output, and\n // where it is heading. Crossing between dry and wet is a ~50 ms jump in the\n // signal, so it is RAMPED rather than switched: a hard swap either way is a\n // click. Ramping in both directions is also what lets a node return to true\n // bypass at semitones 0 \\u2014 a one-way latch left preview stuck with the delay\n // that the render, building a fresh node from the attribute, does not have.\n this.wet = 0;\n this.wetTarget = 0;\n // ~15 ms one-pole, short enough to feel immediate on a slider drag and long\n // enough that the splice is inaudible.\n this.wetCoef = Math.exp(-1 / (sampleRate * 0.015));\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const semitones = Math.max(-12, Math.min(12, p.semitones ?? 0));\n const mix = Math.max(0, Math.min(1, p.mix ?? 1));\n const grain = this.grain;\n const ringLen = grain * 2;\n const n = i[0] ? i[0].length : 0;\n for (let ch = 0; ch < i.length; ch++) {\n if (!this.buf[ch]) this.buf[ch] = new Float32Array(ringLen);\n }\n\n // Nothing to shift, or mixed fully out. The grain delay is ~grain/2\n // whatever the ratio, so at semitones=0 this degenerated into a pure 50 ms\n // delay of the signal \\u2014 while the copy for that exact setting reads\n // "Unchanged pitch".\n this.wetTarget = semitones === 0 ? 0 : mix;\n\n // Fully dry AND settled: take the cheap transparent path. The ring keeps\n // filling, so a later shift does not start cold.\n if (this.wetTarget === 0 && this.wet < 1e-4) {\n this.wet = 0;\n let w = this.write;\n for (let s = 0; s < n; s++) {\n for (let ch = 0; ch < i.length; ch++) {\n const x = i[ch][s];\n this.buf[ch][w] = x;\n o[ch][s] = x;\n }\n w = (w + 1) % ringLen;\n }\n this.write = w;\n this.filled = Math.min(grain, this.filled + n);\n return true;\n }\n\n const ratio = Math.pow(2, semitones / 12);\n const inc = (1 - ratio) / grain;\n let write = this.write, phase = this.phase, filled = this.filled, wetNow = this.wet;\n const target = this.wetTarget, coef = this.wetCoef;\n for (let s = 0; s < n; s++) {\n phase += inc;\n phase -= Math.floor(phase);\n const phaseB = (phase + 0.5) % 1;\n const gA = xfade(phase), gB = xfade(phaseB);\n // Ramp the wet path in as the ring fills rather than reading zeros:\n // 100 ms of unshifted audio at the head of a clip beats 50 ms of silence.\n const warm = filled >= grain ? 1 : filled / grain;\n wetNow = target + coef * (wetNow - target);\n const wetMix = wetNow * warm;\n for (let ch = 0; ch < i.length; ch++) {\n const ring = this.buf[ch];\n const inp = i[ch], out = o[ch];\n const x = inp[s];\n ring[write] = x;\n const wet =\n readTap(ring, write, phase * grain) * gA + readTap(ring, write, phaseB * grain) * gB;\n out[s] = x * (1 - wetMix) + wet * wetMix;\n }\n write = (write + 1) % ringLen;\n if (filled < grain) filled++;\n }\n this.write = write;\n this.phase = phase;\n this.filled = filled;\n this.wet = wetNow;\n return true;\n }\n}\nregisterProcessor("hf-pitchshift", HfPitchshift);\n`,I=new WeakMap;var Me=new WeakSet;function O(e){let t=I.get(e);return t||(t=(async()=>{if(!e.audioWorklet)throw new Error("AudioWorklet is unavailable \\u2014 the page needs a secure context (https, localhost or file://)");let n=`data:text/javascript;base64,${btoa(String.fromCharCode(...new TextEncoder().encode(ve)))}`;await e.audioWorklet.addModule(n),Me.add(e)})().catch(n=>{throw I.delete(e),n}),I.set(e,t)),t}function Fe(e,t,n){let o=.6+Math.max(0,Math.min(1,t))*2.6,a=Math.max(1,Math.floor(e*o)),r=new Float32Array(a),i=Math.round(t*1e3)*2654435761+Math.round(n*1e3)*40503>>>0,s=()=>(i=i*1664525+1013904223>>>0,i/4294967295*2-1),l=Math.max(.001,1-Math.max(0,Math.min(1,n))),u=0,d=0;for(let c=0;c<a;c++){u+=l*(s()-u);let b=u*Math.pow(1-c/a,2.5);r[c]=b,d+=b*b}let m=Math.sqrt(d);if(m>0)for(let c=0;c<a;c++)r[c]=(r[c]??0)/m;return r}var f=e=>typeof e=="number"?e:Number(e??0),B=e=>e/1e3;function te(e,t,n,o){let a=Math.max(1,Math.round(e.sampleRate)),r=e.createBuffer(1,a,e.sampleRate),i=r.getChannelData(0);for(let u=0;u<a;u++){let d=u/a;i[u]=t==="sine"?Math.sin(2*Math.PI*d):4*Math.abs((d+.75)%1-.5)-1}let s=e.createBufferSource();s.buffer=r,s.loop=!0,s.playbackRate.value=n;let l=(o*n%1+1)%1*(a/e.sampleRate);return s.start(typeof e.currentTime=="number"?e.currentTime:0,l),s}function ne(e){try{e.stop()}catch{}e.disconnect()}function L(e,t){return[{param:e},{param:t,map:n=>1-n}]}function ae(e,t,n){e.gain.value=n,t.gain.value=1-n}function G(e,t,n){return{input:e,output:e,update:t,automation:n,dispose:()=>e.disconnect()}}var He=new Set(["peaking","highpass","lowpass"]),Q=e=>Math.pow(10,e/20),Te=(e,t)=>{let n=e.createGain(),o=a=>{n.gain.value=Q(f(a.gain))};return o(t),G(n,o,{gain:[{param:n.gain,map:Q}]})};function T(e,t){return(n,o)=>{let a=n.createBiquadFilter();a.type=e;let r=i=>{a.frequency.value=f(i.frequency),i.q!==void 0&&(a.Q.value=f(i.q)),t&&(a.gain.value=f(i.gain))};return r(o),G(a,r,{frequency:[{param:a.frequency}],...He.has(e)?{q:[{param:a.Q}]}:{},...t?{gain:[{param:a.gain}]}:{}})}}function Ne(e){return(t,n)=>{let o=Math.tan(Math.PI*f(n.frequency)/t.sampleRate),a=e==="highpass"?t.createIIRFilter([1/(1+o),-1/(1+o)],[1,(o-1)/(o+1)]):t.createIIRFilter([o/(1+o),o/(1+o)],[1,(o-1)/(o+1)]);return G(a,()=>{})}}function N(e){return(t,n)=>{let o=new AudioWorkletNode(t,e,{processorOptions:{...n}});return{input:o,output:o,update:a=>o.port.postMessage({...a}),dispose:()=>{o.port.postMessage({__hfDispose:!0}),o.disconnect()}}}}var Pe={tanh:Math.tanh,atan:e=>2/Math.PI*Math.atan(Math.PI/2*e),cubic:e=>Math.abs(e)>=1?Math.sign(e):e-e**3/3,exp:e=>Math.sign(e)*(1-Math.exp(-Math.abs(e))),alg:e=>e/Math.sqrt(1+e*e),quintic:e=>Math.abs(e)>=1?Math.sign(e):e-e**5/5,sin:e=>Math.abs(e)>=1?Math.sign(e):Math.sin(Math.PI/2*e),erf:e=>Math.tanh(1.20211*e),hard:e=>Math.max(-1,Math.min(1,e))},_e=(e,t)=>{let n=e.createWaveShaper(),o=e.createGain(),a=e.createGain();o.connect(n).connect(a);let r=i=>{let s=Pe[String(i.type)]??Math.tanh,l=Math.pow(10,f(i.threshold)/20),u=8192,d=new Float32Array(u);for(let m=0;m<u;m++){let c=m/(u-1)*2-1;d[m]=s(c/Math.max(1e-6,l))*l}n.curve=d,n.oversample=f(i.oversample)>=4?"4x":f(i.oversample)>=2?"2x":"none",a.gain.value=Math.pow(10,f(i.output)/20)};return r(t),{input:o,output:a,update:r,automation:{output:[{param:a.gain,map:i=>Math.pow(10,i/20)}]},dispose:()=>{o.disconnect(),n.disconnect(),a.disconnect()}}},Re=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createDelay(5),r=e.createGain(),i=e.createGain(),s=e.createGain();n.connect(a),a.connect(r),r.connect(a),a.connect(i).connect(o),n.connect(s).connect(o);let l=u=>{a.delayTime.value=Math.min(5,f(u.time)/1e3),r.gain.value=f(u.feedback),ae(i,s,f(u.mix))};return l(t),{input:n,output:o,update:l,automation:{time:[{param:a.delayTime,map:u=>Math.min(5,B(u))}],feedback:[{param:r.gain}],mix:L(i.gain,s.gain)},dispose:()=>[n,o,a,r,i,s].forEach(u=>u.disconnect())}},De=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createDelay(.5),i=te(e,"sine",f(t.speed),n),s=e.createGain(),l=e.createGain(),u=e.createGain();i.connect(s).connect(r.delayTime),o.connect(r).connect(l).connect(a),o.connect(u).connect(a);let d=m=>{r.delayTime.value=f(m.delay)/1e3,s.gain.value=f(m.depth)/1e3,i.playbackRate.value=f(m.speed),ae(l,u,f(m.mix))};return d(t),{input:o,output:a,update:d,automation:{delay:[{param:r.delayTime,map:B}],depth:[{param:s.gain,map:B}],speed:[{param:i.playbackRate}],mix:L(l.gain,u.gain)},dispose:()=>{ne(i),[o,a,r,s,l,u].forEach(m=>m.disconnect())}}},Ce=6,Se=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createGain(),i=e.createGain(),s=te(e,String(t.type)==="1"?"sine":"triangle",f(t.speed),n),l=e.createGain(),u=e.createGain(),d=e.createGain(),m=[];o.connect(r);let c=r;for(let p=0;p<Ce;p++){let h=e.createBiquadFilter();h.type="allpass",h.Q.value=.7071,l.connect(h.frequency),c.connect(h),c=h,m.push(h)}s.connect(l),c.connect(u).connect(i),r.connect(d).connect(i),i.connect(a);let b=p=>{let h=1e3/Math.max(.1,f(p.delay));for(let g of m)g.frequency.value=h;l.gain.value=h*f(p.decay),s.playbackRate.value=f(p.speed),r.gain.value=f(p.in_gain),i.gain.value=f(p.out_gain),u.gain.value=1,d.gain.value=1};return b(t),{input:o,output:a,update:b,automation:{speed:[{param:s.playbackRate}],in_gain:[{param:r.gain}],out_gain:[{param:i.gain}]},dispose:()=>{ne(s),[o,a,r,i,l,u,d,...m].forEach(p=>p.disconnect())}}},Ee=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createConvolver(),r=e.createGain(),i=e.createGain();a.normalize=!1,n.connect(a).connect(r).connect(o),n.connect(i).connect(o);let s="",l=u=>{let d=`${f(u.size)}:${f(u.damping)}`;if(d!==s){let m=Fe(e.sampleRate,f(u.size),f(u.damping)),c=e.createBuffer(1,m.length,e.sampleRate);c.getChannelData(0).set(m),a.buffer=c,s=d}r.gain.value=f(u.wet),i.gain.value=f(u.dry)};return l(t),{input:n,output:o,update:l,automation:{wet:[{param:r.gain}],dry:[{param:i.gain}]},dispose:()=>[n,o,a,r,i].forEach(u=>u.disconnect())}},Ie={"gain-node":Te,"biquad-peaking":T("peaking",!0),"biquad-lowshelf":T("lowshelf",!0),"biquad-highshelf":T("highshelf",!0),"biquad-highpass":T("highpass",!1),"biquad-lowpass":T("lowpass",!1),"worklet-compressor":N("hf-compressor"),"worklet-limiter":N("hf-limiter"),"worklet-gate":N("hf-gate"),"worklet-bitcrush":N("hf-bitcrush"),"worklet-pitchshift":N("hf-pitchshift"),waveshaper:_e,"delay-feedback":Re,"chorus-lfo":De,"allpass-phaser":Se,convolver:Ee};function oe(e){return e.nodes.some(t=>F(t.type)?.web.startsWith("worklet-")??!1)}function Oe(e,t,n,o=0){let a=F(t);if(!a)throw new Error(`Unknown effect type: ${t}`);let r=k(t,n);if((t==="highpass"||t==="lowpass")&&String(r.poles)==="1")return Ne(t)(e,r,o);let i=Ie[a.web];if(!i)throw new Error(`No Web Audio builder for ${a.web}`);return i(e,r,o)}function Z(e){let t=[];for(let n of e){let o=n.fromPreset,a=t.at(-1);if(a&&a.preset===o)a.nodes.push(n);else{let r=typeof n.presetAmount=="number"?n.presetAmount:1;t.push({...o?{preset:o}:{},amount:Math.min(1,Math.max(0,r)),nodes:[n]})}}return t}function ee(e){return H(e).map(t=>{let n=k(t.type,t.params),o=n.poles!==void 0?`:${n.poles}`:"",a=String(n.poles)==="1"?`@${n.frequency}`:"",r=t.type==="phaser"?`~${n.type}`:"",i=t.fromPreset?`%${t.fromPreset}`:"";return`${t.type}${o}${a}${r}${i}`}).join("|")}function re(e,t,n=0){var m;let o=e.createGain(),a=e.createGain(),r=[],i=[],s=Z(H(t)),l=o;for(let c of s){let b=null;if(c.preset){let p=e.createGain(),h=e.createGain(),g=e.createGain(),v=e.createGain();g.gain.value=c.amount,h.gain.value=1-c.amount,l.connect(p),p.connect(h).connect(v),b={entry:p,wet:g,dry:h,join:v},l=p}for(let p of c.nodes){let h=Oe(e,p.type,p.params??{},n);l.connect(h.input),l=h.output,r.push({...p.id?{id:p.id}:{},type:p.type,handle:h})}b&&c.preset&&(l.connect(b.wet).connect(b.join),i.push({id:c.preset,...b}),l=b.join)}l.connect(a);let u=ee(t),d={};for(let c of i)(d[m=c.id]??(d[m]=[])).push(...L(c.wet.gain,c.dry.gain));return{input:o,output:a,presets:d,nodes:r,update(c){if(ee(c)!==u)return!1;H(c).forEach((p,h)=>{let g=r[h];g&&(g.handle.update(k(p.type,p.params)),p.id===void 0?delete g.id:g.id=p.id)});let b=0;for(let p of Z(H(c))){if(!p.preset)continue;let h=i[b++];!h||h.id!==p.preset||(h.wet.gain.value=p.amount,h.dry.gain.value=1-p.amount)}return!0},dispose(){for(let{handle:c}of r)c.dispose();for(let{entry:c,wet:b,dry:p,join:h}of i)c.disconnect(),b.disconnect(),p.disconnect(),h.disconnect();o.disconnect(),a.disconnect()}}}function Be(e){return 10**(e/20)}var ie=Be(12);var q=.1,V=10;var Le="data-automation",Et=Le.slice(5),C=1,Ge=512,x=class extends Error{constructor(t){super(t),this.name="AudioAutomationError"}},U="volume",se="rate";function _(e){if(e===U)return{kind:"volume"};if(e===se)return{kind:"rate"};let t=e.split(".");if(t.length===3&&t[0]==="fx"&&t[1]===qe){let a=t[2];return a?{kind:"preset",presetId:a}:null}if(t.length!==3||t[0]!=="fx")return null;let[,n,o]=t;return!n||!o?null:{kind:"fx",nodeId:n,param:o}}function ue(e,t){return`fx.${e}.${t}`}var qe="preset";var Ve={min:0,max:1,step:.01,unit:"",label:"Amount",scale:"linear",default:1},le={min:0,max:ie,step:.01,unit:"",label:"Volume",scale:"linear",default:1},de={min:q,max:V,step:.05,unit:"x",label:"Speed",scale:"log",default:1};function X(e,t){let n=_(e);if(!n)return null;if(n.kind==="volume")return le;if(n.kind==="rate")return de;if(n.kind==="preset")return t?.nodes.some(s=>s.fromPreset===n.presetId)?{...Ve,label:`${n.presetId} \\xB7 Amount`}:null;let o=t?.nodes.find(i=>i.id===n.nodeId);if(!o)return null;let a=F(o.type),r=a?.params.find(i=>i.key===n.param);return!r||r.kind!=="number"?null:{min:r.min,max:r.max,step:r.step,unit:r.unit,label:`${a?.label??o.type} \\xB7 ${r.label}`,scale:r.scale==="log"&&r.min>0?"log":"linear",default:r.default}}function P(e){if(typeof e=="number")return Number.isFinite(e)?e:null;if(typeof e=="string"&&e.trim()!==""){let t=Number(e);return Number.isFinite(t)?t:null}return null}function Ue(e){let t=P(e);return t===null||t===0?0:Math.min(1,Math.max(-1,t))}function ce(e,t){let n=P(e),o=P(t);if(n===null||o===null)return null;let a=s=>Math.min(.999,Math.max(.001,s)),r=a(n),i=a(o);return Math.abs(r-i)<1e-6?null:{x:r,y:i}}function Xe(e){let t=Ue(e?.curve),n=ce(e?.viaX,e?.viaY);return{...t?{curve:t}:{},...n?{viaX:n.x,viaY:n.y}:{}}}function $e(e,t){let n=P(e?.t),o=P(e?.v);if(n===null||o===null)return null;let a=t?Math.min(t.max,Math.max(t.min,o)):o;return{t:Math.max(0,n),v:a,...Xe(e)}}function me(e,t){let n=e.map(a=>$e(a,t)).filter(a=>a!==null).sort((a,r)=>a.t-r.t),o=[];for(let a of n)o.length>0&&o[o.length-1].t===a.t?o[o.length-1]=a:o.push(a);return o.slice(0,Ge)}function We(e){let t=[];for(let n of e.lanes){if(!_(n.target))continue;let o=n.target===U?le:n.target===se?de:null,a=me(n.points??[],o);a.length>0&&t.push({target:n.target,points:a})}return{version:C,lanes:t}}function pe(e,t){let n=[];for(let o of e.lanes){let a=X(o.target,t);if(!a)continue;let r=me(o.points,a);r.length>0&&n.push({target:o.target,points:r})}return{version:C,lanes:n}}function he(e){let t;try{t=JSON.parse(e)}catch(a){throw new x(`Automation is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new x("Automation must be a JSON object.");let n=t;if(n.version!==C)throw new x(`Unsupported automation version: ${String(n.version)}`);if(!Array.isArray(n.lanes))throw new x("Automation is missing a `lanes` array.");let o=n.lanes.map((a,r)=>{if(typeof a!="object"||a===null)throw new x(`Lane ${r} is not an object.`);let i=a;if(typeof i.target!="string"||!_(i.target))throw new x(`Lane ${r} has an unreadable target: ${String(i.target)}`);if(!Array.isArray(i.points))throw new x(`Lane ${r} is missing a \\`points\\` array.`);return{target:i.target,points:i.points}});return We({version:C,lanes:o})}function je(e,t){return t?Math.pow(e,Math.pow(2,2*t)):e}function ze(e,t){let n=e-.5,o=t-.5,a=.999,r=1e6,i=n>0?n/(a-e):n<0?-n/(e-(1-a)):0,s=o>0?o/(a-t):o<0?-o/(t-(1-a)):0,l=Math.min(r,Math.max(1,i,s));return{cx:e+n/l,cy:t+o/l,w:l}}function Ye(e,t,n){let{cx:o,cy:a,w:r}=ze(t,n),i=2-2*r,s=e*i-1+2*r*o,l=-e*i-2*r*o,u=Ke(s,l,e),d=1-u,m=d*d+2*r*u*d+u*u;return m>0?(2*r*a*u*d+u*u)/m:e}function Ke(e,t,n){if(Math.abs(e)<1e-12)return Math.abs(t)<1e-12?n:-n/t;let o=Math.sqrt(Math.max(0,t*t-4*e*n)),a=(-t+o)/(2*e),r=(-t-o)/(2*e),i=s=>s>=-1e-9&&s<=1+1e-9;return i(a)?Math.min(1,Math.max(0,a)):i(r)?Math.min(1,Math.max(0,r)):n}function Je(e,t){let n=ce(t.viaX,t.viaY);return n?Ye(Math.min(1,Math.max(0,e)),n.x,n.y):je(e,t.curve)}function Qe(e,t,n,o){return o==="log"&&e>0&&t>0?Math.exp(Math.log(e)+(Math.log(t)-Math.log(e))*n):e+(t-e)*n}function $(e,t,n="linear"){let o=e.points;if(o.length===0)return 0;let a=o[0];if(t<=a.t)return a.v;let r=o[o.length-1];if(t>=r.t)return r.v;let i=0,s=o.length-1;for(;s-i>1;){let m=i+s>>1;o[m].t<=t?i=m:s=m}let l=o[i],u=o[s],d=u.t-l.t;return d<=0?u.v:Qe(l.v,u.v,Je((t-l.t)/d,l),n)}function fe(e){return e.points.length<=1||e.points.every(t=>t.v===e.points[0].v)}function be(e,t,n,o,a="linear"){let r=Math.max(2,Math.floor(o)),i=new Float32Array(r),s=n-t;for(let l=0;l<r;l+=1)i[l]=$(e,t+s*l/(r-1),a);return i}var Ze=100,et=8,tt=4096,W=e=>e;function nt(e,t,n){return!(!!e.curve||e.viaX!==void 0&&e.viaY!==void 0)&&t==="linear"&&!n}function at(e){let t=Math.ceil(e*Ze);return Math.min(tt,Math.max(et,t))}function ot(e,t,n,o,a){let{scheduledAt:r,elapsed:i,rate:s}=o,l=a??W,u=w=>r+(w-i)/s,d=e.points[t],m=e.points[t+1],c=u(m.t);if(c<=r)return null;let b=Math.max(r,u(d.t)),p=c-b;if(p<=0)return null;if(nt(d,n,a))return{kind:"ramp",time:c,value:l(m.v)};let h=at(p),g=be(e,Math.max(d.t,i),m.t,h,n),v=new Float32Array(h);for(let w=0;w<h;w+=1)v[w]=l(g[w]??0);return{kind:"curve",time:b,duration:p,values:v}}function rt(e,t,n,o){let a=[];for(let i=0;i+1<e.points.length;i+=1){let s=ot(e,i,t,n,o);s&&a.push(s)}let r=a[0];return r?.kind==="curve"&&r.time<=n.scheduledAt||a.unshift({kind:"set",time:n.scheduledAt,value:(o??W)($(e,n.elapsed,t))}),a}function it(e,t){for(let n of t)if(n.kind==="set")e.setValueAtTime(n.value,n.time);else if(n.kind==="ramp")e.linearRampToValueAtTime(n.value,n.time);else try{e.setValueCurveAtTime(n.values,n.time,n.duration)}catch{let o=n.values[n.values.length-1]??0;e.linearRampToValueAtTime(o,n.time+n.duration)}}function st(e){for(let t of e)t.param.cancelScheduledValues(0)}function ut(e,t,n,o){if(t.points.length!==0)for(let a of e){if(st([a]),fe(t)){let r=(a.map??W)(t.points[0].v);a.param.setValueAtTime(r,o.scheduledAt);continue}it(a.param,rt(t,n,o,a.map))}}function ge(e,t,n,o,a){let r=new Map(n.filter(s=>s.id).map(s=>[s.id,s.handle])),i=[];for(let s of e.lanes){let l=_(s.target);if(!l)continue;let u=l.kind==="preset"?a?.[l.presetId]:l.kind==="fx"?r.get(l.nodeId)?.automation?.[l.param]:void 0;if(!u||u.length===0)continue;let d=X(s.target,t);d&&(ut(u,s,d.scale,o),i.push(...u))}return i}var lt=5,dt=.001;function y(e,t,n){let o=Number(k(e.type,e.params)[t]??0);if(!e.id||!n)return Number.isFinite(o)?o:0;let a=n.lanes.find(r=>r.target===ue(e.id??"",t));return!a||a.points.length===0?Number.isFinite(o)?o:0:a.points.reduce((r,i)=>Math.max(r,i.v),-1/0)}function ct(e,t){let n=Math.min(5,e/1e3);if(n<=0)return 0;let o=Math.max(0,Math.min(.999,t));return o<=0?n:Math.ceil(Math.log(dt)/Math.log(o))*n}function mt(e,t){return y(e,"wet",t)>0?.6+Math.max(0,Math.min(1,y(e,"size",t)))*2.6:0}function pt(e,t){return y(e,"mix",t)>0?ct(y(e,"time",t),y(e,"feedback",t)):0}function ht(e,t){return y(e,"mix",t)>0?y(e,"delay",t)/1e3:0}function ft(e,t){return y(e,"mix",t)>0?.2:0}function bt(e,t){if(e.enabled===!1)return 0;switch(e.type){case"reverb":return mt(e,t);case"delay":return pt(e,t);case"chorus":return ht(e,t);case"pitchshift":return ft(e,t);default:return 0}}function xe(e,t){let n=e.nodes.reduce((o,a)=>o+bt(a,t),0);return Math.min(lt,n)}function gt(e,t,n,o,a){let r=e.createBuffer(n,o,a);for(let i=0;i<n;i++){let s=t[i];s&&r.getChannelData(i).set(s)}return r}function xt(e){let t=[];for(let n=0;n<e.numberOfChannels;n++)t.push(new Float32Array(e.getChannelData(n)));return t}async function At(e,t,n,o){let a=J(n),r=Math.max(1,e.length),i=e[0]?.length??0;if(i===0)return e;let s=o?pe(he(o),a):null,l=Math.ceil(xe(a,s??void 0)*t),u=new OfflineAudioContext(r,i+l,t);oe(a)&&await O(u);let d=u.createBufferSource();d.buffer=gt(u,e,r,i,t);let m=re(u,a);return s&&ge(s,a,m.nodes,{scheduledAt:0,elapsed:0,rate:1},m.presets),d.connect(m.input),m.output.connect(u.destination),d.start(),xt(await u.startRendering())}window.__HF_AUDIO_FX={render:At};})();\n';
|
|
13981
|
+
var AUDIO_FX_RUNTIME_IIFE = '"use strict";(()=>{var we="data-fx-chain",Mt=we.slice(5),W=1,T=(e="frequency",t="Frequency",n=1e3,o=20,a=2e4)=>({kind:"number",key:e,label:t,unit:"Hz",min:o,max:a,step:1,default:n,scale:"log",automatable:!0}),C=(e=.707,t="Bandwidth: higher is narrower.")=>({kind:"number",key:"q",label:"Q",unit:"",min:.1,max:20,step:.01,default:e,scale:"log",automatable:!0,hint:t}),R=(e=-40,t=40,n=0)=>({kind:"number",key:"gain",label:"Gain",unit:"dB",min:e,max:t,step:.1,default:n,automatable:!0}),K={kind:"enum",key:"poles",label:"Slope",options:[{value:"1",label:"6 dB/oct"},{value:"2",label:"12 dB/oct"}],default:"2",hint:"Two poles is the usual biquad; one pole is gentler."},z=[{id:"gain",label:"Gain",group:"dynamics",description:"Raise or lower the whole signal. Automate it to duck under something else.",params:[R(-60,12,0)],web:"gain-node"},{id:"peaking",label:"Peaking EQ",group:"filter",description:"Boost or cut a band, leaving everything either side alone.",params:[T("frequency","Frequency",1e3),R(-40,40,0),C(1)],web:"biquad-peaking"},{id:"lowshelf",label:"Low Shelf",group:"filter",description:"Lift or drop everything below the corner frequency.",params:[T("frequency","Frequency",200,20,2e3),R(-40,40,0)],web:"biquad-lowshelf"},{id:"highshelf",label:"High Shelf",group:"filter",description:"Lift or drop everything above the corner frequency.",params:[T("frequency","Frequency",4e3,500,2e4),R(-40,40,0)],web:"biquad-highshelf"},{id:"highpass",label:"High-pass",group:"filter",description:"Remove low frequencies: the usual fix for rumble on a voice.",params:[T("frequency","Cutoff",300,20,2e4),C(.707),K],web:"biquad-highpass"},{id:"lowpass",label:"Low-pass",group:"filter",description:"Remove high frequencies, which darkens or muffles a track.",params:[T("frequency","Cutoff",8e3,100,2e4),C(.707),K],web:"biquad-lowpass"},{id:"compressor",label:"Compressor",group:"dynamics",description:"Pull loud parts down so the quiet ones can come up.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-60,max:0,step:.5,default:-24,hint:"Level above which the compressor starts working."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:4},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:2e3,step:.1,default:20,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:250,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83,hint:"1 is a hard corner; higher eases into it."},{kind:"number",key:"makeup",label:"Makeup",unit:"dB",min:0,max:36,step:.1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1,hint:"Below 1 blends the dry signal back in."}],web:"worklet-compressor"},{id:"limiter",label:"Limiter",group:"dynamics",description:"Hard ceiling: nothing gets past the limit.",params:[{kind:"number",key:"limit",label:"Ceiling",unit:"dB",min:-24,max:0,step:.1,default:-1},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.1,max:80,step:.1,default:5},{kind:"number",key:"release",label:"Release",unit:"ms",min:1,max:8e3,step:1,default:50,scale:"log"},{kind:"number",key:"level_out",label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0}],web:"worklet-limiter"},{id:"truepeak",label:"True-peak limiter",group:"dynamics",description:"Lookahead limiter that holds a 4x estimate of the inter-sample peak at a dBTP ceiling; full-band noise can land up to about 1.7 dB above it. Adds lookahead plus about 0.3 ms of latency.",params:[{kind:"number",key:"ceiling",label:"Ceiling",unit:"dBTP",min:-24,max:0,step:.1,default:-1,hint:"Target true peak, measured on a 4x oversampled signal. Dense full-band sound can end up to about 1.7 dB above it."},{kind:"number",key:"lookahead",label:"Lookahead",unit:"ms",min:.5,max:10,step:.1,default:3,hint:"How far ahead the gain starts falling. Longer is smoother and adds the same latency."},{kind:"number",key:"release",label:"Release",unit:"ms",min:10,max:2e3,step:1,default:80,scale:"log"}],web:"worklet-truepeak"},{id:"gate",label:"Noise Gate",group:"dynamics",description:"Silence the track when it drops below the threshold.",params:[{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-80,max:0,step:.5,default:-35},{kind:"number",key:"range",label:"Range",unit:"dB",min:-80,max:0,step:.5,default:-24,hint:"How far down the gate pulls when closed."},{kind:"number",key:"ratio",label:"Ratio",unit:":1",min:1,max:20,step:.1,default:10},{kind:"number",key:"attack",label:"Attack",unit:"ms",min:.01,max:9e3,step:.1,default:1,scale:"log"},{kind:"number",key:"release",label:"Release",unit:"ms",min:.01,max:9e3,step:1,default:100,scale:"log"},{kind:"number",key:"knee",label:"Knee",unit:"",min:1,max:8,step:.01,default:2.83}],web:"worklet-gate"},{id:"saturate",label:"Saturation",group:"nonlinear",description:"Soft-clip the waveform for warmth or outright distortion.",params:[{kind:"enum",key:"type",label:"Curve",options:[{value:"tanh",label:"Tanh"},{value:"atan",label:"Arctan"},{value:"cubic",label:"Cubic"},{value:"exp",label:"Exponential"},{value:"alg",label:"Algebraic"},{value:"quintic",label:"Quintic"},{value:"sin",label:"Sine"},{value:"erf",label:"Error function"},{value:"hard",label:"Hard clip"}],default:"tanh"},{kind:"number",key:"threshold",label:"Threshold",unit:"dB",min:-40,max:0,step:.1,default:-6},{kind:"number",key:"output",automatable:!0,label:"Output",unit:"dB",min:-24,max:24,step:.1,default:0},{kind:"number",key:"oversample",label:"Oversample",unit:"x",min:1,max:8,step:1,default:4,hint:"Higher costs more but keeps aliasing down."}],web:"waveshaper"},{id:"bitcrush",label:"Bitcrush",group:"nonlinear",description:"Drop bit depth and sample rate for a lo-fi, digital sound.",params:[{kind:"number",key:"bits",label:"Bit depth",unit:"bit",min:1,max:32,step:.1,default:8},{kind:"number",key:"samples",label:"Sample hold",unit:"x",min:1,max:250,step:1,default:1,hint:"Repeats each sample N times, a crude downsample."},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-bitcrush"},{id:"pitchshift",label:"Pitch shift",group:"time",description:"Shifts pitch up or down without changing playback speed. Adds ~50 ms of latency.",params:[{kind:"number",key:"semitones",label:"Semitones",unit:"st",min:-12,max:12,step:1,default:0},{kind:"number",key:"mix",label:"Mix",unit:"",min:0,max:1,step:.01,default:1}],web:"worklet-pitchshift"},{id:"delay",label:"Delay",group:"time",description:"Repeating echoes behind the dry signal.",params:[{kind:"number",key:"time",automatable:!0,label:"Time",unit:"ms",min:1,max:5e3,step:1,default:250,scale:"log"},{kind:"number",key:"feedback",automatable:!0,label:"Feedback",unit:"",min:.01,max:.95,step:.01,default:.35},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.4}],web:"delay-feedback"},{id:"chorus",label:"Chorus",group:"time",description:"Detuned copies of the signal for width and thickness.",params:[{kind:"number",key:"delay",automatable:!0,label:"Delay",unit:"ms",min:1,max:100,step:.1,default:7},{kind:"number",key:"depth",automatable:!0,label:"Depth",unit:"ms",min:0,max:10,step:.01,default:2},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.01,max:10,step:.01,default:1},{kind:"number",key:"mix",automatable:!0,label:"Mix",unit:"",min:0,max:1,step:.01,default:.5}],web:"chorus-lfo"},{id:"phaser",label:"Phaser",group:"time",description:"Sweeping notches moving through the spectrum.",params:[{kind:"number",key:"in_gain",automatable:!0,label:"Input",unit:"",min:0,max:1,step:.01,default:.4},{kind:"number",key:"out_gain",automatable:!0,label:"Output",unit:"",min:0,max:2,step:.01,default:.74},{kind:"number",key:"delay",label:"Delay",unit:"ms",min:.1,max:5,step:.1,default:3},{kind:"number",key:"decay",label:"Decay",unit:"",min:0,max:.99,step:.01,default:.4},{kind:"number",key:"speed",automatable:!0,label:"Rate",unit:"Hz",min:.1,max:2,step:.01,default:.5},{kind:"enum",key:"type",label:"Waveform",options:[{value:"0",label:"Triangular"},{value:"1",label:"Sinusoidal"}],default:"0"}],web:"allpass-phaser"},{id:"reverb",label:"Reverb",group:"time",description:"Room tail. Both ends convolve the same generated impulse, so preview matches render.",params:[{kind:"number",key:"size",label:"Room size",unit:"",min:.05,max:1,step:.01,default:.7},{kind:"number",key:"damping",label:"Damping",unit:"",min:0,max:1,step:.01,default:.5,hint:"Higher rolls the top off the tail faster."},{kind:"number",key:"wet",automatable:!0,label:"Wet",unit:"",min:0,max:1,step:.01,default:.35},{kind:"number",key:"dry",automatable:!0,label:"Dry",unit:"",min:0,max:1,step:.01,default:.7}],web:"convolver"}],L=new Map(z.map(e=>[e.id,e]));function P(e){return L.get(e)}var Ft=z.map(e=>e.id);function y(e,t){let n=L.get(e);if(!n)return{};let o={};for(let a of n.params){let r=t?.[a.key];if(a.kind==="enum"){let s=a.options.some(l=>l.value===r);o[a.key]=s?r:a.default;continue}let i=typeof r=="number"?r:typeof r=="string"&&r.trim()!==""?Number(r):Number.NaN;o[a.key]=Number.isFinite(i)?Math.min(a.max,Math.max(a.min,i)):a.default}return o}var x=class extends Error{constructor(t){super(t),this.name="AudioFxChainError"}};function J(e){let t;try{t=JSON.parse(e)}catch(a){throw new x(`Chain file is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new x("Chain file must be a JSON object.");let n=t;if(n.version!==W)throw new x(`Unsupported chain version: ${String(n.version)}`);if(!Array.isArray(n.nodes))throw new x("Chain file is missing a `nodes` array.");let o=n.nodes.map(ve);return{version:W,nodes:o}}function w(e){return e.nodes.filter(t=>t.enabled!==!1)}function ve(e,t){if(typeof e!="object"||e===null)throw new x(`Node ${t} is not an object.`);let n=e;if(typeof n.type!="string"||!L.has(n.type))throw new x(`Node ${t} has unknown effect type: ${String(n.type)}`);return Fe({type:n.type,id:E(n.id),fromCarve:Y(n.fromCarve),fromPreset:E(n.fromPreset),label:E(n.label),fromEq:E(n.fromEq),fromLeveller:Y(n.fromLeveller),presetAmount:Me(n.presetAmount),enabled:n.enabled!==!1,params:y(n.type,n.params??void 0)})}function E(e){return typeof e=="string"&&e?e:void 0}function Y(e){return e===!0?!0:void 0}function Me(e){if(!(typeof e!="number"||!Number.isFinite(e)))return Math.min(1,Math.max(0,e))}function Fe(e){for(let t of Object.keys(e))e[t]===void 0&&delete e[t];return e}function Te(e,t){return Math.max(1,Math.round(e*t/1e3))}function Q(e,t){return Te(e,t)+16}var Z=`\nconst TP_OS = 4;\nconst TP_HALF = 64;\nconst TP_BRANCH = 33;\nconst TP_DELAY = 16;\n\nfunction besselI0(x) {\n let sum = 1, term = 1;\n for (let k = 1; k < 40; k++) {\n term *= (x / (2 * k)) * (x / (2 * k));\n sum += term;\n }\n return sum;\n}\n\n/** Kaiser-windowed sinc, one array per polyphase branch, newest tap first. */\nfunction tpBranches() {\n const beta = 6;\n const norm = besselI0(beta);\n const branches = [];\n for (let p = 0; p < TP_OS; p++) {\n const taps = new Float64Array(TP_BRANCH);\n let sum = 0;\n for (let j = 0; j < TP_BRANCH; j++) {\n const k = p + TP_OS * j;\n if (k > 2 * TP_HALF) continue;\n const t = (k - TP_HALF) / TP_OS;\n const sinc = t === 0 ? 1 : Math.sin(Math.PI * t) / (Math.PI * t);\n const r = (k - TP_HALF) / TP_HALF;\n taps[j] = sinc * (besselI0(beta * Math.sqrt(Math.max(0, 1 - r * r))) / norm);\n sum += taps[j];\n }\n // Unity gain at DC on every branch, so a steady level reads as itself.\n for (let j = 0; j < TP_BRANCH; j++) taps[j] /= sum;\n branches.push(taps);\n }\n return branches;\n}\n\nclass HfTruePeak extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.look = Math.max(1, Math.round((this.p.lookahead ?? 3) * sampleRate / 1000));\n this.delayLen = this.look + TP_DELAY;\n this.branches = tpBranches();\n this.setLevels();\n this.clear();\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n if (e.data && e.data.__hfReset) { this.clear(); return; }\n this.p = { ...this.p, ...e.data };\n this.setLevels();\n };\n }\n /** Back to the state of a fresh processor: no audio in the delay line, no gain reduction. */\n clear() {\n this.ch = [];\n this.rel = 1;\n this.dqIdx = new Int32Array(this.look + 2);\n this.dqVal = new Float64Array(this.look + 2);\n this.dqHead = 0;\n this.dqTail = 0;\n this.t = 0;\n this.avgRing = new Float64Array(this.look + 1).fill(1);\n this.avgPos = 0;\n this.avgSum = this.look + 1;\n }\n setLevels() {\n this.ceiling = Math.pow(10, (this.p.ceiling ?? -1) / 20);\n this.relCoef = 1 - Math.exp(-1 / (sampleRate * Math.max(1e-4, (this.p.release ?? 80) / 1000)));\n }\n channel(c) {\n if (!this.ch[c]) {\n this.ch[c] = {\n delay: new Float32Array(this.delayLen),\n w: 0,\n hist: new Float32Array(TP_BRANCH * 2),\n pos: 0,\n };\n }\n return this.ch[c];\n }\n /** Largest interpolated magnitude across channels for the newest sample. */\n peak(chans, inputs, n) {\n let peak = 0;\n for (let c = 0; c < chans; c++) {\n const s = this.channel(c);\n const x = inputs[c][n];\n s.pos = (s.pos + 1) % TP_BRANCH;\n s.hist[s.pos] = x;\n s.hist[s.pos + TP_BRANCH] = x;\n const base = s.pos + TP_BRANCH;\n for (let p = 0; p < TP_OS; p++) {\n const taps = this.branches[p];\n let acc = 0;\n for (let j = 0; j < TP_BRANCH; j++) acc += taps[j] * s.hist[base - j];\n const a = acc < 0 ? -acc : acc;\n if (a > peak) peak = a;\n }\n }\n return peak;\n }\n /** Windowed minimum of the required gain over the last look + 1 values. */\n windowMin(v) {\n const cap = this.dqIdx.length;\n while (this.dqTail !== this.dqHead && this.dqVal[(this.dqTail + cap - 1) % cap] >= v) {\n this.dqTail = (this.dqTail + cap - 1) % cap;\n }\n this.dqIdx[this.dqTail] = this.t;\n this.dqVal[this.dqTail] = v;\n this.dqTail = (this.dqTail + 1) % cap;\n while (this.dqIdx[this.dqHead] <= this.t - (this.look + 1)) {\n this.dqHead = (this.dqHead + 1) % cap;\n }\n this.t++;\n return this.dqVal[this.dqHead];\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const chans = i.length;\n const frames = i[0].length;\n for (let n = 0; n < frames; n++) {\n const peak = this.peak(chans, i, n);\n const need = peak > this.ceiling ? this.ceiling / peak : 1;\n const m = this.windowMin(need);\n this.avgSum += m - this.avgRing[this.avgPos];\n this.avgRing[this.avgPos] = m;\n this.avgPos = (this.avgPos + 1) % this.avgRing.length;\n const target = Math.min(1, this.avgSum / this.avgRing.length);\n const released = this.rel + (1 - this.rel) * this.relCoef;\n this.rel = released < target ? released : target;\n if (target >= 1 && this.rel > 0.9999999) this.rel = 1;\n for (let c = 0; c < chans; c++) {\n const s = this.channel(c);\n o[c][n] = s.delay[s.w] * this.rel;\n s.delay[s.w] = i[c][n];\n s.w = (s.w + 1) % this.delayLen;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-truepeak", HfTruePeak);\n`;var Pe=`\nconst dbToLin = (db) => Math.pow(10, db / 20);\n\n/**\n * One-pole envelope followers, one per channel.\n *\n * Per channel matters: the followers advance once per sample, so a single shared\n * follower stepped once per channel per sample. On stereo that ran a 20 ms attack\n * as 10 ms, and gave the right channel a gain computed from an envelope that had\n * already traversed the left, so the two channels ducked by different amounts\n * from the same input and the image pumped.\n */\nclass EnvBank {\n constructor(attackMs, releaseMs) { this.set(attackMs, releaseMs); this.values = []; }\n set(attackMs, releaseMs) {\n this.a = Math.exp(-1 / (sampleRate * Math.max(1e-5, attackMs / 1000)));\n this.r = Math.exp(-1 / (sampleRate * Math.max(1e-5, releaseMs / 1000)));\n }\n push(ch, x) {\n const prev = this.values[ch] ?? 0;\n const m = Math.abs(x);\n const c = m > prev ? this.a : this.r;\n const next = m + c * (prev - m);\n this.values[ch] = next;\n return next;\n }\n}\n\n/** Soft-knee gain computer in dB, matching acompressor\'s shape. */\nfunction kneeGain(envDb, thresholdDb, ratio, kneeDb) {\n const over = envDb - thresholdDb;\n if (kneeDb > 0 && over > -kneeDb && over < kneeDb) {\n const t = (over + kneeDb) / (2 * kneeDb);\n return -((1 - 1 / ratio) * kneeDb * t * t);\n }\n return over > 0 ? -(over * (1 - 1 / ratio)) : 0;\n}\n\n// log10/exp per sample is the single most expensive thing a dynamics processor\n// can do on the audio thread, and every sample below the knee needs neither:\n// its gain is exactly unity. Comparing envelopes in the linear domain lets the\n// quiet majority of samples skip the transcendentals entirely.\nconst LN10_OVER_20 = Math.LN10 / 20;\nconst dbToLinFast = (db) => Math.exp(db * LN10_OVER_20);\n\nclass HfCompressor extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 20, this.p.release ?? 250);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 20, this.p.release ?? 250);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const makeup = dbToLin(p.makeup ?? 0);\n const mix = p.mix ?? 1;\n // Knee is expressed as a ratio in FFmpeg; convert to dB width.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const thresholdDb = p.threshold ?? -24;\n const ratio = p.ratio ?? 4;\n // Below this the gain computer returns unity, so the sample needs no\n // logarithm at all.\n const kneeStartLin = dbToLin(thresholdDb - kneeDb);\n const dry = 1 - mix;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n // Per-channel follower, and the sub-knee shortcut: below the knee the\n // gain is exactly unity, so the sample needs no logarithm at all.\n const env = this.env.push(ch, x);\n if (env <= kneeStartLin) {\n out[n] = x * makeup * mix + x * dry;\n continue;\n }\n const envDb = 20 * Math.log10(env);\n const g = dbToLinFast(kneeGain(envDb, thresholdDb, ratio, kneeDb));\n out[n] = x * g * makeup * mix + x * dry;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-compressor", HfCompressor);\n\nclass HfLimiter extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 5, this.p.release ?? 50);\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 5, this.p.release ?? 50);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const ceiling = dbToLin(this.p.limit ?? -1);\n const outGain = dbToLin(this.p.level_out ?? 0);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n // Only ever attenuate: a limiter that can raise level is a compressor.\n const g = env > ceiling ? ceiling / env : 1;\n out[n] = x * g * outGain;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-limiter", HfLimiter);\n\nclass HfGate extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.env = new EnvBank(this.p.attack ?? 1, this.p.release ?? 100);\n this.gains = [];\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n this.env.set(this.p.attack ?? 1, this.p.release ?? 100);\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const threshold = dbToLin(p.threshold ?? -35);\n const floor = dbToLin(p.range ?? -24);\n const ratio = p.ratio ?? 10;\n // Knee is declared in the registry as a ratio, like the compressor\'s; a\n // hard threshold ignored it and chattered on material sitting right at the\n // gate point.\n const kneeDb = 20 * Math.log10(Math.max(1.0001, p.knee ?? 2.83));\n const kneeLin = dbToLin((p.threshold ?? -35) + kneeDb);\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n for (let n = 0; n < inp.length; n++) {\n const x = inp[n];\n const env = this.env.push(ch, x);\n let target = 1;\n // Above the threshold the gate is fully open; skip the pow entirely.\n if (env < threshold) {\n const under = env > 1e-9 ? threshold / env : 1e9;\n target = Math.max(floor, 1 / Math.pow(under, ratio - 1));\n if (!isFinite(target)) target = floor;\n }\n // Smooth toward the target so the gate does not click on every sample.\n const held = this.gains[ch] ?? 1;\n const c = target < held ? this.env.r : this.env.a;\n const smoothed = target + c * (held - target);\n this.gains[ch] = smoothed;\n out[n] = x * smoothed;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-gate", HfGate);\n\nclass HfBitcrush extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.holds = [];\n this.held = [];\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const levels = Math.pow(2, (p.bits ?? 8) - 1);\n const step = Math.max(1, Math.round(p.samples ?? 1));\n const mix = p.mix ?? 1;\n for (let ch = 0; ch < i.length; ch++) {\n const inp = i[ch], out = o[ch];\n if (this.held[ch] === undefined) this.held[ch] = 0;\n if (this.holds[ch] === undefined) this.holds[ch] = 0;\n for (let n = 0; n < inp.length; n++) {\n // Per channel: advancing one shared counter only on the last channel\n // left every earlier channel either unheld or frozen for a whole\n // 128-sample quantum, clicking once a block.\n if (this.holds[ch] === 0) this.held[ch] = Math.round(inp[n] * levels) / levels;\n out[n] = this.held[ch] * mix + inp[n] * (1 - mix);\n this.holds[ch] = (this.holds[ch] + 1) % step;\n }\n }\n return true;\n }\n}\nregisterProcessor("hf-bitcrush", HfBitcrush);\n\n/** Linear-interpolated read, \\`delaySamples\\` behind the write head. */\nfunction readTap(ring, write, delaySamples) {\n const len = ring.length;\n const pos = (write - 1 - delaySamples + len) % len;\n const i0 = Math.floor(pos);\n const frac = pos - i0;\n const i1 = (i0 + 1) % len;\n return ring[i0] * (1 - frac) + ring[i1] * frac;\n}\n\n/** Equal-power-ish crossfade, zero at a tap\'s reset point, which hides the splice. */\nfunction xfade(phase) {\n return Math.sin(Math.PI * phase);\n}\n\n/**\n * Dual-tap granular delay line: two read taps 180\\xB0 apart in a 100 ms grain,\n * each sweeping at a speed relative to the write head that shifts pitch\n * without changing duration. One tap is always fading in as the other fades\n * out, which hides the splice each tap makes when it wraps.\n *\n * write/phase are block-level state, advanced once per SAMPLE across all\n * channels together (not once per channel). Advancing them inside the\n * per-channel loop would move the tap 2x/4x too fast on a stereo/quad input.\n */\nclass HfPitchshift extends AudioWorkletProcessor {\n constructor(o) {\n super();\n this.p = o.processorOptions || {};\n this.grain = Math.round(sampleRate * 0.1);\n this.buf = [];\n this.write = 0;\n this.phase = 0;\n // Samples written so far, capped at one grain. The taps read up to a grain\n // behind the write head, so until this fills they would read the ring\'s\n // zeros, so the head of every clip came out attenuated or silent.\n this.filled = 0;\n // How much of the wet (pitch-shifted) path is currently in the output, and\n // where it is heading. Crossing between dry and wet is a ~50 ms jump in the\n // signal, so it is RAMPED rather than switched: a hard swap either way is a\n // click. Ramping in both directions is also what lets a node return to true\n // bypass at semitones 0. A one-way latch left preview stuck with the delay\n // that the render, building a fresh node from the attribute, does not have.\n this.wet = 0;\n this.wetTarget = 0;\n // ~15 ms one-pole, short enough to feel immediate on a slider drag and long\n // enough that the splice is inaudible.\n this.wetCoef = Math.exp(-1 / (sampleRate * 0.015));\n this.port.onmessage = (e) => {\n if (e.data && e.data.__hfDispose) { this.dead = true; return; }\n this.p = { ...this.p, ...e.data };\n };\n }\n process(inputs, outputs) {\n if (this.dead) return false;\n const i = inputs[0], o = outputs[0];\n if (!i || !i.length) return true;\n const p = this.p;\n const semitones = Math.max(-12, Math.min(12, p.semitones ?? 0));\n const mix = Math.max(0, Math.min(1, p.mix ?? 1));\n const grain = this.grain;\n const ringLen = grain * 2;\n const n = i[0] ? i[0].length : 0;\n for (let ch = 0; ch < i.length; ch++) {\n if (!this.buf[ch]) this.buf[ch] = new Float32Array(ringLen);\n }\n\n // Nothing to shift, or mixed fully out. The grain delay is ~grain/2\n // whatever the ratio, so at semitones=0 this degenerated into a pure 50 ms\n // delay of the signal, while the copy for that exact setting reads\n // "Unchanged pitch".\n this.wetTarget = semitones === 0 ? 0 : mix;\n\n // Fully dry AND settled: take the cheap transparent path. The ring keeps\n // filling, so a later shift does not start cold.\n if (this.wetTarget === 0 && this.wet < 1e-4) {\n this.wet = 0;\n let w = this.write;\n for (let s = 0; s < n; s++) {\n for (let ch = 0; ch < i.length; ch++) {\n const x = i[ch][s];\n this.buf[ch][w] = x;\n o[ch][s] = x;\n }\n w = (w + 1) % ringLen;\n }\n this.write = w;\n this.filled = Math.min(grain, this.filled + n);\n return true;\n }\n\n const ratio = Math.pow(2, semitones / 12);\n const inc = (1 - ratio) / grain;\n let write = this.write, phase = this.phase, filled = this.filled, wetNow = this.wet;\n const target = this.wetTarget, coef = this.wetCoef;\n for (let s = 0; s < n; s++) {\n phase += inc;\n phase -= Math.floor(phase);\n const phaseB = (phase + 0.5) % 1;\n const gA = xfade(phase), gB = xfade(phaseB);\n // Ramp the wet path in as the ring fills rather than reading zeros:\n // 100 ms of unshifted audio at the head of a clip beats 50 ms of silence.\n const warm = filled >= grain ? 1 : filled / grain;\n wetNow = target + coef * (wetNow - target);\n const wetMix = wetNow * warm;\n for (let ch = 0; ch < i.length; ch++) {\n const ring = this.buf[ch];\n const inp = i[ch], out = o[ch];\n const x = inp[s];\n ring[write] = x;\n const wet =\n readTap(ring, write, phase * grain) * gA + readTap(ring, write, phaseB * grain) * gB;\n out[s] = x * (1 - wetMix) + wet * wetMix;\n }\n write = (write + 1) % ringLen;\n if (filled < grain) filled++;\n }\n this.write = write;\n this.phase = phase;\n this.filled = filled;\n this.wet = wetNow;\n return true;\n }\n}\nregisterProcessor("hf-pitchshift", HfPitchshift);\n`+Z,S=new WeakMap;var He=new WeakSet;function O(e){let t=S.get(e);return t||(t=(async()=>{if(!e.audioWorklet)throw new Error("AudioWorklet is unavailable: the page needs a secure context (https, localhost or file://)");let n=`data:text/javascript;base64,${btoa(String.fromCharCode(...new TextEncoder().encode(Pe)))}`;await e.audioWorklet.addModule(n),He.add(e)})().catch(n=>{throw S.delete(e),n}),S.set(e,t)),t}function _e(e,t,n){let o=.6+Math.max(0,Math.min(1,t))*2.6,a=Math.max(1,Math.floor(e*o)),r=new Float32Array(a),i=Math.round(t*1e3)*2654435761+Math.round(n*1e3)*40503>>>0,s=()=>(i=i*1664525+1013904223>>>0,i/4294967295*2-1),l=Math.max(.001,1-Math.max(0,Math.min(1,n))),u=0,d=0;for(let c=0;c<a;c++){u+=l*(s()-u);let b=u*Math.pow(1-c/a,2.5);r[c]=b,d+=b*b}let m=Math.sqrt(d);if(m>0)for(let c=0;c<a;c++)r[c]=(r[c]??0)/m;return r}var f=e=>typeof e=="number"?e:Number(e??0),B=e=>e/1e3;function ae(e,t,n,o){let a=Math.max(1,Math.round(e.sampleRate)),r=e.createBuffer(1,a,e.sampleRate),i=r.getChannelData(0);for(let u=0;u<a;u++){let d=u/a;i[u]=t==="sine"?Math.sin(2*Math.PI*d):4*Math.abs((d+.75)%1-.5)-1}let s=e.createBufferSource();s.buffer=r,s.loop=!0,s.playbackRate.value=n;let l=(o*n%1+1)%1*(a/e.sampleRate);return s.start(typeof e.currentTime=="number"?e.currentTime:0,l),s}function oe(e){try{e.stop()}catch{}e.disconnect()}function I(e,t){return[{param:e},{param:t,map:n=>1-n}]}function re(e,t,n){e.gain.value=n,t.gain.value=1-n}function q(e,t,n){return{input:e,output:e,update:t,automation:n,dispose:()=>e.disconnect()}}var Ne=new Set(["peaking","highpass","lowpass"]),ee=e=>Math.pow(10,e/20),Re=(e,t)=>{let n=e.createGain(),o=a=>{n.gain.value=ee(f(a.gain))};return o(t),q(n,o,{gain:[{param:n.gain,map:ee}]})};function H(e,t){return(n,o)=>{let a=n.createBiquadFilter();a.type=e;let r=i=>{a.frequency.value=f(i.frequency),i.q!==void 0&&(a.Q.value=f(i.q)),t&&(a.gain.value=f(i.gain))};return r(o),q(a,r,{frequency:[{param:a.frequency}],...Ne.has(e)?{q:[{param:a.Q}]}:{},...t?{gain:[{param:a.gain}]}:{}})}}function Ee(e){return(t,n)=>{let o=Math.tan(Math.PI*f(n.frequency)/t.sampleRate),a=e==="highpass"?t.createIIRFilter([1/(1+o),-1/(1+o)],[1,(o-1)/(o+1)]):t.createIIRFilter([o/(1+o),o/(1+o)],[1,(o-1)/(o+1)]);return q(a,()=>{})}}function M(e,t=!1){return(n,o)=>{let a=new AudioWorkletNode(n,e,{processorOptions:{...o}});return{input:a,output:a,update:r=>a.port.postMessage({...r}),...t?{reset:()=>a.port.postMessage({__hfReset:!0})}:{},dispose:()=>{a.port.postMessage({__hfDispose:!0}),a.disconnect()}}}}var De={tanh:Math.tanh,atan:e=>2/Math.PI*Math.atan(Math.PI/2*e),cubic:e=>Math.abs(e)>=1?Math.sign(e):e-e**3/3,exp:e=>Math.sign(e)*(1-Math.exp(-Math.abs(e))),alg:e=>e/Math.sqrt(1+e*e),quintic:e=>Math.abs(e)>=1?Math.sign(e):e-e**5/5,sin:e=>Math.abs(e)>=1?Math.sign(e):Math.sin(Math.PI/2*e),erf:e=>Math.tanh(1.20211*e),hard:e=>Math.max(-1,Math.min(1,e))},Ce=(e,t)=>{let n=e.createWaveShaper(),o=e.createGain(),a=e.createGain();o.connect(n).connect(a);let r=i=>{let s=De[String(i.type)]??Math.tanh,l=Math.pow(10,f(i.threshold)/20),u=8192,d=new Float32Array(u);for(let m=0;m<u;m++){let c=m/(u-1)*2-1;d[m]=s(c/Math.max(1e-6,l))*l}n.curve=d,n.oversample=f(i.oversample)>=4?"4x":f(i.oversample)>=2?"2x":"none",a.gain.value=Math.pow(10,f(i.output)/20)};return r(t),{input:o,output:a,update:r,automation:{output:[{param:a.gain,map:i=>Math.pow(10,i/20)}]},dispose:()=>{o.disconnect(),n.disconnect(),a.disconnect()}}},Le=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createDelay(5),r=e.createGain(),i=e.createGain(),s=e.createGain();n.connect(a),a.connect(r),r.connect(a),a.connect(i).connect(o),n.connect(s).connect(o);let l=u=>{a.delayTime.value=Math.min(5,f(u.time)/1e3),r.gain.value=f(u.feedback),re(i,s,f(u.mix))};return l(t),{input:n,output:o,update:l,automation:{time:[{param:a.delayTime,map:u=>Math.min(5,B(u))}],feedback:[{param:r.gain}],mix:I(i.gain,s.gain)},dispose:()=>[n,o,a,r,i,s].forEach(u=>u.disconnect())}},Se=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createDelay(.5),i=ae(e,"sine",f(t.speed),n),s=e.createGain(),l=e.createGain(),u=e.createGain();i.connect(s).connect(r.delayTime),o.connect(r).connect(l).connect(a),o.connect(u).connect(a);let d=m=>{r.delayTime.value=f(m.delay)/1e3,s.gain.value=f(m.depth)/1e3,i.playbackRate.value=f(m.speed),re(l,u,f(m.mix))};return d(t),{input:o,output:a,update:d,automation:{delay:[{param:r.delayTime,map:B}],depth:[{param:s.gain,map:B}],speed:[{param:i.playbackRate}],mix:I(l.gain,u.gain)},dispose:()=>{oe(i),[o,a,r,s,l,u].forEach(m=>m.disconnect())}}},Oe=6,Be=(e,t,n)=>{let o=e.createGain(),a=e.createGain(),r=e.createGain(),i=e.createGain(),s=ae(e,String(t.type)==="1"?"sine":"triangle",f(t.speed),n),l=e.createGain(),u=e.createGain(),d=e.createGain(),m=[];o.connect(r);let c=r;for(let p=0;p<Oe;p++){let h=e.createBiquadFilter();h.type="allpass",h.Q.value=.7071,l.connect(h.frequency),c.connect(h),c=h,m.push(h)}s.connect(l),c.connect(u).connect(i),r.connect(d).connect(i),i.connect(a);let b=p=>{let h=1e3/Math.max(.1,f(p.delay));for(let g of m)g.frequency.value=h;l.gain.value=h*f(p.decay),s.playbackRate.value=f(p.speed),r.gain.value=f(p.in_gain),i.gain.value=f(p.out_gain),u.gain.value=1,d.gain.value=1};return b(t),{input:o,output:a,update:b,automation:{speed:[{param:s.playbackRate}],in_gain:[{param:r.gain}],out_gain:[{param:i.gain}]},dispose:()=>{oe(s),[o,a,r,i,l,u,d,...m].forEach(p=>p.disconnect())}}},Ie=(e,t)=>{let n=e.createGain(),o=e.createGain(),a=e.createConvolver(),r=e.createGain(),i=e.createGain();a.normalize=!1,n.connect(a).connect(r).connect(o),n.connect(i).connect(o);let s="",l=u=>{let d=`${f(u.size)}:${f(u.damping)}`;if(d!==s){let m=_e(e.sampleRate,f(u.size),f(u.damping)),c=e.createBuffer(1,m.length,e.sampleRate);c.getChannelData(0).set(m),a.buffer=c,s=d}r.gain.value=f(u.wet),i.gain.value=f(u.dry)};return l(t),{input:n,output:o,update:l,automation:{wet:[{param:r.gain}],dry:[{param:i.gain}]},dispose:()=>[n,o,a,r,i].forEach(u=>u.disconnect())}},qe={"gain-node":Re,"biquad-peaking":H("peaking",!0),"biquad-lowshelf":H("lowshelf",!0),"biquad-highshelf":H("highshelf",!0),"biquad-highpass":H("highpass",!1),"biquad-lowpass":H("lowpass",!1),"worklet-compressor":M("hf-compressor"),"worklet-limiter":M("hf-limiter"),"worklet-truepeak":M("hf-truepeak",!0),"worklet-gate":M("hf-gate"),"worklet-bitcrush":M("hf-bitcrush"),"worklet-pitchshift":M("hf-pitchshift"),waveshaper:Ce,"delay-feedback":Le,"chorus-lfo":Se,"allpass-phaser":Be,convolver:Ie};function ie(e){return e.nodes.some(t=>P(t.type)?.web.startsWith("worklet-")??!1)}function Ge(e,t,n,o=0){let a=P(t);if(!a)throw new Error(`Unknown effect type: ${t}`);let r=y(t,n);if((t==="highpass"||t==="lowpass")&&String(r.poles)==="1")return Ee(t)(e,r,o);let i=qe[a.web];if(!i)throw new Error(`No Web Audio builder for ${a.web}`);return i(e,r,o)}function te(e){let t=[];for(let n of e){let o=n.fromPreset,a=t.at(-1);if(a&&a.preset===o)a.nodes.push(n);else{let r=typeof n.presetAmount=="number"?n.presetAmount:1;t.push({...o?{preset:o}:{},amount:Math.min(1,Math.max(0,r)),nodes:[n]})}}return t}function ne(e){return w(e).map(t=>{let n=y(t.type,t.params),o=n.poles!==void 0?`:${n.poles}`:"",a=String(n.poles)==="1"?`@${n.frequency}`:"",r=t.type==="phaser"?`~${n.type}`:"",i=t.fromPreset?`%${t.fromPreset}`:"",s=t.type==="truepeak"?`^${n.lookahead}`:"";return`${t.type}${o}${a}${r}${s}${i}`}).join("|")}function se(e,t,n=0){var m;let o=e.createGain(),a=e.createGain(),r=[],i=[],s=te(w(t)),l=o;for(let c of s){let b=null;if(c.preset){let p=e.createGain(),h=e.createGain(),g=e.createGain(),F=e.createGain();g.gain.value=c.amount,h.gain.value=1-c.amount,l.connect(p),p.connect(h).connect(F),b={entry:p,wet:g,dry:h,join:F},l=p}for(let p of c.nodes){let h=Ge(e,p.type,p.params??{},n);l.connect(h.input),l=h.output,r.push({...p.id?{id:p.id}:{},type:p.type,handle:h})}b&&c.preset&&(l.connect(b.wet).connect(b.join),i.push({id:c.preset,...b}),l=b.join)}l.connect(a);let u=ne(t),d={};for(let c of i)(d[m=c.id]??(d[m]=[])).push(...I(c.wet.gain,c.dry.gain));return{input:o,output:a,presets:d,nodes:r,update(c){if(ne(c)!==u)return!1;w(c).forEach((p,h)=>{let g=r[h];g&&(g.handle.update(y(p.type,p.params)),p.id===void 0?delete g.id:g.id=p.id)});let b=0;for(let p of te(w(c))){if(!p.preset)continue;let h=i[b++];!h||h.id!==p.preset||(h.wet.gain.value=p.amount,h.dry.gain.value=1-p.amount)}return!0},reset(){for(let{handle:c}of r)c.reset?.()},dispose(){for(let{handle:c}of r)c.dispose();for(let{entry:c,wet:b,dry:p,join:h}of i)c.disconnect(),b.disconnect(),p.disconnect(),h.disconnect();o.disconnect(),a.disconnect()}}}function Ue(e){return 10**(e/20)}var ue=Ue(12);var G=.1,U=10;var Ve="data-automation",Gt=Ve.slice(5),D=1,$e=512,A=class extends Error{constructor(t){super(t),this.name="AudioAutomationError"}},V="volume",le="rate";function N(e){if(e===V)return{kind:"volume"};if(e===le)return{kind:"rate"};let t=e.split(".");if(t.length===3&&t[0]==="fx"&&t[1]===je){let a=t[2];return a?{kind:"preset",presetId:a}:null}if(t.length!==3||t[0]!=="fx")return null;let[,n,o]=t;return!n||!o?null:{kind:"fx",nodeId:n,param:o}}function ce(e,t){return`fx.${e}.${t}`}var je="preset";var Xe={min:0,max:1,step:.01,unit:"",label:"Amount",scale:"linear",default:1},de={min:0,max:ue,step:.01,unit:"",label:"Volume",scale:"linear",default:1},me={min:G,max:U,step:.05,unit:"x",label:"Speed",scale:"log",default:1};function $(e,t){let n=N(e);if(!n)return null;if(n.kind==="volume")return de;if(n.kind==="rate")return me;if(n.kind==="preset")return t?.nodes.some(s=>s.fromPreset===n.presetId)?{...Xe,label:`${n.presetId} \\xB7 Amount`}:null;let o=t?.nodes.find(i=>i.id===n.nodeId);if(!o)return null;let a=P(o.type),r=a?.params.find(i=>i.key===n.param);return!r||r.kind!=="number"?null:{min:r.min,max:r.max,step:r.step,unit:r.unit,label:`${a?.label??o.type} \\xB7 ${r.label}`,scale:r.scale==="log"&&r.min>0?"log":"linear",default:r.default}}function _(e){if(typeof e=="number")return Number.isFinite(e)?e:null;if(typeof e=="string"&&e.trim()!==""){let t=Number(e);return Number.isFinite(t)?t:null}return null}function We(e){let t=_(e);return t===null||t===0?0:Math.min(1,Math.max(-1,t))}function pe(e,t){let n=_(e),o=_(t);if(n===null||o===null)return null;let a=s=>Math.min(.999,Math.max(.001,s)),r=a(n),i=a(o);return Math.abs(r-i)<1e-6?null:{x:r,y:i}}function Ke(e){let t=We(e?.curve),n=pe(e?.viaX,e?.viaY);return{...t?{curve:t}:{},...n?{viaX:n.x,viaY:n.y}:{}}}function Ye(e,t){let n=_(e?.t),o=_(e?.v);if(n===null||o===null)return null;let a=t?Math.min(t.max,Math.max(t.min,o)):o;return{t:Math.max(0,n),v:a,...Ke(e)}}function he(e,t){let n=e.map(a=>Ye(a,t)).filter(a=>a!==null).sort((a,r)=>a.t-r.t),o=[];for(let a of n)o.length>0&&o[o.length-1].t===a.t?o[o.length-1]=a:o.push(a);return o.slice(0,$e)}function ze(e){let t=[];for(let n of e.lanes){if(!N(n.target))continue;let o=n.target===V?de:n.target===le?me:null,a=he(n.points??[],o);a.length>0&&t.push({target:n.target,points:a})}return{version:D,lanes:t}}function fe(e,t){let n=[];for(let o of e.lanes){let a=$(o.target,t);if(!a)continue;let r=he(o.points,a);r.length>0&&n.push({target:o.target,points:r})}return{version:D,lanes:n}}function be(e){let t;try{t=JSON.parse(e)}catch(a){throw new A(`Automation is not valid JSON: ${a.message}`)}if(typeof t!="object"||t===null)throw new A("Automation must be a JSON object.");let n=t;if(n.version!==D)throw new A(`Unsupported automation version: ${String(n.version)}`);if(!Array.isArray(n.lanes))throw new A("Automation is missing a `lanes` array.");let o=n.lanes.map((a,r)=>{if(typeof a!="object"||a===null)throw new A(`Lane ${r} is not an object.`);let i=a;if(typeof i.target!="string"||!N(i.target))throw new A(`Lane ${r} has an unreadable target: ${String(i.target)}`);if(!Array.isArray(i.points))throw new A(`Lane ${r} is missing a \\`points\\` array.`);return{target:i.target,points:i.points}});return ze({version:D,lanes:o})}function Je(e,t){return t?Math.pow(e,Math.pow(2,2*t)):e}function Qe(e,t){let n=e-.5,o=t-.5,a=.999,r=1e6,i=n>0?n/(a-e):n<0?-n/(e-(1-a)):0,s=o>0?o/(a-t):o<0?-o/(t-(1-a)):0,l=Math.min(r,Math.max(1,i,s));return{cx:e+n/l,cy:t+o/l,w:l}}function Ze(e,t,n){let{cx:o,cy:a,w:r}=Qe(t,n),i=2-2*r,s=e*i-1+2*r*o,l=-e*i-2*r*o,u=et(s,l,e),d=1-u,m=d*d+2*r*u*d+u*u;return m>0?(2*r*a*u*d+u*u)/m:e}function et(e,t,n){if(Math.abs(e)<1e-12)return Math.abs(t)<1e-12?n:-n/t;let o=Math.sqrt(Math.max(0,t*t-4*e*n)),a=(-t+o)/(2*e),r=(-t-o)/(2*e),i=s=>s>=-1e-9&&s<=1+1e-9;return i(a)?Math.min(1,Math.max(0,a)):i(r)?Math.min(1,Math.max(0,r)):n}function tt(e,t){let n=pe(t.viaX,t.viaY);return n?Ze(Math.min(1,Math.max(0,e)),n.x,n.y):Je(e,t.curve)}function nt(e,t,n,o){return o==="log"&&e>0&&t>0?Math.exp(Math.log(e)+(Math.log(t)-Math.log(e))*n):e+(t-e)*n}function j(e,t,n="linear"){let o=e.points;if(o.length===0)return 0;let a=o[0];if(t<=a.t)return a.v;let r=o[o.length-1];if(t>=r.t)return r.v;let i=0,s=o.length-1;for(;s-i>1;){let m=i+s>>1;o[m].t<=t?i=m:s=m}let l=o[i],u=o[s],d=u.t-l.t;return d<=0?u.v:nt(l.v,u.v,tt((t-l.t)/d,l),n)}function ge(e){return e.points.length<=1||e.points.every(t=>t.v===e.points[0].v)}function Ae(e,t,n,o,a="linear"){let r=Math.max(2,Math.floor(o)),i=new Float32Array(r),s=n-t;for(let l=0;l<r;l+=1)i[l]=j(e,t+s*l/(r-1),a);return i}var at=100,ot=8,rt=4096,X=e=>e;function it(e,t,n){return!(!!e.curve||e.viaX!==void 0&&e.viaY!==void 0)&&t==="linear"&&!n}function st(e){let t=Math.ceil(e*at);return Math.min(rt,Math.max(ot,t))}function ut(e,t,n,o,a){let{scheduledAt:r,elapsed:i,rate:s}=o,l=a??X,u=v=>r+(v-i)/s,d=e.points[t],m=e.points[t+1],c=u(m.t);if(c<=r)return null;let b=Math.max(r,u(d.t)),p=c-b;if(p<=0)return null;if(it(d,n,a))return{kind:"ramp",time:c,value:l(m.v)};let h=st(p),g=Ae(e,Math.max(d.t,i),m.t,h,n),F=new Float32Array(h);for(let v=0;v<h;v+=1)F[v]=l(g[v]??0);return{kind:"curve",time:b,duration:p,values:F}}function lt(e,t,n,o){let a=[];for(let i=0;i+1<e.points.length;i+=1){let s=ut(e,i,t,n,o);s&&a.push(s)}let r=a[0];return r?.kind==="curve"&&r.time<=n.scheduledAt||a.unshift({kind:"set",time:n.scheduledAt,value:(o??X)(j(e,n.elapsed,t))}),a}function ct(e,t){for(let n of t)if(n.kind==="set")e.setValueAtTime(n.value,n.time);else if(n.kind==="ramp")e.linearRampToValueAtTime(n.value,n.time);else try{e.setValueCurveAtTime(n.values,n.time,n.duration)}catch{let o=n.values[n.values.length-1]??0;e.linearRampToValueAtTime(o,n.time+n.duration)}}function dt(e){for(let t of e)t.param.cancelScheduledValues(0)}function mt(e,t,n,o){if(t.points.length!==0)for(let a of e){if(dt([a]),ge(t)){let r=(a.map??X)(t.points[0].v);a.param.setValueAtTime(r,o.scheduledAt);continue}ct(a.param,lt(t,n,o,a.map))}}function xe(e,t,n,o,a){let r=new Map(n.filter(s=>s.id).map(s=>[s.id,s.handle])),i=[];for(let s of e.lanes){let l=N(s.target);if(!l)continue;let u=l.kind==="preset"?a?.[l.presetId]:l.kind==="fx"?r.get(l.nodeId)?.automation?.[l.param]:void 0;if(!u||u.length===0)continue;let d=$(s.target,t);d&&(mt(u,s,d.scale,o),i.push(...u))}return i}var pt=5,ht=.001;function k(e,t,n){let o=Number(y(e.type,e.params)[t]??0);if(!e.id||!n)return Number.isFinite(o)?o:0;let a=n.lanes.find(r=>r.target===ce(e.id??"",t));return!a||a.points.length===0?Number.isFinite(o)?o:0:a.points.reduce((r,i)=>Math.max(r,i.v),-1/0)}function ft(e,t){let n=Math.min(5,e/1e3);if(n<=0)return 0;let o=Math.max(0,Math.min(.999,t));return o<=0?n:Math.ceil(Math.log(ht)/Math.log(o))*n}function bt(e,t){return k(e,"wet",t)>0?.6+Math.max(0,Math.min(1,k(e,"size",t)))*2.6:0}function gt(e,t){return k(e,"mix",t)>0?ft(k(e,"time",t),k(e,"feedback",t)):0}function At(e,t){return k(e,"mix",t)>0?k(e,"delay",t)/1e3:0}function xt(e,t){return k(e,"mix",t)>0?.2:0}function yt(e,t){if(e.enabled===!1)return 0;switch(e.type){case"reverb":return bt(e,t);case"delay":return gt(e,t);case"chorus":return At(e,t);case"pitchshift":return xt(e,t);default:return 0}}function ye(e,t){let n=e.nodes.reduce((o,a)=>o+yt(a,t),0);return Math.min(pt,n)}function ke(e,t){return w(e).filter(n=>n.type==="truepeak").reduce((n,o)=>{let{lookahead:a}=y(o.type,o.params);return n+Q(Number(a),t)},0)}function kt(e,t,n,o,a){let r=e.createBuffer(n,o,a);for(let i=0;i<n;i++){let s=t[i];s&&r.getChannelData(i).set(s)}return r}function wt(e,t){let n=[];for(let o=0;o<e.numberOfChannels;o++)n.push(new Float32Array(e.getChannelData(o).subarray(t)));return n}async function vt(e,t,n,o){let a=J(n),r=Math.max(1,e.length),i=e[0]?.length??0;if(i===0)return e;let s=o?fe(be(o),a):null,l=Math.ceil(ye(a,s??void 0)*t),u=ke(a,t),d=new OfflineAudioContext(r,i+l+u,t);ie(a)&&await O(d);let m=d.createBufferSource();m.buffer=kt(d,e,r,i,t);let c=se(d,a);return s&&xe(s,a,c.nodes,{scheduledAt:0,elapsed:0,rate:1},c.presets),m.connect(c.input),c.output.connect(d.destination),m.start(),wt(await d.startRendering(),u)}window.__HF_AUDIO_FX={render:vt};})();\n';
|
|
13724
13982
|
function getAudioFxRuntimeScript() {
|
|
13725
13983
|
return AUDIO_FX_RUNTIME_IIFE;
|
|
13726
13984
|
}
|
|
@@ -15156,7 +15414,7 @@ function expectedFramesForTask(task) {
|
|
|
15156
15414
|
return Math.max(0, Math.ceil((task.endFrame - task.startFrame) / stride));
|
|
15157
15415
|
}
|
|
15158
15416
|
function synthesizeSilentWorkerExitError(result, expectedFrames) {
|
|
15159
|
-
return `worker ${result.workerId} exited without terminal error string (framesCaptured=${result.framesCaptured}, expected=${expectedFrames}, range=[${result.startFrame}, ${result.endFrame})). Field signal ts=1784042064
|
|
15417
|
+
return `worker ${result.workerId} exited without terminal error string (framesCaptured=${result.framesCaptured}, expected=${expectedFrames}, range=[${result.startFrame}, ${result.endFrame})). Field signal ts=1784042064: this class of failure has been reported; consider re-run with --workers=1 to isolate.`;
|
|
15160
15418
|
}
|
|
15161
15419
|
function flagSilentWorkerExits(results) {
|
|
15162
15420
|
for (const r of results) {
|
|
@@ -15453,7 +15711,7 @@ async function psnrForDiskSample(framePath, truth, workerId, idx) {
|
|
|
15453
15711
|
if (isFfmpegInfrastructureFailure(err)) {
|
|
15454
15712
|
const detail = err instanceof Error ? err.message : String(err);
|
|
15455
15713
|
throw new Error(
|
|
15456
|
-
`[Parallel] drawElement disk self-verify aborted (worker ${workerId}, frame ${idx}): ffmpeg or the \`psnr\` filter is unavailable
|
|
15714
|
+
`[Parallel] drawElement disk self-verify aborted (worker ${workerId}, frame ${idx}): ffmpeg or the \`psnr\` filter is unavailable (${detail}). The preflight in initDrawElementOrTransparentBackground normally catches this at bootstrap; if you hit this after a successful preflight, ffmpeg was replaced mid-render or HYPERFRAMES_FFMPEG_PATH now points at a different binary.`,
|
|
15457
15715
|
{ cause: err }
|
|
15458
15716
|
);
|
|
15459
15717
|
}
|
|
@@ -15861,7 +16119,7 @@ var DEFAULT_BLACK_ONLY_FRACTION_THRESHOLD = 1e-3;
|
|
|
15861
16119
|
function diffGpuParityFrames(a, b, options = {}) {
|
|
15862
16120
|
if (a.width !== b.width || a.height !== b.height) {
|
|
15863
16121
|
throw new Error(
|
|
15864
|
-
`diffGpuParityFrames: frame size mismatch
|
|
16122
|
+
`diffGpuParityFrames: frame size mismatch: A is ${a.width}x${a.height}, B is ${b.width}x${b.height}`
|
|
15865
16123
|
);
|
|
15866
16124
|
}
|
|
15867
16125
|
const expectedLen = a.width * a.height * 4;
|
|
@@ -15880,7 +16138,7 @@ function diffGpuParityFrames(a, b, options = {}) {
|
|
|
15880
16138
|
const contentSum = options.contentSumThreshold ?? DEFAULT_CONTENT_SUM_THRESHOLD;
|
|
15881
16139
|
if (contentSum <= blackSum) {
|
|
15882
16140
|
throw new Error(
|
|
15883
|
-
`diffGpuParityFrames: contentSumThreshold (${contentSum}) must be strictly greater than blackSumThreshold (${blackSum})
|
|
16141
|
+
`diffGpuParityFrames: contentSumThreshold (${contentSum}) must be strictly greater than blackSumThreshold (${blackSum}): overlapping thresholds would double-classify borderline pixels`
|
|
15884
16142
|
);
|
|
15885
16143
|
}
|
|
15886
16144
|
const total = a.width * a.height;
|
|
@@ -15967,7 +16225,7 @@ function verifyGpuParity(a, b, options = {}) {
|
|
|
15967
16225
|
const region = bb ? ` (bbox ${bb.x},${bb.y} ${bb.width}x${bb.height})` : "";
|
|
15968
16226
|
return {
|
|
15969
16227
|
ok: false,
|
|
15970
|
-
reason: `hardware-GPU frame has ${diff.blackOnlyInA.pixels} pixel(s) (${(diff.blackOnlyInA.fraction * 100).toFixed(3)}%) that are solid-black while software-GPU frame has content there${region}
|
|
16228
|
+
reason: `hardware-GPU frame has ${diff.blackOnlyInA.pixels} pixel(s) (${(diff.blackOnlyInA.fraction * 100).toFixed(3)}%) that are solid-black while software-GPU frame has content there${region}, likely a shape-dependent hardware-GPU capture bug`,
|
|
15971
16229
|
diff
|
|
15972
16230
|
};
|
|
15973
16231
|
}
|
|
@@ -15976,7 +16234,7 @@ function verifyGpuParity(a, b, options = {}) {
|
|
|
15976
16234
|
const region = bb ? ` (bbox ${bb.x},${bb.y} ${bb.width}x${bb.height})` : "";
|
|
15977
16235
|
return {
|
|
15978
16236
|
ok: false,
|
|
15979
|
-
reason: `software-GPU frame has ${diff.blackOnlyInB.pixels} pixel(s) (${(diff.blackOnlyInB.fraction * 100).toFixed(3)}%) that are solid-black while hardware-GPU frame has content there${region}
|
|
16237
|
+
reason: `software-GPU frame has ${diff.blackOnlyInB.pixels} pixel(s) (${(diff.blackOnlyInB.fraction * 100).toFixed(3)}%) that are solid-black while hardware-GPU frame has content there${region}, an unexpected inverse pattern, worth investigating`,
|
|
15980
16238
|
diff
|
|
15981
16239
|
};
|
|
15982
16240
|
}
|
|
@@ -16296,6 +16554,7 @@ export {
|
|
|
16296
16554
|
analyzeCompositionHdr,
|
|
16297
16555
|
isExternalFfmpegInterruption,
|
|
16298
16556
|
formatFfmpegError,
|
|
16557
|
+
describeFfmpegFailure,
|
|
16299
16558
|
runFfmpeg,
|
|
16300
16559
|
probeMediaProfile,
|
|
16301
16560
|
extractMediaMetadata,
|
|
@@ -16351,6 +16610,7 @@ export {
|
|
|
16351
16610
|
parseImageElements,
|
|
16352
16611
|
extractVideoFramesRange,
|
|
16353
16612
|
resolvePlayableVideoDuration,
|
|
16613
|
+
isVideoPastTimelineEnd,
|
|
16354
16614
|
resolveTimelineExtractionWindow,
|
|
16355
16615
|
resolveFinalFrameExtractionWindow,
|
|
16356
16616
|
resolveVideoExtractionWindow,
|