hyperframes 0.8.145 → 0.8.147
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-B6OUH46W.js → add-5CNUY3EW.js} +15 -15
- package/dist/{animatedGifPrep-MIWRDJIO.js → animatedGifPrep-ZID2XJRQ.js} +10 -10
- package/dist/{asset-XJ2B2PU3.js → asset-TFEENZ7K.js} +2 -2
- package/dist/{assetCataloger-A5ZLSCMZ.js → assetCataloger-GX4B55VB.js} +4 -4
- package/dist/{auth-5HOVCZMV.js → auth-EJVZW6C3.js} +2 -2
- package/dist/{autoUpdate-WVNL5K3D.js → autoUpdate-MBNEZ2U4.js} +2 -2
- package/dist/{backgroundChecks-IGF54SFH.js → backgroundChecks-DNW5NPTY.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{batchRender-T6JEGCQV.js → batchRender-T7S2CGJE.js} +5 -5
- package/dist/{beats-37T6Y6QP.js → beats-STXBGN6Z.js} +16 -16
- package/dist/{benchmark-ZVVLOWPP.js → benchmark-AC6IGEQP.js} +12 -12
- package/dist/{browser-4SSJOLH4.js → browser-FB5AK32T.js} +5 -5
- package/dist/{bundleWithLocalizedFonts-XWK423QP.js → bundleWithLocalizedFonts-HSCWGCFO.js} +2 -2
- package/dist/{capture-KZXOPBMO.js → capture-I24MXH3Y.js} +13 -13
- package/dist/{capture-2SF7YP57.js → capture-VEIVUHWB.js} +2 -2
- package/dist/{catalog-NLD4A2C6.js → catalog-JRC4VTTI.js} +15 -15
- package/dist/{catch-up-4BSRN6IR.js → catch-up-NTCK7Z3G.js} +6 -6
- package/dist/{check-IA44QQWM.js → check-7M7W4LPN.js} +38 -38
- package/dist/{checkBrowser-VM35KZMW.js → checkBrowser-2YRVCAYB.js} +118 -61
- package/dist/{chunk-OTHDVXOJ.js → chunk-2IDHEEAN.js} +3 -3
- package/dist/{chunk-VHFEMFYL.js → chunk-3BETQ4XI.js} +3 -3
- package/dist/{chunk-3KU3PFDW.js → chunk-3BYMCR4O.js} +85 -75
- package/dist/{chunk-FXJJCPPB.js → chunk-3HSWLZ6S.js} +1 -1
- package/dist/{chunk-ZF7SXQJ7.js → chunk-3PPO64IB.js} +2 -2
- package/dist/{chunk-PLR7Z6TC.js → chunk-3YUFN37G.js} +1 -1
- package/dist/{chunk-GRY3PN3K.js → chunk-4YVMMRHR.js} +2 -2
- package/dist/{chunk-ICKDMTLA.js → chunk-55RZG5SQ.js} +2 -2
- package/dist/{chunk-3IBMBNYY.js → chunk-5OKZLMQK.js} +10 -10
- package/dist/{chunk-DIRJ322I.js → chunk-66SAATRI.js} +3 -3
- package/dist/{chunk-GMAJB2P6.js → chunk-6BSIQJPW.js} +1 -1
- package/dist/{chunk-4QWD6JYN.js → chunk-6WD636E5.js} +3 -3
- package/dist/{chunk-5WVW7P23.js → chunk-A72OAGNB.js} +6 -6
- package/dist/{chunk-XBX4QJP7.js → chunk-AFIATIKS.js} +1 -1
- package/dist/{chunk-G75ENCEZ.js → chunk-AIJS74LG.js} +3 -3
- package/dist/{chunk-FBCULCAL.js → chunk-AL2YEVOT.js} +1 -1
- package/dist/{chunk-GEMJYBRU.js → chunk-B45OFFOQ.js} +2 -2
- package/dist/{chunk-VRW6EBHV.js → chunk-BAKPN2D2.js} +101 -94
- package/dist/{chunk-HB7FJOY4.js → chunk-BPUFVVSH.js} +1 -1
- package/dist/{chunk-BIAC7IJT.js → chunk-DDNS2674.js} +1 -1
- package/dist/{chunk-QFSCCQIR.js → chunk-DMKI64EZ.js} +1 -1
- package/dist/{chunk-EPE2HRJK.js → chunk-DYWSCPCR.js} +69 -64
- package/dist/{chunk-ZIYZ77MR.js → chunk-EGO6Z24R.js} +3 -3
- package/dist/{chunk-KC5XXX3X.js → chunk-EOOEC32M.js} +1 -1
- package/dist/{chunk-XIHCMVMG.js → chunk-FDL4HV44.js} +2 -2
- package/dist/{chunk-QZ3AZTXI.js → chunk-FJWEYVEM.js} +42 -16
- package/dist/{chunk-PUGIRFPX.js → chunk-FMNVTKX2.js} +4 -4
- package/dist/{chunk-IHTRYDFN.js → chunk-H7B6JSHI.js} +1 -1
- package/dist/{chunk-T42U2W2R.js → chunk-IXLUTE6N.js} +1 -1
- package/dist/{chunk-FDT6KQV4.js → chunk-JBQJKC23.js} +2 -2
- package/dist/{chunk-IEIJRMLZ.js → chunk-JDB65PPT.js} +62 -63
- package/dist/{chunk-UXO2L7HJ.js → chunk-JF5L5SQ6.js} +278 -65
- package/dist/{chunk-USDEMXXL.js → chunk-JYVGYKXG.js} +1 -1
- package/dist/{chunk-R42WWQVB.js → chunk-KPJMBC4K.js} +2 -2
- package/dist/{chunk-MAYCCGMQ.js → chunk-KVGDFUVO.js} +1 -1
- package/dist/{chunk-CFUXNBWH.js → chunk-LE7T4ML7.js} +1 -1
- package/dist/{chunk-MQZVBHEY.js → chunk-LFNQDTSH.js} +1 -1
- package/dist/{chunk-KIFHYXPB.js → chunk-MOL32UKR.js} +45 -11
- package/dist/{chunk-6LGNTUZN.js → chunk-N53FLAMZ.js} +7 -7
- package/dist/{chunk-ADF3X6GM.js → chunk-P6HELLPU.js} +2 -2
- package/dist/{chunk-5NRXZGKK.js → chunk-POKK6XXU.js} +9 -9
- package/dist/{chunk-GBGJ7A3D.js → chunk-QHRPB2JQ.js} +6 -6
- package/dist/{chunk-BFACGEII.js → chunk-QTTDKWSM.js} +2 -2
- package/dist/{chunk-U5QY7KTN.js → chunk-SEABQTBP.js} +11 -8
- package/dist/{chunk-ZEUONDQG.js → chunk-SYUGVPYH.js} +1 -1
- package/dist/{chunk-PJ3HDUCY.js → chunk-VAS6HQX3.js} +39 -9
- package/dist/{chunk-GQJZORQP.js → chunk-XBSPQ52S.js} +1 -1
- package/dist/{chunk-4ORLNTET.js → chunk-XOJDBRNF.js} +1 -1
- package/dist/{chunk-LAFJSYDL.js → chunk-XVIYZU3D.js} +1 -1
- package/dist/{chunk-OB5MJR4W.js → chunk-YCGU3UNU.js} +1 -1
- package/dist/{chunk-GXJEGZL2.js → chunk-Z5C4LEAD.js} +17 -18
- package/dist/chunk-Z6FLHIDV.js +3795 -0
- package/dist/{chunk-DDIVWXN7.js → chunk-ZFN6GQU4.js} +4 -4
- package/dist/{chunk-QLSM7K5S.js → chunk-ZLL72U3I.js} +2 -2
- package/dist/{chunk-ZSVUAVMT.js → chunk-ZQXNL5DL.js} +5 -5
- package/dist/{chunk-DWUKJQTO.js → chunk-ZVZSFDW3.js} +2 -2
- package/dist/{clean-RDKJB5FK.js → clean-3RY3Y2GX.js} +23 -23
- package/dist/cli.js +52 -52
- package/dist/{cloud-QVPVVNCW.js → cloud-MLPBFCVE.js} +4 -4
- package/dist/{cloudrun-3RE2UCQG.js → cloudrun-NQF5KFQI.js} +6 -6
- package/dist/{compare-LEDOY7MO.js → compare-6EZX2754.js} +32 -32
- package/dist/{compiler-F54OZZPM.js → compiler-ACCPOZMQ.js} +6 -6
- package/dist/{component-N42M67WY.js → component-F5PQHP6A.js} +3 -3
- package/dist/{compositions-VB2J2LZI.js → compositions-AUHWSI2H.js} +15 -15
- package/dist/{delete-Q6RWMJ4Z.js → delete-VUF4SHHJ.js} +2 -2
- package/dist/{deterministicFonts-U2BIMGPG.js → deterministicFonts-C5JQLCTS.js} +2 -2
- package/dist/{dist-Q4BI5A73.js → dist-GPDOV3IQ.js} +4 -4
- package/dist/{dist-KDJTFJHA.js → dist-HUMFCJ6T.js} +3 -3
- package/dist/{doctor-JMMUHDRV.js → doctor-3XHCA5JM.js} +15 -15
- package/dist/{events-RGUAHIBW.js → events-FKOIB3R3.js} +5 -5
- package/dist/{events-6ZBSIBE5.js → events-TIU72LZM.js} +4 -4
- package/dist/{feedback-FIID6MZW.js → feedback-V6VATYBE.js} +10 -10
- package/dist/{ffmpeg-T5SH2EFJ.js → ffmpeg-5V4FP3R6.js} +2 -2
- package/dist/{figma-BL3JJC36.js → figma-YBJM2VGE.js} +3 -3
- package/dist/fontLocalizeCli.js +14 -14
- package/dist/{get-P46GS7S6.js → get-3YVCGZBX.js} +2 -2
- package/dist/{gpuPolicy-AQ2BPLBK.js → gpuPolicy-ZOUG66M7.js} +10 -10
- package/dist/{grade-compare-GDCWU7ZZ.js → grade-compare-MYXIRKQI.js} +33 -33
- package/dist/{help-HJR3RWFG.js → help-WFZ2SUUR.js} +2 -2
- package/dist/{history-GGO5AWJB.js → history-HWPRJFR5.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-3GFWUBJC.js → info-WJDTQ4OZ.js} +11 -11
- package/dist/{init-36OPU2ZG.js → init-EXHDN7RO.js} +31 -31
- package/dist/{inspect-F2HEL5ON.js → inspect-EAOLZOMQ.js} +33 -33
- package/dist/{installer-NI6P6GC4.js → installer-LZ4X5TZZ.js} +6 -6
- package/dist/{keyframes-7OY3LMAZ.js → keyframes-FSZYNEDN.js} +8 -8
- package/dist/{lambda-6GJIWNJ3.js → lambda-3GAKHMC3.js} +7 -7
- package/dist/{layout-FTMLNL5A.js → layout-M2TQ2CIX.js} +33 -33
- package/dist/{lint-U6POHUON.js → lint-7C5UPTNQ.js} +11 -11
- package/dist/{lintProject-UBZ3TDHU.js → lintProject-SSEXWCH6.js} +3 -3
- package/dist/{list-IP5KI5Q2.js → list-GU5KBZ73.js} +2 -2
- package/dist/{login-4VWTVPHG.js → login-ZL2NYBIZ.js} +4 -4
- package/dist/{manager-YKMHR6LN.js → manager-5ORDVWJB.js} +3 -3
- package/dist/{media-treatment-XGV44AGV.js → media-treatment-QC3TAHVU.js} +82 -54
- package/dist/{models-UCXB6UR5.js → models-A54I4QR5.js} +14 -14
- package/dist/{motionShot-WPVNBXKA.js → motionShot-ZXPK562F.js} +35 -29
- package/dist/{normalize-audio-TYJ3UEHO.js → normalize-audio-CAP3DAE6.js} +10 -10
- package/dist/{open-HENOLVB2.js → open-GUSBNMDB.js} +6 -6
- package/dist/{parakeetRunner-VFQ6WFTD.js → parakeetRunner-GCCH72CO.js} +14 -14
- package/dist/{pipeline-6WPSC3H4.js → pipeline-SCJ2MWYH.js} +11 -11
- package/dist/{play-OI53WNUM.js → play-FP2QLMGM.js} +26 -26
- package/dist/{preflight-B4AFK3RG.js → preflight-ONF5AJNO.js} +5 -5
- package/dist/{present-NGMEQ4NO.js → present-H5SA4APW.js} +23 -23
- package/dist/{preview-VT2HWRWZ.js → preview-ATPVKA6K.js} +57 -26
- package/dist/{producer-7TYEAUF7.js → producer-VBYUC27R.js} +1 -1
- package/dist/{projectConfig-B2AF5DXY.js → projectConfig-SBRMQZGG.js} +8 -8
- package/dist/{publication-2PANQVC6.js → publication-5BQICRZO.js} +4 -4
- package/dist/{publish-O5OL42BG.js → publish-XDUZA3E3.js} +27 -26
- package/dist/{remove-background-T2JM6UVP.js → remove-background-GA6F7JZW.js} +12 -12
- package/dist/{render-Z253WHBK.js → render-KUVZFNQD.js} +27 -27
- package/dist/{render-W7OFXDOJ.js → render-PCFO45RK.js} +5 -5
- package/dist/{render-OLPKOJRU.js → render-XCYXFZTL.js} +12 -12
- package/dist/{render-batch-46IIBFKL.js → render-batch-7R2NRNVJ.js} +5 -5
- package/dist/{renderCancellation-QTZOCDJ2.js → renderCancellation-NIXEWRAO.js} +10 -10
- package/dist/renderSetupWorker.js +3 -3
- package/dist/{resolver-J2PL5FVJ.js → resolver-C4FPX72I.js} +6 -6
- package/dist/{sherpa-ZF42V3RN.js → sherpa-IQY52XU6.js} +14 -14
- package/dist/skills/media-use/references/media-treatments.md +5 -1
- package/dist/{skills-ACDOO3PX.js → skills-JSSAEKUW.js} +7 -7
- package/dist/{skillsUpdateCheck-L5OXVVPU.js → skillsUpdateCheck-XE5E2K2F.js} +3 -3
- package/dist/{slideshow-4OJEPWNZ.js → slideshow-YTWATJRT.js} +1 -1
- package/dist/{snapshot-UUVNZOW5.js → snapshot-QMQOE2DD.js} +34 -34
- package/dist/{src-BSLKXEGX.js → src-2GMPFYHL.js} +11 -9
- package/dist/{src-KS3AWFGT.js → src-R55QLGWI.js} +13 -13
- package/dist/{staticProjectServer-2TTVGPQH.js → staticProjectServer-YH2RHYKW.js} +20 -20
- package/dist/{status-LXPGSSKY.js → status-PTKNQD2E.js} +2 -2
- package/dist/studio/assets/{hyperframes-player-DZ5JrMDg.js → hyperframes-player-_eMxRXF_.js} +3 -3
- package/dist/studio/assets/{index-BqFRhNZa.js → index-6okhHdnH.js} +1 -1
- package/dist/studio/assets/{index-BOs6C0aZ.js → index-CEq9KmY2.js} +1 -1
- package/dist/studio/assets/{index-D8W0a0Z9.js → index-bXzd7tvF.js} +135 -135
- package/dist/studio/index.html +1 -1
- package/dist/{studio-api-WYWF7XRC.js → studio-api-T2TS2CHL.js} +16 -14
- package/dist/{studioServer-3FG4YTP7.js → studioServer-Q4MT44LT.js} +50 -50
- package/dist/{system-OQLOGGPJ.js → system-OXRG57F2.js} +2 -2
- package/dist/{telemetry-PAOOB4T4.js → telemetry-V5JYZDS4.js} +5 -5
- package/dist/{timeline-JIIJKKPC.js → timeline-KTCTOKF5.js} +34 -25
- package/dist/{tokens-3QRAM75Z.js → tokens-ZHREZPK6.js} +2 -2
- package/dist/{transcribe-QSBP5MUG.js → transcribe-4QDEWCE6.js} +12 -12
- package/dist/{transcribe-SZB6UMXE.js → transcribe-CRAV4FU3.js} +13 -13
- package/dist/{updateCheck-V3DSB26K.js → updateCheck-E7RMSK3E.js} +2 -2
- package/dist/{upgrade-L5GISOEV.js → upgrade-27PFTDCV.js} +2 -2
- package/dist/{urlDownloader-52FERT4X.js → urlDownloader-EAF6PLUP.js} +9 -9
- package/dist/{validate-HVVLX7KO.js → validate-OKNFDBMG.js} +27 -27
- package/dist/{video-MEOWRMFG.js → video-IEVS4FIH.js} +10 -10
- package/package.json +3 -2
- package/dist/chunk-AUWHD7RA.js +0 -3753
- package/dist/studio/chunk-6JY3MTIX.js +0 -1204
- package/dist/studio/chunk-EEXSYY5F.js +0 -190
- package/dist/studio/chunk-NZPJUZKT.js +0 -1538
- package/dist/studio/chunk-PZ5AY32C.js +0 -10
- package/dist/studio/chunk-TP6D3U37.js +0 -845
- package/dist/studio/chunk-Y2OHHNM2.js +0 -970
- package/dist/studio/domEditingLayers-PTP2ICGM.js +0 -47
- package/dist/studio/index.css +0 -165
- package/dist/studio/index.d.ts +0 -4318
- package/dist/studio/index.js +0 -100952
- package/dist/studio/player.d.ts +0 -30
- package/dist/studio/player.js +0 -10
- package/dist/studio/styles/tailwind-preset.d.ts +0 -39
- package/dist/studio/styles/tailwind-preset.js +0 -50
- package/dist/studio/styles.css +0 -1
- package/dist/studio/ui.d.ts +0 -297
- package/dist/studio/ui.js +0 -66
|
@@ -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(
|
|
@@ -6098,20 +6105,26 @@ async function prepareFrameForCapture(session, frameIndex, time, seekOptions, he
|
|
|
6098
6105
|
seekOptions?.subFrameDivisions
|
|
6099
6106
|
);
|
|
6100
6107
|
const seekStart = Date.now();
|
|
6108
|
+
session.captureStep = "seeking the timeline";
|
|
6101
6109
|
const hasPendingComposite = await seekPageTimeline(page, quantizedTime, seekOptions);
|
|
6110
|
+
session.captureStep = "decoding CSS background images";
|
|
6102
6111
|
await decodeDynamicCssBackgroundImages(page);
|
|
6103
6112
|
const seekMs = Date.now() - seekStart;
|
|
6104
6113
|
const beforeCaptureStart = Date.now();
|
|
6105
6114
|
if (session.onBeforeCapture) {
|
|
6115
|
+
session.captureStep = "injecting video frames";
|
|
6106
6116
|
await session.onBeforeCapture(page, quantizedTime, heldVideoTime);
|
|
6107
6117
|
}
|
|
6118
|
+
session.captureStep = "waiting for page seek handlers";
|
|
6108
6119
|
await waitForPendingSeekCompletion(page);
|
|
6120
|
+
session.captureStep = "loading color-grading LUTs";
|
|
6109
6121
|
await page.evaluate(async () => {
|
|
6110
6122
|
const runtime = window.__hf?.colorGrading;
|
|
6111
6123
|
await runtime?.waitForActiveLuts?.();
|
|
6112
6124
|
});
|
|
6113
6125
|
const beforeCaptureMs = Date.now() - beforeCaptureStart;
|
|
6114
6126
|
if (hasPendingComposite && session.captureMode !== "beginframe" && session.captureMode !== "drawelement") {
|
|
6127
|
+
session.captureStep = "compositing the page";
|
|
6115
6128
|
await page.evaluate(async () => {
|
|
6116
6129
|
const w = window;
|
|
6117
6130
|
if (typeof w.__hf_page_composite_prepare === "function") {
|
|
@@ -6131,6 +6144,7 @@ async function prepareFrameForCapture(session, frameIndex, time, seekOptions, he
|
|
|
6131
6144
|
}
|
|
6132
6145
|
});
|
|
6133
6146
|
}
|
|
6147
|
+
session.captureStep = "capturing the frame";
|
|
6134
6148
|
return { quantizedTime, seekMs, beforeCaptureMs };
|
|
6135
6149
|
}
|
|
6136
6150
|
function computeAuthoredClipBoundaryFrames(schedule, fps) {
|
|
@@ -6861,10 +6875,12 @@ async function captureAccumulatedFrame(session, frameIndex, absFrameIndex, plan)
|
|
|
6861
6875
|
const fps = fpsToNumber(session.options.fps);
|
|
6862
6876
|
const frameTime = quantizeSeekTime(absFrameIndex / fps, fps);
|
|
6863
6877
|
const eventfulSeekStart = Date.now();
|
|
6878
|
+
session.captureStep = "seeking the timeline";
|
|
6864
6879
|
await seekPageTimeline(session.page, frameTime, void 0);
|
|
6865
6880
|
const totals = { seekMs: Date.now() - eventfulSeekStart, beforeCaptureMs: 0, screenshotMs: 0 };
|
|
6866
6881
|
if (session.onBeforeCapture) {
|
|
6867
6882
|
const injectStart = Date.now();
|
|
6883
|
+
session.captureStep = "injecting video frames";
|
|
6868
6884
|
await session.onBeforeCapture(session.page, frameTime, frameTime);
|
|
6869
6885
|
totals.beforeCaptureMs += Date.now() - injectStart;
|
|
6870
6886
|
}
|
|
@@ -6903,6 +6919,7 @@ async function captureAccumulatedFrame(session, frameIndex, absFrameIndex, plan)
|
|
|
6903
6919
|
accumulator.add(sample.buffer);
|
|
6904
6920
|
}
|
|
6905
6921
|
const restoreSeekStart = Date.now();
|
|
6922
|
+
session.captureStep = "seeking the timeline";
|
|
6906
6923
|
await seekPageTimeline(session.page, frameTime, { suppressEvents: true });
|
|
6907
6924
|
totals.seekMs += Date.now() - restoreSeekStart;
|
|
6908
6925
|
const blended = accumulator.finish();
|
|
@@ -6978,7 +6995,7 @@ async function captureFrameToBuffer(session, frameIndex, time) {
|
|
|
6978
6995
|
() => {
|
|
6979
6996
|
session.deFrameTimeouts = (session.deFrameTimeouts ?? 0) + 1;
|
|
6980
6997
|
console.log(
|
|
6981
|
-
`[engine] fast capture
|
|
6998
|
+
`[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
6999
|
);
|
|
6983
7000
|
}
|
|
6984
7001
|
) : await captureFrameCore(session, frameIndex, time);
|
|
@@ -7076,6 +7093,7 @@ async function captureFramesBatchPipelined(session, frameIndices, times) {
|
|
|
7076
7093
|
const startTime = Date.now();
|
|
7077
7094
|
const fps = fpsToNumber(options.fps);
|
|
7078
7095
|
const quantized = times.map((t) => quantizeTimeToFrame(t, fps));
|
|
7096
|
+
session.captureStep = "capturing the frame";
|
|
7079
7097
|
const { encodeResults, failedAt, error } = await produceDrawElementFrameBatch(
|
|
7080
7098
|
page,
|
|
7081
7099
|
quantized,
|
|
@@ -7751,7 +7769,7 @@ function findHdrAutoPromotion(sources) {
|
|
|
7751
7769
|
} : null;
|
|
7752
7770
|
}
|
|
7753
7771
|
function formatHdrAutoPromotionWarning(promotion) {
|
|
7754
|
-
return `[Render] HDR auto-promotion triggered by "${promotion.triggeringAsset}"
|
|
7772
|
+
return `[Render] HDR auto-promotion triggered by "${promotion.triggeringAsset}", output: ${promotion.output.colorSpace} / ${promotion.output.codec}`;
|
|
7755
7773
|
}
|
|
7756
7774
|
function analyzeCompositionHdr(colorSpaces) {
|
|
7757
7775
|
let hasPq = false;
|
|
@@ -10951,6 +10969,7 @@ function unwrapTemplate(html) {
|
|
|
10951
10969
|
// ../engine/src/services/extractionCache.ts
|
|
10952
10970
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
10953
10971
|
import {
|
|
10972
|
+
createReadStream as createReadStream3,
|
|
10954
10973
|
existsSync as existsSync7,
|
|
10955
10974
|
lstatSync as lstatSync2,
|
|
10956
10975
|
mkdirSync as mkdirSync5,
|
|
@@ -11016,6 +11035,15 @@ var COMPLETE_SENTINEL = ".hf-complete";
|
|
|
11016
11035
|
var GC_MARKER = ".hf-last-gc";
|
|
11017
11036
|
var SCHEMA_PREFIX = "hfcache-v6-";
|
|
11018
11037
|
var KEY_HEX_CHARS = 16;
|
|
11038
|
+
async function readContentSha256(path, signal) {
|
|
11039
|
+
try {
|
|
11040
|
+
const hash = createHash2("sha256");
|
|
11041
|
+
for await (const chunk of createReadStream3(path, { signal })) hash.update(chunk);
|
|
11042
|
+
return hash.digest("hex");
|
|
11043
|
+
} catch {
|
|
11044
|
+
return null;
|
|
11045
|
+
}
|
|
11046
|
+
}
|
|
11019
11047
|
function readKeyStat(videoPath) {
|
|
11020
11048
|
try {
|
|
11021
11049
|
const stat = statSync6(videoPath);
|
|
@@ -11026,9 +11054,9 @@ function readKeyStat(videoPath) {
|
|
|
11026
11054
|
}
|
|
11027
11055
|
function canonicalKeyBlob(input) {
|
|
11028
11056
|
const durationForKey = Number.isFinite(input.duration) ? input.duration : -1;
|
|
11057
|
+
const identity = input.contentSha256 !== void 0 ? { c: input.contentSha256 } : { p: input.videoPath, m: input.mtimeMs };
|
|
11029
11058
|
const blob = {
|
|
11030
|
-
|
|
11031
|
-
m: input.mtimeMs,
|
|
11059
|
+
...identity,
|
|
11032
11060
|
s: input.size,
|
|
11033
11061
|
ms: input.mediaStart,
|
|
11034
11062
|
d: durationForKey,
|
|
@@ -11274,6 +11302,7 @@ function extractionFrameCountForDuration(durationSeconds, fps, isVFR) {
|
|
|
11274
11302
|
const frames = Number(frameCount);
|
|
11275
11303
|
return Math.max(1, Number.isSafeInteger(frames) ? frames : Number.MAX_SAFE_INTEGER);
|
|
11276
11304
|
}
|
|
11305
|
+
var DOWNLOAD_SUBDIR = "_downloads";
|
|
11277
11306
|
var EXTRACT_CACHE_MIN_AGE_MS = 60 * 60 * 1e3;
|
|
11278
11307
|
var GC_STALENESS_MS = 24 * 60 * 60 * 1e3;
|
|
11279
11308
|
var SDR_TO_HDR_COLORSPACE_FILTER = "colorspace=all=bt2020:iall=bt709:range=tv";
|
|
@@ -11962,6 +11991,9 @@ function canHoldFinalFramePastEof(video) {
|
|
|
11962
11991
|
return !video.loop && Number.isFinite(timelineDuration) && timelineDuration > 0;
|
|
11963
11992
|
}
|
|
11964
11993
|
var FINAL_FRAME_LOGICAL_DURATION_SECONDS = 1e-6;
|
|
11994
|
+
function isVideoPastTimelineEnd(video, timelineEnd) {
|
|
11995
|
+
return video.start >= timelineEnd;
|
|
11996
|
+
}
|
|
11965
11997
|
function rebaseVideoToWindow(video, window2) {
|
|
11966
11998
|
if (window2.preserveTimelinePhase) return;
|
|
11967
11999
|
const start = Math.abs(window2.compositionStart) < 1e-9 ? 0 : window2.compositionStart;
|
|
@@ -12305,14 +12337,16 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12305
12337
|
const warnedSrcs = /* @__PURE__ */ new Set();
|
|
12306
12338
|
for (const video of videos) {
|
|
12307
12339
|
if (signal?.aborted) break;
|
|
12308
|
-
if (options.timelineEnd !== void 0 && video
|
|
12340
|
+
if (options.timelineEnd !== void 0 && isVideoPastTimelineEnd(video, options.timelineEnd)) {
|
|
12341
|
+
continue;
|
|
12342
|
+
}
|
|
12309
12343
|
try {
|
|
12310
12344
|
let videoPath = video.src;
|
|
12311
12345
|
if (!isHttpUrl(videoPath)) {
|
|
12312
12346
|
videoPath = resolveProjectRelativeSrc(video.src, baseDir, compiledDir);
|
|
12313
12347
|
}
|
|
12314
12348
|
if (isHttpUrl(videoPath)) {
|
|
12315
|
-
const downloadDir = join10(options.outputDir,
|
|
12349
|
+
const downloadDir = join10(options.outputDir, DOWNLOAD_SUBDIR);
|
|
12316
12350
|
mkdirSync6(downloadDir, { recursive: true });
|
|
12317
12351
|
videoPath = await downloadToTemp(
|
|
12318
12352
|
videoPath,
|
|
@@ -12352,6 +12386,10 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12352
12386
|
}
|
|
12353
12387
|
}
|
|
12354
12388
|
breakdown.resolveMs = Date.now() - phase1Start;
|
|
12389
|
+
const contentKeyedDirs = [
|
|
12390
|
+
join10(options.outputDir, DOWNLOAD_SUBDIR),
|
|
12391
|
+
...options.contentKeyedDirs ?? []
|
|
12392
|
+
];
|
|
12355
12393
|
const cacheKeyInputs = resolvedVideos.map(({ video, videoPath }) => {
|
|
12356
12394
|
const stat = readKeyStat(videoPath);
|
|
12357
12395
|
if (!stat) return null;
|
|
@@ -12359,7 +12397,8 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12359
12397
|
videoPath,
|
|
12360
12398
|
mtimeMs: stat.mtimeMs,
|
|
12361
12399
|
size: stat.size,
|
|
12362
|
-
mediaStart: video.mediaStart
|
|
12400
|
+
mediaStart: video.mediaStart,
|
|
12401
|
+
contentKeyed: contentKeyedDirs.some((dir) => isPathInside(videoPath, dir))
|
|
12363
12402
|
};
|
|
12364
12403
|
});
|
|
12365
12404
|
const phase2ProbeStart = Date.now();
|
|
@@ -12505,10 +12544,26 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12505
12544
|
});
|
|
12506
12545
|
return { ...rehydrated, ownedByLookup: true };
|
|
12507
12546
|
}
|
|
12547
|
+
const contentSha256ByPath = /* @__PURE__ */ new Map();
|
|
12548
|
+
async function hashContentKeyedSources(works) {
|
|
12549
|
+
if (!cacheRootDir) return;
|
|
12550
|
+
const paths = /* @__PURE__ */ new Set();
|
|
12551
|
+
for (const work of works) {
|
|
12552
|
+
const keyInput = cacheKeyInputs[work.index];
|
|
12553
|
+
if (keyInput?.contentKeyed) paths.add(keyInput.videoPath);
|
|
12554
|
+
}
|
|
12555
|
+
await Promise.all(
|
|
12556
|
+
[...paths].map(
|
|
12557
|
+
async (path) => contentSha256ByPath.set(path, await readContentSha256(path, signal))
|
|
12558
|
+
)
|
|
12559
|
+
);
|
|
12560
|
+
}
|
|
12508
12561
|
function lookupCacheFor(work) {
|
|
12509
12562
|
if (!cacheRootDir) return { work };
|
|
12510
12563
|
const keyInput = cacheKeyInputs[work.index];
|
|
12511
12564
|
if (!keyInput) return { work };
|
|
12565
|
+
const contentSha256 = keyInput.contentKeyed ? contentSha256ByPath.get(keyInput.videoPath) ?? null : void 0;
|
|
12566
|
+
if (contentSha256 === null) return { work };
|
|
12512
12567
|
const transformParts = [
|
|
12513
12568
|
work.sdrToHdrTransfer ? sdrToHdrTransformKey(work.sdrToHdrTransfer) : void 0,
|
|
12514
12569
|
work.hdrToSdrTransformKey,
|
|
@@ -12520,6 +12575,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12520
12575
|
mtimeMs: keyInput.mtimeMs,
|
|
12521
12576
|
size: keyInput.size,
|
|
12522
12577
|
mediaStart: keyInput.mediaStart,
|
|
12578
|
+
contentSha256,
|
|
12523
12579
|
duration: work.videoDuration,
|
|
12524
12580
|
fps: fpsKey,
|
|
12525
12581
|
format: work.format,
|
|
@@ -12776,8 +12832,11 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config, c
|
|
|
12776
12832
|
});
|
|
12777
12833
|
}
|
|
12778
12834
|
const cacheMisses = [];
|
|
12779
|
-
|
|
12780
|
-
|
|
12835
|
+
const worksToLookUp = [...uniqueWorks.values()].filter(
|
|
12836
|
+
(work) => !uniqueOutcomes.has(work.dedupeKey)
|
|
12837
|
+
);
|
|
12838
|
+
await hashContentKeyedSources(worksToLookUp);
|
|
12839
|
+
for (const work of worksToLookUp) {
|
|
12781
12840
|
const lookup = lookupCacheFor(work);
|
|
12782
12841
|
if ("work" in lookup) {
|
|
12783
12842
|
cacheMisses.push(lookup);
|
|
@@ -13716,6 +13775,159 @@ function applyVolumeEnvelopeToWav(wavPath, keyframes, trackStart, baseVolume) {
|
|
|
13716
13775
|
}
|
|
13717
13776
|
}
|
|
13718
13777
|
|
|
13778
|
+
// ../core/dist/audio/audioFxTruePeak.js
|
|
13779
|
+
var TRUE_PEAK_OVERSAMPLE = 4;
|
|
13780
|
+
var TRUE_PEAK_DETECTOR_DELAY = 16;
|
|
13781
|
+
var KERNEL_HALF = TRUE_PEAK_OVERSAMPLE * TRUE_PEAK_DETECTOR_DELAY;
|
|
13782
|
+
var BRANCH_TAPS = 2 * TRUE_PEAK_DETECTOR_DELAY + 1;
|
|
13783
|
+
var TRUE_PEAK_WORKLET_SOURCE = `
|
|
13784
|
+
const TP_OS = ${TRUE_PEAK_OVERSAMPLE};
|
|
13785
|
+
const TP_HALF = ${KERNEL_HALF};
|
|
13786
|
+
const TP_BRANCH = ${BRANCH_TAPS};
|
|
13787
|
+
const TP_DELAY = ${TRUE_PEAK_DETECTOR_DELAY};
|
|
13788
|
+
|
|
13789
|
+
function besselI0(x) {
|
|
13790
|
+
let sum = 1, term = 1;
|
|
13791
|
+
for (let k = 1; k < 40; k++) {
|
|
13792
|
+
term *= (x / (2 * k)) * (x / (2 * k));
|
|
13793
|
+
sum += term;
|
|
13794
|
+
}
|
|
13795
|
+
return sum;
|
|
13796
|
+
}
|
|
13797
|
+
|
|
13798
|
+
/** Kaiser-windowed sinc, one array per polyphase branch, newest tap first. */
|
|
13799
|
+
function tpBranches() {
|
|
13800
|
+
const beta = 6;
|
|
13801
|
+
const norm = besselI0(beta);
|
|
13802
|
+
const branches = [];
|
|
13803
|
+
for (let p = 0; p < TP_OS; p++) {
|
|
13804
|
+
const taps = new Float64Array(TP_BRANCH);
|
|
13805
|
+
let sum = 0;
|
|
13806
|
+
for (let j = 0; j < TP_BRANCH; j++) {
|
|
13807
|
+
const k = p + TP_OS * j;
|
|
13808
|
+
if (k > 2 * TP_HALF) continue;
|
|
13809
|
+
const t = (k - TP_HALF) / TP_OS;
|
|
13810
|
+
const sinc = t === 0 ? 1 : Math.sin(Math.PI * t) / (Math.PI * t);
|
|
13811
|
+
const r = (k - TP_HALF) / TP_HALF;
|
|
13812
|
+
taps[j] = sinc * (besselI0(beta * Math.sqrt(Math.max(0, 1 - r * r))) / norm);
|
|
13813
|
+
sum += taps[j];
|
|
13814
|
+
}
|
|
13815
|
+
// Unity gain at DC on every branch, so a steady level reads as itself.
|
|
13816
|
+
for (let j = 0; j < TP_BRANCH; j++) taps[j] /= sum;
|
|
13817
|
+
branches.push(taps);
|
|
13818
|
+
}
|
|
13819
|
+
return branches;
|
|
13820
|
+
}
|
|
13821
|
+
|
|
13822
|
+
class HfTruePeak extends AudioWorkletProcessor {
|
|
13823
|
+
constructor(o) {
|
|
13824
|
+
super();
|
|
13825
|
+
this.p = o.processorOptions || {};
|
|
13826
|
+
this.look = Math.max(1, Math.round((this.p.lookahead ?? 3) * sampleRate / 1000));
|
|
13827
|
+
this.delayLen = this.look + TP_DELAY;
|
|
13828
|
+
this.branches = tpBranches();
|
|
13829
|
+
this.setLevels();
|
|
13830
|
+
this.clear();
|
|
13831
|
+
this.port.onmessage = (e) => {
|
|
13832
|
+
if (e.data && e.data.__hfDispose) { this.dead = true; return; }
|
|
13833
|
+
if (e.data && e.data.__hfReset) { this.clear(); return; }
|
|
13834
|
+
this.p = { ...this.p, ...e.data };
|
|
13835
|
+
this.setLevels();
|
|
13836
|
+
};
|
|
13837
|
+
}
|
|
13838
|
+
/** Back to the state of a fresh processor: no audio in the delay line, no gain reduction. */
|
|
13839
|
+
clear() {
|
|
13840
|
+
this.ch = [];
|
|
13841
|
+
this.rel = 1;
|
|
13842
|
+
this.dqIdx = new Int32Array(this.look + 2);
|
|
13843
|
+
this.dqVal = new Float64Array(this.look + 2);
|
|
13844
|
+
this.dqHead = 0;
|
|
13845
|
+
this.dqTail = 0;
|
|
13846
|
+
this.t = 0;
|
|
13847
|
+
this.avgRing = new Float64Array(this.look + 1).fill(1);
|
|
13848
|
+
this.avgPos = 0;
|
|
13849
|
+
this.avgSum = this.look + 1;
|
|
13850
|
+
}
|
|
13851
|
+
setLevels() {
|
|
13852
|
+
this.ceiling = Math.pow(10, (this.p.ceiling ?? -1) / 20);
|
|
13853
|
+
this.relCoef = 1 - Math.exp(-1 / (sampleRate * Math.max(1e-4, (this.p.release ?? 80) / 1000)));
|
|
13854
|
+
}
|
|
13855
|
+
channel(c) {
|
|
13856
|
+
if (!this.ch[c]) {
|
|
13857
|
+
this.ch[c] = {
|
|
13858
|
+
delay: new Float32Array(this.delayLen),
|
|
13859
|
+
w: 0,
|
|
13860
|
+
hist: new Float32Array(TP_BRANCH * 2),
|
|
13861
|
+
pos: 0,
|
|
13862
|
+
};
|
|
13863
|
+
}
|
|
13864
|
+
return this.ch[c];
|
|
13865
|
+
}
|
|
13866
|
+
/** Largest interpolated magnitude across channels for the newest sample. */
|
|
13867
|
+
peak(chans, inputs, n) {
|
|
13868
|
+
let peak = 0;
|
|
13869
|
+
for (let c = 0; c < chans; c++) {
|
|
13870
|
+
const s = this.channel(c);
|
|
13871
|
+
const x = inputs[c][n];
|
|
13872
|
+
s.pos = (s.pos + 1) % TP_BRANCH;
|
|
13873
|
+
s.hist[s.pos] = x;
|
|
13874
|
+
s.hist[s.pos + TP_BRANCH] = x;
|
|
13875
|
+
const base = s.pos + TP_BRANCH;
|
|
13876
|
+
for (let p = 0; p < TP_OS; p++) {
|
|
13877
|
+
const taps = this.branches[p];
|
|
13878
|
+
let acc = 0;
|
|
13879
|
+
for (let j = 0; j < TP_BRANCH; j++) acc += taps[j] * s.hist[base - j];
|
|
13880
|
+
const a = acc < 0 ? -acc : acc;
|
|
13881
|
+
if (a > peak) peak = a;
|
|
13882
|
+
}
|
|
13883
|
+
}
|
|
13884
|
+
return peak;
|
|
13885
|
+
}
|
|
13886
|
+
/** Windowed minimum of the required gain over the last look + 1 values. */
|
|
13887
|
+
windowMin(v) {
|
|
13888
|
+
const cap = this.dqIdx.length;
|
|
13889
|
+
while (this.dqTail !== this.dqHead && this.dqVal[(this.dqTail + cap - 1) % cap] >= v) {
|
|
13890
|
+
this.dqTail = (this.dqTail + cap - 1) % cap;
|
|
13891
|
+
}
|
|
13892
|
+
this.dqIdx[this.dqTail] = this.t;
|
|
13893
|
+
this.dqVal[this.dqTail] = v;
|
|
13894
|
+
this.dqTail = (this.dqTail + 1) % cap;
|
|
13895
|
+
while (this.dqIdx[this.dqHead] <= this.t - (this.look + 1)) {
|
|
13896
|
+
this.dqHead = (this.dqHead + 1) % cap;
|
|
13897
|
+
}
|
|
13898
|
+
this.t++;
|
|
13899
|
+
return this.dqVal[this.dqHead];
|
|
13900
|
+
}
|
|
13901
|
+
process(inputs, outputs) {
|
|
13902
|
+
if (this.dead) return false;
|
|
13903
|
+
const i = inputs[0], o = outputs[0];
|
|
13904
|
+
if (!i || !i.length) return true;
|
|
13905
|
+
const chans = i.length;
|
|
13906
|
+
const frames = i[0].length;
|
|
13907
|
+
for (let n = 0; n < frames; n++) {
|
|
13908
|
+
const peak = this.peak(chans, i, n);
|
|
13909
|
+
const need = peak > this.ceiling ? this.ceiling / peak : 1;
|
|
13910
|
+
const m = this.windowMin(need);
|
|
13911
|
+
this.avgSum += m - this.avgRing[this.avgPos];
|
|
13912
|
+
this.avgRing[this.avgPos] = m;
|
|
13913
|
+
this.avgPos = (this.avgPos + 1) % this.avgRing.length;
|
|
13914
|
+
const target = Math.min(1, this.avgSum / this.avgRing.length);
|
|
13915
|
+
const released = this.rel + (1 - this.rel) * this.relCoef;
|
|
13916
|
+
this.rel = released < target ? released : target;
|
|
13917
|
+
if (target >= 1 && this.rel > 0.9999999) this.rel = 1;
|
|
13918
|
+
for (let c = 0; c < chans; c++) {
|
|
13919
|
+
const s = this.channel(c);
|
|
13920
|
+
o[c][n] = s.delay[s.w] * this.rel;
|
|
13921
|
+
s.delay[s.w] = i[c][n];
|
|
13922
|
+
s.w = (s.w + 1) % this.delayLen;
|
|
13923
|
+
}
|
|
13924
|
+
}
|
|
13925
|
+
return true;
|
|
13926
|
+
}
|
|
13927
|
+
}
|
|
13928
|
+
registerProcessor("hf-truepeak", HfTruePeak);
|
|
13929
|
+
`;
|
|
13930
|
+
|
|
13719
13931
|
// ../core/dist/audio/audioFxTail.js
|
|
13720
13932
|
var MAX_FX_TAIL_SECONDS = 5;
|
|
13721
13933
|
var TAIL_FLOOR = 1e-3;
|
|
@@ -13777,7 +13989,7 @@ import { join as join12 } from "path";
|
|
|
13777
13989
|
import { pathToFileURL } from "url";
|
|
13778
13990
|
|
|
13779
13991
|
// ../core/dist/generated/audio-fx-runtime-inline.js
|
|
13780
|
-
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';
|
|
13992
|
+
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';
|
|
13781
13993
|
function getAudioFxRuntimeScript() {
|
|
13782
13994
|
return AUDIO_FX_RUNTIME_IIFE;
|
|
13783
13995
|
}
|
|
@@ -15213,7 +15425,7 @@ function expectedFramesForTask(task) {
|
|
|
15213
15425
|
return Math.max(0, Math.ceil((task.endFrame - task.startFrame) / stride));
|
|
15214
15426
|
}
|
|
15215
15427
|
function synthesizeSilentWorkerExitError(result, expectedFrames) {
|
|
15216
|
-
return `worker ${result.workerId} exited without terminal error string (framesCaptured=${result.framesCaptured}, expected=${expectedFrames}, range=[${result.startFrame}, ${result.endFrame})). Field signal ts=1784042064
|
|
15428
|
+
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.`;
|
|
15217
15429
|
}
|
|
15218
15430
|
function flagSilentWorkerExits(results) {
|
|
15219
15431
|
for (const r of results) {
|
|
@@ -15510,7 +15722,7 @@ async function psnrForDiskSample(framePath, truth, workerId, idx) {
|
|
|
15510
15722
|
if (isFfmpegInfrastructureFailure(err)) {
|
|
15511
15723
|
const detail = err instanceof Error ? err.message : String(err);
|
|
15512
15724
|
throw new Error(
|
|
15513
|
-
`[Parallel] drawElement disk self-verify aborted (worker ${workerId}, frame ${idx}): ffmpeg or the \`psnr\` filter is unavailable
|
|
15725
|
+
`[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.`,
|
|
15514
15726
|
{ cause: err }
|
|
15515
15727
|
);
|
|
15516
15728
|
}
|
|
@@ -15918,7 +16130,7 @@ var DEFAULT_BLACK_ONLY_FRACTION_THRESHOLD = 1e-3;
|
|
|
15918
16130
|
function diffGpuParityFrames(a, b, options = {}) {
|
|
15919
16131
|
if (a.width !== b.width || a.height !== b.height) {
|
|
15920
16132
|
throw new Error(
|
|
15921
|
-
`diffGpuParityFrames: frame size mismatch
|
|
16133
|
+
`diffGpuParityFrames: frame size mismatch: A is ${a.width}x${a.height}, B is ${b.width}x${b.height}`
|
|
15922
16134
|
);
|
|
15923
16135
|
}
|
|
15924
16136
|
const expectedLen = a.width * a.height * 4;
|
|
@@ -15937,7 +16149,7 @@ function diffGpuParityFrames(a, b, options = {}) {
|
|
|
15937
16149
|
const contentSum = options.contentSumThreshold ?? DEFAULT_CONTENT_SUM_THRESHOLD;
|
|
15938
16150
|
if (contentSum <= blackSum) {
|
|
15939
16151
|
throw new Error(
|
|
15940
|
-
`diffGpuParityFrames: contentSumThreshold (${contentSum}) must be strictly greater than blackSumThreshold (${blackSum})
|
|
16152
|
+
`diffGpuParityFrames: contentSumThreshold (${contentSum}) must be strictly greater than blackSumThreshold (${blackSum}): overlapping thresholds would double-classify borderline pixels`
|
|
15941
16153
|
);
|
|
15942
16154
|
}
|
|
15943
16155
|
const total = a.width * a.height;
|
|
@@ -16024,7 +16236,7 @@ function verifyGpuParity(a, b, options = {}) {
|
|
|
16024
16236
|
const region = bb ? ` (bbox ${bb.x},${bb.y} ${bb.width}x${bb.height})` : "";
|
|
16025
16237
|
return {
|
|
16026
16238
|
ok: false,
|
|
16027
|
-
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}
|
|
16239
|
+
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`,
|
|
16028
16240
|
diff
|
|
16029
16241
|
};
|
|
16030
16242
|
}
|
|
@@ -16033,7 +16245,7 @@ function verifyGpuParity(a, b, options = {}) {
|
|
|
16033
16245
|
const region = bb ? ` (bbox ${bb.x},${bb.y} ${bb.width}x${bb.height})` : "";
|
|
16034
16246
|
return {
|
|
16035
16247
|
ok: false,
|
|
16036
|
-
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}
|
|
16248
|
+
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`,
|
|
16037
16249
|
diff
|
|
16038
16250
|
};
|
|
16039
16251
|
}
|
|
@@ -16409,6 +16621,7 @@ export {
|
|
|
16409
16621
|
parseImageElements,
|
|
16410
16622
|
extractVideoFramesRange,
|
|
16411
16623
|
resolvePlayableVideoDuration,
|
|
16624
|
+
isVideoPastTimelineEnd,
|
|
16412
16625
|
resolveTimelineExtractionWindow,
|
|
16413
16626
|
resolveFinalFrameExtractionWindow,
|
|
16414
16627
|
resolveVideoExtractionWindow,
|