hyperframes 0.8.185 → 0.8.188

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.
Files changed (186) hide show
  1. package/dist/{add-ODTNPRGG.js → add-UORF6235.js} +12 -12
  2. package/dist/{animatedGifPrep-PEP5GWOJ.js → animatedGifPrep-23MLSEXF.js} +7 -7
  3. package/dist/api.js +16 -16
  4. package/dist/{asset-EZG42XPA.js → asset-DOKDVHCW.js} +2 -2
  5. package/dist/{assetCataloger-OHAJFCA7.js → assetCataloger-CD27MHEY.js} +7 -7
  6. package/dist/audio-3NMZHNNU.js +2317 -0
  7. package/dist/{auth-VP2SIZFN.js → auth-AWKTSKGC.js} +1 -1
  8. package/dist/{autoUpdate-RVSUX4SS.js → autoUpdate-VBOI4CNU.js} +2 -2
  9. package/dist/{backgroundChecks-LEHK74NS.js → backgroundChecks-S4R454YU.js} +3 -3
  10. package/dist/backgroundChecksWorker.js +3 -3
  11. package/dist/{batchRender-3X6CL7PK.js → batchRender-EGPYNDHK.js} +4 -4
  12. package/dist/{beats-66VPQLSC.js → beats-YENEQUOA.js} +5 -5
  13. package/dist/{benchmark-PBJVTOJP.js → benchmark-THSJIQG7.js} +9 -9
  14. package/dist/{browser-TPL5MWGC.js → browser-BDMVQ6WJ.js} +3 -3
  15. package/dist/{bundleWithLocalizedFonts-67MLTHKM.js → bundleWithLocalizedFonts-3QG722YD.js} +2 -2
  16. package/dist/{capture-YYRE7EV3.js → capture-WM4XDMFX.js} +2 -2
  17. package/dist/{capture-F3GNMRZM.js → capture-Z6BGCTK3.js} +10 -10
  18. package/dist/{captureCompositionFrame-PXV2ISHD.js → captureCompositionFrame-EMHSAIBZ.js} +15 -11
  19. package/dist/{catalog-6SDD3OHF.js → catalog-7TV6UNCG.js} +12 -12
  20. package/dist/{catch-up-CWIVA5VG.js → catch-up-63IUDZRW.js} +4 -4
  21. package/dist/{check-YQ6REK2S.js → check-7RHZL4DV.js} +32 -32
  22. package/dist/{checkBrowser-5XZCBRXM.js → checkBrowser-PF56CT5X.js} +29 -29
  23. package/dist/{chunk-TAX2ZJAP.js → chunk-2DHCCBCN.js} +9 -9
  24. package/dist/{chunk-K73RJB23.js → chunk-2WUDFZCR.js} +2 -2
  25. package/dist/{chunk-4CTNDBE3.js → chunk-44QWRPGE.js} +2 -2
  26. package/dist/{chunk-X6L6MD5B.js → chunk-46VJ2UAV.js} +6 -6
  27. package/dist/{chunk-BRAPBUPQ.js → chunk-57XL23DE.js} +1 -1
  28. package/dist/{chunk-J36JVBLR.js → chunk-5C7R55BJ.js} +1 -1
  29. package/dist/{chunk-SKYPMHQC.js → chunk-5DPV7LU3.js} +3 -3
  30. package/dist/{chunk-D42NU3DZ.js → chunk-5OZI55AC.js} +1 -1
  31. package/dist/{chunk-5UBZPG74.js → chunk-6A45RKAK.js} +1 -1
  32. package/dist/{chunk-I7QMXZSB.js → chunk-6HYPEIH7.js} +2 -2
  33. package/dist/{chunk-KQ4JEJLF.js → chunk-6HZ7TSJJ.js} +14 -8
  34. package/dist/{chunk-6ASIXMFP.js → chunk-6OWNUXTU.js} +1 -1
  35. package/dist/{chunk-WQLKDL35.js → chunk-6Q5CFUAP.js} +1 -1
  36. package/dist/{chunk-G2UANIDX.js → chunk-B2P4JRUJ.js} +1 -1
  37. package/dist/chunk-BPX7LZG6.js +3896 -0
  38. package/dist/{chunk-EMAWIZ54.js → chunk-BU4HRISA.js} +58 -21
  39. package/dist/{chunk-ESBMUZXZ.js → chunk-BWEZDN6U.js} +1 -1
  40. package/dist/{chunk-5UDRPPN2.js → chunk-CE7EQUBV.js} +8 -5
  41. package/dist/{chunk-UPR3MU6Z.js → chunk-DA4TQFSF.js} +1 -1
  42. package/dist/{chunk-HDIRZGK4.js → chunk-DG4ME554.js} +1 -1
  43. package/dist/{chunk-RTBSA3EA.js → chunk-DLFAVRF3.js} +1 -1
  44. package/dist/{chunk-TTVHYD5R.js → chunk-EI7FP7F3.js} +1 -1
  45. package/dist/{chunk-RUEGYANA.js → chunk-FOLMMQF3.js} +11 -11
  46. package/dist/{chunk-5GUKZRQN.js → chunk-FSTLXRIN.js} +1 -1
  47. package/dist/{chunk-CX3USX23.js → chunk-FWVILDOZ.js} +3 -3
  48. package/dist/{chunk-IVEFOUZI.js → chunk-GYBPAKBM.js} +2287 -2264
  49. package/dist/{chunk-HXUTX5GR.js → chunk-HKUZCSNZ.js} +2 -2
  50. package/dist/{chunk-PMMQRJDE.js → chunk-HPQSUPR7.js} +1 -1
  51. package/dist/{chunk-7RAY4WZP.js → chunk-I7C5GUEQ.js} +3 -3
  52. package/dist/{chunk-55YELV7B.js → chunk-IA2IDJAG.js} +1 -1
  53. package/dist/{chunk-7PS6RVWD.js → chunk-IJYR4TTA.js} +1 -1
  54. package/dist/{chunk-6RIZY7OP.js → chunk-JWRT2IXO.js} +1 -1
  55. package/dist/{chunk-2TAS5O4P.js → chunk-JYC53CW4.js} +108 -20
  56. package/dist/{chunk-IDFNKFPL.js → chunk-KDBPZOXK.js} +18 -7
  57. package/dist/{chunk-ISTRDGIX.js → chunk-KWSEIVR6.js} +1 -1
  58. package/dist/{chunk-IPDKZ5IN.js → chunk-KY7375C4.js} +1 -1
  59. package/dist/{chunk-F3OPUWQX.js → chunk-L243NE72.js} +7 -7
  60. package/dist/{chunk-H6UY725W.js → chunk-LWY3PFJ7.js} +2 -2
  61. package/dist/{chunk-6GPR3G3C.js → chunk-LWYZN7HS.js} +1 -1
  62. package/dist/{chunk-OAG47RE6.js → chunk-MJKUCR3C.js} +1 -1
  63. package/dist/{chunk-SQVRXHOW.js → chunk-MTPPEEYZ.js} +3 -3
  64. package/dist/{chunk-TOI35GN2.js → chunk-MXXX6WVC.js} +1 -1
  65. package/dist/{chunk-BH7TTIPN.js → chunk-NFHRGHCJ.js} +5 -5
  66. package/dist/{chunk-IDGLGXPA.js → chunk-NFLZNSUZ.js} +1 -1
  67. package/dist/{chunk-K2ZKXTVL.js → chunk-NMOTNNP2.js} +1 -1
  68. package/dist/{chunk-LYKSTRAY.js → chunk-OJOZXWLC.js} +3 -3
  69. package/dist/{chunk-FX6RUJUY.js → chunk-OKSYDQHY.js} +3 -3
  70. package/dist/{chunk-H5SM5PJI.js → chunk-PMXR3IUH.js} +1 -1
  71. package/dist/{chunk-A6QCO474.js → chunk-PQLFJYGP.js} +5 -5
  72. package/dist/{chunk-PFD25NAC.js → chunk-RIJHDTM5.js} +4 -0
  73. package/dist/{chunk-Q47FAYYK.js → chunk-SMF6YAUL.js} +9 -11
  74. package/dist/{chunk-EMSDWBG3.js → chunk-T3OXKSRG.js} +2 -2
  75. package/dist/{chunk-VHZ6DL3M.js → chunk-TMK4Q7JA.js} +10 -7
  76. package/dist/{chunk-Q447CX26.js → chunk-TSFA2Y5C.js} +60 -47
  77. package/dist/{chunk-XWC7CNPY.js → chunk-UF6RCYA5.js} +1 -1
  78. package/dist/{chunk-I45HL3VP.js → chunk-V6NM4KL3.js} +3 -3
  79. package/dist/{chunk-FFJ5TDRD.js → chunk-VHBBV5DH.js} +10 -3
  80. package/dist/{chunk-HHSJJFVM.js → chunk-W2KGJHUL.js} +2 -2
  81. package/dist/{chunk-37CNYTQQ.js → chunk-X556BGL7.js} +1 -1
  82. package/dist/{chunk-UJKQWGFF.js → chunk-X7HBSTCZ.js} +8 -0
  83. package/dist/{chunk-CML25PAF.js → chunk-XNFDDVJS.js} +27 -3
  84. package/dist/{chunk-J5XEZ24I.js → chunk-XPVN4R66.js} +3 -3
  85. package/dist/{chunk-I3IH5MV5.js → chunk-YCW345M4.js} +222 -118
  86. package/dist/{chunk-KROXPHXX.js → chunk-YV4WT7CJ.js} +5 -9
  87. package/dist/{chunk-PHMBMOXF.js → chunk-YZ73IPW2.js} +48 -4
  88. package/dist/{clean-7J6ZCCGV.js → clean-43O47OTN.js} +16 -16
  89. package/dist/cli.js +53 -53
  90. package/dist/{cloud-7IJCJCDD.js → cloud-ZFWKFFSU.js} +4 -4
  91. package/dist/{cloudrun-UNZRIIDB.js → cloudrun-XBBESKEU.js} +5 -5
  92. package/dist/{compare-Q7VW5JZT.js → compare-OW4ELTFZ.js} +26 -26
  93. package/dist/{compiler-KPHYGFXL.js → compiler-23MT6CX6.js} +4 -4
  94. package/dist/{component-UUBREJKN.js → component-SDV7LWH6.js} +3 -3
  95. package/dist/{compositions-T4FTKIDP.js → compositions-DI5QZCEK.js} +11 -11
  96. package/dist/{delete-WZQF2IIA.js → delete-QY2NSQ2G.js} +4 -4
  97. package/dist/deploy-MJVC4EFY.js +300 -0
  98. package/dist/{destroy-4B5HW5FH.js → destroy-SSPAKADI.js} +13 -3
  99. package/dist/{dist-LM7AVHOU.js → dist-JH32CBQL.js} +1 -1
  100. package/dist/{dist-ULGTPSU4.js → dist-RFTGYT26.js} +7 -5
  101. package/dist/{doctor-VSNGRKXJ.js → doctor-7ZOECPHR.js} +11 -11
  102. package/dist/{events-E63YBUL2.js → events-WIX4JM2K.js} +3 -3
  103. package/dist/{events-CRW2DYUT.js → events-XVQQGECC.js} +2 -2
  104. package/dist/{feedback-OSZWVR6N.js → feedback-GBYBR6UO.js} +7 -7
  105. package/dist/{ffmpeg-JLH3Z7G7.js → ffmpeg-QGQBYMO3.js} +7 -7
  106. package/dist/{figma-2BQNNR53.js → figma-2CMKUGJQ.js} +3 -3
  107. package/dist/fontLocalizeCli.js +9 -9
  108. package/dist/{get-P2XI53QW.js → get-RWCNUOHU.js} +4 -4
  109. package/dist/{gpuPolicy-NTKQJY75.js → gpuPolicy-UGWSN32D.js} +7 -7
  110. package/dist/{grade-compare-OYASIHIB.js → grade-compare-J2MX7HZL.js} +27 -27
  111. package/dist/{help-HK6K2N4G.js → help-SH3ID7MV.js} +1 -1
  112. package/dist/{history-JMTYKLXW.js → history-C7V5QH7J.js} +6 -6
  113. package/dist/hyperframe.manifest.json +1 -1
  114. package/dist/hyperframe.runtime.iife.js +70 -69
  115. package/dist/hyperframes-player.global.js +3 -3
  116. package/dist/{info-AW7CIVRE.js → info-ZN3JJ3IN.js} +8 -8
  117. package/dist/{init-6KIDG4LB.js → init-KRNGSRNH.js} +65 -47
  118. package/dist/{inspect-B7BLYMRZ.js → inspect-AYDZXTX7.js} +27 -27
  119. package/dist/{installer-ZP2LQZUQ.js → installer-N6AIK6XQ.js} +5 -5
  120. package/dist/{keyframes-EQ36A5JE.js → keyframes-2RFUCEDK.js} +6 -6
  121. package/dist/{lambda-E6VURLIS.js → lambda-VW3CF6EE.js} +20 -13
  122. package/dist/{layout-XYC64QQQ.js → layout-OE64APE7.js} +27 -27
  123. package/dist/{lint-V5DJYWBX.js → lint-UXJTX5WS.js} +7 -7
  124. package/dist/{lintProject-JATQKR64.js → lintProject-XM3DLUKG.js} +1 -1
  125. package/dist/{list-RUGVRTVD.js → list-KOL6JPZJ.js} +7 -7
  126. package/dist/{login-ZTKGKXGS.js → login-MAQXOPDG.js} +4 -4
  127. package/dist/{manager-JUP7PM6N.js → manager-3LZKRK7P.js} +8 -8
  128. package/dist/{media-treatment-VIUPK4OR.js → media-treatment-COLSXXQJ.js} +13 -13
  129. package/dist/{models-LJS2HBOZ.js → models-XXWMGOD7.js} +11 -11
  130. package/dist/{motionShot-3UAXZ7SN.js → motionShot-RECBNTBZ.js} +14 -14
  131. package/dist/{normalize-4UDISLPL.js → normalize-3AYFSN2F.js} +1 -1
  132. package/dist/{normalize-audio-SBYI4YIH.js → normalize-audio-LKVGHZWR.js} +6 -6
  133. package/dist/{open-S27TUD3A.js → open-UEQVSRKH.js} +12 -12
  134. package/dist/{parakeetRunner-ETLMLJ4L.js → parakeetRunner-QVPCZ36U.js} +12 -12
  135. package/dist/{pipeline-I2M22M24.js → pipeline-5VSMQBKW.js} +8 -8
  136. package/dist/{play-COJU6VJK.js → play-MNEABYN4.js} +19 -19
  137. package/dist/{policies-XXNGXWHV.js → policies-MBWGLMWB.js} +17 -0
  138. package/dist/{preflight-FZ24NCHH.js → preflight-ONJ74DWQ.js} +8 -8
  139. package/dist/{present-YAHKLC5D.js → present-GQ24GTYX.js} +15 -15
  140. package/dist/{preview-KQOEU5YE.js → preview-ALL7O4RG.js} +18 -18
  141. package/dist/{producer-DTNNG4T6.js → producer-43L2UDF6.js} +1 -1
  142. package/dist/{progress-NHJIHAAG.js → progress-PX3PVAIM.js} +5 -3
  143. package/dist/{projectConfig-4A76VYUP.js → projectConfig-VJXUXK7U.js} +7 -7
  144. package/dist/{publication-SQPMC2W3.js → publication-L6E5NTFV.js} +3 -3
  145. package/dist/{publish-QUEJBGGT.js → publish-LCLBJEPR.js} +9 -9
  146. package/dist/{remove-background-FP2ETAQH.js → remove-background-PDL4KPR4.js} +9 -9
  147. package/dist/{render-P664SMC7.js → render-7HVKJ7T4.js} +9 -7
  148. package/dist/{render-BIFIL4U6.js → render-J33ZHR6I.js} +44 -28
  149. package/dist/{render-GQFXJWLV.js → render-OPCRLZVL.js} +31 -22
  150. package/dist/{render-batch-ZCPMYH6Z.js → render-batch-ZHHIGXU3.js} +5 -5
  151. package/dist/renderSetupWorker.js +1 -1
  152. package/dist/{resolver-BUC2GFZQ.js → resolver-KNNIMMBM.js} +5 -5
  153. package/dist/{sam-MC24SZHC.js → sam-FHJ7IL7T.js} +3 -1
  154. package/dist/{sherpa-HFT6XEP6.js → sherpa-GI62H3H5.js} +11 -11
  155. package/dist/{sites-MAVH57MO.js → sites-5PWHAMEG.js} +1 -1
  156. package/dist/skills/hyperframes/references/motion.md +1 -0
  157. package/dist/{skills-3WEVO3NU.js → skills-IUTD6VQO.js} +5 -5
  158. package/dist/{skillsUpdateCheck-BFPWFOK5.js → skillsUpdateCheck-XZE7MJG6.js} +3 -3
  159. package/dist/{snapshot-WDQ6S7CJ.js → snapshot-XEXTHLZP.js} +37 -34
  160. package/dist/{src-NPR2JZOJ.js → src-6STRGCDC.js} +8 -8
  161. package/dist/{src-RZ574OKP.js → src-ZPIV4PGX.js} +10 -6
  162. package/dist/{state-ORRPOLMQ.js → state-JPNCCXAV.js} +3 -1
  163. package/dist/{staticProjectServer-FTGTEZI4.js → staticProjectServer-HJLT7MHE.js} +15 -15
  164. package/dist/studio/assets/{hyperframes-player-CoOdf435.js → hyperframes-player-DwWIKKZG.js} +1 -1
  165. package/dist/studio/assets/{index-CR85xg2x.js → index-Cjwz7_13.js} +1 -1
  166. package/dist/studio/assets/{index-C_6rTpaB.js → index-M8lEpS0Q.js} +131 -131
  167. package/dist/studio/index.html +1 -1
  168. package/dist/{studio-api-TAWB7B23.js → studio-api-OBHEB5ND.js} +9 -9
  169. package/dist/{studioServer-HRQGJTBU.js → studioServer-KRIZFYML.js} +47 -46
  170. package/dist/{telemetry-QZ6HJF4W.js → telemetry-N5OOVBCP.js} +3 -3
  171. package/dist/templates/_shared/AGENTS.md +5 -0
  172. package/dist/templates/_shared/CLAUDE.md +5 -0
  173. package/dist/{timeline-IKUEW42T.js → timeline-YUFH65OG.js} +17 -17
  174. package/dist/{tokens-AUGUEWHS.js → tokens-K4VYLSWJ.js} +2 -2
  175. package/dist/{transcribe-7734CG3N.js → transcribe-CY4CVO54.js} +17 -17
  176. package/dist/{transcribe-FWU6EMEO.js → transcribe-GMQXKDYN.js} +10 -10
  177. package/dist/{updateCheck-VIPQ3FPW.js → updateCheck-2625RXK4.js} +2 -2
  178. package/dist/{upgrade-4VNDQTVO.js → upgrade-6ZY5MSBQ.js} +2 -2
  179. package/dist/{urlDownloader-6CXL4CL7.js → urlDownloader-PVJWG7V3.js} +6 -6
  180. package/dist/{validate-QWCMVWID.js → validate-XSRHEE7E.js} +21 -21
  181. package/dist/{video-245Q5WN4.js → video-MBFHC54F.js} +7 -7
  182. package/dist/warmCheck.js +34 -34
  183. package/package.json +18 -2
  184. package/dist/audio-65R4T74I.js +0 -1154
  185. package/dist/chunk-3NPOFC43.js +0 -3805
  186. package/dist/deploy-JU7A3XNC.js +0 -196
