hyperframes 0.8.186 → 0.8.189
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-UGK7AZI6.js → add-TIKUM2YX.js} +12 -12
- package/dist/{animatedGifPrep-HPZPSB43.js → animatedGifPrep-25X4G5IE.js} +7 -7
- package/dist/api.js +15 -15
- package/dist/{asset-X2GTFWOY.js → asset-TPKLIP4R.js} +2 -2
- package/dist/{assetCataloger-5NWEG4IU.js → assetCataloger-PQAIV3FI.js} +7 -7
- package/dist/{audio-7PBM5F3H.js → audio-6RLPKB5X.js} +15 -15
- package/dist/{auth-WABSMO46.js → auth-O7QSTP6Y.js} +1 -1
- package/dist/{autoUpdate-6SCMLTSC.js → autoUpdate-TIEJODP7.js} +2 -2
- package/dist/{backgroundChecks-XOGPAAR3.js → backgroundChecks-K2AXYDYG.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{batchRender-VR6IA77K.js → batchRender-ORVWYJAH.js} +4 -4
- package/dist/{beats-3IWXLMSC.js → beats-2VTB2ATR.js} +4 -4
- package/dist/{benchmark-2SSYATCX.js → benchmark-NLNBL6YR.js} +9 -9
- package/dist/{browser-PMRN345D.js → browser-M5UV6VJB.js} +3 -3
- package/dist/{bundleWithLocalizedFonts-TT32MZBO.js → bundleWithLocalizedFonts-EDD2XDS2.js} +2 -2
- package/dist/{capture-4VD6AKUJ.js → capture-JSYVNFKO.js} +10 -10
- package/dist/{capture-BDGYTE57.js → capture-WHWEE5FE.js} +2 -2
- package/dist/{captureCompositionFrame-5FVLAETA.js → captureCompositionFrame-JANGQCIA.js} +13 -13
- package/dist/{catalog-HUYZO5D7.js → catalog-MPOOKNSZ.js} +12 -12
- package/dist/{catch-up-SSGTR2ZT.js → catch-up-6KZV3UMZ.js} +4 -4
- package/dist/{check-JWUWHGM2.js → check-G273NWK7.js} +37 -36
- package/dist/{checkBrowser-X7G6YNR7.js → checkBrowser-G53E2ZQI.js} +32 -33
- package/dist/{chunk-WHMFW5ET.js → chunk-2CYM34M7.js} +3 -3
- package/dist/{chunk-HZ7M2MZA.js → chunk-37EPEFXM.js} +1 -1
- package/dist/{chunk-4XFFJAWS.js → chunk-3OLOVRWS.js} +1 -1
- package/dist/{chunk-ZIRIDSDB.js → chunk-3X6LH25W.js} +1 -1
- package/dist/{chunk-TH7J3JZ5.js → chunk-44TJOEH5.js} +92 -25
- package/dist/{chunk-K6KBOORN.js → chunk-4DZ3HBKF.js} +2 -2
- package/dist/{chunk-U7QTMRLW.js → chunk-4QFKGRTB.js} +2 -2
- package/dist/{chunk-7MVODWQF.js → chunk-5544QB3U.js} +1 -1
- package/dist/{chunk-7AUMTARQ.js → chunk-565X6Q4X.js} +3 -3
- package/dist/{chunk-J36JVBLR.js → chunk-5C7R55BJ.js} +1 -1
- package/dist/{chunk-YRAJDFIN.js → chunk-5HJJEHGE.js} +1 -1
- package/dist/{chunk-5UBZPG74.js → chunk-6A45RKAK.js} +1 -1
- package/dist/{chunk-ROWEDRVX.js → chunk-6RA4TWGV.js} +1 -1
- package/dist/{chunk-3YHGQJI6.js → chunk-74Y43RJZ.js} +3 -3
- package/dist/{chunk-4AFNEKU7.js → chunk-ANYBRVPF.js} +1 -1
- package/dist/{chunk-R6IGQ5NO.js → chunk-AQ3L7DVB.js} +2 -2
- package/dist/{chunk-LDW3KVGK.js → chunk-ASZ4IQB4.js} +2 -2
- package/dist/{chunk-DQZMJS2P.js → chunk-AYBKDHYD.js} +2 -2
- package/dist/{chunk-RVZNSKRH.js → chunk-B52BJM5K.js} +2 -2
- package/dist/{chunk-GTMBII7X.js → chunk-B634ZCY2.js} +1 -1
- package/dist/chunk-BPX7LZG6.js +3896 -0
- package/dist/{chunk-5ZMIVTHN.js → chunk-BZQLTNMB.js} +219 -79
- package/dist/{chunk-MY6CVBDD.js → chunk-CE7EQUBV.js} +1 -1
- package/dist/{chunk-ZVBEGBII.js → chunk-CSYSEPWL.js} +31 -7
- package/dist/{chunk-EZINX6HH.js → chunk-CTF7BYCV.js} +3 -3
- package/dist/{chunk-2II34JPM.js → chunk-E676Y7AY.js} +82 -29
- package/dist/{chunk-YQICCAVA.js → chunk-E7TI5PAQ.js} +1 -1
- package/dist/{chunk-TTVHYD5R.js → chunk-EI7FP7F3.js} +1 -1
- package/dist/{chunk-UJZT6HNN.js → chunk-F45HHA3M.js} +1 -1
- package/dist/{chunk-TQ2RRB5V.js → chunk-FJF5JBYU.js} +1 -1
- package/dist/{chunk-Z5233OAN.js → chunk-GBT7P3OQ.js} +5 -5
- package/dist/{chunk-NPDPDEPP.js → chunk-GWYMAII7.js} +1 -1
- package/dist/{chunk-UAJCTEK2.js → chunk-HNMDLKA4.js} +1 -1
- package/dist/{chunk-FCWFV4P7.js → chunk-HQIFMCQN.js} +2278 -2273
- package/dist/{chunk-DTXDQ6OP.js → chunk-HUG47UEE.js} +1 -1
- package/dist/{chunk-7M7TMM4S.js → chunk-HXM2ISEK.js} +264 -124
- package/dist/{chunk-WLHNIIXU.js → chunk-IFXOMIBP.js} +3 -3
- package/dist/{chunk-77QM6YRQ.js → chunk-IPEP346Y.js} +1 -1
- package/dist/{chunk-22QKQDEE.js → chunk-JIPUWKLA.js} +14 -14
- package/dist/{chunk-YNH6ERDL.js → chunk-JZDVIDFH.js} +2 -2
- package/dist/{chunk-KNLSYCI5.js → chunk-KLWUB37U.js} +7 -7
- package/dist/{chunk-PEIV2SOG.js → chunk-KS4HMJJD.js} +1 -1
- package/dist/{chunk-4NTW45W5.js → chunk-M6FHSQZ3.js} +3 -3
- package/dist/{chunk-RFJ6SX4U.js → chunk-NDBWI7OA.js} +5 -5
- package/dist/{chunk-ACLABKEF.js → chunk-NSJ5HD6A.js} +5 -5
- package/dist/{chunk-FRLTKTOR.js → chunk-OHXHBMF4.js} +1 -1
- package/dist/{chunk-7MLFYMFB.js → chunk-QMFI4N7J.js} +1 -1
- package/dist/{chunk-HOFX63F2.js → chunk-QYQ2EYOI.js} +3 -3
- package/dist/{chunk-27H2363S.js → chunk-R5FS7GFU.js} +1 -1
- package/dist/{chunk-PFD25NAC.js → chunk-RIJHDTM5.js} +4 -0
- package/dist/{chunk-FH7WODJH.js → chunk-SGFMCNLY.js} +8 -0
- package/dist/{chunk-BRTDLVK3.js → chunk-SMF6YAUL.js} +9 -11
- package/dist/{chunk-6IOBAOU7.js → chunk-T37XXYRK.js} +1 -1
- package/dist/{chunk-T4RP55P2.js → chunk-TFUAKV6O.js} +1 -1
- package/dist/{chunk-U6RXZNZE.js → chunk-TMBUBLZB.js} +55 -23
- package/dist/{chunk-5ZMXGNGC.js → chunk-TPTQHOXU.js} +9 -9
- package/dist/{chunk-U4BMRIE6.js → chunk-TREMHDWT.js} +12 -10
- package/dist/{chunk-Q447CX26.js → chunk-TSFA2Y5C.js} +60 -47
- package/dist/{chunk-Y2M6XSNK.js → chunk-VRQU564T.js} +1 -1
- package/dist/{chunk-ID5AJ4L3.js → chunk-WMJJCHVQ.js} +2 -2
- package/dist/{chunk-RNZEQTWZ.js → chunk-WYCFMNU3.js} +15 -2
- package/dist/{chunk-UJKQWGFF.js → chunk-X7HBSTCZ.js} +8 -0
- package/dist/{chunk-ZKBW7NU2.js → chunk-XJVXROOL.js} +2 -2
- package/dist/{chunk-CML25PAF.js → chunk-XNFDDVJS.js} +27 -3
- package/dist/{chunk-RPUIMFHF.js → chunk-Z2BW2WJS.js} +1 -1
- package/dist/{chunk-2PZZMAME.js → chunk-ZPMF6GAQ.js} +17 -12
- package/dist/{clean-IM2JIAUT.js → clean-PS4VYGQ6.js} +17 -17
- package/dist/cli.js +53 -53
- package/dist/{cloud-6IPBCJSX.js → cloud-E47CE5VQ.js} +4 -4
- package/dist/{cloudrun-MWZ6S2I2.js → cloudrun-3J4SQBXE.js} +5 -5
- package/dist/{compare-WD7A2TV5.js → compare-EAKHXBDO.js} +29 -29
- package/dist/{compiler-2EKSMC3I.js → compiler-23MT6CX6.js} +4 -4
- package/dist/{component-JKALWH36.js → component-QBOXYJJS.js} +3 -3
- package/dist/{compositions-643NAG4A.js → compositions-QC3EO3MJ.js} +11 -11
- package/dist/{delete-EASPXKHQ.js → delete-TXEOLPC6.js} +4 -4
- package/dist/deploy-3B3WJ6PM.js +300 -0
- package/dist/{destroy-4B5HW5FH.js → destroy-SSPAKADI.js} +13 -3
- package/dist/{dist-F6FOKRSU.js → dist-F4UJFK4S.js} +5 -3
- package/dist/{dist-LM7AVHOU.js → dist-JH32CBQL.js} +1 -1
- package/dist/{doctor-7XUVYDJQ.js → doctor-2CRFH4B3.js} +11 -11
- package/dist/{events-TULV6D3G.js → events-GUIOSBIB.js} +3 -3
- package/dist/{events-5TTPYBKG.js → events-KVTX3QFV.js} +2 -2
- package/dist/{feedback-4FTPWM6P.js → feedback-VQIOSTIB.js} +7 -7
- package/dist/{ffmpeg-MP7ZVYLZ.js → ffmpeg-BPCZGXFW.js} +7 -7
- package/dist/{figma-B7LFWYKO.js → figma-QIOKCH26.js} +3 -3
- package/dist/fontLocalizeCli.js +9 -9
- package/dist/{get-F7W4AQ2V.js → get-TU45SNXF.js} +4 -4
- package/dist/{gpuPolicy-NSNVDFXC.js → gpuPolicy-AWAJBGYY.js} +7 -7
- package/dist/{grade-compare-6QXDTLS2.js → grade-compare-LDNUXX3Q.js} +30 -30
- package/dist/{help-RJAA4O74.js → help-YUCHJKKL.js} +1 -1
- package/dist/{history-OOQBDVV5.js → history-MMHFUMIG.js} +7 -7
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +65 -64
- package/dist/hyperframes-player.global.js +3 -3
- package/dist/{info-5OGGYYLO.js → info-HBHOFSC2.js} +8 -8
- package/dist/{init-DM3TJKW2.js → init-3E4GUXIZ.js} +27 -27
- package/dist/{inspect-FECQCLS5.js → inspect-YNUPJVIH.js} +30 -30
- package/dist/{installer-WOG4VORV.js → installer-WLCRP33K.js} +5 -5
- package/dist/{keyframes-SZHWUF5Y.js → keyframes-Z47GHCGO.js} +6 -6
- package/dist/{lambda-JG2ZRGMC.js → lambda-CA4PEUO2.js} +20 -13
- package/dist/{layout-7ACF7ZKC.js → layout-YPV6PIKF.js} +30 -30
- package/dist/{lint-ZCQGMILV.js → lint-3AVVQ74A.js} +7 -7
- package/dist/{lintProject-JATQKR64.js → lintProject-XM3DLUKG.js} +1 -1
- package/dist/{list-MH2X3Y2T.js → list-JH52Y735.js} +7 -7
- package/dist/{login-CK46WX5U.js → login-G7NPJUAW.js} +4 -4
- package/dist/{manager-GXTPNJVW.js → manager-2LSMMZY4.js} +8 -8
- package/dist/{media-treatment-EH2AT34Z.js → media-treatment-BVMMD5EN.js} +13 -13
- package/dist/{models-D5XP2UK6.js → models-P3TNRVRV.js} +11 -11
- package/dist/{motionShot-UR7D3SRQ.js → motionShot-LK62QHNE.js} +18 -18
- package/dist/{normalize-audio-635JOR5W.js → normalize-audio-Y7BAQ7AR.js} +6 -6
- package/dist/{open-6WE5WFVM.js → open-CCFFLAT7.js} +12 -12
- package/dist/{parakeetRunner-STHZPG32.js → parakeetRunner-N2BWSOSD.js} +12 -12
- package/dist/{pipeline-LVEKRETU.js → pipeline-3VT2DQGU.js} +8 -8
- package/dist/{play-W6TDW4FE.js → play-G4I5IFHO.js} +20 -20
- package/dist/{policies-XXNGXWHV.js → policies-MBWGLMWB.js} +17 -0
- package/dist/{preflight-LIMQXLR7.js → preflight-2KU2QLCZ.js} +8 -8
- package/dist/{present-RX5QX6UR.js → present-EJ7XHGQO.js} +16 -16
- package/dist/{preview-KIWTWK63.js → preview-O23BYFAY.js} +18 -18
- package/dist/{producer-TSNKSCUM.js → producer-UJQVLKLP.js} +1 -1
- package/dist/{progress-NHJIHAAG.js → progress-PX3PVAIM.js} +5 -3
- package/dist/{projectConfig-NCP7JRH4.js → projectConfig-QFVIBT37.js} +7 -7
- package/dist/{publication-5W5UZWGC.js → publication-4UYGFB2X.js} +3 -3
- package/dist/{publish-7WR4XJ6I.js → publish-MHLVJLCR.js} +9 -9
- package/dist/{remove-background-43JGBRWQ.js → remove-background-N7AHTZWP.js} +9 -9
- package/dist/{render-7T44HIM6.js → render-6HQV4LZJ.js} +11 -9
- package/dist/{render-WHMLYIOI.js → render-CDATEVDJ.js} +31 -22
- package/dist/{render-456HCO6O.js → render-EI2ODKOP.js} +45 -29
- package/dist/{render-batch-LURBJTBV.js → render-batch-F7HMC7XH.js} +7 -7
- package/dist/renderSetupWorker.js +1 -1
- package/dist/{resolver-MBIW4VC4.js → resolver-63DB5JCY.js} +5 -5
- package/dist/{sam-MC24SZHC.js → sam-FHJ7IL7T.js} +3 -1
- package/dist/{sherpa-5J2AALED.js → sherpa-AK3MTPL3.js} +11 -11
- package/dist/{sites-MAVH57MO.js → sites-5PWHAMEG.js} +1 -1
- package/dist/skills/hyperframes/references/motion.md +1 -0
- package/dist/{skills-UHOXOGGF.js → skills-EVIDL7PV.js} +5 -5
- package/dist/{skillsUpdateCheck-PDZIFUJE.js → skillsUpdateCheck-YCNJYDJO.js} +3 -3
- package/dist/{snapshot-PFV7ALLH.js → snapshot-WSYZKR3C.js} +35 -35
- package/dist/{src-XK5YIR6L.js → src-KTD6WTEV.js} +8 -8
- package/dist/{src-DCVSEODX.js → src-YNXBU4ZQ.js} +12 -6
- package/dist/{state-ORRPOLMQ.js → state-JPNCCXAV.js} +3 -1
- package/dist/{staticProjectServer-4IE4G4JO.js → staticProjectServer-D2D3BKBP.js} +18 -16
- package/dist/studio/assets/{hyperframes-player-B_skzvYC.js → hyperframes-player-CkK8Zijf.js} +1 -1
- package/dist/studio/assets/{index-CgLbvCUo.js → index-ATnRoBug.js} +1 -1
- package/dist/studio/assets/{index-Dn5FAk2e.js → index-B2JcCaRF.js} +130 -130
- package/dist/studio/index.html +1 -1
- package/dist/{studio-api-JMWYU7KF.js → studio-api-3ZGQPE5J.js} +10 -10
- package/dist/{studioServer-BWLSTGIG.js → studioServer-VWM4J7ZP.js} +48 -48
- package/dist/{telemetry-JCU3AV7W.js → telemetry-BEVI64BB.js} +3 -3
- package/dist/templates/_shared/AGENTS.md +5 -0
- package/dist/templates/_shared/CLAUDE.md +5 -0
- package/dist/{timeline-BCIFQYOM.js → timeline-P57FUXJQ.js} +18 -18
- package/dist/{tokens-7IXKALMQ.js → tokens-OUQY6LPD.js} +2 -2
- package/dist/{transcribe-ZUQD3B32.js → transcribe-CK66VIU4.js} +10 -10
- package/dist/{transcribe-HW7HGJ4V.js → transcribe-QUPX67KC.js} +16 -16
- package/dist/{updateCheck-Z6LNEK7B.js → updateCheck-XZ43CH3L.js} +2 -2
- package/dist/{upgrade-JYH72JGL.js → upgrade-7QQNGI7I.js} +2 -2
- package/dist/{urlDownloader-WKLFHSUH.js → urlDownloader-ESAEL6YX.js} +6 -6
- package/dist/{validate-QCC27YYI.js → validate-5GZ6KFRU.js} +24 -24
- package/dist/{video-EZ7ONY6L.js → video-AQNVR6PE.js} +7 -7
- package/dist/warmCheck.js +37 -38
- package/package.json +18 -2
- package/dist/chunk-KANNRHY7.js +0 -53
- package/dist/chunk-UOZX5W5T.js +0 -3852
- package/dist/deploy-CDKCQ5MX.js +0 -196
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
renderFrameIdForRenderId,
|
|
27
27
|
resolveAuthoredTimingWindow,
|
|
28
28
|
toFps
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-WYCFMNU3.js";
|
|
30
30
|
import {
|
|
31
31
|
AUDIO_GROUP_RENDER_ID_ATTR,
|
|
32
32
|
COLOR_GRADING_SOURCE_HIDDEN_ATTR,
|
|
@@ -43,7 +43,10 @@ import {
|
|
|
43
43
|
acceleratedCanvasTrackerSource,
|
|
44
44
|
enabledAudioFxNodes,
|
|
45
45
|
fxAutomationTarget,
|
|
46
|
+
gainedVolume,
|
|
47
|
+
gainedVolumePoints,
|
|
46
48
|
injectScriptsIntoHtml,
|
|
49
|
+
isBentSegment,
|
|
47
50
|
normalizeAudioFxParams,
|
|
48
51
|
normalizePlaybackRate,
|
|
49
52
|
normalizeRateSpec,
|
|
@@ -58,12 +61,14 @@ import {
|
|
|
58
61
|
serializeAutomation,
|
|
59
62
|
shiftRateLane,
|
|
60
63
|
sourceTimeAt,
|
|
61
|
-
timeAtSourceTime
|
|
62
|
-
|
|
64
|
+
timeAtSourceTime,
|
|
65
|
+
volumeSegmentSampleCount
|
|
66
|
+
} from "./chunk-BPX7LZG6.js";
|
|
63
67
|
import {
|
|
64
68
|
clampAudioGain,
|
|
69
|
+
clipGain,
|
|
65
70
|
isAudibleVideoElement
|
|
66
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-X7HBSTCZ.js";
|
|
67
72
|
import {
|
|
68
73
|
redactTelemetryString,
|
|
69
74
|
redactUrlQueryStrings
|
|
@@ -78,7 +83,7 @@ import {
|
|
|
78
83
|
} from "./chunk-4UMPIPCQ.js";
|
|
79
84
|
import {
|
|
80
85
|
resolveReferencedStart
|
|
81
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-TSFA2Y5C.js";
|
|
82
87
|
import {
|
|
83
88
|
FFMPEG_PATH_ENV,
|
|
84
89
|
FFPROBE_PATH_ENV,
|
|
@@ -2991,6 +2996,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
|
|
|
2991
2996
|
async (items, visualProperties, colorGradingSourceHiddenAttr) => {
|
|
2992
2997
|
const injectedIds = [];
|
|
2993
2998
|
const pendingDecodes = [];
|
|
2999
|
+
const imagesToPublish = [];
|
|
2994
3000
|
const replacementLayoutProperties = /* @__PURE__ */ new Set([
|
|
2995
3001
|
"width",
|
|
2996
3002
|
"height",
|
|
@@ -3062,6 +3068,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
|
|
|
3062
3068
|
img.style.objectFit = computedStyle.objectFit;
|
|
3063
3069
|
img.style.objectPosition = computedStyle.objectPosition;
|
|
3064
3070
|
img.style.zIndex = computedStyle.zIndex;
|
|
3071
|
+
img.style.setProperty("visibility", "hidden", "important");
|
|
3065
3072
|
img.decoding = "sync";
|
|
3066
3073
|
if (img.getAttribute("src") !== item.dataUri) {
|
|
3067
3074
|
if (item.crossOrigin) img.crossOrigin = item.crossOrigin;
|
|
@@ -3077,7 +3084,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
|
|
|
3077
3084
|
);
|
|
3078
3085
|
}
|
|
3079
3086
|
img.style.opacity = String(computedOpacity);
|
|
3080
|
-
img
|
|
3087
|
+
imagesToPublish.push(img);
|
|
3081
3088
|
video.style.setProperty("visibility", "hidden", "important");
|
|
3082
3089
|
video.style.setProperty("pointer-events", "none", "important");
|
|
3083
3090
|
injectedIds.push(item.videoId);
|
|
@@ -3085,6 +3092,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
|
|
|
3085
3092
|
if (pendingDecodes.length > 0) {
|
|
3086
3093
|
await Promise.all(pendingDecodes);
|
|
3087
3094
|
}
|
|
3095
|
+
for (const img of imagesToPublish) img.style.visibility = "visible";
|
|
3088
3096
|
if (injectedIds.length > 0) {
|
|
3089
3097
|
const redraw = window.__hf?.colorGrading?.redraw;
|
|
3090
3098
|
redraw?.();
|
|
@@ -3123,7 +3131,7 @@ async function syncVideoFrameVisibility(page, activeVideoIds) {
|
|
|
3123
3131
|
const img = video.nextElementSibling;
|
|
3124
3132
|
const hasImg = img && img.classList.contains("__render_frame__");
|
|
3125
3133
|
const ancestorHidden = isVisualAncestorHidden(video);
|
|
3126
|
-
const visible = active.has(video.id) && !ancestorHidden;
|
|
3134
|
+
const visible = active.has(window.__hfMediaId?.(video) || video.id) && !ancestorHidden;
|
|
3127
3135
|
if (visible) {
|
|
3128
3136
|
video.style.setProperty("visibility", "hidden", "important");
|
|
3129
3137
|
video.style.setProperty("pointer-events", "none", "important");
|
|
@@ -7634,6 +7642,57 @@ function getCapturePerfSummary(session) {
|
|
|
7634
7642
|
};
|
|
7635
7643
|
}
|
|
7636
7644
|
|
|
7645
|
+
// ../engine/src/services/extractedFrameIndex.ts
|
|
7646
|
+
import { readdirSync as readdirSync2 } from "fs";
|
|
7647
|
+
import { join as join4 } from "path";
|
|
7648
|
+
var FRAME_FILENAME_PREFIX = "frame_";
|
|
7649
|
+
function frameSequencePath(dir, framePattern) {
|
|
7650
|
+
return join4(dir.replaceAll("%", "%%"), framePattern);
|
|
7651
|
+
}
|
|
7652
|
+
var ExtractedFrameSequenceError = class extends Error {
|
|
7653
|
+
constructor(message) {
|
|
7654
|
+
super(message);
|
|
7655
|
+
this.name = "ExtractedFrameSequenceError";
|
|
7656
|
+
}
|
|
7657
|
+
};
|
|
7658
|
+
function extractedFrameIndex(file, format) {
|
|
7659
|
+
const match = new RegExp(`^${FRAME_FILENAME_PREFIX}(\\d+)\\.${format}$`).exec(file);
|
|
7660
|
+
if (!match) {
|
|
7661
|
+
throw new ExtractedFrameSequenceError(`Invalid extracted frame filename: ${file}`);
|
|
7662
|
+
}
|
|
7663
|
+
const ordinal = Number.parseInt(match[1], 10);
|
|
7664
|
+
if (!Number.isSafeInteger(ordinal) || ordinal < 1) {
|
|
7665
|
+
throw new ExtractedFrameSequenceError(`Invalid extracted frame ordinal: ${file}`);
|
|
7666
|
+
}
|
|
7667
|
+
return ordinal - 1;
|
|
7668
|
+
}
|
|
7669
|
+
function framePathsFromDirectory(outputDir, format, allowGaps = false) {
|
|
7670
|
+
const suffix = `.${format}`;
|
|
7671
|
+
const indexed = /* @__PURE__ */ new Map();
|
|
7672
|
+
for (const file of readdirSync2(outputDir)) {
|
|
7673
|
+
if (!file.startsWith(FRAME_FILENAME_PREFIX) || !file.endsWith(suffix)) continue;
|
|
7674
|
+
const index = extractedFrameIndex(file, format);
|
|
7675
|
+
if (indexed.has(index)) {
|
|
7676
|
+
throw new ExtractedFrameSequenceError(
|
|
7677
|
+
`Duplicate extracted frame index ${index}: ${indexed.get(index)} and ${file}`
|
|
7678
|
+
);
|
|
7679
|
+
}
|
|
7680
|
+
indexed.set(index, join4(outputDir, file));
|
|
7681
|
+
}
|
|
7682
|
+
if (allowGaps) return new Map([...indexed].sort(([a], [b]) => a - b));
|
|
7683
|
+
const ordered = /* @__PURE__ */ new Map();
|
|
7684
|
+
for (let index = 0; index < indexed.size; index += 1) {
|
|
7685
|
+
const path = indexed.get(index);
|
|
7686
|
+
if (!path) {
|
|
7687
|
+
throw new ExtractedFrameSequenceError(
|
|
7688
|
+
`Missing extracted frame index ${index} in ${outputDir}`
|
|
7689
|
+
);
|
|
7690
|
+
}
|
|
7691
|
+
ordered.set(index, path);
|
|
7692
|
+
}
|
|
7693
|
+
return ordered;
|
|
7694
|
+
}
|
|
7695
|
+
|
|
7637
7696
|
// ../engine/src/services/chunkEncoder.ts
|
|
7638
7697
|
import {
|
|
7639
7698
|
closeSync,
|
|
@@ -7644,12 +7703,12 @@ import {
|
|
|
7644
7703
|
mkdtempSync,
|
|
7645
7704
|
openSync,
|
|
7646
7705
|
readFileSync as readFileSync3,
|
|
7647
|
-
readdirSync as
|
|
7706
|
+
readdirSync as readdirSync3,
|
|
7648
7707
|
statSync as statSync3,
|
|
7649
7708
|
writeFileSync as writeFileSync2,
|
|
7650
7709
|
writeSync
|
|
7651
7710
|
} from "fs";
|
|
7652
|
-
import { join as
|
|
7711
|
+
import { join as join6, dirname as dirname2, extname } from "path";
|
|
7653
7712
|
|
|
7654
7713
|
// ../engine/src/utils/gpuEncoder.ts
|
|
7655
7714
|
import { spawn } from "child_process";
|
|
@@ -8934,9 +8993,15 @@ async function analyzeKeyframeIntervalsUncached(filePath, videoStreamStartSecond
|
|
|
8934
8993
|
};
|
|
8935
8994
|
}
|
|
8936
8995
|
|
|
8996
|
+
// ../engine/src/services/softwareEncoderThreads.ts
|
|
8997
|
+
import { availableParallelism } from "os";
|
|
8998
|
+
function appendSoftwareEncoderThreadsArg(args, parallelism = availableParallelism()) {
|
|
8999
|
+
args.push("-threads", String(Math.max(1, Math.floor(parallelism))));
|
|
9000
|
+
}
|
|
9001
|
+
|
|
8937
9002
|
// ../engine/src/utils/renderProvenance.ts
|
|
8938
9003
|
import { readFileSync as readFileSync2 } from "fs";
|
|
8939
|
-
import { dirname, join as
|
|
9004
|
+
import { dirname, join as join5 } from "path";
|
|
8940
9005
|
import { fileURLToPath } from "url";
|
|
8941
9006
|
var PROVENANCE_RENDERER_TAG = "hyperframes_renderer";
|
|
8942
9007
|
var PROVENANCE_VERSION_TAG = "hyperframes_version";
|
|
@@ -8947,7 +9012,7 @@ var MAX_WALK_DEPTH = 12;
|
|
|
8947
9012
|
function resolveOwnVersionFrom(startDir) {
|
|
8948
9013
|
let dir = startDir;
|
|
8949
9014
|
for (let depth = 0; depth < MAX_WALK_DEPTH; depth++) {
|
|
8950
|
-
const candidate =
|
|
9015
|
+
const candidate = join5(dir, "package.json");
|
|
8951
9016
|
let pkg;
|
|
8952
9017
|
try {
|
|
8953
9018
|
pkg = JSON.parse(readFileSync2(candidate, "utf8"));
|
|
@@ -9158,6 +9223,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
9158
9223
|
} else {
|
|
9159
9224
|
const encoderName = codec === "h264" ? "libx264" : "libx265";
|
|
9160
9225
|
args.push("-c:v", encoderName, "-preset", preset);
|
|
9226
|
+
appendSoftwareEncoderThreadsArg(args);
|
|
9161
9227
|
if (bitrate) args.push("-b:v", bitrate);
|
|
9162
9228
|
else args.push("-crf", String(quality));
|
|
9163
9229
|
const gop = resolveLockedGopSize(options);
|
|
@@ -9280,7 +9346,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
9280
9346
|
const startTime = Date.now();
|
|
9281
9347
|
const outputDir = dirname2(outputPath);
|
|
9282
9348
|
if (!existsSync5(outputDir)) mkdirSync2(outputDir, { recursive: true });
|
|
9283
|
-
const files =
|
|
9349
|
+
const files = readdirSync3(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
|
|
9284
9350
|
const frameCount = files.length;
|
|
9285
9351
|
if (frameCount === 0) {
|
|
9286
9352
|
return {
|
|
@@ -9296,7 +9362,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
|
|
|
9296
9362
|
if (options.useGpu) {
|
|
9297
9363
|
gpuEncoder = await getCachedGpuEncoder();
|
|
9298
9364
|
}
|
|
9299
|
-
const inputPath =
|
|
9365
|
+
const inputPath = frameSequencePath(framesDir, framePattern);
|
|
9300
9366
|
const inputArgs = ["-framerate", fpsToFfmpegArg(options.fps), "-i", inputPath];
|
|
9301
9367
|
const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
|
|
9302
9368
|
const encodeTimeout = config?.ffmpegEncodeTimeout ?? DEFAULT_CONFIG.ffmpegEncodeTimeout;
|
|
@@ -9352,7 +9418,7 @@ function buildConcatArgs(concatListPath, outputPath) {
|
|
|
9352
9418
|
var concatListSeq = 0;
|
|
9353
9419
|
function writeConcatList(dir, inputPaths) {
|
|
9354
9420
|
concatListSeq += 1;
|
|
9355
|
-
const listPath =
|
|
9421
|
+
const listPath = join6(dir, `concat-list-${process.pid}-${concatListSeq}.txt`);
|
|
9356
9422
|
const body = inputPaths.map((p) => `file '${p.replace(/'/g, "'\\''")}'`).join("\n");
|
|
9357
9423
|
writeFileSync2(listPath, body, "utf-8");
|
|
9358
9424
|
return listPath;
|
|
@@ -9382,7 +9448,7 @@ async function concatVideoFiles(inputPaths, outputPath, signal, config) {
|
|
|
9382
9448
|
}
|
|
9383
9449
|
async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, options, chunkSizeFrames, signal, config, onFramesEncoded) {
|
|
9384
9450
|
const start = Date.now();
|
|
9385
|
-
const files =
|
|
9451
|
+
const files = readdirSync3(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
|
|
9386
9452
|
if (files.length === 0) {
|
|
9387
9453
|
return {
|
|
9388
9454
|
success: false,
|
|
@@ -9396,7 +9462,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
9396
9462
|
const chunkSize = Math.max(30, Math.floor(chunkSizeFrames));
|
|
9397
9463
|
const chunkCount = Math.ceil(files.length / chunkSize);
|
|
9398
9464
|
mkdirSync2(dirname2(outputPath), { recursive: true });
|
|
9399
|
-
const chunkDir = mkdtempSync(
|
|
9465
|
+
const chunkDir = mkdtempSync(join6(dirname2(outputPath), "chunk-encode-"));
|
|
9400
9466
|
const chunkPaths = [];
|
|
9401
9467
|
for (let i = 0; i < chunkCount; i++) {
|
|
9402
9468
|
if (signal?.aborted) {
|
|
@@ -9412,8 +9478,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
|
|
|
9412
9478
|
const startNumber = i * chunkSize;
|
|
9413
9479
|
const framesInChunk = Math.min(chunkSize, files.length - startNumber);
|
|
9414
9480
|
const ext = outputPath.endsWith(".webm") ? ".webm" : outputPath.endsWith(".mov") ? ".mov" : ".mp4";
|
|
9415
|
-
const chunkPath =
|
|
9416
|
-
const inputPath =
|
|
9481
|
+
const chunkPath = join6(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
|
|
9482
|
+
const inputPath = frameSequencePath(framesDir, framePattern);
|
|
9417
9483
|
const inputArgs = [
|
|
9418
9484
|
"-framerate",
|
|
9419
9485
|
fpsToFfmpegArg(options.fps),
|
|
@@ -9582,14 +9648,14 @@ async function packageHls(videoPath, audioPath, outputDir, options) {
|
|
|
9582
9648
|
"-master_pl_name",
|
|
9583
9649
|
HLS_MASTER_PLAYLIST,
|
|
9584
9650
|
"-hls_segment_filename",
|
|
9585
|
-
|
|
9651
|
+
join6(outputDir, "%v_%05d.ts"),
|
|
9586
9652
|
// Without these the mpegts muxer starts the stream at PTS 1.4 s.
|
|
9587
9653
|
"-muxdelay",
|
|
9588
9654
|
"0",
|
|
9589
9655
|
"-muxpreload",
|
|
9590
9656
|
"0"
|
|
9591
9657
|
);
|
|
9592
|
-
args.push("-y",
|
|
9658
|
+
args.push("-y", join6(outputDir, "%v.m3u8"));
|
|
9593
9659
|
const processTimeout = options.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
9594
9660
|
const result = await runFfmpeg(args, { signal, timeout: processTimeout });
|
|
9595
9661
|
if (signal?.aborted) {
|
|
@@ -9601,7 +9667,7 @@ async function packageHls(videoPath, audioPath, outputDir, options) {
|
|
|
9601
9667
|
};
|
|
9602
9668
|
}
|
|
9603
9669
|
if (result.success && hasAudio) {
|
|
9604
|
-
const masterPath =
|
|
9670
|
+
const masterPath = join6(outputDir, HLS_MASTER_PLAYLIST);
|
|
9605
9671
|
let master;
|
|
9606
9672
|
try {
|
|
9607
9673
|
master = openSync(masterPath, "r+", 384);
|
|
@@ -9862,6 +9928,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
9862
9928
|
} else {
|
|
9863
9929
|
const encoderName = codec === "h264" ? "libx264" : "libx265";
|
|
9864
9930
|
args.push("-c:v", encoderName, "-preset", preset);
|
|
9931
|
+
appendSoftwareEncoderThreadsArg(args);
|
|
9865
9932
|
if (bitrate) args.push("-b:v", bitrate);
|
|
9866
9933
|
else args.push("-crf", String(quality));
|
|
9867
9934
|
const lockedGop = resolveLockedGopSize(options);
|
|
@@ -10222,7 +10289,7 @@ function isKnownInactiveTimelineWindow(element, resolvedStart) {
|
|
|
10222
10289
|
// ../engine/src/utils/alphaPlaneProbe.ts
|
|
10223
10290
|
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync } from "fs";
|
|
10224
10291
|
import { tmpdir as tmpdir2 } from "os";
|
|
10225
|
-
import { join as
|
|
10292
|
+
import { join as join7 } from "path";
|
|
10226
10293
|
var BYTES_PER_SAMPLE_FRAME = 8 * 8 * 4;
|
|
10227
10294
|
var MAX_SAMPLE_BYTES = BYTES_PER_SAMPLE_FRAME * 3;
|
|
10228
10295
|
function sampledRgbaAlphaIsFullyOpaque(buf) {
|
|
@@ -10237,8 +10304,8 @@ function sampledRgbaAlphaIsFullyOpaque(buf) {
|
|
|
10237
10304
|
async function probeInputAlphaPlane(videoPath, decoder, signal) {
|
|
10238
10305
|
let probeDir;
|
|
10239
10306
|
try {
|
|
10240
|
-
probeDir = mkdtempSync2(
|
|
10241
|
-
const samplePath =
|
|
10307
|
+
probeDir = mkdtempSync2(join7(tmpdir2(), "hf-alpha-probe-"));
|
|
10308
|
+
const samplePath = join7(probeDir, "alpha.raw");
|
|
10242
10309
|
const result = await runFfmpeg(
|
|
10243
10310
|
[
|
|
10244
10311
|
"-v",
|
|
@@ -10284,7 +10351,7 @@ import {
|
|
|
10284
10351
|
mkdirSync as mkdirSync4,
|
|
10285
10352
|
lstatSync,
|
|
10286
10353
|
openSync as openSync2,
|
|
10287
|
-
readdirSync as
|
|
10354
|
+
readdirSync as readdirSync4,
|
|
10288
10355
|
rmdirSync,
|
|
10289
10356
|
rmSync as rmSync2,
|
|
10290
10357
|
statSync as statSync5,
|
|
@@ -10292,7 +10359,7 @@ import {
|
|
|
10292
10359
|
} from "fs";
|
|
10293
10360
|
import { createHash, randomUUID as randomUUID2 } from "crypto";
|
|
10294
10361
|
import { BlockList, isIP } from "net";
|
|
10295
|
-
import { dirname as dirname4, extname as extname2, join as
|
|
10362
|
+
import { dirname as dirname4, extname as extname2, join as join8 } from "path";
|
|
10296
10363
|
import { Readable, Transform } from "stream";
|
|
10297
10364
|
import { pipeline } from "stream/promises";
|
|
10298
10365
|
var inFlightDownloads = /* @__PURE__ */ new Map();
|
|
@@ -10462,7 +10529,7 @@ function sameCacheLockStatGeneration(left, right) {
|
|
|
10462
10529
|
function observeCachePathLock(lockPath) {
|
|
10463
10530
|
for (let pass = 0; pass < 3; pass += 1) {
|
|
10464
10531
|
const before = lstatSync(lockPath);
|
|
10465
|
-
const owner =
|
|
10532
|
+
const owner = readdirSync4(lockPath).find((name) => name.startsWith(CACHE_LOCK_OWNER_PREFIX));
|
|
10466
10533
|
const after = lstatSync(lockPath);
|
|
10467
10534
|
if (sameCacheLockStatGeneration(before, after)) return { stats: after, owner };
|
|
10468
10535
|
}
|
|
@@ -10484,7 +10551,7 @@ function removeCacheLockDirectoryIfEmpty(lockPath) {
|
|
|
10484
10551
|
}
|
|
10485
10552
|
function releaseOwnedCachePathLock(lockPath, owner) {
|
|
10486
10553
|
try {
|
|
10487
|
-
rmdirSync(
|
|
10554
|
+
rmdirSync(join8(lockPath, owner));
|
|
10488
10555
|
} catch (error) {
|
|
10489
10556
|
if (error.code === "ENOENT") return;
|
|
10490
10557
|
throw error;
|
|
@@ -10525,12 +10592,12 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
|
|
|
10525
10592
|
if (createdLock) {
|
|
10526
10593
|
const owner = `${CACHE_LOCK_OWNER_PREFIX}${randomUUID2()}`;
|
|
10527
10594
|
try {
|
|
10528
|
-
mkdirSync4(
|
|
10529
|
-
const entries =
|
|
10595
|
+
mkdirSync4(join8(lockPath, owner));
|
|
10596
|
+
const entries = readdirSync4(lockPath);
|
|
10530
10597
|
if (entries.length === 1 && entries[0] === owner) {
|
|
10531
10598
|
return () => releaseOwnedCachePathLock(lockPath, owner);
|
|
10532
10599
|
}
|
|
10533
|
-
rmdirSync(
|
|
10600
|
+
rmdirSync(join8(lockPath, owner));
|
|
10534
10601
|
removeCacheLockDirectoryIfEmpty(lockPath);
|
|
10535
10602
|
continue;
|
|
10536
10603
|
} catch (error) {
|
|
@@ -10548,7 +10615,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
|
|
|
10548
10615
|
if (Date.now() - observedLock.stats.mtimeMs > CACHE_LOCK_STALE_MS) {
|
|
10549
10616
|
if (observedLock.owner) {
|
|
10550
10617
|
try {
|
|
10551
|
-
rmdirSync(
|
|
10618
|
+
rmdirSync(join8(lockPath, observedLock.owner));
|
|
10552
10619
|
} catch (error) {
|
|
10553
10620
|
if (error.code === "ENOENT") continue;
|
|
10554
10621
|
throw error;
|
|
@@ -10556,7 +10623,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
|
|
|
10556
10623
|
removeCacheLockDirectoryIfEmpty(lockPath);
|
|
10557
10624
|
continue;
|
|
10558
10625
|
}
|
|
10559
|
-
const reclaimPath =
|
|
10626
|
+
const reclaimPath = join8(lockPath, CACHE_LOCK_RECLAIM_NAME);
|
|
10560
10627
|
try {
|
|
10561
10628
|
mkdirSync4(reclaimPath);
|
|
10562
10629
|
} catch (error) {
|
|
@@ -10996,8 +11063,8 @@ function emitDownloadTelemetry(options, event) {
|
|
|
10996
11063
|
}
|
|
10997
11064
|
}
|
|
10998
11065
|
async function runDownloadAttempt(url, localPath, timeoutMs, attempt, options, signal) {
|
|
10999
|
-
const attemptDir = mkdtempSync3(
|
|
11000
|
-
const partialPath =
|
|
11066
|
+
const attemptDir = mkdtempSync3(join8(dirname4(localPath), ".hf-download-"));
|
|
11067
|
+
const partialPath = join8(attemptDir, "payload");
|
|
11001
11068
|
const controller = new AbortController();
|
|
11002
11069
|
let timedOut = false;
|
|
11003
11070
|
let callerAborted = signal?.aborted ?? false;
|
|
@@ -11233,7 +11300,7 @@ async function downloadToTemp(url, destDir, timeoutMs = 3e5, signal, onTransient
|
|
|
11233
11300
|
mkdirSync4(destDir, { recursive: true });
|
|
11234
11301
|
}
|
|
11235
11302
|
const filename = getFilenameFromUrl(url, validationScope);
|
|
11236
|
-
const localPath =
|
|
11303
|
+
const localPath = join8(destDir, filename);
|
|
11237
11304
|
const downloadPromise = (async () => {
|
|
11238
11305
|
const cacheStartedAt = Date.now();
|
|
11239
11306
|
const reused = await reuseOrInvalidateCachedFile(
|
|
@@ -11317,56 +11384,6 @@ import {
|
|
|
11317
11384
|
writeFileSync as writeFileSync3
|
|
11318
11385
|
} from "fs";
|
|
11319
11386
|
import { join as join9 } from "path";
|
|
11320
|
-
|
|
11321
|
-
// ../engine/src/services/extractedFrameIndex.ts
|
|
11322
|
-
import { readdirSync as readdirSync4 } from "fs";
|
|
11323
|
-
import { join as join8 } from "path";
|
|
11324
|
-
var FRAME_FILENAME_PREFIX = "frame_";
|
|
11325
|
-
var ExtractedFrameSequenceError = class extends Error {
|
|
11326
|
-
constructor(message) {
|
|
11327
|
-
super(message);
|
|
11328
|
-
this.name = "ExtractedFrameSequenceError";
|
|
11329
|
-
}
|
|
11330
|
-
};
|
|
11331
|
-
function extractedFrameIndex(file, format) {
|
|
11332
|
-
const match = new RegExp(`^${FRAME_FILENAME_PREFIX}(\\d+)\\.${format}$`).exec(file);
|
|
11333
|
-
if (!match) {
|
|
11334
|
-
throw new ExtractedFrameSequenceError(`Invalid extracted frame filename: ${file}`);
|
|
11335
|
-
}
|
|
11336
|
-
const ordinal = Number.parseInt(match[1], 10);
|
|
11337
|
-
if (!Number.isSafeInteger(ordinal) || ordinal < 1) {
|
|
11338
|
-
throw new ExtractedFrameSequenceError(`Invalid extracted frame ordinal: ${file}`);
|
|
11339
|
-
}
|
|
11340
|
-
return ordinal - 1;
|
|
11341
|
-
}
|
|
11342
|
-
function framePathsFromDirectory(outputDir, format, allowGaps = false) {
|
|
11343
|
-
const suffix = `.${format}`;
|
|
11344
|
-
const indexed = /* @__PURE__ */ new Map();
|
|
11345
|
-
for (const file of readdirSync4(outputDir)) {
|
|
11346
|
-
if (!file.startsWith(FRAME_FILENAME_PREFIX) || !file.endsWith(suffix)) continue;
|
|
11347
|
-
const index = extractedFrameIndex(file, format);
|
|
11348
|
-
if (indexed.has(index)) {
|
|
11349
|
-
throw new ExtractedFrameSequenceError(
|
|
11350
|
-
`Duplicate extracted frame index ${index}: ${indexed.get(index)} and ${file}`
|
|
11351
|
-
);
|
|
11352
|
-
}
|
|
11353
|
-
indexed.set(index, join8(outputDir, file));
|
|
11354
|
-
}
|
|
11355
|
-
if (allowGaps) return new Map([...indexed].sort(([a], [b]) => a - b));
|
|
11356
|
-
const ordered = /* @__PURE__ */ new Map();
|
|
11357
|
-
for (let index = 0; index < indexed.size; index += 1) {
|
|
11358
|
-
const path = indexed.get(index);
|
|
11359
|
-
if (!path) {
|
|
11360
|
-
throw new ExtractedFrameSequenceError(
|
|
11361
|
-
`Missing extracted frame index ${index} in ${outputDir}`
|
|
11362
|
-
);
|
|
11363
|
-
}
|
|
11364
|
-
ordered.set(index, path);
|
|
11365
|
-
}
|
|
11366
|
-
return ordered;
|
|
11367
|
-
}
|
|
11368
|
-
|
|
11369
|
-
// ../engine/src/services/extractionCache.ts
|
|
11370
11387
|
var COMPLETE_SENTINEL = ".hf-complete";
|
|
11371
11388
|
var GC_MARKER = ".hf-last-gc";
|
|
11372
11389
|
var SCHEMA_PREFIX = "hfcache-v8-";
|
|
@@ -12118,7 +12135,7 @@ async function extractVideoFramesRangeUnredacted(videoPath, videoId, startTime,
|
|
|
12118
12135
|
}
|
|
12119
12136
|
const format = resolveFrameFormat(metadata, options.format);
|
|
12120
12137
|
const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${format}`;
|
|
12121
|
-
const outputPattern =
|
|
12138
|
+
const outputPattern = frameSequencePath(videoOutputDir, framePattern);
|
|
12122
12139
|
const isHdr = isHdrColorSpace(metadata.colorSpace);
|
|
12123
12140
|
const isMacOS = process.platform === "darwin";
|
|
12124
12141
|
const toneMappedToSdr = options.toneMapHdrToSdr === true && isHdr && await toneMapsWithZscale();
|
|
@@ -12463,6 +12480,7 @@ function resolveVideoExtractionDuration(video, metadata, timelineEnd) {
|
|
|
12463
12480
|
return resolveVideoExtractionWindow(video, metadata, timelineEnd).durationSeconds;
|
|
12464
12481
|
}
|
|
12465
12482
|
var ALPHA_CAPABLE_CODECS = /* @__PURE__ */ new Set(["vp9", "vp8", "prores"]);
|
|
12483
|
+
var SIDECAR_ALPHA_CODECS = /* @__PURE__ */ new Set(["vp9", "vp8"]);
|
|
12466
12484
|
function codecMayHaveAlpha(codec) {
|
|
12467
12485
|
return ALPHA_CAPABLE_CODECS.has((codec ?? "").toLowerCase());
|
|
12468
12486
|
}
|
|
@@ -12470,7 +12488,8 @@ function decoderForCodec(codec) {
|
|
|
12470
12488
|
return alphaDecoderFor(codec) ?? (codec ?? "").toLowerCase();
|
|
12471
12489
|
}
|
|
12472
12490
|
function resolveFrameFormat(metadata, requested) {
|
|
12473
|
-
if (metadata.hasAlpha ||
|
|
12491
|
+
if (metadata.hasAlpha || SIDECAR_ALPHA_CODECS.has((metadata.videoCodec ?? "").toLowerCase()))
|
|
12492
|
+
return "png";
|
|
12474
12493
|
if (requested === "png" || requested === "jpg") return requested;
|
|
12475
12494
|
return "jpg";
|
|
12476
12495
|
}
|
|
@@ -14054,6 +14073,8 @@ async function encodeAac(input, outputPath, totalDuration, {
|
|
|
14054
14073
|
segments.map(
|
|
14055
14074
|
({ trim, path }) => runFfmpeg(
|
|
14056
14075
|
[
|
|
14076
|
+
"-cpuflags",
|
|
14077
|
+
"0",
|
|
14057
14078
|
"-i",
|
|
14058
14079
|
input,
|
|
14059
14080
|
"-af",
|
|
@@ -14579,7 +14600,7 @@ import { join as join13 } from "path";
|
|
|
14579
14600
|
import { pathToFileURL } from "url";
|
|
14580
14601
|
|
|
14581
14602
|
// ../core/dist/generated/audio-fx-runtime-inline.js
|
|
14582
|
-
var AUDIO_FX_RUNTIME_IIFE = '"use strict";(()=>{var we="data-fx-chain",Ft=we.slice(5),W=1,F=(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}),N=(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:[N(-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:[F("frequency","Frequency",1e3),N(-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:[F("frequency","Frequency",200,20,2e3),N(-40,40,0)],web:"biquad-lowshelf"},{id:"highshelf",label:"High Shelf",group:"filter",description:"Lift or drop everything above the corner frequency.",params:[F("frequency","Frequency",4e3,500,2e4),N(-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:[F("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:[F("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:"Peak limiter that follows the signal\'s level. It has no lookahead, so a fast peak or an inter-sample peak can pass the ceiling; use the true-peak limiter for a delivery 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"}],S=new Map(z.map(e=>[e.id,e]));function P(e){return S.get(e)}var Pt=z.map(e=>e.id);function y(e,t){let n=S.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(u=>u.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"||!S.has(n.type))throw new x(`Node ${t} has unknown effect type: ${String(n.type)}`);return Te({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 Te(e){for(let t of Object.keys(e))e[t]===void 0&&delete e[t];return e}function Fe(e,t){return Math.max(1,Math.round(e*t/1e3))}function Q(e,t){return Fe(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.start = Math.round((this.p.__hfElapsed ?? 0) * sampleRate);\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 // Primed from the input: a counter seeded mid-hold would otherwise play 0 until it wraps.\n if (this.held[ch] === undefined) this.held[ch] = Math.round(inp[0] * levels) / levels;\n // The hold counter runs from the file\'s first sample, so a node built part-way in starts where it would be.\n if (this.holds[ch] === undefined) this.holds[ch] = this.start % step;\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 // The taps sweep at a constant rate from the start of the file, so a node built\n // part-way in joins the sweep where it would already be.\n const ratio0 = Math.pow(2, Math.max(-12, Math.min(12, this.p.semitones ?? 0)) / 12);\n const turns = (this.p.__hfElapsed ?? 0) * sampleRate * ((1 - ratio0) / this.grain);\n this.phase = turns - Math.floor(turns);\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,L=new WeakMap;var He=new WeakSet;function O(e){let t=L.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 L.delete(e),n}),L.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),u=Math.max(.001,1-Math.max(0,Math.min(1,n))),l=0,m=0;for(let c=0;c<a;c++){l+=u*(s()-l);let b=l*Math.pow(1-c/a,2.5);r[c]=b,m+=b*b}let p=Math.sqrt(m);if(p>0)for(let c=0;c<a;c++)r[c]=(r[c]??0)/p;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 l=0;l<a;l++){let m=l/a;i[l]=t==="sine"?Math.sin(2*Math.PI*m):4*Math.abs((m+.75)%1-.5)-1}let s=e.createBufferSource();s.buffer=r,s.loop=!0,s.playbackRate.value=n;let u=(o*n%1+1)%1*(a/e.sampleRate);return s.start(typeof e.currentTime=="number"?e.currentTime:0,u),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 Re=new Set(["peaking","highpass","lowpass"]),ee=e=>Math.pow(10,e/20),Ne=(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}],...Re.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,a)=>{let r=new AudioWorkletNode(n,e,{processorOptions:{...o,__hfElapsed:a}});return{input:r,output:r,update:i=>r.port.postMessage({...i}),...t?{reset:()=>r.port.postMessage({__hfReset:!0})}:{},dispose:()=>{r.port.postMessage({__hfDispose:!0}),r.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,u=Math.pow(10,f(i.threshold)/20),l=8192,m=new Float32Array(l);for(let p=0;p<l;p++){let c=p/(l-1)*2-1;m[p]=s(c/Math.max(1e-6,u))*u}n.curve=m,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()}}},Se=(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 u=l=>{a.delayTime.value=Math.min(5,f(l.time)/1e3),r.gain.value=f(l.feedback),re(i,s,f(l.mix))};return u(t),{input:n,output:o,update:u,automation:{time:[{param:a.delayTime,map:l=>Math.min(5,B(l))}],feedback:[{param:r.gain}],mix:I(i.gain,s.gain)},dispose:()=>[n,o,a,r,i,s].forEach(l=>l.disconnect())}},Le=(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(),u=e.createGain(),l=e.createGain();i.connect(s).connect(r.delayTime),o.connect(r).connect(u).connect(a),o.connect(l).connect(a);let m=p=>{r.delayTime.value=f(p.delay)/1e3,s.gain.value=f(p.depth)/1e3,i.playbackRate.value=f(p.speed),re(u,l,f(p.mix))};return m(t),{input:o,output:a,update:m,automation:{delay:[{param:r.delayTime,map:B}],depth:[{param:s.gain,map:B}],speed:[{param:i.playbackRate}],mix:I(u.gain,l.gain)},dispose:()=>{oe(i),[o,a,r,s,u,l].forEach(p=>p.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),u=e.createGain(),l=e.createGain(),m=e.createGain(),p=[];o.connect(r);let c=r;for(let d=0;d<Oe;d++){let h=e.createBiquadFilter();h.type="allpass",h.Q.value=.7071,u.connect(h.frequency),c.connect(h),c=h,p.push(h)}s.connect(u),c.connect(l).connect(i),r.connect(m).connect(i),i.connect(a);let b=d=>{let h=1e3/Math.max(.1,f(d.delay));for(let g of p)g.frequency.value=h;u.gain.value=h*f(d.decay),s.playbackRate.value=f(d.speed),r.gain.value=f(d.in_gain),i.gain.value=f(d.out_gain),l.gain.value=1,m.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,u,l,m,...p].forEach(d=>d.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="",u=l=>{let m=`${f(l.size)}:${f(l.damping)}`;if(m!==s){let p=_e(e.sampleRate,f(l.size),f(l.damping)),c=e.createBuffer(1,p.length,e.sampleRate);c.getChannelData(0).set(p),a.buffer=c,s=m}r.gain.value=f(l.wet),i.gain.value=f(l.dry)};return u(t),{input:n,output:o,update:u,automation:{wet:[{param:r.gain}],dry:[{param:i.gain}]},dispose:()=>[n,o,a,r,i].forEach(l=>l.disconnect())}},qe={"gain-node":Ne,"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":Se,"chorus-lfo":Le,"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 p;let o=e.createGain(),a=e.createGain(),r=[],i=[],s=te(w(t)),u=o;for(let c of s){let b=null;if(c.preset){let d=e.createGain(),h=e.createGain(),g=e.createGain(),T=e.createGain();g.gain.value=c.amount,h.gain.value=1-c.amount,u.connect(d),d.connect(h).connect(T),b={entry:d,wet:g,dry:h,join:T},u=d}for(let d of c.nodes){let h=Ge(e,d.type,d.params??{},n);u.connect(h.input),u=h.output,r.push({...d.id?{id:d.id}:{},type:d.type,handle:h})}b&&c.preset&&(u.connect(b.wet).connect(b.join),i.push({id:c.preset,...b}),u=b.join)}u.connect(a);let l=ne(t),m={};for(let c of i)(m[p=c.id]??(m[p]=[])).push(...I(c.wet.gain,c.dry.gain));return{input:o,output:a,presets:m,nodes:r,update(c){if(ne(c)!==l)return!1;w(c).forEach((d,h)=>{let g=r[h];g&&(g.handle.update(y(d.type,d.params)),d.id===void 0?delete g.id:g.id=d.id)});let b=0;for(let d of te(w(c))){if(!d.preset)continue;let h=i[b++];!h||h.id!==d.preset||(h.wet.gain.value=d.amount,h.dry.gain.value=1-d.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:d,join:h}of i)c.disconnect(),b.disconnect(),d.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",Vt=Ve.slice(5),D=1,$e=512,A=class extends Error{constructor(t){super(t),this.name="AudioAutomationError"}},V="volume",le="rate";function R(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=R(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(!R(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"||!R(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,u=Math.min(r,Math.max(1,i,s));return{cx:e+n/u,cy:t+o/u,w:u}}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,u=-e*i-2*r*o,l=et(s,u,e),m=1-l,p=m*m+2*r*l*m+l*l;return p>0?(2*r*a*l*m+l*l)/p: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 p=i+s>>1;o[p].t<=t?i=p:s=p}let u=o[i],l=o[s],m=l.t-u.t;return m<=0?l.v:nt(u.v,l.v,tt((t-u.t)/m,u),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 u=0;u<r;u+=1)i[u]=j(e,t+s*u/(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,u=a??X,l=v=>r+(v-i)/s,m=e.points[t],p=e.points[t+1],c=l(p.t);if(c<=r)return null;let b=Math.max(r,l(m.t)),d=c-b;if(d<=0)return null;if(it(m,n,a))return{kind:"ramp",time:c,value:u(p.v)};let h=st(d),g=Ae(e,Math.max(m.t,i),p.t,h,n),T=new Float32Array(h);for(let v=0;v<h;v+=1)T[v]=u(g[v]??0);return{kind:"curve",time:b,duration:d,values:T}}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){e.cancelScheduledValues(0);for(let n of t)n.kind==="set"?e.setValueAtTime(n.value,n.time):n.kind==="ramp"?e.linearRampToValueAtTime(n.value,n.time):(e.setValueAtTime(n.values[0]??0,n.time),e.linearRampToValueAtTime(n.values[n.values.length-1]??0,n.time+n.duration))}function dt(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{ct(e,t);return}}function mt(e){for(let t of e)t.param.cancelScheduledValues(0)}function pt(e,t,n,o){if(t.points.length!==0)for(let a of e){if(mt([a]),ge(t)){let r=(a.map??X)(t.points[0].v);a.param.setValueAtTime(r,o.scheduledAt);continue}dt(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 u=R(s.target);if(!u)continue;let l=u.kind==="preset"?a?.[u.presetId]:u.kind==="fx"?r.get(u.nodeId)?.automation?.[u.param]:void 0;if(!l||l.length===0)continue;let m=$(s.target,t);m&&(pt(l,s,m.scale,o),i.push(...l))}return i}var ht=5,ft=.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 bt(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(ft)/Math.log(o))*n}function gt(e){return .6+Math.max(0,Math.min(1,e))*2.6}function At(e,t){return k(e,"wet",t)>0?gt(k(e,"size",t)):0}function xt(e,t){return k(e,"mix",t)>0?bt(k(e,"time",t),k(e,"feedback",t)):0}function yt(e,t){return k(e,"mix",t)>0?k(e,"delay",t)/1e3:0}function kt(e,t){return k(e,"mix",t)>0?.2:0}function wt(e,t){if(e.enabled===!1)return 0;switch(e.type){case"reverb":return At(e,t);case"delay":return xt(e,t);case"chorus":return yt(e,t);case"pitchshift":return kt(e,t);default:return 0}}function ye(e,t){let n=e.nodes.reduce((o,a)=>o+wt(a,t),0);return Math.min(ht,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 vt(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 Mt(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 Tt(e,t,n,o,a=0){let r=J(n),i=Math.max(1,e.length),s=e[0]?.length??0;if(s===0)return e;let u=o?fe(be(o),r):null,l=Math.ceil(ye(r,u??void 0)*t),m=ke(r,t),p=s+l+m,c=new OfflineAudioContext(i,p,t);ie(r)&&await O(c);let b=c.createBufferSource();b.buffer=vt(c,e,i,p,t);let d=se(c,r,a);return u&&xe(u,r,d.nodes,{scheduledAt:0,elapsed:0,rate:1},d.presets),b.connect(d.input),d.output.connect(c.destination),b.start(),Mt(await c.startRendering(),m)}window.__HF_AUDIO_FX={render:Tt};})();\n';
|
|
14603
|
+
var AUDIO_FX_RUNTIME_IIFE = '"use strict";(()=>{var ve="data-fx-chain",Pt=ve.slice(5),W=1,F=(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: higher is narrower.")=>({kind:"number",key:"q",label:"Q",unit:"",min:.1,max:20,step:.01,default:e,scale:"log",automatable:!0,hint:t}),N=(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:[N(-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:[F("frequency","Frequency",1e3),N(-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:[F("frequency","Frequency",200,20,2e3),N(-40,40,0)],web:"biquad-lowshelf"},{id:"highshelf",label:"High Shelf",group:"filter",description:"Lift or drop everything above the corner frequency.",params:[F("frequency","Frequency",4e3,500,2e4),N(-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:[F("frequency","Cutoff",300,20,2e4),S(.707),K],web:"biquad-highpass"},{id:"lowpass",label:"Low-pass",group:"filter",description:"Remove high frequencies, which darkens or muffles a track.",params:[F("frequency","Cutoff",8e3,100,2e4),S(.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:"Peak limiter that follows the signal\'s level. It has no lookahead, so a fast peak or an inter-sample peak can pass the ceiling; use the true-peak limiter for a delivery 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"}],C=new Map(z.map(e=>[e.id,e]));function P(e){return C.get(e)}var Ht=z.map(e=>e.id);function y(e,t){let n=C.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(u=>u.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(Me);return{version:W,nodes:o}}function w(e){return e.nodes.filter(t=>t.enabled!==!1)}function Me(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"||!C.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:Te(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 Te(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 Pe(e,t){return Math.max(1,Math.round(e*t/1e3))}function Q(e,t){return Pe(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 He=`\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.start = Math.round((this.p.__hfElapsed ?? 0) * sampleRate);\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 // Primed from the input: a counter seeded mid-hold would otherwise play 0 until it wraps.\n if (this.held[ch] === undefined) this.held[ch] = Math.round(inp[0] * levels) / levels;\n // The hold counter runs from the file\'s first sample, so a node built part-way in starts where it would be.\n if (this.holds[ch] === undefined) this.holds[ch] = this.start % step;\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 // The taps sweep at a constant rate from the start of the file, so a node built\n // part-way in joins the sweep where it would already be.\n const ratio0 = Math.pow(2, Math.max(-12, Math.min(12, this.p.semitones ?? 0)) / 12);\n const turns = (this.p.__hfElapsed ?? 0) * sampleRate * ((1 - ratio0) / this.grain);\n this.phase = turns - Math.floor(turns);\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,L=new WeakMap;var _e=new WeakSet;function O(e){let t=L.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(He)))}`;await e.audioWorklet.addModule(n),_e.add(e)})().catch(n=>{throw L.delete(e),n}),L.set(e,t)),t}function Re(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),u=Math.max(.001,1-Math.max(0,Math.min(1,n))),l=0,m=0;for(let c=0;c<a;c++){l+=u*(s()-l);let b=l*Math.pow(1-c/a,2.5);r[c]=b,m+=b*b}let p=Math.sqrt(m);if(p>0)for(let c=0;c<a;c++)r[c]=(r[c]??0)/p;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 l=0;l<a;l++){let m=l/a;i[l]=t==="sine"?Math.sin(2*Math.PI*m):4*Math.abs((m+.75)%1-.5)-1}let s=e.createBufferSource();s.buffer=r,s.loop=!0,s.playbackRate.value=n;let u=(o*n%1+1)%1*(a/e.sampleRate);return s.start(typeof e.currentTime=="number"?e.currentTime:0,u),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),Ee=(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 De(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,a)=>{let r=new AudioWorkletNode(n,e,{processorOptions:{...o,__hfElapsed:a}});return{input:r,output:r,update:i=>r.port.postMessage({...i}),...t?{reset:()=>r.port.postMessage({__hfReset:!0})}:{},dispose:()=>{r.port.postMessage({__hfDispose:!0}),r.disconnect()}}}}var Se={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=Se[String(i.type)]??Math.tanh,u=Math.pow(10,f(i.threshold)/20),l=8192,m=new Float32Array(l);for(let p=0;p<l;p++){let c=p/(l-1)*2-1;m[p]=s(c/Math.max(1e-6,u))*u}n.curve=m,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 u=l=>{a.delayTime.value=Math.min(5,f(l.time)/1e3),r.gain.value=f(l.feedback),re(i,s,f(l.mix))};return u(t),{input:n,output:o,update:u,automation:{time:[{param:a.delayTime,map:l=>Math.min(5,B(l))}],feedback:[{param:r.gain}],mix:I(i.gain,s.gain)},dispose:()=>[n,o,a,r,i,s].forEach(l=>l.disconnect())}},Oe=(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(),u=e.createGain(),l=e.createGain();i.connect(s).connect(r.delayTime),o.connect(r).connect(u).connect(a),o.connect(l).connect(a);let m=p=>{r.delayTime.value=f(p.delay)/1e3,s.gain.value=f(p.depth)/1e3,i.playbackRate.value=f(p.speed),re(u,l,f(p.mix))};return m(t),{input:o,output:a,update:m,automation:{delay:[{param:r.delayTime,map:B}],depth:[{param:s.gain,map:B}],speed:[{param:i.playbackRate}],mix:I(u.gain,l.gain)},dispose:()=>{oe(i),[o,a,r,s,u,l].forEach(p=>p.disconnect())}}},Be=6,Ie=(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),u=e.createGain(),l=e.createGain(),m=e.createGain(),p=[];o.connect(r);let c=r;for(let d=0;d<Be;d++){let h=e.createBiquadFilter();h.type="allpass",h.Q.value=.7071,u.connect(h.frequency),c.connect(h),c=h,p.push(h)}s.connect(u),c.connect(l).connect(i),r.connect(m).connect(i),i.connect(a);let b=d=>{let h=1e3/Math.max(.1,f(d.delay));for(let g of p)g.frequency.value=h;u.gain.value=h*f(d.decay),s.playbackRate.value=f(d.speed),r.gain.value=f(d.in_gain),i.gain.value=f(d.out_gain),l.gain.value=1,m.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,u,l,m,...p].forEach(d=>d.disconnect())}}},qe=(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="",u=l=>{let m=`${f(l.size)}:${f(l.damping)}`;if(m!==s){let p=Re(e.sampleRate,f(l.size),f(l.damping)),c=e.createBuffer(1,p.length,e.sampleRate);c.getChannelData(0).set(p),a.buffer=c,s=m}r.gain.value=f(l.wet),i.gain.value=f(l.dry)};return u(t),{input:n,output:o,update:u,automation:{wet:[{param:r.gain}],dry:[{param:i.gain}]},dispose:()=>[n,o,a,r,i].forEach(l=>l.disconnect())}},Ge={"gain-node":Ee,"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":Oe,"allpass-phaser":Ie,convolver:qe};function ie(e){return e.nodes.some(t=>P(t.type)?.web.startsWith("worklet-")??!1)}function Ve(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 De(t)(e,r,o);let i=Ge[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 p;let o=e.createGain(),a=e.createGain(),r=[],i=[],s=te(w(t)),u=o;for(let c of s){let b=null;if(c.preset){let d=e.createGain(),h=e.createGain(),g=e.createGain(),T=e.createGain();g.gain.value=c.amount,h.gain.value=1-c.amount,u.connect(d),d.connect(h).connect(T),b={entry:d,wet:g,dry:h,join:T},u=d}for(let d of c.nodes){let h=Ve(e,d.type,d.params??{},n);u.connect(h.input),u=h.output,r.push({...d.id?{id:d.id}:{},type:d.type,handle:h})}b&&c.preset&&(u.connect(b.wet).connect(b.join),i.push({id:c.preset,...b}),u=b.join)}u.connect(a);let l=ne(t),m={};for(let c of i)(m[p=c.id]??(m[p]=[])).push(...I(c.wet.gain,c.dry.gain));return{input:o,output:a,presets:m,nodes:r,update(c){if(ne(c)!==l)return!1;w(c).forEach((d,h)=>{let g=r[h];g&&(g.handle.update(y(d.type,d.params)),d.id===void 0?delete g.id:g.id=d.id)});let b=0;for(let d of te(w(c))){if(!d.preset)continue;let h=i[b++];!h||h.id!==d.preset||(h.wet.gain.value=d.amount,h.dry.gain.value=1-d.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:d,join:h}of i)c.disconnect(),b.disconnect(),d.disconnect(),h.disconnect();o.disconnect(),a.disconnect()}}}function Ue(e){return 10**(e/20)}var ue=Ue(12);var G=.1,V=10;var $e="data-automation",Xt=$e.slice(5),D=1,je=512,A=class extends Error{constructor(t){super(t),this.name="AudioAutomationError"}},U="volume",le="rate";function R(e){if(e===U)return{kind:"volume"};if(e===le)return{kind:"rate"};let t=e.split(".");if(t.length===3&&t[0]==="fx"&&t[1]===Xe){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 Xe="preset";var We={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};function me(e){return!!e.curve||e.viaX!==void 0&&e.viaY!==void 0}var pe={min:G,max:V,step:.05,unit:"x",label:"Speed",scale:"log",default:1};function $(e,t){let n=R(e);if(!n)return null;if(n.kind==="volume")return de;if(n.kind==="rate")return pe;if(n.kind==="preset")return t?.nodes.some(s=>s.fromPreset===n.presetId)?{...We,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 Ke(e){let t=_(e);return t===null||t===0?0:Math.min(1,Math.max(-1,t))}function he(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 Ye(e){let t=Ke(e?.curve),n=he(e?.viaX,e?.viaY);return{...t?{curve:t}:{},...n?{viaX:n.x,viaY:n.y}:{}}}function ze(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,...Ye(e)}}function fe(e,t){let n=e.map(a=>ze(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,je)}function Je(e){let t=[];for(let n of e.lanes){if(!R(n.target))continue;let o=n.target===U?de:n.target===le?pe:null,a=fe(n.points??[],o);a.length>0&&t.push({target:n.target,points:a})}return{version:D,lanes:t}}function be(e,t){let n=[];for(let o of e.lanes){let a=$(o.target,t);if(!a)continue;let r=fe(o.points,a);r.length>0&&n.push({target:o.target,points:r})}return{version:D,lanes:n}}function ge(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"||!R(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 Je({version:D,lanes:o})}function Qe(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,u=Math.min(r,Math.max(1,i,s));return{cx:e+n/u,cy:t+o/u,w:u}}function et(e,t,n){let{cx:o,cy:a,w:r}=Ze(t,n),i=2-2*r,s=e*i-1+2*r*o,u=-e*i-2*r*o,l=tt(s,u,e),m=1-l,p=m*m+2*r*l*m+l*l;return p>0?(2*r*a*l*m+l*l)/p:e}function tt(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 nt(e,t){let n=he(t.viaX,t.viaY);return n?et(Math.min(1,Math.max(0,e)),n.x,n.y):Qe(e,t.curve)}function at(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 p=i+s>>1;o[p].t<=t?i=p:s=p}let u=o[i],l=o[s],m=l.t-u.t;return m<=0?l.v:at(u.v,l.v,nt((t-u.t)/m,u),n)}function Ae(e){return e.points.length<=1||e.points.every(t=>t.v===e.points[0].v)}function xe(e,t,n,o,a="linear"){let r=Math.max(2,Math.floor(o)),i=new Float32Array(r),s=n-t;for(let u=0;u<r;u+=1)i[u]=j(e,t+s*u/(r-1),a);return i}var ot=100,rt=8,it=4096,X=e=>e;function st(e,t,n){return!me(e)&&t==="linear"&&!n}function ut(e){let t=Math.ceil(e*ot);return Math.min(it,Math.max(rt,t))}function lt(e,t,n,o,a){let{scheduledAt:r,elapsed:i,rate:s}=o,u=a??X,l=v=>r+(v-i)/s,m=e.points[t],p=e.points[t+1],c=l(p.t);if(c<=r)return null;let b=Math.max(r,l(m.t)),d=c-b;if(d<=0)return null;if(st(m,n,a))return{kind:"ramp",time:c,value:u(p.v)};let h=ut(d),g=xe(e,Math.max(m.t,i),p.t,h,n),T=new Float32Array(h);for(let v=0;v<h;v+=1)T[v]=u(g[v]??0);return{kind:"curve",time:b,duration:d,values:T}}function ct(e,t,n,o){let a=[];for(let i=0;i+1<e.points.length;i+=1){let s=lt(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 dt(e,t){e.cancelScheduledValues(0);for(let n of t)n.kind==="set"?e.setValueAtTime(n.value,n.time):n.kind==="ramp"?e.linearRampToValueAtTime(n.value,n.time):(e.setValueAtTime(n.values[0]??0,n.time),e.linearRampToValueAtTime(n.values[n.values.length-1]??0,n.time+n.duration))}function mt(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{dt(e,t);return}}function pt(e){for(let t of e)t.param.cancelScheduledValues(0)}function ht(e,t,n,o){if(t.points.length!==0)for(let a of e){if(pt([a]),Ae(t)){let r=(a.map??X)(t.points[0].v);a.param.setValueAtTime(r,o.scheduledAt);continue}mt(a.param,ct(t,n,o,a.map))}}function ye(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 u=R(s.target);if(!u)continue;let l=u.kind==="preset"?a?.[u.presetId]:u.kind==="fx"?r.get(u.nodeId)?.automation?.[u.param]:void 0;if(!l||l.length===0)continue;let m=$(s.target,t);m&&(ht(l,s,m.scale,o),i.push(...l))}return i}var ft=5,bt=.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 gt(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(bt)/Math.log(o))*n}function At(e){return .6+Math.max(0,Math.min(1,e))*2.6}function xt(e,t){return k(e,"wet",t)>0?At(k(e,"size",t)):0}function yt(e,t){return k(e,"mix",t)>0?gt(k(e,"time",t),k(e,"feedback",t)):0}function kt(e,t){return k(e,"mix",t)>0?k(e,"delay",t)/1e3:0}function wt(e,t){return k(e,"mix",t)>0?.2:0}function vt(e,t){if(e.enabled===!1)return 0;switch(e.type){case"reverb":return xt(e,t);case"delay":return yt(e,t);case"chorus":return kt(e,t);case"pitchshift":return wt(e,t);default:return 0}}function ke(e,t){let n=e.nodes.reduce((o,a)=>o+vt(a,t),0);return Math.min(ft,n)}function we(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 Mt(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 Tt(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 Ft(e,t,n,o,a=0){let r=J(n),i=Math.max(1,e.length),s=e[0]?.length??0;if(s===0)return e;let u=o?be(ge(o),r):null,l=Math.ceil(ke(r,u??void 0)*t),m=we(r,t),p=s+l+m,c=new OfflineAudioContext(i,p,t);ie(r)&&await O(c);let b=c.createBufferSource();b.buffer=Mt(c,e,i,p,t);let d=se(c,r,a);return u&&ye(u,r,d.nodes,{scheduledAt:0,elapsed:0,rate:1},d.presets),b.connect(d.input),d.output.connect(c.destination),b.start(),Tt(await c.startRendering(),m)}window.__HF_AUDIO_FX={render:Ft};})();\n';
|
|
14583
14604
|
function getAudioFxRuntimeScript() {
|
|
14584
14605
|
return AUDIO_FX_RUNTIME_IIFE;
|
|
14585
14606
|
}
|
|
@@ -14765,6 +14786,9 @@ function applyEnvelopeToPlanes(planes, sampleRate, gainAt, startFrame = 0) {
|
|
|
14765
14786
|
for (const plane of planes) plane[frame] = (plane[frame] ?? 0) * gain;
|
|
14766
14787
|
}
|
|
14767
14788
|
}
|
|
14789
|
+
function applyInputGain(planes, gain = 1) {
|
|
14790
|
+
if (gain !== 1) applyEnvelopeToPlanes(planes, 1, () => gain);
|
|
14791
|
+
}
|
|
14768
14792
|
var DEFAULT_STREAMING_LIMITS = {
|
|
14769
14793
|
aboveBytes: 512 * 1024 * 1024,
|
|
14770
14794
|
segmentSeconds: 300,
|
|
@@ -14838,6 +14862,7 @@ async function renderInSegments(openPage, inputWav, header, chain, outputWav, op
|
|
|
14838
14862
|
throw new AudioFxRenderError(`Audio FX cancelled for track ${options.trackId}`);
|
|
14839
14863
|
}
|
|
14840
14864
|
const planes = deinterleave(readFrames(inFd, header, seg.readFrom, seg.readTo), channels);
|
|
14865
|
+
applyInputGain(planes, options.inputGain);
|
|
14841
14866
|
const page = await openPage();
|
|
14842
14867
|
let outPlanes;
|
|
14843
14868
|
try {
|
|
@@ -14897,6 +14922,7 @@ async function applyAudioFxChain(inputWav, chain, outputWav, options) {
|
|
|
14897
14922
|
}
|
|
14898
14923
|
const input = readWav(inputWav);
|
|
14899
14924
|
const planes = deinterleave(input.samples, input.channels);
|
|
14925
|
+
applyInputGain(planes, options.inputGain);
|
|
14900
14926
|
if ((planes[0]?.length ?? 0) === 0) return { path: inputWav, envelopeBaked: false };
|
|
14901
14927
|
return withFxBrowser(options, async (openPage) => {
|
|
14902
14928
|
const page = await openPage();
|
|
@@ -15223,7 +15249,8 @@ function boundedDetail(message, maxLength = 2e3) {
|
|
|
15223
15249
|
/(^|[\s"'(])\/.+?(?=:\s[A-Z]|\s(?:ENOENT|EACCES|EPERM)\b|\r?$)/gm,
|
|
15224
15250
|
"$1<redacted-path>"
|
|
15225
15251
|
);
|
|
15226
|
-
|
|
15252
|
+
const head = Math.floor(maxLength / 4);
|
|
15253
|
+
return redacted.length <= maxLength ? redacted : `${redacted.slice(0, head)}\u2026${redacted.slice(-(maxLength - head - 1))}`;
|
|
15227
15254
|
}
|
|
15228
15255
|
function probeFailure(message, elementId) {
|
|
15229
15256
|
const unavailable = /(?:not found|ENOENT|spawn)/i.test(message);
|
|
@@ -15256,7 +15283,7 @@ function downloadFailure(error, elementId) {
|
|
|
15256
15283
|
detail: boundedDetail(`Download failed for audio element ${elementId}: ${message}`)
|
|
15257
15284
|
};
|
|
15258
15285
|
}
|
|
15259
|
-
function ffmpegFailure(stage, result, elementId) {
|
|
15286
|
+
function ffmpegFailure(stage, result, timeoutMs, elementId) {
|
|
15260
15287
|
const stderr = result.stderr ?? "";
|
|
15261
15288
|
let reason = "ffmpeg_failed";
|
|
15262
15289
|
let owner = "system";
|
|
@@ -15287,32 +15314,32 @@ function ffmpegFailure(stage, result, elementId) {
|
|
|
15287
15314
|
elementId,
|
|
15288
15315
|
subprocess: audioSubprocessEvidence(result),
|
|
15289
15316
|
detail: boundedDetail(
|
|
15290
|
-
result.error?.message ? `${
|
|
15317
|
+
`(ran ${result.durationMs} ms, limit ${timeoutMs} ms) ` + (result.error?.message ? `${describeFfmpegFailure(result, timeoutMs)}: ${result.error.message}` : describeFfmpegFailure(result, timeoutMs))
|
|
15291
15318
|
)
|
|
15292
15319
|
};
|
|
15293
15320
|
}
|
|
15294
|
-
|
|
15295
|
-
function volumeLaneKeyframes(automation, trackStart, duration) {
|
|
15321
|
+
function volumeLaneKeyframes(automation, trackStart, duration, gain, gainAhead = false) {
|
|
15296
15322
|
const lane = automation.lanes.find((l) => l.target === VOLUME_TARGET);
|
|
15297
15323
|
if (!lane || lane.points.length === 0) return null;
|
|
15324
|
+
const points = gainedVolumePoints(lane, gain, gainAhead);
|
|
15298
15325
|
const out = [];
|
|
15299
15326
|
const push = (t, v) => {
|
|
15300
15327
|
out.push({ time: trackStart + t, volume: v });
|
|
15301
15328
|
};
|
|
15302
|
-
const first =
|
|
15329
|
+
const first = points[0];
|
|
15303
15330
|
if (first.t > 0) push(0, first.v);
|
|
15304
|
-
for (let i = 0; i <
|
|
15305
|
-
const a =
|
|
15331
|
+
for (let i = 0; i < points.length; i += 1) {
|
|
15332
|
+
const a = points[i];
|
|
15306
15333
|
push(a.t, a.v);
|
|
15307
|
-
const b =
|
|
15308
|
-
if (!b || !a
|
|
15309
|
-
const steps =
|
|
15334
|
+
const b = points[i + 1];
|
|
15335
|
+
if (!b || !isBentSegment(a)) continue;
|
|
15336
|
+
const steps = volumeSegmentSampleCount(b.t - a.t);
|
|
15310
15337
|
for (let k = 1; k < steps; k += 1) {
|
|
15311
15338
|
const t = a.t + (b.t - a.t) * k / steps;
|
|
15312
|
-
push(t, sampleAutomationLane(lane, t));
|
|
15339
|
+
push(t, gainedVolume(sampleAutomationLane(lane, t), gain, gainAhead));
|
|
15313
15340
|
}
|
|
15314
15341
|
}
|
|
15315
|
-
const last =
|
|
15342
|
+
const last = points[points.length - 1];
|
|
15316
15343
|
if (duration > last.t) push(duration, last.v);
|
|
15317
15344
|
return out;
|
|
15318
15345
|
}
|
|
@@ -15429,7 +15456,7 @@ async function extractAudioSegment(srcPath, outputPath, stage, mediaStart, durat
|
|
|
15429
15456
|
failure: failure2
|
|
15430
15457
|
};
|
|
15431
15458
|
}
|
|
15432
|
-
let failure = result.success ? void 0 : ffmpegFailure(stage, result);
|
|
15459
|
+
let failure = result.success ? void 0 : ffmpegFailure(stage, result, ffmpegProcessTimeout);
|
|
15433
15460
|
const probedLinkFailedToRead = readsUrl && (failure?.reason === "invalid_media" || failure?.reason === "ffmpeg_failed");
|
|
15434
15461
|
if (failure && probedLinkFailedToRead) {
|
|
15435
15462
|
failure = { ...failure, reason: "download_failed", owner: "system", retryable: true };
|
|
@@ -15475,7 +15502,7 @@ async function generateSilence(outputPath, duration, signal, config) {
|
|
|
15475
15502
|
failure: failure2
|
|
15476
15503
|
};
|
|
15477
15504
|
}
|
|
15478
|
-
const failure = !result.success ? ffmpegFailure("silence", result) : void 0;
|
|
15505
|
+
const failure = !result.success ? ffmpegFailure("silence", result, ffmpegProcessTimeout) : void 0;
|
|
15479
15506
|
return {
|
|
15480
15507
|
success: result.success,
|
|
15481
15508
|
outputPath,
|
|
@@ -15484,8 +15511,13 @@ async function generateSilence(outputPath, duration, signal, config) {
|
|
|
15484
15511
|
failure
|
|
15485
15512
|
};
|
|
15486
15513
|
}
|
|
15487
|
-
|
|
15488
|
-
|
|
15514
|
+
var MIX_TIMEOUT_MS_PER_FILM_SECOND = 100;
|
|
15515
|
+
function mixProcessTimeout(config, totalDuration) {
|
|
15516
|
+
const configured = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
15517
|
+
return Math.max(configured, Math.ceil(totalDuration * MIX_TIMEOUT_MS_PER_FILM_SECOND));
|
|
15518
|
+
}
|
|
15519
|
+
async function mixAudioTracksFlat(tracks, outputPath, totalDuration, signal, config) {
|
|
15520
|
+
const ffmpegProcessTimeout = mixProcessTimeout(config, totalDuration);
|
|
15489
15521
|
const masterOutputGain = config?.audioGain ?? DEFAULT_CONFIG.audioGain;
|
|
15490
15522
|
if (tracks.length === 0) {
|
|
15491
15523
|
const result2 = await generateSilence(outputPath, totalDuration, signal, config);
|
|
@@ -15584,7 +15616,7 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
|
|
|
15584
15616
|
};
|
|
15585
15617
|
}
|
|
15586
15618
|
if (!result.success) {
|
|
15587
|
-
const failure = ffmpegFailure("mix", result);
|
|
15619
|
+
const failure = ffmpegFailure("mix", result, ffmpegProcessTimeout);
|
|
15588
15620
|
return {
|
|
15589
15621
|
success: false,
|
|
15590
15622
|
outputPath,
|
|
@@ -15605,18 +15637,19 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
|
|
|
15605
15637
|
function groupNormalizeOptionUnsupported(stderr) {
|
|
15606
15638
|
return /normalize/i.test(stderr) && /(?:unrecognized option|option (?:was )?not found|no option name)/i.test(stderr);
|
|
15607
15639
|
}
|
|
15608
|
-
async function
|
|
15609
|
-
const ffmpegProcessTimeout = config
|
|
15640
|
+
async function mixGroupMembersFlat(memberTracks, outputPath, totalDuration, signal, config) {
|
|
15641
|
+
const ffmpegProcessTimeout = mixProcessTimeout(config, totalDuration);
|
|
15610
15642
|
const outputDir = dirname7(outputPath);
|
|
15611
15643
|
if (!existsSync11(outputDir)) mkdirSync7(outputDir, { recursive: true });
|
|
15612
|
-
const buildInputFilters = (ignoreKeyframes) => memberTracks.map((track, i) => {
|
|
15644
|
+
const buildInputFilters = (ignoreKeyframes, padMembers) => memberTracks.map((track, i) => {
|
|
15613
15645
|
const volumeFilter = buildVolumeExpression(track, ignoreKeyframes);
|
|
15614
|
-
return buildTrackInputFilter(track, i, volumeFilter, totalDuration);
|
|
15646
|
+
return buildTrackInputFilter(track, i, volumeFilter, totalDuration, padMembers);
|
|
15615
15647
|
});
|
|
15616
15648
|
const mixInputs = memberTracks.map((_, i) => `[a${i}]`).join("");
|
|
15617
15649
|
const runOnce = async (useNormalize2, ignoreKeyframes = false) => {
|
|
15618
|
-
const inputFilters = buildInputFilters(ignoreKeyframes);
|
|
15619
|
-
const
|
|
15650
|
+
const inputFilters = buildInputFilters(ignoreKeyframes, !useNormalize2);
|
|
15651
|
+
const padUnitySumOnceToFilm = `[mixed];[mixed]apad,asetpts=N/SR/TB,atrim=0:${formatFilterNumber(totalDuration)}`;
|
|
15652
|
+
const mixFilter = useNormalize2 ? `${mixInputs}amix=inputs=${memberTracks.length}:duration=longest:dropout_transition=0:normalize=0${padUnitySumOnceToFilm}[out]` : (
|
|
15620
15653
|
// Every member is padded and trimmed to totalDuration above, and
|
|
15621
15654
|
// dropout_transition=0 keeps all N inputs active for that whole span.
|
|
15622
15655
|
// amix's default normalize therefore divides by exactly N throughout;
|
|
@@ -15685,11 +15718,105 @@ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal,
|
|
|
15685
15718
|
}
|
|
15686
15719
|
}
|
|
15687
15720
|
if (signal?.aborted || !result.success) {
|
|
15688
|
-
const failure = ffmpegFailure("mix", result);
|
|
15721
|
+
const failure = ffmpegFailure("mix", result, ffmpegProcessTimeout);
|
|
15689
15722
|
return { success: false, error: failure.detail, failure };
|
|
15690
15723
|
}
|
|
15691
15724
|
return { success: true, degradedAutomation };
|
|
15692
15725
|
}
|
|
15726
|
+
var MAX_MIX_INPUTS = 96;
|
|
15727
|
+
async function sumInSlices(tracks, scratchDir, totalDuration, signal, config) {
|
|
15728
|
+
const wavTrack = (id, srcPath) => ({
|
|
15729
|
+
id,
|
|
15730
|
+
srcPath,
|
|
15731
|
+
start: 0,
|
|
15732
|
+
end: totalDuration,
|
|
15733
|
+
mediaStart: 0,
|
|
15734
|
+
duration: totalDuration,
|
|
15735
|
+
volume: 1
|
|
15736
|
+
});
|
|
15737
|
+
let running = null;
|
|
15738
|
+
let degradedAutomation = false;
|
|
15739
|
+
for (let at = 0; at < tracks.length; at += MAX_MIX_INPUTS) {
|
|
15740
|
+
const id = `sum-${at / MAX_MIX_INPUTS}`;
|
|
15741
|
+
const slicePath = join14(scratchDir, `${id}-slice.wav`);
|
|
15742
|
+
const slice = await mixGroupMembersFlat(
|
|
15743
|
+
tracks.slice(at, at + MAX_MIX_INPUTS),
|
|
15744
|
+
slicePath,
|
|
15745
|
+
totalDuration,
|
|
15746
|
+
signal,
|
|
15747
|
+
config
|
|
15748
|
+
);
|
|
15749
|
+
if (!slice.success) return slice;
|
|
15750
|
+
degradedAutomation ||= slice.degradedAutomation === true;
|
|
15751
|
+
if (!running) {
|
|
15752
|
+
running = wavTrack(id, slicePath);
|
|
15753
|
+
continue;
|
|
15754
|
+
}
|
|
15755
|
+
const foldedPath = join14(scratchDir, `${id}.wav`);
|
|
15756
|
+
const folded = await mixGroupMembersFlat(
|
|
15757
|
+
[running, wavTrack(`${id}-slice`, slicePath)],
|
|
15758
|
+
foldedPath,
|
|
15759
|
+
totalDuration,
|
|
15760
|
+
signal,
|
|
15761
|
+
config
|
|
15762
|
+
);
|
|
15763
|
+
rmSync8(running.srcPath, { force: true });
|
|
15764
|
+
rmSync8(slicePath, { force: true });
|
|
15765
|
+
if (!folded.success) return folded;
|
|
15766
|
+
running = wavTrack(id, foldedPath);
|
|
15767
|
+
}
|
|
15768
|
+
if (!running) throw new Error("[audioMixer] sumInSlices needs at least one track");
|
|
15769
|
+
return { success: true, track: running, degradedAutomation };
|
|
15770
|
+
}
|
|
15771
|
+
async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal, config) {
|
|
15772
|
+
if (memberTracks.length <= MAX_MIX_INPUTS) {
|
|
15773
|
+
return mixGroupMembersFlat(memberTracks, outputPath, totalDuration, signal, config);
|
|
15774
|
+
}
|
|
15775
|
+
mkdirSync7(dirname7(outputPath), { recursive: true });
|
|
15776
|
+
const scratchDir = mkdtempSync7(join14(dirname7(outputPath), ".mix-batches-"));
|
|
15777
|
+
try {
|
|
15778
|
+
const summed = await sumInSlices(memberTracks, scratchDir, totalDuration, signal, config);
|
|
15779
|
+
if (!summed.success) return summed;
|
|
15780
|
+
renameSync3(summed.track.srcPath, outputPath);
|
|
15781
|
+
return { success: true, degradedAutomation: summed.degradedAutomation };
|
|
15782
|
+
} finally {
|
|
15783
|
+
rmSync8(scratchDir, { recursive: true, force: true });
|
|
15784
|
+
}
|
|
15785
|
+
}
|
|
15786
|
+
async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config) {
|
|
15787
|
+
if (tracks.length <= MAX_MIX_INPUTS) {
|
|
15788
|
+
return mixAudioTracksFlat(tracks, outputPath, totalDuration, signal, config);
|
|
15789
|
+
}
|
|
15790
|
+
mkdirSync7(dirname7(outputPath), { recursive: true });
|
|
15791
|
+
const scratchDir = mkdtempSync7(join14(dirname7(outputPath), ".mix-batches-"));
|
|
15792
|
+
try {
|
|
15793
|
+
const summed = await sumInSlices(tracks, scratchDir, totalDuration, signal, config);
|
|
15794
|
+
if (!summed.success) {
|
|
15795
|
+
return {
|
|
15796
|
+
success: false,
|
|
15797
|
+
outputPath,
|
|
15798
|
+
durationMs: 0,
|
|
15799
|
+
tracksProcessed: 0,
|
|
15800
|
+
error: summed.error,
|
|
15801
|
+
failures: [summed.failure]
|
|
15802
|
+
};
|
|
15803
|
+
}
|
|
15804
|
+
const mixed = await mixAudioTracksFlat(
|
|
15805
|
+
[summed.track],
|
|
15806
|
+
outputPath,
|
|
15807
|
+
totalDuration,
|
|
15808
|
+
signal,
|
|
15809
|
+
config
|
|
15810
|
+
);
|
|
15811
|
+
return mixed.success ? {
|
|
15812
|
+
...mixed,
|
|
15813
|
+
tracksProcessed: tracks.length,
|
|
15814
|
+
error: summed.degradedAutomation ? "Volume automation exceeded this ffmpeg build's expression limits; rendered at base volume" : mixed.error
|
|
15815
|
+
} : mixed;
|
|
15816
|
+
} finally {
|
|
15817
|
+
rmSync8(scratchDir, { recursive: true, force: true });
|
|
15818
|
+
}
|
|
15819
|
+
}
|
|
15693
15820
|
async function mixThroughFloatSum(tracks, masterChain, outputPath, workDir, totalDuration, signal, config) {
|
|
15694
15821
|
const sumPath = join14(workDir, "master-sum.wav");
|
|
15695
15822
|
const sum = await mixAudioTracks(tracks, sumPath, totalDuration, signal, config);
|
|
@@ -15703,7 +15830,11 @@ async function mixThroughFloatSum(tracks, masterChain, outputPath, workDir, tota
|
|
|
15703
15830
|
timeout: config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout
|
|
15704
15831
|
});
|
|
15705
15832
|
if (!encoded.success) {
|
|
15706
|
-
const failure = ffmpegFailure(
|
|
15833
|
+
const failure = ffmpegFailure(
|
|
15834
|
+
"mix",
|
|
15835
|
+
encoded,
|
|
15836
|
+
config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout
|
|
15837
|
+
);
|
|
15707
15838
|
return {
|
|
15708
15839
|
success: false,
|
|
15709
15840
|
outputPath,
|
|
@@ -15868,11 +15999,17 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
15868
15999
|
}
|
|
15869
16000
|
audioSrcPath = trimmedPath;
|
|
15870
16001
|
}
|
|
15871
|
-
const
|
|
15872
|
-
|
|
15873
|
-
|
|
16002
|
+
const chain = element.fxChain ? parseAudioFxChain(element.fxChain) : void 0;
|
|
16003
|
+
const automation = element.automation ? resolveAutomation(parseAutomation(element.automation), chain) : null;
|
|
16004
|
+
const gainBeforeFx = chain !== void 0 && enabledAudioFxNodes(chain).length > 0;
|
|
16005
|
+
const laneKeyframes = automation ? volumeLaneKeyframes(
|
|
16006
|
+
automation,
|
|
16007
|
+
element.start,
|
|
16008
|
+
element.end - element.start,
|
|
16009
|
+
element.volume ?? 1,
|
|
16010
|
+
gainBeforeFx
|
|
15874
16011
|
) : null;
|
|
15875
|
-
const
|
|
16012
|
+
const tweened = !laneKeyframes && (element.volumeKeyframes?.length ?? 0) > 0;
|
|
15876
16013
|
const envelopeKeyframes = laneKeyframes ?? element.volumeKeyframes;
|
|
15877
16014
|
const envelope = envelopeKeyframes && envelopeKeyframes.length > 0 ? {
|
|
15878
16015
|
keyframes: envelopeKeyframes,
|
|
@@ -15881,8 +16018,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
15881
16018
|
} : null;
|
|
15882
16019
|
let bakedEnvelope = false;
|
|
15883
16020
|
let tailSeconds = 0;
|
|
15884
|
-
if (
|
|
15885
|
-
const chain = parseAudioFxChain(element.fxChain);
|
|
16021
|
+
if (chain) {
|
|
15886
16022
|
tailSeconds = chainTailSeconds(chain, automation ?? void 0);
|
|
15887
16023
|
const fxResult = await applyAudioFxChain(
|
|
15888
16024
|
audioSrcPath,
|
|
@@ -15892,7 +16028,8 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
15892
16028
|
trackId: element.id,
|
|
15893
16029
|
signal: effectiveSignal,
|
|
15894
16030
|
...automation ? { automation } : {},
|
|
15895
|
-
...envelope ? { envelope } : {}
|
|
16031
|
+
...envelope ? { envelope } : {},
|
|
16032
|
+
...gainBeforeFx ? { inputGain: clipGain(element.volume ?? 1, tweened) } : {}
|
|
15896
16033
|
}
|
|
15897
16034
|
);
|
|
15898
16035
|
audioSrcPath = fxResult.path;
|
|
@@ -15914,7 +16051,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
15914
16051
|
mediaStart: element.mediaStart,
|
|
15915
16052
|
duration: element.end - element.start,
|
|
15916
16053
|
// Gain is already in the samples when baked, so mix at unity.
|
|
15917
|
-
volume: bakedEnvelope ? 1 : element.volume ?? 1,
|
|
16054
|
+
volume: bakedEnvelope || gainBeforeFx && !tweened ? 1 : element.volume ?? 1,
|
|
15918
16055
|
volumeKeyframes: bakedEnvelope ? void 0 : envelopeKeyframes ?? void 0,
|
|
15919
16056
|
...element.fadeIn ? { fadeIn: element.fadeIn } : {},
|
|
15920
16057
|
...element.fadeOut ? { fadeOut: element.fadeOut } : {},
|
|
@@ -16031,7 +16168,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
|
|
|
16031
16168
|
parseAutomation(meta.automation),
|
|
16032
16169
|
meta.fxChain ? parseAudioFxChain(meta.fxChain) : void 0
|
|
16033
16170
|
) : null;
|
|
16034
|
-
const laneKeyframes = automation ? volumeLaneKeyframes(automation, 0, totalDuration) : null;
|
|
16171
|
+
const laneKeyframes = automation ? volumeLaneKeyframes(automation, 0, totalDuration, meta.volume) : null;
|
|
16035
16172
|
const envelope = laneKeyframes && laneKeyframes.length > 0 ? { keyframes: laneKeyframes, trackStart: 0, baseVolume: meta.volume } : null;
|
|
16036
16173
|
let groupSrcPath = groupWavPath;
|
|
16037
16174
|
let bakedEnvelope = false;
|
|
@@ -17316,6 +17453,7 @@ export {
|
|
|
17316
17453
|
assertConfiguredFfmpegBinariesExist,
|
|
17317
17454
|
cloneCaptureWarning,
|
|
17318
17455
|
cloneCaptureWarnings,
|
|
17456
|
+
installMediaRenderIdBridge,
|
|
17319
17457
|
createChromeMemorySampler,
|
|
17320
17458
|
CaptureFailure,
|
|
17321
17459
|
classifyCaptureFailure,
|
|
@@ -17344,6 +17482,7 @@ export {
|
|
|
17344
17482
|
getCompositionDuration,
|
|
17345
17483
|
percentileOf,
|
|
17346
17484
|
getCapturePerfSummary,
|
|
17485
|
+
frameSequencePath,
|
|
17347
17486
|
ManagedChildProcess,
|
|
17348
17487
|
trackChildProcess,
|
|
17349
17488
|
killTrackedProcesses,
|
|
@@ -17367,6 +17506,7 @@ export {
|
|
|
17367
17506
|
extractVideoMetadata,
|
|
17368
17507
|
extractAudioMetadata,
|
|
17369
17508
|
analyzeKeyframeIntervals,
|
|
17509
|
+
appendSoftwareEncoderThreadsArg,
|
|
17370
17510
|
PROVENANCE_RENDERER_TAG,
|
|
17371
17511
|
PROVENANCE_VERSION_TAG,
|
|
17372
17512
|
PROVENANCE_RENDERER_NAME,
|