hyperframes 0.8.144 → 0.8.146

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