@@ -26,7 +26,7 @@ import {
26
26
  renderFrameIdForRenderId,
27
27
  resolveAuthoredTimingWindow,
28
28
  toFps
29
- } from "./chunk-KROXPHXX.js";
29
+ } from "./chunk-YV4WT7CJ.js";
30
30
  import {
31
31
  AUDIO_GROUP_RENDER_ID_ATTR,
32
32
  COLOR_GRADING_SOURCE_HIDDEN_ATTR,
@@ -37,10 +37,16 @@ import {
37
37
  HF_COLOR_GRADING_CANVAS_ID_PREFIX,
38
38
  MEDIA_DURATION_CLAMP_EPSILON_SECONDS,
39
39
  MEDIA_RENDER_ID_ATTR,
40
+ PAGE_DEVICE_SCALE_GLOBAL,
41
+ PAGE_MOTION_BLUR_OFF_GLOBAL,
40
42
  VOLUME_TARGET,
43
+ acceleratedCanvasTrackerSource,
41
44
  enabledAudioFxNodes,
42
45
  fxAutomationTarget,
46
+ gainedVolume,
47
+ gainedVolumePoints,
43
48
  injectScriptsIntoHtml,
49
+ isBentSegment,
44
50
  normalizeAudioFxParams,
45
51
  normalizePlaybackRate,
46
52
  normalizeRateSpec,
@@ -55,12 +61,14 @@ import {
55
61
  serializeAutomation,
56
62
  shiftRateLane,
57
63
  sourceTimeAt,
58
- timeAtSourceTime
59
- } from "./chunk-3NPOFC43.js";
64
+ timeAtSourceTime,
65
+ volumeSegmentSampleCount
66
+ } from "./chunk-BPX7LZG6.js";
60
67
  import {
61
68
  clampAudioGain,
69
+ clipGain,
62
70
  isAudibleVideoElement
63
- } from "./chunk-UJKQWGFF.js";
71
+ } from "./chunk-X7HBSTCZ.js";
64
72
  import {
65
73
  redactTelemetryString,
66
74
  redactUrlQueryStrings
@@ -75,7 +83,7 @@ import {
75
83
  } from "./chunk-4UMPIPCQ.js";
76
84
  import {
77
85
  resolveReferencedStart
78
- } from "./chunk-Q447CX26.js";
86
+ } from "./chunk-TSFA2Y5C.js";
79
87
  import {
80
88
  FFMPEG_PATH_ENV,
81
89
  FFPROBE_PATH_ENV,
@@ -2506,6 +2514,68 @@ var MotionBlurAccumulator = class {
2506
2514
  }
2507
2515
  };
2508
2516
 
2517
+ // ../engine/src/services/pageMotionBlur.ts
2518
+ function pageOffReason(options) {
2519
+ if (options.motionBlur) return "engine-motion-blur";
2520
+ if (options.pageMotionBlur === "off") return "preview-unsupported";
2521
+ return options.pageMotionBlur ?? null;
2522
+ }
2523
+ async function installPageMotionBlur(page, options) {
2524
+ await page.evaluateOnNewDocument(acceleratedCanvasTrackerSource());
2525
+ const scale = options.deviceScaleFactor ?? 1;
2526
+ if (scale > 1) {
2527
+ await page.evaluateOnNewDocument(
2528
+ (key, v) => {
2529
+ window[key] = v;
2530
+ },
2531
+ PAGE_DEVICE_SCALE_GLOBAL,
2532
+ scale
2533
+ );
2534
+ }
2535
+ const off = pageOffReason(options);
2536
+ if (!off) return;
2537
+ await page.evaluateOnNewDocument(
2538
+ (key, v) => {
2539
+ window[key] = v;
2540
+ },
2541
+ PAGE_MOTION_BLUR_OFF_GLOBAL,
2542
+ off
2543
+ );
2544
+ }
2545
+ async function readPageMotionBlur(page) {
2546
+ return page.evaluate(() => {
2547
+ const api = Reflect.get(window, "__hfMotionBlur");
2548
+ const report = api && typeof api === "object" ? Reflect.get(api, "report") : null;
2549
+ const got = typeof report === "function" ? report() : null;
2550
+ if (!got || typeof got !== "object") return null;
2551
+ return {
2552
+ state: String(Reflect.get(got, "state")),
2553
+ code: String(Reflect.get(got, "reasonCode")),
2554
+ movers: Number(Reflect.get(got, "movers")),
2555
+ failedFrames: Number(Reflect.get(got, "failedFrames")) || 0
2556
+ };
2557
+ });
2558
+ }
2559
+ var formatPageMotionBlur = (r) => `[motion-blur] ${r.state} reasonCode=${r.code} movers=${r.movers} failedFrames=${r.failedFrames}`;
2560
+ async function describePageMotionBlur(page) {
2561
+ const r = await readPageMotionBlur(page);
2562
+ return r && formatPageMotionBlur(r);
2563
+ }
2564
+ async function describePageMotionBlurAtEnd(page, timeoutMs = 1500) {
2565
+ let timer;
2566
+ const stuck = new Promise((resolve) => {
2567
+ timer = setTimeout(() => resolve(null), timeoutMs);
2568
+ });
2569
+ try {
2570
+ const r = await Promise.race([readPageMotionBlur(page), stuck]);
2571
+ return r && r.failedFrames > 0 ? `${formatPageMotionBlur(r)} (session end)` : null;
2572
+ } catch {
2573
+ return null;
2574
+ } finally {
2575
+ clearTimeout(timer);
2576
+ }
2577
+ }
2578
+
2509
2579
  // ../engine/src/services/videoFrameTransport.ts
2510
2580
  import { randomUUID } from "crypto";
2511
2581
  var base64ImagePrefix = /^data:(image\/[\w.+-]+);base64,/;
@@ -2926,6 +2996,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
2926
2996
  async (items, visualProperties, colorGradingSourceHiddenAttr) => {
2927
2997
  const injectedIds = [];
2928
2998
  const pendingDecodes = [];
2999
+ const imagesToPublish = [];
2929
3000
  const replacementLayoutProperties = /* @__PURE__ */ new Set([
2930
3001
  "width",
2931
3002
  "height",
@@ -2997,6 +3068,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
2997
3068
  img.style.objectFit = computedStyle.objectFit;
2998
3069
  img.style.objectPosition = computedStyle.objectPosition;
2999
3070
  img.style.zIndex = computedStyle.zIndex;
3071
+ img.style.setProperty("visibility", "hidden", "important");
3000
3072
  img.decoding = "sync";
3001
3073
  if (img.getAttribute("src") !== item.dataUri) {
3002
3074
  if (item.crossOrigin) img.crossOrigin = item.crossOrigin;
@@ -3012,7 +3084,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
3012
3084
  );
3013
3085
  }
3014
3086
  img.style.opacity = String(computedOpacity);
3015
- img.style.visibility = "visible";
3087
+ imagesToPublish.push(img);
3016
3088
  video.style.setProperty("visibility", "hidden", "important");
3017
3089
  video.style.setProperty("pointer-events", "none", "important");
3018
3090
  injectedIds.push(item.videoId);
@@ -3020,6 +3092,7 @@ async function injectResolvedVideoFramesBatch(page, updates) {
3020
3092
  if (pendingDecodes.length > 0) {
3021
3093
  await Promise.all(pendingDecodes);
3022
3094
  }
3095
+ for (const img of imagesToPublish) img.style.visibility = "visible";
3023
3096
  if (injectedIds.length > 0) {
3024
3097
  const redraw = window.__hf?.colorGrading?.redraw;
3025
3098
  redraw?.();
@@ -3058,7 +3131,7 @@ async function syncVideoFrameVisibility(page, activeVideoIds) {
3058
3131
  const img = video.nextElementSibling;
3059
3132
  const hasImg = img && img.classList.contains("__render_frame__");
3060
3133
  const ancestorHidden = isVisualAncestorHidden(video);
3061
- const visible = active.has(video.id) && !ancestorHidden;
3134
+ const visible = active.has(window.__hfMediaId?.(video) || video.id) && !ancestorHidden;
3062
3135
  if (visible) {
3063
3136
  video.style.setProperty("visibility", "hidden", "important");
3064
3137
  video.style.setProperty("pointer-events", "none", "important");
@@ -5437,6 +5510,7 @@ async function constructCaptureSession(input) {
5437
5510
  }
5438
5511
  }, variablesJson);
5439
5512
  }
5513
+ await installPageMotionBlur(page, options);
5440
5514
  const browserVersion = await browser.version();
5441
5515
  const sessionOptions = resolveCaptureSessionOptions(options, browserVersion);
5442
5516
  const expectedMajor = config?.expectedChromiumMajor;
@@ -6614,6 +6688,7 @@ async function createStaticVerificationPage(session) {
6614
6688
  window.__hfVariables = JSON.parse(json);
6615
6689
  }, variablesJson);
6616
6690
  }
6691
+ await installPageMotionBlur(page, session.options);
6617
6692
  await page.setViewport({
6618
6693
  width: session.options.width,
6619
6694
  height: session.options.height,
@@ -7159,6 +7234,10 @@ async function captureFrameCore(session, frameIndex, time) {
7159
7234
  session.capturePerf.screenshotMs += screenshotMs;
7160
7235
  session.capturePerf.totalMs += captureTimeMs;
7161
7236
  session.capturePerf.frameMs.push(captureTimeMs);
7237
+ if (session.capturePerf.frames === 1) {
7238
+ const blur = await describePageMotionBlur(session.page).catch(() => null);
7239
+ if (blur) console.log(blur);
7240
+ }
7162
7241
  if (session.staticFrames) {
7163
7242
  session.lastFrameBuffer = buffer;
7164
7243
  session.lastFrameAbsoluteIndex = absFrameIndex;
@@ -7371,6 +7450,10 @@ async function closeCaptureSession(session) {
7371
7450
  session.chromeMemory.stop();
7372
7451
  await session.chromeMemory.sampleOnce();
7373
7452
  }
7453
+ if (session.page && !session.pageReleased) {
7454
+ const blur = await describePageMotionBlurAtEnd(session.page);
7455
+ if (blur) console.log(blur);
7456
+ }
7374
7457
  const reused = session.staticDedupCount ?? 0;
7375
7458
  if (session.staticFrames && reused > 0) {
7376
7459
  const captured = session.capturePerf.frames;
@@ -7559,6 +7642,57 @@ function getCapturePerfSummary(session) {
7559
7642
  };
7560
7643
  }
7561
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
+
7562
7696
  // ../engine/src/services/chunkEncoder.ts
7563
7697
  import {
7564
7698
  closeSync,
@@ -7569,12 +7703,12 @@ import {
7569
7703
  mkdtempSync,
7570
7704
  openSync,
7571
7705
  readFileSync as readFileSync3,
7572
- readdirSync as readdirSync2,
7706
+ readdirSync as readdirSync3,
7573
7707
  statSync as statSync3,
7574
7708
  writeFileSync as writeFileSync2,
7575
7709
  writeSync
7576
7710
  } from "fs";
7577
- import { join as join5, dirname as dirname2, extname } from "path";
7711
+ import { join as join6, dirname as dirname2, extname } from "path";
7578
7712
 
7579
7713
  // ../engine/src/utils/gpuEncoder.ts
7580
7714
  import { spawn } from "child_process";
@@ -8861,7 +8995,7 @@ async function analyzeKeyframeIntervalsUncached(filePath, videoStreamStartSecond
8861
8995
 
8862
8996
  // ../engine/src/utils/renderProvenance.ts
8863
8997
  import { readFileSync as readFileSync2 } from "fs";
8864
- import { dirname, join as join4 } from "path";
8998
+ import { dirname, join as join5 } from "path";
8865
8999
  import { fileURLToPath } from "url";
8866
9000
  var PROVENANCE_RENDERER_TAG = "hyperframes_renderer";
8867
9001
  var PROVENANCE_VERSION_TAG = "hyperframes_version";
@@ -8872,7 +9006,7 @@ var MAX_WALK_DEPTH = 12;
8872
9006
  function resolveOwnVersionFrom(startDir) {
8873
9007
  let dir = startDir;
8874
9008
  for (let depth = 0; depth < MAX_WALK_DEPTH; depth++) {
8875
- const candidate = join4(dir, "package.json");
9009
+ const candidate = join5(dir, "package.json");
8876
9010
  let pkg;
8877
9011
  try {
8878
9012
  pkg = JSON.parse(readFileSync2(candidate, "utf8"));
@@ -9205,7 +9339,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
9205
9339
  const startTime = Date.now();
9206
9340
  const outputDir = dirname2(outputPath);
9207
9341
  if (!existsSync5(outputDir)) mkdirSync2(outputDir, { recursive: true });
9208
- const files = readdirSync2(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
9342
+ const files = readdirSync3(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i));
9209
9343
  const frameCount = files.length;
9210
9344
  if (frameCount === 0) {
9211
9345
  return {
@@ -9221,7 +9355,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
9221
9355
  if (options.useGpu) {
9222
9356
  gpuEncoder = await getCachedGpuEncoder();
9223
9357
  }
9224
- const inputPath = join5(framesDir, framePattern);
9358
+ const inputPath = frameSequencePath(framesDir, framePattern);
9225
9359
  const inputArgs = ["-framerate", fpsToFfmpegArg(options.fps), "-i", inputPath];
9226
9360
  const args = buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder);
9227
9361
  const encodeTimeout = config?.ffmpegEncodeTimeout ?? DEFAULT_CONFIG.ffmpegEncodeTimeout;
@@ -9277,7 +9411,7 @@ function buildConcatArgs(concatListPath, outputPath) {
9277
9411
  var concatListSeq = 0;
9278
9412
  function writeConcatList(dir, inputPaths) {
9279
9413
  concatListSeq += 1;
9280
- const listPath = join5(dir, `concat-list-${process.pid}-${concatListSeq}.txt`);
9414
+ const listPath = join6(dir, `concat-list-${process.pid}-${concatListSeq}.txt`);
9281
9415
  const body = inputPaths.map((p) => `file '${p.replace(/'/g, "'\\''")}'`).join("\n");
9282
9416
  writeFileSync2(listPath, body, "utf-8");
9283
9417
  return listPath;
@@ -9307,7 +9441,7 @@ async function concatVideoFiles(inputPaths, outputPath, signal, config) {
9307
9441
  }
9308
9442
  async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, options, chunkSizeFrames, signal, config, onFramesEncoded) {
9309
9443
  const start = Date.now();
9310
- const files = readdirSync2(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
9444
+ const files = readdirSync3(framesDir).filter((f) => f.match(/\.(jpg|jpeg|png)$/i)).sort();
9311
9445
  if (files.length === 0) {
9312
9446
  return {
9313
9447
  success: false,
@@ -9321,7 +9455,7 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
9321
9455
  const chunkSize = Math.max(30, Math.floor(chunkSizeFrames));
9322
9456
  const chunkCount = Math.ceil(files.length / chunkSize);
9323
9457
  mkdirSync2(dirname2(outputPath), { recursive: true });
9324
- const chunkDir = mkdtempSync(join5(dirname2(outputPath), "chunk-encode-"));
9458
+ const chunkDir = mkdtempSync(join6(dirname2(outputPath), "chunk-encode-"));
9325
9459
  const chunkPaths = [];
9326
9460
  for (let i = 0; i < chunkCount; i++) {
9327
9461
  if (signal?.aborted) {
@@ -9337,8 +9471,8 @@ async function encodeFramesChunkedConcat(framesDir, framePattern, outputPath, op
9337
9471
  const startNumber = i * chunkSize;
9338
9472
  const framesInChunk = Math.min(chunkSize, files.length - startNumber);
9339
9473
  const ext = outputPath.endsWith(".webm") ? ".webm" : outputPath.endsWith(".mov") ? ".mov" : ".mp4";
9340
- const chunkPath = join5(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
9341
- const inputPath = join5(framesDir, framePattern);
9474
+ const chunkPath = join6(chunkDir, `chunk_${String(i).padStart(4, "0")}${ext}`);
9475
+ const inputPath = frameSequencePath(framesDir, framePattern);
9342
9476
  const inputArgs = [
9343
9477
  "-framerate",
9344
9478
  fpsToFfmpegArg(options.fps),
@@ -9507,14 +9641,14 @@ async function packageHls(videoPath, audioPath, outputDir, options) {
9507
9641
  "-master_pl_name",
9508
9642
  HLS_MASTER_PLAYLIST,
9509
9643
  "-hls_segment_filename",
9510
- join5(outputDir, "%v_%05d.ts"),
9644
+ join6(outputDir, "%v_%05d.ts"),
9511
9645
  // Without these the mpegts muxer starts the stream at PTS 1.4 s.
9512
9646
  "-muxdelay",
9513
9647
  "0",
9514
9648
  "-muxpreload",
9515
9649
  "0"
9516
9650
  );
9517
- args.push("-y", join5(outputDir, "%v.m3u8"));
9651
+ args.push("-y", join6(outputDir, "%v.m3u8"));
9518
9652
  const processTimeout = options.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
9519
9653
  const result = await runFfmpeg(args, { signal, timeout: processTimeout });
9520
9654
  if (signal?.aborted) {
@@ -9526,7 +9660,7 @@ async function packageHls(videoPath, audioPath, outputDir, options) {
9526
9660
  };
9527
9661
  }
9528
9662
  if (result.success && hasAudio) {
9529
- const masterPath = join5(outputDir, HLS_MASTER_PLAYLIST);
9663
+ const masterPath = join6(outputDir, HLS_MASTER_PLAYLIST);
9530
9664
  let master;
9531
9665
  try {
9532
9666
  master = openSync(masterPath, "r+", 384);
@@ -10147,7 +10281,7 @@ function isKnownInactiveTimelineWindow(element, resolvedStart) {
10147
10281
  // ../engine/src/utils/alphaPlaneProbe.ts
10148
10282
  import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync } from "fs";
10149
10283
  import { tmpdir as tmpdir2 } from "os";
10150
- import { join as join6 } from "path";
10284
+ import { join as join7 } from "path";
10151
10285
  var BYTES_PER_SAMPLE_FRAME = 8 * 8 * 4;
10152
10286
  var MAX_SAMPLE_BYTES = BYTES_PER_SAMPLE_FRAME * 3;
10153
10287
  function sampledRgbaAlphaIsFullyOpaque(buf) {
@@ -10162,8 +10296,8 @@ function sampledRgbaAlphaIsFullyOpaque(buf) {
10162
10296
  async function probeInputAlphaPlane(videoPath, decoder, signal) {
10163
10297
  let probeDir;
10164
10298
  try {
10165
- probeDir = mkdtempSync2(join6(tmpdir2(), "hf-alpha-probe-"));
10166
- const samplePath = join6(probeDir, "alpha.raw");
10299
+ probeDir = mkdtempSync2(join7(tmpdir2(), "hf-alpha-probe-"));
10300
+ const samplePath = join7(probeDir, "alpha.raw");
10167
10301
  const result = await runFfmpeg(
10168
10302
  [
10169
10303
  "-v",
@@ -10209,7 +10343,7 @@ import {
10209
10343
  mkdirSync as mkdirSync4,
10210
10344
  lstatSync,
10211
10345
  openSync as openSync2,
10212
- readdirSync as readdirSync3,
10346
+ readdirSync as readdirSync4,
10213
10347
  rmdirSync,
10214
10348
  rmSync as rmSync2,
10215
10349
  statSync as statSync5,
@@ -10217,7 +10351,7 @@ import {
10217
10351
  } from "fs";
10218
10352
  import { createHash, randomUUID as randomUUID2 } from "crypto";
10219
10353
  import { BlockList, isIP } from "net";
10220
- import { dirname as dirname4, extname as extname2, join as join7 } from "path";
10354
+ import { dirname as dirname4, extname as extname2, join as join8 } from "path";
10221
10355
  import { Readable, Transform } from "stream";
10222
10356
  import { pipeline } from "stream/promises";
10223
10357
  var inFlightDownloads = /* @__PURE__ */ new Map();
@@ -10387,7 +10521,7 @@ function sameCacheLockStatGeneration(left, right) {
10387
10521
  function observeCachePathLock(lockPath) {
10388
10522
  for (let pass = 0; pass < 3; pass += 1) {
10389
10523
  const before = lstatSync(lockPath);
10390
- const owner = readdirSync3(lockPath).find((name) => name.startsWith(CACHE_LOCK_OWNER_PREFIX));
10524
+ const owner = readdirSync4(lockPath).find((name) => name.startsWith(CACHE_LOCK_OWNER_PREFIX));
10391
10525
  const after = lstatSync(lockPath);
10392
10526
  if (sameCacheLockStatGeneration(before, after)) return { stats: after, owner };
10393
10527
  }
@@ -10409,7 +10543,7 @@ function removeCacheLockDirectoryIfEmpty(lockPath) {
10409
10543
  }
10410
10544
  function releaseOwnedCachePathLock(lockPath, owner) {
10411
10545
  try {
10412
- rmdirSync(join7(lockPath, owner));
10546
+ rmdirSync(join8(lockPath, owner));
10413
10547
  } catch (error) {
10414
10548
  if (error.code === "ENOENT") return;
10415
10549
  throw error;
@@ -10450,12 +10584,12 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
10450
10584
  if (createdLock) {
10451
10585
  const owner = `${CACHE_LOCK_OWNER_PREFIX}${randomUUID2()}`;
10452
10586
  try {
10453
- mkdirSync4(join7(lockPath, owner));
10454
- const entries = readdirSync3(lockPath);
10587
+ mkdirSync4(join8(lockPath, owner));
10588
+ const entries = readdirSync4(lockPath);
10455
10589
  if (entries.length === 1 && entries[0] === owner) {
10456
10590
  return () => releaseOwnedCachePathLock(lockPath, owner);
10457
10591
  }
10458
- rmdirSync(join7(lockPath, owner));
10592
+ rmdirSync(join8(lockPath, owner));
10459
10593
  removeCacheLockDirectoryIfEmpty(lockPath);
10460
10594
  continue;
10461
10595
  } catch (error) {
@@ -10473,7 +10607,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
10473
10607
  if (Date.now() - observedLock.stats.mtimeMs > CACHE_LOCK_STALE_MS) {
10474
10608
  if (observedLock.owner) {
10475
10609
  try {
10476
- rmdirSync(join7(lockPath, observedLock.owner));
10610
+ rmdirSync(join8(lockPath, observedLock.owner));
10477
10611
  } catch (error) {
10478
10612
  if (error.code === "ENOENT") continue;
10479
10613
  throw error;
@@ -10481,7 +10615,7 @@ async function acquireCachePathLock(localPath, timeoutMs, signal) {
10481
10615
  removeCacheLockDirectoryIfEmpty(lockPath);
10482
10616
  continue;
10483
10617
  }
10484
- const reclaimPath = join7(lockPath, CACHE_LOCK_RECLAIM_NAME);
10618
+ const reclaimPath = join8(lockPath, CACHE_LOCK_RECLAIM_NAME);
10485
10619
  try {
10486
10620
  mkdirSync4(reclaimPath);
10487
10621
  } catch (error) {
@@ -10921,8 +11055,8 @@ function emitDownloadTelemetry(options, event) {
10921
11055
  }
10922
11056
  }
10923
11057
  async function runDownloadAttempt(url, localPath, timeoutMs, attempt, options, signal) {
10924
- const attemptDir = mkdtempSync3(join7(dirname4(localPath), ".hf-download-"));
10925
- const partialPath = join7(attemptDir, "payload");
11058
+ const attemptDir = mkdtempSync3(join8(dirname4(localPath), ".hf-download-"));
11059
+ const partialPath = join8(attemptDir, "payload");
10926
11060
  const controller = new AbortController();
10927
11061
  let timedOut = false;
10928
11062
  let callerAborted = signal?.aborted ?? false;
@@ -11158,7 +11292,7 @@ async function downloadToTemp(url, destDir, timeoutMs = 3e5, signal, onTransient
11158
11292
  mkdirSync4(destDir, { recursive: true });
11159
11293
  }
11160
11294
  const filename = getFilenameFromUrl(url, validationScope);
11161
- const localPath = join7(destDir, filename);
11295
+ const localPath = join8(destDir, filename);
11162
11296
  const downloadPromise = (async () => {
11163
11297
  const cacheStartedAt = Date.now();
11164
11298
  const reused = await reuseOrInvalidateCachedFile(
@@ -11242,56 +11376,6 @@ import {
11242
11376
  writeFileSync as writeFileSync3
11243
11377
  } from "fs";
11244
11378
  import { join as join9 } from "path";
11245
-
11246
- // ../engine/src/services/extractedFrameIndex.ts
11247
- import { readdirSync as readdirSync4 } from "fs";
11248
- import { join as join8 } from "path";
11249
- var FRAME_FILENAME_PREFIX = "frame_";
11250
- var ExtractedFrameSequenceError = class extends Error {
11251
- constructor(message) {
11252
- super(message);
11253
- this.name = "ExtractedFrameSequenceError";
11254
- }
11255
- };
11256
- function extractedFrameIndex(file, format) {
11257
- const match = new RegExp(`^${FRAME_FILENAME_PREFIX}(\\d+)\\.${format}$`).exec(file);
11258
- if (!match) {
11259
- throw new ExtractedFrameSequenceError(`Invalid extracted frame filename: ${file}`);
11260
- }
11261
- const ordinal = Number.parseInt(match[1], 10);
11262
- if (!Number.isSafeInteger(ordinal) || ordinal < 1) {
11263
- throw new ExtractedFrameSequenceError(`Invalid extracted frame ordinal: ${file}`);
11264
- }
11265
- return ordinal - 1;
11266
- }
11267
- function framePathsFromDirectory(outputDir, format, allowGaps = false) {
11268
- const suffix = `.${format}`;
11269
- const indexed = /* @__PURE__ */ new Map();
11270
- for (const file of readdirSync4(outputDir)) {
11271
- if (!file.startsWith(FRAME_FILENAME_PREFIX) || !file.endsWith(suffix)) continue;
11272
- const index = extractedFrameIndex(file, format);
11273
- if (indexed.has(index)) {
11274
- throw new ExtractedFrameSequenceError(
11275
- `Duplicate extracted frame index ${index}: ${indexed.get(index)} and ${file}`
11276
- );
11277
- }
11278
- indexed.set(index, join8(outputDir, file));
11279
- }
11280
- if (allowGaps) return new Map([...indexed].sort(([a], [b]) => a - b));
11281
- const ordered = /* @__PURE__ */ new Map();
11282
- for (let index = 0; index < indexed.size; index += 1) {
11283
- const path = indexed.get(index);
11284
- if (!path) {
11285
- throw new ExtractedFrameSequenceError(
11286
- `Missing extracted frame index ${index} in ${outputDir}`
11287
- );
11288
- }
11289
- ordered.set(index, path);
11290
- }
11291
- return ordered;
11292
- }
11293
-
11294
- // ../engine/src/services/extractionCache.ts
11295
11379
  var COMPLETE_SENTINEL = ".hf-complete";
11296
11380
  var GC_MARKER = ".hf-last-gc";
11297
11381
  var SCHEMA_PREFIX = "hfcache-v8-";
@@ -12043,7 +12127,7 @@ async function extractVideoFramesRangeUnredacted(videoPath, videoId, startTime,
12043
12127
  }
12044
12128
  const format = resolveFrameFormat(metadata, options.format);
12045
12129
  const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${format}`;
12046
- const outputPattern = join10(videoOutputDir, framePattern);
12130
+ const outputPattern = frameSequencePath(videoOutputDir, framePattern);
12047
12131
  const isHdr = isHdrColorSpace(metadata.colorSpace);
12048
12132
  const isMacOS = process.platform === "darwin";
12049
12133
  const toneMappedToSdr = options.toneMapHdrToSdr === true && isHdr && await toneMapsWithZscale();
@@ -13979,6 +14063,8 @@ async function encodeAac(input, outputPath, totalDuration, {
13979
14063
  segments.map(
13980
14064
  ({ trim, path }) => runFfmpeg(
13981
14065
  [
14066
+ "-cpuflags",
14067
+ "0",
13982
14068
  "-i",
13983
14069
  input,
13984
14070
  "-af",
@@ -14504,7 +14590,7 @@ import { join as join13 } from "path";
14504
14590
  import { pathToFileURL } from "url";
14505
14591
 
14506
14592
  // ../core/dist/generated/audio-fx-runtime-inline.js
14507
- 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';
14593
+ 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';
14508
14594
  function getAudioFxRuntimeScript() {
14509
14595
  return AUDIO_FX_RUNTIME_IIFE;
14510
14596
  }
@@ -14690,6 +14776,9 @@ function applyEnvelopeToPlanes(planes, sampleRate, gainAt, startFrame = 0) {
14690
14776
  for (const plane of planes) plane[frame] = (plane[frame] ?? 0) * gain;
14691
14777
  }
14692
14778
  }
14779
+ function applyInputGain(planes, gain = 1) {
14780
+ if (gain !== 1) applyEnvelopeToPlanes(planes, 1, () => gain);
14781
+ }
14693
14782
  var DEFAULT_STREAMING_LIMITS = {
14694
14783
  aboveBytes: 512 * 1024 * 1024,
14695
14784
  segmentSeconds: 300,
@@ -14763,6 +14852,7 @@ async function renderInSegments(openPage, inputWav, header, chain, outputWav, op
14763
14852
  throw new AudioFxRenderError(`Audio FX cancelled for track ${options.trackId}`);
14764
14853
  }
14765
14854
  const planes = deinterleave(readFrames(inFd, header, seg.readFrom, seg.readTo), channels);
14855
+ applyInputGain(planes, options.inputGain);
14766
14856
  const page = await openPage();
14767
14857
  let outPlanes;
14768
14858
  try {
@@ -14822,6 +14912,7 @@ async function applyAudioFxChain(inputWav, chain, outputWav, options) {
14822
14912
  }
14823
14913
  const input = readWav(inputWav);
14824
14914
  const planes = deinterleave(input.samples, input.channels);
14915
+ applyInputGain(planes, options.inputGain);
14825
14916
  if ((planes[0]?.length ?? 0) === 0) return { path: inputWav, envelopeBaked: false };
14826
14917
  return withFxBrowser(options, async (openPage) => {
14827
14918
  const page = await openPage();
@@ -15181,7 +15272,7 @@ function downloadFailure(error, elementId) {
15181
15272
  detail: boundedDetail(`Download failed for audio element ${elementId}: ${message}`)
15182
15273
  };
15183
15274
  }
15184
- function ffmpegFailure(stage, result, elementId) {
15275
+ function ffmpegFailure(stage, result, timeoutMs, elementId) {
15185
15276
  const stderr = result.stderr ?? "";
15186
15277
  let reason = "ffmpeg_failed";
15187
15278
  let owner = "system";
@@ -15212,32 +15303,32 @@ function ffmpegFailure(stage, result, elementId) {
15212
15303
  elementId,
15213
15304
  subprocess: audioSubprocessEvidence(result),
15214
15305
  detail: boundedDetail(
15215
- result.error?.message ? `${formatFfmpegError(result.exitCode, stderr)}: ${result.error.message}` : formatFfmpegError(result.exitCode, stderr)
15306
+ result.error?.message ? `${describeFfmpegFailure(result, timeoutMs)}: ${result.error.message}` : describeFfmpegFailure(result, timeoutMs)
15216
15307
  )
15217
15308
  };
15218
15309
  }
15219
- var CURVED_SEGMENT_SAMPLES_PER_SEC = 30;
15220
- function volumeLaneKeyframes(automation, trackStart, duration) {
15310
+ function volumeLaneKeyframes(automation, trackStart, duration, gain, gainAhead = false) {
15221
15311
  const lane = automation.lanes.find((l) => l.target === VOLUME_TARGET);
15222
15312
  if (!lane || lane.points.length === 0) return null;
15313
+ const points = gainedVolumePoints(lane, gain, gainAhead);
15223
15314
  const out = [];
15224
15315
  const push = (t, v) => {
15225
15316
  out.push({ time: trackStart + t, volume: v });
15226
15317
  };
15227
- const first = lane.points[0];
15318
+ const first = points[0];
15228
15319
  if (first.t > 0) push(0, first.v);
15229
- for (let i = 0; i < lane.points.length; i += 1) {
15230
- const a = lane.points[i];
15320
+ for (let i = 0; i < points.length; i += 1) {
15321
+ const a = points[i];
15231
15322
  push(a.t, a.v);
15232
- const b = lane.points[i + 1];
15233
- if (!b || !a.curve) continue;
15234
- const steps = Math.max(2, Math.ceil((b.t - a.t) * CURVED_SEGMENT_SAMPLES_PER_SEC));
15323
+ const b = points[i + 1];
15324
+ if (!b || !isBentSegment(a)) continue;
15325
+ const steps = volumeSegmentSampleCount(b.t - a.t);
15235
15326
  for (let k = 1; k < steps; k += 1) {
15236
15327
  const t = a.t + (b.t - a.t) * k / steps;
15237
- push(t, sampleAutomationLane(lane, t));
15328
+ push(t, gainedVolume(sampleAutomationLane(lane, t), gain, gainAhead));
15238
15329
  }
15239
15330
  }
15240
- const last = lane.points[lane.points.length - 1];
15331
+ const last = points[points.length - 1];
15241
15332
  if (duration > last.t) push(duration, last.v);
15242
15333
  return out;
15243
15334
  }
@@ -15354,7 +15445,7 @@ async function extractAudioSegment(srcPath, outputPath, stage, mediaStart, durat
15354
15445
  failure: failure2
15355
15446
  };
15356
15447
  }
15357
- let failure = result.success ? void 0 : ffmpegFailure(stage, result);
15448
+ let failure = result.success ? void 0 : ffmpegFailure(stage, result, ffmpegProcessTimeout);
15358
15449
  const probedLinkFailedToRead = readsUrl && (failure?.reason === "invalid_media" || failure?.reason === "ffmpeg_failed");
15359
15450
  if (failure && probedLinkFailedToRead) {
15360
15451
  failure = { ...failure, reason: "download_failed", owner: "system", retryable: true };
@@ -15400,7 +15491,7 @@ async function generateSilence(outputPath, duration, signal, config) {
15400
15491
  failure: failure2
15401
15492
  };
15402
15493
  }
15403
- const failure = !result.success ? ffmpegFailure("silence", result) : void 0;
15494
+ const failure = !result.success ? ffmpegFailure("silence", result, ffmpegProcessTimeout) : void 0;
15404
15495
  return {
15405
15496
  success: result.success,
15406
15497
  outputPath,
@@ -15509,7 +15600,7 @@ async function mixAudioTracks(tracks, outputPath, totalDuration, signal, config)
15509
15600
  };
15510
15601
  }
15511
15602
  if (!result.success) {
15512
- const failure = ffmpegFailure("mix", result);
15603
+ const failure = ffmpegFailure("mix", result, ffmpegProcessTimeout);
15513
15604
  return {
15514
15605
  success: false,
15515
15606
  outputPath,
@@ -15534,14 +15625,15 @@ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal,
15534
15625
  const ffmpegProcessTimeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
15535
15626
  const outputDir = dirname7(outputPath);
15536
15627
  if (!existsSync11(outputDir)) mkdirSync7(outputDir, { recursive: true });
15537
- const buildInputFilters = (ignoreKeyframes) => memberTracks.map((track, i) => {
15628
+ const buildInputFilters = (ignoreKeyframes, padMembers) => memberTracks.map((track, i) => {
15538
15629
  const volumeFilter = buildVolumeExpression(track, ignoreKeyframes);
15539
- return buildTrackInputFilter(track, i, volumeFilter, totalDuration);
15630
+ return buildTrackInputFilter(track, i, volumeFilter, totalDuration, padMembers);
15540
15631
  });
15541
15632
  const mixInputs = memberTracks.map((_, i) => `[a${i}]`).join("");
15542
15633
  const runOnce = async (useNormalize2, ignoreKeyframes = false) => {
15543
- const inputFilters = buildInputFilters(ignoreKeyframes);
15544
- const mixFilter = useNormalize2 ? `${mixInputs}amix=inputs=${memberTracks.length}:duration=longest:dropout_transition=0:normalize=0[out]` : (
15634
+ const inputFilters = buildInputFilters(ignoreKeyframes, !useNormalize2);
15635
+ const padUnitySumOnceToFilm = `[mixed];[mixed]apad,asetpts=N/SR/TB,atrim=0:${formatFilterNumber(totalDuration)}`;
15636
+ const mixFilter = useNormalize2 ? `${mixInputs}amix=inputs=${memberTracks.length}:duration=longest:dropout_transition=0:normalize=0${padUnitySumOnceToFilm}[out]` : (
15545
15637
  // Every member is padded and trimmed to totalDuration above, and
15546
15638
  // dropout_transition=0 keeps all N inputs active for that whole span.
15547
15639
  // amix's default normalize therefore divides by exactly N throughout;
@@ -15610,7 +15702,7 @@ async function mixGroupMembers(memberTracks, outputPath, totalDuration, signal,
15610
15702
  }
15611
15703
  }
15612
15704
  if (signal?.aborted || !result.success) {
15613
- const failure = ffmpegFailure("mix", result);
15705
+ const failure = ffmpegFailure("mix", result, ffmpegProcessTimeout);
15614
15706
  return { success: false, error: failure.detail, failure };
15615
15707
  }
15616
15708
  return { success: true, degradedAutomation };
@@ -15628,7 +15720,11 @@ async function mixThroughFloatSum(tracks, masterChain, outputPath, workDir, tota
15628
15720
  timeout: config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout
15629
15721
  });
15630
15722
  if (!encoded.success) {
15631
- const failure = ffmpegFailure("mix", encoded);
15723
+ const failure = ffmpegFailure(
15724
+ "mix",
15725
+ encoded,
15726
+ config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout
15727
+ );
15632
15728
  return {
15633
15729
  success: false,
15634
15730
  outputPath,
@@ -15793,11 +15889,17 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15793
15889
  }
15794
15890
  audioSrcPath = trimmedPath;
15795
15891
  }
15796
- const automation = element.automation ? resolveAutomation(
15797
- parseAutomation(element.automation),
15798
- element.fxChain ? parseAudioFxChain(element.fxChain) : void 0
15892
+ const chain = element.fxChain ? parseAudioFxChain(element.fxChain) : void 0;
15893
+ const automation = element.automation ? resolveAutomation(parseAutomation(element.automation), chain) : null;
15894
+ const gainBeforeFx = chain !== void 0 && enabledAudioFxNodes(chain).length > 0;
15895
+ const laneKeyframes = automation ? volumeLaneKeyframes(
15896
+ automation,
15897
+ element.start,
15898
+ element.end - element.start,
15899
+ element.volume ?? 1,
15900
+ gainBeforeFx
15799
15901
  ) : null;
15800
- const laneKeyframes = automation ? volumeLaneKeyframes(automation, element.start, element.end - element.start) : null;
15902
+ const tweened = !laneKeyframes && (element.volumeKeyframes?.length ?? 0) > 0;
15801
15903
  const envelopeKeyframes = laneKeyframes ?? element.volumeKeyframes;
15802
15904
  const envelope = envelopeKeyframes && envelopeKeyframes.length > 0 ? {
15803
15905
  keyframes: envelopeKeyframes,
@@ -15806,8 +15908,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15806
15908
  } : null;
15807
15909
  let bakedEnvelope = false;
15808
15910
  let tailSeconds = 0;
15809
- if (element.fxChain) {
15810
- const chain = parseAudioFxChain(element.fxChain);
15911
+ if (chain) {
15811
15912
  tailSeconds = chainTailSeconds(chain, automation ?? void 0);
15812
15913
  const fxResult = await applyAudioFxChain(
15813
15914
  audioSrcPath,
@@ -15817,7 +15918,8 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15817
15918
  trackId: element.id,
15818
15919
  signal: effectiveSignal,
15819
15920
  ...automation ? { automation } : {},
15820
- ...envelope ? { envelope } : {}
15921
+ ...envelope ? { envelope } : {},
15922
+ ...gainBeforeFx ? { inputGain: clipGain(element.volume ?? 1, tweened) } : {}
15821
15923
  }
15822
15924
  );
15823
15925
  audioSrcPath = fxResult.path;
@@ -15839,7 +15941,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15839
15941
  mediaStart: element.mediaStart,
15840
15942
  duration: element.end - element.start,
15841
15943
  // Gain is already in the samples when baked, so mix at unity.
15842
- volume: bakedEnvelope ? 1 : element.volume ?? 1,
15944
+ volume: bakedEnvelope || gainBeforeFx && !tweened ? 1 : element.volume ?? 1,
15843
15945
  volumeKeyframes: bakedEnvelope ? void 0 : envelopeKeyframes ?? void 0,
15844
15946
  ...element.fadeIn ? { fadeIn: element.fadeIn } : {},
15845
15947
  ...element.fadeOut ? { fadeOut: element.fadeOut } : {},
@@ -15956,7 +16058,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
15956
16058
  parseAutomation(meta.automation),
15957
16059
  meta.fxChain ? parseAudioFxChain(meta.fxChain) : void 0
15958
16060
  ) : null;
15959
- const laneKeyframes = automation ? volumeLaneKeyframes(automation, 0, totalDuration) : null;
16061
+ const laneKeyframes = automation ? volumeLaneKeyframes(automation, 0, totalDuration, meta.volume) : null;
15960
16062
  const envelope = laneKeyframes && laneKeyframes.length > 0 ? { keyframes: laneKeyframes, trackStart: 0, baseVolume: meta.volume } : null;
15961
16063
  let groupSrcPath = groupWavPath;
15962
16064
  let bakedEnvelope = false;
@@ -17241,6 +17343,7 @@ export {
17241
17343
  assertConfiguredFfmpegBinariesExist,
17242
17344
  cloneCaptureWarning,
17243
17345
  cloneCaptureWarnings,
17346
+ installMediaRenderIdBridge,
17244
17347
  createChromeMemorySampler,
17245
17348
  CaptureFailure,
17246
17349
  classifyCaptureFailure,
@@ -17269,6 +17372,7 @@ export {
17269
17372
  getCompositionDuration,
17270
17373
  percentileOf,
17271
17374
  getCapturePerfSummary,
17375
+ frameSequencePath,
17272
17376
  ManagedChildProcess,
17273
17377
  trackChildProcess,
17274
17378
  killTrackedProcesses,